From c815dc13601920666a486b3268fb2ce1932fa1a3 Mon Sep 17 00:00:00 2001 From: Ryan Fleury Date: Thu, 24 Sep 2026 20:48:50 -0700 Subject: [PATCH] initial upload --- .gitignore | 12 + build.bat | 108 + code/base/base.c | 1046 ++++++ code/base/base.h | 676 ++++ code/base/base_inc.c | 9 + code/base/base_inc.h | 14 + code/n64/base/n64_base.c | 8 + code/n64/base/n64_base.h | 7 + code/n64/n64.c | 4869 +++++++++++++++++++++++++++ code/n64/n64.h | 1158 +++++++ code/n64/n64.ld | 48 + code/n64/render/n64_render.c | 333 ++ code/n64/render/n64_render.h | 37 + code/n64_test/n64_test.c | 475 +++ code/render/render.c | 2 + code/render/render.h | 60 + code/render/render_inc.c | 9 + code/render/render_inc.h | 14 + code/third_party/n64tool/crc32.c | 72 + code/third_party/n64tool/ipl3.h | 540 +++ code/third_party/n64tool/n64tool.c | 842 +++++ code/third_party/n64tool/polyfill.h | 412 +++ project.4coder | 81 + 23 files changed, 10832 insertions(+) create mode 100644 .gitignore create mode 100644 build.bat create mode 100644 code/base/base.c create mode 100644 code/base/base.h create mode 100644 code/base/base_inc.c create mode 100644 code/base/base_inc.h create mode 100644 code/n64/base/n64_base.c create mode 100644 code/n64/base/n64_base.h create mode 100644 code/n64/n64.c create mode 100644 code/n64/n64.h create mode 100644 code/n64/n64.ld create mode 100644 code/n64/render/n64_render.c create mode 100644 code/n64/render/n64_render.h create mode 100644 code/n64_test/n64_test.c create mode 100644 code/render/render.c create mode 100644 code/render/render.h create mode 100644 code/render/render_inc.c create mode 100644 code/render/render_inc.h create mode 100644 code/third_party/n64tool/crc32.c create mode 100644 code/third_party/n64tool/ipl3.h create mode 100644 code/third_party/n64tool/n64tool.c create mode 100644 code/third_party/n64tool/polyfill.h create mode 100644 project.4coder diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39adfb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +build/* +local/* +*.exe +*.pdb +*.exp +*.lib +*.obj +*.dll +*.ilk +*.d +*.swp +*.rdbg diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..450295e --- /dev/null +++ b/build.bat @@ -0,0 +1,108 @@ +@echo off +setlocal +setlocal EnableDelayedExpansion +cd /D "%~dp0" + +:: --- Unpack Arguments ------------------------------------------------------- +for %%a in (%*) do set "%%a=1" +if not "%msvc%"=="1" if not "%clang%"=="1" set msvc=1 +if not "%release%"=="1" set debug=1 +if "%debug%"=="1" set release=0 && echo [debug] +if "%release%"=="1" set debug=0 && echo [release] +if "%msvc%"=="1" set clang=0 && echo [msvc] +if "%clang%"=="1" set msvc=0 && echo [clang] +if "%deploy%"=="1" echo [deploy] + +:: --- Unpack Command Line Build Mix-ins -------------------------------------- +set auto_compile_flags= +set auto_link_flags= +if "%telemetry%"=="1" set auto_compile_flags=%auto_compile_flags% -DBUILD_TELEMETRY=1 && echo [telemetry] +if "%asan%"=="1" set auto_compile_flags=%auto_compile_flags% -fsanitize=address && echo [asan] + +:: --- Mix Git Commit ID ------------------------------------------------------ +git rev-parse --is-inside-work-tree >nul 2>&1 && set in_git_tree=1 +if "%in_git_tree%"=="1" ( + for /f %%i in ('call git describe --always --dirty') do set auto_compile_flags=%auto_compile_flags% -DBUILD_GIT_HASH=\"%%i\" + for /f %%i in ('call git rev-parse HEAD') do set auto_compile_flags=%auto_compile_flags% -DBUILD_GIT_HASH_FULL=\"%%i\" +) + +:: --- [MSVC] Compile/Link Line Definitions ----------------------------------- +set cl_common= /I..\code\ /I..\local\ /nologo /FC /Z7 +set cl_debug= call cl /Od /DBUILD_DEBUG=1 %cl_common% %auto_compile_flags% +set cl_release= call cl /O2 /DBUILD_DEBUG=1 %cl_common% %auto_compile_flags% +set cl_link= /link /MANIFEST:EMBED /INCREMENTAL:NO /pdbaltpath:%%%%_PDB%%%% %auto_link_flags% +set cl_out= /out: + +:: --- [Clang] Compile/Link Line Definitions ---------------------------------- +set clang_common= -g -I..\code\ -I..\local\ -fdiagnostics-absolute-paths -Wall -Wno-unknown-warning-option -Wno-missing-braces -Wno-unused-function -Wno-unused-parameter -Wno-writable-strings -Wno-missing-field-initializers -Wno-unused-value -Wno-unused-variable -Wno-unused-local-typedef -Wno-deprecated-register -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-single-bit-bitfield-constant-conversion -Wno-compare-distinct-pointer-types -Wno-initializer-overrides -Wno-incompatible-pointer-types-discards-qualifiers -ferror-limit=10000 +set clang_common_n64= --target=mips-unknown-elf -mcpu=mips3 -mabi=32 -EB -mno-abicalls -fno-pic -ffreestanding -nostdlib -fno-builtin -G 0 -fno-unroll-loops -DN64=1 +set clang_debug= call clang -O0 -DBUILD_DEBUG=1 %clang_common% %auto_compile_flags% +set clang_release= call clang -O2 -DBUILD_DEBUG=0 %clang_common% %auto_compile_flags% +set clang_debug_n64= call clang -O0 -DBUILD_DEBUG=1 %clang_common% %auto_compile_flags% %clang_common_n64% -c +set clang_release_n64= call clang -O2 -DBUILD_DEBUG=0 %clang_common% %auto_compile_flags% %clang_common_n64% -c +set clang_link= -fuse-ld=lld -Xlinker /MANIFEST:EMBED -Xlinker /pdbaltpath:%%%%_PDB%%%% %auto_link_flags% +set clang_out= -o + +:: --- [LLD] Link Line Definitions +set lld_link_common= +set lld_link_common_n64= -T "../code/n64/n64.ld" +set lld_link= call ld.lld %lld_link_common% %auto_link_flags% +set lld_link_n64= call ld.lld %lld_link_common% %lld_link_common_n64% %auto_link_flags% + +:: --- Choose Compile/Link Lines ---------------------------------------------- +if "%msvc%"=="1" set compile_debug=%cl_debug% +if "%msvc%"=="1" set compile_release=%cl_release% +if "%msvc%"=="1" set compile_link=%cl_link% +if "%msvc%"=="1" set out=%cl_out% +if "%clang%"=="1" set compile_debug=%clang_debug% +if "%clang%"=="1" set compile_release=%clang_release% +if "%clang%"=="1" set compile_link=%clang_link% +if "%clang%"=="1" set out=%clang_out% +if "%debug%"=="1" set compile=%compile_debug% +if "%release%"=="1" set compile=%compile_release% +if "%debug%"=="1" set compile_n64=%clang_debug_n64% +if "%release%"=="1" set compile_n64=%clang_release_n64% +set link_n64= %lld_link_n64% +set out_n64= %clang_out% +set romify_n64= n64tool -t "^!n64_rom_name^!" -o "^!n64_rom_name^!.z64" --align 256 "^!n64_rom_name^!.elf" + +:: --- Per-Build Settings ----------------------------------------------------- +set link_dll=-DLL +if "%msvc%"=="1" set only_compile=-c +if "%clang%"=="1" set only_compile=/c +if "%msvc%"=="1" set EHsc=/EHsc +if "%clang%"=="1" set EHsc= +if "%msvc%"=="1" set no_aslr=/DYNAMICBASE:NO +if "%clang%"=="1" set no_aslr=-Wl,/DYNAMICBASE:NO +if "%msvc%"=="1" set rc=call rc +if "%clang%"=="1" set rc=call llvm-rc + +:: --- Prep Directories ------------------------------------------------------- +if not exist build mkdir build +if not exist local mkdir local + +:: --- Build n64tool ---------------------------------------------------------- +pushd build +if "%n64tool%"=="1" if exist n64tool.exe del n64tool.exe +if not exist n64tool.exe ( + %clang_debug% ..\code\third_party\n64tool\n64tool.c %clang_link% %clang_out% n64tool.exe +) +popd + +:: --- Build Targets ---------------------------------------------------------- +pushd build +if "%n64_test%"=="1" ( + set didbuild=1 + set "n64_rom_name=n64_test" + %compile_n64% ..\code\n64_test\n64_test.c + %link_n64% n64_test.o %out_n64% !n64_rom_name!.elf + %romify_n64% +) +popd + +:: --- Deploy To N64 ---------------------------------------------------------- +if "%deploy%"=="1" if not "%n64_rom_name%"=="" ( + pushd build + ..\tools\sc64deployer upload %n64_rom_name%.z64 + popd +) diff --git a/code/base/base.c b/code/base/base.c new file mode 100644 index 0000000..9712722 --- /dev/null +++ b/code/base/base.c @@ -0,0 +1,1046 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +//////////////////////////////// +//~ rjf: C Runtime Implementations + +void * +memset(void *buffer, int c, UAddr n) +{ + for(UAddr off = 0; off < n; off += 1) + { + ((U8 *)buffer)[off] = c; + } + return buffer; +} + +void * +memcpy(void *dst, void *src, UAddr n) +{ + for(UAddr off = 0; off < n; off += 1) + { + ((U8 *)dst)[off] = ((U8 *)src)[off]; + } + return dst; +} + +//////////////////////////////// +//~ rjf: Scalar Math Functions + +function F32 +SinF32(F32 turns) +{ + // NOTE(rjf): "normalized frequency" implementation from https://mooooo.ooo/chebyshev-sine-approximation/. + // + // this implementation computes sin(pi*x) - in our case, we want turns, so we + // just adjust x first. at some point, would be better to just compute the + // coefficients for sin(2*pi*x) rather than sin(pi*x), but I don't know how + // to do that, and so I am just letting this ride for now. + // + F32 x_unwrapped = turns * 2.f; + F32 x = ModF32(x_unwrapped + 1.f, 2.f) - 1.f; + F32 x2 = x*x; + F32 p = 0.000385937753182769f; + p += -0.006860187425683514f; p *= x2; + p += 0.0751872634325299f; p *= x2; + p += -0.5240361513980939f; p *= x2; + p += 2.0261194642649887f; p *= x2; + p += -3.1415926444234477f; + F32 result = (x-1.f) * (x+1.f) * p * x; + return result; +} + +//////////////////////////////// +//~ rjf: Vector Functions + +//- rjf: constructors + +function Vec2U32 +V2U32(U32 x, U32 y) +{ + Vec2U32 v = {x, y}; + return v; +} + +function Vec2F32 +V2F32(F32 x, F32 y) +{ + Vec2F32 v = {x, y}; + return v; +} + +function Vec3F32 +V3F32(F32 x, F32 y, F32 z) +{ + Vec3F32 v = {x, y, z}; + return v; +} + +function Vec4F32 +V4F32(F32 x, F32 y, F32 z, F32 w) +{ + Vec4F32 v = {x, y, z, w}; + return v; +} + +//- rjf: 2-vector ops + +function Vec2F32 +Add2F32(Vec2F32 l, Vec2F32 r) +{ + Vec2F32 result = {l.x+r.x, l.y+r.y}; + return result; +} + +function Vec2F32 +Sub2F32(Vec2F32 l, Vec2F32 r) +{ + Vec2F32 result = {l.x-r.x, l.y-r.y}; + return result; +} + +function Vec2F32 +Mul2F32(Vec2F32 l, Vec2F32 r) +{ + Vec2F32 result = {l.x*r.x, l.y*r.y}; + return result; +} + +function Vec2F32 +Div2F32(Vec2F32 l, Vec2F32 r) +{ + Vec2F32 result = {l.x/r.x, l.y/r.y}; + return result; +} + +function F32 +LengthSquared2F32(Vec2F32 v) +{ + F32 result = Dot2F32(v, v); + return result; +} + +function F32 +Length2F32(Vec2F32 v) +{ + F32 result_squared = LengthSquared2F32(v); + F32 result = SquareRootF32(result_squared); + return result; +} + +function Vec2F32 +Scale2F32(Vec2F32 v, F32 s) +{ + Vec2F32 v_scaled = {v.x*s, v.y*s}; + return v_scaled; +} + +function Vec2F32 +Normalize2F32(Vec2F32 v) +{ + F32 v_length = Length2F32(v); + Vec2F32 v_normalized = Scale2F32(v, 1.f / v_length); + return v_normalized; +} + +function F32 +Dot2F32(Vec2F32 l, Vec2F32 r) +{ + F32 result = (l.x*r.x + l.y*r.y); + return result; +} + +//- rjf: 3-vector ops + +function Vec3F32 +Add3F32(Vec3F32 l, Vec3F32 r) +{ + Vec3F32 result = {l.x+r.x, l.y+r.y, l.z+r.z}; + return result; +} + +function Vec3F32 +Sub3F32(Vec3F32 l, Vec3F32 r) +{ + Vec3F32 result = {l.x-r.x, l.y-r.y, l.z-r.z}; + return result; +} + +function Vec3F32 +Mul3F32(Vec3F32 l, Vec3F32 r) +{ + Vec3F32 result = {l.x*r.x, l.y*r.y, l.z*r.z}; + return result; +} + +function Vec3F32 +Div3F32(Vec3F32 l, Vec3F32 r) +{ + Vec3F32 result = {l.x/r.x, l.y/r.y, l.z/r.z}; + return result; +} + +function F32 +LengthSquared3F32(Vec3F32 v) +{ + F32 result = Dot3F32(v, v); + return result; +} + +function F32 +Length3F32(Vec3F32 v) +{ + F32 result_squared = LengthSquared3F32(v); + F32 result = SquareRootF32(result_squared); + return result; +} + +function Vec3F32 +Scale3F32(Vec3F32 v, F32 s) +{ + Vec3F32 v_scaled = {v.x*s, v.y*s, v.z*s}; + return v_scaled; +} + +function Vec3F32 +Normalize3F32(Vec3F32 v) +{ + F32 v_length = Length3F32(v); + Vec3F32 v_normalized = Scale3F32(v, v_length > 0 ? (1.f/v_length) : 0); + return v_normalized; +} + +function F32 +Dot3F32(Vec3F32 l, Vec3F32 r) +{ + F32 result = (l.x*r.x + l.y*r.y + l.z*r.z); + return result; +} + +function Vec3F32 +Cross3F32(Vec3F32 l, Vec3F32 r) +{ + Vec3F32 result = {l.y*r.z - l.z*r.y, l.z*r.x - l.x*r.z, l.x*r.y - l.y*r.x}; + return result; +} + +//- rjf: 4-vector ops + +function Vec4F32 +Add4F32(Vec4F32 l, Vec4F32 r) +{ + Vec4F32 result = {l.x+r.x, l.y+r.y, l.z+r.z, l.w+r.w}; + return result; +} + +function Vec4F32 +Sub4F32(Vec4F32 l, Vec4F32 r) +{ + Vec4F32 result = {l.x-r.x, l.y-r.y, l.z-r.z, l.w-r.w}; + return result; +} + +function Vec4F32 +Mul4F32(Vec4F32 l, Vec4F32 r) +{ + Vec4F32 result = {l.x*r.x, l.y*r.y, l.z*r.z, l.w*r.w}; + return result; +} + +function Vec4F32 +Div4F32(Vec4F32 l, Vec4F32 r) +{ + Vec4F32 result = {l.x/r.x, l.y/r.y, l.z/r.z, l.w/r.w}; + return result; +} + +function F32 +LengthSquared4F32(Vec4F32 v) +{ + F32 result = Dot4F32(v, v); + return result; +} + +function F32 +Length4F32(Vec4F32 v) +{ + F32 result_squared = LengthSquared4F32(v); + F32 result = SquareRootF32(result_squared); + return result; +} + +function Vec4F32 +Scale4F32(Vec4F32 v, F32 s) +{ + Vec4F32 v_scaled = {v.x*s, v.y*s, v.z*s}; + return v_scaled; +} + +function Vec4F32 +Normalize4F32(Vec4F32 v) +{ + F32 v_length = Length4F32(v); + Vec4F32 v_normalized = Scale4F32(v, 1.f / v_length); + return v_normalized; +} + +function F32 +Dot4F32(Vec4F32 l, Vec4F32 r) +{ + F32 result = l.x*r.x + l.y*r.y + l.z*r.z + l.w*r.w; + return result; +} + +function Vec4F32 +XForm4F32(Mat4x4F32 m, Vec4F32 v) +{ + Vec4F32 result; + for(int i = 0; i < 4; i += 1) + { + result.v[i] = (v.v[0]*m.v[0][i] + + v.v[1]*m.v[1][i] + + v.v[2]*m.v[2][i] + + v.v[3]*m.v[3][i]); + } + return result; +} + +//////////////////////////////// +//~ rjf: Matrix Functions + +function Mat3x3F32 +MakeMat3x3F32(F32 d) +{ + Mat3x3F32 result = + { + { + {d, 0, 0}, + {0, d, 0}, + {0, 0, d}, + }, + }; + return result; +} + +function Mat3x3F32 +MakeTranslate3x3F32(Vec2F32 translation) +{ + Mat3x3F32 result = MakeMat3x3F32(1.f); + result.v[2][0] = translation.x; + result.v[2][1] = translation.y; + return result; +} + +function Mat3x3F32 +MakeScale3x3F32(Vec2F32 scale) +{ + Mat3x3F32 result = MakeMat3x3F32(1.f); + result.v[0][0] = scale.x; + result.v[1][1] = scale.y; + return result; +} + +function Mat3x3F32 +MakeRotate3x3F32(F32 turns) +{ + Mat3x3F32 result = MakeMat3x3F32(1.f); + result.v[0][0] = +CosF32(turns); + result.v[1][0] = -SinF32(turns); + result.v[0][1] = +SinF32(turns); + result.v[1][1] = +CosF32(turns); + return result; +} + +function Mat4x4F32 +MakeMat4x4F32(F32 d) +{ + Mat4x4F32 result = + { + { + {d, 0, 0, 0}, + {0, d, 0, 0}, + {0, 0, d, 0}, + {0, 0, 0, d}, + } + }; + return result; +} + +function Mat4x4F32 +MakeTranslate4x4F32(Vec3F32 translation) +{ + Mat4x4F32 result = MakeMat4x4F32(1.f); + result.v[3][0] = translation.x; + result.v[3][1] = translation.y; + result.v[3][2] = translation.z; + return result; +} + +function Mat4x4F32 +MakeScale4x4F32(Vec3F32 scale) +{ + Mat4x4F32 result = MakeMat4x4F32(1.f); + result.v[0][0] = scale.x; + result.v[1][1] = scale.y; + result.v[2][2] = scale.z; + return result; +} + +function Mat4x4F32 +MakePerspective4x4F32(F32 fov, F32 aspect_ratio, F32 near_z, F32 far_z) +{ + Mat4x4F32 result = MakeMat4x4F32(1.f); + F32 tan_theta_over_2 = TanF32(fov / 2); + result.v[0][0] = 1.f / tan_theta_over_2; + result.v[1][1] = aspect_ratio / tan_theta_over_2; + result.v[2][3] = 1.f; + result.v[2][2] = -(near_z + far_z) / (near_z - far_z); + result.v[3][2] = (2.f * near_z * far_z) / (near_z - far_z); + result.v[3][3] = 0.f; + return result; +} + +function Mat4x4F32 +MakeOrthographic4x4F32(F32 left, F32 right, F32 bottom, F32 top, F32 near_z, F32 far_z) +{ + Mat4x4F32 result = MakeMat4x4F32(1.f); + + result.v[0][0] = 2.f / (right - left); + result.v[1][1] = 2.f / (top - bottom); + result.v[2][2] = 2.f / (far_z - near_z); + result.v[3][3] = 1.f; + + result.v[3][0] = (left + right) / (left - right); + result.v[3][1] = (bottom + top) / (bottom - top); + result.v[3][2] = (near_z + far_z) / (near_z - far_z); + + return result; +} + +function Mat4x4F32 +MakeLookAt4x4F32(Vec3F32 eye, Vec3F32 center, Vec3F32 up) +{ + Mat4x4F32 result; + Vec3F32 f = Normalize3F32(Sub3F32(eye, center)); + Vec3F32 s = Normalize3F32(Cross3F32(f, up)); + Vec3F32 u = Cross3F32(s, f); + result.v[0][0] = s.x; + result.v[0][1] = u.x; + result.v[0][2] = -f.x; + result.v[0][3] = 0.0f; + result.v[1][0] = s.y; + result.v[1][1] = u.y; + result.v[1][2] = -f.y; + result.v[1][3] = 0.0f; + result.v[2][0] = s.z; + result.v[2][1] = u.z; + result.v[2][2] = -f.z; + result.v[2][3] = 0.0f; + result.v[3][0] = -Dot3F32(s, eye); + result.v[3][1] = -Dot3F32(u, eye); + result.v[3][2] = Dot3F32(f, eye); + result.v[3][3] = 1.0f; + return result; +} + +function Mat4x4F32 +MakeRotate4x4F32(Vec3F32 axis, F32 turns) +{ + Mat4x4F32 result = MakeMat4x4F32(1.f); + axis = Normalize3F32(axis); + F32 sin_theta = SinF32(turns); + F32 cos_theta = CosF32(turns); + F32 cos_value = 1.f - cos_theta; + result.v[0][0] = (axis.x * axis.x * cos_value) + cos_theta; + result.v[0][1] = (axis.x * axis.y * cos_value) + (axis.z * sin_theta); + result.v[0][2] = (axis.x * axis.z * cos_value) - (axis.y * sin_theta); + result.v[1][0] = (axis.y * axis.x * cos_value) - (axis.z * sin_theta); + result.v[1][1] = (axis.y * axis.y * cos_value) + cos_theta; + result.v[1][2] = (axis.y * axis.z * cos_value) + (axis.x * sin_theta); + result.v[2][0] = (axis.z * axis.x * cos_value) + (axis.y * sin_theta); + result.v[2][1] = (axis.z * axis.y * cos_value) - (axis.x * sin_theta); + result.v[2][2] = (axis.z * axis.z * cos_value) + cos_theta; + return result; +} + +function Mat3x3F32 +Mul3x3F32(Mat3x3F32 a, Mat3x3F32 b) +{ + Mat3x3F32 c = {0}; + for(int j = 0; j < 3; j += 1) + { + for(int i = 0; i < 3; i += 1) + { + c.v[i][j] = (a.v[0][j]*b.v[i][0] + + a.v[1][j]*b.v[i][1] + + a.v[2][j]*b.v[i][2]); + } + } + return c; +} + +function Mat3x3F32 +Scale3x3F32(Mat3x3F32 m, F32 scale) +{ + for(int j = 0; j < 3; j += 1) + { + for(int i = 0; i < 3; i += 1) + { + m.v[i][j] *= scale; + } + } + return m; +} + +function Mat4x4F32 +Mul4x4F32(Mat4x4F32 a, Mat4x4F32 b) +{ + Mat4x4F32 c = {0}; + for(int j = 0; j < 4; j += 1) + { + for(int i = 0; i < 4; i += 1) + { + c.v[i][j] = (a.v[0][j]*b.v[i][0] + + a.v[1][j]*b.v[i][1] + + a.v[2][j]*b.v[i][2] + + a.v[3][j]*b.v[i][3]); + } + } + return c; +} + +function Mat4x4F32 +Scale4x4F32(Mat4x4F32 m, F32 scale) +{ + for(int j = 0; j < 4; j += 1) + { + for(int i = 0; i < 4; i += 1) + { + m.v[i][j] *= scale; + } + } + return m; +} + +function Mat4x4F32 +Inverse4x4F32(Mat4x4F32 m) +{ + F32 coef00 = m.v[2][2] * m.v[3][3] - m.v[3][2] * m.v[2][3]; + F32 coef02 = m.v[1][2] * m.v[3][3] - m.v[3][2] * m.v[1][3]; + F32 coef03 = m.v[1][2] * m.v[2][3] - m.v[2][2] * m.v[1][3]; + F32 coef04 = m.v[2][1] * m.v[3][3] - m.v[3][1] * m.v[2][3]; + F32 coef06 = m.v[1][1] * m.v[3][3] - m.v[3][1] * m.v[1][3]; + F32 coef07 = m.v[1][1] * m.v[2][3] - m.v[2][1] * m.v[1][3]; + F32 coef08 = m.v[2][1] * m.v[3][2] - m.v[3][1] * m.v[2][2]; + F32 coef10 = m.v[1][1] * m.v[3][2] - m.v[3][1] * m.v[1][2]; + F32 coef11 = m.v[1][1] * m.v[2][2] - m.v[2][1] * m.v[1][2]; + F32 coef12 = m.v[2][0] * m.v[3][3] - m.v[3][0] * m.v[2][3]; + F32 coef14 = m.v[1][0] * m.v[3][3] - m.v[3][0] * m.v[1][3]; + F32 coef15 = m.v[1][0] * m.v[2][3] - m.v[2][0] * m.v[1][3]; + F32 coef16 = m.v[2][0] * m.v[3][2] - m.v[3][0] * m.v[2][2]; + F32 coef18 = m.v[1][0] * m.v[3][2] - m.v[3][0] * m.v[1][2]; + F32 coef19 = m.v[1][0] * m.v[2][2] - m.v[2][0] * m.v[1][2]; + F32 coef20 = m.v[2][0] * m.v[3][1] - m.v[3][0] * m.v[2][1]; + F32 coef22 = m.v[1][0] * m.v[3][1] - m.v[3][0] * m.v[1][1]; + F32 coef23 = m.v[1][0] * m.v[2][1] - m.v[2][0] * m.v[1][1]; + + Vec4F32 fac0 = { coef00, coef00, coef02, coef03 }; + Vec4F32 fac1 = { coef04, coef04, coef06, coef07 }; + Vec4F32 fac2 = { coef08, coef08, coef10, coef11 }; + Vec4F32 fac3 = { coef12, coef12, coef14, coef15 }; + Vec4F32 fac4 = { coef16, coef16, coef18, coef19 }; + Vec4F32 fac5 = { coef20, coef20, coef22, coef23 }; + + Vec4F32 vec0 = { m.v[1][0], m.v[0][0], m.v[0][0], m.v[0][0] }; + Vec4F32 vec1 = { m.v[1][1], m.v[0][1], m.v[0][1], m.v[0][1] }; + Vec4F32 vec2 = { m.v[1][2], m.v[0][2], m.v[0][2], m.v[0][2] }; + Vec4F32 vec3 = { m.v[1][3], m.v[0][3], m.v[0][3], m.v[0][3] }; + + Vec4F32 inv0 = Add4F32(Sub4F32(Mul4F32(vec1, fac0), Mul4F32(vec2, fac1)), Mul4F32(vec3, fac2)); + Vec4F32 inv1 = Add4F32(Sub4F32(Mul4F32(vec0, fac0), Mul4F32(vec2, fac3)), Mul4F32(vec3, fac4)); + Vec4F32 inv2 = Add4F32(Sub4F32(Mul4F32(vec0, fac1), Mul4F32(vec1, fac3)), Mul4F32(vec3, fac5)); + Vec4F32 inv3 = Add4F32(Sub4F32(Mul4F32(vec0, fac2), Mul4F32(vec1, fac4)), Mul4F32(vec2, fac5)); + + Vec4F32 sign_a = { +1, -1, +1, -1 }; + Vec4F32 sign_b = { -1, +1, -1, +1 }; + + Mat4x4F32 inverse; + for(U32 i = 0; i < 4; i += 1) + { + inverse.v[0][i] = inv0.v[i] * sign_a.v[i]; + inverse.v[1][i] = inv1.v[i] * sign_b.v[i]; + inverse.v[2][i] = inv2.v[i] * sign_a.v[i]; + inverse.v[3][i] = inv3.v[i] * sign_b.v[i]; + } + + Vec4F32 row0 = { inverse.v[0][0], inverse.v[1][0], inverse.v[2][0], inverse.v[3][0] }; + Vec4F32 m0 = { m.v[0][0], m.v[0][1], m.v[0][2], m.v[0][3] }; + Vec4F32 dot0 = Mul4F32(m0, row0); + F32 dot1 = (dot0.x + dot0.y) + (dot0.z + dot0.w); + + F32 one_over_det = 1 / dot1; + + return Scale4x4F32(inverse, one_over_det); +} + +function Mat4x4F32 +RemoveRotation4x4F32(Mat4x4F32 mat) +{ + Vec3F32 scale = + { + Length3F32(V3F32(mat.v[0][0], mat.v[0][1], mat.v[0][2])), + Length3F32(V3F32(mat.v[1][0], mat.v[1][1], mat.v[1][2])), + Length3F32(V3F32(mat.v[2][0], mat.v[2][1], mat.v[2][2])), + }; + mat.v[0][0] = scale.x; + mat.v[1][0] = 0.f; + mat.v[2][0] = 0.f; + mat.v[0][1] = 0.f; + mat.v[1][1] = scale.y; + mat.v[2][1] = 0.f; + mat.v[0][2] = 0.f; + mat.v[1][2] = 0.f; + mat.v[2][2] = scale.z; + return mat; +} + +//////////////////////////////// +//~ rjf: Arenas + +function Arena * +ArenaMakeStatic(U8 *buffer, UAddr buffer_size) +{ + Arena *arena = 0; + if(buffer_size >= sizeof(Arena)) + { + arena = (Arena *)buffer; + arena->cap = buffer_size; + arena->pos = sizeof(Arena); + } + return arena; +} + +function void * +ArenaPush(Arena *arena, UAddr size, UAddr align) +{ + void *result = 0; + { + UAddr addr = (UAddr)((U8 *)arena + arena->pos); + UAddr addr_aligned = (addr + align - 1) & (~(align - 1)); + UAddr addr_aligned_pushed = addr_aligned + size; + UAddr addr_max = (UAddr)((U8 *)arena + arena->cap); + if(addr_aligned_pushed <= addr_max) + { + result = (void *)addr_aligned; + arena->pos += (addr_aligned_pushed - addr); + } + } + return result; +} + +function UAddr +ArenaPos(Arena *arena) +{ + return arena->pos; +} + +function void +ArenaPopTo(Arena *arena, UAddr pos) +{ + UAddr pos_popped = pos; + if(pos_popped < sizeof(Arena)) + { + pos_popped = sizeof(Arena); + } + arena->pos = pos_popped; +} + +function void +ArenaClear(Arena *arena) +{ + ArenaPopTo(arena, 0); +} + +function void +ArenaPop(Arena *arena, UAddr amt) +{ + if(ArenaPos(arena) >= amt) + { + ArenaPopTo(arena, ArenaPos(arena) - amt); + } +} + +//////////////////////////////// +//~ rjf: Arena Temporary Scopes + +function Temp +TempBegin(Arena *arena) +{ + Temp temp = {arena, ArenaPos(arena)}; + return temp; +} + +function void +TempEnd(Temp temp) +{ + ArenaPopTo(temp.arena, temp.pos); +} + +//////////////////////////////// +//~ rjf: Thread Context + +//- rjf: Thread Context Helpers + +function Arena * +GetScratch(Arena *conflict) +{ + Arena *result = 0; + ThreadCtx *tctx = GetThreadCtx(); + for EachElement(idx, tctx->scratch_arenas) + { + if(tctx->scratch_arenas[idx] != conflict) + { + result = tctx->scratch_arenas[idx]; + break; + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Strings + +function UAddr +CStr8Size(char *cstr) +{ + UAddr result = 0; + for(;cstr[result]; result += 1); + return result; +} + +function String8 +Str8(U8 *str, UAddr size) +{ + String8 result = {str, size}; + return result; +} + +function String8 +Str8FV(Arena *arena, char *fmt, va_list args) +{ + String8 result = {0}; + if(fmt != 0) + { + for(B32 write = 0; write <= 1; write += 1) + { + //- rjf: iterate bytes & format specifiers / arguments, compute string size or fill string + UAddr string_size = 0; + { + va_list args2; + va_copy(args2, args); + for(UAddr off = 0; fmt[off] != 0;) + { + //- rjf: % -> format specifier + if(fmt[off] == '%') + { + U64 int_val = 0; + U8 radix = 10; + B32 uppercase = 0; + B32 treat_as_signed = 0; + B32 leading_zero = 0; + U32 leading_count = 0; + + //- rjf: advance past % + off += 1; + + //- rjf: parse modifiers + for(B32 done = 0; !done;) + { + switch(fmt[off]) + { + default:{done = 1;}break; + case '0': + { + off += 1; + leading_zero = 1; + // TODO(rjf): parse leading count + }break; + // TODO(rjf): case '-': left-justify + // TODO(rjf): case '+': leading plus + // TODO(rjf): case ' ': leading space + // TODO(rjf): case '#": leading 0x + // TODO(rjf): case '\'': digit separator commas + // TODO(rjf): case '$': kilo marker + // TODO(rjf): case '_': no space between metric suffix and number + // TODO(rjf): case '0': leading zero + } + if(!done) + { + off += 1; + } + } + + //- rjf: do replacement + switch(fmt[off]) + { + //- rjf: %% -> escaped % + case '%': + { + off += 1; + if(write) + { + result.str[string_size] = '%'; + } + string_size += 1; + }break; + + //- rjf: %s -> c string + case 's': + { + off += 1; + char *cstr = va_arg(args, char *); + UAddr cstr_size = CStr8Size(cstr); + if(write) + { + MemoryCopy(result.str, cstr, cstr_size); + } + string_size += cstr_size; + }break; + + //- rjf: %S -> string + case 'S': + { + off += 1; + String8 string = va_arg(args, String8); + if(write) + { + MemoryCopy(result.str, string.str, string.size); + } + string_size += string.size; + }break; + + //- rjf: %i[s|u|x|X|o|O|b] -> N-wide integer, [s]igned, [u]nsigned, or he[x], or [o]ctal, or [b]inary + case 'i': + case 'I': + { + off += 1; + if(fmt[off] == '3' && fmt[off+1] == '2') + { + int_val = VariadicU32(args2); + off += 2; + } + else if(fmt[off] == '6' && fmt[off+1] == '4') + { + int_val = VariadicU64(args2); + off += 2; + } + else + { + int_val = VariadicInt(args2); + } + switch(fmt[off]) + { + default:{treat_as_signed = 1; radix = 10;}break; + case 's':{off += 1; radix = 10; treat_as_signed = 1;}break; + case 'u':{off += 1; radix = 10; treat_as_signed = 0;}break; + case 'b':{off += 1; radix = 2; treat_as_signed = 0;}break; + case 'O':{uppercase = 1;} // fallthrough + case 'o':{off += 1; radix = 8; treat_as_signed = 0;}break; + case 'X':{uppercase = 1;} // fallthrough + case 'x':{off += 1; radix = 16; treat_as_signed = 0;}break; + } + }goto int_case; + + //- rjf: %P, %p -> pointer value (specialization of integer case) + case 'P': uppercase = 1; // fallthrough + case 'p': + { + off += 1; + int_val = VariadicAddr(args2); + radix = 16; + }goto int_case; + + //- rjf: all integer value cases + int_case: + { + // rjf: radix prefix + switch(radix) + { + default:{}break; + case 2:{if(write) {result.str[string_size] = '0'; result.str[string_size+1] = 'b';} string_size += 2;}break; // leading 0b + case 8:{if(write) {result.str[string_size] = '0'; result.str[string_size+1] = 'o';} string_size += 2;}break; // leading 0o + case 16:{if(write) {result.str[string_size] = '0'; result.str[string_size+1] = 'x';} string_size += 2;}break; // leading 0x + } + + // rjf: treat as signed? -> push `-` if needed + if(treat_as_signed) + { + if(int_val & (1ull<<63)) + { + if(write) + { + result.str[string_size] = '-'; + } + string_size += 1; + } + } + + // rjf: make digits in reverse order + UAddr start_size = string_size; + switch(radix) + { + case 2: + { + U64 int_val_shifted = int_val; + for(;;) + { + if(write) + { + U8 bit_char = (int_val_shifted & 1) ? '1' : '0'; + result.str[string_size] = bit_char; + } + string_size += 1; + int_val_shifted >>= 1; + if(int_val_shifted == 0) + { + break; + } + } + }break; + case 10: + { + U64 int_val_dived = int_val; + for(;;) + { + if(write) + { + result.str[string_size] = dec_chars[int_val_dived%10]; + } + string_size += 1; + int_val_dived /= 10; + if(int_val_dived == 0) + { + break; + } + } + }break; + case 16: + { + U8 *hex_chars = uppercase ? hex_chars_upper : hex_chars_lower; + U64 int_val_shifted = int_val; + for(;;) + { + if(write) + { + result.str[string_size] = hex_chars[int_val_shifted&0xf]; + } + string_size += 1; + int_val_shifted >>= 4; + if(int_val_shifted == 0) + { + break; + } + } + }break; + } + + // rjf: reverse digits + if(write) + { + UAddr digit_count = string_size - start_size; + for(UAddr digit_idx = 0; digit_idx < digit_count/2; digit_idx += 1) + { + U8 swap = result.str[string_size - 1 - digit_idx]; + result.str[string_size - 1 - digit_idx] = result.str[start_size + digit_idx]; + result.str[start_size + digit_idx] = swap; + } + } + }break; + } + } + + //- rjf: no % -> just copy + else + { + if(write) + { + result.str[string_size] = fmt[off]; + } + off += 1; + string_size += 1; + } + } + va_end(args2); + } + + //- rjf: allocate string on the read step + if(!write) + { + result.size = string_size; + result.str = PushArrayNoZero(arena, U8, result.size+1); + } + else + { + result.str[string_size] = 0; + } + } + } + return result; +} + +function String8 +Str8F(Arena *arena, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + String8 result = Str8FV(arena, fmt, args); + va_end(args); + return result; +} + +function String8 +ByteStringFromData(Arena *arena, String8 data) +{ + String8 result = {0}; + if(data.size != 0) + { + U8 byte_chars[4] = {0}; + UAddr chars_per_byte = ArrayCount(byte_chars); + result.size = (data.size * chars_per_byte - 2); + result.str = PushArray(arena, U8, result.size); + for EachIndex(idx, data.size) + { + U8 byte = data.str[idx]; + byte_chars[0] = hex_chars_lower[(byte&0xf0) >> 4]; + byte_chars[1] = hex_chars_lower[(byte&0x0f) >> 0]; + byte_chars[2] = ','; + byte_chars[3] = ' '; + MemoryCopy(result.str + idx*chars_per_byte, byte_chars, idx+1 == data.size ? chars_per_byte-2 : chars_per_byte); + } + } + return result; +} + +//////////////////////////////// +//~ rjf: Colors + +function U16 +RGBA5551From4F32(Vec4F32 rgba) +{ + U16 rgba5551 = (rgba.w == 1.f ? 1 : 0) | ((U16)(rgba.x * 0x1f) << 11) | ((U16)(rgba.y * 0x1f) << 6) | ((U16)(rgba.z * 0x1f) << 1); + return rgba5551; +} + +function U32 +RGBA32From4F32(Vec4F32 rgba) +{ + U32 rgba32 = ((U32)(rgba.x * 255) << 24) | ((U32)(rgba.y * 255) << 16) | ((U32)(rgba.z * 255) << 8) | ((U32)(rgba.w * 255) << 0); + return rgba32; +} + +function Vec4F32 +RGBA4F32From32(U32 rgba) +{ + Vec4F32 rgba4f32 = {((rgba&0xff000000)>>24) / 255.f, ((rgba&0x00ff0000)>>16) / 255.f, ((rgba&0x0000ff00)>>8) / 255.f, ((rgba&0x000000ff)>>0) / 255.f}; + return rgba4f32; +} diff --git a/code/base/base.h b/code/base/base.h new file mode 100644 index 0000000..8adc1f8 --- /dev/null +++ b/code/base/base.h @@ -0,0 +1,676 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#ifndef BASE_H +#define BASE_H + +//////////////////////////////// +//~ rjf: Context Cracking + +//- rjf: MSVC Extraction + +#if defined(_MSC_VER) + +# define COMPILER_MSVC 1 + +# if defined(_WIN32) +# define PLATFORM_WINDOWS 1 +# else +# error _MSC_VER is defined, but _WIN32 is not. This setup is not supported. +# endif + +# if defined(_M_AMD64) +# define ARCH_X64 1 +# elif defined(_M_IX86) +# define ARCH_X86 1 +# elif defined(_M_ARM64) +# define ARCH_ARM64 1 +# elif defined(_M_ARM) +# define ARCH_ARM32 1 +# else +# error Target architecture is not supported. _MSC_VER is defined, but one of {_M_AMD64, _M_IX86, _M_ARM64, _M_ARM} is not. +# endif + +#if _MSC_VER >= 1920 +# define COMPILER_MSVC_YEAR 2019 +#elif _MSC_VER >= 1910 +# define COMPILER_MSVC_YEAR 2017 +#elif _MSC_VER >= 1900 +# define COMPILER_MSVC_YEAR 2015 +#elif _MSC_VER >= 1800 +# define COMPILER_MSVC_YEAR 2013 +#elif _MSC_VER >= 1700 +# define COMPILER_MSVC_YEAR 2012 +#elif _MSC_VER >= 1600 +# define COMPILER_MSVC_YEAR 2010 +#elif _MSC_VER >= 1500 +# define COMPILER_MSVC_YEAR 2008 +#elif _MSC_VER >= 1400 +# define COMPILER_MSVC_YEAR 2005 +#else +# define COMPILER_MSVC_YEAR 0 +#endif + +//- rjf: Clang Extraction + +#elif defined(__clang__) + +# define COMPILER_CLANG 1 + +# if defined(__APPLE__) && defined(__MACH__) +# define PLATFORM_MAC 1 +# elif defined(__gnu_linux__) +# define PLATFORM_LINUX 1 +# elif defined(N64) +# define PLATFORM_N64 1 +# else +# error __clang__ is defined, but one of {__APPLE__, __gnu_linux__, N64} is not. This setup is not supported. +# endif + +# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) +# define ARCH_X64 1 +# elif defined(i386) || defined(__i386) || defined(__i386__) +# define ARCH_X86 1 +# elif defined(__aarch64__) +# define ARCH_ARM64 1 +# elif defined(__arm__) +# define ARCH_ARM32 1 +# elif defined(__mips) && defined(__mips64) +# define ARCH_MIPS64 1 +# elif defined(__mips) && !defined(__mips64) +# define ARCH_MIPS32 1 +# else +# error Target architecture is not supported. __clang__ is defined, but one of {__amd64__, __amd64, __x86_64__, __x86_64, i386, __i386, __i386__, __aarch64__, __arm__, __mips} is not. +# endif + +//- rjf: GCC Extraction + +#elif defined(__GNUC__) || defined(__GNUG__) + +# define COMPILER_GCC 1 + +# if defined(__gnu_linux__) +# define PLATFORM_LINUX 1 +# else +# error __GNUC__ or __GNUG__ is defined, but __gnu_linux__ is not. This setup is not supported. +# endif + +# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) +# define ARCH_X64 1 +# elif defined(i386) || defined(__i386) || defined(__i386__) +# define ARCH_X86 1 +# elif defined(__aarch64__) +# define ARCH_ARM64 1 +# elif defined(__arm__) +# define ARCH_ARM32 1 +# else +# error Target architecture is not supported. __GNU_C__ or __GNUG__ is defined, but one of {__amd64__, __amd64, __x86_64__, __x86_64, i386, __i386, __i386__, __aarch64__, __arm__} is not. +# endif + +#else +# error Compiler is not supported. _MSC_VER, __clang__, __GNUC__, or __GNUG__ must be defined. +#endif + +#if ARCH_X64 || ARCH_ARM64 || ARCH_MIPS64 +# define ARCH_64BIT 1 +#elif ARCH_X86 || ARCH_ARM32 || ARCH_MIPS32 +# define ARCH_32BIT 1 +#endif + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# define LITTLE_ENDIAN 0 +# define BIG_ENDIAN 1 +#else +# define LITTLE_ENDIAN 1 +# define BIG_ENDIAN 0 +#endif + +//////////////////////////////// +//~ rjf: Variadic Arguments + +#define VariadicS64(args) va_arg(args, S64) +#define VariadicU64(args) va_arg(args, U64) +#if ARCH_X64 || ARCH_ARM64 +# define VariadicS32(args) va_arg(args, S32) +# define VariadicU32(args) va_arg(args, U32) +# define VariadicInt(args) va_arg(args, int) +# define VariadicAddr(args) va_arg(args, UAddr) +#elif ARCH_MIPS32 || ARCH_MIPS64 +# define VariadicS32(args) (S32)(va_arg(args, U64) & 0xffffffff) +# define VariadicU32(args) (U64)(va_arg(args, U64) & 0xffffffff) +# define VariadicInt(args) (int)(va_arg(args, U64) & 0xffffffff) +# define VariadicAddr(args) (UAddr)(va_arg(args, U64) & 0xffffffff) +#endif + +//////////////////////////////// +//~ rjf: Keywords + +#define function static +#define global static +#define local_persist static +#define align(n) __attribute__((aligned(n))) + +//////////////////////////////// +//~ rjf: Preprocessor Operations + +#define Glue_(A, B) A##B +#define Glue(A, B) Glue_(A, B) +#define Stringify_(S) #S +#define Stringify(S) Stringify_(S) + +//////////////////////////////// +//~ rjf: Static Assertions + +#define StaticAssert(C, ID) global U8 Glue(ID, __LINE__)[(C)?1:-1] + +//////////////////////////////////////////////////////////////// +//~ rjf: Clamps, Mins, Maxes + +#define Min(a, b) (((a)<(b)) ? (a) : (b)) +#define Max(a, b) (((a)>(b)) ? (a) : (b)) +#define ClampTop(x, a) Min(x,a) +#define ClampBot(a, x) Max(a,x) +#define Clamp(a, x, b) (((a)>(x))?(a):((b)<(x))?(b):(x)) + +//////////////////////////////////////////////////////////////// +//~ rjf: Swaps + +#define Swap(T, a, b) do{T c = (a); (a) = (b); (b) = c;}while(0) + +//////////////////////////////////////////////////////////////// +//~ rjf: Array Counts + +#define ArrayCount(a) ((sizeof(a)) / sizeof((a)[0])) +#define ArrayAndCount(a) (a), ArrayCount(a) +#define CountAndArray(a) ArrayCount(a), (a) + +//////////////////////////////////////////////////////////////// +//~ rjf: Loop Helpers + +#define EachIndex(idx, count) (UAddr idx = 0; idx < (count); idx += 1) +#define EachElement(idx, array) (UAddr idx = 0; idx < ArrayCount(array); idx += 1) + +#define DeferLoop(start, end) for(int _i_ = ((start), 0); _i_ == 0; (_i_ += 1, (end))) +#define DeferLoopChecked(begin, end) for(int _i_ = 2 * !(begin); (_i_ == 2 ? ((end), 0) : !_i_); _i_ += 1, (end)) + +//////////////////////////////////////////////////////////////// +//~ rjf: Bit Arrays + +#define BitArrayDecl(type, name, count) type name[(count + sizeof(type)*8-1) / sizeof(type)*8] +#define BitArrayAdd(arr, bit) ((arr)[(bit) / (sizeof((arr)[0])*8)] |= (((U64)1) << ((bit) % (sizeof((arr)[0])*8)))) +#define BitArrayRemove(arr, bit) ((arr)[(bit) / (sizeof((arr)[0])*8)] &= ~(((U64)1) << ((bit) % (sizeof((arr)[0])*8)))) +#define BitArrayGet(arr, bit) (!!((arr)[(bit) / (sizeof((arr)[0])*8)] & (((U64)1) << ((bit) % (sizeof((arr)[0])*8))))) + +//////////////////////////////// +//~ rjf: Masks + +#define Lower16(x) (((x)&0x0000ffff) >> 0) +#define Upper16(x) (((x)&0xffff0000) >> 16) +#define Lower32(x) (((x)&0x00000000ffffffffull) >> 0) +#define Upper32(x) (((x)&0xffffffff00000000ull) >> 32) + +//////////////////////////////// +//~ rjf: Units + +#define KiB(x) ((x)*1024) +#define MiB(x) (KiB(x)*1024) +#define GiB(x) (MiB(x)*1024) +#define TiB(x) (GiB(x)*1024) + +//////////////////////////////////////////////////////////////// +//~ rjf: Structure Packing Marker Macros + +#define pack_begin _Pragma("pack(push, 1)") +#define pack_end _Pragma("pack(pop)") + +//////////////////////////////////////////////////////////////// +//~ rjf: Type -> Alignment + +#if COMPILER_MSVC +# define AlignOf(T) __alignof(T) +#elif COMPILER_CLANG +# define AlignOf(T) __alignof(T) +#elif COMPILER_GCC +# define AlignOf(T) __alignof__(T) +#else +# error AlignOf not defined for this compiler. +#endif + +//////////////////////////////// +//~ rjf: Basic Types + +#include + +typedef int8_t S8; +typedef int16_t S16; +typedef int32_t S32; +typedef int64_t S64; +typedef uint8_t U8; +typedef uint16_t U16; +typedef uint32_t U32; +typedef uint64_t U64; +typedef S8 B8; +typedef S16 B16; +typedef S32 B32; +typedef S64 B64; +typedef float F32; +typedef double F64; +typedef void VoidFunction(void); + +#if ARCH_32BIT +typedef S32 SAddr; +typedef U32 UAddr; +#elif ARCH_64BIT +typedef S64 SAddr; +typedef U64 UAddr; +#endif + +//////////////////////////////// +//~ rjf: C Runtime Implementations + +void *memset(void *buffer, int c, UAddr n); +void *memcpy(void *dest, void *src, UAddr n); + +//////////////////////////////// +//~ rjf: Memory Operations + +#define MemoryZero(ptr, size) memset((ptr), 0, (size)) +#define MemoryZeroStruct(ptr) MemoryZero((ptr), sizeof(*(ptr))) +#define MemoryCopy(dst, src, size) memcpy((dst), (src), (size)) +#define MemoryCopyStruct(dst, src) memcpy((dst), (src), Min(sizeof(*(dst)), sizeof(*(src)))) + +function inline void +WriteAndInc(void *buffer, void *src, UAddr size, UAddr *cursor) +{ + MemoryCopy((U8 *)buffer + *cursor, src, size); + *cursor += size; +} + +function inline void +ReadAndInc(void *buffer, void *dst, UAddr size, UAddr *cursor) +{ + MemoryCopy(dst, (U8 *)buffer + *cursor, size); + *cursor += size; +} + +#define WriteStructAndInc(buffer, ptr, cursor_ptr) WriteAndInc((buffer), (ptr), sizeof(*(ptr)), (cursor_ptr)) +#define ReadStructAndInc(buffer, ptr, cursor_ptr) ReadAndInc((buffer), (ptr), sizeof(*(ptr)), (cursor_ptr)) + +//////////////////////////////// +//~ rjf: Float Operations + +function inline F32 +TruncF32(F32 f) +{ +#if ARCH_MIPS32 + F32 result_int; + F32 result; + __asm("trunc.w.s %0,%1" : "=f"(result_int) : "f"(f)); + __asm("cvt.s.w %0,%1" : "=f"(result) : "f"(result_int)); + return result; +#else +# error TruncF32 not implemented for this architecture. +#endif +} + +function inline F32 +FloorF32(F32 f) +{ +#if ARCH_MIPS32 + F32 result_int; + F32 result; + __asm("floor.w.s %0,%1" : "=f"(result_int) : "f"(f)); + __asm("cvt.s.w %0,%1" : "=f"(result) : "f"(result_int)); + return result; +#else +# error FloorF32 not implemented for this architecture. +#endif +} + +function inline F32 +ModF32(F32 f, F32 d) +{ + F32 result = f - TruncF32(f/d)*d; + return result; +} + +function inline S32 +Fixed112FromF32(F32 f) +{ + S32 result = (S32)(f*4.f); + result = Clamp(-4096*4, result, +4095*4); + return result; +} + +function inline S32 +Fixed1616FromF32(F32 f) +{ + S32 result; + if(f >= 32768.f) + { + result = 0x7fffffff; + } + else if(f < -32768.f) + { + result = 0x80000000; + } + else + { + result = (S32)FloorF32(f*65536.f); + } + return result; +} + +//////////////////////////////// +//~ rjf: Scalar Math Functions + +function F32 inline +SquareRootF32(F32 v) +{ +#if ARCH_MIPS32 + F32 result; + __asm("sqrt.s %0,%1" : "=f"(result) : "f"(v)); + return result; +#else +# error SquareRootF32 not implemented for this architecture. +#endif +} + +function F32 SinF32(F32 turns); + +function F32 inline +CosF32(F32 turns) +{ + F32 result = SinF32(turns + 0.25f); + return result; +} + +function F32 inline +TanF32(F32 turns) +{ + F32 result = SinF32(turns) / CosF32(turns); + return result; +} + +//////////////////////////////// +//~ rjf: Vector / Matrix Types + +typedef union Vec2U32 Vec2U32; +union Vec2U32 +{ + struct + { + U32 x; + U32 y; + }; + U32 v[2]; +}; + +typedef union Vec2F32 Vec2F32; +union Vec2F32 +{ + struct + { + F32 x; + F32 y; + }; + F32 v[2]; +}; + +typedef union Vec3F32 Vec3F32; +union Vec3F32 +{ + struct + { + F32 x; + F32 y; + F32 z; + }; + struct + { + Vec2F32 xy; + F32 z_; + }; + struct + { + F32 x_; + Vec2F32 yz; + }; + F32 v[3]; +}; + +typedef union Vec4F32 Vec4F32; +union Vec4F32 +{ + struct + { + F32 x; + F32 y; + F32 z; + F32 w; + }; + struct + { + Vec2F32 xy; + Vec2F32 zw; + }; + struct + { + Vec3F32 xyz; + F32 w_; + }; + struct + { + F32 x_; + Vec3F32 yzw; + }; + F32 v[4]; +}; + +typedef struct Mat3x3F32 Mat3x3F32; +struct Mat3x3F32 +{ + F32 v[3][3]; +}; + +typedef struct Mat4x4F32 Mat4x4F32; +struct Mat4x4F32 +{ + F32 v[4][4]; +}; + +//////////////////////////////// +//~ rjf: Vector Functions + +//- rjf: constructors +function Vec2U32 V2U32(U32 x, U32 y); +function Vec2F32 V2F32(F32 x, F32 y); +function Vec3F32 V3F32(F32 x, F32 y, F32 z); +function Vec4F32 V4F32(F32 x, F32 y, F32 z, F32 w); + +//- rjf: 2-vector ops +function Vec2F32 Add2F32(Vec2F32 l, Vec2F32 r); +function Vec2F32 Sub2F32(Vec2F32 l, Vec2F32 r); +function Vec2F32 Mul2F32(Vec2F32 l, Vec2F32 r); +function Vec2F32 Div2F32(Vec2F32 l, Vec2F32 r); +function F32 LengthSquared2F32(Vec2F32 v); +function F32 Length2F32(Vec2F32 v); +function Vec2F32 Scale2F32(Vec2F32 v, F32 s); +function Vec2F32 Normalize2F32(Vec2F32 v); +function F32 Dot2F32(Vec2F32 l, Vec2F32 r); + +//- rjf: 3-vector ops +function Vec3F32 Add3F32(Vec3F32 l, Vec3F32 r); +function Vec3F32 Sub3F32(Vec3F32 l, Vec3F32 r); +function Vec3F32 Mul3F32(Vec3F32 l, Vec3F32 r); +function Vec3F32 Div3F32(Vec3F32 l, Vec3F32 r); +function F32 LengthSquared3F32(Vec3F32 v); +function F32 Length3F32(Vec3F32 v); +function Vec3F32 Scale3F32(Vec3F32 v, F32 s); +function Vec3F32 Normalize3F32(Vec3F32 v); +function F32 Dot3F32(Vec3F32 l, Vec3F32 r); +function Vec3F32 Cross3F32(Vec3F32 l, Vec3F32 r); + +//- rjf: 4-vector ops +function Vec4F32 Add4F32(Vec4F32 l, Vec4F32 r); +function Vec4F32 Sub4F32(Vec4F32 l, Vec4F32 r); +function Vec4F32 Mul4F32(Vec4F32 l, Vec4F32 r); +function Vec4F32 Div4F32(Vec4F32 l, Vec4F32 r); +function F32 LengthSquared4F32(Vec4F32 v); +function F32 Length4F32(Vec4F32 v); +function Vec4F32 Scale4F32(Vec4F32 v, F32 s); +function Vec4F32 Normalize4F32(Vec4F32 v); +function F32 Dot4F32(Vec4F32 l, Vec4F32 r); +function Vec4F32 XForm4F32(Mat4x4F32 m, Vec4F32 v); + +//////////////////////////////// +//~ rjf: Matrix Functions + +//- rjf: constructors +function Mat3x3F32 MakeMat3x3F32(F32 d); +function Mat3x3F32 MakeTranslate3x3F32(Vec2F32 translation); +function Mat3x3F32 MakeScale3x3F32(Vec2F32 scale); +function Mat3x3F32 MakeRotate3x3F32(F32 turns); +function Mat4x4F32 MakeMat4x4F32(F32 d); +function Mat4x4F32 MakeTranslate4x4F32(Vec3F32 translation); +function Mat4x4F32 MakeScale4x4F32(Vec3F32 scale); +function Mat4x4F32 MakePerspective4x4F32(F32 fov, F32 aspect_ratio, F32 near_z, F32 far_z); +function Mat4x4F32 MakeOrthographic4x4F32(F32 left, F32 right, F32 bottom, F32 top, F32 near_z, F32 far_z); +function Mat4x4F32 MakeLookAt4x4F32(Vec3F32 eye, Vec3F32 center, Vec3F32 up); +function Mat4x4F32 MakeRotate4x4F32(Vec3F32 axis, F32 turns); + +//- rjf: ops +function Mat3x3F32 Mul3x3F32(Mat3x3F32 a, Mat3x3F32 b); +function Mat3x3F32 Scale3x3F32(Mat3x3F32 m, F32 scale); +function Mat4x4F32 Mul4x4F32(Mat4x4F32 a, Mat4x4F32 b); +function Mat4x4F32 Scale4x4F32(Mat4x4F32 m, F32 scale); +function Mat4x4F32 Inverse4x4F32(Mat4x4F32 m); +function Mat4x4F32 RemoveRotation4x4F32(Mat4x4F32 mat); + +//////////////////////////////// +//~ rjf: Arenas + +typedef struct Arena Arena; +struct Arena +{ + UAddr pos; + UAddr cap; +}; + +function Arena *ArenaMakeStatic(U8 *buffer, UAddr buffer_size); +function void *ArenaPush(Arena *arena, UAddr size, UAddr align); +function UAddr ArenaPos(Arena *arena); +function void ArenaPopTo(Arena *arena, UAddr pos); +function void ArenaClear(Arena *arena); +function void ArenaPop(Arena *arena, UAddr amt); +#define PushArrayNoZeroAligned(a, T, c, align) (T *)ArenaPush((a), sizeof(T)*(c), (align)) +#define PushArrayAligned(a, T, c, align) (T *)MemoryZero(PushArrayNoZeroAligned(a, T, c, align), sizeof(T)*(c)) +#define PushArrayNoZero(a, T, c) PushArrayNoZeroAligned(a, T, c, AlignOf(T)) +#define PushArray(a, T, c) PushArrayAligned(a, T, c, AlignOf(T)) + +//////////////////////////////// +//~ rjf: Arena Temporary Scopes + +typedef struct Temp Temp; +struct Temp +{ + Arena *arena; + UAddr pos; +}; + +function Temp TempBegin(Arena *arena); +function void TempEnd(Temp temp); + +//////////////////////////////// +//~ rjf: Thread Context + +typedef struct ThreadCtx ThreadCtx; +struct ThreadCtx +{ + Arena *scratch_arenas[2]; +}; + +//- rjf: @per_platform Thread Context Functions +function ThreadCtx *GetThreadCtx(void); + +//- rjf: Thread Context Helpers +function Arena *GetScratch(Arena *conflict); +#define ScratchBegin(result_arena) TempBegin(GetScratch(result_arena)) +#define ScratchEnd(temp) TempEnd(temp) + +//////////////////////////////// +//~ rjf: Strings + +#include + +typedef struct String8 String8; +struct String8 +{ + U8 *str; + UAddr size; +}; + +function UAddr CStr8Size(char *cstr); +function String8 Str8(U8 *str, UAddr size); +#define Str8Lit(s) Str8((U8 *)(s), sizeof(s)-1) +#define Str8LitComp(s) {(U8 *)(s), sizeof(s)-1} +#define S(s) Str8Lit(s) +function String8 Str8FV(Arena *arena, char *fmt, va_list args); +function String8 Str8F(Arena *arena, char *fmt, ...); +#define SF(...) Str8F(scratch.arena, __VA_ARGS__) +function String8 ByteStringFromData(Arena *arena, String8 data); + +global U8 dec_chars[] = "0123456789"; +global U8 hex_chars_lower[] = "0123456789abcdef"; +global U8 hex_chars_upper[] = "0123456789ABCDEF"; + +//////////////////////////////// +//~ rjf: Colors + +function U16 RGBA5551From4F32(Vec4F32 rgba); +function U32 RGBA32From4F32(Vec4F32 rgba); +function Vec4F32 RGBA4F32From32(U32 rgba); + +//////////////////////////////// +//~ rjf: Game Pads + +typedef enum GamePadButtonKind +{ + GamePadButtonKind_Null, + GamePadButtonKind_A, + GamePadButtonKind_B, + GamePadButtonKind_CLeft, + GamePadButtonKind_CUp, + GamePadButtonKind_CRight, + GamePadButtonKind_CDown, + GamePadButtonKind_Start, + GamePadButtonKind_DLeft, + GamePadButtonKind_DUp, + GamePadButtonKind_DRight, + GamePadButtonKind_DDown, + GamePadButtonKind_Z, + GamePadButtonKind_LeftBumper, + GamePadButtonKind_RightBumper, + GamePadButtonKind_COUNT +} +GamePadButtonKind; + +typedef struct GamePad GamePad; +struct GamePad +{ + U32 connect_gen; // incremented on every connection change + BitArrayDecl(U32, last_buttons, GamePadButtonKind_COUNT); + BitArrayDecl(U32, buttons, GamePadButtonKind_COUNT); + F32 left_trigger; + F32 right_trigger; + Vec2F32 sticks[2]; +}; + +#define GAME_PAD_SLOT_COUNT 4 + +//////////////////////////////// +//~ rjf: Program Tick Entry Point + +function B32 Run(U64 tick_idx, Arena *permanent_arena, GamePad game_pads[GAME_PAD_SLOT_COUNT]); + +#endif // BASE_H diff --git a/code/base/base_inc.c b/code/base/base_inc.c new file mode 100644 index 0000000..35c133b --- /dev/null +++ b/code/base/base_inc.c @@ -0,0 +1,9 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#include "base.c" +#if PLATFORM_N64 +# include "n64/base/n64_base.c" +#else +# error Platform-defined implementations for base layer not defined for this platform. +#endif diff --git a/code/base/base_inc.h b/code/base/base_inc.h new file mode 100644 index 0000000..4a09b8d --- /dev/null +++ b/code/base/base_inc.h @@ -0,0 +1,14 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#ifndef BASE_INC_H +#define BASE_INC_H + +#include "base.h" +#if PLATFORM_N64 +# include "n64/base/n64_base.h" +#else +# error Platform-defined implementations for base layer not defined for this platform. +#endif + +#endif // BASE_INC_H diff --git a/code/n64/base/n64_base.c b/code/n64/base/n64_base.c new file mode 100644 index 0000000..2d2e986 --- /dev/null +++ b/code/n64/base/n64_base.c @@ -0,0 +1,8 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +function ThreadCtx * +GetThreadCtx(void) +{ + return &n64_tctx; +} \ No newline at end of file diff --git a/code/n64/base/n64_base.h b/code/n64/base/n64_base.h new file mode 100644 index 0000000..0d59ec8 --- /dev/null +++ b/code/n64/base/n64_base.h @@ -0,0 +1,7 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#ifndef N64_BASE_H +#define N64_BASE_H + +#endif // N64_BASE_H diff --git a/code/n64/n64.c b/code/n64/n64.c new file mode 100644 index 0000000..692eeaf --- /dev/null +++ b/code/n64/n64.c @@ -0,0 +1,4869 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +// This code was adapted from the excellent work by the authors of the +// libdragon project (https://libdragon.dev). libdragon is licensed under +// The Unlicense: +// +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or distribute +// this software, either in source code form or as a compiled binary, for any +// purpose, commercial or non-commercial, and by any means. +// +// In jurisdictions that recognize copyright laws, the author or authors of +// this software dedicate any and all copyright interest in the software to the +// public domain. We make this dedication for the benefit of the public at +// large and to the detriment of our heirs and successors. We intend this +// dedication to be an overt act of relinquishment in perpetuity of all present +// and future rights to this software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to http://unlicense.org + +//////////////////////////////// +//~ rjf: Globals + +#define N64_VI_V_TOTAL_SET(vsync) ((vsync)-1) // (?) set the total number of visible and non-visible half-lines. +#define N64_VI_H_TOTAL_SET(leap_pattern, hsync) ((((leap_pattern) & 0x1F) << 16) | ((int)((hsync)*4-1) & 0xFFF)) // (?) set the total width of a line in quarter-pixel units (-1), and the 5-bit leap pattern. +#define N64_VI_H_TOTAL_LEAP_SET(leap_a, leap_b) ((((int)((leap_a)*4-1) & 0xFFF) << 16) | ((int)((leap_b)*4-1) & 0xFFF)) // (?) set alternate scanline lengths for one scanline during vsync, leap_a and leap_b are selected based on the leap pattern in h_sync. +#define N64_VI_V_BURST_SET(start, end) ((((start) & 0x3FF) << 16) | ((end) & 0x3FF)) // (?) set the start and end of color burst enable, in half-lines +#define N64_VI_BURST_START(value) ((value & 0x3FF) << 20) // (?) set start of color burst in pixels from hsync. +#define N64_VI_VSYNC_WIDTH(value) ((value & 0xF) << 16) // (?) set vertical sync width in half lines. +#define N64_VI_BURST_WIDTH(value) ((value & 0xFF) << 8) // (?) set color burst width in pixels. +#define N64_VI_HSYNC_WIDTH(value) ((value & 0xFF) << 0) // (?) set horizontal sync width in pixels. +#define N64_VI_BURST_SET(burst_start, vsync_width, burst_width, hsync_width) \ +N64_VI_BURST_START(burst_start) | N64_VI_VSYNC_WIDTH(vsync_width) | \ +N64_VI_BURST_WIDTH(burst_width) | N64_VI_HSYNC_WIDTH(hsync_width) // (?) set all burst values + +global N64_TimingPreset n64_timing_preset_from_tv_kind_table[N64_TVKind_COUNT] = +{ + //- rjf: PAL timing presets + { + .h_total = N64_VI_H_TOTAL_SET(0b10101, 794.5), + .h_total_leap = N64_VI_H_TOTAL_LEAP_SET(796.0, 795.75), + .v_total = N64_VI_V_TOTAL_SET(626), + .burst = N64_VI_BURST_SET(64, 4, 35, 58), + .v_burst = N64_VI_V_BURST_SET(9, 619), + .display_x0 = 128, + .display_y0 = 45, + .display_width = 640, + .display_height = 576, + }, + + //- rjf: NTSC timing presets + { + .h_total = N64_VI_H_TOTAL_SET(0b00000, 773.5), + .h_total_leap = N64_VI_H_TOTAL_LEAP_SET(773.5, 773.5), + .v_total = N64_VI_V_TOTAL_SET(526), + .burst = N64_VI_BURST_SET(62, 5, 34, 57), + .v_burst = N64_VI_V_BURST_SET(14, 516), + .display_x0 = 108, + .display_y0 = 35, + .display_width = 640, + .display_height = 480, + }, + + //- rjf: MPAL timing presets + { + .h_total = N64_VI_H_TOTAL_SET(0b00000, 772.75), + .h_total_leap = N64_VI_H_TOTAL_LEAP_SET(774.50, 774.50), ///< (774.00, 774.00) is slightly more stable for interlaced over composite + .v_total = N64_VI_V_TOTAL_SET(526), + .burst = N64_VI_BURST_SET(70, 5, 30, 57), + .v_burst = N64_VI_V_BURST_SET(14, 516), + .display_x0 = 108, + .display_y0 = 35, + .display_width = 640, + .display_height = 480, + }, +}; + +//////////////////////////////// +//~ rjf: Low-Level Post-IPL3 Entry Point + +__attribute__((naked, section(".text.boot"))) __attribute__((optnone)) void +_start(void) +{ + __asm__ __volatile__ + ( + ".set noreorder\n" + ".set noat\n" + + // rjf: initialize small data pointer + "la $gp, _gp\n" + + // rjf: from libdragon: + // + // a bit from libgloss so we can start at a known state + // + // SR_CU1: 0x20000000 - Mark CP1 as usable + // SR_FR: 0x04000000 - Enable MIPS III FP registers + // SR_PE: 0x00100000 - Mark soft reset (clear parity error) + // SR_KX: 0x00000080 - Kernel extended addressing enabled + // SR_SX: 0x00000040 - Supervisor extended addressing enabled + // SR_UX: 0x00000020 - User extended addressing enabled + // + "li $v0, 0x20000000|0x04000000|0x00100000|0x00000080|0x00000040|0x00000020\n" + "mtc0 $v0, $12\n" // C0_SR: $12 - Status register + + // rjf: enable interrupts + // + // writing to COP0 status register ($12): + // - 0x1 is the global interrupt enabling bit + // - 0x400 is the bit used to enable interrupts from the RCP + // + // (see cop0.h in libdragon for all the definitions) + // + "mfc0 $v0, $12\n" + "ori $v0, 0x1|0x400\n" + "mtc0 $v0, $12\n" + + // rjf: enable flush-to-zero mode on the FPU + "cfc1 $v0, $31\n" + "lui $v1, 0x0100\n" + "or $v0, $v0, $v1\n" + "ctc1 $v0, $31\n" + + // rjf: call C entry point + "li $a0, 0\n" + "jal N64_EntryPoint\n" + "li $a1, 0\n" + + // rjf: block forever if n64_main returns + "3:\n" + "b 3b\n" + "nop\n" + ); +} + +//////////////////////////////// +//~ rjf: Low-Level Interrupt Handler + +__attribute__((naked, section(".intvectors"))) void +_interrupt_handler(void) +{ + __asm__ __volatile__ + ( + ".section .intvectors, \"ax\"\n" + + //- rjf: jump to general interrupt handler path in all cases + // + // 0: TLB miss vector + // 0x80: XTLB miss vector + // 0x100: cache error vector + // 0x180: general exception vector + // + ".set reorder\n" + ".org 0\n" + "j inthandler\n" + ".org 0x80\n" + "j inthandler\n" + ".org 0x100\n" + "j inthandler\n" + ".org 0x180\n" + "j inthandler\n" + + //- rjf: interrupt handler code + ".section .text\n" + ".p2align 5\n" + "inthandler:\n" + ".set noreorder\n" + ".set noat\n" + + //- rjf: reserve space for pre-interrupt register states + // + // from libdragon: + // + // we need 544 + 32 bytes for this - 544 for storing all GPRs and FPRs, but + // also 32 extra bytes, required by MIPS ABI to call C functions (this is + // called "argument slots", required even if the C API does not have any + // parameters, as callees may use that space to store locals). + // + // stack layout for register states: + // - [0, 32): keep empty for calling C + // - [32, 576): GPRs, then FPRs, then special registers + // + "subu $sp, 576\n" + + //- rjf: save caller-saved GPRs; others are callee-saved, meaning called + // functions are responsible for cleaning them up. + "sd $1, (32 + 1*8)($sp)\n" + "sd $2, (32 + 2*8)($sp)\n" + "sd $3, (32 + 3*8)($sp)\n" + "sd $4, (32 + 4*8)($sp)\n" + "sd $5, (32 + 5*8)($sp)\n" + "sd $6, (32 + 6*8)($sp)\n" + "sd $7, (32 + 7*8)($sp)\n" + "sd $8, (32 + 8*8)($sp)\n" + "sd $9, (32 + 9*8)($sp)\n" + "sd $10, (32 + 10*8)($sp)\n" + "sd $11, (32 + 11*8)($sp)\n" + "sd $12, (32 + 12*8)($sp)\n" + "sd $13, (32 + 13*8)($sp)\n" + "sd $14, (32 + 14*8)($sp)\n" + "sd $15, (32 + 15*8)($sp)\n" + "sd $24, (32 + 24*8)($sp)\n" + "sd $25, (32 + 25*8)($sp)\n" + "addiu $t0, $sp, 576\n" + "sd $t0, (32 + 29*8)($sp)\n" + "sd $31, (32 + 31*8)($sp)\n" + + //- rjf: save LO/HI + "mflo $t0\n" + "mfhi $t1\n" + "sd $t0, (32 + 32*8)($sp)\n" + "sd $t1, (32 + 33*8)($sp)\n" + + //- rjf: save C0 special registers (SR/CR/EPC) + "mfc0 $t0, $12\n" // C0 $12 -> SR + "mfc0 $t8, $13\n" // C0 $13 -> cause info + "mfc0 $t1, $14\n" // C0 $14 -> EPC + "sw $t0, (32 + 34*8 + 0)($sp)\n" + "sw $t8, (32 + 34*8 + 4)($sp)\n" + "sw $t1, (32 + 34*8 + 8)($sp)\n" + + //- rjf: save FPU regs + "cfc1 $1, $31\n" + "sw $1, (32 + 34*8 + 12)($sp)\n" + ".set at\n" + "sdc1 $f0, (32 + 34*8 + 16 + 0*8)($sp)\n" + "sdc1 $f1, (32 + 34*8 + 16 + 1*8)($sp)\n" + "sdc1 $f2, (32 + 34*8 + 16 + 2*8)($sp)\n" + "sdc1 $f3, (32 + 34*8 + 16 + 3*8)($sp)\n" + "sdc1 $f4, (32 + 34*8 + 16 + 4*8)($sp)\n" + "sdc1 $f5, (32 + 34*8 + 16 + 5*8)($sp)\n" + "sdc1 $f6, (32 + 34*8 + 16 + 6*8)($sp)\n" + "sdc1 $f7, (32 + 34*8 + 16 + 7*8)($sp)\n" + "sdc1 $f8, (32 + 34*8 + 16 + 8*8)($sp)\n" + "sdc1 $f9, (32 + 34*8 + 16 + 9*8)($sp)\n" + "sdc1 $f10,(32 + 34*8 + 16 + 10*8)($sp)\n" + "sdc1 $f11,(32 + 34*8 + 16 + 11*8)($sp)\n" + "sdc1 $f12,(32 + 34*8 + 16 + 12*8)($sp)\n" + "sdc1 $f13,(32 + 34*8 + 16 + 13*8)($sp)\n" + "sdc1 $f14,(32 + 34*8 + 16 + 14*8)($sp)\n" + "sdc1 $f15,(32 + 34*8 + 16 + 15*8)($sp)\n" + "sdc1 $f16,(32 + 34*8 + 16 + 16*8)($sp)\n" + "sdc1 $f17,(32 + 34*8 + 16 + 17*8)($sp)\n" + "sdc1 $f18,(32 + 34*8 + 16 + 18*8)($sp)\n" + "sdc1 $f19,(32 + 34*8 + 16 + 19*8)($sp)\n" + + // rjf: adjust C0 SR ($12) to reset into known state + // + // SR_CU1: 0x20000000 - Mark CP1 as usable + // SR_FR: 0x04000000 - Enable MIPS III FP registers + // SR_PE: 0x00100000 - Mark soft reset (clear parity error) + // SR_KX: 0x00000080 - Kernel extended addressing enabled + // SR_SX: 0x00000040 - Supervisor extended addressing enabled + // SR_UX: 0x00000020 - User extended addressing enabled + // + // keep interrupts off temporarily + // + "li $v0, 0x20000000|0x04000000|0x00100000|0x00000080|0x00000040|0x00000020\n" + "mtc0 $v0, $12\n" + + //- rjf: call interrupt handler + "mfc0 $t0, $13\n" // C0 $13 -> (cause register) + "andi $t1, $t0, 0x7c\n" // 0x7c -> mask exception code off cause register + "srl $t1, 2\n" // shift off bottom 2 bits - $t1 now holds exception code + "addiu $a0, $sp, 32\n" // $a0 gets the pointer to the register state ($sp + 32) + "move $a1, $t1\n" // $a1 gets the exception code parameter + "jal N64_InterruptHandler\n" + "nop\n" + + // rjf: end interrupt: restore FP regs + ".set noat\n" + "lw $1, (32 + 34*8 + 12)($sp)\n" + "ctc1 $1, $31\n" + ".set at\n" + "ldc1 $f0, (32 + 34*8 + 16 + 0*8)($sp)\n" + "ldc1 $f1, (32 + 34*8 + 16 + 1*8)($sp)\n" + "ldc1 $f2, (32 + 34*8 + 16 + 2*8)($sp)\n" + "ldc1 $f3, (32 + 34*8 + 16 + 3*8)($sp)\n" + "ldc1 $f4, (32 + 34*8 + 16 + 4*8)($sp)\n" + "ldc1 $f5, (32 + 34*8 + 16 + 5*8)($sp)\n" + "ldc1 $f6, (32 + 34*8 + 16 + 6*8)($sp)\n" + "ldc1 $f7, (32 + 34*8 + 16 + 7*8)($sp)\n" + "ldc1 $f8, (32 + 34*8 + 16 + 8*8)($sp)\n" + "ldc1 $f9, (32 + 34*8 + 16 + 9*8)($sp)\n" + "ldc1 $f10,(32 + 34*8 + 16 + 10*8)($sp)\n" + "ldc1 $f11,(32 + 34*8 + 16 + 11*8)($sp)\n" + "ldc1 $f12,(32 + 34*8 + 16 + 12*8)($sp)\n" + "ldc1 $f13,(32 + 34*8 + 16 + 13*8)($sp)\n" + "ldc1 $f14,(32 + 34*8 + 16 + 14*8)($sp)\n" + "ldc1 $f15,(32 + 34*8 + 16 + 15*8)($sp)\n" + "ldc1 $f16,(32 + 34*8 + 16 + 16*8)($sp)\n" + "ldc1 $f17,(32 + 34*8 + 16 + 17*8)($sp)\n" + "ldc1 $f18,(32 + 34*8 + 16 + 18*8)($sp)\n" + "ldc1 $f19,(32 + 34*8 + 16 + 19*8)($sp)\n" + + // rjf: end interrupt: restore C0 special registers (SR/EPC - can skip CR) + "lw $t0, (32 + 34*8 + 0)($sp)\n" + "lw $t1, (32 + 34*8 + 8)($sp)\n" + "mtc0 $t0, $12\n" + "mtc0 $t1, $14\n" + + // rjf: end interrupt: restore LO, HI + "ld $t0, (32 + 32*8)($sp)\n" + "ld $t1, (32 + 33*8)($sp)\n" + "mtlo $t0\n" + "mthi $t1\n" + + // rjf: end interrupt: restore GPRs + "ld $1, (32 + 1*8)($sp)\n" + "ld $2, (32 + 2*8)($sp)\n" + "ld $3, (32 + 3*8)($sp)\n" + "ld $4, (32 + 4*8)($sp)\n" + "ld $5, (32 + 5*8)($sp)\n" + "ld $6, (32 + 6*8)($sp)\n" + "ld $7, (32 + 7*8)($sp)\n" + "ld $8, (32 + 8*8)($sp)\n" + "ld $9, (32 + 9*8)($sp)\n" + "ld $10, (32 + 10*8)($sp)\n" + "ld $11, (32 + 11*8)($sp)\n" + "ld $12, (32 + 12*8)($sp)\n" + "ld $13, (32 + 13*8)($sp)\n" + "ld $14, (32 + 14*8)($sp)\n" + "ld $15, (32 + 15*8)($sp)\n" + "ld $24, (32 + 24*8)($sp)\n" + "ld $25, (32 + 25*8)($sp)\n" + "ld $31, (32 + 31*8)($sp)\n" + + // rjf: pop stack scratch space + "addiu $sp, 576\n" + + // rjf: return from exception + "eret\n" + ); +} + +//////////////////////////////// +//~ rjf: Detected Console Kind + +function N64_ConsoleKind +N64_GetConsoleKind(void) +{ + return n64_state.console_kind; +} + +//////////////////////////////// +//~ rjf: JoyBus Specs + +function N64_JoyBus_CmdIDInfo +N64_JoyBus_InfoFromCmdID(N64_JoyBus_CmdID id) +{ + N64_JoyBus_CmdIDInfo result = {0}; + switch(id) + { + default:{}break; +#define Map(name) case N64_JoyBus_CmdID_##name:{result.send_size = sizeof(((N64_JoyBus_CmdPayload_##name *)0)->send); result.recv_size = sizeof(((N64_JoyBus_CmdPayload_##name *)0)->recv);}break + Map(Reset); + Map(Identify); + Map(N64ControllerRead); +#undef Map + } + return result; +} + +//////////////////////////////// +//~ rjf: Cache Operations + +function void +N64_DataCacheHitWriteback(volatile void *addr, UAddr length) +{ + N64_CacheOp(N64_CacheOpKind_HitWriteback, N64_CacheKind_Data); +} + +function void +N64_DataCacheHitInvalidate(volatile void *addr, UAddr length) +{ + // addr must be 16-byte aligned; length must be multiple of 16: + // + // (((U32)addr % 16) == 0 && (length % 16) == 0) + // + N64_CacheOp(N64_CacheOpKind_HitInvalidate, N64_CacheKind_Data); +} + +function void +N64_DataCacheHitWritebackInvalidate(volatile void *addr, UAddr length) +{ + N64_CacheOp(N64_CacheOpKind_HitWritebackInvalidate, N64_CacheKind_Data); +} + +function void +N64_DataCacheIndexWritebackInvalidate(volatile void *addr, UAddr length) +{ + N64_CacheOp(N64_CacheOpKind_IndexWritebackInvalidate, N64_CacheKind_Data); +} + +function void +N64_InstCacheHitWriteback(volatile void *addr, UAddr length) +{ + N64_CacheOp(N64_CacheOpKind_HitWriteback, N64_CacheKind_Inst); +} + +function void +N64_InstCacheHitInvalidate(volatile void *addr, UAddr length) +{ + N64_CacheOp(N64_CacheOpKind_HitInvalidate, N64_CacheKind_Inst); +} + +function void +N64_InstCacheFill(volatile void *addr, UAddr length) +{ + N64_CacheOp(N64_CacheOpKind_Fill, N64_CacheKind_Inst); +} + +function void +N64_InstCacheIndexInvalidate(volatile void *addr, UAddr length) +{ + N64_CacheOp(N64_CacheOpKind_IndexInvalidate, N64_CacheKind_Inst); +} + +//////////////////////////////// +//~ rjf: JoyBus Command Processing + +function B32 +N64_JoyBusCmdPush(N64_JoyBus_CmdID id) +{ + B32 result = 0; + if((n64_state.joybus_cmds_write_pos - n64_state.joybus_cmds_read_pos) < ArrayCount(n64_state.joybus_cmds)) + { + result = 1; + + //- rjf: take next command slot + U32 next_cmd_idx = n64_state.joybus_cmds_write_pos%ArrayCount(n64_state.joybus_cmds); + N64_JoyBus_Cmd *cmd = &n64_state.joybus_cmds[next_cmd_idx]; + + //- rjf: zero payloads, set ID + MemoryZero(cmd->send_payload, sizeof(cmd->send_payload)); + MemoryZero(cmd->recv_payload, sizeof(cmd->recv_payload)); + cmd->cmd_id = id; + + //- rjf: get command ID's metadata info + N64_JoyBus_CmdIDInfo id_info = N64_JoyBus_InfoFromCmdID(id); + + //- rjf: serialize send payload - pack data per port + U64 write_off = 0; + for(U32 port_idx = 0; port_idx < N64_JOYBUS_PORT_COUNT; port_idx += 1) + { + //- rjf: IQue -> prefix all commands w/ 0xFF + if(N64_GetConsoleKind() == N64_ConsoleKind_IQue) + { + cmd->send_payload[write_off] = 0xFF; + write_off += 1; + } + + //- rjf: push command header + cmd->send_payload[write_off+0] = id_info.send_size; + cmd->send_payload[write_off+1] = id_info.recv_size; + write_off += 2; + + //- rjf: push this port's part of the payload, based on command ID + switch(id) + { + default:{}break; + + //- rjf: identify commands + case N64_JoyBus_CmdID_Reset: + case N64_JoyBus_CmdID_Identify: + { + N64_JoyBus_CmdPayload_Identify payload = + { + .send.cmd_id = cmd->cmd_id, + }; + MemoryCopy(&cmd->send_payload[write_off], &payload.send, sizeof(payload.send)); + }break; + + //- rjf: n64 controller reads + case N64_JoyBus_CmdID_N64ControllerRead: + { + N64_JoyBus_CmdPayload_N64ControllerRead payload = + { + .send.cmd_id = cmd->cmd_id, + }; + MemoryCopy(&cmd->send_payload[write_off], &payload.send, sizeof(payload.send)); + }break; + } + write_off += id_info.send_size + id_info.recv_size; + + //- rjf: IQue -> pad to 8 byte alignment + if(N64_GetConsoleKind() == N64_ConsoleKind_IQue) + { + for(;(write_off&7) != 0;) + { + cmd->send_payload[write_off] = 0xFF; + write_off += 1; + } + } + } + + //- rjf: fill ending markers in payload + cmd->send_payload[write_off] = 0xFE; + cmd->send_payload[sizeof(cmd->send_payload)-1] = 0x1; + + //- rjf: send immediately if possible, to start chain of interrupts + B32 can_send_immediately = (n64_state.joybus_cmds_read_pos == n64_state.joybus_cmds_write_pos); + n64_state.joybus_cmds_write_pos += 1; + if(can_send_immediately) + { + n64_state.joybus_status = N64_JoyBus_Status_Sending; + N64_JoyBusCmdSend(cmd); + } + } + return result; +} + +function void +N64_JoyBusCmdSend(N64_JoyBus_Cmd *cmd) +{ + n64_state.joybus_status = N64_JoyBus_Status_Sending; + for(;N64_SI_REGS->status & (N64_SI_STATUS_DMA_BUSY|N64_SI_STATUS_IO_BUSY);); + N64_DataCacheHitWriteback(cmd->send_payload, sizeof(cmd->send_payload)); + N64_SI_REGS->dram_addr = (U32)(&cmd->send_payload[0]); + N64_MemoryBarrier(); + N64_SI_REGS->pif_addr_write = (U32)N64_PIF_RAM; + N64_MemoryBarrier(); +} + +//////////////////////////////// +//~ rjf: Rendering Helpers + +function U16 * +N64_Framebuffer(void) +{ + return n64_fb; +} + +function void +N64_DrawMonochromeBitmap(Vec2F32 pos, Vec4F32 color, U8 *ptr, U32 width, U32 height) +{ + U16 color_5551 = RGBA5551From4F32(color); + S32 draw_x = (S32)pos.x; + S32 draw_y = (S32)pos.y; + U16 *p = N64_Framebuffer(); + for(S32 bmp_y = 0; bmp_y < height; bmp_y += 1) + { + for(S32 bmp_x = 0; bmp_x < width; bmp_x += 1) + { + U8 bmp_v = ptr[bmp_y*width + bmp_x]; + if(bmp_v) + { + U64 fb_off = (draw_y + bmp_y)*N64_FB_WIDTH + draw_x + bmp_x; + p[fb_off] = color_5551; + } + } + } +} + +function Vec2F32 +N64_DrawDebugString(B32 big, Vec2F32 pos, Vec4F32 color, String8 string) +{ + Vec2F32 advance = {0}; + for(U64 off = 0; off < string.size; off += 1) + { + U8 byte = string.str[off]; + if(big) + { + N64_BigDefaultFontGlyph *glyph = &n64_big_default_font_glyphs[byte]; + N64_DrawMonochromeBitmap(pos, color, glyph->v, N64_BIG_DEFAULT_FONT_GLYPH_WIDTH, N64_BIG_DEFAULT_FONT_GLYPH_HEIGHT); + pos.x += N64_BIG_DEFAULT_FONT_GLYPH_ADVANCE; + advance.x += N64_BIG_DEFAULT_FONT_GLYPH_ADVANCE; + } + else + { + N64_SmallDefaultFontGlyph *glyph = &n64_small_default_font_glyphs[byte]; + N64_DrawMonochromeBitmap(pos, color, glyph->v, N64_SMALL_DEFAULT_FONT_GLYPH_WIDTH, N64_SMALL_DEFAULT_FONT_GLYPH_HEIGHT); + pos.x += N64_SMALL_DEFAULT_FONT_GLYPH_ADVANCE; + advance.x += N64_SMALL_DEFAULT_FONT_GLYPH_ADVANCE; + } + } + advance.y += big ? N64_BIG_DEFAULT_FONT_GLYPH_HEIGHT : N64_SMALL_DEFAULT_FONT_GLYPH_HEIGHT; + return advance; +} + +//////////////////////////////// +//~ rjf: High-Level Interrupt Handler + +void +N64_InterruptHandler(N64_ExceptionRegState *regs, N64_ExceptionCode exception_code) +{ + switch(exception_code) + { + ////////////////////////////// + //- rjf: default case -> unrecoverable exception, display info + // + default: + { + Temp scratch = ScratchBegin(0); + UAddr epc = regs->epc + (regs->cr&0x80000000 ? 4 : 0); // if 0x80000000 is set, -> advance to next instruction + UAddr bad_vaddr = (UAddr)regs->gpr[8]; // $8 gets the bad vaddr of the exception + + //- rjf: get exception explanation + String8 explanation = S("Unrecognized exception"); + if(exception_code < ArrayCount(n64_string_from_exception_code_table)) + { + explanation = n64_string_from_exception_code_table[exception_code]; + switch(exception_code) + { + default:{}break; + + //- rjf: floating-point exceptions + case N64_ExceptionCode_FloatingPoint: + { + // NOTE(rjf): floating-point exception cracking adapted from libdragon + U32 opcode = *(U32 *)epc; + + //- rjf: standard FPU opcode (not MTC/DMTC) + B32 specialized = 0; + if((opcode>>26) == 0x11 && (opcode&0x3F) != 0) + { + U32 fs = (opcode>>11) & 0x1F; + U32 ft = (opcode>>16) & 0x1F; + switch((opcode>>21) & 0x1F) + { + //- rjf: single-precision + case 0x10: + { + U32 reg_fs = regs->fpr[fs]; + U32 reg_ft = regs->fpr[ft]; + + // rjf: check for uninitialized values as initialized by -ftrivial-auto-var-init=pattern + if((reg_fs & 0xFFFF0000) == 0xFEFE0000 || (reg_ft & 0xFFFF0000) == 0xFEFE0000) + { + specialized = 1; + explanation = S("Uninitialized floating point variable"); + } + + // rjf: check for denormals + else if(((reg_fs & 0x7F800000) == 0 && (reg_fs & 0x007FFFFF) != 0) || + ((reg_ft & 0x7F800000) == 0 && (reg_ft & 0x007FFFFF) != 0)) + { + specialized = 1; + explanation = S("Invalid floating point value (denormal)"); + } + + // rjf: check for negative square roots + else if((opcode & 0x3F) == 0x04 && (S32)reg_fs < 0) + { + specialized = 1; + explanation = S("Floating point negative square root"); + } + }break; + + //- rjf: double-precision + case 0x11: + { + U64 reg_fs64 = regs->fpr[fs]; + U64 reg_ft64 = regs->fpr[ft]; + + // rjf: check for uninitialized values as initialized by -ftrivial-auto-var-init=pattern + if((reg_fs64 & 0xFFFFFFFF00000000ull) == 0xFEFEFEFE00000000ull || + (reg_ft64 & 0xFFFFFFFF00000000ull) == 0xFEFEFEFE00000000ull) + { + specialized = 1; + explanation = S("Uninitialized floating point variable"); + } + + // rjf: check for denormals + else if(((reg_fs64 & 0x7FF0000000000000ull) == 0 && (reg_fs64 & 0x000FFFFFFFFFFFFFull) != 0) || + ((reg_ft64 & 0x7FF0000000000000ull) == 0 && (reg_ft64 & 0x000FFFFFFFFFFFFFull) != 0)) + { + specialized = 1; + explanation = S("Invalid floating point value (denormal)"); + } + + // rjf: check for negative square roots + else if((opcode & 0x3F) == 0x04 && (S64)reg_fs64 < 0) + { + specialized = 1; + explanation = S("Floating point negative square root"); + } + }break; + } + } + + // rjf: check general cases + if(!specialized) + { + if(regs->fc31 & 0x00008000) + { + explanation = S("Floating point divide by zero"); + } + else if(regs->fc31 & 0x00010000) + { + explanation = S("Floating point invalid operation"); + } + else if(regs->fc31 & 0x00004000) + { + explanation = S("Floating point overflow"); + } + else if(regs->fc31 & 0x00002000) + { + explanation = S("Floating point underflow"); + } + else if(regs->fc31 & 0x00001000) + { + explanation = S("Floating point inexact operation"); + } + } + }break; + } + } + + //- rjf: blue screen & render exception info + { + n64_fb = (U16 *)0xA0100000u; + N64_VI_REGS->origin = 0x00100000u; + for(int pixel_idx = 0; pixel_idx < N64_FB_WIDTH*N64_FB_HEIGHT; pixel_idx += 1) + { + n64_fb[pixel_idx] = 0x003F; + } + N64_DrawDebugString(1, V2F32(16, 16 + 0*11), V4F32(1, 0, 0, 1), S("Critical exception occurred")); + N64_DrawDebugString(0, V2F32(16, 16 + 14 + 0*11), V4F32(1, 1, 1, 1), SF("Code: %i32x", exception_code)); + N64_DrawDebugString(0, V2F32(16, 16 + 14 + 1*11), V4F32(1, 1, 1, 1), SF("EPC: %i32x", regs->epc)); + N64_DrawDebugString(0, V2F32(16, 16 + 14 + 2*11), V4F32(1, 1, 1, 1), SF("SP: %p", (UAddr)(regs->sp))); + N64_DrawDebugString(0, V2F32(16, 16 + 14 + 3*11), V4F32(1, 1, 1, 1), SF("Address: %p", bad_vaddr)); + N64_DrawDebugString(0, V2F32(16, 16 + 14 + 4*11), V4F32(1, 1, 1, 1), S("Explanation:")); + N64_DrawDebugString(0, V2F32(16, 16 + 14 + 5*11), V4F32(0.8f, 0.8f, 0.8f, 1), explanation); + for(;;){} + } + + ScratchEnd(scratch); + }break; + + ////////////////////////////// + //- rjf: interrupts + // + case N64_ExceptionCode_Interrupt: + { + U64 status = *N64_MI_INTERRUPT & *N64_MI_MASK; + + ///////////////////////////// + //- rjf: [SI] serial interface interrupt + // + if(status & N64_MI_InterruptStatusMask_SI) + { + //- rjf: clear interrupt + N64_SI_REGS->status = 0; + + //- rjf: update joybus state + switch(n64_state.joybus_status) + { + //- rjf: [idle] nothing to do - this interrupt was not caused by our own joybus interactions. + default: + case N64_JoyBus_Status_Idle: + {}break; + + //- rjf: [sending] send is complete - now initiate receive. + case N64_JoyBus_Status_Sending: + { + // rjf: get current cmd index + U32 cmd_idx = n64_state.joybus_cmds_read_pos%ArrayCount(n64_state.joybus_cmds); + + // rjf: initiate receive transfer + for(;N64_SI_REGS->status & (N64_SI_STATUS_DMA_BUSY|N64_SI_STATUS_IO_BUSY);); + N64_DataCacheHitInvalidate(n64_state.joybus_cmds[cmd_idx].recv_payload, sizeof(n64_state.joybus_cmds[cmd_idx].recv_payload)); + N64_SI_REGS->dram_addr = (U32)(&n64_state.joybus_cmds[cmd_idx].recv_payload[0]); + N64_MemoryBarrier(); + N64_SI_REGS->pif_addr_read = (U32)N64_PIF_RAM; + N64_MemoryBarrier(); + + // rjf: sending -> recving + n64_state.joybus_status = N64_JoyBus_Status_Recving; + }break; + + //- rjf: [receiving] receive is complete - push next command, or move back to idle. + case N64_JoyBus_Status_Recving: + { + for(;N64_SI_REGS->status & (N64_SI_STATUS_DMA_BUSY|N64_SI_STATUS_IO_BUSY);); + + // rjf: get current cmd index + U32 cmd_idx = n64_state.joybus_cmds_read_pos%ArrayCount(n64_state.joybus_cmds); + N64_JoyBus_CmdID cmd_id = n64_state.joybus_cmds[cmd_idx].cmd_id; + N64_JoyBus_CmdIDInfo cmd_id_info = N64_JoyBus_InfoFromCmdID(cmd_id); + + // rjf: unpack received payload + U8 *data = n64_state.joybus_cmds[cmd_idx].recv_payload; + UAddr read_off_opl = sizeof(n64_state.joybus_cmds[cmd_idx].recv_payload); + UAddr read_off = 0; + + // rjf: parse payload + for(U32 port_idx = 0; port_idx < N64_JOYBUS_PORT_COUNT; port_idx += 1) + { + // rjf: early-out if we're on iQue and we're on port 4 + if(N64_GetConsoleKind() == N64_ConsoleKind_IQue && port_idx == 4) + { + break; + } + + // rjf: from libdragon: + // + // iQue has a highly fixed layout for commands, and it tends to corrupt + // other parts of PIF-RAM, so we'll want to just parse at fixed offsets + // in those cases. + // + if(N64_GetConsoleKind() == N64_ConsoleKind_IQue) + { + read_off = (port_idx*8)+1; + } + + // rjf: early-out if we don't have space for the next payload + UAddr bytes_per_payload = 2 + cmd_id_info.send_size + cmd_id_info.recv_size; + if(read_off + bytes_per_payload > read_off_opl) + { + break; + } + + // rjf: parse next payload if the error bit (0x80) is not set in the recv size + if(!(data[read_off+1] & 0x80)) + { + U8 *payload_raw = data + read_off + 2; + switch(cmd_id) + { + default:{}break; + + //- rjf: reset/identify + case N64_JoyBus_CmdID_Reset: + case N64_JoyBus_CmdID_Identify: + { + N64_JoyBus_CmdPayload_Identify payload = {0}; + MemoryCopy(&payload, payload_raw, sizeof(payload)); + + // rjf: get last ID on this port + N64_JoyBus_ID last_id = n64_state.joybus_port_states[port_idx].id; + + // rjf: fill joybus port state + n64_state.joybus_port_states[port_idx].id = payload.recv.id; + n64_state.joybus_port_states[port_idx].status = payload.recv.status; + + // rjf: bump generation on changed ID + if(last_id != payload.recv.id) + { + n64_state.joybus_port_states[port_idx].connect_gen += 1; + } + + // rjf: reflect generation in controller state + if(port_idx < ArrayCount(n64_state.game_pads)) + { + n64_state.game_pads[port_idx].connect_gen = n64_state.joybus_port_states[port_idx].connect_gen; + } + }break; + + //- rjf: n64 controller state reads + case N64_JoyBus_CmdID_N64ControllerRead: + if(port_idx < ArrayCount(n64_state.game_pads)) + { + N64_JoyBus_CmdPayload_N64ControllerRead payload = {0}; + MemoryCopy(&payload, payload_raw, sizeof(payload)); + + // rjf: get associated controller state + GamePad *pad = &n64_state.game_pads[port_idx]; + + // rjf: remember last button state + MemoryCopy(pad->last_buttons, pad->buttons, sizeof(pad->buttons)); + + // rjf: fill new controller button states +#define Map(recv_name, button_name) if(payload.recv.recv_name) { BitArrayAdd(pad->buttons, GamePadButtonKind_##button_name); } else { BitArrayRemove(pad->buttons, GamePadButtonKind_##button_name); } + Map(a, A); + Map(b, B); + Map(z, Z); + Map(start, Start); + Map(d_up, DUp); + Map(d_down, DDown); + Map(d_left, DLeft); + Map(d_right, DRight); + Map(l, LeftBumper); + Map(r, RightBumper); + Map(c_up, CUp); + Map(c_down, CDown); + Map(c_left, CLeft); + Map(c_right, CRight); +#undef Map + + // rjf: fill controller stick state + pad->sticks[0].x = ((F32)payload.recv.stick_x / 128.f); + pad->sticks[0].y = ((F32)payload.recv.stick_y / 128.f); + }break; + } + } + read_off += bytes_per_payload; + } + + // rjf: advance one cmd + n64_state.joybus_cmds_read_pos += 1; + + // rjf: recving -> idle + n64_state.joybus_status = N64_JoyBus_Status_Idle; + + // rjf: try to push next command, if there are any in the queue + if(n64_state.joybus_cmds_write_pos - n64_state.joybus_cmds_read_pos > 0) + { + N64_JoyBus_Cmd *next_cmd = &n64_state.joybus_cmds[n64_state.joybus_cmds_read_pos%ArrayCount(n64_state.joybus_cmds)]; + N64_JoyBusCmdSend(next_cmd); + } + }break; + } + + //- rjf: [DEBUG] blue screen & render info +#if 0 + { + n64_fb = (U16 *)0xA0100000u; + N64_VI_REGS->origin = 0x00100000u; + for(int pixel_idx = 0; pixel_idx < N64_FB_WIDTH*N64_FB_HEIGHT; pixel_idx += 1) + { + n64_fb[pixel_idx] = 0x003F; + } + N64_DrawDebugString(1, V2F32(16, 16 + 0*14), V4F32(1, 0, 0, 1), S("SI Interrupt")); + for(;;){} + } +#endif + } + }break; + } +} + +//////////////////////////////// +//~ rjf: High-Level N64 Program Entry Point + +void +N64_EntryPoint(void) +{ + //- rjf: determine console kind + n64_state.console_kind = N64_ConsoleKind_N64; + if(!!(N64_BOOT_INFO->flags & N64_BootInfoFlag_IsIQue)) + { + n64_state.console_kind = N64_ConsoleKind_IQue; + } + else if((*N64_MI_VERSION & 0xFF) == 0x3) + { + n64_state.console_kind = N64_ConsoleKind_Analogue3D; + } + + //- rjf: set up main user memory arena + Arena *user_arena = 0; + { + U32 memory_size = N64_BOOT_INFO->memory_size; + U32 bss_data_end = (U32)__bss_end; + U32 bss_data_end_aligned = ((bss_data_end + 63) & ~63); + U32 rdram_end = 0x80000000 + memory_size; + U32 stack_size = 64*1024; + U32 user_memory_end = rdram_end - stack_size; + U32 user_memory_size = (user_memory_end - bss_data_end_aligned); + user_arena = ArenaMakeStatic((U8 *)bss_data_end_aligned, user_memory_size); + } + + //- rjf: choose timing preset + N64_TVKind tv_kind = (N64_TVKind)((N64_BOOT_INFO->flags & N64_BootInfoFlags_TVKindMask) >> N64_BootInfoFlags_TVKindShift); + N64_TimingPreset *timing_preset = &n64_timing_preset_from_tv_kind_table[tv_kind]; + + //- rjf: allocate space for framebuffers + // + // NOTE(rjf): must be 8-byte aligned. virtual addresses should be in uncached KSEG1 memory. + // + U32 framebuffer_color_paddrs[2] = {0}; + U32 framebuffer_depth_paddrs[2] = {0}; + for(U32 idx = 0; idx < ArrayCount(framebuffer_color_paddrs); idx += 1) + { + void *color_memory = PushArrayNoZeroAligned(user_arena, U16, N64_FB_WIDTH*N64_FB_HEIGHT, 64); + framebuffer_color_paddrs[idx] = (UAddr)color_memory - 0x80000000; + } + for(U32 idx = 0; idx < ArrayCount(framebuffer_depth_paddrs); idx += 1) + { + void *depth_memory = PushArrayNoZeroAligned(user_arena, U16, N64_FB_WIDTH*N64_FB_HEIGHT, 64); + framebuffer_depth_paddrs[idx] = (UAddr)depth_memory - 0x80000000; + } + + //- rjf: allocate space for scratch arenas + UAddr scratch_arena_storage_size = 128*1024; + U8 *scratch_arena_storage = PushArrayNoZeroAligned(user_arena, U8, scratch_arena_storage_size, 64); + + //- rjf: store global scratch arena pointers + { + UAddr size_per_scratch_arena = scratch_arena_storage_size/2; + n64_tctx.scratch_arenas[0] = ArenaMakeStatic(scratch_arena_storage + 0, size_per_scratch_arena); + n64_tctx.scratch_arenas[1] = ArenaMakeStatic(scratch_arena_storage + size_per_scratch_arena, size_per_scratch_arena); + } + + //- rjf: set up VI registers + { + N64_VI_REGS->ctrl = N64_VI_CtrlRegVal(N64_VI_CtrlKind_16bpp, + N64_VI_AAMode_ResampleFetchAlways, + N64_VI_PixelAdvance_Default, + N64_VI_CtrlFlag_GammaCorrect| + N64_VI_CtrlFlag_GammaDitherEnable| + N64_VI_CtrlFlag_Divot| + N64_VI_CtrlFlag_Dedither); + N64_VI_REGS->origin = framebuffer_color_paddrs[1]; + N64_VI_REGS->width = N64_FB_WIDTH; + N64_VI_REGS->v_intr = 0x00000002u; + N64_VI_REGS->v_current = 0x00000000u; + N64_VI_REGS->h_total = timing_preset->h_total; + N64_VI_REGS->h_total_leap= timing_preset->h_total_leap; + N64_VI_REGS->burst = timing_preset->burst; + N64_VI_REGS->v_burst = timing_preset->v_burst; + N64_VI_REGS->v_total &= 1; // NOTE(rjf): the bottom bit of v_total is used for toggling interlaced mode, so we just keep that bit in this case. + N64_VI_REGS->v_total |= 0xFFFFFFFE & (timing_preset->v_total & ~1); + N64_VI_REGS->h_video = ((timing_preset->display_x0 & 0x3ff) << 16) | (((timing_preset->display_x0 + timing_preset->display_width) & 0x3ff) << 0); + N64_VI_REGS->v_video = ((timing_preset->display_y0 & 0x3ff) << 16) | (((timing_preset->display_y0 + timing_preset->display_height) & 0x3ff) << 0); + N64_VI_REGS->x_scale = 0x200u; + N64_VI_REGS->y_scale = 0x400u; + N64_MemoryBarrier(); + } + + //- rjf: set up SI interrupt handling + { + *N64_MI_MASK = N64_MI_MaskCmd_SetSI; + N64_SI_REGS->status = 0; + N64_MemoryBarrier(); + } + + //- rjf: set up RDP + { + N64_DP_REGS->status_flags = (N64_DP_StatusCmd_ResetFlush| + N64_DP_StatusCmd_ResetFreeze| + N64_DP_StatusCmd_ResetXBusDMemDMA); + N64_MemoryBarrier(); + } + + //- rjf: do main loop + for(U64 tick_idx = 0;; tick_idx += 1) + { + //- rjf: do detection of all JoyBus devices periodically + if(tick_idx%10 == 0) + { + N64_JoyBusCmdPush(N64_JoyBus_CmdID_Identify); + } + + //- rjf: do JoyBus input detection + { + N64_JoyBusCmdPush(N64_JoyBus_CmdID_N64ControllerRead); + } + + //- rjf: prep current framebuffer pointer + n64_fb = (U16 *)(framebuffer_color_paddrs[tick_idx&1] + 0xA0000000); + + //- rjf: set up empty RDP queue; wait for RDP to be ready + U32 payload_start_paddr = (U32)(n64_r_state.rdp_command_buffer) - 0x80000000; +#if 1 + n64_r_state.rdp_command_buffer_write_pos = 0; + N64_MemoryBarrier(); + N64_DP_REGS->start = payload_start_paddr; + N64_DP_REGS->end = payload_start_paddr; + N64_MemoryBarrier(); + for(;N64_DP_REGS->current != payload_start_paddr;){} + + //- rjf: clear depth buffer + { + // rjf: sync pipe + { + N64_DP_Cmd_Stub cmd = {0}; + cmd.id = N64_DP_CmdID_SyncPipe; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set color image to depth buffer + { + N64_DP_Cmd_SetColorImage cmd = {0}; + cmd.id = N64_DP_CmdID_SetColorImage; + cmd.format = N64_DP_PixelFormat_RGBA; + cmd.pixel_size_kind = N64_DP_PixelSizeKind_16; + cmd.width = N64_FB_WIDTH-1; + cmd.image_paddr = framebuffer_depth_paddrs[tick_idx&1]; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set other modes + { + N64_DP_Cmd_SetOtherModes cmd = {0}; + cmd.id = N64_DP_CmdID_SetOtherModes; + cmd.atomic_prim = 1; + cmd.cycle_type = 3; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set fill color + { + N64_DP_Cmd_SetFillColor cmd = {0}; + cmd.id = N64_DP_CmdID_SetFillColor; + cmd.rgba32 = 0xfffcfffc; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: clear +#if 0 + { + N64_DP_Cmd_FillRectangle cmd = {0}; + cmd.lower_right_x = ((N64_FB_WIDTH-1)<<2); + cmd.lower_right_y = ((N64_FB_HEIGHT-1)<<2); + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } +#endif + + // rjf: clear +#if 1 + { + N64_DP_Cmd_FillTriangleBase cmd = {0}; + cmd.id = N64_DP_CmdID_FillTriangle; + cmd.flags = N64_DP_Cmd_FillTriangleFlag_LeftMajor; + cmd.yl = Fixed112FromF32(N64_FB_HEIGHT); + cmd.ym = Fixed112FromF32(N64_FB_HEIGHT); + cmd.yh = 0; + cmd.xl = Fixed1616FromF32(N64_FB_WIDTH); + cmd.dxldy = 0; + cmd.xh = 0; + cmd.dxhdy = 0; + cmd.xm = Fixed1616FromF32(N64_FB_WIDTH); + cmd.dxmdy = 0; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } +#endif + } +#endif + + //- rjf: set up common main pass parameters +#if 1 + { + // rjf: sync pipe + { + N64_DP_Cmd_Stub cmd = {0}; + cmd.id = N64_DP_CmdID_SyncPipe; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set color image to framebuffer color + { + N64_DP_Cmd_SetColorImage cmd = {0}; + cmd.id = N64_DP_CmdID_SetColorImage; + cmd.format = N64_DP_PixelFormat_RGBA; + cmd.pixel_size_kind = N64_DP_PixelSizeKind_16; + cmd.width = N64_FB_WIDTH-1; + cmd.image_paddr = framebuffer_color_paddrs[tick_idx&1]; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set scissor + { + N64_DP_Cmd_SetScissor cmd = {0}; + cmd.id = N64_DP_CmdID_SetScissor; + cmd.lower_right_x = (N64_FB_WIDTH<<2); + cmd.lower_right_y = (N64_FB_HEIGHT<<2); + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set depth image to framebuffer depth +#if 1 + { + N64_DP_Cmd_SetDepthImage cmd = {0}; + cmd.id = N64_DP_CmdID_SetDepthImage; + cmd.image_paddr = framebuffer_depth_paddrs[tick_idx&1]; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } +#endif + + // rjf: set combine mode + { + N64_DP_Cmd_SetCombineMode cmd = {0}; + cmd.v[0] = 0xffffffffffffffffull; // sets all bits high - minus ID, this will fix all mixed colors at 0 - then we just override the inputs we want + cmd.id = N64_DP_CmdID_SetCombineMode; + cmd.rgb_d_1 = N64_DP_Cmd_CombineColorCode_Shade; + cmd.alpha_d_1 = N64_DP_Cmd_CombineColorCode_Shade; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + } +#endif + + //- TODO(rjf): RDP command test +#if 1 + { + // rjf: set scissor + { + N64_DP_Cmd_SetScissor cmd = {0}; + MemoryZeroStruct(&cmd); + cmd.id = N64_DP_CmdID_SetScissor; + cmd.lower_right_x = ((N64_FB_WIDTH)<<2); + cmd.lower_right_y = ((N64_FB_HEIGHT)<<2); + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set other modes + { + N64_DP_Cmd_SetOtherModes cmd = {0}; + MemoryZeroStruct(&cmd); + cmd.id = N64_DP_CmdID_SetOtherModes; + cmd.atomic_prim = 1; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set color image to framebuffer color + { + N64_DP_Cmd_SetColorImage cmd = {0}; + MemoryZeroStruct(&cmd); + cmd.id = N64_DP_CmdID_SetColorImage; + cmd.format = N64_DP_PixelFormat_RGBA; + cmd.pixel_size_kind = N64_DP_PixelSizeKind_16; + cmd.width = N64_FB_WIDTH-1; + cmd.image_paddr = framebuffer_color_paddrs[tick_idx&1]; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: fill screen + { + N64_DP_Cmd_FillTriangleBase cmd = {0}; + MemoryZeroStruct(&cmd); + cmd.id = N64_DP_CmdID_FillTriangleS; + cmd.flags = N64_DP_Cmd_FillTriangleFlag_LeftMajor; + cmd.yl = Fixed112FromF32(N64_FB_HEIGHT); + cmd.ym = Fixed112FromF32(N64_FB_HEIGHT); + cmd.yh = 0; + cmd.xl = Fixed1616FromF32(N64_FB_WIDTH); + cmd.dxldy = 0; + cmd.xh = 0; + cmd.dxhdy = 0; + cmd.xm = Fixed1616FromF32(N64_FB_WIDTH); + cmd.dxmdy = 0; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + { + N64_DP_CmdExt_FillTriangleShading ext = {0}; + ext.r_i = 255; + ext.a_i = 255; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &ext, &n64_r_state.rdp_command_buffer_write_pos); + } + } + } +#endif + + //- rjf: do one tick + B32 keep_running = Run(tick_idx, user_arena, n64_state.game_pads); + + //- rjf: push sync full command +#if 1 + { + N64_DP_Cmd_Stub cmd = {0}; + cmd.id = N64_DP_CmdID_SyncFull; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + //- rjf: push payload to RDP + U32 payload_opl_paddr = ((U32)(n64_r_state.rdp_command_buffer) + n64_r_state.rdp_command_buffer_write_pos) - 0x80000000; + N64_DataCacheHitWriteback(n64_r_state.rdp_command_buffer, n64_r_state.rdp_command_buffer_write_pos); + N64_DP_REGS->end = payload_opl_paddr; + N64_MemoryBarrier(); + + //- rjf: wait for RDP to be done + for(;N64_DP_REGS->current != payload_opl_paddr || N64_DP_REGS->status_flags & (N64_DP_StatusFlag_DMABusy|N64_DP_StatusFlag_PipeBusy);){} +#endif + + //- rjf: wait for last frame presentation to complete before swapping + for(;;) + { + U32 current_scanline = (N64_VI_REGS->v_current >> 1); + if(N64_FB_HEIGHT < current_scanline && current_scanline < N64_FB_HEIGHT + 20) + { + break; + } + } + + //- rjf: swap framebuffer + N64_VI_REGS->origin = framebuffer_color_paddrs[tick_idx&1]; + N64_MemoryBarrier(); + + //- rjf: exit if keep_running is off + if(!keep_running) + { + break; + } + } +} + +//////////////////////////////// +//~ rjf: Built-In Resources + +#define B +#define _ 0, +#define X 1, + +U8 n64_dgtlgrove_logo_32x48[32*48] = +{ + B + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ X _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ X X _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ X X _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ X _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ X _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ X _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ X X _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ X X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ X _ X X _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ X X _ _ X _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ _ X X _ X _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ X X X _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ X X X _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X X _ _ X X _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ X X _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ X X X _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X X X X _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X X X _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +}; + +U8 n64_controller_icon_32x32[32*32] = +{ + B + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ X X X X X X X X X X _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ X _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ X _ X _ _ _ _ _ _ _ + _ _ _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ X _ X _ X _ _ _ _ _ _ + _ _ _ _ _ _ X _ X X _ _ _ _ _ X X _ _ _ _ _ X _ _ X _ _ _ _ _ _ + _ _ _ _ _ _ X X X X X _ _ _ _ X X _ _ _ X X _ _ _ X _ _ _ _ _ _ + _ _ _ _ _ _ X X X X X _ _ _ _ _ _ _ _ _ X X _ _ _ X _ _ _ _ _ _ + _ _ _ _ _ _ X _ X X _ _ _ _ _ X X _ _ _ _ _ X X _ X _ _ _ _ _ _ + _ _ _ _ _ _ X _ _ _ _ _ X _ X X X X _ X _ _ X X _ X _ _ _ _ _ _ + _ _ _ _ _ _ X _ _ _ _ X _ X X X X X X _ X _ _ _ _ X _ _ _ _ _ _ + _ _ _ _ _ _ X _ _ _ _ X _ X _ X X _ X _ X _ _ _ _ X _ _ _ _ _ _ + _ _ _ _ _ _ X _ _ _ _ X _ X _ _ _ _ X _ X _ _ _ _ X _ _ _ _ _ _ + _ _ _ _ _ _ _ X _ _ X _ _ X _ _ _ _ X _ _ X _ _ X _ _ _ _ _ _ _ + _ _ _ _ _ _ _ X _ _ X _ _ X _ _ _ _ X _ _ X _ _ X _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ X X _ _ _ X _ _ _ _ X _ _ _ X X _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ X _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ X _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ X _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ +}; + +N64_BigDefaultFontGlyph n64_big_default_font_glyphs[256] = +{ + ['A'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ X _ X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X X X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['a'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ _ _ _ _ X _ + _ _ X X X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['B'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['b'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['C'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X X _ _ + _ _ X _ _ X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ X _ _ X _ + _ _ _ X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['c'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['D'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X _ _ _ + _ X _ _ X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ X _ _ + _ X X X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['d'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ X X X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['E'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['e'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X X X X X _ + _ X _ _ _ _ _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['F'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['f'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ X X _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X X X X X _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X X _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['G'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X X _ _ + _ _ X _ _ X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ X X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X _ _ X _ + _ _ _ X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['g'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X X _ + _ _ _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + } + }, + ['H'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X X X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['h'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['I'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X X _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['i'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ X X X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['J'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X X X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ X _ _ X _ _ + _ _ X X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['j'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ X X X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X _ X _ _ _ + _ _ X _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['K'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ X _ _ + _ X _ X _ _ _ + _ X X X _ _ _ + _ X _ _ X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['k'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ X _ _ + _ X _ X _ _ _ + _ X X X _ _ _ + _ X _ _ X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['L'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['l'] = + { + { + B + _ _ _ _ _ _ _ + _ X X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['M'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X _ X X _ + _ X X _ X X _ + _ X _ X _ X _ + _ X _ X _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['m'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X _ X _ _ + _ X _ X _ X _ + _ X _ X _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['N'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X _ _ X _ + _ X X _ _ X _ + _ X _ X _ X _ + _ X _ _ X X _ + _ X _ _ X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['n'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['O'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['o'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['P'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X X X _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['p'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X X X _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['Q'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ X X _ + _ _ X X X _ _ + _ _ _ _ X _ _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + } + }, + ['q'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X X _ + _ _ _ _ _ X _ + _ _ _ _ _ X X + _ _ _ _ _ X _ + _ _ _ _ _ _ _ + } + }, + ['R'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X X X X _ _ + _ X _ X _ _ _ + _ X _ _ X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['r'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X _ X X _ _ + _ X X _ _ X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['S'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ X X X _ _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['s'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ _ _ + _ _ X X X _ _ + _ _ _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['T'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X X _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['t'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X X X X X _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['U'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['u'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['V'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['v'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['W'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ X _ X _ + _ X _ X _ X _ + _ X X _ X X _ + _ X X _ X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['w'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ X _ X _ + _ X X _ X X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['X'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['x'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ _ X _ X _ _ + _ _ _ X _ _ _ + _ _ X _ X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['Y'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['y'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X X _ + _ _ _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + } + }, + ['Z'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['z'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ X X _ _ _ + _ X _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['0'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ X _ X _ + _ X _ X _ X _ + _ X _ X _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['1'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ X X _ _ _ + _ X _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['2'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['3'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['4'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ X _ _ X _ + _ X _ _ _ X _ + _ X X X X X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['5'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X _ _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['6'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ X X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['7'] = + { + { + B + _ _ _ _ _ _ _ + _ X X X X X _ + _ X _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ X X X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['8'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['9'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X X _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ X _ _ _ X _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['`'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['~'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X _ _ _ _ + _ X _ X _ X _ + _ _ _ _ X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['!'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ _ X X X _ _ + _ _ X X X _ _ + _ _ X X X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['@'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ X X _ + _ X _ X _ X _ + _ X _ X _ X _ + _ X _ X _ X _ + _ X _ _ X X _ + _ X _ _ _ _ _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['#'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ X X X X X _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ X X X X X _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['$'] = + { + { + B + _ _ _ X _ _ _ + _ _ X X X _ _ + _ X _ X _ X _ + _ X _ X _ _ _ + _ X _ X _ _ _ + _ _ X X X _ _ + _ _ _ X _ X _ + _ _ _ X _ X _ + _ X _ X _ X _ + _ _ X X X _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['%'] = + { + { + B + _ _ _ _ _ _ _ + _ X X _ _ X _ + _ X X _ _ X _ + _ X X _ _ X _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ X _ _ X X _ + _ X _ _ X X _ + _ X _ _ X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['^'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ X _ X _ _ + _ X _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['&'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ X _ _ _ _ + _ _ X X _ _ _ + _ X _ _ X _ X + _ X _ _ _ X _ + _ X _ _ X X _ + _ _ X X _ _ X + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['*'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ X _ X _ X _ + _ _ X X X _ _ + _ _ _ X _ _ _ + _ _ X X X _ _ + _ X _ X _ X _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['('] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + [')'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['-'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['_'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['='] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ X X X X X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['+'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ X X X X X _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['['] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + [']'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ X X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['{'] = + { + B + { + _ _ _ _ _ _ _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['}'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + [';'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X _ _ _ + _ _ X X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X _ _ _ + _ _ X X _ _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + [':'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X _ _ _ + _ _ X X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X _ _ _ + _ _ X X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['\''] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['"'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ _ X _ X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['|'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['\\'] = + { + { + B + _ _ _ _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ X _ _ _ _ + _ _ X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X _ _ + _ _ _ _ X _ _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['.'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X _ _ _ + _ _ X X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + [','] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X X _ _ _ + _ _ X X _ _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['<'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ X _ _ _ _ _ + _ _ X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['>'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ X _ _ _ _ + _ _ _ X _ _ _ + _ _ _ _ X _ _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['?'] = + { + { + B + _ _ _ _ _ _ _ + _ _ X X X _ _ + _ X _ _ _ X _ + _ X _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ X _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, + ['/'] = + { + { + B + _ _ _ _ _ _ _ + _ _ _ _ _ X _ + _ _ _ _ _ X _ + _ _ _ _ X _ _ + _ _ _ _ X _ _ + _ _ _ X _ _ _ + _ _ _ X _ _ _ + _ _ X _ _ _ _ + _ _ X _ _ _ _ + _ X _ _ _ _ _ + _ X _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + _ _ _ _ _ _ _ + } + }, +}; + +N64_SmallDefaultFontGlyph n64_small_default_font_glyphs[256] = +{ + ['A'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['a'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ _ _ X _ + _ _ X X _ + _ X _ X _ + _ _ X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['B'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['b'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['C'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['c'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ _ _ + _ X _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['D'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['d'] = + { + { + B + _ _ _ _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ X X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['E'] = + { + { + B + _ _ _ _ _ + _ X X X _ + _ X _ _ _ + _ X _ _ _ + _ X X _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['e'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X X _ + _ X _ X _ + _ X X X _ + _ X _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['F'] = + { + { + B + _ _ _ _ _ + _ X X X _ + _ X _ _ _ + _ X _ _ _ + _ X X _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['f'] = + { + { + B + _ _ _ _ _ + _ _ _ X _ + _ _ X _ _ + _ _ X _ _ + _ X X X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['G'] = + { + { + B + _ _ _ _ _ + _ _ X X _ + _ X _ _ _ + _ X _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['g'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X X _ + _ X _ X _ + _ X _ X _ + _ _ X X _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + } + }, + ['H'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['h'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['I'] = + { + { + B + _ _ _ _ _ + _ X X X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['i'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ X _ _ + _ _ _ _ _ + _ X X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['J'] = + { + { + B + _ _ _ _ _ + _ _ X X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ X _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['j'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ X _ _ + _ _ _ _ _ + _ X X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + X _ X _ _ + _ X _ _ _ + } + }, + ['K'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['k'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ X _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['L'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['l'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['M'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X X X _ + _ X X X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['m'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ X _ + _ X X X _ + _ X X X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['N'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ X X X _ + _ X X X _ + _ X X X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['n'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['O'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['o'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['P'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['p'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + } + }, + ['Q'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X X _ + _ _ _ X X + _ _ _ _ _ + } + }, + ['q'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X X _ + _ X _ X _ + _ X _ X _ + _ _ X X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X X + } + }, + ['R'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['r'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X X _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['S'] = + { + { + B + _ _ _ _ _ + _ _ X X _ + _ X _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['s'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X X _ + _ X _ _ _ + _ X X X _ + _ _ _ X _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['T'] = + { + { + B + _ _ _ _ _ + _ X X X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['t'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ _ X _ _ + _ X X X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['U'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['u'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['V'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['v'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['W'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ X X X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['w'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ X X X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['X'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['x'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['Y'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['y'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X X _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + } + }, + ['Z'] = + { + { + B + _ _ _ _ _ + _ X X X _ + _ _ _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X _ _ _ + _ X _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['z'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X X _ + _ _ _ X _ + _ _ X _ _ + _ X _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['0'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ X X X _ + _ X X X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['1'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['2'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ X _ _ + _ X _ _ _ + _ X _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['3'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['4'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ X _ + _ _ X _ _ + _ X _ _ _ + _ X _ X _ + _ X X X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['5'] = + { + { + B + _ _ _ _ _ + _ X X X _ + _ X _ _ _ + _ X _ _ _ + _ X X _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['6'] = + { + { + B + _ _ _ _ _ + _ _ X X _ + _ X _ _ _ + _ X _ _ _ + _ X X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['7'] = + { + { + B + _ _ _ _ _ + _ X X X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['8'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['9'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ _ X X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['`'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ _ X _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['~'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ _ _ + X _ X _ X + _ _ _ X _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['!'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ X _ _ + _ _ _ _ _ + } + }, + ['@'] = + { + { + B + _ _ _ _ _ + _ X X X _ + X _ _ _ X + X _ _ X X + X _ X _ X + X _ X _ X + X _ X _ X + X _ _ X X + X _ _ _ _ + _ X X X _ + _ _ _ _ _ + } + }, + ['#'] = + { + { + B + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + X X X X X + _ X _ X _ + _ X _ X _ + _ X _ X _ + X X X X X + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + } + }, + ['$'] = + { + { + B + _ _ X _ _ + _ _ X X _ + _ X _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ X X _ _ + _ _ X _ _ + _ _ _ _ _ + } + }, + ['%'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X X _ _ + _ _ _ X _ + _ _ X _ _ + _ X _ _ _ + _ _ X X _ + _ _ X X _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['^'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['&'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ X _ _ + _ X X X _ + _ X _ X X + _ X _ X _ + _ _ X _ X + _ _ _ _ _ + } + }, + ['*'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X _ _ + _ X X X _ + _ _ X _ _ + _ X X X _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['('] = + { + { + B + _ _ _ _ _ + _ _ _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ X _ + _ _ _ _ _ + } + }, + [')'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X _ _ _ + _ _ _ _ _ + } + }, + ['-'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['_'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + X X X X X + } + }, + ['='] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X X _ + _ _ _ _ _ + _ X X X _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['+'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ X _ _ + _ _ X _ _ + _ X X X _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['['] = + { + { + B + _ _ _ _ _ + _ _ X X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X X _ + _ _ _ _ _ + } + }, + [']'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X X _ _ + _ _ _ _ _ + } + }, + ['{'] = + { + B + { + _ _ _ _ _ + _ _ _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ X _ + _ _ _ _ _ + } + }, + ['}'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X _ _ _ + _ _ _ _ _ + } + }, + [';'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X X _ _ + _ _ X _ _ + _ X _ _ _ + } + }, + [':'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['\''] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['"'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ X _ X _ + _ X _ X _ + _ X _ X _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['|'] = + { + { + B + _ _ _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + } + }, + ['\\'] = + { + { + B + _ _ _ _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ _ _ + } + }, + ['.'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + [','] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X X _ _ + _ X X _ _ + _ _ X _ _ + _ X _ _ _ + } + }, + ['<'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ X _ + _ _ X _ _ + _ X _ _ _ + _ _ X _ _ + _ _ _ X _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['>'] = + { + { + B + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ X _ _ _ + _ _ X _ _ + _ _ _ X _ + _ _ X _ _ + _ X _ _ _ + _ _ _ _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['?'] = + { + { + B + _ _ _ _ _ + _ X X _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ X _ _ + _ _ _ _ _ + _ _ X _ _ + _ _ _ _ _ + _ _ _ _ _ + } + }, + ['/'] = + { + { + B + _ _ _ _ _ + _ _ _ X _ + _ _ _ X _ + _ _ _ X _ + _ _ X _ _ + _ _ X _ _ + _ _ X _ _ + _ X _ _ _ + _ X _ _ _ + _ X _ _ _ + _ _ _ _ _ + } + }, +}; + +#undef B +#undef X +#undef _ diff --git a/code/n64/n64.h b/code/n64/n64.h new file mode 100644 index 0000000..3a31cc9 --- /dev/null +++ b/code/n64/n64.h @@ -0,0 +1,1158 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +// This code was adapted from the excellent work by the authors of the +// libdragon project (https://libdragon.dev). libdragon is licensed under +// The Unlicense: +// +// This is free and unencumbered software released into the public domain. +// +// Anyone is free to copy, modify, publish, use, compile, sell, or distribute +// this software, either in source code form or as a compiled binary, for any +// purpose, commercial or non-commercial, and by any means. +// +// In jurisdictions that recognize copyright laws, the author or authors of +// this software dedicate any and all copyright interest in the software to the +// public domain. We make this dedication for the benefit of the public at +// large and to the detriment of our heirs and successors. We intend this +// dedication to be an overt act of relinquishment in perpetuity of all present +// and future rights to this software under copyright law. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// For more information, please refer to http://unlicense.org + +#ifndef N64_H +#define N64_H + +//////////////////////////////// +//~ rjf: Console Kinds + +typedef enum N64_ConsoleKind +{ + N64_ConsoleKind_N64, + N64_ConsoleKind_IQue, + N64_ConsoleKind_Analogue3D, + N64_ConsoleKind_ModRetroM64, +} +N64_ConsoleKind; + +//////////////////////////////// +//~ rjf: Boot Info (defined by libdragon's IPL3) + +typedef enum N64_TVKind +{ + N64_TVKind_PAL = 0, + N64_TVKind_NTSC = 1, + N64_TVKind_MPAL = 2, + N64_TVKind_COUNT = 3, +} +N64_TVKind; + +typedef U32 N64_BootInfoFlags; +enum +{ + N64_BootInfoFlag_IsIQue = (1<<0), + N64_BootInfoFlags_ROMKindMask = 0xff000000u, + N64_BootInfoFlags_ROMKindShift = 24, + N64_BootInfoFlags_TVKindMask = 0x00ff0000u, + N64_BootInfoFlags_TVKindShift = 16, + N64_BootInfoFlags_ResetKindMask = 0x0000ff00u, + N64_BootInfoFlags_ResetKindShift= 8, +}; + +typedef struct N64_BootInfo N64_BootInfo; +struct N64_BootInfo +{ + U32 memory_size; + U32 entropy; + N64_BootInfoFlags flags; + U32 rom_addr; +}; + +StaticAssert(sizeof(N64_BootInfo) == 16, n64_boot_info_size_check); + +#define N64_BOOT_INFO ((volatile N64_BootInfo *)0xA4000000) + +//////////////////////////////// +//~ rjf: Exceptions + +typedef U32 N64_ExceptionCode; +enum +{ + N64_ExceptionCode_Interrupt, + N64_ExceptionCode_TLBModification, + N64_ExceptionCode_TLBLoadMiss, + N64_ExceptionCode_TLBStoreMiss, + N64_ExceptionCode_AddressLoadError, + N64_ExceptionCode_AddressStoreError, + N64_ExceptionCode_BusFetchError, + N64_ExceptionCode_BusLoadStoreError, + N64_ExceptionCode_Syscall, + N64_ExceptionCode_Breakpoint, + N64_ExceptionCode_ReservedInstruction, + N64_ExceptionCode_CoprocessorUnusable, + N64_ExceptionCode_ArithmeticOverflow, + N64_ExceptionCode_Trap, + N64_ExceptionCode_Reserved0, + N64_ExceptionCode_FloatingPoint, + N64_ExceptionCode_Reserved1, + N64_ExceptionCode_Reserved2, + N64_ExceptionCode_Reserved3, + N64_ExceptionCode_Reserved4, + N64_ExceptionCode_Reserved5, + N64_ExceptionCode_Reserved6, + N64_ExceptionCode_Reserved7, + N64_ExceptionCode_Watch, + N64_ExceptionCode_Emux, + N64_ExceptionCode_Reserved8, + N64_ExceptionCode_Reserved9, + N64_ExceptionCode_Reserved10, + N64_ExceptionCode_Reserved11, + N64_ExceptionCode_Reserved12, + N64_ExceptionCode_Reserved13, + N64_ExceptionCode_Reserved14, +}; + +global String8 n64_string_from_exception_code_table[] = +{ + Str8LitComp("Interrupt"), // 0 + Str8LitComp("TLB Modification"), // 1 + Str8LitComp("TLB Miss (load/instruction fetch)"), // 2 + Str8LitComp("TLB Miss (store)"), // 3 + Str8LitComp("Address Error (load/instruction fetch)"), // 4 + Str8LitComp("Address Error (store)"), // 5 + Str8LitComp("Bus Error (instruction fetch)"), // 6 + Str8LitComp("Bus Error (load/store)"), // 7 + Str8LitComp("Syscall"), // 8 + Str8LitComp("Breakpoint"), // 9 + Str8LitComp("Reserved Instruction"), // 10 + Str8LitComp("Coprocessor Unusable"), // 11 + Str8LitComp("Arithmetic Overflow"), // 12 + Str8LitComp("Trap"), // 13 + Str8LitComp("Reserved"), // 14 + Str8LitComp("Floating-Point"), // 15 + Str8LitComp("Reserved"), // 16 + Str8LitComp("Reserved"), // 17 + Str8LitComp("Reserved"), // 18 + Str8LitComp("Reserved"), // 19 + Str8LitComp("Reserved"), // 20 + Str8LitComp("Reserved"), // 21 + Str8LitComp("Reserved"), // 22 + Str8LitComp("Watch"), // 23 + Str8LitComp("Emux"), // 24 + Str8LitComp("Reserved"), // 25 + Str8LitComp("Reserved"), // 26 + Str8LitComp("Reserved"), // 27 + Str8LitComp("Reserved"), // 28 + Str8LitComp("Reserved"), // 29 + Str8LitComp("Reserved"), // 30 + Str8LitComp("Reserved"), // 31 +}; + +typedef struct N64_ExceptionRegState N64_ExceptionRegState; +struct N64_ExceptionRegState +{ + //- rjf: GPRs 1-32 + union + { + U64 gpr[32]; + struct + { + U64 zr, at, v0, v1, a0, a1, a2, a3; + U64 t0, t1, t2, t3, t4, t5, t6, t7; + U64 s0, s1, s2, s3, s4, s5, s6, s7; + U64 t8, t9, k0, k1, gp, sp, fp, ra; + }; + }; + + //- rjf: hi/lo/sr/cr/epc + U64 lo; + U64 hi; + U32 sr; + U32 cr; + U32 epc; + + //- rjf: FC31 + U32 fc31; + + //- rjf: FPRs 1-32 + U64 fpr[32]; +} +__attribute__((packed)); +StaticAssert(sizeof(N64_ExceptionRegState) == 544, n64_exception_reg_state_size_check); + +//////////////////////////////// +//~ rjf: Cache Instruction Specifications + +//- rjf: cache size defines +#define N64_CACHE_INST_SIZE (16 * 1024) +#define N64_CACHE_INST_LINESIZE (32) +#define N64_CACHE_DATA_SIZE (8 * 1024) +#define N64_CACHE_DATA_LINESIZE (16) + +//- rjf: cache kinds +typedef enum N64_CacheKind +{ + N64_CacheKind_Inst = 0, + N64_CacheKind_Data = 1, +} +N64_CacheKind; + +//- rjf: cache op kind (VR4300 manual, pg 404) +typedef enum N64_CacheOpKind +{ + N64_CacheOpKind_IndexInvalidate = 0, + N64_CacheOpKind_IndexWritebackInvalidate = 0, + N64_CacheOpKind_LoadTag = 1, + N64_CacheOpKind_StoreTag = 2, + N64_CacheOpKind_CreateDirty = 3, + N64_CacheOpKind_HitInvalidate = 4, + N64_CacheOpKind_HitWritebackInvalidate = 5, + N64_CacheOpKind_Fill = 5, + N64_CacheOpKind_HitWriteback = 6, +} +N64_CacheOpKind; + +//- rjf: cache opcode builder. from libdragon: +// +// operation is encoded in 5 bits where bits 4..2 contain the operation type +// and bits 0..1 determine the cache that is to be operated on. +// +#define N64_CacheOpcode(op_kind, cache_kind) (((op_kind) << 2) | (cache_kind)) + +//- rjf: cache op implementation helper +#define N64_CacheOp(op_kind, cache_kind) ({\ +if(length)\ +{\ +int linesize = (cache_kind == N64_CacheKind_Data) ? N64_CACHE_DATA_LINESIZE : N64_CACHE_INST_LINESIZE;\ +void *cur = (void*)((unsigned long)addr & ~(linesize-1));\ +int count = (int)length + (addr-cur);\ +for(int i = 0; i < count; i += linesize)\ +{\ +asm ("\tcache %0,(%1)\n"::"i" ((((op_kind) << 2) | (cache_kind))), "r" (cur+i));\ +}\ +} \ +}) + +//////////////////////////////// +//~ rjf: Intrinsics + +#define N64_MemoryBarrier() asm volatile ("" : : : "memory") + +//////////////////////////////// +//~ rjf: PIF Specs + +#define N64_PIF_RAM (void *)0x1fc007c0 + +//////////////////////////////// +//~ rjf: Serial Interface Specs + +//- rjf: SI status bits +#define N64_SI_STATUS_DMA_BUSY (1<<0) +#define N64_SI_STATUS_IO_BUSY (1<<1) + +//- rjf: SI register layout +typedef struct N64_SI_Regs N64_SI_Regs; +struct N64_SI_Regs +{ + U32 dram_addr; + U32 pif_addr_read; + U32 reserved_0; + U32 reserved_1; + U32 pif_addr_write; + U32 reserved_2; + U32 status; +}; + +//- rjf: SI register pointer +#define N64_SI_REGS ((volatile N64_SI_Regs *)0xA4800000u) + +//////////////////////////////// +//~ rjf: JoyBus Specs + +#define N64_JOYBUS_PORT_COUNT 5 // [0, 4) -> Controller Slots, 5 -> EEPROM / real-time clock + +#define N64_JOYBUS_COMMAND_SKIP_SIZE 1 // Size of a Joybus command to "skip this port" in bytes. +#define N64_JOYBUS_COMMAND_METADATA_SIZE 2 // Size of the Joybus command metadata fields in bytes. +#define N64_JOYBUS_COMMAND_OFFSET_SEND_LEN 0 // Offset of the Joybus command "send length" field in bytes. +#define N64_JOYBUS_COMMAND_OFFSET_RECV_LEN 1 // Offset of the Joybus command "receive length" field in bytes. +#define N64_JOYBUS_COMMAND_OFFSET_COMMAND_ID 2 // Offset of the Joybus command "command ID" field in bytes. + +typedef U8 N64_JoyBus_CmdID; +typedef enum N64_JoyBus_CmdIDEnum +{ + N64_JoyBus_CmdID_Reset = 0xFF, // "Reset" Joybus command identifier. + N64_JoyBus_CmdID_Identify = 0x00, // "Identify" Joybus command identifier. + N64_JoyBus_CmdID_N64ControllerRead = 0x01, // "N64 Controller Read" Joybus command identifier. + N64_JoyBus_CmdID_N64AccessoryRead = 0x02, // "N64 Accessory Read" Joybus command identifier. + N64_JoyBus_CmdID_N64AccessoryWrite = 0x03, // "N64 Accessory Write" Joybus command identifier. + N64_JoyBus_CmdID_EEPROMReadBlock = 0x04, // "EEPROM Read Block" Joybus command identifier. + N64_JoyBus_CmdID_EEPROMWriteBlock = 0x05, // "EEPROM Write Block" Joybus command identifier. + N64_JoyBus_CmdID_RTCIdentify = 0x06, // "Real-Time Clock Identify" Joybus command identifier. + N64_JoyBus_CmdID_RTCReadBlock = 0x07, // "Real-Time Clock Read Block" Joybus command identifier. + N64_JoyBus_CmdID_RTCWriteBlock = 0x08, // "Real-Time Clock Write Block" Joybus command identifier. + N64_JoyBus_CmdID_N64RandnetKeyboardRead = 0x13, // "N64 Randnet Keyboard Read" Joybus command identifier. + N64_JoyBus_CmdID_64GBLinkCableRead = 0x13, // "64GB Link Cable Read" Joybus command identifier. + N64_JoyBus_CmdID_GBALinkCableRead = 0x14, // "GBA Link Cable Read" Joybus command identifier. + N64_JoyBus_CmdID_64GBLinkCableWrite = 0x14, // "64GB Link Cable Write" Joybus command identifier. + N64_JoyBus_CmdID_GBALinkCableWrite = 0x15, // "GBA Link Cable Write" Joybus command identifier. + N64_JoyBus_CmdID_PixelFXN64GameID = 0x1D, // "PixelFX N64 Game ID" Joybus command identifier. (https://gitlab.com/pixelfx-public/n64-game-id#how-to-integrate-on-n64) + N64_JoyBus_CmdID_GCNControllerRead = 0x40, // "GameCube Controller Read" Joybus command identifier. + N64_JoyBus_CmdID_GCNControllerOrigin = 0x41, // "GameCube Controller Read Origins" Joybus command identifier. + N64_JoyBus_CmdID_GCNControllerRecalibrate = 0x42, // "GameCube Controller Recalibrate" Joybus command identifier. Invalidates the origins of the GameCube controller. + N64_JoyBus_CmdID_GCNControllerReadLong = 0x43, // "GameCube Controller 'Long Read'" Joybus command identifier. Used by GameCube mode 0, mode 1, mode 2, and mode 4 to read extended controller data, including analog A and B button values. +} +N64_JoyBus_CmdIDEnum; + +typedef struct N64_JoyBus_CmdIDInfo N64_JoyBus_CmdIDInfo; +struct N64_JoyBus_CmdIDInfo +{ + U8 send_size; + U8 recv_size; +}; + +typedef U16 N64_JoyBus_ID; +enum +{ + N64_JoyBus_ID_None = 0x0000, + N64_JoyBus_ID_N64VoiceRecognition = 0x0001, + N64_JoyBus_ID_N64RandnetKeyboard = 0x0002, + N64_JoyBus_ID_64GBLinkCable = 0x0003, + N64_JoyBus_ID_GBALinkCable = 0x0004, + N64_JoyBus_ID_CartRTC = 0x0010, + N64_JoyBus_ID_CartEEProm4KBit = 0x0080, + N64_JoyBus_ID_CartEEProm16KBit = 0x00C0, + N64_JoyBus_ID_N64Controller = 0x0500, + N64_JoyBus_ID_N64Mouse = 0x0200, + // + // NOTE(rjf): some IDs are excluded, for GCN controllers etc. - at that point, + // this ID becomes not just an enum, but a bitfield. check joybus.h in + // libdragon for more info, if that becomes relevant. + // +}; + +typedef U8 N64_JoyBus_PortStatus; +enum +{ + N64_JoyBus_PortStatus_AccessoryUnsupported = 0x00, // Joybus identify status for an N64 controller that does not support accessories. Some third-party controllers incorrectly use this status to mean absence of an accessory. + N64_JoyBus_PortStatus_AccessoryPresent = 0x01, // Joybus identify status for an N64 controller with an accessory present. + N64_JoyBus_PortStatus_AccessoryAbsent = 0x02, // Joybus identify status for an N64 controller with no accessory present. + N64_JoyBus_PortStatus_AccessoryChanged = 0x03, // Joybus identify status for an N64 controller with an accessory present that has changed since it was last identified. + N64_JoyBus_PortStatus_AccessoryMask = 0x03, // Joybus identify status byte mask for N64 accessory presence values. + N64_JoyBus_PortStatus_VoiceRecognitionReady = 0x01, // Joybus identify status bit for a VRU/VRS that is initialized and ready. + N64_JoyBus_PortStatus_CommandChecksumError = 0x04, // Joybus identify status bit that signifies the previous accessory command had a checksum error. + N64_JoyBus_PortStatus_GCNRumbleActive = 0x08, // Joybus identify status bit for GameCube controllers that indicates whether the rumble motor is currently active. + N64_JoyBus_PortStatus_EEPromBusy = 0x80, // Joybus identify status bit for EEPROM devices that indicates a write is in-progress. +}; + +pack_begin + +typedef struct N64_JoyBus_CmdPayload_Identify N64_JoyBus_CmdPayload_Identify; +struct N64_JoyBus_CmdPayload_Identify +{ + struct + { + N64_JoyBus_CmdID cmd_id; + } send; + struct + { + N64_JoyBus_ID id; + N64_JoyBus_PortStatus status; + } recv; +}; +typedef N64_JoyBus_CmdPayload_Identify N64_JoyBus_CmdPayload_Reset; + +typedef struct N64_JoyBus_CmdPayload_N64ControllerRead N64_JoyBus_CmdPayload_N64ControllerRead; +struct N64_JoyBus_CmdPayload_N64ControllerRead +{ + struct + { + N64_JoyBus_CmdID cmd_id; + } send; + struct + { + U32 a : 1; // A button + U32 b : 1; // B button + U32 z : 1; // Z button + U32 start : 1; // Start button + U32 d_up : 1; // D-Pad up + U32 d_down : 1; // D-Pad down + U32 d_left : 1; // D-Pad left + U32 d_right : 1; // D-Pad right + U32 reset : 1; // Reset flag + U32 padding : 1; // Unused padding + U32 l : 1; // L shoulder button + U32 r : 1; // R shoulder button + U32 c_up : 1; // C-Pad up + U32 c_down : 1; // C-Pad down + U32 c_left : 1; // C-Pad left + U32 c_right : 1; // C-Pad right + S32 stick_x : 8; // Analog stick X-axis + S32 stick_y : 8; // Analog stick Y-axis + } recv; +}; + +pack_end + +//////////////////////////////// +//~ rjf: JoyBus Interaction State Types + +typedef enum N64_JoyBus_Status +{ + N64_JoyBus_Status_Idle, + N64_JoyBus_Status_Sending, + N64_JoyBus_Status_Recving, +} +N64_JoyBus_Status; + +typedef struct N64_JoyBus_PortState N64_JoyBus_PortState; +struct N64_JoyBus_PortState +{ + U32 connect_gen; // incremented on every connection change + N64_JoyBus_ID id; + U8 status; +}; + +typedef struct N64_JoyBus_Cmd N64_JoyBus_Cmd; +struct align(64) N64_JoyBus_Cmd +{ + U8 send_payload[64]; + U8 recv_payload[64]; + N64_JoyBus_CmdID cmd_id; +}; + +//////////////////////////////// +//~ rjf: Timing Presets For TV Kinds + +typedef struct N64_TimingPreset N64_TimingPreset; +struct N64_TimingPreset +{ + U32 h_total; + U32 h_total_leap; + U32 v_total; + U32 burst; + U32 v_burst; + U32 display_x0; + U32 display_y0; + U32 display_width; + U32 display_height; +}; + +//////////////////////////////// +//~ rjf: MIPS Interface Specs + +typedef U32 N64_MI_MaskCmd; +enum +{ + N64_MI_MaskCmd_SetMD_iQue = 0x08000000, // Enable memory card interrupt (iQue) + N64_MI_MaskCmd_ClrMD_iQue = 0x04000000, // Disable memory card interrupt (iQue) + N64_MI_MaskCmd_SetBtn_iQue = 0x02000000, // Enable power button interrupt (iQue) + N64_MI_MaskCmd_ClrBtn_iQue = 0x01000000, // Disable power button interrupt (iQue) + N64_MI_MaskCmd_SetUSB1_iQue = 0x00800000, // Enable USB1 interrupt (iQue) + N64_MI_MaskCmd_ClrUSB1_iQue = 0x00400000, // Disable USB1 interrupt (iQue) + N64_MI_MaskCmd_SetUSB0_iQue = 0x00200000, // Enable USB0 interrupt (iQue) + N64_MI_MaskCmd_ClrUSB0_iQue = 0x00100000, // Disable USB0 interrupt (iQue) + N64_MI_MaskCmd_SetPIErr_iQue = 0x00080000, // Enable PI error interrupt (iQue) + N64_MI_MaskCmd_ClrPIErr_iQue = 0x00040000, // Disable PI error interrupt (iQue) + N64_MI_MaskCmd_SetIDE_iQue = 0x00020000, // Enable IDE interrupt (iQue) + N64_MI_MaskCmd_ClrIDE_iQue = 0x00010000, // Disable IDE interrupt (iQue) + N64_MI_MaskCmd_SetAES_iQue = 0x00008000, // Enable AES interrupt (iQue) + N64_MI_MaskCmd_ClrAES_iQue = 0x00004000, // Disable AES interrupt (iQue) + N64_MI_MaskCmd_SetFlash_iQue = 0x00002000, // Enable flash interrupt (iQue) + N64_MI_MaskCmd_ClrFlash_iQue = 0x00001000, // Disable flash interrupt (iQue) + N64_MI_MaskCmd_SetDP = 0x00000800, // Enable RDP SYNC_FULL interrupt + N64_MI_MaskCmd_ClrDP = 0x00000400, // Disable RDP SYNC_FULL interrupt + N64_MI_MaskCmd_SetPI = 0x00000200, // Enable PI transfer interrupt + N64_MI_MaskCmd_ClrPI = 0x00000100, // Disable PI transfer interrupt + N64_MI_MaskCmd_SetVI = 0x00000080, // Enable VI line match interrupt + N64_MI_MaskCmd_ClrVI = 0x00000040, // Disable VI line match interrupt + N64_MI_MaskCmd_SetAI = 0x00000020, // Enable AI buffer playback interrupt + N64_MI_MaskCmd_ClrAI = 0x00000010, // Disable AI buffer playback interrupt + N64_MI_MaskCmd_SetSI = 0x00000008, // Enable SI transfer interrupt + N64_MI_MaskCmd_ClrSI = 0x00000004, // Disable SI transfer interrupt + N64_MI_MaskCmd_SetSP = 0x00000002, // Enable RSP interrupt + N64_MI_MaskCmd_ClrSP = 0x00000001, // Disable RSP interrupt +}; + +typedef U32 N64_MI_InterruptStatusMask; +enum +{ + N64_MI_InterruptStatusMask_BBMDState = 0x02000000, // 1 if the memory card is present, 0 if missing (iQue) + N64_MI_InterruptStatusMask_BBBtnState = 0x01000000, // 1 if the power button is pressed, 0 if not (iQue) + N64_MI_InterruptStatusMask_BBMD = 0x00002000, // Pending interrupt: Memory card removed or inserted (iQue) + N64_MI_InterruptStatusMask_BBBtn = 0x00001000, // Pending interrupt: Power button pressed (iQue) + N64_MI_InterruptStatusMask_BBUSB1 = 0x00000800, // Pending interrupt: USB1 (iQue) + N64_MI_InterruptStatusMask_BBUSB0 = 0x00000400, // Pending interrupt: USB0 (iQue) + N64_MI_InterruptStatusMask_BBPIErr = 0x00000200, // Pending interrupt: PI error (iQue) + N64_MI_InterruptStatusMask_BBIDE = 0x00000100, // Pending interrupt: IDE (iQue) + N64_MI_InterruptStatusMask_BBAES = 0x00000080, // Pending interrupt: AES interrupt (iQue) + N64_MI_InterruptStatusMask_BBFlash = 0x00000040, // Pending interrupt: Flash (iQue) + N64_MI_InterruptStatusMask_DP = 0x00000020, // Pending interrupt: RDP SYNC_FULL has finished + N64_MI_InterruptStatusMask_PI = 0x00000010, // Pending interrupt: PI transfer finished + N64_MI_InterruptStatusMask_VI = 0x00000008, // Pending interrupt: VI line match (normally, vblank) + N64_MI_InterruptStatusMask_AI = 0x00000004, // Pending interrupt: AI buffer playback started + N64_MI_InterruptStatusMask_SI = 0x00000002, // Pending interrupt: SI transfer finished + N64_MI_InterruptStatusMask_SP = 0x00000001, // Pending interrupt: RSP interrupt requested +}; + +#define N64_MI_VERSION ((volatile U32 *)0xA4300004) +#define N64_MI_INTERRUPT ((volatile U32 *)0xA4300008) +#define N64_MI_MASK ((volatile U32 *)0xA430000C) + +//////////////////////////////// +//~ rjf: Visual Interface Specs + +//- rjf: VI CTRL register definitions + +typedef U32 N64_VI_CtrlReg; +#define N64_VI_CtrlRegVal(kind, aa_mode, pixel_advance, flags) ((kind) | (aa_mode) | (pixel_advance) | (flags)) + +typedef enum N64_VI_CtrlKind +{ + N64_VI_CtrlKind_None = 0x0, // turn off VI - TVs show static, black, or no signal. + N64_VI_CtrlKind_16bpp = 0x2, // framebuffer source -> 16-bit, RGBA5551 + N64_VI_CtrlKind_32bpp = 0x3, // framebuffer source -> 32-bit +} +N64_VI_CtrlKind; + +typedef enum N64_VI_AAMode +{ + N64_VI_AAMode_None = (0b11<<8), // disable. hardware bug on NTSC units; do not use this mode in 16bpp mode, or widths <= 320px. + N64_VI_AAMode_Resample = (0b10<<8), // enable bilinear filtering during resampling. not compatible with dedithering. + N64_VI_AAMode_ResampleFetchNeeded = (0b01<<8), // smooth external edges of polygons. can cause corruption in high bandwidth, e.g. high-res. use FetchAlways in those cases (at a performance cost). + N64_VI_AAMode_ResampleFetchAlways = (0b00<<8), // similar to FetchNeeded, but exact difference is unknown - it is slower, fixes some FetchNeeded artifacts, and is broken in 32bpp mode. +} +N64_VI_AAMode; + +typedef enum N64_VI_PixelAdvance +{ + N64_VI_PixelAdvance_Default = 0b11<<12, // default + N64_VI_PixelAdvance_BBPlayer = 0b01<<12, // default on iQue devices +} +N64_VI_PixelAdvance; + +typedef U32 N64_VI_CtrlFlags; +enum +{ + N64_VI_CtrlFlag_GammaDitherEnable = (1<<2), + N64_VI_CtrlFlag_GammaCorrect = (1<<3), + N64_VI_CtrlFlag_Divot = (1<<4), // "divot filter" - fixes 1 pixel holes after AA + N64_VI_CtrlFlag_Serrate = (1<<6), // interlaced output + N64_VI_CtrlFlag_Dedither = (1<<16), +}; + +//- rjf: VI register layout + +typedef struct N64_VI_Regs N64_VI_Regs; +struct N64_VI_Regs +{ + N64_VI_CtrlReg ctrl; // VI configuration register + U32 origin; // physical address of framebuffer + U32 width; // width in pixels of framebuffer + U32 v_intr; // vertical interrupt - which half-line you want the interrupt on? + U32 v_current; // current half-line + U32 burst; // sync/burst values (?) + U32 v_total; // total visible and non-visible lines (?) + U32 h_total; // total width of a line + U32 h_total_leap; // alternative scanline length for one scanline during vsync (?) + U32 h_video; // start/end of active video image, in screen pixels + U32 v_video; // start/end of active video image, in screen half-lines + U32 v_burst; // start/end of color burst enable, in half-lines (?) + U32 x_scale; // horizontal subpixel offset; 1/x-scale-up factor (?) + U32 y_scale; // vertical subpixel offset; 1/y-scale-up factor (?) +}; + +//- rjf: VI register pointer +#define N64_VI_REGS ((volatile N64_VI_Regs *)0xA4400000u) + +//////////////////////////////// +//~ rjf: Display Processor (DP, RDP) Specs +// +// https://n64brew.dev/wiki/Reality_Display_Processor +// + +//- rjf: DP command IDs + +typedef U8 N64_DP_CmdID; +enum +{ + N64_DP_CmdID_NoOp = 0x00, + + //- rjf: triangle fills [Z: depth, T -> texture, S -> shade] + N64_DP_CmdID_FillTriangle = 0x08, + N64_DP_CmdID_FillTriangleZ = 0x09, + N64_DP_CmdID_FillTriangleT = 0x0A, + N64_DP_CmdID_FillTriangleZT = 0x0B, + N64_DP_CmdID_FillTriangleS = 0x0C, + N64_DP_CmdID_FillTriangleSZ = 0x0D, + N64_DP_CmdID_FillTriangleST = 0x0E, + N64_DP_CmdID_FillTriangleSTZ = 0x0F, + + //- rjf: textured rectangles + N64_DP_CmdID_TextureRectangle = 0x24, + N64_DP_CmdID_TextureRectangleFlip = 0x25, + + //- rjf: syncs (partial syncs do not wait on any internal signal - they always wait/waste N GCLK cycles.) + N64_DP_CmdID_SyncLoad = 0x26, // Stalls the RDP pipeline for exactly 25 GCLK cycles. This guarantees that the loading pipeline will be available for use following any prior operation. + N64_DP_CmdID_SyncPipe = 0x27, // Stalls the RDP pipeline for exactly 50 GCLK cycles. This guarantees that any preceding primitives will be fully rendered and it is safe to modify rendering attributes such as color registers, othermodes and combine mode. + N64_DP_CmdID_SyncTile = 0x28, // Stalls the RDP pipeline for exactly 33 GCLK cycles. This guarantees that any preceding primitives will have finished using tile information and that it is now safe to modify tile descriptors without affecting prior primitives. + N64_DP_CmdID_SyncFull = 0x29, // Waits for all currently staged pipeline and memory operations to complete before halting the RDP pipeline counter and raising the DP interrupt in the MIPS Interface. + + //- rjf: chroma keys / color conversions + N64_DP_CmdID_SetKeyGB = 0x2A, + N64_DP_CmdID_SetKeyR = 0x2B, + N64_DP_CmdID_SetConvert = 0x2C, + + //- rjf: scissors + N64_DP_CmdID_SetScissor = 0x2D, + + //- rjf: primitive depth + N64_DP_CmdID_SetPrimitiveDepth = 0x2E, + + //- rjf: set other modes + N64_DP_CmdID_SetOtherModes = 0x2F, + + //- rjf: texture settings + N64_DP_CmdID_LoadTLUT = 0x30, + N64_DP_CmdID_SetTileSize = 0x32, + N64_DP_CmdID_LoadBlock = 0x33, + N64_DP_CmdID_LoadTile = 0x34, + N64_DP_CmdID_SetTile = 0x35, + + //- rjf: rectangle fills + N64_DP_CmdID_FillRectangle = 0x36, + + //- rjf: color settings + N64_DP_CmdID_SetFillColor = 0x37, + N64_DP_CmdID_SetFogColor = 0x38, + N64_DP_CmdID_SetBlendColor = 0x39, + N64_DP_CmdID_SetPrimitiveColor = 0x3A, + N64_DP_CmdID_SetEnvironmentColor = 0x3B, + + //- rjf: combine mode + N64_DP_CmdID_SetCombineMode = 0x3C, + + //- rjf: RDRAM image addresses / settings + N64_DP_CmdID_SetTextureImage = 0x3D, // Sets the texture image, the location in RDRAM where the texture loading pipeline should source image data, and related properties. + N64_DP_CmdID_SetDepthImage = 0x3E, // Sets the depth image, the region in RDRAM where depth information will be stored. The depth image width is the same as the image width for the current color image. The depth buffer format is a fixed 18-bit-per-pixel format irrespective of the color image format. + N64_DP_CmdID_SetColorImage = 0x3F, // Sets the color image address and properties, the region in RDRAM where primitives will be rendered to as pixels. +}; + +//- rjf: DP command structures + +pack_begin + +StaticAssert(BIG_ENDIAN, n64_big_endian_check); + +typedef U8 N64_DP_PixelFormat; +enum +{ + N64_DP_PixelFormat_RGBA = 0, + N64_DP_PixelFormat_YUV = 1, + N64_DP_PixelFormat_CI = 2, + N64_DP_PixelFormat_IA = 3, + N64_DP_PixelFormat_I = 4, +}; + +typedef U8 N64_DP_PixelSizeKind; +enum +{ + N64_DP_PixelSizeKind_4 = 0, + N64_DP_PixelSizeKind_8 = 1, + N64_DP_PixelSizeKind_16 = 2, + N64_DP_PixelSizeKind_32 = 3, +}; + +typedef union N64_DP_Cmd_SetColorImage N64_DP_Cmd_SetColorImage; +union N64_DP_Cmd_SetColorImage +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + N64_DP_PixelFormat format : 3; + N64_DP_PixelSizeKind pixel_size_kind : 2; + U8 padding : 3; + U16 width; + U32 image_paddr; + }; +}; + +typedef union N64_DP_Cmd_SetDepthImage N64_DP_Cmd_SetDepthImage; +union N64_DP_Cmd_SetDepthImage +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U32 flags : 24; + U32 image_paddr; + }; +}; + +typedef union N64_DP_Cmd_SetScissor N64_DP_Cmd_SetScissor; +union N64_DP_Cmd_SetScissor +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U16 upper_left_x : 12; + U16 upper_left_y : 12; + U8 padding : 6; + U8 field : 1; // enable interlaced rendering + U8 odd : 1; // when interlaced rendering, do odd lines or even? + U16 lower_right_x : 12; + U16 lower_right_y : 12; + }; +}; + +typedef union N64_DP_Cmd_SetOtherModes N64_DP_Cmd_SetOtherModes; +union N64_DP_Cmd_SetOtherModes +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U8 atomic_prim : 1; // Enables span buffer coherency, forces active span segments to be written to the frame buffer before reading new span segments + U8 padding_0 : 1; + U8 cycle_type : 2; // Determines pipeline mode. Either 1-Cycle (0), 2-Cycle (1), COPY (2), FILL (3) + U8 persp_tex_en : 1; // Enables perspective correction of texture coordinates + U8 detail_tex_en : 1; // Enables "detail texture" mode in Texture LOD + U8 sharpen_tex_en : 1; // Enables "sharpen texture" mode in Texture LOD + U8 tex_lod_en : 1; // Enables Texture Level of Detail (LOD) + U8 tlut_en : 1; // Enables Texture Look-Up Table (TLUT) sampling. Texels are first fetched from low TMEM that are then used to index a palette in high TMEM to find the final color values. + U8 tlut_type : 1; // Determines TLUT texel format. Either RGBA16 (0) or IA16 (1) + U8 sample_type : 1; // Determines texel sampling mode. Either point-sampled (0) or 2x2 bilinear (1) + U8 mid_texel : 1; // Determines bilinear filter mode. Either 3-point (0) or average mode (1) + U8 bi_lerp_0 : 1; // Determines texture filter mode for the first cycle. Either YUV to RGB conversion (See Set Convert) (0) or bilinear filter (1) + U8 bi_lerp_1 : 1; // Determines texture filter mode for the second cycle. Either YUV to RGB conversion (See Set Convert) (0) or bilinear filter (1) + U8 convert_one : 1; // Determines the input to the second texture filter stage. Either the sample from the second stage of texture sampling (0) or the result from the first texture filter cycle (1) + U8 key_en : 1; // Enables chroma keying following the Color Combiner stage + U8 rgb_dither_sel : 2; // Set RGB dither mode + U8 alpha_dither_sel : 2; // Set Alpha dither mode + U8 padding_1 : 4; + U8 bl_m1a_0 : 2; // Blender input P (first cycle) + U8 bl_m1a_1 : 2; // Blender input P (second cycle) + U8 bl_m1b_0 : 2; // Blender input A (first cycle) + U8 bl_m1b_1 : 2; // Blender input A (second cycle) + U8 bl_m2a_0 : 2; // Blender input M (first cycle) + U8 bl_m2a_1 : 2; // Blender input M (second cycle) + U8 bl_m2b_0 : 2; // Blender input B (first cycle) + U8 bl_m2b_1 : 2; // Blender input B (second cycle) + U8 padding_2 : 1; + U8 force_blend : 1; // Enables blending for all pixels rather than only edge pixels + U8 alpha_cvg_select : 1; // Use coverage (or coverage multiplied by CC alpha) for alpha input to blender rather than alpha output from CC + U8 cvg_x_alpha : 1; // Multiply coverage and alpha from CC (used in conjunction with alpha_cvg_sel) + U8 z_mode : 2; // Determines z-buffer comparator mode + U8 cvg_dest : 2; // Determines coverage output mode + U8 color_on_cvg : 1; // If enabled, writes the blender output only if coverage overflowed, otherwise write the 2B input verbatim + U8 image_read_en : 1; // Enable color image reading + U8 z_update_en : 1; // Enable z-buffer writing + U8 z_compare_en : 1; // Enable z-buffer reading and depth comparison + U8 antialias_en : 1; // Enable anti-aliasing, which may enable blending on edge pixels + U8 z_source_sel : 1; // Selects either per-pixel (0) or primitive (1) depth as depth source to compare against the z-buffer + U8 dither_alpha_en : 1; // Determines alpha compare threshold source. (0 blend color register alpha, 1 random) + U8 alpha_compare_en : 1; // Enables alpha compare, pixels below the alpha threshold (compared against CC alpha output) are not written + }; +}; + +typedef U8 N64_DP_Cmd_CombineColorCode; +enum +{ + N64_DP_Cmd_CombineColorCode_Combined = 0, // Combined color, from first cycle + N64_DP_Cmd_CombineColorCode_Tex0 = 1, // Texture color sampled from the tile set in the primitive command, after texture LOD if enabled + N64_DP_Cmd_CombineColorCode_Tex1 = 2, // Texture color sampled from the tile set in the primitive command, after texture LOD if enabled, plus one (mod 8, e.g. if TEX0 refers to tile 7, TEX1 refers to tile 0) + N64_DP_Cmd_CombineColorCode_Primitive = 3, // Primitive color register + N64_DP_Cmd_CombineColorCode_Shade = 4, // Shade color interpolated per-pixel from shade coefficients + N64_DP_Cmd_CombineColorCode_Environment = 5, // Environment color register +}; + +typedef union N64_DP_Cmd_SetCombineMode N64_DP_Cmd_SetCombineMode; +union N64_DP_Cmd_SetCombineMode +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U8 rgb_a_0 : 4; + U8 rgb_c_0 : 5; + U8 alpha_a_0 : 3; + U8 alpha_c_0 : 3; + U8 rgb_a_1 : 4; + U8 rgb_c_1 : 5; + U8 rgb_b_0 : 4; + U8 rgb_b_1 : 4; + U8 alpha_a_1 : 3; + U8 alpha_c_1 : 3; + U8 rgb_d_0 : 3; + U8 alpha_b_0 : 3; + U8 alpha_d_0 : 3; + U8 rgb_d_1 : 3; + U8 alpha_b_1 : 3; + U8 alpha_d_1 : 3; + }; +}; + +typedef union N64_DP_Cmd_SetFillColor N64_DP_Cmd_SetFillColor; +union N64_DP_Cmd_SetFillColor +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U8 padding_0; + U16 padding_1; + union + { + struct + { + U16 rgba16_odd; + U16 rgba16_even; + }; + struct + { + U32 rgba32; + }; + }; + }; +}; + +typedef union N64_DP_Cmd_SetBlendColor N64_DP_Cmd_SetBlendColor; +union N64_DP_Cmd_SetBlendColor +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U32 padding : 24; + U32 rgba; + }; +}; + +typedef union N64_DP_Cmd_Stub N64_DP_Cmd_Stub; +union N64_DP_Cmd_Stub +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U64 padding : 56; + }; +}; + +typedef U8 N64_DP_Cmd_FillTriangleFlags; +enum +{ + N64_DP_Cmd_FillTriangleFlag_BaseTileDescriptorIndexMask = 0x3, + N64_DP_Cmd_FillTriangleFlag_MaximumLODMask = 0xc, + N64_DP_Cmd_FillTriangleFlag_LeftMajor = 0x80, +}; + +typedef struct N64_DP_Cmd_FillTriangleBase N64_DP_Cmd_FillTriangleBase; +struct N64_DP_Cmd_FillTriangleBase +{ + N64_DP_CmdID id; + N64_DP_Cmd_FillTriangleFlags flags; + U16 yl; + U16 ym; + U16 yh; + //- + U32 xl; + U32 dxldy; + //- + U32 xh; + U32 dxhdy; + //- + U32 xm; + U32 dxmdy; +}; + +typedef struct N64_DP_CmdExt_FillTriangleShading N64_DP_CmdExt_FillTriangleShading; +struct N64_DP_CmdExt_FillTriangleShading +{ + U16 r_i; + U16 g_i; + U16 b_i; + U16 a_i; + //- + U16 drdx_i; + U16 dgdx_i; + U16 dbdx_i; + U16 dadx_i; + //- + U16 r_f; + U16 g_f; + U16 b_f; + U16 a_f; + //- + U16 drdx_f; + U16 dgdx_f; + U16 dbdx_f; + U16 dadx_f; + //- + U16 drde_i; + U16 dgde_i; + U16 dbde_i; + U16 dade_i; + //- + U16 drdy_i; + U16 dgdy_i; + U16 dbdy_i; + U16 dady_i; + //- + U16 drde_f; + U16 dgde_f; + U16 dbde_f; + U16 dade_f; + //- + U16 drdy_f; + U16 dgdy_f; + U16 dbdy_f; + U16 dady_f; +}; + +typedef struct N64_DP_CmdExt_FillTriangleTexturing N64_DP_CmdExt_FillTriangleTexturing; +struct N64_DP_CmdExt_FillTriangleTexturing +{ + U16 s_i; + U16 t_i; + U16 w_i; + U16 padding_0; + //- + U16 dsdx_i; + U16 dtdx_i; + U16 dwdx_i; + U16 padding_1; + //- + U16 s_f; + U16 t_f; + U16 w_f; + U16 padding_2; + //- + U16 dsdx_f; + U16 dtdx_f; + U16 dwdx_f; + U16 padding_3; + //- + U16 dsde_i; + U16 dtde_i; + U16 dwde_i; + U16 padding_4; + //- + U16 dsdy_i; + U16 dtdy_i; + U16 dwdy_i; + U16 padding_5; + //- + U16 dsde_f; + U16 dtde_f; + U16 dwde_f; + U16 padding_6; + //- + U16 dsdy_f; + U16 dtdy_f; + U16 dwdy_f; + U16 padding_7; +}; + +typedef struct N64_DP_CmdExt_FillTriangleDepth N64_DP_CmdExt_FillTriangleDepth; +struct N64_DP_CmdExt_FillTriangleDepth +{ + U32 z; + U32 dzdx; + U32 dzde; + U32 dzdy; +}; + +typedef union N64_DP_Cmd_FillRectangle N64_DP_Cmd_FillRectangle; +union N64_DP_Cmd_FillRectangle +{ + U64 v[1]; + struct + { + N64_DP_CmdID id; + U16 lower_right_x : 12; + U16 lower_right_y : 12; + U8 padding : 8; + U16 upper_left_x : 12; + U16 upper_left_y : 12; + }; +}; + +pack_end + +//- rjf: DP status register flags + +typedef U32 N64_DP_StatusFlags; +enum +{ + N64_DP_StatusFlag_DMemDMA = (1<<0), // DP is using DMEM DMA + N64_DP_StatusFlag_Freeze = (1<<1), // DP is frozen + N64_DP_StatusFlag_Flush = (1<<2), // DP is flushed + N64_DP_StatusFlag_GCLKAlive = (1<<3), // DP GCLK is alive + N64_DP_StatusFlag_TMemBusy = (1<<4), // DP TMEM is busy + N64_DP_StatusFlag_PipeBusy = (1<<5), // DP pipeline is busy + N64_DP_StatusFlag_Busy = (1<<6), // DP command unit is busy + N64_DP_StatusFlag_BufferReady = (1<<7), // DP command buffer is ready + N64_DP_StatusFlag_DMABusy = (1<<8), // DP DMA is busy + N64_DP_StatusFlag_EndValid = (1<<9), // DP command end register is valid + N64_DP_StatusFlag_StartValid = (1<<10), // DP command start register is valid +}; + +typedef U32 N64_DP_StatusCmd; +enum +{ + N64_DP_StatusCmd_ResetXBusDMemDMA = (1<<0), // DP status flags write mask: clear status flags DMemDMA bit + N64_DP_StatusCmd_SetXBusDMemDMA = (1<<1), // DP status flags write mask: set status flags DMemDMA bit + N64_DP_StatusCmd_ResetFreeze = (1<<2), // DP status flags write mask: clear status flags Freeze bit + N64_DP_StatusCmd_SetFreeze = (1<<3), // DP status flags write mask: set status flags Freeze bit + N64_DP_StatusCmd_ResetFlush = (1<<4), // DP status flags write mask: clear status flags Flush bit + N64_DP_StatusCmd_SetFlush = (1<<5), // DP status flags write mask: set status flags Flush bit + N64_DP_StatusCmd_ResetTMemCounter = (1<<6), // DP status flags write mask: clear TMem counter + N64_DP_StatusCmd_ResetPipeCounter = (1<<7), // DP status flags write mask: clear Pipe counter + N64_DP_StatusCmd_ResetCmdCounter = (1<<8), // DP status flags write mask: clear Cmd counter + N64_DP_StatusCmd_ResetClockCounter = (1<<9), // DP status flags write mask: clear Clock counter +}; + +//- rjf: DP register layout + +typedef struct N64_DP_Regs N64_DP_Regs; +struct N64_DP_Regs +{ + U32 start; + U32 end; + U32 current; + N64_DP_StatusFlags status_flags; + U32 clock; + U32 busy; + U32 pipe_busy; + U32 tmem_busy; +}; + +//- rjf: DP register pointer +#define N64_DP_REGS ((volatile N64_DP_Regs *)0xA4100000u) + +//////////////////////////////// +//~ rjf: Framebuffer Dimensions + +#define N64_FB_WIDTH 320 +#define N64_FB_HEIGHT 240 + +//////////////////////////////// +//~ rjf: Default Fonts + +#define N64_BIG_DEFAULT_FONT_GLYPH_WIDTH 7 +#define N64_BIG_DEFAULT_FONT_GLYPH_HEIGHT 14 +#define N64_BIG_DEFAULT_FONT_GLYPH_ADVANCE 6 + +typedef struct N64_BigDefaultFontGlyph N64_BigDefaultFontGlyph; +struct N64_BigDefaultFontGlyph +{ + U8 v[N64_BIG_DEFAULT_FONT_GLYPH_WIDTH*N64_BIG_DEFAULT_FONT_GLYPH_HEIGHT]; +}; + +#define N64_SMALL_DEFAULT_FONT_GLYPH_WIDTH 5 +#define N64_SMALL_DEFAULT_FONT_GLYPH_HEIGHT 11 +#define N64_SMALL_DEFAULT_FONT_GLYPH_ADVANCE 4 + +typedef struct N64_SmallDefaultFontGlyph N64_SmallDefaultFontGlyph; +struct N64_SmallDefaultFontGlyph +{ + U8 v[N64_SMALL_DEFAULT_FONT_GLYPH_WIDTH*N64_SMALL_DEFAULT_FONT_GLYPH_HEIGHT]; +}; + +//////////////////////////////// +//~ rjf: Main State Bundle + +typedef struct N64_State N64_State; +struct N64_State +{ + //- rjf: console kind + N64_ConsoleKind console_kind; + + //- rjf: joybus interaction state + N64_JoyBus_Status joybus_status; + N64_JoyBus_PortState joybus_port_states[N64_JOYBUS_PORT_COUNT]; + N64_JoyBus_Cmd joybus_cmds[8]; + U32 joybus_cmds_write_pos; + U32 joybus_cmds_read_pos; + + //- rjf: current controller states + GamePad game_pads[GAME_PAD_SLOT_COUNT]; +}; + +//////////////////////////////// +//~ rjf: Globals + +extern U8 __bss_end[]; +extern N64_BigDefaultFontGlyph n64_big_default_font_glyphs[256]; +extern N64_SmallDefaultFontGlyph n64_small_default_font_glyphs[256]; +extern U8 n64_dgtlgrove_logo_32x48[32*48]; +extern U8 n64_controller_icon_32x32[32*32]; +global U16 * volatile n64_fb = 0; +global N64_State n64_state = {0}; +global ThreadCtx n64_tctx = {0}; + +//////////////////////////////// +//~ rjf: Low-Level Post-IPL3 Entry Point + +void _start(void); + +//////////////////////////////// +//~ rjf: Low-Level Interrupt Handler + +void _interrupt_handler(void); + +//////////////////////////////// +//~ rjf: Detected Console Kind + +function N64_ConsoleKind N64_GetConsoleKind(void); + +//////////////////////////////// +//~ rjf: JoyBus Specs + +function N64_JoyBus_CmdIDInfo N64_JoyBus_InfoFromCmdID(N64_JoyBus_CmdID id); + +//////////////////////////////// +//~ rjf: Cache Operations + +function void N64_DataCacheHitWriteback(volatile void *addr, UAddr length); +function void N64_DataCacheHitInvalidate(volatile void *addr, UAddr length); +function void N64_DataCacheHitWritebackInvalidate(volatile void *addr, UAddr length); +function void N64_DataCacheIndexWritebackInvalidate(volatile void *addr, UAddr length); +function void N64_InstCacheHitWriteback(volatile void *addr, UAddr length); +function void N64_InstCacheHitInvalidate(volatile void *addr, UAddr length); +function void N64_InstCacheFill(volatile void *addr, UAddr length); +function void N64_InstCacheIndexInvalidate(volatile void *addr, UAddr length); + +//////////////////////////////// +//~ rjf: JoyBus Command Processing + +function B32 N64_JoyBusCmdPush(N64_JoyBus_CmdID id); +function void N64_JoyBusCmdSend(N64_JoyBus_Cmd *cmd); + +//////////////////////////////// +//~ rjf: Rendering Helpers + +function U16 *N64_Framebuffer(void); +function void N64_DrawMonochromeBitmap(Vec2F32 pos, Vec4F32 color, U8 *ptr, U32 width, U32 height); +function Vec2F32 N64_DrawDebugString(B32 big, Vec2F32 pos, Vec4F32 color, String8 string); + +//////////////////////////////// +//~ rjf: High-Level Interrupt Handler + +void N64_InterruptHandler(N64_ExceptionRegState *regs, N64_ExceptionCode exception_code); + +//////////////////////////////// +//~ rjf: High-Level N64 Program Entry Point + +void N64_EntryPoint(void); + +#endif // N64_H diff --git a/code/n64/n64.ld b/code/n64/n64.ld new file mode 100644 index 0000000..0fed2a7 --- /dev/null +++ b/code/n64/n64.ld @@ -0,0 +1,48 @@ +OUTPUT_FORMAT("elf32-bigmips") +OUTPUT_ARCH(mips) +ENTRY(_start) + +PHDRS +{ + irq PT_LOAD AT (0x80000000); + text PT_LOAD AT (0x80000400); + data PT_LOAD; +} + +SECTIONS +{ + .intvectors 0x80000000 : + { + . = ALIGN(32); + KEEP(*(.intvectors)) + __intvectors_end = .; + }:irq + + .text 0x80000400 : + { + *(.text.boot) + *(.text*) + *(.rodata*) + }:text + + .data : + { + *(.data*) + n64_static_data_end = .; + }:data + + .bss (NOLOAD) : + { + __bss_start = .; + *(.bss*) + *(COMMON) + __bss_end = .; + } + + /DISCARD/ : + { + *(.eh_frame*) + *(.comment) + *(.note*) + } +} diff --git a/code/n64/render/n64_render.c b/code/n64/render/n64_render.c new file mode 100644 index 0000000..edb2c41 --- /dev/null +++ b/code/n64/render/n64_render.c @@ -0,0 +1,333 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +//////////////////////////////// +//~ rjf: Render Resources @per_platform + +function R_BufferHandle +R_BufferHandleFromData(void *data, UAddr element_count, UAddr element_size) +{ + R_BufferHandle result = {0}; + if(n64_r_state.buffers_count < ArrayCount(n64_r_state.buffers)) + { + N64_R_Buffer *buffer = &n64_r_state.buffers[n64_r_state.buffers_count]; + buffer->data = data; + buffer->element_count = element_count; + buffer->element_size = element_size; + n64_r_state.buffers_count += 1; + result.uaddr[0] = n64_r_state.buffers_count; + } + return result; +} + +function R_SubModelHandle +R_SubModelHandleFromBuffers(R_BufferHandle vtx_buffer, R_BufferHandle idx_buffer) +{ + R_SubModelHandle result = {0}; + if(n64_r_state.sub_models_count < ArrayCount(n64_r_state.sub_models)) + { + N64_R_SubModel *sub_model= &n64_r_state.sub_models[n64_r_state.sub_models_count]; + sub_model->vtx_buffer_num = (U16)vtx_buffer.uaddr[0]; + sub_model->idx_buffer_num = (U16)idx_buffer.uaddr[0]; + n64_r_state.sub_models_count += 1; + result.uaddr[0] = n64_r_state.sub_models_count; + } + return result; +} + +//////////////////////////////// +//~ rjf: Render Commands @per_platform + +function void +R_Pass(R_PassParams *params) +{ + MemoryCopyStruct(&n64_r_state.pass_params, params); + n64_r_state.pass_view_projection = Mul4x4F32(params->projection, params->view); + n64_r_state.buffers_count = 0; + n64_r_state.sub_models_count = 0; + + //- rjf: draw sky + { + // rjf: sync pipe + { + N64_DP_Cmd_Stub cmd = {0}; + cmd.id = N64_DP_CmdID_SyncPipe; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set other modes + { + N64_DP_Cmd_SetOtherModes cmd = {0}; + cmd.id = N64_DP_CmdID_SetOtherModes; + cmd.atomic_prim = 1; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: draw full-screen triangle for sky + { + N64_DP_Cmd_FillTriangleBase cmd = {0}; + cmd.id = N64_DP_CmdID_FillTriangleS; + cmd.flags = N64_DP_Cmd_FillTriangleFlag_LeftMajor; + cmd.yl = Fixed112FromF32(N64_FB_HEIGHT); + cmd.ym = Fixed112FromF32(N64_FB_HEIGHT); + cmd.yh = 0; + cmd.xl = Fixed1616FromF32(N64_FB_WIDTH); + cmd.dxldy = 0; + cmd.xh = 0; + cmd.dxhdy = 0; + cmd.xm = Fixed1616FromF32(N64_FB_WIDTH); + cmd.dxmdy = 0; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + { + F32 pitch = 0.1f; + Vec4F32 top_color_4f32 = V4F32(0.02f, 0.2f - 0.1f*(0.75f + pitch/0.25f), 0.05f - 0.02f*(0.75f + pitch/0.25f), 1.f); + // top_color_4f32 = V4F32(0.02f, 0.15f - 0.1f*(0.75f + pitch/0.25f), 0.3f - 0.02f*(0.75f + pitch/0.25f), 1.f); + S32 top_color_x_s32 = Fixed1616FromF32(top_color_4f32.x*255.f); + S32 top_color_y_s32 = Fixed1616FromF32(top_color_4f32.y*255.f); + S32 top_color_z_s32 = Fixed1616FromF32(top_color_4f32.z*255.f); + S32 top_color_w_s32 = Fixed1616FromF32(top_color_4f32.w*255.f); + S32 dcdy = Fixed1616FromF32(-0.1f); + S32 dcpdy = Fixed1616FromF32(+0.01f); + N64_DP_CmdExt_FillTriangleShading ext = {0}; + ext.r_i = Upper16(top_color_x_s32); + ext.g_i = Upper16(top_color_y_s32); + ext.b_i = Upper16(top_color_z_s32); + ext.a_i = Upper16(top_color_w_s32); + ext.r_f = Lower16(top_color_x_s32); + ext.g_f = Lower16(top_color_y_s32); + ext.b_f = Lower16(top_color_z_s32); + ext.a_f = Lower16(top_color_w_s32); + ext.drde_i = Upper16(dcdy); + ext.dgde_i = Upper16(dcdy); + ext.dbde_i = Upper16(dcpdy); + ext.drde_f = Lower16(dcdy); + ext.dgde_f = Lower16(dcdy); + ext.dbde_f = Lower16(dcpdy); + WriteStructAndInc(n64_r_state.rdp_command_buffer, &ext, &n64_r_state.rdp_command_buffer_write_pos); + } + } + } + + //- rjf: set up depth tested drawing parameters + { + // rjf: sync pipe + { + N64_DP_Cmd_Stub cmd = {0}; + cmd.id = N64_DP_CmdID_SyncPipe; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: set other modes + { + N64_DP_Cmd_SetOtherModes cmd = {0}; + cmd.id = N64_DP_CmdID_SetOtherModes; + cmd.atomic_prim = 1; + cmd.z_update_en = 1; + cmd.z_compare_en = 1; + cmd.antialias_en = 1; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + } + } +} + +function void +R_SubModel(R_SubModelHandle handle, R_Material material, Mat4x4F32 xform) +{ + Mat4x4F32 view_projection = n64_r_state.pass_view_projection; + UAddr sub_model_num = handle.uaddr[0]; + if(0 < sub_model_num && sub_model_num <= n64_r_state.sub_models_count) + { + N64_R_SubModel *sub_model = &n64_r_state.sub_models[sub_model_num-1]; + if(0 < sub_model->vtx_buffer_num && sub_model->vtx_buffer_num <= n64_r_state.buffers_count && + 0 < sub_model->idx_buffer_num && sub_model->idx_buffer_num <= n64_r_state.buffers_count) + { + N64_R_Buffer *vtx_buffer = &n64_r_state.buffers[sub_model->vtx_buffer_num-1]; + N64_R_Buffer *idx_buffer = &n64_r_state.buffers[sub_model->idx_buffer_num-1]; + Vec3F32 material_rgb = material.color.xyz; + if(vtx_buffer->element_size == sizeof(Vec3F32) && + (idx_buffer->element_size == 2 || idx_buffer->element_size == 4)) + { + UAddr tri_count = idx_buffer->element_count/3; + Vec3F32 *vtx = (Vec3F32 *)vtx_buffer->data; + UAddr vtx_count = vtx_buffer->element_count; + UAddr command_bytes_per_tri = sizeof(N64_DP_Cmd_FillTriangleBase) + sizeof(N64_DP_CmdExt_FillTriangleShading) + sizeof(N64_DP_CmdExt_FillTriangleDepth); + UAddr command_bytes_left = ArrayCount(n64_r_state.rdp_command_buffer) - n64_r_state.rdp_command_buffer_write_pos; + UAddr tri_count_max = command_bytes_left / command_bytes_per_tri; + UAddr tri_count_actual = Min(tri_count_max, tri_count); + for EachIndex(tri_idx, tri_count_actual) + { + // rjf: get face nums for this triangle + UAddr face_nums[3]; + switch(idx_buffer->element_size) + { + default:{MemoryZero(face_nums, sizeof(face_nums));}break; + case 2: + { + face_nums[0] = ((U16 *)idx_buffer->data)[tri_idx*3+0]; + face_nums[1] = ((U16 *)idx_buffer->data)[tri_idx*3+1]; + face_nums[2] = ((U16 *)idx_buffer->data)[tri_idx*3+2]; + }break; + case 4: + { + face_nums[0] = ((U32 *)idx_buffer->data)[tri_idx*3+0]; + face_nums[1] = ((U32 *)idx_buffer->data)[tri_idx*3+1]; + face_nums[2] = ((U32 *)idx_buffer->data)[tri_idx*3+2]; + }break; + } + + // rjf: unpack world-space vertices + Vec4F32 world_v[3] = + { + V4F32(vtx[face_nums[0]-1].x, vtx[face_nums[0]-1].y, vtx[face_nums[0]-1].z, 1.f), + V4F32(vtx[face_nums[1]-1].x, vtx[face_nums[1]-1].y, vtx[face_nums[1]-1].z, 1.f), + V4F32(vtx[face_nums[2]-1].x, vtx[face_nums[2]-1].y, vtx[face_nums[2]-1].z, 1.f), + }; + + // rjf: world -> clip + Vec4F32 clip_v[] = + { + XForm4F32(view_projection, world_v[0]), + XForm4F32(view_projection, world_v[1]), + XForm4F32(view_projection, world_v[2]), + }; + + // rjf: clip -> ndc + Vec3F32 ndc_v[] = + { + Scale3F32(clip_v[0].xyz, 1.f/clip_v[0].w), + Scale3F32(clip_v[1].xyz, 1.f/clip_v[1].w), + Scale3F32(clip_v[2].xyz, 1.f/clip_v[2].w), + }; + + // rjf: early discard of back-facing triangles + { + Vec3F32 normal = Cross3F32(Sub3F32(ndc_v[1], ndc_v[0]), Sub3F32(ndc_v[2], ndc_v[0])); + Vec3F32 eye = V3F32(0, 0, 1); + F32 dot = Dot3F32(normal, eye); + if(dot > 0) + { + continue; + } + } + + // rjf: ndc -> screen + Vec3F32 ndc2screen_v = V3F32(N64_FB_WIDTH/2.f, N64_FB_HEIGHT/2.f, 0x7FFF/2.f); + Vec3F32 screen_v[] = + { + Mul3F32(Add3F32(Mul3F32(ndc_v[0], V3F32(-1, -1, 1)), V3F32(1, 1, 1)), ndc2screen_v), + Mul3F32(Add3F32(Mul3F32(ndc_v[1], V3F32(-1, -1, 1)), V3F32(1, 1, 1)), ndc2screen_v), + Mul3F32(Add3F32(Mul3F32(ndc_v[2], V3F32(-1, -1, 1)), V3F32(1, 1, 1)), ndc2screen_v), + }; + + // rjf: screen -> snapped; ensures joining edges of triangles, otherwise triangles + // disagree on edges in projected space, causing seams. + for EachElement(idx, screen_v) + { + for EachIndex(axis, 3) + { + screen_v[idx].v[axis] = FloorF32(screen_v[idx].v[axis]*4.f)/4.f; + } + } + + // rjf: sort vertices into top/mid/bot + Vec3F32 top = screen_v[0]; + Vec3F32 mid = screen_v[1]; + Vec3F32 bot = screen_v[2]; + U8 world_idxs[] = + { + 0, 1, 2 + }; + if(mid.y > bot.y) + { + Swap(Vec3F32, mid, bot); + } + if(top.y > mid.y) + { + Swap(Vec3F32, top, mid); + } + if(mid.y > bot.y) + { + Swap(Vec3F32, mid, bot); + } + + // rjf: unpack triangle edges + // + // * + // |\ "M":top2mid + // | \ + // "H":top2bot | * + // | / + // |/ "L":mid2bot + // * + // + Vec3F32 top2mid = Sub3F32(mid, top); + Vec3F32 top2bot = Sub3F32(bot, top); + Vec3F32 mid2bot = Sub3F32(bot, mid); + F32 top2bot_invslope = (top2bot.y != 0) ? (top2bot.x / top2bot.y) : 0; + F32 top2mid_invslope = (top2mid.y != 0) ? (top2mid.x / top2mid.y) : 0; + F32 mid2bot_invslope = (mid2bot.y != 0) ? (mid2bot.x / mid2bot.y) : 0; + F32 fy = FloorF32(top.y) - top.y; + F32 nz = top2bot.x*top2mid.y - top2bot.y*top2mid.x; + F32 inv_n = (nz != 0.f) ? (-1.f / nz) : 0.f; + + // rjf: fill command + N64_DP_Cmd_FillTriangleBase cmd = {0}; + F32 x_h_at_mid = top.x + (mid.y - top.y) * top2bot_invslope; + B32 left_major = (mid.x >= x_h_at_mid); + cmd.id = N64_DP_CmdID_FillTriangleSZ; + cmd.flags = left_major ? N64_DP_Cmd_FillTriangleFlag_LeftMajor : 0; + cmd.yl = Fixed112FromF32(bot.y); + cmd.ym = Fixed112FromF32(mid.y); + cmd.yh = Fixed112FromF32(top.y); + cmd.xl = Fixed1616FromF32(mid.x); + cmd.dxldy = Fixed1616FromF32(mid2bot_invslope); + cmd.xh = Fixed1616FromF32(top.x + fy*top2bot_invslope); + cmd.dxhdy = Fixed1616FromF32(top2bot_invslope); + cmd.xm = Fixed1616FromF32(top.x + fy*top2mid_invslope); + cmd.dxmdy = Fixed1616FromF32(top2mid_invslope); + + // rjf: push + WriteStructAndInc(n64_r_state.rdp_command_buffer, &cmd, &n64_r_state.rdp_command_buffer_write_pos); + + // rjf: unpack lighting + Vec3F32 normal = Cross3F32(Sub3F32(world_v[2].xyz, world_v[0].xyz), Sub3F32(world_v[1].xyz, world_v[0].xyz)); + Vec3F32 keylight_dir = n64_r_state.pass_params.keylight_dir; + F32 normal_dot_keylight_dir = Dot3F32(Normalize3F32(keylight_dir), Normalize3F32(normal)); + F32 light_factor_unclamped = 1 - Clamp(0, -normal_dot_keylight_dir, 1); + F32 light_factor = Clamp(0.2f, light_factor_unclamped, 1.f); + + // rjf: unpack vertex colors + Vec3F32 rgb_v[] = + { + Scale3F32(material_rgb, light_factor), + Scale3F32(material_rgb, light_factor), + Scale3F32(material_rgb, light_factor), + }; + + // rjf: extend w/ shading + { + N64_DP_CmdExt_FillTriangleShading ext = {0}; + ext.r_i = 255*rgb_v[0].x; + ext.g_i = 255*rgb_v[0].y; + ext.b_i = 255*rgb_v[0].z; + ext.a_i = 255; + WriteStructAndInc(n64_r_state.rdp_command_buffer, &ext, &n64_r_state.rdp_command_buffer_write_pos); + } + + // rjf: extend w/ depth + { + F32 dzdx = (top2bot.y*top2mid.z - top2mid.y*top2bot.z) * inv_n; + F32 dzdy = (top2mid.x*top2bot.z - top2bot.x*top2mid.z) * inv_n; + F32 dzde = dzdy + dzdx*top2bot_invslope; + N64_DP_CmdExt_FillTriangleDepth ext = {0}; + ext.z = Fixed1616FromF32(top.z + fy * dzde); + ext.dzdx = Fixed1616FromF32(dzdx); + ext.dzdy = Fixed1616FromF32(dzdy); + ext.dzde = Fixed1616FromF32(dzde); + WriteStructAndInc(n64_r_state.rdp_command_buffer, &ext, &n64_r_state.rdp_command_buffer_write_pos); + } + } + } + } + } +} diff --git a/code/n64/render/n64_render.h b/code/n64/render/n64_render.h new file mode 100644 index 0000000..4ee2227 --- /dev/null +++ b/code/n64/render/n64_render.h @@ -0,0 +1,37 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#ifndef N64_RENDER_H +#define N64_RENDER_H + +typedef struct N64_R_Buffer N64_R_Buffer; +struct N64_R_Buffer +{ + void *data; + UAddr element_count; + UAddr element_size; +}; + +typedef struct N64_R_SubModel N64_R_SubModel; +struct N64_R_SubModel +{ + U16 vtx_buffer_num; + U16 idx_buffer_num; +}; + +typedef struct N64_R_State N64_R_State; +struct N64_R_State +{ + U8 align(64) rdp_command_buffer[KiB(256)]; + UAddr rdp_command_buffer_write_pos; + R_PassParams pass_params; + Mat4x4F32 pass_view_projection; + N64_R_Buffer buffers[1024]; + UAddr buffers_count; + N64_R_SubModel sub_models[1024]; + UAddr sub_models_count; +}; + +global N64_R_State n64_r_state = {0}; + +#endif // N64_RENDER_H diff --git a/code/n64_test/n64_test.c b/code/n64_test/n64_test.c new file mode 100644 index 0000000..d1180f9 --- /dev/null +++ b/code/n64_test/n64_test.c @@ -0,0 +1,475 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#include "base/base_inc.h" +#include "n64/n64.h" +#include "render/render_inc.h" + +#include "base/base_inc.c" +#include "n64/n64.c" +#include "render/render_inc.c" + +typedef struct SubModel SubModel; +struct SubModel +{ + U16 material_num; + U16 *idxs; + UAddr idxs_count; + U64 id; +}; + +typedef struct Model Model; +struct Model +{ + Vec3F32 *vtx; + UAddr vtx_count; + SubModel *sub_models; + UAddr sub_models_count; +}; + +R_Material materials[] = +{ + {{1, 0.5f, 0, 1}}, // yellow + {{0, 1, 0, 1}}, // green + {{1, 0, 0, 1}}, // red + {{0, 0, 1, 1}}, // blue +}; + +extern Model n64_logo_model; +extern Model dg_logo_model; + +function B32 +Run(U64 tick_idx, Arena *permanent_arena, GamePad game_pads[GAME_PAD_SLOT_COUNT]) +{ + B32 keep_running = 1; + + //- rjf: get/update camera state + local_persist F32 yaw = 0.125f; + local_persist F32 pitch = 0.125f; + local_persist F32 zoom = 5.f; + { + // rjf: stick -> adjust yaw/pitch + yaw += 0.1f * game_pads[0].sticks[0].x; + pitch += 0.1f * game_pads[0].sticks[0].y; + pitch = Clamp(-0.24f, pitch, +0.24f); + + // rjf: z -> zoom in + if(BitArrayGet(game_pads[0].buttons, GamePadButtonKind_Z)) + { + zoom -= 0.1f; + } + + // rjf: r -> zoom out + else if(BitArrayGet(game_pads[0].buttons, GamePadButtonKind_RightBumper)) + { + zoom += 0.1f; + } + } + + //- rjf: camera state -> view projection + F32 cos_pitch = CosF32(pitch); + F32 cos_pitch_zoom = cos_pitch*zoom; + Vec3F32 eye = V3F32(cos_pitch_zoom*CosF32(yaw), + cos_pitch_zoom*SinF32(yaw), + zoom*SinF32(pitch)); + Mat4x4F32 view = MakeLookAt4x4F32(eye, V3F32(0, 0, 0), V3F32(0, 0, 1)); + Mat4x4F32 projection = MakePerspective4x4F32(0.25f, (F32)N64_FB_WIDTH/N64_FB_HEIGHT, 0.01f, 50.f); + + //- rjf: get/update selected model + local_persist S32 model_num = 0; + if(BitArrayGet(n64_state.game_pads[0].buttons, GamePadButtonKind_CRight) && !BitArrayGet(n64_state.game_pads[0].last_buttons, GamePadButtonKind_CRight)) + { + model_num += 1; + if(model_num > 2) + { + model_num = 0; + } + } + if(BitArrayGet(n64_state.game_pads[0].buttons, GamePadButtonKind_CLeft) && !BitArrayGet(n64_state.game_pads[0].last_buttons, GamePadButtonKind_CLeft)) + { + model_num -= 1; + if(model_num < 0) + { + model_num = 2; + } + } + Model *model = &n64_logo_model; + switch(model_num) + { + default: + case 0: + {}break; + case 1: + { + model = &dg_logo_model; + }break; + case 2: + { + // model = &n64_controller_model; + }break; + } + + //- rjf: set up render pass + R_PassParams pass_params = + { + view, + projection, + V3F32(+0.5f, +0.1f, -1), + }; + R_Pass(&pass_params); + + //- rjf: draw model + R_BufferHandle vtx_buffer = R_BufferHandleFromData(model->vtx, model->vtx_count, sizeof(model->vtx[0])); + for EachIndex(sub_model_idx, model->sub_models_count) + { + SubModel *sub_model = &model->sub_models[sub_model_idx]; + R_Material material = materials[sub_model->material_num-1]; + R_BufferHandle idx_buffer = R_BufferHandleFromData(sub_model->idxs, sub_model->idxs_count, sizeof(sub_model->idxs[0])); + R_SubModelHandle handle = R_SubModelHandleFromBuffers(vtx_buffer, idx_buffer); + R_SubModel(handle, material, MakeMat4x4F32(1.f)); + } + + //- rjf: gradient test +#if 0 +#if 0 + { + U16 *p = N64_Framebuffer(); + int midpoint = N64_FB_WIDTH - (tick_idx%N64_FB_WIDTH); + for(int i = 0; i < N64_FB_WIDTH*N64_FB_HEIGHT; i += 1) + { + // NOTE(rjf): RGBA5551 (R5 G5 B5 A1) + // + // - 0xF801 = bright red + // - 0x07C1 = bright green + // - 0x003F = bright blue + // - 0xFFFF = white + // - 0x0001 = black (with alpha) + // + F32 pct = (F32)(i%N64_FB_WIDTH) / N64_FB_WIDTH; + U16 red = (U16)(pct * 0x1f); + U16 color = 0x0001 | (red<<11); + if((i%N64_FB_WIDTH) > midpoint) + { + F32 pct = (F32)(N64_FB_WIDTH - (i%N64_FB_WIDTH)) / N64_FB_WIDTH; + U16 green = (U16)(pct * 0x1f); + color |= (green<<6); + } + { + F32 pct = ((F32)(i / N64_FB_WIDTH) / N64_FB_HEIGHT); + U16 blue = (U16)(pct * 0x1f); + color |= (blue<<1); + } + p[i] = color; + } + } +#endif +#if 1 + U16 *p = N64_Framebuffer(); + for(int i = 0; i < N64_FB_WIDTH*N64_FB_HEIGHT; i += 1) + { + if(i%N64_FB_WIDTH > tick_idx%N64_FB_WIDTH) + { + p[i] = 0xf801; + } + else + { + p[i] = 0x0001; + } + } +#endif + Temp scratch = ScratchBegin(0); +#if 0 + Vec2F32 text_pos = {150, 64 + 32.f}; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 1, 1, 1), SF("DP status_flags: %ib", N64_DP_REGS->status_flags)).y; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 1, 1, 1), SF("MI_VERSION: %ix", *N64_MI_VERSION)).y; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 1, 1, 1), S("ABCDEFGHIJKLMNOPQRSTUVWXYZ")).y; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 1, 1, 1), S("abcdefghijklmnopqrstuvwxyz")).y; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 1, 1, 1), S("0123456789")).y; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 1, 1, 1), S("`~!@#$%^&*()-_=+[]{};:'\"|\\.,<>?/")).y; + text_pos.y += 4; + text_pos.y += N64_DrawDebugString(1, text_pos, V4F32(1, 0.8f, 0, 1), S("Hello, N64!")).y; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 1, 1, 1), SF("Memory Size = %i", N64_BOOT_INFO->memory_size)).y; + text_pos.y += N64_DrawDebugString(0, text_pos, V4F32(1, 0, 0, 1), SF("DP STATUS = %ib", N64_DP_REGS->status_flags)).y; + N64_DrawMonochromeBitmap(V2F32(320 - 32, 240 - 48), V4F32(1, 1, 0, 1), n64_dgtlgrove_logo_32x48, 32, 48); +#endif + + Vec2F32 button_pos = {16, 48}; + for(U64 idx = 0; idx < 4; idx += 1) + { + B32 controller_is_connected = (n64_state.joybus_port_states[idx].id == N64_JoyBus_ID_N64Controller); + Vec4F32 color = controller_is_connected ? V4F32(1, 1, 1, 1) : V4F32(1, 0, 0, 1); + N64_DrawMonochromeBitmap(V2F32(16 + 32*idx, 16), color, n64_controller_icon_32x32, 32, 32); + if(controller_is_connected) + { + N64_DrawDebugString(0, + V2F32(16 + 32*idx + 16 - N64_SMALL_DEFAULT_FONT_GLYPH_WIDTH/2 + 8*game_pads[idx].sticks[0].x, + 16 + 16 - N64_SMALL_DEFAULT_FONT_GLYPH_HEIGHT/2 + -8*game_pads[idx].sticks[0].y), + V4F32(1, 1, 0, 1), S("*")); + } + if(controller_is_connected) + { + // button_pos.y += N64_DrawDebugString(0, button_pos, V4F32(1, 1, 1, 1), SF("Stick")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_A)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0, 0, 1, 1), S("A")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_B)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0, 1, 0, 1), S("B")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_Start)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(1, 0, 0, 1), S("Start")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_CLeft)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(1, 1, 0, 1), S("C<")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_CUp)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(1, 1, 0, 1), S("C^")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_CRight)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(1, 1, 0, 1), S("C>")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_CDown)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(1, 1, 0, 1), S("Cv")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_DLeft)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0.5f, 0.5f, 0.5f, 1), S("D<")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_DUp)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0.5f, 0.5f, 0.5f, 1), S("D^")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_DRight)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0.5f, 0.5f, 0.5f, 1), S("D>")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_DDown)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0.5f, 0.5f, 0.5f, 1), S("Dv")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_Z)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0.5f, 0.5f, 0.5f, 1), S("Z")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_LeftBumper)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0.5f, 0.5f, 0.5f, 1), S("L")).y; + } + if(BitArrayGet(game_pads[idx].buttons, GamePadButtonKind_RightBumper)) + { + button_pos.y += N64_DrawDebugString(1, button_pos, V4F32(0.5f, 0.5f, 0.5f, 1), S("R")).y; + } + } + + ScratchEnd(scratch); +#endif + + //- rjf: digital grove splash test +#if 0 + F32 t_pct = (F32)tick_idx / 80.f; + if(t_pct > 1.f) + { + t_pct = 1.f; + } + { + U16 *p = N64_Framebuffer(); + U8 bayer[16] = + { + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 5 + }; + F32 dither_step = 1.f / 31.f; + for(U32 off = 0; off < N64_FB_WIDTH*N64_FB_HEIGHT; off += 1) + { + U32 x = off%N64_FB_WIDTH; + U32 y = off/N64_FB_WIDTH; + F32 x_pct = (F32)x/N64_FB_WIDTH; + F32 y_pct = (F32)y/N64_FB_HEIGHT; + Vec4F32 color = V4F32(t_pct * (0.04f + 0.08f*x_pct - 0.03f*y_pct), + t_pct * (0.15f - 0.04f*y_pct), + t_pct * (0.03f + 0.04f*y_pct), + 1.f); + U32 x_bayer = x%4; + U32 y_bayer = y%4; + F32 bayer_f = (bayer[y_bayer*4 + x_bayer] / 16.f); + Vec4F32 color_dithered = color; + color_dithered.x += (bayer_f - 0.5f) * dither_step; + color_dithered.y += (bayer_f - 0.5f) * dither_step; + color_dithered.z += (bayer_f - 0.5f) * dither_step; + p[off] = RGBA5551From4F32(color_dithered); + } + } + // Vec4F32 logo_color = V4F32(1*t_pct, 0.8f*t_pct, 0, 1); + // N64_DrawMonochromeBitmap(V2F32(160 - 16, 120 - 24 + 32*(1-t_pct)), logo_color, n64_dgtlgrove_logo_32x48, 32, 48); + // N64_DrawDebugString(1, V2F32(160 - 6.5f*N64_BIG_DEFAULT_FONT_GLYPH_WIDTH, 120+30 + 32*(1-t_pct)), logo_color, S("Digital Grove 64")); +#endif + + return keep_running; +} + +global Vec3F32 n64_logo_model_vtx[] = +{ + { -0.319213, -0.322617, 0.083041 }, { -0.319213, -0.671946, 0.719425 }, { -0.319213, 0.332592, -0.051930 }, { -0.691264, 0.339679, -0.737678 }, + { -0.691264, -0.321049, -0.737678 }, { -0.691264, -0.319959, 0.719425 }, { -0.691264, 0.689761, 0.087923 }, { -0.319213, 0.339679, -0.737678 }, + { -0.691264, -0.322617, 0.083041 }, { -0.319213, -0.321049, -0.737678 }, { -0.691264, -0.671946, -0.737678 }, { -0.319213, -0.671946, -0.737678 }, + { -0.691264, -0.671946, 0.719425 }, { -0.319213, -0.319959, 0.719425 }, { -0.691264, 0.332592, -0.051930 }, { -0.691264, 0.332592, 0.087923 }, + { -0.319213, 0.332592, 0.087923 }, { -0.319213, 0.689761, 0.087923 }, { -0.691264, 0.689761, -0.737678 }, { -0.319213, 0.689761, -0.737678 }, + { -0.319213, -0.671946, 0.086702 }, { -0.691264, 0.689761, -0.049576 }, { -0.319213, 0.689761, -0.051523 }, { -0.691264, 0.689761, 0.726424 }, + { -0.691264, 0.332592, 0.726424 }, { -0.319213, 0.332592, 0.726424 }, { -0.319213, 0.689761, 0.726424 }, { 0.328262, 0.332592, -0.731953 }, + { 0.328262, 0.689761, -0.731953 }, { 0.328262, 0.332592, -0.040641 }, { 0.328262, 0.689761, -0.040641 }, { 0.663334, 0.332592, -0.731953 }, + { 0.663334, 0.689761, -0.731953 }, { 0.663334, 0.332592, -0.040641 }, { 0.663334, 0.689761, -0.040641 }, { 0.328262, 0.332592, 0.089613 }, + { 0.328262, 0.689761, 0.089613 }, { 0.663334, 0.332592, 0.089613 }, { 0.663334, 0.689761, 0.089613 }, { 0.328262, 0.332592, 0.726182 }, + { 0.328262, 0.689761, 0.726182 }, { 0.663334, 0.332592, 0.726182 }, { 0.663334, 0.689761, 0.726182 }, { 0.663334, -0.324375, -0.734938 }, + { 0.328262, -0.324375, -0.734938 }, { 0.663333, -0.324375, -0.039661 }, { 0.328262, -0.324375, -0.039662 }, { 0.663334, -0.677543, -0.734938 }, + { 0.328262, -0.677543, -0.734938 }, { 0.663333, -0.677543, -0.039661 }, { 0.328262, -0.677543, -0.039662 }, { 0.663333, -0.324375, 0.099964 }, + { 0.328262, -0.324375, 0.099964 }, { 0.663333, -0.677543, 0.099964 }, { 0.328262, -0.677543, 0.099964 }, { 0.663333, -0.324375, 0.720610 }, + { 0.328261, -0.324375, 0.720610 }, { 0.663333, -0.677543, 0.720610 }, { 0.328261, -0.677543, 0.720610 }, { -0.691264, -0.671946, 0.086713 }, + { -0.691264, -0.671946, -0.042679 }, { -0.319213, -0.671946, -0.040508 }, { -0.319213, -0.322617, -0.040018 }, { -0.691264, -0.322617, -0.044321 }, +}; + +global U16 n64_logo_model_tri_faces__yellow[] = +{ + 14, 13, 2, 27, 25, 26, 40, 43, 41, 57, 58, 56, + 14, 6, 13, 27, 24, 25, 40, 42, 43, 57, 59, 58, +}; +global U16 n64_logo_model_tri_faces__green[] = +{ + 15, 8, 3, 9, 14, 1, 63, 9, 1, 12, 5, 10, + 12, 61, 11, 3, 16, 15, 7, 27, 18, 18, 22, 7, + 8, 19, 20, 47, 1, 14, 23, 19, 22, 25, 63, 26, + 29, 32, 28, 37, 8, 20, 23, 37, 20, 31, 33, 29, + 34, 36, 30, 31, 39, 35, 36, 3, 8, 37, 43, 39, + 46, 53, 52, 48, 51, 49, 44, 49, 45, 50, 55, 51, + 55, 58, 59, 57, 34, 30, 60, 2, 13, 62, 60, 61, + 21, 51, 2, 1, 49, 21, 47, 44, 45, 38, 40, 36, + 15, 4, 8, 9, 6, 14, 63, 64, 9, 12, 11, 5, + 12, 62, 61, 3, 17, 16, 7, 24, 27, 18, 23, 22, + 8, 4, 19, 47, 45, 1, 23, 20, 19, 25, 64, 63, + 29, 33, 32, 37, 36, 8, 23, 41, 37, 31, 35, 33, + 34, 38, 36, 31, 37, 39, 36, 40, 3, 37, 41, 43, + 46, 47, 53, 48, 50, 51, 44, 48, 49, 50, 54, 55, + 55, 54, 58, 57, 56, 34, 60, 21, 2, 62, 21, 60, + 21, 49, 51, 1, 45, 49, 47, 46, 44, 38, 42, 40, +}; +global U16 n64_logo_model_tri_faces__red[] = +{ + 10, 16, 17, 40, 23, 3, 2, 47, 14, 52, 28, 32, + 10, 5, 16, 40, 41, 23, 2, 51, 47, 52, 53, 28, +}; +global U16 n64_logo_model_tri_faces__blue[] = +{ + 17, 23, 18, 11, 64, 5, 22, 4, 15, 1, 62, 63, + 63, 17, 26, 22, 16, 7, 12, 63, 62, 16, 24, 7, + 27, 17, 18, 33, 34, 32, 34, 52, 32, 35, 38, 34, + 30, 37, 31, 39, 42, 38, 46, 48, 44, 52, 58, 54, + 51, 53, 47, 46, 54, 50, 59, 53, 55, 60, 6, 9, + 28, 31, 29, 16, 64, 25, 61, 9, 64, 53, 30, 28, + 17, 3, 23, 11, 61, 64, 22, 19, 4, 1, 21, 62, + 63, 10, 17, 22, 15, 16, 12, 10, 63, 16, 25, 24, + 27, 26, 17, 33, 35, 34, 34, 56, 52, 35, 39, 38, + 30, 36, 37, 39, 43, 42, 46, 50, 48, 52, 56, 58, + 51, 55, 53, 46, 52, 54, 59, 57, 53, 60, 13, 6, + 28, 30, 31, 16, 5, 64, 61, 60, 9, 53, 57, 30, +}; + +SubModel n64_logo_submodels[] = +{ + {1, ArrayAndCount(n64_logo_model_tri_faces__yellow)}, + {2, ArrayAndCount(n64_logo_model_tri_faces__green)}, + {3, ArrayAndCount(n64_logo_model_tri_faces__red)}, + {4, ArrayAndCount(n64_logo_model_tri_faces__blue)}, +}; + +Model n64_logo_model = +{ + ArrayAndCount(n64_logo_model_vtx), + ArrayAndCount(n64_logo_submodels), +}; + +global Vec3F32 dg_logo_model_vtx[] = +{ + { 0.085057, 0.094957, -2.584146 }, { 0.084919, -0.027923, -2.658362 }, { 0.083808, -0.171746, -2.550864 }, { 0.083306, -0.107028, -2.394031 }, + { 0.084125, 0.080334, -2.405339 }, { 0.086326, 0.278994, -2.690513 }, { 0.086225, 0.063105, -2.850055 }, { 0.084853, -0.226452, -2.810704 }, + { 0.083159, -0.367724, -2.581724 }, { 0.082095, -0.334552, -2.335907 }, { 0.082196, -0.118662, -2.176362 }, { 0.083569, 0.170893, -2.215714 }, + { 0.085262, 0.312166, -2.444695 }, { 0.082318, -0.135191, -2.215159 }, { 0.075256, -0.133749, -0.766794 }, { 0.068438, -0.894210, -0.004996 }, + { 0.060581, -0.892605, 1.606462 }, { 0.059976, -0.130628, 2.366775 }, { 0.056730, -0.025024, 3.120139 }, { 0.061291, 0.084822, 2.277215 }, + { 0.062657, -0.607715, 1.418925 }, { 0.068886, -0.608987, 0.141494 }, { 0.075700, 0.081880, -0.677655 }, { 0.083340, 0.080320, -2.244440 }, + { 0.064875, -0.875355, 0.740968 }, { 0.057463, -1.609634, 1.646470 }, { 0.065358, -0.664627, 0.818029 }, { 0.078421, 0.011302, -1.294146 }, + { 0.077959, 0.627437, -0.684809 }, { 0.068811, 0.750083, 1.292086 }, { 0.071499, 0.844370, 0.820083 }, { 0.079242, 0.843008, -0.767747 }, + { 0.079871, 0.049410, -1.559573 }, { 0.074583, 0.687116, 0.056670 }, { 0.074556, 1.285580, 0.562082 }, { 0.075410, 0.757468, -0.054058 }, + { 0.056820, -0.262075, 2.903636 }, { 0.056290, -0.354120, 2.935505 }, { 0.054757, -0.443627, 3.174871 }, { 0.056529, -0.201492, 3.013897 }, + { 0.071368, 0.941992, 0.928360 }, { 0.070890, 0.941275, 1.025741 }, { 0.070886, 1.137012, 1.190092 }, { 0.071973, 1.066184, 0.908125 }, + { -0.027071, 0.095414, -2.584693 }, { -0.027210, -0.027466, -2.658910 }, { -0.028321, -0.171288, -2.551411 }, { -0.028822, -0.106571, -2.394578 }, + { -0.028003, 0.080791, -2.405887 }, { -0.025802, 0.279451, -2.691060 }, { -0.025903, 0.063562, -2.850603 }, { -0.027276, -0.225994, -2.811251 }, + { -0.028969, -0.367267, -2.582272 }, { -0.030033, -0.334095, -2.336454 }, { -0.029932, -0.118205, -2.176909 }, { -0.028560, 0.171350, -2.216261 }, + { -0.026866, 0.312623, -2.445242 }, { -0.029810, -0.134734, -2.215707 }, { -0.036872, -0.133292, -0.767341 }, { -0.043690, -0.893753, -0.005543 }, + { -0.051548, -0.892148, 1.605915 }, { -0.052152, -0.130171, 2.366227 }, { -0.055398, -0.024567, 3.119591 }, { -0.050837, 0.085279, 2.276668 }, + { -0.049471, -0.607258, 1.418378 }, { -0.043242, -0.608530, 0.140947 }, { -0.036429, 0.082337, -0.678202 }, { -0.028789, 0.080777, -2.244987 }, + { -0.047254, -0.874897, 0.740421 }, { -0.054666, -1.609177, 1.645923 }, { -0.046771, -0.664170, 0.817482 }, { -0.033708, 0.011759, -1.294693 }, + { -0.034170, 0.627894, -0.685356 }, { -0.043318, 0.750540, 1.291539 }, { -0.040630, 0.844827, 0.819536 }, { -0.032886, 0.843465, -0.768294 }, + { -0.032257, 0.049867, -1.560121 }, { -0.037545, 0.687573, 0.056123 }, { -0.037572, 1.286037, 0.561534 }, { -0.036718, 0.757925, -0.054605 }, + { -0.055308, -0.261618, 2.903088 }, { -0.055839, -0.353663, 2.934958 }, { -0.057372, -0.443170, 3.174324 }, { -0.055599, -0.201035, 3.013350 }, + { -0.040760, 0.942449, 0.927813 }, { -0.041238, 0.941732, 1.025193 }, { -0.041243, 1.137469, 1.189545 }, { -0.040155, 1.066641, 0.907578 }, +}; + +global U16 dg_logo_model_tri_faces[] = +{ + 1, 13, 5, 2, 6, 1, 18, 20, 19, 17, 20, 18, + 17, 21, 20, 16, 21, 17, 16, 22, 21, 16, 23, 22, + 15, 23, 16, 15, 24, 23, 14, 24, 15, 1, 6, 13, + 3, 4, 10, 2, 3, 8, 5, 13, 12, 4, 11, 10, + 3, 9, 8, 2, 7, 6, 4, 5, 11, 5, 12, 11, + 3, 10, 9, 2, 8, 7, 45, 49, 57, 46, 45, 50, + 62, 63, 64, 61, 62, 64, 61, 64, 65, 60, 61, 65, + 60, 65, 66, 60, 66, 67, 59, 60, 67, 59, 67, 68, + 58, 59, 68, 45, 57, 50, 47, 54, 48, 46, 52, 47, + 49, 56, 57, 48, 54, 55, 47, 52, 53, 46, 50, 51, + 48, 55, 49, 49, 55, 56, 47, 53, 54, 46, 51, 52, + 53, 8, 9, 54, 9, 10, 67, 22, 23, 55, 10, 11, + 60, 15, 16, 63, 18, 19, 56, 11, 12, 57, 12, 13, + 68, 23, 24, 64, 19, 20, 49, 1, 5, 62, 17, 18, + 59, 14, 15, 45, 2, 1, 58, 24, 14, 46, 3, 2, + 47, 4, 3, 65, 20, 21, 48, 5, 4, 61, 16, 17, + 50, 13, 6, 51, 6, 7, 52, 7, 8, 66, 21, 22, + 53, 52, 8, 54, 53, 9, 67, 66, 22, 55, 54, 10, + 60, 59, 15, 63, 62, 18, 56, 55, 11, 57, 56, 12, + 68, 67, 23, 64, 63, 19, 49, 45, 1, 62, 61, 17, + 59, 58, 14, 45, 46, 2, 58, 68, 24, 46, 47, 3, + 47, 48, 4, 65, 64, 20, 48, 49, 5, 61, 60, 16, + 50, 57, 13, 51, 50, 6, 52, 51, 7, 66, 65, 21, + 25, 27, 26, 69, 70, 71, 70, 25, 26, 71, 26, 27, + 69, 27, 25, 70, 69, 25, 71, 70, 26, 69, 71, 27, + 29, 31, 30, 29, 32, 31, 29, 33, 32, 28, 33, 29, + 73, 74, 75, 73, 75, 76, 73, 76, 77, 72, 73, 77, + 76, 31, 32, 77, 32, 33, 73, 28, 29, 72, 33, 28, + 75, 30, 31, 74, 29, 30, 76, 75, 31, 77, 76, 32, + 73, 72, 28, 72, 77, 33, 75, 74, 30, 74, 73, 29, + 34, 36, 35, 78, 79, 80, 80, 35, 36, 79, 34, 35, + 78, 36, 34, 80, 79, 35, 79, 78, 34, 78, 80, 36, + 38, 40, 39, 38, 37, 40, 82, 83, 84, 82, 84, 81, + 82, 37, 38, 84, 39, 40, 83, 38, 39, 81, 40, 37, + 82, 81, 37, 84, 83, 39, 83, 82, 38, 81, 84, 40, + 42, 44, 43, 41, 44, 42, 86, 87, 88, 85, 86, 88, + 88, 43, 44, 87, 42, 43, 86, 41, 42, 85, 44, 41, + 88, 87, 43, 87, 86, 42, 86, 85, 41, 85, 88, 44, +}; + +SubModel dg_logo_submodels[] = +{ + {1, ArrayAndCount(dg_logo_model_tri_faces)}, +}; + +Model dg_logo_model = +{ + ArrayAndCount(dg_logo_model_vtx), + ArrayAndCount(dg_logo_submodels), +}; diff --git a/code/render/render.c b/code/render/render.c new file mode 100644 index 0000000..bc3a4a5 --- /dev/null +++ b/code/render/render.c @@ -0,0 +1,2 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. diff --git a/code/render/render.h b/code/render/render.h new file mode 100644 index 0000000..bbcb566 --- /dev/null +++ b/code/render/render.h @@ -0,0 +1,60 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#ifndef RENDER_H +#define RENDER_H + +//////////////////////////////// +//~ rjf: Render Pass Parameters + +typedef struct R_PassParams R_PassParams; +struct R_PassParams +{ + Mat4x4F32 view; + Mat4x4F32 projection; + Vec3F32 keylight_dir; +}; + +//////////////////////////////// +//~ rjf: Render Resource Types + +typedef struct R_Material R_Material; +struct R_Material +{ + Vec4F32 color; +}; + +typedef struct R_TriFace R_TriFace; +struct R_TriFace +{ + U16 idx[3]; +}; + +//////////////////////////////// +//~ rjf: Render Handles + +typedef struct R_BufferHandle R_BufferHandle; +struct R_BufferHandle +{ + UAddr uaddr[1]; +}; + +typedef struct R_SubModelHandle R_SubModelHandle; +struct R_SubModelHandle +{ + UAddr uaddr[1]; +}; + +//////////////////////////////// +//~ rjf: Render Resources @per_platform + +function R_BufferHandle R_BufferHandleFromData(void *data, UAddr element_count, UAddr element_size); +function R_SubModelHandle R_SubModelHandleFromBuffers(R_BufferHandle vtx_buffer, R_BufferHandle idx_buffer); + +//////////////////////////////// +//~ rjf: Render Commands @per_platform + +function void R_Pass(R_PassParams *params); +function void R_SubModel(R_SubModelHandle handle, R_Material material, Mat4x4F32 xform); + +#endif // RENDER_H diff --git a/code/render/render_inc.c b/code/render/render_inc.c new file mode 100644 index 0000000..fd4034b --- /dev/null +++ b/code/render/render_inc.c @@ -0,0 +1,9 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#include "render.c" +#if PLATFORM_N64 +# include "n64/render/n64_render.c" +#else +# error Platform-defined implementations for render layer not defined for this platform. +#endif diff --git a/code/render/render_inc.h b/code/render/render_inc.h new file mode 100644 index 0000000..a3d6cd8 --- /dev/null +++ b/code/render/render_inc.h @@ -0,0 +1,14 @@ +// The Digital Grove Codebase +// Copyright (c) Ryan Fleury. All rights reserved. + +#ifndef RENDER_INC_H +#define RENDER_INC_H + +#include "render.h" +#if PLATFORM_N64 +# include "n64/render/n64_render.h" +#else +# error Platform-defined implementations for render layer not defined for this platform. +#endif + +#endif // RENDER_INC_H diff --git a/code/third_party/n64tool/crc32.c b/code/third_party/n64tool/crc32.c new file mode 100644 index 0000000..45cd694 --- /dev/null +++ b/code/third_party/n64tool/crc32.c @@ -0,0 +1,72 @@ + +static const unsigned int crc_table[256] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +unsigned int crc32_update(unsigned int crc, const unsigned char *buffer, unsigned int len) +{ + for (unsigned int i = 0; i < len; i++) { + crc = crc_table[(crc ^ buffer[i]) & 0xff] ^ (crc >> 8); + } + return crc; +} + +unsigned int crc32(const unsigned char *buffer, unsigned int len) +{ + unsigned int crc = 0xffffffffL; + for (int i=0; i> 8); + } + return crc ^ 0xffffffffL; +} diff --git a/code/third_party/n64tool/ipl3.h b/code/third_party/n64tool/ipl3.h new file mode 100644 index 0000000..8d52843 --- /dev/null +++ b/code/third_party/n64tool/ipl3.h @@ -0,0 +1,540 @@ +unsigned char default_ipl3[] = { + 0x80, 0x37, 0x12, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4c, 0x69, 0x62, 0x64, + 0x72, 0x61, 0x67, 0x6f, 0x6e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x30, 0x44, 0xd2, 0x36, 0x3c, 0x1d, 0xa4, 0x00, + 0x37, 0xbd, 0x0f, 0xf0, 0x27, 0xbd, 0xff, 0xe0, 0x00, 0x00, 0xd8, 0x25, + 0xaf, 0xbf, 0x00, 0x1c, 0xaf, 0xb1, 0x00, 0x18, 0xaf, 0xb0, 0x00, 0x14, + 0x04, 0x11, 0x02, 0x64, 0x40, 0x04, 0x48, 0x00, 0x3c, 0x02, 0xa4, 0x10, + 0x04, 0x11, 0x02, 0x61, 0x8c, 0x44, 0x00, 0x10, 0x00, 0x00, 0x10, 0x25, + 0x40, 0x82, 0x68, 0x00, 0x40, 0x82, 0x48, 0x00, 0x40, 0x82, 0x58, 0x00, + 0x40, 0x82, 0x90, 0x00, 0x3c, 0x02, 0xa4, 0x30, 0x8c, 0x51, 0x00, 0x04, + 0x24, 0x02, 0x00, 0xb0, 0x32, 0x31, 0x00, 0xf0, 0x12, 0x22, 0x00, 0x58, + 0x3c, 0x02, 0xa4, 0x70, 0x8c, 0x42, 0x00, 0x0c, 0x10, 0x40, 0x00, 0x11, + 0x00, 0x00, 0x80, 0x25, 0x3c, 0x02, 0x12, 0x34, 0x3c, 0x06, 0xa0, 0x00, + 0x24, 0x42, 0x56, 0x78, 0x3c, 0x05, 0x00, 0x20, 0x3c, 0x04, 0x00, 0x80, + 0x02, 0x06, 0x18, 0x21, 0xac, 0x60, 0x00, 0x00, 0xac, 0x62, 0x00, 0x00, + 0x8c, 0x63, 0x00, 0x00, 0x54, 0x62, 0x00, 0x69, 0x24, 0x02, 0x00, 0x01, + 0x02, 0x05, 0x80, 0x21, 0x16, 0x04, 0xff, 0xf9, 0x02, 0x06, 0x18, 0x21, + 0x10, 0x00, 0x00, 0x64, 0x24, 0x02, 0x00, 0x01, 0x3c, 0x04, 0xa4, 0x00, + 0x04, 0x11, 0x01, 0x3d, 0x24, 0x84, 0x03, 0xb4, 0x00, 0x40, 0x80, 0x25, + 0x3c, 0x03, 0xb0, 0x00, 0x24, 0x66, 0x10, 0x40, 0x3c, 0x05, 0xa4, 0x60, + 0x8c, 0xa2, 0x00, 0x10, 0x30, 0x42, 0x00, 0x03, 0x14, 0x40, 0xff, 0xfd, + 0x00, 0x00, 0x00, 0x00, 0x8c, 0x62, 0x10, 0x40, 0x3c, 0x07, 0x60, 0x00, + 0x02, 0x02, 0x20, 0x23, 0x3c, 0x03, 0x80, 0x00, 0x24, 0xe7, 0x00, 0x08, + 0x00, 0xc7, 0x30, 0x21, 0x00, 0x83, 0x18, 0x21, 0xac, 0xa3, 0x00, 0x00, + 0xac, 0xa6, 0x00, 0x04, 0x3c, 0x06, 0xa4, 0x04, 0x8c, 0xc5, 0x00, 0x18, + 0x14, 0xa0, 0xff, 0xfe, 0x3c, 0x05, 0xa4, 0x60, 0x24, 0x42, 0xff, 0xff, + 0xac, 0xa2, 0x00, 0x0c, 0x40, 0x80, 0xe0, 0x00, 0x40, 0x80, 0xe8, 0x00, + 0x3c, 0x02, 0x80, 0x00, 0x24, 0x45, 0x20, 0x00, 0xbc, 0x49, 0x00, 0x00, + 0xbc, 0x49, 0x00, 0x10, 0xbc, 0x49, 0x00, 0x20, 0xbc, 0x49, 0x00, 0x30, + 0x24, 0x42, 0x00, 0x40, 0x14, 0x45, 0xff, 0xfa, 0x00, 0x00, 0x00, 0x00, + 0x3c, 0x02, 0x80, 0x00, 0x24, 0x45, 0x40, 0x00, 0xbc, 0x48, 0x00, 0x00, + 0xbc, 0x48, 0x00, 0x20, 0xbc, 0x48, 0x00, 0x40, 0xbc, 0x48, 0x00, 0x60, + 0x24, 0x42, 0x00, 0x80, 0x14, 0x45, 0xff, 0xfa, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x14, 0x34, 0x00, 0x00, 0x13, 0x16, 0x00, 0x00, 0x46, 0x10, 0x25, + 0x3a, 0x31, 0x00, 0xb0, 0x00, 0x15, 0x32, 0x00, 0x00, 0x46, 0x10, 0x25, + 0x2e, 0x31, 0x00, 0x01, 0x3c, 0x05, 0xa4, 0x00, 0x00, 0x51, 0x10, 0x25, + 0xac, 0xb0, 0x00, 0x00, 0xac, 0xa2, 0x00, 0x08, 0xac, 0xa0, 0x00, 0x0c, + 0x3c, 0x05, 0xa4, 0x60, 0x8c, 0xa2, 0x00, 0x10, 0x30, 0x42, 0x00, 0x01, + 0x14, 0x40, 0xff, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x02, 0x7f, 0xff, + 0x34, 0x42, 0xff, 0xf0, 0x00, 0x82, 0x20, 0x21, 0x00, 0x60, 0x00, 0x08, + 0x00, 0x80, 0xe8, 0x25, 0x3c, 0x02, 0xa0, 0x00, 0x8c, 0x50, 0x03, 0x18, + 0x8c, 0x54, 0x03, 0x00, 0x8c, 0x55, 0x03, 0x0c, 0x3c, 0x02, 0x00, 0x80, + 0x12, 0x02, 0x00, 0x33, 0x3c, 0x02, 0x00, 0x40, 0x16, 0x02, 0x00, 0x0c, + 0x24, 0x02, 0x00, 0x20, 0x3c, 0x03, 0xa4, 0x60, 0x8c, 0x62, 0x00, 0x10, + 0x30, 0x42, 0x00, 0x03, 0x14, 0x40, 0xff, 0xfd, 0x3c, 0x02, 0xb0, 0x00, + 0x8c, 0x42, 0x00, 0x08, 0x3c, 0x03, 0x80, 0x40, 0x00, 0x43, 0x10, 0x2b, + 0x50, 0x40, 0x00, 0x01, 0x3c, 0x10, 0x00, 0x80, 0x24, 0x02, 0x00, 0x20, + 0x00, 0x00, 0x20, 0x25, 0x3c, 0x05, 0xa4, 0x30, 0x8c, 0xa3, 0x00, 0x2c, + 0x00, 0x04, 0x20, 0x40, 0x30, 0x63, 0x00, 0x01, 0x24, 0x42, 0xff, 0xff, + 0x14, 0x40, 0xff, 0xfb, 0x00, 0x64, 0x20, 0x25, 0x04, 0x11, 0x01, 0xe1, + 0x00, 0x00, 0x00, 0x00, 0x24, 0x02, 0x00, 0x01, 0x16, 0xa2, 0x00, 0x05, + 0x3c, 0x02, 0xa4, 0x04, 0x3c, 0x02, 0x80, 0x00, 0x04, 0x11, 0x01, 0xdb, + 0x8c, 0x44, 0x01, 0xa4, 0x3c, 0x02, 0xa4, 0x04, 0x8c, 0x43, 0x00, 0x18, + 0x14, 0x60, 0xff, 0xfe, 0x24, 0x03, 0x00, 0xb0, 0x52, 0x23, 0x00, 0x12, + 0x3c, 0x02, 0xa4, 0x00, 0x24, 0x03, 0x10, 0x00, 0xac, 0x43, 0x00, 0x00, + 0x3c, 0x03, 0x00, 0x80, 0x24, 0x63, 0x20, 0x00, 0xac, 0x43, 0x00, 0x04, + 0x24, 0x03, 0x0f, 0xff, 0xac, 0x43, 0x00, 0x08, 0x3c, 0x05, 0xff, 0xff, + 0x24, 0xa5, 0x7c, 0x00, 0x3c, 0x04, 0xa0, 0x00, 0x02, 0x05, 0x28, 0x21, + 0x04, 0x11, 0x00, 0x17, 0x24, 0x84, 0x04, 0x00, 0x10, 0x00, 0xff, 0x89, + 0x3c, 0x03, 0xb0, 0x00, 0x10, 0x00, 0xff, 0xd9, 0x3c, 0x10, 0x00, 0x7c, + 0x24, 0x43, 0x10, 0x00, 0x24, 0x42, 0x20, 0x00, 0xac, 0x60, 0x00, 0x00, + 0x24, 0x63, 0x00, 0x04, 0x54, 0x62, 0xff, 0xfe, 0xac, 0x60, 0x00, 0x00, + 0x10, 0x00, 0xff, 0xf0, 0x3c, 0x05, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x4c, 0x69, 0x62, 0x64, 0x72, 0x61, 0x67, + 0x6f, 0x6e, 0x20, 0x49, 0x50, 0x4c, 0x33, 0x20, 0x20, 0x43, 0x6f, 0x64, + 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x52, 0x61, 0x73, 0x6b, 0x79, 0x20, + 0x3c, 0x02, 0xa0, 0x00, 0x00, 0x82, 0x20, 0x25, 0xb0, 0x80, 0x00, 0x00, + 0xb4, 0x80, 0x00, 0x07, 0x28, 0xa2, 0x00, 0x09, 0x14, 0x40, 0x00, 0x19, + 0x24, 0xa5, 0xff, 0xf8, 0x00, 0xa4, 0x10, 0x21, 0xb0, 0x40, 0x00, 0x00, + 0xb4, 0x40, 0x00, 0x07, 0x3c, 0x09, 0x00, 0x10, 0x24, 0x84, 0x00, 0x08, + 0x24, 0x0a, 0xf0, 0x00, 0x3c, 0x03, 0xa4, 0x04, 0x24, 0x08, 0x10, 0x00, + 0x00, 0xa9, 0x30, 0x2a, 0x10, 0xc0, 0x00, 0x05, 0x3c, 0x02, 0x00, 0x10, + 0x28, 0xa6, 0x10, 0x00, 0x14, 0xc0, 0x00, 0x02, 0x00, 0xa0, 0x10, 0x25, + 0x00, 0xaa, 0x10, 0x24, 0x8c, 0x67, 0x00, 0x14, 0x14, 0xe0, 0xff, 0xfe, + 0x24, 0x47, 0xff, 0xff, 0x00, 0xa2, 0x28, 0x23, 0xac, 0x68, 0x00, 0x00, + 0xac, 0x64, 0x00, 0x04, 0xac, 0x67, 0x00, 0x0c, 0x1c, 0xa0, 0xff, 0xf1, + 0x00, 0x82, 0x20, 0x21, 0x03, 0xe0, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x02, 0xff, 0xff, 0x14, 0x82, 0x00, 0x0c, 0x3c, 0x02, 0xa4, 0x04, + 0x8c, 0x43, 0x00, 0x18, 0x14, 0x60, 0xff, 0xfe, 0x24, 0x03, 0x10, 0x00, + 0xac, 0x43, 0x00, 0x00, 0x3c, 0x03, 0x00, 0x80, 0x24, 0x63, 0x20, 0x00, + 0xac, 0x43, 0x00, 0x04, 0x24, 0x03, 0x0f, 0xff, 0xac, 0x43, 0x00, 0x08, + 0x03, 0xe0, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x14, 0xa0, 0x00, 0x04, + 0x00, 0x04, 0x25, 0x00, 0x3c, 0x05, 0x00, 0x20, 0x10, 0x00, 0xff, 0xcd, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x05, 0x00, 0x1f, 0x10, 0x00, 0xff, 0xfc, + 0x34, 0xa5, 0x80, 0x00, 0x30, 0xa6, 0x00, 0x01, 0x50, 0xc0, 0x00, 0x05, + 0x03, 0x44, 0x20, 0x04, 0x3c, 0x02, 0xa4, 0x30, 0x24, 0x03, 0x20, 0x00, + 0xac, 0x43, 0x00, 0x00, 0x03, 0x44, 0x20, 0x04, 0x00, 0x85, 0x28, 0x21, + 0x00, 0x05, 0x28, 0x80, 0x3c, 0x02, 0xa3, 0xf0, 0x00, 0x45, 0x10, 0x21, + 0x8c, 0x43, 0x00, 0x00, 0x10, 0xc0, 0x00, 0x03, 0x24, 0x04, 0x10, 0x00, + 0x3c, 0x02, 0xa4, 0x30, 0xac, 0x44, 0x00, 0x00, 0x00, 0x03, 0x26, 0x00, + 0x00, 0x03, 0x16, 0x02, 0x00, 0x44, 0x10, 0x25, 0x00, 0x03, 0x22, 0x02, + 0x30, 0x84, 0xff, 0x00, 0x00, 0x44, 0x10, 0x25, 0x00, 0x03, 0x1a, 0x00, + 0x3c, 0x04, 0x00, 0xff, 0x00, 0x64, 0x18, 0x24, 0x03, 0xe0, 0x00, 0x08, + 0x00, 0x62, 0x10, 0x25, 0x24, 0x02, 0x01, 0x00, 0x24, 0x42, 0xff, 0xff, + 0x14, 0x40, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x1e, 0x00, 0x00, 0x06, 0x16, 0x02, + 0x00, 0x43, 0x10, 0x25, 0x00, 0x06, 0x1a, 0x02, 0x30, 0x63, 0xff, 0x00, + 0x00, 0x43, 0x10, 0x25, 0x00, 0x06, 0x32, 0x00, 0x3c, 0x03, 0x00, 0xff, + 0x00, 0xc3, 0x30, 0x24, 0x00, 0xc2, 0x30, 0x25, 0x24, 0x02, 0xff, 0xff, + 0x54, 0x82, 0x00, 0x07, 0x03, 0x44, 0x20, 0x04, 0x00, 0x05, 0x28, 0x80, + 0x3c, 0x02, 0xa3, 0xf8, 0x00, 0x45, 0x10, 0x21, 0xac, 0x46, 0x00, 0x00, + 0x03, 0xe0, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0x28, 0x21, + 0x00, 0x05, 0x28, 0x80, 0x10, 0x00, 0xff, 0xf9, 0x3c, 0x02, 0xa3, 0xf0, + 0x27, 0xbd, 0xff, 0xe8, 0x38, 0xc7, 0x00, 0x3f, 0x00, 0x80, 0x40, 0x25, + 0xaf, 0xbf, 0x00, 0x14, 0x00, 0xa0, 0x48, 0x25, 0x10, 0xa0, 0x00, 0x02, + 0x24, 0x06, 0x00, 0x46, 0x24, 0x06, 0x00, 0xc6, 0x3c, 0x03, 0x40, 0x00, + 0x00, 0x07, 0x17, 0x80, 0x00, 0x43, 0x10, 0x24, 0x00, 0x07, 0x18, 0xc2, + 0x00, 0x03, 0x1f, 0xc0, 0x00, 0x43, 0x10, 0x25, 0x3c, 0x04, 0x00, 0x40, + 0x00, 0x07, 0x1d, 0x40, 0x00, 0x64, 0x18, 0x24, 0x00, 0x43, 0x10, 0x25, + 0x00, 0x07, 0x1b, 0x00, 0x30, 0x63, 0x40, 0x00, 0x00, 0x43, 0x10, 0x25, + 0x3c, 0x04, 0x00, 0x80, 0x00, 0x07, 0x1c, 0xc0, 0x00, 0x64, 0x18, 0x24, + 0x00, 0x43, 0x10, 0x25, 0x00, 0x07, 0x1a, 0x80, 0x30, 0x63, 0x80, 0x00, + 0x00, 0x43, 0x10, 0x25, 0x00, 0x46, 0x30, 0x25, 0x24, 0x05, 0x00, 0x03, + 0x04, 0x11, 0xff, 0xca, 0x01, 0x00, 0x20, 0x25, 0x11, 0x20, 0x00, 0x1b, + 0x8f, 0xbf, 0x00, 0x14, 0x24, 0x05, 0x00, 0x03, 0x04, 0x11, 0xff, 0xbf, + 0x01, 0x00, 0x20, 0x25, 0x04, 0x11, 0xff, 0xa2, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x05, 0x00, 0x03, 0x04, 0x11, 0xff, 0x9f, 0x01, 0x00, 0x20, 0x25, + 0x00, 0x02, 0x3f, 0x82, 0x00, 0x02, 0x1d, 0x42, 0x30, 0x63, 0x00, 0x02, + 0x30, 0xe7, 0x00, 0x01, 0x00, 0xe3, 0x38, 0x25, 0x00, 0x02, 0x1b, 0x02, + 0x30, 0x63, 0x00, 0x04, 0x00, 0xe3, 0x38, 0x25, 0x00, 0x02, 0x1f, 0x02, + 0x30, 0x63, 0x00, 0x08, 0x00, 0xe3, 0x38, 0x25, 0x00, 0x02, 0x1c, 0xc2, + 0x30, 0x63, 0x00, 0x10, 0x00, 0x02, 0x12, 0x82, 0x00, 0xe3, 0x38, 0x25, + 0x30, 0x42, 0x00, 0x20, 0x00, 0xe2, 0x38, 0x25, 0x8f, 0xbf, 0x00, 0x14, + 0x00, 0xe0, 0x10, 0x25, 0x03, 0xe0, 0x00, 0x08, 0x27, 0xbd, 0x00, 0x18, + 0x27, 0xbd, 0xff, 0xb0, 0x3c, 0x03, 0xa4, 0x70, 0x24, 0x02, 0x00, 0x20, + 0xaf, 0xbf, 0x00, 0x34, 0xaf, 0xb4, 0x00, 0x2c, 0xaf, 0xb3, 0x00, 0x28, + 0xaf, 0xb2, 0x00, 0x24, 0xaf, 0xb0, 0x00, 0x1c, 0xe7, 0xb7, 0x00, 0x44, + 0xaf, 0xb5, 0x00, 0x30, 0xaf, 0xb1, 0x00, 0x20, 0xe7, 0xb9, 0x00, 0x4c, + 0xe7, 0xb8, 0x00, 0x48, 0xe7, 0xb6, 0x00, 0x40, 0xe7, 0xb5, 0x00, 0x3c, + 0xe7, 0xb4, 0x00, 0x38, 0xac, 0x62, 0x00, 0x04, 0x04, 0x11, 0xff, 0x92, + 0x00, 0x00, 0x00, 0x00, 0x24, 0x02, 0x00, 0x14, 0xac, 0x60, 0x00, 0x08, + 0xac, 0x62, 0x00, 0x0c, 0xac, 0x60, 0x00, 0x00, 0x04, 0x11, 0xff, 0x8c, + 0x00, 0x00, 0x00, 0x00, 0x24, 0x02, 0x00, 0x0e, 0xac, 0x62, 0x00, 0x00, + 0x04, 0x11, 0xff, 0x88, 0x24, 0x03, 0x01, 0x0f, 0x3c, 0x02, 0xa4, 0x30, + 0xac, 0x43, 0x00, 0x00, 0x3c, 0x02, 0x18, 0x08, 0x3c, 0x10, 0xa3, 0xf8, + 0x24, 0x42, 0x28, 0x38, 0xae, 0x02, 0x00, 0x08, 0x00, 0x80, 0x98, 0x25, + 0x00, 0x00, 0x28, 0x25, 0x24, 0x04, 0xff, 0xff, 0x02, 0x60, 0xf8, 0x09, + 0x24, 0x1a, 0x00, 0x08, 0x3c, 0x06, 0x00, 0x03, 0x34, 0xc6, 0x80, 0xfc, + 0x24, 0x05, 0x00, 0x01, 0x24, 0x04, 0xff, 0xff, 0x04, 0x11, 0xff, 0x7d, + 0x3c, 0x14, 0xa4, 0x00, 0x3c, 0x02, 0x44, 0x00, 0xc6, 0x97, 0x09, 0xe4, + 0xae, 0x02, 0x00, 0x0c, 0x00, 0x00, 0x90, 0x25, 0xae, 0x00, 0x00, 0x14, + 0x00, 0x00, 0x80, 0x25, 0x00, 0x10, 0x30, 0x80, 0x24, 0x05, 0x00, 0x01, + 0x04, 0x11, 0xff, 0x73, 0x24, 0x04, 0x01, 0xff, 0x24, 0x06, 0x00, 0x46, + 0x24, 0x05, 0x00, 0x03, 0x04, 0x11, 0xff, 0x6f, 0x02, 0x00, 0x20, 0x25, + 0x24, 0x05, 0x00, 0x03, 0x04, 0x11, 0xff, 0x4b, 0x02, 0x00, 0x20, 0x25, + 0x30, 0x42, 0x00, 0x02, 0x14, 0x40, 0x00, 0x21, 0x00, 0x00, 0x00, 0x00, + 0x52, 0x00, 0x00, 0x05, 0x00, 0x10, 0x80, 0x43, 0x24, 0x05, 0x00, 0x01, + 0x02, 0x60, 0xf8, 0x09, 0x26, 0x04, 0xff, 0xfe, 0x00, 0x10, 0x80, 0x43, + 0x24, 0x02, 0x00, 0x01, 0x02, 0x02, 0x10, 0x04, 0x24, 0x42, 0xff, 0xff, + 0x3c, 0x03, 0x00, 0x06, 0x24, 0x63, 0x36, 0x34, 0x00, 0x02, 0x14, 0xc0, + 0x00, 0x43, 0x10, 0x25, 0x8f, 0xbf, 0x00, 0x34, 0x3c, 0x03, 0xa4, 0x70, + 0xac, 0x62, 0x00, 0x10, 0x8c, 0x62, 0x00, 0x10, 0x8f, 0xb5, 0x00, 0x30, + 0x8f, 0xb4, 0x00, 0x2c, 0x8f, 0xb3, 0x00, 0x28, 0x8f, 0xb1, 0x00, 0x20, + 0x8f, 0xb0, 0x00, 0x1c, 0xc7, 0xb9, 0x00, 0x4c, 0xc7, 0xb8, 0x00, 0x48, + 0xc7, 0xb7, 0x00, 0x44, 0xc7, 0xb6, 0x00, 0x40, 0xc7, 0xb5, 0x00, 0x3c, + 0xc7, 0xb4, 0x00, 0x38, 0x02, 0x40, 0x10, 0x25, 0x8f, 0xb2, 0x00, 0x24, + 0x03, 0xe0, 0x00, 0x08, 0x27, 0xbd, 0x00, 0x50, 0x16, 0x00, 0x00, 0x44, + 0x00, 0x00, 0x28, 0x25, 0x44, 0x80, 0xa0, 0x00, 0x3c, 0x02, 0xa4, 0x00, + 0x3c, 0x11, 0xa0, 0x00, 0xc4, 0x59, 0x09, 0xe8, 0x02, 0x51, 0x88, 0x21, + 0x46, 0x00, 0xa5, 0x86, 0x00, 0x00, 0xa8, 0x25, 0x46, 0x00, 0xa6, 0x06, + 0x02, 0x00, 0x20, 0x25, 0x32, 0xa6, 0x00, 0xff, 0x44, 0x80, 0xa8, 0x00, + 0x04, 0x11, 0xff, 0x51, 0x00, 0x00, 0x28, 0x25, 0x24, 0x03, 0x00, 0x0a, + 0x24, 0x04, 0xff, 0xff, 0xae, 0x24, 0x00, 0x00, 0xae, 0x24, 0x00, 0x04, + 0x92, 0x22, 0x00, 0x05, 0x30, 0x42, 0x00, 0xff, 0xa3, 0xa2, 0x00, 0x10, + 0x93, 0xa2, 0x00, 0x10, 0x30, 0x42, 0x00, 0xff, 0x14, 0x40, 0x00, 0x33, + 0x00, 0x00, 0x00, 0x00, 0x24, 0x63, 0xff, 0xff, 0x14, 0x60, 0xff, 0xf5, + 0x00, 0x00, 0x00, 0x00, 0x46, 0x15, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x00, + 0x45, 0x02, 0x00, 0x05, 0x46, 0x19, 0xad, 0x42, 0x8e, 0x24, 0x00, 0x04, + 0x04, 0x11, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x46, 0x19, 0xad, 0x42, + 0x44, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x80, 0x00, 0x20, + 0x46, 0x16, 0xad, 0x81, 0x46, 0x00, 0xb5, 0x82, 0xc6, 0x80, 0x09, 0xe4, + 0x46, 0x15, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x45, 0x01, 0x00, 0x05, + 0x46, 0x16, 0xa5, 0x00, 0x26, 0xb5, 0x00, 0x01, 0x24, 0x02, 0x00, 0x40, + 0x56, 0xa2, 0xff, 0xd8, 0x46, 0x00, 0xad, 0x86, 0x3c, 0x02, 0xa4, 0x00, + 0xc4, 0x40, 0x09, 0xec, 0x3c, 0x02, 0xa4, 0x00, 0x46, 0x00, 0xa5, 0x02, + 0xc4, 0x40, 0x09, 0xf0, 0x46, 0x00, 0xa5, 0x00, 0x46, 0x00, 0xa0, 0x0d, + 0x44, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x40, 0x00, 0x17, + 0x00, 0x00, 0x60, 0x25, 0x3c, 0x06, 0x00, 0x03, 0x34, 0xc6, 0x80, 0xf4, + 0x24, 0x05, 0x00, 0x01, 0x04, 0x11, 0xff, 0x06, 0x02, 0x00, 0x20, 0x25, + 0x10, 0x00, 0xff, 0xa2, 0x00, 0x10, 0x80, 0x43, 0x02, 0x60, 0xf8, 0x09, + 0x26, 0x04, 0xff, 0xfe, 0x24, 0x02, 0x00, 0x08, 0x16, 0x02, 0xff, 0xb9, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xff, 0x9b, 0x00, 0x10, 0x80, 0x43, + 0x93, 0xa2, 0x00, 0x10, 0x30, 0x42, 0x00, 0x01, 0x54, 0x40, 0x00, 0x01, + 0x46, 0x17, 0xad, 0x40, 0x93, 0xa2, 0x00, 0x10, 0x30, 0x42, 0x00, 0xff, + 0x10, 0x00, 0xff, 0xc2, 0x00, 0x02, 0x10, 0x42, 0x00, 0x00, 0x68, 0x25, + 0x00, 0x00, 0x58, 0x25, 0x24, 0x0e, 0x00, 0x40, 0x02, 0x00, 0x20, 0x25, + 0x31, 0x66, 0x00, 0xff, 0x04, 0x11, 0xff, 0x05, 0x24, 0x05, 0x00, 0x01, + 0x00, 0x4a, 0x18, 0x23, 0x00, 0x03, 0x27, 0xc3, 0x00, 0x83, 0x18, 0x26, + 0x11, 0x60, 0x00, 0x3a, 0x00, 0x64, 0x18, 0x23, 0x00, 0x6d, 0x20, 0x2a, + 0x54, 0x80, 0x00, 0x02, 0x01, 0x60, 0x60, 0x25, 0x01, 0xa0, 0x18, 0x25, + 0x01, 0x42, 0x10, 0x2a, 0x14, 0x40, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x25, 0x6b, 0x00, 0x01, 0x15, 0x6e, 0xff, 0xee, 0x00, 0x60, 0x68, 0x25, + 0x11, 0x80, 0xff, 0xd3, 0x31, 0x86, 0x00, 0xff, 0x24, 0x05, 0x00, 0x01, + 0x04, 0x11, 0xfe, 0xf1, 0x02, 0x00, 0x20, 0x25, 0x00, 0x00, 0x28, 0x25, + 0x04, 0x11, 0xfe, 0xb6, 0x02, 0x00, 0x20, 0x25, 0x00, 0x40, 0x38, 0x25, + 0x30, 0x43, 0xf0, 0x00, 0x24, 0x02, 0x10, 0x00, 0x14, 0x62, 0xff, 0xc8, + 0x30, 0xe2, 0x0f, 0x00, 0x24, 0x03, 0x09, 0x00, 0x14, 0x43, 0xff, 0xc5, + 0x30, 0xe2, 0x00, 0xf0, 0x24, 0x03, 0x00, 0xb0, 0x14, 0x43, 0xff, 0xc2, + 0x30, 0xe2, 0x00, 0x04, 0x10, 0x40, 0xff, 0xc0, 0x24, 0x05, 0x00, 0x09, + 0x04, 0x11, 0xfe, 0xa7, 0x02, 0x00, 0x20, 0x25, 0x00, 0x02, 0x14, 0x02, + 0x24, 0x03, 0x00, 0x05, 0x10, 0x43, 0x00, 0x17, 0x3c, 0x06, 0x04, 0x0a, + 0x30, 0xe7, 0x00, 0x01, 0x14, 0xe0, 0x00, 0x03, 0x24, 0xc6, 0x1c, 0x10, + 0x3c, 0x06, 0x04, 0x12, 0x24, 0xc6, 0x0c, 0x08, 0x24, 0x05, 0x00, 0x06, + 0x04, 0x11, 0xfe, 0xbc, 0x02, 0x00, 0x20, 0x25, 0x3c, 0x02, 0xa0, 0x20, + 0x3c, 0x03, 0x00, 0x08, 0x02, 0x42, 0x10, 0x21, 0x8e, 0x24, 0x00, 0x00, + 0x8e, 0x24, 0x00, 0x04, 0x02, 0x23, 0x88, 0x21, 0x14, 0x51, 0xff, 0xfc, + 0x00, 0x00, 0x00, 0x00, 0x3c, 0x02, 0x00, 0x20, 0x26, 0x10, 0x00, 0x02, + 0x10, 0x00, 0xff, 0x3a, 0x02, 0x42, 0x90, 0x21, 0x10, 0x00, 0xff, 0xca, + 0x00, 0x00, 0x60, 0x25, 0x10, 0x00, 0xff, 0xee, 0x24, 0xc6, 0x1c, 0x10, + 0x3f, 0x80, 0x00, 0x00, 0x3c, 0x4c, 0xcc, 0xcd, 0x40, 0x0c, 0xcc, 0xcd, + 0x3f, 0x00, 0x00, 0x00, 0x3c, 0x02, 0xcc, 0x9e, 0x24, 0x42, 0x2d, 0x51, + 0x00, 0x82, 0x00, 0x18, 0x00, 0x00, 0x10, 0x12, 0x00, 0x02, 0x1c, 0x42, + 0x00, 0x02, 0x13, 0xc0, 0x00, 0x43, 0x10, 0x25, 0x3c, 0x03, 0x1b, 0x87, + 0x24, 0x63, 0x35, 0x93, 0x00, 0x43, 0x00, 0x18, 0x00, 0x00, 0x10, 0x12, + 0x00, 0x5b, 0x10, 0x26, 0x00, 0x02, 0x1c, 0xc2, 0x00, 0x02, 0x13, 0x40, + 0x00, 0x62, 0x10, 0x25, 0x00, 0x02, 0x18, 0x80, 0x00, 0x62, 0x18, 0x21, + 0x3c, 0x02, 0xe6, 0x54, 0x24, 0x42, 0x6b, 0x64, 0x03, 0xe0, 0x00, 0x08, + 0x00, 0x62, 0xd8, 0x21, 0x00, 0x1b, 0x1c, 0x02, 0x3c, 0x02, 0x85, 0xeb, + 0x00, 0x7b, 0x18, 0x26, 0x34, 0x42, 0xca, 0x6b, 0x00, 0x62, 0x00, 0x18, + 0x00, 0x00, 0x10, 0x12, 0x00, 0x02, 0x1b, 0x42, 0x00, 0x62, 0x18, 0x26, + 0x3c, 0x02, 0xc2, 0xb2, 0x34, 0x42, 0xae, 0x35, 0x00, 0x62, 0x00, 0x18, + 0x00, 0x00, 0x10, 0x12, 0x00, 0x02, 0x1c, 0x02, 0x03, 0xe0, 0x00, 0x08, + 0x00, 0x62, 0x10, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x17, + 0xc4, 0x1d, 0x1c, 0xf5, 0x3c, 0x0a, 0xb0, 0x00, 0x3c, 0x09, 0xa4, 0x00, + 0x25, 0x4b, 0x0f, 0xc0, 0x25, 0x3d, 0x1f, 0xf0, 0x8d, 0x5f, 0x00, 0x40, + 0xad, 0x3f, 0x00, 0x40, 0x25, 0x4a, 0x00, 0x04, 0x15, 0x4b, 0xff, 0xfc, + 0x25, 0x29, 0x00, 0x04, 0x00, 0x00, 0x48, 0x25, 0x24, 0x0a, 0x00, 0x40, + 0x27, 0xab, 0xe0, 0x50, 0x01, 0x60, 0x00, 0x08, 0x27, 0xbf, 0xf5, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x27, 0xbd, 0xff, 0xa0, 0x3c, 0x07, 0x10, 0x00, + 0x3c, 0x03, 0x7f, 0x45, 0x3c, 0x02, 0x14, 0x00, 0x3c, 0x04, 0xa0, 0x00, + 0xaf, 0xbe, 0x00, 0x58, 0xaf, 0xb3, 0x00, 0x44, 0xaf, 0xbf, 0x00, 0x5c, + 0xaf, 0xb7, 0x00, 0x54, 0xaf, 0xb6, 0x00, 0x50, 0xaf, 0xb5, 0x00, 0x4c, + 0xaf, 0xb4, 0x00, 0x48, 0xaf, 0xb2, 0x00, 0x40, 0xaf, 0xb1, 0x00, 0x3c, + 0xaf, 0xb0, 0x00, 0x38, 0x03, 0xa0, 0xf0, 0x25, 0x24, 0xf3, 0x10, 0x00, + 0x24, 0x63, 0x4c, 0x46, 0x24, 0x42, 0x10, 0x00, 0x02, 0x64, 0x28, 0x25, + 0x8c, 0xa5, 0x00, 0x00, 0x50, 0xa3, 0x00, 0xfa, 0x00, 0x13, 0x12, 0x00, + 0x26, 0x73, 0x01, 0x00, 0x16, 0x62, 0xff, 0xfb, 0x02, 0x64, 0x28, 0x25, + 0x3c, 0x04, 0xb0, 0x00, 0x10, 0x00, 0x01, 0x01, 0x24, 0x84, 0x17, 0x55, + 0x16, 0x22, 0x00, 0x7c, 0x24, 0x14, 0x00, 0x20, 0x24, 0x14, 0x00, 0x38, + 0x24, 0x02, 0x00, 0x24, 0x3c, 0x04, 0xa0, 0x00, 0x00, 0x53, 0x10, 0x21, + 0x00, 0x44, 0x10, 0x25, 0x24, 0x04, 0x00, 0x02, 0x8c, 0x48, 0x00, 0x00, + 0x16, 0x24, 0x00, 0x02, 0x24, 0x02, 0x00, 0x2c, 0x24, 0x02, 0x00, 0x38, + 0x3c, 0x04, 0xa0, 0x00, 0x00, 0x53, 0x10, 0x21, 0x00, 0x44, 0x10, 0x25, + 0x8c, 0x42, 0x00, 0x00, 0x24, 0x04, 0x00, 0x02, 0x00, 0x02, 0x14, 0x02, + 0xaf, 0xc2, 0x00, 0x10, 0x16, 0x24, 0x00, 0x02, 0x24, 0x02, 0x00, 0x18, + 0x24, 0x02, 0x00, 0x1c, 0x3c, 0x04, 0xa4, 0x00, 0x8c, 0x83, 0x00, 0x00, + 0x00, 0x53, 0x10, 0x21, 0x3c, 0x04, 0xa0, 0x00, 0x00, 0x44, 0x10, 0x25, + 0x8c, 0x42, 0x00, 0x00, 0xaf, 0xc3, 0x00, 0x28, 0xaf, 0xc2, 0x00, 0x2c, + 0x8f, 0xc2, 0x00, 0x10, 0x01, 0x13, 0x40, 0x21, 0x00, 0x54, 0x00, 0x18, + 0x00, 0x00, 0x30, 0x12, 0x24, 0xc2, 0x00, 0x10, 0x03, 0xa2, 0xe8, 0x23, + 0x27, 0xb0, 0x00, 0x10, 0x00, 0x10, 0x10, 0x23, 0x30, 0x42, 0x00, 0x0f, + 0x02, 0x02, 0x80, 0x21, 0x00, 0xc0, 0x28, 0x25, 0x04, 0x11, 0x00, 0xd8, + 0x02, 0x00, 0x20, 0x25, 0x3c, 0x02, 0xa4, 0x60, 0x8c, 0x44, 0x00, 0x10, + 0x30, 0x84, 0x00, 0x03, 0x14, 0x80, 0xff, 0xfd, 0x00, 0x00, 0x00, 0x00, + 0x24, 0xc6, 0xff, 0xff, 0xac, 0x50, 0x00, 0x00, 0xac, 0x48, 0x00, 0x04, + 0xac, 0x46, 0x00, 0x0c, 0x04, 0x11, 0x01, 0x36, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x03, 0x00, 0x04, 0x3a, 0x22, 0x00, 0x02, 0x14, 0x40, 0x00, 0x03, + 0xaf, 0xc3, 0x00, 0x14, 0x24, 0x03, 0x00, 0x0c, 0xaf, 0xc3, 0x00, 0x14, + 0x24, 0x03, 0x00, 0x08, 0x14, 0x40, 0x00, 0x03, 0xaf, 0xc3, 0x00, 0x18, + 0x24, 0x03, 0x00, 0x14, 0xaf, 0xc3, 0x00, 0x18, 0x24, 0x03, 0x00, 0x0c, + 0x14, 0x40, 0x00, 0x03, 0xaf, 0xc3, 0x00, 0x1c, 0x24, 0x03, 0x00, 0x1c, + 0xaf, 0xc3, 0x00, 0x1c, 0x24, 0x03, 0x00, 0x10, 0x14, 0x40, 0x00, 0x03, + 0xaf, 0xc3, 0x00, 0x20, 0x24, 0x03, 0x00, 0x24, 0xaf, 0xc3, 0x00, 0x20, + 0x24, 0x03, 0x00, 0x14, 0x14, 0x40, 0x00, 0x03, 0xaf, 0xc3, 0x00, 0x24, + 0x24, 0x03, 0x00, 0x2c, 0xaf, 0xc3, 0x00, 0x24, 0x14, 0x40, 0x00, 0x02, + 0x24, 0x17, 0x00, 0x18, 0x24, 0x17, 0x00, 0x04, 0x3c, 0x02, 0xa4, 0x00, + 0x24, 0x42, 0x03, 0x30, 0x00, 0x00, 0x90, 0x25, 0x00, 0x00, 0x88, 0x25, + 0xaf, 0xc2, 0x00, 0x30, 0xaf, 0xd4, 0x00, 0x34, 0x8f, 0xc2, 0x00, 0x10, + 0x02, 0x42, 0x10, 0x2a, 0x54, 0x40, 0x00, 0x23, 0x8f, 0xc2, 0x00, 0x14, + 0x3c, 0x02, 0x00, 0xaa, 0x3c, 0x03, 0xa4, 0x04, 0x34, 0x42, 0xaa, 0x0e, + 0xac, 0x62, 0x00, 0x10, 0x3c, 0x02, 0xa4, 0x08, 0xac, 0x40, 0x00, 0x00, + 0xac, 0x60, 0x00, 0x1c, 0x3c, 0x02, 0xa4, 0x30, 0x24, 0x03, 0x05, 0x55, + 0xac, 0x43, 0x00, 0x0c, 0x24, 0x04, 0x00, 0x02, 0xac, 0x43, 0x00, 0x08, + 0x3c, 0x03, 0xa4, 0x60, 0xac, 0x64, 0x00, 0x10, 0x3c, 0x03, 0xa4, 0x80, + 0xac, 0x60, 0x00, 0x18, 0x3c, 0x03, 0xa4, 0x50, 0xac, 0x60, 0x00, 0x0c, + 0x24, 0x03, 0x08, 0x00, 0xac, 0x43, 0x00, 0x00, 0x3c, 0x02, 0xa4, 0x00, + 0x24, 0x42, 0x0a, 0x48, 0x00, 0x40, 0xf8, 0x09, 0x00, 0x00, 0x00, 0x00, + 0x3c, 0x03, 0xa4, 0x00, 0xac, 0x62, 0x00, 0x04, 0x3c, 0x03, 0x80, 0x00, + 0xac, 0x62, 0x01, 0xa4, 0x3c, 0x02, 0xb0, 0x00, 0x24, 0x42, 0x18, 0x28, + 0x00, 0x40, 0xf8, 0x09, 0x8f, 0xc4, 0x00, 0x2c, 0x10, 0x00, 0xff, 0x86, + 0x24, 0x02, 0x00, 0x1c, 0x3c, 0x03, 0x64, 0xe3, 0x02, 0x02, 0x10, 0x21, + 0x8c, 0x49, 0x00, 0x00, 0x8f, 0xc2, 0x00, 0x18, 0x24, 0x64, 0x63, 0x41, + 0x02, 0x02, 0x10, 0x21, 0x8c, 0x54, 0x00, 0x00, 0x8f, 0xc2, 0x00, 0x1c, + 0x02, 0x02, 0x10, 0x21, 0x8c, 0x55, 0x00, 0x00, 0x8f, 0xc2, 0x00, 0x20, + 0x02, 0x02, 0x10, 0x21, 0x8c, 0x56, 0x00, 0x00, 0x8f, 0xc2, 0x00, 0x24, + 0x02, 0x02, 0x10, 0x21, 0x8c, 0x4a, 0x00, 0x00, 0x02, 0x17, 0x10, 0x21, + 0x8c, 0x4b, 0x00, 0x00, 0x8e, 0x02, 0x00, 0x00, 0x14, 0x44, 0x00, 0x14, + 0x24, 0x04, 0x00, 0x01, 0x16, 0x80, 0x00, 0x14, 0x02, 0x80, 0x88, 0x25, + 0x26, 0xc2, 0x00, 0x17, 0x00, 0x02, 0x10, 0xc2, 0x00, 0x02, 0x10, 0xc0, + 0x03, 0xa2, 0xe8, 0x23, 0x27, 0xb1, 0x00, 0x10, 0x00, 0x11, 0x10, 0x23, + 0x30, 0x42, 0x00, 0x0f, 0x02, 0x22, 0x88, 0x21, 0x02, 0xc0, 0x28, 0x25, + 0x04, 0x11, 0x00, 0x62, 0x02, 0x20, 0x20, 0x25, 0x16, 0xc0, 0x00, 0x0e, + 0x02, 0x20, 0xa0, 0x25, 0x8f, 0xc2, 0x00, 0x34, 0x26, 0x52, 0x00, 0x01, + 0x10, 0x00, 0xff, 0xb3, 0x02, 0x02, 0x80, 0x21, 0x14, 0x44, 0xff, 0xfc, + 0x8f, 0xc2, 0x00, 0x34, 0x12, 0xc0, 0xff, 0xf9, 0x32, 0x82, 0x00, 0x07, + 0x10, 0x40, 0x00, 0x05, 0x31, 0x22, 0x00, 0x01, 0x3c, 0x04, 0xb0, 0x00, + 0x10, 0x00, 0x00, 0x51, 0x24, 0x84, 0x17, 0x18, 0x31, 0x22, 0x00, 0x01, + 0x50, 0x40, 0x00, 0x04, 0x31, 0x64, 0x10, 0x00, 0x3c, 0x04, 0xb0, 0x00, + 0x10, 0x00, 0x00, 0x4b, 0x24, 0x84, 0x16, 0xfa, 0x10, 0x80, 0x00, 0x02, + 0x02, 0x80, 0x10, 0x25, 0x02, 0xa0, 0x10, 0x25, 0x8f, 0xc3, 0x00, 0x28, + 0x3c, 0x05, 0x80, 0x00, 0x00, 0x4a, 0x10, 0x21, 0x00, 0x65, 0x28, 0x21, + 0x00, 0xa2, 0x10, 0x2b, 0x10, 0x40, 0x00, 0x04, 0x24, 0x05, 0xff, 0xfe, + 0x3c, 0x04, 0xb0, 0x00, 0x10, 0x00, 0x00, 0x3e, 0x24, 0x84, 0x16, 0xe4, + 0x26, 0xc2, 0x00, 0x01, 0x00, 0x45, 0x10, 0x24, 0x01, 0x33, 0x48, 0x21, + 0x3c, 0x05, 0xa4, 0x60, 0x8c, 0xaa, 0x00, 0x10, 0x31, 0x4a, 0x00, 0x03, + 0x15, 0x40, 0xff, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x24, 0x42, 0xff, 0xff, + 0xac, 0xb4, 0x00, 0x00, 0xac, 0xa9, 0x00, 0x04, 0xac, 0xa2, 0x00, 0x0c, + 0x10, 0x80, 0x00, 0x16, 0x02, 0xc0, 0x28, 0x25, 0x02, 0x80, 0x20, 0x25, + 0x02, 0x20, 0xf8, 0x09, 0x02, 0xa0, 0x30, 0x25, 0x00, 0x40, 0x28, 0x25, + 0x02, 0xa0, 0x20, 0x25, 0x04, 0x11, 0x00, 0x2b, 0x00, 0x40, 0x48, 0x25, + 0x02, 0x95, 0x10, 0x2b, 0x10, 0x40, 0x00, 0x10, 0x02, 0xd4, 0x18, 0x21, + 0x02, 0xa3, 0x10, 0x2b, 0x54, 0x40, 0x00, 0x01, 0x02, 0xa0, 0x18, 0x25, + 0x00, 0x74, 0x30, 0x23, 0x00, 0xc0, 0x28, 0x25, 0x04, 0x11, 0x00, 0x21, + 0x02, 0x80, 0x20, 0x25, 0x8f, 0xc2, 0x00, 0x30, 0x00, 0xc0, 0x28, 0x25, + 0x00, 0x40, 0xf8, 0x09, 0x02, 0x80, 0x20, 0x25, 0x04, 0x11, 0x00, 0x84, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xff, 0xbb, 0x8f, 0xc2, 0x00, 0x34, + 0x01, 0x35, 0x10, 0x21, 0x00, 0x43, 0x20, 0x2b, 0x10, 0x80, 0xff, 0xf9, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x20, 0x2b, 0x54, 0x80, 0xff, 0xee, + 0x00, 0x40, 0xa0, 0x25, 0x10, 0x00, 0xff, 0xed, 0x00, 0x74, 0x30, 0x23, + 0x3c, 0x03, 0xa4, 0x00, 0xac, 0x62, 0x00, 0x0c, 0x26, 0x62, 0x00, 0x04, + 0x00, 0x44, 0x10, 0x25, 0x8c, 0x42, 0x00, 0x00, 0x3c, 0x03, 0x00, 0xff, + 0x00, 0x02, 0x8e, 0x02, 0x00, 0x43, 0x10, 0x24, 0x3c, 0x03, 0x00, 0x01, + 0x14, 0x43, 0xff, 0x03, 0x24, 0x02, 0x00, 0x02, 0x3c, 0x04, 0xb0, 0x00, + 0x24, 0x84, 0x17, 0x35, 0x04, 0x11, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x03, 0xff, 0xf0, 0x00, 0x83, 0x18, 0x24, 0x00, 0x83, 0x20, 0x23, + 0x00, 0x85, 0x20, 0x21, 0x00, 0x00, 0x10, 0x25, 0x00, 0x44, 0x28, 0x2a, + 0x14, 0xa0, 0x00, 0x03, 0x00, 0x43, 0x28, 0x21, 0x03, 0xe0, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0xbc, 0xb5, 0x00, 0x00, 0x10, 0x00, 0xff, 0xf9, + 0x24, 0x42, 0x00, 0x10, 0x24, 0x02, 0xff, 0xfc, 0x00, 0x82, 0x10, 0x24, + 0x3c, 0x03, 0xa0, 0x00, 0x00, 0x43, 0x10, 0x25, 0x8c, 0x42, 0x00, 0x00, + 0x00, 0x04, 0x20, 0x27, 0x00, 0x04, 0x20, 0xc0, 0x00, 0x82, 0x10, 0x06, + 0x03, 0xe0, 0x00, 0x08, 0x30, 0x42, 0x00, 0xff, 0x27, 0xbd, 0xff, 0xe8, + 0x3c, 0x02, 0xa4, 0x40, 0x3c, 0x03, 0xa0, 0x10, 0xaf, 0xbf, 0x00, 0x14, + 0xac, 0x43, 0x00, 0x04, 0x3c, 0x03, 0xa0, 0x12, 0x00, 0x80, 0x38, 0x25, + 0x3c, 0x02, 0xa0, 0x10, 0x24, 0x04, 0xc9, 0x50, 0x24, 0x63, 0x58, 0x00, + 0xa4, 0x44, 0x00, 0x00, 0x24, 0x42, 0x00, 0x02, 0x54, 0x43, 0xff, 0xfe, + 0xa4, 0x44, 0x00, 0x00, 0x3c, 0x02, 0xa4, 0x40, 0x24, 0x03, 0x01, 0x40, + 0xac, 0x43, 0x00, 0x08, 0x24, 0x03, 0x02, 0x00, 0xac, 0x43, 0x00, 0x30, + 0x3c, 0x06, 0xa0, 0x10, 0x24, 0x03, 0x04, 0x00, 0x3c, 0x08, 0xb0, 0x00, + 0xac, 0x43, 0x00, 0x34, 0x24, 0xc6, 0x64, 0x50, 0x25, 0x08, 0x17, 0x6c, + 0x24, 0x0b, 0xf7, 0xb2, 0x04, 0x11, 0xff, 0xdb, 0x00, 0xe0, 0x20, 0x25, + 0x00, 0x02, 0x1e, 0x00, 0x00, 0x03, 0x1e, 0x03, 0x10, 0x40, 0x00, 0x18, + 0x24, 0xe7, 0x00, 0x01, 0x24, 0x62, 0xff, 0xff, 0x00, 0x02, 0x28, 0x80, + 0x00, 0xa2, 0x28, 0x21, 0x01, 0x05, 0x28, 0x21, 0x24, 0xac, 0x00, 0x05, + 0x00, 0xc0, 0x48, 0x25, 0x24, 0x0d, 0x00, 0x08, 0x04, 0x11, 0xff, 0xce, + 0x00, 0xa0, 0x20, 0x25, 0x01, 0x20, 0x20, 0x25, 0x00, 0x00, 0x18, 0x25, + 0x00, 0x62, 0x50, 0x07, 0x31, 0x4a, 0x00, 0x01, 0x55, 0x40, 0x00, 0x01, + 0xa4, 0x8b, 0x00, 0x00, 0x24, 0x63, 0x00, 0x01, 0x14, 0x6d, 0xff, 0xfa, + 0x24, 0x84, 0x02, 0x80, 0x24, 0xa5, 0x00, 0x01, 0x14, 0xac, 0xff, 0xf3, + 0x25, 0x29, 0x00, 0x02, 0x10, 0x00, 0xff, 0xe4, 0x24, 0xc6, 0x00, 0x0e, + 0x3c, 0x06, 0xa4, 0x00, 0x04, 0x11, 0xff, 0xbd, 0x24, 0xc4, 0x00, 0x09, + 0x00, 0x40, 0x28, 0x25, 0x04, 0x11, 0xff, 0xba, 0x24, 0xc4, 0x00, 0x0b, + 0x00, 0x05, 0x20, 0xc0, 0x00, 0x85, 0x20, 0x23, 0x3c, 0x03, 0xb0, 0x00, + 0x00, 0x04, 0x20, 0x80, 0x24, 0x63, 0x16, 0x90, 0x00, 0x64, 0x18, 0x21, + 0x3c, 0x04, 0xa4, 0x40, 0x24, 0x85, 0x00, 0x14, 0x24, 0x84, 0x00, 0x30, + 0x8c, 0x66, 0x00, 0x00, 0x24, 0xa5, 0x00, 0x04, 0xac, 0xa6, 0xff, 0xfc, + 0x14, 0xa4, 0xff, 0xfc, 0x24, 0x63, 0x00, 0x04, 0x10, 0x40, 0x00, 0x02, + 0x24, 0x03, 0x32, 0x02, 0x24, 0x03, 0x12, 0x02, 0x3c, 0x02, 0xa4, 0x40, + 0xac, 0x43, 0x00, 0x00, 0x10, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x3c, 0x03, 0xa4, 0x60, 0x8c, 0x62, 0x00, 0x10, 0x30, 0x42, 0x00, 0x03, + 0x14, 0x40, 0xff, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x40, 0x04, 0x48, 0x00, + 0x3c, 0x19, 0xa4, 0x00, 0x27, 0x39, 0x09, 0xf4, 0x03, 0x20, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x23, 0x3a, 0x00, 0x00, 0x02, 0x71, + 0x00, 0x15, 0x0c, 0x69, 0x0c, 0x6f, 0x0c, 0x6e, 0x00, 0x80, 0x03, 0x00, + 0x00, 0x5f, 0x02, 0x39, 0x00, 0x09, 0x02, 0x6b, 0x03, 0xe5, 0x22, 0x39, + 0x00, 0x00, 0x02, 0x0d, 0x00, 0x00, 0x0c, 0x15, 0x0c, 0x15, 0x0c, 0x15, + 0x00, 0x6c, 0x02, 0xec, 0x00, 0x25, 0x01, 0xff, 0x00, 0x0e, 0x02, 0x04, + 0x04, 0x65, 0x1e, 0x39, 0x00, 0x00, 0x02, 0x0d, 0x00, 0x04, 0x0c, 0x11, + 0x0c, 0x19, 0x0c, 0x1a, 0x00, 0x6c, 0x02, 0xec, 0x00, 0x25, 0x01, 0xff, + 0x00, 0x0e, 0x02, 0x04, 0x10, 0x17, 0x11, 0x01, 0x1e, 0x10, 0x12, 0x18, + 0x10, 0x19, 0x1f, 0x01, 0x1f, 0x1a, 0x1a, 0x01, 0x17, 0x0c, 0x1d, 0x12, + 0x10, 0x00, 0x10, 0x17, 0x11, 0x01, 0x1a, 0x11, 0x11, 0x1e, 0x10, 0x1f, + 0x01, 0x19, 0x1a, 0x1f, 0x01, 0x04, 0x01, 0x0d, 0x24, 0x1f, 0x10, 0x01, + 0x0c, 0x17, 0x14, 0x12, 0x19, 0x10, 0x0f, 0x00, 0x10, 0x17, 0x11, 0x01, + 0x21, 0x0c, 0x0f, 0x0f, 0x1d, 0x01, 0x19, 0x1a, 0x1f, 0x01, 0x0a, 0x01, + 0x0d, 0x24, 0x1f, 0x10, 0x01, 0x0c, 0x17, 0x14, 0x12, 0x19, 0x10, 0x0f, + 0x00, 0x10, 0x17, 0x11, 0x01, 0x17, 0x14, 0x1f, 0x1f, 0x17, 0x10, 0x01, + 0x10, 0x19, 0x0f, 0x14, 0x0c, 0x19, 0x01, 0x19, 0x1a, 0x1f, 0x01, 0x1e, + 0x20, 0x1b, 0x1b, 0x1a, 0x1d, 0x1f, 0x10, 0x0f, 0x00, 0x10, 0x17, 0x11, + 0x01, 0x13, 0x10, 0x0c, 0x0f, 0x10, 0x1d, 0x01, 0x19, 0x1a, 0x1f, 0x01, + 0x11, 0x1a, 0x20, 0x19, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7e, 0xa1, 0x99, 0x85, 0x7e, 0x84, 0x82, 0xff, 0x80, 0x80, 0xc1, + 0xa1, 0x91, 0x89, 0x86, 0x89, 0x89, 0x89, 0x89, 0x76, 0x18, 0x14, 0x12, + 0xff, 0x10, 0x8f, 0x89, 0x89, 0x89, 0x71, 0x7e, 0x89, 0x89, 0x89, 0x72, + 0x01, 0x81, 0x61, 0x19, 0x07, 0x62, 0x95, 0x89, 0x95, 0x62, 0x4e, 0x91, + 0x91, 0x91, 0x7e, 0xfe, 0x11, 0x11, 0x11, 0xfe, 0xff, 0x89, 0x89, 0x89, + 0x76, 0x7e, 0x81, 0x81, 0x81, 0x81, 0xff, 0x81, 0x81, 0x81, 0x7e, 0xff, + 0x89, 0x89, 0x89, 0x89, 0xff, 0x09, 0x09, 0x09, 0x09, 0x7e, 0x81, 0x91, + 0x51, 0xf1, 0xff, 0x08, 0x08, 0x08, 0xff, 0x00, 0x81, 0xff, 0x81, 0x00, + 0x40, 0x80, 0x80, 0x80, 0x7f, 0xff, 0x08, 0x14, 0x22, 0xc1, 0xff, 0x80, + 0x80, 0x80, 0x80, 0xff, 0x02, 0x04, 0x02, 0xff, 0xff, 0x06, 0x18, 0x60, + 0xff, 0x7e, 0x81, 0x81, 0x81, 0x7e, 0xff, 0x11, 0x11, 0x11, 0x0e, 0x7e, + 0x81, 0xa1, 0xc1, 0xfe, 0xff, 0x11, 0x11, 0x11, 0xee, 0x86, 0x89, 0x89, + 0x89, 0x71, 0x01, 0x01, 0xff, 0x01, 0x01, 0x7f, 0x80, 0x80, 0x80, 0x7f, + 0x1f, 0x60, 0x80, 0x60, 0x1f, 0xff, 0x40, 0x20, 0x40, 0xff, 0xc7, 0x28, + 0x10, 0x28, 0xc7, 0x07, 0x08, 0xf0, 0x08, 0x07, 0xc1, 0xa1, 0x99, 0x85, + 0x83, 0x00, 0x00, 0x00, 0x3c, 0x03, 0xa4, 0x80, 0x8c, 0x62, 0x00, 0x18, + 0x30, 0x42, 0x00, 0x03, 0x14, 0x40, 0xff, 0xfd, 0x3c, 0x02, 0xbf, 0xc0, + 0x24, 0x03, 0x00, 0x08, 0xac, 0x43, 0x07, 0xfc, 0x3c, 0x02, 0xa4, 0x00, + 0x8c, 0x43, 0x00, 0x00, 0x40, 0x80, 0xe0, 0x00, 0x40, 0x80, 0xe8, 0x00, + 0x3c, 0x02, 0x80, 0x00, 0x24, 0x45, 0x20, 0x00, 0xbc, 0x49, 0x00, 0x00, + 0xbc, 0x49, 0x00, 0x10, 0xbc, 0x49, 0x00, 0x20, 0xbc, 0x49, 0x00, 0x30, + 0x24, 0x42, 0x00, 0x40, 0x14, 0x45, 0xff, 0xfa, 0x00, 0x00, 0x00, 0x00, + 0x3c, 0x02, 0x80, 0x00, 0x24, 0x45, 0x40, 0x00, 0xbc, 0x48, 0x00, 0x00, + 0xbc, 0x48, 0x00, 0x20, 0xbc, 0x48, 0x00, 0x40, 0xbc, 0x48, 0x00, 0x60, + 0x24, 0x42, 0x00, 0x80, 0x14, 0x45, 0xff, 0xfa, 0x00, 0x00, 0x00, 0x00, + 0x3c, 0x02, 0xa4, 0x04, 0x8c, 0x45, 0x00, 0x14, 0x14, 0xa0, 0xff, 0xfe, + 0x3c, 0x05, 0xa4, 0x00, 0x24, 0xa5, 0x10, 0x00, 0xac, 0x45, 0x00, 0x00, + 0x24, 0x65, 0x80, 0x00, 0xac, 0x45, 0x00, 0x04, 0x3c, 0x05, 0x00, 0x01, + 0x34, 0xa5, 0xf3, 0xff, 0xac, 0x45, 0x00, 0x0c, 0x3c, 0x02, 0xa4, 0x04, + 0x8c, 0x45, 0x00, 0x14, 0x14, 0xa0, 0xff, 0xfe, 0x3c, 0x05, 0x00, 0x80, + 0x24, 0xa5, 0x20, 0x00, 0xac, 0x45, 0x00, 0x04, 0x3c, 0x05, 0xa4, 0x00, + 0x24, 0xa5, 0x00, 0x10, 0xac, 0x45, 0x00, 0x00, 0x24, 0x05, 0x0f, 0xef, + 0xac, 0x45, 0x00, 0x08, 0x3c, 0x05, 0xa4, 0x80, 0x8c, 0xa2, 0x00, 0x18, + 0x30, 0x42, 0x00, 0x03, 0x14, 0x40, 0xff, 0xfd, 0x00, 0x00, 0x00, 0x00, + 0xac, 0xa0, 0x00, 0x18, 0x3c, 0x05, 0xa4, 0x04, 0x8c, 0xa2, 0x00, 0x18, + 0x14, 0x40, 0xff, 0xfe, 0x3c, 0x02, 0x7f, 0xff, 0x34, 0x42, 0xff, 0xf0, + 0x00, 0x62, 0x18, 0x21, 0x00, 0x80, 0x00, 0x08, 0x00, 0x60, 0xe8, 0x25 +}; +unsigned int default_ipl3_len = 6444; diff --git a/code/third_party/n64tool/n64tool.c b/code/third_party/n64tool/n64tool.c new file mode 100644 index 0000000..8b5f5ab --- /dev/null +++ b/code/third_party/n64tool/n64tool.c @@ -0,0 +1,842 @@ +/* + n64tool V1.0, a program used to append an n64 header to an arbitrary + sized Nintendo64 binary. + Copyright (C) 2009 DragonMinded (dragonminded@dragonminded.com) + + This tool is part of the Libdragon SDK. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA +*/ + +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#include +#include +#include "polyfill.h" +#include "crc32.c" + +typedef intptr_t ssize_t; + +// Default header to use if none is specified +#include "ipl3.h" + +#define ROUND_UP(n, d) ({ \ +typeof(n) _n = n; typeof(d) _d = d; \ +(((_n) + (_d) - 1) / (_d) * (_d)); \ +}) + +// strlcpy() is not available on all platforms, so we provide a simple implementation +#ifndef strlcpy +size_t __strlcpy(char * restrict dst, const char * restrict src, size_t dstsize) +{ + strncpy(dst, src, dstsize - 1); + dst[dstsize - 1] = '\0'; + return strlen(dst); +} +#define strlcpy __strlcpy +#endif + +// Minimum ROM size alignment, used by default. We currently know of two constraints: +// * 64drive firmware has a bug and can only transfer chunks of 512 bytes. Some +// tools like UNFloader and g64drive work around this bug by padding ROMs, +// but others (like the official one) don't. So it's better in general to +// pad to 512 bytes. +// * EverDrive64 also requires ROMs to be transferred in blocks of 512 bytes, +// which means that the ROM has to be padded. +// * iQue player only allows loading ROMs which are multiple of 16 KiB in size. +// +// To allow the maximum compatibility, we pad to 16 KiB by default. Users can still +// force a specific length with --size, if they need to. +// We also allow changing the padding alignment with --padding. +int pad_align = 16384; + +#define WRITE_SIZE (1024 * 1024) + +#define TITLE_OFFSET 0x20 +#define TITLE_SIZE 20 + +#define CATEGORY_OFFSET 0x3B +#define REGION_OFFSET 0x3E + +#define IQUE_ENTRYPOINT_OFFSET 0x8 + +#define STATUS_OK 0 +#define STATUS_ERROR 1 +#define STATUS_BADUSAGE 2 + +#define TOC_SIZE 1024 +#define TOC_ALIGN 16 +#define TOC_ENTRY_SIZE 64 +#define TOC_MAX_ENTRIES ((TOC_SIZE - 16) / 64) + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define SWAPLONG(i) (i) +#define SWAPSHORT(i) (i) +#else +#define SWAPLONG(i) (((uint32_t)((i) & 0xFF000000) >> 24) | ((uint32_t)((i) & 0x00FF0000) >> 8) | ((uint32_t)((i) & 0x0000FF00) << 8) | ((uint32_t)((i) & 0x000000FF) << 24)) +#define SWAPSHORT(i) (((uint16_t)((i) & 0xFF00) >> 8) | ((uint16_t)((i) & 0x00FF) << 8)) +#endif + +static const unsigned char zero[1024] = {0}; +static char * tmp_output = NULL; +static uint32_t elf_loadpoint = 0xFFFFFFFF; + +// The TOC cookie could be just a 32-bit random number; its goal is just to +// differentiate different ROMs, to detect if a ROM was "swapped" by a flashcart +// during runtime. +// However we don't want to use a random number as we want ROMs to be reproducible, +// so we will instead hash the various file contents to generate a fixed-but-random +// cookie. +static uint32_t toc_cookie = 0xFFFFFFFF; + +struct toc_s { + char magic_version[4]; + uint32_t cookie; + uint32_t toc_size; + uint16_t entry_size; + uint16_t num_entries; + struct { + uint32_t offset; + uint32_t size; + char name[TOC_ENTRY_SIZE - 8]; + } files[TOC_MAX_ENTRIES]; +} toc = { + .magic_version = "TOC0", + .toc_size = TOC_SIZE, + .entry_size = TOC_ENTRY_SIZE, + .num_entries = 0, +}; + +_Static_assert(sizeof(toc) <= TOC_SIZE, "invalid table size"); + +int print_usage(const char * prog_name) +{ + fprintf(stderr, "Usage: %s [flags] [file-flags] [[file-flags] ...]\n\n", prog_name); + fprintf(stderr, "This program creates an N64 ROM from a header and a list of files,\n"); + fprintf(stderr, "the first being an Nintendo 64 binary and the rest arbitrary data.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "General flags (to be used before any file):\n"); + fprintf(stderr, "\t-t, --title Title of ROM (max %d characters).\n", TITLE_SIZE); + fprintf(stderr, "\t-l, --size <size> Force ROM output file size to <size> (min 1 mebibyte).\n"); + fprintf(stderr, "\t-P, --padding <size> Padding alignment for the final ROM (default: 16 KiB).\n"); + fprintf(stderr, "\t-h, --header <file> Use <file> as IPL3 header (default: use libdragon IPL3).\n"); + fprintf(stderr, "\t-o, --output <file> Save output ROM to <file>.\n"); + fprintf(stderr, "\t-C, --category <cat> N64 Media Category Code (default: 'N' - N64 Game Pak).\n"); + fprintf(stderr, "\t-R, --region <reg> Specify ROM region (default: 'E' - North America).\n"); + fprintf(stderr, "\t-T, --toc Create a table of contents in the ROM.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "File flags (to be used before each file):\n"); + fprintf(stderr, "\t-a, --align <align> Next file is aligned at <align> bytes from top of memory (default: 16).\n"); + fprintf(stderr, "\t-s, --offset <offset> Next file starts at <offset> from top of memory. Offset must be 4-byte aligned.\n"); + fprintf(stderr, "\n"); + fprintf(stderr, "Binary byte size/offset suffix notation:\n"); + fprintf(stderr, "\tB for bytes.\n"); + fprintf(stderr, "\tK for kibibytes (KiB) [1024 bytes].\n"); + fprintf(stderr, "\tM for mebibytes (MiB) [1024 kibibytes].\n"); + return STATUS_BADUSAGE; +} + +bool check_flag(const char * arg, const char * shortFlag, const char * longFlag) +{ + return !strcmp(arg, shortFlag) || !strcmp(arg, longFlag); +} + +size_t get_file_size(FILE * fp) +{ + size_t restore = ftell(fp); + size_t end = 0; + + fseek(fp, 0, SEEK_END); + end = ftell(fp); + fseek(fp, restore, SEEK_SET); + + return end; +} + +ssize_t copy_file(FILE * dest, const char * file) +{ + FILE *read_file = fopen(file, "rb"); + uint8_t *buffer; + + if(!read_file) + { + fprintf(stderr, "ERROR: Cannot open %s for reading!\n", file); + return -1; + } + + ssize_t fsize = get_file_size(read_file); + ssize_t rsize = fsize; + + /* Should probably make this read in increments, but whatever */ + buffer = malloc(WRITE_SIZE); + + if(!buffer) + { + fprintf(stderr, "ERROR: Out of memory!\n"); + + fclose(read_file); + + return -1; + } + + /* Weird Windows bug fix, plus this is the right way to do things */ + while(fsize > 0) + { + /* Max 1K chunk */ + ssize_t write_size = (fsize > WRITE_SIZE) ? WRITE_SIZE : fsize; + fsize -= write_size; + + fread(buffer, 1, write_size, read_file); + fwrite(buffer, 1, write_size, dest); + + /* Update the TOC cookie with file contents. */ + toc_cookie = crc32_update(toc_cookie, buffer, write_size); + } + + free(buffer); + fclose(read_file); + + return rsize; +} + +ssize_t output_zeros(FILE * dest, ssize_t amount) +{ + if(amount < 0) + { + /* Can't backward seek */ + return -1; + } + + while (amount > 0) { + int sz = amount; + if (sz > sizeof(zero)) + sz = sizeof(zero); + fwrite(zero, 1, sz, dest); + toc_cookie = crc32_update(toc_cookie, zero, sz); + amount -= sz; + } + + return 0; +} + +ssize_t parse_bytes(const char * arg) +{ + size_t arg_len = strlen(arg); + + if(arg_len < 2) + { + /* Need at least 2 characters to parse */ + return -1; + } + + char * suffix; + size_t size = strtol(arg, &suffix, 10); + + /* Multiply by the suffix magnitude */ + switch(suffix[0]) + { + case 'm': + case 'M': + size *= 1024; + case 'k': + case 'K': + size *= 1024; + case 'b': + case 'B': + default: + return size; + } +} + +void remove_tmp_file(void) +{ + if(tmp_output) + remove(tmp_output); +} + +static uint32_t fread32be_at(FILE *fp, int offset) +{ + uint8_t buf[4]; + fseek(fp, offset, SEEK_SET); + fread(buf, 1, 4, fp); + return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; +} + +static uint16_t fread16be_at(FILE *fp, int offset) +{ + uint8_t buf[2]; + fseek(fp, offset, SEEK_SET); + fread(buf, 1, 2, fp); + return (buf[0] << 8) | buf[1]; +} + +int parse_elf_loadpoint(const char *elf_fn, uint32_t *loadpoint) +{ + FILE *elf = fopen(elf_fn, "rb"); + + if(!elf) + { + fprintf(stderr, "ERROR: Cannot open %s for reading!\n", elf_fn); + return -1; + } + + char id[4]; + fread(id, 1, 4, elf); + if (memcmp(id, "\x7F" "ELF", 4) != 0) { + fprintf(stderr, "WARNING: %s is not an ELF file, boot may fail\n", elf_fn); + fclose(elf); + return -1; + } + + bool elf64 = fgetc(elf) == 2; + if (fgetc(elf) == 1) { + fprintf(stderr, "WARNING: %s is a little-endian ELF file, boot may fail\n", elf_fn); + fclose(elf); + return -1; + } + + int phoff = fread32be_at(elf, (elf64 ? 0x20+4 : 0x1C)); + int phnum = fread16be_at(elf, (elf64 ? 0x38 : 0x2C)); + + enum { PF_N64_COMPRESSED = 0x1000 }; + enum { PT_LOAD = 0x1 }; + + bool found_loadpoint = false; + for (int i=0; i<phnum; i++) { + uint32_t type = fread32be_at(elf, phoff + 0); + uint32_t vaddr = fread32be_at(elf, phoff + (elf64 ? 5 : 2)*4); + uint32_t paddr = fread32be_at(elf, phoff + (elf64 ? 7 : 3)*4); + uint32_t flags = fread32be_at(elf, phoff + (elf64 ? 1 : 6)*4); + + if (flags & PF_N64_COMPRESSED) + vaddr = paddr; + + if (type == PT_LOAD && !(vaddr >= 0x80000000 && vaddr < 0x80000400)) { + *loadpoint = vaddr; + found_loadpoint = true; + break; + } + + phoff += elf64 ? 0x38 : 0x20; + } + + if (!found_loadpoint) { + fprintf(stderr, "WARNING: No suitable loading point found in %s, boot may fail on iQue\n", elf_fn); + fclose(elf); + return -1; + } + + fclose(elf); + return 0; +} + +int main(int argc, char *argv[]) +{ + winconsole_utf8(); + FILE * write_file = NULL; + const char * header = NULL; + const char * output = NULL; + size_t declared_size = 0; + size_t total_bytes_written = 0; + char title[TITLE_SIZE + 1] = { 0, }; + bool create_toc = false; + size_t toc_offset = 0; + int header_size = 0; + int align_next = 16; + + char category = 'N'; + // Some flashcarts (at least Everdrive X7) seem to automatically set the TV type based on the region field. + // As a result, some users might not be able to play the ROM because their TV or capture device doesn't + // support either PAL or NTSC. If the field is 0, the flashcart seems to not overwrite the console's region, + // so we use it as default. + char region = 0; + + + if(argc <= 1) + { + /* No way we can have just one argument or less */ + return print_usage(argv[0]); + } + + int i = 1; + const char * arg; + + while(i < argc) + { + arg = argv[i++]; + if(check_flag(arg, "-b", "--byteswap")) + { + /* Invalid usage */ + fprintf(stderr, "ERROR: The byteswap option is no longer supported. "); + fprintf(stderr, "Use another tool to convert the output of this program.\n"); + fprintf(stderr, " For example: dd conv=swab if=rom.z64 of=rom.v64\n\n"); + return print_usage(argv[0]); + } + if(check_flag(arg, "-h", "--header")) + { + if(header) + { + /* Invalid usage */ + fprintf(stderr, "ERROR: The header can only be set once\n\n"); + return print_usage(argv[0]); + } + + if(i >= argc) + { + /* Invalid usage */ + fprintf(stderr, "ERROR: Expected an argument to header flag\n\n"); + return print_usage(argv[0]); + } + + header = argv[i++]; + continue; + } + if(check_flag(arg, "-o", "--output")) + { + if(output) + { + /* Invalid usage */ + fprintf(stderr, "ERROR: The output can only be set once\n\n"); + return print_usage(argv[0]); + } + + if(i >= argc) + { + /* Invalid usage */ + fprintf(stderr, "ERROR: Expected an argument to output flag\n\n"); + return print_usage(argv[0]); + } + + output = argv[i++]; + unsigned int needed_size = snprintf(0, 0, "%s.tmp", output) + 1; + tmp_output = calloc(needed_size, 1); + snprintf(tmp_output, needed_size, "%s.tmp", output); + continue; + } + if(check_flag(arg, "-l", "--size")) + { + if(i >= argc) + { + /* Expected another argument */ + fprintf(stderr, "ERROR: Expected an argument to size flag\n\n"); + return print_usage(argv[0]); + } + + ssize_t size = parse_bytes(argv[i++]); + + if (size % 4 != 0) + { + /* Invalid size */ + fprintf(stderr, "ERROR: Invalid size argument; must be a multiple of 4 bytes\n\n"); + return print_usage(argv[0]); + } + if (size % 512 != 0) + { + fprintf(stderr, "WARNING: Sizes which are not multiple of 512 bytes might have problems being loaded with a 64drive\n\n"); + } + + declared_size = size; + continue; + } + if(check_flag(arg, "-P", "--padding")) + { + if(i >= argc) + { + /* Expected another argument */ + fprintf(stderr, "ERROR: Expected an argument to padding flag\n\n"); + return print_usage(argv[0]); + } + + ssize_t size = parse_bytes(argv[i++]); + + if (size < 0) + { + /* Invalid size */ + fprintf(stderr, "ERROR: Invalid padding argument; must be positive\n\n"); + return print_usage(argv[0]); + } + + pad_align = size; + continue; + } + if(check_flag(arg, "-T", "--toc")) + { + if(total_bytes_written) + { + fprintf(stderr, "ERROR: -T / --toc must be specified before any input file\n\n"); + return print_usage(argv[0]); + } + create_toc = true; + continue; + } + if(check_flag(arg, "-s", "--offset")) + { + if(!output) + { + fprintf(stderr, "ERROR: Need output flag before offset\n\n"); + return print_usage(argv[0]); + } + + if(!total_bytes_written) + { + fprintf(stderr, "ERROR: The first file cannot have an offset\n\n"); + return print_usage(argv[0]); + } + + if(i >= argc) + { + /* Expected another argument */ + fprintf(stderr, "ERROR: Expected an argument to offset flag\n\n"); + return print_usage(argv[0]); + } + + ssize_t offset = parse_bytes(argv[i++]); + + if(offset <= 0) + { + /* Invalid offset */ + fprintf(stderr, "ERROR: Invalid offset argument\n\n"); + return print_usage(argv[0]); + } + if((offset % 4) != 0) + { + fprintf(stderr, "ERROR: Invalid offset argument (must be multiple of 4)\n\n"); + return print_usage(argv[0]); + } + + /* Write out needed number of zeros */ + ssize_t num_zeros = offset - total_bytes_written; + + if(output_zeros(write_file, num_zeros)) + { + fprintf(stderr, "ERROR: Invalid offset %zd to seek to in %s!\n", offset, output); + return STATUS_ERROR; + } + + /* Same as total_bytes_written = offset */ + total_bytes_written += num_zeros; + continue; + } + if(check_flag(arg, "-a", "--align")) + { + if(!output) + { + fprintf(stderr, "ERROR: Need output flag before alignment\n\n"); + return print_usage(argv[0]); + } + + if(i >= argc) + { + /* Expected another argument */ + fprintf(stderr, "ERROR: Expected an argument to align flag\n\n"); + return print_usage(argv[0]); + } + + int align = atoi(argv[i++]); + if (align < 4) + { + fprintf(stderr, "ERROR: Minimum alignment is 4 bytes\n\n"); + return print_usage(argv[0]); + } + + align_next = align; + continue; + } + if(check_flag(arg, "-t", "--title")) + { + if(i >= argc) + { + /* Expected another argument */ + fprintf(stderr, "ERROR: Expected an argument to title flag\n\n"); + return print_usage(argv[0]); + } + + const char * title_arg = argv[i++]; + size_t title_len = strlen(title_arg); + + if(title_len > TITLE_SIZE) + { + fprintf(stderr, "WARNING: Title will be truncated to %d characters\n", TITLE_SIZE); + title_len = TITLE_SIZE; + } + + memcpy(title, title_arg, title_len); + continue; + } + if(check_flag(arg, "-C", "--category")) + { + if(i >= argc) + { + /* Expected another argument */ + fprintf(stderr, "ERROR: Expected an argument to category flag\n\n"); + return print_usage(argv[0]); + } + + const char * category_arg = argv[i++]; + if(strlen(category_arg) != 1) + { + fprintf(stderr, "ERROR: Category must be a single character\n\n"); + return print_usage(argv[0]); + } + + category = category_arg[0]; + continue; + } + if(check_flag(arg, "-R", "--region")) + { + if(i >= argc) + { + /* Expected another argument */ + fprintf(stderr, "ERROR: Expected an argument to region flag\n\n"); + return print_usage(argv[0]); + } + + const char * region_arg = argv[i++]; + if (strlen(region_arg) != 1) + { + fprintf(stderr, "ERROR: Region must be a single character\n\n"); + return print_usage(argv[0]); + } + + region = region_arg[0]; + continue; + } + + /* Argument is not a flag; treat it as an input file */ + + /* Can't copy input file unless header and output set */ + if(!output) + { + fprintf(stderr, "ERROR: Need output flag before first file\n\n"); + return print_usage(argv[0]); + } + + /* If this is the first input file, open the output file and write the header */ + if(!write_file) + { + /* Create or completely overwrite the output file */ + write_file = fopen(tmp_output, "wb"); + + if(!write_file) + { + fprintf(stderr, "ERROR: Cannot open '%s' for writing\n", output); + return STATUS_ERROR; + } + + /* Try to clean up the temporary file if we exit */ + atexit(remove_tmp_file); + + /* Copy over the ROM header */ + if (header) + { + header_size = copy_file(write_file, header); + } + else + { + header_size = sizeof(default_ipl3); + fwrite(default_ipl3, 1, header_size, write_file); + if (parse_elf_loadpoint(arg, &elf_loadpoint) < 0) + return STATUS_ERROR; + } + + if(header_size < 0x100) + { + fprintf(stderr, "ERROR: Header file '%s' is too small (minimum is 4096 bytes)\n", header); + return STATUS_ERROR; + } + + /* HACK. Unfortunately, n64tool handles both --align and --offset with respect + to file positions/sizes *excluding* the header. The header used to be of + fixed size (4096 bytes), but that's now just a minimum. For full backward + compatibility, we still consider the header to always be 4096 bytes, and + just offset from there. */ + total_bytes_written += header_size - 4096; + header_size = 4096; + + /* Leave space for the table, if asked to do so. */ + if(create_toc && !toc_offset) + { + if (total_bytes_written % TOC_ALIGN) + { + ssize_t num_zeros = TOC_ALIGN - (total_bytes_written % TOC_ALIGN); + output_zeros(write_file, num_zeros); + total_bytes_written += num_zeros; + } + + toc_offset = ftell(write_file); + output_zeros(write_file, TOC_SIZE); + total_bytes_written += TOC_SIZE; + } + } + + if (align_next) + { + if (total_bytes_written % align_next) + { + ssize_t num_zeros = align_next - (total_bytes_written % align_next); + + if(output_zeros(write_file, num_zeros)) + { + fprintf(stderr, "ERROR: Invalid alignment %d to seek to in %s!\n", align_next, output); + return STATUS_ERROR; + } + + total_bytes_written += num_zeros; + } + } + + /* Reset to 16 for next file (default) */ + align_next = 16; + + size_t offset = ftell(write_file); + + /* Copy the input file into the output file */ + ssize_t bytes_copied = copy_file(write_file, arg); + + if(bytes_copied < 0) + { + fprintf(stderr, "ERROR: Unable to copy file from '%s' to '%s'\n", arg, output); + return STATUS_ERROR; + } + + if (toc.num_entries < TOC_MAX_ENTRIES) + { + /* Add the file to the toc */ + toc.files[toc.num_entries].offset = offset; + toc.files[toc.num_entries].size = bytes_copied; + + const char *basename = strrchr(arg, '/'); + if (!basename) basename = strrchr(arg, '\\'); + if (!basename) basename = arg; + if (basename[0] == '/' || basename[0] == '\\') basename++; + strlcpy(toc.files[toc.num_entries].name, basename, sizeof(toc.files[toc.num_entries].name)); + toc.num_entries++; + } + else + { + if (create_toc) + { + fprintf(stderr, "ERROR: Too many files to add to table.\n"); + return STATUS_ERROR; + } + } + + + /* Keep track to be sure we align properly when they request a memory alignment */ + total_bytes_written += bytes_copied; + } + + if(!total_bytes_written) + { + /* Didn't write anything! */ + printf("ERROR: No input files, nothing written\n\n"); + return print_usage(argv[0]); + } + + /* If the declared size is too small, error out */ + if(declared_size && declared_size < total_bytes_written) + { + fprintf(stderr, "ERROR: Couldn't fit ROM in %zu bytes as requested.\n", declared_size); + return print_usage(argv[0]); + } + + /* Pad the output file to the declared size (not including the IPL3 header) */ + if(!declared_size) { + /* If the user didn't specify a size, initialize this to the minimum + size that is padded to the correct alignment. Notice that this variable + declares the size WITHOUT header, but the padding refers to the final + ROM and so it must be calculated with the header. */ + if (pad_align) + declared_size = ROUND_UP(header_size, pad_align)-header_size; + } + if(declared_size > total_bytes_written) + { + ssize_t num_zeros = declared_size - total_bytes_written; + + if(output_zeros(write_file, num_zeros)) + { + fprintf(stderr, "ERROR: Couldn't pad %zu bytes to %zu bytes.\n", total_bytes_written, declared_size); + return print_usage(argv[0]); + } + } + else if(pad_align && ((total_bytes_written+header_size) % pad_align) != 0) + { + /* Pad size as required. */ + ssize_t num_zeros = pad_align - ((total_bytes_written+header_size) % pad_align); + if(output_zeros(write_file, num_zeros)) + { + fprintf(stderr, "ERROR: Couldn't pad %zu bytes to %zu bytes.\n", total_bytes_written, total_bytes_written+num_zeros); + return print_usage(argv[0]); + } + } + + /* Set title in header */ + fseek(write_file, TITLE_OFFSET, SEEK_SET); + fwrite(title, 1, TITLE_SIZE, write_file); + + /* Set category in header */ + fseek(write_file, CATEGORY_OFFSET, SEEK_SET); + fwrite(&category, 1, 1, write_file); + + /* Set region in header */ + fseek(write_file, REGION_OFFSET, SEEK_SET); + fwrite(®ion, 1, 1, write_file); + + /* If we are using libdragon's IPL3, set the entrypoint in the header + for iQue to match the first valid loadpoint found in the ELF. This make + sure that the iQue OS, in its initial flat-binary loading, will use + the same memory region as the ELF. */ + if (elf_loadpoint != 0xFFFFFFFF) + { + elf_loadpoint = SWAPLONG(elf_loadpoint); + fseek(write_file, IQUE_ENTRYPOINT_OFFSET, SEEK_SET); + fwrite(&elf_loadpoint, 1, 4, write_file); + } + + /* Write table of contents */ + if(create_toc) + { + for (int i=0; i<toc.num_entries; i++) { + toc.files[i].offset = SWAPLONG(toc.files[i].offset); + toc.files[i].size = SWAPLONG(toc.files[i].size); + } + toc.toc_size = SWAPLONG(toc.toc_size); + toc.num_entries = SWAPSHORT(toc.num_entries); + toc.entry_size = SWAPSHORT(toc.entry_size); + + toc_cookie = ~crc32_update(toc_cookie, (uint8_t *)&toc, sizeof(toc)); + toc.cookie = SWAPLONG(toc_cookie); + + fseek(write_file, toc_offset, SEEK_SET); + fwrite(&toc, 1, sizeof(toc), write_file); + } + + /* Sync and close the output file */ + fclose(write_file); + + /* Rename to the final name */ +#ifdef _WIN32 + /* Windows doesn't support atomic renames, so we have to delete the old file first */ + remove(output); +#endif + if(rename(tmp_output, output) != 0) { + fprintf(stderr, "Couldn't rename temporary output file '%s' to '%s': %s", tmp_output, output, strerror(errno)); + return STATUS_ERROR; + } + + return STATUS_OK; +} diff --git a/code/third_party/n64tool/polyfill.h b/code/third_party/n64tool/polyfill.h new file mode 100644 index 0000000..9ef5f21 --- /dev/null +++ b/code/third_party/n64tool/polyfill.h @@ -0,0 +1,412 @@ +/* + polyfill: mingw32 polyfills for missing C/POSIX functions + Written by Giovanni Bajo <giovannibajo@gmail.com> + + This tool is part of the Libdragon SDK. + + This is free and unencumbered software released into the public domain. + + For more information, please refer to <http://unlicense.org/> +*/ +#ifndef LIBDRAGON_TOOLS_POLYFILL_H +#define LIBDRAGON_TOOLS_POLYFILL_H + +#ifdef __MINGW32__ + +// NOTE: we include both stdio.h and cstdio because both of them will undef +// tmpfile and redefine it to something else. Since we want to provide our +// own implementation, we need to make sure both are included first, so that +// we don't run the risk either of them is included after this file. +#include <stdio.h> +#ifdef __cplusplus +#include <cstdio> +#endif +// NOTE: include both assert.h and cassert because we need to make sure +// _assert and _wassert are declared properly before we mess with them later. +#include <assert.h> +#ifdef __cplusplus +#include <cassert> +#endif +#include <stdlib.h> +#include <errno.h> +#include <stdint.h> +#include <stdbool.h> +#include <string.h> +#include <process.h> +#include <share.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + + // if typedef doesn't exist (msvc, blah) + typedef intptr_t ssize_t; + + /* Implementation of 'getline' fetched from: https://stackoverflow.com/a/47229318 */ + /* The original code is public domain -- Will Hartung 4/9/09 */ + /* Modifications, public domain as well, by Antti Haapala, 11/10/17 + - Switched to getc on 5/23/19 */ + __attribute__((used)) + static ssize_t getline(char **lineptr, size_t *n, FILE *stream) { + size_t pos; + int c; + + if (lineptr == NULL || stream == NULL || n == NULL) { + errno = EINVAL; + return -1; + } + + c = getc(stream); + if (c == EOF) { + return -1; + } + + if (*lineptr == NULL) { + *lineptr = (char*)malloc(128); + if (*lineptr == NULL) { + return -1; + } + *n = 128; + } + + pos = 0; + while(c != EOF) { + if (pos + 1 >= *n) { + size_t new_size = *n + (*n >> 2); + if (new_size < 128) { + new_size = 128; + } + char *new_ptr = (char*)realloc(*lineptr, new_size); + if (new_ptr == NULL) { + return -1; + } + *n = new_size; + *lineptr = new_ptr; + } + + ((unsigned char *)(*lineptr))[pos ++] = c; + if (c == '\n') { + break; + } + c = getc(stream); + } + + (*lineptr)[pos] = '\0'; + return pos; + } + + // Provide implementation of strndup for MSYS2 environments that are missing it (before April 13 2026). +#if defined(MSYS2_RUNTIME_PACMAN_AGE) && MSYS2_RUNTIME_PACMAN_AGE <= 20260413 + __attribute__((used)) + static char *msys2_fallback_strndup(const char *s, size_t n) + { + size_t len = strnlen(s, n); + char *ret = (char*)malloc(len + 1); + if (!ret) return NULL; + memcpy(ret, s, len); + ret[len] = '\0'; + return ret; + } + +#ifdef strndup +#undef strndup +#endif +#define strndup msys2_fallback_strndup +#endif + + // Replace 'assert' so that it has 'noreturn' behaviour on Windows. + // (soft reverts this MSYS2 commit: https://sourceforge.net/p/mingw-w64/mingw-w64/ci/ecf2328a328d11dec7044b40b2b5e93b5b2b9d9e/) +#if defined(MSYS2_RUNTIME_PACMAN_AGE) && MSYS2_RUNTIME_PACMAN_AGE >= 20260611 && !defined(NDEBUG) + +#if defined(_UNICODE) || defined(UNICODE) + + __attribute__((used)) __attribute__((noreturn)) + static void msys2_wassert_fix(const wchar_t* _Message, const wchar_t* _File, unsigned _Line) + { + _wassert(_Message, _File, _Line); + abort(); + __builtin_unreachable(); + } + +#define _wassert(a,b,c) msys2_wassert_fix(a,b,c) + +#else + + __attribute__((used)) __attribute__((noreturn)) + static void msys2_assert_fix(const char* _Message, const char* _File, unsigned _Line) + { + _assert(_Message, _File, _Line); + abort(); + __builtin_unreachable(); + } + +#define _assert(a,b,c) msys2_assert_fix(a,b,c) + +#endif // defined(_UNICODE) || defined(UNICODE) + +#endif + +#if defined(__MSVCRT_VERSION__) && __MSVCRT_VERSION__ >= 0xE00 + // UCRT is being used so we can use runtime-provided tmpfile() implementation +#else + // tmpfile() in MINGW64 is broken for use (it uses MSVCRT that tries to + // create a file in C:\, which is non-writable nowadays) +#ifdef tmpfile +#undef tmpfile +#endif +#define tmpfile() mingw_tmpfile() +#endif + + typedef void* HANDLE; + typedef const char* LPCSTR; + typedef int BOOL; +#define INVALID_HANDLE_VALUE ((HANDLE)(long)-1) + struct _SECURITY_ATTRIBUTES; + + // Access rights +#define GENERIC_READ 0x80000000 +#define GENERIC_WRITE 0x40000000 + + // Share modes +#define FILE_SHARE_READ 0x00000001 +#define FILE_SHARE_WRITE 0x00000002 +#define FILE_SHARE_DELETE 0x00000004 + + // Creation disposition +#define CREATE_NEW 1 + + // Flags and attributes +#define FILE_ATTRIBUTE_TEMPORARY 0x00000100 +#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000 + + __declspec(dllimport) HANDLE __stdcall CreateFileA( + LPCSTR lpFileName, + unsigned long dwDesiredAccess, + unsigned long dwShareMode, + struct _SECURITY_ATTRIBUTES* lpSecurityAttributes, + unsigned long dwCreationDisposition, + unsigned long dwFlagsAndAttributes, + HANDLE hTemplateFile + ); + __declspec(dllimport) int __stdcall CloseHandle(HANDLE); + __declspec(dllimport) unsigned long __stdcall GetLastError(void); + __declspec(dllimport) unsigned long __stdcall GetTickCount(void); + + __attribute__((used)) + static FILE *mingw_tmpfile(void) { + static int counter = 0; + char path[260]; + + for (int i = 0; i < 4096; i++) { + // Generate a random filename. Notice we *purposedly* not make this + // very random with PID, timestamp, etc. because this is the third + // iteration of mingw_tmpfile(): the previous ones were misbehaving + // in various ways on various CRTs, Windows versions, etc. + // We want this code to be exercised often so that it is robust. + snprintf(path, sizeof(path), "mksprite-%04x.tmp", counter++); + + HANDLE h = CreateFileA( + path, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, + NULL + ); + + if (h != INVALID_HANDLE_VALUE) { + int fd = _open_osfhandle((intptr_t)h, _O_RDWR | _O_BINARY); + if (fd == -1) { + CloseHandle(h); + return NULL; + } + return fdopen(fd, "w+b"); + } + + // 80 = ERROR_FILE_EXISTS + if (GetLastError() != 80) + break; + } + + return NULL; + } + + __attribute__((used)) + static char* strcasestr(const char* haystack, const char* needle) + { + size_t needle_len = strlen(needle); + size_t haystack_len = strlen(haystack); + size_t i; + + if (needle_len > haystack_len) + return NULL; + + for (i = 0; i <= haystack_len - needle_len; i++) + { + if (strncasecmp(haystack + i, needle, needle_len) == 0) + return (char*)(haystack + i); + } + + return NULL; + } + + // Implementation from FreeBSD + __attribute__((used)) + static void *memmem(const void *l, size_t l_len, const void *s, size_t s_len) + { + char *cur, *last; + const char *cl = (const char *)l; + const char *cs = (const char *)s; + + /* we need something to compare */ + if (l_len == 0 || s_len == 0) + return NULL; + + /* "s" must be smaller or equal to "l" */ + if (l_len < s_len) + return NULL; + + /* special case where s_len == 1 */ + if (s_len == 1) + return memchr(l, (int)*cs, l_len); + + /* the last position where its possible to find "s" in "l" */ + last = (char *)cl + l_len - s_len; + + for (cur = (char *)cl; cur <= last; cur++) + if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) + return cur; + + return NULL; + } + + // rename() that ovewrites the destination file +#define rename(a,b) mingw_rename(a,b) + +#define MOVEFILE_REPLACE_EXISTING 0x00000001 +#define MOVEFILE_WRITE_THROUGH 0x00000008 + + __declspec(dllimport) int __stdcall MoveFileExA(const char *lpExistingFileName, + const char *lpNewFileName, + unsigned long dwFlags); + __declspec(dllimport) unsigned long __stdcall GetLastError(void); + + static void map_windows_error_to_errno(unsigned long err) { + switch (err) { + case 2: errno = ENOENT; break; // ERROR_FILE_NOT_FOUND + case 3: errno = ENOENT; break; // ERROR_PATH_NOT_FOUND + case 5: errno = EACCES; break; // ERROR_ACCESS_DENIED + case 32: errno = EBUSY; break; // ERROR_SHARING_VIOLATION + case 80: errno = EEXIST; break; // ERROR_FILE_EXISTS + case 183: errno = EEXIST; break; // ERROR_ALREADY_EXISTS + default: errno = EIO; break; // Generic error + } + } + + __attribute__((used)) + static int mingw_rename(const char *oldpath, const char *newpath) { + if (MoveFileExA(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) { + return 0; + } else { + map_windows_error_to_errno(GetLastError()); + return -1; + } + } + + // POISX mkdir has a mode argument, but mingw's mkdir doesn't +#define mkdir(path, mode) mkdir(path) + + typedef struct _PROCESS_BASIC_INFORMATION_MIN { + void* Reserved1; + void* PebBaseAddress; + void* Reserved2[2]; + void* UniqueProcessId; + void* InheritedFromUniqueProcessId; + } PROCESS_BASIC_INFORMATION_MIN; + +#define CURRENT_PROCESS ((HANDLE)(intptr_t)-1) + + __declspec(dllimport) long __stdcall NtQueryInformationProcess( + HANDLE ProcessHandle, unsigned long ProcessInformationClass, + void* ProcessInformation, unsigned long ProcessInformationLength, + unsigned long *ReturnLength); + + // Enable long path support in the current process. + // This is a hacky workaround for a limitation in Windows where + // file paths longer than 260 characters are not supported. + // The documented ways involve also setting a registry key, and thus + // is not suitable for a tool like this. + // This same approach is used by the Go runtime on Windows, so it is + // reasonably safe to use it here as well. + __attribute__((used)) + static void enable_peb_long_path_unsafe(void) + { + PROCESS_BASIC_INFORMATION_MIN pbi; unsigned long retlen; + if (NtQueryInformationProcess(CURRENT_PROCESS, 0, &pbi, sizeof(pbi), &retlen) < 0) + return; + volatile uint8_t *peb = (volatile uint8_t*)pbi.PebBaseAddress; + enum { PEB_BITFIELD_OFFSET = 3, IS_LONG_PATH_AWARE_MASK = 0x80 }; + peb[PEB_BITFIELD_OFFSET] |= (uint8_t)IS_LONG_PATH_AWARE_MASK; + } + + // If it was defined, undefine it as there's no limit on path length after the hack above +#undef MAX_PATH + + __declspec(dllimport) int __stdcall SetConsoleOutputCP(unsigned int); + __declspec(dllimport) int __stdcall SetConsoleCP(unsigned int); + __declspec(dllimport) unsigned int __stdcall GetConsoleOutputCP(void); + __declspec(dllimport) unsigned int __stdcall GetConsoleCP(void); +#define CP_UTF8 65001 + + static int old_out_cp = 0; + static int old_in_cp = 0; + + __attribute__((used)) + static void winconsole_restore(void) { + if (old_out_cp) + SetConsoleOutputCP(old_out_cp); + if (old_in_cp) + SetConsoleCP(old_in_cp); + } + + __attribute__((used)) + static void winconsole_utf8(void) { + // Enable long path support in the current process + enable_peb_long_path_unsafe(); + + // Save the current code page + old_out_cp = GetConsoleOutputCP(); + old_in_cp = GetConsoleCP(); + + // Enable UTF-8 in the console + SetConsoleOutputCP(CP_UTF8); + SetConsoleCP(CP_UTF8); + + atexit(winconsole_restore); + } + +#ifdef __cplusplus +} +#endif + + +#else /* __MINGW32__ */ + +#ifdef __cplusplus +extern "C" { +#endif + + __attribute__((used)) + static void winconsole_utf8(void) {} + +#ifdef __cplusplus +} +#endif + +#endif /* __MINGW32__ */ + +#endif diff --git a/project.4coder b/project.4coder new file mode 100644 index 0000000..d6347e8 --- /dev/null +++ b/project.4coder @@ -0,0 +1,81 @@ +version(2); +project_name = "DIGITAL GROVE 64"; + +//////////////////////////////////////////////////////////////// +//~ rjf: Indentation + +indent_width = "1"; +default_tab_width = "1"; + +//////////////////////////////////////////////////////////////// +//~ rjf: Code File Extensions + +patterns = +{ + "*.c", + "*.cpp", + "*.h", + "*.inc", + "*.hpp", + "*.bat", + "*.sh", + "*.4coder", + "*.glsl", + "*.bfs", + "*.html", + "*.txt", + "*.ds", + "*.mdesk", + "*.asm", +}; + +//////////////////////////////////////////////////////////////// +//~ rjf: Omit Patterns + +blacklist_patterns = +{ + ".*", +}; + +//////////////////////////////////////////////////////////////// +//~ rjf: Load Paths + +paths = +{ + { .path = ".", .recursive = false, .relative = true, }, + { .path = "code", .recursive = true , .relative = true, }, + { .path = "notes", .recursive = true , .relative = true, }, +}; + +load_paths = +{ + .win = paths, + .linux = paths, +}; + +//////////////////////////////////////////////////////////////// +//~ rjf: Commands + +commands = +{ + .build_g0_n64 = { .win = "build n64_test clang deploy release", .linux = "", .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, .cursor_at_end = false, }, +}; + +//////////////////////////////////////////////////////////////// +//~ rjf: Default F-Keys + +fkey_command = +{ + .F1 = "build_g0_n64", +}; + +//////////////////////////////////////////////////////////////// +//~ rjf: Username Overridden F-Keys + +fkey_command_override = +{ + .rjf = + { + .F1 = "build_g0_n64", + }, +};