mirror of
https://github.com/buhman/nds.git
synced 2025-06-18 14:35:38 -04:00
examples: add cube
This commit is contained in:
parent
fb624b986b
commit
df31f45fd1
1
Makefile
1
Makefile
@ -18,6 +18,7 @@ DEFAULT = header.o arm7/arm7.bin.o
|
||||
triangle.elf: $(DEFAULT) arm9/triangle.bin.o
|
||||
triangle_rotating.elf: $(DEFAULT) arm9/triangle_rotating.bin.o
|
||||
texture.elf: $(DEFAULT) arm9/texture.bin.o
|
||||
cube.elf: $(DEFAULT) arm9/cube.bin.o
|
||||
|
||||
TARGET = arm-none-eabi-
|
||||
AARCH = -march=armv4t -mlittle-endian
|
||||
|
@ -22,7 +22,9 @@ triangle_rotating.elf: start.o examples/triangle_rotating.o ../math/cos_table_fp
|
||||
|
||||
texture.elf: start.o examples/texture.o ../res/hotaru_futaba.data.o ../res/hotaru_futaba.data.pal.o
|
||||
|
||||
CFLAGS += -I../include -I../math
|
||||
cube.elf: start.o examples/cube.o ../math/cos_table_fp12.o ../math/cos.o
|
||||
|
||||
CFLAGS += -I../include -I../
|
||||
|
||||
include arm9.mk
|
||||
include ../common.mk
|
||||
|
193
arm9/examples/cube.c
Normal file
193
arm9/examples/cube.c
Normal file
@ -0,0 +1,193 @@
|
||||
#include "io_registers.h"
|
||||
#include "bits.h"
|
||||
|
||||
#include "models/cube.h"
|
||||
#include "math/math.h"
|
||||
|
||||
static const uint16_t face_colors[6] = {
|
||||
COLOR__blue(31),
|
||||
COLOR__red(31),
|
||||
COLOR__green(31),
|
||||
COLOR__red(31) | COLOR__green(31),
|
||||
COLOR__red(31) | COLOR__blue(31),
|
||||
COLOR__green(31) | COLOR__blue(31),
|
||||
};
|
||||
|
||||
void main()
|
||||
{
|
||||
// power control
|
||||
io_registers.a.POWCNT = 0
|
||||
| POWCNT__lcd_output_destination__a_to_upper__b_to_lower
|
||||
| POWCNT__geometry_engine__enable
|
||||
| POWCNT__rendering_engine__enable
|
||||
| POWCNT__lcd__enable;
|
||||
|
||||
// enable bg0 and 3d graphics
|
||||
io_registers.a.DISPCNT = 0
|
||||
| DISPCNT__display_mode__graphics_display
|
||||
| DISPCNT__bg0__enable
|
||||
| DISPCNT__display_selection_for_bg0__3d_graphics
|
||||
;
|
||||
|
||||
// disable all 3d effects
|
||||
io_registers.a.DISP3DCNT = 0
|
||||
| DISP3DCNT__clear_image__disable
|
||||
| DISP3DCNT__fog_master__disable
|
||||
| DISP3DCNT__edge_marking__disable
|
||||
| DISP3DCNT__anti_aliasing__disable
|
||||
| DISP3DCNT__alpha_blending__disable
|
||||
| DISP3DCNT__alpha_test__disable
|
||||
| DISP3DCNT__texture_mapping__disable;
|
||||
|
||||
// clear matrix stack status
|
||||
io_registers.a.GXSTAT |= GXSTAT__matrix_stack_status__overflow_or_underflow;
|
||||
|
||||
// load identity matrices
|
||||
io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__projection;
|
||||
io_registers.a.MTX_IDENTITY = 0;
|
||||
// scale everything by 1/2
|
||||
io_registers.a.MTX_SCALE = (1 << 12) / 2;
|
||||
io_registers.a.MTX_SCALE = (1 << 12) / 2;
|
||||
io_registers.a.MTX_SCALE = (1 << 12) / 2;
|
||||
// scale the x-axis by the ratio of the display height by the display width.
|
||||
io_registers.a.MTX_SCALE = (192 << 12) / 256;
|
||||
io_registers.a.MTX_SCALE = 1 << 12;
|
||||
io_registers.a.MTX_SCALE = 1 << 12;
|
||||
|
||||
io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__position;
|
||||
io_registers.a.MTX_IDENTITY = 0;
|
||||
|
||||
io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__position_and_vector;
|
||||
io_registers.a.MTX_IDENTITY = 0;
|
||||
|
||||
// set the 3d clear color to a dark red
|
||||
io_registers.a.CLEAR_COLOR = 0
|
||||
| CLEAR_COLOR__clear_polygon_id(31)
|
||||
| CLEAR_COLOR__alpha_value(31)
|
||||
| CLEAR_COLOR__blue(1)
|
||||
| CLEAR_COLOR__green(1)
|
||||
| CLEAR_COLOR__red(10);
|
||||
|
||||
// set the depth buffer clear value to the maximum value
|
||||
io_registers.a.CLEAR_DEPTH = CLEAR_DEPTH__value(0x7fff);
|
||||
|
||||
// the following polygons are fully opaque and are not
|
||||
// backface-culled
|
||||
io_registers.a.POLYGON_ATTR = 0
|
||||
| POLYGON_ATTR__alpha_value(31)
|
||||
| POLYGON_ATTR__render_front_surface__enable
|
||||
| POLYGON_ATTR__render_back_surface__enable;
|
||||
|
||||
// the 3d viewport is the entire display area
|
||||
io_registers.a.VIEWPORT = 0
|
||||
| VIEWPORT__y2(191)
|
||||
| VIEWPORT__x2(255)
|
||||
| VIEWPORT__y1(0)
|
||||
| VIEWPORT__x1(0);
|
||||
|
||||
// degrees
|
||||
int theta = 0;
|
||||
|
||||
while (1) {
|
||||
// calculate sin/cos for 2d rotation; signed fp20.12 result
|
||||
int cos = cos_fp12(theta);
|
||||
int sin = sin_fp12(theta);
|
||||
|
||||
int cos2 = cos_fp12(theta >> 1);
|
||||
int sin2 = sin_fp12(theta >> 1);
|
||||
|
||||
io_registers.a.MTX_MODE = MTX_MODE__matrix_mode__position;
|
||||
// reset position matrix
|
||||
io_registers.a.MTX_IDENTITY = 0;
|
||||
|
||||
// multiply by a z-axis rotation
|
||||
io_registers.a.MTX_MULT_3X3 = cos;
|
||||
io_registers.a.MTX_MULT_3X3 = -sin;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
|
||||
io_registers.a.MTX_MULT_3X3 = sin;
|
||||
io_registers.a.MTX_MULT_3X3 = cos;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
io_registers.a.MTX_MULT_3X3 = 1 << 12;
|
||||
|
||||
// multiply by a y-axis rotation
|
||||
io_registers.a.MTX_MULT_3X3 = cos2;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
io_registers.a.MTX_MULT_3X3 = sin2;
|
||||
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
io_registers.a.MTX_MULT_3X3 = 1 << 12;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
|
||||
io_registers.a.MTX_MULT_3X3 = -sin2;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
io_registers.a.MTX_MULT_3X3 = cos2;
|
||||
|
||||
// multiply by a x-axis rotation
|
||||
io_registers.a.MTX_MULT_3X3 = cos2;
|
||||
io_registers.a.MTX_MULT_3X3 = -sin2;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
|
||||
io_registers.a.MTX_MULT_3X3 = sin2;
|
||||
io_registers.a.MTX_MULT_3X3 = cos2;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
io_registers.a.MTX_MULT_3X3 = 0;
|
||||
io_registers.a.MTX_MULT_3X3 = 1 << 12;
|
||||
|
||||
// the following vertices are a quadrilateral
|
||||
io_registers.a.BEGIN_VTXS = BEGIN_VTXS__type__quadrilateral;
|
||||
|
||||
// cube faces
|
||||
for (int i = 0; i < 6; i++) {
|
||||
io_registers.a.COLOR = face_colors[i];
|
||||
|
||||
struct vertex_position * a = &cube_positions[cube_faces[i].a.position];
|
||||
io_registers.a.VTX_10 = 0
|
||||
| VTX_10__z_coordinate(a->z)
|
||||
| VTX_10__y_coordinate(a->y)
|
||||
| VTX_10__x_coordinate(a->x);
|
||||
|
||||
struct vertex_position * b = &cube_positions[cube_faces[i].b.position];
|
||||
io_registers.a.VTX_10 = 0
|
||||
| VTX_10__z_coordinate(b->z)
|
||||
| VTX_10__y_coordinate(b->y)
|
||||
| VTX_10__x_coordinate(b->x);
|
||||
|
||||
struct vertex_position * c = &cube_positions[cube_faces[i].c.position];
|
||||
io_registers.a.VTX_10 = 0
|
||||
| VTX_10__z_coordinate(c->z)
|
||||
| VTX_10__y_coordinate(c->y)
|
||||
| VTX_10__x_coordinate(c->x);
|
||||
|
||||
struct vertex_position * d = &cube_positions[cube_faces[i].d.position];
|
||||
io_registers.a.VTX_10 = 0
|
||||
| VTX_10__z_coordinate(d->z)
|
||||
| VTX_10__y_coordinate(d->y)
|
||||
| VTX_10__x_coordinate(d->x);
|
||||
}
|
||||
|
||||
// end of the quadrilateral
|
||||
io_registers.a.END_VTXS = 0;
|
||||
|
||||
// wait for the end of the current frame
|
||||
while (io_registers.a.VCOUNT != 262);
|
||||
while (io_registers.a.VCOUNT == 262);
|
||||
|
||||
// wait for the geometry engine
|
||||
while (io_registers.a.GXSTAT & GXSTAT__geometry_engine_busy);
|
||||
|
||||
// swap buffers
|
||||
io_registers.a.SWAP_BUFFERS = 0;
|
||||
|
||||
// increment theta once per frame
|
||||
theta += 1;
|
||||
if (theta >= 360 * 2) {
|
||||
theta = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
#include "io_registers.h"
|
||||
#include "bits.h"
|
||||
|
||||
#include "../res/hotaru_futaba.data.h"
|
||||
#include "../res/hotaru_futaba.data.pal.h"
|
||||
#include "res/hotaru_futaba.data.h"
|
||||
#include "res/hotaru_futaba.data.pal.h"
|
||||
|
||||
static inline uint16_t rgb565(const uint8_t * buf)
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
#include "io_registers.h"
|
||||
#include "bits.h"
|
||||
|
||||
#include "math.h"
|
||||
#include "math/math.h"
|
||||
|
||||
void main()
|
||||
{
|
||||
|
71
models/cube.h
Normal file
71
models/cube.h
Normal file
@ -0,0 +1,71 @@
|
||||
#include "model.h"
|
||||
|
||||
// .6 fixed-point
|
||||
struct vertex_position cube_positions[] = {
|
||||
{64, 64, -64},
|
||||
{64, -64, -64},
|
||||
{64, 64, 64},
|
||||
{64, -64, 64},
|
||||
{-64, 64, -64},
|
||||
{-64, -64, -64},
|
||||
{-64, 64, 64},
|
||||
{-64, -64, 64},
|
||||
};
|
||||
|
||||
// .15 fixed-point
|
||||
struct vertex_texture cube_textures[] = {
|
||||
{32768, 32768},
|
||||
{0, 32768},
|
||||
{0, 0},
|
||||
{32768, 0},
|
||||
};
|
||||
|
||||
// .9 fixed-point
|
||||
struct vertex_normal cube_normals[] = {
|
||||
{0, 512, 0},
|
||||
{0, 0, 512},
|
||||
{-512, 0, 0},
|
||||
{0, -512, 0},
|
||||
{512, 0, 0},
|
||||
{0, 0, -512},
|
||||
};
|
||||
|
||||
struct face cube_faces[] = {
|
||||
{
|
||||
{0, 0, 0},
|
||||
{4, 1, 0},
|
||||
{6, 2, 0},
|
||||
{2, 3, 0},
|
||||
},
|
||||
{
|
||||
{3, 3, 1},
|
||||
{2, 0, 1},
|
||||
{6, 1, 1},
|
||||
{7, 2, 1},
|
||||
},
|
||||
{
|
||||
{7, 2, 2},
|
||||
{6, 1, 2},
|
||||
{4, 0, 2},
|
||||
{5, 3, 2},
|
||||
},
|
||||
{
|
||||
{5, 1, 3},
|
||||
{1, 0, 3},
|
||||
{3, 3, 3},
|
||||
{7, 2, 3},
|
||||
},
|
||||
{
|
||||
{1, 3, 4},
|
||||
{0, 0, 4},
|
||||
{2, 1, 4},
|
||||
{3, 2, 4},
|
||||
},
|
||||
{
|
||||
{5, 2, 5},
|
||||
{4, 1, 5},
|
||||
{0, 0, 5},
|
||||
{1, 3, 5},
|
||||
},
|
||||
};
|
||||
|
28
models/cube.obj
Normal file
28
models/cube.obj
Normal file
@ -0,0 +1,28 @@
|
||||
# Blender 4.1.1
|
||||
# www.blender.org
|
||||
o Cube
|
||||
v 1.000000 1.000000 -1.000000
|
||||
v 1.000000 -1.000000 -1.000000
|
||||
v 1.000000 1.000000 1.000000
|
||||
v 1.000000 -1.000000 1.000000
|
||||
v -1.000000 1.000000 -1.000000
|
||||
v -1.000000 -1.000000 -1.000000
|
||||
v -1.000000 1.000000 1.000000
|
||||
v -1.000000 -1.000000 1.000000
|
||||
vn -0.0000 1.0000 -0.0000
|
||||
vn -0.0000 -0.0000 1.0000
|
||||
vn -1.0000 -0.0000 -0.0000
|
||||
vn -0.0000 -1.0000 -0.0000
|
||||
vn 1.0000 -0.0000 -0.0000
|
||||
vn -0.0000 -0.0000 -1.0000
|
||||
vt 1.000000 1.000000
|
||||
vt 0.000000 1.000000
|
||||
vt 0.000000 0.000000
|
||||
vt 1.000000 0.000000
|
||||
s 0
|
||||
f 1/1/1 5/2/1 7/3/1 3/4/1
|
||||
f 4/4/2 3/1/2 7/2/2 8/3/2
|
||||
f 8/3/3 7/2/3 5/1/3 6/4/3
|
||||
f 6/2/4 2/1/4 4/4/4 8/3/4
|
||||
f 2/4/5 1/1/5 3/2/5 4/3/5
|
||||
f 6/3/6 5/2/6 1/1/6 2/4/6
|
1
models/generate.py
Symbolic link
1
models/generate.py
Symbolic link
@ -0,0 +1 @@
|
||||
../registers/generate.py
|
33
models/model.h
Normal file
33
models/model.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
struct index_ptn {
|
||||
uint16_t position;
|
||||
uint16_t texture;
|
||||
uint16_t normal;
|
||||
};
|
||||
|
||||
struct face {
|
||||
struct index_ptn a;
|
||||
struct index_ptn b;
|
||||
struct index_ptn c;
|
||||
struct index_ptn d;
|
||||
};
|
||||
|
||||
struct vertex_position { // signed 4.6 fixed point
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t z;
|
||||
};
|
||||
|
||||
struct vertex_normal { // s.9 fixed point
|
||||
uint16_t x;
|
||||
uint16_t y;
|
||||
uint16_t z;
|
||||
};
|
||||
|
||||
struct vertex_texture { // s.15 fixed point
|
||||
uint16_t u;
|
||||
uint16_t v;
|
||||
};
|
112
models/parse_obj_fixed_point.py
Normal file
112
models/parse_obj_fixed_point.py
Normal file
@ -0,0 +1,112 @@
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
import string
|
||||
|
||||
@dataclass
|
||||
class FixedPoint:
|
||||
negative: bool
|
||||
integer: int
|
||||
fraction: int
|
||||
point: int
|
||||
|
||||
@dataclass
|
||||
class VertexPosition:
|
||||
x: FixedPoint
|
||||
y: FixedPoint
|
||||
z: FixedPoint
|
||||
|
||||
@dataclass
|
||||
class VertexNormal:
|
||||
x: FixedPoint
|
||||
y: FixedPoint
|
||||
z: FixedPoint
|
||||
|
||||
@dataclass
|
||||
class VertexTexture:
|
||||
u: FixedPoint
|
||||
v: FixedPoint
|
||||
|
||||
@dataclass
|
||||
class IndexVTN:
|
||||
vertex_position: int
|
||||
vertex_texture: int
|
||||
vertex_normal: int
|
||||
|
||||
@dataclass
|
||||
class Face:
|
||||
a: IndexVTN
|
||||
b: IndexVTN
|
||||
c: IndexVTN
|
||||
d: IndexVTN
|
||||
|
||||
def parse_fixed_point(s):
|
||||
negative = s.startswith('-')
|
||||
s = s.removeprefix('-')
|
||||
integer, fraction = s.split('.')
|
||||
assert all(c in string.digits for c in integer), integer
|
||||
assert all(c in string.digits for c in fraction), fraction
|
||||
assert len(integer) > 0 and len(fraction) > 0, s
|
||||
return FixedPoint(
|
||||
negative,
|
||||
int(integer),
|
||||
int(fraction),
|
||||
10 ** len(fraction)
|
||||
)
|
||||
|
||||
def parse_fixed_point_vector(args, n):
|
||||
split = args.split()
|
||||
assert len(split) == n, (n, split)
|
||||
return tuple(map(parse_fixed_point, split))
|
||||
|
||||
def parse_vertex_position(args):
|
||||
coordinates = parse_fixed_point_vector(args, 3)
|
||||
yield VertexPosition(*coordinates)
|
||||
|
||||
def parse_vertex_normal(args):
|
||||
coordinates = parse_fixed_point_vector(args, 3)
|
||||
yield VertexNormal(*coordinates)
|
||||
|
||||
def parse_vertex_texture(args):
|
||||
coordinates = parse_fixed_point_vector(args, 2)
|
||||
yield VertexTexture(*coordinates)
|
||||
|
||||
def int_minus_one(s):
|
||||
n = int(s) - 1
|
||||
assert n >= 0
|
||||
return n
|
||||
|
||||
def parse_vertex_indices(args):
|
||||
indices = args.split('/')
|
||||
assert len(indices) == 3, indices
|
||||
return IndexVTN(*map(int_minus_one, indices))
|
||||
|
||||
def parse_face(args):
|
||||
vertices = args.split()
|
||||
assert len(vertices) == 4, vertices
|
||||
yield Face(*map(parse_vertex_indices, vertices))
|
||||
|
||||
def parse_obj_line(line):
|
||||
prefixes = [
|
||||
('v ', parse_vertex_position),
|
||||
('vn ', parse_vertex_normal),
|
||||
('vt ', parse_vertex_texture),
|
||||
('f ', parse_face)
|
||||
]
|
||||
for prefix, parser in prefixes:
|
||||
if line.startswith(prefix):
|
||||
args = line.removeprefix(prefix)
|
||||
yield from parser(args)
|
||||
|
||||
def group_by_type(l):
|
||||
grouped = defaultdict(list)
|
||||
for i in l:
|
||||
grouped[type(i)].append(i)
|
||||
return dict(grouped)
|
||||
|
||||
def parse_obj_file(buf):
|
||||
return group_by_type((
|
||||
parsed
|
||||
for line in buf.strip().split('\n')
|
||||
for parsed in parse_obj_line(line)
|
||||
if not line.startswith('#')
|
||||
))
|
91
models/render_obj_fixed_point_c_source.py
Normal file
91
models/render_obj_fixed_point_c_source.py
Normal file
@ -0,0 +1,91 @@
|
||||
from dataclasses import astuple
|
||||
import sys
|
||||
from generate import renderer
|
||||
|
||||
from parse_obj_fixed_point import parse_obj_file
|
||||
from parse_obj_fixed_point import VertexPosition
|
||||
from parse_obj_fixed_point import VertexNormal
|
||||
from parse_obj_fixed_point import VertexTexture
|
||||
from parse_obj_fixed_point import Face
|
||||
|
||||
vertex_position_fraction_bits = 6 # 4.6
|
||||
vertex_normal_fraction_bits = 9 # s.9
|
||||
vertex_texture_fraction_bits = 15 # s.15
|
||||
|
||||
def convert_fixed_point(fp, fraction_bits):
|
||||
point = 2 ** fraction_bits
|
||||
sign = -1 if fp.negative else 1
|
||||
n0 = sign * (fp.integer * fp.point + fp.fraction)
|
||||
n1 = n0 * point // fp.point
|
||||
return n1
|
||||
|
||||
def render_index_vtn(index_vtn):
|
||||
s = ", ".join(map(str, index_vtn))
|
||||
yield f"{{{s}}},"
|
||||
|
||||
def render_face(face):
|
||||
yield "{"
|
||||
for index_vtn in astuple(face):
|
||||
yield from render_index_vtn(index_vtn)
|
||||
yield "},"
|
||||
|
||||
def render_faces(prefix, faces):
|
||||
yield f"struct face {prefix}_faces[] = {{"
|
||||
for face in faces:
|
||||
yield from render_face(face)
|
||||
yield "};"
|
||||
|
||||
def xyz(vec):
|
||||
return (vec.x, vec.y, vec.z)
|
||||
|
||||
def uv(vec):
|
||||
return (vec.u, vec.v)
|
||||
|
||||
def render_vertex(vertex_tuple, fraction_bits):
|
||||
s = ", ".join(
|
||||
str(convert_fixed_point(fp, fraction_bits))
|
||||
for fp in vertex_tuple
|
||||
)
|
||||
yield f"{{{s}}},"
|
||||
|
||||
def render_vertices(prefix, name, vertices, fraction_bits):
|
||||
yield f"// .{fraction_bits} fixed-point"
|
||||
yield f"struct vertex_{name} {prefix}_{name}s[] = {{"
|
||||
for vertex in vertices:
|
||||
yield from render_vertex(vertex, fraction_bits)
|
||||
yield "};"
|
||||
|
||||
def render_vertex_positions(prefix, vertex_positions):
|
||||
yield from render_vertices(prefix,
|
||||
"position",
|
||||
map(xyz, vertex_positions),
|
||||
vertex_position_fraction_bits)
|
||||
|
||||
def render_vertex_normals(prefix, vertex_normals):
|
||||
yield from render_vertices(prefix,
|
||||
"normal",
|
||||
map(xyz, vertex_normals),
|
||||
vertex_normal_fraction_bits)
|
||||
|
||||
def render_vertex_texture(prefix, vertex_textures):
|
||||
yield from render_vertices(prefix,
|
||||
"texture",
|
||||
map(uv, vertex_textures),
|
||||
vertex_texture_fraction_bits)
|
||||
|
||||
def render_header():
|
||||
yield '#include "model.h"'
|
||||
yield ""
|
||||
|
||||
with open(sys.argv[1]) as f:
|
||||
buf = f.read()
|
||||
|
||||
prefix = sys.argv[2]
|
||||
d = parse_obj_file(buf)
|
||||
render, out = renderer()
|
||||
render(render_header())
|
||||
render(render_vertex_positions(prefix, d[VertexPosition]))
|
||||
render(render_vertex_texture(prefix, d[VertexTexture]))
|
||||
render(render_vertex_normals(prefix, d[VertexNormal]))
|
||||
render(render_faces(prefix, d[Face]))
|
||||
sys.stdout.write(out.getvalue())
|
Loading…
Reference in New Issue
Block a user