From e8878994d0d98df89a426fb7309f5e30176f8ac4 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 27 Jan 2021 10:46:45 -0500 Subject: [PATCH 001/477] :sparkles: Added initial files --- .clang-format | 120 ++++++++++++++++++++++++++++++++++++++++++++++ source/rk_types.h | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 .clang-format create mode 100644 source/rk_types.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..129826576 --- /dev/null +++ b/.clang-format @@ -0,0 +1,120 @@ +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +... + diff --git a/source/rk_types.h b/source/rk_types.h new file mode 100644 index 000000000..12d95f309 --- /dev/null +++ b/source/rk_types.h @@ -0,0 +1,94 @@ +#pragma once + +#include + +#define NULL 0 + +// Unknown types + +//! Describes an unknown 32 bit value +typedef int unk32; +//! Describes an unknown 16 bit value +typedef short unk16; +//! Describes an unknown 8 bit value +typedef unsigned char unk8; +//! Unknown value of unknown size +typedef unk32 unk; + +// Necesary for CW +#if __cplusplus < 201103L && !defined(_WIN32) +#define override +#define noexcept +#define nullptr NULL +#endif + +#define ensures(cond) +#define expects(cond) + +#ifdef __CWCC__ +#define MWREG register +#define CONST_MWREG register + +#define WPOPT volatile +#define MW_PRAG_NOINLINE _Pragma("push") _Pragma("dont_inline on") +#define MW_PRAG_OPT_S _Pragma("push") _Pragma("optimize_for_size on") + +#define MW_PRAG_END _Pragma("pop") +#define DECOMP // TODO: Move to build + +#define FORCE_INLINE __inline + +#else +#pragma Not CW +#define asm +#define MWREG +#define CONST_MWREG const +#define MW_PRAG_NOINLINE +#define MW_PRAG_END +#define MW_PRAG_OPT_S + +#define FORCE_INLINE __forceinline +#endif + +// A function that does nothing +#define NULLSUB void + +// We know it's release stripped usually from context. i.e. allocated on debug +// heap +#define RELEASE_STRIPPED_ALL NULLSUB + +#define UNUSED_PARAM(x) (void)(x); // EPPC:#pragma unused +#define UNUSED + +#ifdef DECOMP + +#define LOCALREF(key, type, data, ref) extern type key; +#else +#define LINKED_ELSEWHERE + +#define LOCALREF(key, type, data, ref) type key = data; +#endif + +// Standard types +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 volatile u8 vu8; +typedef volatile u16 vu16; +typedef volatile u32 vu32; +typedef volatile u64 vu64; +typedef volatile s8 vs8; +typedef volatile s16 vs16; +typedef volatile s32 vs32; +typedef volatile s64 vs64; + +typedef float f32; +typedef double f64; +typedef volatile f32 vf32; +typedef volatile f64 vf64; \ No newline at end of file From 6bed744bdff9f017791f7792bfe378694ed499d5 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 27 Jan 2021 10:52:53 -0500 Subject: [PATCH 002/477] :memo: README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c739913e0..c468f4b72 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # mkw -Decompilation of Mario Kart Wii +An in-development, matching decompilation of Mario Kart Wii. All code in this repository will compile 1:1 to the original game. + +## Accuracy +The primary priority is to maintain absolute code accuracy. To automate verification of this, a special linker setup is used to emplace compiled code back into the original executable, forming a new executable. This new executable is hashed to ensure it matches the original. Once all code is decompiled, this setup will build a new executable from scratch, sampling none of the original. + +## Code Quality +I have written code to be as readable and maintainable as possible. While the original access modifiers and trivial encapsulations have been lost to the optimizer, I have reconstructed both to minimize unsafe data exposure. Common sense debug assertions have been added, enforcing unchecked preconditions. + +### Modern C++isms +While the original game was written and compiled as C++03, several modern C++ features have been used to aid readability and increase code quality. All are define'd out when compiling for C++03. For example: strongly typed null pointers with `nullptr` and the `override` specifier. + +## Documentation +Every fully understood piece of reverse engineered data has been documented in a consistent doxygen style. From 04fbd093d7d3cd0c20426f72a31a74144649853a Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 27 Jan 2021 10:56:23 -0500 Subject: [PATCH 003/477] :sparkles: Added assembly segments and buildscript --- .gitignore | 3 ++ build.py | 72 +++++++++++++++++++++++++++++++++++++++++++++ build/asm_files.txt | 13 ++++++++ build/link.lcf | 37 +++++++++++++++++++++++ build/o_files.txt | 13 ++++++++ 5 files changed, 138 insertions(+) create mode 100644 build.py create mode 100644 build/asm_files.txt create mode 100644 build/link.lcf create mode 100644 build/o_files.txt diff --git a/.gitignore b/.gitignore index 259148fa1..934414b52 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ *.exe *.out *.app + +*.elf +*.dol \ No newline at end of file diff --git a/build.py b/build.py new file mode 100644 index 000000000..1a17de613 --- /dev/null +++ b/build.py @@ -0,0 +1,72 @@ +import os + +DEVKITPPC = "C:\\devkitPro\\devkitPPC" + +GCC = os.path.join(DEVKITPPC, "bin\\powerpc-eabi-gcc.exe") +GAS = os.path.join(DEVKITPPC, "bin\\powerpc-eabi-as.exe") + +MWLD = "tools\\mwldeppc.exe" + +ELF2DOL = "tools\\elf2dol.exe" + +def command(cmd): + print(cmd) + os.system(cmd) + +def assemble(dst, src): + cmd = GAS + " %s -mgekko -o %s" % (src, dst) + command(cmd) + +def link(dst, objs, lcf): + cmd = MWLD + " %s -o %s -lcf %s" % (' '.join(objs), dst, lcf) + command(cmd) + +def make_obj(src): + substitutions = ( + '.cpp', '.asm', '.c', '.s' + ) + for s in substitutions: + src = src.replace(s, '.o') + return src + +def build(): + asm_files = [x.strip() for x in open('build/asm_files.txt', 'r').readlines()] + # src_files = [x.strip() for x in open('build/src_files.txt', 'r').readlines()] + o_files = ["out/" + x.strip() for x in open('build/o_files.txt', 'r').readlines()] + + try: + os.mkdir("out") + except: pass + + for asm in asm_files: + assemble("out/" + make_obj(asm).replace("asm/", ""), asm) + + link('out/built.elf', o_files, "build/link.lcf") + + with open('out/built.elf', 'r+b') as elf: + elf.seek(0x18) + elf.write(bytes([0x80, 0x00, 0x60, 0xA4])) + + try: + os.mkdir("target") + except: pass + command(ELF2DOL + " out/built.elf target/built.dol -v -v") + + # This is a bug with elf2dol; this works fine with makedol + # for now I'll just patch it... + with open('target/built.dol', 'r+b') as dol: + dol.seek(0xDC) + # bss_size + dol.write(bytes([0x00, 0x0E, 0x50, 0xFC])) + + import hashlib + ctx = hashlib.sha1(open('target/built.dol', 'rb').read()) + digest = ctx.hexdigest() + if digest.lower() == 'ac7d72448630ade7655fc8bc5fd7a6543cb53a49': + print("Everything went okay! Output is matching! ^^") + return + + print("Oof: Output doesn't match.") + # TODO: Add diff'ing + +build() \ No newline at end of file diff --git a/build/asm_files.txt b/build/asm_files.txt new file mode 100644 index 000000000..8475fc720 --- /dev/null +++ b/build/asm_files.txt @@ -0,0 +1,13 @@ +asm/init_80004000.s +asm/extab_80006460.s +asm/extabindex_80006a20.s +asm/text_800072c0.s +asm/ctors_80244de0.s +asm/dtors_80244ea4.s +asm/rodata_80244ec0.s +asm/data_80258580.s +asm/bss_802a4080.s +asm/sdata_80384c00.s +asm/sbss_80385fc0.s +asm/sdata2_80386fa0.s +asm/sbss2_80389140.s \ No newline at end of file diff --git a/build/link.lcf b/build/link.lcf new file mode 100644 index 000000000..cc7748894 --- /dev/null +++ b/build/link.lcf @@ -0,0 +1,37 @@ +ENTRY(__start) +MEMORY { +text : origin = 0x80004000 +} +SECTIONS { +GROUP:{ +.init ALIGN(0x20):{} +extab_ ALIGN(0x20):{} +extabindex_ ALIGN(0x20):{} +.text ALIGN(0x20):{} +.ctors ALIGN(0x20):{} +.dtors ALIGN(0x20):{} +.rodata ALIGN(0x20):{} +.data ALIGN(0x20):{} +.bss ALIGN(0x80):{} +.sdata ALIGN(0x20):{} +.sbss ALIGN(0x20):{} +.sdata2 ALIGN(0x20):{} +.sbss2 ALIGN(0x20):{} +.stack ALIGN(0x100):{} +} > text +_stack_addr = (_f_sbss2 + SIZEOF(.sbss2) + 65536 + 0x7) & ~0x7; +_stack_end = _f_sbss2 + SIZEOF(.sbss2); +_db_stack_addr = (_stack_addr + 0x2000); +_db_stack_end = _stack_addr; +__ArenaLo = (_db_stack_addr + 0x1f) & ~0x1f; +__ArenaHi = 0x81700000; + +__start = 0x800060A4; +__destroy_global_chain=0x80021350; +} + +FORCEFILES +{ +extab_80006460.o +extabindex_80006a20.o +} \ No newline at end of file diff --git a/build/o_files.txt b/build/o_files.txt new file mode 100644 index 000000000..ece405b52 --- /dev/null +++ b/build/o_files.txt @@ -0,0 +1,13 @@ +init_80004000.o +extab_80006460.o +extabindex_80006a20.o +text_800072c0.o +ctors_80244de0.o +dtors_80244ea4.o +rodata_80244ec0.o +data_80258580.o +bss_802a4080.o +sdata_80384c00.o +sbss_80385fc0.o +sdata2_80386fa0.o +sbss2_80389140.o \ No newline at end of file From 1bce90e5a10d356120df2f25cf300548fa9d1dd9 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 28 Jan 2021 00:19:56 -0500 Subject: [PATCH 004/477] :memo: Updated README --- README.md | 7 ++++++- build.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c468f4b72..33b63388e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # mkw -An in-development, matching decompilation of Mario Kart Wii. All code in this repository will compile 1:1 to the original game. +A matching decompilation of Mario Kart Wii. All code in this repository will compile 1:1 to the original game. +It produces the following .dol: +mkw_pal.dol: `sha1: ac7d72448630ade7655fc8bc5fd7a6543cb53a49` ## Accuracy The primary priority is to maintain absolute code accuracy. To automate verification of this, a special linker setup is used to emplace compiled code back into the original executable, forming a new executable. This new executable is hashed to ensure it matches the original. Once all code is decompiled, this setup will build a new executable from scratch, sampling none of the original. @@ -12,3 +14,6 @@ While the original game was written and compiled as C++03, several modern C++ fe ## Documentation Every fully understood piece of reverse engineered data has been documented in a consistent doxygen style. + +## Building +Create a root-level folder named `tools`. Place elf2dol, mwldeppc and mwcceppc in it. Then run `python3 build.py` \ No newline at end of file diff --git a/build.py b/build.py index 1a17de613..589cba423 100644 --- a/build.py +++ b/build.py @@ -50,17 +50,17 @@ def build(): try: os.mkdir("target") except: pass - command(ELF2DOL + " out/built.elf target/built.dol -v -v") + command(ELF2DOL + " out/built.elf target/mkw_pal.dol -v -v") # This is a bug with elf2dol; this works fine with makedol # for now I'll just patch it... - with open('target/built.dol', 'r+b') as dol: + with open('target/mkw_pal.dol', 'r+b') as dol: dol.seek(0xDC) # bss_size dol.write(bytes([0x00, 0x0E, 0x50, 0xFC])) import hashlib - ctx = hashlib.sha1(open('target/built.dol', 'rb').read()) + ctx = hashlib.sha1(open('target/mkw_pal.dol', 'rb').read()) digest = ctx.hexdigest() if digest.lower() == 'ac7d72448630ade7655fc8bc5fd7a6543cb53a49': print("Everything went okay! Output is matching! ^^") From 011726deba602892f8edda16a00979e08d15c5de Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 28 Jan 2021 19:38:08 -0500 Subject: [PATCH 005/477] :sparkles: Decompiled ARC --- build.py | 43 +++++ build/asm_files.txt | 9 +- build/link.lcf | 10 ++ build/o_files.txt | 10 +- build/src_files.txt | 1 + source/platform/ctype.h | 37 +++++ source/platform/stdbool.h | 13 ++ source/platform/stdint.h | 18 +++ source/rx/binary_format.h | 74 +++++++++ source/rx/rxArchive.c | 323 ++++++++++++++++++++++++++++++++++++++ source/rx/rxArchive.hpp | 293 ++++++++++++++++++++++++++++++++++ 11 files changed, 825 insertions(+), 6 deletions(-) create mode 100644 build/src_files.txt create mode 100644 source/platform/ctype.h create mode 100644 source/platform/stdbool.h create mode 100644 source/platform/stdint.h create mode 100644 source/rx/binary_format.h create mode 100644 source/rx/rxArchive.c create mode 100644 source/rx/rxArchive.hpp diff --git a/build.py b/build.py index 589cba423..f7c79c9d7 100644 --- a/build.py +++ b/build.py @@ -9,6 +9,47 @@ ELF2DOL = "tools\\elf2dol.exe" +SOURCE_PATH = "./source/" +ASM_TEXT_PATH = "./asm/text/" +BUILD_PATH = "./build/" +CWCC_OLD = True +CWCC_PATH = ".\\tools\\OLD_mwcceppc.exe" if CWCC_OLD else ".\\tools\\mwcceppc.exe" +CWCC_OPT = " ".join([ + "-nodefaults", + "-align powerpc", + "-enc SJIS", + "-c", + # "-I-", + "-gccinc", + "-i ./source/ -i ./source/platform", + # "-inline deferred", + "-proc gekko", + "-enum int", + "-O4,p", + "-inline auto", + "-W all", + "-fp hardware", + "-Cpp_exceptions off", + "-RTTI off", + "-pragma \"cats off\"", # ??? + # "-pragma \"aggressive_inline on\"", + # "-pragma \"auto_inline on\"", + "-ipa file", + "-inline auto", + "-w notinlined -W noimplicitconv", + "-nostdinc", + "-msgstyle gcc -lang=c99 -DREVOKART" +]) + +def compile_source(src, dst): + try: + os.mkdir("tmp") + except: pass + command = f"{CWCC_PATH} {CWCC_OPT if 'rx' not in src else CWCC_OPT.replace(',s', ',p')} {src} -o {dst}" + print(command) + os.system(command + " > ./tmp/compiler_log.txt") + print(open("./tmp/compiler_log.txt").read().replace("source\\", "")) + def command(cmd): print(cmd) os.system(cmd) @@ -38,6 +79,8 @@ def build(): os.mkdir("out") except: pass + compile_source("source/rx/rxArchive.c", "out/rxArchive.o") + for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) diff --git a/build/asm_files.txt b/build/asm_files.txt index 8475fc720..9053fbc25 100644 --- a/build/asm_files.txt +++ b/build/asm_files.txt @@ -1,13 +1,16 @@ +asm/text_800072c0.s +asm/data_80258580.s +asm/sdata_80384c00.s asm/init_80004000.s asm/extab_80006460.s asm/extabindex_80006a20.s -asm/text_800072c0.s +asm/text_80124e80.s asm/ctors_80244de0.s asm/dtors_80244ea4.s asm/rodata_80244ec0.s -asm/data_80258580.s +asm/data_8027e772.s asm/bss_802a4080.s -asm/sdata_80384c00.s +asm/sdata_803857f6.s asm/sbss_80385fc0.s asm/sdata2_80386fa0.s asm/sbss2_80389140.s \ No newline at end of file diff --git a/build/link.lcf b/build/link.lcf index cc7748894..ab3d3849c 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -28,10 +28,20 @@ __ArenaHi = 0x81700000; __start = 0x800060A4; __destroy_global_chain=0x80021350; + +OSPanic=0x801A2660; +_savegpr_26=0x8002159C; +_savegpr_27=0x800215A0; + +_restgpr_26=0x800215E8; +_restgpr_27=0x800215EC; +OSReport=0x801A25D0; +_current_locale=0x80271148; } FORCEFILES { extab_80006460.o extabindex_80006a20.o +rxArchive.o } \ No newline at end of file diff --git a/build/o_files.txt b/build/o_files.txt index ece405b52..73d134010 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -1,13 +1,17 @@ +text_800072c0.o +data_80258580.o +sdata_80384c00.o +rxArchive.o init_80004000.o extab_80006460.o extabindex_80006a20.o -text_800072c0.o +text_80124e80.o ctors_80244de0.o dtors_80244ea4.o rodata_80244ec0.o -data_80258580.o +data_8027e772.o bss_802a4080.o -sdata_80384c00.o +sdata_803857f6.o sbss_80385fc0.o sdata2_80386fa0.o sbss2_80389140.o \ No newline at end of file diff --git a/build/src_files.txt b/build/src_files.txt new file mode 100644 index 000000000..ad336c0a6 --- /dev/null +++ b/build/src_files.txt @@ -0,0 +1 @@ +rxArchive.cpp \ No newline at end of file diff --git a/source/platform/ctype.h b/source/platform/ctype.h new file mode 100644 index 000000000..90a65daeb --- /dev/null +++ b/source/platform/ctype.h @@ -0,0 +1,37 @@ +#pragma once + +#ifndef MSL_CTYPE_H +#define MSL_CTYPE_H + +#include "rk_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct __CMap { + u8 _0[16]; + const u8* to_lower_table; // 10 + const u8* to_upper_table; +}; +struct __Locale { + u8 _[56]; + struct __CMap* cmap; + u8 _1[0x44-56-4]; +}; +extern struct __Locale _current_locale; + +#define case_table (&_current_locale)->cmap->to_lower_table +#define up_case_table (&_current_locale)->cmap->to_upper_table +static inline int tolower(int x) { + return (x < 0 || x >= 256) ? x : (int)case_table[x]; +} +static inline int toupper(int x) { + return (x < 0 || x >= 256) ? x : (int)up_case_table[x]; +} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MSL_CTYPE_H \ No newline at end of file diff --git a/source/platform/stdbool.h b/source/platform/stdbool.h new file mode 100644 index 000000000..f16d5e3ca --- /dev/null +++ b/source/platform/stdbool.h @@ -0,0 +1,13 @@ +#pragma once + +#ifndef MSL_STDBOOL +#define MSL_STDBOOL + +#ifndef __cplusplus + typedef _Bool bool; + + #define true ((_Bool)1) + #define false ((_Bool)0) +#endif // __cplusplus + +#endif // MSL_STDBOOL \ No newline at end of file diff --git a/source/platform/stdint.h b/source/platform/stdint.h new file mode 100644 index 000000000..97bbd8a81 --- /dev/null +++ b/source/platform/stdint.h @@ -0,0 +1,18 @@ +#pragma once + +#ifndef MSL_STDINT +#define MSL_STDINT + +typedef unsigned long long uint64_t; +typedef signed long long int64_t; + +typedef unsigned long uint32_t; +typedef signed long int32_t; + +typedef unsigned short uint16_t; +typedef signed short int16_t; + +typedef unsigned char uint8_t; +typedef signed char int8_t; + +#endif // MSL_STDINT \ No newline at end of file diff --git a/source/rx/binary_format.h b/source/rx/binary_format.h new file mode 100644 index 000000000..d23132876 --- /dev/null +++ b/source/rx/binary_format.h @@ -0,0 +1,74 @@ +#pragma once + +#include // bool +#include // expects + +void OSPanic(...); +void OSReport(...); + +struct rxNode { + union { + struct { + u32 is_folder : 8; + u32 name : 24; + }; + u32 packed_type_name; + }; + union { + struct { + u32 offset; + u32 size; + } file; + struct { + u32 parent; + u32 sibling_next; + } folder; + }; +}; + +// Bitfield access of a packed_type_name X produces a pattern (X >> 24), which +// does not match access done by masking. +#define rxNodeIsFolder(node) ((node).packed_type_name & 0xff000000) +// In some cases (X << 8 >> 8) does not collapse to a mask. +#define rxNodeGetName(node) ((node).packed_type_name & 0x00ffffff) + +struct rxArchiveHeader { + u32 magic; // 00 + struct { + s32 offset; // 04 + s32 size; // 08 + } nodes; + struct { + s32 offset; // 0C + } files; + + u8 _10[0x10]; +}; + +typedef struct rxNode rxNode; +typedef struct rxArchiveHeader rxArchiveHeader; + +enum { RX_ARCHIVE_FILE_MAGIC = 0x55aa382d }; + +static bool rxArchiveHeaderVerify(const rxArchiveHeader* self) { + // Verify the "U8" magic + if (self->magic != RX_ARCHIVE_FILE_MAGIC) + return false; + + return true; +} +static const rxNode* rxArchiveHeaderGetNodes(const rxArchiveHeader* self) { + expects(self->nodes.offset > sizeof(rxArchiveHeader)); + return (const rxNode*)((u8*)self + self->nodes.offset); +} +static const u8* rxArchiveHeaderGetFileData(const rxArchiveHeader* self) { + expects(self->files.offset > sizeof(rxArchiveHeader)); + return (const u8*)((u8*)self + self->files.offset); +} + +#define ARCHIVE_THROW_INVALID_HEADER() \ + OSPanic("arc.c", 74, "ARCInitHandle: bad archive format") +#define ARCHIVE_LOG_FILE_NOT_FOUND(file, root) \ + OSReport("Warning: ARCOpen(): file '%s' was not found under %s in the " \ + "archive.\n", \ + file, root) diff --git a/source/rx/rxArchive.c b/source/rx/rxArchive.c new file mode 100644 index 000000000..62fce31da --- /dev/null +++ b/source/rx/rxArchive.c @@ -0,0 +1,323 @@ +#include + +#include "binary_format.h" // rxArchiveHeader +#include // tolower + +bool __rxGetCurrentPath(rxArchive* self, char* string, u32 capacity); + +// pData must be nonconst +bool rxArchiveOpen(void* pData, rxArchive* pOut) { + expects(pData != nullptr && pOut != nullptr); + + const rxArchiveHeader* pHeader = (rxArchiveHeader*)pData; + if (!rxArchiveHeaderVerify(pHeader)) + ARCHIVE_THROW_INVALID_HEADER(); + + pOut->mHeader = pData; + + const rxNode* nodes = rxArchiveHeaderGetNodes(pHeader); + pOut->mNodes = nodes; + + pOut->mFileData = rxArchiveHeaderGetFileData(pHeader); + // The right bound of the root node is the number of nodes + pOut->mCount = nodes[0].folder.sibling_next; + // Strings exist directly after the last node. + pOut->mStrings = (const char*)(nodes + pOut->mCount); + pOut->mFstSize = (u32)pHeader->nodes.size; + pOut->mCurrentPath = 0; + + return true; +} + +bool rxArchiveFileOpen(const rxArchive* self, const char* path, + rxArchiveFile* pOut) { + expects(self != nullptr && path != nullptr && pOut != nullptr); + + const rxNode* nodes = self->mNodes; + const s32 resolved = rxArchiveResolve((rxArchive*)self, path); + + if (!rxEntryHandleIsValid(resolved)) { + char current_path[128]; + + __rxGetCurrentPath((rxArchive*)self, current_path, sizeof(current_path)); + ARCHIVE_LOG_FILE_NOT_FOUND(path, current_path); + + return false; + } + + if (!rxEntryHandleIsValid(resolved) || rxNodeIsFolder(nodes[resolved])) { + return false; + } + + pOut->parent = (rxArchive*)self; + pOut->offset = nodes[resolved].file.offset; + pOut->size = nodes[resolved].file.size; + return true; +} + +bool rxArchiveFileOpenLow(const rxArchive* self, s32 resolved, + rxArchiveFile* pOut) { + expects(self != nullptr && pOut != nullptr); + + const rxNode* nodes = self->mNodes; + + // Although resolve will never return a handle beyond the node count, + // the user may, so an additional check is needed. + if (!rxEntryHandleIsValid(resolved) || resolved >= self->mCount || + rxNodeIsFolder(nodes[resolved])) { + return false; + } + + pOut->parent = (rxArchive*)self; + pOut->offset = nodes[resolved].file.offset; + pOut->size = nodes[resolved].file.size; + + return true; +} + +// seen in debug builds as a standalone function +inline int __rxPathCompare(const char* lhs, const char* rhs) { + while (rhs[0] != '\0') { + if (tolower(*lhs++) != tolower(*rhs++)) + return false; + } + + if (lhs[0] == '/' || lhs[0] == '\0') + return true; + + return false; +} + +inline u32 __rxPathCopy(char* dst, char* src, u32 capacity) { + u32 i; + for (i = capacity; i != 0 && src[0] != '\0'; --i) + *dst++ = *src++; + + return capacity - i; +} + +s32 rxArchiveResolve(rxArchive* self, const char* path) { + s32 name_length; // r7 + u32 it = self->mCurrentPath; // r8 + rxNode* nodes = (rxNode*)self->mNodes; // r9 + + while (true) { + // End of string -> return what we have + if (path[0] == '\0') + return it; + + // Ignore initial slash: /Path/File vs Path/File + if (path[0] == '/') { + it = 0; + ++path; + continue; + } + + // Handle special cases: + // -../-, -.., -./-, -. + if (path[0] == '.') { + if (path[1] == '.') { + // Seek to parent ../ + if (path[2] == '/') { + it = nodes[it].folder.parent; + path += 3; + continue; + } + // Return parent folder immediately + if (path[2] == '\0') + return nodes[it].folder.parent; + // Malformed: fall through, causing infinite loop + goto compare; + } + + // "." directory does nothing + if (path[1] == '/') { + path += 2; + continue; + } + + // Ignore trailing dot + if (path[1] == '\0') + return it; + } + + compare: + // We've ensured the directory is not special. + // Isolate the name of the current item in the path string. + const char* name_end = path; + while (name_end[0] != '\0' && name_end[0] != '/') + ++name_end; + + // If the name was delimited by a '/' rather than truncated. + // This must be expressed as a ternary, and an enum cannot be used.. + int name_delimited_by_slash = (name_end[0] != '\0') ? 1 : 0; + name_length = name_end - path; + + // Traverse all children of the parent. + const u32 anchor = it; + ++it; + while (it < nodes[anchor].folder.sibling_next) { + while (true) { + if (rxNodeIsFolder(nodes[it]) || name_delimited_by_slash != true) { + char* name_of_it = ((char*)self->mStrings) + rxNodeGetName(nodes[it]); + + // Skip empty directories + if (name_of_it[0] == '.' && name_of_it[1] == '\0') { + ++it; + continue; + } + + // Advance to the next item in the path + if (__rxPathCompare(path, name_of_it) == true) { + goto descend; + } + } + + if (rxNodeIsFolder(nodes[it])) { + it = nodes[it].folder.sibling_next; + break; + } + + ++it; + break; + } + } + + return -1; + + descend: + // If the path was truncated, there is nowhere else to go + // These basic blocks have to go here right at the end, accessed via a goto. + // An odd choice. + if (!name_delimited_by_slash) + return it; + + path += name_length + 1; + } +} + +static u32 __rxMakePathRecursive(rxArchive* self, u32 resolved, char* string, + u32 capacity) { + rxNode* nodes = (rxNode*)self->mNodes; + + if (resolved == 0) + return 0; + + char* name = ((char*)self->mStrings) + nodes[resolved].name; + + u32 written = __rxMakePathRecursive(self, nodes[resolved].folder.parent, + string, capacity); + + if (written == capacity) + return written; + + string[written++] = '/'; + return written + __rxPathCopy(string + written, name, capacity - written); +} + +inline bool __rxMakePath(rxArchive* self, s32 resolved, char* string, + u32 capacity) { + rxNode* nodes = (rxNode*)self->mNodes; + + u32 written = __rxMakePathRecursive(self, resolved, string, capacity); + if (written == capacity) { + string[capacity - 1] = '\0'; + return false; + } + + if (rxNodeIsFolder(nodes[resolved])) { + if (written == capacity - 1) { + string[written] = '\0'; + return false; + } + + string[written++] = '/'; + } + + string[written] = '\0'; + return true; +} + +inline bool __rxGetCurrentPath(rxArchive* self, char* string, u32 capacity) { + return __rxMakePath(self, self->mCurrentPath, string, capacity); +} + +void* rxArchiveFileGetData(rxArchiveFile* self) { + return (char*)(self->parent->mHeader) + self->offset; +} + +#ifndef GALAXY +s32 rxArchiveFileGetOffset(const rxArchiveFile* self) { return self->offset; } +#endif // GALAXY + +u32 rxArchiveFileGetSize(const rxArchiveFile* self) { return self->size; } + +bool rxArchiveFileClose(rxArchiveFile* self) { return true; } + +bool rxArchiveSetCurrentPath(rxArchive* self, const char* path) { + const s32 resolved = rxArchiveResolve((rxArchive*)self, path); + rxNode* nodes = (rxNode*)self->mNodes; + + if (!rxEntryHandleIsValid(resolved) || !rxNodeIsFolder(nodes[resolved])) + return false; + + self->mCurrentPath = resolved; + return true; +} + +bool rxArchiveFolderRangeOpen(const rxArchive* self, const char* path, + rxArchiveFolderRange* pOut) { + const s32 resolved = rxArchiveResolve((rxArchive*)self, path); + const rxNode* nodes = self->mNodes; + + if (resolved < 0 || (nodes[resolved].packed_type_name & 0xff000000) == 0) + return false; + + pOut->parent = (rxArchive*)self; + pOut->path_begin = resolved; + pOut->path_it = resolved + 1; + pOut->path_end = nodes[resolved].folder.sibling_next; + return true; +} + +bool rxArchiveFolderRangeNext(rxArchiveFolderRange* self, + rxArchiveEntry* pOut) { + expects(self->parent != nullptr); + + u32 it; // regalloc fun! + rxNode* nodes; // this must not be const lest regalloc change and the function + // grow in size + + rxArchive* arc = self->parent; + nodes = (rxNode*)arc->mNodes; + + it = self->path_it; + + // while (A) produces a nop + while (true) { + if (it <= self->path_begin || self->path_end <= it) + return false; + + pOut->parent = arc; + pOut->path = it; + // All non-file entries are folders. + // Collapse to one specific value. + pOut->node_type = rxNodeIsFolder(nodes[it]) // + ? RX_ARCHIVE_FOLDER + : RX_ARCHIVE_FILE; + pOut->name = arc->mStrings + nodes[it].name; + + // skip '.' directories + if (pOut->name[0] == '.' && pOut->name[1] == '\0') { + ++it; + continue; + } + + self->path_it = rxNodeIsFolder(nodes[it]) // + ? nodes[it].folder.sibling_next + : it + 1; + return true; + } +} + +bool rxArchiveFolderRangeClose(rxArchiveFolderRange* self) { return true; } diff --git a/source/rx/rxArchive.hpp b/source/rx/rxArchive.hpp new file mode 100644 index 000000000..0ac95ba76 --- /dev/null +++ b/source/rx/rxArchive.hpp @@ -0,0 +1,293 @@ +#pragma once + +#include // bool +#include // u32 + +#ifdef __cplusplus +extern "C" { +#endif + +//! Describes the type of an entry in an archive. +//! +typedef enum rxArchiveType { + //! The entry is a file. + //! Accessed via rxArchiveFile with rxArchiveFileOpen/rxArchiveFileOpenLow + //! + RX_ARCHIVE_FILE, + + //! The entry is a folder. + //! + RX_ARCHIVE_FOLDER +} rxArchiveType; + +//! A unique integer handle. +//! Negative if the operation failed. +//! For use with rxArchiveFileOpenLow. +//! +typedef s32 rxEntryHandle; + +//! Return if the handle returned by rxArchiveResolve is valid. +//! +//! @note A handle may be plausibly valid, +//! although not valid for a specific archive. +//! +#define rxEntryHandleIsValid(handle) ((handle) >= 0) + +//! Return if the handle is valid for a specific archive. +//! +#define rxEntryHandleIsValidForArchive(handle, archive) \ + ((handle) >= 0 && (handle) < archive->mCount) + +//! A read-only view of an archive file. +//! +typedef struct rxArchive { + const struct rxArchiveHeader* mHeader; //!< 00 Pointer to the archive header. + const struct rxNode* mNodes; //!< 04 Array of nodes for each archive entry. + const u8* mFileData; //!< 08 File data buffer, accessed by nodes. + u32 mCount; //!< 0C Number of nodes in the archive. + const char* mStrings; //!< 10 String buffer, accessed by nodes. + u32 mFstSize; //!< 14 Total bytesize of nodes and strings. + u32 mCurrentPath; //!< 18 The current directory of the archive. +} rxArchive; + +//! File info. +//! +typedef struct rxArchiveFile { + rxArchive* parent; //!< 00 The archive the file is a part of. + u32 offset; //!< 04 Offset into the archive's file data buffer. + u32 size; //!< 08 Size of the file. +} rxArchiveFile; + +//! Info about a generic file or folder. +//! - If node_type is RX_ARCHIVE_FILE, use rxArchiveFileOpenLow to fill-in an +//! rxArchiveFile. +//! - If node_type is RX_ARCHIVE_FOLDER, use rxArchiveFolderOpen to fill-in an +//! rxArchiveFolderRange. +//! +typedef struct rxArchiveEntry { + rxArchive* parent; //!< 00 The archive the file is a part of. + u32 path; //!< 04 Special ID corresponding to a file path. + //!< Accepted by rxArchiveFileOpenLow. + rxArchiveType node_type; //!< The entry type. + const char* name; //!< 0C Name of this entry in specific. + //!< (Not an absolute path) +} rxArchiveEntry; + +//! Contains an iterable range of the recursive contents of a folder. +//! +typedef struct rxArchiveFolderRange { + rxArchive* parent; //!< 00 The archive the file is a part of. + u32 path_begin; //!< 04 The lower bound of the iterator range. + u32 path_it; //!< 08 The current position of the iterator. + u32 path_end; //!< 0C The upper bound of the iterator range. +} rxArchiveFolderRange; + +//! @brief Open an archive from the specified U8 data. +//! +//! @param[in] pData Pointer to the U8 data. +//! @param[out] pOut Pointer to the archive to create. +//! +//! @return If the operation succeeded. +//! +bool rxArchiveOpen(void* pData, rxArchive* pOut); + +//! @brief Resolve a string path to a unique integer +//! for use with rxArchiveFileOpenLow. +//! +//! @param[in] self The archive. +//! @param[in] path Null-terminated string path. Forward slashes must be used. +//! +//! @return A unique integer handle. Negative if the operation failed. +//! +rxEntryHandle rxArchiveResolve(rxArchive* self, const char* path); + +//! @brief Set the current path of the archive to a new root for future access. +//! Similar to the "cd" command. +//! +//! @param[in] self The archive. +//! @param[in] path Null-terminated string path. Forward slashes must be used. +//! +//! @return If the operation succeeded. +//! +bool rxArchiveSetCurrentPath(rxArchive* self, const char* path); + +//! @brief Open a file handle from an archive. +//! +//! @param[in] self The archive. +//! @param[in] path Null-terminated string path. Forward slashes must be used. +//! @param[out] pOut Pointer to an rxArchiveFile to fill in. +//! +//! @return If the operation succeeded. +//! +bool rxArchiveFileOpen(const rxArchive* self, const char* path, + rxArchiveFile* pOut); + +//! @brief Open a file handle from an archive. +//! +//! @param[in] self The archive. +//! @param[in] path Direct entry handle, from rxArchiveResolve. +//! @param[out] pOut Pointer to an rxArchiveFile to fill in. +//! +//! @return If the operation succeeded. +//! +bool rxArchiveFileOpenLow(const rxArchive* self, rxEntryHandle path, + rxArchiveFile* pOut); + +//! @brief Get the file data from a file handle. +//! +//! @param[in] self Pointer to a file. +//! +//! @return A pointer to the beginning of the file data buffer. +//! +void* rxArchiveFileGetData(rxArchiveFile* self); + +//! @brief Get the address translation of an archive subfile. +//! +//! @param[in] self Pointer to a file. +//! +//! @return The number of bytes in the archive file until the subfile begins. +//! +s32 rxArchiveFileGetOffset(const rxArchiveFile* self); + +//! @brief Get the filesize of a file handle. +//! +//! @param[in] self Pointer to a file. +//! +//! @return The size of the file data buffer, in bytes. +//! +u32 rxArchiveFileGetSize(const rxArchiveFile* self); + +//! @brief Close a file handle. +//! +//! @param[in] self Pointer to a file. +//! +//! @return If the operation succeeded. +//! +bool rxArchiveFileClose(rxArchiveFile* self); + +//! @brief Get an iterable range of the recursive contents of a folder. +//! +//! @param[in] self Pointer to an archive. +//! @param[in] path Folder path. +//! @param[out] pOut A folder range to fill in. +//! +//! @return If the operation succeeded: +//! - 1) The path exists and +//! - 2) is not a file. +//! +bool rxArchiveFolderRangeOpen(const rxArchive* self, const char* path, + rxArchiveFolderRange* pOut); + +//! @brief Retrieve the next element in the iterator. +//! +//! @param[in] self The folder range. +//! @param[out] pOut The archive entry to fill in. +//! +//! @return False if the iterator has been exhausted. +//! +bool rxArchiveFolderRangeNext(rxArchiveFolderRange* self, rxArchiveEntry* pOut); + +//! @brief Close the folder range handle. +//! +//! @param[in] self The folder range. +//! +//! @return If the operation succeeded. +//! +bool rxArchiveFolderRangeClose(rxArchiveFolderRange* self); + +#ifdef GALAXY +inline s32 rxArchiveFileGetOffset(const rxArchiveFile* self) { + return self->offset; +} +#endif // GALAXY + +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus + +namespace rx { + +template class GenericEntry : private rxArchiveEntry { +public: + bool isFile() const { return node_type == RX_ARCHIVE_FILE; } + bool isFolder() const { return node_type == RX_ARCHIVE_FOLDER; } + + T* getParent() { return reinterpret_cast(parent); } + const T* getParent() const { return reinterpret_cast(parent); } + u32 getPath() const { return path; } + const char* getName() const { return name; } +}; + +namespace low { + +//! Low-level archive wrapper. Directly maps to C functions. +//! +class Archive : private rxArchive { +public: + bool open(const void* pData) { + return rxArchiveOpen(const_cast(pData), this); + } + s32 resolve(const char* path) const { + return rxArchiveResolve(const_cast(this), path); + } + bool setCurrentPath(const char* path) { + return rxArchiveSetCurrentPath(path); + } + + class File : private rxArchiveFile { + public: + bool open(const Archive* parent, const char* path) { + return rxArchiveFileOpen(parent, path, this); + } + bool open(const Archive* parent, s32 path) { + return rxArchiveFileOpenLow(parent, path, this); + } + void* getData() { return rxArchiveFileGetData(this); } + const void* getData() const { + return rxArchiveFileGetData(const_cast(this)); + } + u32 getSize() const { return rxArchiveFileGetSize(this); } + bool close() { return rxArchiveFileClose(this); } + }; + + class FolderRange : private rxArchiveFolderRange { + public: + bool open(const Archive* parent, const char* path) { + rxArchiveFolderRangeOpen(parent, path, this); + } + bool next(GenericEntry& out) { + rxArchiveFolderRangeNext(this, &out); + } + bool close() { return rxArchiveFolderRangeClose(this); } + }; +}; +} // namespace low + +//! High-level archive wrapper. Follows RAII principles. +//! +class Archive : private low::Archive { +public: + Archive(const void* pData) { open(pData); } + + class File : public low::Archive::File { + public: + explicit File(const Archive* parent, const char* path) { open(path); } + explicit File(const Archive* parent, s32 path) { open(path); } + ~File() { close(); } + }; + + class FolderRange : private low::Archive::FolderRange { + FolderRange(const Archive* parent, const char* path) { open(parent, path); } + ~FolderRange() { close(); } + bool next(GenericEntry& out) { + return rx::low::Archive::FolderRange::next( + reinterpret_cast>(out)); + } + }; +} + +} // namespace rx + +#endif // __cplusplus From acba1c785016335d0474ed55ae1f52f2b9aff7bd Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 28 Jan 2021 21:28:26 -0500 Subject: [PATCH 006/477] :sparkles: Started MEM (doubly-linked list) --- .gitignore | 6 +++- build.py | 1 + build/asm_files.txt | 3 +- build/link.lcf | 1 + build/o_files.txt | 4 ++- build/src_files.txt | 1 - source/rx/rxList.c | 72 +++++++++++++++++++++++++++++++++++++++++++++ source/rx/rxList.h | 34 +++++++++++++++++++++ 8 files changed, 118 insertions(+), 4 deletions(-) delete mode 100644 build/src_files.txt create mode 100644 source/rx/rxList.c create mode 100644 source/rx/rxList.h diff --git a/.gitignore b/.gitignore index 934414b52..c986aceef 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,8 @@ *.app *.elf -*.dol \ No newline at end of file +*.dol + +compiler_log.txt +.vs +out \ No newline at end of file diff --git a/build.py b/build.py index f7c79c9d7..77f32ca12 100644 --- a/build.py +++ b/build.py @@ -80,6 +80,7 @@ def build(): except: pass compile_source("source/rx/rxArchive.c", "out/rxArchive.o") + compile_source("source/rx/rxList.c", "out/rxList.o") for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) diff --git a/build/asm_files.txt b/build/asm_files.txt index 9053fbc25..ef0c12385 100644 --- a/build/asm_files.txt +++ b/build/asm_files.txt @@ -1,10 +1,11 @@ asm/text_800072c0.s asm/data_80258580.s asm/sdata_80384c00.s +asm/text_80124e80.s asm/init_80004000.s asm/extab_80006460.s asm/extabindex_80006a20.s -asm/text_80124e80.s +asm/text_80199d04.s asm/ctors_80244de0.s asm/dtors_80244ea4.s asm/rodata_80244ec0.s diff --git a/build/link.lcf b/build/link.lcf index ab3d3849c..d5d6afe78 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -44,4 +44,5 @@ FORCEFILES extab_80006460.o extabindex_80006a20.o rxArchive.o +rxList.o } \ No newline at end of file diff --git a/build/o_files.txt b/build/o_files.txt index 73d134010..8f223b749 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -2,10 +2,12 @@ text_800072c0.o data_80258580.o sdata_80384c00.o rxArchive.o +text_80124e80.o +rxList.o init_80004000.o extab_80006460.o extabindex_80006a20.o -text_80124e80.o +text_80199d04.o ctors_80244de0.o dtors_80244ea4.o rodata_80244ec0.o diff --git a/build/src_files.txt b/build/src_files.txt deleted file mode 100644 index ad336c0a6..000000000 --- a/build/src_files.txt +++ /dev/null @@ -1 +0,0 @@ -rxArchive.cpp \ No newline at end of file diff --git a/source/rx/rxList.c b/source/rx/rxList.c new file mode 100644 index 000000000..53742ff24 --- /dev/null +++ b/source/rx/rxList.c @@ -0,0 +1,72 @@ +#include "rxList.h" + +#ifdef __cplusplus +extern "C" { +#endif + +rxIntrusiveList* rxInitList(rxIntrusiveList* pList, u16 intrusion_offset) { + pList->intrusion_offset = intrusion_offset; + pList->head = nullptr; + pList->tail = nullptr; + pList->count = 0; + + return pList; +} + +rxIntrusiveList* rxListAppend(rxIntrusiveList* pList, void* pObj) { + if (pList->head == nullptr) { + rxBiNode* node = rxListGetNode(pList, pObj); + + node->succ = nullptr; + node->pred = nullptr; + + pList->head = pObj; + pList->tail = pObj; + ++pList->count; + + return pList; + } + + rxBiNode* node = rxListGetNode(pList, pObj); + + node->pred = pList->tail; + node->succ = nullptr; + + rxListGetNode(pList, pList->tail)->succ = pObj; + pList->tail = pObj; + ++pList->count; + + return pList; +} + +rxIntrusiveList* rxListRemove(rxIntrusiveList* pList, void* pObj) { + rxBiNode* node = rxListGetNode(pList, pObj); + + if (node->pred == nullptr) + pList->head = node->succ; + else + rxListGetNode(pList, node->pred)->succ = node->succ; + + if (node->succ == nullptr) + pList->tail = node->pred; + else + rxListGetNode(pList, node->succ)->pred = node->pred; + + node->pred = nullptr; + node->succ = nullptr; + + --pList->count; + + return pList; +} + +void* rxListGetSuccessorOf(rxIntrusiveList* pList, void* pObj) { + if (pObj == nullptr) + return pList->head; + + return rxListGetNode(pList, pObj)->succ; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rx/rxList.h b/source/rx/rxList.h new file mode 100644 index 000000000..e876146ff --- /dev/null +++ b/source/rx/rxList.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//! Bidirectional list node +typedef struct { + void* pred; + void* succ; +} rxBiNode; + +// Unlike modern "std::list"-like structures, list nodes are directly inherited +// by children, which saves a level of indirection. +typedef struct { + void* head; + void* tail; + u16 count; + u16 intrusion_offset; +} rxIntrusiveList; + +rxIntrusiveList* rxInitList(rxIntrusiveList* pList, u16 intrusion_offset); +rxIntrusiveList* rxListAppend(rxIntrusiveList* pList, void* pObj); +rxIntrusiveList* rxListRemove(rxIntrusiveList* pList, void* pObj); +void* rxListGetSuccessorOf(rxIntrusiveList* pList, void* pObj); + +#define rxListGetNode(list, obj) \ + ((rxBiNode*)(((char*)obj) + (list)->intrusion_offset)) + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 264a9e131f54b8d85396b82098a2d88184c996d3 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 28 Jan 2021 22:51:38 -0500 Subject: [PATCH 007/477] :sparkles: DWC: Decompiled dwc_error.c --- build.py | 3 +- build/asm_files.txt | 5 +- build/link.lcf | 1 + build/o_files.txt | 6 +- source/dwc/common/dwc_error.c | 101 +++++++++++++++++++++++++++++++++ source/dwc/common/dwci_error.h | 27 +++++++++ source/dwc/dwci_debug.h | 20 +++++++ 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 source/dwc/common/dwc_error.c create mode 100644 source/dwc/common/dwci_error.h create mode 100644 source/dwc/dwci_debug.h diff --git a/build.py b/build.py index 77f32ca12..551bfd0f9 100644 --- a/build.py +++ b/build.py @@ -59,7 +59,7 @@ def assemble(dst, src): command(cmd) def link(dst, objs, lcf): - cmd = MWLD + " %s -o %s -lcf %s" % (' '.join(objs), dst, lcf) + cmd = MWLD + " %s -o %s -lcf %s -fp hard" % (' '.join(objs), dst, lcf) command(cmd) def make_obj(src): @@ -81,6 +81,7 @@ def build(): compile_source("source/rx/rxArchive.c", "out/rxArchive.o") compile_source("source/rx/rxList.c", "out/rxList.o") + compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o") for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) diff --git a/build/asm_files.txt b/build/asm_files.txt index ef0c12385..3ee20dce1 100644 --- a/build/asm_files.txt +++ b/build/asm_files.txt @@ -1,5 +1,8 @@ asm/text_800072c0.s asm/data_80258580.s +asm/sbss_80385fc0.s +asm/text_800ccc80.s +asm/data_80275758.s asm/sdata_80384c00.s asm/text_80124e80.s asm/init_80004000.s @@ -12,6 +15,6 @@ asm/rodata_80244ec0.s asm/data_8027e772.s asm/bss_802a4080.s asm/sdata_803857f6.s -asm/sbss_80385fc0.s +asm/sbss_803862b0.s asm/sdata2_80386fa0.s asm/sbss2_80389140.s \ No newline at end of file diff --git a/build/link.lcf b/build/link.lcf index d5d6afe78..1a0aa0a9e 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -43,6 +43,7 @@ FORCEFILES { extab_80006460.o extabindex_80006a20.o +dwc_error.o rxArchive.o rxList.o } \ No newline at end of file diff --git a/build/o_files.txt b/build/o_files.txt index 8f223b749..1c8d9119d 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -1,5 +1,9 @@ text_800072c0.o data_80258580.o +sbss_80385fc0.o +dwc_error.o +text_800ccc80.o +data_80275758.o sdata_80384c00.o rxArchive.o text_80124e80.o @@ -14,6 +18,6 @@ rodata_80244ec0.o data_8027e772.o bss_802a4080.o sdata_803857f6.o -sbss_80385fc0.o +sbss_803862b0.o sdata2_80386fa0.o sbss2_80389140.o \ No newline at end of file diff --git a/source/dwc/common/dwc_error.c b/source/dwc/common/dwc_error.c new file mode 100644 index 000000000..3cb0d2203 --- /dev/null +++ b/source/dwc/common/dwc_error.c @@ -0,0 +1,101 @@ +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//! @brief The last error code encountered. +//! +int stDwcErrorCode; + +//! @brief The last error type encountered. +//! +int stDwcLastError; + +int DWC_GetLastError(int* errorCode) { + if (errorCode) + *errorCode = stDwcErrorCode; + + return stDwcLastError; +} + +int DWC_GetLastErrorEx(int* errorCode, int* errorType) { + if (errorCode) + *errorCode = stDwcErrorCode; + if (errorType) { + switch (stDwcLastError) { + case 2: + case 3: + case 4: + case 5: + case 8: + *errorType = 6; + break; + case 6: + if (stDwcErrorCode == -80430) + *errorType = 2; + else + *errorType = 3; + break; + case 7: + *errorType = 4; + break; + case 10: + case 11: + case 12: + case 13: + *errorType = 1; + break; + case 1: + case DWCErrorFatal: + *errorType = 7; + break; + + case 14: + *errorType = 5; + break; + case 15: + case 17: + case 19: + *errorType = 6; + break; + case 16: + case 20: + case 21: + *errorType = 2; + break; + case 18: + *errorType = 1; + break; + default: + *errorType = 0; + break; + } + } + return stDwcLastError; +} + +void DWC_ClearError() { + if (stDwcLastError != DWCErrorFatal) { + stDwcLastError = DWCErrorNone; + stDwcErrorCode = 0; + } +} + +int DWCi_IsError() { return stDwcLastError != DWCErrorNone; } + +void DWCi_SetError(int lastError, int errorCode) { + if (stDwcLastError != DWCErrorFatal) { + stDwcLastError = lastError; + stDwcErrorCode = errorCode; + } +#ifdef DEBUG + if (stDwcLastError == DWCErrorFatal) + DWC_Printf(-1, "FATALERROR_SET\n"); +#endif +} + +#ifdef __cplusplus +} +#endif diff --git a/source/dwc/common/dwci_error.h b/source/dwc/common/dwci_error.h new file mode 100644 index 000000000..1a85becf8 --- /dev/null +++ b/source/dwc/common/dwci_error.h @@ -0,0 +1,27 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +enum dwcError { DWCErrorNone = 0, DWCErrorFatal = 9 }; + + +//! @brief @return Return if there is an error. +//! +int DWCi_IsError(); + +//! @brief Set the static error data. +//! +//! @details Cannot proceed if the last error before this function is called +//! is fatal. +//! +//! @param[in] lastError The last error encountered. +//! @param[in] errorCode Error code. +//! +void DWCi_SetError(int lastError, int errorCode); + +#ifdef __cplusplus +} +#endif diff --git a/source/dwc/dwci_debug.h b/source/dwc/dwci_debug.h new file mode 100644 index 000000000..520645a7c --- /dev/null +++ b/source/dwc/dwci_debug.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DEBUG +#ifndef DWCiAssert +#define DWCiAssert(exp, file, line, msg) \ + (void)((exp) || (OSPanic(file, line, msg), 0)) +#endif +#else +#ifndef DWCiAssert +#define DWCiAssert(exp, file, line, msg) ((void)0) +#endif +#endif + +#ifdef __cplusplus +} +#endif From de0443ea2d89838a2e2c56454ebe5de293958e17 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 29 Jan 2021 04:18:35 -0500 Subject: [PATCH 008/477] :construction: Decided on CWCC versions to use (sadly we're missing the most important one) --- build.py | 52 ++++++++++++++++++++++++++++++-------- tools/4199_60831/README.md | 0 tools/4201_142/README.md | 0 3 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 tools/4199_60831/README.md create mode 100644 tools/4201_142/README.md diff --git a/build.py b/build.py index 551bfd0f9..e6b2e5c45 100644 --- a/build.py +++ b/build.py @@ -1,5 +1,7 @@ import os +VERBOSE = False + DEVKITPPC = "C:\\devkitPro\\devkitPPC" GCC = os.path.join(DEVKITPPC, "bin\\powerpc-eabi-gcc.exe") @@ -12,8 +14,29 @@ SOURCE_PATH = "./source/" ASM_TEXT_PATH = "./asm/text/" BUILD_PATH = "./build/" -CWCC_OLD = True -CWCC_PATH = ".\\tools\\OLD_mwcceppc.exe" if CWCC_OLD else ".\\tools\\mwcceppc.exe" +CWCC_OLD = False + +CWCC_PATHS = { + 'default': ".\\tools\\4199_60831\\mwcceppc.exe", + + # For the main game + # August 17, 2007 + # 4.2.0.1 Build 127 + # + # Ideally we would use this version + # We don't have this, so we use build 142: + # This version has the infuriating bug where random + # nops are inserted into your code. + '4201_127': ".\\tools\\4201_142\\mwcceppc.exe", + + # For most of RVL + # We actually have the correct version + '4199_60831': ".\\tools\\4199_60831\\mwcceppc.exe", + + # For HBM/WPAD, NHTTP/SSL + # We use build 60831 + '4199_60726': '\\tools\\4199_60831\\mwcceppc.exe' +} CWCC_OPT = " ".join([ "-nodefaults", "-align powerpc", @@ -38,20 +61,29 @@ "-inline auto", "-w notinlined -W noimplicitconv", "-nostdinc", - "-msgstyle gcc -lang=c99 -DREVOKART" + "-msgstyle gcc -lang=c99 -DREVOKART", + "-func_align 4" ]) -def compile_source(src, dst): +def postprocess(dst): + command("python tools/postprocess.py -fsymbol-fixup %s" % dst) + +def compile_source(src, dst, version='default'): try: os.mkdir("tmp") except: pass - command = f"{CWCC_PATH} {CWCC_OPT if 'rx' not in src else CWCC_OPT.replace(',s', ',p')} {src} -o {dst}" - print(command) + command = f"{CWCC_PATHS[version]} {CWCC_OPT} {src} -o {dst}" + + if VERBOSE: + print(command) + os.system(command + " > ./tmp/compiler_log.txt") print(open("./tmp/compiler_log.txt").read().replace("source\\", "")) + # postprocess(dst) def command(cmd): - print(cmd) + if VERBOSE: + print(cmd) os.system(cmd) def assemble(dst, src): @@ -79,9 +111,9 @@ def build(): os.mkdir("out") except: pass - compile_source("source/rx/rxArchive.c", "out/rxArchive.o") - compile_source("source/rx/rxList.c", "out/rxList.o") - compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o") + compile_source("source/rx/rxArchive.c", "out/rxArchive.o", '4199_60831') + compile_source("source/rx/rxList.c", "out/rxList.o", '4199_60831') + compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831') for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) diff --git a/tools/4199_60831/README.md b/tools/4199_60831/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/tools/4201_142/README.md b/tools/4201_142/README.md new file mode 100644 index 000000000..e69de29bb From d5290d8a871ef2d39c628e69951106dccda76146 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 29 Jan 2021 04:41:05 -0500 Subject: [PATCH 009/477] :sparkles: Decompiled eggVector.cpp --- build.py | 1 + build/asm_files.txt | 15 +++++--- build/link.lcf | 8 ++++ build/o_files.txt | 16 +++++--- source/egg/math/eggMath.hpp | 34 +++++++++++++++++ source/egg/math/eggVector.cpp | 71 +++++++++++++++++++++++++++++++++++ source/egg/math/eggVector.hpp | 70 ++++++++++++++++++++++++++++++++++ 7 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 source/egg/math/eggMath.hpp create mode 100644 source/egg/math/eggVector.cpp create mode 100644 source/egg/math/eggVector.hpp diff --git a/build.py b/build.py index e6b2e5c45..9e54073ae 100644 --- a/build.py +++ b/build.py @@ -114,6 +114,7 @@ def build(): compile_source("source/rx/rxArchive.c", "out/rxArchive.o", '4199_60831') compile_source("source/rx/rxList.c", "out/rxList.o", '4199_60831') compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831') + compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127') for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) diff --git a/build/asm_files.txt b/build/asm_files.txt index 3ee20dce1..5fa93682e 100644 --- a/build/asm_files.txt +++ b/build/asm_files.txt @@ -5,16 +5,21 @@ asm/text_800ccc80.s asm/data_80275758.s asm/sdata_80384c00.s asm/text_80124e80.s +asm/text_80199d04.s +asm/ctors_80244de0.s +asm/bss_802a4080.s +asm/sbss_803862b0.s +asm/sdata2_80386fa0.s asm/init_80004000.s asm/extab_80006460.s asm/extabindex_80006a20.s -asm/text_80199d04.s -asm/ctors_80244de0.s +asm/text_80243d18.s +asm/ctors_80244e8c.s asm/dtors_80244ea4.s asm/rodata_80244ec0.s asm/data_8027e772.s -asm/bss_802a4080.s +asm/bss_80384bf4.s asm/sdata_803857f6.s -asm/sbss_803862b0.s -asm/sdata2_80386fa0.s +asm/sbss_80386f90.s +asm/sdata2_80389104.s asm/sbss2_80389140.s \ No newline at end of file diff --git a/build/link.lcf b/build/link.lcf index 1a0aa0a9e..d79dc53ec 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -37,6 +37,13 @@ _restgpr_26=0x800215E8; _restgpr_27=0x800215EC; OSReport=0x801A25D0; _current_locale=0x80271148; + +__register_global_object=0x80021338; +__dl__FPv=0x80229E14; +sqrt__Q23EGG5MathfFf=0x8022F80C; +frsqrt__Q23EGG5MathfFf=0x8022F85C; +__dt__Q23EGG8Vector3fFv=0x80009B40; +__dt__Q23EGG8Vector2fFv=0x80009B80; } FORCEFILES @@ -46,4 +53,5 @@ extabindex_80006a20.o dwc_error.o rxArchive.o rxList.o +eggVector.o } \ No newline at end of file diff --git a/build/o_files.txt b/build/o_files.txt index 1c8d9119d..b423f599b 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -8,16 +8,22 @@ sdata_80384c00.o rxArchive.o text_80124e80.o rxList.o +text_80199d04.o +ctors_80244de0.o +bss_802a4080.o +sbss_803862b0.o +sdata2_80386fa0.o +eggVector.o init_80004000.o extab_80006460.o extabindex_80006a20.o -text_80199d04.o -ctors_80244de0.o +text_80243d18.o +ctors_80244e8c.o dtors_80244ea4.o rodata_80244ec0.o data_8027e772.o -bss_802a4080.o +bss_80384bf4.o sdata_803857f6.o -sbss_803862b0.o -sdata2_80386fa0.o +sbss_80386f90.o +sdata2_80389104.o sbss2_80389140.o \ No newline at end of file diff --git a/source/egg/math/eggMath.hpp b/source/egg/math/eggMath.hpp new file mode 100644 index 000000000..f0a8ca0cd --- /dev/null +++ b/source/egg/math/eggMath.hpp @@ -0,0 +1,34 @@ +/*! + * @file + * @brief Provides a compile-time generic interface for performing basic math + * operations on a certain type. + */ + +#pragma once + +#include + +namespace EGG { + +#if 0 +template struct Math { + static T sqrt(T); + static T frsqrt(T); + + static T sin(T radians); + static T cos(T radians); + static T tan(T radians); + static T asin(T radians); + static T acos(T radians); + static T atan2(T num, T denom); +}; + +typedef Math Mathf; +#endif + +namespace Mathf { +float sqrt(float); +float frsqrt(float); +} // namespace Mathf + +} // namespace EGG diff --git a/source/egg/math/eggVector.cpp b/source/egg/math/eggVector.cpp new file mode 100644 index 000000000..7085d0650 --- /dev/null +++ b/source/egg/math/eggVector.cpp @@ -0,0 +1,71 @@ +/*! + * @file + * @brief TODO + */ + +#pragma once + +#include + +#define WEIRD_FLOAT (1.0f / 8388608.0f) + +namespace EGG { + +float Vector2f::normalise() { + float out = 0.0f; + float lenSq = x * x + y * y; + + if (lenSq > WEIRD_FLOAT) { + out = Mathf::sqrt(lenSq); + float inv = 1.0f / out; + x *= inv; + y *= inv; + } + + return out; +} +void Vector2f::normalise2() { + float lenSq = x * x + y * y; + + if (lenSq > WEIRD_FLOAT) { + float inv = Mathf::frsqrt(lenSq); + x *= inv; + y *= inv; + } +} + +float Vector3f::normalise() { + float out = 0.0f; + float lenSq = x * x + y * y + z * z; + + if (lenSq > WEIRD_FLOAT) { + out = Mathf::sqrt(lenSq); + float inv = 1.0f / out; + x *= inv; + y *= inv; + z *= inv; + } + + return out; +} +void Vector3f::normalise2() { + float lenSq = x * x + y * y + z * z; + + if (lenSq > WEIRD_FLOAT) { + float inv = Mathf::frsqrt(lenSq); + x *= inv; + y *= inv; + z *= inv; + } +} + +Vector2f Vector2f::zero = Vector2f(0.0f, 0.0f); +const Vector2f Vector2f::ex = Vector2f(1.0f, 0.0f); +const Vector2f Vector2f::ey = Vector2f(0.0f, 1.0f); + +const Vector3f Vector3f::zero = Vector3f(0.0f, 0.0f, 0.0f); +const Vector3f Vector3f::ex = Vector3f(1.0f, 0.0f, 0.0f); +const Vector3f Vector3f::ey = Vector3f(0.0f, 1.0f, 0.0f); +const Vector3f Vector3f::ez = Vector3f(0.0f, 0.0f, 1.0f); + +} // namespace EGG diff --git a/source/egg/math/eggVector.hpp b/source/egg/math/eggVector.hpp new file mode 100644 index 000000000..cba70d92c --- /dev/null +++ b/source/egg/math/eggVector.hpp @@ -0,0 +1,70 @@ +/*! + * @file + * @brief TODO + */ + +#pragma once + +#include + +namespace EGG { + +struct Vector2f { + float x; + float y; + + float normalise(); + // uses fsqrt directly + void normalise2(); + + static Vector2f zero; + // Basis vectors + static const Vector2f ex, ey; + + inline Vector2f(float _x, float _y) : x(_x), y(_y) {} + Vector2f(); + + // For now + ~Vector2f(); +}; + +struct Vector3f { + float x; + float y; + float z; + + float normalise(); + // uses fsqrt directly + void normalise2(); + + static const Vector3f zero; + // Basis vectors + static const Vector3f ex, ey, ez; + + // Header fns + + inline Vector3f(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} + inline Vector3f() : x(0.0f), y(0.0f), z(0.0f) {} + + // operator Vec*() { return reinterpret_cast(&x); } + + Vector3f operator-() const { return Vector3f(-x, -y, -z); } + Vector3f operator-(const Vector3f& rhs) const { + return Vector3f(x - rhs.x, y - rhs.y, z - rhs.y); + } + Vector3f operator+(const Vector3f& rhs) const { + return Vector3f(x + rhs.x, y + rhs.y, z + rhs.z); + } + Vector3f operator*(float scalar) const { + return Vector3f(x * scalar, y * scalar, z * scalar); + } + Vector3f cross(EGG::Vector3f& rhs) const { + return Vector3f(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z, + x * rhs.y - y * rhs.x); + } + + // for now + ~Vector3f(); +}; + +} // namespace EGG From 22fd027e9c31100ba9d4d4ada4fef12945151ba1 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 29 Jan 2021 12:38:08 -0500 Subject: [PATCH 010/477] :construction: Simplified build data (generate LCF + automatically gather asm files) --- build.py | 13 ++++++++++--- build/asm_files.txt | 25 ------------------------- build/link.lcf | 10 ---------- 3 files changed, 10 insertions(+), 38 deletions(-) delete mode 100644 build/asm_files.txt diff --git a/build.py b/build.py index 9e54073ae..528b56e0c 100644 --- a/build.py +++ b/build.py @@ -103,8 +103,8 @@ def make_obj(src): return src def build(): - asm_files = [x.strip() for x in open('build/asm_files.txt', 'r').readlines()] - # src_files = [x.strip() for x in open('build/src_files.txt', 'r').readlines()] + from pathlib import Path + asm_files = ["asm/" + str(x.stem) + ".s" for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] o_files = ["out/" + x.strip() for x in open('build/o_files.txt', 'r').readlines()] try: @@ -119,7 +119,14 @@ def build(): for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) - link('out/built.elf', o_files, "build/link.lcf") + lcf = open("build/link.lcf", 'r').read() + lcf += "\nFORCEFILES {\n" + lcf += "\n".join(x.replace("out/", "") for x in o_files) + lcf += "\n}\n" + + open('out/generated.lcf', 'w').write(lcf) + + link('out/built.elf', o_files, "out/generated.lcf") with open('out/built.elf', 'r+b') as elf: elf.seek(0x18) diff --git a/build/asm_files.txt b/build/asm_files.txt deleted file mode 100644 index 5fa93682e..000000000 --- a/build/asm_files.txt +++ /dev/null @@ -1,25 +0,0 @@ -asm/text_800072c0.s -asm/data_80258580.s -asm/sbss_80385fc0.s -asm/text_800ccc80.s -asm/data_80275758.s -asm/sdata_80384c00.s -asm/text_80124e80.s -asm/text_80199d04.s -asm/ctors_80244de0.s -asm/bss_802a4080.s -asm/sbss_803862b0.s -asm/sdata2_80386fa0.s -asm/init_80004000.s -asm/extab_80006460.s -asm/extabindex_80006a20.s -asm/text_80243d18.s -asm/ctors_80244e8c.s -asm/dtors_80244ea4.s -asm/rodata_80244ec0.s -asm/data_8027e772.s -asm/bss_80384bf4.s -asm/sdata_803857f6.s -asm/sbss_80386f90.s -asm/sdata2_80389104.s -asm/sbss2_80389140.s \ No newline at end of file diff --git a/build/link.lcf b/build/link.lcf index d79dc53ec..92375edd2 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -45,13 +45,3 @@ frsqrt__Q23EGG5MathfFf=0x8022F85C; __dt__Q23EGG8Vector3fFv=0x80009B40; __dt__Q23EGG8Vector2fFv=0x80009B80; } - -FORCEFILES -{ -extab_80006460.o -extabindex_80006a20.o -dwc_error.o -rxArchive.o -rxList.o -eggVector.o -} \ No newline at end of file From d2bdada63453d6a124ec6e93844d15c1dd837e11 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 29 Jan 2021 15:34:26 -0500 Subject: [PATCH 011/477] :recycle: RVL: updated to official symbols --- build.py | 4 +- build/o_files.txt | 4 +- source/{rx => rvl/arc}/binary_format.h | 31 +- .../{rx/rxArchive.c => rvl/arc/rvlArchive.c} | 144 +++++---- source/rvl/arc/rvlArchive.h | 201 ++++++++++++ source/rvl/mem/rvlMemList.c | 75 +++++ source/rvl/mem/rvlMemList.h | 37 +++ source/rx/rxArchive.hpp | 293 ------------------ source/rx/rxList.c | 72 ----- source/rx/rxList.h | 34 -- 10 files changed, 404 insertions(+), 491 deletions(-) rename source/{rx => rvl/arc}/binary_format.h (60%) rename source/{rx/rxArchive.c => rvl/arc/rvlArchive.c} (55%) create mode 100644 source/rvl/arc/rvlArchive.h create mode 100644 source/rvl/mem/rvlMemList.c create mode 100644 source/rvl/mem/rvlMemList.h delete mode 100644 source/rx/rxArchive.hpp delete mode 100644 source/rx/rxList.c delete mode 100644 source/rx/rxList.h diff --git a/build.py b/build.py index 528b56e0c..c8ca20bef 100644 --- a/build.py +++ b/build.py @@ -111,8 +111,8 @@ def build(): os.mkdir("out") except: pass - compile_source("source/rx/rxArchive.c", "out/rxArchive.o", '4199_60831') - compile_source("source/rx/rxList.c", "out/rxList.o", '4199_60831') + compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831') + compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831') compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831') compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127') diff --git a/build/o_files.txt b/build/o_files.txt index b423f599b..450e459e9 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -5,9 +5,9 @@ dwc_error.o text_800ccc80.o data_80275758.o sdata_80384c00.o -rxArchive.o +rvlArchive.o text_80124e80.o -rxList.o +rvlMemList.o text_80199d04.o ctors_80244de0.o bss_802a4080.o diff --git a/source/rx/binary_format.h b/source/rvl/arc/binary_format.h similarity index 60% rename from source/rx/binary_format.h rename to source/rvl/arc/binary_format.h index d23132876..d62387a56 100644 --- a/source/rx/binary_format.h +++ b/source/rvl/arc/binary_format.h @@ -1,12 +1,12 @@ #pragma once -#include // bool #include // expects +#include // bool void OSPanic(...); void OSReport(...); -struct rxNode { +struct rvlArchiveNode { union { struct { u32 is_folder : 8; @@ -28,11 +28,11 @@ struct rxNode { // Bitfield access of a packed_type_name X produces a pattern (X >> 24), which // does not match access done by masking. -#define rxNodeIsFolder(node) ((node).packed_type_name & 0xff000000) +#define rvlArchiveNodeIsFolder(node) ((node).packed_type_name & 0xff000000) // In some cases (X << 8 >> 8) does not collapse to a mask. -#define rxNodeGetName(node) ((node).packed_type_name & 0x00ffffff) +#define rvlArchiveNodeGetName(node) ((node).packed_type_name & 0x00ffffff) -struct rxArchiveHeader { +struct rvlArchiveHeader { u32 magic; // 00 struct { s32 offset; // 04 @@ -45,24 +45,25 @@ struct rxArchiveHeader { u8 _10[0x10]; }; -typedef struct rxNode rxNode; -typedef struct rxArchiveHeader rxArchiveHeader; +typedef struct rvlArchiveNode rvlArchiveNode; +typedef struct rvlArchiveHeader rvlArchiveHeader; -enum { RX_ARCHIVE_FILE_MAGIC = 0x55aa382d }; +enum { RVL_ARCHIVE_FILE_MAGIC = 0x55aa382d }; -static bool rxArchiveHeaderVerify(const rxArchiveHeader* self) { +static bool rvlArchiveHeaderVerify(const rvlArchiveHeader* self) { // Verify the "U8" magic - if (self->magic != RX_ARCHIVE_FILE_MAGIC) + if (self->magic != RVL_ARCHIVE_FILE_MAGIC) return false; return true; } -static const rxNode* rxArchiveHeaderGetNodes(const rxArchiveHeader* self) { - expects(self->nodes.offset > sizeof(rxArchiveHeader)); - return (const rxNode*)((u8*)self + self->nodes.offset); +static const rvlArchiveNode* +rvlArchiveHeaderGetNodes(const rvlArchiveHeader* self) { + expects(self->nodes.offset > sizeof(rvlArchiveHeader)); + return (const rvlArchiveNode*)((u8*)self + self->nodes.offset); } -static const u8* rxArchiveHeaderGetFileData(const rxArchiveHeader* self) { - expects(self->files.offset > sizeof(rxArchiveHeader)); +static const u8* rvlArchiveHeaderGetFileData(const rvlArchiveHeader* self) { + expects(self->files.offset > sizeof(rvlArchiveHeader)); return (const u8*)((u8*)self + self->files.offset); } diff --git a/source/rx/rxArchive.c b/source/rvl/arc/rvlArchive.c similarity index 55% rename from source/rx/rxArchive.c rename to source/rvl/arc/rvlArchive.c index 62fce31da..556fc0239 100644 --- a/source/rx/rxArchive.c +++ b/source/rvl/arc/rvlArchive.c @@ -1,24 +1,23 @@ -#include - -#include "binary_format.h" // rxArchiveHeader +#include "rvlArchive.h" +#include "binary_format.h" // rvlArchiveHeader #include // tolower -bool __rxGetCurrentPath(rxArchive* self, char* string, u32 capacity); +bool __rvlGetCurrentPath(rvlArchive* self, char* string, u32 capacity); // pData must be nonconst -bool rxArchiveOpen(void* pData, rxArchive* pOut) { +bool ARCInitHandle(void* pData, rvlArchive* pOut) { expects(pData != nullptr && pOut != nullptr); - const rxArchiveHeader* pHeader = (rxArchiveHeader*)pData; - if (!rxArchiveHeaderVerify(pHeader)) + const rvlArchiveHeader* pHeader = (rvlArchiveHeader*)pData; + if (!rvlArchiveHeaderVerify(pHeader)) ARCHIVE_THROW_INVALID_HEADER(); pOut->mHeader = pData; - const rxNode* nodes = rxArchiveHeaderGetNodes(pHeader); + const rvlArchiveNode* nodes = rvlArchiveHeaderGetNodes(pHeader); pOut->mNodes = nodes; - pOut->mFileData = rxArchiveHeaderGetFileData(pHeader); + pOut->mFileData = rvlArchiveHeaderGetFileData(pHeader); // The right bound of the root node is the number of nodes pOut->mCount = nodes[0].folder.sibling_next; // Strings exist directly after the last node. @@ -29,46 +28,45 @@ bool rxArchiveOpen(void* pData, rxArchive* pOut) { return true; } -bool rxArchiveFileOpen(const rxArchive* self, const char* path, - rxArchiveFile* pOut) { +bool ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut) { expects(self != nullptr && path != nullptr && pOut != nullptr); - const rxNode* nodes = self->mNodes; - const s32 resolved = rxArchiveResolve((rxArchive*)self, path); + const rvlArchiveNode* nodes = self->mNodes; + const s32 resolved = ARCConvertPathToEntrynum((rvlArchive*)self, path); - if (!rxEntryHandleIsValid(resolved)) { + if (!rvlArchiveEntryHandleIsValid(resolved)) { char current_path[128]; - __rxGetCurrentPath((rxArchive*)self, current_path, sizeof(current_path)); + __rvlGetCurrentPath((rvlArchive*)self, current_path, sizeof(current_path)); ARCHIVE_LOG_FILE_NOT_FOUND(path, current_path); return false; } - if (!rxEntryHandleIsValid(resolved) || rxNodeIsFolder(nodes[resolved])) { + if (!rvlArchiveEntryHandleIsValid(resolved) || + rvlArchiveNodeIsFolder(nodes[resolved])) { return false; } - pOut->parent = (rxArchive*)self; + pOut->parent = (rvlArchive*)self; pOut->offset = nodes[resolved].file.offset; pOut->size = nodes[resolved].file.size; return true; } -bool rxArchiveFileOpenLow(const rxArchive* self, s32 resolved, - rxArchiveFile* pOut) { +bool ARCFastOpen(const rvlArchive* self, s32 resolved, rvlArchiveFile* pOut) { expects(self != nullptr && pOut != nullptr); - const rxNode* nodes = self->mNodes; + const rvlArchiveNode* nodes = self->mNodes; // Although resolve will never return a handle beyond the node count, // the user may, so an additional check is needed. - if (!rxEntryHandleIsValid(resolved) || resolved >= self->mCount || - rxNodeIsFolder(nodes[resolved])) { + if (!rvlArchiveEntryHandleIsValid(resolved) || resolved >= self->mCount || + rvlArchiveNodeIsFolder(nodes[resolved])) { return false; } - pOut->parent = (rxArchive*)self; + pOut->parent = (rvlArchive*)self; pOut->offset = nodes[resolved].file.offset; pOut->size = nodes[resolved].file.size; @@ -76,7 +74,7 @@ bool rxArchiveFileOpenLow(const rxArchive* self, s32 resolved, } // seen in debug builds as a standalone function -inline int __rxPathCompare(const char* lhs, const char* rhs) { +inline int __rvlPathCompare(const char* lhs, const char* rhs) { while (rhs[0] != '\0') { if (tolower(*lhs++) != tolower(*rhs++)) return false; @@ -88,7 +86,7 @@ inline int __rxPathCompare(const char* lhs, const char* rhs) { return false; } -inline u32 __rxPathCopy(char* dst, char* src, u32 capacity) { +inline u32 __rvlPathCopy(char* dst, char* src, u32 capacity) { u32 i; for (i = capacity; i != 0 && src[0] != '\0'; --i) *dst++ = *src++; @@ -96,10 +94,10 @@ inline u32 __rxPathCopy(char* dst, char* src, u32 capacity) { return capacity - i; } -s32 rxArchiveResolve(rxArchive* self, const char* path) { - s32 name_length; // r7 - u32 it = self->mCurrentPath; // r8 - rxNode* nodes = (rxNode*)self->mNodes; // r9 +s32 ARCConvertPathToEntrynum(rvlArchive* self, const char* path) { + s32 name_length; // r7 + u32 it = self->mCurrentPath; // r8 + rvlArchiveNode* nodes = (rvlArchiveNode*)self->mNodes; // r9 while (true) { // End of string -> return what we have @@ -158,8 +156,10 @@ s32 rxArchiveResolve(rxArchive* self, const char* path) { ++it; while (it < nodes[anchor].folder.sibling_next) { while (true) { - if (rxNodeIsFolder(nodes[it]) || name_delimited_by_slash != true) { - char* name_of_it = ((char*)self->mStrings) + rxNodeGetName(nodes[it]); + if (rvlArchiveNodeIsFolder(nodes[it]) || + name_delimited_by_slash != true) { + char* name_of_it = + ((char*)self->mStrings) + rvlArchiveNodeGetName(nodes[it]); // Skip empty directories if (name_of_it[0] == '.' && name_of_it[1] == '\0') { @@ -168,12 +168,12 @@ s32 rxArchiveResolve(rxArchive* self, const char* path) { } // Advance to the next item in the path - if (__rxPathCompare(path, name_of_it) == true) { + if (__rvlPathCompare(path, name_of_it) == true) { goto descend; } } - if (rxNodeIsFolder(nodes[it])) { + if (rvlArchiveNodeIsFolder(nodes[it])) { it = nodes[it].folder.sibling_next; break; } @@ -196,36 +196,36 @@ s32 rxArchiveResolve(rxArchive* self, const char* path) { } } -static u32 __rxMakePathRecursive(rxArchive* self, u32 resolved, char* string, - u32 capacity) { - rxNode* nodes = (rxNode*)self->mNodes; +static u32 __rvlMakePathRecursive(rvlArchive* self, u32 resolved, char* string, + u32 capacity) { + rvlArchiveNode* nodes = (rvlArchiveNode*)self->mNodes; if (resolved == 0) return 0; char* name = ((char*)self->mStrings) + nodes[resolved].name; - u32 written = __rxMakePathRecursive(self, nodes[resolved].folder.parent, - string, capacity); + u32 written = __rvlMakePathRecursive(self, nodes[resolved].folder.parent, + string, capacity); if (written == capacity) return written; string[written++] = '/'; - return written + __rxPathCopy(string + written, name, capacity - written); + return written + __rvlPathCopy(string + written, name, capacity - written); } -inline bool __rxMakePath(rxArchive* self, s32 resolved, char* string, - u32 capacity) { - rxNode* nodes = (rxNode*)self->mNodes; +inline bool __rvlMakePath(rvlArchive* self, s32 resolved, char* string, + u32 capacity) { + rvlArchiveNode* nodes = (rvlArchiveNode*)self->mNodes; - u32 written = __rxMakePathRecursive(self, resolved, string, capacity); + u32 written = __rvlMakePathRecursive(self, resolved, string, capacity); if (written == capacity) { string[capacity - 1] = '\0'; return false; } - if (rxNodeIsFolder(nodes[resolved])) { + if (rvlArchiveNodeIsFolder(nodes[resolved])) { if (written == capacity - 1) { string[written] = '\0'; return false; @@ -238,58 +238,56 @@ inline bool __rxMakePath(rxArchive* self, s32 resolved, char* string, return true; } -inline bool __rxGetCurrentPath(rxArchive* self, char* string, u32 capacity) { - return __rxMakePath(self, self->mCurrentPath, string, capacity); +inline bool __rvlGetCurrentPath(rvlArchive* self, char* string, u32 capacity) { + return __rvlMakePath(self, self->mCurrentPath, string, capacity); } -void* rxArchiveFileGetData(rxArchiveFile* self) { +void* ARCGetStartAddrInMem(rvlArchiveFile* self) { return (char*)(self->parent->mHeader) + self->offset; } -#ifndef GALAXY -s32 rxArchiveFileGetOffset(const rxArchiveFile* self) { return self->offset; } -#endif // GALAXY +s32 ARCGetStartOffset(const rvlArchiveFile* self) { return self->offset; } -u32 rxArchiveFileGetSize(const rxArchiveFile* self) { return self->size; } +u32 ARCGetLength(const rvlArchiveFile* self) { return self->size; } -bool rxArchiveFileClose(rxArchiveFile* self) { return true; } +bool ARCClose(rvlArchiveFile* self) { return true; } -bool rxArchiveSetCurrentPath(rxArchive* self, const char* path) { - const s32 resolved = rxArchiveResolve((rxArchive*)self, path); - rxNode* nodes = (rxNode*)self->mNodes; +bool ARCChangeDir(rvlArchive* self, const char* path) { + const s32 resolved = ARCConvertPathToEntrynum((rvlArchive*)self, path); + rvlArchiveNode* nodes = (rvlArchiveNode*)self->mNodes; - if (!rxEntryHandleIsValid(resolved) || !rxNodeIsFolder(nodes[resolved])) + if (!rvlArchiveEntryHandleIsValid(resolved) || + !rvlArchiveNodeIsFolder(nodes[resolved])) return false; self->mCurrentPath = resolved; return true; } -bool rxArchiveFolderRangeOpen(const rxArchive* self, const char* path, - rxArchiveFolderRange* pOut) { - const s32 resolved = rxArchiveResolve((rxArchive*)self, path); - const rxNode* nodes = self->mNodes; +bool ARCOpenDir(const rvlArchive* self, const char* path, + rvlArchiveFolderRange* pOut) { + const s32 resolved = ARCConvertPathToEntrynum((rvlArchive*)self, path); + const rvlArchiveNode* nodes = self->mNodes; if (resolved < 0 || (nodes[resolved].packed_type_name & 0xff000000) == 0) return false; - pOut->parent = (rxArchive*)self; + pOut->parent = (rvlArchive*)self; pOut->path_begin = resolved; pOut->path_it = resolved + 1; pOut->path_end = nodes[resolved].folder.sibling_next; return true; } -bool rxArchiveFolderRangeNext(rxArchiveFolderRange* self, - rxArchiveEntry* pOut) { +bool ARCReadDir(rvlArchiveFolderRange* self, rvlArchiveEntry* pOut) { expects(self->parent != nullptr); - u32 it; // regalloc fun! - rxNode* nodes; // this must not be const lest regalloc change and the function - // grow in size + u32 it; // regalloc fun! + rvlArchiveNode* nodes; // this must not be const lest regalloc change and the + // function grow in size - rxArchive* arc = self->parent; - nodes = (rxNode*)arc->mNodes; + rvlArchive* arc = self->parent; + nodes = (rvlArchiveNode*)arc->mNodes; it = self->path_it; @@ -302,9 +300,9 @@ bool rxArchiveFolderRangeNext(rxArchiveFolderRange* self, pOut->path = it; // All non-file entries are folders. // Collapse to one specific value. - pOut->node_type = rxNodeIsFolder(nodes[it]) // - ? RX_ARCHIVE_FOLDER - : RX_ARCHIVE_FILE; + pOut->node_type = rvlArchiveNodeIsFolder(nodes[it]) // + ? RVL_ARCHIVE_FOLDER + : RVL_ARCHIVE_FILE; pOut->name = arc->mStrings + nodes[it].name; // skip '.' directories @@ -313,11 +311,11 @@ bool rxArchiveFolderRangeNext(rxArchiveFolderRange* self, continue; } - self->path_it = rxNodeIsFolder(nodes[it]) // + self->path_it = rvlArchiveNodeIsFolder(nodes[it]) // ? nodes[it].folder.sibling_next : it + 1; return true; } } -bool rxArchiveFolderRangeClose(rxArchiveFolderRange* self) { return true; } +bool ARCCloseDir(rvlArchiveFolderRange* self) { return true; } diff --git a/source/rvl/arc/rvlArchive.h b/source/rvl/arc/rvlArchive.h new file mode 100644 index 000000000..1b1eb611d --- /dev/null +++ b/source/rvl/arc/rvlArchive.h @@ -0,0 +1,201 @@ +#pragma once + +#include // u32 +#include // bool + +#ifdef __cplusplus +extern "C" { +#endif + +//! Describes the type of an entry in an archive. +//! +typedef enum rvlArchiveType { + //! The entry is a file. + //! Accessed via rvlArchiveFile with ARCOpen/ARCFastOpen + //! + RVL_ARCHIVE_FILE, + + //! The entry is a folder. + //! + RVL_ARCHIVE_FOLDER +} rvlArchiveType; + +//! A unique integer handle. +//! Negative if the operation failed. +//! For use with rvlArchiveFileOpenLow. +//! +typedef s32 rvlArchiveEntryHandle; + +//! Return if the handle returned by rvlArchiveResolve is valid. +//! +//! @note A handle may be plausibly valid, +//! although not valid for a specific archive. +//! +#define rvlArchiveEntryHandleIsValid(handle) ((handle) >= 0) + +//! Return if the handle is valid for a specific archive. +//! +#define rvlArchiveEntryHandleIsValidForArchive(handle, archive) \ + ((handle) >= 0 && (handle) < archive->mCount) + +//! A read-only view of an archive file. +//! +typedef struct rvlArchive { + const struct rvlArchiveHeader* mHeader; //!< 00 Pointer to the archive header. + const struct rvlArchiveNode* + mNodes; //!< 04 Array of nodes for each archive entry. + const u8* mFileData; //!< 08 File data buffer, accessed by nodes. + u32 mCount; //!< 0C Number of nodes in the archive. + const char* mStrings; //!< 10 String buffer, accessed by nodes. + u32 mFstSize; //!< 14 Total bytesize of nodes and strings. + u32 mCurrentPath; //!< 18 The current directory of the archive. +} rvlArchive; + +//! File info. +//! +typedef struct rvlArchiveFile { + rvlArchive* parent; //!< 00 The archive the file is a part of. + u32 offset; //!< 04 Offset into the archive's file data buffer. + u32 size; //!< 08 Size of the file. +} rvlArchiveFile; + +//! Info about a generic file or folder. +//! - If node_type is rvl_ARCHIVE_FILE, use rvlArchiveFileOpenLow to fill-in an +//! rvlArchiveFile. +//! - If node_type is rvl_ARCHIVE_FOLDER, use rvlArchiveFolderOpen to fill-in an +//! rvlArchiveFolderRange. +//! +typedef struct rvlArchiveEntry { + rvlArchive* parent; //!< 00 The archive the file is a part of. + u32 path; //!< 04 Special ID corresponding to a file path. + //!< Accepted by rvlArchiveFileOpenLow. + rvlArchiveType node_type; //!< The entry type. + const char* name; //!< 0C Name of this entry in specific. + //!< (Not an absolute path) +} rvlArchiveEntry; + +//! Contains an iterable range of the recursive contents of a folder. +//! +typedef struct rvlArchiveFolderRange { + rvlArchive* parent; //!< 00 The archive the file is a part of. + u32 path_begin; //!< 04 The lower bound of the iterator range. + u32 path_it; //!< 08 The current position of the iterator. + u32 path_end; //!< 0C The upper bound of the iterator range. +} rvlArchiveFolderRange; + +//! @brief Open an archive from the specified U8 data. +//! +//! @param[in] pData Pointer to the U8 data. +//! @param[out] pOut Pointer to the archive to create. +//! +//! @return If the operation succeeded. +//! +bool ARCInitHandle(void* pData, rvlArchive* pOut); + +//! @brief Resolve a string path to a unique integer +//! for use with ARCFastOpen. +//! +//! @param[in] self The archive. +//! @param[in] path Null-terminated string path. Forward slashes must be used. +//! +//! @return A unique integer handle. Negative if the operation failed. +//! +rvlArchiveEntryHandle ARCConvertPathToEntrynum(rvlArchive* self, + const char* path); + +//! @brief Set the current path of the archive to a new root for future access. +//! Similar to the "cd" command. +//! +//! @param[in] self The archive. +//! @param[in] path Null-terminated string path. Forward slashes must be used. +//! +//! @return If the operation succeeded. +//! +bool ARCChangeDir(rvlArchive* self, const char* path); + +//! @brief Open a file handle from an archive. +//! +//! @param[in] self The archive. +//! @param[in] path Null-terminated string path. Forward slashes must be used. +//! @param[out] pOut Pointer to an rvlArchiveFile to fill in. +//! +//! @return If the operation succeeded. +//! +bool ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut); + +//! @brief Open a file handle from an archive. +//! +//! @param[in] self The archive. +//! @param[in] path Direct entry handle, from rvlArchiveResolve. +//! @param[out] pOut Pointer to an rvlArchiveFile to fill in. +//! +//! @return If the operation succeeded. +//! +bool ARCFastOpen(const rvlArchive* self, rvlArchiveEntryHandle path, + rvlArchiveFile* pOut); + +//! @brief Get the file data from a file handle. +//! +//! @param[in] self Pointer to a file. +//! +//! @return A pointer to the beginning of the file data buffer. +//! +void* ARCGetStartAddrInMem(rvlArchiveFile* self); + +//! @brief Get the address translation of an archive subfile. +//! +//! @param[in] self Pointer to a file. +//! +//! @return The number of bytes in the archive file until the subfile begins. +//! +s32 ARCGetStartOffset(const rvlArchiveFile* self); + +//! @brief Get the filesize of a file handle. +//! +//! @param[in] self Pointer to a file. +//! +//! @return The size of the file data buffer, in bytes. +//! +u32 ARCGetLength(const rvlArchiveFile* self); + +//! @brief Close a file handle. +//! +//! @param[in] self Pointer to a file. +//! +//! @return If the operation succeeded. +//! +bool ARCClose(rvlArchiveFile* self); + +//! @brief Get an iterable range of the recursive contents of a folder. +//! +//! @param[in] self Pointer to an archive. +//! @param[in] path Folder path. +//! @param[out] pOut A folder range to fill in. +//! +//! @return If the operation succeeded: +//! - 1) The path exists and +//! - 2) is not a file. +//! +bool ARCOpenDir(const rvlArchive* self, const char* path, + rvlArchiveFolderRange* pOut); + +//! @brief Retrieve the next element in the iterator. +//! +//! @param[in] self The folder range. +//! @param[out] pOut The archive entry to fill in. +//! +//! @return False if the iterator has been exhausted. +//! +bool ARCReadDir(rvlArchiveFolderRange* self, rvlArchiveEntry* pOut); + +//! @brief Close the folder range handle. +//! +//! @param[in] self The folder range. +//! +//! @return If the operation succeeded. +//! +bool ARCCloseDir(rvlArchiveFolderRange* self); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/mem/rvlMemList.c b/source/rvl/mem/rvlMemList.c new file mode 100644 index 000000000..e61180b2f --- /dev/null +++ b/source/rvl/mem/rvlMemList.c @@ -0,0 +1,75 @@ +#include "rvlMemList.h" + +#ifdef __cplusplus +extern "C" { +#endif + +rvlMemIntrusiveList* MEMInitList(rvlMemIntrusiveList* pList, + u16 intrusion_offset) { + pList->intrusion_offset = intrusion_offset; + pList->head = nullptr; + pList->tail = nullptr; + pList->count = 0; + + return pList; +} + +rvlMemIntrusiveList* MEMAppendListObject(rvlMemIntrusiveList* pList, + void* pObj) { + if (pList->head == nullptr) { + rvlMemBiNode* node = rvlMemListGetNode(pList, pObj); + + node->succ = nullptr; + node->pred = nullptr; + + pList->head = pObj; + pList->tail = pObj; + ++pList->count; + + return pList; + } + + rvlMemBiNode* node = rvlMemListGetNode(pList, pObj); + + node->pred = pList->tail; + node->succ = nullptr; + + rvlMemListGetNode(pList, pList->tail)->succ = pObj; + pList->tail = pObj; + ++pList->count; + + return pList; +} + +rvlMemIntrusiveList* MEMRemoveListObject(rvlMemIntrusiveList* pList, + void* pObj) { + rvlMemBiNode* node = rvlMemListGetNode(pList, pObj); + + if (node->pred == nullptr) + pList->head = node->succ; + else + rvlMemListGetNode(pList, node->pred)->succ = node->succ; + + if (node->succ == nullptr) + pList->tail = node->pred; + else + rvlMemListGetNode(pList, node->succ)->pred = node->pred; + + node->pred = nullptr; + node->succ = nullptr; + + --pList->count; + + return pList; +} + +void* MEMGetNextListObject(rvlMemIntrusiveList* pList, void* pObj) { + if (pObj == nullptr) + return pList->head; + + return rvlMemListGetNode(pList, pObj)->succ; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/mem/rvlMemList.h b/source/rvl/mem/rvlMemList.h new file mode 100644 index 000000000..f1719000a --- /dev/null +++ b/source/rvl/mem/rvlMemList.h @@ -0,0 +1,37 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//! Bidirectional list node +typedef struct { + void* pred; + void* succ; +} rvlMemBiNode; + +// Unlike modern "std::list"-like structures, list nodes are directly inherited +// by children, which saves a level of indirection. +typedef struct { + void* head; + void* tail; + u16 count; + u16 intrusion_offset; +} rvlMemIntrusiveList; + +rvlMemIntrusiveList* MEMInitList(rvlMemIntrusiveList* pList, + u16 intrusion_offset); +rvlMemIntrusiveList* MEMAppendListObject(rvlMemIntrusiveList* pList, + void* pObj); +rvlMemIntrusiveList* MEMRemoveListObject(rvlMemIntrusiveList* pList, + void* pObj); +void* MEMGetNextListObject(rvlMemIntrusiveList* pList, void* pObj); + +#define rvlMemListGetNode(list, obj) \ + ((rvlMemBiNode*)(((char*)obj) + (list)->intrusion_offset)) + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rx/rxArchive.hpp b/source/rx/rxArchive.hpp deleted file mode 100644 index 0ac95ba76..000000000 --- a/source/rx/rxArchive.hpp +++ /dev/null @@ -1,293 +0,0 @@ -#pragma once - -#include // bool -#include // u32 - -#ifdef __cplusplus -extern "C" { -#endif - -//! Describes the type of an entry in an archive. -//! -typedef enum rxArchiveType { - //! The entry is a file. - //! Accessed via rxArchiveFile with rxArchiveFileOpen/rxArchiveFileOpenLow - //! - RX_ARCHIVE_FILE, - - //! The entry is a folder. - //! - RX_ARCHIVE_FOLDER -} rxArchiveType; - -//! A unique integer handle. -//! Negative if the operation failed. -//! For use with rxArchiveFileOpenLow. -//! -typedef s32 rxEntryHandle; - -//! Return if the handle returned by rxArchiveResolve is valid. -//! -//! @note A handle may be plausibly valid, -//! although not valid for a specific archive. -//! -#define rxEntryHandleIsValid(handle) ((handle) >= 0) - -//! Return if the handle is valid for a specific archive. -//! -#define rxEntryHandleIsValidForArchive(handle, archive) \ - ((handle) >= 0 && (handle) < archive->mCount) - -//! A read-only view of an archive file. -//! -typedef struct rxArchive { - const struct rxArchiveHeader* mHeader; //!< 00 Pointer to the archive header. - const struct rxNode* mNodes; //!< 04 Array of nodes for each archive entry. - const u8* mFileData; //!< 08 File data buffer, accessed by nodes. - u32 mCount; //!< 0C Number of nodes in the archive. - const char* mStrings; //!< 10 String buffer, accessed by nodes. - u32 mFstSize; //!< 14 Total bytesize of nodes and strings. - u32 mCurrentPath; //!< 18 The current directory of the archive. -} rxArchive; - -//! File info. -//! -typedef struct rxArchiveFile { - rxArchive* parent; //!< 00 The archive the file is a part of. - u32 offset; //!< 04 Offset into the archive's file data buffer. - u32 size; //!< 08 Size of the file. -} rxArchiveFile; - -//! Info about a generic file or folder. -//! - If node_type is RX_ARCHIVE_FILE, use rxArchiveFileOpenLow to fill-in an -//! rxArchiveFile. -//! - If node_type is RX_ARCHIVE_FOLDER, use rxArchiveFolderOpen to fill-in an -//! rxArchiveFolderRange. -//! -typedef struct rxArchiveEntry { - rxArchive* parent; //!< 00 The archive the file is a part of. - u32 path; //!< 04 Special ID corresponding to a file path. - //!< Accepted by rxArchiveFileOpenLow. - rxArchiveType node_type; //!< The entry type. - const char* name; //!< 0C Name of this entry in specific. - //!< (Not an absolute path) -} rxArchiveEntry; - -//! Contains an iterable range of the recursive contents of a folder. -//! -typedef struct rxArchiveFolderRange { - rxArchive* parent; //!< 00 The archive the file is a part of. - u32 path_begin; //!< 04 The lower bound of the iterator range. - u32 path_it; //!< 08 The current position of the iterator. - u32 path_end; //!< 0C The upper bound of the iterator range. -} rxArchiveFolderRange; - -//! @brief Open an archive from the specified U8 data. -//! -//! @param[in] pData Pointer to the U8 data. -//! @param[out] pOut Pointer to the archive to create. -//! -//! @return If the operation succeeded. -//! -bool rxArchiveOpen(void* pData, rxArchive* pOut); - -//! @brief Resolve a string path to a unique integer -//! for use with rxArchiveFileOpenLow. -//! -//! @param[in] self The archive. -//! @param[in] path Null-terminated string path. Forward slashes must be used. -//! -//! @return A unique integer handle. Negative if the operation failed. -//! -rxEntryHandle rxArchiveResolve(rxArchive* self, const char* path); - -//! @brief Set the current path of the archive to a new root for future access. -//! Similar to the "cd" command. -//! -//! @param[in] self The archive. -//! @param[in] path Null-terminated string path. Forward slashes must be used. -//! -//! @return If the operation succeeded. -//! -bool rxArchiveSetCurrentPath(rxArchive* self, const char* path); - -//! @brief Open a file handle from an archive. -//! -//! @param[in] self The archive. -//! @param[in] path Null-terminated string path. Forward slashes must be used. -//! @param[out] pOut Pointer to an rxArchiveFile to fill in. -//! -//! @return If the operation succeeded. -//! -bool rxArchiveFileOpen(const rxArchive* self, const char* path, - rxArchiveFile* pOut); - -//! @brief Open a file handle from an archive. -//! -//! @param[in] self The archive. -//! @param[in] path Direct entry handle, from rxArchiveResolve. -//! @param[out] pOut Pointer to an rxArchiveFile to fill in. -//! -//! @return If the operation succeeded. -//! -bool rxArchiveFileOpenLow(const rxArchive* self, rxEntryHandle path, - rxArchiveFile* pOut); - -//! @brief Get the file data from a file handle. -//! -//! @param[in] self Pointer to a file. -//! -//! @return A pointer to the beginning of the file data buffer. -//! -void* rxArchiveFileGetData(rxArchiveFile* self); - -//! @brief Get the address translation of an archive subfile. -//! -//! @param[in] self Pointer to a file. -//! -//! @return The number of bytes in the archive file until the subfile begins. -//! -s32 rxArchiveFileGetOffset(const rxArchiveFile* self); - -//! @brief Get the filesize of a file handle. -//! -//! @param[in] self Pointer to a file. -//! -//! @return The size of the file data buffer, in bytes. -//! -u32 rxArchiveFileGetSize(const rxArchiveFile* self); - -//! @brief Close a file handle. -//! -//! @param[in] self Pointer to a file. -//! -//! @return If the operation succeeded. -//! -bool rxArchiveFileClose(rxArchiveFile* self); - -//! @brief Get an iterable range of the recursive contents of a folder. -//! -//! @param[in] self Pointer to an archive. -//! @param[in] path Folder path. -//! @param[out] pOut A folder range to fill in. -//! -//! @return If the operation succeeded: -//! - 1) The path exists and -//! - 2) is not a file. -//! -bool rxArchiveFolderRangeOpen(const rxArchive* self, const char* path, - rxArchiveFolderRange* pOut); - -//! @brief Retrieve the next element in the iterator. -//! -//! @param[in] self The folder range. -//! @param[out] pOut The archive entry to fill in. -//! -//! @return False if the iterator has been exhausted. -//! -bool rxArchiveFolderRangeNext(rxArchiveFolderRange* self, rxArchiveEntry* pOut); - -//! @brief Close the folder range handle. -//! -//! @param[in] self The folder range. -//! -//! @return If the operation succeeded. -//! -bool rxArchiveFolderRangeClose(rxArchiveFolderRange* self); - -#ifdef GALAXY -inline s32 rxArchiveFileGetOffset(const rxArchiveFile* self) { - return self->offset; -} -#endif // GALAXY - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus - -namespace rx { - -template class GenericEntry : private rxArchiveEntry { -public: - bool isFile() const { return node_type == RX_ARCHIVE_FILE; } - bool isFolder() const { return node_type == RX_ARCHIVE_FOLDER; } - - T* getParent() { return reinterpret_cast(parent); } - const T* getParent() const { return reinterpret_cast(parent); } - u32 getPath() const { return path; } - const char* getName() const { return name; } -}; - -namespace low { - -//! Low-level archive wrapper. Directly maps to C functions. -//! -class Archive : private rxArchive { -public: - bool open(const void* pData) { - return rxArchiveOpen(const_cast(pData), this); - } - s32 resolve(const char* path) const { - return rxArchiveResolve(const_cast(this), path); - } - bool setCurrentPath(const char* path) { - return rxArchiveSetCurrentPath(path); - } - - class File : private rxArchiveFile { - public: - bool open(const Archive* parent, const char* path) { - return rxArchiveFileOpen(parent, path, this); - } - bool open(const Archive* parent, s32 path) { - return rxArchiveFileOpenLow(parent, path, this); - } - void* getData() { return rxArchiveFileGetData(this); } - const void* getData() const { - return rxArchiveFileGetData(const_cast(this)); - } - u32 getSize() const { return rxArchiveFileGetSize(this); } - bool close() { return rxArchiveFileClose(this); } - }; - - class FolderRange : private rxArchiveFolderRange { - public: - bool open(const Archive* parent, const char* path) { - rxArchiveFolderRangeOpen(parent, path, this); - } - bool next(GenericEntry& out) { - rxArchiveFolderRangeNext(this, &out); - } - bool close() { return rxArchiveFolderRangeClose(this); } - }; -}; -} // namespace low - -//! High-level archive wrapper. Follows RAII principles. -//! -class Archive : private low::Archive { -public: - Archive(const void* pData) { open(pData); } - - class File : public low::Archive::File { - public: - explicit File(const Archive* parent, const char* path) { open(path); } - explicit File(const Archive* parent, s32 path) { open(path); } - ~File() { close(); } - }; - - class FolderRange : private low::Archive::FolderRange { - FolderRange(const Archive* parent, const char* path) { open(parent, path); } - ~FolderRange() { close(); } - bool next(GenericEntry& out) { - return rx::low::Archive::FolderRange::next( - reinterpret_cast>(out)); - } - }; -} - -} // namespace rx - -#endif // __cplusplus diff --git a/source/rx/rxList.c b/source/rx/rxList.c deleted file mode 100644 index 53742ff24..000000000 --- a/source/rx/rxList.c +++ /dev/null @@ -1,72 +0,0 @@ -#include "rxList.h" - -#ifdef __cplusplus -extern "C" { -#endif - -rxIntrusiveList* rxInitList(rxIntrusiveList* pList, u16 intrusion_offset) { - pList->intrusion_offset = intrusion_offset; - pList->head = nullptr; - pList->tail = nullptr; - pList->count = 0; - - return pList; -} - -rxIntrusiveList* rxListAppend(rxIntrusiveList* pList, void* pObj) { - if (pList->head == nullptr) { - rxBiNode* node = rxListGetNode(pList, pObj); - - node->succ = nullptr; - node->pred = nullptr; - - pList->head = pObj; - pList->tail = pObj; - ++pList->count; - - return pList; - } - - rxBiNode* node = rxListGetNode(pList, pObj); - - node->pred = pList->tail; - node->succ = nullptr; - - rxListGetNode(pList, pList->tail)->succ = pObj; - pList->tail = pObj; - ++pList->count; - - return pList; -} - -rxIntrusiveList* rxListRemove(rxIntrusiveList* pList, void* pObj) { - rxBiNode* node = rxListGetNode(pList, pObj); - - if (node->pred == nullptr) - pList->head = node->succ; - else - rxListGetNode(pList, node->pred)->succ = node->succ; - - if (node->succ == nullptr) - pList->tail = node->pred; - else - rxListGetNode(pList, node->succ)->pred = node->pred; - - node->pred = nullptr; - node->succ = nullptr; - - --pList->count; - - return pList; -} - -void* rxListGetSuccessorOf(rxIntrusiveList* pList, void* pObj) { - if (pObj == nullptr) - return pList->head; - - return rxListGetNode(pList, pObj)->succ; -} - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/source/rx/rxList.h b/source/rx/rxList.h deleted file mode 100644 index e876146ff..000000000 --- a/source/rx/rxList.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -//! Bidirectional list node -typedef struct { - void* pred; - void* succ; -} rxBiNode; - -// Unlike modern "std::list"-like structures, list nodes are directly inherited -// by children, which saves a level of indirection. -typedef struct { - void* head; - void* tail; - u16 count; - u16 intrusion_offset; -} rxIntrusiveList; - -rxIntrusiveList* rxInitList(rxIntrusiveList* pList, u16 intrusion_offset); -rxIntrusiveList* rxListAppend(rxIntrusiveList* pList, void* pObj); -rxIntrusiveList* rxListRemove(rxIntrusiveList* pList, void* pObj); -void* rxListGetSuccessorOf(rxIntrusiveList* pList, void* pObj); - -#define rxListGetNode(list, obj) \ - ((rxBiNode*)(((char*)obj) + (list)->intrusion_offset)) - -#ifdef __cplusplus -} -#endif \ No newline at end of file From e753234068dd350db0de358d80c408d62b844cda Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 29 Jan 2021 17:22:10 -0500 Subject: [PATCH 012/477] :sparkles: eggStreamDecomp.cpp --- build.py | 1 + build/link.lcf | 4 +++ build/o_files.txt | 5 +++- source/egg/core/eggStreamDecomp.cpp | 27 ++++++++++++++++++++ source/egg/core/eggStreamDecomp.hpp | 39 +++++++++++++++++++++++++++++ source/rvl/cx/cxLz.h | 20 +++++++++++++++ 6 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 source/egg/core/eggStreamDecomp.cpp create mode 100644 source/egg/core/eggStreamDecomp.hpp create mode 100644 source/rvl/cx/cxLz.h diff --git a/build.py b/build.py index c8ca20bef..0fe4ba4b7 100644 --- a/build.py +++ b/build.py @@ -114,6 +114,7 @@ def build(): compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831') compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831') compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831') + compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127') compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127') for asm in asm_files: diff --git a/build/link.lcf b/build/link.lcf index 92375edd2..30afc5bb0 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -44,4 +44,8 @@ sqrt__Q23EGG5MathfFf=0x8022F80C; frsqrt__Q23EGG5MathfFf=0x8022F85C; __dt__Q23EGG8Vector3fFv=0x80009B40; __dt__Q23EGG8Vector2fFv=0x80009B80; + +CXInitUncompContextLZ=0x8015BEF0; +CXReadUncompLZ=0x8015BF24; +CXGetUncompressedSize=0x8015C2E0; } diff --git a/build/o_files.txt b/build/o_files.txt index 450e459e9..af861c7f3 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -9,6 +9,9 @@ rvlArchive.o text_80124e80.o rvlMemList.o text_80199d04.o +data_8027e772.o +eggStreamDecomp.o +text_80242504.o ctors_80244de0.o bss_802a4080.o sbss_803862b0.o @@ -21,7 +24,7 @@ text_80243d18.o ctors_80244e8c.o dtors_80244ea4.o rodata_80244ec0.o -data_8027e772.o +data_802a3f90.o bss_80384bf4.o sdata_803857f6.o sbss_80386f90.o diff --git a/source/egg/core/eggStreamDecomp.cpp b/source/egg/core/eggStreamDecomp.cpp new file mode 100644 index 000000000..f9be0c7a1 --- /dev/null +++ b/source/egg/core/eggStreamDecomp.cpp @@ -0,0 +1,27 @@ +/*! + * @file + * @brief Implementations for the EGG streaming decompressor headers. + */ + +#include "eggStreamDecomp.hpp" + +namespace EGG { + +bool LZStreamDecomp::initialize(void* dst, unk arg3) { + mpDst = dst; + _08 = arg3; + CXInitUncompContextLZ(&mContext, dst); + return true; +} + +bool LZStreamDecomp::process(const void* src, u32 len) { + return CXReadUncompLZ(&mContext, src, len) == CXResultSuccess; +} + +u32 LZStreamDecomp::getDataOffset() { return 32; } + +u32 LZStreamDecomp::getExpandSize(const void* src) { + return CXGetUncompressedSize(src); +} + +} // namespace EGG \ No newline at end of file diff --git a/source/egg/core/eggStreamDecomp.hpp b/source/egg/core/eggStreamDecomp.hpp new file mode 100644 index 000000000..a856261fe --- /dev/null +++ b/source/egg/core/eggStreamDecomp.hpp @@ -0,0 +1,39 @@ +/*! + * @file + * @brief Wrappers for the CX streaming decompressors. + * @remarks While NSMBW's StreamDecomp had all CX decompressors and SZS + decompression linked, MKW only links the LZ decompressor. + */ + +#pragma once + +#include +#include + +namespace EGG { + +//! @brief Interface for streamed decompression +//! +class IStreamDecomp { +public: + virtual bool initialize(void* dst, unk arg3) = 0; + virtual bool process(const void* src, u32 len) = 0; + virtual u32 getDataOffset() = 0; + virtual u32 getExpandSize(const void* src) = 0; +}; + +class LZStreamDecomp : public IStreamDecomp { +public: + bool initialize(void* dst, unk arg3) override; + bool process(const void* src, u32 len) override; + u32 getDataOffset() override; + u32 getExpandSize(const void* src) override; + +private: + void* mpDst; //!< [+0x04] Pointer to the decompressed destination buffer. + unk _08; //!< [+0x08] Second argument of constructor -- unused. + CXUncompContextLZ + mContext; //!< [+0x0C] CX streaming LZ decompression context. +}; + +} // namespace EGG \ No newline at end of file diff --git a/source/rvl/cx/cxLz.h b/source/rvl/cx/cxLz.h new file mode 100644 index 000000000..09cbdb5fb --- /dev/null +++ b/source/rvl/cx/cxLz.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct CXUncompContextLZ { + u8 _[24]; +}; +enum CXResult { CXResultSuccess = 0 }; + +void CXInitUncompContextLZ(CXUncompContextLZ* pCtx, void* pDst); +CXResult CXReadUncompLZ(CXUncompContextLZ* pCtx, const void* pSrc, u32 len); +u32 CXGetUncompressedSize(const void* pBinary); + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 92cfd4bf2a070b503a1b16a238918842b1f069dd Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 29 Jan 2021 17:33:01 -0500 Subject: [PATCH 013/477] :sparkles: eggDisposer.cpp --- build.py | 1 + build/link.lcf | 5 + build/o_files.txt | 3 + source/egg/core/eggDisposer.cpp | 23 +++ source/egg/core/eggDisposer.hpp | 34 +++++ source/egg/core/eggHeap.hpp | 238 ++++++++++++++++++++++++++++++++ source/nw4r/ut/utList.hpp | 31 +++++ source/rvl/os/osThread.h | 13 ++ 8 files changed, 348 insertions(+) create mode 100644 source/egg/core/eggDisposer.cpp create mode 100644 source/egg/core/eggDisposer.hpp create mode 100644 source/egg/core/eggHeap.hpp create mode 100644 source/nw4r/ut/utList.hpp create mode 100644 source/rvl/os/osThread.h diff --git a/build.py b/build.py index 0fe4ba4b7..7852e5d26 100644 --- a/build.py +++ b/build.py @@ -114,6 +114,7 @@ def build(): compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831') compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831') compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831') + compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127') compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127') compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127') diff --git a/build/link.lcf b/build/link.lcf index 30afc5bb0..e6c42f4dd 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -48,4 +48,9 @@ __dt__Q23EGG8Vector2fFv=0x80009B80; CXInitUncompContextLZ=0x8015BEF0; CXReadUncompLZ=0x8015BF24; CXGetUncompressedSize=0x8015C2E0; + +List_Remove__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AF110; +List_Append__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AEF80; + +findContainHeap__Q23EGG4HeapFPCv=0x80229ADC; } diff --git a/build/o_files.txt b/build/o_files.txt index af861c7f3..c3af0df75 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -10,6 +10,9 @@ text_80124e80.o rvlMemList.o text_80199d04.o data_8027e772.o +eggDisposer.o +text_8021a1b8.o +data_802a2b54.o eggStreamDecomp.o text_80242504.o ctors_80244de0.o diff --git a/source/egg/core/eggDisposer.cpp b/source/egg/core/eggDisposer.cpp new file mode 100644 index 000000000..d60df5d1f --- /dev/null +++ b/source/egg/core/eggDisposer.cpp @@ -0,0 +1,23 @@ +/** + * @file + * @brief Disposer implementations. + */ + +#include "eggDisposer.hpp" +#include "eggHeap.hpp" + +namespace EGG { + +Disposer::Disposer() { + mContainHeap = Heap::findContainHeap(this); + + if (mContainHeap) + mContainHeap->appendDisposer(this); +} + +Disposer::~Disposer() { + if (mContainHeap) + mContainHeap->removeDisposer(this); +} + +} // namespace EGG diff --git a/source/egg/core/eggDisposer.hpp b/source/egg/core/eggDisposer.hpp new file mode 100644 index 000000000..c73234ab5 --- /dev/null +++ b/source/egg/core/eggDisposer.hpp @@ -0,0 +1,34 @@ +/** + * @file + * @brief Disposer. + */ + +#pragma once + +namespace EGG { + +class Heap; + +//--------------------------------------------------------------------------- +//! @brief Interface for objects that can be destroyed. +//! +//! @details Has a virtual table (with virtual destructor) and pointer to +//! containing heap. +//--------------------------------------------------------------------------- +class Disposer { +public: + //! @brief Disposer destructor. If a containing heap is set, unregister self + //! from its children. + //! + virtual ~Disposer(); + + //! @brief Disposer constructor. If a containing heap is set, register self to + //! its children. + //! + Disposer(); + + Heap* mContainHeap; //!< [+0x04] Heap that contains the instance of this + //!< disposer. +}; + +} // namespace EGG diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp new file mode 100644 index 000000000..63d5b180b --- /dev/null +++ b/source/egg/core/eggHeap.hpp @@ -0,0 +1,238 @@ +/** + * @file + * @brief Base Heap class. + */ + +#pragma once + +#include +#include +#include +#include + +namespace EGG { + +class ExpHeap; +class Allocator; + +//! @brief Base Heap class +//! +//! @details EGG::Heap is not concrete and only acts as an interface for actual +//! implementations. +//! +class Heap : public Disposer { +public: + //! @brief Types of heaps that can be created + //! + enum eHeapKind { + HEAP_KIND_NONE = 0, //!< [0] Unconfirmed! + HEAP_KIND_EXPANDED, //!< [1] Expanded heap. + HEAP_KIND_FRAME, //!< [2] Frame heap. Doesn't exist in MKW's EGG. + HEAP_KIND_UNIT //!< [3] Unit heap. + }; + + inline bool isExpHeap() { return getHeapKind() == HEAP_KIND_EXPANDED; } + + virtual ~Heap(); + //! @brief [vt+0x0C] Get the type of heap the current heap is. + //! + //! @returns The eHeapKing of the heap. + //! + virtual eHeapKind getHeapKind() const = 0; + + virtual void initAllocator(Allocator* allocator, s32 align) = 0; // [vt+0x10] + virtual void* alloc(u32 size, s32 align) = 0; // [vt+0x14] + virtual void free(void* block) = 0; // [vt+0x18] + virtual void destroy() = 0; // [vt+0x1C] + virtual u32 resizeForMBlock(void* block, u32 size) = 0; // [vt+0x20] + virtual u32 getAllocatableSize(s32 align) = 0; // [vt+0x24] + virtual u32 adjust() = 0; // [vt+0x28] + + //! @brief Static linked-list of heaps. + //! + //! @details When a heap is created, it is appended to this list. + //! When it is deleted, it is removed. + //! + static nw4r::ut::List sHeapList; + + //! @brief Root heap mutex. + //! + static OSMutex sRootMutex; + + //! @brief When the heap passed to alloc is NULL, use this instead. + static Heap* sCurrentHeap; + + //! @brief Whether or not the static list of heaps has been initialized. + //! @see initialize + static int sIsHeapListInitialized; + + //! @brief When non NULL, this heap MUST be used for heap allocations. + //! This will restrict rather than redirect allocations. + static Heap* sAllocatableHeap; + static void* sErrorCallback; //!< TODO + static void* sAllocCallback; //!< TODO + static void* sErrorCallbackArg; //!< TODO + static void* sAllocCallbackArg; //!< TODO + static struct Thread* sAllocatableThread; //!< TODO +public: + //! @brief [+0x08, +0x0c] unseen + u32 _08; + u32 _0C; + //! @brief [+0x10] argument of heap constructor. Name confirmed by WS assert. + struct rvlHeap* mHeapHandle; + //! @brief [+0x14] set to 0 in heap ctor. treeki -- void* parentHeapMBlock + void* mParentBlock; + //! @brief [+0x18] name from findParentHeap() + Heap* mParentHeap; + //! @brief [+0x1C] Values of HeapFlag + enum HeapFlag { + // tstDisableAllocation, enableAllocation, disableAllocation + // setBit__Q23EGG12TBitFlagFUc + HEAP_FLAG_LOCKED = (1 << 0) + }; + u16 mFlag; + // 2b implicit pad? + //! [+0x20, +0x24] unseen treeki -- globalLink + u32 _20; + u32 _24; + //! @details List of child disposers. + //! When Heap::dispose() is called, ~Disposer() will be called for all + //! children. + nw4r::ut::List mChildren; //!< [+0x28] sizeof=0xC + const char* mName; //!< [+0x034] set to "NoName" in ctor + +public: + //! @brief Must be called before heaps are created. Prepares static heap + //! members. + //! + //! @details Initializes the static heap data for heap creation. + //! + //! @post + //! - sHeapList is initialized. + //! - sRootMutex is initialized. + //! - sIsHeapListInitialized is true. + //! + //! + static void initialize(); + + //! @brief Heap constructor. + //! + //! @details Creates a Heap wrapper around the supplied MEM heap. + //! - Calls down to the parent Disposer constructor + //! before setting values. + //! - Parent members _14, _18, _1C are zeroed; mName + //! is set to "NoName". + //! - ut::List at _28 initialized. + //! - This heap is added to the global heap list + //!(sHeapList). + //! + //! @param[in] heapHandle: MEM heap handle to create EGG::Heap wrapper + //! around. + //! + //! @pre initialize() has been called. (WS asserts + //! sHeapInitialized) + //! + //! @return this + //! + Heap(struct rvlHeapHandle* heapHandle); + + //! @brief Allocate a block of memory in a heap. + //! + //! @details TODO + //! + //! @param[in] size: Size of the block in bytes. + //! @param[in] align: Alignment of block to create. + //! @param[in] heap: Heap to allocate block in. + //! + //! @return The address of the allocated block. + //! @retval NULL: The allocation failed. + //! + static void* alloc(u32 size, s32 align, Heap* heap); + + //! @brief Returns the Disposer's parent heap. + //! + //! @details Initializes the static heap data for heap creation. + //! + //! @pre mHeapHandle != NULL. (WS assert) + //! + Heap* findParentHeap(); + + //! @brief Scan global heap list for heap containing specified memory + //! block. + //! + //! @details First runs MEMFindContainHeap() to determine if it is in a MEM + //! heap. + //! If this check passes, it will iterate through + //! sHeapList, checking if the heap in each + //! iteration's _10 matches the MEM heap found before. + //! + //! @param[in] memBlock: Memory block to look scan for container. + //! + //! @pre sHeapList is intialized. (sIsHeapListInitialized) + //! + //! @return The Heap that contains the memory block. + //! @retval nullptr: The memory block is not contained in any heap + //! + static Heap* findContainHeap(const void* memBlock); + + //! @brief Free a block of memory from a heap. + //! + //! @details Safely calls heap virtual free function on a memory block. + //! Memory contained in a non EGG heap cannot be used. + //! + //! @param[in] memBlock Block of memory to free. + //! @param[in] heap Heap of memBlock to use for free. If NULL, it will be + //! rederived. + //! + //! @pre If no heap is provided, the global heap list must be + //! initialized. + //! + static void free(void* memBlock, Heap* heap); + + //! @brief Destroy all child heaps. + //! + //! @details Calls destructor on first heap in child heap list until there + //! are no children. + //! + void dispose(); + + static void dumpAll(); + + //! @brief Let this heap become the current heap. + //! + //! @details Sets the static Heap sCurrentHeap to this. + //! + //! @pre sRootMutex is intialized. + //! + //! @return The previous current heap. + //! + Heap* becomeCurrentHeap(); + +public: + inline void appendDisposer(Disposer* disposer) { + nw4r::ut::List_Append(&mChildren, disposer); + } + inline void removeDisposer(Disposer* disposer) { + nw4r::ut::List_Remove(&mChildren, disposer); + } +}; + +} // namespace EGG + +typedef unsigned long size_t; + +void* operator new(size_t size); +// __nwa(ulong, ulong) +void* operator new[](size_t size, u32 align); +// __nw(ulong, EGG::Heap *, int) +void* operator new(size_t size, EGG::Heap* heap, int align); +// __nwa(ulong) +void* operator new[](size_t size); +// __nwa(ulong, int) +void* operator new[](size_t size, int align); +// __nwa(ulong, EGG::Heap *, int) +void* operator new[](size_t size, EGG::Heap* heap, int align); +// __dl(void *) +void operator delete(void* p); +// __dla(void *) +void operator delete[](void*); diff --git a/source/nw4r/ut/utList.hpp b/source/nw4r/ut/utList.hpp new file mode 100644 index 000000000..8e1d6df0f --- /dev/null +++ b/source/nw4r/ut/utList.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +// clang-format off +namespace nw4r { namespace ut { +// clang-format on + +//! Bidirectional list node +struct Node { + void* pred; + void* succ; +}; + +// Unlike modern "std::list"-like structures, list nodes are directly inherited +// by children, which saves a level of indirection. +struct List { + void* head; + void* tail; + u16 count; + u16 intrusion_offset; +}; + +// +void List_Init(List* pList, u16 intrusion_offset); +void List_Append(List* pList, void* pObj); +void List_Remove(List* pList, void* pObj); + +// clang-format off +} } // namespace nw4r +// clang-format on diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h new file mode 100644 index 000000000..98edffd10 --- /dev/null +++ b/source/rvl/os/osThread.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char _[0x18]; +} OSMutex; + +#ifdef __cplusplus +} +#endif \ No newline at end of file From bcae7e98a631a3b566af79855976a35e86ec768e Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 30 Jan 2021 05:13:12 -0500 Subject: [PATCH 014/477] :sparkles: Started eggHeap.cpp --- build.py | 26 +++++++++++------- build/link.lcf | 9 +++++++ build/o_files.txt | 11 +++++--- source/egg/core/eggHeap.cpp | 54 +++++++++++++++++++++++++++++++++++++ source/egg/core/eggHeap.hpp | 2 +- source/egg/eggInternal.hpp | 3 +++ source/rk_types.h | 5 +++- source/rvl/os/osThread.h | 4 +++ 8 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 source/egg/core/eggHeap.cpp create mode 100644 source/egg/eggInternal.hpp diff --git a/build.py b/build.py index 7852e5d26..c04f62aa3 100644 --- a/build.py +++ b/build.py @@ -57,7 +57,6 @@ "-pragma \"cats off\"", # ??? # "-pragma \"aggressive_inline on\"", # "-pragma \"auto_inline on\"", - "-ipa file", "-inline auto", "-w notinlined -W noimplicitconv", "-nostdinc", @@ -68,11 +67,11 @@ def postprocess(dst): command("python tools/postprocess.py -fsymbol-fixup %s" % dst) -def compile_source(src, dst, version='default'): +def compile_source(src, dst, version='default', additional='-ipa file'): try: os.mkdir("tmp") except: pass - command = f"{CWCC_PATHS[version]} {CWCC_OPT} {src} -o {dst}" + command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" if VERBOSE: print(command) @@ -111,12 +110,21 @@ def build(): os.mkdir("out") except: pass - compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831') - compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831') - compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831') - compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127') - compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127') - compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127') + RVL_OPTS = '-ipa file' + EGG_OPTS = '-ipa function -rostr' + + compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) + compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) + + compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) + + compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) + compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS) + compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) + # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) + compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) + # compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) + # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) diff --git a/build/link.lcf b/build/link.lcf index e6c42f4dd..2f45d2b7a 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -51,6 +51,15 @@ CXGetUncompressedSize=0x8015C2E0; List_Remove__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AF110; List_Append__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AEF80; +List_Init__Q24nw4r2utFPQ34nw4r2ut4ListUs=0x800AEF60; + findContainHeap__Q23EGG4HeapFPCv=0x80229ADC; +__vt__Q23EGG4Heap=0x802A30C0; +OSInitMutex=0x801A7EAC; + +__nwa__FUlPQ23EGG4Heapi=0x80229E04; + +OSLockMutex=0x801A7EE4; +OSUnlockMutex=0x801A7FC0; } diff --git a/build/o_files.txt b/build/o_files.txt index c3af0df75..f3d8064e8 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -12,12 +12,17 @@ text_80199d04.o data_8027e772.o eggDisposer.o text_8021a1b8.o +rodata_80244ec0.o +bss_802a4080.o +sbss_803862b0.o +eggHeap.o +text_80229780.o data_802a2b54.o eggStreamDecomp.o text_80242504.o ctors_80244de0.o -bss_802a4080.o -sbss_803862b0.o +bss_80384348.o +sbss_80386ec0.o sdata2_80386fa0.o eggVector.o init_80004000.o @@ -26,7 +31,7 @@ extabindex_80006a20.o text_80243d18.o ctors_80244e8c.o dtors_80244ea4.o -rodata_80244ec0.o +rodata_80257747.o data_802a3f90.o bss_80384bf4.o sdata_803857f6.o diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp new file mode 100644 index 000000000..0bc800693 --- /dev/null +++ b/source/egg/core/eggHeap.cpp @@ -0,0 +1,54 @@ +/** + * @file + * @brief Heap implementations. + */ +#include +#include +#include +#include + +namespace EGG { + +// bss +nw4r::ut::List Heap::sHeapList; +OSMutex Heap::sRootMutex; +// sbss +Heap* Heap::sCurrentHeap; +int Heap::sIsHeapListInitialized; +Heap* Heap::sAllocatableHeap; + +void* Heap::sErrorCallback; +void* Heap::sAllocCallback; + +void* Heap::sErrorCallbackArg; +void* Heap::sAllocCallbackArg; + +Thread* Heap::sAllocatableThread; + +#define SIZE_MB 0x100000 + +void Heap::initialize() { + nw4r::ut::List_Init(&sHeapList, 32); + OSInitMutex(&sRootMutex); + sIsHeapListInitialized = true; +} +Heap::Heap(rvlHeap* pHeap) : Disposer(), mHeapHandle(pHeap) { + mParentBlock = nullptr; + mParentHeap = nullptr; + mName = "NoName"; + mFlag = 0; + + // Initialize child heap linked list. + nw4r::ut::List_Init(&mChildren, 8); + + // The static Heap members (set by initialize()) must be configured first. + EGG_ASSERT(sIsHeapListInitialized, "eggHeap.cpp", 63, + "Please call Heap::initialize()"); + + // Add the heap to the static heap list. + OSLockMutex(&sRootMutex); + nw4r::ut::List_Append(&sHeapList, this); + OSUnlockMutex(&sRootMutex); +} + +} // namespace EGG \ No newline at end of file diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 63d5b180b..3afb8befe 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -134,7 +134,7 @@ class Heap : public Disposer { //! //! @return this //! - Heap(struct rvlHeapHandle* heapHandle); + Heap(struct rvlHeap* heapHandle); //! @brief Allocate a block of memory in a heap. //! diff --git a/source/egg/eggInternal.hpp b/source/egg/eggInternal.hpp new file mode 100644 index 000000000..920790139 --- /dev/null +++ b/source/egg/eggInternal.hpp @@ -0,0 +1,3 @@ +#pragma once + +#define EGG_ASSERT(...) \ No newline at end of file diff --git a/source/rk_types.h b/source/rk_types.h index 12d95f309..f8ae790f3 100644 --- a/source/rk_types.h +++ b/source/rk_types.h @@ -91,4 +91,7 @@ typedef volatile s64 vs64; typedef float f32; typedef double f64; typedef volatile f32 vf32; -typedef volatile f64 vf64; \ No newline at end of file +typedef volatile f64 vf64; + +#define ROUND_UP(x, n) (((u32)(x) + n - 1) & ~(n - 1)) +#define ROUND_DOWN(x, n) (((u32)(x)) & ~(n - 1)) \ No newline at end of file diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index 98edffd10..6eb4f3447 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -8,6 +8,10 @@ typedef struct { char _[0x18]; } OSMutex; +void OSInitMutex(OSMutex*); +void OSLockMutex(OSMutex*); +void OSUnlockMutex(OSMutex*); + #ifdef __cplusplus } #endif \ No newline at end of file From 2b22cbc826d01e37e1f9ccfc772669df59a603c6 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 30 Jan 2021 05:32:32 -0500 Subject: [PATCH 015/477] :recycle: macros.inc + eggHeap work --- asm/macros.inc | 73 +++++++++++++++++++++++++++++++++++++ build.py | 2 +- build/link.lcf | 1 - build/o_files.txt | 5 ++- source/egg/core/eggHeap.cpp | 13 +++++++ 5 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 asm/macros.inc diff --git a/asm/macros.inc b/asm/macros.inc new file mode 100644 index 000000000..c05e98427 --- /dev/null +++ b/asm/macros.inc @@ -0,0 +1,73 @@ +# PowerPC Register Constants +.set r0, 0 +.set r1, 1 +.set r2, 2 +.set r3, 3 +.set r4, 4 +.set r5, 5 +.set r6, 6 +.set r7, 7 +.set r8, 8 +.set r9, 9 +.set r10, 10 +.set r11, 11 +.set r12, 12 +.set r13, 13 +.set r14, 14 +.set r15, 15 +.set r16, 16 +.set r17, 17 +.set r18, 18 +.set r19, 19 +.set r20, 20 +.set r21, 21 +.set r22, 22 +.set r23, 23 +.set r24, 24 +.set r25, 25 +.set r26, 26 +.set r27, 27 +.set r28, 28 +.set r29, 29 +.set r30, 30 +.set r31, 31 +.set f0, 0 +.set f1, 1 +.set f2, 2 +.set f3, 3 +.set f4, 4 +.set f5, 5 +.set f6, 6 +.set f7, 7 +.set f8, 8 +.set f9, 9 +.set f10, 10 +.set f11, 11 +.set f12, 12 +.set f13, 13 +.set f14, 14 +.set f15, 15 +.set f16, 16 +.set f17, 17 +.set f18, 18 +.set f19, 19 +.set f20, 20 +.set f21, 21 +.set f22, 22 +.set f23, 23 +.set f24, 24 +.set f25, 25 +.set f26, 26 +.set f27, 27 +.set f28, 28 +.set f29, 29 +.set f30, 30 +.set f31, 31 +.set qr0, 0 +.set qr1, 1 +.set qr2, 2 +.set qr3, 3 +.set qr4, 4 +.set qr5, 5 +.set qr6, 6 +.set qr7, 7 diff --git a/build.py b/build.py index c04f62aa3..3faa7de18 100644 --- a/build.py +++ b/build.py @@ -86,7 +86,7 @@ def command(cmd): os.system(cmd) def assemble(dst, src): - cmd = GAS + " %s -mgekko -o %s" % (src, dst) + cmd = GAS + " %s -mgekko -Iasm -o %s" % (src, dst) command(cmd) def link(dst, objs, lcf): diff --git a/build/link.lcf b/build/link.lcf index 2f45d2b7a..7ffdad2b1 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -55,7 +55,6 @@ List_Init__Q24nw4r2utFPQ34nw4r2ut4ListUs=0x800AEF60; findContainHeap__Q23EGG4HeapFPCv=0x80229ADC; -__vt__Q23EGG4Heap=0x802A30C0; OSInitMutex=0x801A7EAC; __nwa__FUlPQ23EGG4Heapi=0x80229E04; diff --git a/build/o_files.txt b/build/o_files.txt index f3d8064e8..d1a3bc756 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -13,11 +13,12 @@ data_8027e772.o eggDisposer.o text_8021a1b8.o rodata_80244ec0.o +data_802a2b54.o bss_802a4080.o sbss_803862b0.o eggHeap.o -text_80229780.o -data_802a2b54.o +text_80229814.o +data_802a30ec.o eggStreamDecomp.o text_80242504.o ctors_80244de0.o diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp index 0bc800693..d91faaac2 100644 --- a/source/egg/core/eggHeap.cpp +++ b/source/egg/core/eggHeap.cpp @@ -51,4 +51,17 @@ Heap::Heap(rvlHeap* pHeap) : Disposer(), mHeapHandle(pHeap) { OSUnlockMutex(&sRootMutex); } +Heap::~Heap() { + OSLockMutex(&sRootMutex); + nw4r::ut::List_Remove(&sHeapList, this); + OSUnlockMutex(&sRootMutex); +} + +struct HeapAllocCallbackArg { + int userArg; // 00 + u32 size; // 04 + int align; // 08 + Heap* heap; // 0C heap to allocate in +}; + } // namespace EGG \ No newline at end of file From d54c5b755da94f3a6fb03f3d2b81834006a4f57a Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 30 Jan 2021 07:28:47 -0500 Subject: [PATCH 016/477] :sparkles: Finished eggHeap.cpp --- build.py | 2 +- build/link.lcf | 11 +- build/o_files.txt | 7 +- source/egg/core/eggHeap.cpp | 339 +++++++++++++++++++++++++++++++++++- source/egg/core/eggHeap.hpp | 9 +- source/nw4r/ut/utList.hpp | 4 + 6 files changed, 353 insertions(+), 19 deletions(-) diff --git a/build.py b/build.py index 3faa7de18..ec956a925 100644 --- a/build.py +++ b/build.py @@ -119,7 +119,7 @@ def build(): compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) - compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS) + compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) diff --git a/build/link.lcf b/build/link.lcf index 7ffdad2b1..527d75609 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -39,7 +39,6 @@ OSReport=0x801A25D0; _current_locale=0x80271148; __register_global_object=0x80021338; -__dl__FPv=0x80229E14; sqrt__Q23EGG5MathfFf=0x8022F80C; frsqrt__Q23EGG5MathfFf=0x8022F85C; __dt__Q23EGG8Vector3fFv=0x80009B40; @@ -52,13 +51,13 @@ CXGetUncompressedSize=0x8015C2E0; List_Remove__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AF110; List_Append__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AEF80; List_Init__Q24nw4r2utFPQ34nw4r2ut4ListUs=0x800AEF60; - - -findContainHeap__Q23EGG4HeapFPCv=0x80229ADC; +List_GetNext__Q24nw4r2utFPCQ34nw4r2ut4ListPCv=0x800AF180; +findThread__Q23EGG6ThreadFP8OSThread=0x802434D8; OSInitMutex=0x801A7EAC; -__nwa__FUlPQ23EGG4Heapi=0x80229E04; - OSLockMutex=0x801A7EE4; OSUnlockMutex=0x801A7FC0; + +OSGetCurrentThread=0x801A98B0; +MEMFindContainHeap=0x80198658; } diff --git a/build/o_files.txt b/build/o_files.txt index d1a3bc756..8448e96bc 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -16,15 +16,16 @@ rodata_80244ec0.o data_802a2b54.o bss_802a4080.o sbss_803862b0.o +sdata2_80386fa0.o eggHeap.o -text_80229814.o +text_80229fac.o data_802a30ec.o eggStreamDecomp.o text_80242504.o ctors_80244de0.o bss_80384348.o sbss_80386ec0.o -sdata2_80386fa0.o +sdata2_80388d80.o eggVector.o init_80004000.o extab_80006460.o @@ -32,7 +33,7 @@ extabindex_80006a20.o text_80243d18.o ctors_80244e8c.o dtors_80244ea4.o -rodata_80257747.o +rodata_80257824.o data_802a3f90.o bss_80384bf4.o sdata_803857f6.o diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp index d91faaac2..af4d02d56 100644 --- a/source/egg/core/eggHeap.cpp +++ b/source/egg/core/eggHeap.cpp @@ -2,11 +2,16 @@ * @file * @brief Heap implementations. */ +#include #include #include #include #include +extern "C" void OSReport(const char*, ...); +extern "C" struct OSThread* OSGetCurrentThread(); +extern "C" EGG::rvlHeap* MEMFindContainHeap(const void*); + namespace EGG { // bss @@ -16,16 +21,15 @@ OSMutex Heap::sRootMutex; Heap* Heap::sCurrentHeap; int Heap::sIsHeapListInitialized; Heap* Heap::sAllocatableHeap; - -void* Heap::sErrorCallback; -void* Heap::sAllocCallback; +ErrorCallback Heap::sErrorCallback; +ErrorCallback Heap::sAllocCallback; void* Heap::sErrorCallbackArg; void* Heap::sAllocCallbackArg; Thread* Heap::sAllocatableThread; -#define SIZE_MB 0x100000 +#define SIZE_MB ((float)0x100000) void Heap::initialize() { nw4r::ut::List_Init(&sHeapList, 32); @@ -57,11 +61,334 @@ Heap::~Heap() { OSUnlockMutex(&sRootMutex); } -struct HeapAllocCallbackArg { +struct HeapAllocArg { int userArg; // 00 u32 size; // 04 int align; // 08 Heap* heap; // 0C heap to allocate in + + inline HeapAllocArg() : userArg(0), size(0), align(0), heap(nullptr) {} }; +struct HeapErrorArg { + const char* msg; + void* userdata; + + inline HeapErrorArg() {} +}; + +struct Thread { + static Thread* findThread(OSThread*); + u32 vtable; + Heap* mHeapHandle; + char _[60 - 8]; + Heap* mAlloctableHeap; +}; +struct rvlHeap { + char _[0x1c]; + u32 arena_end; +}; + +inline int getArenaEnd(Heap* currentHeap) { + return currentHeap->mHeapHandle->arena_end; +} +#pragma dont_reuse_strings on +void* Heap::alloc(u32 size, int align, Heap* heap) { + Heap* currentHeap = sCurrentHeap; // r28 + // r30 -> size + // r31 -> align + // r27 -> heap + Thread* currentThread = Thread::findThread(OSGetCurrentThread()); // r29 + + if (sAllocatableThread) { + OSGetCurrentThread(); // this does absolutely nothing. possibly debug + // message stripped? + } + + // If our thread defines a heap override, use it! + if (currentThread && currentThread->mAlloctableHeap) + heap = currentHeap = currentThread->mAlloctableHeap; + + // if sAllocatableHeap is not nullptr, it *must* be used + // this has higher priority over the thread heap override! + if (sAllocatableHeap) { + if (currentHeap && !heap) + heap = currentHeap; + // This would be reached if + // - sCurrentHeap && !heap && sCurrentHeap != sAlloctableHeap + // - heap && heap != sAllocatableHeap + if (heap != sAllocatableHeap) { + OSReport( + "cannot allocate from heap %x(%s) : allocatable heap is %x(%s)\n", + heap, heap->mName, sAllocatableHeap, sAllocatableHeap->mName); + OSReport("\tthread heap=%x\n", + currentThread ? currentThread->mAlloctableHeap : 0); + OSReport("\tthread heap=%s\n", + currentThread ? (currentThread->mAlloctableHeap + ? currentThread->mAlloctableHeap->mName + : "none") + : "none"); + if (sErrorCallback) { + HeapErrorArg cb; + cb.msg = "disable_but"; + cb.userdata = sErrorCallbackArg; + sErrorCallback(&cb); + } + dumpAll(); + return nullptr; + } + } + // alloc callback + if (sAllocCallback) { + HeapAllocArg arg; + arg.heap = heap ? heap : currentHeap; + arg.size = size; + arg.align = align; + arg.userArg = (int)sAllocCallbackArg; + sAllocCallback(&arg); + } + // If heap is non nullptr, use that + if (heap != nullptr) + return heap->alloc(size, align); + + // If we're here the argument heap is nullptr, so we can't use it. + // We're left with using the static current heap. If that doesn't exist, we + // can't do anything. + if (currentHeap != nullptr) { + void* allocated = currentHeap->alloc(size, align); // (r3), r27 + + if (allocated == nullptr) { + int arena_end = getArenaEnd(currentHeap); // r29 + int max_avail = currentHeap->getAllocatableSize(4); + int heap_size = arena_end - (u32)currentHeap; + OSReport("heap (%p):(%.1fMBytes free %d)->alloc(size(%d:%.1fMBytes),%d " + "align)\n", + currentHeap, static_cast(heap_size) / SIZE_MB, max_avail, + size, static_cast(size) / SIZE_MB, align); + + dumpAll(); + } + + return allocated; + } + + OSReport("cannot allocate %d from heap %x\n", size, heap); + dumpAll(); + return nullptr; +} + +Heap* Heap::findParentHeap() { + EGG_ASSERT(this->mHeapHandle, "eggHeap.cpp", 173, "mHeapHandle != nullptr"); + return this->mParentHeap; +} -} // namespace EGG \ No newline at end of file +Heap* Heap::findContainHeap(const void* memBlock) { + Heap* containingHeap = nullptr; + rvlHeap* memContainHeap = MEMFindContainHeap(memBlock); + if (memContainHeap) { + containingHeap = nullptr; + OSLockMutex(&sRootMutex); + if (sIsHeapListInitialized) { + Heap* lastObject = nullptr; // r4 + while ((lastObject = (Heap*)List_GetNext(&sHeapList, lastObject))) { + if (lastObject->mHeapHandle == memContainHeap) { + containingHeap = lastObject; + break; + } + } + } + OSUnlockMutex(&sRootMutex); + } + return containingHeap; +} +void Heap::free(void* memBlock, Heap* heap) { + // inside likely inline getContainHeap + if (heap == nullptr) { + rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + // If our memory block is not inside a head, there is not much we can do. + if (!containHeap) + return; + Heap* foundHeap = nullptr; // r29 + OSLockMutex(&sRootMutex); + if (sIsHeapListInitialized) { + Heap* iterHeap = nullptr; // r4 + while ((iterHeap = (Heap*)nw4r::ut::List_GetNext(&sHeapList, iterHeap)) != + nullptr) { + if (iterHeap->mHeapHandle == containHeap) { + foundHeap = iterHeap; + break; + } + } + } + OSUnlockMutex(&sRootMutex); + heap = foundHeap; + if (foundHeap == nullptr) + return; + } + heap->free(memBlock); +} + +void Heap::dispose() { + Disposer* it = nullptr; + // This isn't an infinite loop: the destructor detaches itself.. + while ((it = (Disposer*)nw4r::ut::List_GetFirst(&mChildren))) + it->~Disposer(); +} + +#ifdef NONMATCHING +void Heap::dumpAll() { + Heap* it = nullptr; // r27 + Heap* parent_it; // r26 + // These are incremented, but never used. Likely used in stripped debug + // message. + u32 mem1_remaining = 0; // r30 + u32 mem2_remaining = 0; // r31 + + while ((it = (Heap*)List_GetNext(&sHeapList, it))) { + parent_it = nullptr; // r26 + + if ((u32)it->getStartAddress() < 0x90000000) // MEM2 + mem1_remaining += it->getAllocatableSize(4); + else + mem2_remaining += it->getAllocatableSize(4); + + while ((parent_it = (Heap*)List_GetNext(&sHeapList, parent_it))) { + if (it->getParentHeap() == parent_it) + goto next; + } + next:; + } +} +#else +extern "C" void List_GetNext__Q24nw4r2utFPCQ34nw4r2ut4ListPCv(); +asm void Heap::dumpAll() { + nofralloc + // clang-format off + +/* 80229CB0 94 21 FF E0 */ stwu r1, -0x20(r1) +/* 80229CB4 7C 08 02 A6 */ mflr r0 +/* 80229CB8 90 01 00 24 */ stw r0, 0x24(r1) +/* 80229CBC BF 41 00 08 */ stmw r26, 8(r1) + li r27, 0 + li r30, 0 + li r31, 0 + lis r29, sHeapList@ha + lis r28, -0x7000 + b loc_80229D48 +loc_80229CD8: + cmplw r3, r28 + li r26, 0 + bge loc_80229D04 + lwz r12, 0(r27) + mr r3, r27 + li r4, 4 + lwz r12, 0x24(r12) + mtctr r12 + bctrl + add r30, r30, r3 + b loc_80229D30 +loc_80229D04: + lwz r12, 0(r27) + mr r3, r27 + li r4, 4 + lwz r12, 0x24(r12) + mtctr r12 + bctrl + add r31, r31, r3 + b loc_80229D30 +loc_80229D24: + lwz r0, 0x18(r27) + cmplw r0, r3 + beq loc_80229D48 +loc_80229D30: + mr r4, r26 + addi r3, r29, sHeapList@l + bl List_GetNext__Q24nw4r2utFPCQ34nw4r2ut4ListPCv + cmpwi r3, 0 + mr r26, r3 + bne loc_80229D24 +loc_80229D48: + mr r4, r27 + addi r3, r29, sHeapList@l + bl List_GetNext__Q24nw4r2utFPCQ34nw4r2ut4ListPCv + cmpwi r3, 0 + mr r27, r3 + bne loc_80229CD8 +/* 80229D60 BB 41 00 08 */ lmw r26, 8(r1) +/* 80229D64 80 01 00 24 */ lwz r0, 0x24(r1) +/* 80229D68 7C 08 03 A6 */ mtlr r0 +/* 80229D6C 38 21 00 20 */ addi r1, r1, 0x20 +/* 80229D70 4E 80 00 20 */ blr + // clang-format on +} +#pragma peephole on +#endif + +Heap* Heap::becomeCurrentHeap() { + OSLockMutex(&Heap::sRootMutex); + Heap* oldCurrentHeap = Heap::sCurrentHeap; // r30 + Heap::sCurrentHeap = this; + OSUnlockMutex(&Heap::sRootMutex); + return oldCurrentHeap; +} +} // namespace EGG + +void* operator new(size_t size) { return EGG::Heap::alloc(size, 4, nullptr); } +void* operator new[](size_t size, u32 align) { + return EGG::Heap::alloc(size, align, nullptr); +} +void* operator new(size_t size, EGG::Heap* heap, int align) { + return EGG::Heap::alloc(size, align, heap); +} +void* operator new[](size_t size) { return EGG::Heap::alloc(size, 4, nullptr); } +void* operator new[](size_t size, int align) { + return EGG::Heap::alloc(size, align, nullptr); +} +void* operator new[](size_t size, EGG::Heap* heap, int align) { + return EGG::Heap::alloc(size, align, heap); +} +void operator delete(void* memBlock) { + EGG::rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + // If our memory block is not inside a head, there is not much we can do. + if (!containHeap) + return; + EGG::Heap* foundHeap = nullptr; // r29 + OSLockMutex(&EGG::Heap::sRootMutex); + if (EGG::Heap::sIsHeapListInitialized) { + EGG::Heap* iterHeap = nullptr; // r4 + while ((iterHeap = (EGG::Heap*)nw4r::ut::List_GetNext( + &EGG::Heap::sHeapList, iterHeap)) != nullptr) { + if (iterHeap->mHeapHandle == containHeap) { + foundHeap = iterHeap; + break; + } + } + } + OSUnlockMutex(&EGG::Heap::sRootMutex); + EGG::Heap* heap = foundHeap; + if (foundHeap == nullptr) + return; + heap->free(memBlock); +} +void operator delete[](void* memBlock) { + EGG::rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + // If our memory block is not inside a head, there is not much we can do. + if (!containHeap) + return; + EGG::Heap* foundHeap = nullptr; // r29 + OSLockMutex(&EGG::Heap::sRootMutex); + if (EGG::Heap::sIsHeapListInitialized) { + EGG::Heap* iterHeap = nullptr; // r4 + while ((iterHeap = (EGG::Heap*)nw4r::ut::List_GetNext( + &EGG::Heap::sHeapList, iterHeap)) != nullptr) { + if (iterHeap->mHeapHandle == containHeap) { + foundHeap = iterHeap; + break; + } + } + } + OSUnlockMutex(&EGG::Heap::sRootMutex); + EGG::Heap* heap = foundHeap; + if (foundHeap == nullptr) + return; + heap->free(memBlock); +} diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 3afb8befe..5fcb7c23f 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -14,6 +14,7 @@ namespace EGG { class ExpHeap; class Allocator; +typedef void (*ErrorCallback)(void*); //! @brief Base Heap class //! @@ -32,6 +33,8 @@ class Heap : public Disposer { }; inline bool isExpHeap() { return getHeapKind() == HEAP_KIND_EXPANDED; } + inline Heap* getParentHeap() { return mParentHeap; } + inline void* getStartAddress() { return this; } virtual ~Heap(); //! @brief [vt+0x0C] Get the type of heap the current heap is. @@ -69,8 +72,8 @@ class Heap : public Disposer { //! @brief When non NULL, this heap MUST be used for heap allocations. //! This will restrict rather than redirect allocations. static Heap* sAllocatableHeap; - static void* sErrorCallback; //!< TODO - static void* sAllocCallback; //!< TODO + static ErrorCallback sErrorCallback; //!< TODO + static ErrorCallback sAllocCallback; //!< TODO static void* sErrorCallbackArg; //!< TODO static void* sAllocCallbackArg; //!< TODO static struct Thread* sAllocatableThread; //!< TODO @@ -147,7 +150,7 @@ class Heap : public Disposer { //! @return The address of the allocated block. //! @retval NULL: The allocation failed. //! - static void* alloc(u32 size, s32 align, Heap* heap); + static void* alloc(u32 size, int align, Heap* heap); //! @brief Returns the Disposer's parent heap. //! diff --git a/source/nw4r/ut/utList.hpp b/source/nw4r/ut/utList.hpp index 8e1d6df0f..d12d9afea 100644 --- a/source/nw4r/ut/utList.hpp +++ b/source/nw4r/ut/utList.hpp @@ -25,6 +25,10 @@ struct List { void List_Init(List* pList, u16 intrusion_offset); void List_Append(List* pList, void* pObj); void List_Remove(List* pList, void* pObj); +void* List_GetNext(const List*, const void*); +inline void* List_GetFirst(const List* pList) { + return List_GetNext(pList, nullptr); +} // clang-format off } } // namespace nw4r From 0972fe22deb2f807be62a01951f319fa9fa159bb Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 30 Jan 2021 12:47:42 -0500 Subject: [PATCH 017/477] :sparkles: Decompiled eggArchive.cpp --- build.py | 1 + build/link.lcf | 6 ++ build/o_files.txt | 9 ++- source/egg/core/eggArchive.cpp | 142 +++++++++++++++++++++++++++++++++ source/egg/core/eggArchive.hpp | 140 ++++++++++++++++++++++++++++++++ source/rvl/arc/binary_format.h | 7 +- source/rvl/arc/rvlArchive.c | 4 +- source/rvl/arc/rvlArchive.h | 4 +- 8 files changed, 304 insertions(+), 9 deletions(-) create mode 100644 source/egg/core/eggArchive.cpp create mode 100644 source/egg/core/eggArchive.hpp diff --git a/build.py b/build.py index ec956a925..a58b2159f 100644 --- a/build.py +++ b/build.py @@ -118,6 +118,7 @@ def build(): compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) + compile_source("source/egg/core/eggArchive.cpp", "out/eggArchive.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) diff --git a/build/link.lcf b/build/link.lcf index 527d75609..99688195d 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -60,4 +60,10 @@ OSUnlockMutex=0x801A7FC0; OSGetCurrentThread=0x801A98B0; MEMFindContainHeap=0x80198658; + +DVDReadPrio=0x8015E834; +DVDOpen=0x8015E2BC; +DVDClose=0x8015E568; + +memset=0x80006038; } diff --git a/build/o_files.txt b/build/o_files.txt index 8448e96bc..54b9b0cf2 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -10,12 +10,17 @@ text_80124e80.o rvlMemList.o text_80199d04.o data_8027e772.o +bss_802a4080.o +sbss_803862b0.o +eggArchive.o +text_8020fcc4.o +data_802a268c.o eggDisposer.o text_8021a1b8.o rodata_80244ec0.o data_802a2b54.o -bss_802a4080.o -sbss_803862b0.o +bss_803832e4.o +sbss_80386d84.o sdata2_80386fa0.o eggHeap.o text_80229fac.o diff --git a/source/egg/core/eggArchive.cpp b/source/egg/core/eggArchive.cpp new file mode 100644 index 000000000..6e2ad3d59 --- /dev/null +++ b/source/egg/core/eggArchive.cpp @@ -0,0 +1,142 @@ +/*! + * @file + * @brief TODO + */ + +#include +#include + +struct rvlDvdFile { + char _[0x3c]; +}; +extern "C" unk32 DVDOpen(const char*, rvlDvdFile*); +extern "C" u32 DVDReadPrio(rvlDvdFile*, void*, u32, unk32, unk32); +extern "C" unk32 DVDClose(rvlDvdFile*); + +namespace EGG { + +bool Archive::sIsArchiveListInitialized; +nw4r::ut::List Archive::sArchiveList; + +Archive::~Archive() { removeList(this); } + +void Archive::removeList(Archive* pArchive) { + nw4r::ut::List_Remove(&sArchiveList, pArchive); +} + +Archive* Archive::mount(void* arcStart, Heap* pHeap, int align) { + Archive* wArchive = findArchive(arcStart); // r30, INLINE archive wrapper + if (wArchive == nullptr) { + wArchive = new (pHeap, align) Archive(); // INLINE + EGG_ASSERT(wArchive, "eggArchive.cpp", 159, "archive != NULL"); + + bool bInitSuccess = wArchive->initHandle(arcStart); + if (bInitSuccess) + wArchive->mStatus = LOADED_AND_CAN_FAST_READ; + else + wArchive->mStatus = NOT_LOADED; + EGG_ASSERT(bInitSuccess, "eggArchive.cpp", 166, "false"); + // If we failed, clean up + if (!bInitSuccess) // INLINE + { + delete wArchive; + wArchive = nullptr; + } + } else { + wArchive->_14++; + } + + return wArchive; +} +// exact same as above but _10 set to 2 not 1 +Archive* Archive::mountNoFastGet(void* arcStart, Heap* pHeap, int align) { + Archive* wArchive = findArchive(arcStart); // r30, INLINE archive wrapper + if (wArchive == nullptr) { + wArchive = new (pHeap, align) Archive(); // INLINE + EGG_ASSERT(wArchive, "eggArchive.cpp", 159, "archive != NULL"); + + bool bInitSuccess = wArchive->initHandle(arcStart); + if (bInitSuccess) + wArchive->mStatus = LOADED; + else + wArchive->mStatus = NOT_LOADED; + EGG_ASSERT(bInitSuccess, "eggArchive.cpp", 166, "false"); + // If we failed, clean up + if (!bInitSuccess) // INLINE + { + delete wArchive; + wArchive = nullptr; + } + } else { + wArchive->_14++; + } + + return wArchive; +} +void Archive::unmount() { + if (_14 != 0 && --_14 == 0) { + mStatus = NOT_LOADED; + delete this; + } +} +int Archive::convertPathToEntryID(const char* path) { + return ARCConvertPathToEntrynum(&this->mArcHandle, path); +} +void* Archive::getFileFast(int entryNum, FileInfo* pInfo) { + rvlArchiveFile fileInfo; // col + void* result = nullptr; + if (mStatus == LOADED_AND_CAN_FAST_READ) { + if (mArcHandle.open(entryNum, fileInfo)) { + result = ARCGetStartAddrInMem(&fileInfo); + if (pInfo) { + pInfo->startOffset = ARCGetStartOffset(&fileInfo); + pInfo->length = ARCGetLength(&fileInfo); + } + } + ARCClose(&fileInfo); + } + return result; +} +void Archive::getFile(const char* path, FileInfo* pInfo) { + rvlArchiveFile fileInfo; // col + if (mArcHandle.open(path, fileInfo)) { + pInfo->startOffset = ARCGetStartOffset(&fileInfo); + pInfo->length = ARCGetLength(&fileInfo); + } + ARCClose(&fileInfo); +} + +// Reads header from u8 file on disc. Then reads header and allocates file based +// on that filesize. +void* Archive::loadFromDisc(const char* path, Heap* pHeap, int align) { + rvlDvdFile dvdFileInfo; + int alignRounded = align > 0 ? -32 : 32; // r31 + if (!DVDOpen(path, &dvdFileInfo)) + return nullptr; + + void* ARC = nullptr; + rvlArchiveHeader* readHeader = (rvlArchiveHeader*)pHeap->alloc( + sizeof(rvlArchiveHeader), alignRounded); // r30 + + if (readHeader != nullptr) { + if (DVDReadPrio(&dvdFileInfo, readHeader, 32, 0, 2) >= 32) { + u32 arcSize = + ROUND_UP(readHeader->nodes.size + sizeof(rvlArchiveHeader), 32); + ARC = pHeap->alloc(arcSize, align); // r31 + + if (ARC) { + // needed for match + u32 res = DVDReadPrio(&dvdFileInfo, ARC, arcSize, 0, 2); + if (res != arcSize) { + pHeap->free(ARC); // If we failed to read fst + ARC = nullptr; + } + } + } + pHeap->free(readHeader); + } + DVDClose(&dvdFileInfo); + return ARC; +} + +} // namespace EGG diff --git a/source/egg/core/eggArchive.hpp b/source/egg/core/eggArchive.hpp new file mode 100644 index 000000000..5845aaf69 --- /dev/null +++ b/source/egg/core/eggArchive.hpp @@ -0,0 +1,140 @@ +/*! + * @file + * @brief TODO + */ + +#pragma once + +#include +#include +#include +#include +#include +extern "C" { +#include +} +extern "C" void memset(void*, int, u32); + +namespace EGG { + +class Archive : public Disposer // sizeof=60,0x3C +{ +public: + struct FileInfo { + u32 startOffset; + u32 length; + }; + + //! Initialize the wrapped ARC handle from the start address pointer + //! member. + u32 initHandle(void* pArcStart) { + // Here for now (since we don't have deadstripping) + { + EGG_ASSERT(pArcStart, "eggArchive.cpp", 61, + "arcBinary != NULL"); // from later inline + return ARCInitHandle(pArcStart, &this->mArcHandle); + } + } + + //! @brief Find the EGG Archive wrapping the ARC file starting at pArchive. + //! @remarks Always inlined. + //! + static Archive* findArchive(void* key) { + // Here for now (since we don't have deadstripping) + { + Archive* found = NULL; + if (isArchiveListInitialized()) { + Archive* iterArchive = NULL; + while ((iterArchive = (Archive*)nw4r::ut::List_GetNext(&sArchiveList, + iterArchive))) { + if (iterArchive->mArcHandle.mHeader == key) { + found = iterArchive; + break; + } + } + } + return found; + } + } + + //! @brief Mount an archive. + //! @details If the archive is already mounted, _14 of that archive will be + //! incremented and a pointer to that archive will be returned. + //! @returns A pointed to the mounted EGG Archive if successful. Otherwise, + //! NULL. + //! + static Archive* mount(void* pArcStart, Heap* pHeap, int align); + + //! @brief Exact same as @see mount, except the status will be set such that + //! fast get attemps fail. + //! + static Archive* mountNoFastGet(void* pArcStart, Heap* pHeap, int align); + + //! @brief Unmount an archive. (Set the status as NOT_LOADED and destroy self) + //! + void unmount(); + + //! @brief Convert the path to an ARC entry ID. + int convertPathToEntryID(const char* path); + void* getFileFast(int entryNum, FileInfo* pInfo); + void getFile(const char* path, FileInfo* pInfo); + // read arc file from disc + static void* loadFromDisc(const char* path, Heap* pHeap, int align); + +private: + static bool sIsArchiveListInitialized; + static nw4r::ut::List sArchiveList; + + static inline bool isArchiveListInitialized() { + return sIsArchiveListInitialized; + } + + struct LowArchive : public rvlArchive { + inline void reset() { memset(this, 0, sizeof(*this)); } + inline bool open(rvlArchiveEntryHandle path, rvlArchiveFile& file) { + return ARCFastOpen(this, path, &file); + } + inline bool open(const char* path, rvlArchiveFile& file) { + return ARCOpen(this, path, &file); + } + }; + + Archive() { + _14 = 1; + mStatus = NOT_LOADED; + mArcHandle.reset(); + if (!sIsArchiveListInitialized) { + nw4r::ut::List_Init(&sArchiveList, 52); + sIsArchiveListInitialized = true; + } + appendList(this); + } + virtual ~Archive() override; //!< [vt+0x00] + + //! @brief Append an archive to the static archive list. + //! @remarks Always inlined. + //! + static void appendList(Archive* pArchive) { + nw4r::ut::List_Append(&sArchiveList, pArchive); + } + //! @brief Remove an archive to the static archive list. + //! + static void removeList(Archive* pArchive); + + u32 _08; // unseen + u32 _0C; // unseen + + enum Status { + NOT_LOADED, //!< [0] + LOADED_AND_CAN_FAST_READ, //!< [1] + LOADED //!< [2] Just going by behavior, fast read can only proceed when set + //!< to 1. + }; + Status mStatus; //!< [+0x10] set to 0 in ct + int _14; //!< [+0x14] set to 1 in ct; numMounts? + LowArchive mArcHandle; // 0x18 + + char _unk[8]; +}; + +} // namespace EGG \ No newline at end of file diff --git a/source/rvl/arc/binary_format.h b/source/rvl/arc/binary_format.h index d62387a56..6dc599975 100644 --- a/source/rvl/arc/binary_format.h +++ b/source/rvl/arc/binary_format.h @@ -50,19 +50,20 @@ typedef struct rvlArchiveHeader rvlArchiveHeader; enum { RVL_ARCHIVE_FILE_MAGIC = 0x55aa382d }; -static bool rvlArchiveHeaderVerify(const rvlArchiveHeader* self) { +static inline bool rvlArchiveHeaderVerify(const rvlArchiveHeader* self) { // Verify the "U8" magic if (self->magic != RVL_ARCHIVE_FILE_MAGIC) return false; return true; } -static const rvlArchiveNode* +static inline const rvlArchiveNode* rvlArchiveHeaderGetNodes(const rvlArchiveHeader* self) { expects(self->nodes.offset > sizeof(rvlArchiveHeader)); return (const rvlArchiveNode*)((u8*)self + self->nodes.offset); } -static const u8* rvlArchiveHeaderGetFileData(const rvlArchiveHeader* self) { +static inline const u8* +rvlArchiveHeaderGetFileData(const rvlArchiveHeader* self) { expects(self->files.offset > sizeof(rvlArchiveHeader)); return (const u8*)((u8*)self + self->files.offset); } diff --git a/source/rvl/arc/rvlArchive.c b/source/rvl/arc/rvlArchive.c index 556fc0239..1ab20d635 100644 --- a/source/rvl/arc/rvlArchive.c +++ b/source/rvl/arc/rvlArchive.c @@ -28,7 +28,7 @@ bool ARCInitHandle(void* pData, rvlArchive* pOut) { return true; } -bool ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut) { +int ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut) { expects(self != nullptr && path != nullptr && pOut != nullptr); const rvlArchiveNode* nodes = self->mNodes; @@ -54,7 +54,7 @@ bool ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut) { return true; } -bool ARCFastOpen(const rvlArchive* self, s32 resolved, rvlArchiveFile* pOut) { +int ARCFastOpen(const rvlArchive* self, s32 resolved, rvlArchiveFile* pOut) { expects(self != nullptr && pOut != nullptr); const rvlArchiveNode* nodes = self->mNodes; diff --git a/source/rvl/arc/rvlArchive.h b/source/rvl/arc/rvlArchive.h index 1b1eb611d..f6f28ba4c 100644 --- a/source/rvl/arc/rvlArchive.h +++ b/source/rvl/arc/rvlArchive.h @@ -121,7 +121,7 @@ bool ARCChangeDir(rvlArchive* self, const char* path); //! //! @return If the operation succeeded. //! -bool ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut); +int ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut); //! @brief Open a file handle from an archive. //! @@ -131,7 +131,7 @@ bool ARCOpen(const rvlArchive* self, const char* path, rvlArchiveFile* pOut); //! //! @return If the operation succeeded. //! -bool ARCFastOpen(const rvlArchive* self, rvlArchiveEntryHandle path, +int ARCFastOpen(const rvlArchive* self, rvlArchiveEntryHandle path, rvlArchiveFile* pOut); //! @brief Get the file data from a file handle. From 2ebd3d16ebbe8d4e9a72339fb4d9039aa409ac65 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 30 Jan 2021 14:33:25 -0500 Subject: [PATCH 018/477] :sparkles: Decompiled eggGraphicsFifo.cpp --- build.py | 1 + build/link.lcf | 4 ++ build/o_files.txt | 7 ++- source/egg/core/eggGraphicsFifo.cpp | 38 ++++++++++++++++ source/egg/core/eggGraphicsFifo.hpp | 70 +++++++++++++++++++++++++++++ source/rvl/gx.h | 33 ++++++++++++++ 6 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 source/egg/core/eggGraphicsFifo.cpp create mode 100644 source/egg/core/eggGraphicsFifo.hpp create mode 100644 source/rvl/gx.h diff --git a/build.py b/build.py index a58b2159f..729f1eecf 100644 --- a/build.py +++ b/build.py @@ -120,6 +120,7 @@ def build(): compile_source("source/egg/core/eggArchive.cpp", "out/eggArchive.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) + compile_source("source/egg/core/eggGraphicsFifo.cpp", "out/eggGraphicsFifo.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) diff --git a/build/link.lcf b/build/link.lcf index 99688195d..a427f788d 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -66,4 +66,8 @@ DVDOpen=0x8015E2BC; DVDClose=0x8015E568; memset=0x80006038; + +GXGetGPStatus=0x8016CEC4; +GXInit=0x8016B850; + } diff --git a/build/o_files.txt b/build/o_files.txt index 54b9b0cf2..c576c964a 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -17,10 +17,13 @@ text_8020fcc4.o data_802a268c.o eggDisposer.o text_8021a1b8.o -rodata_80244ec0.o data_802a2b54.o -bss_803832e4.o sbss_80386d84.o +eggGraphicsFifo.o +rodata_80244ec0.o +data_802a30bc.o +bss_803832e4.o +sbss_80386e99.o sdata2_80386fa0.o eggHeap.o text_80229fac.o diff --git a/source/egg/core/eggGraphicsFifo.cpp b/source/egg/core/eggGraphicsFifo.cpp new file mode 100644 index 000000000..9844c3adc --- /dev/null +++ b/source/egg/core/eggGraphicsFifo.cpp @@ -0,0 +1,38 @@ +#include +#include +#include +#include + +namespace EGG { + +GraphicsFifo* GraphicsFifo::sGraphicsFifo; +GraphicsFifo::GPStatus GraphicsFifo::sGpStatus; + +GraphicsFifo* GraphicsFifo::create(u32 fifoSize, Heap* heap) { + EGG_ASSERT(!sGraphicsFifo, "eggGraphicsFifo.cpp", 59, "!sGraphicsFifo"); + if (heap == nullptr) + heap = Heap::sCurrentHeap; + EGG_ASSERT(!!"OSIsMEM1Region(heap)", "eggGraphicsFifo.cpp", 69, + "OSIsMEM1Region( heap )"); + + return sGraphicsFifo = new (heap, 4) GraphicsFifo(fifoSize, heap); +} + +GraphicsFifo::~GraphicsFifo() { + do + GXGetGPStatus(&sGpStatus.overhi, &sGpStatus.underlow, &sGpStatus.readIdle, + &sGpStatus.cmdIdle, &sGpStatus.brkpt); + while (!sGpStatus.readIdle); + + Heap::free(mBufBase, nullptr); +} + +GraphicsFifo::GraphicsFifo(unsigned int fifoSize, EGG::Heap* heap) { + mBufSize = ROUND_UP(fifoSize, 32); + mBufBase = (void*)ROUND_UP(Heap::alloc(mBufSize + 0xA0, 32, heap), 32); + + EGG_ASSERT(mBufBase, "eggGraphicsFifo.cpp", 145, "mBufBase"); + + mFifoObj = GXInit(mBufBase, mBufSize); +} +} // namespace EGG diff --git a/source/egg/core/eggGraphicsFifo.hpp b/source/egg/core/eggGraphicsFifo.hpp new file mode 100644 index 000000000..abb99e965 --- /dev/null +++ b/source/egg/core/eggGraphicsFifo.hpp @@ -0,0 +1,70 @@ +/** + * @file + * @brief Contains code for the GraphicsFifo wrapper. + */ + +#pragma once + +#include + +struct GXFifoObj; + +namespace EGG { + +class Heap; + +//! @brief Wrapper for GX Graphics fifo. +//! +class GraphicsFifo { +public: + //! @brief Holds the state of the Graphics Processor at any given moment. + //! + //! @details Pointers to all members passed as arguments to GXGetGPStatus(). + //! + struct GPStatus { + u8 overhi; //!< [+0x00] If high watermark has been passed. + u8 underlow; //!< [+0x01] If low watermark has been passed. + u8 readIdle; //!< [+0x02] If the GP read unit is idle. + u8 cmdIdle; //!< [+0x03] If all commands been flushed to XF. + u8 brkpt; //!< [+0x04] If FIFO has reached a breakpoint and GP reads + //!< have been stopped. + }; + + virtual ~GraphicsFifo(); + + //! @brief Creates the global GraphicsFifo. + //! + //! @param[in] fifoSize Size of the FIFO to create. Will be rounded to 32B. + //! @param[in] heap Heap to create the FIFO in. If nullptr, default heap + //! used. + //! + //! @pre If heap is nullptr, default heap used. + //! + //! @return The initialized GraphicsFifo. + //! + static GraphicsFifo* create(u32 fifoSize, Heap* heap); + +private: + //! @brief Initializes the GraphicsFifo (called by create). + //! + //! @param[in] fifoSize Size of the FIFO to create. Will be rounded to 32B. + //! @param[in] heap Heap to create the FIFO in. + //! + //! @pre heap must not be nullptr. + //! + //! @return The initialized GraphicsFifo. + //! + GraphicsFifo(unsigned int fifoSize, Heap* heap); + + GXFifoObj* mFifoObj; //!< Pointer to the GXFifoObj* (+0x04) + void* mBufBase; //!< Pointer to the buffer base. (+0x08) + unsigned int mBufSize; //!< Size of the buffer. (+0x0C) + + // Global instance. Not used. + static GraphicsFifo* sGraphicsFifo; + + // Holds GXGetGPStatus results + static GPStatus sGpStatus; +}; + +} // namespace EGG diff --git a/source/rvl/gx.h b/source/rvl/gx.h new file mode 100644 index 000000000..be1e26efc --- /dev/null +++ b/source/rvl/gx.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct GXFifoObj; + +void GXGetGPStatus(u8* overhi, u8* underlow, u8* readIdle, u8* cmdIdle, + u8* brkpt); +GXFifoObj* GXInit(void* buf, u32 size); + +struct GXRenderModeObj { + int tv_mode; + u16 fb_width; + u16 efb_height; + u16 xfb_height; + u16 vi_x; + u16 vi_y; + u16 vi_width; + u16 vi_height; + int vi_xfb; + u8 field; + u8 aa; + u8 sample[12][2]; + u8 vert_filter[7]; +}; + +#ifdef __cplusplus +} +#endif \ No newline at end of file From ec39c4e96e8b575605a503ced1c0641ece965662 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 31 Jan 2021 09:43:51 -0500 Subject: [PATCH 019/477] :construction: Started eggQuat.cpp --- build.py | 1 + build/o_files.txt | 2 ++ source/egg/math/eggQuat.cpp | 17 +++++++++++++++++ source/egg/math/eggQuat.hpp | 19 +++++++++++++++++++ 4 files changed, 39 insertions(+) create mode 100644 source/egg/math/eggQuat.cpp create mode 100644 source/egg/math/eggQuat.hpp diff --git a/build.py b/build.py index 729f1eecf..c2d07864c 100644 --- a/build.py +++ b/build.py @@ -122,6 +122,7 @@ def build(): compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggGraphicsFifo.cpp", "out/eggGraphicsFifo.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") + compile_source("source/egg/math/eggQuat.cpp", "out/eggQuat.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) diff --git a/build/o_files.txt b/build/o_files.txt index c576c964a..8c04483c6 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -27,6 +27,8 @@ sbss_80386e99.o sdata2_80386fa0.o eggHeap.o text_80229fac.o +eggQuat.o +text_80239e10.o data_802a30ec.o eggStreamDecomp.o text_80242504.o diff --git a/source/egg/math/eggQuat.cpp b/source/egg/math/eggQuat.cpp new file mode 100644 index 000000000..87310cd9b --- /dev/null +++ b/source/egg/math/eggQuat.cpp @@ -0,0 +1,17 @@ +/*! + * @file + * @brief TODO + */ + +#include + +namespace EGG { + +void Quatf::set(float a, float b, float c, float d) { + _[3] = a; + _[0] = b; + _[1] = c; + _[2] = d; +} + +} // namespace EGG \ No newline at end of file diff --git a/source/egg/math/eggQuat.hpp b/source/egg/math/eggQuat.hpp new file mode 100644 index 000000000..34982cd9f --- /dev/null +++ b/source/egg/math/eggQuat.hpp @@ -0,0 +1,19 @@ +/*! + * @file + * @brief TODO + */ + +#pragma once + +#include + +namespace EGG { + +class Quatf { +public: + void set(float a, float b, float c, float d); + + f32 _[4]; +}; + +} // namespace EGG \ No newline at end of file From f3458f7cbc8470be858a479e95883587f58bed22 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 1 Feb 2021 08:46:34 -0500 Subject: [PATCH 020/477] :sparkles: Decompiled eggThread.cpp --- build.py | 1 + build/link.lcf | 9 ++- build/o_files.txt | 8 +- source/egg/core/eggHeap.hpp | 7 ++ source/egg/core/eggThread.cpp | 111 ++++++++++++++++++++++++++ source/egg/core/eggThread.hpp | 143 ++++++++++++++++++++++++++++++++++ source/rvl/os/osThread.h | 26 +++++++ 7 files changed, 302 insertions(+), 3 deletions(-) create mode 100644 source/egg/core/eggThread.cpp create mode 100644 source/egg/core/eggThread.hpp diff --git a/build.py b/build.py index c2d07864c..1b3924ae2 100644 --- a/build.py +++ b/build.py @@ -125,6 +125,7 @@ def build(): compile_source("source/egg/math/eggQuat.cpp", "out/eggQuat.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) + compile_source("source/egg/core/eggThread.cpp", "out/eggThread.o", '4201_127', EGG_OPTS) compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) diff --git a/build/link.lcf b/build/link.lcf index a427f788d..0d3f3cbec 100644 --- a/build/link.lcf +++ b/build/link.lcf @@ -52,12 +52,19 @@ List_Remove__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AF110; List_Append__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AEF80; List_Init__Q24nw4r2utFPQ34nw4r2ut4ListUs=0x800AEF60; List_GetNext__Q24nw4r2utFPCQ34nw4r2ut4ListPCv=0x800AF180; -findThread__Q23EGG6ThreadFP8OSThread=0x802434D8; OSInitMutex=0x801A7EAC; OSLockMutex=0x801A7EE4; OSUnlockMutex=0x801A7FC0; +OSCreateThread=0x801A9E84; +OSIsThreadTerminated=0x801A98BC; +OSSetSwitchThreadCallback=0x801A95AC; +OSInitMessageQueue=0x801A72FC; +OSDetachThread=0x801AA4EC; +OSCancelThread=0x801AA1D4; +onExit__Q23EGG6ThreadFv=0x80008E7C; +onEnter__Q23EGG6ThreadFv=0x80008E80; OSGetCurrentThread=0x801A98B0; MEMFindContainHeap=0x80198658; diff --git a/build/o_files.txt b/build/o_files.txt index 8c04483c6..aec3890a3 100644 --- a/build/o_files.txt +++ b/build/o_files.txt @@ -32,8 +32,12 @@ text_80239e10.o data_802a30ec.o eggStreamDecomp.o text_80242504.o -ctors_80244de0.o +data_802a3f90.o bss_80384348.o +eggThread.o +text_80243754.o +ctors_80244de0.o +bss_80384b6c.o sbss_80386ec0.o sdata2_80388d80.o eggVector.o @@ -44,7 +48,7 @@ text_80243d18.o ctors_80244e8c.o dtors_80244ea4.o rodata_80257824.o -data_802a3f90.o +data_802a3fd8.o bss_80384bf4.o sdata_803857f6.o sbss_80386f90.o diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 5fcb7c23f..48ed0e685 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -152,6 +152,13 @@ class Heap : public Disposer { //! static void* alloc(u32 size, int align, Heap* heap); + template static T* alloc(u32 count, Heap* heap, int align = 4) { + return reinterpret_cast(alloc(count * sizeof(T), align, heap)); + } + template static T* alloc(Heap* heap, int align = 4) { + return reinterpret_cast(alloc(sizeof(T), align, heap)); + } + //! @brief Returns the Disposer's parent heap. //! //! @details Initializes the static heap data for heap creation. diff --git a/source/egg/core/eggThread.cpp b/source/egg/core/eggThread.cpp new file mode 100644 index 000000000..47a25ee9a --- /dev/null +++ b/source/egg/core/eggThread.cpp @@ -0,0 +1,111 @@ +#include +#include +#include + +namespace EGG { + +nw4r::ut::List Thread::sThreadList; + +Thread::Thread(u32 stackSize, int msgCount, int prio, Heap* heap) { + if (!heap) + heap = Heap::sCurrentHeap; + + mContainingHeap = heap; + mStackSize = ROUND_DOWN(stackSize, 32); + mStackMemory = + reinterpret_cast(Heap::alloc(mStackSize, 32, mContainingHeap)); + EGG_ASSERT(mStackMemory, "eggThread.cpp", 70, "mStackMemory"); + + mOSThread = Heap::alloc(mContainingHeap, 32); + EGG_ASSERT(mOSThread, "eggThread.cpp", 80, "mOSThread"); + + OSCreateThread(mOSThread, start, + this, // argument for start + mStackMemory + mStackSize, // the created stackBase is the + // very highest value + mStackSize, prio, 1); + + mAlloctableHeap = nullptr; + setCommonMesgQueue(msgCount, mContainingHeap); +} + +Thread::Thread(OSThread* osThread, int msgCount) + : mContainingHeap(nullptr), mOSThread(osThread) { + mStackSize = (char*)osThread->stack_low - (char*)osThread->stack_high; + mStackMemory = osThread->stack_high; + + mAlloctableHeap = nullptr; + setCommonMesgQueue(msgCount, Heap::sCurrentHeap); +} + +Thread::~Thread() { + nw4r::ut::List_Remove(&sThreadList, this); + + if (mContainingHeap != nullptr) { + if (!OSIsThreadTerminated(mOSThread)) { + OSDetachThread(mOSThread); + OSCancelThread(mOSThread); + } + + Heap::free(mStackMemory, mContainingHeap); + Heap::free(mOSThread, mContainingHeap); + } + + Heap::free(mMesgBuffer, nullptr); +} + +Thread* Thread::findThread(OSThread* osThread) { + Thread* iterThread = nullptr; + + while ((iterThread = (Thread*)List_GetNext(&sThreadList, iterThread))) + if (iterThread->mOSThread == osThread) + return iterThread; + + return nullptr; +} + +void Thread::kandoTestCancelAllThread() { + Thread* iterThread = nullptr; // r30 + + while ((iterThread = (Thread*)List_GetNext(&sThreadList, iterThread))) { + OSThread* cur = OSGetCurrentThread(); + OSThread* thread = iterThread->mOSThread; + if (cur != thread) + OSCancelThread(thread); + } +} + +void Thread::initialize() { + // offsetof(Thread, nw4r::ut::Node) + List_Init(&sThreadList, 0x40); + OSSetSwitchThreadCallback(switchThreadCallback); +} + +void Thread::switchThreadCallback(OSThread* from, OSThread* to) { + // findThread inlined twice + Thread* fromThread = from ? findThread(from) : nullptr; // r30 + Thread* toThread = to ? findThread(to) : nullptr; // r31 + + if (fromThread != nullptr) + fromThread->onExit(); + + if (toThread != nullptr) + toThread->onEnter(); +} + +void Thread::setCommonMesgQueue(int msgCount, EGG::Heap* heap) { + mMesgCount = msgCount; + mMesgBuffer = Heap::alloc(msgCount, heap); + EGG_ASSERT(mMesgBuffer, "eggThread.cpp", 262, "mMesgBuffer"); + + OSInitMessageQueue(&mMesgQueue, mMesgBuffer, mMesgCount); + List_Append(&sThreadList, this); +} + +void* Thread::start(void* eggThread) { + return reinterpret_cast(eggThread)->run(); +} + +void* Thread::run() { return nullptr; } + +} // namespace EGG diff --git a/source/egg/core/eggThread.hpp b/source/egg/core/eggThread.hpp new file mode 100644 index 000000000..9cac52f28 --- /dev/null +++ b/source/egg/core/eggThread.hpp @@ -0,0 +1,143 @@ +/** + * @file + * @brief Thread class for extending. + */ + +#pragma once + +#include +#include +#include + +namespace EGG { + +class Heap; + +//------------------------------------------------------------- -------------- +//! @brief Wrapper for OSThread. +//--------------------------------------------------------------------------- +class Thread { +public: + virtual ~Thread(); //!< [vt+0x08] Destroys the EGG Thread, freeing all used + //!< memory and canceling the OSThread + + virtual void* run(); //!< [vt+0x0C] + virtual void onEnter(); /*{}*/ //!< [vt+0x14] + virtual void onExit(); /*{}*/ //!< [vt+0x10] + + Heap* mContainingHeap; //!< [+0x04] Heap to use for thread-specific + //!< allocations (in ctor: create stack and OSThread) + OSThread* mOSThread; //!< [+0x08] The OS thread this object wraps. Name + //!< confirmed by WS assert. + + OSMessageQueue mMesgQueue; //!< [+0x0C] sizeof=0x20 + OSMessage* mMesgBuffer; //!< [+0x2C] name confirmed WS assert + int mMesgCount; //!< [+0x30] + + char* mStackMemory; //!< [+0x34] The base (*beginning*) of the stack. + u32 mStackSize; //!< [+0x38] The size of the stack. + + Heap* mAlloctableHeap; //!< [0+x3C] When not NULL will override the heap used + //!< for allocations. (Note: Does not escape the + //!< Heap::sAllocatableHeap restriction.) + nw4r::ut::Node mLink; + //! @brief A constructor. + //! + //! @details Creates an EGG::Thread and OSThread from arguments. + //! + //! @param[in] stackSize: The size of the stack to create. + //! @param[in] msgCount: The maximum number of messages the + //! buffer can hold. + //! @param[in] prio: Priority of the OSThread to create. 0 is + //! highest priority; 31 is lowest priority. + //! The default thread has a + //! priority of 16. + //! + //! @param[in] heap: Heap to use for allocations. + //! + Thread(u32 stackSize, int msgCount, int prio, Heap* heap); + + //! @brief A constructor. + //! + //! @details Creates an EGG::Thread to wrap the provided OSThread. + //! + //! @param[in] osThread: OS thread to wrap. + //! @param[in] msgCount: The maximum number of messages the + //! buffer can hold. + //! + Thread(OSThread* osThread, int msgCount); + + //! @brief Find the (first) EGG::Thread that matches the provided osThread. + //! + //! @details Iterate through the static thread list (sThreadList) to find the + //! first EGG thread + //! that wraps the specified OS thread. + //! + //! @param[in] osThread: The OS thread to search for. + //! + //! @return The found EGG thread. + //! @retval NULL: No matching EGG thread was found. + //! + static Thread* findThread(OSThread* osThread); + + //! @brief Cancel all but the current thread. + //! + //! @details Iterates through registed threads in sThreadList, checking + //! against + //! OSGetCurrentThread(), then calling + //! OSCancelThread(). + //! + static void kandoTestCancelAllThread(); + + //! @brief Initialize EGG Thread. + //! + //! @details Initializes sThreadList and sets the SwitchThreadCallback to + //! Thread::switchThreadCallback. + //! + static void initialize(); + + //! @brief The callback for switching threads. + //! + //! @details Finds the associated EGG threads of the two OSThreads, and calls + //! onExit() + //! on the first thread and onEnter() on the next. + //! + //! @param[in] from: The last thread running. + //! @param[in] to: The next thread to run. + //! + //! @note Either threads may be null if there is no thread to run. + //! This function handles this accordingly + //! + static void switchThreadCallback(OSThread* from, OSThread* to); + + //! @brief Configures the message queue. + //! + //! @details - Allocates mMesgBuffer to msgCount * 4 + //! - Calls OSInitMessageQueue using mMesgQueue and + //! mMesgBuffer + //! - Appends this Thread to sThreadList + //! + //! @param[in] msgCount: The maximum number of messages the + //! buffer can hold. + //! The buffer size in bytes + //! will be msgCount + //!* 4. + //! + void setCommonMesgQueue(int msgCount, Heap* heap); + + //! @brief Starts a thread. + //! + //! @details Casts eggThread to EGG::Thread* and calls the virtual run + //! method. + //! + //! @param[in] eggThread: Pointer to an object that inherits + //! Thread. + //! + static void* start(void* eggThread); + +private: + // List of all registered threads. + static nw4r::ut::List sThreadList; +}; + +} // namespace EGG diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index 6eb4f3447..9b8eaa88a 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -4,6 +4,19 @@ extern "C" { #endif +struct OSThread { + char _00[0x304]; + char* stack_high; // 304 + char* stack_low; // 308 + char _30c[0x318 - 0x30c]; +}; + +typedef void* OSMessage; + +struct OSMessageQueue { + char _[0x20]; +}; + typedef struct { char _[0x18]; } OSMutex; @@ -12,6 +25,19 @@ void OSInitMutex(OSMutex*); void OSLockMutex(OSMutex*); void OSUnlockMutex(OSMutex*); +int OSCreateThread(OSThread* thread, void* (*callable)(void*), void* user_data, + void* stack, u32 stack_size, s32 prio, u16 flag); + +void OSCancelThread(OSThread* thread); +void OSDetachThread(OSThread* thread); +int OSIsThreadTerminated(OSThread* thread); +OSThread* OSGetCurrentThread(); + +typedef void (*OSSwitchFunction)(OSThread*, OSThread*); +OSSwitchFunction OSSetSwitchThreadCallback(OSSwitchFunction callable); + +void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); + #ifdef __cplusplus } #endif \ No newline at end of file From bb24e594d888e77a5735619c6894257ba510cd4b Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 15 Feb 2021 17:58:33 -0500 Subject: [PATCH 021/477] :bug: rk_types.h Check if NULL is defined --- source/rk_types.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/rk_types.h b/source/rk_types.h index f8ae790f3..b3042c665 100644 --- a/source/rk_types.h +++ b/source/rk_types.h @@ -2,7 +2,9 @@ #include +#ifndef NULL #define NULL 0 +#endif // Unknown types From e4e68d9bfe31a4f33fa857527c3da31935a79497 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 21 Feb 2021 10:20:44 -0500 Subject: [PATCH 022/477] :construction: Started SystemManager --- source/game/host_system/SystemManager.cpp | 47 ++++++ source/game/host_system/SystemManager.hpp | 167 ++++++++++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 source/game/host_system/SystemManager.cpp create mode 100644 source/game/host_system/SystemManager.hpp diff --git a/source/game/host_system/SystemManager.cpp b/source/game/host_system/SystemManager.cpp new file mode 100644 index 000000000..d033965c4 --- /dev/null +++ b/source/game/host_system/SystemManager.cpp @@ -0,0 +1,47 @@ +/*! + * @file + * @brief System Manager implementations. + */ + +#include +#include + +namespace System { + +static const char* sRegionCode = "RMCP"; +static const char* MAIN_SMAP = "/rel/RevoKartR.SMAP"; + + +SystemManager* SystemManager::sInstance; + +SystemManager* SystemManager::initStaticInstance(EGG::Heap* heap) { + return (sInstance = new SystemManager(heap)); +} +SystemManager::~SystemManager() { + delete[] this->buf_88; +} + +void SystemManager::LoadSymbols(OSModuleInfo* pModuleInfo) { + pModuleInfo = pModuleInfo != nullptr ? pModuleInfo : this->_B0; // r5 + + if (pModuleInfo == nullptr) + return; + + if (MAIN_SMAP != nullptr) // "/rel/RevoKartR.SMAP" + { + u32 map_size; + mpMap = SystemManager::sInstance->ripFromDisc(MAIN_SMAP, pModuleInfo, 0, &map_size, 0); + if (mpMap != nullptr) { + EGG::Exception::setMapFile(mpMap, map_size, pModuleInfo); + OSReport("[SYS] Load MapFile \"%s\" success.\n", MAIN_SMAP); + } else { + OSReport("[SYS] Load MapFile \"%s\" fail.\n", MAIN_SMAP); + } + } + + buf_88->_0 = 0; + buf_8C->_0 = 0; + buf_90->_0 = 0; +} + +} // namespace System diff --git a/source/game/host_system/SystemManager.hpp b/source/game/host_system/SystemManager.hpp new file mode 100644 index 000000000..eb675e96e --- /dev/null +++ b/source/game/host_system/SystemManager.hpp @@ -0,0 +1,167 @@ +/*! + * @file + * @brief System Manager + */ +#pragma once + +#include +#include + +#include +#include + +namespace System { + +class SystemManager // sizeof=4352, vt at 0x54 +{ +public: + static SystemManager* sInstance; + + struct SystemRipper { + u8 mPathBuf[64]; //!< [+0x00] Buffer for the ripped path to placed in. + EGG::Heap* pDvdRipperHeap; //!< [+0x40] Heap to use for DVD ripper methods. + bool bTopAllocDir; //!< [+0x44] if true? allocDir=1(ALLOC_DIR_TOP)negative : + //!< allocDir=2(ALLOC_DIR_BOTTOM)positive + u32* mRippedFileSizeOutput; //!< [+0x48] Output filesize pointer for ripped + //!< file. + u8* pManualRipperDestBuffer; //!< [+0x4C] Used if pDvdRipperHeap is NULL as + //!< the destination buffer. + u8* mRippedDestBuf; //!< [+0x50] Used as dest buffer in read function. + } mSystemRipper; + + //! @brief Initialize the static instance for the system manager. + //! + //! @param[in] pHeap Heap for the new instance to use. + //! + //! @return Pointer to the new system manager. + //! + static SystemManager* initStaticInstance(EGG::Heap* PHeap); + + //! @brief [vt(+0x54)+0x08] Destroy the system manager. + //! + virtual ~SystemManager(); + + SystemManager(EGG::Heap* heap); + void NDEVArgParse(); + void LoadSymbols(OSModuleInfo* osmoduleinfo); + void setupSystem(EGG::Heap* heap); + + //! @brief Properly shutdown the Wii. + void shutdownSystem(); + + //! @brief Properly return to the Wii main menu. + //! + void returnToMenu(); + + //! @brief Properly restart the Wii. + //! + void restart(); + + void setSystemPowerCallbacks(); // disable doRestart and doShutdown and set + // Power / Reset callbacks + + void handlePowerState(); // handle doShutdown and doRestart + const char* getRegionCode() const; // For this build, return 'RMCP' + u32 sanitizeGameIDRegion(u32 gameName) const; + + // TODO + u8* ripFromDiscAsync(const char path[64], EGG::Heap* heap, bool allocTop, + u32* fsizeOutput, u8* dst); + u8* ripFromDisc(const char path[64], EGG::Heap* heap, bool allocTop, + u32* fsizeOutput, u8* dst); + + //! @brief Processes a rip request (used as function for ripping task thread) + //! + //! @details + //! If pDvdRipperHeap is set, the EGG::DvdRipper will create a + //! buffer form the heap. The allocation direction is contrlled by + //! bTopAllocDir. Otherwise, pManualRipperDestBuffer will be used. + //! + //! @post If set, the value pointed to by mRippedFileSizeOutput will be set. + //! + //! @return A pointer to the ripped file in memory. + //! + // Internal + // static void* processRipRequest(); + + //! @brief + static u8* ripFromDisc(const char path[64], EGG::Heap* heap, bool allocTop, + u32* fsizeOutput); + static u8* ripFromDisc(const char path[64], u8* dst, u32* fsizeOutput); + static bool isPal50(); + + //! @brief Stop the motors of all controllers on all channels. + //! + static void stopAllControllerMotors(); + + u32 mAspectRatio; //!< [+0x58] 1 - 16x9 + u32 mLanguage; //!< [+0x5C] True language? + u32 mResourceLanguage; //!< [+0x60] Same as above, but Dutch -> English + u32 mProgressiveMode; //!< [+0x64] 1 - on + u32 mEuRgb60Mode; //!< [+0x68] 1 - EURGB60 + int mb50Fps; //!< [+0x6C] Name is really a GUESS. An int. Possible some vi + //!< type? PAL and EURGB set it to true but it's already true. So + //!< possibly isEuro too? + u32 mLaunchType; //!< [+0x70] Determiined by launch/return code + + f32 mFps60; // 74 set 59.94f in CT + f32 mFpsCurrent; // 78 set 59.94f in CT, though always to 50 here? + f32 mFrameSize60; // 0x7C for 60fps: 16.68335f (1000/59.94) + + bool bIsNdev; //!< [+0x80] + // 81 unseen. pad? + u16 _82; // determined by return/launchcode + u32 mMatchingArea; //!< [+0x84] set 15 in ct. everywhere: 2; australia/new + //!< zealand: 3 + u8* buf_88; //!< [+0x88] 32 bytes + u8* buf_8C; //!< [+0x8C] 32 bytes + u8* buf_90; //!< [+0x90] 32 bytes + + u32 _94; + char* pNdevArgument; //!< [+0x98] first argument when loaded from ndev @see + //!< NDEVArgParse max size 48 + u32 mStaticTextStart; // 9C + u32 mStaticTextEnd; // A0 8038917C + u32 mStaticTextSize; // A4 8038917C - 0x80004000 + u32 _A8; + EGG::Heap* mHeap; //!< [+0xAC] arg of ct. maybe another so name for now + OSModuleInfo* _B0; + bool doShutDown; //!< [+0xB4] If true, system will shut down in next + //!< handlePowerState() + bool doRestart; //!< [+0xB5] If true, system will restart in next + //!< handlePowerState() + bool _B6; // 0 in ct + bool _B7; // if it's not enabled, power/reset callbacks won't shut down system + + OSMessageQueue _B8; //!< [+0xB8] sizeof=0x20 + OSMessage _D8[4]; //!< [+0xD8] + EGG::TaskThread* mTaskThread; //!< [+0xE8] + OSThread* pCreatedInThread; //!< [+0xEC] zeroed then OSGetThread() in ct + + u8 mYear; //!< [+0xF0] clamped to 99 in ct + u8 mMonth; //!< [+0xF1] clamped to 12 in ct + u8 mDayOfMonth; //!< [+0xF2] clamped to 31 in ct + u8 _F3; // 0 in ct + u8 _F4; // 0 in ct + struct { + u32 ID; // F8 + u8 unk[0x10fc - 4]; + u16 mLatitude; // 10FC + u16 mLongitude; // 10FE + + //! @brief The check for region has to be in this function, as codwarrior + //! doesn't optimize this as much as it would had + //! the comparison been outside. + inline bool isRegion(u32 region) const { +#ifdef NTSCJ + if (ID != -1) + return ID >> 24 == region; + return -1; +#else + return ID == -1 ? 0 : ID >> 24 == region; +#endif + } + } mSimpleAddressData; // F8 -> 1100 +}; + +} // namespace System From b7bf3755db721134a87c7722cbe134031b0f4155 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 26 Feb 2021 18:06:01 -0500 Subject: [PATCH 023/477] :sparkles: Added slices system --- artifacts/pal/segments.csv | 14 ++ system/gen_asm.py | 232 +++++++++++++++++++++++++++++++ system/ppc_dis.py | 271 +++++++++++++++++++++++++++++++++++++ system/slices.csv | 16 +++ 4 files changed, 533 insertions(+) create mode 100644 artifacts/pal/segments.csv create mode 100644 system/gen_asm.py create mode 100644 system/ppc_dis.py create mode 100644 system/slices.csv diff --git a/artifacts/pal/segments.csv b/artifacts/pal/segments.csv new file mode 100644 index 000000000..11e833132 --- /dev/null +++ b/artifacts/pal/segments.csv @@ -0,0 +1,14 @@ +name,type,start,end +init,code,80004000,80006460 +extab,data,80006460,80006A20 +extabindex,data,80006A20,800072C0 +text,code,800072C0,80244DE0 +ctors,data,80244DE0,80244E90 +dtors,data,80244EA4,80244EAC +rodata,data,80244EC0,80258580 +data,data,80258580,802A4040 +bss,bss,802A4080,80384C00 +sdata,data,80384C00,80385FC0 +sbss,bss,80385FC0,80386FA0 +sdata2,data,80386FA0,80389140 +sbss2,bss,80389140,8038917C diff --git a/system/gen_asm.py b/system/gen_asm.py new file mode 100644 index 000000000..89284df8e --- /dev/null +++ b/system/gen_asm.py @@ -0,0 +1,232 @@ +import struct + + +def read_u8(f): + return struct.unpack("B", f.read(1))[0] + +def read_u32(f): + return struct.unpack(">I", f.read(4))[0] + +def read_u16(f): + return struct.unpack(">H", f.read(2))[0] + +class CSV: + def __init__(self, name): + lines = open(name).readlines() + self.header = lines[0].strip().split(',') + self.body = [x.strip().split(',') for x in lines[1:]] + + def get_row(self, index): + row = {} + for i, value in enumerate(self.body[index]): + label = self.header[i] + row[label] = value + return row + + def rows(self): + for i in range(len(self.body)): + yield self.get_row(i) + +class Segment: + def __init__(self, begin : int, end : int): + assert isinstance(begin, int) and isinstance(end, int) + self.begin = begin + self.end = end + + def __repr__(self): + return "(%s, %s)" % (hex(self.begin), hex(self.end)) + + def empty(self): + return self.begin == self.end + + def size(self): + return self.end - self.begin + +def read_segments_iter(name): + for row in CSV(name).rows(): + yield row["name"], Segment(int(row["start"], 16), int(row["end"], 16)) + +def read_segments(name): + result = {} + for name, segment in read_segments_iter(name): + result[name] = segment + return result + +# Limitation: slices must be ordered +def read_slices(name): + result = [] + for row in CSV(name).rows(): + if not row.pop("enabled"): + continue + + name = row.pop("name") + segments = {} + + for cell, value in row.items(): + segment_attributes = ["Start", "End"] + seg_name = "" + seg_type = "" + for attr in segment_attributes: + if cell.endswith(attr): + seg_type = attr + seg_name = cell[:-len(attr)] + assert seg_name and seg_type + + if not value: continue + + if not seg_name in segments: + segments[seg_name] = Segment(0, 0) + + if seg_type == "Start": + segments[seg_name].begin = int(value, 16) + elif seg_type == "End": + segments[seg_name].end = int(value, 16) + + print("#### %s %s" % (name, segments)) + result.append((name, segments)) + return result + +class DolBinary: + def __init__(self, file): + file = open(file, 'rb') + text_ofs = [read_u32(file) for _ in range(7)] + data_ofs = [read_u32(file) for _ in range(11)] + + text_vaddr = [read_u32(file) for _ in range(7)] + data_vaddr = [read_u32(file) for _ in range(11)] + + text_size = [read_u32(file) for _ in range(7)] + data_size = [read_u32(file) for _ in range(11)] + + self.text_segs = [Segment(x, x + y) for x, y in zip(text_vaddr, text_size)] + self.data_segs = [Segment(x, x + y) for x, y in zip(data_vaddr, data_size)] + + bss_vaddr = read_u32(file) + bss_size = read_u32(file) + + self.bss = Segment(bss_vaddr, bss_vaddr + bss_size) + + self.entry_point = read_u32(file) + + max_vaddr = max(x.end for x in self.text_segs + self.data_segs) + self.image_base = 0x80000000 + self.image = bytearray(max_vaddr - self.image_base) + + for i in range(7): + if not text_size[i]: + continue + + file.seek(text_ofs[i]) + data = file.read(text_size[i]) + for j in range(text_size[i]): + self.image[text_vaddr[i] + j - self.image_base] = data[j] + + for i in range(11): + if not data_size[i]: + continue + + file.seek(data_ofs[i]) + data = file.read(data_size[i]) + for j in range(data_size[i]): + self.image[data_vaddr[i] + j - self.image_base] = data[j] + + + +def format_gap(name, gap): + return "asm/%s_%s.s" % (name, hex(gap.begin)[2:]) + +base = DolBinary("../artifacts/pal/main.dol") +segments = read_segments("../artifacts/pal/segments.csv") +slices = read_slices("slices.csv") + + +from ppc_dis import * +import sys + +def format_segname(name): + if "extab" in name: return name + "_" + return '.' + name + +def disasm(name, base, seg, is_data): + file = open("../" + format_gap(name, seg), 'w') + original_stdout = sys.stdout + sys.stdout = file + perm = "wa" + if name == "text" or name == "init": + perm = "ax" + + # if "bss" in name: + # perm = "ba" + + if name == "rodata" or "2" in name: + perm = perm.replace('w', '') + + print("\n.include \"macros.inc\"") + + file.write("\n.section %s, \"%s\" # 0x%08X - 0x%08X\n" % (format_segname(name), perm, seg.begin, seg.end)) + if "bss" in name: + print(".skip 0x%x" % seg.size()) + elif name != "text" and name != "init": + for i in range(seg.begin, seg.end, 4): + def read_u32b(filecontent, offset): + return (filecontent[offset + 0] << 24) | (filecontent[offset + 1] << 16) | (filecontent[offset + 2] << 8) | filecontent[offset + 3] + + if seg.end - i >= 4: + print(".4byte 0x%08X" % read_u32b(base.image, i- 0x80000000)) + else: + for j in range(i, seg.end): + print(".byte 0x%02x" % base.image[j - 0x80000000]) + else: + disasm_iter(base.image, seg.begin - 0x80000000, seg.begin, seg.size(), disassemble_callback) + sys.stdout = original_stdout + +start_seg = {} +for name, seg in segments.items(): + start_seg[name] = Segment(0, seg.begin) + +end_seg = {} +for name, seg in segments.items(): + end_seg[name] = Segment(seg.end, 0) + +all_slices = slices + [('', end_seg)] + +asm_files = [] +o_files = [] +cpp_files = [] + +import shutil +import os + +try: shutil.rmtree("../asm") +except: pass + +try: os.mkdir("../asm") +except: pass + +with open('../asm/macros.inc', 'w') as file: + file.write('# PowerPC Register Constants\n') + for i in range(0, 32): + file.write(".set r%i, %i\n" % (i, i)) + for i in range(0, 32): + file.write(".set f%i, %i\n" % (i, i)) + for i in range(0, 8): + file.write(".set qr%i, %i\n" % (i, i)) + +last_segments = start_seg +for obj_file, slice in all_slices: + for name, segment in slice.items(): + if last_segments[name].end != segment.begin: + # There's a gap! + + print("[.%s] Gap from %x to %x" % (name, last_segments[name].end, segment.begin)) + gap_seg = Segment(last_segments[name].end, segment.begin) + print(format_gap(name, gap_seg)) + asm_files.append(format_gap(name, gap_seg)) + o_files.append(format_gap(name, gap_seg).replace("asm/", "").replace(".s", ".o")) + disasm(name, base, gap_seg, False) + last_segments[name] = segment + if obj_file: + o_files.append(obj_file.replace(".cpp", ".o").replace(".c", ".o")) + cpp_files.append(obj_file) + +open('../build/o_files.txt', 'w').write('\n'.join(o_files)) \ No newline at end of file diff --git a/system/ppc_dis.py b/system/ppc_dis.py new file mode 100644 index 000000000..c178aa52f --- /dev/null +++ b/system/ppc_dis.py @@ -0,0 +1,271 @@ +# Based on https://gist.github.com/camthesaxman/a36f610dbf4cc53a874322ef146c4123 +# +# Changes: +# - Blacklisted more instructionss +# - Adjusted output to take up fewer bytes +# - mwasm support + +from capstone import * +from capstone.ppc import * + + +def sign_extend_16(value): + if value > 0 and (value & 0x8000): + value -= 0x10000 + return value + +def sign_extend_12(value): + if value > 0 and (value & 0x800): + value -= 0x1000 + return value + + +cs = Cs(CS_ARCH_PPC, CS_MODE_32 | CS_MODE_BIG_ENDIAN) +cs.detail = True +cs.imm_unsigned = False + +blacklistedInsns = { + # Unsupported instructions + PPC_INS_VMSUMSHM, PPC_INS_VMHADDSHS, PPC_INS_XXSLDWI, PPC_INS_VSEL, + PPC_INS_XVSUBSP, PPC_INS_XXSEL, PPC_INS_XVMULSP, PPC_INS_XVDIVSP, + PPC_INS_VADDUHM, PPC_INS_XXPERMDI, PPC_INS_XVMADDASP, PPC_INS_XVMADDMSP, + PPC_INS_XVCMPGTSP, PPC_INS_XXMRGHD, PPC_INS_XSMSUBMDP, PPC_INS_XSTDIVDP, + PPC_INS_XVADDSP, PPC_INS_XVCMPEQSP, PPC_INS_XVMSUBASP, PPC_INS_XVCMPGESP, + + PPC_INS_XSMSUBADP, PPC_INS_XSMADDADP, PPC_INS_XSCMPODP, PPC_INS_XVTDIVSP, + PPC_INS_VMRGHB, PPC_INS_VADDUBM, PPC_INS_XSMADDMDP, PPC_INS_XSCMPUDP, + PPC_INS_XVNMADDASP, PPC_INS_VADDUWM, PPC_INS_XVMSUBMSP, + PPC_INS_XSNMSUBMDP, PPC_INS_XSNMSUBADP, PPC_INS_XVCMPEQDP, + PPC_INS_VPKUHUM, PPC_INS_XVCMPGTDP, PPC_INS_XVMADDADP, PPC_INS_XVMSUBMDP, + PPC_INS_VMADDFP, PPC_INS_XXSPLTW, PPC_INS_XVMSUBADP, PPC_INS_XVCMPGEDP, + PPC_INS_XVNMSUBMDP, PPC_INS_XVMADDMDP, PPC_INS_XXMRGHW, + + + # Instructions that Capstone gets wrong + PPC_INS_MFESR, PPC_INS_MFDEAR, PPC_INS_MTESR, PPC_INS_MTDEAR, PPC_INS_MFICCR, PPC_INS_MFASR +} + +labels = set() +labelNames = {} + + +def addr_to_label(addr, insn_addr): + if addr in labels: + if addr in labelNames: + return labelNames[addr] + else: + return "lbl_%08X" % addr + else: + return hex(addr - insn_addr) + +def insn_to_text(insn, raw): + # Probably data, not a real instruction + if insn.id == PPC_INS_BDNZ and (insn.bytes[0] & 1): + return None + if insn.id in {PPC_INS_B, PPC_INS_BL, PPC_INS_BDZ, PPC_INS_BDNZ}: + return "%s %s" % (insn.mnemonic, addr_to_label(insn.operands[0].imm, insn.address)) + elif insn.id == PPC_INS_BC: + branchPred = '+' if (insn.bytes[1] & 0x20) else '' + if insn.operands[0].type == PPC_OP_IMM: + return "%s%s %s" % (insn.mnemonic, branchPred, addr_to_label(insn.operands[0].imm, insn.address)) + elif insn.operands[1].type == PPC_OP_IMM: + return "%s%s %s, %s" % (insn.mnemonic, branchPred, insn.reg_name(insn.operands[0].value.reg), addr_to_label(insn.operands[1].imm, insn.address)) + + # Sign-extend immediate values because Capstone is an idiot and doesn't do that automatically + if insn.id in {PPC_INS_ADDI, PPC_INS_ADDIC, PPC_INS_SUBFIC, PPC_INS_MULLI} and (insn.operands[2].imm & 0x8000): + return "%s %s, %s, %i" % (insn.mnemonic, insn.reg_name(insn.operands[0].reg), insn.reg_name(insn.operands[1].value.reg), insn.operands[2].imm - 0x10000) + elif (insn.id == PPC_INS_LI or insn.id == PPC_INS_CMPWI) and (insn.operands[1].imm & 0x8000): + return "%s %s, %i" % (insn.mnemonic, insn.reg_name(insn.operands[0].reg), insn.operands[1].imm - 0x10000) + # cntlz -> cntlzw + elif insn.id == PPC_INS_CNTLZW: + return "cntlzw %s" % insn.op_str + elif insn.id == PPC_INS_MTICCR: + return 'mtictc %s' % insn.op_str + # Dunno why GNU assembler doesn't accept this + elif insn.id == PPC_INS_LMW and insn.operands[0].reg == PPC_REG_R0: + return '.4byte 0x%08X /* illegal %s %s */' % (raw, insn.mnemonic, insn.op_str) + return '%s %s' % (insn.mnemonic, insn.op_str) + +def disasm_ps(inst): + RA = ((inst >> 16) & 0x1f) + RB = ((inst >> 11) & 0x1f) + FA = ((inst >> 16) & 0x1f) + FB = ((inst >> 11) & 0x1f) + FC = ((inst >> 6) & 0x1f) + FD = ((inst >> 21) & 0x1f) + FS = ((inst >> 21) & 0x1f) + IX = ((inst >> 7) & 0x7) + WX = ((inst >> 10) & 0x1) + + opcode = (inst >> 1) & 0x1F + if opcode == 6: # doesn't seem to be used + mnemonic = 'psq_lux' if inst & 0x40 else 'psq_lx' + return '%s f%i, r%i, r%i, %i, qr%i' % (mnemonic, FD, RA, RB, WX, IX) + if opcode == 7: + mnemonic = 'psq_stux' if inst & 0x40 else 'psq_stx' + return '%s f%i, r%i, r%i, %i, qr%i' % (mnemonic, FS, RA, RB, WX, IX) + if opcode == 18: + return 'ps_div f%i, f%i, f%i' % (FD, FA, FB) + if opcode == 20: + return 'ps_sub f%i, f%i, f%i' % (FD, FA, FB) + if opcode == 21: + return 'ps_add f%i, f%i, f%i' % (FD, FA, FB) + if opcode == 23: + return 'ps_sel f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 24: + return 'ps_res f%i, f%i' % (FD, FB) + if opcode == 25: + return 'ps_mul f%i, f%i, f%i' % (FD, FA, FC) + if opcode == 26: + return 'ps_rsqrte f%i, f%i' % (FD, FB) + if opcode == 28: + return 'ps_msub f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 29: + return 'ps_madd f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 30: + return 'ps_nmsub f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 31: + return 'ps_nmadd f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 10: + return 'ps_sum0 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 11: + return 'ps_sum1 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 12: + return 'ps_muls0 f%i, f%i, f%i' % (FD, FA, FC) + if opcode == 13: + return 'ps_muls1 f%i, f%i, f%i' % (FD, FA, FC) + if opcode == 14: + return 'ps_madds0 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + if opcode == 15: + return 'ps_madds1 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) + + opcode = (inst >> 1) & 0x3FF + if opcode == 40: + return 'ps_neg f%i, f%i' % (FD, FB) + if opcode == 72: + return 'ps_mr f%i, f%i' % (FD, FB) + if opcode == 136: + return 'ps_nabs f%i, f%i' % (FD, FB) + if opcode == 264: + return 'ps_abs f%i, f%i' % (FD, FB) + if opcode in {0, 32, 64, 96}: + mnemonics = ['ps_cmpu0', 'ps_cmpo0', 'ps_cmpu1', 'ps_cmpo1'] + mnemonic = mnemonics[(inst >> 6) & 3] + i = (inst & 0x03800000) >> 23 + return '%s cr%i, f%i, f%i' % (mnemonic, i, FA, FB) + if opcode == 528: + return 'ps_merge00 f%i, f%i, f%i' % (FD, FA, FB) + if opcode == 560: + return 'ps_merge01 f%i, f%i, f%i' % (FD, FA, FB) + if opcode == 592: + return 'ps_merge10 f%i, f%i, f%i' % (FD, FA, FB) + if opcode == 624: + return 'ps_merge11 f%i, f%i, f%i' % (FD, FA, FB) + if opcode == 1014: + if not (inst & 0x03e00000): + if (inst & 1) == 0: + return 'dcbz_l r%i, r%i' % ((inst & 0x001f0000) >> 16, (inst & 0x0000f800) >> 11) + return None + +def disasm_ps_mem(inst, idx): + RA = ((inst >> 16) & 0x1f) + RS = ((inst >> 21) & 0x1f) + I = ((inst >> 12) & 0x7) + W = ((inst >> 15) & 0x1) + disp = sign_extend_12(inst & 0xFFF) + if idx == 56: + mnemonic = 'psq_l' + if idx == 57: + mnemonic = 'psq_lu' + if idx == 60: + mnemonic = 'psq_st' + if idx == 61: + mnemonic = 'psq_stu' + return '%s f%i, %i(r%i), %i, qr%i' % (mnemonic, RS, disp, RA, W, I) + +def disasm_fcmp(inst): + crd = (inst & 0x03800000) >> 23 + a = (inst & 0x001f0000) >> 16 + b = (inst & 0x0000f800) >> 11 + return 'fcmpo cr%i, f%i, f%i' % (crd, a, b) + +def disasm_mspr(inst, mode): + if (inst & 1): + return None + d = (inst & 0x03e00000) >> 21 + a = (inst & 0x001f0000) >> 16 + b = (inst & 0x0000f800) >>11 + spr = (b << 5) + a + if mode: + return 'mtspr 0x%X, r%i' % (spr, d) + else: + return 'mfspr r%i, 0x%X' % (d, spr) + +def disasm_mcrxr(inst): + if (inst & 0x007ff801): + return None + crd = (inst & 0x03800000) >> 23 + return 'mcrxr cr%i' % crd + +# Disassemble code +def disassemble_callback(filecontent, address, offset, insn, bytes): + prefixComment = '/* %08X %02X %02X %02X %02X */' % (address, bytes[0], bytes[1], bytes[2], bytes[3]) + #prefixComment = '/* %08X */' % address + asm = None + def read_u32b(filecontent, offset): + return (filecontent[offset + 0] << 24) | (filecontent[offset + 1] << 16) | (filecontent[offset + 2] << 8) | filecontent[offset + 3] + + raw = read_u32b(filecontent, offset) + if insn != None: + asm = insn_to_text(insn, raw) + else: # Capstone couldn't disassemble it + idx = (raw & 0xfc000000) >> 26 + idx2 = (raw & 0x000007fe) >> 1 + # mtspr + if idx == 31 and idx2 == 467: + asm = disasm_mspr(raw, 1) + # mfspr + elif idx == 31 and idx2 == 339: + asm = disasm_mspr(raw, 0) + # mcrxr + elif idx == 31 and idx2 == 512: + asm = disasm_mcrxr(raw) + # fcmpo + elif idx == 63 and idx2 == 32: + asm = disasm_fcmp(raw) + # Paired singles + elif idx == 4: + asm = disasm_ps(raw) + elif idx in {56, 57, 60, 61}: + asm = disasm_ps_mem(raw, idx) + if asm == None: + asm = '.4byte 0x%08X /* unknown instruction */' % raw + print('%s\t%s' % (prefixComment, asm)) + + +''' +entryPoint = base.entry_point +# Add entry point +labels.add(entryPoint) +labelNames[entryPoint] = '__start' +''' + +# Calls callback for every instruction in the specified code section +def disasm_iter(filecontent, offset, address, size, callback): + if size == 0: + return + start = address + end = address + size + while address < end: + code = filecontent[offset + (address-start) : offset + size] + for insn in cs.disasm(code, address): + address = insn.address + if insn.id in blacklistedInsns: + callback(filecontent, address, offset + address - start, None, insn.bytes) + else: + callback(filecontent, address, offset + address - start, insn, insn.bytes) + address += 4 + if address < end: + o = offset + address - start + callback(filecontent, address, offset + address - start, None, filecontent[o : o + 4]) + address += 4 \ No newline at end of file diff --git a/system/slices.csv b/system/slices.csv new file mode 100644 index 000000000..5f69ffc4e --- /dev/null +++ b/system/slices.csv @@ -0,0 +1,16 @@ +enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End +1,dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, +1,rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, +,rxFrameHeap.c,,,,,,,80199430,801998A4,,,,,,,,,,,,,,,,,, +1,rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, +1,eggArchive.cpp,,,,,,,8020F6EC,0x8020FCC4,,,,,,,802A2680,802A268C,803832D8,0x803832E4,,,80386D80,80386D84,,,, +1,eggDisposer.cpp,,,,,,,8021A0F0,8021A1B8,,,,,,,802A2B48,802A2B54 ,,,,,,,,,, +1,eggGraphicsFifo.cpp,,,,,,,80229540,802296A8,,,,,,,802A30B0,802A30BC,,,,,0x80386E90,0x80386E99,,,, +1,eggHeap.cpp,,,,,,,802296A8,80229FAC,,,,,80257740,80257824,802A30C0,0x802a30ec,80384320,0x80384348,,,80386EA0,80386EC0,80388D68 ,80388D80,, +1,eggQuat.cpp,,,,,,,80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, +1,eggStreamDecomp.cpp,,,,,,,80242498,80242504,,,,,,,802A3F78,802A3F90,,,,,,,,,, +,eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,80386F60,80386F64,,,, +1,eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, +1,eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,80384B70,80384BF4,,,80386F78,80386F90,803890F8 ,80389104,, +,eggXfb.cpp,,,,,,,80244160,80244200,,,,,,,,,,,,,,,,,, +,eggXfbManager.cpp,,,,,,,80244200,802443AC,,,,,,,,,,,,,,,,,, From 89d75920ad8fba677ff8ae77603b69be347093ab Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 26 Feb 2021 18:15:01 -0500 Subject: [PATCH 024/477] :memo: Updated README to include contribution and .rel information --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 33b63388e..e5d50814f 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,13 @@ While the original game was written and compiled as C++03, several modern C++ fe Every fully understood piece of reverse engineered data has been documented in a consistent doxygen style. ## Building -Create a root-level folder named `tools`. Place elf2dol, mwldeppc and mwcceppc in it. Then run `python3 build.py` \ No newline at end of file +- Create a root-level folder named `tools`. Place elf2dol, mwldeppc and mwcceppc in it. Then run `python3 build.py` +- Place a copy of Mario Kart Wii's PAL .dol file in `artifacts/pal` as `main.dol` + +## Contributing +- Do not manually adjust assembly files; rather add a new entry to [system/slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv). +- Entries must be sorted in the spreadsheet (current limitation). +- Run `gen_asm.py` from the `system` directory to regenerate assembly segments. + +## .rel support +Most of Mario Kart Wii's game code is located inside a relocatable module (StaticR.rel for release builds). Currently, the decompilation targets only the static module (the .dol file). The `relsplit` branch of the repository hosts work on getting the rel incorporated into the decompilation. \ No newline at end of file From 07044ffb912b320b1937d59d5713c5e24e81c440 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 26 Feb 2021 18:15:46 -0500 Subject: [PATCH 025/477] :construction: Added *.pyc exclusion to .gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c986aceef..e12ad2d32 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,7 @@ compiler_log.txt .vs -out \ No newline at end of file +out + +# Python cache/compiled modules +*.pyc From 5023357df9abc5650ba59c6c311bd7b76aa48b33 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 26 Apr 2021 19:50:31 -0600 Subject: [PATCH 026/477] :construction: Raw .rel repacking Co-authored-by: Underscore-tZ Co-authored-by: name zatchiian --- system/dol_rel.bin | Bin 0 -> 741360 bytes system/rel_abs.bin | Bin 0 -> 176848 bytes system/rel_header.bin | Bin 0 -> 76 bytes system/rel_repack.py | 371 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 371 insertions(+) create mode 100644 system/dol_rel.bin create mode 100644 system/rel_abs.bin create mode 100644 system/rel_header.bin create mode 100644 system/rel_repack.py diff --git a/system/dol_rel.bin b/system/dol_rel.bin new file mode 100644 index 0000000000000000000000000000000000000000..a9f0259ccef181dbe19902e60b4e293fcdb70404 GIT binary patch literal 741360 zcmdSCO>A7*l_nT3NGX|VRW;j|({v{73};3;HNdavDk>;8Pmks(GDO?OL`@EAY#~XGM4od@SEm;9dtS6E8Mk7s7Rq11|o+7j=EB zCVs_MZ8q>p@&n!!{=jk<4{D8U{QNoN$AUf-e44>m_ADL@TCS~-YpXUL(aUaQYPd(^$vy z@6_&O{B6Ilj{iu;e_C-p1o@dqL$y)G`y6n_-&}1x=z~Fbo(;h+9yfnX=@pFEa z{BCRgUD%2IySCTBV%QJYwJxsHUIU8*UjI_9sr4?^Iss?3PUJ&v_LBTM&A6FUR;P z;g@Q2)~Eg?*(K&J$M~>weP8mD?Pr$ z;=fYAl-9SrQAbakzm&~iy0EMHvths4BDas@y#qO@cc<2pynum6yw>N#zQ>`@vGJl2 z5P^gFL$&9Ih0)RNIXC3EqhD!N0a`dVSv43-7x_h2 z$vD}BJcbGP_|3Zgvl8;I3`*XC9*(-n`qIOP9*W;~@ZYX&^y78MYwWKB>xJt40P7F= z+^tP;d@*Ht03H9kA>VFsUHXkgzK_7I(Em5AqlR;V%PzQXdyJS~8!60#r5D;)pH zk?~KCio`F?-_?4|ciMRj`z+yX;HbmGd;D{G%n##7G=H|%jrj@JdbKvBH$fPVTy;ve#8d}Y4mgFVELcG38r{ylvy*GO$Q_&LsJ z(a%v2*d_R{C_c8}FZy>Y_SH`4)u|10{6}|;18N>&T<_uJhwJk1Y2$0B%PD@zo`5$q z{>m@#?PS-}`Xjs!{?=;@eIju15f0c4y6qD5iahP$D?0THeN@kXvmO51Hs9Jv*k`2X z`ZR*PzIXj=X-mwj_Cae2S*VuY*oMtk*aD>9y+_E_;>h)g>q8 z(m2?e5PDC_&m*;lV6so-1LE~Q_sjpp{*Z7PdIv7J47)MkZ+d?NRQ&){JpydTJkQ^W z`OE0T%}4i3<@{GHXE*8t_T}Jjxfyp}&wlLG+{bi7|E~Rl{!IE~zcCz({tNLcJxl1i zJQi_Z#@}PvX(7fh;69G=Q|@DmKj8LPrqK)s+IrGL!ly6YL{DUZl6 z_;lrud-ZM2I}AG>#(3}#zDW+}737X}bk2eGRBs@!&YdV9<*x7ah~YwL;hI`zhU1} zJRsf#ufv}7(^4^<`QujD+x*wu_f+&x`_aqVJNJj}*XY$_+p!Vn8Dp{EHo`xR7wo+w z`!7Hb`egxqBl9J<&-GL6lkAr4!~UA{PqR-OeJtpM#~ugR7QPqwUf`R7Z$f_BwT<-+ zZ)Wy=l;JbNx5B@B(2IUeb#AYi>5soJUB~ixOYv=gM?a2w-jIFA>Ptyp*|}P$`s2@W zo9v1@5pkC4nc}WKG%y+Zr1+Un{3D*n`{{#Od=4D;(+iz{UO*iRJC2Ia4*X#|TK*?t z*CzvCu|BBF@N>cb(@^mO+4Hri+Efs*824wvOJ(mFUCFPPIwdblyQ!HiF%d% zmG(9F=e_zA{ZrX*rt!(%DSy*^_iN?+Y1H51!Xnl)946kReul!|T{aFV{GCJNfWqIJ zG7h++xIw=|KHuSf2Z-yA3-&>Aoe}XJovFM6;23Tef?nME1>$=r%yZ{@~Dpm zE_p+~+VfGMIunVH>n-~K4PCzx^y%7!<^dO+ z_BqytlJ4s_g8wwmLAcK8^!0P-&Ct(&gWSTK=*z9Qo(uK#ZD7CPb@msAIsZ)ly6RfR ze(Rc_>@ZPm$iEBFhyGnC)W=?&a=&zOs@M;@<7J_k6W#`$*L+@u=Sj}z*|7Iq*txv#wLWuUKh_7zH;cN;_~AIgLeI%AP6R$tD6b)}?Xpr^ zmV6sMd6N9DJEz)jU^nd13cLDzptP6w5B5>E+k(d9+(LK<`P*;L`?A{->_oYzLcS&F zZ+!a{y83^NcfP+kKhcxCDL&6z@8Q|&6_-w-CwY&~p+7!_p5*=P9Qwg2^tAq#?7I+g zvk>Ps3(>zV6wgnYAM4{>fbo2~J&(elj=K)xh<-xc%3pKXAL-vt(wcrL!Gd0=!L%bWVsUE@qMb?1M1UH;GgV%dKN%U&C$U|K`Yb0&tD^zh&JrTqk~){gB~kw%(5P+y|Zd?)$mTh2y}F z13wJ>(9fYNblneKL_a5apVU^xuN<%EP#3mQFUWt}_gh#et(Vb@F6Up2`w_q~$Y1-{ zmsF=~|9VPwS~&C}J}JL(J=*Iq;{7oEcv!m|{8;ZfUVajP;n-g}?;c}MYe`tr`o6%j@r@a4NjPrLu?H}Nm_}<9YO?2-sXUR{w=HvOsT-_Hm z92a!$3z*js{^ER{gABz!514SvvlMYWWdGuMd{=QeRGXInhtNMUK8MiX8(xzfOQFva z{N(E!LHGVMgt(>LW2x!1cgcXMs0acmE6M!~ySV-A=66 z3Hx9lmL8q@ambbO$N62#lgro8ywmAZ!O#5XUh%mt_kG%THLYv>)t&ImD(pwScj9?v z2k(KJ3hKK_Z{7M|Dsd7 zBYHRb(KNpcc|FhZ(!u#O@v(T1WGworvEVmWn-ZTAwzKhf1Gju**zd3}^qy}n*L6QU zv}Qer)&_hZGzK|5?pp9)4>-Yn=|3$G$e*YOs>gte>sNOpes%|rLJ#}7gL4wt>$O4C z2^C*hH^rIaYYp~wJoVy@IO0wHK#DiuorpW_`@p9&d^XmP_=|m|)AN(_v&5S*KKXGf z)=m6J&cSbF^y$!7@dkY_hrZUU)LZ9I(yKq;bna8px0~sg>3iArsHac#w;lVZ*151E z{mOdaef0GFYRKQG)1N5*(T9mI_ci7N`!dGsEc!afak9VjwTyH3iHLWN$90V_ar>tn zZ~v6|DD63P3-ROiWLMyhvt7sYYm7(JCuIKvT&JB6!XHmTC;rs^1o;scU&iaM>jhw1 z$MuWPSvijK8mjNC)DhjEf9I#_Kx9>v{uyXTLu?MW0gg_j;)R8jtq| zV!oe8!k&RAzX-p9{gC4cGrIQ4*V^Dmzwq3O5OFJf0{w#VjclDZaq!bRi9hn|W%*49 zzqJ1dzsh(AU%-^F$&d0^rJq+n0=yA+yGeeP`AzXJ?d1B^i8$!=_UU9tuQL|*;r%N3 zVPDVfmDZaK`IDVfek6HK|NX76`rBEwy#VJzp0xf{l285&KlRRCfb05Hz)sXr)^GZI zD)diy2Wr|LJ$q&AynFb?e_F0mKhwK;e~b457#}0eqHp})VIt#-9Ir;WH;0?)uA|d=v`+$VSP&1$B}om=Pc%t-%y-$4b`3m z?)7kgfqkX=ew&^<@qIV!FYE`kpQd^UXnWIdmecuC+BvtEhcDYI|G{d{kcn9XI7Zz9xCf{pnud&Qt#Wg?yKTKi(gdyu7b={HJ(# zAGjEPTa5f0^7FgbxbKq>;wt9rxzlS)aec|>Y_G8&)^h; zU!Hm56?@t*o$y19uh8@SaNM*bPTH~0mws^kE<;b-$8{v`cVAnL^Neym54QX5*spK9-l5(kd*u3crGGE~if8iMTFAeK`a(H-dE)aU;FfzK?2b6qxI5wR zg_wV*mp3UdJ#RhoF6GlY^!7S2uJAa@rR#lS>^v6udbt982>6!LJ+r9L&{nUwk==9`Fag_QD%hknso9&$Z=6wKl zP4T*JKOsLIr^}Id>)vN@J@p%k8{m=)uu1s=)&B#w0`I|CH{FL>uX10vKT2Ho0iU!^ z+J}?e*MeWNi|tU(zeIXvzV+LPJii( z_(P3<9(q5AeDrh4&-+)>`xyNy=wFNY2Rza5Ut?Z#T&LL7_!6I&-OGMdb!%|}){EN!r`WyWu)=7M5|C97)A4k1azc5~QtLQtP>Fat>?u)tIy$<}Y^&Ic@*0J)v z-g^Fp*O6QmeVXB4uXAXBXrH1!z_tEU+q3mF-Vb8@z<(IGqxtA7h2xwg;96WC!u;CT zqF?#iHOw<^c&)d;t;Trw?L)o$qQrMG@eBSf@-6eCXo%kFJ+HVU9QXZky{&m;wHd)q z+*g+Sz3R90wqB~=mEU{h>$QV#$_v@CGJYiJUO%mq>=wUI`up4H+q|CkBjAmYZ%Wr` zH`8tJvVW2vF<$utsOMA_dyI2lg|7I*{Y<(q5)Qif0^W@Ey#JQ>QC_#>KDsjA`k&5E zbpq>IpKdyDE9}pX&*ShsE#1fYK7PWu`djo-IRDafJdR^uh5II-L#Ai0bVwV3zn+kwaYS1~`%aSemdtIZe(ex$#a__YG3pU5BaCVI&C>J^_) zyn5v8fYS4|SO?HJ*3mrNw-HiK@%sS&BK|h|b;3r#X24cJoR5iq*9S+xR^G=SMcgQU;cw+lrS4sONqUOAeEwX_KaJi! zhyIXutlW1$iSdr>E}o}b-_+;ne7-XNZsg-~|GXE@MdwnT6g;&56tAqKsea-*{Y3t; zZmJL0v`!cO1O3cC-cbIk)a%Hf-nsoP&nrB?Q~NaJRsG?)WUSNRb>Oxm>Ljm&Z_@vE z%-bNub@>x;Jg%p>vE4fO-I>Qt#CX%o=eMfoSWn{t$0P^xMEtsO?w0Cdc^K^L&RN$^top?`+_ig?AMvC8v2lvqxdtVOzmb>HM;ezgc5qT|t zfBF7rBlLP%ITVjcj<2x)ai3r2`!MA?!ycEV|BH6`y#3H(roZf7S+|+|kn$`1%Rb_y zK4mWB_i}n1hd;k~zcrui74oV-fF3E2+Psc^v_<&=Cy6spi8Eg`ue87HfN?30Ri}VA zLVxEK?xXu~2uCA*S;$Rj&d&OP(KVmjnip9gs_TkhWZNUEbcZ}{4l z>lk2*b?wrgaloc<>dEUo2M7G4Vb@1M$%Q;ldG{#vNqEZpWC!H~yZb=li=_BiB9iab~V;( z2i^X}eU##deF@_z?{nq8=Y4Q2?yr)ZV|c&Ma;H3y zU0_Ga0jPcoa;Ex_{FK&vr1^+@+o61JE&s9KHGe&)fZz2VE}n<+{R=)1h+U+NuHy)p4ZE;Upz0FwH<1BKCSVy zp%1QSxYsGiH-m0}&LZ9sKg0Y!fZ=86Rs8!F_!;Vbl>ci^cBli=E`*8B-`Ab>{<7kC zIO-uF-y5v|;{(JW`82R@;Nshe{Np;b&s=Rq@=aiU$~S@j%5tjTgxr+3#|QlK^(H0X zl>LbJqxE}0=%@F(0Qr3m>N^#7ah~&j0eqjYSU$k}8NQq0cPZB&tyxaMPCp&}ct5|x zejL9W<$T%&_2nA}_6Kp+CEye1(8v4vA^$v2bq({^&Y3@X4n61pDC58TIp;5F{95R_ z=J(toXOed=(`P-#H&qAMkgxsvUC+kf`JCe)X8e>Ff6V-cUjSA2EAnOZqM6NC99HJ9 zoJ9X8%jW-2mW$NysPCwuzQ_3FfAtyV_`y3_9y1RHzWEvW8D{c1&&&DlQ!076O?v@% zUUd=gjz^tyN$Q=$J13rK|;eY3yk zVt<+g-Pf1Gzup&Sqfg*D4C!Ooj|OW+%g=WSW&iSfGxh7)eDf>kx6huB-#ebkKYkBc z@;lagC4L@%_dzc|e(w*A|L2qk&u8ej)VHDk5xx-qTnN7}#CbwH@V3v}@gAD|4w&}$ zw0{qi&tHs~4yb;!^1C*V!+D6}CY_7af`1Pddf#UF0DKtt2ep;V-sThUtCjMgpX2!5 zh-d0!Jgw_}d?Df&&ksTl-6sui#rQ3p*Cam3uSx1_ocrd$M|iHsywi2#)^jfI7w4k? zFW*;oUsSPg*gMH1yTMM*r-kS@7NQPp!9HAn%XJg_8K<5h$0>5T-#T3`_5}mKJT#wQ z&4X{?guwaT&VgUu3;M3>4RO(dlRho@p)1YD5c$-{Wv9~j;aT=Q{VbsaF_>N6_%^=$lI;*cxksVxw%$fNt~ z%6$IbZvb*7d9=?#Z^;SBes17b^pBzOuq*62ul33})BF@qfVLO?%KTgidy${<$eUuT zNB4Ed*Hny~iu;$T!gY5l;(Q9v2O!tse)rY)Z1CIR&P<>4WSKn0Pe>B5=w!~pi;*FT!C~lukci(pU{BP=9 z68zNHz1}F6gMZ+7PtxlJ{@QfJhn^1%KfJ8x%oY9(jpuU<&TIPl5a^y?E0#nb3Y@>M zw7=}%+E|Cd+{K?$37(_-uw*ssLrCa$xt z97H}6r+qNKM<;$0&v|LD1;}qG`+T(Od4TgAPn_evwQ4@V6>q?&;=1Au_$1@?8^c%! z@axY4ZX14sc@%U)(gi`+^@i^&zQuUpdTajO#8=2q`TeapuL9J26|b|NQvX?eH=FDB z=mR<*`uYasp}c^y7wyCGtNvcvw>FG_Yr}oR*YAg3_g&91PxT#eLUy?y_b){*(W4J*PX)I?#{t(5dr%go-@) z-4gZY`-XS1-=j{9f%xtLR&~{+*bA$M-Sh+b1vh z3a5QI{`7U{IPNTaoTHce&HwM_$NsS3_>T~HANS;6^LgYM z^5x@iDQ;L_88=Z+EAl)GdxU+!ALITh`{970kLz)~S3ELmdjhJzsK{sid-cxsZKM(7 z8|crfepx~NGM?7&X7=(vvEuo#^Md{8{bMBhlw^P4QRnP;;Cf#h(DFwe1g?GM>x<4S z)g9W0&!qr?t1c^!B5!5KTa|d?c;ccL?a1$I0Uv1mBIxFOUwF?CabH))#pTQ{S|4)u z^i6sb;`&SKStve7T4Dc|zi09FsJH)=eJ z_%X$S?O(>xDD5-C`w~OjryY8=9aj~);%a0(#*e#?O>qL~`4cgJ;)S?WU;9zqw*t;( z{!x8_AKX`joGr9HjQRGMGb z=@ehRbzI+EhoN6w*FFM!I^VjMcciqZ_Z8SJ?5FeK|IGDpAM&54>N}K&bv5js>?;1R z&km$~N^z$?5P9|$$6a0h_VBU&i@GLxfy?i}O)t+&F3GMT2hJe8u9E0{> zz~8l^o-aS&``aJRc=_=gbG8Sd>hQ>{>l)!7n0~9OBRe4<;OCU@arig+bFSatMegq> zT<_bF2Y`yxivRB^pO%~--`;cn2r{ph3f-Sw#{Nq=zwylbEpg@z@Nw~f>izuNPvg1% z66lPtCFobw1}|L4JVWN$Aij&w^IF1r$zL7MdCM_By>D?k-S=rr@%()mbrbU|-xHgY z9Ls)wTe&VfeS^OT!+1dL8=xzH0d*hvjqRXs`#s*uc=gr5H6Bps%@{9v0A*)D)3^Oz zPi1|c^A5f_W4i$EQtod*@_0bW_xiALz+Ap9+Wif#FIX%HhyKFjcm5T9rZc-tX85hZ zL;phicxC=#Hh!glJmR3=?hlE-&b%~~y-NM-W7Pkfw?Q|Y$mTaQ{<-{*vhkgMyilH0 z*4yc)V}GRGqVEB`ozZ6k4}0|DC*irgx-WQR)%gRM$D!h>l*jP}zba1vFLAxsBklpa z!f{>pdyzidFK=#D9XYlROX)j#A&+BhsYav8B?%ijY4;*me zh4Gvh_5sxTef;|A(hu|$N74&=J|bV*A@r8r!0%9U?Yj2l#jx%~DFX}S#>6>^@iFpNhkK^B*u)ctL zKJ`uJ4e4vnlk$0}>%tkh`jO=@ZU+0@l@fX__(_1`Ggr=IQT050F|GW`Kkk-44DonI=%-cc?i$pCwgVR z`5gs6*oE^Um)1}6K`-Hex(|30^_cMjDF3|P1rB=xcIZz)^)rCi$q)A<$_K!)z$Gv6 zY2l^)r4O!aJ{xI3tCR6nUgEm>@O=}@k@z+hXR*IX57OD^8YVpz z&nM7Z=ge2$L;j?v>InExTkgWos}g-SzDHQWLtZ~e>cn%&%q}yF<_{=4RdCrExby`a zN%|Aw?~Elo2m%)!Q1TK=KhCG0v5xRKk9iZ2es190PkhHPE^z6Gzl){oL$MCNN8;o+;R@^|M_{3Q9cP9;Coms}n3x`0ERKO1oXI3u|jpSX_qZxt7RH(?x5 z_>G_g3a{`NUXe$5C+rL;{BJ{VK;ggHH-EsMAGChN_unp<4tQVk(Vmo#=UV}Joqp^2 zA>qmH^b_BojO#H^{<*pXejX=&z`I(fj3@0&6+M%kQ{J{Y9`nQ>P}j*f$rbCS^-~-v zE@!V>4?vCoqLH)yq>0^*tJiel-FI?yYu9&AFZ@ti`G%0_@ZmB5b8#SW!)evYJ1w?nBO|s~;LUHh(~!%Z?ld zeu(=8&Yuf@b9irqIL>**e<}8hup9j5{48U)UG#ADqeWTZjGpfIpNTnS9@3 zp3q-A=kQz4_^qG9Z!!2Sp2M$`@#~zzuMzwjc%Ezjwq)O`J#ByO%kYn$yTKpx{r1QG z5XaH}gtWJDkDrL~J^S{Lmpv=vfs21>zid41RUN+`<9qhW#?v0v@tqjo)4xzYy}4xh zaUZ7fz~w*SdJb|1J-5e=$Pd*?ZU^w_eG=rSO;YC&eA} z!THfi^zi3X`DH)&?bp})>3Dy}{&#-O`CJisru$&P8PQ8z_p6Y{^f|m|>-oCx$M}!~ z?}5SY--bN}c^(nCU;}mjY`WL!@xwX8+2haSr*j3!m+FY+?bS7(Ta@zScVx0>aqstO zpEufJpJUsHxa@Om`h9rXqfT(>}oM@6+ig+UJea$DhaV3)%;NzsYg;)zq7fh!?)k$$9}?kiVk{T<}Hu ziT2?9Q^udi?+ei%lZ?@zuF`|BILzovgz13n6WgD$`G+$89NkFfvHzT6)T zr#1hJ^IMC0*eBnccu#_S-)saN4tUM-kWP6>7X)4K+R5^L34SNa_r|lZ*M7j=knfV^ zAYJwXT@ZB1cj;vLz68G$<$Lo%$g>{scEBVD=#mF?LC^(vPnPdX@H<()u-}_uzc(LS zzR{tOBkV`I?<6L2};VZa^3?~DYF?*__`v?uL? z`GS})xQzaf*D+sk$2jK;kNFim+0W;8v+@2m`Je1B)!B39-wJtG{GK$&YyB7HZRe`MSTwxK8MCH}k{xIqTerxYWLd_)Kvc_(h$IoFT`!)6-*rYt)S+-rjGiQ4NS6+N)Zm@l} zJ{Py8PuqFfXb+AOKi7|^$D#gy)fs;Mym8pg^mU#iZX_q-Mi6l>7ou}V<{O_N${+H#S>d0wz@I3yz zzi0Wp&mccesnh4lcbfLpPw`<6rF6k+dK6uK$6Id&2s8 z#^1B{xy(B1EcYRN?^*j1aKXFozfP}{_`8*7?&tOM^4RyIcYEt?QrrGy{%)-p?#J=I3h3W?9Q*oX|DDG(^j(x+wxgfe#`~vz{P7%wetQ`A zuMd6R`knRQzmD%jkiHx9cfFr}7r3tTzJc_(fB5ch;rrIdagP1i&q+YPALI4hWc2aH zM}f!q`hoDhSZB}gO@H^khUz}_7wG`AAdQ1(1ZQ;clkWe^9Pf>^6yjl(Ry^wrsF+F%vbyvs{eYc20MU1;5Ee` z`bMpH=;tFPy?&4Mw>p;Rtxhqm`p|oiq_`w}!FK%!=iC{7DdI%@8;$z2z`I-z@iQCz zyf5DIds%!BLiP4eyg#=Z_^RLM!|x(opI7Vi*?RfVd|wB6NBny04}98>hd+)i$9InkJ+Jsq z)KkLHw;}s1_&t3xyk|*j}18>L#ZvWI=Z{9i#`yAr^F3KPA(x}xZ zBrod%?X>6fwYON$h;JJ&$Jh4z=lgu1!TV#x??<>#;5^{=XV>-ZJDb26ubZJC_b-l* z_r@#nF)aT4ZU^~{MSPU;&xC4zaH0f zd34@_agNW^=jVO%WyxRKPv-X?0`X%{Gf7uTS9}4{vp6rw2Al+x0 zzKDIw^IH)ouAlMu3$SjY>--D2&LI%@s?*8~=Yh^$8Y4w5vuCn@S})fFPb2ueI8J>7=BY3E{PXdAx+>Qte=ngY=lS@j*?jd)mH9dTG>w;jqtMUmyFQHi zI{}sX&g;>zFUF_&F;Dd$gDCjbn*?)`ZIIP-AEMtA1=C-dLA5qTedxe?0oX{`rd>|97;PVUsxC&(uBzcw6JyCve^vo@W@IhR^i# z_xXH*qfx`78WKL@)iTd^v-DE90NfKaYOrocSp}yx+CZKhr+(ccTC^ zf7NUK`eA?O_+I=Jqg?MQ>xJR9Z2S|_5$EAgf4`gg#p@qc;uHNs?I$xHhwJWxSQmWV z^@QU&Z^M0da~5>gquBx7_coUTU&41O&~N-tj8nsn4A;3zbIW~Db1mR%z#aU39`XfU z5OhJ%1$T;_jGy|R3P0_;&63~r+5F<~7tMxUb^u}Lx416!#kqs^-hn>8uD& ze>wEo#eB{KE;)A5he5y7p7XxTzk?BU!F|u;b7kV7Pii074}0%NpBwM@e+2uj>7V@*na0C(Td8?#a&yFUNb{2<{_dJ;4{`_j6vz+X#8*V|<+ph=!BrR zIR9P78DWeAuDS+XarXVoK|c<7WcdA-`H(JtpbLUd2zrxzzr=jmYc(Kf(E@y$|ye_8XCZH={1_`#^UP zee3byCi%7EJPq)!*5iDRKdfuN#Cewd_a5!8>$cB(M>xMsxZr@}@p+7U4*&Pj3)QJc z&HO%OJp~=O^Z`D`@r@zt1NeaQo$7{H2fVM>~}A*^WU=EyRUAscQ|*1>CiQ!7+V%ysy^f>q9OLiNRsC}uade3L2+j+? z06GqO^L(!R{dMyNRGp}-W4t%N?l?SE&ia<*;CslF=Q!3&aoCe<;Fxl~#JY;t@3+Hl zA;L~u!g_W+K2l-8T?MOgZ6vy)xH9_0ecqz>b~k4;bqE;zyCTz zz3}&6lU|A6?aWSH;+6Z1Ye7%rMTb1eF1pXH;Ieb2Z{N=5t3NC4G5Cn|%6Q$USMIa# zlV0Kjoxr^>cP&Sc-ol}}YxMh&&&g<4$v=$xiRy0K2;pVkX`uy0uIB>zsV zpYlujP_bu|^sB6+hV$9@JH$U?T{pC!V27~B0^{iiGq6uUj>qqUC2u8eRnKsJi+liA z0xl3@+(baj-SyvXywNQh9*6Tj&y$>(FC21fJR#|2+yjR^t#!~1FOe>J7>C#wh~xLu zmJ{-F9n2rI9L0F(CwVc>&!O@C0@1s*&4AkhKO@ArapCdzs34br&x-tyaR-X?v@Z?g z`vR6H;&mX)xB5z^*X^W7tRw$a>fLC@U-k41`e)<^dnW%h$@fOL{!n~w!Y-8WW^F|{ z^M?3*Pj1E^>{&eTUl;u*_*B-N$@I{94g6t$r|)N+S|{WCVW-p8Up09DL3u9Y`2g0h z@P8cgrMl1*zJ@s9_&e^~8^AT6-z(gNPkZ#n*Bz&8SWnlXFYNu>UR{K|efS0S zrH%cNm-UwOhy#9aHsp)zz?ZdNmG1HCJHIzuY-`?i^ZDNO;$AMxLiS z(uwQg7x({XoD=B1k2C1z$MXir`!?gq(Dm;8-zl^u59+AoM`x@z6YI!MSWoq- zHj8zs@44&Q&zP6yD^4o-qVnPxbmqe``fKZx#&2f0{92i>`Z2nsW#?L5@n2c*PB#DZ%hRuKKL1wm*Zv24C%afa#=+VCQ{GqPT@8EdJQ4QZ5&y{J zw-0=O@+WKP^T2m}2zmno7X&`d>wm)g5ku9*anx~M|M6gqyBzy;)Ja{p-P69@)_BG* z=MC3&UT`JG18V&Wu0FPc-;ms&g*`s=^M$udJ=8Z}Y1Z_9!jJ+J_B%Dc~j!++4y{4U(4tR8Lsaz;W?G|^U6A35ntb@l>ODf6z%oS znC%7VIEKGN|4);B=nvRsO!}7lz3LAB4zTJ5;AGGpmtEM={&ijHqL1&hC(ft9_k)NV zK-KHFd7sSq$9a}vQHyx=^#jXM>ft^H{!4yS-zYyyZl8mlPJd-Ci}y5N@%()Xjsk#~6< zwEE+%@P2c;e9tJ)J9q2{z;WSC=hr(;+u;QInC#Ycp9J}mA9Y^^-1&KcdP=(;)aC?gp7lBdL1`f=3hetnK)*NIp^zK2rbKd$kO;NS55RTwfT=@8>G}sp@}w$NMgz_SXts@$kXFi1~n&uR@=c zzB{I$Fkkkr%-8))W&Q)s|A75dz%RtVoZpuIW&bgq@l#(`S#MYC{|oyIa;ly|KKT*2 z_D{)6`7kc$Pyd4cn0&76TMxj?*}CW3slV>ckY6QERx^HiU(*S>ybpZl@3(yL--TTP zv-wFLoj1X6xt&y>P8%+TT|W9ru8FTuy8L0sL>d)mQz7_e=0Mp!XZ}8%J zt|fg{$18otTsA)SA<3>=nZ0D^_v1Ydz?F=i^3>~nhC0Z2f9Ci8055U9ioUr&TBKL} zF_H1l`=XZUWjx=^^fA5kcdmafukmuc;<&UYo|8Fll0EbOb@2r{^uO|7UDp9sr>;Cj zeDM0yUR_eWz%CudaY>gwD)w?eT*lWt^<|t2ran2*yM8`UneRTd)Nebpm+I38$CeMU zL3seR?|pEr^L+NHDLzIs`#%bKQvA#RSEhab4X8eo@~V&LcRp!{>B!q@|Ly^Fo%aLg z^Y6!cDZVaAKK7H8ll>%MSH3hy-^=K^eOD-d#a^lh72N(W`*h8RoQ=?DF`NG+ z!_VWlnTK(^L9|5TN{b0lK z4Cwk&#>14>E8{E6&ti=H+N+K)z?46ZrvtpF(eJ+|;M{65Y6yC%F`Z#VYUyiR6ufVe@tm37tc-$9Oi+(G8wkytO~ zbE;?QKHq&U-p5ybH{6Hwc_#V%7JvVZc?#GfoSm<0KL!1o@i-?b<5&AR=)*Bz`!I0j z$8VeU+033=r*giukjd}-Idpt{aOmf$6+4V_yw>w`P2MLKc)pp|ohH5Vd~$?1zGJL> z0L=R?$%FGF%?H$ZAaTyCe7{%Mfwy=a&vl*u{O%C&ihS3p?>i3==Y(6ZYdqhj-+`-+ zKz{iH<9Oah`SrUdT%RYuiu@a~p7#s>4x)cOk4FaN&&u<4 z#d~?*-RnQ3@7o7{pPJ`By>-9`c$_m92kAQN63&s6o>~uhGtonS*UK{gk2vp2+wuY4 zko>4el0Tgrw;j*rxWQznsGF{fA2eO}09AJp2Z}@Z@oxBSnGo044F@N=9-hB4&S>W( zf0J}P|Gq?e#Xol?FaBOT?RjMk^7hZueRkO&z}}?eK41DDdw+WW-&h~ObWV`gos$0D z7p}WM?-q(n?xU1L>p(u$1LI?yUpbeUr5;yDiu;B*=cPPl{48YhyWW-kM_2NkxkMoaz=ZbQUhyLSu&S?4l9HHxTj>>(%^sU%G^(*RczreY{9j?#&anD~{X7=@eK65?BEBFFeT#oG+ z2b7*ymW%_cFYCio+!&@fNcoi4E!DZozB5jJu6$-Y0_t~%S3Vo4$-bAo?gy7}u8VOW zu-+Tyc{UvJ7xl#YUBUZ}nl~DG#`hgLe-zITlHP_B8J^|ep!z|~^ZKK`elqz<_3?wA zyk57=U-uD}bGFB^Ub53I;k|q+Qv4@+-oNZ;^E)x$_4O&_j5wnHcs^%7yly=8oB8-O zZh`ZV7qSDZ)_VDwbO8&M&{}jK?h-25KQqIheh4Za^z9l^nC-&cqabmmn@~__PU$;;ky~92U z_68KcpKV$%K>4Xc*FM4cCm)VyJ!Kt^`RZ%%_X$+z02@&^wU6NbC(oZY>DBS%2i)uM zcQ@qUab6Exw|@Y&UtEai0e}sz52*bP<6UPv^&0tC?5;k)f^X1|o+L`kJb-fih zpzvYCpF&=Z1AjsA7X%+cj3eZ@@T2at;Lkbo`RV20GZ^C%J`y;f#zj5(DWLGTf(|Hr zC~)u*ekpK3;Yr@t18)R9;m3g|nCRfE`QHgTpzyuG6HNTS8}#j<2R?paoKW*Of(|Hr zHE=-Tivec?P6Zv1^jD7JdJE8TY&)lQF<*Aad_s;F-b(rM+5`Hd$#YM`8R2;EK=@_< z9=Ulb@JoTSZgKo@;JxvKE0n9uo5K7kAM*w8Luek?H?BI_Ea}Q?;Qe&^o$JLu)NGqy zvl0B-Ay32K7eQR5_&TCp(1*%CzisyJ2i4c%K5sMPG3rU-`;tSi_s-$K0q6f_3H%M4 z#F6ip`{$XC>kmjT@2d*;{T=Z0yxX!b{YZVL{5%`-`o8M0ewp)eAE)tv>Ap_aPq?p3 za>!obGcW$veV@?0{=$9eosd8370xjr$2# zzHl=AT>dv={*8S8fcDv+ZG(?xki+CQ-d9bhG8m=2R zzcuVL#Fygw66{Aj@{M&+j9q?eJ`%Z z@7Z1(GEOM^s^$9WV&IX_KgD(BGp-BH`nk_f%R1qB?P46`uj~1#ZVXKXzn1x=`oQbr z*TQpb%Do-qHsX5N|EC)ltnaSp|7_RK)qcKj9B`NOf4&p=4){P0+$VJ@58y<=`yBVP zm)mkNPX{JB3f7{hj&geDiZJ;J%N;c`nzl?8nN_O1?f&JeA{B&n8AK z$HXY!cYqwgWgpd>813AFe2lkEj5F@Kx9R(X4-(p>zF+y%-Qr26&vxk3kiIYIgL50` z!TwKnku|1Rvy>%TaHoPdA2Z1{^-;D~qW%|18A z5ptYxj1!J=gdawK`|b$7pXT)~U-X;si~YA~KV1D?;J>RWo_>Cab4u_5>~h@-uKEMH z9bdKL2I*zIVLbImTuCp=m2mSB{x0<@YOnmCpV|)K?|d1;_;ej|UmdD_D*n?!N4!w3 ziRqA+zxU?2kr$p%Dvxko@eQc+f(owqtKz{&^>kvwasfJ@xL&Lq_Kf^>oWsA#&MBXo zF)rb%`^-DVNyq!ZdH5$d|K)f<n!k8%5cu1I@_J#FVk;0^12wc+{)IK%Y;Wfwr#m!4ni zny>pQj02Q>6}yc~e(W2vYkW_uLLbZMEy+KL@sxkk^`$ai>sQvx$LD^{`P**b8~k&> z=lrwrLkr=L6erImFXNH&P7JD^y*nHD?1iVIxcu|Gx@FUxUbEIKNrGIz%_m$ z>5entnVv(rox)%4ANY4_`~8yE zcYe>helR}7XQHQ{^KZiEl@-69Jbygq<9$@+IdH+rsB8GWLCDqaTW7-O7+2H0Nt{D5 zUXvUXc<+(;Wb_Y{*uSh#lEe8_+9|iA?0j`9s^cWO1kTLNwF^~CeT<3h} z`v3I#>NkJy^XEd|xtEOBxWB&2e59Vh1tV{JcHlW0>1iDA4}oKUaNc*KpPdUnb9l~+ z_Vdy7&Ku+~PmHGS9Z{7RLd+X>!sOMeZkH2@<@Bd2=+HEP$ z#i57nyHq^X`sn`!7vp-mABgXhV%-^z2W$jXKl9-b#zUVEhYIaC?`?R$0&G#w%J1uC zZ{Qk_-%BfwG0uM3@O{S@pFglJza5+Z#BrfI@x}T1JCI*u{+|5sh3(<@v?mTip97!s zymv3`b+2bX^%aowAl3_eB5oK5n}}aS=fSsIf9KdZp!4y0#Od?G@m=EgLSFla?}N~Ot6_J3r-yow zpY?GaI!1o#I-aL;JoPu+%J{inmG)4aRL`erpUV47>Q{m9M0`X%_1;5Bxa&vP_gQE9 zk8yUY|BL&vj{JjovVD@i$Hm>GU)VwCiqhA1xykwX`}8C9@9glvJ>k9YRSY?g%6c=M z_=UaZ$hXx(JlVeT1MuXJJF#xV_V)W6@JGVeV_x#lT*m)K@R#3OtzseL=e%Y7*iOpZ zR@>{h+I5{fRpLSM41MJXz&l#6jF&a)e|6tXD!^dF{%XjSk?ZaKjOZskIeb_G+J^vSr1BbFF?pp+Lu0y}H zra&j0zMyleUvOW*e8B4(*9kh~kmF`z-i+THY0>_?-oZI8A>+lckZs?b zeq;Ut_4~!6pg$@k|A#xVZ|&4q`uP>d!mq`8*RVg6-$KZ|v(SUr z4`Li}>3iV&UePU=^}_vb=%@HQgFX}T*-r6%2YPw_jY9kJFLvxdz@;?a{Cy7ki=KUO zA8Yx|kKY|h`bfUPdz!yweI|!PAK)A}Iqdf(TEsbisn|{9jiZ0l^&Z{+E649-^lSY% z&c(S-*cVX!TkCP?``Gor@*G0>IC|K{1N4^ z$kWN_4f&n#$@c5fpuU)==cC})iE;a~N2!nPGUUE~pcZi#>jPKcHn|q^Qcudg=J$kP zH|N1>uaEY5(~$GDVrSQ#($2$~yz7#0wRRlvvgr52F8AwCvhmMP9_RXAk!w-&HvJx0 z&(E9w{uc20;G?<%dG#Cxu+<+oem@&=w94z1_@5U4E%&RfE$lyxx6;1u_ey+B{$B}u zu0*_-bj2h6K)C=Xf?rGVJLGth{rT;h4Nlq?$s&PFW}BU_N_&Hr`k~V)cYNLFC6ug z^MBj){Rgkp-@r!^2c)~sm-bbk1pdl%z{!xO$+%%$3oq-4o;%?BuH;;`U9PS=zOJqW zz5?9yB@g7FJ-B|#bLZtF+Y|En`Xkr#irl=8c|}upM!%(XH^V>do1ov-P3sGIJ?PpO zfHySnD8?U!e>snSejevZT(_=12s_}q{IwPELHHBn;=1zV>X!2aQ2hd+;_#QZFphq>RV>Mm+c9q2{l*Vs zpS-&5x&z$)=Xnw1U?j%l{T%5%6mpc~wI5@gp3_&xkHvbHbK8B|FXuv@Ie$0)m)An? z?a-I^DO`6u)-R#!*!lQIteeKGUSb{F;Tr5>{AV%Yx=&mNu7BiV6C_M-e%`;_)P&91pU)Ndi*^E#pPpdWCbC4YP; zlN9?|<~{>(RP|&R>$9%xT0UIYeGlM}_#q!? zj~^bybH#GJ^7vQGZ^}L8?|=NN9rCwPcgS}q_~TqeIQm7c7k%Q@M_9+}CpkQB*Y{bF z%XM!j=Is=!=RZ6SyYY7%kncx_))Vld&f5g zS$@lX!n$D($ye#$bY4^&v%G+c1K2J3LF=Wu1$f*q-+x1$ogY^ zKGq9;UsJuT=;Qiw?D0Q5zHp;oKJ-=KgYn|~Lq1Oc{-Yz~fHVDc?@yIJ$$58#?=^Vd zOu*ph=P=+i&g)o5_rD1TR6mL9Iwt|&h(qVo`T67f@KS!CH;+9CJ3WBEy>9Y{>L=D! zJqGOMd2bx!g>^ymE*{7AmhpOKKcDmSzL@ch-|IhTyaCrf1gLz#clJLGKHdk8{oRvt z+zapDI#0Xso8q@V-yi37)rm@8NUyQ+(0e?ObMG4!ddfT14dmbF&AS_sS1IquiC5~5 z^0dT}SGgZBzEamx9H+R^K6(0k8%2%l<@5PIhvKh>ygz$A?H@ndb6xPGC#o8*e`THKQ-_7y)i$<;RMcqUVW4OW}$N+zRyfN{hhK$ z=g_-JzQ`->pOrd1NBjI0^)YM)RD9yP_B}ww0pdvU3YhXe$$2wd=TYW&#SQq3nSXIz zcHntF?J?eq|mwFCd5G3h%?mdMOX&_mY0$kaqsnCdLaEH)Z!( z*p>0I4}0VK)U5ddcfYqE=S$^2t9+{LEAB@)-+a7JR`9q!q4g*)*P*;=y?~M*{RPKE z?$mE+9QuIN2Z~PxcRrT;USFMhf4FBK`3ZQNb_Pti@~wh9KEe()+&A^cQ!dzx{=#_W zE$>%k*CXo>-1bEtggy~3#XRxK_(Yf8cwK&B{#gGMXN$x^Uk;e_N%PaX@+a(<=BM>% zUvm9yUM*k$X6WTO>%=~{ZMktjKSFy{{4*1NcYNV@I^lQ8c}CnRFQG>(?9tOZ?4W#< z-E1Gl9dOwXQ2oiT=oiQX*h%`o-26iRshm$-;{2<VC$HzUp~K-nA5 zTbu_&(TA0|{3U)`2m2oQPVHiS!^yP1>dCC1FCg9}U#)}pEBnXai}4MQAAFLqF<98G9 zUw6D>UgCF$@>QPisV^w)6Tgd__wVu12R_X7*Zni{!v30o9`w_Q?Rj+q{v^H_*B1w$ zh5SkXZmb{vD#WMakEZPT6#OCQpAUi$;8p%k*7#;7|73=5a$IewK6dhT$2as%arW6c z_Ie)jC;LgRO8hmcuj-xQvwnLOz7H?sP4+M2-}jwgJYLYRVBL!S_A*@giTFx!vYqL( zOFO}TetuCx?NeW&oS9tiuPbqBzxV3Gm$u8(EbdhID|R|B&Td8gDDIFi)``Bqmx zPJJ5keTu)Y=6F%wVx0QAj}9WAxsNf94?O;At$tnW?nfT(`+TwDw_Klzuv_G}&#_U5 z9Dgs?Bh~L2)+fXvVBXhmW_HNeo0C3!;7`5w+<#Z%cJUl~hw`4WeqN9B^>_Q_!*l+A zzcs^tvEO~!xW;{VdHH*F%Jl^rdIDwt6ru}CZb=A1*^ltcR zx4t93+{dWTp5F_5|8dM;jrdp%It z`Mvey{=OUZuAj?R`pi+zui#nVIdF^ouHf$*Q+~kP!bdIdl~Lb+ROsrna9#Rd87&@Y zd?V&J@OxeIY5AN5@Kdh;=fkjD>Sp82Ex zOYT{p+kl_%lV{`aw9P`!B#&WB{7ZSRNnf6aQcvE8KyE%Sqx^t3V*XUgHm#$S01e3@gxs|Uz4L)jbm zcMaL+4(!#x{-eNMS4w`87xrwDzxq?dW7{3?QPOTB5lvLW z3}~OfvIe`;?|`!F9q=c92j}Ab>ph^{(+%%ifR}|II3K4E@I5Zj|GZ{8pw96t_`39N zcs%k)aa7*7c)b!Y7P)?jd!MW^4&iYDm-1?i_67gd&`*E&1<##Czq%jtux?=7)e-L#A1z>g;0a=!>IC#n zeQhJ?>MzjGrM|AE_3+%8{4ihh02MbC{6#wbn)Jr`z3c=%f}hsoIvHQd1HSSHzZ1go zfN4F+1-kmkznd^Wz*)u9R@iw9-`n8&Rh;&KKgdt#KY+MTkvxFP+e*A$>YpF+*TQ(j z-E=GVrILTMe|-IE;NF+E{GR;iBK(8-quqe4Q^5bC1-W<~bV1MsK_>)V`_brO%!_>p zxa1k#ai9Gq_)ka<@D*H-b-6Eb{q?Vi|7!5R9{g8>Kl|R$U+@ds=i@c=|9HXtflGei zUk(4+;NK4Zv%$ZOa~9eO{t_HB98Eap$&SDUK_?_#IKOMa`A0E-FRmYjeQ+L6`p1n} zzh*jc>xWM~lHS}hy}1@}HO6mvJn3AIbV1MwKi-J-A^pF`bI-7o73^hF)P_4%l`swY=R@b^4nSL{c!FZU(V z@p}l32UI=G@f&T^0Y%4mh&3Ki`)#V*fa3Sbk?DXVQAf2OaUTe|1c9s0R>mt{DIevW z-V3-Ba4XMa%ooIbLChD#e8I&FkGby04I|*=qr2G5P!4-p!$43?H`E_dx#%klj8u#WXC%m zkA6q@N$rShK;`Mjk(VDI)wNHzo8W`}22lM%oBIUumfu@BeSV>S?c=cD$9sk6kK4|_ zo(}z{p$GYmyMJ!C18?K}k=J{;zbjS2&2P$oH>W-2=cuRC2ej{%blm5{&L3`Djt{qA z8Pa<5e*WK{N1S{8mgH*>%w+N;ezr#&e`l*t-r{CIKlEXwFZg{{K<)c@o+y99Kac4r z;1dD6^w&I}vsNI+ZQ7s3l=$8B`y=g}b=@ztm%>js+>Zgbd^d`AtZ#h5=l_ty`Ykw* zu)g92aN2Sddm)GN59=%Ll3c*mR{^TO0X&X%8nWBM#a7^L;}^DsbAO~g_ zevIb;XVVWeek=WW(G~ zQy--|!|}7IqiLR@`by-z;t5c7Gq2OYMdv!b_02!6tNN;X<#lxbBKb~U=h{o^bA#() zztMAN!0im*6QA|4+dAq6*E=0QCLQ-jzTa#Vw>5q*=I=!uunsdGHvKzk=><6et>@|E zpPs`{=PtDc+Y?avUg1CXIp??NXYf&6)L0jZw_Vq3;Wxk;+70V?9L94TaK#Dc-y-Ec%dbURUvi^%XyavA*ShQJ#fN9-S+|Kgmz+jPH4N zypi#9{+ImDqsyMKm-GOXUKPBV$s;=_djsbB)9ITTfA71czm~H3IbG{_w!D64s~A@OJ&O1_^8G|f zcb)7+Khl|w{pL8vA7ejceK_`eRPAT64?T1J$@%%~5fcG)i` zBv-kPDUg?{-Cxe4j?VCYw30kwa8 zGKGAGe4pIIb;2p|0sPBG;Boyg8-?Wi39rLGgw$tz#QQd&^!dr8?FcwadIj%Lu3E?I z1IkYo`UL5fbB&zdO7!3>y)a&N0(^9?4tR;z|@kKS8=9rb?d32-hOKSnCBoa_~+3l0M_vddesG!S!Jh^@DYy#FPIL-RIe;pS^L; zpH%|q&@~@?ll^6vzePO`IkYZt(ZM(OC-?K(UrvB-xEb?u z|0elwYyIGdesO~Hi<`&;!{;%7CgM%?YkI};2Pk_?F9*IH`}wWFZ^1s$YkDd0CI5be zIInZR7k}nqWnAh5etpm9eLo)s9dQOYqWs;lexCpm|Es&k0Y(3-!@v*y{2AkPe?>_D zf{*+U_=M~K#g^9xRK8Z|9nm*~zUe*`eB5{J6dRwDpT~2*r5uAkPXbK1>L~D=;#*#C zW^(Rjbk&tApNyXPuJQVvhqfo6=MBaBfTGhL9Jf`hi_dV-hw*!T%bny^J^1;s#}kS^ z;{EjJ{C!pb_y_%Z6qCW<{(W(NuGig+U!Pv^TZ$7sZ(w{djtS3>lc3|Am*fAc>pX=$ z&2JF>8TA+m9Q|X4>-={b=Q)bw+u+A|UJX3H1A%x}yr=6uJk2Y|CqB|+re-||smIJC z@B#emDeyObNqEcSa9w}@>#tk-ePXr0x4)3D7+?Py^OSlqe}xxE(rYFBz2f{uWpX{j z(O*BkP(1)$^#xG*@Bno$0Ur2lA$RJV*U1`b*ym z?)p>e<2a)I?FZRw8s~h9Bb% zfUCa%f2|AHi21q?0It5IBA@-Z5q{h7^K^_$dTKoKAg!nCz|(n`^v1g5nVjlh|2obM z0B?kUZQp&L{ak-E69N(+i{HcDt za2~KOaQrsvP=8#m#d_lZ>sdV4!FtL6fZDez{-4R{S)KFw_}@gH0czir9lh?1;<5CT z>iXXtg@@!Ri5)h+VZ^{DG7_>FM@{}s8tq))}Zj*V}0GX05(t5_onszo6kaD^qm~{H}orE{GE5;U$MS8Z|n0fo;L%p z_*ZcXJk=G&-~Y?r+XYvaU1fszs?xR^WLq|h7)^9O=s`21h|z-{^q?5a(K2zBd0l8? zhezMBN&%`{fSbML%_WIvH=bU?!c*y-doyC*l zzP4%c3TXS_vK|Ou`%%ra zh;f_;=MLfRjz@cYRrTmylg@9_?sI|5Pw!&>bKWugJ{oZtc{rZQHJIA@U92x0=k&0z z)|0UFyC%GU!uX+Gab4R%JMFyVal7sKw(akV{f4&}c#I4B9ks{r1 zJ({oX^8pGUvh&Nkhird@-w~4?3A29lPsy*A%`bn{b}^pzgxvH`vG4bG;@&TPpW!^v z_Zj91q3>^>>2o~Vfs4-fx6k$mWN-+s@&rIpwm3(@I92hSIyt=UOlxR z3i*38`D4G9>s8C>ay@sN_Lp+6X7Ze}r`tjK3H{Wc>j!(9zh9$W$=B_t{cp}6{t$n_ z8=1V?x1K|v`*Gg&Im@28{kEFfFOjDyf5;R23Gq9Je$Mr=d|W+s-c+1}pX!|6heDhI zHm(<3pU}>_MTmao-PjGQGl1HMJaZ5I%s)Wp+4tc+GU9*_Gd%|aC&WIg@!qe*O+WYl zFyE&KeeRDkK5!4wq#xtVzq6(n;z4*47d@95m&~hk0`C1(X92gVFQC?Yz@?0z@(Oy& z-hiW7e;e-2E5E%6eg3_^8KIyoW z4*hKzp34>XX^i92PxqURPvZRWg8tiz`*p8_>)01Gi;FVv?>r&>(FOeWPVwV9oyyaQ z&pN-e>=AV!>ZI&>k>BB;puZ@Nn){=)&lKfL_;G(+dFFw|Kj3&~58cx==xgI6@8+F8 z_GN<^uKhIXi1Le&^L?-HYXCWax<9YGy;*mPdpgYDusHv3|(jPkg;ZsfHro3;&?;AAB6P%N&ALyF$PxP7K_{2W43-BD;y-oI>E9mSzQ9duW zdU+aG+$8jY2OM!d8uCf+{X#y#eec(N$fMlOwy$RVSe{;u_;S48$H#q{`K_#9m$ifB zOY{)G1V2mvXb*kEZh;4kI@(}|(%<+!2IdvcRf6}Sui=(&toOvDo$CSdIowML+IK5S zp6I;rZte?cw;OmKV)j=2!0uXCYPT((01YqW`H|>>6Bhdo?*}gaLy(vJ0kseLzBQ{8 zfZA{VlVzJ9FyAle{DE=0X91L76I^u!xa0>MazDqqr#g!FVTYIxfLcEhKe9KV;;wer z^}B25uTma+zZbaj^`9)`o(+7GbF=C!_{4rl=cAWw#UXmG6AH9ds$q`WNklV|dPHkoyth$8Lv+qiOMDT_K-CJEtXj zORt*OF+kyQj-y}6kNtvfuQ=Bgevk2Y-OtC+hw~j*E6m6D?3uoR+K+wzsPz+4zVAN< zf52zC-!okM(PyWs^`am8eQiGJH++rye+~8&oHzRrr#(Qgb2#ReeJO|d?b`3oeE%-) zuQ}hSJx3*Ud4pbcI~lL{=bl|K`ezrQUxR<`Icq|%bNYK2ceDfFM>Kml|CUkVWxwKq za*BShs`{4T`x(9RB;jxJPxyQjh$qaKKNo#Z+25-EZJQ6zMNPg^o*vCN?eYK$=Y1{p zn6&36?>W0amv2_%3;l5a*R`l<#``7QsQ+v5z zOrLS9OPF7I519K$>qqS#?7_Ua=lR6COZ>i{+cse*>sNc^6Z&IZ(98bAi)LRy)yW@V zU4-0#o76Yns{@q%6MEV2yYHC40sBOM9!|b(;6pfzc@0+{KD+F=w*&8IUfqPh2>;Dh z%P->0FW@!tDdoNGbgD<_pOHMPF5jxX*MYiec8T{q7(cKV&MlL)Tby42r-?Vv!ybwL z+P@~a)(POsui?d(lWe@!Iq-}9gyu{5$2t`2#Dp7B7Hd|4qgf z{Gqr49LVT;-A`KI5<2BaqL1Qv9Ce$10$g@`sy=-8DxS}oJzQU`2ee<^?OAvGpBE1% zr^Z8$dHQpF%;IAl@uqpltWH0RxEAj1z!e|A4u8_`zrJSiL7edcyzcFa2jCG8ruVAZ zuk@EaPfX;mXZp=%`o;b(;%OlKMLi;(0mI+DzAkq=?91>2_e;O~Z@^(5hgJt3n4N16 zPzO05@>}+!{SCLgVtu7uqkY@HvnIXK+5RGlL#+deU9z}7bw8xKe-3?q9_W%C%e)(E zh9~l9|Ci+DMf9WOhwohEm*l$_?VH}Sj(BGOdhuL5{G<3y<{fhXM1Sn#wXX(Um-j0U z>aGvqL(YHBy@&aabq;j3+2WiLaiso)--Pp%+~BMJM9!RFZtsY*i}Vnmi{u_YGPwcs zc{D!h-=f^PoagjkR?f-Hz8Bf!j>{4I(p=7iH@IJMm*0y#d&}x7pyD;LQ)i~%yvrAH zcUe32aa`?@$Jry_FFbVoq2nixpEzFDl~#>E_Woo0f0!C@$%A@KSnR`{uH2vFy-U>L z2fNVIA@Tn@X*ghw`+;jcyN=&4 zVI2ircK*sE%onijjCSca+zGyvv(tW`EE#aYR&1)&YgI#)9j%-H_YFmY{cCk z)g!Kh@R#-l*GJ!E`HFp`__etm+TAYgMZ5N=b;P&$vK@SY8{hV-#n%|Tf#2Mpup9XykHq&8-nTJa>owW~s?K0s&l&wsN*8*yO5RSpm!O{H z|K3i!m%@DAvQOt_<%B&N^s)0`sb6(M{XP%o8`u2ESGA*FXny7q`JCYS1|a;_K^%Tt zYaEYz&W8EUwBM-mg#6)$_n)9X8nnD2&Up5Ctciz||$7R7k=Ie0&+E2aT*E`H-{YUn@JdmrTr+lD`agw_nr*q0bVETp3A9@ca(N}ha zeat^ch!eMm+d*~s>1B&oK%FxucFcZ&D~yY$2Y?&w7QXEGvc=ugey##kd;w~I-lSdU z9<=9hh;mJg`n>)gL@7tLEt_}b0=bI)KJ)H+`8-SZ0IoU<7Lq1RJ<9Ze6-5%%Nwk{2KIo{>#RI*Ns9{uUKa=k9bS)J|1dQ`2n{bS58Xy3;R z$NJj#k8k6?3ikY=I{FFwu)a7h*k|Xt%C5CncB}k9L5|~ofKYk-bf3wCbxCy=aD{rJ zeOCEYwu>J2lzi96TV?+k&-X9n-;;{gC$wwbMZ4-TpuSg_IH^pKKk!+H;|`}Cu5n)U z%P-0JA+|r=WAi`VV|51KVO#w7RhAn2?c4`?`741vPN#8bmmf>Mr*Op)>|*nrdM=FL zH#U12KY!mF`rEvHb}ok=VUKpwgP-gO*q4o0{R6%D0SZ5RezOz&VtTkeIKTLo?^S63 zm7XtXZ{YcU*W^C^o=45)4m$|PJT>uwzkNRG1-_e3A9`l-W9Qf=@^rggJr0$JBa^Tf z@)1z=>jV9;58@qgm3fMG)i3n}{~pGvLDj$SnX`UC`5*RDTmouek&NHS_-S1|XT0r? z7R{f-i^X|fb^`y$A}_aNH(F~1<*D`7n$E@%s@F8h=90 z_W|q^HSYtfU%>U=#^i(Qh|jA!)_=o(uLl0RqIs|mc$|d)wXR^k8;XZH`wl7b_d~Zw z*i-wH#6De&!^EDd%m4EcvN%UPMoqkWwV4lY&cfZXu+;4f0@eyCgK2L6+&@b8-GkL}$ZrpE* zyTtBYP9Jfu^$YV(GH!mz_cuSZgZEI)e=*NoHqVIbeZ%uC>P)xdZ_wj@(C%Nqhv#+X zGoaH)ysfyrszZo3?UMkt?*`1<`_7<0t9?~^WqJ7{dys>9iFKU$30&`aO=3TyeC0aK z{_a!XO|@tGb-Fxf#zH1_)Gpr{gYjz z4*p2Z?END<)(%|u{t-X7qFvvK0AGQ9(>kboUGP22zB;d!b*b8MzPz}1ZlZ6g58m^& z{>Ts6qdsQ#0Mz+1iOT_}@A7;w`<8hfc^~!b48GO*W_JAHdlrv?d=8x0r8=7Vd#0J( zs4M1Y{$82s6>>zK2{`EeftwzSK7JABnPzr7^8O>7LmK-#iJ!Ayi#pU0cgg!Ud(HSE z&Vycl#y&{;07~ANFX(`GlTVZVQhUgk&XbInKQT^p!4I&|Gse5$B?rdmb!dR~;)fU9 zj|=vlal$X`p8KV*A)d;7op5=Bz9F8Hyv^foxq)BdH{6Fe?z3dq;UV|yknMAa2OS?Y zdkhaaKHzv6XRVZPn03{l>^I!&c(0xB%5@R-k#snp5b}Fi!nmIey7*2O?Xu?wcpima z32EP{R*WN@$2^9szD(ggIN|g5UeDA_Rr})6J<%8YD&n9c-ore@cWfNT@1q82pE0fr z2FDEAIjZy{&WVs4`vmC?I6}Pa_x1@}TmgkI|020dD7r1INAn0|)lM0`KEK3U&ij9wm99^B4Y? zQ2YV4PNw$dIN-Y9Y65lMNce?+#1Hg0vphU-e?Ze7vTe!5AD(e z?Sc<*?}PqlD%MXZdfvlu93b&TAMsCc?OTECT=t_A;7fZ>6zjImeXCotE7x)1KWp!Y zw5c8EBH;5prvd7m^s`s7f5W_`UF>-T;;u1H%Jo5U@N~t_Re-7kNgl;|(>Q(+av}b( z4_fm$ihcir>mB;_vA>LK?6*ul?dKt9)IsI-dmptZuTcMma~*L0f|QFm^PTeppLf{h z^*|8)Jt+?TcU%t1jd3%<*Xb;O>#kQF=U4DQm9K@~*oWKv!B2XgqA&SPXY`s6au)hg zj?fEyMx8$DK@V~47o>LrWq086Ta3rOo#OqOR*%nCyypYEKhufv2Jbn4w_`Qr(0rKR z>`~UIG14PGLr)rx z!|yJA9sN`qD-m zbwD^^j;sEa{OtKaf@kxcswXjD%%9a8ypN1}@_}sM^T8G9#X8er_5rSa&j(=#wtvu{ z_n=?rE3_LtsrIqX{2Imy&dQ#`c|Y@Ov#MK_|2P19f)Ak1VbG`H97de`r0?W=oiwWjefFw#nN7OUzFgPJ#kOf zkhk{y`-5CR81FrJ@89elewX~vTXKc|iTt`hNaC#SI~OPO5Bd0zN8=NDY`*fGu$#>%xxi0xk?`%w_zFLVZ|D(n=zNjz6Mw{|^kjUJ ze}db&sLS>2vU7GaUi+@ZkBYmbUvZP*`{aL0-qP;!YeL`Al)kjv&CDK>{}lhT=@kdb ze5*d6>;XP`o;++O&-3FJcYu@Ze}2vJwdUV9o}xc;`trV9{)+ie(H}egadE$XKK-oI z&t8uH1@1qf2jJAD^gVI@C!lZ0i{d;K@MHbY<$US)Lq31_<4V@AI-A%> zd4~H=%?Buc$@$ChG4CJq{<2P6|A_aGcz+R})gI1244jbn7m{z&>76d_HSqj;kn8h@ z4nYU_+z{vtxBHAO!=rr)xaj%~PVHd5=D2?Q{VL!!^e6X&wq8FA`>TBc{9u3LzFsfT zANW5X)hYh35BmY{%Z}5aqrIo?|Kk8<#7$aY-PCWALh}z4w%=se(Ld|Nz>y) z`=%#w)7$nFm1v*ZJM~;J&MnGk$P;oZZh&uR@>rfa-S9N_h1G`dktKhV`$74Wap&s* z-vi)x%-nYpHmyIkS(68FDw8API`~H1YabE$oQyxn#$TjIy(zud54^v+#QsFjk&IsT z8vfF`nEuQ9^ZD~Q(EbER?vDpuJzsnu+=6N7{CjQnsk2sKhA|8_WjxX}x0O!NFE|1sTUwPb5IDO=k(O*7Z zrhRfg4t*qFGT!`g`n%cSdsctbcJS}X;wbDG zdW9Ui*O|KSb&Kl+;t9Cbt=oowDB6Lm&eaW09a+8p5Wg4ZxT7bo*Ncj?qF(E~mHw{6 za^{a-kAv=r2Zu9G7wc`%1s?S%>OtglH^(LUtNb3GvUmk-RX^%F=NX=~`(Qxj@3p?7 zUH!oOXrJen;eUh68UB#`BHxkEnlI`B{ATguzsCn${!O6kQ{01uAEPb>-r@ELd{Fad zaS6VX8*vzU6Z1t~U(fhN{S3ZZ_nsSdeMjva7JLCm-9N!E>T`_G4|X%8GiqK{i?Y6hBi<;~@It@l z;bey2Y{aVzj`MyC^uQba8S7}b=W+PM{8id*IvbbCRas(x@*G_DVEs(ni+Vb1`;Q;I zf;hxJ1h7kW)bIVCp2LZ`V)GcBNBS8 z8%h5R=L4?!0H-s2Im0DyLa%i%;jep#g#VKCDfJuA@R1Df&+xt{#FyoVdCU9Zd0Y45 zdu8B?*LXh}xb_qH-yzGtNq=8>QqOsk^}FRR<#>Om+4=pQc<;w>ouh!uuILZEt+BtQ zE_~hW{Qh}5lofpC1=>7jt5`4?!*7U=DC~?#~+n-I_a~t40 zjK3ed2R;V*-b!VTZ*Hj_ z|H2;d_Zam6ZgTHo-_Cs8tL|Jd--wT^JJdP{KAIPBs8N3VzrvW8bXj~w9EP0YQ_6>P z0Oh)TJ}f;UeaIj7IZM8p>*aRB@4q(YQ$DWd_R)C*dau4ga{WW&8}Xs_7V*7uNjr|Z z{hxH4<@e>|=c0I!97&zg^PU7({sND>q5VOE-;o|TFAE+T9lqPLI&f(Fn;+`~&h@1a z-={X#A)T|z^#b3&GCxk&k@+*)wcmJxd>W;H@ZN;-15oS5@RH+8hR6P`*e8Hqer_1jo`k6nd1GmU0?#BRCC!PQ=&K1tD?)+4Dz^8}pfXi&he9{we z(&=M-)TJ(OSDYmDqD$n;@tCiG*?ggw<^dnAFA2XGr~L@p^Z9#RuNdE#;jdSo*EAmX z3A;&t;5m$SH0&-t61%oDj(>Q|@&j-xvs*XuWZl!b4fY8;$Y1E!eN}QlI>CKgg6HR* zA)JSj{j2RmhL=6=mmA|g+9S?~ms@(t|HE50?}w2G!m&>i#P3Zr-q|m>gncG)?&An| zst-kX-|6l{@7Ki--{GD+AO7d~-2RqBo)166eXl{@E57wJxE~h&Soe%?{cNYS|4W;8z6I=69C*Cp z!kYKzZ~c{9us7hwT|L(jPI-ab`M#u=e5%8c7f|^)-0A-A#QB+VxNYYXz`0CbI|uN7 z9pk4T;yrtVoc}xj{D%1z?fN|g;A--nKI%dEd5U-@^%qyuu|f^M!Wpe8c6v z$i5Sh_et{ms^sT;n3SK-gZ@tA1OMk_>vya#R&UC9wfb7t+v&#qYrQG?%lc#aeC+vr zT)cPl_a$Fvy&ieKwwON#ef=23{sI1&F6FPgzOl~!UDs3H|Deu9z1fld%lfXmjk+H7 zyOVXBb>8ZC)#7d5^_|Cl$m+V*Bh>dh8Na{n^}_C>%Jrq2@_ifEqYe5q+4s~L4cu*U--?K6Y-CI~-v#q0=N_g6EIyr1j6^(%kSu6&=lSLO4CG!{ad4J%#-~8bGm0KA< z-CHE%uV(!du1`D{3aEWK^jBR1>~?!ZeCYlG?NRSyKOWeJ>946MeA@T#A`T^IS|D^jN=-RwJ{5%lx<$j5L`iGJa z1FlclBag@VOy973=>Md4*17W%b{1dwvq7%pJ(v9adeBUdbLellzas8NGd*j>N9vZZ zfU1{CeAzxeTL(VMeJb+gqjx=zW-O0BdT9B8_8#WVM-TD7Z{vG-S>D+5HpD;qHLLSI zZ2yh}@#Xi2cNK><%c~CpdR%&)tf($tBF_}x@Q?fpI8Hkx_@MLkdj8ac+fi{3{=L*c zuP-0D?{=9qy8u^x`6%C?q&;9?*@e%&h>zL&h<5I0()!X9^Qj%{F6#*GgmL`7V7*a3 ze6Iod;kS5SMe$vJ4~ORj)*t&h^(S$oIspBzq;z2q@a^(-BI=6nBNIE1_&Cu~4&vqf z_I-9j-<8gj#9KGphc}_8!FJ&rj&ImK*6vI|DJ_@fdAzx`Z*8Ux!*-Q&%12j^?vP({>#;B z&FA+x!S5z;Tshx{aKDFnezN7B&#yfA5adw*sKxE`qxQc7fRyKLEuAh0`wgFCd-bU3 zS37a*f6IvY)4$Yj1J4T$27Wuk`!acqzO>Jr2FKeq%m<4EO5vOJQf&wGr3(@nd@)_j}lXn;fxzivGEAvn$|~_?NiqE%@ts>W3Cv z#v1vza2=?8?1A%tfV{W)!%rQ}@aO0^>d*VcC!^Q-A&G;l*?9FQ{GXJ5llCe9*TL@Z zhi{r60JRPeZ$V$uZJ{6XPczO8st&xcWA*^tVE=RTt}md|D~>Qu_I+;I?Z0f#N$7v~ zyR8QYKL0^+pL$mRn)8?a!GD+jneE2BfG?bYkKw9=!0%-8cZ-g7l=SoVozrtXmrxJ> zzNzUM{BLFPwC;HK8S=+{X~TLrT!bZX^pSpuhqLUt^x(hTDBp$ga)b1+N7z^Sm*5xKTjLRz5kIfZ-W89}%BlFw?cMG6ILqGi znf|Ir&`0?RIOXG`Ugq}9_X**@u&?%iNqnxm-)rPMJZo_a7=DgGW%7{@>lL4KsU3I+)-B?Sd*Hbq+8-ryMSk>DHrr?M z3D`#c@w`iQi1$@E#bq@$!`vvl2Jp#TH45m0;+T;s`)`=s%EW(V;%e3kq_Z}I5-Fki%Xzxo-s z7H{Gg{G;B1pWS=a@m$mB`W#xj*G152zXd4%fXc4~SAGH4J_}HB6Y~KMQjYqN^%J^W zl8<&G&hIPuyzflf-GnS;T2A;!LoNlWcXxLe#rL*evbb*^f11Vk8<=m|GM+9+wXCIq>j4E`M6&&I79v8|E)hVWcmUs zKG3c_Df(;PUn}|-o65PJ$?5nk>YK|c2)rNTllzV8isUQg#`6LC?FjQ4wEB3*@jG4* z4;??m^H0(NCoKHw{lK-pestFK0Mvf@6YVA!;0fnX`n4bZIOdVu{2v?kxB2)stFIqz zb-eWyp3i6e)}7xv-UlT=)@y^>pMbyNZMN?P-rZC#zi0mVguPEYd;)sve*!y@Un$pE z)A3y{N0*(iKRW4p@V#r(Gvw&X_|3}Bs7IRbk^A9+<2aA0|JdBi|6;fVw~X1ma)#Cf?V#cKPWO>vkWAdwi4s*zVaAT zr!su<3307+m{|4H%j{PFz1NM;J@gB3{ z_t^d8?=^#u?1cZDm_5TjcQd|I&QJRd`HOxX#X4m3$$rnaSC^?@qIbXBTl)^oJ9t5V zJ?3$L_T}`hpZp7c5l6B!@FDNlxfF7`TxNgtZ^U?)TlXM}BhyFsQFFcE3w*A(dX@Au ziynV~!q;rQnOU=Zo>?_H07KsJpYh)|exKOJdq%L&wTk%@Ppkor^D7?kzmuW|TnIjdz-=7(s(st=qJ10ZKF;U;Mf+)blV{uB zXN5f4CjdqshCHkE@7zA-BV5INhMT=t|68@sU$y@E73&AC`}ny-$Gx36`OMQVGMg(GjVSxF8XDIpTv5se*6ywA@JNU zTF1`OXR%QqJfCRv$3}*0U6xX&Klhya-H*PozvpjzhUf9a{c+`!+pbTC z=>y#2Wz6-9_OarAFY4eL<-vQ*!T}{0V62Zpr#e%cH9EkF*Enun@=ltaKRH?42QM_RY8(O#Ut1M3uEQjgT1;63i2n9ufW z)6mbxb^G|h`-!La_W2w1OYFR#jUV*!;zxg7W<2Pgh#vYi$eZ9z^*N`%x0!s2{pG*J zo}zESt!{>WN8KK>7wBXU)PpYC8!+>4RsK7>zjDvzlfI-Qp7{SxHs5R`Uad9azMn7G zuSxd9{;F@6@Ynq(q<=tq$Q|`W_c&pP@?8AXqSH0h*|Oj6UmN0tb!FM}blL7(!8hVG z_8H+9-REGu`T>`HzOd^E@pIl6QCyY#7}PKNf$N+>!^^xhysQ(-gKJm^Mc-z5ajn(y zR(pO=TzvbY-C-a6jyRh=0B%s{{?GK7{(!gC&vO*}$MIf0;hy7rb`A$V=X!^oIx<{( z!j54Vo44dMn(@{CCz)p|>z@=}%B}To9_MSWUx4#YZ+bya;rp&9z6WhO9_xq3`+EeK zKgP@Nv*q|4m%i6}+^(>n?A`5nx8q%oca^y8v{|&9{Wi@nKhF2RVQ)a~cRq7s`y9X% z${+2Yoy2&@DL?Srk?b<*jiH*v<%@HXD}4t@q_xPGBO=ya|U zy|t?zmGj}ffJXY+jGxZUm*-c#*{Dw?@;~aI>Q_<^`efI#KIZ*18NZ;9`0RIi75A{$ zrtnVaPkVIQzUi|wuqXALaepvxt-n|Fxc3s?iu`bU$}W>`$I@@N)qlmuUx9x}kG%Eq z@_%Blz6O4!Um^}hGX0J+T=`YR5B=`)NuM!0rzi89Jf(cOJlfxa-&yfGK)pY+WO@UR zJFasJ@ZAP|O1r2Z@m!<)IH$`$fT~jqcM!kT%G=*gJAr?J*C0o<6V`CrxFG)`NkM@F`POs+y79c;> zErhY3yYAO`uY9AXO|uKcH*S}?3%s*2v`H2-WTxP_V&IG^K&2W7wJFZg1k=y z{dXM5euR7)TfqvmHs{^IK8}=F328<4H z-0fuXKNv8Olf+u?K zJnD98h|?GE$*vVZtq+Oa6h|MY{Iu_Y-76!$&Wh8JC-l*MK@GEK+dsRZ-z9(h z;GxC;yLpd;cqUE=yw3OrRD8d?t*SUo{4%2X*WEAccCV1wCG@^Ux}-hgE!M*uSzTLp zJmPmD!xhgJ+&3_;0Qd)Qvq8~cu-v^VZAEf2#k8yPOUBz5D~1^rQv zqV7c92*0em-=glwUP)cZ=Tm%8AD=h!Gx+8F75CRh%#VPr9Cxkmc-`@u<265j3^|TE zEc!vmaStxMtaAr}3--BOu^$dPv@1R~ZC}iLAJ!Q_?L$8|V16Z}|3BYu{{Q?c=nU8W z=jT_eYehfuX6jzjZ}=wjX$tcT-m`oo&U^!XFYV2HCZGQbaolrj-p_60z8doZPO=~E z+HV8u{S(Ni_t*hd#}Ym_B-a+^qr6-G`-jvM_Qt;YUd-csdmJuj?cLU1)w)HwoKE#Q z!8@{X(hL3lP9Jg)WcWPCt=)oL237CY+#hS(cFsuVk)FI?7a!&&?Q+ldD}c(klD=9| z|Jn4J9#5&hCh^cMJNkb3^J8`nfj`Xd@EiFjcAwLHrJYm{A&>S`fQ#%;^fmwAbpHdY zztA7`nECJV3#k0PHfnwZoXPr?57#%WAFwZzNA@l4gL^^Ruax%*?Q7?CjIW8$rR{A0 zw~);H?Urlk$>hO4j&-QCtJRe;xAU02??6AKbOj!CF&^>4`9FPVaSEvO#Y;D>j=V%W zAuoYjec5*0+dsc;-#L7$%f*E!(R z^DZwS`#(KraZ4!N_g|l$w0eX2wN3y|ia+Z#=NYp1pRP>;r(GwDd#qT;w2llfT0h`{ z_?PzD%W%ah<~9BCJ~`(}x$Y9 z*LUWLo;u$o=a~-a8*v9n{G}zc)wLRU>o@)xa#D4A6Y-3;w!?eHzRj-h*%40y)+oV6v3k~)e<~auT8@^K=^ZKB1dTzqFImCX0aWRcNXn;>?eZf3Y zukLgF%R?4#fLqk(=>g*pxIp{~>etDW(O2uFPxS2}p2YiFhIeFmcQ*c(_lLbipYV_Q zls{B;x?>@@F!>)#Z`BC>QaBUr}ciN9B9@7FTt<=Ky`T_eZ<#A6TEfJ;!yPO6SSs z$o(AWFUf=W(EUpA2cKNd=#O^c3Ef5f)lc~-7voWOal2yPu&?Si?Aayz@*HCJ8x!m? zyP$u-?GVq)hP*%QE&R?W?$tQohWgopov6>J0W#9iB&XYM-?Cy`Es-C4VII-J^e!I;H+= z6DAL!;V$2`aeEH{(EJQaJ-DwRU%p2P`9DM4p!TtgC(sA$I^d@I7frvwNc_4e#Hu`{o3naC)u#pV_RoHsaW)ao%$Ll=5ZT{EIpt>qNxCc;k3`j|=iUe>)E) z{Isu4@FDJ37U4I+`_S8P?bBa+b%r zl?c0?pW+Dov_9Z@xAGLQUv**%=XchX@_ia_H@lsqhw>8fVfj8?nRPw%+%eHZ>k#CP z_)#4Je#i9~JYNs|ZhK>VT7I;HALD@UiXC^G-cmECF4b3;w`zowJ!p{Sbr4{=h$=B=Wn3D zHe>#}HdF0p|25Yi(AJlt-|=au_unzP23&lxj%a+WH%=G#TUc*;Xs@5>v-}|>{spXK z%qKwIXT6Aeh4!MJ68Czld(ki4c6vaK_w(l$_F8(GuP^c(X|T)TZOVmpU2y@ZID%ab zpSC=M9iu*&9o-JY{vIF3S=}u6i(RTaC9d(Hi~WSvrxI`2mw+zEgMW+uyqSB~Ly+(O2>{v3tMAvE?bw7Z>QcuK1aO z-557B#lEn?Pvtsj`{2v3oA&)J@+stLx=tnhS2OvW(kJWStkdg$0`oN0H>um>S-f84 zXRRafL&R~^G5PD-yv04>dNVsG{UK**2iDQlE;)`o!o27DA?fd9-T~LTg1CzPrN#qS zUZOwGyNPCUCvmow`6I6%mN#2o54LPya;{#cr}o{T3w~$G*VI3Wd>bx*_-DZFAi1uU z{#hnnl5Y+3CGoePj!)v|z}r>Fz(@0uAM=O%dgf1c4&OqJHHO!Ov zyJW)8mdvVbAV=3QEUg>uUxfF-sGnDBU`@M!ZJ%`^+v-y*{6mcK= z50JjB8@R8rJdF7|8vFCQa^d;sB;&{PbGjdrKM~g)V;`0uA3f! z{J+8WuV7y~fQxi2Qv^CR%LeaJE1yP{mAHz>Um zd@JjpZ^T>Vw}d|LH~SR)TP&|j{d!$q?LRM}Uz_Mh+#VxlhuJFz`93)H1TKAn^Y^rg z^WLZ(IPcfmzYKc|-Z6UO?Emr|{SF?X#siL!4&#C!@8O8^c`)r9ax>pjJ1?nzm3F?@ zh*vGXihTsmW$t&{7r)yP{;{|l$MawIAHtu6M}AH{g#3=H{SNdXey3$6=!N@x6kPww zA8{V*zxY2rRh8e$`g;nmNUlO&@M+lhoZ3eyu9AAEd674o4>0Z%WxtRM?U7gAzAmn~ zye?!KhLat(*$r*KF)a|1CUcC2db>PVDcIfcPzUP1O4(`F}kMg<+M?sQ&j?)nHp_P9hyR||7keyD09BX1 zxZ?N`lR|9p}27~ZD#N8V3+bKZ8?k^N{FyodTn9Ccan!24Oh*?wT(YhYg1rQ&{*IQpBs zFO0g*agayjr|`bE`D@wh&H!=vHR4R`7VM&U0KDz=ic9!e&*RE|?C)sbHm7gH{@d&w zChk8Gq$usYCT^t9! zB}bxvZ$__mpK`domz78Bp!`C6v=-<8Ugs0`2pIZ{f3nZob$dwOut#~nUY+p%@XuxC zlU~Vw#`13!aRR$sTlIWgb9~L}5!$uB#XhgNmydCMMvwL(+VQ1%i;I`$?RQlc_t1}a zK<)Q_>Hz(SPryCt;dzholf;wypuEL6#TB6T9SN@I$e2(30dH#jy2*2G-R>){ZCJbj z9;$!C`#0?S=A?hY`xRek@5u0>3|Af{<2N(@%9BJs+kY1FR26sV*L@M7>N50eu-6yw z0f)T+wZ0_wQ~mp?y4w@)Nigq!YTNN`tS`V{=6=zj_E!mi?GOGIaqSxxxxZyRaDQuY z5&Etxe)g^ZCI3HFK)omZ(!QN5@t(Tk6;OGd)P*|jpWtJ}kv~yaq7JQ{KTq*oCm(0| zv*hu!WX}OGpX345dY!C)6*K*KNC> zNchM4X7TOo0Q#-o0hfP~uY{5Jk-xxKyg%}2GSg=^!zaY2+v5B>;#GK;<6Vwlb^NO1 zosM@p-r;zMeTNeA0Je$WCiJDeo5(Zxb$Qb44Y)~pe)7oj`6myJpT=9e)j_v!EpA3HZ{>bIkc?5Eg2!|X#Z;ZG8TLJYoK^%pS$OF3<7>{o|QGwcl7Cb^ngqdp_{jkoX-vrToOaif^== zfA71!kL){Xw441sE|xkB2aNg=`t>%Qf5_(_vfsZ#{Fr?Y?RVb5-}J=%)DL{s57?RI zkLuC#EczL@v-qyT@=oWZ>*H3J@tx=-;}cN(_;cW*(`nxUy0hTWCHY-;-kX=>R~@5% zkW2QZd>O8FI_bCdq}SpS?e~nnTJia`UrFc{ude~uI*oDKPrZimp-;>o{I)XvqCcm< z2zP$icbNTxf7tJ``1tD_U-$8fPuMBs3Hf(2{-JNk_k?)$nECgmeakn%6XjFC=WDh*q)fAohuaV`(omC337CFBUXw=%ov`>HQdZrTg)jb`K5h@T^8 zpY-Z;z50AzE$36-7UNIr5q1jswLeb$Ci#{hn0$cSE?@4S*XpMjr}#|lwEVZLH{NS) zj91fp(e++DRo_GIsP_>M;b-~n?@k}Pr~T|=@8GHDA68GNtsm!@KGHu8JvcvLN5)_D3I87U*Ql?-Wv37S)`gezpNM|T>9=e@ zf%{jhF9&vRtd;%K&M)dy@Xew0OyqdLamn-do5Z1y{%|$vrdXVLUavXV(7j%UdB7;eMKMSNw0g{I~Hv1nc(giuT!nX6M^a?PqqC{mSqB?Dvn7IEc6kKWLxC z_^>#s4!U09#|iN-{oX2k!0dc|z}^F7yr=C29^-<3(C#%?+GMxN3gZp>12=onf5g50 z%SXliQpCxu>l^D@9xprIuQ-6eqptUOoaFsxFV4rfIsN}K(hK<%^NS96Pc~0eKA;PF z$pKvJBkol!FQ@GOK8ed*pQ&c^B>AEKf{kf8<%jYpk2m-jUHof4AC)EPk#J;e7?@U*wgy6PN#nEU&(DY)4mE9XR2& zs2AQ3d>;Fb^!>ouM!Xv9K=`2{-?3iEFM!I+iq|E;LC)7CFS{ux+It+HkSQ1^y-o?`v@{e6xfzJfS3`^ay=Bae=}J?!<6INlHL zqF>(o$m+`bW-X2YRZkQAo{x_@DIEN-u>I*18yE8dU&!X`&+rlA?{Rwqnm&cT_I~1f z){O6a*6e+(r%$SyAN^`6Vo{|Dy}cq#t>!TAGThQ8&qduCri(pP?!_hJTVzskR3-85+Ze4my5H;n$@ z`Fw!t-@a-6fWrT`TZRJ~KIAy-75My{eZv8T|Jwn>0fpb>xeIiFMqhXOy7x05**|Ld zzrJHQpy>ZK*E9433jbGk4F@!Q((y^d|K+sdfa?F+yy1Ytw;mV{DEwdEcl!I@@AnN` zvp)WzkAEn<3`K-U)mR^APg9OgO`N z!IwDU9OaGqFDuVaSUj}=N}eYk&-)h4i|=kV{`2Oq7C_+_)r;TQ()bD>{dr-1`Hdc@ z_jv!eJB9-)9=1)M3gD&q{d=Du@N)D%|G&5SFO+x3`2${x|BmwqycGSfIe);*(L4WN z>%sX7abMPZ?cZ8HVEhR6eDY=H6W3p?i}VkEe^hY_sPSJLvwh#!ynX{0-G{feK2*HD z1^6oG`x@>?1l>=-uQLC>)>pmrI_07M6`!B;s_wXaEpvyCo1DZ=9_$}9t`mA7uaJj= z$}{ro#yNwq6Z;73=lk-=KfK2ddo;0EX(zWE^8@zkOXd6OgwG2($>)RIM<()rb-Gbr z^vg~d*G)R~Z$IJu%*V=CS+8s!?bqoa^7%NkqGc)wJ16}*oKJ)U1yJK=rOzmE70`OIEfJbfbK z*X;vb@$_oH$@glH$p>8Wz1mZqdL!lev4h5V0dDeKb$Qxdo~teo|L62__ECH&?;GPe z?9Jmv`~f@pqBG z<-feXei-%B?MFMd0GB+QH%y+*RqF@dMLOW}_nW8CUp{|m3B1YkhpY7erte1yY47jA zzKZh!j?4aLmzGw(UxW5H_lSS7zX}}no%^$7e}(tp8=psTf0gdHCOH2k{P~*fH|_ok ze*!oAExG+{Usc&$a{KXpVCeUzE$R1;7k4dR3jCq;vv%wcUwnxBanN5uUP7;TEbMuH zIsUl?qgOlnfp=5i%{}+q9^U8Re8<8G*U1Nbwf_VhWk39&b0Hw#=WcnE)k(lAzsDnA z(F0!Ao@ebp1}WcJ>;0#uP42KKaLbQ7X3tl=UcPe2^W)&s`SF_M`Lx%&kO#QQbIaw~ z*1qHI-#csY`#MbiS8mz<C%#7eT_T>(()Zl`-zN?f2gUvmxaqs;`mRgQcl=bv;e_kE>H4lqU-na{)TD@<EZ zRUfS#_2%biZM}S|&**<)(CGuW@6X?%cF_OA4PP&}eBLcvFR%0nZvoVL3BFqY07u;) z4eRAombWc{JFqYLiXQN?>*Z^T$B!JF+_63ZxBTlhd%n_T{jc{G;J z;Ah3-t3D6mW#aM88{%KYW8i<^;&G<4(SE?qejRSVR<~b=+ppE^_m)HH_l{pVu=omm zlfMfJ|EnDk_*L5LEuBWciE-Tb2L1}(10=oL0fBdezVhl_n-@^~x7K#yEr5zg@Kt;P zHjT%(Se~^2DjvaC^njO*$FtT`-AAPRH|qD1QR&5fot-P|rYG8E&&|hX&&?Cl3%Jf> znib6RXFkf`V*~%l58x)xeU}IIobsc-YTezuZ|l+~-!HfJWA^_e+fNhTxTL&i z<@34wuTSE8i&S3VCNJt3=iQ_q3H|fN*G!&QmrWkv zc3-l5>HX1J_8Fv}X?)2ZW9Fw9+)v?8;3m(4%QFi)}sPtz|CJHF3*t5Gve|LU0R;A;<;&k`V7w_7FP}Vg?7uYZj)!T)8yIg z_WIOWUAknxKao6NxYt}Bw3|F_E>Fef@%z%viteu~H{5PFh6Y)mD*v44bHV>+o$ryn z%=k8_@2e7A-|zhYcwwLakJ+PoGxL9@-PLKNl@o#ndM^699=@*@T(djEr zkG}-v`wuTXboz%*zwY$wM*l7D%OF3X(LZqd2Ts53^jIGx|A5i|jsG8ret(JjUUB+; zr^j=5(f2sL|KG$c^&x$$)8BFWJ1)8!Yf8FILKk9wQ z>B*0NJ8}9|r(bpYL#IE)_b;TQKMZoeQ~6EGYp{d*d~4aqFXQrP*H`uk3g{hj1rarzav->%VrYuD-N58A=*qbt9;>GYdUPy15u zL#ID+`V+So`BC3p&=cNv{I=U`%g1lIz4m-Oz7rNb=jXVHhZ|0R!{^&^`W>f#==2X= zfBK7d>ooe=iqQkw`(UsS=ec#o+AFk|;q7YYc;fUo^&AIo(CklrXrJ2|ziw+U_;u?( zs&Z?<`3*Qf_vg35pZm^l-}JdvbNZUoyFb4b{@iu?U8kpfly}_e`+!s4KDXCRr@v|M zU%jyI__}>>{lc2#YmP5FzU=st<4caOI=<>S{~Y{wfp7%lbTUXxHgM1{P;K%1R74Wn8$NA>` z@pxWF`dxf);?VhN|N5oXY6s;X-ZuLH>OFGI=l%Boe_xt+d>-;P(jSUn*{}Ce5_+x| zl@ymOyk3SF2@6s&l=?vwD*-0zGK-u%arr_r0E5?oAsMrnfKuL zB|!BfUxhE?xft6^y>h*0vwp>EIbU_I(M~S!+4JRg8_)Rb_d+0#&5kH%ja@F$Gv`BpR(^i*e^LY3VR#A>G%fxNPleFdih&R zz!~35Rr&R&dG7}K0OP&xsBgU*=kF!{R+o?KIyG)Y^N;&H<2aXdzVS*eYgc_p>fKn@ z-y_`X@AWyWC)b&8z?9*qz&$YU1IH2mmZ)2ZLdjd`}-vRY};B$;O!}S~;_%iM--np<=^sAjX z?l0bne#QMiJ+91fK0tk^_)m|kde0kpe`7noi#EEf9nX=`p3C`L$0iS;>eg>>yWGAH z{Ot#hKd|4u0-fp>q4QImfbRkApVuw4N56hY1@a09?9S*_w~~J4UxI7C6nDLw)~VGk zpBIpFCUwf1DTawrt=`1*RBu0{ zKi6~q3He8SguG4b9Ob&?{Hk+_zN&M;ZlsKo@w> z#rOrwqu*g2LcaV?Yt`y`Sx>Yty8L&8Rg-_*<&W=xXAK7*`SoWj7B_(MdxA$ji#n+J zYg;xRa9VnSKmCVsK_|ZuXFvAkvP)$e_gjoNmj~_fy@kf1-}Yy|-wrxJo7d&8`Ts`c z{fS*vzd0}G1MCeseg0gJ1JzQm3G>6=g!V(p_vrTfJkk&M^zpu%^f$ai<4>SJ z`5rl+6X<97UA6ByzSpAo|5@(0XvYFp51Ywd>WSZ_ar?TS_8o2A)*a}r^(TR0-@HA? zbzVEizEg$2&F_k{#NP6AGH=fRvU0?@i{u%1JK6fqc!_vyE}!Zn{G~hxj5x78#kwuK z#{av$b~}drg0N>mi}y3*zeVdA}`u!t}TO$6|HH%lk(G1u6@YM|;52${87c743 zwtq;*=wdaV};eSbFJEb&Ai)v?!sTOId0#rorQLvc7eY4HM>&l7f49%KGd=K1e> z{sZzH*7BVPvNNIfAEn)K@9*|L!|tlLMV*|s|22es@m=N+;~+h+DsD_oFU+tqu>WDMw4Ol1L`>;PI4jAi8jH|I9 zb*acd`ltPt{6QFbZ}ni>_>kV`k9mvnE^h-Kbr61$K7j*99Ib>L&Ns$uo=RCKtbRC- z@rLJhqvWgj1YPh6zM+5c>&WJhdOb*eFh1-y=6F4AkAA@ZGuj*E1fAu{k?%2X=n3sNmGM^|Vjk@a0UPoGc#U=7cUU(JW_7yS z=lnwcfYBau4me%#*Zw`JPs&fs7xYmNRL9b~MLx+mtCKvR)80Iv6RtbH?l{lSY)3s; z|ElAwzF#Qw$@-VQf7$7QYn-1?xPK78phG)xv`_H-zW)gJGN||Y_A#Hg>pV}~`}w~7 zJAViB5C>F#t*rt_9so|${=WzP1m|sEOq~Asy?Ml!_dBk2XKmB=Z)=;?6UIlv?|#Nl z?;RxkhMMw&9VO2?_EY3X`x@NM@LP@tz3v@AuR681;e0kw|82bD7yUtRbS19+)7q}- zzdwLH;W*%w6L`Dk+i`hzDhul0sou=mZ_9tnxG--n*`>wUYYL|(+YWBKdxc75}y1^RQp7xojxxrlJv;sj978~3}szYFiz zSijkCyQ=py_8He~pEWq!ZSaK?tYd8FeAYio$bP~XPFhqS5_+`*mpm`@R*Z_DNZH2dfNWSm&M9LWD$!0CUC6V7-fj^BZ$oa<9Y52)t?`@3#8K-%ZP^YH6e zO@H9>)7P)!|89`K|4$q2M#%k$gOkE(XX4uy--%zI z)Xqbi?`*$hcD6d7%;$D)k^gf0{K0Lr4b3_sYB9-@@;W(e8lWuk%0TSd?C;{n%@MF6^!IHT3Dt=%f9n+Dm`b zFYw1^#z%RP*sI<9gTLfO|4KIQB0r9Z{_=jipYf0R<+nt>XgB_){i6NE=anCRZnC;Y zJoo#-r02yX-kUK0m>;()XXQcI#rTe4->H1`Jizc|Q98V9)Wr7SaD7ymvr5q1!Fuc|F7D9QSyx4rcXyH^XP8Pw6l1 z2a@`KnDyuSNuDGQqJD?jpOZewA`S5Bl};al4ht-Fw0KOz*1Y_+^T)og2m8K%*>fE4IjdfAzr*!{`xQd2 z+Xk(_Tu-c@`z7}8`11<%Yh0{DLBDDL^Y9A~K@YtEyBPQX>88a!pyK5h<}Hr_yQx3u z6!(Dh!jBw3vi0^CaPKF6r{Q!qzLt$wTw}cPKdLA`6Fl$FapgfmKc4Z==NroUr!stA z_85fyXqOGxi4gb3#J@i4xVHnZqdk3Y!h4C9?>K-y2KgTQcm4N|Y(4!S`fVL0&UF*` z3i|=Ijsh;rZ{;~*+;Q_S_Fs}?LHsLUU$Fl>OZ;R0;`x$(o5%B8z@K;f0_q%&`_(DN zOJy4ORE(Q(@H43MJNh*rV2%9=zLU|%`96mOAvfXET?K#516=pGm{)Pyq<_lCYaM~y zidVq6pVa+XeaPeloX+G@{lz`8<^$|ye}d0Cu6qvr-kI(n%6sLOZs*r%cfUvd`Yr6I zji354PIjd}Zb!*COu0b^C_f-h2Ru(g-k>+XK6bx8Mjl`s_?@Ni4fexs8V}gZc>w1f zDsB>eC2!*A+KjnA)(=Sk=nU5xWOTHF`Kc>FFi z^YITi%x{1vE?>hvZ;f>W^8+4ue}n$z^G@75QO+!mlJVL%{37>jusfjQ=06>SKkF3V z-=I8gE>D~K)B4m!KVcl}Tk%|%IO`Rj^V;}j^;iDT?+gEM+3q3!aJKqTIOsP~7TMjUU5?}T1yOQ{ucp>gHHE^fJ^H4eaJ69wDTjN<-uuO`-EgZo&RqySzZ8c zI)9A^o$482;5NR;@t&eToDetIF9FEwalKHCy`J4{shnaweL#$L;s7$UBpNA8FrCgfL5DNU{~sIsGqn>=;hbww`!(Q8 zjDN~2du|tUTV8M-VjbW;uF9e%^On`?h~}1NU9bH{Q1yjQy1Si1w%>fv;TP z&r$DJoWO5d7bUmx4}0fw&bodPKk`SCC)1f8+OH-3+7~2vtZyOrAo*jRm%Sj5+JT20 zyBUAk;cR-m7Y=^l6Lo$n!&iu3zh`~|RJ>ik>-b&Aj~vH-P4vJ8u@4|*os(aJuG}B! zp8k(f{}_kg7yun0aK+yrRSb`I;E%<3-1+(b2>SJqKVVnZU+lBlp6oBBXSr{vYCLe+ z;~csU@=5mh4g9Y^aJe4Xz2z@;T08@aFZjngX!s+ie^kl$0};ovAM}#_05vYb<+o@r z^(pp^W_Q;s*%xK{7x~dZpX@J0kMAH9Z-CmT;(sti52!ku;`9e_t54HL2fOvrer)&t zrOcjrzwDjp)v57QKJS#-6Y`8W-@g-i68Re9A+h&u=4BEOZIm}1C%lZG)eHLfoA#fW zU9O*0ma_TepL6C@eNE(>%=n);9&wq+Z%!X{LmW4}XmSDObmEiPDT~W$cP7W7-P zjw_y^UqG#Ee~dc8y7I>dn4b{+#Q&(xac?IsI@}wnzZ3P4^tZ5&CVi)#yPY~0z+R*; z&IQCl54^e{`(O3^ziRKN|I$^|1@n9C<1N0A%|6$UEk8;>;r`3wM(Z5-_eegki>QaD z&%Vn)VtW12KJLXR|B&_nG52M}N02YxF4%+n6v}%={e;@rouil5P3WO@`j@WS|NKGT zOX^dNIE?s^|LbF>Kj1_bw_1OadfSu5Lq7gdX0Lp{v*Kk;b{#W2{Soiu>G!(Z6HxfB z#l;`(R#n$fcclkmGr6vN-3DwL_le$jvp9}@&!D$Qyi4DN?|f#jkR!$il-=Knk+Xuh>PTBTwP9o7Jj3#zh$}+hn-Cy?W3Vze}>4w(vPsyVZEw)wz2GXS+?){fa_j+W8T~6{oXc>Kgzw} z{R{q_Vcg~P_r{P%%1h=I<%|DK+~9i0@rxL5?b(KAI8Ls!Y z(9Uxm``6s>kuU3~;(-3R1$u*1jDvr%0^DG)aIW8JJM69Y<;tGgm+ijbUzBjn=V_>q zxZgIr44rzvPxpWN-8stf*!+(8U!WZUl`nw3j^FV4O|E4>-<11|KF*)am+SS|_0+u# z^qtLc#V6XMKgS2N`E{O${@rJ^Yn`dLy8f*uZxJ`%Uc^nS<;xw9n>!vKz!f)t;&Bu0 zf3jUwT%wLPtlzaQn-6e0i-S4G&!X2k`+W6D-SH<`8?Srh_u&eTbkoY&} zEN}gH6Nzj8@y#Qb|GvHN_|2ngCCl4fPpu=+qhVi49C^omD(gi0yzOzO?}Fp9GxVO8 zevjQhkL`V<#Ewg6=zpB?iTV)sRNcQuxnN(w8%}>le^vD|S*P^AUv9@g#X2B+PD|ek zaI@ot+i}|V^WU7P+&pDR#>eIDcF*OFxYoWHai=-~IKuv9->P*l>6e|)>A#=x&+QfS zpJlIc_lwRA&`bM=Q}$Xgd;KZ*afrh|^Tc;pbHoiK@@kxH{X8!MIf4$520BV0-zv}o^$2%SG zL|r0%hvOZNw>#c$`};a@(#85xTaJE%vCg2qpYie+w=J#!Z&AL@N0u*usw05%KVYmg z4gRV3Isd-m|2P}_eg9qWM}CvP=Of?%@mJO{-k|E{Uo5&ji}t=JerMVGr?I~`I^{FQ zDSrV2*E;>mn&WHsUI*|E+5`THeUSVVenESm$BE9%Xdl-+r}@m$UU#@&8Pq!Q7j{ms z0J4r(68W{S`HN}SXSzD+a)fIQIUlV?|UBa$mm)C5=eXY%zD z=Xs3w0hC-}H^6ST18NzUk5 z$Af=ET}C`gFTjY8v-)@F*FT4Tf>_V!f8g@}orjKlJ8{zeSq1ms1|Pe8!GEpM?p5V0 z^y@Ku+V8?7_EFxw0ru3rDEO+bz&?>5TbX@xdmhOSeHPb$-d73wsLP6bokOU9o9(ZY zJPrRvd`myfALF|?e-d}K%)i>-C-xYSy=P3GKbx`la?1W{UHv28KVt8H!M>Is^dsg= z^p`vsFL|2SM|B(W$zGI`{NH?N`~j_>c~3p2O`a#9uZRAUdj&m<$55y z)_a}4*Xhf=o~B;RbHY>WmD45bmE`-Y+h*^-<~*=FaOL6Akm1n|{847NxDQo)9(g?ROCc#q@1967Z=8_DQ% z+{SyH{pCta_%-~N=RpVgvEF$-kzW&B^ogH^Cw0&2q1R2&1+M&u-ogPT*I&6^02LqL zqx}+~^7wV&xu4Arx7`o7%@0lF4*f&!fKS>Ub-aFtJ%ew+LVu@^_OM6vhhAZK*_C<8 zarBE|kF5)6@1h^kE_(uwc|I>XR6p!c)IIlmJWtiU7$-dZ68sW8xJ9VAooGm zA99O7=)YZ1a)rMDXWU*j!M?(7g`6%=E#!8*h!4k=ztqd|-i+Sz(Cfort=Rev|H>|a z;+NnWpWvZ?pYxTyupeq}N5&`D^AY0@J;y0m=#6m$!~x~EV%_K|_VwELV0;hhAcx`& z`sKLd349~2<|$utUpzqk8uk}yJ8|*_T`TMQRvU1GI=BAyrqvI?m8@UqtgmrBulo9P>+0!1>Ta^ZGqN=nXw?d%yHR`=0FO{;Q9epX<&KP=4okg`_L&i~e+6 ze0NybJI0UL{qcXMU7#nR?#2H4p6dzwqj& z^x1{o=+|95=eKzp=X>Du0Y2e;x^KBVYw`mszwR;)Fb^R8lm7qCBgu1se#&!zd?UQ& zIDS9b+qF-qPg)(QPue|DJjdGFgS;HKhjFkw+WTqmq95-msDHoczv}Z^`vIP7n0z7U zru@Q{!{A=|;Qa_u9;!ZrAE! zgFQ~$OZj+?z0_ZyhWxNApyE7vkHB!|C;P{}f7J0&yH}|LryP+Nkq2z&xX1^zcggzFL23?d1@i&8U6YEs^jPIo4A1A4Ev#v)?dP)cl*TpB>N?L7xOxu?DllK*$q(f z@N}o+tYcH)<3+5K^*gq$2jj-UV~oy5J1u4yP%#%x~k?uzw8QF z*unWMHPTzWB=m#cAM%A9j#sz6KjP_)(m(VKIXYebu%G6qUnmdkYVkg5@2dir{(!0n z{|b4_ItZBaU39)H&L_s@@gO~6r_ek21g`i_=5_yAo}CjP9d6Hy;-ialf`7zE=J!*6 z9F;zGw?lcq8*!odFa2IsTqADP4=DYUIF@{;@I3BM|5g$O&1F9YKOmcq2QRGqBT{!xKZ^$9MQm@Ls zfWcStfL`_nKj{(rg*?C)T;AZ@{Mz-Lru{?6BtTb1tvRaru1SaK8k<|Chb@ zi>+%r@5IhNlyvDtw&`oemyQ*iwvS^rW}A}?+c{0y8D0+^(-M=*i>fHmvM`I`9nb*L zu&5q59ihW!7=RrRD1qvM4|*U_6%Z0lN=yWV4=RQa7bqX}Ky>sV*m5r8Xr@G?w47Rr z>3ik4zdzsFd!Hpy6m{>l-6BsPzP-Nn{r|qT_S*aGvmE?x#`V6yhXeP%WF2wA{`tSd zK3DL5S?}7vd>7-~UvpgWzTY3E-N7&M&GXlG%=@`YW4`B*a%=B*>-p|?H^2w`9Kc(Y z|6fhQABLvy*gqFLcrSqSUl{lA4nQycE_VD~+X~0?LGr_w`Dq8>?Z8cs=N%z0=0|^M z*zSvsW}Eu{Udw*_y*c}dIQ0iUu5tEjDg``YCm}keCfAs$Rz|Wxb5#!|Na%fp9i?Ab>IrV?{B(9zbpCG`|bTw{w)H~3q`Ee| z274KI)-fTTBU8@GqWKfbj(Ow1$LBj3|J^am1r-09-Ta^I{@sQbDa-pa$j_lz(gcc+2Yx{(TXz z5=Xv4zv{lIxf=K|{e$c2zHOe@k54yV?ukL@Emjid)a8BDj9(E3d-2=^;;BR{-%KNc4`9t0U$Dz060``i2 z%JboeA?Y#Y=hYZjJ^;F3RPcQ8QG78z8TgCRCw|fs^M^8i6B)l059K@PC7*r|yCQGx z4~!?hl0IV@|6Km{G)`EHQ_d&po1plBzLZaWAV2Bb4m|0R=FQ5_alS%*f5LLLZ-LyT zSMHCs&?oJ?N;{LElARg85qQe4@xYVaxtu)iDNbGU;a}l^FRDi?(<{%XTo2V5_@CBZ z?N^`U$M?ne1@!69>`eYszhEB5_;GQb<@gK8Nqka0r2I(z?nUFWOnKjobpUWQ zxqRvU{`vIeePoh(4S!q>zqwyeH3V@z>@B9Vx=#L0c|M+vr|TVFPwP<4?`gjB4{=L+ z%u$c&dEe)KlmG7=I3e)9x}M-CJ@FjRdQbSAvF0aT|K_^aZNTAU^c*_o`p7ZYkF$4z z^w3N8B?$YHKO`qzPp~#FdFgtBwedyrhrO}`>+i}j`W-}`$$re2AHnaiPM`QE2)Y!H zcF-x$Yx1>jfL`_!QV#qry8u((o;hZ|_9fugd(3#=9|wcK_<{d$#Q#+DPb=>w=wH@e z`2%((`;z~XeXY!1^NQ(le*UCwVRu8$rwp5&DNI);DJE9XB;df1itw@s(UBwaolX|kR5{F+?yi&g9e1^iV{Q4F9qvwj^jB^`8 z-fs~?Udl7g$GL>pf%(Q1=YdXo;CyCF^YGq_t`C;aO%-qOnTv7N2kyVs9{>|BI^gOP zpcfxN>orm8vtWJG`FwE@`pPbzi-leZb}~B>-=x=gj7ty5QytXqH^aVUX9?fV=1raw z&*iSg{M6r*ez)U#!dC;2xcK`7$Y;e3`e**>xz$bY`!LWYdnZm9Pj-d8r{*XBCcWg4uVz=BzC4!|N%}5}wWzQ(fWvokQ9DR3EL-V>jd_JI4YK z|5y1zdgw79{6r6YBj{87#)5Ar;J9J&B$IP1#;?RU3{u>Z{Yjt2xSpW$1NKQCU|w&s zuQr}=@x%2zpZ7zrlrIOFo@<$X$#3=p_NUYjeouZ#daq}ERIe#Mfbw&N$8*Yd*{4cA z;@|fBUpV(xTmY3%fNjd*a|c1pCp~cG)x~zKzw`DNR`S)7n!!YGB?*mWuqWl2=#8>%I z(KTCPXBxkn;VWIwv9|p_1I~ZNA8;t-$_~_XU*O^gIlaU|XTQWgwT3ItfG7P^UziR) zxjo4)`LD90T;~#fC!620Uy7aVy7WgKSif@pE!QR6IfVZY>Gp5%Yc}F~*xNfA_;AR} z{h_$hFCH)NJCojXoL|%DS~vg3m5A??xUPHzztE@b>&gFH*?j%J>`(5ZK9INX@b@-= z!1cS9@9r8;*NKacb;vxwhdSXr^%1=PW2~h7xf}ic!^m`2GGwu(7)Vo@zqQ7AO&-mfG@(OS`!_)PA z{$NJux}AWZ!7u4m z1Mo&&575slU(k=_Z~lIn`NMudxNrPV(skgv{s{NW9Djs!J6Ck zWt`V*eL?X>{}UZxJLbs`nD;oNmtS(bYrl)RlF3y)f{&hK)$&383w-2Xz{EG7sCbd>w_MY4;cLQ{x$f|5r=%$^R1i3 zY&VX++BNR?kSqFpKF)i(>Gk}#MxTurEz(1eq|b2R(g)Yow-FEZEkgODB|kUzl|M|{e$oS+A%-Cq(AfG z8F5Bm#`W5F{clRn0_K}vsw4F!)X!z+YpqU)qOKow`=P1#3~GLy$^5($^!lC){i%9E zeVU*5m8i2p;(+RF7ukn^U!1{scigUI_-ckPW_X@A9s2E^orv2`q5cH9>Q_mA?fW@b zLtpDNhyPu&p2D9&Kl9A-McXsJ7|*k-J~Ulf-IPk3b&*^um&t8uBr1ehki@kF_ z?7WWmLAv!mpZO{2p?ZWK^Q1=}B)e0;?6$kIYen>B+>q~_AL|pK@yo&Qa^O>ePx*a? zi@-ULeUdP}2hZ!YFZEAcXP;|)Z^?Ah)42LH@U)(2e|K><67rI+oTgFW@3E@%F{F28(me!nYzkr!n=frmcI&+_`L&-;Pf-?MSf zR^1P;Qvb%b^#@$Z@aq|VkNBS+m>y7lqb4u;CCOU}z2w)w?1diiBcSu4@=Gh|-1n;M z8NcS^Zhsan_`C5hmZ6W}nS&Tl^Ab$@h+24huUp^omVA}t-0#_V)J@E@ZDep86e~(B`<)@^_otP&%I9Hkpy5xtU@bl0y z^OX-(J{ND({u(~PdC)J}(aHE|J+9HOavXk`2s>nF@(bX6%+K{x9BO%$>}x+I&-xta zx8cxVae+MT6QQ5z;h$R>Kj~MK`=WZxg`L6I=Uus-HN4ZEx6Z5D_(JBFsSHp4N`CSB z$M0#uAJ9LI-w8bBjp7G6NzbEfd@#d@GJZL(zF+BGY-aPX60eQ#a=rbdUepO;!~1MN z?JxhRw|ErvNk8q&pF=-+0>9l%eyS_^2k)2P>>lq~3VhJ{T5N=#y-5!D>CngeHky~a z=QoGh|91ya*M^T{JlV5HKL5)Z#K&-1d^?B-`F5}#8zx-)6I}oBaQ5@qj{|D|{D=3f z-yhy9CNh0*WcD~dAEtgM`Gfl7zC!nZ*BW;sZe!HDvIFb1^DyGj)c&0Q4!=KO-0qDz zZuiD|CPY6Sd0N@$`P2U2(MGjyruvV(YHGjz&$-S*55UEMy#J`Lw<#Y}eymZC2iI^P zAc+2L`$JFvpG58aR`(Um59+JyrXNzDZx@$2zothkldtu!R-d}hsQgl>4^`v%odERu zS2On0znW=I2Cj7-*Y6NNSn@g#xF3E`ewpC-pKp2m&$n<+3H&d%jRWp<)8jjJ-Tu=0 z3w^Y10;W1WBAhu|EKOmFIVcqn!UoeR%F6=-)Hc^yT~9pX$G6 z`u=Ohw&<(+`tbW5^{UH*T~+@mQXOkOh2C@2cVO504(#?!mvK@*@q5fQ`#NDy>MN>0 z$n$!^^9jn^alJNT9|h?1tsR_GdEEVM$LDu7efzWdXzF-S#(Ey7L*ozwOGGvRfxe9`_-{VeNG&D+7h9s9-gOx|FIM_<8r z5?#0MgK1x!o@>qeyaoCsdYvbuUtZR{YQHq9zB!AyE54}?V_#L(VH%JA(@1_vc-mJa zKLjieGyRXVXN+;G>eT0q)xO&AC06!$9|L=iv%eK~O$AgQ*Y>#&Px4-8eyRvTGvr zJUW41b#igGNO4zwBd&VRQOlq8&_At9E%L3^`El#tc+f>&_l~0<)b6*@&$@8@UN7&j zKUrM;sMark((B2P;)Q+_@f!-5?$5M;!E+$>gX(!}iKlrd&=spcWk2gYzjoiC{q7%f zokY9=d$rE2#X7Wx-#>F*TX%n|`c^Taef#>Eg`Wv~%KM1sMphr`K34I8{%?1WH&DNfaXm*Vt~76u-|a2m zHv*~-YWS5Hmpok8z8^5*`8dxjKJ^brybl88bCXZ~Bhnjg6aQ}@hdAI`#$Wr&8vh*E zzUF&3ksq-0d(0E^;t znBxz)6XVu@tC-5@=eo!JoUxjJ*8JoC_yeY(FVAKLd*l(W(8NBsZ#2a`A6uZ4Y^ce~>UJeTWz=*fP)^89@DeSfVU%DkYT z|0nj*S{?zP7QaVP|Bnjo2fv;@f3BYY77h9bC_z0{49(e+Nr{OoV$nVcfqy4u3uNcM&j8`!&EZ@`pV66VT@$)q7xr zv;)_d>E}O-=beD(V_qx9Z)hC%(}MIT>46I(UO_Ltz)$NI$B&^`T%R#+zTp3Y{1W|f z^3u5C&hr@gb;JH6&UI>M!|N8H?z3=xj`A;7e)av=V}CyYbZMUTL|o*j3U;J_Z^-FI zTrJ0W3OO-udA%PQCjQld(r%q&03Qzjt4}XXSwFzhjGy$y^)CB}hkZlj1AHuC!nI$0 ziFwioej3c|9xMGTdy8G!$^2KIHRUh()%Vel<2yEw{l4SttM(V5`bG^OCm+n4C!gB< zE8YD4UH8}P^#gj~_WLyA!1+g(1E{Q2kT#g!`Pf z8t=KC@l_vyT-l3wD=)r!-}(TiJV<(7r5w;DTzON|U-7Ku`+wVakmrgE-(PWG(f)U5 z#^-vRPrV>-Q~X0NuFGGI3EKy#I;zQ=2>a!y*XMEn3O=>*fedd4ee##~J{TGAJ!ZDSiH{(zGLCvk$|n}Z_}g) zzqa`{kI45sllCW|_T}F`06$#+_9I^x4*vhT6F9FMm%qU;_2m}h0zT@C54nDV4)D0^ z4qaZnL+257f1EJBkc|&8zVN^D0#J2b^Lv})HU1f1 z2>;hZ5w{`SFVeo{(m%EzIO7LDDZgv>4N87Be3zVCE!UP`%Xe6$>Qovj%E&Zz6c>n%<68gA)9>OlfPrtvVUmm)S{%keu zUd8)9;ji#xNAMw@U-)|Rug>A1r{i+ubBu2v27XwmKh5#~ryvh-iF(xVi6n>cJLBFT zf}i+*XB_7Mygm|f7%3*R`R|wCHS)tgL;3-V|MB?wYtnZk@^+)KDE%&kei!^c1O1`v z?4$OV^BBJ`V4eLRQFnmOn+H+1`@t8u=ItZzyY+Kj9btcCzFH5UulwOqA$`8R6a04L zI&jhNV&6^q^rxZjOTKeI>SoV$z{T%7d*`-g$Nh-Meb?c4_Pj3zbYI+ayn(CVm;AyW z;4RAk&VF&8_Pjoien&jX+XH`HKfBW{55NEIrt25}IP`ps=lveHfA^f9_@1@#qv9y| z$$sh~yd1~AkMm31@u2?F_kQzA%s0RL&GC$0eWjxB+0}UE57kS>|8Mm98S~G*&}T~V z-s{=R@D$G&$Ma(6Tf(mqe-L#JsQb!;P2?Zy3$S0~tas7@3U3G9d|Yn>l0JSfdZ`z5 zsE14EQ5U{GsC`SX`xLILe}hhS1E}xPL%#OG<@kB;$AMpG-T$8r%K@Av4*ilG%il!Z zTW%}ld!KY3`to{`4;cJpC-~|9>Om{)>vO*VJ}A5U+{bEqsjp$bvqE`*+Hcj?L;J1c zI(`t}#liffkM7feYn`q5VqZc(C;5GNFOv8e>^AJs4y=3P12`7=qV&M`zJgx;5A&8H zUdG$Sjcon};%~L=XTa;kYwMf#U0nZ5{$1n6sQ6d;F)tkJiQr(^eHl*4 z@O+T=JlZ;|eFF3y3_Bi%AMBqb|9;TlKi3L>J(vD38m9k?#yRVE2>;N}hlS$(UG_=f zfVxlnzqr4GK7f1<`JvYrO60aV`n`|2c#_^ecSlP3szQ_AlUCZ?Rv{`VXjcGtqfm^LSnBZ3RcD$DiQ$X3Q7B zgi9~rmt*`mTAV%^nA&Fn=Kko*=!b>(<2z84(~tMQ z5l_rZ=ge0FZ!xZ49q{iYYWf^#{+ZD4jL(<#TzWf0_o+$@((*HP!`CapJI`B>WRad~h zPk#D(@{{}`{ffqG57(h5;WhgS*SQbVb-`=k-;MKkEA{alzmWAe@~iLf_~4%g_WnV-N_Ux1G?{Bky5`F;uede~39ly|_n zza`G^uqS(;1^NAZ#=-vQ@7;}Ou+X{YAnKm_yt8b(0JZ+!EXG66ZTHu+ z3t@-q40fr`0e@C-^_A!7(>_7})(PV|fAztdJoVukep>(J^K*Qs@0?#g|0Ml$ z`jhnepylQKay-{B$4{cq?a%Ri{z>>r^7Hw5z5k@g^LXa+Pdk26K2Bxzoy*JT=i@o9 z`=#%n!Ft4f-kA@4eahpR=f`RBzv1V7(#!52{>Fd*|MuT$>nZOCC*e8&yx-*ekG%io zUXGu{Kd1ko@m&4~&Hp5Rx%@o8PCGx3&q?!t+T-un{+~7dPwW5O{?po% z&(G`swES}Voc~GvAzv>hvwo4USGm7_%K8^OnZ76aW9F3fx&0^EoBKcaSH2!DW%}gw zIlq(a%j0=cf6w{n@^g8o#dH3roqrO2&i|x-oYQ~M_MW6~KA!7ylK%Pp4;s()$^Da$ z=koLMt66^M<0s9}>2v&j)?;&@`n|*VuLRHPeV^KU`Aa;%uHlP;>p2dtU(@{Mn7@23 z;X1c}XZg&%7!TaPL&bS{!XI{z_x5$;eK)dm{zuzyf;i_xk_Q$e+&ryxz`(KV{ z{9Vhxlj5U#snO^8ZGZ1a|JwKaA&?jT!+Un!_3?h?jb;3Deco35 z9)|pf_}u~hTz?L@3i(`@6I3po(29$>*NECZ-sta$Vck868K8#zo0J$G=4M2 zZ~8sw+WQ=B<%EhUhuFV$*h_&@Zx>K`^&CAr z`lQ#7UF?sYhbb<3-^$~1f&7m5M>F-U-b|0Eiw5;6e&?2dmpUEaQFyC`dWRmssTao; z_qSU9|M;q|u>b6;gPzrH+}{JK)_c?u_}13f_q!gc&TIVg^{f^8r2WuM;njM%7vr&h zqYs@)Wv}X{rr+P#YyYPEg_HIhKkfLgF(1%p(|eGr|Elj7S-v+LQNOAe)X9?O zSNr>HU5s_M-0%9m{aU?v-73d>&M03?yx5NXO?_mtdmQ_ouKo6zPB%U7N4v(G103IP zc)bU7zpCVo1b@pr@jN2-d*wP==>DU!?_B0jpKoBDbv@pCN`K2Q`JF`H^RV0AM&{3) zf6x5s=y89`{#WW-+|F?Qt_1eBe81Ds?{a|;1wJ1BPxwgSi9W?I;U~;*ZiW8Q&%)mF z{v`6P!tIX=pUdVu{?+(wHlEL4i*da_|ITv%AaSfuX&*it^`!bhU8x>0uKolCIl;&K&L76_s{kL+z6!U!6^?x->T?*s>k;gOJmb0_AMQha5pQ_>EB9i&0sU|t z_=NaBz;{C^=YfBx`a^zikoaBf2L*lK{KI=bmju+l;)i>-4^Za-!`KH%9_^-F<~=(@M6Q0k%67zX}!Fh46Fg$_pcu|j^ntY#IRDM76So*{{<`1`jent3Qd|yQV zYL3OY;*WW6zrQ%)eTqURQ$LP{4z-1hy@Ym-m5#AE4^}-QF1Q#eRx>s`IdM@g+a%0e?xK zR;HscCw~?Qu>jXXXlX}j_cn8pU%16yZUbe({!}|XqzR&cZ z^qCK!)(h~l|K{B%AV>2P9dO+r)bz34cccE3eEQ9LE=a%S@DJB1>bD$nR{VXScUJ>n zMck4-g5J-|K3|`s9e*_feF-~|V_bb1`05GcSG)Op{|A2ZJLc>6H^jxagZ)tO4|yN_ zc(=Xkzkk*Cce4O*WcnToXZ~>B8vf_aFyYJH^xj9jyB&Fq^)u*Q*J*wg_mod55A5g4 zKl+Xm{G+;ozqDVh#Yc6;@$i@FDu3#Jqc(0om;C%4wHm+7A4OaGFGRj9G`5JpH)VeS zDvs}tm$>8uSAVMdv-6X<=AUIMzIWboaJ?0M{z}$|w9aCGEB=_5&WRHJ zW$7FHfOij&UyRp**JsWnU%*lFt#F?o(;n$Dg}5OPe>m+rgB;lfT=Rje&fXgc`T?(F zz+ImQ<#osX0Q*PC!@Sf_OZjm``osa`Kq&QXnP@dNO)3vXm9L__0RTsduZUdjEACV908-O~Gg&$UeUVRI6 zW5jWNHIpknpA(01^8dlxwgYgM_5fa2{3auAlh)&h74H2{nI|~!=Db^`2YjfwAP=P1 zRH1VW9vmaT zybnA^|DW~w2Vq~T7x!uSJL=!@;`cll=R3s>#jnJhv)TNi48NY?tqj*b{Sx{`$W#1* zA0?dhh_m_;V5-AM8K0c*V9eKk0rT7^10sL&ey4u?gPYKUcHV62{`d!5$SdNvU5CQm z7gd+Ip7hpwRjc#lmz1~3zmd1ag%k8I=i81#ef0-iAF0nazE4ZPRDD$Tzc-0E@%m)+ zA=Itt`mAR;-skfjtQV%oxacOMt|tAy3j8Mf0Ea{0;40-m ziTrueTWJ3VT=oIp)_knL;y3Q|q-wm-ehj$u0aRaqGGES@Ux3RmKY2QwAJPxFPYyei zUAAYeafN<^UaD_E`4Mm;qn}{h@cf2+^!$eSTRcxNuD*9 z4$wZ_SLS}u{-H7D{#En)-R}ARE+X~T^{4ZADEMoC1v%<(ptrnv`HA_YzKZyz`Z$iC z>!p&H^3U}&?|s*E^chb2cj-Iteua5s&m2$nKJWKiYW!0jC;js}7*O6#N4`z_KJLBC zjt`*6FUR<0yoV8ap?PDaf1I~tu9G}3e$?>%A2o_$&D#lnJB>-@#g6Cwb;EK2l@~Q! z>lp1qeKH=)5x2B|^7lHXa8COCe7P+7Rh{l7KFO}&?{kp!oslV@m(={4^ho{Cd}pIS z&&B-*_Qx4teLw5H+3+v=kN1zrFV@Fg+D~HLvK`63jf~Hb?3f8YGZD9$@aqijH^`59 zBUpIdfZX8Qr*Z1pRp+6{@%qhW^tyk1uac|%cnw!yfF54I=Di;%{kT`=wf(tg|2^rq z9e^#xV-NR%>=)HK*Czgqeq%o_;D4`a_k#C7PxjnjIiK^8kM2**L)s6yq{p~m%GdXH znmS*2Z^!=&f%_is+xGAsd(PX9`MZ%HHv_*J{o^kFCy#V@k>`f1npe@;|92bXG46E( z=hC8!{o<1`$mRU$Vw~516m5lMS8pF2aaGybWZObVR<(~r3a^im+(Kn+X`+47w2JHvHMezwcel%@9&3*F$G#`HV zpK{oT3_H~CtJ~)L)$M}$Ui3Ilgaf4i>p|-W*ycRItDJXqrfsVcmRY z$#wG?#?!d!1^8sdBh6n9JsrHO*A2bnlJmLK_`Gg+kPd%vD zmiyr|>{@@8eLvnXA3*i1k6gDsfU197pQj&wJQZ~O9xZXyw;=Y%lnY$-_T$N7ihlVy z@FjeOd2d{P0;padV%*m+HF{JJjd2~0iH{1{ls%=^?~X+bv#E(=XJ4f z|M5KN=&yP0|BG{wCq0%BPsV}rIlkn6`{U&pAH=yIuP@_$5JG;h5b>|l;7}|&h?-3Jp6jh-?sdohVb$5>qOYWb64ue?@q&>pJ(10s_*4aIq`=Z1`T0Z&sNV9^o3{fPIGpYQ${ zxcDAD)!%fkUyIX&EN<6`SN*zqP4ar(KhLrLD7P2-Lm&8$_bY|28=p)1NdCUw@8EvQ z^8m%~e8n&Lc%0YikA3?Kd{bVJ20!k{i*q~R6ZtFo#Gx18ALaG8p3Y6rZ|(1{kASo_dm69^~)N568$ve_wg0$ z2gv6Y#IN!CC$32@;bdmlBhB;o3X6{~`*{%0GqnCs6@x+V`rOcdtY{peFOvUK`1dH} zE@}Tse<`0np5n3L`~jZ$a9<%litmxmI}6d1?^V6GR5T7E4o4w}IL8-lkD&Q12S3FP zjl%VXdAwenv0PkN{DEIic7^?_JJ|1e8@i7w3iJWxLvPeI<4<}1{~YR{_6onz;wTt_U-Cl z&2{sy*{^(Rt`{?5ukw}tpdJ{OzVyEIARzhi`hg+lS>D5dIA0K-M}a@;>68AOA(!*$H{jAU8d#GNmCp255c|LK2CYIw1d{3N|A|H#j@ljF2A{9_pQ z!#_S>z&z=DYX9hcmg2QO%!eF6#pSi$z^W6u${P zALaGQ@Gt&1M)O{L{!Gm0|HFfi`^{&D%|1(@a3>DvB z)Jjc>J$vd$9ukk%8`f)4pt;SXP zbqo7~=l_34@@vXN<}LlIJ_kRlj)3d_=ox<1I)=EYKYtqiI_4`b#N{vg-F!uNVO8%9 z7Jz*I`66&t_Tijdc1`2GE5_mFf6_pxc8|DySOJ?wYi=oH?M%tgJ; zhd=oK4D%GY=oeyqG4gaF=oVu?bNqO!m$Z)D7r(2)=c=A}7q3-#%I}mPb1A$UGX7T{Ce%iRbTuuO@o(1KnxsD6a!PkELEIUx$OQ z^jCkgoMdOB(|KEMUMJaK*RM97)2pANKc;$)IK$u8=YH@@eRWFy$>TpCe&PRNFb}Hu zjAVG)M<;$s{&D?qJ(GU|eeC1PeL4Cq{l@jgaIqW5`84&#zEAK**l`2>PS*?TU->8N zcZK)KL(%Vskl*%O>OK8Pre`z!2S14ZCj5o|`c2N`{993nw|sy5+D*`V-20Cb*Z;qj>jLa8 z`-9>h>t3xMw9mc}eG1U{b@!VK*Nc>|DW0h=l3#Bs-iJ{?PvZOvxZ?K2_k|bMgWo#h z3wwTP!1QM?_;x431^9vfor>!h(s{j~ufy*? zzvue~rVII~^KSfMhTDJCmv&4Q4>SAj3SV{KIlJ1NAbxJtegRZ}`s@bgBQF8_G#>jW zh*tbB?SUQ$p!5VB34AzUn|jsg^6^Bc`uc1q!O1xNsj96TyHsFd3vr_-%%>gowwfs7pd=O z*TSwf#H}0GKI*fm$W(Kj$YEq*UQzgvEv>#;63^im$d z{#^cem;C?l&0mpyeBY3M?)UlKXIFh645)oZEl*OOs!t(b*O=esdDboWpSN1Bqo?0T zDCQ!cbl(BK?$cEs4#=M!=R5e1mHZ(`d@WCX;H{R=*O?z(aY%KVu3tMgf3%N5K50Fy z*{SCly3TyNhPpO%zVUz5gyVaxuA5Xx9DiQjJ(8Zwkw?ou->B+E=T*)5$#jt`*xmfj;8JhI<`i=H_7om=#MF5oQr4q{(V z`vIo{Ziz0{>oq)Y#rPLu9S59KA6x~0#P8CozK2kJ?jZQ{+!6G|m52BpJL!3DLwcR} zTMv&f;yq2#7s?Nv|C_EYJtiXF6W$M=n<)9JAJ^!ILXPyp@8t9O{j>vm$zDO~G2!QG zfUcVfuh-|eu95zJ$Xm95Klc#0uUqej-ml@h^8egY@L9t9o20{a(LeOQhH_-ry!$V3 z_uF_bcy8Y77_N_%@-0vGR?~Al{FD4VB>JP!^8kD}@2K>z_Cc4{AeV8&d!Cxl{!2fs z>-V-rX*c=sI^#?j&*Q;Q>jtjt`NySy3jXBxPkmpcJOIvpt>X%R7#|IPw89_5p}+0t z_h=~x{tLSur-!9K^_~mju_n4IKdr|q{75};PO15bbMR08@%)2Ab@vQAtw+=|>{MTa zo!TG6Kb4;#=i(u7L+uAY_Xv8@-`Iyr?qbL%KiakE_eLPsd71jyHSNdw{srd~UJ3op zXD#FslK%nrvw*dFoR+-VkUJOtnhm*gexK=;$V))=`cUqq-rpw7 zzv}1n|`kZP<;VV&x31y?A{6I^b?E| z<f9i(ek_{9hjOeBvAjeqQsiKPCK?dojP^`M@`Moqixa;MF z4|K|J#C0;GQ~vetJw>;X(H+Nc_2uY#I^FYn9tLhdRCXl22bH&R9d<0Aj^6t#_~FCi zKaunaJr@&B2z_05RUCHH_>0k>c_2P`o}&7B(0mfO)`i{&c<$0&FWUDYzf%3=_0){%<(d0u?^gIR-m@Yuy>@(G`74L64?wM_zY_HVI4`}&Vm_a5(m%ij{T+ki zD^ZVtn*Wva<_D>REO=X~^I?T3cnHa(!u zK`!z90rP>6M1G_`HAH{^+%)`PsJ;Tc6ZrjXd@jSMGW@jTvxWXMl`nGU~PBcEa9 zebDPZia6todFlM=Oyq_9d1>zz^c-4LcF8zkDHg--JznSTO z;{^Jx4|#l{Tc6^v8~1x?_#KDq=!(AERMc?gE&NpZ^|}7>edI@Y?!`E)`T6khjqo3y zr^sI5g15@+_U|p{!zt+V`{iEQhyB0c4)!nB!}{;w{=|9cK8Cz!ejqQEUpS9d-PiQj z{-b991?e6Ac6h$IM}C*q&F9kkS@)H8p9Q(I4rz>$p!f?)=&;$K3}u@tzd* z-S&0B#qRtp?dnT*%Dx@%U!nIB>2W_PdjM~Uu8N=KR{A%N8CQQ9p6@*$a#B3)-%7tJ z$=~)mG`tb@vQgaOJoGu+KaKDI(f$|DgVV-U`*HNC)F-U>4&p$6u0_44yxfVrFZK04 z6LHo$0Vw%cx0D}%j{A+UFW%4M@1+#iTb5Jx_hKjfs&yXx4jGr%SG_(G${);A`i0jG z+ssFdtIq+dZ@s=+Jka=B;k-_ERCYNZ4xk_HJ!ra*UJtvkjyr@2YnuQ9@l9n-?xK4iQfhByA${w z=i9H30f&D9bw6`{IsAZiiF|);FYH+cG;V*yb13*z_miL-qkQ0fvU|e%$WNH}w?;i5 zaGrc?_!a3j<9*@z8Sj59SPW}?HR7}!@xXbP{IpywXgvA=ei!ygdf&BOzjoL8QtMYk zNk74Lzp{_NH7Zu$GbB+vfmIw-k*4;}gCx|k`pBL4#S^Sau6_2pmNjXVdGe%rxs+wW~) z{4mQyt&`7qej_=x=QrvnZ}EL-_M1}gVze8NdMxc|&S-o!>b0s1?K@G&wyWYdF8amD z%SH4{*Q@SlP`7Q`^|!cB2`GBr|4=V~m#_F}!+r&9^E%)o(d~x6cZ=z6yhryTBOUVv z)PDbsE#Qz3sQUdV$H}jQbphAWCp8YJ{Y-6qko-S7>3;ChY4-!*T33O$Xve>ceoi|8 zwJu)bIRp4!+AA6y|M%E;4OL&jl^=kc;=dhyx9#^<_ythw(%+8038=pLQN{uM{&sKp z2mOltfv*IVA39^c4meNx8m@SMbSeC>gmLKm)%)hPrV-p8-GB z!AF;x9^VLj!|x4L7Q9bFyQ~_azt~%JBStxi9!}pXlcv=1=>Gr}uQO(hm6X zf#?Tae;?zzPWnOD3-E#GkK_4em)+>Uz%Tv2<8$fvn;I|AfhN2^1wI+_)ej&~`2@Jw z%@6w;>Vy1{USr5x@UQk+IPbQ;X}90mz+n>zVG|kVbJfn?txp6J@@I4?Zx=L;KRO3eedDE0e-3S8T$@g|9Ir- z$EW;zxjy<8*8#PT{)g-f@GIaL{rh^uI3e-B1ARCSI7EDO(c^%VybicXIW@i;nztYG z_T6XyPKB$EN8`O(K&^|=Q+@%wO8p3v{^H+of53J1wLWZhgUW1_A!!Q+sCM0-fH3ett$`k9Ex6lVI#3+OLf{Yri@t9g9Cf%4X( zzAt$G>lZBV^~h(ydHV15xK9L>KCcf4{jmFM-*w>3C%o58`me4#4uI;1eap}b{!#xX z-LlU)fDgvJmh`?JdS7>4fM0%nFrzy!{pern$@6o=HD3RFtJV`xcGU23@mX@c_AU86 zAwb))6mm=4-?_QSzUMkxE$y*g?M$!r;Gg($pZT2U@5NCzKk4Vbu;lMKK)=9CzZ-}D zL!Rmtbc)04ac%{u_3Bcb2LPsgO>mg@;JWuYyuUC$^?beY9*OC+?qdG9=QVH3Ka30g z6Zwj`t3LtG$GG<~pp*Vr11`Jo^j&p-2mLLMe`3h-0hE1zZ^Lm1R6gT++DB=hiTrvH za%2zmkiNC^pE-_Stm5kVmELzVI|f1z`5p3Z2xt9KzbV|u2qwI_CVUO?K|X$sb;EJu zgpr@PZhotN&NGhdi4Wnc{Jj9?255VBa=WeSG}@a)E#S z!0)L|+%r8P>3{Qyalor5#9PmCK&R&}gq#mPI&Ye|2m9eq+;=GMfY;=|3CL$2OfTy`ir#L%*^u6X{eBFG<*PE-HSFX`KE7-IVxU z#(d^i@^kdnPh3ac`TB=7uDnNAM)lHA*H1OLr_VLOeo(mhl zLAtLEps%q`s(fryUtBNqJM6K(?02^3aa>Pvb^cU#RrZtrb?nQ#Dt z()~{C8@s;W!0Wfd4(^+Sj`rX?-@2b0-;Mm(b-x~ub8kS`;a%MKQIE?h@1M~}Xdm9g zl%3ane**f*2k%d6@k{(tJhi|6TCep0)c(8jZ*x=fabK-`ID*~Or}F=9;K|=Q|NYuf zF`>8&>3;*Lf5?*UZ5_{M<80X6=OzQFqe?+qO9FVUV) zh8}>*w{NUC9)Qy0)rZgzasc(c2f%BTdl7!%b-)?&!FPD%UqJQ2Z^V7-H)e5v2)b`9 z2j6A<-xl#{`=K^Ze#iAG`sLNF;V?b|y#l_Klg~J5$^i-+9>Ct&iT%|HgcC zK3z}pEywmrALr3f|4xRF5wGnBw9eGxZardFLbNyF${k}-$ zKR@TG;fG=O3+nIZm{s269H=Yb3)_QwR9sLG(uaO#T;}mVPN4r}BgRpO90#82PjS*X z`98KiHG1=V>~&Ig&@Eqmq}CrMLO=Uu%g=Kz;{H(mW6k+?al>^8==~t}Ez*;HhWfG3 zB99x>p%3%T_;}DsFVJaS27FO`hJJ!v>$8FTE#mh>pZoA{(fFnRZ36xxoW%9O1t)!f z{K4>aQuY%cS2s`Z#^jA@)l*^Uf8{d`-bO#7fAat?U&#;K;7@x?kl_x zQnC|2ZF^hwxG!o&zD? zmpYC=-~{9KFV8#P|FRE!0vGJ_dE+OKFiyFLA?GOKkMmAl$NH?ieT;t5&A;;j^IuN< z#W&V_*kQfg!T3#+_G#$3W)^`yAu^xR{fjkA0pCef0bfd71d6^TCwY8DBi7a=wHg{JswS zl>G8`@V8$d`~IOezR~Sx-%mmh$LX=3qk}%;=KW-UZ^X5?(EUhlT>Z7?r^9akzTc?i zM_fvMd#=i_(KqovnDY8)6n;j%9E~~uh_h~hTiy`HnQz0^vmfW2^l$$#-hc9S*^lvk z;iPB%P!7j|bKSVKf!|pW&STth^!52N9*SSde+d3CT^29zQy@?0tALlSPqWlFhl;+{PaA#hR;)8gZW@ME_}lN!u?ZvpKws)6Nn4x`251KC40~(Gd{z?&+~h+e>4BF zfLa$IH(ei>KRfVq=n?pn;BWtR+}Ha@!=Iy(&z-l~o#1W@`Nh5lTx$oDax z;~Q$qM z)cde#2tPhw=DX+nych9mY5slOe?wkvpJl%Hoj15Hy|6!iB# z^A{0+%Do!#S`7NDxZmM*jBEZP_Ls!RV|?82Ki2rWE)Q@nM1LLN+{pH%JXjFE6#SO( zyJXTmiMTw8b*IXk;Y^u)3ATEOrZ?uR{ZE#TWpj_hFm*xr;+y~>wk`TATF{&OE1!~LrH>pWr-$sxXv{M|;> z%jJ-l;+fkq8-CC9EJi|)E63QE`b`>7c0bDGTWVmmLTGJ$3(b>>rlBe*8l00|2#dgOBPJ@Gj@q@NL>x zqrZ^h-WM%LT$l0uly=5*Wx=v5j9)$~>0)vxre>HQM&)PE~^ILBo?)+4X!2jd|>{D$=k`-RW`qdU;kP}33bOV3R|R|T&74Z@&v z|J?NRo!WT+DaX}!e(OTy2lE-+$eA3nr+i}G(JUwI>W(Bt<; zNEy!hEY`I@SPCkE1^o!OH_FTyLnZDxh_-8ogsm}nak4{$ONuK-A zB;Mbm{?&NSPwN)+O?safedW(p)#(k`%euc&>g#@l_iZx%&gZG3{{iHM9Q9M+_G1+X=QYlwL!SL! z=~2pOyvR4x`#3$95`E~G&r5u7(0|Ai`R%o>VnzPsx=24Rx&MFqPT+T*ii72xDeK2_ z(tXHO__Km3Uo3wr{QUH|{fa!4Uzl&SpK|Jc<@vjD`*jL>IxkbduwScuPI42}x<|h9 zYo+&bdcLT=v#JMvSCw_KUFe*Kew2UXd#k{m-?1-4Uvd3!`};nqr_>Kz|5!(4AJz-S zwZb1o98xsPzZ*w0+gg!gFQ{^|SR z&%C+s{;7Qj@LycFT<%vn{)_8A4*;F^6@coO*l)@{z$wbnJVWuX;pz{-6F>F43dj24 zc`@$$pW1jUYjcxK;; z_J|+&JD=_ss*~UDhdrzZ#vOG6-1##db&2;%xSs#-PjKB(b@n07^M1LKU&*6<=DYRO zz6yG2U#06wzhsZ0>wG%;$aJh*6@SQ2Qy`5&lkgCBN@{Kyg@Pyiq@@ z(+gt}|8K#szOMYhb?3piT=$^&zGwi?ap_moJ@fHG*e&;k-lq057Po4*`dteru>0&*+CUd?mwk{?Z?E#2+x9e4(*=x2UAh&SW4xelH<9K+Uh8laLS3TS8B- zqu;VT&}rSrdCDN=fG*efX2{=(@r}T*6Cy6RrRU4n&t!dngZ*OiPqHt?C*iBIW83?3 z>>qTFQ;V15Q`Mu^soMC2=Cd!^fA;TU>Cd+8`Ig_ifqe^s+wb4P?*!63L*-|69~S-i zTdqgU-%wxV{U!aC`h7YFUW|1q?jtbnec03Zp!l$lvA>QXuMHDE6a3x(7ZFE~dwm)6 z?_Yjz9G(Y4e{gPf@&nmr=p1Lm}U-_}J<2XHM zO8r;|n_ky2U+?7-(!bD0IN){p`(%IV{QJUf_>p{XM?a`=>$z(Ce|s+AHu@X=f31oK z{lNY}KL8HPF3c00!*$Yc2Y$`{4{^==z5IjuX}!76~Rb;vVm3P99I^xGv*82Id!@UrEkd%*Xe>0yo{7*JseBc?X$Z zYZ<*7PdGH-aD{MO({;Gz%zhdkHKn(qs7&g?qdEczvHGyKoG;k*i0-+d!+K-rJH zlHSZO(NBc@3BNCgzHGY|{hmUN{sQysbLjWfOV^ESo&4MY>?A$}euUf?8&7h~H}1bN z-|OYJ^X!dV#sSBvS8d-mEIp?~Pu#C)JpA_NuI&IcKm5+D+q;KT8{K@)JoCm#62EEp;H-Jaoq;*6%=pBzmzkdgN z^}ZMAlU;4j1D_$vQ{0Ni=NjQ3)w}Vu-lVu`-TaUCY+rirl5jka;CsD%9+RF=XrK2c z_btd1K*i&Y$WOquE;;YE@jf5s*PbgaXZSt&FZvDUPpCgEfj|AXw^Zg)P$`NVek0g(Cm zpB{DhtAroduco=5$2`D|jDOybmcqVN&t5-XxW0SW<>yS_=}x!42|rHX|M$~uE9g=maa|4Id(X_{yQTvaKfn=Q$2`R! z&~;JnS9%_YA6)M{#fJP-)vunDz)vyme6R7-eJsYkzU}z=U**?kr<KHYhFJ#d|Of{)g(%KvBX%U-UB zw7cCS|JCffpmClraeSoEb1v{n_UvlB6Z1RR4|mzybD8*;SYL+X_oa=%H~jkt@JsPe z`X_v(o1cFVU-=`}m#Pjs8NJRkYx<_~pC`-f8tcj(^fG*XJbuiC?le;fY_4=lt{WNnfwerT)!F zk`wP6eQwv!Jpt{&vhO80``*s=f`4v zEXHG9_52*QHyl;9y z+4aS}7~hNWkoU!q_r>iPkGlNgM&KJkzZ7`*LI0Boy#OsQ^5KhPwhQ|DdT-3@4SXo@ zA=}kzTTejkhi7Ov?b^3p_`g8K8&LedG-!H2+4ZG?7$1o7koTpK_oYURH$2`Vf9MS; zeyyFr!w;>^z&B%jHSks21wB1)Dy~ljz8Lr-`U&`>kCp4S>VMnk0JtC64&3#-jqk;? zp0_dI@Fe`2=&SzJ7S4Sr=l9|FVt}>xQ`Nt4{kZYfO#U$S9~^SO8XR+<03u?cLH}FV_x^T_u&=)WAS``!sXAJ ze^27C_2fgOSA1T;9>p7Ur=`EZcvt5rP5Jwmncs*LV1C~-Nq_%x+?N5~Q@&L7vnYPM zu0Pxl-^#`%FTLOKjPsuRqQ`xH)UWzjI;TkI!U;d_+~U7r|3Ll$zMbXYBF}AbUC#vo z=d!XQY_732j z+VSvnlDEN+`c8yBIB(E6_-g)MoO4#V_AhT#cqgOJ#~uGFUio-Fe~tS5{TYa9Dw`gObXcjA}kYaYgv9`+y4+a0G)(mVLOo+>*gQhbBn zbwIzdUZ7{1ANH<oG6sJDT`)(N%f3r16=GPofu}nbL2G zuk>NQqThV(@ouFzo@)^wVm|zXKFl+mP<}-J(0+sYzh}O{ou_@ChwHNE1E9MqzxAP? zF`j)pi^1I}C+;OGe#;0R`(l0^T_W|m8?FId#{nVFd;UDDLm**U3;HnGY+7JCQ z#uYcd{|!6vdoAS^p!+lPl=h)+WM74=Kc>D8s5+?q-bnTXpVzzr^97y#Tf$;Z`BUl9 z&g8tTT=Rk4l0WUB9;~}r*h9Z8c>Mw#%Jh_fzC0iP3;l%KAL0XlruT%jUwVeWO*iNL z+9~*rG0t;X7fQX1JKqMP4hCRH_jjDMetd}YTqgsO_tpEOUcYd?)Ytx2Ucv9KhYJ3r z^Gp}ORAT*a?1>J|M>bknwr z=Sv#jiMn_Ya53m{o%Qu)To+u#eJ$i*U#5B3_j%ny-=?2{J3p#@hxu22Q~rOIbxeK7 ztOs!S7rs}G>%i5o7!T%{!{f3{km zpOXGn-Rm7TJS`AKYxFVTx-S5p^wqi6%ixm3`f=P;KZv{d0?Mw7bIv2cN%^VrLvHuD z@-Mz`2mi^xF9Ls>_SWJm`=M`&oBK&$#Jw-p0r(~C%_gq7w^XL6I^_`9PEGs<4&wk~5)X?*-0ni7O zy^G;beh10d?RVx&oi6Qvc%AZqb3Wc{77nO$Iq>)W$w4{a_elrg&$J$~4w5{;Ij&d6 zO~3sVz4}hce>J0DeTu%D|8zz_UD7)~@%y(^@b^CXO5`c^r(b{zhW@p_<+_XYvG%*L zblv`(bUaa)#;42=-!XB0RsPfYzVvlGD*q8bK2BPHYW-)5an$?%h7*cwwVn+qPpb1} z<&oZ7N4x;jdZv8mdL{bV%r8}5I$yc3#{Az#90hT{BnUqWUcptGehz>4 zXZ&=(%-?IhAit}b-7CT;F&}j|bOv!Sbi7jkjQwniPwJ;HKtFU!{`T|n`Y2APkl#(; z8}{{<`Dgnp`_xZhr|qBedERsIVdVkzReU0!-<$~?=e!!P*1<9112O*exln&LKAO#+ z6JF(=^J3QbALx%MPkz?&?z;3Ii#%(D{XCaozBTZFCaBBW{!sUA)qT{to0?zw&GlN< zh30=9{f=<}+{)-52(RMgei+}EO82p4+!EgztkdA9^H4+UHDUdsuk@(&^!G}KU|-DB z{RnX1cMbS|g-SUy<$LMUC+S_YLw>8_l3(k$u9qe2gMO=Z`mF)@!Rt(!f9wx)2cR?b zx{ZAg^#uRH@SFIfPs)#gq5~8i`yS~47dfxiPg}D0e4K-)b+e+E9?z-UG0v~?cYM!B zo}Bmj1jcoKO#eD>!tM#YkInf@q2E%u&TE~8KO7J21LTM8@WXbwFPO>fvEM5B%7f?V zXZxl@pJ~76272!s@PF3fAJ^yk_&xIR8{u%l~FU0}!WGCP@^{>T!N_KDA9_nAW5A%maU)_Itd^Y^>|FZYK!F6rd zeb_x9o`=cc13Kjg%$Oh0>7B{74f{bJ$i_&pg!hCeK@y}uGTt*HQ6Zj%=Rh`WK`JFd zHZ37BEWr}ofgC7-S}4W1R06xSgVs!oT1*Tll!LZxjW%ox*^GrWXpCBn4cSbLX*rqx z{;Yk^zJ~-rJiaH((|%gB&R%={Uu*BP&pG#QdS0`Se(UyA)l1fwroyh2Z`JXRiXT*8 z@L!7i7{`I9c*-wXUvr+|oRM*Q*NF4?5q(qq(BF>Rhx>eg0dm=g7Gm8h`;g`%kE`|A zVBMmQt9{M)CF8+wyveUW=u_RNeWT-z{vU&r^eI>({sX?-*GHRV0~hxq>a z%Qw~48TxNEem%G^=J%EQ9+mIq$Pa_gzjqDdJlNw6T}K_N52Sh|WIxzMUqv4L2>O4b zV;%u_JX1g4Y@S8bCALr|Ed?!C^KcUa*e(y2tUm#!5yC2Nj9zM6C z96i6n`N~JSfg8&28NKvk|0I3t+kkprp4q4UFTZ=DJiO0!MfAW0xBdH^wQ-C?&IfMz zI&sPcuDYm=7pk{2=%;ny5&GSr9<{EJQ+~JUz7*eSX5-!Rf4R;(&Z#fV;CD{JuX&#t z&rPy*eKy9Of4FZIp6-vYc)iN}s=CPJtB!J=k}g9#<2;kawIv*5t=s@S)%D!cM|n z_|3T98_V!{@|{Axi|@AW2W~%aho4Jm{ZGg3=j~X}w%xD9*YcdVl*c%1hhG5$w_i&+ z_AmG)ed*Us&*Su(40`o1&?o#fdne=%+}}!{uCSxa`|ls=g`UipUate$zC(RAd#+O5 zm;C?Zi1+`8{BuWnG!uC~<8_StR$g!JPh(vN?)u$}bA`RguTj?p#$6X(!LQ5rX(#Zz z5_#-8i~Rj-IM-qR0`_q{d%i6GInMkC{?PTxJk`l#(5de}f^JB9mVUY*{y2}4{Pcdn zc;Msizu@D24EnO_bj#;m*?RZ5eDr+&5qfDp-~ad)<*&Y%^v?9ue(#{#P?>#QAFH5hJ_&#AgQ=T-!Pc8MB<#lgG zPBl(de3wd7|eze@M& z^!Z91W$}Kjdd}#p{;cBTivKBnFyr^6@*it{=I_2roK(M=KGir^$4`@2jpteCD?h9H zoPZBLj=wYetMSr({X^?{C10}n&3ZYe-=39URi7%}FZ;@MtU7)gK6*y~sPDWCe<&{= z<)=sK|7SoaKW1`I^V=iGv-#D$_I~JM?1wHkpVNC^9Ot*y^)1WmSG%sN`kp55N$DTy zzcbkDeRtVMRacq+AL=uy9s`au?@pkv=IfK<)xI!5`8fa9eiyJW^ie)QuX;s4jXzX( zC)}TK{;B>f=U44Z`c|)3vKr2CtP@-yl?-CtDq^T*L^Uy;SH z+D}gF7uESmzm#7|pQ_)gcvWsSPpkS@=U3&{EB;9Or2CtuUO`voU*$JYiG%voyLRF{ zXeYjJ+i_pR^&y>q&f~rj=fB_43p$>g&f`4OxSlgkt;Y3LzjrXj=NzQF;pa+I%oE~U ze!i38d{4KSS`Yelf8X>7ex84Je%5a(^Z0J`CwGYdK|c}V?3@KUd3~{@S|WK#)$)po_>n)@Yj`~PuJ@)&v+mF?fKT1 zIQ8JVWH?umqxVNBFXYaNK7Mx*@zQf<#HTghnO+rndSCMl^wS~F{hcHRi8kA3(p~h`5*edYV5=`#Asc{Xf*SoAY3Q_#^SV6#CiUt4)6=KkbCS zoqtQ=zh0b=lHZbl9|it0zuqFn$Mbu^k92ck?;PGcBYwxvcL6Vo|3vVez;hbLXEJbn zKZ56?`S+82E}DNoITHN%{FL8Q+KxM@Pvg#qsi@bfw%g)A9sH;9ogL+IpDF0y75ol< z*GZhuCk<`imGIwG=*ha~c;e6h0ib<5wH5gX{W9Q!_`p8VPZXn~y9a*U$L)sR_b_f; zeH+&=#kkf9;O-xLLBAKzt2mEx@5T8v`I*o8nBU~vl-DZ9b-fpS@O&WjOMKmj%klBY z)PI8VaLy?CfU*N{EUwFbyzi98wH`g&RcM_Ao$%W5xmEdZCH%dD|3#930Zh2=HJYxgVjlRovZ&+AA@pT_OyRph1h zaXqZm+y_d%M=Ik}t@=kFDC4t;0{ypC|)LHysfSL=)QwH{D*|JV%j1$hQIK|hpu+Ya#B#Tn&q?V|Iz#{bt6-9cR6 z2sp2GQ}o~`dO^^O{{j9#f!9GVIPc%5`OYiu`+)t-3qbW}z@b*WxY(+n*U=9wcwYjj z{{5pnq2IXW0@r&xKQ`WWUGvj@&u-{7Z+YJVT;qe`kHK>btbcsJtvY&kx6t|te)1Qf z`giHCb5qLe-NdhOAHsc9TzCKBIQt2&gFpKY;Y)Sc5jY`mt;3TKKo1=72KD>EHR}yH zk@yPUjk=^=tZU$}@_34uD5H7!3pJc!D|E~K@N$>Xthq&K@y@2&reh=kG zPi1_rVsBNR`%Q|^UGEPv`4xVJ;)(HOU$Qsl=Yxv;%6$L6_X+l^FCH)dRD0cTn75Di z?`iyRG4E>U?eE(8qDT3)5c#!W|09p=w}=<*QGV^#j*`EPw>9%ze9(u3F6FoC=%qU1 z4!K%yT?e`jT<3vVzU#aS_2>QNF7ESbXKAm#vjOh?++EOlJmKoUA6N@}*04TM-r7TS z-k(8^^j;78^{~6-pX`zT@4o830Js)8X7Z zp67etJc|3zN84$?mvyfAW_%Va^HQ8ry(RwyUaX3KKjOKM?+++<-|t;!an^ZO_P)Ub z*|iyd+VuI-yYG8HmGTL=`Wf)MO}*@gre5J6(a~S_Pup7LrTm7xoNvyjekmWzxIA3f z^!pw7*U)j;fj-1{@IOV4i`E6iXC?Tj__>bT{T@-;7k+1Zvrp@sPS;@%@_Cs0W_i0v z{0GOJAAnltzw^B3|6u&?6maD|@Y~70$ls-e3*L=9!hK_mr+8X_ybl)Fm6t!rb3DqQ zM;-xYd6@iczbtrt&GP4J$n}0F)p?n3-Jwt9ZBeV}newZer}Aeer_2Y(vzZUFqpag( z=S)S;Y4o=eefXt*3QTpB^fvw7sD~2z{weuoH}dA;`bmB|jo(DlQ}Fb>c0W6cIyq{_ zJ@t{4AI^jQ$b)^KN2tD>4_-IQ{=-lL_we;0k1`yApAu+K{8LixV7^t<-8nLWLgeQny`Hsbn|!o=@5cpCpf`geNY z_5scdkA39y4(?A7m#n{PA3J?B=x=%-h;jGRc;7Rl9}vIwpkMcWcF!De)ED5z;GgCz zKX87d{tBpl^)$}M=zl=1<2?)L=bRt=W8&gB@B9Ad^?7{H!FfL#^MABC-+bb^LX+Pd zvLcLP& zF8G}g^+@;=gP=1mfBeLt`)&{RHR5+I_^gFJ*V^lp|NME^8{kFJ9ZT=;LB_gmFQDrD zc>a(Zbs6=geggW2>{)X@{W$qi?{43xWw`crPase00pxXuye|2x?tJ+1ddMqr#RGm& zeB}@7)f0a1t@TUKp4x<9ulENScRYIheCx;g{D|{<{d@-Uoj>uvk%*h^<@cD3>ou<* zfVyv>KSSTaljDK^HRAmB;J=RkNc?)tzmEMS_2BalL#;RDuj}8}l%st4@$U=$OZ|4! z{Ghj8iQo6ZKIf0~u$-rT1^m~;x&Z8yUK^q3hM)6geo$Ryes@3FfFEhkhTpF!#|!nb zpO^tZ*hNVFJ~(H)KIn4;;u` zaQCHhU6>Ai#Rq!T8K3W-a(n>gcS8C#^Rwa)+}CHr|FfYVzq2#F^_cN@^2v{YS3<7* z3Vca&!H;qN?)bh8*Hisz{T>^~IQ#*q>to}^kmfPIoELGQ>9jv)I-MWD6@S2+Avf_= zoqhK*{K@$%A)o7tan%W~dwe;@fopuZHec~ywT~;G%lyE7lJg_Qe<0)@$6tNn1o`5J z^W!@G0;u!25AJy#<~-%~UQ_>MSJGel!OtoF)2biLk9qQYwvP5z;--3mT=^4F>(&S3 zIThfr_{BaExXur=cx*m09>@FpG=EHy--j1rw_!c(N%1|+AE`c*KN3IHBlw<`-&oRz z5PnR03D!n^J~`8myh6PIs(;O_BVNQ2H(h667k*Ct^Lw~XAwL1NE&wW?-&4oFfY-ZY zyxZ$QIga~A=z(}kKA`NG847v$&Q@}ULLU1S<(A_;mGLo+m-_bzf1S@QW?qMSvt72M z-@j8ZHsgIM-~;M&nf=#L`37A1L|7Sbkl%-}4pR?6+uLw{e@{>Nq2W3OJ|g;SL4VEf zMSN%pbezA0=ac4Ryr*qa{%e4qek}Q_FM;2j;uw7i=L};(uXuf^)8|B-C;pvnD;4=` z!lNI7UVRdJ*q){4zEt+3?`z+KIHi6yMESU`eL0}=YmEID*8$CM)A{tFP3O~kM&KLg4#W@ok^hzLLhA|iw*NM<9y1Q83&o?qDengDh1|6mxBodW(SzTIHjCcM zeD#6v;W-QG#&8}Ax}6x`vEMVhQ$JTcvg`HGb2a>=^TN!& zsKe^Mp;$kKekR6KUfeyI9(LMaL0`!3|7gs4@!{B~5@#Gf+}pNDdyx;$lWz14`7`X_ zY}3B1^iOSH;}ORXZR5O_^pR(PBT2vT$E5hT2mf}zpYuHvz!{eb>?;Vb2Yx;9qlm|m z_Z`SD=?Azk`lFyf^7B~aoyTui?RQ;#xDocEU&MUJ`>1`G`h8?0=5L_y5?={?1^1gN z&kU~w?!4-6Q(d4QRi}Xbf1m&0+7WO;JeRjWb-s)|-H!Pwe{T|hy2JJYs!yRFbsq>g zNq=HJQal0Gr%|W2cMAGNT@qqlPW@i=)1$?8`JeuV9v=lH{;>b*CI)A#$b@_Bx+qm@E zvb>MsdwAlr2j>6rDPIRJ{Xag{HYkj8{I}bo_d<*@YdSidkqJQicppWf5e!Nb3V~3UpsQuRo{G12$ zKlS1NkVibE-&U+wTo1^%gguh8AJ?0(*#~7W`l|B;{DW`3k|&Bw=AZSjJLxf>{1N`t zdi62r(c&NSr~bG7-q_RhH|^m5CD~)RXgvz`yV*;&dv?e3fh&H%70=lr*V*i--%|oE zIkTfh+J`4QbzhR@i}pDg{&Mr@R>dFIyFK)15B<&ue%^f&t5)Vi_*3^w znVvn#UlAwgd-CrV{L-4w{gKxPypDK>KTzL~IN#0m(mru)3wkoIkoR%Uq`vv_ZO7#& zxt=4gz!ewZTh#00=nH~3-S>cNzw_~%HSJG0-}7*eX1yc_`nf)M4o$fa9OsX7-6CJE zTZF$F_^&$u)AKGs#}U7;DF2OyKgU4N{K7tk^0NCc$MVgt-xN}HOlcZuCs>AG2i-tk8rHd(r?1|dmmp9KI^`}&h%BEt?q{~uJ!ff z*YMsG{d27t5#OnZ)3wkSILCkT8vI5(rot~%e*OjAc{vqzS>m$qCrQuwQ+|){>FJ=K z_InW7{QHa(=xpDV`={nhZd*^k9COn zPr-kJtTW^C?@w*Dxz4Gd`E&)pg!f=x?Hu|e<`G_N&0GAgW$-s&?}wmInqLRM+EdHI z36-a1p5Z>aRe#DS9sNf*L3tTH`O#kIG0x3e$HU*T{{AHY_qt`i?z>OW2lOra6pa5# z^fxQ~4=VG|pEBQdTKeni$@GQed8l|4KXq8UNB>_PcD-C3_Wtzhn&S^B`k&snUp~Qp z0(#(z|0hP#7ZKNIXLKF#+xBTswhrsQ=yLRDz`oY;Lh;P#ue6T4ZJ~>3$GUc@Ef~^8=pv4M*SA zyiy;pDj;HZ>JUa^R7?aiF~_*a~J6OiOs+_!H@N}0sk0kKLCA_UcI7U z4f<8&yW<=FDP}Z(Gv8Cl37{(9_$Y|F-K3c`bhe%I~#N z-zPp_^Y!OzKHvG|c|XSo|8E=s0bMet?iy`>wk5t9@5ZdJ$*b ziF^Omlox((#u?8qq*vr=&F?jQvf*=BK=Lm>*%Nq=zpH^>DXz}@CjYkGWMBBf>l?=v z?_#4>Ua>>EzcDU-0o`Z*`j+v(z7=>QaQ+_#`P?@SxR>N9Z*dmp(}k)Mc;h(tW-*T!Jr@|9f~-t~mz^~(IjFZ#|y=O$@9*_rf7^w)!5!mIQHG4B3B zKRG{4k9Ae~bwBd!e)Ik)jVC`R{=*gh)z?b@;Q65a=k{O7*fpoOJSk+#dpWe}R6lu)NujpU&0Q zS9vBbaW561m?fBKw_=Kx$^9{9c(^i}_k(SN;ICj`0f8P~ktc0ccj zU)3iOSFJ;U!xjHJPfLCJG~OTd{a!cc``z~e<dUP8b*epY1k3z_Ze;Pn^L2vG~p7y3H=kRlel!r{ zY5utKEcW|27we00=hZUayJg<8o(!!Q`$^!f^u<--EAG3!D|oMl`mFez0l4}xp!2-M zlinU*_WRPXt4H&fi$Us^(?wSVg|?)}Vp?1y=s?>`uJRP>i0vOJxu z@E-`=`<3%JhiaAoO8av<^woMLKUnYNkF;-ox$&9{`%}J*N#50{hpVxVVLnstT;R-G z;`k0Q?C*&>uKHsk@h9J(jXDFg{&TqhwExCa9|*X_>sg(qeOl^2*1OD$l)tVk)`|U; z{L&>oj?d%6&@=QxKgInc-`^CZ9OJsL#Cz0yURdja9|X}?HGV~Ys`c>qsa+4ruREZl z|Ew4N$njWTS_B^7F7s>x;Z#hnY$?xhve-8Racf?!!7sTEE zDg81j|IJ4p&KGOpk4up+L#_GJb}hzJovv2!75RVF&wXCA+VpQ)pBlmUX2em?8L~K3 z>2ApX>*4=(JSS$nH$vZyw!V;``ry6R{P1_GpPDf}p!Co4pZWlIy^Mm6bu)^*@pb8o z@x4|*z>lV@(rdj2z4QS*j&3&iB{|2@P5c?rT?+eDAF#vw-|!Fc9{HJZV7+aqUW-rR z`9Avt;7p9W{x*DG(sKVt|6v`Bd~jZZPm^C32h4Nb-H35O@sD%O*KGPdobvwHzu)kh zn?aBFpqMwGj(+>;xneT>s`$a4R3`~f@kw}x=m(u2&kuTE0H}2m^Anx=HSoIg$NRn4 zY#|@$r!C*tV_bFvPD!s}%%^_Cc;B9M+oAV%?0fD69{&@c`LPlCIv(+|Kl%T#t@(MC zaz3>l@mY6X9Nv{fzumUMIg*{V%_OZ$tTXy1qJ3eHHCX z_By{$)u(7w{Bfa@Z;s2hI1aQhciVYQOiXUygkGW$*t!h4%ua-*vyQ{iz#~ zA2-4t)N6}9w1f7fI!^XL@5iy{Q?Og}cX59~J~sou8S=J{k%#M1kEws1E|31YAM@^q zzsmli{oSWG!@q#kXMuf+@W_54PJI`SzD@bs#Bcaq;5A2>PdPkyV*Ro&7r`QU#f>7O`{dd-98edh)G8UB}l0Xst9 z;jky!aWUpQ{txhellcW+o(~?wFY&c~qj5hrs(owm>0h@$P={&#w?4n_e8}`u{7c-= zZ80zTc{==`@MY-}`Tgme=)aCf8n@p@BVS5*GwksCvgy3Yj_^JUgr5w;P(F!o=(bf?e44NT-x%2Z=3zx9pluClJ9}@1@apqU;8oURnj}`NqN>6`s)5$ zdfTqah?n}&&ox5-hR>~jZlI}$?E)^oy;%QkXR=pyn)zA#sh=B+`Lv7lz(cQ#6@3~N zy*o6{b283Dy=#0t`~#@*z8LTGb6)t_^nIaUqbc9lfrotc^HN{G=gvH6>H)dHQ{3(U z+u{FsKT`Q~Qhrb#^>3hWFrE|8+i`b2O?cl2J|ntb=EtR=Yn8td{5G)uw&V}^T91^U z{Snto@>8kDV9e`o#-TOOoTp{pUE};y{jwZ-E&ILE6Z~TN6Nr=jp?$(&@|X-~abbMI5I?&Q!o@KgaHyj<`-^eIQ@fwc(WT zsN>HJ`8~+bFmE|N=)C&OJDtyfy5IZ^`wPdpuQngkcehQ*-_*y~ZTD2rcen5BI_g>T z4{Gzma7Va9VgHaMW|mciyky`v>w{wVyxR2)?Vx zL-J`9IzPs^*6rsnfsg6Eu3hr;5sW8X_i@lq_JN=7V~Cqy+|Tg48RqAuHr4^(2V-6n zPFC=W{)o5fs0ZoYC$=EZxcvz{#?|MbN0RS#asoKzPxw3>Q0oC;#~Jxw1AWnleMB~o zc-5Yy&#>s}565v5@j2L(JD)v%{F>;?{HW9i&goCox83^fNBoyUZ{tPZ zLHHThCFga(sUM*F^LY4?bj-(b?+ZVBBz{HTVZi&r7r5qQT>id~{H4DVUAlf4e2?00 zr*Y9A`n-Vj#_=9Z%#U&H3yW^JyX9=_1f;3-`%df5MT3KPW&hbeC8FW-lkmD?Q7Bh zX~%2%-UcE2K;XK5G58~{x{m+-2s+P494mCXFP$EZc^7Lpm_NW5C4V~dh0o1sKi6SG z{;!|q+8v{#g6j zw=r))2e{L!pX&hnDE|OeKd+7F^MJ~0UbjBJ56bA3f57c`)(!Pe{=VpS7Wkp;obvko z^HV;z_<8(pjO_kV^bgyc^q31g`HB7aBtIR}53o;ue%s+W?K?}o+d7lopF*+6yA3~(@2!xt9q?AW>*Ge$`wjF1 z%Xj|W@cRdueitfoxQ-V~8{XFd*6|(HfB5Bl;BVMl;Wrxe_V0$*E!gY&yy5rZmS$at zOS5zp!^`=oXZgx>NW^uHjSexf~qvU4{4UFxfPd!BYtuUVW&w%TvL$NN9| zZB_HyLmqzzk^YGN4xsbr(D(n@b+2b7-K&i^DtfJ}pN>2JwQ=VYpzE&G%k_CYK2+h` zeG2Y%z8MF9|H3$E-rZ(AQe50e4yb=Aycyl)+LHvQRZPm3Sq_xQe4{HE<6)kXU5lzy1Tdu8DJ3p^hq&iEO( z{N3nB@qZ2&cYVbFIehNO^nmIs;G_Bk)cu0^nXl_L^%L-I%JaD6v1@%kw-%`^f$ieJjHeQt$TjYH}gx6`V{>^zh9Bw#V^oa;-di< z%#Y)W55@&CF1XP2XNsHF&DZr}KJD!T9bvEi_PV}ek=K*|0G0oM^2_&cVP6Y-v;R-i zKBCEQqRG#GoQUTqJH8Kleg|=|T#xs8-|;%?&Fc}**9}D+hGKlk?~mYmkLZScP5@l@ zZ-Baw0h|_p+~0~0`>lucZPj0W;dNZEC?D&t;<^Bwab56zA)xvQp!TnyyN3K{Tmio+ z@-{*q;5S9y&5#H9O_8@9@&Lan@^(TV;5S9y-H-?PD$CP-Eb=tvuh!G7o^{?*j^leg z=ehbUuIoM=Fy(s(^)2h)?_p-+Loq+mCp^j5y4=z)TIvta!B|HN=s$+uhf#myTF)_l z8t!%K4)pds>GM3_k1?L+Ydy~VQK$asOYV<5sBilt*`w#ErN8`pr@*z|0qTA_8?XAi zx56*^C-HCM(qp;wyZZlf^iRG=K|QbgIUUxQLD{)%eKY?KhdtWIWp;Z0ne8iNPuAbH z{}SHnSNGcqtb6REX?-cz1Fc(#W1>s(cK`j>p-*|_eaD?xuO44NRS)}lie5Lx0CjR4bp!L?8mhKVm)bp(u;i}>nrWs`r^HK#4+O8 z8~r-v-GkQtjsLex`+URG;*x$}CjGv*`ziOytp6oHAN_?*=*K+V^gjEkS>ZK6)i?VP z^>{t=(d&sIaIJ^me=Dxvf*l+OF8WSie|@L-d#{guY6;t%m%Wjfz23iKT>Ck|*~CY6 zvee`!{eNM}=c~W4RvVH2Yxo~2>e*G1-xd5i&#fdr!B>3@d~8oA&QDn7st>dEeslVB6KLcvt{QB+CV;kq7pfAsHy>4gtCiPsN@O;2Y>O0VY zU6`lmJI3|?0PxWmm)}5lMfTzTgYkI%uGi7!OVE>iFTrn|f4(-&alj?Xqg@>Dgk7Fz zJ9pdFXK?+PalDt^LXY=_$!9q9AIABg`5rf4`an;uGn!|9dd>;_PR!T7eYw-m#Xzt1 z43PbxSRRl0W1%N-*)s+@j*t1hjn^MJKL%>P?;tKe;3tLTmua8p4@|qi{_PFh0k{z3 zt#OzJAID{bdaT^ATtMxQR@MVw4}3Ln{I3_|iRUfqi-0rYProrP`2L*yK0j)DK*a^u zt?w}IOEDkk>e?URcUt=^^y(*|Py9Ms=_9X7eyWepFMGcWsCayS$>*|wqvF5le9rVx zTt2^OeXtIw9sn;=UWTi_vUTDx?6n-)X}isD)Zg7_^H1aFb)xAH^e@PNfq82`$l_Z2H;u*uHp={jtU$HR93@-v|FJ9|#2_-DUU(W`ta=TAibs$QW_ zU&RmFFMWQ^e)wC9_5*S1Ph5T<4*%l0w(Qvm`qF<|Kj06~EB%=2qnVH72Y<>QKz&Ce zKigj^f0F;!nI{-moj{+Ym;6}bZG+V73+M;b8&K=R55zeopzzi^=v({tS^2T{?e43@sXy_megmh_tFL{*^NTOc7SboP@3i@kl2@!A zIL@ov_B(L-7x=dt{bJ;m`Zn@qNO_z3JN8@ZFNhEGWQDk)_lfa-@)vymS$tvSshZ{w z!~U$#HS2B^=YY6AKJGjQoF`uHH`=cxIguBtKgneu+VcKBqwiFHELhJUSit+s%$E{Z zA3OnfJW6`^)p(Cc`AB;oIDUW)+5f=tUEzAo@gslt0Q%&wG1)oke)v*7>;=xc1g`Ue ztp6-Y?-h(w{}uEr+ne}Z2!D1|uB-25adE#JEp}V|isyTfi}ury=fG-&0CtoQKftx$1ze|{fa+s_S1a7#=e~z|qk*fBAU;Ek zA7DS}@qAEz1aw}Y&yn93hGTzlJblW~TP9F80JJ}kcXgFd6m%VF=UzA%jaHvPQ~ z{RGeHzUv3#{r4Z3a$f@8uJI}V&%i4C9`s*bDCWuUi!=5QU>Etl@v8X|V!XCLD0)Km z&1{_eyjOd?SfhQ=6Oi@&O3Kqd^*>v-KLC?`+XuAMWE??e{qu;}UYyJgrBNul+an3Ek|2 zfa<$|x*r9!zH#31hF$Ly$S?e+K8bT6@z351mVFPu7wr3@FLp$HI`F<^i$A?DDE;m8 zqBjmlJcj+g)Cu-DkIo1G^Jk{NeK!9U>bse~+Gl0B&q>gSBX7MfvoE_Iv@gl%ozL7q zvJUEA*Rt{M@VoX~xPDw*tA5bFJ9|H}sz;Td^JB&P7Uani^Czndtv}4)$m8=-S1B(V zq33wIeoX#V^u%)``la-j{dBy)hJt@5;T!oJA)xSn>j%B{y#Ea={vG;kU8(kI>XZ6A zq3SK`vu7MH9G|j&duZPay^cR%U+5oxJD1MYQhvCe2O=K^+GHoZSvtykIKCoA-;6}clJPyTp} zJ|RD$Z~8uU*T0)_LZ7Pk$>i5$x9oW%-YZmoOcevt@2>s!2Efo)?}Nb3waDL#6@L!+ zda+-zSL@pg^@uC#S8?X|5sY`;{>J)OkGl8op5XTn0-rT3E`(mz`)+N!BL7B3ZdJdO z7piCNS6B~M-FLIRf2H?RTJN!sQr-bBhMvj)Deu*HkoU6{JM{Zu@H_WCdH&v~AJ5Pa zv|fF)?Z*C4b_4dxuCyNCX&a|KC)_{XeZuj3^bhW1f7+|ko4P% z@g2Xf_y*w0>z&#>_4&m$;D)+C{KdV%alfqboxpbj=X}!RK3U@%fo~vxk$;eDy=HNp z`3M;G5q%HWT_@>(P(9vNUXSNnrsKI4$nd6WDJD887lJO)(%034!Rm+&3XL3%kUE8^(*bTZm4|^`nmWJ za{RwIvVDMx6RztVEaf$D#h*C&Y8>-^`M~$ZfJ2l|elf1~F5_E|dXZhn@%yEC?+Q?H zds2RX_qz21oT}Pc(O2v83HENtzkH8~eqBJl0%ASYeF)ZXT?gJlJ741WBk<3c2L0X- z-~{6ytRd|i4igkB4U?P}uAXZTO~4yg0k@p0c@0Um^(j~h4t((kI5XYPhRcfHOa9w{zb zZ%cinuSMT_X3z4GSFujGUn&3LUqIb=oV89+Z^{$E75QT)^xKI%xgGl5Zst|8x3krM z?z@?PmMY`w51BuA8Ly1q{wecls&zijMU_v@c=cEOKUm?{m*@q#o_W6OZo}`Zoe&S{ z|D^s!|B-)x>26y`(kJw&_~YDCtNv|ct@Oo3;?PI>r+Ba5LEWIO+hE zZj=l@%F*sukYU=NA>~gf6BkKh~Fcm{IXuh zPT@Z;`dRzsOS8C7=X~}H!|A5p%A50ht?`Jyx#<5nqurrj=wtuk`gHiCqOZS`&GfC* zQ`@HG9Yj4Igdg#qwDM=$_XA&wb9q3;8S=EhdFH_XKkx{>Tvu3c#h-aW`?&rRzI?vd z$nV*j^#vTN;M>Bv{*w;pdGuGxN5IEg*O9l%Bfx5Z&^iRZiT}&$hgjdm|LQF(rR3`1|Z<+x^}T;JWC`b!R5<$osas{J`@w`k^D@z&=6$-}Q5@XOg@P)G4n+ zkC0dC8~#qaxo&LuxkuJlcO~x${qm(H`vvf(_;rTze=#sQ6Y1m591f73c}`fuI)hc|6C zan*4=(KR*CJ=iR#3y%+ky7xMs@M86&5+vgmYTaiz< zkPp@;>2+UtoL{`@4(@+o*MGHx`G&F!_6i5&eJ1S|4yb+A2Ky7QcX%KArp?H&*!Kgs zz5E|((ES?M5ySJsZw7u7@73b^jk<9_jo-Ks_=UjR18+C}Yf+DY<`;4LHRvt=cY^<& z;@U&++1hW(f3EW~ZhdhM(a7Joy}>-Co}>2nP^ax3>VX{i7r5*N-phC*@BBVZJg*%Z z#(dJ@ewqHyaMgE)cUQ*c?;8Wo^BaTCbK=rx(D$#9Z~mRWUvKKysOZQ4YoQ&(F&}zK zUT@gb7xcYhZ_>LD`xWZVyfa)EAL8WKkM~<`xBLqGyDIeROQ>g!1FF6-FU{Xf{4@{# z4Zf;>*rWX$>5mz|O8G+*&^w^$lK#Vf&N9>;^O0w=qdVeO$8$i;%lgqs1)mkab@!to zK+hIjse?^}2lJt!CzV9E!$96eSQ8$`*3vx+^ zekFJ-?A?s<=nF&97lv;7esMGDJL$UJ91s1+{eJT1qV*+|pI2jiHRjL6IH1PoVtfwI zqaBCj5B2xV-^$<3^_U;;Nj?{K1b8R%+2{Df?(fgV`T?l*pZiJCF%Mw>bM4@VJi>L? z|0r-@?~d#0*RKGdN!s_{u3CRU<=1mpLf#esPVlcU*dKt3=X1z^>C^2#1bX9FP`^)| zc%HZ|`{KOsIr;;3zQFql!&}6&b!?XUop3I`7W|Yi&kYy7!bd{?5v=!^551MIfLoGx z&FkTFqsTML1ulE=yFh`D2A$%7am5c%{RnzH9{fEj@c(l72T=L*D%wGw71^)K;8vO35_Pwx6_LctE{^SMbEA5!We#dmfqKkTZj&%e* z|IvB-0Z{uiTxb7z=s6~?+di!SmGNuhyAYRN8a5ebY?@PG9*F5hdpIeD}D^V{c|3>g1tmxsqJ)KVd7V?!3fa-71*Y+;CAD83! zzf9ZXcrC%d#wT>W)bnbE?l?XBDs+io)t(;Z74J9fFZCJdmGJ4xcvZgEowJ;GieKE9 ze)&4y6Q!N8|4aGv4{OL{=<^$M&eMN*&++7S>WS-uxb8aO{*?OPLcSR?50!7^^N-G( z9+2_C^+NC8fL?W#_yKlC9q{*dSO@A`P5JkHu9D&M-y`Nn9ToQXrtSIiPQ)MUitOI7 zUbyc3-0*o5@ar1iO!T< z#B(>|iSM?+za@^8m*t1_P4S30r#>S(@=x&rT}S9^`$?>3Uh;c^nD=t$YoC|#zkf!3wwrNuJh{Fh&dv|O#83O% zN7y$I{)l+s`2+c2-H7X2e-IbPXBqJU|Ksxy_0a0Co>S~uB)_jjKLk|%e}#F=>-b#( zjboh@j&(_R)WcU;FVvUyX4tLqsGF~#4+xKX{K^{s51V}V>>t1l&VNg{=f9=97}7ZM zM)G!W{>Smo81F(3k&HO}y9pb3n!C zw~n0epi>^;`X&13w~lasO8S1>w-~Blf?oXvbc!2rJ~xxe9J)S zKj7yA&}+X!r}q=jy8ryG2lmr%J$NXORagJCJ^a}2eLxm>`=ROA@Jl1~ zL>wATzfM--u0C}_T%5P?Z}?Aj3jb=K3x94^uv0q68^Uv^)`0~1O*B6Uf^}<+wJ>J)Q9{(ycG7L zZm9S0g6j&`qF9u_lwkj7w=mb+P>ZJ=dSw{#+@&x~wJ&)q*I(n3 zLp;W1&++DqFRe$AoX`%lp?x!#>i`!t>A%DDe*zo|=zV?4) zUqPM&uFzj^rT-0O-|_n5JhfGh`b9Qh{)8T*;yV>`r@VjspO{yW3n+abk=G6CjqBC> zMXj&kE58FyivMir&EH?byff>4HRfNn|G`&z{?_QDfLiB&JNC_llFRthUt8X%ymiI( z{nqWczT!FozDvC_JGFoO?V9}nct!lm`RcbBpRpKsd{*4A&oIAn%6#S53HmudUcP>( z%|A`Qg;VsO<9fRj^$FN7`kjae&ZiWooruQ+uWxT7J>m!0$Nq@l=ivVN*x&2O=($fk ziGEP}M852t_dfNlJCSEN$F1b)f#}Nn6|`T}YnjsB}T26TTq zUjFJS{2v`}#5v+VNq@g^7yb(<{J!PCaNp03-iG^h%6S|1Kgx^q0r{RW?c@ERq3ZGR z>5D`0<$jC&FZ$fzt$1D`zDIt3PF(zt@P3`oQ(K<1W&YRt^#b#Q_UtsDv#B4xZC`m@ z{BLt14>x`p45dH4O^cD6q} zWIj?K(NQ1zq0~#~GI!>!C!qHA|Lc-*z)LaDdhPo;=G$XmObGtzJfBedWaGTA%E$Y} zZz1$q@q0^e11`N*@VtWhHSKjhLSOCMaNYjeciey{y{1~{wP_#!HsrmoM;`+2`48L| zC`aSTFD3t~Jgt|RU+;vxh*x2|9-Ch=k@S5``SWL_ACN!sJS6xhKXnm*d(G!cfZ8W0 zADkEGlbo1e519O}dT-|GzR#)7FhA)v9D1dEJWX!0<8kR1#sAp4Xj7j?eyeW->iprf zI{9Zd5{m>qPX3!zuj) z^$h<1%Yx+q)}_~_sDlaX`?gELXCnIZ-5B4C@w+k3@7ieBR*c8@fy9+JJH?vpz;jFa z;Wqj&Cs=cpQImd`+2LrQzAZLemqwZ-wE$man7jz{8GI( z%l-^p>)cCqoO@C366g)pSKmHkd)_`%XrGyl>v=$So-!=Gn)KSYke|*o02jrd&w0r| zexDyu&#?hVG`=Oe=h}J)dTc( z-;4K?-hRdV(9GW#L|=|ePvoEc0yy2OXWIe&lil}g-?WU5`yS*Ip!yY{{0w+i`e44|b%6YGzNh?`-mC}4^`*M=^{X}W z0~{q^KDAx9iha85JHkGT^26@m;s4!H&%`hKXH!1Uqdji< zjK6X9`JFyvhP)s zU(~3!{?@r#^?mA*iO8>s=qD5Y9~jnIy$A;I35$?-akGKz&KAY%M#;xB*=r!kk zHuPwhe!Smi-`EU@>y-PuNBI43%k2+7uCI-toAY;IkZXOgj)>1($hq$4tQg;yU$&xe zZsBYeM`C4ANEWBb?`rV-2LYG`2(7NE9?MF{KCFV z^7Ewaew@A+^A_dr8u-$`HN4l~>W76SM=16*s`iM~$Oi9M_{5UyZt0 z_x=9}aQSZ?`!neGdv|~vs!x3Fym3I`zsG$E$IoN|gbHlGStpC>Ue zE`I|?zJ|XZ#*=*O2|c6_`by|y$UX!9+5J_yZ-)NpKk^^PsR!;KuOxqjo$bQM!rzPo z^}Yz)a7uVR@Vf5xJ@kf6x2?FNoh(jBnw71Mw)=HTR)+Y{WQ!Kj`G~J=*(! z?wCKI?whi8b5?aU?6~84x8zeF+P(J>uIJa;{3?CDGXFH(byvzejsCRpn-zal{dbf8 z_*dJA2jK7DaGeonJb~+e>i5H6zrW-6cyT?AtB;jHVJBJ7b-A=bX=34)*PT zz(x88>Jbj3aa01TvJ~F`}TaF$$TMBefYd~P+rbets?k`|XE^^;4c@?_Z2n>^q}Ae$E2<@;6|A*em@YXZFeXpVNI-mY*Hr zf7k0q;eOVP_c{5ajDO+&(j52muW}sk^(jwM9DN^9j<>l!u3*0ffB(=Gte>XWeKGP& z>vc(we&~3Qk{*Bh1#I8D3pTwZ-XgX822d@RQ0zij+! zjE`65r+K4s-M_Qfe z{x2l)WxofDarqZe_f4ZKn9uPQ*H<}S=sxM|ONck*kH{ zG_G|;a;V1|^nqW1t1f}74@kcCP#pnJdZ@2wdL+F%=wI>;eu+NGZPL-dm_M87PqA0~ zZs=t{Lf@FD`yR;CI+Cq37s(HKB>7oBNj~W26sK#!@0$G$I*kKrUy{vt9t^`S@Wp)X z>%Sg-9k5P$xGp;Zz3=6F^t|4Kb-?3kzVaP(st3TniayK2*F(Q`*X`)BxZiWh{G;=v z7Q8B-aWCz!+E>*__Q4<8*FC~N@)zt#_Srv^(4YA*i8`=+)hX>3UixRK)jwVbM>oKa zdTtb4*NU(2L|*ZG7vgcA0XQjqH}WFRF}}Wwb(C~_F)v-;^K;vhzwd*AJHIw?AH{iz z{sG>D%lYhipD}tf=>7W&C+q{-))i0O7l_YYe}DS*yMFEgI4wJGME%na+Ku%;=IJ>p z?0F!%O`MBx{6^6$e(;0fPHkKM-a$W-f7-Ra-0Q+P{6F4lKLB@K#(oypU6-BC-$&qA zV}6>abxi!s*ZqO~jk`aTe4G5KC-tFznI7&BC4IstW&bwlY45hP&hKjf(ua3F{^7eG2d;78>NC*8 zd4fEYUs68CcWRlQP5na;=S^wnadzo^3HoY%0OWV^Ponoqk{|k3^+IC`v?f2m?Le?=M`k?%e`w#hZ6i|E%=9%aPK`+R@$vhdt`bK}_K1cj_kpI@FFW|j1 z&euL0_9S?Cn!cy-?+W{qUCKY`>wMU?-{2SRhagwyPJs3Bv-Zo>rxkzdKzNx~t^@Q- z`fq&LePA5@Qu#gX`^@p-!hS91X&(!^G*A8{?s@qBgZ_Mc`js=%C;paS>XGWjb>7qm z`zqwacVRscthG(i|G37dxGq?De?IO!{=ps7*Tx-p;Pax#b>;gw`XA?^PFwlJ{(U_5v%}W2HvI2hRlUqb z+#pBwFc)@=M?W6-`8wYBvVC}NB>hQG{c*j5d;KiO-6zNW9?w0>OTTl$`ZLbofvNuf zh5Yy%TcWSw_eD5fBQ7}C{BCQDdZ1qp$PZ2Xn&%k4e;M`p3hE}|j|-LGsyE7s?}Tbo zwi|L?k5jIX+Hld3KQ3XM`occ>iSJ_??}+?JzkkzpN*ui2Fb<6GQN;hq^*z=w4tRm% zW4vE6tVdkBBTmjItTW*+#VNag6rI)~#)Wi-GxF;w;sEtM!hZ6D|H&tlYyWcnvpn(9`e%Gx^2@ls(t69k&`0-&$mf*TPpY@`vGiNYH~BI1 z+vCdb55K9NFkf|;#jEN!@3+c0YF*9hSbo%e=XJ^p^NHX0#(3iE{NcKj)dBQq)u$ZS zx?EgQ9@PLxD{)+j`cNI9P8MT4>A`iLdFXxIaO}&wBR<`DE(JZ%KOL{`W<4f--4~g6 z*7u6)gMFIy(d*ys1WfqV%J`J{m-l-*SHS(`^$Oj5m98?rq5A7|e;P+$5RQHzd!v48 z>As@OzZ9>Oj}7@_EBvwL^BTPG>vP3jKNlDqggA6$)dedIVH$5qD<;(vZ8-B0nHy&=1~52L?hof^9x{>OU4>o3vY2~yA7 zu*1-LG^X~`!& z_1eNY9L8af_F>RB>8X6cb=5^?*U-uK6y~#KKVhEMZOEyIUljN0@Dt#0#LfLP;&yQ+ z;>G?FaS`2-@#n~&aa#ejeZ7hw<_Th6OFZ#i8F9`N#Q!pdUmRCHuXX+;JuLU^df0!r z@V*K4etdlfLm$U;34O=W}e|uXD?>e($>h6|XG*)AVj}RrGyQdtPn%DL%UYe?)xFT2H6t+bhlQtp4e*#OKd@{Z##_ z{r?lx&nwOEN#paR_PpBi7no1lzZhzL`jKw$UkEimg?`TQDfe?g_51gCnU0Ws^3s%X zLaz6JLOdYx|9jLpA?YVCn4Xa1|4+l?gdD$o%j1L`zfXAqIsU#$j}vnIk82($Bz|;Y zoKW+(V?LnhH-jFK{C?y@KA`y5&KJWKejUUwZ~1=d@)rJ=wlyBJ#MAvXpy*3|+jal){B_6e`3=a0 zox}w<{JijaJiilu1Lw~t|GsD^&Li^Q8$F=^p1&FM@!U{2_k+B?h4(a}AHMIGeSo@e zhdr_%Q1_evx`zBh+yJ$&$>?P-ewV93e*zARKjTOKo3$$eXDip$&!3Mx0vwBR>$Qpf zKj)X@D`|fCH_@fIdq0f-p;6pB@%ti{A9QUyoCp21zXwdX?z1lU7j@>{zwU+IhTB0O z<4t~Hm(NRKpU!2U-)vi^JiH(5_)q#hY~bGCP8ORAB0$dZ0_z3m`BA)UW=@>UcTvze%AA-BERB)_l+!`DSq{c$41Og z@zHxJFYtVlc2D{Jt>?ERAGZsw7tk-_<8y=;_=dqtiKqvD z|K<8gb*_9uKT|vbyEu+>Bf(?`@a@olivEFLr4L}L6US@P=bq5t^0$5Nfc|8B%m1I0 z`CI33=tG(hsC5BylYgeDPu8b&P6u520wz5Z{jJbn^@ZPW=~kZAEua4fZTpLkuxHD7 zQ=Z<_K!0@qKx%3{)}hN-p8^;E443}EQ+3F9C(tY2ptHW{_mUsy#+WxOe{Dy7#`^L? z#1p>@CjTKH1Yg_>cwoP9{ndP~Pqe$!&!usFmHr03^Xve>Gfw+%*5-H}bjyU#cC|?# z_%Zp@^*w1K=aN4Z zKlr2KZ~Q)tpC6^?0~h$*0MAF}s29&;3F#l|1H9XI#eQMjdcYt3^wWDLKxfz=^y;_S zzD4~AbkYy-pmpB4J;}v>PxD9pJUx3JvdQrm5hsoVs=lh@@0#&^z+Td4{<=Us8&`gp z{`33D+4x$}OCGNGROZV+xSqyU|Ja`h2dw&Kk@GWsZ&&a;t^V*n2=txyN1O8WZHKn+ zZHM?hI{Q2Mb5=O>iPx9#d>!%-ckM4;{A!!*&EyNmxcm)xw{?DTjq@{m6weHw*8T+Y zM8`VhK4r=L;8*DlsJsV%`2~>g%``sO0G*+pOJUww`S5+Gm&503=$qp(@#odU@uKRr!r!mg^&-X95d)|)UQtZdd{_MCn`?LFB z<`2dD@}T_%==|aDD8oLii^2g-$9iD>^cPbx9_wgz9KTEJI`cY?{e|%IzCd-6@m~r0 zvR*ttI3eBJitc+*mFYsCC zc`?iDz~vXf#lY1sz()}L?YEEvI>iCwu9qSAdFG4cH1#nZuB)CgPve-E;+^<9t`GBX zNYB&x+rCmyzO{Thj^dv40a{C>WlDGJ@+5uLLB_6cD^e1X7*J5 zo9s<-dm_J{ASczwwz^Qc?c3+TR1f6;#SF|1pE*p2=oh~IhrHuMv`*Q>sAKi>N&_Ydkv&%}Kl zAm0zF9sk{-_fP28_FupC&G6|~dH5Y*?4!TJ{g5Hg&%fhoo`)N%A7u7TaXxUxn^5Zt z`=ad|h;jJ~<5!5|zFqq}oExb=a6OF=1-PxpK1 z*w@-$o&FsP>Jxe=zv(@1*dh7wLt1}sQvM0~q5OJpyX^z)m;ajn^1crIl7BlZ%Z?kA2;bv1}pP#3vcQh=lo3^eF*VEH_7b0bU_SsV9>km%z*KM2A9bht!gcur(Dwm&uaNnDdDy?(_1+%vW54S0IaYbD<2uQ3pX;Q0+QaYM zgHIO!#Y%l>pOcN>l>Qsghx%{$`#r#fr}%~co6ieVe(1gh{Pf<$dycGM_WjKQ<)iPa zJ^`mBZ#Vew`uE3h-S*DoTo(Fgzwg#j!54|64@fTHc<@j2Co23mV_f%}kaM%uA8pqI zPj#>7IPdKZeS3?oia)FUbxh;q&KI0_ZUlY!>wG8q>vziyuX6mwe{Om}`4@I5AMoDx zrB?m?zUJ}yg8l@)=wEtH@t(cn-lObMKPDZ2U&{2!-pN*d{X00|SC9Uwddk)--?udD z-S<)N<+={Ofb-(lSsXH-(NCsBp6~bZ9v8>)y?BG;fPEGD@+Yp#AJe04_HWrwE{NZO zt_R4AeDeNoB=QYV`3*aRf3Z{1 zXP5hl-(Wr)^1c7YE%tfC)GrhN+o#ai$v?~A265!C<^#(AS-mTti3gqhu6p;p>iRuI zI^<0{e^R}new1H;^O08&oaeef5@4-sei6Zu2@kJx8SXGp&Wo&3r?GhHX= zT^{xP%VPmq7r4%TrSVI6erLMqUzksvzh_2>_mu_Xee=sD90)nHP5nZD(ktIX&!!&4 zX&1&RUwomT?BVZ-`}(&0FbF!*M_+m$&#R29{(yVlp!+BBW9XHC7`MOh`*guS;fm7< z^Lvs$l0R6~l08ZPuwVAT?!i|2c+Lm^i+(K5e`q)Ti|>gvf874S`ZFQ^{W0F}{VU@b z^m<YGtq5PEPUls3` z-Grb@FN1M=}^my-v^ndjR>@!p!1YU=q zPWU|p^#7K5=u6~Z=I;UZF871ymWU${9KZ46TI;y;r=<7(Bg5+z{?{w`g9@JdS`YI< zeGM?xC*W?P7d)mO|LTVG4p8e6;@d5LyT-A;y59R8*Yw;OaI(oCauMeg59j-M)K4?6 z59L86?%>CG9Yvgv{M;A*aeW})sF(c3IAGnSf3y46LFIir`X2Zk#_wlm@v7#D@*4cK zE&vY6o_o$~#L;~r*%j|iyl+43#CjI$hE6|JfT`5}n$K`JKVaK<^4^o)PR{Yc~Jr-DQ7Pg*BAkLwcQ zkbl<*_)x2T@E7%d5c1;p3bOnj2>sM|kIvUr;x>y7k$Z-$U{YnbvQ=_kKnzWOxRd+~4Xv)fjQ z<33t;Og_Nx#mEluGaM2BCHKj1Ea5#NUSEsxHT>U!ar<=*`P7OtpZz-$X`XTIlfb8o z^b@xcFX}&IzQAqYjQ3s5am1B+HOJ%s8aN);>y&rg_iEoDU5pFb@6GSkFkVmkQS_cAXNZeZ())|1bWo`^n`A@C%#}xclFP;{$yD6#5S7 ze+_)-r)%CHOsofv-&2nGIKFH6y(#E%{64ynepn~c{!Mi?vFZCWz{OU6+Lsr9JnMV` zR6btDK1KFSdA)oQ{e<&g9I#)G`<;eA-oSkZ>6q`P*ZK{7mj2^B!QFt4Z-IHe^b_6} zkUs%+KT@7E`kej6mEgOA{R{c8*iV2{fvawSFOnbpAbvOpxIrB2o9c}A&_1nu)^}Rt zyS5wakIs>v-EGr)o7*4uSCm5e&Tu$(d?LOZAHR+%E`4~AviO%C~%CBsHFeE=R zZ|P6;xeBhlz;)*-&zVf8bpw35i393>E4TX;`(AF`uY^77FObLjE55M%(4$^;{|hL7*}hl(8}_F2uW8cPMx6(MqNjgDk9P6H{iggB zbl9iKkDwb1J(op?JePfg0Y@Ac$!Ff-y5cxu{>(?o=eh?yfDcQ~u=n%$enRU8-~j#f zC%vv)K=t(>uDLz{??~=Z$UOud8)>QD9d!@Lg# zA3((k-;qckKSPInI8?dAFN#(m452 zuK85Ix2W>z4E?k|R`mq`#gKCp{HpJVUODFa)nl$JuK#u|>|gWme`Wcsy662K^NKL# zY2X9$JICq&UBCB%x=}s?4k|y(`Z$H3|K|~WoHyvJd48i_)aOUIKZHJjeH_pB>5Ig( z{i6CCaM1&fR>sxuvT?1S?_ah(?_VxvXy5x+0$)KM+0LZjpvIT2FXrtiepvUEPXjn7 zf<9;Pr{&{5TznZ9%3lrsYdGg(K5?BgtoR@I52RlY`gQNW;7`>v;9Te*@$!4??`QsV z-bSpWm2T=k-7B zxBp46xP`wtk2vYcSN8#?ulAq8O24W0jaBl2oZX6^=bL&xK|g|i@&lmOH|VK-k?aXS zg#NxxyLdhP0=+aJa6R%#llw?O~? zyU>F$>H)a!m$LfTy7SU(#I4)(z^#9`&j&b8J}=D{)ATp`ocm+!7r6dY&(yCnT>Wnb zdBJf&`BUpV`A|>J8;yO{sGnzHzhM46w>IBa{$+oaKfp)(4Zw0=VZP?W{%ZWX`~tc5 zOR?07V?PTz_~!!Y0oBg|y93`jWqh`kpYO*}?~;%2FtiV0d?GHhZ=6xyDD8i#9rhZ!ztR8B z!#<6(zBqo~`_&isoChx+c%PU0DX#nZ#esj91J`vQ1OFs`T901p2!0)~-*S?F_lQq! zL_cD`B98lX$_H#z=r_aP${XAlX}u}q;q`+3fcXG8PkPovlTYAbuhwt)HTe0wDdVU0 zJR9#%{Ky}CtMhffQPTT;m`tAu#hd3N)=&K@lc)7C!zHie=kv>~zN_-*#DCIp!ShY+ zFEaU497nxqy~yQ6{b;>9p}y~j{^|=@f8;mt7X&W*c%5{YuQabK9>B+Sy+5w^`+q?G zH2!}Qa5wZ5z4Qd1XQMv=YCf)~@rj75)_d3^fBnDgy?=1s>scRoj%+R>7*G;J)yA`E zhXbo_lvHHJBBtI&xmA)=s)E=W&@L)bsBUt6uW~JRO;K)5q8+?Nr4DGofEK(HwJ{*T z%c|4~%En2x#DC}y&qPTKt0e|Bi~$XpLESO4DmY|6Kc4q|-}B0ntoVjPhrgbA-sgRv z-|zGO_s#y(-@X$1#r;!;PZs($3jLBk2kqB%pTEa?#dxp!`{~(z zt@0DN{k91`=~vzda6SxosUE&##`Xa&(B9TL#Dn~ntT%A)ZQ#aGkhZK8~YsXQ`|FtpQE4rzZ_Y<_J-XlzlS<; z-*;#J9-$o*6L_x&5PTxfaF5x6`@RtI)jA5OJV|-b;{ChxN)UKI$7lZ5yvuN{E3F~( z1C*XwU2RY=;6vmK`f}uh)?vu)CjZQTt07NzL(VkuL*jcj#z($*-E> z@pHFB=!4R?^4DFC2mhFN-gnTB@LPY_r+Jn6Q*_{}vsr(wYgydW{!@O>+LfR1lkeA> zejarUy|sQHqyI|Sk>+VRZ%!9}9S*;wxLxk_GxjTySA>~gQl6!}DE*e`$*;-J+V35b zx02U7<9r5eB>54iN!G>x5bGbH>+8JlJMnwxQ?h3<>dXQ6?ya@R`!zq`MtqbPfFl|| zALHjeFTmGz3;a1RfcHf|zrQk%dkeH@^FjHF{qIS(jnNan8d2E9ob`I)^bH^n>|3Pp3RDk>{Gf`2FB@#cj%UoBcx{#hd3a;^^N^h&+=X z$Y0xucGUqqM<+!4F8krVGPMJ0{2}-b`)BWAY2HI_lAq)zIijOJ%HODq!_t2ZIPG2Y zc>+-DU2Cn*`$&DCPmum5j{UpxWXX8;oxwxDr}rP0Q139l`o3VjJN#$)Q+}TgessQ1 zhhB^W^_%uOc?ezkN!SKq_&yanEjcuuN+oM+B2d%u1DI{ZVsu7|%@LjM(imxSwc$WdI0t3Tw1 zeQJk4Tg0n%0>2Lcp80!Gd^W>RfWrBGTFSrU=iTQwg1+JJ)>pXCyOIySxW78B@f(qk z8}&KLL*6Ri0B;ko^4IUvoZs_(3*bY}zd!APJi&THIM-+5XYgDMdOd&I`T^dRygk2< zLOvl+_CwyyPI*2LR{Fd`=OK)9p6vNKAaMJ0FYK@AyCE<2ACsTRe z$bT)2XWUw_i~WXy8}h!d{-@mk8)`j8ztq1+@@JgC&(B!?^Q2R+mg@lW&-)n6`;g~& zfgb%90?vtFHGVnbo9dzF&-1gve-`guQ%;4i#Q4N_y5Khua;_Hryf3Ki(sPnO-9X;a z?v2MkpM5^&G2j{bX*~Qi?)SK!Uv-`XYX1g5#60bqX8%8{VIO6vI`VwP9Z+$6f$ItE zBUJxWs59(;D(sH;6_wxg1D;>G&UD@CoCkI1&1(3274z79xz5-BthX&k?+pUi^PfLk zw*7z?)qmW6e_`Bp{;2m)UYLmfJntjFuzm=lK6yRCy*}HMUqJUdk=_@m-VYdHzigkU zJRBo`v=fqE)jjjYent3H=t$r7A*19ZM^Z&n~zt#!BVUP3uXZAaP`?ni*&>!8l z`>hN16JS&RUa+6DcJId*>@Tz*g-Z_lDPDk@*VH5UD=xs5kAOqe2mMqBfcv}w{X)<2 zpnJW#67_=5&umBH*ROt8!k#OBUkCi!d_%tH*@E|jpr`RG1-~>-e9=#JCgYd%n+`ow z|G~g77W|UEpDVwm;3xf|$CZMAJHE=l%aN~_+x8{>lHQB^_;KFPM;@jB8>svdxAp;wD+_9tyO$G>I3S8=#Z;@EMW4}P{gBI*ef~J zeg*G$I-imr$zKV-9(+pvlAG~KaqxRgRsJ<&{EdJMg+3z%p7JNzq4n&ArHCuei%}O? z|M|NguEW{+BI?$Y_rIq2R{dSqE1dg-y`iN)W{kumT`lwzJ7W8@9PrW%`(mKBwb#W2vOeenv<$O~w?iB#D^>gwF zIWve${m<@4eO*)@5ZAl|AL*At*YB$RAYA*aY@GIg;%j-*Khv{2^iKSG1K%e<>8t%p zrDqzK`X{^<{k89e{EKOP+fUY~wAcB!;opnCuu;WV{*3!Co?FFdJ^ZyE`IMg9ZMhxs zZLnXpkLc1mKGTc-i0@2q*A(8{_%Htt@-aLuIl;gEoG9`8w9hBtt9%7K#e8|^F!Z54 z7)QRqZ8!ESk{|n&+C;nuF@bsR{;8kx=WV|ce19Y3OZ_L>d_7MpKH48k{^R>8KQVr} z)6V)e+4<@CURioCG`9=cNI4qH#<=cZIZ`%16dYnme zrEirdWxn}5um$~{hvR8n%IDx`yC-7bH{s{&kFh`X3;XLSZiC@xozpLjV_sl>To}ha z1nt5)KzSFq4jMPT7ViN>Tqjzd&*^)Rz@=|H?!ysx#shrt9dfNhST9_UhU;3VGe2rw zNc;d-3qP0iS<=zZ{PsYVA=b!9_oMFQk;`tuisb|8!JLvzo z>2^Tx7lwRK2D;>+UGSgm)z6Us+a_)Q^Vru)@7Ondcbv=EPC4vH_}!oj20h_zdNz*y zTFeW>8-eSd1nn!t-+t0|y?ra}eB}OYmz;zL{cZ95572r%!n`LR&J)1Y{`QmgLHe8d zL^+dVpa;%=!VO(N;1~5LAN0dKcizv%ILPO?3v<|ib3X0@H&kA-e~eooAJ*dzepP+$ zS9`U8Iu-q0U&dp9U-47DK%EyIQ0pJ~r~XM^!Y}4>Iga@Mc*Xhw-jlqFzxVOYIJYrx zZ2wgBGvBCtsK2rkQ0oBvYrW!s16VKQ=XlRCtxJH(PWvg=rwhv%ProkX9#atG4TogM zUc}*M@ZCe5BmJi94}LG;I8}7*V?nnc_G}OON#E(f0p&-HhrPh9H~LY3;PM+`;Erd- zPjTe9q-Po@`*{x;eE*Wslbr+dH-4uqf39J_LBHM#e{y{`?)a=XbbobWE$Fv{emml~ zrg@Q_V@^JbbCb_c*vf@G$Lr|vE5&%#>wWUqLjG;6%N^&alXr^orync-;PLaFSj=(NA|mk2}6cq@MO`*JzR(xbT=YYq1&8GK)&hhN$_sos=)-~@t{y&R)KMcJedS3&&`wx0w4_x&CQ0r|r zeo1;g41FH@{gsTL<5lq=m7T-rPdkTo&aOWi4Scj~E%-U#9^yWb{a7~*c`mO%nhbpM zfc~wZ^SiI?e=+cjzCVP1&c8jh=l1WNs6AHxe2nLN1LVg#YIrZ%FSwtd_I;T@w4Zyl z6ytr*P=9ni@auK!Rr$;PSA&khE~aPWIn(|IeH>{+W~e_^bbCdV-GcyHNknoC*BQ0sUhhe5OD84@Li>1Nx5yoxjT= zf3EX_-anQ8|I9?tC!+s!;M0CDW?%o=pwCACOMzcHp#MV97oz{wz^@+Ae>v#O(f>x^ zH|omAS@@UvHCscwq2>qLZ>t_)AFBC2=;w5^tQ+jt?Q@svK&l6Z+MkkNjIa2TKIr{H zHqTYJKM#Dz!Y-}X&}Xz=-#Ybegq}LTK(8x>-7gm(?LS@yJGTq{F3ZlRk4wpS`g8Q( zVxiy5#b@zlva?*LUM~NYe8K>l`LE0q?dv`dd7bj6aZLXm zlz&gl=ZfOV^@jDY2j_pT1H3mB#CnFf@;stA4?E83eDd6|e}{x|X}s=r4({*ggX}N4 zr`zLn?w&mz&poPhT0i@}i|ex>{;vc2;M}UZ2B`J&+79?Z-)rngxMTf+t3IZ4@;_^t zkD&QHVBUg$z3_wfm!D<*0Y5F?zFY@GaG`_7S*Gm1lX&YUM+y&s`{eEl;#moRQ?cy5k;{XyT~_$>FQ zK^Gj5-`d~b0DVq*a0~akK<6pYy$+w=mnS9tYK%+$=N11f z4~Tyj`5=h>f#7Vwd$x;o+V|PmKYSK+;rD8MUyym^e8P9~3taE3d=~p^@x^{i5c?Lv zxqwOD3Z8>gPV8$w3%c+m58ufW4t|0|b>n|&`gh%kc-h~GQ{!OXn=_rE|D9d^&$No^m|bt=d=L)4Z#F^-aeQ zIO{j@;Ua%k9}oJj%M--^FPprP!oHCs@{7O2J8V5VihfG*ulf1+OSLYq=c#`8oE!*! zysr0nzC4A$dqbYq0m$S0I;_6Cjvid!KhN<$F~#|b_qnuB0o;^7P9q;NZ)Q)|FNS|& z-*iIfwS)IfC$!#I_09iJ1iq?&fSRA!KPCRHPX2xlbTEHB*P>pm>(bBj8hF~5-3ofd zv#X_gJC1qAx;u_@x1jX_?)sFTTYuX3WRUNAKkjpOhI@Tz_pjgTiaIn)6nCe9G^OTe0{j^hvv_p_l3QEC(8X<+VA`RVECEQ&hcMZ z`d9g0#j~z_t$zRI_sx+{Dc^M;QPI2C5`V$dUT50t*Xg>~n`!&!rD?zC`8}N9@E2f% z<1+kofva9+`~N-G5A2h@zvcgXV4Zl1{qp7Dr}tjL_i6cEXTJUw+TSl0{_K59f8G$k z>iqGv{L*en;Mk9YC86zAI>z6WKP@Im??`>K=5N9Jwd?2o*hZMO^ed&^aRyPm#u zG4l1Ie-BmBzwidfBOf*20aptAV&KYe(9edv(BHqiN4s!9t^dsb7?;{(e$-W8K$pDl z+2~63pse?zV|_~Y9Yo&>ecZpt@AtnH_pOAYx7Q=g2m3Ahf5BbG_s^H0kKuCBe~&o! zv(g{XdbI0DUHj{&*iZBwvHvOd+jtHOePEaLf2nq&C+yL4)!CjC8-YvT*&e?q@UHvr z59oZ`34ABkhn=vm!o~kx=qLN{2mQX^htAf!W3EqluPHs(igm10U!Uimi~AkGVXS*x z-}(dg6|ihic!Gn`zfr)%XP{_L{nGd;HBQm!Kb`$IRf{+)g_+u z5Z6oFU5Y1g`Je5a|Lx5MZ+ zkDwnle&vYqPs{%(`I|?`Ups>Sl=|cMWR8!XlU&-y{~Q=k@$T)!@tha&IQTq|{fx&? zIA8F*=g|JB*N4+bj-Veg-u0)-Kc8i6H+urD3UkE(> zSJ!&Pd18OyUe^6}KEr&v)`@%lLH~${pGV>MKw2mGJ4Eskk9=?0t_5pTNzjt2_{Ib8x0bKfmK1w}sU+8o3Mc-4so4*g^ zxC+h%ALTd43$NrTPMKUif2s2EnOgMs`1$$`;dq}%a&QkX``5#Myk9B$cId(XpQ1jo z-T+RjzxP93^(Fs5(Yx0?UjX|He)dDfPxJ3!etth3bj>&LQ~m%~UIM>zxIWk~GLEhG zcs@T6ZhOCYjDG(Ium}C>369}?M748WG+pZ>=z^fTeFwi62fwZw{A0-b#ICN9z(;)l zi~W!95BNQw$M;vhVO&Y?iM;Up&=;{E``q|jzy115kbkD1;`JDNl718P_j~wzoPeF< z>(j)m^Pk`2K!3&UJ>8fW)c=h0tLw}O-JiT;*8U+Bf6hPh@5g=_{4%=g*?Xpf-<0$7 zJ#oGU9MJeV&&T)7`TeZ-#CZcybtS_!pU|&Q@?Q4-U#L3xo_qfN1fcR2^6ih7=G!6b zci30;Uyywj&iQ)S_@1Kw#R5O>_%)6HYkN2!_+pJn`vunXIjoO{g8}=Zu4o^EI@uq%)_v4x*UPx42JXDVb2HU#eAn@S{-)3Q z9F*xftN!gg^*Q=I_k-Vk?{}d`(q~=$9|rxQpM$*TU|jq9OrF=(E7*r{{i9q${tnl; z*P&LddoAA!&-Ym#pzH(Gc>~bvT-D!osPacy_j{9n!e91p+y2UbL05ms(K-z1bvOC{ z9`4N$KkO@Y&yMFNH#_~Q?;9MpE?sB;`Tnr4-}g-LcZ%ofLSOCUx^TXr+!phJe!C9+ zj7tx+_XV!}dJH}r;}gB1c#x0zog)1^ddwFvJ@40k5BntTmomEgOCRf@eIxLsS6^G- zPCd_r+|rJuS80dtm1OVdrsrAe0sS?f<_Ff-q?=z^W^vyjgH^izzld)^(LH?{-6dkH`2 zIS}J4d+0CP9e(jdR{ipLA#wic--_tuD^&OA*K6xJTksSX12J-&r z+r|NK}Mrao{-e16-0`YZ1RS#|iDKZ-5ViZux6Ck9wTGmkav+!{xy~ z_GjK3-bwsoT;iMbQN5bKANB0M&lA9PuE?P3{CuT%vQu>sdTr8=Z^8FZ=tsZ_$*uT~ z2QInbdrtnnk9#ikZ(W9e4Lxt?1D}V#$uH$iw72mzF7?0c_*~v}yZ{xi441u!;6v1t zwnEH?U_kNd0eSXQH{0RI(^gAh7@mD-R*E(>S@8?l2e-{S$ z10%NM2S)rnAk$+v_;~zg*JQ!>VJF?|LN@-fc3oZ$`OB6E`O*ha^%rqfoB*{hfM37; z)Rl1CyXpH=j1vxcG4x4vou^xi5$8q6xpg)0tMEJXf%VJKd9e`qLi7jTksn)ay7fff zX#AF+pZ(x4aQGEaas21&&ijA9?s<=P#T{@A_g1fKoCANtMff3b!A0LUoQ(AfaDn!L zpZo>5NIa7_QsBy)Ox~q}uJ~u;w+p)JV(XggWb0bJMn7O((YgiLN4o(VM?mG>5B9;% zTwmbfC(Ucf@j419e@`H;;Ez0L8hRc;U#-K>uC&K%+_Niqzrp<-mkHlH{$L~g-3Wg* zVq63JAoPb^@td7OyYTuvA@KE}N4xKP(Jpy_vju&lz;6|}?n|=#-Y)3n_~r0d%nvufup?y%WUu85NH~&!->kuiO5m@XJ!nhil1C5hvt7 z?HG-D(n9~-F4j@{lXUqv@nf7J|6kV7Zdl@{3VMmlK8%+?099`?ypQ95XwLK>nyWX0 zpVkxh6a98=neu<=w(DvJRY!ntM?d)~<1f8I?-zZ;^YVxA9xm~J(Xw6tqUAc5jZ<9( z9(KE~X7q;~U(tPU3x3iY@O01*u)97L@>M4|j`2X=DQ^L_9=&oPXfkT8X^ievbH!z$5N0)Gzg44*GJ1ORvbE)>fM?J}Ga|K1h9j zcmi}o#Vx~|L6<+!t~>w?+;OjP?}y$yYdYW}^>`8dgOB#d#M|=hKjk&*o%NeYb+4<& zaqpe?`1f86IM?PUzmXpN1kq3Wy?3tOI$*rcWzbu3$N2sjZ~UU?>3iq!y8!Day%FCA z@uVl<&f)&?I`zZd;jix62K!&x?bM%m$k#mlVV(yWk8$hq72{>Wy)yM{?F=o$~YA?TLl=Wp%yVm$e> z-;YFp@(FvAo_oo@LVxL3r4jaPvGm)^OW;I{Byle;rSczA02dje{`@*bvx5bc8PAi z5Kl$o%C22HX=?N5w|;m-wFJ7;I{)` z4;=ne+<{Baa$Sgi<+||W+`q#wfJ4*=P<8{Ver9-s`|Njdo&mnZ_l5%}B(Cw}Ugv&% z+|S!FKE*xBQT?w?bm@MtoAPi^19(~EdqRFsy_7#9dgR}a<9s2#=R@9t-;en5gXw1< zJANU?&)06rf2apqhfy~={21+R|84sF##y}6ep_|8IbuG5+UKHQTKBZi&eoR!-fwj? z?uOlu#VMb3>V-J{#5{1r7UMDh5dJV+rQX@PAiJQ4<{My|Uye%?bqW2U-$20I0s9E? z|0Y)oc{4}I)4unwlswqk+ID;ZJ&$<5%JzG>-zMA&oa;g0glXU2&Wq@m_UmX@UjHQb zdx#gH;+Nr?H&20|QGA#`%!iG-@|8{592JdcBqbKaFX%J=41a;;#$Xlayj%^ z-sh*W;Gg_*l--I8{Ve;qzqh}&KKvy2`SxSVlZEK#I9|tj+xjdBiCXxaK$d$!~!3igPdeG2Xr42fe?9_8K;5XQoeDhrJJN z;yw}MYTeM+aE>_m${s+ikANkgt4G*1TgaWDUZ=31qh6=()SJRvfw#O5fgG*NfU4h_ z-aQ4sQR99{YV3Rn;d;a2m%EGR*r1SY{v^&n2&*YEx zA?^E`$9kQhkgzA15|tMJW6ZZN zg&u;8vvI}!OUH0tBfc8+%Ryg-{cOj3pP_H~r#?(Ov%KgJJk=M~>x|y^OXO8MUXIfV z<@J|rTP~pDc)lMv>;lw$`I6nB?*<+9OY}zIeS!CRpLqUE;Aeu~3;W1#Fxr7@JnJv) zc^~B&CVNGPzZI`6f5yq@0@e|Z2ULEcU&^1do@u>$UpM5Ff6e#Pz_o6@51)Eedk@Bw z9?z}chjm2wX|J~xUawJpmXFS-aqELTIva94p7+V_ALk4F-wNZpj{GxJ-2<+A05}u= z@c4euSLkj3)M|U3{Gi|A{um&=?e{^)qrD%9c(m6^(&2}HHDq~!szdLa3Hg9QSDgWU zPWq07zW^x@@ksI0yntVf$NwQ^^Q{@fXauAUXEWV#@n9C z-ocQU?7b+wq7N5z&W#zZ`itANV}%uj_Yc+}H7UU)^C>ninZgQyvXPzw|pY?PEF5DL0?blm}e; z{p(rCgWiCO`@d%0CJq>O^|L?buj#Y4AN_l&51{-3*i+yoUGp@n+pZgX-e3KzJ?8`Y zgg)n_ceRgDy@Vdx$AZtS?BMSyXwQV}URH z5uc%mkK+0MR>*JpJu~!6{im_c)an<38&i3G6@S1}!9VFUU&y;p9R8I2w@h@& zf6yc8BmYBw(!Zw|ulba%7n-jbu6)bJm-02A*+2YZ|5G3PZKUv5U#GsVCmH`zUa7C@ zUDkgp>`CX=!Qh|tkbM_WH|bx%7WIK&r5~W<(;fPD+m4?-VV!Xz^ z9-+QT9-!t~)&IEsaldms{q7yJ|Ba&mS>itjyA`j>ew_nhzxmi|A4ektZ(wIB67bRBKhJ>L+&(8Kcz^)TkW^Jv`Xk!&6{ zi@1+P{8NA3W4vkvd?;@u##ebVuJKLV3BJn9%#Ne%)^kAA71uk?H~4{hm-vQ$!twv8 zrf=DAn4hDrFYWcD5xD9w_~>30Q1@sU-y|LS%>-PEdL}=D-yqwc#l7x z+aC8=dR=ngGJ$)~$A51#odZ(cPV@L?_(6KXzglMiBkud|L&l=N=W*P%WER7WunlE3?oSSPgpW_4=4kXNn~o;Q%!&P&Tr^JIW9{FOZS z(|#E3mpBgbs`B0SMR|*MHXFvY;!@ z2g}2JJD&Y1Z&W`x&p1Adr|KPW%|F1Mf_|Mi^OE%dQ1Zt{j03I~@g6VmG9Su(k^PxI zDgV=a5g!io}c^nCAu$wKU{aXKjb>q^f{#R zlh?=0&ZGPyI^us&9y}=G+($f%^Q_iutc#2<>uczxI-AwIR2RBA9{Q;+5z6kUBj`UD z>q}a8CfXc`7TY+!+UQ6pA^|*)pG34`J-^;5((y?%Z^>;NQx=%f1} zz)9h8{`iH6FWTilz#H=0MEkxk&9}5~86Z8gXOZ~%ZR-c9`O-cQM>}!qFFN%3#ru{A zsPhh>{0P`f{eF?_yrIqm8NDI;deHg&hW+^5=J8NQ03`ltu+v<@iWGybK#CCRJw z)xHShwVwf0d@#@CXYg^pah;()_x;=|@DrD@KO1@(qH2;{WTAN0sAw` z1Jt^e$&o(zZ(O>fz{G}hD;#tY74=Z0EwDpo6kmLM(;Ji5h!21ZmN5|}c zPWtT6&r>OmVdqHX>p;reVw~f+pHIs3gZSw^0nCSf%FFVs8GcIkraU+p?;Nl7;ukT$ zrSG=u^DnY~Gtcks%Nx~S8z)Y;#8lkwN@ z79;4A;bm;Y{7=K4N>a$?H&;CnGz8{7jYe&)y{DwcT>WLuv0*Q=9w z|LbtRH$zX&yVa?#mh7WnY0sMdavt{O?Zn%3==00Cx1(NwXE|?Ak6@hFRn1@EX`Q`7 zdH)8_-Ps>dbtJ=;=fG9Zjsa7hRb7VP6whpbsr>ji-B^z)w;S)d*e|L7fN<^?*&g@f z;FIcd5w8=Q?4PZRlAqzO8x_A&pWZ?q>s|hQw^`83{*(0MFO$Ea@(cbqe#ifR_{)f& z=xeS6z?*@O6z!vdPb8?kSaDoeS9~AW&c~+r|5@Ayi@3@^&#z-V^Jv}iNb@PRr}-l~ z?D)40%L5!BzYK5ZvEtLj{U;`txD_^$N=RNcw+ zlHW7@M#RnQMYX=9dUUGbm-ef+r_y7lh<|Cn=20ehNdD>x{{e>oR1eYbtnfbD^>6v! zx$Xa4{jkdM&u>P2H~l>PdDa28-wAvp@D0cRdEgw^t{*`Meuj19%lRIcq3a-jM@c;= z>UwYS%U7U3=_~&3!A+c}nXiDSG`^xQ3SYJ#ZqhFtw`}`?%YSO;_@0nYy8B&>@d;l# zg5Rwp=nDlu%@@ek{3K+2j)C_cvwz8dy%?{3#?40P(+K}o`jzyD#dxi++4yTm&~F4? z_5NnhGumIk{)B5?xH*it(Jy?D4{`a5Eyo9NT;p**Lwo)`YWoxCHN-LdPAk5X&phIL zH|FPl?3?-Me=?}ok3m*IWyed|5!uh_(XlX1cKVpOlT z9Pj71{GAN&J81lU`D-)$yNUf5@&w~t-?n0Y95}vfEXE%s-}CR_@w#7z-j2_H`5u2A z^$qsDW+~zZoc0309r>R2GpXJud`$jhz2tmYalM{?1RQn(%3kD;>;<0Y$zafD$v^9_ z^9}H(_@eHL?=sE})<l95V)#k> zikn>b*&n|j6y!WJZa?nFyOocXo~1nJK_zeMnDTVLhI~!?uA3T<`Y5>NdX6|*KeS6< zo^Ksb>y!2e%RX1ZzLXb^UxmBARk-(Oy0ej@1sxHloZ(oe-tC#2QChh&oS=&n(5u8a@jGeZZTXJJSDgYK_5)rMo$=wgRqXH4-avo0{|e(|Jms1CsU3FC@H@HW z(?EYh$}#Tv_u+oV?K)3@J}&#K{M5b;{Nyh{&8K7F%Fko;FZnk+?f=4^h)-H~uLj-m zT#fjzM*LT?FS6efUG)t4km7qQ@t1%4+vDtye%~*C72D~je%NoQdGS>P;A7l${@*L{ zfrw*2&O^p^-pa-wm!Ikj##(yRce?6ywe$ImdoKx(d zlwUgqz7YO)y&12&KE%H6gZJ@WCgfSRe#{iO^C{{dzT14<@kRVyXTNGN>>2D*zF=O2 z{`Iw_Z(&D@x8@K068`U6iFk!Sx*v9q>q_|({L!EO+VuSUApajbaKU@1Cy2{e&)L6# zs;^%?8~Ch$rv|^7-~BK1{D^w+E7Ld+aQwXa|H^KRn|B<6OFp3I<1Xp}>sfogb`4TL z&=q&U)xcAKuP+lA&vATSBiN;U{+eye16aJmpcbuc?>#STR2BBO0#X$fHjFB|iNZE3Zp&Tw{HO{{c@kj{ils{RyZ%_?kVe zi)aUQ|0`bK*7%*M&U+>1Bkz-#r~DtFA^LZ181e9U%Nce(06+O3eB^(^g5OB+Gv5h+ z9|7^v`S@#g9B1eq@rgXx34X9A{H}V*e$oTuWDlY3z9T*1hc?~q4fm_J5DDfXK9_y3RMEpvw9|UoZwKSOq3@ISe>}g-A@4HgmG#!V zf?m#t%74vHd%Jdy*Z+jpi7cK+@IR^b;~4$twSO*COxNus>rQ)&pP1dtw;(w|PHfsQSK!^O^eHcKrda`nGo4_nV|E zp8P*q^E194<2HPbLqF|H*EVop1^)e0mO~hN8Qy8@=eXbTJ~q=s=iW>Yt-Eb}&EHLa zUCVv|)I59L#lSD(eh2bjHy(J*!`Fr10JYC}-F(nd|J9%Or5tw!=S14M1>8{e=5_Ql z>8swSeQ0D~9gsa}FYl{YBkwF{IsCTexS>v1?lSO@=X&7t|IO@T;PWZkQ`X@fh8?+ne_jcV+ zbrJ0)e!Ac{8h9G7dHWRn?3YU4e(|sLT`J_2`jvR8?|}R~9&tcD3jX#_^TgGl$Nq31 z{jU16{?bnueeV9yT;Owl-}OVRbEM^y=2|31<@4$xw9~HgZ^$-2~ z)ZXp&^25C`uGjaxng6;Y9?73a;huk$|BvebSo(+0gg$3{-pcgZqCaqdGfjU1P6WJO z=$ZOm3i@Qgl%HYG3FWiy@61p8>I(0A(8>2i%krV$|S$?{se#RGXZa_fAB@zX7|Z;|D>1pJ#ScdJpz^$>)wI>2|sCHNk7IoolAhH@vNUZ?+Q+|<7mAbwK3Ki{x7az z+*`n&8QIh5s*x}L7g_Pg{~`-MNB_Egn*GqOd+X;K^#$1z`|hNd^mV*az9jwAePcs$ z=nwrGu5Zv|&U)1E1U==Qr z=~IqhiSem_vUe!v3*&a!|Eg(ShF*!k>q@2HX6U8)fPMwN>)wB4`L_>{eI?ML{XOS(%!i}=IYNKJ@1CDkJ}a+rZ@nvj_kkb!&GglEkCWAnmswty_Cqs& zVe(FlciPvr_xbhbiA#k%?LTfYo?K4=DgWzuPh>d5`EzRuxZ!o-j1zG@=Lov-srpFZ z+8<(^_CtVPZ>H+^B_IDEpm+}YId(Awp7sQ)VV zpN0v)Bzy(q*}mf6M~($Qz-8)J;U|<2)pM4UJET{5eM9mm!yl8rXZ-qZ%LkkmeJ$v? z2c!J2$2vy&fa9cpq*wDw>n!NJcb2_qFYk-iDIfDi>-yLC#=5>1{=hsEU+`0WAWwc8 z$NxDoUg#(J7#IB7*sNqYQl7H~lQ`q1Cw^xW&~`@Am#pON4{oA?^94;T3DB(H-H z<(seeXB~A}_SLmtM0;D`Hs7$n&_CI^6#Wwai^M;2(((ZN+I%cO*_Zrv6s~$qKZgEC z^)KnFms#Aj-VqOe&4_Q>$ENkFH`-&JI;lGNGVr-y#OHI=r(5&v>GkQBUBip;FXjiK z;)i-eJ%{I=55t$SpENGNeT4e};+MTo!+u%&0zk$4BgiBA7ts0E1V87I*BRiRCw?#K zBdq`I-{vBAuq*O>*q%XL++sG z8RVDa`$8|<#plx0tKYv@08F^%A;w$ZKEGdp_cUZDp!TCYR~+C6%_Hzj{^Yd>E(grC;2 zC*YU%wO1tfV87fJo%4YH#C@sovB1Zm590O>I}z8&Z{XHD=H)kRdwzbyea}zedjA6W z*@&aoEwsyi^{2eKkTZw;)x7M|Is4##^+oFQch5Qx0Cg|-5u8J%|BcY|PT+SikDQk& zKXv~3NJTH_^O)>EkbWY?tuN-W^JO*svFi7|{_b$-H{5kC?0>@k-iKuCV#$9__7B^S zkTWiQ*TawNF+VHZ^`OEV#dz1ZoX)sZxb|Vd?ce?UT@R}MPb;rc*rWXj?6e;!hw-QV z*0W#zV_p8pnxAKi?ta#1E&B62t)2EArN8P1_^Ivy9@k%ILO<*K`ImoO|GEE4_-6(0 z%iC|sUY%>Rd`SND{9kYTGvPeHJ3fY|!{3RX@RUz8g}jj>J}aLd5%FcZ{CuaMXqx#<8x z9@Z`w>%}$cy%+UwH`W=vZ>V_hqCf4}jrjBZG~(zl`X0WoXnY~sy9qyfvdu?%1H3Qf zrhUU%>iyqf-DkZ8JSCibiSPM*{oi)mEW)S_c9s_Ef{V2y%pKjQP_K68Ct`{Hf6Ap!NqLKlPuE{s*+I5_vd+dBS?p#_L)?{;xID z0aeGI?e)9>Jj1&2#-``l8=GIYEqp8RE&T3;^eyx^T%vs#Cw~FTKiPBh3H9gi%-H{t z-XE^L8s{an*Iv!%Foa{G6BobiyAi4rz{MX>>*ufH{KWbM*hBfXpaXKhei(nP=ZB2% zKVtkG{h5uQI;Q+)j90z*)eYBQ@avO3{C#w%JxA%YyiXtcc{lvMi+dL8i+Bm%^EyUc z^6q*6TJ^6_we?GS*k08;_?P){n16dg&*d)^xXz=MJp9i=XMg2)=HKh|2lFA>!Fj0s zuwB}3|LVQAJ*l7bW6Sda{A}+w?yVs2*;t1G_5AA)T>FWSwVZE&UKhrlkI3^g)Gy2T z8RA*}5T4O5QGepJ=VP;uKj3_{4+qpf5be?%@Jc~X{Su#k#uNRPDgT=of5XcKu6ppX zx!^nJ`!49?b#dJPubAOC<*!G`8~W{$@995w+3&A>>~dZ80_}PZ^6Vq5{`gb=6gfngYp}mi&Jkr2j7r?u7%&P`FRuX%SZ=obkcpV0X~ZR$F6lP zl3(TbF3r1&?%#QhPFWt{DfZ9sCUMA-KKOs!(ZEB#pWl3JA>y~-{ZNKarvB1%zOA?3 zJN(!@&fSbxTi?3sCicnZKkn~EX82{NnBr zdH{X&9U$nh{S(?p{+iFB{y9tkWqx;DK4*V_!6ov;_iB}IfLaGKTO98porai63C=aQvC(;qqR6k@V(G^K0JxBJEG!vSA!h{6ByA zGvfdEmn{$Q2)s-5H!Zk7pzu-FPuKw{{O_$A|9h+5=OQn4Zbn>{4}fVtFGjqTM`+i6 z2=G5uT>FE}zN2uRi)uJutKN3wT5Mv`2jc|`r1DJ zzXI)x=e~f>8`{tI^j;mFqkH|q{u_BW7V{CXTlh}!-wAv>@a?YCr2j+mGt{{Y`X~A0 zl>hOlYk=DCXLWVR>yW>T0=o42_`IJhWc}oak1u;44H)CRJ_8?>oLe#e7T!mOKHtK9 zlA-G0$2T0$nbGLC5p{9YdGPT~uakiN^5eLF=fUR>;aBGc{}&c~zd7s%lpntN8SrKM zGxqz(ZzDdmpLLuNxZqyM+i2^-^Ah#dcW?_7?*2pY6@R>6DgU!h zQ_gyq^o9SNhvQw!1IRc1HtGWSzjfGo3piHb!@)QB`FSzwv+M^PEa>8gcF6;@|C;WP ze9-y^KbRlx`@$~?*Lujjk)K2Fb{>tp4t`4w)OZHgWtNJ`+OYvDf@R~-#+TR zh95UVU&moR@}=sp`?5pEn_kINyuP`|@d8{D|IKzhEQdJbNP83)#})fuwLiyuIkxZm z9uEB!Z?wyg=yy}&c57SmOEs^C#kaE0{;cwMJJ~B3_o*KTjB!aH=LhCP{yy}~u;pu> zWbZ?xeUN!NzGFU$kMT4w^t@vn^+Ema!~dj5y%-0s_HDh-!*=aA2&3OQ(!Uk;jCyaL zv44RpuE5jyM)dEMU*O-6CwsK7rri6um$Lk`0S5)|dw;&!>$m`(4ZW0K(x3ACJYNAV zpZ^UC6l{{dI1D9=!`w|3`Sv%l?4ISqF*pf7k%eai3t=uknw9&hs1lJqY|k;P(Q*=kLhP3<75! z4?2HmTgCxZXR7tg`1dZE4ygE^LmZ_?zw?2(^uhb!vVRsh{XOgT=iFM**R0Pu%A@|R zpf3l$9Q4V+Cxd<^@GC**{Ns3j=aleb;1`3=^?>b|Px5!ubp!c*Mt*1gg8l7!)Wq-F zJOB5H|9A97uYo@S`9BIx*);*aw2QyiyYy|&=Z%QB5d+IY>uZjM-$900^5r6T2Ode>x*=);mJ)Y?4 z(m569lZzZb(SxmtAnq{@7mfRQL1j;UTlzAN=!f>x1>RooL07(ju6g)Qu3MCUAHUa0>z|?4Yw8zzdY{I5j{Y+fSl8Sy{jYHe8%NN~{!fYz z1m6+n>$i_Ue?#XHe!ofm@LgHu-zO$*KOyt+6N9z~Q0x1*pR?ZqN6Bw;#yBDI{{i_- z{(!2dlicUAAE4+bgMKpTji3XP{@-(c8hU&!=zye8444im`gh(losjhJ7%}}jMtpCG zdN3mYk9%MDiE+?r*ErVY$HN=4n|W)x{W9Ts`H2aCN9EgDpCJ$MGUa|J_fdw@<2!c( z-*NxRunSQ1$y25S>K@~fWb?yJYD zLy)%_a??6A7JOCzAZLJnoxEm01CD4uGA_*5apa?6!kf{a=DqS7`piaqp;!C;Y17}P_z%Ze_iLDRCfSxv0vt&i_vd@?VxMk@%Li%ub|s+7UNWp zGJVHMhhMcWO)ffq!W}RDo=5G*Rd*3*+uOvx1mm?oHrxn2{Lv*lGI{4BUMVjco&CX& z@mm6aL+$@3uj2gz;#d8hI>b5U(L?p8-lQ*j-w&v`(J%B9pyHAFuN8V|pA3He`{dHz zjhGi#Bab)y{VV9P%KrZta;g72A6Y(d$pd~$c2$0opWnG>{}RTy)IUMh+e7%Ke%%^h z@$)*f7xMS;+?DYt-q-#F>kIAL@pBZwl=tEbKP_?G8)rkm;4j?cl=rg3xYsqFzb()6 zqKWVO!av^?aRF3*ezF^UG47Kmfule3jQXHm^Y@b{>zWs6-!Aw`udKdkpa01-u$SY{ zI3ECK#h>Q^x8D?AgIww}A97-S_^$b`HOZ;&tInu@x8sif$}2*(_k(uJ&kpmeyH@* z^R`UC)AY+*FWFAODdEU_&ZoC-)UQkaMA$zWcE`H*$w|-Gw{ri<{=9ESTwkPr1?%%i zj90!-;oQXWfV%hiB-btKJ%#-R_Oo9wqxjXnz~?IB><_%I^XXf+LjNtSYZ{lvTc2lF ztPkKA^{eo%J4OEoq+{Q$JO(LYnHKZgC-&wcH~8_=&_d&7xy@I&B$#D{{P z`~iMiR{*ahIfR&BiQZ4Y0oVQ#@H**`uXPFVY~CMq?N8r0Tdx)E^o#r?IBNdrukU8j zpZ4DuiXZfmJpBKz_~4u%eQJaCN90@KUO&-aasjsrxjNrMj_3PSeIVj^vFI1})laiM z<2PK$Q=P2z!}|&LhxCI#BtO${p`ecvr#|6V`4RYy$G6v~kI1(vj)OiaKUS4L^We|C zoA*5|pz{~?Cg=(8Df;&o_*~In`>u@NKtXR5cq#8F`taf7>t~ejw>1B{rlOut-GaT$ z|Hazf@VC|h#6$6ee}(6MkA5j`{fr0tPZaHg#ItdU&rGx@JVDnN&VTx!?;ANjlJk~{ zF4g}}QE$)z<^K$qzkn-#qFb+%^o#lh9^)J2pS?e%c>p_l*$x=?`T0L^$Gg%`dH?Ln z37vDNR-=vphTh6g zeKen_KgYc$>eg#E-44GGGcKTyMSLV5_#pYRzu+dGo7mpTfK5V-k9vgnN*oXE(>}RX zmpzale4n|byxNL9+Vb~MKD8ckScg8yuebG@4md9RZwLK$*GS;~ip%XT?dLGwabB-a z9Zv76iN4|AU43f9b?wC-jE5bB@DK0bdI0r5rm&kh?I+$_;HNurd_Tth7>_hQgZOT@ z+tdG~p6l#)GRZxFUT+;f9`Qa@KJE_>r;mnzJTEpPJ{#fRjV{j{?h`mqHhd2NKc@Z* z8qfHWzJ+s``lazWM;TB39~J#)g06iJ_4jxKik0`cBxf<9V9pNw4yB1a#)>$jM6@zZ>}*`qJnz;}A7DS=3daMU zHIBMZyxVpIE=7Gx>z?W$>k|DBc!&JIIPPfxmkM0_%2zOd(C-zIFLxO~%IWp_>`klY z2RI9Rzw{exvinQF@vQ6>4!enWOTTLTsn9R#{uj&!-XHq)5q~lC1>6pLs;?z|?PVH2 zaDe=K!9UgKLO$vP{Sy0kK;?Vp?-VN zJLtz4zZ(1xl7BtuX?>G^$B=*2_#{8=OKtz%u>Y=~du9GhaZlrt-Dy9X@>+RcGCr&>D?`$ zPS|k=`!Lf}KdqbS@A&Pa&Oz^N-X(op-?cM>ja?o z1)2XJg}e^Ej}bq9H||xxb_#Ja90vZ1%}2rsl{YU|^QCkCrS>%caIX;hq&oTd`Z4P` zzc`Ec(~)1l7Qf2^R37|V!|OM2`3Z0=`Xzj>kkd=NT2HXPIxkb6NU!RA{_l%;;5(A)f3SVmf?x8N z;sLpXF~83R)cyhO32MKQ;bnVYj0;%bq#Wdt>;#k?u4^6bDL-*96?z?>pL4-)4*M+R z=dbrTF9CJ`INc5YY`=_l><5Xnjsh3`6~pio_lf*HIN%qpgKntji5=rTPIR>27Tki(9fM`MJauNBC3q6tEHgQ=EYx1?4C7OMcfq#>>br&D)fh2gSdS zew;pSzXFza{>R2!%q#dM*-_fnU(nM&Ddl$>Cmit|RvwH*9*nr||2pL*+H{ zQ}O`IJXd`|yXsDJ#QgwwX?HeG>*W^pLp%V}eoy1F{fqWXpvymi6Tv6dyK-GD$2%U? zyiED7`=PDN!S^!mo2W;HUkN!K>)|1K@jmR8KXpxUA#4!;UzzWQ(&NRpUyF4rwWl~C z{?cbT{ImRoal2}Nzj)Q}`M}@FPYvZ=<%i_AQ$<{oy`x+wFkbs_JfBE)sbA~Ipx>X! z;+e*c!|pfSy@K@-cE5KH^CEEi0r(lTf9dO?ry%P|;DVc;Pw#;J%s0Tf!Vjv8TdX^b z7v6sldcvJo6@8+h_Y0@LIer255kg<#SN5F;li!r@sP6;xS9UH`z8wRe5=09-xBgSn^GI=lQ%Cd9fJtxxyQYbA^}dvHXzbb7^0> zz9)aGAN+qZk86qt_$B?szt*xHNe=MQd>q` zmD|mHuW;AB3io_!i@`rZI-%AmXrM4gL#dxickFj@=^eo=V zKS`eAfN`=9FzItL5sT+ z{yrvto8h0ZqkdKV5f4GEFM`Xp{#5@27yX{ZR{U-l^FvW{-2if1dz|Kd2Ukuf6V!5n0lsp z1lZ*G-&jN*8Y+J?{A|!w-_gDvxbg#dOL*)nf1?rWUCigUoOC{I`F#fDQ_9DrzxYG{ zZRuCdKh4jqzt_{m4|P8Jr+S>uX}a(DjXC&{cFiG==%HyP=3hj;~xEj_((3G_79)l01iI_9=b0D zU3moPysXaCj&sX=kw*_hPSR_T_?yE|fD5+I>$vowUe&$lrJ!rRX7$DKZ~A>-jMF;! z={up<9p_DkuNL-doyWLTAGQ8ua+PXF5{hj_J5t1EBrMf?Db$2sF3zt8IN@(cI~ho4fO zsGb2=dltu=oJaRyC&#OQ;1Rz@f^E9{4HbCmr+75aSuWs%&`Wwiue-%Kjk7fh(UePW!def9-nM!u_oEZs&75KPk87`~#HVGryfp`b%ETc1e!MrF~U` zX+NR)l;!1IA-^MkGkly|8!mnEaBTlfM+dL-?Ny{waS{zq9_DXYhOC*I($T z`jYj(B6`%ld$+yc%EqgI=J)#{zr#H*8gt1AMVc%~JyAI*~C&>lweb|KGAB4WKe)#_ro^RTp=%@QE;;bXUXM$hw zIjQ+rjrTbK_(A8;OBz2EdJK7;`mLehKZO5Rv;We1mgfHf{OZc@7b|-^{g>J&+WvU5 zd|QwH>$S^~pD|zYeggf#JT^@H;{1bqvL~4z75`3sQ+v{P{R!i}5&CZUI|rEWX+Fts zz?}!Xq4zG}O-<^T4$|cIn5d96ckGX!* zIH312ZT`jlOzlZu?n9qM->uMh%kyb_&;9{aJ^AgJr@uXMays~_zM)fP0IS2dX!~2RR<&6z422v`@_L`4qS8erLJxzvdnE za=iKfo}GG%-nm_U`+Ee_JAIo{5eOWfIRzjbo-ym_=>vgqR zyA*yHW&6SRJazdq^M7gA!l_DPH0aKLR?h2VsZtBwv2c`rQe-<_FrR zBM!;WODQfKQf+Rjj|d$p!8&*?!Xaiw}$g?x;WOBI)3n!U?#}>~4_Kcy4+vv?nwJlY z@e9Ns(_i&Yd@XmLc>=r{{L*=6H1taI{0!;YK1O+)?L!7)e9}jFwr>$$*;m*23iorJ ztj-Jvzo@4t`TLbuURa0yhJ0`1sP{Tvd2`kM0cXhXMaJJyb+i3mM{1A!M!zRJf9wXo z-S+PxlKrF9_vS_AGxcCT3m07NI>$UeS)fPJ9hMBrL)(B3CJ^5b{sv5p{r zv)@~c5zl^KFC{UeJ+Rm%h*pLE{Old$h+OZ6)&`l zUTfLkfTIOJZDWB8$bL^juPy0;Kk*DV04E}ijTgXrQd&L+1`<{fJ<^Nt`??~tP zy5zlB<&)-jJ3mu<%Fo>=%+I~Z&%I;kXOsE)`m3(TuiyV(iPoLp-H!Ne`@ON>eHi)r z5c?G7EuUi;I$!Sx&i|=8G+(=J72}<+6~ED-tDZv6?WfGo9py*$o=Agv$T$};ol6q* z{;ImC(>@F161|b)9Q>6>pr^WeiS4L^$}7O_qM!0KtCJ}&Q$8vWQRkJ%fX?%#`B6^D zPjyLog}N>IfTs(2MV)TXgO^L5>OS;J^C;;fe{nqtf5;x#Js$d{{u5!3=$P;F$D8hk z{de2Hk5A)uPWWBccjn=a_vM*Cmz77mk(ax!E6`8-9`qZC_$$u9R| z{t4GQl-Ylm^cSmnD*M~>KDDQLzxQPG{zA?E0Mt4MKc)D|AHO%}=lg*A-ow8416T(! zKfkDndoRH6>4!hyzu(*Seu+5!30&(F;C#d-<+bMP?_uAie)rmWmg1;>@IyI&q$k=B zg2(MIy{hMw@tnV!pUU?v9@=kYc#@aqNs8l>j%(Xn3;%W#_tdCeJ##s;+*%uhXY=W_@_G5qy8~}{~*oZDxcL4^DEWk zgVv$4{kZ)vNsm70@BGv{3A^QQ$ZzXQ|77|kISHnDskmkPAmLTL_dW@H)Svt*7cki| z9(W_NM zug_B#s=VvW<7jW^aqko6G2%u4T~l%RjLn#W>yb|KVJFT!VCt zGrV}jx;_y76(5Xi7Wla%#w#B&PWvZ7)zwUX^O)mD3i(evUiX!U?rZF)r2o^(FYQ0B z{4_qrV>9x%qmFU@L>{L7k><@G@w`lXs(Ph$1N~E-N%P{b3QY6uWIL}t|FnJ~&pPUV zwhtT*|7c!g|1{W%`#q>^eH|b@TNl=g{>P=?k-ojIYk$=1_g@~Y8wY$O`Z(q-;{_ zzg#y5y)ORon&SG=HdO+PmPpTw_Izi4mkH~5754F|tr zKZoA!bKM6#b3~oj@6Z1!##7JTC5(gK#F_uVwGRcHC!a(5O>6vB`(^j)zH?>T$ELhZ z_2VG?Eb|oeMf42%y!!O*Y5$n!qvi?ZD9@e%mh)G6hjA|#Kh5*Z?t$3XYJQ--gnf$7 zsPpchj<)mWApaia|CgIS74cNw)4xT0HLo*!AB&?tBzuOHZ|5T4&bcn{PDkG2_i$0? zqMpP#y_zST^Df%k^KQ63KGj3zOQHkHo(m%}??zBpF+aYq=J^AtdG~#MzQFcv?7s{< z@r|Hw1f9?O*l#uPrNEc=;kwuSKF$O7Z{l|Vn6Ljn`~f&3|F-;GV|OF!!G`t4zIU+j zf6AxCNAv&RpTm1e<{LQnxt5pY>i#*Ct9@sN?}Z+Yd;C5g`l`+Vu11_h&(^1er**17 z_;|f+)mw!g4}a%!nkRS7bFtw-r0G{&i@chAk zQR_-G&LdS_o1)+U-2U$Pi8DH%? zviATar^4~wQ~C$zK>2CPzay@2f5$PiOXt92-~*k0L|(%V=ofX%e#q>;%DkUjgr5cL zgR5ALGh)@ke{2i_Zn@-!;E*k3qiaJ9nu) zedi8%gMNL9-@OVbd=`2WuSNT8K)mUM`ceCL>!1_z9w2bRd8|hm*Tea2sQCpuJwNz8 z`A)z0GY=kafNrRL8T8S;10cV{MLE15A)J&y$73IW|KZbk{NI(v->FR}812g6hj;Lu zE6Yv(JsWaO-|)H&KWZM-!hgV19Zt~uq;Y&-lW`sQe9Y$ip!BQkO?i^?jPa}0FUmiL z)=T{1r*q^Bxy|6?x?cV6F7Z)ZYXjB~aJaB_;(MUE{0D`;tBD@%j05|3)Jf=> z{E_m$quxXRq-V!Cw2vpgxqc}>K<9T=^LQu!rMNBTf^XML^D!^y)BTO_Yg7CH_1x|y-qW-Hb;Q&8 zocude;JQ!C`fIe!tm;9BF(A)7Dw?8tx9G~jLDb}Yy>6gDb|4s@Y@Or|0kX+p3COt!s zR6q3nUyN_C9{$OI-xHnawjO|K{*FW)vwryQKt3;!xAt4qTi~OKP6$1`9=6)`a3$Is z>es5RM*r2&&-~!8&^!61^h2^M(R?6L|qVov#mZ)!RHjvGxY~lUT5<}{`}T8ubY5#8eh#L(X%=u9M5HBCtzBK(*8y3 zANnVMIZjQ#x5D~G{>U@?6*%<-KE(D+&ZzWYUdOs5obxg8h_~0raeVI_<6&37+MD%c zXOcf1^yF{8e>!xT^QT^W-8R?rzyXPOv#z~rL-M;`*);6gGyF2>P5PIup&#Ra7IBRJ zgzT^UJ1c#=IF9WcC%ESKkufg%ozVUb{1Uw{_-g-ye&_b#;P)kq?hjb@pMYJj@5OuF zveWOY3YVR$Sbt#e|I#b^>qlp7KXK5B-xQs85@%gtf9h}in(SwP+SQAA1NN-Az5|{D zp6@4gZh##J*`qwo;;;2H)fqtDBfj#1TR{0PVY|J1#IT;*k%H-66HFmnbDKO8ZsCZ)vnjN*72 zQE4M3BSU3fRQ3s<*o~4J+Az-;8Wz@Zr9wre8B`n@MP+7CaTb+|j5gM&hwV#A8>5(1 zXjoK};>IRl!|l1Q>;1kthcldknZ|aX`RDz5-}iN2zwYb4KlkUyVN_1_uR?y%$IN#5SF?EwlN=;AIXc>Uz+{@!}xo!G!ttMwa@|`-#}~!R?D$ue#34&3|53q;AYV*>ABw>-$`sSF);p>>)h6# zi$mJo{h8%2_C17rM zV_bh!d&TU`E4uh^?s z`6Hz#PJiOMN4(6Ji&Ny6uDiO$_heR2Di=Rbi{t9#T(k7KgROJdFIdg|*?1RUoPV|t zNxRj(v$I^@^jsnRM=DRX^vlg_!aBhFnYcJCZ*ludOOo*}PB*V^omhS~PM*I^PU5uq z1&f!{=j0tq-V*(6q&{1(&R>zUd6RKwU&kltFHkOa%^yLF?;!Gtc1=ERy!pSjZxd-_Z-fHLN1)P^Zuls?Xqj`?KZWes8eHQn_;;*aU7W&<;`j>f*5N3ZNvD(hB zdOp_k*#z!~BtEzA*!f%9ll9xa5_g>_6#wn{q1v}RlGC0$=S8$XE|1wJ&;3vI9j=~y z)f36a{0Z9pX`bWTUEZbN-WR^c`;en^B~Hn=pxtjvd$0KwtA{@2;ye~ap&xM>ale!;~MFw)v?&KeITw*Q0Fmy zt}gIAF81A3E%lgxqNmU5C3(Ik=tq9SeCIhX*QLUdF{v+Lj_I4m{D^<$R)^EbukgZz zII5}NtW1YxzSNe~<#nxMs$;aO36q#Jb;y5?n zVMVfj>M3BqJ$H)BlRZCrO@43B{J84|{H6cr&$G_ldqH_WN57jCrQe>rNnYVc8J<^b z+}bZ_@x^hgv$7)e3tFAU^_QC8b+min_Yoz`r<*T(PkNB`HpF_selv~Vm&NTbiR)?c z{+8qP`v^Qw(*2BopFo}qS-!+?m$zERYx$9WThEf0cD9eYx@5jB57O`ItU&j7a?ej(9U=bM--}IFMLii(==cbqHwm^F4`DqFc&xA8^z`xU}%UIcAkM9%eF zK9|&!__essD)9Z8>N7h z$1nP;`B7en_ypbl$9=<#>kZG_GA{8t?d+O=aeg@Nu7|FU_4(&f6`FTowc3xAGQXwv zy~Tn3CG0Or(y#9uR8KMMBTOESakJy}?tJX>W!!x?BUoeOw4PLdnYUK!f1yRj z1MPefn^7wzG z;`$>A^INW5=E z5$?x=8+1K?S>7w?Iae@fzBWVshB8lk25EZyn=+ADlml1N!AV zH?tShTc+#sPgCL#Ff82X!@5`0bsf)x%W-eLjrR%a?>l_H5^%vp`PKDnU8$!31cyH5Ej(sq0yo-nSy$R#_ zT~bp2yl%f)rT>ZgytxVfCB~n~rw%Ufwe=MjpY4+|j`tC9e1`hme2(dSwsd1YTe`g= zn9rXlzdr-kC-8D|+aE-(afN<8*Q>sz@*$}g zZ4Y_vJB6D)@JDc5{kgfCyc&Pk?&eAIN%_0W(%;H{!3pD|=x-gB`jq}#iopf@jrWrG zdghh4sXsvde&dW!7sx&n!%&IR5_);dZ@>Yq$Gd;bFBC*Iu7sw?X|sL+?L;c6}Ckt7}0! zmkBOt{I^cYby6`;{2@2Y z!4E;pN8Ix}^E1Y=pTy;>!1NVSUy;YuDiz_dz~3A2tu= zi~8;F$ml%l^9;oTjX$9GZ9si)cd-6$%LmVe^4+7^lYCh|1l@VWIp$>sYHj@R!_B+ite<$Id> zo|gM!Ylpl)EdRe3cs}I4<4fmD&XMSM{$Rg|ttaQN`6K#jmgaA<{x}}%aqYbLBYFiV zRllH(7px<9{rY@E`yIFIvCOOfzFOjw{m~Hp(r)K3L1(YS`n5h(u1KywK*te(tbdyQ zR(Empg7}IS-~>Q3uT>M3sjT4{V@eU@_{ z)%$YYPxb#bIPTNBKLdNTp0>$z9kmnDb1^v92j9O)yTvJp=OcS%*OT`%A(HsGo7jb&Y=5{bd#YzbuOv?Nv+t z@lBaNok!Ix?*kHer^?5zd$V^4yeDCN)b#go9`rh)c6-FW`d7oe%f6K`pZa%5&p5Nq z=2J1p=DAJoJ22mF-c>Gs-XT}Wd0g3Uo#m$n*GusS^RMH?j$E&uzd_p7@88f4Kg|9Y z_4yh6(Ej6;vku*L-maH=-B&&J6VNYlnLg!=&-_p1<_GNM(qDJIMg7J0ebR#s?^S+qZgnTZU&%am>X@fGZ;$TpC+R+9=q=NE zkJ~Tpek5+4yX!sfFXZ`W!aC%6&>Plvxu15=(Ml8Kn$>Qo3+Iwh7w*f%j@vige(89H zj*shar+(`fJGTD_I^LAfZ_mGDapRmH>X!AOess&ZTv67e@g8}8rM#ECSAO5Tlou;M zDayR*@7=_|xc$qnyK(wk68in7uY~cJ;9M5!qrE=aAJkq6c?te*WlX=%u9I=U>nt<* ze)tpW_ZHcH*eG1rL!&$|Q+=_1kMRMSA8j9y_?G0?*J@nrMBD3l&MgyvgiC#3KI*`w zA9WIw&q)|>{qh|{zK%aP*E^y8qQ~8Lbdfv#CMW$?H?i~dnl{rLppNBuq4ul1tiqw@ZXdZ174+jX7>L>_4Kxv`f1)Y4C$^ET@LBcOisTrXHJ z+;H=+`enVtP9AxW>Z@Ilcz_cUpXZc(8K>y6IF)OCD(_E_k5Hff{SEa$ zyu$b(%{^10BqwGhVlIKIThxoZ4Y>dh|5q>*=s*LNoMB8iRzFm>) zQBdvq+Ov`GNYwJ99P2MC`BC{E>=%NzE<~rmowbx4CD!(s;efTeO>_c#vOwEkHAc#*#u`ZFW-AnWJ5&toT=jwn)Q?EZq8;;Bc#X-$^x6IN#+bf}MLp1uJgJ|`4>m5|-Z17z>|x$oWZy@8 zE-t&DjO%xD`W{^K;Nh31` zcazWZCHt=VA(*dr&s9GRz1A<>@_6oq4|U=4Hslq>c0I6tNc^#LiuhAxbxk{>*ZJ-0 z$F3XVkK2#zT%vZ=zQ`Bqc)^hK1LzyzJfRL|7WYN-SNA{mV}7MBbRRfc*QMb4IpTn! z?@qa{>i(tuirQ}|{hDX#N8QXwK3Hcf=*wF{pQ|&cFHh(9&M^H6&~8}lX?tKX9w%S0 zf;{Rq=I^1(=O^TMCI1f99_nZH{#7sa2hU?-`-<#)TK_f3m#!15AFX@0j;*}kEE z6d`Wq=2wxNgU}B7BH0gw$LX`U;`CK8p4`J5=dDZF_lB*$<@Zij|4}^Oa`sN1Uo**m ztWW)aH{hT1tH}JwS&Y}tVX^V{U8Ba+moU!mcjO$5`(gcVU*}^&{;r3i+*ir|aFFxQ znC+YL```rsY=1j97teVu?^0)xwpiYsJpI9QaqUMv*u03pC8}Ti!}YArS95TGQYPIx zuzfUkZoqq)3a_2#MW5}PkviY}!R6E2=%?-PQm2&`?}XHuuCL_#^_Yu~@dS)FGrne- zKN&B1!SyB=?~5f~&Zjq;Fy7|-lswe+DS6%j>Vsd+@^^?dZ(8p_+XqA*cI7&5^(x%@ zhh#meT(j5%_LAq57cQ+6uiZCEyymaqO4m!UqM@!f-VoS__1!LH6JI>i(Nt458S$+P`~cdeG#b7QT6wNq_|K`-iI z2Jb6Q=~`2-eoGz}<;>0uo*PMfsl_$II7SZOBiiroV+D)MPB-p*PwAHD?y#GO{Ce}r z?R|;ly{ExBW)eZH4AbFo*h3_g%sdzR%;j9h+}E|A-%Myv<`=eau__ zV)=LZa`ltTe&lsQ>O}mK`NX_W$bPnzTfVmTvA>V?(kJU2a(4ZY@g3A_`nuDU&L>d7o1UhEBkH#pOWq?c3nHAE4ARy^ApcM#P4kS>(-^6U!>ix?{@AL{{$PD zH`-4rr2Ra`cc^)q;CMSvh`p-S<-u=$iTq*O#sAYMr{3TBcIY`4@!I|+JZ$Y% z*mrkSd2N5OI(74m{l)4~>w$4GPmA^K))ne})%7IjNUe`ttXIKK<+1aFJf~aQ@9N9h zuX6Rs`g8kFi0jtQVOyuT-X`$>B?)I;`)vP<+4lu$A9*U1_t#7O!TCh|V?VA-7tF$c z!AA3U<#V5{!#KN9#%c8_?QY+5`%}*<^-ijs%s=->_%A$|unrRI!{nD)2mXZpB$wRU zZGYgt#(Yl$@f}HAo~-_4Kg4~S?2GEBUKh0rJ`zSIN0oNq(MVgHJH>TH}oU)FK8^8apW{ogA6K+9)o4f+L(w7;_x{et;A{=ITPp(y7tn{VUr z&-OiQH!k13soqx62h@Givmqkm6?NSxO5Lbjr|M(Bq3*Nn*YVQ7LFLbD!?^Rr5A7GO z`aQz+{>HcC7Hd~*OW+Gjx!3aT;&IQb9KTARgG!un`?SR;?fKg7k@GzC`8?{M*qe8C zz56b=2GUsc#Uf6sK z2IA*Q=B0q=>%0WCb+w}!{^+^Q{1L7w-1hxAzY_KX?BmjJ`4Y6a1zo=Mz4V!1i-_Mq zJ9otC%d`G^u^%rdobn-!L)O35o#Y+!k_-L9&5oe$6M}Y~lssXcD&&5R^Tl~{+@3#* z{J_$FpFUrR_qUY&wm*2vm!r)OuDuwx0<$E%ig*C%|uuSNyc| z|IRAfsfyKy#U=OaChvalSmASd;6CPCNYH0-UDd*QYw^!=JnU8Ax$UN~>+;nK?MJ;SFEsfQIge|53GPqt36Y2F++_LSyxKh9lZ|yEX#R2j zSL@FK`efeBFTthrjW|>u`Hxt=`F758o$B*2%~vksRW$BFK4l)NgvaLdKz%kpu6~)f z1MG<&ThD@+j}Y>HMhN-MfOwzCOsd80`{oXknU(NpN#D3JP>_2urvVB4Q63+dh#qA&5XG*!> zazBxCtmeNF^~L=`xz3l??Oa^H@7}USIjj#Er+LtJ$fw%*P3i!3#(XJPIa%*^ecst3 z&z02A7O&NNY`uAL9h1K!%=M1?9-}^<%VZrx-;6wevFlJuxysA;423{^&#ij3esrB_ z98w=X8=SSm2o^+k%==hztQ%{Kg==`Z2Szn5aam)HN z`FYBPrPWF?p#!#4Y=c=^HVwQwJ87pA_h-@%yB*~4%eS+O zoX^R2cBj-VZzm<6_H5pF?xMY2srr6T{O}Te9vi=#{kv1Fj&}B7K6dsJ`VygU3WFI&K30>4g}ZpgYr7bo1)Z>@kV_Azq8-lW%~M=cm0k){aigF-u}h& z5zhsDKCSzTo;ND+{~6nPOZDry5a$qg&T{W<%guiE;(Gu$KCJSw=UM(yizg!Ws&VPL zPB9|$XI$1X?7H!dve%#!<-TR9zINrY`${}V5MGV-i}x&c zuC{S05z{wKKc}UhR`LF9Q&jS#@$}04C`LJdz4-2+OX676D|r=e=ku#%zc6{VC-#M7 z{$u(OS4^KhciH6p>s=flHvMnJIQRuWdnM1{Heceeou>sIcbM?pS=K-D0zJ{ix^eT^ z?B;b*&gO&hXt`(9Td&JWFJK=wW9m-{u#tKJVNzhKro#`+1_d{20-j?bM)6@_*m zm(aeZU)$xmg`M{=m`$;Lf0^f;7U#$Ey;Eu@R_{LECoF!4Kzjjqf0AG?Wmw0Hzlrs3 z`sMc*_Pi!}KYm}Zsgw29gWo+Vx4P<)=P%3LCu+UAI#v5thd}op#^&p^TF42mY8{-G z4L?rP=fN`H!fhWD-lhBKeR3T#dlRA;+|3v2U*?bVlC5?~%|MkQPYj<%u%$EBA^)ohqh`Wq&=sMRpW&bz15~*8o z$O*T8jZ@oe@f_p666R6!sq-?!ercyaSch^>(SF&#+&ZeW{K?RC>eBir z;lIhbdew20vaU711L6nJ@^!zfBc5}F+d8{y9{$E5o?jf;Zs+CGqVy{&_us0&ZINSs z+VC9g{zhq+`MJMQ-h->0d>?22wBq@QtWTFWdmek*EUtIq0QJq{y@l|6o$m+QL>_4Q z(eak=+(kXuH~5@S`n%X~`IGBciTN=^{X_EpM)TDwc~!mYujA_P5Y7$OZagJq`X)S4 z&TpRTbN&j}X}|1y7MEbV>h0Ei0j;jPWgd*T$T?K?j){H6aTy1WJdVryQ~jmb7X(c% z+=aM5t$7lAf+jERVY4>|zq`lqK4NiPXi*R3Ltc;iYk5@vI-pND?8rIT{OrK~7!KL< zL|-@0je<50pB})zFKFxP)AGLF{3^q_;nQWlNsA+d`4>GlZ|c9=|8#gU4$GVH66Trf zi0@g%FV!pl*>x(m&Y(x*V86|i&Z~043Cqin*w;99-W7+v?Z$`Y`+oH!=8x6UVt&Kk zcJp82)%lS;C(d&V$HV^s^zRxKd9fpC>u;C7|5iSZdD^9Qs9gVlcG!30o83C+b4S|; zC2z1Fmp{vMK)wUS`ghMM^T}6>wl2cD-Zehn&+N`dUK{e@r*O-QaPu<`&Hp&=`cc0c zr|KQTeaS94A6negk{4~C!23$UPUB;EFD>%$XAJxMt|{hg3fBecr`{BskA^V)3!}ao z%E-%7?0lWP-#c%1M_Hev2kcLJ9Y5f^jF^4e(>%#MN*>&Kpjh>8E<_xHcHg+AN8#5yN98%z z5_!BYYp7#9b=a3<{^0y3`zy?%R82r5{*r`nnNsVK?+$eLvPwBqIBY=og-^ zC|v)};936`6gfrR&&Un4<-P#&$gAk#__}57+?QbIzNL1)ul|Vs@AtrtaA97uben3t*{h)o{h|XV+N7{k;%&XN|VT<^$^`UuEZ1LOu&z=Ry3$7&JYxOZt z`T3RP1GO5T%d24V3f%S+@zeGvLCjYd{qDkdVTC>H@4nDVOBuVCt>@;$fWJq^Ie3Jur(XT)&bb5bYLpOz)p1;xG7z^4X*FUbC> z?X%FEo;zhcJ#Pc>EZ9wdUl;hLGr%uGPx|&w@P}l-5IyPpWZxDHzNHuVxmMsW1BMxa zTHpz?--%qtDX^chg*-w&4L>qB%Kj>HnHSCjs}W!3_B`+%kk9O|GM<$=20R9FXPr_4 zerXtZMHBD==*{}!fbr}c#F1TI4_=EnvTyGP|6vhudIb1;*gtAbw(<2_;n(`B(ZBvC z=v{x`Ao_>=z!zcv=)Z!Uqt8X0M_1%n|IwGv1KT=)@_mBFe{@#?@L|Mp^f>%K`qepW z&&h>4f!WJz^_5xPrWVzehcC}wWAvRt{LDx zl>a8jf43Cv&jx@$gCD0(!QWGVS&#PLBhFLh|HIKbIraZX{x+448Q*k4JMbeVz)wSd z6X$W$7ol&{t=;H<0r8)=s^B1b!*}F8l}hS=iEn_9wD|L%F~g z+JO7VfD1vxGegMZng3j3{H%|Jf!EXnzcLTJm;8wwxelZ(UI)&4xfA_@p6|==hCQA& zGhV?pQ`y5Z|0(_Qow(vxgPv@~#~Q&OllN)XFXuVK9PqEoeT(w1<*u7kJ#U{9{faZh zS>k-KT-)V+u=UG(;(X&8kG9tlBgDp(c5Qzr$G=nK6293()O;wH-yIlgK9rXcD~MIZ z8gGI1>pZDkt0!Qn^Qv6)qA2HF!yfVhqTDxH`vf`T+f2Vs51{?@Tw;h=gmI^rkyjD5 zPQ>2n5%x#PTZtY1UhOYx0WWGJb`ZOXy(zOQSL_EbW?qVQooc(*n_>udio@i^+YUQS*~tR~hHBR=!@(hl%TyNEqR#(C)|`8aXLKd*6A zF^;MUv{x~XD#lU8II0#r-70?>{l1KOxr}+ajDBBM0X>(|zsu^$qr^5M{k@FyavA-- ztdIQzsX?{(53K)xpg;dGhyD+HFi#)OCC?*BY88C{(QKToPK<` z-&1Vkr`Wz<_H)%Y4_@P!CqTk$!sOh~YD&n^i5&`uRv1Ge(ZjF68bzK?pq zKg#<3Xf}B+;`wMEIqTu0MdZbP)A#W@@Q+74A=Ur!R`PaYr>EN52gn)!$LYt%$JjnW zoFdK;nU{~t{kiJdQi%2~%*U2e=-*PqcKWj=Le6|_iITSv+lU>+Zcm=+rypDB$CerF z-*o}V)nyZNiFw2jF-$BXmJrK*1FHX%9REq?_mdp|$q?*+vI6p-tnzM9`T7WWePhbF z@+*hHujF;)$}w{KeI?h^l~Zh=CeC0!ubd^H2S)tFAdzuJxNk=om;Rl2wI3;g9{s!7 z#>m0i@e8EXXEJiR{Fb@*T1Vgkh>~CWZtgIBd32? z$#*8I=c-1wM`1_5r?vJrzx7{J1bz+UxTcJpdB3KLypG6o$~BDR8rJtUjN_Va%JmR? zVfUJT@@Bl=BzO zXFIPyQLdM03ENALviY+66!Bvl~#rCb$%unxr@JBpeTR(e8JYgNbcm9+< z#h-Md|0nVs(KzRMGUsh`%3lk5W(}$5wK;FGa=RZ@%)$7D4MfiSLWn$!^VGtur$@)V z-sZ_Ml-~&(4#1z+M5d3p_}JyAigr&hi|QiT6rPp_R1y}nT! z@AW~C*EbEh6y(>NQsQgZajAolONCu;D)Qz{ov`zUcTKt1?5rKPym;5j??QE40Q2b$ z1Tj94M=bEvT7M0B1F?nJ>6z00b%@`)uFE&1JV^OqomcbW&1mxUDQ4z-`VA*6f8MN0 z=*g-h%6Hk?pVdL$LmVKEc^kC+E*j@@h3btEaxM| z5xc)X;ki-rG4P)iga51s{FQF--w%PmB;RN2_?P8|fNsf2say`s?Ih4KCIHGP%?L;@DDuUM)|Mm-lt? z{{M0zLB7q&dq*|@@18`y-#z2C`70g{l9?zU(hf0 zqT|NB7RQDA?Hupr{i;{)S8KGtycv4S8DDw3?K9rLDb3m-^Rn^Gh@2+{t=8tXLYy0#Z_}2>9Ukd)h2Jeja|Eh@X#c2Ocx9oe;{*u;#;O|=5uls@U(+0hD zf+ru_=u>}At{;&7L+~>rYnn7a5&3;5Ic$8L|k9I5&8Rarf-lqOdKVS5#@Uolba#V66b+iJ;VSpo0vxo5etco zduus)6;ZycP&->A#3tlH&gr&JwziRXk@t|x_gFS=$ltB_*&MWgb^|eA=1JR&$aS8T zm;2@wo2J2==Ah>~KlpVyvBL{U+w)rT{tT;if$Jb5jA^3yI9f zO%>!d#7K&rCvIU}x3ppWEgk5;rHj}@93T!+j`7_x$@Uqx&ymybTNY%UN}g|)wrnEK)!w!cIpb+7ft@zaZyV!ktMuiooo!*r%iqz~^SXCi33(~8jO`q^ zt(xt1Y;Pdv{BL7^wlOcfAM(n5kmYat2FP!xpWA7Fdx>{W?cbh_{@Zhjc|^+J&bV$b zW_zi3R>$2j34X^k{JLX5IrDqR0{ERlVh-f)Oe%y)Y-d@?qZ9Uw{yxm#hn^rr# zkMi!I{0`=A2lFQPQI^LY6&SyxhFIrmxA9ZtoYx(*1 zymoe>zq1GOoqgm3#3ACaFRFI$tp>lB>-ye0Z`j(Y@7^xxxtDtG9Uvd!xG{42e=q&N zcOLy+9`tw7?=IG37w4_3h;k)F=C=#)6}(+FY>yD5#1>+Q-G_SbqrLk*XuqHNx<5cp z`}gO1Y#)6f7wr$^5kuHl9;hd8BGS$S?cQ$93-8CgyI3c?3Lv+u$XlfSyQyz?2<^MW zM8>%r_p{#J6>MkzcQd}-@^?~I&z=qF-$OlnsAmuL?4h1L)U&4wdEQgQadmPo5Iql4 z{z0zy2dmKkU>%WmA8hnQOun1#cn;%zaEN@2I6<6(y$5H=XNmK`hdlQD&HE7bJ=Dwg zevEr)kbIbYlzf~>{SWOY=X!m}>0dzm!+v5m^gPVGJ)F;W=I>$7lYPe^d3l)o#lyvv zEAusI{EslcM`-^M#`g&0dt?E2_69I+Z#HoQuG@R7$T<&t>pZ2ZcW)Qldx*@(-aeo8 z_p!Zi0R4T;d*2B87?J+0jSG_@f@2e;+L(=lnlfMP7qA9&IF#l6R1Ikq?lM zkkjs?yskbLK>uT0*N=tCnb*fkaew<5=j(BQ%An@sYyFV>+8}Y*-=gi`EC>H))!JI+ zPtu?Wuc3=9VM&FpqjhA5Dcp2_X#z%0UFg_>eTT+8{3PJBeR;Ckp1XS|dvM=9 z$vphj4|_k&gPxy;yj`mIX9IHIAUNeg`xNs#l|xSXDdu^qgzXi`^HdXgGqH`>No3tk z(eJ5IuU&s$V4N>B`76}^FNZuMioa?}@hJYL#n-L)yKsu#f6g{~nw8J)hu+ya;4Ad| z75e>(-Y<#VDtTuia1MT-v28I zZr_23KmQdbFD6zHYlsnIl-Ne>BK8r7h-1Vl;wLVW_j(K!F_&i<4eICrW&x3XB^FYq$8D;-Caf*of^m*pNXQdpz+58s zSD&|lJWNC$ecn>?N@9&i>)Pj~eeW3CY1cao?xQ_lHhCT~Z0p76t0AwK^(DV!E!4Ud zJWch*ao6tfG>u2)dJZnPDSGh!@XXTwP2~DJv3UFM)cnTzv$+?1v#v{NKb?N){|~18 ze>%E2V|aqi=f=X+YMs|j*l&EBS|uLk?H=9#e5d7l>J6LiI_NuX*xqyb!o&7F$+x-I z>e#orj@U?)gX8z*clr8H#(O+r)9dk+$a68}wd4)tZRG9t+|1*{^Fz;?PH>N2pQV^j zm2GNTzp+I85WZ~?*cBB0!uxW71H-^Q-N3z_z{ho;5xK!?Vinsb ziPLhQF8yEKAlFmXhr03X?qL6Z;8(TorGIx9@N0Pf=^1PSx9gW|8`6MKo8A8nr^&H_{YM8@+Y zJkRwWUn1>ZkN0T!?>(Us`~I7!2I-{fc59im_~aB;_)6cZ`||!G2+D1 zamR~msxNUI?JYM>>%p6fJnD0O8=sFnd*2K@-VL;~LF1Kt?yrK}8qJH0&*%7j=6^r^ z&6{FB=WG86#}A=BbsF*JgL__Ip!@(ZrHs6r*g*3ejPiCf&hm#5sN2mvQGM)>2PsBLyI_5v!M>N#A0PHBf<`u`(p9F@%H-!V@ovwdS5`Zw=~{1tew>3d=r?ceAn z4iTB3Z%l(fQAgywJi$CaF#>&eP~PQtjCqRIV%)7^U~3U@nf#jn_FP}$xU&gz9h`@K zth0T7wC^h=hKSf7eftvfI#7cCL_a!O(4OeWQ~S~WXfBa)Jx2M*XVLyR{koO*Z|+3< z&H5dS_>_U_;yUa*Qhw}X-Rxr=`{-BV zdRV3o^*+@1bO3%m&3au#*PaY(IL`Gu?$EWzqg=n!b$?LQd?>aNJBdBSKBC5>awEiX z;uLW|aUK}-5Y?`X3u?V9<`cuj5@H3him3jpd>v8iS$TxmK*YJx_cQfN^62X6X^lg8 z;(q@#^;6mt_Y=8pSo_oJzw}Sm0-q@b{%o4KfOYgt8Tb*iM?23{(=P4*oY&bm+MbK= z=iEn*w7sY27*{Nx`n<>Y2gds<^ZOF#!JcbNzq{W5T!@sU`hQd| z`37Phk#QXrA}=6@iH!HCBJyHl36Xg?s*JpxSVd%Bj*5^s5nG7uMApYqz2pPLVd5BZ zk~l+Te%3P|>ly!g#=D;Jt!G^88P9t9zn*@tuYkVw^m{%1UQfT*N7+xm*SC>(5b6K= z9`ZgS<5)jLK0+KLP7tSvGsIcqJn(2gF`Jl23=zY`B4P=#j95XeBGwS=h!J8VF-mM9 zwh=psUBn(D=k4e|@_ym~t`mRcIpdFc;IB;?rW~7X=g*XU{jN@ahkK&Vr(n@IaRPYO z7&*{0sD8_H&A8{=`u$+axmD0xf^%NVyKwGIDXB;MZuL*(_B05}`g)W6-5}*ZWd9HG z{3qq99LPVF3;&*C|5Fk27WQ|LBhHkk@SHy7ht;y4MelQLe{K?TlVOaTl>b*=`zOoE z>&fw4AY~HwwJFcxc|ywXX#Wqm?xx6lSbc7gGBpbPNd)-g65>2?l=9#11b=jxSYzL1 zru?xQ`EdBU2Yj{Y#yNE3l5y9*YGgh9k+`7yhs5pB*>mXR96C9NPR^l|bLiv@r9R#g z@s??wyfqK?@>cFAZw--iUwLboywLpfpRV=g_4rSRzcDON(w-=n$eq4=j;~BW*N=Mg z7v@_3k$6vJaTK<}USTsaLe%|2)}g#df*kxf)b~mL!YSxE0`IlVkF%=b&sjOZGm3y` zj>C@g_e|Ya#s9u+;40sL`HQq3bR6`14)HylpU-jq7tWX-{{;iU_b&j8I)U#)e*Nbm zKmM~T(az_;{tNrb*?&O|`1|J#J$zs1FE0c?On*I(Ph$Mz)6oC7h}(Y=xqZJb^?p$w z#~Mfon za~{Swzsc(^U_|1ad)%=J0^iU=+~WW-`HpjU{OuH#>*#=QNpkmK8k$>Y)UP3l|FpZaFhV`?_yN zQGcmtqpnlWu0wmM6u6P{Cu5#ck4L?wo?VOnO^o+=#-BHc_LDI0seJ#FdP*<+KO5)B z)U$EkOWn}tsnUFw(oPBTpL#C!%I~Qx-|q`UZ(@JR9QsRgm-d%*le7PvD!Jd)zstsW zFBwArrL)9r!;}YcE=|2ce~%;bpX#^vz^|KtuT(-_?CSar{JLMhmlnCt^}~-Q5AdIQ zfH%|vZ}bCio&~nzT%6jvpPblK4&Ky3>;rBe0=84%?aW_OH`;FxP@dyHUjgp$OU#4A z8>8q??UI|Tpr{E{ku+x7AJyk-4etgmlzJ$)-eInMi^ z;jh|VbLynok0Gv0Yc>_2eG{&8Yfi;=b7S+BZ8pdF1qCGxh$0b@w9XeNAz(XGZ7a zJ%e^0S+fP}Yt7}|Hs5P%XMi8Wx?FR4o1F*O{I5B*e+cVp&EI3cS>yf=)Z5#oU#>TA z?_9XGG`TwfWKa>Oct<>-0`^o_N8Q+@UAf7dQXRI#PZlA`u|Kanr=IL7a*~a`N z&Qsec`oF?@x?$SZ)7tiY#Q#5ho?`Kp!`}nusRHr+KQd2iR=b|uJbh;P|EYO$@x`wv zyG~x$gMIuuUJpJW!o0O(9j|TY{w4Q!w*GJJB4VAcy{!_wt&Hte)HmbF(|z&QD)~K{ z;GQA$e}{6ttp7&}QTKbI?C%BkRuggEUc09Re3<*%!~5A^$?+ccHvk`|-w$(L?WX-< z?l%tyDc6AUyJ`Qs#b|%1ll{1kuH8-h-|b?50kNIqJ>cK-10U>We?IzmMX`VF8bbS% zVajoxJc;Y;+Fi8sWF7i@7AW6AJ+$*=7yEnIKZgG9A=v4TvYqkvaGt&spj-*%sP8+> zYmehN2dsTy$euT>y&doI)_#xr$7(Qsd=C2dG0uIQk9{p{?*5%Uo!8U}wC>NAkoU&wKVsKdaisL#Nu&GsBDa9OseTA$m!3-cNH zat`!e)()(}`~+@8y(NwRmI&l;i2^rbKK`8hN!^zMEvS<~+Y0$_#XJXEF`q{w|MB+M zzS79JnE#Wyz<&$iF_*Uru# zJpLZ$`Qh9nYrm%s@y}Eq-2R?H^!E^t)co(Mk-yg^_4tz+dk+$LuFmZE|1?c(CAJWo ziP#tXd@mDttlZiI&*Ar0fuDv^SI7;ffl!VjfZTDlf9}>r#t>+2hjg^{n&5&vmIq;Ms_C zUG^yCj#7Oh=i3iVfuHN*d=7fR|Aq7RE#~3d zV`%?&1MpvafkW_f-FMo+pBw;wuMzm&0Q`BPh1fytAr27f_Y*VZ6U4a$r2c2Z(El|3 znOFdSrWN??G{%kf0{@+LGus*O80Y0D`Dp)%67}^bJ#jx z0msw+bL7ueLywPnbK}GhjH^d|{vrywUkm|XZbJVn<I&d=*!#`gs4W+&u#bF$tx zX}v3!qTc7by&>Hn-{-OMX~z}X__WheH)&@0$rrGQU{)2g6ay^jwxG5WceHr^f+LthHwcl~1eQCeh@qaT9dU&6(+VOsV z2a_Df9DBz8 zyf}J_{=#ugppG6JhX3E;JdbjIMswiL=mugwv4B`eEGCvh&$lYU7whs6=kr^v&%K<_ zC+n$?b@^l~`3iOUWH04bsLQ8v;rCOxj-`Ed%EqmKCP*wNa(=%)4ZTklp#2AsPkSoF z_Av0P@PGXab!V z^*qWr_i{cSZD4;3@KKlVgni=CCd#>fHxB-%?X5@uQx%*K`TyVbx__qXRm9&h^_#1gGVX8EUgkP}xdd@dcThj;;#W>T z=WC_w@r$Jx|0}MGUva*F<<`xSj-wEMzpQ_!TJrreo`a|Tp#XmTp^#V%e3kzF!SV8h z{z}TRo?fjbuP06sQDl(*u3v@H0Jtb&{Tm{U$xD z6?U_xfH^pKrXRZj{MZQas5;Y=#&wgP!?;$tZqnE1a(p}d$$|fAZeQPfWa};c*eLwY z#r~dtEaOgVUK+=-&Mxwnw!*qePiLKS{ikQLF5j>?+8GDqITrJi_B!j`&Bw9Me}|}t zr04PfD#JYZ$B5(13+gHDh|fn44^yB?#e@jB1kH@-N?e!x4t$yZ}b)>aq#_tGr&MAJ6svwmQUf{C?#7c>R9^FL$vGS875y{{iL7a z>Yj0(*}(CrpY*fx$rIu_tB&o6EB#Ejk8qy%Iz28g^(LQwRxkWMYZNHodEvSZ|I^PJ zAYY~bn`bFc|4(=E(ErmrVE6P+qSG7YIQoBjH~C8bbN`Xwg{t4l&im*{mY!g^*LDjCA_X&nu~mu)|vd`{(Y&-vx^JyCfhI4OUF2#_TOJj&U~F; zXgvM=B4QQ$6Y_O_HQSM|^b1DGBSg;kr8pO-U)qAYy>!67n@+E*G`aLGh$npu#-(qe zzAdnqzNLk7^lwWCc^?sRr*GlD)WGZ9)p#zJehtnm>5U%b?{ihaCiXY&2X8JS(%Aam(6pCp`BH-TbVwPChxs@$DGT-&Z*Q z(<4ql<|l1$GvzUFX?wY@BBgBSynforVZPn{Te^G?ruQZ3Uu;I5d@-L`39NK_T|DqF zz1Gz)>LO{~SI#Vr=kIFCSyvy-Cr3Q#9}JN@yNu_91#D+Lf8R}>7|#sj>9_AJ(!Vlo za_QSKE?GULZ|Az%UPt*F;I=B-;e2d^J~tmLUFXxc&0_p&uRq(HX&-jew{u;633Z)x z-@9=D<8BTZ9&nxC&U}B#kN#!$y?m{McJv?i`fEoX7uR3=xY=|0N^hSe=X|$MkT0F@ zv{mLSO@3cw?;YNoEqdhn!F%y{;J%2c0{vI60`1UEoWaqT> zy94lhkNPS0da8&M7?l*7EQ`n>$aEXQ%b z8k{3rekW z<{@d_J>udy%slPQW_--k;}zu4oBntgc?}Ku%;z68 z@5+aWS_jJKfv@He>HlH-TR=UfM@ZkH`cp%@T7MGXtL;Qx7s?M+-=WG6xIU^ilg>C@9Y#)z8Dd9tMw{<9Ng!eDenKHe#z`@XflOlV4AQ zZ^rzs^t?X!mUj4m4D2M^AA@hIg1sdBWAH6q@GEEBaJl_a{S9uch1^Mu@1#!hZeXZ{ z9P=GKsh@n1I7}QR!d~#CN$^lPk^1C&HGRGi3{&4b^U#0#01@Z6;3l1Ckvq)~4AZaE z>X4sPb>5}_RO&r-4qU#w(Q{ew{7T>jI9~)W41<^B92Wc=K_%EWJi>UV^ z`gsxKzKC&OG|X|MM81e`jbHd0OfHLU7A0<=Zj-6Rp61L|v?O zzwVx&B<4H#$sFp95bL4$+F8`gXBUXY!2eZ4ITwekqgBT7L6=X)QH?rxb+g(yf`8vY zKN^XSC&cktKm7h&0psAhxfpe_();RQCF(r!J~H^(Y~<-;_@8VaPFvx%K<_-aH0BTL*x*bP;=jx9$ht#(ndSIq;D| z;JJK6Ksr1RY|#PQ7AT???gvxM!)Tkvkwb+EI6?QXs} z-#cA?8`sq&TP;5?_Fz7)>oqxd zy*}hTeZGq0nWtzQ>|bBTb{GHh_2sU6!Rsq2k30oirpXiY#Ow8o6Y&4VGW+{F!QYmF z|90Fk!`lK(#rn@!(+2LFq23-M*28M|^NbW12jb0;Pt0-ufw)(DeN4R0CwtD7u@-S< ztYutl8xZII?j><#tabV8VmszPX?_1LNIA~iOQ<_{oe#cT%znfhl<$`iXOzhLez}yK z^Y)6H@1^sWv9=od5BTB#ubjV_hv0AKsb`h`|7wo%^#9k+-WxR!b@aawxK^LHdp#LP z!G1<&r}>kSh4qw?H3ps?AR>Pm*~n8yHsX%qDCLlcjHB|wkE$SI-ZR$ujUVoM%~%J& z+;uD?SO)zW2%;pVj-<4?ffDbC0b8BeyaWt`Xt`4jsg zm$0v8@c$pnIH48&%j|15AGN5@jLWI-@&V|*e26$gL>@9OpCF$i&JbsboR3-$cx`}~ zP0S_c5fNubZJ4}>SVAl#RuHR*HN-k%gxHApIv<)6F3(*)G((&vs(;d6?SWi%fIOS1 z{%ODZrx+%xKg!h~Mf_ds4EMW?!@O>0oTL8f^BAs+bJZW^ZH5`|3zD~xtDo8*C1PL6 zcwY{ABe8)PA+o-<)L=cH1HDP>>toQH?74Qv7V4{oU&+oD8CzU@J@jLk*hTCxOt$Z4 z)KyT9`KcQ;p7D<%;3r(1@FU|BF8+l4{9`xuxPCV-AwQ8;d+w9b5k#K;sR_8f68M#T zU>nZO8Mke)^IFCiaPH2y4(F1LrXI+B4)5zTx@zVBrI!8gah%gLzRv6G0Itg!-`D{8 zZ*af-2K&E3`KM@atP^r$)IZh_KEd{hDe!0M=d;|0pQYSSsAnn)x##Kc3pj6PybwbB zb7RaG<}G8Yj(nB#Hp%*Nb^Wtuj&CEj5;1Ry=V*7%Nxbf7{LGC{n70=q@cS1-#981A zW56F*(H`UB^ZZqxW5&ZA&pMoN`@Zw%@ZxdTfwaAe@ifElY0lR#JmBB#Pl&_WU*)_E zRxXX>Uxvt?o+BMc2ji%~I{8JIob|V-JRu&pPFEREkAG=Aj~`w<8IR9U5B7(Q$EV2? z=LPp`8NaAOJkx!M=NE0j-E$b1>^skd-!CLSFUWXdz{Y2E!|rnXs_p+PJcr5nX0_=_ zw!f#X=*KYqsD++o&JP8$pJkqid}Zc!n%p8Lw!dQ*{SH^^4|PEPq@ty`*p7W8^P~y% zhbWiW@7hI9@8dEzh9DovBVs*greVLzOq)V`ZV>q965v~EfN!PUw_=@S+V9v!@9|ux z^Le;_%t3F)+_;U)m_t2f{AV})C`J3rWyC({-w1mdv#^sPzu(pQ&iHj9@IRn0b7L{v zp*M3Q`qO`tjXM182K4{7nph9~9rl&9A6J0?3i->J$u*wo?9X(gf2IfM?EiXh&8g<->(9oE&R=7ZbWi1A77oDBanPo9IGgmZu9h5KQDV4ZzHx65l^!7O6JYZk4DNj5HTO_ zd1dCdQESiK79>J0a~sZ^nXS(69*#f6cy1}=xaH%?ycPLy&tF|UnYT`I9Ork{@k}6& zTM=jGt;k#EZP*txZz`bP!;I%9#NqCf-uU^MMm#s`?6ta=XS2Y#k@sfrycoAnz!u-k0;Si;`u9c2iAG!cFf;$ zcB-@Jpqd3qH19Ov_=)!n zgvg`BCd#4C+;h>)-^0(u`{6A4|5>%(vNG_yx=eY0f#+a7(DOT|w~Or^hDopYzn!5R z{r_D9Is8A;b(a-vp#P|wtl$vl;TO&>;yBdzNtyC|0C`{>zwACDab*5-oO&X}dc&+> z6mewYJ|inQi#UGPO1Z;~Zf3j#j-cCKBrE zIail&R2*H5Bf>Z;5XZkaFPV?5pv&hf*9X7f&)gR_J(>Fo-{ASc^U17C#*-1X-z#M0 z)SKO8=Z36fN~}NYm{MXH{N%YI>loxeD<=>NFtV5gPs zu)o~7!S=5#__}w)-*R88p!~(zXun|pl6+)cl1sjH zUX$kI!cNL#UbFsY+IZGQ*dLDg_3{$z$61$fKe!}f&s($3ZC(<`p}?D71dJjIXx@osT_H_)Ps2rSD2lodD@J5$=VFR4$M;({0=jZ$?|kcKjXnZk#$-n`6|~% zK_TUtr&B}Z$Wzv)PUBgdoW5r8(-5C~&y}^Qo8wl>Q?1C!e%+w;sMtbOd&`eqLQetYy2d8*OhOMv08?>Q4CocRA87`~HU_z`rjyTc9S-gE7OEo-brwQG#)`sF&sJ+CF|ox&40#S&enb z%V%`GNnSqFPOg3_?KHYQDtouii_xopo zpUpG5r2E2WC!tUNzfrYwz`k(*1p2QXNB=VOZu$RIF8ZH=pX;9~C&xTz{a^OJKd$mA z|NqWRGt-cr+r76b-P_Eq?!6Nl?&zA0$p{(kY6+pWt?f%jY-2*~MQdY1OjctvbJ4D> z5F6qXGTRc85MnbzY)mskYcfIz-}^q#=ehKrdAskXGSlj>=i_yLzJ8zcKJRnicNO$7 z_dM3C&f(|&y%+PW^!@NWx8r@$+(+@=eeR=qkaL4#*YEaWbDzj2T}{Nen)&;miEKwZ z{S$hT=kt2k>$(4u`OGWEoBJ5-xZU&liyrWOPaWs2B}%{2$@f2UNAi7N2x<6NFuv#B zmyY&2-z&_0F%^8jSVm;L+}`5*QE z^koCbXWYKhLwd*j&wT~{px$?>@6Bl4Gcj5W0|117#T*`LKU{~X^Ug-6(H1HJ*eQNUIYCwBe7x-1>VPi%X>g^$@ z-$A|?e8jbJ9QtKK8R&#Q@DY)M{u=8+uyx<)|2?0W$I^&#cECrJvYr{@-AK=0Ao2ox}W)Qw{pJSSO9AVVyMoj_rPjykb0|O3W+$(s)J&X?<~JJh4=? zH)cDC(D!K96WxA!Mi%7CO9q}%OT_nejsAP3%6GnuXLx}b!z1JyZQL1uRl@%8=ZQYI zFrJYMd5#?v^J@C{aV^mIagcAjI6H%R=-66}vje|!D0=!+3g)|-`YeM#7|-j#_~#+d z7|$C3eZGNcB-)8?Vk+=TFR?_0jGrrIIpg%oCbm=j+|Sn<D%5MlzwyucqF(F1tntPy%yV6) z<~+)Nz__OK2j`J_#+Xlj)%otmT+Fiu{AlLgl}5BHZv(DO2A)g(o=f|d(@xjXKG)G6 z*QKHTb=2Q=%roajiE>SQT-b+xn*7tBR}EsE9oR$o%|*o+=OQ0a@_%VP=u6vy#UVsH zk#ZKp4#r~6_b0~1jkvFCTme03$r0QRUd+em#GrOqNq?7ee9sQb|BV`J$@e|!h~vN0 zPIu*lUY9QJCm8Q(13nA=8DHoXe5muG@demj>m1kkFYqIu^Ni0?p2y&y#^*C2Upx6~ zFD30Gb^{-G3zYBoH1{Dd44{2;Ht>l&$n~@b^b5lrr-fLJ@@ESL>Xzf_M)YefCsI$( z^nh+lB+~!eD3@M2UJONlwH!}Sz85W`emi@#fG^#0JQjv|q&=Q20{v70k#auO4!Xq% z)GY_&MJ-2DjTlGnBh+%p^Mjq1!~efAN_@-n19>mW*evx{JW4re&+o~Rr^M5LD&b!} z+$Z%ge{EtO*i?-AO^w7hAphqtnEYDL<;HFU<*S4oZ#R*yAeIx$i15Fi-rx6Tg701* z_DQ|@z!y6?eyu2LVdQwQ zvy9j9naM2o3DmN4_zc)nt^?sAkWb1L>^w$3PwU(n^Uco2Tlh>qw|p(*R;~A3U3xcu-OqOPr*G0pPZZzSM}*I;haY`X z&-q63Z~US|AOC|{Dr%Hq#<{>yNz@YaR|8BNK7E660?cmD}1pU%UEBnfsHKhAoc+dG!UbR zZeltym*^!{5NnCe#4h3hFe#L1Cngg!iTT75qK{ZlY$Ns%hkzaP7x zvVRE=U;^n>Vm7gmSVpWSHWE9CeZXV`F^cFWrW13CUSb8Yme@?}A`So#4kg-&$;3=z zKCy)8Bi0k!h&{w1;2}n00x^}CO)Mmq5vz%f#13K~aH)Y9MRXI>iMd2Cv4U7jY$kRQ z2Y~+j6vAF9c49IylbBB|A^M2*#5Q6NaR_**k(fYCC1w)~iDkrUVk5DG*auu@AVv}0 z#B^dV(Mzl#))JeEUBm(4VWC7jF`1aD!hF&tL?5x9*hcIj4gph*!~|k0F`HOOEF)GE z8;KpnKH%X7VieI$Oef|Ny~GM)EwNdJU8Dzq{QY(K5q8qa#7ts7v4rR&)(1u99O=N- zLt_7(%**(+Prd&04g^O#1~%XSizN|Q0()ioIJ>N-IO>Obm5{pZ+M zC4G)_4q1QTr=4g&gy*3HheAg5KX6ky=*@*<`~x=^33}0iIFDGAgnip0k6qL+^0Wh& zL=lJ3Ps*Q<=a8g=P*I*#fcGbopQQhQpQKF~H)&Ja2!4_xJc3S&tU~|DYVh;x2F#ZY zJp{({f6qiYFHV{@jP|o}&ty?%3EF3N36$+Ut_;!Mvk>PHo`v8yP=6ri^BjorJV_W& z)+c>AjQNH`FyAoFt7ZLx58-#?2R`h^ybnX3K>ZzgqW^(+wV?j4cF@${flp+Cexelh zPn35`;uG~r zOR!%|TGD{_XO^LT9`=(`o}|N}&!odKf70Q&$GxbC;}@yMPlW#^9ndSrOFEzr@<{(l zf&P;YZ9@H_%_HhPwnRaDY-yt2V@n@VpY(Eqpp#x}L;u%C>Nn};M%3Rz`EG%H0eK#v zJPj%6-;g?T{HI{Qq_zUox8eSp?C(i!6YV`m`oQ0j`0iMs{$ujLEe!SBjNyg31R zrrASGCE__JoaY?j&3L{IZ!TngF|mxu=b`3m(k;Y6;1dpFIx&~Xyz2Kmv2H$5L98Oy z5*vuk#C9U{?URY1pJe`gvIjK3;~oAK`FyGylyOj7K-w=a$+0s*$`qXF`G!eK1)8It!8;0v60AmpX~$v9OZZ}mvk@i`FvtO zu$|+#4}yLnjF?5FzF$lO{Sx`;Xb1g@kx2RfO}RUBP~O=F_m?Lg%R`@9QTVl(j4c@D9~Th zUSC;2f0afg-(Pit_ODYSo`*feJYpsAYmWD|gS4BNLQE%S5p#(JL@%+FNIiUAOS+lZ zMH~Qr6H27~-z1VwCZ-WHiPY;il<%7&mX{EH#3o`Vk@osF1oXEyqKBA4%p(>PD~WZ) z7GgJXP@u^WMsyINFOva!G8sx)4tz zfHQ5x3}P{{j@S*H_pJB(}?B7PGBhK38j3Yl`QW7&Iuz@o;j3f4&|9M zjQTw&&mKjjDbF69XU`Cn@981t5jo$UEu;s5VGd#zv6R?A>;>*+A*KsjR2{=1-xp&y*fievwH!n@B&OpHF(M$oQJ?<#-ZHNS6^S zh(2O9v6fh`6C2sCSp(ZxF0q5O8oO97v4^z8KGG5gNDl%1&xyj{7K9RwDvTl>4DD=} zKy+(hGRsqSVmjMp60?c9#C&2Q(W`+aESFeDxm2|ppRL)UMsXgL(+;oT? z=>I=6QGQ4!=~U7sqzg&o9Km#WHR&GKcL0|ffkzlfrxMwJDf=yD`xLf2!i{z*#3OP^ z6OX7MO+2ERH1UW5(!?X}pbsS;nMs;>WC>~Fk@cjBNA{2=rWrvmBc`R2CZ-jVCZ<)B zCZ=_eCLUz~eHig5H)$gOzsz)4F3OLpU_CIrp&s_uxW^~&b!pw>o14#ln z_jt(x+W#Ca{Nb=>;s7w!4m^tGshOl@eF^C0Y?oS3n(db3_s}N05A)qui}~!iq^pUj z*Lt7txu#CE^V~83T<0Z?eWK@J>!N)${CjnIu3DRibEWj;^@zDh49;W*n85*8DBF}&iGK!@ou7n zNPmuxqMWezq>W@SVW{hJMf;ypq&Xs8 zyS&5#BK_Hw3pp1e4^J977xt;-90I+N`DtM)Y5McRP{^48Jxm%o6M9F=xsZ7&Asyuj zDU@@VbO_|!$1dneBj-Nl;9u^C5(Yr;6GaRICeY6N7$|3(D4#TP?(2aZQqFyRl&_iC zK&&R#Le6~)1wDD>><0f*&V5rs@0(A|A<~cc<@4Eou=k{qbH7rRoPD78GZI56C;fTf zA=q<2*n85*nV2|I&iyJVUn8-ONPph18geE!iSkJ!=b{A2A^kQn8FV7^Q(_5e`g3AB zi)9Dd!^QrA3U(MVY8yltRS)AJhKG!;{9I`+HS#c7xuZak*%S^7Rt?VNZUi zeA38y0PQ6GR(>CS|2E3oM5G_@UkN!6s1)UsMozZ@a!5H3;QDa@BX)*L} zT098(g34PZ+D#gH`8y!fft7-9(}D0G(}AV>#y)h?_;Yd-fG; zzT*|vq>S&E=Xc8Z3Q`a49fA&a&&X7$$mQ=>2tP5sl7)Wqp3!M;)aTlX@Do#E7oNLv z*l(xj$GTzq4dYbz{Cscz|LVb4UK(%(`PKSP@~R5aPX1q>sZg<>U;f-Wpw>Mr|8uNO zX?H;h`tx3t{LYE#!fKX7|MIzdZYS4kA9T9|m2a_=~^3B8+GwI?%4dLpqh1!SZa5DL%J;W)#tpw~DE-gmv$1~##=EJ6H1whO`#PqZ8rcu}m)`?7-9~wCD;K|? zHQk2a$(nA<0lg-Q{iHmS)+x^#$uIN3ntqN)d9DM>^EK0TXt!M+xX#CZUV)}R*O6As zgYSQv{*}Xe@GbLzZae&|ll{r}V;Il$SOd$;i9c)y(_?LH2fj7W1s`vK|2)#j_B+XU zqnrK7_oHcoHf@md?3i!U2HImoAKEuXahza$bA4*SxLV(LG(E()d#DNHtMkI+?I`#E z?pws^4)TJC&j;$r*G}r^K|A|XKM(ejru_cjpCew&h~Qi8k9xnqWqO?Zhr6U*z_;mf zIj_z*x(j|`x*PJF?yeun_dOWjbWe{cH~k}#*v)pupzAE8VOP_=g`_1GpxxvB@W;PH zUz&EkoqnS6-FDOCLm2P2F5nLAnxf?8O@pY{dyl-gT(l4DZ#IK})4TPW^9cWWHEeYA zsQgs-ef4gx3vZ-CkI(mG-u6sl1+hb5u>I@Y`~d%i(CS3L^h6WL*{M8e$!>nb<+39P9dop3PB%LVxDHkr&KXMZReUv#klZ z0Qa`c3rO1wIS%DrPkGl<-u0AsJ?*>RjrQv)=lU$tly5!dTJK|dEs=IuPrldl`EUI& z>V1me`Ssx6kBI+6^Kl*@=kaCkw0uH-ry`GE50UV1bCjYt{rfw!Q{ms=Ki-3WJIFJl z-hZD{;NFU#`P*ADk2%iE{)$}wawncA%%(!nJIz-c`WqEJ`{fxcUs1@*W>*;eWqKLL z4@m&dOvgMr_r+%3-!)H%-gm^Gu#XGtmD$yed0f<=tDSTUk#^w!2$)@UELY;huUA(U z(8>O@`a#cb0M4NtbFxA20eQ@Ouzt@Bl<%p;nSZ>nBIseSd_iliQ!>Ar zOkd@p-&ezil?IkTyjw4Z) zOO)ju@asjPVm^7EVO|Qo=zWecFQRN6CKh1M_m&SN;E0=HqI`_qEKYWk889Q|d}+>eJmXDs9_F!3x8^*`dAj)$?lVg=A!kWD z@alA+|NLL9`~LHOfqza0UJE&PI-UaONwn9RBEhHmI>>Flj`H1C#&XmLd;T{6IYG4F z&JVAT0^iqDt}AlbPWn#^X`Oz!I+X3n|CQ3uDp+1l1pj)^=gho+wmtt2^6y1Hs0bxp z3oM46ciJAs9V6wrtbp{`)qhHg8p=U7w9~h4`3Jbg+{@L`NCfE;}fWvAI_Bd zi*`A)5dF`|AliuzVj3}BU?9JpGfbNPc%Fwe#*^o!=Ic`^N4~+W#8VaRv@VzJA%|RV z%z^XQAabnhVSmc8zMC}cVZN!3^bh-&`KCO!gB+UnsPaLMDz4XSL)mX6|GL+iwNg*u zU%lTL?f31?Yo+`}cg(l>)+W|N@8(;Cq z@?wGJzsYfe=)D@})aH8{(eLlg#7-i{G5=i))$P@^97kf1`a7UM^BuXE-~YW-@qDF; z>h_YBD94c~%O%S4T=<{=y#w|qJLz{kUzG1n%r!oahq%$}cjmi{?B^vG5mkP756^M_ zVZc7ezg~%T@=*_PgOS(|{3r9?KN)XLTz{YBKGpwyQ_=q^=JltBLASI2iyZf*VwAs} zN@V>j*`Qk;N(1=*cQcXu zjep5_lX5h3UY&A0hWrqa;|at`l*9g&0?m!x zq$f&_Zt&k}ryRM!`_nOR7vuDG_=$Y~YOV)A=6cAx-E-V4ec=CogTNh~Gl}^BH{>-x z5Qg>-7%^{;7kvCf`T_NMUm@yszF#%hC9pm9SqBU}N8F2X<@-qUy>)C~LM#TVo`=o% z*0Wyn1OGF>!aVWs3hWc!rG9%EubY!0*N60*|DwP7qb4z)`J*o4&eF1<8s)eexGfa( zZZi_2h;}0MXx`=~olHyx4ipiKHRw{7mlIX=ryMtcooMI(uDV#4J}pzBESIS6uWqkh zFY@qb`C@$azS{hGBFlXOHS_3a9c))AaIE{FVE^}K{+#Q>M+VMYOKc%l6NB*mQ4!mL zZ}V38v5XJh>*&^Ij^icDaSKT^K0Z%{|8MDGIruhzo;C7sFJv*pe&#P~#C+y2nur}iP~EC)T@Sy9o2mE+&=+L3MjIEys}Uy4RQ5-Hw*;%_ccLwO zR-)gY)Pp8^*iN?3B3(pmP+^ezd7N)vH~K{)9xYm^ZZGLb8~dv;SiPkW{Vc=42>OE- zZr7gnj*y6T*b@B}7AW^kmKpt|3yJinNIYj-qC&-Uyv3T#`dVTOv6}e9{9CNDeWhqW(fdBj0;5>R zEm4?H>%D?gPx&O2*ggxZ>vwr?N!T&zk_gfP31+8U&^}PhkKGf$+3DiRmFadtBBl`=8YyW+ov*biF@6GR?} zI0<a!b5vNCy)|V$DehdGTdWbj;_LTmvR}T@nS)A8P zEFzBd_lVPb!1o#Omxz=$u@jCb;VQZL)<lcan*=3DE0 zGvf3i>K*615oh4sH{vYPXVETR1KN4np6_QP{J*nM@@~YX&`ZSSHG;p0%c0MR%OOw1 zi zw==-!bH6;$-^$9hF>1*3@h#1f)x zS5LZ)*h5t0@b`bfLUa>3&jTFq0gm?o$9sU|J;3oE;CK&kyazengB(`oaUF5%0O;y+ zU`;6S&Sv0UYXH{|-;`R&t?Pb?&QiKWB}Vm10dl1!9zv!9-3n0Ift zzny}`M5Vt%gTHzAR+5H1^X{!7-9W4pemSpJnNRs$7vz`k`StF1=hZ5H;-B~K5@H$0 z!?^SA0Y7rTIPV@gAKJ@((7gL{*iX@if4qCZzxp1@y!+F@_x+tB|IB;481y5lh6JVG z)&$U7Q^fvf-quvm10A40E(PuXzcpnaIBy%`bKbU0(p9M6Hbffxr+J@XU5lJHXed+q z^(BJOz8-;*yth2>!zR-32lpK|LA!776F7_CKaZTzfp+2D0_Sb420f#gm_W1>qXfFY zNG4_wa|ABt|8%*(tpgp_Brq~O3+?WN{TI({N4+^r;JlB@K}R$Tj5LCu$m#Hh$m!%~ zdJ*cU7odG~2oZ8Fjs{2fvZi!B^yTwhu{U`!<2@yL`YJ^08MM z>h~H3{;UBw7yLx-kqUax6kr(miQLNrI)peo7j$S8k#?9<#&+z_`{$9f$mi@#)Q5)x zjpW1V74*C>IlmG5nD=Eq+YJbG-`xoOM-j`b1kM`@A%c!Hl8>3>e1$m!IN+E2|x zqTQ^;c>>+rXjlGEW26~;Mw)5&k1N>@dX6-Mjx^KG<`UHZw@F~68SUKvZ6_T9v}79` zik+S0&)I|Wm^xrQ$D=)i<{kb{AYjjN&O3`|c)^#mTVODIM)LO?ED;Y z|FqVz$RhgX#lz^Qa~+Gki2i;t&Ickd$wz;l8$_<;I#!&C@~ONh^q{%ln#z0dlj1?( zWqrVF>xlHvHT2gtRVZK64=hh1mJ_>y*Ci71yb)Q({Oe=;>p5P75$*J@!{T?R{^yAa z+W!aEVPCS~E9g4x^ML>H-dE{ehsE!4m3>a3;3KdO`>0nR^ODaFK79#912KdcN(=)! zu|7qv?*Z-qU9j?;8M%IdI7I9qb`raY^n;yPhs8XlIT&YM3-Bh6do%gJ*^Ba<+km%3 z5%Y1P`(i0dXd;(W?~ru;{ocLedDCa%+uabMQV^<>I_ru;{b z|2&a}eA9~en~`nYKTi42l>g}QpSD`~&+`tfUoVyjjGECd%A-QCua26T2YMFPx2Rde zpl7!MLo0!EurH3unLr4>L7ZEX+-#Q)TNy7(ju0Z5;@Ps<)AOF zBGwU`h~)p`Zqofk@V)n5XutPf;D2v^k7nOGot^WSL{QhS7DzSy{bfv6MbG7(I_xd=lhH3^Pva5`~2wh3&r^E zFEWTZ0@d%?qR)q(<#TrQ`T3$<^!Wv7&-ZNV^U0sm`wL;u^NXREQ`><#ut#)`SJ1)i z7=3{U{Vsq#)OL)%0DclV!;bN0L=S))q8LxU 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 OX|u4ub^O60e*Yh0yu^wC literal 0 HcmV?d00001 diff --git a/system/rel_abs.bin b/system/rel_abs.bin new file mode 100644 index 0000000000000000000000000000000000000000..885c16abf59e70deb6af61e4d43241d50b784393 GIT binary patch literal 176848 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 cKLP#J^VaB-C%9V+zt;}M%D479_g_i>6rEC2ui literal 0 HcmV?d00001 diff --git a/system/rel_header.bin b/system/rel_header.bin new file mode 100644 index 0000000000000000000000000000000000000000..9965d012c7806f5d185bdd866fdc2487df59d3a4 GIT binary patch literal 76 zcmZQzU|?iG06`$r2g-E<(##AD6&o0AF0wG#T=)Xy3otSQm9zL~0Pz+e{sRL", f.read(4))[0] +us = lambda f : struct.unpack(">H", f.read(2))[0] +uc = lambda f : struct.unpack(">B", f.read(1))[0] +ubool = lambda f : struct.unpack(">?", f.read(1))[0] + +pl = lambda d : struct.pack(">L", d) +ps = lambda d : struct.pack(">H", d) +pc = lambda d : struct.pack(">B", d) +pbool = lambda d : struct.pack(">?", d) + +def hprint(val): + print(hex(val)) + + +def src_artifact(file): + return os.path.join("../artifacts/pal/", file) + +def rel_component(file): + return os.path.join("../build/", file) + +class Rel: + def __init__(self, filename=None): + if filename is None: + with open("rel_header.bin", "rb") as f: + self.header = RelHeader(f) + self.sectionInfo = [RelSection() for i in range(17)] + self.relocations = [RelRelData(), RelRelData()] + self.imps = [RelImpEntry(), RelImpEntry()] + return + + with open(filename, "rb") as f: + # header + self.header = RelHeader(f) + + # section info stuff + self.sectionInfo = [None] * self.header.numSections + for i in range (0, self.header.numSections): + section = RelSection(f, self.header.sectionInfoOffset, i) + self.sectionInfo[i] = section + + # dumb way to divide by 8 and keep int + self.imps = [None] * (self.header.impSize >> 3) + for i in range(0, self.header.impSize >> 3): + self.imps[i] = RelImpEntry(f, self.header.impOffset, i) + + # assertion i guess + assert(self.header.relOffset == self.imps[0].offset) + + # relocation data + self.relocations = [None] * (self.header.impSize >> 3) + for i in range(0, self.header.impSize >> 3): + self.relocations[i] = RelRelData(f, self.imps[i].offset) + + def reconstruct(self, filename): + rel = io.BytesIO() + # write temporary header + rel.write(b'\x41' * 0x4C) + sectionTableOffset = rel.tell() + + # write temporary section table entries + rel.write( b'\x41' * (len(self.sectionInfo) * 0x8) ) + + # write section data + sectionStartOffset = rel.tell() + offset = 0 + for s in self.sectionInfo: + if s.existsInRel(): + # recalculate offset + s.offset = offset + sectionStartOffset + rel.write( s.data ) + # fuck it this is being hardcoded + offset += s.length + if s.length == 0xC: + rel.write(b'\0' * 0x10) + offset += 0x10 + + sectionEndOffset = rel.tell() + + # write section info table + rel.seek(sectionTableOffset, os.SEEK_SET) + for s in self.sectionInfo: + rel.write( s.reconstructTableEntry() ) + + # write temporary imp table + rel.seek(0, os.SEEK_END) + rel.write( b'\x41' * (len(self.imps) * 8)) + + relocationOffsets = [] + # write relocation data + for r in self.relocations: + relocationOffsets.append(rel.tell()) + r.reconstruct(rel) + + # write imp data + rel.seek(sectionEndOffset, os.SEEK_SET) + for i, r in enumerate(relocationOffsets): + rel.write( pl(1 - i) ) # module id (hacky) + rel.write( pl(r) ) # offset + + # finally, the header + # write new values with inconsistent names + self.header.numSections = len(self.sectionInfo) + self.header.sectionInfoOffset = sectionTableOffset + self.header.relOffset = relocationOffsets[0] + self.header.impOffset = sectionEndOffset + self.impSize = len(self.imps) * 8 + + header = self.header.reconstruct() + rel.seek(0, os.SEEK_SET) + rel.write(header) + + # write to file + with open(filename, "wb") as f: + f.write(rel.getbuffer()) + + def dump_reloc(self, index, filename): + rel = io.BytesIO() + self.relocations[index].reconstruct(rel) + + with open(filename, "wb") as f: + f.write(rel.getbuffer()) + + def load_reloc(self, index, filename): + with open(filename, "rb") as f: + self.relocations[index] = RelRelData(f, 0) + + def dump_section(self, index, filename): + with open(filename, "wb") as f: + s = self.sectionInfo[index] + if s.offset != 0: + f.write(s.data) + else: + f.write(b'\0' * s.length) + + def load_section(self, index, filename): + with open(filename, "rb") as f: + s = RelSection() + s.data = f.read() + s.length = len(s.data) + self.sectionInfo[index] = s + + + +class RelHeader: + def __init__(self, f): + f.seek(0, os.SEEK_SET) # just in case + # unpack header + self.id = ul(f) + self.next = ul(f) + self.prev = ul(f) + self.numSections = ul(f) + self.sectionInfoOffset = ul(f) + self.nameOffset = ul(f) + self.nameSize = ul(f) + self.version = ul(f) + self.bssSize = ul(f) + self.relOffset = ul(f) + self.impOffset = ul(f) + self.impSize = ul(f) + self.prologSection = ubool(f) + self.epilogSection = ubool(f) + self.unresolvedSection = ubool(f) + self.bssSection = ubool(f) + self.prolog = ul(f) + self.epilog = ul(f) + self.unresolved = ul(f) + if self.version >= 2: + self.align = ul(f) + self.bssAlign = ul(f) + if self.version >= 3: + self.fixSize = ul(f) + + def reconstruct(self): + header = io.BytesIO() + header.write(pl(self.id)) + header.write(pl(self.next)) + header.write(pl(self.prev)) + header.write(pl(self.numSections)) + header.write(pl(self.sectionInfoOffset)) + header.write(pl(self.nameOffset)) + header.write(pl(self.nameSize)) + header.write(pl(self.version)) + header.write(pl(self.bssSize)) + header.write(pl(self.relOffset)) + header.write(pl(self.impOffset)) + header.write(pl(self.impSize)) + header.write(pbool(self.prologSection)) + header.write(pbool(self.epilogSection)) + header.write(pbool(self.unresolvedSection)) + header.write(pbool(self.bssSection)) + header.write(pl(self.prolog)) + header.write(pl(self.epilog)) + header.write(pl(self.unresolved)) + header.write(pl(self.align)) + header.write(pl(self.bssAlign)) + header.write(pl(self.fixSize)) + return header.getvalue() + + + +class RelSection: + def __init__(self, f=None, sectionInfoOffset=None, sid=None): + if f is None: + self.offset = 0 + self.unk = False + self.executable = False + self.length = 0 + self.data = None + return + + # get the section stuff + f.seek(sectionInfoOffset + (sid * 0x8), os.SEEK_SET) + self.offset = ul(f) + # dumb + self.unk = self.offset & 2 + self.executable = self.offset & 1 + self.offset &= ~3 + self.length = ul(f) + + # data + # skip empty shit and bss + print(f"section {sid}: {self.offset:X} {self.executable} {self.unk:X} {self.length:X}") + if self.offset == 0 or self.length == 0: + return + + f.seek(self.offset, os.SEEK_SET) + self.data = f.read(self.length) + + def existsInRel(self): + return (self.offset != 0 and self.length != 0) + + def reconstructTableEntry(self): + # recalculate length + if self.offset != 0: + self.length = len(self.data) + + entry = io.BytesIO() + word = self.offset | (self.unk << 1) | self.executable + entry.write( pl(word) ) + entry.write( pl(self.length) ) + return entry.getvalue() + + +class RelImpEntry: + def __init__(self, f=None, impOffset=None, sid=None): + if f is None: + self.moduleId = 0 + self.offset = 0 + return + + # does a thing + f.seek(impOffset + (sid * 8), os.SEEK_SET) + self.moduleId = ul(f) + self.offset = ul(f) + print(f"imp {sid}: {self.moduleId:X} {self.offset:X}") + +# funny name +class RelRelData: + def __init__(self, f=None, relOffset=None): + if f is None: + self.entries = OrderedDict() + return + + f.seek(relOffset, os.SEEK_SET) + self.entries = OrderedDict() + + counter = 0 + section = 0 + while True: + entry = RelRelEntry(f) + + #R_RVL_SECT + if entry.type == 202: + print("R_RVL_SECT", counter, entry.section) + section = entry.section + self.entries[section] = [] + continue + + self.entries[section].append(entry) + + # R_RVL_STOP + if entry.type == 203: + print("R_RVL_STOP") + break + + counter += 1 + + def reconstruct(self, f): + for e in self.entries: + # write R_RVL_SECT + f.write( ps(0) ) + f.write( pc(202) ) + f.write( pc(e) ) # section id + + # write R_RVL_NOP + f.write( b'\0' * 4 ) + + # write actual entries + for ee in self.entries[e]: + entry = ee.reconstruct() + f.write(entry) + + +class RelRelEntry: + def __init__(self, f): + self.offset = us(f) + self.type = uc(f) + self.section = uc(f) + self.addend = ul(f) + + def reconstruct(self): + entry = io.BytesIO() + entry.write( ps(self.offset) ) + entry.write( pc(self.type) ) + entry.write( pc(self.section) ) + entry.write( pl(self.addend) ) + return entry.getvalue() + + +def DumpStaticR(): + rel = Rel(src_artifact("StaticR.rel")) + + rel.dump_reloc(0, "dol_rel.bin") + rel.dump_reloc(1, "rel_abs.bin") + + rel.dump_section(1, rel_component("text.bin")) + rel.dump_section(2, rel_component("ctors.bin")) + rel.dump_section(3, rel_component("dtors.bin")) + rel.dump_section(4, rel_component("rodata.bin")) + rel.dump_section(5, rel_component("data.bin")) + rel.dump_section(6, rel_component("bss.bin")) + + + +def ReconstructStaticR(): + rel = Rel() + + rel.load_reloc(0, "dol_rel.bin") + rel.load_reloc(1, "rel_abs.bin") + + rel.load_section(1, rel_component("text.bin")) + rel.load_section(2, rel_component("ctors.bin")) + rel.load_section(3, rel_component("dtors.bin")) + rel.load_section(4, rel_component("rodata.bin")) + rel.load_section(5, rel_component("data.bin")) + rel.load_section(6, rel_component("bss.bin")) + + rel.sectionInfo[1].executable = True + rel.sectionInfo[1].offset = 0xD4 + rel.sectionInfo[2].offset = 0x37F120 + rel.sectionInfo[3].offset = 0x37F424 + rel.sectionInfo[4].offset = 0x37F440 + rel.sectionInfo[5].offset = 0x3A28F0 + rel.sectionInfo[6].offset = 0 + + rel.imps[0].moduleId = 1 + rel.imps[0].offset = 0x3CD104 + rel.imps[1].moduleId = 0 + rel.imps[1].offset = 0x4820F4 + + rel.reconstruct("../target/StaticR.rel") + +if __name__ == "__main__": + DumpStaticR() + ReconstructStaticR() + From 4f972782a8d93ff2ddf1cfa2070f194b90e7ed25 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:53:21 -0600 Subject: [PATCH 027/477] :sparkles: Started MessageGroup.cpp --- build.py | 2 ++ source/game/ui/MessageGroup.cpp | 10 ++++++++++ source/game/ui/MessageGroup.hpp | 18 ++++++++++++++++++ system/rel_slices.csv | 2 ++ 4 files changed, 32 insertions(+) create mode 100644 source/game/ui/MessageGroup.cpp create mode 100644 source/game/ui/MessageGroup.hpp create mode 100644 system/rel_slices.csv diff --git a/build.py b/build.py index 1b3924ae2..92ac5c07d 100644 --- a/build.py +++ b/build.py @@ -130,6 +130,8 @@ def build(): # compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) + compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', EGG_OPTS) + for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) diff --git a/source/game/ui/MessageGroup.cpp b/source/game/ui/MessageGroup.cpp new file mode 100644 index 000000000..af0423f2d --- /dev/null +++ b/source/game/ui/MessageGroup.cpp @@ -0,0 +1,10 @@ +#pragma once + +#include "MessageGroup.hpp" + +namespace UI { + +MessageGroup::MessageGroup() + : mpBin(0), mpINF(0), mpDAT(0), mpSTR(0), mpID(0) {} + +} // namespace UI diff --git a/source/game/ui/MessageGroup.hpp b/source/game/ui/MessageGroup.hpp new file mode 100644 index 000000000..fb923a5c4 --- /dev/null +++ b/source/game/ui/MessageGroup.hpp @@ -0,0 +1,18 @@ +#pragma once + +namespace UI { + +class MessageGroup { +public: + MessageGroup(); + // ... + +private: + void* mpBin; + void* mpINF; + void* mpDAT; + void* mpSTR; + void* mpID; +}; + +} // namespace UI diff --git a/system/rel_slices.csv b/system/rel_slices.csv new file mode 100644 index 000000000..61c01cc27 --- /dev/null +++ b/system/rel_slices.csv @@ -0,0 +1,2 @@ +enabled,name,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd +1,MessageGroup.cpp,0x805F8B34,0x805F8B50,,,,,,,,,, From 2300af18c1d01d77e2eee5839e6a139355bf2cb0 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 1 May 2021 03:05:41 -0600 Subject: [PATCH 028/477] :recycle: Refactored gen_asm.py --- system/gen_asm.py | 226 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 166 insertions(+), 60 deletions(-) diff --git a/system/gen_asm.py b/system/gen_asm.py index 89284df8e..cb29d7079 100644 --- a/system/gen_asm.py +++ b/system/gen_asm.py @@ -1,4 +1,9 @@ import struct +import sys +import shutil +import os + +from ppc_dis import * def read_u8(f): @@ -52,9 +57,16 @@ def read_segments(name): result[name] = segment return result +class Slice: + def __init__(self, obj_file, segments): + self.obj_file = obj_file + self.segments = segments + + def __repr__(self): + return "Slice { %s, %u segs }" % (self.obj_file, len(self.segments)) + # Limitation: slices must be ordered def read_slices(name): - result = [] for row in CSV(name).rows(): if not row.pop("enabled"): continue @@ -83,8 +95,7 @@ def read_slices(name): segments[seg_name].end = int(value, 16) print("#### %s %s" % (name, segments)) - result.append((name, segments)) - return result + yield Slice(name, segments) class DolBinary: def __init__(self, file): @@ -135,22 +146,23 @@ def __init__(self, file): def format_gap(name, gap): return "asm/%s_%s.s" % (name, hex(gap.begin)[2:]) -base = DolBinary("../artifacts/pal/main.dol") -segments = read_segments("../artifacts/pal/segments.csv") -slices = read_slices("slices.csv") - - -from ppc_dis import * -import sys - def format_segname(name): if "extab" in name: return name + "_" return '.' + name -def disasm(name, base, seg, is_data): - file = open("../" + format_gap(name, seg), 'w') - original_stdout = sys.stdout - sys.stdout = file +def read_u32b(filecontent, offset): + return (filecontent[offset + 0] << 24) | (filecontent[offset + 1] << 16) | (filecontent[offset + 2] << 8) | filecontent[offset + 3] + + +# stdout must be redirected +def dump_bss(size): + print(".skip 0x%x" % size) + +# stdout must be redirected +def dump_data(): + pass + +def compute_perm(name): perm = "wa" if name == "text" or name == "init": perm = "ax" @@ -161,41 +173,152 @@ def disasm(name, base, seg, is_data): if name == "rodata" or "2" in name: perm = perm.replace('w', '') - print("\n.include \"macros.inc\"") + return perm - file.write("\n.section %s, \"%s\" # 0x%08X - 0x%08X\n" % (format_segname(name), perm, seg.begin, seg.end)) +# stdout must be redirected +def dump_section_data(name, image, addr_start, seg): if "bss" in name: - print(".skip 0x%x" % seg.size()) - elif name != "text" and name != "init": - for i in range(seg.begin, seg.end, 4): - def read_u32b(filecontent, offset): - return (filecontent[offset + 0] << 24) | (filecontent[offset + 1] << 16) | (filecontent[offset + 2] << 8) | filecontent[offset + 3] - - if seg.end - i >= 4: - print(".4byte 0x%08X" % read_u32b(base.image, i- 0x80000000)) - else: - for j in range(i, seg.end): - print(".byte 0x%02x" % base.image[j - 0x80000000]) - else: - disasm_iter(base.image, seg.begin - 0x80000000, seg.begin, seg.size(), disassemble_callback) + dump_bss(seg.size()) + return + + if name == "text" or name == "init": + disasm_iter(image, seg.begin - addr_start, seg.begin, seg.size(), disassemble_callback) + return + + for i in range(seg.begin, seg.end, 4): + if seg.end - i >= 4: + print(".4byte 0x%08X" % read_u32b(image, i - addr_start)) + continue + + for j in range(i, seg.end): + print(".byte 0x%02x" % image[j - addr_start]) + +def disasm(name, image, addr_start, seg, is_data): + file = open("../" + format_gap(name, seg), 'w') + original_stdout = sys.stdout + sys.stdout = file + + # section permissions + perm = compute_perm(name) + + print("\n.include \"macros.inc\"") + print("\n.section %s, \"%s\" # 0x%08X - 0x%08X" % (format_segname(name), perm, seg.begin, seg.end)) + + dump_section_data(name, image, addr_start, seg) + sys.stdout = original_stdout -start_seg = {} -for name, seg in segments.items(): - start_seg[name] = Segment(0, seg.begin) -end_seg = {} -for name, seg in segments.items(): - end_seg[name] = Segment(seg.end, 0) +def gen_start_segs(segments): + # Start segs: + # ['text']: (0, 0x8...) + start_seg = {} + for name, seg in segments.items(): + start_seg[name] = Segment(0, seg.begin) -all_slices = slices + [('', end_seg)] + return start_seg -asm_files = [] -o_files = [] -cpp_files = [] -import shutil -import os +def gen_end_segs(segments): + # End segs: + # ['text']: (0x8..., 0) + end_seg = {} + for name, seg in segments.items(): + end_seg[name] = Segment(seg.end, 0) + + return end_seg + + +def find_gaps(all_slices): + last_segments = all_slices[0].segments + + # [1:] to skip initial (previously start_seg) + for slice_obj in all_slices[1:]: + obj_file = slice_obj.obj_file + slice = slice_obj.segments + for name, segment in slice.items(): + if last_segments[name].end != segment.begin: + # There's a gap! + + print("[.%s] Gap from %x to %x" % (name, last_segments[name].end, segment.begin)) + yield name, Segment(last_segments[name].end, segment.begin) + + last_segments[name] = segment + if not obj_file.startswith('#'): + yield obj_file, None + +def find_o_files(all_slices): + for name, gap_seg in find_gaps(all_slices): + if gap_seg is None: + yield name, gap_seg, "??" + continue + + print(format_gap(name, gap_seg)) + dest = format_gap(name, gap_seg).replace("asm/", "").replace(".s", ".o") + yield name, gap_seg, dest + +def unpack_binary(base_dol, all_slices, image, addr_start): + for name, gap_seg, dest in find_o_files(all_slices): + is_decompiled = gap_seg is None + + if not is_decompiled: + # print("name %s dest %s" % (name, dest)) + disasm(name, image, addr_start, gap_seg, False) + yield dest + + if is_decompiled: + yield name.replace(".cpp", ".o").replace(".c", ".o") + +def compute_end_cap(segments): + # Final 0x8 -> 0x8; second part ignored + end_seg = gen_end_segs(segments) + + end_slice = Slice('# 0x80 [finish] -> 0x80 [ignored]', end_seg) + + return end_slice + +def compute_begin_cap(segments): + # Final 0x8 -> 0x8; second part ignored + start_seg = gen_start_segs(segments) + + start_slice = Slice('# 0 [ignored] -> 0x80 [start]', start_seg) + + return start_slice + +def gen_cuts(slices, segments): + # Initial 0 -> 0x8; first part ignored + + start_slice = compute_begin_cap(segments) + end_slice = compute_end_cap(segments) + + return [start_slice] + slices + [end_slice] + +def compute_cuts_from_spreadsheets(segments, decomplog): + # segments: binary descriptor, .text: 0x8..0x8 + # decomplog: slices, what decompiled code replaces + + slices = list(read_slices(decomplog)) + segments = read_segments(segments) + + return gen_cuts(slices, segments) + +def unpack_base_dol(): + base_dol = DolBinary("../artifacts/pal/main.dol") + + cuts = compute_cuts_from_spreadsheets("../artifacts/pal/segments.csv", "slices.csv") + + o_files = list(unpack_binary(base_dol, cuts, base_dol.image, 0x80000000)) + + return o_files + +def unpack_staticr_rel(): + return [] + +def unpack_everything(): + o_files = unpack_base_dol() + unpack_staticr_rel() + + open('../build/o_files.txt', 'w').write('\n'.join(o_files)) + try: shutil.rmtree("../asm") except: pass @@ -212,21 +335,4 @@ def read_u32b(filecontent, offset): for i in range(0, 8): file.write(".set qr%i, %i\n" % (i, i)) -last_segments = start_seg -for obj_file, slice in all_slices: - for name, segment in slice.items(): - if last_segments[name].end != segment.begin: - # There's a gap! - - print("[.%s] Gap from %x to %x" % (name, last_segments[name].end, segment.begin)) - gap_seg = Segment(last_segments[name].end, segment.begin) - print(format_gap(name, gap_seg)) - asm_files.append(format_gap(name, gap_seg)) - o_files.append(format_gap(name, gap_seg).replace("asm/", "").replace(".s", ".o")) - disasm(name, base, gap_seg, False) - last_segments[name] = segment - if obj_file: - o_files.append(obj_file.replace(".cpp", ".o").replace(".c", ".o")) - cpp_files.append(obj_file) - -open('../build/o_files.txt', 'w').write('\n'.join(o_files)) \ No newline at end of file +unpack_everything() \ No newline at end of file From f873c935aa01e07d6104688a3cf735d0e23fea0b Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 1 May 2021 03:23:09 -0600 Subject: [PATCH 029/477] :sparkles: Rel disassembly (code) --- .gitignore | 3 ++ artifacts/pal/rel_segments.csv | 7 ++++ build.py | 4 +-- build/link.lcf => link.lcf | 0 build/o_files.txt => o_files.txt | 0 rel_o_files.txt | 8 +++++ system/gen_asm.py | 56 +++++++++++++++++++++++++++----- system/rel_repack.py | 2 +- 8 files changed, 68 insertions(+), 12 deletions(-) create mode 100644 artifacts/pal/rel_segments.csv rename build/link.lcf => link.lcf (100%) rename build/o_files.txt => o_files.txt (100%) create mode 100644 rel_o_files.txt diff --git a/.gitignore b/.gitignore index e12ad2d32..1a0ecadda 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ out # Python cache/compiled modules *.pyc + +tmp +*.rel \ No newline at end of file diff --git a/artifacts/pal/rel_segments.csv b/artifacts/pal/rel_segments.csv new file mode 100644 index 000000000..e58c3b7e6 --- /dev/null +++ b/artifacts/pal/rel_segments.csv @@ -0,0 +1,7 @@ +name,type,start,end +text,code,805103B4,8088F400 +ctors,data,8088F400,8088F704 +dtors,data,8088F704,8088F710 +rodata,data,8088F710,808B2BD0 +data,data,808B2BD0,808DD3D4 +bss,bss,809BD6E0,809C4F90 \ No newline at end of file diff --git a/build.py b/build.py index 92ac5c07d..5d6111e60 100644 --- a/build.py +++ b/build.py @@ -104,7 +104,7 @@ def make_obj(src): def build(): from pathlib import Path asm_files = ["asm/" + str(x.stem) + ".s" for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] - o_files = ["out/" + x.strip() for x in open('build/o_files.txt', 'r').readlines()] + o_files = ["out/" + x.strip() for x in open('o_files.txt', 'r').readlines()] try: os.mkdir("out") @@ -135,7 +135,7 @@ def build(): for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) - lcf = open("build/link.lcf", 'r').read() + lcf = open("link.lcf", 'r').read() lcf += "\nFORCEFILES {\n" lcf += "\n".join(x.replace("out/", "") for x in o_files) lcf += "\n}\n" diff --git a/build/link.lcf b/link.lcf similarity index 100% rename from build/link.lcf rename to link.lcf diff --git a/build/o_files.txt b/o_files.txt similarity index 100% rename from build/o_files.txt rename to o_files.txt diff --git a/rel_o_files.txt b/rel_o_files.txt new file mode 100644 index 000000000..08538047e --- /dev/null +++ b/rel_o_files.txt @@ -0,0 +1,8 @@ +text_805103b4.o +MessageGroup.o +text_805f8b50.o +ctors_8088f400.o +dtors_8088f704.o +rodata_8088f710.o +data_808b2bd0.o +bss_809bd6e0.o \ No newline at end of file diff --git a/system/gen_asm.py b/system/gen_asm.py index cb29d7079..070d6fbaa 100644 --- a/system/gen_asm.py +++ b/system/gen_asm.py @@ -257,7 +257,7 @@ def find_o_files(all_slices): dest = format_gap(name, gap_seg).replace("asm/", "").replace(".s", ".o") yield name, gap_seg, dest -def unpack_binary(base_dol, all_slices, image, addr_start): +def unpack_binary(all_slices, image, addr_start): for name, gap_seg, dest in find_o_files(all_slices): is_decompiled = gap_seg is None @@ -300,25 +300,63 @@ def compute_cuts_from_spreadsheets(segments, decomplog): slices = list(read_slices(decomplog)) segments = read_segments(segments) - return gen_cuts(slices, segments) + return slices, segments, gen_cuts(slices, segments) def unpack_base_dol(): base_dol = DolBinary("../artifacts/pal/main.dol") - cuts = compute_cuts_from_spreadsheets("../artifacts/pal/segments.csv", "slices.csv") + slices, segments, cuts = compute_cuts_from_spreadsheets("../artifacts/pal/segments.csv", "slices.csv") - o_files = list(unpack_binary(base_dol, cuts, base_dol.image, 0x80000000)) + # o_files + return list(unpack_binary(cuts, base_dol.image, base_dol.image_base)) - return o_files +## REL + +def load_rel_binary(segments): + print(segments) + max_vaddr = max(segments[seg].end for seg in segments) + image_base = 0x80000000 + image = bytearray(max_vaddr - image_base) + + for segment in segments: + with open("../tmp/%s.bin" % segment, 'rb') as file: + data = file.read() + + segment_data = segments[segment] + + start = segment_data.begin + end = segment_data.end + + data_len = len(data) # virtual + + for i in range(start, end): + #try: + # x = data[i - start] + #except: + # print(segment, hex(i), hex(start), hex(end),i - start, len(data)) + # print(end - (start + len(data))) + + # Hack for alignment (miss by 16) + if i - start >= data_len: + continue + image[i - image_base] = data[i - start] + + return image, image_base def unpack_staticr_rel(): - return [] + slices, segments, cuts = compute_cuts_from_spreadsheets("../artifacts/pal/rel_segments.csv", "rel_slices.csv") -def unpack_everything(): - o_files = unpack_base_dol() + unpack_staticr_rel() + image, image_base = load_rel_binary(segments) - open('../build/o_files.txt', 'w').write('\n'.join(o_files)) + # o_files + return list(unpack_binary(cuts, image, image_base)) + +def unpack_everything(): + dol_o_files = unpack_base_dol() + rel_o_files = unpack_staticr_rel() + open('../o_files.txt', 'w').write('\n'.join(dol_o_files)) + open('../rel_o_files.txt', 'w').write('\n'.join(rel_o_files)) try: shutil.rmtree("../asm") except: pass diff --git a/system/rel_repack.py b/system/rel_repack.py index 494bad4e4..a424c8782 100644 --- a/system/rel_repack.py +++ b/system/rel_repack.py @@ -21,7 +21,7 @@ def src_artifact(file): return os.path.join("../artifacts/pal/", file) def rel_component(file): - return os.path.join("../build/", file) + return os.path.join("../tmp/", file) class Rel: def __init__(self, filename=None): From 379eae631d20955fb66b3597dcc86e5c40dd5732 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 1 May 2021 03:23:28 -0600 Subject: [PATCH 030/477] :sparkles: Rel disassembly (data) From ff29c302da80738e47a5c5e74b2bf92beb98b321 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 1 May 2021 03:34:09 -0600 Subject: [PATCH 031/477] :bug: PS disassembly fixes --- system/gen_asm.py | 4 ++-- system/ppc_dis.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/system/gen_asm.py b/system/gen_asm.py index 070d6fbaa..6a63ab3b6 100644 --- a/system/gen_asm.py +++ b/system/gen_asm.py @@ -353,9 +353,9 @@ def unpack_staticr_rel(): def unpack_everything(): dol_o_files = unpack_base_dol() - rel_o_files = unpack_staticr_rel() - open('../o_files.txt', 'w').write('\n'.join(dol_o_files)) + + rel_o_files = unpack_staticr_rel() open('../rel_o_files.txt', 'w').write('\n'.join(rel_o_files)) try: shutil.rmtree("../asm") diff --git a/system/ppc_dis.py b/system/ppc_dis.py index c178aa52f..28f37bdc0 100644 --- a/system/ppc_dis.py +++ b/system/ppc_dis.py @@ -40,6 +40,11 @@ def sign_extend_12(value): PPC_INS_VMADDFP, PPC_INS_XXSPLTW, PPC_INS_XVMSUBADP, PPC_INS_XVCMPGEDP, PPC_INS_XVNMSUBMDP, PPC_INS_XVMADDMDP, PPC_INS_XXMRGHW, + PPC_INS_XVTDIVDP, PPC_INS_XXMRGLW, PPC_INS_XVADDDP, PPC_INS_XXLAND, + PPC_INS_XVNMSUBASP, PPC_INS_XVNMSUBMSP, + PPC_INS_XVNMADDADP, PPC_INS_XVNMADDMSP, + PPC_INS_XSSUBDP, + # Instructions that Capstone gets wrong PPC_INS_MFESR, PPC_INS_MFDEAR, PPC_INS_MTESR, PPC_INS_MTDEAR, PPC_INS_MFICCR, PPC_INS_MFASR From 95db0571807e10d99d02c5ea9956ae9d0774af94 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 1 May 2021 04:49:21 -0600 Subject: [PATCH 032/477] :construction: rel: Build an (unverified) .elf --- build.py | 28 +++++++++++++++++++++------- rel_link.lcf | 14 ++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 rel_link.lcf diff --git a/build.py b/build.py index 5d6111e60..ed05c945b 100644 --- a/build.py +++ b/build.py @@ -89,8 +89,10 @@ def assemble(dst, src): cmd = GAS + " %s -mgekko -Iasm -o %s" % (src, dst) command(cmd) -def link(dst, objs, lcf): +def link(dst, objs, lcf, partial=False): cmd = MWLD + " %s -o %s -lcf %s -fp hard" % (' '.join(objs), dst, lcf) + if partial: + cmd += " -r " command(cmd) def make_obj(src): @@ -101,10 +103,23 @@ def make_obj(src): src = src.replace(s, '.o') return src +def gen_lcf(src, dst, o_files): + lcf = "" + + with open(src, 'r') as f: + lcf = f.read() + lcf += "\nFORCEFILES {\n" + lcf += "\n".join(x.replace("out/", "") for x in o_files) + lcf += "\n}\n" + + with open(dst, 'w') as f: + f.write(lcf) + def build(): from pathlib import Path asm_files = ["asm/" + str(x.stem) + ".s" for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] o_files = ["out/" + x.strip() for x in open('o_files.txt', 'r').readlines()] + rel_o_files = ["out/" + x.strip() for x in open('rel_o_files.txt', 'r').readlines()] try: os.mkdir("out") @@ -135,15 +150,14 @@ def build(): for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) - lcf = open("link.lcf", 'r').read() - lcf += "\nFORCEFILES {\n" - lcf += "\n".join(x.replace("out/", "") for x in o_files) - lcf += "\n}\n" - - open('out/generated.lcf', 'w').write(lcf) + gen_lcf("link.lcf", "out/generated.lcf", o_files) link('out/built.elf', o_files, "out/generated.lcf") + gen_lcf("rel_link.lcf", "out/rel_generated.lcf", rel_o_files) + link('out/rel_built.elf', rel_o_files, "out/rel_generated.lcf", partial=True) + + with open('out/built.elf', 'r+b') as elf: elf.seek(0x18) elf.write(bytes([0x80, 0x00, 0x60, 0xA4])) diff --git a/rel_link.lcf b/rel_link.lcf new file mode 100644 index 000000000..0450c3622 --- /dev/null +++ b/rel_link.lcf @@ -0,0 +1,14 @@ +ENTRY(__start) +MEMORY { +text : origin = 0x805103B4 +} +SECTIONS { +GROUP:{ +.text ALIGN(0x20):{} +.ctors ALIGN(0x20):{} +.dtors ALIGN(0x20):{} +.rodata ALIGN(0x20):{} +.data ALIGN(0x20):{} +.bss ALIGN(0x80):{} +} > text +} From c70672641089ed07bbfc5cc4a8d22c8e48ff39a9 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 2 May 2021 01:06:18 -0600 Subject: [PATCH 033/477] =?UTF-8?q?=F0=9F=8E=89=20StaticR.rel=20built=201:?= =?UTF-8?q?1,=20OK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ++- build.py | 185 +++++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 173 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e5d50814f..e2a6d24b7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # mkw A matching decompilation of Mario Kart Wii. All code in this repository will compile 1:1 to the original game. -It produces the following .dol: +It produces the following files: mkw_pal.dol: `sha1: ac7d72448630ade7655fc8bc5fd7a6543cb53a49` +StaticR.rel: `sha1: 887bcc076781f5b005cc317a6e3cc8fd5f911300` ## Accuracy The primary priority is to maintain absolute code accuracy. To automate verification of this, a special linker setup is used to emplace compiled code back into the original executable, forming a new executable. This new executable is hashed to ensure it matches the original. Once all code is decompiled, this setup will build a new executable from scratch, sampling none of the original. @@ -20,9 +21,9 @@ Every fully understood piece of reverse engineered data has been documented in a - Place a copy of Mario Kart Wii's PAL .dol file in `artifacts/pal` as `main.dol` ## Contributing -- Do not manually adjust assembly files; rather add a new entry to [system/slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv). +- Do not manually adjust assembly files; rather add a new entry to [system/slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv) for main.dol and [system/rel_slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv) for StaticR.rel. - Entries must be sorted in the spreadsheet (current limitation). - Run `gen_asm.py` from the `system` directory to regenerate assembly segments. ## .rel support -Most of Mario Kart Wii's game code is located inside a relocatable module (StaticR.rel for release builds). Currently, the decompilation targets only the static module (the .dol file). The `relsplit` branch of the repository hosts work on getting the rel incorporated into the decompilation. \ No newline at end of file +Most of Mario Kart Wii's game code is located inside a relocatable module (StaticR.rel for release builds). The decompilation builds this. \ No newline at end of file diff --git a/build.py b/build.py index ed05c945b..62512ab43 100644 --- a/build.py +++ b/build.py @@ -1,3 +1,24 @@ + +class Segment: + def __init__(self, begin : int, end : int): + assert isinstance(begin, int) and isinstance(end, int) + self.begin = begin + self.end = end + + def __repr__(self): + return "(%s, %s)" % (hex(self.begin), hex(self.end)) + + def empty(self): + return self.begin == self.end + + def size(self): + return self.end - self.begin +import struct +def read_u32(f): + return struct.unpack(">I", f.read(4))[0] + + + import os VERBOSE = False @@ -115,12 +136,113 @@ def gen_lcf(src, dst, o_files): with open(dst, 'w') as f: f.write(lcf) -def build(): - from pathlib import Path - asm_files = ["asm/" + str(x.stem) + ".s" for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] - o_files = ["out/" + x.strip() for x in open('o_files.txt', 'r').readlines()] - rel_o_files = ["out/" + x.strip() for x in open('rel_o_files.txt', 'r').readlines()] +def read_elf_sec(elf, name): + from system.rel_repack import RelSection + + elf_sec = elf.get_section_by_name(name) + + data = elf_sec.data() + + t = RelSection() + t.data = bytearray(data) + t.length = len(data) + + # Added + t.name = name + + return t + +R_PPC_NONE = 0 +R_PPC_ADDR32 = 1 +R_PPC_ADDR24 = 2 +R_PPC_ADDR16 = 3 +R_PPC_ADDR16_LO = 4 +R_PPC_ADDR16_HI = 5 +R_PPC_ADDR16_HA = 6 +R_PPC_ADDR14 = 7 +R_PPC_ADDR14_BRTAKEN = 8 +R_PPC_ADDR14_BRNKTAKEN = 9 +R_PPC_REL24 = 10 +R_PPC_REL14 = 11 + +R_PPC_REL32 = 26 + +def build_elf(rel_path, elf_path): + from system.rel_repack import Rel + + # Hack: for header.bin + os.chdir("system") + + rel = Rel() + os.chdir("..") + + rel.load_reloc(0, "system/dol_rel.bin") + rel.load_reloc(1, "system/rel_abs.bin") + + from elftools.elf.elffile import ELFFile + + with open(elf_path, 'rb') as f: + elf = ELFFile(f) + + rel.sectionInfo[1] = read_elf_sec(elf, ".text") + rel.sectionInfo[2] = read_elf_sec(elf, ".ctors") + rel.sectionInfo[3] = read_elf_sec(elf, ".dtors") + rel.sectionInfo[4] = read_elf_sec(elf, ".rodata") + rel.sectionInfo[5] = read_elf_sec(elf, ".data") + rel.sectionInfo[6] = read_elf_sec(elf, ".bss") + + # .rodata padding hack + rodata = rel.sectionInfo[4] + rodata.data = rodata.data[:-16] + rodata.length = len(rodata.data) + + # Jump to _Unresolved + _Unresolved = 0x805553B0 + text = rel.sectionInfo[1] + relocs = elf.get_section_by_name(".rela.text") + if relocs: + for reloc_acc in relocs.iter_relocations(): + reloc = reloc_acc.entry + if reloc.r_info_type == R_PPC_REL24: + instruction = struct.unpack(">I", text.data[reloc.r_offset : reloc.r_offset + 4])[0] + instruction_addr = 0x805103B4 + reloc.r_offset + + delta = _Unresolved - instruction_addr + new_ins = (instruction & ~0x03fffffc) | (delta & 0x03fffffc) + + # print(hex(instruction)) + # print(hex(new_ins)) + + packed = struct.pack(">I", new_ins) + for i in range(4): + text.data[reloc.r_offset + i] = packed[i] + + rel.sectionInfo[1].executable = True + rel.sectionInfo[1].offset = 0xD4 + rel.sectionInfo[2].offset = 0x37F120 + rel.sectionInfo[3].offset = 0x37F424 + rel.sectionInfo[4].offset = 0x37F440 + rel.sectionInfo[5].offset = 0x3A28F0 + rel.sectionInfo[6].offset = 0 + + rel.imps[0].moduleId = 1 + rel.imps[0].offset = 0x3CD104 + rel.imps[1].moduleId = 0 + rel.imps[1].offset = 0x4820F4 + + rel.reconstruct(rel_path) + + # Debug + for i in range(1, 6+1): + sec = rel.sectionInfo[i] + + dest_path = "tmp/" + sec.name[1:] + "_rebuilt.bin" + + with open(dest_path, 'wb') as f: + f.write(sec.data) + +def compile_sources(): try: os.mkdir("out") except: pass @@ -147,24 +269,20 @@ def build(): compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', EGG_OPTS) + from pathlib import Path + asm_files = ["asm/" + str(x.stem) + ".s" for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] + for asm in asm_files: assemble("out/" + make_obj(asm).replace("asm/", ""), asm) - +def link_dol(o_files): gen_lcf("link.lcf", "out/generated.lcf", o_files) link('out/built.elf', o_files, "out/generated.lcf") - gen_lcf("rel_link.lcf", "out/rel_generated.lcf", rel_o_files) - link('out/rel_built.elf', rel_o_files, "out/rel_generated.lcf", partial=True) - - with open('out/built.elf', 'r+b') as elf: elf.seek(0x18) elf.write(bytes([0x80, 0x00, 0x60, 0xA4])) - try: - os.mkdir("target") - except: pass command(ELF2DOL + " out/built.elf target/mkw_pal.dol -v -v") # This is a bug with elf2dol; this works fine with makedol @@ -174,14 +292,49 @@ def build(): # bss_size dol.write(bytes([0x00, 0x0E, 0x50, 0xFC])) - import hashlib +def link_rel(rel_o_files): + gen_lcf("rel_link.lcf", "out/rel_generated.lcf", rel_o_files) + link('out/rel_built.elf', rel_o_files, "out/rel_generated.lcf", partial=True) + + build_elf("target/StaticR.rel", "out/rel_built.elf") + +def build(): + try: + os.mkdir("target") + except: pass + + o_files = ["out/" + x.strip() for x in open('o_files.txt', 'r').readlines()] + rel_o_files = ["out/" + x.strip() for x in open('rel_o_files.txt', 'r').readlines()] + + compile_sources() + + link_dol(o_files) + + link_rel(rel_o_files) + + verify_dol() + + verify_rel() + +import hashlib + +def verify_rel(): + ctx = hashlib.sha1(open('target/StaticR.rel', 'rb').read()) + digest = ctx.hexdigest() + if digest.lower() == '887bcc076781f5b005cc317a6e3cc8fd5f911300': + print("[REL] Everything went okay! Output is matching! ^^") + return + + print("[REL] Oof: Output doesn't match.") + +def verify_dol(): ctx = hashlib.sha1(open('target/mkw_pal.dol', 'rb').read()) digest = ctx.hexdigest() if digest.lower() == 'ac7d72448630ade7655fc8bc5fd7a6543cb53a49': - print("Everything went okay! Output is matching! ^^") + print("[DOL] Everything went okay! Output is matching! ^^") return - print("Oof: Output doesn't match.") + print("[REL] Oof: Output doesn't match.") # TODO: Add diff'ing build() \ No newline at end of file From c9c25e00595627244f2ab70bb66935f466377920 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 2 May 2021 02:11:51 -0600 Subject: [PATCH 034/477] :construction: MessageGroup.cpp work (tests .rel unresolved function call fixup step) --- rel_o_files.txt | 2 +- source/game/ui/MessageGroup.cpp | 2 ++ source/game/ui/MessageGroup.hpp | 1 + system/rel_slices.csv | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/rel_o_files.txt b/rel_o_files.txt index 08538047e..6d373e892 100644 --- a/rel_o_files.txt +++ b/rel_o_files.txt @@ -1,6 +1,6 @@ text_805103b4.o MessageGroup.o -text_805f8b50.o +text_805f8b90.o ctors_8088f400.o dtors_8088f704.o rodata_8088f710.o diff --git a/source/game/ui/MessageGroup.cpp b/source/game/ui/MessageGroup.cpp index af0423f2d..76bb42078 100644 --- a/source/game/ui/MessageGroup.cpp +++ b/source/game/ui/MessageGroup.cpp @@ -7,4 +7,6 @@ namespace UI { MessageGroup::MessageGroup() : mpBin(0), mpINF(0), mpDAT(0), mpSTR(0), mpID(0) {} +MessageGroup::~MessageGroup() {} + } // namespace UI diff --git a/source/game/ui/MessageGroup.hpp b/source/game/ui/MessageGroup.hpp index fb923a5c4..c58f2e1e8 100644 --- a/source/game/ui/MessageGroup.hpp +++ b/source/game/ui/MessageGroup.hpp @@ -5,6 +5,7 @@ namespace UI { class MessageGroup { public: MessageGroup(); + ~MessageGroup(); // ... private: diff --git a/system/rel_slices.csv b/system/rel_slices.csv index 61c01cc27..f68726402 100644 --- a/system/rel_slices.csv +++ b/system/rel_slices.csv @@ -1,2 +1,2 @@ enabled,name,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd -1,MessageGroup.cpp,0x805F8B34,0x805F8B50,,,,,,,,,, +1,MessageGroup.cpp,0x805F8B34,0x805F8B90,,,,,,,,,, From 63b6083dbf8b576714910fd87b3a5d20e962899e Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 2 May 2021 02:13:46 -0600 Subject: [PATCH 035/477] :recycle: Use env var for DEVKITPPC path --- build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.py b/build.py index 62512ab43..41a1c5b38 100644 --- a/build.py +++ b/build.py @@ -23,7 +23,7 @@ def read_u32(f): VERBOSE = False -DEVKITPPC = "C:\\devkitPro\\devkitPPC" +DEVKITPPC = os.environ["DEVKITPPC"] GCC = os.path.join(DEVKITPPC, "bin\\powerpc-eabi-gcc.exe") GAS = os.path.join(DEVKITPPC, "bin\\powerpc-eabi-as.exe") From cce0f8698e47f28e546f6b98a3abdf5bf8fd8fbc Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 2 May 2021 09:53:41 -0600 Subject: [PATCH 036/477] :construction: Uploaded elf2dol.exe version --- .gitignore | 5 ++++- tools/elf2dol.exe | Bin 0 -> 15872 bytes 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tools/elf2dol.exe diff --git a/.gitignore b/.gitignore index 1a0ecadda..b40537592 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,10 @@ *.lib # Executables -*.exe +mwasmeppc.exe +mwcceppc.exe +mwldeppc.exe +makedol.exe *.out *.app diff --git a/tools/elf2dol.exe b/tools/elf2dol.exe new file mode 100644 index 0000000000000000000000000000000000000000..77d12d992f21c96147365a9c4b61d05ce623a9f1 GIT binary patch literal 15872 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 a`P7&-BG|E%%u25%R~Dt0rE~cx5Bx8Vr6P&| literal 0 HcmV?d00001 From 1df538b265d023925f6ccf9ef69d4e9901b93269 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 2 May 2021 09:56:24 -0600 Subject: [PATCH 037/477] :hammer: Unreproducable bug with .bss section size. Hardcode it for now. --- build.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.py b/build.py index 41a1c5b38..a1c935b95 100644 --- a/build.py +++ b/build.py @@ -225,6 +225,10 @@ def build_elf(rel_path, elf_path): rel.sectionInfo[4].offset = 0x37F440 rel.sectionInfo[5].offset = 0x3A28F0 rel.sectionInfo[6].offset = 0 + + # This seems to affect tz, but absolutely no one else? + # Hack: Fix .bss size + rel.sectionInfo[6].size = 0x78b0 rel.imps[0].moduleId = 1 rel.imps[0].offset = 0x3CD104 From 6b82b37905b170f5d1d532f83f9b0e30e18c4fb8 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 2 May 2021 09:57:56 -0600 Subject: [PATCH 038/477] :hammer: Unreproducable bug with .bss section size. Hardcode it for now. --- build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.py b/build.py index a1c935b95..2fe39293c 100644 --- a/build.py +++ b/build.py @@ -228,7 +228,7 @@ def build_elf(rel_path, elf_path): # This seems to affect tz, but absolutely no one else? # Hack: Fix .bss size - rel.sectionInfo[6].size = 0x78b0 + rel.sectionInfo[6].length = 0x78b0 rel.imps[0].moduleId = 1 rel.imps[0].offset = 0x3CD104 From bcf79dc673b6406e1a36f9bf6ca6fa65a4f4400f Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 2 May 2021 09:58:27 -0600 Subject: [PATCH 039/477] :recycle: Cleanup eggHeap.cpp --- source/egg/core/eggHeap.cpp | 8 +------- source/egg/core/eggHeap.hpp | 2 ++ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp index af4d02d56..395255951 100644 --- a/source/egg/core/eggHeap.cpp +++ b/source/egg/core/eggHeap.cpp @@ -4,6 +4,7 @@ */ #include #include +#include #include #include #include @@ -76,13 +77,6 @@ struct HeapErrorArg { inline HeapErrorArg() {} }; -struct Thread { - static Thread* findThread(OSThread*); - u32 vtable; - Heap* mHeapHandle; - char _[60 - 8]; - Heap* mAlloctableHeap; -}; struct rvlHeap { char _[0x1c]; u32 arena_end; diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 48ed0e685..2a56b18c5 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -8,7 +8,9 @@ #include #include #include +#ifndef RII_CLIENT #include +#endif namespace EGG { From 57b3465a1f6d8d75e12cc7c043cd4225c5958535 Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Mon, 3 May 2021 11:14:21 +0200 Subject: [PATCH 040/477] Add support for building from Linux - stebler (#1) * Remove unused variables * Use cross-platform path separators * Add support for building from Linux --- build.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/build.py b/build.py index 2fe39293c..4897ace87 100644 --- a/build.py +++ b/build.py @@ -18,6 +18,19 @@ def read_u32(f): return struct.unpack(">I", f.read(4))[0] +import sys + +def native_binary(path): + if sys.platform == "win32" or sys.platform == "msys": + return path + ".exe" + else: + return path + +def windows_binary(path): + if sys.platform == "win32" or sys.platform == "msys": + return path + else: + return "wine " + path import os @@ -25,20 +38,14 @@ def read_u32(f): DEVKITPPC = os.environ["DEVKITPPC"] -GCC = os.path.join(DEVKITPPC, "bin\\powerpc-eabi-gcc.exe") -GAS = os.path.join(DEVKITPPC, "bin\\powerpc-eabi-as.exe") - -MWLD = "tools\\mwldeppc.exe" +GAS = native_binary(os.path.join(DEVKITPPC, "bin", "powerpc-eabi-as")) -ELF2DOL = "tools\\elf2dol.exe" +MWLD = windows_binary(os.path.join("tools", "mwldeppc.exe")) -SOURCE_PATH = "./source/" -ASM_TEXT_PATH = "./asm/text/" -BUILD_PATH = "./build/" -CWCC_OLD = False +ELF2DOL = windows_binary(os.path.join("tools", "elf2dol.exe")) CWCC_PATHS = { - 'default': ".\\tools\\4199_60831\\mwcceppc.exe", + 'default': windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), # For the main game # August 17, 2007 @@ -48,15 +55,15 @@ def read_u32(f): # We don't have this, so we use build 142: # This version has the infuriating bug where random # nops are inserted into your code. - '4201_127': ".\\tools\\4201_142\\mwcceppc.exe", + '4201_127': windows_binary(os.path.join(".", "tools", "4201_142", "mwcceppc.exe")), # For most of RVL # We actually have the correct version - '4199_60831': ".\\tools\\4199_60831\\mwcceppc.exe", + '4199_60831': windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), # For HBM/WPAD, NHTTP/SSL # We use build 60831 - '4199_60726': '\\tools\\4199_60831\\mwcceppc.exe' + '4199_60726': windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), } CWCC_OPT = " ".join([ "-nodefaults", @@ -341,4 +348,4 @@ def verify_dol(): print("[REL] Oof: Output doesn't match.") # TODO: Add diff'ing -build() \ No newline at end of file +build() From ee85a70cc5b5a4ac4c7817f97f618ad292e2d2b9 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 3 May 2021 03:26:43 -0600 Subject: [PATCH 041/477] :recycle: Clean up eggArchive --- source/egg/core/eggArchive.cpp | 81 +++++++++++++++++----------------- source/egg/core/eggArchive.hpp | 34 +++++++------- source/rvl/arc/rvlArchive.h | 6 +-- source/rvl/rvlDvd.h | 17 +++++++ 4 files changed, 78 insertions(+), 60 deletions(-) create mode 100644 source/rvl/rvlDvd.h diff --git a/source/egg/core/eggArchive.cpp b/source/egg/core/eggArchive.cpp index 6e2ad3d59..42aa45f2d 100644 --- a/source/egg/core/eggArchive.cpp +++ b/source/egg/core/eggArchive.cpp @@ -5,13 +5,7 @@ #include #include - -struct rvlDvdFile { - char _[0x3c]; -}; -extern "C" unk32 DVDOpen(const char*, rvlDvdFile*); -extern "C" u32 DVDReadPrio(rvlDvdFile*, void*, u32, unk32, unk32); -extern "C" unk32 DVDClose(rvlDvdFile*); +#include namespace EGG { @@ -25,56 +19,62 @@ void Archive::removeList(Archive* pArchive) { } Archive* Archive::mount(void* arcStart, Heap* pHeap, int align) { - Archive* wArchive = findArchive(arcStart); // r30, INLINE archive wrapper - if (wArchive == nullptr) { - wArchive = new (pHeap, align) Archive(); // INLINE - EGG_ASSERT(wArchive, "eggArchive.cpp", 159, "archive != NULL"); - - bool bInitSuccess = wArchive->initHandle(arcStart); - if (bInitSuccess) - wArchive->mStatus = LOADED_AND_CAN_FAST_READ; - else - wArchive->mStatus = NOT_LOADED; - EGG_ASSERT(bInitSuccess, "eggArchive.cpp", 166, "false"); + Archive* archive = findArchive(arcStart); // r30, INLINE archive wrapper + + if (archive == nullptr) { + archive = new (pHeap, align) Archive(); // INLINE + EGG_ASSERT(archive, "eggArchive.cpp", 159, "archive != NULL"); + + bool could_create = archive->initHandle(arcStart); + if (could_create) { + archive->mStatus = LOADED_AND_CAN_FAST_READ; + } else { + archive->mStatus = NOT_LOADED; + EGG_ASSERT(false, "eggArchive.cpp", 166, "false"); + } + // If we failed, clean up - if (!bInitSuccess) // INLINE + if (!could_create) // INLINE { - delete wArchive; - wArchive = nullptr; + delete archive; + archive = nullptr; } } else { - wArchive->_14++; + ++archive->mRefCount; } - return wArchive; + return archive; } + // exact same as above but _10 set to 2 not 1 Archive* Archive::mountNoFastGet(void* arcStart, Heap* pHeap, int align) { - Archive* wArchive = findArchive(arcStart); // r30, INLINE archive wrapper - if (wArchive == nullptr) { - wArchive = new (pHeap, align) Archive(); // INLINE - EGG_ASSERT(wArchive, "eggArchive.cpp", 159, "archive != NULL"); - - bool bInitSuccess = wArchive->initHandle(arcStart); - if (bInitSuccess) - wArchive->mStatus = LOADED; - else - wArchive->mStatus = NOT_LOADED; - EGG_ASSERT(bInitSuccess, "eggArchive.cpp", 166, "false"); + Archive* archive = findArchive(arcStart); // r30, INLINE archive wrapper + if (archive == nullptr) { + archive = new (pHeap, align) Archive(); // INLINE + EGG_ASSERT(archive, "eggArchive.cpp", 159, "archive != NULL"); + + bool could_create = archive->initHandle(arcStart); + if (could_create) { + archive->mStatus = LOADED; + } else { + archive->mStatus = NOT_LOADED; + EGG_ASSERT(false, "eggArchive.cpp", 166, "false"); + } + // If we failed, clean up - if (!bInitSuccess) // INLINE + if (!could_create) // INLINE { - delete wArchive; - wArchive = nullptr; + delete archive; + archive = nullptr; } } else { - wArchive->_14++; + ++archive->mRefCount; } - return wArchive; + return archive; } void Archive::unmount() { - if (_14 != 0 && --_14 == 0) { + if (mRefCount != 0 && --mRefCount == 0) { mStatus = NOT_LOADED; delete this; } @@ -135,6 +135,7 @@ void* Archive::loadFromDisc(const char* path, Heap* pHeap, int align) { } pHeap->free(readHeader); } + DVDClose(&dvdFileInfo); return ARC; } diff --git a/source/egg/core/eggArchive.hpp b/source/egg/core/eggArchive.hpp index 5845aaf69..9a650f05e 100644 --- a/source/egg/core/eggArchive.hpp +++ b/source/egg/core/eggArchive.hpp @@ -10,13 +10,22 @@ #include #include #include -extern "C" { #include -} + extern "C" void memset(void*, int, u32); namespace EGG { +struct LowArchive : public rvlArchive { + inline void reset() { memset(this, 0, sizeof(*this)); } + inline bool open(rvlArchiveEntryHandle path, rvlArchiveFile& file) { + return ARCFastOpen(this, path, &file); + } + inline bool open(const char* path, rvlArchiveFile& file) { + return ARCOpen(this, path, &file); + } +}; + class Archive : public Disposer // sizeof=60,0x3C { public: @@ -58,8 +67,7 @@ class Archive : public Disposer // sizeof=60,0x3C } //! @brief Mount an archive. - //! @details If the archive is already mounted, _14 of that archive will be - //! incremented and a pointer to that archive will be returned. + //! @details If the archive is already mounted, it will be used. //! @returns A pointed to the mounted EGG Archive if successful. Otherwise, //! NULL. //! @@ -70,7 +78,7 @@ class Archive : public Disposer // sizeof=60,0x3C //! static Archive* mountNoFastGet(void* pArcStart, Heap* pHeap, int align); - //! @brief Unmount an archive. (Set the status as NOT_LOADED and destroy self) + //! @brief Unmount an archive. (Set the status as NOT_LOADED and decrease refcount) //! void unmount(); @@ -89,18 +97,8 @@ class Archive : public Disposer // sizeof=60,0x3C return sIsArchiveListInitialized; } - struct LowArchive : public rvlArchive { - inline void reset() { memset(this, 0, sizeof(*this)); } - inline bool open(rvlArchiveEntryHandle path, rvlArchiveFile& file) { - return ARCFastOpen(this, path, &file); - } - inline bool open(const char* path, rvlArchiveFile& file) { - return ARCOpen(this, path, &file); - } - }; - Archive() { - _14 = 1; + mRefCount = 1; mStatus = NOT_LOADED; mArcHandle.reset(); if (!sIsArchiveListInitialized) { @@ -121,8 +119,10 @@ class Archive : public Disposer // sizeof=60,0x3C //! static void removeList(Archive* pArchive); +// Part of disposer? u32 _08; // unseen u32 _0C; // unseen +// enum Status { NOT_LOADED, //!< [0] @@ -131,7 +131,7 @@ class Archive : public Disposer // sizeof=60,0x3C //!< to 1. }; Status mStatus; //!< [+0x10] set to 0 in ct - int _14; //!< [+0x14] set to 1 in ct; numMounts? + int mRefCount; //!< [+0x14] set to 1 in ct LowArchive mArcHandle; // 0x18 char _unk[8]; diff --git a/source/rvl/arc/rvlArchive.h b/source/rvl/arc/rvlArchive.h index f6f28ba4c..d17ace32c 100644 --- a/source/rvl/arc/rvlArchive.h +++ b/source/rvl/arc/rvlArchive.h @@ -1,12 +1,12 @@ #pragma once -#include // u32 -#include // bool - #ifdef __cplusplus extern "C" { #endif +#include // u32 +#include // bool + //! Describes the type of an entry in an archive. //! typedef enum rvlArchiveType { diff --git a/source/rvl/rvlDvd.h b/source/rvl/rvlDvd.h new file mode 100644 index 000000000..58502ce60 --- /dev/null +++ b/source/rvl/rvlDvd.h @@ -0,0 +1,17 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +struct rvlDvdFile { + char _[0x3c]; +}; + +unk32 DVDOpen(const char*, rvlDvdFile*); +u32 DVDReadPrio(rvlDvdFile*, void*, u32, unk32, unk32); +unk32 DVDClose(rvlDvdFile*); + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 053493a9d21496cb6efaabb2de0b6bacea7698ae Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 3 May 2021 03:30:35 -0600 Subject: [PATCH 042/477] :construction: Expanded EGG::Disposer to include 8 unused bytes --- source/egg/core/eggArchive.hpp | 5 ----- source/egg/core/eggDisposer.hpp | 4 ++++ source/egg/core/eggHeap.hpp | 4 +--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/source/egg/core/eggArchive.hpp b/source/egg/core/eggArchive.hpp index 9a650f05e..71450ebae 100644 --- a/source/egg/core/eggArchive.hpp +++ b/source/egg/core/eggArchive.hpp @@ -119,11 +119,6 @@ class Archive : public Disposer // sizeof=60,0x3C //! static void removeList(Archive* pArchive); -// Part of disposer? - u32 _08; // unseen - u32 _0C; // unseen -// - enum Status { NOT_LOADED, //!< [0] LOADED_AND_CAN_FAST_READ, //!< [1] diff --git a/source/egg/core/eggDisposer.hpp b/source/egg/core/eggDisposer.hpp index c73234ab5..685ede562 100644 --- a/source/egg/core/eggDisposer.hpp +++ b/source/egg/core/eggDisposer.hpp @@ -29,6 +29,10 @@ class Disposer { Heap* mContainHeap; //!< [+0x04] Heap that contains the instance of this //!< disposer. + + // All base classes appear to have this, unused. + char _08[4]; // unseen + char _0C[4]; // unseen }; } // namespace EGG diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 2a56b18c5..7e2e905a8 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -79,10 +79,8 @@ class Heap : public Disposer { static void* sErrorCallbackArg; //!< TODO static void* sAllocCallbackArg; //!< TODO static struct Thread* sAllocatableThread; //!< TODO + public: - //! @brief [+0x08, +0x0c] unseen - u32 _08; - u32 _0C; //! @brief [+0x10] argument of heap constructor. Name confirmed by WS assert. struct rvlHeap* mHeapHandle; //! @brief [+0x14] set to 0 in heap ctor. treeki -- void* parentHeapMBlock From c58ea95192dcb1cb0cf9942e643aa889613ba2a0 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 3 May 2021 04:13:37 -0600 Subject: [PATCH 043/477] :construction: Cleaned up EGG files --- source/egg/core/eggDisposer.hpp | 1 + source/egg/core/eggGraphicsFifo.cpp | 2 +- source/egg/core/eggHeap.cpp | 85 ++++++++++++----------------- source/egg/core/eggHeap.hpp | 64 ++++++++++++++++++++-- source/egg/core/eggThread.cpp | 4 +- source/egg/core/eggThread.hpp | 37 ++++++++----- 6 files changed, 121 insertions(+), 72 deletions(-) diff --git a/source/egg/core/eggDisposer.hpp b/source/egg/core/eggDisposer.hpp index 685ede562..4810c2cf0 100644 --- a/source/egg/core/eggDisposer.hpp +++ b/source/egg/core/eggDisposer.hpp @@ -27,6 +27,7 @@ class Disposer { //! Disposer(); +private: Heap* mContainHeap; //!< [+0x04] Heap that contains the instance of this //!< disposer. diff --git a/source/egg/core/eggGraphicsFifo.cpp b/source/egg/core/eggGraphicsFifo.cpp index 9844c3adc..b19824c70 100644 --- a/source/egg/core/eggGraphicsFifo.cpp +++ b/source/egg/core/eggGraphicsFifo.cpp @@ -11,7 +11,7 @@ GraphicsFifo::GPStatus GraphicsFifo::sGpStatus; GraphicsFifo* GraphicsFifo::create(u32 fifoSize, Heap* heap) { EGG_ASSERT(!sGraphicsFifo, "eggGraphicsFifo.cpp", 59, "!sGraphicsFifo"); if (heap == nullptr) - heap = Heap::sCurrentHeap; + heap = Heap::getCurrentHeap(); EGG_ASSERT(!!"OSIsMEM1Region(heap)", "eggGraphicsFifo.cpp", 69, "OSIsMEM1Region( heap )"); diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp index 395255951..4e218d8ab 100644 --- a/source/egg/core/eggHeap.cpp +++ b/source/egg/core/eggHeap.cpp @@ -2,6 +2,9 @@ * @file * @brief Heap implementations. */ + +#define HEAP_PRIVATE public + #include #include #include @@ -11,7 +14,6 @@ extern "C" void OSReport(const char*, ...); extern "C" struct OSThread* OSGetCurrentThread(); -extern "C" EGG::rvlHeap* MEMFindContainHeap(const void*); namespace EGG { @@ -23,7 +25,7 @@ Heap* Heap::sCurrentHeap; int Heap::sIsHeapListInitialized; Heap* Heap::sAllocatableHeap; ErrorCallback Heap::sErrorCallback; -ErrorCallback Heap::sAllocCallback; +HeapAllocCallback Heap::sAllocCallback; void* Heap::sErrorCallbackArg; void* Heap::sAllocCallbackArg; @@ -62,51 +64,28 @@ Heap::~Heap() { OSUnlockMutex(&sRootMutex); } -struct HeapAllocArg { - int userArg; // 00 - u32 size; // 04 - int align; // 08 - Heap* heap; // 0C heap to allocate in - - inline HeapAllocArg() : userArg(0), size(0), align(0), heap(nullptr) {} -}; -struct HeapErrorArg { - const char* msg; - void* userdata; - - inline HeapErrorArg() {} -}; - -struct rvlHeap { - char _[0x1c]; - u32 arena_end; -}; - -inline int getArenaEnd(Heap* currentHeap) { - return currentHeap->mHeapHandle->arena_end; -} #pragma dont_reuse_strings on void* Heap::alloc(u32 size, int align, Heap* heap) { - Heap* currentHeap = sCurrentHeap; // r28 - // r30 -> size - // r31 -> align - // r27 -> heap + Heap* currentHeap = sCurrentHeap; // r28 Thread* currentThread = Thread::findThread(OSGetCurrentThread()); // r29 - if (sAllocatableThread) { - OSGetCurrentThread(); // this does absolutely nothing. possibly debug - // message stripped? + if (sAllocatableThread != nullptr) { + // this does absolutely nothing. possibly debug + // message stripped? + OSGetCurrentThread(); } // If our thread defines a heap override, use it! - if (currentThread && currentThread->mAlloctableHeap) - heap = currentHeap = currentThread->mAlloctableHeap; + if (currentThread != nullptr && + currentThread->getAllocatableHeap() != nullptr) + heap = currentHeap = currentThread->getAllocatableHeap(); // if sAllocatableHeap is not nullptr, it *must* be used // this has higher priority over the thread heap override! - if (sAllocatableHeap) { - if (currentHeap && !heap) + if (sAllocatableHeap != nullptr) { + if (currentHeap != nullptr && heap == nullptr) heap = currentHeap; + // This would be reached if // - sCurrentHeap && !heap && sCurrentHeap != sAlloctableHeap // - heap && heap != sAllocatableHeap @@ -114,13 +93,18 @@ void* Heap::alloc(u32 size, int align, Heap* heap) { OSReport( "cannot allocate from heap %x(%s) : allocatable heap is %x(%s)\n", heap, heap->mName, sAllocatableHeap, sAllocatableHeap->mName); - OSReport("\tthread heap=%x\n", - currentThread ? currentThread->mAlloctableHeap : 0); - OSReport("\tthread heap=%s\n", - currentThread ? (currentThread->mAlloctableHeap - ? currentThread->mAlloctableHeap->mName - : "none") - : "none"); + + const Heap* primary_heap = + currentThread ? currentThread->getAllocatableHeap() : nullptr; + + OSReport("\tthread heap=%x\n", primary_heap); + + const char* primary_heap_name = + currentThread ? (currentThread->getAllocatableHeap() + ? currentThread->getAllocatableHeap()->mName + : "none") + : "none"; + OSReport("\tthread heap=%s\n", primary_heap_name); if (sErrorCallback) { HeapErrorArg cb; cb.msg = "disable_but"; @@ -138,7 +122,7 @@ void* Heap::alloc(u32 size, int align, Heap* heap) { arg.size = size; arg.align = align; arg.userArg = (int)sAllocCallbackArg; - sAllocCallback(&arg); + sAllocCallback(arg); } // If heap is non nullptr, use that if (heap != nullptr) @@ -151,7 +135,7 @@ void* Heap::alloc(u32 size, int align, Heap* heap) { void* allocated = currentHeap->alloc(size, align); // (r3), r27 if (allocated == nullptr) { - int arena_end = getArenaEnd(currentHeap); // r29 + int arena_end = currentHeap->getArenaEnd(); // r29 int max_avail = currentHeap->getAllocatableSize(4); int heap_size = arena_end - (u32)currentHeap; OSReport("heap (%p):(%.1fMBytes free %d)->alloc(size(%d:%.1fMBytes),%d " @@ -167,12 +151,14 @@ void* Heap::alloc(u32 size, int align, Heap* heap) { OSReport("cannot allocate %d from heap %x\n", size, heap); dumpAll(); + return nullptr; } Heap* Heap::findParentHeap() { - EGG_ASSERT(this->mHeapHandle, "eggHeap.cpp", 173, "mHeapHandle != nullptr"); - return this->mParentHeap; + EGG_ASSERT(mHeapHandle, "eggHeap.cpp", 173, "mHeapHandle != nullptr"); + + return mParentHeap; } Heap* Heap::findContainHeap(const void* memBlock) { @@ -340,8 +326,9 @@ void* operator new[](size_t size, int align) { void* operator new[](size_t size, EGG::Heap* heap, int align) { return EGG::Heap::alloc(size, align, heap); } + void operator delete(void* memBlock) { - EGG::rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 // If our memory block is not inside a head, there is not much we can do. if (!containHeap) return; @@ -364,7 +351,7 @@ void operator delete(void* memBlock) { heap->free(memBlock); } void operator delete[](void* memBlock) { - EGG::rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 // If our memory block is not inside a head, there is not much we can do. if (!containHeap) return; diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 7e2e905a8..fd3522db6 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -5,17 +5,53 @@ #pragma once +#ifndef HEAP_PRIVATE +#define HEAP_PRIVATE private +#endif + #include #include #include #ifndef RII_CLIENT #include #endif +#include + +extern "C" { + +typedef struct rvlHeap { + char _[0x1c]; + u32 arena_end; +} rvlHeap; + +rvlHeap* MEMFindContainHeap(const void*); + +} namespace EGG { class ExpHeap; class Allocator; + + +struct HeapAllocArg { + int userArg; // 00 + u32 size; // 04 + int align; // 08 + Heap* heap; // 0C heap to allocate in + + inline HeapAllocArg() : userArg(0), size(0), align(0), heap(nullptr) {} +}; +typedef void (*HeapAllocCallback)(HeapAllocArg& arg); + +struct HeapErrorArg { + const char* msg; + void* userdata; + + inline HeapErrorArg() {} +}; + + typedef void (*ErrorCallback)(void*); //! @brief Base Heap class @@ -39,6 +75,7 @@ class Heap : public Disposer { inline void* getStartAddress() { return this; } virtual ~Heap(); + //! @brief [vt+0x0C] Get the type of heap the current heap is. //! //! @returns The eHeapKing of the heap. @@ -53,6 +90,7 @@ class Heap : public Disposer { virtual u32 getAllocatableSize(s32 align) = 0; // [vt+0x24] virtual u32 adjust() = 0; // [vt+0x28] +HEAP_PRIVATE: //! @brief Static linked-list of heaps. //! //! @details When a heap is created, it is appended to this list. @@ -75,14 +113,13 @@ class Heap : public Disposer { //! This will restrict rather than redirect allocations. static Heap* sAllocatableHeap; static ErrorCallback sErrorCallback; //!< TODO - static ErrorCallback sAllocCallback; //!< TODO + static HeapAllocCallback sAllocCallback; //!< TODO static void* sErrorCallbackArg; //!< TODO static void* sAllocCallbackArg; //!< TODO - static struct Thread* sAllocatableThread; //!< TODO + static class Thread* sAllocatableThread; //!< TODO -public: //! @brief [+0x10] argument of heap constructor. Name confirmed by WS assert. - struct rvlHeap* mHeapHandle; + rvlHeap* mHeapHandle; //! @brief [+0x14] set to 0 in heap ctor. treeki -- void* parentHeapMBlock void* mParentBlock; //! @brief [+0x18] name from findParentHeap() @@ -98,10 +135,12 @@ class Heap : public Disposer { //! [+0x20, +0x24] unseen treeki -- globalLink u32 _20; u32 _24; + //! @details List of child disposers. //! When Heap::dispose() is called, ~Disposer() will be called for all //! children. nw4r::ut::List mChildren; //!< [+0x28] sizeof=0xC + const char* mName; //!< [+0x034] set to "NoName" in ctor public: @@ -137,7 +176,7 @@ class Heap : public Disposer { //! //! @return this //! - Heap(struct rvlHeap* heapHandle); + Heap(rvlHeap* heapHandle); //! @brief Allocate a block of memory in a heap. //! @@ -225,6 +264,19 @@ class Heap : public Disposer { inline void removeDisposer(Disposer* disposer) { nw4r::ut::List_Remove(&mChildren, disposer); } + + inline rvlHeap* getHeapHandle() { + return mHeapHandle; + } + + static inline Heap* getCurrentHeap() { + return sCurrentHeap; + } + + inline int getArenaEnd() { + return mHeapHandle->arena_end; + } + }; } // namespace EGG @@ -246,3 +298,5 @@ void* operator new[](size_t size, EGG::Heap* heap, int align); void operator delete(void* p); // __dla(void *) void operator delete[](void*); + +#undef HEAP_PRIVATE \ No newline at end of file diff --git a/source/egg/core/eggThread.cpp b/source/egg/core/eggThread.cpp index 47a25ee9a..a89f14ef5 100644 --- a/source/egg/core/eggThread.cpp +++ b/source/egg/core/eggThread.cpp @@ -8,7 +8,7 @@ nw4r::ut::List Thread::sThreadList; Thread::Thread(u32 stackSize, int msgCount, int prio, Heap* heap) { if (!heap) - heap = Heap::sCurrentHeap; + heap = Heap::getCurrentHeap(); mContainingHeap = heap; mStackSize = ROUND_DOWN(stackSize, 32); @@ -35,7 +35,7 @@ Thread::Thread(OSThread* osThread, int msgCount) mStackMemory = osThread->stack_high; mAlloctableHeap = nullptr; - setCommonMesgQueue(msgCount, Heap::sCurrentHeap); + setCommonMesgQueue(msgCount, Heap::getCurrentHeap()); } Thread::~Thread() { diff --git a/source/egg/core/eggThread.hpp b/source/egg/core/eggThread.hpp index 9cac52f28..7ca5f5d79 100644 --- a/source/egg/core/eggThread.hpp +++ b/source/egg/core/eggThread.hpp @@ -25,22 +25,7 @@ class Thread { virtual void onEnter(); /*{}*/ //!< [vt+0x14] virtual void onExit(); /*{}*/ //!< [vt+0x10] - Heap* mContainingHeap; //!< [+0x04] Heap to use for thread-specific - //!< allocations (in ctor: create stack and OSThread) - OSThread* mOSThread; //!< [+0x08] The OS thread this object wraps. Name - //!< confirmed by WS assert. - OSMessageQueue mMesgQueue; //!< [+0x0C] sizeof=0x20 - OSMessage* mMesgBuffer; //!< [+0x2C] name confirmed WS assert - int mMesgCount; //!< [+0x30] - - char* mStackMemory; //!< [+0x34] The base (*beginning*) of the stack. - u32 mStackSize; //!< [+0x38] The size of the stack. - - Heap* mAlloctableHeap; //!< [0+x3C] When not NULL will override the heap used - //!< for allocations. (Note: Does not escape the - //!< Heap::sAllocatableHeap restriction.) - nw4r::ut::Node mLink; //! @brief A constructor. //! //! @details Creates an EGG::Thread and OSThread from arguments. @@ -135,9 +120,31 @@ class Thread { //! static void* start(void* eggThread); + //! When not NULL will override the heap used for allocations. + inline Heap* getAllocatableHeap() { + return mAlloctableHeap; + } + private: // List of all registered threads. static nw4r::ut::List sThreadList; + + Heap* mContainingHeap; //!< [+0x04] Heap to use for thread-specific + //!< allocations (in ctor: create stack and OSThread) + OSThread* mOSThread; //!< [+0x08] The OS thread this object wraps. Name + //!< confirmed by WS assert. + + OSMessageQueue mMesgQueue; //!< [+0x0C] sizeof=0x20 + OSMessage* mMesgBuffer; //!< [+0x2C] name confirmed WS assert + int mMesgCount; //!< [+0x30] + + char* mStackMemory; //!< [+0x34] The base (*beginning*) of the stack. + u32 mStackSize; //!< [+0x38] The size of the stack. + + Heap* mAlloctableHeap; //!< [0+x3C] When not NULL will override the heap used + //!< for allocations. (Note: Does not escape the + //!< Heap::sAllocatableHeap restriction.) + nw4r::ut::Node mLink; }; } // namespace EGG From dd453a439bb1ed312f4e158be6835e7677f77f16 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 3 May 2021 04:15:09 -0600 Subject: [PATCH 044/477] :pencil2: Fixed typos --- README.md | 2 +- build.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2a6d24b7..1e26d30de 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Every fully understood piece of reverse engineered data has been documented in a - Place a copy of Mario Kart Wii's PAL .dol file in `artifacts/pal` as `main.dol` ## Contributing -- Do not manually adjust assembly files; rather add a new entry to [system/slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv) for main.dol and [system/rel_slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv) for StaticR.rel. +- Do not manually adjust assembly files; rather add a new entry to [system/slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv) for main.dol and [system/rel_slices.csv](https://github.com/riidefi/mkw/blob/master/system/rel_slices.csv) for StaticR.rel. - Entries must be sorted in the spreadsheet (current limitation). - Run `gen_asm.py` from the `system` directory to regenerate assembly segments. diff --git a/build.py b/build.py index 4897ace87..a6a33a917 100644 --- a/build.py +++ b/build.py @@ -345,7 +345,7 @@ def verify_dol(): print("[DOL] Everything went okay! Output is matching! ^^") return - print("[REL] Oof: Output doesn't match.") + print("[DOL] Oof: Output doesn't match.") # TODO: Add diff'ing build() From 211ddccbeedd8546ee0324e6646892513e8181c1 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 3 May 2021 04:41:33 -0600 Subject: [PATCH 045/477] :recycle: Cleaned up gen_asm.py --- system/gen_asm.py | 62 +++++++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/system/gen_asm.py b/system/gen_asm.py index 6a63ab3b6..d3677ce85 100644 --- a/system/gen_asm.py +++ b/system/gen_asm.py @@ -4,6 +4,7 @@ import os from ppc_dis import * +from contextlib import redirect_stdout def read_u8(f): @@ -159,8 +160,18 @@ def dump_bss(size): print(".skip 0x%x" % size) # stdout must be redirected -def dump_data(): - pass +def dump_data(image, addr_start, seg): + for i in range(seg.begin, seg.end, 4): + if seg.end - i >= 4: + print(".4byte 0x%08X" % read_u32b(image, i - addr_start)) + continue + + for j in range(i, seg.end): + print(".byte 0x%02x" % image[j - addr_start]) + +# stdout must be redirected +def dump_text(image, addr_start, seg): + disasm_iter(image, seg.begin - addr_start, seg.begin, seg.size(), disassemble_callback) def compute_perm(name): perm = "wa" @@ -176,37 +187,46 @@ def compute_perm(name): return perm # stdout must be redirected -def dump_section_data(name, image, addr_start, seg): +def dump_section_body(name, image, addr_start, seg): if "bss" in name: dump_bss(seg.size()) return if name == "text" or name == "init": - disasm_iter(image, seg.begin - addr_start, seg.begin, seg.size(), disassemble_callback) + dump_text(image, addr_start, seg) return - for i in range(seg.begin, seg.end, 4): - if seg.end - i >= 4: - print(".4byte 0x%08X" % read_u32b(image, i - addr_start)) - continue - - for j in range(i, seg.end): - print(".byte 0x%02x" % image[j - addr_start]) - -def disasm(name, image, addr_start, seg, is_data): - file = open("../" + format_gap(name, seg), 'w') - original_stdout = sys.stdout - sys.stdout = file + dump_data(image, addr_start, seg) +# stdout must be redirected +def dump_section_header(name, seg): # section permissions perm = compute_perm(name) + + print("\n.section %s, \"%s\" # 0x%08X - 0x%08X" % (format_segname(name), perm, seg.begin, seg.end)) + +# stdout must be redirected +def dump_section(name, image, addr_start, seg): + dump_section_header(name, seg) + dump_section_body(name, image, addr_start, seg) +# stdout must be redirected +def dump_object_file(image, addr_start, segments = []): print("\n.include \"macros.inc\"") - print("\n.section %s, \"%s\" # 0x%08X - 0x%08X" % (format_segname(name), perm, seg.begin, seg.end)) - - dump_section_data(name, image, addr_start, seg) - sys.stdout = original_stdout + for segment_name, segment in segments: + dump_section(segment_name, image, addr_start, segment) + +def disassemble_object_file(path, image, addr_start, segments = []): + with open(path, 'w') as file: + with redirect_stdout(file): + dump_object_file(image, addr_start, segments) + + +def disasm(name, image, addr_start, seg, is_data): + path = "../" + format_gap(name, seg) + + disassemble_object_file(path, image, addr_start, [ (name, seg) ]) def gen_start_segs(segments): @@ -312,7 +332,7 @@ def unpack_base_dol(): ## REL -def load_rel_binary(segments): +def load_rel_binary(segments) -> (bytearray, int): print(segments) max_vaddr = max(segments[seg].end for seg in segments) image_base = 0x80000000 From 95e36f5b2538f2fb2b4a45192e3ad6885c4e28c8 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Mon, 3 May 2021 05:15:09 -0600 Subject: [PATCH 046/477] :recycle: Split asm into rel and dol folders --- build.py | 24 +++++++++---- o_files.txt | 90 +++++++++++++++++++++++------------------------ rel_o_files.txt | 14 ++++---- system/gen_asm.py | 32 ++++++++++------- 4 files changed, 90 insertions(+), 70 deletions(-) diff --git a/build.py b/build.py index a6a33a917..33e333d42 100644 --- a/build.py +++ b/build.py @@ -92,6 +92,12 @@ def windows_binary(path): "-func_align 4" ]) +def require_folder(path): + try: + os.mkdir(path) + except FileExistsError: + pass + def postprocess(dst): command("python tools/postprocess.py -fsymbol-fixup %s" % dst) @@ -114,6 +120,7 @@ def command(cmd): os.system(cmd) def assemble(dst, src): + print(dst, src) cmd = GAS + " %s -mgekko -Iasm -o %s" % (src, dst) command(cmd) @@ -132,12 +139,13 @@ def make_obj(src): return src def gen_lcf(src, dst, o_files): + from pathlib import Path lcf = "" with open(src, 'r') as f: lcf = f.read() lcf += "\nFORCEFILES {\n" - lcf += "\n".join(x.replace("out/", "") for x in o_files) + lcf += "\n".join(Path(x).stem + ".o" for x in o_files) lcf += "\n}\n" with open(dst, 'w') as f: @@ -254,9 +262,7 @@ def build_elf(rel_path, elf_path): f.write(sec.data) def compile_sources(): - try: - os.mkdir("out") - except: pass + require_folder("out") RVL_OPTS = '-ipa file' EGG_OPTS = '-ipa function -rostr' @@ -281,10 +287,16 @@ def compile_sources(): compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', EGG_OPTS) from pathlib import Path - asm_files = ["asm/" + str(x.stem) + ".s" for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] + asm_files = [str(x.relative_to(os.getcwd())) for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] + + require_folder("out") + require_folder(os.path.join("out", "dol")) + require_folder(os.path.join("out", "rel")) for asm in asm_files: - assemble("out/" + make_obj(asm).replace("asm/", ""), asm) + # Hack: Should use pathlib for this + out_o = "out" + asm[len("asm"):] + assemble(make_obj(out_o), asm) def link_dol(o_files): gen_lcf("link.lcf", "out/generated.lcf", o_files) diff --git a/o_files.txt b/o_files.txt index aec3890a3..477afdff3 100644 --- a/o_files.txt +++ b/o_files.txt @@ -1,56 +1,56 @@ -text_800072c0.o -data_80258580.o -sbss_80385fc0.o +dol/text_800072c0.o +dol/data_80258580.o +dol/sbss_80385fc0.o dwc_error.o -text_800ccc80.o -data_80275758.o -sdata_80384c00.o +dol/text_800ccc80.o +dol/data_80275758.o +dol/sdata_80384c00.o rvlArchive.o -text_80124e80.o +dol/text_80124e80.o rvlMemList.o -text_80199d04.o -data_8027e772.o -bss_802a4080.o -sbss_803862b0.o +dol/text_80199d04.o +dol/data_8027e772.o +dol/bss_802a4080.o +dol/sbss_803862b0.o eggArchive.o -text_8020fcc4.o -data_802a268c.o +dol/text_8020fcc4.o +dol/data_802a268c.o eggDisposer.o -text_8021a1b8.o -data_802a2b54.o -sbss_80386d84.o +dol/text_8021a1b8.o +dol/data_802a2b54.o +dol/sbss_80386d84.o eggGraphicsFifo.o -rodata_80244ec0.o -data_802a30bc.o -bss_803832e4.o -sbss_80386e99.o -sdata2_80386fa0.o +dol/rodata_80244ec0.o +dol/data_802a30bc.o +dol/bss_803832e4.o +dol/sbss_80386e99.o +dol/sdata2_80386fa0.o eggHeap.o -text_80229fac.o +dol/text_80229fac.o eggQuat.o -text_80239e10.o -data_802a30ec.o +dol/text_80239e10.o +dol/data_802a30ec.o eggStreamDecomp.o -text_80242504.o -data_802a3f90.o -bss_80384348.o +dol/text_80242504.o +dol/data_802a3f90.o +dol/bss_80384348.o eggThread.o -text_80243754.o -ctors_80244de0.o -bss_80384b6c.o -sbss_80386ec0.o -sdata2_80388d80.o +dol/text_80243754.o +dol/ctors_80244de0.o +dol/bss_80384b6c.o +dol/sbss_80386ec0.o +dol/sdata2_80388d80.o eggVector.o -init_80004000.o -extab_80006460.o -extabindex_80006a20.o -text_80243d18.o -ctors_80244e8c.o -dtors_80244ea4.o -rodata_80257824.o -data_802a3fd8.o -bss_80384bf4.o -sdata_803857f6.o -sbss_80386f90.o -sdata2_80389104.o -sbss2_80389140.o \ No newline at end of file +dol/init_80004000.o +dol/extab_80006460.o +dol/extabindex_80006a20.o +dol/text_80243d18.o +dol/ctors_80244e8c.o +dol/dtors_80244ea4.o +dol/rodata_80257824.o +dol/data_802a3fd8.o +dol/bss_80384bf4.o +dol/sdata_803857f6.o +dol/sbss_80386f90.o +dol/sdata2_80389104.o +dol/sbss2_80389140.o \ No newline at end of file diff --git a/rel_o_files.txt b/rel_o_files.txt index 6d373e892..06350081d 100644 --- a/rel_o_files.txt +++ b/rel_o_files.txt @@ -1,8 +1,8 @@ -text_805103b4.o +rel/text_805103b4.o MessageGroup.o -text_805f8b90.o -ctors_8088f400.o -dtors_8088f704.o -rodata_8088f710.o -data_808b2bd0.o -bss_809bd6e0.o \ No newline at end of file +rel/text_805f8b90.o +rel/ctors_8088f400.o +rel/dtors_8088f704.o +rel/rodata_8088f710.o +rel/data_808b2bd0.o +rel/bss_809bd6e0.o \ No newline at end of file diff --git a/system/gen_asm.py b/system/gen_asm.py index d3677ce85..5a5ffb1bd 100644 --- a/system/gen_asm.py +++ b/system/gen_asm.py @@ -144,8 +144,8 @@ def __init__(self, file): -def format_gap(name, gap): - return "asm/%s_%s.s" % (name, hex(gap.begin)[2:]) +def format_gap(name, gap, folder): + return "asm/%s/%s_%s.s" % (folder, name, hex(gap.begin)[2:]) def format_segname(name): if "extab" in name: return name + "_" @@ -222,9 +222,17 @@ def disassemble_object_file(path, image, addr_start, segments = []): with redirect_stdout(file): dump_object_file(image, addr_start, segments) +def require_folder(path): + try: + os.mkdir(path) + except FileExistsError: + pass -def disasm(name, image, addr_start, seg, is_data): - path = "../" + format_gap(name, seg) +def disasm(folder, name, image, addr_start, seg, is_data): + require_folder(os.path.join("..", "asm")) + require_folder(os.path.join("..", "asm", folder)) + + path = os.path.join("..", format_gap(name, seg, folder)) disassemble_object_file(path, image, addr_start, [ (name, seg) ]) @@ -267,23 +275,23 @@ def find_gaps(all_slices): if not obj_file.startswith('#'): yield obj_file, None -def find_o_files(all_slices): +def find_o_files(all_slices, folder): for name, gap_seg in find_gaps(all_slices): if gap_seg is None: yield name, gap_seg, "??" continue - print(format_gap(name, gap_seg)) - dest = format_gap(name, gap_seg).replace("asm/", "").replace(".s", ".o") + print(format_gap(name, gap_seg, folder)) + dest = format_gap(name, gap_seg, folder).replace("asm/", "").replace(".s", ".o") yield name, gap_seg, dest -def unpack_binary(all_slices, image, addr_start): - for name, gap_seg, dest in find_o_files(all_slices): +def unpack_binary(folder, all_slices, image, addr_start): + for name, gap_seg, dest in find_o_files(all_slices, folder): is_decompiled = gap_seg is None if not is_decompiled: # print("name %s dest %s" % (name, dest)) - disasm(name, image, addr_start, gap_seg, False) + disasm(folder, name, image, addr_start, gap_seg, False) yield dest if is_decompiled: @@ -328,7 +336,7 @@ def unpack_base_dol(): slices, segments, cuts = compute_cuts_from_spreadsheets("../artifacts/pal/segments.csv", "slices.csv") # o_files - return list(unpack_binary(cuts, base_dol.image, base_dol.image_base)) + return list(unpack_binary("dol", cuts, base_dol.image, base_dol.image_base)) ## REL @@ -369,7 +377,7 @@ def unpack_staticr_rel(): image, image_base = load_rel_binary(segments) # o_files - return list(unpack_binary(cuts, image, image_base)) + return list(unpack_binary("rel", cuts, image, image_base)) def unpack_everything(): dol_o_files = unpack_base_dol() From d21ded47e9a9d832f993e96ce88dcc045c8f125d Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Mon, 3 May 2021 20:29:07 +0200 Subject: [PATCH 047/477] Replace elf2dol with Python code (#2) --- build.py | 80 ++++++++++++++++++++++++++++++++++++++++------ tools/elf2dol.exe | Bin 15872 -> 0 bytes 2 files changed, 70 insertions(+), 10 deletions(-) delete mode 100644 tools/elf2dol.exe diff --git a/build.py b/build.py index 33e333d42..7a79ea74e 100644 --- a/build.py +++ b/build.py @@ -42,8 +42,6 @@ def windows_binary(path): MWLD = windows_binary(os.path.join("tools", "mwldeppc.exe")) -ELF2DOL = windows_binary(os.path.join("tools", "elf2dol.exe")) - CWCC_PATHS = { 'default': windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), @@ -151,6 +149,75 @@ def gen_lcf(src, dst, o_files): with open(dst, 'w') as f: f.write(lcf) +def segment_is_text(segment): + from elftools.elf.constants import P_FLAGS + + return segment["p_flags"] & P_FLAGS.PF_X == P_FLAGS.PF_X + +def segment_is_data(segment): + return not segment_is_text(segment) and not segment_is_bss(segment) + +def segment_is_bss(segment): + return segment["p_filesz"] == 0 + +def write_to_dol_header(dol_file, offset, val): + import io + + dol_file.seek(offset) + dol_file.write(val.to_bytes(4, byteorder='big')) + dol_file.seek(0, io.SEEK_END) + +def write_segment_to_dol(idx, segment, dol_file): + write_to_dol_header(dol_file, 0x00 + 0x04 * idx, dol_file.tell()) + write_to_dol_header(dol_file, 0x48 + 0x04 * idx, segment["p_vaddr"]) + # align filesz to 0x20 + filesz = ((segment["p_filesz"] + 0x1f) >> 5) << 5 + write_to_dol_header(dol_file, 0x90 + 0x04 * idx, filesz) + + dol_file.write(segment.data()) + # align current dol size to 0x20 + size = 0x20 - dol_file.tell() & 0x1f + dol_file.write(bytes([0x00] * size)) + +def elf_to_dol(elf_path, dol_path): + from elftools.elf.elffile import ELFFile + + with open(elf_path, 'rb') as elf_file, open(dol_path, 'wb') as dol_file: + elf = ELFFile(elf_file) + num_segments = elf.num_segments() + + dol_file.write(bytes([0x00] * 0x100)) + + idx = 0 + for i in range(num_segments): + segment = elf.get_segment(i) + if not segment_is_text(segment): + continue + write_segment_to_dol(idx, segment, dol_file) + idx += 1 + + idx = 7 + for i in range(num_segments): + segment = elf.get_segment(i) + if not segment_is_data(segment): + continue + write_segment_to_dol(idx, segment, dol_file) + idx += 1 + + bss_start = 0 + bss_end = 0 + for i in range(num_segments): + segment = elf.get_segment(i) + if not segment_is_bss(segment): + continue + if bss_start == 0: + bss_start = segment["p_vaddr"] + bss_end = segment["p_vaddr"] + segment["p_memsz"] + write_to_dol_header(dol_file, 0xd8, bss_start) + bss_size = bss_end - bss_start + write_to_dol_header(dol_file, 0xdc, bss_size) + + write_to_dol_header(dol_file, 0xe0, elf["e_entry"]) def read_elf_sec(elf, name): from system.rel_repack import RelSection @@ -306,14 +373,7 @@ def link_dol(o_files): elf.seek(0x18) elf.write(bytes([0x80, 0x00, 0x60, 0xA4])) - command(ELF2DOL + " out/built.elf target/mkw_pal.dol -v -v") - - # This is a bug with elf2dol; this works fine with makedol - # for now I'll just patch it... - with open('target/mkw_pal.dol', 'r+b') as dol: - dol.seek(0xDC) - # bss_size - dol.write(bytes([0x00, 0x0E, 0x50, 0xFC])) + elf_to_dol("out/built.elf", "target/mkw_pal.dol") def link_rel(rel_o_files): gen_lcf("rel_link.lcf", "out/rel_generated.lcf", rel_o_files) diff --git a/tools/elf2dol.exe b/tools/elf2dol.exe deleted file mode 100644 index 77d12d992f21c96147365a9c4b61d05ce623a9f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15872 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 a`P7&-BG|E%%u25%R~Dt0rE~cx5Bx8Vr6P&| From b087a8ab69d8e45af542e22b25e2cb46bf8189b3 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 4 May 2021 14:33:44 -0600 Subject: [PATCH 048/477] :sparkles: Added .kmp data headers --- source/game/jmap/resource/Mapdata.hpp | 16 +++ source/game/jmap/resource/MapdataArea.hpp | 101 ++++++++++++++++++ source/game/jmap/resource/MapdataCamera.hpp | 44 ++++++++ source/game/jmap/resource/MapdataCannon.hpp | 15 +++ .../game/jmap/resource/MapdataCheckPath.hpp | 15 +++ .../game/jmap/resource/MapdataCheckPoint.hpp | 18 ++++ source/game/jmap/resource/MapdataCourse.hpp | 17 +++ .../game/jmap/resource/MapdataEnemyPath.hpp | 17 +++ .../game/jmap/resource/MapdataEnemyPoint.hpp | 24 +++++ source/game/jmap/resource/MapdataGeoObj.hpp | 23 ++++ source/game/jmap/resource/MapdataItemPath.hpp | 18 ++++ .../game/jmap/resource/MapdataItemPoint.hpp | 14 +++ .../game/jmap/resource/MapdataJugemPoint.hpp | 15 +++ source/game/jmap/resource/MapdataStage.hpp | 31 ++++++ .../game/jmap/resource/MapdataStartPoint.hpp | 16 +++ 15 files changed, 384 insertions(+) create mode 100644 source/game/jmap/resource/Mapdata.hpp create mode 100644 source/game/jmap/resource/MapdataArea.hpp create mode 100644 source/game/jmap/resource/MapdataCamera.hpp create mode 100644 source/game/jmap/resource/MapdataCannon.hpp create mode 100644 source/game/jmap/resource/MapdataCheckPath.hpp create mode 100644 source/game/jmap/resource/MapdataCheckPoint.hpp create mode 100644 source/game/jmap/resource/MapdataCourse.hpp create mode 100644 source/game/jmap/resource/MapdataEnemyPath.hpp create mode 100644 source/game/jmap/resource/MapdataEnemyPoint.hpp create mode 100644 source/game/jmap/resource/MapdataGeoObj.hpp create mode 100644 source/game/jmap/resource/MapdataItemPath.hpp create mode 100644 source/game/jmap/resource/MapdataItemPoint.hpp create mode 100644 source/game/jmap/resource/MapdataJugemPoint.hpp create mode 100644 source/game/jmap/resource/MapdataStage.hpp create mode 100644 source/game/jmap/resource/MapdataStartPoint.hpp diff --git a/source/game/jmap/resource/Mapdata.hpp b/source/game/jmap/resource/Mapdata.hpp new file mode 100644 index 000000000..3384d45d4 --- /dev/null +++ b/source/game/jmap/resource/Mapdata.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/source/game/jmap/resource/MapdataArea.hpp b/source/game/jmap/resource/MapdataArea.hpp new file mode 100644 index 000000000..f44c0c4a8 --- /dev/null +++ b/source/game/jmap/resource/MapdataArea.hpp @@ -0,0 +1,101 @@ +#pragma once + +#include +#include + +namespace Field { + +enum eMapdataAreaShape { + AREA_SHAPE_BOX, //!< [0] Rectangular prism area. + AREA_SHAPE_CYL, //!< [1] Cylinder area. + AREA_SHAPE_NUM +}; + +enum eMapdataAreaType { + AREA_TYPE_CAM, //!< [0] course.0 - "Camera" + + // ObjClip, // Moved below since course.0 version + + AREA_TYPE_EF_CONTROL, //!< [1] course.0 - "EfControl" + AREA_TYPE_FOG_CONTROL, //!< [2] course.0 - "FogControl" + AREA_TYPE_PULL_CONTROL, //!< [3] course.0 - "PullControl" + + AREA_TYPE_ENEMY_FALL, //!< [4] course.0 - "EnemyFall" + + AREA_TYPE_2D_MAP_AREA, //!< [5] course.0 - "2DmapArea" + + // Below not in couse.0 + AREA_TYPE_SOUND_CONTROL, + AREA_TYPE_B_TERESA_CONTROL, + + AREA_TYPE_OBJCLIP_CLASSIFY, + AREA_TYPE_OBJCLIP_DISCRIMINATE, + + AREA_TYPE_RESPAWN +}; + +class MapdataArea { +public: + //! @brief [+0x00] The shape of the 3d area volume (box/cylinder). + //! eMapdataAreaShape + //! @see MapdataAreaCalcBox, MapdataAreaCalcCylinder + //! + u8 shape; + + //! @brief [+0x01] The type of the area. eMapdataAreaType + //! + u8 type; + + //! @brief [+0x02] Index of linked camera when type is Camera. -1 when unused. + //! + s8 cameraIdx; + + //! @brief [+0x03] A higher number means a higher priority to choose which + //! area activates if multiple areas intersected. + //! + u8 priority; + + EGG::Vector3f position; //!< [+0x04] Translation of the AREA. + EGG::Vector3f rotation; //!< [+0x10] Rotation of the AREA. + EGG::Vector3f scaling; //!< [+0x1C] Scale of the AREA. + + //! @brief wiki - Used by AREA types: 2, 3, 8 and 9. + //! @remarks Hypotheses: FogControl -- fog index in area + //! + u16 Parameter1; + + //! @brief wiki - Used by AREA type 3 + //! @remarks Hypothesis: fog index to use when exiting area + //! + u16 Parameter2; + + // Pre Revision 2200: End of structure + + //! @brief [+0x2C] Used by AREA type 3. + //! @since version 2200 + //! @see AreaAccessor::GetPathAccessorFromRailID + //! + u8 railID; + + //! @brief [+0x2D] Used by AREA type 4. + //! @since version 2200 + //! + u8 eneLinkID; + + u8 _[2]; //! @brief [+0x2E] 2 bytes of implicit padding? + +public: + // This would require u8->u32 cast, is this right? + inline eMapdataAreaShape getShape() { + // TODO: potential asserts + // DebugAssert(shape < AREA_SHAPE_NUM); + return static_cast(shape); + } + inline eMapdataAreaType getType() { + // TODO: potential asserts + // DebugAssert(shape < AREA_TYPE_NUM); + return static_cast(type); + } +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataCamera.hpp b/source/game/jmap/resource/MapdataCamera.hpp new file mode 100644 index 000000000..e66017376 --- /dev/null +++ b/source/game/jmap/resource/MapdataCamera.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +namespace Field { + +struct MapdataCamera { + // TODO: I have this documented in RiiStudio. + u8 cameraType; + + u8 cameraNext; + u8 cameraShake; + u8 pathID; + + u16 pathSpeed; //!< wiki - Velocity of the camera point in units per 100/60 + //!< sec (=distance/1.67 sec) + u16 fovYSpeed; //!< wiki - Velocity of zooming in units per 100/60 sec + //!< (=units/1.67 sec) (tested with camera type 5) + /// + /// wiki - Velocity of the view point in distance per 100/60 sec + /// (=distance/1.67 sec) (tested with camera type 5). course.0 - atSpd + /// + u16 viewSpeed; + u8 StartFlag; //!< Unknown + u8 MovieFlag; //!< Unknown -- I conjecture this has to do with their setup for + //!< the intro cameras on the start that are recorded + + EGG::Vector3f position, rotation; + + f32 fovYStart; //!< course.0 - fovy, wiki - Zoom start: The angle of view + //!< (field of view). Angles >180 create curious effects. + f32 fovVYEnd; //!< course.0 - fovy2, wiki - Zoom end. The camera changes the + //!< zoom to this value. Offset 0x06 (Velocity) controls the + //!< speed of zooming. + + EGG::Vector3f viewStart; //!< wiki - , course.0 - at_[x|y|z] + EGG::Vector3f viewEnd; //!< wiki -, course.0 - at2_[x|y|z] + + float time; //!< course.0 - CamTime (Int), wiki - The time how long this + //!< Camera is active. (in units of 1/60 seconds). +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataCannon.hpp b/source/game/jmap/resource/MapdataCannon.hpp new file mode 100644 index 000000000..18ee3aa52 --- /dev/null +++ b/source/game/jmap/resource/MapdataCannon.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace Field { + +struct MapdataCannonPoint { + EGG::Vector3f position; + EGG::Vector3f rotation; + u16 id; + u16 shootEffect; +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataCheckPath.hpp b/source/game/jmap/resource/MapdataCheckPath.hpp new file mode 100644 index 000000000..ce3c17c43 --- /dev/null +++ b/source/game/jmap/resource/MapdataCheckPath.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace Field { + +struct MapdataCheckPath { + u8 start; //!< [+0x00] + u8 size; //!< [+0x01] + + u8 last[6]; //!< [+0x02] + u8 next[6]; //!< [+0x08] +}; + +} // namespace Field \ No newline at end of file diff --git a/source/game/jmap/resource/MapdataCheckPoint.hpp b/source/game/jmap/resource/MapdataCheckPoint.hpp new file mode 100644 index 000000000..a93cbdfd1 --- /dev/null +++ b/source/game/jmap/resource/MapdataCheckPoint.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +namespace Field { + +struct MapdataCheckPoint { + EGG::Vector2f left; //!< [+0x00] First point of the checkpoint. + EGG::Vector2f right; //!< [+0x08] Second point of the checkpoint. + u8 jugemIndex; //!< [+0x10] Respawn point. + u8 lapCheck; //!< [+0x11] 0 - start line, 1-254 - key checkpoints, 255 - + //!< normal + u8 prevPt; //!< [+0x12] Last checkpoint. 0xFF -> sequence edge + u8 nextPt; //!< [+0x13] Next checkpoint. 0xFF -> sequence edge +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataCourse.hpp b/source/game/jmap/resource/MapdataCourse.hpp new file mode 100644 index 000000000..2d4c0ecfa --- /dev/null +++ b/source/game/jmap/resource/MapdataCourse.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace Field { + +struct MapdataCourse { + u32 magic; // 00 + u32 fileSize; // 04 + u16 numSections; // 08 + u16 headerSize; // 0A + u32 revision; // 0C + // Section offsets start here. 10 + s32 offsets[]; +}; + +} // namespace Field \ No newline at end of file diff --git a/source/game/jmap/resource/MapdataEnemyPath.hpp b/source/game/jmap/resource/MapdataEnemyPath.hpp new file mode 100644 index 000000000..5fab13b78 --- /dev/null +++ b/source/game/jmap/resource/MapdataEnemyPath.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace Field { + +struct MapdataEnemyPath { + u8 start; //!< [+0x00] + u8 size; //!< [+0x01] + + u8 last[6]; //!< [+0x02] + u8 next[6]; //!< [+0x08] + + u8 battle_params[2]; //!< [+0x0E] Unknown +}; + +} // namespace Field \ No newline at end of file diff --git a/source/game/jmap/resource/MapdataEnemyPoint.hpp b/source/game/jmap/resource/MapdataEnemyPoint.hpp new file mode 100644 index 000000000..5cf9308b5 --- /dev/null +++ b/source/game/jmap/resource/MapdataEnemyPoint.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace Field { + +/* +Enemy Point Param [ #0, #1, #2, #3 ] +# 0 : 0 -> isUseKinoko +#2 : + 0 -> continueDrift + 1 -> isEndDrift + 2 -> isDisableDrift, !isAbleToMiniTurbo +isUseKinoko: #0 and #1 == 0 + +*/ +struct MapdataEnemyPoint { + EGG::Vector3f position; // 00 + f32 deviation; // 0C + u8 parameters[4]; // 10 +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataGeoObj.hpp b/source/game/jmap/resource/MapdataGeoObj.hpp new file mode 100644 index 000000000..304224bee --- /dev/null +++ b/source/game/jmap/resource/MapdataGeoObj.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +namespace Field { + +struct MapdataGeoObj { + u16 id; // 00 object id + u16 _; // 02 pad? + + // TODO: generic TRS type (note: computed in order SRT) + + EGG::Vector3f translation; // 04 + EGG::Vector3f rotation; // 10 + EGG::Vector3f scale; // 1c + + s16 pathId; // 28 + u16 settings[8]; // 2a + u16 presenceFlag; // 3a +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataItemPath.hpp b/source/game/jmap/resource/MapdataItemPath.hpp new file mode 100644 index 000000000..de8080111 --- /dev/null +++ b/source/game/jmap/resource/MapdataItemPath.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace Field { + +struct MapdataItemPath { + u8 start; //!< [+0x00] + u8 size; //!< [+0x01] + + u8 last[6]; //!< [+0x02] + u8 next[6]; //!< [+0x08] + +private: + u8 _[2]; //!< [+0x0E] Pad? +}; + +} // namespace Field \ No newline at end of file diff --git a/source/game/jmap/resource/MapdataItemPoint.hpp b/source/game/jmap/resource/MapdataItemPoint.hpp new file mode 100644 index 000000000..480cab01c --- /dev/null +++ b/source/game/jmap/resource/MapdataItemPoint.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +namespace Field { + +struct MapdataItemPoint { + EGG::Vector3f position; // 00 + f32 deviation; // 0C + u16 parameters[2]; // 10 todo +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataJugemPoint.hpp b/source/game/jmap/resource/MapdataJugemPoint.hpp new file mode 100644 index 000000000..7aad8446b --- /dev/null +++ b/source/game/jmap/resource/MapdataJugemPoint.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace Field { + +struct MapdataJugemPoint { + EGG::Vector3f position; + EGG::Vector3f rotation; + u16 id; + s16 range; +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataStage.hpp b/source/game/jmap/resource/MapdataStage.hpp new file mode 100644 index 000000000..e04c870f8 --- /dev/null +++ b/source/game/jmap/resource/MapdataStage.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +namespace Field { + +enum StartPosition { STAGE_START_POS_DEFAULT, STAGE_START_POS_SHORT }; + +struct MapdataStage { + u8 mLapCount; //!< [+0x00] Unused + u8 mCorner; //!< [+0x01] Pole is left / right + u8 mStartPosition; //!< [+0x02] 1 close start position + u8 mFlareTobi; //!< [+0x03] I haven't gotten around to RE'ing this. Though + //!< Tobi (course.0) is japanese for "Jump" which definitely + //!< matches this + struct lensFlareOptions_t { + u8 a, r, g, b; //!< [+0x05] Color of the flare + } mLensFlareOptions; //!< [+0x04-0x08] Loading this is sometimes optimized to + //!< a 32 bit load + // --- Pre Revision 2320: End of Structure + //! @addtogroup KMP2320 KMP revision 2320 additions. + //! @{ + u8 mUnk08; //!< [+0x08] Unknown + u8 _; //!< [+0x09] implicit pad + u16 mSpeedModifier; //!< [+0x0A] Used by the speed modifier cheat code as the + //!< two most significant bytes of a f32. (Originally + //!< implicit pad) + //! @} +}; + +} // namespace Field diff --git a/source/game/jmap/resource/MapdataStartPoint.hpp b/source/game/jmap/resource/MapdataStartPoint.hpp new file mode 100644 index 000000000..9ce553ad0 --- /dev/null +++ b/source/game/jmap/resource/MapdataStartPoint.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace Field { + +struct MapdataStartPoint { + EGG::Vector3f position; // 00 + EGG::Vector3f rotation; // 0c + // --- Pre Revision 1830: End of structure + s16 playerIndex; // 18 + u16 _; // 1a - 1c +}; + +} // namespace Field From da86e69ddcaef47c008e9242324d56f7c5dfab21 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 4 May 2021 14:36:57 -0600 Subject: [PATCH 049/477] :construction: Started JmpResourceCourse.cpp --- build.py | 2 ++ rel_o_files.txt | 8 +++-- source/game/jmap/JmpResourceCourse.cpp | 28 ++++++++++++++++ source/game/jmap/JmpResourceCourse.hpp | 46 ++++++++++++++++++++++++++ system/rel_slices.csv | 1 + 5 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 source/game/jmap/JmpResourceCourse.cpp create mode 100644 source/game/jmap/JmpResourceCourse.hpp diff --git a/build.py b/build.py index 7a79ea74e..b54c2ff8a 100644 --- a/build.py +++ b/build.py @@ -333,6 +333,7 @@ def compile_sources(): RVL_OPTS = '-ipa file' EGG_OPTS = '-ipa function -rostr' + REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) @@ -352,6 +353,7 @@ def compile_sources(): # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', EGG_OPTS) + compile_source("source/game/jmap/JmpResourceCourse.cpp", "out/JmpResourceCourse.o", '4201_127', REL_OPTS) from pathlib import Path asm_files = [str(x.relative_to(os.getcwd())) for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] diff --git a/rel_o_files.txt b/rel_o_files.txt index 06350081d..6ae83671d 100644 --- a/rel_o_files.txt +++ b/rel_o_files.txt @@ -1,8 +1,12 @@ rel/text_805103b4.o +rel/data_808b2bd0.o +rel/bss_809bd6e0.o +JmpResourceCourse.o +rel/text_805127ec.o MessageGroup.o rel/text_805f8b90.o rel/ctors_8088f400.o rel/dtors_8088f704.o rel/rodata_8088f710.o -rel/data_808b2bd0.o -rel/bss_809bd6e0.o \ No newline at end of file +rel/data_808b2c3c.o +rel/bss_809bd6ec.o \ No newline at end of file diff --git a/source/game/jmap/JmpResourceCourse.cpp b/source/game/jmap/JmpResourceCourse.cpp new file mode 100644 index 000000000..45fbea02e --- /dev/null +++ b/source/game/jmap/JmpResourceCourse.cpp @@ -0,0 +1,28 @@ +#include "JmpResourceCourse.hpp" + +namespace Field { + +JmpResourceCourse* JmpResourceCourse::initStaticInstance() { + if (spInstance == nullptr) { + spInstance = new JmpResourceCourse(); + } + + return spInstance; +} + +void JmpResourceCourse::destroyStaticInstance() { + if (spInstance != nullptr) { + delete spInstance; + spInstance = nullptr; + } +} + +JmpResourceCourse::JmpResourceCourse() + : mpKartPoint(nullptr), mpEnemyPath(nullptr), mpCheckPath(nullptr), + mpCheckPoint(nullptr), mpGeoObj(nullptr), mpPointInfo(nullptr), + mpArea(nullptr), mpCamera(nullptr), mGoalCamera(nullptr), + mOpeningPanCamera(nullptr), _50(0) {} + +JmpResourceCourse::~JmpResourceCourse() {} + +} // namespace Field \ No newline at end of file diff --git a/source/game/jmap/JmpResourceCourse.hpp b/source/game/jmap/JmpResourceCourse.hpp new file mode 100644 index 000000000..9f02786bf --- /dev/null +++ b/source/game/jmap/JmpResourceCourse.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace Field { + +class JmpResourceCourse { +public: + static JmpResourceCourse* initStaticInstance(); + static void destroyStaticInstance(); + + static inline JmpResourceCourse* instance() { + return spInstance; + } + +private: + JmpResourceCourse(); + virtual ~JmpResourceCourse(); + + static JmpResourceCourse* spInstance; + + void* mpCourse; + + void* mpKartPoint; + void* mpEnemyPath; + void* mpEnemyPoint; + void* mpItemPath; + void* mpItemPoint; + void* mpCheckPath; + void* mpCheckPoint; + void* mpPointInfo; + void* mpGeoObj; + void* mpArea; + void* mpCamera; + void* mpJugemPoint; + void* mpCannonPoint; + void* mpStageInfo; + void* mpMissionPoint; + + void* mGoalCamera; + void* mType9Camera; + void* mOpeningPanCamera; + unk _50; +}; + +} // namespace Field \ No newline at end of file diff --git a/system/rel_slices.csv b/system/rel_slices.csv index f68726402..580a24a08 100644 --- a/system/rel_slices.csv +++ b/system/rel_slices.csv @@ -1,2 +1,3 @@ enabled,name,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd +1,JmpResourceCourse.cpp,0x80512694,0x805127ec,,,,,,,0x808B2C30,0x808B2C3C,0x809BD6E8,0x809BD6EC 1,MessageGroup.cpp,0x805F8B34,0x805F8B90,,,,,,,,,, From f5dda7cd6a1d2981f5d2c0452eda8ac5259b5858 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 5 May 2021 19:01:56 -0600 Subject: [PATCH 050/477] :memo: README: Clarified dependencies/build process --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1e26d30de..23d735afd 100644 --- a/README.md +++ b/README.md @@ -16,14 +16,20 @@ While the original game was written and compiled as C++03, several modern C++ fe ## Documentation Every fully understood piece of reverse engineered data has been documented in a consistent doxygen style. +## Dependencies +- DevKitPro (for the ppc-eabi assembler) +- Python3: + - `pip insall pyelftools` + - `pip install capstone` (for `gen_asm.py` only) +- Place a copy of Mario Kart Wii's PAL .dol file in `artifacts/pal` as `main.dol` and REL as `StaticR.rel`. (`gen_asm.py` only) + ## Building -- Create a root-level folder named `tools`. Place elf2dol, mwldeppc and mwcceppc in it. Then run `python3 build.py` -- Place a copy of Mario Kart Wii's PAL .dol file in `artifacts/pal` as `main.dol` +- Place the specified CodeWarrior versions in the respective subfolders in the `tools` directory. Then run `python3 build.py` ## Contributing - Do not manually adjust assembly files; rather add a new entry to [system/slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv) for main.dol and [system/rel_slices.csv](https://github.com/riidefi/mkw/blob/master/system/rel_slices.csv) for StaticR.rel. - Entries must be sorted in the spreadsheet (current limitation). -- Run `gen_asm.py` from the `system` directory to regenerate assembly segments. +- Run `gen_asm.py` from the `system` directory to regenerate assembly segments every time either file is editeds. ## .rel support Most of Mario Kart Wii's game code is located inside a relocatable module (StaticR.rel for release builds). The decompilation builds this. \ No newline at end of file From 8efa1ba7430304b6e4eaddbe9f140507c16d33a0 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 17:47:18 -0600 Subject: [PATCH 051/477] :construction: eggDvdFile.cpp --- source/egg/core/eggDvdFile.cpp | 94 ++++++++++++++++++++++++++++++++++ source/egg/core/eggDvdFile.hpp | 90 ++++++++++++++++++++++++++++++++ source/egg/core/eggFile.hpp | 19 +++++++ 3 files changed, 203 insertions(+) create mode 100644 source/egg/core/eggDvdFile.cpp create mode 100644 source/egg/core/eggDvdFile.hpp create mode 100644 source/egg/core/eggFile.hpp diff --git a/source/egg/core/eggDvdFile.cpp b/source/egg/core/eggDvdFile.cpp new file mode 100644 index 000000000..02f8bd258 --- /dev/null +++ b/source/egg/core/eggDvdFile.cpp @@ -0,0 +1,94 @@ +#include +#include + +namespace EGG { + +bool DvdFile::sIsInitialized; +nw4r::ut::List DvdFile::sDvdList; + +void DvdFile::initialize() { + if (!sIsInitialized) { + nw4r::ut::List_Init(&sDvdList, 200); + sIsInitialized = true; + } +} + +DvdFile::DvdFile() : mIsOpen(false) { create(); } + +DvdFile::~DvdFile() { close(); } + +void DvdFile::initiate() { + this->_78 = this; + OSInitMutex(&this->Mutex_08); + OSInitMutex(&this->Mutex_20); + OSInitMessageQueue(&this->MessageQueue_A0, &this->_C0, 1); + OSInitMessageQueue(&this->MessageQueue_7C, &this->_9C, 1); + + this->_C4 = 0; + this->_38 = 0; +} + + +bool DvdFile::open(s32 entryNum) { + if (!this->mIsOpen && entryNum != -1) { + if (mFileInfo.open(entryNum)) { + nw4r::ut::List_Append(&sDvdList, this); + DVDGetCommandBlockStatus(&mFileInfo.cb); + } + } + + return mIsOpen; +} + +bool DvdFile::open(const char* filename) { + return this->open(DVDConvertPathToEntrynum(filename)); +} + +bool DvdFile::open(char* path) { return open((const char*)path); } + +void DvdFile::close() { + if (this->mIsOpen && DVDClose(&this->mFileInfo)) { + this->mIsOpen = false; + nw4r::ut::List_Remove(&sDvdList, this); + } +} + +int DvdFile::readData(void* addr, int len, int offset) { + OSLockMutex(&this->Mutex_08); + if (this->_C4 != 0) { + OSUnlockMutex(&this->Mutex_08); + return -1; + } + this->_C4 = OSGetCurrentThread(); + int r31 = (void*)-1; + if (DVDReadAsyncPrio(&this->mFileInfo, addr, len, offset, (DVDCallback)&doneProcess, 2)) + r31 = this->sync(); + this->_C4 = 0; + OSUnlockMutex(&this->Mutex_08); + return r31; +} + +int DvdFile::writeData(const void*, int, int) { + return -1; // You can't write to the Dvd! +} + +void DvdFile::sync() { + OSLockMutex(&this->Mutex_08); + + OSMessage message; + OSReceiveMessage(&this->MessageQueue_A0, &message, 1); + + _C4 = 0; + + OSUnlockMutex(&this->Mutex_08); + + return message; +} + +void DvdFile::doneProcess(int result, DVDFileInfo* fileInfo) { + OSSendMessage(&fileInfo->_3C->_A0, this, 0); +} + +int DvdFile::getFileSize() const { return mFileInfo.length; } + +} // namespace EGG diff --git a/source/egg/core/eggDvdFile.hpp b/source/egg/core/eggDvdFile.hpp new file mode 100644 index 000000000..336461100 --- /dev/null +++ b/source/egg/core/eggDvdFile.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include + +typedef void* OSMessage; + +namespace EGG { + +class DvdFile : public File { +public: + //! @brief Initialize static members. + //! + static void initialize(); + + //! @brief Sets mIsOpen to false then calls CT + DvdFile(); + + //! @brief Closes the file on the DVD. + ~DvdFile() override; + + void initiate(); + + //! @brief Opens a file given the entry number. + //! + //! @returns If the file successfully opened (mIsOpen) + //! + bool open(s32 entryNum) override; + + //! @brief Opens a file given the path. + //! + //! @details Calculates the entry number and calls down to open(int entryNum) + //! + //! @returns Whether or not the file successfully opened. + //! + bool open(const char* path) override; + + //! @brief just calls down to open(const char* path) + bool open(char* path) override; + + //! @brief Closes the file and removes this* from sDvdList + void close() override; + + //! @brief Reads the data synchronously????. + //! TODO. FINISH THIS + int readData(void* addr, int len, int offset) override; + + //! @brief Always fails. The DVD is read-only. + int writeData(const void*, int, int) override; + + //! @brief TODO + int sync(); + + //! @brief Callback to DVDReadAsyncPrio + //! @brief TODO + static void doneProcess(int result, DVDFileInfo* fileInfo); + + //! @brief Get the filesize. + //! @returns The length attribute of the DVD fileInfo. + int getFileSize() const override; + +private: + static bool sIsInitialized; + static nw4r::ut::List sDvdList; + + + struct FileInfo : public DVDFileInfo { + bool fastOpen(s32 resolved) { + return DVDFastOpen(resolved, this); + } + }; + + bool mIsOpen; // 04 + // 3B implicit pad + OSMutex Mutex_08; // sizeof=0x18 + OSMutex Mutex_20; // sizeof=0x18 + int _38; // set to 0 in ct + FileInfo mFileInfo; // [+0x3C] sizeof=0x3C + DvdFile* _78; // set to this* in ct + + OSMessageQueue MessageQueue_7C; // sizeof=0x20 + OSMessage _9C; + + OSMessageQueue MessageQueue_A0; // sizeof=0x20 + OSMessage _C0; + + OSThread* _C4; +}; +} // namespace EGG diff --git a/source/egg/core/eggFile.hpp b/source/egg/core/eggFile.hpp new file mode 100644 index 000000000..a141834f3 --- /dev/null +++ b/source/egg/core/eggFile.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace EGG { + +class File { +public: + virtual ~File() = 0; //!< [vt+0x08] + virtual int open(const char* path) = 0; //!< [vt+0x0C] + virtual void close() = 0; //!< [vt+0x10] + virtual int readData(void*, int, int) = 0; //!< [vt+0x14] + virtual int writeData(const void*, int, int) = 0; //!< [vt+0x18] + virtual int getFileSize() const = 0; //!< [vt+0x1C] + + // Look DVD added.. + virtual int open(int entryNum) = 0; //!< [vt+0x20] + virtual int open(char* path) = 0; //!< [vt+0x24] +}; + +} // namespace EGG \ No newline at end of file From 242c0e3ca021431c4cd4f688f77ec3f91f5902b8 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 17:47:42 -0600 Subject: [PATCH 052/477] :construction: Misc egg headers --- source/egg/core/eggSystem.cpp | 9 +++++++++ source/egg/core/eggSystem.hpp | 15 +++++++++++++++ source/egg/core/eggViewport.hpp | 25 +++++++++++++++++++++++++ source/egg/math/eggBoundBox.hpp | 12 ++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 source/egg/core/eggSystem.cpp create mode 100644 source/egg/core/eggSystem.hpp create mode 100644 source/egg/core/eggViewport.hpp create mode 100644 source/egg/math/eggBoundBox.hpp diff --git a/source/egg/core/eggSystem.cpp b/source/egg/core/eggSystem.cpp new file mode 100644 index 000000000..09d0d9a31 --- /dev/null +++ b/source/egg/core/eggSystem.cpp @@ -0,0 +1,9 @@ +namespace EGG { +class BaseSystem { +public: + static BaseSystem* sSystem; +}; + +BaseSystem* BaseSystem::sSystem; + +} // namespace EGG \ No newline at end of file diff --git a/source/egg/core/eggSystem.hpp b/source/egg/core/eggSystem.hpp new file mode 100644 index 000000000..74883e466 --- /dev/null +++ b/source/egg/core/eggSystem.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace EGG { + +class Video; + +class BaseSystem { +public: + static BaseSystem* sSystem; + + //! Returns a pointer to the video manager. + virtual Video* getVideo() = 0; // [vt+0x08] +}; + +} // namespace EGG \ No newline at end of file diff --git a/source/egg/core/eggViewport.hpp b/source/egg/core/eggViewport.hpp new file mode 100644 index 000000000..d4e91591e --- /dev/null +++ b/source/egg/core/eggViewport.hpp @@ -0,0 +1,25 @@ +/*! + * @file + * @brief TODO + */ + +#pragma once + +#include +#include + +namespace EGG { + +// assumed names +class Viewport : public BoundBox2f { +public: + Viewport(); + void set(int a, int b, int c, int d); + void calculateAR(); + + f32 m_width; // _10 + f32 m_height; // _14 + f32 m_aspectRatio; // _18 +}; + +} // namespace EGG diff --git a/source/egg/math/eggBoundBox.hpp b/source/egg/math/eggBoundBox.hpp new file mode 100644 index 000000000..4294ece9b --- /dev/null +++ b/source/egg/math/eggBoundBox.hpp @@ -0,0 +1,12 @@ +#pragma once + +namespace EGG { + +struct BoundBox2f { + Vector2f min; + Vector2f max; + + BoundBox2f() {} +}; + +} // namespace EGG \ No newline at end of file From 930e4fe74442067c3a7364a46a53e5cdb14bc5f4 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 17:59:57 -0600 Subject: [PATCH 053/477] :construction: ParameterFile (.prm) + eggStream WIP --- source/egg/util/eggStream.cpp | 174 ++++++++++++++++++++++ source/egg/util/eggStream.hpp | 62 ++++++++ source/game/host_system/ParameterFile.cpp | 65 ++++++++ source/game/host_system/ParameterFile.hpp | 56 +++++++ 4 files changed, 357 insertions(+) create mode 100644 source/egg/util/eggStream.cpp create mode 100644 source/egg/util/eggStream.hpp create mode 100644 source/game/host_system/ParameterFile.cpp create mode 100644 source/game/host_system/ParameterFile.hpp diff --git a/source/egg/util/eggStream.cpp b/source/egg/util/eggStream.cpp new file mode 100644 index 000000000..13f056f07 --- /dev/null +++ b/source/egg/util/eggStream.cpp @@ -0,0 +1,174 @@ +#include +#include + +namespace EGG { + +char Stream::sTextBuffer[0x400]; + +Stream::~Stream() {} + +const char* STRIPPED_0() { return "%d"; } + +u32 Stream::read_u32() { + if (m_flag & STREAM_TEXT) { + u32 readValue; + sscanf(getNextToken(), "%d", &readValue); + return readValue; + } + + u32 readValue; + safeRead(&readValue, sizeof(u32)); + return readValue; +} + +void Stream::readString(char* data, int size) { + if (m_flag & STREAM_TEXT) { + snprintf(data, size, "%s", getNextToken()); + return; + } + + safeRead(data, size); +} + +void Stream::safeRead(void* data, u32 size) { + // (PROBABLY) from an assert that got stripped on release build + eof(); + + read(data, size); + m_position += size; +} + +void Stream::copyToTextBuffer() { + m_textBuffer[0] = skipSpace(); + + s32 r29 = 1; // size + do { + if (eof()) + return; + + if (r29 >= _14) + return; + + eof(); + + char read = _readByte(); + if (isSpace(read)) { + m_textBuffer[0] = '\0'; + + if (_1c || read != '#') + return; + + while (true) { + if (eof()) + return; + + eof(); + char read2 = _readByte(); + if (isUpperSJIS(read2)) + _readByte(); + else if (read2 == '\r' || read2 == '\n') + return; + } + } else { + if (!(m_textBuffer[r29++])) + return; + } + } while (true); +} + +char* Stream::getNextToken() { + if (m_flag & STREAM_TEXT) + return nullptr; + + if (_04) { + _04 = false; + return m_textBuffer; + } else { + copyToTextBuffer(); + return m_textBuffer; + } +} + +unsigned char Add0xF7(u8 in) { return in + 0xF7; } + +bool Stream::isSpace(u8 toCheck) { + // TODO: Fix start of the function to be 1:1 + bool r7 = true; + bool r8 = true; + bool r9 = false; + + u32 r6 = Add0xF7(toCheck); + if (toCheck + 0xf7 <= 0x17) { + if (0x800013 & (1 << r6)) + r9 = true; + } + + if (r9) { + bool r4Unk = false; + if (_1c == 0 && (u32)toCheck == '#') + r4Unk = true; + + if (!r4Unk) + r8 = false; + } + + if (!r8 && (u32)toCheck != 0x21) + r7 = false; + + return r7; +} + +bool Stream::isUpperSJIS(u8 toCheck) { + if (toCheck >= 0x81 && toCheck < 0x9F) + return true; + + if (toCheck < 0xe0 || toCheck > 0xfc) + return false; + return true; +} + +bool Stream::skipSpace() { + if (m_flag & STREAM_TEXT) + return false; + + if (eof()) + return false; +} + +Stream::Stream(u8* data, u32 size) + : _04(false), m_flag(STREAM_BINARY), m_position(0), _18(0), _14(0x400), + m_textBuffer(sTextBuffer), _1c(0) { + m_binaryData = data; + m_dataSize = size; +} + +void Stream::read(void* r4, u32 r5) { + u32 r7 = 0; + if (r5 <= 0) + return; + + do { + + } while (r5 / 8 && r5 & 7); + + do { + m_binaryData[r7++] = ((char*)r4)[m_position]; + r4 = (char*)r4 + 1; + } while (r5--); +} + +u32 Stream::peek_u32() { + u8 data[4] = {m_binaryData[m_position], m_binaryData[m_position + 1], + m_binaryData[m_position + 2], m_binaryData[m_position + 3]}; + + return *reinterpret_cast(data); +} + +void Stream::write(void* data, u32 size) {} + +bool Stream::eof() { return m_position < m_dataSize; } + +void Stream::beginMemoryMap(char*) {} +void Stream::endMemoryMap(char*) {} + +} // namespace EGG diff --git a/source/egg/util/eggStream.hpp b/source/egg/util/eggStream.hpp new file mode 100644 index 000000000..117a685c0 --- /dev/null +++ b/source/egg/util/eggStream.hpp @@ -0,0 +1,62 @@ +/*! + * @file + * @brief The Stream class. + */ + +#pragma once +#include + +namespace EGG { +//! @brief The Stream class allows for reading binary / text streams from RAM. +//! +class Stream { +public: + //! @brief The type of stream to be used when reading/writing. + //! + enum StreamFlags { + STREAM_BINARY = 0, //!< assumed name + STREAM_TEXT = 1 + }; + + Stream(u8* data, u32 size); + virtual ~Stream(); + + void safeRead(void* data, u32 size); + virtual void read(void* data, u32 size); + virtual void write(void* data, u32 size); + virtual bool eof(); + virtual void beginMemoryMap(char*); + virtual void endMemoryMap(char*); + virtual u32 peek_u32(); + + void copyToTextBuffer(); + char* getNextToken(); + + void readString(char* data, int size); + u32 read_u32(); + inline u8 _readByte() { + char data; + read((u32*)&data, sizeof(char)); + m_position += sizeof(char); + return data; + } + + bool skipSpace(); + bool isSpace(u8); + bool isUpperSJIS(u8); + + static char sTextBuffer[0x400]; + +private: + // 0 - vtbl + bool _04; // _04 + u32 m_position; // _08 + u16 m_flag; // _0c + char* m_textBuffer; // _10 + s32 _14; // _14, buffer size? + u32 _18; // _18 + u8 _1c; // _1c + u8* m_binaryData; // _20 + u32 m_dataSize; // _24 +}; +} // namespace EGG diff --git a/source/game/host_system/ParameterFile.cpp b/source/game/host_system/ParameterFile.cpp new file mode 100644 index 000000000..51c9faf06 --- /dev/null +++ b/source/game/host_system/ParameterFile.cpp @@ -0,0 +1,65 @@ +#include "ParameterFile.hpp" +#include + +#include + +namespace System { + +ParameterFile::ParameterFile(const char* path, u32 other) : mPath(path) { + mTotalAllocated = 0; + _18 = other; + nw4r::ut::List_Init(&mStrings, 0); +} + +ParameterFile::~ParameterFile() { + StringView* str = nullptr; + StringView* it = str; + while ((str = nextString(it)) != nullptr) { + it = str; + delete str; + } +} + +void ParameterFile::appendData(char* str, u32 str_capacity, EGG::Heap* heap) { + StringView* newStr = new (heap, 4) StringView; + newStr->set(str, str_capacity); + nw4r::ut::List_Append(&mStrings, newStr); + mTotalAllocated += str_capacity; +} + +void ParameterFile::read(EGG::Heap* heap) { + if (*m_path == '\0') { + // No path to load from + return; + } + + u32 fileSize; + u8* data = SystemManager::ripFromDisc(m_path, heap, true, &fileSize); + + if (data == nullptr) { + // Specified parameter file does not exist + return; + } + + EGG::Stream stream(data, fileSize); + + if (stream.read_u32() != 'RKPF' || stream.eof()) { + // Identifier mismatch + return; + } + + if (stream.read_u32() < _18 || stream.eof()) { + // ? + return; + } + + StringView* it = nullptr; + while ((it = nextString(it)) != nullptr) { + if (stream.eof()) + break; + + stream.readString(it->data(), it->size()); + } +} + +} // namespace System diff --git a/source/game/host_system/ParameterFile.hpp b/source/game/host_system/ParameterFile.hpp new file mode 100644 index 000000000..7b6e17142 --- /dev/null +++ b/source/game/host_system/ParameterFile.hpp @@ -0,0 +1,56 @@ +/*! + * @file ParameterFile.hpp + * @brief Contains reading code for an unused 'Parameter' file. + */ + +#pragma once + +#include +#include +#include + +namespace System { + +class ParameterFile { +public: + ParameterFile(const char* path, u32 other); + virtual ~ParameterFile(); + + virtual void read(EGG::Heap* heap); + virtual void emptySub1() {} + virtual void emptySub2() {} + virtual void emptySub3() {} + + void appendData(char* str, u32 str_size, EGG::Heap* heap); + +private: + class StringView { + public: + inline void set(char* begin, u32 size) { + mData = begin; + mSize = size; + } + + inline const char* data() const { return mData; } + inline char* data() { return mData; } + inline u32 size() const { return mSize; } + + private: + nw4r::ut::Link m_link; + char* mData; + u32 mSize; + }; + + inline StringView* nextString(StringView* string) { + void* nextObj = nw4r::ut::List_GetNext(&mStrings, string); + + return reinterpret_cast(nextObj); + } + + nw4r::ut::List mStrings; // 04 + u32 mTotalAllocated; // 10 + const char* mPath; // 14 + u32 _18; // 18 +}; + +} // namespace System From 0a25b5268088dff6641824e221e2e563245145e3 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:00:12 -0600 Subject: [PATCH 054/477] :construction: eggCntFile.cpp --- source/egg/util/eggCntFile.cpp | 38 ++++++++++++++++++++++++++++++++++ source/egg/util/eggCntFile.hpp | 32 ++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 source/egg/util/eggCntFile.cpp create mode 100644 source/egg/util/eggCntFile.hpp diff --git a/source/egg/util/eggCntFile.cpp b/source/egg/util/eggCntFile.cpp new file mode 100644 index 000000000..f8570ea4d --- /dev/null +++ b/source/egg/util/eggCntFile.cpp @@ -0,0 +1,38 @@ +#include + +namespace EGG { + +CntFile::CntFile() {} +CntFile::~CntFile() {} + +bool CntFile::open() { return false; } + +void CntFile::close() { + if (!_04) { + return; + } + + bool returnVal = contentCloseNAND(&_3C); + _4C = false; + + if (!returnVal) { + _04 = false; + nw4r::ut::List_Remove(&gCntFileList, this); + } + + gCurrentCntFile = returnVal; +} + +void CntFile::readData(void* fileBuffer, u32 length, s32 offset) { + OSLockMutex(&_08); + if (_9C) { + OSUnlockMutex(&_08); + return; + } + _9C = OSGetCurrentThread(); + + const s32 readCode = contentReadNAND(&_3C, fileBuffer, length, offset); + gCurrentCntFile = readCode == 0; +} + +} // namespace EGG diff --git a/source/egg/util/eggCntFile.hpp b/source/egg/util/eggCntFile.hpp new file mode 100644 index 000000000..caf9a0d28 --- /dev/null +++ b/source/egg/util/eggCntFile.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +namespace EGG { + +static nw4r::ut::List gCntFileList; +static BOOL gCurrentCntFile; + +class CntFile { +public: + CntFile(); + virtual ~CntFile(); + + virtual bool open(); // 0 + virtual void close(); // 4 + virtual void readData(void* fileBuffer, u32 length, s32 offset); // 8 + +private: + bool _04; // 4 + OSMutex _08; // 8 + unk32 _38; // 38 + CNTFileInfoNAND _3C; // 3C + char _40[0x10]; // 40 + unk32 _4C; // 4C + char _50[0x50]; // 50 + OSThread* _9C; // 9C +}; +} // namespace EGG From b22b5c763c9284bc8da49c61f2508c438183f044 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:14:34 -0600 Subject: [PATCH 055/477] :sparkles: Decompiled eggAllocator.cpp --- source/egg/core/eggAllocator.cpp | 21 +++++++++++++ source/egg/core/eggAllocator.hpp | 52 ++++++++++++++++++++++++++++++++ system/slices.csv | 1 + 3 files changed, 74 insertions(+) create mode 100644 source/egg/core/eggAllocator.cpp create mode 100644 source/egg/core/eggAllocator.hpp diff --git a/source/egg/core/eggAllocator.cpp b/source/egg/core/eggAllocator.cpp new file mode 100644 index 000000000..7bd3c0032 --- /dev/null +++ b/source/egg/core/eggAllocator.cpp @@ -0,0 +1,21 @@ +/*! + * @file + * @brief Implementation for the allocator wrapper. + */ + +#include +#include + +namespace EGG { + +Allocator::Allocator(Heap* heap, s32 align) : mHeap(heap), mAlign(align) { + heap->initAllocator(this, align); +} + +Allocator::~Allocator() {} + +void* Allocator::alloc(u32 size) { return Heap::alloc(size, mAlign, mHeap); } + +void Allocator::free(void* block) { Heap::free(block, mHeap); } + +} // namespace EGG diff --git a/source/egg/core/eggAllocator.hpp b/source/egg/core/eggAllocator.hpp new file mode 100644 index 000000000..0b018d745 --- /dev/null +++ b/source/egg/core/eggAllocator.hpp @@ -0,0 +1,52 @@ +/*! + * @file + * @brief TODO + */ + +#pragma once + +#include + +extern "C" { + +struct MEMAllocator { + char _[0x10]; +}; + +} // extern "C" + +namespace EGG { + +class Heap; + +class Allocator : public MEMAllocator { +public: // vt at +0x10 + //! @brief [vt+0x08] Create the default allocator. + //! + //! @param[in] heap Heap to use for allocations. + //! @param[in] align Alignment of blocks to allocate. + //! + Allocator(Heap* heap, s32 align); + + //! @brief [vt+0x08] Virtual destructor + //! + virtual ~Allocator(); + + //! @brief [vt+0x0C] Allocate a block of memory. + //! + //! @param[in] size Size of the block to allocate. + //! + virtual void* alloc(u32 size); + + //! @brief [vt+0x10] Free a block of memory. + //! + //! @param[in] block Block of memory to free. + //! + virtual void free(void* block); + +private: + Heap* mHeap; //!< [+0x14] Heap to use for allocation. + s32 mAlign; //!< [+0x18] Alignment of blocks to allocate. +}; + +} // namespace EGG diff --git a/system/slices.csv b/system/slices.csv index 5f69ffc4e..e81033d9f 100644 --- a/system/slices.csv +++ b/system/slices.csv @@ -3,6 +3,7 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, ,rxFrameHeap.c,,,,,,,80199430,801998A4,,,,,,,,,,,,,,,,,, 1,rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, +1,eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,802A2668,802A2680 ,,,,,,,,,, 1,eggArchive.cpp,,,,,,,8020F6EC,0x8020FCC4,,,,,,,802A2680,802A268C,803832D8,0x803832E4,,,80386D80,80386D84,,,, 1,eggDisposer.cpp,,,,,,,8021A0F0,8021A1B8,,,,,,,802A2B48,802A2B54 ,,,,,,,,,, 1,eggGraphicsFifo.cpp,,,,,,,80229540,802296A8,,,,,,,802A30B0,802A30BC,,,,,0x80386E90,0x80386E99,,,, From b965ef498cea489ff3520ca4d473647d3e5381dd Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:19:27 -0600 Subject: [PATCH 056/477] :construction: eggTaskThread.cpp --- source/egg/core/eggTaskThread.cpp | 126 ++++++++++++++++++++++++++++++ source/egg/core/eggTaskThread.hpp | 59 ++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 source/egg/core/eggTaskThread.cpp create mode 100644 source/egg/core/eggTaskThread.hpp diff --git a/source/egg/core/eggTaskThread.cpp b/source/egg/core/eggTaskThread.cpp new file mode 100644 index 000000000..9e16f972b --- /dev/null +++ b/source/egg/core/eggTaskThread.cpp @@ -0,0 +1,126 @@ +/*! + * @file + * @brief TODO. + */ + +#include +#include +#include +#include + +namespace EGG { + +TaskThread* TaskThread::create(int msgCount, int prio, u32 stackSize, + Heap* heap) { + if (heap == nullptr) + heap = Heap::sCurrentHeap; + + TaskThread* thread = new TaskThread(msgCount, prio, stackSize); // r31 + if (thread == nullptr) + return nullptr; + + thread->mJobArray = new (heap, 4) TJob[msgCount]; + thread->mJobCount = msgCount; + + if (thread->mJobArray == nullptr) { + delete thread; + + return nullptr; + } + + for (int i = 0; i < msgCount; ++i) { + thread->mJobArray[i]._00 = 0; + thread->mJobArray[i]._0C = 0; + thread->mJobArray[i]._10 = 0; + thread->mJobArray[i]._14 = 0; + } + + return thread; +} + +TaskThread::TJob::TJob() { clearFunctions(); } + +unk TaskThread::destroy() { + if (OSIsThreadTerminated(mOSThread) != TRUE) { + DVDCancelAll(); + delete[] mJobArray; + delete this; + } +} +// ... +bool TaskThread::request(TaskThread::JobRequestProbably req, void* a, void* b) { + TaskThread::TJob* slot = findBlank(); + if (!slot) + return false; + + slot->_00 = req; + slot->_04 = a; + slot->_08 = b; + bool res = OSSendMessage(&mMesgQueue, slot, 0); + if (!res) + slot->_00 = 0; + return res; +} +bool TaskThread::isTaskExist() const { + if (mJobCount > 0) { + for (int i = 0; i < mJobCount; i++) { + if (mJobArray[i]._00) // lwzx? + return true; + } + } + return false; +} +TaskThread::~TaskThread() {} +void TaskThread::onEnter() { + if (mReceivedJob && mReceivedJob->_0C) + mReceivedJob->_0C(mReceivedJob->_04); +} +void TaskThread::onExit() { + if (mReceivedJob && mReceivedJob->_10) + mReceivedJob->_10(mReceivedJob->_04); +} +int TaskThread::run() { +#ifndef _WIN32 + OSInitFastCast(); +#endif + while (true) { + OSMessage tmpMsg; + OSReceiveMessage(&mMesgQueue, &tmpMsg, OS_MESSAGE_BLOCK); + TJob* taskmsg = (TJob*)tmpMsg; + mReceivedJob = taskmsg; + if (mReceivedJob->_00) { + mReceivedJob->_00(mReceivedJob->_04); + if (mReceivedJob && mReceivedJob->_14) { + mReceivedJob->_14(mReceivedJob->_04); + } + if (_54) + OSSendMessage(_54, taskmsg, 0); + } + taskmsg->_00 = 0; + mReceivedJob = 0; + taskmsg->clearFunctions(); + } +} + +TaskThread::TaskThread(int msgCount, int prio, u32 stackSize) + : Thread(stackSize, msgCount, prio, NULL) { + // TODO: Why is this needed? + OSResumeThread(this->mOSThread); +} + +TaskThread::TJob* TaskThread::findBlank() { + if (mJobCount > 0) { + for (int i = 0; i < mJobCount; i++) { + if (mJobArray[i]._00) // lwzx? + { + TJob* prevJob = &mJobArray[i]; + mJobArray[i]._00 = NULL; + prevJob->clearFunctions(); + return &mJobArray[i]; + } + } + } + return nullptr; +} + +} // namespace EGG diff --git a/source/egg/core/eggTaskThread.hpp b/source/egg/core/eggTaskThread.hpp new file mode 100644 index 000000000..0699aa0d4 --- /dev/null +++ b/source/egg/core/eggTaskThread.hpp @@ -0,0 +1,59 @@ +/*! + * @file + * @brief TODO. + */ + +#pragma once + +#include +#include +#include + +namespace EGG { + +//! @brief TODO sizeof 0x58 +//! +class TaskThread : public Thread { +public: + typedef void* (*JobRequestProbably)(void*); + + static TaskThread* create(int msgCount, int prio, u32 stackSize, Heap* heap); + + struct TJob { + JobRequestProbably _00; // called; "isTaskExist" checks this + void* _04; // argument? + void* _08; // ud? + + JobRequestProbably _0C; // 0C EnterRequest? + JobRequestProbably _10; // exitRequst? + JobRequestProbably _14; // set to 0 in ct + + TJob(); + // inlined only. though if inline not sure + inline void clearFunctions() { _0C = _10 = _14 = 0; } + }; + + unk destroy(); + bool request(JobRequestProbably, void*, void*); + bool isTaskExist() const; + + // Thread overrides + ~TaskThread() override; + void onEnter() override; + void onExit() override; + int run() override; + + //! @brief Create a task thread and resume current thread + //! + TaskThread(int msgCount, int prio, u32 stackSize); + TJob* findBlank(); + + // starts at 0x40 + TJob* mReceivedJob; // 48 for receiving msgs + TJob* mJobArray; //!< [+0x4C] sppepcial __construct_new_array with TJob. + //!< sizeof MessageCount + u32 mJobCount; // 50 Number of jobs / messages + OSMessageQueue* _54; // 54 +}; + +} // namespace EGG From f717995b04b94da28026a1c6cebc0702d61eac6e Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:22:26 -0600 Subject: [PATCH 057/477] :construction: eggVector.cpp: Use FLT_EPISLON --- o_files.txt | 1 + source/egg/math/eggVector.cpp | 11 +++++------ source/platform/float.h | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 source/platform/float.h diff --git a/o_files.txt b/o_files.txt index 477afdff3..7be451798 100644 --- a/o_files.txt +++ b/o_files.txt @@ -10,6 +10,7 @@ dol/text_80124e80.o rvlMemList.o dol/text_80199d04.o dol/data_8027e772.o +eggAllocator.o dol/bss_802a4080.o dol/sbss_803862b0.o eggArchive.o diff --git a/source/egg/math/eggVector.cpp b/source/egg/math/eggVector.cpp index 7085d0650..8f8c9287f 100644 --- a/source/egg/math/eggVector.cpp +++ b/source/egg/math/eggVector.cpp @@ -6,8 +6,7 @@ #pragma once #include - -#define WEIRD_FLOAT (1.0f / 8388608.0f) +#include namespace EGG { @@ -15,7 +14,7 @@ float Vector2f::normalise() { float out = 0.0f; float lenSq = x * x + y * y; - if (lenSq > WEIRD_FLOAT) { + if (lenSq > FLT_EPSILON) { out = Mathf::sqrt(lenSq); float inv = 1.0f / out; x *= inv; @@ -27,7 +26,7 @@ float Vector2f::normalise() { void Vector2f::normalise2() { float lenSq = x * x + y * y; - if (lenSq > WEIRD_FLOAT) { + if (lenSq > FLT_EPSILON) { float inv = Mathf::frsqrt(lenSq); x *= inv; y *= inv; @@ -38,7 +37,7 @@ float Vector3f::normalise() { float out = 0.0f; float lenSq = x * x + y * y + z * z; - if (lenSq > WEIRD_FLOAT) { + if (lenSq > FLT_EPSILON) { out = Mathf::sqrt(lenSq); float inv = 1.0f / out; x *= inv; @@ -51,7 +50,7 @@ float Vector3f::normalise() { void Vector3f::normalise2() { float lenSq = x * x + y * y + z * z; - if (lenSq > WEIRD_FLOAT) { + if (lenSq > FLT_EPSILON) { float inv = Mathf::frsqrt(lenSq); x *= inv; y *= inv; diff --git a/source/platform/float.h b/source/platform/float.h new file mode 100644 index 000000000..a853b39bd --- /dev/null +++ b/source/platform/float.h @@ -0,0 +1,3 @@ +#pragma once + +#define FLT_EPSILON (1.0f / 8388608.0f) \ No newline at end of file From 2d9c94af06b57c154d36eeaf1c88fa2427c0c52f Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:28:50 -0600 Subject: [PATCH 058/477] :construction: eggColorFader.cpp --- source/egg/core/eggColorFader.cpp | 185 ++++++++++++++++++++++++++++++ source/egg/core/eggColorFader.hpp | 119 +++++++++++++++++++ 2 files changed, 304 insertions(+) create mode 100644 source/egg/core/eggColorFader.cpp create mode 100644 source/egg/core/eggColorFader.hpp diff --git a/source/egg/core/eggColorFader.cpp b/source/egg/core/eggColorFader.cpp new file mode 100644 index 000000000..fd693d5dc --- /dev/null +++ b/source/egg/core/eggColorFader.cpp @@ -0,0 +1,185 @@ + +#include +#include +#include +#include +#include + +#include + +namespace EGG { + +ColorFader::ColorFader(f32 leftX, f32 bottomY, f32 width, f32 height, + nw4r::ut::Color color, Fader::EStatus status) { + mFrameCount = 20; + mFlag = FLAG_8; + mFrame = 0; + // This line is useless due to the call to setColor() below + mColor = nw4r::ut::Color::WHITE; + mLeftX = leftX; + mBottomY = bottomY; + mRightX = leftX + width; + mTopY = bottomY + height; + setColor(color); + setStatus(status); + mFlag |= FLAG_2; +} + +// Wii sports function. This is the correct placement. +// Not linked in MKW. +void ColorFader::setFrame(u16 frame) { + WiiSportsAssert(frame, "eggColorFader.cpp", 63, "frame != 0"); + mFrame = frame; +} + +void ColorFader::setColor(nw4r::ut::Color color) { + mColor.Set(color.r, color.g, color.b, mColor.a); +} + +void ColorFader::setStatus(Fader::EStatus status) { + if (status == ESTATUS_OPAQUE) { + mStatus = ESTATUS_OPAQUE; + mColor.a = 0xFF; + } else if (status == ESTATUS_HIDDEN) { + mStatus = ESTATUS_HIDDEN; + mColor.a = 0; + } +} + +bool ColorFader::fadeIn() { + bool doFadeIn = mStatus == ESTATUS_OPAQUE; + if (doFadeIn) { + mStatus = ESTATUS_FADE_IN; + mFrame = 0; + } + + return doFadeIn; +} + +bool ColorFader::fadeOut() { + const bool doFadeOut = mStatus == ESTATUS_HIDDEN; + if (doFadeOut) { + mStatus = ESTATUS_FADE_OUT; + mFrame = 0; + } + + return doFadeOut; +} + +int ColorFader::calc() { + int result = 0; + switch (mStatus) { + case ESTATUS_HIDDEN: + mColor.a = 0; + break; + case ESTATUS_OPAQUE: + mColor.a = 0xFF; + break; + case ESTATUS_FADE_IN: { + u16 curFrame = mFrame; + u16 maxFrame = mFrameCount; + mFrame++; // Advance the current frame! + // If our current frame has completed the FADE_IN + if (curFrame > maxFrame) { + mStatus = ESTATUS_HIDDEN; + curFrame = maxFrame; + // ??? TODO + result = (mFlag & FLAG_1) != 0; + } + // set the alpha to opaque - how much progression made + mColor.a = 0xFF - 0xFF * ((u32)curFrame / mFrameCount); + break; + } + case ESTATUS_FADE_OUT: { + u16 curFrame = mFrame; + u16 maxFrame = mFrameCount; + // If the animation is completed. + if (curFrame > maxFrame) { + if (curFrame > maxFrame + 1) { + mStatus = ESTATUS_OPAQUE; + result = (mFlag & FLAG_4) != 0; + } + curFrame = maxFrame = mFrameCount; + } + // Set the alpha to HIDDEN (0) + amount of progress made. + mColor.a = 0xFF * ((u32)curFrame / maxFrame); + break; + } + } + return result; +} + +void ColorFader::draw() { + Mtx44 projectionMtx; + Mtx positionMtx; + + // If the quad is fully transparent, don't draw it! + if (mColor.a == 0) + return; + + // Setup the projection matrix + MTXOrtho(projectionMtx, mTopY, mBottomY, mLeftX, mRightX, 0, 1); + GXSetProjection(projectionMtx, GX_ORTHOGRAPHIC); + + // Set the viewport + GXSetViewport(mLeftX, mTopY, mRightX - mLeftX, mBottomY - mTopY, 1, 0); + + // Set scissoring + GXSetScissor((u32)mLeftX, (u32)mTopY, (u32)(mRightX - mLeftX), + (u32)(mBottomY - mTopY)); + + // Set the position matrix + MTXIdentity(positionMtx); + GXLoadPosMtxImm(positionMtx, 0); + GXSetCurrentMtx(0); + + // Set up for rendering f32 POS-only QUADS + GXClearVtxDesc(); + GXInvalidateVtxCache(); + GXSetVtxDesc(GX_VA_POS, GX_DIRECT); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); + + // Set material channels + GXSetNumChans(1); + GXSetChanMatColor(GX_COLOR0A0, mColor); + GXSetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, 0, GX_DF_NONE, + GX_AF_NONE); + + // Disable texture and indirect + GXSetNumTexGens(0); + GXSetNumIndStages(0); + __GXSetIndirectMask(0); + + // Setup TEV + GXSetNumTevStages(1); + GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR); + GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); + + // Setup blending + if (mColor.a != 0xFF) + GXSetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, + GX_LO_SET); // translucency + else // If fully opaque, no need to blend + GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_SET); + + // Color/Alpha Update + GXSetColorUpdate(GX_TRUE); + GXSetAlphaUpdate(GX_TRUE); + + // Disable Z comparison + GXSetZMode(0, GX_NEVER, 0); + GXSetCullMode(GX_CULL_BACK); + + GXBegin(GX_QUADS, GX_VTXFMT0, 4); + { + GXPosition3f32(mLeftX, mTopY, 0.0f); // top left + GXPosition3f32(mRightX, mTopY, 0.0f); // top right + GXPosition3f32(mRightX, mBottomY, 0.0f); // bottom right + GXPosition3f32(mLeftX, mBottomY, 0.0f); // bottom left + } + GXEnd(); +} + +ColorFader::~ColorFader() {} + +} // namespace EGG diff --git a/source/egg/core/eggColorFader.hpp b/source/egg/core/eggColorFader.hpp new file mode 100644 index 000000000..98d0b2910 --- /dev/null +++ b/source/egg/core/eggColorFader.hpp @@ -0,0 +1,119 @@ +#pragma once +#include +#include + +namespace EGG { +class Fader { +public: + enum EStatus { + //! @brief The screen is completely blacked out. + ESTATUS_OPAQUE = 0, + //! @brief The screen is completely unblocked. + ESTATUS_HIDDEN = 1, + //! @brief Transition from OPAQUE -> HIDDEN + ESTATUS_FADE_IN = 2, + //! @brief Transition from HIDDEN -> OPAQUE + ESTATUS_FADE_OUT = 3 + }; + +public: + virtual void setStatus(EStatus status) = 0; //!< [vt+0x08] + virtual EStatus getStatus() const = 0; //!< [vt+0x0C] + virtual bool fadeIn() = 0; //!< [vt+0x10] + virtual bool fadeOut() = 0; //!< [vt+0x14] + virtual int calc() = 0; //!< [vt+0x18] + virtual void draw() = 0; //!< [vt+0x1C] +public: + EStatus mStatus; // 04 +}; +class ColorFader : public Fader { +public: + virtual ~ColorFader(); //!< [vt+0x24] + + enum ColorFaderFlag { + FLAG_1 = 1, // calc() -> case mStatus == ESTATUS_2 + FLAG_2 = 2, // ctor + FLAG_4 = 4, // unseen + FLAG_8 = 8 // ctor + }; + u8 mFlag; // 08 + // 1B implicit pad + u16 mFrameCount; // 0A s/u? set to 20 in ctor + u16 mFrame; // set to 0 in ctor + // 2B implicit pad + nw4r::ut::Color mColor; //!< [+0x10] 10 - r, 11 - g, 12 - b, 13 - a + f32 mLeftX; //!< [+0x14] + f32 mTopY; //!< [+0x18] + f32 mRightX; //!< [+0x1C] + f32 mBottomY; //!< [+0x20] + + //! @brief Header definition. Linked into MKW at PAL 80007BC0. + EStatus getStatus() const override { return this->mStatus; } + + //! @brief A constructor + //! + //! @param[in] leftX Left boundary of the screen in px. + //! @param[in] bottomY Bottom boundary of the screen in px. + //! @param[in] width Width of the screen in px. + //! @param[in] height Height of the screen in px. + //! @param[in] color Color of the fading screen. + //! @param[in] status State of the fader to set. + //! Acceptable states are OPAQUE and + //! HIDDEN. + //! + //! @post + //! - mFrameCount = 20 + //! - mFlag = FLAG_8 | FLAG_2 + //! - Color set + //! - mLeftX, mBottomY, mRightX, mTopY set + //! - status and alpha set accordingly + //! + ColorFader(f32 leftX, f32 bottomY, f32 width, f32 height, + nw4r::ut::Color color, Fader::EStatus status); + + //! @brief Sets the frame of the fade. + //! + //! @param[in] frame The frame to set. Must not be zero. + //! + //! @remarks Wii Sports function between ct and setColor. Not linked in MKW. + //! + void setFrame(u16 frame); + + //! @brief Sets the color. Does not modify alpha. + //! + //! @param[in] color Color to set. + //! + void setColor(nw4r::ut::Color color); + + //! @brief Sets the status of the color fader. + //! + //! @param[in] status Status of the fader to set. + //! Acceptable states are OPAQUE and + //! HIDDEN. + //! + override void setStatus(Fader::EStatus status); + + //! @brief Fade in from pure blacked-out. + //! + //! @returns Whether or not this action was carried out. (If the screen was + //! OPAQUE when called) + //! + override bool fadeIn(); + + //! @brief Fade out from no-obstruction. + //! + //! @returns Whether or not this action was carried out. (If the screen was + //! HIDDEN when called) + //! + override bool fadeOut(); + + //! @brief Calculate the fader at the current frame. + //! + //! @returns Whether or not the operation was successful? + //! + override int calc(); + + //! @brief Draw the fader (translucent quad). + override void draw(); +}; +} // namespace EGG From fd635f5027ff096be3afd34b57c385776ab64c63 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:32:49 -0600 Subject: [PATCH 059/477] :construction: eggExpHeap.cpp --- source/egg/core/eggExpHeap.cpp | 151 +++++++++++++++++++++++++++++++++ source/egg/core/eggExpHeap.hpp | 118 ++++++++++++++++++++++++++ 2 files changed, 269 insertions(+) create mode 100644 source/egg/core/eggExpHeap.cpp create mode 100644 source/egg/core/eggExpHeap.hpp diff --git a/source/egg/core/eggExpHeap.cpp b/source/egg/core/eggExpHeap.cpp new file mode 100644 index 000000000..11e55cd3c --- /dev/null +++ b/source/egg/core/eggExpHeap.cpp @@ -0,0 +1,151 @@ +/*! + * @file + * @brief EGG wraper for expanded heaps implementation. + */ + +#include +#include +#include + +namespace EGG { + +ExpHeap::~ExpHeap() { + // Recursively free all memory + Heap::dispose(); + + // Destroy the wrapped MEM heap + MEMDestroyExpHeap(this->mHeapHandle); +} + +ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { + u32 heapEnd = nw4r::ut::RoundDown((u32)block + size, 4); // r3 + u32 heapStart = nw4r::ut::RoundUp((u32)block, 4); // r27 + + ExpHeap* createdHeap = NULL; // r30 + + // This simplifies down to size > 0; + // as size is unsigned, we know this check will always pass. + if (heapStart <= heapEnd) { + u32 arenaSize = heapEnd - heapStart; // r4 + + // Enforce requirement for 56 byte ExpHeap object + minimum 4 bytes in heap. + if (arenaSize >= 60) { + // We reserve 56 bytes in the heap. + MEMHeapHandle memHeap = MEMCreateExpHeapEx((void*)(heapStart + 56), + arenaSize - 56, attr); // r28 + + if (memHeap) { + Heap* containHeap = Heap::findContainHeap((void*)heapStart); // r31 + new ((void*)heapStart) ExpHeap(memHeap); // Inline + ((ExpHeap*)heapStart)->mParentBlock = block; + ((ExpHeap*)heapStart)->mParentHeap = containHeap; + createdHeap = (ExpHeap*)heapStart; + } + return createdHeap; + } + } + + return 0; +} +ExpHeap* ExpHeap::create(u32 size, Heap* heap, u16 attr) { + ExpHeap* newHeap = NULL; + + if (heap == NULL) + heap = Heap::sCurrentHeap; + if (size == -1) + size = heap->getAllocatableSize(4); + + if ((void* block = heap->alloc(size, 4))) { + newHeap = ExpHeap::create(block, size, attr); + + if (newHeap) + newHeap->mParentHeap = heap; + else + heap->free(block); + } + + return newHeap; +} + +void ExpHeap::destroy() { + Heap* parentHeap = findParentHeap(); + + this->~ExpHeap(); // arg=-1 + + if (parentHeap) + parentHeap->free(this); +} + +void* ExpHeap::alloc(u32 size, s32 align) { + if ((this->mFlag & HEAP_FLAG_LOCKED) != 0) + OSPanic("eggExpHeap.cpp", 174, "DAME DAME\n"); + + return MEMAllocFromExpHeapEx(this->mHeapHandle, size, align); +} + +void ExpHeap::free(void* block) { MEMFreeToExpHeap(this->mHeapHandle, block); } + +u32 ExpHeap::resizeForMBlock(void* memBlock, u32 size) { + return MEMResizeForMBlockExpHeap(this->mHeapHandle, memBlock, size); +} +u32 ExpHeap::getTotalFreeSize() { + return MEMGetTotalFreeSizeForExpHeap(this->mHeapHandle); +} +u32 ExpHeap::getAllocatableSize(s32 align) { + return MEMGetAllocatableSizeForExpHeapEx(this->mHeapHandle, align); +} +u16 ExpHeap::setGroupID(u16 groupID) { + return MEMSetGroupIDForExpHeap(this->mHeapHandle, groupID); +} +void ExpHeap::addGroupSize(void* block, MEMHeapHandle heap, u32 userParam) { + u32 grpID = MEMGetGroupIDForMBlockExpHeap(block); + // below may return + ((GroupSizeRecord*)userParam) + ->addSize((u16)grpID, MEMGetSizeForMBlockExpHeap(block)); +} +void ExpHeap::calcGroupSize(GroupSizeRecord* record) { + record->reset(); + MEMVisitAllocatedForExpHeap(this->mHeapHandle, addGroupSize, (u32)record); +} +u32 ExpHeap::adjust() { + u32 adjustedSize = MEMAdjustExpHeap(this->mHeapHandle) + 56; + + if (adjustedSize > 56 && mParentHeap) { + mParentHeap->resizeForMBlock(mParentBlock, adjustedSize); + return adjustedSize; + } else { + return 0; + } +} +void ExpHeap::initAllocator(Allocator* allocator, s32 align) { + MEMInitAllocatorForExpHeap((MEMAllocator*)allocator, this->mHeapHandle, + align); +} +ExpHeap::GroupSizeRecord::GroupSizeRecord() { this->reset(); } +void ExpHeap::GroupSizeRecord::reset() { + // CW optimizes this down to 8 sets of 32 byte writes. + + u32* iterEntry = &entries[0]; + for (int i = 0; i < 256; i++) { + *iterEntry = 0; + iterEntry++; + } +} +void ExpHeap::GroupSizeRecord::addSize(u16 groupID, u32 size) { + entries[groupID] += size; +} +namespace { +void free_all_visitor(void* block, MEMHeapHandle heap, u32 userParam) { + MEMFreeToExpHeap(heap, block); +} +} // namespace +void ExpHeap::freeAll() { + Heap::dispose(); + +// MEMVisitAllocatedForExpHeap(this->mHeapHandle, &free_all_visitor, 0); +} +Heap::eHeapKind ExpHeap::getHeapKind() const { + return Heap::HEAP_KIND_EXPANDED; +} + +} // namespace EGG diff --git a/source/egg/core/eggExpHeap.hpp b/source/egg/core/eggExpHeap.hpp new file mode 100644 index 000000000..fe313a602 --- /dev/null +++ b/source/egg/core/eggExpHeap.hpp @@ -0,0 +1,118 @@ +/*! + * @file + * @brief EGG wraper for expanded heaps header. + */ + +#pragma once + +#include +#include + +namespace EGG { + +class ExpHeap : public Heap { +public: + struct GroupSizeRecord { + u32 entries[256]; + + GroupSizeRecord(); + void reset(); + void addSize(u16 groupID, u32 size); + }; + + // Always inline in MKW? + ExpHeap(MEMHeapHandle heapHandle) : Heap(heapHandle) {} + virtual ~ExpHeap() override; + + //! @brief Create an EGG ExpHeap and wrapped MEM ExpHeap in a certain region. + //! + //! @param[in] block Start of memory block to create. + //! @param[in] size Size of the new heap to create. Must be at least 60 bytes. + //! @param[in] attr MEM ExpHeap option flags. + //! + //! @returns The created ExpHeap + //! + static ExpHeap* create(void* block, u32 size, u16 attr); + + //! @brief Create a new ExpHeap as a child of an existing heap. + //! + //! @param[in] size Size of the new heap to create. If -1, the maximum + //! alloctable size will be used. + //! @param[in] heap Parent heap to use. If NULL, default heap will be used. + //! @param[in] attr MEM ExpHeap option flags. + // + static ExpHeap* create(u32 size, Heap* heap, u16 attr); + + //! @brief There's no evidence of this macro/inline function existing, but it + //! makes future work a lot cleaner. + static inline ExpHeap* Make(void* start, u32 size, char* name) { + ExpHeap* made = ExpHeap::create(start, size, 0); + made->mName = name; + return made; + } + //! @brief There's no evidence of this macro/inline function existing, but it + //! makes future work a lot cleaner. + static inline ExpHeap* MakeChild(u32 size, Heap* parent, char* name) { + ExpHeap* made = ExpHeap::create(size, parent, 0); + made->mName = name; + return made; + } + + //! @brief Destroy the current heap and free itself from its parent if it has + //! one. + //! + void destroy() override; + + //! @brief Allocate a block of memory in the heap. + //! + //! @param[in] size Size of the block of memory. + //! @param[in] align Alignment of memory block. + //! + //! @pre _1 must not have the last bit set. + //! If set, `OSPanic("eggExpHeap.cpp", 174, "DAME DAME\n");` + //! + void* alloc(u32 size, s32 align) override; + + //! @brief Free a block of memory in the heap. + //! + //! @param[in] block Block of memory in the heap. + //! + void free(void* block) override; + + //! @brief Resize allocated memory block. + //! + //! @param[in] block Allocated memory block to resize. + //! @param[in] size New size for memory block. + //! + //! @returns New size of memory block. + //! - When expanding, may be greater than the size of the + //! requested size. + //! - When reducing, may be the original block size. + //! - When unsuccessful, 0. + //! + u32 resizeForMBlock(void* block, u32 size) override; + + u32 getTotalFreeSize(); + + u32 getAllocatableSize(s32 align) override; + + u16 setGroupID(u16 groupID); + + u32 adjust() override; + + void initAllocator(Allocator* allocator, s32 align) override; + + static void addGroupSize(void* block, MEMHeapHandle heap, u32 userParam); + + void calcGroupSize(GroupSizeRecord* record); + + void freeAll(); + + //! @brief Returns the heap kind. + //! + //! @returns HEAP_KIND_EXPANDED + //! + eHeapKind getHeapKind() const; +}; + +} // namespace EGG From d22153306baf2869a6df7be0d8f11ea7cfdd3349 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:34:53 -0600 Subject: [PATCH 060/477] :construction: eggScene.cpp --- source/egg/core/eggScene.cpp | 20 ++++++++++ source/egg/core/eggScene.hpp | 75 ++++++++++++++++++++++++++++++++++++ system/slices.csv | 1 + 3 files changed, 96 insertions(+) create mode 100644 source/egg/core/eggScene.cpp create mode 100644 source/egg/core/eggScene.hpp diff --git a/source/egg/core/eggScene.cpp b/source/egg/core/eggScene.cpp new file mode 100644 index 000000000..7fc9a7f33 --- /dev/null +++ b/source/egg/core/eggScene.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +namespace EGG { + +Scene::Scene() { + _10 = Heap::sCurrentHeap; + mHeapMem1 = SceneManager::sHeapMem1_ForCreateScene; + mHeapMem2 = SceneManager::sHeapMem2_ForCreateScene; + mHeapDebug = SceneManager::sHeapDebug_ForCreateScene; + pParentScene = 0; + pChildScene = 0; + mID = -1; + pSceneMgr = 0; +} + +Scene::~Scene() {} + +} // namespace EGG \ No newline at end of file diff --git a/source/egg/core/eggScene.hpp b/source/egg/core/eggScene.hpp new file mode 100644 index 000000000..2a2bfa7e7 --- /dev/null +++ b/source/egg/core/eggScene.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include + +namespace EGG { + +class SceneManager; + +class Scene : public Disposer { +public: + virtual ~Scene(); //! [vt+0x08] Virtual destructor. + + virtual void calc() {} //! [vt+0x0C] + virtual void draw() {} //! [vt+0x10] + virtual void enter() {} //! [vt+0x14] + virtual void exit() {} //! [vt+0x18] + virtual void reinit() {} //! [vt+0x1C] + virtual void incoming_childDestroy() {} //! [vt+0x20] + virtual void outgoing_childCreate() {} //! [vt+0x24] + + //! @brief Constructor + Scene(); + +private: + // -- vt + inherited mContainHeap from Disposer -- + int _08; // TODO get name from SceneManager usage + Scene* _0C; // TODO get name from SceneManager usage + + Heap* _10; // something like mCreatorHeap? set to Heap::sCurrentHeap in ct + Heap* mHeapMem1; //!< [+0x14] + Heap* mHeapMem2; //!< [+0x18] + Heap* mHeapDebug; //!< [+0x1C] + + Scene* pParentScene; //!< [+0x20] name from @see SceneManager::createScene + Scene* pChildScene; //!< [+0x24] If non null, destroyed too in destroyScene. + //!< childscene?. name from @see SceneManager::createScene + + int mID; //!< [+0x28] + SceneManager* pSceneMgr; //!< [+0x2C] @see SceneManager::createScene + +public: + // inlines. actually above exit and below enter + inline void setSceneMgr(SceneManager* mgr) { this->pSceneMgr = mgr; } + inline void setParentScene(Scene* scene) { this->pParentScene = scene; } + inline void setSceneID(int ID) { this->mID = ID; } + inline void setChildScene(Scene* scene) { pChildScene = scene; } + + inline const Scene* getParentScene() const { return pParentScene; } + inline Scene* getParentScene() { return pParentScene; } + + inline const int getSceneID() const { return mID; } + + inline const Heap* getHeap() const { return _10; } + inline Heap* getHeap() { return _10; } + + inline const Heap* getHeap_Mem1() const { return mHeapMem1; } + inline Heap* getHeap_Mem1() { return mHeapMem1; } + + inline const Scene* getChildScene() const { return pChildScene; } + inline Scene* getChildScene() { return pChildScene; } + + inline const SceneManager* getSceneMgr() const { return pSceneMgr; } + inline SceneManager* getSceneMgr() { return pSceneMgr; } + + inline const Heap* getHeap_Mem2() const { return mHeapMem2; } + inline Heap* getHeap_Mem2() { return mHeapMem2; } + + // not yet found in symbol maps, but needed + inline const Heap* getHeap_Debug() const { return mHeapDebug; } + inline Heap* getHeap_Debug() { return mHeapDebug; } +}; + +} // namespace EGG diff --git a/system/slices.csv b/system/slices.csv index e81033d9f..925b174b0 100644 --- a/system/slices.csv +++ b/system/slices.csv @@ -9,6 +9,7 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,eggGraphicsFifo.cpp,,,,,,,80229540,802296A8,,,,,,,802A30B0,802A30BC,,,,,0x80386E90,0x80386E99,,,, 1,eggHeap.cpp,,,,,,,802296A8,80229FAC,,,,,80257740,80257824,802A30C0,0x802a30ec,80384320,0x80384348,,,80386EA0,80386EC0,80388D68 ,80388D80,, 1,eggQuat.cpp,,,,,,,80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, +0,eggScene.cpp,,,,,,,8023AD10,8023ADDC,,,,,,,,,,,,,,,,,, 1,eggStreamDecomp.cpp,,,,,,,80242498,80242504,,,,,,,802A3F78,802A3F90,,,,,,,,,, ,eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,80386F60,80386F64,,,, 1,eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, From e9b6e795fdd953da95f2097572c9bca3dabdda88 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 18 May 2021 18:39:32 -0600 Subject: [PATCH 061/477] :construction: ObjectFactory.cpp, ObjectParameter.cpp, ObjectDirector.cpp --- source/game/geo/ObjectDirector.cpp | 62 +++++++ source/game/geo/ObjectDirector.hpp | 68 ++++++++ source/game/geo/ObjectFactory.cpp | 247 ++++++++++++++++++++++++++++ source/game/geo/ObjectParameter.cpp | 29 ++++ source/game/geo/ObjectParameter.hpp | 67 ++++++++ 5 files changed, 473 insertions(+) create mode 100644 source/game/geo/ObjectDirector.cpp create mode 100644 source/game/geo/ObjectDirector.hpp create mode 100644 source/game/geo/ObjectFactory.cpp create mode 100644 source/game/geo/ObjectParameter.cpp create mode 100644 source/game/geo/ObjectParameter.hpp diff --git a/source/game/geo/ObjectDirector.cpp b/source/game/geo/ObjectDirector.cpp new file mode 100644 index 000000000..3a2d9921a --- /dev/null +++ b/source/game/geo/ObjectDirector.cpp @@ -0,0 +1,62 @@ +/*! + * @file + * @brief Manages the objects and related tables! + */ + +#include + +namespace Field { + +ObjectDirector::ObjectDirector() { + // Store first virtual table at +0x00. + // Then, store another at 0x134 + mpObjectParameter = new ObjectParameter("ObjFlow.bin"); // sizeof=16 + + mpGeoHitTableItemManager = + new GeoHitTableItemManager("GeoHitTableItem.bin"); // INLINE (depth 1) + + mpGeoHitTableItemObjManager = new GeoHitTableItemObjManager( + "GeoHitTableItemObj.bin"); // INLINE (depth 1) + + mpGeoHitTableKartManager = + new GeoHitTableKartManager("GeoHitTableKart.bin"); // INLINE (depth 1) + + mpGeoHitTableKartObjManager = new GeoHitTableKartObjManager( + "GeoHitTableKartObj.bin"); // INLINE (depth 1) + + // TODO + _54 = (spRaceData->_B70 >> 31) + (6 < spRaceData->field_B70); + + _55 = spRaceData->_B70 == 2 || spRaceData->_B70 == 5; + + u32 slot = spRaceData->_B68; // r0 + + // TODO: expanded switch? + if (slot >= RK_SLOT_SENIOR_COURSE && slot <= RK_SLOT_RIDGEHIGHWAY_COURSE || + slot == RK_SLOT_RING_MISSION || slot == 0x33 || + slot == RK_SLOT_OLD_DESERT_DS || slot == RK_SLOT_OLD_HEYHO_GBA) { + _58 = new unk(); // sizeof=244 INLINE + { // inline ctor of unk class + _58->_F0 = 0; + } + } else { + _58 = NULL; + } + + _1C = (unk*)new u8[800]; + _24 = (unk*)new u8[800]; + _2C = (unk*)new u8[800]; + _3C = (unk*)new u8[800]; + _34 = (unk*)new u8[800]; + _40 = (unk*)new u8[800]; + _44 = new EGG::Vector3f[200]; // sizeof=2416 + _48 = (unk*)new u8[800]; + + // nasty runtime casts + _124 = 0; + _128 = 45; + _12C = 90; + _130 = 135; +} +// ... +} // namespace Field \ No newline at end of file diff --git a/source/game/geo/ObjectDirector.hpp b/source/game/geo/ObjectDirector.hpp new file mode 100644 index 000000000..a81d9c3e9 --- /dev/null +++ b/source/game/geo/ObjectDirector.hpp @@ -0,0 +1,68 @@ +/*! + * @file + * @brief Manages the objects and related tables! + */ + +#pragma once + +#include + +#include + +namespace Field { + +class BaseGeoHitTableManager {}; +class GeoHitTableItemManager : public BaseGeoHitTableManager { // sizeof= +}; +struct ObjectBase; +class ObjectDirector { +public: + static ObjectDirector* sInstance; + static ObjectDirector* getStaticInstance() { return sInstance; } + ObjectDirector(); + // +0x134 vdt + // +0x00 vdt (below) + virtual ~ObjectDirector(); + // private: + ObjectParameter* mpObjectParameter; //!< [+0x04] + + GeoHitTableItemManager* mpGeoHitTableItemManager; //!< [+0x08] + GeoHitTableItemObjManager* mpGeoHitTableItemObjManager; //!< [+0x0C] + + GeoHitTableItemManager* mpGeoHitTableKartManager; //!< [+0x10] + GeoHitTableItemObjManager* mpGeoHitTableKartObjManager; //!< [+0x14] + + // ... + u16 mNumObjectsA; // 0x20 + ObjectBase** mppObjectsA; // 0x24 + //.. + u16 mNumObjectsB; // 0x30 short count + ObjectBase** mppObjectsB; // 0x34 ObjArray + + unk8 _54; + bool _55; + // .. or pad + unk* _58; + + //... + + unk* _1C; // buf sizeof=800 + // ... + unk* _24; // buf sizeof=800 + // ... + unk* _2C; // buf sizeof=800 + // ... + unk* _34; // buf sizeof=800 + // ... + unk* _3C; // buf sizeof=800 + unk* _40; // buf sizeof=800 + EGG::Vector3f* _44; + unk* _48; // buf sizeof=800 + // ... + f32 _124; + f32 _128; + f32 _12C; + f32 _130; +}; + +} // namespace Field diff --git a/source/game/geo/ObjectFactory.cpp b/source/game/geo/ObjectFactory.cpp new file mode 100644 index 000000000..8e5150353 --- /dev/null +++ b/source/game/geo/ObjectFactory.cpp @@ -0,0 +1,247 @@ +#include "ObjectDirector.hpp" +#include + +namespace Field { + +void ConstructObject(Field::ObjectDirector* director, + Field::MapdataGeoObjAccessor* pGobj) { + UNUSED(director); + +#define CASE(name, type) \ + if (!strcmp(&Field::ObjectDirector::getStaticInstance() \ + ->mpObjectParameter->getParameter(pGobj->ref().id) \ + ->data.Name, \ + name)) \ + return new type(pGobj)->vf0x20(); + + // The horrendous autogen part... + { + CASE("kuribo", Objectkuribo); + CASE("cow", Objectcow); + CASE("kinoko_ud", Objectkinoko_ud); + CASE("kinoko_bend", Objectkinoko_bend); + CASE("kinoko_nm", Objectkinoko_nm); + CASE("itembox", Objectitembox); + CASE("wanwan", Objectwanwan); + CASE("Hwanwan", ObjectHwanwan); + CASE("choropu", Objectchoropu); + CASE("choropu2", Objectchoropu2); + CASE("poihana", Objectpoihana); + CASE("Psea", ObjectPsea); + CASE("bulldozer_left", Objectbulldozer_left); + CASE("bulldozer_right", Objectbulldozer_right); + CASE("TruckWagon", ObjectTruckWagon); + CASE("flag", Objectflag); + CASE("flagBlend", ObjectflagBlend); + CASE("pylon01", Objectpylon01); + CASE("basabasa", Objectbasabasa); + CASE("VolcanoPiece", ObjectVolcanoPiece); + CASE("VolcanoRock1", ObjectVolcanoRock1); + CASE("gnd_trapezoid", Objectgnd_trapezoid); + CASE("gnd_sphere", Objectgnd_sphere); + CASE("gnd_wave1", Objectgnd_wave1); + CASE("gnd_wave2", Objectgnd_wave2); + CASE("gnd_wave3", Objectgnd_wave3); + CASE("gnd_wave4", Objectgnd_wave4); + CASE("heyho2", Objectheyho2); + CASE("K_sticklift00", ObjectK_sticklift00); + CASE("Ksticketc", ObjectKsticketc); + CASE("heyho", Objectheyho); + CASE("truckChimSmk", ObjecttruckChimSmk); + CASE("escalator", Objectescalator); + CASE("Crane", ObjectCrane); + CASE("TwistedWay", ObjectTwistedWay); + CASE("kart_truck", Objectkart_truck); + CASE("car_body", Objectcar_body); + CASE("K_bomb_car", ObjectK_bomb_car); + CASE("K_bomb_car1", ObjectK_bomb_car1); + CASE("FlamePole", ObjectFlamePole); + CASE("FlamePole_v", ObjectFlamePole_v); + CASE("FlamePole_v_big", ObjectFlamePole_v_big); + CASE("f_itembox", Objectf_itembox); + CASE("sun", Objectsun); + CASE("SpaceSun", ObjectSpaceSun); + CASE("EarthRing", ObjectEarthRing); + CASE("sanbo", Objectsanbo); + CASE("MashBalloonGC", ObjectMashBalloonGC); + CASE("castleballoon1", Objectcastleballoon1); + CASE("skyship", Objectskyship); + CASE("Piston", ObjectPiston); + CASE("PalmTree", ObjectPalmTree); + CASE("WLarrowGC", ObjectWLarrowGC); + CASE("oilSFC", ObjectoilSFC); + CASE("Press", ObjectPress); + CASE("w_itemboxline", Objectw_itemboxline); + CASE("VolcanoBall1", ObjectVolcanoBall1); + CASE("penguin_l", Objectpenguin_l); + CASE("penguin_m", Objectpenguin_m); + CASE("penguin_s", Objectpenguin_s); + CASE("w_woodbox", Objectw_woodbox); + CASE("w_itembox", Objectw_itembox); + CASE("dossunc", Objectdossunc); + CASE("BeltCurveA", ObjectBeltCurveA); + CASE("BeltEasy", ObjectBeltEasy); + CASE("BeltCrossing", ObjectBeltCrossing); + CASE("WLfirebarGC", ObjectWLfirebarGC); + CASE("WLfireringGC", ObjectWLfireringGC); + CASE("dossun", Objectdossun); + CASE("pakkun_f", Objectpakkun_f); + CASE("monte_a", Objectmonte_a); + CASE("KmoonZ", ObjectKmoonZ); + CASE("Hanabi", ObjectHanabi); + CASE("DonkyCannon_wii", ObjectDonkyCannon_wii); + CASE("DonkyCannonGC", ObjectDonkyCannonGC); + CASE("tree_cannon", Objecttree_cannon); + CASE("StarRing", ObjectStarRing); + CASE("boble", Objectboble); + CASE("escalator_group", Objectescalator_group); + CASE("casino_roulette", Objectcasino_roulette); + CASE("aurora", Objectaurora); + CASE("dc_sandcone", Objectdc_sandcone); + CASE("begoman_spike", Objectbegoman_spike); + CASE("begoman_manager", Objectbegoman_manager); + CASE("venice_nami", Objectvenice_nami); + CASE("venice_saku", Objectvenice_saku); + CASE("venice_hasi", Objectvenice_hasi); + CASE("r_parasol", Objectr_parasol); + CASE("quicksand", Objectquicksand); + CASE("koopaBall", ObjectkoopaBall); + CASE("hanachan", Objecthanachan); + CASE("BossHanachan", ObjectBossHanachan); + CASE("koopaFirebar", ObjectkoopaFirebar); + CASE("DKrockGC", ObjectDKrockGC); + CASE("koopaFigure", ObjectkoopaFigure); + CASE("ami", Objectami); + CASE("seagull", Objectseagull); + CASE("bblock", Objectbblock); + CASE("Epropeller", ObjectEpropeller); + CASE("MiiStatueM1", ObjectMiiStatueM1); + CASE("MiiStatueM2", ObjectMiiStatueM2); + CASE("MiiStatueL1", ObjectMiiStatueL1); + CASE("MiiStatueL2", ObjectMiiStatueL2); + CASE("MiiStatueL3", ObjectMiiStatueL3); + CASE("MiiStatueP1", ObjectMiiStatueP1); + CASE("MiiStatueD1", ObjectMiiStatueD1); + CASE("MiiStatueD2", ObjectMiiStatueD2); + CASE("MiiStatueDK1", ObjectMiiStatueDK1); + CASE("MiiStatueBL1", ObjectMiiStatueBL1); + CASE("MiiStatueBD1", ObjectMiiStatueBD1); + CASE("woodbox", Objectwoodbox); + CASE("sound_river", Objectsound_river); + CASE("sound_big_river", Objectsound_big_river); + CASE("sound_water_fall", Objectsound_water_fall); + CASE("sound_lake", Objectsound_lake); + CASE("sound_big_fall", Objectsound_big_fall); + CASE("sound_sea", Objectsound_sea); + CASE("sound_fountain", Objectsound_fountain); + CASE("sound_audience", Objectsound_audience); + CASE("sound_sand_fall", Objectsound_sand_fall); + CASE("sound_volcano", Objectsound_volcano); + CASE("sound_lift", Objectsound_lift); + CASE("sound_Mii", Objectsound_Mii); + CASE("MiiSphinxY1", ObjectMiiSphinxY1); + CASE("MiiSphinxY2", ObjectMiiSphinxY2); + CASE("crab", Objectcrab); + CASE("starGate", ObjectstarGate); + CASE("karehayama", Objectkarehayama); + CASE("FireSnake", ObjectFireSnake); + CASE("sunDS", ObjectsunDS); + CASE("CarB", ObjectCarB); + CASE("leaf_effect", Objectleaf_effect); + CASE("Alarm", ObjectAlarm); + CASE("Steam", ObjectSteam); + CASE("WLscreenGC", ObjectWLscreenGC); + CASE("fks_screen_wii", Objectfks_screen_wii); + CASE("dkmonitor", Objectdkmonitor); + CASE("RM_ring1", ObjectRM_ring1); + CASE("FireSnake_v", ObjectFireSnake_v); + CASE("venice_gondola", Objectvenice_gondola); + CASE("DKship64", ObjectDKship64); + CASE("pukupuku", Objectpukupuku); + CASE("moray", Objectmoray); + CASE("dc_pillar", Objectdc_pillar); + CASE("b_teresa", Objectb_teresa); + CASE("KoopaFigure64", ObjectKoopaFigure64); + CASE("HeyhoShipGBA", ObjectHeyhoShipGBA); + CASE("HeyhoBallGBA", ObjectHeyhoBallGBA); + CASE("TownBridgeDSc", ObjectTownBridgeDSc); + CASE("BGteresaSFC", ObjectBGteresaSFC); + CASE("s_itembox", Objects_itembox); + CASE("taimatsu", Objecttaimatsu); + CASE("InsekiA", ObjectInsekiA); + CASE("InsekiB", ObjectInsekiB); + CASE("puchi_pakkun", Objectpuchi_pakkun); + CASE("truckChimSmkW", ObjecttruckChimSmkW); + CASE("cruiser", Objectcruiser); + CASE("WLwallGC", ObjectWLwallGC); + CASE("group_enemy_a", Objectgroup_enemy_a); + CASE("group_enemy_b", Objectgroup_enemy_b); + CASE("group_enemy_c", Objectgroup_enemy_c); + CASE("group_enemy_d", Objectgroup_enemy_d); + CASE("group_enemy_e", Objectgroup_enemy_e); + CASE("group_enemy_f", Objectgroup_enemy_f); + CASE("group_monte_a", Objectgroup_monte_a); + CASE("airblock", Objectairblock); + CASE("DKturibashiGCc", ObjectDKturibashiGCc); + CASE("honeBall", ObjecthoneBall); + CASE("sanbo_big", Objectsanbo_big); + CASE("FallBsA", ObjectFallBsA); + CASE("FallBsB", ObjectFallBsB); + CASE("FallBsC", ObjectFallBsC); + CASE("Fall_Y", ObjectFall_Y); + CASE("Fall_MH", ObjectFall_MH); + CASE("DKfalls", ObjectDKfalls); + CASE("volsmk", Objectvolsmk); + CASE("bird", Objectbird); + CASE("Flash_L", ObjectFlash_L); + CASE("Flash_B", ObjectFlash_B); + CASE("Flash_S", ObjectFlash_S); + CASE("Flash_M", ObjectFlash_M); + CASE("Flash_W", ObjectFlash_W); + CASE("cruiserR", ObjectcruiserR); + CASE("M_obj_s_jump", ObjectM_obj_s_jump); + CASE("M_obj_jump", ObjectM_obj_jump); + CASE("Mdush", ObjectMdush); + CASE("Twanwan", ObjectTwanwan); + CASE("sin_itembox", Objectsin_itembox); + CASE("DemoJugemu", ObjectDemoJugemu); + CASE("DemoEf", ObjectDemoEf); + CASE("dokan_sfc", Objectdokan_sfc); + CASE("Kamifubuki", ObjectKamifubuki); + CASE("group_enemy_a_demo", Objectgroup_enemy_a_demo); + CASE("group_monte_a_demo", Objectgroup_monte_a_demo); + CASE("castletree1", Objectcastletree1); + CASE("mare_a", Objectmare_a); + CASE("mare_b", Objectmare_b); + CASE("EnvKarehaUp", ObjectEnvKarehaUp); + CASE("CarA1", ObjectCarA1); + CASE("CarA2", ObjectCarA2); + CASE("CarA3", ObjectCarA3); + CASE("ShMiiObj01", ObjectShMiiObj01); + CASE("ShMiiObj02", ObjectShMiiObj02); + CASE("ShMiiObj03", ObjectShMiiObj03); + CASE("miiposter", Objectmiiposter); + CASE("MiiObj01", ObjectMiiObj01); + CASE("MiiObj02", ObjectMiiObj02); + CASE("MiiObj03", ObjectMiiObj03); + CASE("MiiKanban", ObjectMiiKanban); + CASE("dk_miiobj00", Objectdk_miiobj00); + CASE("ridgemii00", Objectridgemii00); + CASE("RhMiiKanban", ObjectRhMiiKanban); + CASE("MiiSignWario", ObjectMiiSignWario); + CASE("MiiSignNoko", ObjectMiiSignNoko); + CASE("MiiSighKino", ObjectMiiSighKino); + CASE("MiiObjD01", ObjectMiiObjD01); + CASE("MiiObjD02", ObjectMiiObjD02); + CASE("MiiObjD03", ObjectMiiObjD03); + } + + if (Field::ObjectDirector::getStaticInstance() + ->mpObjectParameter->getParameter(pGobj->ref().id) + ->data.P4 == 4) + return new ObjectKCL(pGobj, 0)->vf0x20(); + else + return new ObjectParent(pGobj, 0)->vf0x20(); +} + +} // namespace Field \ No newline at end of file diff --git a/source/game/geo/ObjectParameter.cpp b/source/game/geo/ObjectParameter.cpp new file mode 100644 index 000000000..7a6a0a8d2 --- /dev/null +++ b/source/game/geo/ObjectParameter.cpp @@ -0,0 +1,29 @@ +/*! + * @file + * @brief Manages the objects and related tables! + */ + +#include +#include +#include + +namespace Field { + +ObjectParameter::ObjectParameter(const char* path) { + const Header* bin = + reinterpret_cast(Resource::Manager::spInstance->getFile( + Resource::RES_CHAN_RACE_SYS, path, nullptr)); + + mNumEntries = bin->nEntry; + mpEntries = bin->entries; + mpRemapTable = reinterpret_cast(&mpEntries[mNumEntries]); +} + +eMapdataGeoObjID ObjectParameter::getObjectIdByName(const char* key) { + for (int i = 0; i < mNumEntries; ++i) + if (!strcmp(key, mpEntries[i].data.Name)) + return static_cast(mpEntries[i].ID); + + return 0; +} +} // namespace Field diff --git a/source/game/geo/ObjectParameter.hpp b/source/game/geo/ObjectParameter.hpp new file mode 100644 index 000000000..dbc28923b --- /dev/null +++ b/source/game/geo/ObjectParameter.hpp @@ -0,0 +1,67 @@ +/*! + * @file + * @brief Manages the object flow (parameters)! + */ + +#pragma once + +#include + +namespace Field { + +typedef u32 eMapdataGeoObjID; + +class ObjectParameter { +public: + //! @brief sizeof=0x74 + //! + struct Parameter { + s16 ID; // 00 + struct Data { + char Name[32]; // 02 + char AssetsListing[64]; // 22 + // Likely: s16[8] UserData; + // Unk + u16 P0; // 62 + s16 ClipBias; // 64 + s16 ClipNear; // 66 + s16 ClipFar; // 68 + // P4: Assumption: If not null, the object is solid. + s16 P4; // 6a + // P5: Assumption: Solitity parameter, only used if P4 ≥ 1 + s16 P5; // 6c + // P6: Assumption: Solitity parameter, only used if P4 ≥ 2 + s16 P6; // 6e + // P7: Assumption: Solitity parameter, only used if P4 ≥ 3 + s16 P7; // 70 + // An unknown attribute. + // For nearly all object the value is null, only for itembox (0x65) and + // sanbo (0x199) it is 1. The attribute of the very first element is the + // total number of elements. + s16 Attribute; // 72 + } data; + }; + + //! @brief Header of the object parameter resource file. (`ObjFlow.bin`) + //! + struct Header { + short nEntry; //!< [+0x00] Number of entries in this file. + Parameter entries[]; //!< [+0x + }; + + ObjectParameter(const char* path); + virtual ~ObjectParameter() { + } // linked at end of ObjectParameter.o (as virtual) + eMapdataGeoObjID getObjectIdByName(const char* key); + + // private: + s16 mNumEntries; //!< [+0x04] + const Parameter* mpEntries; //!< [+0x08] + const s16* mpRemapTable; //!< [+0x0c] After all entries. + + inline const Parameter* getParameter(eMapdataGeoObjID id) { + return &mpEntries[mpRemapTable[id]]; + } +}; + +} // namespace Field From a7104cc2cb2e15f41f7c619b458d998a8f4dad34 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 19 May 2021 17:41:10 -0600 Subject: [PATCH 062/477] :sparkles: percent_decompiled.py --- percent_decompiled.py | 114 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 percent_decompiled.py diff --git a/percent_decompiled.py b/percent_decompiled.py new file mode 100644 index 000000000..1dda5fe80 --- /dev/null +++ b/percent_decompiled.py @@ -0,0 +1,114 @@ +def process_line(tags, items): + name = "Untitled" + start = None + code_total = 0 + data_total = 0 + + for tag, entry in zip(tags, items): + if tag == 'enabled' and entry == 0: + return + elif tag == 'name': + name = entry + continue + + is_code = 'text' in tag + + if 'Start' in tag: + if not entry: + start = None + continue + + start = int(entry, 16) + continue + + if 'End' in tag and start: + size = int(entry, 16) - start + + if is_code: + code_total += size + else: + data_total += size + + name = items[1] + return name, code_total, data_total + + +def parse_slices(path): + with open(path, 'r') as file: + lines = file.readlines() + + tags = lines[0].split(',') + for line in lines[1:]: + yield process_line(tags, line.split(',')) + +def simple_count(path): + code_total = 0 + data_total = 0 + + for o_name, o_code_total, o_data_total in parse_slices(path): + code_total += o_code_total + data_total += o_data_total + + return code_total, data_total + +def segments_of(path): + with open(path, 'r') as file: + for line in file.readlines()[1:]: + seg, segclass, start, end = line.split(',') + + is_code = 'text' in seg + + yield is_code, int(end, 16) - int(start, 16) + +def binary_total(path): + segments = list(segments_of(path)) + + num_code = sum(size if is_code else 0 for is_code, size in segments) + num_data = sum(size if not is_code else 0 for is_code, size in segments) + + return num_code, num_data + + +dol_progress = simple_count("system/slices.csv") +rel_progress = simple_count("system/rel_slices.csv") + +dol_total = binary_total("artifacts/pal/segments.csv") +rel_total = binary_total("artifacts/pal/rel_segments.csv") + +def to_percent(frac): + return round(frac * 100_000) / 1000 + +def analyze(prefix, progress, total): + # print("%s %s bytes (%s%%) of code, %s bytes (%s%%) of data decompiled" % ( + # prefix, + # progress[0], to_percent(progress[0] / total[0]) if total[0] else '', + # progress[1], to_percent(progress[1] / total[1]) if total[1] else '' + # )) + print("%s %s%% code, %s%% data decompiled" % ( + prefix, + to_percent(progress[0] / total[0]) if total[0] else '', + to_percent(progress[1] / total[1]) if total[1] else '' + )) + +analyze('[DOL]', dol_progress, dol_total) + +egg_progress = [0, 0] +for o_name, o_code_total, o_data_total in parse_slices("system/slices.csv"): + if 'egg' not in o_name: + continue + + egg_progress[0] += o_code_total + egg_progress[1] += o_data_total + +egg_total = [0x80244DD4-0x8020F62C, None] + +analyze(' -> [EGG]', egg_progress, egg_total) + +analyze('[REL]', rel_progress, rel_total) + +def piecewise_add(x, y): + return list(a + b for a, b in zip(x, y)) + +analyze('--- main.dol + StaticR.rel ---\n', + piecewise_add(dol_progress, rel_progress), + piecewise_add(dol_total, rel_total)) \ No newline at end of file From 88c074db0336c65e642f2ab0a4d300a5fc285ecf Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 19 May 2021 17:46:47 -0600 Subject: [PATCH 063/477] :recycle: Tidied up python scripts --- build.py | 82 +++++++++++++++++++++++++++++++------------- sources.py | 24 +++++++++++++ system/gen_asm.py | 33 +++++++++--------- system/rel_repack.py | 4 +-- 4 files changed, 101 insertions(+), 42 deletions(-) create mode 100644 sources.py diff --git a/build.py b/build.py index b54c2ff8a..2927c5e56 100644 --- a/build.py +++ b/build.py @@ -118,7 +118,7 @@ def command(cmd): os.system(cmd) def assemble(dst, src): - print(dst, src) + # print(dst, src) cmd = GAS + " %s -mgekko -Iasm -o %s" % (src, dst) command(cmd) @@ -331,29 +331,9 @@ def build_elf(rel_path, elf_path): def compile_sources(): require_folder("out") - RVL_OPTS = '-ipa file' - EGG_OPTS = '-ipa function -rostr' - REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' - - compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) - compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) - - compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) - - compile_source("source/egg/core/eggArchive.cpp", "out/eggArchive.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") - compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) - compile_source("source/egg/core/eggGraphicsFifo.cpp", "out/eggGraphicsFifo.o", '4201_127', EGG_OPTS) - compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") - compile_source("source/egg/math/eggQuat.cpp", "out/eggQuat.o", '4201_127', EGG_OPTS) - compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) - # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) - compile_source("source/egg/core/eggThread.cpp", "out/eggThread.o", '4201_127', EGG_OPTS) - compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) - # compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) - # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) - - compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', EGG_OPTS) - compile_source("source/game/jmap/JmpResourceCourse.cpp", "out/JmpResourceCourse.o", '4201_127', REL_OPTS) + # import sources + with open('sources.py', 'r') as sourcespy: + exec(sourcespy.read()) from pathlib import Path asm_files = [str(x.relative_to(os.getcwd())) for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] @@ -401,6 +381,8 @@ def build(): verify_rel() + import percent_decompiled + import hashlib def verify_rel(): @@ -420,6 +402,58 @@ def verify_dol(): return print("[DOL] Oof: Output doesn't match.") + + class DolBinary: + def __init__(self, file): + file = open(file, 'rb') + text_ofs = [read_u32(file) for _ in range(7)] + data_ofs = [read_u32(file) for _ in range(11)] + + text_vaddr = [read_u32(file) for _ in range(7)] + data_vaddr = [read_u32(file) for _ in range(11)] + + self.text_size = [read_u32(file) for _ in range(7)] + self.data_size = [read_u32(file) for _ in range(11)] + + self.text_segs = [Segment(x, x + y) for x, y in zip(text_vaddr, self.text_size)] + self.data_segs = [Segment(x, x + y) for x, y in zip(data_vaddr, self.data_size)] + + bss_vaddr = read_u32(file) + bss_size = read_u32(file) + + self.bss = Segment(bss_vaddr, bss_vaddr + bss_size) + + self.entry_point = read_u32(file) + + max_vaddr = max(x.end for x in self.text_segs + self.data_segs) + self.image_base = 0x80000000 + self.image = bytearray(max_vaddr - self.image_base) + return + for i in range(7): + if not self.text_size[i]: + continue + + file.seek(text_ofs[i]) + data = file.read(self.text_size[i]) + for j in range(self.text_size[i]): + self.image[text_vaddr[i] + j - self.image_base] = data[j] + + for i in range(11): + if not self.data_size[i]: + continue + + file.seek(data_ofs[i]) + data = file.read(self.data_size[i]) + for j in range(self.data_size[i]): + self.image[data_vaddr[i] + j - self.image_base] = data[j] + + good = DolBinary("source/baserom.dol") + bad = DolBinary("target/mkw_pal.dol") + + for i, sizes in enumerate(zip(good.text_size, bad.text_size)): + print(sizes) + for i, sizes in enumerate(zip(good.data_size, bad.data_size)): + print(sizes) # TODO: Add diff'ing build() diff --git a/sources.py b/sources.py new file mode 100644 index 000000000..20b50f3fe --- /dev/null +++ b/sources.py @@ -0,0 +1,24 @@ +RVL_OPTS = '-ipa file' +EGG_OPTS = '-ipa function -rostr' +REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' + +compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) + +compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) + +compile_source("source/egg/core/eggAllocator.cpp", "out/eggAllocator.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") +compile_source("source/egg/core/eggArchive.cpp", "out/eggArchive.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") +compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) +compile_source("source/egg/core/eggGraphicsFifo.cpp", "out/eggGraphicsFifo.o", '4201_127', EGG_OPTS) +compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") +compile_source("source/egg/math/eggQuat.cpp", "out/eggQuat.o", '4201_127', EGG_OPTS) +compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) +# compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) +compile_source("source/egg/core/eggThread.cpp", "out/eggThread.o", '4201_127', EGG_OPTS) +compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) +# compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) +# compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) + +compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', EGG_OPTS) +compile_source("source/game/jmap/JmpResourceCourse.cpp", "out/JmpResourceCourse.o", '4201_127', REL_OPTS) \ No newline at end of file diff --git a/system/gen_asm.py b/system/gen_asm.py index 5a5ffb1bd..6b5fb29df 100644 --- a/system/gen_asm.py +++ b/system/gen_asm.py @@ -386,19 +386,20 @@ def unpack_everything(): rel_o_files = unpack_staticr_rel() open('../rel_o_files.txt', 'w').write('\n'.join(rel_o_files)) -try: shutil.rmtree("../asm") -except: pass - -try: os.mkdir("../asm") -except: pass - -with open('../asm/macros.inc', 'w') as file: - file.write('# PowerPC Register Constants\n') - for i in range(0, 32): - file.write(".set r%i, %i\n" % (i, i)) - for i in range(0, 32): - file.write(".set f%i, %i\n" % (i, i)) - for i in range(0, 8): - file.write(".set qr%i, %i\n" % (i, i)) - -unpack_everything() \ No newline at end of file +if __name__ == '__main__': + try: shutil.rmtree("../asm") + except: pass + + try: os.mkdir("../asm") + except: pass + + with open('../asm/macros.inc', 'w') as file: + file.write('# PowerPC Register Constants\n') + for i in range(0, 32): + file.write(".set r%i, %i\n" % (i, i)) + for i in range(0, 32): + file.write(".set f%i, %i\n" % (i, i)) + for i in range(0, 8): + file.write(".set qr%i, %i\n" % (i, i)) + + unpack_everything() \ No newline at end of file diff --git a/system/rel_repack.py b/system/rel_repack.py index a424c8782..92e97a91d 100644 --- a/system/rel_repack.py +++ b/system/rel_repack.py @@ -276,7 +276,7 @@ def __init__(self, f=None, relOffset=None): #R_RVL_SECT if entry.type == 202: - print("R_RVL_SECT", counter, entry.section) + # print("R_RVL_SECT", counter, entry.section) section = entry.section self.entries[section] = [] continue @@ -285,7 +285,7 @@ def __init__(self, f=None, relOffset=None): # R_RVL_STOP if entry.type == 203: - print("R_RVL_STOP") + # print("R_RVL_STOP") break counter += 1 From 56eb6ec5005d2fe6876922af37e8c6351b48340a Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 23 May 2021 21:58:29 -0600 Subject: [PATCH 064/477] :sparkles: Decompiled eggUnitHeap.cpp --- link.lcf | 10 +++ o_files.txt | 4 +- source/egg/core/eggAllocator.hpp | 11 +--- source/egg/core/eggExpHeap.cpp | 53 +++++++++------ source/egg/core/eggExpHeap.hpp | 4 +- source/egg/core/eggHeap.hpp | 16 +---- source/egg/core/eggUnitHeap.cpp | 107 +++++++++++++++++++++++++++++++ source/egg/core/eggUnitHeap.hpp | 32 +++++++++ source/nw4r/ut/utInlines.hpp | 15 +++++ source/rvl/mem/memAllocator.h | 27 ++++++++ source/rvl/mem/unitHeap.h | 35 ++++++++++ sources.py | 1 + system/slices.csv | 3 +- 13 files changed, 271 insertions(+), 47 deletions(-) create mode 100644 source/egg/core/eggUnitHeap.cpp create mode 100644 source/egg/core/eggUnitHeap.hpp create mode 100644 source/nw4r/ut/utInlines.hpp create mode 100644 source/rvl/mem/memAllocator.h create mode 100644 source/rvl/mem/unitHeap.h diff --git a/link.lcf b/link.lcf index 0d3f3cbec..a3ee5684f 100644 --- a/link.lcf +++ b/link.lcf @@ -77,4 +77,14 @@ memset=0x80006038; GXGetGPStatus=0x8016CEC4; GXInit=0x8016B850; + +MEMCreateUnitHeapEx= 0x801998A4; +MEMDestroyUnitHeap= 0x80199A00; +MEMAllocFromUnitHeap= 0x80199A30; +MEMFreeToUnitHeap= 0x80199AC4; +MEMCalcHeapSizeForUnitHeap= 0x80199B34; +AllocatorAllocForUnitHeap_= 0x80199B70; +AllocatorFreeForUnitHeap_= 0x80199B90; +MEMInitAllocatorForUnitHeap= 0x80199BD4; + } diff --git a/o_files.txt b/o_files.txt index 7be451798..ba177f71d 100644 --- a/o_files.txt +++ b/o_files.txt @@ -36,7 +36,7 @@ dol/text_80242504.o dol/data_802a3f90.o dol/bss_80384348.o eggThread.o -dol/text_80243754.o +eggUnitHeap.o dol/ctors_80244de0.o dol/bss_80384b6c.o dol/sbss_80386ec0.o @@ -49,7 +49,7 @@ dol/text_80243d18.o dol/ctors_80244e8c.o dol/dtors_80244ea4.o dol/rodata_80257824.o -dol/data_802a3fd8.o +dol/data_802a4004.o dol/bss_80384bf4.o dol/sdata_803857f6.o dol/sbss_80386f90.o diff --git a/source/egg/core/eggAllocator.hpp b/source/egg/core/eggAllocator.hpp index 0b018d745..da31447a5 100644 --- a/source/egg/core/eggAllocator.hpp +++ b/source/egg/core/eggAllocator.hpp @@ -6,14 +6,7 @@ #pragma once #include - -extern "C" { - -struct MEMAllocator { - char _[0x10]; -}; - -} // extern "C" +#include namespace EGG { @@ -44,6 +37,8 @@ class Allocator : public MEMAllocator { //! virtual void free(void* block); + inline MEMAllocator* getHandle() { return static_cast(this); } + private: Heap* mHeap; //!< [+0x14] Heap to use for allocation. s32 mAlign; //!< [+0x18] Alignment of blocks to allocate. diff --git a/source/egg/core/eggExpHeap.cpp b/source/egg/core/eggExpHeap.cpp index 11e55cd3c..0c91d4741 100644 --- a/source/egg/core/eggExpHeap.cpp +++ b/source/egg/core/eggExpHeap.cpp @@ -14,14 +14,14 @@ ExpHeap::~ExpHeap() { Heap::dispose(); // Destroy the wrapped MEM heap - MEMDestroyExpHeap(this->mHeapHandle); + MEMDestroyExpHeap(mHeapHandle); } ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { u32 heapEnd = nw4r::ut::RoundDown((u32)block + size, 4); // r3 u32 heapStart = nw4r::ut::RoundUp((u32)block, 4); // r27 - ExpHeap* createdHeap = NULL; // r30 + ExpHeap* createdHeap = nullptr; // r30 // This simplifies down to size > 0; // as size is unsigned, we know this check will always pass. @@ -36,7 +36,7 @@ ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { if (memHeap) { Heap* containHeap = Heap::findContainHeap((void*)heapStart); // r31 - new ((void*)heapStart) ExpHeap(memHeap); // Inline + new ((void*)heapStart) ExpHeap(memHeap); // Inline ((ExpHeap*)heapStart)->mParentBlock = block; ((ExpHeap*)heapStart)->mParentHeap = containHeap; createdHeap = (ExpHeap*)heapStart; @@ -47,6 +47,7 @@ ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { return 0; } + ExpHeap* ExpHeap::create(u32 size, Heap* heap, u16 attr) { ExpHeap* newHeap = NULL; @@ -70,80 +71,90 @@ ExpHeap* ExpHeap::create(u32 size, Heap* heap, u16 attr) { void ExpHeap::destroy() { Heap* parentHeap = findParentHeap(); - this->~ExpHeap(); // arg=-1 + ~ExpHeap(); // arg=-1 if (parentHeap) parentHeap->free(this); } void* ExpHeap::alloc(u32 size, s32 align) { - if ((this->mFlag & HEAP_FLAG_LOCKED) != 0) + if ((mFlag & HEAP_FLAG_LOCKED) != 0) OSPanic("eggExpHeap.cpp", 174, "DAME DAME\n"); - return MEMAllocFromExpHeapEx(this->mHeapHandle, size, align); + return MEMAllocFromExpHeapEx(mHeapHandle, size, align); } -void ExpHeap::free(void* block) { MEMFreeToExpHeap(this->mHeapHandle, block); } +void ExpHeap::free(void* block) { MEMFreeToExpHeap(mHeapHandle, block); } u32 ExpHeap::resizeForMBlock(void* memBlock, u32 size) { - return MEMResizeForMBlockExpHeap(this->mHeapHandle, memBlock, size); + return MEMResizeForMBlockExpHeap(mHeapHandle, memBlock, size); } + u32 ExpHeap::getTotalFreeSize() { - return MEMGetTotalFreeSizeForExpHeap(this->mHeapHandle); + return MEMGetTotalFreeSizeForExpHeap(mHeapHandle); } + u32 ExpHeap::getAllocatableSize(s32 align) { - return MEMGetAllocatableSizeForExpHeapEx(this->mHeapHandle, align); + return MEMGetAllocatableSizeForExpHeapEx(mHeapHandle, align); } + u16 ExpHeap::setGroupID(u16 groupID) { - return MEMSetGroupIDForExpHeap(this->mHeapHandle, groupID); + return MEMSetGroupIDForExpHeap(mHeapHandle, groupID); } + void ExpHeap::addGroupSize(void* block, MEMHeapHandle heap, u32 userParam) { u32 grpID = MEMGetGroupIDForMBlockExpHeap(block); // below may return ((GroupSizeRecord*)userParam) ->addSize((u16)grpID, MEMGetSizeForMBlockExpHeap(block)); } + void ExpHeap::calcGroupSize(GroupSizeRecord* record) { record->reset(); - MEMVisitAllocatedForExpHeap(this->mHeapHandle, addGroupSize, (u32)record); + MEMVisitAllocatedForExpHeap(mHeapHandle, addGroupSize, (u32)record); } + u32 ExpHeap::adjust() { - u32 adjustedSize = MEMAdjustExpHeap(this->mHeapHandle) + 56; + u32 adjustedSize = MEMAdjustExpHeap(mHeapHandle) + 56; if (adjustedSize > 56 && mParentHeap) { mParentHeap->resizeForMBlock(mParentBlock, adjustedSize); return adjustedSize; - } else { - return 0; } + + return nullptr; } + void ExpHeap::initAllocator(Allocator* allocator, s32 align) { - MEMInitAllocatorForExpHeap((MEMAllocator*)allocator, this->mHeapHandle, - align); + MEMInitAllocatorForExpHeap((MEMAllocator*)allocator, mHeapHandle, align); } -ExpHeap::GroupSizeRecord::GroupSizeRecord() { this->reset(); } -void ExpHeap::GroupSizeRecord::reset() { - // CW optimizes this down to 8 sets of 32 byte writes. +ExpHeap::GroupSizeRecord::GroupSizeRecord() { reset(); } + +void ExpHeap::GroupSizeRecord::reset() { u32* iterEntry = &entries[0]; for (int i = 0; i < 256; i++) { *iterEntry = 0; iterEntry++; } } + void ExpHeap::GroupSizeRecord::addSize(u16 groupID, u32 size) { entries[groupID] += size; } + namespace { void free_all_visitor(void* block, MEMHeapHandle heap, u32 userParam) { MEMFreeToExpHeap(heap, block); } } // namespace + void ExpHeap::freeAll() { Heap::dispose(); -// MEMVisitAllocatedForExpHeap(this->mHeapHandle, &free_all_visitor, 0); + // MEMVisitAllocatedForExpHeap(mHeapHandle, &free_all_visitor, 0); } + Heap::eHeapKind ExpHeap::getHeapKind() const { return Heap::HEAP_KIND_EXPANDED; } diff --git a/source/egg/core/eggExpHeap.hpp b/source/egg/core/eggExpHeap.hpp index fe313a602..1b72fedb6 100644 --- a/source/egg/core/eggExpHeap.hpp +++ b/source/egg/core/eggExpHeap.hpp @@ -5,8 +5,8 @@ #pragma once -#include -#include +#include +#include namespace EGG { diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index fd3522db6..ef72be71e 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -6,7 +6,7 @@ #pragma once #ifndef HEAP_PRIVATE -#define HEAP_PRIVATE private +#define HEAP_PRIVATE protected #endif #include @@ -16,17 +16,7 @@ #include #endif #include - -extern "C" { - -typedef struct rvlHeap { - char _[0x1c]; - u32 arena_end; -} rvlHeap; - -rvlHeap* MEMFindContainHeap(const void*); - -} +#include namespace EGG { @@ -119,7 +109,7 @@ class Heap : public Disposer { static class Thread* sAllocatableThread; //!< TODO //! @brief [+0x10] argument of heap constructor. Name confirmed by WS assert. - rvlHeap* mHeapHandle; + MEMHeapHandle mHeapHandle; //! @brief [+0x14] set to 0 in heap ctor. treeki -- void* parentHeapMBlock void* mParentBlock; //! @brief [+0x18] name from findParentHeap() diff --git a/source/egg/core/eggUnitHeap.cpp b/source/egg/core/eggUnitHeap.cpp new file mode 100644 index 000000000..d244b4c62 --- /dev/null +++ b/source/egg/core/eggUnitHeap.cpp @@ -0,0 +1,107 @@ +/*! + * @file + * @brief EGG wraper for unit heaps (each element uniform size). + */ + +#include + +#define HEAP_PRIVATE public + +#include +#include + +inline void* operator new(unsigned long, void* p) { return p; } + +namespace EGG { + +enum { + UNIT_HEAP_BASE_SIZE = (sizeof(UnitHeap) - sizeof(MEMiUntHeapHead)), + UNIT_HEAP_ALIGN = 4 +}; + +UnitHeap::~UnitHeap() { + dispose(); + MEMDestroyUnitHeap(mHeapHandle); +} +UnitHeap* UnitHeap::create(void* block, u32 size, u32 unit_size, s32 align, + u16 flag) { + u32 end_addr = nw4r::ut::RoundDown((u32)block + size, UNIT_HEAP_ALIGN); + u32 start_addr = nw4r::ut::RoundUp((u32)block, UNIT_HEAP_ALIGN); + + UnitHeap* ret = nullptr; + + // Note: really need the full size + if (start_addr > end_addr || + end_addr - start_addr < UNIT_HEAP_BASE_SIZE + UNIT_HEAP_ALIGN) { + ret = nullptr; + } else { + MEMHeapHandle hnd = MEMCreateUnitHeapEx( + reinterpret_cast(start_addr + UNIT_HEAP_BASE_SIZE), + end_addr - start_addr - UNIT_HEAP_BASE_SIZE, unit_size, align, flag); + + if (hnd) { + Heap* contain = + Heap::findContainHeap(reinterpret_cast(start_addr)); + + UnitHeap* new_heap = + new (reinterpret_cast(start_addr)) UnitHeap(hnd); + ret = new_heap; + new_heap->mParentHeap = contain; + } + } + + return ret; +} +UnitHeap* UnitHeap::create(u32 size, u32 unit_size, EGG::Heap* heap, s32 align, + u16 flag) { + UnitHeap* ret = nullptr; + void* block; + + if (!heap) + heap = Heap::sCurrentHeap; + if (size == -1) + size = heap->getAllocatableSize(4); + + block = heap->alloc(size, UNIT_HEAP_ALIGN); + if (block) { + if ((ret = create(block, size, unit_size, align, flag)) != nullptr) // INLINE + ret->mParentHeap = heap; + else + heap->free(block); + } + + return ret; +} +void UnitHeap::destroy() { + Heap* parentHeap = findParentHeap(); + this->~UnitHeap(); + if (parentHeap) + parentHeap->free(this); +} + +void* UnitHeap::alloc(u32 size, s32 /* align */) { + if (size > mHeapHandle->_40) + return nullptr; + + return MEMAllocFromUnitHeap(mHeapHandle); +} +void UnitHeap::free(void* block) { MEMFreeToUnitHeap(mHeapHandle, block); } + +u32 UnitHeap::resizeForMBlock(void*, u32) { return 0; } + +u32 UnitHeap::getAllocatableSize(s32 /* align */) { return 0; } + +u32 UnitHeap::adjust() { return 0; } + +void UnitHeap::initAllocator(Allocator* allocator, s32 /* align */) { + return MEMInitAllocatorForUnitHeap(allocator->getHandle(), mHeapHandle); +} + +u32 UnitHeap::calcHeapSize(u32 unit_size, u32 unit_count, s32 align) { + return MEMCalcHeapSizeForUnitHeap(unit_size, unit_count, align) + + UNIT_HEAP_BASE_SIZE; +} + +Heap::eHeapKind UnitHeap::getHeapKind() const { return HEAP_KIND_UNIT; } + +} // namespace EGG \ No newline at end of file diff --git a/source/egg/core/eggUnitHeap.hpp b/source/egg/core/eggUnitHeap.hpp new file mode 100644 index 000000000..3f0ea720e --- /dev/null +++ b/source/egg/core/eggUnitHeap.hpp @@ -0,0 +1,32 @@ +/*! + * @file + * @brief EGG wraper for unit heaps (each element uniform size). + */ + +#pragma once + +#include +#include + +namespace EGG { + +class UnitHeap : public Heap, private MEMiUntHeapHead { + inline UnitHeap(MEMHeapHandle heapHandle) : Heap(heapHandle) {} + ~UnitHeap() override; + // Always inline in mkw + static inline UnitHeap* create(void* block, u32 size, u32 unit_size, s32 align, + u16 flag); + static UnitHeap* create(u32 size, u32 unit_size, EGG::Heap* heap, s32 align, + u16 flag); + void destroy() override; + void* alloc(u32 size, s32 align) override; + void free(void* block) override; + u32 resizeForMBlock(void*, u32) override; + u32 getAllocatableSize(s32 align) override; + u32 adjust() override; + void initAllocator(Allocator* allocator, s32) override; + static u32 calcHeapSize(u32 unit_size, u32 unit_count, s32 align); + eHeapKind getHeapKind() const override; +}; + +} // namespace EGG diff --git a/source/nw4r/ut/utInlines.hpp b/source/nw4r/ut/utInlines.hpp new file mode 100644 index 000000000..e957c71d3 --- /dev/null +++ b/source/nw4r/ut/utInlines.hpp @@ -0,0 +1,15 @@ +#pragma once + +namespace nw4r { +namespace ut { + +template static inline T RoundUp(T num, unsigned int align) { + return (num + align - 1) & -align; +} + +template static inline T RoundDown(T num, unsigned int align) { + return num & -align; +} + +} // namespace ut +} // namespace nw4r \ No newline at end of file diff --git a/source/rvl/mem/memAllocator.h b/source/rvl/mem/memAllocator.h new file mode 100644 index 000000000..633d6c0c3 --- /dev/null +++ b/source/rvl/mem/memAllocator.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct MEMAllocator { + char _[0x10]; +}; +typedef struct MEMiHeapHead { + char _[0x1c]; + u32 arena_end; // 0x20 + char _2[0x40 - 0x20]; + u32 _40; +} MEMiHeapHead; +#define rvlHeap MEMiHeapHead + +typedef MEMiHeapHead* MEMHeapHandle; + +MEMiHeapHead* MEMFindContainHeap(const void*); + + +#ifdef __cplusplus +} // extern "C" +#endif \ No newline at end of file diff --git a/source/rvl/mem/unitHeap.h b/source/rvl/mem/unitHeap.h new file mode 100644 index 000000000..392662d68 --- /dev/null +++ b/source/rvl/mem/unitHeap.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +struct MEMiUntHeapMBlockHead { + MEMiUntHeapMBlockHead* succ; +}; + +struct MEMiUntHeapHead { + MEMiUntHeapMBlockHead* free_list; + u32 unit_size; +}; + +MEMHeapHandle MEMCreateUnitHeapEx(void* begin, u32 size, u32 unit_size, + int align, u16 flags); + +void* MEMDestroyUnitHeap(MEMHeapHandle heap); + +void* MEMAllocFromUnitHeap(MEMHeapHandle heap); + +void MEMFreeToUnitHeap(MEMHeapHandle heap, void* block); + +u32 MEMCountFreeBlockForUnitHeap(MEMHeapHandle heap); + +u32 MEMCalcHeapSizeForUnitHeap(u32 unit_size, u32 unit_count, int align); + +void MEMInitAllocatorForUnitHeap(MEMAllocator* allocator, MEMHeapHandle heap); + +#ifdef __cplusplus +} +#endif // __cplusplus \ No newline at end of file diff --git a/sources.py b/sources.py index 20b50f3fe..dd450e10b 100644 --- a/sources.py +++ b/sources.py @@ -16,6 +16,7 @@ compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggThread.cpp", "out/eggThread.o", '4201_127', EGG_OPTS) +compile_source("source/egg/core/eggUnitHeap.cpp", "out/eggUnitHeap.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) diff --git a/system/slices.csv b/system/slices.csv index 925b174b0..1cfb1be62 100644 --- a/system/slices.csv +++ b/system/slices.csv @@ -9,10 +9,11 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,eggGraphicsFifo.cpp,,,,,,,80229540,802296A8,,,,,,,802A30B0,802A30BC,,,,,0x80386E90,0x80386E99,,,, 1,eggHeap.cpp,,,,,,,802296A8,80229FAC,,,,,80257740,80257824,802A30C0,0x802a30ec,80384320,0x80384348,,,80386EA0,80386EC0,80388D68 ,80388D80,, 1,eggQuat.cpp,,,,,,,80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, -0,eggScene.cpp,,,,,,,8023AD10,8023ADDC,,,,,,,,,,,,,,,,,, +,eggScene.cpp,,,,,,,8023AD10,8023ADDC,,,,,,,,,,,,,,,,,, 1,eggStreamDecomp.cpp,,,,,,,80242498,80242504,,,,,,,802A3F78,802A3F90,,,,,,,,,, ,eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,80386F60,80386F64,,,, 1,eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, +1,eggUnitHeap.cpp,,,,,,,80243754,80243A00,,,,,,,802A3FD8,802A4004,,,,,,,,,, 1,eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,80384B70,80384BF4,,,80386F78,80386F90,803890F8 ,80389104,, ,eggXfb.cpp,,,,,,,80244160,80244200,,,,,,,,,,,,,,,,,, ,eggXfbManager.cpp,,,,,,,80244200,802443AC,,,,,,,,,,,,,,,,,, From 3498c387f8742e87529cb1d827d2ffcfbba8eed5 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 30 May 2021 04:44:47 -0600 Subject: [PATCH 065/477] :sparkles: Represent decomp progress as BR/VR (5000 to 9999) in percent_decompiled.py --- percent_decompiled.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/percent_decompiled.py b/percent_decompiled.py index 1dda5fe80..3f44af682 100644 --- a/percent_decompiled.py +++ b/percent_decompiled.py @@ -111,4 +111,12 @@ def piecewise_add(x, y): analyze('--- main.dol + StaticR.rel ---\n', piecewise_add(dol_progress, rel_progress), - piecewise_add(dol_total, rel_total)) \ No newline at end of file + piecewise_add(dol_total, rel_total)) + +print('------') +print('Player:') +print(' - %u BR (main.dol)' % (dol_progress[0] / dol_total[0] * 4999 + 5000)) +print(' - %u VR (StaticR.rel)' % (rel_progress[0] / rel_total[0] * 4999 + 5000)) + +print(dol_total[0] / 4999 / 4) +print(rel_total[0] / 4999 / 4) \ No newline at end of file From 4d7d3f38c002c9811741c2b77be36ca6ccca756a Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 30 May 2021 04:46:38 -0600 Subject: [PATCH 066/477] :sparkles: eggExpHeap.cpp --- link.lcf | 17 ++++ o_files.txt | 6 +- source/egg/core/eggExpHeap.cpp | 139 ++++++++++++++++++++++++--------- source/egg/core/eggExpHeap.hpp | 4 +- source/egg/core/eggHeap.hpp | 33 ++++---- source/rvl/mem/expHeap.h | 40 ++++++++++ source/rvl/mem/memAllocator.h | 7 +- source/rvl/os/osThread.h | 3 +- sources.py | 1 + system/slices.csv | 1 + 10 files changed, 195 insertions(+), 56 deletions(-) create mode 100644 source/rvl/mem/expHeap.h diff --git a/link.lcf b/link.lcf index a3ee5684f..0c5747c42 100644 --- a/link.lcf +++ b/link.lcf @@ -87,4 +87,21 @@ AllocatorAllocForUnitHeap_= 0x80199B70; AllocatorFreeForUnitHeap_= 0x80199B90; MEMInitAllocatorForUnitHeap= 0x80199BD4; +MEMCreateExpHeapEx = 0x80198CA8; +MEMDestroyExpHeap = 0x80198D58; +MEMAllocFromExpHeapEx = 0x80198D88; +MEMResizeForMBlockExpHeap = 0x80198E38; +MEMFreeToExpHeap = 0x80199038; +MEMGetTotalFreeSizeForExpHeap = 0x80199104; +MEMGetAllocatableSizeForExpHeapEx = 0x80199180; +MEMSetGroupIDForExpHeap = 0x80199258; +MEMVisitAllocatedForExpHeap = 0x801992A8; +MEMGetSizeForMBlockExpHeap = 0x80199344; +MEMGetGroupIDForMBlockExpHeap = 0x8019934C; +MEMAdjustExpHeap = 0x80199358; +AllocatorAllocForExpHeap_ = 0x80199B58; +AllocatorFreeForExpHeap_ = 0x80199B68; +MEMInitAllocatorForExpHeap = 0x80199BB8; + + } diff --git a/o_files.txt b/o_files.txt index ba177f71d..a2f1e4ec2 100644 --- a/o_files.txt +++ b/o_files.txt @@ -18,10 +18,14 @@ dol/text_8020fcc4.o dol/data_802a268c.o eggDisposer.o dol/text_8021a1b8.o +dol/rodata_80244ec0.o dol/data_802a2b54.o +eggExpHeap.o +dol/text_80226f04.o +dol/data_802a3024.o dol/sbss_80386d84.o eggGraphicsFifo.o -dol/rodata_80244ec0.o +dol/rodata_8025771a.o dol/data_802a30bc.o dol/bss_803832e4.o dol/sbss_80386e99.o diff --git a/source/egg/core/eggExpHeap.cpp b/source/egg/core/eggExpHeap.cpp index 0c91d4741..a99425dde 100644 --- a/source/egg/core/eggExpHeap.cpp +++ b/source/egg/core/eggExpHeap.cpp @@ -4,10 +4,17 @@ */ #include -#include -#include +#include +#include + +inline void* operator new(unsigned long, void* p) { return p; } namespace EGG { + +enum { + EXP_HEAP_BASE_SIZE = 56, // (sizeof(ExpHeap) - sizeof(...)), + EXP_HEAP_ALIGN = 4 +}; ExpHeap::~ExpHeap() { // Recursively free all memory @@ -17,46 +24,106 @@ ExpHeap::~ExpHeap() { MEMDestroyExpHeap(mHeapHandle); } +#ifdef NONMATCHING ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { - u32 heapEnd = nw4r::ut::RoundDown((u32)block + size, 4); // r3 - u32 heapStart = nw4r::ut::RoundUp((u32)block, 4); // r27 - - ExpHeap* createdHeap = nullptr; // r30 - - // This simplifies down to size > 0; - // as size is unsigned, we know this check will always pass. - if (heapStart <= heapEnd) { - u32 arenaSize = heapEnd - heapStart; // r4 - - // Enforce requirement for 56 byte ExpHeap object + minimum 4 bytes in heap. - if (arenaSize >= 60) { - // We reserve 56 bytes in the heap. - MEMHeapHandle memHeap = MEMCreateExpHeapEx((void*)(heapStart + 56), - arenaSize - 56, attr); // r28 - - if (memHeap) { - Heap* containHeap = Heap::findContainHeap((void*)heapStart); // r31 - new ((void*)heapStart) ExpHeap(memHeap); // Inline - ((ExpHeap*)heapStart)->mParentBlock = block; - ((ExpHeap*)heapStart)->mParentHeap = containHeap; - createdHeap = (ExpHeap*)heapStart; - } - return createdHeap; + ExpHeap* createdHeap = nullptr; // r30 + + u32 heapEnd = nw4r::ut::RoundDown((u32)block + size, EXP_HEAP_ALIGN); // r3 + u32 heapStart = nw4r::ut::RoundUp((u32)block, EXP_HEAP_ALIGN); // r27 + + // Enforce requirement for 56 byte ExpHeap object + minimum 4 bytes in heap. + if (heapStart > heapEnd || + heapEnd - heapStart < EXP_HEAP_BASE_SIZE + EXP_HEAP_ALIGN) { + createdHeap = nullptr; + } else { + // We reserve 56 bytes in the heap. + MEMHeapHandle hnd = MEMCreateExpHeapEx( + reinterpret_cast(heapStart + EXP_HEAP_BASE_SIZE), + heapEnd - heapStart - EXP_HEAP_BASE_SIZE, attr); // r28 + + if (hnd) { + Heap* contain = + Heap::findContainHeap(reinterpret_cast(heapStart)); // r31 + + createdHeap = new (reinterpret_cast(heapStart)) ExpHeap(hnd); + + createdHeap->mParentBlock = block; + createdHeap->mParentHeap = contain; } } - return 0; -} + return createdHeap; +} +#else +extern "C" void __ct__Q23EGG4HeapFP12MEMiHeapHead(); +extern "C" void findContainHeap__Q23EGG4HeapFPCv(); + +// clang-format off[ +asm ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { + nofralloc + +/* 80226A1C 94 21 FF E0 */ stwu r1, -0x20(r1) +/* 80226A20 7C 08 02 A6 */ mflr r0 +/* 80226A24 7C 83 22 14 */ add r4, r3, r4 +/* 80226A28 90 01 00 24 */ stw r0, 0x24(r1) +/* 80226A2C 38 03 00 03 */ addi r0, r3, 3 +/* 80226A30 BF 61 00 0C */ stmw r27, 0xc(r1) +/* 80226A34 7C 7D 1B 78 */ mr r29, r3 +/* 80226A38 54 83 00 3A */ rlwinm r3, r4, 0, 0, 0x1d +/* 80226A3C 54 1B 00 3A */ rlwinm r27, r0, 0, 0, 0x1d +/* 80226A40 7C 1B 18 40 */ cmplw r27, r3 +/* 80226A44 3B C0 00 00 */ li r30, 0 +/* 80226A48 41 81 00 10 */ bgt loc_80226A58 +/* 80226A4C 7C 9B 18 50 */ subf r4, r27, r3 +/* 80226A50 28 04 00 3C */ cmplwi r4, 0x3c +/* 80226A54 40 80 00 0C */ bge loc_80226A60 +loc_80226A58: +/* 80226A58 38 60 00 00 */ li r3, 0 +/* 80226A5C 48 00 00 58 */ b loc_80226AB4 +loc_80226A60: +/* 80226A60 38 7B 00 38 */ addi r3, r27, 0x38 +/* 80226A64 38 84 FF C8 */ addi r4, r4, -56 +/* 80226A68 4B F7 22 41 */ bl MEMCreateExpHeapEx +/* 80226A6C 2C 03 00 00 */ cmpwi r3, 0 +/* 80226A70 7C 7C 1B 78 */ mr r28, r3 +/* 80226A74 41 82 00 3C */ beq loc_80226AB0 +/* 80226A78 7F 63 DB 78 */ mr r3, r27 +/* 80226A7C 48 00 30 61 */ bl findContainHeap__Q23EGG4HeapFPCv +/* 80226A80 2C 1B 00 00 */ cmpwi r27, 0 +/* 80226A84 7C 7F 1B 78 */ mr r31, r3 +/* 80226A88 41 82 00 1C */ beq loc_80226AA4 +/* 80226A8C 7F 63 DB 78 */ mr r3, r27 +/* 80226A90 7F 84 E3 78 */ mr r4, r28 +/* 80226A94 48 00 2C 55 */ bl __ct__Q23EGG4HeapFP12MEMiHeapHead +/* 80226A98 3C 60 80 2A */ lis r3, 0x802a +/* 80226A9C 38 63 2F F8 */ addi r3, r3, 0x2ff8 +/* 80226AA0 90 7B 00 00 */ stw r3, 0(r27) +loc_80226AA4: +/* 80226AA4 93 BB 00 14 */ stw r29, 0x14(r27) +/* 80226AA8 7F 7E DB 78 */ mr r30, r27 +/* 80226AAC 93 FB 00 18 */ stw r31, 0x18(r27) +loc_80226AB0: +/* 80226AB0 7F C3 F3 78 */ mr r3, r30 +loc_80226AB4: +/* 80226AB4 BB 61 00 0C */ lmw r27, 0xc(r1) +/* 80226AB8 80 01 00 24 */ lwz r0, 0x24(r1) +/* 80226ABC 7C 08 03 A6 */ mtlr r0 +/* 80226AC0 38 21 00 20 */ addi r1, r1, 0x20 +/* 80226AC4 4E 80 00 20 */ blr +} +// clang-format on +#endif ExpHeap* ExpHeap::create(u32 size, Heap* heap, u16 attr) { - ExpHeap* newHeap = NULL; + ExpHeap* newHeap = nullptr; - if (heap == NULL) + if (heap == nullptr) heap = Heap::sCurrentHeap; if (size == -1) size = heap->getAllocatableSize(4); - if ((void* block = heap->alloc(size, 4))) { + void* block = heap->alloc(size, 4); + if (block) { newHeap = ExpHeap::create(block, size, attr); if (newHeap) @@ -71,12 +138,12 @@ ExpHeap* ExpHeap::create(u32 size, Heap* heap, u16 attr) { void ExpHeap::destroy() { Heap* parentHeap = findParentHeap(); - ~ExpHeap(); // arg=-1 + (*this).~ExpHeap(); if (parentHeap) parentHeap->free(this); } - +extern "C" void OSPanic(const char* file, u32 line, const char* msg, ...); void* ExpHeap::alloc(u32 size, s32 align) { if ((mFlag & HEAP_FLAG_LOCKED) != 0) OSPanic("eggExpHeap.cpp", 174, "DAME DAME\n"); @@ -103,10 +170,10 @@ u16 ExpHeap::setGroupID(u16 groupID) { } void ExpHeap::addGroupSize(void* block, MEMHeapHandle heap, u32 userParam) { - u32 grpID = MEMGetGroupIDForMBlockExpHeap(block); + u16 grpID = MEMGetGroupIDForMBlockExpHeap(block); // below may return ((GroupSizeRecord*)userParam) - ->addSize((u16)grpID, MEMGetSizeForMBlockExpHeap(block)); + ->addSize(grpID, MEMGetSizeForMBlockExpHeap(block)); } void ExpHeap::calcGroupSize(GroupSizeRecord* record) { @@ -152,7 +219,7 @@ void free_all_visitor(void* block, MEMHeapHandle heap, u32 userParam) { void ExpHeap::freeAll() { Heap::dispose(); - // MEMVisitAllocatedForExpHeap(mHeapHandle, &free_all_visitor, 0); + MEMVisitAllocatedForExpHeap(mHeapHandle, &free_all_visitor, 0); } Heap::eHeapKind ExpHeap::getHeapKind() const { diff --git a/source/egg/core/eggExpHeap.hpp b/source/egg/core/eggExpHeap.hpp index 1b72fedb6..def6b3b3e 100644 --- a/source/egg/core/eggExpHeap.hpp +++ b/source/egg/core/eggExpHeap.hpp @@ -32,7 +32,9 @@ class ExpHeap : public Heap { //! //! @returns The created ExpHeap //! - static ExpHeap* create(void* block, u32 size, u16 attr); + static ExpHeap* create(void* block, u32 size, u16 attr) + __attribute__((never_inline)); + //! @brief Create a new ExpHeap as a child of an existing heap. //! diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index ef72be71e..2ac6e269d 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -23,7 +23,6 @@ namespace EGG { class ExpHeap; class Allocator; - struct HeapAllocArg { int userArg; // 00 u32 size; // 04 @@ -41,7 +40,6 @@ struct HeapErrorArg { inline HeapErrorArg() {} }; - typedef void (*ErrorCallback)(void*); //! @brief Base Heap class @@ -102,12 +100,12 @@ class Heap : public Disposer { //! @brief When non NULL, this heap MUST be used for heap allocations. //! This will restrict rather than redirect allocations. static Heap* sAllocatableHeap; - static ErrorCallback sErrorCallback; //!< TODO - static HeapAllocCallback sAllocCallback; //!< TODO - static void* sErrorCallbackArg; //!< TODO - static void* sAllocCallbackArg; //!< TODO - static class Thread* sAllocatableThread; //!< TODO - + static ErrorCallback sErrorCallback; //!< TODO + static HeapAllocCallback sAllocCallback; //!< TODO + static void* sErrorCallbackArg; //!< TODO + static void* sAllocCallbackArg; //!< TODO + static class Thread* sAllocatableThread; //!< TODO +public: //! @brief [+0x10] argument of heap constructor. Name confirmed by WS assert. MEMHeapHandle mHeapHandle; //! @brief [+0x14] set to 0 in heap ctor. treeki -- void* parentHeapMBlock @@ -131,7 +129,7 @@ class Heap : public Disposer { //! children. nw4r::ut::List mChildren; //!< [+0x28] sizeof=0xC - const char* mName; //!< [+0x034] set to "NoName" in ctor + const char* mName; //!< [+0x034] set to "NoName" in ctor public: //! @brief Must be called before heaps are created. Prepares static heap @@ -248,6 +246,10 @@ class Heap : public Disposer { Heap* becomeCurrentHeap(); public: + static void* addOffset(void* begin, u32 size) { + return reinterpret_cast(begin) + size; + } + inline void appendDisposer(Disposer* disposer) { nw4r::ut::List_Append(&mChildren, disposer); } @@ -255,18 +257,17 @@ class Heap : public Disposer { nw4r::ut::List_Remove(&mChildren, disposer); } - inline rvlHeap* getHeapHandle() { - return mHeapHandle; - } + inline rvlHeap* getHeapHandle() { return mHeapHandle; } - static inline Heap* getCurrentHeap() { - return sCurrentHeap; - } + static inline Heap* getCurrentHeap() { return sCurrentHeap; } inline int getArenaEnd() { +#ifdef RII_CLIENT + return 0; +#else return mHeapHandle->arena_end; +#endif } - }; } // namespace EGG diff --git a/source/rvl/mem/expHeap.h b/source/rvl/mem/expHeap.h new file mode 100644 index 000000000..d3be1aba8 --- /dev/null +++ b/source/rvl/mem/expHeap.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +MEMHeapHandle MEMCreateExpHeapEx(void* begin, u32 size, u16 flags); + +void* MEMDestroyExpHeap(MEMHeapHandle heap); + +void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, u32 size, int align); + +void MEMFreeToExpHeap(MEMHeapHandle heap, void* block); + +u32 MEMResizeForMBlockExpHeap(MEMHeapHandle heap, void* block, u32 size); + +u32 MEMGetTotalFreeSizeForExpHeap(MEMHeapHandle heap); +u32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, int align); + +typedef void (*MEMExpHeapVisitor)(void* block, MEMHeapHandle heap, + u32 user_data); + +void MEMVisitAllocatedForExpHeap(MEMHeapHandle heap, MEMExpHeapVisitor visitor, + u32 user_data); + +u32 MEMGetSizeForMBlockExpHeap(const void* block); + +u16 MEMSetGroupIDForExpHeap(MEMHeapHandle heap, u16 group_id); +u16 MEMGetGroupIDForMBlockExpHeap(void* block); + +u32 MEMAdjustExpHeap(MEMHeapHandle heap); + + +void MEMInitAllocatorForExpHeap(MEMAllocator* allocator, MEMHeapHandle heap, int align); + +#ifdef __cplusplus +} +#endif // __cplusplus \ No newline at end of file diff --git a/source/rvl/mem/memAllocator.h b/source/rvl/mem/memAllocator.h index 633d6c0c3..3e3da60a4 100644 --- a/source/rvl/mem/memAllocator.h +++ b/source/rvl/mem/memAllocator.h @@ -6,6 +6,7 @@ extern "C" { #endif +#ifndef RII_CLIENT struct MEMAllocator { char _[0x10]; }; @@ -15,13 +16,17 @@ typedef struct MEMiHeapHead { char _2[0x40 - 0x20]; u32 _40; } MEMiHeapHead; -#define rvlHeap MEMiHeapHead typedef MEMiHeapHead* MEMHeapHandle; MEMiHeapHead* MEMFindContainHeap(const void*); +#else +#include +#endif + +#define rvlHeap MEMiHeapHead #ifdef __cplusplus } // extern "C" #endif \ No newline at end of file diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index 9b8eaa88a..b09ea233f 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -3,7 +3,7 @@ #ifdef __cplusplus extern "C" { #endif - +#ifndef RII_CLIENT struct OSThread { char _00[0x304]; char* stack_high; // 304 @@ -38,6 +38,7 @@ OSSwitchFunction OSSetSwitchThreadCallback(OSSwitchFunction callable); void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); +#endif #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/sources.py b/sources.py index dd450e10b..287c5046a 100644 --- a/sources.py +++ b/sources.py @@ -10,6 +10,7 @@ compile_source("source/egg/core/eggAllocator.cpp", "out/eggAllocator.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") compile_source("source/egg/core/eggArchive.cpp", "out/eggArchive.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) +compile_source("source/egg/core/eggExpHeap.cpp", "out/eggExpHeap.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") compile_source("source/egg/core/eggGraphicsFifo.cpp", "out/eggGraphicsFifo.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") compile_source("source/egg/math/eggQuat.cpp", "out/eggQuat.o", '4201_127', EGG_OPTS) diff --git a/system/slices.csv b/system/slices.csv index 1cfb1be62..6eee82b74 100644 --- a/system/slices.csv +++ b/system/slices.csv @@ -6,6 +6,7 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,802A2668,802A2680 ,,,,,,,,,, 1,eggArchive.cpp,,,,,,,8020F6EC,0x8020FCC4,,,,,,,802A2680,802A268C,803832D8,0x803832E4,,,80386D80,80386D84,,,, 1,eggDisposer.cpp,,,,,,,8021A0F0,8021A1B8,,,,,,,802A2B48,802A2B54 ,,,,,,,,,, +1,eggExpHeap.cpp,,,,,,,802269A8,80226F04,,,,,80257700,8025771A,802A2FF8,802A3024,,,,,,,,,, 1,eggGraphicsFifo.cpp,,,,,,,80229540,802296A8,,,,,,,802A30B0,802A30BC,,,,,0x80386E90,0x80386E99,,,, 1,eggHeap.cpp,,,,,,,802296A8,80229FAC,,,,,80257740,80257824,802A30C0,0x802a30ec,80384320,0x80384348,,,80386EA0,80386EC0,80388D68 ,80388D80,, 1,eggQuat.cpp,,,,,,,80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, From a97c1d9ca2bcf035f08f43af27db86584a154ae1 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 30 May 2021 06:54:25 -0600 Subject: [PATCH 067/477] :sparkles: eggVideo.cpp --- build.py | 8 +- link.lcf | 34 ++- o_files.txt | 9 +- source/egg/core/eggVideo.cpp | 575 +++++++++++++++++++++++++++++++++++ source/egg/core/eggVideo.hpp | 59 ++++ source/rvl/gx.h | 10 +- source/rvl/os/osThread.h | 5 + source/rvl/sc.h | 20 ++ source/rvl/vi.h | 59 ++++ sources.py | 1 + system/slices.csv | 1 + 11 files changed, 772 insertions(+), 9 deletions(-) create mode 100644 source/egg/core/eggVideo.cpp create mode 100644 source/egg/core/eggVideo.hpp create mode 100644 source/rvl/sc.h create mode 100644 source/rvl/vi.h diff --git a/build.py b/build.py index 2927c5e56..1f75f074c 100644 --- a/build.py +++ b/build.py @@ -143,8 +143,12 @@ def gen_lcf(src, dst, o_files): with open(src, 'r') as f: lcf = f.read() lcf += "\nFORCEFILES {\n" - lcf += "\n".join(Path(x).stem + ".o" for x in o_files) - lcf += "\n}\n" + for x in o_files: + # TODO: Add ability to disable FORCEFILE to slices.csv + if 'eggVideo' in x: + continue + lcf += Path(x).stem + ".o\n" + lcf += "}\n" with open(dst, 'w') as f: f.write(lcf) diff --git a/link.lcf b/link.lcf index 0c5747c42..4ab1eec02 100644 --- a/link.lcf +++ b/link.lcf @@ -56,6 +56,7 @@ OSInitMutex=0x801A7EAC; OSLockMutex=0x801A7EE4; OSUnlockMutex=0x801A7FC0; +OSGetTick = 0x801AAD74; OSCreateThread=0x801A9E84; OSIsThreadTerminated=0x801A98BC; @@ -76,7 +77,11 @@ memset=0x80006038; GXGetGPStatus=0x8016CEC4; GXInit=0x8016B850; - +GXGetYScaleFactor=0x8016F6CC; +GXGetNumXfbLines=0x8016F640; +GXSetDispCopySrc=0x8016F438; +GXSetDispCopyDst=0x8016F4B8; +GXSetDispCopyYScale=0x8016F8FC; MEMCreateUnitHeapEx= 0x801998A4; MEMDestroyUnitHeap= 0x80199A00; @@ -103,5 +108,30 @@ AllocatorAllocForExpHeap_ = 0x80199B58; AllocatorFreeForExpHeap_ = 0x80199B68; MEMInitAllocatorForExpHeap = 0x80199BB8; - +VIInit = 0x801B94A4; +VIWaitForRetrace = 0x801B99EC; +VIConfigure = 0x801B9F6C; +VIConfigurePan = 0x801BA650; +VIFlush = 0x801BA9A4; +VISetNextFrameBuffer = 0x801BAAB8; +VIGetNextFrameBuffer = 0x801BAB24; +VISetBlack = 0x801BAB2C; +VIGetRetraceCount = 0x801BABA4; +VIGetNextField = 0x801BABAC; +VIGetCurrentLine = 0x801BAC48; +VIGetTvFormat = 0x801BACD8; +VIGetDTVStatus = 0x801BAD38; +VISetTimeToDimming = 0x801BAFA8; + +SCGetProgressiveMode=0x801B1D84; +SCGetEuRgb60Mode=0x801B1CAC; +SCGetAspectRatio=0x801B1BE4; + +__cvt_fp2unsigned = 0x80021478; } + +FORCEACTIVE { +initialize__Q23EGG5VideoFPC15GXRenderModeObjPCPC15GXRenderModeObj +getTickPerVRetrace__Q23EGG5VideoFUl +getTickPerVRetrace__Q23EGG5VideoFv +} \ No newline at end of file diff --git a/o_files.txt b/o_files.txt index a2f1e4ec2..04f9b581b 100644 --- a/o_files.txt +++ b/o_files.txt @@ -46,16 +46,19 @@ dol/bss_80384b6c.o dol/sbss_80386ec0.o dol/sdata2_80388d80.o eggVector.o +dol/rodata_80257824.o +dol/sdata2_80389104.o +eggVideo.o dol/init_80004000.o dol/extab_80006460.o dol/extabindex_80006a20.o -dol/text_80243d18.o +dol/text_80244074.o dol/ctors_80244e8c.o dol/dtors_80244ea4.o -dol/rodata_80257824.o +dol/rodata_80258560.o dol/data_802a4004.o dol/bss_80384bf4.o dol/sdata_803857f6.o dol/sbss_80386f90.o -dol/sdata2_80389104.o +dol/sdata2_80389118.o dol/sbss2_80389140.o \ No newline at end of file diff --git a/source/egg/core/eggVideo.cpp b/source/egg/core/eggVideo.cpp new file mode 100644 index 000000000..e11b56264 --- /dev/null +++ b/source/egg/core/eggVideo.cpp @@ -0,0 +1,575 @@ +/*! + * @file + * @brief Implementations for the EGG video / render manager. + */ + +#include +#include +#include +#include +#include + +namespace EGG { + +namespace { + +const GXRenderModeObj gRMO_Ntsc_640x456IntDf_4x3 = {VI_TVMODE_NTSC_INT, + 0x280, + 0x1c8, + 0x1c8, + 0x19, + 0xc, + 0x29e, + 0x1c8, + VI_XFBMODE_DF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 7, + 0xC, + 0xC, + 0xC, + 7, + 7}; +const GXRenderModeObj gRMO_Ntsc_640x456Prog_4x3 = {VI_TVMODE_NTSC_PROG, + 0x280, + 0x1c8, + 0x1c8, + 0x19, + 0xc, + 0x29e, + 0x1c8, + VI_XFBMODE_SF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 0, + 0, + 0x15, + 0x16, + 0x15, + 0}; + +const GXRenderModeObj gRMO_Pal50_640x456IntDf_4x3 = {VI_TVMODE_PAL_INT, + 0x280, + 0x1c8, + 0x21e, + 0x1B, + 0x10, + 0x29a, + 0x21e, + VI_XFBMODE_DF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 7, + 0xC, + 0xC, + 0xC, + 7, + 7}; + +const GXRenderModeObj gRMO_Pal60_640x456IntDf_4x3 = {VI_TVMODE_EURGB60_INT, + 0x280, + 0x1C8, + 0x1c8, + 0x19, + 0xc, + 0x29e, + 0x1c8, + VI_XFBMODE_DF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 7, + 0xC, + 0xC, + 0xC, + 7, + 7}; + +const GXRenderModeObj gRMO_Pal60_640x456Prog_4x3 = {VI_TVMODE_EURGB60_PROG, + 0x280, + 0x1c8, + 0x1c8, + 0x19, + 0xc, + 0x29e, + 0x1c8, + VI_XFBMODE_SF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 0, + 0, + 0x15, + 0x16, + 0x15, + 0, + 0}; + +const GXRenderModeObj gRMO_Ntsc_640x456IntDf_16x9 = {VI_TVMODE_NTSC_INT, + 0x280, + 0x1c8, + 0x1c8, + 0x11, + 0xc, + 0x2ae, + 0x1c8, + VI_XFBMODE_DF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 7, + 0xC, + 0xC, + 0xC, + 7, + 7}; +const GXRenderModeObj gRMO_Ntsc_640x456Prog_16x9 = {VI_TVMODE_NTSC_PROG, + 0x280, + 0x1c8, + 0x1c8, + 0x11, + 0xc, + 0x2ae, + 0x1c8, + VI_XFBMODE_SF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 0, + 0, + 0x15, + 0x16, + 0x15, + 0, + 0}; + +const GXRenderModeObj gRMO_Pal50_640x456IntDf_16x9 = {VI_TVMODE_PAL_INT, + 0x280, + 0x1c8, + 0x21e, + 0x13, + 0x10, + 0x2aa, + 0x21e, + VI_XFBMODE_DF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 7, + 0xC, + 0xC, + 0xC, + 7, + 7}; + +const GXRenderModeObj gRMO_Pal60_640x456IntDf_16x9 = {VI_TVMODE_EURGB60_INT, + 0x280, + 0x1c8, + 0x1c8, + 0x11, + 0xc, + 0x2ae, + 0x1c8, + VI_XFBMODE_DF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 7, + 7, + 0xC, + 0xC, + 0xC, + 7, + 7}; +const GXRenderModeObj gRMO_Pal60_640x456Prog_16x9 = {VI_TVMODE_EURGB60_PROG, + 0x280, + 0x1c8, + 0x1c8, + 0x11, + 0xc, + 0x2ae, + 0x1c8, + VI_XFBMODE_SF, + 0, + 0, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 6, + 0, + 0, + 0x15, + 0x16, + 0x15, + 0, + 0}; + +const GXRenderModeObj* const StandardRenderModesTable[] = { + &gRMO_Ntsc_640x456IntDf_4x3, &gRMO_Ntsc_640x456Prog_4x3, + &gRMO_Pal50_640x456IntDf_4x3, &gRMO_Pal60_640x456IntDf_4x3, + &gRMO_Pal60_640x456Prog_4x3, + + &gRMO_Ntsc_640x456IntDf_16x9, &gRMO_Ntsc_640x456Prog_16x9, + &gRMO_Pal50_640x456IntDf_16x9, &gRMO_Pal60_640x456IntDf_16x9, + &gRMO_Pal60_640x456Prog_16x9}; + +} // namespace + +const GXRenderModeObj* +Video::initialize(const GXRenderModeObj* renderMode, + const GXRenderModeObj* const* renderModesTable) { + VIInit(); + + return configure((GXRenderModeObj*)renderMode, + (GXRenderModeObj**)renderModesTable); +} + +GXRenderModeObj* Video::configure(GXRenderModeObj* renderMode, + GXRenderModeObj** renderModesTable) { + GXRenderModeObj* rm = (GXRenderModeObj*)pRenderMode; // r30 + + if (!renderModesTable) + renderModesTable = (GXRenderModeObj**)StandardRenderModesTable; + if (!renderMode) + renderMode = (GXRenderModeObj*)getStandardRenderModeObj(renderModesTable); + + if (pRenderMode != renderMode) { + pRenderMode = renderMode; + + VISetBlack(true); + VIConfigure(renderMode); + VIFlush(); + + _08 = OSGetTick(); + mFlag |= VIDEO_FLAG_IS_NOT_BLACKED_OUT; + + u16 fbWidth = renderMode->fb_width; + u16 efbHeight = renderMode->efb_height; + + const float yScaleFactor = + GXGetYScaleFactor(efbHeight, renderMode->xfb_height); + const u16 numXfbLines = GXGetNumXfbLines(efbHeight, yScaleFactor); + + GXSetDispCopySrc(0, 0, fbWidth, efbHeight); + GXSetDispCopyDst(fbWidth, numXfbLines); + GXSetDispCopyYScale(yScaleFactor); + + VIWaitForRetrace(); + VIWaitForRetrace(); + } + + return rm; +} + +// Extracted into own function to force .sdata2 float ordering +static f32 FrameRateToTickPerVRetrace(f32 fps) { return OS_TIMER_CLOCK / fps; } + +u32 Video::getTickPerVRetrace(u32 tvFormat) { + f32 frame_rate; // f2 [VIGetTvFormat() - 1 > 1 ? ... : ...] + switch (tvFormat) { + case VI_PAL: + case VI_MPAL: + frame_rate = 50.0f; + break; + default: + frame_rate = 59.94f; + break; + } + + return FrameRateToTickPerVRetrace(frame_rate); +} + +u32 Video::getTickPerVRetrace() { + return getTickPerVRetrace(VIGetTvFormat()); // inline +} + +const GXRenderModeObj* Video::getStandardRenderModeObj( + const GXRenderModeObj* const* renderModesTable) { + // Query the system configuration + bool bProgressive = (u32)SCGetProgressiveMode() == 1u; // r31 + bool bEuRGB60 = (u32)SCGetEuRgb60Mode() == 1u; // r30 + bool b4x3 = (u32)SCGetAspectRatio() == SC_ASPECT_RATIO_4x3; // r29 + bool bDigitalAVConnected = + VIGetDTVStatus() == 1; // r28 is digital av (progressive) cable connected? + + bool bLivesInGoodCountry; // r0 + switch (VIGetTvFormat()) { + // North America / Japan + case VI_NTSC: + bLivesInGoodCountry = true; + break; + // Europe + case VI_PAL: + case VI_EURGB60: + bLivesInGoodCountry = false; + break; + default: + bLivesInGoodCountry = true; + break; + } + // progresive + if (bDigitalAVConnected && bProgressive) { + // North America / Japan + if (bLivesInGoodCountry) + return b4x3 ? renderModesTable[RM_NTSC_4x3_PROGRESSIVE] + : renderModesTable[RM_NTSC_16x9_PROGRESSIVE]; + // Europe + else + return b4x3 ? renderModesTable[RM_PAL_4x3_PROGRESSIVE] + : renderModesTable[RM_PAL_16x9_PROGRESSIVE]; + } + // non-progressive + else { + // North America / Japan + if (bLivesInGoodCountry) + return b4x3 ? renderModesTable[RM_NTSC_4x3] + : renderModesTable[RM_NTSC_16x9]; + // Europe + else { + if (bEuRGB60) + return b4x3 ? renderModesTable[RM_EURGB60_4x3] + : renderModesTable[RM_EURGB60_16x9]; + // PAL50 + else + return b4x3 ? renderModesTable[RM_PAL50_4x3] + : renderModesTable[RM_PAL50_16x9]; + } + } +} + +} // namespace EGG diff --git a/source/egg/core/eggVideo.hpp b/source/egg/core/eggVideo.hpp new file mode 100644 index 000000000..7e1ac0c17 --- /dev/null +++ b/source/egg/core/eggVideo.hpp @@ -0,0 +1,59 @@ +/*! + * @file + * @brief + */ +#pragma once + +#include + +struct GXRenderModeObj; + +namespace EGG { + +class Video { +public: + enum RenderModeTable { + // NTSC narrow + RM_NTSC_4x3, //!< [0] North America / Japan: narrow, non-progressive + //!< display. + RM_NTSC_4x3_PROGRESSIVE, //!< [1] North America / Japan: narrow, progressive + //!< display. + // PAL narrow + RM_PAL50_4x3, //!< [2] Europe: narrow, non-progressive display + RM_EURGB60_4x3, //!< [3] Europe: EuRGB60 narrow display. + RM_PAL_4x3_PROGRESSIVE, //!< [4] Europe: narrow, progressive display. + // NTSC wide + RM_NTSC_16x9, //!< [5] North America / Japan: wide, non-progressive display. + RM_NTSC_16x9_PROGRESSIVE, //!< [6] North America / Japan: wide, progressive + //!< display. + // PAL wide + RM_PAL50_16x9, //!< [7] Europe: wide, non-progressive display. + RM_EURGB60_16x9, //!< [8] Europe: RGB60 wide display. + RM_PAL_16x9_PROGRESSIVE //!< [9] Europe: wide, progressive display. + }; + + // calls VIInit() and then calls down to configure + const GXRenderModeObj* + initialize(const GXRenderModeObj* renderMode, + const GXRenderModeObj* const* renderModesTable); + // configure video (VI/GX) + GXRenderModeObj* configure(GXRenderModeObj* renderMode, + GXRenderModeObj** renderModesTable); + static u32 getTickPerVRetrace(u32 tvMode); + static u32 getTickPerVRetrace(); + static const GXRenderModeObj* + getStandardRenderModeObj(const GXRenderModeObj* const* renderModesTable); + + inline Video(const GXRenderModeObj* rm, const GXRenderModeObj* const* rmTable) + : pRenderMode(nullptr), mFlag(0), _08(0) { + initialize(rm, rmTable); + } + +private: + enum Flag { VIDEO_FLAG_IS_NOT_BLACKED_OUT = 1 }; + GXRenderModeObj const* pRenderMode; + u8 mFlag; + u32 _08; +}; + +} // namespace EGG diff --git a/source/rvl/gx.h b/source/rvl/gx.h index be1e26efc..ec4ae6a9b 100644 --- a/source/rvl/gx.h +++ b/source/rvl/gx.h @@ -12,7 +12,7 @@ void GXGetGPStatus(u8* overhi, u8* underlow, u8* readIdle, u8* cmdIdle, u8* brkpt); GXFifoObj* GXInit(void* buf, u32 size); -struct GXRenderModeObj { +typedef struct GXRenderModeObj { int tv_mode; u16 fb_width; u16 efb_height; @@ -26,7 +26,13 @@ struct GXRenderModeObj { u8 aa; u8 sample[12][2]; u8 vert_filter[7]; -}; +} GXRenderModeObj; + +f32 GXGetYScaleFactor(u16 efb_height, u16 xfb_height); +u16 GXGetNumXfbLines(u16 efb_height, f32 y_scale_factor); +void GXSetDispCopySrc(u16 left, u16 top, u16 width, u16 height); +void GXSetDispCopyDst(u16 width, u16 height); +u32 GXSetDispCopyYScale(f32 y_scale_factor); #ifdef __cplusplus } diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index b09ea233f..cdddc2bb7 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -4,6 +4,11 @@ extern "C" { #endif #ifndef RII_CLIENT + +u32 OSGetTick(); +u32 __OSBusClock : 0x800000F8; +#define OS_TIMER_CLOCK (__OSBusClock >> 2) + struct OSThread { char _00[0x304]; char* stack_high; // 304 diff --git a/source/rvl/sc.h b/source/rvl/sc.h new file mode 100644 index 000000000..7ce430eb8 --- /dev/null +++ b/source/rvl/sc.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum SCAspectRatio { + SC_ASPECT_RATIO_4x3, + SC_ASPECT_RATIO_16x9 +} SCAspectRatio; + +u8 SCGetProgressiveMode(); +u8 SCGetEuRgb60Mode(); +u8 SCGetAspectRatio(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/vi.h b/source/rvl/vi.h new file mode 100644 index 000000000..e0ba9a961 --- /dev/null +++ b/source/rvl/vi.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum VIMode { VI_INTERLACE, VI_NON_INTERLACE, VI_PROGRESSIVE } VIMode; + +typedef enum VIRegion { + VI_NTSC, + VI_PAL, + VI_MPAL, + VI_DEBUG, + VI_DEBUG_PAL, + VI_EURGB60 +} VIRegion; + +typedef enum VIXFBMode { VI_XFBMODE_SF, VI_XFBMODE_DF } VIXFBMode; + +// Credit: YAGCD +typedef enum VITVMode { +#define MAKE_TV_MODE(region, mode) ((region << 2) + mode) + VI_TVMODE_NTSC_INT = MAKE_TV_MODE(VI_NTSC, VI_INTERLACE), + VI_TVMODE_NTSC_DS = MAKE_TV_MODE(VI_NTSC, VI_NON_INTERLACE), + VI_TVMODE_NTSC_PROG = MAKE_TV_MODE(VI_NTSC, VI_PROGRESSIVE), + + VI_TVMODE_PAL_INT = MAKE_TV_MODE(VI_PAL, VI_INTERLACE), + VI_TVMODE_PAL_DS = MAKE_TV_MODE(VI_PAL, VI_NON_INTERLACE), + + VI_TVMODE_EURGB60_INT = MAKE_TV_MODE(VI_EURGB60, VI_INTERLACE), + VI_TVMODE_EURGB60_DS = MAKE_TV_MODE(VI_EURGB60, VI_NON_INTERLACE), + VI_TVMODE_EURGB60_PROG = MAKE_TV_MODE(VI_EURGB60, VI_PROGRESSIVE), + + VI_TVMODE_MPAL_INT = MAKE_TV_MODE(VI_MPAL, VI_INTERLACE), + VI_TVMODE_MPAL_DS = MAKE_TV_MODE(VI_MPAL, VI_NON_INTERLACE), + VI_TVMODE_MPAL_PROG = MAKE_TV_MODE(VI_MPAL, VI_PROGRESSIVE), + + VI_TVMODE_DEBUG_INT = MAKE_TV_MODE(VI_DEBUG, VI_INTERLACE), + + VI_TVMODE_DEBUG_PAL_INT = MAKE_TV_MODE(VI_DEBUG_PAL, VI_INTERLACE), + VI_TVMODE_DEBUG_PAL_DS = MAKE_TV_MODE(VI_DEBUG_PAL, VI_NON_INTERLACE) +#undef MAKE_TV_MODE +} VITVMode; + + +void VIInit(); +void VISetBlack(int black); +void VIConfigure(const GXRenderModeObj* obj); +void VIFlush(); +void VIWaitForRetrace(); + +int VIGetDTVStatus(); +unsigned int VIGetTvFormat(); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/sources.py b/sources.py index 287c5046a..41d42dc96 100644 --- a/sources.py +++ b/sources.py @@ -18,6 +18,7 @@ # compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggThread.cpp", "out/eggThread.o", '4201_127', EGG_OPTS) compile_source("source/egg/core/eggUnitHeap.cpp", "out/eggUnitHeap.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") +compile_source("source/egg/core/eggVideo.cpp", "out/eggVideo.o", '4201_127', EGG_OPTS+ " -use_lmw_stmw=on ") compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) diff --git a/system/slices.csv b/system/slices.csv index 6eee82b74..ab79ca69b 100644 --- a/system/slices.csv +++ b/system/slices.csv @@ -16,5 +16,6 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, 1,eggUnitHeap.cpp,,,,,,,80243754,80243A00,,,,,,,802A3FD8,802A4004,,,,,,,,,, 1,eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,80384B70,80384BF4,,,80386F78,80386F90,803890F8 ,80389104,, +1,eggVideo.cpp,,,,,,,80243D18,80244074,,,,,802582E0,80258560,,,,,,,,,80389108,80389118,, ,eggXfb.cpp,,,,,,,80244160,80244200,,,,,,,,,,,,,,,,,, ,eggXfbManager.cpp,,,,,,,80244200,802443AC,,,,,,,,,,,,,,,,,, From a6b6d19673367d3ba70f277e50915a2b48d5ca20 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 30 May 2021 08:56:36 -0600 Subject: [PATCH 068/477] :construction: UIControl.hpp --- source/egg/math/eggVector.hpp | 4 +- source/game/ui/RTTI.hpp | 19 +++ source/game/ui/UIControl.cpp | 259 ++++++++++++++++++++++++++++++++++ source/game/ui/UIControl.hpp | 174 +++++++++++++++++++++++ 4 files changed, 454 insertions(+), 2 deletions(-) create mode 100644 source/game/ui/RTTI.hpp create mode 100644 source/game/ui/UIControl.cpp create mode 100644 source/game/ui/UIControl.hpp diff --git a/source/egg/math/eggVector.hpp b/source/egg/math/eggVector.hpp index cba70d92c..be1db8725 100644 --- a/source/egg/math/eggVector.hpp +++ b/source/egg/math/eggVector.hpp @@ -22,7 +22,7 @@ struct Vector2f { static const Vector2f ex, ey; inline Vector2f(float _x, float _y) : x(_x), y(_y) {} - Vector2f(); + inline Vector2f() {} // For now ~Vector2f(); @@ -44,7 +44,7 @@ struct Vector3f { // Header fns inline Vector3f(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {} - inline Vector3f() : x(0.0f), y(0.0f), z(0.0f) {} + inline Vector3f() {} // operator Vec*() { return reinterpret_cast(&x); } diff --git a/source/game/ui/RTTI.hpp b/source/game/ui/RTTI.hpp new file mode 100644 index 000000000..e9ab5c4c6 --- /dev/null +++ b/source/game/ui/RTTI.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace UI { + +struct TypeInfo { + TypeInfo* base; +}; + +template struct Deferred { + char data[sizeof(T)]; + + T& get() { return *reinterpret_cast(&data); } + const T& get() const { return *reinterpret_cast(&data); } + + operator T&() { return get(); }; + operator const T&() const { return get(); }; +}; + +} // namespace UI \ No newline at end of file diff --git a/source/game/ui/UIControl.cpp b/source/game/ui/UIControl.cpp new file mode 100644 index 000000000..41fa07e1a --- /dev/null +++ b/source/game/ui/UIControl.cpp @@ -0,0 +1,259 @@ +#include "UIControl.hpp" + +namespace UI { + +UIControl::UIControl() + : mGroup(nullptr), mZIndex(0.0f), mHidden(false), mDrawPass(0), + mAnimated(true), mAnimStartFrame(0.0f), mOnShowSfxId(-1), + mOnHideSfxId(-1) {} + +struct UnkStruct { + ~UnkStruct(); +}; + +UnkStruct::~UnkStruct() {} + +UIControl::~UIControl() {} + +void UIControl::debug() { mChildren.debug(); } + +void UIControl::init() { + initSelf(); + mChildren.init(); +} + +void UIControl::calc() { + calcSelf(); + mChildren.calc(); +} + +void UIControl::draw(int draw_pass) { + if (getParent() == nullptr && mDrawPass != draw_pass) + return; + + if (mHidden) + return; + + mChildren.draw(draw_pass); +} + +void UIControl::solve(float frame) { + if (!mAnimated) + frame = 0.0f; + + solveAnim(elem(POS_FIXED).trans.x, frame); +} + +extern void SpawnSfx(s32 id, s32, void*); // 807146A8 +#define SFX_ID_NULL (~0u) + +void UIControl::onPageEvent(PageEventTrigger event, u32 page_id) { + if (event == EVENT_PAGE_OPEN && mOnShowSfxId != SFX_ID_NULL) { + SpawnSfx(mOnShowSfxId, -1, getGroup()->getRoot()); + } else if (event == EVENT_PAGE_CLOSE && mOnHideSfxId != SFX_ID_NULL) { + SpawnSfx(mOnHideSfxId, -1, getGroup()->getRoot()); + } + + onPageEventSelf(event, page_id); +} + +extern float*** spMenuData_; // 809C1E38 + +#define BLEND_COLORS(a, b) ((((a) + 1) * ((b) + 1) - 1) >> 8) + +void UIControl::solveAnim(float& page_anim_x, float frame) { + page_anim_x = -spMenuData_[0][0][0x3F0 / 4] * frame; + + UIControl* parent = getParent(); + if (parent) { + elem(POS_SCREENSPACE).trans.x = + elem(POS_ARTIST_REL).trans.x + + elem(POS_FIXED).trans.x * parent->elem(POS_ARTIST_REL).scale.x; + elem(POS_SCREENSPACE).trans.y = + elem(POS_ARTIST_REL).trans.y + + elem(POS_FIXED).trans.y * parent->elem(POS_ARTIST_REL).scale.y; + // mScreenSpace.translate.x = + // artistPos.translate.x + (fixedPos.translate.x * + // parent.artistPos.scale.x); + // mScreenSpace.translate.y = + // artistPos.translate.y + fixedPos.translate.y * + // parent.artistPos.scale.y; + } else { + elem(POS_SCREENSPACE).trans.x = + elem(POS_ARTIST_REL).trans.x + elem(POS_FIXED).trans.x; + elem(POS_SCREENSPACE).trans.y = + elem(POS_ARTIST_REL).trans.y + elem(POS_FIXED).trans.y; + // mScreenSpace.translate.x = artistPos.translate.x + fixedPos.translate.x; + // mScreenSpace.translate.y = artistPos.translate.y + fixedPos.translate.y; + } + + elem(POS_SCREENSPACE).trans.z = + elem(POS_ARTIST_REL).trans.z + elem(POS_FIXED).trans.z; + // mScreenSpace.translate.z = artistPos.translate.z + fixedPos.translate.z; + + elem(POS_SCREENSPACE).scale.x = + elem(POS_ARTIST_REL).scale.x * elem(POS_FIXED).scale.x; + elem(POS_SCREENSPACE).scale.y = + elem(POS_ARTIST_REL).scale.y * elem(POS_FIXED).scale.y; + // mScreenSpace.scale.x = artistPos.scale.x * fixedPos.scale.x; + // mScreenSpace.scale.y = artistPos.scale.y * fixedPos.scale.y; + + elem(POS_SCREENSPACE).opacity = + BLEND_COLORS(elem(POS_ARTIST_REL).opacity, elem(POS_FIXED).opacity); + // mScreenSpace.alpha = ((artistPos.alpha + 1) * (fixedPos.alpha + 1) - 1) >> + // 8; +} + +void UIControl::initChildren(u32 capacity) { mChildren.create(this, capacity); } + +void UIControl::insertChild(u32 index, UIControl* child) { + mChildren.insert(index, child, mDrawPass); +} + +void UIControl::onGroupAttached(ControlGroup* group, u32 draw_pass) { + mGroup = group; + mDrawPass = draw_pass; +} + +void UIControl::setSfxIds(u32 on_show, u32 on_hide) { + mOnShowSfxId = on_show; + mOnHideSfxId = on_hide; +} + +const char* UIControl::getTypeName_Public() const { return getTypeName(); } + +const char* UIControl::getTypeName() const { return "UIControl"; } + +void UIControl::solve_propagate() { + UIControl* parent = getParent(); + if (parent) { + elem(POS_ARTIST_REL).trans.x = + parent->elem(POS_ARTIST_REL).trans.x + + elem(POS_PROGRAMMER_REL).trans.x * parent->elem(POS_ARTIST_REL).scale.x; + elem(POS_ARTIST_REL).trans.y = + parent->elem(POS_ARTIST_REL).trans.y + + elem(POS_PROGRAMMER_REL).trans.y * parent->elem(POS_ARTIST_REL).scale.y; + // artistPos.translate.x = + // parent->artistPos.translate.x + + // (programmerPos.translate.x * parent->artistPos.scale.x); + // artistPos.translate.y = + // parent->artistPos.translate.y + + // (programmerPos.translate.y * parent->artistPos.scale.y); + + elem(POS_ARTIST_REL).trans.z = + elem(POS_PROGRAMMER_REL).trans.z + parent->elem(POS_ARTIST_REL).trans.z; + // artistPos.translate.z = + // programmerPos.translate.z + parent->artistPos.translate.z; + + elem(POS_ARTIST_REL).scale.x = + elem(POS_PROGRAMMER_REL).scale.x * parent->elem(POS_ARTIST_REL).scale.x; + elem(POS_ARTIST_REL).scale.y = + elem(POS_PROGRAMMER_REL).scale.y * parent->elem(POS_ARTIST_REL).scale.y; + // artistPos.scale.x = programmerPos.scale.x * parent->artistPos.scale.x; + // artistPos.scale.y = programmerPos.scale.y * parent->artistPos.scale.y; + + elem(POS_ARTIST_REL).opacity = BLEND_COLORS( + elem(POS_PROGRAMMER_REL).opacity, parent->elem(POS_ARTIST_REL).opacity); + // artistPos.alpha = + // ((programmerPos.alpha + 1) * (grp->mParent->artistPos.alpha + 1) - 1) + // >> 8; + } else { + elem(POS_ARTIST_REL) = elem(POS_PROGRAMMER_REL); + // artistPos.translate.x = programmerPos.translate.x; + // artistPos.translate.y = programmerPos.translate.y; + // artistPos.translate.z = programmerPos.translate.z; + // artistPos.scale.x = programmerPos.scale.x; + // artistPos.scale.y = programmerPos.scale.y; + // artistPos.alpha = programmerPos.alpha + } + parent = getParent(); + if (parent) { + elem(POS_SCREENSPACE).trans.x = + elem(POS_ARTIST_REL).trans.x + + elem(POS_FIXED).trans.x * parent->elem(POS_ARTIST_REL).scale.x; + elem(POS_SCREENSPACE).trans.y = + elem(POS_ARTIST_REL).trans.y + + elem(POS_FIXED).trans.y * parent->elem(POS_ARTIST_REL).scale.y; + // mScreenSpace.translate.x = + // artistPos.translate.x + + // (fixedPos.translate.x * parent->artistPos.scale.x); + // mScreenSpace.translate.y = + // (artistPos.translate.y + + // (fixedPos.translate.y * parent->artistPos.scale.y)); + } else { + elem(POS_SCREENSPACE).trans.x = + elem(POS_ARTIST_REL).trans.x + elem(POS_FIXED).trans.x; + elem(POS_SCREENSPACE).trans.y = + elem(POS_ARTIST_REL).trans.y + elem(POS_FIXED).trans.y; + // mScreenSpace.translate.x = artistPos.translate.x + fixedPos.translate.x; + // mScreenSpace.translate.y = artistPos.translate.y + fixedPos.translate.y; + } + + elem(POS_SCREENSPACE).trans.z = + elem(POS_ARTIST_REL).trans.z + elem(POS_FIXED).trans.z; + // mScreenSpace.translate.z = artistPos.translate.z + fixedPos.translate.z; + + elem(POS_SCREENSPACE).scale.x = + elem(POS_ARTIST_REL).scale.x * elem(POS_FIXED).scale.x; + elem(POS_SCREENSPACE).scale.y = + elem(POS_ARTIST_REL).scale.y * elem(POS_FIXED).scale.y; + // mScreenSpace.scale.x = artistPos.scale.x * fixedPos.scale.x; + // mScreenSpace.scale.y = artistPos.scale.y * fixedPos.scale.y; + + elem(POS_SCREENSPACE).opacity = + BLEND_COLORS(elem(POS_ARTIST_REL).opacity, elem(POS_FIXED).opacity); + // mScreenSpace.alpha = ((artistPos.alpha + 1) * (fixedPos.alpha + 1) - 1) >> + // 8; + + // + // Recurse down to children + // + mChildren.solve(); +} + +void UIControl::solve_screenSpace() { + UIControl* parent = getParent(); + + if (parent) { + elem(POS_SCREENSPACE).trans.x = + elem(POS_ARTIST_REL).trans.x + + elem(POS_FIXED).trans.x * parent->elem(POS_ARTIST_REL).scale.x; + elem(POS_SCREENSPACE).trans.y = + elem(POS_ARTIST_REL).trans.y + + elem(POS_FIXED).trans.y * parent->elem(POS_ARTIST_REL).scale.y; + // mScreenSpace.translate.x = + // artistPos.translate.x + + // (fixedPos.translate.x * parent->artistPos.scale.x); + // mScreenSpace.translate.y = + // (artistPos.translate.y + + // (fixedPos.translate.y * parent->artistPos.scale.y)); + } else { + elem(POS_SCREENSPACE).trans.x = + elem(POS_ARTIST_REL).trans.x + elem(POS_FIXED).trans.x; + elem(POS_SCREENSPACE).trans.y = + elem(POS_ARTIST_REL).trans.y + elem(POS_FIXED).trans.y; + // mScreenSpace.translate.x = artistPos.translate.x + fixedPos.translate.x; + // mScreenSpace.translate.y = (artistPos.translate.y + + // fixedPos.translate.y); + } + + elem(POS_SCREENSPACE).trans.z = + elem(POS_ARTIST_REL).trans.z + elem(POS_FIXED).trans.z; + // mScreenSpace.translate.z = artistPos.translate.z + fixedPos.translate.z; + + elem(POS_SCREENSPACE).scale.x = + elem(POS_ARTIST_REL).scale.x * elem(POS_FIXED).scale.x; + elem(POS_SCREENSPACE).scale.y = + elem(POS_ARTIST_REL).scale.y * elem(POS_FIXED).scale.y; + // mScreenSpace.scale.x = artistPos.scale.x * fixedPos.scale.x; + // mScreenSpace.scale.y = artistPos.scale.y * fixedPos.scale.y; + + elem(POS_SCREENSPACE).opacity = + BLEND_COLORS(elem(POS_ARTIST_REL).opacity, elem(POS_FIXED).opacity); + // mScreenSpace.alpha = ((artistPos.alpha + 1) * (fixedPos.alpha + 1) - 1) >> + // 8; +} + +#undef BLEND_COLORS + +} // namespace UI \ No newline at end of file diff --git a/source/game/ui/UIControl.hpp b/source/game/ui/UIControl.hpp new file mode 100644 index 000000000..9476fa1f4 --- /dev/null +++ b/source/game/ui/UIControl.hpp @@ -0,0 +1,174 @@ +#pragma once + +#include + +#include +#include +#include + +namespace UI { + +// css "position" +enum ElementPosition { + //! Set by the programmer (css "relative") + POS_PROGRAMMER_REL, + + //! Set by the layout artist in a .brctr file (css "relative") + POS_ARTIST_REL, + + //! Propagated down from the root (css "fixed") + POS_FIXED, + + //! The final position displayed on screen (device coordinates) + POS_SCREENSPACE, + + //! Number of element positions + POS_MAX +}; + +struct Element { + // css "transform" { + EGG::Vector3f trans; // css "translate" + EGG::Vector2f scale; // css "scale" + // } + u8 opacity; // css "opacity" + + u8 _[3]; + + inline void init() { + trans.x = 0.0f; + trans.y = 0.0f; + trans.z = 0.0f; + + scale.x = 1.0f; + scale.y = 1.0f; + + opacity = 0xFF; + } +}; + +class UIControl { +public: + UIControl(); + virtual ~UIControl(); + + // + // VFX + // + + // Draw self + children + virtual void init(); + virtual void calc(); + virtual void draw(int draw_pass); + +protected: + // Draw self only + virtual void initSelf(); + virtual void calcSelf(); + +public: + void solve(float frame); + + virtual void solveAnim(float& page_anim_x, float frame); + + // Something stripped + void debug(); + + // + // SFX + // + + enum PageEventTrigger { EVENT_PAGE_OPEN, EVENT_PAGE_CLOSE }; + + void onPageEvent(PageEventTrigger event, u32 page_id); + +protected: + // Special behavior + virtual void onPageEventSelf(PageEventTrigger event, u32 page_id); + + void setSfxIds(u32 on_show, u32 on_hide); + +public: + // + // Hierarchy + // + + void initChildren(u32 capacity); + + void insertChild(u32 index, UIControl* child); + + void onGroupAttached(ControlGroup* group, u32 draw_pass); + + void solve_propagate(); + void solve_screenSpace(); + +public: + // + // RTTI + // + + const char* getTypeName_Public() const; + +protected: + virtual const TypeInfo& getTypeInfo() const; + virtual const char* getTypeName() const; + + virtual void vf30(); + virtual void vf34(); + + // + // Positioning + // + +private: + struct ElementPositionsArray { + Deferred data[POS_MAX]; + + ElementPositionsArray() { + for (int i = 0; i < POS_MAX; ++i) { + data[i].get().init(); + } + } + }; + ElementPositionsArray mElementPositions; + +public: + Element& elem(ElementPosition pos) { + expects(pos < POS_MAX); + return mElementPositions.data[pos]; + } + const Element& elem(ElementPosition pos) const { + expects(pos < POS_MAX); + return mElementPositions.data[pos]; + } + +private: + ControlGroup* mGroup; // the true "parent" is mGroup->mParent + ControlGroup mChildren; + +public: + inline ControlGroup* getGroup() { return mGroup; } + inline UIControl* getParent() { return mGroup->getParent(); } + inline UIControl* getRoot() { return mGroup->getRoot(); } + inline ControlGroup* getChildren() { return &mChildren; } + + inline f32 getZIndex() const { return mZIndex; } + +private: + f32 mZIndex; + + bool mHidden; + char _pad0[3]; + + u32 mDrawPass; + + bool mAnimated; + char _pad1[3]; + + f32 mAnimStartFrame; + + s32 mOnShowSfxId; + s32 mOnHideSfxId; +}; + +} // namespace UI From 17a70373ea9a233e95bac5189bc643e8ccf300a9 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 30 May 2021 09:10:13 -0600 Subject: [PATCH 069/477] :construction: ControlGroup.cpp --- source/game/ui/ControlGroup.cpp | 85 +++++++++++++++++++++++++++++++++ source/game/ui/ControlGroup.hpp | 61 +++++++++++++++++++++++ sources.py | 10 +++- 3 files changed, 154 insertions(+), 2 deletions(-) create mode 100644 source/game/ui/ControlGroup.cpp create mode 100644 source/game/ui/ControlGroup.hpp diff --git a/source/game/ui/ControlGroup.cpp b/source/game/ui/ControlGroup.cpp new file mode 100644 index 000000000..022086118 --- /dev/null +++ b/source/game/ui/ControlGroup.cpp @@ -0,0 +1,85 @@ +#include "ControlGroup.hpp" +#include "UIControl.hpp" + +namespace UI { + +ControlGroup::ControlGroup() + : mData(nullptr), mDataSorted(nullptr), mParent(nullptr), mRoot(nullptr), + mSize(0) {} + +ControlGroup::~ControlGroup() { + delete[] mDataSorted; + delete[] mData; +} + +void ControlGroup::create(UIControl* parent, int capacity) { + mParent = parent; + mRoot = parent->getRoot(); + mSize = capacity; + + create(); +} + +void ControlGroup::create(UIControl* parent, UIControl* root, int capacity) { + mParent = parent; + mRoot = root; + mSize = capacity; + + create(); +} + +void ControlGroup::insert(int index, UIControl* control, int pass) { + mData[index] = control; + mDataSorted[index] = control; + control->onGroupAttached(this, pass); +} + +void ControlGroup::debug() { + for (int i = 0; i < mSize; ++i) { + mData[i]->debug(); + } +} + +void ControlGroup::init() { + for (int i = 0; i < mSize; ++i) { + mData[i]->init(); + } +} +void ControlGroup::calc() { + for (int i = 0; i < mSize; ++i) { + mData[i]->calc(); + } +} + +void ControlGroup::draw(int draw_pass) { + for (int i = 0; i < mSize - 1; ++i) { + UIControl* key = mDataSorted[i]; + + int j = i; + for (; j > 0 && mDataSorted[j - 1]->getZIndex() > key->getZIndex(); --j) { + mDataSorted[j] = mDataSorted[j - 1]; + } + mDataSorted[j] = key; + } + + for (int i = 0; i < mSize; ++i) { + mDataSorted[i]->draw(draw_pass); + } +} + +void ControlGroup::solve() { + for (int i = 0; i < mSize; ++i) { + mData[i]->solve_propagate(); + } +} + +UIControl* ControlGroup::at(int index) { return mData[index]; } + +void ControlGroup::transform(ControlGroup::Functor& functor) { + for (int i = 0; i < mSize; ++i) { + functor(mData[i]); + mData[i]->getChildren()->transform(functor); + } +} + +} // namespace UI \ No newline at end of file diff --git a/source/game/ui/ControlGroup.hpp b/source/game/ui/ControlGroup.hpp new file mode 100644 index 000000000..5d1e8a784 --- /dev/null +++ b/source/game/ui/ControlGroup.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include + +namespace UI { + +class UIControl; + +class ControlGroup { +public: + ControlGroup(); + ~ControlGroup(); + + // After construction, create() must be called once to put the group + // in a valid state. Calling create() twice is a memory leak. + void create(UIControl* parent, int size); + void create(UIControl* parent, UIControl* root, int size); + + void insert(int index, UIControl* control, int pass); + UIControl* at(int index); + inline UIControl* operator[](int index) { return at(index); } + + struct Functor { + virtual void operator()(UIControl* control) = 0; + }; + + // Call a function on every element + void debug(); // effective no-op + void init(); // initialize every control + + void calc(); // call each frame + void draw(int draw_pass); // call each frame + + void solve(); // solve positioning + void transform(Functor& functor); // a la std::transform + + UIControl* getParent() { return mParent; } + UIControl* getRoot() { return mRoot; } + +private: + UIControl** mData; + UIControl** mDataSorted; // By z_index + UIControl* mParent; + UIControl* mRoot; + s32 mSize; + + inline void create() { + if (mSize <= 0) + return; + + mData = new UIControl*[mSize]; + mDataSorted = new UIControl*[mSize]; + + for (int i = 0; i < mSize; ++i) { + mData[i] = nullptr; + mDataSorted[i] = nullptr; + } + } +}; + +} // namespace UI \ No newline at end of file diff --git a/sources.py b/sources.py index 41d42dc96..0cc65cba2 100644 --- a/sources.py +++ b/sources.py @@ -23,5 +23,11 @@ # compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) # compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) -compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', EGG_OPTS) -compile_source("source/game/jmap/JmpResourceCourse.cpp", "out/JmpResourceCourse.o", '4201_127', REL_OPTS) \ No newline at end of file +""" +StaticR.rel +""" + +compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', REL_OPTS) +compile_source("source/game/ui/ControlGroup.cpp", "out/ControlGroup.o", '4201_127', REL_OPTS) +compile_source("source/game/ui/UIControl.cpp", "out/UIControl.o", '4201_127', REL_OPTS) +compile_source("source/game/jmap/JmpResourceCourse.cpp", "out/JmpResourceCourse.o", '4201_127', REL_OPTS) From 57eb9e26332c3fd98f05fa895eb5f11476c38b57 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 6 Jun 2021 11:29:50 +0200 Subject: [PATCH 070/477] Add GitHub workflow for clang-format (#4) * Add GitHub workflow for clang-format * Fix formatting --- .github/workflows/lint.yml | 20 +++++++++++ source/egg/core/eggArchive.cpp | 2 +- source/egg/core/eggDvdFile.cpp | 6 ++-- source/egg/core/eggExpHeap.cpp | 6 ++-- source/egg/core/eggUnitHeap.cpp | 3 +- source/egg/util/eggCntFile.cpp | 2 +- source/game/host_system/SystemManager.cpp | 44 +++++++++++------------ 7 files changed, 51 insertions(+), 32 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..9beb98c57 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,20 @@ +--- +on: [push] +name: Lint +jobs: + lint-clang-format: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + + - name: Install clang-format + run: sudo apt-get install -y clang-format + + - name: Verify formatting + run: | + find source -type f \ + -name '*.h' \ + -o -name '*.c' \ + -o -name '*.hpp' \ + -o -name '*.cpp' \ + -exec clang-format --dry-run --Werror {} + diff --git a/source/egg/core/eggArchive.cpp b/source/egg/core/eggArchive.cpp index 42aa45f2d..5681d20ae 100644 --- a/source/egg/core/eggArchive.cpp +++ b/source/egg/core/eggArchive.cpp @@ -32,7 +32,7 @@ Archive* Archive::mount(void* arcStart, Heap* pHeap, int align) { archive->mStatus = NOT_LOADED; EGG_ASSERT(false, "eggArchive.cpp", 166, "false"); } - + // If we failed, clean up if (!could_create) // INLINE { diff --git a/source/egg/core/eggDvdFile.cpp b/source/egg/core/eggDvdFile.cpp index 02f8bd258..f454ae2fc 100644 --- a/source/egg/core/eggDvdFile.cpp +++ b/source/egg/core/eggDvdFile.cpp @@ -28,7 +28,6 @@ void DvdFile::initiate() { this->_38 = 0; } - bool DvdFile::open(s32 entryNum) { if (!this->mIsOpen && entryNum != -1) { if (mFileInfo.open(entryNum)) { @@ -61,8 +60,9 @@ int DvdFile::readData(void* addr, int len, int offset) { } this->_C4 = OSGetCurrentThread(); int r31 = (void*)-1; - if (DVDReadAsyncPrio(&this->mFileInfo, addr, len, offset, (DVDCallback)&doneProcess, 2)) - r31 = this->sync(); + if (DVDReadAsyncPrio(&this->mFileInfo, addr, len, offset, + (DVDCallback)&doneProcess, 2)) + r31 = this->sync(); this->_C4 = 0; OSUnlockMutex(&this->Mutex_08); return r31; diff --git a/source/egg/core/eggExpHeap.cpp b/source/egg/core/eggExpHeap.cpp index a99425dde..37d6a6e72 100644 --- a/source/egg/core/eggExpHeap.cpp +++ b/source/egg/core/eggExpHeap.cpp @@ -10,7 +10,7 @@ inline void* operator new(unsigned long, void* p) { return p; } namespace EGG { - + enum { EXP_HEAP_BASE_SIZE = 56, // (sizeof(ExpHeap) - sizeof(...)), EXP_HEAP_ALIGN = 4 @@ -26,7 +26,7 @@ ExpHeap::~ExpHeap() { #ifdef NONMATCHING ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { - ExpHeap* createdHeap = nullptr; // r30 + ExpHeap* createdHeap = nullptr; // r30 u32 heapEnd = nw4r::ut::RoundDown((u32)block + size, EXP_HEAP_ALIGN); // r3 u32 heapStart = nw4r::ut::RoundUp((u32)block, EXP_HEAP_ALIGN); // r27 @@ -58,7 +58,7 @@ ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { extern "C" void __ct__Q23EGG4HeapFP12MEMiHeapHead(); extern "C" void findContainHeap__Q23EGG4HeapFPCv(); -// clang-format off[ +// clang-format off asm ExpHeap* ExpHeap::create(void* block, u32 size, u16 attr) { nofralloc diff --git a/source/egg/core/eggUnitHeap.cpp b/source/egg/core/eggUnitHeap.cpp index d244b4c62..8b4e0fce1 100644 --- a/source/egg/core/eggUnitHeap.cpp +++ b/source/egg/core/eggUnitHeap.cpp @@ -64,7 +64,8 @@ UnitHeap* UnitHeap::create(u32 size, u32 unit_size, EGG::Heap* heap, s32 align, block = heap->alloc(size, UNIT_HEAP_ALIGN); if (block) { - if ((ret = create(block, size, unit_size, align, flag)) != nullptr) // INLINE + if ((ret = create(block, size, unit_size, align, flag)) != + nullptr) // INLINE ret->mParentHeap = heap; else heap->free(block); diff --git a/source/egg/util/eggCntFile.cpp b/source/egg/util/eggCntFile.cpp index f8570ea4d..5b483b43a 100644 --- a/source/egg/util/eggCntFile.cpp +++ b/source/egg/util/eggCntFile.cpp @@ -1,7 +1,7 @@ #include namespace EGG { - + CntFile::CntFile() {} CntFile::~CntFile() {} diff --git a/source/game/host_system/SystemManager.cpp b/source/game/host_system/SystemManager.cpp index d033965c4..d01b9ed47 100644 --- a/source/game/host_system/SystemManager.cpp +++ b/source/game/host_system/SystemManager.cpp @@ -11,37 +11,35 @@ namespace System { static const char* sRegionCode = "RMCP"; static const char* MAIN_SMAP = "/rel/RevoKartR.SMAP"; - SystemManager* SystemManager::sInstance; SystemManager* SystemManager::initStaticInstance(EGG::Heap* heap) { return (sInstance = new SystemManager(heap)); } -SystemManager::~SystemManager() { - delete[] this->buf_88; -} +SystemManager::~SystemManager() { delete[] this->buf_88; } void SystemManager::LoadSymbols(OSModuleInfo* pModuleInfo) { - pModuleInfo = pModuleInfo != nullptr ? pModuleInfo : this->_B0; // r5 - - if (pModuleInfo == nullptr) - return; - - if (MAIN_SMAP != nullptr) // "/rel/RevoKartR.SMAP" - { - u32 map_size; - mpMap = SystemManager::sInstance->ripFromDisc(MAIN_SMAP, pModuleInfo, 0, &map_size, 0); - if (mpMap != nullptr) { - EGG::Exception::setMapFile(mpMap, map_size, pModuleInfo); - OSReport("[SYS] Load MapFile \"%s\" success.\n", MAIN_SMAP); - } else { - OSReport("[SYS] Load MapFile \"%s\" fail.\n", MAIN_SMAP); - } - } - + pModuleInfo = pModuleInfo != nullptr ? pModuleInfo : this->_B0; // r5 + + if (pModuleInfo == nullptr) + return; + + if (MAIN_SMAP != nullptr) // "/rel/RevoKartR.SMAP" + { + u32 map_size; + mpMap = SystemManager::sInstance->ripFromDisc(MAIN_SMAP, pModuleInfo, 0, + &map_size, 0); + if (mpMap != nullptr) { + EGG::Exception::setMapFile(mpMap, map_size, pModuleInfo); + OSReport("[SYS] Load MapFile \"%s\" success.\n", MAIN_SMAP); + } else { + OSReport("[SYS] Load MapFile \"%s\" fail.\n", MAIN_SMAP); + } + } + buf_88->_0 = 0; - buf_8C->_0 = 0; - buf_90->_0 = 0; + buf_8C->_0 = 0; + buf_90->_0 = 0; } } // namespace System From 0050e14a09822fd34764aaced742f7320f7aacd4 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 6 Jun 2021 12:32:26 +0200 Subject: [PATCH 071/477] Add GitHub build workflow (#3) * Add GitHub Actions build workflow * Fetch tooling from URL, remove encryption --- .github/workflows/build.yml | 41 +++++++++++++++++++++++++++++++++++++ .gitignore | 5 ++++- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..73dddcf22 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,41 @@ +--- +on: [push] +name: Build +jobs: + build: + runs-on: windows-2019 + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + + - name: Install dependencies + run: | + pip3 install capstone pyelftools + + - name: Percent decompiled + run: python3 percent_decompiled.py + + - name: Extract tooling + env: + TOOLS_URL: ${{ secrets.TOOLS_URL }} + run: | + # Download tooling + Invoke-WebRequest "$Env:TOOLS_URL" -OutFile .\tools.7z + $hash = (Get-FileHash '.\tools.7z').Hash + if ($hash -ne '0AC8F3CE1AA968EB83400D080519472EC590FBF613E5D55A204D6CF946012FAB') + { + echo "Invalid hash: $hash" + exit 1 + } + # Extract using 7-zip + choco install 7zip + 7z.exe x tools.7z -aoa -otools/ + + - name: Build mkw + env: + DEVKITPPC: '.\tools\devkitppc' + run: python3 build.py diff --git a/.gitignore b/.gitignore index b40537592..5b62afff8 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ out *.pyc tmp -*.rel \ No newline at end of file +*.rel + +# Compiler tools bundle +tools.7z From 3996f20916e35aca9d9a38148ff8c3ab6cf4f1bb Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 6 Jun 2021 13:24:49 +0200 Subject: [PATCH 072/477] build.py: auto-discover devkitPPC (#5) --- .gitignore | 1 + build.py | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 5b62afff8..61254c809 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ mwasmeppc.exe mwcceppc.exe mwldeppc.exe makedol.exe +powerpc-eabi-as.exe *.out *.app diff --git a/build.py b/build.py index 1f75f074c..f30f41134 100644 --- a/build.py +++ b/build.py @@ -1,3 +1,10 @@ +import io +import os +import os.path +from pathlib import Path +import struct +import sys + class Segment: def __init__(self, begin : int, end : int): @@ -13,13 +20,10 @@ def empty(self): def size(self): return self.end - self.begin -import struct + def read_u32(f): return struct.unpack(">I", f.read(4))[0] - -import sys - def native_binary(path): if sys.platform == "win32" or sys.platform == "msys": return path + ".exe" @@ -32,11 +36,18 @@ def windows_binary(path): else: return "wine " + path -import os VERBOSE = False -DEVKITPPC = os.environ["DEVKITPPC"] +DEVKITPPC = os.environ.get("DEVKITPPC") +if DEVKITPPC is None: + # devkitPPC not specified in env. + # Default to ./tools/devkitppc + DEVKITPPC = Path().joinpath("tools", "devkitppc") + if not os.path.isdir(DEVKITPPC): + print(f'Could not find devkitPPC under "{DEVKITPPC}" and $DEVKITPPC var is not set.', file=sys.stderr) + sys.exit(1) + GAS = native_binary(os.path.join(DEVKITPPC, "bin", "powerpc-eabi-as")) @@ -137,7 +148,6 @@ def make_obj(src): return src def gen_lcf(src, dst, o_files): - from pathlib import Path lcf = "" with open(src, 'r') as f: @@ -165,8 +175,6 @@ def segment_is_bss(segment): return segment["p_filesz"] == 0 def write_to_dol_header(dol_file, offset, val): - import io - dol_file.seek(offset) dol_file.write(val.to_bytes(4, byteorder='big')) dol_file.seek(0, io.SEEK_END) @@ -339,7 +347,6 @@ def compile_sources(): with open('sources.py', 'r') as sourcespy: exec(sourcespy.read()) - from pathlib import Path asm_files = [str(x.relative_to(os.getcwd())) for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] require_folder("out") From 250b797750ff1c1fcfa2c34d10344fdb79ca25c2 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 6 Jun 2021 14:03:31 +0200 Subject: [PATCH 073/477] GitHub Actions: use non-secret tools URL, check PRs (#6) --- .github/workflows/build.yml | 4 ++-- .github/workflows/lint.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73dddcf22..a36b8d767 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,5 +1,5 @@ --- -on: [push] +on: [push, pull_request] name: Build jobs: build: @@ -21,7 +21,7 @@ jobs: - name: Extract tooling env: - TOOLS_URL: ${{ secrets.TOOLS_URL }} + TOOLS_URL: 'http://163.172.81.216:12369/tools.7z' run: | # Download tooling Invoke-WebRequest "$Env:TOOLS_URL" -OutFile .\tools.7z diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9beb98c57..fc8f063a5 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,5 @@ --- -on: [push] +on: [push, pull_request] name: Lint jobs: lint-clang-format: From e2ab03deac25de56a28cc206e1e37558ef44ace8 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Mon, 7 Jun 2021 02:11:42 +0200 Subject: [PATCH 074/477] Minor Python cleanup, implement nw4r::ut List (#7) * Minor Python cleanup - Mention rel_repack.py in README - Use standard library CSV parser - Make system/gen_asm,rel_repack agnostic to cwdir * Implement nw4r::ut::List * Progress nw4r::ut List --- .gitignore | 1 + README.md | 3 +- build.py | 9 +++- link.lcf | 4 -- o_files.txt | 2 + percent_decompiled.py | 12 ++--- source/nw4r/ut/utList.cpp | 107 ++++++++++++++++++++++++++++++++++++++ source/nw4r/ut/utList.hpp | 14 ++--- sources.py | 3 ++ system/gen_asm.py | 36 +++++-------- system/rel_repack.py | 8 ++- system/slices.csv | 1 + 12 files changed, 157 insertions(+), 43 deletions(-) create mode 100644 source/nw4r/ut/utList.cpp diff --git a/.gitignore b/.gitignore index 61254c809..6ae219dfd 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,7 @@ mwcceppc.exe mwldeppc.exe makedol.exe powerpc-eabi-as.exe +elf2dol.exe *.out *.app diff --git a/README.md b/README.md index 23d735afd..690a8dc90 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,9 @@ Every fully understood piece of reverse engineered data has been documented in a - DevKitPro (for the ppc-eabi assembler) - Python3: - `pip insall pyelftools` - - `pip install capstone` (for `gen_asm.py` only) + - `pip install capstone` - Place a copy of Mario Kart Wii's PAL .dol file in `artifacts/pal` as `main.dol` and REL as `StaticR.rel`. (`gen_asm.py` only) +- Run `python3 system/rel_repack.py` to dump binary blobs for further tooling. ## Building - Place the specified CodeWarrior versions in the respective subfolders in the `tools` directory. Then run `python3 build.py` diff --git a/build.py b/build.py index f30f41134..d775961a5 100644 --- a/build.py +++ b/build.py @@ -3,6 +3,7 @@ import os.path from pathlib import Path import struct +import subprocess import sys @@ -119,8 +120,12 @@ def compile_source(src, dst, version='default', additional='-ipa file'): if VERBOSE: print(command) - os.system(command + " > ./tmp/compiler_log.txt") - print(open("./tmp/compiler_log.txt").read().replace("source\\", "")) + with open('./tmp/compiler_log.txt', 'w+') as f: + try: + subprocess.run(command, stdout=f, check=True) + finally: + f.seek(0, 0) + print(f.read().replace("source\\", "").strip()) # postprocess(dst) def command(cmd): diff --git a/link.lcf b/link.lcf index 4ab1eec02..cd3cd5447 100644 --- a/link.lcf +++ b/link.lcf @@ -48,10 +48,6 @@ CXInitUncompContextLZ=0x8015BEF0; CXReadUncompLZ=0x8015BF24; CXGetUncompressedSize=0x8015C2E0; -List_Remove__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AF110; -List_Append__Q24nw4r2utFPQ34nw4r2ut4ListPv=0x800AEF80; -List_Init__Q24nw4r2utFPQ34nw4r2ut4ListUs=0x800AEF60; -List_GetNext__Q24nw4r2utFPCQ34nw4r2ut4ListPCv=0x800AF180; OSInitMutex=0x801A7EAC; OSLockMutex=0x801A7EE4; diff --git a/o_files.txt b/o_files.txt index 04f9b581b..cf519c256 100644 --- a/o_files.txt +++ b/o_files.txt @@ -1,4 +1,6 @@ dol/text_800072c0.o +utList.o +dol/text_800af1a0.o dol/data_80258580.o dol/sbss_80385fc0.o dwc_error.o diff --git a/percent_decompiled.py b/percent_decompiled.py index 3f44af682..260820a46 100644 --- a/percent_decompiled.py +++ b/percent_decompiled.py @@ -1,3 +1,5 @@ +import csv + def process_line(tags, items): name = "Untitled" start = None @@ -53,12 +55,10 @@ def simple_count(path): def segments_of(path): with open(path, 'r') as file: - for line in file.readlines()[1:]: - seg, segclass, start, end = line.split(',') - - is_code = 'text' in seg - - yield is_code, int(end, 16) - int(start, 16) + reader = csv.DictReader(file) + for line in reader: + is_code = 'text' in line["name"] + yield is_code, int(line["end"], 16) - int(line["start"], 16) def binary_total(path): segments = list(segments_of(path)) diff --git a/source/nw4r/ut/utList.cpp b/source/nw4r/ut/utList.cpp new file mode 100644 index 000000000..67d6e3a1f --- /dev/null +++ b/source/nw4r/ut/utList.cpp @@ -0,0 +1,107 @@ +#include "utList.hpp" + +namespace nw4r { +namespace ut { + +#define LIST_GET_ELEM(list, obj) \ + ((nw4r::ut::Node*)(((u32)(obj)) + (list)->intrusion_offset)) + +void List_Init(List* pList, u16 intrusion_offset) { + pList->head = nullptr; + pList->tail = nullptr; + pList->count = 0; + pList->intrusion_offset = intrusion_offset; +} + +void List_Append(List* pList, void* pObj) { + if (pList->head == nullptr) { + // Node is the only element in the list. + // Therefore it is both head and tail. + nw4r::ut::Node* node = LIST_GET_ELEM(pList, pObj); + node->succ = nullptr; + node->pred = nullptr; + pList->head = pObj; + pList->tail = pObj; + pList->count++; + } else { + // Link node to end of list. + nw4r::ut::Node* node = LIST_GET_ELEM(pList, pObj); + node->pred = pList->tail; + node->succ = nullptr; + // Link second to last node to new tail. + LIST_GET_ELEM(pList, pList->tail)->succ = pObj; + // Set new tail. + pList->tail = pObj; + pList->count++; + } +} + +static void List_Prepend(List* pList, void* pObj) { + if (pList->head == nullptr) { + // Node is the only element in the list. + // Therefore it is both head and tail. + nw4r::ut::Node* node = LIST_GET_ELEM(pList, pObj); + node->succ = nullptr; + node->pred = nullptr; + pList->head = pObj; + pList->tail = pObj; + pList->count++; + } else { + // Link node to start of list. + nw4r::ut::Node* node = LIST_GET_ELEM(pList, pObj); + node->pred = nullptr; + node->succ = pList->head; + // Link second node to new head. + LIST_GET_ELEM(pList, pList->head)->pred = pObj; + // Set new head. + pList->head = pObj; + pList->count++; + } +} + +void List_Insert(List* pList, void* pTgt, void* pObj) { + if (pTgt == nullptr) { + // A null target means inserting at the end of the list. + List_Append(pList, pObj); + } else if (pTgt == pList->head) { + List_Prepend(pList, pObj); + } else { + nw4r::ut::Node* node = LIST_GET_ELEM(pList, pObj); + void* pred = LIST_GET_ELEM(pList, pTgt)->pred; + nw4r::ut::Node* predNode = LIST_GET_ELEM(pList, pred); + // Update links of node to be inserted. + node->pred = pred; + node->succ = pTgt; + // Update links of neighbors. + predNode->succ = pObj; + LIST_GET_ELEM(pList, pTgt)->pred = pObj; + // Update list. + pList->count++; + } +} + +void List_Remove(List* pList, void* pObj) { + nw4r::ut::Node* node = LIST_GET_ELEM(pList, pObj); + if (node->pred == nullptr) + pList->head = node->succ; // second node becomes list head + else + LIST_GET_ELEM(pList, node->pred)->succ = node->succ; // punch hole in list + if (node->succ == nullptr) + pList->tail = node->pred; // second to last node becomes list tail + else + LIST_GET_ELEM(pList, node->succ)->pred = node->pred; // punch hole in list + // Reset node. + node->pred = nullptr; + node->succ = nullptr; + // Update list. + pList->count--; +} + +void* List_GetNext(const nw4r::ut::List* pList, const void* pObj) { + if (pObj == nullptr) + return pList->head; + return LIST_GET_ELEM(pList, pObj)->succ; +} + +} // namespace ut +} // namespace nw4r diff --git a/source/nw4r/ut/utList.hpp b/source/nw4r/ut/utList.hpp index d12d9afea..76fe917a6 100644 --- a/source/nw4r/ut/utList.hpp +++ b/source/nw4r/ut/utList.hpp @@ -2,9 +2,8 @@ #include -// clang-format off -namespace nw4r { namespace ut { -// clang-format on +namespace nw4r { +namespace ut { //! Bidirectional list node struct Node { @@ -21,15 +20,16 @@ struct List { u16 intrusion_offset; }; -// void List_Init(List* pList, u16 intrusion_offset); void List_Append(List* pList, void* pObj); +void List_Insert(List* pList, void* pTgt, void* pObj); void List_Remove(List* pList, void* pObj); void* List_GetNext(const List*, const void*); inline void* List_GetFirst(const List* pList) { return List_GetNext(pList, nullptr); } +// Seems to be not included as a symbol. Only inlined. +//void List_Prepend(List* pList, void* pObj); -// clang-format off -} } // namespace nw4r -// clang-format on +} // namespace ut +} // namespace nw4r diff --git a/sources.py b/sources.py index 0cc65cba2..5607bdf0a 100644 --- a/sources.py +++ b/sources.py @@ -1,10 +1,13 @@ RVL_OPTS = '-ipa file' EGG_OPTS = '-ipa function -rostr' REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' +NW4R_OPTS = '-ipa file -inline auto -rostr -O4,p' compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) +compile_source("source/nw4r/ut/utList.cpp", "out/utList.o", '4201_127', NW4R_OPTS) + compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) compile_source("source/egg/core/eggAllocator.cpp", "out/eggAllocator.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") diff --git a/system/gen_asm.py b/system/gen_asm.py index 6b5fb29df..4720f4e79 100644 --- a/system/gen_asm.py +++ b/system/gen_asm.py @@ -1,12 +1,17 @@ +import csv +import os +import shutil import struct import sys -import shutil -import os from ppc_dis import * from contextlib import redirect_stdout +# Change directory to script folder to make relative repo paths work. +os.chdir(sys.path[0]) + + def read_u8(f): return struct.unpack("B", f.read(1))[0] @@ -16,23 +21,6 @@ def read_u32(f): def read_u16(f): return struct.unpack(">H", f.read(2))[0] -class CSV: - def __init__(self, name): - lines = open(name).readlines() - self.header = lines[0].strip().split(',') - self.body = [x.strip().split(',') for x in lines[1:]] - - def get_row(self, index): - row = {} - for i, value in enumerate(self.body[index]): - label = self.header[i] - row[label] = value - return row - - def rows(self): - for i in range(len(self.body)): - yield self.get_row(i) - class Segment: def __init__(self, begin : int, end : int): assert isinstance(begin, int) and isinstance(end, int) @@ -49,8 +37,10 @@ def size(self): return self.end - self.begin def read_segments_iter(name): - for row in CSV(name).rows(): - yield row["name"], Segment(int(row["start"], 16), int(row["end"], 16)) + with open(name) as f: + reader = csv.DictReader(f) + for row in reader: + yield row["name"], Segment(int(row["start"], 16), int(row["end"], 16)) def read_segments(name): result = {} @@ -68,7 +58,9 @@ def __repr__(self): # Limitation: slices must be ordered def read_slices(name): - for row in CSV(name).rows(): + lines = open(name).readlines() + reader = csv.DictReader(lines) + for row in reader: if not row.pop("enabled"): continue diff --git a/system/rel_repack.py b/system/rel_repack.py index 92e97a91d..d9f3f1a88 100644 --- a/system/rel_repack.py +++ b/system/rel_repack.py @@ -1,6 +1,12 @@ -import struct, os, io +import io +import os +import struct +import sys from collections import OrderedDict +# Change directory to script folder to make relative repo paths work. +os.chdir(sys.path[0]) + REL_BASE = 0x805102e0 ul = lambda f : struct.unpack(">L", f.read(4))[0] diff --git a/system/slices.csv b/system/slices.csv index ab79ca69b..2f26bb67b 100644 --- a/system/slices.csv +++ b/system/slices.csv @@ -1,4 +1,5 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End +1,utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, 1,dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, 1,rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, ,rxFrameHeap.c,,,,,,,80199430,801998A4,,,,,,,,,,,,,,,,,, From e7b21bc56645374d7fc52b678c1c58d9ac9874be Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 19 Jun 2021 18:41:26 +0200 Subject: [PATCH 075/477] Python Refactor (#13) * Project restructuring * Fix README.md * Fix README.md * Fix README.md * Add structure.md --- .editorconfig | 16 + .github/workflows/build.yml | 10 +- .gitignore | 13 + README.md | 20 +- .../orig/pal/rel}/dol_rel.bin | Bin .../orig/pal/rel}/rel_abs.bin | Bin build.py | 588 +++++------------- mkwutil/dol.py | 81 +++ mkwutil/dump_dol_header.py | 26 + {system => mkwutil}/gen_asm.py | 303 ++++----- mkwutil/gen_lcf.py | 46 ++ mkwutil/pack_main_dol.py | 104 ++++ mkwutil/pack_staticr_rel.py | 118 ++++ mkwutil/percent_decompiled.py | 143 +++++ mkwutil/ppc_dis.py | 387 ++++++++++++ mkwutil/rel.py | 375 +++++++++++ {system => mkwutil}/rel_header.bin | Bin mkwutil/rel_repack.py | 42 ++ mkwutil/verify_main_dol.py | 108 ++++ mkwutil/verify_staticr_rel.py | 33 + o_files.txt | 66 -- link.lcf => pack/dol.base.lcf | 15 +- pack/dol_objects.txt | 66 ++ .../pal/segments.csv => pack/dol_segments.csv | 0 pack/dol_slices.csv | 22 + rel_link.lcf => pack/rel.base.lcf | 0 pack/rel_objects.txt | 12 + {artifacts/pal => pack}/rel_segments.csv | 0 pack/rel_slices.csv | 3 + percent_decompiled.py | 122 ---- structure.md | 59 ++ system/ppc_dis.py | 276 -------- system/rel_repack.py | 377 ----------- system/rel_slices.csv | 3 - system/slices.csv | 22 - 35 files changed, 1996 insertions(+), 1460 deletions(-) create mode 100644 .editorconfig rename {system => artifacts/orig/pal/rel}/dol_rel.bin (100%) rename {system => artifacts/orig/pal/rel}/rel_abs.bin (100%) create mode 100644 mkwutil/dol.py create mode 100644 mkwutil/dump_dol_header.py rename {system => mkwutil}/gen_asm.py (54%) create mode 100644 mkwutil/gen_lcf.py create mode 100644 mkwutil/pack_main_dol.py create mode 100644 mkwutil/pack_staticr_rel.py create mode 100644 mkwutil/percent_decompiled.py create mode 100644 mkwutil/ppc_dis.py create mode 100644 mkwutil/rel.py rename {system => mkwutil}/rel_header.bin (100%) create mode 100644 mkwutil/rel_repack.py create mode 100644 mkwutil/verify_main_dol.py create mode 100644 mkwutil/verify_staticr_rel.py delete mode 100644 o_files.txt rename link.lcf => pack/dol.base.lcf (93%) create mode 100644 pack/dol_objects.txt rename artifacts/pal/segments.csv => pack/dol_segments.csv (100%) create mode 100644 pack/dol_slices.csv rename rel_link.lcf => pack/rel.base.lcf (100%) create mode 100644 pack/rel_objects.txt rename {artifacts/pal => pack}/rel_segments.csv (100%) create mode 100644 pack/rel_slices.csv delete mode 100644 percent_decompiled.py create mode 100644 structure.md delete mode 100644 system/ppc_dis.py delete mode 100644 system/rel_repack.py delete mode 100644 system/rel_slices.csv delete mode 100644 system/slices.csv diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..4d7ddb500 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +end_of_line = crlf + +[*.{h,c,hpp,cpp}] +indent_style = space +indent_size = 2 + +[*.py] +indent_style = space +indent_size = 4 + +[Makefile] +indent_style = tab +indent_size = 4 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a36b8d767..ee7e0b640 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,11 +13,10 @@ jobs: architecture: 'x64' - name: Install dependencies - run: | - pip3 install capstone pyelftools + run: pip3 install capstone pyelftools - name: Percent decompiled - run: python3 percent_decompiled.py + run: python3 -m mkwutil.percent_decompiled - name: Extract tooling env: @@ -36,6 +35,5 @@ jobs: 7z.exe x tools.7z -aoa -otools/ - name: Build mkw - env: - DEVKITPPC: '.\tools\devkitppc' - run: python3 build.py + shell: bash + run: python3 ./build.py diff --git a/.gitignore b/.gitignore index 6ae219dfd..8334d75ea 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,10 @@ *.a *.lib +# Auto-generated linker scripts +/pack/dol.lcf +/pack/rel.lcf + # Executables mwasmeppc.exe mwcceppc.exe @@ -45,9 +49,18 @@ out # Python cache/compiled modules *.pyc +__pycache__/ tmp *.rel +/artifacts/orig/pal/rel/*.bin +!/artifacts/orig/pal/rel/dol_rel.bin +!/artifacts/orig/pal/rel/rel_abs.bin # Compiler tools bundle tools.7z + +# Editors +.vscode/ +*.bndb +.~lock.* diff --git a/README.md b/README.md index 690a8dc90..e4e617312 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,27 @@ Every fully understood piece of reverse engineered data has been documented in a ## Dependencies - DevKitPro (for the ppc-eabi assembler) +- CodeWarrior compilers (in `tools`) - Python3: - `pip insall pyelftools` - `pip install capstone` -- Place a copy of Mario Kart Wii's PAL .dol file in `artifacts/pal` as `main.dol` and REL as `StaticR.rel`. (`gen_asm.py` only) -- Run `python3 system/rel_repack.py` to dump binary blobs for further tooling. +- Place a copy of Mario Kart Wii's PAL binaries: + - `artifacts/orig/pal/main.dol` + - `artifacts/orig/pal/StaticR.rel` ## Building -- Place the specified CodeWarrior versions in the respective subfolders in the `tools` directory. Then run `python3 build.py` + +Run `python3 ./build.py` to build the game and verify build authenticity. Final results: + - `artifacts/target/pal/main.dol` + - `artifacts/target/pal/StaticR.rel` ## Contributing -- Do not manually adjust assembly files; rather add a new entry to [system/slices.csv](https://github.com/riidefi/mkw/blob/master/system/slices.csv) for main.dol and [system/rel_slices.csv](https://github.com/riidefi/mkw/blob/master/system/rel_slices.csv) for StaticR.rel. +- Do not manually adjust assembly (`asm`) files. They are auto-generated. +- To add a new decompiled section, modify the slice tables: + - [pack/dol_slices.csv](./pack/dol_slices.csv) + - [pack/rel_slices.csv](./pack/rel_slices.csv) - Entries must be sorted in the spreadsheet (current limitation). -- Run `gen_asm.py` from the `system` directory to regenerate assembly segments every time either file is editeds. +- After modifying slices, run `python3 -m mkwutil.gen_asm`. ## .rel support -Most of Mario Kart Wii's game code is located inside a relocatable module (StaticR.rel for release builds). The decompilation builds this. \ No newline at end of file +Most of Mario Kart Wii's game code is located inside a relocatable module (StaticR.rel for release builds). The decompilation builds this. diff --git a/system/dol_rel.bin b/artifacts/orig/pal/rel/dol_rel.bin similarity index 100% rename from system/dol_rel.bin rename to artifacts/orig/pal/rel/dol_rel.bin diff --git a/system/rel_abs.bin b/artifacts/orig/pal/rel/rel_abs.bin similarity index 100% rename from system/rel_abs.bin rename to artifacts/orig/pal/rel/rel_abs.bin diff --git a/build.py b/build.py index d775961a5..b1ace0bc2 100644 --- a/build.py +++ b/build.py @@ -1,53 +1,42 @@ -import io import os import os.path from pathlib import Path -import struct import subprocess import sys +from mkwutil.gen_lcf import gen_lcf +from mkwutil.pack_main_dol import pack_main_dol +from mkwutil.pack_staticr_rel import pack_staticr_rel +from mkwutil.verify_main_dol import verify_dol +from mkwutil.verify_staticr_rel import verify_rel +from mkwutil.percent_decompiled import percent_decompiled -class Segment: - def __init__(self, begin : int, end : int): - assert isinstance(begin, int) and isinstance(end, int) - self.begin = begin - self.end = end - - def __repr__(self): - return "(%s, %s)" % (hex(self.begin), hex(self.end)) - - def empty(self): - return self.begin == self.end - - def size(self): - return self.end - self.begin - -def read_u32(f): - return struct.unpack(">I", f.read(4))[0] def native_binary(path): - if sys.platform == "win32" or sys.platform == "msys": - return path + ".exe" - else: - return path + if sys.platform == "win32" or sys.platform == "msys": + return path + ".exe" + return path + def windows_binary(path): - if sys.platform == "win32" or sys.platform == "msys": - return path - else: - return "wine " + path + if sys.platform == "win32" or sys.platform == "msys": + return path + return "wine " + path VERBOSE = False DEVKITPPC = os.environ.get("DEVKITPPC") if DEVKITPPC is None: - # devkitPPC not specified in env. - # Default to ./tools/devkitppc - DEVKITPPC = Path().joinpath("tools", "devkitppc") - if not os.path.isdir(DEVKITPPC): - print(f'Could not find devkitPPC under "{DEVKITPPC}" and $DEVKITPPC var is not set.', file=sys.stderr) - sys.exit(1) + # devkitPPC not specified in env. + # Default to ./tools/devkitppc + DEVKITPPC = Path().joinpath("tools", "devkitppc") + if not os.path.isdir(DEVKITPPC): + print( + f'Could not find devkitPPC under "{DEVKITPPC}" and $DEVKITPPC var is not set.', + file=sys.stderr, + ) + sys.exit(1) GAS = native_binary(os.path.join(DEVKITPPC, "bin", "powerpc-eabi-as")) @@ -55,421 +44,160 @@ def windows_binary(path): MWLD = windows_binary(os.path.join("tools", "mwldeppc.exe")) CWCC_PATHS = { - 'default': windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), - - # For the main game - # August 17, 2007 - # 4.2.0.1 Build 127 - # - # Ideally we would use this version - # We don't have this, so we use build 142: - # This version has the infuriating bug where random - # nops are inserted into your code. - '4201_127': windows_binary(os.path.join(".", "tools", "4201_142", "mwcceppc.exe")), - - # For most of RVL - # We actually have the correct version - '4199_60831': windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), - - # For HBM/WPAD, NHTTP/SSL - # We use build 60831 - '4199_60726': windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), + "default": windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), + # For the main game + # August 17, 2007 + # 4.2.0.1 Build 127 + # + # Ideally we would use this version + # We don't have this, so we use build 142: + # This version has the infuriating bug where random + # nops are inserted into your code. + "4201_127": windows_binary(os.path.join(".", "tools", "4201_142", "mwcceppc.exe")), + # For most of RVL + # We actually have the correct version + "4199_60831": windows_binary( + os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") + ), + # For HBM/WPAD, NHTTP/SSL + # We use build 60831 + "4199_60726": windows_binary( + os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") + ), } -CWCC_OPT = " ".join([ - "-nodefaults", - "-align powerpc", - "-enc SJIS", - "-c", - # "-I-", - "-gccinc", - "-i ./source/ -i ./source/platform", - # "-inline deferred", - "-proc gekko", - "-enum int", - "-O4,p", - "-inline auto", - "-W all", - "-fp hardware", - "-Cpp_exceptions off", - "-RTTI off", - "-pragma \"cats off\"", # ??? - # "-pragma \"aggressive_inline on\"", - # "-pragma \"auto_inline on\"", - "-inline auto", - "-w notinlined -W noimplicitconv", - "-nostdinc", - "-msgstyle gcc -lang=c99 -DREVOKART", - "-func_align 4" -]) - -def require_folder(path): - try: - os.mkdir(path) - except FileExistsError: - pass - -def postprocess(dst): - command("python tools/postprocess.py -fsymbol-fixup %s" % dst) - -def compile_source(src, dst, version='default', additional='-ipa file'): - try: - os.mkdir("tmp") - except: pass - command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" - - if VERBOSE: - print(command) - - with open('./tmp/compiler_log.txt', 'w+') as f: - try: - subprocess.run(command, stdout=f, check=True) - finally: - f.seek(0, 0) - print(f.read().replace("source\\", "").strip()) - # postprocess(dst) - -def command(cmd): - if VERBOSE: - print(cmd) - os.system(cmd) +CWCC_OPT = " ".join( + [ + "-nodefaults", + "-align powerpc", + "-enc SJIS", + "-c", + # "-I-", + "-gccinc", + "-i ./source/ -i ./source/platform", + # "-inline deferred", + "-proc gekko", + "-enum int", + "-O4,p", + "-inline auto", + "-W all", + "-fp hardware", + "-Cpp_exceptions off", + "-RTTI off", + '-pragma "cats off"', # ??? + # "-pragma \"aggressive_inline on\"", + # "-pragma \"auto_inline on\"", + "-inline auto", + "-w notinlined -W noimplicitconv", + "-nostdinc", + "-msgstyle gcc -lang=c99 -DREVOKART", + "-func_align 4", + ] +) + + +def compile_source(src, dst, version="default", additional="-ipa file"): + command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" + + if VERBOSE: + print(command) + + subprocess.run(command, check=True) + def assemble(dst, src): - # print(dst, src) - cmd = GAS + " %s -mgekko -Iasm -o %s" % (src, dst) - command(cmd) + subprocess.run([GAS, src, "-mgekko", "-Iasm", "-o", dst], check=True, text=True) + def link(dst, objs, lcf, partial=False): - cmd = MWLD + " %s -o %s -lcf %s -fp hard" % (' '.join(objs), dst, lcf) - if partial: - cmd += " -r " - command(cmd) + cmd = [MWLD] + objs + ["-o", dst, "-lcf", lcf, "-fp", "hard"] + if partial: + cmd.append("-r") + subprocess.run(cmd, check=True, text=True) + def make_obj(src): - substitutions = ( - '.cpp', '.asm', '.c', '.s' - ) - for s in substitutions: - src = src.replace(s, '.o') - return src - -def gen_lcf(src, dst, o_files): - lcf = "" - - with open(src, 'r') as f: - lcf = f.read() - lcf += "\nFORCEFILES {\n" - for x in o_files: - # TODO: Add ability to disable FORCEFILE to slices.csv - if 'eggVideo' in x: - continue - lcf += Path(x).stem + ".o\n" - lcf += "}\n" - - with open(dst, 'w') as f: - f.write(lcf) - -def segment_is_text(segment): - from elftools.elf.constants import P_FLAGS - - return segment["p_flags"] & P_FLAGS.PF_X == P_FLAGS.PF_X - -def segment_is_data(segment): - return not segment_is_text(segment) and not segment_is_bss(segment) - -def segment_is_bss(segment): - return segment["p_filesz"] == 0 - -def write_to_dol_header(dol_file, offset, val): - dol_file.seek(offset) - dol_file.write(val.to_bytes(4, byteorder='big')) - dol_file.seek(0, io.SEEK_END) - -def write_segment_to_dol(idx, segment, dol_file): - write_to_dol_header(dol_file, 0x00 + 0x04 * idx, dol_file.tell()) - write_to_dol_header(dol_file, 0x48 + 0x04 * idx, segment["p_vaddr"]) - # align filesz to 0x20 - filesz = ((segment["p_filesz"] + 0x1f) >> 5) << 5 - write_to_dol_header(dol_file, 0x90 + 0x04 * idx, filesz) - - dol_file.write(segment.data()) - # align current dol size to 0x20 - size = 0x20 - dol_file.tell() & 0x1f - dol_file.write(bytes([0x00] * size)) - -def elf_to_dol(elf_path, dol_path): - from elftools.elf.elffile import ELFFile - - with open(elf_path, 'rb') as elf_file, open(dol_path, 'wb') as dol_file: - elf = ELFFile(elf_file) - num_segments = elf.num_segments() - - dol_file.write(bytes([0x00] * 0x100)) - - idx = 0 - for i in range(num_segments): - segment = elf.get_segment(i) - if not segment_is_text(segment): - continue - write_segment_to_dol(idx, segment, dol_file) - idx += 1 - - idx = 7 - for i in range(num_segments): - segment = elf.get_segment(i) - if not segment_is_data(segment): - continue - write_segment_to_dol(idx, segment, dol_file) - idx += 1 - - bss_start = 0 - bss_end = 0 - for i in range(num_segments): - segment = elf.get_segment(i) - if not segment_is_bss(segment): - continue - if bss_start == 0: - bss_start = segment["p_vaddr"] - bss_end = segment["p_vaddr"] + segment["p_memsz"] - write_to_dol_header(dol_file, 0xd8, bss_start) - bss_size = bss_end - bss_start - write_to_dol_header(dol_file, 0xdc, bss_size) - - write_to_dol_header(dol_file, 0xe0, elf["e_entry"]) - -def read_elf_sec(elf, name): - from system.rel_repack import RelSection - - elf_sec = elf.get_section_by_name(name) - - data = elf_sec.data() - - t = RelSection() - t.data = bytearray(data) - t.length = len(data) - - # Added - t.name = name - - return t - -R_PPC_NONE = 0 -R_PPC_ADDR32 = 1 -R_PPC_ADDR24 = 2 -R_PPC_ADDR16 = 3 -R_PPC_ADDR16_LO = 4 -R_PPC_ADDR16_HI = 5 -R_PPC_ADDR16_HA = 6 -R_PPC_ADDR14 = 7 -R_PPC_ADDR14_BRTAKEN = 8 -R_PPC_ADDR14_BRNKTAKEN = 9 -R_PPC_REL24 = 10 -R_PPC_REL14 = 11 - -R_PPC_REL32 = 26 - -def build_elf(rel_path, elf_path): - from system.rel_repack import Rel - - # Hack: for header.bin - os.chdir("system") - - rel = Rel() - os.chdir("..") - - rel.load_reloc(0, "system/dol_rel.bin") - rel.load_reloc(1, "system/rel_abs.bin") - - from elftools.elf.elffile import ELFFile - - with open(elf_path, 'rb') as f: - elf = ELFFile(f) - - rel.sectionInfo[1] = read_elf_sec(elf, ".text") - rel.sectionInfo[2] = read_elf_sec(elf, ".ctors") - rel.sectionInfo[3] = read_elf_sec(elf, ".dtors") - rel.sectionInfo[4] = read_elf_sec(elf, ".rodata") - rel.sectionInfo[5] = read_elf_sec(elf, ".data") - rel.sectionInfo[6] = read_elf_sec(elf, ".bss") - - # .rodata padding hack - rodata = rel.sectionInfo[4] - rodata.data = rodata.data[:-16] - rodata.length = len(rodata.data) - - # Jump to _Unresolved - _Unresolved = 0x805553B0 - text = rel.sectionInfo[1] - relocs = elf.get_section_by_name(".rela.text") - if relocs: - for reloc_acc in relocs.iter_relocations(): - reloc = reloc_acc.entry - if reloc.r_info_type == R_PPC_REL24: - instruction = struct.unpack(">I", text.data[reloc.r_offset : reloc.r_offset + 4])[0] - instruction_addr = 0x805103B4 + reloc.r_offset - - delta = _Unresolved - instruction_addr - new_ins = (instruction & ~0x03fffffc) | (delta & 0x03fffffc) - - # print(hex(instruction)) - # print(hex(new_ins)) - - packed = struct.pack(">I", new_ins) - for i in range(4): - text.data[reloc.r_offset + i] = packed[i] - - rel.sectionInfo[1].executable = True - rel.sectionInfo[1].offset = 0xD4 - rel.sectionInfo[2].offset = 0x37F120 - rel.sectionInfo[3].offset = 0x37F424 - rel.sectionInfo[4].offset = 0x37F440 - rel.sectionInfo[5].offset = 0x3A28F0 - rel.sectionInfo[6].offset = 0 - - # This seems to affect tz, but absolutely no one else? - # Hack: Fix .bss size - rel.sectionInfo[6].length = 0x78b0 - - rel.imps[0].moduleId = 1 - rel.imps[0].offset = 0x3CD104 - rel.imps[1].moduleId = 0 - rel.imps[1].offset = 0x4820F4 - - rel.reconstruct(rel_path) - - # Debug - for i in range(1, 6+1): - sec = rel.sectionInfo[i] - - dest_path = "tmp/" + sec.name[1:] + "_rebuilt.bin" - - with open(dest_path, 'wb') as f: - f.write(sec.data) + substitutions = (".cpp", ".asm", ".c", ".s") + for sub in substitutions: + src = src.replace(sub, ".o") + return src + def compile_sources(): - require_folder("out") + out_dir = Path("out") + out_dir.mkdir(exist_ok=True) + + # compile sources + # TODO exec() is bad practice + with open("sources.py", "r") as sourcespy: + exec(sourcespy.read()) - # import sources - with open('sources.py', 'r') as sourcespy: - exec(sourcespy.read()) + asm_files = [ + str(x.relative_to(os.getcwd())) + for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s") + ] - asm_files = [str(x.relative_to(os.getcwd())) for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s")] - - require_folder("out") - require_folder(os.path.join("out", "dol")) - require_folder(os.path.join("out", "rel")) + (out_dir / "dol").mkdir(exist_ok=True) + (out_dir / "rel").mkdir(exist_ok=True) + + for asm in asm_files: + # Hack: Should use pathlib for this + out_o = "out" + asm[len("asm") :] + assemble(make_obj(out_o), asm) - for asm in asm_files: - # Hack: Should use pathlib for this - out_o = "out" + asm[len("asm"):] - assemble(make_obj(out_o), asm) def link_dol(o_files): - gen_lcf("link.lcf", "out/generated.lcf", o_files) - link('out/built.elf', o_files, "out/generated.lcf") + # Generate LCF. + src_lcf_path = Path("pack", "dol.base.lcf") + dst_lcf_path = Path("pack", "dol.lcf") + gen_lcf(src_lcf_path, dst_lcf_path, o_files) + # Create dest dir. + dest_dir = Path("target", "pal") + dest_dir.mkdir(parents=True, exist_ok=True) + # Link ELF. + elf_path = dest_dir / "main.elf" + link(elf_path, o_files, dst_lcf_path) + # Convert ELF to DOL. + dol_path = dest_dir / "main.dol" + pack_main_dol(elf_path, dol_path) + return dol_path + + +def link_rel(o_files): + # Generate LCF. + src_lcf_path = Path("pack", "rel.base.lcf") + dst_lcf_path = Path("pack", "rel.lcf") + gen_lcf(src_lcf_path, dst_lcf_path, o_files) + # Create dest dir. + dest_dir = Path("target", "pal") + dest_dir.mkdir(parents=True, exist_ok=True) + # Link ELF. + elf_path = dest_dir / "StaticR.elf" + link(elf_path, o_files, dst_lcf_path, partial=True) + # Convert ELF to REL. + rel_path = dest_dir / "StaticR.rel" + orig_dir = Path("artifacts", "orig") + pack_staticr_rel(elf_path, rel_path, orig_dir) + return rel_path + - with open('out/built.elf', 'r+b') as elf: - elf.seek(0x18) - elf.write(bytes([0x80, 0x00, 0x60, 0xA4])) +def build(): + Path("target").mkdir(exist_ok=True) - elf_to_dol("out/built.elf", "target/mkw_pal.dol") + dol_objects_path = Path("pack/dol_objects.txt") + rel_objects_path = Path("pack/rel_objects.txt") + dol_objects = open(dol_objects_path, "r").readlines() + rel_objects = open(rel_objects_path, "r").readlines() -def link_rel(rel_o_files): - gen_lcf("rel_link.lcf", "out/rel_generated.lcf", rel_o_files) - link('out/rel_built.elf', rel_o_files, "out/rel_generated.lcf", partial=True) + compile_sources() - build_elf("target/StaticR.rel", "out/rel_built.elf") + orig_dol_path = Path("artifacts", "orig", "pal", "main.dol") + target_dol_path = link_dol(dol_objects) + target_rel_path = link_rel(rel_objects) + + verify_dol(orig_dol_path, target_dol_path) + verify_rel(target_rel_path) + + percent_decompiled() -def build(): - try: - os.mkdir("target") - except: pass - - o_files = ["out/" + x.strip() for x in open('o_files.txt', 'r').readlines()] - rel_o_files = ["out/" + x.strip() for x in open('rel_o_files.txt', 'r').readlines()] - - compile_sources() - - link_dol(o_files) - - link_rel(rel_o_files) - - verify_dol() - - verify_rel() - - import percent_decompiled - -import hashlib - -def verify_rel(): - ctx = hashlib.sha1(open('target/StaticR.rel', 'rb').read()) - digest = ctx.hexdigest() - if digest.lower() == '887bcc076781f5b005cc317a6e3cc8fd5f911300': - print("[REL] Everything went okay! Output is matching! ^^") - return - - print("[REL] Oof: Output doesn't match.") - -def verify_dol(): - ctx = hashlib.sha1(open('target/mkw_pal.dol', 'rb').read()) - digest = ctx.hexdigest() - if digest.lower() == 'ac7d72448630ade7655fc8bc5fd7a6543cb53a49': - print("[DOL] Everything went okay! Output is matching! ^^") - return - - print("[DOL] Oof: Output doesn't match.") - - class DolBinary: - def __init__(self, file): - file = open(file, 'rb') - text_ofs = [read_u32(file) for _ in range(7)] - data_ofs = [read_u32(file) for _ in range(11)] - - text_vaddr = [read_u32(file) for _ in range(7)] - data_vaddr = [read_u32(file) for _ in range(11)] - - self.text_size = [read_u32(file) for _ in range(7)] - self.data_size = [read_u32(file) for _ in range(11)] - - self.text_segs = [Segment(x, x + y) for x, y in zip(text_vaddr, self.text_size)] - self.data_segs = [Segment(x, x + y) for x, y in zip(data_vaddr, self.data_size)] - - bss_vaddr = read_u32(file) - bss_size = read_u32(file) - - self.bss = Segment(bss_vaddr, bss_vaddr + bss_size) - - self.entry_point = read_u32(file) - - max_vaddr = max(x.end for x in self.text_segs + self.data_segs) - self.image_base = 0x80000000 - self.image = bytearray(max_vaddr - self.image_base) - return - for i in range(7): - if not self.text_size[i]: - continue - - file.seek(text_ofs[i]) - data = file.read(self.text_size[i]) - for j in range(self.text_size[i]): - self.image[text_vaddr[i] + j - self.image_base] = data[j] - - for i in range(11): - if not self.data_size[i]: - continue - - file.seek(data_ofs[i]) - data = file.read(self.data_size[i]) - for j in range(self.data_size[i]): - self.image[data_vaddr[i] + j - self.image_base] = data[j] - - good = DolBinary("source/baserom.dol") - bad = DolBinary("target/mkw_pal.dol") - - for i, sizes in enumerate(zip(good.text_size, bad.text_size)): - print(sizes) - for i, sizes in enumerate(zip(good.data_size, bad.data_size)): - print(sizes) - # TODO: Add diff'ing build() diff --git a/mkwutil/dol.py b/mkwutil/dol.py new file mode 100644 index 000000000..ed3dcf19e --- /dev/null +++ b/mkwutil/dol.py @@ -0,0 +1,81 @@ +import struct + + +read_u32 = lambda f: struct.unpack(">L", f.read(4))[0] +read_u16 = lambda f: struct.unpack(">H", f.read(2))[0] +read_u8 = lambda f: struct.unpack(">B", f.read(1))[0] + + +class Segment: + """Describes a DOL segment.""" + + def __init__(self, begin: int, end: int): + assert isinstance(begin, int) and isinstance(end, int) + self.begin = begin + self.end = end + + def __repr__(self): + return "(%s, %s)" % (hex(self.begin), hex(self.end)) + + def empty(self): + return self.begin == self.end + + def size(self): + return self.end - self.begin + + +class DolBinary: + """Describes a DOL executable.""" + + def __init__(self, file): + file = open(file, "rb") + text_ofs = [read_u32(file) for _ in range(7)] + data_ofs = [read_u32(file) for _ in range(11)] + + text_vaddr = [read_u32(file) for _ in range(7)] + data_vaddr = [read_u32(file) for _ in range(11)] + + self.text_size = [read_u32(file) for _ in range(7)] + self.data_size = [read_u32(file) for _ in range(11)] + + self.text_segs = [Segment(x, x + y) for x, y in zip(text_vaddr, self.text_size)] + self.data_segs = [Segment(x, x + y) for x, y in zip(data_vaddr, self.data_size)] + + bss_vaddr = read_u32(file) + bss_size = read_u32(file) + + self.bss = Segment(bss_vaddr, bss_vaddr + bss_size) + + self.entry_point = read_u32(file) + + max_vaddr = max(x.end for x in self.text_segs + self.data_segs) + self.image_base = 0x80000000 + self.image = bytearray(max_vaddr - self.image_base) + + for i in range(7): + if not self.text_size[i]: + continue + file.seek(text_ofs[i]) + offset = text_vaddr[i] - self.image_base + self.image[offset : offset + self.text_size[i]] = file.read( + self.text_size[i] + ) + + for i in range(11): + if not self.data_size[i]: + continue + file.seek(data_ofs[i]) + offset = data_vaddr[i] - self.image_base + self.image[offset : offset + self.data_size[i]] = file.read( + self.data_size[i] + ) + + def get_text_segment(self, num): + """Reads the binary content of a text segment.""" + offset = self.text_segs[num].begin - self.image_base + return self.image[offset : offset + self.text_size[num]] + + def get_data_segment(self, num): + """Reads the binary content of a data segment.""" + offset = self.data_segs[num].begin - self.image_base + return self.image[offset : offset + self.data_size[num]] diff --git a/mkwutil/dump_dol_header.py b/mkwutil/dump_dol_header.py new file mode 100644 index 000000000..ab2b7ac7c --- /dev/null +++ b/mkwutil/dump_dol_header.py @@ -0,0 +1,26 @@ +import argparse +from pathlib import Path +import struct + +parser = argparse.ArgumentParser() +parser.add_argument("dol", type=Path) +args = parser.parse_args() + +section_count = 18 + +with open(args.dol, "rb") as file: + offsets = list(struct.iter_unpack(">I", file.read(section_count * 4))) + addresses = list(struct.iter_unpack(">I", file.read(section_count * 4))) + lengths = list(struct.iter_unpack(">I", file.read(section_count * 4))) + bss_address = struct.unpack(">I", file.read(4)) + bss_length = struct.unpack(">I", file.read(4)) + entrypoint = struct.unpack(">I", file.read(4)) + + +print("entrypoint=%08x" % entrypoint[0]) +for i in range(section_count): + print( + "section=0x%02x offset=0x%08x address=0x%08x length=%08x" + % (i, offsets[i][0], addresses[i][0], lengths[i][0]) + ) +print("section=bss address=%08x length=%08x" % (bss_address[0], bss_length[0])) diff --git a/system/gen_asm.py b/mkwutil/gen_asm.py similarity index 54% rename from system/gen_asm.py rename to mkwutil/gen_asm.py index 4720f4e79..e9cdd7124 100644 --- a/system/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -1,53 +1,34 @@ +import argparse import csv +from contextlib import redirect_stdout import os +from pathlib import Path import shutil import struct -import sys - -from ppc_dis import * -from contextlib import redirect_stdout - - -# Change directory to script folder to make relative repo paths work. -os.chdir(sys.path[0]) - - -def read_u8(f): - return struct.unpack("B", f.read(1))[0] -def read_u32(f): - return struct.unpack(">I", f.read(4))[0] +from .ppc_dis import disasm_iter, disassemble_callback +from .dol import DolBinary, Segment -def read_u16(f): - return struct.unpack(">H", f.read(2))[0] -class Segment: - def __init__(self, begin : int, end : int): - assert isinstance(begin, int) and isinstance(end, int) - self.begin = begin - self.end = end +read_u32 = lambda f: struct.unpack(">L", f.read(4))[0] +read_u16 = lambda f: struct.unpack(">H", f.read(2))[0] +read_u8 = lambda f: struct.unpack(">B", f.read(1))[0] - def __repr__(self): - return "(%s, %s)" % (hex(self.begin), hex(self.end)) - - def empty(self): - return self.begin == self.end - - def size(self): - return self.end - self.begin def read_segments_iter(name): - with open(name) as f: - reader = csv.DictReader(f) + with open(name) as file: + reader = csv.DictReader(file) for row in reader: yield row["name"], Segment(int(row["start"], 16), int(row["end"], 16)) + def read_segments(name): result = {} for name, segment in read_segments_iter(name): result[name] = segment return result + class Slice: def __init__(self, obj_file, segments): self.obj_file = obj_file @@ -56,6 +37,7 @@ def __init__(self, obj_file, segments): def __repr__(self): return "Slice { %s, %u segs }" % (self.obj_file, len(self.segments)) + # Limitation: slices must be ordered def read_slices(name): lines = open(name).readlines() @@ -63,7 +45,7 @@ def read_slices(name): for row in reader: if not row.pop("enabled"): continue - + name = row.pop("name") segments = {} @@ -74,10 +56,11 @@ def read_slices(name): for attr in segment_attributes: if cell.endswith(attr): seg_type = attr - seg_name = cell[:-len(attr)] + seg_name = cell[: -len(attr)] assert seg_name and seg_type - if not value: continue + if not value: + continue if not seg_name in segments: segments[seg_name] = Segment(0, 0) @@ -90,67 +73,32 @@ def read_slices(name): print("#### %s %s" % (name, segments)) yield Slice(name, segments) -class DolBinary: - def __init__(self, file): - file = open(file, 'rb') - text_ofs = [read_u32(file) for _ in range(7)] - data_ofs = [read_u32(file) for _ in range(11)] - - text_vaddr = [read_u32(file) for _ in range(7)] - data_vaddr = [read_u32(file) for _ in range(11)] - - text_size = [read_u32(file) for _ in range(7)] - data_size = [read_u32(file) for _ in range(11)] - - self.text_segs = [Segment(x, x + y) for x, y in zip(text_vaddr, text_size)] - self.data_segs = [Segment(x, x + y) for x, y in zip(data_vaddr, data_size)] - - bss_vaddr = read_u32(file) - bss_size = read_u32(file) - - self.bss = Segment(bss_vaddr, bss_vaddr + bss_size) - - self.entry_point = read_u32(file) - - max_vaddr = max(x.end for x in self.text_segs + self.data_segs) - self.image_base = 0x80000000 - self.image = bytearray(max_vaddr - self.image_base) - - for i in range(7): - if not text_size[i]: - continue - - file.seek(text_ofs[i]) - data = file.read(text_size[i]) - for j in range(text_size[i]): - self.image[text_vaddr[i] + j - self.image_base] = data[j] - - for i in range(11): - if not data_size[i]: - continue - file.seek(data_ofs[i]) - data = file.read(data_size[i]) - for j in range(data_size[i]): - self.image[data_vaddr[i] + j - self.image_base] = data[j] +def get_asm_path(name, gap, folder): + folder.mkdir(exist_ok=True) + return folder / ("%s_%s.s" % (name, hex(gap.begin)[2:])) - -def format_gap(name, gap, folder): - return "asm/%s/%s_%s.s" % (folder, name, hex(gap.begin)[2:]) - def format_segname(name): - if "extab" in name: return name + "_" - return '.' + name + if "extab" in name: + return name + "_" + return "." + name + def read_u32b(filecontent, offset): - return (filecontent[offset + 0] << 24) | (filecontent[offset + 1] << 16) | (filecontent[offset + 2] << 8) | filecontent[offset + 3] + return ( + (filecontent[offset + 0] << 24) + | (filecontent[offset + 1] << 16) + | (filecontent[offset + 2] << 8) + | filecontent[offset + 3] + ) # stdout must be redirected def dump_bss(size): print(".skip 0x%x" % size) + # stdout must be redirected def dump_data(image, addr_start, seg): for i in range(seg.begin, seg.end, 4): @@ -161,23 +109,28 @@ def dump_data(image, addr_start, seg): for j in range(i, seg.end): print(".byte 0x%02x" % image[j - addr_start]) + # stdout must be redirected def dump_text(image, addr_start, seg): - disasm_iter(image, seg.begin - addr_start, seg.begin, seg.size(), disassemble_callback) + disasm_iter( + image, seg.begin - addr_start, seg.begin, seg.size(), disassemble_callback + ) + def compute_perm(name): perm = "wa" if name == "text" or name == "init": perm = "ax" - + # if "bss" in name: # perm = "ba" if name == "rodata" or "2" in name: - perm = perm.replace('w', '') + perm = perm.replace("w", "") return perm + # stdout must be redirected def dump_section_body(name, image, addr_start, seg): if "bss" in name: @@ -190,43 +143,42 @@ def dump_section_body(name, image, addr_start, seg): dump_data(image, addr_start, seg) + # stdout must be redirected def dump_section_header(name, seg): # section permissions perm = compute_perm(name) - - print("\n.section %s, \"%s\" # 0x%08X - 0x%08X" % (format_segname(name), perm, seg.begin, seg.end)) + + print( + '\n.section %s, "%s" # 0x%08X - 0x%08X' + % (format_segname(name), perm, seg.begin, seg.end) + ) + # stdout must be redirected def dump_section(name, image, addr_start, seg): dump_section_header(name, seg) dump_section_body(name, image, addr_start, seg) + # stdout must be redirected -def dump_object_file(image, addr_start, segments = []): - print("\n.include \"macros.inc\"") +def dump_object_file(image, addr_start, segments): + print('\n.include "macros.inc"') for segment_name, segment in segments: dump_section(segment_name, image, addr_start, segment) -def disassemble_object_file(path, image, addr_start, segments = []): - with open(path, 'w') as file: + +def disassemble_object_file(path, image, addr_start, segments): + with open(path, "w") as file: with redirect_stdout(file): dump_object_file(image, addr_start, segments) -def require_folder(path): - try: - os.mkdir(path) - except FileExistsError: - pass def disasm(folder, name, image, addr_start, seg, is_data): - require_folder(os.path.join("..", "asm")) - require_folder(os.path.join("..", "asm", folder)) - - path = os.path.join("..", format_gap(name, seg, folder)) + path = get_asm_path(name, seg, folder) - disassemble_object_file(path, image, addr_start, [ (name, seg) ]) + disassemble_object_file(path, image, addr_start, [(name, seg)]) def gen_start_segs(segments): @@ -255,104 +207,126 @@ def find_gaps(all_slices): # [1:] to skip initial (previously start_seg) for slice_obj in all_slices[1:]: obj_file = slice_obj.obj_file - slice = slice_obj.segments + slice = slice_obj.segments for name, segment in slice.items(): if last_segments[name].end != segment.begin: # There's a gap! - print("[.%s] Gap from %x to %x" % (name, last_segments[name].end, segment.begin)) + print( + "[.%s] Gap from %x to %x" + % (name, last_segments[name].end, segment.begin) + ) yield name, Segment(last_segments[name].end, segment.begin) last_segments[name] = segment - if not obj_file.startswith('#'): + if not obj_file.startswith("#"): yield obj_file, None + def find_o_files(all_slices, folder): + """Returns all paths to object files that will assemble the binary.""" for name, gap_seg in find_gaps(all_slices): if gap_seg is None: yield name, gap_seg, "??" continue + path = get_asm_path(name, gap_seg, folder) + print(path) + path.stem.replace(".s", ".o") + yield name, gap_seg, path - print(format_gap(name, gap_seg, folder)) - dest = format_gap(name, gap_seg, folder).replace("asm/", "").replace(".s", ".o") - yield name, gap_seg, dest def unpack_binary(folder, all_slices, image, addr_start): - for name, gap_seg, dest in find_o_files(all_slices, folder): + for name, gap_seg, dest in find_o_files(all_slices, folder): is_decompiled = gap_seg is None if not is_decompiled: # print("name %s dest %s" % (name, dest)) disasm(folder, name, image, addr_start, gap_seg, False) - yield dest - + kind = Path(dest.parent.name) # dol or rel + yield Path("out", kind, dest.stem + ".o") + if is_decompiled: - yield name.replace(".cpp", ".o").replace(".c", ".o") + object_name = Path(name).stem + ".o" + yield Path("out", object_name) + def compute_end_cap(segments): # Final 0x8 -> 0x8; second part ignored end_seg = gen_end_segs(segments) - end_slice = Slice('# 0x80 [finish] -> 0x80 [ignored]', end_seg) + end_slice = Slice("# 0x80 [finish] -> 0x80 [ignored]", end_seg) return end_slice + def compute_begin_cap(segments): # Final 0x8 -> 0x8; second part ignored start_seg = gen_start_segs(segments) - start_slice = Slice('# 0 [ignored] -> 0x80 [start]', start_seg) + start_slice = Slice("# 0 [ignored] -> 0x80 [start]", start_seg) return start_slice + def gen_cuts(slices, segments): # Initial 0 -> 0x8; first part ignored - + start_slice = compute_begin_cap(segments) end_slice = compute_end_cap(segments) - - return [start_slice] + slices + [end_slice] + + return [start_slice] + slices + [end_slice] + def compute_cuts_from_spreadsheets(segments, decomplog): # segments: binary descriptor, .text: 0x8..0x8 # decomplog: slices, what decompiled code replaces - + slices = list(read_slices(decomplog)) segments = read_segments(segments) return slices, segments, gen_cuts(slices, segments) - -def unpack_base_dol(): - base_dol = DolBinary("../artifacts/pal/main.dol") - slices, segments, cuts = compute_cuts_from_spreadsheets("../artifacts/pal/segments.csv", "slices.csv") + +def unpack_base_dol(asm_dir, pack_dir, binary_dir): + base_dol = DolBinary(binary_dir / "main.dol") + + _, _, cuts = compute_cuts_from_spreadsheets( + pack_dir / "dol_segments.csv", + pack_dir / "dol_slices.csv", + ) # o_files - return list(unpack_binary("dol", cuts, base_dol.image, base_dol.image_base)) + return list( + unpack_binary(asm_dir / "dol", cuts, base_dol.image, base_dol.image_base) + ) + ## REL -def load_rel_binary(segments) -> (bytearray, int): + +def load_rel_binary(segments, binary_dir) -> (bytearray, int): print(segments) max_vaddr = max(segments[seg].end for seg in segments) image_base = 0x80000000 image = bytearray(max_vaddr - image_base) + rel_segment_dir = binary_dir / "rel" for segment in segments: - with open("../tmp/%s.bin" % segment, 'rb') as file: + rel_segment_path = rel_segment_dir / (segment + ".bin") + with open(rel_segment_path, "rb") as file: data = file.read() segment_data = segments[segment] start = segment_data.begin - end = segment_data.end + end = segment_data.end - data_len = len(data) # virtual + data_len = len(data) # virtual for i in range(start, end): - #try: + # try: # x = data[i - start] - #except: + # except: # print(segment, hex(i), hex(start), hex(end),i - start, len(data)) # print(end - (start + len(data))) @@ -363,30 +337,57 @@ def load_rel_binary(segments) -> (bytearray, int): return image, image_base -def unpack_staticr_rel(): - slices, segments, cuts = compute_cuts_from_spreadsheets("../artifacts/pal/rel_segments.csv", "rel_slices.csv") - image, image_base = load_rel_binary(segments) - - # o_files - return list(unpack_binary("rel", cuts, image, image_base)) +def unpack_staticr_rel(asm_dir, pack_dir, binary_dir): + _, segments, cuts = compute_cuts_from_spreadsheets( + pack_dir / "rel_segments.csv", + pack_dir / "rel_slices.csv", + ) -def unpack_everything(): - dol_o_files = unpack_base_dol() - open('../o_files.txt', 'w').write('\n'.join(dol_o_files)) - - rel_o_files = unpack_staticr_rel() - open('../rel_o_files.txt', 'w').write('\n'.join(rel_o_files)) + image, image_base = load_rel_binary(segments, binary_dir) -if __name__ == '__main__': - try: shutil.rmtree("../asm") - except: pass - - try: os.mkdir("../asm") - except: pass - - with open('../asm/macros.inc', 'w') as file: - file.write('# PowerPC Register Constants\n') + # o_files + return list(unpack_binary(asm_dir / "rel", cuts, image, image_base)) + + +def unpack_everything(asm_dir, pack_dir, binary_dir): + """Unpack all ASM blobs into asm_dir.""" + dol_o_files = unpack_base_dol(asm_dir, pack_dir, binary_dir) + with open(pack_dir / "dol_objects.txt", "w") as file: + for path in dol_o_files: + print(path, file=file) + rel_o_files = unpack_staticr_rel(asm_dir, pack_dir, binary_dir) + with open(pack_dir / "rel_objects.txt", "w") as file: + for path in rel_o_files: + print(path, file=file) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Generate ASM blobs and linker object lists." + ) + parser.add_argument("--asm_dir", type=Path, default="./asm", help="Path to ASM dir") + parser.add_argument( + "--pack_dir", type=Path, default="./pack", help="Path to link instructions dir" + ) + parser.add_argument( + "--binary_dir", + type=Path, + default="./artifacts/orig/pal", + help="Binary containing main.dol and StaticR.rel", + ) + args = parser.parse_args() + + # Recreate the ASM dir. + if os.path.exists(args.asm_dir / "dol"): + shutil.rmtree(args.asm_dir / "dol") + if os.path.exists(args.asm_dir / "rel"): + shutil.rmtree(args.asm_dir / "rel") + args.asm_dir.mkdir(exist_ok=True) + + # Write the macros file. + with open(args.asm_dir / "macros.inc", "w") as file: + file.write("# PowerPC Register Constants\n") for i in range(0, 32): file.write(".set r%i, %i\n" % (i, i)) for i in range(0, 32): @@ -394,4 +395,4 @@ def unpack_everything(): for i in range(0, 8): file.write(".set qr%i, %i\n" % (i, i)) - unpack_everything() \ No newline at end of file + unpack_everything(args.asm_dir, args.pack_dir, args.binary_dir) diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py new file mode 100644 index 000000000..956a17df6 --- /dev/null +++ b/mkwutil/gen_lcf.py @@ -0,0 +1,46 @@ +import argparse +from pathlib import Path + + +def gen_lcf(src, dst, object_paths): + lcf = "" + + with open(src, "r") as f: + lcf = f.read() + lcf += "\nFORCEFILES {\n" + for obj_path in object_paths: + obj_path = Path(obj_path) + # TODO: Add ability to disable FORCEFILE to slices.csv + if obj_path.stem == "eggVideo": + continue + lcf += str(obj_path.parent / (obj_path.stem + ".o")) + "\n" + lcf += "}\n" + + with open(dst, "w") as f: + f.write(lcf) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--base", type=Path, required=True, help="Path to base linker script" + ) + parser.add_argument( + "--out", type=Path, required=True, help="Path to linker script output" + ) + parser.add_argument("--objs", type=Path, required=True, help="Path to objects list") + parser.add_argument( + "--prefix", + type=Path, + default="", + help="Dir prefix for references in linker script", + ) + args = parser.parse_args() + + # Read list of objects. + objs = [] + with open(args.objs, "r") as file: + for line in file: + objs.append(args.prefix / Path(line)) + + gen_lcf(args.base, args.out, objs) diff --git a/mkwutil/pack_main_dol.py b/mkwutil/pack_main_dol.py new file mode 100644 index 000000000..bd5ff3335 --- /dev/null +++ b/mkwutil/pack_main_dol.py @@ -0,0 +1,104 @@ +""" +Script to convert a main.elf to main.dol executable. +""" + +import argparse +import io +from elftools.elf.constants import P_FLAGS +from elftools.elf.elffile import ELFFile + + +def segment_is_text(seg): + """Returns whether segment is executable text.""" + return seg["p_flags"] & P_FLAGS.PF_X == P_FLAGS.PF_X + + +def segment_is_data(seg): + """Returns whether segment is data.""" + return not segment_is_text(seg) and not segment_is_bss(seg) + + +def segment_is_bss(seg): + """Returns whether segment is bss.""" + return seg["p_filesz"] == 0 + + +def write_to_dol_header(file, offset, val): + """Writes a 4 byte value to DOL.""" + file.seek(offset) + file.write(val.to_bytes(4, byteorder="big")) + file.seek(0, io.SEEK_END) + + +def write_segment_to_dol(idx, segment, dol_file): + """Writes a segment to DOL file.""" + write_to_dol_header(dol_file, 0x00 + 0x04 * idx, dol_file.tell()) + write_to_dol_header(dol_file, 0x48 + 0x04 * idx, segment["p_vaddr"]) + # align filesz to 0x20 + filesz = ((segment["p_filesz"] + 0x1F) >> 5) << 5 + write_to_dol_header(dol_file, 0x90 + 0x04 * idx, filesz) + + dol_file.write(segment.data()) + # align current dol size to 0x20 + size = 0x20 - dol_file.tell() & 0x1F + dol_file.write(bytes([0x00] * size)) + + +def elf_to_dol(elf_file_path, dol_file_path): + """Ports a DOL file to a dummy ELF file.""" + with open(elf_file_path, "rb") as elf_raw_file, open( + dol_file_path, "wb" + ) as dol_file: + elf_file = ELFFile(elf_raw_file) + num_segments = elf_file.num_segments() + + dol_file.write(bytes([0x00] * 0x100)) + + idx = 0 + for i in range(num_segments): + segment = elf_file.get_segment(i) + if not segment_is_text(segment): + continue + write_segment_to_dol(idx, segment, dol_file) + idx += 1 + + idx = 7 + for i in range(num_segments): + segment = elf_file.get_segment(i) + if not segment_is_data(segment): + continue + write_segment_to_dol(idx, segment, dol_file) + idx += 1 + + bss_start = 0 + bss_end = 0 + for i in range(num_segments): + segment = elf_file.get_segment(i) + if not segment_is_bss(segment): + continue + if bss_start == 0: + bss_start = segment["p_vaddr"] + bss_end = segment["p_vaddr"] + segment["p_memsz"] + write_to_dol_header(dol_file, 0xD8, bss_start) + bss_size = bss_end - bss_start + write_to_dol_header(dol_file, 0xDC, bss_size) + + write_to_dol_header(dol_file, 0xE0, elf_file["e_entry"]) + + +def pack_main_dol(src, dst): + """Performs the full ELF to DOL conversion.""" + # TODO Why do we garble the ELF here? + with open(src, "r+b") as dolf: + dolf.seek(0x18) + dolf.write(bytes([0x80, 0x00, 0x60, 0xA4])) + elf_to_dol(src, dst) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Convert main.elf to main.dol") + parser.add_argument("-o", "--out", type=str, required=True, help="DOL output path") + parser.add_argument("input", type=str, help="ELF input path") + args = parser.parse_args() + + pack_main_dol(args.input, args.out) diff --git a/mkwutil/pack_staticr_rel.py b/mkwutil/pack_staticr_rel.py new file mode 100644 index 000000000..44e15ba4b --- /dev/null +++ b/mkwutil/pack_staticr_rel.py @@ -0,0 +1,118 @@ +""" +Script to convert a StaticR.elf to StaticR.rel relocatable file. +""" + +import argparse +from pathlib import Path +import struct + +from elftools.elf.elffile import ELFFile + +from .rel import Rel, RelSection + + +R_PPC_NONE = 0 +R_PPC_ADDR32 = 1 +R_PPC_ADDR24 = 2 +R_PPC_ADDR16 = 3 +R_PPC_ADDR16_LO = 4 +R_PPC_ADDR16_HI = 5 +R_PPC_ADDR16_HA = 6 +R_PPC_ADDR14 = 7 +R_PPC_ADDR14_BRTAKEN = 8 +R_PPC_ADDR14_BRNKTAKEN = 9 +R_PPC_REL24 = 10 +R_PPC_REL14 = 11 +R_PPC_REL32 = 26 + + +def read_elf_sec(elf, name): + """Constructs a RelSection for the given ELF section name.""" + elf_sec = elf.get_section_by_name(name) + + data = elf_sec.data() + + sec = RelSection() + sec.data = bytearray(data) + sec.length = len(data) + + # Added + sec.name = name + + return sec + + +def pack_staticr_rel(elf_path, rel_path, orig_dir): + rel = Rel() + + # TODO hardcoded path + orig_dir = Path(orig_dir) + rel.load_reloc(0, orig_dir / "pal" / "rel" / "dol_rel.bin") + rel.load_reloc(1, orig_dir / "pal" / "rel" / "rel_abs.bin") + + with open(elf_path, "rb") as f: + elf = ELFFile(f) + + rel.section_info[1] = read_elf_sec(elf, ".text") + rel.section_info[2] = read_elf_sec(elf, ".ctors") + rel.section_info[3] = read_elf_sec(elf, ".dtors") + rel.section_info[4] = read_elf_sec(elf, ".rodata") + rel.section_info[5] = read_elf_sec(elf, ".data") + rel.section_info[6] = read_elf_sec(elf, ".bss") + + # .rodata padding hack + rodata = rel.section_info[4] + rodata.data = rodata.data[:-16] + rodata.length = len(rodata.data) + + # Jump to _Unresolved + _unresolved = 0x805553B0 + text = rel.section_info[1] + relocs = elf.get_section_by_name(".rela.text") + if relocs: + for reloc_acc in relocs.iter_relocations(): + reloc = reloc_acc.entry + if reloc.r_info_type == R_PPC_REL24: + instruction = struct.unpack( + ">I", text.data[reloc.r_offset : reloc.r_offset + 4] + )[0] + instruction_addr = 0x805103B4 + reloc.r_offset + + delta = _unresolved - instruction_addr + new_ins = (instruction & ~0x03FFFFFC) | (delta & 0x03FFFFFC) + + # print(hex(instruction)) + # print(hex(new_ins)) + + packed = struct.pack(">I", new_ins) + for i in range(4): + text.data[reloc.r_offset + i] = packed[i] + + rel.section_info[1].executable = True + rel.section_info[1].offset = 0xD4 + rel.section_info[2].offset = 0x37F120 + rel.section_info[3].offset = 0x37F424 + rel.section_info[4].offset = 0x37F440 + rel.section_info[5].offset = 0x3A28F0 + rel.section_info[6].offset = 0 + + # This seems to affect tz, but absolutely no one else? + # Hack: Fix .bss size + rel.section_info[6].length = 0x78B0 + + rel.imps[0].moduleId = 1 + rel.imps[0].offset = 0x3CD104 + rel.imps[1].moduleId = 0 + rel.imps[1].offset = 0x4820F4 + + with open(rel_path, "wb") as file: + rel.reconstruct(file) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Convert StaticR.elf to StaticR.rel") + parser.add_argument("-o", "--out", type=str, required=True, help="REL output path") + parser.add_argument("input", type=str, help="ELF input path") + args = parser.parse_args() + + pack_staticr_rel(args.input, args.out, "./artifacts/orig") diff --git a/mkwutil/percent_decompiled.py b/mkwutil/percent_decompiled.py new file mode 100644 index 000000000..802e54526 --- /dev/null +++ b/mkwutil/percent_decompiled.py @@ -0,0 +1,143 @@ +import csv +import os +from pathlib import Path + + +def process_line(tags, items): + name = "Untitled" + start = None + code_total = 0 + data_total = 0 + + for tag, entry in zip(tags, items): + if tag == "enabled" and entry == 0: + return + elif tag == "name": + name = entry + continue + + is_code = "text" in tag + + if "Start" in tag: + if not entry: + start = None + continue + + start = int(entry, 16) + continue + + if "End" in tag and start: + size = int(entry, 16) - start + + if is_code: + code_total += size + else: + data_total += size + + name = items[1] + return name, code_total, data_total + + +def parse_slices(path): + with open(path, "r") as file: + lines = file.readlines() + + tags = lines[0].split(",") + for line in lines[1:]: + yield process_line(tags, line.split(",")) + + +def simple_count(path): + code_total = 0 + data_total = 0 + + for o_name, o_code_total, o_data_total in parse_slices(path): + code_total += o_code_total + data_total += o_data_total + + return code_total, data_total + + +def segments_of(path): + with open(path, "r") as file: + reader = csv.DictReader(file) + for line in reader: + is_code = "text" in line["name"] + yield is_code, int(line["end"], 16) - int(line["start"], 16) + + +def binary_total(path): + segments = list(segments_of(path)) + + num_code = sum(size if is_code else 0 for is_code, size in segments) + num_data = sum(size if not is_code else 0 for is_code, size in segments) + + return num_code, num_data + + +def to_percent(frac): + return round(frac * 100_000) / 1000 + + +def analyze(prefix, progress, total): + # print("%s %s bytes (%s%%) of code, %s bytes (%s%%) of data decompiled" % ( + # prefix, + # progress[0], to_percent(progress[0] / total[0]) if total[0] else '', + # progress[1], to_percent(progress[1] / total[1]) if total[1] else '' + # )) + print( + "%s %s%% code, %s%% data decompiled" + % ( + prefix, + to_percent(progress[0] / total[0]) if total[0] else "", + to_percent(progress[1] / total[1]) if total[1] else "", + ) + ) + + +def percent_decompiled(dir="."): + dir = Path(dir) + dol_slices_path = dir / "pack" / "dol_slices.csv" + dol_progress = simple_count(dol_slices_path) + dol_segments_path = dir / "pack" / "dol_segments.csv" + dol_total = binary_total(dol_segments_path) + analyze("[DOL]", dol_progress, dol_total) + + egg_progress = [0, 0] + for o_name, o_code_total, o_data_total in parse_slices(dol_slices_path): + if "egg" not in o_name: + continue + + egg_progress[0] += o_code_total + egg_progress[1] += o_data_total + + egg_total = [0x80244DD4 - 0x8020F62C, None] + + analyze(" -> [EGG]", egg_progress, egg_total) + + rel_slices_path = dir / "pack" / "rel_slices.csv" + rel_progress = simple_count(rel_slices_path) + rel_segments_path = dir / "pack" / "rel_segments.csv" + rel_total = binary_total(rel_segments_path) + analyze("[REL]", rel_progress, rel_total) + + def piecewise_add(x, y): + return list(a + b for a, b in zip(x, y)) + + analyze( + "--- main.dol + StaticR.rel ---\n", + piecewise_add(dol_progress, rel_progress), + piecewise_add(dol_total, rel_total), + ) + + print("------") + print("Player:") + print(" - %u BR (main.dol)" % (dol_progress[0] / dol_total[0] * 4999 + 5000)) + print(" - %u VR (StaticR.rel)" % (rel_progress[0] / rel_total[0] * 4999 + 5000)) + + print(dol_total[0] / 4999 / 4) + print(rel_total[0] / 4999 / 4) + + +if __name__ == "__main__": + percent_decompiled() diff --git a/mkwutil/ppc_dis.py b/mkwutil/ppc_dis.py new file mode 100644 index 000000000..f2038ef37 --- /dev/null +++ b/mkwutil/ppc_dis.py @@ -0,0 +1,387 @@ +""" +Disassembles Broadway code. + +Based on https://gist.github.com/camthesaxman/a36f610dbf4cc53a874322ef146c4123 + +Changes: +- Blacklisted more instructionss +- Adjusted output to take up fewer bytes +- mwasm support +""" + +# Wildcard imports are okay. +# pylint: disable=W0401 +# pylint: disable=W0614 + +from capstone import * +from capstone.ppc import * + + +def sign_extend_16(value): + if value > 0 and (value & 0x8000): + value -= 0x10000 + return value + + +def sign_extend_12(value): + if value > 0 and (value & 0x800): + value -= 0x1000 + return value + + +cs = Cs(CS_ARCH_PPC, CS_MODE_32 | CS_MODE_BIG_ENDIAN) +cs.detail = True +cs.imm_unsigned = False + +blacklistedInsns = { + # Unsupported instructions + PPC_INS_VMSUMSHM, + PPC_INS_VMHADDSHS, + PPC_INS_XXSLDWI, + PPC_INS_VSEL, + PPC_INS_XVSUBSP, + PPC_INS_XXSEL, + PPC_INS_XVMULSP, + PPC_INS_XVDIVSP, + PPC_INS_VADDUHM, + PPC_INS_XXPERMDI, + PPC_INS_XVMADDASP, + PPC_INS_XVMADDMSP, + PPC_INS_XVCMPGTSP, + PPC_INS_XXMRGHD, + PPC_INS_XSMSUBMDP, + PPC_INS_XSTDIVDP, + PPC_INS_XVADDSP, + PPC_INS_XVCMPEQSP, + PPC_INS_XVMSUBASP, + PPC_INS_XVCMPGESP, + PPC_INS_XSMSUBADP, + PPC_INS_XSMADDADP, + PPC_INS_XSCMPODP, + PPC_INS_XVTDIVSP, + PPC_INS_VMRGHB, + PPC_INS_VADDUBM, + PPC_INS_XSMADDMDP, + PPC_INS_XSCMPUDP, + PPC_INS_XVNMADDASP, + PPC_INS_VADDUWM, + PPC_INS_XVMSUBMSP, + PPC_INS_XSNMSUBMDP, + PPC_INS_XSNMSUBADP, + PPC_INS_XVCMPEQDP, + PPC_INS_VPKUHUM, + PPC_INS_XVCMPGTDP, + PPC_INS_XVMADDADP, + PPC_INS_XVMSUBMDP, + PPC_INS_VMADDFP, + PPC_INS_XXSPLTW, + PPC_INS_XVMSUBADP, + PPC_INS_XVCMPGEDP, + PPC_INS_XVNMSUBMDP, + PPC_INS_XVMADDMDP, + PPC_INS_XXMRGHW, + PPC_INS_XVTDIVDP, + PPC_INS_XXMRGLW, + PPC_INS_XVADDDP, + PPC_INS_XXLAND, + PPC_INS_XVNMSUBASP, + PPC_INS_XVNMSUBMSP, + PPC_INS_XVNMADDADP, + PPC_INS_XVNMADDMSP, + PPC_INS_XSSUBDP, + # Instructions that Capstone gets wrong + PPC_INS_MFESR, + PPC_INS_MFDEAR, + PPC_INS_MTESR, + PPC_INS_MTDEAR, + PPC_INS_MFICCR, + PPC_INS_MFASR, +} + +labels = set() +labelNames = {} + + +def addr_to_label(addr, insn_addr): + if addr in labels: + if addr in labelNames: + return labelNames[addr] + else: + return "lbl_%08X" % addr + else: + return hex(addr - insn_addr) + + +def insn_to_text(insn, raw): + # Probably data, not a real instruction + if insn.id == PPC_INS_BDNZ and (insn.bytes[0] & 1): + return None + if insn.id in {PPC_INS_B, PPC_INS_BL, PPC_INS_BDZ, PPC_INS_BDNZ}: + return "%s %s" % ( + insn.mnemonic, + addr_to_label(insn.operands[0].imm, insn.address), + ) + elif insn.id == PPC_INS_BC: + branch_pred = "+" if (insn.bytes[1] & 0x20) else "" + if insn.operands[0].type == PPC_OP_IMM: + return "%s%s %s" % ( + insn.mnemonic, + branch_pred, + addr_to_label(insn.operands[0].imm, insn.address), + ) + elif insn.operands[1].type == PPC_OP_IMM: + return "%s%s %s, %s" % ( + insn.mnemonic, + branch_pred, + insn.reg_name(insn.operands[0].value.reg), + addr_to_label(insn.operands[1].imm, insn.address), + ) + + # Sign-extend immediate values because Capstone is an idiot and doesn't do that automatically + if insn.id in {PPC_INS_ADDI, PPC_INS_ADDIC, PPC_INS_SUBFIC, PPC_INS_MULLI} and ( + insn.operands[2].imm & 0x8000 + ): + return "%s %s, %s, %i" % ( + insn.mnemonic, + insn.reg_name(insn.operands[0].reg), + insn.reg_name(insn.operands[1].value.reg), + insn.operands[2].imm - 0x10000, + ) + elif (insn.id == PPC_INS_LI or insn.id == PPC_INS_CMPWI) and ( + insn.operands[1].imm & 0x8000 + ): + return "%s %s, %i" % ( + insn.mnemonic, + insn.reg_name(insn.operands[0].reg), + insn.operands[1].imm - 0x10000, + ) + # cntlz -> cntlzw + elif insn.id == PPC_INS_CNTLZW: + return "cntlzw %s" % insn.op_str + elif insn.id == PPC_INS_MTICCR: + return "mtictc %s" % insn.op_str + # Dunno why GNU assembler doesn't accept this + elif insn.id == PPC_INS_LMW and insn.operands[0].reg == PPC_REG_R0: + return ".4byte 0x%08X /* illegal %s %s */" % (raw, insn.mnemonic, insn.op_str) + return "%s %s" % (insn.mnemonic, insn.op_str) + + +def disasm_ps(inst): + """Disassembles special ps instruction.""" + RA = (inst >> 16) & 0x1F + RB = (inst >> 11) & 0x1F + FA = (inst >> 16) & 0x1F + FB = (inst >> 11) & 0x1F + FC = (inst >> 6) & 0x1F + FD = (inst >> 21) & 0x1F + FS = (inst >> 21) & 0x1F + IX = (inst >> 7) & 0x7 + WX = (inst >> 10) & 0x1 + + opcode = (inst >> 1) & 0x1F + if opcode == 6: # doesn't seem to be used + mnemonic = "psq_lux" if inst & 0x40 else "psq_lx" + return "%s f%i, r%i, r%i, %i, qr%i" % (mnemonic, FD, RA, RB, WX, IX) + if opcode == 7: + mnemonic = "psq_stux" if inst & 0x40 else "psq_stx" + return "%s f%i, r%i, r%i, %i, qr%i" % (mnemonic, FS, RA, RB, WX, IX) + if opcode == 18: + return "ps_div f%i, f%i, f%i" % (FD, FA, FB) + if opcode == 20: + return "ps_sub f%i, f%i, f%i" % (FD, FA, FB) + if opcode == 21: + return "ps_add f%i, f%i, f%i" % (FD, FA, FB) + if opcode == 23: + return "ps_sel f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 24: + return "ps_res f%i, f%i" % (FD, FB) + if opcode == 25: + return "ps_mul f%i, f%i, f%i" % (FD, FA, FC) + if opcode == 26: + return "ps_rsqrte f%i, f%i" % (FD, FB) + if opcode == 28: + return "ps_msub f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 29: + return "ps_madd f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 30: + return "ps_nmsub f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 31: + return "ps_nmadd f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 10: + return "ps_sum0 f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 11: + return "ps_sum1 f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 12: + return "ps_muls0 f%i, f%i, f%i" % (FD, FA, FC) + if opcode == 13: + return "ps_muls1 f%i, f%i, f%i" % (FD, FA, FC) + if opcode == 14: + return "ps_madds0 f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + if opcode == 15: + return "ps_madds1 f%i, f%i, f%i, f%i" % (FD, FA, FC, FB) + + opcode = (inst >> 1) & 0x3FF + if opcode == 40: + return "ps_neg f%i, f%i" % (FD, FB) + if opcode == 72: + return "ps_mr f%i, f%i" % (FD, FB) + if opcode == 136: + return "ps_nabs f%i, f%i" % (FD, FB) + if opcode == 264: + return "ps_abs f%i, f%i" % (FD, FB) + if opcode in {0, 32, 64, 96}: + mnemonics = ["ps_cmpu0", "ps_cmpo0", "ps_cmpu1", "ps_cmpo1"] + mnemonic = mnemonics[(inst >> 6) & 3] + i = (inst & 0x03800000) >> 23 + return "%s cr%i, f%i, f%i" % (mnemonic, i, FA, FB) + if opcode == 528: + return "ps_merge00 f%i, f%i, f%i" % (FD, FA, FB) + if opcode == 560: + return "ps_merge01 f%i, f%i, f%i" % (FD, FA, FB) + if opcode == 592: + return "ps_merge10 f%i, f%i, f%i" % (FD, FA, FB) + if opcode == 624: + return "ps_merge11 f%i, f%i, f%i" % (FD, FA, FB) + if opcode == 1014: + if not inst & 0x03E00000: + if (inst & 1) == 0: + return "dcbz_l r%i, r%i" % ( + (inst & 0x001F0000) >> 16, + (inst & 0x0000F800) >> 11, + ) + return None + + +def disasm_ps_mem(inst, idx): + """Disassembles special ps_mem instruction.""" + RA = (inst >> 16) & 0x1F + RS = (inst >> 21) & 0x1F + I = (inst >> 12) & 0x7 + W = (inst >> 15) & 0x1 + disp = sign_extend_12(inst & 0xFFF) + if idx == 56: + mnemonic = "psq_l" + if idx == 57: + mnemonic = "psq_lu" + if idx == 60: + mnemonic = "psq_st" + if idx == 61: + mnemonic = "psq_stu" + return "%s f%i, %i(r%i), %i, qr%i" % (mnemonic, RS, disp, RA, W, I) + + +def disasm_fcmp(inst): + """Disassembles special fcmp instruction.""" + crd = (inst & 0x03800000) >> 23 + a = (inst & 0x001F0000) >> 16 + b = (inst & 0x0000F800) >> 11 + return "fcmpo cr%i, f%i, f%i" % (crd, a, b) + + +def disasm_mspr(inst, mode): + """Disassembles special mspr instruction.""" + if inst & 1: + return None + d = (inst & 0x03E00000) >> 21 + a = (inst & 0x001F0000) >> 16 + b = (inst & 0x0000F800) >> 11 + spr = (b << 5) + a + if mode: + return "mtspr 0x%X, r%i" % (spr, d) + else: + return "mfspr r%i, 0x%X" % (d, spr) + + +def disasm_mcrxr(inst): + """Disassembles special mcrxr instruction.""" + if inst & 0x007FF801: + return None + crd = (inst & 0x03800000) >> 23 + return "mcrxr cr%i" % crd + + +def disassemble_callback(filecontent, address, offset, insn, buf): + """Disassembles code.""" + + prefix_comment = "/* %08X %02X %02X %02X %02X */" % ( + address, + buf[0], + buf[1], + buf[2], + buf[3], + ) + # prefix_comment = '/* %08X */' % address + asm = None + + def read_u32b(filecontent, offset): + return ( + (filecontent[offset + 0] << 24) + | (filecontent[offset + 1] << 16) + | (filecontent[offset + 2] << 8) + | filecontent[offset + 3] + ) + + raw = read_u32b(filecontent, offset) + if insn is not None: + asm = insn_to_text(insn, raw) + else: # Capstone couldn't disassemble it + idx = (raw & 0xFC000000) >> 26 + idx2 = (raw & 0x000007FE) >> 1 + # mtspr + if idx == 31 and idx2 == 467: + asm = disasm_mspr(raw, 1) + # mfspr + elif idx == 31 and idx2 == 339: + asm = disasm_mspr(raw, 0) + # mcrxr + elif idx == 31 and idx2 == 512: + asm = disasm_mcrxr(raw) + # fcmpo + elif idx == 63 and idx2 == 32: + asm = disasm_fcmp(raw) + # Paired singles + elif idx == 4: + asm = disasm_ps(raw) + elif idx in {56, 57, 60, 61}: + asm = disasm_ps_mem(raw, idx) + if asm is None: + asm = ".4byte 0x%08X /* unknown instruction */" % raw + print("%s\t%s" % (prefix_comment, asm)) + + +# entryPoint = base.entry_point +# # Add entry point +# labels.add(entryPoint) +# labelNames[entryPoint] = '__start' + + +def disasm_iter(filecontent, offset, address, size, callback): + """Calls callback for every instruction in the specified code section.""" + + if size == 0: + return + start = address + end = address + size + while address < end: + code = filecontent[offset + (address - start) : offset + size] + for insn in cs.disasm(code, address): + address = insn.address + if insn.id in blacklistedInsns: + callback( + filecontent, address, offset + address - start, None, insn.bytes + ) + else: + callback( + filecontent, address, offset + address - start, insn, insn.bytes + ) + address += 4 + if address < end: + o = offset + address - start + callback( + filecontent, + address, + offset + address - start, + None, + filecontent[o : o + 4], + ) + address += 4 diff --git a/mkwutil/rel.py b/mkwutil/rel.py new file mode 100644 index 000000000..5b205d59d --- /dev/null +++ b/mkwutil/rel.py @@ -0,0 +1,375 @@ +""" +Utility for manipulating rel files. +""" + +from collections import OrderedDict +import io +import os +from os import PathLike +from pathlib import Path +import struct + +REL_BASE = 0x805102E0 + +read_u32 = lambda f: struct.unpack(">L", f.read(4))[0] +read_u16 = lambda f: struct.unpack(">H", f.read(2))[0] +read_u8 = lambda f: struct.unpack(">B", f.read(1))[0] +read_bool = lambda f: struct.unpack(">?", f.read(1))[0] + +write_u32 = lambda d: struct.pack(">L", d) +write_u16 = lambda d: struct.pack(">H", d) +write_u8 = lambda d: struct.pack(">B", d) +write_bool = lambda d: struct.pack(">?", d) + + +class Rel: + """Holds a .rel library.""" + + def __init__(self, file=None): + if file is None: + module_path = Path(__file__).parent + with open(module_path / "rel_header.bin", "rb") as rel_header: + self.header = RelHeader(rel_header) + self.section_info = [RelSection() for i in range(17)] + self.relocations = [RelRelData(), RelRelData()] + self.imps = [RelImpEntry(), RelImpEntry()] + return + + # header + self.header = RelHeader(file) + + # section info stuff + self.section_info = [None] * self.header.num_sections + for i in range(0, self.header.num_sections): + section = RelSection(file, self.header.section_info_offset, i) + self.section_info[i] = section + + # dumb way to divide by 8 and keep int + self.imps = [None] * (self.header.imp_size >> 3) + for i in range(0, self.header.imp_size >> 3): + self.imps[i] = RelImpEntry(file, self.header.imp_offset, i) + + # assertion i guess + assert self.header.rel_offset == self.imps[0].offset + + # relocation data + self.relocations = [None] * (self.header.imp_size >> 3) + for i in range(0, self.header.imp_size >> 3): + self.relocations[i] = RelRelData(file, self.imps[i].offset) + + def reconstruct(self, file): + rel = io.BytesIO() + # write temporary header + rel.write(b"\x41" * 0x4C) + section_table_offset = rel.tell() + + # write temporary section table entries + rel.write(b"\x41" * (len(self.section_info) * 0x8)) + + # write section data + section_start_offset = rel.tell() + offset = 0 + for s in self.section_info: + if s.exists_in_rel(): + # recalculate offset + s.offset = offset + section_start_offset + rel.write(s.data) + # fuck it this is being hardcoded + offset += s.length + if s.length == 0xC: + rel.write(b"\0" * 0x10) + offset += 0x10 + + section_end_offset = rel.tell() + + # write section info table + rel.seek(section_table_offset, os.SEEK_SET) + for s in self.section_info: + rel.write(s.reconstruct_table_entry()) + + # write temporary imp table + rel.seek(0, os.SEEK_END) + rel.write(b"\x41" * (len(self.imps) * 8)) + + relocation_offsets = [] + # write relocation data + for r in self.relocations: + relocation_offsets.append(rel.tell()) + r.reconstruct(rel) + + # write imp data + rel.seek(section_end_offset, os.SEEK_SET) + for i, r in enumerate(relocation_offsets): + rel.write(write_u32(1 - i)) # module id (hacky) + rel.write(write_u32(r)) # offset + + # finally, the header + # write new values with inconsistent names + self.header.num_sections = len(self.section_info) + self.header.section_info_offset = section_table_offset + self.header.rel_offset = relocation_offsets[0] + self.header.imp_offset = section_end_offset + self.header.imp_size = len(self.imps) * 8 + + header = self.header.reconstruct() + rel.seek(0, os.SEEK_SET) + rel.write(header) + + # write to file + file.write(rel.getbuffer()) + + def dump_reloc(self, index, file): + """Dumps a relocation to the provided file.""" + rel = io.BytesIO() + self.relocations[index].reconstruct(rel) + + with open(file, "wb") as file: + file.write(rel.getbuffer()) + + def load_reloc(self, index, file): + """Loads a relocation from the provided file.""" + with open(file, "rb") as file: + self.relocations[index] = RelRelData(file, 0) + + def dump_section(self, index, file): + """Dumps a section to the provided file.""" + s = self.section_info[index] + if s.offset != 0: + data = s.data + else: + data = b"\0" * s.length + + with open(file, "wb") as file: + file.write(data) + + def load_section(self, index, file): + """Loads a section from the provided file.""" + s = RelSection() + s.data = file.read() + s.length = len(s.data) + self.section_info[index] = s + + +class RelHeader: + """Holds a .rel header.""" + + def __init__(self, file): + file.seek(0, os.SEEK_SET) # just in case + # unpack header + self.rel_id = read_u32(file) + self.next = read_u32(file) + self.prev = read_u32(file) + self.num_sections = read_u32(file) + self.section_info_offset = read_u32(file) + self.name_offset = read_u32(file) + self.name_size = read_u32(file) + self.version = read_u32(file) + self.bss_size = read_u32(file) + self.rel_offset = read_u32(file) + self.imp_offset = read_u32(file) + self.imp_size = read_u32(file) + self.prolog_section = read_bool(file) + self.epilog_section = read_bool(file) + self.unresolved_section = read_bool(file) + self.bss_section = read_bool(file) + self.prolog = read_u32(file) + self.epilog = read_u32(file) + self.unresolved = read_u32(file) + if self.version >= 2: + self.align = read_u32(file) + self.bss_align = read_u32(file) + if self.version >= 3: + self.fix_size = read_u32(file) + + def reconstruct(self): + """Writes a .rel header""" + header = io.BytesIO() + header.write(write_u32(self.rel_id)) + header.write(write_u32(self.next)) + header.write(write_u32(self.prev)) + header.write(write_u32(self.num_sections)) + header.write(write_u32(self.section_info_offset)) + header.write(write_u32(self.name_offset)) + header.write(write_u32(self.name_size)) + header.write(write_u32(self.version)) + header.write(write_u32(self.bss_size)) + header.write(write_u32(self.rel_offset)) + header.write(write_u32(self.imp_offset)) + header.write(write_u32(self.imp_size)) + header.write(write_bool(self.prolog_section)) + header.write(write_bool(self.epilog_section)) + header.write(write_bool(self.unresolved_section)) + header.write(write_bool(self.bss_section)) + header.write(write_u32(self.prolog)) + header.write(write_u32(self.epilog)) + header.write(write_u32(self.unresolved)) + header.write(write_u32(self.align)) + header.write(write_u32(self.bss_align)) + header.write(write_u32(self.fix_size)) + return header.getvalue() + + +class RelSection: + def __init__(self, f=None, section_info_offset=None, sid=None): + if f is None: + self.offset = 0 + self.unk = False + self.executable = False + self.length = 0 + self.data = None + return + + # get the section stuff + f.seek(section_info_offset + (sid * 0x8), os.SEEK_SET) + self.offset = read_u32(f) + # dumb + self.unk = self.offset & 2 + self.executable = self.offset & 1 + self.offset &= ~3 + self.length = read_u32(f) + + # data + # skip empty shit and bss + print( + f"section {sid}: {self.offset:X} {self.executable} {self.unk:X} {self.length:X}" + ) + if self.offset == 0 or self.length == 0: + return + + f.seek(self.offset, os.SEEK_SET) + self.data = f.read(self.length) + + def exists_in_rel(self): + return self.offset != 0 and self.length != 0 + + def reconstruct_table_entry(self): + # recalculate length + if self.offset != 0: + self.length = len(self.data) + + entry = io.BytesIO() + word = self.offset | (self.unk << 1) | self.executable + entry.write(write_u32(word)) + entry.write(write_u32(self.length)) + return entry.getvalue() + + +class RelImpEntry: + def __init__(self, f=None, imp_offset=None, sid=None): + if f is None: + self.module_id = 0 + self.offset = 0 + return + + # does a thing + f.seek(imp_offset + (sid * 8), os.SEEK_SET) + self.module_id = read_u32(f) + self.offset = read_u32(f) + print(f"imp {sid}: {self.module_id:X} {self.offset:X}") + + +# funny name +class RelRelData: + def __init__(self, f=None, rel_offset=None): + if f is None: + self.entries = OrderedDict() + return + + f.seek(rel_offset, os.SEEK_SET) + self.entries = OrderedDict() + + counter = 0 + section = 0 + while True: + entry = RelRelEntry(f) + + # R_RVL_SECT + if entry.type == 202: + # print("R_RVL_SECT", counter, entry.section) + section = entry.section + self.entries[section] = [] + continue + + self.entries[section].append(entry) + + # R_RVL_STOP + if entry.type == 203: + # print("R_RVL_STOP") + break + + counter += 1 + + def reconstruct(self, file): + for entry_bin in self.entries: + # write R_RVL_SECT + file.write(write_u16(0)) + file.write(write_u8(202)) + file.write(write_u8(entry_bin)) # section id + + # write R_RVL_NOP + file.write(b"\0" * 4) + + # write actual entries + for inner_entry_bin in self.entries[entry_bin]: + entry = inner_entry_bin.reconstruct() + file.write(entry) + + +class RelRelEntry: + def __init__(self, f): + self.offset = read_u16(f) + self.type = read_u8(f) + self.section = read_u8(f) + self.addend = read_u32(f) + + def reconstruct(self): + entry = io.BytesIO() + entry.write(write_u16(self.offset)) + entry.write(write_u8(self.type)) + entry.write(write_u8(self.section)) + entry.write(write_u32(self.addend)) + return entry.getvalue() + + +def dump_staticr(rel, path): + _path = Path(path) + + rel.dump_reloc(0, _path / "dol_rel.bin") + rel.dump_reloc(1, _path / "rel_abs.bin") + + rel.dump_section(1, _path / "text.bin") + rel.dump_section(2, _path / "ctors.bin") + rel.dump_section(3, _path / "dtors.bin") + rel.dump_section(4, _path / "rodata.bin") + rel.dump_section(5, _path / "data.bin") + rel.dump_section(6, _path / "bss.bin") + + +def reconstruct_staticr(path): + _path = Path(path) + rel = Rel() + + rel.load_reloc(0, _path / "dol_rel.bin") + rel.load_reloc(1, _path / "rel_abs.bin") + + rel.load_section(1, _path / "text.bin") + rel.load_section(2, _path / "ctors.bin") + rel.load_section(3, _path / "dtors.bin") + rel.load_section(4, _path / "rodata.bin") + rel.load_section(5, _path / "data.bin") + rel.load_section(6, _path / "bss.bin") + + rel.section_info[1].executable = True + rel.section_info[1].offset = 0xD4 + rel.section_info[2].offset = 0x37F120 + rel.section_info[3].offset = 0x37F424 + rel.section_info[4].offset = 0x37F440 + rel.section_info[5].offset = 0x3A28F0 + rel.section_info[6].offset = 0 + + rel.imps[0].module_id = 1 + rel.imps[0].offset = 0x3CD104 + rel.imps[1].module_id = 0 + rel.imps[1].offset = 0x4820F4 + + return rel diff --git a/system/rel_header.bin b/mkwutil/rel_header.bin similarity index 100% rename from system/rel_header.bin rename to mkwutil/rel_header.bin diff --git a/mkwutil/rel_repack.py b/mkwutil/rel_repack.py new file mode 100644 index 000000000..af91f2ee9 --- /dev/null +++ b/mkwutil/rel_repack.py @@ -0,0 +1,42 @@ +""" +Utility to dump StaticR.rel to its individual pieces +and/or reconstruct it back to its original form. +""" + +import argparse + +from mkw.rel import Rel, dump_staticr, reconstruct_staticr + +parser = argparse.ArgumentParser(description="Repack StaticR.Rel") +parser.add_argument( + "--dump", + action="store_true", + default=False, + help="Dump relocs and sections from StaticR.rel", +) +parser.add_argument( + "--reconstruct", + action="store_true", + default=False, + help="Regenerate StaticR.rel from dump", +) +parser.add_argument("--rel", type=str, required=True, help="Path to StaticR.rel") +parser.add_argument("--dir", type=str, required=True, help="Path to dump dir") +args = parser.parse_args() + + +if not args.dump and not args.dump: + parser.print_usage() + print("rel_repack.py: error: --dump and/or --reconstruct required") + +if args.dump: + print(f"Dumping {args.rel} to {args.dir}") + with open(args.rel, "rb") as file: + rel = Rel(file) + dump_staticr(rel, args.dir) + +if args.reconstruct: + print(f"Reconstructing {args.rel} from {args.dir}") + rel = reconstruct_staticr(args.dir) + with open(args.rel, "wb") as file: + rel.reconstruct(file) diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py new file mode 100644 index 000000000..95f337788 --- /dev/null +++ b/mkwutil/verify_main_dol.py @@ -0,0 +1,108 @@ +""" +Script to verify the target main.dol for authenticity. +""" + +import argparse +import hashlib +from pathlib import Path + +from .dol import DolBinary + + +def format_segment(name, at, at2, want_size, have_size, tag): + return "%10s: at_src=0x%08x at_dst=0x%08x want=0x%08x have=0x%08x [%s]" % ( + name, + at, + at2, + want_size, + have_size, + tag, + ) + + +def verify_dol(reference, target): + """Verifies the target main.dol for authenticity.""" + print("[DOL] Verifying...") + content = open(target, "rb").read() + ctx = hashlib.sha1(content) + digest = ctx.hexdigest() + if digest.lower() == "ac7d72448630ade7655fc8bc5fd7a6543cb53a49": + print("[DOL] Everything went okay! Output is matching! ^^") + return + + want_len = 2766496 + if len(content) != want_len: + print( + "Mismatched file size: Got %d (%+d)" + % (len(content), len(content) - want_len) + ) + + good = DolBinary(reference) + bad = DolBinary(target) + + text_names = ["init", "text"] + data_names = [ + "extab", + "extabindex", + "ctors", + "dtors", + "rodata", + "data", + "sdata", + "sdata2", + ] + + for i, sizes in enumerate(zip(good.text_size, bad.text_size)): + if sizes[0] == 0 and sizes[1] == 0: + continue + good_segment = good.get_text_segment(i) + bad_segment = bad.get_text_segment(i) + match = good_segment == bad_segment + tag = "OK" if match else "FAIL" + if len(good_segment) != len(bad_segment): + tag = "SIZE" + print( + format_segment( + text_names[i], + good.text_segs[i].begin, + bad.text_segs[i].begin, + sizes[0], + sizes[1], + tag, + ) + ) + for i, sizes in enumerate(zip(good.data_size, bad.data_size)): + if sizes[0] == 0 and sizes[1] == 0: + continue + good_segment = good.get_data_segment(i) + bad_segment = bad.get_data_segment(i) + match = good_segment == bad_segment + tag = "OK" if match else "FAIL" + if len(good_segment) != len(bad_segment): + tag = "SIZE" + print( + format_segment( + data_names[i], + good.data_segs[i].begin, + bad.data_segs[i].begin, + sizes[0], + sizes[1], + tag, + ) + ) + # TODO: Add diff'ing + + print("[DOL] Oof: Output doesn't match.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--reference", type=Path, required=True, help="Path to reference main.dol" + ) + parser.add_argument( + "--target", type=Path, required=True, help="Path to target main.dol" + ) + args = parser.parse_args() + + verify_dol(args.reference, args.target) diff --git a/mkwutil/verify_staticr_rel.py b/mkwutil/verify_staticr_rel.py new file mode 100644 index 000000000..f2b7d4e69 --- /dev/null +++ b/mkwutil/verify_staticr_rel.py @@ -0,0 +1,33 @@ +""" +Script to verify the target StaticR.rel for authenticity. +""" + +import argparse +import hashlib +from pathlib import Path + + +def verify_rel(target): + """Verifies the target StaticR.rel for authenticity.""" + content = open(target, "rb").read() + ctx = hashlib.sha1(content) + digest = ctx.hexdigest() + if digest.lower() == "887bcc076781f5b005cc317a6e3cc8fd5f911300": + print("[REL] Everything went okay! Output is matching! ^^") + return + + want_len = 4903876 + if len(content) != want_len: + print(f"Mismatched file size: Got {len(content)} ({len(content)-want_len})") + + print("[REL] Oof: Output doesn't match.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "--target", type=Path, required=True, help="Path to target main.dol" + ) + args = parser.parse_args() + + verify_rel(args.target) diff --git a/o_files.txt b/o_files.txt deleted file mode 100644 index cf519c256..000000000 --- a/o_files.txt +++ /dev/null @@ -1,66 +0,0 @@ -dol/text_800072c0.o -utList.o -dol/text_800af1a0.o -dol/data_80258580.o -dol/sbss_80385fc0.o -dwc_error.o -dol/text_800ccc80.o -dol/data_80275758.o -dol/sdata_80384c00.o -rvlArchive.o -dol/text_80124e80.o -rvlMemList.o -dol/text_80199d04.o -dol/data_8027e772.o -eggAllocator.o -dol/bss_802a4080.o -dol/sbss_803862b0.o -eggArchive.o -dol/text_8020fcc4.o -dol/data_802a268c.o -eggDisposer.o -dol/text_8021a1b8.o -dol/rodata_80244ec0.o -dol/data_802a2b54.o -eggExpHeap.o -dol/text_80226f04.o -dol/data_802a3024.o -dol/sbss_80386d84.o -eggGraphicsFifo.o -dol/rodata_8025771a.o -dol/data_802a30bc.o -dol/bss_803832e4.o -dol/sbss_80386e99.o -dol/sdata2_80386fa0.o -eggHeap.o -dol/text_80229fac.o -eggQuat.o -dol/text_80239e10.o -dol/data_802a30ec.o -eggStreamDecomp.o -dol/text_80242504.o -dol/data_802a3f90.o -dol/bss_80384348.o -eggThread.o -eggUnitHeap.o -dol/ctors_80244de0.o -dol/bss_80384b6c.o -dol/sbss_80386ec0.o -dol/sdata2_80388d80.o -eggVector.o -dol/rodata_80257824.o -dol/sdata2_80389104.o -eggVideo.o -dol/init_80004000.o -dol/extab_80006460.o -dol/extabindex_80006a20.o -dol/text_80244074.o -dol/ctors_80244e8c.o -dol/dtors_80244ea4.o -dol/rodata_80258560.o -dol/data_802a4004.o -dol/bss_80384bf4.o -dol/sdata_803857f6.o -dol/sbss_80386f90.o -dol/sdata2_80389118.o -dol/sbss2_80389140.o \ No newline at end of file diff --git a/link.lcf b/pack/dol.base.lcf similarity index 93% rename from link.lcf rename to pack/dol.base.lcf index cd3cd5447..59ac4b425 100644 --- a/link.lcf +++ b/pack/dol.base.lcf @@ -30,9 +30,22 @@ __start = 0x800060A4; __destroy_global_chain=0x80021350; OSPanic=0x801A2660; + +cosf=0x8001b590; +sinf=0x8001ba98; +tanf=0x8001bb64; + +_savegpr_22=0x8002158C; +_savegpr_23=0x80021590; +_savegpr_24=0x80021594; +_savegpr_25=0x80021598; _savegpr_26=0x8002159C; _savegpr_27=0x800215A0; +_restgpr_22=0x800215D8; +_restgpr_23=0x800215DC; +_restgpr_24=0x800215E0; +_restgpr_25=0x800215E4; _restgpr_26=0x800215E8; _restgpr_27=0x800215EC; OSReport=0x801A25D0; @@ -130,4 +143,4 @@ FORCEACTIVE { initialize__Q23EGG5VideoFPC15GXRenderModeObjPCPC15GXRenderModeObj getTickPerVRetrace__Q23EGG5VideoFUl getTickPerVRetrace__Q23EGG5VideoFv -} \ No newline at end of file +} diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt new file mode 100644 index 000000000..6ea352bca --- /dev/null +++ b/pack/dol_objects.txt @@ -0,0 +1,66 @@ +out\dol\text_800072c0.o +out\utList.o +out\dol\text_800af1a0.o +out\dol\data_80258580.o +out\dol\sbss_80385fc0.o +out\dwc_error.o +out\dol\text_800ccc80.o +out\dol\data_80275758.o +out\dol\sdata_80384c00.o +out\rvlArchive.o +out\dol\text_80124e80.o +out\rvlMemList.o +out\dol\text_80199d04.o +out\dol\data_8027e772.o +out\eggAllocator.o +out\dol\bss_802a4080.o +out\dol\sbss_803862b0.o +out\eggArchive.o +out\dol\text_8020fcc4.o +out\dol\data_802a268c.o +out\eggDisposer.o +out\dol\text_8021a1b8.o +out\dol\rodata_80244ec0.o +out\dol\data_802a2b54.o +out\eggExpHeap.o +out\dol\text_80226f04.o +out\dol\data_802a3024.o +out\dol\sbss_80386d84.o +out\eggGraphicsFifo.o +out\dol\rodata_8025771a.o +out\dol\data_802a30bc.o +out\dol\bss_803832e4.o +out\dol\sbss_80386e99.o +out\dol\sdata2_80386fa0.o +out\eggHeap.o +out\dol\text_80229fac.o +out\eggQuat.o +out\dol\text_80239e10.o +out\dol\data_802a30ec.o +out\eggStreamDecomp.o +out\dol\text_80242504.o +out\dol\data_802a3f90.o +out\dol\bss_80384348.o +out\eggThread.o +out\eggUnitHeap.o +out\dol\ctors_80244de0.o +out\dol\bss_80384b6c.o +out\dol\sbss_80386ec0.o +out\dol\sdata2_80388d80.o +out\eggVector.o +out\dol\rodata_80257824.o +out\dol\sdata2_80389104.o +out\eggVideo.o +out\dol\init_80004000.o +out\dol\extab_80006460.o +out\dol\extabindex_80006a20.o +out\dol\text_80244074.o +out\dol\ctors_80244e8c.o +out\dol\dtors_80244ea4.o +out\dol\rodata_80258560.o +out\dol\data_802a4004.o +out\dol\bss_80384bf4.o +out\dol\sdata_803857f6.o +out\dol\sbss_80386f90.o +out\dol\sdata2_80389118.o +out\dol\sbss2_80389140.o diff --git a/artifacts/pal/segments.csv b/pack/dol_segments.csv similarity index 100% rename from artifacts/pal/segments.csv rename to pack/dol_segments.csv diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv new file mode 100644 index 000000000..5c3fd56d5 --- /dev/null +++ b/pack/dol_slices.csv @@ -0,0 +1,22 @@ +enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End +1,source/nw4r/ut/utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, +1,source/dwc/common/dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, +1,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, +,rxFrameHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, +1,source/rvl/mem/rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, +1,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680 ,,,,,,,,,, +1,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, +1,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54 ,,,,,,,,,, +1,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, +1,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, +1,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68 ,0x80388D80,, +1,source/egg/math/eggQuat.cpp,,,,,,,0x80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, +,source/egg/core/eggScene.cpp,,,,,,,0x8023AD10,0x8023ADDC,,,,,,,,,,,,,,,,,, +1,source/egg/core/eggStreamDecomp.cpp,,,,,,,0x80242498,0x80242504,,,,,,,0x802A3F78,0x802A3F90,,,,,,,,,, +,source/egg/core/eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,0x80386F60,0x80386F64,,,, +1,source/egg/core/eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, +1,source/egg/core/eggUnitHeap.cpp,,,,,,,0x80243754,0x80243A00,,,,,,,0x802A3FD8,0x802A4004,,,,,,,,,, +1,source/egg/math/eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,0x80384B70,0x80384BF4,,,0x80386F78,0x80386F90,0x803890F8 ,0x80389104,, +1,source/egg/core/eggVideo.cpp,,,,,,,0x80243D18,0x80244074,,,,,0x802582E0,0x80258560,,,,,,,,,0x80389108,0x80389118,, +,eggXfb.cpp,,,,,,,0x80244160,0x80244200,,,,,,,,,,,,,,,,,, +,eggXfbManager.cpp,,,,,,,0x80244200,0x802443AC,,,,,,,,,,,,,,,,,, diff --git a/rel_link.lcf b/pack/rel.base.lcf similarity index 100% rename from rel_link.lcf rename to pack/rel.base.lcf diff --git a/pack/rel_objects.txt b/pack/rel_objects.txt new file mode 100644 index 000000000..95b901f8d --- /dev/null +++ b/pack/rel_objects.txt @@ -0,0 +1,12 @@ +out\rel\text_805103b4.o +out\rel\data_808b2bd0.o +out\rel\bss_809bd6e0.o +out\JmpResourceCourse.o +out\rel\text_805127ec.o +out\MessageGroup.o +out\rel\text_805f8b90.o +out\rel\ctors_8088f400.o +out\rel\dtors_8088f704.o +out\rel\rodata_8088f710.o +out\rel\data_808b2c3c.o +out\rel\bss_809bd6ec.o diff --git a/artifacts/pal/rel_segments.csv b/pack/rel_segments.csv similarity index 100% rename from artifacts/pal/rel_segments.csv rename to pack/rel_segments.csv diff --git a/pack/rel_slices.csv b/pack/rel_slices.csv new file mode 100644 index 000000000..e03fa68f1 --- /dev/null +++ b/pack/rel_slices.csv @@ -0,0 +1,3 @@ +enabled,name,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd +1,source/game/jmap/JmpResourceCourse.cpp,0x80512694,0x805127ec,,,,,,,0x808B2C30,0x808B2C3C,0x809BD6E8,0x809BD6EC +1,source/game/ui/MessageGroup.cpp,0x805F8B34,0x805F8B90,,,,,,,,,, diff --git a/percent_decompiled.py b/percent_decompiled.py deleted file mode 100644 index 260820a46..000000000 --- a/percent_decompiled.py +++ /dev/null @@ -1,122 +0,0 @@ -import csv - -def process_line(tags, items): - name = "Untitled" - start = None - code_total = 0 - data_total = 0 - - for tag, entry in zip(tags, items): - if tag == 'enabled' and entry == 0: - return - elif tag == 'name': - name = entry - continue - - is_code = 'text' in tag - - if 'Start' in tag: - if not entry: - start = None - continue - - start = int(entry, 16) - continue - - if 'End' in tag and start: - size = int(entry, 16) - start - - if is_code: - code_total += size - else: - data_total += size - - name = items[1] - return name, code_total, data_total - - -def parse_slices(path): - with open(path, 'r') as file: - lines = file.readlines() - - tags = lines[0].split(',') - for line in lines[1:]: - yield process_line(tags, line.split(',')) - -def simple_count(path): - code_total = 0 - data_total = 0 - - for o_name, o_code_total, o_data_total in parse_slices(path): - code_total += o_code_total - data_total += o_data_total - - return code_total, data_total - -def segments_of(path): - with open(path, 'r') as file: - reader = csv.DictReader(file) - for line in reader: - is_code = 'text' in line["name"] - yield is_code, int(line["end"], 16) - int(line["start"], 16) - -def binary_total(path): - segments = list(segments_of(path)) - - num_code = sum(size if is_code else 0 for is_code, size in segments) - num_data = sum(size if not is_code else 0 for is_code, size in segments) - - return num_code, num_data - - -dol_progress = simple_count("system/slices.csv") -rel_progress = simple_count("system/rel_slices.csv") - -dol_total = binary_total("artifacts/pal/segments.csv") -rel_total = binary_total("artifacts/pal/rel_segments.csv") - -def to_percent(frac): - return round(frac * 100_000) / 1000 - -def analyze(prefix, progress, total): - # print("%s %s bytes (%s%%) of code, %s bytes (%s%%) of data decompiled" % ( - # prefix, - # progress[0], to_percent(progress[0] / total[0]) if total[0] else '', - # progress[1], to_percent(progress[1] / total[1]) if total[1] else '' - # )) - print("%s %s%% code, %s%% data decompiled" % ( - prefix, - to_percent(progress[0] / total[0]) if total[0] else '', - to_percent(progress[1] / total[1]) if total[1] else '' - )) - -analyze('[DOL]', dol_progress, dol_total) - -egg_progress = [0, 0] -for o_name, o_code_total, o_data_total in parse_slices("system/slices.csv"): - if 'egg' not in o_name: - continue - - egg_progress[0] += o_code_total - egg_progress[1] += o_data_total - -egg_total = [0x80244DD4-0x8020F62C, None] - -analyze(' -> [EGG]', egg_progress, egg_total) - -analyze('[REL]', rel_progress, rel_total) - -def piecewise_add(x, y): - return list(a + b for a, b in zip(x, y)) - -analyze('--- main.dol + StaticR.rel ---\n', - piecewise_add(dol_progress, rel_progress), - piecewise_add(dol_total, rel_total)) - -print('------') -print('Player:') -print(' - %u BR (main.dol)' % (dol_progress[0] / dol_total[0] * 4999 + 5000)) -print(' - %u VR (StaticR.rel)' % (rel_progress[0] / rel_total[0] * 4999 + 5000)) - -print(dol_total[0] / 4999 / 4) -print(rel_total[0] / 4999 / 4) \ No newline at end of file diff --git a/structure.md b/structure.md new file mode 100644 index 000000000..b6aef76bc --- /dev/null +++ b/structure.md @@ -0,0 +1,59 @@ +# Project Structure + +## Build parts + +### `./artifacts`: Build final outputs + +Contains the reference artifacts from the original game, and the generated final output. + +- `./artifacts/orig/pal/main.dol`: Reference main executable. +- `./artifacts/orig/pal/StaticR.rel`: Reference library. +- `./artifacts/orig/pal/rel/*.bin`: Segments of reference library. +- `./artifacts/target/pal/main.dol`: Ouptut main executable. +- `./artifacts/target/pal/StaticR.rel`: Output library. + +### `./pack`: Link instructions + +Specifies which sections are decompiled and holds information needed to reconstruct final binaries. + +- `./pack/dol_segments.csv`: Table of main.dol segments +- `./pack/dol_slices.csv`: Table of main.dol decompiled sections +- `./pack/dol_objects.txt`: List of main.dol reconstructed objects (in order) +- `./pack/rel_segments.csv`: Table of StaticR.rel segments +- `./pack/rel_slices.csv`: Table of StaticR.rel decompiled sections +- `./pack/dol_objects.txt`: List of StaticR.rel reconstructed objects (in order) + +### `./asm`: Binary blobs + +Contains diassembled binary blobs. + +- `./asm/dol/*.bin`: Main binary blobs. +- `./asm/dol/*.s`: Main assembly script. +- `./asm/dol/*.o`: Main binary blob object for linking. +- `./asm/rel/*.bin`: Library binary blobs. +- `./asm/rel/*.s`: Library assembly script. +- `./asm/rel/*.o`: Library binary blob object for linking. + +### `./source`: Source code + +Reconstructed/decompiled source code. + +- `./source/**/*.{h,c}`: C headers / code. +- `./source/**/*.{hpp,cpp}`: C++ headers / code. +- `./source/**/*.o`: Compiled C/C++ code. + +### `./out`: Object files + +Compiled / assembled object files. + +- `./out/*.o`: C/C++ reconstructed sources. +- `./out/dol/*.o`: DOL binary blobs. +- `./out/rel/*.o`: REL binary blobs. + +### `./tools`: Compiler tooling + +Default path of various compiler tooling. + +### `./mkwutil`: Python utilities + +Various Python utilities packed in a module. diff --git a/system/ppc_dis.py b/system/ppc_dis.py deleted file mode 100644 index 28f37bdc0..000000000 --- a/system/ppc_dis.py +++ /dev/null @@ -1,276 +0,0 @@ -# Based on https://gist.github.com/camthesaxman/a36f610dbf4cc53a874322ef146c4123 -# -# Changes: -# - Blacklisted more instructionss -# - Adjusted output to take up fewer bytes -# - mwasm support - -from capstone import * -from capstone.ppc import * - - -def sign_extend_16(value): - if value > 0 and (value & 0x8000): - value -= 0x10000 - return value - -def sign_extend_12(value): - if value > 0 and (value & 0x800): - value -= 0x1000 - return value - - -cs = Cs(CS_ARCH_PPC, CS_MODE_32 | CS_MODE_BIG_ENDIAN) -cs.detail = True -cs.imm_unsigned = False - -blacklistedInsns = { - # Unsupported instructions - PPC_INS_VMSUMSHM, PPC_INS_VMHADDSHS, PPC_INS_XXSLDWI, PPC_INS_VSEL, - PPC_INS_XVSUBSP, PPC_INS_XXSEL, PPC_INS_XVMULSP, PPC_INS_XVDIVSP, - PPC_INS_VADDUHM, PPC_INS_XXPERMDI, PPC_INS_XVMADDASP, PPC_INS_XVMADDMSP, - PPC_INS_XVCMPGTSP, PPC_INS_XXMRGHD, PPC_INS_XSMSUBMDP, PPC_INS_XSTDIVDP, - PPC_INS_XVADDSP, PPC_INS_XVCMPEQSP, PPC_INS_XVMSUBASP, PPC_INS_XVCMPGESP, - - PPC_INS_XSMSUBADP, PPC_INS_XSMADDADP, PPC_INS_XSCMPODP, PPC_INS_XVTDIVSP, - PPC_INS_VMRGHB, PPC_INS_VADDUBM, PPC_INS_XSMADDMDP, PPC_INS_XSCMPUDP, - PPC_INS_XVNMADDASP, PPC_INS_VADDUWM, PPC_INS_XVMSUBMSP, - PPC_INS_XSNMSUBMDP, PPC_INS_XSNMSUBADP, PPC_INS_XVCMPEQDP, - PPC_INS_VPKUHUM, PPC_INS_XVCMPGTDP, PPC_INS_XVMADDADP, PPC_INS_XVMSUBMDP, - PPC_INS_VMADDFP, PPC_INS_XXSPLTW, PPC_INS_XVMSUBADP, PPC_INS_XVCMPGEDP, - PPC_INS_XVNMSUBMDP, PPC_INS_XVMADDMDP, PPC_INS_XXMRGHW, - - PPC_INS_XVTDIVDP, PPC_INS_XXMRGLW, PPC_INS_XVADDDP, PPC_INS_XXLAND, - PPC_INS_XVNMSUBASP, PPC_INS_XVNMSUBMSP, - PPC_INS_XVNMADDADP, PPC_INS_XVNMADDMSP, - PPC_INS_XSSUBDP, - - - # Instructions that Capstone gets wrong - PPC_INS_MFESR, PPC_INS_MFDEAR, PPC_INS_MTESR, PPC_INS_MTDEAR, PPC_INS_MFICCR, PPC_INS_MFASR -} - -labels = set() -labelNames = {} - - -def addr_to_label(addr, insn_addr): - if addr in labels: - if addr in labelNames: - return labelNames[addr] - else: - return "lbl_%08X" % addr - else: - return hex(addr - insn_addr) - -def insn_to_text(insn, raw): - # Probably data, not a real instruction - if insn.id == PPC_INS_BDNZ and (insn.bytes[0] & 1): - return None - if insn.id in {PPC_INS_B, PPC_INS_BL, PPC_INS_BDZ, PPC_INS_BDNZ}: - return "%s %s" % (insn.mnemonic, addr_to_label(insn.operands[0].imm, insn.address)) - elif insn.id == PPC_INS_BC: - branchPred = '+' if (insn.bytes[1] & 0x20) else '' - if insn.operands[0].type == PPC_OP_IMM: - return "%s%s %s" % (insn.mnemonic, branchPred, addr_to_label(insn.operands[0].imm, insn.address)) - elif insn.operands[1].type == PPC_OP_IMM: - return "%s%s %s, %s" % (insn.mnemonic, branchPred, insn.reg_name(insn.operands[0].value.reg), addr_to_label(insn.operands[1].imm, insn.address)) - - # Sign-extend immediate values because Capstone is an idiot and doesn't do that automatically - if insn.id in {PPC_INS_ADDI, PPC_INS_ADDIC, PPC_INS_SUBFIC, PPC_INS_MULLI} and (insn.operands[2].imm & 0x8000): - return "%s %s, %s, %i" % (insn.mnemonic, insn.reg_name(insn.operands[0].reg), insn.reg_name(insn.operands[1].value.reg), insn.operands[2].imm - 0x10000) - elif (insn.id == PPC_INS_LI or insn.id == PPC_INS_CMPWI) and (insn.operands[1].imm & 0x8000): - return "%s %s, %i" % (insn.mnemonic, insn.reg_name(insn.operands[0].reg), insn.operands[1].imm - 0x10000) - # cntlz -> cntlzw - elif insn.id == PPC_INS_CNTLZW: - return "cntlzw %s" % insn.op_str - elif insn.id == PPC_INS_MTICCR: - return 'mtictc %s' % insn.op_str - # Dunno why GNU assembler doesn't accept this - elif insn.id == PPC_INS_LMW and insn.operands[0].reg == PPC_REG_R0: - return '.4byte 0x%08X /* illegal %s %s */' % (raw, insn.mnemonic, insn.op_str) - return '%s %s' % (insn.mnemonic, insn.op_str) - -def disasm_ps(inst): - RA = ((inst >> 16) & 0x1f) - RB = ((inst >> 11) & 0x1f) - FA = ((inst >> 16) & 0x1f) - FB = ((inst >> 11) & 0x1f) - FC = ((inst >> 6) & 0x1f) - FD = ((inst >> 21) & 0x1f) - FS = ((inst >> 21) & 0x1f) - IX = ((inst >> 7) & 0x7) - WX = ((inst >> 10) & 0x1) - - opcode = (inst >> 1) & 0x1F - if opcode == 6: # doesn't seem to be used - mnemonic = 'psq_lux' if inst & 0x40 else 'psq_lx' - return '%s f%i, r%i, r%i, %i, qr%i' % (mnemonic, FD, RA, RB, WX, IX) - if opcode == 7: - mnemonic = 'psq_stux' if inst & 0x40 else 'psq_stx' - return '%s f%i, r%i, r%i, %i, qr%i' % (mnemonic, FS, RA, RB, WX, IX) - if opcode == 18: - return 'ps_div f%i, f%i, f%i' % (FD, FA, FB) - if opcode == 20: - return 'ps_sub f%i, f%i, f%i' % (FD, FA, FB) - if opcode == 21: - return 'ps_add f%i, f%i, f%i' % (FD, FA, FB) - if opcode == 23: - return 'ps_sel f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 24: - return 'ps_res f%i, f%i' % (FD, FB) - if opcode == 25: - return 'ps_mul f%i, f%i, f%i' % (FD, FA, FC) - if opcode == 26: - return 'ps_rsqrte f%i, f%i' % (FD, FB) - if opcode == 28: - return 'ps_msub f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 29: - return 'ps_madd f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 30: - return 'ps_nmsub f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 31: - return 'ps_nmadd f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 10: - return 'ps_sum0 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 11: - return 'ps_sum1 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 12: - return 'ps_muls0 f%i, f%i, f%i' % (FD, FA, FC) - if opcode == 13: - return 'ps_muls1 f%i, f%i, f%i' % (FD, FA, FC) - if opcode == 14: - return 'ps_madds0 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - if opcode == 15: - return 'ps_madds1 f%i, f%i, f%i, f%i' % (FD, FA, FC, FB) - - opcode = (inst >> 1) & 0x3FF - if opcode == 40: - return 'ps_neg f%i, f%i' % (FD, FB) - if opcode == 72: - return 'ps_mr f%i, f%i' % (FD, FB) - if opcode == 136: - return 'ps_nabs f%i, f%i' % (FD, FB) - if opcode == 264: - return 'ps_abs f%i, f%i' % (FD, FB) - if opcode in {0, 32, 64, 96}: - mnemonics = ['ps_cmpu0', 'ps_cmpo0', 'ps_cmpu1', 'ps_cmpo1'] - mnemonic = mnemonics[(inst >> 6) & 3] - i = (inst & 0x03800000) >> 23 - return '%s cr%i, f%i, f%i' % (mnemonic, i, FA, FB) - if opcode == 528: - return 'ps_merge00 f%i, f%i, f%i' % (FD, FA, FB) - if opcode == 560: - return 'ps_merge01 f%i, f%i, f%i' % (FD, FA, FB) - if opcode == 592: - return 'ps_merge10 f%i, f%i, f%i' % (FD, FA, FB) - if opcode == 624: - return 'ps_merge11 f%i, f%i, f%i' % (FD, FA, FB) - if opcode == 1014: - if not (inst & 0x03e00000): - if (inst & 1) == 0: - return 'dcbz_l r%i, r%i' % ((inst & 0x001f0000) >> 16, (inst & 0x0000f800) >> 11) - return None - -def disasm_ps_mem(inst, idx): - RA = ((inst >> 16) & 0x1f) - RS = ((inst >> 21) & 0x1f) - I = ((inst >> 12) & 0x7) - W = ((inst >> 15) & 0x1) - disp = sign_extend_12(inst & 0xFFF) - if idx == 56: - mnemonic = 'psq_l' - if idx == 57: - mnemonic = 'psq_lu' - if idx == 60: - mnemonic = 'psq_st' - if idx == 61: - mnemonic = 'psq_stu' - return '%s f%i, %i(r%i), %i, qr%i' % (mnemonic, RS, disp, RA, W, I) - -def disasm_fcmp(inst): - crd = (inst & 0x03800000) >> 23 - a = (inst & 0x001f0000) >> 16 - b = (inst & 0x0000f800) >> 11 - return 'fcmpo cr%i, f%i, f%i' % (crd, a, b) - -def disasm_mspr(inst, mode): - if (inst & 1): - return None - d = (inst & 0x03e00000) >> 21 - a = (inst & 0x001f0000) >> 16 - b = (inst & 0x0000f800) >>11 - spr = (b << 5) + a - if mode: - return 'mtspr 0x%X, r%i' % (spr, d) - else: - return 'mfspr r%i, 0x%X' % (d, spr) - -def disasm_mcrxr(inst): - if (inst & 0x007ff801): - return None - crd = (inst & 0x03800000) >> 23 - return 'mcrxr cr%i' % crd - -# Disassemble code -def disassemble_callback(filecontent, address, offset, insn, bytes): - prefixComment = '/* %08X %02X %02X %02X %02X */' % (address, bytes[0], bytes[1], bytes[2], bytes[3]) - #prefixComment = '/* %08X */' % address - asm = None - def read_u32b(filecontent, offset): - return (filecontent[offset + 0] << 24) | (filecontent[offset + 1] << 16) | (filecontent[offset + 2] << 8) | filecontent[offset + 3] - - raw = read_u32b(filecontent, offset) - if insn != None: - asm = insn_to_text(insn, raw) - else: # Capstone couldn't disassemble it - idx = (raw & 0xfc000000) >> 26 - idx2 = (raw & 0x000007fe) >> 1 - # mtspr - if idx == 31 and idx2 == 467: - asm = disasm_mspr(raw, 1) - # mfspr - elif idx == 31 and idx2 == 339: - asm = disasm_mspr(raw, 0) - # mcrxr - elif idx == 31 and idx2 == 512: - asm = disasm_mcrxr(raw) - # fcmpo - elif idx == 63 and idx2 == 32: - asm = disasm_fcmp(raw) - # Paired singles - elif idx == 4: - asm = disasm_ps(raw) - elif idx in {56, 57, 60, 61}: - asm = disasm_ps_mem(raw, idx) - if asm == None: - asm = '.4byte 0x%08X /* unknown instruction */' % raw - print('%s\t%s' % (prefixComment, asm)) - - -''' -entryPoint = base.entry_point -# Add entry point -labels.add(entryPoint) -labelNames[entryPoint] = '__start' -''' - -# Calls callback for every instruction in the specified code section -def disasm_iter(filecontent, offset, address, size, callback): - if size == 0: - return - start = address - end = address + size - while address < end: - code = filecontent[offset + (address-start) : offset + size] - for insn in cs.disasm(code, address): - address = insn.address - if insn.id in blacklistedInsns: - callback(filecontent, address, offset + address - start, None, insn.bytes) - else: - callback(filecontent, address, offset + address - start, insn, insn.bytes) - address += 4 - if address < end: - o = offset + address - start - callback(filecontent, address, offset + address - start, None, filecontent[o : o + 4]) - address += 4 \ No newline at end of file diff --git a/system/rel_repack.py b/system/rel_repack.py deleted file mode 100644 index d9f3f1a88..000000000 --- a/system/rel_repack.py +++ /dev/null @@ -1,377 +0,0 @@ -import io -import os -import struct -import sys -from collections import OrderedDict - -# Change directory to script folder to make relative repo paths work. -os.chdir(sys.path[0]) - -REL_BASE = 0x805102e0 - -ul = lambda f : struct.unpack(">L", f.read(4))[0] -us = lambda f : struct.unpack(">H", f.read(2))[0] -uc = lambda f : struct.unpack(">B", f.read(1))[0] -ubool = lambda f : struct.unpack(">?", f.read(1))[0] - -pl = lambda d : struct.pack(">L", d) -ps = lambda d : struct.pack(">H", d) -pc = lambda d : struct.pack(">B", d) -pbool = lambda d : struct.pack(">?", d) - -def hprint(val): - print(hex(val)) - - -def src_artifact(file): - return os.path.join("../artifacts/pal/", file) - -def rel_component(file): - return os.path.join("../tmp/", file) - -class Rel: - def __init__(self, filename=None): - if filename is None: - with open("rel_header.bin", "rb") as f: - self.header = RelHeader(f) - self.sectionInfo = [RelSection() for i in range(17)] - self.relocations = [RelRelData(), RelRelData()] - self.imps = [RelImpEntry(), RelImpEntry()] - return - - with open(filename, "rb") as f: - # header - self.header = RelHeader(f) - - # section info stuff - self.sectionInfo = [None] * self.header.numSections - for i in range (0, self.header.numSections): - section = RelSection(f, self.header.sectionInfoOffset, i) - self.sectionInfo[i] = section - - # dumb way to divide by 8 and keep int - self.imps = [None] * (self.header.impSize >> 3) - for i in range(0, self.header.impSize >> 3): - self.imps[i] = RelImpEntry(f, self.header.impOffset, i) - - # assertion i guess - assert(self.header.relOffset == self.imps[0].offset) - - # relocation data - self.relocations = [None] * (self.header.impSize >> 3) - for i in range(0, self.header.impSize >> 3): - self.relocations[i] = RelRelData(f, self.imps[i].offset) - - def reconstruct(self, filename): - rel = io.BytesIO() - # write temporary header - rel.write(b'\x41' * 0x4C) - sectionTableOffset = rel.tell() - - # write temporary section table entries - rel.write( b'\x41' * (len(self.sectionInfo) * 0x8) ) - - # write section data - sectionStartOffset = rel.tell() - offset = 0 - for s in self.sectionInfo: - if s.existsInRel(): - # recalculate offset - s.offset = offset + sectionStartOffset - rel.write( s.data ) - # fuck it this is being hardcoded - offset += s.length - if s.length == 0xC: - rel.write(b'\0' * 0x10) - offset += 0x10 - - sectionEndOffset = rel.tell() - - # write section info table - rel.seek(sectionTableOffset, os.SEEK_SET) - for s in self.sectionInfo: - rel.write( s.reconstructTableEntry() ) - - # write temporary imp table - rel.seek(0, os.SEEK_END) - rel.write( b'\x41' * (len(self.imps) * 8)) - - relocationOffsets = [] - # write relocation data - for r in self.relocations: - relocationOffsets.append(rel.tell()) - r.reconstruct(rel) - - # write imp data - rel.seek(sectionEndOffset, os.SEEK_SET) - for i, r in enumerate(relocationOffsets): - rel.write( pl(1 - i) ) # module id (hacky) - rel.write( pl(r) ) # offset - - # finally, the header - # write new values with inconsistent names - self.header.numSections = len(self.sectionInfo) - self.header.sectionInfoOffset = sectionTableOffset - self.header.relOffset = relocationOffsets[0] - self.header.impOffset = sectionEndOffset - self.impSize = len(self.imps) * 8 - - header = self.header.reconstruct() - rel.seek(0, os.SEEK_SET) - rel.write(header) - - # write to file - with open(filename, "wb") as f: - f.write(rel.getbuffer()) - - def dump_reloc(self, index, filename): - rel = io.BytesIO() - self.relocations[index].reconstruct(rel) - - with open(filename, "wb") as f: - f.write(rel.getbuffer()) - - def load_reloc(self, index, filename): - with open(filename, "rb") as f: - self.relocations[index] = RelRelData(f, 0) - - def dump_section(self, index, filename): - with open(filename, "wb") as f: - s = self.sectionInfo[index] - if s.offset != 0: - f.write(s.data) - else: - f.write(b'\0' * s.length) - - def load_section(self, index, filename): - with open(filename, "rb") as f: - s = RelSection() - s.data = f.read() - s.length = len(s.data) - self.sectionInfo[index] = s - - - -class RelHeader: - def __init__(self, f): - f.seek(0, os.SEEK_SET) # just in case - # unpack header - self.id = ul(f) - self.next = ul(f) - self.prev = ul(f) - self.numSections = ul(f) - self.sectionInfoOffset = ul(f) - self.nameOffset = ul(f) - self.nameSize = ul(f) - self.version = ul(f) - self.bssSize = ul(f) - self.relOffset = ul(f) - self.impOffset = ul(f) - self.impSize = ul(f) - self.prologSection = ubool(f) - self.epilogSection = ubool(f) - self.unresolvedSection = ubool(f) - self.bssSection = ubool(f) - self.prolog = ul(f) - self.epilog = ul(f) - self.unresolved = ul(f) - if self.version >= 2: - self.align = ul(f) - self.bssAlign = ul(f) - if self.version >= 3: - self.fixSize = ul(f) - - def reconstruct(self): - header = io.BytesIO() - header.write(pl(self.id)) - header.write(pl(self.next)) - header.write(pl(self.prev)) - header.write(pl(self.numSections)) - header.write(pl(self.sectionInfoOffset)) - header.write(pl(self.nameOffset)) - header.write(pl(self.nameSize)) - header.write(pl(self.version)) - header.write(pl(self.bssSize)) - header.write(pl(self.relOffset)) - header.write(pl(self.impOffset)) - header.write(pl(self.impSize)) - header.write(pbool(self.prologSection)) - header.write(pbool(self.epilogSection)) - header.write(pbool(self.unresolvedSection)) - header.write(pbool(self.bssSection)) - header.write(pl(self.prolog)) - header.write(pl(self.epilog)) - header.write(pl(self.unresolved)) - header.write(pl(self.align)) - header.write(pl(self.bssAlign)) - header.write(pl(self.fixSize)) - return header.getvalue() - - - -class RelSection: - def __init__(self, f=None, sectionInfoOffset=None, sid=None): - if f is None: - self.offset = 0 - self.unk = False - self.executable = False - self.length = 0 - self.data = None - return - - # get the section stuff - f.seek(sectionInfoOffset + (sid * 0x8), os.SEEK_SET) - self.offset = ul(f) - # dumb - self.unk = self.offset & 2 - self.executable = self.offset & 1 - self.offset &= ~3 - self.length = ul(f) - - # data - # skip empty shit and bss - print(f"section {sid}: {self.offset:X} {self.executable} {self.unk:X} {self.length:X}") - if self.offset == 0 or self.length == 0: - return - - f.seek(self.offset, os.SEEK_SET) - self.data = f.read(self.length) - - def existsInRel(self): - return (self.offset != 0 and self.length != 0) - - def reconstructTableEntry(self): - # recalculate length - if self.offset != 0: - self.length = len(self.data) - - entry = io.BytesIO() - word = self.offset | (self.unk << 1) | self.executable - entry.write( pl(word) ) - entry.write( pl(self.length) ) - return entry.getvalue() - - -class RelImpEntry: - def __init__(self, f=None, impOffset=None, sid=None): - if f is None: - self.moduleId = 0 - self.offset = 0 - return - - # does a thing - f.seek(impOffset + (sid * 8), os.SEEK_SET) - self.moduleId = ul(f) - self.offset = ul(f) - print(f"imp {sid}: {self.moduleId:X} {self.offset:X}") - -# funny name -class RelRelData: - def __init__(self, f=None, relOffset=None): - if f is None: - self.entries = OrderedDict() - return - - f.seek(relOffset, os.SEEK_SET) - self.entries = OrderedDict() - - counter = 0 - section = 0 - while True: - entry = RelRelEntry(f) - - #R_RVL_SECT - if entry.type == 202: - # print("R_RVL_SECT", counter, entry.section) - section = entry.section - self.entries[section] = [] - continue - - self.entries[section].append(entry) - - # R_RVL_STOP - if entry.type == 203: - # print("R_RVL_STOP") - break - - counter += 1 - - def reconstruct(self, f): - for e in self.entries: - # write R_RVL_SECT - f.write( ps(0) ) - f.write( pc(202) ) - f.write( pc(e) ) # section id - - # write R_RVL_NOP - f.write( b'\0' * 4 ) - - # write actual entries - for ee in self.entries[e]: - entry = ee.reconstruct() - f.write(entry) - - -class RelRelEntry: - def __init__(self, f): - self.offset = us(f) - self.type = uc(f) - self.section = uc(f) - self.addend = ul(f) - - def reconstruct(self): - entry = io.BytesIO() - entry.write( ps(self.offset) ) - entry.write( pc(self.type) ) - entry.write( pc(self.section) ) - entry.write( pl(self.addend) ) - return entry.getvalue() - - -def DumpStaticR(): - rel = Rel(src_artifact("StaticR.rel")) - - rel.dump_reloc(0, "dol_rel.bin") - rel.dump_reloc(1, "rel_abs.bin") - - rel.dump_section(1, rel_component("text.bin")) - rel.dump_section(2, rel_component("ctors.bin")) - rel.dump_section(3, rel_component("dtors.bin")) - rel.dump_section(4, rel_component("rodata.bin")) - rel.dump_section(5, rel_component("data.bin")) - rel.dump_section(6, rel_component("bss.bin")) - - - -def ReconstructStaticR(): - rel = Rel() - - rel.load_reloc(0, "dol_rel.bin") - rel.load_reloc(1, "rel_abs.bin") - - rel.load_section(1, rel_component("text.bin")) - rel.load_section(2, rel_component("ctors.bin")) - rel.load_section(3, rel_component("dtors.bin")) - rel.load_section(4, rel_component("rodata.bin")) - rel.load_section(5, rel_component("data.bin")) - rel.load_section(6, rel_component("bss.bin")) - - rel.sectionInfo[1].executable = True - rel.sectionInfo[1].offset = 0xD4 - rel.sectionInfo[2].offset = 0x37F120 - rel.sectionInfo[3].offset = 0x37F424 - rel.sectionInfo[4].offset = 0x37F440 - rel.sectionInfo[5].offset = 0x3A28F0 - rel.sectionInfo[6].offset = 0 - - rel.imps[0].moduleId = 1 - rel.imps[0].offset = 0x3CD104 - rel.imps[1].moduleId = 0 - rel.imps[1].offset = 0x4820F4 - - rel.reconstruct("../target/StaticR.rel") - -if __name__ == "__main__": - DumpStaticR() - ReconstructStaticR() - diff --git a/system/rel_slices.csv b/system/rel_slices.csv deleted file mode 100644 index 580a24a08..000000000 --- a/system/rel_slices.csv +++ /dev/null @@ -1,3 +0,0 @@ -enabled,name,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd -1,JmpResourceCourse.cpp,0x80512694,0x805127ec,,,,,,,0x808B2C30,0x808B2C3C,0x809BD6E8,0x809BD6EC -1,MessageGroup.cpp,0x805F8B34,0x805F8B90,,,,,,,,,, diff --git a/system/slices.csv b/system/slices.csv deleted file mode 100644 index 2f26bb67b..000000000 --- a/system/slices.csv +++ /dev/null @@ -1,22 +0,0 @@ -enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End -1,utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, -1,dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, -1,rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, -,rxFrameHeap.c,,,,,,,80199430,801998A4,,,,,,,,,,,,,,,,,, -1,rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, -1,eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,802A2668,802A2680 ,,,,,,,,,, -1,eggArchive.cpp,,,,,,,8020F6EC,0x8020FCC4,,,,,,,802A2680,802A268C,803832D8,0x803832E4,,,80386D80,80386D84,,,, -1,eggDisposer.cpp,,,,,,,8021A0F0,8021A1B8,,,,,,,802A2B48,802A2B54 ,,,,,,,,,, -1,eggExpHeap.cpp,,,,,,,802269A8,80226F04,,,,,80257700,8025771A,802A2FF8,802A3024,,,,,,,,,, -1,eggGraphicsFifo.cpp,,,,,,,80229540,802296A8,,,,,,,802A30B0,802A30BC,,,,,0x80386E90,0x80386E99,,,, -1,eggHeap.cpp,,,,,,,802296A8,80229FAC,,,,,80257740,80257824,802A30C0,0x802a30ec,80384320,0x80384348,,,80386EA0,80386EC0,80388D68 ,80388D80,, -1,eggQuat.cpp,,,,,,,80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, -,eggScene.cpp,,,,,,,8023AD10,8023ADDC,,,,,,,,,,,,,,,,,, -1,eggStreamDecomp.cpp,,,,,,,80242498,80242504,,,,,,,802A3F78,802A3F90,,,,,,,,,, -,eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,80386F60,80386F64,,,, -1,eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, -1,eggUnitHeap.cpp,,,,,,,80243754,80243A00,,,,,,,802A3FD8,802A4004,,,,,,,,,, -1,eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,80384B70,80384BF4,,,80386F78,80386F90,803890F8 ,80389104,, -1,eggVideo.cpp,,,,,,,80243D18,80244074,,,,,802582E0,80258560,,,,,,,,,80389108,80389118,, -,eggXfb.cpp,,,,,,,80244160,80244200,,,,,,,,,,,,,,,,,, -,eggXfbManager.cpp,,,,,,,80244200,802443AC,,,,,,,,,,,,,,,,,, From 824a212de1982caff721a32e5a2fc630fd0391f0 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 19 Jun 2021 18:47:05 +0200 Subject: [PATCH 076/477] Delete unused rel_o_files.txt (#14) --- rel_o_files.txt | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 rel_o_files.txt diff --git a/rel_o_files.txt b/rel_o_files.txt deleted file mode 100644 index 6ae83671d..000000000 --- a/rel_o_files.txt +++ /dev/null @@ -1,12 +0,0 @@ -rel/text_805103b4.o -rel/data_808b2bd0.o -rel/bss_809bd6e0.o -JmpResourceCourse.o -rel/text_805127ec.o -MessageGroup.o -rel/text_805f8b90.o -rel/ctors_8088f400.o -rel/dtors_8088f704.o -rel/rodata_8088f710.o -rel/data_808b2c3c.o -rel/bss_809bd6ec.o \ No newline at end of file From 0291ec35772b241e3bc757bbaf51c8d53a33ee1c Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 19 Jun 2021 19:31:03 +0200 Subject: [PATCH 077/477] Progress nw4r::math (#15) --- pack/dol_objects.txt | 14 +- pack/dol_slices.csv | 2 + source/nw4r/math/mathCast.hpp | 54 ++++ source/nw4r/math/mathTriangular.cpp | 388 ++++++++++++++++++++++++++++ source/nw4r/math/mathTriangular.hpp | 35 +++ source/nw4r/math/mathTypes.cpp | 100 +++++++ source/nw4r/math/mathTypes.h | 70 +++++ sources.py | 2 + 8 files changed, 662 insertions(+), 3 deletions(-) create mode 100644 source/nw4r/math/mathCast.hpp create mode 100644 source/nw4r/math/mathTriangular.cpp create mode 100644 source/nw4r/math/mathTriangular.hpp create mode 100644 source/nw4r/math/mathTypes.cpp create mode 100644 source/nw4r/math/mathTypes.h diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 6ea352bca..69a6bf3d7 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -1,7 +1,15 @@ out\dol\text_800072c0.o +out\dol\rodata_80244ec0.o +out\dol\data_80258580.o +out\dol\sdata2_80386fa0.o +out\mathTriangular.o +out\dol\text_80085578.o +out\dol\sdata2_80387ea4.o +out\mathTypes.o +out\dol\text_80085738.o out\utList.o out\dol\text_800af1a0.o -out\dol\data_80258580.o +out\dol\data_80274250.o out\dol\sbss_80385fc0.o out\dwc_error.o out\dol\text_800ccc80.o @@ -20,7 +28,7 @@ out\dol\text_8020fcc4.o out\dol\data_802a268c.o out\eggDisposer.o out\dol\text_8021a1b8.o -out\dol\rodata_80244ec0.o +out\dol\rodata_80249020.o out\dol\data_802a2b54.o out\eggExpHeap.o out\dol\text_80226f04.o @@ -31,7 +39,7 @@ out\dol\rodata_8025771a.o out\dol\data_802a30bc.o out\dol\bss_803832e4.o out\dol\sbss_80386e99.o -out\dol\sdata2_80386fa0.o +out\dol\sdata2_80387eb0.o out\eggHeap.o out\dol\text_80229fac.o out\eggQuat.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 5c3fd56d5..8c3d5eb2f 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,4 +1,6 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End +1,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, +1,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085738,,,,,,,,,,,,,,,0x80387ea8,0x80387eb0,, 1,source/nw4r/ut/utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, 1,source/dwc/common/dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, 1,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, diff --git a/source/nw4r/math/mathCast.hpp b/source/nw4r/math/mathCast.hpp new file mode 100644 index 000000000..41f479df2 --- /dev/null +++ b/source/nw4r/math/mathCast.hpp @@ -0,0 +1,54 @@ +#pragma once + +namespace nw4r { +namespace math { + +static inline u16 ps_f32_to_u16(register f32 in) { + f32 a; + register f32* ptr = &a; + register u16 r; + + asm + { + psq_st in, 0(ptr), 1, 3 + lhz r, 0(ptr) + } + return r; +} + +static inline void fast_f32_to_u16(register f32* in, + volatile register u16* out) { + *out = ps_f32_to_u16(*in); +} + +static inline f32 ps_u16_to_f32(register u16* in) { + register f32 r; + asm { psq_l r, 0(in), 1, 3 } + return r; +} + +static inline void fast_u16_to_f32(register u16* in, + volatile register f32* out) { + *out = ps_u16_to_f32(in); +} + +inline u16 f32_to_u16(f32 in) { + u16 out; + fast_f32_to_u16(&in, &out); + return out; +} + +inline f32 u16_to_f32(u16 in) { + f32 out; + fast_u16_to_f32(&in, &out); + return out; +} + +inline f32 FAbs(register f32 x) { + register f32 ret; + asm { fabs ret, x } + return ret; +} + +} // namespace math +} // namespace nw4r diff --git a/source/nw4r/math/mathTriangular.cpp b/source/nw4r/math/mathTriangular.cpp new file mode 100644 index 000000000..11f9bec87 --- /dev/null +++ b/source/nw4r/math/mathTriangular.cpp @@ -0,0 +1,388 @@ +#include "mathTriangular.hpp" + +#include "mathCast.hpp" + +namespace nw4r { +namespace math { + +namespace detail { + +// PAL: 0x80248010 +const SinCosSample gSinCosTbl[256 + 1] = { + 0.f, 1.f, 0.024541f, -0.000301f, 0.024541f, 0.999699f, + 0.024526f, -0.000903f, 0.049068f, 0.998795f, 0.024497f, -0.001505f, + 0.073565f, 0.99729f, 0.024453f, -0.002106f, 0.098017f, 0.995185f, + 0.024394f, -0.002705f, 0.122411f, 0.99248f, 0.02432f, -0.003303f, + 0.14673f, 0.989177f, 0.024231f, -0.003899f, 0.170962f, 0.985278f, + 0.024128f, -0.004492f, 0.19509f, 0.980785f, 0.024011f, -0.005083f, + 0.219101f, 0.975702f, 0.023879f, -0.005671f, 0.24298f, 0.970031f, + 0.023733f, -0.006255f, 0.266713f, 0.963776f, 0.023572f, -0.006836f, + 0.290285f, 0.95694f, 0.023397f, -0.007412f, 0.313682f, 0.949528f, + 0.023208f, -0.007984f, 0.33689f, 0.941544f, 0.023005f, -0.008551f, + 0.359895f, 0.932993f, 0.022788f, -0.009113f, 0.382683f, 0.92388f, + 0.022558f, -0.00967f, 0.405241f, 0.91421f, 0.022314f, -0.01022f, + 0.427555f, 0.903989f, 0.022056f, -0.010765f, 0.449611f, 0.893224f, + 0.021785f, -0.011303f, 0.471397f, 0.881921f, 0.021501f, -0.011834f, + 0.492898f, 0.870087f, 0.021205f, -0.012358f, 0.514103f, 0.857729f, + 0.020895f, -0.012875f, 0.534998f, 0.844854f, 0.020573f, -0.013384f, + 0.55557f, 0.83147f, 0.020238f, -0.013885f, 0.575808f, 0.817585f, + 0.019891f, -0.014377f, 0.595699f, 0.803208f, 0.019532f, -0.014861f, + 0.615232f, 0.788346f, 0.019162f, -0.015336f, 0.634393f, 0.77301f, + 0.01878f, -0.015802f, 0.653173f, 0.757209f, 0.018386f, -0.016258f, + 0.671559f, 0.740951f, 0.017982f, -0.016704f, 0.689541f, 0.724247f, + 0.017566f, -0.01714f, 0.707107f, 0.707107f, 0.01714f, -0.017566f, + 0.724247f, 0.689541f, 0.016704f, -0.017982f, 0.740951f, 0.671559f, + 0.016258f, -0.018386f, 0.757209f, 0.653173f, 0.015802f, -0.01878f, + 0.77301f, 0.634393f, 0.015336f, -0.019162f, 0.788346f, 0.615232f, + 0.014861f, -0.019532f, 0.803208f, 0.595699f, 0.014377f, -0.019891f, + 0.817585f, 0.575808f, 0.013885f, -0.020238f, 0.83147f, 0.55557f, + 0.013384f, -0.020573f, 0.844854f, 0.534998f, 0.012875f, -0.020895f, + 0.857729f, 0.514103f, 0.012358f, -0.021205f, 0.870087f, 0.492898f, + 0.011834f, -0.021501f, 0.881921f, 0.471397f, 0.011303f, -0.021785f, + 0.893224f, 0.449611f, 0.010765f, -0.022056f, 0.903989f, 0.427555f, + 0.01022f, -0.022314f, 0.91421f, 0.405241f, 0.00967f, -0.022558f, + 0.92388f, 0.382683f, 0.009113f, -0.022788f, 0.932993f, 0.359895f, + 0.008551f, -0.023005f, 0.941544f, 0.33689f, 0.007984f, -0.023208f, + 0.949528f, 0.313682f, 0.007412f, -0.023397f, 0.95694f, 0.290285f, + 0.006836f, -0.023572f, 0.963776f, 0.266713f, 0.006255f, -0.023733f, + 0.970031f, 0.24298f, 0.005671f, -0.023879f, 0.975702f, 0.219101f, + 0.005083f, -0.024011f, 0.980785f, 0.19509f, 0.004492f, -0.024128f, + 0.985278f, 0.170962f, 0.003899f, -0.024231f, 0.989177f, 0.14673f, + 0.003303f, -0.02432f, 0.99248f, 0.122411f, 0.002705f, -0.024394f, + 0.995185f, 0.098017f, 0.002106f, -0.024453f, 0.99729f, 0.073565f, + 0.001505f, -0.024497f, 0.998795f, 0.049068f, 0.000903f, -0.024526f, + 0.999699f, 0.024541f, 0.000301f, -0.024541f, 1.f, 0.f, + -0.000301f, -0.024541f, 0.999699f, -0.024541f, -0.000903f, -0.024526f, + 0.998795f, -0.049068f, -0.001505f, -0.024497f, 0.99729f, -0.073565f, + -0.002106f, -0.024453f, 0.995185f, -0.098017f, -0.002705f, -0.024394f, + 0.99248f, -0.122411f, -0.003303f, -0.02432f, 0.989177f, -0.14673f, + -0.003899f, -0.024231f, 0.985278f, -0.170962f, -0.004492f, -0.024128f, + 0.980785f, -0.19509f, -0.005083f, -0.024011f, 0.975702f, -0.219101f, + -0.005671f, -0.023879f, 0.970031f, -0.24298f, -0.006255f, -0.023733f, + 0.963776f, -0.266713f, -0.006836f, -0.023572f, 0.95694f, -0.290285f, + -0.007412f, -0.023397f, 0.949528f, -0.313682f, -0.007984f, -0.023208f, + 0.941544f, -0.33689f, -0.008551f, -0.023005f, 0.932993f, -0.359895f, + -0.009113f, -0.022788f, 0.92388f, -0.382683f, -0.00967f, -0.022558f, + 0.91421f, -0.405241f, -0.01022f, -0.022314f, 0.903989f, -0.427555f, + -0.010765f, -0.022056f, 0.893224f, -0.449611f, -0.011303f, -0.021785f, + 0.881921f, -0.471397f, -0.011834f, -0.021501f, 0.870087f, -0.492898f, + -0.012358f, -0.021205f, 0.857729f, -0.514103f, -0.012875f, -0.020895f, + 0.844854f, -0.534998f, -0.013384f, -0.020573f, 0.83147f, -0.55557f, + -0.013885f, -0.020238f, 0.817585f, -0.575808f, -0.014377f, -0.019891f, + 0.803208f, -0.595699f, -0.014861f, -0.019532f, 0.788346f, -0.615232f, + -0.015336f, -0.019162f, 0.77301f, -0.634393f, -0.015802f, -0.01878f, + 0.757209f, -0.653173f, -0.016258f, -0.018386f, 0.740951f, -0.671559f, + -0.016704f, -0.017982f, 0.724247f, -0.689541f, -0.01714f, -0.017566f, + 0.707107f, -0.707107f, -0.017566f, -0.01714f, 0.689541f, -0.724247f, + -0.017982f, -0.016704f, 0.671559f, -0.740951f, -0.018386f, -0.016258f, + 0.653173f, -0.757209f, -0.01878f, -0.015802f, 0.634393f, -0.77301f, + -0.019162f, -0.015336f, 0.615232f, -0.788346f, -0.019532f, -0.014861f, + 0.595699f, -0.803208f, -0.019891f, -0.014377f, 0.575808f, -0.817585f, + -0.020238f, -0.013885f, 0.55557f, -0.83147f, -0.020573f, -0.013384f, + 0.534998f, -0.844854f, -0.020895f, -0.012875f, 0.514103f, -0.857729f, + -0.021205f, -0.012358f, 0.492898f, -0.870087f, -0.021501f, -0.011834f, + 0.471397f, -0.881921f, -0.021785f, -0.011303f, 0.449611f, -0.893224f, + -0.022056f, -0.010765f, 0.427555f, -0.903989f, -0.022314f, -0.01022f, + 0.405241f, -0.91421f, -0.022558f, -0.00967f, 0.382683f, -0.92388f, + -0.022788f, -0.009113f, 0.359895f, -0.932993f, -0.023005f, -0.008551f, + 0.33689f, -0.941544f, -0.023208f, -0.007984f, 0.313682f, -0.949528f, + -0.023397f, -0.007412f, 0.290285f, -0.95694f, -0.023572f, -0.006836f, + 0.266713f, -0.963776f, -0.023733f, -0.006255f, 0.24298f, -0.970031f, + -0.023879f, -0.005671f, 0.219101f, -0.975702f, -0.024011f, -0.005083f, + 0.19509f, -0.980785f, -0.024128f, -0.004492f, 0.170962f, -0.985278f, + -0.024231f, -0.003899f, 0.14673f, -0.989177f, -0.02432f, -0.003303f, + 0.122411f, -0.99248f, -0.024394f, -0.002705f, 0.098017f, -0.995185f, + -0.024453f, -0.002106f, 0.073565f, -0.99729f, -0.024497f, -0.001505f, + 0.049068f, -0.998795f, -0.024526f, -0.000903f, 0.024541f, -0.999699f, + -0.024541f, -0.000301f, 0.f, -1.f, -0.024541f, 0.000301f, + -0.024541f, -0.999699f, -0.024526f, 0.000903f, -0.049068f, -0.998795f, + -0.024497f, 0.001505f, -0.073565f, -0.99729f, -0.024453f, 0.002106f, + -0.098017f, -0.995185f, -0.024394f, 0.002705f, -0.122411f, -0.99248f, + -0.02432f, 0.003303f, -0.14673f, -0.989177f, -0.024231f, 0.003899f, + -0.170962f, -0.985278f, -0.024128f, 0.004492f, -0.19509f, -0.980785f, + -0.024011f, 0.005083f, -0.219101f, -0.975702f, -0.023879f, 0.005671f, + -0.24298f, -0.970031f, -0.023733f, 0.006255f, -0.266713f, -0.963776f, + -0.023572f, 0.006836f, -0.290285f, -0.95694f, -0.023397f, 0.007412f, + -0.313682f, -0.949528f, -0.023208f, 0.007984f, -0.33689f, -0.941544f, + -0.023005f, 0.008551f, -0.359895f, -0.932993f, -0.022788f, 0.009113f, + -0.382683f, -0.92388f, -0.022558f, 0.00967f, -0.405241f, -0.91421f, + -0.022314f, 0.01022f, -0.427555f, -0.903989f, -0.022056f, 0.010765f, + -0.449611f, -0.893224f, -0.021785f, 0.011303f, -0.471397f, -0.881921f, + -0.021501f, 0.011834f, -0.492898f, -0.870087f, -0.021205f, 0.012358f, + -0.514103f, -0.857729f, -0.020895f, 0.012875f, -0.534998f, -0.844854f, + -0.020573f, 0.013384f, -0.55557f, -0.83147f, -0.020238f, 0.013885f, + -0.575808f, -0.817585f, -0.019891f, 0.014377f, -0.595699f, -0.803208f, + -0.019532f, 0.014861f, -0.615232f, -0.788346f, -0.019162f, 0.015336f, + -0.634393f, -0.77301f, -0.01878f, 0.015802f, -0.653173f, -0.757209f, + -0.018386f, 0.016258f, -0.671559f, -0.740951f, -0.017982f, 0.016704f, + -0.689541f, -0.724247f, -0.017566f, 0.01714f, -0.707107f, -0.707107f, + -0.01714f, 0.017566f, -0.724247f, -0.689541f, -0.016704f, 0.017982f, + -0.740951f, -0.671559f, -0.016258f, 0.018386f, -0.757209f, -0.653173f, + -0.015802f, 0.01878f, -0.77301f, -0.634393f, -0.015336f, 0.019162f, + -0.788346f, -0.615232f, -0.014861f, 0.019532f, -0.803208f, -0.595699f, + -0.014377f, 0.019891f, -0.817585f, -0.575808f, -0.013885f, 0.020238f, + -0.83147f, -0.55557f, -0.013384f, 0.020573f, -0.844854f, -0.534998f, + -0.012875f, 0.020895f, -0.857729f, -0.514103f, -0.012358f, 0.021205f, + -0.870087f, -0.492898f, -0.011834f, 0.021501f, -0.881921f, -0.471397f, + -0.011303f, 0.021785f, -0.893224f, -0.449611f, -0.010765f, 0.022056f, + -0.903989f, -0.427555f, -0.01022f, 0.022314f, -0.91421f, -0.405241f, + -0.00967f, 0.022558f, -0.92388f, -0.382683f, -0.009113f, 0.022788f, + -0.932993f, -0.359895f, -0.008551f, 0.023005f, -0.941544f, -0.33689f, + -0.007984f, 0.023208f, -0.949528f, -0.313682f, -0.007412f, 0.023397f, + -0.95694f, -0.290285f, -0.006836f, 0.023572f, -0.963776f, -0.266713f, + -0.006255f, 0.023733f, -0.970031f, -0.24298f, -0.005671f, 0.023879f, + -0.975702f, -0.219101f, -0.005083f, 0.024011f, -0.980785f, -0.19509f, + -0.004492f, 0.024128f, -0.985278f, -0.170962f, -0.003899f, 0.024231f, + -0.989177f, -0.14673f, -0.003303f, 0.02432f, -0.99248f, -0.122411f, + -0.002705f, 0.024394f, -0.995185f, -0.098017f, -0.002106f, 0.024453f, + -0.99729f, -0.073565f, -0.001505f, 0.024497f, -0.998795f, -0.049068f, + -0.000903f, 0.024526f, -0.999699f, -0.024541f, -0.000301f, 0.024541f, + -1.f, -0.f, 0.000301f, 0.024541f, -0.999699f, 0.024541f, + 0.000903f, 0.024526f, -0.998795f, 0.049068f, 0.001505f, 0.024497f, + -0.99729f, 0.073565f, 0.002106f, 0.024453f, -0.995185f, 0.098017f, + 0.002705f, 0.024394f, -0.99248f, 0.122411f, 0.003303f, 0.02432f, + -0.989177f, 0.14673f, 0.003899f, 0.024231f, -0.985278f, 0.170962f, + 0.004492f, 0.024128f, -0.980785f, 0.19509f, 0.005083f, 0.024011f, + -0.975702f, 0.219101f, 0.005671f, 0.023879f, -0.970031f, 0.24298f, + 0.006255f, 0.023733f, -0.963776f, 0.266713f, 0.006836f, 0.023572f, + -0.95694f, 0.290285f, 0.007412f, 0.023397f, -0.949528f, 0.313682f, + 0.007984f, 0.023208f, -0.941544f, 0.33689f, 0.008551f, 0.023005f, + -0.932993f, 0.359895f, 0.009113f, 0.022788f, -0.92388f, 0.382683f, + 0.00967f, 0.022558f, -0.91421f, 0.405241f, 0.01022f, 0.022314f, + -0.903989f, 0.427555f, 0.010765f, 0.022056f, -0.893224f, 0.449611f, + 0.011303f, 0.021785f, -0.881921f, 0.471397f, 0.011834f, 0.021501f, + -0.870087f, 0.492898f, 0.012358f, 0.021205f, -0.857729f, 0.514103f, + 0.012875f, 0.020895f, -0.844854f, 0.534998f, 0.013384f, 0.020573f, + -0.83147f, 0.55557f, 0.013885f, 0.020238f, -0.817585f, 0.575808f, + 0.014377f, 0.019891f, -0.803208f, 0.595699f, 0.014861f, 0.019532f, + -0.788346f, 0.615232f, 0.015336f, 0.019162f, -0.77301f, 0.634393f, + 0.015802f, 0.01878f, -0.757209f, 0.653173f, 0.016258f, 0.018386f, + -0.740951f, 0.671559f, 0.016704f, 0.017982f, -0.724247f, 0.689541f, + 0.01714f, 0.017566f, -0.707107f, 0.707107f, 0.017566f, 0.01714f, + -0.689541f, 0.724247f, 0.017982f, 0.016704f, -0.671559f, 0.740951f, + 0.018386f, 0.016258f, -0.653173f, 0.757209f, 0.01878f, 0.015802f, + -0.634393f, 0.77301f, 0.019162f, 0.015336f, -0.615232f, 0.788346f, + 0.019532f, 0.014861f, -0.595699f, 0.803208f, 0.019891f, 0.014377f, + -0.575808f, 0.817585f, 0.020238f, 0.013885f, -0.55557f, 0.83147f, + 0.020573f, 0.013384f, -0.534998f, 0.844854f, 0.020895f, 0.012875f, + -0.514103f, 0.857729f, 0.021205f, 0.012358f, -0.492898f, 0.870087f, + 0.021501f, 0.011834f, -0.471397f, 0.881921f, 0.021785f, 0.011303f, + -0.449611f, 0.893224f, 0.022056f, 0.010765f, -0.427555f, 0.903989f, + 0.022314f, 0.01022f, -0.405241f, 0.91421f, 0.022558f, 0.00967f, + -0.382683f, 0.92388f, 0.022788f, 0.009113f, -0.359895f, 0.932993f, + 0.023005f, 0.008551f, -0.33689f, 0.941544f, 0.023208f, 0.007984f, + -0.313682f, 0.949528f, 0.023397f, 0.007412f, -0.290285f, 0.95694f, + 0.023572f, 0.006836f, -0.266713f, 0.963776f, 0.023733f, 0.006255f, + -0.24298f, 0.970031f, 0.023879f, 0.005671f, -0.219101f, 0.975702f, + 0.024011f, 0.005083f, -0.19509f, 0.980785f, 0.024128f, 0.004492f, + -0.170962f, 0.985278f, 0.024231f, 0.003899f, -0.14673f, 0.989177f, + 0.02432f, 0.003303f, -0.122411f, 0.99248f, 0.024394f, 0.002705f, + -0.098017f, 0.995185f, 0.024453f, 0.002106f, -0.073565f, 0.99729f, + 0.024497f, 0.001505f, -0.049068f, 0.998795f, 0.024526f, 0.000903f, + -0.024541f, 0.999699f, 0.024541f, 0.000301f, -0.f, 1.f, + 0.024541f, -0.000301f, +}; + +} // namespace detail + +namespace { +// PAL: 0x80274148 +struct { + f32 atan_val; + f32 atan_delta; +} sArcTanTbl[32 + 1] = { + 0.000000000f, 1.272825321f, 1.272825321f, 1.270345790f, 2.543171111f, + 1.265415586f, 3.808586697f, 1.258091595f, 5.066678293f, 1.248457103f, + 6.315135396f, 1.236619467f, 7.551754863f, 1.222707202f, 8.774462065f, + 1.206866624f, 9.981328688f, 1.189258212f, 11.170586901f, 1.170052841f, + 12.340639741f, 1.149428034f, 13.490067775f, 1.127564381f, 14.617632156f, + 1.104642222f, 15.722274378f, 1.080838675f, 16.803113053f, 1.056325088f, + 17.859438141f, 1.031264918f, 18.890703059f, 1.005812061f, 19.896515121f, + 0.980109621f, 20.876624742f, 0.954289072f, 21.830913814f, 0.928469801f, + 22.759383615f, 0.902758952f, 23.662142567f, 0.877251558f, 24.539394125f, + 0.852030871f, 25.391424996f, 0.827168886f, 26.218593881f, 0.802726967f, + 27.021320848f, 0.778756582f, 27.800077430f, 0.755300081f, 28.555377511f, + 0.732391496f, 29.287769007f, 0.710057351f, 29.997826358f, 0.688317453f, + 30.686143811f, 0.667185647f, 31.353329458f, 0.646670542f, 32.000000000f, + 0.626776175f, +}; + +f32 AtanFIdx_(f32 x) { + u16 idx; + f32 val; + f32 r; + + x *= 32.f; + idx = f32_to_u16(x); + r = x - u16_to_f32(idx); + + val = sArcTanTbl[idx].atan_val + r * sArcTanTbl[idx].atan_delta; + + return val; +} +} // namespace + +f32 SinFIdx(f32 fidx) { + f32 abs_fidx; + f32 val; + f32 r; + u16 idx; + + abs_fidx = FAbs(fidx); + while (abs_fidx > 65536.0f) { + abs_fidx -= 65536.0f; + } + + idx = f32_to_u16(abs_fidx); + r = abs_fidx - u16_to_f32(idx); + idx &= 0xff; + + val = detail::gSinCosTbl[idx].sin_val + r * detail::gSinCosTbl[idx].sin_delta; + + return (fidx < 0.0f) ? -val : val; +} + +f32 CosFIdx(f32 fidx) { + u16 idx; + f32 r; + + fidx = FAbs(fidx); + while (fidx > 65536.0f) { + fidx -= 65536.0f; + } + + idx = f32_to_u16(fidx); + r = fidx - u16_to_f32(idx); + idx &= 0xff; + + return detail::gSinCosTbl[idx].cos_val + + r * detail::gSinCosTbl[idx].cos_delta; +} + +void SinCosFIdx(register f32* pSin, register f32* pCos, register f32 fidx) { + register u32 idx; + register f32 abs_fidx; + register f32 r; + register f32 idxmax = 65536.0F; + register __vec2x32float__ scval, scdel; + register __vec2x32float__ result; + + register f32 c_zero; + + const register f32* pTbl = + reinterpret_cast(&detail::gSinCosTbl[0]); + + asm { + fabs abs_fidx, fidx; + psq_st abs_fidx, 0(pSin), 1, 3; + fcmpu cr0, abs_fidx, idxmax; + ble loc2; +loc1: + fsubs abs_fidx, abs_fidx, idxmax; + fcmpu cr0, abs_fidx, idxmax; + bgt loc1; + psq_st abs_fidx, 0(pSin), 1, 3; +loc2: + lhz idx, 0(pSin); + fsubs c_zero, idxmax, idxmax; + rlwinm idx, idx, 4, 20, 27; + add pTbl, pTbl, idx; + psq_l r, 0(pSin), 1, 3; + fsubs r, abs_fidx, r; + psq_l scval, 0(pTbl), 0, 0; + psq_l scdel, 8(pTbl), 0, 0; + ps_madds0 result, scdel, r, scval; + ps_merge10 r, result, result; + psq_st r, 0(pCos), 1, 0; + fcmpu cr0, fidx, c_zero; + bge loc3; + ps_neg result, result; +loc3: + psq_st result, 0(pSin), 1, 0; + } +} + +f32 AtanFIdx(f32 x) { + if (x >= 0.f) { + if (x > 1.f) + return 64.f - AtanFIdx_(1.f / x); + else + return AtanFIdx_(x); + } else { + if (x < -1.f) + return -64.f + AtanFIdx_(-1.f / x); + else + return -AtanFIdx_(-x); + } +} + +// PAL: 0x800853c0 +f32 Atan2FIdx(f32 y, f32 x) { + f32 a; + f32 b; + f32 c; + bool minus; + + if (x == 0.f && y == 0.f) { + return 0.f; + } + + if (x >= 0.f) { + if (y >= 0.f) { + if (x >= y) { + a = x; + b = y; + c = 0.f; + minus = false; + } else { + a = y; + b = x; + c = 64.f; + minus = true; + } + } else { + if (x >= -y) { + a = x; + b = -y; + c = 0.f; + minus = true; + } else { + a = -y; + b = x; + c = -64.f; + minus = false; + } + } + } else { + if (y >= 0.f) { + if (-x >= y) { + a = -x; + b = y; + c = 128.f; + minus = true; + } else { + a = y; + b = -x; + c = 64.f; + minus = false; + } + } else { + if (-x >= -y) { + a = -x; + b = -y; + c = -128.f; + minus = false; + } else { + a = -y; + b = -x; + c = -64.f; + minus = true; + } + } + } + + return minus ? c - AtanFIdx_(b / a) : c + AtanFIdx_(b / a); +} + +} // namespace math +} // namespace nw4r diff --git a/source/nw4r/math/mathTriangular.hpp b/source/nw4r/math/mathTriangular.hpp new file mode 100644 index 000000000..ae7b19a95 --- /dev/null +++ b/source/nw4r/math/mathTriangular.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +namespace nw4r { +namespace math { + +namespace detail { +struct SinCosSample { + f32 sin_val; + f32 cos_val; + f32 sin_delta; + f32 cos_delta; +}; + +extern const SinCosSample gSinCosTbl[256 + 1]; +} // namespace detail + +// PAL 0x80085110 +f32 SinFIdx(f32 fidx); + +// PAL 0x80085180 +f32 CosFIdx(f32 fidx); + +// PAL 0x800851e0 +void SinCosFIdx(register f32*, register f32*, register f32); + +// PAL 0x80085250 +f32 AtanFIdx(f32); + +// PAL: 0x800853c0 +f32 Atan2FIdx(f32, f32); + +} // namespace math +} // namespace nw4r diff --git a/source/nw4r/math/mathTypes.cpp b/source/nw4r/math/mathTypes.cpp new file mode 100644 index 000000000..a3652b55b --- /dev/null +++ b/source/nw4r/math/mathTypes.cpp @@ -0,0 +1,100 @@ +#include "mathTypes.h" + +namespace nw4r { +namespace math { + +MTX33* MTX33Identity(register MTX33* out) { + register f32 n00 = 0.0f; + register f32 n11 = 1.0f; + register f32 n10; + asm + { + ps_merge00 n10, n11, n00 + psq_st n10, 0(out), 0, 0 + psq_st n00, 8(out), 0, 0 + psq_st n10, 16(out), 0, 0 + psq_st n00, 24(out), 0, 0 + stfs n11, 32(out) + } + return out; +} + +MTX33* MTX34ToMTX33(register MTX33* out, const register MTX34* in) { + register f32 _fp0, _fp1, _fp2, _fp3, _fp4, _fp5; + asm + { + psq_l _fp0, 0(in), 0, 0 + psq_l _fp1, 8(in), 0, 0 + psq_l _fp2, 16(in), 0, 0 + psq_l _fp3, 24(in), 0, 0 + psq_l _fp4, 32(in), 0, 0 + psq_l _fp5, 40(in), 0, 0 + psq_st _fp0, 0(out), 0, 0 + psq_st _fp1, 8(out), 1, 0 + psq_st _fp2, 12(out),0, 0 + psq_st _fp3, 20(out), 1, 0 + psq_st _fp4, 24(out), 0, 0 + psq_st _fp5, 32(out), 1, 0 + } + return out; +} + +// clang-format off +asm u32 MTX34InvTranspose(register MTX33* inv, register const MTX34* in) { + nofralloc + psq_l fp0, 0(in), 1, 0; + psq_l fp1, 4(in), 0, 0; + psq_l fp2, 16(in), 1, 0; + ps_merge10 fp6, fp1, fp0; + psq_l fp3, 20(in), 0, 0; + psq_l fp4, 32(in), 1, 0; + ps_merge10 fp7, fp3, fp2; + psq_l fp5, 36(in), 0, 0; + ps_mul fp11, fp3, fp6; + ps_merge10 fp8, fp5, fp4; + ps_mul fp13, fp5, fp7; + ps_msub fp11, fp1, fp7, fp11; + ps_mul fp12, fp1, fp8; + ps_msub fp13, fp3, fp8, fp13; + ps_msub fp12, fp5, fp6, fp12; + ps_mul fp10, fp3, fp4; + ps_mul fp9, fp0, fp5; + ps_mul fp8, fp1, fp2; + ps_msub fp10, fp2, fp5, fp10; + ps_msub fp9, fp1, fp4, fp9; + ps_msub fp8, fp0, fp3, fp8; + ps_mul fp7, fp0, fp13; + ps_sub fp1, fp1, fp1; + ps_madd fp7, fp2, fp12, fp7; + ps_madd fp7, fp4, fp11, fp7; + ps_cmpo0 cr0, fp7, fp1; + bne loc0; + li r3, 0; + blr; +loc0: + fres fp0, fp7; + ps_add fp6, fp0, fp0; + ps_mul fp5, fp0, fp0; + ps_nmsub fp0, fp7, fp5, fp6; + ps_add fp6, fp0, fp0; + ps_mul fp5, fp0, fp0; + ps_nmsub fp0, fp7, fp5, fp6; + ps_muls0 fp13, fp13, fp0; + ps_muls0 fp12, fp12, fp0; + psq_st fp13, 0x00(r3), 0, 0; + ps_muls0 fp11, fp11, fp0; + psq_st fp12, 0x0c(r3), 0, 0; + ps_muls0 fp10, fp10, fp0; + psq_st fp11, 0x18(r3), 0, 0; + ps_muls0 fp9, fp9, fp0; + psq_st fp10, 0x8(r3), 1, 0; + ps_muls0 fp8, fp8, fp0; + psq_st fp9, 0x14(r3), 1, 0; + psq_st fp8, 0x20(r3), 1, 0; + li r3, 1; + blr; +} +// clang-format on + +} // namespace math +} // namespace nw4r diff --git a/source/nw4r/math/mathTypes.h b/source/nw4r/math/mathTypes.h new file mode 100644 index 000000000..97b91ac48 --- /dev/null +++ b/source/nw4r/math/mathTypes.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +namespace nw4r { +namespace math { + +struct _MTX33 { + union { + struct { + f32 _00, _01, _02; + f32 _10, _11, _12; + f32 _20, _21, _22; + }; + f32 arr[9]; + f32 mtx[3][3]; + }; +}; + +struct _MTX34 { + union { + struct { + f32 _00, _01, _02, _03; + f32 _10, _11, _12, _13; + f32 _20, _21, _22, _23; + }; + f32 arr[12]; + f32 mtx[3][4]; + }; +}; + +struct _MTX44 { + f32 arr[16]; +}; + +// forward decls +struct MTX34; +struct MTX44; + +class MTX33 : public _MTX33 { +public: + MTX33() {} + MTX33(const f32* p); + MTX33(const MTX34& rhs); + MTX33(f32 x00, f32 x01, f32 x02, f32 x10, f32 x11, f32 x12, f32 x20, f32 x21, + f32 x22) { + _00 = x00; + _01 = x01; + _02 = x02; + _10 = x10; + _11 = x11; + _12 = x12; + _20 = x20; + _21 = x21; + _22 = x22; + } + + operator f32*() { return &_00; } + operator const f32*() const { return &_00; } +}; + +// PAL: 0x80085600 +MTX33* MTX33Identity(MTX33* pOut); +// PAL: 0x80085630 +MTX33* MTX34ToMTX33(MTX33* pOut, const MTX34* pM); +// PAL: 0x80085670 +u32 MTX34InvTranspose(MTX33* pOut, const MTX34* p); + +} // namespace math +} // namespace nw4r diff --git a/sources.py b/sources.py index 5607bdf0a..061fb54ea 100644 --- a/sources.py +++ b/sources.py @@ -6,6 +6,8 @@ compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) +compile_source("source/nw4r/math/mathTriangular.cpp", "out/mathTriangular.o", '4201_127', NW4R_OPTS) +compile_source("source/nw4r/math/mathTypes.cpp", "out/mathTypes.o", '4201_127', NW4R_OPTS) compile_source("source/nw4r/ut/utList.cpp", "out/utList.o", '4201_127', NW4R_OPTS) compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) From 9d66485e66d2a864d6359d79c73e3bd64ad744dd Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 19 Jun 2021 23:16:34 +0200 Subject: [PATCH 078/477] Don't regenerate existing segments: gen_asm.py (#17) --- mkwutil/gen_asm.py | 31 +++++++----- pack/dol_objects.txt | 112 +++++++++++++++++++++---------------------- pack/rel_objects.txt | 20 ++++---- 3 files changed, 86 insertions(+), 77 deletions(-) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index e9cdd7124..cb7bb75b5 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -3,7 +3,6 @@ from contextlib import redirect_stdout import os from pathlib import Path -import shutil import struct from .ppc_dis import disasm_iter, disassemble_callback @@ -76,7 +75,7 @@ def read_slices(name): def get_asm_path(name, gap, folder): folder.mkdir(exist_ok=True) - return folder / ("%s_%s.s" % (name, hex(gap.begin)[2:])) + return folder / ("%s_%08x_%08x.s" % (name, gap.begin, gap.end)) def format_segname(name): @@ -170,6 +169,8 @@ def dump_object_file(image, addr_start, segments): def disassemble_object_file(path, image, addr_start, segments): + if os.path.exists(path): + return # Don't bother updating existing file with open(path, "w") as file: with redirect_stdout(file): dump_object_file(image, addr_start, segments) @@ -177,8 +178,8 @@ def disassemble_object_file(path, image, addr_start, segments): def disasm(folder, name, image, addr_start, seg, is_data): path = get_asm_path(name, seg, folder) - disassemble_object_file(path, image, addr_start, [(name, seg)]) + return path def gen_start_segs(segments): @@ -236,18 +237,32 @@ def find_o_files(all_slices, folder): def unpack_binary(folder, all_slices, image, addr_start): + # Disassemble all slices if they don't exist already. + # Keep track of the expected paths. + asm_paths = set() for name, gap_seg, dest in find_o_files(all_slices, folder): is_decompiled = gap_seg is None if not is_decompiled: # print("name %s dest %s" % (name, dest)) - disasm(folder, name, image, addr_start, gap_seg, False) - kind = Path(dest.parent.name) # dol or rel + asm_path = disasm(folder, name, image, addr_start, gap_seg, False) + asm_paths.add(str(asm_path.relative_to(folder))) + kind = Path(dest.parent.name) # dol or rel yield Path("out", kind, dest.stem + ".o") if is_decompiled: object_name = Path(name).stem + ".o" yield Path("out", object_name) + # Check with paths we actually have. + # Delete asm blobs that don't match those we just unpacked. + for path in folder.iterdir(): + have_path = path.relative_to(folder) + if have_path.suffix != ".s": + continue + if str(have_path) in asm_paths: + continue + print(f"Removing {path}") + os.remove(path) def compute_end_cap(segments): @@ -377,12 +392,6 @@ def unpack_everything(asm_dir, pack_dir, binary_dir): help="Binary containing main.dol and StaticR.rel", ) args = parser.parse_args() - - # Recreate the ASM dir. - if os.path.exists(args.asm_dir / "dol"): - shutil.rmtree(args.asm_dir / "dol") - if os.path.exists(args.asm_dir / "rel"): - shutil.rmtree(args.asm_dir / "rel") args.asm_dir.mkdir(exist_ok=True) # Write the macros file. diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 69a6bf3d7..df9259073 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -1,74 +1,74 @@ -out\dol\text_800072c0.o -out\dol\rodata_80244ec0.o -out\dol\data_80258580.o -out\dol\sdata2_80386fa0.o +out\dol\text_800072c0_80085110.o +out\dol\rodata_80244ec0_80248010.o +out\dol\data_80258580_80274148.o +out\dol\sdata2_80386fa0_80387e80.o out\mathTriangular.o -out\dol\text_80085578.o -out\dol\sdata2_80387ea4.o +out\dol\text_80085578_80085600.o +out\dol\sdata2_80387ea4_80387ea8.o out\mathTypes.o -out\dol\text_80085738.o +out\dol\text_80085738_800aef60.o out\utList.o -out\dol\text_800af1a0.o -out\dol\data_80274250.o -out\dol\sbss_80385fc0.o +out\dol\text_800af1a0_800ccb4c.o +out\dol\data_80274250_80275700.o +out\dol\sbss_80385fc0_803862a8.o out\dwc_error.o -out\dol\text_800ccc80.o -out\dol\data_80275758.o -out\dol\sdata_80384c00.o +out\dol\text_800ccc80_80124500.o +out\dol\data_80275758_8027e708.o +out\dol\sdata_80384c00_803857f0.o out\rvlArchive.o -out\dol\text_80124e80.o +out\dol\text_80124e80_80199bf0.o out\rvlMemList.o -out\dol\text_80199d04.o -out\dol\data_8027e772.o +out\dol\text_80199d04_8020f62c.o +out\dol\data_8027e772_802a2668.o out\eggAllocator.o -out\dol\bss_802a4080.o -out\dol\sbss_803862b0.o +out\dol\bss_802a4080_803832d8.o +out\dol\sbss_803862b0_80386d80.o out\eggArchive.o -out\dol\text_8020fcc4.o -out\dol\data_802a268c.o +out\dol\text_8020fcc4_8021a0f0.o +out\dol\data_802a268c_802a2b48.o out\eggDisposer.o -out\dol\text_8021a1b8.o -out\dol\rodata_80249020.o -out\dol\data_802a2b54.o +out\dol\text_8021a1b8_802269a8.o +out\dol\rodata_80249020_80257700.o +out\dol\data_802a2b54_802a2ff8.o out\eggExpHeap.o -out\dol\text_80226f04.o -out\dol\data_802a3024.o -out\dol\sbss_80386d84.o +out\dol\text_80226f04_80229540.o +out\dol\data_802a3024_802a30b0.o +out\dol\sbss_80386d84_80386e90.o out\eggGraphicsFifo.o -out\dol\rodata_8025771a.o -out\dol\data_802a30bc.o -out\dol\bss_803832e4.o -out\dol\sbss_80386e99.o -out\dol\sdata2_80387eb0.o +out\dol\rodata_8025771a_80257740.o +out\dol\data_802a30bc_802a30c0.o +out\dol\bss_803832e4_80384320.o +out\dol\sbss_80386e99_80386ea0.o +out\dol\sdata2_80387eb0_80388d68.o out\eggHeap.o -out\dol\text_80229fac.o +out\dol\text_80229fac_80239dfc.o out\eggQuat.o -out\dol\text_80239e10.o -out\dol\data_802a30ec.o +out\dol\text_80239e10_80242498.o +out\dol\data_802a30ec_802a3f78.o out\eggStreamDecomp.o -out\dol\text_80242504.o -out\dol\data_802a3f90.o -out\dol\bss_80384348.o +out\dol\text_80242504_802432e0.o +out\dol\data_802a3f90_802a3fc0.o +out\dol\bss_80384348_80384b60.o out\eggThread.o out\eggUnitHeap.o -out\dol\ctors_80244de0.o -out\dol\bss_80384b6c.o -out\dol\sbss_80386ec0.o -out\dol\sdata2_80388d80.o +out\dol\ctors_80244de0_80244e88.o +out\dol\bss_80384b6c_80384b70.o +out\dol\sbss_80386ec0_80386f78.o +out\dol\sdata2_80388d80_803890f8.o out\eggVector.o -out\dol\rodata_80257824.o -out\dol\sdata2_80389104.o +out\dol\rodata_80257824_802582e0.o +out\dol\sdata2_80389104_80389108.o out\eggVideo.o -out\dol\init_80004000.o -out\dol\extab_80006460.o -out\dol\extabindex_80006a20.o -out\dol\text_80244074.o -out\dol\ctors_80244e8c.o -out\dol\dtors_80244ea4.o -out\dol\rodata_80258560.o -out\dol\data_802a4004.o -out\dol\bss_80384bf4.o -out\dol\sdata_803857f6.o -out\dol\sbss_80386f90.o -out\dol\sdata2_80389118.o -out\dol\sbss2_80389140.o +out\dol\init_80004000_80006460.o +out\dol\extab_80006460_80006a20.o +out\dol\extabindex_80006a20_800072c0.o +out\dol\text_80244074_80244de0.o +out\dol\ctors_80244e8c_80244e90.o +out\dol\dtors_80244ea4_80244eac.o +out\dol\rodata_80258560_80258580.o +out\dol\data_802a4004_802a4040.o +out\dol\bss_80384bf4_80384c00.o +out\dol\sdata_803857f6_80385fc0.o +out\dol\sbss_80386f90_80386fa0.o +out\dol\sdata2_80389118_80389140.o +out\dol\sbss2_80389140_8038917c.o diff --git a/pack/rel_objects.txt b/pack/rel_objects.txt index 95b901f8d..24a23808c 100644 --- a/pack/rel_objects.txt +++ b/pack/rel_objects.txt @@ -1,12 +1,12 @@ -out\rel\text_805103b4.o -out\rel\data_808b2bd0.o -out\rel\bss_809bd6e0.o +out\rel\text_805103b4_80512694.o +out\rel\data_808b2bd0_808b2c30.o +out\rel\bss_809bd6e0_809bd6e8.o out\JmpResourceCourse.o -out\rel\text_805127ec.o +out\rel\text_805127ec_805f8b34.o out\MessageGroup.o -out\rel\text_805f8b90.o -out\rel\ctors_8088f400.o -out\rel\dtors_8088f704.o -out\rel\rodata_8088f710.o -out\rel\data_808b2c3c.o -out\rel\bss_809bd6ec.o +out\rel\text_805f8b90_8088f400.o +out\rel\ctors_8088f400_8088f704.o +out\rel\dtors_8088f704_8088f710.o +out\rel\rodata_8088f710_808b2bd0.o +out\rel\data_808b2c3c_808dd3d4.o +out\rel\bss_809bd6ec_809c4f90.o From 2685787607cc29c52b65ece82ce5ffd16191a080 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 20 Jun 2021 02:34:39 +0200 Subject: [PATCH 079/477] More nw4r::math functions (#18) * add MTX34Zero * add MTX34Mult * add MTX34Scale * add MTX34Trans * add MTX34MAdd * add MTX34RotAxisFIdx --- pack/dol.base.lcf | 2 + pack/dol_objects.txt | 4 +- pack/dol_slices.csv | 2 +- source/nw4r/math/mathConstants.hpp | 11 ++ source/nw4r/math/mathTypes.cpp | 169 +++++++++++++++++++++++++++++ source/nw4r/math/mathTypes.h | 52 ++++++++- source/rvl/mtx/mtx.h | 20 ++++ 7 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 source/nw4r/math/mathConstants.hpp create mode 100644 source/rvl/mtx/mtx.h diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index 59ac4b425..77c375a29 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -117,6 +117,8 @@ AllocatorAllocForExpHeap_ = 0x80199B58; AllocatorFreeForExpHeap_ = 0x80199B68; MEMInitAllocatorForExpHeap = 0x80199BB8; +PSMTXRotAxisRad=0x8019a364; + VIInit = 0x801B94A4; VIWaitForRetrace = 0x801B99EC; VIConfigure = 0x801B9F6C; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index df9259073..57dcdcb5d 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -6,7 +6,7 @@ out\mathTriangular.o out\dol\text_80085578_80085600.o out\dol\sdata2_80387ea4_80387ea8.o out\mathTypes.o -out\dol\text_80085738_800aef60.o +out\dol\text_80085938_800aef60.o out\utList.o out\dol\text_800af1a0_800ccb4c.o out\dol\data_80274250_80275700.o @@ -39,7 +39,7 @@ out\dol\rodata_8025771a_80257740.o out\dol\data_802a30bc_802a30c0.o out\dol\bss_803832e4_80384320.o out\dol\sbss_80386e99_80386ea0.o -out\dol\sdata2_80387eb0_80388d68.o +out\dol\sdata2_80387eb4_80388d68.o out\eggHeap.o out\dol\text_80229fac_80239dfc.o out\eggQuat.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 8c3d5eb2f..da94d6309 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,6 +1,6 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End 1,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, -1,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085738,,,,,,,,,,,,,,,0x80387ea8,0x80387eb0,, +1,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085938,,,,,,,,,,,,,,,0x80387ea8,0x80387eb4,, 1,source/nw4r/ut/utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, 1,source/dwc/common/dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, 1,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, diff --git a/source/nw4r/math/mathConstants.hpp b/source/nw4r/math/mathConstants.hpp new file mode 100644 index 000000000..74997359c --- /dev/null +++ b/source/nw4r/math/mathConstants.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace nw4r { +namespace math { + +const f32 F_PI = 3.141592653589793f; + +} +} // namespace nw4r diff --git a/source/nw4r/math/mathTypes.cpp b/source/nw4r/math/mathTypes.cpp index a3652b55b..f2ece71be 100644 --- a/source/nw4r/math/mathTypes.cpp +++ b/source/nw4r/math/mathTypes.cpp @@ -1,5 +1,7 @@ #include "mathTypes.h" +#include "mathConstants.hpp" + namespace nw4r { namespace math { @@ -96,5 +98,172 @@ asm u32 MTX34InvTranspose(register MTX33* inv, register const MTX34* in) { } // clang-format on +MTX34* MTX34Zero(register MTX34* out) { + register f32 zero = 0.f; + asm + { + psq_st zero, 0(out), 0, 0; + psq_st zero, 8(out), 0, 0; + psq_st zero, 16(out), 0, 0; + psq_st zero, 24(out), 0, 0; + psq_st zero, 32(out), 0, 0; + psq_st zero, 40(out), 0, 0; + } + return out; +} + +MTX34* MTX34Mult(register MTX34* out, const register MTX34* in, + register f32 f) { + register f32 a, b; + asm + { + psq_l a, 0(in), 0, 0; + ps_muls0 b, a, f; + psq_st b, 0(out), 0, 0; + + psq_l a, 8(in), 0, 0; + ps_muls0 b, a, f; + psq_st b, 8(out), 0, 0; + + psq_l a, 16(in), 0, 0; + ps_muls0 b, a, f; + psq_st b, 16(out), 0, 0; + + psq_l a, 24(in), 0, 0; + ps_muls0 b, a, f; + psq_st b, 24(out), 0, 0; + + psq_l a, 32(in), 0, 0; + ps_muls0 b, a, f; + psq_st b, 32(out), 0, 0; + + psq_l a, 40(in), 0, 0; + ps_muls0 b, a, f; + psq_st b, 40(out), 0, 0; + } + return out; +} + +MTX34* MTX34Scale(register MTX34* out, const register MTX34* in, + const register VEC3* scale) { + register f32 xy, z1; + register f32 r0a, r0b; + register f32 r1a, r1b; + register f32 r2a, r2b; + asm + { + psq_l xy, 0(scale), 0, 0 + psq_l z1, 8(scale), 1, 0 + + psq_l r0a, 0(in), 0, 0 + psq_l r0b, 8(in), 0, 0 + psq_l r1a, 16(in), 0, 0 + psq_l r1b, 24(in), 0, 0 + psq_l r2a, 32(in), 0, 0 + psq_l r2b, 40(in), 0, 0 + + ps_mul r0a, r0a, xy + ps_mul r1a, r1a, xy + ps_mul r2a, r2a, xy + ps_mul r0b, r0b, z1 + ps_mul r1b, r1b, z1 + ps_mul r2b, r2b, z1 + + psq_st r0a, 0(out), 0, 0 + psq_st r0b, 8(out), 0, 0 + psq_st r1a, 16(out), 0, 0 + psq_st r1b, 24(out), 0, 0 + psq_st r2a, 32(out), 0, 0 + psq_st r2b, 40(out), 0, 0 + } + return out; +} + +MTX34* MTX34Trans(register MTX34* out, const register MTX34* in, + const register VEC3* trans) { + register f32 xy, z1; + register f32 vv0, vv1, vv2, vv3, vv4, vv5; + register f32 tmp0, tmp1, tmp2; + asm + { + psq_l vv0, 0(in), 0, 0; + psq_st vv0, 0(out), 0, 0; + psq_l vv1, 8(in), 0, 0; + psq_st vv1, 8(out), 0, 0; + psq_l vv2, 16(in), 0, 0; + psq_st vv2, 16(out), 0, 0; + psq_l vv3, 24(in), 0, 0; + psq_st vv3, 24(out), 0, 0; + psq_l vv4, 32(in), 0, 0; + psq_st vv4, 32(out), 0, 0; + psq_l vv5, 40(in), 0, 0; + psq_st vv5, 40(out), 0, 0; + psq_l xy, 0(trans), 0, 0; + psq_l z1, 8(trans), 1, 0; + ps_mul tmp0, vv0, xy; + ps_madd tmp1, vv1, z1, tmp0; + ps_sum0 tmp2, tmp1, tmp2, tmp1; + psq_st tmp2, 12(out), 1, 0; + ps_mul tmp0, vv2, xy; + ps_madd tmp1, vv3, z1, tmp0; + ps_sum0 tmp2, tmp1, tmp2, tmp1; + psq_st tmp2, 28(out), 1, 0; + ps_mul tmp0, vv4, xy; + ps_madd tmp1, vv5, z1, tmp0; + ps_sum0 tmp2, tmp1, tmp2, tmp1; + psq_st tmp2, 44(out), 1, 0; + } + return out; +} + +MTX34* MTX34MAdd(register MTX34* out, register f32 t, const register MTX34* m1, + const register MTX34* m2) { + register f32 a, b, c; + asm + { + psq_l a, 0(m1), 0, 0; + psq_l b, 0(m2), 0, 0; + ps_muls0 a, a, t; + ps_add c, a, b; + psq_st c, 0(out), 0, 0; + + psq_l a, 8(m1), 0, 0; + psq_l b, 8(m2), 0, 0; + ps_muls0 a, a, t; + ps_add c, a, b; + psq_st c, 8(out), 0, 0; + + psq_l a, 16(m1), 0, 0; + psq_l b, 16(m2), 0, 0; + ps_muls0 a, a, t; + ps_add c, a, b; + psq_st c, 16(out), 0, 0; + + psq_l a, 24(m1), 0, 0; + psq_l b, 24(m2), 0, 0; + ps_muls0 a, a, t; + ps_add c, a, b; + psq_st c, 24(out), 0, 0; + + psq_l a, 32(m1), 0, 0; + psq_l b, 32(m2), 0, 0; + ps_muls0 a, a, t; + ps_add c, a, b; + psq_st c, 32(out), 0, 0; + + psq_l a, 40(m1), 0, 0; + psq_l b, 40(m2), 0, 0; + ps_muls0 a, a, t; + ps_add c, a, b; + psq_st c, 40(out), 0, 0; + } + return out; +} + +MTX34* MTX34RotAxisFIdx(MTX34* out, const VEC3* axis, f32 amnt) { + PSMTXRotAxisRad(*out, *axis, amnt * ((2 * F_PI) / 256.f)); + return out; +} + } // namespace math } // namespace nw4r diff --git a/source/nw4r/math/mathTypes.h b/source/nw4r/math/mathTypes.h index 97b91ac48..c914829ce 100644 --- a/source/nw4r/math/mathTypes.h +++ b/source/nw4r/math/mathTypes.h @@ -2,9 +2,17 @@ #include +#include "rvl/mtx/mtx.h" + namespace nw4r { namespace math { +struct _VEC3 { + f32 x; + f32 y; + f32 z; +}; + struct _MTX33 { union { struct { @@ -34,9 +42,21 @@ struct _MTX44 { }; // forward decls +struct VEC3; struct MTX34; struct MTX44; +struct VEC3 : public _VEC3 { +public: + VEC3() {} + + operator f32*() { return &x; } + operator const f32*() const { return &x; } + + operator Vec*() { return (Vec*)&x; } + operator const Vec*() const { return (const Vec*)&x; } +}; + class MTX33 : public _MTX33 { public: MTX33() {} @@ -59,12 +79,38 @@ class MTX33 : public _MTX33 { operator const f32*() const { return &_00; } }; +struct MTX34 : public _MTX34 +{ +public: + typedef const f32 (*ConstMtxPtr)[4]; +public: + MTX34() {} + + operator f32*() { return &_00; } + operator const f32*() const { return &_00; } + + operator MtxPtr() { return (MtxPtr)&_00; } + operator ConstMtxPtr() const { return (ConstMtxPtr)&_00; } +}; + // PAL: 0x80085600 -MTX33* MTX33Identity(MTX33* pOut); +MTX33* MTX33Identity(MTX33*); // PAL: 0x80085630 -MTX33* MTX34ToMTX33(MTX33* pOut, const MTX34* pM); +MTX33* MTX34ToMTX33(MTX33*, const MTX34*); // PAL: 0x80085670 -u32 MTX34InvTranspose(MTX33* pOut, const MTX34* p); +u32 MTX34InvTranspose(MTX33*, const MTX34*); +// PAL: 0x80085740 +MTX34* MTX34Zero(MTX34*); +// PAL: 0x80085760 +MTX34* MTX34Mult(MTX34*, const MTX34*, f32); +// PAL: 0x800857b0 +MTX34* MTX34Scale(MTX34*, const MTX34*, const VEC3*); +// PAL: 0x80085810 +MTX34* MTX34Trans(MTX34*, const MTX34*, const VEC3*); +// PAL: 0x80085880 +MTX34* MTX34MAdd(MTX34*, f32, const MTX34*, const MTX34*); +// PAL: 0x80085900 +MTX34* MTX34RotAxisFIdx(MTX34*, const VEC3*, f32); } // namespace math } // namespace nw4r diff --git a/source/rvl/mtx/mtx.h b/source/rvl/mtx/mtx.h new file mode 100644 index 000000000..5c6776fcb --- /dev/null +++ b/source/rvl/mtx/mtx.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + f32 x, y, z; +} Vec; + +typedef f32 Mtx[3][4]; +typedef f32 (*MtxPtr)[4]; + +void PSMTXRotAxisRad(Mtx m, const Vec* axis, f32 rad); + +#ifdef __cplusplus +} +#endif From 8081ccb49461d92bb583f7a7a2c39d3579002be8 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 20 Jun 2021 23:59:40 +0200 Subject: [PATCH 080/477] Decompile RVL/MTX (#19) * add PSMTXIdentity * add PSMTXCopy * add PSMTXConcat * add PSMTXConcatArray * add PSMTXInverse * add PSMTXInvXpose * add C_MTXPerspective * add C_MTXOrtho * add PSVECAdd * add PSVECScale * add PSVECNormalize * add PSVECMag * add PSVECDotProduct * add PSVECCrossProduct * add C_VECHalfAngle * add PSVECSquareDistance * add PSQUATMultiply * add PSQUATScale * add PSQUATDotProduct, PSQUOATNormalize * add PSQUATInverse * add C_QUATMtx * add C_QUATLerp * add PSMTXRotRad, PSMTXRotTrig * revert RVL_OPTS * fix relative reference to sdata2 * remove unused function * add PSMTXRotAxisRad * add PSMTXTrans * add PSMTXTransApply * add PSMTXScale, PSMTXScaleApply * add PSMTXQuat * add C_MTXLookAt * add C_MTXLightFrustum * add C_MTXLightPerspective * add C_MTXLightOrtho * add PSMTXMultVec * add PSMTXMultVecSR * add C_MTXFrustum --- build.py | 4 +- pack/dol.base.lcf | 13 +- pack/dol_objects.txt | 16 +- pack/dol_slices.csv | 4 + source/rvl/mtx/mtx.h | 94 +++++- source/rvl/mtx/rvlMtx.c | 711 +++++++++++++++++++++++++++++++++++++++ source/rvl/mtx/rvlMtx2.c | 73 ++++ source/rvl/mtx/rvlQuat.c | 178 ++++++++++ source/rvl/mtx/rvlVec.c | 156 +++++++++ sources.py | 4 + 10 files changed, 1239 insertions(+), 14 deletions(-) create mode 100644 source/rvl/mtx/rvlMtx.c create mode 100644 source/rvl/mtx/rvlMtx2.c create mode 100644 source/rvl/mtx/rvlQuat.c create mode 100644 source/rvl/mtx/rvlVec.c diff --git a/build.py b/build.py index b1ace0bc2..04dd498ca 100644 --- a/build.py +++ b/build.py @@ -151,7 +151,7 @@ def link_dol(o_files): dst_lcf_path = Path("pack", "dol.lcf") gen_lcf(src_lcf_path, dst_lcf_path, o_files) # Create dest dir. - dest_dir = Path("target", "pal") + dest_dir = Path("artifacts", "target", "pal") dest_dir.mkdir(parents=True, exist_ok=True) # Link ELF. elf_path = dest_dir / "main.elf" @@ -168,7 +168,7 @@ def link_rel(o_files): dst_lcf_path = Path("pack", "rel.lcf") gen_lcf(src_lcf_path, dst_lcf_path, o_files) # Create dest dir. - dest_dir = Path("target", "pal") + dest_dir = Path("artifacts", "target", "pal") dest_dir.mkdir(parents=True, exist_ok=True) # Link ELF. elf_path = dest_dir / "StaticR.elf" diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index 77c375a29..fd5881c68 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -29,11 +29,10 @@ __ArenaHi = 0x81700000; __start = 0x800060A4; __destroy_global_chain=0x80021350; -OSPanic=0x801A2660; - -cosf=0x8001b590; -sinf=0x8001ba98; -tanf=0x8001bb64; +cos=0x8001b590; +sin=0x8001ba98; +tan=0x8001bb64; +sqrt=0x8001bbf4; _savegpr_22=0x8002158C; _savegpr_23=0x80021590; @@ -61,6 +60,8 @@ CXInitUncompContextLZ=0x8015BEF0; CXReadUncompLZ=0x8015BF24; CXGetUncompressedSize=0x8015C2E0; +OSPanic=0x801A2660; + OSInitMutex=0x801A7EAC; OSLockMutex=0x801A7EE4; @@ -117,8 +118,6 @@ AllocatorAllocForExpHeap_ = 0x80199B58; AllocatorFreeForExpHeap_ = 0x80199B68; MEMInitAllocatorForExpHeap = 0x80199BB8; -PSMTXRotAxisRad=0x8019a364; - VIInit = 0x801B94A4; VIWaitForRetrace = 0x801B99EC; VIConfigure = 0x801B9F6C; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 57dcdcb5d..2acdff564 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -18,7 +18,15 @@ out\dol\sdata_80384c00_803857f0.o out\rvlArchive.o out\dol\text_80124e80_80199bf0.o out\rvlMemList.o -out\dol\text_80199d04_8020f62c.o +out\dol\sdata_803857f6_80385a08.o +out\dol\sdata2_80387eb4_80388870.o +out\rvlMtx.o +out\rvlMtx2.o +out\rvlVec.o +out\dol\rodata_80249020_80252c78.o +out\dol\sdata2_803888b4_803888b8.o +out\rvlQuat.o +out\dol\text_8019b178_8020f62c.o out\dol\data_8027e772_802a2668.o out\eggAllocator.o out\dol\bss_802a4080_803832d8.o @@ -28,7 +36,7 @@ out\dol\text_8020fcc4_8021a0f0.o out\dol\data_802a268c_802a2b48.o out\eggDisposer.o out\dol\text_8021a1b8_802269a8.o -out\dol\rodata_80249020_80257700.o +out\dol\rodata_80252c84_80257700.o out\dol\data_802a2b54_802a2ff8.o out\eggExpHeap.o out\dol\text_80226f04_80229540.o @@ -39,7 +47,7 @@ out\dol\rodata_8025771a_80257740.o out\dol\data_802a30bc_802a30c0.o out\dol\bss_803832e4_80384320.o out\dol\sbss_80386e99_80386ea0.o -out\dol\sdata2_80387eb4_80388d68.o +out\dol\sdata2_803888cc_80388d68.o out\eggHeap.o out\dol\text_80229fac_80239dfc.o out\eggQuat.o @@ -68,7 +76,7 @@ out\dol\dtors_80244ea4_80244eac.o out\dol\rodata_80258560_80258580.o out\dol\data_802a4004_802a4040.o out\dol\bss_80384bf4_80384c00.o -out\dol\sdata_803857f6_80385fc0.o +out\dol\sdata_80385a10_80385fc0.o out\dol\sbss_80386f90_80386fa0.o out\dol\sdata2_80389118_80389140.o out\dol\sbss2_80389140_8038917c.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index da94d6309..4d0e22067 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -6,6 +6,10 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, ,rxFrameHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, 1,source/rvl/mem/rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, +1,source/rvl/mtx/rvlMtx.c,,,,,,,0x80199d04,0x8019a9c4,,,,,,,,,,,0x80385a08,0x80385a10,,,0x80388870,0x80388890,, +1,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, +1,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, +1,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b178,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888cc,, 1,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680 ,,,,,,,,,, 1,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, 1,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54 ,,,,,,,,,, diff --git a/source/rvl/mtx/mtx.h b/source/rvl/mtx/mtx.h index 5c6776fcb..3b99e5f9d 100644 --- a/source/rvl/mtx/mtx.h +++ b/source/rvl/mtx/mtx.h @@ -13,7 +13,99 @@ typedef struct { typedef f32 Mtx[3][4]; typedef f32 (*MtxPtr)[4]; -void PSMTXRotAxisRad(Mtx m, const Vec* axis, f32 rad); +typedef f32 Mtx44[4][4]; +typedef f32 (*Mtx44Ptr)[4]; + +typedef struct { + f32 x, y, z, w; +} Quaternion; + +// rvlMtx.c +// PAL: 0x80199d04 +void PSMTXIdentity(Mtx); +// PAL: 0x80199d30 +void PSMTXCopy(const Mtx, Mtx); +// PAL: 0x80199d64 +void PSMTXConcat(const Mtx, const Mtx, Mtx); +// PAL: 0x80199e30 +void PSMTXConcatArray(const Mtx, const Mtx*, Mtx*, u32); +// PAL: 0x80199fc8 +u32 PSMTXInverse(const Mtx, Mtx); +// PAL: 0x8019a0c0 +u32 PSMTXInvXpose(const Mtx, Mtx); +// PAL: 0x8019a188 +void PSMTXRotRad(Mtx, char, f32); +// PAL: 0x8019a204 +void PSMTXRotTrig(Mtx, char, f32, f32); +// PAL: 0x8019a2b4 +void __PSMTXRotAxisRadInternal(Mtx, const Vec*, f32, f32); +// PAL: 0x8019a364 +void PSMTXRotAxisRad(Mtx, const Vec*, f32); +// PAL: 0x8019a3e0 +void PSMTXTrans(Mtx, f32, f32, f32); +// PAL: 0x8019a414 +void PSMTXTransApply(const Mtx, Mtx, f32, f32, f32); +// PAL: 0x8019a460 +void PSMTXScale(Mtx, f32, f32, f32); +// PAL: 0x8019a488 +void PSMTXScaleApply(const Mtx, Mtx, f32, f32, f32); +// PAL: 0x8019a4e0 +void PSMTXQuat(Mtx m, const Quaternion*); +// PAL: 0x8019a584 +void C_MTXLookAt(Mtx, const Vec*, const Vec*, const Vec*); +// PAL: 0x8019a6f8 +void C_MTXLightFrustum(Mtx, f32, f32, f32, f32, f32, f32, f32, f32, f32); +// PAL: 0x8019a79c +void C_MTXLightPerspective(Mtx, f32, f32, f32, f32, f32, f32); +// PAL: 0x8019a894 +void C_MTXLightOrtho(Mtx, f32, f32, f32, f32, f32, f32, f32, f32); +// PAL: 0x8019a91c +void PSMTXMultVec(const Mtx, const Vec*, Vec*); +// PAL: 0x8019a970 +void PSMTXMultVecSR(const Mtx, const Vec*, Vec*); + +// rvlMtx2.c +// PAL: 0x8019a9c4 +void C_MTXFrustum(Mtx44, f32, f32, f32, f32, f32, f32); +// PAL: 0x8019aa60 +void C_MTXPerspective(Mtx44, f32, f32, f32, f32); +// PAL: 0x8019ab4c +void C_MTXOrtho(Mtx44, f32, f32, f32, f32, f32, f32); + +// rvlVec.c +// PAL: 0x8019abe4 +void PSVECAdd(const Vec*, const Vec*, Vec*); +// PAL: 0x8019ac08 +void PSVECScale(const Vec*, Vec*, f32); +// PAL: 0x8019ac24 +void PSVECNormalize(const Vec*, Vec*); +// PAL: 0x8019ac68 +f32 PSVECMag(const Vec*); +// PAL: 0x8019acac +f32 PSVECDotProduct(const Vec*, const Vec*); +// PAL: 0x8019accc +void PSVECCrossProduct(const Vec*, const Vec*, Vec*); +// PAL: 0x8019ad08 +void C_VECHalfAngle(const Vec*, const Vec*, Vec*); +// PAL: 0x8019ade0 +f32 PSVECSquareDistance(const Vec*, const Vec*); + +// rvlQuat.c +// PAL: 0x8019ae08 +void PSQUATMultiply(const Quaternion*, const Quaternion*, Quaternion*); +// PAL: 0x8019ae64 +void PSQUATScale(const Quaternion*, Quaternion*, f32); +// PAL: 0x8019ae80 +f32 PSQUATDotProduct(const Quaternion*, const Quaternion*); +// PAL: 0x8019aea0 +void PSQUATNormalize(const Quaternion*, Quaternion*); +// PAL: 0x8019aef4 +void PSQUATInverse(const Quaternion*, Quaternion*); +// PAL: 0x8019af48 +void C_QUATMtx(Quaternion*, const Mtx); +// PAL: 0x8019b114 +void C_QUATLerp(const Quaternion*, const Quaternion*, Quaternion*, f32); +// PAL: 0x8019b178 #ifdef __cplusplus } diff --git a/source/rvl/mtx/rvlMtx.c b/source/rvl/mtx/rvlMtx.c new file mode 100644 index 000000000..5e62e9511 --- /dev/null +++ b/source/rvl/mtx/rvlMtx.c @@ -0,0 +1,711 @@ +#include "mtx.h" + +static f32 Unit01[] = {0.0f, 1.0f}; + +void PSMTXIdentity(register Mtx m) { + // sdata2 ordering + (void)1.0f; + (void)0.0f; + + register f32 tmp0 = 0.0F; + register f32 tmp1 = 1.0F; + register f32 tmp2; + register f32 tmp3; + asm + { + psq_st tmp0, 8(m), 0, 0; + ps_merge01 tmp2, tmp0, tmp1; + psq_st tmp0, 24(m), 0, 0; + ps_merge10 tmp3, tmp1, tmp0; + psq_st tmp0, 32(m), 0, 0; + psq_st tmp2, 16(m), 0, 0; + psq_st tmp3, 0(m), 0, 0; + psq_st tmp3, 40(m), 0, 0; + } +} + +asm void PSMTXCopy(const register Mtx in, register Mtx out) { + nofralloc; + psq_l fp0, 0(in), 0, 0; + psq_st fp0, 0(out), 0, 0; + psq_l fp1, 8(in), 0, 0; + psq_st fp1, 8(out), 0, 0; + psq_l fp2, 16(in), 0, 0; + psq_st fp2, 16(out), 0, 0; + psq_l fp3, 24(in), 0, 0; + psq_st fp3, 24(out), 0, 0; + psq_l fp4, 32(in), 0, 0; + psq_st fp4, 32(out), 0, 0; + psq_l fp5, 40(in), 0, 0; + psq_st fp5, 40(out), 0, 0; + blr; +} + +asm void PSMTXConcat(const register Mtx mA, const register Mtx mB, + register Mtx mAB) { + nofralloc; + stwu r1, -64(r1); + psq_l fp0, 0(mA), 0, 0; + stfd fp14, 8(r1); + psq_l fp6, 0(mB), 0, 0; + addis r6, 0, Unit01 @ha; + psq_l fp7, 8(mB), 0, 0; + stfd fp15, 16(r1); + addi r6, r6, Unit01 @l; + stfd fp31, 40(r1); + psq_l fp8, 16(mB), 0, 0; + ps_muls0 fp12, fp6, fp0; + psq_l fp2, 16(mA), 0, 0; + ps_muls0 fp13, fp7, fp0; + psq_l fp31, 0(r6), 0, 0; + ps_muls0 fp14, fp6, fp2; + psq_l fp9, 24(mB), 0, 0; + ps_muls0 fp15, fp7, fp2; + psq_l fp1, 8(mA), 0, 0; + ps_madds1 fp12, fp8, fp0, fp12; + psq_l fp3, 24(mA), 0, 0; + ps_madds1 fp14, fp8, fp2, fp14; + psq_l fp10, 32(mB), 0, 0; + ps_madds1 fp13, fp9, fp0, fp13; + psq_l fp11, 40(mB), 0, 0; + ps_madds1 fp15, fp9, fp2, fp15; + psq_l fp4, 32(mA), 0, 0; + psq_l fp5, 40(mA), 0, 0; + ps_madds0 fp12, fp10, fp1, fp12; + ps_madds0 fp13, fp11, fp1, fp13; + ps_madds0 fp14, fp10, fp3, fp14; + ps_madds0 fp15, fp11, fp3, fp15; + psq_st fp12, 0(mAB), 0, 0; + ps_muls0 fp2, fp6, fp4; + ps_madds1 fp13, fp31, fp1, fp13; + ps_muls0 fp0, fp7, fp4; + psq_st fp14, 16(mAB), 0, 0; + ps_madds1 fp15, fp31, fp3, fp15; + psq_st fp13, 8(mAB), 0, 0; + ps_madds1 fp2, fp8, fp4, fp2; + ps_madds1 fp0, fp9, fp4, fp0; + ps_madds0 fp2, fp10, fp5, fp2; + lfd fp14, 8(r1); + psq_st fp15, 24(mAB), 0, 0; + ps_madds0 fp0, fp11, fp5, fp0; + psq_st fp2, 32(mAB), 0, 0; + ps_madds1 fp0, fp31, fp5, fp0; + lfd fp15, 16(r1); + psq_st fp0, 40(mAB), 0, 0; + lfd fp31, 40(r1); + addi r1, r1, 64; + blr; +} + +void PSMTXConcatArray(const register Mtx mtx1, const register Mtx* mtx2, + register Mtx* mtx3, register u32 vv4) { + register f32 va0, va1, va2, va3, va4, va5; + register f32 vb0, vb1, vb2, vb3, vb4, vb5; + register f32 vd0, vd1, vd2, vd3, vd4, vd5; + register f32 u01; + register f32* u01Ptr = Unit01; + + asm { + psq_l va0, 0(mtx1), 0, 0; + psq_l va1, 8(mtx1), 0, 0; + psq_l va2, 16(mtx1), 0, 0; + psq_l va3, 24(mtx1), 0, 0; + subi vv4, vv4, 1; + psq_l va4, 32(mtx1), 0, 0; + psq_l va5, 40(mtx1), 0, 0; + mtctr vv4; + psq_l u01, 0(u01Ptr), 0, 0; + psq_l vb0, 0(mtx2), 0, 0; + psq_l vb2, 16(mtx2), 0, 0; + ps_muls0 vd0, vb0, va0; + ps_muls0 vd2, vb0, va2; + ps_muls0 vd4, vb0, va4; + psq_l vb4, 32(mtx2), 0, 0; + ps_madds1 vd0, vb2, va0, vd0; + ps_madds1 vd2, vb2, va2, vd2; + ps_madds1 vd4, vb2, va4, vd4; + psq_l vb1, 8(mtx2), 0, 0; + ps_madds0 vd0, vb4, va1, vd0; + ps_madds0 vd2, vb4, va3, vd2; + ps_madds0 vd4, vb4, va5, vd4; + psq_l vb3, 24(mtx2), 0, 0; + psq_st vd0, 0(mtx3), 0, 0; + ps_muls0 vd1, vb1, va0; + ps_muls0 vd3, vb1, va2; + ps_muls0 vd5, vb1, va4; + psq_l vb5, 40(mtx2), 0, 0; + psq_st vd2, 16(mtx3), 0, 0; + ps_madds1 vd1, vb3, va0, vd1; + ps_madds1 vd3, vb3, va2, vd3; + ps_madds1 vd5, vb3, va4, vd5; +_loop: + addi mtx2, mtx2, sizeof(Mtx); + ps_madds0 vd1, vb5, va1, vd1; + ps_madds0 vd3, vb5, va3, vd3; + ps_madds0 vd5, vb5, va5, vd5; + psq_l vb0, 0(mtx2), 0, 0; + psq_st vd4, 32(mtx3), 0, 0; + ps_madd vd1, u01, va1, vd1; + ps_madd vd3, u01, va3, vd3; + ps_madd vd5, u01, va5, vd5; + psq_l vb2, 16(mtx2), 0, 0; + psq_st vd1, 8(mtx3), 0, 0; + ps_muls0 vd0, vb0, va0; + ps_muls0 vd2, vb0, va2; + ps_muls0 vd4, vb0, va4; + psq_l vb4, 32(mtx2), 0, 0; + psq_st vd3, 24(mtx3), 0, 0; + ps_madds1 vd0, vb2, va0, vd0; + ps_madds1 vd2, vb2, va2, vd2; + ps_madds1 vd4, vb2, va4, vd4; + psq_l vb1, 8(mtx2), 0, 0; + psq_st vd5, 40(mtx3), 0, 0; + addi mtx3, mtx3, sizeof(Mtx); + ps_madds0 vd0, vb4, va1, vd0; + ps_madds0 vd2, vb4, va3, vd2; + ps_madds0 vd4, vb4, va5, vd4; + psq_l vb3, 24(mtx2), 0, 0; + psq_st vd0, 0(mtx3), 0, 0; + ps_muls0 vd1, vb1, va0; + ps_muls0 vd3, vb1, va2; + ps_muls0 vd5, vb1, va4; + psq_l vb5, 40(mtx2), 0, 0; + psq_st vd2, 16(mtx3), 0, 0; + ps_madds1 vd1, vb3, va0, vd1; + ps_madds1 vd3, vb3, va2, vd3; + ps_madds1 vd5, vb3, va4, vd5; + bdnz _loop; + psq_st vd4, 32(mtx3), 0, 0; + ps_madds0 vd1, vb5, va1, vd1; + ps_madds0 vd3, vb5, va3, vd3; + ps_madds0 vd5, vb5, va5, vd5; + ps_madd vd1, u01, va1, vd1; + ps_madd vd3, u01, va3, vd3; + ps_madd vd5, u01, va5, vd5; + psq_st vd1, 8(mtx3), 0, 0; + psq_st vd3, 24(mtx3), 0, 0; + psq_st vd5, 40(mtx3), 0, 0; + } +} + +asm u32 PSMTXInverse(const register Mtx src, register Mtx inv) { + nofralloc; + psq_l fp0, 0(src), 1, 0; + psq_l fp1, 4(src), 0, 0; + psq_l fp2, 16(src), 1, 0; + ps_merge10 fp6, fp1, fp0; + psq_l fp3, 20(src), 0, 0; + psq_l fp4, 32(src), 1, 0; + ps_merge10 fp7, fp3, fp2; + psq_l fp5, 36(src), 0, 0; + ps_mul fp11, fp3, fp6; + ps_mul fp13, fp5, fp7; + ps_merge10 fp8, fp5, fp4; + ps_msub fp11, fp1, fp7, fp11; + ps_mul fp12, fp1, fp8; + ps_msub fp13, fp3, fp8, fp13; + ps_mul fp10, fp3, fp4; + ps_msub fp12, fp5, fp6, fp12; + ps_mul fp9, fp0, fp5; + ps_mul fp8, fp1, fp2; + ps_sub fp6, fp6, fp6; + ps_msub fp10, fp2, fp5, fp10; + ps_mul fp7, fp0, fp13; + ps_msub fp9, fp1, fp4, fp9; + ps_madd fp7, fp2, fp12, fp7; + ps_msub fp8, fp0, fp3, fp8; + ps_madd fp7, fp4, fp11, fp7; + ps_cmpo0 cr0, fp7, fp6; + bne loc0; + addi r3, 0, 0; + blr; +loc0: + fres fp0, fp7; + ps_add fp6, fp0, fp0; + ps_mul fp5, fp0, fp0; + ps_nmsub fp0, fp7, fp5, fp6; + lfs fp1, 12(src); + ps_muls0 fp13, fp13, fp0; + lfs fp2, 28(src); + ps_muls0 fp12, fp12, fp0; + lfs fp3, 44(src); + ps_muls0 fp11, fp11, fp0; + ps_merge00 fp5, fp13, fp12; + ps_muls0 fp10, fp10, fp0; + ps_merge11 fp4, fp13, fp12; + ps_muls0 fp9, fp9, fp0; + psq_st fp5, 0(inv), 0, 0; + ps_mul fp6, fp13, fp1; + psq_st fp4, 16(inv), 0, 0; + ps_muls0 fp8, fp8, fp0; + ps_madd fp6, fp12, fp2, fp6; + psq_st fp10, 32(inv), 1, 0; + ps_nmadd fp6, fp11, fp3, fp6; + psq_st fp9, 36(inv), 1, 0; + ps_mul fp7, fp10, fp1; + ps_merge00 fp5, fp11, fp6; + psq_st fp8, 40(inv), 1, 0; + ps_merge11 fp4, fp11, fp6; + psq_st fp5, 8(inv), 0, 0; + ps_madd fp7, fp9, fp2, fp7; + psq_st fp4, 24(inv), 0, 0; + ps_nmadd fp7, fp8, fp3, fp7; + addi r3, 0, 1; + psq_st fp7, 44(inv), 1, 0; + blr; +} + +asm u32 PSMTXInvXpose(const register Mtx src, register Mtx invX) { + nofralloc; + psq_l fp0, 0(src), 1, 0; + psq_l fp1, 4(src), 0, 0; + psq_l fp2, 16(src), 1, 0; + ps_merge10 fp6, fp1, fp0; + psq_l fp3, 20(src), 0, 0; + psq_l fp4, 32(src), 1, 0; + ps_merge10 fp7, fp3, fp2; + psq_l fp5, 36(src), 0, 0; + ps_mul fp11, fp3, fp6; + ps_merge10 fp8, fp5, fp4; + ps_mul fp13, fp5, fp7; + ps_msub fp11, fp1, fp7, fp11; + ps_mul fp12, fp1, fp8; + ps_msub fp13, fp3, fp8, fp13; + ps_msub fp12, fp5, fp6, fp12; + ps_mul fp10, fp3, fp4; + ps_mul fp9, fp0, fp5; + ps_mul fp8, fp1, fp2; + ps_msub fp10, fp2, fp5, fp10; + ps_msub fp9, fp1, fp4, fp9; + ps_msub fp8, fp0, fp3, fp8; + ps_mul fp7, fp0, fp13; + ps_sub fp1, fp1, fp1; + ps_madd fp7, fp2, fp12, fp7; + ps_madd fp7, fp4, fp11, fp7; + ps_cmpo0 cr0, fp7, fp1; + bne _regular; + addi r3, 0, 0; + blr; +_regular: + fres fp0, fp7; + psq_st fp1, 12(invX), 1, 0; + ps_add fp6, fp0, fp0; + ps_mul fp5, fp0, fp0; + psq_st fp1, 28(invX), 1, 0; + ps_nmsub fp0, fp7, fp5, fp6; + psq_st fp1, 44(invX), 1, 0; + ps_muls0 fp13, fp13, fp0; + ps_muls0 fp12, fp12, fp0; + ps_muls0 fp11, fp11, fp0; + psq_st fp13, 0(invX), 0, 0; + psq_st fp12, 16(invX), 0, 0; + ps_muls0 fp10, fp10, fp0; + ps_muls0 fp9, fp9, fp0; + psq_st fp11, 32(invX), 0, 0; + psq_st fp10, 8(invX), 1, 0; + ps_muls0 fp8, fp8, fp0; + addi r3, 0, 1; + psq_st fp9, 24(invX), 1, 0; + psq_st fp8, 40(invX), 1, 0; + blr; +} + +f64 sin(f64); +inline f32 sinf(f32 x) { return (float)sin(x); }; +f64 cos(f64); +inline f32 cosf(f32 x) { return (float)cos(x); }; + +void PSMTXRotRad(Mtx m, char axis, f32 rad) { + f32 sinA, cosA; + sinA = sinf(rad); + cosA = cosf(rad); + PSMTXRotTrig(m, axis, sinA, cosA); +} + +void PSMTXRotTrig(register Mtx m, register char arg2, register f32 arg3, + register f32 arg4) { + register f32 vv1, vv2, vv3; + register f32 vv4, vv5, vv6, vv7; + + asm { + frsp arg3, arg3; + frsp arg4, arg4; + } + + vv1 = 0.0F; + vv2 = 1.0F; + + asm { + ori arg2, arg2, 0x20; + ps_neg vv3, arg3; + cmplwi arg2, 'x'; + beq loc0; + cmplwi arg2, 'y'; + beq loc1; + cmplwi arg2, 'z'; + beq loc2; + b loc3; +loc0: + psq_st vv2, 0(m), 1, 0; + psq_st vv1, 4(m), 0, 0; + ps_merge00 vv4, arg3, arg4; + psq_st vv1, 12(m), 0, 0; + ps_merge00 vv5, arg4, vv3; + psq_st vv1, 28(m), 0, 0; + psq_st vv1, 44(m), 1, 0; + psq_st vv4, 36(m), 0, 0; + psq_st vv5, 20(m), 0, 0; + b loc3; +loc1: + ps_merge00 vv4, arg4, vv1; + ps_merge00 vv5, vv1, vv2; + psq_st vv1, 24(m), 0, 0; + psq_st vv4, 0(m), 0, 0; + ps_merge00 vv6, vv3, vv1; + ps_merge00 vv7, arg3, vv1; + psq_st vv4, 40(m), 0, 0; + psq_st vv5, 16(m), 0, 0; + psq_st vv7, 8(m), 0, 0; + psq_st vv6, 32(m), 0, 0; + b loc3; +loc2: + psq_st vv1, 8(m), 0, 0; + ps_merge00 vv4, arg3, arg4; + ps_merge00 vv6, arg4, vv3; + psq_st vv1, 24(m), 0, 0; + psq_st vv1, 32(m), 0, 0; + ps_merge00 vv5, vv2, vv1; + psq_st vv4, 16(m), 0, 0; + psq_st vv6, 0(m), 0, 0; + psq_st vv5, 40(m), 0, 0; +loc3: + } +} + +void __PSMTXRotAxisRadInternal(register Mtx m, const register Vec* arg2, + register f32 arg3, register f32 arg4) { + register f32 vv1, vv2; + register f32 tmp0, tmp1, tmp2, tmp3, tmp4; + register f32 tmp5, tmp6, tmp7, tmp8, tmp9; + tmp9 = 0.5F; + tmp8 = 3.0F; + asm + { + frsp arg4, arg4; + psq_l tmp0, 0(arg2), 0, 0; + frsp arg3, arg3; + lfs tmp1, 8(arg2); + ps_mul tmp2, tmp0, tmp0; + fadds tmp7, tmp9, tmp9; + ps_madd tmp3, tmp1, tmp1, tmp2; + fsubs vv2, tmp9, tmp9; + ps_sum0 tmp4, tmp3, tmp1, tmp2; + fsubs vv1, tmp7, arg4; + frsqrte tmp5, tmp4; + fmuls tmp2, tmp5, tmp5; + fmuls tmp3, tmp5, tmp9; + fnmsubs tmp2, tmp2, tmp4, tmp8; + fmuls tmp5, tmp2, tmp3; + ps_merge00 arg4, arg4, arg4; + ps_muls0 tmp0, tmp0, tmp5; + ps_muls0 tmp1, tmp1, tmp5; + ps_muls0 tmp4, tmp0, vv1; + ps_muls0 tmp9, tmp0, arg3; + ps_muls0 tmp5, tmp1, vv1; + ps_muls1 tmp3, tmp4, tmp0; + ps_muls0 tmp2, tmp4, tmp0; + ps_muls0 tmp4, tmp4, tmp1; + fnmsubs tmp6, tmp1, arg3, tmp3; + fmadds tmp7, tmp1, arg3, tmp3; + ps_neg tmp0, tmp9; + ps_sum0 tmp8, tmp4, vv2, tmp9; + ps_sum0 tmp2, tmp2, tmp6, arg4; + ps_sum1 tmp3, arg4, tmp7, tmp3; + ps_sum0 tmp6, tmp0, vv2, tmp4; + psq_st tmp8, 8(m), 0, 0; + ps_sum0 tmp0, tmp4, tmp4, tmp0; + psq_st tmp2, 0(m), 0, 0; + ps_muls0 tmp5, tmp5, tmp1; + psq_st tmp3, 16(m), 0, 0; + ps_sum1 tmp4, tmp9, tmp0, tmp4; + psq_st tmp6, 24(m), 0, 0; + ps_sum0 tmp5, tmp5, vv2, arg4; + psq_st tmp4, 32(m), 0, 0; + psq_st tmp5, 40(m), 0, 0; + } +} + +void PSMTXRotAxisRad(Mtx m, const Vec* arg2, f32 arg3) { + f32 arg2sin = sinf(arg3); + f32 arg2cos = cosf(arg3); + __PSMTXRotAxisRadInternal(m, arg2, arg2sin, arg2cos); +} + +void PSMTXTrans(register Mtx m, register f32 _x, register f32 _y, + register f32 _z) { + register f32 vv0 = 0.0f; + register f32 vv1 = 1.0f; + asm + { + stfs _x, 12(m); + stfs _y, 28(m); + psq_st vv0, 4(m), 0, 0; + psq_st vv0, 32(m), 0, 0; + stfs vv0, 16(m); + stfs vv1, 20(m); + stfs vv0, 24(m); + stfs vv1, 40(m); + stfs _z, 44(m); + stfs vv1, 0(m); + } +} + +asm void PSMTXTransApply(const register Mtx in, register Mtx out, + register f32 _x, register f32 _y, register f32 _z) { + nofralloc; + psq_l fp4, 0(in), 0, 0; + frsp _x, _x; + psq_l fp5, 8(in), 0, 0; + frsp _y, _y; + psq_l fp7, 24(in), 0, 0; + frsp _z, _z; + psq_l fp8, 40(in), 0, 0; + psq_st fp4, 0(out), 0, 0; + ps_sum1 fp5, _x, fp5, fp5; + psq_l fp6, 16(in), 0, 0; + psq_st fp5, 8(out), 0, 0; + ps_sum1 fp7, _y, fp7, fp7; + psq_l fp9, 32(in), 0, 0; + psq_st fp6, 16(out), 0, 0; + ps_sum1 fp8, _z, fp8, fp8; + psq_st fp7, 24(out), 0, 0; + psq_st fp9, 32(out), 0, 0; + psq_st fp8, 40(out), 0, 0; + blr; +} + +void PSMTXScale(register Mtx m, register f32 _x, register f32 _y, + register f32 _z) { + register f32 vv0 = 0.0F; + asm + { + stfs _x, 0(m); + psq_st vv0, 4(m), 0, 0; + psq_st vv0, 12(m), 0, 0; + stfs _y, 20(m); + psq_st vv0, 24(m), 0, 0; + psq_st vv0, 32(m), 0, 0; + stfs _z, 40(m); + stfs vv0, 44(m); + } +} + +asm void PSMTXScaleApply(const register Mtx in, register Mtx out, + register f32 _x, register f32 _y, register f32 _z) { + nofralloc; + frsp _x, _x; + psq_l fp4, 0(in), 0, 0; + frsp _y, _y; + psq_l fp5, 8(in), 0, 0; + frsp _z, _z; + ps_muls0 fp4, fp4, _x; + psq_l fp6, 16(in), 0, 0; + ps_muls0 fp5, fp5, _x; + psq_l fp7, 24(in), 0, 0; + ps_muls0 fp6, fp6, _y; + psq_l fp8, 32(in), 0, 0; + psq_st fp4, 0(out), 0, 0; + ps_muls0 fp7, fp7, _y; + psq_l fp2, 40(in), 0, 0; + psq_st fp5, 8(out), 0, 0; + ps_muls0 fp8, fp8, _z; + psq_st fp6, 16(out), 0, 0; + ps_muls0 fp2, fp2, _z; + psq_st fp7, 24(out), 0, 0; + psq_st fp8, 32(out), 0, 0; + psq_st fp2, 40(out), 0, 0; + blr; +} + +void PSMTXQuat(register Mtx m, const register Quaternion* quat) { + register f32 vv0, vv1, vv2, vv3; + register f32 tmp0, tmp1, tmp2, tmp3, tmp4; + register f32 tmp5, tmp6, tmp7, tmp8, tmp9; + vv1 = 1.0f; + asm + { + psq_l tmp0, 0(quat), 0, 0; + psq_l tmp1, 8(quat), 0, 0; + fsubs vv0, vv1, vv1; + fadds vv2, vv1, vv1; + ps_mul tmp2, tmp0, tmp0; + ps_merge10 tmp5, tmp0, tmp0; + ps_madd tmp4, tmp1, tmp1, tmp2; + ps_mul tmp3, tmp1, tmp1; + ps_sum0 vv3, tmp4, tmp4, tmp4; + ps_muls1 tmp7, tmp5, tmp1; + fres tmp9, vv3; + ps_sum1 tmp4, tmp3, tmp4, tmp2; + ps_nmsub vv3, vv3, tmp9, vv2; + ps_muls1 tmp6, tmp1, tmp1; + ps_mul vv3, tmp9, vv3; + ps_sum0 tmp2, tmp2, tmp2, tmp2; + fmuls vv3, vv3, vv2; + ps_madd tmp8, tmp0, tmp5, tmp6; + ps_msub tmp6, tmp0, tmp5, tmp6; + psq_st vv0, 12(m), 1, 0; + ps_nmsub tmp2, tmp2, vv3, vv1; + ps_nmsub tmp4, tmp4, vv3, vv1; + psq_st vv0, 44(m), 1, 0; + ps_mul tmp8, tmp8, vv3; + ps_mul tmp6, tmp6, vv3; + psq_st tmp2, 40(m), 1, 0; + ps_madds0 tmp5, tmp0, tmp1, tmp7; + ps_merge00 tmp1, tmp8, tmp4; + ps_nmsub tmp7, tmp7, vv2, tmp5; + ps_merge10 tmp0, tmp4, tmp6; + psq_st tmp1, 16(m), 0, 0; + ps_mul tmp5, tmp5, vv3; + ps_mul tmp7, tmp7, vv3; + psq_st tmp0, 0(m), 0, 0; + psq_st tmp5, 8(m), 1, 0; + ps_merge10 tmp3, tmp7, vv0; + ps_merge01 tmp9, tmp7, tmp5; + psq_st tmp3, 24(m), 0, 0; + psq_st tmp9, 32(m), 0, 0; + } +} + +void C_MTXLookAt(Mtx m, const Vec* _pos, const Vec* _up, const Vec* _dest) { + Vec vv0, vv1, vv2; + vv0.x = _pos->x - _dest->x; + vv0.y = _pos->y - _dest->y; + vv0.z = _pos->z - _dest->z; + PSVECNormalize(&vv0, &vv0); + PSVECCrossProduct(_up, &vv0, &vv1); + PSVECNormalize(&vv1, &vv1); + PSVECCrossProduct(&vv0, &vv1, &vv2); + m[0][0] = vv1.x; + m[0][1] = vv1.y; + m[0][2] = vv1.z; + m[0][3] = -(_pos->x * vv1.x + _pos->y * vv1.y + _pos->z * vv1.z); + m[1][0] = vv2.x; + m[1][1] = vv2.y; + m[1][2] = vv2.z; + m[1][3] = -(_pos->x * vv2.x + _pos->y * vv2.y + _pos->z * vv2.z); + m[2][0] = vv0.x; + m[2][1] = vv0.y; + m[2][2] = vv0.z; + m[2][3] = -(_pos->x * vv0.x + _pos->y * vv0.y + _pos->z * vv0.z); +} + +void C_MTXLightFrustum(Mtx m, float arg1, float arg2, float arg3, float arg4, + float arg5, float arg6, float arg7, float arg8, + float arg9) { + f32 tmp = 1.0f / (arg4 - arg3); + m[0][0] = ((2 * arg5) * tmp) * arg6; + m[0][1] = 0.0f; + m[0][2] = (((arg4 + arg3) * tmp) * arg6) - arg8; + m[0][3] = 0.0f; + tmp = 1.0f / (arg1 - arg2); + m[1][0] = 0.0f; + m[1][1] = ((2 * arg5) * tmp) * arg7; + m[1][2] = (((arg1 + arg2) * tmp) * arg7) - arg9; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = -1.0f; + m[2][3] = 0.0f; +} + +f64 tan(f64); +inline f32 tanf(f32 x) { return (float)tan(x); }; + +void C_MTXLightPerspective(Mtx m, f32 arg1, f32 arg2, float arg3, float arg4, + float arg5, float arg6) { + f32 angle = arg1 * 0.5f * 0.01745329252f; + f32 cot = 1.0f / tanf(angle); + m[0][0] = (cot / arg2) * arg3; + m[0][1] = 0.0f; + m[0][2] = -arg5; + m[0][3] = 0.0f; + m[1][0] = 0.0f; + m[1][1] = cot * arg4; + m[1][2] = -arg6; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = -1.0f; + m[2][3] = 0.0f; +} + +void C_MTXLightOrtho(Mtx m, f32 arg2, f32 arg3, f32 arg4, f32 arg5, f32 arg6, + f32 arg7, f32 arg8, f32 arg9) { + f32 tmp; + tmp = 1.0f / (arg5 - arg4); + m[0][0] = (2.0f * tmp * arg6); + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = ((-(arg5 + arg4) * tmp) * arg6) + arg8; + tmp = 1.0f / (arg2 - arg3); + m[1][0] = 0.0f; + m[1][1] = (2.0f * tmp) * arg7; + m[1][2] = 0.0f; + m[1][3] = ((-(arg2 + arg3) * tmp) * arg7) + arg9; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + m[2][2] = 0.0f; + m[2][3] = 1.0f; +} + +asm void PSMTXMultVec(const register Mtx m, const register Vec* in, + register Vec* out) { + nofralloc; + psq_l fp0, 0(in), 0, 0; + psq_l fp2, 0(m), 0, 0; + psq_l fp1, 8(in), 1, 0; + ps_mul fp4, fp2, fp0; + psq_l fp3, 8(m), 0, 0; + ps_madd fp5, fp3, fp1, fp4; + psq_l fp8, 16(m), 0, 0; + ps_sum0 fp6, fp5, fp6, fp5; + psq_l fp9, 24(m), 0, 0; + ps_mul fp10, fp8, fp0; + psq_st fp6, 0(out), 1, 0; + ps_madd fp11, fp9, fp1, fp10; + psq_l fp2, 32(m), 0, 0; + ps_sum0 fp12, fp11, fp12, fp11; + psq_l fp3, 40(m), 0, 0; + ps_mul fp4, fp2, fp0; + psq_st fp12, 4(out), 1, 0; + ps_madd fp5, fp3, fp1, fp4; + ps_sum0 fp6, fp5, fp6, fp5; + psq_st fp6, 8(out), 1, 0; + blr; +} + +asm void PSMTXMultVecSR(const register Mtx m, const register Vec* in, + register Vec* out) { + nofralloc; + psq_l fp0, 0(m), 0, 0; + psq_l fp6, 0(in), 0, 0; + psq_l fp2, 16(m), 0, 0; + ps_mul fp8, fp0, fp6; + psq_l fp4, 32(m), 0, 0; + ps_mul fp10, fp2, fp6; + psq_l fp7, 8(in), 1, 0; + ps_mul fp12, fp4, fp6; + psq_l fp3, 24(m), 0, 0; + ps_sum0 fp8, fp8, fp8, fp8; + psq_l fp5, 40(m), 0, 0; + ps_sum0 fp10, fp10, fp10, fp10; + psq_l fp1, 8(m), 0, 0; + ps_sum0 fp12, fp12, fp12, fp12; + ps_madd fp9, fp1, fp7, fp8; + psq_st fp9, 0(out), 1, 0; + ps_madd fp11, fp3, fp7, fp10; + psq_st fp11, 4(out), 1, 0; + ps_madd fp13, fp5, fp7, fp12; + psq_st fp13, 8(out), 1, 0; + blr; +} diff --git a/source/rvl/mtx/rvlMtx2.c b/source/rvl/mtx/rvlMtx2.c new file mode 100644 index 000000000..090252150 --- /dev/null +++ b/source/rvl/mtx/rvlMtx2.c @@ -0,0 +1,73 @@ +#include "mtx.h" + +double tan(double); + +inline float tanf(float x) { return (float)tan(x); } + +void C_MTXFrustum(Mtx44 m, f32 arg1, f32 arg2, f32 arg3, f32 arg4, f32 arg5, + f32 arg6) { + f32 tmp = 1.0f / (arg4 - arg3); + m[0][0] = (2 * arg5) * tmp; + m[0][1] = 0.0f; + m[0][2] = (arg4 + arg3) * tmp; + m[0][3] = 0.0f; + tmp = 1.0f / (arg1 - arg2); + m[1][0] = 0.0f; + m[1][1] = (2 * arg5) * tmp; + m[1][2] = (arg1 + arg2) * tmp; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + tmp = 1.0f / (arg6 - arg5); + m[2][2] = -(arg5)*tmp; + m[2][3] = -(arg6 * arg5) * tmp; + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = -1.0f; + m[3][3] = 0.0f; +} + +void C_MTXPerspective(Mtx44 m, f32 fovY, f32 aspect, f32 n, f32 f) { + f32 angle = fovY * 0.5f; + angle = ((angle)*0.01745329252f); + f32 cot = 1.0f / tanf(angle); + m[0][0] = cot / aspect; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = 0.0f; + m[1][0] = 0.0f; + m[1][1] = cot; + m[1][2] = 0.0f; + m[1][3] = 0.0f; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + f32 tmp = 1.0f / (f - n); + m[2][2] = -(n)*tmp; + m[2][3] = -(f * n) * tmp; + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = -1.0f; + m[3][3] = 0.0f; +} + +void C_MTXOrtho(Mtx44 m, f32 a1, f32 a2, f32 a3, f32 a4, f32 a5, f32 a6) { + f32 tmp = 1.0f / (a4 - a3); + m[0][0] = 2.0f * tmp; + m[0][1] = 0.0f; + m[0][2] = 0.0f; + m[0][3] = -(a4 + a3) * tmp; + tmp = 1.0f / (a1 - a2); + m[1][0] = 0.0f; + m[1][1] = 2.0f * tmp; + m[1][2] = 0.0f; + m[1][3] = -(a1 + a2) * tmp; + m[2][0] = 0.0f; + m[2][1] = 0.0f; + tmp = 1.0f / (a6 - a5); + m[2][2] = -(1.0f) * tmp; + m[2][3] = -(a6)*tmp; + m[3][0] = 0.0f; + m[3][1] = 0.0f; + m[3][2] = 0.0f; + m[3][3] = 1.0f; +} diff --git a/source/rvl/mtx/rvlQuat.c b/source/rvl/mtx/rvlQuat.c new file mode 100644 index 000000000..9312d45df --- /dev/null +++ b/source/rvl/mtx/rvlQuat.c @@ -0,0 +1,178 @@ +#include "mtx.h" + +void PSQUATMultiply(const register Quaternion* quat1, + const register Quaternion* quat2, + register Quaternion* out) { + register f32 vv1, vv2, vv3, vv4; + register f32 vv5, vv6, vv7, vv8; + register f32 vv9, vvA, vvB, vvC; + asm + { + psq_l vv1, 0(quat1), 0, 0; + psq_l vv2, 8(quat1), 0, 0; + psq_l vv3, 0(quat2), 0, 0; + ps_neg vv7, vv1; + psq_l vv4, 8(quat2), 0, 0; + ps_neg vv8, vv2; + ps_merge01 vv5, vv7, vv1; + ps_muls0 vv9, vv2, vv3; + ps_muls0 vvA, vv7, vv3; + ps_merge01 vv6, vv8, vv2; + ps_muls1 vvC, vv5, vv3; + ps_madds0 vv9, vv5, vv4, vv9; + ps_muls1 vvB, vv6, vv3; + ps_madds0 vvA, vv6, vv4, vvA; + ps_madds1 vvC, vv8, vv4, vvC; + ps_merge10 vv9, vv9, vv9; + ps_madds1 vvB, vv1, vv4, vvB; + ps_merge10 vvA, vvA, vvA; + ps_add vv9, vv9, vvB; + psq_st vv9, 0(out), 0, 0; + ps_sub vvA, vvA, vvC; + psq_st vvA, 8(out), 0, 0; + } +} + +void PSQUATScale(const register Quaternion* quat1, register Quaternion* quat2, + register f32 ff1) { + register f32 vv1, vv2; + asm + { + psq_l vv1, 0(quat1), 0, 0; + psq_l vv2, 8(quat1), 0, 0; + ps_muls0 vv1, vv1, ff1; + psq_st vv1, 0(quat2), 0, 0; + ps_muls0 vv2, vv2, ff1; + psq_st vv2, 8(quat2), 0, 0; + } +} + +f32 PSQUATDotProduct(const register Quaternion* quat1, + const register Quaternion* quat2) { + register f32 vv1, vv2, vv3, vv4, out; + asm + { + psq_l vv1, 0(quat1), 0, 0; + psq_l vv3, 0(quat2), 0, 0; + ps_mul out, vv1, vv3; + psq_l vv2, 8(quat1), 0, 0; + psq_l vv4, 8(quat2), 0, 0; + ps_madd out, vv2, vv4, out; + ps_sum0 out, out, out, out; + } + return out; +} + +void PSQUATNormalize(const register Quaternion* src, + register Quaternion* unit) { + // sdata2 + (void)0.00001f; + (void)1.0f; + (void)0.0f; + (void)0.5f; + (void)3.0f; + + register f32 vv1, vv2, vv3; + register f32 vv4, vv5, vv6; + register f32 vv7, vv8; + register f32 vv9 = 0.00001f; + register f32 vvA = 0.5F; + register f32 vvB = 3.0F; + asm + { + psq_l vv1, 0(src), 0, 0; + ps_mul vv3, vv1, vv1; + psq_l vv2, 8(src), 0, 0; + ps_sub vv6, vv9, vv9; + ps_madd vv3, vv2, vv2, vv3; + ps_sum0 vv3, vv3, vv3, vv3; + frsqrte vv4, vv3; + ps_sub vv5, vv3, vv9; + fmul vv7, vv4, vv4; + fmul vv8, vv4, vvA; + fnmsub vv7, vv7, vv3, vvB; + fmul vv4, vv7, vv8; + ps_sel vv4, vv5, vv4, vv6; + ps_muls0 vv1, vv1, vv4; + ps_muls0 vv2, vv2, vv4; + psq_st vv1, 0(unit), 0, 0; + psq_st vv2, 8(unit), 0, 0; + } +} + +void PSQUATInverse(const register Quaternion* src, register Quaternion* inv) { + register f32 vv1, vv2, vv3, vv4; + register f32 vv5, vv6, vv7, vv8, vv9, vvA, vvB; + register f32 vvC = 1.0F; + asm { + psq_l vv1, 0(src), 0, 0; + ps_mul vv5, vv1, vv1; + ps_sub vvB, vvC, vvC; + psq_l vv2, 8(src), 0, 0; + ps_madd vv5, vv2, vv2, vv5; + ps_add vvA, vvC, vvC; + ps_sum0 vv5, vv5, vv5, vv5; + fcmpu cr0, vv5, vvB; + beq- loc0; + fres vv7, vv5; + ps_neg vv6, vv5; + ps_nmsub vv9, vv5, vv7, vvA; + ps_mul vv7, vv7, vv9; + b loc1; +loc0: + fmr vv7, vvC; +loc1: + ps_neg vv8, vv7; + ps_muls1 vv4, vv7, vv2; + ps_muls0 vv1, vv1, vv8; + psq_st vv4, 12(inv), 1, 0; + ps_muls0 vv3, vv2, vv8; + psq_st vv1, 0(inv), 0, 0; + psq_st vv3, 8(inv), 1, 0; + } +} + +double sqrt(double); +inline float sqrtf(float x) { return (float)sqrt(x); } + +void C_QUATMtx(Quaternion* r, const Mtx m) { + f32 vv0, vv1; + s32 i, j, k; + s32 idx[3] = {1, 2, 0}; + f32 vec[3]; + vv0 = m[0][0] + m[1][1] + m[2][2]; + if (vv0 > 0.0f) { + vv1 = (f32)sqrtf(vv0 + 1.0f); + r->w = vv1 * 0.5f; + vv1 = 0.5f / vv1; + r->x = (m[2][1] - m[1][2]) * vv1; + r->y = (m[0][2] - m[2][0]) * vv1; + r->z = (m[1][0] - m[0][1]) * vv1; + } else { + i = 0; + if (m[1][1] > m[0][0]) + i = 1; + if (m[2][2] > m[i][i]) + i = 2; + j = idx[i]; + k = idx[j]; + vv1 = (f32)sqrtf((m[i][i] - (m[j][j] + m[k][k])) + 1.0f); + vec[i] = vv1 * 0.5f; + if (vv1 != 0.0f) + vv1 = 0.5f / vv1; + r->w = (m[k][j] - m[j][k]) * vv1; + vec[j] = (m[i][j] + m[j][i]) * vv1; + vec[k] = (m[i][k] + m[k][i]) * vv1; + r->x = vec[0]; + r->y = vec[1]; + r->z = vec[2]; + } +} + +void C_QUATLerp(const Quaternion* quat1, const Quaternion* quat2, + Quaternion* out, f32 f) { + out->x = f * (quat2->x - quat1->x) + quat1->x; + out->y = f * (quat2->y - quat1->y) + quat1->y; + out->z = f * (quat2->z - quat1->z) + quat1->z; + out->w = f * (quat2->w - quat1->w) + quat1->w; +} diff --git a/source/rvl/mtx/rvlVec.c b/source/rvl/mtx/rvlVec.c new file mode 100644 index 000000000..112fb17b2 --- /dev/null +++ b/source/rvl/mtx/rvlVec.c @@ -0,0 +1,156 @@ +#include "mtx.h" + +asm void PSVECAdd(const register Vec* vv1, const register Vec* vv2, + register Vec* out) { + nofralloc; + psq_l fp2, 0(vv1), 0, 0; + psq_l fp4, 0(vv2), 0, 0; + ps_add fp6, fp2, fp4; + psq_st fp6, 0(out), 0, 0; + psq_l fp3, 8(vv1), 1, 0; + psq_l fp5, 8(vv2), 1, 0; + ps_add fp7, fp3, fp5; + psq_st fp7, 8(out), 1, 0; + blr; +} + +void PSVECScale(const register Vec* in, register Vec* out, register f32 vv1) { + register f32 vxy, vz, rxy, rz; + asm + { + psq_l vxy, 0(in), 0, 0; + psq_l vz, 8(in), 1, 0; + ps_muls0 rxy, vxy, vv1; + psq_st rxy, 0(out), 0, 0; + ps_muls0 rz, vz, vv1; + psq_st rz, 8(out), 1, 0; + } +} + +void PSVECNormalize(const register Vec* vec1, register Vec* dst) { + register f32 vv1 = 0.5F; + register f32 vv2 = 3.0F; + register f32 vv3, vv4; + register f32 vv5, vv6; + register f32 vv7; + register f32 vv8; + register f32 vv9, vv10; + + asm + { + psq_l vv3, 0(vec1), 0, 0; + ps_mul vv6, vv3, vv3; + psq_l vv4, 8(vec1), 1, 0; + ps_madd vv5, vv4, vv4, vv6; + ps_sum0 vv7, vv5, vv4, vv6; + frsqrte vv8, vv7; + fmuls vv9, vv8, vv8; + fmuls vv10, vv8, vv1; + fnmsubs vv9, vv9, vv7, vv2; + fmuls vv8, vv9, vv10; + ps_muls0 vv3, vv3, vv8; + psq_st vv3, 0(dst), 0, 0; + ps_muls0 vv4, vv4, vv8; + psq_st vv4, 8(dst), 1, 0; + } +} + +f32 PSVECMag(const register Vec* v) { + register f32 vv1, vv2, vv3; + register f32 vv4, vv5, vv6; + register f32 vv7, vv8, vv9; + vv8 = 0.5F; + asm { + psq_l vv1, 0(v), 0, 0; + ps_mul vv1, vv1, vv1; + lfs vv2, 8(v); + fsubs vv9, vv8, vv8; + ps_madd vv3, vv2, vv2, vv1; + ps_sum0 vv3, vv3, vv1, vv1; + fcmpu cr0, vv3, vv9; + beq- end; + frsqrte vv4, vv3; + } + vv7 = 3.0F; + asm { + fmuls vv5, vv4, vv4; + fmuls vv6, vv4, vv8; + fnmsubs vv5, vv5, vv3, vv7; + fmuls vv4, vv5, vv6; + fmuls vv3, vv3, vv4; + end: + } + return vv3; +} + +asm f32 PSVECDotProduct(const register Vec* vec1, const register Vec* vec2) { + nofralloc; + psq_l fp2, 4(vec1), 0, 0; + psq_l fp3, 4(vec2), 0, 0; + ps_mul fp2, fp2, fp3; + psq_l fp5, 0(vec1), 0, 0; + psq_l fp4, 0(vec2), 0, 0; + ps_madd fp3, fp5, fp4, fp2; + ps_sum0 fp1, fp3, fp2, fp2; + blr; +} + +asm void PSVECCrossProduct(const register Vec* vec1, const register Vec* vec2, + register Vec* out) { + nofralloc; + psq_l fp1, 0(vec2), 0, 0; + lfs fp2, 8(vec1); + psq_l fp0, 0(vec1), 0, 0; + ps_merge10 fp6, fp1, fp1; + lfs fp3, 8(vec2); + ps_mul fp4, fp1, fp2; + ps_muls0 fp7, fp1, fp0; + ps_msub fp5, fp0, fp3, fp4; + ps_msub fp8, fp0, fp6, fp7; + ps_merge11 fp9, fp5, fp5; + ps_merge01 fp10, fp5, fp8; + psq_st fp9, 0(out), 1, 0; + ps_neg fp10, fp10; + psq_st fp10, 4(out), 0, 0; + blr; +} + +void C_VECHalfAngle(const Vec* a, const Vec* b, Vec* half) { + Vec vv1, vv2, vv3; + + vv1.x = -a->x; + vv1.y = -a->y; + vv1.z = -a->z; + + vv2.x = -b->x; + vv2.y = -b->y; + vv2.z = -b->z; + + PSVECNormalize(&vv1, &vv1); + PSVECNormalize(&vv2, &vv2); + + PSVECAdd(&vv1, &vv2, &vv3); + + if (PSVECDotProduct(&vv3, &vv3) > 0.0F) + PSVECNormalize(&vv3, half); + else + *half = vv3; +} + +f32 PSVECSquareDistance(const register Vec* vec1, const register Vec* vec2) { + register f32 vv1, vv2, vv3, vv4, vv5, vv6; + register f32 out; + asm + { + psq_l vv1, 4(vec1), 0, 0; + psq_l vv2, 4(vec2), 0, 0; + ps_sub vv5, vv1, vv2; + psq_l vv3, 0(vec1), 0, 0; + psq_l vv4, 0(vec2), 0, 0; + ps_mul vv5, vv5, vv5; + ps_sub vv6, vv3, vv4; + ps_madd out, vv6, vv6, vv5; + ps_sum0 out, out, vv5, vv5; + } + return out; +} diff --git a/sources.py b/sources.py index 061fb54ea..200b2222e 100644 --- a/sources.py +++ b/sources.py @@ -5,6 +5,10 @@ compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mtx/rvlMtx.c", "out/rvlMtx.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mtx/rvlMtx2.c", "out/rvlMtx2.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mtx/rvlVec.c", "out/rvlVec.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mtx/rvlQuat.c", "out/rvlQuat.o", '4199_60831', RVL_OPTS) compile_source("source/nw4r/math/mathTriangular.cpp", "out/mathTriangular.o", '4201_127', NW4R_OPTS) compile_source("source/nw4r/math/mathTypes.cpp", "out/mathTypes.o", '4201_127', NW4R_OPTS) From d1e92af77c1b2c8600eadf71a444edc4d93f7561 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sun, 20 Jun 2021 16:01:00 -0600 Subject: [PATCH 081/477] :art: Add RVL progress to percent_decompiled.py --- mkwutil/percent_decompiled.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/mkwutil/percent_decompiled.py b/mkwutil/percent_decompiled.py index 802e54526..8129c3425 100644 --- a/mkwutil/percent_decompiled.py +++ b/mkwutil/percent_decompiled.py @@ -94,6 +94,24 @@ def analyze(prefix, progress, total): ) ) +# TODO: This system is garbage. +# Ideally we'd have +# +# { "name": "EGG", "start": ..., "end": ...} +# +# and it would generate everything for us +# + +def get_progress(slices, filter): + progress = [0, 0] + for o_name, o_code_total, o_data_total in slices: + if not filter in o_name: + continue + + progress[0] += o_code_total + progress[1] += o_data_total + + return progress def percent_decompiled(dir="."): dir = Path(dir) @@ -103,16 +121,12 @@ def percent_decompiled(dir="."): dol_total = binary_total(dol_segments_path) analyze("[DOL]", dol_progress, dol_total) - egg_progress = [0, 0] - for o_name, o_code_total, o_data_total in parse_slices(dol_slices_path): - if "egg" not in o_name: - continue - - egg_progress[0] += o_code_total - egg_progress[1] += o_data_total + rvl_progress = get_progress(parse_slices(dol_slices_path), "rvl") + rvl_total = [0x8020F62C - 0x80123F88, None] + analyze(" -> [RVL]", rvl_progress, rvl_total) + egg_progress = get_progress(parse_slices(dol_slices_path), "egg") egg_total = [0x80244DD4 - 0x8020F62C, None] - analyze(" -> [EGG]", egg_progress, egg_total) rel_slices_path = dir / "pack" / "rel_slices.csv" @@ -135,8 +149,8 @@ def piecewise_add(x, y): print(" - %u BR (main.dol)" % (dol_progress[0] / dol_total[0] * 4999 + 5000)) print(" - %u VR (StaticR.rel)" % (rel_progress[0] / rel_total[0] * 4999 + 5000)) - print(dol_total[0] / 4999 / 4) - print(rel_total[0] / 4999 / 4) + print("1 BR = %s lines of asm code." % (.1 * round(10 * dol_total[0] / 4999 / 4))) + print("1 VR = %s lines of asm code." % (.1 * round(10 * rel_total[0] / 4999 / 4))) if __name__ == "__main__": From 4433c6ce4f24feb3c5a8407712facaf41741219d Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 3 Jul 2021 17:16:52 +0200 Subject: [PATCH 082/477] Decompile RVL/MEM (#21) * Add MEMiInitHeapHead * add MEMiFinalizeHeap * add MEMFindContainHeap * add AllocFromHead_, AllocFromTail_ * add MEM_RecycleRegion * add MEMCreateExpHeapEx * add MEMAllocFromExpHeapEx, MEMResizeForMBlockExpHeap * add MEMFreeToExpHeap * add MEMGetTotalFreeSizeForExpHeap * add MEMSetGroupIDForExpHeap * add MEMVisitAllocatedForExpHeap * add MEMGetSizeForMBlockExpHeap, MEMGetGroupIDForMBlockExpHeap * add MEMAdjustExpHeap * add MEMCreateFrmHeapEx * add MEMAllocFromFrmHeapEx * add MEMFreeToFrmHeap * add MEMGetAllocatableSizeForFrmHeapEx * add MEMRecordStateForFrmHeap * add MEMFreeByStateToFrmHeap * inlining * add MEMCreateUnitHeapEx * add MEMDestroyUnitHeap, MEMAllocFromUnitHeap, MEMFreeToUnitHeap * add MEMCalcHeapSizeForUnitHeap * add MEM_AllocForExpHeap_ * add MEM_FreeForExpHeap_ * add MEM_AllocForUnitHeap, MEM_FreeForUnitHeap * add MEMAllocFromAllocator, MEMFreeToAllocator * add MEMInitAllocatorForExpHeap, MEMInitAllocatorForUnitHeap --- build.py | 14 +- mkwutil/gen_asm.py | 5 +- mkwutil/verify_object_file.py | 17 + pack/dol.base.lcf | 30 +- pack/dol_objects.txt | 15 +- pack/dol_slices.csv | 5 + source/egg/core/eggArchive.cpp | 1 + source/egg/core/eggArchive.hpp | 6 +- source/egg/core/eggHeap.cpp | 10 +- source/egg/core/eggHeap.hpp | 8 +- source/egg/core/eggUnitHeap.cpp | 4 +- source/platform/math.h | 20 ++ source/platform/stdlib.h | 5 + source/platform/string.h | 13 + source/rvl/mem/expHeap.h | 95 +++-- source/rvl/mem/frmHeap.h | 41 +++ source/rvl/mem/heap.h | 18 + source/rvl/mem/heapi.h | 48 +++ source/rvl/mem/memAllocator.h | 66 +++- source/rvl/mem/rvlMemAllocator.c | 64 ++++ source/rvl/mem/rvlMemExpHeap.c | 592 +++++++++++++++++++++++++++++++ source/rvl/mem/rvlMemFrmHeap.c | 193 ++++++++++ source/rvl/mem/rvlMemHeap.c | 70 ++++ source/rvl/mem/rvlMemList.c | 17 +- source/rvl/mem/rvlMemList.h | 30 +- source/rvl/mem/rvlMemUnitHeap.c | 89 +++++ source/rvl/mem/unitHeap.h | 16 +- source/rvl/mtx/rvlMtx.c | 10 +- source/rvl/os/osThread.h | 20 +- sources.py | 5 + 30 files changed, 1400 insertions(+), 127 deletions(-) create mode 100644 mkwutil/verify_object_file.py create mode 100644 source/platform/math.h create mode 100644 source/platform/stdlib.h create mode 100644 source/platform/string.h create mode 100644 source/rvl/mem/frmHeap.h create mode 100644 source/rvl/mem/heap.h create mode 100644 source/rvl/mem/heapi.h create mode 100644 source/rvl/mem/rvlMemAllocator.c create mode 100644 source/rvl/mem/rvlMemExpHeap.c create mode 100644 source/rvl/mem/rvlMemFrmHeap.c create mode 100644 source/rvl/mem/rvlMemHeap.c create mode 100644 source/rvl/mem/rvlMemUnitHeap.c diff --git a/build.py b/build.py index 04dd498ca..51b62900b 100644 --- a/build.py +++ b/build.py @@ -4,6 +4,8 @@ import subprocess import sys +from mkwutil.gen_asm import read_slices +from mkwutil.verify_object_file import verify_object_file from mkwutil.gen_lcf import gen_lcf from mkwutil.pack_main_dol import pack_main_dol from mkwutil.pack_staticr_rel import pack_staticr_rel @@ -12,6 +14,9 @@ from mkwutil.percent_decompiled import percent_decompiled +dol_slices = read_slices("pack/dol_slices.csv", verbose=False) +dol_slices = { sl.obj_file : sl for sl in dol_slices } + def native_binary(path): if sys.platform == "win32" or sys.platform == "msys": return path + ".exe" @@ -96,12 +101,17 @@ def windows_binary(path): def compile_source(src, dst, version="default", additional="-ipa file"): + # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" - if VERBOSE: print(command) - subprocess.run(command, check=True) + # Verify ELF file section sizes. + tha_slice = dol_slices.get(src) + if tha_slice: + verify_object_file(dst, src, tha_slice) + else: + print("# Skipping slices verification on", src) def assemble(dst, src): diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index cb7bb75b5..e23d307b9 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -38,7 +38,7 @@ def __repr__(self): # Limitation: slices must be ordered -def read_slices(name): +def read_slices(name, verbose=True): lines = open(name).readlines() reader = csv.DictReader(lines) for row in reader: @@ -69,7 +69,8 @@ def read_slices(name): elif seg_type == "End": segments[seg_name].end = int(value, 16) - print("#### %s %s" % (name, segments)) + if verbose: + print("#### %s %s" % (name, segments)) yield Slice(name, segments) diff --git a/mkwutil/verify_object_file.py b/mkwutil/verify_object_file.py new file mode 100644 index 000000000..99ff6f5a1 --- /dev/null +++ b/mkwutil/verify_object_file.py @@ -0,0 +1,17 @@ +from elftools.elf.elffile import ELFFile + +def verify_object_file(dst, src, obj_slice): + match = True + with open(dst, 'rb') as f: + elf_file = ELFFile(f) + for section in elf_file.iter_sections(): + section_name = section.name.removeprefix(".") + part = obj_slice.segments.get(section_name) + if not part: + continue + want_size = part.size() + have_size = section.data_size + if want_size != have_size: + match = False + print("[!] %s %s want=0x%x got=0x%x" % (src, section_name, want_size, have_size)) + return match diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index fd5881c68..dc6b005bf 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -62,6 +62,10 @@ CXGetUncompressedSize=0x8015C2E0; OSPanic=0x801A2660; +OSDisableInterrupts=0x801A65AC; +OSEnableInterrupts=0x801A65C0; +OSRestoreInterrupts=0x801A65D4; + OSInitMutex=0x801A7EAC; OSLockMutex=0x801A7EE4; @@ -77,7 +81,6 @@ OSCancelThread=0x801AA1D4; onExit__Q23EGG6ThreadFv=0x80008E7C; onEnter__Q23EGG6ThreadFv=0x80008E80; OSGetCurrentThread=0x801A98B0; -MEMFindContainHeap=0x80198658; DVDReadPrio=0x8015E834; DVDOpen=0x8015E2BC; @@ -93,31 +96,6 @@ GXSetDispCopySrc=0x8016F438; GXSetDispCopyDst=0x8016F4B8; GXSetDispCopyYScale=0x8016F8FC; -MEMCreateUnitHeapEx= 0x801998A4; -MEMDestroyUnitHeap= 0x80199A00; -MEMAllocFromUnitHeap= 0x80199A30; -MEMFreeToUnitHeap= 0x80199AC4; -MEMCalcHeapSizeForUnitHeap= 0x80199B34; -AllocatorAllocForUnitHeap_= 0x80199B70; -AllocatorFreeForUnitHeap_= 0x80199B90; -MEMInitAllocatorForUnitHeap= 0x80199BD4; - -MEMCreateExpHeapEx = 0x80198CA8; -MEMDestroyExpHeap = 0x80198D58; -MEMAllocFromExpHeapEx = 0x80198D88; -MEMResizeForMBlockExpHeap = 0x80198E38; -MEMFreeToExpHeap = 0x80199038; -MEMGetTotalFreeSizeForExpHeap = 0x80199104; -MEMGetAllocatableSizeForExpHeapEx = 0x80199180; -MEMSetGroupIDForExpHeap = 0x80199258; -MEMVisitAllocatedForExpHeap = 0x801992A8; -MEMGetSizeForMBlockExpHeap = 0x80199344; -MEMGetGroupIDForMBlockExpHeap = 0x8019934C; -MEMAdjustExpHeap = 0x80199358; -AllocatorAllocForExpHeap_ = 0x80199B58; -AllocatorFreeForExpHeap_ = 0x80199B68; -MEMInitAllocatorForExpHeap = 0x80199BB8; - VIInit = 0x801B94A4; VIWaitForRetrace = 0x801B99EC; VIConfigure = 0x801B9F6C; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 2acdff564..30c1e9419 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -16,10 +16,17 @@ out\dol\text_800ccc80_80124500.o out\dol\data_80275758_8027e708.o out\dol\sdata_80384c00_803857f0.o out\rvlArchive.o -out\dol\text_80124e80_80199bf0.o +out\dol\text_80124e80_801981ec.o +out\dol\bss_802a4080_80346cf0.o +out\dol\sbss_803862b0_80386838.o +out\rvlMemHeap.o +out\rvlMemExpHeap.o +out\rvlMemFrmHeap.o +out\rvlMemUnitHeap.o +out\dol\sdata2_80387eb4_80388860.o +out\rvlMemAllocator.o out\rvlMemList.o out\dol\sdata_803857f6_80385a08.o -out\dol\sdata2_80387eb4_80388870.o out\rvlMtx.o out\rvlMtx2.o out\rvlVec.o @@ -29,8 +36,8 @@ out\rvlQuat.o out\dol\text_8019b178_8020f62c.o out\dol\data_8027e772_802a2668.o out\eggAllocator.o -out\dol\bss_802a4080_803832d8.o -out\dol\sbss_803862b0_80386d80.o +out\dol\bss_80346d18_803832d8.o +out\dol\sbss_8038683c_80386d80.o out\eggArchive.o out\dol\text_8020fcc4_8021a0f0.o out\dol\data_802a268c_802a2b48.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 4d0e22067..307fc6450 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -5,6 +5,11 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,source/dwc/common/dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, 1,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, ,rxFrameHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, +1,source/rvl/mem/rvlMemHeap.c,,,,,,,0x801981ec,0x80198798,,,,,,,,,0x80346cf0,0x80346d18,,,0x80386838,0x8038683C,,,, +1,source/rvl/mem/rvlMemExpHeap.c,,,,,,,0x80198798,0x80199430,,,,,,,,,,,,,,,,,, +1,source/rvl/mem/rvlMemFrmHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, +1,source/rvl/mem/rvlMemUnitHeap.c,,,,,,,0x801998A4,0x80199b58,,,,,,,,,,,,,,,,,, +1,source/rvl/mem/rvlMemAllocator.c,,,,,,,0x80199b58,0x80199bf0,,,,,,,,,,,,,,,0x80388860,0x80388870,, 1,source/rvl/mem/rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, 1,source/rvl/mtx/rvlMtx.c,,,,,,,0x80199d04,0x8019a9c4,,,,,,,,,,,0x80385a08,0x80385a10,,,0x80388870,0x80388890,, 1,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, diff --git a/source/egg/core/eggArchive.cpp b/source/egg/core/eggArchive.cpp index 5681d20ae..62bc9f2df 100644 --- a/source/egg/core/eggArchive.cpp +++ b/source/egg/core/eggArchive.cpp @@ -10,6 +10,7 @@ namespace EGG { bool Archive::sIsArchiveListInitialized; +// PAL: 0x803832d8 nw4r::ut::List Archive::sArchiveList; Archive::~Archive() { removeList(this); } diff --git a/source/egg/core/eggArchive.hpp b/source/egg/core/eggArchive.hpp index 71450ebae..8f9c99d31 100644 --- a/source/egg/core/eggArchive.hpp +++ b/source/egg/core/eggArchive.hpp @@ -5,15 +5,15 @@ #pragma once +#include +#include + #include #include #include #include -#include #include -extern "C" void memset(void*, int, u32); - namespace EGG { struct LowArchive : public rvlArchive { diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp index 4e218d8ab..328bd931a 100644 --- a/source/egg/core/eggHeap.cpp +++ b/source/egg/core/eggHeap.cpp @@ -39,7 +39,7 @@ void Heap::initialize() { OSInitMutex(&sRootMutex); sIsHeapListInitialized = true; } -Heap::Heap(rvlHeap* pHeap) : Disposer(), mHeapHandle(pHeap) { +Heap::Heap(MEMiHeapHead* pHeap) : Disposer(), mHeapHandle(pHeap) { mParentBlock = nullptr; mParentHeap = nullptr; mName = "NoName"; @@ -163,7 +163,7 @@ Heap* Heap::findParentHeap() { Heap* Heap::findContainHeap(const void* memBlock) { Heap* containingHeap = nullptr; - rvlHeap* memContainHeap = MEMFindContainHeap(memBlock); + MEMiHeapHead* memContainHeap = MEMFindContainHeap(memBlock); if (memContainHeap) { containingHeap = nullptr; OSLockMutex(&sRootMutex); @@ -183,7 +183,7 @@ Heap* Heap::findContainHeap(const void* memBlock) { void Heap::free(void* memBlock, Heap* heap) { // inside likely inline getContainHeap if (heap == nullptr) { - rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + MEMiHeapHead* containHeap = MEMFindContainHeap(memBlock); // r30 // If our memory block is not inside a head, there is not much we can do. if (!containHeap) return; @@ -328,7 +328,7 @@ void* operator new[](size_t size, EGG::Heap* heap, int align) { } void operator delete(void* memBlock) { - rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + MEMiHeapHead* containHeap = MEMFindContainHeap(memBlock); // r30 // If our memory block is not inside a head, there is not much we can do. if (!containHeap) return; @@ -351,7 +351,7 @@ void operator delete(void* memBlock) { heap->free(memBlock); } void operator delete[](void* memBlock) { - rvlHeap* containHeap = MEMFindContainHeap(memBlock); // r30 + MEMiHeapHead* containHeap = MEMFindContainHeap(memBlock); // r30 // If our memory block is not inside a head, there is not much we can do. if (!containHeap) return; diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 2ac6e269d..9baa30ed6 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -164,7 +164,7 @@ class Heap : public Disposer { //! //! @return this //! - Heap(rvlHeap* heapHandle); + Heap(MEMiHeapHead* heapHandle); //! @brief Allocate a block of memory in a heap. //! @@ -257,7 +257,7 @@ class Heap : public Disposer { nw4r::ut::List_Remove(&mChildren, disposer); } - inline rvlHeap* getHeapHandle() { return mHeapHandle; } + inline MEMiHeapHead* getHeapHandle() { return mHeapHandle; } static inline Heap* getCurrentHeap() { return sCurrentHeap; } @@ -265,7 +265,7 @@ class Heap : public Disposer { #ifdef RII_CLIENT return 0; #else - return mHeapHandle->arena_end; + return (int) mHeapHandle->arena_end; #endif } }; @@ -290,4 +290,4 @@ void operator delete(void* p); // __dla(void *) void operator delete[](void*); -#undef HEAP_PRIVATE \ No newline at end of file +#undef HEAP_PRIVATE diff --git a/source/egg/core/eggUnitHeap.cpp b/source/egg/core/eggUnitHeap.cpp index 8b4e0fce1..be9b3c56f 100644 --- a/source/egg/core/eggUnitHeap.cpp +++ b/source/egg/core/eggUnitHeap.cpp @@ -81,9 +81,9 @@ void UnitHeap::destroy() { } void* UnitHeap::alloc(u32 size, s32 /* align */) { - if (size > mHeapHandle->_40) + // TODO use proper accessors + if (size > *((u32*)((u8*)mHeapHandle + 0x40))) return nullptr; - return MEMAllocFromUnitHeap(mHeapHandle); } void UnitHeap::free(void* block) { MEMFreeToUnitHeap(mHeapHandle, block); } diff --git a/source/platform/math.h b/source/platform/math.h new file mode 100644 index 000000000..44c986362 --- /dev/null +++ b/source/platform/math.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +f64 sin(f64); +inline f32 sinf(f32 x) { return (float)sin(x); }; + +f64 cos(f64); +inline f32 cosf(f32 x) { return (float)cos(x); }; + +f64 tan(f64); +inline f32 tanf(f32 x) { return (float)tan(x); }; + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/source/platform/stdlib.h b/source/platform/stdlib.h new file mode 100644 index 000000000..c2a82910e --- /dev/null +++ b/source/platform/stdlib.h @@ -0,0 +1,5 @@ +#pragma once + +// Compiler intrinsic functions. +#define abs(x) __abs(x) +#define labs(x) __labs(x) diff --git a/source/platform/string.h b/source/platform/string.h new file mode 100644 index 000000000..58de271e2 --- /dev/null +++ b/source/platform/string.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void memset(void*, s32, u32); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/source/rvl/mem/expHeap.h b/source/rvl/mem/expHeap.h index d3be1aba8..92601a82c 100644 --- a/source/rvl/mem/expHeap.h +++ b/source/rvl/mem/expHeap.h @@ -1,40 +1,93 @@ #pragma once -#include +#include "memAllocator.h" #ifdef __cplusplus extern "C" { #endif // __cplusplus -MEMHeapHandle MEMCreateExpHeapEx(void* begin, u32 size, u16 flags); +// MEMiExpHeapMBlockHead is the heap block header. +typedef struct MEMiExpHeapMBlockHead MEMiExpHeapMBlockHead; +struct MEMiExpHeapMBlockHead { + u16 signature; // 0x00 + union { + u16 val; // 0x02 + struct { + u16 dir : 1; // 0x02 + u16 alignment : 7; + u16 groupID : 8; // 0x03 + } fields; + } attribute; + u32 blockSize; // 0x04 + MEMiExpHeapMBlockHead* prev; // 0x08 + MEMiExpHeapMBlockHead* next; // 0x0C +}; + +// MEMiExpMBlockList is the header of the free/used blocks double-linked list. +typedef struct MEMiExpMBlockList MEMiExpMBlockList; +struct MEMiExpMBlockList { + MEMiExpHeapMBlockHead* head; + MEMiExpHeapMBlockHead* tail; +}; + +// MEMiExpHeapHead is the extended heap header. +typedef struct MEMiExpHeapHead MEMiExpHeapHead; +struct MEMiExpHeapHead { + MEMiExpMBlockList freeList; + MEMiExpMBlockList usedList; + u16 groupID; + union { + u16 val; + struct { + u16 _unk0_15 : 15; + u16 allocMode : 1; + } fields; + } feature; +}; + +typedef struct MEM_Extent { + void* start; + void* end; +} MEM_Extent; -void* MEMDestroyExpHeap(MEMHeapHandle heap); +typedef void (*MEMExpHeapVisitor)(void* block, MEMHeapHandle heap, + u32 user_data); +// PAL: 0x8019899c +void* MEM_ExpAllocFromHead(MEMiHeapHead*, u32, int); +// PAL: 0x80198a78 +void* MEM_ExpAllocFromTail(MEMiHeapHead*, u32, int); +// PAL: 0x80198b40 +u32 MEM_ExpRecycleRegion(MEMiExpHeapHead*, const MEM_Extent*); +// PAL: 0x80198ca8 +MEMHeapHandle MEMCreateExpHeapEx(void* begin, u32 size, u16 flags); +// PAL: 0x80198d58 +void* MEMDestroyExpHeap(MEMHeapHandle); +// PAL: 0x80198d88 void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, u32 size, int align); - -void MEMFreeToExpHeap(MEMHeapHandle heap, void* block); - +// PAL: 0x80198e38 u32 MEMResizeForMBlockExpHeap(MEMHeapHandle heap, void* block, u32 size); - +// PAL: 0x80199038 +void MEMFreeToExpHeap(MEMHeapHandle heap, void* block); +// PAL: 0x80199104 u32 MEMGetTotalFreeSizeForExpHeap(MEMHeapHandle heap); -u32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, int align); - -typedef void (*MEMExpHeapVisitor)(void* block, MEMHeapHandle heap, - u32 user_data); - +// PAL: 0x80199180 +u32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, s32 align); +// PAL: 0x80199258 +u16 MEMSetGroupIDForExpHeap(MEMHeapHandle heap, u16 group_id); +// PAL: 0x801992a8 void MEMVisitAllocatedForExpHeap(MEMHeapHandle heap, MEMExpHeapVisitor visitor, u32 user_data); - -u32 MEMGetSizeForMBlockExpHeap(const void* block); - -u16 MEMSetGroupIDForExpHeap(MEMHeapHandle heap, u16 group_id); -u16 MEMGetGroupIDForMBlockExpHeap(void* block); - +// PAL: 0x80199344 +u32 MEMGetSizeForMBlockExpHeap(const void*); +// PAL: 0x8019934c +u16 MEMGetGroupIDForMBlockExpHeap(const void*); +// PAL: 0x80199358 u32 MEMAdjustExpHeap(MEMHeapHandle heap); - -void MEMInitAllocatorForExpHeap(MEMAllocator* allocator, MEMHeapHandle heap, int align); +void MEMInitAllocatorForExpHeap(MEMAllocator* allocator, MEMHeapHandle heap, + int align); #ifdef __cplusplus } -#endif // __cplusplus \ No newline at end of file +#endif // __cplusplus diff --git a/source/rvl/mem/frmHeap.h b/source/rvl/mem/frmHeap.h new file mode 100644 index 000000000..6b3849820 --- /dev/null +++ b/source/rvl/mem/frmHeap.h @@ -0,0 +1,41 @@ +#pragma once + +#include "memAllocator.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +typedef struct MEMiFrmHeapState MEMiFrmHeapState; +struct MEMiFrmHeapState { + u32 tag; + void* head; + void* tail; + MEMiFrmHeapState* state; +}; + +typedef struct MEMiFrmHeapHead MEMiFrmHeapHead; +struct MEMiFrmHeapHead { + void* head; + void* tail; + MEMiFrmHeapState* state; +}; + +// PAL: 0x80199444 +MEMHeapHandle MEMCreateFrmHeapEx(void* start, u32 size, u16 flags); +// PAL: 0x801994b4 +void* MEMDestroyFrmHeap(MEMHeapHandle); +// PAL: 0x801994e4 +void* MEMAllocFromFrmHeapEx(MEMHeapHandle heap, u32 size, int align); +// PAL: 0x80199604 +void MEMFreeToFrmHeap(MEMHeapHandle, int); +// PAL: 0x801996a4 +u32 MEMGetAllocatableSizeForFrmHeapEx(MEMHeapHandle, int); +// PAL: 0x8019971c +int MEMRecordStateForFrmHeap(MEMHeapHandle, u32); +// PAL: 0x801997f0 +int MEMFreeByStateToFrmHeap(MEMHeapHandle, u32); + +#ifdef __cplusplus +} +#endif // __cplusplus diff --git a/source/rvl/mem/heap.h b/source/rvl/mem/heap.h new file mode 100644 index 000000000..afed2eb30 --- /dev/null +++ b/source/rvl/mem/heap.h @@ -0,0 +1,18 @@ +#pragma once + +#include "memAllocator.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// PAL: 0x8019832c +void MEMiInitHeapHead(MEMiHeapHead*, u32, void*, void*, u16); +// PAL: 0x801984ec +void MEMiFinalizeHeap(MEMiHeapHead*); +// PAL: 0x80198658 +MEMHeapHandle MEMFindContainHeap(const void*); + +#ifdef __cplusplus +} +#endif // __cplusplus diff --git a/source/rvl/mem/heapi.h b/source/rvl/mem/heapi.h new file mode 100644 index 000000000..0e8627fd7 --- /dev/null +++ b/source/rvl/mem/heapi.h @@ -0,0 +1,48 @@ +#pragma once + +#include "memAllocator.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// ceil() for 2^x aligned +#define fastceil_u32(value, align) (((value) + ((align)-1)) & ~((align)-1)) +#define fastceil_ptr(ptr, align) ((void*)fastceil_u32((u32)(ptr), (align))) +// floor() for 2^x aligned +#define fastfloor_u32(value, align) ((value) & ~((align)-1)) +#define fastfloor_ptr(ptr, align) ((void*)fastfloor_u32((u32)(ptr), (align))) + +static inline void MEM_BlockZero(MEMiHeapHead* heap, void* address, u32 size) { + if ((u16)heap->_unk38.parts.flags & 1) { + (void)memset(address, 0, size); + } +} + +// These computations have to be inlined for an exact match. +static inline u32 cast_ptr_u32(const void* ptr) { return (u32)(ptr); } +static inline void* ptr_sub(void* ptr, u32 val) { + return (void*)(cast_ptr_u32(ptr) - val); +} +static inline void* ptr_add(void* ptr, u32 val) { + return (void*)(cast_ptr_u32(ptr) + val); +} + +// ptr_dist returns the distance between two pointers. +// end address must be larger than start. +static inline u32 ptr_dist(const void* start, const void* end) { + return cast_ptr_u32(end) - cast_ptr_u32(start); +} + +// ptr_diff returns the difference between two pointers. +static inline int ptr_diff(const void* p1, const void* p2) { + const u8* b1 = (const u8*)p1; + const u8* b2 = (const u8*)p2; + return b1 - b2; +} + +#ifdef __cplusplus +} +#endif // __cplusplus diff --git a/source/rvl/mem/memAllocator.h b/source/rvl/mem/memAllocator.h index 3e3da60a4..8250db8d8 100644 --- a/source/rvl/mem/memAllocator.h +++ b/source/rvl/mem/memAllocator.h @@ -2,31 +2,77 @@ #include +#include "rvl/os/osThread.h" +#include "rvlMemList.h" + #ifdef __cplusplus extern "C" { #endif #ifndef RII_CLIENT + +typedef struct MEMAllocator MEMAllocator; + +typedef void* (*MEMFuncAllocatorAlloc)(MEMAllocator*, u32); +typedef void (*MEMFuncAllocatorFree)(MEMAllocator*, void*); + +typedef struct MEMAllocatorFunc MEMAllocatorFunc; +struct MEMAllocatorFunc { + MEMFuncAllocatorAlloc alloc; // 0x00 + MEMFuncAllocatorFree free; // 0x04 +}; + struct MEMAllocator { - char _[0x10]; + const MEMAllocatorFunc* func; // 0x00 + void* heap; // 0x04 + u32 _unk08; // 0x08 + u32 _unk0C; // 0x0C +}; + +typedef struct MEMiHeapHead MEMiHeapHead; + +struct MEMiHeapHead { + u32 _unk00; // 0x00 + MEMLink link; // 0x04 + MEMList list; // 0x0c + void* arena_start; // 0x18 + void* arena_end; // 0x1C + OSMutex mutex; // 0x20 + // No idea what this is. + union { + u32 val; // 0x38 + struct { + u32 _0_24 : 24; // 0x38 + u32 flags : 8; // 0x38 + } parts; + } _unk38; }; -typedef struct MEMiHeapHead { - char _[0x1c]; - u32 arena_end; // 0x20 - char _2[0x40 - 0x20]; - u32 _40; -} MEMiHeapHead; typedef MEMiHeapHead* MEMHeapHandle; +// PAL: 0x80199b58 +void* MEM_AllocForExpHeap_(MEMAllocator* alloc, u32 size); +// PAL: 0x80199b68 +void MEM_FreeForExpHeap_(MEMAllocator* alloc, void* data); +// PAL: 0x80199b70 +void* MEM_AllocForUnitHeap(MEMAllocator* alloc, u32 size); +// PAL: 0x80199b90 +void MEM_FreeForUnitHeap(MEMAllocator* alloc, void* data); +// PAL: 0x80199b98 +void* MEMAllocFromAllocator(MEMAllocator* alloc, u32 size); +// PAL: 0x80199ba8 +void MEMFreeToAllocator(MEMAllocator* alloc, void* data); +// PAL: 0x80199bb8 +void MEMInitAllocatorForExpHeap(MEMAllocator*, MEMHeapHandle, int); +// PAL: 0x80199bd4 +void MEMInitAllocatorForUnitHeap(MEMAllocator*, MEMHeapHandle); + MEMiHeapHead* MEMFindContainHeap(const void*); #else #include #endif - -#define rvlHeap MEMiHeapHead #ifdef __cplusplus } // extern "C" -#endif \ No newline at end of file +#endif diff --git a/source/rvl/mem/rvlMemAllocator.c b/source/rvl/mem/rvlMemAllocator.c new file mode 100644 index 000000000..58849b69a --- /dev/null +++ b/source/rvl/mem/rvlMemAllocator.c @@ -0,0 +1,64 @@ +#include "memAllocator.h" + +#include "expHeap.h" +#include "heapi.h" +#include "unitHeap.h" + +void* MEM_AllocForExpHeap_(MEMAllocator* alloc, u32 size) { + MEMHeapHandle heap = (MEMHeapHandle)alloc->heap; + int alignment = (int)alloc->_unk08; + return MEMAllocFromExpHeapEx(heap, size, alignment); +} + +void MEM_FreeForExpHeap_(MEMAllocator* alloc, void* data) { + MEMHeapHandle heap = (MEMHeapHandle)alloc->heap; + MEMFreeToExpHeap(heap, data); +} + +void* MEM_AllocForUnitHeap(MEMAllocator* alloc, u32 size) { + MEMHeapHandle heap = (MEMHeapHandle)alloc->heap; + MEMiUntHeapHead* unitHeap = + (MEMiUntHeapHead*)(ptr_add(heap, sizeof(MEMiHeapHead))); + u32 unit_size = unitHeap->unit_size; + if (size > unit_size) + return NULL; + return MEMAllocFromUnitHeap(heap); +} + +void MEM_FreeForUnitHeap(MEMAllocator* alloc, void* data) { + MEMHeapHandle const heap = (MEMHeapHandle)alloc->heap; + MEMFreeToUnitHeap(heap, data); +} + +void* MEMAllocFromAllocator(MEMAllocator* alloc, u32 size) { + return (*alloc->func->alloc)(alloc, size); +} + +void MEMFreeToAllocator(MEMAllocator* alloc, void* data) { + (*alloc->func->free)(alloc, data); +} + +void MEMInitAllocatorForExpHeap(MEMAllocator* alloc, MEMHeapHandle heap, + int align) { + // PAL: 0x80388860 + static const MEMAllocatorFunc funcs = { + MEM_AllocForExpHeap_, + MEM_FreeForExpHeap_, + }; + alloc->func = &funcs; + alloc->heap = heap; + alloc->_unk08 = (u32)align; + alloc->_unk0C = 0; +} + +void MEMInitAllocatorForUnitHeap(MEMAllocator* alloc, MEMHeapHandle heap) { + // PAL: 0x80388868 + static const MEMAllocatorFunc funcs = { + MEM_AllocForUnitHeap, + MEM_FreeForUnitHeap, + }; + alloc->func = &funcs; + alloc->heap = heap; + alloc->_unk08 = 0; + alloc->_unk0C = 0; +} diff --git a/source/rvl/mem/rvlMemExpHeap.c b/source/rvl/mem/rvlMemExpHeap.c new file mode 100644 index 000000000..d5ddbbf37 --- /dev/null +++ b/source/rvl/mem/rvlMemExpHeap.c @@ -0,0 +1,592 @@ +#include "expHeap.h" + +#include "heap.h" +#include "heapi.h" + +#include +#include + +// MEM_ExpBlockInsert inserts a block into the heap linked list. +static inline MEMiExpHeapMBlockHead* +MEM_ExpBlockInsert(MEMiExpMBlockList* list, MEMiExpHeapMBlockHead* target, + MEMiExpHeapMBlockHead* prev) { + MEMiExpHeapMBlockHead* next; + target->prev = prev; + if (prev) { + next = prev->next; + prev->next = target; + } else { + next = list->head; + list->head = target; + } + target->next = next; + if (next) + next->prev = target; + else + list->tail = target; + return target; +} + +// MEM_ExpBlockRemove removes a block from the heap linked list. +static inline MEMiExpHeapMBlockHead* +MEM_ExpBlockRemove(MEMiExpMBlockList* list, MEMiExpHeapMBlockHead* block) { + MEMiExpHeapMBlockHead* prev = block->prev; + MEMiExpHeapMBlockHead* next = block->next; + if (prev) + prev->next = next; + else + list->head = next; + if (next) + next->prev = prev; + else + list->tail = prev; + return prev; +} + +// MEM_BlockInit prepares a block for use. +static inline MEMiExpHeapMBlockHead* MEM_BlockInit(MEM_Extent* region, + u16 signature) { + MEMiExpHeapMBlockHead* block = (MEMiExpHeapMBlockHead*)region->start; + block->signature = signature; + block->attribute.val = 0; + u32 start = (u32)block + 0x10; + block->blockSize = (u32)region->end - start; + block->prev = NULL; + block->next = NULL; + return block; +} + +// MEM_BlockAppend inserts a block at the end of a heap list. +static inline void MEM_BlockAppend(MEMiExpMBlockList* list, + MEMiExpHeapMBlockHead* block) { + (void)MEM_ExpBlockInsert(list, block, list->tail); +} + +// MEM_ExpBlockGetExtent retrieves the memory extent of a given block. +static inline void MEM_ExpBlockGetExtent(MEM_Extent* region, + MEMiExpHeapMBlockHead* block) { + region->start = ptr_sub(block, block->attribute.fields.alignment); + region->end = (void*)((u32)block + 0x10 + block->blockSize); +} + +static void* MEM_ExpAllocNewBlock(MEMiExpHeapHead* expHeap, + MEMiExpHeapMBlockHead* expBlockFree, + void* block, u32 size, u16 dir) { + MEM_Extent extFreeL; + MEM_Extent extFreeR; + MEMiExpHeapMBlockHead* expBlockFreePrev; + // Get the extents before and after the free block. + MEM_ExpBlockGetExtent(&extFreeL, expBlockFree); + extFreeR.end = extFreeL.end; + extFreeR.start = ptr_add(block, size); + extFreeL.end = ptr_sub(block, 0x10); + // Remove block from free list. + expBlockFreePrev = MEM_ExpBlockRemove(&expHeap->freeList, expBlockFree); + // Shrink or deallocate left extent. + if ((u32)extFreeL.end - (u32)extFreeL.start < 0x14) { + extFreeL.end = extFreeL.start; + } else { + expBlockFreePrev = MEM_ExpBlockInsert( + &expHeap->freeList, MEM_BlockInit(&extFreeL, 'FR'), expBlockFreePrev); + } + // Shrink or deallocate right extent. + if ((u32)extFreeR.end - (u32)extFreeR.start < 0x14) { + extFreeR.start = extFreeR.end; + } else { + MEM_ExpBlockInsert(&expHeap->freeList, MEM_BlockInit(&extFreeR, 'FR'), + expBlockFreePrev); + } + // Optionally clear block. + MEM_BlockZero((MEMiHeapHead*)((u32)expHeap - sizeof(MEMiHeapHead)), + extFreeL.end, ptr_dist(extFreeL.end, extFreeR.start)); + // Mark block as used. + MEMiExpHeapMBlockHead* expBlockUsed; + MEM_Extent extent; + extent.start = ptr_sub(block, 0x10); + extent.end = extFreeR.start; + expBlockUsed = MEM_BlockInit(&extent, 'UD'); + expBlockUsed->attribute.fields.dir = dir; + expBlockUsed->attribute.fields.alignment = + (u16)((u32)expBlockUsed - (u32)extFreeL.end); + expBlockUsed->attribute.fields.groupID = (u8)expHeap->groupID; + MEM_BlockAppend(&expHeap->usedList, expBlockUsed); + return block; +} + +void* MEM_ExpAllocFromHead(MEMiHeapHead* heap, u32 size, int alignment) { + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)((u32)heap + sizeof(MEMiHeapHead)); + const u32 special = expHeap->feature.fields.allocMode == 0; + // Search heap for the next free block. + MEMiExpHeapMBlockHead* curExpBlock = NULL; // current block we're looking at + MEMiExpHeapMBlockHead* resExpBlock = NULL; // matching / result block + u32 resSize = 0xffffffff; + void* resMemBlock = NULL; + // Begin search. + for (curExpBlock = expHeap->freeList.head; curExpBlock; + curExpBlock = curExpBlock->next) { + void* block = (void*)((u32)curExpBlock + 0x10); + void* alignedBlock = fastceil_ptr(block, alignment); + u32 offset = (u32)alignedBlock - (u32)block; + if (curExpBlock->blockSize >= size + offset && + resSize > curExpBlock->blockSize) { + resExpBlock = curExpBlock; + resSize = curExpBlock->blockSize; + resMemBlock = alignedBlock; + if (special || resSize == size) + break; + } + } + // Out of memory if no block found. + if (!resExpBlock) + return NULL; + // Mark block as used. + return MEM_ExpAllocNewBlock(expHeap, resExpBlock, resMemBlock, size, 0); +} + +void* MEM_ExpAllocFromTail(MEMiHeapHead* heap, u32 size, int alignment) { + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)((u32)heap + sizeof(MEMiHeapHead)); + const u32 special = expHeap->feature.fields.allocMode == 0; + // Search heap backwards for the next free block. + MEMiExpHeapMBlockHead* curExpBlock = NULL; // current block we're looking at + MEMiExpHeapMBlockHead* resExpBlock = NULL; // matching / result block + u32 resSize = 0xffffffff; + void* resMemBlock = NULL; + // Begin search. + for (curExpBlock = expHeap->freeList.tail; curExpBlock; + curExpBlock = curExpBlock->prev) { + void* block = (void*)((u32)curExpBlock + 0x10); + void* blockEnd = ptr_add(block, curExpBlock->blockSize); + void* alignedBlock = fastfloor_ptr(ptr_sub(blockEnd, size), alignment); + if ((int)alignedBlock - (int)block >= 0 && + resSize > curExpBlock->blockSize) { + resExpBlock = curExpBlock; + resSize = curExpBlock->blockSize; + resMemBlock = alignedBlock; + if (special || resSize == size) + break; + } + } + // Out of memory if no block found. + if (!resExpBlock) + return NULL; + // Mark block as used. + return MEM_ExpAllocNewBlock(expHeap, resExpBlock, resMemBlock, size, 1); +} + +u32 MEM_ExpRecycleRegion(MEMiExpHeapHead* expHeap, const MEM_Extent* ext) { + MEMiExpHeapMBlockHead* blockFree = NULL; + MEM_Extent extFree = *ext; + MEMiExpHeapMBlockHead* block; + for (block = expHeap->freeList.head; block; block = block->next) { + if (block < ext->start) { + blockFree = block; + continue; + } + if (block == ext->end) { + extFree.end = (void*)((u32)block + 0x10 + block->blockSize); + MEM_ExpBlockRemove(&expHeap->freeList, block); + } + break; + } + if (blockFree && (void*)((u32)blockFree + sizeof(MEMiExpHeapMBlockHead) + + blockFree->blockSize) == ext->start) { + extFree.start = blockFree; + blockFree = MEM_ExpBlockRemove(&expHeap->freeList, blockFree); + } + if (ptr_dist(extFree.start, extFree.end) < sizeof(MEMiExpHeapMBlockHead)) + return false; + MEM_ExpBlockInsert(&expHeap->freeList, MEM_BlockInit(&extFree, 'FR'), + blockFree); + return true; +} + +static inline MEMiHeapHead* MEM_ExpHeapInit(void* begin, void* end, u16 flags) { + MEMiHeapHead* heap = (MEMiHeapHead*)begin; + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + MEMiInitHeapHead(heap, 'EXPH', ptr_add(expHeap, sizeof(MEMiExpHeapHead)), end, + flags); + expHeap->groupID = 0; + expHeap->feature.val = 0; + expHeap->feature.fields.allocMode = 0; + { + MEMiExpHeapMBlockHead* block; + MEM_Extent region; + region.start = heap->arena_start; + region.end = heap->arena_end; + block = MEM_BlockInit(®ion, 'FR'); + expHeap->freeList.head = block; + expHeap->freeList.tail = block; + expHeap->usedList.head = NULL; + expHeap->usedList.tail = NULL; + return heap; + } +} + +MEMHeapHandle MEMCreateExpHeapEx(void* addr, u32 size, u16 flags) { + void* end = fastfloor_ptr(ptr_add(addr, size), 4); + addr = fastceil_ptr(addr, 4); + if ((u32)addr > (u32)end || ptr_dist(addr, end) < 0x64) { + return NULL; + } + return MEM_ExpHeapInit(addr, end, flags); +} + +void* MEMDestroyExpHeap(MEMHeapHandle heap) { + MEMiFinalizeHeap(heap); + return (void*)heap; +} + +void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, u32 size, int dir) { + void* memory = NULL; + if (size == 0) + size = 1; + size = fastceil_u32(size, 4); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + if (dir >= 0) + memory = MEM_ExpAllocFromHead(heap, size, dir); + else + memory = MEM_ExpAllocFromTail(heap, size, -dir); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + return memory; +} + +// Referenced by assembly. +void _restgpr_26(); +void _savegpr_26(); + +asm u32 MEMResizeForMBlockExpHeap(MEMHeapHandle heap, void* addr, u32 size) { + nofralloc; + stwu r1, -48(r1); + mflr r0; + stw r0, 52(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + addi r5, r5, 0x3; + lwz r0, -12(r4); + rlwinm r29, r5, 0x0, 0x0, 0x1d; + addi r30, r4, -0x10; + cmplw r29, r0; + mr r27, r3; + mr r28, r4; + addi r31, r3, 0x3c; + bne loc1; +loc0: + mr r3, r29; + b loc35; +loc1: + lwz r0, 56(r3); + rlwinm.r0, r0, 0x0, 0x1d, 0x1d; + beq loc3; +loc2: + addi r3, r3, 0x20; + bl OSLockMutex; +loc3: + lwz r26, 4(r30); + cmplw r29, r26; + ble loc30; +loc4: + add r3, r30, r26; + lwz r7, 0(r31); + addi r0, r3, 0x10; + b loc7; +loc5: + cmplw r7, r0; + beq loc8; +loc6: + lwz r7, 12(r7); +loc7: + cmpwi r7, 0x0; + bne loc5; +loc8: + cmpwi r7, 0x0; + beq loc10; +loc9: + lwz r4, 4(r7); + add r3, r26, r4; + addi r0, r3, 0x10; + cmplw r29, r0; + ble loc13; +loc10: + lwz r0, 56(r27); + rlwinm.r0, r0, 0x0, 0x1d, 0x1d; + beq loc12; +loc11: + addi r3, r27, 0x20; + bl OSUnlockMutex; +loc12: + li r3, 0x0; + b loc35; +loc13: + lwz r5, 8(r7); + add r3, r7, r4; + lhz r0, 2(r7); + addi r6, r3, 0x10; + cmpwi r5, 0x0; + lwz r4, 12(r7); + rlwinm r0, r0, 0x18, 0x19, 0x1f; + subf r3, r0, r7; + beq loc15; +loc14: + stw r4, 12(r5); + b loc16; +loc15: + stw r4, 0(r31); +loc16: + cmpwi r4, 0x0; + beq loc18; +loc17: + stw r5, 8(r4); + b loc19; +loc18: + stw r5, 4(r31); +loc19: + add r7, r29, r28; + subf r0, r7, r6; + cmplwi r0, 0x10; + bge loc21; +loc20: + mr r7, r6; +loc21: + subf r0, r7, r6; + subf r4, r28, r7; + cmplwi r0, 0x10; + stw r4, 4(r30); + blt loc28; +loc22: + li r4, 0x4652; + addi r0, r7, 0x10; + sth r4, 0(r7); + li r4, 0x0; + subf r0, r0, r6; + cmpwi r5, 0x0; + sth r4, 2(r7); + stw r0, 4(r7); + stw r4, 12(r7); + stw r5, 8(r7); + beq loc24; +loc23: + lwz r4, 12(r5); + stw r7, 12(r5); + b loc25; +loc24: + lwz r4, 0(r31); + stw r7, 0(r31); +loc25: + cmpwi r4, 0x0; + stw r4, 12(r7); + beq loc27; +loc26: + stw r7, 8(r4); + b loc28; +loc27: + stw r7, 4(r31); +loc28: + lwz r0, 56(r27); + subf r5, r3, r7; + clrlwi.r0, r0, 0x1f; + beq loc32; +loc29: + li r4, 0x0; + bl memset; + b loc32; +loc30: + add r0, r29, r28; + mr r3, r31; + stw r0, 8(r1); + addi r4, r1, 0x8; + lwz r0, 4(r30); + add r5, r30, r0; + addi r0, r5, 0x10; + stw r0, 12(r1); + stw r29, 4(r30); + bl MEM_ExpRecycleRegion; + cmpwi r3, 0x0; + bne loc32; +loc31: + stw r26, 4(r30); +loc32: + lwz r0, 56(r27); + rlwinm.r0, r0, 0x0, 0x1d, 0x1d; + beq loc34; +loc33: + addi r3, r27, 0x20; + bl OSUnlockMutex; +loc34: + lwz r3, 4(r30); +loc35: + addi r11, r1, 0x30; + bl _restgpr_26; + lwz r0, 52(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; +} + +void MEMFreeToExpHeap(MEMHeapHandle heap, void* addr) { + if (addr == NULL) + return; + + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + MEMiExpHeapMBlockHead* block = + (MEMiExpHeapMBlockHead*)ptr_sub(addr, sizeof(MEMiExpHeapMBlockHead)); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + MEM_Extent region; + MEM_ExpBlockGetExtent(®ion, block); + (void)MEM_ExpBlockRemove(&expHeap->usedList, block); + (void)MEM_ExpRecycleRegion(expHeap, ®ion); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); +} + +// MEMGetTotalFreeSizeForExpHeap returns the total amount of free space on the +// heap. +u32 MEMGetTotalFreeSizeForExpHeap(MEMHeapHandle heap) { + u32 ret = 0; + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + // Loop over all blocks in the heap and count along the way. + MEMiExpHeapMBlockHead* block; + for (block = expHeap->freeList.head; block; block = block->next) + ret += block->blockSize; + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + return ret; +} + +u32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, s32 alignment) { + alignment = abs(alignment); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + { + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + MEMiExpHeapMBlockHead* block; + u32 maxSize = 0; + u32 offsetMin = 0xFFFFFFFF; + + for (block = expHeap->freeList.head; block; block = block->next) { + void* blockAddress1 = ptr_add(block, sizeof(MEMiExpHeapMBlockHead)); + void* baseAddress = fastceil_ptr(blockAddress1, alignment); + void* endAddress1 = ptr_add(blockAddress1, block->blockSize); + if ((u32)(baseAddress) < (u32)(endAddress1)) { + void* blockAddress2 = ptr_add(block, sizeof(MEMiExpHeapMBlockHead)); + void* endAddress2 = ptr_add(blockAddress2, block->blockSize); + const u32 blockSize = ptr_dist(baseAddress, endAddress2); + const u32 offset = ptr_dist(blockAddress2, baseAddress); + if (maxSize < blockSize || + (maxSize == blockSize && offsetMin > offset)) { + maxSize = blockSize; + offsetMin = offset; + } + } + } + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + return maxSize; + } +} + +u32 OSDisableInterrupts(void); +u32 OSEnableInterrupts(void); +u32 OSRestoreInterrupts(u32 level); + +u16 MEMSetGroupIDForExpHeap(MEMHeapHandle heap, u16 groupID) { + u32 interrupts = OSDisableInterrupts(); + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + u16 old = expHeap->groupID; + expHeap->groupID = groupID; + OSRestoreInterrupts(interrupts); + return old; +} + +void MEMVisitAllocatedForExpHeap(MEMHeapHandle heap, MEMExpHeapVisitor visitor, + u32 userParam) { + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + MEMiExpHeapMBlockHead* block = expHeap->usedList.head; + while (block) { + MEMiExpHeapMBlockHead* next = block->next; + void* blockAddr = ptr_add(block, sizeof(MEMiExpHeapMBlockHead)); + (*visitor)(blockAddr, heap, userParam); + block = next; + } + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); +} + +u32 MEMGetSizeForMBlockExpHeap(const void* addr) { + const MEMiExpHeapMBlockHead* block = + (const MEMiExpHeapMBlockHead*)((u32)addr - sizeof(MEMiExpHeapMBlockHead)); + return block->blockSize; +} + +u16 MEMGetGroupIDForMBlockExpHeap(const void* data) { + MEMiExpHeapMBlockHead* block = + (MEMiExpHeapMBlockHead*)((u32)data - sizeof(MEMiExpHeapMBlockHead)); + return block->attribute.fields.groupID; +} + +u32 MEMAdjustExpHeap(MEMHeapHandle heapHandle) { + MEMiHeapHead* heap = heapHandle; + MEMiExpHeapHead* expHeap = + (MEMiExpHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + u32 ret; + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + MEMiExpHeapMBlockHead* block = expHeap->freeList.tail; + if (block == NULL) { + ret = 0; + goto ret_; + } + + void* const start = ptr_add(block, sizeof(MEMiExpHeapMBlockHead)); + void* const end = ptr_add(start, block->blockSize); + u32 blockSize; + + if (end != heap->arena_end) { + ret = 0; + goto ret_; + } + + MEM_ExpBlockRemove(&expHeap->freeList, block); + blockSize = block->blockSize + sizeof(MEMiExpHeapMBlockHead); + heap->arena_end = ptr_sub(heap->arena_end, blockSize); + ret = ptr_dist(heap, heap->arena_end); + +ret_: + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + return ret; +} diff --git a/source/rvl/mem/rvlMemFrmHeap.c b/source/rvl/mem/rvlMemFrmHeap.c new file mode 100644 index 000000000..9ca120b87 --- /dev/null +++ b/source/rvl/mem/rvlMemFrmHeap.c @@ -0,0 +1,193 @@ +#include "frmHeap.h" + +#include "heap.h" +#include "heapi.h" + +#include +#include + +static inline MEMiHeapHead* MEM_FrameHeapInit(void* start, void* end, + u16 flags) { + MEMiHeapHead* heap = (MEMiHeapHead*)start; + MEMiFrmHeapHead* frmHeap = + (MEMiFrmHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + + MEMiInitHeapHead(heap, 'FRMH', ptr_add(frmHeap, sizeof(MEMiFrmHeapHead)), end, + flags); + frmHeap->head = heap->arena_start; + frmHeap->tail = heap->arena_end; + frmHeap->state = NULL; + return heap; +} + +MEMHeapHandle MEMCreateFrmHeapEx(void* start, u32 size, u16 flags) { + void* end = fastfloor_ptr(ptr_add(start, size), 4); + start = fastceil_ptr(start, 4); + + if ((u32)(start) > (u32)(end) || + ptr_dist(start, end) < sizeof(MEMiHeapHead) + sizeof(MEMiFrmHeapHead)) { + return NULL; + } + + return MEM_FrameHeapInit(start, end, flags); +} + +void* MEMDestroyFrmHeap(MEMHeapHandle heap) { + MEMiFinalizeHeap(heap); + return (void*)heap; +} + +static inline void* MEM_FrmAllocFromHead(MEMiFrmHeapHead* frmHeap, u32 size, + int align) { + void* newBlock = fastceil_ptr(frmHeap->head, align); + void* endAddress = ptr_add(newBlock, size); + + if ((u32)(endAddress) > (u32)(frmHeap->tail)) + return NULL; + + MEMiHeapHead* heap = (MEMiHeapHead*)ptr_sub(frmHeap, sizeof(MEMiHeapHead)); + MEM_BlockZero(heap, frmHeap->head, ptr_dist(frmHeap->head, endAddress)); + frmHeap->head = endAddress; + return newBlock; +} + +static void* MEM_FrmAllocFromTail(MEMiFrmHeapHead* frmHeap, u32 size, + int align) { + void* newBlock = fastfloor_ptr(ptr_sub(frmHeap->tail, size), align); + + if ((u32)(newBlock) < (u32)(frmHeap->head)) + return NULL; + + MEMiHeapHead* heap = (MEMiHeapHead*)ptr_sub(frmHeap, sizeof(MEMiHeapHead)); + MEM_BlockZero(heap, newBlock, ptr_dist(newBlock, frmHeap->tail)); + frmHeap->tail = newBlock; + return newBlock; +} + +void* MEMAllocFromFrmHeapEx(MEMHeapHandle heap, u32 size, int alignment) { + void* memory = NULL; + MEMiFrmHeapHead* frmHeap = + (MEMiFrmHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + + if (size == 0) + size = 1; + size = fastceil_u32(size, 4); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + if (alignment >= 0) + memory = MEM_FrmAllocFromHead(frmHeap, size, alignment); + else + memory = MEM_FrmAllocFromTail(frmHeap, size, -alignment); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + return memory; +} + +void MEMFreeToFrmHeap(MEMHeapHandle heap, int mode) { + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + if (mode & 1) { + MEMiFrmHeapHead* frmHeap = + (MEMiFrmHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + frmHeap->head = heap->arena_start; + frmHeap->state = NULL; + } + if (mode & 2) { + MEMiFrmHeapHead* frmHeap = + (MEMiFrmHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + MEMiFrmHeapState* state; + for (state = frmHeap->state; state; state = state->state) + state->tail = heap->arena_end; + frmHeap->tail = heap->arena_end; + } + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); +} + +u32 OSDisableInterrupts(void); +u32 OSEnableInterrupts(void); +u32 OSRestoreInterrupts(u32 level); + +u32 MEMGetAllocatableSizeForFrmHeapEx(MEMHeapHandle heap, int align) { + align = abs(align); + const MEMiFrmHeapHead* frmHeap = + (MEMiFrmHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + int interrupts = OSDisableInterrupts(); + const void* addr = fastceil_ptr(frmHeap->head, align); + u32 retVal; + if ((u32)addr > (u32)(frmHeap->tail)) + retVal = 0; + else + retVal = ptr_dist(addr, frmHeap->tail); + OSRestoreInterrupts(interrupts); + return retVal; +} + +int MEMRecordStateForFrmHeap(MEMHeapHandle heap, u32 tag) { + int ret; + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + MEMiFrmHeapHead* frmHeap = + (MEMiFrmHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + void* oldHeadAllocator = frmHeap->head; + MEMiFrmHeapState* state = (MEMiFrmHeapState*)MEM_FrmAllocFromHead( + frmHeap, sizeof(MEMiFrmHeapState), 4); + + if (!state) { + ret = false; + goto ret_; + } + + state->tag = tag; + state->head = oldHeadAllocator; + state->tail = frmHeap->tail; + state->state = frmHeap->state; + frmHeap->state = state; + ret = true; + +ret_: + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + return ret; +} + +int MEMFreeByStateToFrmHeap(MEMHeapHandle heap, u32 tag) { + int ret; + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + MEMiFrmHeapHead* frmHeap = + (MEMiFrmHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + MEMiFrmHeapState* state = frmHeap->state; + + if (tag != 0) + for (; state; state = state->state) + if (state->tag == tag) + break; + + if (!state) { + ret = false; + goto ret_; + } + + void* oldHeadAllocator = frmHeap->head; + void* oldTailAllocator = frmHeap->tail; + frmHeap->head = state->head; + frmHeap->tail = state->tail; + frmHeap->state = state->state; + ret = true; + +ret_: + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + return ret; +} diff --git a/source/rvl/mem/rvlMemHeap.c b/source/rvl/mem/rvlMemHeap.c new file mode 100644 index 000000000..32f3f110d --- /dev/null +++ b/source/rvl/mem/rvlMemHeap.c @@ -0,0 +1,70 @@ +#include "heap.h" + +#include +#include + +#include "rvl/os/osThread.h" + +// PAL: 0x80346cf0 @bss +static MEMList MEM_RootList; +// PAL: 0x80386838 @sbss +static u32 MEM_RootListInitialized = false; +// PAL: 0x80346d00 @bss +static OSMutex MEM_GlobalLock; + +// MEM_FindHeap searches the heap linked list for the heap that holds the given +// arena. +// +// PAL: 0x801981ec +static MEMiHeapHead* MEM_FindHeap(MEMList* list, const void* arena) { + MEMiHeapHead* next = NULL; + while ((next = (MEMiHeapHead*)MEMGetNextListObject(list, next)) != NULL) { + if ((u32)(next->arena_start) <= (u32)arena && + (u32)arena < (u32)(next->arena_end)) { + MEMiHeapHead* recursed = MEM_FindHeap(&next->list, arena); + if (recursed) + return recursed; + return next; + } + } + return NULL; +} + +static inline MEMList* MEM_FindHeapList(const MEMiHeapHead* search) { + MEMList* list = &MEM_RootList; + MEMiHeapHead* heap = MEM_FindHeap(&MEM_RootList, search); + if (heap) { + list = &heap->list; + } + return list; +} + +void MEMiInitHeapHead(MEMiHeapHead* heap, u32 arg2, void* start, void* end, + u16 flags) { + heap->_unk00 = arg2; + heap->arena_start = start; + heap->arena_end = end; + heap->_unk38.val = 0; + heap->_unk38.parts.flags = (u8)flags; + MEMInitList(&heap->list, 0x4); + if (!MEM_RootListInitialized) { + MEMInitList(&MEM_RootList, 0x4); + OSInitMutex(&MEM_GlobalLock); + MEM_RootListInitialized = true; + } + OSInitMutex(&heap->mutex); // TODO remove + OSLockMutex(&MEM_GlobalLock); + MEMAppendListObject(MEM_FindHeapList(heap), heap); + OSUnlockMutex(&MEM_GlobalLock); +} + +void MEMiFinalizeHeap(MEMiHeapHead* heap) { + OSLockMutex(&MEM_GlobalLock); + MEMRemoveListObject(MEM_FindHeapList(heap), heap); + OSUnlockMutex(&MEM_GlobalLock); + heap->_unk00 = 0; +} + +MEMHeapHandle MEMFindContainHeap(const void* arena) { + return MEM_FindHeap(&MEM_RootList, arena); +} diff --git a/source/rvl/mem/rvlMemList.c b/source/rvl/mem/rvlMemList.c index e61180b2f..75009e31d 100644 --- a/source/rvl/mem/rvlMemList.c +++ b/source/rvl/mem/rvlMemList.c @@ -4,8 +4,7 @@ extern "C" { #endif -rvlMemIntrusiveList* MEMInitList(rvlMemIntrusiveList* pList, - u16 intrusion_offset) { +MEMList* MEMInitList(MEMList* pList, u16 intrusion_offset) { pList->intrusion_offset = intrusion_offset; pList->head = nullptr; pList->tail = nullptr; @@ -14,10 +13,9 @@ rvlMemIntrusiveList* MEMInitList(rvlMemIntrusiveList* pList, return pList; } -rvlMemIntrusiveList* MEMAppendListObject(rvlMemIntrusiveList* pList, - void* pObj) { +MEMList* MEMAppendListObject(MEMList* pList, void* pObj) { if (pList->head == nullptr) { - rvlMemBiNode* node = rvlMemListGetNode(pList, pObj); + MEMLink* node = rvlMemListGetNode(pList, pObj); node->succ = nullptr; node->pred = nullptr; @@ -29,7 +27,7 @@ rvlMemIntrusiveList* MEMAppendListObject(rvlMemIntrusiveList* pList, return pList; } - rvlMemBiNode* node = rvlMemListGetNode(pList, pObj); + MEMLink* node = rvlMemListGetNode(pList, pObj); node->pred = pList->tail; node->succ = nullptr; @@ -41,9 +39,8 @@ rvlMemIntrusiveList* MEMAppendListObject(rvlMemIntrusiveList* pList, return pList; } -rvlMemIntrusiveList* MEMRemoveListObject(rvlMemIntrusiveList* pList, - void* pObj) { - rvlMemBiNode* node = rvlMemListGetNode(pList, pObj); +MEMList* MEMRemoveListObject(MEMList* pList, void* pObj) { + MEMLink* node = rvlMemListGetNode(pList, pObj); if (node->pred == nullptr) pList->head = node->succ; @@ -63,7 +60,7 @@ rvlMemIntrusiveList* MEMRemoveListObject(rvlMemIntrusiveList* pList, return pList; } -void* MEMGetNextListObject(rvlMemIntrusiveList* pList, void* pObj) { +void* MEMGetNextListObject(MEMList* pList, void* pObj) { if (pObj == nullptr) return pList->head; diff --git a/source/rvl/mem/rvlMemList.h b/source/rvl/mem/rvlMemList.h index f1719000a..5a6a15c3f 100644 --- a/source/rvl/mem/rvlMemList.h +++ b/source/rvl/mem/rvlMemList.h @@ -10,27 +10,29 @@ extern "C" { typedef struct { void* pred; void* succ; -} rvlMemBiNode; +} MEMLink; // Unlike modern "std::list"-like structures, list nodes are directly inherited // by children, which saves a level of indirection. +// Size: 0x0C typedef struct { - void* head; - void* tail; - u16 count; - u16 intrusion_offset; -} rvlMemIntrusiveList; + void* head; // 0x00 + void* tail; // 0x04 + u16 count; // 0x08 + u16 intrusion_offset; // 0x0A +} MEMList; -rvlMemIntrusiveList* MEMInitList(rvlMemIntrusiveList* pList, - u16 intrusion_offset); -rvlMemIntrusiveList* MEMAppendListObject(rvlMemIntrusiveList* pList, - void* pObj); -rvlMemIntrusiveList* MEMRemoveListObject(rvlMemIntrusiveList* pList, - void* pObj); -void* MEMGetNextListObject(rvlMemIntrusiveList* pList, void* pObj); +// PAL: 0x80199bf0 +MEMList* MEMInitList(MEMList*, u16); +// PAL: 0x80199c08 +MEMList* MEMAppendListObject(MEMList*, void*); +// PAL: 0x80199c78 +MEMList* MEMRemoveListObject(MEMList*, void*); +// PAL: 0x80199ce4 +void* MEMGetNextListObject(MEMList*, void*); #define rvlMemListGetNode(list, obj) \ - ((rvlMemBiNode*)(((char*)obj) + (list)->intrusion_offset)) + ((MEMLink*)(((char*)obj) + (list)->intrusion_offset)) #ifdef __cplusplus } diff --git a/source/rvl/mem/rvlMemUnitHeap.c b/source/rvl/mem/rvlMemUnitHeap.c new file mode 100644 index 000000000..f9ee8fdb5 --- /dev/null +++ b/source/rvl/mem/rvlMemUnitHeap.c @@ -0,0 +1,89 @@ +#include "unitHeap.h" + +#include "heap.h" +#include "heapi.h" + +MEMHeapHandle MEMCreateUnitHeapEx(void* start, u32 heapSize, u32 memBlockSize, + int align, u16 flags) { + MEMiHeapHead* heap = (MEMiHeapHead*)fastceil_ptr(start, 4); + void* heapEnd = fastfloor_ptr(ptr_add(start, heapSize), 4); + + if (ptr_diff(heap, heapEnd) > 0) + return NULL; + + memBlockSize = fastceil_u32(memBlockSize, align); + + MEMiUntHeapHead* unitHeap = + (MEMiUntHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + void* heapStart = + fastceil_ptr(ptr_add(unitHeap, sizeof(MEMiUntHeapHead)), align); + + if (ptr_diff(heapStart, heapEnd) > 0) + return NULL; + + u32 count = ptr_dist(heapStart, heapEnd) / memBlockSize; + if (count == 0) + return NULL; + + heapEnd = ptr_add(heapStart, count * memBlockSize); + + MEMiInitHeapHead(heap, 'UNTH', heapStart, heapEnd, flags); + + unitHeap->free_list = (MEMiUntHeapMBlockHead*)heapStart; + unitHeap->unit_size = memBlockSize; + + MEMiUntHeapMBlockHead* block = unitHeap->free_list; + for (int i = 0; i < count - 1; ++i, block = block->succ) + block->succ = (MEMiUntHeapMBlockHead*)ptr_add(block, memBlockSize); + block->succ = NULL; + + return heap; +} + +void* MEMDestroyUnitHeap(MEMHeapHandle heap) { + MEMiFinalizeHeap(heap); + return (void*)heap; +} + +void* MEMAllocFromUnitHeap(MEMHeapHandle heap) { + MEMiUntHeapHead* unitHeap = + (MEMiUntHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + // Pop the current block and set linked list head to next block. + MEMiUntHeapMBlockHead* block = unitHeap->free_list; + if (block) + unitHeap->free_list = block->succ; + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); + + if (block) + MEM_BlockZero(heap, block, unitHeap->unit_size); + return block; +} + +void MEMFreeToUnitHeap(MEMHeapHandle heap, void* addr) { + if (addr == NULL) + return; + + MEMiUntHeapHead* unitHeap = + (MEMiUntHeapHead*)ptr_add(heap, sizeof(MEMiHeapHead)); + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSLockMutex(&heap->mutex); + + MEMiUntHeapMBlockHead* block = (MEMiUntHeapMBlockHead*)addr; + block->succ = unitHeap->free_list; + unitHeap->free_list = block; + + if (((u16)heap->_unk38.parts.flags) & 0x04) + OSUnlockMutex(&heap->mutex); +} + +u32 MEMCalcHeapSizeForUnitHeap(u32 unit_size, u32 unit_count, int align) { + return sizeof(MEMiHeapHead) + sizeof(MEMiUntHeapHead) + (align - 4) + + unit_count * fastceil_u32(unit_size, align); +} diff --git a/source/rvl/mem/unitHeap.h b/source/rvl/mem/unitHeap.h index 392662d68..7799d92ac 100644 --- a/source/rvl/mem/unitHeap.h +++ b/source/rvl/mem/unitHeap.h @@ -6,28 +6,30 @@ extern "C" { #endif // __cplusplus +typedef struct MEMiUntHeapMBlockHead MEMiUntHeapMBlockHead; struct MEMiUntHeapMBlockHead { MEMiUntHeapMBlockHead* succ; }; -struct MEMiUntHeapHead { +typedef struct MEMiUntHeapHead { MEMiUntHeapMBlockHead* free_list; u32 unit_size; -}; +} MEMiUntHeapHead; +// PAL: 0x801998a4 MEMHeapHandle MEMCreateUnitHeapEx(void* begin, u32 size, u32 unit_size, int align, u16 flags); - +// PAL: 0x80199a00 void* MEMDestroyUnitHeap(MEMHeapHandle heap); - +// PAL: 0x80199a30 void* MEMAllocFromUnitHeap(MEMHeapHandle heap); - +// PAL: 0x80199ac4 void MEMFreeToUnitHeap(MEMHeapHandle heap, void* block); +// PAL: 0x80199b34 +u32 MEMCalcHeapSizeForUnitHeap(u32 unit_size, u32 unit_count, int align); u32 MEMCountFreeBlockForUnitHeap(MEMHeapHandle heap); -u32 MEMCalcHeapSizeForUnitHeap(u32 unit_size, u32 unit_count, int align); - void MEMInitAllocatorForUnitHeap(MEMAllocator* allocator, MEMHeapHandle heap); #ifdef __cplusplus diff --git a/source/rvl/mtx/rvlMtx.c b/source/rvl/mtx/rvlMtx.c index 5e62e9511..5d9703f1e 100644 --- a/source/rvl/mtx/rvlMtx.c +++ b/source/rvl/mtx/rvlMtx.c @@ -1,5 +1,7 @@ #include "mtx.h" +#include + static f32 Unit01[] = {0.0f, 1.0f}; void PSMTXIdentity(register Mtx m) { @@ -310,11 +312,6 @@ asm u32 PSMTXInvXpose(const register Mtx src, register Mtx invX) { blr; } -f64 sin(f64); -inline f32 sinf(f32 x) { return (float)sin(x); }; -f64 cos(f64); -inline f32 cosf(f32 x) { return (float)cos(x); }; - void PSMTXRotRad(Mtx m, char axis, f32 rad) { f32 sinA, cosA; sinA = sinf(rad); @@ -618,9 +615,6 @@ void C_MTXLightFrustum(Mtx m, float arg1, float arg2, float arg3, float arg4, m[2][3] = 0.0f; } -f64 tan(f64); -inline f32 tanf(f32 x) { return (float)tan(x); }; - void C_MTXLightPerspective(Mtx m, f32 arg1, f32 arg2, float arg3, float arg4, float arg5, float arg6) { f32 angle = arg1 * 0.5f * 0.01745329252f; diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index cdddc2bb7..5d1865ceb 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -1,26 +1,28 @@ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif #ifndef RII_CLIENT u32 OSGetTick(); -u32 __OSBusClock : 0x800000F8; +u32 __OSBusClock : (0x800000F8); #define OS_TIMER_CLOCK (__OSBusClock >> 2) -struct OSThread { +typedef struct OSThread { char _00[0x304]; char* stack_high; // 304 char* stack_low; // 308 char _30c[0x318 - 0x30c]; -}; +} OSThread; typedef void* OSMessage; -struct OSMessageQueue { +typedef struct OSMessageQueue { char _[0x20]; -}; +} OSMessageQueue; typedef struct { char _[0x18]; @@ -33,9 +35,9 @@ void OSUnlockMutex(OSMutex*); int OSCreateThread(OSThread* thread, void* (*callable)(void*), void* user_data, void* stack, u32 stack_size, s32 prio, u16 flag); -void OSCancelThread(OSThread* thread); -void OSDetachThread(OSThread* thread); -int OSIsThreadTerminated(OSThread* thread); +void OSCancelThread(OSThread*); +void OSDetachThread(OSThread*); +int OSIsThreadTerminated(OSThread*); OSThread* OSGetCurrentThread(); typedef void (*OSSwitchFunction)(OSThread*, OSThread*); @@ -46,4 +48,4 @@ void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); #endif #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/sources.py b/sources.py index 200b2222e..7a4970433 100644 --- a/sources.py +++ b/sources.py @@ -4,6 +4,11 @@ NW4R_OPTS = '-ipa file -inline auto -rostr -O4,p' compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemHeap.c", "out/rvlMemHeap.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemExpHeap.c", "out/rvlMemExpHeap.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemFrmHeap.c", "out/rvlMemFrmHeap.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemUnitHeap.c", "out/rvlMemUnitHeap.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemAllocator.c", "out/rvlMemAllocator.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mtx/rvlMtx.c", "out/rvlMtx.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mtx/rvlMtx2.c", "out/rvlMtx2.o", '4199_60831', RVL_OPTS) From bd3288f57750280cc435d3bf3cbe582206611146 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 3 Jul 2021 17:32:29 -0600 Subject: [PATCH 083/477] :zap: Speed up linking slightly by giving linker more RAM --- build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.py b/build.py index 51b62900b..95620b91d 100644 --- a/build.py +++ b/build.py @@ -119,7 +119,7 @@ def assemble(dst, src): def link(dst, objs, lcf, partial=False): - cmd = [MWLD] + objs + ["-o", dst, "-lcf", lcf, "-fp", "hard"] + cmd = [MWLD] + objs + ["-o", dst, "-lcf", lcf, "-fp", "hard", "-linkmode", "moreram"] if partial: cmd.append("-r") subprocess.run(cmd, check=True, text=True) From 930d036b07dd7e4f9e7d9f70b3fae46a85268e7f Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 3 Jul 2021 17:52:07 -0600 Subject: [PATCH 084/477] :bug: Automatically unpack rel in gen_asm.py --- mkwutil/gen_asm.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index e23d307b9..3eb12c8ed 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -8,6 +8,8 @@ from .ppc_dis import disasm_iter, disassemble_callback from .dol import DolBinary, Segment +from .rel import Rel, dump_staticr + read_u32 = lambda f: struct.unpack(">L", f.read(4))[0] read_u16 = lambda f: struct.unpack(">H", f.read(2))[0] @@ -395,6 +397,10 @@ def unpack_everything(asm_dir, pack_dir, binary_dir): args = parser.parse_args() args.asm_dir.mkdir(exist_ok=True) + # Feel free to move this around, dump staticr.rel segments + with open("artifacts/orig/pal/StaticR.rel", 'rb') as f: + dump_staticr(Rel(f), "artifacts/orig/pal/rel") + # Write the macros file. with open(args.asm_dir / "macros.inc", "w") as file: file.write("# PowerPC Register Constants\n") From 1f41ff01bfd4a5cb0b173c401471d55028a34f1f Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 3 Jul 2021 17:58:48 -0600 Subject: [PATCH 085/477] :construction: Compile most of MEM in C++ mode --- .../{rvlMemAllocator.c => rvlMemAllocator.cpp} | 0 source/rvl/mem/rvlMemExpHeap.c | 16 ++++++++++++---- .../mem/{rvlMemFrmHeap.c => rvlMemFrmHeap.cpp} | 9 +++++++++ source/rvl/mem/{rvlMemHeap.c => rvlMemHeap.cpp} | 0 source/rvl/mem/{rvlMemList.c => rvlMemList.cpp} | 0 .../mem/{rvlMemUnitHeap.c => rvlMemUnitHeap.cpp} | 0 sources.py | 10 +++++----- 7 files changed, 26 insertions(+), 9 deletions(-) rename source/rvl/mem/{rvlMemAllocator.c => rvlMemAllocator.cpp} (100%) rename source/rvl/mem/{rvlMemFrmHeap.c => rvlMemFrmHeap.cpp} (98%) rename source/rvl/mem/{rvlMemHeap.c => rvlMemHeap.cpp} (100%) rename source/rvl/mem/{rvlMemList.c => rvlMemList.cpp} (100%) rename source/rvl/mem/{rvlMemUnitHeap.c => rvlMemUnitHeap.cpp} (100%) diff --git a/source/rvl/mem/rvlMemAllocator.c b/source/rvl/mem/rvlMemAllocator.cpp similarity index 100% rename from source/rvl/mem/rvlMemAllocator.c rename to source/rvl/mem/rvlMemAllocator.cpp diff --git a/source/rvl/mem/rvlMemExpHeap.c b/source/rvl/mem/rvlMemExpHeap.c index d5ddbbf37..372aea9a9 100644 --- a/source/rvl/mem/rvlMemExpHeap.c +++ b/source/rvl/mem/rvlMemExpHeap.c @@ -259,9 +259,21 @@ void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, u32 size, int dir) { return memory; } +#ifdef __cplusplus +extern "C" { +#endif + +// TODO: Move to OS +u32 OSDisableInterrupts(void); +u32 OSEnableInterrupts(void); +u32 OSRestoreInterrupts(u32 level); + // Referenced by assembly. void _restgpr_26(); void _savegpr_26(); +#ifdef __cplusplus +} +#endif asm u32 MEMResizeForMBlockExpHeap(MEMHeapHandle heap, void* addr, u32 size) { nofralloc; @@ -510,10 +522,6 @@ u32 MEMGetAllocatableSizeForExpHeapEx(MEMHeapHandle heap, s32 alignment) { } } -u32 OSDisableInterrupts(void); -u32 OSEnableInterrupts(void); -u32 OSRestoreInterrupts(u32 level); - u16 MEMSetGroupIDForExpHeap(MEMHeapHandle heap, u16 groupID) { u32 interrupts = OSDisableInterrupts(); MEMiExpHeapHead* expHeap = diff --git a/source/rvl/mem/rvlMemFrmHeap.c b/source/rvl/mem/rvlMemFrmHeap.cpp similarity index 98% rename from source/rvl/mem/rvlMemFrmHeap.c rename to source/rvl/mem/rvlMemFrmHeap.cpp index 9ca120b87..a6cf96824 100644 --- a/source/rvl/mem/rvlMemFrmHeap.c +++ b/source/rvl/mem/rvlMemFrmHeap.cpp @@ -108,10 +108,19 @@ void MEMFreeToFrmHeap(MEMHeapHandle heap, int mode) { OSUnlockMutex(&heap->mutex); } +#ifdef __cplusplus +extern "C" { +#endif + +// TODO: Move to OS u32 OSDisableInterrupts(void); u32 OSEnableInterrupts(void); u32 OSRestoreInterrupts(u32 level); +#ifdef __cplusplus +} +#endif + u32 MEMGetAllocatableSizeForFrmHeapEx(MEMHeapHandle heap, int align) { align = abs(align); const MEMiFrmHeapHead* frmHeap = diff --git a/source/rvl/mem/rvlMemHeap.c b/source/rvl/mem/rvlMemHeap.cpp similarity index 100% rename from source/rvl/mem/rvlMemHeap.c rename to source/rvl/mem/rvlMemHeap.cpp diff --git a/source/rvl/mem/rvlMemList.c b/source/rvl/mem/rvlMemList.cpp similarity index 100% rename from source/rvl/mem/rvlMemList.c rename to source/rvl/mem/rvlMemList.cpp diff --git a/source/rvl/mem/rvlMemUnitHeap.c b/source/rvl/mem/rvlMemUnitHeap.cpp similarity index 100% rename from source/rvl/mem/rvlMemUnitHeap.c rename to source/rvl/mem/rvlMemUnitHeap.cpp diff --git a/sources.py b/sources.py index 7a4970433..92fbc3944 100644 --- a/sources.py +++ b/sources.py @@ -4,12 +4,12 @@ NW4R_OPTS = '-ipa file -inline auto -rostr -O4,p' compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemHeap.c", "out/rvlMemHeap.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemHeap.cpp", "out/rvlMemHeap.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemExpHeap.c", "out/rvlMemExpHeap.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemFrmHeap.c", "out/rvlMemFrmHeap.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemUnitHeap.c", "out/rvlMemUnitHeap.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemAllocator.c", "out/rvlMemAllocator.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemList.c", "out/rvlMemList.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemFrmHeap.cpp", "out/rvlMemFrmHeap.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemUnitHeap.cpp", "out/rvlMemUnitHeap.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemAllocator.cpp", "out/rvlMemAllocator.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/mem/rvlMemList.cpp", "out/rvlMemList.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mtx/rvlMtx.c", "out/rvlMtx.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mtx/rvlMtx2.c", "out/rvlMtx2.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mtx/rvlVec.c", "out/rvlVec.o", '4199_60831', RVL_OPTS) From 10d5e40fab496ad6b27ad9f7f1006c51e729f8fe Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 4 Jul 2021 02:03:26 +0200 Subject: [PATCH 086/477] TRK memcpy, memset (#22) --- pack/dol.base.lcf | 2 - pack/dol_objects.txt | 4 +- pack/dol_slices.csv | 1 + source/platform/string.h | 5 +- source/rvl/trk/rvlTrkMem.c | 96 ++++++++++++++++++++++++++++++++++++++ sources.py | 1 + 6 files changed, 105 insertions(+), 4 deletions(-) create mode 100644 source/rvl/trk/rvlTrkMem.c diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index dc6b005bf..7752b7b0d 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -86,8 +86,6 @@ DVDReadPrio=0x8015E834; DVDOpen=0x8015E2BC; DVDClose=0x8015E568; -memset=0x80006038; - GXGetGPStatus=0x8016CEC4; GXInit=0x8016B850; GXGetYScaleFactor=0x8016F6CC; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 30c1e9419..afc4f3bcb 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -1,3 +1,5 @@ +out\dol\init_80004000_80005f34.o +out\rvlTrkMem.o out\dol\text_800072c0_80085110.o out\dol\rodata_80244ec0_80248010.o out\dol\data_80258580_80274148.o @@ -74,7 +76,7 @@ out\eggVector.o out\dol\rodata_80257824_802582e0.o out\dol\sdata2_80389104_80389108.o out\eggVideo.o -out\dol\init_80004000_80006460.o +out\dol\init_80006068_80006460.o out\dol\extab_80006460_80006a20.o out\dol\extabindex_80006a20_800072c0.o out\dol\text_80244074_80244de0.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 307fc6450..054551743 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,4 +1,5 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End +1,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, 1,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, 1,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085938,,,,,,,,,,,,,,,0x80387ea8,0x80387eb4,, 1,source/nw4r/ut/utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, diff --git a/source/platform/string.h b/source/platform/string.h index 58de271e2..91464bd4a 100644 --- a/source/platform/string.h +++ b/source/platform/string.h @@ -6,7 +6,10 @@ extern "C" { #endif -void memset(void*, s32, u32); +// PAL: 0x80005f34 +void* memcpy(void*, const void*, u32); +// PAL: 0x80006038 +void* memset(void*, s32, u32); #ifdef __cplusplus } // extern "C" diff --git a/source/rvl/trk/rvlTrkMem.c b/source/rvl/trk/rvlTrkMem.c new file mode 100644 index 000000000..bd1ed21e2 --- /dev/null +++ b/source/rvl/trk/rvlTrkMem.c @@ -0,0 +1,96 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/src/RevoSDK/TRK/__mem.c +// Decompiled by GibHaltmannKill. + +#include + +// PAL: 0x80005f34 +__declspec(section ".init") void* memcpy(void* dest, const void* src, + u32 count) { + const char* csrc = (const char*)src; + char* cdest = (char*)dest; + + if (src >= dest) { + csrc--; + cdest--; + count++; + + while (--count) { + *++cdest = *++csrc; + } + } else { + csrc += count; + cdest += count; + count++; + + while (--count) { + *--cdest = *--csrc; + } + } + return dest; +} + +// PAL: 0x80005f84 +__declspec(section ".init") static void __fill_mem(void* dest, int val, + u32 count) { + char* cdest = (char*)dest; + int cval = (unsigned char)val; + int* idest = (int*)dest; + int r0; + cdest--; + if (count >= 0x20) { + r0 = ~(int)(cdest)&3; + + if (r0) { + count -= r0; + + do { + *++cdest = cval; + } while (--r0); + } + + if (cval) { + cval = (cval << 0x18) | (cval << 0x10) | (cval << 0x8) | cval; + } + + r0 = count >> 5; + idest = (int*)(cdest - 3); + + if (r0) { + do { + idest[1] = cval; // 4 + --r0; + idest[2] = cval; // 8 + idest[3] = cval; // c + idest[4] = cval; // 10 + idest[5] = cval; // 14 + idest[6] = cval; // 18 + idest[7] = cval; // 1c + *(idest += 8) = cval; // 20 + } while (r0); + } + + r0 = (count >> 2) & 7; + + if (r0) { + do { + *++idest = cval; + } while (--r0); + } + + cdest = (char*)idest + 3; + count &= 3; + } + + if (count) { + do { + *++cdest = cval; + } while (--count); + } +} + +// PAL: 0x80006038 +__declspec(section ".init") void* memset(void* dest, s32 val, u32 count) { + __fill_mem(dest, val, count); + return dest; +} diff --git a/sources.py b/sources.py index 92fbc3944..a764fe144 100644 --- a/sources.py +++ b/sources.py @@ -3,6 +3,7 @@ REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' NW4R_OPTS = '-ipa file -inline auto -rostr -O4,p' +compile_source("source/rvl/trk/rvlTrkMem.c", "out/rvlTrkMem.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemHeap.cpp", "out/rvlMemHeap.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mem/rvlMemExpHeap.c", "out/rvlMemExpHeap.o", '4199_60831', RVL_OPTS) From 4e050bc5f9f6dab2b0a670dcec42822a28392a05 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 3 Jul 2021 20:12:39 -0600 Subject: [PATCH 087/477] Start dwc_init.c, dwc_memfunc.c --- source/dwc/common/dwc_init.c | 124 +++++++++++++++++++++++++++++ source/dwc/common/dwc_init.h | 25 ++++++ source/dwc/common/dwc_memfunc.c | 131 +++++++++++++++++++++++++++++++ source/dwc/common/dwc_memfunc.h | 40 ++++++++++ source/dwc/common/dwci_memfunc.h | 35 +++++++++ 5 files changed, 355 insertions(+) create mode 100644 source/dwc/common/dwc_init.c create mode 100644 source/dwc/common/dwc_init.h create mode 100644 source/dwc/common/dwc_memfunc.c create mode 100644 source/dwc/common/dwc_memfunc.h create mode 100644 source/dwc/common/dwci_memfunc.h diff --git a/source/dwc/common/dwc_init.c b/source/dwc/common/dwc_init.c new file mode 100644 index 000000000..dc8a2a1a1 --- /dev/null +++ b/source/dwc/common/dwc_init.c @@ -0,0 +1,124 @@ +#include +// dev +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +static u32 dwci_gamecode; + +#ifdef DEBUG +#define DWC_VERSION_STRING "<< RVL_SDK - DWC \tdebug build: Jul 30 2008 11:48:37 (0x4199_60831) >>" +#else +// MKW version +#define DWC_VERSION_STRING "<< RVL_SDK - DWC \trelease build: Mar 1 2008 21:50:41 (0x4199_60831) >>" +#endif + +// no release equivalent yet +#define DWC_SDK_STRING "2_0_3_SDK3" + +#define DWC_PRINT_VERSION() { \ + DWC_Printf(8, "================================\n"); \ + DWC_Printf(8, "- %s\n", DWC_SDK_STRING); \ + DWC_Printf(8, "--------------------------------\n"); } + +#define DWC_PRINT_DEVSVR() { \ + DWC_Printf(8, "\n"); \ + DWC_Printf(8, "********************************\n"); \ + DWC_Printf(8, "*YOU ARE USING THE DEBUG SERVER*\n"); \ + DWC_Printf(8, "********************************\n"); \ + DWC_Printf(8, "*\n"); \ + DWC_Printf(8, "* Please note that you need to\n"); \ + DWC_Printf(8, "* switch it to the RELEASE\n"); \ + DWC_Printf(8, "* server with your FINALROM!\n"); \ + DWC_Printf(8, "*\n"); \ + DWC_Printf(8, "*******************************\n"); \ + DWC_Printf(8, "\n"); } + +#define DWC_PRINT_DEMO() { \ + DWC_Printf(8, "\n"); \ + DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ + DWC_Printf(8, "x IS THIS DEMO? x\n"); \ + DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ + DWC_Printf(8, "x\n"); \ + DWC_Printf(8, "x You are using the gamecode for\n"); \ + DWC_Printf(8, "x demos. It is not allowed\n"); \ + DWC_Printf(8, "x using this gamecode except\n"); \ + DWC_Printf(8, "x in demos and testing.\n"); \ + DWC_Printf(8, "x\n"); \ + DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ + DWC_Printf(8, "\n"); } + + +#define DWC_SDKDEV_SERVER_HOSTNAME "sdkdev.gamespy.com" +#define DWC_GAMESTATS_SERVER_HOSTNAME "gamestats.gs.nintendowifi.net" + +int DWC_Init(DWC_AuthServer authSvr, const char* gameName, u32 gameCode, DWCAllocEx allocator, DWCFreeEx freeer) +{ + + + OSRegisterVersion(DWC_VERSION_STRING); + + DWCiAssert(authSvr != DWC_SVR_PROHIBITED, "dwc_init.c", 135, "invalid auth server\n"); + DWCiAssert(gameCode, "dwc_init.c", 138, "invalid gamecode\n"); + + DWCi_SetMemFunc(allocator, freeer); + +#ifdef DEBUG + DWC_SetReportLevel(0xffffffff); + DWCi_MemWatch_init(); + + DWC_PRINT_VERSION(); + + if (authSvr != 1) + DWC_PRINT_DEVSVR(); + + if (gameCode == 'NTRJ') + DWC_PRINT_DEMO(); + + if (gameName && !strcmp(gameName, "dwctest")) + DWC_PRINT_DEMO(); +#endif + + DWCi_Auth_InitInterface(authSvr); + + dwci_gamecode = gameName; + + gsiMemoryCallbacksSet(&DWCi_GsMalloc, &DWCi_GsFree, &DWCi_GsRealloc, &DWCi_GsMemalign); + + DWCiAssert(strlen(gameName) <= 255, "dwc_init.c", 230, "gamename is too long\n"); + + strcpy(&gcd_gamename, gameName); + + if (authSvr == 0) + strcpy(&StatsServerHostname, DWC_SDKDEV_SERVER_HOSTNAME); + else + strcpy(&StatsServerHostname, DWC_GAMESTATS_SERVER_HOSTNAME); + + DWCi_Np_SetInitFlag(1); + DWCi_Np_GetConsoleId(); + + return 0; +} +void DWC_Shutdown() +{ + gsiMemoryCallbacksSet(&DWCi_GsMalloc, &DWCi_GsFree, &DWCi_GsRealloc, &DWCi_GsMemalign); + gethostbyname("clear"); + DWCi_CfReset(); + DWCi_MemWatch_finish(); + DWCi_Np_SetInitFlag(0); +} + +u32 DWCi_GetGamecode() +{ + return dwci_gamecode; +} +#ifdef __cplusplus +} +#endif diff --git a/source/dwc/common/dwc_init.h b/source/dwc/common/dwc_init.h new file mode 100644 index 000000000..3c9b591aa --- /dev/null +++ b/source/dwc/common/dwc_init.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef enum { + DWC_SVR_DEV, + DWC_SVR_RELEASE, + DWC_SVR_PROHIBITED // test +} DWC_AuthServer; +//! @brief Initialize the DWC library. +//! +//! @param[in] authSvr TODO... +int DWC_Init(DWC_AuthServer authSvr, const char* gameName, u32 gameCode, DWCAllocEx allocator, DWCFreeEx freeer); +void DWC_Shutdown(); +u32 DWCi_GetGamecode(); +#ifdef __cplusplus +} +#endif diff --git a/source/dwc/common/dwc_memfunc.c b/source/dwc/common/dwc_memfunc.c new file mode 100644 index 000000000..924432f27 --- /dev/null +++ b/source/dwc/common/dwc_memfunc.c @@ -0,0 +1,131 @@ +#include "dwc_memfunc.h" +#include "dwci_memfunc.h" + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +static DWCAllocEx s_alloc; +static DWCFreeEx s_free; + + +DWCi_AllocateHeader* DWCi_GetAllocateHeader(void* block) +{ + DWCiAssert(*(u32*)((u32)block - 32) == DWCi_MEMFUNC_SIGNATURE, "dwc_memfunc.c", 56, "Failed assertion header->signature == DWCi_MEMFUNC_SIGNATURE"); + + return (DWCi_AllocateHeader*)((u32)block - sizeof(DWCi_AllocateHeader)); +} + +void* DWCi_SetAllocateHeader(DWCi_AllocateHeader* blockHeader, u32 size) +{ + blockHeader->magic = DWCi_MEMFUNC_SIGNATURE; + blockHeader->size = size; + return (void*)&blockHeader[1]; +} + +u32 DWCi_GetAllocateSize(void* block) +{ + DWCi_AllocateHeader* pHeader = DWCi_GetAllocateHeade(block); + + DWCiAssert(pHeader, "dwc_memfunc.c", 75, "Failed assertion header != NULL"); + + return pHeader->size; +} +void DWCi_SetMemFunc(DWCAllocEx allocator, DWCFreeEx freer) +{ + s_alloc = allocator; + s_free = freer; +} +void* DWC_Alloc(DWCAllocType type, u32 size) +{ + DWC_AllocEx(type, size, 32); +} +// TODO: Figure out what is in release and what isn't +void* DWC_AllocEx(DWCAllocType type, u32 size, int align) +{ + void* block; + + DWCiAssert(DWCi_Np_GetInitFlag(), "dwc_memfunc.c", 158, "DWC hasn't initialized yet."); + + DWCiAssert(s_alloc, "dwc_memfunc.c", 160, "DWC_MEMFUNC: Allocator function isn't defined.\n"); + DWCiAssert(align <= 32 && align >= -32, "dwc_memfunc.c", 161, "DWC_Alloc() dosen't support to align over 32 bytes order"); + + block = s_alloc(type, size + sizeof(DWCi_AllocateHeader), align); + +#ifdef DEBUG + DWCi_MemWatch_add(block, size + sizeof(DWCi_AllocateHeader), type); +#endif + + return block ? DWCi_SetAllocateHeader(block, size) : NULL; +} + +void DWC_Free(DWCAllocType name, void* block, u32 size) +{ + DWCiAssert(DWCi_Np_GetInitFlag(), "dwc_memfunc.c", 191, "DWC hasn't initialized yet."); + + DWCiAssert(s_free, "dwc_memfunc.c", 193, "DWC_MEMFUNC: Free function isn't defined.\n"); + + if (block) + { + block = (void*)DWCi_GetAllocateHeader(block); + +#ifdef DEBUG + DWCi_MemWatch_del(block); +#endif + + s_free(name, block, size); + } +} +void* DWC_Realloc(DWCAllocType type, void* block, u32 before, u32 after) +{ + DWC_ReallocEx(type, block, before, after, 32); +} +void* DWC_ReallocEx(DWCAllocType type, void* block, u32 before, u32 after, int align) +{ + u32 allocSize; + void* newBlock = DWC_AllocEx(type, after, align); + + if (newBlock == NULL) + return 0; + + if (block) + { + // TODO: Double check this + allocSize = DWCi_GetAllocateSize(block); + + + DWCi_Np_CpuCopy8(block, newBlock, allocSize > after ? after : allocSize); + DWC_Free(type, block, allocSize); + } + return newBlock; +} + +void* DWCi_GsMalloc(u32 size) +{ + return DWC_Alloc(DWC_ALLOCTYPE_GS, size); +} +void* DWCi_GsRealloc(void* block, u32 size) +{ + DWC_Realloc(DWC_ALLOCTYPE_GS, block, size, size); +} +void* DWCi_GsFree(void* block) +{ + DWC_Free(DWC_ALLOCTYPE_GS, block, 0); +} +void DWCi_GsMemalign(int align, u32 size) +{ + DWC_AllocEx(DWC_ALLOCTYPE_GS, size, align); +} +// later in object.. (header??) +void DWCi_Np_CpuCopy8(void* src, void* dst, u32 size) +{ + DWCi_Np_CPUCopyFast(dst, src, size); +} + +#ifdef __cplusplus +} +#endif diff --git a/source/dwc/common/dwc_memfunc.h b/source/dwc/common/dwc_memfunc.h new file mode 100644 index 000000000..81fa721bd --- /dev/null +++ b/source/dwc/common/dwc_memfunc.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef enum +{ + DWC_ALLOCTYPE_AUTH, + DWC_ALLOCTYPE_AC, + DWC_ALLOCTYPE_BM, + DWC_ALLOCTYPE_UTIL, + DWC_ALLOCTYPE_BASE, + DWC_ALLOCTYPE_LANMATCH, + DWC_ALLOCTYPE_GHTTP, + DWC_ALLOCTYPE_RANKING, + DWC_ALLOCTYPE_ENC, + DWC_ALLOCTYPE_GS, + DWC_ALLOCTYPE_ND, + DWC_ALLOCTYPE_OPTION_CF, + DWC_ALLOCTYPE_NHTTP, + DWC_ALLOCTYPE_MAIL, + DWC_ALLOCTYPE_NUM +} DWCAllocType; + +typedef void* (*DWCAllocEx)(DWCAllocType name, u32 size, int align); +typedef void (*DWCFreeEx )(DWCAllocType name, void* ptr, u32 size ); + +void* DWC_Alloc (DWCAllocType name, u32 size); +void* DWC_AllocEx (DWCAllocType name, u32 size, int align); +void DWC_Free (DWCAllocType name, void* ptr, u32 size); +void* DWC_Realloc (DWCAllocType name, void* ptr, u32 oldsize, u32 newsize); +void* DWC_ReallocEx (DWCAllocType name, void* ptr, u32 oldsize, u32 newsize, int align); + +#ifdef __cplusplus +} +#endif diff --git a/source/dwc/common/dwci_memfunc.h b/source/dwc/common/dwci_memfunc.h new file mode 100644 index 000000000..db8bb9709 --- /dev/null +++ b/source/dwc/common/dwci_memfunc.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define DWCi_MEMFUNC_SIGNATURE 'DWCM' + +typedef struct +{ + u32 magic; // 00 DWCi_MEMFUNC_SIGNATURE + u32 size; // 04 + u32 _[6]; // 08 +} DWCi_AllocateHeader; + +DWCi_AllocateHeader* DWCi_GetAllocateHeader(void* block); + +void* DWCi_SetAllocateHeader(DWCi_AllocateHeader* blockHeader, u32 size); + +u32 DWCi_GetAllocateSize(void* block); + +void DWCi_SetMemFunc(DWCAllocEx allocator, DWCFreeEx freer); + +void* DWCi_GsMalloc(u32 size); +void* DWCi_GsRealloc(void* block, u32 size); +void* DWCi_GsFree(void* block); +void DWCi_GsMemalign(int align, u32 size); + +#ifdef __cplusplus +} +#endif From 7980c56b4c04a5fb52ab2cd4e52fa5a5f90c4a5e Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 4 Jul 2021 04:53:45 +0200 Subject: [PATCH 088/477] g3d::Fog, g3d::Camera from doldecomp/ogws (#23) * add nw4r::g3d::Fog * add nw4r::g3d::Camera --- pack/dol.base.lcf | 4 + pack/dol_objects.txt | 13 ++- pack/dol_slices.csv | 2 + source/nw4r/g3d/g3d_camera.cpp | 157 ++++++++++++++++++++++++++++++++ source/nw4r/g3d/g3d_camera.hpp | 118 ++++++++++++++++++++++++ source/nw4r/g3d/g3d_fog.cpp | 76 ++++++++++++++++ source/nw4r/g3d/g3d_fog.hpp | 41 +++++++++ source/nw4r/g3d/g3d_rescommon.h | 117 ++++++++++++++++++++++++ source/nw4r/g3d/g3d_state.hpp | 19 ++++ source/nw4r/math/mathTypes.h | 20 ++++ source/nw4r/ut/utColor.hpp | 62 +++++++++++++ source/nw4r/ut/utList.hpp | 2 +- source/rvl/gx/gx.h | 22 +++++ source/rvl/gx/gxFrameBuf.h | 38 ++++++++ source/rvl/gx/gxPixel.h | 29 ++++++ source/rvl/gx/gxTransform.h | 27 ++++++ sources.py | 2 + 17 files changed, 745 insertions(+), 4 deletions(-) create mode 100644 source/nw4r/g3d/g3d_camera.cpp create mode 100644 source/nw4r/g3d/g3d_camera.hpp create mode 100644 source/nw4r/g3d/g3d_fog.cpp create mode 100644 source/nw4r/g3d/g3d_fog.hpp create mode 100644 source/nw4r/g3d/g3d_rescommon.h create mode 100644 source/nw4r/g3d/g3d_state.hpp create mode 100644 source/nw4r/ut/utColor.hpp create mode 100644 source/rvl/gx/gx.h create mode 100644 source/rvl/gx/gxFrameBuf.h create mode 100644 source/rvl/gx/gxPixel.h create mode 100644 source/rvl/gx/gxTransform.h diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index 7752b7b0d..070b8582a 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -55,6 +55,7 @@ sqrt__Q23EGG5MathfFf=0x8022F80C; frsqrt__Q23EGG5MathfFf=0x8022F85C; __dt__Q23EGG8Vector3fFv=0x80009B40; __dt__Q23EGG8Vector2fFv=0x80009B80; +GetRenderModeObj__Q34nw4r3g3d8G3DStateFv=0x80064440; CXInitUncompContextLZ=0x8015BEF0; CXReadUncompLZ=0x8015BF24; @@ -93,6 +94,9 @@ GXGetNumXfbLines=0x8016F640; GXSetDispCopySrc=0x8016F438; GXSetDispCopyDst=0x8016F4B8; GXSetDispCopyYScale=0x8016F8FC; +GXSetFog=0x801722CC; +GXInitFogAdjTable=0x801724F8; +GXSetFogRangeAdj=0x80172658; VIInit = 0x801B94A4; VIWaitForRetrace = 0x801B99EC; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index afc4f3bcb..fee5cd48c 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -1,9 +1,16 @@ out\dol\init_80004000_80005f34.o out\rvlTrkMem.o -out\dol\text_800072c0_80085110.o +out\dol\text_800072c0_8006a0c0.o +out\dol\bss_802a4080_802bd4ec.o +out\dol\sdata2_80386fa0_80387cac.o +out\g3d_camera.o +out\dol\text_8006a518_800774d0.o +out\dol\sdata2_80387cd8_80387d58.o +out\g3d_fog.o +out\dol\text_800775d0_80085110.o out\dol\rodata_80244ec0_80248010.o out\dol\data_80258580_80274148.o -out\dol\sdata2_80386fa0_80387e80.o +out\dol\sdata2_80387d5c_80387e80.o out\mathTriangular.o out\dol\text_80085578_80085600.o out\dol\sdata2_80387ea4_80387ea8.o @@ -19,7 +26,7 @@ out\dol\data_80275758_8027e708.o out\dol\sdata_80384c00_803857f0.o out\rvlArchive.o out\dol\text_80124e80_801981ec.o -out\dol\bss_802a4080_80346cf0.o +out\dol\bss_802bd4ec_80346cf0.o out\dol\sbss_803862b0_80386838.o out\rvlMemHeap.o out\rvlMemExpHeap.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 054551743..e2ee133ee 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,5 +1,7 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End 1,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, +1,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0 ,0x8006a518,,,,,,,,,0x802bd4ec,0x802bd4ec,,,,,0x80387cac,0x80387cd8,, +1,source/nw4r/g3d/g3d_fog.cpp,,,,,,,0x800774d0,0x800775d0,,,,,,,,,,,,,,,0x80387d58,0x80387d5C,, 1,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, 1,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085938,,,,,,,,,,,,,,,0x80387ea8,0x80387eb4,, 1,source/nw4r/ut/utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, diff --git a/source/nw4r/g3d/g3d_camera.cpp b/source/nw4r/g3d/g3d_camera.cpp new file mode 100644 index 000000000..0070f75fd --- /dev/null +++ b/source/nw4r/g3d/g3d_camera.cpp @@ -0,0 +1,157 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/src/nw4r/g3d/g3d_camera.cpp +// Decompiled by kiwi515. + +#include "g3d_camera.hpp" + +#include +#include + +#include "g3d_state.hpp" + +namespace nw4r { +namespace g3d { + +Camera::Camera(CameraData* pCamData) : mCamData(pCamData) {} + +void Camera::Init() { + GXRenderModeObj& rRenderMode = G3DState::GetRenderModeObj(); + Init(rRenderMode.mFbWidth, rRenderMode.mEfbHeight, rRenderMode.mFbWidth, + rRenderMode.SHORT_0x8, rRenderMode.SHORT_0xE, rRenderMode.SHORT_0x10); +} + +void Camera::Init(u16 r4, u16 r5, u16 r6, u16 r7, u16 r8, u16 r9) { + CameraData& rCamData = mCamData.ref(); + + if (mCamData.IsValid()) { + rCamData.mFlags = 0x21; + + rCamData.mPos.x = 0.0f; + rCamData.mPos.y = 0.0f; + rCamData.mPos.z = 15.0f; + + rCamData.VEC3_0x80.x = 0.0f; + rCamData.VEC3_0x80.y = 1.0f; + rCamData.VEC3_0x80.z = 0.0f; + + rCamData.VEC3_0x8C.x = 0.0f; + rCamData.VEC3_0x8C.y = 0.0f; + rCamData.VEC3_0x8C.z = 0.0f; + + rCamData.VEC3_0x98.x = 0.0f; + rCamData.VEC3_0x98.y = 0.0f; + rCamData.VEC3_0x98.z = 0.0f; + + rCamData.FLOAT_0xA4 = 0.0f; + rCamData.INT_0xA8 = 0; + rCamData.FLOAT_0xAC = 60.0f; + rCamData.FLOAT_0xB0 = (4.0f / 3.0f); + rCamData.FLOAT_0xB4 = 0.1f; + rCamData.FLOAT_0xB8 = 1000.f; + rCamData.FLOAT_0xBC = 0.0f; + rCamData.FLOAT_0xC0 = (float)r9; + rCamData.FLOAT_0xC4 = 0.0f; + rCamData.FLOAT_0xC8 = (float)r8; + rCamData.FLOAT_0xCC = 0.5f; + rCamData.FLOAT_0xD0 = 0.5f; + rCamData.FLOAT_0xD4 = 0.5f; + rCamData.FLOAT_0xD8 = 0.5f; + rCamData.FLOAT_0xDC = 0.0f; + rCamData.FLOAT_0xE0 = 0.0f; + rCamData.FLOAT_0xE4 = (float)r6; + rCamData.FLOAT_0xE8 = (float)r7; + rCamData.FLOAT_0xEC = 0.0; + rCamData.FLOAT_0xF0 = 1.0f; + rCamData.INT_0xF4 = 0; + rCamData.INT_0xF8 = 0; + rCamData.INT_0xFC = r4; + rCamData.INT_0x100 = r5; + rCamData.INT_0x104 = 0; + rCamData.INT_0x108 = 0; + } +} + +void Camera::SetPosition(f32 x, f32 y, f32 z) { + CameraData& rCamData = mCamData.ref(); + + if (mCamData.IsValid()) { + rCamData.mPos.x = x; + rCamData.mPos.y = y; + rCamData.mPos.z = z; + + rCamData.mFlags &= ~0x8; + } +} + +void Camera::SetPosition(const math::VEC3& rPos) { + CameraData& rCamData = mCamData.ref(); + + if (mCamData.IsValid()) { + rCamData.mPos = rPos; + rCamData.mFlags &= ~0x8; + } +} + +void Camera::SetPosture(const PostureInfo& rPosture) { + CameraData& rCamData = mCamData.ref(); + + if (mCamData.IsValid()) { + switch (rPosture.INT_0x0) { + case 0: + if (rCamData.mFlags & 0x1) { + bool b = (rPosture.VEC3_0x04 != rCamData.VEC3_0x80); + if (!b) { + b = (rPosture.VEC3_0x10 != rCamData.VEC3_0x8C); + if (!b) + return; + } + } + rCamData.mFlags &= ~0x7; + rCamData.mFlags |= 0x1; + rCamData.VEC3_0x80 = rPosture.VEC3_0x04; + rCamData.VEC3_0x8C = rPosture.VEC3_0x10; + rCamData.mFlags &= ~0x8; + break; + + case 1: + if (rCamData.mFlags & 0x2) { + bool b = (rPosture.VEC3_0x1C != rCamData.VEC3_0x98); + if (!b) + return; + } + rCamData.mFlags &= ~0x7; + rCamData.mFlags |= 0x2; + rCamData.VEC3_0x98 = rPosture.VEC3_0x1C; + rCamData.mFlags &= ~0x8; + break; + + case 2: + if (rCamData.mFlags & 0x4) { + bool b = (rPosture.VEC3_0x10 != rCamData.VEC3_0x8C); + if (!b && rPosture.FLOAT_0x28 == rCamData.FLOAT_0xA4) + return; + } + rCamData.mFlags &= ~0x7; + rCamData.mFlags |= 0x4; + rCamData.VEC3_0x8C = rPosture.VEC3_0x10; + rCamData.FLOAT_0xA4 = rPosture.FLOAT_0x28; + rCamData.mFlags &= ~0x8; + break; + + default: + break; + } + } +} + +void Camera::SetCameraMtxDirectly(const math::MTX34& rMtx) { + CameraData& rCamData = mCamData.ref(); + + if (mCamData.IsValid()) { + PSMTXCopy(rMtx.mtx, rCamData.mCamMtx.mtx); + rCamData.mFlags |= 0x8; + } +} + +} // namespace g3d +} // namespace nw4r diff --git a/source/nw4r/g3d/g3d_camera.hpp b/source/nw4r/g3d/g3d_camera.hpp new file mode 100644 index 000000000..91fa7788f --- /dev/null +++ b/source/nw4r/g3d/g3d_camera.hpp @@ -0,0 +1,118 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/nw4r/g3d/g3d_camera.h +// Decompiled by kiwi515. + +#pragma once + +#include + +#include +#include + +#include "g3d_rescommon.h" + +namespace nw4r { +namespace g3d { +struct CameraData { + math::MTX34 mCamMtx; // at 0x0 + math::MTX44 mProjMtx; // at 0x30 + u32 mFlags; + math::VEC3 mPos; // at 0x74 + math::VEC3 VEC3_0x80; + math::VEC3 VEC3_0x8C; + math::VEC3 VEC3_0x98; + f32 FLOAT_0xA4; + u32 INT_0xA8; + f32 FLOAT_0xAC; + f32 FLOAT_0xB0; + f32 FLOAT_0xB4; + f32 FLOAT_0xB8; + f32 FLOAT_0xBC; + f32 FLOAT_0xC0; + f32 FLOAT_0xC4; + f32 FLOAT_0xC8; + f32 FLOAT_0xCC; + f32 FLOAT_0xD0; + f32 FLOAT_0xD4; + f32 FLOAT_0xD8; + f32 FLOAT_0xDC; + f32 FLOAT_0xE0; + f32 FLOAT_0xE4; + f32 FLOAT_0xE8; + f32 FLOAT_0xEC; + f32 FLOAT_0xF0; + u32 INT_0xF4; + u32 INT_0xF8; + u32 INT_0xFC; + u32 INT_0x100; + u32 INT_0x104; + u32 INT_0x108; +}; + +struct Camera { + struct PostureInfo { + int INT_0x0; + math::VEC3 VEC3_0x04; + math::VEC3 VEC3_0x10; + math::VEC3 VEC3_0x1C; + f32 FLOAT_0x28; + // . . . + }; + + ResCommon mCamData; + + inline Camera(void* vptr) : mCamData(vptr) {} + inline void UpdateProjectionMtx() const { + CameraData& rCamData = mCamData.ref(); + + if (rCamData.mFlags & 0x40) { + C_MTXOrtho(rCamData.mProjMtx, rCamData.FLOAT_0xBC, rCamData.FLOAT_0xC0, + rCamData.FLOAT_0xC4, rCamData.FLOAT_0xC8, rCamData.FLOAT_0xB4, + rCamData.FLOAT_0xB8); + } else { + if (rCamData.mFlags & 0x10) { + C_MTXFrustum(rCamData.mProjMtx, rCamData.FLOAT_0xBC, + rCamData.FLOAT_0xC0, rCamData.FLOAT_0xC4, + rCamData.FLOAT_0xC8, rCamData.FLOAT_0xB4, + rCamData.FLOAT_0xB8); + } else { + C_MTXPerspective(rCamData.mProjMtx, rCamData.FLOAT_0xAC, + rCamData.FLOAT_0xB0, rCamData.FLOAT_0xB4, + rCamData.FLOAT_0xB8); + } + } + + rCamData.mFlags |= 0x80; + } + + // PAL: 0x8006a0c0 + Camera(CameraData*); + // PAL: 0x8006a0d0 + void Init(); + // PAL: 0x8006a120 + void Init(u16, u16, u16, u16, u16, u16); + void SetPosition(f32, f32, f32); + void SetPosition(const math::VEC3&); + // PAL: + void SetPosture(const PostureInfo&); + void SetCameraMtxDirectly(const math::MTX34&); + void SetPerspective(f32, f32, f32, f32); + void SetOrtho(f32, f32, f32, f32, f32, f32); + void SetProjectionMtxDirectly(const math::MTX44*); + void SetScissor(u32, u32, u32, u32); + void SetScissorBoxOffset(s32, s32); + void SetViewport(f32, f32, f32, f32); + void SetViewportZRange(f32, f32); + void GetViewport(f32*, f32*, f32*, f32*, f32*, f32*) const; + void GetCameraMtx(math::MTX34*) const; + void GetProjectionMtx(math::MTX44*) const; + void GetProjectionTexMtx(math::MTX34*) const; + void GetEnvironmentTexMtx(math::MTX34*) const; + void GXSetViewport() const; + void GXSetProjection() const; + void GXSetScissor() const; + void GXSetScissorBoxOffset() const; + void UpdateCameraMtx() const; +}; +} // namespace g3d +} // namespace nw4r diff --git a/source/nw4r/g3d/g3d_fog.cpp b/source/nw4r/g3d/g3d_fog.cpp new file mode 100644 index 000000000..ee3cfdcd4 --- /dev/null +++ b/source/nw4r/g3d/g3d_fog.cpp @@ -0,0 +1,76 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/src/nw4r/g3d/g3d_fog.cpp +// Decompiled by kiwi515. + +#include "g3d_fog.hpp" + +#include +#include + +namespace nw4r { +namespace g3d { + +Fog::Fog(FogData* pFogData) : mFogData(pFogData) {} + +void Fog::Init() { + if (mFogData.IsValid()) { + FogData& rFogData = mFogData.ref(); + + rFogData.INT_0x0 = 0; + + for (int i = 0; i < 4; i++) { + rFogData.FLOATS_0x4[i] = 0.0f; + } + + rFogData.mColor.mChannels.a = 0; + rFogData.mColor.mChannels.b = 0; + rFogData.mColor.mChannels.g = 0; + rFogData.mColor.mChannels.r = 0; + + rFogData.BYTE_0x18 = 0; + rFogData.BYTE_0x19 = 0; + + for (int i = 0; i < 11; i++) { + rFogData.mAdjTable[i] = 0; + } + } +} + +void* Fog::CopyTo(void* pFog) const { + if (pFog) { + register FogData& src = mFogData.ref(); + register void* dest = pFog; + + if (mFogData.IsValid()) { + asm { + lfd f0, 0(src); + stfd f0, 0(dest); + lfd f0, 8(src); + stfd f0, 8(dest); + lfd f0, 16(src); + stfd f0, 16(dest); + lfd f0, 24(src); + stfd f0, 24(dest); + lfd f0, 32(src); + stfd f0, 32(dest); + lfd f0, 40(src); + stfd f0, 40(dest); + } + return pFog; + } + } + + return NULL; +} + +void Fog::SetFogRangeAdjParam(u16 param1, u16 param2, const math::MTX44& rMtx) { + FogData& rFogData = mFogData.ref(); + + if (mFogData.IsValid()) { + rFogData.mAdjTable[0] = param2; + GXInitFogAdjTable(&rFogData.mAdjTable[1], param1, rMtx); + } +} + +} // namespace g3d +} // namespace nw4r diff --git a/source/nw4r/g3d/g3d_fog.hpp b/source/nw4r/g3d/g3d_fog.hpp new file mode 100644 index 000000000..243fff28a --- /dev/null +++ b/source/nw4r/g3d/g3d_fog.hpp @@ -0,0 +1,41 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/nw4r/g3d/g3d_fog.h +// Decompiled by kiwi515. + +#pragma once + +#include + +#include +#include + +#include "g3d_rescommon.h" + +namespace nw4r { +namespace g3d { + +struct FogData { + u32 INT_0x0; + f32 FLOATS_0x4[4]; + ut::Color mColor; + u8 BYTE_0x18; + u8 BYTE_0x19; + u16 mAdjTable[11]; // at 0x1A +}; + +struct Fog { + ResCommon mFogData; + + inline Fog(void* vptr) : mFogData(vptr) {} + + Fog(FogData*); + void Init(); + // PAL: 0x80077550 + void* CopyTo(void*) const; + // PAL: 0x800775b0 + void SetFogRangeAdjParam(u16, u16, const math::MTX44&); + void SetGP() const; +}; + +} // namespace g3d +} // namespace nw4r diff --git a/source/nw4r/g3d/g3d_rescommon.h b/source/nw4r/g3d/g3d_rescommon.h new file mode 100644 index 000000000..3bc3b7433 --- /dev/null +++ b/source/nw4r/g3d/g3d_rescommon.h @@ -0,0 +1,117 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/nw4r/g3d/g3d_rescommon.h +// Decompiled by kiwi515, GibHaltmannKill. + +#pragma once + +#include + +#define NW4R_G3D_CREATE_RES_NAME_DATA(VAR, VAL) \ + ResNameData ResNameData_##VAR = {sizeof(VAL) - 1, VAL} + +#define FIFO_ACCESS_BP 0x61 +#define FIFO_ACCESS_CP 0x8 +#define FIFO_ACCESS_XF 0x10 + +namespace nw4r { +namespace g3d { + +template class ResCommon { + T* mPtr; + +public: + inline ResCommon(void* vptr) : mPtr(static_cast(vptr)) {} + inline ResCommon(const void* vptr) : mPtr(static_cast(vptr)) {} + + inline T& ref() const { return *mPtr; } + inline T* ptr() const { return mPtr; } + inline bool IsValid() const { return mPtr; } + + template inline const TPtr* ofs_to_ptr_raw(s32 ofs) const { + return (const TPtr*)((u8*)mPtr + ofs); + } + + template inline TPtr* ofs_to_ptr(s32 ofs) { + if (ofs) + return (TPtr*)((u8*)mPtr + ofs); + + return NULL; + } + + template inline const TPtr* ofs_to_ptr(s32 ofs) const { + if (ofs) + return (const TPtr*)((u8*)mPtr + ofs); + + return NULL; + } + + template inline TObj ofs_to_obj(s32 ofs) const { + if (ofs) + return (u8*)mPtr + ofs; + + return NULL; + } +}; + +struct ResNameData { + u32 mLength; + char mName[0x1C]; +}; + +struct ResName { + ResCommon mRes; + + inline ResName(const void* vptr) : mRes(vptr) {} + + inline u32 GetLength() const { return mRes.ref().mLength; } + + inline const char* GetName() const { return mRes.ref().mName; } + + bool operator==(ResName) const; +}; + +namespace detail { +typedef u8 CPCmd[6]; +typedef u8 BPCmd[5]; + +inline void ResWrite_u8(u8* res, u8 arg) { *res = arg; } + +inline void ResWrite_u16(u8* res, u16 arg) { + ResWrite_u8(res + 0, arg >> 8); + ResWrite_u8(res + 1, arg >> 0); +} + +inline void ResWrite_u32(u8* res, u32 arg) { + ResWrite_u8(res + 0, arg >> 24); + ResWrite_u8(res + 1, arg >> 16); + ResWrite_u8(res + 2, arg >> 8); + ResWrite_u8(res + 3, arg >> 0); +} + +inline u8 ResRead_u8(const u8* res) { return *res; } + +inline u32 ResRead_u32(const u8* res) { + int ret = ResRead_u8(res) << 24; + ret |= ResRead_u8(res + 1) << 16; + ret |= ResRead_u8(res + 2) << 8; + ret |= ResRead_u8(res + 3); + return ret; +} + +inline void ResReadBPCmd(const u8* res, u32* out) { + *out = ResRead_u32(res + 1); +} + +inline void ResReadCPCmd(const u8* res, u32* out) { + *out = ResRead_u32(res + 2); +} + +void ResWriteBPCmd(u8*, u32); +void ResWriteBPCmd(u8*, u32, u32); +void ResWriteCPCmd(u8*, u8, u32); +void ResWriteXFCmd(u8*, u16, u32); +void ResWriteSSMask(u8*, u32); + +} // namespace detail +} // namespace g3d +} // namespace nw4r diff --git a/source/nw4r/g3d/g3d_state.hpp b/source/nw4r/g3d/g3d_state.hpp new file mode 100644 index 000000000..ffad26522 --- /dev/null +++ b/source/nw4r/g3d/g3d_state.hpp @@ -0,0 +1,19 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/nw4r/g3d/g3d_state.h +// Decompiled by kiwi515. + +#pragma once + +#include + +namespace nw4r { +namespace g3d { +namespace G3DState { + +// PAL: 0x80064440 +GXRenderModeObj& GetRenderModeObj(); +void SetRenderModeObj(const GXRenderModeObj&); + +} // namespace G3DState +} // namespace g3d +} // namespace nw4r diff --git a/source/nw4r/math/mathTypes.h b/source/nw4r/math/mathTypes.h index c914829ce..a6a8cd56d 100644 --- a/source/nw4r/math/mathTypes.h +++ b/source/nw4r/math/mathTypes.h @@ -55,6 +55,10 @@ struct VEC3 : public _VEC3 { operator Vec*() { return (Vec*)&x; } operator const Vec*() const { return (const Vec*)&x; } + + bool operator==(const VEC3& o) const { return x == o.x && y == o.y && z == o.z; } + bool operator!=(const VEC3& o) const { return x != o.x || y != o.y || z != o.z; } + }; class MTX33 : public _MTX33 { @@ -82,6 +86,7 @@ class MTX33 : public _MTX33 { struct MTX34 : public _MTX34 { public: + typedef f32 (*MtxPtr)[4]; typedef const f32 (*ConstMtxPtr)[4]; public: MTX34() {} @@ -93,6 +98,21 @@ struct MTX34 : public _MTX34 operator ConstMtxPtr() const { return (ConstMtxPtr)&_00; } }; +struct MTX44 : public _MTX44 +{ +public: + typedef f32 (*Mtx44Ptr)[4]; + typedef const f32 (*ConstMtx44Ptr)[4]; +public: + MTX44() {} + + operator f32*() { return &arr[0]; } + operator const f32*() const { return &arr[0]; } + + operator Mtx44Ptr() { return (Mtx44Ptr)&arr[0]; } + operator ConstMtx44Ptr() const { return (ConstMtx44Ptr)&arr[0]; } +}; + // PAL: 0x80085600 MTX33* MTX33Identity(MTX33*); // PAL: 0x80085630 diff --git a/source/nw4r/ut/utColor.hpp b/source/nw4r/ut/utColor.hpp new file mode 100644 index 000000000..0b86955e2 --- /dev/null +++ b/source/nw4r/ut/utColor.hpp @@ -0,0 +1,62 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/nw4r/ut/ut_Color.h +// Decompiled by kiwi515, GibHaltmannKill. + +#pragma once + +#include + +#include + +namespace nw4r { +namespace ut { + +struct Color { + GXColor mChannels; + + inline u32& ToU32ref() { return *reinterpret_cast(this); } + + inline const u32& ToU32ref() const { + return *reinterpret_cast(this); + } + + inline Color& operator=(u32 value) { + ToU32ref() = value; + return *this; + } + + inline Color() { *this = 0xFFFFFFFF; } + + inline Color(const Color& other) { + mChannels.r = other.mChannels.r; + mChannels.g = other.mChannels.g; + mChannels.b = other.mChannels.b; + mChannels.a = other.mChannels.a; + } + + inline Color& operator=(const Color& other) { + mChannels.r = other.mChannels.r; + mChannels.g = other.mChannels.g; + mChannels.b = other.mChannels.b; + mChannels.a = other.mChannels.a; + return *this; + } + + inline Color(u32 rgba) { *this = rgba; } + + inline Color(int red, int green, int blue, int alpha) { + mChannels.r = red; + mChannels.g = green; + mChannels.b = blue; + mChannels.a = alpha; + } + + inline operator u32() const { return ToU32ref(); } + + inline operator GXColor() const { return mChannels; } + + inline ~Color() {} +}; + +} // namespace ut +} // namespace nw4r diff --git a/source/nw4r/ut/utList.hpp b/source/nw4r/ut/utList.hpp index 76fe917a6..18d466fae 100644 --- a/source/nw4r/ut/utList.hpp +++ b/source/nw4r/ut/utList.hpp @@ -29,7 +29,7 @@ inline void* List_GetFirst(const List* pList) { return List_GetNext(pList, nullptr); } // Seems to be not included as a symbol. Only inlined. -//void List_Prepend(List* pList, void* pObj); +// void List_Prepend(List* pList, void* pObj); } // namespace ut } // namespace nw4r diff --git a/source/rvl/gx/gx.h b/source/rvl/gx/gx.h new file mode 100644 index 000000000..70ba7d9ba --- /dev/null +++ b/source/rvl/gx/gx.h @@ -0,0 +1,22 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/RevoSDK/GX/GX.h +// Decompiled by kiwi515, GibHaltmannKill. + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _GXColor { + u8 r; + u8 g; + u8 b; + u8 a; +} GXColor; + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/gx/gxFrameBuf.h b/source/rvl/gx/gxFrameBuf.h new file mode 100644 index 000000000..f1cdeac1e --- /dev/null +++ b/source/rvl/gx/gxFrameBuf.h @@ -0,0 +1,38 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/RevoSDK/GX/GXFrameBuf.h +// Decompiled by kiwi515. + +#pragma once + +#include + +#include "gx.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _GXRenderModeObj { + u32 INT_0x0; + u16 mFbWidth; // at 0x4 + u16 mEfbHeight; // at 0x8 + u16 SHORT_0x8; + u16 SHORT_0xA; + u16 SHORT_0xC; + u16 SHORT_0xE; + u16 SHORT_0x10; + u16 UNUSED_0x12; + u32 INT_0x14; + u8 BYTE_0x18; + u8 BYTE_0x19; + u8 BYTES_0x1A[24]; + u8 BYTES_0x32[7]; +} GXRenderModeObj; + +void GXCopyDisp(void*, u8); +void GXSetCopyClear(GXColor, u32 zClear); +void GXSetCopyFilter(u8, const u8[12][2], u8, const u8[7]); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/gx/gxPixel.h b/source/rvl/gx/gxPixel.h new file mode 100644 index 000000000..56a63003e --- /dev/null +++ b/source/rvl/gx/gxPixel.h @@ -0,0 +1,29 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/RevoSDK/GX/GXPixel.h +// Decompiled by kiwi515. + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void GXSetFog(int, f32, f32, f32, f32, GXColor); + +void GXInitFogAdjTable(u16* table, u16 width, const f32[4][4]); +void GXSetFogRangeAdj(u8, u16, u16* table); + +void GXSetBlendMode(u32, u32, u32, u32); + +void GXSetColorUpdate(u8); +void GXSetAlphaUpdate(u8); +void GXSetZMode(u8, u32, u8); +void GXSetZCompLoc(u8); + +void GXSetDstAlpha(u8, u8); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/gx/gxTransform.h b/source/rvl/gx/gxTransform.h new file mode 100644 index 000000000..93ceea7ab --- /dev/null +++ b/source/rvl/gx/gxTransform.h @@ -0,0 +1,27 @@ +// Source: +// https://github.com/doldecomp/ogws/blob/82dbeac2267170fc5cef7e67a6d8c31c5ff45d69/include/RevoSDK/GX/GXTransform.h +// Decompiled by kiwi515. + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void GXSetScissor(u32, u32, u32, u32); +void GXSetScissorBoxOffset(s32, s32); + +void GXSetCurrentMtx(u32); + +void GXSetViewport(f32, f32, f32, f32, f32, f32); +void GXSetViewportJitter(f32, f32, f32, f32, f32, f32, u32); + +void GXSetProjection(const f32*, u32); + +void GXLoadPosMtxImm(const f32 mtxPtr[3][4], u32 id2); + +#ifdef __cplusplus +} +#endif diff --git a/sources.py b/sources.py index a764fe144..1e704024b 100644 --- a/sources.py +++ b/sources.py @@ -18,6 +18,8 @@ compile_source("source/nw4r/math/mathTriangular.cpp", "out/mathTriangular.o", '4201_127', NW4R_OPTS) compile_source("source/nw4r/math/mathTypes.cpp", "out/mathTypes.o", '4201_127', NW4R_OPTS) +compile_source("source/nw4r/g3d/g3d_camera.cpp", "out/g3d_camera.o", '4201_127', NW4R_OPTS) +compile_source("source/nw4r/g3d/g3d_fog.cpp", "out/g3d_fog.o", '4201_127', NW4R_OPTS) compile_source("source/nw4r/ut/utList.cpp", "out/utList.o", '4201_127', NW4R_OPTS) compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) From 785ea54e6eb7ec58bb38897f765b07ba91149b99 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Tue, 6 Jul 2021 10:04:03 -0600 Subject: [PATCH 089/477] :construction: Add graphic.py --- mkwutil/graphic.py | 314 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 mkwutil/graphic.py diff --git a/mkwutil/graphic.py b/mkwutil/graphic.py new file mode 100644 index 000000000..ea2613a8b --- /dev/null +++ b/mkwutil/graphic.py @@ -0,0 +1,314 @@ +def html_emit_box(style, tooltip): + return "
" % (style, tooltip) + +def html_emit_page(boxes, boxes2): + return """ + + + + + + + +

DOL Decompiled

""" + boxes + """ +

DOL Libraries

""" + boxes2 + """ + +""" + +#
+#
+ + +def build_box(width, label, color): + return html_emit_box("background-color:%s; width:%spx;" % (color, width), label) + +#CODE_COLOR = "#d5feff" +CODE_COLOR = "hsl(%s, 100, 91.8)" +UNK_COLOR = "#000000" + +import colorsys + +def make_code_color(hue): + rgb = colorsys.hsv_to_rgb(hue, 100/100, 91.8/100) + #print(rgb) + return rgb + + +def make_code_color_hex(hue): + vals = list(round(i * 255) for i in make_code_color(hue)) + + r = "#" + + for v in vals: + if len(hex(v)) == len(hex(3)): + r += "0" + r += hex(v)[2:] + + return r + +PX_FACTOR = 1 / 2000 + +import random +def box_from_code_seg(begin, end, label): + return build_box(abs(end - begin) * PX_FACTOR, label, make_code_color_hex(random.randint(0, 360)/360)) + +def box_from_gap_seg(begin, end): + return build_box(abs(end - begin) * PX_FACTOR, "gap", UNK_COLOR) + +SPLITS = [ + ["rvl/trk/rvlTrkMem.c",0x80005f34,0x80006068,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["nw4r/g3d/g3d_camera.cpp",0,0, 0,0, 0,0, 0x8006a0c0 ,0x8006a518, 0,0, 0,0, 0,0, 0,0, 0x802bd4ec,0x802bd4ec, 0,0, 0,0, 0x80387cac,0x80387cd8, 0,0], + ["nw4r/g3d/g3d_fog.cpp",0,0,0,0,0,0,0x800774d0,0x800775d0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80387d58,0x80387d5C,0,0], + ["nw4r/math/mathTriangular.cpp",0,0,0,0,0,0,0x80085110,0x80085578,0,0,0,0,0x80248010,0x80249020,0x80274148,0x80274250,0,0,0,0,0,0,0x80387e80,0x80387ea4,0,0], + ["nw4r/math/mathTypes.cpp",0,0,0,0,0,0,0x80085600,0x80085938,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80387ea8,0x80387eb4,0,0], + ["nw4r/ut/utList.cpp",0,0,0,0,0,0,0x800AEF60,0x800af1a0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["dwc/common/dwc_error.c",0,0,0,0,0,0,0x800CCB4C,0x800CCC80,0,0,0,0,0,0,0x80275700,0x80275758,0,0,0,0,0x803862A8,0x803862B0,0,0,0,0], + ["gamespy/darray.c",0,0,0,0,0,0,0x800ef378,0x800efdcc,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["gamespy/hashtable.c",0,0,0,0,0,0,0x800efdcc,0x800f0264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["gamespy/md5c.c",0,0,0,0,0,0,0x800f0264,0x800f118c,0,0,0,0,0x8024c6b8,0x8024c6c9,0x8027aca0,0x8027ace0,0,0,0,0,0,0,0,0,0,0], + ["gamespy/common/revolution/gsSocketRevolution.c",0,0,0,0,0,0,0x800f118c,0x800f164c,0,0,0,0,0,0,0,0,0,0,0,0,0x80386350,0x80386358,0,0,0,0], + ["gamespy/common/revolution/gsUtilRevolution.c",0,0,0,0,0,0,0x800f1f58,0x800f2048,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["gamespy/common/gsAvailable.c",0,0,0,0,0,0,0x800f38a4,0x800f3a20,0,0,0,0,0,0,0x8027ad58,0x8027ad79,0x802f2338,0x802f2410,0,0,0x80386358,0x8038635c,0,0,0,0], + ["gamespy/common/gsCore.c",0,0,0,0,0,0,0x800f3c08 ,0x800f41dc,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["gamespy/common/gsUdpEngine.c",0,0,0,0,0,0,0x800f489c,0x800f5a6c,0,0,0,0,0,0,0,0,0x802f2440,0x802f247c,0,0,0,0,0,0,0,0], + ["gamespy/common/gsXML.c",0,0,0,0,0,0,0x800f5a6c,0x800fb828,0,0,0,0,0,0,0x8027ad80,0x8027af18,0,0,0x803850a0,0x80385105,0,0,0,0,0,0], + ["gamespy/gp/gp.c",0,0,0,0,0,0,0x800fb828,0x800fc7d4,0,0,0,0,0,0,0x8027af18,0x8027b289,0,0,0x80385108,0x80385118,0,0,0,0,0,0], + ["gamespy/gp/gpi.c",0,0,0,0,0,0,0x800fc7d4,0x800fd160,0,0,0,0,0,0,0x8027b290,0x8027b30f,0,0,0x80385118,0x80385150,0,0,0,0,0,0], + ["gamespy/gp/gpiBuddy.c",0,0,0,0,0,0,0x800fd160,0x800fee90,0,0,0,0,0,0,0x8027b310,0x8027b453,0,0,0x80385150,0x803851ea,0,0,0,0,0,0], + ["gamespy/gp/gpiBuffer.c",0,0,0,0,0,0,0x800fee90,0x800ff8c4,0,0,0,0,0,0,0x8027b458,0x8027b4ba,0,0,0x803851f0,0x80385206,0,0,0,0,0,0], + ["gamespy/gp/gpiCallback.c",0,0,0,0,0,0,0x800ff8c4,0x800ffe28 ,0,0,0,0,0,0,0x8027b4c0,0x8027b4cf,0,0,0,0,0,0,0,0,0,0], + ["gamespy/gp/gpiConnect.c",0,0,0,0,0,0,0x800ffe28 ,0x80101470,0,0,0,0,0,0,0x8027b4d0,0x8027b876,0,0,0x80385208,0x8038529b,0,0,0,0,0,0], + ["gamespy/gp/gpiInfo.c",0,0,0,0,0,0,0x80101470,0x80103908,0,0,0,0,0,0,0x8027b878,0x8027bbce,0,0,0x803852a0,0x80385355,0,0,0x80388470,0x80388474,0,0], + ["gamespy/gp/gpiKeys.c",0,0,0,0,0,0,0x80103908,0x80103f70,0,0,0,0,0,0,0x8027bbd0,0x8027bc11,0,0,0x80385358,0x8038535a,0,0,0,0,0,0], + ["gamespy/gp/gpiOperation.c",0,0,0,0,0,0,0x80103f70,0x80104648,0,0,0,0,0,0,0x8027bc18,0x8027bc60,0,0,0,0,0,0,0,0,0,0], + ["gamespy/gp/gpiPeer.c",0,0,0,0,0,0,0x80104648,0x80105d54,0,0,0,0,0,0,0x8027bc60,0x8027bd2a,0,0,0x80385360,0x803853b9,0,0,0,0,0,0], + ["gamespy/sake/sakeMain.c",0,0,0,0,0,0,0x80121eec ,0x8012249c,0,0,0,0,0,0,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,0,0,0,0,0,0], + ["rvl/arc/rvlArchive.c",0,0,0,0,0,0,0x80124500,0x80124E80,0,0,0,0,0,0,0x8027E708,0x8027E772,0,0,0x803857F0,0x803857F6,0,0,0,0,0,0], + ["rvl/mem/rvlMemHeap.c",0,0,0,0,0,0,0x801981ec,0x80198798,0,0,0,0,0,0,0,0,0x80346cf0,0x80346d18,0,0,0x80386838,0x8038683C,0,0,0,0], + ["rvl/mem/rvlMemExpHeap.c",0,0,0,0,0,0,0x80198798,0x80199430,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["rvl/mem/rvlMemFrmHeap.c",0,0,0,0,0,0,0x80199430,0x801998A4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["rvl/mem/rvlMemUnitHeap.c",0,0,0,0,0,0,0x801998A4,0x80199b58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["rvl/mem/rvlMemAllocator.c",0,0,0,0,0,0,0x80199b58,0x80199bf0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80388860,0x80388870,0,0], + ["rvl/mem/rvlMemList.c",0,0,0,0,0,0,0x80199BF0,0x80199D04,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["rvl/mtx/rvlMtx.c",0,0,0,0,0,0,0x80199d04,0x8019a9c4,0,0,0,0,0,0,0,0,0,0,0x80385a08,0x80385a10,0,0,0x80388870,0x80388890,0,0], + ["rvl/mtx/rvlMtx2.c",0,0,0,0,0,0,0x8019a9c4,0x8019abe4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80388890,0x803888a8,0,0], + ["rvl/mtx/rvlVec.c",0,0,0,0,0,0,0x8019abe4,0x8019ae08,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x803888a8,0x803888b4,0,0], + ["rvl/mtx/rvlQuat.c",0,0,0,0,0,0,0x8019ae08,0x8019b178,0,0,0,0,0x80252c78,0x80252c84,0,0,0,0,0,0,0,0,0x803888b8,0x803888cc,0,0], + ["rvl/so/soCommon.c",0,0,0,0,0,0,0x801ec088,0x801ecf20,0,0,0,0,0,0,0x802a2318,0x802a24f4,0x80357220,0x80357238,0x80385ee0,0x80385ee8,0x80386D30,0x80386D38,0,0,0,0], + ["rvl/so/soBasic.c",0,0,0,0,0,0,0x801ecf20,0x801ecff4,0,0,0,0,0,0,0x802a24f8 ,0x802a2543,0,0,0x80385ee8,0x80385eeC,0,0,0,0,0,0], + ["egg/core/eggAllocator.cpp",0,0,0,0,0,0,0x8020F62C,0x8020F6EC,0,0,0,0,0,0,0x802A2668,0x802A2680 ,0,0,0,0,0,0,0,0,0,0], + ["egg/core/eggArchive.cpp",0,0,0,0,0,0,0x8020F6EC,0x8020FCC4,0,0,0,0,0,0,0x802A2680,0x802A268C,0x803832D8,0x803832E4,0,0,0x80386D80,0x80386D84,0,0,0,0], + ["egg/core/eggDisposer.cpp",0,0,0,0,0,0,0x8021A0F0,0x8021A1B8,0,0,0,0,0,0,0x802A2B48,0x802A2B54 ,0,0,0,0,0,0,0,0,0,0], + ["egg/core/eggExpHeap.cpp",0,0,0,0,0,0,0x802269A8,0x80226F04,0,0,0,0,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,0,0,0,0,0,0,0,0,0,0], + ["egg/core/eggGraphicsFifo.cpp",0,0,0,0,0,0,0x80229540,0x802296A8,0,0,0,0,0,0,0x802A30B0,0x802A30BC,0,0,0,0,0x80386E90,0x80386E99,0,0,0,0], + ["egg/core/eggHeap.cpp",0,0,0,0,0,0,0x802296A8,0x80229FAC,0,0,0,0,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,0,0,0x80386EA0,0x80386EC0,0x80388D68 ,0x80388D80,0,0], + ["egg/math/eggQuat.cpp",0,0,0,0,0,0,0x80239DFC,0x80239E10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["egg/core/eggScene.cpp",0,0,0,0,0,0,0x8023AD10,0x8023ADDC,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], + ["egg/core/eggStreamDecomp.cpp",0,0,0,0,0,0,0x80242498,0x80242504,0,0,0,0,0,0,0x802A3F78,0x802A3F90,0,0,0,0,0,0,0,0,0,0], + ["egg/core/eggSystem.cpp",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80386F60,0x80386F64,0,0,0,0], + ["egg/core/eggThread.cpp",0,0,0,0,0,0,0x802432E0 ,0x80243754,0,0,0,0,0,0,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,0,0,0,0,0,0,0,0], + ["egg/core/eggUnitHeap.cpp",0,0,0,0,0,0,0x80243754,0x80243A00,0,0,0,0,0,0,0x802A3FD8,0x802A4004,0,0,0,0,0,0,0,0,0,0], + ["egg/math/eggVector.cpp",0,0,0,0,0,0,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,0,0,0,0,0,0,0x80384B70,0x80384BF4,0,0,0x80386F78,0x80386F90,0x803890F8 ,0x80389104,0,0], + ["egg/core/eggVideo.cpp",0,0,0,0,0,0,0x80243D18,0x80244074,0,0,0,0,0x802582E0,0x80258560,0,0,0,0,0,0,0,0,0x80389108,0x80389118,0,0] +] + + + +def child_of(parent, child): + if child[0] >= parent[1]: + return False + + if child[1] <= parent[0]: + return False + + return True + +def fs(a): + return "%x %x %s" % (a[0], a[1], a[2]) + + +class RangeMapper: + def __init__(self, begin, end): + self.segs = [ [ begin, end, "unk"] ] + self.begin = begin + self.end = end + + + def comp_total(self): + t = 0 + for s in self.segs: + t += abs(s[1] - s[0]) + + if s[0] > s[1]: + return 0 + + return t + + def assert_valid(self): + ctotal = self.comp_total() + rtotal = self.end - self.begin + + if ctotal == rtotal: + return + + print("INVALID: CTOTAL = %x, RTOTAL = %x" % (ctotal, rtotal)) + + assert False + + def find_parent(self, block): + for s in self.segs: + if child_of(s, block): + return s + + return None + + def add_mem_range(self, sec, file): + self.assert_valid() + + low, high = sec + + # Handle replace case + for s in self.segs: + if s[0] == low and s[1] == high: + # print("REPLACE") + s[0] = low + s[1] = high + s[2] = file + self.assert_valid() + self.segs = sorted(self.segs) + return + + + self.segs = sorted(self.segs) + + parent = self.find_parent(sec) + assert parent + + print("Splitting to add %s" % file) + + + parent_file = parent[2] + low_block = [parent[0], low, "unk"] + mid_block = [low, high, file] + top_block = [high, parent[1], "unk"] + + + + #print("Parent") + #print(fs(parent)) + #print("us") + #print(fs((sec[0], sec[1], file))) + #print(list(fs(a) for a in segs)) + assert low_block[0] <= low_block[1] + assert mid_block[0] <= mid_block[1] + assert top_block[0] <= top_block[1] + + if low_block[0] != low_block[1]: + self.segs.append(low_block) + + parent[0] = mid_block[0] + parent[1] = mid_block[1] + parent[2] = mid_block[2] + + if parent[0] == parent[1]: + print("Removing parent") + self.segs.remove(parent) + + if top_block[0] != top_block[1]: + self.segs.append(top_block) + + self.assert_valid() + self.segs = sorted(self.segs) + + +def populate_mapper(mapper, splits): + for split in splits: + file = split[0] + for i in range(1, len(split), 2): + sec = [split[i], split[i + 1]] + + if sec[0] == 0: + assert sec[1] == 0 + continue + + if sec[0] == sec[1]: + print("WARN: file %s invalidy configured, begin == end, begin != 0" % file) + continue + + assert sec[1] > sec[0] + + mapper.add_mem_range(sec, file) + + for seg in mapper.segs: + perc = "-" * int(1 + abs(seg[0]-seg[1]) / 400000) + print("%s %s %s %s" % (perc, hex(seg[0]), hex(seg[1]), seg[2])) + +def size_of_mapper(mapper): + n_code = 0 + n_unk = 0 + + for seg in mapper.segs: + if seg[2] == "unk": + n_unk += abs(seg[0] - seg[1]) + else: + n_code += abs(seg[0] - seg[1]) + + return n_code, n_unk + +def boxlist_from_mapper(mapper): + boxes = [] + + for seg in mapper.segs: + if seg[2] == "unk": + boxes.append(box_from_gap_seg(seg[0], seg[1])) + else: + boxes.append(box_from_code_seg(seg[0], seg[1], seg[2])) + + return boxes + +def percent_decomp_stats(mapper): + n_code, n_unk = size_of_mapper(mapper) + + total = n_unk + n_code + print("Total: %s, real total: %s" % (total, DOL_SIZE)) + + print("Code&Data Percent: %s" % (100 * n_code / total)) + +DOL_BEGIN = 0x80004000 +DOL_END = 0x8038917C + +DOL_SIZE = DOL_END - DOL_BEGIN + +def standard_boxes(): + mapper = RangeMapper(DOL_BEGIN, DOL_END) + populate_mapper(mapper, SPLITS) + + # percent_decomp_stats(mapper) + + return boxlist_from_mapper(mapper) + +LIB_SPLITS = [ + [ "NW4R", 0x80021BB0, 0x800BBB80], + [ "RFL", 0x800BBB80, 0x800CC7E4], + [ "DWC", 0x800CC7E4, 0x800EF378], + [ "SPY", 0x800EF378, 0x80123F88], + [ "RVL", 0x80123F88, 0x8020F62C], + [ "EGG", 0x8020F62C, 0x80244DD4] +] + +def lib_boxes(): + mapper = RangeMapper(DOL_BEGIN, DOL_END) + populate_mapper(mapper, LIB_SPLITS) + + return boxlist_from_mapper(mapper) + +page = html_emit_page("\n".join(standard_boxes()), "\n".join(lib_boxes())) + + +with open("out.html", 'w') as f: + f.write(page) \ No newline at end of file From d6c35a4e39d2c4b3f8134c4b1b6a92a4486b3551 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:28:21 -0600 Subject: [PATCH 090/477] :sparkles: Add asm differ --- .gitignore | 1 + build.py | 11 +- diff.py | 1760 ++++++++++++++++++++++++++++++++++++++++++++++ diff_settings.py | 13 + 4 files changed, 1781 insertions(+), 4 deletions(-) create mode 100644 diff.py create mode 100644 diff_settings.py diff --git a/.gitignore b/.gitignore index 8334d75ea..0f1411d4b 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ tmp /artifacts/orig/pal/rel/*.bin !/artifacts/orig/pal/rel/dol_rel.bin !/artifacts/orig/pal/rel/rel_abs.bin +*.map # Compiler tools bundle tools.7z diff --git a/build.py b/build.py index 95620b91d..9337bc86c 100644 --- a/build.py +++ b/build.py @@ -118,8 +118,9 @@ def assemble(dst, src): subprocess.run([GAS, src, "-mgekko", "-Iasm", "-o", dst], check=True, text=True) -def link(dst, objs, lcf, partial=False): - cmd = [MWLD] + objs + ["-o", dst, "-lcf", lcf, "-fp", "hard", "-linkmode", "moreram"] +def link(dst, objs, lcf, map_path, partial=False): + print(map_path) + cmd = [MWLD] + objs + ["-o", dst, "-lcf", lcf, "-fp", "hard", "-linkmode", "moreram", "-map", map_path] if partial: cmd.append("-r") subprocess.run(cmd, check=True, text=True) @@ -165,7 +166,8 @@ def link_dol(o_files): dest_dir.mkdir(parents=True, exist_ok=True) # Link ELF. elf_path = dest_dir / "main.elf" - link(elf_path, o_files, dst_lcf_path) + map_path = dest_dir / "main.map" + link(elf_path, o_files, dst_lcf_path, map_path) # Convert ELF to DOL. dol_path = dest_dir / "main.dol" pack_main_dol(elf_path, dol_path) @@ -182,7 +184,8 @@ def link_rel(o_files): dest_dir.mkdir(parents=True, exist_ok=True) # Link ELF. elf_path = dest_dir / "StaticR.elf" - link(elf_path, o_files, dst_lcf_path, partial=True) + map_path = dest_dir / "StaticR.map" + link(elf_path, o_files, dst_lcf_path, map_path, partial=True) # Convert ELF to REL. rel_path = dest_dir / "StaticR.rel" orig_dir = Path("artifacts", "orig") diff --git a/diff.py b/diff.py new file mode 100644 index 000000000..ff5df0b43 --- /dev/null +++ b/diff.py @@ -0,0 +1,1760 @@ +#!/usr/bin/env python3 +# PYTHON_ARGCOMPLETE_OK +import argparse +import sys +from typing import ( + Any, + Callable, + Dict, + List, + Match, + NoReturn, + Optional, + Pattern, + Set, + Tuple, + Type, + Union, +) + + +def fail(msg: str) -> NoReturn: + print(msg, file=sys.stderr) + sys.exit(1) + + +# Prefer to use diff_settings.py from the current working directory +sys.path.insert(0, ".") +try: + import diff_settings +except ModuleNotFoundError: + fail("Unable to find diff_settings.py in the same directory.") +sys.path.pop(0) + +# ==== COMMAND-LINE ==== + +if __name__ == "__main__": + try: + import argcomplete # type: ignore + except ModuleNotFoundError: + argcomplete = None + + parser = argparse.ArgumentParser(description="Diff MIPS, PPC or AArch64 assembly.") + + start_argument = parser.add_argument( + "start", + help="Function name or address to start diffing from.", + ) + + if argcomplete: + + def complete_symbol( + prefix: str, parsed_args: argparse.Namespace, **kwargs: object + ) -> List[str]: + if not prefix or prefix.startswith("-"): + # skip reading the map file, which would + # result in a lot of useless completions + return [] + config: Dict[str, Any] = {} + diff_settings.apply(config, parsed_args) # type: ignore + mapfile = config.get("mapfile") + if not mapfile: + return [] + completes = [] + with open(mapfile) as f: + data = f.read() + # assume symbols are prefixed by a space character + search = f" {prefix}" + pos = data.find(search) + while pos != -1: + # skip the space character in the search string + pos += 1 + # assume symbols are suffixed by either a space + # character or a (unix-style) line return + spacePos = data.find(" ", pos) + lineReturnPos = data.find("\n", pos) + if lineReturnPos == -1: + endPos = spacePos + elif spacePos == -1: + endPos = lineReturnPos + else: + endPos = min(spacePos, lineReturnPos) + if endPos == -1: + match = data[pos:] + pos = -1 + else: + match = data[pos:endPos] + pos = data.find(search, endPos) + completes.append(match) + return completes + + setattr(start_argument, "completer", complete_symbol) + + parser.add_argument( + "end", + nargs="?", + help="Address to end diff at.", + ) + parser.add_argument( + "-o", + dest="diff_obj", + action="store_true", + help="Diff .o files rather than a whole binary. This makes it possible to " + "see symbol names. (Recommended)", + ) + parser.add_argument( + "-e", + "--elf", + dest="diff_elf_symbol", + metavar="SYMBOL", + help="Diff a given function in two ELFs, one being stripped and the other " + "one non-stripped. Requires objdump from binutils 2.33+.", + ) + parser.add_argument( + "--source", + action="store_true", + help="Show source code (if possible). Only works with -o and -e.", + ) + parser.add_argument( + "--inlines", + action="store_true", + help="Show inline function calls (if possible). Only works with -o and -e.", + ) + parser.add_argument( + "--base-asm", + dest="base_asm", + metavar="FILE", + help="Read assembly from given file instead of configured base img.", + ) + parser.add_argument( + "--write-asm", + dest="write_asm", + metavar="FILE", + help="Write the current assembly output to file, e.g. for use with --base-asm.", + ) + parser.add_argument( + "-m", + "--make", + dest="make", + action="store_true", + help="Automatically run 'make' on the .o file or binary before diffing.", + ) + parser.add_argument( + "-l", + "--skip-lines", + dest="skip_lines", + type=int, + default=0, + metavar="LINES", + help="Skip the first N lines of output.", + ) + parser.add_argument( + "-s", + "--stop-jr-ra", + dest="stop_jrra", + action="store_true", + help="Stop disassembling at the first 'jr ra'. Some functions have multiple return points, so use with care!", + ) + parser.add_argument( + "-i", + "--ignore-large-imms", + dest="ignore_large_imms", + action="store_true", + help="Pretend all large enough immediates are the same.", + ) + parser.add_argument( + "-I", + "--ignore-addr-diffs", + dest="ignore_addr_diffs", + action="store_true", + help="Ignore address differences. Currently only affects AArch64.", + ) + parser.add_argument( + "-B", + "--no-show-branches", + dest="show_branches", + action="store_false", + help="Don't visualize branches/branch targets.", + ) + parser.add_argument( + "-S", + "--base-shift", + dest="base_shift", + type=str, + default="0", + help="Diff position X in our img against position X + shift in the base img. " + 'Arithmetic is allowed, so e.g. |-S "0x1234 - 0x4321"| is a reasonable ' + "flag to pass if it is known that position 0x1234 in the base img syncs " + "up with position 0x4321 in our img. Not supported together with -o.", + ) + parser.add_argument( + "-w", + "--watch", + dest="watch", + action="store_true", + help="Automatically update when source/object files change. " + "Recommended in combination with -m.", + ) + parser.add_argument( + "-3", + "--threeway=prev", + dest="threeway", + action="store_const", + const="prev", + help="Show a three-way diff between target asm, current asm, and asm " + "prior to -w rebuild. Requires -w.", + ) + parser.add_argument( + "-b", + "--threeway=base", + dest="threeway", + action="store_const", + const="base", + help="Show a three-way diff between target asm, current asm, and asm " + "when diff.py was started. Requires -w.", + ) + parser.add_argument( + "--width", + dest="column_width", + type=int, + default=50, + help="Sets the width of the left and right view column.", + ) + parser.add_argument( + "--algorithm", + dest="algorithm", + default="levenshtein", + choices=["levenshtein", "difflib"], + help="Diff algorithm to use. Levenshtein gives the minimum diff, while difflib " + "aims for long sections of equal opcodes. Defaults to %(default)s.", + ) + parser.add_argument( + "--max-size", + "--max-lines", + dest="max_lines", + type=int, + default=1024, + help="The maximum length of the diff, in lines.", + ) + + # Project-specific flags, e.g. different versions/make arguments. + add_custom_arguments_fn = getattr(diff_settings, "add_custom_arguments", None) + if add_custom_arguments_fn: + add_custom_arguments_fn(parser) + + if argcomplete: + argcomplete.autocomplete(parser) + +# ==== IMPORTS ==== + +# (We do imports late to optimize auto-complete performance.) + +import ast +from dataclasses import dataclass, field, replace +import difflib +import itertools +import os +import queue +import re +import string +import subprocess +import threading +import time + + +MISSING_PREREQUISITES = ( + "Missing prerequisite python module {}. " + "Run `python3 -m pip install --user colorama ansiwrap watchdog python-Levenshtein cxxfilt` to install prerequisites (cxxfilt only needed with --source)." +) + +try: + from colorama import Fore, Style # type: ignore + import ansiwrap # type: ignore + import watchdog # type: ignore +except ModuleNotFoundError as e: + fail(MISSING_PREREQUISITES.format(e.name)) + +# ==== CONFIG ==== + + +@dataclass +class ProjectSettings: + arch_str: str + objdump_executable: str + build_command: List[str] + map_format: str + mw_build_dir: str + baseimg: Optional[str] + myimg: Optional[str] + mapfile: Optional[str] + source_directories: Optional[List[str]] + source_extensions: List[str] + + +@dataclass +class Config: + arch: "ArchSettings" + + # Build/objdump options + diff_obj: bool + make: bool + source: Optional[str] + inlines: bool + max_function_size_lines: int + max_function_size_bytes: int + + # Display options + threeway: Optional[str] + base_shift: int + skip_lines: int + column_width: int + show_branches: bool + stop_jrra: bool + ignore_large_imms: bool + ignore_addr_diffs: bool + algorithm: str + + +def create_project_settings(settings: Dict[str, Any]) -> ProjectSettings: + return ProjectSettings( + arch_str=settings.get("arch", "mips"), + baseimg=settings.get("baseimg"), + myimg=settings.get("myimg"), + mapfile=settings.get("mapfile"), + build_command=settings.get( + "make_command", ["make", *settings.get("makeflags", [])] + ), + source_directories=settings.get("source_directories"), + source_extensions=settings.get( + "source_extensions", [".c", ".h", ".cpp", ".hpp", ".s"] + ), + objdump_executable=get_objdump_executable(settings.get("objdump_executable")), + map_format=settings.get("map_format", "gnu"), + mw_build_dir=settings.get("mw_build_dir", "build/"), + ) + + +def create_config(args: argparse.Namespace, project: ProjectSettings) -> Config: + return Config( + arch=get_arch(project.arch_str), + # Build/objdump options + diff_obj=args.diff_obj, + make=args.make, + source=args.source, + inlines=args.inlines, + max_function_size_lines=args.max_lines, + max_function_size_bytes=args.max_lines * 4, + # Display options + threeway=args.threeway, + base_shift=eval_int( + args.base_shift, "Failed to parse --base-shift (-S) argument as an integer." + ), + skip_lines=args.skip_lines, + column_width=args.column_width, + show_branches=args.show_branches, + stop_jrra=args.stop_jrra, + ignore_large_imms=args.ignore_large_imms, + ignore_addr_diffs=args.ignore_addr_diffs, + algorithm=args.algorithm, + ) + + +def get_objdump_executable(objdump_executable: Optional[str]) -> str: + if objdump_executable is not None: + return objdump_executable + + for objdump_cand in ["mips-linux-gnu-objdump", "mips64-elf-objdump"]: + try: + subprocess.check_call( + [objdump_cand, "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + return objdump_cand + except subprocess.CalledProcessError: + pass + except FileNotFoundError: + pass + + return fail( + "Missing binutils; please ensure mips-linux-gnu-objdump or mips64-elf-objdump exist, or configure objdump_executable." + ) + + +def get_arch(arch_str: str) -> "ArchSettings": + if arch_str == "mips": + return MIPS_SETTINGS + if arch_str == "aarch64": + return AARCH64_SETTINGS + if arch_str == "ppc": + return PPC_SETTINGS + return fail(f"Unknown architecture: {arch_str}") + + +COLOR_ROTATION: List[str] = [ + Fore.MAGENTA, + Fore.CYAN, + Fore.GREEN, + Fore.RED, + Fore.LIGHTYELLOW_EX, + Fore.LIGHTMAGENTA_EX, + Fore.LIGHTCYAN_EX, + Fore.LIGHTGREEN_EX, + Fore.LIGHTBLACK_EX, +] + +BUFFER_CMD: List[str] = ["tail", "-c", str(10 ** 9)] + +# -S truncates long lines instead of wrapping them +# -R interprets color escape sequences +# -i ignores case when searching +# -c something about how the screen gets redrawn; I don't remember the purpose +# -#6 makes left/right arrow keys scroll by 6 characters +LESS_CMD: List[str] = ["less", "-SRic", "-#6"] + +DEBOUNCE_DELAY: float = 0.1 + +# ==== LOGIC ==== + +ObjdumpCommand = Tuple[List[str], str, Optional[str]] + + +def maybe_eval_int(expr: str) -> Optional[int]: + try: + ret = ast.literal_eval(expr) + if not isinstance(ret, int): + raise Exception("not an integer") + return ret + except Exception: + return None + + +def eval_int(expr: str, emsg: str) -> int: + ret = maybe_eval_int(expr) + if ret is None: + fail(emsg) + return ret + + +def eval_line_num(expr: str) -> int: + return int(expr.strip().replace(":", ""), 16) + + +def run_make(target: str, project: ProjectSettings) -> None: + subprocess.check_call(project.build_command + [target]) + + +def run_make_capture_output( + target: str, project: ProjectSettings +) -> "subprocess.CompletedProcess[bytes]": + return subprocess.run( + project.build_command + [target], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + + +def restrict_to_function(dump: str, fn_name: str, config: Config) -> str: + out: List[str] = [] + search = f"<{fn_name}>:" + found = False + for line in dump.split("\n"): + if found: + if len(out) >= config.max_function_size_lines: + break + out.append(line) + elif search in line: + found = True + return "\n".join(out) + + +def maybe_get_objdump_source_flags(config: Config) -> List[str]: + if not config.source: + return [] + + flags = [ + "--source", + "--source-comment=│ ", + "-l", + ] + + if config.inlines: + flags.append("--inlines") + + return flags + + +def run_objdump(cmd: ObjdumpCommand, config: Config, project: ProjectSettings) -> str: + flags, target, restrict = cmd + out = subprocess.check_output( + [project.objdump_executable] + config.arch.arch_flags + flags + [target], + universal_newlines=True, + ) + if restrict is not None: + return restrict_to_function(out, restrict, config) + return out + + +def search_map_file( + fn_name: str, project: ProjectSettings +) -> Tuple[Optional[str], Optional[int]]: + if not project.mapfile: + fail(f"No map file configured; cannot find function {fn_name}.") + + try: + with open(project.mapfile) as f: + contents = f.read() + except Exception: + fail(f"Failed to open map file {project.mapfile} for reading.") + + if project.map_format == "gnu": + lines = contents.split("\n") + + try: + cur_objfile = None + ram_to_rom = None + cands = [] + last_line = "" + for line in lines: + if line.startswith(" .text"): + cur_objfile = line.split()[3] + if "load address" in line: + tokens = last_line.split() + line.split() + ram = int(tokens[1], 0) + rom = int(tokens[5], 0) + ram_to_rom = rom - ram + if line.endswith(" " + fn_name): + ram = int(line.split()[0], 0) + if cur_objfile is not None and ram_to_rom is not None: + cands.append((cur_objfile, ram + ram_to_rom)) + last_line = line + except Exception as e: + import traceback + + traceback.print_exc() + fail(f"Internal error while parsing map file") + + if len(cands) > 1: + fail(f"Found multiple occurrences of function {fn_name} in map file.") + if len(cands) == 1: + return cands[0] + elif project.map_format == "mw": + # ram elf rom object name + find = re.findall(re.compile(r' \S+ \S+ (\S+) (\S+) . ' + fn_name + r'(?: \(entry of \.(?:init|text)\))? \t(\S+)'), contents) + if len(find) > 1: + fail(f"Found multiple occurrences of function {fn_name} in map file.") + if len(find) == 1: + rom = int(find[0][1], 16) + objname = find[0][2] + # The metrowerks linker map format does not contain the full object path, + # so we must complete it manually. + objfiles = [ + os.path.join(dirpath, f) + for dirpath, _, filenames in os.walk(project.mw_build_dir) + for f in filenames + if f == objname + ] + if len(objfiles) > 1: + all_objects = "\n".join(objfiles) + fail(f"Found multiple objects of the same name {objname} in {project.mw_build_dir}, cannot determine which to diff against: \n{all_objects}") + if len(objfiles) == 1: + objfile = objfiles[0] + # TODO Currently the ram-rom conversion only works for diffing ELF + # executables, but it would likely be more convenient to diff DOLs. + # At this time it is recommended to always use -o when running the diff + # script as this mode does not make use of the ram-rom conversion. + return objfile, rom + else: + fail(f"Linker map format {project.map_format} unrecognised.") + return None, None + + +def dump_elf( + start: str, + end: Optional[str], + diff_elf_symbol: str, + config: Config, + project: ProjectSettings, +) -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: + if not project.baseimg or not project.myimg: + fail("Missing myimg/baseimg in config.") + if config.base_shift: + fail("--base-shift not compatible with -e") + + start_addr = eval_int(start, "Start address must be an integer expression.") + + if end is not None: + end_addr = eval_int(end, "End address must be an integer expression.") + else: + end_addr = start_addr + config.max_function_size_bytes + + flags1 = [ + f"--start-address={start_addr}", + f"--stop-address={end_addr}", + ] + + flags2 = [ + f"--disassemble={diff_elf_symbol}", + ] + + objdump_flags = ["-drz", "-j", ".text"] + return ( + project.myimg, + (objdump_flags + flags1, project.baseimg, None), + (objdump_flags + flags2 + maybe_get_objdump_source_flags(config), project.myimg, None), + ) + + +def dump_objfile( + start: str, end: Optional[str], config: Config, project: ProjectSettings +) -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: + if config.base_shift: + fail("--base-shift not compatible with -o") + if end is not None: + fail("end address not supported together with -o") + if start.startswith("0"): + fail("numerical start address not supported with -o; pass a function name") + + objfile, _ = search_map_file(start, project) + if not objfile: + fail("Not able to find .o file for function.") + + if config.make: + run_make(objfile, project) + + if not os.path.isfile(objfile): + fail(f"Not able to find .o file for function: {objfile} is not a file.") + + refobjfile = "expected/" + objfile + if not os.path.isfile(refobjfile): + fail(f'Please ensure an OK .o file exists at "{refobjfile}".') + + objdump_flags = ["-drz"] + return ( + objfile, + (objdump_flags, refobjfile, start), + (objdump_flags + maybe_get_objdump_source_flags(config), objfile, start), + ) + + +def dump_binary( + start: str, end: Optional[str], config: Config, project: ProjectSettings +) -> Tuple[str, ObjdumpCommand, ObjdumpCommand]: + if not project.baseimg or not project.myimg: + fail("Missing myimg/baseimg in config.") + if config.make: + run_make(project.myimg, project) + start_addr = maybe_eval_int(start) + if start_addr is None: + _, start_addr = search_map_file(start, project) + if start_addr is None: + fail("Not able to find function in map file.") + if end is not None: + end_addr = eval_int(end, "End address must be an integer expression.") + else: + end_addr = start_addr + config.max_function_size_bytes + objdump_flags = ["-Dz", "-bbinary", "-EB"] + flags1 = [ + f"--start-address={start_addr + config.base_shift}", + f"--stop-address={end_addr + config.base_shift}", + ] + flags2 = [f"--start-address={start_addr}", f"--stop-address={end_addr}"] + return ( + project.myimg, + (objdump_flags + flags1, project.baseimg, None), + (objdump_flags + flags2, project.myimg, None), + ) + + +def ansi_ljust(s: str, width: int) -> str: + """Like s.ljust(width), but accounting for ANSI colors.""" + needed: int = width - ansiwrap.ansilen(s) + if needed > 0: + return s + " " * needed + else: + return s + + +class DifferenceNormalizer: + def __init__(self, config: Config) -> None: + self.config = config + + def normalize(self, mnemonic: str, row: str) -> str: + """This should be called exactly once for each line.""" + row = self._normalize_arch_specific(mnemonic, row) + if self.config.ignore_large_imms: + row = re.sub(self.config.arch.re_large_imm, "", row) + return row + + def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: + return row + + +class DifferenceNormalizerAArch64(DifferenceNormalizer): + def __init__(self, config: Config) -> None: + super().__init__(config) + self._adrp_pair_registers: Set[str] = set() + + def _normalize_arch_specific(self, mnemonic: str, row: str) -> str: + if self.config.ignore_addr_diffs: + row = self._normalize_adrp_differences(mnemonic, row) + row = self._normalize_bl(mnemonic, row) + return row + + def _normalize_bl(self, mnemonic: str, row: str) -> str: + if mnemonic != "bl": + return row + + row, _ = split_off_branch(row) + return row + + def _normalize_adrp_differences(self, mnemonic: str, row: str) -> str: + """Identifies ADRP + LDR/ADD pairs that are used to access the GOT and + suppresses any immediate differences. + + Whenever an ADRP is seen, the destination register is added to the set of registers + that are part of an ADRP + LDR/ADD pair. Registers are removed from the set as soon + as they are used for an LDR or ADD instruction which completes the pair. + + This method is somewhat crude but should manage to detect most such pairs. + """ + row_parts = row.split("\t", 1) + if mnemonic == "adrp": + self._adrp_pair_registers.add(row_parts[1].strip().split(",")[0]) + row, _ = split_off_branch(row) + elif mnemonic == "ldr": + for reg in self._adrp_pair_registers: + # ldr xxx, [reg] + # ldr xxx, [reg, ] + if f", [{reg}" in row_parts[1]: + self._adrp_pair_registers.remove(reg) + return normalize_imms(row, AARCH64_SETTINGS) + elif mnemonic == "add": + for reg in self._adrp_pair_registers: + # add reg, reg, + if row_parts[1].startswith(f"{reg}, {reg}, "): + self._adrp_pair_registers.remove(reg) + return normalize_imms(row, AARCH64_SETTINGS) + + return row + + +@dataclass +class ArchSettings: + re_int: Pattern[str] + re_comment: Pattern[str] + re_reg: Pattern[str] + re_sprel: Pattern[str] + re_large_imm: Pattern[str] + re_imm: Pattern[str] + branch_instructions: Set[str] + instructions_with_address_immediates: Set[str] + forbidden: Set[str] = field(default_factory=lambda: set(string.ascii_letters + "_")) + arch_flags: List[str] = field(default_factory=list) + branch_likely_instructions: Set[str] = field(default_factory=set) + difference_normalizer: Type[DifferenceNormalizer] = DifferenceNormalizer + + +MIPS_BRANCH_LIKELY_INSTRUCTIONS = { + "beql", + "bnel", + "beqzl", + "bnezl", + "bgezl", + "bgtzl", + "blezl", + "bltzl", + "bc1tl", + "bc1fl", +} +MIPS_BRANCH_INSTRUCTIONS = MIPS_BRANCH_LIKELY_INSTRUCTIONS.union( + { + "b", + "beq", + "bne", + "beqz", + "bnez", + "bgez", + "bgtz", + "blez", + "bltz", + "bc1t", + "bc1f", + } +) + +AARCH64_BRANCH_INSTRUCTIONS = { + "bl", + "b", + "b.eq", + "b.ne", + "b.cs", + "b.hs", + "b.cc", + "b.lo", + "b.mi", + "b.pl", + "b.vs", + "b.vc", + "b.hi", + "b.ls", + "b.ge", + "b.lt", + "b.gt", + "b.le", + "cbz", + "cbnz", + "tbz", + "tbnz", +} + +PPC_BRANCH_INSTRUCTIONS = { + "b", + "beq", + "beq+", + "beq-", + "bne", + "bne+", + "bne-", + "blt", + "blt+", + "blt-", + "ble", + "ble+", + "ble-", + "bdnz", + "bdnz+", + "bdnz-", + "bge", + "bge+", + "bge-", + "bgt", + "bgt+", + "bgt-", +} + +MIPS_SETTINGS = ArchSettings( + re_int=re.compile(r"[0-9]+"), + re_comment=re.compile(r"<.*?>"), + re_reg=re.compile( + r"\$?\b(a[0-3]|t[0-9]|s[0-8]|at|v[01]|f[12]?[0-9]|f3[01]|k[01]|fp|ra|zero)\b" + ), + re_sprel=re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\(sp\)"), + re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), + re_imm=re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)"), + arch_flags=["-m", "mips:4300"], + branch_likely_instructions=MIPS_BRANCH_LIKELY_INSTRUCTIONS, + branch_instructions=MIPS_BRANCH_LIKELY_INSTRUCTIONS, + instructions_with_address_immediates=MIPS_BRANCH_INSTRUCTIONS.union({"jal", "j"}), +) + +AARCH64_SETTINGS = ArchSettings( + re_int=re.compile(r"[0-9]+"), + re_comment=re.compile(r"(<.*?>|//.*$)"), + # GPRs and FP registers: X0-X30, W0-W30, [DSHQ]0..31 + # The zero registers and SP should not be in this list. + re_reg=re.compile(r"\$?\b([dshq][12]?[0-9]|[dshq]3[01]|[xw][12]?[0-9]|[xw]30)\b"), + re_sprel=re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b"), + re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), + re_imm=re.compile(r"(?|//.*$)"), + re_reg=re.compile(r"\$?\b([rf][0-9]+)\b"), + re_sprel=re.compile(r"(?<=,)(-?[0-9]+|-?0x[0-9a-f]+)\(r1\)"), + re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), + re_imm=re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(r1)|[^@]*@(ha|h|lo)"), + arch_flags=["-mpowerpc", "-M", "gekko"], + branch_instructions=PPC_BRANCH_INSTRUCTIONS, + instructions_with_address_immediates=PPC_BRANCH_INSTRUCTIONS.union({"bl"}), +) + + +def hexify_int(row: str, pat: Match[str], arch: ArchSettings) -> str: + full = pat.group(0) + if len(full) <= 1: + # leave one-digit ints alone + return full + start, end = pat.span() + if start and row[start - 1] in arch.forbidden: + return full + if end < len(row) and row[end] in arch.forbidden: + return full + return hex(int(full)) + + +def parse_relocated_line(line: str) -> Tuple[str, str, str]: + try: + ind2 = line.rindex(",") + except ValueError: + try: + ind2 = line.rindex("\t") + except ValueError: + ind2 = line.rindex(" ") + before = line[: ind2 + 1] + after = line[ind2 + 1 :] + ind2 = after.find("(") + if ind2 == -1: + imm, after = after, "" + else: + imm, after = after[:ind2], after[ind2:] + if imm == "0x0": + imm = "0" + return before, imm, after + + +def process_mips_reloc(row: str, prev: str, arch: ArchSettings) -> str: + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + if imm != "0": + # MIPS uses relocations with addends embedded in the code as immediates. + # If there is an immediate, show it as part of the relocation. Ideally + # we'd show this addend in both %lo/%hi, but annoyingly objdump's output + # doesn't include enough information to pair up %lo's and %hi's... + # TODO: handle unambiguous cases where all addends for a symbol are the + # same, or show "+???". + mnemonic = prev.split()[0] + if ( + mnemonic in arch.instructions_with_address_immediates + and not imm.startswith("0x") + ): + imm = "0x" + imm + repl += "+" + imm if int(imm, 0) > 0 else imm + if "R_MIPS_LO16" in row: + repl = f"%lo({repl})" + elif "R_MIPS_HI16" in row: + # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a + # correct addend for each, but objdump doesn't give us the order of + # the relocations, so we can't find the right LO16. :( + repl = f"%hi({repl})" + elif "R_MIPS_26" in row: + # Function calls + pass + elif "R_MIPS_PC16" in row: + # Branch to glabel. This gives confusing output, but there's not much + # we can do here. + pass + else: + assert False, f"unknown relocation type '{row}' for line '{prev}'" + return before + repl + after + + +def process_ppc_reloc(row: str, prev: str) -> str: + assert any( + r in row for r in ["R_PPC_REL24", "R_PPC_ADDR16", "R_PPC_EMB_SDA21"] + ), f"unknown relocation type '{row}' for line '{prev}'" + before, imm, after = parse_relocated_line(prev) + repl = row.split()[-1] + if "R_PPC_REL24" in row: + # function calls + pass + elif "R_PPC_ADDR16_HI" in row: + # absolute hi of addr + repl = f"{repl}@h" + elif "R_PPC_ADDR16_HA" in row: + # adjusted hi of addr + repl = f"{repl}@ha" + elif "R_PPC_ADDR16_LO" in row: + # lo of addr + repl = f"{repl}@l" + elif "R_PPC_ADDR16" in row: + # 16-bit absolute addr + if "+0x7" in repl: + # remove the very large addends as they are an artifact of (label-_SDA(2)_BASE_) + # computations and are unimportant in a diff setting. + if int(repl.split("+")[1], 16) > 0x70000000: + repl = repl.split("+")[0] + elif "R_PPC_EMB_SDA21" in row: + # small data area + pass + return before + repl + after + + +def pad_mnemonic(line: str) -> str: + if "\t" not in line: + return line + mn, args = line.split("\t", 1) + return f"{mn:<7s} {args}" + + +@dataclass +class Line: + mnemonic: str + diff_row: str + original: str + normalized_original: str + line_num: str + branch_target: Optional[str] + source_lines: List[str] + comment: Optional[str] + + +def process(lines: List[str], config: Config) -> List[Line]: + arch = config.arch + normalizer = arch.difference_normalizer(config) + skip_next = False + source_lines = [] + if not config.diff_obj: + lines = lines[7:] + if lines and not lines[-1]: + lines.pop() + + output: List[Line] = [] + stop_after_delay_slot = False + for row in lines: + if config.diff_obj and (">:" in row or not row): + continue + + if config.source and (row and row[0] != " "): + source_lines.append(row) + continue + + if "R_AARCH64_" in row: + # TODO: handle relocation + continue + + if "R_MIPS_" in row: + # N.B. Don't transform the diff rows, they already ignore immediates + # if output[-1].diff_row != "": + # output[-1] = output[-1].replace(diff_row=process_mips_reloc(row, output[-1].row_with_imm, arch)) + new_original = process_mips_reloc(row, output[-1].original, arch) + output[-1] = replace(output[-1], original=new_original) + continue + + if "R_PPC_" in row: + new_original = process_ppc_reloc(row, output[-1].original) + output[-1] = replace(output[-1], original=new_original) + continue + + m_comment = re.search(arch.re_comment, row) + comment = m_comment[0] if m_comment else None + row = re.sub(arch.re_comment, "", row) + row = row.rstrip() + tabs = row.split("\t") + row = "\t".join(tabs[2:]) + line_num = tabs[0].strip() + + if "\t" in row: + row_parts = row.split("\t", 1) + else: + # powerpc-eabi-objdump doesn't use tabs + row_parts = [part.lstrip() for part in row.split(" ", 1)] + mnemonic = row_parts[0].strip() + + if mnemonic not in arch.instructions_with_address_immediates: + row = re.sub(arch.re_int, lambda m: hexify_int(row, m, arch), row) + original = row + normalized_original = normalizer.normalize(mnemonic, original) + if skip_next: + skip_next = False + row = "" + mnemonic = "" + if mnemonic in arch.branch_likely_instructions: + skip_next = True + row = re.sub(arch.re_reg, "", row) + row = re.sub(arch.re_sprel, "addr(sp)", row) + row_with_imm = row + if mnemonic in arch.instructions_with_address_immediates: + row = row.strip() + row, _ = split_off_branch(row) + row += "" + else: + row = normalize_imms(row, arch) + + branch_target = None + if mnemonic in arch.branch_instructions: + target = int(row_parts[1].strip().split(",")[-1], 16) + if mnemonic in arch.branch_likely_instructions: + target -= 4 + branch_target = hex(target)[2:] + + output.append( + Line( + mnemonic=mnemonic, + diff_row=row, + original=original, + normalized_original=normalized_original, + line_num=line_num, + branch_target=branch_target, + source_lines=source_lines, + comment=comment, + ) + ) + source_lines = [] + + if config.stop_jrra and mnemonic == "jr" and row_parts[1].strip() == "ra": + stop_after_delay_slot = True + elif stop_after_delay_slot: + break + + return output + + +class SymbolColorer: + symbol_colors: Dict[str, str] + + def __init__(self, base_index: int) -> None: + self.color_index = base_index + self.symbol_colors = {} + + def color_symbol(self, s: str, t: Optional[str] = None) -> str: + try: + color = self.symbol_colors[s] + except: + color = COLOR_ROTATION[self.color_index % len(COLOR_ROTATION)] + self.color_index += 1 + self.symbol_colors[s] = color + t = t or s + return f"{color}{t}{Fore.RESET}" + + +def normalize_imms(row: str, arch: ArchSettings) -> str: + return re.sub(arch.re_imm, "", row) + + +def normalize_stack(row: str, arch: ArchSettings) -> str: + return re.sub(arch.re_sprel, "addr(sp)", row) + + +def split_off_branch(line: str) -> Tuple[str, str]: + parts = line.split(",") + if len(parts) < 2: + parts = line.split(None, 1) + off = len(line) - len(parts[-1]) + return line[:off], line[off:] + + +ColorFunction = Callable[[str], str] + + +def color_fields( + pat: Pattern[str], + out1: str, + out2: str, + color1: ColorFunction, + color2: Optional[ColorFunction] = None, +) -> Tuple[str, str]: + diffs = [ + of.group() != nf.group() + for (of, nf) in zip(pat.finditer(out1), pat.finditer(out2)) + ] + + it = iter(diffs) + + def maybe_color(color: ColorFunction, s: str) -> str: + return color(s) if next(it, False) else f"{Style.RESET_ALL}{s}" + + out1 = pat.sub(lambda m: maybe_color(color1, m.group()), out1) + it = iter(diffs) + out2 = pat.sub(lambda m: maybe_color(color2 or color1, m.group()), out2) + + return out1, out2 + + +def color_branch_imms(br1: str, br2: str) -> Tuple[str, str]: + if br1 != br2: + br1 = f"{Fore.LIGHTBLUE_EX}{br1}{Style.RESET_ALL}" + br2 = f"{Fore.LIGHTBLUE_EX}{br2}{Style.RESET_ALL}" + return br1, br2 + + +def diff_sequences_difflib( + seq1: List[str], seq2: List[str] +) -> List[Tuple[str, int, int, int, int]]: + differ = difflib.SequenceMatcher(a=seq1, b=seq2, autojunk=False) + return differ.get_opcodes() + + +def diff_sequences( + seq1: List[str], seq2: List[str], algorithm: str +) -> List[Tuple[str, int, int, int, int]]: + if ( + algorithm != "levenshtein" + or len(seq1) * len(seq2) > 4 * 10 ** 8 + or len(seq1) + len(seq2) >= 0x110000 + ): + return diff_sequences_difflib(seq1, seq2) + + # The Levenshtein library assumes that we compare strings, not lists. Convert. + # (Per the check above we know we have fewer than 0x110000 unique elements, so chr() works.) + remapping: Dict[str, str] = {} + + def remap(seq: List[str]) -> str: + seq = seq[:] + for i in range(len(seq)): + val = remapping.get(seq[i]) + if val is None: + val = chr(len(remapping)) + remapping[seq[i]] = val + seq[i] = val + return "".join(seq) + + rem1 = remap(seq1) + rem2 = remap(seq2) + import Levenshtein # type: ignore + + return Levenshtein.opcodes(rem1, rem2) # type: ignore + + +def diff_lines( + lines1: List[Line], + lines2: List[Line], + algorithm: str, +) -> List[Tuple[Optional[Line], Optional[Line]]]: + ret = [] + for (tag, i1, i2, j1, j2) in diff_sequences( + [line.mnemonic for line in lines1], + [line.mnemonic for line in lines2], + algorithm, + ): + for line1, line2 in itertools.zip_longest(lines1[i1:i2], lines2[j1:j2]): + if tag == "replace": + if line1 is None: + tag = "insert" + elif line2 is None: + tag = "delete" + elif tag == "insert": + assert line1 is None + elif tag == "delete": + assert line2 is None + ret.append((line1, line2)) + + return ret + + +class OutputLine: + base: Optional[str] + fmt2: str + key2: Optional[str] + + def __init__(self, base: Optional[str], fmt2: str, key2: Optional[str]) -> None: + self.base = base + self.fmt2 = fmt2 + self.key2 = key2 + + def __eq__(self, other: object) -> bool: + if not isinstance(other, OutputLine): + return NotImplemented + return self.key2 == other.key2 + + def __hash__(self) -> int: + return hash(self.key2) + + +def do_diff(basedump: str, mydump: str, config: Config) -> List[OutputLine]: + if config.source: + import cxxfilt # type: ignore + arch = config.arch + output: List[OutputLine] = [] + + lines1 = process(basedump.split("\n"), config) + lines2 = process(mydump.split("\n"), config) + + sc1 = SymbolColorer(0) + sc2 = SymbolColorer(0) + sc3 = SymbolColorer(4) + sc4 = SymbolColorer(4) + sc5 = SymbolColorer(0) + sc6 = SymbolColorer(0) + bts1: Set[str] = set() + bts2: Set[str] = set() + + if config.show_branches: + for (lines, btset, sc) in [ + (lines1, bts1, sc5), + (lines2, bts2, sc6), + ]: + for line in lines: + bt = line.branch_target + if bt is not None: + btset.add(bt + ":") + sc.color_symbol(bt + ":") + + for (line1, line2) in diff_lines(lines1, lines2, config.algorithm): + line_color1 = line_color2 = sym_color = Fore.RESET + line_prefix = " " + if line1 and line2 and line1.diff_row == line2.diff_row: + if line1.normalized_original == line2.normalized_original: + out1 = line1.original + out2 = line2.original + elif line1.diff_row == "": + out1 = f"{Style.BRIGHT}{Fore.LIGHTBLACK_EX}{line1.original}" + out2 = f"{Style.BRIGHT}{Fore.LIGHTBLACK_EX}{line2.original}" + else: + mnemonic = line1.original.split()[0] + out1, out2 = line1.original, line2.original + branch1 = branch2 = "" + if mnemonic in arch.instructions_with_address_immediates: + out1, branch1 = split_off_branch(line1.original) + out2, branch2 = split_off_branch(line2.original) + branchless1 = out1 + branchless2 = out2 + out1, out2 = color_fields(arch.re_imm, out1, out2, lambda s: f"{Fore.LIGHTBLUE_EX}{s}{Style.RESET_ALL}") + + same_relative_target = False + if line1.branch_target is not None and line2.branch_target is not None: + relative_target1 = eval_line_num(line1.branch_target) - eval_line_num(line1.line_num) + relative_target2 = eval_line_num(line2.branch_target) - eval_line_num(line2.line_num) + same_relative_target = relative_target1 == relative_target2 + + if not same_relative_target: + branch1, branch2 = color_branch_imms(branch1, branch2) + + out1 += branch1 + out2 += branch2 + if normalize_imms(branchless1, arch) == normalize_imms(branchless2, arch): + if not same_relative_target: + # only imms differences + sym_color = Fore.LIGHTBLUE_EX + line_prefix = "i" + else: + out1, out2 = color_fields(arch.re_sprel, out1, out2, sc3.color_symbol, sc4.color_symbol) + if normalize_stack(branchless1, arch) == normalize_stack(branchless2, arch): + # only stack differences (luckily stack and imm + # differences can't be combined in MIPS, so we + # don't have to think about that case) + sym_color = Fore.YELLOW + line_prefix = "s" + else: + # regs differences and maybe imms as well + out1, out2 = color_fields(arch.re_reg, out1, out2, sc1.color_symbol, sc2.color_symbol) + line_color1 = line_color2 = sym_color = Fore.YELLOW + line_prefix = "r" + elif line1 and line2: + line_prefix = "|" + line_color1 = Fore.LIGHTBLUE_EX + line_color2 = Fore.LIGHTBLUE_EX + sym_color = Fore.LIGHTBLUE_EX + out1 = line1.original + out2 = line2.original + elif line1: + line_prefix = "<" + line_color1 = sym_color = Fore.RED + out1 = line1.original + out2 = "" + elif line2: + line_prefix = ">" + line_color2 = sym_color = Fore.GREEN + out1 = "" + out2 = line2.original + + if config.source and line2 and line2.comment: + out2 += f" {line2.comment}" + + def format_part( + out: str, + line: Optional[Line], + line_color: str, + btset: Set[str], + sc: SymbolColorer, + ) -> Optional[str]: + if line is None: + return None + in_arrow = " " + out_arrow = "" + if config.show_branches: + if line.line_num in btset: + in_arrow = sc.color_symbol(line.line_num, "~>") + line_color + if line.branch_target is not None: + out_arrow = " " + sc.color_symbol(line.branch_target + ":", "~>") + out = pad_mnemonic(out) + return f"{line_color}{line.line_num} {in_arrow} {out}{Style.RESET_ALL}{out_arrow}" + + part1 = format_part(out1, line1, line_color1, bts1, sc5) + part2 = format_part(out2, line2, line_color2, bts2, sc6) + key2 = line2.original if line2 else None + + mid = f"{sym_color}{line_prefix}" + + if line2: + for source_line in line2.source_lines: + color = Style.DIM + # File names and function names + if source_line and source_line[0] != "│": + color += Style.BRIGHT + # Function names + if source_line.endswith("():"): + # Underline. Colorama does not provide this feature, unfortunately. + color += "\u001b[4m" + try: + source_line = cxxfilt.demangle( + source_line[:-3], external_only=False + ) + except: + pass + output.append( + OutputLine( + None, + f" {color}{source_line}{Style.RESET_ALL}", + source_line, + ) + ) + + fmt2 = mid + " " + (part2 or "") + output.append(OutputLine(part1, fmt2, key2)) + + return output + + +def chunk_diff(diff: List[OutputLine]) -> List[Union[List[OutputLine], OutputLine]]: + cur_right: List[OutputLine] = [] + chunks: List[Union[List[OutputLine], OutputLine]] = [] + for output_line in diff: + if output_line.base is not None: + chunks.append(cur_right) + chunks.append(output_line) + cur_right = [] + else: + cur_right.append(output_line) + chunks.append(cur_right) + return chunks + + +def format_diff( + old_diff: List[OutputLine], new_diff: List[OutputLine], config: Config +) -> Tuple[str, List[str]]: + old_chunks = chunk_diff(old_diff) + new_chunks = chunk_diff(new_diff) + output: List[Tuple[str, OutputLine, OutputLine]] = [] + assert len(old_chunks) == len(new_chunks), "same target" + empty = OutputLine("", "", None) + for old_chunk, new_chunk in zip(old_chunks, new_chunks): + if isinstance(old_chunk, list): + assert isinstance(new_chunk, list) + if not old_chunk and not new_chunk: + # Most of the time lines sync up without insertions/deletions, + # and there's no interdiffing to be done. + continue + differ = difflib.SequenceMatcher(a=old_chunk, b=new_chunk, autojunk=False) + for (tag, i1, i2, j1, j2) in differ.get_opcodes(): + if tag in ["equal", "replace"]: + for i, j in zip(range(i1, i2), range(j1, j2)): + output.append(("", old_chunk[i], new_chunk[j])) + if tag in ["insert", "replace"]: + for j in range(j1 + i2 - i1, j2): + output.append(("", empty, new_chunk[j])) + if tag in ["delete", "replace"]: + for i in range(i1 + j2 - j1, i2): + output.append(("", old_chunk[i], empty)) + else: + assert isinstance(new_chunk, OutputLine) + assert new_chunk.base + # old_chunk.base and new_chunk.base have the same text since + # both diffs are based on the same target, but they might + # differ in color. Use the new version. + output.append((new_chunk.base, old_chunk, new_chunk)) + + # TODO: status line, with e.g. approximate permuter score? + width = config.column_width + if config.threeway: + header_line = "TARGET".ljust(width) + " CURRENT".ljust(width) + " PREVIOUS" + diff_lines = [ + ansi_ljust(base, width) + + ansi_ljust(new.fmt2, width) + + (old.fmt2 or "-" if old != new else "") + for (base, old, new) in output + ] + else: + header_line = "" + diff_lines = [ + ansi_ljust(base, width) + new.fmt2 + for (base, old, new) in output + if base or new.key2 is not None + ] + return header_line, diff_lines + + +def debounced_fs_watch( + targets: List[str], + outq: "queue.Queue[Optional[float]]", + config: Config, + project: ProjectSettings, +) -> None: + import watchdog.events # type: ignore + import watchdog.observers # type: ignore + + class WatchEventHandler(watchdog.events.FileSystemEventHandler): # type: ignore + def __init__( + self, queue: "queue.Queue[float]", file_targets: List[str] + ) -> None: + self.queue = queue + self.file_targets = file_targets + + def on_modified(self, ev: object) -> None: + if isinstance(ev, watchdog.events.FileModifiedEvent): + self.changed(ev.src_path) + + def on_moved(self, ev: object) -> None: + if isinstance(ev, watchdog.events.FileMovedEvent): + self.changed(ev.dest_path) + + def should_notify(self, path: str) -> bool: + for target in self.file_targets: + if path == target: + return True + if config.make and any( + path.endswith(suffix) for suffix in project.source_extensions + ): + return True + return False + + def changed(self, path: str) -> None: + if self.should_notify(path): + self.queue.put(time.time()) + + def debounce_thread() -> NoReturn: + listenq: "queue.Queue[float]" = queue.Queue() + file_targets: List[str] = [] + event_handler = WatchEventHandler(listenq, file_targets) + observer = watchdog.observers.Observer() + observed = set() + for target in targets: + if os.path.isdir(target): + observer.schedule(event_handler, target, recursive=True) + else: + file_targets.append(target) + target = os.path.dirname(target) or "." + if target not in observed: + observed.add(target) + observer.schedule(event_handler, target) + observer.start() + while True: + t = listenq.get() + more = True + while more: + delay = t + DEBOUNCE_DELAY - time.time() + if delay > 0: + time.sleep(delay) + # consume entire queue + more = False + try: + while True: + t = listenq.get(block=False) + more = True + except queue.Empty: + pass + outq.put(t) + + th = threading.Thread(target=debounce_thread, daemon=True) + th.start() + + +class Display: + basedump: str + mydump: str + config: Config + emsg: Optional[str] + last_diff_output: Optional[List[OutputLine]] + pending_update: Optional[Tuple[str, bool]] + ready_queue: "queue.Queue[None]" + watch_queue: "queue.Queue[Optional[float]]" + less_proc: "Optional[subprocess.Popen[bytes]]" + + def __init__(self, basedump: str, mydump: str, config: Config) -> None: + self.config = config + self.basedump = basedump + self.mydump = mydump + self.emsg = None + self.last_diff_output = None + + def run_less(self) -> "Tuple[subprocess.Popen[bytes], subprocess.Popen[bytes]]": + if self.emsg is not None: + output = self.emsg + else: + diff_output = do_diff(self.basedump, self.mydump, self.config) + last_diff_output = self.last_diff_output or diff_output + if self.config.threeway != "base" or not self.last_diff_output: + self.last_diff_output = diff_output + header, diff_lines = format_diff(last_diff_output, diff_output, self.config) + header_lines = [header] if header else [] + output = "\n".join(header_lines + diff_lines[self.config.skip_lines :]) + + # Pipe the output through 'tail' and only then to less, to ensure the + # write call doesn't block. ('tail' has to buffer all its input before + # it starts writing.) This also means we don't have to deal with pipe + # closure errors. + buffer_proc = subprocess.Popen( + BUFFER_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) + less_proc = subprocess.Popen(LESS_CMD, stdin=buffer_proc.stdout) + assert buffer_proc.stdin + assert buffer_proc.stdout + buffer_proc.stdin.write(output.encode()) + buffer_proc.stdin.close() + buffer_proc.stdout.close() + return (buffer_proc, less_proc) + + def run_sync(self) -> None: + proca, procb = self.run_less() + procb.wait() + proca.wait() + + def run_async(self, watch_queue: "queue.Queue[Optional[float]]") -> None: + self.watch_queue = watch_queue + self.ready_queue = queue.Queue() + self.pending_update = None + dthread = threading.Thread(target=self.display_thread) + dthread.start() + self.ready_queue.get() + + def display_thread(self) -> None: + proca, procb = self.run_less() + self.less_proc = procb + self.ready_queue.put(None) + while True: + ret = procb.wait() + proca.wait() + self.less_proc = None + if ret != 0: + # fix the terminal + os.system("tput reset") + if ret != 0 and self.pending_update is not None: + # killed by program with the intent to refresh + msg, error = self.pending_update + self.pending_update = None + if not error: + self.mydump = msg + self.emsg = None + else: + self.emsg = msg + proca, procb = self.run_less() + self.less_proc = procb + self.ready_queue.put(None) + else: + # terminated by user, or killed + self.watch_queue.put(None) + self.ready_queue.put(None) + break + + def progress(self, msg: str) -> None: + # Write message to top-left corner + sys.stdout.write("\x1b7\x1b[1;1f{}\x1b8".format(msg + " ")) + sys.stdout.flush() + + def update(self, text: str, error: bool) -> None: + if not error and not self.emsg and text == self.mydump: + self.progress("Unchanged. ") + return + self.pending_update = (text, error) + if not self.less_proc: + return + self.less_proc.kill() + self.ready_queue.get() + + def terminate(self) -> None: + if not self.less_proc: + return + self.less_proc.kill() + self.ready_queue.get() + + +def main() -> None: + args = parser.parse_args() + + # Apply project-specific configuration. + settings: Dict[str, Any] = {} + diff_settings.apply(settings, args) # type: ignore + project = create_project_settings(settings) + + config = create_config(args, project) + + if config.algorithm == "levenshtein": + try: + import Levenshtein + except ModuleNotFoundError as e: + fail(MISSING_PREREQUISITES.format(e.name)) + + if config.source: + try: + import cxxfilt + except ModuleNotFoundError as e: + fail(MISSING_PREREQUISITES.format(e.name)) + + if config.threeway and not args.watch: + fail("Threeway diffing requires -w.") + + if args.diff_elf_symbol: + make_target, basecmd, mycmd = dump_elf(args.start, args.end, args.diff_elf_symbol, config, project) + elif config.diff_obj: + make_target, basecmd, mycmd = dump_objfile(args.start, args.end, config, project) + else: + make_target, basecmd, mycmd = dump_binary(args.start, args.end, config, project) + + map_build_target_fn = getattr(diff_settings, "map_build_target", None) + if map_build_target_fn: + make_target = map_build_target_fn(make_target=make_target) + + if args.write_asm is not None: + mydump = run_objdump(mycmd, config, project) + with open(args.write_asm, "w") as f: + f.write(mydump) + print(f"Wrote assembly to {args.write_asm}.") + sys.exit(0) + + if args.base_asm is not None: + with open(args.base_asm) as f: + basedump = f.read() + else: + basedump = run_objdump(basecmd, config, project) + + mydump = run_objdump(mycmd, config, project) + + display = Display(basedump, mydump, config) + + if not args.watch: + display.run_sync() + else: + if not args.make: + yn = input( + "Warning: watch-mode (-w) enabled without auto-make (-m). " + "You will have to run make manually. Ok? (Y/n) " + ) + if yn.lower() == "n": + return + if args.make: + watch_sources = None + watch_sources_for_target_fn = getattr( + diff_settings, "watch_sources_for_target", None + ) + if watch_sources_for_target_fn: + watch_sources = watch_sources_for_target_fn(make_target) + watch_sources = watch_sources or project.source_directories + if not watch_sources: + fail("Missing source_directories config, don't know what to watch.") + else: + watch_sources = [make_target] + q: "queue.Queue[Optional[float]]" = queue.Queue() + debounced_fs_watch(watch_sources, q, config, project) + display.run_async(q) + last_build = 0.0 + try: + while True: + t = q.get() + if t is None: + break + if t < last_build: + continue + last_build = time.time() + if args.make: + display.progress("Building...") + ret = run_make_capture_output(make_target, project) + if ret.returncode != 0: + display.update( + ret.stderr.decode("utf-8-sig", "replace") + or ret.stdout.decode("utf-8-sig", "replace"), + error=True, + ) + continue + mydump = run_objdump(mycmd, config, project) + display.update(mydump, error=False) + except KeyboardInterrupt: + display.terminate() + + +if __name__ == "__main__": + main() diff --git a/diff_settings.py b/diff_settings.py new file mode 100644 index 000000000..3ea8e8d96 --- /dev/null +++ b/diff_settings.py @@ -0,0 +1,13 @@ +import os + +def apply(config, args): + config['mapfile'] = 'artifacts/target/pal/main.map' + config['myimg'] = 'artifacts/target/pal/main.dol' + config['baseimg'] = 'artifacts/orig/pal/main.dol' + config['makeflags'] = [] + config['source_directories'] = ['source'] + config["arch"] = "ppc" + config["map_format"] = "mw" # gnu or mw + config["mw_build_dir"] = "out" # only needed for mw map format + config["makeflags"] = [] + config["objdump_executable"] = os.environ['DEVKITPPC'] + "/bin/powerpc-eabi-objdump.exe" \ No newline at end of file From 4740cb171d3fec6a1d1c38aba829257c7438c76d Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 7 Jul 2021 12:40:10 -0600 Subject: [PATCH 091/477] :construction: Fix diffing by symbol --- diff.py | 7 +++++++ mkwutil/dol.py | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/diff.py b/diff.py index ff5df0b43..10a96385c 100644 --- a/diff.py +++ b/diff.py @@ -544,6 +544,7 @@ def search_map_file( if len(find) > 1: fail(f"Found multiple occurrences of function {fn_name} in map file.") if len(find) == 1: + ram = int(find[0][0], 16) rom = int(find[0][1], 16) objname = find[0][2] # The metrowerks linker map format does not contain the full object path, @@ -563,6 +564,12 @@ def search_map_file( # executables, but it would likely be more convenient to diff DOLs. # At this time it is recommended to always use -o when running the diff # script as this mode does not make use of the ram-rom conversion. + + from mkwutil import dol + + maindol = dol.DolBinary(project.myimg) + return objfile, maindol.virtual_to_rom(ram) + return objfile, rom else: fail(f"Linker map format {project.map_format} unrecognised.") diff --git a/mkwutil/dol.py b/mkwutil/dol.py index ed3dcf19e..2b4f62d63 100644 --- a/mkwutil/dol.py +++ b/mkwutil/dol.py @@ -1,4 +1,5 @@ import struct +from itertools import chain read_u32 = lambda f: struct.unpack(">L", f.read(4))[0] @@ -29,8 +30,8 @@ class DolBinary: def __init__(self, file): file = open(file, "rb") - text_ofs = [read_u32(file) for _ in range(7)] - data_ofs = [read_u32(file) for _ in range(11)] + self.text_ofs = [read_u32(file) for _ in range(7)] + self.data_ofs = [read_u32(file) for _ in range(11)] text_vaddr = [read_u32(file) for _ in range(7)] data_vaddr = [read_u32(file) for _ in range(11)] @@ -55,7 +56,7 @@ def __init__(self, file): for i in range(7): if not self.text_size[i]: continue - file.seek(text_ofs[i]) + file.seek(self.text_ofs[i]) offset = text_vaddr[i] - self.image_base self.image[offset : offset + self.text_size[i]] = file.read( self.text_size[i] @@ -64,7 +65,7 @@ def __init__(self, file): for i in range(11): if not self.data_size[i]: continue - file.seek(data_ofs[i]) + file.seek(self.data_ofs[i]) offset = data_vaddr[i] - self.image_base self.image[offset : offset + self.data_size[i]] = file.read( self.data_size[i] @@ -79,3 +80,10 @@ def get_data_segment(self, num): """Reads the binary content of a data segment.""" offset = self.data_segs[num].begin - self.image_base return self.image[offset : offset + self.data_size[num]] + + def virtual_to_rom(self, vaddr): + for seg, ofs in chain(zip(self.text_segs, self.text_ofs), zip(self.data_segs, self.data_ofs)): + if vaddr >= seg.begin and vaddr <= seg.end: + return vaddr - seg.begin + ofs + + assert not "Address not found" \ No newline at end of file From c5e883b259dd427477782e89568dbd2dffd70fa5 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 7 Jul 2021 14:15:03 -0600 Subject: [PATCH 092/477] :sparkles: Compile sources in parallel --- build.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/build.py b/build.py index 9337bc86c..d1381ba45 100644 --- a/build.py +++ b/build.py @@ -4,6 +4,9 @@ import subprocess import sys +from multiprocessing.dummy import Pool as ThreadPool +import multiprocessing + from mkwutil.gen_asm import read_slices from mkwutil.verify_object_file import verify_object_file from mkwutil.gen_lcf import gen_lcf @@ -100,7 +103,7 @@ def windows_binary(path): ) -def compile_source(src, dst, version="default", additional="-ipa file"): +def compile_source_impl(src, dst, version="default", additional="-ipa file"): # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" if VERBOSE: @@ -113,6 +116,24 @@ def compile_source(src, dst, version="default", additional="-ipa file"): else: print("# Skipping slices verification on", src) +gSourceQueue = [] + +def compile_queued_sources(): + max_hw_concurrency = multiprocessing.cpu_count() + print("max_hw_concurrency=%s" % max_hw_concurrency) + + pool = ThreadPool(max_hw_concurrency) + + pool.map(lambda s: compile_source_impl(*s), gSourceQueue) + + pool.close() + pool.join() + + gSourceQueue.clear() + +# Queued +def compile_source(src, dst, version="default", additional="-ipa file"): + gSourceQueue.append((src, dst, version, additional)) def assemble(dst, src): subprocess.run([GAS, src, "-mgekko", "-Iasm", "-o", dst], check=True, text=True) @@ -142,6 +163,8 @@ def compile_sources(): with open("sources.py", "r") as sourcespy: exec(sourcespy.read()) + compile_queued_sources() + asm_files = [ str(x.relative_to(os.getcwd())) for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s") From 07d1548910a4c674346929d1c0e58ba6a45a4d37 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 7 Jul 2021 17:49:48 -0600 Subject: [PATCH 093/477] :art: Color build output --- build.py | 25 ++++++++++++++++++------- mkwutil/verify_main_dol.py | 7 ++++--- mkwutil/verify_object_file.py | 6 +++++- mkwutil/verify_staticr_rel.py | 8 ++++---- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/build.py b/build.py index d1381ba45..625af8eee 100644 --- a/build.py +++ b/build.py @@ -16,6 +16,10 @@ from mkwutil.verify_staticr_rel import verify_rel from mkwutil.percent_decompiled import percent_decompiled +import colorama +from colorama import Fore, Style + +colorama.init() dol_slices = read_slices("pack/dol_slices.csv", verbose=False) dol_slices = { sl.obj_file : sl for sl in dol_slices } @@ -109,18 +113,12 @@ def compile_source_impl(src, dst, version="default", additional="-ipa file"): if VERBOSE: print(command) subprocess.run(command, check=True) - # Verify ELF file section sizes. - tha_slice = dol_slices.get(src) - if tha_slice: - verify_object_file(dst, src, tha_slice) - else: - print("# Skipping slices verification on", src) gSourceQueue = [] def compile_queued_sources(): max_hw_concurrency = multiprocessing.cpu_count() - print("max_hw_concurrency=%s" % max_hw_concurrency) + print(Fore.YELLOW + f"max_hw_concurrency={max_hw_concurrency}" + Style.RESET_ALL) pool = ThreadPool(max_hw_concurrency) @@ -129,6 +127,19 @@ def compile_queued_sources(): pool.close() pool.join() + # + # colorama doesn't seem to work with multithreading + # + for s in gSourceQueue: + src, dst = s[0:2] + + # Verify ELF file section sizes. + tha_slice = dol_slices.get(src) + if tha_slice: + verify_object_file(dst, src, tha_slice) + else: + print(Fore.YELLOW + "# Skipping slices verification on " + src + Style.RESET_ALL) + gSourceQueue.clear() # Queued diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py index 95f337788..05656b5dd 100644 --- a/mkwutil/verify_main_dol.py +++ b/mkwutil/verify_main_dol.py @@ -3,6 +3,7 @@ """ import argparse +from colorama import Fore, Style import hashlib from pathlib import Path @@ -27,13 +28,13 @@ def verify_dol(reference, target): ctx = hashlib.sha1(content) digest = ctx.hexdigest() if digest.lower() == "ac7d72448630ade7655fc8bc5fd7a6543cb53a49": - print("[DOL] Everything went okay! Output is matching! ^^") + print(Fore.GREEN + Style.BRIGHT + "[DOL] Everything went okay! Output is matching! ^^" + Style.RESET_ALL) return want_len = 2766496 if len(content) != want_len: print( - "Mismatched file size: Got %d (%+d)" + Fore.RED + "Mismatched file size: Got %d (%+d)" + Style.RESET_ALL % (len(content), len(content) - want_len) ) @@ -92,7 +93,7 @@ def verify_dol(reference, target): ) # TODO: Add diff'ing - print("[DOL] Oof: Output doesn't match.") + print(Fore.RED + Style.BRIGHT + "[DOL] Oof: Output doesn't match." + Style.RESET_ALL) if __name__ == "__main__": diff --git a/mkwutil/verify_object_file.py b/mkwutil/verify_object_file.py index 99ff6f5a1..194001e3f 100644 --- a/mkwutil/verify_object_file.py +++ b/mkwutil/verify_object_file.py @@ -1,5 +1,7 @@ from elftools.elf.elffile import ELFFile +from termcolor import colored + def verify_object_file(dst, src, obj_slice): match = True with open(dst, 'rb') as f: @@ -13,5 +15,7 @@ def verify_object_file(dst, src, obj_slice): have_size = section.data_size if want_size != have_size: match = False - print("[!] %s %s want=0x%x got=0x%x" % (src, section_name, want_size, have_size)) + warn ="[!] %s %s want=0x%x got=0x%x" % (src, section_name, want_size, have_size) + + print(colored(warn, 'red')) return match diff --git a/mkwutil/verify_staticr_rel.py b/mkwutil/verify_staticr_rel.py index f2b7d4e69..e979ce54d 100644 --- a/mkwutil/verify_staticr_rel.py +++ b/mkwutil/verify_staticr_rel.py @@ -3,24 +3,24 @@ """ import argparse +from colorama import Fore, Style import hashlib from pathlib import Path - def verify_rel(target): """Verifies the target StaticR.rel for authenticity.""" content = open(target, "rb").read() ctx = hashlib.sha1(content) digest = ctx.hexdigest() if digest.lower() == "887bcc076781f5b005cc317a6e3cc8fd5f911300": - print("[REL] Everything went okay! Output is matching! ^^") + print(Fore.GREEN + Style.BRIGHT + "[REL] Everything went okay! Output is matching! ^^" + Style.RESET_ALL) return want_len = 4903876 if len(content) != want_len: - print(f"Mismatched file size: Got {len(content)} ({len(content)-want_len})") + print(Fore.RED + f"Mismatched file size: Got {len(content)} ({len(content)-want_len})" + Style.RESET_ALL) - print("[REL] Oof: Output doesn't match.") + print(Fore.RED + Style.BRIGHT + "[REL] Oof: Output doesn't match." + Style.RESET_ALL) if __name__ == "__main__": From a9c42f11109b96ba1edd1d4bf3ae111a8d5616f3 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 8 Jul 2021 19:53:11 -0600 Subject: [PATCH 094/477] :construction: Update dependencies --- .github/workflows/build.yml | 2 +- README.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ee7e0b640..00659bb8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: architecture: 'x64' - name: Install dependencies - run: pip3 install capstone pyelftools + run: pip3 install capstone pyelftools colorama termcolor - name: Percent decompiled run: python3 -m mkwutil.percent_decompiled diff --git a/README.md b/README.md index e4e617312..92df3d561 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Every fully understood piece of reverse engineered data has been documented in a - Python3: - `pip insall pyelftools` - `pip install capstone` + - `pip install colorama termcolor` - Place a copy of Mario Kart Wii's PAL binaries: - `artifacts/orig/pal/main.dol` - `artifacts/orig/pal/StaticR.rel` From a082f262955f9dd32b238f25bbdd8236fde1670d Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 11 Jul 2021 14:30:49 +0200 Subject: [PATCH 095/477] Decompile RVL/PAD (#20) * WIP: Decompile PAD * fix abs() usage * code style * rvlPad.c to use common headers * Update rvlPad.c Co-authored-by: riidefi <34194588+riidefi@users.noreply.github.com> --- mkwutil/verify_main_dol.py | 13 +- pack/dol.base.lcf | 44 ++- pack/dol_objects.txt | 25 +- pack/dol_slices.csv | 3 + source/rvl/pad/pad.h | 103 ++++++ source/rvl/pad/rvlPad.c | 695 +++++++++++++++++++++++++++++++++++ source/rvl/pad/rvlPadClamp.c | 88 +++++ source/rvl/si/si.h | 32 ++ source/rvl/si/siBios.c | 6 + sources.py | 3 + 10 files changed, 1000 insertions(+), 12 deletions(-) create mode 100644 source/rvl/pad/pad.h create mode 100644 source/rvl/pad/rvlPad.c create mode 100644 source/rvl/pad/rvlPadClamp.c create mode 100644 source/rvl/si/si.h create mode 100644 source/rvl/si/siBios.c diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py index 05656b5dd..df4b77633 100644 --- a/mkwutil/verify_main_dol.py +++ b/mkwutil/verify_main_dol.py @@ -11,10 +11,11 @@ def format_segment(name, at, at2, want_size, have_size, tag): - return "%10s: at_src=0x%08x at_dst=0x%08x want=0x%08x have=0x%08x [%s]" % ( + return "%10s: at_src=0x%08x at_dst=0x%08x at_end=0x%08x want=0x%08x have=0x%08x [%s]" % ( name, at, at2, + at + want_size, want_size, have_size, tag, @@ -91,6 +92,16 @@ def verify_dol(reference, target): tag, ) ) + print( + format_segment( + "bss", + good.bss.begin, + bad.bss.begin, + good.bss.size(), + bad.bss.size(), + "OK" if good.bss.size() == bad.bss.size() else "SIZE", + ) + ) # TODO: Add diff'ing print(Fore.RED + Style.BRIGHT + "[DOL] Oof: Output doesn't match." + Style.RESET_ALL) diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index 070b8582a..42a7b1207 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -34,6 +34,13 @@ sin=0x8001ba98; tan=0x8001bb64; sqrt=0x8001bbf4; +_savegpr_15=0x80021570; +_savegpr_16=0x80021574; +_savegpr_17=0x80021578; +_savegpr_18=0x8002157C; +_savegpr_19=0x80021580; +_savegpr_20=0x80021584; +_savegpr_21=0x80021588; _savegpr_22=0x8002158C; _savegpr_23=0x80021590; _savegpr_24=0x80021594; @@ -41,6 +48,13 @@ _savegpr_25=0x80021598; _savegpr_26=0x8002159C; _savegpr_27=0x800215A0; +_restgpr_15=0x800215BC; +_restgpr_16=0x800215C0; +_restgpr_17=0x800215C4; +_restgpr_18=0x800215C8; +_restgpr_19=0x800215CC; +_restgpr_20=0x800215D0; +_restgpr_21=0x800215D4; _restgpr_22=0x800215D8; _restgpr_23=0x800215DC; _restgpr_24=0x800215E0; @@ -63,10 +77,6 @@ CXGetUncompressedSize=0x8015C2E0; OSPanic=0x801A2660; -OSDisableInterrupts=0x801A65AC; -OSEnableInterrupts=0x801A65C0; -OSRestoreInterrupts=0x801A65D4; - OSInitMutex=0x801A7EAC; OSLockMutex=0x801A7EE4; @@ -98,6 +108,30 @@ GXSetFog=0x801722CC; GXInitFogAdjTable=0x801724F8; GXSetFogRangeAdj=0x80172658; +OSRegisterVersion = 0x801A0504; +OSClearContext = 0x801A2098; +OSDisableInterrupts = 0x801A65AC; +OSEnableInterrupts = 0x801A65C0; +OSRestoreInterrupts = 0x801A65D4; +OSRegisterResetFunction = 0x801A8238; +OSSetWirelessID = 0x801A9260; +OSSetCurrentContext = 0x801A1E70; +OSGetTime = 0x801AAD5C; + +SIBusy = 0x801B254C; +SIIsChanBusy = 0x801B2568; +SIUnregisterPollingHandler = 0x801B2CF8; +SIGetStatus = 0x801B3050; +SITransfer = 0x801B33EC; +SISetCommand = 0x801B30C8; +SITransferCommands = 0x801B30DC; +SIEnablePolling = 0x801B3148; +SIDisablePolling = 0x801B31D0; +SIGetResponse = 0x801B323c; +SIGetType = 0x801B3808; +SIGetTypeAsync = 0x801B39BC; +SIRefreshSamplingRate = 0x801B3BA4; + VIInit = 0x801B94A4; VIWaitForRetrace = 0x801B99EC; VIConfigure = 0x801B9F6C; @@ -113,6 +147,8 @@ VIGetTvFormat = 0x801BACD8; VIGetDTVStatus = 0x801BAD38; VISetTimeToDimming = 0x801BAFA8; +sub_801BB0D0 = 0x801BB0D0; + SCGetProgressiveMode=0x801B1D84; SCGetEuRgb60Mode=0x801B1CAC; SCGetAspectRatio=0x801B1BE4; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index fee5cd48c..69fb60154 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -42,17 +42,28 @@ out\rvlVec.o out\dol\rodata_80249020_80252c78.o out\dol\sdata2_803888b4_803888b8.o out\rvlQuat.o -out\dol\text_8019b178_8020f62c.o -out\dol\data_8027e772_802a2668.o +out\dol\text_8019b178_801ae5d8.o +out\dol\rodata_80252c84_80252dd0.o +out\dol\sdata2_803888cc_80388930.o +out\rvlPadClamp.o +out\dol\data_8027e772_8029cc80.o +out\dol\bss_80346d18_803481b0.o +out\dol\sdata_80385a10_80385b08.o +out\dol\sbss_8038683c_80386998.o +out\rvlPad.o +out\dol\sbss_803869c4_803869f0.o +out\siBios.o +out\dol\text_801b0180_8020f62c.o +out\dol\data_8029ccd8_802a2668.o out\eggAllocator.o -out\dol\bss_80346d18_803832d8.o -out\dol\sbss_8038683c_80386d80.o +out\dol\bss_80348230_803832d8.o +out\dol\sbss_803869f8_80386d80.o out\eggArchive.o out\dol\text_8020fcc4_8021a0f0.o out\dol\data_802a268c_802a2b48.o out\eggDisposer.o out\dol\text_8021a1b8_802269a8.o -out\dol\rodata_80252c84_80257700.o +out\dol\rodata_80252de6_80257700.o out\dol\data_802a2b54_802a2ff8.o out\eggExpHeap.o out\dol\text_80226f04_80229540.o @@ -63,7 +74,7 @@ out\dol\rodata_8025771a_80257740.o out\dol\data_802a30bc_802a30c0.o out\dol\bss_803832e4_80384320.o out\dol\sbss_80386e99_80386ea0.o -out\dol\sdata2_803888cc_80388d68.o +out\dol\sdata2_80388938_80388d68.o out\eggHeap.o out\dol\text_80229fac_80239dfc.o out\eggQuat.o @@ -92,7 +103,7 @@ out\dol\dtors_80244ea4_80244eac.o out\dol\rodata_80258560_80258580.o out\dol\data_802a4004_802a4040.o out\dol\bss_80384bf4_80384c00.o -out\dol\sdata_80385a10_80385fc0.o +out\dol\sdata_80385b28_80385fc0.o out\dol\sbss_80386f90_80386fa0.o out\dol\sdata2_80389118_80389140.o out\dol\sbss2_80389140_8038917c.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index e2ee133ee..59e2a7c7d 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -18,6 +18,9 @@ enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd 1,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, 1,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, 1,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b178,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888cc,, +1,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, +1,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, +1,source/rvl/si/siBios.c,,,,,,,,,,,,,,,,,,,,,0x803869f0,0x803869f8,,,, 1,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680 ,,,,,,,,,, 1,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, 1,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54 ,,,,,,,,,, diff --git a/source/rvl/pad/pad.h b/source/rvl/pad/pad.h new file mode 100644 index 000000000..7299f5d8d --- /dev/null +++ b/source/rvl/pad/pad.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OSContext { + u32 unk[178]; +} OSContext; + +// https://github.com/devkitPro/libogc/blob/bc4b778d558915aa40676e33514c4c9ba2af66b8/gc/ogc/pad.h#L46 +// Size: 0x0B (arrays padded to 0x0C) +typedef struct PADStatus { + // 0x00 + u16 button; + // 0x02 + s8 stickX; + // 0x03 + s8 stickY; + // 0x04 + s8 substickX; + // 0x05 + s8 substickY; + // 0x06 + u8 triggerL; + // 0x07 + u8 triggerR; + // 0x08 + u8 analogA; + // 0x09 + u8 analogB; + // 0x0A + s8 err; +} PADStatus; + +// Size: 0x0A +typedef struct PADClampRegion { + u8 unk_00; + u8 unk_01; + s8 stickMin; // 0x02 + s8 unk_03; + s8 unk_04; + s8 substickMin; // 0x05 + s8 unk_06; + s8 unk_07; + s8 stickRad; // 0x08 + s8 substickRad; // 0x09 +} PADClampRegion; + +// PAL: 0x801ae5d8 +void PAD_ClampCircle(s8*, s8*, s8, s8); +// PAL: 0x801ae6f4 +void PADClampCircle(PADStatus*); +// PAL: 0x801ae7dc +void PADClampCircle2(PADStatus*, u32); +// PAL: 0x801ae880 +void PAD_UpdateOrigin(s32); +// PAL: 0x801aea24 +void PADOriginCallback(s32, u32, void*); +// PAL: 0x801aeae4 +void PADOriginUpdateCallback(s32, u32, void*); +// PAL: 0x801aebac +void PADProbeCallback(s32, u32, void*); +// PAL: 0x801aec80 +void PADTypeAndStatusCallback(s32, u32); +// PAL: 0x801aefa0 +void PADReceiveCheckCallback(s32, u32); +// PAL: 0x801af0dc +int PADReset(u32); +// PAL: 0x801af1e4 +int PADRecalibrate(u32); +// PAL: 0x801af2f0 +int PADInit(void); +// PAL: 0x801af44c +u32 PADRead(PADStatus*); +// PAL: 0x801af908 +void PADControlMotor(s32, u32); +// PAL: 0x801af9c0 +void SPEC0_MakeStatus(s32, PADStatus*, u32[2]); +// PAL: 0x801afad8 +void SPEC1_MakeStatus(s32, PADStatus*, u32[2]); +// PAL: 0x801afbf0 +void SPEC2_MakeStatus(s32, PADStatus*, u32[2]); +// PAL: 0x801afffc +int PAD_OnReset(int); +// PAL: 0x801b00c4 +void PAD_SamplingHandler(s32, OSContext*); +// PAL: 0x801b0124 +int __PADDisableRecalibration(int); +// PAL: 0x801b0124 +// PADSetSamplingCallback + +void PADSetSpec(u32 model); +u32 PADGetSpec(void); + +typedef void (*PADSamplingCallback)(void); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/source/rvl/pad/rvlPad.c b/source/rvl/pad/rvlPad.c new file mode 100644 index 000000000..b0bd5be26 --- /dev/null +++ b/source/rvl/pad/rvlPad.c @@ -0,0 +1,695 @@ +#include "pad.h" + +// TODO: Move to osMisc.h +typedef int (*OSResetFunction)(int final); +typedef struct OSResetFunctionInfo { + OSResetFunction func; // 0x00 + u32 priority; // 0x04 + u32 unk_08; + u32 unk_0A; +} OSResetFunctionInfo; +void OSSetCurrentContext(OSContext*); +void OSClearContext(OSContext*); +void OSRegisterVersion(const char*); +s64 OSGetTime(); +void OSRegisterResetFunction(OSResetFunctionInfo* info); +// Broadway / IOS global locations: https://wiibrew.org/wiki/Memory_Map +u8 oslow_30e3 : (0x800030e3); +u16 oslow_30e0 : (0x800030e0); + +#include "rvl/si/si.h" + +#include + + +// PAL: 0x80385b08 @sdata (pointer) +// PAL: 0x8029cc80 @data (string literal) +static const char* __PAD_VERSION = + "<< RVL_SDK - PAD \trelease build: Oct 3 2007 01:00:54 (0x4199_60831) >>"; + +// PAL: 0x803869c0 @sbss +static u32 PADInitialized; +// PAL: 0x803869bc @sbss +static u32 PADEnabledBits; +// PAL: 0x803869b8 @sbss +static u32 PADResetBits; +// PAL: 0x80385b0c @sdata +static s32 PADResetChan = 32; +// PAL: 0x803869b4 @sbss +static u32 PADUnk803869b4; +// PAL: 0x803869b0 @sbss +static u32 PADUnk803869b0; +// PAL: 0x803869ac @sbss +static u32 PADUnk803869ac; +// PAL: 0x803869a8 @sbss +static u32 PADUnk803869a8; +// PAL: 0x803869a4 @sbss +static u32 PADUnk803869a4; + +// PAL: 0x80385b10 @sdata +static u32 PAD_StickXResetBit = 0xf0000000; +// PAL: 0x80385b14 @sdata +static u32 PAD_AnalogMode = 0x00000300u; + +// PAL: 0x803869a0 +u32 PAD_Spec; +// From rvl/si/siBios.c +extern u32 __PADFixBits; +// PAL: 0x80385b18 @sdata +static u32 Spec = 5; +// PAL: 0x80385b1c @sdata +static void (*PAD_MakeStatus)(s32, PADStatus*, u32[2]) = SPEC2_MakeStatus; + +// PAL: 0x803481e0 @bss +static u32 Type[4]; +// PAL: 0x803481b0 @bss +static PADStatus Origin[4]; + +static u32 unk_padding = 0; +// PAL: 0x80385b20 @sdata +static u32 PAD_Unk80385b20 = 0x41u << 24; +// PAL: 0x80385b24 @sdata +static u32 PAD_Unk80385b24 = 0x42u << 24; +// PAL: 0x803481f0 @bss +static u32 PAD_Unk803481f0[4]; + +// PAL: 0x80348200 => 0x80348230 @bss +static PADStatus PAD_AltStatus[4]; + +// PAL: 0x8029ccc8 => 0x8029ccd8 +static OSResetFunctionInfo PAD_ResetFunctionInfo = {PAD_OnReset, 127}; + +// PAL: 0x8038699c +static void (*PAD_SamplingCallback)(void); + +void PAD_UpdateOrigin(s32 chan) { + PADStatus* orig; + u32 chanBit = 0x80000000 >> chan; + orig = &Origin[chan]; + switch (PAD_AnalogMode & 0x00000700u) { + case 0x00000000u: + case 0x00000500u: + case 0x00000600u: + case 0x00000700u: + orig->triggerL &= ~15; + orig->triggerR &= ~15; + orig->analogA &= ~15; + orig->analogB &= ~15; + break; + case 0x00000100u: + orig->substickX &= ~15; + orig->substickY &= ~15; + orig->analogA &= ~15; + orig->analogB &= ~15; + break; + case 0x00000200u: + orig->substickX &= ~15; + orig->substickY &= ~15; + orig->triggerL &= ~15; + orig->triggerR &= ~15; + break; + case 0x00000300u: + break; + case 0x00000400u: + break; + } + orig->stickX -= 128; + orig->stickY -= 128; + orig->substickX -= 128; + orig->substickY -= 128; + if (PAD_StickXResetBit & chanBit) { + if (64 < orig->stickX && + (SIGetType(chan) & 0xffff0000) == (0x08000000u | 0x01000000u)) { + orig->stickX = 0; + } + } +} + +inline static void PADEnable(s32 chan) { + u32 cmd; + u32 chanBit; + u32 resp[2]; + chanBit = 0x80000000 >> chan; + PADEnabledBits |= chanBit; + SIGetResponse(chan, resp); + cmd = (0x40u << 16) | PAD_AnalogMode; + SISetCommand(chan, cmd); + SIEnablePolling(PADEnabledBits); +} + +int OSDisableInterrupts(void); +int OSEnableInterrupts(void); +int OSRestoreInterrupts(int); + +void OSSetWirelessID(s32, u16); + +inline static void PADDisable(s32 chan) { + int interrupts; + u32 chanBit; + interrupts = OSDisableInterrupts(); + chanBit = 0x80000000 >> chan; + SIDisablePolling(chanBit); + PADEnabledBits &= ~chanBit; + PADUnk803869b0 &= ~chanBit; + PADUnk803869ac &= ~chanBit; + PADUnk803869a8 &= ~chanBit; + PADUnk803869a4 &= ~chanBit; + OSSetWirelessID(chan, 0); + OSRestoreInterrupts(interrupts); +} + +inline static void DoReset(void) { + u32 chanBit; + PADResetChan = __cntlzw(PADResetBits); + if (PADResetChan == 32) + return; + chanBit = 0x80000000 >> PADResetChan; + PADResetBits &= ~chanBit; + memset(&Origin[PADResetChan], 0, sizeof(PADStatus)); + SIGetTypeAsync(PADResetChan, PADTypeAndStatusCallback); +} + +void PADOriginCallback(s32 chan, u32 error, void* context) { +#pragma unused(chan, context) + if (!(error & (0x0001 | 0x0002 | 0x0008 | 0x0004))) { + PAD_UpdateOrigin(PADResetChan); + PADEnable(PADResetChan); + } + DoReset(); +} + +void PADOriginUpdateCallback(s32 chan, u32 error, void* context) { +#pragma unused(context) + if (!(PADEnabledBits & (0x80000000 >> chan))) + return; + if (!(error & (0x0001 | 0x0002 | 0x0008 | 0x0004))) + PAD_UpdateOrigin(chan); + if (error & 0x0008) + PADDisable(chan); +} + +void PADProbeCallback(s32 chan, u32 error, void* context) { +#pragma unused(chan, context) + if (!(error & (0x0001 | 0x0002 | 0x0008 | 0x0004))) { + PADEnable(PADResetChan); + PADUnk803869b0 |= 0x80000000 >> PADResetChan; + } + DoReset(); +} + +void PADTypeAndStatusCallback(s32 chan, u32 type) { +#pragma unused(chan) + u32 chanBit; + u32 variant1; + int ok = true; + u32 error; + chanBit = 0x80000000 >> PADResetChan; + error = type & 0xff; + variant1 = PADUnk803869b4 & chanBit; + PADUnk803869b4 &= ~chanBit; + if (error & (0x0001 | 0x0002 | 0x0008 | 0x0004)) { + DoReset(); + return; + } + type &= ~0xff; + Type[PADResetChan] = type; + if ((type & 0x18000000u) != 0x08000000u || !(type & 0x01000000u)) { + DoReset(); + return; + } + if (Spec < 2) { + PADEnable(PADResetChan); + DoReset(); + return; + } + if (!(type & 0x80000000u) || (type & 0x04000000u)) { + if (variant1) { + ok = SITransfer(PADResetChan, &PAD_Unk80385b24, 3, &Origin[PADResetChan], + 10, PADOriginCallback, 0); + } else { + ok = SITransfer(PADResetChan, &PAD_Unk80385b20, 1, &Origin[PADResetChan], + 10, PADOriginCallback, 0); + } + } else if ((type & 0x00100000u) && (type & 0x00080000u) == 0x00000000u && + !(type & 0x00040000u)) { + if (type & 0x40000000u) { + ok = SITransfer(PADResetChan, &PAD_Unk80385b20, 1, &Origin[PADResetChan], + 10, PADOriginCallback, 0); + } else { + ok = SITransfer(PADResetChan, &PAD_Unk803481f0[PADResetChan], 3, + &Origin[PADResetChan], 8, PADProbeCallback, 0); + } + } + if (!ok) { + PADUnk803869a8 |= chanBit; + DoReset(); + return; + } +} + +static void PADReceiveCheckCallback(s32 chan, u32 type) { + u32 error; + u32 chanBit; + chanBit = 0x80000000 >> chan; + if (!(PADEnabledBits & chanBit)) { + return; + } + error = type & 0xff; + type &= ~0xff; + PADUnk803869b0 &= ~chanBit; + PADUnk803869ac &= ~chanBit; + if (!(error & 0x000f) && (type & 0x80000000u) && (type & 0x00100000u) && + (type & 0x40000000u) && !(type & 0x04000000u) && + (type & 0x00080000u) == 0x00000000u && !(type & 0x00040000u)) { + SITransfer(chan, &PAD_Unk80385b20, 1, &Origin[chan], 10, + PADOriginUpdateCallback, 0); + } else { + PADDisable(chan); + } +} + +int PADReset(u32 mask) { + int enabled; + u32 disableBits; + enabled = OSDisableInterrupts(); + mask |= PADUnk803869a8; + PADUnk803869a8 = 0; + mask &= ~(PADUnk803869b0 | PADUnk803869ac); + PADResetBits |= mask; + disableBits = PADResetBits & PADEnabledBits; + PADEnabledBits &= ~mask; + PADUnk803869a4 &= ~mask; + if (Spec == 4) { + PADUnk803869b4 |= mask; + } + SIDisablePolling(disableBits); + if (PADResetChan == 32) + DoReset(); + OSRestoreInterrupts(enabled); + return true; +} + +int PADRecalibrate(u32 mask) { + int interrupts; + u32 disableBits; + interrupts = OSDisableInterrupts(); + mask |= PADUnk803869a8; + PADUnk803869a8 = 0; + mask &= ~(PADUnk803869b0 | PADUnk803869ac); + PADResetBits |= mask; + disableBits = PADResetBits & PADEnabledBits; + PADEnabledBits &= ~mask; + PADUnk803869a4 &= ~mask; + if (!(oslow_30e3 & 0x40)) + PADUnk803869b4 |= mask; + SIDisablePolling(disableBits); + if (PADResetChan == 32) + DoReset(); + OSRestoreInterrupts(interrupts); + return true; +} + +int PADInit(void) { + s32 chan; + if (PADInitialized) { + return true; + } + OSRegisterVersion(__PAD_VERSION); + if (PAD_Spec) { + PADSetSpec(PAD_Spec); + } + PADInitialized = true; + if (__PADFixBits != 0) { + s64 time = OSGetTime(); + oslow_30e0 = (u16)((((time)&0xffff) + ((time >> 16) & 0xffff) + + ((time >> 32) & 0xffff) + ((time >> 48) & 0xffff)) & + 0x3fffu); + PADUnk803869b4 = 0xf0000000; + } + for (chan = 0; chan < 4; ++chan) { + PAD_Unk803481f0[chan] = + (0x4du << 24) | (chan << 22) | ((oslow_30e0 & 0x3fffu) << 8); + } + SIRefreshSamplingRate(); + OSRegisterResetFunction(&PAD_ResetFunctionInfo); + return PADReset(0xf0000000); +} + +void sub_801BB0D0(void); + +u32 PADRead(PADStatus* status) { + int interrupts; + s32 i; + u32 data[2]; + u32 chanBit; + u32 statusNum; + int chanShift; + u32 motor; + interrupts = OSDisableInterrupts(); + motor = 0; + for (i = 0; i < 4; i++, status++) { + chanBit = 0x80000000 >> i; + chanShift = 8 * (4 - 1 - i); + if (PADUnk803869a8 & chanBit) { + PADReset(0); + status->err = -2; + memset(status, 0, 0xa); + continue; + } + if ((PADResetBits & chanBit) || PADResetChan == i) { + status->err = -2; + memset(status, 0, 0xa); + continue; + } + if (!(PADEnabledBits & chanBit)) { + status->err = (s8)-1; + memset(status, 0, 0xa); + continue; + } + if (SIIsChanBusy(i)) { + status->err = -3; + memset(status, 0, 0xa); + continue; + } + statusNum = SIGetStatus(i); + if (statusNum & 0x0008) { + SIGetResponse(i, data); + if (PADUnk803869b0 & chanBit) { + status->err = (s8)0; + memset(status, 0, 0xa); + if (!(PADUnk803869ac & chanBit)) { + PADUnk803869ac |= chanBit; + SIGetTypeAsync(i, PADReceiveCheckCallback); + } + continue; + } + PADDisable(i); + status->err = (s8)-1; + memset(status, 0, 0xa); + continue; + } + if (!(SIGetType(i) & 0x20000000u)) { + motor |= chanBit; + } + if (!SIGetResponse(i, data)) { + status->err = -3; + memset(status, 0, 0xa); + continue; + } + if (data[0] & 0x80000000) { + status->err = -3; + memset(status, 0, 0xa); + continue; + } + // 0x801af690 + PAD_MakeStatus(i, status, data); + s32 thres; + if (((Type[i] & 0xffff0000) == 0x09000000u) && + ((status->button & 0x0080) ^ 0x0080)) { + thres = 10; + } else { + thres = 3; + } +// TODO add proper stdlib.h +#define abs __abs + if ((abs(abs(status->stickX) - abs(PAD_AltStatus[i].stickX))) >= thres || + (abs(abs(status->stickY) - abs(PAD_AltStatus[i].stickY))) >= thres || + (abs(abs(status->substickX) - abs(PAD_AltStatus[i].substickX))) >= + thres || + (abs(abs(status->substickY) - abs(PAD_AltStatus[i].substickY))) >= + thres || + (abs(abs(status->triggerL) - abs(PAD_AltStatus[i].triggerL))) >= + thres || + (abs(abs(status->triggerR) - abs(PAD_AltStatus[i].triggerR))) >= + thres || + status->button != PAD_AltStatus[i].button) { + sub_801BB0D0(); + } +#undef abs + memcpy(&PAD_AltStatus[i], status, 0xc); + if (status->button & 0x2000) { + status->err = -3; + memset(status, 0, 0xa); + SITransfer(i, &PAD_Unk80385b20, 1, &Origin[i], 10, + PADOriginUpdateCallback, 0); + continue; + } + status->err = 0; + status->button &= ~0x0080; + } + OSRestoreInterrupts(interrupts); + return motor; +} + +void PADControlMotor(s32 chan, u32 command) { + int interrupts; + u32 chanBit; + interrupts = OSDisableInterrupts(); + chanBit = 0x80000000 >> chan; + if ((PADEnabledBits & chanBit) && !(SIGetType(chan) & 0x20000000u)) { + if (Spec < 2 && command == 2) { + command = 0; + } + if (oslow_30e3 & 0x20) { + command = 0; + } + SISetCommand(chan, (0x40u << 16) | PAD_AnalogMode | + (command & (0x00000001u | 0x00000002u))); + SITransferCommands(); + } + OSRestoreInterrupts(interrupts); +} + +inline void PADSetSpec(u32 spec) { + PAD_Spec = 0; + switch (spec) { + case 0: + PAD_MakeStatus = SPEC0_MakeStatus; + break; + case 1: + PAD_MakeStatus = SPEC1_MakeStatus; + break; + case 2: + case 3: + case 4: + case 5: + PAD_MakeStatus = SPEC2_MakeStatus; + break; + } + Spec = spec; +} + +static void SPEC0_MakeStatus(s32 chan, PADStatus* status, u32 data[2]) { +#pragma unused(chan) + status->button = 0; + status->button |= ((data[0] >> 16) & 0x0008) ? 0x0100 : 0; + status->button |= ((data[0] >> 16) & 0x0020) ? 0x0200 : 0; + status->button |= ((data[0] >> 16) & 0x0100) ? 0x0400 : 0; + status->button |= ((data[0] >> 16) & 0x0001) ? 0x0800 : 0; + status->button |= ((data[0] >> 16) & 0x0010) ? 0x1000 : 0; + status->stickX = (s8)(data[1] >> 16); + status->stickY = (s8)(data[1] >> 24); + status->substickX = (s8)(data[1]); + status->substickY = (s8)(data[1] >> 8); + status->triggerL = (u8)(data[0] >> 8); + status->triggerR = (u8)data[0]; + status->analogA = 0; + status->analogB = 0; + if (170u <= status->triggerL) { + status->button |= 0x0040; + } + if (170u <= status->triggerR) { + status->button |= 0x0020; + } + status->stickX -= 128; + status->stickY -= 128; + status->substickX -= 128; + status->substickY -= 128; +} + +static void SPEC1_MakeStatus(s32 chan, PADStatus* status, u32 data[2]) { +#pragma unused(chan) + status->button = 0; + status->button |= ((data[0] >> 16) & 0x0080) ? 0x0100 : 0; + status->button |= ((data[0] >> 16) & 0x0100) ? 0x0200 : 0; + status->button |= ((data[0] >> 16) & 0x0020) ? 0x0400 : 0; + status->button |= ((data[0] >> 16) & 0x0010) ? 0x0800 : 0; + status->button |= ((data[0] >> 16) & 0x0200) ? 0x1000 : 0; + status->stickX = (s8)(data[1] >> 16); + status->stickY = (s8)(data[1] >> 24); + status->substickX = (s8)(data[1]); + status->substickY = (s8)(data[1] >> 8); + status->triggerL = (u8)(data[0] >> 8); + status->triggerR = (u8)data[0]; + status->analogA = 0; + status->analogB = 0; + if (170u <= status->triggerL) { + status->button |= 0x0040; + } + if (170u <= status->triggerR) { + status->button |= 0x0020; + } + status->stickX -= 128; + status->stickY -= 128; + status->substickX -= 128; + status->substickY -= 128; +} + +inline static s8 ClampS8(s8 arg1, s8 arg2) { + if (0 < arg2) { + s8 min = (s8)(-128 + arg2); + if (arg1 < min) { + arg1 = min; + } + } else if (arg2 < 0) { + s8 max = (s8)(127 + arg2); + if (max < arg1) { + arg1 = max; + } + } + return arg1 -= arg2; +} + +inline static u8 ClampU8(u8 arg1, u8 arg2) { + if (arg1 < arg2) { + arg1 = arg2; + } + return arg1 -= arg2; +} + +static void SPEC2_MakeStatus(s32 chan, PADStatus* status, u32 data[2]) { + PADStatus* origin; + status->button = (u16)((data[0] >> 16) & 0x00003fff); + status->stickX = (s8)(data[0] >> 8); + status->stickY = (s8)(data[0]); + switch (PAD_AnalogMode & 0x00000700u) { + case 0x00000000u: + case 0x00000500u: + case 0x00000600u: + case 0x00000700u: + status->substickX = (s8)(data[1] >> 24); + status->substickY = (s8)(data[1] >> 16); + status->triggerL = (u8)(((data[1] >> 12) & 0x0f) << 4); + status->triggerR = (u8)(((data[1] >> 8) & 0x0f) << 4); + status->analogA = (u8)(((data[1] >> 4) & 0x0f) << 4); + status->analogB = (u8)(((data[1] >> 0) & 0x0f) << 4); + break; + case 0x00000100u: + status->substickX = (s8)(((data[1] >> 28) & 0x0f) << 4); + status->substickY = (s8)(((data[1] >> 24) & 0x0f) << 4); + status->triggerL = (u8)(data[1] >> 16); + status->triggerR = (u8)(data[1] >> 8); + status->analogA = (u8)(((data[1] >> 4) & 0x0f) << 4); + status->analogB = (u8)(((data[1] >> 0) & 0x0f) << 4); + break; + case 0x00000200u: + status->substickX = (s8)(((data[1] >> 28) & 0x0f) << 4); + status->substickY = (s8)(((data[1] >> 24) & 0x0f) << 4); + status->triggerL = (u8)(((data[1] >> 20) & 0x0f) << 4); + status->triggerR = (u8)(((data[1] >> 16) & 0x0f) << 4); + status->analogA = (u8)(data[1] >> 8); + status->analogB = (u8)(data[1] >> 0); + break; + case 0x00000300u: + status->substickX = (s8)(data[1] >> 24); + status->substickY = (s8)(data[1] >> 16); + status->triggerL = (u8)(data[1] >> 8); + status->triggerR = (u8)(data[1] >> 0); + status->analogA = 0; + status->analogB = 0; + break; + case 0x00000400u: + status->substickX = (s8)(data[1] >> 24); + status->substickY = (s8)(data[1] >> 16); + status->triggerL = 0; + status->triggerR = 0; + status->analogA = (u8)(data[1] >> 8); + status->analogB = (u8)(data[1] >> 0); + break; + } + status->stickX -= 128; + status->stickY -= 128; + status->substickX -= 128; + status->substickY -= 128; + if (((Type[chan] & 0xffff0000) == 0x09000000u) && + ((status->button & 0x0080) ^ 0x0080)) { + PADUnk803869a4 |= (0x80000000 >> chan); + status->stickX = 0; + status->stickY = 0; + status->substickX = 0; + status->substickY = 0; + return; + } else { + PADUnk803869a4 &= ~(0x80000000 >> chan); + } + origin = &Origin[chan]; + status->stickX = ClampS8(status->stickX, origin->stickX); + status->stickY = ClampS8(status->stickY, origin->stickY); + status->substickX = ClampS8(status->substickX, origin->substickX); + status->substickY = ClampS8(status->substickY, origin->substickY); + status->triggerL = ClampU8(status->triggerL, origin->triggerL); + status->triggerR = ClampU8(status->triggerR, origin->triggerR); +} + +inline int PADSync(void) { + return PADResetBits == 0 && PADResetChan == 32 && !SIBusy(); +} + +PADSamplingCallback PADSetSamplingCallback(PADSamplingCallback); + +int PAD_OnReset(int final) { + // PAL: 0x80386998 + static int isCalibrated = false; + int sync; + if (PAD_SamplingCallback) + PADSetSamplingCallback(NULL); + if (!final) { + sync = PADSync(); + if (!isCalibrated && sync) { + isCalibrated = PADRecalibrate(0xf0000000); + return false; + } + return sync; + } else { + isCalibrated = false; + } + return true; +} + +void PAD_SamplingHandler(s32 interrupt, OSContext* context) { +#pragma unused(interrupt) + OSContext context2; + if (PAD_SamplingCallback) { + OSClearContext(&context2); + OSSetCurrentContext(&context2); + PAD_SamplingCallback(); + OSClearContext(&context2); + OSSetCurrentContext(context); + } +} + +inline static PADSamplingCallback +PADSetSamplingCallback(PADSamplingCallback callback) { + PADSamplingCallback prev; + prev = PAD_SamplingCallback; + PAD_SamplingCallback = callback; + if (callback) { + // Never used. + // SIRegisterPollingHandler(PAD_SamplingHandler); + } else { + SIUnregisterPollingHandler(PAD_SamplingHandler); + } + return prev; +} + +int __PADDisableRecalibration(int disable) { + int interrupts; + int prev; + interrupts = OSDisableInterrupts(); + prev = (oslow_30e3 & 0x40) ? true : false; + oslow_30e3 &= ~0x40; + if (disable) + oslow_30e3 |= 0x40; + OSRestoreInterrupts(interrupts); + return prev; +} diff --git a/source/rvl/pad/rvlPadClamp.c b/source/rvl/pad/rvlPadClamp.c new file mode 100644 index 000000000..7dcf7ef14 --- /dev/null +++ b/source/rvl/pad/rvlPadClamp.c @@ -0,0 +1,88 @@ +#include "pad.h" + +static const PADClampRegion PADClampRegionV1 = { + 30, 180, 15, 72, 47, 15, 59, 37, 62, 50, +}; + +static const PADClampRegion PADClampRegionV2 = { + 0, 180, 0, 87, 62, 0, 74, 52, 80, 68, +}; + +f64 sqrt(f64); +inline f32 sqrtf(f32 x) { return (f32)sqrt(x); } + +// Clamps a Vec2 into a circle with bounds min <= len(vec) <= r +void PAD_ClampCircle(s8* outX, s8* outY, s8 r, s8 min) { + int x = *outX; + int y = *outY; + int mag; + int len; + if (-min < x && x < min) { + x = 0; + } else if (0 < x) { + x -= min; + } else { + x += min; + } + if (-min < y && y < min) { + y = 0; + } else if (0 < y) { + y -= min; + } else { + y += min; + } + mag = x * x + y * y; + if (r * r < mag) { + len = (int)sqrtf(mag); + x = (x * r) / len; + y = (y * r) / len; + } + *outX = (s8)x; + *outY = (s8)y; +} + +// Clamps a value to min <= value <= max. +static inline void PAD_ClampTrigger(u8* out, u8 min, u8 max) { + if (*out <= min) { + *out = 0; + } else { + if (max < *out) { + *out = max; + } + *out -= min; + } +} + +// PADClampCircle clamps stick, substick and both triggers. +void PADClampCircle(PADStatus* status) { + int i; + for (i = 0; i < 4; i++, status++) { + if (status->err != 0) { + continue; + } + PAD_ClampCircle(&status->stickX, &status->stickY, 56, 15); + PAD_ClampCircle(&status->substickX, &status->substickY, 44, 15); + PAD_ClampTrigger(&status->triggerL, 30, 180); + PAD_ClampTrigger(&status->triggerR, 30, 180); + } +} + +// PADClampCircle2 clamps circles of all controllers. +void PADClampCircle2(PADStatus* status, u32 type) { + const PADClampRegion* region; + int i; + for (i = 0; i < 4; i++, status++) { + if (status->err != 0) { + continue; + } + if (type == 2) { + region = &PADClampRegionV1; + } else { + region = &PADClampRegionV2; + } + PAD_ClampCircle(&status->stickX, &status->stickY, region->stickRad, + region->stickMin); + PAD_ClampCircle(&status->substickX, &status->substickY, region->substickRad, + region->substickMin); + } +} diff --git a/source/rvl/si/si.h b/source/rvl/si/si.h new file mode 100644 index 000000000..7e29195cc --- /dev/null +++ b/source/rvl/si/si.h @@ -0,0 +1,32 @@ +#include +#include + +typedef void (*SICallback)(s32, u32, void*); +typedef void (*SITypeAndStatusCallback)(s32, u32); + +// PAL: 0x801b254c +bool SIBusy(void); +// PAL: 0x801b2568 +bool SIIsChanBusy(s32); +// PAL: 0x801b2cf8; +bool SIUnregisterPollingHandler(void*); +// PAL: 0x801b3050; +u32 SIGetStatus(s32); +// PAL: 0x801b30c8 +void SISetCommand(s32, u32); +// PAL: 0x801b30dc; +void SITransferCommands(void); +// PAL: 0x801b3148 +u32 SIEnablePolling(u32); +// PAL: 0x801b31d0; +u32 SIDisablePolling(u32); +// PAL: 0x801b33ec; +bool SITransfer(s32, void*, u32, void*, u32, SICallback, s64); +// PAL: 0x801b3ba4; +void SIRefreshSamplingRate(void); +// PAL: 0x801b323c +bool SIGetResponse(s32, void*); +// PAL: 0x801b3808 +u32 SIGetType(s32); +// PAL: 0x801b39bc +u32 SIGetTypeAsync(s32, SITypeAndStatusCallback); diff --git a/source/rvl/si/siBios.c b/source/rvl/si/siBios.c new file mode 100644 index 000000000..503618d29 --- /dev/null +++ b/source/rvl/si/siBios.c @@ -0,0 +1,6 @@ +#include "si.h" + +// PAL: 0x803869f4 +u32 __PADFixBits; + +u32 __unk_803869f0; diff --git a/sources.py b/sources.py index 1e704024b..e3d045d6d 100644 --- a/sources.py +++ b/sources.py @@ -15,6 +15,9 @@ compile_source("source/rvl/mtx/rvlMtx2.c", "out/rvlMtx2.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mtx/rvlVec.c", "out/rvlVec.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/mtx/rvlQuat.c", "out/rvlQuat.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/pad/rvlPadClamp.c", "out/rvlPadClamp.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/pad/rvlPad.c", "out/rvlPad.o", '4199_60831', RVL_OPTS + " -inline on,noauto ") +compile_source("source/rvl/si/siBios.c", "out/siBios.o", '4199_60831', RVL_OPTS) compile_source("source/nw4r/math/mathTriangular.cpp", "out/mathTriangular.o", '4201_127', NW4R_OPTS) compile_source("source/nw4r/math/mathTypes.cpp", "out/mathTypes.o", '4201_127', NW4R_OPTS) From 339348641d8bc7dcd1ffc5167bdf29bb6839521b Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Tue, 13 Jul 2021 06:43:04 +0200 Subject: [PATCH 096/477] SO, GameSpy (#24) * add SOFinish * add SOStartupEx * add SOCleanup * add SOiGetSysWork * progress SO * add SOiPrepareTempRm, SOiConcludeTempRm * add SOiWaitForDHCPEx * add ICMPSocket * add ICMPClose * fix SO * add version string * Initial Gamespy integration * add gamespay/hashtable * extend hashtable * add gamespy/md5c * add gsSocketRevolution * add gsThreadRevolution.c * rollup into gsUtilRevolution * fomrat * gsXML * sakeMain * gsXML: undo crazy string ordering hack * clang-format * add gsAvailable * clang-format * gsUdpEngine * add gp.c * gamespy: remove references to other consoles * add gpi.c * add gpiBuddy * add gpiBuffer * add gpiCallback * read FORCEFILES opt from slices * add gpiConnect * fix percent_decompiled.py * clang-format * add gpiInfo * add gpiKeys * add gpiOperation * add gpiPeer * fix a few compile warnings * add gpiProfile * add gpiSearch * add gpiTransfer * add gpiUnique * add gpiUtility * add gt2Auth * clang-format * add gt2Buffer * add gt2Callback * add gt2Connection * add gt2Main * add gt2Socket * add clang-format * add gt2Utility * clang-format * add qr2.c * add qr2regkeys * add ghttpBuffer.c * clang-format * add ghttpCallbacks * add ghttpCommon.c * add sb_crypt.c * add sb_queryengine.c * add sb_server.c * add sb_serverbrowsing.c * add sb_serverlist.c * add ghttpPost.c * add ghttpEncryption.c * add ghttpConnection.c * add ghttpMain.c * clang-format * add ghttpProcess.c * add gbucket.c * add gstats.c * formatting * don't verify ranges of stripped files * README: update dead-stripping * remove dead defines * clang-format * clean up gethostbyname() invocations * fix formatting in gp.c * add gamespy/license.txt * add NON_MATCHING qr2_check_queries() * add NON_MATCHING ghiDoReceive() --- README.md | 17 + build.py | 18 +- mkwutil/dump_elf_segments.py | 15 + mkwutil/gen_asm.py | 3 +- mkwutil/gen_lcf.py | 21 +- mkwutil/percent_decompiled.py | 21 +- pack/dol.base.lcf | 689 +++- pack/dol_objects.txt | 158 +- pack/dol_slices.csv | 130 +- source/gamespy/GP/gp.c | 3106 +++++++++++++++++ source/gamespy/GP/gp.h | 1198 +++++++ source/gamespy/GP/gpi.c | 670 ++++ source/gamespy/GP/gpi.h | 153 + source/gamespy/GP/gpiBuddy.c | 992 ++++++ source/gamespy/GP/gpiBuddy.h | 73 + source/gamespy/GP/gpiBuffer.c | 663 ++++ source/gamespy/GP/gpiBuffer.h | 96 + source/gamespy/GP/gpiCallback.c | 222 ++ source/gamespy/GP/gpiCallback.h | 88 + source/gamespy/GP/gpiConnect.c | 926 +++++ source/gamespy/GP/gpiConnect.h | 48 + source/gamespy/GP/gpiInfo.c | 1138 ++++++ source/gamespy/GP/gpiInfo.h | 89 + source/gamespy/GP/gpiKeys.c | 188 + source/gamespy/GP/gpiKeys.h | 25 + source/gamespy/GP/gpiOperation.c | 313 ++ source/gamespy/GP/gpiOperation.h | 86 + source/gamespy/GP/gpiPeer.c | 1202 +++++++ source/gamespy/GP/gpiPeer.h | 139 + source/gamespy/GP/gpiProfile.c | 472 +++ source/gamespy/GP/gpiProfile.h | 129 + source/gamespy/GP/gpiSearch.c | 1473 ++++++++ source/gamespy/GP/gpiSearch.h | 116 + source/gamespy/GP/gpiTransfer.c | 1738 +++++++++ source/gamespy/GP/gpiTransfer.h | 129 + source/gamespy/GP/gpiUnique.c | 219 ++ source/gamespy/GP/gpiUnique.h | 37 + source/gamespy/GP/gpiUtility.c | 331 ++ source/gamespy/GP/gpiUtility.h | 92 + source/gamespy/common/gsAssert.c | 33 + source/gamespy/common/gsAssert.h | 110 + source/gamespy/common/gsAvailable.c | 201 ++ source/gamespy/common/gsAvailable.h | 49 + source/gamespy/common/gsCommon.h | 47 + source/gamespy/common/gsCore.c | 400 +++ source/gamespy/common/gsCore.h | 111 + source/gamespy/common/gsCrypt.h | 95 + source/gamespy/common/gsDebug.c | 293 ++ source/gamespy/common/gsDebug.h | 157 + source/gamespy/common/gsLargeInt.c | 1850 ++++++++++ source/gamespy/common/gsLargeInt.h | 92 + source/gamespy/common/gsMemory.c | 1560 +++++++++ source/gamespy/common/gsMemory.h | 167 + source/gamespy/common/gsPlatform.c | 68 + source/gamespy/common/gsPlatform.h | 157 + source/gamespy/common/gsPlatformSocket.c | 254 ++ source/gamespy/common/gsPlatformSocket.h | 166 + source/gamespy/common/gsPlatformThread.c | 25 + source/gamespy/common/gsPlatformThread.h | 58 + source/gamespy/common/gsPlatformUtil.c | 917 +++++ source/gamespy/common/gsPlatformUtil.h | 125 + source/gamespy/common/gsRC4.h | 29 + source/gamespy/common/gsSHA1.h | 66 + source/gamespy/common/gsSSL.h | 182 + source/gamespy/common/gsSoap.c | 260 ++ source/gamespy/common/gsSoap.h | 68 + source/gamespy/common/gsStringUtil.c | 652 ++++ source/gamespy/common/gsStringUtil.h | 88 + source/gamespy/common/gsUdpEngine.c | 1143 ++++++ source/gamespy/common/gsUdpEngine.h | 189 + source/gamespy/common/gsXML.c | 1861 ++++++++++ source/gamespy/common/gsXML.h | 156 + .../common/revolution/gsSocketRevolution.c | 194 + .../common/revolution/gsThreadRevolution.c | 162 + .../common/revolution/gsUtilRevolution.c | 120 + source/gamespy/darray.c | 347 ++ source/gamespy/darray.h | 302 ++ source/gamespy/ghttp/ghttp.h | 594 ++++ source/gamespy/ghttp/ghttpASCII.h | 280 ++ source/gamespy/ghttp/ghttpBuffer.c | 384 ++ source/gamespy/ghttp/ghttpBuffer.h | 141 + source/gamespy/ghttp/ghttpCallbacks.c | 86 + source/gamespy/ghttp/ghttpCallbacks.h | 37 + source/gamespy/ghttp/ghttpCommon.c | 473 +++ source/gamespy/ghttp/ghttpCommon.h | 119 + source/gamespy/ghttp/ghttpConnection.c | 383 ++ source/gamespy/ghttp/ghttpConnection.h | 209 ++ source/gamespy/ghttp/ghttpEncryption.c | 412 +++ source/gamespy/ghttp/ghttpEncryption.h | 125 + source/gamespy/ghttp/ghttpMain.c | 1013 ++++++ source/gamespy/ghttp/ghttpMain.h | 18 + source/gamespy/ghttp/ghttpPost.c | 1425 ++++++++ source/gamespy/ghttp/ghttpPost.h | 75 + source/gamespy/ghttp/ghttpProcess.c | 1607 +++++++++ source/gamespy/ghttp/ghttpProcess.h | 34 + source/gamespy/gstats/gbucket.c | 365 ++ source/gamespy/gstats/gbucket.h | 48 + source/gamespy/gstats/gpersist.h | 443 +++ source/gamespy/gstats/gstats.c | 1562 +++++++++ source/gamespy/gstats/gstats.h | 394 +++ source/gamespy/gt2/gt2.h | 698 ++++ source/gamespy/gt2/gt2Auth.c | 90 + source/gamespy/gt2/gt2Auth.h | 28 + source/gamespy/gt2/gt2Buffer.c | 75 + source/gamespy/gt2/gt2Buffer.h | 24 + source/gamespy/gt2/gt2Callback.c | 355 ++ source/gamespy/gt2/gt2Callback.h | 64 + source/gamespy/gt2/gt2Connection.c | 316 ++ source/gamespy/gt2/gt2Connection.h | 39 + source/gamespy/gt2/gt2Filter.h | 27 + source/gamespy/gt2/gt2Main.c | 383 ++ source/gamespy/gt2/gt2Main.h | 271 ++ source/gamespy/gt2/gt2Message.h | 54 + source/gamespy/gt2/gt2Socket.c | 411 +++ source/gamespy/gt2/gt2Socket.h | 40 + source/gamespy/gt2/gt2Utility.c | 278 ++ source/gamespy/gt2/gt2Utility.h | 21 + source/gamespy/hashtable.c | 202 ++ source/gamespy/hashtable.h | 211 ++ source/gamespy/license.txt | 26 + source/gamespy/md5.h | 78 + source/gamespy/md5c.c | 338 ++ source/gamespy/natneg/NATify.h | 67 + source/gamespy/natneg/natneg.h | 179 + source/gamespy/nonport.h | 12 + source/gamespy/qr2/qr2.c | 1625 +++++++++ source/gamespy/qr2/qr2.h | 441 +++ source/gamespy/qr2/qr2regkeys.c | 78 + source/gamespy/qr2/qr2regkeys.h | 64 + source/gamespy/sake/sake.h | 350 ++ source/gamespy/sake/sakeMain.c | 407 +++ source/gamespy/sake/sakeMain.h | 49 + source/gamespy/sake/sakeRequest.h | 89 + source/gamespy/sake/sakeRequestInternal.h | 63 + source/gamespy/serverbrowsing/sb_ascii.h | 197 ++ source/gamespy/serverbrowsing/sb_crypt.c | 209 ++ source/gamespy/serverbrowsing/sb_crypt.h | 32 + source/gamespy/serverbrowsing/sb_internal.h | 464 +++ .../gamespy/serverbrowsing/sb_queryengine.c | 683 ++++ source/gamespy/serverbrowsing/sb_server.c | 672 ++++ .../serverbrowsing/sb_serverbrowsing.c | 509 +++ .../serverbrowsing/sb_serverbrowsing.h | 516 +++ source/gamespy/serverbrowsing/sb_serverlist.c | 1667 +++++++++ source/platform/assert.h | 1 + source/platform/ctype.c | 18 + source/platform/ctype.h | 104 +- source/platform/float.h | 2 +- source/platform/limits.h | 18 + source/platform/stdarg.h | 1 + source/platform/stdbool.h | 11 +- source/platform/stdint.h | 5 - source/platform/stdio.h | 79 + source/platform/stdlib.h | 16 + source/platform/string.h | 22 + source/platform/strings.h | 12 + source/platform/time.h | 17 + source/platform/wchar.h | 10 + source/rk_types.h | 1 + source/rvl/gx.h | 2 +- source/rvl/ios/ios.h | 7 + source/rvl/mem/rvlMemExpHeap.c | 5 - source/rvl/mem/rvlMemFrmHeap.cpp | 5 - source/rvl/os/os.h | 44 + source/rvl/os/osAlarm.h | 26 + source/rvl/os/osSemaphore.h | 25 + source/rvl/os/osThread.h | 46 +- source/rvl/so/so.h | 382 ++ source/rvl/so/soBasic.c | 36 + source/rvl/so/soCommon.c | 589 ++++ source/rvl/ssl.h | 40 + sources.py | 53 + 171 files changed, 54066 insertions(+), 117 deletions(-) create mode 100644 mkwutil/dump_elf_segments.py create mode 100644 source/gamespy/GP/gp.c create mode 100644 source/gamespy/GP/gp.h create mode 100644 source/gamespy/GP/gpi.c create mode 100644 source/gamespy/GP/gpi.h create mode 100644 source/gamespy/GP/gpiBuddy.c create mode 100644 source/gamespy/GP/gpiBuddy.h create mode 100644 source/gamespy/GP/gpiBuffer.c create mode 100644 source/gamespy/GP/gpiBuffer.h create mode 100644 source/gamespy/GP/gpiCallback.c create mode 100644 source/gamespy/GP/gpiCallback.h create mode 100644 source/gamespy/GP/gpiConnect.c create mode 100644 source/gamespy/GP/gpiConnect.h create mode 100644 source/gamespy/GP/gpiInfo.c create mode 100644 source/gamespy/GP/gpiInfo.h create mode 100644 source/gamespy/GP/gpiKeys.c create mode 100644 source/gamespy/GP/gpiKeys.h create mode 100644 source/gamespy/GP/gpiOperation.c create mode 100644 source/gamespy/GP/gpiOperation.h create mode 100644 source/gamespy/GP/gpiPeer.c create mode 100644 source/gamespy/GP/gpiPeer.h create mode 100644 source/gamespy/GP/gpiProfile.c create mode 100644 source/gamespy/GP/gpiProfile.h create mode 100644 source/gamespy/GP/gpiSearch.c create mode 100644 source/gamespy/GP/gpiSearch.h create mode 100644 source/gamespy/GP/gpiTransfer.c create mode 100644 source/gamespy/GP/gpiTransfer.h create mode 100644 source/gamespy/GP/gpiUnique.c create mode 100644 source/gamespy/GP/gpiUnique.h create mode 100644 source/gamespy/GP/gpiUtility.c create mode 100644 source/gamespy/GP/gpiUtility.h create mode 100644 source/gamespy/common/gsAssert.c create mode 100644 source/gamespy/common/gsAssert.h create mode 100644 source/gamespy/common/gsAvailable.c create mode 100644 source/gamespy/common/gsAvailable.h create mode 100644 source/gamespy/common/gsCommon.h create mode 100644 source/gamespy/common/gsCore.c create mode 100644 source/gamespy/common/gsCore.h create mode 100644 source/gamespy/common/gsCrypt.h create mode 100644 source/gamespy/common/gsDebug.c create mode 100644 source/gamespy/common/gsDebug.h create mode 100644 source/gamespy/common/gsLargeInt.c create mode 100644 source/gamespy/common/gsLargeInt.h create mode 100644 source/gamespy/common/gsMemory.c create mode 100644 source/gamespy/common/gsMemory.h create mode 100644 source/gamespy/common/gsPlatform.c create mode 100644 source/gamespy/common/gsPlatform.h create mode 100644 source/gamespy/common/gsPlatformSocket.c create mode 100644 source/gamespy/common/gsPlatformSocket.h create mode 100644 source/gamespy/common/gsPlatformThread.c create mode 100644 source/gamespy/common/gsPlatformThread.h create mode 100644 source/gamespy/common/gsPlatformUtil.c create mode 100644 source/gamespy/common/gsPlatformUtil.h create mode 100644 source/gamespy/common/gsRC4.h create mode 100644 source/gamespy/common/gsSHA1.h create mode 100644 source/gamespy/common/gsSSL.h create mode 100644 source/gamespy/common/gsSoap.c create mode 100644 source/gamespy/common/gsSoap.h create mode 100644 source/gamespy/common/gsStringUtil.c create mode 100644 source/gamespy/common/gsStringUtil.h create mode 100644 source/gamespy/common/gsUdpEngine.c create mode 100644 source/gamespy/common/gsUdpEngine.h create mode 100644 source/gamespy/common/gsXML.c create mode 100644 source/gamespy/common/gsXML.h create mode 100644 source/gamespy/common/revolution/gsSocketRevolution.c create mode 100644 source/gamespy/common/revolution/gsThreadRevolution.c create mode 100644 source/gamespy/common/revolution/gsUtilRevolution.c create mode 100644 source/gamespy/darray.c create mode 100644 source/gamespy/darray.h create mode 100644 source/gamespy/ghttp/ghttp.h create mode 100644 source/gamespy/ghttp/ghttpASCII.h create mode 100644 source/gamespy/ghttp/ghttpBuffer.c create mode 100644 source/gamespy/ghttp/ghttpBuffer.h create mode 100644 source/gamespy/ghttp/ghttpCallbacks.c create mode 100644 source/gamespy/ghttp/ghttpCallbacks.h create mode 100644 source/gamespy/ghttp/ghttpCommon.c create mode 100644 source/gamespy/ghttp/ghttpCommon.h create mode 100644 source/gamespy/ghttp/ghttpConnection.c create mode 100644 source/gamespy/ghttp/ghttpConnection.h create mode 100644 source/gamespy/ghttp/ghttpEncryption.c create mode 100644 source/gamespy/ghttp/ghttpEncryption.h create mode 100644 source/gamespy/ghttp/ghttpMain.c create mode 100644 source/gamespy/ghttp/ghttpMain.h create mode 100644 source/gamespy/ghttp/ghttpPost.c create mode 100644 source/gamespy/ghttp/ghttpPost.h create mode 100644 source/gamespy/ghttp/ghttpProcess.c create mode 100644 source/gamespy/ghttp/ghttpProcess.h create mode 100644 source/gamespy/gstats/gbucket.c create mode 100644 source/gamespy/gstats/gbucket.h create mode 100644 source/gamespy/gstats/gpersist.h create mode 100644 source/gamespy/gstats/gstats.c create mode 100644 source/gamespy/gstats/gstats.h create mode 100644 source/gamespy/gt2/gt2.h create mode 100644 source/gamespy/gt2/gt2Auth.c create mode 100644 source/gamespy/gt2/gt2Auth.h create mode 100644 source/gamespy/gt2/gt2Buffer.c create mode 100644 source/gamespy/gt2/gt2Buffer.h create mode 100644 source/gamespy/gt2/gt2Callback.c create mode 100644 source/gamespy/gt2/gt2Callback.h create mode 100644 source/gamespy/gt2/gt2Connection.c create mode 100644 source/gamespy/gt2/gt2Connection.h create mode 100644 source/gamespy/gt2/gt2Filter.h create mode 100644 source/gamespy/gt2/gt2Main.c create mode 100644 source/gamespy/gt2/gt2Main.h create mode 100644 source/gamespy/gt2/gt2Message.h create mode 100644 source/gamespy/gt2/gt2Socket.c create mode 100644 source/gamespy/gt2/gt2Socket.h create mode 100644 source/gamespy/gt2/gt2Utility.c create mode 100644 source/gamespy/gt2/gt2Utility.h create mode 100644 source/gamespy/hashtable.c create mode 100644 source/gamespy/hashtable.h create mode 100644 source/gamespy/license.txt create mode 100644 source/gamespy/md5.h create mode 100644 source/gamespy/md5c.c create mode 100644 source/gamespy/natneg/NATify.h create mode 100644 source/gamespy/natneg/natneg.h create mode 100644 source/gamespy/nonport.h create mode 100644 source/gamespy/qr2/qr2.c create mode 100644 source/gamespy/qr2/qr2.h create mode 100644 source/gamespy/qr2/qr2regkeys.c create mode 100644 source/gamespy/qr2/qr2regkeys.h create mode 100644 source/gamespy/sake/sake.h create mode 100644 source/gamespy/sake/sakeMain.c create mode 100644 source/gamespy/sake/sakeMain.h create mode 100644 source/gamespy/sake/sakeRequest.h create mode 100644 source/gamespy/sake/sakeRequestInternal.h create mode 100644 source/gamespy/serverbrowsing/sb_ascii.h create mode 100644 source/gamespy/serverbrowsing/sb_crypt.c create mode 100644 source/gamespy/serverbrowsing/sb_crypt.h create mode 100644 source/gamespy/serverbrowsing/sb_internal.h create mode 100644 source/gamespy/serverbrowsing/sb_queryengine.c create mode 100644 source/gamespy/serverbrowsing/sb_server.c create mode 100644 source/gamespy/serverbrowsing/sb_serverbrowsing.c create mode 100644 source/gamespy/serverbrowsing/sb_serverbrowsing.h create mode 100644 source/gamespy/serverbrowsing/sb_serverlist.c create mode 100644 source/platform/assert.h create mode 100644 source/platform/ctype.c create mode 100644 source/platform/limits.h create mode 100644 source/platform/stdarg.h create mode 100644 source/platform/stdio.h create mode 100644 source/platform/strings.h create mode 100644 source/platform/time.h create mode 100644 source/platform/wchar.h create mode 100644 source/rvl/ios/ios.h create mode 100644 source/rvl/os/os.h create mode 100644 source/rvl/os/osAlarm.h create mode 100644 source/rvl/os/osSemaphore.h create mode 100644 source/rvl/so/so.h create mode 100644 source/rvl/so/soBasic.c create mode 100644 source/rvl/so/soCommon.c create mode 100644 source/rvl/ssl.h diff --git a/README.md b/README.md index 92df3d561..79529543c 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,23 @@ Run `python3 ./build.py` to build the game and verify build authenticity. Final - `artifacts/target/pal/main.dol` - `artifacts/target/pal/StaticR.rel` +### Symbol dead-stripping + +By default, the CodeWarrior linker wants to remove any symbols (e.g. functions) that it considers unused. +Due to the unique nature of this build system, this would fail and result in all functions being removed. + +To fix this, the `gen_lcf.py` script places all objects into the `FORCEFILES` linker directive. +This prevents any content from being dead-stripped. + +In edge cases require carefully controlled use of the dead-stripping feature. +For example: Symbols that were stripped in the initial build retain all string literals. +This is very hard to replicate without dead-stripping: +Simply commenting out the stripped function would result the string literals from vanishing too. + +The dead-stripping feature can be re-enabled by: +- Setting `strip` to 1 in the slices CSV +- Listing all symbols that will _not_ be stripped in the `FORCEACTIVE` directive in `dol.base.lcf` (all other symbols get thrown out) + ## Contributing - Do not manually adjust assembly (`asm`) files. They are auto-generated. - To add a new decompiled section, modify the slice tables: diff --git a/build.py b/build.py index 625af8eee..0d89c8003 100644 --- a/build.py +++ b/build.py @@ -1,3 +1,4 @@ +import csv import os import os.path from pathlib import Path @@ -24,6 +25,14 @@ dol_slices = read_slices("pack/dol_slices.csv", verbose=False) dol_slices = { sl.obj_file : sl for sl in dol_slices } +# Remember which files are stripped. +stripped_files = set() +with open("pack/dol_slices.csv") as f: + rd = csv.DictReader(f) + for line in rd: + if line["strip"]: + stripped_files.add(line["name"]) + def native_binary(path): if sys.platform == "win32" or sys.platform == "msys": return path + ".exe" @@ -132,7 +141,8 @@ def compile_queued_sources(): # for s in gSourceQueue: src, dst = s[0:2] - + if src in stripped_files: + continue # Verify ELF file section sizes. tha_slice = dol_slices.get(src) if tha_slice: @@ -194,7 +204,8 @@ def link_dol(o_files): # Generate LCF. src_lcf_path = Path("pack", "dol.base.lcf") dst_lcf_path = Path("pack", "dol.lcf") - gen_lcf(src_lcf_path, dst_lcf_path, o_files) + slices_path = Path("pack", "dol_slices.csv") + gen_lcf(src_lcf_path, dst_lcf_path, o_files, slices_path) # Create dest dir. dest_dir = Path("artifacts", "target", "pal") dest_dir.mkdir(parents=True, exist_ok=True) @@ -212,7 +223,8 @@ def link_rel(o_files): # Generate LCF. src_lcf_path = Path("pack", "rel.base.lcf") dst_lcf_path = Path("pack", "rel.lcf") - gen_lcf(src_lcf_path, dst_lcf_path, o_files) + slices_path = Path("pack", "rel_slices.csv") + gen_lcf(src_lcf_path, dst_lcf_path, o_files, slices_path) # Create dest dir. dest_dir = Path("artifacts", "target", "pal") dest_dir.mkdir(parents=True, exist_ok=True) diff --git a/mkwutil/dump_elf_segments.py b/mkwutil/dump_elf_segments.py new file mode 100644 index 000000000..a7fec3af3 --- /dev/null +++ b/mkwutil/dump_elf_segments.py @@ -0,0 +1,15 @@ +import argparse +from pathlib import Path +from elftools.elf.elffile import ELFFile + +parser = argparse.ArgumentParser() +parser.add_argument("elf", type=Path) +args = parser.parse_args() + +with open(args.elf, 'rb') as f: + elf_file = ELFFile(f) + for section in elf_file.iter_sections(): + section_name = section.name.removeprefix(".") + if section_name.strip() == "": + continue + print("section=%s data_size=%x" % (section_name, section.data_size)) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index 3eb12c8ed..de2a438c2 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -46,7 +46,8 @@ def read_slices(name, verbose=True): for row in reader: if not row.pop("enabled"): continue - + if "strip" in row: + row.pop("strip") name = row.pop("name") segments = {} diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index 956a17df6..d36df444e 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -1,8 +1,19 @@ import argparse +import csv from pathlib import Path -def gen_lcf(src, dst, object_paths): +def gen_lcf(src, dst, object_paths, slices_path): + # Read slices and search for stripped objects. + stripped = set() + for entry in csv.DictReader(open(slices_path, "r")): + strip_opt = entry.get("strip") + if strip_opt is None: + continue + if strip_opt.strip() != "1": + continue + stripped.add(Path(entry["name"]).stem) + lcf = "" with open(src, "r") as f: @@ -10,8 +21,7 @@ def gen_lcf(src, dst, object_paths): lcf += "\nFORCEFILES {\n" for obj_path in object_paths: obj_path = Path(obj_path) - # TODO: Add ability to disable FORCEFILE to slices.csv - if obj_path.stem == "eggVideo": + if obj_path.stem in stripped: continue lcf += str(obj_path.parent / (obj_path.stem + ".o")) + "\n" lcf += "}\n" @@ -35,6 +45,9 @@ def gen_lcf(src, dst, object_paths): default="", help="Dir prefix for references in linker script", ) + parser.add_argument( + "--slices", type=Path, required=True, help="Path to slices file" + ) args = parser.parse_args() # Read list of objects. @@ -43,4 +56,4 @@ def gen_lcf(src, dst, object_paths): for line in file: objs.append(args.prefix / Path(line)) - gen_lcf(args.base, args.out, objs) + gen_lcf(args.base, args.out, objs, args.slices) diff --git a/mkwutil/percent_decompiled.py b/mkwutil/percent_decompiled.py index 8129c3425..0baacc42a 100644 --- a/mkwutil/percent_decompiled.py +++ b/mkwutil/percent_decompiled.py @@ -3,22 +3,24 @@ from pathlib import Path -def process_line(tags, items): +def process_line(line): name = "Untitled" start = None code_total = 0 data_total = 0 - for tag, entry in zip(tags, items): + for tag, entry in line.items(): if tag == "enabled" and entry == 0: return elif tag == "name": name = entry continue + elif tag == "strip": + continue is_code = "text" in tag - if "Start" in tag: + if tag.endswith("Start"): if not entry: start = None continue @@ -34,17 +36,14 @@ def process_line(tags, items): else: data_total += size - name = items[1] return name, code_total, data_total def parse_slices(path): with open(path, "r") as file: - lines = file.readlines() - - tags = lines[0].split(",") - for line in lines[1:]: - yield process_line(tags, line.split(",")) + reader = csv.DictReader(file) + for line in reader: + yield process_line(line) def simple_count(path): @@ -129,6 +128,10 @@ def percent_decompiled(dir="."): egg_total = [0x80244DD4 - 0x8020F62C, None] analyze(" -> [EGG]", egg_progress, egg_total) + spy_progress = get_progress(parse_slices(dol_slices_path), "gamespy") + spy_total = [0x80123F88 - 0x800EF378, None] + analyze(" -> [SPY]", spy_progress, spy_total) + rel_slices_path = dir / "pack" / "rel_slices.csv" rel_progress = simple_count(rel_slices_path) rel_segments_path = dir / "pack" / "rel_segments.csv" diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index 42a7b1207..42ae0c1fa 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -28,12 +28,51 @@ __ArenaHi = 0x81700000; __start = 0x800060A4; __destroy_global_chain=0x80021350; +__div2i=0x800216F0; +ftell=0x8000c21c; +fseek=0x8000c3e4; +rewind=0x8000c3e8; +fread=0x8000e610; +fwrite=0x8000e614; +fclose=0x8000ec5c; + +memmove=0x8000f1f0; +memchr=0x8000f2bc; +memcmp=0x8000f314; +qsort=0x80011b00; +srand=0x80011c90; +printf=0x800116e4; +vprintf=0x800117b0; +vsnprintf=0x8001182c; +vsprintf=0x800118b4; +snprintf=0x80011938; +sprintf=0x80011a2c; +sscanf=0x80013040; +strcpy=0x80013120; +strncpy=0x800131e0; +strcat=0x80013224; +strncat=0x80013250; +strchr=0x800133f8; +strcspn=0x80013428; +strstr=0x800135f0; +strlen=0x80021254; +strcmp=0x8001329c; +strncmp=0x800133b8; +strncasecmp=0x8001bc98; +strcasecmp=0x8001bc9c; +atof=0x80014990; +atoi=0x8001543c; +wcslen=0x80017998; +wcsncpy=0x800179d0; +wcscmp=0x80017a14; cos=0x8001b590; sin=0x8001ba98; tan=0x8001bb64; sqrt=0x8001bbf4; +__ctype_mapC=0x80270fd0; +_savegpr_14=0x8002156C; _savegpr_15=0x80021570; _savegpr_16=0x80021574; _savegpr_17=0x80021578; @@ -48,6 +87,7 @@ _savegpr_25=0x80021598; _savegpr_26=0x8002159C; _savegpr_27=0x800215A0; +_restgpr_14=0x800215B8; _restgpr_15=0x800215BC; _restgpr_16=0x800215C0; _restgpr_17=0x800215C4; @@ -71,27 +111,142 @@ __dt__Q23EGG8Vector3fFv=0x80009B40; __dt__Q23EGG8Vector2fFv=0x80009B80; GetRenderModeObj__Q34nw4r3g3d8G3DStateFv=0x80064440; +SocketStartUp=0x800f24c0; +SocketShutDown=0x800f24c4; +current_time=0x800f24c8; +msleep=0x800f2510; +gethostbyname=0x800f164c; +gsCoreIsShutdown=0x800f4114; +gsimalloc=0x800f3860; +gsirealloc=0x800f3870; +gsifree=0x800f3884; +gsiSecondsToDate=0x800f254c; +gsiDateToSeconds=0x800f27b4; +gsiStartResolvingHostname=0x800f2104; +gsiCancelResolvingHostname=0x800f2238; +gsiGetResolvedIP=0x800f2300; + +gti2HandleESN=0x8010ad14; +gti2HandleServerChallenge=0x8010ae44; +gti2HandleClientResponse=0x8010b178; +gti2DeliverReliableMessage=0x8010b480; +gti2IncomingBufferMessageCompare=0x8010bc3c; +gti2BufferIncomingMessage=0x8010bc50; +gti2HandleReliableMessage=0x8010bdfc; +gti2HandleNack=0x8010c24c; +gti2HandleUnreliableMessage=0x8010c434; +gti2HandleMessage=0x8010c6fc; +gti2HandleConnectionReset=0x8010cb90; +gti2HandleHostUnreachable=0x8010cda8; +gti2ReceiveMessages=0x8010ced8; +gti2BeginReliableMessage=0x8010d124; +gti2SendClientChallenge=0x8010d4b8; +gti2SendAccept=0x8010d59c; +gti2SendReject=0x8010d664; +gti2SendClose=0x8010d758; +gti2SendKeepAlive=0x8010d820; +gti2SendAppUnreliable=0x8010d8e8; +gti2SendAck=0x8010da14; +gti2SendNack=0x8010dad8; +gti2SendClosed=0x8010dbcc; +gti2ResendMessage=0x8010dc84; +gti2Send=0x8010dd40; + +Util_RandSeed=0x800f2ec8; +Util_RandInt=0x800f2ee0; +rand=0x80011c70; +B64Decode=0x800f2f54; +B64Encode=0x800f3278; +B64DecodeLen=0x800f3484; +B64InitEncodeStream=0x800f3528; +B64EncodeStream=0x800f3538; +_UCS2CharToUTF8String=0x800f4680; +UTF8ToUCS2StringLen=0x800f47e4; +goastrdup=0x800f23f4; +_strlwr=0x800f2464; +SetSockBlocking=0x800f1bc8; +SetReceiveBufferSize=0x800f1c40; +CanReceiveOnSocket=0x800f1c9c; +CanSendOnSocket=0x800f1ce4; +getlocalhost=0x800f1d2c; + +NNMagicData=0x80385520; +NNFreeNegotiateList=0x8011a7c4; +NNBeginNegotiationWithSocket=0x8011ae3c; +NNCancel=0x8011b158; +NNProcessData=0x8011bf54; +NNThink=0x8011b718; + +ghiTrySendThenBuffer=0x8011248c; +ghiSendBufferedData=0x80111de8; + +sakeiInitRequest=0x8012249c; +sakeiFreeRequest=0x8012256c; +sakeiCheckSakeResult=0x80122570; +sakeiSoapCallback=0x8012279c; +sakeiSetupRequest=0x80122944; +sakeiStartRequest=0x80122a84; +sakeiValidateRequestFields=0x80122b18; +sakeiFillSoapRequestFieldValues=0x80122bdc; +sakeiCreateRecordValidateInput=0x80122efc; +sakeiCreateRecordFillSoapRequest=0x80122f48; +sakeiCreateRecordProcessSoapResponse=0x80122fa4; +sakeiStartCreateRecordRequest=0x80122fe8; +sakeiUpdateRecordFillSoapRequest=0x80123038; +sakeiStartUpdateRecordRequest=0x801230ac; +sakeiDeleteRecordFillSoapRequest=0x801230d4; +sakeiReadOutputRecords=0x80123138; +sakeiFreeOutputRecords=0x8012365c; +sakeiSearchForRecordsValidateInput=0x80123724; +sakeiSearchForRecordsFillSoapRequest=0x801237c4; +sakeiSearchForRecordsFreeData=0x80123964; +sakeiStartSearchForRecordsRequest=0x801239a0; +sakeiGetMyRecordsValidateInput=0x801239ac; +sakeiGetMyRecordsFillSoapRequest=0x80123a24; +sakeiGetMyRecordsProcessSoapResponse=0x80123ad4; +sakeiGetMyRecordsFreeData=0x80123aec; +sakeiStartGetMyRecordsRequest=0x80123b10; +sakeiGetSpecificRecordsFillSoapRequest=0x80123bbc; +sakeiGetSpecificRecordsProcessSoapResponse=0x80123cb8; +sakeiGetRandomRecordValidateInput=0x80123cf4; +sakeiGetRandomRecordFillSoapRequest=0x80123d6c; +sakeiGetRandomRecordProcessSoapResponse=0x80123e4c; +sakeiGetRandomRecordFreeData=0x80123ed8; + CXInitUncompContextLZ=0x8015BEF0; CXReadUncompLZ=0x8015BF24; CXGetUncompressedSize=0x8015C2E0; +IOS_OpenAsync=0x801937E0; +IOS_Open=0x801938F8; +IOS_Close=0x80193AD8; +IOS_Ioctl=0x80194290; + OSPanic=0x801A2660; OSInitMutex=0x801A7EAC; OSLockMutex=0x801A7EE4; OSUnlockMutex=0x801A7FC0; -OSGetTick = 0x801AAD74; +OSGetTick=0x801AAD74; OSCreateThread=0x801A9E84; OSIsThreadTerminated=0x801A98BC; OSSetSwitchThreadCallback=0x801A95AC; OSInitMessageQueue=0x801A72FC; OSDetachThread=0x801AA4EC; +OSResumeThread=0x801aa58c; +OSSuspendThread=0x801aa824; +OSSleepThread=0x801aa9b8; +OSWakeupThread=0x801aaaa4; OSCancelThread=0x801AA1D4; onExit__Q23EGG6ThreadFv=0x80008E7C; onEnter__Q23EGG6ThreadFv=0x80008E80; OSGetCurrentThread=0x801A98B0; +OSSleepTicks=0x801AACA8; +OSGetTime=0x801aad5c; +__OSGetSystemTime=0x801AAD7C; +OSTicksToCalendarTime=0x801aafa8; DVDReadPrio=0x8015E834; DVDOpen=0x8015E2BC; @@ -116,7 +271,6 @@ OSRestoreInterrupts = 0x801A65D4; OSRegisterResetFunction = 0x801A8238; OSSetWirelessID = 0x801A9260; OSSetCurrentContext = 0x801A1E70; -OSGetTime = 0x801AAD5C; SIBusy = 0x801B254C; SIIsChanBusy = 0x801B2568; @@ -153,11 +307,542 @@ SCGetProgressiveMode=0x801B1D84; SCGetEuRgb60Mode=0x801B1CAC; SCGetAspectRatio=0x801B1BE4; +NCDGetLinkStatus=0x801D0C6C; +NWC24iStartupSocket=0x801E5FD8; +NWC24iCleanupSocket=0x801E5FE8; +NWC24iLockSocket=0x801E5FF8; +NWC24iUnlockSocket=0x801E6008; +SOInetAtoN=0x801ed82c; +SOInetNtoA=0x801ed938; +SOGetHostByName=0x801edf00; +SOGetInterfaceOpt=0x801EE48C; +SOGetHostID=0x801EDE88; +SOSocket=0x801ecff4; +SOClose=0x801ed0e4; +SOBind=0x801ed188; +SOConnect=0x801ed270; +SOGetSockName=0x801ed358; +SORecvFrom=0x801ed454; +SORecv=0x801ed47c; +SOSendTo=0x801ed4a0; +SOSend=0x801ed4c8; +SOFcntl=0x801ed4ec; +SOShutdown=0x801ed61c; +SOPoll=0x801ed6d0; +SOSetSockOpt=0x801ee388; +SONtoHl=0x801ed98c; +SONtoHs=0x801ed990; +SOHtoNl=0x801ed998; +SOHtoNs=0x801ed99c; + +SSLNew=0x801ee668; +SSLConnect=0x801ee7c0; +SSLDoHandshake=0x801ee888; +SSLRead=0x801ee934; +SSLWrite=0x801eec04; +SSLShutdown=0x801eeec4; +SSLSetClientCert=0x801eef70; +SSLSetRootCA=0x801ef0dc; +SSLSetBuiltinRootCA=0x801ef224; +SSLSetBuiltinClientCert=0x801ef2ec; + +ParseSingleQR2Reply=0x8011ca4c; +ProcessIncomingReplies=0x8011cc3c; + __cvt_fp2unsigned = 0x80021478; } FORCEACTIVE { + initialize__Q23EGG5VideoFPC15GXRenderModeObjPCPC15GXRenderModeObj getTickPerVRetrace__Q23EGG5VideoFUl getTickPerVRetrace__Q23EGG5VideoFv + +// gsAvailable +GSIStartAvailableCheckA +//GSIAvailableCheckThink + +// gsUdpEngine +gsUdpMsgHandlerFree +gsUdpMsgHandlerCompare +gsUdpMsgHandlerCompare2 +gsUdpRemotePeerCompare +gsUdpRemotePeerCompare2 +gsUdpSocketError +gsUdpClosedRoutingCB +gsUdpConnectedRoutingCB +gsUdpPingRoutingCB +gsUdpReceivedRoutingCB +gsUdpUnrecognizedMsgCB +gsUdpConnAttemptCB +gsUdpEngineIsInitialized +gsUdpEngineInitialize +gsUdpEngineGetPeerState +gsUdpEngineStartTalkingToPeer +gsUdpEngineSendMessage +gsUdpEngineThink +gsUdpEngineShutdown +gsUdpEngineGetLocalPort +gsUdpEngineAddMsgHandler +gsUdpEngineRemoveMsgHandler +gsUdpEngineNoMoreMsgHandlers +gsUdpEngineNoApp +gsUdpEngineGetPeerOutBufferFreeSpace + +// gsXML +gsXmlCreateStreamWriter +gsXmlCreateStreamReader +gsXmlParseBuffer +gsXmlFreeWriter +gsXmlFreeReader +gsXmlCloseWriter +gsXmlWriterGetData +gsXmlWriterGetDataLength +gsXmlWriteOpenTag +gsXmlWriteCloseTag +gsXmlWriteStringElement +gsXmlWriteUnicodeStringElement +gsXmlWriteIntElement +gsXmlWriteFloatElement +gsXmlWriteBase64BinaryElement +gsXmlWriteDateTimeElement +gsiXmlUtilWriteXmlSafeString +gsXmlMoveToStart +gsXmlMoveToNext +gsXmlMoveToParent +gsXmlMoveToChild +gsXmlReadChildAsString +gsXmlReadChildAsStringNT +gsXmlReadChildAsHexBinary +gsXmlReadChildAsBase64Binary +gsXmlReadChildAsInt +gsXmlReadChildAsDateTimeElement +gsXmlReadChildAsFloat +gsiXmlUtilTagMatches +gsXmlCountChildren + +// gp +gpInitialize +gpDestroy +gpProcess +gpSetCallback +gpConnectPreAuthenticatedA +gpDisconnect +gpProfileSearchA +gpGetInfo +gpSetInfosA +gpSendBuddyRequestA +gpAuthBuddyRequest +gpDenyBuddyRequest +gpGetNumBuddies +gpGetBuddyStatus +gpGetBuddyIndex +gpIsBuddy +gpDeleteBuddy +gpSetStatusA +gpSendBuddyMessageA +gpGetReversBuddiesList +gpGetLoginTicket + +// gpi +gpiInitialize +gpiDestroy +gpiReset +gpiProcessConnectionManager +gpiProcess + +// gpiBuddy +gpiProcessRecvBuddyMessage +gpiProcessRecvBuddyStatusInfo +gpiSendServerBuddyMessage +gpiSendBuddyMessage +gpiBuddyHandleKeyRequest +gpiBuddyHandleKeyReply +gpiAuthBuddyRequest +gpiFixBuddyIndices +gpiDeleteBuddy + +// gpiBuffer +gpiAppendCharToBuffer +gpiAppendStringToBufferLen +gpiAppendStringToBuffer +gpiAppendIntToBuffer +gpiSendOrBufferChar +gpiSendOrBufferStringLenToPeer +gpiSendOrBufferString +gpiRecvToBuffer +gpiSendFromBuffer +gpiSendBufferToPeer +gpiReadMessageFromBuffer +gpiClipBufferToPosition + +// gpiCallback +gpiCallErrorCallback +gpiAddCallback +gpiProcessCallbacks + +// gpiConnect +gpiConnect +gpiProcessConnect +gpiCheckConnect +gpiDisconnect + +// gpiInfo +gpiInfoCacheToArg +gpiProcessGetInfo +gpiAddLocalInfo +gpiSetInfoi +gpiSetInfos +gpiSendGetInfo +gpiGetInfo +gpiFreeInfoCache + +// gpiKeys +gpiStatusInfoKeyFree +gpiStatusInfoKeysInit +gpiStatusInfoKeysDestroy +gpiStatusInfoKeyCompFunc +gpiStatusInfoAddKey +gpiStatusInfoSetKey +gpiStatusInfoCheckKey +gpiSaveKeysToBuffer + +// gpiOperation +gpiFailedOpCallback +gpiAddOperation +gpiDestroyOperation +gpiRemoveOperation +gpiFindOperationByID +gpiOperationsAreBlocking +gpiProcessOperation + +// gpiPeer +gpiPeerSendMessages +gpiCheckTimedOutPeerOperations +gpiDestroyPeer +gpiRemovePeer +gpiProcessPeers +gpiGetPeerByProfile +gpiAddPeer +gpiPeerGetSig +gpiPeerStartConnect +gpiPeerAddMessage +gpiPeerStartTransferMessage +gpiPeerFinishTransferMessage +gpiPeerLeftCallback +gpiPeerMessageCallback +gpiPeerAcceptedCallback +gpiPeerPingReplyCallback +gpiPeerAddOp +gpiPeerRemoveOp + +// gpiProfile +GPIInfoCacheFilename +gpiInitProfiles +gpiProcessNewProfile +gpiProfileListAdd +gpiGetProfile +gpiProcessDeleteProfle +gpiRemoveProfileByID +gpiRemoveProfile +gpiFindProfileByUser +gpiProfileMap +gpiFindBuddy +gpiRemoveBuddyStatus +gpiRemoveBuddyStatusInfo +gpiCanFreeProfile + +// gpiSearch +gpiProfileSearch +gpiOthersBuddyList +gpiProcessSearches + +// gpiTransfer +gpiHandleTransferMessage + +// gpiUnique +gpiProcessRegisterUniqueNick +gpiProcessRegisterCdKey + +// gpiUtility +strzcpy +gpiCheckForError +gpiValueForKey +gpiCheckSocketConnect +gpiReadKeyAndValue +gpiSetError +gpiSetErrorString +gpiEncodeString + +// gt2Auth +gti2GetChallenge +gti2GetResponse +gti2CheckResponse + +// gt2Buffer +gti2AllocateBuffer +gti2GetBufferFreeSpace +gti2BufferWriteByte +gti2BufferWriteUShort +gti2BufferWriteData +gti2BufferShorten + +// gt2Callback +gti2SocketErrorCallback +gti2ConnectAttemptCallback +gti2ConnectedCallback +gti2ReceivedCallback +gti2ClosedCallback +gti2PingCallback +gti2SendFilterCallback +gti2ReceiveFilterCallback +gti2DumpCallback +gti2UnrecognizedMessageCallback + +// gt2Connection +gti2NewOutgoingConnection +gti2NewIncomingConnection +gti2StartConnectionAttempt +gti2AcceptConnection +gti2RejectConnection +gti2ConnectionSendData +gti2ConnectionThink +gti2CloseConnection +gti2ConnectionClosed +gti2ConnectionCleanup + +// gt2Main +gt2CreateSocket +gt2CloseSocket +gt2Think +gt2Listen +gt2Accept +gt2Reject +gt2Connect +gt2Send +gt2CloseConnectionHard +gt2CloseAllConnectionsHard +gt2GetConnectionState +gt2GetRemoteIP +gt2GetRemotePort +gt2GetLocalIP +gt2GetLocalPort +gt2GetOutgoingBufferSize +gt2GetOutgoingBufferFreeSpace +gt2GetSocketSOCKET +gt2SetUnrecognizedMessageCallback +gt2SetConnectionData +gt2GetConnectionData + +// gt2Socket +gti2SocketFindConnection +gti2CreateSocket +gti2CloseSocket +gti2Listen +gti2NewSocketConnection +gti2FreeSocketConnection +gti2SocketSend +gti2SocketConnectionsThink +gti2FreeClosedConnections +gti2SocketError + +// gt2Utility +gt2AddressToString +gt2StringToAddress +gti2MessageCheck + +// qr2 +qr2_init_socketA +qr2_register_natneg_callback +qr2_register_clientmessage_callback +qr2_register_publicaddress_callback +qr2_think +qr2_send_statechanged +qr2_shutdown +qr2_keybuffer_add +qr2_buffer_add_int +qr2_buffer_addA +qr2_parse_queryA +qr2_check_queries_indata + +// qr2RegKeys +qr2_internal_is_master_only_key +qr2_register_keyA + +// ghttpBuffer +ghiResizeBuffer +ghiInitBuffer +ghiInitFixedBuffer +ghiFreeBuffer +ghiAppendDataToBuffer +ghiEncryptDataToBuffer +ghiAppendHeaderToBuffer +ghiAppendCharToBuffer +ghiAppendIntToBuffer +ghiResetBuffer +// ghiSendBufferedData + +// ghttpCommon +ghiProxyAddress +ghiProxyPort +ghiCreateLock +ghiFreeLock +ghiLock +ghiUnlock +ghiDecryptReceivedData +ghiDoReceive +ghiDoSend + +// ghttpConnection +ghiNewConnection +ghiFreeConnection +ghiRequestToConnection +ghiEnumConnections +ghiRedirectConnection +ghiCleanupConnections + +// ghttpEncryption +ghttpSetRequestEncryptionEngine +ghiEncryptorSslInitFunc +ghiEncryptorSslCleanupFunc +ghiEncryptorSslStartFunc +ghiEncryptorSslEncryptSend +ghiEncryptorSslDecryptRecv +ghiEncryptorSslEncryptFunc +ghiEncryptorSslDecryptFunc + +// ghttpMain +ghttpStartup +ghttpCleanup +ghttpGetExA +ghttpPostA +ghttpThink +ghttpRequestThink +ghttpCancelRequest +ghttpGetHeaders +ghttpSetMaxRecvTime +ghttpNewPost +ghttpPostSetAutoFree +ghttpFreePost +ghttpPostAddFileFromMemoryA +ghttpPostAddXml + +// ghttpPost +ghiNewPost +ghiPostSetAutoFree +ghiIsPostAutoFree +ghiFreePost +ghiPostAddFileFromMemory +ghiPostAddXml +ghiPostGetContentType +ghiPostInitState +ghiPostCleanupState +ghiPostDoPosting + +// ghttpProcess +ghiDoSocketInit +ghiDoHostLookup +ghiDoLookupPending +ghiDoConnecting +ghiDoSecuringSession +ghiDoSendingRequest +ghiDoPosting +ghiDoWaiting +ghiDoReceivingStatus +ghiDoReceivingHeaders +ghiDoReceivingFile + +// gbucket +BucketNew +BucketSet +BucketAdd +BucketSub +BucketMult +BucketDiv +BucketConcat +BucketAvg + +// gstats +CloseStatsConnection +IsStatsConnected +PersistThink + +// sb_crypt +GOACryptInit +GOADecrypt + +// sb_queryengine +SBQueryEngineInit +SBQueryEngineSetPublicIP +SBEngineHaltUpdates +SBEngineCleanup +SBQueryEngineUpdateServer +ParseSingleQR2Reply +ProcessIncomingReplies +SBQueryEngineThink +SBQueryEngineAddQueryKey +SBQueryEngineRemoveServerFromFIFOs + +// sb_server +SBRefStrHash +SBRefStrHashCleanup +SBServerFree +SBServerAddKeyValue +SBServerAddIntKeyValue +SBServerGetStringValueA +SBServerGetIntValueA +SBServerGetFloatValueA +SBServerGetPublicAddress +SBServerGetPublicInetAddress +SBServerGetPublicQueryPort +SBServerGetPublicQueryPortNBO +SBServerHasPrivateAddress +SBServerGetPrivateAddress +SBServerGetPrivateInetAddress +SBServerGetPrivateQueryPort +SBServerSetNext +SBServerGetNext +SBServerParseKeyVals +SBServerParseQR2FullKeysSingle +SBServerParseQR2FullKeysSplit +SBAllocServer +SBServerSetFlags +SBServerSetPrivateAddr +SBServerSetICMPIP +SBServerSetState +SBServerGetState +SBIsNullServer + +// sb_serverbrowsing +ServerBrowserNewA +ServerBrowserFree +ServerBrowserBeginUpdate2 +ServerBrowserLimitUpdateA +ServerBrowserSendMessageToServerA +ServerBrowserSendNatNegotiateCookieToServerA +ServerBrowserRemoveServer +ServerBrowserThink +ServerBrowserClear +ServerBrowserState +ServerBrowserGetServer +ServerBrowserCount +ServerBrowserSortA +ServerBrowserGetMyPublicIPAddr + +// sakeMain +sakeStartup +sakeShutdown +sakeSetGame +sakeSetProfile +sakeGetStartRequestResult +sakeCreateRecord +sakeUpdateRecord +sakeSearchForRecords +sakeGetMyRecords +sakeSetFileDownloadURL +sakeGetFileDownloadURL +sakeSetFileUploadURL +sakeGetFileResultFromHeaders +sakeGetFileIdFromHeaders + } diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 69fb60154..dae189a42 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -21,25 +21,151 @@ out\dol\text_800af1a0_800ccb4c.o out\dol\data_80274250_80275700.o out\dol\sbss_80385fc0_803862a8.o out\dwc_error.o -out\dol\text_800ccc80_80124500.o -out\dol\data_80275758_8027e708.o -out\dol\sdata_80384c00_803857f0.o +out\dol\text_800ccc80_800ef378.o +out\darray.o +out\hashtable.o +out\dol\rodata_80249020_8024c6b8.o +out\dol\data_80275758_8027aca0.o +out\md5c.o +out\dol\sbss_803862b0_80386350.o +out\gsSocketRevolution.o +out\dol\text_800f164c_800f1f58.o +out\gsUtilRevolution.o +out\dol\text_800f2048_800f38a4.o +out\dol\data_8027ace0_8027ad58.o +out\dol\bss_802bd4ec_802f2338.o +out\gsAvailable.o +out\dol\text_800f3a20_800f489c.o +out\dol\bss_802f2410_802f2440.o +out\gsUdpEngine.o +out\dol\data_8027ad79_8027ad80.o +out\dol\sdata_80384c00_803850a0.o +out\gsXML.o +out\dol\sdata_80385105_80385108.o +out\gp.o +out\dol\data_8027b289_8027b290.o +out\gpi.o +out\dol\data_8027b30f_8027b310.o +out\gpiBuddy.o +out\dol\data_8027b453_8027b458.o +out\dol\sdata_803851ea_803851f0.o +out\gpiBuffer.o +out\dol\data_8027b4ba_8027b4c0.o +out\gpiCallback.o +out\dol\data_8027b4cf_8027b4d0.o +out\dol\sdata_80385206_80385208.o +out\gpiConnect.o +out\dol\data_8027b876_8027b878.o +out\dol\sdata_8038529b_803852a0.o +out\dol\sdata2_80387eb4_80388470.o +out\gpiInfo.o +out\dol\data_8027bbce_8027bbd0.o +out\dol\sdata_80385355_80385358.o +out\gpiKeys.o +out\dol\data_8027bc11_8027bc18.o +out\gpiOperation.o +out\dol\sdata_8038535a_80385360.o +out\gpiPeer.o +out\dol\data_8027bd2a_8027bd30.o +out\dol\sdata_803853b9_803853c0.o +out\gpiProfile.o +out\dol\data_8027bee5_8027bee8.o +out\dol\sdata_803853ce_803853d0.o +out\gpiSearch.o +out\dol\data_8027c204_8027c208.o +out\dol\sdata_803854bb_803854c0.o +out\gpiTransfer.o +out\dol\data_8027c229_8027c230.o +out\dol\sdata_803854cb_803854d0.o +out\gpiUnique.o +out\dol\data_8027c26f_8027c270.o +out\dol\sdata_803854dd_803854e0.o +out\gpiUtility.o +out\dol\data_8027c2c5_8027c2c8.o +out\gt2Auth.o +out\gt2Buffer.o +out\gt2Callback.o +out\gt2Connection.o +out\gt2Main.o +out\dol\text_8010ad14_8010de30.o +out\gt2Socket.o +out\dol\bss_802f247c_802f3480.o +out\dol\sdata_803854f8_80385508.o +out\dol\sbss_8038635c_80386360.o +out\gt2Utility.o +out\dol\data_8027c2e9_8027c2f0.o +out\dol\bss_802f34b0_802f34c0.o +out\dol\sdata_80385519_80385520.o +out\dol\sbss_80386364_80386368.o +out\dol\sdata2_80388474_80388478.o +out\qr2.o +out\dol\data_8027d21b_8027d220.o +out\dol\sdata_8038554a_80385550.o +out\qr2RegKeys.o +out\dol\sdata_803855c7_803855c8.o +out\ghttpBuffer.o +out\dol\text_80111de8_80111f0c.o +out\ghttpCallbacks.o +out\dol\sdata_803855d3_803855d8.o +out\dol\sbss_8038636c_80386370.o +out\ghttpCommon.o +out\dol\text_8011248c_801125c8.o +out\ghttpConnection.o +out\dol\data_8027d701_8027d708.o +out\ghttpEncryption.o +out\dol\data_8027d711_8027d718.o +out\ghttpMain.o +out\dol\data_8027d731_8027d738.o +out\dol\sbss_8038638c_80386390.o +out\dol\sdata2_8038847e_80388480.o +out\ghttpPost.o +out\dol\rodata_8024c6c9_8024c6d0.o +out\dol\data_8027d974_8027d978.o +out\dol\sdata_803855ff_80385600.o +out\ghttpProcess.o +out\dol\sbss_803863a4_803863a8.o +out\dol\sdata2_80388484_80388488.o +out\gbucket.o +out\dol\data_8027da43_8027da48.o +out\dol\bss_802f3624_802f3820.o +out\dol\sdata_80385655_80385658.o +out\gstats.o +out\dol\text_8011a054_8011c10c.o +out\sb_crypt.o +out\dol\data_8027dca0_8027ddc0.o +out\dol\sdata_803856be_803856f0.o +out\sb_queryengine.o +out\dol\data_8027dde5_8027dde8.o +out\dol\sbss_803863dc_80386430.o +out\sb_server.o +out\dol\data_8027ddf1_8027ddf8.o +out\dol\sdata_80385721_80385728.o +out\sb_serverbrowsing.o +out\dol\sdata_8038572c_80385730.o +out\dol\sbss_8038643c_80386440.o +out\sb_serverlist.o +out\dol\data_8027de44_8027de48.o +out\dol\bss_802f3a20_802f3f40.o +out\sakeMain.o +out\dol\text_8012249c_80124500.o +out\dol\data_8027df3e_8027e708.o +out\dol\sdata_80385744_803857f0.o out\rvlArchive.o out\dol\text_80124e80_801981ec.o -out\dol\bss_802bd4ec_80346cf0.o -out\dol\sbss_803862b0_80386838.o +out\dol\bss_802f4040_80346cf0.o +out\dol\sbss_80386448_80386838.o out\rvlMemHeap.o out\rvlMemExpHeap.o out\rvlMemFrmHeap.o out\rvlMemUnitHeap.o -out\dol\sdata2_80387eb4_80388860.o +out\dol\sdata2_803884a4_80388860.o out\rvlMemAllocator.o out\rvlMemList.o out\dol\sdata_803857f6_80385a08.o out\rvlMtx.o out\rvlMtx2.o out\rvlVec.o -out\dol\rodata_80249020_80252c78.o +out\dol\rodata_8024c6db_80252c78.o out\dol\sdata2_803888b4_803888b8.o out\rvlQuat.o out\dol\text_8019b178_801ae5d8.o @@ -53,11 +179,19 @@ out\dol\sbss_8038683c_80386998.o out\rvlPad.o out\dol\sbss_803869c4_803869f0.o out\siBios.o -out\dol\text_801b0180_8020f62c.o -out\dol\data_8029ccd8_802a2668.o +out\dol\text_801b0180_801ec088.o +out\dol\data_8029ccd8_802a2318.o +out\dol\bss_80348230_80357220.o +out\dol\sdata_80385b28_80385ee0.o +out\dol\sbss_803869f8_80386d30.o +out\soCommon.o +out\dol\data_802a24f4_802a24f8.o +out\soBasic.o +out\dol\text_801ecff4_8020f62c.o +out\dol\data_802a2543_802a2668.o out\eggAllocator.o -out\dol\bss_80348230_803832d8.o -out\dol\sbss_803869f8_80386d80.o +out\dol\bss_80357238_803832d8.o +out\dol\sbss_80386d38_80386d80.o out\eggArchive.o out\dol\text_8020fcc4_8021a0f0.o out\dol\data_802a268c_802a2b48.o @@ -103,7 +237,7 @@ out\dol\dtors_80244ea4_80244eac.o out\dol\rodata_80258560_80258580.o out\dol\data_802a4004_802a4040.o out\dol\bss_80384bf4_80384c00.o -out\dol\sdata_80385b28_80385fc0.o +out\dol\sdata_80385eec_80385fc0.o out\dol\sbss_80386f90_80386fa0.o out\dol\sdata2_80389118_80389140.o out\dol\sbss2_80389140_8038917c.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 59e2a7c7d..a5ab194f9 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,39 +1,91 @@ -enabled,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End -1,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, -1,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0 ,0x8006a518,,,,,,,,,0x802bd4ec,0x802bd4ec,,,,,0x80387cac,0x80387cd8,, -1,source/nw4r/g3d/g3d_fog.cpp,,,,,,,0x800774d0,0x800775d0,,,,,,,,,,,,,,,0x80387d58,0x80387d5C,, -1,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, -1,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085938,,,,,,,,,,,,,,,0x80387ea8,0x80387eb4,, -1,source/nw4r/ut/utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, -1,source/dwc/common/dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, -1,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, -,rxFrameHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, -1,source/rvl/mem/rvlMemHeap.c,,,,,,,0x801981ec,0x80198798,,,,,,,,,0x80346cf0,0x80346d18,,,0x80386838,0x8038683C,,,, -1,source/rvl/mem/rvlMemExpHeap.c,,,,,,,0x80198798,0x80199430,,,,,,,,,,,,,,,,,, -1,source/rvl/mem/rvlMemFrmHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, -1,source/rvl/mem/rvlMemUnitHeap.c,,,,,,,0x801998A4,0x80199b58,,,,,,,,,,,,,,,,,, -1,source/rvl/mem/rvlMemAllocator.c,,,,,,,0x80199b58,0x80199bf0,,,,,,,,,,,,,,,0x80388860,0x80388870,, -1,source/rvl/mem/rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, -1,source/rvl/mtx/rvlMtx.c,,,,,,,0x80199d04,0x8019a9c4,,,,,,,,,,,0x80385a08,0x80385a10,,,0x80388870,0x80388890,, -1,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, -1,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, -1,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b178,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888cc,, -1,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, -1,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, -1,source/rvl/si/siBios.c,,,,,,,,,,,,,,,,,,,,,0x803869f0,0x803869f8,,,, -1,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680 ,,,,,,,,,, -1,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, -1,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54 ,,,,,,,,,, -1,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, -1,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, -1,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68 ,0x80388D80,, -1,source/egg/math/eggQuat.cpp,,,,,,,0x80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, -,source/egg/core/eggScene.cpp,,,,,,,0x8023AD10,0x8023ADDC,,,,,,,,,,,,,,,,,, -1,source/egg/core/eggStreamDecomp.cpp,,,,,,,0x80242498,0x80242504,,,,,,,0x802A3F78,0x802A3F90,,,,,,,,,, -,source/egg/core/eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,0x80386F60,0x80386F64,,,, -1,source/egg/core/eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, -1,source/egg/core/eggUnitHeap.cpp,,,,,,,0x80243754,0x80243A00,,,,,,,0x802A3FD8,0x802A4004,,,,,,,,,, -1,source/egg/math/eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,0x80384B70,0x80384BF4,,,0x80386F78,0x80386F90,0x803890F8 ,0x80389104,, -1,source/egg/core/eggVideo.cpp,,,,,,,0x80243D18,0x80244074,,,,,0x802582E0,0x80258560,,,,,,,,,0x80389108,0x80389118,, -,eggXfb.cpp,,,,,,,0x80244160,0x80244200,,,,,,,,,,,,,,,,,, -,eggXfbManager.cpp,,,,,,,0x80244200,0x802443AC,,,,,,,,,,,,,,,,,, +enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End +1,,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, +1,,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0 ,0x8006a518,,,,,,,,,0x802bd4ec,0x802bd4ec,,,,,0x80387cac,0x80387cd8,, +1,,source/nw4r/g3d/g3d_fog.cpp,,,,,,,0x800774d0,0x800775d0,,,,,,,,,,,,,,,0x80387d58,0x80387d5C,, +1,,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, +1,,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085938,,,,,,,,,,,,,,,0x80387ea8,0x80387eb4,, +1,,source/nw4r/ut/utList.cpp,,,,,,,0x800AEF60,0x800af1a0,,,,,,,,,,,,,,,,,, +1,,source/dwc/common/dwc_error.c,,,,,,,0x800CCB4C,0x800CCC80,,,,,,,0x80275700,0x80275758,,,,,0x803862A8,0x803862B0,,,, +1,,source/gamespy/darray.c,,,,,,,0x800ef378,0x800efdcc,,,,,,,,,,,,,,,,,, +1,,source/gamespy/hashtable.c,,,,,,,0x800efdcc,0x800f0264,,,,,,,,,,,,,,,,,, +1,,source/gamespy/md5c.c,,,,,,,0x800f0264,0x800f118c,,,,,0x8024c6b8,0x8024c6c9,0x8027aca0,0x8027ace0,,,,,,,,,, +1,,source/gamespy/common/revolution/gsSocketRevolution.c,,,,,,,0x800f118c,0x800f164c,,,,,,,,,,,,,0x80386350,0x80386358,,,, +1,,source/gamespy/common/revolution/gsUtilRevolution.c,,,,,,,0x800f1f58,0x800f2048,,,,,,,,,,,,,,,,,, +,,source/gamespy/common/gsPlatformUtil.c,,,,,,,0x800f1f58,0x800f3844,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/common/gsAvailable.c,,,,,,,0x800f38a4,0x800f3a20,,,,,,,0x8027ad58,0x8027ad79,0x802f2338,0x802f2410,,,0x80386358,0x8038635c,,,, +,1,source/gamespy/common/gsCore.c,,,,,,,0x800f3c08 ,0x800f41dc,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/common/gsUdpEngine.c,,,,,,,0x800f489c,0x800f5a6c,,,,,,,,,0x802f2440,0x802f247c,,,,,,,, +1,1,source/gamespy/common/gsXML.c,,,,,,,0x800f5a6c,0x800fb828,,,,,,,0x8027ad80,0x8027af18,,,0x803850a0,0x80385105,,,,,, +1,1,source/gamespy/gp/gp.c,,,,,,,0x800fb828,0x800fc7d4,,,,,,,0x8027af18,0x8027b289,,,0x80385108,0x80385118,,,,,, +1,1,source/gamespy/gp/gpi.c,,,,,,,0x800fc7d4,0x800fd160,,,,,,,0x8027b290,0x8027b30f,,,0x80385118,0x80385150,,,,,, +1,1,source/gamespy/gp/gpiBuddy.c,,,,,,,0x800fd160,0x800fee90,,,,,,,0x8027b310,0x8027b453,,,0x80385150,0x803851ea,,,,,, +1,1,source/gamespy/gp/gpiBuffer.c,,,,,,,0x800fee90,0x800ff8c4,,,,,,,0x8027b458,0x8027b4ba,,,0x803851f0,0x80385206,,,,,, +1,1,source/gamespy/gp/gpiCallback.c,,,,,,,0x800ff8c4,0x800ffe28 ,,,,,,,0x8027b4c0,0x8027b4cf,,,,,,,,,, +1,1,source/gamespy/gp/gpiConnect.c,,,,,,,0x800ffe28 ,0x80101470,,,,,,,0x8027b4d0,0x8027b876,,,0x80385208,0x8038529b,,,,,, +1,1,source/gamespy/gp/gpiInfo.c,,,,,,,0x80101470,0x80103908,,,,,,,0x8027b878,0x8027bbce,,,0x803852a0,0x80385355,,,0x80388470,0x80388474,, +1,1,source/gamespy/gp/gpiKeys.c,,,,,,,0x80103908,0x80103f70,,,,,,,0x8027bbd0,0x8027bc11,,,0x80385358,0x8038535a,,,,,, +1,1,source/gamespy/gp/gpiOperation.c,,,,,,,0x80103f70,0x80104648,,,,,,,0x8027bc18,0x8027bc60,,,,,,,,,, +1,1,source/gamespy/gp/gpiPeer.c,,,,,,,0x80104648,0x80105d54,,,,,,,0x8027bc60,0x8027bd2a,,,0x80385360,0x803853b9,,,,,, +1,1,source/gamespy/gp/gpiProfile.c,,,,,,,0x80105d54,0x8010669c,,,,,,,0x8027bd30,0x8027bee5,,,0x803853c0,0x803853ce,,,,,, +1,1,source/gamespy/gp/gpiSearch.c,,,,,,,0x8010669c,0x80108b38,,,,,,,0x8027bee8,0x8027c204,,,0x803853d0,0x803854bb,,,,,, +1,1,source/gamespy/gp/gpiTransfer.c,,,,,,,0x80108b38,0x80108c20,,,,,,,0x8027c208,0x8027c229,,,0x803854c0,0x803854cb,,,,,, +1,1,source/gamespy/gp/gpiUnique.c,,,,,,,0x80108c20,0x80108e78,,,,,,,0x8027c230,0x8027c26f,,,0x803854d0,0x803854dd,,,,,, +1,1,source/gamespy/gp/gpiUtility.c,,,,,,,0x80108e78,0x8010945c,,,,,,,0x8027c270,0x8027c2c5,,,0x803854e0,0x803854f8,,,,,, +1,1,source/gamespy/gt2/gt2Auth.c,,,,,,,0x8010945c,0x80109820,,,,,,,0x8027c2c8,0x8027c2e9,,,,,,,,,, +1,1,source/gamespy/gt2/gt2Buffer.c,,,,,,,0x80109820,0x801099c4,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/gt2/gt2Callback.c,,,,,,,0x801099c4,0x8010a244,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/gt2/gt2Connection.c,,,,,,,0x8010a244,0x8010a918,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/gt2/gt2Main.c,,,,,,,0x8010a918,0x8010ad14,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/gt2/gt2Socket.c,,,,,,,0x8010de30 ,0x8010e9c0,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/gt2/gt2Utility.c,,,,,,,0x8010e9c0,0x8010ecac,,,,,,,,,0x802f3480,0x802f34b0,0x80385508,0x80385519,0x80386360,0x80386364,,,, +1,1,source/gamespy/qr2/qr2.c,,,,,,,0x8010ecac,0x8011156c,,,,,,,0x8027c2f0,0x8027d21b,0x802f34c0,0x802f3624,0x80385520,0x8038554a,0x80386368,0x8038636c,0x80388478,0x8038847e,, +1,1,source/gamespy/qr2/qr2RegKeys.c,,,,,,,0x8011156c,0x801115fc,,,,,,,0x8027d220,0x8027d6f8,,,0x80385550,0x803855c7,,,,,, +1,1,source/gamespy/ghttp/ghttpBuffer.c,,,,,,,0x801115fc,0x80111de8,,,,,,,,,,,0x803855c8,0x803855d3,,,,,, +1,,source/gamespy/ghttp/ghttpCallbacks.c,,,,,,,0x80111f0c,0x8011202c,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/ghttp/ghttpCommon.c,,,,,,,0x8011202c,0x8011248c ,,,,,,,,,,,0x803855d8,0x803855e0,0x80386370,0x80386378,,,, +1,1,source/gamespy/ghttp/ghttpConnection.c,,,,,,,0x801125c8,0x80112d14,,,,,,,0x8027d6f8,0x8027d701,,,,,0x80386378,0x80386388,,,, +1,1,source/gamespy/ghttp/ghttpEncryption.c,,,,,,,0x80112d14,0x8011312c ,,,,,,,0x8027d708,0x8027d711,,,,,,,,,, +1,1,source/gamespy/ghttp/ghttpMain.c,,,,,,,0x8011312c ,0x801139b0 ,,,,,,,0x8027d718,0x8027d731,,,0x803855e0,0x803855e1,0x80386388,0x8038638c,,,, +1,1,source/gamespy/ghttp/ghttpPost.c,,,,,,,0x801139b0,0x80115384,,,,,,,0x8027d738,0x8027d974,,,0x803855e1,0x803855ff,0x80386390,0x803863a4,0x80388480,0x80388484,, +1,1,source/gamespy/ghttp/ghttpProcess.c,,,,,,,0x80115384,0x80116dd4 ,,,,,0x8024c6d0,0x8024c6db,0x8027d978,0x8027da43,,,0x80385600,0x80385655,,,,,, +1,1,source/gamespy/gstats/gbucket.c,,,,,,,0x80116dd4 ,0x80117f6c ,,,,,,,,,,,,,0x803863a8,0x803863c0,0x80388488,0x80388490,, +1,1,source/gamespy/gstats/gstats.c,,,,,,,0x80117f6c ,0x8011a054 ,,,,,,,0x8027da48,0x8027dca0,0x802f3820,0x802f3a20,0x80385658,0x803856be,0x803863c0,0x803863dc,,,, +1,1,source/gamespy/serverbrowsing/sb_crypt.c,,,,,,,0x8011c10c,0x8011c540,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/serverbrowsing/sb_queryengine.c,,,,,,,0x8011c540,0x8011d024,,,,,,,0x8027ddc0,0x8027dde5,,,0x803856f0,0x803856f8,,,,,, +1,1,source/gamespy/serverbrowsing/sb_server.c,,,,,,,0x8011d024,0x8011dd04,,,,,,,0x8027dde8,0x8027ddf1,,,0x803856f8,0x80385721,0x80386430,0x8038643c,0x80388490,0x80388498,, +1,1,source/gamespy/serverbrowsing/sb_serverbrowsing.c,,,,,,,0x8011dd04,0x8011e518,,,,,,,0x8027ddf8,0x8027de18,,,0x80385728,0x8038572c,,,,,, +1,1,source/gamespy/serverbrowsing/sb_serverlist.c,,,,,,,0x8011e518,0x80121eec ,,,,,,,0x8027de18,0x8027de44,,,0x80385730,0x80385740,0x80386440,0x80386448,0x80388498,0x803884a4,, +1,1,source/gamespy/sake/sakeMain.c,,,,,,,0x80121eec ,0x8012249c,,,,,,,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,,,,,, +1,,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, +,,rxFrameHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, +1,,source/rvl/mem/rvlMemHeap.c,,,,,,,0x801981ec,0x80198798,,,,,,,,,0x80346cf0,0x80346d18,,,0x80386838,0x8038683C,,,, +1,,source/rvl/mem/rvlMemExpHeap.c,,,,,,,0x80198798,0x80199430,,,,,,,,,,,,,,,,,, +1,,source/rvl/mem/rvlMemFrmHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, +1,,source/rvl/mem/rvlMemUnitHeap.c,,,,,,,0x801998A4,0x80199b58,,,,,,,,,,,,,,,,,, +1,,source/rvl/mem/rvlMemAllocator.c,,,,,,,0x80199b58,0x80199bf0,,,,,,,,,,,,,,,0x80388860,0x80388870,, +1,,source/rvl/mem/rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, +1,,source/rvl/mtx/rvlMtx.c,,,,,,,0x80199d04,0x8019a9c4,,,,,,,,,,,0x80385a08,0x80385a10,,,0x80388870,0x80388890,, +1,,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, +1,,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, +1,,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b178,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888cc,, +1,,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, +1,,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, +1,,source/rvl/si/siBios.c,,,,,,,,,,,,,,,,,,,,,0x803869f0,0x803869f8,,,, +1,,source/rvl/so/soCommon.c,,,,,,,0x801ec088,0x801ecf20,,,,,,,0x802a2318,0x802a24f4,0x80357220,0x80357238,0x80385ee0,0x80385ee8,0x80386D30,0x80386D38,,,, +1,,source/rvl/so/soBasic.c,,,,,,,0x801ecf20,0x801ecff4,,,,,,,0x802a24f8 ,0x802a2543,,,0x80385ee8,0x80385eeC,,,,,, +1,,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680 ,,,,,,,,,, +1,,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, +1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54 ,,,,,,,,,, +1,,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, +1,,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, +1,,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68 ,0x80388D80,, +1,,source/egg/math/eggQuat.cpp,,,,,,,0x80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, +,,source/egg/core/eggScene.cpp,,,,,,,0x8023AD10,0x8023ADDC,,,,,,,,,,,,,,,,,, +1,,source/egg/core/eggStreamDecomp.cpp,,,,,,,0x80242498,0x80242504,,,,,,,0x802A3F78,0x802A3F90,,,,,,,,,, +,,source/egg/core/eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,0x80386F60,0x80386F64,,,, +1,,source/egg/core/eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, +1,,source/egg/core/eggUnitHeap.cpp,,,,,,,0x80243754,0x80243A00,,,,,,,0x802A3FD8,0x802A4004,,,,,,,,,, +1,,source/egg/math/eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,0x80384B70,0x80384BF4,,,0x80386F78,0x80386F90,0x803890F8 ,0x80389104,, +1,1,source/egg/core/eggVideo.cpp,,,,,,,0x80243D18,0x80244074,,,,,0x802582E0,0x80258560,,,,,,,,,0x80389108,0x80389118,, +,,eggXfb.cpp,,,,,,,0x80244160,0x80244200,,,,,,,,,,,,,,,,,, +,,eggXfbManager.cpp,,,,,,,0x80244200,0x802443AC,,,,,,,,,,,,,,,,,, diff --git a/source/gamespy/GP/gp.c b/source/gamespy/GP/gp.c new file mode 100644 index 000000000..af6b2b347 --- /dev/null +++ b/source/gamespy/GP/gp.c @@ -0,0 +1,3106 @@ +/* +gp.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include +#include + +#include "gpi.h" + +#define NOFILE 1 + +// FUNCTIONS +/////////// +GPResult gpInitialize(GPConnection* connection, int productID, int namespaceID, + int partnerID) { + // Check if the backend is available. + ///////////////////////////////////// + if (__GSIACResult != GSIACAvailable) + return GP_PARAMETER_ERROR; + + // Error check. + /////////////// + if (connection == NULL) + return GP_PARAMETER_ERROR; + + return gpiInitialize(connection, productID, namespaceID, partnerID); +} + +void gpDestroy(GPConnection* connection) { + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return; + + gpiDestroy(connection); +} + +GPResult gpEnable(GPConnection* connection, GPEnum state) { + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + return gpiEnable(connection, state); +} + +GPResult gpDisable(GPConnection* connection, GPEnum state) { + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + return gpiDisable(connection, state); +} + +GPResult gpProcess(GPConnection* connection) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + return gpiProcess(connection, 0); +} + +GPResult gpSetCallback(GPConnection* connection, GPEnum func, + GPCallback callback, void* param) { + GPIConnection* iconnection; + int index; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Find which callback. + /////////////////////// + index = func; + if ((index < 0) || (index >= GPI_NUM_CALLBACKS)) + Error(connection, GP_PARAMETER_ERROR, "Invalid func."); + + // Set the info. + //////////////// + iconnection->callbacks[index].callback = callback; + iconnection->callbacks[index].param = param; + + return GP_NO_ERROR; +} + +GPResult gpConnectA(GPConnection* connection, const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], GPEnum firewall, + GPEnum blocking, GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if ((nick == NULL) || (nick[0] == '\0')) + return GP_PARAMETER_ERROR; + if ((email == NULL) || (email[0] == '\0')) + return GP_PARAMETER_ERROR; + if ((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, nick, "", email, password, "", "", NULL, + firewall, GPIFalse, blocking, callback, param); +} + +GPResult gpConnectNewUserA(GPConnection* connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum firewall, + GPEnum blocking, GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if ((nick == NULL) || (nick[0] == '\0')) + return GP_PARAMETER_ERROR; + if (uniquenick == NULL) + uniquenick = ""; + if ((email == NULL) || (email[0] == '\0')) + return GP_PARAMETER_ERROR; + if ((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + if (cdkey && (cdkey[0] == '\0')) + cdkey = NULL; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the nick. + //////////////////////////////// + if (strlen(nick) >= GP_NICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Nick too long."); + + // Check the length of the uniquenick. + ////////////////////////////////////// + if (strlen(uniquenick) >= GP_UNIQUENICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Uniquenick too long."); + + // Check the length of the email. + ///////////////////////////////// + if (strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if (strlen(password) >= GP_PASSWORD_LEN) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, nick, uniquenick, email, password, "", "", + cdkey, firewall, GPITrue, blocking, callback, param); +} + +GPResult gpConnectUniqueNickA(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char password[GP_PASSWORD_LEN], + GPEnum firewall, GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if ((uniquenick == NULL) || (uniquenick[0] == '\0')) + return GP_PARAMETER_ERROR; + if ((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, "", uniquenick, "", password, "", "", NULL, + firewall, GPIFalse, blocking, callback, param); +} + +GPResult gpConnectPreAuthenticatedA( + GPConnection* connection, const char authtoken[GP_AUTHTOKEN_LEN], + const char partnerchallenge[GP_PARTNERCHALLENGE_LEN], GPEnum firewall, + GPEnum blocking, GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if ((authtoken == NULL) || (authtoken[0] == '\0')) + return GP_PARAMETER_ERROR; + if ((partnerchallenge == NULL) || (partnerchallenge[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) { + GPConnectResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do it. + ///////// + return gpiConnect(connection, "", "", "", "", authtoken, partnerchallenge, + NULL, firewall, GPIFalse, blocking, callback, param); +} + +void gpDisconnect(GPConnection* connection) { + GPIConnection* iconnection; + int oldState; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) + return; + + // Make a note of connection state prior to reset + ///////////////////////////////////////////////// + oldState = iconnection->connectState; + + gpiDisconnect(connection, GPITrue); + // Added by Saad Nader + // 08-28-2004; fix for memory leaks after being disconnected abruptly + //////////////////////////////////////////////// + gpiReset(connection); + + // If we were connected prior, set to disconnected to save off info cache + ////////////////////////////////////////////////////////////////////////// + // TODO This was commented out + // if (oldState == GPI_CONNECTED) + // iconnection->connectState = GPI_DISCONNECTED; +} + +GPResult gpIsConnected(GPConnection* connection, GPEnum* connected) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Set the flag. + //////////////// + if (iconnection->connectState == GPI_CONNECTED) + *connected = GP_CONNECTED; + else + *connected = GP_NOT_CONNECTED; + + return GP_NO_ERROR; +} + +GPResult gpCheckUserA(GPConnection* connection, const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the nick. + //////////////////////////////// + if (strlen(nick) >= GP_NICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Nick too long."); + + // Check the length of the email. + ///////////////////////////////// + if (strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if (password && (strlen(password) >= GP_PASSWORD_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPCheckResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the check. + //////////////// + return gpiCheckUser(connection, nick, email, password, blocking, callback, + param); +} + +GPResult gpNewUserA(GPConnection* connection, const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if ((nick == NULL) || (nick[0] == '\0')) + return GP_PARAMETER_ERROR; + if (uniquenick == NULL) + uniquenick = ""; + if ((email == NULL) || (email[0] == '\0')) + return GP_PARAMETER_ERROR; + if ((password == NULL) || (password[0] == '\0')) + return GP_PARAMETER_ERROR; + if (cdkey && (cdkey[0] == '\0')) + cdkey = NULL; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the nick. + //////////////////////////////// + if (strlen(nick) >= GP_NICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Nick too long."); + + // Check the length of the uniquenick. + ////////////////////////////////////// + if (strlen(uniquenick) >= GP_UNIQUENICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Uniquenick too long."); + + // Check the length of the email. + ///////////////////////////////// + if (strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if (strlen(password) >= GP_PASSWORD_LEN) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPNewUserResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Creat the new user. + ////////////////////// + return gpiNewUser(connection, nick, uniquenick, email, password, cdkey, + blocking, callback, param); +} + +GPResult gpSuggestUniqueNickA(GPConnection* connection, + const char desirednick[GP_NICK_LEN], + GPEnum blocking, GPCallback callback, + void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the desirednick. + /////////////////////////////////////// + if (strlen(desirednick) >= GP_UNIQUENICK_LEN) + Error(connection, GP_PARAMETER_ERROR, "Desirednick too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPSuggestUniqueNickResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Creat the new user. + ////////////////////// + return gpiSuggestUniqueNick(connection, desirednick, blocking, callback, + param); +} + +GPResult gpRegisterUniqueNickA(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if ((uniquenick == NULL) || (uniquenick[0] == '\0')) + return GP_PARAMETER_ERROR; + if (cdkey && (cdkey[0] == '\0')) + cdkey = NULL; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPRegisterUniqueNickResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiRegisterUniqueNick(connection, uniquenick, cdkey, blocking, + callback, param); +} + +GPResult gpRegisterCdKeyA(GPConnection* connection, + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + if ((cdkey == NULL) || (cdkey[0] == '\0')) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPRegisterCdKeyResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiRegisterCdKey(connection, cdkey, blocking, callback, param); +} + +GPResult gpGetErrorCode(GPConnection* connection, GPErrorCode* errorCode) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Error check. + /////////////// + if (errorCode == NULL) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + *errorCode = (GPErrorCode)0; + return GP_NO_ERROR; + } + + // Set the code. + //////////////// + *errorCode = iconnection->errorCode; + + return GP_NO_ERROR; +} + +GPResult gpGetErrorStringA(GPConnection* connection, + char errorString[GP_ERROR_STRING_LEN]) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Error check. + /////////////// + if (errorString == NULL) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + errorString[0] = '\0'; + return GP_NO_ERROR; + } + + // Copy the error string. + ///////////////////////// + strzcpy(errorString, iconnection->errorString, GP_ERROR_STRING_LEN); + return GP_NO_ERROR; +} + +GPResult gpNewProfileA(GPConnection* connection, const char nick[GP_NICK_LEN], + GPEnum replace, GPEnum blocking, GPCallback callback, + void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for no nick. + // PANTS|05.18.00 + ///////////////////// + if ((nick == NULL) || (nick[0] == '\0')) + Error(connection, GP_PARAMETER_ERROR, "Invalid nick."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) { + GPNewProfileResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiNewProfile(connection, nick, replace, blocking, callback, param); +} + +GPResult gpDeleteProfile(GPConnection* connection, GPCallback callback, + void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) { + GPDeleteProfileResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiDeleteProfile(connection, callback, param); +} + +GPResult gpProfileFromID(GPConnection* connection, GPProfile* profile, int id) { + GSI_UNUSED(connection); + + // Set the profile. + // This function is depreciated & may be removed from future versions. + ////////////////////////////////////////////////////////////////////// + *profile = id; + + return GP_NO_ERROR; +} + +// gpIDFromProfile +////////////////// +GPResult gpIDFromProfile(GPConnection* connection, GPProfile profile, int* id) { + GSI_UNUSED(connection); + + // ID is the same as GPProfile + // This function is depreciated & may be removed from future versions. + ////////////////////////////////////////////////////////////////////// + *id = profile; + + return GP_NO_ERROR; +} + +// gpUserIDFromProfile +////////////////// +GPResult gpUserIDFromProfile(GPConnection* connection, GPProfile profile, + int* userid) { + GPIConnection* iconnection; + GPIProfile* pProfile; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + *userid = 0; + return GP_NO_ERROR; + } + + // Get the profile object. + ////////////////////////// + if (!gpiGetProfile(connection, profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Set the id. + ////////////// + *userid = pProfile->userId; + + return GP_NO_ERROR; +} + +GPResult gpProfileSearchA(GPConnection* connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char firstname[GP_FIRSTNAME_LEN], + const char lastname[GP_LASTNAME_LEN], int icquin, + GPEnum blocking, GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPProfileSearchResponseArg arg; + memset(&arg, 0, sizeof(arg)); + arg.more = GP_DONE; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the search. + ///////////////// + return gpiProfileSearch(connection, nick, uniquenick, email, firstname, + lastname, icquin, 0, blocking, callback, param); +} + +GPResult gpProfileSearchUniquenickA(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[GP_MAX_NAMESPACEIDS], + int numNamespaces, GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL) || (namespaceIDs == NULL) || + (numNamespaces < 1)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPProfileSearchResponseArg arg; + memset(&arg, 0, sizeof(arg)); + arg.more = GP_DONE; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the search. + ///////////////// + return gpiProfileSearchUniquenick(connection, uniquenick, namespaceIDs, + numNamespaces, blocking, callback, param); +} + +GPResult gpGetInfo(GPConnection* connection, GPProfile profile, + GPEnum checkCache, GPEnum blocking, GPCallback callback, + void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL) || (profile == 0)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPGetInfoResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiGetInfo(connection, profile, checkCache, blocking, callback, param); +} + +GPResult gpGetInfoNoWait(GPConnection* connection, GPProfile profile, + GPGetInfoResponseArg* arg) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL) || (profile == 0) || + (arg == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) { + memset(arg, 0, sizeof(arg)); + return GP_NO_ERROR; + } + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiGetInfoNoWait(connection, profile, arg); +} + +GPResult gpSetInfoi(GPConnection* connection, GPEnum info, int value) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiSetInfoi(connection, info, value); +} + +GPResult gpSetInfosA(GPConnection* connection, GPEnum info, const char* value) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiSetInfos(connection, info, value); +} + +GPResult gpSetInfod(GPConnection* connection, GPEnum info, int day, int month, + int year) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiSetInfod(connection, info, day, month, year); +} + +GPResult gpSetInfoMask(GPConnection* connection, GPEnum mask) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiSetInfoMask(connection, mask); +} + +GPResult gpSendBuddyRequestA(GPConnection* connection, GPProfile profile, + const char reason[GP_REASON_LEN]) { + GPIConnection* iconnection; + char reasonFixed[GP_REASON_LEN]; + int i; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Error check. + /////////////// + if (reason == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid reason."); + + // Replace backslashes in reason. + ///////////////////////////////// + strzcpy(reasonFixed, reason, GP_REASON_LEN); + for (i = 0; reasonFixed[i]; i++) + if (reasonFixed[i] == '\\') + reasonFixed[i] = '/'; + + // Send the request. + //////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\addbuddy\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\newprofileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\reason\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, reasonFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpAuthBuddyRequest(GPConnection* connection, GPProfile profile) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiAuthBuddyRequest(connection, profile); +} + +GPResult gpDenyBuddyRequest(GPConnection* connection, GPProfile profile) { + GPIConnection* iconnection; + GPIProfile* pProfile; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for simulation mode. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Get the profile. + /////////////////// + if (!gpiGetProfile(connection, profile, &pProfile)) + return GP_NO_ERROR; + + // freeclear the sig if no more requests. + //////////////////////////////////// + pProfile->requestCount--; + if (!iconnection->infoCaching && (pProfile->requestCount <= 0)) { + freeclear(pProfile->authSig); + if (gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + } + + return GP_NO_ERROR; +} + +GPResult gpGetNumBuddies(GPConnection* connection, int* numBuddies) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + *numBuddies = 0; + return GP_NO_ERROR; + } + + // Set the number of buddies. + ///////////////////////////// + *numBuddies = iconnection->profileList.numBuddies; + + return GP_NO_ERROR; +} + +GPResult gpGetBuddyStatus(GPConnection* connection, int index, + GPBuddyStatus* status) { + GPIConnection* iconnection; + int num; + GPIProfile* profile; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + memset(status, 0, sizeof(GPBuddyStatus)); + return GP_NO_ERROR; + } + + // Check for a NULL status. + /////////////////////////// + if (status == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid status."); + + // Check the buddy index. + ///////////////////////// + num = iconnection->profileList.numBuddies; + if ((index < 0) || (index >= num)) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + // Find the buddy with this index. + ////////////////////////////////// + profile = gpiFindBuddy(connection, index); + if (!profile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + // assert(buddyStatus || buddyStatusInfo); + + // assert(profile->buddyStatus); + + // @wait-wtf: + // NOTE: This is heavily edited. + // Probably edits by Nintendo or from an unknown version of Gamespy SDK. + if (profile->buddyStatusInfo && profile->buddyStatusInfo->statusState == 0) { + status->profile = (GPProfile)profile->profileId; + status->status = profile->buddyStatusInfo->statusState; + status->statusString[0] = '\0'; + status->locationString[0] = '\0'; + status->ip = profile->buddyStatusInfo->buddyIp; + status->port = profile->buddyStatusInfo->buddyPort; + status->quietModeFlags = profile->buddyStatusInfo->quietModeFlags; + } else { + status->profile = (GPProfile)profile->profileId; + status->status = profile->buddyStatus->status; + if (profile->buddyStatus->statusString) + strzcpy(status->statusString, profile->buddyStatus->statusString, + GP_STATUS_STRING_LEN); + else + status->statusString[0] = '\0'; + if (profile->buddyStatus->locationString) + strzcpy(status->locationString, profile->buddyStatus->locationString, + GP_LOCATION_STRING_LEN); + else + status->locationString[0] = '\0'; + status->ip = profile->buddyStatus->ip; + status->port = profile->buddyStatus->port; + status->quietModeFlags = profile->buddyStatus->quietModeFlags; + } + return GP_NO_ERROR; +} + +GPResult gpSetBuddyAddr(GPConnection* connection, int index, + unsigned int buddyIp, unsigned short buddyPort) { + GPIConnection* iconnection; + int num; + GPIProfile* profile; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check the buddy index. + ///////////////////////// + num = iconnection->profileList.numBuddies; + if ((index < 0) || (index >= num)) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + // Find the buddy with this index. + ////////////////////////////////// + profile = gpiFindBuddy(connection, index); + if (!profile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + if (buddyIp == 0 || buddyPort == 0) + Error(connection, GP_PARAMETER_ERROR, "Invalid IP and port"); + if (profile->buddyStatusInfo) { + profile->buddyStatusInfo->buddyIp = buddyIp; + profile->buddyStatusInfo->buddyPort = buddyPort; + } + return GP_NO_ERROR; +} + +GPResult gpGetBuddyIndex(GPConnection* connection, GPProfile profile, + int* index) { + GPIProfile* pProfile; + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + *index = 0; + return GP_NO_ERROR; + } + + // Get the index. + ///////////////// + if (gpiGetProfile(connection, profile, &pProfile) && pProfile->buddyStatus) + *index = pProfile->buddyStatus->buddyIndex; + else if (gpiGetProfile(connection, profile, &pProfile) && + pProfile->buddyStatusInfo) + *index = pProfile->buddyStatusInfo->buddyIndex; + else + *index = -1; + + return GP_NO_ERROR; +} + +int gpIsBuddy(GPConnection* connection, GPProfile profile) { + GPIProfile* pProfile; + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return 0; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return 0; + + // Get the index. + ///////////////// + if (gpiGetProfile(connection, profile, &pProfile) && pProfile->buddyStatus) + return 1; + else if (gpiGetProfile(connection, profile, &pProfile) && + pProfile->buddyStatusInfo) + return 1; + + return 0; +} + +int gpIsBuddyConnectionOpen(GPConnection* connection, GPProfile profile) { + GPIConnection* iconnection; + GPIPeer* aPeer; + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return 0; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return 0; + + aPeer = gpiGetPeerByProfile(connection, profile); + + if (aPeer == NULL || !gpiIsPeerConnected(aPeer)) + return 0; // not connected + else + return 1; // connected +} + +GPResult gpDeleteBuddy(GPConnection* connection, GPProfile profile) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Delete the buddy. + //////////////////// + CHECK_RESULT(gpiDeleteBuddy(connection, profile, GPITrue)); + + return GP_NO_ERROR; +} + +#ifndef GP_NEW_STATUS_INFO +GPResult gpSetStatusA(GPConnection* connection, GPEnum status, + const char statusString[GP_STATUS_STRING_LEN], + const char locationString[GP_LOCATION_STRING_LEN]) { + char statusStringFixed[GP_STATUS_STRING_LEN]; + char locationStringFixed[GP_LOCATION_STRING_LEN]; + GPIConnection* iconnection; + int i; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Error check. + /////////////// + if (statusString == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid statusString."); + if (locationString == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid locationString."); + + // Replace backslashes with slashes. + //////////////////////////////////// + strzcpy(statusStringFixed, statusString, GP_STATUS_STRING_LEN); + for (i = 0; statusStringFixed[i]; i++) + if (statusStringFixed[i] == '\\') + statusStringFixed[i] = '/'; + strzcpy(locationStringFixed, locationString, GP_LOCATION_STRING_LEN); + for (i = 0; locationStringFixed[i]; i++) + if (locationStringFixed[i] == '\\') + locationStringFixed[i] = '/'; + + // Don't send it if its the same as the previous. + ///////////////////////////////////////////////// + if ((status == iconnection->lastStatusState) && + (strcmp(statusStringFixed, iconnection->lastStatusString) == 0) && + (strcmp(locationStringFixed, iconnection->lastLocationString) == 0)) { + return GP_NO_ERROR; + } + + // Copy off the new status. + /////////////////////////// + iconnection->lastStatusState = status; + strzcpy(iconnection->lastStatusString, statusStringFixed, + GP_STATUS_STRING_LEN); + strzcpy(iconnection->lastLocationString, locationStringFixed, + GP_LOCATION_STRING_LEN); + + // Send the new status. + /////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\status\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, status); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\statstring\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + statusStringFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\locstring\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + locationStringFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} +#endif + +GPResult gpSetStatusInfoA(GPConnection* connection, GPEnum statusState, + unsigned int hostIp, unsigned int hostPrivateIp, + unsigned short queryPort, unsigned short hostPort, + unsigned int sessionFlags, const char* richStatus, + int richStatusLen, const char* gameType, + int gameTypeLen, const char* gameVariant, + int gameVariantLen, const char* gameMapName, + int gameMapNameLen) { + GPIConnection* iconnection; + + char gameTypeFixed[GP_STATUS_BASIC_STR_LEN]; + char gameVariantFixed[GP_STATUS_BASIC_STR_LEN]; + char gameMapNameFixed[GP_STATUS_BASIC_STR_LEN]; + + GS_ASSERT(connection != NULL); + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Error check. + /////////////// + GS_ASSERT(richStatus != NULL); + if (richStatus == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid richStatus."); + + GS_ASSERT(richStatusLen <= GP_RICH_STATUS_LEN); + GS_ASSERT(gameTypeLen <= GP_STATUS_BASIC_STR_LEN); + GS_ASSERT(gameVariantLen <= GP_STATUS_BASIC_STR_LEN); + GS_ASSERT(gameMapNameLen <= GP_STATUS_BASIC_STR_LEN); + + if (gameType == NULL) + strncpy(gameTypeFixed, "", GP_STATUS_BASIC_STR_LEN); + else + strncpy(gameTypeFixed, gameType, GP_STATUS_BASIC_STR_LEN); + if (gameVariant == NULL) + strncpy(gameVariantFixed, "", GP_STATUS_BASIC_STR_LEN); + else + strncpy(gameVariantFixed, gameVariant, GP_STATUS_BASIC_STR_LEN); + if (gameMapName == NULL) + strncpy(gameMapNameFixed, "", GP_STATUS_BASIC_STR_LEN); + else + strncpy(gameMapNameFixed, gameMapName, GP_STATUS_BASIC_STR_LEN); + + // Don't send it if its the same as the previous. + ///////////////////////////////////////////////// + if ((statusState == iconnection->lastStatusState) && + (strcmp(richStatus, iconnection->richStatus) == 0) && + (strcmp(gameTypeFixed, iconnection->gameType) == 0) && + (strcmp(gameVariantFixed, iconnection->gameVariant) == 0) && + (strcmp(gameMapNameFixed, iconnection->gameMapName) == 0) && + (sessionFlags == iconnection->sessionFlags) && + (hostIp == iconnection->hostIp) && + (hostPrivateIp == iconnection->hostPrivateIp) && + (queryPort == iconnection->queryPort) && + (hostPort == iconnection->hostPort)) { + return GP_NO_ERROR; + } + + iconnection->lastStatusState = statusState; + iconnection->hostIp = hostIp; + iconnection->hostPrivateIp = hostPrivateIp; + iconnection->queryPort = queryPort; + iconnection->hostPort = hostPort; + iconnection->sessionFlags = sessionFlags; + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\statusinfo\\\\state\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, statusState); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\hostIp\\"); + gpiAppendUIntToBuffer(connection, &iconnection->outputBuffer, ntohl(hostIp)); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\hprivIp\\"); + gpiAppendUIntToBuffer(connection, &iconnection->outputBuffer, + ntohl(hostPrivateIp)); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\qport\\"); + gpiAppendUShortToBuffer(connection, &iconnection->outputBuffer, queryPort); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\hport\\"); + gpiAppendUShortToBuffer(connection, &iconnection->outputBuffer, hostPort); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sessflags\\"); + gpiAppendUIntToBuffer(connection, &iconnection->outputBuffer, sessionFlags); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\richStatus\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, richStatus); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\gameType\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + gameTypeFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\gameVariant\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + gameVariantFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\gameMapName\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + gameMapNameFixed); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + GSI_UNUSED(richStatusLen); + GSI_UNUSED(gameTypeLen); + GSI_UNUSED(gameVariantLen); + GSI_UNUSED(gameMapNameLen); + return GP_NO_ERROR; +} + +GPResult gpAddStatusInfoKeyA(GPConnection* connection, const char* keyName, + const char* keyValue) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiStatusInfoAddKey(connection, iconnection->extendedInfoKeys, keyName, + keyValue); +} + +GPResult gpSetStatusInfoKeyA(GPConnection* connection, const char* keyName, + const char* keyValue) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiStatusInfoSetKey(connection, iconnection->extendedInfoKeys, keyName, + keyValue); +} + +GPResult gpDelStatusInfoKeyA(GPConnection* connection, const char* keyName) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiStatusInfoDelKey(connection, iconnection->extendedInfoKeys, + keyName); +} + +GPResult gpGetStatusInfoKeyValA(GPConnection* connection, const char* keyName, + char** keyValue) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + return gpiStatusInfoGetKey(connection, iconnection->extendedInfoKeys, keyName, + keyValue); +} + +GPResult gpGetBuddyStatusInfoKeys(GPConnection* connection, int index, + GPCallback callback, void* userData) { + GPIConnection* iconnection; + GPIProfile* pProfile; + GPResult aResult; + GPIPeerOp* aPeerOp; + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + pProfile = gpiFindBuddy(connection, index); + if (!pProfile) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + + if (pProfile->buddyStatus) + CallbackError(connection, GP_PARAMETER_ERROR, GP_BM_EXT_INFO_NOT_SUPPORTED, + "The profile does not support extended info keys."); + + if (!pProfile->buddyStatusInfo && !pProfile->buddyStatus) + CallbackError(connection, GP_PARAMETER_ERROR, GP_BM_NOT_BUDDY, + "The profile used to get extended info keys is not a buddy."); + + if (pProfile->buddyStatusInfo && + pProfile->buddyStatusInfo->statusState == GP_OFFLINE) + CallbackError(connection, GP_NETWORK_ERROR, GP_BM_BUDDY_OFFLINE, + "The profile used to get extended info keys is offline."); + + aPeerOp = (GPIPeerOp*)gsimalloc(sizeof(GPIPeerOp)); + aPeerOp->callback = callback; + aPeerOp->next = NULL; + aPeerOp->state = GPI_PEER_OP_STATE_REQUESTED; + aPeerOp->type = GPI_BM_KEYS_REQUEST; + aPeerOp->userData = userData; + aPeerOp->timeout = current_time() + GPI_PEER_OP_TIMEOUT; + aResult = + gpiSendBuddyMessage(connection, pProfile->profileId, GPI_BM_KEYS_REQUEST, + "Keys?", GP_DONT_ROUTE, aPeerOp); + return aResult; +} + +GPResult gpSendBuddyMessageA(GPConnection* connection, GPProfile profile, + const char* message) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Error check. + /////////////// + if (message == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid message."); + + return gpiSendBuddyMessage(connection, profile, GPI_BM_MESSAGE, message, 0, + NULL); +} + +GPResult gpSendBuddyUTMA(GPConnection* connection, GPProfile profile, + const char* message, int sendOption) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Error check. + /////////////// + if (message == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid message."); + + return gpiSendBuddyMessage(connection, profile, GPI_BM_UTM, message, + sendOption, NULL); +} + +GPResult gpIsValidEmailA(GPConnection* connection, + const char email[GP_EMAIL_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the email. + ///////////////////////////////// + if (strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPIsValidEmailResponseArg arg; + memset(&arg, 0, sizeof(arg)); + strzcpy(arg.email, email, GP_EMAIL_LEN); + arg.isValid = GP_INVALID; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the validation. + ///////////////////// + return gpiIsValidEmail(connection, email, blocking, callback, param); +} + +GPResult gpGetUserNicksA(GPConnection* connection, + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Check the length of the email. + ///////////////////////////////// + if (strlen(email) >= GP_EMAIL_LEN) + Error(connection, GP_PARAMETER_ERROR, "Email too long."); + + // Check the length of the password. + //////////////////////////////////// + if (strlen(password) >= GP_PASSWORD_LEN) + Error(connection, GP_PARAMETER_ERROR, "Password too long."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPGetUserNicksResponseArg arg; + memset(&arg, 0, sizeof(arg)); + strzcpy(arg.email, email, GP_EMAIL_LEN); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Do the validation. + ///////////////////// + return gpiGetUserNicks(connection, email, password, blocking, callback, + param); +} + +GPResult gpSetInvitableGames(GPConnection* connection, int numProductIDs, + const int* productIDs) { + GPIConnection* iconnection; + int i; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Error check. + /////////////// + if (numProductIDs < 0) + Error(connection, GP_PARAMETER_ERROR, "Invalid numProductIDs."); + if ((numProductIDs > 0) && (productIDs == NULL)) + Error(connection, GP_PARAMETER_ERROR, "Invalid productIDs."); + + // Send the list. + ///////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\inviteto\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\products\\"); + for (i = 0; i < numProductIDs; i++) { + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, productIDs[i]); + if (i < (numProductIDs - 1)) + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, ","); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpFindPlayers(GPConnection* connection, int productID, GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPFindPlayersResponseArg arg; + memset(&arg, 0, sizeof(arg)); + arg.productID = productID; + arg.numMatches = 0; + arg.matches = NULL; + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Start the find. + ////////////////// + return gpiFindPlayers(connection, productID, blocking, callback, param); +} + +GPResult gpInvitePlayerA(GPConnection* connection, GPProfile profile, + int productID, + const char location[GP_LOCATION_STRING_LEN]) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Send the invite. + /////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\pinvite\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\productid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, productID); + + if (location && location[0]) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\location\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, location); + } + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpGetReverseBuddies(GPConnection* connection, GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPGetReverseBuddiesResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Start the search. + //////////////////// + return gpiOthersBuddy(connection, blocking, callback, param); +} + +GPResult gpGetReversBuddiesList(GPConnection* connection, GPProfile* targets, + int numOfTargets, GPEnum blocking, + GPCallback callback, void* param) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for no callback. + ///////////////////////// + if (callback == NULL) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) { + GPGetReverseBuddiesListResponseArg arg; + memset(&arg, 0, sizeof(arg)); + callback(connection, &arg, param); + return GP_NO_ERROR; + } + + // Start the search. + //////////////////// + return gpiOthersBuddyList(connection, targets, numOfTargets, blocking, + callback, param); +} + +GPResult gpRevokeBuddyAuthorization(GPConnection* connection, + GPProfile profile) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + return GP_NO_ERROR; + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Send the invite. + /////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\revoke\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profile); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpGetLoginTicket(GPConnection* connection, + char loginTicket[GP_LOGIN_TICKET_LEN]) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + memcpy(loginTicket, iconnection->loginTicket, GP_LOGIN_TICKET_LEN); + return GP_NO_ERROR; +} + +GPResult gpSetQuietMode(GPConnection* connection, GPEnum flags) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Store the flags locally. + /////////////////////////// + iconnection->quietModeFlags = flags; + + // Check for a connection. + ////////////////////////// + if (iconnection->connectState == GPI_CONNECTED) { + // Send the flags. + ////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\quiet\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->quietModeFlags); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\final\\"); + } + + return GP_NO_ERROR; +} + +#ifndef NOFILE +void gpSetInfoCacheFilenameA(const char* filename) { + gpiSetInfoCacheFilename(filename); +} +void gpSetInfoCacheFilenameW(const unsigned short* filename) { + char* filename_A = UCS2ToUTF8StringAlloc(filename); + gpiSetInfoCacheFilename(filename_A); + gsifree(filename_A); +} + +static GPResult gpiAddSendingFileA(GPConnection* connection, + GPITransfer* transfer, const char* path, + const char* name) { + GPIFile* file = NULL; + int size = 0; + gsi_time modTime = 0; + + // Check for a bad path or name. + //////////////////////////////// + if (!path && !name) + Error(connection, GP_PARAMETER_ERROR, "File missing path and name."); + if (path && !path[0]) + Error(connection, GP_PARAMETER_ERROR, "Empty path."); + if (name && !name[0]) + Error(connection, GP_PARAMETER_ERROR, "Empty name."); + + // Check that the file exists and is readable. + ////////////////////////////////////////////// + if (path) { + FILE* fileVerify; + + fileVerify = fopen(path, "r"); + if (!fileVerify) + Error(connection, GP_PARAMETER_ERROR, "Can't find file."); + + if (!gpiGetTransferFileInfo(fileVerify, &size, &modTime)) { + fclose(fileVerify); + Error(connection, GP_PARAMETER_ERROR, "Can't get info on file."); + } + + fclose(fileVerify); + } + + // Validate the name. + ///////////////////// + if (name) { + size_t len; + + len = strlen(name); + + if (strstr(name, "//") || strstr(name, "\\\\")) + Error(connection, GP_PARAMETER_ERROR, "Empty directory in filename."); + if (strstr(name, "./") || strstr(name, ".\\") || (name[len - 1] == '.')) + Error(connection, GP_PARAMETER_ERROR, "Directory level in filename."); + if ((name[0] == '/') || (name[0] == '\\')) + Error(connection, GP_PARAMETER_ERROR, + "Filename can't start with a slash."); + if (strcspn(name, ":*?\"<>|\n") != len) + Error(connection, GP_PARAMETER_ERROR, "Invalid character in filename."); + } + // The name is the path's title. + //////////////////////////////// + else { + const char* str; + + // Find the end of the path. + //////////////////////////// + name = strrchr(path, '/'); + str = strrchr(path, '\\'); + if (str > name) + name = str; + + // Point the name at the title. + /////////////////////////////// + if (name) + name++; + else + name = path; + } + + // Add this to the list. + //////////////////////// + file = gpiAddFileToTransfer(transfer, path, name); + if (!file) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Set the size and time. + ///////////////////////// + file->size = size; + file->modTime = modTime; + + // Update the total size. + ///////////////////////// + transfer->totalSize += size; + + return GP_NO_ERROR; +} + +GPResult gpSendFilesA(GPConnection* connection, GPTransfer* transfer, + GPProfile profile, const char* message, + gpSendFilesCallback callback, void* param) { + GPIConnection* iconnection; + GPITransfer* pTransfer; + GPResult result; + const gsi_char* path; + const gsi_char* name; + int numFiles; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Check for simulation mode. + ///////////////////////////// + if (iconnection->simulation) + Error(connection, GP_PARAMETER_ERROR, + "Cannot send files in simulation mode."); + + // Check for disconnected. + ////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + Error(connection, GP_PARAMETER_ERROR, + "The connection has already been disconnected."); + + // Check other stuff. + ///////////////////// + if (!callback) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + if (!iconnection->callbacks[GPI_TRANSFER_CALLBACK].callback) + Error(connection, GP_PARAMETER_ERROR, "No callback."); + + // No message is an empty message. + ////////////////////////////////// + if (!message) + message = ""; + + // Create the transfer object. + ////////////////////////////// + CHECK_RESULT(gpiNewSenderTransfer(connection, &pTransfer, profile)); + + // Fill in the message. + /////////////////////// + pTransfer->message = goastrdup(message); + if (!pTransfer->message) { + gpiFreeTransfer(connection, pTransfer); + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // Add all the files. + ///////////////////// + numFiles = 0; + do { + path = NULL; + name = NULL; + callback(connection, numFiles++, &path, &name, param); + if (path && !path[0]) + path = NULL; + if (name && !name[0]) + name = NULL; + + if (name || path) { + result = gpiAddSendingFileA(connection, pTransfer, path, name); + if (result != GP_NO_ERROR) { + gpiFreeTransfer(connection, pTransfer); + return result; + } + } + } while (name || path); + + // Check that we got at least 1 file. + ///////////////////////////////////// + if (!ArrayLength(pTransfer->files)) { + gpiFreeTransfer(connection, pTransfer); + Error(connection, GP_PARAMETER_ERROR, "No files to send."); + } + + // Ping the receiver. + ///////////////////// + result = gpiSendBuddyMessage(connection, profile, GPI_BM_PING, "1", 0, NULL); + if (result != GP_NO_ERROR) { + gpiFreeTransfer(connection, pTransfer); + return result; + } + + // Successful so far. + ///////////////////// + if (transfer) + *transfer = pTransfer->localID; + + return GP_NO_ERROR; +} +GPResult gpSendFilesW(GPConnection* connection, GPTransfer* transfer, + GPProfile profile, const unsigned short* message, + gpSendFilesCallback callback, void* param) { + char* message_A = NULL; + GPResult result; + + if (message == NULL) + return gpSendFilesA(connection, transfer, profile, NULL, callback, param); + + message_A = UCS2ToUTF8StringAlloc(message); + result = + gpSendFilesA(connection, transfer, profile, message_A, callback, param); + gsifree(message_A); + return result; +} + +GPResult gpAcceptTransferA(GPConnection* connection, GPTransfer transfer, + const char* message) { + GPITransfer* pTransfer; + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Check that we have a directory set. + ////////////////////////////////////// + if (!pTransfer->baseDirectory) + Error(connection, GP_PARAMETER_ERROR, "No transfer directory set."); + + // Check if this transfer has been cancelled. + ///////////////////////////////////////////// + if (pTransfer->state & GPITransferCancelled) + Error(connection, GP_PARAMETER_ERROR, "Transfer already cancelled."); + + // Send a reply. + //////////////// + CHECK_RESULT(gpiSendTransferReply(connection, &pTransfer->transferID, + pTransfer->peer, GPI_ACCEPTED, message)); + + // We're now transferring. + ////////////////////////// + pTransfer->state = GPITransferTransferring; + + // Set the current file index to the first file. + //////////////////////////////////////////////// + pTransfer->currentFile = 0; + + return GP_NO_ERROR; +} +GPResult gpAcceptTransferW(GPConnection* connection, GPTransfer transfer, + const unsigned short* message) { + char* message_A = NULL; + GPResult result; + + if (message == NULL) + return gpAcceptTransferA(connection, transfer, NULL); + + message_A = UCS2ToUTF8StringAlloc(message); + result = gpAcceptTransferA(connection, transfer, message_A); + gsifree(message_A); + return result; +} + +GPResult gpRejectTransferA(GPConnection* connection, GPTransfer transfer, + const char* message) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + return GP_NO_ERROR; + + // Check if this transfer has been cancelled. + ///////////////////////////////////////////// + if (pTransfer->state & GPITransferCancelled) + return GP_NO_ERROR; + + // Send the reply. + ////////////////// + gpiSendTransferReply(connection, &pTransfer->transferID, pTransfer->peer, + GPI_REJECTED, message); + + // Free the transfer. + ///////////////////// + gpiFreeTransfer(connection, pTransfer); + + return GP_NO_ERROR; +} +GPResult gpRejectTransferW(GPConnection* connection, GPTransfer transfer, + const unsigned short* message) { + char* message_A = NULL; + GPResult result; + + if (message == NULL) + return gpRejectTransferA(connection, transfer, NULL); + + message_A = UCS2ToUTF8StringAlloc(message); + result = gpRejectTransferA(connection, transfer, message_A); + gsifree(message_A); + return result; +} + +GPResult gpFreeTransfer(GPConnection* connection, GPTransfer transfer) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + return GP_NO_ERROR; + + // Check if this should be a reject. + //////////////////////////////////// + if (!pTransfer->sender && (pTransfer->state == GPITransferWaiting)) + return gpRejectTransfer(connection, transfer, NULL); + + // Check for cancelling. + //////////////////////// + if (pTransfer->state < GPITransferComplete) + gpiCancelTransfer(connection, pTransfer); + + // Free the transfer. + ///////////////////// + gpiFreeTransfer(connection, pTransfer); + + return GP_NO_ERROR; +} + +GPResult gpSetTransferData(GPConnection* connection, GPTransfer transfer, + void* userData) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Set the data. + //////////////// + pTransfer->userData = userData; + + return GP_NO_ERROR; +} + +void* gpGetTransferData(GPConnection* connection, GPTransfer transfer) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + return NULL; + + // Return the data. + /////////////////// + return pTransfer->userData; +} + +GPResult gpSetTransferDirectoryA(GPConnection* connection, GPTransfer transfer, + const char* directory) { + GPITransfer* pTransfer; + char lastChar; + + if (!directory || !directory[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid directory."); + lastChar = directory[strlen(directory) - 1]; + if ((lastChar != '\\') && (lastChar != '/')) + Error(connection, GP_PARAMETER_ERROR, "Invalid directory."); + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // This has to be set before transferring. + ////////////////////////////////////////// + if (pTransfer->sender) + Error(connection, GP_PARAMETER_ERROR, "Sender has no transfer directory."); + if (pTransfer->state != GPITransferWaiting) + Error(connection, GP_PARAMETER_ERROR, + "Can only set transfer directory before transferring."); + + // Free any existing directory. + /////////////////////////////// + if (pTransfer->baseDirectory) + gsifree(pTransfer->baseDirectory); + pTransfer->baseDirectory = NULL; + + // Set the directory. + ///////////////////// + pTransfer->baseDirectory = goastrdup(directory); + if (!pTransfer->baseDirectory) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + return GP_NO_ERROR; +} +GPResult gpSetTransferDirectoryW(GPConnection* connection, GPTransfer transfer, + const unsigned short* directory) { + char* directory_A = UCS2ToUTF8StringAlloc(directory); + GPResult result = gpSetTransferDirectoryA(connection, transfer, directory_A); + gsifree(directory_A); + return result; +} + +GPResult gpSetTransferThrottle(GPConnection* connection, GPTransfer transfer, + int throttle) { + GPITransfer* pTransfer; + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Negative means no throttle. + ////////////////////////////// + if (throttle < 0) + throttle = -1; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Store the throttle setting. + ////////////////////////////// + pTransfer->throttle = throttle; + + // Send the rate. + ///////////////// + CHECK_RESULT(gpiPeerStartTransferMessage( + connection, pTransfer->peer, GPI_BM_FILE_TRANSFER_THROTTLE, + (GPITransferID_st)&pTransfer->transferID)); + gpiSendOrBufferString(connection, pTransfer->peer, "\\rate\\"); + gpiSendOrBufferInt(connection, pTransfer->peer, throttle); + gpiPeerFinishTransferMessage(connection, pTransfer->peer, NULL, 0); + + // If we're the sender, call the callback. + ////////////////////////////////////////// + if (pTransfer->sender) { + GPTransferCallbackArg* arg; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = pTransfer->localID; + arg->type = GP_TRANSFER_THROTTLE; + arg->num = throttle; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + + return GP_NO_ERROR; +} + +GPResult gpGetTransferThrottle(GPConnection* connection, GPTransfer transfer, + int* throttle) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the throttle. + //////////////////// + *throttle = pTransfer->throttle; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferProfile(GPConnection* connection, GPTransfer transfer, + GPProfile* profile) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the profile. + /////////////////// + *profile = pTransfer->profile; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferSide(GPConnection* connection, GPTransfer transfer, + GPEnum* side) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the side. + //////////////// + if (pTransfer->sender) + *side = GP_TRANSFER_SENDER; + else + *side = GP_TRANSFER_RECEIVER; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferSize(GPConnection* connection, GPTransfer transfer, + int* size) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the size. + //////////////// + *size = pTransfer->totalSize; + + return GP_NO_ERROR; +} + +GPResult gpGetTransferProgress(GPConnection* connection, GPTransfer transfer, + int* progress) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the progress. + //////////////////// + *progress = pTransfer->progress; + + return GP_NO_ERROR; +} + +GPResult gpGetNumFiles(GPConnection* connection, GPTransfer transfer, + int* num) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the progress. + //////////////////// + *num = ArrayLength(pTransfer->files); + + return GP_NO_ERROR; +} + +GPResult gpGetCurrentFile(GPConnection* connection, GPTransfer transfer, + int* index) { + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the current file. + //////////////////////// + *index = pTransfer->currentFile; + + return GP_NO_ERROR; +} + +GPResult gpSkipFile(GPConnection* connection, GPTransfer transfer, int index) { + GPIFile* file; + GPITransfer* pTransfer; + GPTransferCallbackArg* arg; + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if ((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile*)ArrayNth(pTransfer->files, index); + + // Are we already past this file? + ///////////////////////////////// + if (index < pTransfer->currentFile) + return GP_NO_ERROR; + + // Did we not get to this file yet? + /////////////////////////////////// + if (pTransfer->currentFile != index) { + // Mark it. + /////////// + file->flags |= GPI_FILE_SKIP; + + // If we're receiving, let the sender know we want to skip it. + ////////////////////////////////////////////////////////////// + if (!pTransfer->sender) + gpiSkipFile(connection, pTransfer, index, GPI_SKIP_USER_SKIP); + + return GP_NO_ERROR; + } + + // If we're receiving, delete our temp file. + //////////////////////////////////////////// + if (!pTransfer->sender && (index == pTransfer->currentFile) && file->file) { + fclose(file->file); + file->file = NULL; + remove(file->path); + } + + // Skip the current file. + ///////////////////////// + gpiSkipCurrentFile(connection, pTransfer, GPI_SKIP_USER_SKIP); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = pTransfer->localID; + arg->index = index; + arg->type = GP_FILE_SKIP; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GP_NO_ERROR; +} + +GPResult gpGetFileName(GPConnection* connection, GPTransfer transfer, int index, + gsi_char** name) { + GPIFile* file; + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if ((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile*)ArrayNth(pTransfer->files, index); + + // Get the name. + //////////////// + *name = file->name; + + return GP_NO_ERROR; +} + +GPResult gpGetFilePath(GPConnection* connection, GPTransfer transfer, int index, + gsi_char** path) { + GPIFile* file; + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if ((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile*)ArrayNth(pTransfer->files, index); + + // Get the path. + //////////////// + *path = file->path; + + return GP_NO_ERROR; +} + +GPResult gpGetFileSize(GPConnection* connection, GPTransfer transfer, int index, + int* size) { + GPIFile* file; + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if ((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile*)ArrayNth(pTransfer->files, index); + + // Get the size. + //////////////// + *size = file->size; + + return GP_NO_ERROR; +} + +GPResult gpGetFileProgress(GPConnection* connection, GPTransfer transfer, + int index, int* progress) { + GPIFile* file; + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if ((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile*)ArrayNth(pTransfer->files, index); + + // Get the progress. + //////////////////// + *progress = file->progress; + + return GP_NO_ERROR; +} + +GPResult gpGetFileModificationTime(GPConnection* connection, + GPTransfer transfer, int index, + gsi_time* modTime) { + GPIFile* file; + GPITransfer* pTransfer; + + // Get the transfer. + //////////////////// + pTransfer = gpiFindTransferByLocalID(connection, transfer); + if (!pTransfer) + Error(connection, GP_PARAMETER_ERROR, "Invalid transfer."); + + // Get the file. + //////////////// + if ((index < 0) || (index >= ArrayLength(pTransfer->files))) + Error(connection, GP_PARAMETER_ERROR, "Invalid index."); + file = (GPIFile*)ArrayNth(pTransfer->files, index); + + // Get the modTime. + /////////////////// + *modTime = file->modTime; + + return GP_NO_ERROR; +} + +GPResult gpGetNumTransfers(GPConnection* connection, int* num) { + GPIConnection* iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for NULL. + ////////////////// + if (num == NULL) + Error(connection, GP_PARAMETER_ERROR, "NULL pointer."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Set num. + /////////// + *num = ArrayLength(iconnection->transfers); + + return GP_NO_ERROR; +} + +GPResult gpGetTransfer(GPConnection* connection, int index, + GPTransfer* transfer) { + GPIConnection* iconnection; + int localID; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return GP_PARAMETER_ERROR; + + // Check for NULL. + ////////////////// + if (transfer == NULL) + Error(connection, GP_PARAMETER_ERROR, "NULL pointer."); + + // Get the connection object. + ///////////////////////////// + iconnection = (GPIConnection*)*connection; + + // Get the local ID. + //////////////////// + localID = gpiGetTransferLocalIDByIndex(connection, index); + + // Check if it was a bad index. + /////////////////////////////// + if (localID == -1) + Error(connection, GP_PARAMETER_ERROR, "Index out of range."); + + // Set the transfer they want. + ////////////////////////////// + *transfer = localID; + + return GP_NO_ERROR; +} +#endif + +#ifdef _DEBUG +void gpProfilesReport(GPConnection* connection, + void (*report)(const char* output)) { + // GPIConnection * iconnection; + + // Error check. + /////////////// + if ((connection == NULL) || (*connection == NULL)) + return; + + // Get the connection object. + ///////////////////////////// + // iconnection = (GPIConnection *)*connection; + + gpiReport(connection, report); +} +#endif diff --git a/source/gamespy/GP/gp.h b/source/gamespy/GP/gp.h new file mode 100644 index 000000000..a5bf57f83 --- /dev/null +++ b/source/gamespy/GP/gp.h @@ -0,0 +1,1198 @@ +/* +gp.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ +#pragma once + +// necessary for gsi_char and UNICODE support +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ENUMS +//////// +typedef enum _GPEnum { + // Callbacks + //////////// + GP_ERROR = 0, + GP_RECV_BUDDY_REQUEST, + GP_RECV_BUDDY_STATUS, + GP_RECV_BUDDY_MESSAGE, + GP_RECV_BUDDY_UTM, + GP_RECV_GAME_INVITE, + GP_TRANSFER_CALLBACK, + GP_RECV_BUDDY_AUTH, + GP_RECV_BUDDY_REVOKE, + + // Global States. + ///////////////// + GP_INFO_CACHING = 0x0100, + GP_SIMULATION, + GP_INFO_CACHING_BUDDY_AND_BLOCK_ONLY, + + // Blocking + /////////// + GP_BLOCKING = 1, + GP_NON_BLOCKING = 0, + + // Firewall + /////////// + GP_FIREWALL = 1, + GP_NO_FIREWALL = 0, + + // Check Cache + ////////////// + GP_CHECK_CACHE = 1, + GP_DONT_CHECK_CACHE = 0, + + // Is Valid Email. + // PANTS|02.15.00 + ////////////////// + GP_VALID = 1, + GP_INVALID = 0, + + // Fatal Error. + /////////////// + GP_FATAL = 1, + GP_NON_FATAL = 0, + + // Sex + ////// + GP_MALE = 0x0500, + GP_FEMALE, + GP_PAT, + + // Profile Search. + ////////////////// + GP_MORE = 0x0600, + GP_DONE, + + // Set Info + /////////// + GP_NICK = 0x0700, + GP_UNIQUENICK, + GP_EMAIL, + GP_PASSWORD, + GP_FIRSTNAME, + GP_LASTNAME, + GP_ICQUIN, + GP_HOMEPAGE, + GP_ZIPCODE, + GP_COUNTRYCODE, + GP_BIRTHDAY, + GP_SEX, + GP_CPUBRANDID, + GP_CPUSPEED, + GP_MEMORY, + GP_VIDEOCARD1STRING, + GP_VIDEOCARD1RAM, + GP_VIDEOCARD2STRING, + GP_VIDEOCARD2RAM, + GP_CONNECTIONID, + GP_CONNECTIONSPEED, + GP_HASNETWORK, + GP_OSSTRING, + GP_AIMNAME, // PANTS|03.20.01 + GP_PIC, + GP_OCCUPATIONID, + GP_INDUSTRYID, + GP_INCOMEID, + GP_MARRIEDID, + GP_CHILDCOUNT, + GP_INTERESTS1, + + // New Profile. + /////////////// + GP_REPLACE = 1, + GP_DONT_REPLACE = 0, + + // Is Connected. + //////////////// + GP_CONNECTED = 1, + GP_NOT_CONNECTED = 0, + + // Public mask. + /////////////// + GP_MASK_NONE = 0x00000000, + GP_MASK_HOMEPAGE = 0x00000001, + GP_MASK_ZIPCODE = 0x00000002, + GP_MASK_COUNTRYCODE = 0x00000004, + GP_MASK_BIRTHDAY = 0x00000008, + GP_MASK_SEX = 0x00000010, + GP_MASK_EMAIL = 0x00000020, + GP_MASK_ALL = 0xFFFFFFFF, + + // Status + ///////// + GP_OFFLINE = 0, + GP_ONLINE = 1, + GP_PLAYING = 2, + GP_STAGING = 3, + GP_CHATTING = 4, + GP_AWAY = 5, + + // Session flags + ///////////////// + GP_SESS_IS_CLOSED = 0x00000001, + GP_SESS_IS_OPEN = 0x00000002, + GP_SESS_HAS_PASSWORD = 0x00000004, + GP_SESS_IS_BEHIND_NAT = 0x00000008, + GP_SESS_IS_RANKED = 0x000000010, + + // CPU Brand ID + /////////////// + GP_INTEL = 1, + GP_AMD, + GP_CYRIX, + GP_MOTOROLA, + GP_ALPHA, + + // Connection ID. + ///////////////// + GP_MODEM = 1, + GP_ISDN, + GP_CABLEMODEM, + GP_DSL, + GP_SATELLITE, + GP_ETHERNET, + GP_WIRELESS, + + // Transfer callback type. + // *** the transfer is ended when these types are received + ////////////////////////// + GP_TRANSFER_SEND_REQUEST = 0x800, // arg->num == numFiles + GP_TRANSFER_ACCEPTED, + GP_TRANSFER_REJECTED, // *** + GP_TRANSFER_NOT_ACCEPTING, // *** + GP_TRANSFER_NO_CONNECTION, // *** + GP_TRANSFER_DONE, // *** + GP_TRANSFER_CANCELLED, // *** + GP_TRANSFER_LOST_CONNECTION, // *** + GP_TRANSFER_ERROR, // *** + GP_TRANSFER_THROTTLE, // arg->num == Bps + GP_FILE_BEGIN, + GP_FILE_PROGRESS, // arg->num == numBytes + GP_FILE_END, + GP_FILE_DIRECTORY, + GP_FILE_SKIP, + GP_FILE_FAILED, // arg->num == error + + // GP_FILE_FAILED error + /////////////////////// + GP_FILE_READ_ERROR = 0x900, + GP_FILE_WRITE_ERROR, + GP_FILE_DATA_ERROR, + + // Transfer Side. + ///////////////// + GP_TRANSFER_SENDER = 0xA00, + GP_TRANSFER_RECEIVER, + + // UTM send options. + //////////////////// + GP_DONT_ROUTE = 0xB00, // only send direct + + // Quiet mode flags. + //////////////////// + GP_SILENCE_NONE = 0x00000000, + GP_SILENCE_MESSAGES = 0x00000001, + GP_SILENCE_UTMS = 0x00000002, + GP_SILENCE_LIST = 0x00000004, // includes requests, auths, and revokes + GP_SILENCE_ALL = 0xFFFFFFFF, + + // Flags for checking if newer version of status info is supported + GP_NEW_STATUS_INFO_SUPPORTED = 0xC00, + GP_NEW_STATUS_INFO_NOT_SUPPORTED = 0xC01 +} GPEnum; + +// RESULTS +////////// +typedef enum _GPResult { + GP_NO_ERROR, + GP_MEMORY_ERROR, + GP_PARAMETER_ERROR, + GP_NETWORK_ERROR, + GP_SERVER_ERROR, + GP_MISC_ERROR, + GP_COUNT +} GPResult; + +// ERROR CODES +///////////// +//#define GP_ERROR_TYPE(errorCode) ((errorCode) >> 8) +typedef enum _GPErrorCode { + // General. + /////////// + GP_GENERAL = 0x0000, + GP_PARSE, + GP_NOT_LOGGED_IN, + GP_BAD_SESSKEY, + GP_DATABASE, + GP_NETWORK, + GP_FORCED_DISCONNECT, + GP_CONNECTION_CLOSED, + GP_UDP_LAYER, + + // Login. + ///////// + GP_LOGIN = 0x0100, + GP_LOGIN_TIMEOUT, + + GP_LOGIN_BAD_NICK, + GP_LOGIN_BAD_EMAIL, + GP_LOGIN_BAD_PASSWORD, + GP_LOGIN_BAD_PROFILE, + GP_LOGIN_PROFILE_DELETED, + GP_LOGIN_CONNECTION_FAILED, + GP_LOGIN_SERVER_AUTH_FAILED, + GP_LOGIN_BAD_UNIQUENICK, + GP_LOGIN_BAD_PREAUTH, + + // Newuser. + /////////// + GP_NEWUSER = 0x0200, + GP_NEWUSER_BAD_NICK, + GP_NEWUSER_BAD_PASSWORD, + GP_NEWUSER_UNIQUENICK_INVALID, + GP_NEWUSER_UNIQUENICK_INUSE, + + // Updateui. + //////////// + GP_UPDATEUI = 0x0300, + GP_UPDATEUI_BAD_EMAIL, + + // Newprofile. + ////////////// + GP_NEWPROFILE = 0x0400, + GP_NEWPROFILE_BAD_NICK, + GP_NEWPROFILE_BAD_OLD_NICK, + + // Updatepro. + ///////////// + GP_UPDATEPRO = 0x0500, + GP_UPDATEPRO_BAD_NICK, + + // Addbuddy. + //////////// + GP_ADDBUDDY = 0x0600, + GP_ADDBUDDY_BAD_FROM, + GP_ADDBUDDY_BAD_NEW, + GP_ADDBUDDY_ALREADY_BUDDY, + + // Authadd. + /////////// + GP_AUTHADD = 0x0700, + GP_AUTHADD_BAD_FROM, + GP_AUTHADD_BAD_SIG, + + // Status. + ////////// + GP_STATUS = 0x0800, + + // Bm. + ////// + GP_BM = 0x0900, + GP_BM_NOT_BUDDY, + GP_BM_EXT_INFO_NOT_SUPPORTED, + GP_BM_BUDDY_OFFLINE, + + // Getprofile. + ////////////// + GP_GETPROFILE = 0x0A00, + GP_GETPROFILE_BAD_PROFILE, + + // Delbuddy. + //////////// + GP_DELBUDDY = 0x0B00, + GP_DELBUDDY_NOT_BUDDY, + + // Delprofile. + ///////////// + GP_DELPROFILE = 0x0C00, + GP_DELPROFILE_LAST_PROFILE, + + // Search. + ////////// + GP_SEARCH = 0x0D00, + GP_SEARCH_CONNECTION_FAILED, + GP_SEARCH_TIMED_OUT, + + // Check. + ///////// + GP_CHECK = 0x0E00, + GP_CHECK_BAD_EMAIL, + GP_CHECK_BAD_NICK, + GP_CHECK_BAD_PASSWORD, + + // Revoke. + ////////// + GP_REVOKE = 0x0F00, + GP_REVOKE_NOT_BUDDY, + + // Registeruniquenick. + ////////////////////// + GP_REGISTERUNIQUENICK = 0x1000, + GP_REGISTERUNIQUENICK_TAKEN, + GP_REGISTERUNIQUENICK_RESERVED, + GP_REGISTERUNIQUENICK_BAD_NAMESPACE, + + // Register cdkey. + ////////////////// + GP_REGISTERCDKEY = 0x1100, + GP_REGISTERCDKEY_BAD_KEY, + GP_REGISTERCDKEY_ALREADY_SET, + GP_REGISTERCDKEY_ALREADY_TAKEN, + +} GPErrorCode; + +// STRING LENGTHS +//////////////// +#define GP_NICK_LEN 31 +#define GP_UNIQUENICK_LEN 21 +#define GP_FIRSTNAME_LEN 31 +#define GP_LASTNAME_LEN 31 +#define GP_EMAIL_LEN 51 +#define GP_PASSWORD_LEN 31 +#define GP_PASSWORDENC_LEN ((((GP_PASSWORD_LEN + 2) * 4) / 3) + 1) +#define GP_HOMEPAGE_LEN 76 +#define GP_ZIPCODE_LEN 11 +#define GP_COUNTRYCODE_LEN 3 +#define GP_PLACE_LEN 128 +#define GP_AIMNAME_LEN 51 +#define GP_REASON_LEN 1025 +#define GP_STATUS_STRING_LEN 256 +#define GP_LOCATION_STRING_LEN 256 +#define GP_ERROR_STRING_LEN 256 +#define GP_AUTHTOKEN_LEN 256 +#define GP_PARTNERCHALLENGE_LEN 256 +#define GP_CDKEY_LEN 65 +#define GP_CDKEYENC_LEN ((((GP_CDKEY_LEN + 2) * 4) / 3) + 1) +#define GP_LOGIN_TICKET_LEN 25 + +#define GP_RICH_STATUS_LEN 256 +#define GP_STATUS_BASIC_STR_LEN 33 + +// Random number seed for PASSWORDENC and CDKEYENC +// MUST MATCH SERVER - If you change this, you'll have to +// release an updated server +#define GP_XOR_SEED 0x79707367 // "gspy" + +// Well known values for partner ID. +#define GP_PARTNERID_GAMESPY 0 +#define GP_PARTNERID_IGN 10 + +// Maximum number of namespaces that can be searched for a uniquenick +#define GP_MAX_NAMESPACEIDS 16 + +// TYPES +//////// +// GPConnection +/////////////// +typedef void* GPConnection; + +// GPProfile +//////////// +typedef int GPProfile; + +// GPTransfer +///////////// +typedef int GPTransfer; + +// GPCallback +///////////// +typedef void (*GPCallback)(GPConnection* connection, void* arg, void* param); + +// STRUCTURES +///////////// +// GPErrorArg +///////////// +typedef struct { + GPResult result; + GPErrorCode errorCode; + gsi_char* errorString; + GPEnum fatal; +} GPErrorArg; + +// GPConnectResponseArg +//////////////////////// +typedef struct { + GPResult result; + GPProfile profile; + gsi_char uniquenick[GP_UNIQUENICK_LEN]; +} GPConnectResponseArg; + +// GPNewUserResponseArg +/////////////////////// +typedef struct { + GPResult result; + GPProfile profile; +} GPNewUserResponseArg; + +// GPCheckResponseArg +///////////////////// +typedef struct { + GPResult result; + GPProfile profile; +} GPCheckResponseArg; + +// GPSuggestUniqueNickResponseArg +///////////////////////////////// +typedef struct { + GPResult result; + int numSuggestedNicks; + gsi_char** suggestedNicks; +} GPSuggestUniqueNickResponseArg; + +// GPRegisterUniqueNickResponseArg +////////////////////////////////// +typedef struct { + GPResult result; +} GPRegisterUniqueNickResponseArg; + +// GPRegisterCdKeyResponseArg +////////////////////////////////// +typedef struct { + GPResult result; +} GPRegisterCdKeyResponseArg; + +// GPNewProfileResponseArg +////////////////////////// +typedef struct { + GPResult result; + GPProfile profile; +} GPNewProfileResponseArg; + +// GPDeleteProfileResponseArg +///////////////////////////// + +typedef struct { + GPResult result; + GPProfile profile; +} GPDeleteProfileResponseArg; + +// GPProfileSearchMatch +/////////////////////// +typedef struct { + GPProfile profile; + gsi_char nick[GP_NICK_LEN]; + gsi_char uniquenick[GP_UNIQUENICK_LEN]; + // int namespaceID; + gsi_char firstname[GP_FIRSTNAME_LEN]; + gsi_char lastname[GP_LASTNAME_LEN]; + gsi_char email[GP_EMAIL_LEN]; +} GPProfileSearchMatch; + +// GPProfileSearchResponseArg +///////////////////////////// +typedef struct { + GPResult result; + int numMatches; + GPEnum more; + GPProfileSearchMatch* matches; +} GPProfileSearchResponseArg; + +// GPGetInfoResponseArg +/////////////////////// +typedef struct { + GPResult result; + GPProfile profile; + gsi_char nick[GP_NICK_LEN]; + gsi_char uniquenick[GP_UNIQUENICK_LEN]; + gsi_char email[GP_EMAIL_LEN]; + gsi_char firstname[GP_FIRSTNAME_LEN]; + gsi_char lastname[GP_LASTNAME_LEN]; + gsi_char homepage[GP_HOMEPAGE_LEN]; + int icquin; + gsi_char zipcode[GP_ZIPCODE_LEN]; + gsi_char countrycode[GP_COUNTRYCODE_LEN]; + float longitude; // negative is west, positive is east. (0, 0) means unknown. + float + latitude; // negative is south, positive is north. (0, 0) means unknown. + gsi_char place[GP_PLACE_LEN]; // e.g., "USA|California|Irvine", "South + // Korea|Seoul", "Turkey" + int birthday; + int birthmonth; + int birthyear; + GPEnum sex; + GPEnum publicmask; + gsi_char aimname[GP_AIMNAME_LEN]; + int pic; + int occupationid; + int industryid; + int incomeid; + int marriedid; + int childcount; + int interests1; + int ownership1; + int conntypeid; +} GPGetInfoResponseArg; + +// GPRecvBuddyRequestArg +//////////////////////// +typedef struct { + GPProfile profile; + unsigned int date; + gsi_char reason[GP_REASON_LEN]; +} GPRecvBuddyRequestArg; + +// GPBuddyStatus +//////////////// +typedef struct { + GPProfile profile; // 0x00 + GPEnum status; // 0x04 + gsi_char statusString[GP_STATUS_STRING_LEN]; // 0x08 + gsi_char locationString[GP_LOCATION_STRING_LEN]; // 0x108 + unsigned int ip; // 0x208 + int port; // 0x20C + GPEnum quietModeFlags; // 0x210 +} GPBuddyStatus; + +// BETA +// GPBuddyStatusInfo +/////////////////// +typedef struct { + GPProfile profile; // 0x00 + GPEnum statusState; // 0x04 + unsigned int buddyIp; // 0x08 + unsigned short buddyPort; // 0x0C + unsigned int hostIp; // 0x10 + unsigned int hostPrivateIp; // 0x14 + unsigned short queryPort; // 0x18 + unsigned short hostPort; // 0x1A + unsigned int sessionFlags; // 0x1C + gsi_char richStatus[GP_RICH_STATUS_LEN]; // 0x20 + gsi_char gameType[GP_STATUS_BASIC_STR_LEN]; // 0x120 + gsi_char gameVariant[GP_STATUS_BASIC_STR_LEN]; // 0x141 + gsi_char gameMapName[GP_STATUS_BASIC_STR_LEN]; // 0x162 + GPEnum quietModeFlags; // 0x184 + GPEnum newStatusInfoFlag; // 0x188 +} GPBuddyStatusInfo; + +// GPGetBuddyStatusInfoKeysArg +///////////////////////////// +typedef struct { + GPProfile profile; + gsi_char** keys; + gsi_char** values; + int numKeys; + +} GPGetBuddyStatusInfoKeysArg; + +// GPRecvBuddyStatusArg +/////////////////////// +typedef struct { + GPProfile profile; + unsigned int date; + int index; +} GPRecvBuddyStatusArg; + +// GPRecvBuddyMessageArg +//////////////////////// +typedef struct { + GPProfile profile; + unsigned int date; + gsi_char* message; +} GPRecvBuddyMessageArg; + +typedef struct { + GPProfile profile; + unsigned int date; + gsi_char* message; +} GPRecvBuddyUTMArg; + +typedef struct { + GPProfile profile; + unsigned int date; +} GPRecvBuddyAuthArg; + +typedef struct { + GPProfile profile; + unsigned int date; +} GPRecvBuddyRevokeArg; + +// GPTransferCallbackArg; +///////////////////////// +typedef struct { + GPTransfer transfer; + GPEnum type; + int index; + int num; + gsi_char* message; +} GPTransferCallbackArg; + +// GPIsValidEmailResponseArg +//////////////////////////// +typedef struct { + GPResult result; + gsi_char email[GP_EMAIL_LEN]; + GPEnum isValid; +} GPIsValidEmailResponseArg; + +// GPGetUserNicksResponseArg +//////////////////////////// +typedef struct { + GPResult result; + gsi_char email[GP_EMAIL_LEN]; + int numNicks; // If 0, then the email/password did not match. + gsi_char** nicks; + gsi_char** uniquenicks; +} GPGetUserNicksResponseArg; + +// GPRecvGameInviteArg +////////////////////// +typedef struct { + GPProfile profile; + int productID; + gsi_char location[GP_LOCATION_STRING_LEN]; +} GPRecvGameInviteArg; + +// GPFindPlayerMatch +//////////////////// +typedef struct { + GPProfile profile; + gsi_char nick[GP_NICK_LEN]; + GPEnum status; + gsi_char statusString[GP_STATUS_STRING_LEN]; +} GPFindPlayerMatch; + +// GPFindPlayersResponseArg +/////////////////////////// +typedef struct { + GPResult result; + int productID; // PANTS|06.06.00 - added by request for JED + int numMatches; + GPFindPlayerMatch* matches; +} GPFindPlayersResponseArg; + +// GPGetReverseBuddiesResponseArg +///////////////////////////////// +typedef struct { + GPResult result; + int numProfiles; + GPProfileSearchMatch* profiles; +} GPGetReverseBuddiesResponseArg; + +typedef struct { + GPProfile profile; + gsi_char uniqueNick[GP_UNIQUENICK_LEN]; +} GPUniqueMatch; + +typedef struct { + GPResult result; + int numOfUniqueMatchs; + GPUniqueMatch* matches; +} GPGetReverseBuddiesListResponseArg; + +// GLOBALS +///////// +/* The hostnames of the connection manager +server and the search manager server. +If the app resolves either or both hostnames, +the IP(s) can be stored in the string(s) before +calling gpInitialize */ +extern char GPConnectionManagerHostname[64]; +extern char GPSearchManagerHostname[64]; + +// FUNCTIONS +//////////// +#define gpConnect gpConnectA +#define gpConnectNewUser gpConnectNewUserA +#define gpConnectUniqueNick gpConnectUniqueNickA +#define gpConnectPreAuthenticated gpConnectPreAuthenticatedA +#define gpCheckUser gpCheckUserA +#define gpNewUser gpNewUserA +#define gpSuggestUniqueNick gpSuggestUniqueNickA +#define gpRegisterUniqueNick gpRegisterUniqueNickA +#define gpRegisterCdKey gpRegisterCdKeyA +#define gpGetErrorString gpGetErrorStringA +#define gpNewProfile gpNewProfileA +#define gpProfileSearch gpProfileSearchA +#define gpProfileSearchUniquenick gpProfileSearchUniquenickA +#define gpSetInfos gpSetInfosA +#define gpSendBuddyRequest gpSendBuddyRequestA +#ifndef GP_NEW_STATUS_INFO +#define gpSetStatus gpSetStatusA +#endif +#ifdef GP_NEW_STATUS_INFO +// BETA +#define gpSetStatusInfo gpSetStatusInfoA +#endif +#define gpSendBuddyMessage gpSendBuddyMessageA +#define gpSendBuddyUTM gpSendBuddyUTMA +#define gpIsValidEmail gpIsValidEmailA +#define gpGetUserNicks gpGetUserNicksA +#define gpSetInfoCacheFilename gpSetInfoCacheFilenameA +#define gpSendFiles gpSendFilesA +#define gpAcceptTransfer gpAcceptTransferA +#define gpRejectTransfer gpRejectTransferA +#define gpSetTransferDirectory gpSetTransferDirectoryA +#define gpGetFileName gpGetFileNameA +#define gpGetFilePath gpGetFilePathA +#define gpInvitePlayer gpInvitePlayerA +#ifdef GP_NEW_STATUS_INFO +// BETA +#define gpAddStatusInfoKey gpAddStatusInfoKeyA +#define gpSetStatusInfoKey gpSetStatusInfoKeyA +#define gpGetStatusInfoKeyVal gpGetStatusInfoKeyValA +#define gpDelStatusInfoKey gpDelStatusInfoKeyA +#endif + +// gpInitialize +/////////////// +GPResult gpInitialize( + GPConnection* connection, + int productID, // The productID is a unique ID that identifies your product + int namespaceID, // The namespaceID identified which namespace to login + // under. A namespaceID of 0 indicates that no namespace + // should be used. A namespaceID of 1 represents the + // default GameSpy namespace + int partnerID // The partnerID identifies the account system being used. + // Use GP_PARTNERID_GAMESPY for GSID accounts. + // Use GP_PARTNERID_IGN for IGN accounts. +); + +// gpDestroy +//////////// +void gpDestroy(GPConnection* connection); + +// gpEnable +/////////// +GPResult gpEnable(GPConnection* connection, GPEnum state); + +// gpDisable +//////////// +GPResult gpDisable(GPConnection* connection, GPEnum state); + +// gpProcess +//////////// +GPResult gpProcess(GPConnection* connection); + +// gpSetCallback +//////////////// +GPResult gpSetCallback(GPConnection* connection, GPEnum func, + GPCallback callback, void* param); + +// gpConnect +//////////// +GPResult gpConnect(GPConnection* connection, const gsi_char nick[GP_NICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], GPEnum firewall, + GPEnum blocking, GPCallback callback, void* param); + +// gpConnectNewUser +/////////////////// +GPResult gpConnectNewUser(GPConnection* connection, + const gsi_char nick[GP_NICK_LEN], + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + const gsi_char cdkey[GP_CDKEY_LEN], GPEnum firewall, + GPEnum blocking, GPCallback callback, void* param); + +// gpConnectUniqueNick +////////////////////// +GPResult gpConnectUniqueNick(GPConnection* connection, + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char password[GP_PASSWORD_LEN], + GPEnum firewall, GPEnum blocking, + GPCallback callback, void* param); + +// gpConnectPreAuthenticated +//////////////////////////// +GPResult gpConnectPreAuthenticated( + GPConnection* connection, const gsi_char authtoken[GP_AUTHTOKEN_LEN], + const gsi_char partnerchallenge[GP_PARTNERCHALLENGE_LEN], GPEnum firewall, + GPEnum blocking, GPCallback callback, void* param); + +// gpDisconnect +/////////////// +void gpDisconnect(GPConnection* connection); + +// gpIsConnected +//////////////// +GPResult gpIsConnected(GPConnection* connection, GPEnum* connected); + +// gpCheckUser +////////////// +GPResult gpCheckUser(GPConnection* connection, const gsi_char nick[GP_NICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], GPEnum blocking, + GPCallback callback, void* param); + +// gpNewUser +//////////// +GPResult gpNewUser(GPConnection* connection, const gsi_char nick[GP_NICK_LEN], + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + const gsi_char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param); + +// gpSuggestUniqueNick +////////////////////// +GPResult gpSuggestUniqueNick(GPConnection* connection, + const gsi_char desirednick[GP_UNIQUENICK_LEN], + GPEnum blocking, GPCallback callback, void* param); + +// gpRegisterUniqueNick +/////////////////////// +GPResult gpRegisterUniqueNick(GPConnection* connection, + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char cdkey[GP_CDKEY_LEN], + GPEnum blocking, GPCallback callback, + void* param); + +// gpRegisterCdKey +/////////////////////// +GPResult gpRegisterCdKey(GPConnection* connection, + const gsi_char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param); + +// gpGetErrorCode +///////////////// +GPResult gpGetErrorCode(GPConnection* connection, GPErrorCode* errorCode); + +// gpGetErrorString +/////////////////// +GPResult gpGetErrorString(GPConnection* connection, + gsi_char errorString[GP_ERROR_STRING_LEN]); + +// gpNewProfile +/////////////// +GPResult gpNewProfile(GPConnection* connection, + const gsi_char nick[GP_NICK_LEN], GPEnum replace, + GPEnum blocking, GPCallback callback, void* param); + +// gpDeleteProfile +////////////////// +GPResult gpDeleteProfile(GPConnection* connection, GPCallback callback, + void* param); + +// gpProfileFromID +// PANTS|09.11.00 - A GPProfile is now the same +// as a profileid. This function is no longer needed +// and will be removed in a future version of GP. +///////////////////////////////////////////////////// +GPResult gpProfileFromID(GPConnection* connection, GPProfile* profile, int id); + +// gpIDFromProfile +// PANTS|09.11.00 - A GPProfile is now the same +// as a profileid. This function is no longer needed +// and will be removed in a future version of GP. +///////////////////////////////////////////////////// +GPResult gpIDFromProfile(GPConnection* connection, GPProfile profile, int* id); + +// gpUserIDFromProfile +////////////////// +GPResult gpUserIDFromProfile(GPConnection* connection, GPProfile profile, + int* userid); + +// gpProfileSearch +////////////////// +GPResult gpProfileSearch(GPConnection* connection, + const gsi_char nick[GP_NICK_LEN], + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const gsi_char email[GP_EMAIL_LEN], + const gsi_char firstname[GP_FIRSTNAME_LEN], + const gsi_char lastname[GP_LASTNAME_LEN], int icquin, + GPEnum blocking, GPCallback callback, void* param); + +// gpProfileSearchUniquenick +//////////////////////////// +GPResult gpProfileSearchUniquenick(GPConnection* connection, + const gsi_char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[GP_MAX_NAMESPACEIDS], + int numNamespaces, GPEnum blocking, + GPCallback callback, void* param); + +// gpGetInfo +//////////// +GPResult gpGetInfo(GPConnection* connection, GPProfile profile, + GPEnum checkCache, GPEnum blocking, GPCallback callback, + void* param); + +// gpGetInfoNoWait +////////////////// +GPResult gpGetInfoNoWait(GPConnection* connection, GPProfile profile, + GPGetInfoResponseArg* arg); + +// gpSetInfoi +///////////// +GPResult gpSetInfoi(GPConnection* connection, GPEnum info, int value); + +// gpSetInfos +///////////// +GPResult gpSetInfos(GPConnection* connection, GPEnum info, + const gsi_char* value); + +// gpSetInfod +///////////// +GPResult gpSetInfod(GPConnection* connection, GPEnum info, int day, int month, + int year); + +// gpSetInfoMask +//////////////// +GPResult gpSetInfoMask(GPConnection* connection, GPEnum mask); + +// gpSendBuddyRequest +///////////////////// +GPResult gpSendBuddyRequest(GPConnection* connection, GPProfile profile, + const gsi_char reason[GP_REASON_LEN]); + +// gpAuthBuddyRequest +///////////////////// +GPResult gpAuthBuddyRequest(GPConnection* connection, GPProfile profile); + +// gpDenyBuddyRequest +// PANTS|09.11.00 +///////////////////// +GPResult gpDenyBuddyRequest(GPConnection* connection, GPProfile profile); + +// gpDeleteBuddy +//////////////// +GPResult gpDeleteBuddy(GPConnection* connection, GPProfile profile); + +// gpGetNumBuddies +////////////////// +GPResult gpGetNumBuddies(GPConnection* connection, int* numBuddies); + +// gpGetBuddyStatus +/////////////////// +#ifndef GP_NEW_STATUS_INFO +GPResult gpGetBuddyStatus(GPConnection* connection, int index, + GPBuddyStatus* status); +#endif + +#ifdef GP_NEW_STATUS_INFO +// +////////////////////////////// +GPResult gpGetBuddyStatusInfo(GPConnection* connection, int index, + GPBuddyStatusInfo* statusInfo); + +GPResult gpSetBuddyAddr(GPConnection* connection, int index, + unsigned int buddyIp, unsigned short buddyPort); +#endif +// gpGetBuddyIndex +////////////////// +GPResult gpGetBuddyIndex(GPConnection* connection, GPProfile profile, + int* index); + +// gpIsBuddy +// returns 1 if a buddy, 0 if not a buddy +//////////// +int gpIsBuddy(GPConnection* connection, GPProfile profile); + +int gpIsBuddyConnectionOpen(GPConnection* connection, GPProfile profile); + +// gpSetStatus +////////////// +#ifndef GP_NEW_STATUS_INFO +GPResult gpSetStatus(GPConnection* connection, GPEnum status, + const gsi_char statusString[GP_STATUS_STRING_LEN], + const gsi_char locationString[GP_LOCATION_STRING_LEN]); +#endif + +#ifdef GP_NEW_STATUS_INFO +GPResult gpSetStatusInfo(GPConnection* connection, GPEnum statusState, + unsigned int hostIp, unsigned int hostPrivateIp, + unsigned short queryPort, unsigned short hostPort, + unsigned int sessionFlags, const gsi_char* richStatus, + int richStatusLen, const gsi_char* gameType, + int gameTypeLen, const gsi_char* gameVariant, + int gameVariantLen, const gsi_char* gameMapName, + int gameMapNameLen); + +GPResult gpAddStatusInfoKey(GPConnection* connection, const gsi_char* keyName, + const gsi_char* keyValue); +GPResult gpSetStatusInfoKey(GPConnection* connection, const gsi_char* keyName, + const gsi_char* keyValue); +GPResult gpGetStatusInfoKeyVal(GPConnection* connection, + const gsi_char* keyName, gsi_char** keyValue); +GPResult gpDelStatusInfoKey(GPConnection* connection, const gsi_char* keyName); + +GPResult gpGetBuddyStatusInfoKeys(GPConnection* connection, int index, + GPCallback callback, void* userData); +#endif +// gpSendBuddyMessage +///////////////////// +GPResult gpSendBuddyMessage(GPConnection* connection, GPProfile profile, + const gsi_char* message); + +// gpSendBuddyUTM +///////////////////// +GPResult gpSendBuddyUTM(GPConnection* connection, GPProfile profile, + const gsi_char* message, + int sendOption // GP_DONT_ROUTE +); + +// PANTS|02.15.00 +// Added gpIsValidEmail and gpGetUserNicks for login wizard. +//////////////////////////////////////////////////////////// + +// gpIsValidEmail +///////////////// +GPResult gpIsValidEmail(GPConnection* connection, + const gsi_char email[GP_EMAIL_LEN], GPEnum blocking, + GPCallback callback, void* param); + +// gpGetUserNicks +///////////////// +GPResult gpGetUserNicks(GPConnection* connection, + const gsi_char email[GP_EMAIL_LEN], + const gsi_char password[GP_PASSWORD_LEN], + GPEnum blocking, GPCallback callback, void* param); + +// *DEPRECATED* +// gpSetInvitableGames +////////////////////// +GPResult gpSetInvitableGames(GPConnection* connection, int numProductIDs, + const int* productIDs); + +// *DEPRECATED* +// gpFindPlayers +//////////////// +GPResult gpFindPlayers(GPConnection* connection, int productID, GPEnum blocking, + GPCallback callback, void* param); + +// gpInvitePlayer +///////////////// +GPResult gpInvitePlayer(GPConnection* connection, GPProfile profile, + int productID, + const gsi_char location[GP_LOCATION_STRING_LEN]); + +// gpGetReverseBuddies +// Get profiles that have you on their buddy list. +////////////////////////////////////////////////// +GPResult gpGetReverseBuddies(GPConnection* connection, GPEnum blocking, + GPCallback callback, void* param); + +// gpGetReverseBuddiesList +// Get profile ids and unique nicks for profiles +// that have you on their buddy list. +////////////////////////////////////////////////// +GPResult gpGetReversBuddiesList(GPConnection* connection, GPProfile* targets, + int numOfTargets, GPEnum blocking, + GPCallback callback, void* param); + +// gpRevokeBuddyAuthorization +///////////////////////////// +GPResult gpRevokeBuddyAuthorization(GPConnection* connection, + GPProfile profile); + +// gpSetCdKey +GPResult gpSetCdKey(GPConnection* connection, const gsi_char cdkeyhash, + GPCallback callback); + +// gpGetLoginTicket +///////////////////////////// +GPResult gpGetLoginTicket(GPConnection* connection, + char loginTicket[GP_LOGIN_TICKET_LEN]); + +// gpSetQuietMode +///////////////// +GPResult gpSetQuietMode(GPConnection* connection, GPEnum flags); + +#ifndef NOFILE + +// gpiSetInfoCacheFilename +// Should be called before gpIntialize. +/////////////////////////////////////// +void gpSetInfoCacheFilename(const gsi_char* filename); + +/////////////////// +// FILE TRANSFER // +/////////////////// +typedef void (*gpSendFilesCallback)(GPConnection* connection, int index, + const gsi_char** path, + const gsi_char** name, void* param); + +GPResult gpSendFiles(GPConnection* connection, GPTransfer* transfer, + GPProfile profile, const gsi_char* message, + gpSendFilesCallback callback, void* param); + +GPResult gpAcceptTransfer(GPConnection* connection, GPTransfer transfer, + const gsi_char* message); + +GPResult gpRejectTransfer(GPConnection* connection, GPTransfer transfer, + const gsi_char* message); + +GPResult gpFreeTransfer(GPConnection* connection, GPTransfer transfer); + +GPResult gpSetTransferData(GPConnection* connection, GPTransfer transfer, + void* userData); + +void* gpGetTransferData(GPConnection* connection, GPTransfer transfer); + +GPResult gpSetTransferDirectory(GPConnection* connection, GPTransfer transfer, + const gsi_char* directory); + +// NOTE: THROTTLING IS NOT CURRENTLY IMPLEMENTED +GPResult gpSetTransferThrottle(GPConnection* connection, GPTransfer transfer, + int throttle); + +// NOTE: THROTTLING IS NOT CURRENTLY IMPLEMENTED +GPResult gpGetTransferThrottle(GPConnection* connection, GPTransfer transfer, + int* throttle); + +GPResult gpGetTransferProfile(GPConnection* connection, GPTransfer transfer, + GPProfile* profile); + +GPResult gpGetTransferSide(GPConnection* connection, GPTransfer transfer, + GPEnum* side); + +GPResult gpGetTransferSize(GPConnection* connection, GPTransfer transfer, + int* size); + +GPResult gpGetTransferProgress(GPConnection* connection, GPTransfer transfer, + int* progress); + +GPResult gpGetNumFiles(GPConnection* connection, GPTransfer transfer, int* num); + +GPResult gpGetCurrentFile(GPConnection* connection, GPTransfer transfer, + int* index); + +GPResult gpSkipFile(GPConnection* connection, GPTransfer transfer, int index); + +GPResult gpGetFileName(GPConnection* connection, GPTransfer transfer, int index, + gsi_char** name); + +GPResult gpGetFilePath(GPConnection* connection, GPTransfer transfer, int index, + gsi_char** path); + +GPResult gpGetFileSize(GPConnection* connection, GPTransfer transfer, int index, + int* size); + +GPResult gpGetFileProgress(GPConnection* connection, GPTransfer transfer, + int index, int* progress); + +GPResult gpGetFileModificationTime(GPConnection* connection, + GPTransfer transfer, int index, + gsi_time* modTime); + +GPResult gpGetNumTransfers(GPConnection* connection, int* num); + +GPResult gpGetTransfer(GPConnection* connection, int index, + GPTransfer* transfer); +#endif + +#ifdef _DEBUG +// gpProfilesReport +// PANTS|09.11.00 +/////////////////// +void gpProfilesReport(GPConnection* connection, + void (*report)(const char* output)); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/GP/gpi.c b/source/gamespy/GP/gpi.c new file mode 100644 index 000000000..cad825724 --- /dev/null +++ b/source/gamespy/GP/gpi.c @@ -0,0 +1,670 @@ +/* +gpi.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include + +// DEFINES +////////// +#define KEEPALIVE_TIMEOUT (60 * 2000) + +// This is so VisualAssist will know about these functions. +/////////////////////////////////////////////////////////// +#if 0 +void MD5Init(MD5_CTX *); +void MD5Update(MD5_CTX *, unsigned char *, unsigned int); +void MD5Final(unsigned char [16], MD5_CTX *); +void MD5Print(unsigned char [16], char[33]); +void MD5Digest(unsigned char *, unsigned int, char[33]); +#endif + +// FUNCTIONS +/////////// +GPResult gpiInitialize(GPConnection* connection, int productID, int namespaceID, + int partnerID) { + GPIConnection* iconnection; + int i; + GPResult result; + + // Set the connection to NULL in case of error. + /////////////////////////////////////////////// + *connection = NULL; + + // Allocate the connection. + /////////////////////////// + iconnection = (GPIConnection*)gsimalloc(sizeof(GPIConnection)); + if (iconnection == NULL) + return GP_MEMORY_ERROR; + + // Initialize connection-specific variables. + //////////////////////////////////////////// + memset(iconnection, 0, sizeof(GPIConnection)); + iconnection->errorString[0] = '\0'; + iconnection->errorCode = (GPErrorCode)0; + iconnection->infoCaching = GPITrue; + iconnection->infoCachingBuddyAndBlockOnly = GPIFalse; + iconnection->simulation = GPIFalse; + iconnection->firewall = GPIFalse; + iconnection->productID = productID; + iconnection->namespaceID = namespaceID; + iconnection->partnerID = partnerID; + + if (!gpiInitProfiles((GPConnection*)&iconnection)) { + freeclear(iconnection); + return GP_MEMORY_ERROR; + } + iconnection->diskCache = NULL; + for (i = 0; i < GPI_NUM_CALLBACKS; i++) { + iconnection->callbacks[i].callback = NULL; + iconnection->callbacks[i].param = NULL; + } + + // Reset connection-specific stuff. + /////////////////////////////////// + result = gpiReset((GPConnection*)&iconnection); + if (result != GP_NO_ERROR) { + gpiDestroy((GPConnection*)&iconnection); + return result; + } + + // Initialize the sockets library. + ////////////////////////////////// + SocketStartUp(); + + // Seed the random number generator. + //////////////////////////////////// + srand((unsigned int)current_time()); + +#ifndef NOFILE + // Load profiles cached on disk. + //////////////////////////////// + result = gpiLoadDiskProfiles((GPConnection*)&iconnection); + if (result != GP_NO_ERROR) { + gpiDestroy((GPConnection*)&iconnection); + return result; + } +#endif + +#ifndef NOFILE + result = gpiInitTransfers((GPConnection*)&iconnection); + if (result != GP_NO_ERROR) { + gpiDestroy((GPConnection*)&iconnection); + return result; + } +#endif + + // Set the connection. + ////////////////////// + *connection = (GPConnection)iconnection; + + return GP_NO_ERROR; +} + +void gpiDestroy(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Cleanup connection-specific stuff. + ///////////////////////////////////// + gpiDisconnect(connection, GPITrue); + gpiStatusInfoKeysDestroy(connection); + +#ifndef NOFILE + // Write the profile info to disk. + // BD - Don't update if we never connected. + ////////////////////////////////// + if (iconnection->infoCaching && + iconnection->connectState != GPI_NOT_CONNECTED) { + if (gpiSaveDiskProfiles(connection) != GP_NO_ERROR) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_HotError, + "Error saving profiles to disk."); + } + } +#endif + + // Free the profile list. + ///////////////////////// + TableFree(iconnection->profileList.profileTable); + +#ifndef NOFILE + // Free the transfers. + ////////////////////// + gpiCleanupTransfers(connection); +#endif + + // Free the memory. + /////////////////// + freeclear(iconnection); + + // Set the connection pointer to NULL. + ////////////////////////////////////// + *connection = NULL; +} + +static GPIBool gpiResetProfile(GPConnection* connection, GPIProfile* profile, + void* data) { + GSI_UNUSED(connection); + GSI_UNUSED(data); + + profile->buddyStatus = NULL; + profile->buddyStatusInfo = NULL; + profile->authSig = NULL; + profile->requestCount = 0; + profile->peerSig = NULL; + + return GPITrue; +} + +GPResult gpiReset(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPResult result; + iconnection->nick[0] = '\0'; + iconnection->uniquenick[0] = '\0'; + iconnection->email[0] = '\0'; + iconnection->cmSocket = INVALID_SOCKET; + iconnection->connectState = GPI_NOT_CONNECTED; + + iconnection->socketBuffer.len = 0; + iconnection->socketBuffer.pos = 0; + iconnection->socketBuffer.size = 0; + freeclear(iconnection->socketBuffer.buffer); + iconnection->socketBuffer.buffer = NULL; + + iconnection->inputBufferSize = 0; + freeclear(iconnection->inputBuffer); + iconnection->inputBuffer = NULL; + + iconnection->outputBuffer.len = 0; + iconnection->outputBuffer.pos = 0; + iconnection->outputBuffer.size = 0; + freeclear(iconnection->outputBuffer.buffer); + iconnection->outputBuffer.buffer = NULL; + + iconnection->updateproBuffer.len = 0; + iconnection->updateproBuffer.pos = 0; + iconnection->updateproBuffer.size = 0; + freeclear(iconnection->updateproBuffer.buffer); + iconnection->updateproBuffer.buffer = NULL; + + iconnection->updateuiBuffer.len = 0; + iconnection->updateuiBuffer.pos = 0; + iconnection->updateuiBuffer.size = 0; + freeclear(iconnection->updateuiBuffer.buffer); + iconnection->updateuiBuffer.buffer = NULL; + gpiStatusInfoKeysDestroy(connection); + result = gpiStatusInfoKeysInit((GPConnection*)&iconnection); + if (result != GP_NO_ERROR) { + gpiDestroy((GPConnection*)&iconnection); + return result; + } + // iconnection->peerSocket = INVALID_SOCKET; + iconnection->nextOperationID = 2; + while (iconnection->operationList != NULL) + gpiRemoveOperation(connection, iconnection->operationList); + iconnection->operationList = NULL; + iconnection->profileList.numBuddies = 0; + gpiProfileMap(connection, gpiResetProfile, NULL); + iconnection->userid = 0; + iconnection->profileid = 0; + iconnection->sessKey = 0; + iconnection->numSearches = 0; + iconnection->fatalError = GPIFalse; + iconnection->peerList = NULL; + iconnection->lastStatusState = (GPEnum)-1; + iconnection->lastStatusString[0] = '\0'; + iconnection->lastLocationString[0] = '\0'; + iconnection->kaTransmit = 0; + + return GP_NO_ERROR; +} + +GPResult gpiProcessConnectionManager(GPConnection* connection) { + char* next; + char* str; + int id; + GPIOperation* operation; + char* tempPtr; + int len; + GPIBool connClosed = GPIFalse; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPResult result; + GPIBool loop; + gsi_time now = current_time(); + + // Loop through the rest while waiting for any blocking operations. + /////////////////////////////////////////////////////////////////// + do { + // Add any waiting info to the output buffer. + ///////////////////////////////////////////// + gpiAddLocalInfo(connection, &iconnection->outputBuffer); + + // Send anything that needs to be sent. + /////////////////////////////////////// + if (iconnection->outputBuffer.len > 0) + iconnection->kaTransmit = now; // data already being transmitted. We don't + // need to send keep alives + CHECK_RESULT(gpiSendFromBuffer(connection, iconnection->cmSocket, + &iconnection->outputBuffer, &connClosed, + GPITrue, "CM")); + + // Read everything the connection manager sent. + /////////////////////////////////////////////// + result = + gpiRecvToBuffer(connection, iconnection->cmSocket, + &iconnection->socketBuffer, &len, &connClosed, "CM"); + if (result != GP_NO_ERROR) { + if (result == GP_NETWORK_ERROR) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error reading from the server."); + + return result; + } + + // Check if we have a completed command. + //////////////////////////////////////// + while ((next = strstr(iconnection->socketBuffer.buffer, "\\final\\")) != + NULL) { + // Received command. Connection is still valid + ////////////////////////////////////////////// + iconnection->kaTransmit = now; + + // NUL terminate the command. + ///////////////////////////// + next[0] = '\0'; + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "CMD: %s\n", iconnection->socketBuffer.buffer); + + // Copy the command to the input buffer. + //////////////////////////////////////// + len = (next - iconnection->socketBuffer.buffer); + if (len > iconnection->inputBufferSize) { + iconnection->inputBufferSize += max(GPI_READ_SIZE, len); + tempPtr = + (char*)gsirealloc(iconnection->inputBuffer, + (unsigned int)iconnection->inputBufferSize + 1); + if (tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + iconnection->inputBuffer = tempPtr; + } + memcpy(iconnection->inputBuffer, iconnection->socketBuffer.buffer, + (unsigned int)len + 1); + + // Point to the start of the next one. + ////////////////////////////////////// + next += 7; + + // Move the rest of the connect buffer up to the front. + /////////////////////////////////////////////////////// + iconnection->socketBuffer.len -= + (next - iconnection->socketBuffer.buffer); + memmove(iconnection->socketBuffer.buffer, next, + (unsigned int)iconnection->socketBuffer.len + 1); + + // Check for an id. + /////////////////// + str = strstr(iconnection->inputBuffer, "\\id\\"); + if (str != NULL) { + // Get the id. + ////////////// + id = atoi(str + 4); + + // Try and match the id with an operation. + ////////////////////////////////////////// + if (!gpiFindOperationByID(connection, &operation, id)) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, + GSIDebugLevel_HotError, + "No matching operation found for id %d\n", id); + } else { + // Process the operation. + ///////////////////////// + CHECK_RESULT(gpiProcessOperation(connection, operation, + iconnection->inputBuffer)); + } + } + // This is an unsolicited message. + ////////////////////////////////// + else { + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, iconnection->inputBuffer, GPITrue)) { + return GP_SERVER_ERROR; + } else if (strncmp(iconnection->inputBuffer, "\\bm\\", 4) == 0) { + CHECK_RESULT( + gpiProcessRecvBuddyMessage(connection, iconnection->inputBuffer)); + } else if (strncmp(iconnection->inputBuffer, "\\ka\\", 4) == 0) { + // Ignore the keep-alive. + ///////////////////////// + } else if (strncmp(iconnection->inputBuffer, "\\lt\\", 4) == 0) { + // Process the login ticket + ///////////////////////// + gpiValueForKey(iconnection->inputBuffer, "\\lt\\", + iconnection->loginTicket, + sizeof(iconnection->loginTicket)); + } else if (strncmp(iconnection->inputBuffer, "\\bsi\\", 5) == 0) { + CHECK_RESULT(gpiProcessRecvBuddyStatusInfo(connection, + iconnection->inputBuffer)); + } else { + // This is an unrecognized message. + /////////////////////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, + GSIDebugLevel_HotError, + "Received an unrecognized message.\n"); + } + } + } + + // Check for a closed connection. + ///////////////////////////////// + if (connClosed && iconnection->connectState != GPI_PROFILE_DELETING) { + // We've been disconnected. + /////////////////////////// + // Let gpiDisconnect change the state to GPI_DISCONNECTED + // iconnection->connectState = GPI_DISCONNECTED; + gpiSetError(connection, GP_CONNECTION_CLOSED, + "The server has closed the connection."); + gpiCallErrorCallback(connection, GP_NETWORK_ERROR, GP_FATAL); + return GP_NO_ERROR; + } + + // PANTS|05.23.00 - removed sleep + // crt - added it back 6/13/00 + // PANTS|07.10.00 - only sleep if looping + loop = gpiOperationsAreBlocking(connection); + if (loop) + msleep(10); + } while (loop); + + // Send Keep-Alive. Just need TCP to ack the data + ///////////////////////////////////////////////// + if (now - iconnection->kaTransmit > KEEPALIVE_TIMEOUT) { + // keep alive packet will be sent next think + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\ka\\\\final\\"); + iconnection->kaTransmit = now; + } + + return GP_NO_ERROR; +} + +GPResult gpiProcess(GPConnection* connection, int blockingOperationID) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIOperation* operation; + GPIOperation* delOperation; + GPResult result = GP_NO_ERROR; + GPIBool loop; + + assert((iconnection->connectState == GPI_NOT_CONNECTED) || + (iconnection->connectState == GPI_CONNECTING) || + (iconnection->connectState == GPI_NEGOTIATING) || + (iconnection->connectState == GPI_CONNECTED) || + (iconnection->connectState == GPI_DISCONNECTED) || + (iconnection->connectState == GPI_PROFILE_DELETING)); + + // Check if no connection was attempted. + //////////////////////////////////////// + /* if(iconnection->connectState == GPI_NOT_CONNECTED) + return GP_NO_ERROR; + + // Check for a disconnection. + ///////////////////////////// + if(iconnection->connectState == GPI_DISCONNECTED) + return GP_NO_ERROR; + */ + // Check if we're connecting. + ///////////////////////////// + if (iconnection->connectState == GPI_CONNECTING) { + do { + result = gpiCheckConnect(connection); + // PANTS|07.10.00 - only sleep if looping + loop = (((result == GP_NO_ERROR) && (blockingOperationID != 0) && + (iconnection->connectState == GPI_CONNECTING))) + ? GPITrue + : GPIFalse; + if (loop) + msleep(10); + } while (loop); + + if (result != GP_NO_ERROR) { + // Find the connect operation. + ////////////////////////////// + if (gpiFindOperationByID(connection, &operation, 1)) { + operation->result = GP_SERVER_ERROR; + } else { + // Couldn't find the connect operation. + /////////////////////////////////////// + assert(0); + } + } + } + + // Only do this stuff if we're connected. + ///////////////////////////////////////// + if ((iconnection->connectState == GPI_CONNECTED) || + (iconnection->connectState == GPI_NEGOTIATING) || + (iconnection->connectState == GPI_PROFILE_DELETING)) { + // Process the connection. + ////////////////////////// + if (result == GP_NO_ERROR) + result = gpiProcessConnectionManager(connection); + + // Process peer messaging stuff. + //////////////////////////////// + if (result == GP_NO_ERROR) + result = gpiProcessPeers(connection); + +#ifndef NOFILE + // Process transfers. + ///////////////////// + if (result == GP_NO_ERROR) + result = gpiProcessTransfers(connection); +#endif + } + + // Process searches. + //////////////////// + if (result == GP_NO_ERROR) + result = gpiProcessSearches(connection); + + // Look for failed operations. + ////////////////////////////// + for (operation = iconnection->operationList; operation != NULL;) { + if (operation->result != GP_NO_ERROR) { + gpiFailedOpCallback(connection, operation); + delOperation = operation; + operation = operation->pnext; + gpiRemoveOperation(connection, delOperation); + } else { + operation = operation->pnext; + } + } + + // Call callbacks. + ////////////////// + CHECK_RESULT(gpiProcessCallbacks(connection, blockingOperationID)); + + if (iconnection->fatalError) { + gpiDisconnect(connection, GPIFalse); + gpiReset(connection); + } else { + // assert(!((result != GP_NO_ERROR) && (iconnection->connectState != + // GPI_CONNECTED))); + } + + return result; +} + +GPResult gpiEnable(GPConnection* connection, GPEnum state) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Enable the state. + //////////////////// + switch (state) { + case GP_INFO_CACHING: + iconnection->infoCaching = GPITrue; + break; + + case GP_SIMULATION: + iconnection->simulation = GPITrue; + break; + + case GP_INFO_CACHING_BUDDY_AND_BLOCK_ONLY: + iconnection->infoCachingBuddyAndBlockOnly = GPITrue; + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid state."); + } + + return GP_NO_ERROR; +} + +static GPIBool gpiFreeProfileInfo(GPConnection* connection, GPIProfile* profile, + void* data) { + GSI_UNUSED(data); + + gpiFreeInfoCache(profile); + freeclear(profile->peerSig); + + if (gpiCanFreeProfile(profile)) { + gpiRemoveProfile(connection, profile); + return GPIFalse; + } + + return GPITrue; +} + +GPResult gpiDisable(GPConnection* connection, GPEnum state) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + if (state == GP_INFO_CACHING) { + iconnection->infoCaching = GPIFalse; + + // freeclear everyone's info. + //////////////////////// + while (!gpiProfileMap(connection, gpiFreeProfileInfo, NULL)) { + }; + } else if (state == GP_SIMULATION) { + iconnection->simulation = GPIFalse; + } else if (state == GP_INFO_CACHING_BUDDY_AND_BLOCK_ONLY) { + iconnection->infoCachingBuddyAndBlockOnly = GPIFalse; + } else { + Error(connection, GP_PARAMETER_ERROR, "Invalid state."); + } + + return GP_NO_ERROR; +} + +#ifdef _DEBUG +static int nProfiles; +static int nUserID; +static int nBuddyStatus; +static int nBuddyMemory; +static int nInfoCache; +static int nInfoMemory; +static int nAuthSig; +static int nPeerSig; +static int nTotalMemory; + +static GPIBool gpiReportProfile(GPConnection* connection, GPIProfile* profile, + void* data) { + int temp; + + GSI_UNUSED(connection); + GSI_UNUSED(data); + + nProfiles++; + nTotalMemory += sizeof(GPIProfile); + if (profile->userId) + nUserID++; + if (profile->buddyStatus) { + nBuddyStatus++; + temp = sizeof(GPIBuddyStatus); + if (profile->buddyStatus->statusString) + temp += (int)(strlen(profile->buddyStatus->statusString) + 1); + if (profile->buddyStatus->locationString) + temp += (int)(strlen(profile->buddyStatus->locationString) + 1); + nBuddyMemory += temp; + nTotalMemory += temp; + } + if (profile->cache) { + nInfoCache++; + temp = sizeof(GPIInfoCache); + if (profile->cache->nick) + temp += (int)(strlen(profile->cache->nick) + 1); + if (profile->cache->uniquenick) + temp += (int)(strlen(profile->cache->uniquenick) + 1); + if (profile->cache->email) + temp += (int)(strlen(profile->cache->email) + 1); + if (profile->cache->firstname) + temp += (int)(strlen(profile->cache->firstname) + 1); + if (profile->cache->lastname) + temp += (int)(strlen(profile->cache->lastname) + 1); + if (profile->cache->homepage) + temp += (int)(strlen(profile->cache->homepage) + 1); + nInfoMemory += temp; + nTotalMemory += temp; + } + if (profile->authSig) + nAuthSig++; + if (profile->peerSig) + nPeerSig++; + + return GPITrue; +} + +void gpiReport(GPConnection* connection, void (*report)(const char* output)) { + char buf[128]; + + nProfiles = 0; + nUserID = 0; + nBuddyStatus = 0; + nBuddyMemory = 0; + nInfoCache = 0; + nInfoMemory = 0; + nAuthSig = 0; + nPeerSig = 0; + nTotalMemory = 0; + + report("START PROFILE MAP"); + report("-----------------"); + gpiProfileMap(connection, gpiReportProfile, NULL); + + sprintf(buf, "%d profiles %d bytes (%d avg)", nProfiles, nTotalMemory, + nTotalMemory / max(nProfiles, 1)); + report(buf); + if (nProfiles) { + sprintf(buf, "UserID: %d (%d%%)", nUserID, nUserID * 100 / nProfiles); + report(buf); + sprintf(buf, "BuddyStatus: %d (%d%%) %d bytes (%d avg)", nBuddyStatus, + nBuddyStatus * 100 / nProfiles, nBuddyMemory, + nBuddyMemory / max(nBuddyStatus, 1)); + report(buf); + sprintf(buf, "InfoCache: %d (%d%%) %d bytes (%d avg)", nInfoCache, + nInfoCache * 100 / nProfiles, nInfoMemory, + nInfoMemory / max(nInfoCache, 1)); + report(buf); + sprintf(buf, "AuthSig: %d (%d%%)", nAuthSig, nAuthSig * 100 / nProfiles); + report(buf); + sprintf(buf, "PeerSig: %d (%d%%)", nPeerSig, nPeerSig * 100 / nProfiles); + report(buf); + sprintf(buf, "Blocked: %d (%d%%)", nBlocked, nBlocked * 100 / nProfiles); + report(buf); + } + + report("---------------"); + report("END PROFILE MAP"); +} +#endif diff --git a/source/gamespy/GP/gpi.h b/source/gamespy/GP/gpi.h new file mode 100644 index 000000000..53e57c7db --- /dev/null +++ b/source/gamespy/GP/gpi.h @@ -0,0 +1,153 @@ +/* +gpi.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +// clang-format off +#include "../common/gsCommon.h" +#include "../common/gsAvailable.h" +#include "../common/gsUdpEngine.h" +#include "../hashtable.h" +#include "../darray.h" +#include "../md5.h" +#include "gp.h" +// clang-format on + +// Extended message support +#define GPI_NEW_AUTH_NOTIFICATION (1 << 0) +#define GPI_NEW_REVOKE_NOTIFICATION (1 << 1) + +// New Status Info support +#define GPI_NEW_STATUS_NOTIFICATION (1 << 2) + +// Buddy List + Block List retrieval on login +#define GPI_NEW_LIST_RETRIEVAL_ON_LOGIN (1 << 3) + +// Extended SDK features +#define GPI_SDKREV 0x3 + +// New UDP Layer port +#define GPI_PEER_PORT 6500 + +// TYPES +/////// +// Boolean. +/////////// +typedef enum _GPIBool { GPIFalse, GPITrue } GPIBool; + +// clang-format off +#include "gpiUtility.h" +#include "gpiCallback.h" +#include "gpiOperation.h" +#include "gpiConnect.h" +#include "gpiBuffer.h" +#include "gpiInfo.h" +#include "gpiProfile.h" +#include "gpiPeer.h" +#include "gpiSearch.h" +#include "gpiBuddy.h" +#include "gpiTransfer.h" +#include "gpiUnique.h" +#include "gpiKeys.h" +// clang-format on + +// Connection data. +/////////////////// +typedef struct { + char errorString[GP_ERROR_STRING_LEN]; // 0x00 + GPIBool infoCaching; // 0x100 + GPIBool infoCachingBuddyAndBlockOnly; // 0x104 + GPIBool simulation; // 0x108 + GPIBool firewall; // 0x10C + char nick[GP_NICK_LEN]; // 0x110 + char uniquenick[GP_UNIQUENICK_LEN]; + char email[GP_EMAIL_LEN]; + char password[GP_PASSWORD_LEN]; + int sessKey; + int userid; + int profileid; + int partnerID; + GPICallback callbacks[GPI_NUM_CALLBACKS]; + SOCKET cmSocket; + int connectState; + GPIBuffer socketBuffer; + char* inputBuffer; + int inputBufferSize; + GPIBuffer outputBuffer; + // Replaced by UDP Layer + // SOCKET peerSocket; + char mHeader[GS_UDP_MSG_HEADER_LEN]; + unsigned short peerPort; + int nextOperationID; + int numSearches; + + GPEnum lastStatusState; + unsigned int hostIp; + unsigned int hostPrivateIp; + unsigned short queryPort; + unsigned short hostPort; + unsigned int sessionFlags; + + char richStatus[GP_RICH_STATUS_LEN]; + char gameType[GP_STATUS_BASIC_STR_LEN]; + char gameVariant[GP_STATUS_BASIC_STR_LEN]; + char gameMapName[GP_STATUS_BASIC_STR_LEN]; + + // New Status Info extended info Keys + DArray extendedInfoKeys; + + // Deprecated + char lastStatusString[GP_STATUS_STRING_LEN]; + char lastLocationString[GP_LOCATION_STRING_LEN]; + + GPErrorCode errorCode; + GPIBool fatalError; + FILE* diskCache; + GPIOperation* operationList; + GPIProfileList profileList; + GPIPeer* peerList; + GPICallbackData* callbackList; + GPICallbackData* lastCallback; + GPIBuffer updateproBuffer; + GPIBuffer updateuiBuffer; + DArray transfers; // matches up to here + unsigned int nextTransferID; + int productID; + int namespaceID; + char loginTicket[GP_LOGIN_TICKET_LEN]; + GPEnum quietModeFlags; + gsi_time kaTransmit; +} GPIConnection; + +// FUNCTIONS +/////////// +GPResult gpiInitialize(GPConnection* connection, int productID, int namespaceID, + int partnerID); + +void gpiDestroy(GPConnection* connection); + +GPResult gpiReset(GPConnection* connection); + +GPResult gpiProcessConnectionManager(GPConnection* connection); + +GPResult gpiProcess(GPConnection* connection, int blockingOperationID); + +GPResult gpiEnable(GPConnection* connection, GPEnum state); + +GPResult gpiDisable(GPConnection* connection, GPEnum state); + +#ifdef _DEBUG +void gpiReport(GPConnection* connection, void (*report)(const char* output)); +#endif diff --git a/source/gamespy/GP/gpiBuddy.c b/source/gamespy/GP/gpiBuddy.c new file mode 100644 index 000000000..4b39f4bf0 --- /dev/null +++ b/source/gamespy/GP/gpiBuddy.c @@ -0,0 +1,992 @@ +/* +gpiBuddy.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include + +// FUNCTIONS +/////////// +static GPResult gpiSendAuthBuddyRequest(GPConnection* connection, + GPIProfile* profile) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Send the auth. + ///////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\authadd\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\fromprofileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + profile->profileId); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\sig\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + profile->authSig); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiProcessRecvBuddyMessage(GPConnection* connection, + const char* input) { + char buffer[4096]; + int type; + int profileid; + time_t date; + GPICallback callback; + GPIProfile* profile; + GPIBuddyStatus* buddyStatus; + char intValue[16]; + char* str; + unsigned short port; + int productID; + GPIConnection* iconnection = (GPIConnection*)*connection; + char strTemp[max(GP_STATUS_STRING_LEN, GP_LOCATION_STRING_LEN)]; + + // Check the type of bm. + //////////////////////// + if (!gpiValueForKey(input, "\\bm\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + type = atoi(buffer); + + // Get the profile this is from. + //////////////////////////////// + if (!gpiValueForKey(input, "\\f\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + profileid = atoi(buffer); + + // Get the time. + //////////////// + if (!gpiValueForKey(input, "\\date\\", buffer, sizeof(buffer))) + date = time(NULL); + else + date = atoi(buffer); + + // What type of message is this? + //////////////////////////////// + switch (type) { + case GPI_BM_MESSAGE: + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_MESSAGE]; + if (callback.callback != NULL) { + GPRecvBuddyMessageArg* arg; + arg = (GPRecvBuddyMessageArg*)gsimalloc(sizeof(GPRecvBuddyMessageArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + if (!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + arg->message = (char*)gsimalloc(strlen(buffer) + 1); + if (arg->message == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + strcpy(arg->message, buffer); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_MESSAGE)); + } + break; + case GPI_BM_UTM: + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_UTM]; + if (callback.callback != NULL) { + GPRecvBuddyUTMArg* arg; + arg = (GPRecvBuddyUTMArg*)gsimalloc(sizeof(GPRecvBuddyUTMArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + if (!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + arg->message = (char*)gsimalloc(strlen(buffer) + 1); + if (arg->message == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + strcpy(arg->message, buffer); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYUTM)); + } + break; + + case GPI_BM_REQUEST: + // Get the profile, adding if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if (!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the reason. + ////////////////// + if (!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Find where the sig starts. + ///////////////////////////// + str = strstr(buffer, "|signed|"); + if (str == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the sig out of the message. + ////////////////////////////////// + *str = '\0'; + str += 8; + if (strlen(str) != 32) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + freeclear(profile->authSig); + profile->authSig = goastrdup(str); + profile->requestCount++; + + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_REQUEST]; + if (callback.callback != NULL) { + GPRecvBuddyRequestArg* arg; + arg = (GPRecvBuddyRequestArg*)gsimalloc(sizeof(GPRecvBuddyRequestArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + strzcpy(arg->reason, buffer, GP_REASON_LEN); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, + GPI_ADD_BUDDDYREQUEST)); + } + break; + + case GPI_BM_AUTH: + // call the callback + callback = iconnection->callbacks[GPI_RECV_BUDDY_AUTH]; + if (callback.callback != NULL) { + GPRecvBuddyAuthArg* arg; + arg = (GPRecvBuddyAuthArg*)gsimalloc(sizeof(GPRecvBuddyAuthArg)); + + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYAUTH)); + } + break; + + case GPI_BM_REVOKE: + // call the callback + callback = iconnection->callbacks[GPI_RECV_BUDDY_REVOKE]; + if (callback.callback != NULL) { + GPRecvBuddyRevokeArg* arg; + arg = (GPRecvBuddyRevokeArg*)gsimalloc(sizeof(GPRecvBuddyRevokeArg)); + + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = (GPProfile)profileid; + arg->date = (unsigned int)date; + + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYREVOKE)); + } + break; + + case GPI_BM_STATUS: + // Get the profile, adding if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if (!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // This is a buddy. + /////////////////// + if (!profile->buddyStatus) { + profile->buddyStatus = (GPIBuddyStatus*)gsimalloc(sizeof(GPIBuddyStatus)); + if (!profile->buddyStatus) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatus, 0, sizeof(GPIBuddyStatus)); + if (profile->buddyStatusInfo) { + profile->buddyStatus->buddyIndex = profile->buddyStatusInfo->buddyIndex; + gpiRemoveBuddyStatusInfo(profile->buddyStatusInfo); + profile->buddyStatusInfo = NULL; + } else + profile->buddyStatus->buddyIndex = + iconnection->profileList.numBuddies++; + } + + // Get the buddy status. + //////////////////////// + buddyStatus = profile->buddyStatus; + + // Get the msg. + /////////////// + if (!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the status. + ////////////////// + if (!gpiValueForKey(buffer, "|s|", intValue, sizeof(intValue))) { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + } else { + buddyStatus->status = (GPEnum)atoi(intValue); + } + // Get the status string. + ///////////////////////// + freeclear(buddyStatus->statusString); + if (!gpiValueForKey(buffer, "|ss|", strTemp, GP_STATUS_STRING_LEN)) + strTemp[0] = '\0'; + buddyStatus->statusString = goastrdup(strTemp); + if (!buddyStatus->statusString) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the location string. + /////////////////////////// + freeclear(buddyStatus->locationString); + if (!gpiValueForKey(buffer, "|ls|", strTemp, GP_LOCATION_STRING_LEN)) + strTemp[0] = '\0'; + buddyStatus->locationString = goastrdup(strTemp); + if (!buddyStatus->locationString) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the ip. + ////////////// + if (!gpiValueForKey(buffer, "|ip|", intValue, sizeof(intValue))) + buddyStatus->ip = 0; + else + buddyStatus->ip = htonl((unsigned int)atoi(intValue)); + + // Get the port. + //////////////// + if (!gpiValueForKey(buffer, "|p|", intValue, sizeof(intValue))) + buddyStatus->port = 0; + else { + port = (unsigned short)atoi(intValue); + buddyStatus->port = htons(port); + } + + // Get the quiet mode flags. + //////////////////////////// + if (!gpiValueForKey(buffer, "|qm|", intValue, sizeof(intValue))) + buddyStatus->quietModeFlags = GP_SILENCE_NONE; + else + buddyStatus->quietModeFlags = (GPEnum)atoi(intValue); + + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_BUDDY_STATUS]; + if (callback.callback != NULL) { + GPRecvBuddyStatusArg* arg; + arg = (GPRecvBuddyStatusArg*)gsimalloc(sizeof(GPRecvBuddyStatusArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = (GPProfile)profileid; + arg->index = buddyStatus->buddyIndex; + arg->date = (unsigned int)date; + + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_STATUS)); + } + break; + + case GPI_BM_INVITE: + // Get the msg. + /////////////// + if (!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Find the productid. + ////////////////////// + str = strstr(buffer, "|p|"); + if (str == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Skip the |p|. + //////////////// + str += 3; + if (str[0] == '\0') + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the productid. + ///////////////////// + productID = atoi(str); + + // Find the location string (optional - older versions won't have) + str = strstr(buffer, "|l|"); + if (str != NULL) + strzcpy(strTemp, (str + 3), sizeof(strTemp)); + else + strTemp[0] = '\0'; // no location, set to empty string + + // Call the callback. + ///////////////////// + callback = iconnection->callbacks[GPI_RECV_GAME_INVITE]; + if (callback.callback != NULL) { + GPRecvGameInviteArg* arg; + arg = (GPRecvGameInviteArg*)gsimalloc(sizeof(GPRecvGameInviteArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = (GPProfile)profileid; + arg->productID = productID; + strcpy(arg->location, strTemp); + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, NULL, 0)); + } + break; + + case GPI_BM_PING: + // Get the msg. + /////////////// + if (!gpiValueForKey(input, "\\msg\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Send back a pong. + //////////////////// + gpiSendBuddyMessage(connection, profileid, GPI_BM_PONG, "1", 0, NULL); + + break; + +#ifndef NOFILE + case GPI_BM_PONG: + // Lets the transfers handle this. + ////////////////////////////////// + gpiTransfersHandlePong(connection, profileid, NULL); + + break; +#endif + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessRecvBuddyStatusInfo(GPConnection* connection, + const char* input) { + char buffer[1024]; + int profileid; + time_t date; + GPICallback callback; + GPIProfile* profile; + GPIBuddyStatusInfo* buddyStatusInfo; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // This is what the message should look like. Its broken up for easy viewing. + // + // "\bsi\\state\\profile\\bip\\bport\\hostip\\hprivip\" + // "\qport\\hport\\sessflags\\rstatus\\gameType\" + // "\gameVnt\\gameMn\\product\\qmodeflags\" + //////////////////////////////// + date = time(NULL); + // Get the buddy's profile + //////////////////////////////// + if (!gpiValueForKey(input, "\\profile\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + profileid = atoi(buffer); + + // Get the profile from the SDK's list, adding it if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if (!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // This is a buddy. + /////////////////// + if (!profile->buddyStatusInfo) { + profile->buddyStatusInfo = + (GPIBuddyStatusInfo*)gsimalloc(sizeof(GPIBuddyStatusInfo)); + if (!profile->buddyStatusInfo) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatusInfo, 0, sizeof(GPIBuddyStatusInfo)); + if (profile->buddyStatus) { + profile->buddyStatusInfo->buddyIndex = profile->buddyStatus->buddyIndex; + gpiRemoveBuddyStatus(profile->buddyStatus); + profile->buddyStatus = NULL; + } else + profile->buddyStatusInfo->buddyIndex = + iconnection->profileList.numBuddies++; + profile->buddyStatusInfo->extendedInfoKeys = + ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree); + if (!profile->buddyStatusInfo->extendedInfoKeys) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // extract the buddy status information and + // fill in appropriate information. + ///////////////////////////////////////////// + buddyStatusInfo = profile->buddyStatusInfo; + + if (!gpiValueForKey(input, "\\state\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->statusState = (GPEnum)atoi(buffer); + + if (!gpiValueForKey(input, "\\bip\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->buddyIp = htonl((unsigned int)atoi(buffer)); + + if (!gpiValueForKey(input, "\\bport\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->buddyPort = (unsigned short)atoi(buffer); + + if (!gpiValueForKey(input, "\\hostip\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->hostIp = htonl((unsigned int)atoi(buffer)); + + if (!gpiValueForKey(input, "\\hprivip\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->hostPrivateIp = htonl((unsigned int)atoi(buffer)); + + if (!gpiValueForKey(input, "\\qport\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->queryPort = (unsigned short)atoi(buffer); + + if (!gpiValueForKey(input, "\\hport\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->hostPort = (unsigned short)atoi(buffer); + + if (!gpiValueForKey(input, "\\sessflags\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->sessionFlags = (unsigned int)atoi(buffer); + + freeclear(buddyStatusInfo->richStatus); + if (!gpiValueForKey(input, "\\rstatus\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->richStatus = goastrdup(buffer); + + freeclear(buddyStatusInfo->gameType); + if (!gpiValueForKey(input, "\\gameType\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->gameType = goastrdup(buffer); + + freeclear(buddyStatusInfo->gameVariant); + if (!gpiValueForKey(input, "\\gameVnt\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->gameVariant = goastrdup(buffer); + + freeclear(buddyStatusInfo->gameMapName); + if (!gpiValueForKey(input, "\\gameMn\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->gameMapName = goastrdup(buffer); + + if (!gpiValueForKey(input, "\\product\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->productId = (int)atoi(buffer); + + if (!gpiValueForKey(input, "\\qmodeflags\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + buddyStatusInfo->quietModeFlags = (GPEnum)atoi(buffer); + + callback = iconnection->callbacks[GPI_RECV_BUDDY_STATUS]; + if (callback.callback != NULL) { + GPRecvBuddyStatusArg* anArg; + anArg = (GPRecvBuddyStatusArg*)gsimalloc(sizeof(GPRecvBuddyStatusArg)); + if (anArg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + anArg->date = (unsigned int)date; + anArg->index = buddyStatusInfo->buddyIndex; + anArg->profile = profileid; + + CHECK_RESULT(gpiAddCallback(connection, callback, anArg, NULL, 0)); + } + return GP_NO_ERROR; +} + +GPResult gpiProcessRecvBuddyList(GPConnection* connection, const char* input) { + int i = 0, j = 0; + int num = 0; + int index = 0; + char c; + char* str = NULL; + char buffer[512]; + GPIProfile* profile; + GPProfile profileid; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // Process Buddy List Retrieval msg - Format like: + /* =============================================== + \bdy\\list\\final\ + =============================================== */ + + if (!gpiValueForKeyWithIndex(input, "\\bdy\\", &index, buffer, + sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + num = atoi(buffer); + + // Check to make sure list is there + /////////////////////////////////// + str = strstr(input, "\\list\\"); + if (str == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Then increment index to get ready for parsing + //////////////////////////////////////////////// + str += 6; + index += 6; + + for (i = 0; i < num; i++) { + if (i == 0) { + // Manually grab first profile in list - comma delimiter + //////////////////////////////////////////////////////// + for (j = 0; (j < sizeof(buffer)) && ((c = str[j]) != '\0') && (c != ','); + j++) { + buffer[j] = c; + } + buffer[j] = '\0'; + index += j; + } else { + if (!gpiValueForKeyWithIndex(input, ",", &index, buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + } + + profileid = atoi(buffer); + + // Get the profile, adding if needed. + ///////////////////////////////////// + profile = gpiProfileListAdd(connection, profileid); + if (!profile) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Mark as offline buddy for now until we get the real status + ///////////////////////////////////////////////////////////// +#ifdef GP_NEW_STATUS_INFO + // Use new status info as placeholder + profile->buddyStatusInfo = + (GPIBuddyStatusInfo*)gsimalloc(sizeof(GPIBuddyStatusInfo)); + if (!profile->buddyStatusInfo) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatusInfo, 0, sizeof(GPIBuddyStatusInfo)); + + profile->buddyStatusInfo->extendedInfoKeys = + ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree); + if (!profile->buddyStatusInfo->extendedInfoKeys) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + profile->buddyStatusInfo->buddyIndex = + iconnection->profileList.numBuddies++; + profile->buddyStatusInfo->statusState = GP_OFFLINE; +#else + // Use buddy status as placeholder + profile->buddyStatus = (GPIBuddyStatus*)gsimalloc(sizeof(GPIBuddyStatus)); + if (!profile->buddyStatus) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(profile->buddyStatus, 0, sizeof(GPIBuddyStatus)); + profile->buddyStatus->buddyIndex = iconnection->profileList.numBuddies++; + profile->buddyStatus->status = GP_OFFLINE; +#endif + } + + return GP_NO_ERROR; +} + +GPResult gpiSendServerBuddyMessage(GPConnection* connection, int profileid, + int type, const char* message) { + char buffer[3501]; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Copy the message into an internal buffer. + //////////////////////////////////////////// + strzcpy(buffer, message, sizeof(buffer)); + + // Setup the message. + ///////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\bm\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, type); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\t\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\msg\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, buffer); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiSendBuddyMessage(GPConnection* connection, int profileid, int type, + const char* message, int sendOption, + GPIPeerOp* peerOp) { + GPIPeer* peer; + GPIProfile* profile; + // GPIConnection *iconnection = (GPIConnection *)*connection; + peer = gpiGetPeerByProfile(connection, profileid); + if (!peer) { + // Check if we should send this through the server. + //////////////////////////////////////////////////// + if (!gpiGetProfile(connection, profileid, &profile) || + (!profile->buddyStatusInfo || !profile->buddyStatusInfo->buddyPort)) { + if (sendOption == GP_DONT_ROUTE) + return GP_NETWORK_ERROR; + return gpiSendServerBuddyMessage(connection, profileid, type, message); + } + + // Create a new peer connection for this message. + ///////////////////////////////////////////////// + peer = gpiAddPeer(connection, profileid, GPITrue); + if (!peer) + return GP_MEMORY_ERROR; + + // Check if we need a sig. + ////////////////////////// + if (!profile->peerSig) { + // Get the sig. + /////////////// + CHECK_RESULT(gpiPeerGetSig(connection, peer)); + } else { + // Try to connect to the peer. + ////////////////////////////// + CHECK_RESULT(gpiPeerStartConnect(connection, peer)); + } + } else if (peer->state == GPI_PEER_DISCONNECTED) { + if (gpiGetProfile(connection, profileid, &profile)) { + // clear the buddy port to prevent future messages from + // being sent via UDP layer + if (profile->buddyStatusInfo) + profile->buddyStatusInfo->buddyPort = 0; + + // send the message through the server + if (sendOption == GP_DONT_ROUTE) + return GP_NETWORK_ERROR; + if (type < 100) + return gpiSendServerBuddyMessage(connection, profileid, type, message); + } + } + + if (peerOp) { + gpiPeerAddOp(peer, peerOp); + } + // Copy the message. + //////////////////// + CHECK_RESULT(gpiPeerAddMessage(connection, peer, type, message)); + + return GP_NO_ERROR; +} + +GPResult gpiBuddyHandleKeyRequest(GPConnection* connection, GPIPeer* peer) { + char* message; + + // get all the keys and put them in the message part of bm + ////////////////////////////////////////////////////////// + CHECK_RESULT(gpiSaveKeysToBuffer(connection, &message)); + + // Done in case we haven't set any keys + if (message == NULL) + message = ""; + + CHECK_RESULT(gpiSendBuddyMessage(connection, peer->profile, GPI_BM_KEYS_REPLY, + message, GP_DONT_ROUTE, NULL)); + + if (strcmp(message, "") != 0) + freeclear(message); + return GP_NO_ERROR; +} + +GPResult gpiBuddyHandleKeyReply(GPConnection* connection, GPIPeer* peer, + char* buffer) { + GPIProfile* pProfile; + + // Get the profile object to store the keys internally + ////////////////////////////////////////////////////// + + if (!gpiGetProfile(connection, peer->profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // calculate the B64Decoded string len + if (strcmp(buffer, "") == 0) { + GPIPeerOp* anIterator; + + for (anIterator = peer->peerOpQueue.first; anIterator != NULL; + anIterator = anIterator->next) + if (anIterator->type == GPI_BM_KEYS_REQUEST) + break; + + if (!anIterator) { + return GP_NO_ERROR; + } else if (anIterator->type == GPI_BM_KEYS_REQUEST && + anIterator->callback) { + GPGetBuddyStatusInfoKeysArg* arg = + (GPGetBuddyStatusInfoKeysArg*)gsimalloc( + sizeof(GPGetBuddyStatusInfoKeysArg)); + GPICallback callback; + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + + arg->keys = NULL; + arg->numKeys = 0; + arg->values = NULL; + arg->profile = peer->profile; + gpiAddCallback(connection, callback, arg, NULL, 0); + gpiPeerRemoveOp(peer, anIterator); + } + } else { + int decodedLen = 0, index = 0, numKeys, i; + char keyName[512]; + char keyVal[512]; + char decodeKey[512]; + char decodeVal[512]; + gsi_char** keys; + gsi_char** values; + GPIPeerOp* anIterator; + char* checkKey = NULL; + + // start by getting the number of keys + gpiReadKeyAndValue(connection, buffer, &index, keyName, keyVal); + + // do not continue further if the header is missing + if (strcmp(keyName, "keys") != 0) + CallbackError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading keys reply message"); + + numKeys = atoi(keyVal); + + if (numKeys == 0) { + GPIPeerOp* anIterator; + + for (anIterator = peer->peerOpQueue.first; anIterator != NULL; + anIterator = anIterator->next) + if (anIterator->type == GPI_BM_KEYS_REQUEST) + break; + + if (!anIterator) { + return GP_NO_ERROR; + } else if (anIterator->type == GPI_BM_KEYS_REQUEST && + anIterator->callback) { + GPGetBuddyStatusInfoKeysArg* arg = + (GPGetBuddyStatusInfoKeysArg*)gsimalloc( + sizeof(GPGetBuddyStatusInfoKeysArg)); + GPICallback callback; + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + + arg->keys = NULL; + arg->numKeys = 0; + arg->values = NULL; + arg->profile = peer->profile; + gpiAddCallback(connection, callback, arg, NULL, 0); + gpiPeerRemoveOp(peer, anIterator); + } + } else { + keys = (gsi_char**)gsimalloc(sizeof(gsi_char*) * numKeys); + values = (gsi_char**)gsimalloc(sizeof(gsi_char*) * numKeys); + + for (i = 0; i < numKeys; i++) { + gpiReadKeyAndValue(connection, buffer, &index, keyName, keyVal); + B64Decode(keyName, decodeKey, (int)strlen(keyName), &decodedLen, 2); + decodeKey[decodedLen] = '\0'; + B64Decode(keyVal, decodeVal, (int)strlen(keyVal), &decodedLen, 2); + decodeVal[decodedLen] = '\0'; + keys[i] = goastrdup(decodeKey); + values[i] = goastrdup(decodeVal); + + if (gpiStatusInfoCheckKey(connection, + pProfile->buddyStatusInfo->extendedInfoKeys, + decodeKey, &checkKey) == GP_NO_ERROR && + checkKey == NULL) { + gpiStatusInfoAddKey(connection, + pProfile->buddyStatusInfo->extendedInfoKeys, + decodeKey, decodeVal); + } else { + gpiStatusInfoSetKey(connection, + pProfile->buddyStatusInfo->extendedInfoKeys, + decodeKey, decodeVal); + } + } + + for (anIterator = peer->peerOpQueue.first; anIterator != NULL; + anIterator = anIterator->next) + if (anIterator->type == GPI_BM_KEYS_REQUEST) + break; + + if (!anIterator) { + return GP_NO_ERROR; + } else if (anIterator->type == GPI_BM_KEYS_REQUEST && + anIterator->callback) { + GPICallback callback; + GPGetBuddyStatusInfoKeysArg* arg = + (GPGetBuddyStatusInfoKeysArg*)gsimalloc( + sizeof(GPGetBuddyStatusInfoKeysArg)); + + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + + // allocate a key array that points to each extended info key for that + // player + arg->numKeys = numKeys; + + arg->keys = keys; + arg->values = values; + arg->profile = peer->profile; + + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_BUDDYKEYS); + gpiPeerRemoveOp(peer, anIterator); + } + } + } + + return GP_NO_ERROR; +} + +GPResult gpiAuthBuddyRequest(GPConnection* connection, GPProfile profile) { + GPIProfile* pProfile; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Get the profile object. + ////////////////////////// + if (!gpiGetProfile(connection, profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Check for a valid sig. + ///////////////////////// + if (!pProfile->authSig) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Send the request. + //////////////////// + CHECK_RESULT(gpiSendAuthBuddyRequest(connection, pProfile)); + + // freeclear the sig if no more requests. + //////////////////////////////////// + pProfile->requestCount--; + if (!iconnection->infoCaching && (pProfile->requestCount <= 0)) { + freeclear(pProfile->authSig); + if (gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + } + + return GP_NO_ERROR; +} + +GPIBool gpiFixBuddyIndices(GPConnection* connection, GPIProfile* profile, + void* data) { + int baseIndex = (int)(unsigned long)data; + + GSI_UNUSED(connection); + + if (profile->buddyStatus && (profile->buddyStatus->buddyIndex > baseIndex)) + profile->buddyStatus->buddyIndex--; + else if (profile->buddyStatusInfo && + profile->buddyStatusInfo->buddyIndex > baseIndex) + profile->buddyStatusInfo->buddyIndex--; + return GPITrue; +} + +GPResult gpiDeleteBuddy(GPConnection* connection, GPProfile profile, + GPIBool sendServerRequest) { + GPIProfile* pProfile; + GPIConnection* iconnection = (GPIConnection*)*connection; + int index; + + // Get the profile object. + ////////////////////////// + if (!gpiGetProfile(connection, profile, &pProfile)) + Error(connection, GP_PARAMETER_ERROR, "Invalid profile."); + + // Check that this is a buddy. + ////////////////////////////// + // Removed - 092404 BED - User could be a buddy even though we don't have the + // status + // if(!pProfile->buddyStatus) + // Error(connection, GP_PARAMETER_ERROR, "Profile not a buddy."); + + // Send the request. + //////////////////// + if (GPITrue == sendServerRequest) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\delbuddy\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\delprofileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + pProfile->profileId); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\final\\"); + } + + // Need to fix up the buddy indexes. + //////////////////////////////////// + if (pProfile->buddyStatus) { + index = pProfile->buddyStatus->buddyIndex; + assert(index >= 0); + freeclear(pProfile->buddyStatus->statusString); + freeclear(pProfile->buddyStatus->locationString); + freeclear(pProfile->buddyStatus); + if (gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + iconnection->profileList.numBuddies--; + assert(iconnection->profileList.numBuddies >= 0); + gpiProfileMap(connection, gpiFixBuddyIndices, (void*)(unsigned long)index); + } + if (pProfile->buddyStatusInfo) { + index = pProfile->buddyStatusInfo->buddyIndex; + assert(index >= 0); + freeclear(pProfile->buddyStatusInfo->richStatus); + freeclear(pProfile->buddyStatusInfo->gameType); + freeclear(pProfile->buddyStatusInfo->gameVariant); + freeclear(pProfile->buddyStatusInfo->gameMapName); + freeclear(pProfile->buddyStatusInfo); + if (pProfile->buddyStatusInfo->extendedInfoKeys) { + ArrayFree(pProfile->buddyStatusInfo->extendedInfoKeys); + pProfile->buddyStatusInfo->extendedInfoKeys = NULL; + } + + if (gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + iconnection->profileList.numBuddies--; + assert(iconnection->profileList.numBuddies >= 0); + gpiProfileMap(connection, gpiFixBuddyIndices, (void*)(unsigned long)index); + } + return GP_NO_ERROR; +} diff --git a/source/gamespy/GP/gpiBuddy.h b/source/gamespy/GP/gpiBuddy.h new file mode 100644 index 000000000..e4ac919b7 --- /dev/null +++ b/source/gamespy/GP/gpiBuddy.h @@ -0,0 +1,73 @@ +/* +gpiBuddy.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +// Types of bm's. +///////////////// +#define GPI_BM_MESSAGE 1 +#define GPI_BM_REQUEST 2 +#define GPI_BM_REPLY 3 // only used on the backend +#define GPI_BM_AUTH 4 +#define GPI_BM_UTM 5 +#define GPI_BM_REVOKE 6 // remote buddy removed from local list +#define GPI_BM_STATUS 100 +#define GPI_BM_INVITE 101 +#define GPI_BM_PING 102 +#define GPI_BM_PONG 103 +#define GPI_BM_KEYS_REQUEST 104 +#define GPI_BM_KEYS_REPLY 105 +#define GPI_BM_FILE_SEND_REQUEST 200 +#define GPI_BM_FILE_SEND_REPLY 201 +#define GPI_BM_FILE_BEGIN 202 +#define GPI_BM_FILE_END 203 +#define GPI_BM_FILE_DATA 204 +#define GPI_BM_FILE_SKIP 205 +#define GPI_BM_FILE_TRANSFER_THROTTLE 206 +#define GPI_BM_FILE_TRANSFER_CANCEL 207 +#define GPI_BM_FILE_TRANSFER_KEEPALIVE 208 + +// FUNCTIONS +/////////// +GPResult gpiProcessRecvBuddyMessage(GPConnection* connection, + const char* input); + +GPResult gpiProcessRecvBuddyStatusInfo(GPConnection* connection, + const char* input); + +GPResult gpiProcessRecvBuddyList(GPConnection* connection, const char* input); + +GPResult gpiSendServerBuddyMessage(GPConnection* connection, int profileid, + int type, const char* message); + +GPResult gpiSendBuddyMessage(GPConnection* connection, int profileid, int type, + const char* message, int sendOptions, + GPIPeerOp* peerOp); + +GPResult gpiBuddyHandleKeyRequest(GPConnection* connection, GPIPeer* peer); +GPResult gpiBuddyHandleKeyReply(GPConnection* connection, GPIPeer* peer, + char* buffer); + +GPResult gpiAuthBuddyRequest(GPConnection* connection, GPProfile profile); + +GPIBool gpiFixBuddyIndices(GPConnection* connection, GPIProfile* profile, + void* data); + +GPResult gpiDeleteBuddy(GPConnection* connection, GPProfile profile, + GPIBool sendServerRequest); diff --git a/source/gamespy/GP/gpiBuffer.c b/source/gamespy/GP/gpiBuffer.c new file mode 100644 index 000000000..68526ea49 --- /dev/null +++ b/source/gamespy/GP/gpiBuffer.c @@ -0,0 +1,663 @@ +/* +gpiBuffer.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include + +// DEFINES +///////// +#define GPI_DUMP_NET_TRAFFIC + +// FUNCTIONS +/////////// +GPResult gpiAppendCharToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, char c) { + int len; + int size; + char* output; + + assert(outputBuffer != NULL); + + // Init locals. + /////////////// + len = outputBuffer->len; + size = outputBuffer->size; + output = outputBuffer->buffer; + + // Check if it needs to be resized. + /////////////////////////////////// + if (size == len) { + size += GPI_READ_SIZE; + output = (char*)gsirealloc(output, (unsigned int)size + 1); + if (output == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // Do the copy. + /////////////// + output[len] = c; + output[len + 1] = '\0'; + + // Update the buffer info. + ////////////////////////// + outputBuffer->len++; + outputBuffer->size = size; + outputBuffer->buffer = output; + + return GP_NO_ERROR; +} + +GPResult gpiAppendStringToBufferLen(GPConnection* connection, + GPIBuffer* outputBuffer, const char* string, + int stringLen) { + int len; + int size; + char* output; + + assert(string != NULL); + assert(stringLen >= 0); + assert(outputBuffer != NULL); + + if (!string) + return GP_NO_ERROR; + + // Init locals. + /////////////// + len = outputBuffer->len; + size = outputBuffer->size; + output = outputBuffer->buffer; + + // Check if it needs to be resized. + /////////////////////////////////// + if ((size - len) < stringLen) { + size += max(GPI_READ_SIZE, stringLen); + output = (char*)gsirealloc(output, (unsigned int)size + 1); + if (output == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + + // Do the copy. + /////////////// + memcpy(&output[len], string, (unsigned int)stringLen); + output[len + stringLen] = '\0'; + + // Update the buffer info. + ////////////////////////// + outputBuffer->len += stringLen; + outputBuffer->size = size; + outputBuffer->buffer = output; + + return GP_NO_ERROR; +} + +GPResult gpiAppendStringToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, const char* buffer) { + return gpiAppendStringToBufferLen(connection, outputBuffer, buffer, + (int)strlen(buffer)); +} + +GPResult gpiAppendShortToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, short num) { + char shortVal[8]; + sprintf(shortVal, "%d", num); + return gpiAppendStringToBuffer(connection, outputBuffer, shortVal); +} + +GPResult gpiAppendUShortToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, unsigned short num) { + char shortVal[8]; + sprintf(shortVal, "%u", num); + return gpiAppendStringToBuffer(connection, outputBuffer, shortVal); +} + +GPResult gpiAppendIntToBuffer(GPConnection* connection, GPIBuffer* outputBuffer, + int num) { + char intValue[16]; + sprintf(intValue, "%d", num); + return gpiAppendStringToBuffer(connection, outputBuffer, intValue); +} + +GPResult gpiAppendUIntToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, unsigned int num) { + char intValue[16]; + sprintf(intValue, "%u", num); + return gpiAppendStringToBuffer(connection, outputBuffer, intValue); +} + +static GPResult gpiSendData(GPConnection* connection, SOCKET sock, + const char* buffer, int bufferLen, GPIBool* closed, + int* sent, char id[3]) { + int rcode; + + rcode = send(sock, buffer, bufferLen, 0); + if (gsiSocketIsError(rcode)) { + rcode = GOAGetLastError(sock); + if ((rcode != WSAEWOULDBLOCK) && (rcode != WSAEINPROGRESS) && + (rcode != WSAETIMEDOUT)) { + // handle peer connections specially + if ((id[0] == 'P') && (id[1] == 'R')) + return GP_NETWORK_ERROR; + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error sending on a socket."); + } + + *sent = 0; + *closed = GPIFalse; + } else if (rcode == 0) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Comment, + "SENDXXXX(%s): Connection closed\n", id); + + *sent = 0; + *closed = GPITrue; + } else { +#if defined(GPI_DUMP_NET_TRAFFIC) && defined(GSI_COMMON_DEBUG) + { + static int sendCount; + char* buf = (char*)gsimalloc((size_t)(rcode + 1)); + memcpy(buf, buffer, (size_t)rcode); + buf[rcode] = '\0'; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "SENT%04d(%s): %s\n", sendCount++, id, buf); + freeclear(buf); + } +#elif defined(GSI_COMMON_DEBUG) + { + static int sendCount; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "SENT%04d(%s): %d\n", sendCount++, id, rcode); + } +#endif + + *sent = rcode; + *closed = GPIFalse; + } + + return GP_NO_ERROR; +} + +GPResult gpiSendOrBufferChar(GPConnection* connection, GPIPeer_st peer, + char c) { + // GPIBool closed; + // int sent; + /* + assert(peer->outputBuffer.buffer != NULL); + + // Only try to send if the buffer is empty and there are no messages. + ///////////////////////////////////////////////////////////////////// + if(!(peer->outputBuffer.len - peer->outputBuffer.pos) && + !ArrayLength(peer->messages)) + { + CHECK_RESULT(gpiSendData(connection, peer->sock, &c, 1, + &closed, &sent, "PT")); if(sent) return GP_NO_ERROR; + } + + // Buffer if not sent. + ////////////////////// + return gpiAppendCharToBuffer(connection, &peer->outputBuffer, c); + */ + GSI_UNUSED(c); + GSI_UNUSED(peer); + GSI_UNUSED(connection); + return GP_NO_ERROR; +} + +GPResult gpiSendOrBufferStringLenToPeer(GPConnection* connection, + GPIPeer_st peer, const char* string, + int stringLen) { + GPIConnection* iconnection; + + unsigned int sent; + unsigned int total; + unsigned int remaining; + + assert(peer->outputBuffer.buffer != NULL); + + sent = 0; + iconnection = (GPIConnection*)*connection; + remaining = (unsigned int)stringLen; + total = 0; + + // Check for nothing to send. + ///////////////////////////// + if (stringLen == 0) + return GP_NO_ERROR; + + // Only try to send if the buffer is empty and there are no messages. + ///////////////////////////////////////////////////////////////////// + if (!(peer->outputBuffer.len - peer->outputBuffer.pos) && + !ArrayLength(peer->messages)) { + if ((int)remaining <= + (gsUdpEngineGetPeerOutBufferFreeSpace(peer->ip, peer->port) - + GS_UDP_RELIABLE_MSG_HEADER - GS_UDP_MSG_HEADER_LEN)) { + gsUdpEngineSendMessage(peer->ip, peer->port, iconnection->mHeader, + (unsigned char*)string, remaining, gsi_true); + total = remaining; + remaining = 0; + } else { + unsigned int freeSpace = + (unsigned int)gsUdpEngineGetPeerOutBufferFreeSpace(peer->ip, + peer->port); + if (freeSpace > (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER)) { + sent = freeSpace - (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER); + gsUdpEngineSendMessage(peer->ip, peer->port, iconnection->mHeader, + (unsigned char*)string, sent, gsi_true); + total = sent; + remaining -= sent; + } + } + } + + // Buffer what wasn't sent. + /////////////////////////// + if (remaining) + CHECK_RESULT(gpiAppendStringToBufferLen(connection, &peer->outputBuffer, + &string[total], (int)remaining)); + + return GP_NO_ERROR; +} + +/* +GPResult +gpiSendOrBufferStringLen( + GPConnection * connection, + GPIPeer_st peer, + const char * string, + int stringLen + ) +{ + + GPIBool closed; + int sent; + int total; + int remaining; + + + assert(peer->outputBuffer.buffer != NULL); + + remaining = stringLen; + total = 0; + + // Check for nothing to send. + ///////////////////////////// + if(stringLen == 0) + return GP_NO_ERROR; + + // Only try to send if the buffer is empty and there are no messages. + ///////////////////////////////////////////////////////////////////// + if(!(peer->outputBuffer.len - peer->outputBuffer.pos) && +!ArrayLength(peer->messages)) + { + do + { + CHECK_RESULT(gpiSendData(connection, peer->sock, +&string[total], remaining, &closed, &sent, "PT")); if(sent) + { + total += sent; + remaining -= sent; + } + } + while(sent && remaining); + } + + // Buffer what wasn't sent. + /////////////////////////// + if(remaining) + CHECK_RESULT(gpiAppendStringToBufferLen(connection, +&peer->outputBuffer, &string[total], remaining)); + + + GSI_UNUSED(stringLen); + GSI_UNUSED(string); + GSI_UNUSED(peer); + GSI_UNUSED(connection); + return GP_NO_ERROR; +} +*/ + +GPResult gpiSendOrBufferString(GPConnection* connection, GPIPeer_st peer, + char* string) { + return gpiSendOrBufferStringLenToPeer(connection, peer, string, + (int)strlen(string)); +} + +GPResult gpiSendOrBufferInt(GPConnection* connection, GPIPeer_st peer, + int num) { + char intValue[16]; + sprintf(intValue, "%d", num); + return gpiSendOrBufferString(connection, peer, intValue); +} + +GPResult gpiSendOrBufferUInt(GPConnection* connection, GPIPeer_st peer, + unsigned int num) { + char intValue[16]; + sprintf(intValue, "%u", num); + return gpiSendOrBufferString(connection, peer, intValue); +} + +GPResult gpiRecvToBuffer(GPConnection* connection, SOCKET sock, + GPIBuffer* inputBuffer, int* bytesRead, + GPIBool* connClosed, char id[3]) { + char* buffer; + int len; + int size; + int rcode; + int total; + GPIBool closed; + + assert(sock != INVALID_SOCKET); + assert(inputBuffer != NULL); + assert(bytesRead != NULL); + assert(connClosed != NULL); + + // Init locals. + /////////////// + buffer = inputBuffer->buffer; + len = inputBuffer->len; + size = inputBuffer->size; + total = 0; + closed = GPIFalse; + + do { + // Check if the buffer needs to be resized. + /////////////////////////////////////////// + if ((len + GPI_READ_SIZE) > size) { + size = (len + GPI_READ_SIZE); + buffer = (char*)gsirealloc(buffer, (unsigned int)size + 1); + if (buffer == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + inputBuffer->size = size; + inputBuffer->buffer = buffer; + } + + // Read from the network. + rcode = recv(sock, &buffer[len], size - len, 0); + + if (gsiSocketIsError(rcode)) { + int error = GOAGetLastError(sock); + if ((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && + (error != WSAETIMEDOUT)) { + Error(connection, GP_NETWORK_ERROR, + "There was an error reading from a socket."); + } + } else if (rcode == 0) { + // Check for a closed connection. + ///////////////////////////////// + closed = GPITrue; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Comment, + "RECVXXXX(%s): Connection closed\n", id); + } else { +#if defined(GPI_DUMP_NET_TRAFFIC) && defined(GSI_COMMON_DEBUG) + { + static int recvCount; + char* buf = (char*)gsimalloc((size_t)(rcode + 1)); + memcpy(buf, &buffer[len], (size_t)rcode); + buf[rcode] = '\0'; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, + GSIDebugLevel_RawDump, "RECV%04d(%s): %s\n", recvCount++, + id, buf); + freeclear(buf); + } +#elif defined(GSI_COMMON_DEBUG) + { + static int recvCount; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, + GSIDebugLevel_RawDump, "RECV%04d(%s): %d\n", recvCount++, + id, rcode); + } +#endif + // Update the buffer len. + ///////////////////////// + len += rcode; + + // Update the total. + //////////////////// + total += rcode; + } + + buffer[len] = '\0'; + } while ((rcode >= 0) && !closed && (total < (128 * 1024))); + + if (total) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_RawDump, + "RECVTOTL(%s): %d\n", id, total); + } + + // Set output stuff. + //////////////////// + inputBuffer->buffer = buffer; + inputBuffer->len = len; + inputBuffer->size = size; + *bytesRead = total; + *connClosed = closed; + + GSI_UNUSED(id); // to get rid of codewarrior warnings + + return GP_NO_ERROR; +} + +GPResult gpiSendFromBuffer(GPConnection* connection, SOCKET sock, + GPIBuffer* outputBuffer, GPIBool* connClosed, + GPIBool clipSentData, char id[3]) { + GPIBool closed; + int sent; + int total; + int remaining; + char* buffer; + int pos; + int len; + + assert(outputBuffer != NULL); + + buffer = outputBuffer->buffer; + len = outputBuffer->len; + pos = outputBuffer->pos; + remaining = (len - pos); + total = 0; + + // Check for nothing to send. + ///////////////////////////// + if (remaining == 0) + return GP_NO_ERROR; + + do { + CHECK_RESULT(gpiSendData(connection, sock, &buffer[pos + total], remaining, + &closed, &sent, id)); + if (sent) { + total += sent; + remaining -= sent; + } + } while (sent && remaining); + + if (clipSentData) { + if (total > 0) { + memmove(buffer, &buffer[total], (unsigned int)remaining + 1); + len -= total; + } + } else { + pos += total; + } + + assert(len >= 0); + assert(pos >= 0); + assert(pos <= len); + + // Set outputs. + /////////////// + outputBuffer->len = len; + outputBuffer->pos = pos; + if (connClosed) + *connClosed = closed; + + return GP_NO_ERROR; +} + +GPResult gpiSendBufferToPeer(GPConnection* connection, unsigned int ip, + unsigned short port, GPIBuffer* outputBuffer, + GPIBool* closed, GPIBool clipSentData) { + GPIConnection* iconnection = (GPIConnection*)*connection; + // GPIBool closed; + unsigned int remaining; + unsigned char* buffer; + unsigned int pos; + unsigned int len; + unsigned int total = 0; + GSUdpPeerState aPeerState; + assert(outputBuffer != NULL); + + buffer = (unsigned char*)outputBuffer->buffer; + len = (unsigned int)outputBuffer->len; + pos = (unsigned int)outputBuffer->pos; + remaining = (len - pos); + + // Check for nothing to send. + ///////////////////////////// + if (remaining == 0) + return GP_NO_ERROR; + + // length of message remaining must be smaller than total buffer size minus + // gt2 reliable msg header size minus in order to send the message in one + // shot. + if ((int)remaining <= (gsUdpEngineGetPeerOutBufferFreeSpace(ip, port) - + GS_UDP_RELIABLE_MSG_HEADER - GS_UDP_MSG_HEADER_LEN)) { + gsUdpEngineSendMessage(ip, port, iconnection->mHeader, &buffer[pos], + remaining, gsi_true); + total = remaining; + remaining = 0; + } else { + unsigned int freeSpace = 0; + unsigned int sendAmount = 0; + do { + freeSpace = (unsigned int)gsUdpEngineGetPeerOutBufferFreeSpace(ip, port); + sendAmount = + freeSpace - (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER); + if (sendAmount <= (GS_UDP_MSG_HEADER_LEN + GS_UDP_RELIABLE_MSG_HEADER)) + break; + if (gsUdpEngineSendMessage(ip, port, iconnection->mHeader, + &buffer[pos + total], sendAmount, + gsi_true) == GS_UDP_SEND_FAILED) + break; + total += sendAmount; + remaining -= sendAmount; + } while (remaining); + } + + if (clipSentData) { + if (total > 0) { + memmove(buffer, &buffer[total], remaining + 1); + len -= total; + } + } else { + pos += total; + } + // Set outputs. + /////////////// + outputBuffer->len = (int)len; + outputBuffer->pos = (int)pos; + + gsUdpEngineGetPeerState(ip, port, &aPeerState); + if (aPeerState == GS_UDP_PEER_CLOSED) + *closed = GPITrue; + else + *closed = GPIFalse; + + return GP_NO_ERROR; +} + +GPResult gpiReadMessageFromBuffer(GPConnection* connection, + GPIBuffer* inputBuffer, char** message, + int* type, int* plen) { + char* str; + int len; + char intValue[16]; + + // Default. + /////////// + *message = NULL; + + // Check for not enough data. + ///////////////////////////// + if (inputBuffer->len < 5) + return GP_NO_ERROR; + + // Find the end of the header. + ////////////////////////////// + str = strchr(inputBuffer->buffer, '\n'); + if (str != NULL) { + // Check that this is the msg. + ////////////////////////////// + if (strncmp(str - 5, "\\msg\\", 5) != 0) + return GP_NETWORK_ERROR; + + // Cap the header. + ////////////////// + *str = '\0'; + + // Read the header. + /////////////////// + if (!gpiValueForKey(inputBuffer->buffer, "\\m\\", intValue, + sizeof(intValue))) + return GP_NETWORK_ERROR; + *type = atoi(intValue); + + // Get the length. + ////////////////// + if (!gpiValueForKey(inputBuffer->buffer, "\\len\\", intValue, + sizeof(intValue))) + return GP_NETWORK_ERROR; + len = atoi(intValue); + len++; + + // Is the whole message available? + ////////////////////////////////// + if (inputBuffer->len > ((str - inputBuffer->buffer) + len)) { + // Does it not end with a NUL? + ////////////////////////////// + if (str[len] != '\0') + return GP_NETWORK_ERROR; + + // Set the message stuff. + ///////////////////////// + *message = &str[1]; + *plen = (len - 1); + + // Set the position to the end of the message. + ////////////////////////////////////////////// + inputBuffer->pos = ((str - inputBuffer->buffer) + len + 1); + } else { + // Put the LF back. + /////////////////// + *str = '\n'; + } + } + + GSI_UNUSED(connection); + return GP_NO_ERROR; +} + +GPResult gpiClipBufferToPosition(GPConnection* connection, GPIBuffer* buffer) { + if (!buffer || !buffer->buffer || !buffer->pos) + return GP_NO_ERROR; + + buffer->len -= buffer->pos; + if (buffer->len) + memmove(buffer->buffer, buffer->buffer + buffer->pos, + (unsigned int)buffer->len); + buffer->buffer[buffer->len] = '\0'; + buffer->pos = 0; + + GSI_UNUSED(connection); + return GP_NO_ERROR; +} diff --git a/source/gamespy/GP/gpiBuffer.h b/source/gamespy/GP/gpiBuffer.h new file mode 100644 index 000000000..1701ad18f --- /dev/null +++ b/source/gamespy/GP/gpiBuffer.h @@ -0,0 +1,96 @@ +/* +gpiBuffer.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// TYPES +/////// +// A buffer. +//////////// +typedef struct { + char* buffer; + int size; + int len; + int pos; +} GPIBuffer; + +typedef struct GPIPeer_s* GPIPeer_st; + +// FUNCTIONS +/////////// +GPResult gpiAppendCharToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, char c); + +GPResult gpiAppendStringToBufferLen(GPConnection* connection, + GPIBuffer* outputBuffer, const char* string, + int stringLen); + +GPResult gpiAppendStringToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, const char* buffer); + +GPResult gpiAppendShortToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, short num); + +GPResult gpiAppendUShortToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, unsigned short num); + +GPResult gpiAppendIntToBuffer(GPConnection* connection, GPIBuffer* outputBuffer, + int num); + +GPResult gpiAppendUIntToBuffer(GPConnection* connection, + GPIBuffer* outputBuffer, unsigned int num); + +GPResult gpiSendOrBufferChar(GPConnection* connection, GPIPeer_st peer, char c); + +/* +GPResult +gpiSendOrBufferStringLen( + GPConnection * connection, + GPIPeer_st peer, + const char * string, + int stringLen +); +*/ +GPResult gpiSendOrBufferStringLenToPeer(GPConnection* connection, + GPIPeer_st peer, const char* string, + int stringLen); + +GPResult gpiSendOrBufferString(GPConnection* connection, GPIPeer_st peer, + char* string); + +GPResult gpiSendOrBufferInt(GPConnection* connection, GPIPeer_st peer, int num); + +GPResult gpiSendOrBufferUInt(GPConnection* connection, GPIPeer_st peer, + unsigned int num); + +GPResult gpiSendFromBuffer(GPConnection* connection, SOCKET sock, + GPIBuffer* outputBuffer, GPIBool* connClosed, + GPIBool clipSentData, char id[3]); + +GPResult gpiRecvToBuffer(GPConnection* connection, SOCKET sock, + GPIBuffer* inputBuffer, int* bytesRead, + GPIBool* connClosed, char id[3]); + +GPResult gpiReadMessageFromBuffer(GPConnection* connection, + GPIBuffer* inputBuffer, char** message, + int* type, int* len); + +GPResult gpiClipBufferToPosition(GPConnection* connection, GPIBuffer* buffer); + +GPResult gpiSendBufferToPeer(GPConnection* connection, unsigned int ip, + unsigned short port, GPIBuffer* outputBuffer, + GPIBool* closed, GPIBool clipSentData); diff --git a/source/gamespy/GP/gpiCallback.c b/source/gamespy/GP/gpiCallback.c new file mode 100644 index 000000000..95236c74b --- /dev/null +++ b/source/gamespy/GP/gpiCallback.c @@ -0,0 +1,222 @@ +/* +gpiCallback.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include + +// FUNCTIONS +/////////// +void gpiCallErrorCallback(GPConnection* connection, GPResult result, + GPEnum fatal) { + GPICallback callback; + GPIConnection* iconnection = (GPIConnection*)*connection; + + assert(iconnection != NULL); + assert(result != GP_NO_ERROR); + assert((fatal == GP_FATAL) || (fatal == GP_NON_FATAL)); + + if (fatal == GP_FATAL) + iconnection->fatalError = GPITrue; + + callback = iconnection->callbacks[GPI_ERROR]; + if (callback.callback != NULL) { + GPErrorArg* arg; + arg = (GPErrorArg*)gsimalloc(sizeof(GPErrorArg)); + + if (arg != NULL) { + arg->result = result; + arg->fatal = fatal; + arg->errorCode = iconnection->errorCode; + arg->errorString = iconnection->errorString; + } + + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_ERROR); + } +} + +GPResult gpiAddCallback(GPConnection* connection, GPICallback callback, + void* arg, const struct GPIOperation_s* operation, + int type) { + GPICallbackData* data; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Allocate the callback data. + ////////////////////////////// + data = (GPICallbackData*)gsimalloc(sizeof(GPICallbackData)); + if (data == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + data->callback = callback; + data->arg = arg; + if (operation != NULL) + data->operationID = operation->id; + else + data->operationID = 0; + data->type = type; + data->pnext = NULL; + + // Update the list. + /////////////////// + if (iconnection->callbackList == NULL) + iconnection->callbackList = data; + if (iconnection->lastCallback != NULL) + iconnection->lastCallback->pnext = data; + iconnection->lastCallback = data; + + return GP_NO_ERROR; +} + +static void gpiCallCallback(GPConnection* connection, GPICallbackData* data) { + // Call the callback. + ///////////////////// + assert(data->callback.callback != NULL); + assert(data->arg != NULL); + data->callback.callback(connection, data->arg, data->callback.param); + if (data->type == GPI_ADD_MESSAGE) { + freeclear(((GPRecvBuddyMessageArg*)data->arg)->message); + } else if (data->type == GPI_ADD_BUDDYUTM) { + freeclear(((GPRecvBuddyUTMArg*)data->arg)->message); + } else if (data->type == GPI_ADD_NICKS) { + int i; + GPGetUserNicksResponseArg* arg = (GPGetUserNicksResponseArg*)data->arg; + + for (i = 0; i < arg->numNicks; i++) { + freeclear(arg->nicks[i]); + freeclear(arg->uniquenicks[i]); + } + freeclear(arg->nicks); + freeclear(arg->uniquenicks) + } else if (data->type == GPI_ADD_PMATCH) { + GPFindPlayersResponseArg* arg = (GPFindPlayersResponseArg*)data->arg; + + freeclear(arg->matches); + } else if (data->type == GPI_ADD_TRANSFER_CALLBACK) { + GPTransferCallbackArg* arg = (GPTransferCallbackArg*)data->arg; + + if (arg->message) + freeclear(arg->message); + } else if (data->type == GPI_ADD_REVERSE_BUDDIES) { + GPGetReverseBuddiesResponseArg* arg = + (GPGetReverseBuddiesResponseArg*)data->arg; + + if (arg->profiles) + freeclear(arg->profiles); + } else if (data->type == GPI_ADD_SUGGESTED_UNIQUE) { + int i; + GPSuggestUniqueNickResponseArg* arg = + (GPSuggestUniqueNickResponseArg*)data->arg; + + for (i = 0; i < arg->numSuggestedNicks; i++) { + freeclear(arg->suggestedNicks[i]); + } + freeclear(arg->suggestedNicks); + } else if (data->type == GPI_ADD_BUDDYREVOKE) { + GPRecvBuddyRevokeArg* arg = (GPRecvBuddyRevokeArg*)data->arg; + + // Remove the profile from our local lists AFTER the callback has been + // called + gpiDeleteBuddy(connection, arg->profile, GPIFalse); + } else if (data->type == GPI_ADD_REVERSE_BUDDIES_LIST) { + GPGetReverseBuddiesListResponseArg* arg = + (GPGetReverseBuddiesListResponseArg*)data->arg; + + if (arg->matches) + freeclear(arg->matches); + } else if (data->type == GPI_ADD_BUDDYKEYS) { + GPGetBuddyStatusInfoKeysArg* arg = (GPGetBuddyStatusInfoKeysArg*)data->arg; + if (arg->numKeys != 0) { + int i; + for (i = 0; i < arg->numKeys; i++) { + freeclear(arg->keys[i]); + freeclear(arg->values[i]); + } + freeclear(arg->keys); + freeclear(arg->values); + } + } + freeclear(data->arg); + freeclear(data); +} + +GPResult gpiProcessCallbacks(GPConnection* connection, + int blockingOperationID) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPICallbackData* list; + GPICallbackData* last; + GPICallbackData* pcurr; + GPICallbackData* pnext; + GPICallbackData* pprev; + + if (blockingOperationID != 0) { + list = iconnection->callbackList; + last = iconnection->lastCallback; + iconnection->callbackList = NULL; + iconnection->lastCallback = NULL; + + pprev = NULL; + for (pcurr = list; pcurr != NULL;) { + pnext = pcurr->pnext; + + if ((pcurr->operationID == blockingOperationID) || + (pcurr->type == GPI_ADD_ERROR)) { + // Take this one out of the list. + ///////////////////////////////// + if (pprev != NULL) + pprev->pnext = pcurr->pnext; + else + list = pcurr->pnext; + if (last == pcurr) + last = pprev; + + // Call the callback. + ///////////////////// + gpiCallCallback(connection, pcurr); + } else { + pprev = pcurr; + } + + pcurr = pnext; + } + + // Were callbacks added within the callback? + //////////////////////////////////////////// + if (iconnection->callbackList != NULL) { + iconnection->lastCallback->pnext = list; + iconnection->lastCallback = last; + } else { + // Reset the list. + ////////////////// + iconnection->callbackList = list; + iconnection->lastCallback = last; + } + + return GP_NO_ERROR; + } + + while (iconnection->callbackList != NULL) { + list = iconnection->callbackList; + iconnection->callbackList = NULL; + iconnection->lastCallback = NULL; + + for (pcurr = list; pcurr != NULL; pcurr = pnext) { + pnext = pcurr->pnext; + + // Call the callback. + ///////////////////// + gpiCallCallback(connection, pcurr); + } + } + + return GP_NO_ERROR; +} diff --git a/source/gamespy/GP/gpiCallback.h b/source/gamespy/GP/gpiCallback.h new file mode 100644 index 000000000..2508e45c6 --- /dev/null +++ b/source/gamespy/GP/gpiCallback.h @@ -0,0 +1,88 @@ +/* +gpiCallback.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +// Unsolicited Callbacks. +///////////////////////// +enum GPICallbackId { + GPI_ERROR = GP_ERROR, + GPI_RECV_BUDDY_REQUEST = GP_RECV_BUDDY_REQUEST, + GPI_RECV_BUDDY_STATUS = GP_RECV_BUDDY_STATUS, + GPI_RECV_BUDDY_MESSAGE = GP_RECV_BUDDY_MESSAGE, + GPI_RECV_BUDDY_UTM = GP_RECV_BUDDY_UTM, + GPI_RECV_GAME_INVITE = GP_RECV_GAME_INVITE, + GPI_TRANSFER_CALLBACK = GP_TRANSFER_CALLBACK, + GPI_RECV_BUDDY_AUTH = GP_RECV_BUDDY_AUTH, + GPI_RECV_BUDDY_REVOKE = GP_RECV_BUDDY_REVOKE, + GPI_NUM_CALLBACKS +}; + +// Add type - not 0 only for a few. +/////////////////////////////////// +enum GPIAddCallbackType { + GPI_ADD_NORMAL, + GPI_ADD_ERROR, + GPI_ADD_MESSAGE, + GPI_ADD_NICKS, + GPI_ADD_PMATCH, + GPI_ADD_STATUS, + GPI_ADD_BUDDDYREQUEST, + GPI_ADD_TRANSFER_CALLBACK, + GPI_ADD_REVERSE_BUDDIES, + GPI_ADD_SUGGESTED_UNIQUE, + GPI_ADD_BUDDYAUTH, + GPI_ADD_BUDDYUTM, + GPI_ADD_BUDDYREVOKE, + GPI_ADD_REVERSE_BUDDIES_LIST, + GPI_ADD_BUDDYKEYS, + GPI_NUM_ADD_CALLBACK_TYPES +}; + +// TYPES +/////// +// A Callback. +////////////// +typedef struct { + GPCallback callback; + void* param; +} GPICallback; + +// Data for a pending callback. +/////////////////////////////// +typedef struct GPICallbackData { + GPICallback callback; + void* arg; + int type; + int operationID; + struct GPICallbackData* pnext; +} GPICallbackData; + +// FUNCTIONS +/////////// +void gpiCallErrorCallback(GPConnection* connection, GPResult result, + GPEnum fatal); + +typedef struct GPIOperation_s* GPIOperation_st; + +GPResult gpiAddCallback(GPConnection* connection, GPICallback callback, + void* arg, const struct GPIOperation_s* operation, + int type); + +GPResult gpiProcessCallbacks(GPConnection* connection, int blockingOperationID); diff --git a/source/gamespy/GP/gpiConnect.c b/source/gamespy/GP/gpiConnect.c new file mode 100644 index 000000000..5b790404a --- /dev/null +++ b/source/gamespy/GP/gpiConnect.c @@ -0,0 +1,926 @@ +/* +gpiConnect.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include +#include + +// DEFINES +///////// +// Connection Manager Address. +////////////////////////////// +#define GPI_CONNECTION_MANAGER_NAME "gpcm." GSI_DOMAIN_NAME +#define GPI_CONNECTION_MANAGER_PORT 29900 + +#define GPI_UDP_HEADER "gamespygp" +// Random String stuff. +/////////////////////// +#define RANDSTRING \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +// this is off by one +//#define RANDOMCHAR() (RANDSTRING[(rand() * sizeof(RANDSTRING)) / (RAND_MAX +//+ 1)]) +#define RANDOMCHAR() (RANDSTRING[rand() % (sizeof(RANDSTRING) - 1)]) + +// GLOBALS +///////// +char GPConnectionManagerHostname[64] = GPI_CONNECTION_MANAGER_NAME; + +// FUNCTIONS +/////////// +static void randomString(char* buffer, int numChars) { + int i; + + for (i = 0; i < numChars; i++) + buffer[i] = RANDOMCHAR(); + buffer[i] = '\0'; +} + +static GPResult gpiStartConnect(GPConnection* connection, + GPIOperation* operation) { + struct sockaddr_in address; + int rcode; + // int len; + GPIConnection* iconnection = (GPIConnection*)*connection; + struct hostent* host; + + GSUdpErrorCode anError; + strncpy(iconnection->mHeader, GPI_UDP_HEADER, GS_UDP_MSG_HEADER_LEN); + + if (!gsUdpEngineIsInitialized()) { + unsigned short peerPort = GPI_PEER_PORT; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Initializing UDP Layer\n"); + anError = gsUdpEngineInitialize(peerPort, 0, 0, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + if (anError != GS_UDP_NO_ERROR) { + while (anError != GS_UDP_NO_ERROR && peerPort < GPI_PEER_PORT + 100) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, + GSIDebugLevel_Comment, + "Port %d failed, trying next port\n", peerPort); + anError = gsUdpEngineInitialize(++peerPort, 0, 0, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL); + } + if (anError != GS_UDP_NO_ERROR) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, + GSIDebugLevel_HotError, + "Tryed all 100 ports after default port, giving up.\n"); + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_UDP_LAYER, + "There was error starting the UDP layer."); + } + } + if (!iconnection->firewall) { + iconnection->peerPort = peerPort; + } + } else { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "UDP Layer already initialized, using existing port.\n"); + iconnection->peerPort = gsUdpEngineGetLocalPort(); + } + anError = gsUdpEngineAddMsgHandler( + iconnection->mHeader, iconnection->mHeader, NULL, gpiPeerAcceptedCallback, + gpiPeerLeftCallback, gpiPeerPingReplyCallback, gpiPeerMessageCallback, + connection); + if (anError != GS_UDP_NO_ERROR) { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_UDP_LAYER, + "There was an error starting the UDP Layer."); + } + if (iconnection->firewall) { + + /* + // Create the peer listening socket. + //////////////////////////////////// + iconnection->peerSocket = socket(AF_INET, SOCK_STREAM, 0); + if(iconnection->peerSocket == INVALID_SOCKET) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There + was an error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(iconnection->peerSocket,0); + if (rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There + was an error making a socket non-blocking."); + // Bind the socket. + /////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(iconnection->peerSocket, (struct sockaddr *)&address, + sizeof(struct sockaddr_in)); if(gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There + was an error binding a socket."); + + // Start listening on the socket. + ///////////////////////////////// + rcode = listen(iconnection->peerSocket, SOMAXCONN); + if(gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There + was an error listening on a socket."); + + // Get the socket's port. + ///////////////////////// + len = sizeof(struct sockaddr_in); + rcode = getsockname(iconnection->peerSocket, (struct sockaddr *)&address, + &len); + + if (gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There + was an error getting a socket's addres."); iconnection->peerPort = + address.sin_port; + */ + + iconnection->peerPort = 0; + } + /* + else + { + // Deprecated TCP code; Replaced by UDP Layer + // No local port. + ///////////////// + //iconnection->peerSocket = INVALID_SOCKET; + + // Set to nothing because NN will determine this + ////////////////////////// + //iconnection->peerPort = 0; + } + */ + + // Create the cm socket. + //////////////////////// + iconnection->cmSocket = socket(AF_INET, SOCK_STREAM, 0); + if (iconnection->cmSocket == INVALID_SOCKET) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(iconnection->cmSocket, 0); + if (rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error making a socket non-blocking."); + /* + // Bind the socket. + /////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(iconnection->cmSocket, (struct sockaddr *)&address, + sizeof(struct sockaddr_in)); if (gsiSocketIsError(rcode)) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error binding a socket."); + */ + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + // Get the server host. + /////////////////////// + if (inet_addr(GPConnectionManagerHostname) == INADDR_NONE) { + host = gethostbyname(GPConnectionManagerHostname); + if (host == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "Could not resolve connection mananger host name."); + address.sin_addr.s_addr = *(unsigned int*)host->h_addr_list[0]; + // printf("Resolved Hostname and copied address: %s\n", + // inet_ntoa(address.sin_addr)); + } else { + address.sin_addr.s_addr = inet_addr(GPConnectionManagerHostname); + // printf("Using hardcoded address: %s", GPConnectionManagerHostname); + } + + // Connect the socket. + ////////////////////// + assert(address.sin_addr.s_addr != 0); + address.sin_port = htons(GPI_CONNECTION_MANAGER_PORT); + rcode = connect(iconnection->cmSocket, (struct sockaddr*)&address, + sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) { + int error = GOAGetLastError(iconnection->cmSocket); + if ((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && + (error != WSAETIMEDOUT)) { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error connecting a socket."); + } + } + + // We're waiting for the connect to complete. + ///////////////////////////////////////////// + operation->state = GPI_CONNECTING; + iconnection->connectState = GPI_CONNECTING; + + return GP_NO_ERROR; +} + +GPResult gpiConnect(GPConnection* connection, const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char authtoken[GP_AUTHTOKEN_LEN], + const char partnerchallenge[GP_PARTNERCHALLENGE_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum firewall, + GPIBool newuser, GPEnum blocking, GPCallback callback, + void* param) { + GPIConnectData* data; + GPIOperation* operation; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPResult result; + + // Reset if this connection was already used. + ///////////////////////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + CHECK_RESULT(gpiReset(connection)); + + // Error check. + /////////////// + if (iconnection->connectState != GPI_NOT_CONNECTED) + Error(connection, GP_PARAMETER_ERROR, "Invalid connection."); + + // Get the firewall setting. + //////////////////////////// +#if defined(GS_WIRELESS_DEVICE) + GSI_UNUSED(firewall); + iconnection->firewall = GPITrue; +#else + switch (firewall) { + case GP_FIREWALL: + iconnection->firewall = GPITrue; + break; + case GP_NO_FIREWALL: + iconnection->firewall = GPIFalse; + break; + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid firewall."); + } +#endif + + // Get the nick, uniquenick, email, and password. + ///////////////////////////////////////////////// + strzcpy(iconnection->nick, nick, GP_NICK_LEN); + strzcpy(iconnection->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + strzcpy(iconnection->email, email, GP_EMAIL_LEN); + strzcpy(iconnection->password, password, GP_PASSWORD_LEN); + + // Lowercase the email. + /////////////////////// + _strlwr(iconnection->email); + + // Create a connect operation data struct. + ////////////////////////////////////////// + data = (GPIConnectData*)gsimalloc(sizeof(GPIConnectData)); + if (data == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(data, 0, sizeof(GPIConnectData)); + + // Check for new user. + ////////////////////// + data->newuser = newuser; + + // Store pre-auth data. + /////////////////////// + if (authtoken[0] && partnerchallenge[0]) { + strzcpy(data->authtoken, authtoken, GP_AUTHTOKEN_LEN); + strzcpy(data->partnerchallenge, partnerchallenge, GP_PARTNERCHALLENGE_LEN); + } + + // Store cdkey if we have one. + ////////////////////////////// + if (cdkey) + strzcpy(data->cdkey, cdkey, GP_CDKEY_LEN); + + // Add the operation to the list. + ///////////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_CONNECT, data, &operation, + blocking, callback, param)); + + // Start it. + //////////// + result = gpiStartConnect(connection, operation); + if (result != GP_NO_ERROR) { + operation->result = result; + gpiFailedOpCallback(connection, operation); + gpiDisconnect(connection, GPIFalse); + return result; + } + + // Process it if blocking. + ////////////////////////// + if (operation->blocking) + CHECK_RESULT(gpiProcess(connection, operation->id)); + + return GP_NO_ERROR; +} + +static GPResult gpiSendLogin(GPConnection* connection, GPIConnectData* data) { + char buffer[512]; + char response[33]; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIProfile* profile; + char* passphrase; + char userBuffer[GP_NICK_LEN + GP_EMAIL_LEN]; + char partnerBuffer[11]; + char* user; + + // Construct the user challenge. + //////////////////////////////// + randomString(data->userChallenge, sizeof(data->userChallenge) - 1); + + // Hash the password. + ///////////////////// + if (data->partnerchallenge[0]) + passphrase = data->partnerchallenge; + else + passphrase = iconnection->password; + MD5Digest((unsigned char*)passphrase, strlen(passphrase), data->passwordHash); + + // Construct the user. + ////////////////////// + if (iconnection->partnerID != GP_PARTNERID_GAMESPY) { + sprintf(partnerBuffer, "%d@", iconnection->partnerID); + } else { + // GS ID's do not stash the partner ID in the auth challenge to support + // legacy clients. + strcpy(partnerBuffer, ""); + } + + if (data->authtoken[0]) + user = data->authtoken; + else if (iconnection->uniquenick[0]) { + sprintf(userBuffer, "%s%s", partnerBuffer, iconnection->uniquenick); + user = userBuffer; + } else { + sprintf(userBuffer, "%s%s@%s", partnerBuffer, iconnection->nick, + iconnection->email); + user = userBuffer; + } + + // Construct the response. + ////////////////////////// + sprintf(buffer, "%s%s%s%s%s%s", data->passwordHash, + " ", user, + data->userChallenge, data->serverChallenge, data->passwordHash); + MD5Digest((unsigned char*)buffer, strlen(buffer), response); + + // Check for an existing profile. + ///////////////////////////////// + if (iconnection->infoCaching) { + gpiFindProfileByUser(connection, iconnection->nick, iconnection->email, + &profile); + if (profile != NULL) { + // Get the userid and profileid. + //////////////////////////////// + iconnection->userid = profile->userId; + iconnection->profileid = profile->profileId; + } + } + + // Construct the outgoing message. + ////////////////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\login\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\challenge\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + data->userChallenge); + if (data->authtoken[0]) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\authtoken\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + data->authtoken); + } else if (iconnection->uniquenick[0]) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + iconnection->uniquenick); + } else { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\user\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + iconnection->nick); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "@"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + iconnection->email); + } + if (iconnection->userid != 0) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\userid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->userid); + } + if (iconnection->profileid != 0) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->profileid); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\response\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, response); + if (iconnection->firewall == GP_FIREWALL) + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\firewall\\1"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\port\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->peerPort); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\productid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->productID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\gamename\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + __GSIACGamename); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sdkrevision\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, GPI_SDKREV); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\quiet\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->quietModeFlags); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\1"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +static GPResult gpiSendNewuser(GPConnection* connection, GPIConnectData* data) { + GPIConnection* iconnection = (GPIConnection*)*connection; + size_t i; + const int useAlternateEncoding = 1; + + // Encrypt the password (xor with random values) + char passwordenc[GP_PASSWORDENC_LEN]; + gpiEncodeString(iconnection->password, passwordenc); + + // Construct the outgoing message. + ////////////////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\newuser\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + iconnection->email); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + iconnection->nick); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\passwordenc\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, passwordenc); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\productid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->productID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\gamename\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + __GSIACGamename); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + iconnection->uniquenick); + if (data->cdkey[0]) { + // Encrypt the cdkey (xor with random values) + char cdkeyxor[GP_CDKEY_LEN]; + char cdkeyenc[GP_CDKEYENC_LEN]; + size_t cdkeylen = strlen(data->cdkey); + + Util_RandSeed((unsigned long)GP_XOR_SEED); + for (i = 0; i < cdkeylen; i++) { + // XOR each character with the next rand + char aRand = (char)Util_RandInt(0, 0xFF); + cdkeyxor[i] = (char)(data->cdkey[i] ^ aRand); + } + cdkeyxor[i] = '\0'; + + // Base 64 it (printable chars only) + B64Encode(cdkeyxor, cdkeyenc, (int)cdkeylen, useAlternateEncoding); + + // gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + // "\\cdkey\\"); gpiAppendStringToBuffer(connection, + // &iconnection->outputBuffer, data->cdkey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\cdkeyenc\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, cdkeyenc); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\1"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiProcessConnect(GPConnection* connection, GPIOperation* operation, + const char* input) { + char buffer[512]; + char check[33]; + char uniquenick[GP_UNIQUENICK_LEN]; + GPIConnectData* data; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPICallback callback; + GPIProfile* profile; + char userBuffer[GP_NICK_LEN + GP_EMAIL_LEN]; + char partnerBuffer[11]; + char* user; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, input, GPIFalse)) { + // Is this a deleted profile? + ///////////////////////////// + if ((iconnection->errorCode == GP_LOGIN_PROFILE_DELETED) && + iconnection->profileid) { + // Remove this profile object. + ////////////////////////////// + gpiRemoveProfileByID(connection, iconnection->profileid); + + // If we have the profileid/userid cached, lose them. + ///////////////////////////////////////////////////// + iconnection->userid = 0; + iconnection->profileid = 0; + } + // Check for creating an existing profile. + ////////////////////////////////////////// + else if (iconnection->errorCode == GP_NEWUSER_BAD_NICK) { + // Store the pid. + ///////////////// + if (gpiValueForKey(input, "\\pid\\", buffer, sizeof(buffer))) + iconnection->profileid = atoi(buffer); + } + + // Call the callbacks. + ////////////////////// + CallbackFatalError(connection, GP_SERVER_ERROR, iconnection->errorCode, + iconnection->errorString); + } + + // Get a pointer to the data. + ///////////////////////////// + data = (GPIConnectData*)operation->data; + + switch (operation->state) { + case GPI_CONNECTING: + // This should be \lc\1. + //////////////////////// + if (strncmp(input, "\\lc\\1", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the server challenge. + //////////////////////////// + if (!gpiValueForKey(input, "\\challenge\\", data->serverChallenge, + sizeof(data->serverChallenge))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Check if this is a new user. + /////////////////////////////// + if (data->newuser) { + // Send a new user message. + /////////////////////////// + CHECK_RESULT(gpiSendNewuser(connection, data)); + + // Update the operation's state. + //////////////////////////////// + operation->state = GPI_REQUESTING; + } else { + // Send a login message. + //////////////////////// + CHECK_RESULT(gpiSendLogin(connection, data)); + + // Update the operation's state. + //////////////////////////////// + operation->state = GPI_LOGIN; + } + + break; + + case GPI_REQUESTING: + // This should be \nur\. + //////////////////////// + if (strncmp(input, "\\nur\\", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the userid. + ////////////////// + if (!gpiValueForKey(input, "\\userid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexepected data was received from the server."); + iconnection->userid = atoi(buffer); + + // Get the profileid. + ///////////////////// + if (!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexepected data was received from the server."); + iconnection->profileid = atoi(buffer); + + // Send a login request. + //////////////////////// + CHECK_RESULT(gpiSendLogin(connection, data)); + + // Update the operation's state. + //////////////////////////////// + operation->state = GPI_LOGIN; + + break; + + case GPI_LOGIN: + // This should be \lc\2. + //////////////////////// + if (strncmp(input, "\\lc\\2", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the sesskey. + /////////////////// + if (!gpiValueForKey(input, "\\sesskey\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexepected data was received from the server."); + iconnection->sessKey = atoi(buffer); + + // Get the userid. + ////////////////// + if (!gpiValueForKey(input, "\\userid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexepected data was received from the server."); + iconnection->userid = atoi(buffer); + + // Get the profileid. + ///////////////////// + if (!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexepected data was received from the server."); + iconnection->profileid = atoi(buffer); + + // Get the uniquenick. + ////////////////////// + if (!gpiValueForKey(input, "\\uniquenick\\", uniquenick, + sizeof(uniquenick))) + uniquenick[0] = '\0'; + + // Get the loginticket. + ////////////////////// + if (!gpiValueForKey(input, "\\lt\\", iconnection->loginTicket, + sizeof(iconnection->loginTicket))) + iconnection->loginTicket[0] = '\0'; + + // Construct the user. + ////////////////////// + if (iconnection->partnerID != GP_PARTNERID_GAMESPY) { + sprintf(partnerBuffer, "%d@", iconnection->partnerID); + } else { + // GS ID's do not stash the partner ID in the auth challenge to support + // legacy clients. + strcpy(partnerBuffer, ""); + } + + if (data->authtoken[0]) + user = data->authtoken; + else if (iconnection->uniquenick[0]) { + sprintf(userBuffer, "%s%s", partnerBuffer, iconnection->uniquenick); + user = userBuffer; + } else { + sprintf(userBuffer, "%s%s@%s", partnerBuffer, iconnection->nick, + iconnection->email); + user = userBuffer; + } + + // Construct the check. + /////////////////////// + sprintf(buffer, "%s%s%s%s%s%s", data->passwordHash, + " ", user, + data->serverChallenge, data->userChallenge, data->passwordHash); + MD5Digest((unsigned char*)buffer, strlen(buffer), check); + + // Get the proof. + ///////////////// + if (!gpiValueForKey(input, "\\proof\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexepected data was received from the server."); + + // Check the server authentication. + /////////////////////////////////// + if (memcmp(check, buffer, 32) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, + GP_LOGIN_SERVER_AUTH_FAILED, + "Could not authenticate server."); + + // Add the local profile to the list. + ///////////////////////////////////// + if (iconnection->infoCaching) { + profile = gpiProfileListAdd(connection, iconnection->profileid); + profile->profileId = iconnection->profileid; + profile->userId = iconnection->userid; + } + + // Set the connect state. + ///////////////////////// + iconnection->connectState = GPI_CONNECTED; + + // Call the connect-response callback. + ////////////////////////////////////// + callback = operation->callback; + if (callback.callback != NULL) { + GPConnectResponseArg* arg; + arg = (GPConnectResponseArg*)gsimalloc(sizeof(GPConnectResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPConnectResponseArg)); + + arg->profile = (GPProfile)iconnection->profileid; + arg->result = GP_NO_ERROR; + strzcpy(arg->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + break; + default: + break; + } + + return GP_NO_ERROR; +} + +GPResult gpiCheckConnect(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + int state; + + // Check if the connection is completed. + //////////////////////////////////////// + CHECK_RESULT( + gpiCheckSocketConnect(connection, iconnection->cmSocket, &state)); + + // Check for a failed attempt. + ////////////////////////////// + if (state == GPI_DISCONNECTED) + CallbackFatalError(connection, GP_SERVER_ERROR, GP_LOGIN_CONNECTION_FAILED, + "The server has refused the connection."); + + // Check if not finished connecting. + //////////////////////////////////// + if (state == GPI_NOT_CONNECTED) + return GP_NO_ERROR; + + // We're now negotiating the connection. + //////////////////////////////////////// + assert(state == GPI_CONNECTED); + iconnection->connectState = GPI_NEGOTIATING; + + return GP_NO_ERROR; +} + +static GPIBool gpiDisconnectCleanupProfile(GPConnection* connection, + GPIProfile* profile, void* data) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GSI_UNUSED(data); + + // Even if we cache buddy/block info, free it up to get rid of mem + // leaks, just don't remove the profile until we save the cache. + ////////////////////////////////////////////////////////////////// + if (profile->buddyStatus && iconnection->infoCachingBuddyAndBlockOnly == 0) { + freeclear(profile->buddyStatus->statusString); + freeclear(profile->buddyStatus->locationString); + freeclear(profile->buddyStatus); + } + if (profile->buddyStatusInfo && + iconnection->infoCachingBuddyAndBlockOnly == 0) { + freeclear(profile->buddyStatusInfo->richStatus); + freeclear(profile->buddyStatusInfo->gameType); + freeclear(profile->buddyStatusInfo->gameVariant); + freeclear(profile->buddyStatusInfo->gameMapName); + if (profile->buddyStatusInfo->extendedInfoKeys) { + ArrayFree(profile->buddyStatusInfo->extendedInfoKeys); + profile->buddyStatusInfo->extendedInfoKeys = NULL; + } + freeclear(profile->buddyStatusInfo); + } + + freeclear(profile->authSig); + freeclear(profile->peerSig); + profile->requestCount = 0; + + // Remove Profile if: + // (there is no info to cache) or + // (we only cache buddies/blocked and the user is not a buddy or a block) + if ((!profile->cache) || + (iconnection->infoCachingBuddyAndBlockOnly == GPITrue && + profile->buddyStatus == 0 && profile->buddyStatusInfo == 0)) { + gpiRemoveProfile(connection, profile); + return GPIFalse; + } + + return GPITrue; +} + +void gpiDisconnect(GPConnection* connection, GPIBool tellServer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIPeer* peer; + GPIPeer* delPeer; + GPIBool connClosed; + + // Check if we're already disconnected. + // PANTS|05.15.00 + /////////////////////////////////////// + if (iconnection->connectState == GPI_DISCONNECTED) + return; + + // Skip most of this stuff if we never actually connected. + // PANTS|05.16.00 + ////////////////////////////////////////////////////////// + if (iconnection->connectState != GPI_NOT_CONNECTED) { + // Are we connected? + //////////////////// + if (tellServer && (iconnection->connectState == GPI_CONNECTED)) { + // Send the disconnect. + /////////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\logout\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\final\\"); + } + + // Always flush remaining messages. + // PANTS|05.16.00 + /////////////////////////////////// + gpiSendFromBuffer(connection, iconnection->cmSocket, + &iconnection->outputBuffer, &connClosed, GPITrue, "CM"); + + // Cleanup the connection. + ////////////////////////// + if (iconnection->cmSocket != INVALID_SOCKET) { + shutdown(iconnection->cmSocket, 2); + closesocket(iconnection->cmSocket); + iconnection->cmSocket = INVALID_SOCKET; + } + + if (/*iconnection->peerSocket != INVALID_SOCKET*/ + gsUdpEngineIsInitialized()) { + // shutdown(iconnection->peerSocket, 2); + // closesocket(iconnection->peerSocket); + // iconnection->peerSocket = INVALID_SOCKET; + gsUdpEngineRemoveMsgHandler(iconnection->mHeader); + if (gsUdpEngineNoMoreMsgHandlers() && gsUdpEngineNoApp()) + gsUdpEngineShutdown(); + } + + // We're disconnected. + ////////////////////// + iconnection->connectState = GPI_DISCONNECTED; + + // Don't keep the userid/profileid. + /////////////////////////////////// + iconnection->userid = 0; + iconnection->profileid = 0; + } + + // freeclear all the memory. + /////////////////////// + freeclear(iconnection->socketBuffer.buffer); + freeclear(iconnection->inputBuffer); + freeclear(iconnection->outputBuffer.buffer); + freeclear(iconnection->updateproBuffer.buffer); + freeclear(iconnection->updateuiBuffer.buffer); + while (iconnection->operationList != NULL) + gpiRemoveOperation(connection, iconnection->operationList); + iconnection->operationList = NULL; + for (peer = iconnection->peerList; peer != NULL;) { + delPeer = peer; + peer = peer->pnext; + gpiDestroyPeer(connection, delPeer); + } + iconnection->peerList = NULL; + + // Cleanup buddies. + // This is not optimal - because we can't continue the mapping + // after freeing a profile, we need to start it all over again. + /////////////////////////////////////////////////////////////// + while (!gpiProfileMap(connection, gpiDisconnectCleanupProfile, NULL)) { + }; +} diff --git a/source/gamespy/GP/gpiConnect.h b/source/gamespy/GP/gpiConnect.h new file mode 100644 index 000000000..a0ffe7d7b --- /dev/null +++ b/source/gamespy/GP/gpiConnect.h @@ -0,0 +1,48 @@ +/* +gpiConnect.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +// Connect States. +////////////////// +#define GPI_NOT_CONNECTED 0 +#define GPI_CONNECTING 1 +#define GPI_NEGOTIATING 2 +#define GPI_CONNECTED 3 +#define GPI_DISCONNECTED 4 +#define GPI_PROFILE_DELETING 5 + +// FUNCTIONS +/////////// +GPResult gpiConnect(GPConnection* connection, const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char authtoken[GP_AUTHTOKEN_LEN], + const char partnerchallenge[GP_PARTNERCHALLENGE_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum firewall, + GPIBool newuser, GPEnum blocking, GPCallback callback, + void* param); + +void gpiDisconnect(GPConnection* connection, GPIBool tellServer); + +GPResult gpiProcessConnect(GPConnection* connection, GPIOperation* operation, + const char* input); + +GPResult gpiCheckConnect(GPConnection* connection); diff --git a/source/gamespy/GP/gpiInfo.c b/source/gamespy/GP/gpiInfo.c new file mode 100644 index 000000000..cc4325d93 --- /dev/null +++ b/source/gamespy/GP/gpiInfo.c @@ -0,0 +1,1138 @@ +/* +gpiInfo.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" + +// FUNCTIONS +/////////// +static GPIBool gpiIsValidDate(int day, int month, int year) { + // Check for a blank. + ///////////////////// + if ((day == 0) && (month == 0) && (year == 0)) + return GPITrue; + + // Check for negatives. + /////////////////////// + if ((day < 0) || (month < 0) || (year < 0)) + return GPIFalse; + + // Validate the day of the month. + ///////////////////////////////// + switch (month) { + // No month. + //////////// + case 0: + // Can't specify a day without a month. + /////////////////////////////////////// + if (day != 0) + return GPIFalse; + break; + + // 31-day month. + //////////////// + case 1: + case 3: + case 5: + case 7: + case 8: + case 10: + case 12: + if (day > 31) + return GPIFalse; + break; + + // 30-day month. + //////////////// + case 4: + case 6: + case 9: + case 11: + if (day > 30) + return GPIFalse; + break; + + // 28/29-day month. + /////////////////// + case 2: + // Leap year? + ///////////// + if ((((year % 4) == 0) && ((year % 100) != 0)) || ((year % 400) == 0)) { + if (day > 29) + return GPIFalse; + } else { + if (day > 28) + return GPIFalse; + } + break; + + // Invalid month. + ///////////////// + default: + return GPIFalse; + } + + // Check that the date is in the valid range. + // 01.01.1900 - 06.06.2079 + // PANTS|02.14.2000 + ///////////////////////////////////////////// + if (year < 1900) + return GPIFalse; + if (year > 2079) + return GPIFalse; + if (year == 2079) { + if (month > 6) + return GPIFalse; + if ((month == 6) && (day > 6)) + return GPIFalse; + } + + return GPITrue; +} + +static GPResult gpiDateToInt(GPConnection* connection, int* date, int day, + int month, int year) { + int temp; + + // Pack the day/month/year into an int. + // 31-22: day + // 23-16: month + // 15-00: year + /////////////////////////////////////// + + // Error check. + /////////////// + assert(gpiIsValidDate(day, month, year)); + if (!gpiIsValidDate(day, month, year)) + Error(connection, GP_PARAMETER_ERROR, "Invalid date."); + + // Pack! + //////// + temp = 0; + temp |= (day << 24); + temp |= (month << 16); + temp |= year; + + // Set it. + ////////// + *date = temp; + + return GP_NO_ERROR; +} + +static GPResult gpiIntToDate(GPConnection* connection, int date, int* day, + int* month, int* year) { + int d; + int m; + int y; + + // Unpack the int into a day/month/year. + // 31-22: day + // 23-16: month + // 15-00: year + //////////////////////////////////////// + + // Split up the date. + ///////////////////// + d = ((date >> 24) & 0xFF); + m = ((date >> 16) & 0xFF); + y = (date & 0xFFFF); + + // Error check. + /////////////// + // assert(gpiIsValidDate(d, m, y)); + if (!gpiIsValidDate(d, m, y)) + Error(connection, GP_PARAMETER_ERROR, "Invalid date."); + + // It's all good. + ///////////////// + *day = d; + *month = m; + *year = y; + + return GP_NO_ERROR; +} + +void gpiInfoCacheToArg(const GPIInfoCache* cache, GPGetInfoResponseArg* arg) { + // Copy.... + /////////// + if (cache->nick) + strzcpy(arg->nick, cache->nick, GP_NICK_LEN); + else + arg->nick[0] = '\0'; + if (cache->uniquenick) + strzcpy(arg->uniquenick, cache->uniquenick, GP_UNIQUENICK_LEN); + else + arg->uniquenick[0] = '\0'; + if (cache->email) + strzcpy(arg->email, cache->email, GP_EMAIL_LEN); + else + arg->email[0] = '\0'; + if (cache->firstname) + strzcpy(arg->firstname, cache->firstname, GP_FIRSTNAME_LEN); + else + arg->firstname[0] = '\0'; + if (cache->lastname) + strzcpy(arg->lastname, cache->lastname, GP_LASTNAME_LEN); + else + arg->lastname[0] = '\0'; + if (cache->homepage) + strzcpy(arg->homepage, cache->homepage, GP_HOMEPAGE_LEN); + else + arg->homepage[0] = '\0'; + arg->icquin = cache->icquin; + strzcpy(arg->zipcode, cache->zipcode, GP_ZIPCODE_LEN); + strzcpy(arg->countrycode, cache->countrycode, GP_COUNTRYCODE_LEN); + arg->longitude = cache->longitude; + arg->latitude = cache->latitude; + if (cache->place) + strzcpy(arg->place, cache->place, GP_PLACE_LEN); + else + arg->place[0] = '\0'; + arg->birthday = cache->birthday; + arg->birthmonth = cache->birthmonth; + arg->birthyear = cache->birthyear; + arg->sex = (GPEnum)cache->sex; + arg->publicmask = (GPEnum)cache->publicmask; + if (cache->aimname) + strzcpy(arg->aimname, cache->aimname, GP_AIMNAME_LEN); + else + arg->aimname[0] = '\0'; + + // Non string members + arg->icquin = cache->icquin; + arg->longitude = cache->longitude; + arg->latitude = cache->latitude; + + arg->birthday = cache->birthday; + arg->birthmonth = cache->birthmonth; + arg->birthyear = cache->birthyear; + arg->sex = (GPEnum)cache->sex; + arg->publicmask = (GPEnum)cache->publicmask; + + arg->pic = cache->pic; + arg->occupationid = cache->occupationid; + arg->industryid = cache->industryid; + arg->incomeid = cache->incomeid; + arg->marriedid = cache->marriedid; + arg->childcount = cache->childcount; + arg->interests1 = cache->interests1; + arg->ownership1 = cache->ownership1; + arg->conntypeid = cache->conntypeid; +} + +GPResult gpiProcessGetInfo(GPConnection* connection, GPIOperation* operation, + const char* input) { + GPIInfoCache infoCache; + char buffer[64]; + int profileid; + GPIProfile* profile; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPICallback callback; + GPIPeer* peer; + char nick[GP_NICK_LEN]; + char uniquenick[GP_UNIQUENICK_LEN]; + char email[GP_EMAIL_LEN]; + char firstname[GP_FIRSTNAME_LEN]; + char lastname[GP_LASTNAME_LEN]; + char homepage[GP_HOMEPAGE_LEN]; + char aimname[GP_AIMNAME_LEN]; + GPIBool saveSig; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \pi\. + /////////////////////// + if (strncmp(input, "\\pi\\", 4) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the profile id. + ////////////////////// + if (!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + profileid = atoi(buffer); + assert(profileid > 0); + + // Get the profile - we might not have a profile object. + //////////////////////////////////////////////////////// + gpiGetProfile(connection, (GPProfile)profileid, &profile); + + // Setup the info cache. + //////////////////////// + memset(&infoCache, 0, sizeof(GPIInfoCache)); + infoCache.nick = nick; + infoCache.uniquenick = uniquenick; + infoCache.email = email; + infoCache.firstname = firstname; + infoCache.lastname = lastname; + infoCache.homepage = homepage; + infoCache.aimname = aimname; + + // Parse the info. + ////////////////// + if (!gpiValueForKey(input, "\\nick\\", infoCache.nick, GP_NICK_LEN)) + infoCache.nick[0] = '\0'; + if (!gpiValueForKey(input, "\\uniquenick\\", infoCache.uniquenick, + GP_UNIQUENICK_LEN)) + infoCache.uniquenick[0] = '\0'; + if (!gpiValueForKey(input, "\\email\\", infoCache.email, GP_EMAIL_LEN)) + infoCache.email[0] = '\0'; + if (!gpiValueForKey(input, "\\firstname\\", infoCache.firstname, + GP_FIRSTNAME_LEN)) + infoCache.firstname[0] = '\0'; + if (!gpiValueForKey(input, "\\lastname\\", infoCache.lastname, + GP_LASTNAME_LEN)) + infoCache.lastname[0] = '\0'; + if (!gpiValueForKey(input, "\\icquin\\", buffer, sizeof(buffer))) + infoCache.icquin = -1; + else + infoCache.icquin = atoi(buffer); + if (!gpiValueForKey(input, "\\homepage\\", infoCache.homepage, + GP_HOMEPAGE_LEN)) + infoCache.homepage[0] = '\0'; + if (!gpiValueForKey(input, "\\zipcode\\", infoCache.zipcode, + sizeof(infoCache.zipcode))) + infoCache.zipcode[0] = '\0'; + if (!gpiValueForKey(input, "\\countrycode\\", infoCache.countrycode, + sizeof(infoCache.countrycode))) + infoCache.countrycode[0] = '\0'; + if (!gpiValueForKey(input, "\\lon\\", buffer, sizeof(buffer))) + infoCache.longitude = 0; + else + infoCache.longitude = (float)atof(buffer); + if (!gpiValueForKey(input, "\\lat\\", buffer, sizeof(buffer))) + infoCache.latitude = 0; + else + infoCache.latitude = (float)atof(buffer); + if (!gpiValueForKey(input, "\\loc\\", infoCache.place, GP_PLACE_LEN)) + infoCache.place[0] = '\0'; + if (!gpiValueForKey(input, "\\birthday\\", buffer, sizeof(buffer))) { + infoCache.birthday = 0; + infoCache.birthmonth = 0; + infoCache.birthyear = 0; + } else { + CHECK_RESULT(gpiIntToDate(connection, atoi(buffer), &infoCache.birthday, + &infoCache.birthmonth, &infoCache.birthyear)); + } + if (!gpiValueForKey(input, "\\sex\\", buffer, sizeof(buffer))) + infoCache.sex = GP_PAT; + else if (buffer[0] == '0') + infoCache.sex = GP_MALE; + else if (buffer[0] == '1') + infoCache.sex = GP_FEMALE; + else + infoCache.sex = GP_PAT; + if (!gpiValueForKey(input, "\\pmask\\", buffer, sizeof(buffer))) + infoCache.publicmask = 0xFFFFFFFF; + else + infoCache.publicmask = atoi(buffer); + if (!gpiValueForKey(input, "\\aim\\", infoCache.aimname, GP_AIMNAME_LEN)) + infoCache.aimname[0] = '\0'; + if (!gpiValueForKey(input, "\\pic\\", buffer, sizeof(buffer))) + infoCache.pic = 0; + else + infoCache.pic = atoi(buffer); + if (!gpiValueForKey(input, "\\occ\\", buffer, sizeof(buffer))) + infoCache.occupationid = 0; + else + infoCache.occupationid = atoi(buffer); + if (!gpiValueForKey(input, "\\ind\\", buffer, sizeof(buffer))) + infoCache.industryid = 0; + else + infoCache.industryid = atoi(buffer); + if (!gpiValueForKey(input, "\\inc\\", buffer, sizeof(buffer))) + infoCache.incomeid = 0; + else + infoCache.incomeid = atoi(buffer); + if (!gpiValueForKey(input, "\\mar\\", buffer, sizeof(buffer))) + infoCache.marriedid = 0; + else + infoCache.marriedid = atoi(buffer); + if (!gpiValueForKey(input, "\\chc\\", buffer, sizeof(buffer))) + infoCache.childcount = 0; + else + infoCache.childcount = atoi(buffer); + if (!gpiValueForKey(input, "\\i1\\", buffer, sizeof(buffer))) + infoCache.interests1 = 0; + else + infoCache.interests1 = atoi(buffer); + if (!gpiValueForKey(input, "\\o1\\", buffer, sizeof(buffer))) + infoCache.ownership1 = 0; + else + infoCache.ownership1 = atoi(buffer); + if (!gpiValueForKey(input, "\\conn\\", buffer, sizeof(buffer))) + infoCache.conntypeid = 0; + else + infoCache.conntypeid = atoi(buffer); + + // Get the peer sig. + //////////////////// + if (!gpiValueForKey(input, "\\sig\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + saveSig = iconnection->infoCaching; + + // Is there a pending peer connection looking for a sig? + //////////////////////////////////////////////////////// + for (peer = iconnection->peerList; peer != NULL; peer = peer->pnext) { + // Is it the same profile? + ////////////////////////// + if (peer->profile == profileid) { + // Is it getting the sig? + ///////////////////////// + if (peer->state == GPI_PEER_GETTING_SIG) { + // We need to make sure there's an actual profile object. + ///////////////////////////////////////////////////////// + if (!profile) + profile = gpiProfileListAdd(connection, profileid); + + // It got it. + ///////////// + peer->state = GPI_PEER_GOT_SIG; + + saveSig = GPITrue; + } + } + } + + // Cache info? + ////////////// + if (!profile && iconnection->infoCaching) + profile = gpiProfileListAdd(connection, profileid); + + // Set the peer sig. + //////////////////// + if (saveSig) { + freeclear(profile->peerSig); + profile->peerSig = goastrdup(buffer); + } + + // Caching info? + //////////////// + if (iconnection->infoCaching) + gpiSetInfoCache(connection, profile, &infoCache); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if (callback.callback != NULL) { + GPGetInfoResponseArg* arg; + arg = (GPGetInfoResponseArg*)gsimalloc(sizeof(GPGetInfoResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + gpiInfoCacheToArg(&infoCache, arg); + arg->result = GP_NO_ERROR; + arg->profile = (GPProfile)profileid; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + +GPResult gpiAddLocalInfo(GPConnection* connection, GPIBuffer* buffer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Add updatepro info. + ////////////////////// + if (iconnection->updateproBuffer.len > 0) { + gpiAppendStringToBuffer(connection, buffer, "\\updatepro\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, buffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, buffer, + iconnection->updateproBuffer.buffer); + gpiAppendStringToBuffer(connection, buffer, "\\partnerid\\"); + gpiAppendIntToBuffer(connection, buffer, iconnection->partnerID); + gpiAppendStringToBuffer(connection, buffer, "\\final\\"); + + iconnection->updateproBuffer.len = 0; + } + + // Add updateui info. + ////////////////////// + if (iconnection->updateuiBuffer.len > 0) { + gpiAppendStringToBuffer(connection, buffer, "\\updateui\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, buffer, iconnection->sessKey); + gpiAppendStringToBuffer(connection, buffer, + iconnection->updateuiBuffer.buffer); + gpiAppendStringToBuffer(connection, buffer, "\\final\\"); + + iconnection->updateuiBuffer.len = 0; + } + + return GP_NO_ERROR; +} + +static GPResult gpiSendLocalInfo(GPConnection* connection, const char* info, + const char* value) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + CHECK_RESULT( + gpiAppendStringToBuffer(connection, &iconnection->updateproBuffer, info)); + CHECK_RESULT(gpiAppendStringToBuffer(connection, + &iconnection->updateproBuffer, value)); + + return GP_NO_ERROR; +} + +static GPResult gpiSendUserInfo(GPConnection* connection, const char* info, + const char* value) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + CHECK_RESULT( + gpiAppendStringToBuffer(connection, &iconnection->updateuiBuffer, info)); + CHECK_RESULT( + gpiAppendStringToBuffer(connection, &iconnection->updateuiBuffer, value)); + + return GP_NO_ERROR; +} + +GPResult gpiSetInfoi(GPConnection* connection, GPEnum info, int value) { + char intValue[16]; + + // Check the info param. + //////////////////////// + switch (info) { + case GP_ZIPCODE: + // Error check. + /////////////// + if (value < 0) + Error(connection, GP_PARAMETER_ERROR, "Invalid zipcode."); + + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\zipcode\\", intValue)); + + break; + + case GP_SEX: + // Check the sex type. + ////////////////////// + switch (value) { + case GP_MALE: + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", "0")); + break; + + case GP_FEMALE: + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", "1")); + break; + + case GP_PAT: + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", "2")); + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid sex."); + } + + break; + + case GP_ICQUIN: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\icquin\\", intValue)); + + break; + + case GP_CPUBRANDID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\cpubrandid\\", intValue)); + + break; + + case GP_CPUSPEED: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\cpuspeed\\", intValue)); + + break; + + case GP_MEMORY: + // Divide by 16. + //////////////// + value /= 16; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\memory\\", intValue)); + + break; + + case GP_VIDEOCARD1RAM: + // Divide by 4. + /////////////// + value /= 4; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\videocard1ram\\", intValue)); + + break; + + case GP_VIDEOCARD2RAM: + // Divide by 4. + /////////////// + value /= 4; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\videocard2ram\\", intValue)); + + break; + + case GP_CONNECTIONID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\connectionid\\", intValue)); + + break; + + case GP_CONNECTIONSPEED: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\connectionspeed\\", intValue)); + + break; + + case GP_HASNETWORK: + // A boolean. + ///////////// + if (value) + value = 1; + + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendUserInfo(connection, "\\hasnetwork\\", intValue)); + + break; + + case GP_PIC: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\pic\\", intValue)); + + break; + + case GP_OCCUPATIONID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\occ\\", intValue)); + + break; + + case GP_INDUSTRYID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\ind\\", intValue)); + + break; + + case GP_INCOMEID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\inc\\", intValue)); + + break; + + case GP_MARRIEDID: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\mar\\", intValue)); + + break; + + case GP_CHILDCOUNT: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\chc\\", intValue)); + + break; + + case GP_INTERESTS1: + // Convert it to a string. + ////////////////////////// + sprintf(intValue, "%d", value); + + // Send it to the server. + ///////////////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\i1\\", intValue)); + + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid info."); + } + + return GP_NO_ERROR; +} + +GPResult gpiSetInfos(GPConnection* connection, GPEnum info, const char* value) { + + GPIConnection* iconnection = (GPIConnection*)*connection; + char buffer[256]; + char sex; + + // password encryption stuff + char passwordenc[GP_PASSWORDENC_LEN]; + + // Error check. + /////////////// + if (value == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + + // Check the info param. + //////////////////////// + switch (info) { + case GP_NICK: + if (!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_NICK_LEN); + strzcpy(iconnection->nick, buffer, GP_NICK_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\nick\\", buffer)); + break; + + case GP_UNIQUENICK: + if (!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_UNIQUENICK_LEN); + strzcpy(iconnection->uniquenick, buffer, GP_UNIQUENICK_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\uniquenick\\", buffer)); + break; + + case GP_EMAIL: + if (!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_EMAIL_LEN); + _strlwr(buffer); + strzcpy(iconnection->email, buffer, GP_EMAIL_LEN); + CHECK_RESULT(gpiSendUserInfo(connection, "\\email\\", buffer)); + break; + + case GP_PASSWORD: + if (!value[0]) + Error(connection, GP_PARAMETER_ERROR, "Invalid value."); + strzcpy(buffer, value, GP_PASSWORD_LEN); + strzcpy(iconnection->password, buffer, GP_PASSWORD_LEN); + gpiEncodeString(iconnection->password, passwordenc); + CHECK_RESULT(gpiSendUserInfo(connection, "\\passwordenc\\", passwordenc)); + break; + + case GP_FIRSTNAME: + strzcpy(buffer, value, GP_FIRSTNAME_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\firstname\\", buffer)); + break; + + case GP_LASTNAME: + strzcpy(buffer, value, GP_LASTNAME_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\lastname\\", buffer)); + break; + + case GP_HOMEPAGE: + strzcpy(buffer, value, GP_HOMEPAGE_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\homepage\\", buffer)); + break; + + case GP_ZIPCODE: + strzcpy(buffer, value, GP_ZIPCODE_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\zipcode\\", buffer)); + break; + + case GP_COUNTRYCODE: + // Error check. + /////////////// + if (strlen(value) != 2) + Error(connection, GP_PARAMETER_ERROR, "Invalid countrycode."); + + strzcpy(buffer, value, GP_COUNTRYCODE_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\countrycode\\", buffer)); + break; + + case GP_SEX: + sex = (char)toupper(value[0]); + if (sex == 'M') + strcpy(buffer, "0"); + else if (sex == 'F') + strcpy(buffer, "1"); + else + strcpy(buffer, "2"); + + CHECK_RESULT(gpiSendLocalInfo(connection, "\\sex\\", buffer)); + break; + + case GP_ICQUIN: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\icquin\\", buffer)); + break; + + case GP_CPUSPEED: + CHECK_RESULT(gpiSetInfoi(connection, GP_CPUSPEED, atoi(value))); + break; + + case GP_MEMORY: + CHECK_RESULT(gpiSetInfoi(connection, GP_MEMORY, atoi(value))); + break; + + case GP_VIDEOCARD1STRING: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\videocard1string\\", buffer)); + break; + + case GP_VIDEOCARD1RAM: + CHECK_RESULT(gpiSetInfoi(connection, GP_VIDEOCARD1RAM, atoi(value))); + break; + + case GP_VIDEOCARD2STRING: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\videocard2string\\", buffer)); + break; + + case GP_VIDEOCARD2RAM: + CHECK_RESULT(gpiSetInfoi(connection, GP_VIDEOCARD2RAM, atoi(value))); + break; + + case GP_CONNECTIONSPEED: + CHECK_RESULT(gpiSetInfoi(connection, GP_CONNECTIONSPEED, atoi(value))); + break; + + case GP_HASNETWORK: + CHECK_RESULT(gpiSetInfoi(connection, GP_HASNETWORK, atoi(value))); + break; + + case GP_OSSTRING: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\osstring\\", buffer)); + break; + + case GP_AIMNAME: + strzcpy(buffer, value, GP_AIMNAME_LEN); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\aim\\", buffer)); + break; + + case GP_PIC: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\pic\\", buffer)); + break; + + case GP_OCCUPATIONID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\occ\\", buffer)); + break; + + case GP_INDUSTRYID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\ind\\", buffer)); + break; + + case GP_INCOMEID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\inc\\", buffer)); + break; + + case GP_MARRIEDID: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\mar\\", buffer)); + break; + + case GP_CHILDCOUNT: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\chc\\", buffer)); + break; + + case GP_INTERESTS1: + strzcpy(buffer, value, sizeof(buffer)); + CHECK_RESULT(gpiSendLocalInfo(connection, "\\i1\\", buffer)); + break; + + default: + Error(connection, GP_PARAMETER_ERROR, "Invalid info."); + } + + return GP_NO_ERROR; +} + +GPResult gpiSetInfod(GPConnection* connection, GPEnum info, int day, int month, + int year) { + int date; + char intValue[16]; + + // Birthday is the only date supported. + /////////////////////////////////////// + if (info != GP_BIRTHDAY) + Error(connection, GP_PARAMETER_ERROR, "Invalid info."); + + // Convert the date into our internal format. + ///////////////////////////////////////////// + CHECK_RESULT(gpiDateToInt(connection, &date, day, month, year)); + + // Convert the int to a string. + /////////////////////////////// + sprintf(intValue, "%d", date); + + // Send the date. + ///////////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\birthday\\", intValue)); + + return GP_NO_ERROR; +} + +GPResult gpiSetInfoMask(GPConnection* connection, GPEnum mask) { + char buffer[16]; + + // Convert the mask to a string. + //////////////////////////////// + sprintf(buffer, "%d", mask); + + // Send it. + /////////// + CHECK_RESULT(gpiSendLocalInfo(connection, "\\publicmask\\", buffer)); + + return GP_NO_ERROR; +} + +GPResult gpiSendGetInfo(GPConnection* connection, int profileid, + int operationid) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\getprofile\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, profileid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operationid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiGetInfo(GPConnection* connection, GPProfile profile, + GPEnum checkCache, GPEnum blocking, GPCallback callback, + void* param) { + GPIProfile* pProfile; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIOperation* operation = NULL; + GPIBool useCache; + GPResult result; + int id; + + // Check checkCache. + //////////////////// + useCache = (checkCache == GP_CHECK_CACHE) ? GPITrue : GPIFalse; + + // Check the info cache state. + ////////////////////////////// + if (!iconnection->infoCaching) + useCache = GPIFalse; + + // Check for using cached info. + /////////////////////////////// + if (callback && useCache && gpiGetProfile(connection, profile, &pProfile) && + pProfile->cache) { + GPICallback gpiCallback; + GPGetInfoResponseArg* arg; + + arg = (GPGetInfoResponseArg*)gsimalloc(sizeof(GPGetInfoResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + gpiInfoCacheToArg(pProfile->cache, arg); + arg->result = GP_NO_ERROR; + arg->profile = profile; + + gpiCallback.callback = callback; + gpiCallback.param = param; + + // Add a dummy operation. + ///////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, + GP_BLOCKING, callback, param)); + id = operation->id; + + // Add the callback. + //////////////////// + CHECK_RESULT(gpiAddCallback(connection, gpiCallback, arg, operation, 0)); + + // Remove the dummy operation. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + } else { + // Add the operation. + ///////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, + blocking, callback, param)); + id = operation->id; + + // Send a request for info. + /////////////////////////// + result = gpiSendGetInfo(connection, profile, operation->id); + CHECK_RESULT(result); + } + + // Process it if blocking. + ////////////////////////// + if (blocking) { + result = gpiProcess(connection, id); + CHECK_RESULT(result); + } + + return GP_NO_ERROR; +} + +GPResult gpiGetInfoNoWait(GPConnection* connection, GPProfile profile, + GPGetInfoResponseArg* arg) { + GPIProfile* pProfile; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Check the info cache state. + ////////////////////////////// + if (!iconnection->infoCaching) + return GP_NETWORK_ERROR; + + // Check to see if we have the info cached. + /////////////////////////////////////////// + if (!gpiGetProfile(connection, profile, &pProfile) || !pProfile->cache) + return GP_NETWORK_ERROR; + + // Fill in the arg. + /////////////////// + gpiInfoCacheToArg(pProfile->cache, arg); + arg->result = GP_NO_ERROR; + arg->profile = profile; + + return GP_NO_ERROR; +} + +GPIBool gpiSetInfoCache(GPConnection* connection, pGPIProfile profile, + const GPIInfoCache* cache) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Check if we're caching info. + /////////////////////////////// + if (!iconnection->infoCaching) + return GPITrue; + + // Free any old cached info. + //////////////////////////// + gpiFreeInfoCache(profile); + + // Allocate the new info. + ///////////////////////// + profile->cache = (GPIInfoCache*)gsimalloc(sizeof(GPIInfoCache)); + + // Copy in the new info. + //////////////////////// + if (profile->cache) { + *profile->cache = *cache; + profile->cache->nick = goastrdup(cache->nick); + profile->cache->uniquenick = goastrdup(cache->uniquenick); + profile->cache->email = goastrdup(cache->email); + profile->cache->firstname = goastrdup(cache->firstname); + profile->cache->lastname = goastrdup(cache->lastname); + profile->cache->homepage = goastrdup(cache->homepage); + profile->cache->aimname = goastrdup(cache->aimname); + } + + return (profile->cache != NULL) ? GPITrue : GPIFalse; +} + +void gpiFreeInfoCache(pGPIProfile profile) { + if (!profile->cache) + return; + + freeclear(profile->cache->nick); + freeclear(profile->cache->uniquenick); + freeclear(profile->cache->email); + freeclear(profile->cache->firstname); + freeclear(profile->cache->lastname); + freeclear(profile->cache->homepage); + freeclear(profile->cache->aimname); + freeclear(profile->cache); +} diff --git a/source/gamespy/GP/gpiInfo.h b/source/gamespy/GP/gpiInfo.h new file mode 100644 index 000000000..5fcb86187 --- /dev/null +++ b/source/gamespy/GP/gpiInfo.h @@ -0,0 +1,89 @@ +/* +gpiInfo.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// TYPES +/////// +// Profile info cache. +////////////////////// +typedef struct { + char* nick; + char* uniquenick; + char* email; + char* firstname; + char* lastname; + char* homepage; + int icquin; + char zipcode[GP_ZIPCODE_LEN]; + char countrycode[GP_COUNTRYCODE_LEN]; + float longitude; // negative is west, positive is east. (0, 0) means unknown. + float + latitude; // negative is south, positive is north. (0, 0) means unknown. + char place[GP_PLACE_LEN]; // e.g., "USA|California|Irvine", "South + // Korea|Seoul", "Turkey" + int birthday; + int birthmonth; + int birthyear; + GPEnum sex; + int publicmask; + char* aimname; + int pic; + int occupationid; + int industryid; + int incomeid; + int marriedid; + int childcount; + int interests1; + int ownership1; + int conntypeid; +} GPIInfoCache; + +// FUNCTIONS +/////////// +GPResult gpiSetInfoi(GPConnection* connection, GPEnum info, int value); + +GPResult gpiSetInfos(GPConnection* connection, GPEnum info, const char* value); + +GPResult gpiSetInfod(GPConnection* connection, GPEnum info, int day, int month, + int year); + +GPResult gpiSetInfoMask(GPConnection* connection, GPEnum mask); + +void gpiInfoCacheToArg(const GPIInfoCache* cache, GPGetInfoResponseArg* arg); + +GPResult gpiGetInfo(GPConnection* connection, GPProfile profile, + GPEnum checkCache, GPEnum blocking, GPCallback callback, + void* param); + +GPResult gpiGetInfoNoWait(GPConnection* connection, GPProfile profile, + GPGetInfoResponseArg* arg); + +GPResult gpiProcessGetInfo(GPConnection* connection, GPIOperation* operation, + const char* input); + +GPResult gpiSendGetInfo(GPConnection* connection, int profileid, + int operationid); + +GPResult gpiAddLocalInfo(GPConnection* connection, GPIBuffer* buffer); + +typedef struct GPIProfile* pGPIProfile; + +GPIBool gpiSetInfoCache(GPConnection* connection, pGPIProfile profile, + const GPIInfoCache* cache); + +void gpiFreeInfoCache(pGPIProfile profile); diff --git a/source/gamespy/GP/gpiKeys.c b/source/gamespy/GP/gpiKeys.c new file mode 100644 index 000000000..d42250a56 --- /dev/null +++ b/source/gamespy/GP/gpiKeys.c @@ -0,0 +1,188 @@ +#include "gpi.h" + +void gpiStatusInfoKeyFree(void* element) { + GPIKey* aKey = (GPIKey*)element; + freeclear(aKey->keyName); + freeclear(aKey->keyValue); +} + +GPResult gpiStatusInfoKeysInit(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + iconnection->extendedInfoKeys = + ArrayNew(sizeof(GPIKey), GPI_INITIAL_NUM_KEYS, gpiStatusInfoKeyFree); + if (!iconnection->extendedInfoKeys) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + return GP_NO_ERROR; +} + +void gpiStatusInfoKeysDestroy(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + if (iconnection->extendedInfoKeys) { + ArrayFree(iconnection->extendedInfoKeys); + iconnection->extendedInfoKeys = NULL; + } +} + +int gpiStatusInfoKeyCompFunc(const void* elem1, const void* elem2) { + GPIKey *key1 = (GPIKey*)elem1, *key2 = (GPIKey*)elem2; + return strcmp(key1->keyName, key2->keyName); +} + +GPResult gpiStatusInfoAddKey(GPConnection* connection, DArray keys, + const char* theKeyName, const char* theKeyValue) { + GPIKey aKey; + GS_ASSERT(keys); + GS_ASSERT(theKeyName); + GS_ASSERT(theKeyValue); + + if (!theKeyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + if (!theKeyValue) + Error(connection, GP_PARAMETER_ERROR, "Invalid key value"); + + aKey.keyName = goastrdup(theKeyName); + aKey.keyValue = goastrdup(theKeyValue); + + ArrayInsertSorted(keys, &aKey, gpiStatusInfoKeyCompFunc); + + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoDelKey(GPConnection* connection, DArray keys, + const char* keyName) { + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) { + ArrayDeleteAt(keys, anIndex); + } + + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoSetKey(GPConnection* connection, DArray keys, + const char* keyName, const char* newKeyValue) { + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) { + GPIKey* aKeyFound = (GPIKey*)ArrayNth(keys, anIndex); + gsifree(aKeyFound->keyValue); + aKeyFound->keyValue = goastrdup(newKeyValue); + } + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoGetKey(GPConnection* connection, DArray keys, + const char* keyName, char** keyValue) { + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) { + GPIKey* aKeyFound = (GPIKey*)ArrayNth(keys, anIndex); + *keyValue = goastrdup(aKeyFound->keyValue); + } + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiStatusInfoCheckKey(GPConnection* connection, DArray keys, + const char* keyName, char** keyValue) { + GPIKey aKey; + int anIndex; + GS_ASSERT(keys); + GS_ASSERT(keyName); + + if (!keyName) + Error(connection, GP_PARAMETER_ERROR, "Invalid key name"); + + aKey.keyName = goastrdup(keyName); + anIndex = ArraySearch(keys, &aKey, gpiStatusInfoKeyCompFunc, 0, 1); + if (anIndex != NOT_FOUND) { + GPIKey* aKeyFound = (GPIKey*)ArrayNth(keys, anIndex); + *keyValue = aKeyFound->keyValue; + } + freeclear(aKey.keyName); + return GP_NO_ERROR; +} + +GPResult gpiSaveKeysToBuffer(GPConnection* connection, char** buffer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + char* tempPoint; + int sizeKeys = 0, i, bytesWritten; + int base64KeyNameLen, base64KeyValLen; + int aLength = ArrayLength(iconnection->extendedInfoKeys); + + char keysHeader[64]; + sprintf(keysHeader, "\\keys\\%d", aLength); + + // figure out the size of the buffer to allocate + // by adding up the key value pairs with backslashes + for (i = 0; i < aLength; i++) { + GPIKey* aKey = (GPIKey*)ArrayNth(iconnection->extendedInfoKeys, i); + if (strlen(aKey->keyName) % 3 != 0) + base64KeyNameLen = (int)(strlen(aKey->keyName) * 4 / 3) + + (int)(4 - (strlen(aKey->keyName) % 3)); + else + base64KeyNameLen = (int)(strlen(aKey->keyName) * 4 / 3); + if (strlen(aKey->keyValue) % 3 != 0) + base64KeyValLen = (int)(strlen(aKey->keyValue) * 4 / 3) + + (int)(4 - (strlen(aKey->keyValue) % 3)); + else + base64KeyValLen = (int)(strlen(aKey->keyValue) * 4 / 3); + sizeKeys += 1 + base64KeyNameLen + 1 + base64KeyValLen; + } + *buffer = (char*)gsimalloc(strlen(keysHeader) + (size_t)sizeKeys + 1); + if (*buffer == NULL) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_HotError, + "gpiSaveKeysToBuffer: buffer Out of memory."); + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + } + bytesWritten = sprintf(*buffer, keysHeader); + tempPoint = *buffer + bytesWritten; + for (i = 0; i < aLength; i++) { + GPIKey* aKey = (GPIKey*)ArrayNth(iconnection->extendedInfoKeys, i); + strcat(tempPoint, "\\"); + tempPoint++; + B64Encode(aKey->keyName, tempPoint, (int)strlen(aKey->keyName), 2); + if (strlen(aKey->keyName) % 3 != 0) + tempPoint += (int)(strlen(aKey->keyName) * 4 / 3) + + (4 - (strlen(aKey->keyName) % 3)); + else + tempPoint += (int)(strlen(aKey->keyName) * 4 / 3); + strcat(tempPoint, "\\"); + tempPoint++; + B64Encode(aKey->keyValue, tempPoint, (int)strlen(aKey->keyValue), 2); + if (strlen(aKey->keyValue) % 3 != 0) + tempPoint += (int)(strlen(aKey->keyValue) * 4 / 3) + + (4 - (strlen(aKey->keyValue) % 3)); + else + tempPoint += (int)(strlen(aKey->keyValue) * 4 / 3); + } + return GP_NO_ERROR; +} diff --git a/source/gamespy/GP/gpiKeys.h b/source/gamespy/GP/gpiKeys.h new file mode 100644 index 000000000..691f204ea --- /dev/null +++ b/source/gamespy/GP/gpiKeys.h @@ -0,0 +1,25 @@ +#pragma once + +#include "gpi.h" +#define GPI_INITIAL_NUM_KEYS 1 + +typedef struct { + char* keyName; + char* keyValue; +} GPIKey; + +void gpiStatusInfoKeyFree(void* element); +GPResult gpiStatusInfoKeysInit(GPConnection* connection); +void gpiStatusInfoKeysDestroy(GPConnection* connection); +int gpiStatusInfoKeyCompFunc(const void* elem1, const void* elem2); +GPResult gpiStatusInfoAddKey(GPConnection* connection, DArray keys, + const char* theKeyName, const char* theKeyValue); +GPResult gpiStatusInfoDelKey(GPConnection* connection, DArray keys, + const char* keyName); +GPResult gpiStatusInfoSetKey(GPConnection* connection, DArray keys, + const char* keyName, const char* newKeyValue); +GPResult gpiStatusInfoGetKey(GPConnection* connection, DArray keys, + const char* keyName, char** keyValue); +GPResult gpiSaveKeysToBuffer(GPConnection* connection, char** buffer); +GPResult gpiStatusInfoCheckKey(GPConnection* connection, DArray keys, + const char* keyName, char** keyValue); diff --git a/source/gamespy/GP/gpiOperation.c b/source/gamespy/GP/gpiOperation.c new file mode 100644 index 000000000..c7385b1ba --- /dev/null +++ b/source/gamespy/GP/gpiOperation.c @@ -0,0 +1,313 @@ +/* +gpiOperation.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include + +// FUNCTIONS +/////////// +GPResult gpiFailedOpCallback(GPConnection* connection, + const GPIOperation* operation) { + GPICallback callback; + GPIConnection* iconnection = (GPIConnection*)*connection; + + assert(connection != NULL); + assert(*connection != NULL); + assert(operation != NULL); + + callback = operation->callback; + if (callback.callback != NULL) { + // Handle based on operation type. + ////////////////////////////////// + switch (operation->type) { + case GPI_CONNECT: { + GPConnectResponseArg* arg; + arg = (GPConnectResponseArg*)gsimalloc(sizeof(GPConnectResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPConnectResponseArg)); + arg->result = operation->result; + if (iconnection->errorCode == GP_NEWUSER_BAD_NICK) { + arg->profile = (GPProfile)iconnection->profileid; + iconnection->profileid = 0; + } + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_NEW_PROFILE: { + GPNewProfileResponseArg* arg; + arg = + (GPNewProfileResponseArg*)gsimalloc(sizeof(GPNewProfileResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPNewProfileResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_DELETE_PROFILE: { + GPDeleteProfileResponseArg* arg; + arg = (GPDeleteProfileResponseArg*)gsimalloc( + sizeof(GPDeleteProfileResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPDeleteProfileResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_GET_INFO: { + GPGetInfoResponseArg* arg; + arg = (GPGetInfoResponseArg*)gsimalloc(sizeof(GPGetInfoResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPGetInfoResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_PROFILE_SEARCH: { + GPProfileSearchResponseArg* arg; + arg = (GPProfileSearchResponseArg*)gsimalloc( + sizeof(GPProfileSearchResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPProfileSearchResponseArg)); + arg->result = operation->result; + ((GPProfileSearchResponseArg*)arg)->matches = NULL; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_REGISTER_UNIQUENICK: { + GPRegisterUniqueNickResponseArg* arg; + arg = (GPRegisterUniqueNickResponseArg*)gsimalloc( + sizeof(GPRegisterUniqueNickResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPRegisterUniqueNickResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + case GPI_REGISTER_CDKEY: { + GPRegisterCdKeyResponseArg* arg; + arg = (GPRegisterCdKeyResponseArg*)gsimalloc( + sizeof(GPRegisterCdKeyResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(arg, 0, sizeof(GPRegisterCdKeyResponseArg)); + arg->result = operation->result; + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + break; + } + default: + assert(0); + } + } + + return GP_NO_ERROR; +} + +GPResult gpiAddOperation(GPConnection* connection, int type, void* data, + GPIOperation** op, GPEnum blocking, + GPCallback callback, void* param) { + GPIOperation* operation; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Create a new operation struct. + ///////////////////////////////// + operation = (GPIOperation*)gsimalloc(sizeof(GPIOperation)); + if (operation == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Set the data. + //////////////// + operation->type = type; + operation->data = data; + operation->blocking = (GPIBool)blocking; + operation->state = GPI_START; + if (type == GPI_CONNECT) { + // Connect is always ID 1. + ////////////////////////// + operation->id = 1; + } else { + operation->id = iconnection->nextOperationID++; + if (iconnection->nextOperationID < 2) + iconnection->nextOperationID = 2; + } + operation->result = GP_NO_ERROR; + operation->callback.callback = callback; + operation->callback.param = param; + + // Add it to the list. + ////////////////////// + operation->pnext = iconnection->operationList; + iconnection->operationList = operation; + + *op = operation; + return GP_NO_ERROR; +} + +void gpiDestroyOperation(GPConnection* connection, GPIOperation* operation) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Search? + ////////// + if (operation->type == GPI_PROFILE_SEARCH) { + GPISearchData* data = (GPISearchData*)operation->data; + + // One less. + //////////// + iconnection->numSearches--; + assert(iconnection->numSearches >= 0); + + // Close the socket. + //////////////////// + shutdown(data->sock, 2); + closesocket(data->sock); + + // freeclear the buffers. + //////////////////// + freeclear(data->outputBuffer.buffer); + freeclear(data->inputBuffer.buffer); + } + + // freeclear the data. + ///////////////// + freeclear(operation->data); + + // freeclear the operation struct. + ///////////////////////////// + freeclear(operation); +} + +void gpiRemoveOperation(GPConnection* connection, GPIOperation* operation) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIOperation* pcurr = iconnection->operationList; + GPIOperation* pprev = NULL; + + // Go through the list of operations. + ///////////////////////////////////// + while (pcurr != NULL) { + // Check for a match. + ///////////////////// + if (pcurr == operation) { + // Update the list. + /////////////////// + if (pprev == NULL) + iconnection->operationList = pcurr->pnext; + else + pprev->pnext = operation->pnext; + + gpiDestroyOperation(connection, operation); + + return; + } + + pprev = pcurr; + pcurr = pcurr->pnext; + } +} + +GPIBool gpiFindOperationByID(const GPConnection* connection, + GPIOperation** operation, int id) { + GPIOperation* op; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Go through the list of operations. + ///////////////////////////////////// + for (op = iconnection->operationList; op != NULL; op = op->pnext) { + // Check the id. + //////////////// + if (op->id == id) { + // Found it. + //////////// + if (operation != NULL) + *operation = op; + return GPITrue; + } + } + + // Didn't find it. + ////////////////// + if (operation != NULL) + *operation = NULL; + return GPIFalse; +} + +GPIBool gpiOperationsAreBlocking(const GPConnection* connection) { + GPIOperation* operation; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Loop through the operations. + /////////////////////////////// + for (operation = iconnection->operationList; operation != NULL; + operation = operation->pnext) { + // Check if it's blocking. + ////////////////////////// + if ((operation->blocking) && (operation->type != GPI_PROFILE_SEARCH)) + return GPITrue; + } + + // Nothing was blocking. + //////////////////////// + return GPIFalse; +} + +GPResult gpiProcessOperation(GPConnection* connection, GPIOperation* operation, + const char* input) { + GPResult result = GP_NO_ERROR; + + // Check the operation type. + //////////////////////////// + switch (operation->type) { + case GPI_CONNECT: + result = gpiProcessConnect(connection, operation, input); + break; + + case GPI_NEW_PROFILE: + result = gpiProcessNewProfile(connection, operation, input); + break; + + case GPI_DELETE_PROFILE: + result = gpiProcessDeleteProfle(connection, operation, input); + break; + + case GPI_GET_INFO: + result = gpiProcessGetInfo(connection, operation, input); + break; + + case GPI_REGISTER_UNIQUENICK: + result = gpiProcessRegisterUniqueNick(connection, operation, input); + break; + + case GPI_REGISTER_CDKEY: + result = gpiProcessRegisterCdKey(connection, operation, input); + break; + + default: + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "gpiProcessOperation was passed an operation with an invalid " + "type (%d)\n", + operation->type); + assert(0); + break; + } + + if (result != GP_NO_ERROR) + operation->result = result; + + return result; +} diff --git a/source/gamespy/GP/gpiOperation.h b/source/gamespy/GP/gpiOperation.h new file mode 100644 index 000000000..84d5cc0ed --- /dev/null +++ b/source/gamespy/GP/gpiOperation.h @@ -0,0 +1,86 @@ +/* +gpiOperation.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +// Operation Types. +/////////////////// +#define GPI_CONNECT 0 +#define GPI_NEW_PROFILE 1 +#define GPI_GET_INFO 2 +#define GPI_PROFILE_SEARCH 3 +#define GPI_REGISTER_UNIQUENICK 4 +#define GPI_DELETE_PROFILE 5 +#define GPI_REGISTER_CDKEY 6 +// Operation States. +//////////////////// +#define GPI_START 0 +//#define GPI_CONNECTING 1 +#define GPI_LOGIN 2 +#define GPI_REQUESTING 3 +#define GPI_WAITING 4 +#define GPI_FINISHING 5 + +// TYPES +/////// +// Operation data. +////////////////// +typedef struct GPIOperation_s { + int type; + void* data; + GPIBool blocking; + GPICallback callback; + int state; + int id; + GPResult result; + struct GPIOperation_s* pnext; +} GPIOperation; + +// Connect operation data. +////////////////////////// +typedef struct { + char serverChallenge[128]; + char userChallenge[33]; + char passwordHash[33]; + char authtoken[GP_AUTHTOKEN_LEN]; + char partnerchallenge[GP_PARTNERCHALLENGE_LEN]; + char cdkey[GP_CDKEY_LEN]; + GPIBool newuser; +} GPIConnectData; + +// FUNCTIONS +/////////// +GPResult gpiAddOperation(GPConnection* connection, int type, void* data, + GPIOperation** op, GPEnum blocking, + GPCallback callback, void* param); + +void gpiRemoveOperation(GPConnection* connection, GPIOperation* operation); + +void gpiDestroyOperation(GPConnection* connection, GPIOperation* operation); + +GPIBool gpiFindOperationByID(const GPConnection* connection, + GPIOperation** operation, int id); + +GPIBool gpiOperationsAreBlocking(const GPConnection* connection); + +GPResult gpiProcessOperation(GPConnection* connection, GPIOperation* operation, + const char* input); + +GPResult gpiFailedOpCallback(GPConnection* connection, + const GPIOperation* operation); diff --git a/source/gamespy/GP/gpiPeer.c b/source/gamespy/GP/gpiPeer.c new file mode 100644 index 000000000..c2d419fc7 --- /dev/null +++ b/source/gamespy/GP/gpiPeer.c @@ -0,0 +1,1202 @@ +/* +gpiPeer.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include +#include + +// FUNCTIONS +/////////// +static GPResult gpiProcessPeerInitiatingConnection(GPConnection* connection, + GPIPeer* peer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + // int state; + char* str = NULL; + // int len; + GPIBool connClosed; + GPIProfile* pProfile; + GPResult result; + GSUdpPeerState aPeerState; + + GS_ASSERT(peer); + if (!peer) + return GP_NETWORK_ERROR; + + GS_ASSERT(peer->state != GPI_PEER_DISCONNECTED && + peer->state != GPI_PEER_NOT_CONNECTED); + if (peer->state == GPI_PEER_DISCONNECTED || + peer->state == GPI_PEER_NOT_CONNECTED) + return GP_NETWORK_ERROR; + // Check the state. + /////////////////// + switch (peer->state) { + case GPI_PEER_GETTING_SIG: + // Do nothing - we're waiting for getinfo to get the sig. + ///////////////////////////////////////////////////////// + break; + + case GPI_PEER_GOT_SIG: { + // Start the connect. + ///////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_State, GSIDebugLevel_Verbose, + "Got the peer signature for profileid: %d\n", peer->profile); + CHECK_RESULT(gpiPeerStartConnect(connection, peer)); + + break; + } + case GPI_PEER_CONNECTING: { + // Check if the connect finished. + ///////////////////////////////// + /* + CHECK_RESULT(gpiCheckSocketConnect(connection, peer->sock, &state)); + if(state == GPI_DISCONNECTED) + { + Error(connection, GP_NETWORK_ERROR, "Error connecting to a peer."); + } + */ + + gsUdpEngineGetPeerState(peer->ip, peer->port, &aPeerState); + + if (aPeerState == GS_UDP_PEER_CONNECTED) { + GPIPeer* pcurr; + GPIBool freePeerSig = GPITrue; + + // Get the profile object. + ////////////////////////// + if (!gpiGetProfile(connection, peer->profile, &pProfile)) + Error(connection, GP_NETWORK_ERROR, "Error connecting to a peer."); + + // Send the auth. + ///////////////// + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\auth\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\pid\\"); + gpiAppendIntToBuffer(connection, &peer->outputBuffer, + iconnection->profileid); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, + iconnection->nick); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\sig\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, + pProfile->peerSig); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\final\\"); + + // Are there any other peers still connecting? + ////////////////////////////////////////////// + for (pcurr = iconnection->peerList; pcurr != NULL; pcurr = pcurr->pnext) + if ((pcurr->profile == peer->profile) && (pcurr != peer)) + if (pcurr->state <= GPI_PEER_CONNECTING) + freePeerSig = GPIFalse; + + // freeclear it? + /////////// + if (freePeerSig) { + freeclear(pProfile->peerSig); + if (gpiCanFreeProfile(pProfile)) + gpiRemoveProfile(connection, pProfile); + } + + // Update the state. + //////////////////// + peer->state = GPI_PEER_WAITING; + } + + break; + } + case GPI_PEER_WAITING: { + // Check for a response. + //////////////////////// + // CHECK_RESULT(gpiRecvToBuffer(connection, peer->sock, &peer->inputBuffer, + // &len, &connClosed, "PR")); + + // Check for a final. + ///////////////////// + if (peer->inputBuffer.buffer) + str = strstr(peer->inputBuffer.buffer, "\\final\\"); + if (str != NULL) { + str[0] = '\0'; + str += 7; + + // Was it rejected? + /////////////////// + if (strncmp(peer->inputBuffer.buffer, "\\anack\\", 7) == 0) { + // Rejected. + //////////// + peer->nackCount++; + + // Is this more than once? + ////////////////////////// + if (peer->nackCount > 1) { + // we shouldn't reach this case unless there is a problem with + // the server when getting a buddy's signature + + // Give up already. + /////////////////// + Error(connection, GP_NETWORK_ERROR, + "Error getting buddy authorization."); + } + + // Try getting the latest sig. + ////////////////////////////// + CHECK_RESULT(gpiPeerGetSig(connection, peer)); + } else if (strncmp(peer->inputBuffer.buffer, "\\aack\\", 6) != 0) { + // Unknown message. + /////////////////// + Error(connection, GP_NETWORK_ERROR, "Error parsing buddy message."); + } + + // The connection has been established. + /////////////////////////////////////// + peer->state = GPI_PEER_CONNECTED; + peer->inputBuffer.len = 0; + } + + break; + } + // code should not reach here. + default: + break; + } + + // Send stuff that needs to be sent. + //////////////////////////////////// + if (peer->outputBuffer.len > 0) { + // result = gpiSendFromBuffer(connection, peer->sock, &peer->outputBuffer, + // &connClosed, GPITrue, "PR"); + result = gpiSendBufferToPeer(connection, peer->ip, peer->port, + &peer->outputBuffer, &connClosed, GPITrue); + if (connClosed || (result != GP_NO_ERROR)) + peer->state = GPI_PEER_DISCONNECTED; + } + + return GP_NO_ERROR; +} + +static GPResult gpiProcessPeerAcceptingConnection(GPConnection* connection, + GPIPeer* peer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GSUdpPeerState aPeerState; + char* str; + // int len; + GPIBool connClosed; + char intValue[16]; + int pid; + char nick[GP_NICK_LEN]; + char sig[33]; + char sigCheck[33]; + char buffer[256]; + + // Check the state. + /////////////////// + GS_ASSERT(peer->state == GPI_PEER_WAITING); + if (peer->state != GPI_PEER_WAITING) + return GP_NETWORK_ERROR; + + // Read any pending info. + ///////////////////////// + // CHECK_RESULT(gpiRecvToBuffer(connection, peer->sock, &peer->inputBuffer, + // &len, &connClosed, "PR")); + gsUdpEngineGetPeerState(peer->ip, peer->port, &aPeerState); + + // Check for a closed connection. + ///////////////////////////////// + if (aPeerState == GS_UDP_PEER_CLOSED) { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Check for a final. + ///////////////////// + str = strstr(peer->inputBuffer.buffer, "\\final\\"); + if (str != NULL) { + str[0] = '\0'; + str += 7; + + // Is it an auth? + ///////////////// + if (strncmp(peer->inputBuffer.buffer, "\\auth\\", 6) == 0) { + // Get the pid. + /////////////// + if (!gpiValueForKey(peer->inputBuffer.buffer, "\\pid\\", intValue, + sizeof(intValue))) { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + pid = atoi(intValue); + + // Get the nick. + //////////////// + if (!gpiValueForKey(peer->inputBuffer.buffer, "\\nick\\", nick, + sizeof(nick))) { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Get the sig. + /////////////// + if (!gpiValueForKey(peer->inputBuffer.buffer, "\\sig\\", sig, + sizeof(sig))) { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Compute what the sig should be. + ////////////////////////////////// + sprintf(buffer, "%s%d%d", iconnection->password, iconnection->profileid, + pid); + MD5Digest((unsigned char*)buffer, strlen(buffer), sigCheck); + + // Check the sig. + ///////////////// + if (strcmp(sig, sigCheck) != 0) { + // Bad sig. + /////////// + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\anack\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\final\\"); + + gpiSendBufferToPeer(connection, peer->ip, peer->port, + &peer->outputBuffer, &connClosed, GPITrue); + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Send an ack. + /////////////// + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\aack\\"); + gpiAppendStringToBuffer(connection, &peer->outputBuffer, "\\final\\"); + + peer->state = GPI_PEER_CONNECTED; + peer->profile = (GPProfile)pid; + } else { + // Unrecognized command. + //////////////////////// + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Update the buffer length. + //////////////////////////// + peer->inputBuffer.len = 0; + } + + return GP_NO_ERROR; +} + +GPResult gpiPeerSendMessages(GPConnection* connection, GPIPeer* peer) { + GPIBool connClosed; + GPIMessage* message; + GPResult result; + + GS_ASSERT(peer); + if (!peer) + return GP_NETWORK_ERROR; + // Only send messages if there's nothing waiting in the output buffer. + ////////////////////////////////////////////////////////////////////// + if (peer->outputBuffer.len) + return GP_NO_ERROR; + + // Send outgoing messages. + ////////////////////////// + while (ArrayLength(peer->messages)) { + // Get the first message. + ///////////////////////// + message = (GPIMessage*)ArrayNth(peer->messages, 0); + + // Send as much as possible. + //////////////////////////// + // result = gpiSendFromBuffer(connection, peer->sock, &message->buffer, + // &connClosed, GPIFalse, "PR"); + result = gpiSendBufferToPeer(connection, peer->ip, peer->port, + &message->buffer, &connClosed, GPIFalse); + if (connClosed || (result != GP_NO_ERROR)) { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + + // Did we not send it all? + ////////////////////////// + if (message->buffer.pos != message->buffer.len) + break; + + // Remove the message. + ////////////////////// + ArrayDeleteAt(peer->messages, 0); + } + + return GP_NO_ERROR; +} + +static GPResult gpiProcessPeerConnected(GPConnection* connection, + GPIPeer* peer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + // int len; + GSUdpPeerState aPeerState; + GPIBool connClosed; + GPICallback callback; + char* buffer; + int type; + int messageLen; + GPResult result; + + GS_ASSERT(peer); + if (!peer) + return GP_NETWORK_ERROR; + // Send stuff. + ////////////// + if (peer->outputBuffer.len) { + // result = gpiSendFromBuffer(connection, peer->sock, &peer->outputBuffer, + // &connClosed, GPITrue, "PR"); + result = gpiSendBufferToPeer(connection, peer->ip, peer->port, + &peer->outputBuffer, &connClosed, GPITrue); + if (connClosed || (result != GP_NO_ERROR)) { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + } + + // Send outgoing messages. + ////////////////////////// + if (!peer->outputBuffer.len) { + CHECK_RESULT(gpiPeerSendMessages(connection, peer)); + if (peer->state == GPI_PEER_DISCONNECTED) + return GP_NO_ERROR; + } + + // Read messages. + ///////////////// + /* + result = gpiRecvToBuffer(connection, peer->sock, &peer->inputBuffer, &len, + &connClosed, "PR"); if(result != GP_NO_ERROR) + { + peer->state = GPI_PEER_DISCONNECTED; + return GP_NO_ERROR; + } + */ + if (peer->inputBuffer.len > 0) { + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + } + + // Grab the message header. + /////////////////////////// + do { + // Read a message. + ////////////////// + CHECK_RESULT(gpiReadMessageFromBuffer(connection, &peer->inputBuffer, + &buffer, &type, &messageLen)); + if (buffer != NULL) { + // Got a message! + ///////////////// + switch (type) { + case GPI_BM_MESSAGE: + callback = iconnection->callbacks[GPI_RECV_BUDDY_MESSAGE]; + if (callback.callback != NULL) { + GPRecvBuddyMessageArg* arg; + + arg = + (GPRecvBuddyMessageArg*)gsimalloc(sizeof(GPRecvBuddyMessageArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = peer->profile; + arg->message = goastrdup(buffer); + arg->date = (unsigned int)time(NULL); + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_MESSAGE)); + } + break; + + case GPI_BM_UTM: + callback = iconnection->callbacks[GPI_RECV_BUDDY_UTM]; + if (callback.callback != NULL) { + GPRecvBuddyUTMArg* arg; + + arg = (GPRecvBuddyUTMArg*)gsimalloc(sizeof(GPRecvBuddyUTMArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profile = peer->profile; + arg->message = goastrdup(buffer); + arg->date = (unsigned int)time(NULL); + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, NULL, GPI_ADD_MESSAGE)); + } + break; + + case GPI_BM_PING: + // Send back a pong. + //////////////////// + gpiSendBuddyMessage(connection, peer->profile, GPI_BM_PONG, "1", 0, + NULL); + + break; + +#ifndef NOFILE + case GPI_BM_PONG: + // Lets the transfers handle this. + ////////////////////////////////// + gpiTransfersHandlePong(connection, peer->profile, peer); + break; +#endif + case GPI_BM_KEYS_REQUEST: + CHECK_RESULT(gpiBuddyHandleKeyRequest(connection, peer)); + break; + case GPI_BM_KEYS_REPLY: + CHECK_RESULT(gpiBuddyHandleKeyReply(connection, peer, buffer)); + // Let the keys request reply handler take care of this. + //////////////////////////////////////////////////////// + break; + case GPI_BM_FILE_SEND_REQUEST: + case GPI_BM_FILE_SEND_REPLY: + case GPI_BM_FILE_BEGIN: + case GPI_BM_FILE_END: + case GPI_BM_FILE_DATA: + case GPI_BM_FILE_SKIP: + case GPI_BM_FILE_TRANSFER_THROTTLE: + case GPI_BM_FILE_TRANSFER_CANCEL: + case GPI_BM_FILE_TRANSFER_KEEPALIVE: + // Handle a transfer protocol message. + ////////////////////////////////////// + gpiHandleTransferMessage(connection, peer, type, + peer->inputBuffer.buffer, buffer, messageLen); + + break; + + default: + break; + } + + // Remove it from the buffer. + ///////////////////////////// + gpiClipBufferToPosition(connection, &peer->inputBuffer); + } + } while (buffer); + + gsUdpEngineGetPeerState(peer->ip, peer->port, &aPeerState); + // if(connClosed) + if (aPeerState == GS_UDP_PEER_CLOSED) + peer->state = GPI_PEER_DISCONNECTED; + + return GP_NO_ERROR; +} + +// Used to check for any timed out peer operations +// assumes peer is not NULL +// makes no assumption of the operation queue +void gpiCheckTimedOutPeerOperations(GPConnection* connection, GPIPeer* peer) { + GPIPeerOp* anIterator = peer->peerOpQueue.first; + GS_ASSERT(peer); + if (!peer) + return; + + while (anIterator && anIterator != peer->peerOpQueue.last) { + if (anIterator->state != GPI_PEER_OP_STATE_FINISHED && + current_time() > anIterator->timeout && anIterator->callback) { + // currently only one type of peer operation exists + // when it's found, we need to provide the application with + // a result of no data + if (anIterator->type == GPI_BM_KEYS_REQUEST) { + GPICallback callback; + GPGetBuddyStatusInfoKeysArg* arg = + (GPGetBuddyStatusInfoKeysArg*)gsimalloc( + sizeof(GPGetBuddyStatusInfoKeysArg)); + callback.callback = anIterator->callback; + callback.param = anIterator->userData; + arg->keys = NULL; + arg->numKeys = 0; + arg->values = NULL; + arg->profile = peer->profile; + gpiAddCallback(connection, callback, arg, NULL, 0); + } + // The peer operation is removed regardless of type + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Peer operation timed out"); + gpiPeerRemoveOp(peer, anIterator); + } + anIterator = anIterator->next; + } +} + +static GPResult gpiProcessPeer(GPConnection* connection, GPIPeer* peer) { + GPResult result = GP_NO_ERROR; + + // This state should never get out of initialization. + ///////////////////////////////////////////////////// + GS_ASSERT(peer->state != GPI_PEER_NOT_CONNECTED); + if (peer->state == GPI_PEER_NOT_CONNECTED) + return GP_NETWORK_ERROR; + + // If we're not connected yet. + ////////////////////////////// + if (peer->state != GPI_PEER_CONNECTED) { + if (peer->initiated) + result = gpiProcessPeerInitiatingConnection(connection, peer); + else + result = gpiProcessPeerAcceptingConnection(connection, peer); + } + + // If we're connected. + ////////////////////// + if ((result == GP_NO_ERROR) && (peer->state == GPI_PEER_CONNECTED)) { + result = gpiProcessPeerConnected(connection, peer); + gpiCheckTimedOutPeerOperations(connection, peer); + } + + return result; +} + +void gpiDestroyPeer(GPConnection* connection, GPIPeer* peer) { +#ifndef NOFILE + // Cleanup any transfers that use this peer. + //////////////////////////////////////////// + gpiTransferPeerDestroyed(connection, peer); +#endif + + // shutdown(peer->sock, 2); + // closesocket(peer->sock); + freeclear(peer->inputBuffer.buffer); + freeclear(peer->outputBuffer.buffer); + if (peer->messages) { + ArrayFree(peer->messages); + peer->messages = NULL; + } + freeclear(peer); + + GSI_UNUSED(connection); +} + +void gpiRemovePeer(GPConnection* connection, GPIPeer* peer) { + GPIPeer* pprev; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIMessage* message; + + GS_ASSERT(peer != NULL); + if (peer == NULL) + return; + + GS_ASSERT(iconnection->peerList); + if (iconnection->peerList == NULL) + return; + // Check if this is the first peer. + /////////////////////////////////// + if (iconnection->peerList == peer) { + iconnection->peerList = peer->pnext; + } else { + // Find the previous peer. + ////////////////////////// + for (pprev = iconnection->peerList; pprev->pnext != peer; + pprev = pprev->pnext) { + if (pprev->pnext == NULL) { + // Can't find this peer in the list! + //////////////////////////////////// + assert(0); + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "Tried to remove peer not in list."); + return; + } + } + pprev->pnext = peer->pnext; + } + + // Check for pending messages. + ////////////////////////////// + while (ArrayLength(peer->messages)) { + // Get the next message. + //////////////////////// + message = (GPIMessage*)ArrayNth(peer->messages, 0); + + // Don't forward protocol messages. + /////////////////////////////////// + if (message->type < 100) + gpiSendServerBuddyMessage(connection, peer->profile, message->type, + message->buffer.buffer + message->start); + + // Remove the message. + ////////////////////// + ArrayDeleteAt(peer->messages, 0); + } + + gpiDestroyPeer(connection, peer); +} + +GPResult gpiProcessPeers(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIPeer* nextPeer; + GPIPeer* peer; + // SOCKET incoming; + GPResult result; + + /* + // Check for incoming peer connections. + /////////////////////////////////////// + if(iconnection->peerSocket != INVALID_SOCKET) + { + // Have to manually check if accept is possible since + // PS2 Insock only supports blocking sockets. + if (CanReceiveOnSocket(iconnection->peerSocket)) + { + incoming = accept(iconnection->peerSocket, NULL, NULL); + if(incoming != INVALID_SOCKET) + { + // This is a new peer. + ////////////////////// + peer = gpiAddPeer(connection, -1, GPIFalse); + if(peer) + { + peer->state = GPI_PEER_WAITING; + peer->sock = incoming; + SetSockBlocking(incoming, 0); + gpiSetPeerSocketSizes(peer->sock); + } + else + { + closesocket(incoming); + } + } + } + } + */ + gsUdpEngineThink(); + // Got through the list of peers. + ///////////////////////////////// + for (peer = iconnection->peerList; peer != NULL; peer = nextPeer) { + // Store the next peer. + /////////////////////// + nextPeer = peer->pnext; + if (peer->state == GPI_PEER_DISCONNECTED) { + // Remove it. + ///////////// + // gsDebug + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Peer disconnected, pid: %d", peer->profile); + gpiRemovePeer(connection, peer); + } else { + // Process the peer. + //////////////////// + result = gpiProcessPeer(connection, peer); + + // Check for a disconnection or a timeout. + ////////////////////////////////////////// + if ((peer->state == GPI_PEER_DISCONNECTED) || (result != GP_NO_ERROR) || + (time(NULL) > peer->timeout)) { + // Remove it. + ///////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Peer disconnected, pid: %d", peer->profile); + gpiRemovePeer(connection, peer); + } + } + } + + return GP_NO_ERROR; +} + +// NOTE: use this function when in a gp function +GPIPeer* gpiGetPeerByProfile(const GPConnection* connection, int profileid) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIPeer* pcurr; + + // Go through the list of peers. + //////////////////////////////// + for (pcurr = iconnection->peerList; pcurr != NULL; pcurr = pcurr->pnext) { + // Check for a match. + ///////////////////// + if (pcurr->profile == profileid) { + // Got it. + ////////// + return pcurr; + } + } + + return NULL; +} + +// NOTE: use this function only when in a UDP layer callback +GPIPeer* gpiGetPeerByAddr(const GPConnection* connection, unsigned int ip, + unsigned short port) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIPeer* pcurr; + + GS_ASSERT(ip); + GS_ASSERT(port); + if (!ip && !port) + return NULL; + // Go through the list of peers. + //////////////////////////////// + for (pcurr = iconnection->peerList; pcurr != NULL; pcurr = pcurr->pnext) { + // Check for a match. + ///////////////////// + if (pcurr->ip == ip && pcurr->port == port) { + // Got it. + ////////// + return pcurr; + } + } + + return NULL; +} + +gsi_bool gpiIsPeerConnected(GPIPeer* peer) { + GS_ASSERT(peer); + if (!peer) + return gsi_false; + + if (peer && peer->state != GPI_PEER_CONNECTED) + return gsi_false; + + return gsi_true; +} + +static void gpiFreeMessage(void* elem) { + GPIMessage* message = (GPIMessage*)elem; + + freeclear(message->buffer.buffer); +} + +GPIPeer* gpiAddPeer(GPConnection* connection, int profileid, GPIBool initiate) { + GPIPeer* peer; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Create a new peer. + ///////////////////// + peer = (GPIPeer*)gsimalloc(sizeof(GPIPeer)); + if (peer == NULL) + return NULL; + memset(peer, 0, sizeof(GPIPeer)); + peer->state = GPI_PEER_NOT_CONNECTED; + peer->initiated = initiate; + // peer->sock = INVALID_SOCKET; + peer->profile = profileid; + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + peer->pnext = iconnection->peerList; + peer->messages = ArrayNew(sizeof(GPIMessage), 0, gpiFreeMessage); + iconnection->peerList = peer; + peer->peerOpQueue.first = NULL; + peer->peerOpQueue.last = NULL; + peer->peerOpQueue.opList = NULL; + return peer; +} + +GPResult gpiPeerGetSig(GPConnection* connection, GPIPeer* peer) { + GPIOperation* operation; + + // Start a get info operation to get the sig. + ///////////////////////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_GET_INFO, NULL, &operation, + GP_NON_BLOCKING, NULL, NULL)); + + // Send the get info. + ///////////////////// + CHECK_RESULT(gpiSendGetInfo(connection, peer->profile, operation->id)); + + // Set the state. + ///////////////// + peer->state = GPI_PEER_GETTING_SIG; + + return GP_NO_ERROR; +} + +GPResult gpiPeerStartConnect(GPConnection* connection, GPIPeer* peer) { + // int rcode; + // struct sockaddr_in address; + GPIProfile* profile; + GPIConnection* iconnection = (GPIConnection*)*connection; + GSUdpErrorCode anError; + + // Get the profile object. + ////////////////////////// + if (!gpiGetProfile(connection, peer->profile, &profile)) + Error(connection, GP_NETWORK_ERROR, "Error connecting to a peer."); + + /* + // Create the socket. + ///////////////////// + peer->sock = socket(AF_INET, SOCK_STREAM, 0); + if(peer->sock == INVALID_SOCKET) + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an +error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(peer->sock, 0); + if(rcode == 0) + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, "There was an +error making a socket non-blocking."); + + // Bind the socket. + /////////////////// + +// BD: PS2 Insock has bug with binding to port 0 +// No sockets after the first will be able to bind + + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(peer->sock, (struct sockaddr *)&address, sizeof(struct +sockaddr_in)); if (gsiSocketIsError(rcode)) CallbackError(connection, +GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket."); + + // Set the socket sizes. + //////////////////////// + gpiSetPeerSocketSizes(peer->sock); + + // Connect the socket. + ////////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = profile->buddyStatus->ip; + address.sin_port = (gsi_u16)profile->buddyStatus->port; + rcode = connect(peer->sock, (struct sockaddr *)&address, sizeof(struct +sockaddr_in)); if (gsiSocketIsError(rcode)) + { + int error = GOAGetLastError(peer->sock); + if((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && (error != +WSAETIMEDOUT) ) + { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, +"There was an error connecting a socket."); + } + } + */ + + if (profile->buddyStatusInfo) { + GSUdpPeerState aPeerState; + gsUdpEngineGetPeerState(profile->buddyStatusInfo->buddyIp, + profile->buddyStatusInfo->buddyPort, &aPeerState); + if (aPeerState != GS_UDP_PEER_CONNECTED || + aPeerState != GS_UDP_PEER_CONNECTING) { + anError = + gsUdpEngineStartTalkingToPeer(profile->buddyStatusInfo->buddyIp, + profile->buddyStatusInfo->buddyPort, + iconnection->mHeader, GPI_PEER_TIMEOUT); + if (anError != GS_UDP_ADDRESS_ALREADY_IN_USE) + CallbackError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error starting communication with a peer."); + } + peer->ip = profile->buddyStatusInfo->buddyIp; + peer->port = profile->buddyStatusInfo->buddyPort; + } + // We're waiting for the connect to complete. + ///////////////////////////////////////////// + peer->state = GPI_PEER_CONNECTING; + + return GP_NO_ERROR; +} + +GPResult gpiPeerAddMessage(GPConnection* connection, GPIPeer* peer, int type, + const char* message) { + GPIMessage gpiMessage; + int len; + + GS_ASSERT(peer != NULL); + GS_ASSERT(message != NULL); + + if (peer == NULL) + return GP_NETWORK_ERROR; + if (message == NULL) + return GP_NETWORK_ERROR; + + // Get the length. + ////////////////// + len = (int)strlen(message); + + // Clear the message. + ///////////////////// + memset(&gpiMessage, 0, sizeof(GPIMessage)); + + // Copy the type. + ///////////////// + gpiMessage.type = type; + + // Copy the header to the buffer. + ///////////////////////////////// + CHECK_RESULT( + gpiAppendStringToBuffer(connection, &gpiMessage.buffer, "\\m\\")); + CHECK_RESULT(gpiAppendIntToBuffer(connection, &gpiMessage.buffer, type)); + CHECK_RESULT( + gpiAppendStringToBuffer(connection, &gpiMessage.buffer, "\\len\\")); + CHECK_RESULT(gpiAppendIntToBuffer(connection, &gpiMessage.buffer, len)); + CHECK_RESULT( + gpiAppendStringToBuffer(connection, &gpiMessage.buffer, "\\msg\\\n")); + + // Copy the message to the buffer. + ////////////////////////////////// + gpiMessage.start = gpiMessage.buffer.len; + CHECK_RESULT( + gpiAppendStringToBufferLen(connection, &gpiMessage.buffer, message, len)); + CHECK_RESULT(gpiAppendCharToBuffer(connection, &gpiMessage.buffer, '\0')); + + // Add it to the list. + ////////////////////// + ArrayAppend(peer->messages, &gpiMessage); + + // Reset the timeout. + ///////////////////// + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + + return GP_NO_ERROR; +} + +GPResult gpiPeerStartTransferMessage(GPConnection* connection, GPIPeer* peer, + int type, + const struct GPITransferID_s* transferID) { + char buffer[64]; + GPITransferID tid; + tid.count = transferID->count; + tid.profileid = transferID->profileid; + tid.time = transferID->time; + + GS_ASSERT(transferID); + if (!transferID) + return GP_NETWORK_ERROR; + // Start the message. + ///////////////////// + sprintf(buffer, "\\m\\%d\\xfer\\%d %u %u", type, tid.profileid, tid.count, + tid.time); + + return gpiSendOrBufferString(connection, peer, buffer); +} + +GPResult gpiPeerFinishTransferMessage(GPConnection* connection, GPIPeer* peer, + const char* message, int len) { + char buffer[32]; + GS_ASSERT(peer != NULL); + if (!peer) + return GP_NETWORK_ERROR; + + // Check the message. + ///////////////////// + if (!message) + message = ""; + + if (len == -1) + len = (int)strlen(message); + + // Set the len and the message. + /////////////////////////////// + sprintf(buffer, "\\len\\%d\\msg\\\n", len); + CHECK_RESULT(gpiSendOrBufferString(connection, peer, buffer)); + + // Copy the message to the buffer. + ////////////////////////////////// + CHECK_RESULT(gpiSendOrBufferStringLenToPeer(connection, peer, message, len)); + CHECK_RESULT(gpiSendOrBufferChar(connection, peer, '\0')); + + // Reset the timeout. + ///////////////////// + peer->timeout = (time(NULL) + GPI_PEER_TIMEOUT); + + return GP_NO_ERROR; +} + +void gpiPeerLeftCallback(unsigned int ip, unsigned short port, + GSUdpCloseReason reason, void* userData) { + + GPConnection* connection = (GPConnection*)userData; + GPIPeer* aPeer; + IN_ADDR anAddr; + anAddr.s_addr = ip; + aPeer = gpiGetPeerByAddr(connection, ip, port); + // gpiRemovePeer(connection, aPeer); + if (aPeer) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Peer left: addr: %s:%d, profile: %d\n", inet_ntoa(anAddr), + port, aPeer->profile); + aPeer->state = GPI_PEER_DISCONNECTED; + } + + GSI_UNUSED(anAddr); + GSI_UNUSED(reason); +} + +void gpiPeerMessageCallback(unsigned int ip, unsigned short port, + unsigned char* message, unsigned int messageLength, + gsi_bool reliable, void* userData) { + GPConnection* connection = (GPConnection*)userData; + GPIPeer* aPeer; + unsigned char* buff; + int writePos; + int size; + IN_ADDR anAddr; + anAddr.s_addr = ip; + aPeer = gpiGetPeerByAddr(connection, ip, port); + if (!aPeer) { + aPeer = gpiAddPeer(connection, -1, GPIFalse); + if (aPeer) { + aPeer->state = GPI_PEER_WAITING; + aPeer->ip = ip; + aPeer->port = port; + } else { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_HotError, + "gpiPeerMessageCallback: out of memory when allocating " + "peer, addr: %s:%d", + inet_ntoa(anAddr), port); + return; + } + } + + buff = (unsigned char*)aPeer->inputBuffer.buffer; + writePos = aPeer->inputBuffer.len; + size = aPeer->inputBuffer.size; + + // Check if the buffer needs to be resized. + /////////////////////////////////////////// + if ((int)messageLength > (size - writePos)) { + unsigned char* reallocedBuff; + size = (writePos + max(GPI_READ_SIZE, (int)messageLength)); + reallocedBuff = (unsigned char*)gsirealloc(buff, (unsigned int)size + 1); + if (reallocedBuff == NULL) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Memory, GSIDebugLevel_HotError, + "gpiPeerMessageCallback: out of memory when reallocating " + "buffer, addr: %s:%d", + inet_ntoa(anAddr), port); + gsifree(buff); + gpiSetErrorString(connection, "Out of memory."); + gpiCallErrorCallback(connection, GP_MEMORY_ERROR, GP_NON_FATAL); + return; + } else + buff = reallocedBuff; + } + + memcpy(&buff[writePos], message, messageLength); + + aPeer->inputBuffer.buffer = (char*)buff; + aPeer->inputBuffer.len += messageLength; + aPeer->inputBuffer.size = size; + buff[aPeer->inputBuffer.len] = '\0'; + GSI_UNUSED(reliable); + GSI_UNUSED(anAddr); +} + +void gpiPeerAcceptedCallback(unsigned int ip, unsigned short port, + GSUdpErrorCode error, gsi_bool rejected, + void* userData) { + GPConnection* connection = (GPConnection*)userData; + GPIPeer* aPeer; + IN_ADDR anAddr; + anAddr.s_addr = ip; + + aPeer = gpiGetPeerByAddr(connection, ip, port); + if (!aPeer) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "Peer does not exist: ip-port: %s:%d\n", inet_ntoa(anAddr), + port); + } else { + if (rejected) { + aPeer->state = GPI_PEER_DISCONNECTED; + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Peer Connection rejected: ip-port: %s:%d\n", + inet_ntoa(anAddr), port); + return; + } + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Peer Connection accepted: ip-port: %s:%d\n", inet_ntoa(anAddr), + port); + + GSI_UNUSED(userData); + GSI_UNUSED(rejected); + GSI_UNUSED(error); + GSI_UNUSED(anAddr); +} +void gpiPeerPingReplyCallback(unsigned int ip, unsigned short port, + unsigned int latency, void* userData) { + GSI_UNUSED(userData); + GSI_UNUSED(latency); + GSI_UNUSED(port); + GSI_UNUSED(ip); +} + +// gpiPeerAddOp notes: +// Assumes non-null inputs! +// The queue should be empty when the first element is added. +// Any new element added will be added to the end of the queue. +void gpiPeerAddOp(GPIPeer* peer, GPIPeerOp* operation) { + GS_ASSERT(peer); + GS_ASSERT(operation); + + if (!peer || !operation) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Peer operation not added"); + return; + } + // Three cases can occur: + // The list is empty - set all pointers to the new node + // The list has only one element - set the first element's next to the new + // and set the last element to the new + // The list has more than one element - add the new element to the end of + // the queue + if (peer->peerOpQueue.opList == NULL) { + peer->peerOpQueue.first = operation; + peer->peerOpQueue.last = operation; + peer->peerOpQueue.opList = operation; + } else if (peer->peerOpQueue.first == peer->peerOpQueue.last) { + peer->peerOpQueue.first->next = operation; + peer->peerOpQueue.last = operation; + } else { + peer->peerOpQueue.last->next = operation; + peer->peerOpQueue.last = operation; + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Peer Operation Added"); +} + +// gpiPeerRemoveOp: +// Assumes the list is NOT NULL otherwise it returns. +// Assumes the operation being passed in is on the queue. +// Assumes non-null inputs! +// Completed or Timed out Operations are deleted from queue by finding +// the operation passed in. Removal of operations don't necessarily +// happen in order. +void gpiPeerRemoveOp(GPIPeer* peer, GPIPeerOp* operation) { + GS_ASSERT(peer); + GS_ASSERT(operation); + if (!peer || !operation) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Peer operation not removed"); + return; + } + + GS_ASSERT(peer->peerOpQueue.opList != NULL); + if (peer->peerOpQueue.opList == NULL) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Peer operation not removed"); + return; + } + + if (peer->peerOpQueue.first == peer->peerOpQueue.last && + peer->peerOpQueue.first == operation) { + peer->peerOpQueue.opList = peer->peerOpQueue.first = + peer->peerOpQueue.last = operation->next; + } else if (peer->peerOpQueue.first == operation) { + peer->peerOpQueue.first = peer->peerOpQueue.first->next; + peer->peerOpQueue.opList = peer->peerOpQueue.first; + } else { + GPIPeerOp* aPrevOp = NULL; + for (aPrevOp = peer->peerOpQueue.first; aPrevOp->next != operation; + aPrevOp = aPrevOp->next) { + if (aPrevOp->next == NULL) { + // Can't find this peer in the list! + //////////////////////////////////// + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "Tried to remove peer operation not in list."); + return; + } + } + aPrevOp->next = operation->next; + } + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Peer operation removed"); + freeclear(operation); +} diff --git a/source/gamespy/GP/gpiPeer.h b/source/gamespy/GP/gpiPeer.h new file mode 100644 index 000000000..bb3ae9716 --- /dev/null +++ b/source/gamespy/GP/gpiPeer.h @@ -0,0 +1,139 @@ +/* +gpiPeer.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +// Peer states. +/////////////// +#define GPI_PEER_NOT_CONNECTED 100 +#define GPI_PEER_GETTING_SIG 101 +#define GPI_PEER_GOT_SIG 102 +#define GPI_PEER_CONNECTING 103 +#define GPI_PEER_WAITING 104 +#define GPI_PEER_CONNECTED 105 +#define GPI_PEER_DISCONNECTED 106 + +// Timeout for a peer connection, in milliseconds. +///////////////////////////////////////////// +#define GPI_PEER_TIMEOUT (10 * 1000) + +// Timeout for a peer operation, in milliseconds +//////////////////////////////////////////// +#define GPI_PEER_OP_TIMEOUT 60000 + +typedef enum { + GPI_PEER_OP_STATE_NONE, + GPI_PEER_OP_STATE_REQUESTED, + GPI_PEER_OP_STATE_FINISHED +} GPIPeerOpState; + +typedef struct GPITransferID_s* GPITransferID_st; + +// TYPES +/////// +// A peer message. +////////////////// +typedef struct GPIMessage { + GPIBuffer buffer; + int type; + int start; +} GPIMessage; + +typedef struct _GPIPeerOp { + GPIPeerOpState state; + void* userData; + GPCallback callback; + struct _GPIPeerOp* next; + int type; + gsi_time timeout; +} GPIPeerOp; + +typedef struct _GPIPeerOpQueue { + GPIPeerOp* opList; + GPIPeerOp* first; + GPIPeerOp* last; +} GPIPeerOpQueue; + +// A peer connection. +///////////////////// +typedef struct GPIPeer_s { + int state; + GPIBool initiated; + // SOCKET sock; + unsigned int ip; + unsigned short port; + GPProfile profile; + time_t timeout; + int nackCount; + GPIBuffer inputBuffer; + GPIBuffer outputBuffer; + DArray messages; + GPIPeerOpQueue peerOpQueue; + struct GPIPeer_s* pnext; +} GPIPeer; + +// FUNCTIONS +/////////// +GPResult gpiProcessPeers(GPConnection* connection); + +GPResult gpiPeerGetSig(GPConnection* connection, GPIPeer* peer); + +GPResult gpiPeerStartConnect(GPConnection* connection, GPIPeer* peer); + +// NOTE: use this function when in a gp function +GPIPeer* gpiGetPeerByProfile(const GPConnection* connection, int profileid); + +// NOTE: use this function only when in a UDP layer callback +GPIPeer* gpiGetPeerByAddr(const GPConnection* connection, unsigned int ip, + unsigned short port); + +gsi_bool gpiIsPeerConnected(GPIPeer* peer); + +GPIPeer* gpiAddPeer(GPConnection* connection, int profileid, GPIBool initiate); + +void gpiDestroyPeer(GPConnection* connection, GPIPeer* peer); + +void gpiRemovePeer(GPConnection* connection, GPIPeer* peer); + +GPResult gpiPeerAddMessage(GPConnection* connection, GPIPeer* peer, int type, + const char* message); + +GPResult gpiPeerStartTransferMessage(GPConnection* connection, GPIPeer* peer, + int type, + const struct GPITransferID_s* transferID); + +GPResult gpiPeerFinishTransferMessage(GPConnection* connection, GPIPeer* peer, + const char* message, int len); + +GPResult gpiPeerSendMessages(GPConnection* connection, GPIPeer* peer); + +void gpiPeerLeftCallback(unsigned int ip, unsigned short port, + GSUdpCloseReason reason, void* userData); +void gpiPeerMessageCallback(unsigned int ip, unsigned short port, + unsigned char* message, unsigned int messageLength, + gsi_bool reliable, void* userData); +void gpiPeerAcceptedCallback(unsigned int ip, unsigned short port, + GSUdpErrorCode error, gsi_bool rejected, + void* userData); +void gpiPeerPingReplyCallback(unsigned int ip, unsigned short port, + unsigned int latency, void* userData); + +void gpiPeerAddOp(GPIPeer* peer, GPIPeerOp* operation); +void gpiPeerRemoveOp(GPIPeer* peer, GPIPeerOp* operation); +void gpiCheckTimedOutPeerOperations(GPConnection* connection, GPIPeer* peer); diff --git a/source/gamespy/GP/gpiProfile.c b/source/gamespy/GP/gpiProfile.c new file mode 100644 index 000000000..6974933c8 --- /dev/null +++ b/source/gamespy/GP/gpiProfile.c @@ -0,0 +1,472 @@ +/* +gpiProfile.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +#define GPI_PROFILE_GROW_SIZE 16 +#define GPI_PROFILE_CACHE_VERSION 2 + +// GLOBALS +////////// +char GPIInfoCacheFilename[FILENAME_MAX + 1] = "gp.info"; + +// FUNCTIONS +/////////// +static int gpiProfilesTableHash(const void* arg, int numBuckets) { + const GPIProfile* profile = (const GPIProfile*)arg; + return (profile->profileId % numBuckets); +} + +static int gpiProfilesTableCompare(const void* arg1, const void* arg2) { + const GPIProfile* profile1 = (const GPIProfile*)arg1; + const GPIProfile* profile2 = (const GPIProfile*)arg2; + return (profile1->profileId - profile2->profileId); +} + +static void gpiProfilesTableFree(void* arg) { + GPIProfile* profile = (GPIProfile*)arg; + if (profile->buddyStatus) { + freeclear(profile->buddyStatus->statusString); + freeclear(profile->buddyStatus->locationString); + freeclear(profile->buddyStatus); + } + if (profile->buddyStatusInfo) { + freeclear(profile->buddyStatusInfo->richStatus); + freeclear(profile->buddyStatusInfo->gameType); + freeclear(profile->buddyStatusInfo->gameVariant); + freeclear(profile->buddyStatusInfo->gameMapName); + if (profile->buddyStatusInfo->extendedInfoKeys) { + ArrayFree(profile->buddyStatusInfo->extendedInfoKeys); + profile->buddyStatusInfo->extendedInfoKeys = NULL; + } + freeclear(profile->buddyStatusInfo); + } + gpiFreeInfoCache(profile); + freeclear(profile->authSig); + freeclear(profile->peerSig); +} + +GPIBool gpiInitProfiles(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + iconnection->profileList.numBuddies = 0; + iconnection->profileList.num = 0; + iconnection->profileList.profileTable = + TableNew(sizeof(GPIProfile), 32, gpiProfilesTableHash, + gpiProfilesTableCompare, gpiProfilesTableFree); + if (!iconnection->profileList.profileTable) + return GPIFalse; + + return GPITrue; +} + +GPResult gpiProcessNewProfile(GPConnection* connection, GPIOperation* operation, + const char* input) { + char buffer[16]; + int pid; + GPICallback callback; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \npr\. + //////////////////////// + if (strncmp(input, "\\npr\\", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Get the profile id. + ////////////////////// + if (!gpiValueForKey(input, "\\profileid\\", buffer, sizeof(buffer))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + pid = atoi(buffer); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if (callback.callback != NULL) { + GPNewProfileResponseArg* arg; + arg = (GPNewProfileResponseArg*)gsimalloc(sizeof(GPNewProfileResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = (GPProfile)pid; + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // Remove the operation. + //////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + +GPIProfile* gpiProfileListAdd(GPConnection* connection, int id) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIProfileList* profileList = &iconnection->profileList; + GPIProfile profile; + GPIProfile* pProfile; + + assert(id > 0); + + // Check the parameters. 2000.02.14.JED was not checked in release build. + ///////////////////////////////////////////////////////////////////////// + if (id <= 0) + return NULL; + + // Check if this id is already in the list. + /////////////////////////////////////////// + if (gpiGetProfile(connection, (GPProfile)id, &pProfile)) + return pProfile; + + // Setup the new profile. + ///////////////////////// + memset(&profile, 0, sizeof(GPIProfile)); + profile.profileId = id; + profile.userId = 0; + profile.cache = NULL; + profile.authSig = NULL; + profile.peerSig = NULL; + profile.requestCount = 0; + + // Add it to the table. + /////////////////////// + TableEnter(profileList->profileTable, &profile); + + // One new one. + /////////////// + profileList->num++; + + // Get a pointer to the profile. + //////////////////////////////// + if (gpiGetProfile(connection, (GPProfile)id, &pProfile)) + return pProfile; + + // It wasn't added. + /////////////////// + return NULL; +} + +GPIBool gpiGetProfile(GPConnection* connection, GPProfile profileid, + GPIProfile** pProfile) { + GPIProfile* profile; + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIProfile profileTemp; + + profileTemp.profileId = profileid; + profile = (GPIProfile*)TableLookup(iconnection->profileList.profileTable, + &profileTemp); + if (pProfile) + *pProfile = profile; + + return ((profile != NULL) ? GPITrue : GPIFalse); +} + +GPResult gpiNewProfile(GPConnection* connection, const char nick[31], + GPEnum replace, GPEnum blocking, GPCallback callback, + void* param) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIOperation* operation; + GPResult result; + char buffer[31]; + + // Error check. + /////////////// + if (nick == NULL) + Error(connection, GP_PARAMETER_ERROR, "Invalid nick."); + if ((replace != GP_REPLACE) && (replace != GP_DONT_REPLACE)) + Error(connection, GP_PARAMETER_ERROR, "Invalid replace."); + + // Create a new operation. + ////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_NEW_PROFILE, NULL, &operation, + blocking, callback, param)); + + // Send the request. + //////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\newprofile\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\nick\\"); + strzcpy(buffer, nick, GP_NICK_LEN); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, buffer); + if (replace == GP_REPLACE) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\replace\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, 1); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\oldnick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + iconnection->nick); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operation->id); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + /* + if(string.result != GP_NO_ERROR) + { + gpiRemoveOperation(connection, operation); + return string.result; + } + */ + // Process it if blocking. + ////////////////////////// + if (operation->blocking) { + result = gpiProcess(connection, operation->id); + if (result != GP_NO_ERROR) { + gpiRemoveOperation(connection, operation); + return result; + } + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessDeleteProfle(GPConnection* connection, + GPIOperation* operation, const char* input) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPICallback callback; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \dpr\. + //////////////////////// + if (strncmp(input, "\\dpr\\", 5) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if (callback.callback != NULL) { + GPDeleteProfileResponseArg* arg; + arg = (GPDeleteProfileResponseArg*)gsimalloc( + sizeof(GPDeleteProfileResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->profile = iconnection->profileid; + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // Remove the operation. + //////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + +GPResult gpiDeleteProfile(GPConnection* connection, GPCallback callback, + void* param) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIOperation* operation; + GPResult result; + + CHECK_RESULT(gpiAddOperation(connection, GPI_DELETE_PROFILE, NULL, &operation, + GP_BLOCKING, callback, param)); + // Send the message. + //////////////////// + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\delprofile\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operation->id); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + // Remove the profile object. + ///////////////////////////// + gpiRemoveProfileByID(connection, iconnection->profileid); + + // Disconnect the connection. + // PANTS|05.16.00 + ///////////////////////////// + iconnection->connectState = GPI_PROFILE_DELETING; + result = gpiProcess(connection, operation->id); + if (result != GP_NO_ERROR) { + gpiRemoveOperation(connection, operation); + return result; + } + + gpiDisconnect(connection, GPIFalse); + return GP_NO_ERROR; +} + +void gpiRemoveProfileByID(GPConnection* connection, int profileid) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIProfile* profile; + + if (gpiGetProfile(connection, (GPProfile)profileid, &profile)) + TableRemove(iconnection->profileList.profileTable, profile); +} + +void gpiRemoveProfile(GPConnection* connection, GPIProfile* profile) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + TableRemove(iconnection->profileList.profileTable, profile); +} + +typedef struct GPIFindProfileByUserData { + char* nick; + char* email; + GPIProfile** profile; + GPIBool found; +} GPIFindProfileByUserData; + +static GPIBool gpiCheckProfileForUser(GPConnection* connection, + GPIProfile* profile, void* udata) { + GPIFindProfileByUserData* data = (GPIFindProfileByUserData*)udata; + + GSI_UNUSED(connection); + + // Check for a valid cache. + /////////////////////////// + if (profile->cache) { + // Check the nick and email. + //////////////////////////// + if ((strcmp(data->nick, profile->cache->nick) == 0) && + (strcmp(data->email, profile->cache->email) == 0)) { + // Found it. + //////////// + *data->profile = profile; + data->found = GPITrue; + return GPIFalse; + } + } + + return GPITrue; +} + +GPResult gpiFindProfileByUser(GPConnection* connection, char nick[GP_NICK_LEN], + char email[GP_EMAIL_LEN], GPIProfile** profile) { + GPIFindProfileByUserData data; + + data.nick = nick; + data.email = email; + data.profile = profile; + data.found = GPIFalse; + + gpiProfileMap(connection, gpiCheckProfileForUser, &data); + + if (!data.found) + *profile = NULL; + + return GP_NO_ERROR; +} + +typedef struct GPIProfileMapData { + GPConnection* connection; + gpiProfileMapFunc func; + void* data; +} GPIProfileMapData; + +static int gpiProfileMapCallback(void* arg, void* udata) { + GPIProfile* profile = (GPIProfile*)arg; + GPIProfileMapData* data = (GPIProfileMapData*)udata; + return (int)data->func(data->connection, profile, data->data); +} + +GPIBool gpiProfileMap(GPConnection* connection, gpiProfileMapFunc func, + void* data) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIProfileMapData mapData; + + mapData.connection = connection; + mapData.func = func; + mapData.data = data; + + return (!TableMapSafe2(iconnection->profileList.profileTable, + gpiProfileMapCallback, &mapData)) + ? GPITrue + : GPIFalse; +} + +typedef struct GPIFindProfileData { + int index; + GPIProfile* profile; +} GPIFindProfileData; + +static GPIBool gpiCheckForBuddy(GPConnection* connection, GPIProfile* profile, + void* udata) { + GPIFindProfileData* data = (GPIFindProfileData*)udata; + if (profile->buddyStatus && + (data->index == profile->buddyStatus->buddyIndex)) { + data->profile = profile; + return GPIFalse; + } else if (profile->buddyStatusInfo && + (data->index == profile->buddyStatusInfo->buddyIndex)) { + data->profile = profile; + return GPIFalse; + } + + GSI_UNUSED(connection); + + return GPITrue; +} + +GPIProfile* gpiFindBuddy(GPConnection* connection, int buddyIndex) { + GPIFindProfileData data; + + data.index = buddyIndex; + data.profile = NULL; + + gpiProfileMap(connection, gpiCheckForBuddy, &data); + + return data.profile; +} + +void gpiRemoveBuddyStatus(GPIBuddyStatus* buddyStatus) { + GS_ASSERT(buddyStatus); + + freeclear(buddyStatus->locationString); + freeclear(buddyStatus->statusString); + freeclear(buddyStatus); +} + +void gpiRemoveBuddyStatusInfo(GPIBuddyStatusInfo* buddyStatusInfo) { + GS_ASSERT(buddyStatusInfo); + + freeclear(buddyStatusInfo->richStatus); + freeclear(buddyStatusInfo->gameType); + freeclear(buddyStatusInfo->gameVariant); + freeclear(buddyStatusInfo->gameMapName); + ArrayFree(buddyStatusInfo->extendedInfoKeys); + buddyStatusInfo->extendedInfoKeys = NULL; + freeclear(buddyStatusInfo); +} + +GPIBool gpiCanFreeProfile(GPIProfile* profile) { + return ((profile && !profile->cache && !profile->buddyStatus && + !profile->buddyStatusInfo && !profile->peerSig && !profile->authSig)) + ? GPITrue + : GPIFalse; +} diff --git a/source/gamespy/GP/gpiProfile.h b/source/gamespy/GP/gpiProfile.h new file mode 100644 index 000000000..a16af175a --- /dev/null +++ b/source/gamespy/GP/gpiProfile.h @@ -0,0 +1,129 @@ +/* +gpiProfile.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +#define GPI_SIG_LEN 33 + +// TYPES +/////// +// The status for a buddy profile. +////////////////////////////////// + +// New Status Info +typedef struct _GPIBuddyStatusInfo { + int buddyIndex; // 0x00 + GPEnum statusState; // 0x04 + char* richStatus; // 0x08 + char* gameType; // 0x0C + char* gameVariant; // 0x10 + char* gameMapName; // 0x14 + unsigned int sessionFlags; // 0x18 + unsigned int buddyIp; // 0x1C + unsigned short buddyPort; // 0x20 + unsigned int hostIp; // 0x24 + unsigned int hostPrivateIp; // 0x28 + unsigned short queryPort; // 0x2C + unsigned short hostPort; // 0x2E + GPEnum quietModeFlags; // 0x30 + int productId; // 0x34 + // New Status Info extended info Keys + DArray extendedInfoKeys; // 0x38 +} GPIBuddyStatusInfo; + +// Old status +typedef struct { + int buddyIndex; + GPEnum status; + char* statusString; + char* locationString; + unsigned int ip; + unsigned short port; + GPEnum quietModeFlags; +} GPIBuddyStatus; + +// Profile data. +//////////////// +typedef struct GPIProfile { + int profileId; // 0x00 0 + int userId; // 0x04 4 + GPIBuddyStatus* buddyStatus; // 0x08 8 + GPIBuddyStatusInfo* buddyStatusInfo; // 0x0C 12 + GPIInfoCache* cache; + char* authSig; + int requestCount; + char* peerSig; +} GPIProfile; + +// A list of profiles. +////////////////////// +typedef struct { + HashTable profileTable; + int num; + int numBuddies; +} GPIProfileList; + +// FUNCTIONS +/////////// +GPIBool gpiInitProfiles(GPConnection* connection); + +GPIProfile* gpiProfileListAdd(GPConnection* connection, int id); + +GPIBool gpiGetProfile(GPConnection* connection, GPProfile profileid, + GPIProfile** pProfile); + +GPResult gpiProcessNewProfile(GPConnection* connection, GPIOperation* operation, + const char* input); + +GPResult gpiNewProfile(GPConnection* connection, const char nick[GP_NICK_LEN], + GPEnum replace, GPEnum blocking, GPCallback callback, + void* param); + +GPResult gpiProcessDeleteProfle(GPConnection* connection, + GPIOperation* operation, const char* input); + +GPResult gpiDeleteProfile(GPConnection* connection, GPCallback callback, + void* param); + +void gpiRemoveProfile(GPConnection* connection, GPIProfile* profile); + +void gpiRemoveProfileByID(GPConnection* connection, int profileid); + +GPResult gpiLoadDiskProfiles(GPConnection* connection); + +GPResult gpiSaveDiskProfiles(GPConnection* connection); + +GPResult gpiFindProfileByUser(GPConnection* connection, char nick[GP_NICK_LEN], + char email[GP_EMAIL_LEN], GPIProfile** profile); + +// return false to stop the mapping +typedef GPIBool (*gpiProfileMapFunc)(GPConnection* connection, + GPIProfile* profile, void* data); + +GPIBool gpiProfileMap(GPConnection* connection, gpiProfileMapFunc func, + void* data); + +GPIProfile* gpiFindBuddy(GPConnection* connection, int buddyIndex); + +void gpiRemoveBuddyStatus(GPIBuddyStatus* buddyStatus); +void gpiRemoveBuddyStatusInfo(GPIBuddyStatusInfo* buddyStatusInfo); + +GPIBool gpiCanFreeProfile(GPIProfile* profile); + +void gpiSetInfoCacheFilename(const char filename[FILENAME_MAX + 1]); diff --git a/source/gamespy/GP/gpiSearch.c b/source/gamespy/GP/gpiSearch.c new file mode 100644 index 000000000..d0ca17f57 --- /dev/null +++ b/source/gamespy/GP/gpiSearch.c @@ -0,0 +1,1473 @@ +/* +gpiSearch.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include + +// DEFINES +///////// +// Search Manager Address. +////////////////////////// +#define GPI_SEARCH_MANAGER_NAME "gpsp." GSI_DOMAIN_NAME +#define GPI_SEARCH_MANAGER_PORT 29901 + +// GLOBALS +///////// +char GPSearchManagerHostname[64] = GPI_SEARCH_MANAGER_NAME; +// char GPSearchManagerHostname[64] = "localhost"; + +// FUNCTIONS +/////////// +static GPResult gpiStartProfileSearch(GPConnection* connection, + GPIOperation* operation) { + GPISearchData* data = (GPISearchData*)operation->data; + int rcode; + struct sockaddr_in address; + struct hostent* host; + + // Initialize the buffer. + ///////////////////////// + data->inputBuffer.size = 4096; + data->inputBuffer.buffer = + (char*)gsimalloc((unsigned int)data->inputBuffer.size + 1); + if (data->inputBuffer.buffer == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Create the socket. + ///////////////////// + data->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (data->sock == INVALID_SOCKET) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error creating a socket."); + + // Make it non-blocking. + //////////////////////// + rcode = SetSockBlocking(data->sock, 0); + if (rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error making a socket non-blocking."); + + // Bind the socket. + /////////////////// + /* + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + rcode = bind(data->sock, (struct sockaddr *)&address, sizeof(struct + sockaddr_in)); if (gsiSocketIsError(rcode)) CallbackFatalError(connection, + GP_NETWORK_ERROR, GP_NETWORK, "There was an error binding a socket."); + */ + + // Get the server host. + /////////////////////// + host = gethostbyname(GPSearchManagerHostname); + if (host == NULL) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "Could not resolve search mananger host name."); + + // Connect the socket. + ////////////////////// + memset(&address, 0, sizeof(address)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = *(unsigned int*)host->h_addr_list[0]; + assert(address.sin_addr.s_addr != 0); + address.sin_port = htons(GPI_SEARCH_MANAGER_PORT); + rcode = connect(data->sock, (struct sockaddr*)&address, + sizeof(struct sockaddr_in)); + if (gsiSocketIsError(rcode)) { + int error = GOAGetLastError(data->sock); + if ((error != WSAEWOULDBLOCK) && (error != WSAEINPROGRESS) && + (error != WSAETIMEDOUT)) { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error connecting a socket."); + } + } + + // We're waiting for the connect to complete. + ///////////////////////////////////////////// + operation->state = GPI_CONNECTING; + data->searchStartTime = current_time(); + return GP_NO_ERROR; +} + +static GPResult gpiInitSearchData(GPConnection* connection, + GPISearchData** searchData, int type) { + GPISearchData* data; + + // Init the data. + ///////////////// + data = (GPISearchData*)gsimalloc(sizeof(GPISearchData)); + if (data == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + memset(data, 0, sizeof(GPISearchData)); + data->type = type; + data->sock = INVALID_SOCKET; + data->inputBuffer.buffer = NULL; + data->inputBuffer.len = 0; + data->inputBuffer.pos = 0; + data->inputBuffer.size = 0; + data->outputBuffer.len = 0; + data->outputBuffer.pos = 0; + data->outputBuffer.size = 4096; + data->outputBuffer.buffer = + (char*)gsimalloc((unsigned int)data->outputBuffer.size + 1); + if (data->outputBuffer.buffer == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + data->processing = GPIFalse; + data->remove = GPIFalse; + + *searchData = data; + + return GP_NO_ERROR; +} + +static GPResult gpiStartSearch(GPConnection* connection, GPISearchData* data, + GPEnum blocking, GPCallback callback, + void* param) { + GPIOperation* operation; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // One more search. + /////////////////// + iconnection->numSearches++; + + // Create a new operation. + ////////////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_PROFILE_SEARCH, data, &operation, + blocking, callback, param)); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartProfileSearch(connection, operation)); + + // Process it if blocking. + ////////////////////////// + if (operation->blocking) + CHECK_RESULT(gpiProcess(connection, operation->id)); + + return GP_NO_ERROR; +} + +GPResult gpiProfileSearch(GPConnection* connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char firstname[GP_FIRSTNAME_LEN], + const char lastname[GP_LASTNAME_LEN], int icquin, + int skip, GPEnum blocking, GPCallback callback, + void* param) { + GPISearchData* data; + + // Error check. + /////////////// + if ((nick == NULL) || (*nick == '\0')) + if ((email == NULL) || (*email == '\0')) + if ((firstname == NULL) || (*firstname == '\0')) + if ((lastname == NULL) || (*lastname == '\0')) + if (icquin == 0) + if ((uniquenick == NULL) || (*uniquenick == '\0')) + Error(connection, GP_PARAMETER_ERROR, "No search criteria."); + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_PROFILE)); + + // Fill in the data. + //////////////////// + if (nick == NULL) + data->nick[0] = '\0'; + else + strzcpy(data->nick, nick, GP_NICK_LEN); + if (uniquenick == NULL) + data->uniquenick[0] = '\0'; + else + strzcpy(data->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + if (email == NULL) + data->email[0] = '\0'; + else + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + if (firstname == NULL) + data->firstname[0] = '\0'; + else + strzcpy(data->firstname, firstname, GP_FIRSTNAME_LEN); + if (lastname == NULL) + data->lastname[0] = '\0'; + else + strzcpy(data->lastname, lastname, GP_LASTNAME_LEN); + data->icquin = icquin; + if (skip < 0) + skip = 0; + data->skip = skip; + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +/* +GPResult gpiProfileSearchUniquenick(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[], int numNamespaces, + GPEnum blocking, GPCallback callback, + void* param) { + GPISearchData* data; + + // Error check. + /////////////// + if ((uniquenick == NULL) || (*uniquenick == '\0')) + Error(connection, GP_PARAMETER_ERROR, "No search criteria."); + + // Init the data. + ///////////////// + CHECK_RESULT( + gpiInitSearchData(connection, &data, GPI_SEARCH_PROFILE_UNIQUENICK)); + + // Fill in the data. + //////////////////// + strzcpy(data->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + if ((namespaceIDs != NULL) && (numNamespaces > 0)) { + data->numNamespaces = min(numNamespaces, GP_MAX_NAMESPACEIDS); + memcpy(data->namespaceIDs, namespaceIDs, + sizeof(namespaceIDs[0]) * data->numNamespaces); + } else { + data->numNamespaces = 0; + } + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} +*/ + +GPResult gpiIsValidEmail(GPConnection* connection, + const char email[GP_EMAIL_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPISearchData* data; + + // Error check. + /////////////// + if ((email == NULL) || (*email == '\0') || (strlen(email) >= GP_EMAIL_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Invalid e-mail."); + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_IS_VALID)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiGetUserNicks(GPConnection* connection, + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPISearchData* data; + + // Error check. + /////////////// + if ((email == NULL) || (*email == '\0') || (strlen(email) >= GP_EMAIL_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Invalid e-mail."); + if ((password == NULL) || (strlen(password) >= GP_PASSWORD_LEN)) + Error(connection, GP_PARAMETER_ERROR, "Invalid password."); + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_NICKS)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + strzcpy(data->password, password, GP_PASSWORD_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiFindPlayers(GPConnection* connection, int productID, + GPEnum blocking, GPCallback callback, void* param) { + GPISearchData* data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_PLAYERS)); + + // Fill in the data. + //////////////////// + data->productID = productID; + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiCheckUser(GPConnection* connection, const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPISearchData* data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_CHECK)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + _strlwr(data->email); + strzcpy(data->nick, nick, GP_NICK_LEN); + if (password) + strzcpy(data->password, password, GP_PASSWORD_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiNewUser(GPConnection* connection, const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPISearchData* data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_NEWUSER)); + + // Fill in the data. + //////////////////// + strzcpy(data->email, email, GP_EMAIL_LEN); + strzcpy(data->nick, nick, GP_NICK_LEN); + strzcpy(data->password, password, GP_PASSWORD_LEN); + strzcpy(data->uniquenick, uniquenick, GP_UNIQUENICK_LEN); + if (cdkey) + strzcpy(data->cdkey, cdkey, GP_CDKEY_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiOthersBuddy(GPConnection* connection, GPEnum blocking, + GPCallback callback, void* param) { + GPISearchData* data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_OTHERS_BUDDY)); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiOthersBuddyList(GPConnection* connection, int* profiles, + int numOfProfiles, GPEnum blocking, + GPCallback callback, void* param) { + GPISearchData* data; + + CHECK_RESULT( + gpiInitSearchData(connection, &data, GPI_SEARCH_OTHERS_BUDDY_LIST)); + + data->revBuddyProfileIds = profiles; + data->numOfRevBuddyProfiles = numOfProfiles; + + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +GPResult gpiSuggestUniqueNick(GPConnection* connection, + const char desirednick[GP_NICK_LEN], + GPEnum blocking, GPCallback callback, + void* param) { + GPISearchData* data; + + // Init the data. + ///////////////// + CHECK_RESULT(gpiInitSearchData(connection, &data, GPI_SEARCH_SUGGEST_UNIQUE)); + + // Fill in the data. + //////////////////// + strzcpy(data->uniquenick, desirednick, GP_UNIQUENICK_LEN); + + // Start the search. + //////////////////// + CHECK_RESULT(gpiStartSearch(connection, data, blocking, callback, param)); + + return GP_NO_ERROR; +} + +static GPResult gpiProcessSearch(GPConnection* connection, + GPIOperation* operation) { + int state; + GPISearchData* data; + char key[512]; + char value[512]; + GPIBool done; + int index; + int oldIndex; + GPIBool loop; + GPIBool more; + GPICallback callback; + GPIConnection* iconnection = (GPIConnection*)*connection; + int len; + GPIBool connClosed; + GPResult result; + void* tempPtr; + GPIBool doneParsingMatch; + int rcode; + int pid; + GPProfileSearchMatch* match; + GPUniqueMatch* uniqueNickMatch; + + // password encryption stuff + char passwordenc[GP_PASSWORDENC_LEN]; + + // Get a pointer to the data. + ///////////////////////////// + data = (GPISearchData*)operation->data; + + // Loop if blocking. + //////////////////// + if (operation->blocking) + loop = GPITrue; + else + loop = GPIFalse; + + if (!operation->blocking && + (current_time() - data->searchStartTime > GPI_SEARCH_TIMEOUT)) { + data->remove = GPITrue; + CallbackError(connection, GP_NETWORK_ERROR, GP_SEARCH_TIMED_OUT, + "The search timed out"); + } + + do { + // Send anything that needs to be sent. + /////////////////////////////////////// + CHECK_RESULT(gpiSendFromBuffer(connection, data->sock, &data->outputBuffer, + &connClosed, GPITrue, "SM")); + + // Is it connecting? + //////////////////// + if (operation->state == GPI_CONNECTING) { + // Check the connect state. + /////////////////////////// + CHECK_RESULT(gpiCheckSocketConnect(connection, data->sock, &state)); + + // Check for a failed attempt. + ////////////////////////////// + if (state == GPI_DISCONNECTED) + CallbackError(connection, GP_SERVER_ERROR, GP_SEARCH_CONNECTION_FAILED, + "Could not connect to the search manager."); + + // Check if finished connecting. + //////////////////////////////// + if (state == GPI_CONNECTED) { + // Send a request based on type. + //////////////////////////////// + if (data->type == GPI_SEARCH_PROFILE) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\search\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->partnerID); + if (data->nick[0] != '\0') { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\nick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->nick); + } + if (data->uniquenick[0] != '\0') { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->uniquenick); + } + if (data->email[0] != '\0') { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->email); + } + if (data->firstname[0] != '\0') { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\firstname\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->firstname); + } + if (data->lastname[0] != '\0') { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\lastname\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->lastname); + } + if (data->icquin != 0) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\icquin\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->icquin); + } + if (data->skip > 0) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\skip\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, data->skip); + } + } else if (data->type == GPI_SEARCH_IS_VALID) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\valid\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->partnerID); + } else if (data->type == GPI_SEARCH_NICKS) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\nicks\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + + gpiEncodeString(data->password, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\passenc\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, passwordenc); + + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->namespaceID); + + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->partnerID); + } else if (data->type == GPI_SEARCH_PLAYERS) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\pmatch\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\productid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + data->productID); + } else if (data->type == GPI_SEARCH_CHECK) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\check\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->nick); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->partnerID); + gpiEncodeString(data->password, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\passenc\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, passwordenc); + } else if (data->type == GPI_SEARCH_NEWUSER) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\newuser\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\nick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->nick); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\email\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, data->email); + gpiEncodeString(data->password, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\passenc\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, passwordenc); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\productID\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->productID); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->namespaceID); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->uniquenick); + if (data->cdkey[0]) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\cdkey\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->cdkey); + } + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->partnerID); + } else if (data->type == GPI_SEARCH_OTHERS_BUDDY) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\others\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->namespaceID); + } else if (data->type == GPI_SEARCH_OTHERS_BUDDY_LIST) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\otherslist\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\sesskey\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\profileid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->profileid); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\numopids\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + data->numOfRevBuddyProfiles); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\opids\\"); + if (data->revBuddyProfileIds) { + int i; + + gpiAppendIntToBuffer(connection, &data->outputBuffer, + data->revBuddyProfileIds[0]); + + for (i = 1; i < data->numOfRevBuddyProfiles; i++) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, "|"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + data->revBuddyProfileIds[i]); + } + } + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->namespaceID); + } else if (data->type == GPI_SEARCH_SUGGEST_UNIQUE) { + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\uniquesearch\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\preferrednick\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + data->uniquenick); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\namespaceid\\"); + gpiAppendIntToBuffer(connection, &data->outputBuffer, + iconnection->namespaceID); + } else { + assert(0); + } + + gpiAppendStringToBuffer(connection, &data->outputBuffer, + "\\gamename\\"); + gpiAppendStringToBuffer(connection, &data->outputBuffer, + __GSIACGamename); + gpiAppendStringToBuffer(connection, &data->outputBuffer, "\\final\\"); + + // Update the state. + //////////////////// + operation->state = GPI_WAITING; + } + } + // Is it waiting? + ///////////////// + else if (operation->state == GPI_WAITING) { + // Read from the socket. + //////////////////////// + result = gpiRecvToBuffer(connection, data->sock, &data->inputBuffer, &len, + &connClosed, "SM"); + if (result != GP_NO_ERROR) { + if (result == GP_NETWORK_ERROR) + CallbackError(connection, GP_NETWORK_ERROR, + GP_SEARCH_CONNECTION_FAILED, + "There was an error reading from the server."); + return result; + } + if (operation->blocking && + (current_time() - data->searchStartTime > GPI_SEARCH_TIMEOUT)) { + data->remove = GPITrue; + CallbackError(connection, GP_NETWORK_ERROR, GP_SEARCH_TIMED_OUT, + "The search timed out"); + } + // Is this the end of the response? + /////////////////////////////////// + if (strstr(data->inputBuffer.buffer, "\\final\\") != NULL) { + // Reset the index. + /////////////////// + index = 0; + + // This operation is finishing up. + ////////////////////////////////// + operation->state = GPI_FINISHING; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, data->inputBuffer.buffer, GPITrue)) { + data->remove = GPITrue; + return GP_SERVER_ERROR; + } + + // Process it based on type. + //////////////////////////// + if (data->type == GPI_SEARCH_PROFILE) { + GPProfileSearchResponseArg arg; + // Start setting up the arg. + //////////////////////////// + arg.result = GP_NO_ERROR; + arg.numMatches = 0; + arg.matches = NULL; + arg.more = GP_DONE; + + // Parse the message. + ///////////////////// + done = GPIFalse; + do { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if (strcmp(key, "bsrdone") == 0) { + // Check for more. + ////////////////// + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "more") == 0) { + // Make sure there are actually more. + ///////////////////////////////////// + if (strcmp(value, "0") != 0) + arg.more = GP_MORE; + } + + // Done. + //////// + done = GPITrue; + } else if (strcmp(key, "bsr") == 0) { + // Create a new match. + ////////////////////// + arg.numMatches++; + arg.matches = (GPProfileSearchMatch*)gsirealloc( + arg.matches, sizeof(GPProfileSearchMatch) * arg.numMatches); + if (arg.matches == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + match = &arg.matches[arg.numMatches - 1]; + memset(match, 0, sizeof(GPProfileSearchMatch)); + + // Get the profile id. + ////////////////////// + match->profile = atoi(value); + + // PANTS|05.16.00 + // Changed to be order independent, and ignore unrecognized keys. + ///////////////////////////////////////////////////////////////// + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + + // Set the field based on the key. + ////////////////////////////////// + if (strcmp(key, "nick") == 0) + strzcpy(match->nick, value, GP_NICK_LEN); + else if (strcmp(key, "uniquenick") == 0) + strzcpy(match->uniquenick, value, GP_UNIQUENICK_LEN); + // else if (strcmp(key, "namespaceid") == 0) + // match->namespaceID = atoi(value); + else if (strcmp(key, "firstname") == 0) + strzcpy(match->firstname, value, GP_FIRSTNAME_LEN); + else if (strcmp(key, "lastname") == 0) + strzcpy(match->lastname, value, GP_LASTNAME_LEN); + else if (strcmp(key, "email") == 0) + strzcpy(match->email, value, GP_EMAIL_LEN); + else if ((strcmp(key, "bsr") == 0) || + (strcmp(key, "bsrdone") == 0)) { + doneParsingMatch = GPITrue; + index = oldIndex; + } + } while (!doneParsingMatch); + } else { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + } + } while (!done); + + // Save the more state. + /////////////////////// + more = (GPIBool)arg.more; + + // Get the callback. + //////////////////// + callback = operation->callback; + + // Call the callback. + ///////////////////// + if (callback.callback != NULL) + callback.callback(connection, &arg, callback.param); + + // Start a new operation if they want more matches. + /////////////////////////////////////////////////// + if ((more == GP_MORE) && (arg.more == GP_MORE)) + CHECK_RESULT(gpiProfileSearch( + connection, data->nick, data->uniquenick, data->email, + data->firstname, data->lastname, data->icquin, + arg.numMatches + data->skip, (GPEnum)operation->blocking, + operation->callback.callback, operation->callback.param)); + + // We're done. + ////////////// + freeclear(arg.matches); + } else if (data->type == GPI_SEARCH_IS_VALID) { + callback = operation->callback; + if (callback.callback != NULL) { + GPIsValidEmailResponseArg* arg; + + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "vr") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + + // Setup the arg. + ///////////////// + arg = (GPIsValidEmailResponseArg*)gsimalloc( + sizeof(GPIsValidEmailResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + strzcpy(arg->email, data->email, GP_EMAIL_LEN); + if (value[0] == '0') + arg->isValid = GP_INVALID; + else + arg->isValid = GP_VALID; + + // Add the callback. + //////////////////// + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, operation, 0)); + } + } else if (data->type == GPI_SEARCH_NICKS) { + callback = operation->callback; + if (callback.callback != NULL) { + GPGetUserNicksResponseArg* arg; + + // Setup the arg. + ///////////////// + arg = (GPGetUserNicksResponseArg*)gsimalloc( + sizeof(GPGetUserNicksResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + strcpy(arg->email, data->email); + arg->numNicks = 0; + arg->nicks = NULL; + arg->uniquenicks = NULL; + + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "nr") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + + // Get the nicks. + ///////////////// + done = GPIFalse; + do { + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "nick") == 0) { + // Add it. + ////////// + tempPtr = + gsirealloc(arg->nicks, sizeof(char*) * (arg->numNicks + 1)); + if (tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->nicks = (char**)tempPtr; + tempPtr = gsimalloc(GP_NICK_LEN); + if (tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->nicks[arg->numNicks] = (gsi_char*)tempPtr; + strzcpy(arg->nicks[arg->numNicks], value, GP_NICK_LEN); + arg->numNicks++; + } else if (strcmp(key, "uniquenick") == 0) { + if (arg->numNicks <= 0) + continue; + + // Add it. + ////////// + tempPtr = + gsirealloc(arg->uniquenicks, sizeof(char*) * arg->numNicks); + if (tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->uniquenicks = (char**)tempPtr; + tempPtr = gsimalloc(GP_UNIQUENICK_LEN); + if (tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->uniquenicks[arg->numNicks - 1] = (gsi_char*)tempPtr; + strzcpy(arg->uniquenicks[arg->numNicks - 1], value, + GP_UNIQUENICK_LEN); + } else if (strcmp(key, "ndone") == 0) { + // Done. + //////// + done = GPITrue; + } else { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + } + } while (!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, + GPI_ADD_NICKS)); + } + } else if (data->type == GPI_SEARCH_PLAYERS) { + callback = operation->callback; + if (callback.callback != NULL) { + GPFindPlayersResponseArg* arg; + GPFindPlayerMatch* match; + + // Start setting up the arg. + //////////////////////////// + arg = (GPFindPlayersResponseArg*)gsimalloc( + sizeof(GPFindPlayersResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->productID = data->productID; + arg->result = GP_NO_ERROR; + arg->numMatches = 0; + arg->matches = NULL; + + // Parse the message. + ///////////////////// + done = GPIFalse; + do { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if (strcmp(key, "psrdone") == 0) { + // Done. + //////// + done = GPITrue; + } else if (strcmp(key, "psr") == 0) { + // Create a new match. + ////////////////////// + arg->numMatches++; + arg->matches = (GPFindPlayerMatch*)gsirealloc( + arg->matches, sizeof(GPFindPlayerMatch) * arg->numMatches); + if (arg->matches == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + match = &arg->matches[arg->numMatches - 1]; + memset(match, 0, sizeof(GPFindPlayerMatch)); + match->status = GP_ONLINE; + + // Get the profile id. + ////////////////////// + match->profile = atoi(value); + + // PANTS|05.16.00 + // Changed to be order independent, and ignore unrecognized + // keys. + ///////////////////////////////////////////////////////////////// + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue(connection, + data->inputBuffer.buffer, + &index, key, value)); + + // Set the field based on the key. + ////////////////////////////////// + if (strcmp(key, "status") == 0) + strzcpy(match->statusString, value, GP_STATUS_STRING_LEN); + else if (strcmp(key, "nick") == 0) + strzcpy(match->nick, value, GP_NICK_LEN); + if (strcmp(key, "statuscode") == 0) + match->status = (GPEnum)atoi(value); + else if ((strcmp(key, "psr") == 0) || + (strcmp(key, "psrdone") == 0)) { + doneParsingMatch = GPITrue; + index = oldIndex; + } + } while (!doneParsingMatch); + } else { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + } + } while (!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, + GPI_ADD_PMATCH)); + } + } else if (data->type == GPI_SEARCH_CHECK) { + callback = operation->callback; + if (callback.callback != NULL) { + GPCheckResponseArg* arg; + + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "cur") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + + rcode = atoi(value); + if (rcode) { + iconnection->errorCode = (GPErrorCode)rcode; + pid = 0; + } else { + if (!gpiValueForKey(data->inputBuffer.buffer, "\\pid\\", value, + sizeof(value))) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + pid = atoi(value); + } + + // Setup the arg. + ///////////////// + arg = (GPCheckResponseArg*)gsimalloc(sizeof(GPCheckResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = (GPResult)rcode; + arg->profile = pid; + + // Add the callback. + //////////////////// + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, operation, 0)); + } + } else if (data->type == GPI_SEARCH_NEWUSER) { + callback = operation->callback; + if (callback.callback != NULL) { + GPNewUserResponseArg* arg; + + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "nur") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + + rcode = atoi(value); + if (rcode) + iconnection->errorCode = (GPErrorCode)rcode; + if (!gpiValueForKey(data->inputBuffer.buffer, "\\pid\\", value, + sizeof(value))) { + if (rcode == 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + pid = 0; + } else + pid = atoi(value); + + // Setup the arg. + ///////////////// + arg = + (GPNewUserResponseArg*)gsimalloc(sizeof(GPNewUserResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = (GPResult)rcode; + arg->profile = pid; + + // Add the callback. + //////////////////// + CHECK_RESULT( + gpiAddCallback(connection, callback, arg, operation, 0)); + } + } else if (data->type == GPI_SEARCH_OTHERS_BUDDY) { + callback = operation->callback; + if (callback.callback != NULL) { + GPGetReverseBuddiesResponseArg* arg; + + // Setup the arg. + ///////////////// + arg = (GPGetReverseBuddiesResponseArg*)gsimalloc( + sizeof(GPGetReverseBuddiesResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + arg->numProfiles = 0; + arg->profiles = NULL; + + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "others") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + + // Get the profiles. + ///////////////// + done = GPIFalse; + do { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if (strcmp(key, "odone") == 0) { + // Done. + //////// + done = GPITrue; + } else if (strcmp(key, "o") == 0) { + // Add it. + ////////// + tempPtr = + gsirealloc(arg->profiles, sizeof(GPProfileSearchMatch) * + (arg->numProfiles + 1)); + if (tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->profiles = (GPProfileSearchMatch*)tempPtr; + match = &arg->profiles[arg->numProfiles]; + memset(match, 0, sizeof(GPProfileSearchMatch)); + arg->numProfiles++; + + // Get the profile id. + ////////////////////// + match->profile = atoi(value); + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue(connection, + data->inputBuffer.buffer, + &index, key, value)); + + // Set the field based on the key. + ////////////////////////////////// + if (strcmp(key, "nick") == 0) + strzcpy(match->nick, value, GP_NICK_LEN); + else if (strcmp(key, "uniquenick") == 0) + strzcpy(match->uniquenick, value, GP_UNIQUENICK_LEN); + else if (strcmp(key, "first") == 0) + strzcpy(match->firstname, value, GP_FIRSTNAME_LEN); + else if (strcmp(key, "last") == 0) + strzcpy(match->lastname, value, GP_LASTNAME_LEN); + else if (strcmp(key, "email") == 0) + strzcpy(match->email, value, GP_EMAIL_LEN); + else if ((strcmp(key, "o") == 0) || + (strcmp(key, "odone") == 0)) { + doneParsingMatch = GPITrue; + index = oldIndex; + } + } while (!doneParsingMatch); + } else { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + } + } while (!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, + GPI_ADD_REVERSE_BUDDIES)); + } + } else if (data->type == GPI_SEARCH_OTHERS_BUDDY_LIST) { + callback = operation->callback; + if (callback.callback != NULL) { + GPGetReverseBuddiesListResponseArg* arg; + + // Setup the arg. + ///////////////// + arg = (GPGetReverseBuddiesListResponseArg*)gsimalloc( + sizeof(GPGetReverseBuddiesListResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + arg->numOfUniqueMatchs = 0; + arg->matches = NULL; + + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "otherslist") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + + // Get the profiles. + ///////////////// + done = GPIFalse; + do { + // Read the next key and value. + /////////////////////////////// + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + + // Is the list done? + //////////////////// + if (strcmp(key, "oldone") == 0) { + // Done. + //////// + done = GPITrue; + } else if (strcmp(key, "o") == 0) { + // Add it. + ////////// + tempPtr = + gsirealloc(arg->matches, sizeof(GPUniqueMatch) * + (arg->numOfUniqueMatchs + 1)); + if (tempPtr == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->matches = (GPUniqueMatch*)tempPtr; + uniqueNickMatch = &arg->matches[arg->numOfUniqueMatchs]; + memset(uniqueNickMatch, 0, sizeof(GPUniqueMatch)); + arg->numOfUniqueMatchs++; + + // Get the profile id. + ////////////////////// + uniqueNickMatch->profile = atoi(value); + + // Read key/value pairs. + //////////////////////// + doneParsingMatch = GPIFalse; + do { + // Read the next key/value. + /////////////////////////// + oldIndex = index; + CHECK_RESULT(gpiReadKeyAndValue(connection, + data->inputBuffer.buffer, + &index, key, value)); + // Set the field based on the key. + ////////////////////////////////// + if (strcmp(key, "uniquenick") == 0) + strzcpy(uniqueNickMatch->uniqueNick, value, + GP_UNIQUENICK_LEN); + else if ((strcmp(key, "o") == 0) || + (strcmp(key, "oldone") == 0)) { + doneParsingMatch = GPITrue; + index = oldIndex; + } + } while (!doneParsingMatch); + } else { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + } + } while (!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, + GPI_ADD_REVERSE_BUDDIES_LIST)); + } + + } else if (data->type == GPI_SEARCH_SUGGEST_UNIQUE) { + callback = operation->callback; + if (callback.callback != NULL) { + int count = 0; + GPSuggestUniqueNickResponseArg* arg; + + // Setup the arg. + ///////////////// + arg = (GPSuggestUniqueNickResponseArg*)gsimalloc( + sizeof(GPSuggestUniqueNickResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + arg->result = GP_NO_ERROR; + arg->numSuggestedNicks = 0; + arg->suggestedNicks = NULL; + + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "us") != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + arg->numSuggestedNicks = atoi(value); + + // Allocate memory for the nick array. + ////////////////////////////////////// + arg->suggestedNicks = (gsi_char**)gsimalloc(sizeof(gsi_char*) * + arg->numSuggestedNicks); + if (!arg->suggestedNicks) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Get the nicks. + ///////////////// + done = GPIFalse; + do { + CHECK_RESULT(gpiReadKeyAndValue( + connection, data->inputBuffer.buffer, &index, key, value)); + if (strcmp(key, "nick") == 0) { + // Add it. + ////////// + arg->suggestedNicks[count] = gsimalloc(GP_UNIQUENICK_LEN); + if (arg->suggestedNicks[count] == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + strzcpy(arg->suggestedNicks[count], value, GP_UNIQUENICK_LEN); + count++; + } else if (strcmp(key, "usdone") == 0) { + // Check that the header matches the actual number of nicks. + //////////////////////////////////////////////////////////// + assert(count == arg->numSuggestedNicks); + arg->numSuggestedNicks = count; + + // Done. + //////// + done = GPITrue; + } else { + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Error reading from the search server."); + } + } while (!done); + + // Do it. + ///////// + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, + GPI_ADD_SUGGESTED_UNIQUE)); + } + } else { + assert(0); + } + + // Flag the operation for removal. + ////////////////////////////////// + data->remove = GPITrue; + + // If we're looping, stop. + ////////////////////////// + loop = GPIFalse; + } + } + // PANTS|05.23.00 - removed sleep + // crt - added it back 6/13/00 + // PANTS|07.10.00 - only sleep if looping + if (loop) + msleep(10); + } while (loop); + + return GP_NO_ERROR; +} + +GPResult gpiProcessSearches(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIOperation** searchList; + GPIOperation* operation; + GPISearchData* data; + GPResult result; + int num = 0; + int i; + + // Are there any searches? + ////////////////////////// + if (iconnection->numSearches > 0) { + // Alloc mem for a search list. + /////////////////////////////// + searchList = (GPIOperation**)gsimalloc(sizeof(GPIOperation*) * + iconnection->numSearches); + if (searchList == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + // Create the search list. + ////////////////////////// + for (operation = &iconnection->operationList[0]; operation != NULL; + operation = operation->pnext) { + // Is this a search? + //////////////////// + if ((operation->type == GPI_PROFILE_SEARCH) && + (operation->state != GPI_FINISHING)) { + // Is this search being processed already? + ////////////////////////////////////////// + if (!((GPISearchData*)operation->data)->processing) { + assert(num < iconnection->numSearches); + searchList[num++] = operation; + ((GPISearchData*)operation->data)->processing = GPITrue; + } + } + } + + // Process the searches. + //////////////////////// + for (i = 0; i < num; i++) { + result = gpiProcessSearch(connection, searchList[i]); + if (result != GP_NO_ERROR) + searchList[i]->result = result; + } + + // Clear the processing flags, and remove searches that are done. + ///////////////////////////////////////////////////////////////// + for (i = 0; i < num; i++) { + data = ((GPISearchData*)searchList[i]->data); + data->processing = GPIFalse; + if (data->remove) + gpiRemoveOperation(connection, searchList[i]); + } + + freeclear(searchList); + } + + return GP_NO_ERROR; +} diff --git a/source/gamespy/GP/gpiSearch.h b/source/gamespy/GP/gpiSearch.h new file mode 100644 index 000000000..c66b703de --- /dev/null +++ b/source/gamespy/GP/gpiSearch.h @@ -0,0 +1,116 @@ +/* +gpiSearch.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// TYPES +/////// +#define GPI_SEARCH_PROFILE 1 +#define GPI_SEARCH_IS_VALID 2 +#define GPI_SEARCH_NICKS 3 +#define GPI_SEARCH_PLAYERS 4 +#define GPI_SEARCH_CHECK 5 +#define GPI_SEARCH_NEWUSER 6 +#define GPI_SEARCH_OTHERS_BUDDY 7 +#define GPI_SEARCH_SUGGEST_UNIQUE 8 +#define GPI_SEARCH_OTHERS_BUDDY_LIST 9 +#define GPI_SEARCH_PROFILE_UNIQUENICK 10 + +// A timeout used to abort searches taking too long +#define GPI_SEARCH_TIMEOUT 60000 + +// Profile Search operation data. +///////////////////////////////// +typedef struct { + int type; + SOCKET sock; + GPIBuffer inputBuffer; + GPIBuffer outputBuffer; + char nick[GP_NICK_LEN]; + char uniquenick[GP_UNIQUENICK_LEN]; + // int namespaceIDs[GP_MAX_NAMESPACEIDS]; + // int numNamespaces; + char email[GP_EMAIL_LEN]; + char firstname[GP_FIRSTNAME_LEN]; + char lastname[GP_LASTNAME_LEN]; + char password[GP_PASSWORD_LEN]; + char cdkey[GP_CDKEY_LEN]; + int partnerID; + int icquin; + int skip; + int productID; + GPIBool processing; + GPIBool remove; + gsi_time searchStartTime; + int* revBuddyProfileIds; + int numOfRevBuddyProfiles; +} GPISearchData; + +// FUNCTIONS +/////////// +GPResult gpiProfileSearch(GPConnection* connection, + const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char firstname[GP_FIRSTNAME_LEN], + const char lastname[GP_LASTNAME_LEN], int icquin, + int skip, GPEnum blocking, GPCallback callback, + void* param); + +GPResult gpiProfileSearchUniquenick(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const int namespaceIDs[], int numNamespaces, + GPEnum blocking, GPCallback callback, + void* param); + +GPResult gpiIsValidEmail(GPConnection* connection, + const char email[GP_EMAIL_LEN], GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiGetUserNicks(GPConnection* connection, + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiFindPlayers(GPConnection* connection, int productID, + GPEnum blocking, GPCallback callback, void* param); + +GPResult gpiCheckUser(GPConnection* connection, const char nick[GP_NICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiNewUser(GPConnection* connection, const char nick[GP_NICK_LEN], + const char uniquenick[GP_UNIQUENICK_LEN], + const char email[GP_EMAIL_LEN], + const char password[GP_PASSWORD_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiOthersBuddy(GPConnection* connection, GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiOthersBuddyList(GPConnection* connection, int* profiles, + int numOfProfiles, GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiSuggestUniqueNick(GPConnection* connection, + const char desirednick[GP_NICK_LEN], + GPEnum blocking, GPCallback callback, + void* param); + +GPResult gpiProcessSearches(GPConnection* connection); diff --git a/source/gamespy/GP/gpiTransfer.c b/source/gamespy/GP/gpiTransfer.c new file mode 100644 index 000000000..fc88eff3b --- /dev/null +++ b/source/gamespy/GP/gpiTransfer.c @@ -0,0 +1,1738 @@ +/* +gpiTransfer.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include + +#define GPI_TRANSFER_VERSION 1 +#define GPI_PEER_TIMEOUT_TIME (1 * 60000) +#define GPI_KEEPALIVE_TIME (4 * 60000) + +//#define GPI_ACKNOWLEDGED_WINDOW (100000 * 1024) +#define GPI_DATA_SIZE (1 * 1024) +//#define GPI_CONFIRM_FILES + +// FUNCTIONS +/////////// +#ifndef NOFILE +static void gpiTransferFree(void* elem) { + GPITransfer* transfer = (GPITransfer*)elem; + + if (transfer->message) + freeclear(transfer->message); + + if (transfer->baseDirectory) + gsifree(transfer->baseDirectory); + + if (transfer->files) { + ArrayFree(transfer->files); + transfer->files = NULL; + } +} + +static int gpiTransferCompare(const void* elem1, const void* elem2) { + GPITransfer* transfer1 = (GPITransfer*)elem1; + GPITransfer* transfer2 = (GPITransfer*)elem2; + + return (transfer1->localID - transfer2->localID); +} + +static void gpiFreeFile(void* elem) { + GPIFile* file = (GPIFile*)elem; + gsifree(file->path); + gsifree(file->name); +} + +static GPResult gpiFinishTransferMessage(GPConnection* connection, + GPITransfer* transfer, + const char* message, int len) { + GPResult result = + gpiPeerFinishTransferMessage(connection, transfer->peer, message, len); + + transfer->lastSend = current_time(); + + return result; +} + +GPResult gpiInitTransfers(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + iconnection->transfers = ArrayNew(sizeof(GPITransfer), 0, gpiTransferFree); + if (!iconnection->transfers) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + return GP_NO_ERROR; +} + +void gpiCleanupTransfers(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Free the transfers. + ////////////////////// + if (iconnection->transfers) { + ArrayFree(iconnection->transfers); + iconnection->transfers = NULL; + } +} + +static GPResult gpiNewTransfer(GPConnection* connection, GPITransfer** transfer, + GPProfile profile, GPIBool sender) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPITransfer transferTemp; + + // Fill in the object. + ////////////////////// + memset(&transferTemp, 0, sizeof(GPITransfer)); + transferTemp.files = ArrayNew(sizeof(GPIFile), 0, gpiFreeFile); + if (!transferTemp.files) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + transferTemp.localID = iconnection->nextTransferID++; + transferTemp.sender = sender; + transferTemp.profile = profile; + transferTemp.throttle = -1; + transferTemp.currentFile = -1; + + // Add it. + ////////// + ArrayAppend(iconnection->transfers, &transferTemp); + + // Get it. + ////////// + *transfer = (GPITransfer*)ArrayNth(iconnection->transfers, + ArrayLength(iconnection->transfers) - 1); + + return GP_NO_ERROR; +} + +GPResult gpiNewSenderTransfer(GPConnection* connection, GPITransfer** transfer, + GPProfile profile) { + GPITransfer* pTransfer; + GPIConnection* iconnection = (GPIConnection*)*connection; + unsigned long now; + + CHECK_RESULT(gpiNewTransfer(connection, transfer, profile, GPITrue)); + + now = current_time(); + + pTransfer = *transfer; + pTransfer->state = GPITransferPinging; + pTransfer->transferID.profileid = iconnection->profileid; + pTransfer->transferID.count = pTransfer->localID; + pTransfer->transferID.time = (unsigned int)now; + pTransfer->lastSend = now; + + return GP_NO_ERROR; +} + +static GPResult gpiNewReceiverTransfer(GPConnection* connection, + GPITransfer** transfer, + GPProfile profile, + GPITransferID* transferID) { + GPITransfer* pTransfer; + + CHECK_RESULT(gpiNewTransfer(connection, transfer, profile, GPIFalse)); + + pTransfer = *transfer; + pTransfer->state = GPITransferWaiting; + memcpy(&pTransfer->transferID, transferID, sizeof(GPITransferID)); + + return GP_NO_ERROR; +} + +void gpiFreeTransfer(GPConnection* connection, GPITransfer* transfer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + int pos; + + // Find the transfer. + ///////////////////// + pos = ArraySearch(iconnection->transfers, transfer, gpiTransferCompare, 0, 0); + assert(pos != NOT_FOUND); + if (pos == NOT_FOUND) + return; + + // Remove it. + ///////////// + ArrayDeleteAt(iconnection->transfers, pos); +} + +void gpiCancelTransfer(GPConnection* connection, GPITransfer* transfer) { + // Send the cancel message. + /////////////////////////// + if (transfer->peer) { + // Start the message. + ///////////////////// + if (gpiPeerStartTransferMessage( + connection, transfer->peer, GPI_BM_FILE_TRANSFER_CANCEL, + (GPITransferID_st)&transfer->transferID) == GP_NO_ERROR) { + // Finish the message. + ////////////////////// + gpiFinishTransferMessage(connection, transfer, NULL, 0); + } + } +} + +void gpiTransferError(GPConnection* connection, const GPITransfer* transfer) { + GPTransferCallbackArg* arg; + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_ERROR; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } +} + +GPIFile* gpiAddFileToTransfer(GPITransfer* transfer, const char* path, + const char* name) { + GPIFile file; + char* str; + + assert(name && name[0]); + + memset(&file, 0, sizeof(GPIFile)); + + // Copy the path. + ///////////////// + if (path) { + file.path = goastrdup(path); + if (!file.path) + return NULL; + } + + // Copy the name. + ///////////////// + file.name = goastrdup(name); + if (!file.name) { + gsifree(file.path); + return NULL; + } + + // Change all slashes to backslashes. + ///////////////////////////////////// + while ((str = strchr(file.name, '\\')) != NULL) + *str = '/'; + + // Check for a directory. + ///////////////////////// + if (name[strlen(name) - 1] == '/') + file.flags = GPI_FILE_DIRECTORY; + + // No size yet. + /////////////// + file.size = -1; + + // Add it to the list of files. + /////////////////////////////// + ArrayAppend(transfer->files, &file); + + // Return the file. + /////////////////// + return (GPIFile*)ArrayNth(transfer->files, ArrayLength(transfer->files) - 1); +} + +static GPResult gpiSendTransferRequest(GPConnection* connection, + GPITransfer* transfer) { + char buffer[32]; + GPIFile* file; + int i; + int num; + + // Get the number of files. + /////////////////////////// + num = ArrayLength(transfer->files); + + // Start the message. + ///////////////////// + CHECK_RESULT(gpiPeerStartTransferMessage( + connection, transfer->peer, GPI_BM_FILE_SEND_REQUEST, + (GPITransferID_st)&transfer->transferID)); + + // Add the rest of the headers. + /////////////////////////////// + sprintf(buffer, "\\version\\%d\\num\\%d", GPI_TRANSFER_VERSION, num); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + for (i = 0; i < num; i++) { + file = (GPIFile*)ArrayNth(transfer->files, i); + + sprintf(buffer, "\\name%d\\", i); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, file->name)); + + sprintf(buffer, "\\size%d\\", i); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + CHECK_RESULT(gpiSendOrBufferInt(connection, transfer->peer, file->size)); + + sprintf(buffer, "\\mtime%d\\", i); + CHECK_RESULT(gpiSendOrBufferString(connection, transfer->peer, buffer)); + CHECK_RESULT( + gpiSendOrBufferUInt(connection, transfer->peer, file->modTime)); + } + + // Finish the message. + ////////////////////// + CHECK_RESULT( + gpiFinishTransferMessage(connection, transfer, transfer->message, -1)); + + return GP_NO_ERROR; +} + +static GPITransfer* gpiFindTransferByTransferID(GPConnection* connection, + GPITransferID* transferID) { + GPIConnection* iconnection = (GPIConnection*)*connection; + int i; + int num; + GPITransfer* transfer; + + num = ArrayLength(iconnection->transfers); + for (i = 0; i < num; i++) { + transfer = (GPITransfer*)ArrayNth(iconnection->transfers, i); + if (memcmp(&transfer->transferID, transferID, sizeof(GPITransferID)) == 0) + return transfer; + } + + return NULL; +} + +GPITransfer* gpiFindTransferByLocalID(GPConnection* connection, int localID) { + GPIConnection* iconnection = (GPIConnection*)*connection; + int i; + int num; + GPITransfer* transfer; + + num = ArrayLength(iconnection->transfers); + for (i = 0; i < num; i++) { + transfer = (GPITransfer*)ArrayNth(iconnection->transfers, i); + if (transfer->localID == localID) + return transfer; + } + + return NULL; +} + +int gpiGetTransferLocalIDByIndex(GPConnection* connection, int index) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPITransfer* transfer; + int num; + + num = ArrayLength(iconnection->transfers); + assert(index >= 0); + assert(index < num); + if ((index < 0) || (index >= num)) + return -1; + + transfer = (GPITransfer*)ArrayNth(iconnection->transfers, index); + assert(transfer); + if (!transfer) + return -1; + + return transfer->localID; +} + +void gpiSkipFile(GPConnection* connection, GPITransfer* transfer, int file, + int reason) { + char buffer[32]; + + if (gpiPeerStartTransferMessage(connection, transfer->peer, GP_FILE_SKIP, + (GPITransferID_st)&transfer->transferID) != + GP_NO_ERROR) + return; + + sprintf(buffer, "\\file\\%d\\reason\\%d", file, reason); + gpiSendOrBufferString(connection, transfer->peer, buffer); + gpiFinishTransferMessage(connection, transfer, NULL, 0); +} + +void gpiSkipCurrentFile(GPConnection* connection, GPITransfer* transfer, + int reason) { + gpiSkipFile(connection, transfer, transfer->currentFile, reason); + + transfer->currentFile++; +} + +static GPIBool gpiHandleSendRequest(GPConnection* connection, GPIPeer* peer, + GPITransferID* transferID, + const char* headers, const char* buffer, + int bufferLen) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPITransfer* transfer; + GPIFile* file; + GPTransferCallbackArg* arg; + char key[16]; + char intValue[16]; + int version; + int numFiles; + char name[FILENAME_MAX]; + int size; + unsigned int mtime; + int i; + size_t len; + int totalSize = 0; + + // If we don't have a callback, we're not accepting requests. + ///////////////////////////////////////////////////////////// + if (!iconnection->callbacks[GPI_TRANSFER_CALLBACK].callback) + return GPIFalse; + + // Check the version. + ///////////////////// + if (!gpiValueForKey(headers, "\\version\\", intValue, sizeof(intValue))) + return GPIFalse; + version = atoi(intValue); + if (version < 1) + return GPIFalse; + + // Get the number of files. + /////////////////////////// + if (!gpiValueForKey(headers, "\\num\\", intValue, sizeof(intValue))) + return GPIFalse; + numFiles = atoi(intValue); + if (numFiles < 1) + return GPIFalse; + + // Create the transfer object. + ////////////////////////////// + if (gpiNewReceiverTransfer(connection, &transfer, peer->profile, + transferID) != GP_NO_ERROR) + return GPIFalse; + + // Set the peer. + //////////////// + transfer->peer = peer; + + // Parse the file list. + /////////////////////// + for (i = 0; i < numFiles; i++) { + sprintf(key, "\\name%d\\", i); + if (!gpiValueForKey(headers, key, name, sizeof(name))) { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + len = strlen(name); + if (strstr(name, "//") || strstr(name, "./") || (name[len - 1] == '.') || + (name[0] == '/') || (strcspn(name, ":*?\"<>|\n") != len)) { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + + sprintf(key, "\\size%d\\", i); + if (!gpiValueForKey(headers, key, intValue, sizeof(intValue))) { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + size = atoi(intValue); + if (size < 0) { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + totalSize += size; + + sprintf(key, "\\mtime%d\\", i); + if (!gpiValueForKey(headers, key, intValue, sizeof(intValue))) { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + mtime = (unsigned int)strtoul(intValue, NULL, 10); + + file = gpiAddFileToTransfer(transfer, NULL, name); + if (!file) { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + file->size = size; + file->modTime = mtime; + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (!arg) { + gpiFreeTransfer(connection, transfer); + return GPIFalse; + } + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_SEND_REQUEST; + arg->num = numFiles; + arg->message = goastrdup(buffer); + { + GPResult aResult = gpiAddCallback( + connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, + GPI_ADD_TRANSFER_CALLBACK); + if (aResult != GP_NO_ERROR) + return GPIFalse; + } + + // Store the total size. + //////////////////////// + transfer->totalSize = totalSize; + + return GPITrue; + + GSI_UNUSED(bufferLen); +} + +static GPIBool gpiHandleSendReply(GPConnection* connection, + GPITransfer* transfer, const char* headers, + const char* buffer, int bufferLen) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg* arg; + char intValue[16]; + int version; + int result; + + if (!transfer->sender) + return GPIFalse; + + // Check the version. + //////////////////// + if (!gpiValueForKey(headers, "\\version\\", intValue, sizeof(intValue))) + return GPIFalse; + version = atoi(intValue); + if (version < 1) + return GPIFalse; + + // Get the result. + ////////////////// + if (!gpiValueForKey(headers, "\\result\\", intValue, sizeof(intValue))) + return GPIFalse; + result = atoi(intValue); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + if (result == GPI_ACCEPTED) + arg->type = GP_TRANSFER_ACCEPTED; + else if (result == GPI_REJECTED) + arg->type = GP_TRANSFER_REJECTED; + else + arg->type = GP_TRANSFER_NOT_ACCEPTING; + + arg->message = goastrdup(buffer); + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Update transfer state if accepted. + ///////////////////////////////////// + if (result == GPI_ACCEPTED) { + transfer->state = GPITransferTransferring; + transfer->currentFile = 0; + } + + return GPITrue; + + GSI_UNUSED(bufferLen); +} + +static GPIBool gpiHandleBegin(GPConnection* connection, GPITransfer* transfer, + const char* headers) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg* arg; + GPIFile* file; + char intValue[16]; + int fileIndex; + int size; + unsigned int mtime; + char buffer[FILENAME_MAX]; + int count; + + if (transfer->sender) + return GPIFalse; + + // Get the file. + //////////////// + if (!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if ((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + if (fileIndex != transfer->currentFile) + return GPIFalse; + file = (GPIFile*)ArrayNth(transfer->files, fileIndex); + + // Is this a directory? + /////////////////////// + if (file->flags & GPI_FILE_DIRECTORY) + return GPIFalse; + + // Get the size. + //////////////// + if (!gpiValueForKey(headers, "\\size\\", intValue, sizeof(intValue))) + return GPIFalse; + size = atoi(intValue); + if (size < 0) + return GPIFalse; + + // Update the total size. + ///////////////////////// + transfer->totalSize -= file->size; + transfer->totalSize += size; + + // Get the mod time. + //////////////////// + if (!gpiValueForKey(headers, "\\mtime\\", intValue, sizeof(intValue))) + return GPIFalse; + mtime = (unsigned int)strtoul(intValue, NULL, 10); + + // Set file stuff. + ////////////////// + MD5Init(&file->md5); + file->modTime = mtime; + file->size = size; + + // Setup the temp path. + /////////////////////// + count = 0; + do { + sprintf(buffer, "%sgpt_%d_%d_%d.gpt", transfer->baseDirectory, + transfer->localID, fileIndex, rand()); + file->file = fopen(buffer, "wb"); + count++; + } while (!file->file && (count < 5)); + + // Copy off the path. + ///////////////////// + if (file->file) { + file->path = goastrdup(buffer); + if (!file->path) + return GPIFalse; + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = fileIndex; + if (file->file) { + arg->type = GP_FILE_BEGIN; + } else { + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_WRITE_ERROR; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Did it fail? + /////////////// + if (!file->file) { + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_WRITE_ERROR); + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_WRITE_ERROR; + } + + return GPITrue; +} + +static GPIBool gpiHandleEnd(GPConnection* connection, GPITransfer* transfer, + const char* headers) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg* arg; + GPIFile* file; + GPIBool md5Failed = GPITrue; + unsigned char rawMD5[16]; + char localMD5[33]; + char remoteMD5[33]; + char intValue[16]; + int fileIndex; + + if (transfer->currentFile == -1) + return GPIFalse; + + // Check the file index. + //////////////////////// + if (!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if ((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + if (fileIndex != transfer->currentFile) + return GPITrue; + + // Get the current file. + //////////////////////// + file = (GPIFile*)ArrayNth(transfer->files, transfer->currentFile); + + // Sender? + ////////// + if (transfer->sender) { +#ifdef GPI_CONFIRM_FILES + // We should be waiting for confirmation. + ///////////////////////////////////////// + assert(file->flags & GPI_FILE_CONFIRMING); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_END; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Done with the file. + ////////////////////// + file->flags &= ~GPI_FILE_CONFIRMING; + file->flags |= GPI_FILE_COMPLETED; + transfer->currentFile++; +#endif + return GPITrue; + } + + // Is this a directory? + /////////////////////// + if (file->flags & GPI_FILE_DIRECTORY) { + // Directory completed. + /////////////////////// + file->flags |= GPI_FILE_COMPLETED; + } else { + // Check the file. + ////////////////// + assert(file->file); + if (!file->file) + return GPIFalse; + + // Get the remote md5. + ////////////////////// + if (!gpiValueForKey(headers, "\\md5\\", remoteMD5, sizeof(remoteMD5))) + return GPIFalse; + + // Get the local md5. + ///////////////////// + MD5Final(rawMD5, &file->md5); + MD5Print(rawMD5, localMD5); + + // Check the md5. + ///////////////// + md5Failed = (memcmp(localMD5, remoteMD5, 32) != 0) ? GPITrue : GPIFalse; + + // Set the state. + ///////////////// + if (md5Failed) { + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_DATA_ERROR; + } else + file->flags |= GPI_FILE_COMPLETED; + + // Close the file. + ////////////////// + fclose(file->file); + file->file = NULL; + + // If the md5 failed, remove the file. + ////////////////////////////////////// + if (md5Failed) + remove(file->path); + +#ifdef GPI_CONFIRM_FILES + // Send a confirmation. + /////////////////////// + if (gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_END, + (GPITransferID_st)&transfer->transferID) != + GP_NO_ERROR) + return GPIFalse; + gpiFinishTransferMessage(connection, transfer, NULL, 0); +#endif + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + if (file->flags & GPI_FILE_DIRECTORY) { + arg->type = GP_FILE_DIRECTORY; + } else if (md5Failed) { + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_DATA_ERROR; + } else { + arg->type = GP_FILE_END; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Next file. + ///////////// + transfer->currentFile++; + + // Done? + //////// + if (transfer->currentFile == ArrayLength(transfer->files)) { + // The transfer is complete. + //////////////////////////// + transfer->state = GPITransferComplete; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_DONE; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + + return GPITrue; +} + +static GPIBool gpiHandleData(GPConnection* connection, GPITransfer* transfer, + const char* headers, const char* buffer, + int bufferLen) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg* arg; + GPIFile* file; + GPIBool writeFailed; + char intValue[16]; + int fileIndex; + + if (transfer->currentFile == -1) + return GPIFalse; + + // Check the file index. + //////////////////////// + if (!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if ((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + if (fileIndex != transfer->currentFile) + return GPITrue; + + // Get the current file. + //////////////////////// + file = (GPIFile*)ArrayNth(transfer->files, transfer->currentFile); + + // Is this a directory? + /////////////////////// + if (file->flags & GPI_FILE_DIRECTORY) + return GPIFalse; + +#ifdef GPI_ACKNOWLEDGED_WINDOW + // Sender? + ////////// + if (transfer->sender) { + char intValue[16]; + + // Get the progress. + //////////////////// + if (!gpiValueForKey(headers, "\\pro\\", intValue, sizeof(intValue))) + return GPIFalse; + file->acknowledged = atoi(intValue); + + return GPITrue; + } +#endif + + // Check the file. + ////////////////// + assert(file->file); + if (!file->file) + return GPIFalse; + + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_RawDump, + "HNDLDATA(PT): %d\n", bufferLen); + + // Write the data. + ////////////////// + writeFailed = + (GPIBool)(fwrite(buffer, 1, bufferLen, file->file) != (size_t)bufferLen); + if (writeFailed) { + // Flag the errors. + /////////////////// + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_WRITE_ERROR; + + // Remove the file. + /////////////////// + fclose(file->file); + file->file = NULL; + remove(file->path); + } else { + // Update the MD5. + /////////////////// + MD5Update(&file->md5, (unsigned char*)buffer, bufferLen); + + // Update the progress. + /////////////////////// + file->progress += bufferLen; + transfer->progress += bufferLen; + +#ifdef GPI_ACKNOWLEDGED_WINDOW + // Send an acknowledgment. + ////////////////////////// + if (gpiPeerStartTransferMessage( + connection, transfer->peer, GPI_BM_FILE_DATA, + (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR) + return GPIFalse; + gpiSendOrBufferString(connection, transfer->peer, "\\pro\\"); + gpiSendOrBufferInt(connection, transfer->peer, file->progress); + gpiFinishTransferMessage(connection, transfer, NULL, 0); +#endif + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + if (!writeFailed) { + arg->type = GP_FILE_PROGRESS; + arg->num = file->progress; + } else { + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_WRITE_ERROR; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Did it fail? + /////////////// + if (writeFailed) { + // Skip the file. + ///////////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_WRITE_ERROR); + } + + return GPITrue; +} + +static GPIBool gpiHandleSkip(GPConnection* connection, GPITransfer* transfer, + const char* headers) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg* arg; + GPIFile* file; + char intValue[16]; + int fileIndex; + int reason; + + // Get the file. + //////////////// + if (!gpiValueForKey(headers, "\\file\\", intValue, sizeof(intValue))) + return GPIFalse; + fileIndex = atoi(intValue); + if ((fileIndex < 0) || (fileIndex >= ArrayLength(transfer->files))) + return GPIFalse; + file = (GPIFile*)ArrayNth(transfer->files, fileIndex); + + // Get the reason. + ////////////////// + if (!gpiValueForKey(headers, "\\reason\\", intValue, sizeof(intValue))) + return GPIFalse; + reason = atoi(intValue); + + // Is it not the current file? + ////////////////////////////// + if (fileIndex != transfer->currentFile) { + // Check if we already finished this file. + ////////////////////////////////////////// + if (fileIndex < transfer->currentFile) + return GPIFalse; + + // Mark it for skipping later. + ////////////////////////////// + if (reason == GPI_SKIP_USER_SKIP) { + file->flags |= GPI_FILE_SKIP; + } else { + file->flags |= GPI_FILE_FAILED; + if (reason == GPI_SKIP_READ_ERROR) + file->reason = GP_FILE_READ_ERROR; + else + file->reason = GP_FILE_WRITE_ERROR; + } + + return GPITrue; + } + + // Delete the file if its already opened. + ///////////////////////////////////////// + if (!transfer->sender && file->file) { + fclose(file->file); + file->file = NULL; + remove(file->path); + } + + // Next file. + ///////////// + transfer->currentFile++; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = fileIndex; + if (reason == GPI_SKIP_USER_SKIP) { + arg->type = GP_FILE_SKIP; + } else { + arg->type = GP_FILE_FAILED; + if (reason == GPI_SKIP_READ_ERROR) + arg->num = GP_FILE_READ_ERROR; + else + arg->num = GP_FILE_WRITE_ERROR; + } + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GPITrue; +} + +static GPIBool gpiHandleTransferThrottle(GPConnection* connection, + GPITransfer* transfer, + const char* headers) { + GPIConnection* iconnection = (GPIConnection*)*connection; + int throttle; + char intValue[16]; + GPTransferCallbackArg* arg; + + // Get the throttle. + //////////////////// + if (!gpiValueForKey(headers, "\\rate\\", intValue, sizeof(intValue))) + return GPIFalse; + throttle = atoi(intValue); + + // Store the throttle. + ////////////////////// + transfer->throttle = throttle; + + // If we're the sender, send this back. + /////////////////////////////////////// + if (transfer->sender) { + if (gpiPeerStartTransferMessage( + connection, transfer->peer, GPI_BM_FILE_TRANSFER_THROTTLE, + (GPITransferID_st)&transfer->transferID) != GP_NO_ERROR) + return GPIFalse; + gpiSendOrBufferString(connection, transfer->peer, "\\rate\\"); + gpiSendOrBufferInt(connection, transfer->peer, throttle); + gpiFinishTransferMessage(connection, transfer, NULL, 0); + } + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_THROTTLE; + arg->num = throttle; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GPITrue; +} + +static GPIBool gpiHandleTransferCancel(GPConnection* connection, + GPITransfer* transfer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg* arg; + + // if(transfer->sender) + // return GPIFalse; + + // Mark the transfer cancelled. + /////////////////////////////// + transfer->state = GPITransferCancelled; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_CANCELLED; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + return GPITrue; +} + +static GPIBool gpiHandleTransferKeepalive(GPConnection* connection, + GPITransfer* transfer) { + GSI_UNUSED(connection); + GSI_UNUSED(transfer); + + // Ignore keep-alive. + ///////////////////// + return GPITrue; +} + +static GPResult gpiSendFileEnd(GPConnection* connection, GPITransfer* transfer, + GPIFile* file) { + CHECK_RESULT( + gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_END, + (GPITransferID_st)&transfer->transferID)); + + // Add the file index. + ////////////////////// + gpiSendOrBufferStringLenToPeer(connection, transfer->peer, "\\file\\", 6); + gpiSendOrBufferInt(connection, transfer->peer, transfer->currentFile); + + // Only add the MD5 for files. + ////////////////////////////// + if (!(file->flags & GPI_FILE_DIRECTORY)) { + unsigned char md5Raw[16]; + char md5[33]; + + // Get the MD5. + /////////////// + MD5Final(md5Raw, &file->md5); + MD5Print(md5Raw, md5); + + // Add it. + ////////// + gpiSendOrBufferString(connection, transfer->peer, "\\md5\\"); + gpiSendOrBufferString(connection, transfer->peer, md5); + } + + gpiFinishTransferMessage(connection, transfer, NULL, 0); + + return GP_NO_ERROR; +} + +static GPResult gpiSendFileBegin(GPConnection* connection, + GPITransfer* transfer, GPIFile* file) { + char buffer[64]; + + // Get the file info. + ///////////////////// + if (!gpiGetTransferFileInfo(file->file, &file->size, &file->modTime)) + Error(connection, GP_PARAMETER_ERROR, "Can't get info on file."); + + CHECK_RESULT( + gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_BEGIN, + (GPITransferID_st)&transfer->transferID)); + sprintf(buffer, "\\file\\%d\\size\\%d\\mtime\\%u", transfer->currentFile, + file->size, (unsigned int)file->modTime); + gpiSendOrBufferString(connection, transfer->peer, buffer); + gpiFinishTransferMessage(connection, transfer, NULL, 0); + + return GP_NO_ERROR; +} + +static GPResult gpiSendFileData(GPConnection* connection, GPITransfer* transfer, + unsigned char* data, size_t len) { + CHECK_RESULT( + gpiPeerStartTransferMessage(connection, transfer->peer, GPI_BM_FILE_DATA, + (GPITransferID_st)&transfer->transferID)); + + // Add the file index. + ////////////////////// + gpiSendOrBufferStringLenToPeer(connection, transfer->peer, "\\file\\", 6); + gpiSendOrBufferInt(connection, transfer->peer, transfer->currentFile); + + gpiFinishTransferMessage(connection, transfer, (char*)data, len); + + return GP_NO_ERROR; +} + +GPResult gpiProcessCurrentFile(GPConnection* connection, + GPITransfer* transfer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPIFile* file; + GPTransferCallbackArg* arg; + size_t num; + int i; + int total; + + assert(transfer->currentFile >= 0); + assert(transfer->currentFile < ArrayLength(transfer->files)); + + // Get the current file. + //////////////////////// + file = (GPIFile*)ArrayNth(transfer->files, transfer->currentFile); + + assert(!(file->flags & GPI_FILE_FAILED)); + +#ifdef GPI_CONFIRM_FILES + // If it's being confirmed, just wait. + ////////////////////////////////////// + if (file->flags & GPI_FILE_CONFIRMING) + return GP_NO_ERROR; +#endif + + // Check if its been marked for skipping. + ///////////////////////////////////////// + if (file->flags & GPI_FILE_SKIP) { + // Skip it. + /////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_USER_SKIP); + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_SKIP; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } else { + // Is this a directory? + /////////////////////// + if (file->flags & GPI_FILE_DIRECTORY) { + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_DIRECTORY; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, + GPI_ADD_TRANSFER_CALLBACK); + } + + // Send the end. + //////////////// + gpiSendFileEnd(connection, transfer, file); + file->flags |= GPI_FILE_COMPLETED; + transfer->currentFile++; + } else { + static char buffer[GPI_DATA_SIZE]; + + // Open the file if we need to. + /////////////////////////////// + if (!file->file) { + // Open it. + /////////// + file->file = fopen(file->path, "rb"); + if (file->file) { + // Send the begin. + ////////////////// + CHECK_RESULT(gpiSendFileBegin(connection, transfer, file)); + + // Init the md5. + //////////////// + MD5Init(&file->md5); + + // Call the callback. + ///////////////////// + arg = + (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_BEGIN; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, + NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } else { + // Call the callback. + ///////////////////// + arg = + (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_FAILED; + arg->num = GP_FILE_READ_ERROR; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, + NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Failed to open. + ////////////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_READ_ERROR); + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_READ_ERROR; + + return GP_NO_ERROR; + } + } + + // TODO: THROTTLING + + // Send until done, and while messages are actually being sent. + /////////////////////////////////////////////////////////////// + total = 0; + for (i = 0; (file->progress < file->size) && + !transfer->peer->outputBuffer.len /*&& (i < 20)*/; + i++) { +#ifdef GPI_ACKNOWLEDGED_WINDOW + // Don't get too far ahead. + /////////////////////////// + if ((file->acknowledged + GPI_ACKNOWLEDGED_WINDOW) < file->progress) + break; +#endif + + // Read data. + ///////////// + num = fread(buffer, 1, sizeof(buffer), file->file); + if (num) { + // Update the md5. + ////////////////// + MD5Update(&file->md5, (unsigned char*)buffer, num); + + // Send the data. + ///////////////// + CHECK_RESULT(gpiSendFileData(connection, transfer, + (unsigned char*)buffer, num)); + + // Update progress. + /////////////////// + transfer->progress += num; + file->progress += num; + total += num; + + // Call the callback. + ///////////////////// + arg = + (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_PROGRESS; + arg->num = file->progress; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, + NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } + + // Did we not get to the end? + ///////////////////////////// + if ((num < sizeof(buffer)) && (file->progress != file->size)) { + // Failed reading. + ////////////////// + gpiSkipCurrentFile(connection, transfer, GPI_SKIP_READ_ERROR); + file->flags |= GPI_FILE_FAILED; + file->reason = GP_FILE_READ_ERROR; + + return GP_NO_ERROR; + } + } + + if (total) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_File, GSIDebugLevel_RawDump, + "SENTTOTL(PT): %d\n", total); + } + + // Did we finish the file? + ////////////////////////// + if (file->progress == file->size) { + // Close the file. + ////////////////// + fclose(file->file); + file->file = NULL; + + // Send the end. + //////////////// + gpiSendFileEnd(connection, transfer, file); + +#ifdef GPI_CONFIRM_FILES + // Wait for the confirmation. + ///////////////////////////// + file->flags |= GPI_FILE_CONFIRMING; +#else + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->index = transfer->currentFile; + arg->type = GP_FILE_END; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, + NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Done with the file. + ////////////////////// + file->flags |= GPI_FILE_COMPLETED; + transfer->currentFile++; +#endif + } + } + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessTransfer(GPConnection* connection, GPITransfer* transfer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + int currentFile; + int len; + GPTransferCallbackArg* arg; + unsigned long now; + + // We only process sending transfers. + ///////////////////////////////////// + if (!transfer->sender) + return GP_NO_ERROR; + + // Is the transfer finished? + //////////////////////////// + if (transfer->state >= GPITransferComplete) + return GP_NO_ERROR; + + // Get the time. + //////////////// + now = current_time(); + + // Check for no peer connection established. + //////////////////////////////////////////// + if (!transfer->peer) { + // If its been too long, the person probably isn't really online. + ///////////////////////////////////////////////////////////////// + if ((now - transfer->lastSend) > GPI_PEER_TIMEOUT_TIME) { + GPTransferCallbackArg* arg; + + // We couldn't connect. + /////////////////////// + transfer->state = GPITransferNoConnection; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_NO_CONNECTION; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, + GPI_ADD_TRANSFER_CALLBACK); + } + + return GP_NO_ERROR; + } + } else { + // Check for inactivity. + //////////////////////// + if ((now - transfer->lastSend) > GPI_KEEPALIVE_TIME) { + // Send a keepalive. + //////////////////// + CHECK_RESULT(gpiPeerStartTransferMessage( + connection, transfer->peer, GPI_BM_FILE_TRANSFER_KEEPALIVE, + (GPITransferID_st)&transfer->transferID)); + gpiFinishTransferMessage(connection, transfer, NULL, 0); + } + } + + // If we're paused, there's nothing else to do. + /////////////////////////////////////////////// + if (transfer->throttle == 0) + return GP_NO_ERROR; + + // Don't send files if we're not transfering yet. + ////////////////////////////////////////////////// + if (transfer->state < GPITransferTransferring) + return GP_NO_ERROR; + + // Don't send files if we have regular messages pending. + //////////////////////////////////////////////////////// + if (ArrayLength(transfer->peer->messages)) + return GP_NO_ERROR; + + // Process the current file. + //////////////////////////// + len = ArrayLength(transfer->files); + while (transfer->currentFile < len) { + currentFile = transfer->currentFile; + CHECK_RESULT(gpiProcessCurrentFile(connection, transfer)); + if (currentFile == transfer->currentFile) + break; + } + + // Did we finish? + ///////////////// + if (transfer->currentFile == len) { + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_DONE; + gpiAddCallback(connection, iconnection->callbacks[GPI_TRANSFER_CALLBACK], + arg, NULL, GPI_ADD_TRANSFER_CALLBACK); + } + + // Mark it as complete. + /////////////////////// + transfer->state = GPITransferComplete; + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessTransfers(GPConnection* connection) { + GPIConnection* iconnection = (GPIConnection*)*connection; + int len; + int i; + GPITransfer* transfer; + + // Go through each transfer. + //////////////////////////// + len = ArrayLength(iconnection->transfers); + for (i = 0; i < len; i++) { + // Get the transfer. + //////////////////// + transfer = (GPITransfer*)ArrayNth(iconnection->transfers, i); + + // Process it. + ////////////// + gpiProcessTransfer(connection, transfer); + } + + return GP_NO_ERROR; +} + +GPIBool gpiGetTransferFileInfo(FILE* file, int* size, gsi_time* modTime) { + if (fseek(file, 0, SEEK_END) != 0) + return GPIFalse; + + *size = (int)ftell(file); + if (*size == -1) + return GPIFalse; + + *modTime = 0; + + fseek(file, 0, SEEK_SET); + + return GPITrue; +} + +void gpiTransferPeerDestroyed(GPConnection* connection, GPIPeer* peer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPTransferCallbackArg* arg; + GPITransfer* transfer; + int i; + int len; + + // Search for transfers that use this peer. + /////////////////////////////////////////// + len = ArrayLength(iconnection->transfers); + for (i = 0; i < len; i++) { + transfer = (GPITransfer*)ArrayNth(iconnection->transfers, i); + + if (transfer->peer == peer) { + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_LOST_CONNECTION; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, NULL, + GPI_ADD_TRANSFER_CALLBACK); + } + + // So long tranfer. + /////////////////// + transfer->state = GPITransferNoConnection; + } + } +} + +void gpiTransfersHandlePong(GPConnection* connection, GPProfile profile, + GPIPeer* peer) { + GPIConnection* iconnection = (GPIConnection*)*connection; + GPITransfer* transfer; + int i; + int len; + + // Go through all the transfers. + //////////////////////////////// + len = ArrayLength(iconnection->transfers); + for (i = 0; i < len; i++) { + // Get this transfer. + ///////////////////// + transfer = (GPITransfer*)ArrayNth(iconnection->transfers, i); + assert(transfer); + + // Is it waiting on a pong from this profile? + ///////////////////////////////////////////// + if ((transfer->state == GPITransferPinging) && + (transfer->profile == profile)) { + // Did we not get a connection? + /////////////////////////////// + if (!peer) { + GPTransferCallbackArg* arg; + + // We couldn't connect. + /////////////////////// + transfer->state = GPITransferNoConnection; + + // Call the callback. + ///////////////////// + arg = (GPTransferCallbackArg*)gsimalloc(sizeof(GPTransferCallbackArg)); + if (arg) { + memset(arg, 0, sizeof(GPTransferCallbackArg)); + arg->transfer = transfer->localID; + arg->type = GP_TRANSFER_NO_CONNECTION; + gpiAddCallback(connection, + iconnection->callbacks[GPI_TRANSFER_CALLBACK], arg, + NULL, GPI_ADD_TRANSFER_CALLBACK); + } + } else { + // Store the peer we're connected on. + ///////////////////////////////////// + transfer->peer = peer; + + // We're connected, so send our request. + //////////////////////////////////////// + gpiSendTransferRequest(connection, transfer); + + // Waiting for a response. + ////////////////////////// + transfer->state = GPITransferWaiting; + } + } + } +} +#endif + +GPResult gpiSendTransferReply(GPConnection* connection, + const GPITransferID* transferID, GPIPeer* peer, + int result, const char* msg) { + char buffer[32]; + + if (!msg) + msg = ""; + + // Start the message. + ///////////////////// + CHECK_RESULT(gpiPeerStartTransferMessage(connection, peer, + GPI_BM_FILE_SEND_REPLY, transferID)); + + // Add the rest of the headers. + /////////////////////////////// + sprintf(buffer, "\\version\\%d\\result\\%d", GPI_TRANSFER_VERSION, result); + CHECK_RESULT(gpiSendOrBufferString(connection, peer, buffer)); + + // Finish the message. + ////////////////////// + CHECK_RESULT(gpiPeerFinishTransferMessage(connection, peer, msg, -1)); + + return GP_NO_ERROR; +} + +void gpiHandleTransferMessage(GPConnection* connection, GPIPeer* peer, int type, + const char* headers, const char* buffer, + int len) { + char value[64]; + GPITransferID transferID; +#ifndef NOFILE + GPITransfer* transfer; + GPIBool success; +#endif + + // Get the transfer ID. + /////////////////////// + if (!gpiValueForKey(headers, "\\xfer\\", value, sizeof(value))) + return; + if (sscanf(value, "%d %u %u", &transferID.profileid, &transferID.count, + &transferID.time) != 3) + return; + +#ifdef NOFILE + gpiSendTransferReply(connection, &transferID, peer, GPI_NOT_ACCEPTING, NULL); +#else + + // Send request messages don't yet have a transfer object. + ////////////////////////////////////////////////////////// + if (type == GPI_BM_FILE_SEND_REQUEST) { + // Check for not accepting connections. + /////////////////////////////////////// + if (!gpiHandleSendRequest(connection, peer, &transferID, headers, buffer, + len)) + gpiSendTransferReply(connection, &transferID, peer, GPI_NOT_ACCEPTING, + NULL); + + return; + } + + // Find the transfer based on the ID. + ///////////////////////////////////// + transfer = gpiFindTransferByTransferID(connection, &transferID); + if (!transfer || (transfer->peer != peer)) + return; + + // Handle it based on the type. + /////////////////////////////// + switch (type) { + case GPI_BM_FILE_SEND_REPLY: + success = gpiHandleSendReply(connection, transfer, headers, buffer, len); + break; + case GPI_BM_FILE_BEGIN: + success = gpiHandleBegin(connection, transfer, headers); + break; + case GPI_BM_FILE_END: + success = gpiHandleEnd(connection, transfer, headers); + break; + case GPI_BM_FILE_DATA: + success = gpiHandleData(connection, transfer, headers, buffer, len); + break; + case GPI_BM_FILE_SKIP: + success = gpiHandleSkip(connection, transfer, headers); + break; + case GPI_BM_FILE_TRANSFER_THROTTLE: + success = gpiHandleTransferThrottle(connection, transfer, headers); + break; + case GPI_BM_FILE_TRANSFER_CANCEL: + success = gpiHandleTransferCancel(connection, transfer); + break; + case GPI_BM_FILE_TRANSFER_KEEPALIVE: + success = gpiHandleTransferKeepalive(connection, transfer); + break; + default: + success = GPITrue; + } + + // Check if there was a transfer error. + /////////////////////////////////////// + if (!success) + gpiTransferError(connection, transfer); +#endif + + GSI_UNUSED(type); + GSI_UNUSED(buffer); + GSI_UNUSED(len); +} diff --git a/source/gamespy/GP/gpiTransfer.h b/source/gamespy/GP/gpiTransfer.h new file mode 100644 index 000000000..9be8cecec --- /dev/null +++ b/source/gamespy/GP/gpiTransfer.h @@ -0,0 +1,129 @@ +/* +gpiTransfer.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +#define GPI_FILE_DIRECTORY (1 << 1) +#define GPI_FILE_SKIP (1 << 2) +#define GPI_FILE_FAILED (1 << 3) +#define GPI_FILE_COMPLETED (1 << 4) +#define GPI_FILE_CONFIRMING (1 << 5) + +#define GPI_ACCEPTED 0 +#define GPI_REJECTED 1 +#define GPI_NOT_ACCEPTING 2 + +#define GPI_SKIP_READ_ERROR 0 +#define GPI_SKIP_WRITE_ERROR 1 +#define GPI_SKIP_USER_SKIP 2 + +// TYPES +/////// +typedef enum { + GPITransferPinging, + GPITransferWaiting, + GPITransferTransferring, + GPITransferComplete, + GPITransferCancelled, + GPITransferNoConnection +} GPITransferState; + +typedef struct GPITransferID_s { + int profileid; + unsigned int count; + unsigned int time; +} GPITransferID; + +typedef struct { + GPITransferState state; + DArray files; + GPITransferID transferID; + int localID; + GPIBool sender; + GPProfile profile; + GPIPeer* peer; + int currentFile; + int throttle; + char* baseDirectory; + unsigned long lastSend; + char* message; + int totalSize; + int progress; + void* userData; +} GPITransfer; + +typedef struct { + char* path; + char* name; + + int progress; + int size; + int acknowledged; + FILE* file; + int flags; + gsi_time modTime; + MD5_CTX md5; + int reason; +} GPIFile; + +// FUNCTIONS +/////////// +#ifndef NOFILE +GPResult gpiInitTransfers(GPConnection* connection); + +void gpiCleanupTransfers(GPConnection* connection); + +GPResult gpiProcessTransfers(GPConnection* connection); + +GPResult gpiNewSenderTransfer(GPConnection* connection, GPITransfer** transfer, + GPProfile profile); + +void gpiFreeTransfer(GPConnection* connection, GPITransfer* transfer); + +void gpiCancelTransfer(GPConnection* connection, GPITransfer* transfer); + +void gpiTransferError(GPConnection* connection, const GPITransfer* transfer); + +GPITransfer* gpiFindTransferByLocalID(GPConnection* connection, int localID); + +int gpiGetTransferLocalIDByIndex(GPConnection* connection, int index); + +GPIFile* gpiAddFileToTransfer(GPITransfer* transfer, const char* path, + const char* name); + +void gpiSkipFile(GPConnection* connection, GPITransfer* transfer, int file, + int reason); + +void gpiSkipCurrentFile(GPConnection* connection, GPITransfer* transfer, + int reason); + +GPIBool gpiGetTransferFileInfo(FILE* file, int* size, gsi_time* modTime); + +void gpiTransferPeerDestroyed(GPConnection* connection, GPIPeer* peer); + +void gpiTransfersHandlePong(GPConnection* connection, GPProfile profile, + GPIPeer* peer); +#endif + +GPResult gpiSendTransferReply(GPConnection* connection, + const GPITransferID* transferID, GPIPeer* peer, + int result, const char* message); + +void gpiHandleTransferMessage(GPConnection* connection, GPIPeer* peer, int type, + const char* headers, const char* buffer, int len); diff --git a/source/gamespy/GP/gpiUnique.c b/source/gamespy/GP/gpiUnique.c new file mode 100644 index 000000000..1f270351b --- /dev/null +++ b/source/gamespy/GP/gpiUnique.c @@ -0,0 +1,219 @@ +/* +gpiUnique.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" + +// FUNCTIONS +/////////// +static GPResult +gpiSendRegisterUniqueNick(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], int operationid) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\registernick\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\uniquenick\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, uniquenick); + if (cdkey) { + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\cdkey\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, cdkey); + } + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\partnerid\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operationid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiRegisterUniqueNick(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIOperation* operation = NULL; + GPResult result; + + // Add the operation. + ///////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_REGISTER_UNIQUENICK, NULL, + &operation, blocking, callback, param)); + + // Send a request for info. + /////////////////////////// + result = + gpiSendRegisterUniqueNick(connection, uniquenick, cdkey, operation->id); + CHECK_RESULT(result); + + // Process it if blocking. + ////////////////////////// + if (blocking) { + result = gpiProcess(connection, operation->id); + CHECK_RESULT(result); + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessRegisterUniqueNick(GPConnection* connection, + GPIOperation* operation, + const char* input) { + GPICallback callback; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \rn\. + /////////////////////// + if (strncmp(input, "\\rn\\", 4) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if (callback.callback != NULL) { + GPRegisterUniqueNickResponseArg* arg; + arg = (GPRegisterUniqueNickResponseArg*)gsimalloc( + sizeof(GPRegisterUniqueNickResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Registration of cdKey now offered separately from uniquenick +static GPResult gpiSendRegisterCdKey(GPConnection* connection, + const char cdkey[GP_CDKEY_LEN], + int operationid) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Encrypt the cdkey (xor with random values) + const int useAlternateEncoding = 1; + char cdkeyxor[GP_CDKEY_LEN]; + char cdkeyenc[GP_CDKEYENC_LEN]; + int cdkeylen = (int)strlen(cdkey); + int i = 0; + + Util_RandSeed((unsigned long)GP_XOR_SEED); + for (i = 0; i < cdkeylen; i++) { + // XOR each character with the next rand + char aRand = (char)Util_RandInt(0, 0xFF); + cdkeyxor[i] = (char)(cdkey[i] ^ aRand); + } + cdkeyxor[i] = '\0'; + + // Base 64 it (printable chars only) + B64Encode(cdkeyxor, cdkeyenc, (int)cdkeylen, useAlternateEncoding); + + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\registercdkey\\\\sesskey\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, + iconnection->sessKey); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + "\\cdkeyenc\\"); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, cdkeyenc); + // gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, + //"\\partnerid\\"); gpiAppendIntToBuffer(connection, + //&iconnection->outputBuffer, iconnection->partnerID); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\id\\"); + gpiAppendIntToBuffer(connection, &iconnection->outputBuffer, operationid); + gpiAppendStringToBuffer(connection, &iconnection->outputBuffer, "\\final\\"); + + return GP_NO_ERROR; +} + +GPResult gpiRegisterCdKey(GPConnection* connection, + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param) { + GPIOperation* operation = NULL; + GPResult result; + + // Add the operation. + ///////////////////// + CHECK_RESULT(gpiAddOperation(connection, GPI_REGISTER_CDKEY, NULL, &operation, + blocking, callback, param)); + + // Send a request for info. + /////////////////////////// + result = gpiSendRegisterCdKey(connection, cdkey, operation->id); + CHECK_RESULT(result); + + // Process it if blocking. + ////////////////////////// + if (blocking) { + result = gpiProcess(connection, operation->id); + CHECK_RESULT(result); + } + + return GP_NO_ERROR; +} + +GPResult gpiProcessRegisterCdKey(GPConnection* connection, + GPIOperation* operation, const char* input) { + GPICallback callback; + + // Check for an error. + ////////////////////// + if (gpiCheckForError(connection, input, GPITrue)) + return GP_SERVER_ERROR; + + // This should be \rc\. + /////////////////////// + if (strncmp(input, "\\rc\\", 4) != 0) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Unexpected data was received from the server."); + + // Call the callback. + ///////////////////// + callback = operation->callback; + if (callback.callback != NULL) { + GPRegisterCdKeyResponseArg* arg; + arg = (GPRegisterCdKeyResponseArg*)gsimalloc( + sizeof(GPRegisterCdKeyResponseArg)); + if (arg == NULL) + Error(connection, GP_MEMORY_ERROR, "Out of memory."); + + arg->result = GP_NO_ERROR; + + CHECK_RESULT(gpiAddCallback(connection, callback, arg, operation, 0)); + } + + // This operation is complete. + ////////////////////////////// + gpiRemoveOperation(connection, operation); + + return GP_NO_ERROR; +} diff --git a/source/gamespy/GP/gpiUnique.h b/source/gamespy/GP/gpiUnique.h new file mode 100644 index 000000000..e1e5a4225 --- /dev/null +++ b/source/gamespy/GP/gpiUnique.h @@ -0,0 +1,37 @@ +/* +gpiUnique.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// FUNCTIONS +/////////// +GPResult gpiRegisterUniqueNick(GPConnection* connection, + const char uniquenick[GP_UNIQUENICK_LEN], + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiProcessRegisterUniqueNick(GPConnection* connection, + GPIOperation* operation, + const char* input); + +// Seperated registration of unique nick and cdkey +GPResult gpiRegisterCdKey(GPConnection* connection, + const char cdkey[GP_CDKEY_LEN], GPEnum blocking, + GPCallback callback, void* param); + +GPResult gpiProcessRegisterCdKey(GPConnection* connection, + GPIOperation* operation, const char* input); diff --git a/source/gamespy/GP/gpiUtility.c b/source/gamespy/GP/gpiUtility.c new file mode 100644 index 000000000..bccb9f4a0 --- /dev/null +++ b/source/gamespy/GP/gpiUtility.c @@ -0,0 +1,331 @@ +/* +gpiUtility.c +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +// INCLUDES +////////// +#include "gpi.h" +#include +#include +#include +#include + +// DEFINES +///////// +#define OUTPUT_MAX_COL 100 + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio +// Level4, "conditional expression is constant". +// Occurs with use of the MS provided macro FD_SET +#pragma warning(disable : 4127) +#endif // _MSC_VER + +// FUNCTIONS +/////////// +void strzcpy(char* dest, const char* src, size_t len) { + assert(dest != NULL); + assert(src != NULL); + + strncpy(dest, src, len); + dest[len - 1] = '\0'; +} + +GPIBool gpiCheckForError(GPConnection* connection, const char* input, + GPIBool callErrorCallback) { + char buffer[16]; + GPIConnection* iconnection = (GPIConnection*)*connection; + + if (strncmp(input, "\\error\\", 7) == 0) { + // Get the err code. + //////////////////// + if (gpiValueForKey(input, "\\err\\", buffer, sizeof(buffer))) + iconnection->errorCode = (GPErrorCode)atoi(buffer); + + // Get the error string. + //////////////////////// + if (!gpiValueForKey(input, "\\errmsg\\", iconnection->errorString, + sizeof(iconnection->errorString))) + iconnection->errorString[0] = '\0'; + + // Call the error callback? + /////////////////////////// + if (callErrorCallback) { + GPIBool fatal = (GPIBool)(strstr(input, "\\fatal\\") != NULL); + gpiCallErrorCallback(connection, GP_SERVER_ERROR, + fatal ? GP_FATAL : GP_NON_FATAL); + } + + return GPITrue; + } + + return GPIFalse; +} + +GPIBool gpiValueForKeyWithIndex(const char* command, const char* key, + int* index, char* value, int len) { + char delimiter; + const char* start; + int i; + char c; + + // Check for NULL. + ////////////////// + assert(command != NULL); + assert(key != NULL); + assert(value != NULL); + assert(len > 0); + + // Find which char is the delimiter. + //////////////////////////////////// + delimiter = key[0]; + + // Find the key - first navigate to the index + ///////////////////////////////////////////// + command += *index; + start = strstr(command, key); + if (start == NULL) + return GPIFalse; + + // Get to the start of the value. + ///////////////////////////////// + start += strlen(key); + + // Copy in the value. + ///////////////////// + len--; + for (i = 0; (i < len) && ((c = start[i]) != '\0') && (c != delimiter); i++) { + value[i] = c; + } + value[i] = '\0'; + + // Copy back current end point for index + //////////////////////////////////////// + *index += ((start - command) + strlen(value)); + + return GPITrue; +} + +GPIBool gpiValueForKey(const char* command, const char* key, char* value, + int len) { + char delimiter; + const char* start; + int i; + char c; + + // Check for NULL. + ////////////////// + assert(command != NULL); + assert(key != NULL); + assert(value != NULL); + assert(len > 0); + + // Find which char is the delimiter. + //////////////////////////////////// + delimiter = key[0]; + + // Find the key. + //////////////// + start = strstr(command, key); + if (start == NULL) + return GPIFalse; + + // Get to the start of the value. + ///////////////////////////////// + start += strlen(key); + + // Copy in the value. + ///////////////////// + len--; + for (i = 0; (i < len) && ((c = start[i]) != '\0') && (c != delimiter); i++) { + value[i] = c; + } + value[i] = '\0'; + + return GPITrue; +} + +char* gpiValueForKeyAlloc(const char* command, const char* key) { + char delimiter; + const char* start; + char c; + char* value; + int len; + + // Check for NULL. + ////////////////// + assert(command != NULL); + assert(key != NULL); + + // Find which char is the delimiter. + //////////////////////////////////// + delimiter = key[0]; + + // Find the key. + //////////////// + start = strstr(command, key); + if (start == NULL) + return NULL; + + // Get to the start of the value. + ///////////////////////////////// + start += strlen(key); + + // Find the key length. + /////////////////////// + for (len = 0; ((c = start[len]) != '\0') && (c != delimiter); len++) { + }; + + // Allocate the value. + ////////////////////// + value = (char*)gsimalloc((unsigned int)len + 1); + if (!value) + return NULL; + + // Copy in the value. + ///////////////////// + memcpy(value, start, (unsigned int)len); + value[len] = '\0'; + + return value; +} + +GPResult gpiCheckSocketConnect(GPConnection* connection, SOCKET sock, + int* state) { + int aWriteFlag = 0; + int aExceptFlag = 0; + int aReturnCode = 0; + + // Check if the connect is completed. + ///////////////////////////////////// + aReturnCode = GSISocketSelect(sock, NULL, &aWriteFlag, &aExceptFlag); + if (gsiSocketIsError(aReturnCode)) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_HotError, + "Error connecting\n"); + CallbackFatalError( + connection, GP_NETWORK_ERROR, GP_NETWORK, + "There was an error checking for a completed connection."); + } + + if (aReturnCode > 0) { + // Check for a failed attempt. + ////////////////////////////// + if (aExceptFlag) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, + GSIDebugLevel_HotError, "Connection rejected\n"); + *state = GPI_DISCONNECTED; + return GP_NO_ERROR; + } + + // Check for a successful attempt. + ////////////////////////////////// + if (aWriteFlag) { + gsDebugFormat(GSIDebugCat_GP, GSIDebugType_Network, GSIDebugLevel_Notice, + "Connection accepted\n"); + *state = GPI_CONNECTED; + return GP_NO_ERROR; + } + } + + // Not connected yet. + ///////////////////// + *state = GPI_NOT_CONNECTED; + return GP_NO_ERROR; +} + +GPResult gpiReadKeyAndValue(GPConnection* connection, const char* buffer, + int* index, char key[512], char value[512]) { + int c; + int i; + char* start; + + assert(buffer != NULL); + assert(key != NULL); + assert(value != NULL); + + buffer += *index; + start = (char*)buffer; + + if (*buffer++ != '\\') + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, "Parse Error."); + + for (i = 0; (c = *buffer++) != '\\'; i++) { + if (c == '\0') + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Parse Error."); + if (i == 511) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Parse Error."); + *key++ = (char)c; + } + *key = '\0'; + + for (i = 0; ((c = *buffer++) != '\\') && (c != '\0'); i++) { + if (i == 511) + CallbackFatalError(connection, GP_NETWORK_ERROR, GP_PARSE, + "Parse Error."); + *value++ = (char)c; + } + *value = '\0'; + + *index += (buffer - start - 1); + + return GP_NO_ERROR; +} + +void gpiSetError(GPConnection* connection, GPErrorCode errorCode, + const char* errorString) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Copy the string. + /////////////////// + strzcpy(iconnection->errorString, errorString, GP_ERROR_STRING_LEN); + + // Set the code. + //////////////// + iconnection->errorCode = errorCode; +} + +void gpiSetErrorString(GPConnection* connection, const char* errorString) { + GPIConnection* iconnection = (GPIConnection*)*connection; + + // Copy the string. + /////////////////// + strzcpy(iconnection->errorString, errorString, GP_ERROR_STRING_LEN); +} + +void gpiEncodeString(const char* unencodedString, char* encodedString) { + size_t i; + const int useAlternateEncoding = 1; + + // Encrypt the password (xor with random values) + char passwordxor[GP_PASSWORD_LEN]; + size_t passwordlen = strlen(unencodedString); + + Util_RandSeed((unsigned long)GP_XOR_SEED); + for (i = 0; i < passwordlen; i++) { + // XOR each character with the next rand + char aRand = (char)Util_RandInt(0, 0xFF); + passwordxor[i] = (char)(unencodedString[i] ^ aRand); + } + passwordxor[i] = '\0'; + + // Base 64 it (printable chars only) + B64Encode(passwordxor, encodedString, (int)passwordlen, useAlternateEncoding); +} + +// Re-enable previously disabled compiler warnings +/////////////////////////////////////////////////// +#if defined(_MSC_VER) +#pragma warning(default : 4127) +#endif // _MSC_VER diff --git a/source/gamespy/GP/gpiUtility.h b/source/gamespy/GP/gpiUtility.h new file mode 100644 index 000000000..bb13582bb --- /dev/null +++ b/source/gamespy/GP/gpiUtility.h @@ -0,0 +1,92 @@ +/* +gpiUtility.h +GameSpy Presence SDK +Dan "Mr. Pants" Schoenblum + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +*********************************************************************** +Please see the GameSpy Presence SDK documentation for more information +**********************************************************************/ + +#pragma once + +// INCLUDES +////////// +#include "gpi.h" + +// DEFINES +///////// +// Buffer read size. +//////////////////// +#define GPI_READ_SIZE (16 * 1024) + +// MACROS +//////// +#define freeclear(mem) \ + { \ + gsifree(mem); \ + (mem) = NULL; \ + } + +#define Error(connection, result, string) \ + { \ + gpiSetErrorString(connection, string); \ + return (result); \ + } + +#define CallbackError(connection, result, code, string) \ + { \ + gpiSetError(connection, code, string); \ + gpiCallErrorCallback(connection, result, GP_NON_FATAL); \ + return result; \ + } + +#define CallbackFatalError(connection, result, code, string) \ + { \ + gpiSetError(connection, code, string); \ + gpiCallErrorCallback(connection, result, GP_FATAL); \ + return result; \ + } + +#define CHECK_RESULT(result) \ + { \ + GPResult __result__ = (result); \ + if (__result__ != GP_NO_ERROR) { \ + return __result__; \ + } \ + } + +// FUNCTIONS +/////////// +void strzcpy(char* dest, const char* src, + size_t len // length of buffer, including space for '\0' +); + +void gpiDebug(GPConnection* connection, const char* fmt, ...); + +GPIBool gpiValueForKeyWithIndex(const char* command, const char* key, + int* index, char* value, int len); + +GPIBool gpiValueForKey(const char* command, const char* key, char* value, + int len); + +char* gpiValueForKeyAlloc(const char* command, const char* key); + +GPResult gpiCheckSocketConnect(GPConnection* connection, SOCKET sock, + int* state); + +GPResult gpiReadKeyAndValue(GPConnection* connection, const char* buffer, + int* index, char key[512], char value[512]); + +GPIBool gpiCheckForError(GPConnection* connection, const char* input, + GPIBool callErrorCallback); + +void gpiSetError(GPConnection* connection, GPErrorCode errorCode, + const char* errorString); + +void gpiSetErrorString(GPConnection* connection, const char* errorString); + +void gpiEncodeString(const char* unencodedString, char* encodedString); diff --git a/source/gamespy/common/gsAssert.c b/source/gamespy/common/gsAssert.c new file mode 100644 index 000000000..531119f80 --- /dev/null +++ b/source/gamespy/common/gsAssert.c @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" +#include "gsDebug.h" + +// This is the platform specific default assert condition handler +extern void _gsDebugAssert(const char* string); + +static gsDebugAssertCallback gsDebugAssertHandler = _gsDebugAssert; + +// Call this function to override the default assert handler +// New function should render message / log message based on string passed +void gsDebugAssertCallbackSet(gsDebugAssertCallback theCallback) { + if (theCallback) + gsDebugAssertHandler = theCallback; + else + gsDebugAssertHandler = _gsDebugAssert; +} + +// This is the default assert condition handler +void gsDebugAssert(const char* szError, const char* szText, const char* szFile, + int line) { + char String[256]; + // format into buffer + sprintf(&String[0], szError, szText, szFile, line); + + // call plat specific handler + (*gsDebugAssertHandler)(String); +} + +// **************************************************** +// Todo: move to platform specific modules +void _gsDebugAssert(const char* string) { OSHalt(string); } diff --git a/source/gamespy/common/gsAssert.h b/source/gamespy/common/gsAssert.h new file mode 100644 index 000000000..90cd2c6fe --- /dev/null +++ b/source/gamespy/common/gsAssert.h @@ -0,0 +1,110 @@ +#pragma once + +#if defined(__LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) || \ + defined(c_plusplus) +extern "C" { +#endif +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Assert for GameSpy SDKs +// +// Usage: +// 1) #define _DEBUG to enable assert. This should be set by compiler +// configuration. +// +// Todo: +// Allow user to specify IP to send debug output to (remote log for PS2) + +// GS_ASSERT Use this to trap any programming bugs, such as range checks, +// invalid parameters use at start of each function to check all parameters also +// check all assumptions, ex// assume module is init. +// + +// GS_FAIL() Use instead of GS_ASSERT(0) when reaching an illegal area of +// code +// ex// the default: in a case statement that +// should be completely handled. + +/* + ***The reason for using a GameSpy assert or custom assert*** + + although assert is handled very gracefully on the windows platform, + most consoles do very little of real use during assert. Furthermore, the + program counter is lost, along with the callstack sometimes. By having a + custom critical error function, an asm "break" can be set in it, or a + debugger break point. a call stack is immediately available. The error can + be drawn onto the screen. And the choice to ignore can also be given, in + order to continue stepping through code and further debug. + +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +extern void gsDebugAssert(const char* format, const char* szError, + const char* szFile, int line); +#ifndef _DEBUG +// On non-debug builds, release builds, ignore all of this. +// be carefull never to have function calls within one of these macros, as they +// will be ignored. +// ex// BAD: GS_ASSERT( i== FN()) // FN() will never be called, i +// will never be set in release builds + +#define GS_ASSERT(x) {}; // ex// GS_ASSERT( result == GS_OK ) +#define GS_ASSERT_STR(x, t) \ + {}; // ex// GS_ASSERT_STR( result == GS_OK ,"GSFunction failed") +#define GS_ASSERT_ALIGN_16(x) {}; +#define GS_FAIL() +#define GS_FAIL_STR(x) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#else + +#define GS_ASSERT(x) \ + { \ + if (!(x)) { \ + gsDebugAssert("ASSERT on '" #x "' [%s] in %s line:%d\n", "", __FILE__, \ + __LINE__); \ + } \ + }; +#define GS_ASSERT_STR(x, t) \ + { \ + if (!(x)) { \ + gsDebugAssert("ASSERT on '" #x "' [%s] in %s line:%d\n", t, __FILE__, \ + __LINE__); \ + } \ + }; +#define GS_ASSERT_ALIGN_16(x) \ + { \ + if (((U32)(x)) % 16) { \ + gsDebugAssert("ASSERT on '" #x "' [%s] in %s line:%d\n", \ + "16 byte misalign", __FILE__, __LINE__); \ + } \ + }; +#define GS_FAIL() \ + { gsDebugAssert("FAIL [%s] ln %s line:%d\n", "", __FILE__, __LINE__); }; +#define GS_FAIL_STR(t) \ + { gsDebugAssert("FAIL [%s] ln %s line:%d\n", t, __FILE__, __LINE__); }; + +#endif // GSI_COMMON_DEBUG + +// This is the default assert condition handler +typedef void (*gsDebugAssertCallback)(const char* string); + +// Call this function to override the default assert handler +// New function should render message / log message based on string passed +// calling this with NULL is restores the default setting. +void gsDebugAssertCallbackSet(gsDebugAssertCallback theCallback); + +// This is like an assert, but test at compile, not run time. +// ex use STATIC_CHECK(DIM(array) == enumArrayCount) +#define GS_STATIC_CHECK(expr, msg) \ + { \ + CompileTimeError<((expr) != 0)> ERROR_##msg; \ + (void)ERROR_##msg; \ + } + +#if defined(__LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) || \ + defined(c_plusplus) +} +#endif diff --git a/source/gamespy/common/gsAvailable.c b/source/gamespy/common/gsAvailable.c new file mode 100644 index 000000000..90be0b9c3 --- /dev/null +++ b/source/gamespy/common/gsAvailable.c @@ -0,0 +1,201 @@ +#include "gsAvailable.h" +#include "gsCommon.h" + +#include + +#define PACKET_TYPE 0x09 +#define MASTER_PORT 27900 +#define MAX_RETRIES 1 +#define TIMEOUT_TIME 2000 + +// this is the global var that the SDKs check +// to see if they should communicate with the backend +GSIACResult __GSIACResult; + +// static u32 _unk = 0xfefd0900; + +// this makes the gamename available to all of the SDKs +char __GSIACGamename[64] = {0}; + +// this allows devs to do their own hostname resolution +char GSIACHostname[64] = {0}; + +// used to keep state during the check +static struct { + SOCKET sock; + SOCKADDR_IN address; + char packet[64]; + int packetLen; + gsi_time sendTime; + int retryCount; +} AC; + +static int get_sockaddrin(const char* hostname, int port, SOCKADDR_IN* saddr) { + GS_ASSERT(hostname) + GS_ASSERT(saddr) + + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + saddr->sin_addr.s_addr = inet_addr(hostname); + + if (saddr->sin_addr.s_addr == INADDR_NONE) { + HOSTENT* host = gethostbyname(hostname); + if (!host) + return 0; + saddr->sin_addr.s_addr = *(unsigned int*)host->h_addr_list[0]; + } + + return 1; +} + +static void SendPacket(void) { + sendto(AC.sock, AC.packet, AC.packetLen, 0, (SOCKADDR*)&AC.address, + sizeof(AC.address)); + AC.sendTime = current_time(); +} + +void GSIStartAvailableCheckA(const char* gamename) { + char hostname[64]; + int override_; + int rcode; + int len; + + GS_ASSERT(gamename) + + // store the gamename + strcpy(__GSIACGamename, gamename); + + // clear the sock + AC.sock = INVALID_SOCKET; + + // startup sockets + SocketStartUp(); + + // setup the hostname + override_ = GSIACHostname[0]; + if (!override_) + sprintf(hostname, "%s.available." GSI_DOMAIN_NAME, gamename); + + // get the master address + rcode = get_sockaddrin(override_ ? GSIACHostname : hostname, MASTER_PORT, + &AC.address); + if (!rcode) + return; + + // create the socket + AC.sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (AC.sock == INVALID_SOCKET) + return; + + // setup our packet + AC.packet[0] = PACKET_TYPE; + len = (int)strlen(gamename); + memcpy(AC.packet + 5, gamename, (size_t)len + 1); + AC.packetLen = (len + 6); + + // send it + SendPacket(); + + // no retries yet + AC.retryCount = 0; +} + +static int HandlePacket(char* packet, int len, SOCKADDR_IN* address, + int* disabledservices) { + int bitfield; + + // check the length + if (len < 7) + return 1; + + // check the IP + if (memcmp(&address->sin_addr, &AC.address.sin_addr, sizeof(IN_ADDR)) != 0) + return 1; + + // check the port + if (address->sin_port != AC.address.sin_port) + return 1; + + // check the header + if (memcmp(packet, "\xFE\xFD\x09", 3) != 0) + return 1; + + // read out the bitfield + // read byte-by-byte to avoid alignment issues + bitfield = (int)((packet[3] << 24) & 0xFF000000); + bitfield |= ((packet[4] << 16) & 0x00FF0000); + bitfield |= ((packet[5] << 8) & 0x0000FF00); + bitfield |= (packet[6] & 0x000000FF); + + // set it + *disabledservices = bitfield; + + return 0; +} + +/* +GSIACResult GSIAvailableCheckThink(void) { + char packet[64]; + SOCKADDR_IN address; + int len = sizeof(address); + int rcode; + int disabledservices; + + // if we don't have a sock, possibly because of an initialization error, + // default to available + if (AC.sock == INVALID_SOCKET) { + __GSIACResult = GSIACAvailable; + return __GSIACResult; + } + + // did we get a response? + if (CanReceiveOnSocket(AC.sock)) { + // read it from the socket + rcode = (int)recvfrom(AC.sock, packet, (int)sizeof(packet), 0, + (SOCKADDR*)&address, &len); + + // verify the packet + rcode = HandlePacket(packet, rcode, &address, &disabledservices); + if (rcode == 0) { + // we got a valid response, clean up + closesocket(AC.sock); + + // set the result based on the bit flags + if (disabledservices & 1) + __GSIACResult = GSIACUnavailable; + else if (disabledservices & 2) + __GSIACResult = GSIACTemporarilyUnavailable; + else + __GSIACResult = GSIACAvailable; + + // return it + return __GSIACResult; + } + } + + // check for a timeout + if (current_time() > (AC.sendTime + TIMEOUT_TIME)) { + // check for too many retries + if (AC.retryCount == MAX_RETRIES) { + // default to available + closesocket(AC.sock); + __GSIACResult = GSIACAvailable; + return __GSIACResult; + } + + // send a retry + SendPacket(); + AC.retryCount++; + } + + return GSIACWaiting; +} + +void GSICancelAvailableCheck(void) { + if (AC.sock != INVALID_SOCKET) { + closesocket(AC.sock); + AC.sock = INVALID_SOCKET; + __GSIACResult = GSIACWaiting; + } +} +*/ diff --git a/source/gamespy/common/gsAvailable.h b/source/gamespy/common/gsAvailable.h new file mode 100644 index 000000000..ba28c48de --- /dev/null +++ b/source/gamespy/common/gsAvailable.h @@ -0,0 +1,49 @@ +#pragma once + +#include "gsStringUtil.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define GSIStartAvailableCheck GSIStartAvailableCheckA + +// the available check contacts a backend server at +// ".available.gamespy.com" an app can resolve the hostname itself and +// store the IP here before starting the check +extern char GSIACHostname[64]; + +// these are possible return types for GSIAvailableCheckThink +typedef enum { + GSIACWaiting, // still waiting for a response from the backend + GSIACAvailable, // the game's backend services are available + GSIACUnavailable, // the game's backend services are unavailable + GSIACTemporarilyUnavailable // the game's backend services are temporarily + // unavailable +} GSIACResult; + +// start an available check for a particular game +// return 0 if no error starting up, non-zero if there's an error +void GSIStartAvailableCheckA(const char* gamename); + +// let the available check think +// continue to call this while it returns GSIACWaiting +// if it returns GSIACAvailable, use the GameSpy SDKs as normal +// if it returns GSIACUnavailable or GSIACTemporarilyUnavailable, do NOT +// continue to use the GameSpy SDKs. the backend services are not available +// for the game. in this case, you can show the user a +// message based on the particular result. +GSIACResult GSIAvailableCheckThink(void); + +// this should only be used if the availability check needs to be aborted +// for example, if the player leaves the game's multiplayer area before the +// check completes +void GSICancelAvailableCheck(void); + +// internal use only +extern GSIACResult __GSIACResult; +extern char __GSIACGamename[64]; + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/common/gsCommon.h b/source/gamespy/common/gsCommon.h new file mode 100644 index 000000000..cacdce94b --- /dev/null +++ b/source/gamespy/common/gsCommon.h @@ -0,0 +1,47 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +// Common is more like "all" + +// Build settings (set by developer project) +//#define GS_MEM_MANAGED // use GameSpy memory manager for SDK allocations +//#define GS_COMMON_DEBUG // use GameSpy debug utilities for SDK debug +// output #define GS_WINSOCK2 // use winsock2 #define GS_UNICODE // Use +// widechar (UCS2) SDK interface. #define GS_NO_FILE // disable file +// storage (on by default for PS2/DS) #define GS_NO_THREAD // no +// multi-thread support +//#define GS_PEER // MUST be defined if you are using Peer +// SDK + +#ifdef GS_PEER +#define UNIQUEID // enable unique id support +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" +#include "gsPlatformSocket.h" +#include "gsPlatformThread.h" +#include "gsPlatformUtil.h" + +// platform independent +#include "gsAssert.h" +#include "gsDebug.h" +#include "gsMemory.h" +#include "gsStringUtil.h" + +//#include "md5.h" +//#include "darray.h" +//#include "hashtable.h" + +#define GSI_MIN(a, b) (((a) < (b) ? (a) : (b))) +#define GSI_MAX(a, b) (((a) > (b) ? (a) : (b))) +#define GSI_LIMIT(x, minx, maxx) \ + (((x) < (minx) ? (minx) : ((x) > (maxx) ? (maxx) : (x)))) +#define GSI_WRAP(x, minx, maxx) \ + (((x) < (minx) ? (maxx - 1) : ((x) >= (maxx) ? (minx) : (x)))) +#define GSI_DIM(x) (sizeof(x) / sizeof((x)[0])) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/common/gsCore.c b/source/gamespy/common/gsCore.c new file mode 100644 index 000000000..96e1d4630 --- /dev/null +++ b/source/gamespy/common/gsCore.c @@ -0,0 +1,400 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Core task/callback manager +#include "gsPlatform.h" +#include "gsPlatformThread.h" + +#include "../ghttp/ghttp.h" +#include "gsAssert.h" +#include "gsCommon.h" +#include "gsCore.h" + +// This defines how long the core will wait if there is a thread synchronization +// problem when initializing or shutting down the core. +#define GSI_CORE_INIT_YIELD_MS 100 +#define GSI_CORE_SHUTDOWN_YIELD_MS 50 + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static GSCoreMgr* gsiGetStaticCore() { + static GSCoreMgr gStaticCore; + return &gStaticCore; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// This is registered with the ANSI atexit() function +// - don't do anything that might fail +// - don't do anything that won't complete instantly +// - don't do anything that requires other objects/resources to exist +static void gsiCoreAtExitShutdown(void) { + // delete queue critical section + GSCoreMgr* aCore = gsiGetStaticCore(); + gsiDeleteCriticalSection(&aCore->mQueueCrit); + GSI_UNUSED(aCore); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Increment core ref count, initialize the core if necessary +// - WARNING: This code is a bit tricky do to multithread issues +void gsCoreInitialize() { + GSCoreMgr* aCore = gsiGetStaticCore(); + + // Is someone else shutting down the core? + while (gsi_is_true(aCore->mIsShuttingDown)) + msleep(GSI_CORE_INIT_YIELD_MS); // yield to other thread + + // If we're the first reference, initialize the core + if (gsiInterlockedIncrement(&aCore->mRefCount) == 1) { + // Are we the first ever? + if (gsi_is_false(aCore->mIsStaticInitComplete)) { + // perform one-time initialization of core critical section + gsiInitializeCriticalSection(&aCore->mQueueCrit); + + // register function to destroy critical section at program termination + // TODO + //#ifndef _MANAGED + // atexit(gsiCoreAtExitShutdown); + //#endif + + // one time init completed + aCore->mIsStaticInitComplete = gsi_true; + } + + // take the critical section to begin initialization + // this is necessary in case another thread began shutdown before we + // incremented ref count + gsiEnterCriticalSection(&aCore->mQueueCrit); + gsiLeaveCriticalSection(&aCore->mQueueCrit); + + // wait here if another thread is concurrently shutting down the core + // we may need to wait a few times if the shutdown does not complete + // immediately + while (gsi_is_true(aCore->mIsShuttingDown)) + msleep(GSI_CORE_INIT_YIELD_MS); + +// Setup the task array +#ifdef GSICORE_DYNAMIC_TASK_LIST + aCore->mTaskArray = ArrayNew(sizeof(GSTask*), 10, NULL); + GS_ASSERT(aCore->mTaskArray); +#else + memset(aCore->mTaskArray, 0, sizeof(aCore->mTaskArray)); +#endif + + // Init http sdk (ghttp is ref counted) + ghttpStartup(); + + // release other threads that may have blocked during init + // - this must be the last thing done at end of init + aCore->mIsInitialized = gsi_true; + } else { + // Core is already initialized -OR- another thread will initialize the core + + // make sure critical section has been initialized + while (gsi_is_false(aCore->mIsStaticInitComplete)) + msleep(GSI_CORE_INIT_YIELD_MS); + + // take the critical section + // this is necessary in case another thread began shutdown before we + // incremented ref count + gsiEnterCriticalSection(&aCore->mQueueCrit); + gsiLeaveCriticalSection(&aCore->mQueueCrit); + + // wait for other thread to initial core + while (gsi_is_false(aCore->mIsInitialized)) + msleep(GSI_CORE_INIT_YIELD_MS); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void gsiCoreTaskDispatchCallback(GSTask* theTask, + GSTaskResult theResult) { + if (theTask->mIsCallbackPending) { + theTask->mIsCallbackPending = 0; + if (theTask->mCallbackFunc) + (theTask->mCallbackFunc)(theTask->mTaskData, theResult); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Return values: +// GSTaskResult_InProgress - Keep calling gsCoreTaskThink +// GSTaskResult_Finished - Task memory freed; task object is now invalid +GSTaskResult gsCoreTaskThink(GSTask* theTask) { + GSCoreMgr* aCore = gsiGetStaticCore(); + GSTaskResult aResult = GSTaskResult_None; + + if (theTask == NULL) + return GSTaskResult_Finished; + + // If the task is running let it think (it may be cancelled and still running) + if (theTask->mIsRunning && theTask->mThinkFunc) + aResult = (theTask->mThinkFunc)(theTask->mTaskData); + + // Check for time out + if ((!theTask->mIsCanceled) && (aResult == GSTaskResult_InProgress)) { + if ((theTask->mTimeout != 0) && + (current_time() - theTask->mStartTime > theTask->mTimeout)) { + // Cancel the task... + gsiCoreCancelTask(theTask); + + // ...but trigger callback immediately with "Timed Out" + gsiCoreTaskDispatchCallback(theTask, GSTaskResult_TimedOut); + } + // else + // continue processing it + } else if (aResult != GSTaskResult_InProgress) { + // Note: This section may be triggered multiple times if the cleanup + // function fails. (possibly due to lack of memory) + int i = 0; + gsi_bool removeTask = gsi_true; + + // Call the callback if we haven't already + if (theTask->mIsRunning) { + gsiCoreTaskDispatchCallback(theTask, aResult); + theTask->mIsRunning = 0; + } + + // Call Cleanup hook and remove task + if (theTask->mCleanupFunc) + removeTask = (theTask->mCleanupFunc)(theTask->mTaskData); + + // Remove the task + if (gsi_is_true(removeTask)) { + gsiEnterCriticalSection(&aCore->mQueueCrit); +#ifdef GSICORE_DYNAMIC_TASK_LIST + { + int len = ArrayLength(aCore->mTaskArray); + for (i = 0; i < len; i++) { + if (*(GSTask**)ArrayNth(aCore->mTaskArray, i) == theTask) { + ArrayRemoveAt(aCore->mTaskArray, i); + gsifree(theTask); + break; + } + } + } +#else + for (i = 0; i < GSICORE_MAXTASKS; i++) { + if (aCore->mTaskArray[i] == theTask) { + aCore->mTaskArray[i] = NULL; + gsifree(theTask); + break; + } + } +#endif + gsiLeaveCriticalSection(&aCore->mQueueCrit); + return GSTaskResult_Finished; + } + } + + // Note: This function should always return InProgress until + // the task has been removed from the TaskArray. + // The developer may have already received a completed callback + // while this continue to return InProgress meaning "still needs to be + // pumped" + return GSTaskResult_InProgress; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Optional maximum processing time +// - Pass in 0 to process each task once +void gsCoreThink(gsi_time theMS) { + GSCoreMgr* aCore = gsiGetStaticCore(); + int i = 0; + gsi_time aStartTime = 0; + gsi_i32 allTasksAreDead = 1; + + if (gsi_is_false(aCore->mIsInitialized)) + return; + + // enter queue critical section + gsiEnterCriticalSection(&aCore->mQueueCrit); + + // start timing + aStartTime = current_time(); + +// process all tasks in the queue, dispatch callbacks +// cancelled tasks continue processing until the cancel is acknowledge by the +// task +#ifdef GSICORE_DYNAMIC_TASK_LIST + { + int len = ArrayLength(aCore->mTaskArray); + if (len > 0) + allTasksAreDead = 0; + for (i = (len - 1); i >= 0; i--) { + GSTask* task = *(GSTask**)ArrayNth(aCore->mTaskArray, i); + if (gsi_is_true(task->mAutoThink)) + gsCoreTaskThink(task); + if (theMS != 0 && (current_time() - aStartTime > theMS)) + break; + } + } +#else + for (i = 0; i < GSICORE_MAXTASKS; i++) { + if (aCore->mTaskArray[i] != NULL) { + allTasksAreDead = 0; + + if (aCore->mTaskArray[i]->mAutoThink == gsi_true) + gsCoreTaskThink(aCore->mTaskArray[i]); + } + // Enough time to process another? (if not, break) + if (theMS != 0 && (current_time() - aStartTime > theMS)) + break; + } +#endif + + // shutting down? + if (aCore->mIsShuttingDown && allTasksAreDead) { + ghttpCleanup(); + +#ifdef GSICORE_DYNAMIC_TASK_LIST + if (aCore->mTaskArray) { + ArrayFree(aCore->mTaskArray); + aCore->mTaskArray = NULL; + } +#endif + + aCore->mIsShuttingDown = 0; + } + + // leave queue critical section + gsiLeaveCriticalSection(&aCore->mQueueCrit); + + GSI_UNUSED(theMS); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsCoreShutdown() { + GSCoreMgr* aCore = gsiGetStaticCore(); + int i = 0; + + // If not initialized, just bail + if (gsi_is_false(aCore->mIsInitialized)) + return; + + // Take the critical section to prevent anyone from re-initializing while + // we decide if we need to shutdown + gsiEnterCriticalSection(&aCore->mQueueCrit); + + // If there are other references, just return + if (gsiInterlockedDecrement(&aCore->mRefCount) > 0) { + gsiLeaveCriticalSection(&aCore->mQueueCrit); + return; + } else { + // we released the final reference, begin shutdown + // no other thread will begin using the core until + // mIsShuttingDown has been set back to false + aCore->mIsShuttingDown = gsi_true; + +// Cancel all tasks +#ifdef GSICORE_DYNAMIC_TASK_LIST + { + int len = ArrayLength(aCore->mTaskArray); + for (i = 0; i < len; i++) { + gsiCoreCancelTask(*(GSTask**)ArrayNth(aCore->mTaskArray, i)); + } + } +#else + for (i = 0; i < GSICORE_MAXTASKS; i++) { + if (aCore->mTaskArray[i] != NULL) { + gsiCoreCancelTask(aCore->mTaskArray[i]); + } + } +#endif + gsiLeaveCriticalSection(&aCore->mQueueCrit); + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSCoreValue gsCoreIsShutdown() { + GSCoreMgr* aCore = gsiGetStaticCore(); + + if (gsi_is_true(aCore->mIsShuttingDown)) + return GSCore_SHUTDOWN_PENDING; + if (aCore->mRefCount == 0) + return GSCore_SHUTDOWN_COMPLETE; + + // The core isn't shutting down, and ref count > 0, + // therefore the core is in use + return GSCore_IN_USE; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Adds a GSCoreTask to the execution array +// - Tasks may come from multiple threads +void gsiCoreExecuteTask(GSTask* theTask, gsi_time theTimeoutMs) { + GSCoreMgr* aCore = gsiGetStaticCore(); + + // Bail, if the task has already started + GS_ASSERT(!theTask->mIsRunning); + + // Mark it as started and running + theTask->mIsCallbackPending = 1; + theTask->mIsStarted = 1; + theTask->mIsRunning = 1; + theTask->mTimeout = theTimeoutMs; + theTask->mStartTime = current_time(); + + // Execute the task + if (theTask->mExecuteFunc) + (theTask->mExecuteFunc)(theTask->mTaskData); + + gsiEnterCriticalSection(&aCore->mQueueCrit); +// add it to the process list +#ifdef GSICORE_DYNAMIC_TASK_LIST + ArrayAppend(aCore->mTaskArray, &theTask); +#else + { + int anInsertPos = -1; + int i = 0; + for (i = 0; i < GSICORE_MAXTASKS; i++) { + if (aCore->mTaskArray[i] == NULL) { + anInsertPos = i; + break; + } + } + GS_ASSERT(anInsertPos != -1); // make sure it got in + aCore->mTaskArray[anInsertPos] = theTask; + } +#endif + gsiLeaveCriticalSection(&aCore->mQueueCrit); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// cancelling a task is an *async request* +// A task that doesn't support cancelling, such as a blocking socket operation, +// may complete normally even though it was cancelled. +void gsiCoreCancelTask(GSTask* theTask) { + GSCoreMgr* aCore = gsiGetStaticCore(); + + // Enter critical secction here so the developer + // may cancel a task from any thread. (e.g. The task thread has blocked) + gsiEnterCriticalSection(&aCore->mQueueCrit); + if (theTask->mIsRunning && !theTask->mIsCanceled) { + theTask->mIsCanceled = 1; + if (theTask->mCancelFunc) + (theTask->mCancelFunc)(theTask->mTaskData); + } + gsiLeaveCriticalSection(&aCore->mQueueCrit); + GSI_UNUSED(aCore); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSTask* gsiCoreCreateTask() { + GSTask* aTask = (GSTask*)gsimalloc(sizeof(GSTask)); + if (aTask == NULL) + return NULL; + + memset(aTask, 0, sizeof(GSTask)); + aTask->mAutoThink = gsi_true; + return aTask; +} diff --git a/source/gamespy/common/gsCore.h b/source/gamespy/common/gsCore.h new file mode 100644 index 000000000..c17c7c1ec --- /dev/null +++ b/source/gamespy/common/gsCore.h @@ -0,0 +1,111 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Core task/callback manager +#include "../darray.h" +#include "gsCommon.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GSICORE_DYNAMIC_TASK_LIST +#define GSICORE_MAXTASKS 40 + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef enum { + GSCore_IN_USE, + GSCore_SHUTDOWN_PENDING, + GSCore_SHUTDOWN_COMPLETE +} GSCoreValue; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef enum { + GSTaskResult_None, + GSTaskResult_InProgress, + GSTaskResult_Canceled, + GSTaskResult_TimedOut, + GSTaskResult_Finished +} GSTaskResult; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// delegates (optional, may be NULL) +typedef void (*GSTaskExecuteFunc)(void* theTaskData); +typedef void (*GSTaskCallbackFunc)(void* theTaskData, GSTaskResult theResult); +typedef void (*GSTaskCancelFunc)(void* theTaskData); +typedef gsi_bool (*GSTaskCleanupFunc)(void* theTaskData); // post run cleanup +typedef GSTaskResult (*GSTaskThinkFunc)(void* theTaskData); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// "Private" struct for dispatching tasks. Once tasks have been put in the +// queue they should only be modified from the think thread. +// - When creating a task, you should set only the task data and delegates +typedef struct { + int mId; + gsi_time mTimeout; + gsi_time mStartTime; + gsi_bool mAutoThink; + + // These are not exclusive states (use bit flags?) + gsi_i32 mIsStarted; + gsi_i32 mIsRunning; + gsi_i32 mIsCanceled; + gsi_i32 mIsCallbackPending; // does the task require a callback? + + // delegates + void* mTaskData; + GSTaskExecuteFunc mExecuteFunc; + GSTaskCallbackFunc mCallbackFunc; + GSTaskCancelFunc mCancelFunc; + GSTaskCleanupFunc mCleanupFunc; + GSTaskThinkFunc mThinkFunc; +} GSTask; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct { + gsi_u32 mRefCount; + + gsi_bool volatile mIsStaticInitComplete; // once per application init + gsi_bool volatile mIsInitialized; // gsi_true when ready to use + gsi_bool volatile mIsShuttingDown; // gsi_true when shutting down + + GSICriticalSection mQueueCrit; +#ifdef GSICORE_DYNAMIC_TASK_LIST + DArray mTaskArray; +#else + GSTask* mTaskArray[GSICORE_MAXTASKS]; +#endif + +} GSCoreMgr; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsCoreInitialize(void); +void gsCoreThink(gsi_time theMS); +void gsCoreShutdown(void); +GSCoreValue gsCoreIsShutdown(void); + +GSTaskResult gsCoreTaskThink(GSTask* theTask); +void gsiCoreExecuteTask(GSTask* theTask, gsi_time theTimeoutMs); +void gsiCoreCancelTask(GSTask* theTask); + +GSTask* gsiCoreCreateTask(void); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/common/gsCrypt.h b/source/gamespy/common/gsCrypt.h new file mode 100644 index 000000000..33e412591 --- /dev/null +++ b/source/gamespy/common/gsCrypt.h @@ -0,0 +1,95 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "../md5.h" +#include "gsLargeInt.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// RSA +// +// Based on PKCS #1 v2.1, RSA Laboratories June 14, 2002 +// +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GS_CRYPT_HASHSIZE GS_CRYPT_SHA1_HASHSIZE + +#define GS_CRYPT_SHA1_HASHSIZE 20 +#define GS_CRYPT_MD5_HASHSIZE 16 + +//#define GS_CRYPT_RSA_ES_OAEP +#define GS_CRYPT_RSA_ES_PKCS1v1_5 + +#ifndef GS_CRYPT_RSA_BINARY_SIZE +#define GS_CRYPT_RSA_BINARY_SIZE 1024 +#endif + +#define GS_CRYPT_RSA_BYTE_SIZE (GS_CRYPT_RSA_BINARY_SIZE / 8) // 1024/8 = 128 + +#define GS_CRYPT_RSA_DATABLOCKSIZE \ + (GS_CRYPT_RSA_BYTE_SIZE - GS_CRYPT_HASHSIZE - 1) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct { + gsLargeInt_t modulus; + gsLargeInt_t exponent; +} gsCryptRSAKey; + +typedef struct { + gsi_u8 headerByte; // always 0x00 + gsi_u8 maskedSeed[GS_CRYPT_HASHSIZE]; // not a MD5 hash, but must be same size + gsi_u8 maskedData[GS_CRYPT_RSA_DATABLOCKSIZE]; // data block xor'd +} gsCryptRSAOAEPPacket; + +typedef struct { + gsi_u8 headerByte[2]; // always 0x00 0x02 + gsi_u8 data[GS_CRYPT_RSA_BYTE_SIZE - 2]; // data block xor'd +} gsCryptRSAPKCS1Packet; + +// The cipherText must be equal to GS_CRYPT_RSA_BYTE_SIZE +// The plainText maximum len is: +// OAEP: 62-bytes when using 1024-bit encryption +// (GS_CRYPT_RSA_BYTE_SIZE-2*GS_CRYPT_MD5_HASHSIZE-2) PKCS1: 117-bytes when +// using 1024-bit encryption (GS_CRYPT_RSA_BYTE_SIZE-11) +gsi_i32 +gsCryptRSAEncryptBuffer(const gsCryptRSAKey* publicKey, + const unsigned char* plainText, gsi_u32 len, + unsigned char cipherText[GS_CRYPT_RSA_BYTE_SIZE]); +gsi_i32 gsCryptRSAVerifySignedHash(const gsCryptRSAKey* publicKey, + const unsigned char* hash, gsi_u32 hashLen, + const unsigned char* sig, gsi_u32 sigLen); + +// These require the private key, which only the server should have. Included +// here for test purposes +gsi_i32 +gsCryptRSADecryptBuffer(const gsCryptRSAKey* privateKey, + const unsigned char cipherText[GS_CRYPT_RSA_BYTE_SIZE], + unsigned char* plainTextOut, gsi_u32* lenOut); +// Note: There is a debate on whether or not to sign-first-then-encrypt, or +// encrypt-first-then-sign +// SignFirst: Decryption must take place before the signature is +// validated. This is a high overhead for invalid signatures. +// EncryptFirst: Signature validation takes place before decryption, +// but the MAC is unencrypted to packet sniffers. (Exposes it to +// attack) +// We use SignFirst since it's unlikely a client will receive a invalid +// signature DOS attack. +gsi_i32 gsCryptRSASignData(const gsCryptRSAKey* privateKey, + const unsigned char* plainText, gsi_u32 plainTextLen, + unsigned char* signedDataOut, gsi_u32* lenOut); +gsi_i32 gsCryptRSASignHash(const gsCryptRSAKey* privateKey, + const unsigned char* hash, gsi_u32 hashLen, + unsigned char* signedDataOut, gsi_u32* lenOut); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif diff --git a/source/gamespy/common/gsDebug.c b/source/gamespy/common/gsDebug.c new file mode 100644 index 000000000..43beb0735 --- /dev/null +++ b/source/gamespy/common/gsDebug.c @@ -0,0 +1,293 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsDebug.h" +#include "gsCommon.h" +//#include +//#include + +// THIS FILE ONLY INCLUDED WHEN USING GAMESPY DEBUG FUNCTIONS +// (don't put this above the header includes or VC will whine +#ifdef GSI_COMMON_DEBUG + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Static debug data +static struct GSIDebugInstance gGSIDebugInstance; // simple singleton "class" + +// Line prefixes, e.g. "[ cat][type][ lev] text" +char* gGSIDebugCatStrings[GSIDebugCat_Count] = {" APP", " GP ", "PEER", " QR2", + " SB", " V2", " AD", " NN", + "HTTP", "CDKY", " CMN"}; +char* gGSIDebugTypeStrings[GSIDebugType_Count] = {" NET", "FILE", " MEM", + "STAT", "MISC"}; +char* gGSIDebugLevelStrings[GSIDebugLevel_Count] = { + "*ERR", "****", "----", " ", " ", " ", " ->"}; +char* gGSIDebugLevelDescriptionStrings[8] = { + "None", "", "", "", + "", "", "", ""}; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// utility to convert bit flag back to base (e.g. 1<<2 returns 2) +static gsi_u32 gsiDebugLog2(gsi_u32 theInt) { + gsi_u32 total = 0; + while (theInt > 1) { + theInt = theInt >> 1; + total++; + } + return total; +} + +// default supplied debug function, will receive debug text +// this is platform specific +static void gsiDebugCallback(GSIDebugCategory category, GSIDebugType type, + GSIDebugLevel level, const char* format, + va_list params) { + static char string[256]; + vsprintf(string, format, params); + OSReport(string); + + GSI_UNUSED(category); + GSI_UNUSED(type); + GSI_UNUSED(level); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// process debug output +void gsDebugVaList(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theTokenStr, + va_list theParamList) { + // Retrieve the current debug level + GSIDebugLevel aCurLevel; + + // Verify Parameters + assert(theCat <= GSIDebugCat_Count); + assert(theType <= GSIDebugType_Count); + assert(theLevel <= (1 << GSIDebugLevel_Count)); + assert(theTokenStr); + + // Make thread safe + if (gGSIDebugInstance.mInitialized == 0) { + // Warning: Slight race condition risk here the first time + // gsDebug functions are used. + // The risk is minimal since you usually set + // debug levels and targets at program startup + gGSIDebugInstance.mInitialized = 1; + gsiInitializeCriticalSection(&gGSIDebugInstance.mDebugCrit); + } + + gsiEnterCriticalSection(&gGSIDebugInstance.mDebugCrit); + + // Are we currently logging this type and level? + aCurLevel = gGSIDebugInstance.mGSIDebugLevel[theCat][theType]; + if (aCurLevel & theLevel) // check the flag + { + // Output line prefix + if (gGSIDebugInstance.mGSIDebugFile) { + fprintf(gGSIDebugInstance.mGSIDebugFile, "[%s][%s][%s] ", + gGSIDebugCatStrings[theCat], gGSIDebugTypeStrings[theType], + gGSIDebugLevelStrings[gsiDebugLog2(theLevel)]); + + // Output to file + vfprintf(gGSIDebugInstance.mGSIDebugFile, theTokenStr, theParamList); + } + // Output to developer function if provided + if (gGSIDebugInstance.mDebugCallback != NULL) { + (*gGSIDebugInstance.mDebugCallback)(theCat, theType, theLevel, + theTokenStr, theParamList); + } else { + gsiDebugCallback(theCat, theType, theLevel, theTokenStr, theParamList); + } + } + + gsiLeaveCriticalSection(&gGSIDebugInstance.mDebugCrit); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// process debug output +void gsDebugFormat(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theTokenStr, ...) { + va_list aParameterList; + + // Verify Parameters + assert(theCat <= GSIDebugCat_Count); + assert(theType <= GSIDebugType_Count); + assert(theLevel <= (1 << GSIDebugLevel_Count)); + assert(theTokenStr); + + // Find start of var arg list + va_start(aParameterList, theTokenStr); + + // Pass to VA version + gsDebugVaList(theCat, theType, theLevel, theTokenStr, aParameterList); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Converts binary buffer to memory view form: +// 0000 0000 0000 0000 0000 0000 0000 0000 ................ +static void HexEncode16(const char* theInStream, char* theOutStream, + unsigned int theInLen) { + const int aRowWidth = 64; // width of the output + const char aReplaceChar = '.'; // Replace non print characters + const int aTextOffSet = 41; // text comes after hex bytes + char* aTextOutStream = (theOutStream + aTextOffSet); // set the write ptr + const unsigned int aWriteBit = theInLen & 1; // write on odd or even bytes? + + assert(theInLen <= 16); + + // Set buffer to ' ' + memset(theOutStream, ' ', aRowWidth); + + // Convert characters one at a time + while (theInLen--) { + // Read the next byte + unsigned char aChar = (unsigned char)(*theInStream++); + + // Write one byte in hex form + sprintf(theOutStream, "%02X", aChar); + + // Write the printable character + if (isgraph(aChar)) + *(aTextOutStream++) = (char)aChar; + else + *(aTextOutStream++) = aReplaceChar; + + // Move to next hex byte + theOutStream += 2; + + // Insert a space every other byte + if ((theInLen & 1) == aWriteBit) + *theOutStream++ = ' '; + } + + // Remove NULL terminator from last sprintf + *theOutStream = ' '; + + // NULL terminate the full string + *(aTextOutStream) = '\0'; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Write binary data as B64 bytes (40 bytes per line) +void gsDebugBinary(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theBuffer, + gsi_i32 theLength) { + int aBytesLeft = theLength; + const char* aReadPos = theBuffer; + char aHexStr[80]; + + // convert and display in 40 byte segments + while (aBytesLeft > 0) { + gsi_i32 aBytesToRead = min(aBytesLeft, 16); + + HexEncode16(aReadPos, aHexStr, (unsigned int)aBytesToRead); + gsDebugFormat(theCat, theType, theLevel, " %s\r\n", aHexStr); + + aReadPos += aBytesToRead; + aBytesLeft -= aBytesToRead; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsSetDebugLevel(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel) { + // Verify Parameters + assert(theCat <= GSIDebugCat_Count); + assert(theType <= GSIDebugType_Count); + + // Set for all categories? + if (theCat == GSIDebugCat_Count) { + int i = 0; + for (; i < GSIDebugCat_Count; i++) + gsSetDebugLevel((GSIDebugCategory)i, theType, theLevel); + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_Debug, + "Debug level set to %s for all categories (SDKs)\r\n", + gGSIDebugLevelDescriptionStrings[gsiDebugLog2(theLevel)]); + + return; + } + + // Set for all types? + if (theType == GSIDebugType_Count) { + int i = 0; + for (; i < GSIDebugType_Count; i++) + gsSetDebugLevel(theCat, (GSIDebugType)i, theLevel); + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_Debug, + "Debug level set to %s for all types\r\n", + gGSIDebugLevelDescriptionStrings[gsiDebugLog2(theLevel)]); + return; + } + + // Is the new level different from the old? + if (gGSIDebugInstance.mGSIDebugLevel[theCat][theType] != theLevel) { + // Notify of the change + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, GSIDebugLevel_Comment, + "Changing debug level: [%s][%s][%02X]\r\n", + gGSIDebugCatStrings[theCat], gGSIDebugTypeStrings[theType], + theLevel); + gGSIDebugInstance.mGSIDebugLevel[theCat][theType] = theLevel; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Set the debug output file to an already open file +// Set to "stdout" for console output +void gsSetDebugFile(FILE* theFile) { + if (theFile != gGSIDebugInstance.mGSIDebugFile) { + // If the old file is valid, notify it of the closing + if (gGSIDebugInstance.mGSIDebugFile != NULL) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, + GSIDebugLevel_Comment, "Debug disabled in this file\r\n"); + } + + // If the new file is valid, notify it of the opening + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Misc, + GSIDebugLevel_Comment, "Debug enabled in this file\r\n"); + } + + gGSIDebugInstance.mGSIDebugFile = theFile; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Opens and sets the debug output file +FILE* gsOpenDebugFile(const char* theFileName) { + // The new file + FILE* aFile = NULL; + + // Verify parameters + assert(theFileName != NULL); + + // Open the new file (clear contents) + aFile = fopen(theFileName, "w+"); + if (aFile != NULL) + gsSetDebugFile(aFile); + + return aFile; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Retrieve the current debug file (if any) +FILE* gsGetDebugFile() { return gGSIDebugInstance.mGSIDebugFile; } + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Set the developer callback +void gsSetDebugCallback(GSIDebugCallback theCallback) { + gGSIDebugInstance.mDebugCallback = theCallback; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // GSI_COMMON_DEBUG diff --git a/source/gamespy/common/gsDebug.h b/source/gamespy/common/gsDebug.h new file mode 100644 index 000000000..08c475cea --- /dev/null +++ b/source/gamespy/common/gsDebug.h @@ -0,0 +1,157 @@ +#pragma once +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Advanced debug logging for GameSpy SDKs +// +// Usage: +// 1) #define GSI_COMMON_DEBUG to enable debug output +// 2) Set target output (file or console or custom func) +// 3) Use Debug macros to log output +// +// Todo: +// Allow user to specify IP to send debug output to (remote log for PS2) +//#include "nonport.h" +#include + +#if defined(__LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) || \ + defined(c_plusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Input levels (text is reported at one of these levels) +typedef gsi_u8 GSIDebugLevel; +#define GSIDebugLevel_HotError (GSIDebugLevel)(1 << 0) // 1 Unexpected Error +#define GSIDebugLevel_WarmError (GSIDebugLevel)(1 << 1) // 2 Expected Error +#define GSIDebugLevel_Warning (GSIDebugLevel)(1 << 2) // 4 Warnings and Errors +#define GSIDebugLevel_Notice (GSIDebugLevel)(1 << 3) // 8 Usefull debug info +#define GSIDebugLevel_Comment (GSIDebugLevel)(1 << 4) // 16 Debug spam +#define GSIDebugLevel_RawDump (GSIDebugLevel)(1 << 5) // 32 e.g. MemoryBuffer +#define GSIDebugLevel_StackTrace \ + (GSIDebugLevel)(1 << 6) // 64 Important function entries +// add new ones here (update string table in gsiDebug.c!) +#define GSIDebugLevel_Count 7 // 7 reporting levels + +// Output levels (a mask for the levels you want to receive) +// (update string table in gsiDebug.c!) +#define GSIDebugLevel_None (GSIDebugLevel)(0) // No output +#define GSIDebugLevel_Normal (GSIDebugLevel)(0x07) // Warnings and above +#define GSIDebugLevel_Debug (GSIDebugLevel)(0x0F) // Notice and above +#define GSIDebugLevel_Verbose (GSIDebugLevel)(0x1F) // Comment and above +#define GSIDebugLevel_Hardcore (GSIDebugLevel)(0xFF) // Recv all + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Output types +typedef enum { + GSIDebugType_Network, // Network activity + GSIDebugType_File, // File output + GSIDebugType_Memory, // Memory allocations + GSIDebugType_State, // State update + GSIDebugType_Misc, // None of the above + // add new ones here (update string table in gsiDebug.c!) + + GSIDebugType_Count, + GSIDebugType_All = GSIDebugType_Count +} GSIDebugType; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Debug categories (SDKs) +typedef enum { + GSIDebugCat_App, + GSIDebugCat_GP, + GSIDebugCat_Peer, + GSIDebugCat_QR2, + GSIDebugCat_SB, + GSIDebugCat_Voice, + GSIDebugCat_AD, + GSIDebugCat_NatNeg, + GSIDebugCat_HTTP, + GSIDebugCat_CDKey, + // Add new ones here (update string table in gsiDebug.c!) + + GSIDebugCat_Common, // Common should be last to prevent display weirdness + // resulting from initialization order + GSIDebugCat_Count, + GSIDebugCat_All = GSIDebugCat_Count +} GSIDebugCategory; + +extern char* gGSIDebugCatStrings[GSIDebugCat_Count]; +extern char* gGSIDebugTypeStrings[GSIDebugType_Count]; +extern char* gGSIDebugLevelStrings[GSIDebugLevel_Count]; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Only include static data and functions if GSI_COMMON_DEBUG is defined +#ifndef GSI_COMMON_DEBUG +#define gsDebugFormat +#define gsDebugVaList +#define gsDebugBinary +#define gsSetDebugLevel +#define gsSetDebugFile +#define gsOpenDebugFile +#define gsGetDebugFile +#define gsSetDebugCallback +#else + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// User supplied debug function, will receive debug text +typedef void (*GSIDebugCallback)(GSIDebugCategory, GSIDebugType, GSIDebugLevel, + const char*, va_list); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Global debug instance +typedef struct GSIDebugInstance { + FILE* mGSIDebugFile; + GSIDebugCallback mDebugCallback; + gsi_i32 mInitialized; + + GSICriticalSection mDebugCrit; + + GSIDebugLevel mGSIDebugLevel[GSIDebugCat_Count][GSIDebugType_Count]; +} GSIDebugInstance; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Logging functions +void gsDebugFormat(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theTokenStr, ...); + +void gsDebugVaList(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theTokenStr, + va_list theParams); + +void gsDebugBinary(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel, const char* theBuffer, + gsi_i32 theLength); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Output functions +void gsSetDebugLevel(GSIDebugCategory theCat, GSIDebugType theType, + GSIDebugLevel theLevel); + +// Set the output file (NULL for no file) +void gsSetDebugFile(FILE* theFile); + +// Open and set the debug file +FILE* gsOpenDebugFile(const char* theFileName); + +// Retrieve the debug file +FILE* gsGetDebugFile(); + +// Set a callback to be triggered with debug output +void gsSetDebugCallback(GSIDebugCallback theCallback); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // GSI_COMMON_DEBUG + +#if defined(__LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) || \ + defined(c_plusplus) +} +#endif diff --git a/source/gamespy/common/gsLargeInt.c b/source/gamespy/common/gsLargeInt.c new file mode 100644 index 000000000..3730671ec --- /dev/null +++ b/source/gamespy/common/gsLargeInt.c @@ -0,0 +1,1850 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsLargeInt.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Many parameters are gsi_u32* instead of gsLargeInt_t*. +// This was done to allow easy conversion of databuffer to gsLargeInt_t +// Raw buffer destinations must have enough space to store the result +static gsi_bool gsiLargeIntPrint(FILE* logFile, const l_word* data, + l_word length); +static gsi_bool gsiLargeIntResize(gsLargeInt_t* lint, l_word length); +static gsi_bool gsiLargeIntStripLeadingZeroes(gsLargeInt_t* lint); +static gsi_bool gsiLargeIntSizePower2(const gsLargeInt_t* src1, + const gsLargeInt_t* src2, l_word* lenout); +static gsi_i32 gsiLargeIntCompare(const l_word* data1, l_word len1, + const l_word* data2, l_word len2); + +static gsi_bool gsiLargeIntKMult(const l_word* data1, const l_word* data2, + l_word length, l_word* dest, l_word* lenout, + l_word maxlen); +static gsi_bool gsiLargeIntMult(const l_word* data1, l_word length1, + const l_word* data2, l_word length2, + l_word* dest, l_word* lenout, l_word maxlen); +static gsi_bool gsiLargeIntDiv(const l_word* src1, l_word length1, + const gsLargeInt_t* divisor, gsLargeInt_t* dest, + gsLargeInt_t* remainder); + +// Dest may be data1 or data2 to support in-place arithmetic +static gsi_bool gsiLargeIntAdd(const l_word* data1, l_word length1, + const l_word* data2, l_word length2, + l_word* dest, l_word* lenout, l_word maxlen); +static gsi_bool gsiLargeIntSub(const l_word* amount, l_word length1, + const l_word* from, l_word length2, l_word* dest, + l_word* lenout); + +// Special division, removes divisor directly from src1, leaving remainder +static gsi_bool gsiLargeIntSubDivide(l_word* src1, l_word length, + const l_word* divisor, l_word dlen, + gsi_u32 highbit, l_word* quotient); + +// Montgomery utilities +// gsi_bool gsiLargeIntSquareM(const gsLargeInt_t *src, const gsLargeInt_t *mod, +// gsi_u32 modPrime, gsi_u32 R, gsLargeInt_t *dest); gsi_bool +// gsiLargeIntMultM(gsLargeInt_t *src1, gsLargeInt_t *src2, const gsLargeInt_t +// *mod, gsi_u32 modPrime, gsLargeInt_t *dest); +gsi_bool gsiLargeIntMultM(gsLargeInt_t* src1, gsLargeInt_t* src2, + const gsLargeInt_t* mod, gsi_u32 modPrime, + gsLargeInt_t* dest); +gsi_bool gsiLargeIntInverseMod(const gsLargeInt_t* mod, l_word* modPrimeOut); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// execution timing/profiling +#define GS_LINT_TIMING +#ifdef GS_LINT_TIMING + +typedef enum { + GSLintTimerMult, // "regular" multiplication + GSLintTimerMultM, // montgomery + GSLintTimerKMult, // karatsuba + GSLintTimerAdd, + GSLintTimerSub, // subtract + GSLintTimerDiv, + GSLintTimerSubDivide, // atomic divide + GSLintTimerSquareMod, + GSLintTimerPowerMod, // modular exponentiation + + GSLintTimerCount +} GSLintTimerID; + +typedef struct GSLintTimer { + gsi_time started; + gsi_time total; + gsi_u32 entries; + gsi_u32 running; // already entered? +} GSLintTimer; +static struct GSLintTimer gTimers[GSLintTimerCount]; + +static void gsiLargeIntTimerEnter(GSLintTimerID id) { + if (gTimers[id].running == 0) { + gTimers[id].entries++; + gTimers[id].started = current_time_hires(); + gTimers[id].running = 1; + } +} +static void gsiLargeIntTimerExit(GSLintTimerID id) { + if (gTimers[id].running == 1) { + gTimers[id].total += current_time_hires() - gTimers[id].started; + gTimers[id].running = 0; + } +} + +#define GSLINT_ENTERTIMER(id) gsiLargeIntTimerEnter(id) +#define GSLINT_EXITTIMER(id) gsiLargeIntTimerExit(id) + +#else +#define GSLINT_ENTERTIMER(id) +#define GSLINT_EXITTIMER(id) +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntSetValue(gsLargeInt_t* lint, l_word value) { + lint->mLength = 1; + lint->mData[0] = value; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Resize by: +// Padding a GSLINT with leading zeroes. +// or stripping lead zeroes. +// This function will not strip digits other than zero. +gsi_bool gsiLargeIntResize(gsLargeInt_t* lint, l_word length) { + if (length > GS_LARGEINT_MAX_DIGITS) + return gsi_false; + + // strip leading zeroes until length is reached + if (lint->mLength >= length) { + while (lint->mLength > length && lint->mData[lint->mLength - 1] == 0) + lint->mLength--; // check each digit to make sure it's zero + if (lint->mLength == length) + return gsi_true; + else + return gsi_false; + } + + // otherwise, add zeroes until length is reached + else { + memset(&lint->mData[lint->mLength], 0, + (length - lint->mLength) * sizeof(l_word)); + lint->mLength = length; + return gsi_true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Makes two GSLINT the same size, the size being a power of 2 +// NOTE: Testing next multiple of two, not power of 2 +gsi_bool gsiLargeIntSizePower2(const gsLargeInt_t* src1, + const gsLargeInt_t* src2, l_word* lenout) { + unsigned int i = 0; + + int len1 = (int)src1->mLength; + int len2 = (int)src2->mLength; + + // strip leading zeroes + while (len1 > 0 && src1->mData[len1 - 1] == 0) + len1--; + while (len2 > 0 && src2->mData[len2 - 1] == 0) + len2--; + + // set to longer length + *lenout = (l_word)max(len1, len2); + + // search for power of two >= length + // (this length is in digits, not bits) + i = 1; + while (i < *lenout) + i = i << 1; + *lenout = (l_word)i; + + if (*lenout > GS_LARGEINT_MAX_DIGITS) + return gsi_false; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Compare two integers +// -1 = data1 < data2 +// 0 = data1 = data2 +// 1 = data1 > data2 +static gsi_i32 gsiLargeIntCompare(const l_word* data1, l_word len1, + const l_word* data2, l_word len2) { + // skip leading whitespace, if any + while (data1[len1 - 1] == 0 && len1 > 0) + len1--; + while (data2[len2 - 1] == 0 && len2 > 0) + len2--; + if (len1 < len2) + return -1; + else if (len1 > len2) + return 1; + else { + // same size, compare digits + while (len1 > 0) { + if (data1[len1 - 1] < data2[len1 - 1]) + return -1; + else if (data1[len1 - 1] > data2[len1 - 1]) + return 1; + len1--; + } + } + return 0; // equal! +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiLargeIntStripLeadingZeroes(gsLargeInt_t* lint) { + while (lint->mLength > 0 && lint->mData[lint->mLength - 1] == 0) + lint->mLength--; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Addition may cause overflow +gsi_bool gsLargeIntAdd(const gsLargeInt_t* src1, const gsLargeInt_t* src2, + gsLargeInt_t* dest) { + gsi_bool result = + gsiLargeIntAdd(src1->mData, src1->mLength, src2->mData, src2->mLength, + dest->mData, &dest->mLength, GS_LARGEINT_MAX_DIGITS); + if (gsi_is_false(result)) + memset(dest, 0, sizeof(gsLargeInt_t)); // overflow + return result; +} + +// len: In value = maxsize +// Out value = actual size +static gsi_bool gsiLargeIntAdd(const l_word* data1, l_word length1, + const l_word* data2, l_word length2, + l_word* dest, l_word* lenout, l_word maxlen) { + gsi_u32 i = 0; + l_dword carry = 0; // to hold overflow + + gsi_u32 shorterLen = 0; + gsi_u32 longerLen = 0; + // const gsi_u32 *shorterSrc = NULL; + const l_word* longerSrc = NULL; + + GSLINT_ENTERTIMER(GSLintTimerAdd); + + if (maxlen < length1 || maxlen < length2) + return gsi_false; // dest not large enough, OVERFLOW + + if (length1 < length2) { + shorterLen = length1; + // shorterSrc = data1; + longerLen = length2; + longerSrc = data2; + } else { + shorterLen = length2; + // shorterSrc = data2; + longerLen = length1; + longerSrc = data1; + } + + // Add digits until the shorterSrc's length is reached + while (i < shorterLen) { + carry += (l_dword)data1[i] + data2[i]; + dest[i] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; // 32; + i++; + } + + // Continue adding until carry is zero + while ((carry > 0) && (i < longerLen)) { + carry += (l_dword)longerSrc[i]; + dest[i] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; // 32; + i++; + } + + // Is there still a carry? + // do not perform length check here, so that we can support oversized + // buffers + if (carry > 0) // && i < GS_LARGEINT_INT_SIZE) + { + if (maxlen <= i) + return gsi_false; // OVERFLOW, no room for extra digit + dest[i++] = (l_word)carry; + carry = 0; + } + + // Copy the rest of the bytes straight over (careful of memory overlap) + // this can't happen if there was a carry (see above carry>0 check) + if (i < longerLen) { + // check overlap + if (&dest[i] != &longerSrc[i]) + memcpy(&dest[i], &longerSrc[i], (longerLen - i) * sizeof(l_word)); + i = longerLen; + } + *lenout = (l_word)i; + + GSLINT_EXITTIMER(GSLintTimerAdd); + + if (carry) + return gsi_false; // overflow + else + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Subtraction may cause underflow +// subtracts src1 FROM src2 +// strips leading zeroes (gsiLargeIntSub doesn't strip for compatability with +// karatsuba fixed size numbers) +gsi_bool gsLargeIntSub(const gsLargeInt_t* src1, const gsLargeInt_t* src2, + gsLargeInt_t* dest) { + gsi_bool result = gsiLargeIntSub(src1->mData, src1->mLength, src2->mData, + src2->mLength, dest->mData, &dest->mLength); + if (gsi_is_true(result)) + gsiLargeIntStripLeadingZeroes(dest); + return result; +} + +gsi_bool gsiLargeIntSub(const l_word* src1, l_word length1, const l_word* src2, + l_word length2, l_word* dest, l_word* lenout) { + l_dword borrow = 0; // to hold overflow + gsi_u32 shorterLen = min(length1, length2); + gsi_u32 i = 0; + + GSLINT_ENTERTIMER(GSLintTimerSub); + + // printf("--From: "); + // gsiLargeIntPrint(src2, length2); + // printf("--Subtracting: "); + // gsiLargeIntPrint(src1, length1); + + // Subtract digits + while (i < shorterLen) { + borrow = (l_dword)src2[i] - src1[i] - borrow; + dest[i] = (l_word)borrow; + borrow = + borrow >> + 63; // shift to last bit. This will be 1 if negative, 0 if positive + i++; + } + while (i < length2) { + borrow = (l_dword)src2[i] - borrow; + dest[i] = (l_word)borrow; + borrow = borrow >> 63; + i++; + } + + // check for underflow + if (borrow != 0) { + GSLINT_EXITTIMER(GSLintTimerSub); + return gsi_false; + } + while (length1 > i) // make sure remaining digits are only leading zeroes + { + if (src1[i] != 0) { + GSLINT_EXITTIMER(GSLintTimerSub); + return gsi_false; + } + i++; + } + + // Don't reduce length from subtraction, instead keep leading zeroes + // (do this for ease of use with Karatsuba which requires Power2 length) + *lenout = length2; + + GSLINT_EXITTIMER(GSLintTimerSub); + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Multiply using normal method (use KMult when working with LargeInt*LargeInt) +gsi_bool gsLargeIntMult(const gsLargeInt_t* src1, const gsLargeInt_t* src2, + gsLargeInt_t* dest) { + gsi_bool result = + gsiLargeIntMult(src1->mData, src1->mLength, src2->mData, src2->mLength, + dest->mData, &dest->mLength, GS_LARGEINT_MAX_DIGITS); + if (gsi_is_false(result)) + memset(dest, 0, sizeof(gsLargeInt_t)); // overflow + return result; +} + +static gsi_bool gsiLargeIntMult(const l_word* data1, l_word length1, + const l_word* data2, l_word length2, + l_word* dest, l_word* lenout, l_word maxlen) { + unsigned int i = 0; + unsigned int k = 0; + + gsLargeInt_t temp; + memset(&temp, 0, sizeof(temp)); + *lenout = 0; + + GSLINT_ENTERTIMER(GSLintTimerMult); + + for (i = 0; i < length2; i++) { + // don't have to multiply by 0 + if (data2[i] != 0) { + // multiply data1 by data2[i] + for (k = 0; k < length1; k++) { + // carry starts out as product + // (it is mathematically impossible for carry to overflow + // at the first addition [see below]) + l_dword carry = (l_dword)data1[k] * data2[i]; + unsigned int digit = (unsigned int)(i + k); + if (digit >= maxlen) { + GSLINT_EXITTIMER(GSLintTimerMult); + return gsi_false; // overflow + } + while (carry) { + carry += temp.mData[digit]; + temp.mData[digit] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; + digit++; + if ((digit > maxlen) || (digit == maxlen && carry > 0)) { + GSLINT_EXITTIMER(GSLintTimerMult); + return gsi_false; // overflow + } + } + if (digit > (gsi_i32)temp.mLength) + temp.mLength = (l_word)digit; + } + } + } + // copy into destination (calculate length at this time) + while (temp.mLength > 0 && temp.mData[temp.mLength - 1] == 0) + temp.mLength--; // strip leading zeroes + *lenout = temp.mLength; + memcpy(dest, temp.mData, (*lenout) * sizeof(l_word)); + + GSLINT_EXITTIMER(GSLintTimerMult); + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// divide src1 by divisor +gsi_bool gsLargeIntDiv(const gsLargeInt_t* src1, const gsLargeInt_t* divisor, + gsLargeInt_t* dest, gsLargeInt_t* remainder) { + // call the free-buffer version + return gsiLargeIntDiv(src1->mData, src1->mLength, divisor, dest, remainder); +} + +// length1 can be, at most, 2*GS_LARGEINT_INT_SIZE +static gsi_bool gsiLargeIntDiv(const l_word* src, l_word len, + const gsLargeInt_t* div, gsLargeInt_t* dest, + gsLargeInt_t* remainder) { + gsi_i32 result = 0; // temp, to store compare result + gsi_i32 divisorHighBit = + GS_LARGEINT_DIGIT_SIZE_BITS - 1; // pre-calculate this + + // Bytes used from src1 + int readIndex = 0; + int readLength = 0; + + // setup scratch copies + gsLargeInt_t quotient; + + l_word scopy[GS_LARGEINT_MAX_DIGITS * 2]; // we support double length source + // for division, when dest is null + l_word scopyLen = len; + + const l_word* divisorData = div->mData; + l_word divisorLen = div->mLength; + + gsi_bool endLoop = gsi_false; + + GSLINT_ENTERTIMER(GSLintTimerDiv); + + memset(scopy, 0, sizeof(scopy)); + + // we only support oversized sources for calculating a remainder + // e.g. dest must be null + if (scopyLen > GS_LARGEINT_MAX_DIGITS && dest != NULL) + return gsi_false; + + // strip leading zeroes (from our scratch copies) + while (scopyLen > 0 && src[scopyLen - 1] == 0) + scopyLen--; + while (divisorLen > 0 && divisorData[divisorLen - 1] == 0) + divisorLen--; + + memcpy(scopy, src, scopyLen * sizeof(l_word)); + memset("ient, 0, sizeof(quotient)); + + // check the unusual cases + if (scopyLen == 0 || divisorLen == 0) { + if (dest) { + dest->mData[0] = 0; + dest->mLength = 0; + } + if (remainder) { + remainder->mData[0] = 0; + remainder->mLength = 0; + } + + GSLINT_EXITTIMER(GSLintTimerDiv); + + if (divisorLen == 0) + return gsi_false; // division by zero + else + return gsi_true; // zero divided, this is legal + } + if (gsiLargeIntCompare(scopy, scopyLen, divisorData, divisorLen) == -1) { + // divisor is larger than source + if (dest) { + dest->mLength = 0; + dest->mData[0] = 0; + } + remainder->mLength = scopyLen; + memcpy(remainder->mData, scopy, scopyLen * sizeof(l_word)); + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_true; + } + + // calculate the divisor high bit + while ((divisorData[divisorLen - 1] & (1 << (gsi_u32)divisorHighBit)) == 0 && + divisorHighBit >= 0) + divisorHighBit--; + if (divisorHighBit == -1) { + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_false; // divide by zero + } + divisorHighBit += (divisorLen - 1) * GS_LARGEINT_DIGIT_SIZE_BITS; + + // position "sliding" window for first interation + // 41529 / [71389]2564 + // WARNING: digits are indexed [2][1][0], first byte to read is index[2] + readIndex = (int)(scopyLen - divisorLen); + readLength = (int)divisorLen; + + // if (readIndex < 0) + // _asm {int 3}; // overflow readIndex + + do { + result = gsiLargeIntCompare(&scopy[readIndex], (l_word)readLength, + divisorData, divisorLen); + if (result == -1) { + // scopy window is smaller, we'll need an extra digit + if (readIndex > 0) { + readIndex--; + readLength++; + } else { + // no more digits! + endLoop = gsi_true; + } + } else if (result == 0) { + // not likely! set digits to zero and slide window + memset(&scopy[readIndex], 0, readLength * sizeof(l_word)); + quotient.mData[readIndex] += 1; + if (quotient.mLength < (l_word)(readIndex + readLength)) + quotient.mLength = (l_word)(readIndex + readLength); + readIndex -= readLength; + readLength = 1; + + if (readIndex < 0) + endLoop = gsi_true; + ; // no more digits + } else { + // subtract directly onto our temp copy, so we don't have to worry about + // carry values + l_word quotientTemp = 0; + // if (readLength > 0xffff) + // _asm {int 3} + if (gsi_is_false(gsiLargeIntSubDivide( + &scopy[readIndex], (l_word)readLength, divisorData, divisorLen, + (gsi_u32)divisorHighBit, "ientTemp))) { + // overflow + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_false; + } + quotient.mData[readIndex] = + (l_word)(quotient.mData[readIndex] + quotientTemp); + if (quotient.mLength < (l_word)(readIndex + readLength)) + quotient.mLength = (l_word)(readIndex + readLength); + // remove new leading zeroes + while (scopy[readIndex + readLength - 1] == 0 && readLength > 1) + readLength--; + while (scopy[readIndex + readLength - 1] == 0 && readIndex > 1) + readIndex--; + } + } while (gsi_is_false(endLoop)); + + // no more digits, leftover is remainder + if (readIndex >= 0) { + memcpy(remainder->mData, &scopy[readIndex], readLength * sizeof(l_word)); + remainder->mLength = (l_word)readLength; + } else { + remainder->mData[0] = 0; + remainder->mLength = 0; + } + + // save off quotient, if desired + if (dest) { + memcpy(dest->mData, quotient.mData, quotient.mLength * sizeof(l_word)); + dest->mLength = quotient.mLength; + } + GSLINT_EXITTIMER(GSLintTimerDiv); + return gsi_true; +} + +// atomic divide. +// Subtract divisor directly from src. +// Leave remainder in src. +static gsi_bool gsiLargeIntSubDivide(l_word* src, l_word length, + const l_word* divisor, l_word dlen, + gsi_u32 highbit, l_word* quotient) { + l_dword aboveBits = 0; + gsLargeInt_t temp; // stores temporary product before subtraction + gsLargeInt_t + quotientCopy; // copy of quotient, length padded for multiplication + + GSLINT_ENTERTIMER(GSLintTimerSubDivide); + // assert(src > divisor) + // assert(src < (MAX_DIGIT_VALUE * divisor)) + // if(dlen==1 && *divisor==0) + // _asm {int 3} // division by zero + + // Q: how many times to subtract? + // A: we estimate by taking the bits in src above the highest bit in divisor + if (length > dlen) + aboveBits = (src[length - 2] & divisor[dlen - 1]) | + ((l_dword)src[length - 1] << GS_LARGEINT_DIGIT_SIZE_BITS); + else + aboveBits = src[length - 1]; + aboveBits /= divisor[dlen - 1]; + + memset("ientCopy, 0, sizeof(quotientCopy)); + quotientCopy.mData[0] = (l_word)(aboveBits); + quotientCopy.mData[1] = (l_word)(aboveBits >> GS_LARGEINT_DIGIT_SIZE_BITS); + + // We only support quotients up to MAX_INT + if (quotientCopy.mData[1] != 0) { + quotientCopy.mData[0] = (l_word)(-1); + quotientCopy.mData[1] = 0; + } + quotientCopy.mLength = 1; + + // multiply this value by divisor, and that's how much to subtract + if (gsi_is_false(gsiLargeIntMult(divisor, dlen, quotientCopy.mData, + quotientCopy.mLength, temp.mData, + &temp.mLength, GS_LARGEINT_MAX_DIGITS))) { + GSLINT_EXITTIMER(GSLintTimerSubDivide); + return gsi_false; // overflow + } + + // while subtraction amount is larger than src, reduce it + while (gsiLargeIntCompare(temp.mData, temp.mLength, src, length) == 1) { + // divide by two + quotientCopy.mData[0] = (l_word)(quotientCopy.mData[0] >> 1); + // if (quotientCopy.mData[0] == 0) + // _asm {int 3} + if (gsi_is_false(gsiLargeIntMult(divisor, dlen, quotientCopy.mData, + quotientCopy.mLength, temp.mData, + &temp.mLength, GS_LARGEINT_MAX_DIGITS))) { + GSLINT_EXITTIMER(GSLintTimerSubDivide); + return gsi_false; // overflow + } + } + // if (gsiLargeIntCompare(temp.mData, temp.mLength, src, length)==1) + // _asm {int 3} // temp > src, subtraction will cause underflow! + + // subtract it + gsiLargeIntSub(temp.mData, temp.mLength, src, length, src, &length); + + *quotient = quotientCopy.mData[0]; + // if (quotientCopy.mData[1] != 0) + // _asm {int 3} + GSLINT_EXITTIMER(GSLintTimerSubDivide); + + GSI_UNUSED(highbit); + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Multiply using Karatsuba +// Karatsuba requires that the sizes be equal and a power of two +gsi_bool gsLargeIntKMult(const gsLargeInt_t* src1, const gsLargeInt_t* src2, + gsLargeInt_t* dest) { + l_word len = 0; + gsi_bool result = gsi_false; + + gsLargeInt_t temp; // to prevent issues if (src1 == src2 == dest) + + // quick check for multiplication by 0 + if (src1->mLength == 0 || src2->mLength == 0) { + dest->mLength = 0; + return gsi_true; + } + + // when length is small it's faster to use "normal" multiplication + if (max(src1->mLength, src2->mLength) < GS_LARGEINT_KARATSUBA_CUTOFF) + return gsLargeIntMult(src1, src2, dest); + + // Check for size/length restrictions + result = gsiLargeIntSizePower2(src1, src2, &len); + if (gsi_is_false(result) || len > (GS_LARGEINT_MAX_DIGITS / 2)) { + // try regular multiplication + return gsLargeIntMult(src1, src2, dest); + } + + // (don't time above section since it defers to Mult) + GSLINT_ENTERTIMER(GSLintTimerKMult); + + // clear the temporary dest + memset(&temp, 0, sizeof(gsLargeInt_t)); + temp.mLength = 0; + + // resize if necessary + if (src1->mLength != len || src2->mLength != len) { + // size is not correct, make a copy then multiply + gsLargeInt_t src1Copy; + gsLargeInt_t src2Copy; + memcpy(&src1Copy, src1, sizeof(gsLargeInt_t)); + memcpy(&src2Copy, src2, sizeof(gsLargeInt_t)); + gsiLargeIntResize(&src1Copy, len); + gsiLargeIntResize(&src2Copy, len); + + result = gsiLargeIntKMult(src1Copy.mData, src2Copy.mData, len, temp.mData, + &temp.mLength, GS_LARGEINT_MAX_DIGITS); + } else { + // size is correct, perform multiplication + result = gsiLargeIntKMult(src1->mData, src2->mData, len, temp.mData, + &temp.mLength, GS_LARGEINT_MAX_DIGITS); + } + if (gsi_is_true(result)) { + // strip leading zeroes and copy into dest + gsiLargeIntStripLeadingZeroes(&temp); + memcpy(dest, &temp, sizeof(gsLargeInt_t)); + } + GSLINT_EXITTIMER(GSLintTimerKMult); + return result; +} + +// Utility for Karasuba +static gsi_bool gsiLargeIntKMult(const l_word* data1, const l_word* data2, + l_word length, l_word* dest, l_word* lenout, + l_word maxlen) { + // No timer here, this function is only called from GSLINTKMult + // GSLINT_ENTERTIMER(GSLintTimerKMult); + + // "normal" multiplication is faster when length is small + if (length <= GS_LARGEINT_KARATSUBA_CUTOFF) + return gsiLargeIntMult(data1, length, data2, length, dest, lenout, maxlen); + else { + gsLargeInt_t temp1, temp2, temp3; + l_word halfLen = (l_word)(length >> 1); + + temp1.mLength = 0; + temp2.mLength = 0; + temp3.mLength = 0; + + // printf("Karasuba splitting at %d (1/2 = %d)\r\n", length, halfLen); + + // Karatsuba: k = 12*34 + // a = (1*3) + // b = (1+2)*(3+4)-a-c + // c = (2*4) + // k = a*B^N+b*B^(N/2)+c = a*100+b*10+c + + // Enter the recursive portion + // TH = top half + // BH = bottom half + + // Note that since (a*B^N + c) cannot overlap, we can immediately store both + // in dest + + // Compute a. (TH of data1 * TH of data2) + // Stores in TH of dest, so later *B^N isn't necessary + // For the example, this puts 1*3 into the high half 03xx + gsiLargeIntKMult(&data1[halfLen], &data2[halfLen], halfLen, &dest[length], + lenout, (l_word)(maxlen - length)); + // printf("Calculated A (%d) = ", *lenout); + // gsiLargeIntPrint(&dest[length], *lenout); + + // Compute c. (BH of data1 * BH of data2) + // For the example, this puts 2*4 into the low half xx08 + gsiLargeIntKMult(data1, data2, halfLen, dest, lenout, maxlen); + // printf("Calculated C (%d) = ", *lenout); + // gsiLargeIntPrint(dest, *lenout); + + // Compute b1. (TH of data1 + BH of data1) + gsiLargeIntAdd(&data1[halfLen], halfLen, data1, halfLen, temp1.mData, + &temp1.mLength, GS_LARGEINT_MAX_DIGITS); + // printf("Calculated B1 (%d) = ", temp1.mLength); + // gsiLargeIntPrint(temp1.mData, temp1.mLength); + + // Compute b2. (TH of data2 + BH of data2) + gsiLargeIntAdd(&data2[halfLen], halfLen, data2, halfLen, temp2.mData, + &temp2.mLength, GS_LARGEINT_MAX_DIGITS); + // printf("Calculated B2 (%d) = ", temp2.mLength); + // gsiLargeIntPrint(temp2.mData, temp2.mLength); + + // Compute b3. (b1*b2) (*B^N) + // For the example, (1+2)(3+4)*B^N = 21*B^N = 0210 + memset(&temp3, 0, sizeof(gsLargeInt_t)); + + // May require resizing, but don't go above halfLen + if (temp1.mLength > halfLen || temp2.mLength > halfLen) + gsiLargeIntMult(temp1.mData, temp1.mLength, temp2.mData, temp2.mLength, + &temp3.mData[halfLen], &temp3.mLength, + (l_word)(GS_LARGEINT_MAX_DIGITS - halfLen)); + else { + gsi_bool result = gsiLargeIntSizePower2(&temp1, &temp2, lenout); + if (gsi_is_false(result)) + return gsi_false; // could not resize + gsiLargeIntResize(&temp1, *lenout); // pad to new size + gsiLargeIntResize(&temp2, *lenout); // pad to new size + gsiLargeIntKMult(temp1.mData, temp2.mData, *lenout, &temp3.mData[halfLen], + &temp3.mLength, + (l_word)(GS_LARGEINT_MAX_DIGITS - halfLen)); + } + temp3.mLength = (l_word)(temp3.mLength + halfLen); // fix length for temp3 + // if (temp3.mLength > GS_LARGEINT_INT_SIZE) + // _asm {int 3} // this should be at most temp1.mLength+temp2.mLength + memset(temp3.mData, 0, halfLen * sizeof(l_word)); + // printf("Calculated B3 (%d) = ", temp3.mLength); + // gsiLargeIntPrint(&temp3.mData[halfLen], temp3.mLength-halfLen); + + // Compute final b. (b3-a-c) (*B^N) + // Note: The subtraction is in terms of (*B^N) + // For the example, 021x - 03x - 08x = 0100 + gsiLargeIntSub(&dest[length], length, &temp3.mData[halfLen], + (l_word)(temp3.mLength - halfLen), &temp3.mData[halfLen], + &temp3.mLength); + temp3.mLength = (l_word)(temp3.mLength + halfLen); + gsiLargeIntSub(dest, length, &temp3.mData[halfLen], + (l_word)(temp3.mLength - halfLen), &temp3.mData[halfLen], + &temp3.mLength); + temp3.mLength = (l_word)(temp3.mLength + halfLen); + // printf("Calculated B (%d) = ", temp3.mLength); + // gsiLargeIntPrint(temp3.mData, temp3.mLength); + + // Add em up + // Dest already contains A+C, so Add B + // For the example, 0308 + 0100 = 0408 (the correct answer) + gsiLargeIntAdd(dest, (l_word)(length * 2), temp3.mData, temp3.mLength, dest, + lenout, maxlen); + } + // strip leading zeroes from dest + while (*lenout > 0 && dest[*lenout - 1] == 0) + *lenout = (l_word)(*lenout - 1); + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntSquareMod(const gsLargeInt_t* lint, const gsLargeInt_t* mod, + gsLargeInt_t* dest) { + int i = 0; + int k = 0; + int len = (int)lint->mLength; // signed version + l_dword carry = 0; + int oldShiftBit = 0; + int newShiftBit = 0; + gsi_bool result = gsi_false; + unsigned int mask = (unsigned int)1 << (GS_LARGEINT_DIGIT_SIZE_BITS - 1); + + l_word squareSums[GS_LARGEINT_MAX_DIGITS * 2]; // temp dest for square sums + l_word otherSums[GS_LARGEINT_MAX_DIGITS * 2]; // temp dest for other sums + l_word squareLen = 0; + l_word otherLen = 0; + + GSLINT_ENTERTIMER(GSLintTimerSquareMod); + + memset(&squareSums, 0, sizeof(squareSums)); + memset(&otherSums, 0, sizeof(otherSums)); + + // Go through each digit, multiplying with each other digit + // (only do this once per pair, since AB == BA) + // Ex: ABC * ABC, we want AB,AC,BC only + for (i = 1; i < len; i++) { + for (k = 0; k < i; k++) { + carry += (l_dword)lint->mData[i] * lint->mData[k] + otherSums[i + k]; + otherSums[i + k] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; + } + if (carry) { + otherSums[i + k] = (l_word)carry; + carry = carry >> GS_LARGEINT_DIGIT_SIZE_BITS; + } + } + + // Multiply by 2 (because each internal pair appears twice) + for (i = 0; i < (2 * len); i++) { + newShiftBit = + (otherSums[i] & mask) == mask ? 1 : 0; // calc next carry 1 or 0 + otherSums[i] = (l_word)((otherSums[i] << 1) + oldShiftBit); // do the shift + oldShiftBit = newShiftBit; + } + // don't worry about left-overy carry because this can't overflow + // maxlen N-digit*N-digit = 2n-digit + + // Go through each digit, multiplying with itself + for (i = 0; i < len; i++) { + carry = (l_dword)lint->mData[i] * lint->mData[i]; + squareSums[i * 2] = (l_word)carry; + squareSums[i * 2 + 1] = (l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS); + } + squareLen = (l_word)(2 * len); + otherLen = (l_word)(2 * len); + + // Add the two together + result = gsiLargeIntAdd(otherSums, otherLen, squareSums, squareLen, + squareSums, &squareLen, GS_LARGEINT_MAX_DIGITS * 2); + result = gsiLargeIntDiv(squareSums, squareLen, mod, NULL, dest); + + GSLINT_EXITTIMER(GSLintTimerSquareMod); + return result; +} + +//#define NEWEXP +#ifdef NEWEXP + +//#define printf + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Montgomery exponentiation (see HAC 14.94) +// +// SPECIAL NOTE: +// A small public exponent will reduce the load on client encryption. +// (below 65535 is a security risk, so don't go too small) +gsi_bool gsLargeIntPowerMod(const gsLargeInt_t* b, const gsLargeInt_t* p, + const gsLargeInt_t* m, gsLargeInt_t* dest) { + gsLargeInt_t base; + gsLargeInt_t power; + gsLargeInt_t mod; + gsLargeInt_t one; + + gsi_u32 expHighBit; // highest bit set in exponent; + + int i = 0; // temp / counter + int k = 0; // binary size of our subdigits + int pow2k = 0; // 2^k + int kmask = 0; // 2^k-1 + int kdigits = 0; // number of k-sized digits in p + // int leadingZeroBits = 0; // to make p evenly divisible by k + + l_word modPrime; + gsLargeInt_t R; // "R" as used in the montgomery exponentiation algorithm. + // gsLargeInt_t Rmod; // R mod n + // gsLargeInt_t R2mod; // R^2 mod n + + gsLargeInt_t* lut = NULL; + + GSLINT_ENTERTIMER(GSLintTimerPowerMod); + + memcpy(&base, b, sizeof(base)); + memcpy(&power, p, sizeof(power)); + memcpy(&mod, m, sizeof(mod)); + memset(&R, 0, sizeof(R)); + + gsLargeIntSetValue(&one, 1); + + // Catch the unusual cases + if (mod.mLength == 0) { + // mod 0 = undefined + dest->mLength = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } else if (mod.mLength == 1 && mod.mData[0] == 1) { + // mod 1 = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } else if (power.mLength == 0) { + // x^0 = 1 + dest->mLength = 1; + dest->mData[0] = 1; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } else if ((mod.mData[0] & 1) == 0) { + // Montgomery only works with odd modulus! + // (rsa modulus is prime1*prime2, which must be odd) + dest->mLength = 0; + dest->mData[0] = 0; + //_asm {int 3} + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + // If base is larger than mod, we can (must) reduce it + if (gsiLargeIntCompare(base.mData, base.mLength, mod.mData, mod.mLength) != + -1) { + gsLargeIntDiv(&base, &mod, NULL, &base); + } + if (base.mLength == 0) { + // 0^e = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + + // find the highest bit set in power + expHighBit = GS_LARGEINT_DIGIT_SIZE_BITS; + while (((1 << (expHighBit - 1)) & power.mData[power.mLength - 1]) == 0) + expHighBit--; + expHighBit += + ((power.mLength - 1) * + GS_LARGEINT_DIGIT_SIZE_BITS); // add in 32 bits for each extra byte + + // The previous algorithm used 1-bit digits + // This algorithm uses k-bit digits + // Determine the optimal size for k + k = 8; // this will support up to 4096 bit encryption (and probably higher) + while ((k > 1) && (gsi_u32)((k - 1) * (k << ((k - 1) << 1)) / + ((1 << k) - k - 1)) >= expHighBit - 1) { + --k; + } + pow2k = 1 << k; + kmask = pow2k - 1; + kdigits = (expHighBit + (k - 1)) / k; // ceiling(expHighBit/k) + + // calculate "R" (if mod=5678, R=10000 e.g. One digit higher) + memset(&R, 0, sizeof(R)); + R.mLength = (l_word)(mod.mLength + 1); + if (R.mLength > GS_LARGEINT_MAX_DIGITS) + return gsi_false; // you need to increase the large int capacity + R.mData[R.mLength - 1] = 1; // set first bit one byte higher than mod + + // find the multiplicative inverse of mod + gsiLargeIntInverseMod(&mod, &modPrime); + + /* + // calculate Rmod (R%mod) + if (gsi_is_false(gsLargeIntDiv(&R, &mod, NULL, &Rmod))) + { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // calculate R2mod (R^2%mod = (Rmod*Rmod)%mod) + if (gsi_is_false(gsLargeIntSquareMod(&Rmod, &mod, &R2mod))) + { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + */ + // Allocate space for a table of values that will come up repeatedly + // xwiggle is (br mod n) + // These are the odd powers of xwiggle, x^3, x^5 and so on + // We generate these by repeated multiplications by xwiggle + // if (k >= 3) + { + // no no no[0] = xwiggle^3 (montgomery multiply [2]*[1]) + // no no no[1] = xwiggle^5 (montgomery multiply [2]*[3]) + // no no no[2] = xwiggle^7 (montgomery multiply [2]*[5]) + + // allocate space + // ~1k for typical small RSA public exponents (e.g. 65537) + // ~16k for 1024-bit RSA exponent + // ~32k for 2048-bit RSA exponent + // ~64k for 4096-bit RSA exponent + int i = 0; + int valuesNeeded = pow2k; //((pow2k/2)-1); + int spaceneeded = sizeof(gsLargeInt_t) * valuesNeeded; + + lut = (gsLargeInt_t*)gsimalloc(spaceneeded); + if (lut == NULL) + return gsi_false; // out of memory + memset(lut, 0x00, spaceneeded); + + // set first values + // [0] = 1 + // [1] = br mod n (normal multiplication) + // [i] = mont([1] * [i-1]) + gsLargeIntSetValue(&lut[0], 1); + if (gsi_is_false(gsLargeIntMult(&base, &R, &lut[1])) || + gsi_is_false(gsLargeIntDiv(&lut[1], &mod, NULL, &lut[1]))) { + gsifree(lut); + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // fill in the values + for (i = 2; i < valuesNeeded; i++) { + if (gsi_is_false(gsiLargeIntMultM(&lut[1], &lut[i - 1], &mod, modPrime, + &lut[i]))) { + gsifree(lut); + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + } + } + + // set starting point + if (gsi_is_false(gsLargeIntMult(&base, &R, dest)) || // Normal multiply + gsi_is_false(gsLargeIntDiv(dest, &mod, NULL, dest))) // A mod operation + { + gsifree(lut); + return gsi_false; + } + + // loop through the k-sized digits + for (i = 0; i < kdigits; i++) { + int bitReadIndex = expHighBit - (i * k); // index of the bit we're reading + int l_index; // = ((bitReadIndex-1)/GS_LARGEINT_DIGIT_SIZE_BITS); // -1 to + // use zero based indexes + int l_firstbit; + l_dword twodigits; + l_dword mask; + l_word digitval; + + l_index = ((bitReadIndex - 1) / + GS_LARGEINT_DIGIT_SIZE_BITS); // -1 to use zero based indexes + + // for first digit, use leading zeroes when necessary + if ((bitReadIndex % k) != 0) + bitReadIndex += k - (bitReadIndex % k); // round up to next k + if (i != 0) { + if (bitReadIndex - (l_index * GS_LARGEINT_DIGIT_SIZE_BITS) > + GS_LARGEINT_DIGIT_SIZE_BITS) + l_index++; + } + + if (i == 0) { + // first digit + l_firstbit = + l_index * GS_LARGEINT_DIGIT_SIZE_BITS; // first bit of this digit + twodigits = p->mData[l_index]; + } else if (l_index > 0) { + // middle digits + l_firstbit = (l_index - 1) * + GS_LARGEINT_DIGIT_SIZE_BITS; // first bit of this digit + twodigits = + (l_dword)((l_dword)p->mData[l_index] << GS_LARGEINT_DIGIT_SIZE_BITS) | + p->mData[l_index - 1]; + } else if (l_index == 0 && p->mLength > 1) { + // final digit, when there are proceeding digits + l_firstbit = 0; + twodigits = + (l_dword)(p->mData[l_index + 1] << GS_LARGEINT_DIGIT_SIZE_BITS) | + p->mData[l_index]; + } else { + // final digit, no proceeding digits + l_firstbit = + l_index * GS_LARGEINT_DIGIT_SIZE_BITS; // first bit of this digit + twodigits = p->mData[l_index]; + } + mask = (l_dword)kmask << (bitReadIndex - l_firstbit - k); + digitval = (l_word)((twodigits & mask) >> (bitReadIndex - l_firstbit - k)); + + // use digitval to determine how many squaring and multiplication operations + // we need to perform + { + static int twotab[] = { + 0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, + 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, + 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, + 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, + 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, + 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, + 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, + 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, + 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, + 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, + 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, + 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0}; + + static USHORT oddtab[] = { + 0, 1, 1, 3, 1, 5, 3, 7, 1, 9, 5, 11, 3, 13, + 7, 15, 1, 17, 9, 19, 5, 21, 11, 23, 3, 25, 13, 27, + 7, 29, 15, 31, 1, 33, 17, 35, 9, 37, 19, 39, 5, 41, + 21, 43, 11, 45, 23, 47, 3, 49, 25, 51, 13, 53, 27, 55, + 7, 57, 29, 59, 15, 61, 31, 63, 1, 65, 33, 67, 17, 69, + 35, 71, 9, 73, 37, 75, 19, 77, 39, 79, 5, 81, 41, 83, + 21, 85, 43, 87, 11, 89, 45, 91, 23, 93, 47, 95, 3, 97, + 49, 99, 25, 101, 51, 103, 13, 105, 53, 107, 27, 109, 55, 111, + 7, 113, 57, 115, 29, 117, 59, 119, 15, 121, 61, 123, 31, 125, + 63, 127, 1, 129, 65, 131, 33, 133, 67, 135, 17, 137, 69, 139, + 35, 141, 71, 143, 9, 145, 73, 147, 37, 149, 75, 151, 19, 153, + 77, 155, 39, 157, 79, 159, 5, 161, 81, 163, 41, 165, 83, 167, + 21, 169, 85, 171, 43, 173, 87, 175, 11, 177, 89, 179, 45, 181, + 91, 183, 23, 185, 93, 187, 47, 189, 95, 191, 3, 193, 97, 195, + 49, 197, 99, 199, 25, 201, 101, 203, 51, 205, 103, 207, 13, 209, + 105, 211, 53, 213, 107, 215, 27, 217, 109, 219, 55, 221, 111, 223, + 7, 225, 113, 227, 57, 229, 115, 231, 29, 233, 117, 235, 59, 237, + 119, 239, 15, 241, 121, 243, 61, 245, 123, 247, 31, 249, 125, 251, + 63, 253, 127, 255}; + + // printf("[gsint] Digit %d = %d\r\n", i, digitval); + if (i == 0) { + int counter = 0; + + memcpy(dest, &lut[oddtab[digitval]], sizeof(gsLargeInt_t)); + // printf("[gsint] Set start to %d\r\n", dest->mData[0]); + + for (counter = twotab[digitval]; counter > 0; counter--) { + if (gsi_is_false( + gsiLargeIntMultM(dest, dest, &mod, modPrime, dest))) { + gsifree(lut); + return gsi_false; + } + // printf("[gsint] First digit, squared to %d\r\n", dest->mData[0]); + } + } else if (digitval != 0) { + int counter = 0; + int lutindex = oddtab[digitval]; // we only precalculate the odd powers + // int lutindex = (oddtab[digitval]+1)/2; // we only precalculate the + // odd powers + + for (counter = (int)(k - twotab[digitval]); counter > 0; counter--) { + if (gsi_is_false( + gsiLargeIntMultM(dest, dest, &mod, modPrime, dest))) { + gsifree(lut); + return gsi_false; + } + // printf("[gsint] Squared to %d\r\n", dest->mData[0]); + } + + if (gsi_is_false( + gsiLargeIntMultM(dest, &lut[lutindex], &mod, modPrime, dest))) { + gsifree(lut); + return gsi_false; + } + // printf("[gsint] Mult by [%d](%d) to %d\r\n", lutindex, + // lut[lutindex].mData[0], dest->mData[0]); + for (counter = twotab[digitval]; counter > 0; counter--) { + if (gsi_is_false( + gsiLargeIntMultM(dest, dest, &mod, modPrime, dest))) { + gsifree(lut); + return gsi_false; + } + // printf("[gsint] Squared to %d\r\n", dest->mData[0]); + } + } else { + int counter = 0; + for (counter = k; counter > 0; counter--) { + if (gsi_is_false( + gsiLargeIntMultM(dest, dest, &mod, modPrime, dest))) { + gsifree(lut); + return gsi_false; + } + // printf("[gsint] Squared to %d\r\n", dest->mData[0]); + } + } + } + } + + // normalize (MultM by 1) + if (gsi_is_false(gsiLargeIntMultM(dest, &one, &mod, modPrime, dest))) + return gsi_false; + + gsifree(lut); + return gsi_true; +} + +#else + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Montgomery exponentiation (see HAC 14.94) +// +// SPECIAL NOTE: +// A small public exponent will reduce the load on client encryption. +// (below 65535 is a security risk, so don't go too small) +gsi_bool gsLargeIntPowerMod(const gsLargeInt_t* b, const gsLargeInt_t* p, + const gsLargeInt_t* m, gsLargeInt_t* dest) { + int i = 0; // temp/counter + int digitNum = 0; // temp/counter + int digitBit = 0; + + l_word modPrime; + + gsi_u32 expHighBit; // highest bit set in exponent; + + gsLargeInt_t R; // "R" as used in the montgomery exponentiation algorithm. + gsLargeInt_t Rmod; // R%mod + gsLargeInt_t R2mod; // R^2%mod + gsLargeInt_t temp; + gsLargeInt_t xwiggle; // montgomery mult of (x,R2mod) + + gsLargeInt_t base; + gsLargeInt_t power; + gsLargeInt_t mod; + + GSLINT_ENTERTIMER(GSLintTimerPowerMod); + + memset(&R, 0, sizeof(R)); + memset(&Rmod, 0, sizeof(Rmod)); + memset(&R2mod, 0, sizeof(R2mod)); + memset(&temp, 0, sizeof(temp)); + memset(&xwiggle, 0, sizeof(xwiggle)); + + memcpy(&base, b, sizeof(base)); + memcpy(&power, p, sizeof(power)); + memcpy(&mod, m, sizeof(mod)); + + gsiLargeIntStripLeadingZeroes(&base); + gsiLargeIntStripLeadingZeroes(&power); + gsiLargeIntStripLeadingZeroes(&mod); + + // Catch the unusual cases + if (mod.mLength == 0) { + // mod 0 = undefined + dest->mLength = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } else if (mod.mLength == 1 && mod.mData[0] == 1) { + // mod 1 = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } else if (power.mLength == 0) { + // x^0 = 1 + dest->mLength = 1; + dest->mData[0] = 1; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } else if ((mod.mData[0] & 1) == 0) { + // Montgomery only works with odd modulus! + // (rsa modulus is prime1*prime2, which must be odd) + dest->mLength = 0; + dest->mData[0] = 0; + //_asm {int 3} + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + // If base is larger than mod, we can (must) reduce it + if (gsiLargeIntCompare(base.mData, base.mLength, mod.mData, mod.mLength) != + -1) { + gsLargeIntDiv(&base, &mod, NULL, &base); + } + if (base.mLength == 0) { + // 0^e = 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_true; + } + + // find the highest bit set in power + expHighBit = GS_LARGEINT_DIGIT_SIZE_BITS; + while (((1 << (expHighBit - 1)) & power.mData[power.mLength - 1]) == 0) + expHighBit--; + expHighBit += + ((power.mLength - 1) * + GS_LARGEINT_DIGIT_SIZE_BITS); // add in 32 bits for each extra byte + + // On to the tricky tricky! + // 1) We can't compute B^P and later apply the mod; B^P is just too big + // So we have to make modular reductions along the way + // 2) Since modular reduction is essentially a division, we would like + // to use a mod 2^E so that division is just a bit strip. + // ex. (1383 mod 16) = binary(0000010101100111 mod 00010000) = 00000111 + // = dec 7 + + // Precalculate some values that will come up repeatedly + + // calculate "R" (if mod=5678, R=10000 e.g. One digit higher) + memset(&R, 0, sizeof(R)); + R.mLength = (l_word)(mod.mLength + 1); + if (R.mLength > GS_LARGEINT_MAX_DIGITS) + return gsi_false; // you need to increase the large int capacity + R.mData[R.mLength - 1] = 1; // set first bit one byte higher than mod + + // find the multiplicative inverse of mod + gsiLargeIntInverseMod(&mod, &modPrime); + + // calculate Rmod (R%mod) + if (gsi_is_false(gsLargeIntDiv(&R, &mod, NULL, &Rmod))) { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // calculate R2mod (R^2%mod = (Rmod*Rmod)%mod) + if (gsi_is_false(gsLargeIntSquareMod(&Rmod, &mod, &R2mod))) { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // calculate xwiggle + if (gsi_is_false(gsiLargeIntMultM(&base, &R2mod, &mod, modPrime, &xwiggle))) { + GSLINT_EXITTIMER(GSLintTimerPowerMod); + return gsi_false; + } + + // loop through the BITS of power + // if the bit is 1, perform a multiplication by xwiggle? (11/2/2006) + // TODO: THIS DOESN'T WORK IF THE HIGHBIT IS EVER ABOVE + // GS_LARGEINT_DIGIT_SIZE_BITS + memcpy(dest, &Rmod, sizeof(gsLargeInt_t)); // start dest at Rmod + for (i = (int)(expHighBit - 1); i >= 0; i--) { + // mont square the current total + gsiLargeIntMultM(dest, dest, &mod, modPrime, dest); + digitNum = (gsi_i32)( + i / GS_LARGEINT_DIGIT_SIZE_BITS); // which digit to extract a bit from? + digitBit = (gsi_i32)( + i % + GS_LARGEINT_DIGIT_SIZE_BITS); // which bit to extract from that digit? + // if ((power.mData[k] & (1<mLength; + + memset(temp, 0, sizeof(temp)); + + if (gsi_is_false(gsiLargeIntMult(x->mData, x->mLength, y->mData, y->mLength, + temp, &tempLen, GS_LARGEINT_MAX_DIGITS * 2))) + return gsi_false; + + lasttnptr = &temp[m->mLength - 1]; + lastnptr = &m->mData[m->mLength - 1]; + + if (tempLen < m->mLength * 2) { + memset(&temp[tempLen], 0, + (m->mLength * 2 - tempLen) * GS_LARGEINT_DIGIT_SIZE_BYTES); + // memset(&temp[tempLen], 0, sizeof(temp) - tempLen * + // GS_LARGEINT_DIGIT_SIZE_BYTES); // safer to clear out the whole thing? + tempLen = (l_word)(m->mLength * 2); + } + + for (tptr = &temp[0]; tptr <= lasttnptr; tptr++) { + carry = 0; + mi = (l_word)((l_dword)modPrime * (l_dword)*tptr); + tiptr = tptr; + for (nptr = &m->mData[0]; nptr <= lastnptr; nptr++, tiptr++) { + carry = (l_dword)mi * (l_dword)*nptr + (l_dword)*tiptr + + (l_dword)(l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS); + *tiptr = (l_word)(carry); + } + + // apply the carry value + for (; ((carry >> GS_LARGEINT_DIGIT_SIZE_BITS) > 0) && + tiptr <= &temp[tempLen - 1]; + tiptr++) { + *tiptr = (l_word)( + carry = (l_dword)*tiptr + + (l_dword)(l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS)); + } + + // If we still have a carry, increase the length of temp + if (((carry >> GS_LARGEINT_DIGIT_SIZE_BITS) > 0)) { + *tiptr = (l_word)(carry >> GS_LARGEINT_DIGIT_SIZE_BITS); + tempLen++; + } + } + + // **WARNING** + // Bytes from the plain text message may appear within the temporary buffer. + // These bytes should be cleared to prevent bugs where that data may be + // exposed. (buffer overrun?) + if (gsiLargeIntCompare(&temp[logB_r], tempLen - logB_r, m->mData, + m->mLength) != -1) { + if (gsi_is_false(gsiLargeIntSub(m->mData, m->mLength, &temp[logB_r], + tempLen - logB_r, dest->mData, + &dest->mLength))) { + memset(temp, 0, sizeof(temp)); + memset(dest, 0, sizeof(gsLargeInt_t)); + return gsi_false; + } + } else { + memset(dest, 0, sizeof(gsLargeInt_t)); + dest->mLength = m->mLength; + memcpy(dest->mData, &temp[logB_r], + (tempLen - logB_r) * GS_LARGEINT_DIGIT_SIZE_BYTES); + memset(temp, 0, sizeof(temp)); + } + + return gsi_true; +} + +#else + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Montgomery multiplication +// Computes (src1*src2*r^-1)%mod +// Note: +// This implementation is based on HAC14.36 which has a lot of room for +// improvement FLINT algorithm runs approx 30 times faster. +gsi_bool gsiLargeIntMultM(gsLargeInt_t* x, gsLargeInt_t* y, + const gsLargeInt_t* m, gsi_u32 modPrime, + gsLargeInt_t* dest) { + int i = 0; + l_dword xiy0; + l_word u = 0; + + gsLargeInt_t A; + gsLargeInt_t xiy; + gsLargeInt_t temp; + + GSLINT_ENTERTIMER(GSLintTimerMultM); + + gsiLargeIntStripLeadingZeroes(x); + gsiLargeIntStripLeadingZeroes(y); + + // Check inputs + i = (int)(m->mLength); + while (i > 0 && m->mData[i - 1] == 0) + i--; + if (i == 0) { + // modulus is zero, answer undefined + dest->mData[0] = 0; + dest->mLength = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + if (x->mLength == 0) { + // x == 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_true; + } + if (y->mLength == 0) { + // y == 0 + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_true; + } + + // We pad with zeroes so that we don't have to check for overruns in the loop + // below + // (note: resize will not remove non-zero digits from x or y) + gsiLargeIntResize(x, m->mLength); + gsiLargeIntResize(y, m->mLength); + + // Continue with the Multiplication + memset(&A, 0, sizeof(A)); + memset(&temp, 0, sizeof(temp)); + memset(&xiy, 0, sizeof(xiy)); + + for (i = 0; (gsi_u32)i < m->mLength; i++) { + xiy0 = (l_dword)x->mData[i] * y->mData[0]; // y[0], NOT y[i] !! + u = (l_word)((xiy0 + A.mData[0]) * + modPrime); // strip bits over the first digit + + // A = (A+x[i]*y + u[i]*m)/b + // compute x[i]*y + memset(temp.mData, 0, + y->mLength * sizeof(l_word)); // clear out a portion of temp + temp.mData[0] = x->mData[i]; + temp.mLength = y->mLength; // xi padded with zeroes + if (gsi_is_false(gsiLargeIntMult(temp.mData, temp.mLength, y->mData, + y->mLength, xiy.mData, &xiy.mLength, + GS_LARGEINT_MAX_DIGITS))) { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + // compute u[i]*m + memset(temp.mData, 0, + m->mLength * sizeof(l_word)); // clear out a portion of temp + temp.mData[0] = u; + temp.mLength = m->mLength; + // if (gsi_is_false(gsiLargeIntMult(temp.mData, temp.mLength, m->mData, + // m->mLength, temp.mData, &temp.mLength))) + if (gsi_is_false(gsLargeIntKMult(&temp, m, &temp))) { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + // Add both to A + if (gsi_is_false(gsiLargeIntAdd(xiy.mData, xiy.mLength, A.mData, A.mLength, + A.mData, &A.mLength, + GS_LARGEINT_MAX_DIGITS))) { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + if (gsi_is_false(gsiLargeIntAdd(temp.mData, temp.mLength, A.mData, + A.mLength, A.mData, &A.mLength, + GS_LARGEINT_MAX_DIGITS))) { + // overflow + dest->mLength = 0; + dest->mData[0] = 0; + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_false; + } + // Divide by b (e.g. Remove first digit from A) + if (A.mLength > 1) { + memmove(&A.mData[0], &A.mData[1], (A.mLength - 1) * sizeof(l_word)); + A.mData[A.mLength - 1] = 0; + A.mLength--; + } else { + A.mLength = 0; + A.mData[0] = 0; + } + } + + // if (A >= m then subtract another m) + if (gsiLargeIntCompare(A.mData, A.mLength, m->mData, m->mLength) != -1) + gsiLargeIntSub(m->mData, m->mLength, A.mData, A.mLength, dest->mData, + &dest->mLength); + else + memcpy(dest, &A, sizeof(A)); + GSLINT_EXITTIMER(GSLintTimerMultM); + return gsi_true; +} + +#endif +/* +// Computes (src*src*r^-1)%mod +static gsi_bool gsiLargeIntSquareM(const gsLargeInt_t *src, const gsLargeInt_t +*mod, gsi_u32 modPrime, gsi_u32 R, gsLargeInt_t *dest) +{ + GSI_UNUSED(src); + GSI_UNUSED(mod); + GSI_UNUSED(modPrime); + GSI_UNUSED(R); + GSI_UNUSED(dest); + assert(0); + return gsi_true; +}*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Calculate multiplicative inverse of mod, (-mod^-1 mod 2^R) +// ala. Dusse and Kaliski, extended Euclidean algorithm +gsi_bool gsiLargeIntInverseMod(const gsLargeInt_t* mod, l_word* dest) { + l_dword x = 2; + l_dword y = 1; + l_dword check = 0; + + gsi_u32 i; + for (i = 2; i <= GS_LARGEINT_DIGIT_SIZE_BITS; i++) { + check = (l_dword)mod->mData[0] * (l_dword)y; + if (x < (check & ((x << 1) - 1))) + y += x; + x = x << 1; + } + *dest = (l_word)(x - y); + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntPrint(FILE* logFile, const gsLargeInt_t* lint) { + return gsiLargeIntPrint(logFile, lint->mData, lint->mLength); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsiLargeIntPrint(FILE* logFile, const l_word* data, l_word length) { + while (length > 0) { + fprintf(logFile, "%08X", data[length - 1]); + length--; + } + fprintf(logFile, "\r\n"); + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// stream of bytes, big endian. (first byte = most significant digit) +gsi_bool gsLargeIntSetFromHexString(gsLargeInt_t* lint, const char* hexstream) { + l_word* writePos = lint->mData; + gsi_u32 temp; + int len = 0; + int byteIndex = 0; + + GS_ASSERT(hexstream != NULL); + + len = (int)strlen(hexstream); + if (len == 0) { + lint->mLength = 0; + lint->mData[0] = 0; + return gsi_true; + } + if ((len / 2) > (GS_LARGEINT_MAX_DIGITS * GS_LARGEINT_DIGIT_SIZE_BYTES)) + return gsi_false; + + // 2 characters per byte, 4 bytes per integer + lint->mLength = (l_word)((len + (2 * GS_LARGEINT_DIGIT_SIZE_BYTES - 1)) / + (2 * GS_LARGEINT_DIGIT_SIZE_BYTES)); + lint->mData[lint->mLength - 1] = + 0; // set last byte to zero for left over characters + + while (len > 0) { + if (len >= 2) + sscanf((char*)(hexstream + len - 2), "%02x", + &temp); // sscanf requires a 4 byte dest + else + sscanf((char*)(hexstream + len - 1), "%01x", + &temp); // sscanf requires a 4 byte dest + if (byteIndex == 0) + *writePos = 0; + *writePos |= (temp << (byteIndex * 8)); + if (++byteIndex == GS_LARGEINT_DIGIT_SIZE_BYTES) { + writePos++; + byteIndex = 0; + } + len -= min(2, len); + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Reverse bytes in a LINT, which are LittleEndian +// ex: Packing an RSA message of which the first bytes are 0x00 0x02 +// The first bytes of the packet must become the MSD of the LINT +gsi_bool gsLargeIntReverseBytes(gsLargeInt_t* lint) { +#if defined(GSI_LITTLE_ENDIAN) + char* left = (char*)&lint->mData[0]; + char* right = ((char*)&lint->mData[lint->mLength]) - 1; + char temp; +#else + l_word* left = lint->mData; + l_word* right = lint->mData + (lint->mLength - 1); + l_word temp; +#endif + + if (lint->mLength == 0) + return gsi_true; + + while (left < right) { + temp = *left; + (*left++) = (*right); + (*right--) = temp; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// hashing is made complicated by differing byte orders +void gsLargeIntAddToMD5(const gsLargeInt_t* _lint, MD5_CTX* md5) { + int byteLength = 0; + gsi_u8* dataStart = NULL; + + // Create a non-const copy so we can reverse bytes to add to the MD5 hash + gsLargeInt_t lint; + memcpy(&lint, _lint, sizeof(lint)); + + // first, calculate the byte length + byteLength = (int)gsLargeIntGetByteLength(&lint); + if (byteLength == 0) + return; // no data + + dataStart = (gsi_u8*)lint.mData; + if ((byteLength % GS_LARGEINT_DIGIT_SIZE_BYTES) != 0) + dataStart += GS_LARGEINT_DIGIT_SIZE_BYTES - + (byteLength % GS_LARGEINT_DIGIT_SIZE_BYTES); + + // reverse to big-endian (MS) then hash + gsLargeIntReverseBytes(&lint); + MD5Update(md5, dataStart, (unsigned int)byteLength); + gsLargeIntReverseBytes(&lint); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Length in bytes so leading zeroes can be dropped from hex strings +gsi_u32 gsLargeIntGetByteLength(const gsLargeInt_t* lint) { + int intSize = (int)lint->mLength; + int byteSize = 0; + int i = 0; + l_word mask = 0xFF; + + // skip leading zeroes + while (intSize > 0 && lint->mData[intSize - 1] == 0) + intSize--; + if (intSize == 0) + return 0; // no data + + byteSize = intSize * (gsi_i32)sizeof(l_word); + + // subtract bytes for each leading 0x00 byte + mask = 0xFF; + for (i = 1; i < GS_LARGEINT_DIGIT_SIZE_BYTES; i++) { + if (lint->mData[intSize - 1] <= mask) { + byteSize -= sizeof(l_word) - i; + break; + } + mask = (l_word)((mask << 8) | 0xFF); + } + + return (gsi_u32)byteSize; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Creates a large int from a byte buffer +// Essentially, constructs the array of digits in appropriate byte +// order +gsi_bool gsLargeIntSetFromMemoryStream(gsLargeInt_t* lint, const gsi_u8* data, + gsi_u32 len) { + lint->mData[0] = 0; + memcpy(((char*)lint->mData) + (4 - len % 4) % 4, data, len); + + // Set length to ceiling of len/digit_size + lint->mLength = (unsigned int)((len + (GS_LARGEINT_DIGIT_SIZE_BYTES - 1)) / + GS_LARGEINT_DIGIT_SIZE_BYTES); + + return gsLargeIntReverseBytes(lint); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntWriteToMemoryStream(const gsLargeInt_t* lint, gsi_u8* data) { + gsLargeInt_t copy; + memcpy(©, lint, sizeof(gsLargeInt_t)); + + gsLargeIntReverseBytes(©); + + memcpy(data, copy.mData, copy.mLength * GS_LARGEINT_DIGIT_SIZE_BYTES); + return gsi_true; +} diff --git a/source/gamespy/common/gsLargeInt.h b/source/gamespy/common/gsLargeInt.h new file mode 100644 index 000000000..260601440 --- /dev/null +++ b/source/gamespy/common/gsLargeInt.h @@ -0,0 +1,92 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Large Integer Library +#pragma once + +#include "../md5.h" +#include "gsCommon.h" +#include "gsXML.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// for simplicity, set the binary size to a value > gsCrypt.h binary size +#ifndef GS_LARGEINT_BINARY_SIZE +#define GS_LARGEINT_BINARY_SIZE (2048) // *BIT* size (divide by 8 for byte size) +#endif + +// !!!!!!WARNING!!!!!! Encryption is fastest when digit type is the default +// system type, (ex: gsi_u32 on 32bit processor) +#define GS_LARGEINT_DIGIT_TYPE gsi_u32 +#define GS_LARGEINT_DIGIT_LONG_TYPE gsi_u64 + +#define GS_LARGEINT_DIGIT_SIZE_BYTES (sizeof(GS_LARGEINT_DIGIT_TYPE)) +#define GS_LARGEINT_DIGIT_SIZE_BITS (GS_LARGEINT_DIGIT_SIZE_BYTES * 8) + +// short forms for legibility +#define l_word GS_LARGEINT_DIGIT_TYPE +#define l_dword GS_LARGEINT_DIGIT_LONG_TYPE + +//#define GS_LARGEINT_BYTE_SIZE 32 // binary size of system data type +//#define GS_LARGEINT_INT_SIZE (GS_LARGEINT_BINARY_SIZE/GS_LARGEINT_BYTE_SIZE) +//// size in values +#define GS_LARGEINT_MAX_DIGITS \ + (GS_LARGEINT_BINARY_SIZE / GS_LARGEINT_DIGIT_SIZE_BITS) + +#define GS_LARGEINT_KARATSUBA_CUTOFF 32 + +typedef struct gsLargeInt_s { + GS_LARGEINT_DIGIT_TYPE mLength; + GS_LARGEINT_DIGIT_TYPE mData[GS_LARGEINT_MAX_DIGITS]; +} gsLargeInt_t; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Commonly used functions +void gsLargeIntAddToMD5(const gsLargeInt_t* lint, MD5_CTX* md5); +gsi_bool gsLargeIntSetFromHexString(gsLargeInt_t* lint, const char* hexstring); +gsi_bool gsLargeIntPrint(FILE* logFile, const gsLargeInt_t* lint); +gsi_u32 gsLargeIntGetByteLength(const gsLargeInt_t* lint); + +// Modular exponentiation (and helpers) +// -- uses Montgomery exponentiation, reduction, multiplication +gsi_bool gsLargeIntPowerMod(const gsLargeInt_t* base, const gsLargeInt_t* power, + const gsLargeInt_t* mod, gsLargeInt_t* dest); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsLargeIntSquareMod(const gsLargeInt_t* lint, const gsLargeInt_t* mod, + gsLargeInt_t* dest); +gsi_bool gsLargeIntAdd(const gsLargeInt_t* src1, const gsLargeInt_t* src2, + gsLargeInt_t* dest); +gsi_bool gsLargeIntSub(const gsLargeInt_t* src1, const gsLargeInt_t* fromsrc2, + gsLargeInt_t* dest); +gsi_bool gsLargeIntMult(const gsLargeInt_t* src1, const gsLargeInt_t* src2, + gsLargeInt_t* dest); +gsi_bool gsLargeIntDiv(const gsLargeInt_t* src1, const gsLargeInt_t* divisor, + gsLargeInt_t* dest, gsLargeInt_t* remainder); + +// Karatsuba requires that the sizes be equal and a power of two +gsi_bool gsLargeIntKMult(const gsLargeInt_t* src1, const gsLargeInt_t* src2, + gsLargeInt_t* dest); + +// This is useful when packing a BigEndian message directly into a LittleEndian +// lint buffer. +gsi_bool gsLargeIntReverseBytes(gsLargeInt_t* lint); +gsi_bool gsLargeIntSetValue(gsLargeInt_t* lint, l_word value); + +// These are necessary to preserve byte order when copying from array-of-int to +// char* +gsi_bool gsLargeIntSetFromMemoryStream(gsLargeInt_t* lint, const gsi_u8* data, + gsi_u32 len); +gsi_bool gsLargeIntWriteToMemoryStream(const gsLargeInt_t* lint, gsi_u8* data); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif diff --git a/source/gamespy/common/gsMemory.c b/source/gamespy/common/gsMemory.c new file mode 100644 index 000000000..5ade48226 --- /dev/null +++ b/source/gamespy/common/gsMemory.c @@ -0,0 +1,1560 @@ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsMemory.h" +#include "gsAssert.h" +#include "gsDebug.h" +#include "gsPlatform.h" +#include "gsPlatformUtil.h" + +typedef gsi_u32 gsi_uint; +#define PTR_ALIGNMENT 16 +#define GSI_64BIT (0) + +// To Do: +// Small block optimization using fixed size mempool. +// add multi-threaded support + +#define MEM_PROFILE \ + (1) // if on additional memprofiling code will be enabled for such things as + // high water mark calcs +#if defined(MEM_PROFILE) +#define IF_MEM_PROFILE_ISON(a) a +#else +#define IF_MEM_PROFILE_ISON(a) +#endif + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio +// Level4, "conditional expression is constant". +// Occurs with use of the MS provided macro FD_SET +#pragma warning(disable : 4127) +#include +#endif // _MSC_VER + +#define MEM_MANAGER_CALL + +typedef struct { + void*(MEM_MANAGER_CALL* malloc)(size_t size); + void(MEM_MANAGER_CALL* free)(void* ptr); + void*(MEM_MANAGER_CALL* realloc)(void* ptr, size_t size); + void*(MEM_MANAGER_CALL* memalign)(size_t boundary, size_t size); +} MemManagerCallbacks; + +static void* MEM_MANAGER_CALL _gsi_malloc(size_t size) { return malloc(size); } + +static void MEM_MANAGER_CALL _gsi_free(void* ptr) { free(ptr); } + +static void* MEM_MANAGER_CALL _gsi_realloc(void* ptr, size_t size) { + return realloc(ptr, size); +} + +// no built in system memalign +static void* _gsi_memalign(size_t boundary, size_t size) { + void* ptr = calloc((size) / (boundary), (boundary)); + // check alignment + GS_ASSERT((((gsi_u32)ptr) % boundary) == 0); + return ptr; +} + +static MemManagerCallbacks memmanagercallbacks = { + &_gsi_malloc, &_gsi_free, &_gsi_realloc, &_gsi_memalign}; + +void gsiMemoryCallbacksSet(gsMallocCB p_malloc, gsFreeCB p_free, + gsReallocCB p_realloc, gsMemalignCB p_memalign) { + + memmanagercallbacks.malloc = p_malloc; + memmanagercallbacks.free = p_free; + memmanagercallbacks.realloc = p_realloc; + memmanagercallbacks.memalign = p_memalign; +} + +// These functions shunt to virtual function pointer +void* gsimalloc(size_t size) { return (*memmanagercallbacks.malloc)(size); } +void* gsirealloc(void* ptr, size_t size) { + return (*memmanagercallbacks.realloc)(ptr, size); +} +void gsifree(void* ptr) { + if (ptr == NULL) + return; + (*memmanagercallbacks.free)(ptr); +} +void* gsimemalign(size_t boundary, size_t size) { + return (*memmanagercallbacks.memalign)(boundary, size); +} + +#ifdef GSI_MEM_MANAGED + +/***************************************************************************/ +/* + + Random Access Memory Pool + +*/ +/***************************************************************************/ + +// Context Stack +#define MEM_CONTEXT_STACK_MAX 10 // max stack depth +static gsMemMgrContext MemTypeStack[MEM_CONTEXT_STACK_MAX] = { + gsMemMgrContext_Default}; +static gsi_u32 MemTypeStackIndex = 0; +extern gsMemMgrContext gsMemMgrContextCurrent; + +// Memtype Tag stack +#define MEM_TAG_STACK_MAX 10 // max stack depth +static gsi_u8 MemTagStack[MEM_TAG_STACK_MAX] = {0}; +static gsi_u32 MemTagStackIndex = 0; + +// ToDo: +// - Add 64 bit pointer support + +// Default pointer alignment. Must be 16, 32, 64, 128, or 256 bytes. +// i.e. malloc (x) = memalign(default alignment,x); + +#define MEM_IS_POWER_OF_2(x) (((x) & ((x)-1)) == 0) +#define MEMALIGN_POWEROF2(x, a) \ + (((gsi_uint)(x) + (a - 1)) & ~(((gsi_uint)(a)) - 1)) + +#if (1) // enable assert, otherwise this runs faster +#define MP_ASSERT(x) GS_ASSERT(x) +#else +#define MP_ASSERT(x) +#endif + +#define MEM_TYPES_MAX 127 + +typedef struct { + gsi_u32 MemTotal; + gsi_u32 MemAvail; + gsi_u32 MemUsed; + gsi_u32 MemUsed_At_HighWater; + gsi_u32 MemWasted; // overhead memory + memory lost due to fragmentation. + + gsi_u32 ChunksCount; // number of ChunkHeaders in linked list. + gsi_u32 ChunksFreeCount; // number of free ChunkHeaders in linked list. + gsi_u32 ChunksFreeLargestAvail; + // these are the same as handles + gsi_u32 ChunksUsedCount; // number of ChunkHeaders which are in use. + gsi_u32 ChunksUsedCount_At_HighWater; // the most handles used at any one time + + // memtype specifics + gsi_u32 MemType_ChunksCount[MEM_TYPES_MAX]; + gsi_u32 MemType_MemUsed[MEM_TYPES_MAX]; + gsi_u32 MemType_MemUsed_At_HighWater[MEM_TYPES_MAX]; + +} MEM_STATS; + +void MEM_STATSAddAll(MEM_STATS* _this, const MEM_STATS* ms); +void MEM_STATSClear(MEM_STATS* _this); +// except HW +void MEM_STATSClearAll(MEM_STATS* _this); + +// RA_MEM_CHUNK +typedef struct tMEM_CHUNK { + + // private + union { + gsi_uint MemUsed; // size used by application. ex// malloc(size) +#ifdef GS_BIG_ENDIAN + struct { +#if (GSI_64BIT) + char pad[7], MemType; +#else + char pad[3], MemType; +#endif + } MEM_TypeStruct; +#else + struct { +#if (GSI_64BIT) + char MemType, pad[7]; +#else + char MemType, pad[3]; +#endif + } MEM_TypeStruct; +#endif + } MEM_UsageStat; + + // public: + // double linked list of all chunks + struct tMEM_CHUNK* prev; + struct tMEM_CHUNK* next; // next chunk + // single linked list of free chunks + struct tMEM_CHUNK* NextFree; // next free chunk +} MEM_CHUNK; + +/***************************************/ +// flag as in use, set size, memtype +void MEM_CHUNKAlloc(MEM_CHUNK* _this, gsi_u8 _MemType, size_t _UsedSize) { + _UsedSize = MEMALIGN_POWEROF2( + _UsedSize, 4); // The lower 2 bits are zero, so we don't store them. + GS_ASSERT_STR(_UsedSize < 0x3FFFFFC, "Alloc Memory size is too big."); + _this->MEM_UsageStat.MemUsed = _UsedSize << 6; + _this->MEM_UsageStat.MEM_TypeStruct.MemType = _MemType; +} +void MEM_CHUNKFree(MEM_CHUNK* _this) { _this->MEM_UsageStat.MemUsed = 0; } + +/***************************************/ +// returns true if not in use +gsi_bool MEM_CHUNKIsFree(MEM_CHUNK* _this) { + return (_this->MEM_UsageStat.MemUsed == 0); +} + +/***************************************/ +gsi_u32 MEM_CHUNKTotalSizeGet(MEM_CHUNK* _this) +// Total size chunk is using up, including header. +{ + if (!_this->next) { + return PTR_ALIGNMENT + sizeof(MEM_CHUNK) /*Nub*/; + } + return (gsi_uint)_this->next - (gsi_uint)_this; +} + +/***************************************/ +gsi_u32 MEM_CHUNKChunkSizeGet(MEM_CHUNK* _this) +// size of chunk, without header. "Available memory" +{ + if (!_this->next) + return PTR_ALIGNMENT; /*Nub*/ + ; + return (gsi_uint)_this->next - (gsi_uint)_this - sizeof(MEM_CHUNK); +} + +gsi_u32 MEM_CHUNKMemUsedGet(MEM_CHUNK* _this) { + return (_this->MEM_UsageStat.MemUsed & ~0xFF) >> 6; +} + +void MEM_CHUNKMemUsedSet(MEM_CHUNK* _this, gsi_u32 size) { + _this->MEM_UsageStat.MemUsed = (MEMALIGN_POWEROF2(size, 4) << 6) + + _this->MEM_UsageStat.MEM_TypeStruct.MemType; +} + +gsi_u32 MEM_CHUNKMemAvailGet(MEM_CHUNK* _this) { + return MEM_CHUNKChunkSizeGet(_this) - MEM_CHUNKMemUsedGet(_this); +} + +char MEM_CHUNKMemTypeGet(MEM_CHUNK* _this) { + return _this->MEM_UsageStat.MEM_TypeStruct.MemType; +} + +void MEM_CHUNKMemTypeSet(MEM_CHUNK* _this, char _MemType) { + GS_ASSERT(_MemType < MEM_TYPES_MAX); + _this->MEM_UsageStat.MEM_TypeStruct.MemType = _MemType; +} + +void* MEM_CHUNKMemPtrGet(MEM_CHUNK* _this) { + return (void*)((gsi_uint)_this + sizeof(MEM_CHUNK)); +} + +/*inline */ MEM_CHUNK* Ptr_To_MEM_CHUNK(void* ptr) { + return ((MEM_CHUNK*)ptr) - 1; +} + +/***************************************/ +/***************************************/ +typedef struct MEM_CHUNK_POOL { + // public: + char Name[20]; // name of this pool. Used for debug purposes + // private: + MEM_CHUNK* HeaderStart; + MEM_CHUNK* HeaderEnd; + MEM_CHUNK* pFirstFree; + gsi_u32 HeapSize; +#if MEM_PROFILE + gsi_u32 HWMemUsed; + gsi_u32 MemUsed; +#endif +} MEM_CHUNK_POOL; + +// private +MEM_CHUNK* MEM_CHUNK_POOLFindPreviousFreeChunk(MEM_CHUNK_POOL* _this, + MEM_CHUNK* header); +MEM_CHUNK* MEM_CHUNK_POOLFindNextFreeChunk(MEM_CHUNK_POOL* _this, + MEM_CHUNK* header); +void MEM_CHUNK_POOLSplitChunk(MEM_CHUNK_POOL* _this, MEM_CHUNK* header, + gsi_bool ReAlloc); +void MEM_CHUNK_POOLFreeChunk(MEM_CHUNK_POOL* _this, MEM_CHUNK* header); +MEM_CHUNK* MEM_CHUNK_POOLAllocChunk( + MEM_CHUNK_POOL* _this, size_t Size, int Alignment, + gsi_bool Backwards); // int Alignment = PTR_ALIGNMENT, + // gsi_bool Backwards = gsi_false); + +// move a chunk within the limits of prev + prev_size and next - this_size +void MEM_CHUNK_POOLChunkMove(MEM_CHUNK_POOL* _this, MEM_CHUNK* oldpos, + MEM_CHUNK* newpos); + +// public +/***************************************/ +void MEM_CHUNK_POOLCreate(MEM_CHUNK_POOL* _this, const char* szName, char* ptr, + gsi_u32 _size); +void MEM_CHUNK_POOLDestroy(MEM_CHUNK_POOL* _this); +gsi_bool MEM_CHUNK_POOLIsValid(MEM_CHUNK_POOL* _this) { + return _this->HeapSize > 0; +} + +/***************************************/ +void* MEM_CHUNK_POOLmalloc(MEM_CHUNK_POOL* _this, size_t Size, + gsi_i32 Alignment); //= PTR_ALIGNMENT); +// allocated backwards from top of heap +void* MEM_CHUNK_POOLmalloc_backwards(MEM_CHUNK_POOL* _this, size_t Size, + gsi_i32 Alignment); //= PTR_ALIGNMENT); +void* MEM_CHUNK_POOLrealloc(MEM_CHUNK_POOL* _this, void* oldmem, + size_t newSize); +void MEM_CHUNK_POOLfree(MEM_CHUNK_POOL* _this, void* mem); + +/***************************************/ +void MEM_CHUNK_POOLCheckValidity(MEM_CHUNK_POOL* _this); +void MEM_CHUNK_POOLMemStatsGet(MEM_CHUNK_POOL* _this, MEM_STATS* stats); +gsi_u32 MEM_CHUNK_POOLWalkForType(MEM_CHUNK_POOL* _this, int _MemType, + gsi_bool _LogUse); + +// returns true if this is a valid heap ptr +gsi_bool MEM_CHUNK_POOLIsHeapPtr(MEM_CHUNK_POOL* _this, void* mem); + +/***************************************/ +// add to table, filling in memtype . +void MEM_CHUNK_POOLFillMemoryTable(MEM_CHUNK_POOL* _this, char* Table, + const int TableSize, gsi_u32 _HeapStart, + gsi_u32 _HeapSize); + +/***************************************/ +// returns true if mem handle is in range of heap +gsi_bool MEM_CHUNK_POOLItemIsInPoolMemory(MEM_CHUNK_POOL* _this, void* ptr) { + GS_ASSERT(MEM_CHUNK_POOLIsValid(_this)); + return (((gsi_uint)ptr >= (gsi_uint)MEM_CHUNKMemPtrGet(_this->HeaderStart)) && + ((gsi_uint)ptr <= (gsi_uint)MEM_CHUNKMemPtrGet(_this->HeaderEnd))); +} + +void MEM_STATSAddAll(MEM_STATS* _this, const MEM_STATS* ms) { + int i; + _this->MemTotal += ms->MemTotal; + _this->MemAvail += ms->MemAvail; + _this->MemUsed += ms->MemUsed; + _this->MemUsed_At_HighWater += ms->MemUsed_At_HighWater; + _this->MemWasted += ms->MemWasted; + _this->ChunksCount += ms->ChunksCount; + _this->ChunksFreeCount += ms->ChunksFreeCount; + _this->ChunksFreeLargestAvail += ms->ChunksFreeLargestAvail; + _this->ChunksUsedCount += ms->ChunksUsedCount; + _this->ChunksUsedCount_At_HighWater += ms->ChunksUsedCount_At_HighWater; + for (i = 0; i < MEM_TYPES_MAX; i++) { + _this->MemType_ChunksCount[i] += ms->MemType_ChunksCount[i]; + _this->MemType_MemUsed[i] += ms->MemType_MemUsed[i]; + } +} + +void MEM_STATSClear(MEM_STATS* _this) +// except HW +{ + _this->MemTotal = 0; + _this->MemAvail = 0; + _this->MemUsed = 0; + _this->MemWasted = 0; + _this->ChunksCount = 0; + _this->ChunksFreeCount = 0; + _this->ChunksFreeLargestAvail = 0; + _this->ChunksUsedCount = 0; + + memset(_this->MemType_ChunksCount, 0, 4 * MEM_TYPES_MAX); + memset(_this->MemType_MemUsed, 0, 4 * MEM_TYPES_MAX); +} + +void MEM_STATSClearAll(MEM_STATS* _this) { + int i; + MEM_STATSClear(_this); + _this->MemUsed_At_HighWater = 0; + for (i = 0; i < MEM_TYPES_MAX; i++) + _this->MemType_MemUsed_At_HighWater[i] = 0; + _this->ChunksUsedCount_At_HighWater = 0; +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLChunkMove(MEM_CHUNK_POOL* _this, MEM_CHUNK* oldpos, + MEM_CHUNK* newpos) +//-------------------------------------------------------------------------- +{ + MEM_CHUNK* firstfree; + // todo!!! + MEM_CHUNK temp = *oldpos; + + // can not be end/start chunk + MP_ASSERT(oldpos->prev) + MP_ASSERT(oldpos->next) + + // check if within movement limits + MP_ASSERT((gsi_uint)newpos <= (gsi_uint)oldpos->next - + MEM_CHUNKMemUsedGet(oldpos) - + sizeof(MEM_CHUNK)) + MP_ASSERT((gsi_uint)newpos >= (gsi_uint)oldpos->prev + + MEM_CHUNKMemUsedGet(oldpos->prev) + + sizeof(MEM_CHUNK)) + + // check if alignment is valid + MP_ASSERT((((gsi_uint)newpos) % sizeof(MEM_CHUNK)) == 0) + + *newpos = temp; + + // link into chunk list + newpos->prev->next = newpos; + newpos->next->prev = newpos; + + // Fix links in free chunk list + if (MEM_CHUNKIsFree(newpos)) { + + if (_this->pFirstFree == oldpos) + _this->pFirstFree = newpos; + else { + firstfree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this, newpos->prev); + if (firstfree != newpos) + firstfree->NextFree = newpos; + else { + // first in list. + _this->pFirstFree = newpos; + } + + MP_ASSERT((newpos->NextFree == NULL) || + ((gsi_uint)newpos->NextFree > (gsi_uint)newpos)) + } + } +} + +void MEM_CHUNK_POOLDestroy(MEM_CHUNK_POOL* _this) { + memset(_this, 0, sizeof(MEM_CHUNK_POOL)); +} +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLCreate(MEM_CHUNK_POOL* _this, const char* szNameIn, + char* ptr, gsi_u32 size) +//-------------------------------------------------------------------------- +{ + int len; + MEM_CHUNK* HeaderMid; + MP_ASSERT(((gsi_uint)ptr & 15) == 0) // ensure 16 byte alignment + + // Copy limited length name + len = strlen(szNameIn) + 1; + if (len > 20) + len = 20; + memcpy(_this->Name, szNameIn, len); + _this->Name[19] = '\0'; // in case str is too long. + + // create two nubs, at start, and end, with a chunk in between + MP_ASSERT(size > 48 + 3 * sizeof(MEM_CHUNK)) + + _this->HeaderStart = (MEM_CHUNK*)(ptr); + HeaderMid = (MEM_CHUNK*)(ptr + 2 * sizeof(MEM_CHUNK)); + _this->HeaderEnd = (MEM_CHUNK*)(ptr + size - 2 * sizeof(MEM_CHUNK)); + + // Bogus nub which is never freed. + _this->HeaderStart->prev = NULL; + _this->HeaderStart->next = HeaderMid; + _this->HeaderStart->NextFree = HeaderMid; + MEM_CHUNKAlloc(_this->HeaderStart, 0, + sizeof(MEM_CHUNK)); // don't mark as free + + // Here is our real heap, after before and after overhead + HeaderMid->prev = _this->HeaderStart; + HeaderMid->next = _this->HeaderEnd; + HeaderMid->NextFree = 0; + MEM_CHUNKFree(HeaderMid); + + // Bogus nub which is never freed. + _this->HeaderEnd->prev = HeaderMid; + _this->HeaderEnd->next = NULL; + _this->HeaderEnd->NextFree = NULL; + MEM_CHUNKAlloc(_this->HeaderEnd, 0, sizeof(MEM_CHUNK)); // don't mark as free + + _this->HeapSize = size; + _this->pFirstFree = HeaderMid; +} + +//-------------------------------------------------------------------------- +MEM_CHUNK* MEM_CHUNK_POOLFindPreviousFreeChunk(MEM_CHUNK_POOL* _this, + MEM_CHUNK* header) +// find previous free chunk +// return NULL if start header is not free, and there is nothing free before +// it. return header if start header is first free chunk +{ + while ((header) && (!MEM_CHUNKIsFree(header))) { + // GS_ASSERT(header->prev == NULL || (header->prev >= _this->HeaderStart && + // header->prev <= _this->HeaderEnd)); + header = header->prev; + } + + GSI_UNUSED(_this); + return header; +} + +//-------------------------------------------------------------------------- +MEM_CHUNK* MEM_CHUNK_POOLFindNextFreeChunk(MEM_CHUNK_POOL* _this, + MEM_CHUNK* header_in) +// find previous free chunk +// return NULL if no next free chunk. +{ + MEM_CHUNK* header = header_in; + while ((header) && (!MEM_CHUNKIsFree(header))) { + header = header->next; + } + if (header == header_in) + return NULL; + + GSI_UNUSED(_this); + return header; +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLSplitChunk(MEM_CHUNK_POOL* _this, MEM_CHUNK* header, + gsi_bool ReAlloc) +// split a used chunk into two if the UsedSize is smaller then the ChunkSize +//-------------------------------------------------------------------------- +{ + MEM_CHUNK* next; + MEM_CHUNK* PrevFree; + MEM_CHUNK* NewHeader; + + // calc new position at end of used mem + NewHeader = (MEM_CHUNK*)((gsi_u8*)header + MEM_CHUNKMemUsedGet(header) + + sizeof(MEM_CHUNK)); + NewHeader = (MEM_CHUNK*)MEMALIGN_POWEROF2(NewHeader, sizeof(MEM_CHUNK)); + + // assert we have enough room for this new chunk + MP_ASSERT((gsi_uint)NewHeader + 2 * sizeof(MEM_CHUNK) <= + (gsi_uint)header->next) + +// update some stats +#if (MEM_PROFILE) + if (ReAlloc) { + // 09-OCT-07 BED: Since we're splitting the chunk, it seems more accurate + // to use the full size of the chunk, not just the used + // portion + _this->MemUsed -= MEM_CHUNKChunkSizeGet(header); + //_this->MemUsed -= MEM_CHUNKMemUsedGet(header); + GS_ASSERT(_this->MemUsed >= 0); + } +#endif + + // Can this new chunk fit in the current one? + // create a new chunk header, at the end of used space, plus enough to align + // us to 16 bytes + + // Splice into linked list + NewHeader->prev = header; + NewHeader->next = header->next; + MEM_CHUNKFree(NewHeader); + + if (NewHeader->next) { + NewHeader->next->prev = NewHeader; + } + + header->next = NewHeader; + + // Splice into free chunks linked list + + // this need to merge can happen on a realloc before a free chunk + if (MEM_CHUNKIsFree(NewHeader->next)) { + MP_ASSERT(ReAlloc) + + // merge and splice + next = NewHeader->next->next; + next->prev = NewHeader; + + NewHeader->NextFree = NewHeader->next->NextFree; + NewHeader->next = next; + } else { + if (ReAlloc) { + // on a realloc, this next value is useless + NewHeader->NextFree = + MEM_CHUNK_POOLFindNextFreeChunk(_this, NewHeader->next); + } else + NewHeader->NextFree = header->NextFree; + } + + if (_this->pFirstFree == header) { + // this is first free chunk + _this->pFirstFree = NewHeader; + } else { + // link previous free chunk to this one. + PrevFree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this, header); + if (PrevFree) + PrevFree->NextFree = NewHeader; + else + // this is first free chunk + _this->pFirstFree = NewHeader; + } + +#if (MEM_PROFILE) + if (ReAlloc) { + _this->MemUsed += MEM_CHUNKMemUsedGet(header); + // update highwater mark + if (_this->MemUsed > _this->HWMemUsed) + _this->HWMemUsed = _this->MemUsed; + + GS_ASSERT(_this->MemUsed <= _this->HeapSize); + } +#endif + +#ifdef _DEBUG_ + header->NextFree = NULL; +#endif +} + +//-------------------------------------------------------------------------- +gsi_bool MEM_CHUNK_POOLIsHeapPtr(MEM_CHUNK_POOL* _this, void* mem) +// returns true if this is a valid heap ptr +{ + MEM_CHUNK* headertofind = Ptr_To_MEM_CHUNK(mem); + MEM_CHUNK* header = _this->HeaderStart; + + while (header) { + header = header->next; + if (headertofind == header) + return gsi_true; + } + + return gsi_false; +} + +//-------------------------------------------------------------------------- +MEM_CHUNK* MEM_CHUNK_POOLAllocChunk(MEM_CHUNK_POOL* _this, size_t Size, + gsi_i32 Alignment, gsi_bool Backwards) +// size = requested size from app. + +// Find first chunk that will fit, +// allocate from it, splitting it +// merge split with next free chunk, if next chunk is free +//-------------------------------------------------------------------------- +{ + gsi_u32 Ptr; + gsi_u32 AlignedPtr; + int delta; + MEM_CHUNK* PrevFree; + int total_size; + int MemRemain; + MEM_CHUNK* alignedheader; + + MEM_CHUNK* header; + gsi_u32 SizeNeeded = Size + sizeof(MEM_CHUNK); + SizeNeeded = MEMALIGN_POWEROF2( + SizeNeeded, sizeof(MEM_CHUNK)); // must be aligned to this at least!!! + + MP_ASSERT(Size) + MP_ASSERT(MEM_IS_POWER_OF_2(Alignment)) // must be power of two!!! + MP_ASSERT(Alignment >= PTR_ALIGNMENT) + + // Backwards = gsi_false; + + if (Backwards) + header = MEM_CHUNK_POOLFindPreviousFreeChunk(_this, _this->HeaderEnd); + else + header = _this->pFirstFree; + + // should all be free chunks linked from here in. + while (header) { + // is this chunk available + MP_ASSERT(MEM_CHUNKIsFree(header)) + + // Calc memory left in this chunk after we alloc + total_size = MEM_CHUNKTotalSizeGet(header); + MemRemain = total_size - SizeNeeded; + + // can we fit? + if (MemRemain >= 0) { + // are we aligned properly? + Ptr = (gsi_uint)MEM_CHUNKMemPtrGet(header); + AlignedPtr = MEMALIGN_POWEROF2(Ptr, Alignment); + delta = AlignedPtr - Ptr; + if (delta) { + // we need to move free chunk over by ptr. + if (MemRemain < delta) { + // not enough space in this chunk + header = header->NextFree; + continue; + } + + // move the chunk over so that the pointer is aligned. + alignedheader = Ptr_To_MEM_CHUNK((void*)(gsi_uint)AlignedPtr); + MEM_CHUNK_POOLChunkMove(_this, header, alignedheader); + header = alignedheader; + MemRemain -= delta; + } + + // at this point we've taken this chunk, and need to split off the unused + // part in theory, there should be no other free chunk ahead of us. + + MEM_CHUNKAlloc(header, MemTagStack[MemTagStackIndex], Size); + + // split as needed + if (MemRemain > sizeof(MEM_CHUNK) * 2) { + + // split chunk, this will handle free chunk pointer list + MEM_CHUNK_POOLSplitChunk(_this, header, gsi_false); + } else { + // remove from free list + if (_this->pFirstFree == header) { + // this is first free chunk + _this->pFirstFree = header->NextFree; + + } else { + // link previous free chunk to this one. + PrevFree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this, header); + if (PrevFree) + PrevFree->NextFree = header->NextFree; + else + _this->pFirstFree = header->NextFree; + } + } + { +#if (MEM_PROFILE) + _this->MemUsed += MEM_CHUNKMemUsedGet(header); + // update highwater mark + if (_this->MemUsed > _this->HWMemUsed) + _this->HWMemUsed = _this->MemUsed; + + GS_ASSERT(_this->MemUsed <= _this->HeapSize); +#endif + } + return header; + } + if (Backwards) + header = MEM_CHUNK_POOLFindPreviousFreeChunk(_this, header); + else + header = header->NextFree; + } + // not crashing here. + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, + " Could not allocate %i bytes\n", Size); + GS_ASSERT_STR(0, "Out of memory"); //(_this->Name); + + return NULL; +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLFreeChunk(MEM_CHUNK_POOL* _this, MEM_CHUNK* header) +// set chunk as free +// merge if possible with prev and next +// adding chunk to free chunks list. +//-------------------------------------------------------------------------- +{ + + MEM_CHUNK* prev = header; + MEM_CHUNK* next = header; + MEM_CHUNK* PrevFree; + +#if (MEM_PROFILE) + _this->MemUsed -= MEM_CHUNKMemUsedGet(header); + GS_ASSERT(_this->MemUsed >= 0); +#endif + + while (next->next && (MEM_CHUNKIsFree(next->next))) { + next = next->next; + } + + while (prev->prev && (MEM_CHUNKIsFree(prev->prev))) { + prev = prev->prev; + } + + if (prev != next) { + // merge + // prev becomes the new chunk. + prev->next = next->next; + + if (next->next) + next->next->prev = prev; + } + + // since this is now a free chunk, we must add it to the free chunk list + + // find previous free + PrevFree = MEM_CHUNK_POOLFindPreviousFreeChunk(_this, prev); + if (PrevFree == NULL) { + // this is first free chunk + _this->pFirstFree = prev; + + } else { + // link previous free chunk to this one. + PrevFree->NextFree = prev; + } + + // find and set next free chunk + if (next->next) + prev->NextFree = MEM_CHUNK_POOLFindNextFreeChunk(_this, next->next); + else + prev->NextFree = NULL; + + MEM_CHUNKFree(prev); + +#if (0) + // ToDo: steal unused memory from previous used chunk + gsi_u32 destptr = + (gsi_u32)prev->prev + prev->prev->MemAvailGet() + sizeof(MEM_CHUNK); + destptr = MEMALIGN_POWEROF2(destptr, sizeof(MEM_CHUNK)); + + // we can move back to this ptr. Is it worth it? + if (destptr < (gsi_u32)prev) + ChunkMove(prev, (MEM_CHUNK*)destptr); +#endif +} + +//-------------------------------------------------------------------------- +void* MEM_CHUNK_POOLmalloc(MEM_CHUNK_POOL* _this, size_t Size, + gsi_i32 Alignment) +//-------------------------------------------------------------------------- +{ + void* mem; + + // return ptr to the first block big enough + MEM_CHUNK* header = + MEM_CHUNK_POOLAllocChunk(_this, Size, Alignment, gsi_false); + + if (header) { + // alloc new chunk + mem = MEM_CHUNKMemPtrGet(header); + return mem; + } + + return NULL; +} + +//-------------------------------------------------------------------------- +void* MEM_CHUNK_POOLmalloc_backwards(MEM_CHUNK_POOL* _this, size_t Size, + gsi_i32 Alignment) +//-------------------------------------------------------------------------- +{ + void* mem; + + // return ptr to the first block big enough + MEM_CHUNK* header = + MEM_CHUNK_POOLAllocChunk(_this, Size, Alignment, gsi_true); + + if (header) { + // alloc new chunk + mem = MEM_CHUNKMemPtrGet(header); + return mem; + } + + return NULL; +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLfree(MEM_CHUNK_POOL* _this, void* mem) +// return 0 if memory freed in this call +// else return mem value passed in +//-------------------------------------------------------------------------- +{ + MEM_CHUNK* header = Ptr_To_MEM_CHUNK(mem); + MEM_CHUNK_POOLFreeChunk(_this, header); +} + +//-------------------------------------------------------------------------- +void* MEM_CHUNK_POOLrealloc(MEM_CHUNK_POOL* _this, void* oldmem, size_t newSize) +//-------------------------------------------------------------------------- +{ + MEM_CHUNK* oldheader; + MEM_CHUNK* NewHeader; + gsi_u32 OldSize; + char MemType; + + MP_ASSERT(newSize) + + if (!oldmem) { + return MEM_CHUNK_POOLmalloc(_this, newSize, PTR_ALIGNMENT); + } + + oldheader = Ptr_To_MEM_CHUNK(oldmem); + OldSize = MEM_CHUNKMemUsedGet(oldheader); + + if (newSize == OldSize) + return oldmem; + + if (newSize < OldSize) { + + if ((newSize + 2 * sizeof(MEM_CHUNK)) > OldSize) { + // not enough room to create another chunk, can't shrink + return oldmem; + } + + // shrink it + MEM_CHUNKMemUsedSet(oldheader, newSize); + MEM_CHUNK_POOLSplitChunk(_this, oldheader, gsi_true); + return MEM_CHUNKMemPtrGet(oldheader); + } else { + // get a new chunk + MemType = MEM_CHUNKMemTypeGet(oldheader); + MEM_CHUNK_POOLFreeChunk(_this, oldheader); + NewHeader = + MEM_CHUNK_POOLAllocChunk(_this, newSize, PTR_ALIGNMENT, gsi_false); + MEM_CHUNKMemTypeSet(NewHeader, MemType); + + memmove(MEM_CHUNKMemPtrGet(NewHeader), oldmem, OldSize); + + return MEM_CHUNKMemPtrGet(NewHeader); + } +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLMEM_CHUNK_POOL(MEM_CHUNK_POOL* _this) +//-------------------------------------------------------------------------- +{ + _this->Name[0] = 0; + _this->HeaderEnd = NULL; + _this->HeaderStart = NULL; + _this->HeapSize = 0; + _this->pFirstFree = NULL; +} + +//-------------------------------------------------------------------------- +gsi_u32 MEM_CHUNK_POOLWalkForType(MEM_CHUNK_POOL* _this, int type, + gsi_bool _LogUse) +//-------------------------------------------------------------------------- +{ + MEM_CHUNK* header; + gsi_u32 Total = 0; + header = _this->HeaderStart; + + while (header) { + MP_ASSERT((header->next == NULL) || + ((gsi_uint)header < + (gsi_uint)header->next)) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || + ((gsi_uint)header->prev < + (gsi_uint)header)) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || + (header->prev->next == header)) // previous linked correctly to us + MP_ASSERT( + (header->next == NULL) || + (header->next->prev == header)) // next linked correctly to us + MP_ASSERT(MEM_CHUNKMemUsedGet(header) <= + MEM_CHUNKChunkSizeGet(header)) // using too much mem + + if (!MEM_CHUNKIsFree(header) && (MEM_CHUNKMemTypeGet(header) == type)) { + // Don't log a message for the HeaderStart and HeaderEnd blocks. + if ((header != _this->HeaderStart) && (header != _this->HeaderEnd)) { + // Used Chunk + Total += MEM_CHUNKTotalSizeGet(header); + if (_LogUse) { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, + GSIDebugLevel_Notice, + "MemFound ptr:0x%8x size:%8u %s\n", + MEM_CHUNKMemPtrGet(header), MEM_CHUNKMemUsedGet(header), + MemMgrBufferGetName((gsMemMgrContext)type)); + } + } + } + + // make sure we hit the correct end + MP_ASSERT(header->next || (header == _this->HeaderEnd)) + header = header->next; + } + return Total; +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLMemStatsGet(MEM_CHUNK_POOL* _this, MEM_STATS* pS) { + int ChunksFreeLostCount; + int i, type; + MEM_CHUNK* header; + MEM_CHUNK* NextFree; + MEM_STATSClear(pS); + + // check free chunk linked list + header = _this->HeaderStart; + NextFree = _this->pFirstFree; + + /*** Test validity of all chunks chain ***/ + while (header) { + MP_ASSERT((header->next == NULL) || + ((gsi_uint)header < + (gsi_uint)header->next)) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || + ((gsi_uint)header->prev < + (gsi_uint)header)) // infinite loop or out of place + MP_ASSERT((header->prev == NULL) || + (header->prev->next == header)) // previous linked correctly to us + MP_ASSERT( + (header->next == NULL) || + (header->next->prev == header)) // next linked correctly to us + MP_ASSERT(MEM_CHUNKMemUsedGet(header) <= + MEM_CHUNKChunkSizeGet(header)) // using too much mem + + pS->MemTotal += MEM_CHUNKTotalSizeGet(header); + if (!MEM_CHUNKIsFree(header)) { + // Used Chunk + pS->ChunksUsedCount++; + if (pS->ChunksUsedCount_At_HighWater < pS->ChunksUsedCount) + pS->ChunksUsedCount_At_HighWater = pS->ChunksUsedCount; + + // calc overhead and waste + pS->MemWasted += + MEM_CHUNKTotalSizeGet(header) - MEM_CHUNKMemUsedGet(header); + pS->MemUsed += MEM_CHUNKTotalSizeGet(header); + + type = MEM_CHUNKMemTypeGet(header); + pS->MemType_MemUsed[type] += MEM_CHUNKTotalSizeGet(header); + pS->MemType_ChunksCount[type]++; + + } else { + // free chunk + MP_ASSERT((header->NextFree == NULL) || + ((gsi_uint)header < + (gsi_uint)header->NextFree)) // infinite loop or out of place + + // make sure we aren't fragmented, as this ruins some algorithm + // assumptions + MP_ASSERT( + (header->next == NULL) || + (!MEM_CHUNKIsFree(header->next))) // infinite loop or out of place + MP_ASSERT( + (header->prev == NULL) || + (!MEM_CHUNKIsFree(header->prev))) // infinite loop or out of place + + // previous free chunk linked correctly to us, we aren't a lost chunk + MP_ASSERT(header == NextFree) + NextFree = header->NextFree; + + // calc overhead and waste (in this case, the same + // value...sizeof(MEM_CHUNK) header) + pS->MemWasted += + MEM_CHUNKTotalSizeGet(header) - MEM_CHUNKChunkSizeGet(header); + pS->MemUsed += + MEM_CHUNKTotalSizeGet(header) - MEM_CHUNKChunkSizeGet(header); + + pS->ChunksFreeCount++; + if (pS->ChunksFreeLargestAvail < MEM_CHUNKChunkSizeGet(header)) + pS->ChunksFreeLargestAvail = MEM_CHUNKChunkSizeGet(header); + } + + pS->ChunksCount++; + + // make sure we hit the correct end + MP_ASSERT(header->next || (header == _this->HeaderEnd)) + header = header->next; + } + + // Check free chunks + header = _this->HeaderStart; + + /*** Test validity of free chunks chain ***/ + // Walk heap looking for first free chunk, + while (header && (!MEM_CHUNKIsFree(header))) + header = header->next; + + // make sure the first free one is linked correctly + MP_ASSERT(_this->pFirstFree == header) + + ChunksFreeLostCount = pS->ChunksFreeCount; + while (header) { + // add up sizes + ChunksFreeLostCount--; + pS->MemAvail += MEM_CHUNKChunkSizeGet(header); + header = header->NextFree; + } + + // Update stats + if (pS->MemUsed_At_HighWater < pS->MemUsed) + pS->MemUsed_At_HighWater = pS->MemUsed; + + for (i = 0; i < MEM_TYPES_MAX; i++) { + if (pS->MemType_MemUsed_At_HighWater[i] < pS->MemType_MemUsed[i]) + pS->MemType_MemUsed_At_HighWater[i] = pS->MemType_MemUsed[i]; + } + + MP_ASSERT(ChunksFreeLostCount == 0) // lost free blocks +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLCheckValidity(MEM_CHUNK_POOL* _this) { + MEM_STATS stats; + MEM_CHUNK_POOLMemStatsGet(_this, &stats); +} + +//-------------------------------------------------------------------------- +void MEM_CHUNK_POOLFillMemoryTable(MEM_CHUNK_POOL* _this, char* Table, + const int TableSize, gsi_u32 _HeapStart, + gsi_u32 _HeapSize) +//-------------------------------------------------------------------------- +{ + int s, e, j; + gsi_u32 start_address; + gsi_u32 end_address; + MEM_CHUNK* pChunk = _this->HeaderStart; + MP_ASSERT(_this->HeapSize) + + while (pChunk) { + if (!MEM_CHUNKIsFree(pChunk)) { + start_address = (gsi_uint)pChunk; + end_address = ((gsi_uint)pChunk->next) - 1; + + // translate address into table positions + s = ((start_address - _HeapStart) * (TableSize >> 4)) / (_HeapSize >> 4); + MP_ASSERT(s < TableSize) + MP_ASSERT(s >= 0) + + e = ((end_address - _HeapStart) * (TableSize >> 4)) / (_HeapSize >> 4); + MP_ASSERT(e < TableSize) + MP_ASSERT(e >= 0) + + for (j = s; j <= e; j++) { + // if(Table[j] != -2) + // Table[j] = -1; + // else + Table[j] = MEM_CHUNKMemTypeGet(pChunk); + } + } + pChunk = pChunk->next; + } +} + +static MEM_CHUNK_POOL gChunkPool[gsMemMgrContext_Count]; + +// Use this to determine which pool and subsequent allocations will be taken +// from. +gsMemMgrContext gsMemMgrContextCurrent = gsMemMgrContext_Default; + +// static GSICriticalSection gMemCrit; + +//-------------------------------------------------------------------------- +gsMemMgrContext gsMemMgrContextFind(void* ptr) +// find pool corresponding to mem ptr. +{ + int i; + // find which pool owns this pointer!!!!, this is kind of a hack.... but here + // goes. + for (i = 0; i < gsMemMgrContext_Count; i++) { + if (MEM_CHUNK_POOLIsValid(&gChunkPool[i]) && + MEM_CHUNK_POOLItemIsInPoolMemory(&gChunkPool[i], ptr)) { + return (gsMemMgrContext)i; + } + } + return gsMemMgrContext_Invalid; +} + +void* gs_malloc(size_t size) { + GS_ASSERT(size) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]), + "malloc: context is invalid mempool"); + + return MEM_CHUNK_POOLmalloc(&gChunkPool[gsMemMgrContextCurrent], size, + PTR_ALIGNMENT); +} + +void* gs_calloc(size_t size, size_t size2) { + GS_ASSERT(size) + GS_ASSERT(size2) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]), + "calloc: context is invalid mempool"); + + return MEM_CHUNK_POOLmalloc(&gChunkPool[gsMemMgrContextCurrent], size * size2, + PTR_ALIGNMENT); +} + +void* gs_realloc(void* ptr, size_t size) { + GS_ASSERT(size) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]), + "realloc: context is invalid mempool"); + + return MEM_CHUNK_POOLrealloc(&gChunkPool[gsMemMgrContextCurrent], ptr, size); +} + +void* gs_memalign(size_t boundary, size_t size) { + GS_ASSERT(size) + GS_ASSERT(boundary) + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]), + "memalign: context is invalid mempool"); + + return MEM_CHUNK_POOLmalloc(&gChunkPool[gsMemMgrContextCurrent], size, + boundary); +} + +void gs_free(void* ptr) { + gsMemMgrContext context; + + context = gsMemMgrContextFind(ptr); + GS_ASSERT_STR(context != gsMemMgrContext_Invalid, + "Attempt to free invalid ptr") + + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), + "free: ptr context is invalid mempool"); + MEM_CHUNK_POOLfree(&gChunkPool[context], ptr); +} + +//-------------------------------------------------------------------------- +const char* MemMgrBufferGetName(gsMemMgrContext context) { + GS_ASSERT_STR(context != gsMemMgrContext_Invalid, "Invalid Context"); + GS_ASSERT_STR(context < gsMemMgrContext_Count, "Context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), "Invalid mempool"); + + return gChunkPool[context].Name; +} + +void gsMemMgrContextSet(gsMemMgrContext context) { + GS_ASSERT_STR(context != gsMemMgrContext_Invalid, "Invalid Context"); + GS_ASSERT_STR(context < gsMemMgrContext_Count, "Context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), + "Setting context to invalid mempool"); + + gsMemMgrContextCurrent = context; +} + +//-------------------------------------------------------------------------- +// call this to enable GameSpy's provided memory manager +// Create a mem pool for the given context. If that context is in use, it will +// return the next available if none are available it will return +// gsMemMgrContext_Invalid ex use: gQR2MemContext = gsMemMgrCreate +// (0,0,16 * 1024); will find the first avaiable spot, create a mem pool of 16k, +// and return the context handle. then later in your API +// enter an API function +// gsMemMgrContextPush(gQR2MemContext); +// do some allocs +// gQR2MemContextPop() +// return from function. +gsMemMgrContext gsMemMgrCreate(gsMemMgrContext context, const char* PoolName, + void* thePoolBuffer, size_t thePoolSize) { + char* ptr = (char*)thePoolBuffer; + + GS_ASSERT_STR(thePoolSize, "Cannnot create a pool of size 0") + GS_ASSERT_STR(thePoolSize, "thePoolBuffer ptr is inivalid"); + GS_ASSERT_STR(((((gsi_uint)thePoolSize) & 15) == 0), + "PoolSize must be aligned to 16 bytes"); + GS_ASSERT_STR(((((gsi_uint)thePoolBuffer) & 15) == 0), + "thePoolBuffer must be aligned to 16 bytes"); + + while (MEM_CHUNK_POOLIsValid(&gChunkPool[context])) { + context = (gsMemMgrContext)(context + 1); + } + if (context == gsMemMgrContext_Count) { + // Warn!!!! + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "Out of memory context handles!\n"); + GS_ASSERT(0); + return gsMemMgrContext_Invalid; // ran out of context slots + } + + MEM_CHUNK_POOLCreate(&gChunkPool[context], PoolName, ptr, thePoolSize); + // Set call backs. + gsiMemoryCallbacksSet(gs_malloc, gs_free, gs_realloc, gs_memalign); + return context; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsMemMgrDestroy(gsMemMgrContext context) { + GS_ASSERT(gChunkPool[context].HeapSize != 0); + MEM_CHUNK_POOLDestroy(&gChunkPool[context]); + + // if this is the last one, +#if (0) + { + // Set call backs. + gsiMemoryCallbacksSet(malloc, free, realloc, memalign); + + // Reset memmgr + gsiDeleteCriticalSection(&gMemCrit); + + // #ifdef _GSI_MULTI_THREADED_ + // gsiLeaveCriticalSection(&gMemCrit); + // gsiEnterCriticalSection(&gMemCrit); + // #endif + } +#endif +} + +//-------------------------------------------------------------------------- +void gsMemMgrTagPush(gsi_u8 tag) { + GS_ASSERT(MemTagStackIndex < MEM_TAG_STACK_MAX - 1) + MemTagStackIndex++; + MemTagStack[MemTagStackIndex] = tag; +} +//-------------------------------------------------------------------------- +void gsMemMgrTagPop() { + GS_ASSERT(MemTagStackIndex > 0) + MemTagStackIndex--; +} +//-------------------------------------------------------------------------- +gsi_u8 gsMemMgrTagGet(void* ptr) { + GS_ASSERT(ptr); + return MEM_CHUNKMemTypeGet(Ptr_To_MEM_CHUNK(ptr)); +} +//-------------------------------------------------------------------------- +gsi_u32 gsMemMgrMemUsedByTagGet(gsi_u8 tag) { + int i; + gsi_u32 used = 0; + for (i = 0; i < gsMemMgrContext_Count; i++) { + used += MEM_CHUNK_POOLWalkForType(&gChunkPool[i], tag, gsi_false); + } + return used; +} + +//-------------------------------------------------------------------------- +void gsMemMgrContextPush(gsMemMgrContext NewType) { + // PARANOID_MemProfilerCheck(); + GS_ASSERT(MemTypeStackIndex < MEM_CONTEXT_STACK_MAX) + GS_ASSERT(NewType < gsMemMgrContext_Count) + + // gsDebugFormat(GSIDebugCat_App, GSIDebugType_State, + // GSIDebugLevel_Comment,"MemProfilerStart: + //%s\n",MemProfiler.MemPool[NewType].Name); + MemTypeStack[MemTypeStackIndex++] = gsMemMgrContextCurrent; + gsMemMgrContextCurrent = NewType; +} + +//-------------------------------------------------------------------------- +gsMemMgrContext gsMemMgrContextPop() { + // PARANOID_MemProfilerCheck(); + GS_ASSERT(MemTypeStackIndex > 0) + // gsDebugFormat(GSIDebugCat_App, GSIDebugType_State, + // GSIDebugLevel_Comment,"MemProfilerEnd: + //%s\n",MemProfiler.MemPool[OldType].Name); + gsMemMgrContextCurrent = MemTypeStack[--MemTypeStackIndex]; + return gsMemMgrContextCurrent; +} + +//-------------------------------------------------------------------------- +// return total available memory for the given memory pool +gsi_u32 gsMemMgrMemAvailGet(gsMemMgrContext context) { + MEM_STATS stats; + MEM_STATSClearAll(&stats); + GS_ASSERT_STR(context < gsMemMgrContext_Count, + "gsMemMgrMemAvailGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), + "gsMemMgrMemAvailGet: context is invalid mempool"); + MEM_CHUNK_POOLMemStatsGet(&gChunkPool[context], &stats); + return stats.MemAvail; +} + +//-------------------------------------------------------------------------- +// return total used memory for the given memory pool +gsi_u32 gsMemMgrMemUsedGet(gsMemMgrContext context) { + MEM_STATS stats; + MEM_STATSClearAll(&stats); + GS_ASSERT_STR(context < gsMemMgrContext_Count, + "gsMemMgrMemUsedGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), + "gsMemMgrMemUsedGet: context is invalid mempool"); + MEM_CHUNK_POOLMemStatsGet(&gChunkPool[context], &stats); + return stats.MemUsed; +} + +//-------------------------------------------------------------------------- +// return largest allocatable chunk the given memory pool. This +// will be the same or probably smaller then the value returned by +// gsMemMgrMemAvailGet depending on degree of memory fragmentation. +gsi_u32 gsMemMgrMemLargestAvailGet(gsMemMgrContext context) { + MEM_STATS stats; + MEM_STATSClearAll(&stats); + GS_ASSERT_STR(context < gsMemMgrContext_Count, + "gsMemMgrMemLargestAvailGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), + "gsMemMgrMemLargestAvailGet: context is invalid mempool"); + MEM_CHUNK_POOLMemStatsGet(&gChunkPool[context], &stats); + return stats.ChunksFreeLargestAvail; +} + +//-------------------------------------------------------------------------- +gsi_u32 gsMemMgrMemHighwaterMarkGet(gsMemMgrContext context) { + GS_ASSERT_STR(context < gsMemMgrContext_Count, + "gsMemMgrMemLargestAvailGet: context out of range"); + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[context]), + "gsMemMgrMemLargestAvailGet: context is invalid mempool"); + +#if (MEM_PROFILE) + return gChunkPool[context].HWMemUsed; +#else + // Display info - App type b/c it was requested by the app + gsDebugFormat( + GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "gsMemMgrMemHighwaterMarkGet called without MEM_PROFILE enabled."); + return 0; +#endif +} + +//-------------------------------------------------------------------------- +void gsMemMgrValidateMemoryPool() { + GS_ASSERT_STR(MEM_CHUNK_POOLIsValid(&gChunkPool[gsMemMgrContextCurrent]), + "memalign: context is invalid mempool"); + MEM_CHUNK_POOLCheckValidity(&gChunkPool[gsMemMgrContextCurrent]); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Show allocated, free, total memory, num blocks +void gsMemMgrDumpStats() { +#if (0) + int numUsed = 0; + int numFree = 0; + + struct GSIMemoryBlock* aTempPtr = NULL; + + gsiEnterCriticalSection(&gMemCrit); + + // Display the number of free blocks + // TODO: dump size statistics + aTempPtr = gMemoryMgr->mFirstFreeBlock; + while (aTempPtr != NULL) { + numFree++; + aTempPtr = aTempPtr->mNext; + } + + // Display the number of used blocks + // TODO: dump size statistics + aTempPtr = gMemoryMgr->mFirstUsedBlock; + while (aTempPtr != NULL) { + numUsed++; + aTempPtr = aTempPtr->mNext; + } + + // Display info - App type b/c it was requested by the app + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "BytesUsed: %d, BlocksUsed: %d, BlocksFree: %d\r\n", + gMemoryMgr->mMemUsed, numUsed, numFree); + + gsiLeaveCriticalSection(&gMemCrit); +#endif +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsMemMgrDumpAllocations() { +#if (0) + struct GSIMemoryBlock* aBlockPtr = NULL; + gsi_time aStartTime = 0; + gsi_i32 aNumAllocations = 0; + gsi_i32 aNumBytesAllocated = 0; + + gsiEnterCriticalSection(&gMemCrit); + + aStartTime = current_time(); + aBlockPtr = (GSIMemoryBlock*)gMemoryMgr->mPoolStart; + + // Announce start + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "Dumping allocations from pool - [0x%08x] %d bytes.\r\n", + gMemoryMgr->mPoolStart, gMemoryMgr->mPoolSize); + + // Dump information about each allocated block + // - Do this in linear order, not list order + while (aBlockPtr != NULL) { + // If it's in use, verify contents and dump info + if (gsiMemBlockIsFlagged(aBlockPtr, BlockFlag_Used)) { + int anObjectSize = gsiMemBlockGetObjectSize(aBlockPtr); + aNumAllocations++; + aNumBytesAllocated += anObjectSize; + + if (aBlockPtr == gMemoryMgr->mPoolStart) { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, + GSIDebugLevel_Comment, + "\t[0x%08x] Size: %d (memmgr instance)\r\n", + (gsi_u32)aBlockPtr, anObjectSize); + } else { + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, + GSIDebugLevel_Comment, "\t[0x%08x] Size: %d\r\n", + (gsi_u32)(gsiMemBlockGetObjectPtr(aBlockPtr)), + anObjectSize); + } + } else { + // Verify that the block has the correct memory fill + } + // Get linear next (not list next!) + aBlockPtr = gsiMemBlockGetLinearNext(aBlockPtr); + } + + // Announce finish + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "\t--%d allocations, %d bytes allocated.\r\n", aNumAllocations, + aNumBytesAllocated); + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "\t--%d peak memory usage\r\n", gMemoryMgr->mPeakMemoryUsage); + + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Memory, GSIDebugLevel_Comment, + "Memory dump complete. (%d ms)\r\n", + current_time() - aStartTime); + + gsiLeaveCriticalSection(&gMemCrit); + + GSI_UNUSED(aStartTime); // may be unused if common debug is not defined +#endif +} + +#if (1) // test stuff + +#define PTR_TABLE_SIZE 2048 +static int PtrTableCount = 0; +static void* PtrTable[2048]; + +int Random(int x) { return Util_RandInt(0, x); } +//-------------------------------------------------------------------------- +void gsMemMgrSelfText() +//-------------------------------------------------------------------------- +{ + + static MEM_CHUNK_POOL gChunkPool; + int size = 32 * 1024 * 1024; + int c = 0; + int i, j, k; + + char* ptr = + (char*)(((gsi_uint)malloc(size - PTR_ALIGNMENT) + (PTR_ALIGNMENT - 1)) & + ~(PTR_ALIGNMENT - 1)); + MEM_CHUNK_POOLCreate(&gChunkPool, "", ptr, size); + + while (1) { + + i = Random(4); + if ((i == 0) && (PtrTableCount < 1024)) { + // malloc + j = Random(1024) + 1; + k = 32 << (Random(4)); + + if (c & 1) + PtrTable[PtrTableCount] = MEM_CHUNK_POOLmalloc(&gChunkPool, j, k); + else + PtrTable[PtrTableCount] = + MEM_CHUNK_POOLmalloc_backwards(&gChunkPool, j, k); + + if (PtrTable[PtrTableCount]) { + PtrTableCount++; + } else { + GS_ASSERT(0); + } + + } else if ((i == 1) && (PtrTableCount)) { + // free + j = Random(PtrTableCount); + MP_ASSERT(j < PtrTableCount) + + MEM_CHUNK_POOLfree(&gChunkPool, PtrTable[j]); + + // swap with last. + PtrTableCount--; + PtrTable[j] = PtrTable[PtrTableCount]; + + } else if ((i == 2) && (PtrTableCount)) { + j = Random(PtrTableCount); + MP_ASSERT(j < PtrTableCount) + + // realloc + k = Random(1024) + 1; +#if (1) + PtrTable[j] = MEM_CHUNK_POOLrealloc(&gChunkPool, PtrTable[j], k); +#else + // skip + PtrTable[j] = PtrTable[j]; +#endif + + if (PtrTable[j]) { + } else { + GS_ASSERT(0); + } + + } else + continue; // skip count + + c++; + MEM_CHUNK_POOLCheckValidity(&gChunkPool); + } +} + +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#endif // GSI_MEM_MANAGED diff --git a/source/gamespy/common/gsMemory.h b/source/gamespy/common/gsMemory.h new file mode 100644 index 000000000..c46d99df4 --- /dev/null +++ b/source/gamespy/common/gsMemory.h @@ -0,0 +1,167 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" + +// GameSpy allocation wrappers. Used for quickly adding pre-post allocation +// functionality such as +// - routing to a specific mempool +// - collecting mem usage stats +// (x) is a enumerated type for the specific module +#if (1) +#define GSI_PRE_ALLOC(x) +#define GSI_POST_ALLOC() +#elif (0) +// - collecting mem usage stats +#define GSI_PRE_ALLOC(x) gsMemMgrTagPush(x); +#define GSI_POST_ALLOC() gsMemMgrTagPop(); +#elif (0) +// - routing to a specific mempool +#define GSI_PRE_ALLOC(x) gsMemMgrContextPush(x); +#define GSI_POST_ALLOC() gsMemMgrContextPop(); +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +typedef enum { + MEMTAG_DEFAULT, + MEMTAG_SERVER_BROWSER, + MEMTAG_PEER, + MEMTAG_GP, + MEMTAG_QR2, + MEMTAG_NN, + MEMTAG_GT2, + MEMTAG_COUNT +} MEMTAG_SDK; + +//-------------------------------------------------------------------------- +// GameSpy specific memory functions. By default these will route to system +// malloc calls. Use gsiMemoryCallbacksSet or gsiMemoryCallbacksGameSpySet to +// change this. +void* gsimalloc(size_t size); +void* gsirealloc(void* ptr, size_t size); +void gsifree(void* ptr); +void* gsimemalign(size_t boundary, size_t size); // TODO + +//-------------------------------------------------------------------------- +// Customer supplied memory manager customization interface +// call this to replace the Gamespy specific memory functions with your own. +typedef void* (*gsMallocCB)(size_t size); +typedef void (*gsFreeCB)(void* ptr); +typedef void* (*gsReallocCB)(void* ptr, size_t size); +typedef void* (*gsMemalignCB)(size_t boundary, size_t size); +// call this to override above gsi.... calls with your own. +void gsiMemoryCallbacksSet(gsMallocCB p_malloc, gsFreeCB p_free, + gsReallocCB p_realloc, gsMemalignCB p_memalign); + +//-------------------------------------------------------------------------- +// GameSpy Built in Memory Manager +// call this to override above gsi.... calls with GameSpy's built in memory +// manager +// *** You must have GSI_MEM_MANAGED defined, otherwise, you will have a link +// error ***/ + +// This is a list of memory pools used. API specific values determined at run +// time. the gsi mem manager uses the concept of multiple memory pools or +// contexts. use pop and push commands to pop and push the current context off +// of the stack + +typedef enum { + gsMemMgrContext_Invalid = -1, + gsMemMgrContext_Default = 0, + gsMemMgrContext_Count = 16 // max number of mempools +} gsMemMgrContext; + +// call this to enable GameSpy's provided memory manager +// Create a mempool for the given context. If that context is in use, it will +// return the next available if none are avaible it will return +// gsMemMgrContext_Invalid exx use: gQR2MemContext = gsMemMgrCreate +// (0,0,16 * 1024); will find the first avaiable spot, create a mempool of 16k, +// and return the context handle. then later in your API +// enter an API function +// gsMemMgrContextPush(gQR2MemContext); +// do some allocs +// gQR2MemContextPop() +// return from function. +// PoolName is purely for debugging and stats feedback purposes only. +// If you want your api to use the current, or the default pool, then don't +// bother creating one just always set the context to 0, and make sure int your +// app init, the default (0) pool is created. +/* + Recommended usage: + Call gsMemMgrCreate once at app start with a static buffer. Make all + calls to this. Alternatively, call it once per API to sue a seperate pool per + API. +*/ +gsMemMgrContext gsMemMgrCreate(gsMemMgrContext context, const char* PoolName, + void* thePoolBuffer, size_t thePoolSize); + +// Use this to determine which pool and subsequent allocations will be taken +// from. +// exx use +/* + fn() + { + gsMemMgrContextPush(thisAPIContext); + + make allocations. + + //restore settings + gsMemMgrContextPop(thisAPIContext); + + } +*/ +// note, this is not neccessary for "free". +void gsMemMgrContextPush(gsMemMgrContext context); +gsMemMgrContext gsMemMgrContextPop(); + +// clear contents, original mempool ptr must still be freed by app. +void gsMemMgrDestroy(gsMemMgrContext context); + +// -------------Diagnostics------------------------ +// These functions all run on the current mempool context. +void gsMemMgrDumpStats(); +void gsMemMgrDumpAllocations(); +void gsMemMgrValidateMemoryPool(); // walk heap and check integrity + +// -------------Tool use ------------------------ +// find which mempool context this ptr is part of, if any. +// returns gsMemMgrContext_Invalid otherwise. +gsMemMgrContext gsMemMgrContextFind(void* ptr); +const char* MemMgrBufferGetName(gsMemMgrContext context); + +// -------------Memory Use Profiling ------------------------ +// this tag is added to each concurrent alloc. Use this to reference +// allocations. For example, you can find out the mem used by all ptr with a +// given tag in order to find out how much mem a module or set of allocs use. +void gsMemMgrTagPush(gsi_u8 tag); +void gsMemMgrTagPop(); +gsi_u8 gsMemMgrTagGet(void* ptr); +gsi_u32 gsMemMgrMemUsedByTagGet(gsi_u8 tag); + +// return total available memory for the given memory pool context +gsi_u32 gsMemMgrMemAvailGet(gsMemMgrContext context); +// return total used memory for the given memory pool context +gsi_u32 gsMemMgrMemUsedGet(gsMemMgrContext context); +// return largest allocatable chunk within the given memory pool context. This +// will be the same or probably smaller then the value returned by +// gsMemMgrMemAvailGet depending on degree of memory fragmentation. +gsi_u32 gsMemMgrMemLargestAvailGet(gsMemMgrContext context); + +// The Highwater mark for memory used is the highest memory usage ever gets to +// for this given heap. It is the most important stat, as your mempool must be +// at least this big. Exactly how big your pool needs to be depends on +// fragmentation. So it may need to be slightly bigger then this amount. +gsi_u32 gsMemMgrMemHighwaterMarkGet(gsMemMgrContext context); + +// -------------Self Test, not for production use ------------------------ +void gsMemMgrSelfText(); + +#if defined(__cplusplus) +} +#endif diff --git a/source/gamespy/common/gsPlatform.c b/source/gamespy/common/gsPlatform.c new file mode 100644 index 000000000..8552e0a94 --- /dev/null +++ b/source/gamespy/common/gsPlatform.c @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Floating point specific byte reversal functions +// stores the result in a 4-byte character array +unsigned char* gsiFloatSwap(unsigned char buf[4], float f) { + unsigned char* dst = (unsigned char*)buf; + unsigned char* src = (unsigned char*)&f; + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + + return buf; +} + +// unswap using char pointers +float gsiFloatUnswap(unsigned char buf[4]) { + float f; + unsigned char* src = (unsigned char*)buf; + unsigned char* dst = (unsigned char*)&f; + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + + return f; +} + +gsi_u16 gsiByteOrderSwap16(gsi_u16 _in) { + gsi_u16 t; + const char* in = (char*)&_in; + char* out = (char*)&t; + out[0] = in[1]; + out[1] = in[0]; + return t; +} + +gsi_u32 gsiByteOrderSwap32(gsi_u32 _in) { + gsi_u32 t; + const char* in = (char*)&_in; + char* out = (char*)&t; + out[0] = in[3]; + out[1] = in[2]; + out[2] = in[1]; + out[3] = in[0]; + return t; +} + +gsi_u64 gsiByteOrderSwap64(gsi_u64 _in) { + gsi_u64 t; + const char* in = (char*)&_in; + char* out = (char*)&t; + out[0] = in[7]; + out[1] = in[6]; + out[2] = in[5]; + out[3] = in[4]; + out[4] = in[3]; + out[5] = in[2]; + out[6] = in[1]; + out[7] = in[0]; + return t; +} diff --git a/source/gamespy/common/gsPlatform.h b/source/gamespy/common/gsPlatform.h new file mode 100644 index 000000000..a7b7691f4 --- /dev/null +++ b/source/gamespy/common/gsPlatform.h @@ -0,0 +1,157 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +// GameSpy platform definition and headers + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Include common OS headers +#include +#include +#include +#include + +#include + +// Raw sockets are undefined on Revolution +#define SB_NO_ICMP_SUPPORT + +//---------- __cdecl fix for __fastcall conventions ---------- +#define GS_STATIC_CALLBACK + +//---------- Handle Endianess ---------------------- +#define GSI_BIG_ENDIAN + +#include + +#include +#define assert (void) + +#define NOFILE 1 +#define GSI_DOMAIN_NAME "gs.nintendowifi.net" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Define GameSpy types + +// common base type defines, please refer to ranges below when porting +typedef char gsi_i8; +typedef unsigned char gsi_u8; +typedef short gsi_i16; +typedef unsigned short gsi_u16; +typedef int gsi_i32; +typedef unsigned int gsi_u32; +typedef unsigned int gsi_time; // must be 32 bits + +// decprecated +typedef gsi_i32 goa_int32; // 2003.Oct.04.JED - typename deprecated +typedef gsi_u32 + goa_uint32; // these types will be removed once all SDK's are updated + +typedef int gsi_bool; +#define gsi_false ((gsi_bool)0) +#define gsi_true ((gsi_bool)1) +#define gsi_is_false(x) ((x) == gsi_false) +#define gsi_is_true(x) ((x) != gsi_false) + +// Max integer size +#if defined(_INTEGRAL_MAX_BITS) && !defined(GSI_MAX_INTEGRAL_BITS) +#define GSI_MAX_INTEGRAL_BITS _INTEGRAL_MAX_BITS +#else +#define GSI_MAX_INTEGRAL_BITS 32 +#endif + +// Platform dependent types +typedef signed long long gsi_i64; +typedef unsigned long long gsi_u64; + +#define gsi_char char + +// expected ranges for integer types +#define GSI_MIN_I8 CHAR_MIN +#define GSI_MAX_I8 CHAR_MAX +#define GSI_MAX_U8 UCHAR_MAX + +#define GSI_MIN_I16 SHRT_MIN +#define GSI_MAX_I16 SHRT_MAX +#define GSI_MAX_U16 USHRT_MAX + +#define GSI_MIN_I32 INT_MIN +#define GSI_MAX_I32 INT_MAX +#define GSI_MAX_U32 UINT_MAX + +#if (GSI_MAX_INTEGRAL_BITS >= 64) +#define GSI_MIN_I64 (-9223372036854775807 - 1) +#define GSI_MAX_I64 9223372036854775807 +#define GSI_MAX_U64 0xffffffffffffffffui64 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Common platform string functions +#undef _vftprintf +#undef _ftprintf +#undef _stprintf +#undef _tprintf +#undef _tcscpy +#undef _tcsncpy +#undef _tcscat +#undef _tcslen +#undef _tcschr +#undef _tcscmp +#undef _tfopen +#undef _T +#undef _tsnprintf + +#define _T(a) a + +#define _tsnprintf snprintf +#define _vftprintf vfprintf +#define _ftprintf fprintf +#define _stprintf sprintf +#define _tprintf printf +#define _tcscpy strcpy +#define _tcsncpy strncpy +#define _tcscat strcat +#define _tcslen strlen + +char* _strlwr(char* string); +char* _strupr(char* string); + +char* goastrdup(const char* src); +unsigned short* goawstrdup(const unsigned short* src); + +// ------ Cross Plat Alignment macros ------------ +/* ex use +PRE_ALIGN(16) struct VECTOR +{ + float x,y,z,_unused; +} POST_ALIGN(16); + +// another example when defining a variable: +PRE_ALIGN(16); +static char _mempool[MEMPOOL_SIZE] POST_ALIGN(16); + +*/ +#define PRE_ALIGN(x) // not needed +#define POST_ALIGN(x) __attribute__((aligned(32))) + +#define DIM(x) (sizeof(x) / sizeof((x)[0])) + +unsigned char* gsiFloatSwap(unsigned char buf[4], float); +float gsiFloatUnswap(unsigned char buf[4]); +extern gsi_u16 gsiByteOrderSwap16(gsi_u16); +extern gsi_u32 gsiByteOrderSwap32(gsi_u32); +extern gsi_u64 gsiByteOrderSwap64(gsi_u64); + +#ifdef __cplusplus +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/common/gsPlatformSocket.c b/source/gamespy/common/gsPlatformSocket.c new file mode 100644 index 000000000..824fe9721 --- /dev/null +++ b/source/gamespy/common/gsPlatformSocket.c @@ -0,0 +1,254 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatformSocket.h" +#include "gsMemory.h" +#include "gsPlatformUtil.h" + +// mj-ToDo: remove these and include the files int the linker instead. +// removing reference to other platforms. +// remove all plat specific code from here, move it to the platspecific files. + +// Include platform separated functions +#include "revolution/gsSocketRevolution.c" + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio +// Level4, "conditional expression is constant". +// Occurs with use of the MS provided macro FD_SET +#pragma warning(disable : 4127) +#endif // _MSC_VER + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int SetSockBlocking(SOCKET sock, int isblocking) { + int rcode; + + int val; + + val = SOFcntl(sock, SO_F_GETFL, 0); + + if (isblocking) + val &= ~SO_O_NONBLOCK; + else + val |= SO_O_NONBLOCK; + + rcode = SOFcntl(sock, SO_F_SETFL, val); + + if (rcode == 0) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "SetSockBlocking: Set socket %d to %s\r\n", + (unsigned int)sock, isblocking ? "blocking" : "non-blocking"); + return 1; + } + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "SetSockBlocking failed: tried to set socket %d to %s\r\n", + (unsigned int)sock, isblocking ? "blocking" : "non-blocking"); + return 0; +} + +/* +int SetSockBroadcast(SOCKET sock) { + GSI_UNUSED(sock); + return 1; +} + +int DisableNagle(SOCKET sock) { + GSI_UNUSED(sock); + return 0; +} +*/ + +int SetReceiveBufferSize(SOCKET sock, int size) { + int rcode; + rcode = + setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (const char*)&size, sizeof(int)); + return gsiSocketIsNotError(rcode); +} + +/* +int SetSendBufferSize(SOCKET sock, int size) { + int rcode; + rcode = + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (const char*)&size, sizeof(int)); + return gsiSocketIsNotError(rcode); +} + +int GetReceiveBufferSize(SOCKET sock) { + int rcode; + int size; + int len; + + len = sizeof(size); + + rcode = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&size, &len); + + if (gsiSocketIsError(rcode)) + return -1; + + return size; +} + +int GetSendBufferSize(SOCKET sock) { + int rcode; + int size; + int len; + + len = sizeof(size); + + rcode = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&size, &len); + + if (gsiSocketIsError(rcode)) + return -1; + + return size; +} +*/ + +// Return 1 for immediate recv, otherwise 0 +int CanReceiveOnSocket(SOCKET sock) { + int aReadFlag = 0; + if (1 == GSISocketSelect(sock, &aReadFlag, NULL, NULL)) + return aReadFlag; + + // SDKs expect 0 on SOCKET_ERROR + return 0; +} + +// Return 1 for immediate send, otherwise 0 +int CanSendOnSocket(SOCKET sock) { + int aWriteFlag = 0; + if (1 == GSISocketSelect(sock, NULL, &aWriteFlag, NULL)) + return aWriteFlag; + + // SDKs expect 0 on SOCKET_ERROR + return 0; +} + +HOSTENT* getlocalhost(void) { +#define MAX_IPS 5 + static HOSTENT aLocalHost; + static char* aliases = NULL; + int aNumOfIps, i; + int aSizeNumOfIps; + static IPAddrEntry aAddrs[MAX_IPS]; + int aAddrsSize, aAddrsSizeInitial; + static u8* ipPtrs[MAX_IPS + 1]; + static unsigned int ips[MAX_IPS]; + int ret; + aSizeNumOfIps = sizeof(aNumOfIps); + ret = SOGetInterfaceOpt(NULL, SO_SOL_CONFIG, SO_CONFIG_IP_ADDR_NUMBER, + &aNumOfIps, &aSizeNumOfIps); + if (ret != 0) + return NULL; + + aAddrsSize = (int)(MAX_IPS * sizeof(IPAddrEntry)); + aAddrsSizeInitial = aAddrsSize; + ret = SOGetInterfaceOpt(NULL, SO_SOL_CONFIG, SO_CONFIG_IP_ADDR_TABLE, &aAddrs, + &aAddrsSize); + if (ret != 0) + return NULL; + + if (aAddrsSize != aAddrsSizeInitial) { + aNumOfIps = aAddrsSize / (int)sizeof(IPAddrEntry); + } + + aLocalHost.h_name = "localhost"; + aLocalHost.h_aliases = &aliases; + aLocalHost.h_addrtype = AF_INET; + aLocalHost.h_length = 4; + + for (i = 0; i < MAX_IPS; i++) { + if (i < aNumOfIps) { + memcpy(&ips[i], &aAddrs[i].addr, sizeof(aAddrs[i].addr)); + ipPtrs[i] = (u8*)&ips[i]; + } else + ipPtrs[i] = NULL; + } + aLocalHost.h_addr_list = ipPtrs; + + return &aLocalHost; +} + +int IsPrivateIP(IN_ADDR* addr) { + int b1; + int b2; + unsigned int ip; + + // get the first 2 bytes + ip = ntohl(addr->s_addr); + b1 = (int)((ip >> 24) & 0xFF); + b2 = (int)((ip >> 16) & 0xFF); + + // 10.X.X.X + if (b1 == 10) + return 1; + + // 172.16-31.X.X + if ((b1 == 172) && ((b2 >= 16) && (b2 <= 31))) + return 1; + + // 192.168.X.X + if ((b1 == 192) && (b2 == 168)) + return 1; + + return 0; +} + +/* +gsi_u32 gsiGetBroadcastIP(void) { + int length; + gsi_u32 ip; + + length = (gsi_u32)sizeof(ip); + + // IP_GetBroadcastAddr replaced by SOGetInterfaceOpt + // IP_GetBroadcastAddr(NULL, (u8*)&ip); + SOGetInterfaceOpt(NULL, SO_SOL_IP, SO_INADDR_BROADCAST, (u8*)&ip, &length); + IPAddrEntry* addrtbl; + int addrnum; + int ret; + int length; + gsi_u32 ip; + length = (int)sizeof(addrnum); + + ret = SOGetInterfaceOpt(NULL, SO_SOL_CONFIG, SO_CONFIG_IP_ADDR_NUMBER, + (u8*)&addrnum, &length); + + if (ret >= 0) + return 0xFFFFFFFF; + + length = (int)(sizeof(IPAddrEntry) * addrnum); + + addrtbl = (IPAddrEntry*)gsimalloc((u32)length); + + if (addrtbl == NULL) + return 0xFFFFFFFF; + + ret = SOGetInterfaceOpt(NULL, SO_SOL_CONFIG, SO_CONFIG_IP_ADDR_TABLE, + (u8*)addrtbl, &length); + + if (ret < 0) { + gsifree(addrtbl); + return 0xFFFFFFFF; + } + + ip = (u32)(addrtbl->bcastAddr[3] | (addrtbl->bcastAddr[2] << 8) | + (addrtbl->bcastAddr[1] << 16) | (addrtbl->bcastAddr[0] << 24)); + + gsifree(addrtbl); + + return ip; +} +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +// Re-enable previously disabled compiler warnings +/////////////////////////////////////////////////// +#if defined(_MSC_VER) +#pragma warning(default : 4127) +#endif // _MSC_VER diff --git a/source/gamespy/common/gsPlatformSocket.h b/source/gamespy/common/gsPlatformSocket.h new file mode 100644 index 000000000..abbb9c7aa --- /dev/null +++ b/source/gamespy/common/gsPlatformSocket.h @@ -0,0 +1,166 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatform.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// GSI Cross Platform Socket Wrapper + +#define gsiSocketIsError(theReturnValue) ((theReturnValue) == -1) +#define gsiSocketIsNotError(theReturnValue) ((theReturnValue) != -1) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Types + +#ifndef INADDR_NONE +#define INADDR_NONE 0xffffffff +#endif + +#ifndef INVALID_SOCKET +#define INVALID_SOCKET (-1) +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Platform socket types +#define WSAEWOULDBLOCK SO_EWOULDBLOCK +#define WSAEINPROGRESS SO_EINPROGRESS +#define WSAEALREADY SO_EALREADY +#define WSAENOTSOCK SO_ENOTSOCK +#define WSAEDESTADDRREQ SO_EDESTADDRREQ +#define WSAEMSGSIZE SO_EMSGSIZE +#define WSAEPROTOTYPE SO_EPROTOTYPE +#define WSAENOPROTOOPT SO_ENOPROTOOPT +#define WSAEPROTONOSUPPORT SO_EPROTONOSUPPORT +#define WSAEOPNOTSUPP SO_EOPNOTSUPP +#define WSAEAFNOSUPPORT SO_EAFNOSUPPORT +#define WSAEADDRINUSE SO_EADDRINUSE +#define WSAEADDRNOTAVAIL SO_EADDRNOTAVAIL +#define WSAENETDOWN SO_ENETDOWN +#define WSAENETUNREACH SO_ENETUNREACH +#define WSAENETRESET SO_ENETRESET +#define WSAECONNABORTED SO_ECONNABORTED +#define WSAECONNRESET SO_ECONNRESET +#define WSAENOBUFS SO_ENOBUFS +#define WSAEISCONN SO_EISCONN +#define WSAENOTCONN SO_ENOTCONN +#define WSAETIMEDOUT SO_ETIMEDOUT +#define WSAECONNREFUSED SO_ECONNREFUSED +#define WSAELOOP SO_ELOOP +#define WSAENAMETOOLONG SO_ENAMETOOLONG +#define WSAEHOSTUNREACH SO_EHOSTUNREACH +#define WSAENOTEMPTY SO_ENOTEMPTY +#define WSAEDQUOT SO_EDQUOT +#define WSAESTALE SO_ESTALE +#define WSAEINVAL SO_EINVAL + +#define AF_INET SO_PF_INET +#define SOCK_DGRAM SO_SOCK_DGRAM +#define SOCK_STREAM SO_SOCK_STREAM +#define IPPROTO_UDP SO_IPPROTO_UDP +#define IPPROTO_TCP SO_IPPROTO_TCP +#define INADDR_ANY SO_INADDR_ANY +#define SOL_SOCKET SO_SOL_SOCKET +#define SO_SNDBUF SO_SO_SNDBUF +#define SO_RCVBUF SO_SO_RCVBUF +#define SO_REUSEADDR SO_SO_REUSEADDR + +typedef int SOCKET; +typedef struct SOSockAddr SOCKADDR; +#define sockaddr SOSockAddr +typedef struct SOSockAddrIn SOCKADDR_IN; +#define sockaddr_in SOSockAddrIn +#define sin_family family +#define sin_port port +#define sin_addr addr +typedef struct SOInAddr IN_ADDR; +#define in_addr SOInAddr +#define s_addr addr +typedef struct SOHostEnt HOSTENT; +#define hostent SOHostEnt +#define h_name name +#define h_aliases aliases +#define h_addrtype addrType +#define h_length length +#define h_addr_list addrList +#define h_addr addrList[0] + +int socket(int pf, int type, int protocol); +int closesocket(SOCKET sock); +int shutdown(SOCKET sock, int how); +int bind(SOCKET sock, const SOCKADDR* addr, int len); + +int connect(SOCKET sock, const SOCKADDR* addr, int len); +int listen(SOCKET sock, int backlog); +SOCKET accept(SOCKET sock, SOCKADDR* addr, int* len); + +int recv(SOCKET sock, char* buf, int len, int flags); +int recvfrom(SOCKET sock, char* buf, int len, int flags, SOCKADDR* addr, + int* fromlen); +int send(SOCKET sock, const char* buf, int len, int flags); +int sendto(SOCKET sock, const char* buf, int len, int flags, + const SOCKADDR* addr, int tolen); + +int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen); +int setsockopt(SOCKET sock, int level, int optname, const char* optval, + int optlen); + +#define gethostbyaddr(a, l, t) SOGetHostByAddr(a, l, t) +// The original gamespy SDK defines gethostbyname as an alternative name of SOGetHostByName. +// Mario Kart Wii's code doesn't do this. +// gethostbyname is a wrapper that internally calls SOGetHostByName. +// Probably used to hijack certain hostnames like localhost. +SOHostEnt* gethostbyname(const char* name); + +// thread safe DNS lookups +#define getaddrinfo(n, s, h, r) SOGetAddrInfo(n, s, h, r) +#define freeaddrinfo(a) SOFreeAddrInfo(a) + +int getsockname(SOCKET sock, SOCKADDR* addr, int* len); + +#define htonl(l) SOHtoNl((u32)l) +#define ntohl(l) SONtoHl((u32)l) +#define htons(s) SOHtoNs((u16)s) +#define ntohs(s) SONtoHs((u16)s) + +#define inet_ntoa(n) SOInetNtoA(n) +unsigned long inet_addr(const char* name); + +int GOAGetLastError(SOCKET sock); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Functions +int SetSockBlocking(SOCKET sock, int isblocking); +int SetSockBroadcast(SOCKET sock); +int DisableNagle(SOCKET sock); +int SetReceiveBufferSize(SOCKET sock, int size); +int SetSendBufferSize(SOCKET sock, int size); +int GetReceiveBufferSize(SOCKET sock); +int GetSendBufferSize(SOCKET sock); +int CanReceiveOnSocket(SOCKET sock); +int CanSendOnSocket(SOCKET sock); +int GSISocketSelect(SOCKET theSocket, int* theReadFlag, int* theWriteFlag, + int* theExceptFlag); +void SocketStartUp(); +void SocketShutDown(); + +HOSTENT* getlocalhost(void); + +int IsPrivateIP(IN_ADDR* addr); +gsi_u32 gsiGetBroadcastIP(void); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/common/gsPlatformThread.c b/source/gamespy/common/gsPlatformThread.c new file mode 100644 index 000000000..94c7cd881 --- /dev/null +++ b/source/gamespy/common/gsPlatformThread.c @@ -0,0 +1,25 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if !defined(GSI_NO_THREADS) +#include "gsCommon.h" + +// Include platform separated functions +#include "revolution/gsThreadRevoulution.c" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif + +#endif // GSI_NO_THREADS diff --git a/source/gamespy/common/gsPlatformThread.h b/source/gamespy/common/gsPlatformThread.h new file mode 100644 index 000000000..34f8700d4 --- /dev/null +++ b/source/gamespy/common/gsPlatformThread.h @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "gsPlatform.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Thread types +typedef OSMutex GSICriticalSection; +typedef OSSemaphore GSISemaphoreID; +typedef struct { + OSThread mThread; + void* mStack; +} GSIThreadID; +typedef void* (*GSThreadFunc)(void* arg); + +#define GSI_INFINITE (gsi_u32)(-1) + +gsi_u32 gsiInterlockedIncrement(gsi_u32* num); +gsi_u32 gsiInterlockedDecrement(gsi_u32* num); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsiStartThread(GSThreadFunc aThreadFunc, gsi_u32 theStackSize, void* arg, + GSIThreadID* theThreadIdOut); +void gsiCancelThread(GSIThreadID theThreadID); +void gsiExitThread(GSIThreadID theThreadID); +void gsiCleanupThread(GSIThreadID theThreadID); + +// Thread Synchronization - Startup/Shutdown +gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID); + +// Thread Synchronization - Critical Section +void gsiInitializeCriticalSection(GSICriticalSection* theCrit); +void gsiEnterCriticalSection(GSICriticalSection* theCrit); +void gsiLeaveCriticalSection(GSICriticalSection* theCrit); +void gsiDeleteCriticalSection(GSICriticalSection* theCrit); + +// Thread Synchronization - Semaphore +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, + char* theName); +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs); +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount); +void gsiCloseSemaphore(GSISemaphoreID theSemaphore); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/common/gsPlatformUtil.c b/source/gamespy/common/gsPlatformUtil.c new file mode 100644 index 000000000..42f2ffb34 --- /dev/null +++ b/source/gamespy/common/gsPlatformUtil.c @@ -0,0 +1,917 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsPlatformUtil.h" +#include "gsCommon.h" + +// Include platform separated functions +#include "revolution/gsUtilRevolution.c" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ********** ASYNC DNS ********** // + +// struct is used in both threaded and non-threaded versions +typedef struct GSIResolveHostnameInfo { + char* hostname; + unsigned int ip; + int finishedResolving; + GSIThreadID threadID; +} GSIResolveHostnameInfo; + +/////////////////////////////////////////////////////////////////////////////// +static void* gsiResolveHostnameThread(void* arg) { + static GSICriticalSection aHostnameCrit; + static int aInitialized = 0; + // SOAddrInfo *aHostAddr; + HOSTENT* aHostAddr; + // int retval; + GSIResolveHostnameHandle handle = (GSIResolveHostnameHandle)arg; + + if (!aInitialized) { + gsiInitializeCriticalSection(&aHostnameCrit); + aInitialized = 1; + } + gsiEnterCriticalSection(&aHostnameCrit); + + // retval = getaddrinfo(handle->hostname, NULL, NULL, &aHostAddr); + aHostAddr = gethostbyname(handle->hostname); + if (aHostAddr != 0) { + char* ip; + // first convert to character string for debug output + ip = inet_ntoa(*(in_addr*)aHostAddr->addrList[0]); + + // gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, + // GSIDebugLevel_Comment, + // "Resolved host '%s' to ip '%s'\n", handle->hostname, ip); + + handle->ip = inet_addr(ip); + // freeaddrinfo(aHostAddr); + } else { + // couldnt reach host - debug output is printed later + handle->ip = GSI_ERROR_RESOLVING_HOSTNAME; + } + + // finished resolving + handle->finishedResolving = 1; + + OSUnlockMutex(&aHostnameCrit); +} +//////////////////////////////////////////////////////////////////////////////// + +int gsiStartResolvingHostname(const char* hostname, + GSIResolveHostnameHandle* handle) { + GSIResolveHostnameInfo* info; + + // allocate a handle + info = (GSIResolveHostnameInfo*)gsimalloc(sizeof(GSIResolveHostnameInfo)); + if (!info) + return -1; + + // make a copy of the hostname so the thread has access to it + info->hostname = goastrdup(hostname); + if (!info->hostname) { + gsifree(info); + return -1; + } + + // not resolved yet + info->finishedResolving = 0; + + // start the thread + if (gsiStartThread(gsiResolveHostnameThread, (0x1000), info, + &info->threadID) == -1) { + gsifree(info->hostname); + info->hostname = NULL; + gsifree(info); + info = NULL; + return -1; + } + + // set the handle to the info + *handle = info; + + return 0; +} + +void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle) { + // cancel the thread + gsiCancelThread(handle->threadID); + + if (handle->hostname) { + gsifree(handle->hostname); + handle->hostname = NULL; + } + gsifree(handle); + handle = NULL; +} + +// PAL: 0x800f2300 +unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle) { + unsigned int ip; + + // check if we haven't finished + if (!handle->finishedResolving) + return GSI_STILL_RESOLVING_HOSTNAME; + + // save the ip + ip = handle->ip; + + // free resources + gsiCleanupThread(handle->threadID); + gsifree(handle->hostname); + gsifree(handle); + handle = NULL; + + return ip; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +char* goastrdup(const char* src) { + char* res; + if (src == NULL) // PANTS|02.11.00|check for NULL before strlen + return NULL; + res = (char*)gsimalloc(strlen(src) + 1); + if (res != NULL) // PANTS|02.02.00|check for NULL before strcpy + strcpy(res, src); + return res; +} + +/* +unsigned short* goawstrdup(const unsigned short* src) { + unsigned short* res; + if (src == NULL) + return NULL; + res = (unsigned short*)gsimalloc((wcslen((wchar_t*)src) + 1) * + sizeof(unsigned short)); + if (res != NULL) + wcscpy((wchar_t*)res, (const wchar_t*)src); + return res; +} +*/ + +char* _strlwr(char* string) { + char* hold = string; + while (*string) { + *string = (char)tolower(*string); + string++; + } + + return hold; +} + +/* +char* _strupr(char* string) { + char* hold = string; + while (*string) { + *string = (char)toupper(*string); + string++; + } + + return hold; +} +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// PAL: 0x800f24c0 +void SocketStartUp() {} +// PAL: 0x800f24c4 +void SocketShutDown() {} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// PAL: 0x800f24c8 +gsi_time current_time() // returns current time in milliseconds +{ + OSTick aTickNow = OSGetTick(); + gsi_time aMilliseconds = (gsi_time)OSTicksToMilliseconds(aTickNow); + return aMilliseconds; +} + +// PAL: 0x800f2510 +void msleep(gsi_time msec) { + OSSleepTicks((((s64)msec) * ((__OSBusClock / 4) / 1000))); +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// Cross-platform GSI wrapper time conversion functions +// +// NOTE: some portions of this copied from standard C library + +// if an error occurs when calling mktime, return -1 +#define MKTIME_ERROR (time_t)(-1) + +// define common conversions for mktime +#define DAY_SEC (24L * 60L * 60L) /* secs in a day */ +#define YEAR_SEC (365L * DAY_SEC) /* secs in a year */ +#define FOUR_YEAR_SEC (1461L * DAY_SEC) /* secs in a 4 year interval */ +#define DEC_SEC 315532800L /* secs in 1970-1979 */ +#define BASE_DOW 4 /* 01-01-70 was a Thursday */ +#define BASE_YEAR 70L /* 1970 is the base year */ +#define LEAP_YEAR_ADJUST 17L /* Leap years 1900 - 1970 */ +#define MAX_YEAR 138L /* 2038 is the max year */ + +// ChkAdd evaluates to TRUE if dest = src1 + src2 has overflowed +#define ChkAdd(dest, src1, src2) \ + (((src1 >= 0L) && (src2 >= 0L) && (dest < 0L)) || \ + ((src1 < 0L) && (src2 < 0L) && (dest >= 0L))) + +// ChkMul evaluates to TRUE if dest = src1 * src2 has overflowed +#define ChkMul(dest, src1, src2) (src1 ? (dest / src1 != src2) : 0) + +int _lpdays[] = {-1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}; +int _days[] = {-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364}; + +const char _dnames[] = {"SunMonTueWedThuFriSat"}; +/* Month names must be Three character abbreviations strung together */ +const char _mnames[] = {"JanFebMarAprMayJunJulAugSepOctNovDec"}; + +static struct tm tb = {0}; /* time block used in SecondsToDate */ + +static char buf[26]; /* buffer used to store string in SecondsToString */ + +#define _T(a) L##a +static char* store_dt(char*, int); +static char* store_dt(char* p, int val) { + *p++ = (char)(_T('0') + val / 10); + *p++ = (char)(_T('0') + val % 10); + return (p); +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// GSI equivalent of Standard C-lib "gmtime function" +struct tm* gsiSecondsToDate(const time_t* timp) { + time_t caltim = *timp; /* calendar time to convert */ + int islpyr = 0; /* is-current-year-a-leap-year flag */ + int tmptim; + int* mdays; /* pointer to days or lpdays */ + struct tm* ptb = &tb; + + if (caltim < 0L) + return (NULL); + + /* + * Determine years since 1970. First, identify the four-year interval + * since this makes handling leap-years easy (note that 2000 IS a + * leap year and 2100 is out-of-range). + */ + tmptim = (int)(caltim / FOUR_YEAR_SEC); + caltim -= ((long)tmptim * FOUR_YEAR_SEC); + + /* + * Determine which year of the interval + */ + tmptim = (tmptim * 4) + 70; /* 1970, 1974, 1978,...,etc. */ + + if (caltim >= YEAR_SEC) { + tmptim++; /* 1971, 1975, 1979,...,etc. */ + caltim -= YEAR_SEC; + + if (caltim >= YEAR_SEC) { + tmptim++; /* 1972, 1976, 1980,...,etc. */ + caltim -= YEAR_SEC; + + /* + * Note, it takes 366 days-worth of seconds to get past a leap + * year. + */ + if (caltim >= (YEAR_SEC + DAY_SEC)) { + tmptim++; /* 1973, 1977, 1981,...,etc. */ + caltim -= (YEAR_SEC + DAY_SEC); + } else { + /* + * In a leap year after all, set the flag. + */ + islpyr++; + } + } + } + + /* + * tmptim now holds the value for tm_year. caltim now holds the + * number of elapsed seconds since the beginning of that year. + */ + ptb->tm_year = tmptim; + + /* + * Determine days since January 1 (0 - 365). This is the tm_yday value. + * Leave caltim with number of elapsed seconds in that day. + */ + ptb->tm_yday = (int)(caltim / DAY_SEC); + caltim -= (long)(ptb->tm_yday) * DAY_SEC; + + /* + * Determine months since January (0 - 11) and day of month (1 - 31) + */ + if (islpyr) + mdays = _lpdays; + else + mdays = _days; + + for (tmptim = 1; mdays[tmptim] < ptb->tm_yday; tmptim++) + ; + + ptb->tm_mon = --tmptim; + + ptb->tm_mday = ptb->tm_yday - mdays[tmptim]; + + /* + * Determine days since Sunday (0 - 6) + */ + ptb->tm_wday = ((int)(*timp / DAY_SEC) + BASE_DOW) % 7; + + /* + * Determine hours since midnight (0 - 23), minutes after the hour + * (0 - 59), and seconds after the minute (0 - 59). + */ + ptb->tm_hour = (int)(caltim / 3600); + caltim -= (long)ptb->tm_hour * 3600L; + + ptb->tm_min = (int)(caltim / 60); + ptb->tm_sec = (int)(caltim - (ptb->tm_min) * 60); + + ptb->tm_isdst = 0; + return ((struct tm*)ptb); +} + +////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////// +// GSI equivalent of Standard C-lib "mktime function" +// PAL: 0x800f27b4 +time_t gsiDateToSeconds(struct tm* tb) { + time_t tmptm1, tmptm2, tmptm3; + struct tm* tbtemp; + /* + * First, make sure tm_year is reasonably close to being in range. + */ + if (((tmptm1 = tb->tm_year) < BASE_YEAR - 1) || (tmptm1 > MAX_YEAR + 1)) + return MKTIME_ERROR; + + /* + * Adjust month value so it is in the range 0 - 11. This is because + * we don't know how many days are in months 12, 13, 14, etc. + */ + + if ((tb->tm_mon < 0) || (tb->tm_mon > 11)) { + + /* + * no danger of overflow because the range check above. + */ + tmptm1 += (tb->tm_mon / 12); + + if ((tb->tm_mon %= 12) < 0) { + tb->tm_mon += 12; + tmptm1--; + } + + /* + * Make sure year count is still in range. + */ + if ((tmptm1 < BASE_YEAR - 1) || (tmptm1 > MAX_YEAR + 1)) + return MKTIME_ERROR; + } + + /***** HERE: tmptm1 holds number of elapsed years *****/ + + /* + * Calculate days elapsed minus one, in the given year, to the given + * month. Check for leap year and adjust if necessary. + */ + tmptm2 = _days[tb->tm_mon]; + if (!(tmptm1 & 3) && (tb->tm_mon > 1)) + tmptm2++; + + /* + * Calculate elapsed days since base date (midnight, 1/1/70, UTC) + * + * + * 365 days for each elapsed year since 1970, plus one more day for + * each elapsed leap year. no danger of overflow because of the range + * check (above) on tmptm1. + */ + tmptm3 = + (tmptm1 - BASE_YEAR) * 365L + ((tmptm1 - 1L) >> 2) - LEAP_YEAR_ADJUST; + + /* + * elapsed days to current month (still no possible overflow) + */ + tmptm3 += tmptm2; + + /* + * elapsed days to current date. overflow is now possible. + */ + tmptm1 = tmptm3 + (tmptm2 = (long)(tb->tm_mday)); + if (ChkAdd(tmptm1, tmptm3, tmptm2)) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed days *****/ + + /* + * Calculate elapsed hours since base date + */ + tmptm2 = tmptm1 * 24L; + if (ChkMul(tmptm2, tmptm1, 24L)) + return MKTIME_ERROR; + + tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_hour); + if (ChkAdd(tmptm1, tmptm2, tmptm3)) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed hours *****/ + + /* + * Calculate elapsed minutes since base date + */ + + tmptm2 = tmptm1 * 60L; + if (ChkMul(tmptm2, tmptm1, 60L)) + return MKTIME_ERROR; + + tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_min); + if (ChkAdd(tmptm1, tmptm2, tmptm3)) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed minutes *****/ + + /* + * Calculate elapsed seconds since base date + */ + + tmptm2 = tmptm1 * 60L; + if (ChkMul(tmptm2, tmptm1, 60L)) + return MKTIME_ERROR; + + tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_sec); + if (ChkAdd(tmptm1, tmptm2, tmptm3)) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed seconds *****/ + + if ((tbtemp = gsiSecondsToDate(&tmptm1)) == NULL) + return MKTIME_ERROR; + + /***** HERE: tmptm1 holds number of elapsed seconds, adjusted *****/ + /***** for local time if requested *****/ + + *tb = *tbtemp; + return (time_t)tmptm1; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Cross platform random number generator +#define RANa 16807 // multiplier +#define LONGRAND_MAX 2147483647L // 2**31 - 1 + +static long randomnum = 1; + +static long nextlongrand(long seed) { + unsigned + + long lo, + hi; + lo = RANa * (unsigned long)(seed & 0xFFFF); + hi = RANa * ((unsigned long)seed >> 16); + lo += (hi & 0x7FFF) << 16; + + if (lo > LONGRAND_MAX) { + lo &= LONGRAND_MAX; + ++lo; + } + lo += hi >> 15; + + if (lo > LONGRAND_MAX) { + lo &= LONGRAND_MAX; + ++lo; + } + + return (long)lo; +} + +// return next random long +static long longrand(void) { + randomnum = nextlongrand(randomnum); + return randomnum; +} + +// to seed it +// PAL: 0x800f2ec8 +void Util_RandSeed(unsigned long seed) { + // nonzero seed + randomnum = seed ? (long)(seed & LONGRAND_MAX) : 1; +} + +// PAL: 0x800f2ee0 +int Util_RandInt(int low, int high) { + unsigned int range = (unsigned int)high - low; + int num; + + if (range == 0) + return (low); // Prevent divide by zero + + num = (int)(longrand() % range); + + return (num + low); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +static void QuartToTrip(char* quart, char* trip, int inlen) { + if (inlen >= 2) + trip[0] = (char)(quart[0] << 2 | quart[1] >> 4); + if (inlen >= 3) + trip[1] = (char)((quart[1] & 0x0F) << 4 | quart[2] >> 2); + if (inlen >= 4) + trip[2] = (char)((quart[2] & 0x3) << 6 | quart[3]); +} + +static void TripToQuart(const char* trip, char* quart, int inlen) { + unsigned char triptemp[3]; + int i; + for (i = 0; i < inlen; i++) { + triptemp[i] = (unsigned char)trip[i]; + } + while (i < 3) // fill the rest with 0 + { + triptemp[i] = 0; + i++; + } + quart[0] = (char)(triptemp[0] >> 2); + quart[1] = (char)(((triptemp[0] & 3) << 4) | (triptemp[1] >> 4)); + quart[2] = (char)((triptemp[1] & 0x0F) << 2 | (triptemp[2] >> 6)); + quart[3] = (char)(triptemp[2] & 0x3F); +} + +const char defaultEncoding[] = {'+', '/', '='}; +const char alternateEncoding[] = {'[', ']', '_'}; +const char urlSafeEncodeing[] = {'-', '_', '='}; + +void B64Decode(const char* input, char* output, int inlen, int* outlen, + int encodingType) { + const char* encoding = NULL; + const char* holdin = input; + int readpos = 0; + int writepos = 0; + char block[4]; + + // int outlen = -1; + // int inlen = (int)strlen(input); + + // 10-31-2004 : Added by Saad Nader + // now supports URL safe encoding + //////////////////////////////////////////////// + switch (encodingType) { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: + encoding = defaultEncoding; + } + + GS_ASSERT(inlen >= 0); + if (inlen <= 0) { + if (outlen) + *outlen = 0; + output[0] = '\0'; + return; + } + + // Break at end of string or padding character + while (readpos < inlen && input[readpos] != encoding[2]) { + // 'A'-'Z' maps to 0-25 + // 'a'-'z' maps to 26-51 + // '0'-'9' maps to 52-61 + // 62 maps to encoding[0] + // 63 maps to encoding[1] + if (input[readpos] >= '0' && input[readpos] <= '9') + block[readpos % 4] = (char)(input[readpos] - 48 + 52); + else if (input[readpos] >= 'a' && input[readpos] <= 'z') + block[readpos % 4] = (char)(input[readpos] - 71); + else if (input[readpos] >= 'A' && input[readpos] <= 'Z') + block[readpos % 4] = (char)(input[readpos] - 65); + else if (input[readpos] == encoding[0]) + block[readpos % 4] = 62; + else if (input[readpos] == encoding[1]) + block[readpos % 4] = 63; + + // padding or '\0' characters also mark end of input + else if (input[readpos] == encoding[2]) + break; + else if (input[readpos] == '\0') + break; + else { + // (assert(0)); //bad input data + if (outlen) + *outlen = 0; + output[0] = '\0'; + return; // invaid data + } + + // every 4 bytes, convert QuartToTrip into destination + if (readpos % 4 == 3) // zero based, so (3%4) means four bytes, 0-1-2-3 + { + QuartToTrip(block, &output[writepos], 4); + writepos += 3; + } + readpos++; + } + + // Convert any leftover characters in block + if ((readpos != 0) && (readpos % 4 != 0)) { + // fill block with pad (required for QuartToTrip) + memset(&block[readpos % 4], encoding[2], (unsigned int)4 - (readpos % 4)); + QuartToTrip(block, &output[writepos], readpos % 4); + + // output bytes depend on the number of non-pad input bytes + if (readpos % 4 == 3) + writepos += 2; + else + writepos += 1; + } + + if (outlen) + *outlen = writepos; + + GSI_UNUSED(holdin); +} + +void B64Encode(const char* input, char* output, int inlen, int encodingType) { + const char* encoding; + char* holdout = output; + char* lastchar; + int todo = inlen; + + // 10-31-2004 : Added by Saad Nader + // now supports URL safe encoding + //////////////////////////////////////////////// + switch (encodingType) { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: + encoding = defaultEncoding; + } + + // assume interval of 3 + while (todo > 0) { + TripToQuart(input, output, min(todo, 3)); + output += 4; + input += 3; + todo -= 3; + } + lastchar = output; + if (inlen % 3 == 1) + lastchar -= 2; + else if (inlen % 3 == 2) + lastchar -= 1; + *output = 0; // null terminate! + while (output > holdout) { + output--; + if (output >= lastchar) // pad the end + *output = encoding[2]; + else if (*output <= 25) + *output = (char)(*output + 65); + else if (*output <= 51) + *output = (char)(*output + 71); + else if (*output <= 61) + *output = (char)(*output + 48 - 52); + else if (*output == 62) + *output = encoding[0]; + else if (*output == 63) + *output = encoding[1]; + } +} + +// PAL: 0x800f3484 +int B64DecodeLen(const char* input, int encodingType) { + const char* encoding; + const char* holdin = input; + + switch (encodingType) { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: + encoding = defaultEncoding; + } + + while (*input) { + if (*input == encoding[2]) + return (input - holdin) / 4 * 3 + (input - holdin - 1) % 4; + input++; + } + + return (input - holdin) / 4 * 3; +} + +// PAL: 0x800f3528 +void B64InitEncodeStream(B64StreamData* data, const char* input, int len, + int encodingType) { + data->input = input; + data->len = len; + data->encodingType = encodingType; +} + +gsi_bool B64EncodeStream(B64StreamData* data, char output[4]) { + const char* encoding; + char* c; + int i; + + if (data->len <= 0) + return gsi_false; + + // 10-31-2004 : Added by Saad Nader + // now supports URL safe encoding + //////////////////////////////////////////////// + switch (data->encodingType) { + case 1: + encoding = alternateEncoding; + break; + case 2: + encoding = urlSafeEncodeing; + break; + default: + encoding = defaultEncoding; + } + + TripToQuart(data->input, output, min(data->len, 3)); + data->input += 3; + data->len -= 3; + + for (i = 0; i < 4; i++) { + c = &output[i]; + if (*c <= 25) + *c = (char)(*c + 65); + else if (*c <= 51) + *c = (char)(*c + 71); + else if (*c <= 61) + *c = (char)(*c + 48 - 52); + else if (*c == 62) + *c = encoding[0]; + else if (*c == 63) + *c = encoding[1]; + } + + if (data->len < 0) { + output[3] = encoding[2]; + if (data->len == -2) + output[2] = encoding[2]; + } + + return gsi_true; +} + +/* +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiPadRight(char* cArray, char padChar, int cLength); +char* gsiXxteaAlg(const char* sIn, int nIn, char key[XXTEA_KEY_SIZE], int bEnc, + int* nOut); + +void gsiPadRight(char* cArray, char padChar, int cLength) { + int diff; + int length = (int)strlen(cArray); + + diff = cLength - length; + memset(&cArray[length], padChar, (size_t)diff); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// The heart of the XXTEA encryption/decryption algorithm. +// +// sIn: Input stream. +// nIn: Input length (bytes). +// key: Key (only first 128 bits are significant). +// bEnc: Encrypt (else decrypt)? +char* gsiXxteaAlg(const char* sIn, int nIn, char key[XXTEA_KEY_SIZE], int bEnc, + int* nOut) { + int i, p, n1; + unsigned int *k, *v, z, y; + char *oStr = NULL, *pStr = NULL; + char* sIn2 = NULL; + ///////////////////////////////// + // ERROR CHECK! + if (!sIn || !key[0] || nIn == 0) + return NULL; + + // Convert stream length to a round number of 32-bit words + // Convert byte count to 32-bit word count + if (nIn % 4 == 0) // Fix for null terminated strings divisible by 4 + nIn = (nIn / 4) + 1; + else + nIn = (nIn + 3) / 4; + + if (nIn <= 1) // XXTEA requires at least 64 bits + nIn = 2; + + // Load and zero-pad first 16 characters (128 bits) of key + gsiPadRight(key, '\0', XXTEA_KEY_SIZE); + k = (unsigned int*)key; + + // Load and zero-pad entire input stream as 32-bit words + sIn2 = (char*)gsimalloc((size_t)(4 * nIn)); + strcpy(sIn2, sIn); + gsiPadRight(sIn2, '\0', 4 * nIn); + v = (unsigned int*)sIn2; + + // Prepare to encrypt or decrypt + n1 = nIn - 1; + z = v[n1]; + y = v[0]; + i = (int)(6 + 52 / nIn); + + if (bEnc == 1) // Encrypt + { + unsigned int sum = 0; + while (i-- != 0) { + int e; + sum += 0x9E3779B9; + e = (int)(sum >> 2); + for (p = -1; ++p < nIn;) { + y = v[(p < n1) ? p + 1 : 0]; + z = (v[p] += (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ + ((sum ^ y) + (k[(p ^ e) & 3] ^ z))); + } + } + } else if (bEnc == 0) // Decrypt + { + unsigned int sum = (unsigned int)i * 0x9E3779B9; + while (sum != 0) { + int e = (int)(sum >> 2); + for (p = nIn; p-- != 0;) { + z = v[(p != 0) ? p - 1 : n1]; + y = (v[p] -= (((z >> 5) ^ (y << 2)) + ((y >> 3) ^ (z << 4))) ^ + ((sum ^ y) + (k[(p ^ e) & 3] ^ z))); + } + sum -= 0x9E3779B9; + } + } else + return NULL; + // Convert result from 32-bit words to a byte stream + + oStr = (char*)gsimalloc((size_t)(4 * nIn + 1)); + pStr = oStr; + *nOut = 4 * nIn; + for (i = -1; ++i < nIn;) { + unsigned int q = v[i]; + + *pStr++ = (char)(q & 0xFF); + *pStr++ = (char)((q >> 8) & 0xFF); + *pStr++ = (char)((q >> 16) & 0xFF); + *pStr++ = (char)((q >> 24) & 0xFF); + } + *pStr = '\0'; + gsifree(sIn2); + + return oStr; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// XXTEA Encrpyt +// params +// iStr : the input string to be encrypted +// iLength : the length of the input string +// key : the key used to encrypt +char* gsXxteaEncrypt(const char* iStr, int iLength, char key[XXTEA_KEY_SIZE], + int* oLength) { + return gsiXxteaAlg(iStr, iLength, key, 1, oLength); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// XXTEA Decrypt +// params +// iStr : the input string to be decrypted +// iLength : the length of the input string +// key : the key used to decrypt +char* gsXxteaDecrypt(const char* iStr, int iLength, char key[XXTEA_KEY_SIZE], + int* oLength) { + return gsiXxteaAlg(iStr, iLength, key, 0, oLength); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +*/ + +#if defined(__cplusplus) +} +#endif diff --git a/source/gamespy/common/gsPlatformUtil.h b/source/gamespy/common/gsPlatformUtil.h new file mode 100644 index 000000000..2ad225a14 --- /dev/null +++ b/source/gamespy/common/gsPlatformUtil.h @@ -0,0 +1,125 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "gsPlatform.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Async DNS lookup + +// async way to resolve a hostname to an IP +typedef struct GSIResolveHostnameInfo* GSIResolveHostnameHandle; +#define GSI_STILL_RESOLVING_HOSTNAME 0 +#define GSI_ERROR_RESOLVING_HOSTNAME 0xFFFFFFFF + +// start resolving a hostname +// returns 0 on success, -1 on error +int gsiStartResolvingHostname(const char* hostname, + GSIResolveHostnameHandle* handle); +// cancel a resolve in progress +void gsiCancelResolvingHostname(GSIResolveHostnameHandle handle); +// returns GSI_STILL_RESOLVING if still resolving the hostname +// returns GSI_ERROR_RESOLVING if it was unable to resolve the hostname +// on success, returns the IP of the host in network byte order +unsigned int gsiGetResolvedIP(GSIResolveHostnameHandle handle); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Get rid of compiler warnings when parameters are never used +// (Mainly used in sample apps and callback for platform switches) +#define GSI_UNUSED(x) x + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Cross platform random number generator +void Util_RandSeed(unsigned long seed); // to seed it +int Util_RandInt(int low, int high); // retrieve a random int + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Base 64 encoding (printable characters) +void B64Encode(const char* input, char* output, int inlen, int encodingType); +void B64Decode(const char* input, char* output, int inlen, int* outlen, + int encodingType); + +// returns the length of the binary data represented by the base64 input string +int B64DecodeLen(const char* input, int encodingType); + +typedef struct { + const char* input; + int len; + int encodingType; +} B64StreamData; + +void B64InitEncodeStream(B64StreamData* data, const char* input, int len, + int encodingType); + +// returns gsi_false if the stream has ended +gsi_bool B64EncodeStream(B64StreamData* data, char output[4]); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define XXTEA_KEY_SIZE 17 +gsi_i8* gsXxteaEncrypt(const gsi_i8* iStr, gsi_i32 iLength, + gsi_i8 key[XXTEA_KEY_SIZE], gsi_i32* oLength); +gsi_i8* gsXxteaDecrypt(const gsi_i8* iStr, gsi_i32 iLength, + gsi_i8 key[XXTEA_KEY_SIZE], gsi_i32* oLength); + +#ifndef max +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#if defined(_DEBUG) +void gsiCheckStack(void); +#else +#define gsiCheckStack() +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// time functions + +gsi_time current_time(); // milliseconds +gsi_time current_time_hires(); // microseconds +void msleep(gsi_time msec); // milliseconds + +// GSI equivalent of common C-lib time functions +struct tm* gsiSecondsToDate(const time_t* timp); // gmtime +time_t gsiDateToSeconds(struct tm* tb); // mktime +char* gsiSecondsToString(const time_t* timp); // ctime + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Misc utilities + +time_t gsiTimeInSec(time_t* timer); +struct tm* gsiGetGmTime(time_t* theTime); +char* gsiCTime(time_t* theTime); +#define time(t) gsiTimeInSec(t) +#define gmtime(t) gsiGetGmTime(t) +#define ctime(t) gsiCTime(t) + +#ifndef SOMAXCONN +#define SOMAXCONN 5 +#endif + +typedef const char* (*GetUniqueIDFunction)(); + +extern GetUniqueIDFunction GOAGetUniqueID; + +// 64-bit Integer reads and writes +gsi_i64 gsiStringToInt64(const char* theNumberStr); +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber); +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/common/gsRC4.h b/source/gamespy/common/gsRC4.h new file mode 100644 index 000000000..3276280c2 --- /dev/null +++ b/source/gamespy/common/gsRC4.h @@ -0,0 +1,29 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "gsCommon.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct RC4Context { + unsigned char x; + unsigned char y; + unsigned char state[256]; +} RC4Context; + +void RC4Init(RC4Context* context, const unsigned char* key, int len); +void RC4Encrypt(RC4Context* context, const unsigned char* src, + unsigned char* dest, int len); + +// Note: RC4Encrypt with src==dest is OK + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} +#endif diff --git a/source/gamespy/common/gsSHA1.h b/source/gamespy/common/gsSHA1.h new file mode 100644 index 000000000..92f5f5923 --- /dev/null +++ b/source/gamespy/common/gsSHA1.h @@ -0,0 +1,66 @@ +/* + * sha1.h + * + * Description: + * This is the header file for code which implements the Secure + * Hashing Algorithm 1 as defined in FIPS PUB 180-1 published + * April 17, 1995. + * + * Many of the variable names in this code, especially the + * single character names, were used because those were the names + * used in the publication. + * + * Please read the file sha1.c for more information. + * + */ + +#pragma once + +#include "gsCommon.h" + +typedef gsi_i16 int_least16_t; + +#ifndef _SHA_enum_ +#define _SHA_enum_ +enum { + shaSuccess = 0, + shaNull, /* Null pointer parameter */ + shaInputTooLong, /* input data too long */ + shaStateError /* called Input after Result */ +}; +#endif +#define SHA1HashSize 20 + +/* + * This structure will hold context information for the SHA-1 + * hashing operation + */ +typedef struct SHA1Context { + uint32_t Intermediate_Hash[SHA1HashSize / 4]; /* Message Digest */ + + uint32_t Length_Low; /* Message length in bits */ + uint32_t Length_High; /* Message length in bits */ + + /* Index into message block array */ + int_least16_t Message_Block_Index; + uint8_t Message_Block[64]; /* 512-bit message blocks */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corrupted? */ +} SHA1Context; + +/* + * Function Prototypes + */ + +#if defined(__cplusplus) +extern "C" { +#endif + +int SHA1Reset(SHA1Context*); +int SHA1Input(SHA1Context*, const uint8_t*, unsigned int); +int SHA1Result(SHA1Context*, uint8_t Message_Digest[SHA1HashSize]); + +#if defined(__cplusplus) +} +#endif // extern "C" diff --git a/source/gamespy/common/gsSSL.h b/source/gamespy/common/gsSSL.h new file mode 100644 index 000000000..ef0bb079d --- /dev/null +++ b/source/gamespy/common/gsSSL.h @@ -0,0 +1,182 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "../darray.h" +#include "../md5.h" +#include "gsCrypt.h" +#include "gsRC4.h" +#include "gsSHA1.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +// SSL common types and defines. Used by HTTP SSL encryption engine + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// SSL v3.0 +#define GS_SSL_VERSION_MAJOR (0x03) +#define GS_SSL_VERSION_MINOR (0x00) + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// SSL content types +#define GS_SSL_CONTENT_CHANGECIPHERSPEC (0x14) // 20 +#define GS_SSL_CONTENT_ALERT (0x15) // 21 Not sure if this is the correct value +#define GS_SSL_CONTENT_HANDSHAKE (0x16) // 22 +#define GS_SSL_CONTENT_APPLICATIONDATA (0x17) // 23 + +// SSL handshake message types +//#define GS_SSL_HANDSHAKE_HELLOREQUEST (0) +#define GS_SSL_HANDSHAKE_CLIENTHELLO (1) +#define GS_SSL_HANDSHAKE_SERVERHELLO (2) +#define GS_SSL_HANDSHAKE_CERTIFICATE (11) +//#define GS_SSL_HANDSHAKE_SERVERKEYEXCHANGE (12) +//#define GS_SSL_HANDSHAKE_CERTIFICATEREQUEST (13) +#define GS_SSL_HANDSHAKE_SERVERHELLODONE (14) +//#define GS_SSL_HANDSHAKE_CERTIFICATEVERIFY (15) +#define GS_SSL_HANDSHAKE_CLIENTKEYEXCHANGE (16) +#define GS_SSL_HANDSHAKE_FINISHED (20) + +// the largest payload for a single SSL packet, RFC const +// ----> RFC includes MAC and any padding, actual user data must be less +#define GS_SSL_MAX_CONTENTLENGTH ((0x4000) - (0xFF)) + +#ifndef HAVE_CIPHER_SUITES +/* these are the ones used by IE */ +#define TLS_RSA_WITH_RC4_128_MD5 0x04 +#define TLS_RSA_WITH_RCA_128_SHA 0x05 +#define TLS_RSA_WITH_3DES_EDE_CBC_SHA 0x0a +#define TLS_RSA_WITH_DES_CBC_SHA 0x09 +#define TLS_RSA_EXPORT1024_WITH_RC4_56_SHA 0x64 +#define TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA 0x62 +#define TLS_RSA_EXPORT_WITH_RC4_40_MD5 0x03 +#define TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 0x06 +#define TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA 0x13 +#define TLS_DHE_DSS_WITH_DES_CBC_SHA 0x12 +#define TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA 0x63 +#endif + +// These depend on the SSL cipher suite ranges +#define GS_SSL_MAX_MAC_SECRET_SIZE (20) +#define GS_SSL_MAX_SYMMETRIC_KEY_SIZE (16) +#define GS_SSL_MAX_IV_SIZE (16) +#define GS_SSL_NUM_CIPHER_SUITES (1) // cipher suite list defined in gsSSL.c +#define GS_SSL_MASTERSECRET_LEN (48) +#define GS_SSL_PAD_ONE \ + "666666666666666666666666666666666666666666666666" // 48 bytes +#define GS_SSL_PAD_TWO \ + "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\" \ + "\\\\\\\\\\\\\\\\\\\\\\" // 48 bytes +#define GS_SSL_MD5_PAD_LEN (48) +#define GS_SSL_SHA1_PAD_LEN (40) // use only 40 of the 48 bytes +#define GS_SSL_CLIENT_FINISH_VALUE "CLNT" +#define GS_SSL_SERVER_FINISH_VALUE "SRVR" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// SSL instance/session info +typedef struct gsSSL { + int sessionLen; + unsigned char sessionData[255]; // up to 256 bytes + unsigned short cipherSuite; + + // DArray certificateArray; + gsCryptRSAKey serverpub; + unsigned char + sendSeqNBO[8]; // incrementing sequence number (for messages sent) + unsigned char receiveSeqNBO[8]; // ditto (for messages received) + + // Key buffers + // Actual data may be smaller than array size + unsigned char clientWriteMACSecret[GS_CRYPT_SHA1_HASHSIZE]; + unsigned char clientReadMACSecret[GS_CRYPT_SHA1_HASHSIZE]; + unsigned char clientWriteKey[GS_SSL_MAX_SYMMETRIC_KEY_SIZE]; + unsigned char clientReadKey[GS_SSL_MAX_SYMMETRIC_KEY_SIZE]; + unsigned char clientWriteIV[GS_SSL_MAX_IV_SIZE]; + unsigned char clientReadIV[GS_SSL_MAX_IV_SIZE]; + + // Actual lengths of the above data blocks + int clientWriteMACLen; + int clientReadMACLen; + int clientWriteKeyLen; + int clientReadKeyLen; + int clientWriteIVLen; + int clientReadIVLen; + + RC4Context sendRC4; // initialized ONCE per key exchange + RC4Context recvRC4; // initialized ONCE per key exchange + + // these are unused once the handshake is complete + // todo: dynamically allocate or remove to free space + MD5_CTX finishHashMD5; + SHA1Context finishHashSHA1; + unsigned char + serverRandom[32]; // server random for key generation, sent plain text + unsigned char + clientRandom[32]; // client random for key generation, sent plain text + unsigned char + premastersecret[GS_SSL_MASTERSECRET_LEN]; // client random for key + // generation, sent encrypted + // with serverpub + unsigned char mastersecret[GS_SSL_MASTERSECRET_LEN]; + +} gsSSL; + +// SSL messages (like the ClientHello) are wrapped in a "record" struct +typedef struct gsSSLRecordHeaderMsg { + unsigned char contentType; // = GS_SSL_CONTENT_HANDSHAKE; + unsigned char versionMajor; // = GS_SSL_VERSION_MAJOR; + unsigned char versionMinor; // = GS_SSL_VERSION_MINOR; + unsigned char lengthNBO[2]; // length of msg, limited to 2^14 + + // WARNING: lengthNBO can NOT be an unsigned short + // This would create alignment issues from the previous 3 parameters + +} gsSSLRecordHeaderMsg; + +typedef struct gsSSLClientHelloMsg { + gsSSLRecordHeaderMsg header; // include the header for easier packing + unsigned char handshakeType; // 0x01 + unsigned char lengthNBO[3]; // 3 byte length, NBO integer! 61 = 0x00 00 3d + unsigned char versionMajor; // = GS_SSL_VERSION_MAJOR; + unsigned char versionMinor; // = GS_SSL_VERSION_MINOR; + unsigned char time[4]; // 4 byte random (spec says set to current unix-time) + unsigned char random[28]; // 28 byte random, total of 32 random bytes + unsigned char sessionIdLen; // how many of the bytes that follow are session + // info? (def:0) + + // ALIGNMENT: 44 bytes prior to this, alignment should be OK + unsigned short cipherSuitesLength; // 2* number of cipher suites + unsigned short cipherSuites[GS_SSL_NUM_CIPHER_SUITES]; + unsigned char compressionMethodLen; // no standard methods, set to 1 + unsigned char compressionMethodList; // set to 0 +} gsSSLClientHelloMsg; + +typedef struct gsSSLClientKeyExchangeMsg { + gsSSLRecordHeaderMsg header; // included here for easier packing + unsigned char handshakeType; // 0x10 + unsigned char lengthNBO[3]; + // The next lengthNBO bytes are the client contribution to the key +} gsSSLClientKeyExchangeMsg; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Information about each cipher suite +typedef struct gsSSLCipherSuiteDesc { + int mSuiteID; + int mKeyLen; + int mMACLen; + int mIVLen; +} gsSSLCipherSuiteDesc; + +extern const gsSSLCipherSuiteDesc gsSSLCipherSuites[GS_SSL_NUM_CIPHER_SUITES]; +extern const unsigned char gsSslRsaOid[9]; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif diff --git a/source/gamespy/common/gsSoap.c b/source/gamespy/common/gsSoap.c new file mode 100644 index 000000000..011b65130 --- /dev/null +++ b/source/gamespy/common/gsSoap.c @@ -0,0 +1,260 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// gSOAP Glue +#include "gsSoap.h" +#include "../ghttp/ghttpASCII.h" +#include "gsCore.h" +#include "gsPlatformThread.h" +#include "gsXML.h" + +// GAMESPY DEVELOPERS -> Use gsiExecuteSoap + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Soap task delegates +static void gsiSoapTaskExecute(void* theTask); +static void gsiSoapTaskCallback(void* theTask, GSTaskResult theResult); +static void gsiSoapTaskCancel(void* theTask); +static gsi_bool gsiSoapTaskCleanup(void* theTask); +static GSTaskResult gsiSoapTaskThink(void* theTask); + +// Http triggered callbacks (don't take action now, wait for task callbacks) +static GHTTPBool gsiSoapTaskHttpCompletedCallback(GHTTPRequest request, + GHTTPResult result, + char* buffer, + GHTTPByteCount bufferLen, + void* param); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Execute a soap function (this should be the only call made from other SDKs) +GSSoapTask* gsiExecuteSoap(const char* theURL, const char* theService, + GSXmlStreamWriter theRequestSoap, + GSSoapCallbackFunc theCallbackFunc, + void* theUserData) { + GSSoapTask* aSoapTask = NULL; + GSTask* aCoreTask = NULL; + + aSoapTask = (GSSoapTask*)gsimalloc(sizeof(GSSoapTask)); + if (aSoapTask == NULL) + return NULL; // out of memory + + aSoapTask->mCallbackFunc = theCallbackFunc; + aSoapTask->mCustomFunc = NULL; + aSoapTask->mURL = theURL; + aSoapTask->mService = theService; + aSoapTask->mRequestSoap = theRequestSoap; + aSoapTask->mPostData = NULL; + aSoapTask->mResponseSoap = NULL; + aSoapTask->mResponseBuffer = NULL; + aSoapTask->mUserData = theUserData; + aSoapTask->mRequestResult = (GHTTPResult)0; + aSoapTask->mCompleted = gsi_false; + + aCoreTask = gsiCoreCreateTask(); + if (aCoreTask == NULL) { + gsifree(aSoapTask); + return NULL; // out of memory + } + + aCoreTask->mCallbackFunc = gsiSoapTaskCallback; + aCoreTask->mExecuteFunc = gsiSoapTaskExecute; + aCoreTask->mThinkFunc = gsiSoapTaskThink; + aCoreTask->mCleanupFunc = gsiSoapTaskCleanup; + aCoreTask->mCancelFunc = gsiSoapTaskCancel; + aCoreTask->mTaskData = (void*)aSoapTask; + + aSoapTask->mCoreTask = aCoreTask; + + gsiCoreExecuteTask(aCoreTask, 0); + + return aSoapTask; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Execute a soap function with a GSSoapCustomFunc that can access the soap +// structure prior to execution. This allows the client to set DIME +// attachments. (The GSSoapCustomFunc parameter could be added to +// gsiExecuteSoap itself as long as existing client code is updated) +GSSoapTask* gsiExecuteSoapCustom(const char* theURL, const char* theService, + GSXmlStreamWriter theRequestSoap, + GSSoapCallbackFunc theCallbackFunc, + GSSoapCustomFunc theCustomFunc, + void* theUserData) { + GSSoapTask* aSoapTask = NULL; + GSTask* aCoreTask = NULL; + + aSoapTask = (GSSoapTask*)gsimalloc(sizeof(GSSoapTask)); + aSoapTask->mCallbackFunc = theCallbackFunc; + aSoapTask->mCustomFunc = theCustomFunc; + aSoapTask->mURL = theURL; + aSoapTask->mService = theService; + aSoapTask->mRequestSoap = theRequestSoap; + aSoapTask->mPostData = NULL; + aSoapTask->mResponseSoap = NULL; + aSoapTask->mResponseBuffer = NULL; + aSoapTask->mUserData = theUserData; + aSoapTask->mRequestResult = (GHTTPResult)0; + aSoapTask->mCompleted = gsi_false; + + aCoreTask = gsiCoreCreateTask(); + aCoreTask->mCallbackFunc = gsiSoapTaskCallback; + aCoreTask->mExecuteFunc = gsiSoapTaskExecute; + aCoreTask->mThinkFunc = gsiSoapTaskThink; + aCoreTask->mCleanupFunc = gsiSoapTaskCleanup; + aCoreTask->mCancelFunc = gsiSoapTaskCancel; + aCoreTask->mTaskData = (void*)aSoapTask; + + aSoapTask->mCoreTask = aCoreTask; + + gsiCoreExecuteTask(aCoreTask, 0); + + return aSoapTask; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Cancels a soap task. +// - Because of network race conditions, the task may complete before it +// can be cancelled. If this happens, the task callback will be triggered +// with status GHTTPRequestCancelled and the result data will be discarded. +void gsiCancelSoap(GSSoapTask* theTask) { + GS_ASSERT(theTask != NULL); + + // Still in progress? cancel it! + if (gsi_is_false(theTask->mCompleted)) + gsiCoreCancelTask(theTask->mCoreTask); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +////////// HTTP CALLBACKS ////////// + +static GHTTPBool gsiSoapTaskHttpCompletedCallback(GHTTPRequest request, + GHTTPResult result, + char* buffer, + GHTTPByteCount bufferLen, + void* param) { + gsi_bool parseResult = gsi_false; + + GSSoapTask* aSoapTask = (GSSoapTask*)param; + aSoapTask->mRequestResult = result; + aSoapTask->mCompleted = gsi_true; + aSoapTask->mResponseBuffer = buffer; + + if (result == GHTTPSuccess) { + aSoapTask->mResponseSoap = gsXmlCreateStreamReader(); + if (aSoapTask->mResponseSoap == NULL) { + // OOM! + aSoapTask->mRequestResult = GHTTPOutOfMemory; + } else { + parseResult = + gsXmlParseBuffer(aSoapTask->mResponseSoap, buffer, (int)bufferLen); + if (gsi_is_false(parseResult)) { + // Todo: handle multiple error conditions + aSoapTask->mRequestResult = GHTTPBadResponse; + } + } + } + + GSI_UNUSED(request); + + return GHTTPFalse; // don't let http free the buffer +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +////////// SOAP EXECUTE TASK ////////// + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Checks to see if the soap task has completed +// - return GSTaskResult_InProgress for "keep checking" +// - return anything else for "finished - trigger callback and +// delete" +static GSTaskResult gsiSoapTaskThink(void* theTask) { + // is the request still processing? + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + if (gsi_is_true(aSoapTask->mCompleted)) + return GSTaskResult_Finished; + else { + ghttpRequestThink(aSoapTask->mRequestId); + return GSTaskResult_InProgress; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Spawns the soap thread and begins execution +static void gsiSoapTaskExecute(void* theTask) { + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + // int threadID = 0; + + // make sure we aren't reusing a task without first resetting it + GS_ASSERT(gsi_is_false(aSoapTask->mCompleted)); + + aSoapTask->mPostData = ghttpNewPost(); + if (aSoapTask->mPostData == NULL) { + // OOM: abort task + aSoapTask->mCompleted = gsi_true; + aSoapTask->mRequestResult = GHTTPOutOfMemory; + return; + } + + ghttpPostSetAutoFree(aSoapTask->mPostData, GHTTPFalse); + ghttpPostAddXml(aSoapTask->mPostData, aSoapTask->mRequestSoap); + + // Allow client to further configure soap object if desired + if (aSoapTask->mCustomFunc != NULL) + (aSoapTask->mCustomFunc)(aSoapTask->mPostData, aSoapTask->mUserData); + + aSoapTask->mRequestId = + ghttpGetExA(aSoapTask->mURL, aSoapTask->mService, NULL, 0, + aSoapTask->mPostData, GHTTPFalse, GHTTPFalse, NULL, + gsiSoapTaskHttpCompletedCallback, (void*)aSoapTask); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Called when the soap task needs to be cancelled +static void gsiSoapTaskCancel(void* theTask) { + GSSoapTask* soapTask = (GSSoapTask*)theTask; + if (gsi_is_false(soapTask->mCompleted)) { + if (soapTask->mRequestId >= 0) + ghttpCancelRequest(soapTask->mRequestId); + soapTask->mRequestResult = GHTTPRequestCancelled; + soapTask->mCompleted = gsi_true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Called when the soap task completes or is cancelled/timed out +static void gsiSoapTaskCallback(void* theTask, GSTaskResult theResult) { + // Call the developer callback + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + + (aSoapTask->mCallbackFunc)(aSoapTask->mRequestResult, aSoapTask->mRequestSoap, + aSoapTask->mResponseSoap, aSoapTask->mUserData); + + GSI_UNUSED(theResult); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// After the soap call has completed, launch a separate cleanup event (see +// comments) +static gsi_bool gsiSoapTaskCleanup(void* theTask) { + GSSoapTask* aSoapTask = (GSSoapTask*)theTask; + + if (aSoapTask->mResponseSoap != NULL) + gsXmlFreeReader(aSoapTask->mResponseSoap); + if (aSoapTask->mResponseBuffer != NULL) + gsifree(aSoapTask->mResponseBuffer); + if (aSoapTask->mPostData != NULL) + ghttpFreePost(aSoapTask->mPostData); // this also frees the request soap xml + gsifree(aSoapTask); + + return gsi_true; +} diff --git a/source/gamespy/common/gsSoap.h b/source/gamespy/common/gsSoap.h new file mode 100644 index 000000000..ba152a874 --- /dev/null +++ b/source/gamespy/common/gsSoap.h @@ -0,0 +1,68 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsCommon.h" +#include "gsCore.h" + +#include "../ghttp/ghttp.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef void (*GSSoapCallbackFunc)(GHTTPResult theHTTPResult, + GSXmlStreamWriter theRequest, + GSXmlStreamReader theResponse, + void* theUserData); +typedef void (*GSSoapCustomFunc)(GHTTPPost theSoap, void* theUserData); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct { + GSSoapCallbackFunc mCallbackFunc; + GSSoapCustomFunc mCustomFunc; + const char* mURL; + const char* mService; + + GSXmlStreamWriter mRequestSoap; + GSXmlStreamReader mResponseSoap; + + char* mResponseBuffer; // so we can free it later + GHTTPPost mPostData; // so we can free it later + + void* mUserData; + GSTask* mCoreTask; + + GHTTPRequest mRequestId; + GHTTPResult mRequestResult; + gsi_bool mCompleted; +} GSSoapTask; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Execute a soap call (Uses GameSpy core object) +GSSoapTask* gsiExecuteSoap(const char* theURL, const char* theService, + GSXmlStreamWriter theSoapData, + GSSoapCallbackFunc theCallbackFunc, + void* theUserData); + +// Alternate version with GSSoapCustomFunc parameter allows client access +// to soap object to set DIME attachments +GSSoapTask* gsiExecuteSoapCustom(const char* theURL, const char* theService, + GSXmlStreamWriter theSoapData, + GSSoapCallbackFunc theCallbackFunc, + GSSoapCustomFunc theCustomFunc, + void* theUserData); + +void gsiCancelSoap(GSSoapTask* theTask); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif diff --git a/source/gamespy/common/gsStringUtil.c b/source/gamespy/common/gsStringUtil.c new file mode 100644 index 000000000..16212a72c --- /dev/null +++ b/source/gamespy/common/gsStringUtil.c @@ -0,0 +1,652 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Conversion Utility for ASCII, UTF8 and USC2 (Unicode) character sets +// +// See RFC2279 for reference +// +// +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsStringUtil.h" +#include "gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Reads UCS2 character from UTF8String +// +// [in] theUTF8String : UTF8String, doesn't need to be null +// terminated [out] theUCS2Char : The 2 byte UCS2 +// equivalent [in] theMaxLength : Maximum number of *bytes* to read +// (not UTF8 characters) +// +// return value : The number of bytes read from +// theUTF8String +// 0 = error when parsing +// +// Remarks: +// If theUTF8String is invalid, theUnicodeChar will be set to '?' +// Function is designed for convenient parsing of UTF8 data streams +// +// Security Concern: +// Because data is routed through an ASCII stream prior to this +// function being called, embedded NULLs are stripped and hence, +// this function does not check for them For example, the UTF-8 +// byte :1000 0000, would convert to a UCS2 NULL character If this +// appeared in the middle of a stream, it could cause undesired operation +int _ReadUCS2CharFromUTF8String(const UTF8String theUTF8String, + UCS2Char* theUnicodeChar, int theMaxLength) { + assert(theUnicodeChar != NULL); + + if (theMaxLength == 0) { + // assert? + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + return 0; // not enough data + } + + // Check for normal ascii range (includes NULL terminator) + if (UTF8_IS_SINGLE_BYTE(theUTF8String[0])) { + // ASCII, just copy the value + *theUnicodeChar = (UCS2Char)theUTF8String[0]; + return 1; + } + + // Check for 2 byte UTF8 + else if (UTF8_IS_TWO_BYTE(theUTF8String[0])) { + if (theMaxLength < 2) { + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + return 0; // not enough data + } + + // Make sure the second byte is valid + if (UTF8_IS_FOLLOW_BYTE(theUTF8String[1])) { + // Construct 11 bit unicode character + // 5 value bits from first UTF8Byte + //(:000ABCDE) plus 6 value bits from the second UTF8Byte + //(:00FGHIJK) Store as (:0000 0ABC DEFG HIJK) + *theUnicodeChar = + (unsigned short)(((theUTF8String[0] & UTF8_TWO_BYTE_MASK) << 6) + + ((theUTF8String[1] & UTF8_FOLLOW_BYTE_MASK))); + return 2; + } + } + + // Check for 3 byte UTF8 + else if (UTF8_IS_THREE_BYTE(theUTF8String[0])) { + if (theMaxLength < 3) { + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + return 0; // not enough data + } + + // Make sure the second and third bytes are valid + if (UTF8_IS_FOLLOW_BYTE(theUTF8String[1]) && + UTF8_IS_FOLLOW_BYTE(theUTF8String[2])) { + // Construct 16 bit unicode character + // 4 value bits from first UTF8Byte + //(:0000ABCD) plus 6 value bits from the second UTF8Byte + //(:00EFGHIJ) plus 6 value bits from the third UTF8Byte + // (:00KLMNOP) Store as (:ABCD EFGH IJKL MNOP) + *theUnicodeChar = + (unsigned short)(((theUTF8String[0] & UTF8_THREE_BYTE_MASK) << 12) + + ((theUTF8String[1] & UTF8_FOLLOW_BYTE_MASK) << 6) + + ((theUTF8String[2] & UTF8_FOLLOW_BYTE_MASK))); + return 3; + } + } + + // Invalid character, replace with '?' and return false + *theUnicodeChar = (UCS2Char)REPLACE_INVALID_CHAR; + + // The second byte on could have been the start of a new valid UTF8 character + // so we can only safely discard one invalid character + return 1; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Converts UCS2 (Unicode) character into UTF8String +// +// [in] theUCS2Char : The 2 byte character to convert +// [out] theUTF8String : The 1-3 byte UTF8 equivalent +// +// return value : The length of theUTF8String in +// bytes +// +// Remarks: +// theUTF8String may be up to 3 bytes, caller is responsible for +// allocating memory theUTF8String is NOT NULL terminated, +int _UCS2CharToUTF8String(UCS2Char theUCS2Char, UTF8String theUTF8String) { + assert(theUTF8String != NULL); + + // Screen out simple ascii (includes NULL terminator) + if (theUCS2Char <= 0x7F) { + // 0-7 bit unicode, copy stright over + theUTF8String[0] = (char)(UTF8ByteType)theUCS2Char; + return 1; + } else if (theUCS2Char <= 0x07FF) { + // 8-11 bits unicode, store as two byte UTF8 + // :00000ABC DEFGHIJK + // :110ABCDE 10FGHIJK + theUTF8String[0] = (char)(UTF8ByteType)( + UTF8_TWO_BYTE_TAG | + (theUCS2Char >> 6)); // Store the upper 5/11 bits as 0x110xxxxx + theUTF8String[1] = (char)(UTF8ByteType)( + UTF8_FOLLOW_BYTE_TAG | + (theUCS2Char & + UTF8_FOLLOW_BYTE_MASK)); // Store the lower 6 bits as 0x10xxxxxx + return 2; + } else { + // 12-16 bits unicode, store as three byte UTF8 + // :ABCDEFGH IJKLMNOP + // :1110ABCD 10EFGHIJ 10KLMNOP + theUTF8String[0] = (char)(UTF8ByteType)( + UTF8_THREE_BYTE_TAG | + (theUCS2Char >> 12)); // Store the upper 4/16 bits as 0x1110xxxx + theUTF8String[1] = (char)(UTF8ByteType)( + UTF8_FOLLOW_BYTE_TAG | + ((theUCS2Char >> 6) & + UTF8_FOLLOW_BYTE_MASK)); // Store the 5th-10th bits as 0x10xxxxxx + theUTF8String[2] = (char)(UTF8ByteType)( + UTF8_FOLLOW_BYTE_TAG | + ((theUCS2Char)&UTF8_FOLLOW_BYTE_MASK)); // Store the last 6 bits as + // 0x10xxxxxx + return 3; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert an ASCII string to UTF8 +// +// Since an ASCII string IS a valid UTF8 string, just copy and return +// +// [in] theAsciiString, NULL terminated c-string +// [out] theUTF8String, NULL terminated UTF8String +// +// returns the length of theUTF8String +int AsciiToUTF8String(const char* theAsciiString, UTF8String theUTF8String) { + // Allow for NULL here since SDKs allow for NULL string arrays + if (theAsciiString == NULL) { + *theUTF8String = 0x00; + return 1; + } else { + // Copy the string, keeping track of length + unsigned int aLength = 0; + while (*theAsciiString != '\0') { + *(theUTF8String++) = *(theAsciiString++); + aLength++; + } + + // Append the null + *theUTF8String = '\0'; + aLength++; + + return (int)aLength; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8String to it's ASCII equivalent +// +// [in] theUTF8String, NULL terminated UTF8String +// [out] theAsciiString, NULL terminated c-string +// +// returns the length of theAsciiString +// +// Remarks: +// Unvalid ASCII characters are replaced with '?' +// Memory allocated for theAsciiString may need to be as large as +// the UTF8String UTF8String will be NULL terminated +int UTF8ToAsciiString(const UTF8String theUTF8String, char* theAsciiString) { + // Strip non-ascii characters and replace with REPLACE_INVALID_CHAR + const unsigned char* anInStream = (const unsigned char*)theUTF8String; + unsigned int aNumBytesWritten = 0; + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUTF8String == NULL) { + *theAsciiString = 0x00; + return 1; + } + + // Keep extracting characters until we get a '\0' + while (*anInStream != '\0') { + if (UTF8_IS_SINGLE_BYTE(*anInStream)) + theAsciiString[aNumBytesWritten++] = (char)*anInStream; + else + theAsciiString[aNumBytesWritten++] = REPLACE_INVALID_CHAR; + + // move to next character + anInStream++; + } + + // Append the '\0' + theAsciiString[aNumBytesWritten++] = '\0'; + return (int)aNumBytesWritten; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2 (Unicode) string to it's UTF8 equivalent +// +// [in] theUCS2String, double NULL terminated UTF8String +// [out] theUTF8String, NULL terminated c-string +// +// returns the length of theUTF8String +// +// Remarks: +// Memory allocated for theUTF8String may need to be up to 1.5* the +// size of theUCS2String +// This means that for each UCS2 character, 3 UTF8 characters may be +// generated +int UCS2ToUTF8String(const UCS2String theUCS2String, UTF8String theUTF8String) { + unsigned int aTotalBytesWritten = 0; + unsigned int aUTF8CharLength = 0; + const UCS2Char* anInStream = theUCS2String; + unsigned char* anOutStream = (unsigned char*)theUTF8String; + + // Allow for NULL here since SDKs allow for NULL string parameters + if (theUCS2String == NULL) { + *anOutStream = 0x00; + return 1; + } + + // Loop until we reach a NULL terminator + while (*anInStream != 0) { + aUTF8CharLength = (unsigned int)_UCS2CharToUTF8String( + *anInStream, (UTF8String)anOutStream); + + // Move out stream to next character position + anOutStream += aUTF8CharLength; + + // Move to next UCS2 character + anInStream++; + + // Record number of bytes written + aTotalBytesWritten += aUTF8CharLength; + } + + // Copy over the null terminator + *anOutStream = '\0'; + aTotalBytesWritten++; + + return (int)aTotalBytesWritten; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8 string to it's UCS2 (Unicode) equivalent +// +// [in] theUTF8String, NULL terminated UTF8String +// [out] theUCS2String, NULL terminated c-string +// +// returns the length of theUCS2String +// +// Remarks: +// Unvalid UTF8 characters are replaced with '?' +// Memory allocated for theAsciiString may need to be as large as +// the UTF8String UTF8String will be NULL terminated +int UTF8ToUCS2String(const UTF8String theUTF8String, UCS2String theUCS2String) { + return UTF8ToUCS2StringLen(theUTF8String, theUCS2String, + (gsi_i32)strlen(theUTF8String)); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Calculate the size needed to convert a UTF8String to a UCS2String +// +// [in] theUTF8String, NULL terminated UTF8String +// +// returns the length (in UCS2 characters) of theUCS2String that would be +// created +// +// Remarks: +// Unvalid UTF8 characters are treated as 1 byte +int _UTF8ToUCS2ConversionLengthOnly(const UTF8String theUTF8String) { + int length = 0; + const UTF8String theReadPos = theUTF8String; + + assert(theUTF8String != NULL); + if (theUTF8String == NULL) + return 0; + + while (*theReadPos != '\0') { + // Check for valid two byte string + if (UTF8_IS_TWO_BYTE(theReadPos[0]) && UTF8_IS_FOLLOW_BYTE(theReadPos[1])) + theReadPos += 2; + + // Check for valid three byte string + else if (UTF8_IS_THREE_BYTE(theReadPos[0]) && + UTF8_IS_FOLLOW_BYTE(theReadPos[1]) && + UTF8_IS_FOLLOW_BYTE(theReadPos[2])) { + theReadPos += 3; + } + // Anything else means one UTF8 character read from the buffer + else + theReadPos++; + + // Increment the length of the UCS2 string + length++; + } + + // don't count the null as a character, this conforms + // with ANSI strlen functions + return length; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Calculate the size needed to convert a UCS2String to a UTF8String +// +// [in] theUCS2String, NULL terminated UCS2String +// +// returns the length of theUTF8String that would be created +// +// Remarks: +// Unvalid UTF8 characters are treated as 1 byte +int _UCS2ToUTF8ConversionLengthOnly(const UCS2String theUCS2String) { + int length = 0; + const UCS2String theReadPos = theUCS2String; + assert(theUCS2String != NULL); + while (*theReadPos != 0x0000) { + // Values <= 0x7F are single byte ascii + if (*theReadPos <= 0x7F) + length++; + // Values > 0x7F and <= 0x07FF are two bytes in UTF8 + else if (*theReadPos <= 0x07FF) + length += 2; + // Anything else is 3 bytes of UTF8 + else + length += 3; + + // Set read pos to right spot (1 more UCS2 Character = 2 bytes) + theReadPos++; + } + + // don't count the null as a character, this conforms + // with ANSI strlen functions + return length; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8String to a UCS2String, allocate space for the UCS2String +// +// [in] theUTF8String, NULL terminated UTF8String +// +// returns the newly allocated UCS2String +// +// Remarks: +// The callee is responsible for freeing the allocated memory block +UCS2String UTF8ToUCS2StringAlloc(const UTF8String theUTF8String) { + // Allow for NULL here since SDKs allow for NULL string parameters + if (theUTF8String == NULL) + return NULL; + else { + // Find the length of the UCS2 string and allocate a block + int newLength = _UTF8ToUCS2ConversionLengthOnly(theUTF8String); + UCS2String aUCS2String = + (UCS2String)gsimalloc(sizeof(UCS2Char) * (newLength + 1)); + + // Do the conversion + UTF8ToUCS2String(theUTF8String, aUCS2String); + + // Return the allocated string + return aUCS2String; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2String to a UTF8String, allocate space for the UTF8String +// +// [in] UCS2String, NULL terminated UCS2String +// +// returns the newly allocated UTF8String +// +// Remarks: +// The callee is responsible for freeing the allocated memory block +UTF8String UCS2ToUTF8StringAlloc(const UCS2String theUCS2String) { + // Allow for NULL here since SDKs allow for NULL string parameters + if (theUCS2String == NULL) + return NULL; + else { + // Find the length of the UCS2 string and allocate a block + int newLength = _UCS2ToUTF8ConversionLengthOnly(theUCS2String); + UTF8String aUTF8String = + (UTF8String)gsimalloc(sizeof(char) * (newLength + 1)); + + // Do the conversion + UCS2ToUTF8String(theUCS2String, aUTF8String); + + // Return the allocated string + return aUTF8String; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8StringArray to a UCS2StringArray, allocate space for the +// UCS2Strings +// +// [in] UTF8StringArray, array of NULL terminated UTF8Strings +// [in] theNumStrings, how many strings are in the array +// +// returns the newly allocated UCS2StringArray +// +// Remarks: +// The callee is responsible for freeing the allocated memory +// block(s) +UCS2String* UTF8ToUCS2StringArrayAlloc(const UTF8String* theUTF8StringArray, + int theNumStrings) { + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUTF8StringArray == NULL || theNumStrings == 0) + return NULL; + else { + UCS2String* aUCS2StringArray = + (UCS2String*)gsimalloc(sizeof(UCS2String) * theNumStrings); + int stringNum = 0; + while (stringNum < theNumStrings) { + aUCS2StringArray[stringNum] = + UTF8ToUCS2StringAlloc(theUTF8StringArray[stringNum]); + stringNum++; + } + + return aUCS2StringArray; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2StringArray to a UTF8StringArray, allocate space for the +// UTF8Strings +// +// [in] UCS2StringArray, array of NULL terminated UCS2Strings +// [in] theNumStrings, how many strings are in the array +// +// returns the newly allocated UTF8StringArray +// +// Remarks: +// The callee is responsible for freeing the allocated memory block +UTF8String* UCS2ToUTF8StringArrayAlloc(const UCS2String* theUCS2StringArray, + int theNumStrings) { + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUCS2StringArray == NULL || theNumStrings == 0) + return NULL; + else { + UTF8String* aUTF8StringArray = + (UTF8String*)gsimalloc(sizeof(UTF8String) * theNumStrings); + int stringNum = 0; + while (stringNum < theNumStrings) { + aUTF8StringArray[stringNum] = + UCS2ToUTF8StringAlloc(theUCS2StringArray[stringNum]); + stringNum++; + } + + return aUTF8StringArray; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2String to an AsciiString +// +// [in] UCS2StringArray, NULL terminated UCS2String +// [in/out] theAsciiString, ascii representation +// +// returns the length of the Ascii string +// +// Remarks: +// callee is responsible for allocating memory for theAsciiString +// Invalid ASCII characters are truncated +// The ASCII buffer must be at least 1/2 the size of the UCS2String +int UCS2ToAsciiString(const UCS2String theUCS2String, char* theAsciiString) { + int length = 0; + const UCS2String aReadPos = theUCS2String; + char* aWritePos = theAsciiString; + + assert(theAsciiString != NULL); + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUCS2String == NULL) { + *theAsciiString = '\0'; + return 1; + } + + // Convert each character until a '\0' is reached + while (*aReadPos != '\0') { + (*aWritePos++) = (char)(0x00FF & (*aReadPos++)); + length++; + } + + // append the NULL + *aWritePos = '\0'; + length++; + + return length; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert an ASCII string to a UCS2String +// +// [in] theAsciiString, NULL terminated ASCII string +// [in/out] theUCS2String, UCS2String to be filled with the converted ASCII +// +// returns the length of the unicode string +// +// Remarks: +// The callee is responsible for allocating memory for +// theUCS2String the size returned should always be 2x the size +// passed in +int AsciiToUCS2String(const char* theAsciiString, UCS2String theUCS2String) { + int length = 0; + const char* aReadPos = theAsciiString; + UCS2String aWritePos = theUCS2String; + + assert(theUCS2String != NULL); + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theAsciiString == NULL) { + *theUCS2String = 0x0000; + return 1; + } + + // Convert each character until a '\0' is reached + while (*aReadPos != '\0') { + (*aWritePos++) = + (unsigned short)(0x00FF & (*aReadPos++)); // copy and strip extra byte + length++; + } + + // append a NULL terminator to the UCS2String + *aWritePos = '\0'; + length++; + + return length; +} + +/* +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UCS2String to a UTF8String with a maximum length +// +// [in] theUCS2String, NULL terminated UCS2String +// [in/out] theUTF8String, The UTF8 equivilent of theUCS2String +// [in] theMaxLength, maximum number of UTF8 characters to write +// +// returns the length of the UTF8String +// +// Remarks: +// The length of theUTF8String will not exceed theMaxLength +supplied. int UCS2ToUTF8StringLength(const UCS2String theUCS2String, UTF8String +theUTF8String, int theMaxLength) +{ + return 0; +} +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Convert a UTF8String to a UCS2String with a maximum length +// +// [in] theUTF8String, NULL terminated UTF8String +// [in/out] theUCS2String, The UCS2 equivilent of theUTF8String +// [in] theMaxLength, maximum number of UTF8 characters to write +// +// returns the length of the UCS2String +// +// Remarks: +// The length of theUCS2String will not exceed theMaxLength +// supplied. +int UTF8ToUCS2StringLen(const UTF8String theUTF8String, + UCS2String theUCS2String, int theMaxLength) { + int aNumCharsWritten = 0; + int aNumBytesRead = 0; + int aTotalBytesRead = 0; + const unsigned char* anInStream = (const unsigned char*)theUTF8String; + UCS2Char* anOutStream = theUCS2String; + + // Allow for NULL here since SDKs allow for NULL string arrays + if (theUTF8String == NULL) { + *anOutStream = 0x0000; + return 1; + } + + // Loop until we find the NULL terminator + while (*anInStream != '\0' && theMaxLength > aTotalBytesRead) { + // Convert one character + aNumBytesRead = _ReadUCS2CharFromUTF8String( + (UTF8String)anInStream, anOutStream, theMaxLength - aTotalBytesRead); + if (aNumBytesRead == 0) { + // Error, read past end of buffer + theUCS2String[0] = 0x0000; + return 0; + } + aTotalBytesRead += aNumBytesRead; + + // Move InStream position to new data + anInStream += aNumBytesRead; + + // Keep track of characters written + aNumCharsWritten++; + + // Move OutStream to next write position + anOutStream++; + } + + // NULL terminate the UCS2String + *anOutStream = 0x0000; + aNumCharsWritten++; + + return aNumCharsWritten; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/source/gamespy/common/gsStringUtil.h b/source/gamespy/common/gsStringUtil.h new file mode 100644 index 000000000..185563821 --- /dev/null +++ b/source/gamespy/common/gsStringUtil.h @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +// String utilities used by the SDKs + +#ifdef __cplusplus +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define ALIGNED + +#define UCS2Char unsigned short +#define UCS2String unsigned short* // null terminated +#define UTF8ByteType unsigned char // For type casting +#define UTF8String \ + char* // may not be NULL terminated when treated as a single character + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define UTF8_FOLLOW_BYTE_TAG \ + 0x80 //:1000 0000 // Identifies 2nd or 3rd byte of UTF8String +#define UTF8_TWO_BYTE_TAG \ + 0xC0 //:1100 0000 // Identifies start of Two-byte UTF8String +#define UTF8_THREE_BYTE_TAG \ + 0xE0 //:1110 0000 // Identifies start of Three-byte UTF8String +#define UTF8_FOUR_BYTE_TAG \ + 0xF0 //:1111 0000 // Unsupported tag, need USC4 to store this + +#define UTF8_FOLLOW_BYTE_MASK \ + 0x3F //:0011 1111 // The value bits in a follow byte +#define UTF8_TWO_BYTE_MASK \ + 0x1F //:0001 1111 // The value bits in a two byte tag +#define UTF8_THREE_BYTE_MASK \ + 0x0F //:0000 1111 // The value bits in a three byte tag + +#define UTF8_IS_THREE_BYTE(a) \ + (((UTF8ByteType)a & UTF8_FOUR_BYTE_TAG) == UTF8_THREE_BYTE_TAG) +#define UTF8_IS_TWO_BYTE(a) \ + (((UTF8ByteType)a & UTF8_THREE_BYTE_TAG) == UTF8_TWO_BYTE_TAG) +#define UTF8_IS_FOLLOW_BYTE(a) \ + (((UTF8ByteType)a & UTF8_TWO_BYTE_TAG) == UTF8_FOLLOW_BYTE_TAG) +#define UTF8_IS_SINGLE_BYTE(a) ((UTF8ByteType)a <= 0x7F) // 0-127 + +#define REPLACE_INVALID_CHAR '?' // Replace invalid UTF8 chars with this + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Prototypes +// '_' denotes internal use functions +int _ReadUCS2CharFromUTF8String(const UTF8String theUTF8String, + UCS2Char* theUnicodeChar, int theMaxLength); +int _UCS2CharToUTF8String(UCS2Char theUCS2Char, UTF8String theUTF8String); +int _UCS2ToUTF8ConversionLengthOnly(const UCS2String theUCS2String); +int _UTF8ToUCS2ConversionLengthOnly(const UTF8String theUTF8String); + +// Convert string types +int AsciiToUTF8String(const char* theAsciiString, UTF8String theUTF8String); +int UTF8ToAsciiString(const UTF8String theUTF8String, char* theAsciiString); +int UCS2ToUTF8String(const UCS2String theUCS2String, UTF8String theUTF8String); +int UTF8ToUCS2String(const UTF8String theUTF8String, UCS2String theUCS2String); +int UCS2ToAsciiString(const UCS2String theUCS2String, char* theAsciiString); +int AsciiToUCS2String(const char* theAsciiString, UCS2String theUCS2String); + +// Convert with maximum buffer length +// similar to strncpy +// int UCS2ToUTF8StringLength(const UCS2String theUCS2String, UTF8String +// theUTF8String, int theMaxLength); +int UTF8ToUCS2StringLen(const UTF8String theUTF8String, + UCS2String theUCS2String, int theMaxLength); + +// Convert a string, allocate space for the new string +UTF8String UCS2ToUTF8StringAlloc(const UCS2String theUCS2String); +UCS2String UTF8ToUCS2StringAlloc(const UTF8String theUTF8String); + +// Convert an array of strings, allocate space for the new strings +UTF8String* UCS2ToUTF8StringArrayAlloc(const UCS2String* theUCS2StringArray, + int theNumStrings); +UCS2String* UTF8ToUCS2StringArrayAlloc(const UTF8String* theUTF8StringArray, + int theNumStrings); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/source/gamespy/common/gsUdpEngine.c b/source/gamespy/common/gsUdpEngine.c new file mode 100644 index 000000000..fbe09c778 --- /dev/null +++ b/source/gamespy/common/gsUdpEngine.c @@ -0,0 +1,1143 @@ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Communication Engine +#include "gsUdpEngine.h" + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Internal Structures + +// Internal representation of a connection +// Also includes a ip and port for mapping to a connection +typedef struct { + unsigned int mAddr; + unsigned short mPort; + GT2Connection mConnection; +} GSUdpRemotePeer; + +// Message handler used filter traffic to a specific SDK or part of application +typedef struct { + unsigned char mInitialMsg[GS_UDP_MSG_HEADER_LEN]; + unsigned char mHeader[GS_UDP_MSG_HEADER_LEN]; + DArray mPendingConnections; + gsUdpConnClosedCallback mClosed; + gsUdpConnReceivedDataCallback mReceived; + gsUdpConnConnectedCallback mConnected; + gsUdpConnPingCallback mPingReply; + gsUdpErrorCallback mNetworkError; + void* mUserData; +} GSUdpMsgHandler; + +// The internal representation of UDP Communication Engine +typedef struct { + GT2Socket mSocket; + DArray mRemotePeers; + DArray mMsgHandlers; + gsi_bool mInitialized; + // Application callbacks for connection that gets + // un-handled messages + gsUdpConnConnectedCallback mAppConnected; + gsUdpConnClosedCallback mAppClosed; + gsUdpConnPingCallback mAppPingReply; + gsUdpConnReceivedDataCallback mAppRecvData; + gsUdpAppConnectAttemptCallback mAppConnAttempt; + // Error callback ? + gsUdpErrorCallback mAppNetworkError; + // Unknown Message + gsUdpUnknownMsgCallback mAppUnknownMessage; + void* mAppUserData; + + // This was any easier way to keep track of pending connections + // It does not rely on an address since it only keeps track of pending + // connections an app makes. + int mAppPendingConnections; + unsigned int mLocalAddr; + unsigned short mLocalPort; +} GSUdpEngineObject; + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Acts as the function to return the singleton +// This function is not exposed. It will only be +// used internally to do any modifications to the +// GSUdpEngineObject +GSUdpEngineObject* gsUdpEngineGetEngine() { + static GSUdpEngineObject aUdpObject; + return &aUdpObject; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Message Handler DArray functions +void gsUdpMsgHandlerFree(void* theMsgHandler) { + GSUdpMsgHandler* aHandler = (GSUdpMsgHandler*)theMsgHandler; + ArrayFree(aHandler->mPendingConnections); +} + +// Used to find a message handler based on the initial message. +int gsUdpMsgHandlerCompare(const void* theFirstHandler, + const void* theSecondHandler) { + GSUdpMsgHandler *msgHandler1 = (GSUdpMsgHandler*)theFirstHandler, + *msgHandler2 = (GSUdpMsgHandler*)theSecondHandler; + int initCmp; + initCmp = memcmp(msgHandler1->mInitialMsg, msgHandler2->mInitialMsg, + GS_UDP_MSG_HEADER_LEN); + return initCmp; +} + +// Used to find a message handler based on the header. +int gsUdpMsgHandlerCompare2(const void* theFirstHandler, + const void* theSecondHandler) { + GSUdpMsgHandler *msgHandler1 = (GSUdpMsgHandler*)theFirstHandler, + *msgHandler2 = (GSUdpMsgHandler*)theSecondHandler; + int headerCmp; + headerCmp = + memcmp(msgHandler1->mHeader, msgHandler2->mHeader, GS_UDP_MSG_HEADER_LEN); + return headerCmp; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Remote Peer DArray compare functions + +// Finds a remote peer based on IP and Port +int gsUdpRemotePeerCompare(const void* theFirstPeer, + const void* theSecondPeer) { + GSUdpRemotePeer *aPeer1 = (GSUdpRemotePeer*)theFirstPeer, + *aPeer2 = (GSUdpRemotePeer*)theSecondPeer; + if (aPeer1->mAddr != aPeer2->mAddr) + return 1; + if (aPeer1->mPort != aPeer2->mPort) + return 1; + + return 0; +} + +// Finds a remote peer based on a GT2Connection +int gsUdpRemotePeerCompare2(const void* theFirstPeer, + const void* theSecondPeer) { + GSUdpRemotePeer *aPeer1 = (GSUdpRemotePeer*)theFirstPeer, + *aPeer2 = (GSUdpRemotePeer*)theSecondPeer; + if (aPeer1->mConnection != aPeer2->mConnection) + return 1; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Lets the Message Handler and App know about network errors +void gsUdpSocketError(GT2Socket theSocket) { + int i, len; + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Socket error, passing to app and message handlers\n"); + if (aUdp->mAppNetworkError) + aUdp->mAppNetworkError(GS_UDP_NETWORK_ERROR, aUdp->mAppUserData); + len = ArrayLength(aUdp->mMsgHandlers); + for (i = 0; i < len; i++) { + GSUdpMsgHandler* aMsgHandler = + (GSUdpMsgHandler*)ArrayNth(aUdp->mMsgHandlers, i); + if (aMsgHandler->mNetworkError) + aMsgHandler->mNetworkError(GS_UDP_NETWORK_ERROR, aMsgHandler->mUserData); + } + GSI_UNUSED(theSocket); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Lets the App and Message Handlers know a peer left +void gsUdpClosedRoutingCB(GT2Connection theConnection, GT2CloseReason reason) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GSUdpRemotePeer aRemotePeer; + int index, len; + GSUdpCloseReason aReason; + char anAddr[GS_IP_ADDR_AND_PORT]; + + if (reason == GT2CommunicationError || reason == GT2SocketError) + aReason = GS_UDP_CLOSED_BY_COMM_ERROR; + else if (reason == GT2NotEnoughMemory) + aReason = GS_UDP_CLOSED_BY_LOW_MEM; + else + aReason = (GSUdpCloseReason)reason; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection closed to %s\n", + gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + len = ArrayLength(aUdp->mMsgHandlers); + for (index = 0; index < len; index++) { + GSUdpMsgHandler* aHandler = + (GSUdpMsgHandler*)ArrayNth(aUdp->mMsgHandlers, index); + if (aHandler->mClosed) { + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection closed: passed to message handler\n"); + aHandler->mClosed(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), aReason, + aHandler->mUserData); + } + } + + if (aUdp->mAppClosed) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "[Udp Engine] Connection closed: passed to app\n"); + aUdp->mAppClosed(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), aReason, + aUdp->mAppUserData); + } + + aRemotePeer.mConnection = theConnection; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare2, + 0, 0); + if (index != NOT_FOUND) { + ArrayDeleteAt(aUdp->mRemotePeers, index); + } + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// When a peer has accepted a GT2Connection the UDP layer needs to let +// higher level app or message handler know that it accepted the request to +// to message a peer. +void gsUdpConnectedRoutingCB(GT2Connection theConnection, GT2Result theResult, + GT2Byte* theMessage, int theMessageLen) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + int aIndex, len; + GSUdpErrorCode aCode; + char anAddr[GS_IP_ADDR_AND_PORT]; + + switch (theResult) { + case GT2NegotiationError: + aCode = GS_UDP_REMOTE_ERROR; + break; + case GT2Rejected: + aCode = GS_UDP_REJECTED; + break; + case GT2TimedOut: + aCode = GS_UDP_TIMED_OUT; + break; + case GT2Success: + aCode = GS_UDP_NO_ERROR; + break; + default: + aCode = GS_UDP_UNKNOWN_ERROR; + break; + } + if (theResult == GT2Rejected) { + int aRemotePeerIdx; + GSUdpRemotePeer aRemotePeer; + aRemotePeer.mAddr = gt2GetRemoteIP(theConnection); + aRemotePeer.mPort = gt2GetRemotePort(theConnection); + aRemotePeerIdx = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, + gsUdpRemotePeerCompare, 0, 0); + if (aRemotePeerIdx != NOT_FOUND) { + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connect rejected by %s\n", + gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + + ArrayDeleteAt(aUdp->mRemotePeers, aRemotePeerIdx); + } + } + + len = ArrayLength(aUdp->mMsgHandlers); + for (aIndex = 0; aIndex < len; aIndex++) { + int aRemotePeerIdx; + GSUdpRemotePeer aRemotePeer; + GSUdpMsgHandler* aTempHandler = + (GSUdpMsgHandler*)ArrayNth(aUdp->mMsgHandlers, aIndex); + + aRemotePeer.mAddr = gt2GetRemoteIP(theConnection); + aRemotePeer.mPort = gt2GetRemotePort(theConnection); + aRemotePeerIdx = ArraySearch(aTempHandler->mPendingConnections, + &aRemotePeer, gsUdpRemotePeerCompare, 0, 0); + if (aRemotePeerIdx != NOT_FOUND) { + if (aTempHandler->mConnected) { + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Passing connect result to message handler\n"); + aTempHandler->mConnected( + gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), + aCode, theResult == GT2Rejected ? gsi_true : gsi_false, + aTempHandler->mUserData); + } + ArrayDeleteAt(aTempHandler->mPendingConnections, aRemotePeerIdx); + return; + } + } + + if (aUdp->mAppPendingConnections > 0) { + if (aUdp->mAppConnected) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "[Udp Engine] Passing connect result to app\n"); + aUdp->mAppConnected( + gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), aCode, + theResult == GT2Rejected ? gsi_true : gsi_false, aUdp->mAppUserData); + } + aUdp->mAppPendingConnections--; + } + GSI_UNUSED(theMessage); + GSI_UNUSED(theMessageLen); + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Pings are passed on to higher level app or message handlers +void gsUdpPingRoutingCB(GT2Connection theConnection, int theLatency) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + int index, len; + char anAddr[GS_IP_ADDR_AND_PORT]; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Received ping from %s\n", + gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + len = ArrayLength(aUdp->mMsgHandlers); + for (index = 0; index < len; index++) { + GSUdpMsgHandler* aHandler = + (GSUdpMsgHandler*)ArrayNth(aUdp->mMsgHandlers, index); + if (aHandler->mPingReply) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "[Udp Engine] Passed to message handler\n"); + aHandler->mPingReply(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), + (unsigned int)theLatency, aHandler->mUserData); + return; + } + } + + if (aUdp->mAppPingReply) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, "[Udp Engine] Passed to app\n"); + aUdp->mAppPingReply(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), + (unsigned int)theLatency, aUdp->mAppUserData); + } + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Any data received prompts the UDP layer to first find a higher level +// message handler to handle the data. If there was no message handler +// found, the data is passed to the higher level app. +void gsUdpReceivedRoutingCB(GT2Connection theConnection, GT2Byte* theMessage, + int theMessageLen, GT2Bool reliable) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GSUdpMsgHandler aHandler; + int index; + char anAddr[GS_IP_ADDR_AND_PORT]; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Received data from %s\n", + gt2AddressToString(gt2GetRemoteIP(theConnection), + gt2GetRemotePort(theConnection), anAddr)); + // If there is a handler, pass it to the handler + // The header should not be stripped off + if (theMessageLen >= GS_UDP_MSG_HEADER_LEN) { + memcpy(aHandler.mHeader, theMessage, GS_UDP_MSG_HEADER_LEN); + + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare2, + 0, 0); + if (index != NOT_FOUND) { + GSUdpMsgHandler* aHandlerFound = + (GSUdpMsgHandler*)ArrayNth(aUdp->mMsgHandlers, index); + if (aHandlerFound->mReceived) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "[Udp Engine] Passed to message handler\n"); + aHandlerFound->mReceived( + gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), + theMessage + GS_UDP_MSG_HEADER_LEN, + (unsigned int)(theMessageLen - GS_UDP_MSG_HEADER_LEN), reliable, + aHandlerFound->mUserData); + return; + } + } + } + + if (aUdp->mAppRecvData) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, "[Udp Engine] Passed to app\n"); + aUdp->mAppRecvData( + gt2GetRemoteIP(theConnection), gt2GetRemotePort(theConnection), + theMessage, (unsigned int)theMessageLen, reliable, aUdp->mAppUserData); + } + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Messages that are not recognized are passed only to the higher level app +// since message handlers always request to talk to peers +GT2Bool gsUdpUnrecognizedMsgCB(GT2Socket theSocket, unsigned int theIp, + unsigned short thePort, GT2Byte* theMessage, + int theMsgLen) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + char anAddr[GS_IP_ADDR_AND_PORT]; + if (aUdp->mAppUnknownMessage) { + gsi_bool aRet; + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "[Udp Engine] Unknown message from %s, passing to app\n", + gt2AddressToString(theIp, thePort, anAddr)); + aRet = + aUdp->mAppUnknownMessage(theIp, thePort, (unsigned char*)theMessage, + (unsigned int)theMsgLen, aUdp->mAppUserData); + return aRet ? GT2True : GT2False; + } + + GSI_UNUSED(theSocket); + GSI_UNUSED(anAddr); + return GT2False; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Requests for communication from a peer is handled by first checking if the +// initial message has a message handler registered for it. Otherwise +// the message is passed onto the app. +void gsUdpConnAttemptCB(GT2Socket socket, GT2Connection connection, + unsigned int ip, unsigned short port, int latency, + GT2Byte* message, int len) { + // Get the message handler for the connection + int index; + GSUdpMsgHandler aHandler; + GSUdpRemotePeer aRemotePeer; + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + char anAddr[GS_IP_ADDR_AND_PORT]; + + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Comment, + "[Udp Engine] Connection attempt from %s\n", + gt2AddressToString(ip, port, anAddr)); + // If there is a handler, automatically accept a connection if the initial + // message is the same as the handler's registered initial message + if (len >= GS_UDP_MSG_HEADER_LEN) { + memcpy(aHandler.mInitialMsg, message, GS_UDP_MSG_HEADER_LEN); + + aRemotePeer.mAddr = ip; + aRemotePeer.mPort = port; + aRemotePeer.mConnection = connection; + + ArrayAppend(aUdp->mRemotePeers, &aRemotePeer); + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare, + 0, 0); + if (index != NOT_FOUND) { + GT2ConnectionCallbacks aCallbacks; + + aCallbacks.closed = gsUdpClosedRoutingCB; + aCallbacks.connected = gsUdpConnectedRoutingCB; + aCallbacks.ping = gsUdpPingRoutingCB; + aCallbacks.received = gsUdpReceivedRoutingCB; + + // Automatically accept connections for Message Handlers + gt2Accept(aRemotePeer.mConnection, &aCallbacks); + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "[Udp Engine] Connection attempt auto-accepted for message " + "handler\n"); + return; + } + } + // all other messages go to the app + if (aUdp->mAppConnAttempt) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Comment, + "[Udp Engine] Connection attempt from %s, asking app to " + "accept/reject\n", + gt2AddressToString(ip, port, anAddr)); + aUdp->mAppConnAttempt(ip, port, latency, (unsigned char*)message, + (unsigned int)len, aUdp->mAppUserData); + } else { + // Reject any un-handled connections or unknown connections + gt2Reject(connection, NULL, 0); + ArrayRemoveAt(aUdp->mRemotePeers, ArrayLength(aUdp->mRemotePeers) - 1); + } + GSI_UNUSED(socket); + GSI_UNUSED(anAddr); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Public functions + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Used to check if the UDP layer needs to be initialized +gsi_bool gsUdpEngineIsInitialized() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + return aUdp->mInitialized; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Initializes the UDP layer +// A specific port can be used if needed. +// An app must register their callbacks here. +// An App must also call this before starting SDKs since they might start +// the UDP layer without the app having a chance to register its callbacks. +// Any message handler can get the port the UDP layer is using if it did not +// initialize the UDP Layer. +// Use of the UDP Layer requires it to be initialized as is the case with most +// functions below. +GSUdpErrorCode gsUdpEngineInitialize( + unsigned short thePort, int theIncomingBufSize, int theOutgoingBufSize, + gsUdpErrorCallback theAppNetworkError, + gsUdpConnConnectedCallback theAppConnected, + gsUdpConnClosedCallback theAppClosed, gsUdpConnPingCallback theAppPing, + gsUdpConnReceivedDataCallback theAppReceive, + gsUdpUnknownMsgCallback theAppUnownMsg, + gsUdpAppConnectAttemptCallback theAppConnectAttempt, void* theAppUserData) { + int incomingBufferSize, outgoingBufferSize; + char anAddr[GS_IP_ADDR_AND_PORT]; + GT2Result aGt2Result; + // Grab the single instance of the UDP Communication Engine + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + + // Setup our gt2 buffer sizes for reliable messages + + incomingBufferSize = + theIncomingBufSize != 0 ? theIncomingBufSize : GS_UDP_DEFAULT_IN_BUFFSIZE; + outgoingBufferSize = theOutgoingBufSize != 0 ? theOutgoingBufSize + : GS_UDP_DEFAULT_OUT_BUFFSIZE; + + // Setup our internal socket that will be shared among more than one + // application + aUdp->mAppNetworkError = theAppNetworkError; + aUdp->mAppUnknownMessage = theAppUnownMsg; + aUdp->mAppConnected = theAppConnected; + aUdp->mAppClosed = theAppClosed; + aUdp->mAppPingReply = theAppPing; + aUdp->mAppRecvData = theAppReceive; + aUdp->mAppConnAttempt = theAppConnectAttempt; + + // Any port can be used + gt2AddressToString(0, thePort, anAddr); + aGt2Result = gt2CreateSocket(&aUdp->mSocket, anAddr, outgoingBufferSize, + incomingBufferSize, gsUdpSocketError); + if (aGt2Result != GT2Success) { + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_WarmError, + "[Udp Engine] error creating gt2 socket, error code: %d\n", aGt2Result); + return GS_UDP_NETWORK_ERROR; + } + // We'll need to keep track of connections with an array of GPconnection to + // address mapping + aUdp->mRemotePeers = ArrayNew(sizeof(GSUdpRemotePeer), 1, NULL); + if (aUdp->mRemotePeers == NULL) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, + GSIDebugLevel_HotError, "[Udp Engine] No more memory!!!\n"); + return GS_UDP_NO_MEMORY; + } + + aUdp->mMsgHandlers = + ArrayNew(sizeof(GSUdpMsgHandler), 1, gsUdpMsgHandlerFree); + if (aUdp->mMsgHandlers == NULL) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, + GSIDebugLevel_HotError, "[Udp Engine] No more memory!!!\n"); + return GS_UDP_NO_MEMORY; + } + + // Used by the manager to receive messages from backend services or other + // clients that may not be using gt2 + gt2SetUnrecognizedMessageCallback(aUdp->mSocket, gsUdpUnrecognizedMsgCB); + + // Automatically start listening for all SDKS and the game if game uses UDP + // Engine + gt2Listen(aUdp->mSocket, gsUdpConnAttemptCB); + aUdp->mLocalAddr = gt2GetLocalIP(aUdp->mSocket); + aUdp->mLocalPort = gt2GetLocalPort(aUdp->mSocket); + aUdp->mAppPendingConnections = 0; + aUdp->mInitialized = gsi_true; + if (theAppUserData) { + aUdp->mAppUserData = theAppUserData; + } else + aUdp->mAppUserData = NULL; + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Used obtain the peer's state +// theIp and thePort cannot be 0 (Zero) +GSUdpErrorCode gsUdpEngineGetPeerState(unsigned int theIp, + unsigned short thePort, + GSUdpPeerState* thePeerState) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GSUdpRemotePeer aPeer, *aPeerFound; + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + GS_ASSERT(thePeerState != NULL); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_State, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + *thePeerState = GS_UDP_PEER_CLOSED; + return GS_UDP_NOT_INITIALIZED; + } + + aPeer.mAddr = theIp; + aPeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aPeer, gsUdpRemotePeerCompare, 0, 0); + if (index == NOT_FOUND) { + *thePeerState = GS_UDP_PEER_CLOSED; + return GS_UDP_NO_ERROR; + } + + aPeerFound = (GSUdpRemotePeer*)ArrayNth(aUdp->mRemotePeers, index); + + *thePeerState = + (GSUdpPeerState)gt2GetConnectionState(aPeerFound->mConnection); + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Starts a request to open a communication channel with another peer based on +// IP and port. +GSUdpErrorCode +gsUdpEngineStartTalkingToPeer(unsigned int theIp, unsigned short thePort, + char theInitMsg[GS_UDP_MSG_HEADER_LEN], + int timeOut) { + char anAddr[GS_IP_ADDR_AND_PORT]; + GSUdpRemotePeer aRemotePeer; + GSUdpMsgHandler aHandler; + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GT2ConnectionCallbacks aCallbacks; + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (theIp == 0 || thePort == 0) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid parameter(s), check ip, port"); + return GS_UDP_PARAMETER_ERROR; + } + + aRemotePeer.mAddr = theIp; // In Network Byte Order for GT2 + aRemotePeer.mPort = thePort; // In Host Byte Order for GT2 + + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, + 0, 0); + if (index != NOT_FOUND) { + GSUdpRemotePeer* aPeerFound = + (GSUdpRemotePeer*)ArrayNth(aUdp->mRemotePeers, index); + GT2ConnectionState aState = gt2GetConnectionState(aPeerFound->mConnection); + if (aState == GT2Connected) { + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine is already talking to remote address\n"); + return GS_UDP_ADDRESS_ALREADY_IN_USE; + } else if (aState == GT2Connecting) { + memcpy(aHandler.mInitialMsg, theInitMsg, GS_UDP_MSG_HEADER_LEN); + + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare, + 0, 0); + if (index != NOT_FOUND) { + GSUdpMsgHandler* aHandlerFound = + (GSUdpMsgHandler*)ArrayNth(aUdp->mMsgHandlers, index); + ArrayAppend(aHandlerFound->mPendingConnections, aPeerFound); + } + } + } else { + gt2AddressToString(theIp, thePort, anAddr); + aCallbacks.closed = gsUdpClosedRoutingCB; + aCallbacks.connected = gsUdpConnectedRoutingCB; + aCallbacks.ping = gsUdpPingRoutingCB; + aCallbacks.received = gsUdpReceivedRoutingCB; + + // start the connect without blocking since we want the engine to be as + // asynchronous as possible + gt2Connect(aUdp->mSocket, &aRemotePeer.mConnection, anAddr, + (unsigned char*)theInitMsg, GS_UDP_MSG_HEADER_LEN, timeOut, + &aCallbacks, GT2False); + + ArrayAppend(aUdp->mRemotePeers, &aRemotePeer); + + memcpy(aHandler.mInitialMsg, theInitMsg, GS_UDP_MSG_HEADER_LEN); + + index = ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare, + 0, 0); + if (index != NOT_FOUND) { + GSUdpRemotePeer* aRemotePeerPtr = (GSUdpRemotePeer*)ArrayNth( + aUdp->mRemotePeers, ArrayLength(aUdp->mRemotePeers) - 1); + GSUdpMsgHandler* aHandlerFound = + (GSUdpMsgHandler*)ArrayNth(aUdp->mMsgHandlers, index); + ArrayAppend(aHandlerFound->mPendingConnections, &aRemotePeerPtr); + } else { + aUdp->mAppPendingConnections++; + } + } + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Accepts a Peer's request for communication +// Should only be used by App +GSUdpErrorCode gsUdpEngineAcceptPeer(unsigned int theIp, + unsigned short thePort) { + GSUdpRemotePeer aRemotePeer; + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (theIp == 0 || thePort == 0) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid parameter(s), check ip, port\n"); + return GS_UDP_PARAMETER_ERROR; + } + + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, + 0, 0); + if (index != NOT_FOUND) { + GT2ConnectionCallbacks aCallbacks; + + GSUdpRemotePeer* aPeerFound = + (GSUdpRemotePeer*)ArrayNth(aUdp->mRemotePeers, index); + + aCallbacks.closed = gsUdpClosedRoutingCB; + aCallbacks.connected = gsUdpConnectedRoutingCB; + aCallbacks.ping = gsUdpPingRoutingCB; + aCallbacks.received = gsUdpReceivedRoutingCB; + + gt2Accept(aPeerFound->mConnection, &aCallbacks); + } + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Rejects a Peer's request for communication +// Should only be used by App +GSUdpErrorCode gsUdpEngineRejectPeer(unsigned int theIp, + unsigned short thePort) { + GSUdpRemotePeer aRemotePeer; + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (theIp == 0 || thePort == 0) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid parameter(s), check ip, port\n"); + return GS_UDP_PARAMETER_ERROR; + } + + // Find the connection to reject in our array of peers + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, + 0, 0); + if (index != NOT_FOUND) { + GSUdpRemotePeer* aPeerFound = + (GSUdpRemotePeer*)ArrayNth(aUdp->mRemotePeers, index); + gt2Reject(aPeerFound->mConnection, NULL, 0); + ArrayDeleteAt(aUdp->mRemotePeers, index); + } + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Sends a message to a peer using IP and Port +// An empty header constitutes the app sending this message. +// theIp and thePort cannot be 0 (Zero) +// +// WARNING: Messages should not be greater than the outgoing buffer size minus +// the header and the 7 byte header for reliable messages (used for internal gt2 +// operations). Most UDP fragmentation occurs if messages are bigger than 1500 +// bytes. Also, some routers are known to drop those packets that are larger +// than 1500 bytes. The recommended outgoing buffer size is the default (1460). +// So take that, and subtract 16 for message handler header and reliable message +// header (if sending data reliably). freeSpace = 1460 - 16 - 7 +GSUdpErrorCode gsUdpEngineSendMessage(unsigned int theIp, + unsigned short thePort, + char theHeader[GS_UDP_MSG_HEADER_LEN], + unsigned char* theMsg, + unsigned int theMsgLen, + gsi_bool theReliable) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + int aTotalMessageLen, index; + GSUdpRemotePeer aRemotePeer, *aRemotePeerFound; + GT2Byte* fullMessage; + GT2Result aResult; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + // Messages being sent with an empty header are treated as app messages + if (!theHeader[0]) + aTotalMessageLen = (int)theMsgLen; + else + aTotalMessageLen = (int)(GS_UDP_MSG_HEADER_LEN + theMsgLen); + + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, + 0, 0); + if (index == NOT_FOUND) { + char anAddr[GS_IP_ADDR_AND_PORT]; + gt2AddressToString(theIp, thePort, anAddr); + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_WarmError, + "[Udp Engine] address not found for sending message\n", anAddr); + return GS_UDP_ADDRESS_ERROR; + } else { + aRemotePeerFound = (GSUdpRemotePeer*)ArrayNth(aUdp->mRemotePeers, index); + } + + if (aTotalMessageLen > + gt2GetOutgoingBufferSize(aRemotePeerFound->mConnection) && + theReliable) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_WarmError, + "[Udp Engine] Message Size too large, dropping message\n"); + return GS_UDP_MSG_TOO_BIG; + } + + fullMessage = (GT2Byte*)gsimalloc((unsigned long)aTotalMessageLen); + memcpy(fullMessage, theHeader, GS_UDP_MSG_HEADER_LEN); + memcpy(fullMessage + GS_UDP_MSG_HEADER_LEN, theMsg, theMsgLen); + // Send the message + // reliable messages will be kept in the outgoing buffers till they are sent + aResult = gt2Send(aRemotePeerFound->mConnection, fullMessage, + aTotalMessageLen, theReliable); + gsifree(fullMessage); + + if (aResult != GT2Success) + return GS_UDP_SEND_FAILED; + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// One the UDP Layer is initialized, this function +// should be called every 10-100 ms +// Message handlers already call this which means +// that the app may not have to call this function +GSUdpErrorCode gsUdpEngineThink() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + gt2Think(aUdp->mSocket); + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Shutdown should only happen if following is met: +// Apps can call this function after they have shutdown message handlers (SDKs), +// and verified there are no message handlers remaining. Calling the function +// gsUdpEngineNoMoreMsgHandlers will do this. +// Message handlers cannot call this function without checking if there no more +// message handlers remaining and if there is no app (gsUdpEngineNoApp). +GSUdpErrorCode gsUdpEngineShutdown() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + gt2CloseSocket(aUdp->mSocket); + ArrayFree(aUdp->mMsgHandlers); + ArrayFree(aUdp->mRemotePeers); + aUdp->mInitialized = gsi_false; + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Obtains the lower level socket that can be used to perform other operations +// or pass to other SDKs. +SOCKET gsUdpEngineGetSocket() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_HotError, + "[Udp Engine] Engine not initialized\n"); + return INVALID_SOCKET; + } + + return gt2GetSocketSOCKET(aUdp->mSocket); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Gets the local IP the UDP layer is bound to. +unsigned int gsUdpEngineGetLocalAddr() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_HotError, + "[Udp Engine] Engine not initialized\n"); + return (unsigned int)-1; + } + return aUdp->mLocalAddr; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Gets the local port the UDP layer is bound to. +unsigned short gsUdpEngineGetLocalPort() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_HotError, + "[Udp Engine] Engine not initialized\n"); + return 0; + } + return aUdp->mLocalPort; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Message handlers are added using this function +// The initial message and header of a message handler cannot be empty +// However, they can be the same. +// User data can be useful for keeping track of Message Handler +GSUdpErrorCode +gsUdpEngineAddMsgHandler(char theInitMsg[GS_UDP_MSG_HEADER_LEN], + char theHeader[GS_UDP_MSG_HEADER_LEN], + gsUdpErrorCallback theMsgHandlerError, + gsUdpConnConnectedCallback theMsgHandlerConnected, + gsUdpConnClosedCallback theMsgHandlerClosed, + gsUdpConnPingCallback theMsgHandlerPing, + gsUdpConnReceivedDataCallback theMsgHandlerRecv, + void* theUserData) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GSUdpMsgHandler aMsgHandler; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theInitMsg || theInitMsg[0]); + GS_ASSERT(theHeader || theHeader[0]); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + // setup a message handler that the UDP engine will use to pass connection + // attempts to + + // check for valid input + if (!theInitMsg[0]) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid init message\n"); + return GS_UDP_PARAMETER_ERROR; + } + + if (!theHeader[0]) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Invalid header\n"); + return GS_UDP_PARAMETER_ERROR; + } + + // This check is not necessary. Some SDKs may not use all callbacks + /*if (!theMsgHandlerError || !theMsgHandlerConnected || !theMsgHandlerClosed + || !theMsgHandlerPing || !theMsgHandlerRecv) + { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, + GSIDebugLevel_Debug, + "[Udp Engine] Invalid callback(s)"); + return GS_UDP_PARAMETER_ERROR; + } + */ + aMsgHandler.mClosed = theMsgHandlerClosed; + aMsgHandler.mConnected = theMsgHandlerConnected; + aMsgHandler.mPingReply = theMsgHandlerPing; + aMsgHandler.mReceived = theMsgHandlerRecv; + + aMsgHandler.mNetworkError = theMsgHandlerError; + + memcpy(aMsgHandler.mInitialMsg, theInitMsg, GS_UDP_MSG_HEADER_LEN); + memcpy(aMsgHandler.mHeader, theHeader, GS_UDP_MSG_HEADER_LEN); + + aMsgHandler.mPendingConnections = ArrayNew(sizeof(GSUdpRemotePeer*), 1, NULL); + if (aMsgHandler.mPendingConnections == NULL) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, + GSIDebugLevel_HotError, "[Udp Engine] No more memory!!!\n"); + return GS_UDP_NO_MEMORY; + } + aMsgHandler.mUserData = theUserData; + ArrayAppend(aUdp->mMsgHandlers, &aMsgHandler); + + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// When a message handler is done or shutting down, the message handler should +// remove itself from the UDP Layer The header cannot be empty +GSUdpErrorCode +gsUdpEngineRemoveMsgHandler(char theHeader[GS_UDP_MSG_HEADER_LEN]) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GSUdpMsgHandler aHandler; + int index; + + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theHeader); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return GS_UDP_NETWORK_ERROR; + } + + if (!theHeader || !theHeader[0]) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] invalid or empty header\n"); + return GS_UDP_PARAMETER_ERROR; + } + + memcpy(aHandler.mHeader, theHeader, GS_UDP_MSG_HEADER_LEN); + + index = + ArraySearch(aUdp->mMsgHandlers, &aHandler, gsUdpMsgHandlerCompare2, 0, 0); + if (index != NOT_FOUND) { + ArrayDeleteAt(aUdp->mMsgHandlers, index); + } + return GS_UDP_NO_ERROR; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// Checks to see if there any remaining message handlers. +// returns gsi_false if there are none. +gsi_bool gsUdpEngineNoMoreMsgHandlers() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return gsi_true; + } + + return ArrayLength(aUdp->mMsgHandlers) == 0 ? gsi_true : gsi_false; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// returns gsi_false if there is no app using the UDP Layer +gsi_bool gsUdpEngineNoApp() { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + + GS_ASSERT(aUdp->mInitialized); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return gsi_true; + } + + if (aUdp->mAppClosed || aUdp->mAppConnAttempt || aUdp->mAppConnected || + aUdp->mAppNetworkError || aUdp->mAppPingReply || aUdp->mAppRecvData || + aUdp->mAppUnknownMessage) { + return gsi_false; + } + + return gsi_true; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Layer must be initialized +// theIp and thePort cannot be 0 (Zero) +// Based on an IP and port, the function will return the amount of free space +// of a peer's buffer. +int gsUdpEngineGetPeerOutBufferFreeSpace(unsigned int theIp, + unsigned short thePort) { + GSUdpEngineObject* aUdp = gsUdpEngineGetEngine(); + GSUdpRemotePeer aRemotePeer, *aRemotePeerFound; + int index; + GS_ASSERT(aUdp->mInitialized); + GS_ASSERT(theIp); + GS_ASSERT(thePort); + if (!aUdp->mInitialized) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Network, GSIDebugLevel_Debug, + "[Udp Engine] Engine not initialized\n"); + return 0; + } + + aRemotePeer.mAddr = theIp; + aRemotePeer.mPort = thePort; + index = ArraySearch(aUdp->mRemotePeers, &aRemotePeer, gsUdpRemotePeerCompare, + 0, 0); + if (index != NOT_FOUND) { + aRemotePeerFound = (GSUdpRemotePeer*)ArrayNth(aUdp->mRemotePeers, index); + return gt2GetOutgoingBufferFreeSpace(aRemotePeerFound->mConnection); + } + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Creates a string address given an IP and port +// IP and port can be 0. +void gsUdpEngineAddrToString(unsigned int theIp, unsigned short thePort, + char addrstring[GS_IP_ADDR_AND_PORT]) { + gt2AddressToString(theIp, thePort, addrstring); +} diff --git a/source/gamespy/common/gsUdpEngine.h b/source/gamespy/common/gsUdpEngine.h new file mode 100644 index 000000000..efe3e7950 --- /dev/null +++ b/source/gamespy/common/gsUdpEngine.h @@ -0,0 +1,189 @@ +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +#pragma once + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// UDP Communication Engine +// +#include "../darray.h" +#include "../gt2/gt2.h" +#include "gsCommon.h" + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Constants + +// The UDP Engine should try the MSS (Maximum Segment Size = IP Packet size - IP +// Header) as a default size to keep the packets from being fragmented. +// Currently 1460 is the MSS for windows. Consoles may have a different size. +#define GS_UDP_DEFAULT_OUT_BUFFSIZE 1460 +#define GS_UDP_DEFAULT_IN_BUFFSIZE 1500 + +// a default size for address strings +#define GS_IP_ADDR_AND_PORT 22 + +// A fixed header len. unless we require a bigger size, it will stay this size. +// These need to be used for calculating the free space available on +// the outgoing buffers for an IP and Port which represent the peer. +#define GS_UDP_MSG_HEADER_LEN 16 +#define GS_UDP_RELIABLE_MSG_HEADER 7 + +// The following error codes will be given back to the higher level app +// or message handler. +typedef enum _GSUdpErrorCode { + GS_UDP_NO_ERROR, + GS_UDP_NO_MEMORY, + GS_UDP_REJECTED, + GS_UDP_NETWORK_ERROR, + GS_UDP_ADDRESS_ERROR, + GS_UDP_ADDRESS_ALREADY_IN_USE, + GS_UDP_TIMED_OUT, + GS_UDP_REMOTE_ERROR, + GS_UDP_SEND_FAILED, + GS_UDP_INVALID_MESSAGE, + GS_UDP_PARAMETER_ERROR, + GS_UDP_NOT_INITIALIZED, + GS_UDP_MSG_TOO_BIG, + GS_UDP_UNKNOWN_ERROR, + // Add new errors before here + GS_UDP_NUM_ERROR_CODES +} GSUdpErrorCode; + +// Used so that an app or message handler does +// not need to request to start talking to a peer twice +// Also lets higher level app or message handlers know about +// if a communication channel to a peer has been broken. +typedef enum _GSUdpPeerState { + GS_UDP_PEER_CONNECTING, + GS_UDP_PEER_CONNECTED, + GS_UDP_PEER_CLOSING, + GS_UDP_PEER_CLOSED, + // Add new connection state before here + GS_UDP_PEER_STATE_NUM +} GSUdpPeerState; + +// When a communication channel to a peer is closed +// the closed callback will let us know why it was closed +typedef enum _GSUdpCloseReason { + GS_UDP_CLOSED_LOCALLY, + GS_UDP_CLOSED_REMOTELY, + // errors: + GS_UDP_CLOSED_BY_COMM_ERROR, // An invalid message was received (it was either + // unexpected or wrongly formatted). + GS_UDP_CLOSED_BY_LOW_MEM, + // Add new reasons before here + GS_UDP_CLOSED_NUM +} GSUdpCloseReason; + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Callbacks + +// Errors to give higher layers feedback +typedef void (*gsUdpErrorCallback)(GSUdpErrorCode theCode, void* theUserData); + +// app Request attempt callback used to tell registered listeners about +// connection attempts +typedef void (*gsUdpAppConnectAttemptCallback)( + unsigned int theIp, unsigned short thePort, int theLatency, + unsigned char* theInitMsg, unsigned int theInitMsgLen, void* theUserData); + +// peer communication channel callback types +typedef void (*gsUdpConnClosedCallback)(unsigned int ip, unsigned short port, + GSUdpCloseReason reason, + void* theUserData); +typedef void (*gsUdpConnReceivedDataCallback)( + unsigned int ip, unsigned short port, unsigned char* message, + unsigned int messageLength, gsi_bool reliable, void* theUserData); +typedef void (*gsUdpConnConnectedCallback)(unsigned int ip, unsigned short port, + GSUdpErrorCode error, + gsi_bool rejected, + void* theUserData); +typedef void (*gsUdpConnPingCallback)(unsigned int ip, unsigned short port, + unsigned int latency, void* theUserData); + +// Messages that cannot be interpreted are passed on to the higher level app +typedef gsi_bool (*gsUdpUnknownMsgCallback)(unsigned int ip, + unsigned short port, + unsigned char* message, + unsigned int messageLength, + void* theUserData); + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Public Functionality +// Initialization and test functions +gsi_bool gsUdpEngineIsInitialized(); +GSUdpErrorCode gsUdpEngineInitialize( + unsigned short thePort, int theIncomingBufSize, int theOutgoingBufSize, + gsUdpErrorCallback theAppNetworkError, + gsUdpConnConnectedCallback theAppConnected, + gsUdpConnClosedCallback theAppClosed, gsUdpConnPingCallback theAppPing, + gsUdpConnReceivedDataCallback theAppReceive, + gsUdpUnknownMsgCallback theAppUnownMsg, + gsUdpAppConnectAttemptCallback theAppConnectAttempt, void* theAppUserData); +// update and shutdown +GSUdpErrorCode gsUdpEngineThink(); +GSUdpErrorCode gsUdpEngineShutdown(); + +// Connectivity functions +GSUdpErrorCode gsUdpEngineGetPeerState(unsigned int theIp, + unsigned short thePort, + GSUdpPeerState* thePeerState); +GSUdpErrorCode +gsUdpEngineStartTalkingToPeer(unsigned int theIp, unsigned short thePort, + char theInitMsg[GS_UDP_MSG_HEADER_LEN], + int timeOut); +GSUdpErrorCode gsUdpEngineAcceptPeer(unsigned int theIp, + unsigned short thePort); +GSUdpErrorCode gsUdpEngineRejectPeer(unsigned int theIp, + unsigned short thePort); + +// Sending functionality +// WARNING: Messages should not be greater than the outgoing buffer size minus +// the header and the 7 byte header for reliable messages (used for internal gt2 +// operations). Most UDP fragmentation occurs if messages are bigger than 1500 +// bytes. Also, some routers are known to drop those packets that are larger +// than 1500 bytes because of set MTU sizes. The recommended outgoing buffer +// size is the default (1460). So take that, and subtract 16 for message +// handler header and reliable message header (if sending data reliably). +// freeSpace = 1460 - 16 - 7 +GSUdpErrorCode gsUdpEngineSendMessage(unsigned int theIp, + unsigned short thePort, + char theHeader[GS_UDP_MSG_HEADER_LEN], + unsigned char* theMsg, + unsigned int theMsgLen, + gsi_bool theReliable); + +// This function should be called for those parts of the code that want specific +// handling of messages Any call to send should include the header registered +// here. +GSUdpErrorCode +gsUdpEngineAddMsgHandler(char theInitMsg[GS_UDP_MSG_HEADER_LEN], + char theHeader[GS_UDP_MSG_HEADER_LEN], + gsUdpErrorCallback theMsgHandlerError, + gsUdpConnConnectedCallback theMsgHandlerConnected, + gsUdpConnClosedCallback theMsgHandlerClosed, + gsUdpConnPingCallback theMsgHandlerPing, + gsUdpConnReceivedDataCallback theMsgHandlerRecv, + void* theUserData); +GSUdpErrorCode +gsUdpEngineRemoveMsgHandler(char theHeader[GS_UDP_MSG_HEADER_LEN]); +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +// Public Utility functionality +SOCKET gsUdpEngineGetSocket(); +void gsUdpEngineAddrToString(unsigned int theIp, unsigned short thePort, + char addrstring[GS_IP_ADDR_AND_PORT]); +unsigned int gsUdpEngineGetLocalAddr(); +unsigned short gsUdpEngineGetLocalPort(); + +// lets the app or message handler know if it is able to shutdown the udp layer +gsi_bool gsUdpEngineNoMoreMsgHandlers(); +gsi_bool gsUdpEngineNoApp(); + +// check the remaining free space on the outgoing buffer for the peer based +// IP and port +int gsUdpEngineGetPeerOutBufferFreeSpace(unsigned int theIp, + unsigned short thePort); diff --git a/source/gamespy/common/gsXML.c b/source/gamespy/common/gsXML.c new file mode 100644 index 000000000..13223d584 --- /dev/null +++ b/source/gamespy/common/gsXML.c @@ -0,0 +1,1861 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "gsXML.h" +#include "../darray.h" +#include "gsMemory.h" +#include "gsPlatform.h" +#include "gsStringUtil.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GS_XML_INITIAL_ELEMENT_ARRAY_COUNT 32 +#define GS_XML_INITIAL_ATTRIBUTE_ARRAY_COUNT 16 + +#define GS_XML_WHITESPACE "\x20\x09\x0D\x0A" + +#define GS_XML_CHECK(a) \ + { \ + if (gsi_is_false(a)) \ + return gsi_false; \ + } + +#define GS_XML_BASE64_ENCODING_TYPE 0 + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GS_XML_SOAP_BUFFER_INITIAL_SIZE (1 * 1024) +#define GS_XML_SOAP_BUFFER_INCREMENT_SIZE (1 * 1024) +#define GS_XML_SOAP_INITIAL_NAMESPACE_COUNT 6 + +#define GS_XML_SOAP_HEADER \ + "" +#define GS_XML_SOAP_FOOTER "" +#define GS_XML_SOAP_NAMESPACE_PREFIX "xmlns:" + +#define GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT 4 +const char* + GS_XML_SOAP_DEFAULT_NAMESPACES[GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT] = { + "SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"", + "SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\"", + "xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", + "xsd=\"http://www.w3.org/2001/XMLSchema\""}; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Warning: Do not store pointers to other GSXml* objects within a GSXml object +// Pointers to elements may be invalidate if the DArray's grow +// Instead, store the array index and require that indexes never change +typedef struct GSIXmlString { + const gsi_u8* mData; + int mLen; +} GSIXmlString; + +typedef struct GSIXmlAttribute { + GSIXmlString mName; + GSIXmlString mValue; + + int mIndex; + int mParentIndex; +} GSIXmlAttribute; + +typedef struct GSIXmlElement { + GSIXmlString mName; + GSIXmlString mValue; // most do not have a value + + int mIndex; + int mParentIndex; +} GSIXmlElement; + +typedef struct GSIXmlStreamReader { + DArray mElementArray; + DArray mAttributeArray; + + int mElemReadIndex; // current index + int mValueReadIndex; // current child parsing index +} GSIXmlStreamReader; + +typedef struct GSIXmlStreamWriter { + char* mBuffer; + int mLen; + int mCapacity; + gsi_bool mClosed; // footer has been written, append not allowed +} GSIXmlStreamWriter; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilSkipWhiteSpace(GSIXmlStreamReader* stream, + const char* buffer, int len, int* pos); +static gsi_bool gsiXmlUtilParseName(GSIXmlStreamReader* stream, + const char* buffer, int len, int* pos, + GSIXmlString* strOut); +static gsi_bool gsiXmlUtilParseString(GSIXmlStreamReader* stream, char* buffer, + int len, int* pos, GSIXmlString* strOut); +static gsi_bool gsiXmlUtilParseValue(GSIXmlStreamReader* stream, char* buffer, + int len, int* pos, GSIXmlString* strOut); +static gsi_bool gsiXmlUtilParseElement(GSIXmlStreamReader* stream, char* buffer, + int len, int* pos, int parentIndex); +gsi_bool gsiXmlUtilTagMatches(const char* matchtag, GSIXmlString* xmlstr); + +// Note: Writes decoded form back into buffer +static gsi_bool gsiXmlUtilDecodeString(char* buffer, int* len); + +static void gsiXmlUtilElementFree(void* elem); +static void gsiXmlUtilAttributeFree(void* elem); + +static gsi_bool gsiXmlUtilWriteChar(GSIXmlStreamWriter* stream, char ch); +static gsi_bool gsiXmlUtilWriteString(GSIXmlStreamWriter* stream, + const char* str); +gsi_bool gsiXmlUtilWriteXmlSafeString(GSIXmlStreamWriter* stream, + const char* str); +static gsi_bool gsiXmlUtilGrowBuffer(GSIXmlStreamWriter* stream); +static gsi_bool gsiXmlUtilWriteUnicodeString(GSIXmlStreamWriter* stream, + const unsigned short* str); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static int gsUnicodeStringLen(const unsigned short* str) { + const unsigned short* end = str; + while (*end++) { + } + return (end - str - 1); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSXmlStreamWriter gsXmlCreateStreamWriter(const char** namespaces, int count) { + GSIXmlStreamWriter* newStream = NULL; + int initialCapacity = GS_XML_SOAP_BUFFER_INCREMENT_SIZE; + int namespaceLen = 0; + int i = 0; + + GS_ASSERT((namespaces == NULL && count == 0) || + (namespaces != NULL && count != 0)); + + newStream = (GSIXmlStreamWriter*)gsimalloc(sizeof(GSIXmlStreamWriter)); + if (newStream == NULL) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, + GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStreamWriter, needed %d bytes", + sizeof(GSIXmlStreamWriter)); + return NULL; // OOM + } + + // do this to prevent an immediately reallocation due to long namespace string + for (i = 0; i < GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT; i++) { + GS_ASSERT(GS_XML_SOAP_DEFAULT_NAMESPACES[i] != NULL); + namespaceLen += strlen(GS_XML_SOAP_NAMESPACE_PREFIX) + 1; // +1 for space + namespaceLen += strlen(GS_XML_SOAP_DEFAULT_NAMESPACES[i]); + } + for (i = 0; i < count; i++) { + GS_ASSERT(namespaces[i] != NULL); + namespaceLen += strlen(GS_XML_SOAP_NAMESPACE_PREFIX) + 1; // +1 for space + namespaceLen += strlen(namespaces[i]); + } + while (initialCapacity < namespaceLen) + initialCapacity += GS_XML_SOAP_BUFFER_INCREMENT_SIZE; + + // allocate write buffer + newStream->mBuffer = (char*)gsimalloc((size_t)initialCapacity); + if (newStream->mBuffer == NULL) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, + GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStreamWriter, needed %d bytes", + initialCapacity); + return NULL; // OOM + } + newStream->mCapacity = initialCapacity; + newStream->mLen = 0; + newStream->mBuffer[0] = '\0'; + newStream->mClosed = gsi_false; + + // write the XML header + if (gsi_is_false(gsiXmlUtilWriteString(newStream, GS_XML_SOAP_HEADER))) { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + for (i = 0; i < GS_XML_SOAP_DEFAULT_NAMESPACE_COUNT; i++) { + if (gsi_is_false(gsiXmlUtilWriteChar(newStream, ' ')) || + gsi_is_false( + gsiXmlUtilWriteString(newStream, GS_XML_SOAP_NAMESPACE_PREFIX)) || + gsi_is_false(gsiXmlUtilWriteString( + newStream, GS_XML_SOAP_DEFAULT_NAMESPACES[i]))) { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + } + for (i = 0; i < count; i++) { + if (gsi_is_false(gsiXmlUtilWriteChar(newStream, ' ')) || + gsi_is_false( + gsiXmlUtilWriteString(newStream, GS_XML_SOAP_NAMESPACE_PREFIX)) || + gsi_is_false(gsiXmlUtilWriteString(newStream, namespaces[i]))) { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + } + if (gsi_is_false(gsiXmlUtilWriteChar(newStream, '>')) || + gsi_is_false(gsiXmlUtilWriteString(newStream, GS_XML_SOAP_BODY_TAG))) { + gsifree(newStream->mBuffer); + gsifree(newStream); + return NULL; // OOM + } + + return (GSXmlStreamWriter)newStream; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GSXmlStreamReader gsXmlCreateStreamReader() { + GSIXmlStreamReader* newStream = NULL; + + newStream = (GSIXmlStreamReader*)gsimalloc(sizeof(GSIXmlStreamReader)); + if (newStream == NULL) { + gsDebugFormat(GSIDebugCat_Common, GSIDebugType_Memory, + GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStream, needed %d bytes", + sizeof(GSIXmlStreamReader)); + return NULL; // OOM + } + + newStream->mElementArray = + ArrayNew(sizeof(GSIXmlElement), GS_XML_INITIAL_ELEMENT_ARRAY_COUNT, + gsiXmlUtilElementFree); + if (newStream->mElementArray == NULL) { + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStream with mElementArray=ArrayNew()"); + gsifree(newStream); + return NULL; // OOM + } + + newStream->mAttributeArray = + ArrayNew(sizeof(GSIXmlAttribute), GS_XML_INITIAL_ATTRIBUTE_ARRAY_COUNT, + gsiXmlUtilAttributeFree); + if (newStream->mAttributeArray == NULL) { + gsDebugFormat( + GSIDebugCat_Common, GSIDebugType_Memory, GSIDebugLevel_HotError, + "Out of memory in gsXmlCreateStream with mElementArray=ArrayNew()"); + ArrayFree(newStream->mElementArray); + gsifree(newStream); + return NULL; // OOM + } + + gsXmlMoveToStart(newStream); + return (GSXmlStreamReader)newStream; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlParseBuffer(GSXmlStreamReader stream, char* data, int len) { + GSIXmlStreamReader* reader; + int readPos = 0; + + GS_ASSERT(data != NULL); + GS_ASSERT(len > 0); + + reader = (GSIXmlStreamReader*)stream; + + // gsXmlResetReader(stream); + + // Parse the root elements (automatically includes sub-elements) + while (readPos < len) { + if (gsi_is_false(gsiXmlUtilParseElement(reader, data, len, &readPos, -1))) + return gsi_false; + } + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsXmlFreeWriter(GSXmlStreamWriter stream) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + gsifree(writer->mBuffer); + gsifree(writer); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsXmlFreeReader(GSXmlStreamReader stream) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + ArrayFree(reader->mAttributeArray); + ArrayFree(reader->mElementArray); + gsifree(reader); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsXmlResetReader(GSXmlStreamReader stream) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + ArrayClear(reader->mAttributeArray); + ArrayClear(reader->mElementArray); + gsXmlMoveToStart(stream); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlCloseWriter(GSXmlStreamWriter stream) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + GS_ASSERT(stream != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsiXmlUtilWriteString(writer, GS_XML_SOAP_FOOTER))) + return gsi_false; + + writer->mClosed = gsi_true; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void gsiXmlUtilElementFree(void* elem) { + GSI_UNUSED(elem); + // GSXmlElement * dataPtr = (GSXmlElement*)elem; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static void gsiXmlUtilAttributeFree(void* elem) { + GSI_UNUSED(elem); + // GSXmlAttribute * dataPtr = (GSXmlAttribute*)elem; + // gsifree(dataPtr); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +const char* gsXmlWriterGetData(GSXmlStreamWriter stream) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + GS_ASSERT(stream != NULL) + return writer->mBuffer; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsXmlWriterGetDataLength(GSXmlStreamWriter stream) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + GS_ASSERT(stream != NULL); + return writer->mLen; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilSkipWhiteSpace(GSIXmlStreamReader* stream, + const char* buffer, int len, + int* pos) { + GS_ASSERT(buffer != NULL); + GS_ASSERT(len > 0); + // GS_ASSERT(*pos < len); + + // check if the next character is in the whitespace set + while (*pos < len) { + if (NULL == strchr(GS_XML_WHITESPACE, buffer[*pos])) + return gsi_true; + (*pos)++; // move to next character + } + + GSI_UNUSED(stream); + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilParseElement(GSIXmlStreamReader* stream, char* buffer, + int len, int* pos, int parentIndex) { + GSIXmlElement newElem; + int startPos = 0; + gsi_bool storeElement = gsi_true; + + GS_ASSERT(stream != NULL); + GS_ASSERT(buffer != NULL); + GS_ASSERT(len > 0); + GS_ASSERT(*pos < len); + + gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos); + if (*pos >= len) + return gsi_true; // legal EOF + + memset(&newElem, 0, sizeof(newElem)); + + // elements must begin with '<' + if (buffer[*pos] != '<') + return gsi_false; + (*pos)++; + if (*pos >= len) + return gsi_false; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // '<' can be followed with '!', '?', '%', '/' special characters + if (buffer[*pos] == '!' || buffer[*pos] == '?' || buffer[*pos] == '%' || + buffer[*pos] == '/') { + storeElement = gsi_false; + (*pos)++; + startPos = (*pos) - 2; + } else + startPos = (*pos) - 1; // store the position of '<' + + // should be a name (type) next + GS_XML_CHECK(gsiXmlUtilParseName(stream, buffer, len, pos, &newElem.mName)); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + if (storeElement) { + GS_ASSERT(newElem.mName.mData != NULL); + newElem.mIndex = ArrayLength(stream->mElementArray); + newElem.mParentIndex = parentIndex; + ArrayAppend(stream->mElementArray, &newElem); + } + + // read attributes (if any) + while (*pos < len && isalnum(buffer[*pos])) { + // attribute format is name="", e.g. nickname="player1" + GSIXmlAttribute newAttr; + memset(&newAttr, 0, sizeof(newAttr)); + GS_XML_CHECK(gsiXmlUtilParseName(stream, buffer, len, pos, &newAttr.mName)); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] != '=') + return gsi_false; + (*pos)++; // skip the '=' + GS_XML_CHECK( + gsiXmlUtilParseString(stream, buffer, len, pos, &newAttr.mValue)); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // Store it in the array + if (storeElement) { + GS_ASSERT(newElem.mName.mData != NULL); + GS_ASSERT(newElem.mIndex != -1); + GS_ASSERT(newAttr.mName.mData != NULL); + GS_ASSERT(newAttr.mValue.mData != NULL); + + newAttr.mIndex = ArrayLength(stream->mAttributeArray); + newAttr.mParentIndex = newElem.mIndex; + ArrayAppend(stream->mAttributeArray, &newAttr); + } + } + + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // Check for immediate termination (no value or children) + // non-element tags end with the same character they start with + // element tags ending with '/>' also have no children + if ((!isalnum(buffer[startPos + 1]) && + buffer[*pos] == buffer[startPos + 1]) || + buffer[*pos] == '/') { + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] != '>') + return gsi_false; // only legal character here is closing brace + (*pos)++; + return gsi_true; // legal termination + } + + // make sure we've found the end of the start tag + if (buffer[*pos] != '>') + return gsi_false; + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // check for element value + if (buffer[*pos] != '<') { + GS_XML_CHECK( + gsiXmlUtilParseValue(stream, buffer, len, pos, &newElem.mValue)); + // update the array with the value information + if (storeElement) + ArrayReplaceAt(stream->mElementArray, &newElem, newElem.mIndex); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + } + + // read child elements and close tag + while (*pos < len) { + int childStartPos = *pos; + if (buffer[*pos] != '<') + return gsi_false; + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] == '/') { + // this MUST be a close of the current element + // close tags are in the form: + (*pos)++; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if ((*pos) + newElem.mName.mLen >= len) + return gsi_false; // EOF before tag close + if (0 != strncmp((const char*)newElem.mName.mData, &buffer[*pos], + (size_t)newElem.mName.mLen)) + return gsi_false; // close tag mismatch + (*pos) += newElem.mName.mLen; + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + if (buffer[*pos] != '>') + return gsi_false; + (*pos)++; + return gsi_true; + } else { + if (newElem.mValue.mData != NULL) + return gsi_false; // elements with value cannot have children + + // move read position to start of child element, then parse + *pos = childStartPos; + GS_XML_CHECK( + gsiXmlUtilParseElement(stream, buffer, len, pos, newElem.mIndex)); + gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos); + } + } + return gsi_false; // EOF before tag close +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Parse element name +// Must begin with a letter and contains only alphanumeric | '_' | '-' +// characters Element names may consist of two "names", +// : +static gsi_bool gsiXmlUtilParseName(GSIXmlStreamReader* stream, + const char* buffer, int len, int* pos, + GSIXmlString* strOut) { + gsi_bool haveNamespace = gsi_false; + GS_ASSERT(buffer != NULL); + GS_ASSERT(len > 0); + + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // EOF? + if (*pos >= len) + return gsi_false; + + // first character must be alphanumeric but not a digit + if (!isalnum(buffer[*pos]) || isdigit(buffer[*pos])) + return gsi_false; + + strOut->mData = (gsi_u8*)&buffer[*pos]; + strOut->mLen = 1; + (*pos)++; + + while (*pos < len && NULL == strchr(GS_XML_WHITESPACE, buffer[*pos])) { + // only alpha numeric and '_' characters are allowed, plus one namespace + // separator ':' + if (buffer[*pos] == ':') { + if (gsi_is_true(haveNamespace)) + return gsi_false; // already have a namespace! + haveNamespace = gsi_true; + } else if ((buffer[*pos] != '_') && (buffer[*pos] != '-') && + (!isalnum(buffer[*pos]))) + return gsi_true; // treat all others as a new token example '=' + + strOut->mLen++; + (*pos)++; + } + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilParseString(GSIXmlStreamReader* stream, char* buffer, + int len, int* pos, GSIXmlString* strOut) { + char startCh = '\0'; + char* strStart = NULL; + // gsi_bool hasMarkup = gsi_false; + + GS_ASSERT(stream != NULL); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + // strings may start with either ' or " + startCh = buffer[*pos]; + if (startCh != '\"' && startCh != '\'') + return gsi_false; + + (*pos)++; + strStart = &buffer[*pos]; // remember this for easier processing below + strOut->mLen = 0; + + if (*pos >= len) + return gsi_false; // EOF when looking for string terminator + if (buffer[*pos] == startCh) { + // empty string ? + strOut->mData = (const gsi_u8*)strStart; + (*pos)++; // skip the terminating character + return gsi_true; + } + + while (buffer[*pos] != startCh) { + if (*pos >= len) + return gsi_false; // EOF when looking for string terminator + + // if (buffer[*pos] == '&') + // hasMarkup = gsi_true; + + (*pos)++; + strOut->mLen++; + } + (*pos)++; // skip the terminating character + + // decode the string if necessary + if (gsi_is_false(gsiXmlUtilDecodeString(strStart, &strOut->mLen))) + return gsi_false; + + // set the data into strOut + strOut->mData = (const gsi_u8*)strStart; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Remove '&' markup from the buffer, converts in place +static gsi_bool gsiXmlUtilDecodeString(char* buffer, int* len) { + int readPos = 0; + + while (readPos < *len) { + int charsToRemove = 0; + + // we only want '&' + if (buffer[readPos] != '&') { + readPos++; + continue; + } + + // check single character switches + if (strncmp(&buffer[readPos], "&", 5) == 0) { + buffer[readPos++] = '&'; + charsToRemove = 5 - 1; + } else if (strncmp(&buffer[readPos], """, 6) == 0) { + buffer[readPos++] = '\"'; + charsToRemove = 6 - 1; + } else if (strncmp(&buffer[readPos], "'", 6) == 0) { + buffer[readPos++] = '\''; + charsToRemove = 6 - 1; + } else if (strncmp(&buffer[readPos], "<", 4) == 0) { + buffer[readPos++] = '<'; + charsToRemove = 4 - 1; + } else if (strncmp(&buffer[readPos], ">", 4) == 0) { + buffer[readPos++] = '>'; + charsToRemove = 4 - 1; + } else if (strncmp(&buffer[readPos], "&#x", 3) == 0) { + // hex digit + unsigned int digitValue = 0; + // unsigned int digitLength = 0; // 0x00000065 = 1 byte + char ch = ' '; + gsi_bool haveWritten = gsi_false; + int i = 0; + unsigned int mask = 0xFF000000; + + char* digitEnd = strchr(&buffer[readPos + 3], ';'); + if (digitEnd == NULL) + return gsi_false; // missing ';' + if (digitEnd - &buffer[readPos + 3] > 8) + return gsi_false; // too many digits before end + + // scan digits into memory, do this as a block so that ť = 01 65 + sscanf(&buffer[readPos + 3], "%08x", &digitValue); + + // write the digit back as a character array + for (i = 0; i < 4; i++) { + ch = (char)((digitValue & mask) >> + ((3 - i) * 8)); // make 0x00006500 into 0x65 + if (haveWritten || ch != 0x00) { + buffer[readPos++] = ch; + haveWritten = gsi_true; + } + mask = mask >> 8; + } + + // remove everything between the current read position and the semicolon + charsToRemove = digitEnd - &buffer[readPos] + 1; // remove the semicolon + } else if (strncmp(&buffer[readPos], "&#", 2) == 0) { + // dec digit - like a hex digit, only use atoi instead of sscanf + unsigned int digitValue = 0; + // unsigned int digitLength = 0; // 0x00000065 = 1 byte + char ch = ' '; + gsi_bool haveWritten = gsi_false; + int i = 0; + unsigned int mask = 0xFF000000; + + char* digitEnd = strchr(&buffer[readPos + 2], ';'); + if (digitEnd == NULL) + return gsi_false; // missing ';' + + // scan digits into memory, do this as a block so that ť = 0165h = 01 + // 65 + digitValue = (unsigned int)atoi(&buffer[readPos + 2]); + + // write the digit back as a character array + for (i = 0; i < 4; i++) { + ch = (char)((digitValue & mask) >> + ((3 - i) * 8)); // make 0x00006500 into 0x65 + if (haveWritten || ch != 0x00) { + buffer[readPos++] = ch; + haveWritten = gsi_true; + } + mask = mask >> 8; + } + + // remove everything between the current read position and the semicolon + charsToRemove = digitEnd - &buffer[readPos] + 1; // remove the semicolon + + } else + return gsi_false; // unhandle '&' type + + // remove characters by compressing buffer and adding whitespace at the end + // "&&" becomes "&& " after one iteration + memmove(&buffer[readPos], &buffer[readPos + charsToRemove], + (size_t)(*len - (readPos + charsToRemove))); + memset(&buffer[*len - charsToRemove], ' ', (size_t)charsToRemove); + (*len) -= charsToRemove; + } + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Parse an entity value. +// Todo: resolving escaped strings? +static gsi_bool gsiXmlUtilParseValue(GSIXmlStreamReader* stream, char* buffer, + int len, int* pos, GSIXmlString* strOut) { + char* strStart = NULL; + + GS_ASSERT(stream != NULL); + GS_XML_CHECK(gsiXmlUtilSkipWhiteSpace(stream, buffer, len, pos)); + + if (buffer[*pos] != '<') { + strStart = &buffer[*pos]; // store this so we can find it later + strOut->mData = (const gsi_u8*)strStart; + } + + while (*pos < len) { + if (buffer[*pos] == '<') { + // decode the string if necessary + if (gsi_is_false(gsiXmlUtilDecodeString(strStart, &strOut->mLen))) + return gsi_false; + return gsi_true; // extracted and decoded + } + (*pos)++; + strOut->mLen++; + } + return gsi_false; // EOF before tag end +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteOpenTag(GSXmlStreamWriter stream, const char* namespaceName, + const char* tag) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsiXmlUtilWriteChar(writer, '<')) || + gsi_is_false(gsiXmlUtilWriteString(writer, namespaceName)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, ':')) || + gsi_is_false(gsiXmlUtilWriteString(writer, tag)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, '>'))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteCloseTag(GSXmlStreamWriter stream, const char* namespaceName, + const char* tag) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsiXmlUtilWriteChar(writer, '<')) || + gsi_is_false(gsiXmlUtilWriteChar(writer, '/')) || + gsi_is_false(gsiXmlUtilWriteString(writer, namespaceName)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, ':')) || + gsi_is_false(gsiXmlUtilWriteString(writer, tag)) || + gsi_is_false(gsiXmlUtilWriteChar(writer, '>'))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteStringElement(GSXmlStreamWriter stream, + const char* namespaceName, const char* tag, + const char* value) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + int i = 0; + int len = 0; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(value != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + // Check legal ASCII characters + // 0x9, 0xA, 0xD + // [0x20-0xFF] + len = (int)strlen(value); + for (i = 0; i < len; i++) { + // only check values less than 0x20 + if ((unsigned char)value[i] < 0x20) { + if ((unsigned char)value[i] != 0x09 && + ((unsigned char)value[i] != 0x0A) && + ((unsigned char)value[i] != 0x0D)) { + // contains illegal (and unencodable) characters. + return gsi_false; + } + } + } + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteXmlSafeString(writer, value)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Converts unicode to ascii strings for writing to XML writer +gsi_bool gsXmlWriteAsciiStringElement(GSXmlStreamWriter stream, + const char* namespaceName, + const char* tag, const gsi_char* value) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + int i = 0; + int len = 0; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(value != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + // Check legal ASCII characters + // 0x9, 0xA, 0xD + // [0x20-0xFF] + // len = (int)_tcslen(value); + len = (int)strlen(value); + for (i = 0; i < len; i++) { + // only check values less than 0x20 + if ((unsigned char)value[i] < 0x20) { + if ((unsigned char)value[i] != 0x09 && + ((unsigned char)value[i] != 0x0A) && + ((unsigned char)value[i] != 0x0D)) { + // contains illegal (and unencodable) characters. + return gsi_false; + } + } + } + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteXmlSafeString(writer, value)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteUnicodeStringElement(GSXmlStreamWriter stream, + const char* namespaceName, + const char* tag, + const unsigned short* value) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + int i = 0; + int len = 0; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(value != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + // Check legal UNICODE characters + // 0x9, 0xA, 0xD + // [0x20-0xD7FF] + // [xE000-xFFFD] + // [x10000-x10FFFF] (UTF-16, not supported) + len = gsUnicodeStringLen(value); + for (i = 0; i < len; i++) { + // check values less than 0x20 + if (value[i] < 0x0020) { + if ((value[i] != 0x0009) && (value[i] != 0x0A) && (value[i] != 0x0D)) + return gsi_false; // contains illegal (and unencodable) characters. + } else if (value[i] > 0xD7FF && value[i] < 0xE000) + return gsi_false; // contains illegal (and unencodable) characters. + else if (value[i] > 0xFFFD) + return gsi_false; // contains illegal (and unencodable) characters. + } + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteUnicodeString(writer, value)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteIntElement(GSXmlStreamWriter stream, + const char* namespaceName, const char* tag, + gsi_u32 value) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + char buf[32]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + sprintf(buf, "%d", value); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, buf)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteInt64Element(GSXmlStreamWriter stream, + const char* namespaceName, const char* tag, + gsi_i64 value) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + char buf[33]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + gsiInt64ToString(buf, value); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, buf)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteFloatElement(GSXmlStreamWriter stream, + const char* namespaceName, const char* tag, + float value) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + char buf[32]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + sprintf(buf, "%f", value); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, buf)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// first character of HEX binary is HIGH byte +gsi_bool gsXmlWriteHexBinaryElement(GSXmlStreamWriter stream, + const char* namespaceName, const char* tag, + const gsi_u8* data, int len) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + int pos = 0; + int temp = 0; + + char hex[3]; + hex[2] = '\0'; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(data != NULL); + GS_ASSERT(len > 0); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag))) + return gsi_false; + + while (pos < len) { + temp = data[pos]; // sprintf requires an int parameter for %02x operation + sprintf(hex, "%02x", temp); + pos++; + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[0]))) + return gsi_false; + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[1]))) + return gsi_false; + } + + if (gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) + return gsi_false; + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteBase64BinaryElement(GSXmlStreamWriter stream, + const char* namespaceName, + const char* tag, const gsi_u8* data, + int len) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + B64StreamData streamData; + char b64[5]; + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(data != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag))) + return gsi_false; + + B64InitEncodeStream(&streamData, (const char*)data, len, + GS_XML_BASE64_ENCODING_TYPE); + while (B64EncodeStream(&streamData, b64)) { + b64[4] = '\0'; + if (gsi_is_false(gsiXmlUtilWriteString(writer, b64))) + return gsi_false; + } + + if (gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) + return gsi_false; + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlWriteDateTimeElement(GSXmlStreamWriter stream, + const char* namespaceName, const char* tag, + time_t value) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + char timeString[21]; + struct tm* timePtr; + + // convert the time to a string + timePtr = gsiSecondsToDate(&value); + + sprintf(timeString, "%d-%02d-%02dT%02d:%02d:%02dZ", timePtr->tm_year + 1900, + timePtr->tm_mon + 1, timePtr->tm_mday, timePtr->tm_hour, + timePtr->tm_min, timePtr->tm_sec); + + GS_ASSERT(stream != NULL); + GS_ASSERT(namespaceName != NULL); + GS_ASSERT(tag != NULL); + GS_ASSERT(gsi_is_false(writer->mClosed)); + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag)) || + gsi_is_false(gsiXmlUtilWriteString(writer, timeString)) || + gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) { + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Must reverse byte order and strip leading zeroes +gsi_bool gsXmlWriteLargeIntElement(GSXmlStreamWriter stream, + const char* namespaceName, const char* tag, + const struct gsLargeInt_s* lint) { + GSIXmlStreamWriter* writer = (GSIXmlStreamWriter*)stream; + int readPos = (int)(lint->mLength - 1); + const l_word* readBuf = (const l_word*)lint->mData; + unsigned int temp; + int i; + char hex[3]; + gsi_bool first = gsi_true; + + if (gsi_is_false(gsXmlWriteOpenTag(stream, namespaceName, tag))) + return gsi_false; + + // skip leading zeroes + while (readPos >= 0 && readBuf[readPos] == 0) + readPos--; + + // dump words + for (; readPos >= 0; readPos--) { + if (gsi_is_true(first)) { + for (i = 0; i < GS_LARGEINT_DIGIT_SIZE_BYTES; i++) { + temp = ((lint->mData[readPos] >> + (8 * (GS_LARGEINT_DIGIT_SIZE_BYTES - i - 1))) & + 0xFF); + if (temp != 0) + break; + } + first = gsi_false; + } else { + i = 0; + } + + // loop through each byte from most to least significant + for (; i < GS_LARGEINT_DIGIT_SIZE_BYTES; i++) { + temp = ((lint->mData[readPos] >> + (8 * (GS_LARGEINT_DIGIT_SIZE_BYTES - i - 1))) & + 0xFF); + sprintf(hex, "%02x", temp); + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[0]))) + return gsi_false; + if (gsi_is_false(gsiXmlUtilWriteChar(writer, hex[1]))) + return gsi_false; + } + } + + if (gsi_is_false(gsXmlWriteCloseTag(stream, namespaceName, tag))) + return gsi_false; + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilWriteChar(GSIXmlStreamWriter* stream, char ch) { + GS_ASSERT(gsi_is_false(stream->mClosed)); + + if (stream->mLen >= stream->mCapacity) { + if (gsi_is_false(gsiXmlUtilGrowBuffer(stream))) + return gsi_false; // OOM + } + stream->mBuffer[stream->mLen] = ch; + stream->mLen++; + + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilWriteString(GSIXmlStreamWriter* stream, + const char* str) { + int strLen = 0; + + GS_ASSERT(str != NULL); + GS_ASSERT(gsi_is_false(stream->mClosed)); + + // get URL encoded length + strLen = (int)strlen(str); + if (strLen == 0) + return gsi_true; + + // grow the buffer if necessary + while ((stream->mCapacity - stream->mLen) <= strLen) { + if (gsi_is_false(gsiXmlUtilGrowBuffer(stream))) + return gsi_false; // OOM + } + + strcpy(&stream->mBuffer[stream->mLen], str); + stream->mLen += strLen; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilWriteUnicodeString(GSIXmlStreamWriter* stream, + const unsigned short* str) { + int strLen = 0; + int pos = 0; + int utf8Len = 0; + char utf8String[4] = {'\0'}; + // gsi_bool result = gsi_false; + + GS_ASSERT(str != NULL); + GS_ASSERT(gsi_is_false(stream->mClosed)); + + strLen = gsUnicodeStringLen(str); + utf8String[3] = '\0'; + + for (pos = 0; pos < strLen; pos++) { + utf8Len = _UCS2CharToUTF8String(str[pos], utf8String); + utf8String[utf8Len] = '\0'; // null terminate it + if (gsi_is_false(gsiXmlUtilWriteXmlSafeString(stream, utf8String))) + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsiXmlUtilWriteXmlSafeString(GSIXmlStreamWriter* stream, + const char* str) { + int strLen = 0; + int pos = 0; + gsi_bool result = gsi_false; + + GS_ASSERT(str != NULL); + GS_ASSERT(gsi_is_false(stream->mClosed)); + + strLen = (int)strlen(str); + + for (pos = 0; pos < strLen; pos++) { + if (str[pos] == '&') + result = gsiXmlUtilWriteString(stream, "&"); + else if (str[pos] == '\'') + result = gsiXmlUtilWriteString(stream, "'"); + else if (str[pos] == '"') + result = gsiXmlUtilWriteString(stream, """); + else if (str[pos] == '<') + result = gsiXmlUtilWriteString(stream, "<"); + else if (str[pos] == '>') + result = gsiXmlUtilWriteString(stream, ">"); + else if (str[pos] == ' ') + result = gsiXmlUtilWriteString(stream, " "); + else if (str[pos] < 0x20 || ((unsigned char)str[pos]) > 0x7F) { + // write as hex + char numeric[7]; + sprintf(numeric, "&#x%02x;", (unsigned char)str[pos]); + numeric[6] = '\0'; + result = gsiXmlUtilWriteString(stream, numeric); + } else + result = gsiXmlUtilWriteChar(stream, str[pos]); + + if (gsi_is_false(result)) + return gsi_false; + } + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool gsiXmlUtilGrowBuffer(GSIXmlStreamWriter* stream) { + int newCapacity = stream->mCapacity + GS_XML_SOAP_BUFFER_INCREMENT_SIZE; + void* newBuf = NULL; + + newBuf = gsirealloc(stream->mBuffer, (size_t)newCapacity); + if (newBuf == NULL) + return gsi_false; + if (newBuf != stream->mBuffer) + stream->mBuffer = (char*)newBuf; + stream->mCapacity = newCapacity; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlMoveToStart(GSXmlStreamReader stream) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + reader->mElemReadIndex = -1; // start BEFORE first element + reader->mValueReadIndex = -1; + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Move to next occurance of "matchtag" at any level +gsi_bool gsXmlMoveToNext(GSXmlStreamReader stream, const char* matchtag) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + int i = 0; + + for (i = (reader->mElemReadIndex + 1); i < ArrayLength(reader->mElementArray); + i++) { + GSIXmlElement* elem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &elem->mName))) { + reader->mElemReadIndex = i; + reader->mValueReadIndex = -1; + return gsi_true; + } + } + // no matching element found + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Move up one level in tree +gsi_bool gsXmlMoveToParent(GSXmlStreamReader stream) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + + // check for invalid position + if (reader->mElemReadIndex >= ArrayLength(reader->mElementArray) || + reader->mElemReadIndex == -1) { + return gsi_false; // current position invalid + } else { + GSIXmlElement* elem = + (GSIXmlElement*)ArrayNth(reader->mElementArray, reader->mElemReadIndex); + if (elem->mParentIndex == -1) + return gsi_false; // current elem is at highest level + if (elem->mParentIndex >= ArrayLength(reader->mElementArray)) + return gsi_false; // parent is invalid! + reader->mElemReadIndex = elem->mParentIndex; + reader->mValueReadIndex = -1; + return gsi_true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Move to next unit who shares common parent +gsi_bool gsXmlMoveToSibling(GSXmlStreamReader stream, const char* matchtag) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + int i = 0; + + int curElemParent = -1; + GSIXmlElement* searchElem = NULL; + + // If the current element is valid use its parent id + if (reader->mElemReadIndex < ArrayLength(reader->mElementArray)) { + GSIXmlElement* curElem = + (GSIXmlElement*)ArrayNth(reader->mElementArray, reader->mElemReadIndex); + curElemParent = curElem->mParentIndex; + } else + // otherwise search root elements only + curElemParent = -1; + + for (i = (reader->mElemReadIndex + 1); i < ArrayLength(reader->mElementArray); + i++) { + searchElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + // if sibling... + if (searchElem->mParentIndex == curElemParent) { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchElem->mName))) { + reader->mElemReadIndex = i; + reader->mValueReadIndex = -1; + return gsi_true; + } + } + // bail if we reach a higher brance + if (searchElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + + // no matching element found + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlMoveToChild(GSXmlStreamReader stream, const char* matchtag) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchElem = NULL; + int i = 0; + + for (i = (reader->mElemReadIndex + 1); i < ArrayLength(reader->mElementArray); + i++) { + searchElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchElem->mName))) { + reader->mElemReadIndex = i; + reader->mValueReadIndex = -1; + return gsi_true; + } + } + // check if we've reached a higher branch + // -- we know this when we've reached an element whose + // parent is above our level in the tree + if (searchElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsString(GSXmlStreamReader stream, const char* matchtag, + const char** valueOut, int* lenOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + reader->mValueReadIndex = i; + *valueOut = (const char*)searchValueElem->mValue.mData; + *lenOut = searchValueElem->mValue.mLen; + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsUnicodeString(GSXmlStreamReader stream, + const char* matchtag, + gsi_char** valueOut, int* lenOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) { + *valueOut = NULL; + *lenOut = 0; + return gsi_true; + } + *lenOut = UTF8ToUCS2StringLen( + (const char*)searchValueElem->mValue.mData, + (unsigned short*)*valueOut, searchValueElem->mValue.mLen); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Same as read child as string, but copies into valueOut and null terminates +gsi_bool gsXmlReadChildAsStringNT(GSXmlStreamReader stream, + const char* matchtag, char valueOut[], + int maxLen) { + const char* strValue = NULL; + int strLen = 0; + + if (gsi_is_false( + gsXmlReadChildAsString(stream, matchtag, &strValue, &strLen))) { + valueOut[0] = '\0'; + return gsi_false; + } else { + strncpy(valueOut, strValue, (size_t)min(maxLen, strLen)); + valueOut[min(maxLen - 1, strLen)] = '\0'; + return gsi_true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Same as readChildAsStringNT, but converts Ascii/UTF-8 to USC2 +gsi_bool gsXmlReadChildAsUnicodeStringNT(GSXmlStreamReader stream, + const char* matchtag, + gsi_char valueOut[], int maxLen) { + const char* utf8Value = NULL; + int unicodeLen = 0; + int utf8Len = 0; + + if (gsi_is_false( + gsXmlReadChildAsString(stream, matchtag, &utf8Value, &utf8Len))) { + valueOut[0] = '\0'; + return gsi_false; + } else { + // Convert into destination buffer + unicodeLen = + UTF8ToUCS2StringLen(utf8Value, (unsigned short*)valueOut, utf8Len); + valueOut[min(maxLen - 1, unicodeLen)] = '\0'; + return gsi_true; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsHexBinary(GSXmlStreamReader stream, + const char* matchtag, gsi_u8 valueOut[], + int maxLen, int* lenOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + // switch endianess, e.g. first character in hexstring is HI byte + gsi_u32 temp = 0; + int writepos = 0; + int readpos = 0; + int bytesleft = min(maxLen * 2, searchValueElem->mValue.mLen); + + // special case: zero length value + if (searchValueElem->mValue.mLen == 0 || + searchValueElem->mValue.mData == NULL) { + valueOut[0] = 0; + *lenOut = 0; + return gsi_true; + } + + // Checking length only? + if (valueOut == NULL) { + *lenOut = searchValueElem->mValue.mLen / 2; + + // note: read position left at this elemtent so next read can record + // the data + return gsi_true; + } + + // 2 characters of hexbyte = 1 value byte + while (bytesleft > 1) { + sscanf((char*)(&searchValueElem->mValue.mData[readpos]), "%02x", + &temp); // sscanf requires a 4 byte dest + valueOut[writepos] = (gsi_u8) + temp; // then we convert to byte, to ensure correct byte order + readpos += 2; + writepos += 1; + bytesleft -= 2; + } + if (bytesleft == 1) { + sscanf((char*)(&searchValueElem->mValue.mData[readpos]), "%01x", + &temp); // sscanf requires a 4 byte dest + valueOut[writepos] = (gsi_u8) + temp; // then we convert to byte, to ensure correct byte order + readpos += 1; + writepos += 1; + bytesleft -= 1; + } + if (lenOut != NULL) + *lenOut = writepos; + + reader->mValueReadIndex = i; // mark that this element was read + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsBase64Binary(GSXmlStreamReader stream, + const char* matchtag, gsi_u8 valueOut[], + int* lenOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + if (valueOut) { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData) + B64Decode((char*)searchValueElem->mValue.mData, (char*)valueOut, + searchValueElem->mValue.mLen, lenOut, + GS_XML_BASE64_ENCODING_TYPE); + else + *lenOut = 0; + } else { + if (searchValueElem->mValue.mData) + *lenOut = B64DecodeLen((const char*)searchValueElem->mValue.mData, + GS_XML_BASE64_ENCODING_TYPE); + else + *lenOut = 0; + } + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsInt(GSXmlStreamReader stream, const char* matchtag, + int* valueOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + *valueOut = atoi((const char*)searchValueElem->mValue.mData); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsInt64(GSXmlStreamReader stream, const char* matchtag, + gsi_i64* valueOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + *valueOut = + gsiStringToInt64((const char*)searchValueElem->mValue.mData); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsDateTimeElement(GSXmlStreamReader stream, + const char* matchtag, + time_t* valueOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + struct tm timePtr; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + + // convert the time to from a string to a time struct + sscanf((const char*)searchValueElem->mValue.mData, + "%i-%02d-%02dT%02d:%02d:%02d", &timePtr.tm_year, &timePtr.tm_mon, + &timePtr.tm_mday, &timePtr.tm_hour, &timePtr.tm_min, + &timePtr.tm_sec); + + timePtr.tm_year -= 1900; + timePtr.tm_mon -= 1; + timePtr.tm_isdst = -1; + *valueOut = gsiDateToSeconds(&timePtr); + + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool gsXmlReadChildAsFloat(GSXmlStreamReader stream, const char* matchtag, + float* valueOut) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchValueElem = NULL; + int i = 0; + + // Do we have a valid value position already? + if (reader->mValueReadIndex == -1) + reader->mValueReadIndex = + reader->mElemReadIndex; // start at current element + + for (i = (reader->mValueReadIndex + 1); + i < ArrayLength(reader->mElementArray); i++) { + searchValueElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchValueElem->mParentIndex == reader->mElemReadIndex) { + // check match + if (gsi_is_true( + gsiXmlUtilTagMatches(matchtag, &searchValueElem->mName))) { + reader->mValueReadIndex = i; + if (searchValueElem->mValue.mData == NULL) + return gsi_false; // invalid type! + *valueOut = (float)atof((const char*)searchValueElem->mValue.mData); + return gsi_true; + } + } + // bail if we've reached a higher branch + if (searchValueElem->mParentIndex < reader->mElemReadIndex) + return gsi_false; + } + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Compare only the text following the namespace character +gsi_bool gsiXmlUtilTagMatches(const char* matchtag, GSIXmlString* xmlstr) { + const char* matchNoNamespace = NULL; + GSIXmlString xmlNoNamespace; + int xmlNamespacePos = 0; + + GS_ASSERT(xmlstr != NULL); + + if (matchtag == NULL) + return gsi_true; + if (matchtag[strlen(matchtag) - 1] == ':') + return gsi_false; // illegal to end with ':' + + // find post-namespace positions + matchNoNamespace = strchr(matchtag, ':'); + if (matchNoNamespace == NULL) + matchNoNamespace = matchtag; + + while (xmlNamespacePos < xmlstr->mLen && + xmlstr->mData[xmlNamespacePos] != ':') + xmlNamespacePos++; + if (xmlNamespacePos == xmlstr->mLen) + xmlNamespacePos = 0; + else + xmlNamespacePos++; // add one more to skip over the ':' + xmlNoNamespace.mData = xmlstr->mData + xmlNamespacePos; + xmlNoNamespace.mLen = xmlstr->mLen - xmlNamespacePos; + + // compare strings + if (0 == strncmp(matchNoNamespace, (const char*)xmlNoNamespace.mData, + (size_t)xmlNoNamespace.mLen)) + return gsi_true; + else + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Reads childs as bin-endian hexbinary, then converts to little-endian large +// int +gsi_bool gsXmlReadChildAsLargeInt(GSXmlStreamReader stream, + const char* matchtag, + struct gsLargeInt_s* valueOut) { + int len = 0; + + // set to zero + memset(valueOut, 0, sizeof(struct gsLargeInt_s)); + + // parse the hexbinary + if (gsi_is_false( + gsXmlReadChildAsHexBinary(stream, matchtag, (gsi_u8*)valueOut->mData, + GS_LARGEINT_BINARY_SIZE / 8 * 2, &len))) + return gsi_false; + + // save off length + valueOut->mLength = (l_word)(len / GS_LARGEINT_DIGIT_SIZE_BYTES); + if (len % GS_LARGEINT_DIGIT_SIZE_BYTES != 0) + valueOut->mLength++; + + // reverse byte order + if (gsi_is_false(gsLargeIntReverseBytes(valueOut))) + return gsi_false; + else + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Resets the child read position to the first child of the current element +// +gsi_bool gsXmlResetChildReadPosition(GSXmlStreamReader stream) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + reader->mValueReadIndex = + -1; // no current child position means start at first + return gsi_true; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Count the number of children with the tag +// If the tag is NULL, count all the children +// Only counts direct children, not grandchildren or lower +int gsXmlCountChildren(GSXmlStreamReader stream, const char* matchtag) { + GSIXmlStreamReader* reader = (GSIXmlStreamReader*)stream; + GSIXmlElement* searchElem = NULL; + int i = 0; + int count = 0; + + for (i = (reader->mElemReadIndex + 1); i < ArrayLength(reader->mElementArray); + i++) { + searchElem = (GSIXmlElement*)ArrayNth(reader->mElementArray, i); + if (searchElem->mParentIndex == reader->mElemReadIndex) { + // check match + if ((matchtag == NULL) || + gsi_is_true(gsiXmlUtilTagMatches(matchtag, &searchElem->mName))) { + count++; + } + } + // check if we've reached a higher branch + // -- we know this when we've reached an element whose + // parent is above our level in the tree + else if (searchElem->mParentIndex < reader->mElemReadIndex) + break; + } + return count; +} diff --git a/source/gamespy/common/gsXML.h b/source/gamespy/common/gsXML.h new file mode 100644 index 000000000..142d99cf0 --- /dev/null +++ b/source/gamespy/common/gsXML.h @@ -0,0 +1,156 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "gsLargeInt.h" // so that it can write large ints +#include "gsPlatform.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// GameSpy XML parser for soap messages +// Create the stream object and attach to an XML text buffer. +// The stream will not modify the buffer. +// The buffer should not be released until after the stream is destroyed +// +// +// Limitations: +// Processing instructions other than ' + +// include the revolution socket header +#include "../gsPlatform.h" +#include "../gsPlatformSocket.h" +#include "../gsPlatformUtil.h" + +#include + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// static variables +// PAL: 0x80386354 @sbss +static int GSIRevolutionErrno; +extern int _pad_80386350; +int _pad_80386350; // FIXME does not exist but required as sbss padding. + +// prototypes of static functions +static int CheckRcode(int rcode, int errCode); + +#define REVOlUTION_SOCKET_ERROR -1 +#define SOCKET int + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// parses rcode into a generic -1 error if an error has occured +static int CheckRcode(int rcode, int errCode) { + if (rcode >= 0) + return rcode; + GSIRevolutionErrno = rcode; + return errCode; +} + +int socket(int pf, int type, int protocol) { + int rcode = SOSocket(pf, type, 0); + GSI_UNUSED(protocol); + return CheckRcode(rcode, INVALID_SOCKET); +} +int closesocket(SOCKET sock) { + int rcode = SOClose(sock); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +int shutdown(SOCKET sock, int how) { + int rcode = SOShutdown(sock, how); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +int bind(SOCKET sock, const SOCKADDR* addr, int len) { + SOCKADDR localAddr; + int rcode; + + // with Revolution, don't bind to 0, just start using the port + if (((const SOCKADDR_IN*)addr)->port == 0) + return 0; + + memcpy(&localAddr, addr, sizeof(SOCKADDR)); + localAddr.len = (u8)len; + + rcode = SOBind(sock, &localAddr); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +int connect(SOCKET sock, const SOCKADDR* addr, int len) { + SOCKADDR remoteAddr; + int rcode; + + memcpy(&remoteAddr, addr, sizeof(SOCKADDR)); + remoteAddr.len = (u8)len; + + rcode = SOConnect(sock, &remoteAddr); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +/* +int listen(SOCKET sock, int backlog) +{ + int rcode = SOListen(sock, backlog); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +SOCKET accept(SOCKET sock, SOCKADDR* addr, int* len) +{ + int rcode; + addr->len = (u8)*len; + rcode = SOAccept(sock, addr); + *len = addr->len; + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +*/ + +int recv(SOCKET sock, char* buf, int len, int flags) { + // TODO flags hardcoded to 4 here. + int rcode = SORecv(sock, buf, len, 4); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +int recvfrom(SOCKET sock, char* buf, int len, int flags, SOCKADDR* addr, + int* fromlen) { + int rcode; + addr->len = (u8)*fromlen; + // TODO flags hardcoded to 4 here. + rcode = SORecvFrom(sock, buf, len, 4, addr); + *fromlen = addr->len; + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +SOCKET send(SOCKET sock, const char* buf, int len, int flags) { + int rcode = SOSend(sock, buf, len, flags); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +SOCKET sendto(SOCKET sock, const char* buf, int len, int flags, + const SOCKADDR* addr, int tolen) { + SOCKADDR remoteAddr; + int rcode; + + memcpy(&remoteAddr, addr, sizeof(SOCKADDR)); + remoteAddr.len = (u8)tolen; + + rcode = SOSendTo(sock, buf, len, flags, &remoteAddr); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +/* +int getsockopt(SOCKET sock, int level, int optname, char* optval, int* optlen) +{ + int rcode = SOGetSockOpt(sock, level, optname, optval, optlen); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} +*/ +SOCKET setsockopt(SOCKET sock, int level, int optname, const char* optval, + int optlen) { + int rcode = SOSetSockOpt(sock, level, optname, optval, optlen); + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +int getsockname(SOCKET sock, SOCKADDR* addr, int* len) { + int rcode; + addr->len = (u8)*len; + rcode = SOGetSockName(sock, addr); + *len = addr->len; + return CheckRcode(rcode, REVOlUTION_SOCKET_ERROR); +} + +unsigned long inet_addr(const char* name) { + int rcode; + SOInAddr addr; + rcode = SOInetAtoN(name, &addr); + if (rcode == false) + return INADDR_NONE; + return addr.addr; +} + +int GOAGetLastError(SOCKET sock) { + GSI_UNUSED(sock); + return GSIRevolutionErrno; +} + +int GSISocketSelect(SOCKET theSocket, int* theReadFlag, int* theWriteFlag, + int* theExceptFlag) { + SOPollFD pollFD; + int rcode; + + pollFD.fd = theSocket; + pollFD.events = 0; + if (theReadFlag != NULL) + pollFD.events |= SO_POLLRDNORM; + if (theWriteFlag != NULL) + pollFD.events |= SO_POLLWRNORM; + pollFD.revents = 0; + + rcode = SOPoll(&pollFD, 1, 0); + if (rcode < 0) + return REVOlUTION_SOCKET_ERROR; + + if (theReadFlag != NULL) { + if ((rcode > 0) && (pollFD.revents & (SO_POLLRDNORM | SO_POLLHUP))) + *theReadFlag = 1; + else + *theReadFlag = 0; + } + if (theWriteFlag != NULL) { + if ((rcode > 0) && (pollFD.revents & SO_POLLWRNORM)) + *theWriteFlag = 1; + else + *theWriteFlag = 0; + } + if (theExceptFlag != NULL) { + if ((rcode > 0) && (pollFD.revents & SO_POLLERR)) + *theExceptFlag = 1; + else + *theExceptFlag = 0; + } + return rcode; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/common/revolution/gsThreadRevolution.c b/source/gamespy/common/revolution/gsThreadRevolution.c new file mode 100644 index 000000000..d9ea9b796 --- /dev/null +++ b/source/gamespy/common/revolution/gsThreadRevolution.c @@ -0,0 +1,162 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" + +#include + +// Begin of Threading for Revolution +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiInterlockedIncrement(gsi_u32* value) { + int enabled = OSDisableInterrupts(); + + gsi_u32 ret = ++(*value); + OSRestoreInterrupts(enabled); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + +gsi_u32 gsiInterlockedDecrement(gsi_u32* value) { + int state = OSDisableInterrupts(); + gsi_u32 ret = --(*value); + OSRestoreInterrupts(state); + + // return "ret" rather than "value" here b/c + // value may be modified by another thread + // before we can return it + return ret; +} + +/* +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +int gsiStartThread(GSThreadFunc aThreadFunc, gsi_u32 theStackSize, void* arg, + GSIThreadID* theThreadIdOut) { + char* aStackBase; + if (theStackSize % 32 != 0) { + OSRoundUp32B(theStackSize); + } + + theThreadIdOut->mStack = gsimalloc(theStackSize); + aStackBase = (char*)theThreadIdOut->mStack; + aStackBase += theStackSize; + + OSCreateThread(&theThreadIdOut->mThread, aThreadFunc, arg, (void*)aStackBase, + theStackSize, 16, 0x0001u); + + OSResumeThread(&theThreadIdOut->mThread); + + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiCancelThread(GSIThreadID theThreadID) { + OSCancelThread(&theThreadID.mThread); + if (theThreadID.mStack) { + gsifree(theThreadID.mStack); + theThreadID.mStack = NULL; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiCleanupThread(GSIThreadID theThreadID) { + + if (!OSIsThreadTerminated(&theThreadID.mThread)) + OSCancelThread(&theThreadID.mThread); + if (theThreadID.mStack) { + gsifree(theThreadID.mStack); + theThreadID.mStack = NULL; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiHasThreadShutdown(GSIThreadID theThreadID) { + int shutdown = OSIsThreadTerminated(&theThreadID.mThread); + + if (shutdown == true) + return 1; + return 0; +} +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiInitializeCriticalSection(GSICriticalSection* theCrit) { + OSInitMutex(theCrit); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiEnterCriticalSection(GSICriticalSection* theCrit) { + OSLockMutex(theCrit); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiLeaveCriticalSection(GSICriticalSection* theCrit) { + OSUnlockMutex(theCrit); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiDeleteCriticalSection(GSICriticalSection* theCrit) { + GSI_UNUSED(theCrit); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/* +GSISemaphoreID gsiCreateSemaphore(gsi_i32 theInitialCount, gsi_i32 theMaxCount, +char* theName) +{ + GSISemaphoreID semaphore; + + OSInitSemaphore(&semaphore, theInitialCount); + + GSI_UNUSED(theName); + GSI_UNUSED(theMaxCount); + + return semaphore; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_u32 gsiWaitForSemaphore(GSISemaphoreID theSemaphore, gsi_u32 theTimeoutMs) +{ + gsi_u32 retval = (unsigned int)OSWaitSemaphore(&theSemaphore); + GSI_UNUSED(theTimeoutMs); + return retval; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiReleaseSemaphore(GSISemaphoreID theSemaphore, gsi_i32 theReleaseCount) +{ + OSSignalSemaphore(&theSemaphore); + GSI_UNUSED(theReleaseCount); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiCloseSemaphore(GSISemaphoreID theSemaphore) +{ + GSI_UNUSED(theSemaphore); +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void gsiExitThread(GSIThreadID theThreadID) { GSI_UNUSED(theThreadID); } +*/ + +// End of Threading for Revolution +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/source/gamespy/common/revolution/gsUtilRevolution.c b/source/gamespy/common/revolution/gsUtilRevolution.c new file mode 100644 index 000000000..3731bef4a --- /dev/null +++ b/source/gamespy/common/revolution/gsUtilRevolution.c @@ -0,0 +1,120 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../gsCommon.h" +#include "../gsPlatformUtil.h" + +#include +#include +#include +#include + +#include "gsThreadRevolution.c" + +void gsiRevolutionSleep(u32 msec); +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static OSAlarm gAlarm; +static OSThreadQueue gQueue; +static int gQueueInitialized; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/* +static const char * GOAGetUniqueID_Internal(void) +{ + static char keyval[17]; + u8 aMac[6]; + int aMacLen; + + // check if we already have the Unique ID + if(keyval[0]) + return keyval; + + aMacLen = 6; + SOGetInterfaceOpt (NULL, SO_SOL_CONFIG, SO_CONFIG_MAC_ADDRESS, + aMac, &aMacLen); + + // format it + sprintf(keyval, "%02X%02X%02X%02X%02X%02X0000", + aMac[0] & 0xFF, + aMac[1] & 0xFF, + aMac[2] & 0xFF, + aMac[3] & 0xFF, + aMac[4] & 0xFF, + aMac[5] & 0xFF); + + return keyval; +} +*/ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Time Functions +// static char GSIMonthNames[12][3] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", +// "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; +// static char GSIWeekDayNames[7][3] = {"Sun", "Mon", "Tue", "Wed", +// "Thu", "Fri", "Sat"}; + +time_t gsiTimeInSec(time_t* timer) { + time_t t = 0; + t = OSTicksToSeconds(OSGetTime()); + + if (timer) + *timer = t; + + return t; +} + +/* +struct tm* gsiGetGmTime(time_t* theTime) { + static struct tm aTimeStruct; + static struct tm* aRetVal = &aTimeStruct; + OSCalendarTime aCalTimeStruct; + + OSTicksToCalendarTime(*theTime, &aCalTimeStruct); + + aRetVal->tm_sec = aCalTimeStruct.sec; + aRetVal->tm_min = aCalTimeStruct.min; + aRetVal->tm_hour = aCalTimeStruct.hour; + aRetVal->tm_mday = aCalTimeStruct.mday; + aRetVal->tm_mon = aCalTimeStruct.mon; + aRetVal->tm_year = aCalTimeStruct.year - 1900; + aRetVal->tm_wday = aCalTimeStruct.wday; + aRetVal->tm_yday = 0; + aRetVal->tm_isdst = 0; + return aRetVal; +} + +char *gsiCTime(time_t *theTime) +{ + static char str[26]; + struct tm *ptm = gsiGetGmTime(theTime); + + // e.g.: "Wed Jan 02 02:03:55 1980\n\0" + sprintf(str, "%s %s %02d %02d:%02d:%02d %d\n", + GSIWeekDayNames[ptm->tm_wday], + GSIMonthNames[ptm->tm_mon], ptm->tm_mday, + ptm->tm_hour, ptm->tm_min, ptm->tm_sec, + ptm->tm_year + 1900); + + return str; +} +*/ + +/* +gsi_i64 gsiStringToInt64(const char *theNumberStr) +{ + return atoll(theNumberStr); +} +*/ + +/* +void gsiInt64ToString(char theNumberStr[33], gsi_i64 theNumber) +{ + // you want to fit the number! + // give me a valid string! + GS_ASSERT(theNumberStr != NULL); + + sprintf(theNumberStr, "%lld", theNumber); +} +*/ diff --git a/source/gamespy/darray.c b/source/gamespy/darray.c new file mode 100644 index 000000000..d27dd2e0b --- /dev/null +++ b/source/gamespy/darray.c @@ -0,0 +1,347 @@ +/* + * + * File: darray.c + * --------------- + * David Wright + * 10/8/98 + * + * See darray.h for function descriptions + */ +#include "darray.h" +#include +#include + +#ifdef _MFC_MEM_DEBUG +#define _CRTDBG_MAP_ALLOC 1 +#include +#endif + +#define DEF_GROWBY 8 + +#include "nonport.h" + +// STRUCTURES +struct DArrayImplementation { + int count, capacity; + int elemsize; + int growby; + ArrayElementFreeFn elemfreefn; + void* list; // array of elements +}; + +// PROTOTYPES +static void* mylsearch(const void* key, void* base, int count, int size, + ArrayCompareFn comparator); +static void* mybsearch(const void* elem, void* base, int num, int elemsize, + ArrayCompareFn comparator, int* found); +// FUNCTIONS + +/* FreeElement + * Frees the element at position N in the array + */ +static void FreeElement(DArray array, int n) { + if (array->elemfreefn != NULL) + array->elemfreefn(ArrayNth(array, n)); +} + +/* ArrayGrow + * Reallocates the array to a new size, incresed by growby + */ +static void ArrayGrow(DArray array) { + GS_ASSERT(array->elemsize) // sanity check -mj Oct 31st + array->capacity += array->growby; + array->list = + gsirealloc(array->list, (size_t)array->capacity * array->elemsize); + GS_ASSERT(array->list); +} + +/* SetElement + * Sets the element at pos to the contents of elem + */ +static void SetElement(DArray array, const void* elem, int pos) { + GS_ASSERT(array) // safety check -mj Oct 31st + GS_ASSERT(elem) + GS_ASSERT(array->elemsize) + + memcpy(ArrayNth(array, pos), elem, (size_t)array->elemsize); +} + +DArray ArrayNew(int elemSize, int numElemsToAllocate, + ArrayElementFreeFn elemFreeFn) { + DArray array; + + array = (DArray)gsimalloc(sizeof(struct DArrayImplementation)); + GS_ASSERT(array); + GS_ASSERT(elemSize); + if (numElemsToAllocate == 0) + numElemsToAllocate = DEF_GROWBY; + array->count = 0; + array->capacity = numElemsToAllocate; + ; + array->elemsize = elemSize; + array->growby = numElemsToAllocate; + array->elemfreefn = elemFreeFn; + if (array->capacity != 0) { + array->list = gsimalloc((size_t)array->capacity * array->elemsize); + GS_ASSERT(array->list); + } else + array->list = NULL; + + return array; +} + +void ArrayFree(DArray array) { + int i; + + GS_ASSERT(array); + for (i = 0; i < array->count; i++) { + FreeElement(array, i); + } + // mj to do: move these asserts into gsi_free. maybe, depends on whether user + // overloads them + GS_ASSERT(array->list) + GS_ASSERT(array) + gsifree(array->list); + gsifree(array); +} + +/* +void *ArrayGetDataPtr(DArray array) +{ + GS_ASSERT(array); + return array->list; +} + +void ArraySetDataPtr(DArray array, void *ptr, int count, int capacity) +{ + int i; + + GS_ASSERT(array); + if (array->list != NULL) + { + for (i = 0; i < array->count; i++) + { + FreeElement(array, i); + } + gsifree(array->list); + } + array->list = ptr; + array->count = count; + array->capacity = capacity; + +} +*/ + +int ArrayLength(const DArray array) { + GS_ASSERT(array) + return array->count; +} + +void* ArrayNth(DArray array, int n) { + // 2004.Nov.16.JED - modified GS_ASSERT to include "if" to add robustness + GS_ASSERT((n >= 0) && (n < array->count)); + if (!((n >= 0) && (n < array->count))) + return NULL; + + return (char*)array->list + array->elemsize * n; +} + +/* ArrayAppend + * Just do an Insert at the end of the array + */ +void ArrayAppend(DArray array, const void* newElem) { + GS_ASSERT(array); + if (array) + ArrayInsertAt(array, newElem, array->count); +} + +static inline void ArrayInsertAt(DArray array, const void* newElem, int n) { + GS_ASSERT(array) + GS_ASSERT((n >= 0) && (n <= array->count)); + + if (array->count == array->capacity) + ArrayGrow(array); + array->count++; + if (n < array->count - 1) // if we aren't appending + memmove(ArrayNth(array, n + 1), ArrayNth(array, n), + (size_t)(array->count - 1 - n) * array->elemsize); + SetElement(array, newElem, n); +} + +void ArrayInsertSorted(DArray array, const void* newElem, + ArrayCompareFn comparator) { + int n; + void* res; + int found; + + GS_ASSERT(array) + GS_ASSERT(comparator); + + res = mybsearch(newElem, array->list, array->count, array->elemsize, + comparator, &found); + n = (((char*)res - (char*)array->list) / array->elemsize); + ArrayInsertAt(array, newElem, n); +} + +void ArrayRemoveAt(DArray array, int n) { + GS_ASSERT(array) + GS_ASSERT((n >= 0) && (n < array->count)); + + if (n < array->count - 1) // if not last element + memmove(ArrayNth(array, n), ArrayNth(array, n + 1), + (size_t)(array->count - 1 - n) * array->elemsize); + array->count--; +} + +void ArrayDeleteAt(DArray array, int n) { + GS_ASSERT(array) + GS_ASSERT((n >= 0) && (n < array->count)); + + FreeElement(array, n); + ArrayRemoveAt(array, n); +} + +void ArrayReplaceAt(DArray array, const void* newElem, int n) { + GS_ASSERT(array) + GS_ASSERT((n >= 0) && (n < array->count)); + + FreeElement(array, n); + SetElement(array, newElem, n); +} + +void ArraySort(DArray array, ArrayCompareFn comparator) { + GS_ASSERT(array) + qsort(array->list, (size_t)array->count, (size_t)array->elemsize, comparator); +} + +// GS_ASSERT will be raised by ArrayNth if fromindex out of range +int ArraySearch(DArray array, const void* key, ArrayCompareFn comparator, + int fromIndex, int isSorted) { + void* res; + int found = 1; + if (!array || array->count == 0) + return NOT_FOUND; + + if (isSorted) + res = mybsearch(key, ArrayNth(array, fromIndex), array->count - fromIndex, + array->elemsize, comparator, &found); + else + res = mylsearch(key, ArrayNth(array, fromIndex), array->count - fromIndex, + array->elemsize, comparator); + if (res != NULL && found) + return (((char*)res - (char*)array->list) / array->elemsize); + else + return NOT_FOUND; +} + +/* +void ArrayMap(DArray array, ArrayMapFn fn, void *clientData) +{ + int i; + + GS_ASSERT (array) + GS_ASSERT(fn); + + for (i = 0; i < array->count; i++) + fn(ArrayNth(array,i), clientData); + +} +*/ + +void ArrayMapBackwards(DArray array, ArrayMapFn fn, void* clientData) { + int i; + + GS_ASSERT(fn); + + for (i = (array->count - 1); i >= 0; i--) + fn(ArrayNth(array, i), clientData); +} + +/* +void * ArrayMap2(DArray array, ArrayMapFn2 fn, void *clientData) +{ + int i; + void * pcurr; + + GS_ASSERT(fn); + GS_ASSERT(clientData); + + for (i = 0; i < array->count; i++) + { + pcurr = ArrayNth(array,i); + if(!fn(pcurr, clientData)) + return pcurr; + } + + return NULL; +} +*/ + +void* ArrayMapBackwards2(DArray array, ArrayMapFn2 fn, void* clientData) { + int i; + void* pcurr; + + GS_ASSERT(fn); + GS_ASSERT(clientData); + + for (i = (array->count - 1); i >= 0; i--) { + pcurr = ArrayNth(array, i); + if (!fn(pcurr, clientData)) + return pcurr; + } + + return NULL; +} + +void ArrayClear(DArray array) { + int i; + + // This could be more optimal! + ////////////////////////////// + for (i = (ArrayLength(array) - 1); i >= 0; i--) + ArrayDeleteAt(array, i); +} + +/* mylsearch + * Implementation of a standard linear search on an array, since we + * couldn't use lfind + */ +static void* mylsearch(const void* key, void* base, int count, int size, + ArrayCompareFn comparator) { + int i; + GS_ASSERT(key); + GS_ASSERT(base); + for (i = 0; i < count; i++) { + if (comparator(key, (char*)base + size * i) == 0) + return (char*)base + size * i; + } + return NULL; +} + +/* mybsearch + * Implementation of a bsearch, since its not available on all platforms + */ +static void* mybsearch(const void* elem, void* base, int num, int elemsize, + ArrayCompareFn comparator, int* found) { + int L, H, I, C; + + GS_ASSERT(elem); + GS_ASSERT(base); + GS_ASSERT(found); + + L = 0; + H = num - 1; + *found = 0; + while (L <= H) { + I = (L + H) >> 1; + C = comparator(((char*)base) + I * elemsize, elem); + if (C == 0) + *found = 1; + if (C < 0) + L = I + 1; + else { + H = I - 1; + } + } + return ((char*)base) + L * elemsize; +} diff --git a/source/gamespy/darray.h b/source/gamespy/darray.h new file mode 100644 index 000000000..b46442e18 --- /dev/null +++ b/source/gamespy/darray.h @@ -0,0 +1,302 @@ +#pragma once + +/* File: darray.h + * -------------- + * Defines the interface for the DynamicArray ADT. + * The DArray allows the client to store any number of elements of any desired + * base type and is appropriate for a wide variety of storage problems. It + * supports efficient element access, and appending/inserting/deleting elements + * as well as optional sorting and searching. In all cases, the DArray imposes + * no upper bound on the number of elements and deals with all its own memory + * management. The client specifies the size (in bytes) of the elements that + * will be stored in the array when it is created. Thereafter the client and + * the DArray can refer to elements via (void*) ptrs. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Type: DArray + * ---------------- + * Defines the DArray type itself. The client can declare variables of type + * DArray, but these variables must be initialized with the result of ArrayNew. + * The DArray is implemented with pointers, so all client copies in variables + * or parameters will be "shallow" -- they will all actually point to the + * same DArray structure. Only calls to ArrayNew create new arrays. + * The struct declaration below is "incomplete"- the implementation + * details are literally not visible in the client .h file. + */ +typedef struct DArrayImplementation* DArray; + +/* ArrayCompareFn + * -------------- + * ArrayCompareFn is a pointer to a client-supplied function which the + * DArray uses to sort or search the elements. The comparator takes two + * (const void*) pointers (these will point to elements) and returns an int. + * The comparator should indicate the ordering of the two elements + * using the same convention as the strcmp library function: + * If elem1 is "less than" elem2, return a negative number. + * If elem1 is "greater than" elem2, return a positive number. + * If the two elements are "equal", return 0. + */ +typedef int (*ArrayCompareFn)(const void* elem1, const void* elem2); + +/* ArrayMapFn + * ---------- + * ArrayMapFn defines the space of functions that can be used to map over + * the elements in a DArray. A map function is called with a pointer to + * the element and a client data pointer passed in from the original + * caller. + */ +typedef void (*ArrayMapFn)(void* elem, void* clientData); + +/* ArrayMapFn2 + * ----------_ + * Same as ArrayMapFn, but can return 0 to stop the mapping. + * Used by ArrayMap2 + */ +typedef int (*ArrayMapFn2)(void* elem, void* clientData); + +/* ArrayElementFreeFn + * ------------------ + * ArrayElementFreeFn defines the space of functions that can be used as the + * clean-up function for an element as it is deleted from the array + * or when the entire array of elements is freed. The cleanup function is + * called with a pointer to an element about to be deleted. + */ +typedef void (*ArrayElementFreeFn)(void* elem); + +/* ArrayNew + * -------- + * Creates a new DArray and returns it. There are zero elements in the array. + * to start. The elemSize parameter specifies the number of bytes that a single + * element of this array should take up. For example, if you want to store + * elements of type Binky, you would pass sizeof(Binky) as this parameter. + * An assert is raised if the size is not greater than zero. + * + * The numElemsToAllocate parameter specifies the initial allocated length + * of the array, as well as the dynamic reallocation increment for when the + * array grows. Rather than growing the array one element at a time as + * elements are added (which is rather inefficient), you will grow the array + * in chunks of numElemsToAllocate size. The "allocated length" is the number + * of elements that have been allocated, the "logical length" is the number of + * those slots actually being currently used. + * + * A new array is initially allocated to the size of numElemsToAllocate, the + * logical length is zero. As elements are added, those allocated slots fill + * up and when the initial allocation is all used, grow the array by another + * numElemsToAllocate elements. You will continue growing the array in chunks + * like this as needed. Thus the allocated length will always be a multiple + * of numElemsToAllocate. Don't worry about using realloc to shrink the array + * allocation if many elements are deleted from the array. It turns out that + * many implementations of realloc don't even pay attention to such a request + * so there is little point in asking. Just leave the array over-allocated. + * + * The numElemsToAllocate is the client's opportunity to tune the resizing + * behavior for their particular needs. If constructing large arrays, + * specifying a large allocation chunk size will result in fewer resizing + * operations. If using small arrays, a small allocation chunk size will + * result in less space going unused. If the client passes 0 for + * numElemsToAllocate, the implementation will use the default value of 8. + * + * The elemFreeFn is the function that will be called on an element that + * is about to be deleted (using ArrayDeleteAt) or on each element in the + * array when the entire array is being freed (using ArrayFree). This function + * is your chance to do any deallocation/cleanup required for the element + * (such as freeing any pointers contained in the element). The client can pass + * NULL for the cleanupFn if the elements don't require any handling on free. + */ +DArray ArrayNew(int elemSize, int numElemsToAllocate, + ArrayElementFreeFn elemFreeFn); + +/* ArrayFree + * ---------- + * Frees up all the memory for the array and elements. It DOES NOT + * automatically free memory owned by pointers embedded in the elements. + * This would require knowledge of the structure of the elements which the + * DArray does not have. However, it will iterate over the elements calling + * the elementFreeFn earlier supplied to ArrayNew and therefore, the client, + * who knows what the elements are, can do the appropriate deallocation of any + * embedded pointers through that function. After calling this, the value of + * what array is pointing to is undefined. + */ +void ArrayFree(DArray array); + +/* ArrayLength + * ----------- + * Returns the logical length of the array, i.e. the number of elements + * currently in the array. Must run in constant time. + */ +int ArrayLength(const DArray array); + +/* ArrayNth + * -------- + * Returns a pointer to the element numbered n in the specified array. + * Numbering begins with 0. An assert is raised if n is less than 0 or greater + * than the logical length minus 1. Note this function returns a pointer into + * the DArray's element storage, so the pointer should be used with care. + * This function must operate in constant time. + * + * We could have written the DArray without this sort of access, but it + * is useful and efficient to offer it, although the client needs to be + * careful when using it. In particular, a pointer returned by ArrayNth + * becomes invalid after any calls which involve insertion, deletion or + * sorting the array, as all of these may rearrange the element storage. + */ +void* ArrayNth(DArray array, int n); + +/* ArrayAppend + * ----------- + * Adds a new element to the end of the specified array. The element is + * passed by address, the element contents are copied from the memory pointed + * to by newElem. Note that right after this call, the new element will be + * the last in the array; i.e. its element number will be the logical length + * minus 1. This function must run in constant time (neglecting + * the memory reallocation time which may be required occasionally). + */ +void ArrayAppend(DArray array, const void* newElem); + +/* ArrayInsertAt + * ------------- + * Inserts a new element into the array, placing it at the position n. + * An assert is raised if n is less than 0 or greater than the logical length. + * The array elements after position n will be shifted over to make room. The + * element is passed by address, the new element's contents are copied from + * the memory pointed to by newElem. This function runs in linear time. + */ +void ArrayInsertAt(DArray array, const void* newElem, int n); + +/* ArrayInsertSorted + * ------------- + * Inserts a new element into the array, placing it at the position indicated by + * a binary search of the array using comparator. + * The array MUST be sorted prior to calling InsertSorted. + * Note that if you only ever call InsertSorted, the array will always be + * sorted. + */ +void ArrayInsertSorted(DArray array, const void* newElem, + ArrayCompareFn comparator); + +/* ArrayDeleteAt + * ------------- + * Deletes the element numbered n from the array. Before being removed, + * the elemFreeFn that was supplied to ArrayNew will be called on the element. + * An assert is raised if n is less than 0 or greater than the logical length + * minus one. All the elements after position n will be shifted over to fill + * the gap. This function runs in linear time. It does not shrink the + * allocated size of the array when an element is deleted, the array just + * stays over-allocated. + */ +void ArrayDeleteAt(DArray array, int n); + +/* ArrayDeleteAt + * ------------- + * Removes the element numbered n from the array. The element will not be freed + * before being removed. All the elements after position n will be shifted over + * to fill the gap. This function runs in linear time. It does not shrink the + * allocated size of the array when an element is deleted, the array just + * stays over-allocated. + */ +void ArrayRemoveAt(DArray array, int n); + +/* ArrayReplaceAt + * ------------- + * Overwrites the element numbered n from the array with a new value. Before + * being overwritten, the elemFreeFn that was supplied to ArrayNew is called + * on the old element. Then that position in the array will get a new value by + * copying the new element's contents from the memory pointed to by newElem. + * An assert is raised if n is less than 0 or greater than the logical length + * minus one. None of the other elements are affected or rearranged by this + * operation and the size of the array remains constant. This function must + * operate in constant time. + */ +void ArrayReplaceAt(DArray array, const void* newElem, int n); + +/* ArraySort + * --------- + * Sorts the specified array into ascending order according to the supplied + * comparator. The numbering of the elements will change to reflect the + * new ordering. An assert is raised if the comparator is NULL. + */ +void ArraySort(DArray array, ArrayCompareFn comparator); + +#define NOT_FOUND -1 // returned when a search fails to find the key + +/* ArraySearch + * ----------- + * Searches the specified array for an element whose contents match + * the element passed as the key. Uses the comparator argument to test + * for equality. The "fromIndex" parameter controls where the search + * starts looking from. If the client desires to search the entire array, + * they should pass 0 as the fromIndex. The function will search from + * there to the end of the array. The "isSorted" parameter allows the client + * to specify that the array is already in sorted order, and thus it uses a + * faster binary search. If isSorted is false, a simple linear search is + * used. If a match is found, the position of the matching element is returned + * else the function returns NOT_FOUND. Calling this function does not + * re-arrange or change contents of DArray or modify the key in any way. + * An assert is raised if fromIndex is less than 0 or greater than + * the logical length (although searching from logical length will never + * find anything, allowing this case means you can search an entirely empty + * array from 0 without getting an assert). An assert is raised if the + * comparator is NULL. + */ +int ArraySearch(DArray array, const void* key, ArrayCompareFn comparator, + int fromIndex, int isSorted); + +/* ArrayMap + * ----------- + * Iterates through each element in the array in order (from element 0 to + * element n-1) and calls the function fn for that element. The function is + * called with the address of the array element and the clientData pointer. + * The clientData value allows the client to pass extra state information to + * the client-supplied function, if necessary. If no client data is required, + * this argument should be NULL. An assert is raised if map function is NULL. + */ +void ArrayMap(DArray array, ArrayMapFn fn, void* clientData); + +/* ArrayMapBackwards + * ----------- + * Same as ArrayMap, but goes through the array from end to front. This + * makes it safe to free elements during the mapping. + */ +void ArrayMapBackwards(DArray array, ArrayMapFn fn, void* clientData); + +/* ArrayMap2 + * ----------- + * Same as ArrayMap, but allows the mapping to be stopped by returning 0 + * from the mapping function. If the mapping was stopped, the element + * it was stopped at will be returned. If it wasn't stopped, then NULL + * will be returned. + */ +void* ArrayMap2(DArray array, ArrayMapFn2 fn, void* clientData); + +/* ArrayMapBackwards2 + * ------------ + * Goes through the array backwards, and allows you to stop the mapping. + */ +void* ArrayMapBackwards2(DArray array, ArrayMapFn2 fn, void* clientData); + +/* ArrayClear + * ----------- + * Deletes all elements in the array, but without freeing the array. + */ +void ArrayClear(DArray array); + +/* ArrayGetDataPtr + * ----------- + * Obtain the pointer to the actual data storage + */ +void* ArrayGetDataPtr(DArray array); + +/* ArraySetDataPtr + * ----------- + * Set the pointer to the actual data storage, which must be allocated with + * malloc + */ +void ArraySetDataPtr(DArray array, void* ptr, int count, int capacity); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttp.h b/source/gamespy/ghttp/ghttp.h new file mode 100644 index 000000000..2a21189a7 --- /dev/null +++ b/source/gamespy/ghttp/ghttp.h @@ -0,0 +1,594 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include + +#include "../common/gsCommon.h" +#include "../common/gsXML.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ghttpGet ghttpGetA +#define ghttpGetEx ghttpGetExA +#define ghttpSave ghttpSaveA +#define ghttpSaveEx ghttpSaveExA +#define ghttpStream ghttpStreamA +#define ghttpStreamEx ghttpStreamExA +#define ghttpHead ghttpHeadA +#define ghttpHeadEx ghttpHeadExA +#define ghttpPost ghttpPostA +#define ghttpPostEx ghttpPostExA +#define ghttpPostAddString ghttpPostAddStringA +#define ghttpPostAddFileFromDisk ghttpPostAddFileFromDiskA +#define ghttpPostAddFileFromMemory ghttpPostAddFileFromMemoryA + +// Boolean. +/////////// +typedef enum { GHTTPFalse, GHTTPTrue } GHTTPBool; + +// ByteCount. +///////////// +#if (GSI_MAX_INTEGRAL_BITS >= 64) +typedef gsi_i64 GHTTPByteCount; +#else +typedef gsi_i32 GHTTPByteCount; +#endif + +// The current state of an http request. +//////////////////////////////////////// +typedef enum { + GHTTPSocketInit, // Socket creation and initialization. + GHTTPHostLookup, // Resolving hostname to IP (asynchronously if possible). + GHTTPLookupPending, // Asychronous DNS lookup pending. + GHTTPConnecting, // Waiting for socket connect to complete. + GHTTPSecuringSession, // Setup secure channel. + GHTTPSendingRequest, // Sending the request. + GHTTPPosting, // Positing data (skipped if not posting). + GHTTPWaiting, // Waiting for a response. + GHTTPReceivingStatus, // Receiving the response status. + GHTTPReceivingHeaders, // Receiving the headers. + GHTTPReceivingFile // Receiving the file. +} GHTTPState; + +// The result of an http request. +///////////////////////////////// +typedef enum { + GHTTPSuccess, // 0: Successfully retrieved file. + GHTTPOutOfMemory, // 1: A memory allocation failed. + GHTTPBufferOverflow, // 2: The user-supplied buffer was too small to hold the + // file. + GHTTPParseURLFailed, // 3: There was an error parsing the URL. + GHTTPHostLookupFailed, // 4: Failed looking up the hostname. + GHTTPSocketFailed, // 5: Failed to create/initialize/read/write a socket. + GHTTPConnectFailed, // 6: Failed connecting to the http server. + GHTTPBadResponse, // 7: Error understanding a response from the server. + GHTTPRequestRejected, // 8: The request has been rejected by the server. + GHTTPUnauthorized, // 9: Not authorized to get the file. + GHTTPForbidden, // 10: The server has refused to send the file. + GHTTPFileNotFound, // 11: Failed to find the file on the server. + GHTTPServerError, // 12: The server has encountered an internal error. + GHTTPFileWriteFailed, // 13: An error occured writing to the local file (for + // ghttpSaveFile[Ex]). + GHTTPFileReadFailed, // 14: There was an error reading from a local file (for + // posting files from disk). + GHTTPFileIncomplete, // 15: Download started but was interrupted. Only + // reported if file size is known. + GHTTPFileToBig, // 16: The file is to big to be downloaded (size exceeds range + // of interal data types) + GHTTPEncryptionError, // 17: Error with encryption engine. + GHTTPRequestCancelled // 18: User requested cancel and/or graceful close. +} GHTTPResult; + +// Encryption engines +typedef enum { + GHTTPEncryptionEngine_None, + GHTTPEncryptionEngine_GameSpy, // must add /common/gsSSL.h and /common/gsSSL.c + // to project + GHTTPEncryptionEngine_MatrixSsl, // must define MATRIXSSL and include + // matrixssl source files + GHTTPEncryptionEngine_RevoEx, // must define REVOEXSSL and include RevoEX SSL + // source files + + GHTTPEncryptionEngine_Default // Will use GameSpy unless another engine is + // defined + // using MATRIXSSL or REVOEXSSL +} GHTTPEncryptionEngine; + +// Represents an http file request. +/////////////////////////////////// +typedef int GHTTPRequest; + +// Invalid GHTTPRequest values represent an error +/////////////////////////////////// +#ifdef GHTTP_EXTENDEDERROR +typedef enum { + GHTTPErrorStart = -8, + GHTTPFailedToOpenFile, + GHTTPInvalidPost, + GHTTPInsufficientMemory, + GHTTPInvalidFileName, + GHTTPInvalidBufferSize, + GHTTPInvalidURL, + GHTTPUnspecifiedError = -1 +} GHTTPRequestError; +#else +// Backwards compatibility, developers may have relied on -1 as the only error +// code +typedef enum { + GHTTPErrorStart = -1, + GHTTPFailedToOpenFile = -1, + GHTTPInvalidPost = -1, + GHTTPInsufficientMemory = -1, + GHTTPInvalidFileName = -1, + GHTTPInvalidBufferSize = -1, + GHTTPInvalidURL = -1, + GHTTPUnspecifiedError = -1 +} GHTTPRequestError; +#endif + +#define IS_GHTTP_ERROR(x) (x < 0) + +// Data that can be posted to the server. +// Don't try to access this object directly, +// use the ghttpPost*() functions. +//////////////////////////////////////////// +typedef struct GHIPost* GHTTPPost; + +// Called with updates on the current state of the request. +// The buffer should not be accessed once this callback returns. +// If ghttpGetFile[Ex] was used, buffer contains all of the data that has been +// received so far, and bufferSize is the total number of bytes received. +// If ghttpSaveFile[Ex] was used, buffer only contains the most recent data +// that has been received. This same data is saved to the file. The buffer +// will not be valid after this callback returns. +// If ghttpStreamFileEx was used, buffer only contains the most recent data +// that has been received. This data will be lost once the callback +// returns, and should be copied if it needs to be saved. bufferSize +// is the number of bytes in the current block of data. +////////////////////////////////////////////////////////////////////////////// +typedef void (*ghttpProgressCallback)( + GHTTPRequest request, // The request. + GHTTPState state, // The current state of the request. + const char* + buffer, // The file's bytes so far, NULL if state[:port]". If port is omitted, 80 will be used. +// If server is NULL or "", no proxy server will be used. +// This should not be called while there are any current requests. +////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetProxy(const char* server); + +// Sets a proxy server for a specific request. The address should be of the +// form "[:port]". If port is omitted, 80 will be used. +// If server is NULL or "", no proxy server will be used. +////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetRequestProxy(GHTTPRequest request, const char* server); + +// Used to start/stop throttling an existing connection. +// This may not be as efficient as starting a request +// with the desired setting. +//////////////////////////////////////////////////////// +void ghttpSetThrottle(GHTTPRequest request, GHTTPBool throttle); + +// Used to adjust the throttle settings. +//////////////////////////////////////// +void ghttpThrottleSettings( + int bufferSize, // The number of bytes to get each receive. + gsi_time timeDelay // How often to receive data, in milliseconds. +); + +// Used to throttle based on time, not on bandwidth +// Prevents recv-loop blocking on ultrafast connections without directly +// limiting transfer rate +//////////////////////////////////////// +void ghttpSetMaxRecvTime(GHTTPRequest request, gsi_time maxRecvTime); + +// Creates a new post object, which is used to represent data to send to +// the web server as part of a request. +// After getting the post object, use the ghttpPostAdd*() functions +// to add data to the object, and ghttPostSetCallback() to add a +// callback to monitor the progress of the data upload. +// By default post objects automatically free themselves after posting. +// To use the same post with more than one request, set auto-free to false, +// then use ghttpFreePost to free it _after_ every request its being used +// in is _completed_. +// Returns NULL on error. +/////////////////////////////////////////////////////////////////////////// +GHTTPPost ghttpNewPost(void); + +// Sets a post object's auto-free flag. +// By default post objects automatically free themselves after posting. +// To use the same post with more than one request, set auto-free to false, +// then use ghttpFreePost to free it _after_ every request its being used +// in is _completed_. +/////////////////////////////////////////////////////////////////////////// +void ghttpPostSetAutoFree(GHTTPPost post, GHTTPBool autoFree); + +// Frees a post object. +/////////////////////// +void ghttpFreePost(GHTTPPost post // The post object to free. +); + +// Adds a string to the post object. +//////////////////////////////////// +GHTTPBool +ghttpPostAddString(GHTTPPost post, // The post object to add to. + const gsi_char* name, // The name to attach to this string. + const gsi_char* string // The actual string. +); + +// Adds a disk file to the post object. +// The reportFilename is what is reported to the server as the filename. +// If NULL or empty, the filename will be used (including any possible path). +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The file isn't read from until the data is actually sent to the server. +// Returns false for any error. +///////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromDisk( + GHTTPPost post, // The post object to add to. + const gsi_char* name, // The name to attach to this file. + const gsi_char* + filename, // The name (and possibly path) to the file to upload. + const gsi_char* reportFilename, // The filename given to the web server. + const gsi_char* contentType // The MIME type for this file. +); + +// Adds a file, in memory, to the post object. +// The reportFilename is what is reported to the server as the filename. +// Cannot be NULL or empty. +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The data is NOT copied off in this call. The data pointer is read from +// as the data is actually sent to the server. The pointer must remain +// valid during requests. +// Returns false for any error. +////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromMemory( + GHTTPPost post, // The post object to add to. + const gsi_char* name, // The name to attach to this string. + const char* buffer, // The data to send. + int bufferLen, // The number of bytes of data to send. + const gsi_char* reportFilename, // The filename given to the web server. + const gsi_char* contentType // The MIME type for this file. +); + +// Adds an XML SOAP object to the post object. +// See ghttpNewSoap and other Soap related functions +// Content-Type = text/xml +// The most common use of this function is to add ghttpSoap data +GHTTPBool ghttpPostAddXml(GHTTPPost post, GSXmlStreamWriter xmlSoap); + +// Called during requests to let the app know how much of the post +// data has been uploaded. +////////////////////////////////////////////////////////////////// +typedef void (*ghttpPostCallback)( + GHTTPRequest request, // The request. + int bytesPosted, // The number of bytes of data posted so far. + int totalBytes, // The total number of bytes being posted. + int objectsPosted, // The total number of data objects uploaded so far. + int totalObjects, // The total number of data objects to upload. + void* param // User-data. +); + +// Set the callback for a post object. +////////////////////////////////////// +void ghttpPostSetCallback( + GHTTPPost post, // The post object to set the callback on. + ghttpPostCallback + callback, // The callback to call when using this post object. + void* param // User-data passed to the callback. +); + +// Use ssl encryption engine +GHTTPBool ghttpSetRequestEncryptionEngine(GHTTPRequest request, + GHTTPEncryptionEngine engine); + +// These are defined for backwards compatibility with the "file" function names. +//////////////////////////////////////////////////////////////////////////////// +#define ghttpGetFile(a, b, c, d) ghttpGet(a, b, c, d) +#define ghttpGetFileEx(a, b, c, d, e, f, g, h, i, j) \ + ghttpGetEx(a, b, c, d, e, f, g, h, i, j) +#define ghttpSaveFile(a, b, c, d, e) ghttpSave(a, b, c, d, e) +#define ghttpSaveFileEx(a, b, c, d, e, f, g, h, i) \ + ghttpSaveEx(a, b, c, d, e, f, g, h, i) +#define ghttpStreamFile(a, b, c, d, e) ghttpStream(a, b, c, d, e) +#define ghttpStreamFileEx(a, b, c, d, e, f, g, h) \ + ghttpStreamEx(a, b, c, d, e, f, g, h) +#define ghttpHeadFile(a, b, c, d) ghttpHead(a, b, c, d) +#define ghttpHeadFileEx(a, b, c, d, e, f, g) ghttpHeadEx(a, b, c, d, e, f, g) + +// This ASCII version needs to be define even in UNICODE mode +GHTTPRequest +ghttpGetA(const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until the + // file has been recevied. + ghttpCompletedCallback + completedCallback, // Called when the file has been received. + void* param // User-data to be passed to the callbacks. +); +#define ghttpGetFileA(a, b, c, d) ghttpGetA(a, b, c, d) + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttpASCII.h b/source/gamespy/ghttp/ghttpASCII.h new file mode 100644 index 000000000..536457ffd --- /dev/null +++ b/source/gamespy/ghttp/ghttpASCII.h @@ -0,0 +1,280 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +// ASCII PROTOTYPES FOR USE IN UNICODE MODE +// INCLUDED TO SILENCE CODEWARRIOR WARNINGS +#pragma once + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Get a file from an http server. +// Returns GHTTPRequestError if an error occurs. +////////////////////////////////// +GHTTPRequest +ghttpGetA(const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until the + // file has been recevied. + ghttpCompletedCallback + completedCallback, // Called when the file has been received. + void* param // User-data to be passed to the callbacks. +); + +// Get a file from an http server. +// Returns GHTTPRequestError if an error occurs. +// Allows an optional user-supplied buffer to be used, +// optional extra http headers, +// and an optional progress callback. +// The optional headers must be 0 or more HTTP headers, +// each terminated by a CR-LF pair (0xD, 0xA). +// If using a user-supplied buffer: +// set buffer to the buffer to use, +// set bufferSize to the size of the buffer in bytes. +// To have the library allocate a buffer: +// set buffer to NULL, set bufferSize to 0 +/////////////////////////////////////////////////////// +GHTTPRequest ghttpGetExA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + const char* headers, // Optional headers to pass with the request. Can be + // NULL or "". + char* buffer, // Optional user-supplied buffer. Set to NULL to have one + // allocated. + int bufferSize, // The size of the user-supplied buffer in bytes. 0 if + // buffer is NULL. + GHTTPPost post, // Optional data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until the file has + // been recevied. + ghttpProgressCallback + progressCallback, // Called periodically with progress updates. + ghttpCompletedCallback + completedCallback, // Called when the file has been received. + void* param // User-data to be passed to the callbacks. +); + +// Gets a file and saves it to disk. +// Returns GHTTPRequestError if an error occurs. +//////////////////////////////////// +GHTTPRequest ghttpSaveA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + const char* filename, // The path and name to store the file as locally. + GHTTPBool blocking, // If true, this call doesn't return until the file has + // been recevied. + ghttpCompletedCallback + completedCallback, // Called when the file has been received. + void* param // User-data to be passed to the callbacks. +); + +// Gets a file and saves it to disk. +// Returns GHTTPRequestError if an error occurs. +// Allows optional extra http headers and +// an optional progress callback. +///////////////////////////////////////// +GHTTPRequest ghttpSaveExA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + const char* filename, // The path and name to store the file as locally. + const char* headers, // Optional headers to pass with the request. Can be + // NULL or "". + GHTTPPost post, // Optional data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until the file has + // been recevied. + ghttpProgressCallback + progressCallback, // Called periodically with progress updates. + ghttpCompletedCallback + completedCallback, // Called when the file has been received. + void* param // User-data to be passed to the callbacks. +); + +// Streams a file from an http server. +// Returns GHTTPRequestError if an error occurs. +////////////////////////////////////// +GHTTPRequest ghttpStreamA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until the file has + // finished streaming. + ghttpProgressCallback + progressCallback, // Called whenever new data is received. + ghttpCompletedCallback + completedCallback, // Called when the file has finished streaming. + void* param // User-data to be passed to the callbacks. +); + +// Streams a file from an http server. +// Returns GHTTPRequestError if an error occurs. +// Allows optional extra http headers. +////////////////////////////////////// +GHTTPRequest ghttpStreamExA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + const char* headers, // Optional headers to pass with the request. Can be + // NULL or "". + GHTTPPost post, // Optional data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until the file has + // finished streaming. + ghttpProgressCallback + progressCallback, // Called whenever new data is received. + ghttpCompletedCallback + completedCallback, // Called when the file has finished streaming. + void* param // User-data to be passed to the callbacks. +); + +// Does a file request without actually getting the file. +// Use this to check the headers returned by a server when a request is made. +// Returns GHTTPRequestError if an error occurs. +///////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpHeadA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpCompletedCallback + completedCallback, // Called when the request has finished. + void* param // User-data to be passed to the callbacks. +); + +// Does a file request without actually getting the file. +// Use this to check the headers returned by a server when a request is made. +// Returns GHTTPRequestError if an error occurs. +// Allows optional extra http headers. +///////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpHeadExA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + const char* headers, // Optional headers to pass with the request. Can be + // NULL or "". + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpProgressCallback + progressCallback, // Called whenever new data is received. + ghttpCompletedCallback + completedCallback, // Called when the request has finished. + void* param // User-data to be passed to the callbacks. +); + +// Does an HTTP POST, which can be used to upload data to a web server. +// The post parameter must be a valid GHTTPPost, setup with the data to be +// uploaded. No data will be returned from this request. If data is needed, use +// one of the ghttp*FileEx() functions, and pass in a GHTTPPost object. Returns +// GHTTPRequestError if an error occurs. +/////////////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpPostA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + GHTTPPost post, // The data to be posted. + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpCompletedCallback + completedCallback, // Called when the file has finished streaming. + void* param // User-data to be passed to the callbacks. +); + +// Does an HTTP POST, which can be used to upload data to a web server. +// The post parameter must be a valid GHTTPPost, setup with the data to be +// uploaded. No data will be returned from this request. If data is needed, use +// one of the ghttp*FileEx() functions, and pass in a GHTTPPost object. Returns +// GHTTPRequestError if an error occurs. Allows optional extra http headers and +// an optional progress callback. +/////////////////////////////////////////////////////////////////////////////////// +GHTTPRequest ghttpPostExA( + const char* URL, // The URL for the file + // ("http://host.domain[:port]/path/filename"). + const char* headers, // Optional headers to pass with the request. Can be + // NULL or "". + GHTTPPost post, // The data to be posted. + GHTTPBool throttle, // If true, throttle this connection's download speed. + GHTTPBool blocking, // If true, this call doesn't return until finished + ghttpProgressCallback + progressCallback, // Called whenever new data is received. + ghttpCompletedCallback + completedCallback, // Called when the file has finished streaming. + void* param // User-data to be passed to the callbacks. +); + +// Gets the status code and status string for a request. +// A pointer to the status string is returned, or NULL on error. +// Only valid if the GHTTPState for this request +// is greater than GHTTPReceivingStatus. +//////////////////////////////////////////////////////////////// +const char* ghttpGetResponseStatus( + GHTTPRequest request, // The request to get the response state of. + int* statusCode // If not NULL, the status code is stored here. +); + +// Gets headers returned by the http server. +// Only valid if the GHTTPState for this +// request is GHTTPReceivingFile. +//////////////////////////////////////////// +const char* ghttpGetHeaders(GHTTPRequest request); + +// Gets the URL for a given request. +//////////////////////////////////// +const char* ghttpGetURL(GHTTPRequest request); + +// Sets a proxy server address. The address should be of the +// form "[:port]". If port is omitted, 80 will be used. +// If server is NULL or "", no proxy server will be used. +// This should not be called while there are any current requests. +////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetProxyA(const char* server); + +// Adds a string to the post object. +//////////////////////////////////// +GHTTPBool +ghttpPostAddStringA(GHTTPPost post, // The post object to add to. + const char* name, // The name to attach to this string. + const char* string // The actual string. +); + +// Adds a disk file to the post object. +// The reportFilename is what is reported to the server as the filename. +// If NULL or empty, the filename will be used (including any possible path). +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The file isn't read from until the data is actually sent to the server. +// Returns false for any error. +///////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromDiskA( + GHTTPPost post, // The post object to add to. + const char* name, // The name to attach to this file. + const char* filename, // The name (and possibly path) to the file to upload. + const char* reportFilename, // The filename given to the web server. + const char* contentType // The MIME type for this file. +); + +// Adds a file, in memory, to the post object. +// The reportFilename is what is reported to the server as the filename. +// Cannot be NULL or empty. +// The contentType is the MIME type to report for this file. +// If NULL, "application/octet-stream" is used. +// The data is NOT copied off in this call. The data pointer is read from +// as the data is actually sent to the server. The pointer must remain +// valid during requests. +// Returns false for any error. +////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpPostAddFileFromMemoryA( + GHTTPPost post, // The post object to add to. + const char* name, // The name to attach to this string. + const char* buffer, // The data to send. + int bufferLen, // The number of bytes of data to send. + const char* reportFilename, // The filename given to the web server. + const char* contentType // The MIME type for this file. +); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttpBuffer.c b/source/gamespy/ghttp/ghttpBuffer.c new file mode 100644 index 000000000..4605a614c --- /dev/null +++ b/source/gamespy/ghttp/ghttpBuffer.c @@ -0,0 +1,384 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpBuffer.h" +#include "../common/gsCrypt.h" +#include "../common/gsSSL.h" +#include "ghttpCommon.h" +#include "ghttpConnection.h" +#include "ghttpMain.h" + +// Resize the buffer. +///////////////////// +GHTTPBool ghiResizeBuffer(GHIBuffer* buffer, int sizeIncrement) { + char* tempPtr; + int newSize; + + assert(buffer); + assert(sizeIncrement > 0); + assert(buffer->fixed == GHTTPFalse); // implied by sizeIncrement > 0 + + // Check args. + ////////////// + if (!buffer) + return GHTTPFalse; + if (sizeIncrement <= 0) + return GHTTPFalse; + + // Reallocate with the bigger size. + /////////////////////////////////// + newSize = (buffer->size + sizeIncrement); + tempPtr = (char*)gsirealloc(buffer->data, (unsigned int)newSize); + if (!tempPtr) + return GHTTPFalse; + + // Set the new info. + //////////////////// + buffer->data = tempPtr; + buffer->size = newSize; + + return GHTTPTrue; +} + +GHTTPBool ghiInitBuffer(struct GHIConnection* connection, GHIBuffer* buffer, + int initialSize, int sizeIncrement) { + GHTTPBool bResult; + + assert(connection); + assert(buffer); + assert(initialSize > 0); + assert(sizeIncrement > 0); + + // Check args. + ////////////// + if (!connection) + return GHTTPFalse; + if (!buffer) + return GHTTPFalse; + if (initialSize <= 0) + return GHTTPFalse; + if (sizeIncrement <= 0) + return GHTTPFalse; + + // Init the struct. + /////////////////// + buffer->connection = connection; + buffer->data = NULL; + buffer->size = 0; + buffer->len = 0; + buffer->pos = 0; + buffer->sizeIncrement = sizeIncrement; + buffer->fixed = GHTTPFalse; + buffer->dontFree = GHTTPFalse; + buffer->readOnly = GHTTPFalse; + + // Do the initial resize. + ///////////////////////// + bResult = ghiResizeBuffer(buffer, initialSize); + if (!bResult) + return GHTTPFalse; + + // Start with an empty string. + ////////////////////////////// + *buffer->data = '\0'; + + return GHTTPTrue; +} + +GHTTPBool ghiInitFixedBuffer(struct GHIConnection* connection, + GHIBuffer* buffer, char* userBuffer, int size) { + assert(connection); + assert(buffer); + assert(userBuffer); + assert(size > 0); + + // Check args. + ////////////// + if (!connection) + return GHTTPFalse; + if (!buffer) + return GHTTPFalse; + if (!userBuffer) + return GHTTPFalse; + if (size <= 0) + return GHTTPFalse; + + // Init the struct. + /////////////////// + buffer->connection = connection; + buffer->data = userBuffer; + buffer->size = size; + buffer->len = 0; + buffer->pos = 0; + buffer->sizeIncrement = 0; + buffer->fixed = GHTTPTrue; + buffer->dontFree = GHTTPTrue; + buffer->readOnly = GHTTPFalse; + + // Start with an empty string. + ////////////////////////////// + *buffer->data = '\0'; + + return GHTTPTrue; +} + +GHTTPBool +ghiInitReadOnlyBuffer(struct GHIConnection* connection, // The connection. + GHIBuffer* buffer, // The buffer to init. + const char* userBuffer, // The user-buffer to use. + int size // The size of the buffer. +) { + assert(connection); + assert(buffer); + assert(userBuffer); + assert(size > 0); + + // Check args. + ////////////// + if (!connection) + return GHTTPFalse; + if (!buffer) + return GHTTPFalse; + if (!userBuffer) + return GHTTPFalse; + if (size <= 0) + return GHTTPFalse; + + // Init the struct. + /////////////////// + buffer->connection = connection; + buffer->data = (char*)userBuffer; // cast away const + buffer->size = size; + buffer->pos = 0; + buffer->sizeIncrement = 0; + buffer->fixed = GHTTPTrue; + buffer->dontFree = GHTTPTrue; + buffer->readOnly = GHTTPTrue; + + // Start with user supplied data + ////////////////////////////// + buffer->len = size; + buffer->len = size; + buffer->len = size; + buffer->len = size; + buffer->len = size; + + return GHTTPTrue; +} + +void ghiFreeBuffer(GHIBuffer* buffer) { + assert(buffer); + + // Check args. + ////////////// + if (!buffer) + return; + if (!buffer->data) + return; + + // Cleanup the struct. + ////////////////////// + if (!buffer->dontFree) + gsifree(buffer->data); + memset(buffer, 0, sizeof(GHIBuffer)); +} + +GHTTPBool ghiAppendDataToBuffer(GHIBuffer* buffer, const char* data, + int dataLen) { + GHTTPBool bResult; + int newLen; + + assert(buffer); + assert(data); + assert(dataLen >= 0); + + // Check args. + ////////////// + if (!buffer) + return GHTTPFalse; + if (!data) + return GHTTPFalse; + if (dataLen < 0) + return GHTTPFalse; + if (buffer->readOnly) + return GHTTPFalse; + + // Get the string length if needed. + /////////////////////////////////// + if (dataLen == 0) + dataLen = (int)strlen(data); + + // Get the new length. + ////////////////////// + newLen = (buffer->len + dataLen); + + // Make sure the array is big enough. + ///////////////////////////////////// + while (newLen >= buffer->size) { + // Check for a fixed buffer. + //////////////////////////// + if (buffer->fixed) { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPBufferOverflow; + return GHTTPFalse; + } + + bResult = ghiResizeBuffer(buffer, buffer->sizeIncrement); + if (!bResult) { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPOutOfMemory; + return GHTTPFalse; + } + } + + // Add the data. + //////////////// + memcpy(buffer->data + buffer->len, data, (unsigned int)dataLen); + buffer->len = newLen; + buffer->data[buffer->len] = '\0'; + return GHTTPTrue; +} + +// Use sparingly. This function wraps the data in an SSL record. +GHTTPBool ghiEncryptDataToBuffer(GHIBuffer* buffer, const char* data, + int dataLen) { + GHIEncryptionResult result; + int bufSpace = 0; + int pos = 0; + + assert(buffer); + assert(data); + assert(dataLen >= 0); + + // Check args. + ////////////// + if (!buffer) + return GHTTPFalse; + if (!data) + return GHTTPFalse; + if (dataLen < 0) + return GHTTPFalse; + if (buffer->readOnly) + return GHTTPFalse; + + // Switch to plain text append when not using SSL + if (buffer->connection->encryptor.mEngine == GHTTPEncryptionEngine_None || + buffer->connection->encryptor.mSessionEstablished == GHTTPFalse) { + return ghiAppendDataToBuffer(buffer, data, dataLen); + } + + // Get the string length if needed. + /////////////////////////////////// + if (dataLen == 0) + dataLen = (int)strlen(data); + if (dataLen == 0) + return GHTTPTrue; // no data and strlen == 0 + bufSpace = buffer->size - buffer->len; + + do { + int fragmentLen = min(dataLen, GS_SSL_MAX_CONTENTLENGTH); + + // Call the encryptor function + // bufSize is reduced by the number of bytes written + result = buffer->connection->encryptor.mEncryptFunc( + buffer->connection, &buffer->connection->encryptor, &data[pos], dataLen, + &buffer->data[buffer->len], &bufSpace); + if (result == GHIEncryptionResult_BufferTooSmall) { + if (ghiResizeBuffer(buffer, buffer->sizeIncrement) == GHTTPFalse) + return GHTTPFalse; + bufSpace = buffer->size - buffer->len; + } else if (result == GHIEncryptionResult_Success) { + // update data and buffer positions + pos += fragmentLen; + buffer->len = buffer->size - bufSpace; + } else { + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "ghiEncryptDataToBuffer encountered unhandled return code: %d\r\n", + result); + return GHTTPFalse; + } + } while (pos < dataLen); + + return GHTTPTrue; +} + +GHTTPBool ghiAppendHeaderToBuffer(GHIBuffer* buffer, const char* name, + const char* value) { + if (!ghiAppendDataToBuffer(buffer, name, 0)) + return GHTTPFalse; + if (!ghiAppendDataToBuffer(buffer, ": ", 2)) + return GHTTPFalse; + if (!ghiAppendDataToBuffer(buffer, value, 0)) + return GHTTPFalse; + if (!ghiAppendDataToBuffer(buffer, CRLF, 2)) + return GHTTPFalse; + + return GHTTPTrue; +} + +GHTTPBool ghiAppendCharToBuffer(GHIBuffer* buffer, int c) { + GHTTPBool bResult; + assert(buffer); + + // Check args. + ////////////// + if (!buffer) + return GHTTPFalse; + if (buffer->readOnly) + return GHTTPFalse; + + // Make sure the array is big enough. + ///////////////////////////////////// + if ((buffer->len + 1) >= buffer->size) { + // Check for a fixed buffer. + //////////////////////////// + if (buffer->fixed) { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPBufferOverflow; + return GHTTPFalse; + } + + bResult = ghiResizeBuffer(buffer, buffer->sizeIncrement); + if (!bResult) { + buffer->connection->completed = GHTTPTrue; + buffer->connection->result = GHTTPOutOfMemory; + return GHTTPFalse; + } + } + + // Add the char. + //////////////// + buffer->data[buffer->len] = (char)(c & 0xFF); + buffer->len++; + buffer->data[buffer->len] = '\0'; + + return GHTTPTrue; +} + +GHTTPBool ghiAppendIntToBuffer(GHIBuffer* buffer, int i) { + char intValue[16]; + + sprintf(intValue, "%d", i); + + return ghiAppendDataToBuffer(buffer, intValue, 0); +} + +void ghiResetBuffer(GHIBuffer* buffer) { + assert(buffer); + + buffer->len = 0; + buffer->pos = 0; + + // Start with an empty string. + ////////////////////////////// + if (!buffer->readOnly) + *buffer->data = '\0'; +} diff --git a/source/gamespy/ghttp/ghttpBuffer.h b/source/gamespy/ghttp/ghttpBuffer.h new file mode 100644 index 000000000..c6513aecf --- /dev/null +++ b/source/gamespy/ghttp/ghttpBuffer.h @@ -0,0 +1,141 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +// clang-format off +#include "ghttpMain.h" +#include "ghttpEncryption.h" +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +// A data buffer. +///////////////// +typedef struct GHIBuffer { + struct GHIConnection* connection; // The connection. + char* data; // The actual bytes of data. + int size; // The number of bytes allocated for data. + int len; // The number of actual data bytes filled in. + int pos; // A marker to keep track of position. + int sizeIncrement; // How much to increment the buffer by when needed. + GHTTPBool fixed; // If true, don't resize the buffer. + GHTTPBool dontFree; // Don't free the data when the buffer is cleaned up. + GHTTPBool readOnly; // Read Only, write operations will fail +} GHIBuffer; + +// Initializes a buffer and allocates the initial data bytes. +// The initialSize and sizeIncrement must both be >0. +///////////////////////////////////////////////////////////// +GHTTPBool ghiInitBuffer(struct GHIConnection* connection, // The connection. + GHIBuffer* buffer, // The buffer to init. + int initialSize, // The initial size of the buffer. + int sizeIncrement // The size increment for the buffer. +); + +// Initializes a fixed-size buffer. This will not get resized. +/////////////////////////////////////////////////////////////// +GHTTPBool +ghiInitFixedBuffer(struct GHIConnection* connection, // The connection. + GHIBuffer* buffer, // The buffer to init. + char* userBuffer, // The user-buffer to use. + int size // The size of the buffer. +); + +// Initializes a read-only fixed-size buffer. This will not get resized. +/////////////////////////////////////////////////////////////// +GHTTPBool +ghiInitReadOnlyBuffer(struct GHIConnection* connection, // The connection. + GHIBuffer* buffer, // The buffer to init. + const char* userBuffer, // The user-buffer to use. + int size // The size of the buffer. +); + +// Free's a buffer's allocated memory (does +// not free the actual GHIBuffer structure). +//////////////////////////////////////////// +void ghiFreeBuffer(GHIBuffer* buffer); + +// Appends data to the buffer. +// If data is a NUL-terminated string, 0 can be +// used for dataLen to use the length of the string. +//////////////////////////////////////////////////// +GHTTPBool +ghiAppendDataToBuffer(GHIBuffer* buffer, // The buffer to append to. + const char* data, // The data to append. + int dataLen // The number of bytes of data to append, 0 + // for NUL-terminated string. +); + +// Appends data to the buffer, wrapped in an SSL record +// If data is a NUL-terminated string, 0 can be +// used for dataLen to use the length of the string. +// Encryption has some size overhead, so call this sparingly. +//////////////////////////////////////////////////// +GHTTPBool +ghiEncryptDataToBuffer(GHIBuffer* buffer, // The buffer to append to. + const char* data, // The data to append. + int dataLen // The number of bytes of data to append, 0 + // for NUL-terminated string. +); + +// Appends a header to the buffer. +// Both the name and value must be NUL-terminated. +// The header will be added to the buffer as: +// : \n +////////////////////////////////////////////////// +GHTTPBool ghiAppendHeaderToBuffer(GHIBuffer* buffer, // The buffer to append to. + const char* name, // The name of the header. + const char* value // The value of the header. +); + +// Appends a single character to the buffer. +//////////////////////////////////////////// +GHTTPBool ghiAppendCharToBuffer(GHIBuffer* buffer, // The buffer to append to. + int c // The char to append. +); + +// Read data from a buffer +GHTTPBool ghiReadDataFromBuffer( + GHIBuffer* bufferIn, // the GHIBuffer to read from + char bufferOut[], // the raw buffer to write to + int* len // max number of bytes to append, becomes actual length written +); + +// Read a fixed number of bytes from a buffer +GHTTPBool ghiReadDataFromBufferFixed(GHIBuffer* bufferIn, char bufferOut[], + int len); + +// Converts the int to a string and appends it to the buffer. +///////////////////////////////////////////////////////////// +GHTTPBool ghiAppendIntToBuffer(GHIBuffer* buffer, // The buffer to append to. + int i // The int to append. +); + +// Resets a buffer. +// Does this by setting both len and pos to 0. +////////////////////////////////////////////// +void ghiResetBuffer(GHIBuffer* buffer // The buffer to reset. +); + +// Sends as much buffer data as it can. +// Returns false if there was an error. +/////////////////////////////////////// +GHTTPBool ghiSendBufferedData(struct GHIConnection* connection); + +// Increases the size of a buffer. +// This happens automatically when using the ghiAppend* functions +GHTTPBool ghiResizeBuffer(GHIBuffer* buffer, int sizeIncrement); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttpCallbacks.c b/source/gamespy/ghttp/ghttpCallbacks.c new file mode 100644 index 000000000..37625fe50 --- /dev/null +++ b/source/gamespy/ghttp/ghttpCallbacks.c @@ -0,0 +1,86 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpCallbacks.h" +#include "ghttpPost.h" + +void ghiCallCompletedCallback(GHIConnection* connection) { + GHTTPBool freeBuffer; + char* buffer; + GHTTPByteCount bufferLen; + + assert(connection); + +#ifdef GSI_COMMON_DEBUG + if (connection->result != GHTTPSuccess) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, + GSIDebugLevel_WarmError, "Socket Error: %d\n", + connection->socketError); + } +#endif + + // Check for no callback. + ///////////////////////// + if (!connection->completedCallback) + return; + + // Figure out the buffer/bufferLen parameters. + ////////////////////////////////////////////// + if (connection->type == GHIGET) { + buffer = connection->getFileBuffer.data; + } else { + buffer = NULL; + } + bufferLen = connection->fileBytesReceived; + + // Call the callback. + ///////////////////// + freeBuffer = connection->completedCallback( + connection->request, connection->result, buffer, bufferLen, + connection->callbackParam); + + // Check for gsifree. + ////////////////// + if (buffer && !freeBuffer) + connection->getFileBuffer.dontFree = GHTTPTrue; +} + +void ghiCallProgressCallback(GHIConnection* connection, const char* buffer, + GHTTPByteCount bufferLen) { + assert(connection); + + // Check for no callback. + ///////////////////////// + if (!connection->progressCallback) + return; + + // Call the callback. + ///////////////////// + connection->progressCallback(connection->request, connection->state, buffer, + bufferLen, connection->fileBytesReceived, + connection->totalSize, + connection->callbackParam); +} + +void ghiCallPostCallback(GHIConnection* connection) { + assert(connection); + + // Check for no callback. + ///////////////////////// + if (!connection->postingState.callback) + return; + + // Call the callback. + ///////////////////// + connection->postingState.callback( + connection->request, connection->postingState.bytesPosted, + connection->postingState.totalBytes, connection->postingState.index, + ArrayLength(connection->postingState.states), connection->callbackParam); +} diff --git a/source/gamespy/ghttp/ghttpCallbacks.h b/source/gamespy/ghttp/ghttpCallbacks.h new file mode 100644 index 000000000..b52756ac4 --- /dev/null +++ b/source/gamespy/ghttp/ghttpCallbacks.h @@ -0,0 +1,37 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +// clang-format off +#include "ghttpMain.h" +#include "ghttpConnection.h" +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +// Call the completed callback for this connection. +/////////////////////////////////////////////////// +void ghiCallCompletedCallback(GHIConnection* connection); + +// Call the progress callback for this connection. +////////////////////////////////////////////////// +void ghiCallProgressCallback(GHIConnection* connection, const char* buffer, + GHTTPByteCount bufferLen); + +// Call the post callback for this connection. +////////////////////////////////////////////// +void ghiCallPostCallback(GHIConnection* connection); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttpCommon.c b/source/gamespy/ghttp/ghttpCommon.c new file mode 100644 index 000000000..2ea97c77d --- /dev/null +++ b/source/gamespy/ghttp/ghttpCommon.c @@ -0,0 +1,473 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpCommon.h" + +// Disable compiler warnings for issues that are unavoidable. +///////////////////////////////////////////////////////////// +#if defined(_MSC_VER) // DevStudio +// Level4, "conditional expression is constant". +// Occurs with use of the MS provided macro FD_SET +#pragma warning(disable : 4127) +#endif // _MSC_VER + +// Proxy server. +//////////////// +char* ghiProxyAddress; +unsigned short ghiProxyPort; + +// Throttle settings. +///////////////////// +int ghiThrottleBufferSize = 125; +gsi_time ghiThrottleTimeDelay = 250; + +// Number of connections +///////////////////// +extern int ghiNumConnections; + +// Creates the ghttp lock. +////////////////////////// +void ghiCreateLock(void) {} + +// Frees the ghttp lock. +//////////////////////// +void ghiFreeLock(void) {} + +// Locks the ghttp lock. +//////////////////////// +void ghiLock(void) {} + +// Unlocks the ghttp lock. +////////////////////////// +void ghiUnlock(void) {} + +// Reads encrypted data from decodeBuffer +// Appends decrypted data to recvBuffer +// Returns GHTTPFalse if there was a fatal error +//////////////////////////////////////////////// +GHTTPBool ghiDecryptReceivedData(struct GHIConnection* connection) { + // Decrypt data from decodeBuffer to recvBuffer + GHIEncryptionResult aResult = GHIEncryptionResult_None; + + // data to be decrypted + char* aReadPos = NULL; + char* aWritePos = NULL; + int aReadLen = 0; + int aWriteLen = 0; + + do { + // Call the decryption func + do { + aReadPos = connection->decodeBuffer.data + connection->decodeBuffer.pos; + aReadLen = connection->decodeBuffer.len - connection->decodeBuffer.pos; + aWritePos = connection->recvBuffer.data + connection->recvBuffer.len; + aWriteLen = + connection->recvBuffer.size - + connection->recvBuffer.len; // the amount of room in recvbuffer + + aResult = (connection->encryptor.mDecryptFunc)( + connection, &connection->encryptor, aReadPos, &aReadLen, aWritePos, + &aWriteLen); + if (aResult == GHIEncryptionResult_BufferTooSmall) { + // Make some more room + if (GHTTPFalse == ghiResizeBuffer(&connection->recvBuffer, + connection->recvBuffer.sizeIncrement)) + return GHTTPFalse; // error + } else if (aResult == GHIEncryptionResult_Error) { + return GHTTPFalse; + } + } while (aResult == GHIEncryptionResult_BufferTooSmall && aWriteLen == 0); + + // Adjust GHIBuffer sizes so they account for transfered data + if (aReadLen > connection->decodeBuffer.len) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_HotError, + "ghiDecryptReceivedData read past the end of " + "connection->decodeBuffer! (%d\\%d bytes)\r\n", + aReadLen, connection->decodeBuffer.len); + return GHTTPFalse; + } + + connection->decodeBuffer.pos += aReadLen; + connection->recvBuffer.len += aWriteLen; + + } while (aWriteLen > 0); + + // Discard data from the decodedBuffer in chunks + if (connection->decodeBuffer.pos > 0xFF) { + int bytesToKeep = + connection->decodeBuffer.len - connection->decodeBuffer.pos; + if (bytesToKeep == 0) + ghiResetBuffer(&connection->decodeBuffer); + else { + memmove(connection->decodeBuffer.data, + connection->decodeBuffer.data + connection->decodeBuffer.pos, + (size_t)bytesToKeep); + connection->decodeBuffer.pos = 0; + connection->decodeBuffer.len = bytesToKeep; + } + } + + return GHTTPTrue; +} + +// Receive some data. +///////////////////// +#ifdef NON_MATCHING +GHIRecvResult ghiDoReceive(GHIConnection* connection, char buffer[], + int* bufferLen) { + int rcode; + int socketError; + int len; + + // How much to try and receive. + /////////////////////////////// + len = (*bufferLen - 1); + + // Are we throttled? + //////////////////// + if (connection->throttle) { + unsigned long now; + + // Don't receive too often. + /////////////////////////// + now = current_time(); + if (now < (connection->lastThrottleRecv + ghiThrottleTimeDelay)) + return GHINoData; + + // Update the receive time. + /////////////////////////// + connection->lastThrottleRecv = (unsigned int)now; + + // Don't receive too much. + ////////////////////////// + len = min(len, ghiThrottleBufferSize); + } + + // Receive some data. + ///////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mSessionEstablished == GHTTPTrue && + connection->encryptor.mEncryptOnSend == GHTTPTrue) { + GHIEncryptionResult result; + int recvLength = len; + + result = ghiEncryptorSslDecryptRecv(connection, &connection->encryptor, + buffer, &recvLength); + if (result == GHIEncryptionResult_Success) + rcode = recvLength; + else + rcode = -1; // signal termination of connection + } else { + rcode = recv(connection->socket, buffer, len, 0); + } + + // There was an error. + ////////////////////// + if (gsiSocketIsError(rcode)) { + // Get the error code. + ////////////////////// + socketError = GOAGetLastError(connection->socket); + + // Check for a closed connection. + ///////////////////////////////// + if (socketError == WSAENOTCONN) { + connection->connectionClosed = GHTTPTrue; + return GHIConnClosed; + } + + // Check for nothing waiting. + ///////////////////////////// + if ((socketError == WSAEWOULDBLOCK) || (socketError == WSAEINPROGRESS) || + (socketError == WSAETIMEDOUT)) + return GHINoData; + + // There was a real error. + ////////////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = socketError; + connection->connectionClosed = GHTTPTrue; + + return GHIError; + } + + // The connection was closed. + ///////////////////////////// + if (rcode == 0) { + connection->connectionClosed = GHTTPTrue; + return GHIConnClosed; + } + + // Cap the buffer. + ////////////////// + buffer[rcode] = '\0'; + *bufferLen = rcode; + + // gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_RawDump, + // "Received %d bytes\n", rcode); + + // Notify app. + ////////////// + return GHIRecvData; +} +#else +asm GHIRecvResult ghiDoReceive(GHIConnection* connection, char buffer[], + int* bufferLen) { + nofralloc; + stwu r1, -32(r1); + mflr r0; + stw r0, 36(r1); + stw r31, 28(r1); + mr r31, r3; + stw r30, 24(r1); + stw r29, 20(r1); + mr r29, r5; + stw r28, 16(r1); + mr r28, r4; + lwz r0, 344(r3); + lwz r3, 0(r5); + cmpwi r0, 0x0; + addi r30, r3, -0x1; + beq loc6; +loc1: + bl current_time; + lwz r4, 348(r31); + lwz r0, -30244(r13); + add r0, r4, r0; + cmplw r3, r0; + bge loc3; +loc2: + li r3, 0x1; + b loc25; +loc3: + stw r3, 348(r31); + lwz r0, -30248(r13); + cmpw r30, r0; + bge loc5; +loc4: + mr r0, r30; +loc5: + mr r30, r0; +loc6: + lwz r0, 404(r31); + cmpwi r0, 0x0; + beq loc14; +loc7: + lwz r0, 416(r31); + cmpwi r0, 0x1; + bne loc14; +loc8: + lwz r0, 424(r31); + cmpwi r0, 0x1; + bne loc14; +loc9: + stw r30, 8(r1); + mr r3, r31; + mr r5, r28; + addi r4, r31, 0x190; + addi r6, r1, 0x8; + bl ghiEncryptorSslDecryptRecv; + cmpwi r3, 0x1; + bne loc13; +loc10: + lwz r4, 8(r1); + cmpwi r4, -0x1; + bne loc22; +loc11: + li r3, 0x1; + b loc25; +loc12: + // ?????????????????????????????? + b loc22; +loc13: + li r5, 0x1; + li r4, 0x5; + li r0, 0x0; + stw r5, 288(r31); + li r3, 0x3; + stw r4, 60(r31); + stw r0, 80(r31); + stw r5, 340(r31); + b loc25; +loc14: + lwz r3, 76(r31); + mr r4, r28; + mr r5, r30; + li r6, 0x0; + bl recv; + cmpwi r3, -0x1; + mr r4, r3; + bne loc22; +loc15: + lwz r3, 76(r31); + bl GOAGetLastError; + cmpwi r3, -0x38; + bne loc17; +loc16: + li r0, 0x1; + li r3, 0x2; + stw r0, 340(r31); + b loc25; +loc17: + cmpwi r3, -0x6; + beq loc20; +loc18: + cmpwi r3, -0x1a; + beq loc20; +loc19: + cmpwi r3, -0x4c; + bne loc21; +loc20: + li r3, 0x1; + b loc25; +loc21: + li r4, 0x1; + li r0, 0x5; + stw r3, 80(r31); + li r3, 0x3; + stw r4, 288(r31); + stw r0, 60(r31); + stw r4, 340(r31); + b loc25; +loc22: + cmpwi r4, 0x0; + bne loc24; +loc23: + li r0, 0x1; + li r3, 0x2; + stw r0, 340(r31); + b loc25; +loc24: + li r0, 0x0; + li r3, 0x0; + stbx r0, r28, r4; + stw r4, 0(r29); +loc25: + lwz r0, 36(r1); + lwz r31, 28(r1); + lwz r30, 24(r1); + lwz r29, 20(r1); + lwz r28, 16(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} +#endif + +asm int ghiDoSend(struct GHIConnection* connection, const char* buffer, + int len) { + stwu r1, -32(r1); + mflr r0; + cmpwi r4, 0x0; + mr r6, r5; + stw r0, 36(r1); + stw r31, 28(r1); + stw r30, 24(r1); + mr r30, r3; + beq loc2; +loc1: + cmpwi r5, 0x0; + bne loc3; +loc2: + li r3, 0x0; + b loc20; +loc3: + lwz r0, 404(r3); + cmpwi r0, 0x0; + beq loc11; +loc4: + lwz r0, 416(r3); + cmpwi r0, 0x1; + bne loc11; +loc5: + lwz r0, 424(r3); + cmpwi r0, 0x1; + bne loc11; +loc6: + li r31, 0x0; + mr r5, r4; + stw r31, 8(r1); + addi r4, r3, 0x190; + addi r7, r1, 0x8; + bl ghiEncryptorSslEncryptSend; + cmpwi r3, 0x1; + bne loc10; +loc7: + lwz r3, 8(r1); + cmpwi r3, -0x1; + bne loc17; +loc8: + li r3, -0x2; + b loc20; +loc9: + b loc17; +loc10: + li r3, 0x1; + li r0, 0x5; + stw r3, 288(r30); + li r3, -0x1; + stw r0, 60(r30); + stw r31, 80(r30); + b loc20; +loc11: + lwz r3, 76(r3); + mr r5, r6; + li r6, 0x0; + bl send; + cmpwi r3, -0x1; + bne loc17; +loc12: + lwz r3, 76(r30); + bl GOAGetLastError; + cmpwi r3, -0x6; + beq loc15; +loc13: + cmpwi r3, -0x1a; + beq loc15; +loc14: + cmpwi r3, -0x4c; + bne loc16; +loc15: + li r3, 0x0; + b loc20; +loc16: + li r4, 0x1; + li r0, 0x5; + stw r3, 80(r30); + li r3, -0x1; + stw r4, 288(r30); + stw r0, 60(r30); + b loc20; +loc17: + lwz r0, 16(r30); + cmpwi r0, 0x6; + bne loc20; +loc18: + lwz r0, 380(r30); + cmpwi r0, 0x0; + bne loc20; +loc19: + lwz r0, 364(r30); + add r0, r0, r3; + stw r0, 364(r30); +loc20: + lwz r0, 36(r1); + lwz r31, 28(r1); + lwz r30, 24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Re-enable previously disabled compiler warnings +/////////////////////////////////////////////////// +#if defined(_MSC_VER) +#pragma warning(default : 4127) +#endif // _MSC_VER diff --git a/source/gamespy/ghttp/ghttpCommon.h b/source/gamespy/ghttp/ghttpCommon.h new file mode 100644 index 000000000..e14337862 --- /dev/null +++ b/source/gamespy/ghttp/ghttpCommon.h @@ -0,0 +1,119 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "ghttp.h" +#include "ghttpConnection.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// HTTP Line-terminator. +//////////////////////// +#define CRLF "\xD\xA" + +// HTTP URL Encoding +//////////////////////// +#define GHI_LEGAL_URLENCODED_CHARS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@-.*" +#define GHI_DIGITS "0123456789ABCDEF" + +// Default HTTP port. +///////////////////// +#define GHI_DEFAULT_PORT 80 +#define GHI_DEFAULT_SECURE_PORT 443 +#define GHI_DEFAULT_THROTTLE_BUFFER_SIZE 125 +#define GHI_DEFAULT_THROTTLE_TIME_DELAY 250 + +// Proxy server. +//////////////// +extern char* ghiProxyAddress; +extern unsigned short ghiProxyPort; + +// Throttle settings. +///////////////////// +extern int ghiThrottleBufferSize; +extern gsi_time ghiThrottleTimeDelay; + +// Our thread lock. +/////////////////// +void ghiCreateLock(void); +void ghiFreeLock(void); +void ghiLock(void); +void ghiUnlock(void); + +// Do logging. +////////////// +#ifdef HTTP_LOG +void ghiLogToFile(const char* buffer, int len, const char* fileName); +#define ghiLogRequest(b, c) ghiLogToFile(b, c, "request.log"); +#define ghiLogResponse(b, c) ghiLogToFile(b, c, "response.log"); +#define ghiLogPost(b, c) ghiLogToFile(b, c, "post.log"); +#else +#define ghiLogRequest(b, c) +#define ghiLogResponse(b, c) +#define ghiLogPost(b, c) +#endif + +// Possible results from ghiDoReceive. +////////////////////////////////////// +typedef enum { + GHIRecvData, // Data was received. + GHINoData, // No data was available. + GHIConnClosed, // The connection was closed. + GHIError // There was a socket error. +} GHIRecvResult; + +// Receive some data. +///////////////////// +GHIRecvResult ghiDoReceive(GHIConnection* connection, char buffer[], + int* bufferLen); + +// Do a send on the connection's socket. +// Returns number of bytes sent (0 or more). +// If error, returns (-1). +//////////////////////////////////////////// +int ghiDoSend(GHIConnection* connection, const char* buffer, int len); + +// Results for ghtTrySendThenBuffer. +//////////////////////////////////// +typedef enum { + GHITrySendError, // There was an error sending. + GHITrySendSent, // Everything was sent. + GHITrySendBuffered // Some or all of the data was buffered. +} GHITrySendResult; + +// Sends whatever it can on the socket. +// Buffers whatever can't be sent in the sendBuffer. +//////////////////////////////////////////////////// +GHITrySendResult ghiTrySendThenBuffer(GHIConnection* connection, + const char* buffer, int len); + +// Set the proxy server +//////////////////////// +GHTTPBool ghiSetProxy(const char* server); + +// Set the proxy server for a specific request +//////////////////////// +GHTTPBool ghiSetRequestProxy(GHTTPRequest request, const char* server); + +// Set the throttle settings. +///////////////////////////// +void ghiThrottleSettings(int bufferSize, gsi_time timeDelay); + +// Decrypt data from the decode buffer into the receive buffer. +/////////////////////////////////////////////////////////////// +GHTTPBool ghiDecryptReceivedData(struct GHIConnection* connection); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttpConnection.c b/source/gamespy/ghttp/ghttpConnection.c new file mode 100644 index 000000000..7de53b609 --- /dev/null +++ b/source/gamespy/ghttp/ghttpConnection.c @@ -0,0 +1,383 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpConnection.h" +#include "ghttpCommon.h" + +// Initial size and increment amount for the connections array. +/////////////////////////////////////////////////////////////// +#define CONNECTIONS_CHUNK_LEN 4 + +// An array of pointers to GHIConnection objects. +// A GHTTPRequest is an index into this array. +///////////////////////////////////////////////// +static GHIConnection** ghiConnections; +static int ghiConnectionsLen; +static int ghiNumConnections; +static int ghiNextUniqueID; + +// Finds a gsifree slot in the ghiConnections array. +// If there are no gsifree slots, the array size will be increased. +//////////////////////////////////////////////////////////////// +static int ghiFindFreeSlot(void) { + int i; + GHIConnection** tempPtr; + int oldLen; + int newLen; + + // Look for an open slot. + ///////////////////////// + for (i = 0; i < ghiConnectionsLen; i++) { + if (!ghiConnections[i]->inUse) + return i; + } + + assert(ghiNumConnections == ghiConnectionsLen); + + // Nothing found, resize the array. + /////////////////////////////////// + oldLen = ghiConnectionsLen; + newLen = (ghiConnectionsLen + CONNECTIONS_CHUNK_LEN); + tempPtr = (GHIConnection**)gsirealloc(ghiConnections, + sizeof(GHIConnection*) * newLen); + if (!tempPtr) + return -1; + ghiConnections = tempPtr; + + // Create the new connection objects. + ///////////////////////////////////// + for (i = oldLen; i < newLen; i++) { + ghiConnections[i] = (GHIConnection*)gsimalloc(sizeof(GHIConnection)); + if (!ghiConnections[i]) { + for (i--; i >= oldLen; i--) + gsifree(ghiConnections[i]); + return -1; + } + ghiConnections[i]->inUse = GHTTPFalse; + } + + // Update the length. + ///////////////////// + ghiConnectionsLen = newLen; + + return oldLen; +} + +GHIConnection* ghiNewConnection(void) { + int slot; + GHIConnection* connection; + GHTTPBool bResult; + + ghiLock(); + + // Find a gsifree slot. + //////////////////// + slot = ghiFindFreeSlot(); + if (slot == -1) { + ghiUnlock(); + return NULL; + } + + // Get a pointer to the object. + /////////////////////////////// + connection = ghiConnections[slot]; + + // Init the object. + /////////////////// + memset(connection, 0, sizeof(GHIConnection)); + connection->inUse = GHTTPTrue; + connection->request = (GHTTPRequest)slot; + connection->uniqueID = ghiNextUniqueID++; + connection->type = GHIGET; + connection->state = GHTTPSocketInit; + connection->URL = NULL; + connection->serverAddress = NULL; + connection->serverIP = INADDR_ANY; + connection->serverPort = (unsigned short)0; + connection->requestPath = NULL; + connection->sendHeaders = NULL; + connection->saveFile = NULL; + connection->blocking = GHTTPFalse; + connection->persistConnection = GHTTPFalse; + connection->result = GHTTPSuccess; + connection->progressCallback = NULL; + connection->completedCallback = NULL; + connection->callbackParam = NULL; + connection->socket = INVALID_SOCKET; + connection->socketError = 0; + connection->userBufferSupplied = GHTTPFalse; + connection->statusMajorVersion = 0; + connection->statusMinorVersion = 0; + connection->statusCode = 0; + connection->statusStringIndex = 0; + connection->headerStringIndex = 0; + connection->completed = GHTTPFalse; + connection->fileBytesReceived = 0; + connection->totalSize = -1; + connection->redirectURL = NULL; + connection->redirectCount = 0; + connection->chunkedTransfer = GHTTPFalse; + connection->processing = GHTTPFalse; + connection->throttle = GHTTPFalse; + connection->lastThrottleRecv = 0; + connection->post = NULL; + connection->maxRecvTime = 500; // Prevent blocking in async mode with systems + // that never generate WSAEWOULDBLOCK + connection->proxyOverridePort = GHI_DEFAULT_PORT; + connection->proxyOverrideServer = NULL; + connection->encryptor.mInterface = NULL; + +// handle used for asynch DNS lookups +#if !defined(GSI_NO_THREADS) + connection->handle = NULL; +#endif + + bResult = ghiInitBuffer(connection, &connection->sendBuffer, + SEND_BUFFER_INITIAL_SIZE, SEND_BUFFER_INCREMENT_SIZE); + if (bResult) + bResult = + ghiInitBuffer(connection, &connection->encodeBuffer, + ENCODE_BUFFER_INITIAL_SIZE, ENCODE_BUFFER_INCREMENT_SIZE); + if (bResult) + bResult = + ghiInitBuffer(connection, &connection->recvBuffer, + RECV_BUFFER_INITIAL_SIZE, RECV_BUFFER_INCREMENT_SIZE); + if (bResult) + bResult = + ghiInitBuffer(connection, &connection->decodeBuffer, + DECODE_BUFFER_INITIAL_SIZE, DECODE_BUFFER_INCREMENT_SIZE); + + if (!bResult) { + ghiFreeConnection(connection); + ghiUnlock(); + return NULL; + } + + // One more connection. + /////////////////////// + ghiNumConnections++; + + ghiUnlock(); + + return connection; +} + +GHTTPBool ghiFreeConnection(GHIConnection* connection) { + assert(connection); + assert(connection->request >= 0); + assert(connection->request < ghiConnectionsLen); + assert(connection->inUse); + + // Check args. + ////////////// + if (!connection) + return GHTTPFalse; + if (!connection->inUse) + return GHTTPFalse; + if (connection->request < 0) + return GHTTPFalse; + if (connection->request >= ghiConnectionsLen) + return GHTTPFalse; + + ghiLock(); + + // Free data. + ///////////// + gsifree(connection->URL); + gsifree(connection->serverAddress); + gsifree(connection->requestPath); + gsifree(connection->sendHeaders); + gsifree(connection->redirectURL); + gsifree(connection->proxyOverrideServer); +#ifndef NOFILE + if (connection->saveFile) + fclose(connection->saveFile); +#endif + if (connection->socket != INVALID_SOCKET) { + shutdown(connection->socket, 2); + closesocket(connection->socket); + } + ghiFreeBuffer(&connection->sendBuffer); + ghiFreeBuffer(&connection->encodeBuffer); + ghiFreeBuffer(&connection->recvBuffer); + ghiFreeBuffer(&connection->decodeBuffer); + ghiFreeBuffer(&connection->getFileBuffer); + if (connection->postingState.states) + ghiPostCleanupState(connection); + + // Check for an auto-free post. + /////////////////////////////// + if (connection->post && ghiIsPostAutoFree(connection->post)) { + ghiFreePost(connection->post); + connection->post = NULL; + } + + // Check for an encryptor + if (connection->encryptor.mInitialized != GHTTPFalse) { + if (connection->encryptor.mCleanupFunc) + (connection->encryptor.mCleanupFunc)(connection, &connection->encryptor); + connection->encryptor.mInitialized = GHTTPFalse; + } + + // Free the slot. + ///////////////// + connection->inUse = GHTTPFalse; + + // One less connection. + /////////////////////// + ghiNumConnections--; + + ghiUnlock(); + + return GHTTPTrue; +} + +GHIConnection* ghiRequestToConnection(GHTTPRequest request) { + GHIConnection* connection; + + assert(request >= 0); + assert(request < ghiConnectionsLen); + + ghiLock(); + + // Check args. + ////////////// + if ((request < 0) || (request >= ghiConnectionsLen)) { + ghiUnlock(); + return NULL; + } + + connection = ghiConnections[request]; + + // Check for not in use. + //////////////////////// + if (!connection->inUse) + connection = NULL; + + ghiUnlock(); + + return connection; +} + +void ghiEnumConnections(GHTTPBool (*callback)(GHIConnection*)) { + int i; + + // Check for no connections. + //////////////////////////// + if (ghiNumConnections <= 0) + return; + + ghiLock(); + for (i = 0; i < ghiConnectionsLen; i++) + if (ghiConnections[i]->inUse) + callback(ghiConnections[i]); + ghiUnlock(); +} + +void ghiRedirectConnection(GHIConnection* connection) { + assert(connection); + assert(connection->redirectURL); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Redirecting Connection\n"); + + // Reset state. + /////////////// + connection->state = GHTTPSocketInit; + + // Cancel and free asychronous lookup if it has not already been done + ///////////////////////////////////////////////////////////////////// + if (connection->handle) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Cancelling Thread and freeing memory\n"); + gsiCancelResolvingHostname(connection->handle); + gsifree(connection->handle); + connection->handle = NULL; + } + + // New URL. + /////////// + gsifree(connection->URL); + connection->URL = connection->redirectURL; + connection->redirectURL = NULL; + + // Reset stuff parsed from the URL. + /////////////////////////////////// + gsifree(connection->serverAddress); + connection->serverAddress = NULL; + connection->serverIP = 0; + connection->serverPort = 0; + gsifree(connection->requestPath); + connection->requestPath = NULL; + + // Close the socket. + //////////////////// + shutdown(connection->socket, 2); + closesocket(connection->socket); + connection->socket = INVALID_SOCKET; + + // Reset buffers. + ///////////////// + ghiResetBuffer(&connection->sendBuffer); + ghiResetBuffer(&connection->encodeBuffer); + ghiResetBuffer(&connection->recvBuffer); + ghiResetBuffer(&connection->decodeBuffer); + + // Reset status. + //////////////// + connection->statusMajorVersion = 0; + connection->statusMinorVersion = 0; + connection->statusCode = 0; + connection->statusStringIndex = 0; + + connection->headerStringIndex = 0; + + // The connection isn't closed. + /////////////////////////////// + connection->connectionClosed = GHTTPFalse; + + // Check for an encryptor + if (connection->encryptor.mInitialized != GHTTPFalse) { + // cleanup the encryptor + if (connection->encryptor.mCleanupFunc) + (connection->encryptor.mCleanupFunc)(connection, &connection->encryptor); + connection->encryptor.mInitialized = GHTTPFalse; + + // if the redirect isn't secure, clear it + if (strncmp("https://", connection->URL, 8) != 0) { + connection->encryptor.mEngine = GHTTPEncryptionEngine_None; + connection->encryptor.mInterface = NULL; + } + } + + // One more redirect. + ///////////////////// + connection->redirectCount++; +} + +void ghiCleanupConnections(void) { + int i; + + if (!ghiConnections) + return; + + // Cleanup all running connections. + /////////////////////////////////// + ghiEnumConnections(ghiFreeConnection); + + // Cleanup the connection states. + ///////////////////////////////// + for (i = 0; i < ghiConnectionsLen; i++) + gsifree(ghiConnections[i]); + gsifree(ghiConnections); + ghiConnections = NULL; + ghiConnectionsLen = 0; + ghiNumConnections = 0; +} diff --git a/source/gamespy/ghttp/ghttpConnection.h b/source/gamespy/ghttp/ghttpConnection.h new file mode 100644 index 000000000..dfa99b946 --- /dev/null +++ b/source/gamespy/ghttp/ghttpConnection.h @@ -0,0 +1,209 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "ghttpBuffer.h" +#include "ghttpEncryption.h" +#include "ghttpMain.h" +#include "ghttpPost.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Initial size and increment amount for the send buffer. +///////////////////////////////////////////////////////// +#define SEND_BUFFER_INITIAL_SIZE (2 * 1024) +#define SEND_BUFFER_INCREMENT_SIZE (4 * 1024) + +// Initial size and increment amount for the recv buffer. +///////////////////////////////////////////////////////////////// +#define RECV_BUFFER_INITIAL_SIZE (2 * 1024) +#define RECV_BUFFER_INCREMENT_SIZE (2 * 1024) + +// Initial size and increment amount for the get file buffer. +///////////////////////////////////////////////////////////// +#define GET_FILE_BUFFER_INITIAL_SIZE (2 * 1024) +#define GET_FILE_BUFFER_INCREMENT_SIZE (2 * 1024) + +// Initial size and increment amount for the ssl encoding buffer. +///////////////////////////////////////////////////////////////// +#define ENCODE_BUFFER_INITIAL_SIZE (2 * 1024) +#define ENCODE_BUFFER_INCREMENT_SIZE (1 * 1024) + +// Initial size and increment amount for the ssl decoding buffer. +///////////////////////////////////////////////////////////////// +#define DECODE_BUFFER_INITIAL_SIZE (2 * 1024) +#define DECODE_BUFFER_INCREMENT_SIZE (1 * 1024) + +// The size of the buffer for chunk headers (NOT including the NUL). +//////////////////////////////////////////////////////////////////// +#define CHUNK_HEADER_SIZE 10 + +// The type of request made. +//////////////////////////// +typedef enum { + GHIGET, // Buffer the file. + GHISAVE, // Save the file to disk. + GHISTREAM, // Stream the file. + GHIHEAD, // Get just the headers for a request. + GHIPOST // Only posting data (all types can post). +} GHIRequestType; + +// Chunk-reading states. +//////////////////////// +typedef enum { + CRHeader, // Reading a chunk header. + CRChunk, // Reading chunk (actual content). + CRCRLF, // Reading the CRLF at the end of a chunk. + CRFooter // Reading the footer at the end of the file (chunk with size of 0). +} CRState; + +// Protocol. +//////////// +typedef enum { GHIHttp, GHIHttps } GHIProtocol; + +// This is the data for a single http connection. +///////////////////////////////////////////////// +typedef struct GHIConnection { + GHTTPBool inUse; // If true, this connection object is being used. + GHTTPRequest request; // This object's request index. + int uniqueID; // Every connection object has a unqiue ID. + + GHIRequestType type; // The type of request this connection is for. + + GHTTPState state; // The state of the request. + + char* URL; // The URL for the file. + char* serverAddress; // The address of the server as contained in the URL. + unsigned int serverIP; // The server's IP. + unsigned short serverPort; // The server's port. + char* requestPath; // The path as contained in the URL. + + GHIProtocol protocol; // Protocol used for this connection. + + char* sendHeaders; // Optional headers to pass with the request. + + FILE* saveFile; // If saving to disk, the file being saved to. + + GHTTPBool blocking; // Blocking flag. + + GHTTPBool + persistConnection; // If TRUE, Connection: close will not be sent in the + // headers and the connection will be left open + + GHTTPResult result; // The result of the request. + ghttpProgressCallback + progressCallback; // Called periodically with progress updates. + ghttpCompletedCallback + completedCallback; // Called when the file has been received. + void* callbackParam; // User-data to be passed to the callbacks. + + SOCKET socket; // The socket for this connection. + int socketError; // If there was a socket error, the last error code is stored + // here. + + GHIBuffer sendBuffer; // The buffer for outgoing data. + GHIBuffer encodeBuffer; // The buffer for outgoing data. (will be encrypted; + // only used with https) + GHIBuffer recvBuffer; // The buffer for incoming data. (plain text) + GHIBuffer decodeBuffer; // The buffer for incoming data. (encrypted)(only used + // with https) + + GHIBuffer getFileBuffer; // ghttpGetFile[Ex] uses this buffer (which may be + // user-supplied). + GHTTPBool userBufferSupplied; // True if a user buffer was supplied. + + int statusMajorVersion; // The major-version number from the server's + // response. + int statusMinorVersion; // The minor-version number from the server's + // response. + int statusCode; // The status-code from the server's response. + int statusStringIndex; // Index in the recvBuffer where the status string + // starts. + + int headerStringIndex; // Index in the recvBuffer where the headers begin + + GHTTPBool completed; // This connection is completed - call the callback and + // kill it. + + GHTTPByteCount fileBytesReceived; // Number of file bytes received. + GHTTPByteCount totalSize; // Total size of the file, -1 if unknown. + + char* redirectURL; // If this is not NULL, we need to redirect the download to + // this URL. + int redirectCount; // Number of redirections done for this request. + + GHTTPBool chunkedTransfer; // The body of the response is chunky + // ("Transfer-Encoding: chunked"). + char chunkHeader[CHUNK_HEADER_SIZE + + 1]; // Partial chunk headers are stored in here. + int chunkHeaderLen; // The number of bytes in chunkHeader. + int chunkBytesLeft; // Number of bytes left in the chunk (only valid for + // CRChunk). + CRState chunkReadingState; // Determines if a chunk header or chunk data is + // being read. + + GHTTPBool processing; // If true, being processed. Used to prevent recursive + // processing. + GHTTPBool connectionClosed; // If true, the connection has been closed + // (orderly or abortive) + + GHTTPBool throttle; // If true, throttle this connection. + gsi_time + lastThrottleRecv; // The last time we received on a throttled connection. + + GHTTPPost post; // If not NULL, a reference to a post object to upload with + // the request. + GHIPostingState postingState; // If posting, the state of the upload. + + gsi_time maxRecvTime; // Max time spent receiving per call to "Think" - + // Prevents blocking on ultrafast connections + char* proxyOverrideServer; // Allows use of a different proxy than the global + // proxy + unsigned short proxyOverridePort; + + struct GHIEncryptor encryptor; + + GSIResolveHostnameHandle handle; // handle used for asychronous DNS lookups +} GHIConnection; + +// Create a new connection object. +// Returns NULL on failure. +////////////////////////////////// +GHIConnection* ghiNewConnection(void); + +// Frees the connection object. +/////////////////////////////// +GHTTPBool ghiFreeConnection(GHIConnection* connection); + +// Returns the connection object for a request index. +// Returns NULL if the index is bad. +///////////////////////////////////////////////////// +GHIConnection* ghiRequestToConnection(GHTTPRequest request); + +// Calls the callback on each connection. +///////////////////////////////////////// +void ghiEnumConnections(GHTTPBool (*callback)(GHIConnection*)); + +// Redirects the given connection. +// Resets the connection to get the +// file at connection->redirectURL. +/////////////////////////////////// +void ghiRedirectConnection(GHIConnection* connection); + +// Kills all connections and frees up all memory. +///////////////////////////////////////////////// +void ghiCleanupConnections(void); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttpEncryption.c b/source/gamespy/ghttp/ghttpEncryption.c new file mode 100644 index 000000000..b596caa68 --- /dev/null +++ b/source/gamespy/ghttp/ghttpEncryption.c @@ -0,0 +1,412 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "ghttpCommon.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +GHTTPBool ghttpSetRequestEncryptionEngine(GHTTPRequest request, + GHTTPEncryptionEngine engine) { + GHIConnection* connection = ghiRequestToConnection(request); + if (!connection) + return GHTTPFalse; + + // Translate default into the actual engine name + // We don't want to set the engine value to "default" because + // we'd lose the ability to determine the engine name in other places + if (engine == GHTTPEncryptionEngine_Default) { + engine = GHTTPEncryptionEngine_RevoEx; + } + + // If the same engine has previously been set then we're done + if (connection->encryptor.mEngine == engine) + return GHTTPTrue; + + // If a different engine has previously been set then we're screwed + if (connection->encryptor.mInterface != NULL && + connection->encryptor.mEngine != engine) { + return GHTTPFalse; + } + + // If the URL is HTTPS but the engine is specific as NONE then we can't + // connect + if ((engine == GHTTPEncryptionEngine_None) && + (strncmp(connection->URL, "https://", 8) == 0)) + return GHTTPFalse; + + // Initialize the engine + connection->encryptor.mEngine = engine; + + if (engine == GHTTPEncryptionEngine_None) { + connection->encryptor.mInterface = NULL; + return GHTTPTrue; // this is the default, just return + } else { + // 02OCT07 BED: Design was changed to only allow one engine at a time + // Assert that the specified engine is the one supported + if (engine != GHTTPEncryptionEngine_Default) { + GS_ASSERT(engine == GHTTPEncryptionEngine_RevoEx); + } + + connection->encryptor.mInterface = NULL; + connection->encryptor.mInitFunc = ghiEncryptorSslInitFunc; + connection->encryptor.mStartFunc = ghiEncryptorSslStartFunc; + connection->encryptor.mCleanupFunc = ghiEncryptorSslCleanupFunc; + connection->encryptor.mEncryptFunc = ghiEncryptorSslEncryptFunc; + connection->encryptor.mDecryptFunc = ghiEncryptorSslDecryptFunc; + connection->encryptor.mInitialized = GHTTPFalse; + connection->encryptor.mSessionStarted = GHTTPFalse; + connection->encryptor.mSessionEstablished = GHTTPFalse; + connection->encryptor.mEncryptOnBuffer = GHTTPTrue; + connection->encryptor.mEncryptOnSend = GHTTPFalse; + connection->encryptor.mLibSendsHandshakeMessages = GHTTPFalse; + return GHTTPTrue; + } +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ********************* REVOEX SSL ENCRYPTION ENGINE ******************** // +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include + +typedef struct gsRevoExInterface { + int mId; + int mClientCertId; + int mRootCAId; + GHTTPBool mConnected; // means "connected to socket", not "connected to remote + // machine" +} gsRevoExInterface; + +// Init the engine +GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor) { + int i = 0; + gsRevoExInterface* sslInterface = NULL; + + // There is only one place where this function should be called, + // and it should check if the engine has been initialized + GS_ASSERT(theEncryptor->mInitialized == GHTTPFalse); + GS_ASSERT(theEncryptor->mInterface == NULL); + + // allocate the interface (need one per connection) + theEncryptor->mInterface = gsimalloc(sizeof(gsRevoExInterface)); + if (theEncryptor->mInterface == NULL) { + // memory allocation failed + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Memory, GSIDebugLevel_WarmError, + "Failed to allocate SSL interface (out of memory: %d bytes)\r\n", + sizeof(gsRevoExInterface)); + return GHIEncryptionResult_Error; + } + memset(theEncryptor->mInterface, 0, sizeof(gsRevoExInterface)); + sslInterface = (gsRevoExInterface*)theEncryptor->mInterface; + + { + int verifyOption = 0; + int rcode = 0; + + // verifyOption = SSL_VERIFY_COMMON_NAME | SSL_VERIFY_ROOT_CA | + // SSL_VERIFY_DATE | SSL_VERIFY_CHAIN | + // SSL_VERIFY_SUBJECT_ALT_NAME; + verifyOption = 0xb; + + // todo serverAddress, is this used for certificate name? + sslInterface->mId = SSLNew(verifyOption, connection->serverAddress); + + rcode = SSLSetBuiltinRootCA(sslInterface->mId, 1); + if (rcode != SSL_ENONE) { + SSLShutdown(sslInterface->mId); + return GHIEncryptionResult_Error; + } + + rcode = SSLSetBuiltinClientCert(sslInterface->mId, 0); + if (rcode != SSL_ENONE) { + SSLShutdown(sslInterface->mId); + return GHIEncryptionResult_Error; + } + } + + theEncryptor->mInitialized = GHTTPTrue; + theEncryptor->mSessionStarted = GHTTPFalse; + theEncryptor->mSessionEstablished = GHTTPFalse; + // theEncryptor->mUseSSLConnect = GHTTPTrue; + theEncryptor->mEncryptOnBuffer = GHTTPFalse; + theEncryptor->mEncryptOnSend = GHTTPTrue; + theEncryptor->mLibSendsHandshakeMessages = GHTTPTrue; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx engine) initialized\r\n"); + + return GHIEncryptionResult_Success; +} + +// Destroy the engine +GHIEncryptionResult +ghiEncryptorSslCleanupFunc(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor) { + if (theEncryptor != NULL) { + gsRevoExInterface* sslInterface = + (gsRevoExInterface*)theEncryptor->mInterface; + if (sslInterface != NULL) { + SSLShutdown(sslInterface->mId); + gsifree(sslInterface); + theEncryptor->mInterface = NULL; + } + theEncryptor->mInitialized = GHTTPFalse; + theEncryptor->mSessionStarted = GHTTPFalse; + theEncryptor->mSessionEstablished = GHTTPFalse; + } + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) engine cleanup called\r\n"); + + GSI_UNUSED(connection); + + return GHIEncryptionResult_Success; +} + +GHIEncryptionResult +ghiEncryptorSslStartFunc(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor) { + gsRevoExInterface* sslInterface = + (gsRevoExInterface*)theEncryptor->mInterface; + int result = 0; + + GS_ASSERT(theEncryptor->mSessionStarted == GHTTPFalse); + + // Call this only AFTER the socket has been connected to the remote server + if (!sslInterface->mConnected) { + result = SSLConnect(sslInterface->mId, connection->socket); + if (result != SSL_ENONE) { + switch (result) { + case SSL_EFAILED: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLConnect failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLConnect failed (SSL_ESSLID)\r\n"); + break; + default: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLConnect failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } + sslInterface->mConnected = GHTTPTrue; + } + + GS_ASSERT(sslInterface->mConnected == GHTTPTrue); + + // begin securing the session + result = SSLDoHandshake(sslInterface->mId); + if (result != SSL_ENONE) { + // Check for EWOULDBLOCK conditions + if (result == SSL_EWANT_READ || result == SSL_EWANT_WRITE) + return GHIEncryptionResult_Success; + + switch (result) { + case SSL_EFAILED: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_ESSLID)\r\n"); + break; + case SSL_ESYSCALL: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_ESYSCALL)\r\n"); + break; + case SSL_EZERO_RETURN: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EZERO_RETURN)\r\n"); + break; + case SSL_EWANT_CONNECT: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EWANT_CONNECT)\r\n"); + break; + case SSL_EVERIFY_COMMON_NAME: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed " + "(SSL_EVERIFY_COMMON_NAME)\r\n"); + break; + case SSL_EVERIFY_CHAIN: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_CHAIN)\r\n"); + break; + case SSL_EVERIFY_ROOT_CA: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed " + "(SSL_EVERIFY_ROOT_CA)\r\n"); + break; + case SSL_EVERIFY_DATE: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (SSL_EVERIFY_DATE)\r\n"); + break; + default: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLDoHandshake failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } + + // Success + theEncryptor->mSessionStarted = GHTTPTrue; + theEncryptor->mSessionEstablished = GHTTPTrue; + return GHIEncryptionResult_Success; +} + +// Encrypt and send some data +GHIEncryptionResult +ghiEncryptorSslEncryptSend(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor, + const char* thePlainTextBuffer, + int thePlainTextLength, int* theBytesSentOut) { + gsRevoExInterface* sslInterface = + (gsRevoExInterface*)theEncryptor->mInterface; + int result = 0; + + result = SSLWrite(sslInterface->mId, thePlainTextBuffer, thePlainTextLength); + if (result == SSL_EWANT_WRITE) { + // signal socket error, GetLastError will return EWOULDBLOCK or EINPROGRESS + *theBytesSentOut = -1; + } else if (result < 0) { + switch (result) { + case SSL_EFAILED: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_ESSLID)\r\n"); + break; + case SSL_EWANT_READ: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_EWANT_READ)\r\n"); + break; + case SSL_ESYSCALL: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_ESYSCALL)\r\n"); + break; + case SSL_EWANT_CONNECT: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (SSL_EWANT_CONNECT)\r\n"); + break; + default: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLWrite failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } else { + GS_ASSERT(result > 0); + *theBytesSentOut = result; + } + GSI_UNUSED(connection); + return GHIEncryptionResult_Success; +} + +// Receive and decrypt some data +GHIEncryptionResult +ghiEncryptorSslDecryptRecv(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor, + char* theDecryptedBuffer, int* theDecryptedLength) { + gsRevoExInterface* sslInterface = + (gsRevoExInterface*)theEncryptor->mInterface; + int result = 0; + + result = SSLRead(sslInterface->mId, theDecryptedBuffer, *theDecryptedLength); + if (result == SSL_EZERO_RETURN) { + // receive 0 is not fatal + *theDecryptedLength = 0; + } else if (result == SSL_EWANT_READ) { + // signal socket error, GetLastError will return EWOULDBLOCK or EINPROGRESS + *theDecryptedLength = -1; + } else if (result < 0) { + // Fatal errors + switch (result) { + case SSL_EFAILED: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_EFAILED)\r\n"); + break; + case SSL_ESSLID: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_ESSLID)\r\n"); + break; + case SSL_EWANT_WRITE: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_EWANT_WRITE)\r\n"); + break; + case SSL_ESYSCALL: + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_ESYSCALL)\r\n"); + break; + case SSL_EWANT_CONNECT: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (SSL_EWANT_CONNECT)\r\n"); + break; + default: + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Misc, GSIDebugLevel_Debug, + "GameSpy SSL (RevoEx) SSLRead failed (Unhandled Error)\r\n"); + break; + } + return GHIEncryptionResult_Error; + } else { + GS_ASSERT(result > 0); + *theDecryptedLength = result; + } + GSI_UNUSED(connection); + return GHIEncryptionResult_Success; +} + +GHIEncryptionResult ghiEncryptorSslEncryptFunc( + struct GHIConnection* connection, struct GHIEncryptor* theEncryptor, + const char* thePlainTextBuffer, int thePlainTextLength, + char* theEncryptedBuffer, int* theEncryptedLength) { + GS_FAIL(); // Should never call this for RevoEx SSL! It uses encrypt on send + + GSI_UNUSED(connection); + GSI_UNUSED(theEncryptor); + GSI_UNUSED(thePlainTextBuffer); + GSI_UNUSED(thePlainTextLength); + GSI_UNUSED(theEncryptedBuffer); + GSI_UNUSED(theEncryptedLength); + + return GHIEncryptionResult_Error; +} + +GHIEncryptionResult ghiEncryptorSslDecryptFunc( + struct GHIConnection* connection, struct GHIEncryptor* theEncryptor, + const char* theEncryptedBuffer, int* theEncryptedLength, + char* theDecryptedBuffer, int* theDecryptedLength) { + GS_FAIL(); // Should never call this for RevoEx SSL! It uses decrypt on recv + + GSI_UNUSED(connection); + GSI_UNUSED(theEncryptor); + GSI_UNUSED(theEncryptedBuffer); + GSI_UNUSED(theEncryptedLength); + GSI_UNUSED(theDecryptedBuffer); + GSI_UNUSED(theDecryptedLength); + + return GHIEncryptionResult_Error; +} diff --git a/source/gamespy/ghttp/ghttpEncryption.h b/source/gamespy/ghttp/ghttpEncryption.h new file mode 100644 index 000000000..8600335db --- /dev/null +++ b/source/gamespy/ghttp/ghttpEncryption.h @@ -0,0 +1,125 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//#include "ghttpCommon.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Encryption method +typedef enum { + GHIEncryptionMethod_None, + GHIEncryptionMethod_Encrypt, // encrypt raw data written to buffer + GHIEncryptionMethod_Decrypt // decrypt raw data written to buffer +} GHIEncryptionMethod; + +// Encryption results +typedef enum { + GHIEncryptionResult_None, + GHIEncryptionResult_Success, // successfully encrypted/decrypted + GHIEncryptionResult_BufferTooSmall, // buffer was too small to hold converted + // data + GHIEncryptionResult_Error // some other kind of error +} GHIEncryptionResult; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +struct GHIEncryptor; // forward declare for callbacks +struct GHIConnection; + +// Called to init the encryption engine +typedef GHIEncryptionResult (*GHTTPEncryptorInitFunc)( + struct GHIConnection* theConnection, struct GHIEncryptor* theEncryptor); + +// Called to connect the socket (some engines do this internally) +typedef GHIEncryptionResult (*GHTTPEncryptorConnectFunc)( + struct GHIConnection* theConnection, struct GHIEncryptor* theEncryptor); + +// Called to start the handshake process engine +typedef GHIEncryptionResult (*GHTTPEncryptorStartFunc)( + struct GHIConnection* theConnection, struct GHIEncryptor* theEncryptor); + +// Called to destroy the encryption engine +typedef GHIEncryptionResult (*GHTTPEncryptorCleanupFunc)( + struct GHIConnection* theConnection, struct GHIEncryptor* theEncryptor); + +// Called when data needs to be encrypted +// - entire plain text buffer will be encrypted +typedef GHIEncryptionResult (*GHTTPEncryptorEncryptFunc)( + struct GHIConnection* theConnection, struct GHIEncryptor* theEncryptor, + const char* thePlainTextBuffer, + int thePlainTextLength, // [in] + char* theEncryptedBuffer, + int* theEncryptedLength); // [in/out] + +// Called when data needs to be decrypted +// - encrypted data may be left in the buffer +// - decrypted buffer is appended to, not overwritten +typedef GHIEncryptionResult (*GHTTPEncryptorDecryptFunc)( + struct GHIConnection* theConnection, struct GHIEncryptor* theEncryptor, + const char* theEncryptedBuffer, + int* theEncryptedLength, // [in/out] + char* theDecryptedBuffer, + int* theDecryptedLength); // [in/out] + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct GHIEncryptor { + void* mInterface; // only SSL is currently supported + GHTTPEncryptionEngine mEngine; + GHTTPBool mInitialized; + GHTTPBool mSessionStarted; // handshake started? + GHTTPBool mSessionEstablished; // handshake completed? + + // (As coded, these two are exclusive!) + // pattern 1 = manually encrypt the buffer, then send using normal socket + // functions pattern 2 = send plain text through the encryption engine, it + // will send + GHTTPBool + mEncryptOnBuffer; // engine encrypts when writing to a buffer? (pattern 1) + GHTTPBool + mEncryptOnSend; // engine encrypts when sending over socket? (pattern 2) + + // If GHTTPTrue, the SSL library handles sending/receiving handshake messages + GHTTPBool mLibSendsHandshakeMessages; + + // Functions for engine use + GHTTPEncryptorInitFunc mInitFunc; + GHTTPEncryptorCleanupFunc mCleanupFunc; + GHTTPEncryptorStartFunc mStartFunc; // start the handshake process + GHTTPEncryptorEncryptFunc mEncryptFunc; + GHTTPEncryptorDecryptFunc mDecryptFunc; +} GHIEncryptor; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// ssl encryption +GHIEncryptionResult ghiEncryptorSslInitFunc(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor); +GHIEncryptionResult +ghiEncryptorSslCleanupFunc(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor); + +GHIEncryptionResult ghiEncryptorSslStartFunc(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor); + +GHIEncryptionResult ghiEncryptorSslEncryptFunc( + struct GHIConnection* connection, struct GHIEncryptor* theEncryptor, + const char* thePlainTextBuffer, int thePlainTextLength, + char* theEncryptedBuffer, int* theEncryptedLength); +GHIEncryptionResult ghiEncryptorSslDecryptFunc( + struct GHIConnection* connection, struct GHIEncryptor* theEncryptor, + const char* theEncryptedBuffer, int* theEncryptedLength, + char* theDecryptedBuffer, int* theDecryptedLength); +GHIEncryptionResult ghiEncryptorSslEncryptSend( + struct GHIConnection* connection, struct GHIEncryptor* theEncryptor, + const char* thePlainTextBuffer, int thePlainTextLength, int* theBytesSent); +GHIEncryptionResult +ghiEncryptorSslDecryptRecv(struct GHIConnection* connection, + struct GHIEncryptor* theEncryptor, + char* theDecryptedBuffer, int* theDecryptedLength); + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/ghttp/ghttpMain.c b/source/gamespy/ghttp/ghttpMain.c new file mode 100644 index 000000000..2585c7085 --- /dev/null +++ b/source/gamespy/ghttp/ghttpMain.c @@ -0,0 +1,1013 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2001 GameSpy Industries, Inc + +18002 Skypark Circle +Irvine, California 92614 +949.798.4200 (Tel) +949.798.4299 (Fax) +devsupport@gamespy.com +*/ + +// clang-format off +#include "ghttpMain.h" +#include "ghttpASCII.h" +#include "ghttpConnection.h" +#include "ghttpCallbacks.h" +#include "ghttpProcess.h" +#include "ghttpPost.h" +#include "ghttpCommon.h" +// clang-format on + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Ascii versions which must be available even in the unicode build +GHTTPRequest ghttpGetExA(const char* URL, const char* headers, char* buffer, + int bufferSize, GHTTPPost post, GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, void* param); +GHTTPRequest ghttpSaveExA(const char* URL, const char* filename, + const char* headers, GHTTPPost post, + GHTTPBool throttle, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param); +GHTTPRequest ghttpStreamExA(const char* URL, const char* headers, + GHTTPPost post, GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param); +GHTTPRequest ghttpHeadExA(const char* URL, const char* headers, + GHTTPBool throttle, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param); +GHTTPRequest ghttpPostExA(const char* URL, const char* headers, GHTTPPost post, + GHTTPBool throttle, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param); + +// Reference count. +/////////////////// +static int ghiReferenceCount; + +// Called right before callback is called. +// Sets result based on response status code. +///////////////////////////////////////////// +static void ghiHandleStatus(GHIConnection* connection) { + // Check the status code. + ///////////////////////// + switch (connection->statusCode / 100) { + case 1: // Informational. + return; + case 2: // Successful. + return; + case 3: // Redirection. + return; + case 4: // Client Error. + switch (connection->statusCode) { + case 401: + connection->result = GHTTPUnauthorized; + break; + case 403: + connection->result = GHTTPForbidden; + break; + case 404: + case 410: + connection->result = GHTTPFileNotFound; + break; + default: + connection->result = GHTTPRequestRejected; + break; + } + return; + case 5: // Internal Server Error. + connection->result = GHTTPServerError; + return; + } +} + +// Processes a single connection based on its state. +// Returns true if the connection is finished. +//////////////////////////////////////////////////// +static GHTTPBool ghiProcessConnection(GHIConnection* connection) { + GHTTPBool completed; + + // assert(connection); + // assert(ghiRequestToConnection(connection->request) == connection); + + // Don't process if already processing this connection. + // Happens if, for example, ghttpThink is called from a callback. + ///////////////////////////////////////////////////////////////// + if (connection->processing) + return GHTTPFalse; + + // We're now processing. + //////////////////////// + connection->processing = GHTTPTrue; + + // Process based on state. + // else-if is not used so that if one ghiDo*() + // finishes the one after it can start. + ////////////////////////////////////////////// + + if (connection->state == GHTTPSocketInit) + ghiDoSocketInit(connection); + if (connection->state == GHTTPHostLookup) + ghiDoHostLookup(connection); + if (connection->state == GHTTPLookupPending) + ghiDoLookupPending(connection); + if (connection->state == GHTTPConnecting) + ghiDoConnecting(connection); + if (connection->state == GHTTPSecuringSession) + ghiDoSecuringSession(connection); + if (connection->state == GHTTPSendingRequest) + ghiDoSendingRequest(connection); + if (connection->state == GHTTPPosting) + ghiDoPosting(connection); + if (connection->state == GHTTPWaiting) + ghiDoWaiting(connection); + if (connection->state == GHTTPReceivingStatus) + ghiDoReceivingStatus(connection); + if (connection->state == GHTTPReceivingHeaders) + ghiDoReceivingHeaders(connection); + if (connection->state == GHTTPReceivingFile) + ghiDoReceivingFile(connection); + + // Check for a redirect. + //////////////////////// + if (connection->redirectURL) + ghiRedirectConnection(connection); + + // Grab completed before we possibly free it. + ///////////////////////////////////////////// + completed = connection->completed; + + // Graceful shutdown support. + // Close connection when there is no more data + if (connection->result == GHTTPRequestCancelled && !connection->completed && + !CanReceiveOnSocket(connection->socket)) { + connection->completed = GHTTPTrue; + } + + // Is it finished? + ////////////////// + if (connection->completed) { + // Set result based on status code. + /////////////////////////////////// + ghiHandleStatus(connection); + + // If we're saving to file, close it before the callback. + ///////////////////////////////////////////////////////// +#ifndef NOFILE + if (connection->saveFile) { + fclose(connection->saveFile); + connection->saveFile = NULL; + } +#endif + // Log buffer data + ghiLogResponse(connection->getFileBuffer.data, + connection->getFileBuffer.len); + + // Call the callback. + ///////////////////// + ghiCallCompletedCallback(connection); + + // Free it. + /////////// + ghiFreeConnection(connection); + } else { + // Done processing. This is in the else, + // because we don't want to set it if the + // connection has already been freed. + ///////////////////////////////////////// + connection->processing = GHTTPFalse; + } + + return completed; +} + +void ghttpStartup(void) { + // This will just return if we haven't created the lock yet. + //////////////////////////////////////////////////////////// + ghiLock(); + + // One more startup. + //////////////////// + ghiReferenceCount++; + + // Check if we are the first. + ///////////////////////////// + if (ghiReferenceCount == 1) { + // Create the lock. + /////////////////// + ghiCreateLock(); + + // Set some defaults. + ///////////////////// + ghiThrottleBufferSize = GHI_DEFAULT_THROTTLE_BUFFER_SIZE; + ghiThrottleTimeDelay = GHI_DEFAULT_THROTTLE_TIME_DELAY; + } else { + // Unlock the lock. + /////////////////// + ghiUnlock(); + } +} + +void ghttpCleanup(void) { + // Lockdown for cleanup. + //////////////////////// + ghiLock(); + + // One less. + //////////// + ghiReferenceCount--; + + // Should we cleanup? + ///////////////////// + if (!ghiReferenceCount) { + // Cleanup the connections. + /////////////////////////// + ghiCleanupConnections(); + + // Cleanup proxy. + ///////////////// + if (ghiProxyAddress) { + gsifree(ghiProxyAddress); + ghiProxyAddress = NULL; + } + + // Unlock the lock before freeing it. + ///////////////////////////////////// + ghiUnlock(); + + // Free the lock. + ///////////////// + ghiFreeLock(); + } else { + // Unlock our lock. + /////////////////// + ghiUnlock(); + } +} + +GHTTPRequest ghttpGetA(const char* URL, GHTTPBool blocking, + ghttpCompletedCallback completedCallback, void* param) { + return ghttpGetExA(URL, NULL, NULL, 0, NULL, GHTTPFalse, blocking, NULL, + completedCallback, param); +} + +GHTTPRequest ghttpGetExA(const char* URL, const char* headers, char* buffer, + int bufferSize, GHTTPPost post, GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param) { + GHTTPBool bResult; + GHIConnection* connection; + + // assert(URL && URL[0]); + // assert(bufferSize >= 0); + // assert(!buffer || bufferSize); + + // Check args. + ////////////// + if (!URL || !URL[0]) + return GHTTPInvalidURL; + if (bufferSize < 0) + return GHTTPInvalidBufferSize; + if (buffer && !bufferSize) + return GHTTPInvalidBufferSize; + + // Startup if it hasn't been done. + ////////////////////////////////// + if (!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if (!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHIGET; + connection->URL = goastrdup(URL); + if (!connection->URL) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if (headers && *headers) { + connection->sendHeaders = goastrdup(headers); + if (!connection->sendHeaders) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + connection->userBufferSupplied = (buffer != NULL) ? GHTTPTrue : GHTTPFalse; + if (connection->userBufferSupplied) + bResult = ghiInitFixedBuffer(connection, &connection->getFileBuffer, buffer, + bufferSize); + else + bResult = ghiInitBuffer(connection, &connection->getFileBuffer, + GET_FILE_BUFFER_INITIAL_SIZE, + GET_FILE_BUFFER_INCREMENT_SIZE); + if (!bResult) { + ghiFreeConnection(connection); + return GHTTPUnspecifiedError; + } + + // Setup the post state if needed. + ////////////////////////////////// + if (post && !ghiPostInitState(connection)) { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Check blocking. + ////////////////// + if (blocking) { + // Loop until completed. + //////////////////////// + while (!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} + +GHTTPRequest ghttpSaveA(const char* URL, const char* filename, + GHTTPBool blocking, + ghttpCompletedCallback completedCallback, void* param) { + return ghttpSaveExA(URL, filename, NULL, NULL, GHTTPFalse, blocking, NULL, + completedCallback, param); +} + +static GHTTPRequest _ghttpSaveEx(const char* URL, const gsi_char* filename, + const char* headers, GHTTPPost post, + GHTTPBool throttle, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param) { + GHIConnection* connection; + + // assert(URL && URL[0]); + // assert(filename && filename[0]); + + // Check args. + ////////////// + if (!URL || !URL[0]) + return GHTTPInvalidURL; + if (!filename || !filename[0]) + return GHTTPInvalidFileName; + + // Startup if it hasn't been done. + ////////////////////////////////// + if (!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if (!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHISAVE; + connection->URL = goastrdup(URL); + if (!connection->URL) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if (headers && *headers) { + connection->sendHeaders = goastrdup(headers); + if (!connection->sendHeaders) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Setup the post state if needed. + ////////////////////////////////// + if (post && !ghiPostInitState(connection)) { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Open the file we're saving to. + ///////////////////////////////// +#ifdef NOFILE + connection->saveFile = NULL; +#else + connection->saveFile = _tfopen(filename, _T("wb")); +#endif + if (!connection->saveFile) { + ghiFreeConnection(connection); + return GHTTPFailedToOpenFile; + } + + // Check blocking. + ////////////////// + if (blocking) { + // Loop until completed. + //////////////////////// + while (!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} + +GHTTPRequest ghttpSaveExA(const char* URL, const char* filename, + const char* headers, GHTTPPost post, + GHTTPBool throttle, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param) { + return _ghttpSaveEx(URL, filename, headers, post, throttle, blocking, + progressCallback, completedCallback, param); +} + +GHTTPRequest ghttpStreamA(const char* URL, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param) { + return ghttpStreamExA(URL, NULL, NULL, GHTTPFalse, blocking, progressCallback, + completedCallback, param); +} + +GHTTPRequest ghttpStreamExA(const char* URL, const char* headers, + GHTTPPost post, GHTTPBool throttle, + GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param) { + GHIConnection* connection; + + // assert(URL && URL[0]); + + // Check args. + ////////////// + if (!URL || !URL[0]) + return GHTTPInvalidURL; + + // Startup if it hasn't been done. + ////////////////////////////////// + if (!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if (!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHISTREAM; + connection->URL = goastrdup(URL); + if (!connection->URL) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if (headers && *headers) { + connection->sendHeaders = goastrdup(headers); + if (!connection->sendHeaders) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Setup the post state if needed. + ////////////////////////////////// + if (post && !ghiPostInitState(connection)) { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Check blocking. + ////////////////// + if (blocking) { + // Loop until completed. + //////////////////////// + while (!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} + +GHTTPRequest ghttpHeadA(const char* URL, GHTTPBool blocking, + ghttpCompletedCallback completedCallback, void* param) { + return ghttpHeadExA(URL, NULL, GHTTPFalse, blocking, NULL, completedCallback, + param); +} + +GHTTPRequest ghttpHeadExA(const char* URL, const char* headers, + GHTTPBool throttle, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param) { + GHIConnection* connection; + + // assert(URL && URL[0]); + + // Check args. + ////////////// + if (!URL || !URL[0]) + return GHTTPInvalidURL; + + // Startup if it hasn't been done. + ////////////////////////////////// + if (!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if (!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHIHEAD; + connection->URL = goastrdup(URL); + if (!connection->URL) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if (headers && *headers) { + connection->sendHeaders = goastrdup(headers); + if (!connection->sendHeaders) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Check blocking. + ////////////////// + if (blocking) { + // Loop until completed. + //////////////////////// + while (!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} + +GHTTPRequest ghttpPostA(const char* URL, GHTTPPost post, GHTTPBool blocking, + ghttpCompletedCallback completedCallback, void* param) { + return ghttpPostExA(URL, NULL, post, GHTTPFalse, blocking, NULL, + completedCallback, param); +} + +GHTTPRequest ghttpPostExA(const char* URL, const char* headers, GHTTPPost post, + GHTTPBool throttle, GHTTPBool blocking, + ghttpProgressCallback progressCallback, + ghttpCompletedCallback completedCallback, + void* param) { + GHIConnection* connection; + + // assert(URL && URL[0]); + // assert(post); + + // Check args. + ////////////// + if (!URL || !URL[0]) + return GHTTPInvalidURL; + if (!post) + return GHTTPInvalidPost; + + // Startup if it hasn't been done. + ////////////////////////////////// + if (!ghiReferenceCount) + ghttpStartup(); + + // Get a new connection object. + /////////////////////////////// + connection = ghiNewConnection(); + if (!connection) + return GHTTPInsufficientMemory; + + // Fill in the necessary info. + ////////////////////////////// + connection->type = GHIPOST; + connection->URL = goastrdup(URL); + if (!connection->URL) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + if (headers && *headers) { + connection->sendHeaders = goastrdup(headers); + if (!connection->sendHeaders) { + ghiFreeConnection(connection); + return GHTTPInsufficientMemory; + } + } + connection->post = post; + connection->blocking = blocking; + connection->progressCallback = progressCallback; + connection->completedCallback = completedCallback; + connection->callbackParam = param; + connection->throttle = throttle; + + // Setup the post state if needed. + ////////////////////////////////// + if (post && !ghiPostInitState(connection)) { + ghiFreeConnection(connection); + return GHTTPInvalidPost; + } + + // Check blocking. + ////////////////// + if (blocking) { + // Loop until completed. + //////////////////////// + while (!ghiProcessConnection(connection)) + msleep(10); + + // Done. + //////// + return 0; + } + + return connection->request; +} + +void ghttpThink(void) { + // Process all the connections. + /////////////////////////////// + ghiEnumConnections(ghiProcessConnection); +} + +GHTTPBool ghttpRequestThink(GHTTPRequest request) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return GHTTPFalse; + + // Think. + ///////// + ghiProcessConnection(connection); + return GHTTPTrue; +} + +void ghttpCancelRequest(GHTTPRequest request) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return; + + // Free it. + /////////// + ghiFreeConnection(connection); +} + +#if !defined(INSOCK) +// INSOCK does not support partial shutdown +void ghttpCloseRequest(GHTTPRequest request) { + GHIConnection* connection; + + connection = ghiRequestToConnection(request); + if (!connection) + return; + + if (connection->socket) { + // Gracefully close the connection + // SDK will dispatch a "request cancelled" callback when all data + // has been received + shutdown(connection->socket, 1); + connection->result = GHTTPRequestCancelled; + } +} +#endif + +GHTTPState ghttpGetState(GHTTPRequest request) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return (GHTTPState)0; + + return connection->state; +} + +const char* ghttpGetResponseStatus(GHTTPRequest request, int* statusCode) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return NULL; + + // Check if we don't have the status yet. + ///////////////////////////////////////// + if (connection->state <= GHTTPReceivingStatus) + return NULL; + + // Set the status code. + /////////////////////// + if (statusCode) + *statusCode = connection->statusCode; + + return (connection->recvBuffer.data + connection->statusStringIndex); +} + +const char* ghttpGetHeaders(GHTTPRequest request) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return NULL; + + // Check if we don't have the headers yet. + ////////////////////////////////////////// + if (connection->state < GHTTPReceivingHeaders) + return NULL; + + // Verify we have headers. + ////////////////////////// + if (connection->headerStringIndex >= connection->recvBuffer.len) + return NULL; + + return (connection->recvBuffer.data + connection->headerStringIndex); +} + +const char* ghttpGetURL(GHTTPRequest request) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return NULL; + + return connection->URL; +} + +GHTTPBool ghttpSetProxy(const char* server) { return ghiSetProxy(server); } + +GHTTPBool ghttpSetRequestProxy(GHTTPRequest request, const char* server) { + return ghiSetRequestProxy(request, server); +} + +void ghttpSetThrottle(GHTTPRequest request, GHTTPBool throttle) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return; + + connection->throttle = throttle; + + // Set the buffer size based on the throttle setting. + ///////////////////////////////////////////////////// + if (connection->socket != INVALID_SOCKET) + SetReceiveBufferSize(connection->socket, + throttle ? ghiThrottleBufferSize : (8 * 1024)); +} + +void ghttpThrottleSettings(int bufferSize, gsi_time timeDelay) { + ghiThrottleSettings(bufferSize, timeDelay); +} + +void ghttpSetMaxRecvTime(GHTTPRequest request, gsi_time maxRecvTime) { + GHIConnection* connection = ghiRequestToConnection(request); + if (connection == NULL) + return; + + connection->maxRecvTime = maxRecvTime; +} + +// Internal prototypes for persistent HTTP connections +// Prevents warnings from strict compilers +////////////////////////////////////////////////////// +SOCKET ghttpGetSocket(GHTTPRequest request); +GHTTPBool ghttpReuseSocket(GHTTPRequest request, SOCKET socket); + +// For use in persistent HTTP connections +// Call this in the completed callback to obtain the socket, which can be used +// with ghttpReuseSocket to make a second request to the same host +/////////////////////////////////////////////////////////////////// +SOCKET ghttpGetSocket(GHTTPRequest request) { + GHIConnection* connection; + SOCKET ret; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return INVALID_SOCKET; + + // Only allow them to grab the socket during the competion callback (not while + // a request is in process) + ///////////////////////////////////////////////////////// + if (!connection->completed) + return INVALID_SOCKET; + + ret = connection->socket; + // Mark the connection as invalid so that it doesn't get closed + connection->socket = INVALID_SOCKET; + + return ret; +} + +// For use in persistent HTTP connections +// Call this after creating a request, but before calling think the first time +// in order to reuse an existing connection to the same server If the socket +// passed is INVALID_SOCKET, a new connection will be created and marked as +// persistent +/////////////////////////////////////////////////////////////////// +GHTTPBool ghttpReuseSocket(GHTTPRequest request, SOCKET socket) { + GHIConnection* connection; + + // Get the connection object for this request. + ////////////////////////////////////////////// + connection = ghiRequestToConnection(request); + if (!connection) + return GHTTPFalse; + if (connection->state != GHTTPSocketInit) + return GHTTPFalse; + if (connection->socket != INVALID_SOCKET) + return GHTTPFalse; + + connection->persistConnection = GHTTPTrue; + connection->socket = socket; + + /* + if (socket == INVALID_SOCKET) + { + // The connection is marked as persistent, but we still need to lookup + and connect, so don't advance the state + ///////////////////////////////////////////////////////////////////// + return GHTTPTrue; + } + + // Skip the host lookup & connect - send data once the socket is writable + ////////////////////////////////////////////// + connection->state = GHTTPConnecting; + */ + return GHTTPTrue; +} + +GHTTPPost ghttpNewPost(void) { return ghiNewPost(); } + +void ghttpPostSetAutoFree(GHTTPPost post, GHTTPBool autoFree) { + // assert(post); + if (!post) + return; + + ghiPostSetAutoFree(post, autoFree); +} + +void ghttpFreePost(GHTTPPost post) { + // assert(post); + if (!post) + return; + + ghiFreePost(post); +} + +GHTTPBool ghttpPostAddStringA(GHTTPPost post, const char* name, + const char* string) { + // assert(post); + // assert(name && name[0]); + + if (!post) + return GHTTPFalse; + if (!name || !name[0]) + return GHTTPFalse; + if (!string) + string = ""; + + return ghiPostAddString(post, name, string); +} + +GHTTPBool ghttpPostAddFileFromDiskA(GHTTPPost post, const char* name, + const char* filename, + const char* reportFilename, + const char* contentType) { + // assert(post); + // assert(name && name[0]); + // assert(filename && filename[0]); + + if (!post) + return GHTTPFalse; + if (!name || !name[0]) + return GHTTPFalse; + if (!filename || !filename[0]) + return GHTTPFalse; + if (!reportFilename || !reportFilename[0]) + reportFilename = filename; + if (!contentType) + contentType = "application/octet-stream"; + + return ghiPostAddFileFromDisk(post, name, filename, reportFilename, + contentType); +} + +GHTTPBool ghttpPostAddFileFromMemoryA(GHTTPPost post, const char* name, + const char* buffer, int bufferLen, + const char* reportFilename, + const char* contentType) { + // assert(post); + // assert(name && name[0]); + // assert(bufferLen >= 0); +#ifdef _DEBUG + if (bufferLen > 0) + // assert(buffer); +#endif + // assert(reportFilename && reportFilename[0]); + + if (!post) + return GHTTPFalse; + if (!name || !name[0]) + return GHTTPFalse; + if (bufferLen < 0) + return GHTTPFalse; + if (!bufferLen && !buffer) + return GHTTPFalse; + if (!contentType) + contentType = "application/octet-stream"; + + return ghiPostAddFileFromMemory(post, name, buffer, bufferLen, reportFilename, + contentType); +} + +GHTTPBool ghttpPostAddXml(GHTTPPost post, GSXmlStreamWriter soap) { + GS_ASSERT(post != NULL); + GS_ASSERT(soap != NULL); + + return ghiPostAddXml(post, soap); +} + +void ghttpPostSetCallback(GHTTPPost post, ghttpPostCallback callback, + void* param) { + // assert(post); + + if (!post) + return; + + ghiPostSetCallback(post, callback, param); +} diff --git a/source/gamespy/ghttp/ghttpMain.h b/source/gamespy/ghttp/ghttpMain.h new file mode 100644 index 000000000..1e4fbb6fd --- /dev/null +++ b/source/gamespy/ghttp/ghttpMain.h @@ -0,0 +1,18 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +// clang-format off +#include +#include "ghttp.h" +#include "../common/gsCommon.h" +#include "../common/gsStringUtil.h" +// clang-format on diff --git a/source/gamespy/ghttp/ghttpPost.c b/source/gamespy/ghttp/ghttpPost.c new file mode 100644 index 000000000..529789c30 --- /dev/null +++ b/source/gamespy/ghttp/ghttpPost.c @@ -0,0 +1,1425 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +// clang-format off +#include "ghttpPost.h" +#include "ghttpMain.h" +#include "ghttpConnection.h" +#include "ghttpCommon.h" + +#include "../common/gsCrypt.h" +#include "../common/gsSSL.h" +#include "../common/gsXML.h" +// clang-format on + +// The border between parts in a file send. +/////////////////////////////////////////// +#define GHI_MULTIPART_BOUNDARY "Qr4G823s23d---<<><><<<>--7d118e0536" +#define GHI_MULTIPART_BOUNDARY_BASE "--" GHI_MULTIPART_BOUNDARY +#define GHI_MULTIPART_BOUNDARY_FIRST GHI_MULTIPART_BOUNDARY_BASE CRLF +#define GHI_MULTIPART_BOUNDARY_NORMAL CRLF GHI_MULTIPART_BOUNDARY_BASE CRLF +#define GHI_MULTIPART_BOUNDARY_END CRLF GHI_MULTIPART_BOUNDARY_BASE "--" CRLF + +#define GHI_LEGAL_URLENCODED_CHARS \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@-.*" +#define GHI_DIGITS "0123456789ABCDEF" + +// DIME header settings +// first byte is a combination of VERSION + first/last/chunked +#define GHI_DIME_VERSION (0x1 << 3) // 5th bit (from the left) +#define GHI_DIMEFLAG_FIRSTRECORD (1 << 2) +#define GHI_DIMEFLAG_LASTRECORD (1 << 1) +#define GHI_DIMEFLAG_CHUNKED (1 << 0) +// second byte is combination of TYPE_T and reserved (4bits = 0) +#define GHI_DIMETYPE_T_UNCHANGED (0x0 << 4) +#define GHI_DIMETYPE_T_MEDIA (0x1 << 4) +#define GHI_DIMETYPE_T_URI (0x2 << 4) +#define GHI_DIMETYPE_T_UNKNOWN (0x3 << 4) +#define GHI_DIMETYPE_T_EMPTY (0x4 << 4) // lengths must be set to 0 + +//#define GHI_DIME_SOAPID "gsi:soap" +#define GHI_DIME_SOAPID "cid:id0" +#define GHI_DIME_SOAPTYPE "http://schemas.xmlsoap.org/soap/envelope/" + +typedef struct GSIDimeHeader { + gsi_u8 mVersionAndFlags; + gsi_u8 mTypeT; + gsi_u16 mOptionsLength; + gsi_u16 mIdLength; + gsi_u16 mTypeLength; + gsi_u32 mDataLength; + // gsi_u8 mOptions[mOptionsLength]; + // gsi_u8 mId[mIdLength]; + // gsi_u8 mType[mTypeLength]; + // gsi_u8 mData[mDataLength]; +} GHIDimeHeader; + +// POST TYPES. +////////////// +typedef enum { + GHIString, // A regular string. + GHIFileDisk, // A file from disk. + GHIFileMemory, // A file from memory. + GHIXmlData // XML Soap. (long string) +} GHIPostDataType; + +// POST OBJECT. +/////////////// +typedef struct GHIPost { + DArray data; + ghttpPostCallback callback; + void* param; + GHTTPBool hasFiles; + GHTTPBool hasSoap; + GHTTPBool useDIME; + GHTTPBool autoFree; +} GHIPost; + +// POST DATA. +///////////// +typedef struct GHIPostStringData { + char* string; + int len; + GHTTPBool invalidChars; + int extendedChars; +} GHIPostStringData; + +typedef struct GHIPostFileDiskData { + char* filename; + char* reportFilename; + char* contentType; +} GHIPostFileDiskData; + +typedef struct GHIPostFileMemoryData { + const char* buffer; + int len; + char* reportFilename; + char* contentType; +} GHIPostFileMemoryData; + +typedef struct GHIPostXmlData { + GSXmlStreamWriter xml; +} GHIPostXmlData; + +typedef struct GHIPostData { + GHIPostDataType type; + char* name; + union { + GHIPostStringData string; + GHIPostFileDiskData fileDisk; + GHIPostFileMemoryData fileMemory; + GHIPostXmlData xml; + } data; +} GHIPostData; + +// POST STATE. +////////////// +// typedef struct GHIPostStringState +//{ +//} GHIPostStringState; + +typedef struct GHIPostFileDiskState { + FILE* file; + long len; +} GHIPostFileDiskState; + +// typedef struct GHIPostFileMemoryState +//{ +//} GHIPostFileMemoryState; + +// typedef struct GHIPostSoapState +//{ +//} GHIPostSoapState; + +typedef struct GHIPostState { + GHIPostData* data; + int pos; + union { + // GHIPostStringState string; + GHIPostFileDiskState fileDisk; + // GHIPostFileMemoryState fileMemory; + // GHIPostSoapState soap; + } state; +} GHIPostState; + +// FUNCTIONS. +///////////// +static void ghiPostDataFree(void* elem) { + GHIPostData* data = (GHIPostData*)elem; + + // Free the name. + ///////////////// + if (data->type != GHIXmlData) + gsifree(data->name); + + // Free based on type. + ////////////////////// + if (data->type == GHIString) { + // Free the string string. + ///////////////////////// + gsifree(data->data.string.string); + } else if (data->type == GHIFileDisk) { + // Free the strings. + //////////////////// + gsifree(data->data.fileDisk.filename); + gsifree(data->data.fileDisk.reportFilename); + gsifree(data->data.fileDisk.contentType); + } else if (data->type == GHIFileMemory) { + // Free the strings. + //////////////////// + gsifree(data->data.fileMemory.reportFilename); + gsifree(data->data.fileMemory.contentType); + } else if (data->type == GHIXmlData) { + gsXmlFreeWriter(data->data.xml.xml); + } else { + // The type didn't match any known types. + ///////////////////////////////////////// + // assert(0); + } +} + +GHTTPPost ghiNewPost(void) { + GHIPost* post; + + // Allocate the post object. + //////////////////////////// + post = (GHIPost*)gsimalloc(sizeof(GHIPost)); + if (!post) + return NULL; + + // Initialize it. + ///////////////// + memset(post, 0, sizeof(GHIPost)); + post->autoFree = GHTTPTrue; + + // Create the array of data objects. + //////////////////////////////////// + post->data = ArrayNew(sizeof(GHIPostData), 0, ghiPostDataFree); + if (!post->data) { + gsifree(post); + return NULL; + } + + return (GHTTPPost)post; +} + +void ghiPostSetAutoFree(GHTTPPost _post, GHTTPBool autoFree) { + GHIPost* post = (GHIPost*)_post; + + post->autoFree = autoFree; +} + +GHTTPBool ghiIsPostAutoFree(GHTTPPost _post) { + GHIPost* post = (GHIPost*)_post; + + return post->autoFree; +} + +void ghiFreePost(GHTTPPost _post) { + GHIPost* post = (GHIPost*)_post; + + // Free the array of data objects. + ////////////////////////////////// + ArrayFree(post->data); + + // Free the post object. + //////////////////////// + gsifree(post); +} + +GHTTPBool ghiPostAddString(GHTTPPost _post, const char* name, + const char* string) { + GHIPost* post = (GHIPost*)_post; + GHIPostData data; + int len; + int rcode; + + // Copy the strings. + //////////////////// + name = goastrdup(name); + string = goastrdup(string); + if (!name || !string) { + gsifree((char*)name); + gsifree((char*)string); + return GHTTPFalse; + } + + // Set the data. + //////////////// + memset(&data, 0, sizeof(GHIPostData)); + data.type = GHIString; + data.name = (char*)name; + data.data.string.string = (char*)string; + len = (int)strlen(string); + data.data.string.len = len; + data.data.string.invalidChars = GHTTPFalse; + + // Are there any invalid characters? + //////////////////////////////////// + rcode = (int)strspn(string, GHI_LEGAL_URLENCODED_CHARS); + if (rcode != len) { + int i; + int count = 0; + + data.data.string.invalidChars = GHTTPTrue; + + // Count the number, not including spaces. + ////////////////////////////////////////// + for (i = 0; string[i]; i++) + if (!strchr(GHI_LEGAL_URLENCODED_CHARS, string[i]) && (string[i] != ' ')) + count++; + + data.data.string.extendedChars = count; + } + + // Add it. + ////////// + ArrayAppend(post->data, &data); + + return GHTTPTrue; +} + +GHTTPBool ghiPostAddFileFromDisk(GHTTPPost _post, const char* name, + const char* filename, + const char* reportFilename, + const char* contentType) { + GHIPost* post = (GHIPost*)_post; + GHIPostData data; + + // Copy the strings. + //////////////////// + name = goastrdup(name); + filename = goastrdup(filename); + reportFilename = goastrdup(reportFilename); + contentType = goastrdup(contentType); + if (!name || !filename || !reportFilename || !contentType) { + gsifree((char*)name); + gsifree((char*)filename); + gsifree((char*)reportFilename); + gsifree((char*)contentType); + return GHTTPFalse; + } + + // Set the data. + //////////////// + memset(&data, 0, sizeof(GHIPostData)); + data.type = GHIFileDisk; + data.name = (char*)name; + data.data.fileDisk.filename = (char*)filename; + data.data.fileDisk.reportFilename = (char*)reportFilename; + data.data.fileDisk.contentType = (char*)contentType; + + // Add it. + ////////// + ArrayAppend(post->data, &data); + + // We have files. + ///////////////// + post->hasFiles = GHTTPTrue; + + // if we have both soap and a file we MUST use DIME + if (post->hasSoap == GHTTPTrue) + post->useDIME = GHTTPTrue; + + return GHTTPTrue; +} + +GHTTPBool ghiPostAddFileFromMemory(GHTTPPost _post, const char* name, + const char* buffer, int bufferLen, + const char* reportFilename, + const char* contentType) { + GHIPost* post = (GHIPost*)_post; + GHIPostData data; + + // Copy the strings. + //////////////////// + name = goastrdup(name); + reportFilename = goastrdup(reportFilename); + contentType = goastrdup(contentType); + if (!name || !reportFilename || !contentType) { + gsifree((char*)name); + gsifree((char*)reportFilename); + gsifree((char*)contentType); + return GHTTPFalse; + } + + // Set it. + ////////// + memset(&data, 0, sizeof(GHIPostData)); + data.type = GHIFileMemory; + data.name = (char*)name; + data.data.fileMemory.buffer = (char*)buffer; + data.data.fileMemory.len = bufferLen; + data.data.fileMemory.reportFilename = (char*)reportFilename; + data.data.fileMemory.contentType = (char*)contentType; + + // Add it. + ////////// + ArrayAppend(post->data, &data); + + // We have a file. + ////////////////// + post->hasFiles = GHTTPTrue; + + // if we have both soap and a file we MUST use DIME + if (post->hasSoap == GHTTPTrue) + post->useDIME = GHTTPTrue; + + return GHTTPTrue; +} + +GHTTPBool ghiPostAddXml(GHTTPPost _post, GSXmlStreamWriter xml) { + GHIPostData data; + // unsigned int rcode = 0; + + GHIPost* post = (GHIPost*)_post; + + data.type = GHIXmlData; + data.data.xml.xml = xml; + ArrayAppend(post->data, &data); + post->hasSoap = GHTTPTrue; + + // if we have both soap and a file we MUST use DIME + if (post->hasFiles == GHTTPTrue) + post->useDIME = GHTTPTrue; + + return GHTTPTrue; +} + +void ghiPostSetCallback(GHTTPPost _post, ghttpPostCallback callback, + void* param) { + GHIPost* post = (GHIPost*)_post; + + // Set the callback and param. + ////////////////////////////// + post->callback = callback; + post->param = param; +} + +const char* ghiPostGetContentType(struct GHIConnection* connection) { + GHIPost* post = connection->post; + + // assert(post); + if (!post) + return ""; + + // Report content-type based on if we are sending files or not. + /////////////////////////////////////////////////////////////// + if (post->useDIME) + return ("application/dime"); + else if (post->hasFiles) { + GS_ASSERT(!post->hasSoap); + return ("multipart/form-data; boundary=" GHI_MULTIPART_BOUNDARY); + } else if (post->hasSoap) { + GS_ASSERT(!post->hasFiles); + return ("text/xml"); + } else { + GS_ASSERT(!post->hasSoap); + GS_ASSERT(!post->hasFiles); + return "application/x-www-form-urlencoded"; + } +} + +static int ghiPostGetNoFilesContentLength(struct GHIConnection* connection) { + GHIPost* post = connection->post; + GHIPostData* data; + int i; + int num; + int total = 0; + int foundSoapAlready = 0; + + num = ArrayLength(post->data); + if (!num) + return 0; + + for (i = 0; i < num; i++) { + data = (GHIPostData*)ArrayNth(post->data, i); + + GS_ASSERT(data->type == GHIString || data->type == GHIXmlData); + + if (data->type == GHIString) { + total += (int)strlen(data->name); + total += data->data.string.len; + total += (data->data.string.extendedChars * 2); + total++; // '=' + } else if (data->type == GHIXmlData) { + GS_ASSERT(foundSoapAlready == + 0); // only support one soap object per request + foundSoapAlready = 1; + total += gsXmlWriterGetDataLength(data->data.xml.xml); + } + } + + total += (num - 1); // '&' + + GSI_UNUSED(foundSoapAlready); + return total; +} + +static int ghiPostGetHasFilesContentLength(struct GHIConnection* connection) { + GHIPost* post = connection->post; + GHIPostData* data; + int i; + int num; + int total = 0; + int foundSoapAlready = 0; + static int boundaryLen; + static int stringBaseLen; + static int fileBaseLen; + static int endLen; + static int xmlBaseLen; + + if (!boundaryLen) { + if (post->useDIME) { + GS_ASSERT(post->hasSoap); + GS_ASSERT(post->hasFiles); + boundaryLen = sizeof(GHIDimeHeader); + stringBaseLen = boundaryLen; + fileBaseLen = boundaryLen; + xmlBaseLen = boundaryLen; + endLen = 0; + } else { + GS_ASSERT(!post->hasSoap); + boundaryLen = (int)strlen(GHI_MULTIPART_BOUNDARY_BASE); + stringBaseLen = (boundaryLen + 47); // + name + string + fileBaseLen = + (boundaryLen + 76); // + name + filename + content-type + file + xmlBaseLen = 0; // no boundaries for text/xml type soap + endLen = (boundaryLen + 4); + } + } + + num = ArrayLength(post->data); + + for (i = 0; i < num; i++) { + data = (GHIPostData*)ArrayNth(post->data, i); + + if (data->type == GHIString) { + total += stringBaseLen; + total += (int)strlen(data->name); + total += data->data.string.len; + } else if (data->type == GHIFileDisk) { + GHIPostState* state; + + total += fileBaseLen; + total += (int)strlen(data->name); + total += (int)strlen(data->data.fileDisk.contentType); + state = (GHIPostState*)ArrayNth(connection->postingState.states, i); + // assert(state); + total += (int)state->state.fileDisk.len; + + if (!post->useDIME) + total += (int)strlen(data->data.fileDisk.reportFilename); + + if (post->useDIME) { + // have to include padding bytes! + int padBytes = 0; + + padBytes = 4 - (int)strlen(data->name) % 4; + if (padBytes != 4) + total += padBytes; + padBytes = 4 - (int)strlen(data->data.fileDisk.contentType) % 4; + if (padBytes != 4) + total += padBytes; + padBytes = 4 - (int)state->state.fileDisk.len % 4; + if (padBytes != 4) + total += padBytes; + } + } else if (data->type == GHIFileMemory) { + total += fileBaseLen; + total += (int)strlen(data->name); + total += (int)strlen(data->data.fileMemory.contentType); + total += data->data.fileMemory.len; + + if (!post->useDIME) + total += (int)strlen(data->data.fileMemory.reportFilename); + + if (post->useDIME) { + // have to include padding bytes! + int padBytes = 0; + + padBytes = 4 - (int)strlen(data->name) % 4; + if (padBytes != 4) + total += padBytes; + padBytes = 4 - (int)strlen(data->data.fileMemory.contentType) % 4; + if (padBytes != 4) + total += padBytes; + padBytes = 4 - (int)data->data.fileMemory.len % 4; + if (padBytes != 4) + total += padBytes; + } + } else if (data->type == GHIXmlData) { + int padBytes = 0; + + GS_ASSERT(foundSoapAlready == 0); // only one soap envelope per request + GS_ASSERT(post->useDIME); // soap+file = use DIME + foundSoapAlready = 1; + total += xmlBaseLen; + total += gsXmlWriterGetDataLength(data->data.xml.xml); + + // have to include padding bytes! + padBytes = 4 - (int)gsXmlWriterGetDataLength(data->data.xml.xml) % 4; + if (padBytes != 4) + total += padBytes; + total += (int)strlen(GHI_DIME_SOAPID); + padBytes = 4 - (int)strlen(GHI_DIME_SOAPID) % 4; + if (padBytes != 4) + total += padBytes; + total += (int)strlen(GHI_DIME_SOAPTYPE); + padBytes = 4 - (int)strlen(GHI_DIME_SOAPTYPE) % 4; + if (padBytes != 4) + total += padBytes; + } else { + // assert(0); + return 0; + } + } + + // Add the end. + /////////////// + total += endLen; + + GSI_UNUSED(foundSoapAlready); + return total; +} + +static int ghiPostGetContentLength(struct GHIConnection* connection) { + GHIPost* post = connection->post; + + // assert(post); + if (!post) + return 0; + + if (post->hasFiles) + return ghiPostGetHasFilesContentLength(connection); + + return ghiPostGetNoFilesContentLength(connection); +} + +static GHTTPBool ghiPostStateInit(GHIPostState* state) { + GHIPostDataType type; + + // The type. + //////////// + type = state->data->type; + + // Set the position to sending header. + ////////////////////////////////////// + state->pos = -1; + + // Init based on type. + ////////////////////// + if (type == GHIString) { + } else if (type == GHIFileDisk) { + // Open the file. + ///////////////// +#ifndef NOFILE + state->state.fileDisk.file = + fopen(state->data->data.fileDisk.filename, "rb"); +#endif + if (!state->state.fileDisk.file) + return GHTTPFalse; + + // Get the file length. + /////////////////////// + if (fseek(state->state.fileDisk.file, 0, SEEK_END) != 0) + return GHTTPFalse; + state->state.fileDisk.len = ftell(state->state.fileDisk.file); + if (state->state.fileDisk.len == EOF) + return GHTTPFalse; + rewind(state->state.fileDisk.file); + } else if (type == GHIFileMemory) { + } else if (type == GHIXmlData) { + } else { + // The type didn't match any known types. + ///////////////////////////////////////// + // assert(0); + + return GHTTPFalse; + } + + return GHTTPTrue; +} + +static void ghiPostStateCleanup(GHIPostState* state) { + GHIPostDataType type; + + // The type. + //////////// + type = state->data->type; + + // Init based on type. + ////////////////////// + if (type == GHIString) { + } else if (type == GHIFileDisk) { + if (state->state.fileDisk.file) + fclose(state->state.fileDisk.file); + state->state.fileDisk.file = NULL; + } else if (type == GHIFileMemory) { + } else if (type == GHIXmlData) { + } else { + // The type didn't match any known types. + ///////////////////////////////////////// + // assert(0); + } +} + +GHTTPBool ghiPostInitState(struct GHIConnection* connection) { + int i; + int len; + GHIPostData* data; + GHIPostState state; + GHIPostState* pState; + + // assert(connection->post); + if (!connection->post) + return GHTTPFalse; + + // Create an array for the states. + ////////////////////////////////// + connection->postingState.index = 0; + connection->postingState.bytesPosted = 0; + connection->postingState.totalBytes = 0; + connection->postingState.completed = GHTTPFalse; + connection->postingState.callback = connection->post->callback; + connection->postingState.param = connection->post->param; + len = ArrayLength(connection->post->data); + connection->postingState.states = ArrayNew(sizeof(GHIPostState), len, NULL); + if (!connection->postingState.states) + return GHTTPFalse; + + // Setup all the states. + //////////////////////// + for (i = 0; i < len; i++) { + // Get the data object for this index. + ////////////////////////////////////// + data = (GHIPostData*)ArrayNth(connection->post->data, i); + + // Initialize the state's members. + ////////////////////////////////// + memset(&state, 0, sizeof(GHIPostState)); + state.data = data; + + // Call the init function. + ////////////////////////// + if (!ghiPostStateInit(&state)) { + // We need to cleanup everything we just initialized. + ///////////////////////////////////////////////////// + for (i--; i >= 0; i--) { + pState = (GHIPostState*)ArrayNth(connection->postingState.states, i); + ghiPostStateCleanup(pState); + } + + // Free the array. + ////////////////// + ArrayFree(connection->postingState.states); + connection->postingState.states = NULL; + + return GHTTPFalse; + } + + // Add it to the array. + /////////////////////// + ArrayAppend(connection->postingState.states, &state); + } + + // If this asserts, there aren't the same number of state objects as data + // objects. There should be a 1-to-1 mapping between data and states. + ////////////////////////////////////////////////////////////////////////////////// + // assert(ArrayLength(connection->post->data) == + // ArrayLength(connection->postingState.states)); + + // Get the total number of bytes. + ///////////////////////////////// + connection->postingState.totalBytes = ghiPostGetContentLength(connection); + + // Wait for continue before posting. + // -- Enabled for Soap messages only + // -- Disabled for all other content because many web servers do not + // support it + ////////////////////////////////////////////////////// + if (connection->post->hasSoap == GHTTPTrue) + connection->postingState.waitPostContinue = GHTTPTrue; + else + connection->postingState.waitPostContinue = GHTTPFalse; + + return GHTTPTrue; +} + +void ghiPostCleanupState(struct GHIConnection* connection) { + int i; + int len; + GHIPostState* state; + + // Loop through and call the cleanup function. + ////////////////////////////////////////////// + if (connection->postingState.states) { + len = ArrayLength(connection->postingState.states); + for (i = 0; i < len; i++) { + state = (GHIPostState*)ArrayNth(connection->postingState.states, i); + ghiPostStateCleanup(state); + } + + // Free the array. + ////////////////// + ArrayFree(connection->postingState.states); + connection->postingState.states = NULL; + } + + // Free the post. + ///////////////// + if (connection->post && connection->post->autoFree) { + ghiFreePost(connection->post); + connection->post = NULL; + } +} + +static GHIPostingResult ghiPostStringStateDoPosting(GHIPostState* state, + GHIConnection* connection) { + // GHTTPBool result; + + assert(state->pos >= 0); + + // Is this an empty string? + /////////////////////////// + if (state->data->data.string.len == 0) + return GHIPostingDone; + + assert(state->pos < state->data->data.string.len); + + // If we're doing a simple post, we need to fix invalid characters. + // - only applies to simple posts + /////////////////////////////////////////////////////////////////// + if (!connection->post->hasFiles && !connection->post->hasSoap && + state->data->data.string.invalidChars) { + int i; + int c; + const char* string = state->data->data.string.string; + char hex[4] = "%00"; + GHIBuffer* writeBuffer; + + // When encrypting, we need space for two copies + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None) + writeBuffer = &connection->sendBuffer; + else + writeBuffer = &connection->encodeBuffer; + + // This could probably be done a lot better. + //////////////////////////////////////////// + for (i = 0; (c = string[i]) != 0; i++) { + if (strchr(GHI_LEGAL_URLENCODED_CHARS, c)) { + // Legal. + ///////// + // result = ghiAppendCharToBuffer(writeBuffer, c); + ghiAppendCharToBuffer(writeBuffer, c); + } else if (c == ' ') { + // Space. + ///////// + // result = ghiAppendCharToBuffer(writeBuffer, '+'); + ghiAppendCharToBuffer(writeBuffer, '+'); + } else { + // To hex. + ////////// + assert((c / 16) < 16); + hex[1] = GHI_DIGITS[c / 16]; + hex[2] = GHI_DIGITS[c % 16]; + // result = ghiAppendDataToBuffer(writeBuffer, hex, 3); + ghiAppendDataToBuffer(writeBuffer, hex, 3); + } + } + } else { + // copy the string as-is, encrypting if necessary + GHITrySendResult result = + ghiTrySendThenBuffer(connection, state->data->data.string.string, + state->data->data.string.len); + if (result == GHITrySendError) + return GHIPostingError; + else + return GHIPostingDone; + } + + // Send the URL fixed string + //////////////////////////// + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None) { + // The URL fixed string was written to the send buffer, so send it! + if (!ghiSendBufferedData(connection)) + return GHIPostingError; + + if (connection->sendBuffer.pos == connection->sendBuffer.len) + ghiResetBuffer(&connection->sendBuffer); + return GHIPostingDone; + } else { + // SSL data is in the "to be encrypted" buffer, so wait until + // we have the full MIME form before encrypting (for efficiency) + return GHIPostingDone; + } +} + +static GHIPostingResult ghiPostXmlStateDoPosting(GHIPostState* state, + GHIConnection* connection) { + GSXmlStreamWriter xml = state->data->data.xml.xml; + char pad[3] = {'\0', '\0', '\0'}; + int padlen = 0; + + // make sure state is valid + GS_ASSERT(state->pos >= 0); + GS_ASSERT(connection->post != NULL); + + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) { + padlen = 4 - (gsXmlWriterGetDataLength(xml) % 4); + if (padlen == 4) + padlen = 0; + } + + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) { + // Copy to encode buffer before encrypting + GS_ASSERT(connection->encodeBuffer.len >= + 0); // there must be a header for this soap data! + if (!ghiAppendDataToBuffer(&connection->encodeBuffer, + gsXmlWriterGetData(xml), + gsXmlWriterGetDataLength(xml)) || + !ghiAppendDataToBuffer(&connection->encodeBuffer, pad, padlen) || + !ghiEncryptDataToBuffer(&connection->sendBuffer, + connection->encodeBuffer.data, + connection->encodeBuffer.len)) { + return GHIPostingError; + } + + // Clear out our temporary buffer + ghiResetBuffer(&connection->encodeBuffer); + + // Send what we can now + if (GHTTPFalse == ghiSendBufferedData(connection)) + return GHIPostingError; + + // is there more to send? + if (connection->sendBuffer.pos == connection->sendBuffer.len) + ghiResetBuffer(&connection->sendBuffer); + + return GHIPostingDone; + } else { + GHITrySendResult result; + + // plain text - send immediately + result = ghiTrySendThenBuffer(connection, gsXmlWriterGetData(xml), + gsXmlWriterGetDataLength(xml)); + if (result == GHITrySendError) + return GHIPostingError; + result = ghiTrySendThenBuffer(connection, pad, padlen); + if (result == GHITrySendError) + return GHIPostingError; + return GHIPostingDone; + } +} + +static GHIPostingResult +ghiPostFileDiskStateDoPosting(GHIPostState* state, GHIConnection* connection) { + char buffer[4096]; + int len; + GHITrySendResult result; + + // assert(state->pos >= 0); + // assert(state->pos < state->state.fileDisk.len); + // assert(state->pos == (int)ftell(state->state.fileDisk.file)); + + // Loop while data is being sent. + ///////////////////////////////// + do { + // Read some data from the file. + //////////////////////////////// + len = (int)fread(buffer, 1, sizeof(buffer), state->state.fileDisk.file); + if (len <= 0) { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileReadFailed; + return GHIPostingError; + } + + // Update our position. + /////////////////////// + state->pos += len; + + // Check for too much. + ////////////////////// + if (state->pos > state->state.fileDisk.len) { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileReadFailed; + return GHIPostingError; + } + + // Send. + //////// + result = ghiTrySendThenBuffer(connection, buffer, len); + if (result == GHITrySendError) + return GHIPostingError; + + // Check if we've handled everything. + ///////////////////////////////////// + if (state->pos == state->state.fileDisk.len) { + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) { + char pad[3] = {'\0', '\0', '\0'}; + int padlen = 4 - state->state.fileDisk.len % 4; + if (padlen != 4 && padlen > 0) { + if (GHITrySendError == ghiTrySendThenBuffer(connection, pad, padlen)) + return GHIPostingError; + } + } + return GHIPostingDone; + } + } while (result == GHITrySendSent); + + return GHIPostingPosting; +} + +static GHIPostingResult +ghiPostFileMemoryStateDoPosting(GHIPostState* state, + GHIConnection* connection) { + int rcode; + int len; + + // assert(state->pos >= 0); + + // Is this an empty file? + ///////////////////////// + if (state->data->data.fileMemory.len == 0) + return GHIPostingDone; + + // assert(state->pos < state->data->data.fileMemory.len); + + // Send what we can. + //////////////////// + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None || + connection->encryptor.mEncryptOnBuffer == NULL) { + // Plain text: Send directly from memory + do { + len = (state->data->data.fileMemory.len - state->pos); + rcode = ghiDoSend(connection, + state->data->data.fileMemory.buffer + state->pos, len); + switch (rcode) { + case -1: + return GHIPostingError; + case -2: + return GHIPostingPosting; + } + + // Update the pos. + ////////////////// + state->pos += rcode; + + // Did we send it all? + ////////////////////// + if (state->data->data.fileMemory.len == state->pos) { + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) { + char pad[3] = {'\0', '\0', '\0'}; + int padlen = 4 - state->data->data.fileMemory.len % 4; + if (padlen != 4 && padlen > 0) { + if (GHITrySendError == + ghiTrySendThenBuffer(connection, pad, padlen)) + return GHIPostingError; + } + } + return GHIPostingDone; + } + } while (rcode); + return GHIPostingPosting; // (rcode == 0) ? + } else { + // Encrypted: can't avoid the copy due to encryption+MAC + GHITrySendResult result; + do { + len = (state->data->data.fileMemory.len - state->pos); + len = min(len, GS_SSL_MAX_CONTENTLENGTH); + result = ghiTrySendThenBuffer( + connection, state->data->data.fileMemory.buffer + state->pos, len); + if (result == GHITrySendError) + return GHIPostingError; + + // Update the pos. + ////////////////// + state->pos += len; + + // Did we send it all? + ////////////////////// + if (state->data->data.fileMemory.len == state->pos) { + // when using a DIME, we have to pad to multiple of 4 + if (connection->post->useDIME) { + char pad[3] = {'\0', '\0', '\0'}; + int padlen = 4 - state->data->data.fileMemory.len % 4; + if (padlen != 4 && padlen > 0) { + if (GHITrySendError == + ghiTrySendThenBuffer(connection, pad, padlen)) + return GHIPostingError; + } + } + return GHIPostingDone; + } + } while (result == GHITrySendSent); + return GHIPostingPosting; + } +} + +static GHIPostingResult ghiPostStateDoPosting(GHIPostState* state, + GHIConnection* connection, + GHTTPBool first, GHTTPBool last) { + int len = 0; + GHITrySendResult result; + + // Check for sending the header. + //////////////////////////////// + if (state->pos == -1) { + char buffer[2048]; + + // Bump up the position so we only send the header once. + //////////////////////////////////////////////////////// + state->pos = 0; + + // Check if this is a simple post. + ////////////////////////////////// + if (!connection->post->hasFiles && !connection->post->hasSoap) { + // Simple post only supports strings. + ///////////////////////////////////// + // assert(state->data->type == GHIString); + + // Format the header. + ///////////////////// + if (first) + sprintf(buffer, "%s=", state->data->name); + else + sprintf(buffer, "&%s=", state->data->name); + } else { + // Format the header based on string or file. + ///////////////////////////////////////////// + if (state->data->type == GHIString) { + sprintf(buffer, + "%s" + "Content-Disposition: form-data; " + "name=\"%s\"" CRLF CRLF, + first ? GHI_MULTIPART_BOUNDARY_FIRST + : GHI_MULTIPART_BOUNDARY_NORMAL, + state->data->name); + } else if (state->data->type == GHIXmlData) { + if (connection->post->useDIME) { + // use DIME header + // Copy from a temp struct to circumvent alignment issues + int writePos = 0; + int padBytes = 0; + GHIDimeHeader header; + + header.mVersionAndFlags = GHI_DIME_VERSION; + if (first) + header.mVersionAndFlags |= GHI_DIMEFLAG_FIRSTRECORD; + if (last) + header.mVersionAndFlags |= GHI_DIMEFLAG_LASTRECORD; + header.mTypeT = GHI_DIMETYPE_T_URI; + header.mOptionsLength = 0; + header.mIdLength = htons((short)strlen(GHI_DIME_SOAPID)); + header.mTypeLength = htons((short)strlen(GHI_DIME_SOAPTYPE)); + header.mDataLength = + htonl(gsXmlWriterGetDataLength(state->data->data.xml.xml)); + + memcpy(&buffer[writePos], &header, sizeof(GHIDimeHeader)); + writePos += sizeof(GHIDimeHeader); + + // id + strcpy(&buffer[writePos], GHI_DIME_SOAPID); + writePos += strlen(GHI_DIME_SOAPID); + padBytes = (int)(4 - strlen(GHI_DIME_SOAPID) % 4); + if (padBytes != 4) { + while (padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + // type + strcpy(&buffer[writePos], GHI_DIME_SOAPTYPE); + writePos += strlen(GHI_DIME_SOAPTYPE); + padBytes = (int)(4 - strlen(GHI_DIME_SOAPTYPE) % 4); + if (padBytes != 4) { + while (padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + len = writePos; + } else + buffer[0] = '\0'; + } else if ((state->data->type == GHIFileDisk) || + (state->data->type == GHIFileMemory)) { + const char* filename; + const char* contentType; + int filelen; + + if (state->data->type == GHIFileDisk) { + filelen = state->state.fileDisk.len; + filename = state->data->data.fileDisk.reportFilename; + contentType = state->data->data.fileDisk.contentType; + } else { + filelen = state->data->data.fileMemory.len; + filename = state->data->data.fileMemory.reportFilename; + contentType = state->data->data.fileMemory.contentType; + } + + if (connection->post->useDIME) { + // use DIME header + // Copy from a temp struct to circumvent alignment issues + int writePos = 0; + int padBytes = 0; + GHIDimeHeader header; + + header.mVersionAndFlags = GHI_DIME_VERSION; + if (first) + header.mVersionAndFlags |= GHI_DIMEFLAG_FIRSTRECORD; + if (last) + header.mVersionAndFlags |= GHI_DIMEFLAG_LASTRECORD; + header.mTypeT = GHI_DIMETYPE_T_MEDIA; + header.mOptionsLength = 0; + header.mIdLength = htons((short)strlen(state->data->name)); + header.mTypeLength = htons((short)strlen(contentType)); + header.mDataLength = htonl(filelen); + + memcpy(&buffer[writePos], &header, sizeof(GHIDimeHeader)); + writePos += sizeof(GHIDimeHeader); + + // id + strcpy(&buffer[writePos], state->data->name); + writePos += strlen(state->data->name); + padBytes = (int)(4 - strlen(state->data->name) % 4); + if (padBytes != 4) { + while (padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + // type + strcpy(&buffer[writePos], contentType); + writePos += strlen(contentType); + padBytes = (int)(4 - strlen(contentType) % 4); + if (padBytes != 4) { + while (padBytes-- > 0) + buffer[writePos++] = '\0'; + } + + len = writePos; + } else { + // use MIME header + sprintf(buffer, + "%s" + "Content-Disposition: form-data; " + "name=\"%s\"; " + "filename=\"%s\"" CRLF "Content-Type: %s" CRLF CRLF, + first ? GHI_MULTIPART_BOUNDARY_FIRST + : GHI_MULTIPART_BOUNDARY_NORMAL, + state->data->name, filename, contentType); + } + } else { + // assert(0); + } + } + + // SSL: encrypt and send + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) { + if (len == 0) + len = (int)strlen(buffer); + if (GHTTPFalse == + ghiEncryptDataToBuffer(&connection->sendBuffer, buffer, len)) + return GHIPostingError; + if (GHTTPFalse == ghiSendBufferedData(connection)) + return GHIPostingError; + + // any data remaining? + if (connection->sendBuffer.pos < connection->sendBuffer.len) + return GHIPostingPosting; + + // We sent everything, reset the send buffer to conserve space + ghiResetBuffer(&connection->sendBuffer); + } + // If sending plain text, send right away + else { + // Try sending. (the one-time header) + ///////////////////////////////////// + if (len == 0) + len = (int)strlen(buffer); + result = ghiTrySendThenBuffer(connection, buffer, len); + if (result == GHITrySendError) + return GHIPostingError; + + // If it was buffered, don't try anymore. + ///////////////////////////////////////// + if (result == GHITrySendBuffered) + return GHIPostingPosting; + + // We sent everything, reset the send buffer to conserve space + ghiResetBuffer(&connection->sendBuffer); + } + } + + // Post based on type. + ////////////////////// + if (state->data->type == GHIString) + return ghiPostStringStateDoPosting(state, connection); + + if (state->data->type == GHIXmlData) + return ghiPostXmlStateDoPosting(state, connection); + + if (state->data->type == GHIFileDisk) + return ghiPostFileDiskStateDoPosting(state, connection); + + // assert(state->data->type == GHIFileMemory); + return ghiPostFileMemoryStateDoPosting(state, connection); +} + +GHIPostingResult ghiPostDoPosting(struct GHIConnection* connection) { + GHIPostingResult postingResult; + GHITrySendResult trySendResult; + GHIPostingState* postingState; + GHIPostState* postState; + int len; + + // assert(connection); + // assert(connection->post); + // assert(connection->postingState.states); + // assert(ArrayLength(connection->post->data) == + // ArrayLength(connection->postingState.states)); + // assert(connection->postingState.index >= 0); + // assert(connection->postingState.index <= + // ArrayLength(connection->postingState.states)); + + // Cache some stuff. + //////////////////// + postingState = &connection->postingState; + len = ArrayLength(postingState->states); + + // Check for buffered data. + /////////////////////////// + if (connection->sendBuffer.pos < connection->sendBuffer.len) { + // Send the buffered data. + ////////////////////////// + if (!ghiSendBufferedData(connection)) + return GHIPostingError; + + // Check if we couldn't send it all. + //////////////////////////////////// + if (connection->sendBuffer.pos < connection->sendBuffer.len) + return GHIPostingPosting; + + // We sent it all, so reset the buffer. + /////////////////////////////////////// + ghiResetBuffer(&connection->sendBuffer); + + // If uploading a DIME attachment, wait for HTTP continue. + ////////////////////////////////////////////////////////// + if (connection->postingState.waitPostContinue) + return GHIPostingWaitForContinue; + + // Was that all that's left? + //////////////////////////// + if (connection->postingState.index == len) + return GHIPostingDone; + } + + // When posting soap and DIME attachments, we should terminate the + // header and wait for a response. This will either be a continue or + // a server error. + if (connection->postingState.waitPostContinue) { + if (connection->post->hasFiles || connection->post->hasSoap) { + // terminate the header and wait for a response + GS_ASSERT(connection->encodeBuffer.len == 0); + trySendResult = ghiTrySendThenBuffer(connection, CRLF, (int)strlen(CRLF)); + if (trySendResult == GHITrySendError) + return GHIPostingError; + else if (trySendResult == GHITrySendBuffered) + return GHIPostingPosting; + else { + if (connection->postingState.waitPostContinue == GHTTPTrue) + return GHIPostingWaitForContinue; + // else + // fall through + } + } else { + // simple posts don't have to wait + connection->postingState.waitPostContinue = GHTTPFalse; + // fall through + } + } + + // Loop while there's data to upload. + ///////////////////////////////////// + while (postingState->index < len) { + // Get the current data state. + ////////////////////////////// + postState = + (GHIPostState*)ArrayNth(postingState->states, postingState->index); + // assert(postState); + + // Upload the current data. + /////////////////////////// + postingResult = ghiPostStateDoPosting( + postState, connection, + (postingState->index == 0) ? GHTTPTrue : GHTTPFalse, + (postingState->index == (ArrayLength(postingState->states) - 1)) + ? GHTTPTrue + : GHTTPFalse); + + // Check for error. + /////////////////// + if (postingResult == GHIPostingError) { + // Make sure we already set the error stuff. + //////////////////////////////////////////// + // assert(connection->completed && connection->result); + + return GHIPostingError; + } + + // Check for still posting. + /////////////////////////// + if (postingResult == GHIPostingPosting) + return GHIPostingPosting; + + // One more done. + ///////////////// + postingState->index++; + } + + // Encrypt and send anything left in the encode buffer + // -- for example, when posting string data we don't encrypt until we have + // the entire string (for efficiency only) + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None) { + if (connection->encodeBuffer.len > 0) { + GS_ASSERT(connection->encodeBuffer.pos == + 0); // if you hit this, it means you forgot the clear the buffer + if (GHTTPFalse == ghiEncryptDataToBuffer(&connection->sendBuffer, + connection->encodeBuffer.data, + connection->encodeBuffer.len)) { + return GHIPostingError; + } + ghiResetBuffer(&connection->encodeBuffer); + } + } + + // Send or buffer the end marker. + ///////////////////////////////// + if (connection->post->hasFiles && !connection->post->useDIME) { + GS_ASSERT(!connection->post->hasSoap); + + // send MIME boundary end + trySendResult = + ghiTrySendThenBuffer(connection, GHI_MULTIPART_BOUNDARY_END, + (int)strlen(GHI_MULTIPART_BOUNDARY_END)); + if (trySendResult == GHITrySendError) + return GHIPostingError; + } + + // We're not done if there's stuff in the buffer. + ///////////////////////////////////////////////// + if (connection->sendBuffer.pos < connection->sendBuffer.len) + return GHIPostingPosting; + + return GHIPostingDone; +} diff --git a/source/gamespy/ghttp/ghttpPost.h b/source/gamespy/ghttp/ghttpPost.h new file mode 100644 index 000000000..761caecb0 --- /dev/null +++ b/source/gamespy/ghttp/ghttpPost.h @@ -0,0 +1,75 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "../darray.h" +#include "ghttp.h" +#include "ghttpBuffer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + GHIPostingError, + GHIPostingDone, + GHIPostingPosting, + GHIPostingWaitForContinue +} GHIPostingResult; + +typedef struct GHIPostingState { + DArray states; + int index; + int bytesPosted; + int totalBytes; + ghttpPostCallback callback; + void* param; + GHTTPBool waitPostContinue; // does DIME need to wait for continue? + GHTTPBool completed; // prevent re-post in the event of a redirect. +} GHIPostingState; + +GHTTPPost ghiNewPost(void); + +void ghiPostSetAutoFree(GHTTPPost post, GHTTPBool autoFree); + +GHTTPBool ghiIsPostAutoFree(GHTTPPost post); + +void ghiFreePost(GHTTPPost post); + +GHTTPBool ghiPostAddString(GHTTPPost post, const char* name, + const char* string); + +GHTTPBool ghiPostAddFileFromDisk(GHTTPPost post, const char* name, + const char* filename, + const char* reportFilename, + const char* contentType); + +GHTTPBool ghiPostAddFileFromMemory(GHTTPPost post, const char* name, + const char* buffer, int bufferLen, + const char* reportFilename, + const char* contentType); + +GHTTPBool ghiPostAddXml(GHTTPPost post, GSXmlStreamWriter xmlSoap); + +void ghiPostSetCallback(GHTTPPost post, ghttpPostCallback callback, + void* param); + +const char* ghiPostGetContentType(struct GHIConnection* connection); + +GHTTPBool ghiPostInitState(struct GHIConnection* connection); + +void ghiPostCleanupState(struct GHIConnection* connection); + +GHIPostingResult ghiPostDoPosting(struct GHIConnection* connection); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/ghttp/ghttpProcess.c b/source/gamespy/ghttp/ghttpProcess.c new file mode 100644 index 000000000..5373ac647 --- /dev/null +++ b/source/gamespy/ghttp/ghttpProcess.c @@ -0,0 +1,1607 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "ghttpProcess.h" +#include "ghttpCallbacks.h" +#include "ghttpCommon.h" +#include "ghttpMain.h" +#include "ghttpPost.h" + +// Parse the URL into: +// server address (and IP) +// server port +// request path. +///////////////////////////// +static GHTTPBool ghiParseURL(GHIConnection* connection) { + char* URL; + int nIndex; + char tempChar; + char* str; + + assert(connection); + if (!connection) + return GHTTPFalse; + + // 2002.Apr.18.JED - Make sure we have an URL + ///////////////////////////////////////////// + assert(connection->URL); + if (!connection->URL) + return GHTTPFalse; + + URL = connection->URL; + + // Check for "http://". + ////////////////////// + if (strncmp(URL, "http://", 7) == 0) { + connection->protocol = GHIHttp; + URL += 7; + } else if (strncmp(URL, "https://", 8) == 0) { + connection->protocol = GHIHttps; + URL += 8; + } else { + return GHTTPFalse; + } + + // Read the address. + //////////////////// + nIndex = (int)strcspn(URL, ":/"); + tempChar = URL[nIndex]; + URL[nIndex] = '\0'; + connection->serverAddress = goastrdup(URL); + if (!connection->serverAddress) + return GHTTPFalse; + URL[nIndex] = tempChar; + URL += nIndex; + + // Read the port. + ///////////////// + if (*URL == ':') { + URL++; + connection->serverPort = (unsigned short)atoi(URL); + if (!connection->serverPort) + return GHTTPFalse; + do { + URL++; + } while (*URL && (*URL != '/')); + } else { + if (connection->protocol == GHIHttps) + connection->serverPort = GHI_DEFAULT_SECURE_PORT; + else + connection->serverPort = GHI_DEFAULT_PORT; + } + + // Read the path. + ///////////////// + if (!*URL) + URL = "/"; + connection->requestPath = goastrdup(URL); + while ((str = strchr(connection->requestPath, ' ')) != NULL) + *str = '+'; + if (!connection->requestPath) + return GHTTPFalse; + + return GHTTPTrue; +} + +/**************** +** SOCKET INIT ** +****************/ +void ghiDoSocketInit(GHIConnection* connection) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Socket Initialization\n"); + + // Progress. + //////////// + ghiCallProgressCallback(connection, NULL, 0); + + // Init sockets. + //////////////// + SocketStartUp(); + + // Parse the URL. + ///////////////// + if (!ghiParseURL(connection)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPParseURLFailed; + return; + } + + // Check if an encryption type was set. + /////////////////////////////////////// + if ((connection->protocol == GHIHttps) && + (connection->encryptor.mEngine == GHTTPEncryptionEngine_None)) { + // default to gamespy engine + // ghttpSetRequestEncryptionEngine(connection->request, + // GHTTPEncryptionEngine_GameSpy); + + // 02OCT07 BED: Design changed so that only one engine can be active at a + // time + // Use the active engine rather than GameSpy + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Encryption engine not set for HTTPS. Using default engine\r\n"); + ghttpSetRequestEncryptionEngine(connection->request, + GHTTPEncryptionEngine_Default); + } else if ((connection->protocol != GHIHttps) && + (connection->encryptor.mEngine != GHTTPEncryptionEngine_None)) { + // URL is not secured + ghttpSetRequestEncryptionEngine(connection->request, + GHTTPEncryptionEngine_None); + + gsDebugFormat( + GSIDebugCat_HTTP, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Encryption engine set for unsecured URL. Removing encryption.\r\n"); + } + + // Init the encryption engine. + ////////////////////////////// + if ((connection->protocol == GHIHttps) && + connection->encryptor.mInitialized == GHTTPFalse) { + GHIEncryptionResult aResult; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Debug, + "Initializing SSL engine\n"); + aResult = + (connection->encryptor.mInitFunc)(connection, &connection->encryptor); + if (aResult == GHIEncryptionResult_Error) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, + GSIDebugLevel_WarmError, + "Failed to initialize SSL engine\n"); + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + + // Progress. + //////////// + connection->state = GHTTPHostLookup; + ghiCallProgressCallback(connection, NULL, 0); +} + +/**************** +** HOST LOOKUP ** +****************/ +void ghiDoHostLookup(GHIConnection* connection) { + HOSTENT* host = NULL; + const char* server = NULL; + +#if !defined(GSI_NO_THREADS) + // Check to see if asynch lookup is taking place + //////////////////////////////////////////////// + if (connection->handle) { + GSI_UNUSED(host); + GSI_UNUSED(server); + + // Lookup incomplete - set to lookupPending state + ///////////////////////////////////////////////// + connection->state = GHTTPLookupPending; + ghiCallProgressCallback(connection, NULL, 0); + return; + } +#endif + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Host Lookup\n"); + + // Check for using a proxy. + /////////////////////////// + if (connection->proxyOverrideServer) // request specific proxy + server = connection->proxyOverrideServer; + else if (ghiProxyAddress) + server = ghiProxyAddress; + else + server = connection->serverAddress; + + // Try resolving the address as an IP a.b.c.d number. + ///////////////////////////////////////////////////// + connection->serverIP = inet_addr(server); + if (connection->serverIP == INADDR_NONE) { + // Try resolving with DNS - asynchronously if possible + ////////////////////////// + +#if defined(GSI_NO_THREADS) + // blocking version - no threads + host = gethostbyname(server); + + if (host == NULL) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, + GSIDebugLevel_HotError, "Host Lookup failed\n"); + connection->completed = GHTTPTrue; + connection->result = GHTTPHostLookupFailed; + return; + } + + // Get the IP. + ////////////// + connection->serverIP = *(unsigned int*)host->h_addr_list[0]; +#else + + // threaded version + if (gsiStartResolvingHostname(server, &(connection->handle)) == -1) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, + GSIDebugLevel_HotError, "Thread Creation Failed\n"); + + // make sure to set it back to NULL + connection->handle = NULL; + + // exit with Host Lookup Failed error message + connection->completed = GHTTPTrue; + connection->result = GHTTPHostLookupFailed; + return; + } else { + // thread created properly - continue into lookupPending state + GSI_UNUSED(host); + } +#endif + } + + // Progress. + //////////// + + // check to see if lookup is complete + if (connection->serverIP == INADDR_NONE) { + // lookup incomplete - set to lookupPending state + connection->state = GHTTPLookupPending; + ghiCallProgressCallback(connection, NULL, 0); + } else { + // lookup complete - proceed with connection stage + connection->state = GHTTPConnecting; + ghiCallProgressCallback(connection, NULL, 0); + } +} + +/****************** +** LOOKUP PENDING** +******************/ +void ghiDoLookupPending(GHIConnection* connection) { +#if !defined(GSI_NO_THREADS) + // check if lookup is complete + connection->serverIP = gsiGetResolvedIP(connection->handle); + + // make sure there were no problems with the IP + if (connection->serverIP == GSI_ERROR_RESOLVING_HOSTNAME) { + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_HotError, + "Error resolving hostname\n"); + + // set to NULL + connection->handle = NULL; + + // notify that the lookup failed + connection->completed = GHTTPTrue; + connection->result = GHTTPHostLookupFailed; + return; + } + + if (connection->serverIP == GSI_STILL_RESOLVING_HOSTNAME) { + // lookup incomplete - keep calling this function + connection->state = GHTTPLookupPending; + ghiCallProgressCallback(connection, NULL, 0); + } else { + // set to NULL + connection->handle = NULL; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "DNS lookup complete\n"); + // looks like we got ourselves a server! proceed with connection phase + connection->state = GHTTPConnecting; + ghiCallProgressCallback(connection, NULL, 0); + } +#endif +} + +/*************** +** CONNECTING ** +***************/ +void ghiDoConnecting(GHIConnection* connection) { + int rcode; + SOCKADDR_IN address; + int writeFlag; + int exceptFlag; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Connecting\n"); + + // If we don't have a socket yet, set it up. + //////////////////////////////////////////// + if (connection->socket == INVALID_SOCKET) { + // Create the socket. + ///////////////////// + connection->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (connection->socket == INVALID_SOCKET) { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = GOAGetLastError(connection->socket); + return; + } + + // Set the socket to non-blocking. + ////////////////////////////////// + if (!SetSockBlocking(connection->socket, 0)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = GOAGetLastError(connection->socket); + return; + } + + int var_28 = 0x2000; + if (setsockopt(connection->socket, 0xffff, 0x1001, (char*)(&var_28), 4) < + 0) { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + connection->socketError = GOAGetLastError(connection->socket); + return; + } + + // If throttling, use a small receive buffer. + ///////////////////////////////////////////// + if (connection->throttle) + SetReceiveBufferSize(connection->socket, ghiThrottleBufferSize); + + // Setup the server address. + //////////////////////////// + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + if (connection->proxyOverrideServer) + address.sin_port = htons(connection->proxyOverridePort); + else if (ghiProxyAddress) + address.sin_port = htons(ghiProxyPort); + else + address.sin_port = htons(connection->serverPort); + address.sin_addr.s_addr = connection->serverIP; + + // Start the connect. + ///////////////////// + rcode = connect(connection->socket, (SOCKADDR*)&address, sizeof(address)); + if (gsiSocketIsError(rcode)) { + int socketError = GOAGetLastError(connection->socket); + if ((socketError != WSAEWOULDBLOCK) && (socketError != WSAEINPROGRESS) && + (socketError != WSAETIMEDOUT)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPConnectFailed; + connection->socketError = socketError; + return; + } + } + } + + // Check if the connect has completed. + ////////////////////////////////////// + rcode = GSISocketSelect(connection->socket, NULL, &writeFlag, &exceptFlag); + if ((gsiSocketIsError(rcode)) || ((rcode == 1) && exceptFlag)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPConnectFailed; + if (gsiSocketIsError(rcode)) + connection->socketError = GOAGetLastError(connection->socket); + else + connection->socketError = 0; + return; + } + + // Check if we're connected. + //////////////////////////// + if ((rcode == 1) && writeFlag) { + // Progress. + //////////// + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None) + connection->state = GHTTPSendingRequest; + else + connection->state = GHTTPSecuringSession; + ghiCallProgressCallback(connection, NULL, 0); + } +} + +/****************** +** SSL HANDSHAKE ** +*******************/ +void ghiDoSecuringSession(GHIConnection* connection) { + // Client sends hello + // Server sends hello, [certificate], [certificate request], [server key + // exchange] Client sends client , , [certificate], + // [certificate verify] Server sends finished + + // skip the ghiDoSecuringSession step... + // - when not using encryption or + // - if the connection is already secure + + GHIRecvResult result; + + // This buffer must be large enough to receive any handshake messages. + char buffer[1025]; + int bufferLen; + + // Start the handshake process + if (connection->encryptor.mSessionStarted == GHTTPFalse) { + GHIEncryptionResult aResult; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Securing Session\n"); + + GS_ASSERT(connection->encryptor.mStartFunc != NULL); + if (connection->encryptor.mStartFunc != NULL) { + aResult = (connection->encryptor.mStartFunc)(connection, + &connection->encryptor); + if (aResult == GHIEncryptionResult_Error) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + + // Check for session established + if (connection->encryptor.mSessionEstablished) { + connection->state = GHTTPSendingRequest; + ghiCallProgressCallback(connection, NULL, 0); + return; + } + } + + // if the SSL lib controls the handshake, just keep calling + // start until the session has been established + GS_ASSERT(connection->encryptor.mSessionEstablished == GHTTPFalse); + if (connection->encryptor.mLibSendsHandshakeMessages) { + GS_ASSERT(connection->encryptor.mStartFunc != NULL); + if (connection->encryptor.mStartFunc != NULL) { + GHIEncryptionResult aResult = (connection->encryptor.mStartFunc)( + connection, &connection->encryptor); + if (aResult == GHIEncryptionResult_Error) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } + + // Check for session established + if (connection->encryptor.mSessionEstablished) { + connection->state = GHTTPSendingRequest; + ghiCallProgressCallback(connection, NULL, 0); + } + } else { + // Continue to send and receive handshake messages until the session has + // been secured Send any session messages + if (connection->sendBuffer.pos < connection->sendBuffer.len) { + if (!ghiSendBufferedData(connection)) + return; // Todo: handle error? + + // Check for data still buffered. + ///////////////////////////////// + if (connection->sendBuffer.pos < connection->sendBuffer.len) + return; + + ghiResetBuffer(&connection->sendBuffer); + } + + // Get data + bufferLen = sizeof(buffer); + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error or conn closed. + /////////////////////////////// + if ((result == GHIError) || (result == GHIConnClosed)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + + // check for received data + if (result == GHIRecvData) { + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + return; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + + // Check for session established (handshake complete) + if (connection->encryptor.mSessionEstablished) { + connection->state = GHTTPSendingRequest; + ghiCallProgressCallback(connection, NULL, 0); + return; + } + } + } +} + +/******************** +** SENDING REQUEST ** +********************/ +void ghiDoSendingRequest(GHIConnection* connection) { + char* requestType; + int oldPos; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Sending Request\n"); + + // If we haven't filled the send buffer yet, do that first. + /////////////////////////////////////////////////////////// + if (!connection->sendBuffer.len) { + // Using a pointer so we can pipe output to a different destination + // (e.g. for efficiency and testing purposes we may want to encrypt in + // larger blocks) + GHIBuffer* writeBuffer = NULL; + if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None || + connection->encryptor.mEncryptOnBuffer == GHTTPFalse) { + // write directly to send buffer + writeBuffer = &connection->sendBuffer; + } else { + // write to temp buffer so it can be encrypted before sending + writeBuffer = &connection->encodeBuffer; + } + + // Fill in the request line. + //////////////////////////// + if (connection->post && !connection->postingState.completed) + requestType = "POST "; + else if (connection->type == GHIHEAD) + requestType = "HEAD "; + else + requestType = "GET "; + ghiAppendDataToBuffer(writeBuffer, requestType, 0); + if (connection->proxyOverrideServer || ghiProxyAddress) + ghiAppendDataToBuffer(writeBuffer, connection->URL, 0); + else + ghiAppendDataToBuffer(writeBuffer, connection->requestPath, 0); + ghiAppendDataToBuffer(writeBuffer, " HTTP/1.1" CRLF, 0); + + // Add the host header. + /////////////////////// + if (connection->serverPort == GHI_DEFAULT_PORT) { + ghiAppendHeaderToBuffer(writeBuffer, "Host", connection->serverAddress); + } else { + ghiAppendDataToBuffer(writeBuffer, "Host: ", 0); + ghiAppendDataToBuffer(writeBuffer, connection->serverAddress, 0); + ghiAppendCharToBuffer(writeBuffer, ':'); + ghiAppendIntToBuffer(writeBuffer, connection->serverPort); + ghiAppendDataToBuffer(writeBuffer, CRLF, 2); + } + + // Add the user-agent header. + ///////////////////////////// + if (connection->sendHeaders == NULL || + strstr(connection->sendHeaders, "User-Agent") == NULL) + ghiAppendHeaderToBuffer(writeBuffer, "User-Agent", "GameSpyHTTP/1.0"); + + // Check for persistant connections. + ////////////////////////////////////// + if (connection->persistConnection) + ghiAppendHeaderToBuffer(writeBuffer, "Connection", "Keep-Alive"); + else + ghiAppendHeaderToBuffer(writeBuffer, "Connection", "close"); + + // Post needs extra headers. + //////////////////////////// + if (connection->post && !connection->postingState.completed) { + char buf[16]; + + // Add the content-length header. + ///////////////////////////////// + sprintf(buf, "%d", connection->postingState.totalBytes); + ghiAppendHeaderToBuffer(writeBuffer, "Content-Length", buf); + + // Add the content-type header. + /////////////////////////////// + ghiAppendHeaderToBuffer(writeBuffer, "Content-Type", + ghiPostGetContentType(connection)); + } + + // Not supported by all servers + // ghiAppendHeaderToBuffer(writeBuffer, "Expect", "100-continue"); + + // Add user-headers. + //////////////////// + if (connection->sendHeaders) + ghiAppendDataToBuffer(writeBuffer, connection->sendHeaders, 0); + + // Add the blank line to finish it off. + /////////////////////////////////////// + ghiAppendDataToBuffer(writeBuffer, CRLF, 2); + + // Encrypt it, if necessary. This copy is unfortunate since matrixSsl can't + // encrypt in place + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) { + GS_ASSERT(writeBuffer == &connection->encodeBuffer); + if (!ghiEncryptDataToBuffer(&connection->sendBuffer, writeBuffer->data, + writeBuffer->len)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + ghiResetBuffer(writeBuffer); + } + } + + // Store the old position. + ////////////////////////// + oldPos = connection->sendBuffer.pos; + + // Send what we can. + //////////////////// + if (!ghiSendBufferedData(connection)) + return; + +// Log anything we sent. +//////////////////////// +#ifdef HTTP_LOG + if (connection->sendBuffer.pos != oldPos) + ghiLogRequest(connection->sendBuffer.data + oldPos, + connection->sendBuffer.pos - oldPos); +#endif + + // Check for data still buffered. + ///////////////////////////////// + if (connection->sendBuffer.pos < connection->sendBuffer.len) + return; + + // Clear the send buffer. + ///////////////////////// + ghiResetBuffer(&connection->sendBuffer); + + // Finished sending. + //////////////////// + if (connection->post && !connection->postingState.completed) + connection->state = GHTTPPosting; + else + connection->state = GHTTPWaiting; + ghiCallProgressCallback(connection, NULL, 0); + + GSI_UNUSED(oldPos); +} + +/************ +** POSTING ** +************/ +void ghiDoPosting(GHIConnection* connection) { + GHIPostingResult result; + int oldBytesPosted; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Posting\n"); + + // Store the old bytes posted. + ////////////////////////////// + oldBytesPosted = connection->postingState.bytesPosted; + + // Do some posting. + /////////////////// + result = ghiPostDoPosting(connection); + + // Check for an error. + ////////////////////// + if (result == GHIPostingError) { + int rcode = 0; + int readFlag = 0; + + // Make sure we already set the error stuff. + //////////////////////////////////////////// + assert(connection->completed && connection->result); + + // Cleanup the posting state. + ///////////////////////////// + ghiPostCleanupState(connection); + + // Is there a server response? + rcode = GSISocketSelect(connection->socket, &readFlag, NULL, NULL); + if ((rcode == 1) && readFlag) { + // Ready to receive. + //////////////////// + connection->state = GHTTPReceivingStatus; + ghiCallProgressCallback(connection, NULL, 0); + } + return; + } + + // When sending DIME wait for initial + // continue before uploading + ///////////////////////////////////////// + if (result == GHIPostingWaitForContinue) { + // Disable by skipping the wait + connection->postingState.waitPostContinue = GHTTPFalse; + return; + + // connection->state = GHTTPWaiting; + // return; + } + + // Call the callback if we sent anything. + ///////////////////////////////////////// + if (oldBytesPosted != connection->postingState.bytesPosted) + ghiCallPostCallback(connection); + + // Check for done. + ////////////////// + if (result == GHIPostingDone) { + // Cleanup the posting state. + ///////////////////////////// + ghiPostCleanupState(connection); + connection->postingState.completed = GHTTPTrue; + + // Set the new connection state. + //////////////////////////////// + connection->state = GHTTPWaiting; + ghiCallProgressCallback(connection, NULL, 0); + + return; + } +} + +/************ +** WAITING ** +************/ +void ghiDoWaiting(GHIConnection* connection) { + int readFlag; + int exceptFlag; + int rcode; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Waiting\n"); + + // We're waiting to receive something. + ////////////////////////////////////// + rcode = GSISocketSelect(connection->socket, &readFlag, NULL, &exceptFlag); + if ((gsiSocketIsError(rcode)) || ((rcode == 1) && exceptFlag)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPSocketFailed; + if (gsiSocketIsError(rcode)) + connection->socketError = GOAGetLastError(connection->socket); + else + connection->socketError = 0; + return; + } + + // Check for waiting data. + ////////////////////////// + if ((rcode == 1) && readFlag) { + // Ready to receive. + //////////////////// + connection->state = GHTTPReceivingStatus; + ghiCallProgressCallback(connection, NULL, 0); + } +} + +// Parse the status line. +///////////////////////// +static GHTTPBool ghiParseStatus(GHIConnection* connection) { + int majorVersion; + int minorVersion; + int statusCode; + int statusStringIndex; + int rcode; + char c; + + GS_ASSERT(connection); + GS_ASSERT(connection->recvBuffer.len > 0); + +#if defined(_X360) + // Xbox 360 needs "%n" to be manually enabled + { + int oldPrintCountValue = _set_printf_count_output(1); +#endif + + // Parse the string. + //////////////////// + rcode = + sscanf(connection->recvBuffer.data, "HTTP/%d.%d %d%n", &majorVersion, + &minorVersion, &statusCode, &statusStringIndex); + +#if defined(_X360) + _set_printf_count_output(oldPrintCountValue); + } +#endif + + // Check what we got. + ///////////////////// + if ((rcode != 3) || // Not all fields read. + //!*statusString || // No status string. PANTS|9.16.02 - + //! apparently some servers don't return a status string + (majorVersion < 1) || // Major version is less than 1. + (statusCode < 100) || // 1xx is lowest status code. + (statusCode >= 600)) // 5xx is highest status code. + { + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + return GHTTPFalse; + } + + // Figure out where the status string starts. + ///////////////////////////////////////////// + while ((c = connection->recvBuffer.data[statusStringIndex]) != '\0' && + isspace(c)) + statusStringIndex++; + + // Set connection members. + ////////////////////////// + connection->statusMajorVersion = majorVersion; + connection->statusMinorVersion = minorVersion; + connection->statusCode = statusCode; + connection->statusStringIndex = statusStringIndex; + + return GHTTPTrue; +} + +/********************* +** RECEIVING STATUS ** +*********************/ +void ghiDoReceivingStatus(GHIConnection* connection) { + char buffer[1024]; + int bufferLen; + GHIRecvResult result; + char* endOfStatus; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Receiving Status\n"); + + // Get data. + //////////// + bufferLen = sizeof(buffer); + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error or no data. + /////////////////////////// + if (result == GHIError) + return; + if (result == GHINoData) + return; + + // Only append data if we got data. + /////////////////////////////////// + if (result == GHIRecvData) { + // Check for encryption. + //////////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) { + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + return; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + return; + } + } else { + // Add the data directly to the buffer. + /////////////////////////////////////// + if (!ghiAppendDataToBuffer(&connection->recvBuffer, buffer, bufferLen)) + return; + } + } + + // Check if the status is finished. + ///////////////////////////////////// + endOfStatus = strstr(connection->recvBuffer.data, CRLF); + if (endOfStatus) { + int statusLength; + + // Cap the status. + ////////////////// + *endOfStatus = '\0'; + + // Get the status length. + ///////////////////////// + statusLength = (endOfStatus - connection->recvBuffer.data); + + // Log it. + ////////// + ghiLogResponse(connection->recvBuffer.data, statusLength); + ghiLogResponse("\n", 1); + + // Parse the status line. + ///////////////////////// + if (!ghiParseStatus(connection)) + return; + + // Store the position of the start of the headers. + ////////////////////////////////////////////////// + connection->headerStringIndex = (statusLength + 2); + + if (connection->statusCode == 100 && + connection->postingState.waitPostContinue) { + // DIME uploads must wait for initial continue before posting + connection->postingState.waitPostContinue = GHTTPFalse; + ghiResetBuffer(&connection->recvBuffer); // clear the continue + connection->state = GHTTPPosting; + ghiCallProgressCallback(connection, NULL, 0); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, + GSIDebugLevel_Comment, "Got HTTP continue\r\n"); + } else { + // We're receiving headers now. + /////////////////////////////// + connection->state = GHTTPReceivingHeaders; + ghiCallProgressCallback(connection, NULL, 0); + } + } else if (result == GHIConnClosed) { + // Connection closed. + ///////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + connection->socketError = GOAGetLastError(connection->socket); + return; + } +} + +// Delivers incoming file data to the appropriate place, +// then calls the progress callback. +// For GetFile, adds to buffer. +// For SaveFile, writes to disk. +// For StreamFile, does nothing. +// Returns false on error. +//////////////////////////////////////////////////////// +static GHTTPBool ghiDeliverIncomingFileData(GHIConnection* connection, + char* data, int len) { + char* buffer = NULL; + int bufferLen = 0; + + // Add this to the total. + ///////////////////////// + connection->fileBytesReceived += len; + + // Do we have the whole thing? + ////////////////////////////// + if (connection->fileBytesReceived == connection->totalSize || + connection->connectionClosed) + connection->completed = GHTTPTrue; + + // Handle based on type. + //////////////////////// + if (connection->type == GHIGET) { + // Put this in the buffer. + ////////////////////////// + if (!ghiAppendDataToBuffer(&connection->getFileBuffer, data, len)) + return GHTTPFalse; + + // Set the callback parameters. + /////////////////////////////// + buffer = connection->getFileBuffer.data; + bufferLen = connection->getFileBuffer.len; + } else if (connection->type == GHISAVE) { + int bytesWritten = 0; +#ifndef NOFILE + bytesWritten = fwrite(data, 1, len, connection->saveFile); +#endif + if (bytesWritten != len) { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileWriteFailed; + return GHTTPFalse; + } + + // Set the callback parameters. + /////////////////////////////// + buffer = data; + bufferLen = len; + } else if (connection->type == GHISTREAM) { + // Set the callback parameters. + /////////////////////////////// + buffer = data; + bufferLen = len; + } + + // Call the callback. + ///////////////////// + ghiCallProgressCallback(connection, buffer, bufferLen); + + return GHTTPTrue; +} + +// Gets the size of a chunk from a chunk header. +// Returns -1 on error. +//////////////////////////////////////////////// +static int ghiParseChunkSize(GHIConnection* connection) { + char* header; + int len; + int num; + int rcode; + + header = connection->chunkHeader; + len = connection->chunkHeaderLen; + + assert(len); + GSI_UNUSED(len); + + rcode = sscanf(header, "%x", &num); + if (rcode != 1) + return -1; + + return num; +} + +// Appends the data to the chunk header buffer. +/////////////////////////////////////////////// +static void ghiAppendToChunkHeaderBuffer(GHIConnection* connection, char* data, + int len) { + assert(connection); + assert(data); + assert(len >= 0); + + // This can happen at the end of a header. + ////////////////////////////////////////// + if (len == 0) + return; + + // Is there room in the buffer? If not, just + // skip, we most likely already have the chunk size. + //////////////////////////////////////////////////// + if (connection->chunkHeaderLen < CHUNK_HEADER_SIZE) { + int numBytes; + + // How many bytes are we copying? + ///////////////////////////////// + numBytes = min(CHUNK_HEADER_SIZE - connection->chunkHeaderLen, len); + + // Move the (possibly partial) header into the buffer. + ////////////////////////////////////////////////////// + memcpy(connection->chunkHeader + connection->chunkHeaderLen, data, + (unsigned int)numBytes); + + // Cap off the buffer. + ////////////////////// + connection->chunkHeaderLen += numBytes; + connection->chunkHeader[connection->chunkHeaderLen] = '\0'; + } +} + +// Does any neccessary processing to incoming file data +// before it gets delivered. This includes un-chunking. +// Returns false on error. +//////////////////////////////////////////////////////// +static GHTTPBool ghiProcessIncomingFileData(GHIConnection* connection, + char* data, int len) { + assert(connection); + assert(data); + assert(len > 0); + + // Is this a chunked transfer? + ////////////////////////////// + if (connection->chunkedTransfer) { + // Loop while there's stuff to process. + /////////////////////////////////////// + while (len > 0) { + // Reading a header? + //////////////////// + if (connection->chunkReadingState == CRHeader) { + char* endOfHeader; + + // Have we hit the LF (as in the CRLF ending the header)? + ///////////////////////////////////////////////////////// + endOfHeader = strchr(data, 0xA); + if (endOfHeader) { + // Append what we have to the buffer. + ///////////////////////////////////// + ghiAppendToChunkHeaderBuffer(connection, data, endOfHeader - data); + + // Adjust data and len. + /////////////////////// + endOfHeader++; + len -= (endOfHeader - data); + data = endOfHeader; + + // Read the chunk size. + /////////////////////// + connection->chunkBytesLeft = ghiParseChunkSize(connection); + if (connection->chunkBytesLeft == -1) { + // There was an error reading the chunk size. + ///////////////////////////////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + return GHTTPFalse; + } + + // Set the chunk reading state. + /////////////////////////////// + if (connection->chunkBytesLeft == 0) { + connection->chunkReadingState = CRFooter; + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, + GSIDebugLevel_RawDump, "Reading footer\n"); + } else { + connection->chunkReadingState = CRChunk; + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, + GSIDebugLevel_RawDump, "Reading %d byte chunk\n", + connection->chunkBytesLeft); + } + } else { + // Move it all into the buffer. + /////////////////////////////// + ghiAppendToChunkHeaderBuffer(connection, data, len); + + // Nothing else we can do now. + ////////////////////////////// + return GHTTPTrue; + } + } + // Reading a chunk? + /////////////////// + else if (connection->chunkReadingState == CRChunk) { + int numBytes; + + // How many bytes of data are we dealing with? + ////////////////////////////////////////////// + numBytes = min(connection->chunkBytesLeft, len); + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, + GSIDebugLevel_RawDump, "Read %d bytes of chunk\n", + numBytes); + + // Deliver the bytes. + ///////////////////// + if (!ghiDeliverIncomingFileData(connection, data, numBytes)) + return GHTTPFalse; + + // Adjust data and len. + /////////////////////// + data += numBytes; + len -= numBytes; + + // Figure out how many bytes left in chunk. + /////////////////////////////////////////// + connection->chunkBytesLeft -= numBytes; + + // Did we finish the chunk? + /////////////////////////// + if (connection->chunkBytesLeft == 0) + connection->chunkReadingState = CRCRLF; + } + // Reading a chunk footer (CRLF)? + ///////////////////////////////// + else if (connection->chunkReadingState == CRCRLF) { + char* endOfFooter; + + // Did we get an LF? + //////////////////// + endOfFooter = strchr(data, 0xA); + + // The footer hasn't ended yet. + /////////////////////////////// + if (!endOfFooter) + return GHTTPTrue; + + // Adjust data and len. + /////////////////////// + endOfFooter++; + len -= (endOfFooter - data); + data = endOfFooter; + + // Set up for reading the next header. + ////////////////////////////////////// + connection->chunkHeader[0] = '\0'; + connection->chunkHeaderLen = 0; + connection->chunkBytesLeft = 0; + connection->chunkReadingState = CRHeader; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, + GSIDebugLevel_RawDump, "Read chunk footer\n"); + } + // Reading the footer? + ////////////////////// + else if (connection->chunkReadingState == CRFooter) { + // We're done. + ////////////// + connection->completed = GHTTPTrue; + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_Network, + GSIDebugLevel_RawDump, "Finished reading chunks\n"); + + return GHTTPTrue; + } + // Bad state! + ///////////// + else { + assert(0); + return GHTTPFalse; + } + } + + return GHTTPTrue; + } + + // Regular transfer, just deliver it. + ///////////////////////////////////// + return ghiDeliverIncomingFileData(connection, data, len); +} + +/********************** +** RECEIVING HEADERS ** +**********************/ +void ghiDoReceivingHeaders(GHIConnection* connection) { + char* buffer; + int bufferLen; + GHIRecvResult result; + GHTTPBool hasHeaders = GHTTPTrue; + char* headers; + char* endOfHeaders = NULL; + + buffer = gsimalloc(4096); + if (buffer == 0) { + connection->completed = GHTTPTrue; + connection->result = GHTTPOutOfMemory; + return; + } + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Receiving Headers\n"); + + // Get data. + //////////// + bufferLen = 4096; // buffer + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error, no data, conn closed. + ////////////////////////////////////// + if (result == GHIError) + goto _exit; + if (result == GHINoData) + goto _exit; + + // Only append data if we got data. + /////////////////////////////////// + if (result == GHIRecvData) { + // Check for encryption. + //////////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) { + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + goto _exit; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + goto _exit; + } + } else { + // Add the data directly to the buffer. + /////////////////////////////////////// + if (!ghiAppendDataToBuffer(&connection->recvBuffer, buffer, bufferLen)) + goto _exit; + } + } + + // Cache a pointer to the front of the headers. + /////////////////////////////////////////////// + headers = (connection->recvBuffer.data + connection->headerStringIndex); + + // Check if the headers are finished. + ///////////////////////////////////// + if (((connection->statusCode / 100) == 1) && + (strncmp(headers, "\r\n", 2) == 0 || + strncmp(headers, "\xA\xA", 2) == 0)) { + // If a continue doesn't have a header (immediate CRLF) move on to next + // status + endOfHeaders = headers; + hasHeaders = GHTTPFalse; + } else { + endOfHeaders = strstr(headers, CRLF CRLF); + } + if (!endOfHeaders) { + endOfHeaders = + strstr(headers, "\xA\xA"); // some servers seem to use LFs only?! Seen + // in 302 redirect. (28may01/bgw) + } + if (endOfHeaders) { + char* fileStart; + int fileLength; + // int headersLength; + char* contentLength; + +#ifdef HTTP_LOG + int headersLength; +#endif + + // Clear off the empty line. + //////////////////////////// + if (GHTTPTrue == hasHeaders) + endOfHeaders += 2; + *endOfHeaders = '\0'; + + // Figure out where the file starts, and how many bytes. + //////////////////////////////////////////////////////// +#ifdef HTTP_LOG + headersLength = (endOfHeaders - headers); +#endif + + fileStart = (endOfHeaders + 2); + fileLength = (connection->recvBuffer.len - + (fileStart - connection->recvBuffer.data)); + + // Set the headers buffer's new length. + /////////////////////////////////////// + connection->recvBuffer.len = + (endOfHeaders - connection->recvBuffer.data + 1); + connection->recvBuffer.pos = connection->recvBuffer.len; + + // Log it. + ////////// +#ifdef HTTP_LOG + ghiLogResponse(headers, headersLength); + ghiLogResponse("\n", 1); +#endif + + // Check for continue. + ////////////////////// + if ((connection->statusCode / 100) == 1) { + if (fileLength) { + // Move any data to the front of the buffer. + //////////////////////////////////////////// + memmove(connection->recvBuffer.data, fileStart, + (unsigned int)fileLength + 1); + connection->recvBuffer.len = fileLength; + } else { + // Reset the buffer. + ///////////////////////// + ghiResetBuffer(&connection->recvBuffer); + } + + // Some posts must wait for continue before uploading + // Check if we should return to posting + if (connection->postingState.waitPostContinue) { + connection->postingState.waitPostContinue = GHTTPFalse; + connection->state = GHTTPPosting; + ghiCallProgressCallback(connection, NULL, 0); + } + + // We're back to receiving status. + ////////////////////////////////// + connection->state = GHTTPReceivingStatus; + ghiCallProgressCallback(connection, NULL, 0); + + goto _exit; + } + + // Check for redirection. + ///////////////////////// + if ((connection->statusCode / 100) == 3) { + char* location; + + // Are we over our redirection count? + ///////////////////////////////////// + if (connection->redirectCount > 10) { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileNotFound; + goto _exit; + } + + // Find the new location. + ///////////////////////// + location = strstr(headers, "Location:"); + if (location) { + char* end; + + // Find the start of the URL. + ///////////////////////////// + location += 9; + while (isspace(*location)) + location++; + + // Find the end. + //////////////// + for (end = location; *end && !isspace(*end); end++) { + }; + *end = '\0'; + + // Check if this is not a full URL. + /////////////////////////////////// + if (*location == '/') { + int len; + + // Recompose the URL ourselves. + /////////////////////////////// + len = (int)(strlen(connection->serverAddress) + 13 + + strlen(location) + 1); + connection->redirectURL = (char*)gsimalloc((unsigned int)len); + if (!connection->redirectURL) { + connection->completed = GHTTPTrue; + connection->result = GHTTPOutOfMemory; + } + sprintf(connection->redirectURL, "http://%s:%d%s", + connection->serverAddress, connection->serverPort, location); + } else { + // Set the redirect URL. + //////////////////////// + connection->redirectURL = goastrdup(location); + if (!connection->redirectURL) { + connection->completed = GHTTPTrue; + connection->result = GHTTPOutOfMemory; + } + } + + goto _exit; + } + } + + // If we know the file-length, set it. + ////////////////////////////////////// + contentLength = strstr(headers, "Content-Length:"); + if (contentLength) { + // Verify that the download size is something we can handle + /////////////////////////////////////////////////////////// +#if (GSI_MAX_INTEGRAL_BITS >= 64) + char szMaxSize[] = "9223372036854775807"; // == GSI_MAX_I64 +#else + char szMaxSize[] = "2147483647"; // == GSI_MAX_I32 +#endif + char* pStart = contentLength + 16; + char* pEnd = pStart; + int nMaxLen = (int)strlen(szMaxSize); + + // Skip to the end of the line + while (pEnd && *pEnd != '\0' && *pEnd != '\n' && *pEnd != '\r' && + *pEnd != ' ') + pEnd++; + + if (pEnd - pStart > nMaxLen) { + // Wow, that IS a big number + connection->completed = GHTTPTrue; + connection->result = GHTTPFileToBig; + goto _exit; + } else if (pEnd - pStart == nMaxLen) { + // Same length, maybe a bigger number + if (strncmp(pStart, szMaxSize, (unsigned int)(pEnd - pStart)) >= 0) { + connection->completed = GHTTPTrue; + connection->result = GHTTPFileToBig; + goto _exit; + } + } + + // Record the full size of the expected download + //////////////////////////////////////////////// +#if (GSI_MAX_INTEGRAL_BITS >= 64) + connection->totalSize = _atoi64(pStart); +#else + connection->totalSize = atoi(pStart); +#endif + } + + // Check the chunky. + //////////////////// + connection->chunkedTransfer = + (strstr(headers, "Transfer-Encoding: chunked") != NULL) ? GHTTPTrue + : GHTTPFalse; + if (connection->chunkedTransfer) { + connection->chunkHeader[0] = '\0'; + connection->chunkHeaderLen = 0; + connection->chunkBytesLeft = 0; + connection->chunkReadingState = CRHeader; + } + + // If we're just getting headers, or only posting data, we're done. + /////////////////////////////////////////////////////////////////// + if ((connection->type == GHIHEAD) || (connection->type == GHIPOST)) { + connection->completed = GHTTPTrue; + goto _exit; + } + + // We're receiving file data now. + ///////////////////////////////// + connection->state = GHTTPReceivingFile; + + // Is this an empty file? + ///////////////////////// + if (contentLength && !connection->totalSize) { + connection->completed = GHTTPTrue; + goto _exit; + } + + // If any of the body has arrived, handle it. + ///////////////////////////////////////////// + if (fileLength > 0) + ghiProcessIncomingFileData(connection, fileStart, fileLength); + + // Don't reset the buffer -- we store status and header info + // ghiResetBuffer(&connection->recvBuffer); + } else if (result == GHIConnClosed) { + // The conn was closed, and we didn't finish the headers - bad. + /////////////////////////////////////////////////////////////// + connection->completed = GHTTPTrue; + connection->result = GHTTPBadResponse; + connection->socketError = GOAGetLastError(connection->socket); + } +_exit: + gsifree(buffer); +} + +/******************* +** RECEIVING FILE ** +*******************/ +void ghiDoReceivingFile(GHIConnection* connection) { + char* buffer; + int bufferLen; + GHIRecvResult result; + gsi_time start_time = current_time(); + gsi_time running_time = 0; + + buffer = gsimalloc(8192); + if (buffer == 0) { + connection->completed = GHTTPTrue; + connection->result = GHTTPOutOfMemory; + return; + } + + gsDebugFormat(GSIDebugCat_HTTP, GSIDebugType_State, GSIDebugLevel_Comment, + "Receiving File\n"); + + while (!connection->completed && (running_time < connection->maxRecvTime)) { + // Get data. + //////////// + bufferLen = 8192; // buffer + result = ghiDoReceive(connection, buffer, &bufferLen); + + // Handle error, no data, conn closed. + ////////////////////////////////////// + if (result == GHIError) + goto _exit; + if (result == GHINoData) + goto _exit; + if (result == GHIConnClosed) { + // The file is done (hopefully). + //////////////////////////////// + connection->completed = GHTTPTrue; + + if (connection->totalSize > 0 && + connection->fileBytesReceived < connection->totalSize) + connection->result = GHTTPFileIncomplete; + goto _exit; + } + + // Check for encryption. + //////////////////////// + if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None && + connection->encryptor.mEncryptOnBuffer == GHTTPTrue) { + char* decryptedData; + int decryptedLen; + + // Append new encrypted data to anything we've held over + // We have to do this because we can't decrypt partial SSL messages + if (!ghiAppendDataToBuffer(&connection->decodeBuffer, buffer, bufferLen)) + goto _exit; + + // Previously decrypted parts of the file have already been handled. + connection->recvBuffer.len = connection->recvBuffer.pos; + + // Decrypt as much as we can + if (!ghiDecryptReceivedData(connection)) { + connection->completed = GHTTPTrue; + connection->result = GHTTPEncryptionError; + goto _exit; + } + + // Check for decrypted data. + //////////////////////////// + decryptedLen = (connection->recvBuffer.len - connection->recvBuffer.pos); + if (decryptedLen) { + // Process the data. + //////////////////// + decryptedData = + (connection->recvBuffer.data + connection->recvBuffer.pos); + if (!ghiProcessIncomingFileData(connection, decryptedData, + decryptedLen)) + goto _exit; + } + } else { + // Process the data. + //////////////////// + if (!ghiProcessIncomingFileData(connection, buffer, bufferLen)) + goto _exit; + } + + running_time = current_time() - start_time; + } + +_exit: + gsifree(buffer); +} diff --git a/source/gamespy/ghttp/ghttpProcess.h b/source/gamespy/ghttp/ghttpProcess.h new file mode 100644 index 000000000..9dc67c795 --- /dev/null +++ b/source/gamespy/ghttp/ghttpProcess.h @@ -0,0 +1,34 @@ +/* +GameSpy GHTTP SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "ghttpConnection.h" +#include "ghttpMain.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void ghiDoSocketInit(GHIConnection* connection); +void ghiDoHostLookup(GHIConnection* connection); +void ghiDoLookupPending(GHIConnection* connection); +void ghiDoConnecting(GHIConnection* connection); +void ghiDoSecuringSession(GHIConnection* connection); +void ghiDoSendingRequest(GHIConnection* connection); +void ghiDoPosting(GHIConnection* connection); +void ghiDoWaiting(GHIConnection* connection); +void ghiDoReceivingStatus(GHIConnection* connection); +void ghiDoReceivingHeaders(GHIConnection* connection); +void ghiDoReceivingFile(GHIConnection* connection); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gstats/gbucket.c b/source/gamespy/gstats/gbucket.c new file mode 100644 index 000000000..b459f978e --- /dev/null +++ b/source/gamespy/gstats/gbucket.c @@ -0,0 +1,365 @@ +/****** +gbucket.c +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +******/ + +/******** +INCLUDES +********/ +#include "gbucket.h" +#include "../common/gsCommon.h" +#include "../hashtable.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******** +TYPEDEFS +********/ +struct bucketset_s { + HashTable buckets; +}; + +typedef struct bucket_s { + char* name; + BucketType type; + int nvals; // for averaging + union { + int ival; + double fval; + char* sval; + } vals; +} bucket_t; + +typedef struct dumpdata_s { + char* data; + unsigned int maxlen; + unsigned int len; +} dumpdata_t; + +/******** +PROTOTYPES +********/ +static void DumpMap(void* elem, void* clientData); +static void BucketFree(void* elem); +static int BucketCompare(const void* entry1, const void* entry2); +static int BucketHash(const void* elem, int numbuckets); +static void* DoSet(bucket_t* pbucket, void* value); +static void* DoGet(bucket_t* pbucket); +static bucket_t* DoFind(bucketset_t set, char* name); + +/******** +VARS +********/ +bucketset_t g_buckets; + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ +bucketset_t NewBucketSet(void) { + bucketset_t set; + + set = (bucketset_t)gsimalloc(sizeof(struct bucketset_s)); + assert(set); + set->buckets = + TableNew(sizeof(bucket_t), 32, BucketHash, BucketCompare, BucketFree); + + g_buckets = set; + return set; +} + +void FreeBucketSet(bucketset_t set) { + assert(set); + assert(set->buckets); + TableFree(set->buckets); + gsifree(set); +} + +char* DumpBucketSet(bucketset_t set) { + dumpdata_t data; + if (set == NULL) + set = g_buckets; + assert(set); + data.data = (char*)gsimalloc(128); // alloc an initial buffer + data.data[0] = 0; + data.len = 0; + data.maxlen = 128; + TableMap(set->buckets, DumpMap, &data); + return data.data; +} + +void* BucketNew(bucketset_t set, char* name, BucketType type, + void* initialvalue) { + bucket_t bucket; + + if (set == NULL) + set = g_buckets; + assert(set); + bucket.name = goastrdup(name); + bucket.type = type; + bucket.vals.sval = NULL; + bucket.nvals = 1; + DoSet(&bucket, initialvalue); + TableEnter(set->buckets, &bucket); + return DoGet(DoFind(set, name)); +} + +void* BucketSet(bucketset_t set, char* name, void* value) { + bucket_t* pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + pbucket->nvals = 0; + return DoSet(pbucket, value); +} + +void* BucketGet(bucketset_t set, char* name) { + return DoGet(DoFind(set, name)); +} + +void* BucketAdd(bucketset_t set, char* name, void* value) { + bucket_t* pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint((*(int*)DoGet(pbucket)) + (*(int*)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, + bfloat((*(double*)DoGet(pbucket)) + (*(double*)value))); + // else, string -- just concat + return BucketConcat(set, name, value); +} + +void* BucketSub(bucketset_t set, char* name, void* value) { + bucket_t* pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint((*(int*)DoGet(pbucket)) - (*(int*)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, + bfloat((*(double*)DoGet(pbucket)) - (*(double*)value))); + // else, string -- just ignore + return DoGet(pbucket); +} + +void* BucketMult(bucketset_t set, char* name, void* value) { + bucket_t* pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint((*(int*)DoGet(pbucket)) * (*(int*)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, + bfloat((*(double*)DoGet(pbucket)) * (*(double*)value))); + // else, string -- just ignore + return DoGet(pbucket); +} + +void* BucketDiv(bucketset_t set, char* name, void* value) { + bucket_t* pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint((*(int*)DoGet(pbucket)) / (*(int*)value))); + if (pbucket->type == bt_float) + return DoSet(pbucket, + bfloat((*(double*)DoGet(pbucket)) / (*(double*)value))); + // else, string -- just ignore + return DoGet(pbucket); +} + +void* BucketConcat(bucketset_t set, char* name, void* value) { + bucket_t* pbucket = DoFind(set, name); + char *temp, *s; + if (!pbucket) + return NULL; + + assert(pbucket->type == bt_string); + s = DoGet(pbucket); + temp = (char*)gsimalloc(strlen(s) + strlen(value) + 1); + strcpy(temp, s); + strcat(temp, value); + + DoSet(pbucket, temp); + gsifree(temp); + + return DoGet(pbucket); +} + +#define AVG(cur, new, num) (((cur * num) + new) / (++num)) +void* BucketAvg(bucketset_t set, char* name, void* value) { + bucket_t* pbucket = DoFind(set, name); + if (!pbucket) + return NULL; + + if (pbucket->type == bt_int) + return DoSet(pbucket, bint(AVG((*(int*)DoGet(pbucket)), (*(int*)value), + pbucket->nvals))); + if (pbucket->type == bt_float) + return DoSet(pbucket, bfloat(AVG((*(double*)DoGet(pbucket)), + (*(double*)value), pbucket->nvals))); + // else, string -- just ignore + return DoGet(pbucket); +} + +/* Note: these are NOT thread safe! */ +void* bint(int i) { + static int j; + j = i; + return &j; +} + +void* bfloat(double f) { + static double g; + g = f; + return &g; +} + +/*********** + * UTILITY FUNCTIONS + **********/ +static void DumpMap(void* elem, void* clientData) { + bucket_t* bucket = (bucket_t*)elem; + dumpdata_t* data = (dumpdata_t*)clientData; + unsigned int minlen; + + // find out if we need to resize! + minlen = strlen(bucket->name) + 3; + if (bucket->type == bt_int || bucket->type == bt_float) + minlen += data->len + 16; + else if (bucket->type == bt_string) + minlen += data->len + strlen(bucket->vals.sval); + + if (data->maxlen <= minlen) { + if (data->maxlen == 0) + data->maxlen = minlen * 2; + else + data->maxlen *= 2; + data->data = gsirealloc(data->data, data->maxlen); + } + + switch (bucket->type) { + case bt_int: + data->len += (unsigned int)sprintf(data->data + data->len, "\\%s\\%d", + bucket->name, bucket->vals.ival); + break; + case bt_float: + data->len += (unsigned int)sprintf(data->data + data->len, "\\%s\\%f", + bucket->name, bucket->vals.fval); + break; + case bt_string: + data->len += (unsigned int)sprintf(data->data + data->len, "\\%s\\%s", + bucket->name, bucket->vals.sval); + break; + } +} + +static char* stripchars(char* s) { + char* p = s; + while (*s) { + if (*s == '\\') + *s = '/'; + s++; + } + return p; +} + +static void* DoSet(bucket_t* pbucket, void* value) { + if (pbucket->type == bt_int) + pbucket->vals.ival = *(int*)value; + else if (pbucket->type == bt_float) + pbucket->vals.fval = *(double*)value; + else if (pbucket->type == bt_string) { + if (pbucket->vals.sval != NULL) + gsifree(pbucket->vals.sval); + pbucket->vals.sval = + (value == NULL ? NULL : stripchars(goastrdup((char*)value))); + } + return DoGet(pbucket); +} + +static void* DoGet(bucket_t* pbucket) { + if (!pbucket) + return NULL; + if (pbucket->type == bt_string) + return pbucket->vals.sval; + else // since it's a union, we can return any member + return &pbucket->vals.sval; +} + +static bucket_t* DoFind(bucketset_t set, char* name) { + bucket_t tbucket; + + if (set == NULL) + set = g_buckets; + assert(set); + + tbucket.name = name; + return (bucket_t*)TableLookup(set->buckets, &tbucket); +} + +/* NonTermHash + * ---------- + * The hash code is computed using a method called "linear congruence." + * This hash function has the additional feature of being case-insensitive, + */ +#define MULTIPLIER -1664117991 +static int BucketHash(const void* elem, int numbuckets) { + unsigned int i; + unsigned int len; + unsigned int hashcode = 0; + + char* s = ((bucket_t*)elem)->name; + len = strlen(s); + for (i = 0; i < len; i++) { + hashcode = (unsigned int)((int)hashcode * MULTIPLIER + tolower(s[i])); + } + return (int)(hashcode % numbuckets); +} + +/* CaseInsensitiveCompare + * ---------------------- + * Comparison function passed to qsort to sort an array of + * strings in alphabetical order. It uses strcasecmp which is + * identical to strcmp, except that it doesn't consider case of the + * characters when comparing them, thus it sorts case-insensitively. + */ +static int CaseInsensitiveCompare(const void* entry1, const void* entry2) { + return strcasecmp(*(char**)entry1, *(char**)entry2); +} + +/* keyval + * Compares two buckets (case insensative) + */ +static int BucketCompare(const void* entry1, const void* entry2) { + + return CaseInsensitiveCompare(&((bucket_t*)entry1)->name, + &((bucket_t*)entry2)->name); +} + +/* KeyValFree + * Frees the memory INSIDE a Bucket structure + */ +static void BucketFree(void* elem) { + gsifree(((bucket_t*)elem)->name); + if (((bucket_t*)elem)->type == bt_string && + ((bucket_t*)elem)->vals.sval != NULL) + gsifree(((bucket_t*)elem)->vals.sval); +} + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gstats/gbucket.h b/source/gamespy/gstats/gbucket.h new file mode 100644 index 000000000..0f12fcba1 --- /dev/null +++ b/source/gamespy/gstats/gbucket.h @@ -0,0 +1,48 @@ +/****** +gbucket.h +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + +Please see the GameSpy Stats and Tracking SDK for more info +You should not need to use the functions in this file, they +are used to manage the buckets by the gstats SDK. +Use the type-safe bucket functions in the gstats SDK instead. +******/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct bucketset_s* bucketset_t; +typedef enum { bt_int, bt_float, bt_string } BucketType; + +bucketset_t NewBucketSet(void); +void FreeBucketSet(bucketset_t set); +char* DumpBucketSet(bucketset_t set); + +void* BucketNew(bucketset_t set, char* name, BucketType type, + void* initialvalue); +void* BucketSet(bucketset_t set, char* name, void* value); +void* BucketAdd(bucketset_t set, char* name, void* value); +void* BucketSub(bucketset_t set, char* name, void* value); +void* BucketMult(bucketset_t set, char* name, void* value); +void* BucketDiv(bucketset_t set, char* name, void* value); +void* BucketConcat(bucketset_t set, char* name, void* value); +void* BucketAvg(bucketset_t set, char* name, void* value); +void* BucketGet(bucketset_t set, char* name); + +/* Helper functions */ +void* bint(int i); +void* bfloat(double f); +#define bstring(a) ((void*)a) + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gstats/gpersist.h b/source/gamespy/gstats/gpersist.h new file mode 100644 index 000000000..9bb7c416c --- /dev/null +++ b/source/gamespy/gstats/gpersist.h @@ -0,0 +1,443 @@ +/****** +gpersist.h +GameSpy Persistent Storage SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Persistent Storage SDK for more info + +*****/ +// todo: get/set @ offset / length + +#pragma once + +#include "gstats.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/************************* +The following defines and prototypes are included +inside the "gstats.h" file, but listed here as well for easy reference +since they are also used by the Persistent Storage SDK. +The comments for them have also been changed to reflect their +specific use inside the Persistent Storage SDK. +**************************/ +#if 0 + /* Error codes */ +#define GE_NOERROR 0 +#define GE_NOSOCKET 1 /* Unable to create a socket */ +#define GE_NODNS 2 /* Unable to resolve a DNS name */ +#define GE_NOCONNECT \ + 3 /* Unable to connect to stats server, or connection lost */ +#define GE_BUSY 4 /* Not used */ +#define GE_DATAERROR 5 /* Bad data from the stats server */ + + /* You need to fill these in with your game-specific info */ + extern char gcd_secret_key[256]; + extern char gcd_gamename[256]; + + /******** + InitStatsConnection + + DESCRIPTION + Opens a connection to the stats / persistent storage server. Should be done before calling + any of the other persistent storage functions. May block for 1-2 secs + while the connection is established so you will want to do this before + gameplay starts or in another thread. + + PARAMETERS + gameport: integer port associated with your server (may be the same as + your developer spec query port). If not appropriate for your game, pass in 0. + + RETURNS + GE_NODNS: Unable to resolve stats server DNS + GE_NOSOCKET: Unable to create data socket + GE_NOCONNECT: Unable to connect to stats server + GE_DATAERROR: Unable to receive challenge from stats server, or bad challenge + GE_NOERROR: Connected to stats server and ready to send data + + Note: If the connection fails, all Persistent Storage functions will fail. + *********/ + int InitStatsConnection(int gameport); + + /******** + IsStatsConnected + + DESCRIPTION + Returns whether or not you are currently connected to the stats server. Even + if your initial connection was successful, you may lose connection later and + want to try to reconnnect. + If a callback returns unsuccessfully, check this function to see if it was + because of a disconnection. + + RETURNS + 1 if connected, 0 otherwise + *********/ + int IsStatsConnected(); + + /******** + CloseStatsConnection + + DESCRIPTION + Closes the connection to the stats server. You should do this when done + with the connection. + *********/ + void CloseStatsConnection(void); + + /******** + GetChallenge + + DESCRIPTION + Returns the string to pass as the challenge to the GenerateAuth function. + + PARAMETERS + game: Pass in NULL (or your current game, if you are also using the Stats SDK) + + RETURNS + A string to pass to GenerateAuth to create the authentication hash + *********/ + char *GetChallenge(statsgame_t game); + + /******** + GenerateAuth + + DESCRIPTION + Used to generate on the "challengeresponse" parameter for the PreAuthenticatePlayer + functions. + + PARAMETERS + challenge: The challenge string generated by GetChallange. + password: The CD Key (un-hashed) or profile password + response: The output authentication string + + RETURNS + A pointer to response + *********/ + char *GenerateAuth(char *challenge, gsi_char *password,/*[out]*/char response[33]); +#endif //#ifdef 0 section from gstats.h + +/************************* +The rest of the prototypes in this file are specific to +the Persistent Storage SDK +**************************/ + +#define GenerateAuth GenerateAuthA +#define PreAuthenticatePlayerCD PreAuthenticatePlayerCDA +#define GetProfileIDFromCD GetProfileIDFromCDA +#define GetPersistDataValues GetPersistDataValuesA +#define GetPersistDataValuesModified GetPersistDataValuesModifiedA +#define SetPersistDataValues SetPersistDataValuesA + +/******** +persisttype_t +There are 4 types of persistent data stored for each player: +pd_private_ro: Readable only by the authenticated client it belongs to, can only +by set on the server pd_private_rw: Readable only by the authenticated client it +belongs to, set by the authenticated client it belongs to pd_public_ro: Readable +by any client, can only be set on the server pd_public_rw: Readable by any +client, set by the authenicated client is belongs to +*********/ +typedef enum { + pd_private_ro, + pd_private_rw, + pd_public_ro, + pd_public_rw +} persisttype_t; + +/***************** +CALLBACK FUNCTIONS +*****************/ + +/**************** +PersAuthCallbackFn + +DESCRIPTION +This type of function is passed to the two PreAuthentication functions. +It returns the result of the Authentication request. + +PARAMETERS +localid: The localid number passed into the PreAuthenticate function +profileid: If authentication was successful, the profileid for this user +authenticated: 1 if the player was authenticated < 1 otherwise +errmsg: Error returned by the server to indicate why the player was not +authenticated instance: Opaque value passed into the PreAuthenticate function +(for your use) +*****************/ +typedef void (*PersAuthCallbackFn)(int localid, int profileid, + int authenticated, gsi_char* errmsg, + void* instance); + +/**************** +PersDataCallbackFn + +DESCRIPTION +This type of function is passed to the two GetPersistData functions. +It returns the result of the data request. +localid + +PARAMETERS +localid: The localid number passed into the GetPersistData function +profileid: The profileid of the user who the data was requested for +type: The type of persistent data being returned +index: The persistent data index +success: 1 if the data was retrieved successfully + 2 if the data had not been modified since the time requested + < 1 if there was an error +modified: The last time the data for this index was modified (any persist type) + Only returned if success is 1 +data: Pointer to the data being returned. Note: you must use or copy + off the data before returning from the callback, as it may be freed or +overwritten once the callback is complete. len: Length of the data being +returned. 0 indicates that no data was stored on the server (if success was 1) +instance: Opaque value passed into the GetPersistData function (for your use) +*****************/ +typedef void (*PersDataCallbackFn)(int localid, int profileid, + persisttype_t type, int index, int success, + time_t modified, char* data, int len, + void* instance); + +/**************** +PersDataSaveCallbackFn + +DESCRIPTION +This type of function is passed to the two SetPersistData functions. +It returns the result of the set data request. + +PARAMETERS +localid: The localid number passed into the SetPersistData function +profileid: The profileid of the user who the data is being saved for +success: 1 if the data was saved successfully, < 1 otherwise +modified: The time recorded on the backend for last modification +instance: Opaque value passed into the SetPersistData function (for your use) +*****************/ +typedef void (*PersDataSaveCallbackFn)(int localid, int profileid, + persisttype_t type, int index, + int success, time_t modified, + void* instance); + +/**************** +ProfileCallbackFn + +DESCRIPTION +This type of function is passed to the GetProfileIDFromCD function. +It returns the result of the lookup request. + +PARAMETERS +localid: The localid number passed into the GetProfileIDFromCD function +profileid: The profileid of the requested user, if the lookup was successful +success: 1 if the lookup was successful, < 1 otherwise +instance: Opaque value passed into the GetProfileIDFromCD function (for your +use) +*****************/ +typedef void (*ProfileCallbackFn)(int localid, int profileid, int success, + void* instance); + +/*************************** +PERSISTENT STORAGE FUNCTIONS +****************************/ + +/**************** +PreAuthenticatePlayerPM +PreAuthenticatePlayerCD + +DESCRIPTION +These two functions are used to authenticate a player on the Stats server. +A player MUST be authenticated before getting private persistent data, or +setting public or private data. +If the StatsServer connection is ever lost and reconnected (using +InitStatsConnection) the player must be reauthenticated before reading / writing +their data. PreAuthenticatePlayerPM authenticates players using the Presence & +Messaging SDK account info PreAuthenticatePlayerCD authenticates players using +the CDKey SDK CD Key. Typically you will only use one of these in your game +(depending on whether you use the Presence & Messaging SDK, or the CD Key SDK), +however they can both be used in the same game if needed. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the +callback to allow you to identify which player it is referring to. profileid: +(PreAuthenticatePlayerPM) The profileid of the player being authenicated. This +can be obtained in the Presence & Messaging SDK through gpIDFromProfile() nick: +(PreAuthenticatePlayerCD) Nickname of the player to authenticate. Note that the +nickname is not actually authenticated, it is simply used to determine which +profile under the authenticated CD Key to use. Each CD Key can have mutiple +profiles, each using a different nick. keyhash: (PreAuthenticatePlayerCD) Hash +of the player's CD Key. If used on the server, this can be obtained from +gcd_getkeyhash On the client, you can easily get the hash by calling +GenerateAuth() with challenge as an empty string ("") and the CD Key has the +password parameter. challengeresponse: Result of the GenerateAuth() call, after +passing in the challenge and the client's password or CD Key PersAuthCallbackFn: +Callback to be called after the authentication is complete instance: Pointer +that will be passed to the callback function (for your use) Typically used for +passing an object or structure pointer into the callback. +*****************/ +void PreAuthenticatePlayerPartner(int localid, const char* authtoken, + const char* challengeresponse, + PersAuthCallbackFn callback, void* instance); +void PreAuthenticatePlayerPM(int localid, int profileid, + const char* challengeresponse, + PersAuthCallbackFn callback, void* instance); +void PreAuthenticatePlayerCD(int localid, const gsi_char* nick, + const char* keyhash, const char* challengeresponse, + PersAuthCallbackFn callback, void* instance); + +/**************** +GetProfileIDFromCD + +DESCRIPTION +Given a nickname and CD Key hash, this will lookup the profileid for the user. +If the user has never authenticated (and has no persistent data associated with +them), the callback will indicate a failure. No persistent data can be retreived +for the user, since they don't have any stored. Persistent data can be stored, +but only after authenticating with PreAuthenticatePlayerCD. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the +callback to allow you to identify which player it is referring to. nick: The +nick of the user whose profileid you are looking up keyhash: The CD Key Hash of +the user whose profileid you are looking up ProfileCallbackFn: Callback to be +called when the lookup is completed instance: Pointer that will be passed to the +callback function (for your use) +*****************/ +void GetProfileIDFromCD(int localid, const gsi_char* nick, const char* keyhash, + ProfileCallbackFn callback, void* instance); + +/**************** +GetPersistData[Modified] + +DESCRIPTION +Gets the entire block of persistent data for a user. +The data and length are returned in the callback function. +Note that only an authenticated player can get their private data. Any +player can get any other player's public data. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the +callback to allow you to identify which player it is referring to. profileid: +The profileid of the player whose data you are looking up. Returned by +gpIDFromProfile() in the Presence & Messaging SDK, or using GetProfileIDFromCD +type: The type of persistent data you are looking up +index: Each profile can have multiple persistent data records associated with +them. Usually you just want to use index 0. modifiedsince: A time value to limit +the request for data. Data will only be returned if it has been modified since +the time provided. If data has not been modified since that time, the callback +will be called with a success value that indicates it is unmodified. Note: +modification time is tracked for the given profileid/index, not on a per +persisttype basis PersDataCallbackFn: Callback that will be called with the data +when it is returned ProfileCallbackFn: Callback to be called when the lookup is +completed instance: Pointer that will be passed to the callback function (for +your use) +*****************/ +void GetPersistData(int localid, int profileid, persisttype_t type, int index, + PersDataCallbackFn callback, void* instance); +void GetPersistDataModified(int localid, int profileid, persisttype_t type, + int index, time_t modifiedsince, + PersDataCallbackFn callback, void* instance); + +/**************** +GetPersistDataValues[Modified] + +DESCRIPTION +If you store your data in key\value delimited pairs, GetPersistDataValues will +allow you to easily retrieve a subset of the stored data. To retrieve the entire +data set, use GetPersistData. The data will be returned as a null-terminated +string, unless no data is available (in which case len will be 0 in the +callback). + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the +callback to allow you to identify which player it is referring to. profileid: +The profileid of the player whose data you are looking up. Returned by +gpIDFromProfile() in the Presence & Messaging SDK, or using GetProfileIDFromCD +type: The type of persistent data you are looking up +index: Each profile can have multiple persistent data records associated with +them. Usually you just want to use index 0. modifiedsince: A time value to limit +the request for data. Data will only be returned if it has been modified since +the time provided. If data has not been modified since that time, the callback +will be called with a success value that indicates it is unmodified. Note: +modification time is tracked for the given profileid/index, not on a +per-persisttype or per-key basis keys: A "\" delimited list of the keys you want +returned (for example: "\clan\color\homepage\birthday") PersDataCallbackFn: +Callback that will be called with the data when it is returned instance: Pointer +that will be passed to the callback function (for your use) +*****************/ +void GetPersistDataValues(int localid, int profileid, persisttype_t type, + int index, gsi_char* keys, + PersDataCallbackFn callback, void* instance); +void GetPersistDataValuesModified(int localid, int profileid, + persisttype_t type, int index, + time_t modifiedsince, gsi_char* keys, + PersDataCallbackFn callback, void* instance); + +/**************** +SetPersistData + +DESCRIPTION +Sets the entire block of persistent data for a user. +The profileid for whom the data is being set MUST have been authenticated +already. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the +callback to allow you to identify which player it is referring to. profileid: +The profileid of the player whose data you are setting. The player must have +already been authenticated with one of the PreAuthenticatePlayer functions. +type: The type of persistent data you are setting. Only rw data is setable. +index: Each profile can have multiple persistent data records associated with +them. Usually you just want to use index 0. data: The persistent data to be +saved len: The length of the data. If you are setting key\value delimited data, +make sure the "len" parameter includes length of the null terminator +PersDataSaveCallbackFn: Callback that will be called with the data save is +complete instance: Pointer that will be passed to the callback function (for +your use) +*****************/ +void SetPersistData(int localid, int profileid, persisttype_t type, int index, + const char* data, int len, PersDataSaveCallbackFn callback, + void* instance); + +/**************** +SetPersistDataValues + +DESCRIPTION +If you are saving data in key\value delimited format, you can use this function +to only set SOME of the key\value pairs. Only the key value pairs you include in +they keyvalues parameter will be updated, the other pairs will stay the same. + +PARAMETERS +localid: Your game-specific reference number for this player, returned in the +callback to allow you to identify which player it is referring to. profileid: +The profileid of the player whose data you are setting. The player must have +already been authenticated with one of the PreAuthenticatePlayer functions. +type: The type of persistent data you are setting. Only rw data is setable. +index: Each profile can have multiple persistent data records associated with +them. Usually you just want to use index 0. keyvalues: The key\value pairs to be +updated (for example: \clan\The A-Team\homepage\http://www.myclan.net\age\15) +PersDataSaveCallbackFn: Callback that will be called with the data save is +complete instance: Pointer that will be passed to the callback function (for +your use) +*****************/ +void SetPersistDataValues(int localid, int profileid, persisttype_t type, + int index, const gsi_char* keyvalues, + PersDataSaveCallbackFn callback, void* instance); + +/**************** +PersistThink + +DESCRIPTION +This function needs to be called any time a asynchronous operation is in +progress. It will check for the incoming replies and call the callbacks +associated with them as needed. It's recommened that you call this in your main +loop at all times while you are connected to the stats server, so that if the +stats server disconnects it can be detected immediately. + +RETURNS +0 if the connection to the stats server is lost, 1 otherwise +*****************/ +int PersistThink(); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gstats/gstats.c b/source/gamespy/gstats/gstats.c new file mode 100644 index 000000000..0cf9bc7e1 --- /dev/null +++ b/source/gamespy/gstats/gstats.c @@ -0,0 +1,1562 @@ +/****** +gstats.c +GameSpy Stats/Tracking SDK +GameSpy Persistent Storage SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Stats and Tracking SDK documentation for more info + +******/ + +/******** +INCLUDES +********/ +// clang-format off +#include "gstats.h" +#include "gpersist.h" +#include "../common/gsAvailable.h" +#include "../darray.h" +#include "../md5.h" +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +/******** +TYPEDEFS +********/ +struct statsgame_s { + int connid; + int sesskey; + int usebuckets; + bucketset_t buckets; + char challenge[9]; + DArray playernums; // for player number translation + DArray teamnums; // for team number translation + int totalplayers, totalteams; + gsi_time sttime; +}; + +typedef enum { rt_authcb, rt_datacb, rt_savecb, rt_profilecb } reqtype_t; +typedef struct { + reqtype_t reqtype; + int localid; + int profileid; + persisttype_t pdtype; + int pdindex; + void* instance; + void* callback; + +} serverreq_t; + +/******** +PROTOTYPES +********/ +static int ServerOpInt(statsgame_t game, char* name, BucketFunc func, int value, + int index); +static double ServerOpFloat(statsgame_t game, char* name, BucketFunc func, + double value, int index); +static char* ServerOpString(statsgame_t game, char* name, BucketFunc func, + char* value, int index); + +static int TeamOpInt(statsgame_t game, char* name, BucketFunc func, int value, + int index); +static double TeamOpFloat(statsgame_t game, char* name, BucketFunc func, + double value, int index); +static char* TeamOpString(statsgame_t game, char* name, BucketFunc func, + char* value, int index); + +static int PlayerOpInt(statsgame_t game, char* name, BucketFunc func, int value, + int index); +static double PlayerOpFloat(statsgame_t game, char* name, BucketFunc func, + double value, int index); +static char* PlayerOpString(statsgame_t game, char* name, BucketFunc func, + char* value, int index); + +static char* CreateBucketSnapShot(bucketset_t buckets); + +#ifdef ALLOW_DISK +static void CheckDiskFile(); +static void DiskWrite(char* line, int len); +#endif +static void InternalInit(); +static int SendChallengeResponse(const char* indata, int gameport); +static int RecvSessionKey(); +static int DoSend(char* data, int len); +static void xcode_buf(char* buf, int len); +static int g_crc32(char* s, int len); +static void create_challenge(int challenge, char chstr[9]); +static char* value_for_key(const char* s, const char* key); +static char* value_for_key_safe(const char* s, const char* key); +static int get_sockaddrin(const char* host, int port, struct sockaddr_in* saddr, + struct hostent** savehent); +/************** +PERSISTENT STORAGE PROTOTYPES +**************/ +static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, + persisttype_t pdtype, int pdindex, + void* callback, void* instance); +static void SendPlayerAuthRequest(char* data, int len, int localid, + PersAuthCallbackFn callback, void* instance); +static void SendPlayerAuthRequest(char* data, int len, int localid, + PersAuthCallbackFn callback, void* instance); +static int SocketReadable(SOCKET s); +static char* FindFinal(char* buff, int len); +static int FindRequest(reqtype_t reqtype, int localid, int profileid); +static void ProcessPlayerAuth(const char* buf, int len); +static void ProcessGetPid(const char* buf, int len); +static void ProcessGetData(const char* buf, int len); +static void ProcessSetData(const char* buf, int len); +static void ProcessStatement(char* buff, int len); +static int ProcessInBuffer(char* buff, int len); +static void CallReqCallback(int reqindex, int success, time_t modified, + char* data, int length); +static void ClosePendingCallbacks(); +static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, + int index, const char* data, int len, + PersDataSaveCallbackFn callback, + void* instance, int kvset); +void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, + int index, char* keys, PersDataCallbackFn callback, + void* instance); +void GetPersistDataValuesModifiedA(int localid, int profileid, + persisttype_t type, int index, + time_t modifiedsince, char* keys, + PersDataCallbackFn callback, void* instance); + +/******** +DEFINES +********/ +//#define SSHOST "207.199.80.230" +#define SSHOST "gamestats." GSI_DOMAIN_NAME +#define SSPORT 29920 + +#define FIXGAME(g, r) \ + if (g == NULL) \ + g = g_statsgame; \ + if (g == NULL) \ + return r; +#define DoFunc(f, g, n, v, t, r) \ + if (g == NULL) \ + g = g_statsgame; \ + if (!g) \ + r = v; \ + else { \ + r = f(g->buckets, n, v); \ + if (!r) \ + r = BucketNew(g->buckets, n, t, v); \ + } +#define DOXCODE(b, l, e) \ + enc = e; \ + xcode_buf(b, l); + +/******** +VARS +********/ +char gcd_gamename[256] = ""; +char gcd_secret_key[256] = ""; +static statsgame_t g_statsgame = NULL; +static int connid = 0; +static int sesskey = 0; +static SOCKET sock = INVALID_SOCKET; +/* #define enc1 "GameSpy 3D" + #define enc2 "Industries" + #define enc3 "ProjectAphex" + #define STATSFILE "gstats.dat" */ +/* A couple vars to help avoid the string table */ +static char enc1[16] = {'\0', 'a', 'm', 'e', 'S', 'p', 'y', '3', 'D', '\0'}; +static char enc3[16] = {'\0', 'r', 'o', 'j', 'e', 'c', 't', + 'A', 'p', 'h', 'e', 'x', '\0'}; + +#ifdef ALLOW_DISK +static char statsfile[16] = {'\0', 's', 't', 'a', 't', 's', + '.', 'd', 'a', 't', '\0'}; +static char enc2[16] = {'\0', 'n', 'd', 'u', 's', 't', + 'r', 'i', 'e', 's', '\0'}; +#endif + +static char finalstr[10] = {'\0', 'f', 'i', 'n', 'a', 'l', '\\', '\0'}; +static char* enc = enc1; +static int internal_init = 0; +static char* rcvbuffer = NULL; +static int rcvmax = 0; +static int rcvlen = 0; +// Changed By Saad Nader, 09-16-2004 +// Due to confliction with MacOS X +/////////////////////////////////////////// +static int stats_initstate = init_none; +static int gameport = 0; + +static gsi_time initstart = 0; +static gsi_time inittimeout = 20000; // 20 seconds + +char StatsServerHostname[64] = SSHOST; + +static DArray serverreqs = NULL; // for pre-authentication requests + +BucketFunc bucketfuncs[NUMOPS] = {BucketSet, BucketAdd, BucketSub, + BucketMult, BucketDiv, BucketConcat, + BucketAvg}; + +void* bopfuncs[][3] = { + {ServerOpInt, ServerOpFloat, ServerOpString}, + {TeamOpInt, TeamOpFloat, TeamOpString}, + {PlayerOpInt, PlayerOpFloat, PlayerOpString}, +}; + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ +#define RAWSIZE 128 +char* GenerateAuthA(const char* challenge, const char* password, + char response[33]) { + char rawout[RAWSIZE]; + + /* check to make sure we weren't passed a huge pass/challenge */ + if (strlen(password) + strlen(challenge) + 20 >= RAWSIZE) { + strcpy(response, "CD Key or challenge too long"); + return response; + } + + /* response = MD5(pass + challenge) */ + sprintf(rawout, "%s%s", password, challenge); + + /* do the response md5 */ + MD5Digest((unsigned char*)rawout, strlen(rawout), response); + return response; +} + +/****************************************************************************/ +int InitStatsAsync(int theGamePort, gsi_time theInitTimeout) { + struct sockaddr_in saddr; + char tempHostname[128]; + int ret; + + gameport = theGamePort; + + if (theInitTimeout != 0) + inittimeout = theInitTimeout; + + /* check if the backend is available */ + if (__GSIACResult != GSIACAvailable) + return GE_NOSOCKET; + + /* Init our hidden strings if needed */ + if (!internal_init) + InternalInit(); + + SocketStartUp(); + sesskey = (int)current_time(); + + /* Get connected */ + if (sock != INVALID_SOCKET) + CloseStatsConnection(); + + rcvlen = 0; // make sure ther receive buffer is cleared + + if (inet_addr(StatsServerHostname) == INADDR_NONE) { + strcpy(tempHostname, gcd_gamename); + strcat(tempHostname, "."); + strcat(tempHostname, StatsServerHostname); + } else + strcpy(tempHostname, StatsServerHostname); // it's already been resolved + + if (get_sockaddrin(tempHostname, SSPORT, &saddr, NULL) == 0) + return GE_NODNS; + +#ifdef INSOCK + sock = socket(AF_INET, SOCK_STREAM, 0); +#else + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); +#endif + if (sock == INVALID_SOCKET) + return GE_NOSOCKET; + + SetSockBlocking(sock, 0); + + ret = connect(sock, (struct sockaddr*)&saddr, sizeof(saddr)); + if (gsiSocketIsError(ret)) { + int anError = GOAGetLastError(sock); + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && + (anError != WSAEINPROGRESS)) { + stats_initstate = init_failed; + closesocket(sock); + return GE_NOCONNECT; + } + } + + // allocate the recv buffer + rcvbuffer = gsimalloc(64); + if (rcvbuffer == NULL) + return GE_NOCONNECT; // add a new error code for out of mem? + + rcvmax = 64; + rcvlen = 0; + + initstart = current_time(); + stats_initstate = init_connecting; + return GE_CONNECTING; +} + +/****************************************************************************/ +int InitStatsThink() { + switch (stats_initstate) { + case init_failed: + return GE_NOCONNECT; + case init_connecting: { + // Check if socket is writeable yet + int aWriteFlag = 0; + int aExceptFlag = 0; + int aResult = GSISocketSelect(sock, NULL, &aWriteFlag, &aExceptFlag); + if ((gsiSocketIsError(aResult)) || // socket error + (aResult == 1 && aExceptFlag == 1)) // exception + { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_NOCONNECT; + } else if (aResult == 0) // no progress yet + { + // Should we continue to wait? + if (current_time() - initstart > inittimeout) { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } else + return GE_CONNECTING; + } + + // Otherwise connected + assert(aResult == 1 && aWriteFlag == 1); + stats_initstate = init_awaitchallenge; + // fall through + } + case init_awaitchallenge: { + int ret = 0; + + // Try to receive data + if (!CanReceiveOnSocket(sock)) { + // should we continue to wait? + if (current_time() - initstart > inittimeout) { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + return GE_CONNECTING; + } + + // Receive the 38 byte challenge + ret = recv(sock, rcvbuffer + rcvlen, rcvmax - rcvlen, 0); + if (gsiSocketIsError(ret)) { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_NOCONNECT; + } + rcvlen += ret; + rcvmax -= ret; + + // need at least 38 bytes + if (rcvlen < 38) + return GE_CONNECTING; + + // Process challenge + rcvbuffer[rcvlen] = '\0'; + stats_initstate = init_awaitsessionkey; + + /* Decode it */ + DOXCODE(rcvbuffer, rcvlen, enc1); + /* Send a response */ + ret = SendChallengeResponse(rcvbuffer, gameport); + if (ret != GE_NOERROR) { + stats_initstate = init_failed; + CloseStatsConnection(); + return ret; + } + + stats_initstate = init_awaitsessionkey; + + // clear receive buffer for next stage + rcvmax += rcvlen; // reclaim the used bytes as free space + rcvlen = 0; + memset(rcvbuffer, 0, (unsigned int)rcvmax); + + // fall through + } + case init_awaitsessionkey: { + int ret = 0; + + // Try to receive data + if (!CanReceiveOnSocket(sock)) { + // should we continue to wait? + if (current_time() - initstart > inittimeout) { + stats_initstate = init_failed; + CloseStatsConnection(); + return GE_TIMEDOUT; + } + return GE_CONNECTING; + } + + ret = RecvSessionKey(); + if (ret != GE_NOERROR) { + stats_initstate = init_failed; + CloseStatsConnection(); + return ret; + } + + // Init complete + // Clear the receive buffer + rcvmax += rcvlen; + rcvlen = 0; + memset(rcvbuffer, 0, (unsigned int)rcvmax); + +#ifdef ALLOW_DISK + /* Check for old data */ + CheckDiskFile(); +#endif + + stats_initstate = init_complete; + + // fall through + } + case init_complete: + return GE_NOERROR; + + default: + return GE_NOCONNECT; + }; +} + +/****************************************************************************/ +// Blocking version of InitStatsAsync, for backwards compatability +int InitStatsConnection(int gameport) { + int aResult = InitStatsAsync(gameport, 0); + while (aResult == GE_CONNECTING) { + aResult = InitStatsThink(); + msleep(5); + } + return aResult; +} + +/****************************************************************************/ +void CloseStatsConnection() { + if (sock != INVALID_SOCKET) { + shutdown(sock, 2); + closesocket(sock); + } + sock = INVALID_SOCKET; + // call any pending callbacks with the data as lost + ClosePendingCallbacks(); + if (rcvbuffer != NULL) { + gsifree(rcvbuffer); + rcvbuffer = NULL; + rcvmax = 0; + rcvlen = 0; + } +} + +/****************************************************************************/ +int IsStatsConnected() { return (sock != INVALID_SOCKET); } + +/****************************************************************************/ +#define CHALLENGEXOR 0x38F371E6 +char* GetChallenge(statsgame_t game) { + static char challenge[9]; + if (game == NULL) + game = g_statsgame; + if (game == NULL) { + create_challenge(connid ^ CHALLENGEXOR, challenge); + return challenge; + } + return game->challenge; +} + +/****************************************************************************/ +statsgame_t NewGame(int usebuckets) { + statsgame_t game = (statsgame_t)gsimalloc(sizeof(struct statsgame_s)); + char data[256]; + int len; + + if (!internal_init) + InternalInit(); + game->connid = connid; + game->sesskey = sesskey++; + game->buckets = NULL; + game->playernums = NULL; + game->teamnums = NULL; + game->usebuckets = usebuckets; + /* If connected, try to send */ + if (sock != INVALID_SOCKET) { + char respformat[] = + "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x6\x17\x3E\x1C\x6\xE\x39\x46\x10" + "\x1D\x3\xD\x16\xB\x3B\x17\x16\x36\x40\x7"; + //"\newgame\\connid\%d\sesskey\%d" + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = sprintf(data, respformat, game->connid, game->sesskey); + len = DoSend(data, len); + if (len <= 0) { + CloseStatsConnection(); + } + create_challenge(game->connid ^ CHALLENGEXOR, game->challenge); + } + /* If send failed then write to disk */ + if (sock == INVALID_SOCKET) { +#ifdef ALLOW_DISK + char respformat[] = + "\xC\x1C\xA\x1D\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51" + "\x25\x2C\xB\xD\x19\x3C\x1E\xA\x4\x2\x6\x28\x64\x14"; + // "\newgame\\sesskey\%d\challenge\%d"; + + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = + sprintf(data, respformat, game->sesskey, game->sesskey ^ CHALLENGEXOR); + DiskWrite(data, len); + game->connid = 0; + create_challenge(game->sesskey ^ CHALLENGEXOR, game->challenge); + +#else + gsifree(game); + game = NULL; +#endif + } + + if (game && game->usebuckets) { + game->buckets = NewBucketSet(); + game->playernums = ArrayNew(sizeof(int), 32, NULL); + game->teamnums = ArrayNew(sizeof(int), 2, NULL); + game->totalplayers = game->totalteams = 0; + } + if (game) + game->sttime = current_time(); + g_statsgame = game; + return game; +} + +/****************************************************************************/ +void FreeGame(statsgame_t game) { + if (!game || game == g_statsgame) { + game = g_statsgame; + g_statsgame = NULL; + } + if (!game) + return; + if (game->usebuckets) { + if (game->buckets != NULL) + FreeBucketSet(game->buckets); + if (game->playernums != NULL) + ArrayFree(game->playernums); + if (game->teamnums != NULL) + ArrayFree(game->teamnums); + } + gsifree(game); +} + +/****************************************************************************/ +int SendGameSnapShotA(statsgame_t game, const char* snapshot, int final) { + int snaplen; + int len; + int ret = GE_NOERROR; + char* snapcopy; + char* data; + FIXGAME(game, GE_DATAERROR); + + /* If using buckets, get the data out of the buckets */ + if (game->usebuckets) + snapcopy = CreateBucketSnapShot(game->buckets); + else + snapcopy = goastrdup(snapshot); + snaplen = (int)strlen(snapcopy); + + data = (char*)gsimalloc((unsigned int)snaplen + 256); + + /* Escape the data */ + while (snaplen--) + if (snapcopy[snaplen] == '\\') + snapcopy[snaplen] = '\x1'; + + /* If connected, try to send it */ + if (sock != INVALID_SOCKET) { + // Updated response format to contain connid + // char respformat[] = + // "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51\x25\x2C\xC\xA\x16\x35\x2E\x4A\xE\x39\x4\x15\x2C\x15\xC\x4\xC\x31\x2E\x4A\x19"; + char respformat[] = + "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51" + "\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x00\x24\x75" + "\x16\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1"; + // "\updgame\\sesskey\%d\done\%d\gamedata\%s" + // The above string is now: + // "\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s" + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = + sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); + snaplen = DoSend(data, len); + /* If the send failed, close the socket */ + if (snaplen <= 0) { + CloseStatsConnection(); + } + } + /* If not connected, or send failed, return error or log to disk */ + if (sock == INVALID_SOCKET) { +#ifdef ALLOW_DISK + char respformat[] = + "\xC\x7\x1F\xE\x2\x2\x19\x24\x2C\x34\x16\x1D\x23\x1\x4\xF\x1C\x3F\x51" + "\x25\x2C\xB\xA\x16\x3E\x1B\xB\x36\x40\x7\x28\x25\x1F\x6\x0\x24\x75\x16" + "\x33\xD\x4\xE\x11\x25\x11\x1C\x4\x24\x75\x1\x33\xE\x9\x3F\x45"; + //"\updgame\\sesskey\%d\connid\%d\done\%d\gamedata\%s\dl\1" + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = + sprintf(data, respformat, game->sesskey, game->connid, final, snapcopy); + DiskWrite(data, len); +#else + ret = GE_NOCONNECT; +#endif + } + gsifree(snapcopy); + gsifree(data); + return ret; +} + +/****************************************************************************/ +void NewPlayerA(statsgame_t game, int pnum, char* name) { + int i = -1; + FIXGAME(game, ;) + while (pnum >= ArrayLength(game->playernums)) + ArrayAppend(game->playernums, &i); + i = game->totalplayers++; + /* update the pnum array */ + ArrayReplaceAt(game->playernums, &i, pnum); + BucketIntOp(game, "ctime", bo_set, + (int)(current_time() - game->sttime) / 1000, bl_player, pnum); + BucketStringOp(game, "player", bo_set, name, bl_player, pnum); +} + +/****************************************************************************/ +void RemovePlayer(statsgame_t game, int pnum) { + FIXGAME(game, ;); + BucketIntOp(game, "dtime", bo_set, + (int)(current_time() - game->sttime) / 1000, bl_player, pnum); +} + +/****************************************************************************/ +void NewTeamA(statsgame_t game, int tnum, char* name) { + int i = -1; + FIXGAME(game, ;) + while (tnum >= ArrayLength(game->teamnums)) + ArrayAppend(game->teamnums, &i); + i = game->totalteams++; + /* update the tnum array */ + ArrayReplaceAt(game->teamnums, &i, tnum); + BucketIntOp(game, "ctime", bo_set, + (int)(current_time() - game->sttime) / 1000, bl_team, tnum); + BucketStringOp(game, "team", bo_set, name, bl_team, tnum); +} + +/****************************************************************************/ +void RemoveTeam(statsgame_t game, int tnum) { + FIXGAME(game, ;); + BucketIntOp(game, "dtime", bo_set, + (int)(current_time() - game->sttime) / 1000, bl_team, tnum); +} + +/**************************************************************************** + * PERSISTENT STORAGE FUNCTIONS + ****************************************************************************/ + +/****************************************************************************/ +void PreAuthenticatePlayerPartner(int localid, const char* authtoken, + const char* challengeresponse, + PersAuthCallbackFn callback, void* instance) { + char respformat[] = + "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x11\x1D\x11\x10\x24\x1D\x04\x0F\x0B\x3F" + "\x51\x32\x2C\x1A\x00\x0B\x20\x2E\x4A\x19\x39\x0F\x1D\x25\x2C\x4D\x01"; + //\authp\\authtoken\%s\resp\%s\lid\%d"; + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = sprintf(data, respformat, authtoken, challengeresponse, localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); +} + +/****************************************************************************/ +void PreAuthenticatePlayerPM(int localid, int profileid, + const char* challengeresponse, + PersAuthCallbackFn callback, void* instance) { + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x0\x1\x1\x24\x75\x16\x33" + "\x18\x0\x10\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; + //\authp\\pid\%d\resp\%s\lid\%d + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = sprintf(data, respformat, profileid, challengeresponse, localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); +} + +/****************************************************************************/ +void PreAuthenticatePlayerCDA(int localid, const char* nick, + const char* keyhash, + const char* challengeresponse, + PersAuthCallbackFn callback, void* instance) { + char respformat[] = "\xC\x13\x1A\x1E\xD\x13\x28\x1D\x1E\x1\x6\x13\xC\x57\x1C" + "\x36\xE\x6\xD\x29\x11\x1B\xD\x24\x75\x1\x33\x18\x0\x10" + "\x4\x1D\x55\x1B\x39\x14\x39\x16\x33\x4F\x1"; + //\authp\\nick\%s\keyhash\%s\resp\%s\lid\%d + int len; + char data[256]; + + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = sprintf(data, respformat, nick, keyhash, challengeresponse, localid); + + SendPlayerAuthRequest(data, len, localid, callback, instance); +} + +/****************************************************************************/ +void GetProfileIDFromCDA(int localid, const char* nick, const char* keyhash, + ProfileCallbackFn callback, void* instance) { + char respformat[] = + "\xC\x15\xA\x1E\x15\xA\x10\x1D\x2C\x6\xC\x1B\x3B\x2E\x4A\x19\x39\x8\x11" + "\x38\x18\x9\x16\x10\xC\x57\x1C\x36\x9\xA\x10\x1D\x55\xC"; + //\getpid\\nick\%s\keyhash\%s\lid\%d + int len; + char data[512]; + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = sprintf(data, respformat, nick, keyhash, localid); + + if (sock != INVALID_SOCKET) + len = DoSend(data, len); + + /* If the send failed, close the socket */ + if (len <= 0) { + CloseStatsConnection(); + if (callback) + callback(0, -1, 0, instance); + } else { /* set up the callback */ + AddRequestCallback(rt_profilecb, localid, 0, (persisttype_t)0, 0, callback, + instance); + } +} + +/****************************************************************************/ +void GetPersistData(int localid, int profileid, persisttype_t type, int index, + PersDataCallbackFn callback, void* instance) { + GetPersistDataValuesModifiedA(localid, profileid, type, index, 0, "", + callback, instance); +} + +void GetPersistDataModified(int localid, int profileid, persisttype_t type, + int index, time_t modifiedsince, + PersDataCallbackFn callback, void* instance) { + GetPersistDataValuesModifiedA(localid, profileid, type, index, modifiedsince, + "", callback, instance); +} + +/****************************************************************************/ +void SetPersistData(int localid, int profileid, persisttype_t type, int index, + const char* data, int len, PersDataSaveCallbackFn callback, + void* instance) { + SetPersistDataHelper(localid, profileid, type, index, data, len, callback, + instance, 0); +} + +/****************************************************************************/ +void GetPersistDataValuesModifiedA(int localid, int profileid, + persisttype_t type, int index, + time_t modifiedsince, char* keys, + PersDataCallbackFn callback, + void* instance) { + char respformat[] = + "\xC\x15\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4" + "\x24\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\x1D\x29\x1" + "\x33\x4F\x16\x3F\x18\x28\x14\x34\x40\x1C"; + char modformat[] = {'\\', 'm', 'o', 'd', '\\', '%', 'd', '\0'}; //\\mod\\%d + //\getpd\\pid\%d\ptype\%d\dindex\%d\keys\%s\lid\%d + int len; + char data[512]; + char tempkeys[256]; + char* p; + + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + strcpy(tempkeys, keys); + // replace the \ chars with #1 + for (p = tempkeys; *p != 0; p++) + if (*p == '\\') + *p = '\x1'; + + len = sprintf(data, respformat, profileid, type, index, tempkeys, localid); + if (modifiedsince != 0) // append it + { + len += sprintf(data + len, modformat, modifiedsince); + } + + if (sock != INVALID_SOCKET) + len = DoSend(data, len); + + /* If the send failed, close the socket */ + if (len <= 0) { + CloseStatsConnection(); + if (callback) + callback(localid, profileid, type, index, 0, 0, "", 0, instance); + } else { /* set up the callback */ + AddRequestCallback(rt_datacb, localid, profileid, type, index, callback, + instance); + } +} + +void GetPersistDataValuesA(int localid, int profileid, persisttype_t type, + int index, char* keys, PersDataCallbackFn callback, + void* instance) { + GetPersistDataValuesModifiedA(localid, profileid, type, index, 0, keys, + callback, instance); +} + +/****************************************************************************/ +void SetPersistDataValuesA(int localid, int profileid, persisttype_t type, + int index, const char* keyvalues, + PersDataSaveCallbackFn callback, void* instance) { + SetPersistDataHelper(localid, profileid, type, index, keyvalues, + (int)strlen(keyvalues) + 1, callback, instance, 1); +} + +/****************************************************************************/ +int PersistThink() { + int len; + int processed; + + if (sock == INVALID_SOCKET) + return 0; + + if (stats_initstate != init_complete) + return 0; + + while (SocketReadable(sock)) { + if (rcvmax - rcvlen < + 128) // make sure there are at least 128 bytes gsifree in the buffer + { + if (rcvmax < 256) + rcvmax = 256; + else + rcvmax *= 2; + rcvbuffer = gsirealloc(rcvbuffer, (unsigned int)(rcvmax + 1)); + if (rcvbuffer == NULL) + return 0; // errcon + } + len = recv(sock, rcvbuffer + rcvlen, rcvmax - rcvlen, 0); + if (len <= 0) // lost the connection + { + CloseStatsConnection(); + return 0; + } + rcvlen += len; + rcvbuffer[rcvlen] = 0; + + processed = ProcessInBuffer(rcvbuffer, rcvlen); + if (processed == rcvlen) // then we can just zero it + rcvlen = 0; + else { + // shift the remaining data down + memmove(rcvbuffer, rcvbuffer + processed, + (unsigned int)(rcvlen - processed)); + rcvlen -= processed; + } + } + if (sock == INVALID_SOCKET) + return 0; + else + return 1; +} + +/****************************************************************************/ +int StatsThink() { return PersistThink(); } + +/**************************************************************************** + * UTILITY FUNCTIONS + ****************************************************************************/ + +void InternalInit() { + internal_init = 1; + enc1[0] = 'G'; + enc3[0] = 'P'; + finalstr[0] = '\\'; + +#ifdef ALLOW_DISK + statsfile[0] = 'g'; + enc2[0] = 'I'; +#endif +} + +static int SendChallengeResponse(const char* indata, int gameport) { + static char challengestr[] = {'\0', 'h', 'a', 'l', 'l', + 'e', 'n', 'g', 'e', '\0'}; + char* challenge; + char resp[128]; + char md5val[33]; + + /* make this harder to find in the string table */ + char respformat[] = "\xC\x13\x1A\x1E\xD\x3F\x28\x26\x11\x5\x0\x16\x31\x1F\xA" + "\x36\x40\x10\x28\x33\x15\x1B\x15\x17\x3E\x1\xA\x36\x40" + "\x10\x28\x31\x1F\x1A\x11\x24\x75\x16\x33\x3\x1\x3F\x45"; + /* \auth\\gamename\%s\response\%s\port\%d\id\1 */ + int len; + + challengestr[0] = 'c'; + challenge = value_for_key(indata, challengestr); + if (challenge == NULL) { + closesocket(sock); + return GE_DATAERROR; + } + + len = sprintf(resp, "%d%s", g_crc32(challenge, (int)strlen(challenge)), + gcd_secret_key); + + MD5Digest((unsigned char*)resp, (unsigned int)len, md5val); + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + len = sprintf(resp, respformat, gcd_gamename, md5val, gameport); + + if (DoSend(resp, len) <= 0) { + closesocket(sock); + return GE_NOCONNECT; + } + + return GE_NOERROR; +} + +// 09-13-2004 BED Unused parameter removed +static int RecvSessionKey() { + /* get the response */ + static char sesskeystr[] = {'\0', 'e', 's', 's', 'k', 'e', 'y', '\0'}; + char resp[128]; + char* stext; + int len = (int)recv(sock, resp, 128, 0); + if (gsiSocketIsError(len)) { + int anError = GOAGetLastError(sock); + closesocket(sock); + + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && + (anError != WSAEINPROGRESS)) + return GE_NOCONNECT; + else + return GE_DATAERROR; // temp fix in case len == -1, SOCKET_ERROR + } + + resp[len] = 0; + DOXCODE(resp, len, enc1); + sesskeystr[0] = 's'; + stext = value_for_key(resp, sesskeystr); + if (stext == NULL) { + closesocket(sock); + return GE_DATAERROR; + } else + connid = atoi(stext); + + return GE_NOERROR; +} + +static int DoSend(char* data, int len) { + int sent = 0; + + DOXCODE(data, len, enc1); + strcpy(data + len, finalstr); + + // Loop to make sure async send goes through! + while (sent < (len + 7)) { + // Send remaining data + int ret = send(sock, (data + sent), (len + 7 - sent), 0); + if (gsiSocketIsError(ret)) { + int anError = GOAGetLastError(sock); + if ((anError != WSAEWOULDBLOCK) && (anError != WSAETIMEDOUT) && + (anError != WSAEINPROGRESS)) + return anError; + } else if (ret == 0) { + // socket was closed + return -1; + } else + sent += ret; + }; + + return sent; +} + +#ifdef ALLOW_DISK +/* Note: lots of this is byte order and type size specific, but it shouldn't +matter since the data is read/written always on the same machine */ +#define DISKLENXOR 0x70F33A5F +static void CheckDiskFile() { + FILE* f; + char* line; + char* alldata; + int len, check, alllen, fsize; + char filemode[3]; + /* hide our file access from the string table */ + filemode[0] = 'r'; + filemode[1] = 'b'; + filemode[2] = 0; + f = fopen(statsfile, filemode); + if (!f) + return; + /* get the size */ + fseek(f, 0, SEEK_END); + fsize = ftell(f); + fseek(f, 0, SEEK_SET); + /* make room for the whole thing */ + alldata = (char*)gsimalloc(fsize + 2); + alldata[0] = 0; + alllen = 0; + while (!feof(f) && !ferror(f)) { + /* read the check and line values */ + if (fread(&check, sizeof(check), 1, f) == 0 || + fread(&len, sizeof(len), 1, f) == 0) + break; + len ^= DISKLENXOR; + line = (char*)gsimalloc(len + 1); + /* read the data */ + if (fread(line, 1, len, f) != (size_t)len) + break; + line[len] = 0; + /* decode for checking */ + DOXCODE(line, len, enc2); + /* double "check" */ + if (check != g_crc32(line, len)) { + gsifree(line); + break; + } + /* encode for xmission */ + DOXCODE(line, len, enc1); + memcpy(alldata + alllen, line, len); + alllen += len; + memcpy(alldata + alllen, finalstr, 7); + alllen += 7; + gsifree(line); + } + fclose(f); + /* try to send */ + len = send(sock, alldata, alllen, 0); + if (len <= 0) { + closesocket(sock); + sock = INVALID_SOCKET; + } else + remove(statsfile); +} + +static void DiskWrite(char* line, int len) { + FILE* f; + int check; + int temp; + char filemode[3]; + /* hide our file access from the string table */ + filemode[0] = 'a'; + filemode[1] = 'b'; + filemode[2] = 0; + f = fopen(statsfile, filemode); + if (!f) + return; + check = g_crc32(line, len); + fwrite(&check, sizeof(check), 1, f); + temp = len ^ DISKLENXOR; + fwrite(&temp, sizeof(temp), 1, f); + DOXCODE(line, len, enc2); + fwrite(line, 1, len, f); + fclose(f); +} + +#endif /* ALLOW_DISK */ + +/* simple xor encoding */ +static void xcode_buf(char* buf, int len) { + int i; + char* pos = enc; + + for (i = 0; i < len; i++) { + buf[i] ^= *pos++; + if (*pos == 0) + pos = enc; + } +} + +#define MULTIPLIER -1664117991 +static int g_crc32(char* s, int len) { + int i; + int hashcode = 0; + + for (i = 0; i < len; i++) + hashcode = hashcode * MULTIPLIER + s[i]; + return hashcode; +} + +static void create_challenge(int challenge, char chstr[9]) { + char* p = chstr; + sprintf(chstr, "%08x", challenge); + + while (*p != 0) { + *p = (char)((*p) + ('A' - '0') + (p - chstr)); + p++; + } +} + +/* value_for_key: this returns a value for a certain key in s, where s is a +string containing key\value pairs. If the key does not exist, it returns NULL. +Note: the value is stored in a common buffer. If you want to keep it, make a +copy! */ +static char* value_for_key(const char* s, const char* key) { + static int valueindex; + char *pos, *pos2; + char keyspec[256] = "\\"; + static char value[2][256]; + + valueindex ^= 1; + strcat(keyspec, key); + strcat(keyspec, "\\"); + pos = strstr(s, keyspec); + if (!pos) + return NULL; + pos += strlen(keyspec); + pos2 = value[valueindex]; + while (*pos && *pos != '\\') + *pos2++ = *pos++; + *pos2 = '\0'; + return value[valueindex]; +} + +/* like value_for_key, but returns an empty string instead of NULL in the + * not-found case */ +static char* value_for_key_safe(const char* s, const char* key) { + char* temp; + + temp = value_for_key(s, key); + if (!temp) + return ""; + else + return temp; +} + +/* Return a sockaddrin for the given host (numeric or DNS) and port) +Returns the hostent in savehent if it is not NULL */ +static int get_sockaddrin(const char* host, int port, struct sockaddr_in* saddr, + struct hostent** savehent) { + struct hostent* hent = NULL; + + memset(saddr, 0, sizeof(struct sockaddr_in)); + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + if (host == NULL) + saddr->sin_addr.s_addr = INADDR_ANY; + else + saddr->sin_addr.s_addr = inet_addr(host); + + if (saddr->sin_addr.s_addr == INADDR_NONE) { + hent = gethostbyname(host); + if (!hent) + return 0; + saddr->sin_addr.s_addr = *(unsigned int*)hent->h_addr_list[0]; + } + if (savehent != NULL) + *savehent = hent; + return 1; +} + +/* adds a request callback to the list */ +static void AddRequestCallback(reqtype_t reqtype, int localid, int profileid, + persisttype_t pdtype, int pdindex, + void* callback, void* instance) { + serverreq_t req; + + req.callback = callback; + req.instance = instance; + req.localid = localid; + req.reqtype = reqtype; + req.profileid = profileid; + req.pdtype = pdtype; + req.pdindex = pdindex; + if (serverreqs == NULL) // create the callback array + { + serverreqs = ArrayNew(sizeof(serverreq_t), 2, NULL); + } + ArrayAppend(serverreqs, &req); +} + +/* sends the player authentication request (GP or CD) */ +static void SendPlayerAuthRequest(char* data, int len, int localid, + PersAuthCallbackFn callback, void* instance) { + int sentlen = 0; + char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + //"Connection Lost" + + if (sock != INVALID_SOCKET) + sentlen = DoSend(data, len); + + /* If the send failed, close the socket */ + if (sentlen <= 0) { + CloseStatsConnection(); + DOXCODE(connerror, sizeof(connerror) - 1, enc3); + if (callback) { + callback(localid, 0, 0, connerror, instance); + } + } else { /* set up the callback */ + AddRequestCallback(rt_authcb, localid, 0, (persisttype_t)0, 0, callback, + instance); + } +} + +/* send a set request, if kvset, then only those keys/values will bet updated */ +static void SetPersistDataHelper(int localid, int profileid, persisttype_t type, + int index, const char* data, int len, + PersDataSaveCallbackFn callback, + void* instance, int kvset) { + char respformat[] = + "\xC\x1\xA\x1E\x15\x7\x28\x1D\x0\x1\x1\x24\x75\x16\x33\x1A\x11\x1A\x4\x24" + "\x2C\x4D\x1\x24\x34\x1B\x1\xE\x0\x1B\x28\x64\x14\x34\xE\xE\xC\x57\xB\x36" + "\x9\xA\x10\x1D\x55\xC\x39\x14\x35\x1C\x8\x1E\xD\x3F\x51\x25\x2C\xC\x4\xC" + "\x31\x2E"; + //\setpd\\pid\%d\ptype\%d\dindex\%d\kv\%d\lid\%d\length\%d\data\ -- + int tlen; + char tdata[512]; + char* senddata; + + DOXCODE(respformat, sizeof(respformat) - 1, enc3); + + if (type == pd_private_ro || + type == + pd_public_ro) { // can't set read-only types, check that client side + if (callback) + callback(localid, profileid, type, index, 0, 0, instance); + return; + } + + tlen = + sprintf(tdata, respformat, profileid, type, index, kvset, localid, len); + if (tlen + len < 480) // we have enough room to put it in the data block + { + memcpy(tdata + tlen, data, (unsigned int)len); + senddata = tdata; + + } else // need to alloc a temp buffer + { + senddata = (char*)gsimalloc((unsigned int)(len + tlen + 256)); + memcpy(senddata, tdata, (unsigned int)tlen); + memcpy(senddata + tlen, data, (unsigned int)len); + } + + if (sock != INVALID_SOCKET) + tlen = DoSend(senddata, tlen + len); + + /* If the send failed, close the socket */ + if (tlen <= 0) { + CloseStatsConnection(); + if (callback) + callback(localid, profileid, type, index, 0, 0, instance); + } else { /* set up the callback */ + AddRequestCallback(rt_savecb, localid, profileid, type, index, callback, + instance); + } + if (senddata != tdata) // if we alloc'd before sending + gsifree(senddata); +} + +/* returns 1 if the socket is readable, 0 otherwise */ +static int SocketReadable(SOCKET s) { + return CanReceiveOnSocket(s); + /* + fd_set set; + struct timeval tv = {0,0}; + int n; + + if (s == INVALID_SOCKET) + return 0; + + FD_ZERO(&set); + FD_SET(s, &set); + + n = select(FD_SETSIZE, &set, NULL, NULL, &tv); + + return n; + */ +} + +/* find the \final\ string */ +static char* FindFinal(char* buff, int len) { + char* pos = buff; + + while (pos - buff < len - 6) { + if (pos[0] == '\\' && pos[1] == 'f' && pos[2] == 'i' && pos[3] == 'n' && + pos[4] == 'a' && pos[5] == 'l' && pos[6] == '\\') { + return pos; + } else + pos++; + } + return NULL; +} + +/* find the request in the callback list */ +static int FindRequest(reqtype_t reqtype, int localid, int profileid) { + int i; + serverreq_t* req; + + if (serverreqs == NULL) + return -1; + for (i = 0; i < ArrayLength(serverreqs); i++) { + req = (serverreq_t*)ArrayNth(serverreqs, i); + if (req->reqtype == reqtype && req->localid == localid && + req->profileid == profileid) + return i; + } + return -1; +} + +/* process the playerauth result */ +static void ProcessPlayerAuth(const char* buf, int len) { + // \\pauthr\\100000\\lid\\1 + int reqindex; + char* errmsg; + int pid; + int lid; + pid = atoi(value_for_key_safe(buf, "pauthr")); + lid = atoi(value_for_key_safe(buf, "lid")); + errmsg = value_for_key_safe(buf, "errmsg"); + reqindex = FindRequest(rt_authcb, lid, 0); + if (reqindex == -1) + return; + ((serverreq_t*)ArrayNth(serverreqs, reqindex))->profileid = pid; + CallReqCallback(reqindex, (pid > 0), 0, errmsg, 0); + + GSI_UNUSED(len); +} + +/* process the get profileid result */ +static void ProcessGetPid(const char* buf, int len) { + // \\getpidr\\100000\\lid\\1 + int reqindex; + int pid; + int lid; + pid = atoi(value_for_key_safe(buf, "getpidr")); + lid = atoi(value_for_key_safe(buf, "lid")); + reqindex = FindRequest(rt_profilecb, lid, 0); + if (reqindex == -1) + return; + ((serverreq_t*)ArrayNth(serverreqs, reqindex))->profileid = pid; + CallReqCallback(reqindex, (pid > 0), 0, NULL, 0); + + GSI_UNUSED(len); +} + +/* process the get data result */ +static void ProcessGetData(const char* buf, int len) { + // \\getpdr\\1\\lid\\1\\mod\\1234\\length\\5\\data\\mydata\\final + int reqindex; + int pid; + int lid; + int success; + int length; + time_t modified; + char* data; + success = atoi(value_for_key_safe(buf, "getpdr")); + lid = atoi(value_for_key_safe(buf, "lid")); + pid = atoi(value_for_key_safe(buf, "pid")); + modified = atoi(value_for_key_safe(buf, "mod")); + reqindex = FindRequest(rt_datacb, lid, pid); + if (reqindex == -1) + return; + length = atoi(value_for_key_safe(buf, "length")); + data = strstr(buf, "\\data\\"); + if (!data) { + length = 0; + data = ""; + } else + data += 6; // skip the key + CallReqCallback(reqindex, success, modified, data, length); + + GSI_UNUSED(len); +} + +/* process the set data result */ +static void ProcessSetData(const char* buf, int len) { + // \\setpdr\\1\\lid\\2\\pid\\100000\\mod\\12345 + int reqindex; + int pid; + int lid; + int success; + int modified; + success = atoi(value_for_key_safe(buf, "setpdr")); + pid = atoi(value_for_key_safe(buf, "pid")); + lid = atoi(value_for_key_safe(buf, "lid")); + modified = atoi(value_for_key_safe(buf, "mod")); + reqindex = FindRequest(rt_savecb, lid, pid); + if (reqindex == -1) + return; + CallReqCallback(reqindex, success, modified, NULL, 0); + + GSI_UNUSED(len); +} + +/* process a single statement */ +static void ProcessStatement(char* buff, int len) { + // determine the type + + buff[len] = 0; + // printf("GOT: %s\n",buff); + if (strncmp(buff, "\\pauthr\\", 8) == 0) { + ProcessPlayerAuth(buff, len); + } else if (strncmp(buff, "\\getpidr\\", 9) == 0) { + ProcessGetPid(buff, len); + } else if (strncmp(buff, "\\getpidr\\", 9) == 0) { + ProcessGetPid(buff, len); + } else if (strncmp(buff, "\\getpdr\\", 8) == 0) { + ProcessGetData(buff, len); + } else if (strncmp(buff, "\\setpdr\\", 8) == 0) { + ProcessSetData(buff, len); + } +} + +/* processes statements in the buffer and returns amount processed */ +// 09-13-2004 BED Modified loop to silence compiler warning +static int ProcessInBuffer(char* buff, int len) { + char* pos; + int oldlen = len; + + pos = FindFinal(buff, len); + // while (len > 0 && (pos = FindFinal(buff, len))) + while ((len > 0) && (pos != NULL)) { + DOXCODE(buff, pos - buff, enc1); + ProcessStatement(buff, pos - buff); + len -= (pos - buff) + 7; + buff = pos + 7; // skip the final + if (len > 0) + pos = FindFinal(buff, len); + } + return oldlen - len; // amount processed +} + +/* call a single callback function */ +static void CallReqCallback(int reqindex, int success, time_t modified, + char* data, int length) { + serverreq_t* req; + if (reqindex < 0 || reqindex >= ArrayLength(serverreqs)) + return; + req = (serverreq_t*)ArrayNth(serverreqs, reqindex); + if (req->callback) + switch (req->reqtype) { + case rt_authcb: + ((PersAuthCallbackFn)req->callback)(req->localid, req->profileid, success, + data, req->instance); + break; + case rt_datacb: + ((PersDataCallbackFn)req->callback)( + req->localid, req->profileid, req->pdtype, req->pdindex, success, + modified, data, length, req->instance); + break; + case rt_savecb: + ((PersDataSaveCallbackFn)req->callback)(req->localid, req->profileid, + req->pdtype, req->pdindex, + success, modified, req->instance); + break; + case rt_profilecb: + ((ProfileCallbackFn)req->callback)(req->localid, req->profileid, success, + req->instance); + break; + } + ArrayDeleteAt(serverreqs, reqindex); +} + +/* if we get disconnected while callbacks are still pending, make sure we +call all of them, with a success of 0 */ +static void ClosePendingCallbacks() { + int i; + + if (serverreqs == NULL) + return; + for (i = ArrayLength(serverreqs) - 1; i >= 0; i--) { + char connerror[] = "\x13\x1D\x1\x4\x0\x0\x0\x28\x1F\x6\x45\x34\x3F\x1\x1B"; + //"Connection Lost" + DOXCODE(connerror, sizeof(connerror) - 1, enc3); + + CallReqCallback(i, 0, 0, connerror, 0); + } + ArrayFree(serverreqs); + serverreqs = NULL; +} + +/****************************************************************************/ +/* BUCKET FUNCTIONS */ +/****************************************************************************/ +int GetTeamIndex(statsgame_t game, int tnum) { + FIXGAME(game, tnum); + return *(int*)ArrayNth(game->teamnums, tnum); +} +int GetPlayerIndex(statsgame_t game, int pnum) { + FIXGAME(game, pnum); + return *(int*)ArrayNth(game->playernums, pnum); +} + +static int ServerOpInt(statsgame_t game, char* name, BucketFunc func, int value, + int index) { + int* ret; + DoFunc(func, game, name, &value, bt_int, ret); + + GSI_UNUSED(index); + return *(int*)ret; +} +static double ServerOpFloat(statsgame_t game, char* name, BucketFunc func, + double value, int index) { + double* ret; + DoFunc(func, game, name, &value, bt_float, ret); + + GSI_UNUSED(index); + return *(double*)ret; +} +static char* ServerOpString(statsgame_t game, char* name, BucketFunc func, + char* value, int index) { + char* ret; + DoFunc(func, game, name, value, bt_string, ret); + + GSI_UNUSED(index); + return ret; +} + +static int TeamOpInt(statsgame_t game, char* name, BucketFunc func, int value, + int index) { + char fullname[64]; + sprintf(fullname, "%s_t%d", name, GetTeamIndex(game, index)); + return ServerOpInt(game, fullname, func, value, index); +} +static double TeamOpFloat(statsgame_t game, char* name, BucketFunc func, + double value, int index) { + char fullname[64]; + sprintf(fullname, "%s_t%d", name, GetTeamIndex(game, index)); + return ServerOpFloat(game, fullname, func, value, index); +} +static char* TeamOpString(statsgame_t game, char* name, BucketFunc func, + char* value, int index) { + char fullname[64]; + sprintf(fullname, "%s_t%d", name, GetTeamIndex(game, index)); + return ServerOpString(game, fullname, func, value, index); +} + +static int PlayerOpInt(statsgame_t game, char* name, BucketFunc func, int value, + int index) { + char fullname[64]; + sprintf(fullname, "%s_%d", name, GetPlayerIndex(game, index)); + return ServerOpInt(game, fullname, func, value, index); +} +static double PlayerOpFloat(statsgame_t game, char* name, BucketFunc func, + double value, int index) { + char fullname[64]; + sprintf(fullname, "%s_%d", name, GetPlayerIndex(game, index)); + return ServerOpFloat(game, fullname, func, value, index); +} +static char* PlayerOpString(statsgame_t game, char* name, BucketFunc func, + char* value, int index) { + char fullname[64]; + sprintf(fullname, "%s_%d", name, GetPlayerIndex(game, index)); + return ServerOpString(game, fullname, func, value, index); +} + +static char* CreateBucketSnapShot(bucketset_t buckets) { + return DumpBucketSet(buckets); +} + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gstats/gstats.h b/source/gamespy/gstats/gstats.h new file mode 100644 index 000000000..c9d88a1c8 --- /dev/null +++ b/source/gamespy/gstats/gstats.h @@ -0,0 +1,394 @@ +/****** +gstats.h +GameSpy Stats/Tracking SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +****** + +Please see the GameSpy Stats and Tracking SDK documentation for more info + +08-23-00 - DDW +Fixed a problem that prevented opening/closing/re-opening of the connection +within a single session. + +*****/ + +#pragma once + +/******** +INCLUDES +********/ +#include "../common/gsCommon.h" +#include "gbucket.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/******** +TYPEDEFS +********/ + +/* The abstracted "game" structure */ +typedef struct statsgame_s* statsgame_t; + +/* All of the operations you can do on a bucket */ +typedef enum { + bo_set, + bo_add, + bo_sub, + bo_mult, + bo_div, + bo_concat, + bo_avg +} bucketop_t; +#define NUMOPS 7 + +/* The types of buckets (server info, team info, or player info) */ +typedef enum { bl_server, bl_team, bl_player } bucketlevel_t; + +/* Init states for async initialization */ +typedef enum { + init_none, + init_failed, + init_connecting, + init_awaitchallenge, + init_awaitsessionkey, + init_complete +} initstate_t; + +/* Used by the bucket operation macros */ +typedef void* (*BucketFunc)(bucketset_t set, char* name, void* value); +typedef int (*SetIntFunc)(statsgame_t game, char* name, BucketFunc func, + int value, int index); +typedef double (*SetFloatFunc)(statsgame_t game, char* name, BucketFunc func, + double value, int index); +typedef char* (*SetStringFunc)(statsgame_t game, char* name, BucketFunc func, + char* value, int index); +extern BucketFunc bucketfuncs[NUMOPS]; +extern void* bopfuncs[][3]; + +/******** +DEFINES +********/ +/* Error codes */ +#define GE_NOERROR 0 +#define GE_NOSOCKET 1 /* Unable to create a socket */ +#define GE_NODNS 2 /* Unable to resolve a DNS name */ +#define GE_NOCONNECT \ + 3 /* Unable to connect to stats server, or connection lost */ +#define GE_BUSY 4 /* Not used */ +#define GE_DATAERROR 5 /* Bad data from the stats server */ +#define GE_CONNECTING \ + 6 /* Connect did no immediately complete. Call InitStatsThink() */ +#define GE_TIMEDOUT 7 /* Connect attempt timed out */ + +/* Types of snapshots, update (any snapshot that is not final) or final */ +#define SNAP_UPDATE 0 +#define SNAP_FINAL 1 + +/* If you want to allow disk logging in case the stats server isn't available. +This has SERIOUS security repercussions, so please read the docs before turning +this on */ +#define ALLOW_DISK + +#if defined(NOFILE) +#undef ALLOW_DISK +#endif /* make sure it's never defined on platforms with no disk! */ + +/******** +VARS +********/ + +/* You need to fill these in with your game-specific info */ +extern char gcd_secret_key[256]; +extern char gcd_gamename[256]; + +/* The hostname of the stats server. +If the app resolves the hostname, an +IP can be stored here before calling +InitStatsConnection */ +extern char StatsServerHostname[64]; + +/******** +PROTOTYPES +********/ +#define GenerateAuth GenerateAuthA +#define SendGameSnapShot SendGameSnapShotA +#define NewPlayer NewPlayerA +#define NewTeam NewTeamA + +/******** +InitStatsConnection + +DESCRIPTION +Opens a connection to the stats server. Should be done before calling +NewGame or any of the bucket/snapshot functions. May block for 1-2 secs +while the connection is established so you will want to do this before +gameplay starts or in another thread. + +PARAMETERS +gameport: integer port associated with your server (may be the same as + your developer spec query port). Used only to help players differentiate + between servers on the same machine (no queries are done on it). If not + appropriate for your game, pass in 0. + +RETURNS +GE_NODNS: Unable to resolve stats server DNS +GE_NOSOCKET: Unable to create data socket +GE_NOCONNECT: Unable to connect to stats server +GE_DATAERROR: Unable to receive challenge from stats server, or bad challenge +GE_NOERROR: Connected to stats server and ready to send data + +Note: You can still call ANY of the other Stats SDK functions, even if the +connection fails. If you have disk logging enabled, these calls will be logged +for future sending, otherwise they will be discarded. +*********/ +int InitStatsConnection(int gameport); +int InitStatsAsync(int gameport, gsi_time theInitTimeout); +int InitStatsThink(); + +/******** +StatsThink + +DESCRIPTION +Eats up any incoming keep-alive messages that are sent by the stats server. +Returns any errors occur because of a socket problem or if the SDK was +not completely initialized. + +RETURNS +1 if no errors occured during read, 0 on all other errors +********/ +int StatsThink(); + +/******** +IsStatsConnected + +DESCRIPTION +Returns whether or not you are currently connected to the stats server. Even +if your initial connection was successful, you may lose connection later and +want to try to reconnnect + +RETURNS +1 if connected, 0 otherwise +*********/ +int IsStatsConnected(); + +/******** +CloseStatsConnection + +DESCRIPTION +Closes the connection to the stats server. You should do this when done +with the connection. +*********/ +void CloseStatsConnection(void); + +/******** +GetChallenge + +DESCRIPTION +Returns a string that should be sent to clients for authentication +(using GenerateAuth). You do not have to free the string when done. +This string will be constant for the entire length of the game and is +generated during the call to NewGame. + +PARAMETERS +game: Game to return the challenge string for. If game is NULL, the last + game created with NewGame will be used. + +RETURNS +A string to send to clients so they can authorize. If you game is NULL and +you haven't created a game with NewGame, it returns "NULLGAME". +*********/ +char* GetChallenge(statsgame_t game); + +/******** +GenerateAuth + +DESCRIPTION +Should be used on the CLIENT SIDE to generate an authentication reply +(auth_N) for a given challenge and password (CD Key or Profile password) + +PARAMETERS +challenge: The challenge string sent by the server. On the server this + should be generated with GetChallenge +password: The CD Key (un-hashed) or profile password +response: The output authentication string + +RETURNS +A pointer to response +*********/ +char* GenerateAuth(const char* challenge, const gsi_char* password, + /*[out]*/ char response[33]); + +/******** +NewGame + +DESCRIPTION +Creates a new game for logging and registers it with the stats server. +Creates all the game structures, including buckets if needed. + +PARAMETERS +usebuckets: Set to 1 for bucket based logging, 0 if you are going to create + the snapshots yourself. See the SDK for more info. + +RETURNS +A pointer to the new game. If you are not connected, and disk logging is +disabled, this will be NULL. You can still pass NULL to any function without +causing any errors. +Note: The last game created by NewGame is stored internally. +If you only create / use one game at a time, you can simply discard +the return value and pass NULL for game into all of the bucket and snapshot +functions. +*********/ +statsgame_t NewGame(int usebuckets); + +/******** +FreeGame + +DESCRIPTION +Frees a game and its associated structures (including buckets). You should +send a final snapshot for the game (using SendGameSnapShot with SNAP_FINAL) +before freeing the game. + +PARAMETERS +game: The game you want to free. If set to NULL, it will free the last + game created with NewGame. +*********/ +void FreeGame(statsgame_t game); + +/******** +SendGameSnapShot + +DESCRIPTION +Sends a snapshot of information about the current game. If bucket based +logging is enabled the snapshot will be generated from the buckets, otherwise +you should provide it in "snapshot". + +PARAMETERS +game: The game to send a snapshot for. If set to NULL, the last game + created with NewGame will be used. +snapshot: The snapshot to send. If you are using buckets, this will not be + used, so you can pass in NULL +final: If this is SNAP_UPDATE, the game is marked as in progress, if it + is SNAP_FINAL, the game is marked as complete. + +RETURNS +GE_DATAERROR: If game is NULL and the last game created by NewGame failed + (because the connection was lost and disk logging is disabled) +GE_NOCONNECT: If the connection is lost and disk logging is disabled +GE_NOERROR: The update was sent, or disk logging is enabled and the game was +logged +*********/ +int SendGameSnapShot(statsgame_t game, const gsi_char* snapshot, int final); + +/****************************** +BUCKET FUNCTION PROTOTYPES +These functions are only used for bucket-based logging +*******************************/ + +/******** +Bucket_____Op + +DESCRIPTION +Performs an operation on a bucket for a game. If the bucket doesn't exist +already, the call will set the bucket to whatever "value" is. You can always +create each bucket explicitly by using bo_set with whatever initial value you +want the bucket to have. Valid operations include set, add, subtract, multiply, +divide, concat, and average. Each bucket type (int, float, or string) has its +own operation function, always call the same one for each bucket (i.e. don't +create a bucket with BucketIntOp then try to add a float with BucketFloatOp). + +PARAMETERS +game: The game to send containing the bucket you want to operate on. + If set to NULL, the last game created with NewGame will be used. +name: The name of the bucket to update. Note that for player or team buckets, +this name does NOT include the "_" or "_t" (e.g. "score" for player score, not +"score_N"). The underscore and number will be added automatically. operation: +One of the bucketop_t enums defined above value: Argument for the operation +(bucket OP= value, e.g. bucket += value, bucket *= value) bucketlevel: One of +the bucketlevel_t enums defined above. Determines whether you are referring to a +server, player, or team bucket. Note that you can have seperate buckets of each +type with the same name (e.g. "score" player bucket for each player and "score" +team bucket for each team) index: For player or team buckets, the game index of +the player or team (as passed to NewPlayer or NewTeam). This will be translated +to the actual index internally. Not used for server buckets (bl_server). +*********/ +#define BucketIntOp(game, name, operation, value, bucketlevel, index) \ + (((SetIntFunc)bopfuncs[bucketlevel][bt_int])( \ + game, name, bucketfuncs[operation], value, index)) +#define BucketFloatOp(game, name, operation, value, bucketlevel, index) \ + (((SetFloatFunc)bopfuncs[bucketlevel][bt_float])( \ + game, name, bucketfuncs[operation], value, index)) +#define BucketStringOp(game, name, operation, value, bucketlevel, index) \ + (((SetStringFunc)bopfuncs[bucketlevel][bt_string])( \ + game, name, bucketfuncs[operation], value, index)) + +/******** +NewPlayer + +DESCRIPTION +Adds a "player" to the game and assigns them an internal player number. Sets +their connect time to the number of seconds since NewGame was called. + +PARAMETERS +game: The game to add the player to. If set to NULL, the last game created + with NewGame will be used. +pnum: Your internal reference for this player, use this value in any calls + to the Bucket___Op functions. +name: The name for this player. If you don't have one yet, set it to empty ("") + then call: BucketStringOp(game,"player",bo_set,realplayername, +bl_player, pnum) when you get a realplayername. +**********/ +void NewPlayer(statsgame_t game, int pnum, gsi_char* name); + +/******** +RemovePlayer + +DESCRIPTION +Removes a "player" from the game and sets their disconnect time to the +number of seconds since NewGame was called. + +PARAMETERS +game: The game to remove the player from. If set to NULL, the last game created + with NewGame will be used. +pnum: Your internal reference for this player, use this value in any calls + to the Bucket___Op functions. +**********/ +void RemovePlayer(statsgame_t game, int pnum); + +/********* +NewTeam +RemoveTeam + +DESCRIPTION +See the player functions above. These function the same, except for teams +**********/ +void NewTeam(statsgame_t game, int tnum, gsi_char* name); +void RemoveTeam(statsgame_t game, int tnum); + +/********* +GetPlayerIndex +GetTeamIndex + +DESCRIPTION +Gets the gstats reference number for that player or team. For +example, if you start the game and players 0, 1, and 2 join, then player 1 +leaves, and another player 1 joins, the new player 1 will be referenced +by gstats as 3. If player 3 joins, it will be referenced as player 4, and so on. +Normally this doesn't matter to you, but if you want to do a key name or key +value that references a player or team number (for example, setting a player's +team number), you need to use the translated values. + +PARAMETERS +game: The game to retrieve the translated value for. If set to NULL,the last +game created with NewGame will be used. pnum/tnum: Your internal player or team +number (as sent to NewTeam/NewPlayer) +**********/ +int GetPlayerIndex(statsgame_t game, int pnum); +int GetTeamIndex(statsgame_t game, int tnum); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gt2/gt2.h b/source/gamespy/gt2/gt2.h new file mode 100644 index 000000000..6651ed991 --- /dev/null +++ b/source/gamespy/gt2/gt2.h @@ -0,0 +1,698 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +/**************************** +** GameSpy Transport SDK 2 ** +****************************/ + +/* +** see "configurable defines" in gt2Main.h for certain performance settings that +*can be changed +*/ + +#pragma once + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/********** +** TYPES ** +**********/ + +// boolean +typedef int GT2Bool; +#define GT2False 0 +#define GT2True 1 + +// a byte +typedef unsigned char GT2Byte; + +// a handle to a socket object (can be used to accept connections and initiate +// connections) +typedef struct GTI2Socket* GT2Socket; + +// a handle to an object representing a connection to a specific IP and port +// the local endpoint is a GT2Socket +typedef struct GTI2Connection* GT2Connection; + +// the id of a reliably sent message +// unreliable messages don't have ids +typedef unsigned short GT2MessageID; + +// the result of a GT2 operation +// check individual function definitions to see possible results +// TODO: list possible results wherever this is used +typedef enum { + GT2Success, // success + // errors: + GT2OutOfMemory, // ran out of memory + GT2Rejected, // attempt rejected + GT2NetworkError, // networking error (could be local or remote) + GT2AddressError, // invalid or unreachable address + GT2DuplicateAddress, // a connection was attempted to an address that already + // has a connection on the socket + GT2TimedOut, // time out reached + GT2NegotiationError, // there was an error negotiating with the remote side + GT2InvalidConnection, // the connection didn't exist + GT2InvalidMessage, // used for vdp reliable messages containing voice data, no + // voice data in reliable messages + GT2SendFailed // the send failed, +} GT2Result; + +// possible states for any GT2Connection +typedef enum { + GT2Connecting, // negotiating the connection + GT2Connected, // the connection is active + GT2Closing, // the connection is being closed + GT2Closed // the connection has been closed and can no longer be used +} GT2ConnectionState; + +// The cause of the connection being closed. +typedef enum { + GT2LocalClose, // The connection was closed with gt2CloseConnection. + GT2RemoteClose, // The connection was closed remotely. + // errors: + GT2CommunicationError, // An invalid message was received (it was either + // unexpected or wrongly formatted). + GT2SocketError, // An error with the socket forced the connection to close. + GT2NotEnoughMemory // There wasn't enough memory to store an incoming or + // outgoing message. +} GT2CloseReason; + +/************ +** GLOBALS ** +************/ + +// The challenge key is a 32 character string +// that is used in the authentication process. +// The key can be set before GT2 is used so +// that the key will be application-specific. +extern char GT2ChallengeKey[33]; + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +// this callback gets called when there was is an error that forces a socket to +// close all connections that use this socket are terminated, and their +// gt2CloseCallback callbacks will be called before this callback is called +// (with the reason set to GT2SocketError). the socket cannot be used again +// after this callback returns +typedef void (*gt2SocketErrorCallback)(GT2Socket socket); + +/********************* +** SOCKET FUNCTIONS ** +*********************/ + +// creates a local socket +// if the IP of the local address is 0, then any/all ips will be bound. +// if the port of the local address is 0, then a port will be assigned. +// if either buffer sizes is set to 0, a default value will be used (currently +// 64K for PC, 4k for Xbox). the buffer needs to be able to hold all messages +// waiting for confirmation of delivery, and it needs to hold any messages that +// arrive out of order. if either buffer runs out of space the connection will +// be dropped. +GT2Result gt2CreateSocket( + GT2Socket* socket, // if the result is GT2Success, the socket object handle + // will be stored at this address + const char* localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages + // waiting to be confirmed are held, use 0 for + // default + int incomingBufferSize, // size of per-connection buffer where out-of-order + // received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an + // error with the socket +); + +// AdHoc Sockets use MAC address instead of IP address. +GT2Result gt2CreateAdHocSocket( + GT2Socket* socket, // if the result is GT2Success, the socket object handle + // will be stored at this address + const char* localAddress, // the local address to bind to + int outgoingBufferSize, // size of per-connection buffer where sent messages + // waiting to be confirmed are held, use 0 for + // default + int incomingBufferSize, // size of per-connection buffer where out-of-order + // received messages are held, use 0 for default + gt2SocketErrorCallback callback // a callback that is called if there is an + // error with the socket +); + +// closes a local socket. +// all existing connections will be hard closed, as if +// gt2CloseAllConnectionsHard was called for this socket. all connections send +// a close message to the remote side, and any closed callbacks will be called +// from within this function +void gt2CloseSocket(GT2Socket socket); + +// processes a socket (and all associated connections) +void gt2Think(GT2Socket socket); + +// sends a raw UDP datagram through the socket +// this function bypasses the normal connection logic +// note that all messages sent this way will be unreliable +// to broadcast a datagram, omit the IP from the remoteAddress (e.g., ":12345") +GT2Result gt2SendRawUDP( + GT2Socket socket, // the socket through which to send the raw UDP datagram + const char* remoteAddress, // the address to which to send the datagram + const GT2Byte* + message, // the message to send, or NULL for an empty datagram + int len // the len of the message (0 for an empty message, ignored if + // message==NULL) +); + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +// Called when the connect has completed. +// If the result is GT2Rejected, +// then message is the message that the +// listener passed to gt2Reject. If the +// result is anything else, then message +// is NULL and len is 0. +typedef void (*gt2ConnectedCallback)( + GT2Connection connection, // The connection object. + GT2Result result, // Result from connect attempt. + GT2Byte* message, // If result==GT2Rejected, the reason. Otherwise, NULL. + int len // If result==GT2Rejected, the length of the reason. Otherwise, 0. +); + +// Called when a message is received. +typedef void (*gt2ReceivedCallback)( + GT2Connection connection, // The connection the message was received on. + GT2Byte* message, // The message that was received. Will be NULL if an + // empty message. + int len, // The length of the message in bytes. Will be 0 if an empty + // message. + GT2Bool reliable // True if this is was sent reliably. +); + +// Called when the connection is closed (remotely or locally). +// The connection can no longer be used after this callback returns. +typedef void (*gt2ClosedCallback)( + GT2Connection connection, // The connection that was closed. + GT2CloseReason reason // The reason the connection was closed. +); + +// When a reply is received for a ping that was sent, this callback is called. +// The latency reported here is the amount of time between when the ping +// was first sent with gt2Ping and when the pong was received. +typedef void (*gt2PingCallback)( + GT2Connection connection, // the connection the ping was sent on + int latency // the round-trip time for the ping, in milliseconds +); + +// Callbacks set for each connection. +// The connected callback is ignored +// when this is passed to gt2Accept. +typedef struct { + gt2ConnectedCallback connected; // Called when gt2Connect is complete. + gt2ReceivedCallback received; // Called when a message is received. + gt2ClosedCallback + closed; // Called when the connection is closed (remotely or locally). + gt2PingCallback ping; // Called when a ping reply is received. +} GT2ConnectionCallbacks; + +/************************* +** CONNECTION FUNCTIONS ** +*************************/ + +// initiates a connection between a local socket and a remote socket +// if blocking is true, the return value signals the connection result: +// GT2Success means the connect attempt succeeded +// anything else means it failed +// if blocking is false, the return value signals the current status of the +// attempt +// GT2Success means the connection is being attempted +// anything else means there was an error and the connection attempt has been +// aborted +GT2Result gt2Connect( + GT2Socket socket, // the local socket to use for the connection + GT2Connection* + connection, // if the result is GT2Success, and blocking is false, the + // connection object handle is stored here + const char* remoteAddress, // the address to connect to + const GT2Byte* message, // an optional initial message (may be NULL) + int len, // length of the initial message (may be 0, or -1 for strlen) + int timeout, // timeout in milliseconds (may be 0 for infinite retries) + GT2ConnectionCallbacks* callbacks, // callbacks for connection related stuff + GT2Bool blocking // if true, don't return until complete (successfuly or + // unsuccessfuly) +); + +// sends data reliably or unreliably +// reliable messages are guaranteed to arrive, arrive in order, and arrive only +// once. unreliable messages are not guaranteed to arrive, arrive in order, or +// arrive only once. because messages may be held in the outgoing buffer (even +// unreliable messages may need to be put in the buffer), the message size +// cannot exceed +GT2Result +gt2Send(GT2Connection connection, // the connection to send the message on + const GT2Byte* + message, // the message to send, or NULL for an empty message0 + int len, // the len of the message (0 for an empty message, ignored if + // message==NULL) + GT2Bool reliable // if true, send the message reliably +); + +// sends a ping on a connection in an attempt to determine latency +// the ping is unreliable, and either it or the pong sent in reply +// could be dropped (resulting in the callback never being called), +// or it could even arrive multiple times (resulting in multiple +// calls to the callback). +void gt2Ping(GT2Connection connection); + +// starts an attempt to close the connection +// when the close is completed, the connection's closed callback will be called +void gt2CloseConnection(GT2Connection connection); + +// same as gt2CloseConnection, but doesn't wait for confirmation from the remote +// side of the connection the closed callback will be called from within this +// function +void gt2CloseConnectionHard(GT2Connection connection); + +// closes all of a socket's connections (essentially calls gt2CloseConnection on +// each of them). +void gt2CloseAllConnections(GT2Socket socket); + +// same as gt2CloseAllConnections, but does a hard close +// any closed callbacks will be called from within this function +void gt2CloseAllConnectionsHard(GT2Socket socket); + +/********************* +** LISTEN CALLBACKS ** +*********************/ + +// callback gets called when someone attempts to connect to a socket that is +// listening for new connections. in response to this callback the application +// should call gt2Accept or gt2Reject. they do not need to be called from +// inside the callback, however they should be called in a timely manner so that +// the remote side does not need to sit around indefinitely waiting for a +// response. the latency is an estimate of the round trip time between +// connections. +typedef void (*gt2ConnectAttemptCallback)( + GT2Socket socket, // the socket the attempt came in on + GT2Connection + connection, // a connection object for the incoming connection attempt + unsigned int ip, // the IP being used remotely for the connection attempt + unsigned short + port, // the port being used remotely for the connection attempt + int latency, // the approximate latency on the connection + GT2Byte* message, // an optional message sent with the attempt. Will be + // NULL if an empty message. + int len // the length of the message, in characters. Will be 0 if an empty + // message. +); + +/********************* +** LISTEN FUNCTIONS ** +*********************/ + +// tells a socket to start listening for incoming connections +// any connections attempts will cause the callback to be called +// if the socket is already listening, this callback will replace the exsiting +// callback being used if the callback is NULL, this will cause the connection +// to stop listening +void gt2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback); + +// after a socket's gt2ConnectAttemptCallback has been called, this function can +// be used to accept the incoming connection attempt. it can be called from +// either within the callback or some later time. as soon as it is called the +// connection is active, and messages can be sent and received. the remote side +// of the connection will have it's connected callback called with the result +// set to GT2Success. the callbacks that are passed in to this function are the +// same callbacks that get passed to gt2Connect, with the exception that the +// connected callback can be ignored, as the connection is already established. +// if this function returns GT2True, then the connection has been successfully +// accepted. if it returns GT2False, then the remote side has already closed +// the connection attempt. in that case, the connection is considered closed, +// and it cannot be referenced again. +GT2Bool gt2Accept(GT2Connection connection, GT2ConnectionCallbacks* callbacks); + +// after a socket's gt2ConnectAttemptCallback has been called, this function can +// be used to reject the incoming connection attempt. it can be called from +// either within the callback or some later time. once the function is called +// the connection is considered closed and cannot be referenced again. the +// remote side attempting the connection will have its connected callback called +// with the result set to GT2Rejected. if the message is not NULL and the len is +// not 0, the message will be sent with the rejection, and passed into the +// remote side's connected callback. +void gt2Reject(GT2Connection connection, const GT2Byte* message, int len); + +/************************* +** MESSAGE CONFIRMATION ** +*************************/ +// gets the message id for the last reliably sent message. unreliable messages +// do not have ids. this should be called immediately after gt2Send. waiting +// until after a call to gt2Think can result in an invalid message id being +// returned. note that the use of filters that can either drop or delay messages +// can complicate the process, because in those cases a call to gt2Send does not +// guarantee that a message will actually be sent. in those cases, +// gt2GetLastSentMessageID should be called after gt2FilteredSend, because the +// actual message will be sent from within that function. +GT2MessageID gt2GetLastSentMessageID(GT2Connection connection); + +// returns true if confirmation was received locally that the reliable message +// represented by the message id was received by the remote end of the +// connection. returns false if confirmation was not yet received. this should +// only be called on message ids that were returned by gt2GetLastSendMessageID, +// and should be used relatively soon after the message was sent, due to message +// ids wrapping around after a period of time. +GT2Bool gt2WasMessageIDConfirmed(GT2Connection connection, + GT2MessageID messageID); + +/********************* +** FILTER CALLBACKS ** +*********************/ + +// Callback for filtering outgoing data. +// Call gt2FilteredSend with the filtered data, either from within the callback +// or later. the message points to the same memory location as the message +// passed to gt2Send (or gt2FilteredSend). so if the call to gt2FilteredSend is +// delayed, it is the filter's responsibility to make sure the data is still +// around when and if it is needed. +typedef void (*gt2SendFilterCallback)( + GT2Connection + connection, // The connection on which the message is being sent. + int filterID, // Pass this ID to gt2FilteredSend. + const GT2Byte* + message, // The message being sent. Will be NULL if an empty message. + int len, // The length of the message being sent, in bytes. Will be 0 if an + // empty message. + GT2Bool reliable // If the message is being sent reliably. +); + +// Callback for filtering incoming data. +// Call gt2FilteredRecieve with the filtered data, +// either from within the callback or later. +// the message may point to a memory location supplied to gt2FilteredReceive by +// a previous filter. so if this filter's call to gt2FilteredReceive is delayed, +// it is the filter's responsibility to make sure the data is still around when +// and if it is needed. +typedef void (*gt2ReceiveFilterCallback)( + GT2Connection connection, // The connection the message was received on. + int filterID, // Pass this ID to gtFilteredReceive. + GT2Byte* message, // The message that was received. Will be NULL if an + // empty message. + int len, // The length of the message in bytes. Will be 0 if an empty + // message. + GT2Bool reliable // True if this is a reliable message. +); + +/********************* +** FILTER FUNCTIONS ** +*********************/ + +// Adds a filter to the connection's outgoing data. +// Returns GT2False if there was an error adding the filter (due to no free +// memory) +GT2Bool gt2AddSendFilter( + GT2Connection connection, // The connection on which to add the filter. + gt2SendFilterCallback + callback // The callback the outgoing data is filtered through. +); + +// Removes a filter from the connection's outgoing data. +// if callback is NULL, all send filters are removed +void gt2RemoveSendFilter( + GT2Connection connection, // The connection on which to remove the filter. + gt2SendFilterCallback callback // The callback to remove. +); + +// Called in response to a gt2SendFilterCallback being called. +// It can be called from within the callback, or at any later time. +void gt2FilteredSend( + GT2Connection + connection, // The connection on which the message is being sent. + int filterID, // The ID passed to the gt2SendFilterCallback. + const GT2Byte* message, // The message being sent. May be NULL. + int len, // The lengt2h of the message being sent, in bytes. May be 0 or -1. + GT2Bool reliable // If the message should be sent reliably. +); + +// Adds a filter to the connection's incoming data. +// Returns GT2False if there was an error adding the filter (due to no free +// memory) +GT2Bool gt2AddReceiveFilter( + GT2Connection connection, // The connection on which to add the filter. + gt2ReceiveFilterCallback + callback // The callback the incoming data is filtered through. +); + +// Removes a filter from the connection's incoming data. +// if callback is NULL, all receive filters are removed +void gt2RemoveReceiveFilter( + GT2Connection connection, // The connection on which to remove the filter. + gt2ReceiveFilterCallback callback // The callback to remove. +); + +// Called in response to a gt2ReceiveFilterCallback being called. +// It can be called from within the callback, or at any later time. +void gt2FilteredReceive( + GT2Connection connection, // The connection the message was received on. + int filterID, // The ID passed to the gt2ReceiveFilterCallback. + GT2Byte* message, // The message that was received. May be NULL. + int len, // The lengt2h of the message in bytes. May be 0. + GT2Bool reliable // True if this is a reliable message. +); + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +// this callback gets called when the sock receives a message that it cannot +// match to an existing connection. if the callback recognizes the message and +// handles it, it should return GT2True, which will tell the socket to ignore +// the message. if the callback does not recognize the message, it should +// return GT2False, which tells the socket to let the other side know there is +// no connection. +typedef GT2Bool (*gt2UnrecognizedMessageCallback)( + GT2Socket socket, // the socket the message was received on + unsigned int ip, // the ip of the remote machine the message came from (in + // network byte order) + unsigned short port, // the port on the remote machine (in host byte order) + GT2Byte* message, // the message (may be NULL for an empty message) + int len // the length of the message (may be 0) +); + +/***************************** +** SOCKET SHARING FUNCTIONS ** +*****************************/ + +// this function returns the actual underlying socket for a GT2Socket. +// this can be used for socket sharing purposes, along with the +// gt2UnrecognizedMessageCallback. +SOCKET gt2GetSocketSOCKET(GT2Socket socket); + +// sets a callback that all unrecognized messages are passed to. an +// unrecognized message is one that can't be matched up to a specific +// connection. if the callback handles the message, it returns true, and the +// GT2Socket ignores the message. if the callback does not recognize the +// message, it returns false, and the socket handles the message (by sending a +// message back indicating the connection is closed). if the callback is NULL, +// it removes any previously set callback. +void gt2SetUnrecognizedMessageCallback(GT2Socket socket, + gt2UnrecognizedMessageCallback callback); + +/******************* +** INFO FUNCTIONS ** +*******************/ + +// gets the socket this connection exists on +GT2Socket gt2GetConnectionSocket(GT2Connection connection); + +// gets the connection's connection state +// GT2Connecting - the connection is still being negotiated +// GT2Connected - the connection is active (has successfully connected, and not +// yet closed) GT2Closing - the connection is in the process of closing (i.e., +// sent a close message and waiting for confirmation). GT2Closed - the +// connection has already been closed and will soon be freed +GT2ConnectionState gt2GetConnectionState(GT2Connection connection); + +// gets a connection's remote IP (in network byte order) +unsigned int gt2GetRemoteIP(GT2Connection connection); + +// gets a connection's remote port (in host byte order) +unsigned short gt2GetRemotePort(GT2Connection connection); + +// gets a socket's local IP (in network byte order) +unsigned int gt2GetLocalIP(GT2Socket socket); + +// gets a socket's local port (in host byte order) +unsigned short gt2GetLocalPort(GT2Socket socket); + +// gets the total size of the connection's incoming buffer. +int gt2GetIncomingBufferSize(GT2Connection connection); + +// gets the amount of available space in the connection's incoming buffer. +int gt2GetIncomingBufferFreeSpace(GT2Connection connection); + +// gets the total size of the connection's outgoing buffer. +int gt2GetOutgoingBufferSize(GT2Connection connection); + +// gets the amount of available space in the connection's outgoing buffer. +int gt2GetOutgoingBufferFreeSpace(GT2Connection connection); + +/************************ +** USER DATA FUNCTIONS ** +************************/ + +void gt2SetSocketData(GT2Socket socket, void* data); +void* gt2GetSocketData(GT2Socket socket); +void gt2SetConnectionData(GT2Connection connection, void* data); +void* gt2GetConnectionData(GT2Connection connection); + +/************************* +** BYTE ORDER FUNCTIONS ** +*************************/ + +unsigned int gt2NetworkToHostInt(unsigned int i); +unsigned int gt2HostToNetworkInt(unsigned int i); +unsigned short gt2HostToNetworkShort(unsigned short s); +unsigned short gt2NetworkToHostShort(unsigned short s); + +/********************** +** ADDRESS FUNCTIONS ** +**********************/ + +// Converts an IP and a port into a text string. The IP must be in network byte +// order, and the port in host byte order. The string must be able to hold at +// least 22 characters (including the NUL). "XXX.XXX.XXX.XXX:XXXXX" If both the +// IP and port are non-zero, the string will be of the form "1.2.3.4:5" +// (":"). If the port is zero, and the IP is non-zero, the string will +// be of the form "1.2.3.4" (""). If the IP is zero, and the port is +// non-zero, the string will be of the form ":5" (":"). If both the IP and +// port are zero, the string will be an empty string ("") The string is +// returned. If the string paramater is NULL, then an internal static string +// will be used. There are two internal strings that are alternated between. +const char* gt2AddressToString( + unsigned int ip, // IP in network byte order. Can be 0. + unsigned short port, // Port in host byte order. Can be 0. + char string[22] // String will be placed in here. Can be NULL. +); + +// Converts a string address into an IP and a port. The IP is stored in network +// byte order, and the port is stored in host byte order. Returns false if +// there was an error parsing the string, or if a supplied hostname can't be +// resolved. Possible string forms: NULL => all IPs, any port (localAddress +// only). +// "" => all IPs, any port (localAddress only). +// "1.2.3.4" => 1.2.3.4 IP, any port (localAddress only). +// "host.com" => host.com's IP, any port (localAddress only). +// ":2786" => all IPs, 2786 port (localAddress only). +// "1.2.3.4:0" => 1.2.3.4 IP, any port (localAddress only). +// "host.com:0" => host.com's IP, any port (localAddress only). +// "0.0.0.0:2786" => all IPs, 2786 port (localAddress only). +// "1.2.3.4:2786" => 1.2.3.4 IP, 2786 port (localAddress or remoteAddress). +// "host.com:2786" => host.com's IP, 2786 port (localAddress or remoteAddress). +// If this function needs to resolve a hostname ("host.com") it may need to +// contact a DNS server, which can cause the function to block for an indefinite +// period of time. Usually it is < 2 seconds, but on certain systems, and under +// certain circumstances, it can take 30 seconds or longer. +GT2Bool gt2StringToAddress( + const char* string, // The string to convert. + unsigned int* + ip, // The IP is stored here, in network byte order. Can be NULL. + unsigned short* + port // The port is stored here, in host byte order. Can be NULL. +); + +// Gets the host information for a machine on the Internet. The first version +// takes an IP in network byte order, and the second version takes a string that +// is either a dotted ip ("1.2.3.4"), or a hostname ("www.gamespy.com"). If the +// function can successfully lookup the host's info, the host's main hostname +// will be returned. If it cannot find the host's info, it returns NULL. For +// the aliases parameter, pass in a pointer to a variable of type (char **). If +// this parameter is not NULL, and the function succeeds, the variable will +// point to a NULL-terminated list of alternate names for the host. For the ips +// parameter, pass in a pointer to a variable of type (int **). If this +// parameter is not NULL, and the function succeeds, the variable will point to +// a NULL-terminated list of altername IPs for the host. Each element in the +// list is actually a pointer to an unsigned int, which is an IP address in +// network byte order. The return value, aliases, and IPs all point to an +// internal data structure, and none of these values should be modified +// directly. Also, the data is only valid until another call needs to use the +// same data structure (virtually ever internet address function will use this +// data structure). If the data will be needed in the future, it should be +// copied off. If this function needs to resolve a hostname ("host.com") it may +// need to contact a DNS server, which can cause the function to block for an +// indefinite period of time. Usually it is < 2 seconds, but on certain +// systems, and under certain circumstances, it can take 30 seconds or longer. +const char* gt2IPToHostInfo(unsigned int ip, char*** aliases, + unsigned int*** ips); +const char* gt2StringToHostInfo(const char* string, char*** aliases, + unsigned int*** ips); + +// The following functions are shortcuts for the above two functions +// (gt2*ToHostInfo()), and each performs a subset of the functionality. They +// are provided so that code that only needs certain information can be a little +// simpler. Before using these, read the comments for the gt2*ToHostInfo() +// functions, as the info also applies to these functions. +const char* gt2IPToHostname(unsigned int ip); +const char* gt2StringToHostname(const char* string); +char** gt2IPToAliases(unsigned int ip); +char** gt2StringToAliases(const char* string); +unsigned int** gt2IPToIPs(unsigned int ip); +unsigned int** gt2StringToIPs(const char* string); + +// these are for getting around adhoc which requires a 48 bit address v.s. a 32 +// bit inet address +void gt2IpToMac(gsi_u32 ip, char* mac); +// change IP address to mac ethernet +gsi_u32 gt2MacToIp(const char* mac); +// change mac ethernet to IP address + +/******************* +** DUMP CALLBACKS ** +*******************/ + +// called with either sent or received data +// trying to send a message from within the send dump callback, or letting the +// socket think from within the receive dump callback can cause serious +// problems, and should not be done. +typedef void (*gt2DumpCallback)( + GT2Socket socket, // the socket the message was on + GT2Connection connection, // the connection the message was on, or NULL if + // there is no connection for this message + unsigned int ip, // the remote ip, in network byte order + unsigned short port, // the remote port, in host byte order + GT2Bool reset, // if true, the connection has been reset (only used by the + // receive callback) + const GT2Byte* message, // the message (should not be modified) + int len // the length of the message +); + +/******************* +** DUMP FUNCTIONS ** +*******************/ + +// sets a callback to be called whenever a UDP datagram is sent or received, and +// when a connection reset is received. pass in a callback of NULL to remove the +// callback. the dumps sit at a lower level than the filters, and allow an app +// to keep an eye on exactly what datagrams are being sent and received, +// allowing for close monitoring. however the dumps cannot be used to modify +// data, only monitor it. the dumps are useful for debugging purposes, and to +// keep track of data send and receive rates (e.g., the Quake 3 engine's +// netgraph). note that these are the actual UDP datagrams being sent and +// received - datagrams may be dropped, repeated, or out-of-order. control +// datagrams (those used internally by the protocol) will be passed to the dump +// callbacks, and certain application messages will have a header at the +// beginning. +void gt2SetSendDump(GT2Socket socket, gt2DumpCallback callback); +void gt2SetReceiveDump(GT2Socket socket, gt2DumpCallback callback); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gt2/gt2Auth.c b/source/gamespy/gt2/gt2Auth.c new file mode 100644 index 000000000..e79988574 --- /dev/null +++ b/source/gamespy/gt2/gt2Auth.c @@ -0,0 +1,90 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +// clang-format off +#include "gt2Main.h" +#include "gt2Auth.h" +#include +// clang-format on + +#define CALCULATEODDMODE(buffer, i, oddmode) \ + ((buffer[i - 1] & 1) ^ (i & 1) ^ oddmode ^ (buffer[0] & 1) ^ \ + ((buffer[0] < 79) ? 1 : 0) ^ ((buffer[i - 1] < buffer[0]) ? 1 : 0)); + +char GT2ChallengeKey[33] = "3b8dd8995f7c40a9a5c5b7dd5b481341"; + +static int gti2VerifyChallenge(const GT2Byte* buffer) { + int oddmode = 0; + int i; + for (i = 1; i < GTI2_CHALLENGE_LEN; i++) { + oddmode = CALCULATEODDMODE(buffer, i, oddmode); + if ((oddmode && (buffer[i] & 1) == 0) || + (!oddmode && ((buffer[i] & 1) == 1))) + return 0; // failed!! + } + return 1; +} + +GT2Byte* gti2GetChallenge(GT2Byte* buffer) { + int i; + int oddmode; + assert(buffer); + + srand((unsigned int)current_time()); + buffer[0] = (GT2Byte)(33 + rand() % 93); // use chars in the range 33 - 125 + oddmode = 0; + for (i = 1; i < GTI2_CHALLENGE_LEN; i++) { + oddmode = CALCULATEODDMODE(buffer, i, oddmode); + buffer[i] = (GT2Byte)(33 + rand() % 93); // use chars in the range 33 - 125 + // if oddmode make sure the char is odd, otherwise make sure it's even + if ((oddmode && (buffer[i] & 1) == 0) || + (!oddmode && ((buffer[i] & 1) == 1))) + buffer[i]++; + } + return buffer; +} + +GT2Byte* gti2GetResponse(GT2Byte* buffer, const GT2Byte* challenge) { + int i; + int valid; + char cchar; + int keylen = (int)strlen(GT2ChallengeKey); + int chalrand; + valid = gti2VerifyChallenge( + challenge); // it's an invalid challenge, give them a bogus response + // assert(GTI2_RESPONSE_LEN <= GTI2_CHALLENGE_LEN); + for (i = 0; i < GTI2_RESPONSE_LEN; i++) { + // use random vals for spots 0 and 13 + if (!valid || i == 0 || i == 13) + buffer[i] = (GT2Byte)(33 + rand() % 93); // use chars in the range 33 - + // 125 + else { // set the character to look back at, never use the random ones! + if (i == 1 || i == 14) + cchar = (char)challenge[i]; + else + cchar = (char)challenge[i - 1]; + chalrand = abs((challenge[((i * challenge[i]) + + GT2ChallengeKey[(i + challenge[i]) % keylen]) % + GTI2_CHALLENGE_LEN] ^ + GT2ChallengeKey[(i * 17991 * cchar) % keylen])); + buffer[i] = (GT2Byte)(33 + chalrand % 93); + } + } + return buffer; +} + +GT2Bool gti2CheckResponse(const GT2Byte* response1, const GT2Byte* response2) { + int i; // when comparing ignore the ones that are random + for (i = 0; i < GTI2_RESPONSE_LEN; i++) { + if (i != 0 && i != 13 && response1[i] != response2[i]) + return GT2False; + } + return GT2True; +} diff --git a/source/gamespy/gt2/gt2Auth.h b/source/gamespy/gt2/gt2Auth.h new file mode 100644 index 000000000..3d31e2544 --- /dev/null +++ b/source/gamespy/gt2/gt2Auth.h @@ -0,0 +1,28 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#define GTI2_CHALLENGE_LEN 32 +#define GTI2_RESPONSE_LEN 32 + +#ifdef __cplusplus +extern "C" { +#endif + +GT2Byte* gti2GetChallenge(GT2Byte* buffer); + +GT2Byte* gti2GetResponse(GT2Byte* buffer, const GT2Byte* challenge); + +GT2Bool gti2CheckResponse(const GT2Byte* response1, const GT2Byte* response2); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/gt2/gt2Buffer.c b/source/gamespy/gt2/gt2Buffer.c new file mode 100644 index 000000000..d1125bb8d --- /dev/null +++ b/source/gamespy/gt2/gt2Buffer.c @@ -0,0 +1,75 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Buffer.h" +#include + +GT2Bool gti2AllocateBuffer(GTI2Buffer* buffer, int size) { + buffer->buffer = (GT2Byte*)gsimalloc((unsigned int)size); + if (!buffer->buffer) + return GT2False; + buffer->size = size; + + return GT2True; +} + +int gti2GetBufferFreeSpace(const GTI2Buffer* buffer) { + return (buffer->size - buffer->len); +} + +void gti2BufferWriteByte(GTI2Buffer* buffer, GT2Byte b) { + assert(buffer->len < buffer->size); +#if 0 + if(buffer->len >= buffer->size) + return; +#endif + + buffer->buffer[buffer->len++] = b; +} + +void gti2BufferWriteUShort(GTI2Buffer* buffer, unsigned short s) { + assert((buffer->len + 2) <= buffer->size); +#if 0 + if((buffer->len + 2) > buffer->size) + return; +#endif + + buffer->buffer[buffer->len++] = (GT2Byte)((s >> 8) & 0xFF); + buffer->buffer[buffer->len++] = (GT2Byte)(s & 0xFF); +} + +void gti2BufferWriteData(GTI2Buffer* buffer, const GT2Byte* data, int len) { + if (!data || !len) + return; + + if (len == -1) + len = (int)strlen((const char*)data); + + assert((buffer->len + len) <= buffer->size); +#if 0 + if(buffer->len >= buffer->size) + return; +#endif + + memcpy(buffer->buffer + buffer->len, data, (unsigned int)len); + buffer->len += len; +} + +void gti2BufferShorten(GTI2Buffer* buffer, int start, int shortenBy) { + if (start == -1) + start = (buffer->len - shortenBy); + + assert(start <= buffer->len); + assert(shortenBy <= (buffer->len - start)); + + memmove(buffer->buffer + start, buffer->buffer + start + shortenBy, + (unsigned int)(buffer->len - start - shortenBy)); + buffer->len -= shortenBy; +} diff --git a/source/gamespy/gt2/gt2Buffer.h b/source/gamespy/gt2/gt2Buffer.h new file mode 100644 index 000000000..6fa95430e --- /dev/null +++ b/source/gamespy/gt2/gt2Buffer.h @@ -0,0 +1,24 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "gt2Main.h" + +GT2Bool gti2AllocateBuffer(GTI2Buffer* buffer, int size); + +int gti2GetBufferFreeSpace(const GTI2Buffer* buffer); + +void gti2BufferWriteByte(GTI2Buffer* buffer, GT2Byte b); +void gti2BufferWriteUShort(GTI2Buffer* buffer, unsigned short s); +void gti2BufferWriteData(GTI2Buffer* buffer, const GT2Byte* data, int len); + +// shortens the buffer by "shortenBy" (length, not size) +void gti2BufferShorten(GTI2Buffer* buffer, int start, int shortenBy); diff --git a/source/gamespy/gt2/gt2Callback.c b/source/gamespy/gt2/gt2Callback.c new file mode 100644 index 000000000..7c7625fba --- /dev/null +++ b/source/gamespy/gt2/gt2Callback.c @@ -0,0 +1,355 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Callback.h" +#include "gt2Socket.h" + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +GT2Bool gti2SocketErrorCallback(GT2Socket socket) { + assert(socket); + if (!socket) + return GT2True; + + if (!socket->socketErrorCallback) + return GT2True; + + socket->callbackLevel++; + + socket->socketErrorCallback(socket); + + socket->callbackLevel--; + + // check if the socket should be closed + if (socket->close && !socket->callbackLevel) { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ConnectAttemptCallback(GT2Socket socket, GT2Connection connection, + unsigned int ip, unsigned short port, + int latency, GT2Byte* message, int len) { + assert(socket && connection); + if (!socket || !connection) + return GT2True; + + if (!socket->connectAttemptCallback) + return GT2True; + + // check for an empty message + if (!len || !message) { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + connection->callbackLevel++; + + socket->connectAttemptCallback(socket, connection, ip, port, latency, message, + len); + + socket->callbackLevel--; + connection->callbackLevel--; + + // check if the socket should be closed + if (socket->close && !socket->callbackLevel) { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +GT2Bool gti2ConnectedCallback(GT2Connection connection, GT2Result result, + GT2Byte* message, int len) { + assert(connection); + if (!connection) + return GT2True; + + // store the result + connection->connectionResult = result; + + if (!connection->callbacks.connected) + return GT2True; + + // check for an empty message + if (!len || !message) { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.connected(connection, result, message, len); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if (connection->socket->close && !connection->socket->callbackLevel) { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ReceivedCallback(GT2Connection connection, GT2Byte* message, + int len, GT2Bool reliable) { + assert(connection); + if (!connection) + return GT2True; + + if (!connection->callbacks.received) + return GT2True; + + // check for an empty message + if (!len || !message) { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.received(connection, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if (connection->socket->close && !connection->socket->callbackLevel) { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ClosedCallback(GT2Connection connection, GT2CloseReason reason) { + assert(connection); + if (!connection) + return GT2True; + + if (!connection->callbacks.closed) + return GT2True; + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.closed(connection, reason); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if (connection->socket->close && !connection->socket->callbackLevel) { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2PingCallback(GT2Connection connection, int latency) { + assert(connection); + if (!connection) + return GT2True; + + if (!connection->callbacks.ping) + return GT2True; + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + connection->callbacks.ping(connection, latency); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if (connection->socket->close && !connection->socket->callbackLevel) { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +/********************* +** FILTER CALLBACKS ** +*********************/ + +GT2Bool gti2SendFilterCallback(GT2Connection connection, int filterID, + const GT2Byte* message, int len, + GT2Bool reliable) { + gt2SendFilterCallback* callback; + + assert(connection); + if (!connection) + return GT2True; + + callback = + (gt2SendFilterCallback*)ArrayNth(connection->sendFilters, filterID); + if (!callback) + return GT2True; + + // check for an empty message + if (!len || !message) { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + (*callback)(connection, filterID, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if (connection->socket->close && !connection->socket->callbackLevel) { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ReceiveFilterCallback(GT2Connection connection, int filterID, + GT2Byte* message, int len, GT2Bool reliable) { + gt2ReceiveFilterCallback* callback; + + assert(connection); + if (!connection) + return GT2True; + + callback = + (gt2ReceiveFilterCallback*)ArrayNth(connection->receiveFilters, filterID); + if (!callback) + return GT2True; + + // check for an empty message + if (!len || !message) { + message = NULL; + len = 0; + } + + connection->callbackLevel++; + connection->socket->callbackLevel++; + + (*callback)(connection, filterID, message, len, reliable); + + connection->callbackLevel--; + connection->socket->callbackLevel--; + + // check if the socket should be closed + if (connection->socket->close && !connection->socket->callbackLevel) { + gti2CloseSocket(connection->socket); + return GT2False; + } + + return GT2True; +} + +/******************* +** DUMP CALLBACKS ** +*******************/ + +GT2Bool gti2DumpCallback(GT2Socket socket, GT2Connection connection, + unsigned int ip, unsigned short port, GT2Bool reset, + const GT2Byte* message, int len, GT2Bool send) { + gt2DumpCallback callback; + + assert(socket); + if (!socket) + return GT2True; + + if (send) + callback = socket->sendDumpCallback; + else + callback = socket->receiveDumpCallback; + + if (!callback) + return GT2True; + + // check for an empty message + if (!len || !message) { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + if (connection) + connection->callbackLevel++; + + callback(socket, connection, ip, port, reset, message, len); + + socket->callbackLevel--; + if (connection) + connection->callbackLevel--; + + // check if the socket should be closed + if (socket->close && !socket->callbackLevel) { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +GT2Bool gti2UnrecognizedMessageCallback(GT2Socket socket, unsigned int ip, + unsigned short port, GT2Byte* message, + int len, GT2Bool* handled) { + *handled = GT2False; + + assert(socket); + if (!socket) + return GT2True; + + if (!socket->unrecognizedMessageCallback) + return GT2True; + + // check for an empty message + if (!len || !message) { + message = NULL; + len = 0; + } + + socket->callbackLevel++; + + *handled = + socket->unrecognizedMessageCallback(socket, ip, port, message, len); + + socket->callbackLevel--; + + // check if the socket should be closed + if (socket->close && !socket->callbackLevel) { + gti2CloseSocket(socket); + return GT2False; + } + + return GT2True; +} diff --git a/source/gamespy/gt2/gt2Callback.h b/source/gamespy/gt2/gt2Callback.h new file mode 100644 index 000000000..7501bd56b --- /dev/null +++ b/source/gamespy/gt2/gt2Callback.h @@ -0,0 +1,64 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "gt2Main.h" + +/********************* +** SOCKET CALLBACKS ** +*********************/ + +GT2Bool gti2SocketErrorCallback(GT2Socket socket); + +GT2Bool gti2ConnectAttemptCallback(GT2Socket socket, GT2Connection connection, + unsigned int ip, unsigned short port, + int latency, GT2Byte* message, int len); + +/************************* +** CONNECTION CALLBACKS ** +*************************/ + +GT2Bool gti2ConnectedCallback(GT2Connection connection, GT2Result result, + GT2Byte* message, int len); + +GT2Bool gti2ReceivedCallback(GT2Connection connection, GT2Byte* message, + int len, GT2Bool reliable); + +GT2Bool gti2ClosedCallback(GT2Connection connection, GT2CloseReason reason); + +GT2Bool gti2PingCallback(GT2Connection connection, int latency); + +/********************* +** FILTER CALLBACKS ** +*********************/ + +GT2Bool gti2SendFilterCallback(GT2Connection connection, int filterID, + const GT2Byte* message, int len, + GT2Bool reliable); + +GT2Bool gti2ReceiveFilterCallback(GT2Connection connection, int filterID, + GT2Byte* message, int len, GT2Bool reliable); + +/******************* +** DUMP CALLBACKS ** +*******************/ + +GT2Bool gti2DumpCallback(GT2Socket socket, GT2Connection connection, + unsigned int ip, unsigned short port, GT2Bool reset, + const GT2Byte* message, int len, GT2Bool send); + +/***************************** +** SOCKET SHARING CALLBACKS ** +*****************************/ + +GT2Bool gti2UnrecognizedMessageCallback(GT2Socket socket, unsigned int ip, + unsigned short port, GT2Byte* message, + int len, GT2Bool* handled); diff --git a/source/gamespy/gt2/gt2Connection.c b/source/gamespy/gt2/gt2Connection.c new file mode 100644 index 000000000..d10747c94 --- /dev/null +++ b/source/gamespy/gt2/gt2Connection.c @@ -0,0 +1,316 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Connection.h" +#include "gt2Callback.h" +#include "gt2Message.h" +#include "gt2Socket.h" +#include "gt2Utility.h" +#include + +GT2Result gti2NewOutgoingConnection(GT2Socket socket, GT2Connection* connection, + unsigned int ip, unsigned short port) { + GT2Result result; + + // create the object + result = gti2NewSocketConnection(socket, connection, ip, port); + if (result != GT2Success) + return result; + + // set initial states + (*connection)->state = GTI2AwaitingServerChallenge; + (*connection)->initiated = GT2True; + + return GT2Success; +} + +GT2Result gti2NewIncomingConnection(GT2Socket socket, GT2Connection* connection, + unsigned int ip, unsigned short port) { + GT2Result result; + + // create the object + result = gti2NewSocketConnection(socket, connection, ip, port); + if (result != GT2Success) + return result; + + // set initial states + (*connection)->state = GTI2AwaitingClientChallenge; + (*connection)->initiated = GT2False; + + return GT2Success; +} + +GT2Result gti2StartConnectionAttempt(GT2Connection connection, + const GT2Byte* message, int len, + GT2ConnectionCallbacks* callbacks) { + char challenge[GTI2_CHALLENGE_LEN]; + + // check the message and len + gti2MessageCheck(&message, &len); + + // copy off the message + if (len > 0) { + connection->initialMessage = (char*)gsimalloc((unsigned int)len); + if (!connection->initialMessage) + return GT2OutOfMemory; + + memcpy(connection->initialMessage, message, (unsigned int)len); + connection->initialMessageLen = len; + } + + // copy the callbacks + if (callbacks) + connection->callbacks = *callbacks; + + // generate a challenge + gti2GetChallenge((GT2Byte*)challenge); + + // generate and store the expected response + gti2GetResponse((GT2Byte*)connection->response, (GT2Byte*)challenge); + + // send the client challenge + gti2SendClientChallenge(connection, challenge); + + // update our state + connection->state = GTI2AwaitingServerChallenge; + + return GT2Success; +} + +GT2Bool gti2AcceptConnection(GT2Connection connection, + GT2ConnectionCallbacks* callbacks) { + // was the connection already closed? + if (connection->freeAtAcceptReject) { + // clear the flag + connection->freeAtAcceptReject = GT2False; + + // let the app know if was already closed + return GT2False; + } + + // make sure this flag gets cleared + connection->freeAtAcceptReject = GT2False; + + // check that we're still awaiting this + if (connection->state != GTI2AwaitingAcceptReject) + return GT2False; + + // let the other side know + gti2SendAccept(connection); + + // update our state + connection->state = GTI2Connected; + + // store the callbacks + if (callbacks) + connection->callbacks = *callbacks; + + return GT2True; +} + +void gti2RejectConnection(GT2Connection connection, const GT2Byte* message, + int len) { + // make sure this flag gets cleared + connection->freeAtAcceptReject = GT2False; + + // check that we're still awaiting this + if (connection->state != GTI2AwaitingAcceptReject) + return; + + // check the message and len + gti2MessageCheck(&message, &len); + + // let the other side know + gti2SendReject(connection, message, len); + + // update our state + connection->state = GTI2Closing; +} + +GT2Bool gti2ConnectionSendData(GT2Connection connection, const GT2Byte* message, + int len) { + // send the data on the socket + if (!gti2SocketSend(connection->socket, connection->ip, connection->port, + message, len)) + return GT2False; + + // mark the time (used for keep-alives) + connection->lastSend = current_time(); + + return GT2True; +} + +static GT2Bool gti2CheckTimeout(GT2Connection connection, gsi_time now) { + // are we still trying to connect? + if (connection->state < GTI2Connected) { + GT2Bool timedOut = GT2False; + + // is this the initiator + if (connection->initiated) { + // do we have a timeout? + if (connection->timeout) { + // check the time taken against the timeout + if ((now - connection->startTime) > connection->timeout) + timedOut = GT2True; + } + } else { + // don't time them out if they're waiting for us + if (connection->state < GTI2AwaitingAcceptReject) { + // check the time taken against the timeout + if ((now - connection->startTime) > GTI2_SERVER_TIMEOUT) + timedOut = GT2True; + } + } + + // check if we timed out + if (timedOut) { + // let them know + gti2SendClosed(connection); + + // mark it as closed + gti2ConnectionClosed(connection); + + // call the callback + if (!gti2ConnectedCallback(connection, GT2TimedOut, NULL, 0)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2SendRetries(GT2Connection connection, gsi_time now) { + int i; + int len; + GTI2OutgoingBufferMessage* message; + + // go through the list of outgoing messages awaiting confirmation + len = ArrayLength(connection->outgoingBufferMessages); + for (i = 0; i < len; i++) { + // get the message + message = (GTI2OutgoingBufferMessage*)ArrayNth( + connection->outgoingBufferMessages, i); + + // check if it's time to resend it + if ((now - message->lastSend) > GTI2_RESEND_TIME) { + if (!gti2ResendMessage(connection, message)) + return GT2False; + } + } + + return GT2True; +} + +static GT2Bool gti2CheckPendingAck(GT2Connection connection, gsi_time now) { + // check for nothing pending + if (!connection->pendingAck) + return GT2True; + + // check how long it has been pending + if ((now - connection->pendingAckTime) > GTI2_PENDING_ACK_TIME) { + if (!gti2SendAck(connection)) + return GT2False; + } + + return GT2True; +} + +static GT2Bool gti2CheckKeepAlive(GT2Connection connection, gsi_time now) { + if ((now - connection->lastSend) > GTI2_KEEP_ALIVE_TIME) { + if (!gti2SendKeepAlive(connection)) + return GT2False; + } + + return GT2True; +} + +GT2Bool gti2ConnectionThink(GT2Connection connection, gsi_time now) { + // check timeout + if (!gti2CheckTimeout(connection, now)) + return GT2False; + + // check keep alives + if (!gti2CheckKeepAlive(connection, now)) + return GT2False; + + // send retries + if (!gti2SendRetries(connection, now)) + return GT2False; + + // check the pending ack + if (!gti2CheckPendingAck(connection, now)) + return GT2False; + + return GT2True; +} + +void gti2CloseConnection(GT2Connection connection, GT2Bool hard) { + // check if it should be hard or soft closed + if (hard) { + // check if it's already closed + if (connection->state >= GTI2Closed) + return; + + // mark it as closed + gti2ConnectionClosed(connection); + + // send a closed message + gti2SendClosed(connection); + + // call the callback + gti2ClosedCallback(connection, GT2LocalClose); + + // try and free it + gti2FreeSocketConnection(connection); + } else { + // mark it as closing + connection->state = GTI2Closing; + + // send the close + gti2SendClose(connection); + } +} + +void gti2ConnectionClosed(GT2Connection connection) { + // check for already closed + if (connection->state == GTI2Closed) + return; + + // mark the connection as closed + connection->state = GTI2Closed; + + // remove it from the connected list + TableRemove(connection->socket->connections, &connection); + + // add it to the closed list + ArrayAppend(connection->socket->closedConnections, &connection); +} + +void gti2ConnectionCleanup(GT2Connection connection) { + if (connection->initialMessage) + gsifree(connection->initialMessage); + + if (connection->incomingBuffer.buffer) + gsifree(connection->incomingBuffer.buffer); + if (connection->outgoingBuffer.buffer) + gsifree(connection->outgoingBuffer.buffer); + + if (connection->incomingBufferMessages) + ArrayFree(connection->incomingBufferMessages); + if (connection->outgoingBufferMessages) + ArrayFree(connection->outgoingBufferMessages); + + if (connection->sendFilters) + ArrayFree(connection->sendFilters); + if (connection->receiveFilters) + ArrayFree(connection->receiveFilters); + + gsifree(connection); +} diff --git a/source/gamespy/gt2/gt2Connection.h b/source/gamespy/gt2/gt2Connection.h new file mode 100644 index 000000000..77afeac9c --- /dev/null +++ b/source/gamespy/gt2/gt2Connection.h @@ -0,0 +1,39 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "gt2Main.h" + +GT2Result gti2NewOutgoingConnection(GT2Socket socket, GT2Connection* connection, + unsigned int ip, unsigned short port); +GT2Result gti2NewIncomingConnection(GT2Socket socket, GT2Connection* connection, + unsigned int ip, unsigned short port); + +GT2Result gti2StartConnectionAttempt(GT2Connection connection, + const GT2Byte* message, int len, + GT2ConnectionCallbacks* callbacks); + +GT2Bool gti2AcceptConnection(GT2Connection connection, + GT2ConnectionCallbacks* callbacks); + +void gti2RejectConnection(GT2Connection connection, const GT2Byte* message, + int len); + +GT2Bool gti2ConnectionSendData(GT2Connection connection, const GT2Byte* message, + int len); + +GT2Bool gti2ConnectionThink(GT2Connection connection, gsi_time now); + +void gti2CloseConnection(GT2Connection connection, GT2Bool hard); + +void gti2ConnectionClosed(GT2Connection connection); + +void gti2ConnectionCleanup(GT2Connection connection); diff --git a/source/gamespy/gt2/gt2Filter.h b/source/gamespy/gt2/gt2Filter.h new file mode 100644 index 000000000..210b27548 --- /dev/null +++ b/source/gamespy/gt2/gt2Filter.h @@ -0,0 +1,27 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "gt2Main.h" + +GT2Bool gti2AddSendFilter(GT2Connection connection, + gt2SendFilterCallback callback); +void gti2RemoveSendFilter(GT2Connection connection, + gt2SendFilterCallback callback); +GT2Bool gti2FilteredSend(GT2Connection connection, int filterID, + const GT2Byte* message, int len, GT2Bool reliable); + +GT2Bool gti2AddReceiveFilter(GT2Connection connection, + gt2ReceiveFilterCallback callback); +void gti2RemoveReceiveFilter(GT2Connection connection, + gt2ReceiveFilterCallback callback); +GT2Bool gti2FilteredReceive(GT2Connection connection, int filterID, + GT2Byte* message, int len, GT2Bool reliable); diff --git a/source/gamespy/gt2/gt2Main.c b/source/gamespy/gt2/gt2Main.c new file mode 100644 index 000000000..9a8b9fa82 --- /dev/null +++ b/source/gamespy/gt2/gt2Main.c @@ -0,0 +1,383 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Main.h" +#include "gt2Callback.h" +#include "gt2Connection.h" +#include "gt2Filter.h" +#include "gt2Message.h" +#include "gt2Socket.h" +#include "gt2Utility.h" + +#define GTI2_INVALID_IP_MASK 0xE0000000 + +/********************* +** SOCKET FUNCTIONS ** +*********************/ + +GT2Result gt2CreateSocket(GT2Socket* socket, const char* localAddress, + int outgoingBufferSize, int incomingBufferSize, + gt2SocketErrorCallback callback) { + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, + incomingBufferSize, callback, GTI2UdpProtocol); +} + +GT2Result gt2CreateAdHocSocket(GT2Socket* socket, const char* localAddress, + int outgoingBufferSize, int incomingBufferSize, + gt2SocketErrorCallback callback) { + return gti2CreateSocket(socket, localAddress, outgoingBufferSize, + incomingBufferSize, callback, GTI2AdHocProtocol); +} + +void gt2CloseSocket(GT2Socket socket) { + // hard close the connections + gt2CloseAllConnectionsHard(socket); + + // close the socket + gti2CloseSocket(socket); +} + +void gt2Think(GT2Socket socket) { + // check for incoming messages + if (!gti2ReceiveMessages(socket)) + return; + + // let the connections think + if (!gti2SocketConnectionsThink(socket)) + return; + + // free closed connections + gti2FreeClosedConnections(socket); +} + +GT2Result gt2SendRawUDP(GT2Socket socket, const char* remoteAddress, + const GT2Byte* message, int len) { + unsigned int ip; + unsigned short port; + + // get the ip and port + if (!gt2StringToAddress(remoteAddress, &ip, &port) || !port) + return GT2AddressError; + + // check for invalid IP ranges + // class D (224-239.*, multicast) and class E (240-255.*, experimental) + if ((ntohl(ip) & GTI2_INVALID_IP_MASK) == GTI2_INVALID_IP_MASK) + return GT2AddressError; + + // check if this is for broadcast + if (!ip) { + // check if broadcast is enable + if (!socket->broadcastEnabled) { + if (!SetSockBroadcast(socket->socket)) + return GT2NetworkError; + socket->broadcastEnabled = GT2True; + } + + // set the broadcast ip + ip = gsiGetBroadcastIP(); + } + + // send the datagram + gti2SocketSend(socket, ip, port, message, len); + + return GT2Success; +} + +/********************* +** LISTEN FUNCTIONS ** +*********************/ + +void gt2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback) { + gti2Listen(socket, callback); +} + +GT2Bool gt2Accept(GT2Connection connection, GT2ConnectionCallbacks* callbacks) { + return gti2AcceptConnection(connection, callbacks); +} + +void gt2Reject(GT2Connection connection, const GT2Byte* message, int len) { + gti2RejectConnection(connection, message, len); +} + +/************************* +** CONNECTION FUNCTIONS ** +*************************/ + +GT2Result gt2Connect(GT2Socket socket, GT2Connection* connection, + const char* remoteAddress, const GT2Byte* message, int len, + int timeout, GT2ConnectionCallbacks* callbacks, + GT2Bool blocking) { + GT2Connection connectionTemp; + GT2Result result; + GT2Bool done; + unsigned int ip; + unsigned short port; + + { + // get the ip and port + if (!gt2StringToAddress(remoteAddress, &ip, &port) || !ip || !port) + return GT2AddressError; + } + + // check for invalid IP ranges + // class D (224-239.*, multicast) and class E (240-255.*, experimental) + if ((ntohl(ip) & GTI2_INVALID_IP_MASK) == GTI2_INVALID_IP_MASK) + return GT2AddressError; + + // create the connection object + result = gti2NewOutgoingConnection(socket, &connectionTemp, ip, port); + if (result) + return result; + + // save the timeout value + connectionTemp->timeout = (unsigned int)timeout; + + // initiate the connection attempt + result = gti2StartConnectionAttempt(connectionTemp, message, len, callbacks); + if (result) { + gti2FreeSocketConnection(connectionTemp); + return result; + } + + // if not blocking, return now + if (!blocking) { + if (connection) + *connection = connectionTemp; + return GT2Success; + } + + // we're not really in a callback, but this will prevent the connection + // from being freed before the loop finishes. + connectionTemp->callbackLevel++; + + // if blocking, loop until the connect attempt is done + do { + // think + gt2Think(socket); + + // check if we're done + done = (connectionTemp->state >= GTI2Connected); + + // if we're not done, take a rest + if (!done) + msleep(1); + } while (!done); + + // bring the callback level back down + connectionTemp->callbackLevel--; + + // is it success? + if (connectionTemp->state == GTI2Connected) + *connection = connectionTemp; + + return connectionTemp->connectionResult; +} + +GT2Result gt2Send(GT2Connection connection, const GT2Byte* message, int len, + GT2Bool reliable) { + // used to check for voice data in reliable messages + unsigned short vdpDataLength; + + // can't send a message if not connected + if (connection->state != GTI2Connected) + return GT2InvalidConnection; + + // check the message and len + gti2MessageCheck(&message, &len); + + if (reliable && connection->socket->protocolType == GTI2VdpProtocol) { + memcpy(&vdpDataLength, message, sizeof(unsigned short)); + assert(vdpDataLength + connection->socket->protocolOffset == len); + if (vdpDataLength + connection->socket->protocolOffset != len) + return GT2InvalidMessage; + } + + // do we need to filter it? + if (ArrayLength(connection->sendFilters)) { + gti2SendFilterCallback(connection, 0, message, len, reliable); + return GT2Success; + } + + if (gti2Send(connection, message, len, reliable)) + return GT2Success; + + return GT2SendFailed; +} + +void gt2Ping(GT2Connection connection) { gti2SendPing(connection); } + +void gt2CloseConnection(GT2Connection connection) { + gti2CloseConnection(connection, GT2False); +} + +void gt2CloseConnectionHard(GT2Connection connection) { + gti2CloseConnection(connection, GT2True); +} + +static void gti2CloseAllConnectionsMap(void* elem, void* clientData) { + gt2CloseConnection(*(GT2Connection*)elem); + + GSI_UNUSED(clientData); +} + +void gt2CloseAllConnections(GT2Socket socket) { + TableMapSafe(socket->connections, gti2CloseAllConnectionsMap, NULL); +} + +static void gti2CloseAllConnectionsHardMap(void* elem, void* clientData) { + gt2CloseConnectionHard(*(GT2Connection*)elem); + + GSI_UNUSED(clientData); +} + +void gt2CloseAllConnectionsHard(GT2Socket socket) { + TableMapSafe(socket->connections, gti2CloseAllConnectionsHardMap, NULL); +} + +/************************* +** MESSAGE CONFIRMATION ** +*************************/ +GT2MessageID gt2GetLastSentMessageID(GT2Connection connection) { + return (GT2MessageID)(connection->serialNumber - 1); +} + +GT2Bool gt2WasMessageIDConfirmed(GT2Connection connection, + GT2MessageID messageID) { + return gti2WasMessageIDConfirmed(connection, messageID); +} + +/********************* +** FILTER FUNCTIONS ** +*********************/ + +GT2Bool gt2AddSendFilter(GT2Connection connection, + gt2SendFilterCallback callback) { + return gti2AddSendFilter(connection, callback); +} + +void gt2RemoveSendFilter(GT2Connection connection, + gt2SendFilterCallback callback) { + gti2RemoveSendFilter(connection, callback); +} + +void gt2FilteredSend(GT2Connection connection, int filterID, + const GT2Byte* message, int len, GT2Bool reliable) { + gti2FilteredSend(connection, filterID, message, len, reliable); +} + +GT2Bool gt2AddReceiveFilter(GT2Connection connection, + gt2ReceiveFilterCallback callback) { + return gti2AddReceiveFilter(connection, callback); +} + +void gt2RemoveReceiveFilter(GT2Connection connection, + gt2ReceiveFilterCallback callback) { + gti2RemoveReceiveFilter(connection, callback); +} + +void gt2FilteredReceive(GT2Connection connection, int filterID, + GT2Byte* message, int len, GT2Bool reliable) { + gti2FilteredReceive(connection, filterID, message, len, reliable); +} + +/******************* +** INFO FUNCTIONS ** +*******************/ + +GT2Socket gt2GetConnectionSocket(GT2Connection connection) { + return connection->socket; +} + +GT2ConnectionState gt2GetConnectionState(GT2Connection connection) { + if (connection->state < GTI2Connected) + return GT2Connecting; + if (connection->state == GTI2Connected) + return GT2Connected; + if (connection->state == GTI2Closing) + return GT2Closing; + return GT2Closed; +} + +unsigned int gt2GetRemoteIP(GT2Connection connection) { return connection->ip; } + +unsigned short gt2GetRemotePort(GT2Connection connection) { + return connection->port; +} + +unsigned int gt2GetLocalIP(GT2Socket socket) { return socket->ip; } + +unsigned short gt2GetLocalPort(GT2Socket socket) { return socket->port; } + +int gt2GetIncomingBufferSize(GT2Connection connection) { + return connection->incomingBuffer.size; +} + +int gt2GetIncomingBufferFreeSpace(GT2Connection connection) { + return (connection->incomingBuffer.size - connection->incomingBuffer.len); +} + +int gt2GetOutgoingBufferSize(GT2Connection connection) { + return connection->outgoingBuffer.size; +} + +int gt2GetOutgoingBufferFreeSpace(GT2Connection connection) { + return (connection->outgoingBuffer.size - connection->outgoingBuffer.len); +} + +/***************************** +** SOCKET SHARING FUNCTIONS ** +*****************************/ + +SOCKET gt2GetSocketSOCKET(GT2Socket socket) { return socket->socket; } + +void gt2SetUnrecognizedMessageCallback( + GT2Socket socket, gt2UnrecognizedMessageCallback callback) { + socket->unrecognizedMessageCallback = callback; +} + +/************************ +** USER DATA FUNCTIONS ** +************************/ + +void gt2SetSocketData(GT2Socket socket, void* data) { + assert(socket); + + socket->data = data; +} + +void* gt2GetSocketData(GT2Socket socket) { + assert(socket); + + return socket->data; +} + +void gt2SetConnectionData(GT2Connection connection, void* data) { + assert(connection); + + connection->data = data; +} + +void* gt2GetConnectionData(GT2Connection connection) { + assert(connection); + + return connection->data; +} + +/******************* +** DUMP FUNCTIONS ** +*******************/ + +void gt2SetSendDump(GT2Socket socket, gt2DumpCallback callback) { + socket->sendDumpCallback = callback; +} + +void gt2SetReceiveDump(GT2Socket socket, gt2DumpCallback callback) { + socket->receiveDumpCallback = callback; +} diff --git a/source/gamespy/gt2/gt2Main.h b/source/gamespy/gt2/gt2Main.h new file mode 100644 index 000000000..db918689e --- /dev/null +++ b/source/gamespy/gt2/gt2Main.h @@ -0,0 +1,271 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +// clang-format off +#include "gt2.h" +#include "../darray.h" +#include "../hashtable.h" +#include "gt2Auth.h" +// clang-format on + +/************************* +** CONFIGURABLE DEFINES ** +*************************/ + +// these defines are internal to GT2 and are NOT guaranteed to persist from +// version to version. + +// If set, this will convert all big endian vars to little endian before +// sending accross the net +// And on big endian machines, convert little endian to big endian on recv +//#define _GT2_ENDIAN_CONVERT_ENABLE // add this to your compiler +// pre-processor options + +#if defined GSI_BIG_ENDIAN && defined _GT2_ENDIAN_CONVERT_ENABLE +#define _GT2_ENDIAN_CONVERT +#endif + +// any unreliable application message that starts with this magic string will +// have extra overhead. the string can be changed to something that your +// application will not use, or not use frequently. the only impact of this +// change will be to make your application incomatible with other application's +// using either the original or another different magic string. +// the string can consist of any number of characters, as long as there's at +// least one character, and the length define matches the string's length. +#define GTI2_MAGIC_STRING "\xFE\xFE" +#define GTI2_MAGIC_STRING_LEN 2 + +// the size of the buffer into which GT2 directly receives messages. this +// buffer is declared on the stack, and so can be fairly large on most systems +// without having any impact. however, on some systems with small stacks, this +// size can overflow the stack, in which case it should be lowered. note, this +// buffer size only needs to be slighty larger than the largest message that +// will be sent ("slighty larger" due to overhead with reliable messages, and +// unreliable messages starting with the magic string). +#define GTI2_STACK_RECV_BUFFER_SIZE 65535 + +// a server will disconnect a client that doesn't not successfully connect +// within this time (in milliseconds). if the connectAttemptCallback has been +// called, and GT2 is awaiting an accept/reject, the attempt will not be +// timed-out (although the client may abort the attempt at any time). +#define GTI2_SERVER_TIMEOUT (1 * 60 * 1000) +// the time (in milliseconds) GT2 waits between resending a message whose +// delivery has not yet been confirmed. +#define GTI2_RESEND_TIME 1000 +// the time (in milliseconds) GT2 waits after receiving a message it must +// acknowledge before it actually sends the ack. this allows it to combine +// acks, or include acks as part of other reliable messages it sends. if an ack +// is pending, a new incoming message does not reset this timer. +#define GTI2_PENDING_ACK_TIME 100 +// if GT2 does not send a message for this amount of time (in milliseconds), it +// sends a keep-alive message. +#define GTI2_KEEP_ALIVE_TIME (30 * 1000) +// if this is defined, it sets the percentage of sent datagrams to drop. this +// is good for simulating what will happen on a high packet loss connection. +//#define GTI2_DROP_SEND_RATE 30 +typedef enum { + GTI2UdpProtocol, // UDP socket type for standard sockets + GTI2VdpProtocol = 2, // VDP socket type only used for Xbox VDP sockets + GTI2AdHocProtocol = 3 // socket type only used for PSP Adhoc sockets +} GTI2ProtocolType; + +// The Maximum offset of eiter UDP or VDP +// measured in bytes +// used as a buffer offset +#define MAX_PROTOCOL_OFFSET 2 + +/********** +** TYPES ** +**********/ + +typedef enum { + // client-only states + GTI2AwaitingServerChallenge, // sent challenge, waiting for server's challenge + GTI2AwaitingAcceptance, // sent response, waiting for accept/reject from + // server + + // server-only states + GTI2AwaitingClientChallenge, // receiving challenge from a new client + GTI2AwaitingClientResponse, // sent challenge, waiting for client's response + GTI2AwaitingAcceptReject, // got client's response, waiting for app to + // accept/reject + + // post-negotiation states + GTI2Connected, // connected + GTI2Closing, // sent a close message (GTI2Close or GTI2Reject), waiting for + // confirmation + GTI2Closed // connection has been closed, free it as soon as possible +} GTI2ConnectionState; + +// message types +typedef enum { + // reliable messages + // all start with + // type is 1 bytes, SN and ESN are 2 bytes each + GTI2MsgAppReliable, // reliable application message + GTI2MsgClientChallenge, // client's challenge to the server (initial + // connection request) + GTI2MsgServerChallenge, // server's response to the client's challenge, and + // his challenge to the client + GTI2MsgClientResponse, // client's response to the server's challenge + GTI2MsgAccept, // server accepting client's connection attempt + GTI2MsgReject, // server rejecting client's connection attempt + GTI2MsgClose, // message indicating the connection is closing + GTI2MsgKeepAlive, // keep-alive used to help detect dropped connections + + GTI2NumReliableMessages, + + // unreliable messages + GTI2MsgAck = 100, // acknowledge receipt of reliable message(s) + GTI2MsgNack, // alert sender to missing reliable message(s) + GTI2MsgPing, // used to determine latency + GTI2MsgPong, // a reply to a ping + GTI2MsgClosed // confirmation of connection closure (GTI2MsgClose or + // GTI2MsgReject) - also sent in response to bad messages from + // unknown addresses + + // unreliable messages don't really have a message type, just the magic string + // repeated at the start +} GTI2MessageType; + +/*************** +** STRUCTURES ** +***************/ + +typedef struct GTI2Buffer { + GT2Byte* buffer; // The buffer's bytes. + int size; // Number of bytes in buffer. + int len; // Length of actual data in buffer. +} GTI2Buffer; + +typedef struct GTI2IncomingBufferMessage { + int start; // the start of the message + int len; // the length of the message + GTI2MessageType type; // the type + unsigned short serialNumber; // the serial number +} GTI2IncomingBufferMessage; + +typedef struct GTI2OutgoingBufferMessage { + int start; // the start of the message + int len; // the length of the message + unsigned short serialNumber; // the serial number + gsi_time lastSend; // last time this message was sent +} GTI2OutgoingBufferMessage; + +typedef struct GTI2Socket { + SOCKET socket; // the network socket used for all network communication + + unsigned int ip; // the ip this socket is bound to + unsigned short port; // the port this socket is bound to + + HashTable connections; // the connections that are using this socket + DArray closedConnections; // connections that are closed no longer get a spot + // in the hash table + + GT2Bool close; // if true, a close was attempted inside a callback, and it + // should be closed as soon as possible + GT2Bool error; // if true, there was a socket error using this socket + + int callbackLevel; // if >0, then we're inside a callback (or recursive + // callbacks) + gt2ConnectAttemptCallback + connectAttemptCallback; // if set, callback used to handle incoming + // connection attempts + gt2SocketErrorCallback + socketErrorCallback; // if set, call this in case of an error + gt2DumpCallback + sendDumpCallback; // if set, gets called for every datagram sent + gt2DumpCallback receiveDumpCallback; // if set, gets called for every datagram + // and connection reset received + gt2UnrecognizedMessageCallback + unrecognizedMessageCallback; // if set, gets called for all unrecognized + // messages + + void* data; // user data + + int outgoingBufferSize; // per-connection buffer sizes + int incomingBufferSize; + + GTI2ProtocolType + protocolType; // set to UDP or VDP protocol depending on the call to + // create socket also used as an offset for VDP sockets + int protocolOffset; + GT2Bool broadcastEnabled; // set to true if the socket has already been + // broadcast enabled +} GTI2Socket; + +typedef struct GTI2Connection { + // ip and port uniquely identify this connection on this socket + unsigned int + ip; // the ip on the other side of this connection (network byte order) + unsigned short + port; // the port on the other side of this connection (host byte order) + + GTI2Socket* socket; // the parent socket + + GTI2ConnectionState state; // connection state + + GT2Bool initiated; // if true, the local side of the connection initiated the + // connection (client) + + GT2Bool freeAtAcceptReject; // if true, don't free the connection until + // accept/reject is called + + GT2Result connectionResult; // the result of the connect attempt + + gsi_time startTime; // the time the connection was created + gsi_time timeout; // the timeout value passed into gt2Connect + + int callbackLevel; // if >0, then we're inside a callback (or recursive + // callbacks) + GT2ConnectionCallbacks callbacks; // connection callbacks + + char* initialMessage; // this is the initial message for the client + int initialMessageLen; // the initial message length + + void* data; // user data + + GTI2Buffer incomingBuffer; // buffer for incoming data + GTI2Buffer outgoingBuffer; // buffer for outgoing data + DArray incomingBufferMessages; // identifies incoming messages stored in the + // buffer + DArray outgoingBufferMessages; // identifies outgoing messages stored in the + // buffer + + unsigned short + serialNumber; // serial number of the next message to be sent out + unsigned short expectedSerialNumber; // the next serial number we're expecting + // from the remote side + + char response[GTI2_RESPONSE_LEN]; // after the challenge is sent during + // negotiation, this is the response we're + // expecting + + gsi_time lastSend; // the last time something was sent on this connection + gsi_time challengeTime; // the time the challenge was sent + + GT2Bool pendingAck; // if true, there is an ack waiting to go out, either on + // its own or as part of a reliable message + + gsi_time pendingAckTime; // the time at which the pending ack was first set + + DArray sendFilters; // filters that apply to outgoing data + DArray receiveFilters; // filters that apply to incoming data + +} GTI2Connection; + +// store last 32 ip's in a ring buffer +#define MAC_TABLE_SIZE 32 // must be power of 2 +typedef struct { + gsi_u32 ip; + char mac[6]; +} GTI2MacEntry; diff --git a/source/gamespy/gt2/gt2Message.h b/source/gamespy/gt2/gt2Message.h new file mode 100644 index 000000000..033672abb --- /dev/null +++ b/source/gamespy/gt2/gt2Message.h @@ -0,0 +1,54 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "gt2Main.h" + +GT2Bool gti2ReceiveMessages(GT2Socket socket); + +GT2Bool gti2Send(GT2Connection connection, const GT2Byte* message, int len, + GT2Bool reliable); + +GT2Bool gti2SendAppReliable(GT2Connection connection, const GT2Byte* message, + int len); +GT2Bool gti2SendAppUnreliable(GT2Connection connection, const GT2Byte* message, + int len); +GT2Bool gti2SendClientChallenge(GT2Connection connection, + const char challenge[GTI2_CHALLENGE_LEN]); +GT2Bool gti2SendServerChallenge(GT2Connection connection, + const char response[GTI2_RESPONSE_LEN], + const char challenge[GTI2_CHALLENGE_LEN]); +GT2Bool gti2SendClientResponse(GT2Connection connection, + const char response[GTI2_RESPONSE_LEN], + const char* message, int len); +GT2Bool gti2SendAccept(GT2Connection connection); +GT2Bool gti2SendReject(GT2Connection connection, const GT2Byte* message, + int len); +GT2Bool gti2SendClose(GT2Connection connection); +GT2Bool gti2SendKeepAlive(GT2Connection connection); +GT2Bool gti2SendAck(GT2Connection connection); +GT2Bool gti2SendNack(GT2Connection connection, unsigned short SNMin, + unsigned short SNMax); +GT2Bool gti2SendPing(GT2Connection connection); +GT2Bool gti2SendPong(GT2Connection connection, GT2Byte* message, int len); +GT2Bool gti2SendClosed(GT2Connection connection); +GT2Bool gti2SendClosedOnSocket(GT2Socket socket, unsigned int ip, + unsigned short port); + +GT2Bool gti2ResendMessage(GT2Connection connection, + GTI2OutgoingBufferMessage* message); + +GT2Bool gti2HandleConnectionReset(GT2Socket socket, unsigned int ip, + unsigned short port); +GT2Bool gti2HandleHostUnreachable(GT2Socket socket, unsigned int ip, + unsigned short port, GT2Bool send); +GT2Bool gti2WasMessageIDConfirmed(const GT2Connection connection, + GT2MessageID messageID); diff --git a/source/gamespy/gt2/gt2Socket.c b/source/gamespy/gt2/gt2Socket.c new file mode 100644 index 000000000..52f50125a --- /dev/null +++ b/source/gamespy/gt2/gt2Socket.c @@ -0,0 +1,411 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Socket.h" +#include "gt2Buffer.h" +#include "gt2Callback.h" +#include "gt2Connection.h" +#include "gt2Message.h" +#include "gt2Utility.h" +#include + +#define GTI2_DEFAULT_INCOMING_BUFFER_SIZE (64 * 1024) +#define GTI2_DEFAULT_OUTGOING_BUFFER_SIZE (64 * 1024) + +#define STARTING_SERIAL_NUMBER 0 + +static int gti2ConnectionHash(const void* elem, int numBuckets) { + GT2Connection connection = *(GT2Connection*)elem; + + return (int)(((connection->ip * connection->port)) % numBuckets); +} + +static int GS_STATIC_CALLBACK gti2ConnectionCompare(const void* elem1, + const void* elem2) { + GT2Connection connection1 = *(GT2Connection*)elem1; + GT2Connection connection2 = *(GT2Connection*)elem2; + + if (connection1->ip != connection2->ip) + return (int)(connection1->ip - connection2->ip); + + return (int)(short)(connection1->port - connection2->port); +} + +static void gti2ConnectionFree(void* elem) { + gti2ConnectionCleanup(*(GT2Connection*)elem); +} + +GT2Connection gti2SocketFindConnection(GT2Socket socket, unsigned int ip, + unsigned short port) { + GTI2Connection connection; + GT2Connection connectionPtr; + GT2Connection* connectionPtrPtr; + + connection.ip = ip; + connection.port = port; + + connectionPtr = &connection; + connectionPtrPtr = + (GT2Connection*)TableLookup(socket->connections, &connectionPtr); + if (connectionPtrPtr) + return *connectionPtrPtr; + + return NULL; +} + +GT2Result gti2CreateSocket(GT2Socket* sock, const char* localAddress, + int outgoingBufferSize, int incomingBufferSize, + gt2SocketErrorCallback callback, + GTI2ProtocolType type) { + SOCKADDR_IN address; + GT2Socket socketTemp; + int rcode; + unsigned int ip; + unsigned short port; + int len; + + // startup the sockets engine if needed + SocketStartUp(); + + // check for using defaults + if (!incomingBufferSize) + incomingBufferSize = GTI2_DEFAULT_INCOMING_BUFFER_SIZE; + if (!outgoingBufferSize) + outgoingBufferSize = GTI2_DEFAULT_OUTGOING_BUFFER_SIZE; + + // convert the address to an IP and port + if (!gt2StringToAddress(localAddress, &ip, &port)) + return GT2AddressError; + + // allocate the object + socketTemp = (GT2Socket)gsimalloc(sizeof(GTI2Socket)); + if (!socketTemp) + return GT2OutOfMemory; + + // set initial values + memset(socketTemp, 0, sizeof(GTI2Socket)); + socketTemp->socket = INVALID_SOCKET; + socketTemp->incomingBufferSize = incomingBufferSize; + socketTemp->outgoingBufferSize = outgoingBufferSize; + socketTemp->socketErrorCallback = callback; + + // create the connections table + socketTemp->connections = + TableNew2(sizeof(GT2Connection), 32, 2, gti2ConnectionHash, + gti2ConnectionCompare, NULL); + if (!socketTemp->connections) { + gsifree(socketTemp); + return GT2OutOfMemory; + } + + // create the closed connections list + socketTemp->closedConnections = + ArrayNew(sizeof(GT2Connection), 4, gti2ConnectionFree); + if (!socketTemp->closedConnections) { + TableFree(socketTemp->connections); + gsifree(socketTemp); + return GT2OutOfMemory; + } + + // create the socket + socketTemp->socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + socketTemp->protocolType = type; + + if (type == GTI2AdHocProtocol) + socketTemp->protocolOffset = 0; + else + socketTemp->protocolOffset = type; + + if (socketTemp->socket == INVALID_SOCKET) { + TableFree(socketTemp->connections); + ArrayFree(socketTemp->closedConnections); + gsifree(socketTemp); + return GT2NetworkError; + } + + // bind it + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = htons(port); + if (type != GTI2AdHocProtocol) { + rcode = bind(socketTemp->socket, (SOCKADDR*)&address, sizeof(SOCKADDR_IN)); + if (gsiSocketIsError(rcode)) { + closesocket(socketTemp->socket); + TableFree(socketTemp->connections); + ArrayFree(socketTemp->closedConnections); + gsifree(socketTemp); + return GT2NetworkError; + } + } + + len = sizeof(SOCKADDR_IN); + getsockname(socketTemp->socket, (SOCKADDR*)&address, &len); + socketTemp->ip = address.sin_addr.s_addr; + socketTemp->port = ntohs(address.sin_port); + + *sock = socketTemp; + + return GT2Success; +} + +void gti2CloseSocket(GT2Socket socket) { + + if (socket->callbackLevel) { + socket->close = GT2True; + return; + } + + closesocket(socket->socket); + + TableFree(socket->connections); + ArrayFree(socket->closedConnections); + gsifree(socket); + + SocketShutDown(); +} + +void gti2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback) { + socket->connectAttemptCallback = callback; +} + +static GT2Connection gti2CreateConnectionObject(void) { + // mj todo: give options of allocating this from a static pool for games with + // known number of connections. + return (GT2Connection)gsimalloc(sizeof(GTI2Connection)); +} + +GT2Result gti2NewSocketConnection(GT2Socket socket, GT2Connection* connection, + unsigned int ip, unsigned short port) { + GT2Connection connectionPtr = NULL; + + // check if this ip and port already exists + if (gti2SocketFindConnection(socket, ip, port)) + return GT2DuplicateAddress; + + // allocate a connection object + connectionPtr = gti2CreateConnectionObject(); + if (!connectionPtr) + goto out_of_memory; + + // set some basics + memset(connectionPtr, 0, sizeof(GTI2Connection)); + connectionPtr->ip = ip; + connectionPtr->port = port; + connectionPtr->socket = socket; + connectionPtr->startTime = current_time(); + connectionPtr->lastSend = connectionPtr->startTime; + connectionPtr->serialNumber = STARTING_SERIAL_NUMBER; + connectionPtr->expectedSerialNumber = STARTING_SERIAL_NUMBER; + + // allocate the buffers + if (!gti2AllocateBuffer(&connectionPtr->incomingBuffer, + socket->incomingBufferSize)) + goto out_of_memory; + if (!gti2AllocateBuffer(&connectionPtr->outgoingBuffer, + socket->outgoingBufferSize)) + goto out_of_memory; + + // allocate the message arrays + connectionPtr->incomingBufferMessages = + ArrayNew(sizeof(GTI2IncomingBufferMessage), 64, NULL); + if (!connectionPtr->incomingBufferMessages) + goto out_of_memory; + connectionPtr->outgoingBufferMessages = + ArrayNew(sizeof(GTI2OutgoingBufferMessage), 64, NULL); + if (!connectionPtr->outgoingBufferMessages) + goto out_of_memory; + + // allocate the filter arrays + connectionPtr->sendFilters = ArrayNew(sizeof(gt2SendFilterCallback), 2, NULL); + if (!connectionPtr->sendFilters) + goto out_of_memory; + connectionPtr->receiveFilters = + ArrayNew(sizeof(gt2ReceiveFilterCallback), 2, NULL); + if (!connectionPtr->receiveFilters) + goto out_of_memory; + + // add it to the table + TableEnter(socket->connections, &connectionPtr); + + // check that it's in the table (and get the address) + *connection = gti2SocketFindConnection(socket, ip, port); + if (!*connection) + goto out_of_memory; + + return GT2Success; + +out_of_memory: + + // there wasn't enough memory, free everything and return the error + if (connectionPtr) { + gsifree(connectionPtr->incomingBuffer.buffer); + gsifree(connectionPtr->outgoingBuffer.buffer); + if (connectionPtr->incomingBufferMessages) + ArrayFree(connectionPtr->incomingBufferMessages); + if (connectionPtr->outgoingBufferMessages) + ArrayFree(connectionPtr->outgoingBufferMessages); + if (connectionPtr->sendFilters) + ArrayFree(connectionPtr->sendFilters); + if (connectionPtr->receiveFilters) + ArrayFree(connectionPtr->receiveFilters); + gsifree(connectionPtr); + } + + return GT2OutOfMemory; +} + +void gti2FreeSocketConnection(GT2Connection connection) { + // check if we can actually free it + if (connection->freeAtAcceptReject || connection->callbackLevel) + return; + + // remove it from the correct list depending on the connect state + if (connection->state == GTI2Closed) { + int len; + int i; + + len = ArrayLength(connection->socket->closedConnections); + for (i = 0; i < len; i++) { + if (connection == + *(GT2Connection*)ArrayNth(connection->socket->closedConnections, i)) { + ArrayDeleteAt(connection->socket->closedConnections, i); + return; + } + } + } else { + TableRemove(connection->socket->connections, &connection); + } +} + +GT2Bool gti2SocketSend(GT2Socket socket, unsigned int ip, unsigned short port, + const GT2Byte* message, int len) { + SOCKADDR_IN address; + int rcode; + +#ifdef GTI2_DROP_SEND_RATE + // drop some percentage of packets and see what happens + if ((rand() % 100) < GTI2_DROP_SEND_RATE) + return GT2True; +#endif + + // check the message and len + gti2MessageCheck(&message, &len); + + if (socket->protocolType != GTI2AdHocProtocol) { + // check if we can't send + if (!CanSendOnSocket(socket->socket)) + return GT2True; + } + + // fill the address structure + memset(&address, 0, sizeof(SOCKADDR_IN)); + address.sin_family = AF_INET; + address.sin_addr.s_addr = ip; + address.sin_port = htons(port); + rcode = sendto(socket->socket, (const char*)message, len, 0, + (SOCKADDR*)&address, sizeof(SOCKADDR_IN)); + if (gsiSocketIsError(rcode)) { + rcode = GOAGetLastError(socket->socket); + if (rcode == WSAECONNRESET) { + // handle the reset + if (!gti2HandleConnectionReset(socket, ip, port)) + return GT2False; + } +#ifndef SN_SYSTEMS + else if (rcode == WSAEHOSTUNREACH) { + if (!gti2HandleHostUnreachable(socket, ip, port, GT2True)) + return GT2False; + } +#endif + // some systems might return these errors + else if ((rcode == WSAENOBUFS) || (rcode == WSAEWOULDBLOCK)) { + return GT2True; + } + // for systems that don't support WSAEHOSTDOWN (EHOSTDOWN) + else if ((rcode != WSAEMSGSIZE)) { + // fatal socket error + gti2SocketError(socket); + return GT2False; + } + } else { + // let the dump handle this + if (socket->sendDumpCallback) { + if (!gti2DumpCallback(socket, gti2SocketFindConnection(socket, ip, port), + ip, port, GT2False, message, len, GT2True)) + return GT2False; + } + } + + return GT2True; +} + +static int gti2SocketConnectionsThinkMap(void* elem, void* clientData) { + GT2Connection connection = *(GT2Connection*)elem; + gsi_time now = *(gsi_time*)clientData; + + // only think if we're not closed + if (connection->state != GTI2Closed) { + // think + if (!gti2ConnectionThink(connection, now)) + return 0; + } + + // check for a closed connection + if ((connection->state == GTI2Closed) && !connection->freeAtAcceptReject && + !connection->callbackLevel) + gti2FreeSocketConnection(connection); + + return 1; +} + +GT2Bool gti2SocketConnectionsThink(GT2Socket socket) { + gsi_time now; + + // get the current time + now = current_time(); + + // go through the list of connections and let them think + if (TableMapSafe2(socket->connections, gti2SocketConnectionsThinkMap, &now)) + return GT2False; + + return GT2True; +} + +void gti2FreeClosedConnections(GT2Socket socket) { + int i; + int len; + + // loop through the closed connections, attempting to free them all + len = ArrayLength(socket->closedConnections); + for (i = (len - 1); i >= 0; i--) + gti2FreeSocketConnection( + *(GT2Connection*)ArrayNth(socket->closedConnections, i)); +} + +void gti2SocketError(GT2Socket socket) { + // if there was already an error, don't go through this again + if (socket->error) + return; + + // flag the error + socket->error = GT2True; + + // first close all the socket's connections + gt2CloseAllConnectionsHard(socket); + + // call the error callback + if (!gti2SocketErrorCallback(socket)) + return; + + // close the socket + gti2CloseSocket(socket); +} diff --git a/source/gamespy/gt2/gt2Socket.h b/source/gamespy/gt2/gt2Socket.h new file mode 100644 index 000000000..208290785 --- /dev/null +++ b/source/gamespy/gt2/gt2Socket.h @@ -0,0 +1,40 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "gt2Main.h" + +GT2Result gti2CreateSocket(GT2Socket* socket, const char* localAddress, + int outgoingBufferSize, int incomingBufferSize, + gt2SocketErrorCallback callback, + GTI2ProtocolType type); + +void gti2CloseSocket(GT2Socket socket); + +void gti2Listen(GT2Socket socket, gt2ConnectAttemptCallback callback); + +GT2Result gti2NewSocketConnection(GT2Socket socket, GT2Connection* connection, + unsigned int ip, unsigned short port); +void gti2FreeSocketConnection(GT2Connection connection); + +GT2Connection gti2SocketFindConnection(GT2Socket socket, unsigned int ip, + unsigned short port); + +// ip is network byte order, port is host byte order +// returns false if there was a fatal error +GT2Bool gti2SocketSend(GT2Socket socket, unsigned int ip, unsigned short port, + const GT2Byte* message, int len); + +GT2Bool gti2SocketConnectionsThink(GT2Socket socket); + +void gti2FreeClosedConnections(GT2Socket socket); + +void gti2SocketError(GT2Socket socket); diff --git a/source/gamespy/gt2/gt2Utility.c b/source/gamespy/gt2/gt2Utility.c new file mode 100644 index 000000000..12bfe3741 --- /dev/null +++ b/source/gamespy/gt2/gt2Utility.c @@ -0,0 +1,278 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#include "gt2Utility.h" +#include "gt2Main.h" +#include +#include + +#define GTI2_STACK_HOSTLEN_MAX 256 + +/************************* +** BYTE ORDER FUNCTIONS ** +*************************/ + +unsigned int gt2NetworkToHostInt(unsigned int i) { + return (unsigned int)ntohl(i); +} + +unsigned int gt2HostToNetworkInt(unsigned int i) { + return (unsigned int)htonl(i); +} + +unsigned short gt2NetworkToHostShort(unsigned short s) { return ntohs(s); } + +unsigned short gt2HostToNetworkShort(unsigned short s) { return htons(s); } + +/******************************* +** INTERNET ADDRESS FUNCTIONS ** +*******************************/ + +const char* gt2AddressToString(unsigned int ip, unsigned short port, + char string[22]) { + static char strAddressArray[2][22]; + static int nIndex; + char* strAddress; + + if (string) + strAddress = string; + else { + nIndex ^= 1; + strAddress = strAddressArray[nIndex]; + } + + if (ip) { + IN_ADDR inAddr; + + inAddr.s_addr = ip; + + if (port) + sprintf(strAddress, "%s:%d", inet_ntoa(inAddr), port); + else + sprintf(strAddress, "%s", inet_ntoa(inAddr)); + } else if (port) + sprintf(strAddress, ":%d", port); + else + strAddress[0] = '\0'; + + return strAddress; +} + +GT2Bool gt2StringToAddress(const char* string, unsigned int* ip, + unsigned short* port) { + unsigned int srcIP = 0; // avoid use uninit condition + unsigned short srcPort = 0; + + if (!string || !string[0]) { + srcIP = 0; + srcPort = 0; + } else { + char stackHost[GTI2_STACK_HOSTLEN_MAX + 1]; + const char* colon; + const char* host; + + // Is there a port? + colon = strchr(string, ':'); + if (!colon) { + // The string is the host. + host = string; + + // No port. + srcPort = 0; + } else { + int len; + const char* check; + int temp; + + // Is it just a port? + if (colon == string) { + host = NULL; + srcIP = 0; + } else { + // Copy the host portion into the array on the stack. + len = (colon - string); + assert(len < GTI2_STACK_HOSTLEN_MAX); + memcpy(stackHost, string, (unsigned int)len); + stackHost[len] = '\0'; + host = stackHost; + } + + // Check the port. + for (check = (colon + 1); *check; check++) + if (!isdigit(*check)) + return GT2False; + + // Get the port. + temp = atoi(colon + 1); + if ((temp < 0) || (temp > 0xFFFF)) + return GT2False; + srcPort = (unsigned short)temp; + } + + // Is there a host? + if (host) { + // Try dotted IP. + ///////////////// + srcIP = inet_addr(host); + if (srcIP == INADDR_NONE) { + HOSTENT* hostent; + + hostent = gethostbyname(host); + if (hostent == NULL) + return GT2False; + + srcIP = *(unsigned int*)hostent->h_addr_list[0]; + } + } + } + + if (ip) + *ip = srcIP; + if (port) + *port = srcPort; + + return GT2True; +} + +static const char* gti2HandleHostInfo(HOSTENT* host, char*** aliases, + unsigned int*** ips) { + if (!host || (host->h_addrtype != AF_INET) || (host->h_length != 4)) + return NULL; + + if (aliases) + *aliases = host->h_aliases; + if (ips) + *ips = (unsigned int**)host->h_addr_list; + + return host->h_name; +} + +const char* gt2IPToHostInfo(unsigned int ip, char*** aliases, + unsigned int*** ips) { + HOSTENT* host; + + host = gethostbyaddr((const char*)&ip, 4, AF_INET); + + GSI_UNUSED(ip); + return gti2HandleHostInfo(host, aliases, ips); +} + +const char* gt2StringToHostInfo(const char* string, char*** aliases, + unsigned int*** ips) { + HOSTENT* host; + unsigned int ip; + + if (!string || !string[0]) + return NULL; + + // Is the string actually a dotted IP? + ip = inet_addr(string); + if (ip != INADDR_NONE) + return gt2IPToHostInfo(ip, aliases, ips); + + host = gethostbyname(string); + + return gti2HandleHostInfo(host, aliases, ips); +} + +const char* gt2IPToHostname(unsigned int ip) { + return gt2IPToHostInfo(ip, NULL, NULL); +} + +const char* gt2StringToHostname(const char* string) { + return gt2StringToHostInfo(string, NULL, NULL); +} + +char** gt2IPToAliases(unsigned int ip) { + char** aliases; + + if (!gt2IPToHostInfo(ip, &aliases, NULL)) + return NULL; + + return aliases; +} + +char** gt2StringToAliases(const char* string) { + char** aliases; + + if (!gt2StringToHostInfo(string, &aliases, NULL)) + return NULL; + + return aliases; +} + +unsigned int** gt2IPToIPs(unsigned int ip) { + unsigned int** ips; + + if (!gt2IPToHostInfo(ip, NULL, &ips)) + return NULL; + + return ips; +} + +unsigned int** gt2StringToIPs(const char* string) { + unsigned int** ips; + + if (!gt2StringToHostInfo(string, NULL, &ips)) + return NULL; + + return ips; +} + +/*********************** +** INTERNAL FUNCTIONS ** +***********************/ + +#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped +void gti2MessageCheck(const GT2Byte** message, int* len); +#endif + +// Used from gt2main.c +void gti2MessageCheck(const GT2Byte** message, int* len) { + // check for an empty message + if (!*message) { + *message = (const GT2Byte*)""; + *len = 0; + } + // check for calculating the message length + else if (*len == -1) { + *len = (int)(strlen((const char*)*message) + 1); + } +} + +#ifdef RECV_LOG +void gti2LogMessage(unsigned int fromIP, unsigned short fromPort, + unsigned int toIP, unsigned short toPort, + const GT2Byte* message, int len) { + FILE* file; + IN_ADDR ip; + int i; + + file = fopen("recv.log", "at"); + if (!file) + return; + + // from + ip.s_addr = fromIP; + fprintf(file, "%s:%d -> ", inet_ntoa(ip), fromPort); + + // to + ip.s_addr = toIP; + fprintf(file, "%s:%d\n", inet_ntoa(ip), toPort); + + // data + fprintf(file, "%d: ", len); + for (i = 0; i < len; i++) + fprintf(file, "%02X ", message[i]); + fprintf(file, "\n\n"); + + fclose(file); +} +#endif diff --git a/source/gamespy/gt2/gt2Utility.h b/source/gamespy/gt2/gt2Utility.h new file mode 100644 index 000000000..8e75da148 --- /dev/null +++ b/source/gamespy/gt2/gt2Utility.h @@ -0,0 +1,21 @@ +/* +GameSpy GT2 SDK +Dan "Mr. Pants" Schoenblum +dan@gamespy.com + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com +*/ + +#pragma once + +#include "gt2Main.h" + +void gti2MessageCheck(const GT2Byte** message, int* len); + +#ifdef RECV_LOG +void gti2LogMessage(unsigned int fromIP, unsigned short fromPort, + unsigned int toIP, unsigned short toPort, + const GT2Byte* message, int len); +#endif diff --git a/source/gamespy/hashtable.c b/source/gamespy/hashtable.c new file mode 100644 index 000000000..5d4020b53 --- /dev/null +++ b/source/gamespy/hashtable.c @@ -0,0 +1,202 @@ +/* + * + * File: hashtable.c + * --------------- + * David Wright + * 10/8/98 + * + * See hashtable.h for function comments + * Implmentation is straight-forward, using a fixed dynamically allocated + * array for the buckets, and a DArray for each individual bucket + */ + +#include "hashtable.h" +#include "darray.h" +#include +#include + +#include "nonport.h" + +struct HashImplementation { + DArray* buckets; + int nbuckets; + TableElementFreeFn freefn; + TableHashFn hashfn; + TableCompareFn compfn; +}; + +HashTable TableNew(int elemSize, int nBuckets, TableHashFn hashFn, + TableCompareFn compFn, TableElementFreeFn freeFn) { + return TableNew2(elemSize, nBuckets, 4, hashFn, compFn, freeFn); +} +HashTable TableNew2(int elemSize, int nBuckets, int nChains, TableHashFn hashFn, + TableCompareFn compFn, TableElementFreeFn freeFn) { + HashTable table; + int i; + + assert(hashFn); + assert(compFn); + assert(elemSize); + assert(nBuckets); + + table = (HashTable)gsimalloc(sizeof(struct HashImplementation)); + assert(table); + + table->buckets = (DArray*)gsimalloc(nBuckets * sizeof(DArray)); + assert(table->buckets); + for (i = 0; i < nBuckets; i++) // ArrayNew will assert if allocation fails + table->buckets[i] = ArrayNew(elemSize, nChains, freeFn); + table->nbuckets = nBuckets; + table->freefn = freeFn; + table->compfn = compFn; + table->hashfn = hashFn; + + return table; +} + +void TableFree(HashTable table) { + int i; + + assert(table); + + if (NULL == table) + return; + + for (i = 0; i < table->nbuckets; i++) + ArrayFree(table->buckets[i]); + gsifree(table->buckets); + gsifree(table); +} + +int TableCount(HashTable table) { + int i, count = 0; + + assert(table); + + if (NULL == table) + return count; + + for (i = 0; i < table->nbuckets; i++) + count += ArrayLength(table->buckets[i]); + + return count; +} + +void TableEnter(HashTable table, const void* newElem) { + int hash, itempos; + + assert(table); + + if (NULL == table) + return; + + hash = table->hashfn(newElem, table->nbuckets); + itempos = ArraySearch(table->buckets[hash], newElem, table->compfn, 0, 0); + if (itempos == NOT_FOUND) + ArrayAppend(table->buckets[hash], newElem); + else + ArrayReplaceAt(table->buckets[hash], newElem, itempos); +} + +int TableRemove(HashTable table, const void* delElem) { + int hash, itempos; + + assert(table); + + if (NULL == table) + return 0; + + hash = table->hashfn(delElem, table->nbuckets); + itempos = ArraySearch(table->buckets[hash], delElem, table->compfn, 0, 0); + if (itempos == NOT_FOUND) + return 0; + else + ArrayDeleteAt(table->buckets[hash], itempos); + return 1; +} + +void* TableLookup(HashTable table, const void* elemKey) { + int hash, itempos; + + assert(table); + + if (NULL == table) + return NULL; + + hash = table->hashfn(elemKey, table->nbuckets); + itempos = ArraySearch(table->buckets[hash], elemKey, table->compfn, 0, 0); + if (itempos == NOT_FOUND) + return NULL; + else + return ArrayNth(table->buckets[hash], itempos); +} + +/* +void TableMap(HashTable table, TableMapFn fn, void *clientData) +{ + int i; + + assert(table); + assert(fn); + + if (NULL == table || NULL == fn) + return; + + for (i = 0 ; i < table->nbuckets ; i++) + ArrayMap(table->buckets[i], fn, clientData); + +} +*/ + +void TableMapSafe(HashTable table, TableMapFn fn, void* clientData) { + int i; + + assert(fn); + + for (i = 0; i < table->nbuckets; i++) + ArrayMapBackwards(table->buckets[i], fn, clientData); +} + +/* +void * TableMap2(HashTable table, TableMapFn2 fn, void *clientData) +{ + int i; + void * pcurr; + + assert(fn); + + for (i = 0 ; i < table->nbuckets ; i++) + { + pcurr = ArrayMap2(table->buckets[i], fn, clientData); + if(pcurr) + return pcurr; + } + + return NULL; +} +*/ + +void* TableMapSafe2(HashTable table, TableMapFn2 fn, void* clientData) { + int i; + void* pcurr; + + assert(fn); + + for (i = 0; i < table->nbuckets; i++) { + pcurr = ArrayMapBackwards2(table->buckets[i], fn, clientData); + if (pcurr) + return pcurr; + } + + return NULL; +} + +/* +void TableClear(HashTable table) +{ + int i; + + for (i = 0 ; i < table->nbuckets ; i++) + ArrayClear(table->buckets[i]); +} +*/ diff --git a/source/gamespy/hashtable.h b/source/gamespy/hashtable.h new file mode 100644 index 000000000..29983a400 --- /dev/null +++ b/source/gamespy/hashtable.h @@ -0,0 +1,211 @@ +#pragma once + +/* File: hashtable.h + * ------------------ + * Defines the interface for the HashTable ADT. + * The HashTable allows the client to store any number of elements of any + * type in a hash table for fast storage and retrieval. The client specifies + * the size (in bytes) of the elements that will be stored in the table when + * it is created. Thereafter the client and the HashTable refer to elements + * via (void*) ptrs. The HashTable imposes no upper bound on the number of + * elements and deals with all its own memory management. + * + * The client-supplied information (in the form of the number of buckets + * to use and the hashing function to be applied to each element) is employed + * to divide elements in buckets with hopefully only few collisions, resulting + * in Enter & Lookup performance in constant-time. The HashTable also supports + * iterating over all elements by use of mapping function. + */ + +/* Type: HashTable + * ---------------- + * Defines the HashTable type itself. The client can declare variables of type + * HashTable, but these variables must be initialized with the result of + * TableNew. The HashTable is implemented with pointers, so all client + * copies in variables or parameters will be "shallow" -- they will all + * actually point to the same HashTable structure. Only calls to TableNew + * create new tables. The struct declaration below is "incomplete"- the + * implementation details are literally not visible in the client .h file. + */ +typedef struct HashImplementation* HashTable; + +/* TableHashFn + * ----------- + * TableHashFn is a pointer to a client-supplied function which the + * HashTable uses to hash elements. The hash function takes a (const void*) + * pointer to an element and the number of buckets and returns an int, + * which represents the hash code for this element. The returned hash code + * should be within the range 0 to numBuckets-1 and should be stable (i.e. + * an element's hash code should not change over time). + * For best performance, the hash function should be designed to + * uniformly distribute elements over the available number of buckets. + */ +typedef int (*TableHashFn)(const void* elem, int numBuckets); + +/* TableCompareFn + * -------------- + * TableCompareFn is a pointer to a client-supplied function which the + * HashTable uses to compare elements. The comparator takes two + * (const void*) pointers (these will point to elements) and returns an int. + * The comparator should indicate the ordering of the two elements + * using the same convention as the strcmp library function: + * If elem1 is "less than" elem2, return a negative number. + * If elem1 is "greater than" elem2, return a positive number. + * If the two elements are "equal", return 0. + */ +typedef int (*TableCompareFn)(const void* elem1, const void* elem2); + +/* TableMapFn + * ---------- + * TableMapFn defines the space of functions that can be used to map over + * the elements in a HashTable. A map function is called with a pointer to + * the element and a client data pointer passed in from the original caller. + */ +typedef void (*TableMapFn)(void* elem, void* clientData); + +/* TableMapFn2 + * ---------- + * Same as TableMapFn, but can return 0 to stop the mapping. + * Used by TableMap2. + */ +typedef int (*TableMapFn2)(void* elem, void* clientData); + +/* TableElementFreeFn + * ------------------ + * TableElementFreeFn defines the space of functions that can be used as the + * clean-up function for each element as it is deleted from the array + * or when the entire array of elements is freed. The cleanup function is + * called with a pointer to an element about to be deleted. + */ +typedef void (*TableElementFreeFn)(void* elem); + +#ifdef __cplusplus +extern "C" { +#endif + +/* TableNew + * -------- + * Creates a new HashTable with no entries and returns it. The elemSize + * parameter specifies the number of bytes that a single element of the + * table should take up. For example, if you want to store elements of type + * Binky, you would pass sizeof(Binky) as this parameter. An assert is + * raised if this size is not greater than 0. + * + * The nBuckets parameter specifies the number of buckets that the elements + * will be partitioned into. Once a HashTable is created, this number does + * not change. The nBuckets parameter must be in synch with the behavior of + * the hashFn, which must return a hash code between 0 and nBuckets-1. + * The hashFn parameter specifies the function that is called to retrieve the + * hash code for a given element. See the type declaration of TableHashFn + * above for more information. An assert is raised if nBuckets is not + * greater than 0. + * + * The compFn is used for testing equality between elements. See the + * type declaration for TableCompareFn above for more information. + * + * The elemFreeFn is the function that will be called on an element that is + * about to be overwritten (by a new entry in TableEnter) or on each element + * in the table when the entire table is being freed (using TableFree). This + * function is your chance to do any deallocation/cleanup required, + * (such as freeing any pointers contained in the element). The client can pass + * NULL for the cleanupFn if the elements don't require any handling on free. + * An assert is raised if either the hash or compare functions are NULL. + * + * nChains is the number of chains to allocate initially in each bucket + * + */ + +HashTable TableNew(int elemSize, int nBuckets, TableHashFn hashFn, + TableCompareFn compFn, TableElementFreeFn freeFn); + +HashTable TableNew2(int elemSize, int nBuckets, int nChains, TableHashFn hashFn, + TableCompareFn compFn, TableElementFreeFn freeFn); + +/* TableFree + * ---------- + * Frees up all the memory for the table and its elements. It DOES NOT + * automatically free memory owned by pointers embedded in the elements. This + * would require knowledge of the structure of the elements which the HashTable + * does not have. However, it will iterate over the elements calling + * the elementFreeFn earlier supplied to TableNew and therefore, the client, + * who knows what the elements are,can do the appropriate deallocation of any + * embedded pointers through that function. + * After calling this, the value of what table points to is undefined. + */ +void TableFree(HashTable table); + +/* TableCount + * ---------- + * Returns the number of elements currently in the table. + */ +int TableCount(HashTable table); + +/* TableEnter + * ---------- + * Enters a new element into the table. Uses the hash function to determine + * which bucket to place the new element. Its contents are copied from the + * memory pointed to by newElem. If there is already an element in the table + * which is determined to be equal (using the comparison function) this will + * use the contents of the new element to replace the previous element, + * calling the free function on the replaced element. + */ +void TableEnter(HashTable table, const void* newElem); + +/* TableRemove + * ---------- + * Remove a element frin the table. If the element does not exist + * the function returns 0. If it exists, it returns 1 and calls the + * free function on the removed element. + */ +int TableRemove(HashTable table, const void* delElem); + +/* TableLookup + * ---------- + * Returns a pointer to the table element which matches the elemKey parameter + * (equality is determined by the comparison function). If there is no + * matching element, returns NULL. Calling this function does not + * re-arrange or change contents of the table or modify elemKey in any way. + */ +void* TableLookup(HashTable table, const void* elemKey); + +/* TableMap + * ----------- + * Iterates through each element in the table (in any order) and calls the + * function fn for that element. The function is called with the address of + * the table element and the clientData pointer. The clientData value allows + * the client to pass extra state information to the client-supplied function, + * if necessary. If no client data is required, this argument should be NULL. + * An assert is raised if the map function is NULL. + */ +void TableMap(HashTable table, TableMapFn fn, void* clientData); + +/* TableMapSafe + * ----------- + * Same as TableMap, but allows elements to be freed during the mapping. + */ +void TableMapSafe(HashTable table, TableMapFn fn, void* clientData); + +/* TableMap2 + * ----------- + * Same as TableMap, but allows the mapping to be stopped by returning 0 + * from the mapping function. If the mapping was stopped, the element + * it was stopped at will be returned. If it wasn't stopped, then NULL + * will be returned. + */ +void* TableMap2(HashTable table, TableMapFn2 fn, void* clientData); + +/* TableMapSafe2 + * ----------- + * Same as TableMap2, but allows elements to be freed during the mapping. + */ +void* TableMapSafe2(HashTable table, TableMapFn2 fn, void* clientData); + +/* TableClear + * ----------- + * Clears all the elements in the table without freeing it + */ +void TableClear(HashTable table); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/license.txt b/source/gamespy/license.txt new file mode 100644 index 000000000..c2a0ab382 --- /dev/null +++ b/source/gamespy/license.txt @@ -0,0 +1,26 @@ +Copyright (c) 2011, IGN Entertainment, Inc. ("IGN") +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +- Neither the name of IGN nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/source/gamespy/md5.h b/source/gamespy/md5.h new file mode 100644 index 000000000..c02853a43 --- /dev/null +++ b/source/gamespy/md5.h @@ -0,0 +1,78 @@ +/* MD5.H - header file for MD5C.C + */ + +/* Copyright (C) 1991, RSA Data Security, Inc. All rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +/* GLOBAL.H - RSAREF types and constants + */ + +/* PROTOTYPES should be set to one if and only if the compiler supports + function argument prototyping. +The following makes PROTOTYPES default to 0 if it has not already + + been defined with C compiler flags. + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef PROTOTYPES +#define PROTOTYPES 1 +#endif + +/* POINTER defines a generic pointer type */ +typedef unsigned char* POINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned int UINT4; + +/* PROTO_LIST is defined depending on how PROTOTYPES is defined above. +If using PROTOTYPES, then PROTO_LIST returns the list, otherwise it + returns an empty list. + */ +#if PROTOTYPES +#define PROTO_LIST(list) list +#else +#define PROTO_LIST(list) () +#endif + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + +void MD5Init PROTO_LIST((MD5_CTX*)); +void MD5Update PROTO_LIST((MD5_CTX*, unsigned char*, unsigned int)); +void MD5Final PROTO_LIST((unsigned char[16], MD5_CTX*)); +void MD5Print PROTO_LIST((unsigned char[16], char[33])); +void MD5Digest PROTO_LIST((unsigned char*, unsigned int, char[33])); +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/md5c.c b/source/gamespy/md5c.c new file mode 100644 index 000000000..77b37bd68 --- /dev/null +++ b/source/gamespy/md5c.c @@ -0,0 +1,338 @@ +/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm + */ + +/* Copyright (C) 1991, RSA Data Security, Inc. All rights reserved. + + License to copy and use this software is granted provided that it + is identified as the "RSA Data Security, Inc. MD5 Message-Digest + Algorithm" in all material mentioning or referencing this software + or this function. + + License is also granted to make and use derivative works provided + that such works are identified as "derived from the RSA Data + Security, Inc. MD5 Message-Digest Algorithm" in all material + mentioning or referencing the derived work. + + RSA Data Security, Inc. makes no representations concerning either + the merchantability of this software or the suitability of this + software for any particular purpose. It is provided "as is" + without express or implied warranty of any kind. + + These notices must be retained in any copies of any part of this + documentation and/or software. + */ + +//#include "global.h" +#include "md5.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +static void MD5Transform PROTO_LIST((UINT4[4], unsigned char[64])); +static void Encode PROTO_LIST((unsigned char*, UINT4*, unsigned int)); +static void Decode PROTO_LIST((UINT4*, unsigned char*, unsigned int)); +static void MD5_memcpy PROTO_LIST((POINTER, POINTER, unsigned int)); +static void MD5_memset PROTO_LIST((POINTER, int, unsigned int)); + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. + Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) \ + { \ + (a) += F((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) \ + { \ + (a) += G((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) \ + { \ + (a) += H((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) \ + { \ + (a) += I((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT((a), (s)); \ + (a) += (b); \ + } + +/* MD5 initialization. Begins an MD5 operation, writing a new context. + */ +static inline void MD5Init(MD5_CTX* context) { + context->count[0] = context->count[1] = 0; + + /* Load magic initialization constants. + */ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; +} + +/* MD5 block update operation. Continues an MD5 message-digest operation, + processing another message block, and updating the context. + */ +void MD5Update(MD5_CTX* context, /* context */ + unsigned char* input, /* input block */ + unsigned int inputLen /* length of input block */ +) { + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3)) + context->count[1]++; + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible. + */ + if (inputLen >= partLen) { + MD5_memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); + MD5Transform(context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform(context->state, &input[i]); + + index = 0; + } else + i = 0; + + /* Buffer remaining input */ + MD5_memcpy((POINTER)&context->buffer[index], (POINTER)&input[i], + inputLen - i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +inline void MD5Final(unsigned char digest[16], /* message digest */ + MD5_CTX* context /* context */ +) { + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode(bits, context->count, 8); + + /* Pad out to 56 mod 64. + */ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update(context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update(context, bits, 8); + + /* Store state in digest */ + Encode(digest, context->state, 16); + + /* Zeroize sensitive information. + */ + MD5_memset((POINTER)context, 0, sizeof(*context)); +} + +/* MD5 basic transformation. Transforms state based on block. + */ +static void MD5Transform(UINT4 state[4], unsigned char block[64]) { + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode(x, block, 64); + + /* Round 1 */ + FF(a, b, c, d, x[0], S11, 0xd76aa478); /* 1 */ + FF(d, a, b, c, x[1], S12, 0xe8c7b756); /* 2 */ + FF(c, d, a, b, x[2], S13, 0x242070db); /* 3 */ + FF(b, c, d, a, x[3], S14, 0xc1bdceee); /* 4 */ + FF(a, b, c, d, x[4], S11, 0xf57c0faf); /* 5 */ + FF(d, a, b, c, x[5], S12, 0x4787c62a); /* 6 */ + FF(c, d, a, b, x[6], S13, 0xa8304613); /* 7 */ + FF(b, c, d, a, x[7], S14, 0xfd469501); /* 8 */ + FF(a, b, c, d, x[8], S11, 0x698098d8); /* 9 */ + FF(d, a, b, c, x[9], S12, 0x8b44f7af); /* 10 */ + FF(c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF(b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF(a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF(d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF(c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF(b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG(a, b, c, d, x[1], S21, 0xf61e2562); /* 17 */ + GG(d, a, b, c, x[6], S22, 0xc040b340); /* 18 */ + GG(c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG(b, c, d, a, x[0], S24, 0xe9b6c7aa); /* 20 */ + GG(a, b, c, d, x[5], S21, 0xd62f105d); /* 21 */ + GG(d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG(c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG(b, c, d, a, x[4], S24, 0xe7d3fbc8); /* 24 */ + GG(a, b, c, d, x[9], S21, 0x21e1cde6); /* 25 */ + GG(d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG(c, d, a, b, x[3], S23, 0xf4d50d87); /* 27 */ + GG(b, c, d, a, x[8], S24, 0x455a14ed); /* 28 */ + GG(a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG(d, a, b, c, x[2], S22, 0xfcefa3f8); /* 30 */ + GG(c, d, a, b, x[7], S23, 0x676f02d9); /* 31 */ + GG(b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH(a, b, c, d, x[5], S31, 0xfffa3942); /* 33 */ + HH(d, a, b, c, x[8], S32, 0x8771f681); /* 34 */ + HH(c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH(b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH(a, b, c, d, x[1], S31, 0xa4beea44); /* 37 */ + HH(d, a, b, c, x[4], S32, 0x4bdecfa9); /* 38 */ + HH(c, d, a, b, x[7], S33, 0xf6bb4b60); /* 39 */ + HH(b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH(a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH(d, a, b, c, x[0], S32, 0xeaa127fa); /* 42 */ + HH(c, d, a, b, x[3], S33, 0xd4ef3085); /* 43 */ + HH(b, c, d, a, x[6], S34, 0x4881d05); /* 44 */ + HH(a, b, c, d, x[9], S31, 0xd9d4d039); /* 45 */ + HH(d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH(c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH(b, c, d, a, x[2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II(a, b, c, d, x[0], S41, 0xf4292244); /* 49 */ + II(d, a, b, c, x[7], S42, 0x432aff97); /* 50 */ + II(c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II(b, c, d, a, x[5], S44, 0xfc93a039); /* 52 */ + II(a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II(d, a, b, c, x[3], S42, 0x8f0ccc92); /* 54 */ + II(c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II(b, c, d, a, x[1], S44, 0x85845dd1); /* 56 */ + II(a, b, c, d, x[8], S41, 0x6fa87e4f); /* 57 */ + II(d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II(c, d, a, b, x[6], S43, 0xa3014314); /* 59 */ + II(b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II(a, b, c, d, x[4], S41, 0xf7537e82); /* 61 */ + II(d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II(c, d, a, b, x[2], S43, 0x2ad7d2bb); /* 63 */ + II(b, c, d, a, x[9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information. + */ + MD5_memset((POINTER)x, 0, sizeof(x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is + a multiple of 4. + */ +static void Encode(unsigned char* output, UINT4* input, unsigned int len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j + 1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j + 2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j + 3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode(UINT4* output, unsigned char* input, unsigned int len) { + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((UINT4)input[j]) | (((UINT4)input[j + 1]) << 8) | + (((UINT4)input[j + 2]) << 16) | (((UINT4)input[j + 3]) << 24); +} + +/* Note: Replace "for loop" with standard memcpy if possible. + */ +static void MD5_memcpy(POINTER output, POINTER input, unsigned int len) { + memcpy(output, input, len); + /* unsigned int i; + + for (i = 0; i < len; i++) + output[i] = input[i];*/ +} + +/* Note: Replace "for loop" with standard memset if possible. + */ +static void MD5_memset(POINTER output, int value, unsigned int len) { + memset(output, value, len); + /* unsigned int i; + + for (i = 0; i < len; i++) + ((char *)output)[i] = (char)value; */ +} + +inline void MD5Print(unsigned char digest[16], char output[33]) { + static const char hex_digits[] = "0123456789abcdef"; + unsigned int i; + + for (i = 0; i < 16; i++) { + output[i * 2] = hex_digits[digest[i] / 16]; + output[i * 2 + 1] = hex_digits[digest[i] % 16]; + } + output[32] = '\0'; +} + +void MD5Digest(unsigned char* input, unsigned int len, char output[33]) { + MD5_CTX ctx; + unsigned char digest[16]; + + MD5Init(&ctx); + MD5Update(&ctx, input, len); + MD5Final(digest, &ctx); + MD5Print(digest, output); +} +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/natneg/NATify.h b/source/gamespy/natneg/NATify.h new file mode 100644 index 000000000..1a269ad9f --- /dev/null +++ b/source/gamespy/natneg/NATify.h @@ -0,0 +1,67 @@ +#pragma once + +#define NATIFY_COOKIE 777 +#define NATIFY_TIMEOUT 10000 +#define NATIFY_STATUS_STEPS (NATIFY_TIMEOUT / 1000) + 7 + +typedef enum { + packet_map1a, + packet_map2, + packet_map3, + packet_map1b, + NUM_PACKETS +} NatifyPacket; +typedef enum { + no_nat, + firewall_only, + full_cone, + restricted_cone, + port_restricted_cone, + symmetric, + unknown, + NUM_NAT_TYPES +} NatType; +typedef enum { + promiscuous, + not_promiscuous, + port_promiscuous, + ip_promiscuous, + promiscuity_not_applicable, + NUM_PROMISCUITY_TYPES +} NatPromiscuity; +typedef enum { + unrecognized, + private_as_public, + consistent_port, + incremental, + mixed, + NUM_MAPPING_SCHEMES +} NatMappingScheme; + +typedef struct _AddressMapping { + unsigned int privateIp; + unsigned short privatePort; + unsigned int publicIp; + unsigned short publicPort; +} AddressMapping; + +typedef struct _NAT { + char brand[32]; + char model[32]; + char firmware[64]; + gsi_bool ipRestricted; + gsi_bool portRestricted; + NatPromiscuity promiscuity; + NatType natType; + NatMappingScheme mappingScheme; + AddressMapping mappings[4]; + gsi_bool qr2Compatible; +} NAT; + +int DiscoverReachability(SOCKET sock, unsigned int ip, unsigned short port, + int portType); +int DiscoverMapping(SOCKET sock, unsigned int ip, unsigned short port, + int portType, int id); +int NatifyThink(SOCKET sock, NAT* nat); +gsi_bool DetermineNatType(NAT* nat); +void OutputMapping(const AddressMapping* theMap); diff --git a/source/gamespy/natneg/natneg.h b/source/gamespy/natneg/natneg.h new file mode 100644 index 000000000..a7cf937bb --- /dev/null +++ b/source/gamespy/natneg/natneg.h @@ -0,0 +1,179 @@ + +/****** +GameSpy NAT Negotiation SDK + +Copyright 2002 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy NAT Negotiation SDK documentation for more + information + +******/ + +#pragma once + +#include "../common/gsCommon.h" +#include "NATify.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +NAT Negotiation Packet Magic Bytes +These bytes will start each incoming packet that is part of the NAT Negotiation +SDK. If you are sharing a game socket with the SDK, you can use these bytes to +determine when to pass a packet to NNProcessData +*/ +#define NATNEG_MAGIC_LEN 6 +#define NN_MAGIC_0 0xFD +#define NN_MAGIC_1 0xFC +#define NN_MAGIC_2 0x1E +#define NN_MAGIC_3 0x66 +#define NN_MAGIC_4 0x6A +#define NN_MAGIC_5 0xB2 + +// This external array contains all 6 magic bytes - you can use it with memcmp +// to quickly check incoming packets for the bytes +extern unsigned char NNMagicData[]; + +/* +Possible states for the SDK. The two you will be notified for are: +ns_initack - when the NAT Negotiation server acknowledges your connection +request ns_connectping - when direct negotiation with the other client has +started +*/ +typedef enum { + ns_initsent, + ns_initack, + ns_connectping, + ns_finished, + ns_canceled, + ns_reportsent, + ns_reportack +} NegotiateState; + +/* +Possible reslts of the negotiation. +nr_success: Successful negotiation, other parameters can be used to continue +communications with the client nr_deadbeatpartner: Partner did not register with +the NAT Negotiation Server nr_inittimeout: Unable to communicate with NAT +Negotiation Server nr_unknownerror: NAT Negotiation server indicated an unknown +error condition +*/ +typedef enum { + nr_success, + nr_deadbeatpartner, + nr_inittimeout, + nr_pingtimeout, + nr_unknownerror, + nr_noresult +} NegotiateResult; + +/* +Possible errors that can be returned when starting a negotiation +ne_noerror: No error +ne_allocerror: Memory allocation failed +ne_socketerror: Socket allocation failed +ne_dnserror: DNS lookup failed +*/ +typedef enum { + ne_noerror, + ne_allocerror, + ne_socketerror, + ne_dnserror +} NegotiateError; + +// Callback prototype for your progress function +typedef void (*NegotiateProgressFunc)(NegotiateState state, void* userdata); + +// Callback prototype for your negotiation completed function +typedef void (*NegotiateCompletedFunc)(NegotiateResult result, + SOCKET gamesocket, + struct sockaddr_in* remoteaddr, + void* userdata); + +// Callback prototype for your NAT detection results function +typedef void (*NatDetectionResultsFunc)(gsi_bool success, NAT nat); + +/* +NNBeginNegotiation +------------------- +Starts the negotiation process. +cookie: Shared cookie value that both players will use so that the NAT +Negotiation Server can match them up. clientindex: One client must use +clientindex 0, the other must use clientindex 1. progresscallback: Callback +function that will be called as the state changes completedcallback: Callback +function that will be called when negotiation is complete. userdata: Pointer for +your own use that will be passed into the callback functions. +*/ +NegotiateError NNBeginNegotiation(int cookie, int clientindex, + NegotiateProgressFunc progresscallback, + NegotiateCompletedFunc completedcallback, + void* userdata); + +/* +NNBeginNegotiationWithSocket +------------------- +Starts the negotiation process using the socket provided, which will be shared +with the game. Incoming traffic is not processed automatically - you will need +to read the data off the socket and pass NN packets to NNProcessData +*/ +NegotiateError +NNBeginNegotiationWithSocket(SOCKET gamesocket, int cookie, int clientindex, + NegotiateProgressFunc progresscallback, + NegotiateCompletedFunc completedcallback, + void* userdata); + +/* +NNThink +------------------- +Processes any negotiation requests that are in progress +*/ +void NNThink(); + +/* +NNProcessData +------------------- +When sharing a socket with the NAT Negotiation SDK, you must read incoming data +and pass packets that start the the NN magic bytes to this function for +processing, along with the address they came from. +*/ +void NNProcessData(char* data, int len, struct sockaddr_in* fromaddr); + +/* +NNCancel +------------------- +Cancels a NAT Negotiation request in progress +*/ +void NNCancel(int cookie); + +/* +NNFreeNegotiateList +------------------- +De-allocates the memory used by for the negotiate list when you are done with +NAT Negotiation. The list will be re-allocated at a later time if you start +additional negotiations. If any negotiations are outstanding this will cancel +them. +*/ +void NNFreeNegotiateList(); + +/* +NNStartNatDetection +------------------- +Detects if there is network address translation going on between the machine and +the Internet. +*/ +NegotiateError NNStartNatDetection(NatDetectionResultsFunc resultscallback); + +// Used for over-riding the default negotiation hostnames. Should not be used +// normally. +extern char* Matchup2Hostname; +extern char* Matchup1Hostname; + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/nonport.h b/source/gamespy/nonport.h new file mode 100644 index 000000000..c6b273ffc --- /dev/null +++ b/source/gamespy/nonport.h @@ -0,0 +1,12 @@ +/****** +nonport.h +GameSpy Common Code + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com +******/ + +#pragma once + +#include "common/gsCommon.h" diff --git a/source/gamespy/qr2/qr2.c b/source/gamespy/qr2/qr2.c new file mode 100644 index 000000000..3efcb046f --- /dev/null +++ b/source/gamespy/qr2/qr2.c @@ -0,0 +1,1625 @@ +/******** +INCLUDES +********/ +#include "qr2.h" +#include "../common/gsAvailable.h" +#include "../common/gsCommon.h" +#include "../natneg/natneg.h" +#include "qr2regkeys.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __MWERKS__ // Codewarrior requires function prototypes +qr2_error_t qr2_initW(/*[out]*/ qr2_t* qrec, const unsigned short* ip, + int baseport, const unsigned short* gamename, + const unsigned short* secret_key, int ispublic, + int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, void* userdata); +qr2_error_t qr2_init_socketW(/*[out]*/ qr2_t* qrec, SOCKET s, int boundport, + const unsigned short* gamename, + const unsigned short* secret_key, int ispublic, + int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata); +qr2_error_t qr2_init_socketA(/*[out]*/ qr2_t* qrec, SOCKET s, int boundport, + const char* gamename, const char* secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata); +qr2_error_t qr2_initA(/*[out]*/ qr2_t* qrec, const char* ip, int baseport, + const char* gamename, const char* secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, void* userdata); +#endif + +/******** +DEFINES +********/ +#define MASTER_PORT 27900 +#define MASTER_ADDR "master." GSI_DOMAIN_NAME +//#define MASTER_ADDR "207.199.80.230" +#define FIRST_HB_TIME 10000 /* 10 sec */ +#define HB_TIME 60000 /* 1 minute */ +#define KA_TIME 20000 /* 20 sec */ +#define MIN_STATECHANGED_HB_TIME 10000 /* 10 sec */ +#define MAX_FIRST_COUNT 4 /* 4 tries */ +#define MAX_DATA_SIZE 1400 +#define INBUF_LEN 256 +#define PUBLIC_ADDR_LEN 12 +#define QR2_OPTION_USE_QUERY_CHALLENGE 128 + +#define PACKET_QUERY 0x00 +#define PACKET_CHALLENGE 0x01 +#define PACKET_ECHO 0x02 +#define PACKET_ECHO_RESPONSE 0x05 // 0x05, not 0x03 (order) +#define PACKET_HEARTBEAT 0x03 +#define PACKET_ADDERROR 0x04 +#define PACKET_CLIENT_MESSAGE 0x06 +#define PACKET_CLIENT_MESSAGE_ACK 0x07 +#define PACKET_KEEPALIVE 0x08 +#define PACKET_PREQUERY_IP_VERIFY 0x09 + +#define MAX_LOCAL_IP 5 + +// magic bytes for nat negotiation message +#define NATNEG_MAGIC_LEN 6 +#define NN_MAGIC_0 0xFD +#define NN_MAGIC_1 0xFC +#define NN_MAGIC_2 0x1E +#define NN_MAGIC_3 0x66 +#define NN_MAGIC_4 0x6A +#define NN_MAGIC_5 0xB2 + +// ex flags are the 11th byte in the query packet +// Old queries will end at 10 bytes. +#define QR2_EXFLAG_SPLIT (1 << 0) + +// Some other settings for split packet responses +#define QR2_SPLITNUM_MAX 7 +#define QR2_SPLITNUM_FINALFLAG (1 << 7) + +/******** +TYPEDEFS +********/ +typedef unsigned char uchar; + +struct qr2_keybuffer_s { + uchar keys[MAX_REGISTERED_KEYS]; + int numkeys; +}; + +struct qr2_buffer_s { + char buffer[MAX_DATA_SIZE]; + int len; +}; + +#define AVAILABLE_BUFFER_LEN(a) (MAX_DATA_SIZE - (a)->len) + +/******** +VARS +********/ +struct qr2_implementation_s static_qr2_rec = {INVALID_SOCKET}; +static qr2_t current_rec = &static_qr2_rec; +char qr2_hostname[64 + 8]; + +static int num_local_ips = 0; +static struct in_addr local_ip_list[MAX_LOCAL_IP]; + +/******** +PROTOTYPES +********/ +static void send_heartbeat(qr2_t qrec, int statechanged); +static void send_keepalive(qr2_t qrec); +static int get_sockaddrin(const char* host, int port, struct sockaddr_in* saddr, + struct hostent** savehent); +static void qr2_check_queries(qr2_t qrec); +static void qr2_check_send_heartbeat(qr2_t qrec); +static void enum_local_ips(); +static void qr2_expire_ip_verify(qr2_t qrec); +qr2_error_t qr2_create_socket(/*[out]*/ SOCKET* sock, const char* ip, + /*[in/out]*/ int* port); + +/****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/****************************************************************************/ + +/* qr2_init: Initializes the sockets, etc. Returns an error value +if an error occured, or 0 otherwise */ +qr2_error_t qr2_init_socketA(/*[out]*/ qr2_t* qrec, SOCKET s, int boundport, + const char* gamename, const char* secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata) { + char hostname[64]; + int ret; + int i; + qr2_t cr; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_init_socket()\r\n"); + + if (qrec == NULL) { + cr = &static_qr2_rec; + } else { + *qrec = (qr2_t)gsimalloc(sizeof(struct qr2_implementation_s)); + cr = *qrec; + } + srand((unsigned int)current_time()); + strcpy(cr->gamename, gamename); + strcpy(cr->secret_key, secret_key); + cr->qport = boundport; + cr->lastheartbeat = 0; + cr->lastka = 0; + cr->hbsock = s; + cr->listed_state = 1; + cr->udata = userdata; + cr->server_key_callback = server_key_callback; + cr->player_key_callback = player_key_callback; + cr->team_key_callback = team_key_callback; + cr->key_list_callback = key_list_callback; + cr->playerteam_count_callback = playerteam_count_callback; + cr->adderror_callback = adderror_callback; + cr->nn_callback = NULL; + cr->cm_callback = NULL; + cr->cdkeyprocess = NULL; + cr->ispublic = ispublic; + cr->read_socket = 0; + cr->nat_negotiate = natnegotiate; + cr->publicip = 0; + cr->publicport = 0; + cr->pa_callback = NULL; + cr->userstatechangerequested = 0; + cr->backendoptions = 0; + + for (i = 0; i < REQUEST_KEY_LEN; i++) + cr->instance_key[i] = (char)(rand() % 0xFF); + for (i = 0; i < RECENT_CLIENT_MESSAGES_TO_TRACK; i++) + cr->client_message_keys[i] = -1; + cr->cur_message_key = 0; + + memset(cr->ipverify, 0, sizeof(cr->ipverify)); + + // if (num_local_ips == 0) - caching IPs can result in problems if DHCP has + // allocated a new one + enum_local_ips(); + + if (ispublic) { + int override_ = qr2_hostname[0]; + if (!override_) + sprintf(hostname, "%s.master." GSI_DOMAIN_NAME, gamename); + ret = get_sockaddrin(override_ ? qr2_hostname : hostname, MASTER_PORT, + &(cr->hbaddr), NULL); + + if (ret == 1) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, + GSIDebugLevel_Comment, "%s resolved to %s\r\n", + override_ ? qr2_hostname : hostname, + inet_ntoa(cr->hbaddr.sin_addr)); + } else { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, + GSIDebugLevel_HotError, "Failed on DNS lookup for %s \r\n", + override_ ? qr2_hostname : hostname); + } + } else // don't need to look up + ret = 1; + if (!ret) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_HotError, + "qr2_init_socket() returned failed (DNS error)\r\n"); + return e_qrdnserror; + } else { + return e_qrnoerror; + } +} + +qr2_error_t qr2_init_socketW(/*[out]*/ qr2_t* qrec, SOCKET s, int boundport, + const unsigned short* gamename, + const unsigned short* secret_key, int ispublic, + int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata) { + char gamename_A[255]; + char secretkey_A[255]; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_init_socketW()\r\n"); + + UCS2ToAsciiString(gamename, gamename_A); + UCS2ToAsciiString(secret_key, secretkey_A); + + return qr2_init_socketA(qrec, s, boundport, gamename_A, secretkey_A, ispublic, + natnegotiate, server_key_callback, + player_key_callback, team_key_callback, + key_list_callback, playerteam_count_callback, + adderror_callback, userdata); +} + +qr2_error_t qr2_create_socket(/*[out]*/ SOCKET* sock, const char* ip, + /*[in/out]*/ int* port) { + struct sockaddr_in saddr; + SOCKET hbsock; + int maxport; + int lasterror = 0; + int baseport = *port; + + int saddrlen; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_create_socket()\r\n"); + + SocketStartUp(); + + hbsock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (INVALID_SOCKET == hbsock) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "Failed to create heartbeat socket\r\n"); + return e_qrwsockerror; + } + + maxport = baseport + NUM_PORTS_TO_TRY; + while (baseport < maxport) { + get_sockaddrin(ip, baseport, &saddr, NULL); + if (saddr.sin_addr.s_addr == + htonl(0x7F000001)) // localhost -- we don't want that! + saddr.sin_addr.s_addr = INADDR_ANY; + + lasterror = bind(hbsock, (struct sockaddr*)&saddr, sizeof(saddr)); + if (lasterror == 0) + break; // we found a port + baseport++; + } + + if (lasterror != 0) // we weren't able to find a port + { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "Failed to bind() query socket\r\n"); + return e_qrbinderror; + } + + if (baseport == 0) // we bound it dynamically + { + saddrlen = sizeof(saddr); + + lasterror = getsockname(hbsock, (struct sockaddr*)&saddr, &saddrlen); + + if (lasterror) { + gsDebugFormat( + GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "Query socket bind() success, but getsockname() failed\r\n"); + return e_qrbinderror; + } + baseport = ntohs(saddr.sin_port); + } + + *sock = hbsock; + *port = baseport; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Query socket created and bound to port %d\r\n", *port); + + return e_qrnoerror; +} + +qr2_error_t qr2_initA(/*[out]*/ qr2_t* qrec, const char* ip, int baseport, + const char* gamename, const char* secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata) { + SOCKET hbsock; + qr2_error_t ret; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_init()\r\n"); + + ret = qr2_create_socket(&hbsock, ip, &baseport); + if (ret != e_qrnoerror) { + SocketShutDown(); + return ret; + } + + ret = + qr2_init_socketA(qrec, hbsock, baseport, gamename, secret_key, ispublic, + natnegotiate, server_key_callback, player_key_callback, + team_key_callback, key_list_callback, + playerteam_count_callback, adderror_callback, userdata); + if (qrec == NULL) + qrec = ¤t_rec; + (*qrec)->read_socket = 1; + + return ret; +} + +qr2_error_t qr2_initW(/*[out]*/ qr2_t* qrec, const unsigned short* ip, + int baseport, const unsigned short* gamename, + const unsigned short* secret_key, int ispublic, + int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata) { + char ip_A[255]; + char gamename_A[255]; + char secretkey_A[255]; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_initW()\r\n"); + + if (ip != NULL) // NULL value is valid for IP + UCS2ToAsciiString(ip, ip_A); + UCS2ToAsciiString(gamename, gamename_A); + UCS2ToAsciiString(secret_key, secretkey_A); + + return qr2_initA(qrec, (ip != NULL) ? ip_A : NULL, baseport, gamename_A, + secretkey_A, ispublic, natnegotiate, server_key_callback, + player_key_callback, team_key_callback, key_list_callback, + playerteam_count_callback, adderror_callback, userdata); +} + +void qr2_register_natneg_callback(qr2_t qrec, qr2_natnegcallback_t nncallback) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_natneg_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->nn_callback = nncallback; +} +void qr2_register_clientmessage_callback( + qr2_t qrec, qr2_clientmessagecallback_t cmcallback) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_clientmessage_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->cm_callback = cmcallback; +} +void qr2_register_publicaddress_callback( + qr2_t qrec, qr2_publicaddresscallback_t pacallback) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_publicaddress_callback()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + qrec->pa_callback = pacallback; +} + +/* qr2_think: Processes any waiting queries, and sends a +heartbeat if needed */ +void qr2_think(qr2_t qrec) { + if (qrec == NULL) + qrec = current_rec; + if (qrec->ispublic) + qr2_check_send_heartbeat(qrec); + qr2_check_queries(qrec); + qr2_expire_ip_verify(qrec); + // NNThink(); +} + +// Linker will remove this function through dead-stripping. +// We use this to force some strings to be defined. +void define_some_strings() { + char* x; + void (*foo)(void); + x = "Received %d bytes on query socket\r\n"; // 0x8027d0a4 + foo = (void (*)(void))x; + foo(); + x = "CanReceiveOnSocket() returned true, but recvfrom return 0!\r\n"; // 0x8027d0c8 + foo = (void (*)(void))x; + foo(); + x = "CanReceiveOnSocket() returned true, but recvfrom failed!\r\n"; // 0x8027d108 + foo = (void (*)(void))x; + foo(); +} + +char qr2_check_queries_indata[INBUF_LEN]; +#ifdef NON_MATCHING +/* qr2_check_queries: Processes any waiting queries */ +void qr2_check_queries(qr2_t qrec) { + struct sockaddr_in saddr; + int error; + + int saddrlen = sizeof(struct sockaddr_in); + + if (!qrec->read_socket) + return; // not our job + + while (CanReceiveOnSocket(qrec->hbsock)) { + // else we have data + error = + (int)recvfrom(qrec->hbsock, qr2_check_queries_indata, (INBUF_LEN - 1), + 0, (struct sockaddr*)&saddr, &saddrlen); + if (gsiSocketIsNotError(error)) { + qr2_check_queries_indata[error] = '\0'; + qr2_parse_queryA(qrec, qr2_check_queries_indata, error, + (struct sockaddr*)&saddr); + } + } +} +#else +// clang-format off +asm void qr2_check_queries(qr2_t qrec) { + nofralloc; + stwu r1, -48(r1) + mflr r0 + stw r0, 52(r1) + li r0, 0x8 + stw r31, 44(r1) + stw r30, 40(r1) + stw r29, 36(r1) + stw r28, 32(r1) + mr r28, r3 + stw r0, 8(r1) + lwz r0, 196(r3) + cmpwi r0, 0x0 + bne loc2 +loc1: + b loc7 +loc2: + // Not relocatable :( + lis r29, 0x802f + li r30, 0x0 + addi r31, r29, 0x3520 + b loc6 +loc3: + lwz r3, 0(r28) + addi r4, r29, 0x3520 + addi r7, r1, 0x10 + addi r8, r1, 0x8 + li r5, 0xff + li r6, 0x0 + bl recvfrom + cmpwi r3, -0x1 + mr r5, r3 + beq loc5 +loc4: + stbx r30, r31, r3 + mr r3, r28 + mr r4, r31 + addi r6, r1, 0x10 + bl qr2_parse_queryA + b loc6 +loc5: + cmpwi r3, 0x0 +loc6: + lwz r3, 0(r28) + bl CanReceiveOnSocket + cmpwi r3, 0x0 + bne loc3 +loc7: + lwz r0, 52(r1) + lwz r31, 44(r1) + lwz r30, 40(r1) + lwz r29, 36(r1) + lwz r28, 32(r1) + mtlr r0 + addi r1, r1, 0x30 + blr +} +// clang-format on +#endif + +/* check_send_heartbeat: Perform any scheduled outgoing +heartbeats */ +void qr2_check_send_heartbeat(qr2_t qrec) { + gsi_time tc = current_time(); + + if (INVALID_SOCKET == qrec->hbsock) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, + GSIDebugLevel_WarmError, "HBSock is invalid\r\n"); + return; // no sockets to work with! + } + + // check if we need to send a heartbet + if (qrec->listed_state > 0 && + tc - qrec->lastheartbeat > + FIRST_HB_TIME) { // check to see if we haven't gotten a query yet + if (qrec->listed_state >= MAX_FIRST_COUNT) { + gsDebugFormat( + GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_HotError, + "No response from master, generating NoChallengeResponse error\r\n"); + + qrec->listed_state = 0; // we failed to get a challenge! let them know + qrec->adderror_callback( + e_qrnochallengeerror, + "No challenge value was received from the master server.", + qrec->udata); + return; + } else { + send_heartbeat(qrec, 3); + qrec->listed_state++; + } + } else if (qrec->userstatechangerequested && + (tc - qrec->lastheartbeat > MIN_STATECHANGED_HB_TIME)) + send_heartbeat(qrec, 1); // Send out pending statechange request + else if (tc - qrec->lastheartbeat > HB_TIME || qrec->lastheartbeat == 0 || + tc < qrec->lastheartbeat) + send_heartbeat(qrec, 0); // Send out a normal hearbeat + + if (current_time() - qrec->lastka > + KA_TIME) // send a keep alive (to keep NAT port mappings the same if + // possible) + send_keepalive(qrec); +} + +/* qr2_send_statechanged: Sends a statechanged heartbeat, call when +your gamemode changes */ +void qr2_send_statechanged(qr2_t qrec) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_send_statechanged()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + if (!qrec->ispublic) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Warning, + "Requested send statechange for LAN game, discarding\r\n"); + return; + } + if (current_time() - qrec->lastheartbeat < MIN_STATECHANGED_HB_TIME) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Queing statechange for later send (too soon)\r\n"); + + // Queue up the statechange and send later + qrec->userstatechangerequested = 1; + return; // don't allow the server to spam statechanges + } + + send_heartbeat(qrec, 1); + qrec->userstatechangerequested = + 0; // clear the flag in case a queued statechange was still pending +} + +/* qr2_shutdown: Cleans up the sockets and shuts down */ +void qr2_shutdown(qr2_t qrec) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace, + "qr2_shutdown()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + if (qrec->ispublic) + send_heartbeat(qrec, 2); + if (INVALID_SOCKET != qrec->hbsock && + qrec->read_socket) // if we own the socket + { + closesocket(qrec->hbsock); + } + qrec->hbsock = INVALID_SOCKET; + qrec->lastheartbeat = 0; + if (qrec->read_socket) // if we own the socket + { + SocketShutDown(); + } + if (qrec != + &static_qr2_rec) // need to gsifree it, it was dynamically allocated + { + gsifree(qrec); + } + + // free ACE negotiate list + // NNFreeNegotiateList(); // comment out if ACE is not being used + + // BD: Removed - Peer SDK repeatedly calls qr2_shutdown, but + // keys should only be deallocated once. + + // Developers should call this manually (when in GSI_UNICODE mode) + // qr2_internal_key_list_free(); +} + +gsi_bool qr2_keybuffer_add(qr2_keybuffer_t keybuffer, int keyid) { + // mj these are codetime not runtime errors, changing to assert + if (keybuffer->numkeys >= MAX_REGISTERED_KEYS) + return gsi_false; + if (keyid < 1 || keyid > MAX_REGISTERED_KEYS) + return gsi_false; + + keybuffer->keys[keybuffer->numkeys++] = (uchar)keyid; + return gsi_true; +} + +gsi_bool qr2_buffer_add_int(qr2_buffer_t outbuf, int value) { + char temp[20]; + sprintf(temp, "%d", value); + return qr2_buffer_addA(outbuf, temp); +} + +gsi_bool qr2_buffer_addA(qr2_buffer_t outbuf, const char* value) { + GS_ASSERT(outbuf) + GS_ASSERT(value) { + int copylen; + copylen = (int)strlen(value) + 1; + if (copylen > AVAILABLE_BUFFER_LEN(outbuf)) + copylen = + AVAILABLE_BUFFER_LEN(outbuf); // max length we can fit in the buffer + if (copylen <= 0) + return gsi_false; // no space + memcpy(outbuf->buffer + outbuf->len, value, (unsigned int)copylen); + outbuf->len += copylen; + outbuf->buffer[outbuf->len - 1] = 0; // make sure it's null terminated + return gsi_true; + } +} + +static void enum_local_ips() { + struct hostent* phost; + phost = getlocalhost(); + if (phost == NULL) + return; + for (num_local_ips = 0; num_local_ips < MAX_LOCAL_IP; num_local_ips++) { + if (phost->h_addr_list[num_local_ips] == 0) + break; + memcpy(&local_ip_list[num_local_ips], phost->h_addr_list[num_local_ips], + sizeof(struct in_addr)); + } +} + +/****************************************************************************/ + +/* Return a sockaddrin for the given host (numeric or DNS) and port) +Returns the hostent in savehent if it is not NULL */ +static int get_sockaddrin(const char* host, int port, struct sockaddr_in* saddr, + struct hostent** savehent) { + struct hostent* hent = NULL; + + saddr->sin_family = AF_INET; + saddr->sin_port = htons((unsigned short)port); + if (host == NULL) + saddr->sin_addr.s_addr = INADDR_ANY; + else + saddr->sin_addr.s_addr = inet_addr(host); + + if (saddr->sin_addr.s_addr == INADDR_NONE && + strcmp(host, "255.255.255.255") != 0) { + hent = gethostbyname(host); + if (!hent) + return 0; + saddr->sin_addr.s_addr = *(unsigned int*)hent->h_addr_list[0]; + } + if (savehent != NULL) + *savehent = hent; + return 1; +} + +/*****************************************************************************/ +/* Various encryption / encoding routines */ + +static void swap_byte(uchar* a, uchar* b) { + uchar swapByte; + + swapByte = *a; + *a = *b; + *b = swapByte; +} + +static uchar encode_ct(uchar c) { + if (c < 26) + return (uchar)('A' + c); + if (c < 52) + return (uchar)('a' + c - 26); + if (c < 62) + return (uchar)('0' + c - 52); + if (c == 62) + return (uchar)('+'); + if (c == 63) + return (uchar)('/'); + + return 0; +} + +static void gs_encode(uchar* ins, int size, uchar* result) { + int i, pos; + uchar trip[3]; + uchar kwart[4]; + + i = 0; + while (i < size) { + for (pos = 0; pos <= 2; pos++, i++) + if (i < size) + trip[pos] = *ins++; + else + trip[pos] = '\0'; + kwart[0] = (unsigned char)((trip[0]) >> 2); + kwart[1] = (unsigned char)((((trip[0]) & 3) << 4) + ((trip[1]) >> 4)); + kwart[2] = (unsigned char)((((trip[1]) & 15) << 2) + ((trip[2]) >> 6)); + kwart[3] = (unsigned char)((trip[2]) & 63); + for (pos = 0; pos <= 3; pos++) + *result++ = encode_ct(kwart[pos]); + } + *result = '\0'; +} + +static void gs_encrypt(uchar* key, int key_len, uchar* buffer_ptr, + int buffer_len) { + short counter; + uchar x, y, xorIndex; + uchar state[256]; + + for (counter = 0; counter < 256; counter++) + state[counter] = (uchar)counter; + + x = 0; + y = 0; + for (counter = 0; counter < 256; counter++) { + y = (uchar)((key[x] + state[counter] + y) % 256); + x = (uchar)((x + 1) % key_len); + swap_byte(&state[counter], &state[y]); + } + + x = 0; + y = 0; + for (counter = 0; counter < buffer_len; counter++) { + x = (uchar)((x + buffer_ptr[counter] + 1) % 256); + y = (uchar)((state[x] + y) % 256); + swap_byte(&state[x], &state[y]); + xorIndex = (uchar)((state[x] + state[y]) % 256); + buffer_ptr[counter] ^= state[xorIndex]; + } +} + +static void qr_add_packet_header(qr2_buffer_t buf, char ptype, char* reqkey) { + buf->buffer[0] = ptype; + memcpy(buf->buffer + 1, reqkey, REQUEST_KEY_LEN); + buf->len = REQUEST_KEY_LEN + 1; +} + +#define MAX_CHALLENGE 64 +static void compute_challenge_response(qr2_t qrec, qr2_buffer_t buf, + char* challenge, int challengelen) { + char encrypted_val[MAX_CHALLENGE + 1]; // don't need to null terminate + + if (challengelen < 1) + return; // invalid, need room for the NUL + if (challengelen > (MAX_CHALLENGE + 1)) + return; // invalid + if (challenge[challengelen - 1] != 0) + return; // invalid - must be NTS + + strcpy(encrypted_val, challenge); + gs_encrypt((uchar*)qrec->secret_key, (int)strlen(qrec->secret_key), + (uchar*)encrypted_val, challengelen - 1); + gs_encode((uchar*)encrypted_val, challengelen - 1, + (uchar*)(buf->buffer + buf->len)); + buf->len += (int)strlen(buf->buffer + buf->len) + 1; +} + +static void handle_public_address(qr2_t qrec, char* buffer) { + unsigned int ip; + unsigned int portTemp; + unsigned short port; + + // get the public ip and port as the master server sees it + sscanf(buffer, "%08X%04X", &ip, &portTemp); + port = (unsigned short)portTemp; + ip = htonl(ip); + + // sanity check + if ((ip == 0) || (port == 0)) + return; + +#ifdef GSI_COMMON_DEBUG + { + IN_ADDR addr; + addr.s_addr = ip; + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Received public address (%s:%d)\r\n", inet_ntoa(addr), port); + } +#endif + + // has anything changed? + if ((qrec->publicip != ip) || (qrec->publicport != port)) { + qrec->publicip = ip; + qrec->publicport = port; + qrec->pa_callback(ip, port, qrec->udata); + } +} + +static void qr_build_partial_query_reply(qr2_t qrec, qr2_buffer_t buf, + qr2_key_type keytype, int keycount, + uchar* keys) { + struct qr2_keybuffer_s kb; + int playerteamcount; + unsigned short cttemp; + int i; + int pindex; + const char* k; + int len; + + kb.numkeys = 0; + if (keycount == 0) + return; // no keys wanted + + if (keytype == key_player || + keytype == key_team) // need to add the player/team counts + { + if (AVAILABLE_BUFFER_LEN(buf) < sizeof(cttemp)) + return; // no more space + playerteamcount = qrec->playerteam_count_callback(keytype, qrec->udata); + cttemp = htons((unsigned short)playerteamcount); + memcpy(buf->buffer + buf->len, &cttemp, sizeof(cttemp)); + buf->len += sizeof(cttemp); + } else + playerteamcount = 1; + + if (keycount == 0xFF) // need to get the list of keys + { + qrec->key_list_callback(keytype, &kb, qrec->udata); + // add all the keys + for (i = 0; i < kb.numkeys; i++) { + k = qr2_registered_key_list[kb.keys[i]]; + if (k == NULL) + k = "unknown"; + qr2_buffer_addA(buf, k); + if (keytype == key_server) // add the server values + { + len = buf->len; + qrec->server_key_callback(kb.keys[i], buf, qrec->udata); + if (len == buf->len) + qr2_buffer_addA(buf, ""); + } + } + // add an extra null + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return; // no space + buf->buffer[buf->len++] = 0; + keycount = kb.numkeys; + keys = kb.keys; + if (keytype == key_server) + return; // already added the keys + } + for (pindex = 0; pindex < playerteamcount; pindex++) { + for (i = 0; i < keycount; i++) { + len = buf->len; + if (keytype == key_server) // add the server keys + qrec->server_key_callback(keys[i], buf, qrec->udata); + else if (keytype == key_player) + qrec->player_key_callback(keys[i], pindex, buf, qrec->udata); + else if (keytype == key_team) + qrec->team_key_callback(keys[i], pindex, buf, qrec->udata); + if (len == buf->len) + qr2_buffer_addA(buf, ""); + } + } +} + +static void qr_build_query_reply(qr2_t qrec, qr2_buffer_t buf, + int serverkeycount, uchar* serverkeys, + int playerkeycount, uchar* playerkeys, + int teamkeycount, uchar* teamkeys) { + qr_build_partial_query_reply(qrec, buf, key_server, serverkeycount, + serverkeys); + qr_build_partial_query_reply(qrec, buf, key_player, playerkeycount, + playerkeys); + qr_build_partial_query_reply(qrec, buf, key_team, teamkeycount, teamkeys); +} + +struct QRSplitQueryProgress { + qr2_key_type mCurKeyType; + int mCurPacketNum; + int mCurKeyIndex; // serverkey index, playerkey index, teamkey index + int mCurSubCount; // number of players or number of teams + int mCurSubIndex; // current player num or current team num + struct qr2_keybuffer_s mKeyBuffer; // keybuffer, for key name indexing +}; + +// return values: +// gsi_true = send buffer, then call this function again +// gsi_false = don't send buffer, don't call this function again +static gsi_bool +qr_build_split_query_reply(qr2_t qrec, qr2_buffer_t buf, + struct QRSplitQueryProgress* progress) { + unsigned char* packetNumPos = + NULL; // Used to store the byte position of the packet number + + // Make sure the key type is valid + // (The key type is set to invalid when all keys have been processed.) + // if (progress->mCurKeyType < 0 ||progress->mCurKeyType >= key_type_count) + if (progress->mCurKeyType >= key_type_count) + return gsi_false; // stop processing + + // check buffer space + // (buffer should only contain header at this point) + if (AVAILABLE_BUFFER_LEN(buf) < 32) + return gsi_false; // no space? + + // Dump the split packet "header" + qr2_buffer_addA(buf, "splitnum"); + packetNumPos = (unsigned char*)&buf->buffer[buf->len++]; + *packetNumPos = (gsi_u8)progress->mCurPacketNum++; + + // Resume dumping at key_type level + while (progress->mCurKeyType < key_type_count) { + // Get the list of keys if we don't have it already + if (progress->mKeyBuffer.numkeys == 0) + qrec->key_list_callback(progress->mCurKeyType, &progress->mKeyBuffer, + qrec->udata); + + // Get the list of players/teams if we don't have it already + if (progress->mCurSubCount == 0 && progress->mCurKeyType != key_server) + progress->mCurSubCount = + qrec->playerteam_count_callback(progress->mCurKeyType, qrec->udata); + + // check buffer space + if (AVAILABLE_BUFFER_LEN(buf) < 100) + return gsi_true; // no space + + // Write the key type + buf->buffer[buf->len++] = (char)progress->mCurKeyType; + + // For each key + while (progress->mCurKeyIndex < progress->mKeyBuffer.numkeys) { + // check buffer space + int aRegisteredKeyIndex = + progress->mKeyBuffer.keys[progress->mCurKeyIndex]; + const char* aKeyName = qr2_registered_key_list[aRegisteredKeyIndex]; + + // Write the key name + if (gsi_is_false(qr2_buffer_addA(buf, aKeyName))) + return gsi_true; // send, then try again + + if (progress->mCurKeyType == key_server) { + // write the key value + qrec->server_key_callback(aRegisteredKeyIndex, buf, qrec->udata); + + // make sure the key was written + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return gsi_true; // ran out of space! retry this key/value next packet + } else { + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return gsi_true; // ran out of space, retry this key/value next packet + + // Non-split packets implicitly being with player/team number zero, + // split packet explicitly specify the starting number + buf->buffer[buf->len++] = (char)progress->mCurSubIndex; + + // For each player/team + while (progress->mCurSubIndex < progress->mCurSubCount) { + // dump the value into the buffer + if (progress->mCurKeyType == key_player) + qrec->player_key_callback(aRegisteredKeyIndex, + progress->mCurSubIndex, buf, qrec->udata); + else if (progress->mCurKeyType == key_team) + qrec->team_key_callback(aRegisteredKeyIndex, progress->mCurSubIndex, + buf, qrec->udata); + + // make sure the key was written + if (AVAILABLE_BUFFER_LEN(buf) < 1) + return gsi_true; // ran out of space, try again next packet + + // move onto the next player/team value + progress->mCurSubIndex++; + } + // append a null to signify end of this team/player key + if (AVAILABLE_BUFFER_LEN(buf) > 0) + buf->buffer[buf->len++] = '\0'; + } + // move onto next key + progress->mCurKeyIndex++; + progress->mCurSubIndex = 0; + } + + // append a null to signify end of this key_type section + if (AVAILABLE_BUFFER_LEN(buf) > 0) + buf->buffer[buf->len++] = '\0'; + + // Move onto next key type + progress->mCurKeyType = (qr2_key_type)(progress->mCurKeyType + 1); + progress->mCurKeyIndex = 0; + progress->mCurSubCount = 0; + progress->mCurSubIndex = 0; + progress->mKeyBuffer.numkeys = 0; + } + + // Add the "final" flag to the packet number + *packetNumPos |= QR2_SPLITNUM_FINALFLAG; + return gsi_true; // function will bail without sending next iteration +} + +static void qr_process_query(qr2_t qrec, qr2_buffer_t buf, uchar* qdata, + int len, struct sockaddr* sender) { + uchar serverkeycount; + uchar playerkeycount; + uchar teamkeycount; + uchar exflags = 0; + + uchar* serverkeys = NULL; + uchar* playerkeys = NULL; + uchar* teamkeys = NULL; + if (len < 3) { + gsDebugFormat( + GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#1: %d bytes total)\r\n", len); + return; // invalid + } + serverkeycount = qdata[0]; + qdata++; + len--; + if (serverkeycount != 0 && serverkeycount != 0xFF) { + serverkeys = qdata; + qdata += serverkeycount; + len -= serverkeycount; + } + if (len < 2) { + gsDebugFormat( + GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#2: %d bytes remain)\r\n", len); + return; // invalid + } + playerkeycount = qdata[0]; + qdata++; + len--; + if (playerkeycount != 0 && playerkeycount != 0xFF) { + playerkeys = qdata; + qdata += playerkeycount; + len -= playerkeycount; + } + if (len < 1) { + gsDebugFormat( + GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#3: %d bytes remain)\r\n", len); + return; // invalid + } + teamkeycount = qdata[0]; + qdata++; + len--; + if (teamkeycount != 0 && teamkeycount != 0xFF) { + teamkeys = qdata; + qdata += teamkeycount; + len -= teamkeycount; + } + if (len < 0) { + gsDebugFormat( + GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Discarding invalid query (too short#4: %d bytes remain)\r\n", len); + return; // invalid + } + + // check the exflags + if (len > 0) { + exflags = qdata[0]; + len--; + } + + // Support split queries? + if ((exflags & QR2_EXFLAG_SPLIT) == QR2_EXFLAG_SPLIT) { + struct QRSplitQueryProgress progress; + progress.mCurPacketNum = 0; + progress.mCurKeyType = key_server; + progress.mCurKeyIndex = 0; + progress.mCurSubCount = 0; + progress.mCurSubIndex = 0; + progress.mKeyBuffer.numkeys = 0; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Building query reply (split packet supported)\r\n"); + + // Send packets as long as we need to + while (gsi_true == qr_build_split_query_reply(qrec, buf, &progress)) { + sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, + sizeof(struct sockaddr_in)); + buf->len = 5; // reset buffer but preserve 5-byte qr2 header + if (progress.mCurPacketNum > QR2_SPLITNUM_MAX) + return; // more than 7 isn't supported (likely a bug if you hit it) + } + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Finished split query reply (%d packets)\r\n", + progress.mCurPacketNum); + } else { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Building query reply (single packet)\r\n"); + qr_build_query_reply(qrec, buf, serverkeycount, serverkeys, playerkeycount, + playerkeys, teamkeycount, teamkeys); + sendto(qrec->hbsock, buf->buffer, buf->len, 0, sender, + sizeof(struct sockaddr_in)); + } + + GSI_UNUSED(sender); +} + +/* +static void qr_build_partial_old_query_reply(qr2_t qrec, qr2_buffer_t buf, +qr2_key_type keytype) +{ + char tempkeyname[128]; + struct qr2_keybuffer_s kb; + int playerteamcount; + int i; + int pindex; + const char *k; + int len; + + kb.numkeys = 0; + + if (keytype == key_player || keytype == key_team) //need to add the +player/team counts + { + playerteamcount = qrec->playerteam_count_callback(keytype, +qrec->udata); } else playerteamcount = 1; + + qrec->key_list_callback(keytype, &kb, qrec->udata); + //add all the keys + for (i = 0 ; i < kb.numkeys ; i++) + { + k = qr2_registered_key_list[kb.keys[i]]; + if (k == NULL) + k = "unknown"; + if (keytype == key_server) //add the server values + { + qr2_buffer_addA(buf, k); + buf->buffer[buf->len - 1] = '\\'; + len = buf->len; + qrec->server_key_callback(kb.keys[i], buf, qrec->udata); + if(len == buf->len) + qr2_buffer_addA(buf, ""); + buf->buffer[buf->len - 1] = '\\'; + } else //need to look it up for each player/team + { + + for (pindex = 0 ; pindex < playerteamcount ; pindex++) + { + sprintf(tempkeyname, "%s%d", k, pindex); + qr2_buffer_addA(buf, tempkeyname); + buf->buffer[buf->len - 1] = '\\'; + len = buf->len; + if (keytype == key_player) + qrec->player_key_callback(kb.keys[i], +pindex, buf, qrec->udata); else if (keytype == key_team) + qrec->team_key_callback(kb.keys[i], +pindex, buf, qrec->udata); if(len == buf->len) qr2_buffer_addA(buf, ""); + buf->buffer[buf->len - 1] = '\\'; + } + } + } +} +*/ + +// we just build a status reply, since we don't have equivalent callbacks +/*static void qr_process_old_query(qr2_t qrec, qr2_buffer_t buf) +{ + buf->len = 1; + buf->buffer[0] = '\\'; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Comment, + "Processing QR1 style query\r\n"); + + qr_build_partial_old_query_reply(qrec, buf, key_server); + qr_build_partial_old_query_reply(qrec, buf, key_player); + qr_build_partial_old_query_reply(qrec, buf, key_team); + qr2_buffer_addA(buf, "final\\\\queryid\\1.1"); + buf->len--; //remove the final null; +}*/ + +static void qr_process_client_message(qr2_t qrec, char* buf, int len) { + unsigned char natNegBytes[NATNEG_MAGIC_LEN] = { + NN_MAGIC_0, NN_MAGIC_1, NN_MAGIC_2, NN_MAGIC_3, NN_MAGIC_4, NN_MAGIC_5}; + int i; + int isnatneg = 1; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Processing client message\r\n"); + + if (len >= NATNEG_MAGIC_LEN + 4) { + for (i = 0; i < NATNEG_MAGIC_LEN; i++) { + if ((unsigned char)buf[i] != natNegBytes[i]) { + isnatneg = 0; + break; + } + } + } else { + isnatneg = 0; + } + if (isnatneg) { + int cookie; + memcpy(&cookie, buf + NATNEG_MAGIC_LEN, 4); + if (qrec->nn_callback) { + qrec->nn_callback((int)ntohl((unsigned int)cookie), qrec->udata); + } + } else if (qrec->cm_callback) { + qrec->cm_callback(buf, len, qrec->udata); + } +} + +static int qr_got_recent_message(qr2_t qrec, int msgkey) { + int i; + for (i = 0; i < RECENT_CLIENT_MESSAGES_TO_TRACK; i++) { + if (qrec->client_message_keys[i] == msgkey) + return 1; + } + // else, add it to the list + qrec->cur_message_key = + (qrec->cur_message_key + 1) % RECENT_CLIENT_MESSAGES_TO_TRACK; + qrec->client_message_keys[qrec->cur_message_key] = msgkey; + return 0; +} + +// Send a random value to the user, to verify IP address +static gsi_bool qr2_process_ip_verify(qr2_t qrec, struct qr2_buffer_s* buf, + struct sockaddr_in* sender) { + int i = 0; + gsi_time now = current_time(); + int firstFreeIndex = -1; + int numDuplicates = 0; + + // if the query challenge is disabled, return 0 as the challenge + if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == 0) { + qr2_buffer_add_int(buf, 0); + return gsi_true; + } + + // check if this ip/port combo is already in the list + for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++) { + // Mark the first free index when/if found + if (firstFreeIndex == -1 && qrec->ipverify[i].addr.sin_addr.s_addr == 0) + firstFreeIndex = i; + // Count any indexes that match this IP/port + if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr && + qrec->ipverify[i].addr.sin_port == sender->sin_port) { + numDuplicates++; + } + } + + // discard if too many duplicates or no index found + if (numDuplicates > QR2_IPVERIFY_MAXDUPLICATES) + return gsi_false; + if (firstFreeIndex == -1) + return gsi_false; // no free indexes + + // create a random challenge for this ip/port combo + qrec->ipverify[firstFreeIndex].addr = *sender; + qrec->ipverify[firstFreeIndex].challenge = htonl((rand() << 16) | rand()); + qrec->ipverify[firstFreeIndex].createtime = now; + + qr2_buffer_add_int(buf, (int)qrec->ipverify[firstFreeIndex].challenge); + return gsi_true; // buffer ready to be sent +} + +// Check if the returned ipverify value matches the random we sent earlier +// If it matches, remove it +static gsi_bool qr2_check_ip_verify(qr2_t qrec, struct sockaddr_in* sender, + gsi_u32 ipverify) { + int i = 0; + for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++) { + if (qrec->ipverify[i].addr.sin_addr.s_addr == sender->sin_addr.s_addr && + qrec->ipverify[i].addr.sin_port == sender->sin_port) { + if (qrec->ipverify[i].challenge == ipverify) { + // reset structure + qrec->ipverify[i].addr.sin_addr.s_addr = 0; + qrec->ipverify[i].addr.sin_port = 0; + return gsi_true; + } + // else + // keep searching...a single IP may have multiple outstanding + // challenges. + } + } + return gsi_false; +} + +// Expire old verify attempts +static void qr2_expire_ip_verify(qr2_t qrec) { + int i = 0; + gsi_time now = current_time(); + + for (; i < QR2_IPVERIFY_ARRAY_SIZE; i++) { + if (qrec->ipverify[i].addr.sin_addr.s_addr != 0 && + (now - qrec->ipverify[i].createtime > QR2_IPVERIFY_TIMEOUT)) + qrec->ipverify[i].addr.sin_addr.s_addr = 0; + } +} + +/* parse_query: parse an incoming query and reply to each query */ +void qr2_parse_queryA(qr2_t qrec, char* query, int len, + struct sockaddr* sender) { + struct qr2_buffer_s buf; + char ptype; + char* reqkey; + char* pos; + gsi_u32 ipverify = 0; + int i; + + buf.len = 0; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_StackTrace, + "qr2_parse_queryA()\r\n"); + + if (qrec == NULL) + qrec = current_rec; + if (query[0] == 0x3B) /* a cdkey query */ + { + if (qrec->cdkeyprocess != NULL) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Forwarding cdkey query onto cdkey sdk\r\n"); + qrec->cdkeyprocess(query, len, sender); + } else { + gsDebugFormat( + GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Received cdkey query but not using qr2-cdkey integration!\r\n"); + } + return; + } + + if (len < 7) + return; // too small to be valid + // check the magic... + if ((uchar)query[0] != QR_MAGIC_1 || (uchar)query[1] != QR_MAGIC_2) + return; + + if (qrec->listed_state > 0) + qrec->listed_state = 0; + + ptype = query[2]; + reqkey = &query[3]; + pos = query + 7; + len -= 7; + + qr_add_packet_header(&buf, ptype, reqkey); + switch (ptype) { + case PACKET_PREQUERY_IP_VERIFY: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Received IP verify challenge request\r\n"); + if (gsi_is_true( + qr2_process_ip_verify(qrec, &buf, (struct sockaddr_in*)sender))) + break; // break so that we send below + else + return; // otherwise return and discard buf + + case PACKET_QUERY: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing query packet\r\n"); + + // When using query challenge option, verify that the client sent a + // PREQUERY_IP_VERIFY + if ((qrec->backendoptions & QR2_OPTION_USE_QUERY_CHALLENGE) == + QR2_OPTION_USE_QUERY_CHALLENGE) { + if (len < 4) + return; // too small for an ip-verify query + + ipverify = ntohl(*(gsi_u32*)pos); + pos += 4; + len -= 4; + + // Has this client verified their IP? (prevent IP spoofing) + if (gsi_is_false(qr2_check_ip_verify(qrec, (struct sockaddr_in*)sender, + ipverify))) { + // Don't send an error. As nice as the debug info would be, + // the incompatible SBs will interpret it as a server response. + return; + } + } + + // qr_process_query now sends packets + qr_process_query(qrec, &buf, (uchar*)pos, len, sender); + return; + case PACKET_CHALLENGE: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing challenge packet\r\n"); + + // Check the instance key (to prove this packet came from the master + for (i = 0; i < REQUEST_KEY_LEN; i++) { + if (reqkey[i] != qrec->instance_key[i]) + return; // not a valid instance key + } + + // calculate the challenge + if (len >= (PUBLIC_ADDR_LEN + 3)) { + unsigned int backendoptions; + + // read options, then public address + sscanf(pos + len - (PUBLIC_ADDR_LEN + 3), "%02x", &backendoptions); + qrec->backendoptions = (gsi_u8)backendoptions; + +#ifdef QR2_DEBUG_FORCE_USE_QUERY_CHALLENGE + qrec->backendoptions = QR2_OPTION_USE_QUERY_CHALLENGE; +#endif + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Notice, + "Received setting options: %d\r\n", qrec->backendoptions); + + if (qrec->pa_callback) + handle_public_address(qrec, pos + len - (PUBLIC_ADDR_LEN + 1)); + else { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, + GSIDebugLevel_Notice, + "Discarding public address (no callback set)\r\n"); + } + } + compute_challenge_response(qrec, &buf, pos, len); + break; + case PACKET_ECHO: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing echo packet\r\n"); + + // now add the echo data + if (len > 32) + len = 32; // max 32 bytes + buf.buffer[0] = PACKET_ECHO_RESPONSE; + memcpy(buf.buffer + buf.len, pos, (size_t)len); + buf.len += len; + break; + + case PACKET_ADDERROR: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, + GSIDebugLevel_WarmError, "Processing adderror packet\r\n"); + + if (qrec->listed_state == -1) + return; // we already got an error message + // verify the instance code + for (i = 0; i < REQUEST_KEY_LEN; i++) { + if (reqkey[i] != qrec->instance_key[i]) + return; // not a valid instance key + } + if (len < 2) + return; // not a valid message + qrec->listed_state = -1; + qrec->adderror_callback((qr2_error_t)*pos, pos + 1, qrec->udata); + return; // we don't need to send anything back for this type of message + case PACKET_CLIENT_MESSAGE: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Notice, + "Processing clientmessage packet\r\n"); + + // verify the instance code + for (i = 0; i < REQUEST_KEY_LEN; i++) { + if (reqkey[i] != qrec->instance_key[i]) + return; // not a valid instance key + } + if (len < 4) // no message key? + return; + buf.buffer[0] = PACKET_CLIENT_MESSAGE_ACK; + // add the msg key + memcpy(buf.buffer + buf.len, pos, (size_t)4); + buf.len += 4; + // see if we've recently gotten this same message, to help avoid dupes + memcpy(&i, pos, (size_t)4); + if (!qr_got_recent_message(qrec, i)) + qr_process_client_message(qrec, pos + 4, len - 4); + // send an ack response + break; + case PACKET_KEEPALIVE: + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Processing keepalive packet\r\n"); + return; // if we get a keep alive, ignore it and return (just used to tell + // us the server knows about us) + default: + return; // not valid type + } + // send the reply + sendto(qrec->hbsock, buf.buffer, buf.len, 0, sender, + sizeof(struct sockaddr_in)); + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sent %d bytes as QR2 query response\r\n", buf.len); + gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump, + buf.buffer, buf.len); +} + +/* send_keepalive: Send a keepalive packet to the hbmaster3 */ +static void send_keepalive(qr2_t qrec) { + struct qr2_buffer_s buf; + buf.len = 0; + qr_add_packet_header(&buf, PACKET_KEEPALIVE, qrec->instance_key); + sendto(qrec->hbsock, buf.buffer, buf.len, 0, + (struct sockaddr*)&(qrec->hbaddr), sizeof(struct sockaddr_in)); + + // set the ka time to now + qrec->lastka = current_time(); + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sent keepalive to master\r\n", buf); +} + +/* send_heartbeat: Sends a heartbeat to the gamemaster, +adds \statechanged\ if statechanged != 0 */ +static void send_heartbeat(qr2_t qrec, int statechanged) { + struct qr2_buffer_s buf; + // int ret; + int i; + char ipkey[20]; + + buf.len = 0; + qr_add_packet_header(&buf, PACKET_HEARTBEAT, qrec->instance_key); + // now we add our special keys + for (i = 0; i < num_local_ips; i++) { + sprintf(ipkey, "localip%d", i); + qr2_buffer_addA(&buf, ipkey); + qr2_buffer_addA(&buf, inet_ntoa(local_ip_list[i])); + } + qr2_buffer_addA(&buf, "localport"); + qr2_buffer_add_int(&buf, qrec->qport); + qr2_buffer_addA(&buf, "natneg"); + qr2_buffer_addA(&buf, qrec->nat_negotiate ? "1" : "0"); + if (statechanged) { + qr2_buffer_addA(&buf, "statechanged"); + qr2_buffer_add_int(&buf, statechanged); + } + qr2_buffer_addA(&buf, "gamename"); + qr2_buffer_addA(&buf, qrec->gamename); + if (qrec->pa_callback) { + qr2_buffer_addA(&buf, "publicip"); + qr2_buffer_add_int(&buf, (int)qrec->publicip); + qr2_buffer_addA(&buf, "publicport"); + qr2_buffer_add_int(&buf, qrec->publicport); + } + + // add the rest of our keys + if (statechanged != 2) // don't need if we are exiting + { + // The hbmaster will crap out if the packet is malformed + // which might happen if the buffer isn't large enough + // So first copy dump the keys into a temporary buffer + struct qr2_buffer_s temp; + memcpy(temp.buffer, buf.buffer, (size_t)buf.len); + temp.len = buf.len; + qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0xFF, NULL, 0xFF, NULL); + + // If we maxxed out the packet, try again using only the server keys + if (AVAILABLE_BUFFER_LEN(&temp) < 1) { + temp.len = buf.len; + qr_build_query_reply(qrec, &temp, 0xFF, NULL, 0, NULL, 0, NULL); + } + // copy temp back into buffer + memcpy(buf.buffer, temp.buffer, (size_t)temp.len); + buf.len = temp.len; + } else { + // PANTS - 2002.6.28 + // add an extra NUL to end the server keys + if (AVAILABLE_BUFFER_LEN(&buf) >= 1) + buf.buffer[buf.len++] = 0; + } + + // ret = (int)sendto(qrec->hbsock, buf.buffer, buf.len, 0, (struct sockaddr + // *)&(qrec->hbaddr), sizeof(struct sockaddr_in)); + sendto(qrec->hbsock, buf.buffer, buf.len, 0, + (struct sockaddr*)&(qrec->hbaddr), sizeof(struct sockaddr_in)); + + // set the ka time and hb time to now + qrec->lastka = qrec->lastheartbeat = current_time(); + + // clear the pending heartbeat request flag + if (statechanged != 0) + qrec->userstatechangerequested = 0; + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_Comment, + "Sent heartbeat to master (size %d)\r\n", buf.len); + gsDebugBinary(GSIDebugCat_QR2, GSIDebugType_Network, GSIDebugLevel_RawDump, + buf.buffer, buf.len); +} + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/qr2/qr2.h b/source/gamespy/qr2/qr2.h new file mode 100644 index 000000000..37384b8b8 --- /dev/null +++ b/source/gamespy/qr2/qr2.h @@ -0,0 +1,441 @@ +#pragma once + +#include "../common/gsCommon.h" + +/********** +qr2regkeys.h contains defines for all of the reserved keys currently available. +***********/ +#include "qr2regkeys.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define qr2_init qr2_initA +#define qr2_init_socket qr2_init_socketA +#define qr2_parse_query qr2_parse_queryA +#define qr2_buffer_add qr2_buffer_addA + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + +// no need to escape strings, more flexible querying, less bandwidth, no need to +// space check buffers + +/******** +ERROR CONSTANTS +--------------- +These constants are returned from qr2_init and the error callback to signal an +error condition +***************/ +typedef enum { + e_qrnoerror, // no error occured + e_qrwsockerror, // a standard socket call failed (exhausted resources?) + e_qrbinderror, // the SDK was unable to find an available port to bind on + e_qrdnserror, // a DNS lookup (for the master server) failed + e_qrconnerror, // the server is behind a nat and does not support negotiation + e_qrnochallengeerror, // no challenge was received from the master - either + // the master is down, or a firewall is blocking UDP + qr2_error_t_count +} qr2_error_t; + +/******** +KEY TYPES +--------------- +Server information is reported in key/value pairs. There are three key types: +key_server - General information about the game in progress +key_player - Information about a specific player +key_team - Information about a specific team +***************/ +typedef enum { key_server, key_player, key_team, key_type_count } qr2_key_type; + +/********* +NUM_PORTS_TO_TRY +---------------- +This value is the maximum number of ports that will be scanned to +find an open query port, starting from the value passed to qr2_init +as the base port. Generally there is no reason to modify this value. +***********/ +#define NUM_PORTS_TO_TRY 100 + +/********* +MAGIC VALUES +---------------- +These values will be used at the start of all QR2 query packets. If you are +processing query data on your game socket, you can use these bytes to determine +if a packet should be forwarded to the QR2 SDK for processing. +***********/ +#define QR_MAGIC_1 0xFE +#define QR_MAGIC_2 0xFD + +/* The app can resolve the master server hostname for this +game itself and store the IP here before calling qr2_init. +For more information, contact devsupport@gamespy.com. */ +extern char qr2_hostname[64 + 8]; + +/*********** +qr2_t +---- +This abstract type is used to instantiate multiple instances of the +Query & Reporting SDK (for example, if you are running multiple servers +in the same process). +For most games, you can ignore this value and pass NULL in to all functions +that require it. A single global instance will be used in this case. +************/ +typedef struct qr2_implementation_s* qr2_t; + +/*********** +qr2_keybuffer_t +--------------- +This structure is used to store a list of keys when enumerating available keys. +Use the qr2_keybuffer_add function to add keys to the list. +************/ +typedef struct qr2_keybuffer_s* qr2_keybuffer_t; + +/*********** +qr2_buffer_t +------------ +This structure stores data that will be sent back to a client in response to +a query. Use the qr2_buffer_add functions to add data to the buffer in your +callbacks. +************/ +typedef struct qr2_buffer_s* qr2_buffer_t; + +typedef struct qr2_ipverify_node_s* qr2_ipverify_node_t; + +/******** +qr2_serverkeycallback_t +------------------- +This is the prototype for one of the callback functions you will need to +provide. The serverkey callback is called when a client requests information +about a specific server key. [keyid] is the key being requested. [outbuf] is the +destination buffer for the value information. Use qr2_buffer_add to report the +value. [userdata] is the pointer that was passed into qr2_init. You can use this +for an object or structure pointer if needed. If you don't have a value for the +provided keyid, you should add a empty ("") string to the buffer. +********/ +typedef void (*qr2_serverkeycallback_t)(int keyid, qr2_buffer_t outbuf, + void* userdata); + +/******** +qr2_playerteamkeycallback_t +------------------- +This is the prototype for two of the callback functions you will need to +provide. The player key callback is called when a client requests information +about a specific key for a specific player. The team key callback is called when +a client requests the value for a team key. [keyid] is the key being requested. +[index] is the 0-based index of the player or team being requested. +[outbuf] is the destination buffer for the value information. Use qr2_buffer_add +to report the value. [userdata] is the pointer that was passed into qr2_init. +You can use this for an object or structure pointer if needed. If you don't have +a value for the provided keyid, you should add a empty ("") string to the +buffer. +********/ +typedef void (*qr2_playerteamkeycallback_t)(int keyid, int index, + qr2_buffer_t outbuf, + void* userdata); + +/******** +qr2_keylistcallback_t +------------------- +This is the prototype for one of the callback functions you will need to +provide. The key list callback is called when the SDK needs to determine all of +the keys you game has values for. [keytype] is the type of keys being requested +(server, player, team). You should only add keys of this type to the keybuffer. +[keybuffer] is the structure that holds the list of keys. Use qr2_keybuffer_add +to add a key to the buffer. [userdata] is the pointer that was passed into +qr2_init. You can use this for an object or structure pointer if needed. +********/ +typedef void (*qr2_keylistcallback_t)(qr2_key_type keytype, + qr2_keybuffer_t keybuffer, + void* userdata); + +/******** +qr2_countcallback_t +------------------- +This is the prototype for one of the callback functions you will need to +provide. The count callback is used by the SDK to get a count of player or teams +on the server. [keytype] should be used to determine whether the player or team +count is being requested (key_player or key_team will be passed) [userdata] is +the pointer that was passed into qr2_init. You can use this for an object or +structure pointer if needed. If your game does not support teams, you can return +0 for the count of teams. +********/ +typedef int (*qr2_countcallback_t)(qr2_key_type keytype, void* userdata); + +/******** +qr2_adderrorcallback_t +------------------- +This is the prototype for one of the callback functions you will need to +provide. The add error callback is called in response to a message from the +master server indicating a problem listing the server. [error] is a code that +can be used to determine the specific listing error. [errmsg] is a +human-readable error string returned from the master server. [userdata] is the +pointer that was passed into qr2_init. You can use this for an object or +structure pointer if needed. The most common error that will be returned is if +the master is unable to list the server due to a firewall or proxy that would +block incoming game packets. +********/ +typedef void (*qr2_adderrorcallback_t)(qr2_error_t error, gsi_char* errmsg, + void* userdata); + +// todo - document +typedef void (*qr2_natnegcallback_t)(int cookie, void* userdata); +typedef void (*qr2_clientmessagecallback_t)(gsi_char* data, int len, + void* userdata); +typedef void (*qr2_publicaddresscallback_t)(unsigned int ip, + unsigned short port, + void* userdata); +typedef void (*qr2_clientconnectedcallback_t)(SOCKET gamesocket, + struct sockaddr_in* remoteaddr, + void* userdata); + +//#if defined(QR2_IP_FILTER) +typedef void (*qr2_denyqr2responsetoipcallback_t)(void* userdata, + unsigned int sender_ip, + int* result); +//#endif //#if defined(QR2_IP_FILTER) + +void qr2_register_natneg_callback(qr2_t qrec, qr2_natnegcallback_t nncallback); +void qr2_register_clientmessage_callback( + qr2_t qrec, qr2_clientmessagecallback_t cmcallback); +void qr2_register_publicaddress_callback( + qr2_t qrec, qr2_publicaddresscallback_t pacallback); +void qr2_register_clientconnected_callback( + qr2_t qrec, qr2_clientconnectedcallback_t cccallback); + +//#if defined(QR2_IP_FILTER) +void qr2_register_denyresponsetoip_callback( + qr2_t qrec, qr2_denyqr2responsetoipcallback_t dertoipcallback); +//#endif //#if defined(QR2_IP_FILTER) + +/***************** +QR2_REGISTER_KEY +-------------------- +Use this function to register custom server, player, and team keys that your +server reports. [keyid] is the ID number you have chosen for this key. The first +NUM_RESERVED_KEYS (50) keys are reserved, all other keyid values up to +MAX_REGISTERED_KEYS (254) are available for your use. [key] is the string name +of the key. Player keys should end in "_" (such as "score_") and team keys +should end in "_t". All custom keys should be registered prior to calling +qr2_init. Reserved keys are already registered and should not be passed to this +function. +*******************/ +void qr2_register_key(int keyid, const gsi_char* key); + +/************ +QR2_INIT +-------- +This function initializes the Query and Reporting SDK and prepares the SDK to +accept incoming queries and send heartbeats to the master server. [qrec] if not +null, will be filled with the qr2_t instance for this server. If you are not +using more than one instance of the Query & Reporting SDK you can pass in NULL +for this value. [ip] is an optional parameter that determines which dotted IP +address to bind to on a multi-homed machine. You can pass NULL to bind to all IP +addresses. If your game networking supports binding to user-specified IPs, you +should make sure the same IP is bound by the Query and Reporting SDK. [baseport] +is the port to accept queries on. If baseport is not available, the Query and +Reporting SDK will scan for an available port in the range of baseport -> +baseport + NUM_PORTS_TO_TRY Optionally, you can pass in 0 to have a port chosen +automatically (makes it harder for debugging/testing). [gamename] is the unique +gamename that you were given [secretkey] is your unique secret key [ispublic] is +1 if the server should send heartbeats to the GameSpy master server and be +publicly listed, If 0, the server will only be available for LAN browsing +[natnegotiate] is 1 if the server supports GameSpy's NAT-negotiation technology +(or another similar technology) which allows hosting behind a NAT. If you do not +support NAT-negotiation (i.e. a 3rd party handshake server), pass 0 to prevent +the server from being listed if it is behind a NAT that cannot be traversed by +outside clients. [qr2_*_callback] are your callback functions, these cannot be +NULL [userdata] is an optional, implementation specific parameter that will be + passed to all callback functions. Use it to store an object or structure + pointer if needed. + +Returns +e_qrnoerror is successful, otherwise one of the qr2_error_t constants above. +************/ +qr2_error_t qr2_init(/*[out]*/ qr2_t* qrec, const gsi_char* ip, int baseport, + const gsi_char* gamename, const gsi_char* secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, void* userdata); + +/************ +QR2_INIT_SOCKET +-------- +This version of qr2_init allows the game to specify the UDP socket to use for +sending heartbeats and query replies. This enables the game and the QR2 SDK to +share a single UDP socket for all networking, which can make hosting games +behind a NAT proxy possible (see the documentation for more information). +You must also use qr2_parse_query to pass in any data received for the QR SDK +on the socket, since the SDK will not try to read any data off the socket +directly. [s] is the UDP socket to use for heartbeats and query replies. It must +be a valid socket and should be bound to a port before calling qr_init_socket. +It can be blocking or non-blocking. [boundport] is the local port that the +socket is bound to. All other parameters are the same as described in qr2_init. +************/ +qr2_error_t qr2_init_socket(/*[out]*/ qr2_t* qrec, SOCKET s, int boundport, + const gsi_char* gamename, + const gsi_char* secret_key, int ispublic, + int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata); + +/******************* +QR2_THINK +------------------- +This function should be called somewhere in your main program loop to +process any pending server queries and send a heartbeat if needed. + +Query replies are very latency sensative, so you should make sure this +function is called at least every 100ms while your game is in progress. +The function has very low overhead and should not cause any performance +problems. +Unless you are using multiple instances of the SDK, you should pass NULL +for qrec. +********************/ +void qr2_think(qr2_t qrec); + +/******************* +QR2_PARSE_QUERY +------------------- +Use only with qr2_init_socket to pass in data that is destined for the Q&R SDK +from your game socket. +You still need to call qr2_think in your main loop, and just call this +function whenever data is received. +Unless you are using multiple instances of the SDK, you should pass NULL +for qrec. +[query] is the packet of query data received from the client. +[len] is the length of the data +[sender] is the address that the query is received from. The QR SDK will reply +directly to that address using the socket provided in qr2_init_socket. +*******************/ +void qr2_parse_query(qr2_t qrec, gsi_char* query, int len, + struct sockaddr* sender); + +/***************** +QR2_SEND_STATECHANGED +-------------------- +This function forces a "statechanged" heartbeat to be sent immediately. +Use it any time you have changed the gamestate of your game to signal the +master to update your status. +Unless you are using multiple instances of the SDK, you should pass NULL +for qrec. +*******************/ +void qr2_send_statechanged(qr2_t qrec); + +/***************** +QR2_SHUTDOWN +------------ +This function closes the sockets created in qr_init and takes care of +any misc. cleanup. You should try to call it when before exiting the server +process. An "exiting" statechanged heartbeat will automatically be sent to +assist in quickly de-listing the server. If you pass in a qrec that was returned +from qr_init, all resources associated with that qrec will be freed. If you +passed NULL into qr_int, you can pass NULL in here as well. +******************/ +void qr2_shutdown(qr2_t qrec); + +/***************** +QR2_KEYBUFFER_ADD +------------ +Use this function to add a registered key to the key buffer when asked to +provide a list of supported keys. +******************/ +gsi_bool qr2_keybuffer_add(qr2_keybuffer_t keybuffer, int keyid); + +/***************** +QR2_BUFFER_ADD / ADD_INT +------------ +These functions are used to add a key's value to the outgoing buffer when +requested in a callback function. +******************/ +gsi_bool qr2_buffer_add(qr2_buffer_t outbuf, const gsi_char* value); +gsi_bool qr2_buffer_add_int(qr2_buffer_t outbuf, int value); + +/* for CDKey SDK integration */ +#define REQUEST_KEY_LEN 4 +#define RECENT_CLIENT_MESSAGES_TO_TRACK 10 +typedef void (*cdkey_process_t)(char* buf, int len, struct sockaddr* fromaddr); + +/* ip verification / spoof prevention */ +#define QR2_IPVERIFY_TIMEOUT 4000 // timeout after 4 seconds round trip time +#define QR2_IPVERIFY_ARRAY_SIZE \ + 200 // allowed outstanding queryies in those 4 seconds +#define QR2_IPVERIFY_MAXDUPLICATES 5 // allow maximum of 5 requests per IP/PORT +struct qr2_ipverify_info_s { + struct sockaddr_in addr; // addr = 0 when not in use + gsi_u32 challenge; + gsi_time createtime; +}; + +struct qr2_implementation_s { + SOCKET hbsock; // 0x00..0x04 + char gamename[64]; // 0x04..0x44 + char secret_key[64]; // 0x44..0x84 + char instance_key[REQUEST_KEY_LEN]; // 0x84..0x8c + qr2_serverkeycallback_t server_key_callback; // 0x8c..0x90 + qr2_playerteamkeycallback_t player_key_callback; + qr2_playerteamkeycallback_t team_key_callback; + qr2_keylistcallback_t key_list_callback; + qr2_countcallback_t playerteam_count_callback; + qr2_adderrorcallback_t adderror_callback; + qr2_natnegcallback_t nn_callback; // 0xa0 + qr2_clientmessagecallback_t cm_callback; // 0xa4 + qr2_publicaddresscallback_t pa_callback; // 0xa8 + gsi_time lastheartbeat; // 0xac + gsi_time lastka; // 0xb0 + int userstatechangerequested; // 0xb4 + int listed_state; // 0xb8 + int ispublic; // 0xbc + int qport; // 0xc0 + int read_socket; // 0xc4 + int nat_negotiate; // 0xc8 + struct sockaddr_in hbaddr; + cdkey_process_t cdkeyprocess; + int client_message_keys[RECENT_CLIENT_MESSAGES_TO_TRACK]; + int cur_message_key; + unsigned int publicip; + unsigned short publicport; + void* udata; + + gsi_u8 backendoptions; // received from server inside challenge packet + struct qr2_ipverify_info_s ipverify[QR2_IPVERIFY_ARRAY_SIZE]; +}; + +// These need to be defined, even in GSI_UNICODE MODE +void qr2_parse_queryA(qr2_t qrec, char* query, int len, + struct sockaddr* sender); +gsi_bool qr2_buffer_addA(qr2_buffer_t outbuf, const char* value); +qr2_error_t qr2_initA(/*[out]*/ qr2_t* qrec, const char* ip, int baseport, + const char* gamename, const char* secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, void* userdata); +qr2_error_t qr2_init_socketA(/*[out]*/ qr2_t* qrec, SOCKET s, int boundport, + const char* gamename, const char* secret_key, + int ispublic, int natnegotiate, + qr2_serverkeycallback_t server_key_callback, + qr2_playerteamkeycallback_t player_key_callback, + qr2_playerteamkeycallback_t team_key_callback, + qr2_keylistcallback_t key_list_callback, + qr2_countcallback_t playerteam_count_callback, + qr2_adderrorcallback_t adderror_callback, + void* userdata); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/qr2/qr2regkeys.c b/source/gamespy/qr2/qr2regkeys.c new file mode 100644 index 000000000..2fb9b9861 --- /dev/null +++ b/source/gamespy/qr2/qr2regkeys.c @@ -0,0 +1,78 @@ + +#include "qr2regkeys.h" + +#include "../common/gsDebug.h" +#include "../common/gsStringUtil.h" + +#ifdef __MWERKS__ // CodeWarrior requires prototypes +void qr2_register_keyW(int keyid, const unsigned short* key); +void qr2_register_keyA(int keyid, const char* key); +#endif + +const char* qr2_registered_key_list[MAX_REGISTERED_KEYS] = { + "", // 0 is reserved + "hostname", // 1 + "gamename", // 2 + "gamever", // 3 + "hostport", // 4 + "mapname", // 5 + "gametype", // 6 + "gamevariant", // 7 + "numplayers", // 8 + "numteams", // 9 + "maxplayers", // 10 + "gamemode", // 11 + "teamplay", // 12 + "fraglimit", // 13 + "teamfraglimit", // 14 + "timeelapsed", // 15 + "timelimit", // 16 + "roundtime", // 17 + "roundelapsed", // 18 + "password", // 19 + "groupid", // 20 + "player_", // 21 + "score_", // 22 + "skill_", // 23 + "ping_", // 24 + "team_", // 25 + "deaths_", // 26 + "pid_", // 27 + "team_t", // 28 + "score_t", // 29 + "nn_groupid", // 30 + + // Query From Master Only keys + "country", // 31 + "region" // 32 +}; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +gsi_bool qr2_internal_is_master_only_key(const char* keyname) { + if (strcmp(keyname, qr2_registered_key_list[COUNTRY_KEY]) == 0 || + strcmp(keyname, qr2_registered_key_list[REGION_KEY]) == 0) + return gsi_true; + + return gsi_false; +} + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void qr2_register_keyA(int keyid, const char* key) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_StackTrace, + "qr2_register_keyA()\r\n"); + + // Verify the key range + if (keyid < NUM_RESERVED_KEYS || keyid > MAX_REGISTERED_KEYS) { + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_WarmError, + "Attempted to register invalid key %d - %s\r\n", keyid, key); + return; + } + + gsDebugFormat(GSIDebugCat_QR2, GSIDebugType_Misc, GSIDebugLevel_Comment, + "Registered key %d - %s\r\n", keyid, key); + + qr2_registered_key_list[keyid] = key; +} diff --git a/source/gamespy/qr2/qr2regkeys.h b/source/gamespy/qr2/qr2regkeys.h new file mode 100644 index 000000000..09081f6ba --- /dev/null +++ b/source/gamespy/qr2/qr2regkeys.h @@ -0,0 +1,64 @@ +#pragma once + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MAX_REGISTERED_KEYS 254 +#define NUM_RESERVED_KEYS 50 + +#define HOSTNAME_KEY 1 +#define GAMENAME_KEY 2 +#define GAMEVER_KEY 3 +#define HOSTPORT_KEY 4 +#define MAPNAME_KEY 5 +#define GAMETYPE_KEY 6 +#define GAMEVARIANT_KEY 7 +#define NUMPLAYERS_KEY 8 +#define NUMTEAMS_KEY 9 +#define MAXPLAYERS_KEY 10 +#define GAMEMODE_KEY 11 +#define TEAMPLAY_KEY 12 +#define FRAGLIMIT_KEY 13 +#define TEAMFRAGLIMIT_KEY 14 +#define TIMEELAPSED_KEY 15 +#define TIMELIMIT_KEY 16 +#define ROUNDTIME_KEY 17 +#define ROUNDELAPSED_KEY 18 +#define PASSWORD_KEY 19 +#define GROUPID_KEY 20 +#define PLAYER__KEY 21 +#define SCORE__KEY 22 +#define SKILL__KEY 23 +#define PING__KEY 24 +#define TEAM__KEY 25 +#define DEATHS__KEY 26 +#define PID__KEY 27 +#define TEAM_T_KEY 28 +#define SCORE_T_KEY 29 +#define NN_GROUP_ID_KEY 30 + +// Query-From-Master-Only keys +// - these two values are retrieved only from the master server so we need to +// make +// sure not to overwrite them when querying servers directly +#define COUNTRY_KEY 31 +#define REGION_KEY 32 + +#define qr2_register_key qr2_register_keyA + +extern const char* qr2_registered_key_list[]; +void qr2_register_key(int keyid, const gsi_char* key); + +// internal function used by ServerBrowser to check if a key is +// Query-Master-Only +gsi_bool qr2_internal_is_master_only_key(const char* keyname); + +// Always define for direct access +void qr2_register_keyA(int keyid, const char* key); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/sake/sake.h b/source/gamespy/sake/sake.h new file mode 100644 index 000000000..a054717ea --- /dev/null +++ b/source/gamespy/sake/sake.h @@ -0,0 +1,350 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "../common/gsCommon.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#ifndef SAKE_CALL + #define SAKE_CALL +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// URL for sake webservice +#define SAKE_MAX_URL_LENGTH 128 +extern char sakeiSoapUrl[SAKE_MAX_URL_LENGTH]; + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// General +typedef struct SAKEInternal *SAKE; + +typedef enum +{ + SAKEStartupResult_SUCCESS, + SAKEStartupResult_NOT_AVAILABLE, + SAKEStartupResult_CORE_SHUTDOWN, + SAKEStartupResult_OUT_OF_MEMORY +} SAKEStartupResult; + +SAKEStartupResult SAKE_CALL sakeStartup(SAKE *sakePtr); +void SAKE_CALL sakeShutdown(SAKE sake); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Authentication +void SAKE_CALL sakeSetGame(SAKE sake, const gsi_char *gameName, int gameId, const gsi_char *secretKey); +void SAKE_CALL sakeSetProfile(SAKE sake, int profileId, const char *loginTicket); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Fields +typedef enum +{ + SAKEFieldType_BYTE, + SAKEFieldType_SHORT, + SAKEFieldType_INT, + SAKEFieldType_FLOAT, + SAKEFieldType_ASCII_STRING, + SAKEFieldType_UNICODE_STRING, + SAKEFieldType_BOOLEAN, + SAKEFieldType_DATE_AND_TIME, + SAKEFieldType_BINARY_DATA, + SAKEFieldType_INT64, + SAKEFieldType_NUM_FIELD_TYPES +} SAKEFieldType; + +typedef struct +{ + gsi_u8 *mValue; + int mLength; +} SAKEBinaryData; + +typedef union +{ + gsi_u8 mByte; + gsi_i16 mShort; + gsi_i32 mInt; + float mFloat; + char *mAsciiString; + gsi_u16 *mUnicodeString; + gsi_bool mBoolean; + time_t mDateAndTime; + SAKEBinaryData mBinaryData; + gsi_i64 mInt64; +} SAKEValue; + +typedef struct +{ + char *mName; + SAKEFieldType mType; + SAKEValue mValue; +} SAKEField; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Requests +typedef struct SAKERequestInternal *SAKERequest; + +typedef enum +{ + SAKEStartRequestResult_SUCCESS, + SAKEStartRequestResult_NOT_AUTHENTICATED, + SAKEStartRequestResult_OUT_OF_MEMORY, + SAKEStartRequestResult_BAD_INPUT, + SAKEStartRequestResult_BAD_TABLEID, + SAKEStartRequestResult_BAD_FIELDS, + SAKEStartRequestResult_BAD_NUM_FIELDS, + SAKEStartRequestResult_BAD_FIELD_NAME, + SAKEStartRequestResult_BAD_FIELD_TYPE, + SAKEStartRequestResult_BAD_FIELD_VALUE, + SAKEStartRequestResult_BAD_OFFSET, + SAKEStartRequestResult_BAD_MAX, + SAKEStartRequestResult_BAD_RECORDIDS, + SAKEStartRequestResult_BAD_NUM_RECORDIDS, + SAKEStartRequestResult_UNKNOWN_ERROR +} SAKEStartRequestResult; + +typedef enum +{ + SAKERequestResult_SUCCESS, + SAKERequestResult_SECRET_KEY_INVALID, + SAKERequestResult_SERVICE_DISABLED, + SAKERequestResult_CONNECTION_TIMEOUT, + SAKERequestResult_CONNECTION_ERROR, + SAKERequestResult_MALFORMED_RESPONSE, + SAKERequestResult_OUT_OF_MEMORY, + SAKERequestResult_DATABASE_UNAVAILABLE, + SAKERequestResult_LOGIN_TICKET_INVALID, + SAKERequestResult_LOGIN_TICKET_EXPIRED, + SAKERequestResult_TABLE_NOT_FOUND, + SAKERequestResult_RECORD_NOT_FOUND, + SAKERequestResult_FIELD_NOT_FOUND, + SAKERequestResult_FIELD_TYPE_INVALID, + SAKERequestResult_NO_PERMISSION, + SAKERequestResult_RECORD_LIMIT_REACHED, + SAKERequestResult_ALREADY_RATED, + SAKERequestResult_NOT_RATEABLE, + SAKERequestResult_NOT_OWNED, + SAKERequestResult_FILTER_INVALID, + SAKERequestResult_SORT_INVALID, + SAKERequestResult_TARGET_FILTER_INVALID, + SAKERequestResult_UNKNOWN_ERROR +} SAKERequestResult; + +typedef void (*SAKERequestCallback)(SAKE sake, SAKERequest request, SAKERequestResult result, void *inputData, void *outputData, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get start request result +SAKEStartRequestResult SAKE_CALL sakeGetStartRequestResult(SAKE sake); + +/////////////////////////////////////////////////////////////////////////////// +// create record +typedef struct +{ + char *mTableId; + SAKEField *mFields; + int mNumFields; +} SAKECreateRecordInput; +typedef struct +{ + int mRecordId; +} SAKECreateRecordOutput; +SAKERequest SAKE_CALL sakeCreateRecord(SAKE sake, SAKECreateRecordInput *input, SAKERequestCallback callback, void *userData); + +//////////////////////////// /////////////////////////////////////////////////// +// update record +typedef struct +{ + char *mTableId; + int mRecordId; + SAKEField *mFields; + int mNumFields; +} SAKEUpdateRecordInput; +SAKERequest SAKE_CALL sakeUpdateRecord(SAKE sake, SAKEUpdateRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// delete record +typedef struct +{ + char *mTableId; + int mRecordId; +} SAKEDeleteRecordInput; +SAKERequest SAKE_CALL sakeDeleteRecord(SAKE sake, SAKEDeleteRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// search for records +typedef struct +{ + char *mTableId; + char **mFieldNames; + int mNumFields; + gsi_char *mFilter; + char *mSort; + int mOffset; + int mMaxRecords; + gsi_char *mTargetRecordFilter; + int mSurroundingRecordsCount; + int *mOwnerIds; + int mNumOwnerIds; + gsi_bool mCacheFlag; +} SAKESearchForRecordsInput; +typedef struct +{ + int mNumRecords; + SAKEField **mRecords; +} SAKESearchForRecordsOutput; +SAKERequest SAKE_CALL sakeSearchForRecords(SAKE sake, SAKESearchForRecordsInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get my records +typedef struct +{ + char *mTableId; + char **mFieldNames; + int mNumFields; +} SAKEGetMyRecordsInput; +typedef struct +{ + int mNumRecords; + SAKEField **mRecords; +} SAKEGetMyRecordsOutput; +SAKERequest SAKE_CALL sakeGetMyRecords(SAKE sake, SAKEGetMyRecordsInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get specific records +typedef struct +{ + char *mTableId; + int *mRecordIds; + int mNumRecordIds; + char **mFieldNames; + int mNumFields; +} SAKEGetSpecificRecordsInput; +typedef struct +{ + int mNumRecords; + SAKEField **mRecords; +} SAKEGetSpecificRecordsOutput; +SAKERequest SAKE_CALL sakeGetSpecificRecords(SAKE sake, SAKEGetSpecificRecordsInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get random record +typedef struct +{ + char *mTableId; + char **mFieldNames; + int mNumFields; + gsi_char *mFilter; +} SAKEGetRandomRecordInput; +typedef struct +{ + SAKEField *mRecord; +} SAKEGetRandomRecordOutput; +SAKERequest SAKE_CALL sakeGetRandomRecord(SAKE sake, SAKEGetRandomRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// rate record +typedef struct +{ + char *mTableId; + int mRecordId; + gsi_u8 mRating; +} SAKERateRecordInput; +typedef struct +{ + int mNumRatings; + float mAverageRating; +} SAKERateRecordOutput; +SAKERequest SAKE_CALL sakeRateRecord(SAKE sake, SAKERateRecordInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get record limit +typedef struct +{ + char *mTableId; +} SAKEGetRecordLimitInput; +typedef struct +{ + int mLimitPerOwner; + int mNumOwned; +} SAKEGetRecordLimitOutput; +SAKERequest SAKE_CALL sakeGetRecordLimit(SAKE sake, SAKEGetRecordLimitInput *input, SAKERequestCallback callback, void *userData); + +/////////////////////////////////////////////////////////////////////////////// +// get record count +typedef struct +{ + char *mTableId; + gsi_char *mFilter; + gsi_bool mCacheFlag; +} SAKEGetRecordCountInput; +typedef struct +{ + int mCount; +} SAKEGetRecordCountOutput; +SAKERequest SAKE_CALL sakeGetRecordCount(SAKE sake, SAKEGetRecordCountInput *input, SAKERequestCallback callback, void *userData); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// read request utility +SAKEField * SAKE_CALL sakeGetFieldByName(const char *name, SAKEField *fields, int numFields); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Files + +#define SAKE_FILE_RESULT_HEADER "Sake-File-Result:" +#define SAKE_FILE_ID_HEADER "Sake-File-Id:" + +// Sake-File-Result from the HTTP response header +typedef enum +{ + SAKEFileResult_SUCCESS = 0, + SAKEFileResult_BAD_HTTP_METHOD = 1, + SAKEFileResult_BAD_FILE_COUNT = 2, + SAKEFileResult_MISSING_PARAMETER = 3, + SAKEFileResult_FILE_NOT_FOUND = 4, + SAKEFileResult_FILE_TOO_LARGE = 5, + SAKEFileResult_SERVER_ERROR = 6, + SAKEFileResult_UNKNOWN_ERROR +} SAKEFileResult; + +gsi_bool SAKE_CALL sakeSetFileDownloadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]); +gsi_bool SAKE_CALL sakeSetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]); + +gsi_bool SAKE_CALL sakeGetFileDownloadURL(SAKE sake, int fileId, gsi_char url[SAKE_MAX_URL_LENGTH]); +gsi_bool SAKE_CALL sakeGetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]); + +gsi_bool SAKE_CALL sakeGetFileResultFromHeaders(const char *headers, SAKEFileResult *result); +gsi_bool SAKE_CALL sakeGetFileIdFromHeaders(const char *headers, int *fileId); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/sake/sakeMain.c b/source/gamespy/sake/sakeMain.c new file mode 100644 index 000000000..75171e032 --- /dev/null +++ b/source/gamespy/sake/sakeMain.c @@ -0,0 +1,407 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeMain.h" +#include "sakeRequest.h" +#include "../common/gsAvailable.h" +#include "../common/gsCore.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// General + +gsi_char gSakeUploadUrlOverride[SAKE_MAX_URL_LENGTH]; +gsi_char gSakeDownloadUrlOverride[SAKE_MAX_URL_LENGTH]; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartupResult SAKE_CALL sakeStartup(SAKE * sakePtr) +{ + SAKE sake; + + GS_ASSERT(sakePtr); + + // check for availability + if(__GSIACResult != GSIACAvailable) + return SAKEStartupResult_NOT_AVAILABLE; + + // check that the core is initialized + if(gsCoreIsShutdown()) + return SAKEStartupResult_CORE_SHUTDOWN; + + // allocate the sake object + sake = (SAKE)gsimalloc(sizeof(SAKEInternal)); + if(sake == NULL) + return SAKEStartupResult_OUT_OF_MEMORY; + + // init the sake object + memset(sake, 0, sizeof(SAKEInternal)); + + // store the object in the user pointer + *sakePtr = sake; + + return SAKEStartupResult_SUCCESS; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SAKE_CALL sakeShutdown(SAKE sake) +{ + GS_ASSERT(sake); + + //TODO: ensure that there are no pending operations + // that might reference this object + + // free the struct + gsifree(sake); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Authentication + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SAKE_CALL sakeSetGame(SAKE sake, const gsi_char * gameName, int gameId, const gsi_char *secretKey) +{ + GS_ASSERT(sake); + GS_ASSERT(gameName && (_tcslen(gameName) <= SAKEI_GAME_NAME_LENGTH)); + GS_ASSERT(gameId >= 0); + GS_ASSERT(secretKey && (_tcslen(secretKey) <= SAKEI_SECRET_KEY_LENGTH)); + + strcpy(sake->mGameName, gameName); + strcpy(sake->mSecretKey, secretKey); + + sake->mGameId = gameId; + sake->mIsGameAuthenticated = gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +void SAKE_CALL sakeSetProfile(SAKE sake, int profileId, const char *loginTicket) +{ + GS_ASSERT(sake); + GS_ASSERT(loginTicket); + GS_ASSERT(strlen(loginTicket) == SAKEI_LOGIN_TICKET_LENGTH); + + sake->mProfileId = profileId; + memcpy(sake->mLoginTicket, loginTicket, SAKEI_LOGIN_TICKET_LENGTH + 1); + sake->mIsProfileAuthenticated = gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Requests + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartRequestResult SAKE_CALL sakeGetStartRequestResult(SAKE sake) +{ + GS_ASSERT(sake); + + return sake->mStartRequestResult; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static inline SAKERequest SAKE_CALL sakeiRunRequest(SAKE sake, void *input, + SAKERequestCallback callback, void *userData, + SAKEIRequestType type, + SAKEStartRequestResult (*startRequestFunc)(SAKERequest request)) +{ + SAKERequest request; + + GS_ASSERT(sake); + + request = sakeiInitRequest(sake, type, input, callback, userData); + if(!request) + return NULL; + + sake->mStartRequestResult = startRequestFunc(request); + if(sake->mStartRequestResult != SAKEStartRequestResult_SUCCESS) + { + sakeiFreeRequest(request); + return NULL; + } + + return request; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeCreateRecord(SAKE sake, SAKECreateRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_CREATE_RECORD, sakeiStartCreateRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeUpdateRecord(SAKE sake, SAKEUpdateRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_UPDATE_RECORD, sakeiStartUpdateRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/* +SAKERequest SAKE_CALL sakeDeleteRecord(SAKE sake, SAKEDeleteRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_DELETE_RECORD, sakeiStartDeleteRecordRequest); +} +*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeSearchForRecords(SAKE sake, SAKESearchForRecordsInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_SEARCH_FOR_RECORDS, sakeiStartSearchForRecordsRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetMyRecords(SAKE sake, SAKEGetMyRecordsInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_MY_RECORDS, sakeiStartGetMyRecordsRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +/* +SAKERequest SAKE_CALL sakeGetSpecificRecords(SAKE sake, SAKEGetSpecificRecordsInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_SPECIFIC_RECORDS, sakeiStartGetSpecificRecordsRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetRandomRecord(SAKE sake, SAKEGetRandomRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_RANDOM_RECORD, sakeiStartGetRandomRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeRateRecord(SAKE sake, SAKERateRecordInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_RATE_RECORD, sakeiStartRateRecordRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetRecordLimit(SAKE sake, SAKEGetRecordLimitInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_RECORD_LIMIT, sakeiStartGetRecordLimitRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeGetRecordCount(SAKE sake, SAKEGetRecordCountInput *input, SAKERequestCallback callback, void *userData) +{ + return sakeiRunRequest(sake, input, callback, userData, SAKEIRequestType_GET_RECORD_COUNT, sakeiStartGetRecordCountRequest); +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// read request utility +SAKEField * SAKE_CALL sakeGetFieldByName(const char *name, SAKEField *fields, int numFields) +{ + int i; + + GS_ASSERT(name); + GS_ASSERT(fields); + GS_ASSERT(numFields >= 0); + + for(i = 0 ; i < numFields ; i++) + { + if(strcmp(fields[i].mName, name) == 0) + return &fields[i]; + } + + return NULL; +} +*/ + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Files + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Set the URL format to be used by sakeGetFileDownloadUrl +gsi_bool SAKE_CALL sakeSetFileDownloadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + GS_ASSERT(sake); + GS_ASSERT(url); + + if(!sake || !url) + return gsi_false; + + _tcscpy(gSakeDownloadUrlOverride, url); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileDownloadURL(SAKE sake, int fileId, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + int rcode; + + GS_ASSERT(sake); + GS_ASSERT(fileId != 0); + GS_ASSERT(url); + GS_ASSERT(sake->mIsGameAuthenticated); + GS_ASSERT(sake->mIsProfileAuthenticated); + + if(!sake || !url || !sake->mIsGameAuthenticated || !sake->mIsProfileAuthenticated) + return gsi_false; + + if (gSakeDownloadUrlOverride[0] != '\0') + { + // modification, injected fileId here. + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("%s?fileid=%d&gameid=%d&pid=%d"), + gSakeDownloadUrlOverride, fileId, sake->mGameId, sake->mProfileId); + } + else + { + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("http://%s.sake.%s/SakeFileServer/download.aspx?fileid=%d&gameid=%d&pid=%d"), + sake->mGameName, GSI_DOMAIN_NAME, fileId, sake->mGameId, sake->mProfileId); + } + + if(rcode < 0) + return gsi_false; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeSetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + GS_ASSERT(sake); + GS_ASSERT(url); + + if(!sake || !url) + return gsi_false; + + _tcscpy(gSakeUploadUrlOverride, url); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileUploadURL(SAKE sake, gsi_char url[SAKE_MAX_URL_LENGTH]) +{ + int rcode; + + GS_ASSERT(sake); + GS_ASSERT(url); + GS_ASSERT(sake->mIsGameAuthenticated); + GS_ASSERT(sake->mIsProfileAuthenticated); + + if(!sake || !url || !sake->mIsGameAuthenticated || !sake->mIsProfileAuthenticated) + return gsi_false; + + if (gSakeUploadUrlOverride[0] != '\0') + { + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("%s?gameid=%d&pid=%d"), + gSakeUploadUrlOverride, sake->mGameId, sake->mProfileId); + } + else + { + rcode = _tsnprintf(url, SAKE_MAX_URL_LENGTH, _T("http://%s.sake.%s/SakeFileServer/upload.aspx?gameid=%d&pid=%d"), + sake->mGameName, GSI_DOMAIN_NAME, sake->mGameId, sake->mProfileId); + } + + if(rcode < 0) + return gsi_false; + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static SAKEFileResult SAKE_CALL sakeiParseFileResult(int resultCode) +{ + if(resultCode >= SAKEFileResult_UNKNOWN_ERROR) + return SAKEFileResult_UNKNOWN_ERROR; + return (SAKEFileResult)resultCode; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +static gsi_bool SAKE_CALL sakeiGetHeaderValueInt(const char *headers, const char *headerName, int *value) +{ + const char * header; + int rcode; + + GS_ASSERT(headers); + GS_ASSERT(headerName); + GS_ASSERT(value); +#ifdef _DEBUG + // headerName must include the trailing colon + GS_ASSERT(headerName[strlen(headerName) - 1] == ':'); +#endif + + // find this header in the list of headers + header = strstr(headers, headerName); + if(header) + { + // skip the header name + header += strlen(headerName); + + // scan in the result + rcode = sscanf(header, " %d", value); + if(rcode == 1) + return gsi_true; + } + + return gsi_false; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileResultFromHeaders(const char *headers, SAKEFileResult *result) +{ + int resultCode; + gsi_bool foundResultCode; + + foundResultCode = sakeiGetHeaderValueInt(headers, SAKE_FILE_RESULT_HEADER, &resultCode); + + if(gsi_is_false(foundResultCode)) + return gsi_false; + + *result = sakeiParseFileResult(resultCode); + return gsi_true; +} + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +gsi_bool SAKE_CALL sakeGetFileIdFromHeaders(const char *headers, int *fileId) +{ + return sakeiGetHeaderValueInt(headers, SAKE_FILE_ID_HEADER, fileId); +} diff --git a/source/gamespy/sake/sakeMain.h b/source/gamespy/sake/sakeMain.h new file mode 100644 index 000000000..1a37a400a --- /dev/null +++ b/source/gamespy/sake/sakeMain.h @@ -0,0 +1,49 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sake.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define SAKEI_GAME_NAME_LENGTH 15 +#define SAKEI_SECRET_KEY_LENGTH 8 +#define SAKEI_LOGIN_TICKET_LENGTH 24 + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct SAKEInternal +{ + gsi_bool mIsGameAuthenticated; + char mGameName[SAKEI_GAME_NAME_LENGTH + 1]; + int mGameId; + char mSecretKey[SAKEI_SECRET_KEY_LENGTH + 1]; + + gsi_bool mIsProfileAuthenticated; + int mProfileId; + char mLoginTicket[SAKEI_LOGIN_TICKET_LENGTH + 1]; + + SAKEStartRequestResult mStartRequestResult; +} SAKEInternal; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/sake/sakeRequest.h b/source/gamespy/sake/sakeRequest.h new file mode 100644 index 000000000..f86203ff2 --- /dev/null +++ b/source/gamespy/sake/sakeRequest.h @@ -0,0 +1,89 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeMain.h" +#include "sakeRequestInternal.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define GSI_SAKE_SERVICE_NAMESPACE_COUNT 1 +#define GSI_SAKE_SERVICE_NAMESPACE "ns1" +#define GSI_SAKE_SERVICE_NAMESPACE_URL "http://gamespy.net/sake" +extern const char * GSI_SAKE_SERVICE_NAMESPACES[GSI_SAKE_SERVICE_NAMESPACE_COUNT]; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef enum +{ + SAKEIRequestType_CREATE_RECORD, + SAKEIRequestType_UPDATE_RECORD, + SAKEIRequestType_DELETE_RECORD, + SAKEIRequestType_SEARCH_FOR_RECORDS, + SAKEIRequestType_GET_MY_RECORDS, + SAKEIRequestType_GET_SPECIFIC_RECORDS, + SAKEIRequestType_GET_RANDOM_RECORD, + SAKEIRequestType_RATE_RECORD, + SAKEIRequestType_GET_RECORD_LIMIT, + SAKEIRequestType_GET_RECORD_COUNT +} SAKEIRequestType; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct SAKERequestInternal +{ + SAKE mSake; + SAKEIRequestType mType; + void *mInput; + void *mOutput; + SAKERequestCallback mCallback; + void *mUserData; + GSXmlStreamWriter mSoapRequest; + GSXmlStreamWriter mSoapResponse; + SAKEIRequestInfo *mInfo; +} SAKERequestInternal; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKERequest SAKE_CALL sakeiInitRequest(SAKE sake, SAKEIRequestType type, void *input, SAKERequestCallback callback, void *userData); +void SAKE_CALL sakeiFreeRequest(SAKERequest request); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartRequestResult SAKE_CALL sakeiStartCreateRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartUpdateRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartDeleteRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartSearchForRecordsRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetMyRecordsRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetSpecificRecordsRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetRandomRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartRateRecordRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetRecordLimitRequest(SAKERequest request); +SAKEStartRequestResult SAKE_CALL sakeiStartGetRecordCountRequest(SAKERequest request); + + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/sake/sakeRequestInternal.h b/source/gamespy/sake/sakeRequestInternal.h new file mode 100644 index 000000000..1577fa3c2 --- /dev/null +++ b/source/gamespy/sake/sakeRequestInternal.h @@ -0,0 +1,63 @@ +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#pragma once + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#include "sakeMain.h" +#include "../common/gsSoap.h" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +extern "C" { +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#define SAKEI_REQUEST_SAFE_MALLOC(dest, type) SAKEI_REQUEST_SAFE_MALLOC_ARRAY(dest, type, 1) +#define SAKEI_REQUEST_SAFE_MALLOC_ARRAY(dest, type, num) {\ + dest = (type*)gsimalloc(sizeof(type)*num); /*malloc*/ \ + if(!dest) goto out_of_mem_cleanup; /*check*/ \ + memset(dest, 0, sizeof(type)*num); } /*zero*/ + +#define SAKEI_FUNC_NAME_STRINGS(func) func,\ + "SOAPAction: \"http://gamespy.net/sake/" func "\"",\ + func "Response",\ + func "Result" + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +typedef struct +{ + size_t mSakeOutputSize; + const char *mFuncName; + const char *mSoapAction; + const char *mResponseTag; + const char *mResultTag; + + SAKEStartRequestResult (*mValidateInputFunc)(SAKERequest request); + SAKEStartRequestResult (*mFillSoapRequestFunc)(SAKERequest request); + SAKERequestResult (*mProcessSoapResponseFunc)(SAKERequest request); + void (*mFreeDataFunc)(SAKERequest request); +} SAKEIRequestInfo; + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +SAKEStartRequestResult SAKE_CALL sakeiStartRequest(SAKERequest request, SAKEIRequestInfo * info); + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +#if defined(__cplusplus) +} // extern "C" +#endif + + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// diff --git a/source/gamespy/serverbrowsing/sb_ascii.h b/source/gamespy/serverbrowsing/sb_ascii.h new file mode 100644 index 000000000..8263d5011 --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_ascii.h @@ -0,0 +1,197 @@ +/****** +GameSpy Server Browsing SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy Server Browsing SDK documentation for more + information + +******/ + +// PROTOTYPES FOR ASCII VERSIONS +// This is required to silence CodeWarrior warnings about functions not having a +// prototype + +#pragma once + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* +ServerBrowserNew +---------------- +Creates and returns a new (empty) ServerBrowser object. +Returns NULL if an allocation error occurs. + +queryForGamename - The gamename you are querying for +queryFromGamename - The gamename you are querying from - generally the same as +queryForGamename queryFromKey - Secret key that corresponds to the +queryFromGamename queryFromVersion - A game-specific version identifier (pass 0 +unless told otherwise) maxConcUpdates - Max number of concurent updates (10-15 +for modem users, 20-30 for high-bandwidth) queryVersion - Query protocol to use. +Use QVERSION_GOA for DeveloperSpec/Query&Reporting1 games, and QVERSION_QR2 for +games that use Query & Reporting 2 callback - The function that will be called +with list updates instance - User-defined instance data (e.g. structure or +object pointer) */ +ServerBrowser ServerBrowserNewA(const char* queryForGamename, + const char* queryFromGamename, + const char* queryFromKey, int queryFromVersion, + int maxConcUpdates, int queryVersion, + SBBool lanBrowse, + ServerBrowserCallback callback, void* instance); + +/* ServerBrowserUpdate +------------------- +Starts an update by downloading a list of servers from the master server, then +querying them. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be +called for processing and querying to occur If SBFalse, the function will not +return until the initial list of servers has been completely updated +disconnectOnComplete - If SBTrue, the connection to the master server will be +disconnected immediately after the list is downloaded. If SBFalse, the +connection will be left open for additional data queries, and can be closed via +ServerBrowserDisconnect basicFields - This array of registered QR2 keys is used +to determine the fields requested from servers during the initial "basic" +update. Only server keys listed in this array will be returned for servers. +numBasicFields - The number of fields in the basicFields array +serverFilter - SQL Filter string that will be applied on the master server to +limit the list of servers returned. All server keys are available for filtering +on the master server, as well as the master-defined "country" and "region" keys. + Standard SQL syntax should be used. + +ServerBrowserLimitUpdate +------------------------ +Identical to ServerBrowserUpdate, except that the number of servers returned can +be limited maxServers - Maximum number of servers to be returned +*/ +SBError ServerBrowserUpdateA(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, const char* serverFilter); +SBError ServerBrowserLimitUpdateA(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, const char* serverFilter, + int maxServers); + +/* ServerBrowserAuxUpdateIP +------------------- +Manually updates a server given an IP address and query port. Use to manually +add servers to the list when you just have an IP and port for them. + +sb - The server browser object to add the server to +ip - The dotted IP address of the server e.g. "1.2.3.4" +port - The query port of the server +viaMaster - If SBTrue, information about the server will be retrieved from the +master server instead of attempting to query the server directly. If a +connection to the master server does not exist, it will be made to kept open +afterwards. If SBFalse, the server will be contacted directly for information. +async - If SBTrue, the update will be initiated, and ServerListThink must be +called for processing and querying to occur If SBFalse, the function will not +return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be +retrieved If SBFalse, only the keys specified in the basicFields array of the +ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateIPA(ServerBrowser sb, const char* ip, + unsigned short port, SBBool viaMaster, + SBBool async, SBBool fullUpdate); + +/* ServerBrowserRemoveIP +------------------- +Removes a server from the list given an IP and query port */ +void ServerBrowserRemoveIPA(ServerBrowser sb, const char* ip, + unsigned short port); + +/* ServerBrowserErrorDesc +------------------- +Returns a human-readable error string for the given error code. */ +const char* ServerBrowserErrorDescA(ServerBrowser sb, SBError error); + +/* ServerBrowserListQueryError +------------------- +When a list query error occurs, as indicated by the sbc_queryerror callback, +this function allows you to obtain the human-readable error string for the error +(generally these errors are caused by errors in the filter string) */ +const char* ServerBrowserListQueryErrorA(ServerBrowser sb); + +/* ServerBrowserSendNatNegotiateCookieToServer +------------------ +Sends a cookie value to the server for use with NAT Negotiation */ +SBError ServerBrowserSendNatNegotiateCookieToServerA(ServerBrowser sb, + const char* ip, + unsigned short port, + int cookie); + +/* ServerBrowserSendMessageToServer +------------------ +Sends a game-specific message to a server */ +SBError ServerBrowserSendMessageToServerA(ServerBrowser sb, const char* ip, + unsigned short port, const char* data, + int len); + +/* ServerBrowserSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void ServerBrowserSortA(ServerBrowser sb, SBBool ascending, const char* sortkey, + SBCompareMode comparemode); + +/******************* +SBServer Object Functions +********************/ + +/* SBServerGetPublicAddress/SBServerGetPrivateAddress +------------------- +Returns the string, dotted IP address for the specified server +The "private" version is only valid when the server has a private address +available */ +char* SBServerGetPublicAddress(SBServer server); +char* SBServerGetPrivateAddress(SBServer server); + +/* SBServerGet[]Value +------------------ +Returns the value for the specified key. If the key does not exist for the +given server, the default value is returned */ +const char* SBServerGetStringValueA(SBServer server, const char* keyname, + const char* def); +int SBServerGetIntValueA(SBServer server, const char* key, int idefault); +double SBServerGetFloatValueA(SBServer server, const char* key, + double fdefault); +SBBool SBServerGetBoolValueA(SBServer server, const char* key, SBBool bdefault); + +/* SBServerGetPlayer[]Value / SBServerGetTeam[]Value +------------------ +Returns the value for the specified key on the specified player or team. If the +key does not exist for the given server, the default value is returned Player +keys take the form keyname_N where N is the player index, and team keys take the +form keyname_tN where N is the team index. You should only specify the keyname +for the key in the below functions. +*/ +const char* SBServerGetPlayerStringValueA(SBServer server, int playernum, + const char* key, + const char* sdefault); +int SBServerGetPlayerIntValueA(SBServer server, int playernum, const char* key, + int idefault); +double SBServerGetPlayerFloatValueA(SBServer server, int playernum, + const char* key, double fdefault); + +const char* SBServerGetTeamStringValueA(SBServer server, int teamnum, + const char* key, const char* sdefault); +int SBServerGetTeamIntValueA(SBServer server, int teamnum, const char* key, + int idefault); +double SBServerGetTeamFloatValueA(SBServer server, int teamnum, const char* key, + double fdefault); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/serverbrowsing/sb_crypt.c b/source/gamespy/serverbrowsing/sb_crypt.c new file mode 100644 index 000000000..b48f0af16 --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_crypt.c @@ -0,0 +1,209 @@ +#include "sb_crypt.h" +#include +#include + +static unsigned char keyrand(GOACryptState* state, int limit, + unsigned char* user_key, unsigned char keysize, + unsigned char* rsum, unsigned* keypos) { + unsigned int u, // Value from 0 to limit to return. + retry_limiter, // No infinite loops allowed. + mask; // Select just enough bits. + + if (!limit) + return 0; // Avoid divide by zero error. + retry_limiter = 0; + mask = 1; // Fill mask with enough bits to cover + while (mask < (unsigned)limit) // the desired range. + mask = (mask << 1) + 1; + do { + *rsum = (unsigned char)(state->cards[*rsum] + user_key[(*keypos)++]); + if (*keypos >= keysize) { + *keypos = 0; // Recycle the user key. + *rsum = (unsigned char)(*rsum + keysize); // key "aaaa" != key "aaaaaaaa" + } + u = mask & *rsum; + if (++retry_limiter > 11) + u %= (unsigned int)limit; // Prevent very rare long loops. + } while (u > (unsigned)limit); + return (unsigned char)(u); +} + +void GOAHashInit(GOACryptState* state) { + // This function is used to initialize non-keyed hash + // computation. + + int i, j; + + // Initialize the indices and data dependencies. + + state->rotor = 1; + state->ratchet = 3; + state->avalanche = 5; + state->last_plain = 7; + state->last_cipher = 11; + + // Start with state->cards all in inverse order. + + for (i = 0, j = 255; i < 256; i++, j--) + state->cards[i] = (unsigned char)j; +} + +void GOACryptInit(GOACryptState* state, unsigned char* key, + unsigned char keysize) { + // Key size may be up to 256 bytes. + // Pass phrases may be used directly, with longer length + // compensating for the low entropy expected in such keys. + // Alternatively, shorter keys hashed from a pass phrase or + // generated randomly may be used. For random keys, lengths + // of from 4 to 16 bytes are recommended, depending on how + // secure you want this to be. + + int i; + unsigned char toswap, swaptemp, rsum; + unsigned keypos; + + // If we have been given no key, assume the default hash setup. + + if (keysize < 1) { + GOAHashInit(state); + return; + } + + // Start with state->cards all in order, one of each. + + for (i = 0; i < 256; i++) + state->cards[i] = (unsigned char)(i); + + // Swap the card at each position with some other card. + + toswap = 0; + keypos = 0; // Start with first byte of user key. + rsum = 0; + for (i = 255; i >= 0; i--) { + toswap = keyrand(state, i, key, keysize, &rsum, &keypos); + swaptemp = state->cards[i]; + state->cards[i] = state->cards[toswap]; + state->cards[toswap] = swaptemp; + } + + // Initialize the indices and data dependencies. + // Indices are set to different values instead of all 0 + // to reduce what is known about the state of the state->cards + // when the first byte is emitted. + + state->rotor = state->cards[1]; + state->ratchet = state->cards[3]; + state->avalanche = state->cards[5]; + state->last_plain = state->cards[7]; + state->last_cipher = state->cards[rsum]; + + toswap = swaptemp = rsum = 0; + keypos = 0; +} + +unsigned char GOAEncryptByte(GOACryptState* state, unsigned char b) { + // Picture a single enigma state->rotor with 256 positions, rewired + // on the fly by card-shuffling. + + // This cipher is a variant of one invented and written + // by Michael Paul Johnson in November, 1993. + + unsigned char swaptemp; + + // Shuffle the deck a little more. + + state->ratchet = + (unsigned char)(state->ratchet + state->cards[state->rotor++]); + swaptemp = state->cards[state->last_cipher]; + state->cards[state->last_cipher] = state->cards[state->ratchet]; + state->cards[state->ratchet] = state->cards[state->last_plain]; + state->cards[state->last_plain] = state->cards[state->rotor]; + state->cards[state->rotor] = swaptemp; + state->avalanche = (unsigned char)(state->avalanche + state->cards[swaptemp]); + + // Output one byte from the state in such a way as to make it + // very hard to figure out which one you are looking at. + /* +state->last_cipher = b^state->cards[(state->cards[state->ratchet] + +state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->avalanche])&0xFF]]; + */ + state->last_cipher = + (unsigned char)(b ^ + state->cards[(state->cards[state->avalanche] + + state->cards[state->rotor]) & + 0xFF] ^ + state->cards + [state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->ratchet]) & + 0xFF]]); + state->last_plain = b; + return state->last_cipher; +} + +void GOAEncrypt(GOACryptState* state, unsigned char* bp, int len) { + int i; + for (i = 0; i < len; i++) { + bp[i] = GOAEncryptByte(state, bp[i]); + } +} + +unsigned char GOADecryptByte(GOACryptState* state, unsigned char b) { + unsigned char swaptemp; + + // Shuffle the deck a little more. + + state->ratchet = + (unsigned char)(state->ratchet + state->cards[state->rotor++]); + swaptemp = state->cards[state->last_cipher]; + state->cards[state->last_cipher] = state->cards[state->ratchet]; + state->cards[state->ratchet] = state->cards[state->last_plain]; + state->cards[state->last_plain] = state->cards[state->rotor]; + state->cards[state->rotor] = swaptemp; + state->avalanche = (unsigned char)(state->avalanche + state->cards[swaptemp]); + + // Output one byte from the state in such a way as to make it + // very hard to figure out which one you are looking at. + /* +state->last_plain = b^state->cards[(state->cards[state->ratchet] + +state->cards[state->rotor]) & 0xFF] ^ + state->cards[state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->avalanche])&0xFF]]; + */ + // crt - change this around + state->last_plain = + (unsigned char)(b ^ + state->cards[(state->cards[state->avalanche] + + state->cards[state->rotor]) & + 0xFF] ^ + state->cards + [state->cards[(state->cards[state->last_plain] + + state->cards[state->last_cipher] + + state->cards[state->ratchet]) & + 0xFF]]); + state->last_cipher = b; + return state->last_plain; +} + +void GOADecrypt(GOACryptState* state, unsigned char* bp, int len) { + int i; + for (i = 0; i < len; i++) { + bp[i] = GOADecryptByte(state, bp[i]); + } +} + +void GOAHashFinal(GOACryptState* state, + unsigned char* outputhash, // Destination + unsigned char hashlength) // Size of hash. +{ + int i; + + for (i = 255; i >= 0; i--) + GOAEncryptByte(state, (unsigned char)i); + for (i = 0; i < hashlength; i++) + outputhash[i] = GOAEncryptByte(state, 0); +} diff --git a/source/gamespy/serverbrowsing/sb_crypt.h b/source/gamespy/serverbrowsing/sb_crypt.h new file mode 100644 index 000000000..8a973abde --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_crypt.h @@ -0,0 +1,32 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _GOACryptState { + unsigned char cards[256]; // A permutation of 0-255. + unsigned char rotor; // Index that rotates smoothly + unsigned char ratchet; // Index that moves erratically + unsigned char avalanche; // Index heavily data dependent + unsigned char last_plain; // Last plain text byte + unsigned char last_cipher; // Last cipher text byte +} GOACryptState; + +void GOACryptInit(GOACryptState* state, unsigned char* key, + unsigned char keysize); +void GOAHashInit(GOACryptState* state); +unsigned char GOAEncryptByte(GOACryptState* state, + unsigned char b); // Encrypt byte +void GOAEncrypt(GOACryptState* state, unsigned char* bp, + int len); // Encrypt byte array +unsigned char GOADecryptByte(GOACryptState* state, + unsigned char b); // Decrypt byte. +void GOADecrypt(GOACryptState* state, unsigned char* bp, + int len); // decrypt byte array +void GOAHashFinal(GOACryptState* state, unsigned char* hash, + unsigned char hashlength); // Hash length (16-32) + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/serverbrowsing/sb_internal.h b/source/gamespy/serverbrowsing/sb_internal.h new file mode 100644 index 000000000..5ff05169b --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_internal.h @@ -0,0 +1,464 @@ +#pragma once + +// clang-format off +#include "../common/gsCommon.h" +#include "../common/gsAvailable.h" + +#include "../darray.h" +#include "../hashtable.h" + +#include "sb_serverbrowsing.h" +#include "../qr2/qr2regkeys.h" + +#include "sb_crypt.h" +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + pi_cryptheader, + pi_fixedheader, + pi_keylist, + pi_uniquevaluelist, + pi_servers, + pi_finished +} SBListParseState; + +typedef enum { + sl_lanbrowse, + sl_disconnected, + sl_connected, + sl_mainlist +} SBServerListState; + +// master server query port +#define MSPORT2 28910 + +// number of master servers +#define NUM_MASTER_SERVERS 20 + +// max length of field list to master server +#define MAX_FIELD_LIST_LEN 256 + +// max number of values in a popular value list +#define MAX_POPULAR_VALUES 255 + +// max number of maps to track in a map loop +#define MAX_MAPLOOP_LENGTH 16 + +// max number of bytes that can be received from a single recvfrom call +// This must not be higher than 2048 for PS2 insock compatability +#define MAX_RECVFROM_SIZE 2048 + +// states for SBServer->state +#define STATE_BASICKEYS (1 << 0) +#define STATE_FULLKEYS (1 << 1) +#define STATE_PENDINGBASICQUERY (1 << 2) +#define STATE_PENDINGFULLQUERY (1 << 3) +#define STATE_QUERYFAILED (1 << 4) +#define STATE_PENDINGICMPQUERY (1 << 5) +#define STATE_VALIDPING (1 << 6) +#define STATE_PENDINGQUERYCHALLENGE (1 << 7) + +// how long before a server query times out +#define MAX_QUERY_MSEC 2500 + +// game server flags +#define UNSOLICITED_UDP_FLAG 1 +#define PRIVATE_IP_FLAG 2 +#define CONNECT_NEGOTIATE_FLAG 4 +#define ICMP_IP_FLAG 8 +#define NONSTANDARD_PORT_FLAG 16 +#define NONSTANDARD_PRIVATE_PORT_FLAG 32 +#define HAS_KEYS_FLAG 64 +#define HAS_FULL_RULES_FLAG 128 + +// backend query flags (set in hbmaster, don't change) +#define QR2_USE_QUERY_CHALLENGE 128 + +// key types for the key type list +#define KEYTYPE_STRING 0 +#define KEYTYPE_BYTE 1 +#define KEYTYPE_SHORT 2 + +// how long to make the outgoing challenge +#define LIST_CHALLENGE_LEN 8 + +// protocol versions +#define LIST_PROTOCOL_VERSION 1 +#define LIST_ENCODING_VERSION 3 + +// message types for outgoing requests +#define SERVER_LIST_REQUEST 0 +#define SERVER_INFO_REQUEST 1 +#define SEND_MESSAGE_REQUEST 2 +#define KEEPALIVE_REPLY 3 +#define MAPLOOP_REQUEST 4 +#define PLAYERSEARCH_REQUEST 5 + +// message types for incoming requests +#define PUSH_KEYS_MESSAGE 1 +#define PUSH_SERVER_MESSAGE 2 +#define KEEPALIVE_MESSAGE 3 +#define DELETE_SERVER_MESSAGE 4 +#define MAPLOOP_MESSAGE 5 +#define PLAYERSEARCH_MESSAGE 6 + +// server list update options +#define SEND_FIELDS_FOR_ALL 1 +#define NO_SERVER_LIST 2 +#define PUSH_UPDATES 4 +#define SEND_GROUPS 32 +#define NO_LIST_CACHE 64 +#define LIMIT_RESULT_COUNT 128 + +// player search options +#define SEARCH_ALL_GAMES 1 +#define SEARCH_LEFT_SUBSTRING 2 +#define SEARCH_RIGHT_SUBSTRING 4 +#define SEARCH_ANY_SUBSTRING 8 + +// max number of keys for the basic key list +#define MAX_QUERY_KEYS 20 + +// how long to search on the LAN +#define SL_LAN_SEARCH_TIME 2000 + +// MAGIC bytes for the QR2 queries +#define QR2_MAGIC_1 0xFE +#define QR2_MAGIC_2 0xFD + +// magic bytes for nat negotiation message +#define NATNEG_MAGIC_LEN 6 +#define NN_MAGIC_0 0xFD +#define NN_MAGIC_1 0xFC +#define NN_MAGIC_2 0x1E +#define NN_MAGIC_3 0x66 +#define NN_MAGIC_4 0x6A +#define NN_MAGIC_5 0xB2 + +// query types +#define QTYPE_BASIC 0 +#define QTYPE_FULL 1 +#define QTYPE_ICMP 2 + +// query strings for old-style servers +#define BASIC_GOA_QUERY "\\basic\\\\info\\" +#define BASIC_GOA_QUERY_LEN 13 +#define FULL_GOA_QUERY "\\status\\" +#define FULL_GOA_QUERY_LEN 8 + +// maximum length of a sortkey string +#define SORTKEY_LENGTH 255 + +// include ICMP support by default +#ifndef SB_NO_ICMP_SUPPORT +#undef SB_ICMP_SUPPORT +#define SB_ICMP_SUPPORT +#endif + +// a key/value pair +typedef struct _SBKeyValuePair { + const char* key; + const char* value; +} SBKeyValuePair; + +// a ref-counted string +typedef struct _SBRefString { + const char* str; + int refcount; +} SBRefString; + +typedef struct _SBServerList* SBServerListPtr; +typedef struct _SBQueryEngine* SBQueryEnginePtr; + +// callback types for server lists +typedef enum { + slc_serveradded, + slc_serverupdated, + slc_serverdeleted, + slc_initiallistcomplete, + slc_disconnected, + slc_queryerror, + slc_publicipdetermined, + slc_serverchallengereceived +} SBListCallbackReason; +// callback types for query engine +typedef enum { + qe_updatesuccess, + qe_updatefailed, + qe_engineidle, + qe_challengereceived +} SBQueryEngineCallbackReason; + +// callback function prototypes +typedef void (*SBListCallBackFn)(SBServerListPtr serverlist, + SBListCallbackReason reason, SBServer server, + void* instance); +typedef void (*SBEngineCallbackFn)(SBQueryEnginePtr engine, + SBQueryEngineCallbackReason reason, + SBServer server, void* instance); +typedef void (*SBMaploopCallbackFn)(SBServerListPtr serverlist, SBServer server, + time_t mapChangeTime, int numMaps, + char* mapList[], void* instance); +typedef void (*SBPlayerSearchCallbackFn)(SBServerListPtr serverlist, char* nick, + goa_uint32 serverIP, + unsigned short serverPort, + time_t lastSeenTime, char* gamename, + void* instance); + +// key information structure +typedef struct _KeyInfo { + const char* keyName; + int keyType; +} KeyInfo; + +typedef struct _SBServerList SBServerList; + +#ifdef VENGINE_SUPPORT +#define FTABLE_TYPES +#include "../../VEngine/ve_gm3ftable.h" +#endif + +// keeps track of previous and current sorting information +typedef struct _SortInfo { + gsi_char sortkey[SORTKEY_LENGTH]; + SBCompareMode comparemode; +} SortInfo; + +struct _SBServerList { + SBServerListState state; + DArray servers; + DArray keylist; + char queryforgamename[36]; + char queryfromgamename[36]; + char queryfromkey[32]; + char mychallenge[LIST_CHALLENGE_LEN]; + char* inbuffer; + int inbufferlen; + const char* popularvalues[MAX_POPULAR_VALUES]; + int numpopularvalues; + int expectedelements; + + SBListCallBackFn ListCallback; + SBMaploopCallbackFn MaploopCallback; + SBPlayerSearchCallbackFn PlayerSearchCallback; + void* instance; + + SortInfo currsortinfo; + SortInfo prevsortinfo; + + SBBool sortascending; + goa_uint32 mypublicip; + goa_uint32 srcip; + unsigned short defaultport; + + char* lasterror; + + SOCKET slsocket; + gsi_time lanstarttime; + int fromgamever; + GOACryptState cryptkey; + int queryoptions; + SBListParseState pstate; + gsi_u16 backendgameflags; + + const char* mLanAdapterOverride; + + SBServer deadlist; + +#ifdef VENGINE_SUPPORT +#define FTABLE_IMPLEMENT +#include "../../VEngine/ve_gm3ftable.h" +#endif +}; + +// server object + +#ifndef SB_SERVER_DECLARED +#define SB_SERVER_DECLARED + +struct _SBServer { + goa_uint32 publicip; + unsigned short publicport; + goa_uint32 privateip; + unsigned short privateport; + goa_uint32 icmpip; + unsigned char state; + unsigned char flags; + HashTable keyvals; + gsi_time updatetime; + gsi_u32 querychallenge; + struct _SBServer* next; + gsi_u8 splitResponseBitmap; +}; + +#endif + +typedef struct _SBServerFIFO { + SBServer first; + SBServer last; + int count; +} SBServerFIFO; + +typedef struct _SBQueryEngine { + int queryversion; + int maxupdates; + SBServerFIFO querylist; + SBServerFIFO pendinglist; + SOCKET querysock; +#if !defined(SN_SYSTEMS) + SOCKET icmpsock; +#endif + goa_uint32 mypublicip; + unsigned char serverkeys[MAX_QUERY_KEYS]; + int numserverkeys; + SBEngineCallbackFn ListCallback; + void* instance; +} SBQueryEngine; + +struct _ServerBrowser { + SBQueryEngine engine; + SBServerList list; + SBBool disconnectFlag; + SBBool dontUpdate; + goa_uint32 triggerIP; + unsigned short triggerPort; + ServerBrowserCallback BrowserCallback; + void* instance; +}; + +#define SB_ICMP_ECHO 8 +#define SB_ICMP_ECHO_REPLY 0 + +typedef struct _IPHeader { + gsi_u8 ip_hl_ver; + gsi_u8 ip_tos; + gsi_i16 ip_len; + gsi_u16 ip_id; + gsi_i16 ip_off; + gsi_u8 ip_ttl; + gsi_u8 ip_p; + gsi_u16 ip_sum; + struct in_addr ip_src, ip_dst; +} SBIPHeader; + +typedef struct _ICMPHeader { + gsi_u8 type; + gsi_u8 code; + gsi_u16 cksum; + union { + struct { + gsi_u16 id; + gsi_u16 sequence; + } echo; + gsi_u32 idseq; + gsi_u16 gateway; + struct { + gsi_u16 __notused; + gsi_u16 mtu; + } frag; + } un; +} SBICMPHeader; + +// server list functions +void SBServerListInit(SBServerList* slist, const char* queryForGamename, + const char* queryFromGamename, const char* queryFromKey, + int queryFromVersion, SBBool lanBrowse, + SBListCallBackFn callback, void* instance); +SBError SBServerListConnectAndQuery(SBServerList* slist, const char* fieldList, + const char* serverFilter, int options, + int maxServers); +SBError SBServerListGetLANList(SBServerList* slist, unsigned short startport, + unsigned short endport, int queryversion); +void SBServerListDisconnect(SBServerList* slist); +void SBServerListCleanup(SBServerList* slist); +void SBServerListClear(SBServerList* slist); +SBError SBGetServerRulesFromMaster(SBServerList* slist, goa_uint32 ip, + unsigned short port); +SBError SBSendMessageToServer(SBServerList* slist, goa_uint32 ip, + unsigned short port, const char* data, int len); +SBError SBSendNatNegotiateCookieToServer(SBServerList* slist, goa_uint32 ip, + unsigned short port, int cookie); +SBError SBSendMaploopRequest(SBServerList* slist, SBServer server, + SBMaploopCallbackFn callback); +SBError SBSendPlayerSearchRequest(SBServerList* slist, char* searchName, + int searchOptions, int maxResults, + SBPlayerSearchCallbackFn callback); +int SBServerListFindServerByIP(SBServerList* slist, goa_uint32 ip, + unsigned short port); +int SBServerListFindServer(SBServerList* slist, SBServer findserver); +void SBServerListRemoveAt(SBServerList* slist, int index); +void SBServerListAppendServer(SBServerList* slist, SBServer server); +void SBServerListSort(SBServerList* slist, SBBool ascending, SortInfo sortinfo); +int SBServerListCount(SBServerList* slist); +SBServer SBServerListNth(SBServerList* slist, int i); +SBError SBListThink(SBServerList* slist); +const char* SBLastListErrorA(SBServerList* slist); +const unsigned short* SBLastListErrorW(SBServerList* slist); +void SBSetLastListErrorPtr(SBServerList* slist, char* theError); +void SBFreeDeadList(SBServerList* slist); +void SBAllocateServerList(SBServerList* slist); + +// sbserver functions +SBServer SBAllocServer(SBServerList* slist, goa_uint32 publicip, + unsigned short publicport); +void SBServerFree(void* elem); +void SBServerAddKeyValue(SBServer server, const char* keyname, + const char* value); +void SBServerAddIntKeyValue(SBServer server, const char* keyname, int value); +void SBServerParseKeyVals(SBServer server, char* keyvals); +void SBServerParseQR2FullKeysSingle(SBServer server, char* data, int len); +void SBServerParseQR2FullKeysSplit(SBServer server, char* data, int len); +void SBServerSetFlags(SBServer server, unsigned char flags); +void SBServerSetPublicAddr(SBServer server, goa_uint32 ip, unsigned short port); +void SBServerSetPrivateAddr(SBServer server, goa_uint32 ip, + unsigned short port); +void SBServerSetICMPIP(SBServer server, goa_uint32 icmpip); +void SBServerSetState(SBServer server, unsigned char state); +void SBServerSetNext(SBServer server, SBServer next); +SBServer SBServerGetNext(SBServer server); +unsigned char SBServerGetState(SBServer server); +unsigned char SBServerGetFlags(SBServer server); +unsigned short SBServerGetPublicQueryPortNBO(SBServer server); +int SBIsNullServer(SBServer server); +extern SBServer SBNullServer; + +// ref-str functions +const char* SBRefStr(SBServerList* slist, const char* str); +void SBReleaseStr(SBServerList* slist, const char* str); +HashTable SBRefStrHash(SBServerList* slist); +void SBRefStrHashCleanup(SBServerList* slist); + +extern char* SBOverrideMasterServer; + +// query engine functions +void SBQueryEngineInit(SBQueryEngine* engine, int maxupdates, int queryversion, + SBBool lanBrowse, SBEngineCallbackFn callback, + void* instance); +void SBQueryEngineUpdateServer(SBQueryEngine* engine, SBServer server, + int addfront, int querytype, + SBBool usequerychallenge); +void SBQueryEngineSetPublicIP(SBQueryEngine* engine, goa_uint32 mypublicip); +SBServer SBQueryEngineUpdateServerByIP(SBQueryEngine* engine, const char* ip, + unsigned short queryport, int addfront, + int querytype, SBBool usequerychallenge); +void SBQueryEngineThink(SBQueryEngine* engine); +void SBQueryEngineAddQueryKey(SBQueryEngine* engine, unsigned char keyid); +void SBEngineCleanup(SBQueryEngine* engine); +void SBQueryEngineRemoveServerFromFIFOs(SBQueryEngine* engine, SBServer server); +int NTSLengthSB(char* buf, int len); +void SBEngineHaltUpdates(SBQueryEngine* engine); + +// server browser internal function +SBError ServerBrowserBeginUpdate2(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, const char* serverFilter, + int updateOptions, int maxServers); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/serverbrowsing/sb_queryengine.c b/source/gamespy/serverbrowsing/sb_queryengine.c new file mode 100644 index 000000000..0ece3eb63 --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_queryengine.c @@ -0,0 +1,683 @@ +#include "sb_internal.h" +#include "sb_serverbrowsing.h" + +#ifdef GSI_MANIC_DEBUG +// Make sure the server isn't already in the fifo +void FIFODebugCheckAdd(SBServerFIFO* fifo, SBServer server) { + SBServer aServer = fifo->first; + while (aServer != NULL) { + assert(aServer != server); + aServer = aServer->next; + } +} + +// Verify the contents of the fifo +void FIFODebugCheck(SBServerFIFO* fifo) { + int i = 0; + SBServer aServer; + + assert(fifo != NULL); + aServer = fifo->first; + for (i = 0; i < fifo->count; i++) { + assert(aServer != NULL); + aServer = aServer->next; + } +} +#else +#define FIFODebugCheckAdd(a, b) +#define FIFODebugCheck(a) +#endif + +// FIFO Queue management functions +static void FIFOAddRear(SBServerFIFO* fifo, SBServer server) { + FIFODebugCheckAdd(fifo, server); + + if (fifo->last != NULL) + fifo->last->next = server; + fifo->last = server; + server->next = NULL; + if (fifo->first == NULL) + fifo->first = server; + fifo->count++; + + FIFODebugCheck(fifo); +} + +static void FIFOAddFront(SBServerFIFO* fifo, SBServer server) { + FIFODebugCheckAdd(fifo, server); + + server->next = fifo->first; + fifo->first = server; + if (fifo->last == NULL) + fifo->last = server; + fifo->count++; + + FIFODebugCheck(fifo); +} + +static SBServer FIFOGetFirst(SBServerFIFO* fifo) { + SBServer hold; + hold = fifo->first; + if (hold != NULL) { + fifo->first = hold->next; + if (fifo->first == NULL) + fifo->last = NULL; + fifo->count--; + } + + FIFODebugCheck(fifo); + return hold; +} + +static SBBool FIFORemove(SBServerFIFO* fifo, SBServer server) { + SBServer hold, prev; + prev = NULL; + hold = fifo->first; + while (hold != NULL) { + if (hold == server) // found + { + if (prev != NULL) // there is a previous.. + prev->next = hold->next; + if (fifo->first == hold) + fifo->first = hold->next; + if (fifo->last == hold) + fifo->last = prev; + fifo->count--; + // assert((fifo->count == 0 && fifo->first == NULL && fifo->last == + // NULL) || fifo->count > 0); + return SBTrue; + } + prev = hold; + hold = hold->next; + } + + FIFODebugCheck(fifo); + return SBFalse; +} + +static void FIFOClear(SBServerFIFO* fifo) { + fifo->first = fifo->last = NULL; + fifo->count = 0; + + FIFODebugCheck(fifo); +} + +#ifdef SB_ICMP_SUPPORT +static unsigned short IPChecksum(const unsigned short* buf, int len) { + unsigned long cksum = 0; + + // Calculate the checksum + while (len > 1) { + cksum += *buf++; + len -= sizeof(unsigned short); + } + + // If we have one char left + if (len) { + cksum += *(unsigned char*)buf; + } + + // Complete the calculations + cksum = (cksum >> 16) + (cksum & 0xffff); + cksum += (cksum >> 16); + + // Return the value (inversed) + return (unsigned short)(~cksum); +} +#endif + +static void QEStartQuery(SBQueryEngine* engine, SBServer server) { + unsigned char queryBuffer[256]; + int queryLen; + gsi_bool querySuccess = gsi_false; + struct sockaddr_in saddr; + + saddr.sin_family = AF_INET; + server->updatetime = current_time(); + + if (server->state & STATE_PENDINGICMPQUERY) // send an ICMP ping request + { +#ifdef SB_ICMP_SUPPORT +#if !defined(SN_SYSTEMS) + SBICMPHeader* _icmp = (SBICMPHeader*)(queryBuffer); + // todo: alignment issues on PS2 + _icmp->type = SB_ICMP_ECHO; + _icmp->code = 0; + _icmp->un.idseq = server->updatetime; // no need for network byte order + // since only we read the reply + _icmp->cksum = 0; + queryLen = sizeof(SBICMPHeader) + 6; + memcpy(queryBuffer + sizeof(SBICMPHeader), &server->publicip, + 4); // put some data in the echo packet that we can use to verify the + // reply + memcpy(queryBuffer + sizeof(SBICMPHeader) + 4, &server->publicport, 2); + _icmp->cksum = IPChecksum((unsigned short*)queryBuffer, queryLen); + if (SBServerGetFlags(server) & + ICMP_IP_FLAG) // there is a special ICMP address + { + saddr.sin_addr.s_addr = server->icmpip; + } else { + saddr.sin_addr.s_addr = server->publicip; + } + + sendto(engine->icmpsock, (char*)queryBuffer, queryLen, 0, + (struct sockaddr*)&saddr, sizeof(saddr)); + querySuccess = gsi_true; +#else + int result; + sndev_set_ping_ip_type optval; + optval.ip_addr = server->icmpip; + + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Attempting to send ICMP ping to %s\r\n", + inet_ntoa(*((struct in_addr*)&server->icmpip))); + + result = sndev_set_options(0, SN_DEV_SET_PING_IP, (void*)&optval, + sizeof(optval)); // tell SN to ping this addr + if (result == SN_PING_FAIL) { + gsDebugFormat( + GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "ICMP ping attempt failed on SNSystems (unknown error)\r\n"); + querySuccess = + gsi_true; // let the SDK process failures as a timed out ping + } else if (result == SN_PING_FULL) { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, + GSIDebugLevel_WarmError, + "ICMP ping attempt failed on SNSystems (SN_PING_FULL)\r\n"); + querySuccess = gsi_false; + } else + querySuccess = gsi_true; +#endif +#endif + } else // send a UDP query + { + if (engine->queryversion == QVERSION_QR2) { + if (server->state & STATE_PENDINGQUERYCHALLENGE) { + // send IP verify request now, but send qr2 query later when IP verify + // returns + int pos = 0; + queryBuffer[pos++] = QR2_MAGIC_1; + queryBuffer[pos++] = QR2_MAGIC_2; + queryBuffer[pos++] = 0x09; // ip verify prequery + // set the request key + memcpy(&queryBuffer[pos], &server->updatetime, 4); + pos += 4; + queryLen = pos; + } else { + gsi_u32 challengeNBO = htonl(server->querychallenge); + int pos = 0; + + // set the header + queryBuffer[pos++] = QR2_MAGIC_1; + queryBuffer[pos++] = QR2_MAGIC_2; + queryBuffer[pos++] = 0; + memcpy(&queryBuffer[pos], &server->updatetime, 4); // set the request + // key + pos += 4; + if (challengeNBO != 0) { + memcpy(&queryBuffer[pos], &challengeNBO, 4); // set the challenge + pos += 4; + } + if (server->state & STATE_PENDINGBASICQUERY) { + int i; + queryBuffer[pos++] = (unsigned char)engine->numserverkeys; + for (i = 0; i < engine->numserverkeys; i++) + queryBuffer[pos++] = engine->serverkeys[i]; + + // don't request any player or team keys + queryBuffer[pos++] = 0x00; + queryBuffer[pos++] = 0x00; + queryLen = pos; + + } else // request all keys for everyone + { + queryBuffer[pos++] = 0xFF; + queryBuffer[pos++] = 0xFF; + queryBuffer[pos++] = 0xFF; + + // Tell the server we support split packets + queryBuffer[pos++] = 0x1; + queryLen = pos; // 11 + } + } + } else // GOA + { + if (server->state & + STATE_PENDINGBASICQUERY) // original - do a \basic\info\ query + { + memcpy(queryBuffer, BASIC_GOA_QUERY, BASIC_GOA_QUERY_LEN); + queryLen = BASIC_GOA_QUERY_LEN; + } else // original - do a \status\ query + { + memcpy(queryBuffer, FULL_GOA_QUERY, FULL_GOA_QUERY_LEN); + queryLen = FULL_GOA_QUERY_LEN; + } + } + if (server->publicip == engine->mypublicip && + (server->flags & PRIVATE_IP_FLAG)) // try querying the private IP + { + saddr.sin_addr.s_addr = server->privateip; + saddr.sin_port = server->privateport; + } else { + saddr.sin_addr.s_addr = server->publicip; + saddr.sin_port = server->publicport; + } + sendto(engine->querysock, (char*)queryBuffer, queryLen, 0, + (struct sockaddr*)&saddr, sizeof(saddr)); + querySuccess = gsi_true; + } + + // add it to the query list + if (gsi_is_true(querySuccess)) + FIFOAddRear(&engine->querylist, server); + else + server->updatetime = 0; +} + +void SBQueryEngineInit(SBQueryEngine* engine, int maxupdates, int queryversion, + SBBool lanBrowse, SBEngineCallbackFn callback, + void* instance) { + // 11-03-2004 : Added by Saad Nader + // fix for LANs and unnecessary availability check + /////////////////////////////////////////////////// + if (lanBrowse == SBFalse) { + if (__GSIACResult != GSIACAvailable) + return; + } + SocketStartUp(); + engine->queryversion = queryversion; + engine->maxupdates = maxupdates; + engine->numserverkeys = 0; + engine->ListCallback = callback; + engine->instance = instance; + engine->mypublicip = 0; + engine->querysock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); +#if defined(SB_ICMP_SUPPORT) +#if defined(SN_SYSTEMS) + { + // reset SNSystems internal ICMP ping structures + sndev_set_ping_reset_type optval; + optval.timeout_ms = MAX_QUERY_MSEC; // this gets rounded up to 3 sec + optval.reserved = 0; + sndev_set_options(0, SN_DEV_SET_PING_RESET, &optval, sizeof(optval)); + } +#else + engine->icmpsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); +#endif +#endif + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + +void SBQueryEngineSetPublicIP(SBQueryEngine* engine, goa_uint32 mypublicip) { + engine->mypublicip = mypublicip; +} + +void SBEngineHaltUpdates(SBQueryEngine* engine) { + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + +void SBEngineCleanup(SBQueryEngine* engine) { + closesocket(engine->querysock); +#ifdef SB_ICMP_SUPPORT +#if !defined(SN_SYSTEMS) + closesocket(engine->icmpsock); +#endif +#endif + engine->querysock = INVALID_SOCKET; + FIFOClear(&engine->pendinglist); + FIFOClear(&engine->querylist); +} + +// NOTE: the server must not be in the pending or update list currently! +void SBQueryEngineUpdateServer(SBQueryEngine* engine, SBServer server, + int addfront, int querytype, + SBBool usequerychallenge) { + // Assert state of FIFOs + FIFODebugCheckAdd(&engine->pendinglist, server); + FIFODebugCheckAdd(&engine->querylist, server); + + server->state &= + (unsigned char)~(STATE_PENDINGBASICQUERY | STATE_PENDINGFULLQUERY | + STATE_PENDINGICMPQUERY | STATE_PENDINGQUERYCHALLENGE | + STATE_QUERYFAILED); // clear out these flags + server->splitResponseBitmap = 0; + server->querychallenge = 0; + +#ifndef SB_ICMP_SUPPORT + if (querytype == QTYPE_ICMP) + return; // ICMP not supported +#endif + + if (querytype == QTYPE_BASIC) + server->state |= STATE_PENDINGBASICQUERY; + else if (querytype == QTYPE_FULL) + server->state |= STATE_PENDINGFULLQUERY; + else if (querytype == QTYPE_ICMP) + server->state |= STATE_PENDINGICMPQUERY; + else + return; // hoterror: unsupported querytype! + + if (usequerychallenge && + (querytype == QTYPE_FULL || querytype == QTYPE_BASIC)) + server->state |= STATE_PENDINGQUERYCHALLENGE; + + if (engine->querylist.count < engine->maxupdates) // add it now.. + { + QEStartQuery(engine, server); + return; + } + // else need to queue it + + if (addfront) + FIFOAddFront(&engine->pendinglist, server); + else + FIFOAddRear(&engine->pendinglist, server); +} + +SBServer SBQueryEngineUpdateServerByIP(SBQueryEngine* engine, const char* ip, + unsigned short queryport, int addfront, + int querytype, + SBBool usequerychallenge) { + // need to create a new server + SBServer server; + goa_uint32 ipaddr; + ipaddr = inet_addr(ip); + server = SBAllocServer(NULL, ipaddr, htons(queryport)); + server->flags = UNSOLICITED_UDP_FLAG; // we assume we can talk directly to it + SBQueryEngineUpdateServer(engine, server, addfront, querytype, + usequerychallenge); + return server; +} + +static void ParseSingleQR2Reply(SBQueryEngine* engine, SBServer server, + char* data, int len) { + int i; + int dlen; + + // 0x00 == qr2 query response, 0x09 == qr2 challenge response + if (data[0] != 0x00 && data[0] != 0x09) + return; + + // we could test the request key here for added security, or skip + data += 5; + len -= 5; + if (server->state & STATE_PENDINGQUERYCHALLENGE) { + server->state &= (unsigned char)~(STATE_PENDINGQUERYCHALLENGE); + + if (len > 0) { + server->querychallenge = (gsi_u32)atoi(data); + FIFORemove(&engine->querylist, server); // remove it + QEStartQuery(engine, server); // readd it with a keys query + engine->ListCallback(engine, qe_challengereceived, server, + engine->instance); + return; + } + } else if (server->state & STATE_PENDINGBASICQUERY) { + // need to pick out the keys they selected + for (i = 0; i < engine->numserverkeys; i++) { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + break; + // add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key( + qr2_registered_key_list[engine->serverkeys[i]])) { + SBServerAddKeyValue( + server, qr2_registered_key_list[engine->serverkeys[i]], data); + } + data += dlen; + len -= dlen; + } + server->state |= STATE_BASICKEYS | STATE_VALIDPING; + } else // need to parse out all the keys + { + // Is this a split packet format? + if (*data && strncmp("splitnum", data, 8) == 0) { + SBServerParseQR2FullKeysSplit(server, data, len); + if (server->splitResponseBitmap != 0xFF) + return; + server->state |= STATE_FULLKEYS | STATE_BASICKEYS | STATE_VALIDPING; + } else { + // single packet + SBServerParseQR2FullKeysSingle(server, data, len); + server->state |= STATE_FULLKEYS | STATE_BASICKEYS | STATE_VALIDPING; + } + } + server->state &= + (unsigned char)~(STATE_PENDINGBASICQUERY | STATE_PENDINGFULLQUERY); + server->updatetime = current_time() - server->updatetime; + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); +} + +static void ParseSingleGOAReply(SBQueryEngine* engine, SBServer server, + char* data, int len) { + int isfinal; + // need to check before parse as it will modify the string + isfinal = (strstr(data, "\\final\\") != NULL); + SBServerParseKeyVals(server, data); + if (isfinal) { + if (server->state & STATE_PENDINGBASICQUERY) + server->state |= STATE_BASICKEYS | STATE_VALIDPING; + else + server->state |= STATE_FULLKEYS | STATE_VALIDPING; + server->state &= + (unsigned char)~(STATE_PENDINGBASICQUERY | STATE_PENDINGFULLQUERY); + server->updatetime = current_time() - server->updatetime; + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); + } + + GSI_UNUSED(len); +} + +static SBBool ParseSingleICMPReply(SBQueryEngine* engine, SBServer server, + char* data, int len) { +#ifdef SB_ICMP_SUPPORT + SBIPHeader* ipheader = (SBIPHeader*)data; + SBICMPHeader* icmpheader; + int ipheaderlen; + goa_uint32 packetpublicip; + unsigned short packetpublicport; + // todo: byte alignment on PS2 + ipheaderlen = (gsi_u8)(ipheader->ip_hl_ver & 15); + ipheaderlen *= 4; + icmpheader = (SBICMPHeader*)(data + ipheaderlen); + if (icmpheader->type != SB_ICMP_ECHO_REPLY) + return SBFalse; + if (icmpheader->un.idseq != server->updatetime) + return SBFalse; + if (len < ipheaderlen + (int)sizeof(SBICMPHeader) + 6) + return SBFalse; // not enough data + // check the server IP and port + memcpy(&packetpublicip, data + ipheaderlen + sizeof(SBICMPHeader), 4); + memcpy(&packetpublicport, data + ipheaderlen + sizeof(SBICMPHeader) + 4, 2); + if (packetpublicport != server->publicport || + packetpublicip != server->publicip) + return SBFalse; + // else its a valid echo + server->updatetime = current_time() - server->updatetime; + server->state |= STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGICMPQUERY); + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, engine->instance); +#else + GSI_UNUSED(engine); + GSI_UNUSED(server); + GSI_UNUSED(data); + GSI_UNUSED(len); +#endif + return SBTrue; +} + +#if defined(SN_SYSTEMS) && defined(SB_ICMP_SUPPORT) +static void ProcessIncomingICMPReplies(SBQueryEngine* engine) { + SBServer server; + int result = 0; + int found = 0; + int i = 0; + sndev_stat_ping_times_type optval; + gsi_i32 optsize = sizeof(optval); + + // Get the ICMP replies from the SNSystems stack + result = + sndev_get_status(0, SN_DEV_STAT_PING_TIMES, (void*)&optval, &optsize); + if (result != 0) { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Failed on sndev_get_status (checking ICMP pings): %d\r\n", + result); + return; + } + if (optval.num_entries == 0) + return; // no outstanding pings (according to sn_systems) + + // match servers to ping responses + for (server = engine->querylist.first; server != NULL; + server = server->next) { + if ((server->state & STATE_PENDINGICMPQUERY) == 0 || + (server->flags & ICMP_IP_FLAG) == 0) + continue; // server not flagged for ICMP + + // find this server + for (i = 0; i < optval.num_entries; i++) { + if (server->icmpip == optval.times[i].ip_addr) { + if (optval.times[i].status == SN_PING_TIMES_CODE_GOTREPLY) { + server->updatetime = optval.times[i].time_ms; + server->state |= STATE_VALIDPING; + server->state &= (unsigned char)~(STATE_PENDINGICMPQUERY); + FIFORemove(&engine->querylist, server); + engine->ListCallback(engine, qe_updatesuccess, server, + engine->instance); + } + // else + // let query engine timeout queries on its own (for simplicity) + + found++; + if (found == optval.num_entries) + return; // found them all + } + } + } +} +#endif // SN_SYSTEMS && SB_ICMP_SUPPORT + +static void ProcessIncomingReplies(SBQueryEngine* engine, SBBool icmpSocket) { + int i; + __attribute__((aligned(32))) char indata[MAX_RECVFROM_SIZE]; + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + SBServer server; + SOCKET recvSock = 0; + + if (icmpSocket) { +#ifdef SB_ICMP_SUPPORT +#if defined(SN_SYSTEMS) + ProcessIncomingICMPReplies(engine); + return; +#else + recvSock = engine->icmpsock; +#endif +#endif + } else { + recvSock = engine->querysock; + } + + // Process all information in the socket buffer + while (CanReceiveOnSocket(recvSock)) { + i = (int)recvfrom(recvSock, indata, sizeof(indata) - 1, 0, + (struct sockaddr*)&saddr, &saddrlen); + + if (gsiSocketIsError(i)) + break; + indata[i] = 0; + // find the server in our query list + for (server = engine->querylist.first; server != NULL; + server = server->next) { + if ((icmpSocket && (server->flags & ICMP_IP_FLAG) && + server->icmpip == + saddr.sin_addr.s_addr) || // if it's an ICMP query and it matches + // the ICMP address + (server->publicip == saddr.sin_addr.s_addr && + (server->publicport == saddr.sin_port || + icmpSocket)) || // if it matches public - port doesnt need to match + // for ICMP + (server->publicip == engine->mypublicip && + (server->flags & PRIVATE_IP_FLAG) && + server->privateip == saddr.sin_addr.s_addr && + server->privateport == + saddr.sin_port)) // or has a private, and matches + { + if (icmpSocket) { + if (ParseSingleICMPReply(engine, server, indata, i)) + break; // only break if it matches exactly, since we may have + // multiple outstanding pings to the same ICMPIP for + // different servers! + } else { + if (engine->queryversion == QVERSION_QR2) + ParseSingleQR2Reply(engine, server, indata, i); + else + ParseSingleGOAReply(engine, server, indata, i); + break; + } + } + } + } +} + +static void TimeoutOldQueries(SBQueryEngine* engine) { + gsi_time ctime = current_time(); + while (engine->querylist.first != NULL) { + if (ctime > engine->querylist.first->updatetime + MAX_QUERY_MSEC) { + engine->querylist.first->flags |= STATE_QUERYFAILED; + engine->querylist.first->updatetime = MAX_QUERY_MSEC; + engine->querylist.first->flags &= + (unsigned char)~(STATE_PENDINGBASICQUERY | STATE_PENDINGFULLQUERY | + STATE_PENDINGICMPQUERY); + engine->ListCallback(engine, qe_updatefailed, engine->querylist.first, + engine->instance); + FIFOGetFirst(&engine->querylist); + } else + break; // since servers are added in FIFO order, nothing later can have + // already expired + } +} + +static void QueueNextQueries(SBQueryEngine* engine) { + while (engine->querylist.count < engine->maxupdates && + engine->pendinglist.count > 0) { + SBServer server = FIFOGetFirst(&engine->pendinglist); + QEStartQuery(engine, server); + } +} + +void SBQueryEngineThink(SBQueryEngine* engine) { + if (engine->querylist.count == 0) // not querying anything - we can go away + return; + ProcessIncomingReplies(engine, SBFalse); +#ifdef SB_ICMP_SUPPORT + ProcessIncomingReplies(engine, SBTrue); +#endif + TimeoutOldQueries(engine); + if (engine->pendinglist.count > 0) + QueueNextQueries(engine); + if (engine->querylist.count == 0) // we are now idle.. + engine->ListCallback(engine, qe_engineidle, NULL, engine->instance); +} + +void SBQueryEngineAddQueryKey(SBQueryEngine* engine, unsigned char keyid) { + if (engine->numserverkeys < MAX_QUERY_KEYS) + engine->serverkeys[engine->numserverkeys++] = keyid; +} + +// remove a server from our update FIFOs +void SBQueryEngineRemoveServerFromFIFOs(SBQueryEngine* engine, + SBServer server) { + SBBool ret; + + // remove the server from the current query list + ret = FIFORemove(&engine->querylist, server); + if (ret) + return; // -- Caution: assumes that server will not be in pendinglist + FIFORemove(&engine->pendinglist, server); +} diff --git a/source/gamespy/serverbrowsing/sb_server.c b/source/gamespy/serverbrowsing/sb_server.c new file mode 100644 index 000000000..155fb439a --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_server.c @@ -0,0 +1,672 @@ +// clang-format off +#include "sb_internal.h" +#include "sb_ascii.h" +// clang-format on + +#include + +// for the unique value list +#if defined(_NITRO) +#define LIST_NUMKEYBUCKETS 100 +#define LIST_NUMKEYCHAINS 2 +#else +#define LIST_NUMKEYBUCKETS 500 +#define LIST_NUMKEYCHAINS 4 +#endif + +// for unicode version of key/value pairs +#define UKEY_LENGTH_MAX 255 +#define UVALUE_LENGTH_MAX 255 + +#ifndef EXTERN_REFSTR_HASH +// global, shared unique value list +#if defined(_WIN32) && !defined(_DLL) && !defined(_USRDLL) && \ + !defined(_MANAGED) && defined(GM_2B) +// for gmaster2b +__declspec(thread) +#endif + HashTable g_SBRefStrList = NULL; +#endif + +/*********** + * REF COUNTING STRING HASHTABLE FUNCTIONS + **********/ +static int StringHash(const char* s, int numbuckets); +static int RefStringHash(const void* elem, int numbuckets) { + return StringHash(((SBRefString*)elem)->str, numbuckets); +} + +/* keyval + * Compares two gkeyvaluepair + */ + +static int GS_STATIC_CALLBACK RefStringCompare(const void* entry1, + const void* entry2) { + return strcasecmp(((SBRefString*)entry1)->str, ((SBRefString*)entry2)->str); +} + +static void RefStringFree(void* elem) { + gsifree((char*)((SBRefString*)elem)->str); +} + +#ifndef EXTERN_REFSTR_HASH + +HashTable SBRefStrHash(SBServerList* slist) { + if (g_SBRefStrList == NULL) + g_SBRefStrList = + TableNew2(sizeof(SBRefString), LIST_NUMKEYBUCKETS, LIST_NUMKEYCHAINS, + RefStringHash, RefStringCompare, RefStringFree); + + GSI_UNUSED(slist); + return g_SBRefStrList; +} + +void SBRefStrHashCleanup(SBServerList* slist) { + if (g_SBRefStrList != NULL && TableCount(g_SBRefStrList) == 0) { + TableFree(g_SBRefStrList); + g_SBRefStrList = NULL; + } + + GSI_UNUSED(slist); +} + +#endif + +void SBServerFree(void* elem) { + SBServer server = *(SBServer*)elem; + // free all the keys.. + TableFree(server->keyvals); + server->keyvals = NULL; + gsifree(server); +} + +void SBServerAddKeyValue(SBServer server, const char* keyname, + const char* value) { + SBKeyValuePair kv; + kv.key = SBRefStr(NULL, keyname); + kv.value = SBRefStr(NULL, value); + TableEnter(server->keyvals, &kv); + + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Misc, GSIDebugLevel_Comment, + "SBServerAddKeyValue added %s\\%s\r\n", keyname, value); +} + +void SBServerAddIntKeyValue(SBServer server, const char* keyname, int value) { + char stemp[20]; + sprintf(stemp, "%d", value); + SBServerAddKeyValue(server, keyname, stemp); +} + +typedef struct { + SBServerKeyEnumFn EnumFn; + void* instance; +} SBServerEnumData; + +/* ServerEnumKeys +----------------- +Enumerates the keys/values for a given server by calling KeyEnumFn with each +key/value. The user-defined instance data will be passed to the KeyFn callback +*/ + +static void KeyMapF(void* elem, void* clientData) { + SBKeyValuePair* kv = (SBKeyValuePair*)elem; + SBServerEnumData* ped = (SBServerEnumData*)clientData; + ped->EnumFn((char*)kv->key, (char*)kv->value, ped->instance); +} +void SBServerEnumKeys(SBServer server, SBServerKeyEnumFn KeyFn, + void* instance) { + SBServerEnumData ed; + + ed.EnumFn = KeyFn; + ed.instance = instance; + TableMap(server->keyvals, KeyMapF, &ed); +} + +const char* SBServerGetStringValueA(SBServer server, const char* keyname, + const char* def) { + SBKeyValuePair kv, *ptr; + + // 11-03-2004 : Saad Nader + // Check if we are getting a valid server + // before doing anything! + /////////////////////////////////////////// + assert(server); + if (!server) + return NULL; + kv.key = keyname; + ptr = (SBKeyValuePair*)TableLookup(server->keyvals, &kv); + if (ptr == NULL) + return def; + return ptr->value; +} + +int SBServerGetIntValueA(SBServer server, const char* key, int idefault) { + const char *s, *s2; + // check assumtions during development + GS_ASSERT(key != NULL); + GS_ASSERT(server != NULL); + if (server == NULL) + return idefault; + if (strcmp(key, "ping") == 0) // ooh! they want the ping! + return SBServerGetPing(server); + s = SBServerGetStringValueA(server, key, NULL); + if (s == NULL) + return idefault; + s2 = (*s != '-') ? s : s + 1; // check for signed values + if (!isdigit(( + unsigned char)*s2)) // empty-string/non-numeric should return idefault + return idefault; + else + return atoi(s); +} + +double SBServerGetFloatValueA(SBServer server, const char* key, + double fdefault) { + const char* s; + s = SBServerGetStringValueA(server, key, NULL); + if (s == NULL) + return fdefault; + else + return atof(s); +} + +SBBool SBServerGetBoolValueA(SBServer server, const char* key, + SBBool bdefault) { + const char* s; + s = SBServerGetStringValueA(server, key, NULL); + if (!s || !s[0]) + return bdefault; + + // check the first char for known "false" values + if ('0' == s[0] || 'F' == s[0] || 'f' == s[0] || 'N' == s[0] || 'n' == s[0]) + return SBFalse; + + // presume that all other non-zero values are "true" + return SBTrue; +} + +const char* SBServerGetPlayerStringValueA(SBServer server, int playernum, + const char* key, + const char* sdefault) { + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetStringValueA(server, keyname, sdefault); +} + +int SBServerGetPlayerIntValueA(SBServer server, int playernum, const char* key, + int idefault) { + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetIntValueA(server, keyname, idefault); +} + +double SBServerGetPlayerFloatValueA(SBServer server, int playernum, + const char* key, double fdefault) { + char keyname[128]; + sprintf(keyname, "%s_%d", key, playernum); + return SBServerGetFloatValueA(server, keyname, fdefault); +} + +const char* SBServerGetTeamStringValueA(SBServer server, int teamnum, + const char* key, const char* sdefault) { + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetStringValueA(server, keyname, sdefault); +} + +int SBServerGetTeamIntValueA(SBServer server, int teamnum, const char* key, + int idefault) { + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetIntValueA(server, keyname, idefault); +} + +double SBServerGetTeamFloatValueA(SBServer server, int teamnum, const char* key, + double fdefault) { + char keyname[128]; + sprintf(keyname, "%s_t%d", key, teamnum); + return SBServerGetFloatValueA(server, keyname, fdefault); +} + +SBBool SBServerHasBasicKeys(SBServer server) { + return (((server->state & STATE_BASICKEYS) == STATE_BASICKEYS) ? SBTrue + : SBFalse); +} + +SBBool SBServerHasFullKeys(SBServer server) { + return (((server->state & STATE_FULLKEYS) == STATE_FULLKEYS) ? SBTrue + : SBFalse); +} + +SBBool SBServerHasValidPing(SBServer server) { + return (((server->state & STATE_VALIDPING) == STATE_VALIDPING) ? SBTrue + : SBFalse); +} + +char* SBServerGetPublicAddress(SBServer server) { + return (char*)inet_ntoa(*(struct in_addr*)&server->publicip); +} + +unsigned int SBServerGetPublicInetAddress(SBServer server) { + return server->publicip; +} + +unsigned short SBServerGetPublicQueryPort(SBServer server) { + return ntohs(server->publicport); +} + +unsigned short SBServerGetPublicQueryPortNBO(SBServer server) { + return server->publicport; +} + +SBBool SBServerHasPrivateAddress(SBServer server) { + return (((server->flags & PRIVATE_IP_FLAG) == PRIVATE_IP_FLAG) ? SBTrue + : SBFalse); +} + +SBBool SBServerDirectConnect(SBServer server) { + return (((server->flags & UNSOLICITED_UDP_FLAG) == UNSOLICITED_UDP_FLAG) + ? SBTrue + : SBFalse); +} + +char* SBServerGetPrivateAddress(SBServer server) { + return (char*)inet_ntoa(*(struct in_addr*)&server->privateip); +} + +unsigned int SBServerGetPrivateInetAddress(SBServer server) { + return server->privateip; +} + +unsigned short SBServerGetPrivateQueryPort(SBServer server) { + return ntohs(server->privateport); +} + +void SBServerSetNext(SBServer server, SBServer next) { server->next = next; } + +SBServer SBServerGetNext(SBServer server) { return server->next; } + +static int CheckValidKey(char* key) { + const char* InvalidKeys[] = {"queryid", "final"}; + int i; + for (i = 0; i < sizeof(InvalidKeys) / sizeof(InvalidKeys[0]); i++) { + if (strcmp(key, InvalidKeys[i]) == 0) + return 0; + } + return 1; +} + +static char* mytok(char* instr, char delim) { + char* result; + static char* thestr; + + if (instr) + thestr = instr; + result = thestr; + while (*thestr && *thestr != delim) { + thestr++; + } + if (thestr == result) + result = NULL; + if (*thestr) // not the null term + *thestr++ = '\0'; + return result; +} + +void SBServerParseKeyVals(SBServer server, char* keyvals) { + char *k, *v; + + k = mytok(++keyvals, '\\'); // skip over starting backslash + while (k != NULL) { + v = mytok(NULL, '\\'); + if (v == NULL) + v = ""; + if (CheckValidKey(k)) { + // add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) { + SBServerAddKeyValue(server, k, v); + } + } + k = mytok(NULL, '\\'); + } +} + +/* +Query Response Format +Packet Type: 1 Byte +Request Key: 4 Bytes (copied from request packet) + +Server Keys (if number of keys requested > 0) +If server key list specified: +Server Values: NTS, one per key specified +If server key list not specified: +Server Keys / Server Values: NTS Pairs, terminated with NUL + +Player Keys (if number of keys requested > 0) +Number of Players: 2 Bytes +If player key list NOT specified + Player Keys: NTS, one per key, terminated with NUL +Player Values: NTS, one per key specified, per player + +Team Keys (if number of keys requested > 0) +Number of Teams: 2 Bytes +If team key list NOT specified + Team Keys: NTS, one per key, terminated with NUL +Team Values: NTS, one per key specified, per team + +*/ +void SBServerParseQR2FullKeysSingle(SBServer server, char* data, int len) { + int dlen; + char* k; + char* v; + char* keys; + int nkeys; + unsigned short nunits; + int pflag; + int i, j; + char tempkey[128]; + // first pull out all the server keys/values + while (*data) { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; // not a full NTS + k = data; + data += dlen; + len -= dlen; + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; // not a full NTS + v = data; + data += dlen; + len -= dlen; + + // add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) { + SBServerAddKeyValue(server, k, v); + } + } + // skip the NUL + data++; + len--; + // now get out the # of players (or teams) .. we do this whole thing 2X, once + // for players once for teams + for (pflag = 0; pflag < 2; pflag++) { + if (len < 2) + return; + memcpy(&nunits, data, 2); + nunits = ntohs(nunits); + data += 2; + len -= 2; + keys = data; + nkeys = 0; + // count up the number of keys.. + while (*data) { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; // not all there + if (dlen > 100) // key is too long, may cause buffer overrun + return; + nkeys++; + data += dlen; + len -= dlen; + } + // skip the NUL + data++; + len--; + // now for each player/team + for (i = 0; i < nunits; i++) { + k = keys; + // for each key.. + for (j = 0; j < nkeys; j++) { + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; // not all there + sprintf(tempkey, "%s%d", k, i); + SBServerAddKeyValue(server, tempkey, data); + data += dlen; + len -= dlen; + k += strlen(k) + 1; // skip to the next key + } + } + } +} + +// FullKeys with split response support +// Goes something like: +// [qr2header]["splitnum"][num (byte)][keytype] +// if keytype is server, read KV's until a NULL +// otherwise read a keyname, then a number of values until NULL +#define QR2_SPLITPACKET_NUMSTRING "splitnum" +#define QR2_SPLITPACKET_MAX 7 // -xxxxxxx +#define QR2_SPLITPACKET_FINAL (1 << 7) // x------- +void SBServerParseQR2FullKeysSplit(SBServer server, char* data, int len) { + int dlen; + char* k; + char* v; + unsigned int packetNum = 0; + gsi_bool isFinal = gsi_false; + + // make sure it's valid + if (*data == '\0') + return; + + // data should have "splitnum" followed by BINARY key + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + k = data; + data += dlen; + len -= dlen; + + if (strncasecmp(QR2_SPLITPACKET_NUMSTRING, k, + strlen(QR2_SPLITPACKET_NUMSTRING)) != 0) + return; + if (len < 1) + return; + + packetNum = (unsigned int)((unsigned char)*data); + data++; + len--; + + // check final flag + if ((packetNum & QR2_SPLITPACKET_FINAL) == QR2_SPLITPACKET_FINAL) { + isFinal = gsi_true; + packetNum ^= QR2_SPLITPACKET_FINAL; + } + + // sanity check the packet num + if (packetNum > QR2_SPLITPACKET_MAX) + return; + + // update the received flags + if (isFinal == SBTrue) + // mark all bits higher than the final packet + server->splitResponseBitmap |= (char)(0xFF << packetNum); + else + // mark only the bit for the received packet + server->splitResponseBitmap |= (1 << packetNum); + + // any data in this packet? (could be a final tag) + if (len < 1) + return; + + // Parse the key sections (server/player/team) + while (len > 0) { + int keyType = 0; + int nindex = 0; + + // Read the key type + keyType = *data; + data++; + len--; + + if (keyType < 0 || keyType > 2) { + gsDebugFormat( + GSIDebugCat_SB, GSIDebugType_Network, GSIDebugLevel_WarmError, + "Split packet parse error, invalid key type! (%d)\r\n", keyType); + return; // invalid key type! + } + + // read keys until section terminator + while (*data) { + // Read key name + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + k = data; + data += dlen; + len -= dlen; + + if (keyType == 0) { + // read the server key value + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + v = data; + data += dlen; + len -= dlen; + + // add the value if its not a Query-From-Master-Only key + if (!qr2_internal_is_master_only_key(k)) { + SBServerAddKeyValue(server, k, v); + } + } else { + char tempkey[128]; + + // Read first player/team number + if (len < 1) + return; + + nindex = *data; + data++; + len--; + + // read values until + while (*data) { + // read the value + dlen = NTSLengthSB(data, len); + if (dlen < 0) + return; + v = data; + data += dlen; + len -= dlen; + // append team or player index before adding + sprintf(tempkey, "%s%d", k, nindex); + SBServerAddKeyValue(server, tempkey, v); + nindex++; // index increments from start + } + + // skip the null (key terminator) + if (len > 0) { + data++; + len--; + } + } + } + + // skip the null (section terminator) + if (len > 0) { + if (*data != '\0') { + gsDebugFormat(GSIDebugCat_SB, GSIDebugType_Network, + GSIDebugLevel_WarmError, + "Split packet parse error, NULL byte expected!\r\n"); + return; // ERROR! + } + data++; + len--; + } + } +} + +/*********** + * UTILITY FUNCTIONS + **********/ +#define MULTIPLIER -1664117991 +static int StringHash(const char* s, int numbuckets) { + goa_uint32 hashcode = 0; + while (*s != 0) { + hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s)); + s++; + } + return (int)(hashcode % numbuckets); +} + +static void KeyValFree(void* elem) { + SBKeyValuePair* kv = (SBKeyValuePair*)elem; + SBReleaseStr(NULL, kv->key); + SBReleaseStr(NULL, kv->value); +} + +static int KeyValHashKey(const void* elem, int numbuckets) { + return StringHash(((SBKeyValuePair*)elem)->key, numbuckets); +} + +static int GS_STATIC_CALLBACK KeyValCompareKey(const void* entry1, + const void* entry2) { + GS_ASSERT(entry1) + GS_ASSERT(entry2) + if ((((SBKeyValuePair*)entry1)->key == NULL) || + (((SBKeyValuePair*)entry2)->key == NULL)) + return 1; // treat NULL as not the same + + return strcasecmp(((SBKeyValuePair*)entry1)->key, + ((SBKeyValuePair*)entry2)->key); +} + +int SBServerGetPing(SBServer server) { return (int)server->updatetime; } + +#define NUM_BUCKETS 8 +#define NUM_CHAINS 4 + +// todo: benchmark sorted darray vs. hashtable - memory + speed +SBServer SBAllocServer(SBServerList* slist, goa_uint32 publicip, + unsigned short publicport) { + SBServer server; + server = (SBServer)gsimalloc(sizeof(struct _SBServer)); + if (server == NULL) + return NULL; + server->keyvals = TableNew2(sizeof(SBKeyValuePair), NUM_BUCKETS, NUM_CHAINS, + KeyValHashKey, KeyValCompareKey, KeyValFree); + if (server->keyvals == NULL) { + gsifree(server); + return NULL; + } + server->state = 0; + server->flags = 0; + server->next = NULL; + server->updatetime = 0; + server->icmpip = 0; + server->publicip = publicip; + server->publicport = publicport; + server->privateip = 0; + server->privateport = 0; + + GSI_UNUSED(slist); + return server; +} + +void SBServerSetFlags(SBServer server, unsigned char flags) { + server->flags = flags; +} +void SBServerSetPublicAddr(SBServer server, goa_uint32 ip, + unsigned short port) { + server->publicip = ip; + server->publicport = port; +} +void SBServerSetPrivateAddr(SBServer server, goa_uint32 ip, + unsigned short port) { + server->privateip = ip; + server->privateport = port; +} +void SBServerSetICMPIP(SBServer server, goa_uint32 icmpip) { + server->icmpip = icmpip; +} +void SBServerSetState(SBServer server, unsigned char state) { + server->state = state; +} +unsigned char SBServerGetState(SBServer server) { return server->state; } +unsigned char SBServerGetFlags(SBServer server) { return server->flags; } + +int SBIsNullServer(SBServer server) { return (server == SBNullServer) ? 1 : 0; } + +SBServer SBNullServer = NULL; diff --git a/source/gamespy/serverbrowsing/sb_serverbrowsing.c b/source/gamespy/serverbrowsing/sb_serverbrowsing.c new file mode 100644 index 000000000..867d1f255 --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_serverbrowsing.c @@ -0,0 +1,509 @@ +// clang-format off +#include "sb_internal.h" +#include "sb_ascii.h" +#include "../natneg/natneg.h" +// clang-format on + +// Future Versions: +// ICMP Ping support (icmp engine) + +// internal callback proxy for server list +static void ListCallback(SBServerList* serverlist, SBListCallbackReason reason, + SBServer server, void* instance) { + ServerBrowser sb = (ServerBrowser)instance; + switch (reason) { + case slc_serveradded: + sb->BrowserCallback(sb, sbc_serveradded, server, sb->instance); + if ((server->state & (STATE_BASICKEYS | STATE_FULLKEYS)) == 0 || + (server->state & STATE_VALIDPING) == 0) // we need to do an update + { + // Don't update if an update is already pending + if (server->state & (STATE_PENDINGBASICQUERY | STATE_PENDINGFULLQUERY | + STATE_PENDINGICMPQUERY)) + break; + + if (!sb->dontUpdate) // if this flag is set, we don't want to trigger + // updates + { + int qtype; + if (server->flags & UNSOLICITED_UDP_FLAG) { + if (sb->list.state == sl_lanbrowse || sb->engine.numserverkeys == 0) + qtype = QTYPE_FULL; + else + qtype = QTYPE_BASIC; + } else + qtype = QTYPE_ICMP; // we can only do an ICMP query + + if (serverlist->backendgameflags & QR2_USE_QUERY_CHALLENGE) + SBQueryEngineUpdateServer(&sb->engine, server, 0, qtype, SBTrue); + else + SBQueryEngineUpdateServer(&sb->engine, server, 0, qtype, SBFalse); + } + } + break; + case slc_serverupdated: + if ((server->state & + (STATE_BASICKEYS | STATE_FULLKEYS | STATE_VALIDPING)) == + 0) // if it was updated, but with no data, then the update failed! + sb->BrowserCallback(sb, sbc_serverupdatefailed, server, sb->instance); + else + sb->BrowserCallback(sb, sbc_serverupdated, server, sb->instance); + break; + case slc_serverdeleted: + if ((server->state & (STATE_PENDINGBASICQUERY | STATE_PENDINGFULLQUERY | + STATE_PENDINGICMPQUERY)) != 0) + SBQueryEngineRemoveServerFromFIFOs(&sb->engine, server); + sb->BrowserCallback(sb, sbc_serverdeleted, server, sb->instance); + break; + case slc_initiallistcomplete: + if (sb->disconnectFlag) + SBServerListDisconnect(serverlist); + // If there aren't any servers to query, call the completed callback + if (ArrayLength(serverlist->servers) == 0 || + sb->engine.querylist.count == 0) + sb->BrowserCallback(sb, sbc_updatecomplete, NULL, sb->instance); + break; + case slc_disconnected: + break; + case slc_queryerror: + sb->BrowserCallback(sb, sbc_queryerror, NULL, sb->instance); + break; + case slc_publicipdetermined: + SBQueryEngineSetPublicIP(&sb->engine, sb->list.mypublicip); + break; + case slc_serverchallengereceived: + break; + } + if (server != NULL && server->publicip == sb->triggerIP && + server->publicport == sb->triggerPort) + sb->triggerIP = 0; // clear the trigger +} + +// internal callback proxy for query engine +static void EngineCallback(SBQueryEnginePtr engine, + SBQueryEngineCallbackReason reason, SBServer server, + void* instance) { + ServerBrowser sb = (ServerBrowser)instance; + switch (reason) { + case qe_updatefailed: + sb->BrowserCallback(sb, sbc_serverupdatefailed, server, sb->instance); + break; + case qe_updatesuccess: + sb->BrowserCallback(sb, sbc_serverupdated, server, sb->instance); + break; + case qe_engineidle: + sb->BrowserCallback(sb, sbc_updatecomplete, server, sb->instance); + break; + case qe_challengereceived: + sb->BrowserCallback(sb, sbc_serverchallengereceived, server, sb->instance); + // challenge received will return instead of break - since the game has not + // yet been updated + return; + } + if (server != NULL && server->publicip == sb->triggerIP && + server->publicport == sb->triggerPort) + sb->triggerIP = 0; // clear the trigger + + GSI_UNUSED(engine); +} + +ServerBrowser +ServerBrowserNewA(const char* queryForGamename, const char* queryFromGamename, + const char* queryFromKey, int queryFromVersion, + int maxConcUpdates, int queryVersion, SBBool lanBrowse, + ServerBrowserCallback callback, void* instance) { + ServerBrowser sb; + if (lanBrowse == SBFalse) { + if (__GSIACResult != GSIACAvailable) + return NULL; + } + + sb = (ServerBrowser)gsimalloc(sizeof(struct _ServerBrowser)); + if (sb == NULL) + return NULL; + sb->BrowserCallback = callback; + sb->instance = instance; + sb->dontUpdate = SBFalse; + SBServerListInit(&sb->list, queryForGamename, queryFromGamename, queryFromKey, + queryFromVersion, lanBrowse, ListCallback, sb); + SBQueryEngineInit(&sb->engine, maxConcUpdates, queryVersion, lanBrowse, + EngineCallback, sb); + return sb; +} + +void ServerBrowserFree(ServerBrowser sb) { + SBServerListCleanup(&sb->list); + SBEngineCleanup(&sb->engine); + // NNFreeNegotiateList(); + gsifree(sb); +} + +// internal version that allows passing of additional options +SBError ServerBrowserBeginUpdate2(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, const char* serverFilter, + int updateOptions, int maxServers) { + char keyList[MAX_FIELD_LIST_LEN] = ""; + int listLen = 0; + int i; + int keylen; + SBError err; + + sb->disconnectFlag = disconnectOnComplete; + // clear this out in case it was already set + sb->engine.numserverkeys = 0; + // build the key list... + for (i = 0; i < numBasicFields; i++) { + keylen = (int)strlen(qr2_registered_key_list[basicFields[i]]); + if (listLen + keylen + 1 >= MAX_FIELD_LIST_LEN) + break; // can't add this field, too long + listLen += sprintf(keyList + listLen, "\\%s", + qr2_registered_key_list[basicFields[i]]); + // add to the engine query list + SBQueryEngineAddQueryKey(&sb->engine, basicFields[i]); + } + +#if defined(SN_SYSTEMS) && defined(SB_ICMP_SUPPORT) + { + // reset SNSystems internal ICMP ping structures + sndev_set_ping_reset_type optval; + optval.timeout_ms = MAX_QUERY_MSEC; // this gets rounded up to 3 sec + optval.reserved = 0; + sndev_set_options(0, SN_DEV_SET_PING_RESET, &optval, sizeof(optval)); + } +#endif + + err = SBServerListConnectAndQuery(&sb->list, keyList, serverFilter, + updateOptions, maxServers); + if (err != sbe_noerror) + return err; + + if (!async) // loop while we are still getting the main list and the engine is + // updating... + { + while ((sb->list.state == sl_mainlist) || + ((sb->engine.querylist.count > 0) && (err == sbe_noerror))) { + msleep(10); + err = ServerBrowserThink(sb); + } + } + return err; +} + +SBError ServerBrowserUpdateA(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, const char* serverFilter) { + return ServerBrowserBeginUpdate2(sb, async, disconnectOnComplete, basicFields, + numBasicFields, serverFilter, 0, 0); +} + +SBError ServerBrowserLimitUpdateA(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, const char* serverFilter, + int maxServers) { + return ServerBrowserBeginUpdate2(sb, async, disconnectOnComplete, basicFields, + numBasicFields, serverFilter, + LIMIT_RESULT_COUNT, maxServers); +} + +SBError ServerBrowserLANUpdate(ServerBrowser sb, SBBool async, + unsigned short startSearchPort, + unsigned short endSearchPort) { + SBError err = sbe_noerror; + ServerBrowserHalt(sb); + SBServerListGetLANList(&sb->list, startSearchPort, endSearchPort, + sb->engine.queryversion); + if (!async) { + while ((sb->list.state == sl_lanbrowse) || + ((sb->engine.querylist.count > 0) && (err == sbe_noerror))) { + msleep(10); + err = ServerBrowserThink(sb); + } + } + return err; +} + +static SBError WaitForTriggerUpdate(ServerBrowser sb, SBBool viaMaster) { + SBError err = sbe_noerror; + // wait until info is received for the triggerIP + while (sb->triggerIP != 0 && err == sbe_noerror) { + msleep(10); + err = ServerBrowserThink(sb); + if (viaMaster && + sb->list.state == sb_disconnected) // we were supposed to get from + // master, and it's disconnected + break; + } + return err; +} + +SBError ServerBrowserSendMessageToServerA(ServerBrowser sb, const char* ip, + unsigned short port, const char* data, + int len) { + return SBSendMessageToServer(&sb->list, inet_addr(ip), htons(port), data, + len); +} + +SBError ServerBrowserSendNatNegotiateCookieToServerA(ServerBrowser sb, + const char* ip, + unsigned short port, + int cookie) { + return SBSendNatNegotiateCookieToServer(&sb->list, inet_addr(ip), htons(port), + cookie); +} + +static void NatNegProgressCallback(NegotiateState state, void* userdata) { + // we don't do anything here + GSI_UNUSED(state); + GSI_UNUSED(userdata); +} + +SBError ServerBrowserAuxUpdateIPA(ServerBrowser sb, const char* ip, + unsigned short port, SBBool viaMaster, + SBBool async, SBBool fullUpdate) { + + SBError err = sbe_noerror; + sb->dontUpdate = SBTrue; + + if (!viaMaster) // do an engine query + { + SBServer server; + int i; + SBBool usequerychallenge = + (sb->list.backendgameflags & QR2_USE_QUERY_CHALLENGE) > 0 ? SBTrue + : SBFalse; + + // need to see if the server exists... + i = SBServerListFindServerByIP(&sb->list, inet_addr(ip), htons(port)); + if (i == -1) { + server = SBQueryEngineUpdateServerByIP( + &sb->engine, ip, port, 1, (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, + usequerychallenge); + SBServerListAppendServer(&sb->list, server); + } else + server = SBServerListNth(&sb->list, i); + + // Don't overwrite the existing update, otherwise the response for the first + // update will be mistaken as the response for the second update. (Which is + // bad if they have different update parameters.) + if (server->state & (STATE_PENDINGBASICQUERY | STATE_PENDINGFULLQUERY | + STATE_PENDINGICMPQUERY)) + return sbe_duplicateupdateerror; + else + SBQueryEngineUpdateServer(&sb->engine, server, 1, + (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, + usequerychallenge); + + } else // do a master update + { + err = SBGetServerRulesFromMaster(&sb->list, inet_addr(ip), htons(port)); + // this will add the server itself.. + } + if (!async && err == sbe_noerror) { + sb->triggerIP = inet_addr(ip); + sb->triggerPort = htons(port); + err = WaitForTriggerUpdate(sb, viaMaster); + } + sb->dontUpdate = SBFalse; + return err; +} + +SBError ServerBrowserAuxUpdateServer(ServerBrowser sb, SBServer server, + SBBool async, SBBool fullUpdate) { + SBBool viaMaster; + SBError err = sbe_noerror; + SBBool usequerychallenge = + (sb->list.backendgameflags & QR2_USE_QUERY_CHALLENGE) > 0 ? SBTrue + : SBFalse; + + sb->dontUpdate = SBTrue; + + if (server->flags & UNSOLICITED_UDP_FLAG) // do an engine query + { + // remove from the existing update lists if present + SBQueryEngineRemoveServerFromFIFOs(&sb->engine, server); + SBQueryEngineUpdateServer(&sb->engine, server, 1, + (fullUpdate) ? QTYPE_FULL : QTYPE_BASIC, + usequerychallenge); + viaMaster = SBFalse; + } else // do a master update + { + err = SBGetServerRulesFromMaster(&sb->list, server->publicip, + server->publicport); + viaMaster = SBTrue; + } + if (!async && err == sbe_noerror) { + sb->triggerIP = server->publicip; + sb->triggerPort = server->publicport; + err = WaitForTriggerUpdate(sb, viaMaster); + } + sb->dontUpdate = SBFalse; + return err; +} + +void ServerBrowserRemoveIPA(ServerBrowser sb, const char* ip, + unsigned short port) { + int i = SBServerListFindServerByIP(&sb->list, inet_addr(ip), htons(port)); + if (i != -1) + SBServerListRemoveAt(&sb->list, i); +} + +void ServerBrowserRemoveServer(ServerBrowser sb, SBServer server) { + int i = SBServerListFindServer(&sb->list, server); + if (i != -1) + SBServerListRemoveAt(&sb->list, i); +} + +SBError ServerBrowserThink(ServerBrowser sb) { + SBQueryEngineThink(&sb->engine); + return SBListThink(&sb->list); +} + +void ServerBrowserHalt(ServerBrowser sb) { + // stop the list + SBServerListDisconnect(&sb->list); + // stop the query engine... + SBEngineHaltUpdates(&sb->engine); +} + +void ServerBrowserClear(ServerBrowser sb) { + ServerBrowserHalt(sb); + SBServerListClear(&sb->list); +} + +const char* ServerBrowserErrorDescA(ServerBrowser sb, SBError error) { + switch (error) { + case sbe_noerror: + return "None"; + // break; + case sbe_socketerror: + return "Socket creation error"; + // break; + case sbe_dnserror: + return "DNS lookup error"; + // break; + case sbe_connecterror: + return "Connection failed"; + // break; + case sbe_dataerror: + return "Data stream error"; + // break; + case sbe_allocerror: + return "Memory allocation error"; + // break; + case sbe_paramerror: + return "Function parameter error"; + // break; + case sbe_duplicateupdateerror: + return "Duplicate update request error"; + // break; + } + + GSI_UNUSED(sb); + return ""; +} + +const char* ServerBrowserListQueryErrorA(ServerBrowser sb) { + return SBLastListErrorA(&sb->list); +} + +SBState ServerBrowserState(ServerBrowser sb) { + if (sb->engine.querylist.count > 0) + return sb_querying; + if (sb->list.state == sl_mainlist || sb->list.state == sl_lanbrowse) + return sb_listxfer; + if (sb->list.state == sl_disconnected) + return sb_disconnected; + return sb_connected; +} + +int ServerBrowserPendingQueryCount(ServerBrowser sb) { + return sb->engine.querylist.count + sb->engine.pendinglist.count; +} + +SBServer ServerBrowserGetServer(ServerBrowser sb, int index) { + return SBServerListNth(&sb->list, index); +} + +SBServer ServerBrowserGetServerByIPA(ServerBrowser sb, const char* ip, + unsigned short port) { + int anIndex = -1; + goa_uint32 anIP = 0; + unsigned short aPortNBO = htons(port); + + anIP = inet_addr(ip); + anIndex = SBServerListFindServerByIP(&sb->list, anIP, aPortNBO); + if (anIndex != -1) + return SBServerListNth(&sb->list, anIndex); + return NULL; +} + +int ServerBrowserCount(ServerBrowser sb) { + return SBServerListCount(&sb->list); +} + +void ServerBrowserSortA(ServerBrowser sb, SBBool ascending, const char* sortkey, + SBCompareMode comparemode) { + SortInfo info; + info.comparemode = comparemode; + GS_ASSERT(sortkey != NULL && _tcslen(sortkey) <= SORTKEY_LENGTH); + _tcscpy(info.sortkey, sortkey); + SBServerListSort(&sb->list, ascending, info); +} + +char* ServerBrowserGetMyPublicIP(ServerBrowser sb) { + return (char*)inet_ntoa(*(struct in_addr*)&sb->list.mypublicip); +} + +unsigned int ServerBrowserGetMyPublicIPAddr(ServerBrowser sb) { + return sb->list.mypublicip; +} + +void ServerBrowserDisconnect(ServerBrowser sb) { + SBServerListDisconnect(&sb->list); +} + +// Allows the user to specify a broadcast address for LAN hosting +void ServerBrowserLANSetLocalAddr(ServerBrowser sb, const char* theAddr) { + sb->list.mLanAdapterOverride = theAddr; +} + +/* SBServerGetConnectionInfo +---------------- +Check if Nat Negotiation is requires, based off whether it is a lan game, public +ip present and several other facts. Returns an IP string to use for NatNeg, or +direct connect if possible Work for subsequent connection to this server, One of +three results will occur i) Lan game, connect using ipstring 2) Internet game, +connect using ipstring 3) nat traversal required, perform nat negotiation using +Nat SDK and this ipstring before connecting. + +return sb_true if further processing is required... i.e. NAT. sb_false if not. +fills an IP string +*/ +SBBool SBServerGetConnectionInfo(ServerBrowser gSB, SBServer server, + gsi_u16 PortToConnectTo, char* ipstring) { + SBBool natneg = SBFalse; + if (SBServerHasPrivateAddress(server) == SBTrue && + (SBServerGetPublicInetAddress(server) == + ServerBrowserGetMyPublicIPAddr(gSB))) { + + // directly connect to private IP (LAN) + sprintf(ipstring, "%s:%d", SBServerGetPrivateAddress(server), + PortToConnectTo); + + } else if ((SBServerDirectConnect(server) == SBTrue) && + (SBServerHasPrivateAddress(server) == SBFalse)) { + // can directly connect to public IP, no negotiation required + sprintf(ipstring, "%s:%d", SBServerGetPrivateAddress(server), + PortToConnectTo); + } else { + // Nat Negotiation required + natneg = SBTrue; + sprintf(ipstring, "%s:%d", SBServerGetPublicAddress(server), + SBServerGetPublicQueryPort(server)); + } + return natneg; +} diff --git a/source/gamespy/serverbrowsing/sb_serverbrowsing.h b/source/gamespy/serverbrowsing/sb_serverbrowsing.h new file mode 100644 index 000000000..1729c32ad --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_serverbrowsing.h @@ -0,0 +1,516 @@ +/****** +GameSpy Server Browsing SDK + +Copyright 1999-2007 GameSpy Industries, Inc + +devsupport@gamespy.com + +****** + + Please see the GameSpy Server Browsing SDK documentation for more + information + +******/ + +#pragma once + +#include "../common/gsCommon.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/******************* +ServerBrowser Typedefs +********************/ +// ServerBrowser is an abstract data type used to represent the server list and +// query engine objects. +typedef struct _ServerBrowser* ServerBrowser; + +// SBServer is an abstract data type representing a single server. +#ifndef SBServer +typedef struct _SBServer* SBServer; +#endif +// Simple boolean type used for some functions +typedef enum { SBFalse, SBTrue } SBBool; + +// Error codes that can be returned from functions +typedef enum { + sbe_noerror, // no error has occured + sbe_socketerror, // a socket function has returned an unexpected error + sbe_dnserror, // DNS lookup of master address failed + sbe_connecterror, // connection to master server failed + sbe_dataerror, // invalid data was returned from master server + sbe_allocerror, // memory allocation failed + sbe_paramerror, // an invalid parameter was passed to a function + sbe_duplicateupdateerror // server update requested on a server that was + // already being updated +} SBError; + +// States the ServerBrowser object can be in +typedef enum { + sb_disconnected, // idle and not connected to the master server + sb_listxfer, // downloading list of servers from the master server + sb_querying, // querying servers + sb_connected // idle but still connected to the master server +} SBState; + +// Callbacks that can occur during server browsing operations +typedef enum { + sbc_serveradded, // a server was added to the list, may just have an IP & port + // at this point + sbc_serverupdated, // server information has been updated - either basic or + // full information is now available about this server + sbc_serverupdatefailed, // an attempt to retrieve information about this + // server, either directly or from the master, failed + sbc_serverdeleted, // a server was removed from the list + sbc_updatecomplete, // the server query engine is now idle + sbc_queryerror, // the master returned an error string for the provided query + sbc_serverchallengereceived // received ip verification challenge from server +} SBCallbackReason; + +// Passed to callback to indicate state of attempt to connect to server +typedef enum { sbcs_succeeded, sbcs_failed } SBConnectToServerState; + +// Prototype for the callback function you need to provide +typedef void (*ServerBrowserCallback)(ServerBrowser sb, SBCallbackReason reason, + SBServer server, void* instance); + +// Prototype for the callback function used when connecting to a server +typedef void (*SBConnectToServerCallback)(ServerBrowser sb, + SBConnectToServerState state, + SOCKET gamesocket, + struct sockaddr_in* remoteaddr, + void* instance); + +// Maximum length for the SQL filter string +#define MAX_FILTER_LEN 256 + +// Version defines for query protocol +#define QVERSION_GOA 0 +#define QVERSION_QR2 1 + +/******************* +ServerBrowser Object Functions +********************/ + +#define ServerBrowserNew ServerBrowserNewA +#define ServerBrowserUpdate ServerBrowserUpdateA +#define ServerBrowserLimitUpdate ServerBrowserLimitUpdateA +#define ServerBrowserAuxUpdateIP ServerBrowserAuxUpdateIPA +#define ServerBrowserRemoveIP ServerBrowserRemoveIPA +#define ServerBrowserSendNatNegotiateCookieToServer \ + ServerBrowserSendNatNegotiateCookieToServerA +#define ServerBrowserSendMessageToServer ServerBrowserSendMessageToServerA +#define ServerBrowserSort ServerBrowserSortA +#define SBServerGetStringValue SBServerGetStringValueA +#define SBServerGetIntValue SBServerGetIntValueA +#define SBServerGetFloatValue SBServerGetFloatValueA +#define SBServerGetBoolValue SBServerGetBoolValueA +#define SBServerGetPlayerStringValue SBServerGetPlayerStringValueA +#define SBServerGetPlayerIntValue SBServerGetPlayerIntValueA +#define SBServerGetPlayerFloatValue SBServerGetPlayerFloatValueA +#define SBServerGetTeamStringValue SBServerGetTeamStringValueA +#define SBServerGetTeamIntValue SBServerGetTeamIntValueA +#define SBServerGetTeamFloatValue SBServerGetTeamFloatValueA +#define ServerBrowserListQueryError ServerBrowserListQueryErrorA +#define ServerBrowserErrorDesc ServerBrowserErrorDescA +#define ServerBrowserGetServerByIP ServerBrowserGetServerByIPA + +/* +ServerBrowserNew +---------------- +Creates and returns a new (empty) ServerBrowser object. +Returns NULL if an allocation error occurs. + +queryForGamename - The gamename you are querying for +queryFromGamename - The gamename you are querying from - generally the same as +queryForGamename queryFromKey - Secret key that corresponds to the +queryFromGamename queryFromVersion - A game-specific version identifier (pass 0 +unless told otherwise) maxConcUpdates - Max number of concurent updates (10-15 +for modem users, 20-30 for high-bandwidth) queryVersion - Query protocol to use. +Use QVERSION_GOA for DeveloperSpec/Query&Reporting1 games, and QVERSION_QR2 for +games that use Query & Reporting 2 callback - The function that will be called +with list updates instance - User-defined instance data (e.g. structure or +object pointer) */ +ServerBrowser ServerBrowserNew(const gsi_char* queryForGamename, + const gsi_char* queryFromGamename, + const gsi_char* queryFromKey, + int queryFromVersion, int maxConcUpdates, + int queryVersion, SBBool lanBrowse, + ServerBrowserCallback callback, void* instance); + +/* +ServerBrowserFree +----------------- +Free a ServerBrowser and all internal sturctures and servers */ +void ServerBrowserFree(ServerBrowser sb); + +/* ServerBrowserUpdate +------------------- +Starts an update by downloading a list of servers from the master server, then +querying them. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be +called for processing and querying to occur If SBFalse, the function will not +return until the initial list of servers has been completely updated +disconnectOnComplete - If SBTrue, the connection to the master server will be +disconnected immediately after the list is downloaded. If SBFalse, the +connection will be left open for additional data queries, and can be closed via +ServerBrowserDisconnect basicFields - This array of registered QR2 keys is used +to determine the fields requested from servers during the initial "basic" +update. Only server keys listed in this array will be returned for servers. +numBasicFields - The number of fields in the basicFields array +serverFilter - SQL Filter string that will be applied on the master server to +limit the list of servers returned. All server keys are available for filtering +on the master server, as well as the master-defined "country" and "region" keys. + Standard SQL syntax should be used. + +ServerBrowserLimitUpdate +------------------------ +Identical to ServerBrowserUpdate, except that the number of servers returned can +be limited maxServers - Maximum number of servers to be returned +*/ +SBError ServerBrowserUpdate(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, const gsi_char* serverFilter); +SBError ServerBrowserLimitUpdate(ServerBrowser sb, SBBool async, + SBBool disconnectOnComplete, + const unsigned char* basicFields, + int numBasicFields, + const gsi_char* serverFilter, int maxServers); + +/* ServerBrowserThink +------------------- +Processes incoming data from the master server and game servers that are being +queried. Should be called +as often as possible while a server list update is in progress (~10ms is ideal). +*/ +SBError ServerBrowserThink(ServerBrowser sb); + +/* ServerBrowserLANUpdate +------------------- +Starts an update by searching for servers on the LAN, then querying them. You +can specifiy a range of ports to search for servers. Generally this should start +with your standard query port, and range above it, since the QR and QR2 SDKs +will automatically allocate higher port numbers when running multiple servers on +the same machine. You should limit your search to 100 ports or less in most +cases to limit flooding of the LAN with broadcast packets. + +sb - The server browser object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be +called for processing and querying to occur If SBFalse, the function will not +return until the initial list of servers has been completely updated +startSearchPort - The initial port to begin searching for servers on +endSearchPort - The final port to search. All ports between start and end will +be queried. */ +SBError ServerBrowserLANUpdate(ServerBrowser sb, SBBool async, + unsigned short startSearchPort, + unsigned short endSearchPort); + +/* ServerBrowserAuxUpdateIP +------------------- +Manually updates a server given an IP address and query port. Use to manually +add servers to the list when you just have an IP and port for them. + +sb - The server browser object to add the server to +ip - The dotted IP address of the server e.g. "1.2.3.4" +port - The query port of the server +viaMaster - If SBTrue, information about the server will be retrieved from the +master server instead of attempting to query the server directly. If a +connection to the master server does not exist, it will be made to kept open +afterwards. If SBFalse, the server will be contacted directly for information. +async - If SBTrue, the update will be initiated, and ServerListThink must be +called for processing and querying to occur If SBFalse, the function will not +return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be +retrieved If SBFalse, only the keys specified in the basicFields array of the +ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateIP(ServerBrowser sb, const gsi_char* ip, + unsigned short port, SBBool viaMaster, + SBBool async, SBBool fullUpdate); + +/* ServerBrowserAuxUpdateServer +------------------- +Manually updates a server object. Generally used to get additional information +about a server (for example, to get full rules and player information from a +server that only has basic information so far), but can also be used to +"refresh" the information about a given server. Data will automatically be +retrieved from the master server directly or from the game server as +appropriate. When called asynchronously, multiple server update requests can be +queued and will be executed by the query engine in turn. + +sb - The server browser object to add the server to +server - Server object to update +async - If SBTrue, the update will be initiated, and ServerListThink must be +called for processing and querying to occur If SBFalse, the function will not +return until the server has been successfully or unsuccessfully updated +fullUpdate - If SBTrue, all server keys/rules/player/team information will be +retrieved If SBFalse, only the keys specified in the basicFields array of the +ServerBrowserUpdate function will be retrieved */ +SBError ServerBrowserAuxUpdateServer(ServerBrowser sb, SBServer server, + SBBool async, SBBool fullUpdate); + +/* ServerBrowserDisconnects +------------------- +Disconnects an idle connection to the master server when it is no longer needed. +Note that if you disconnect and then request an operation that requires a +connection to the master, such as an AuxServerUpdate via the master, the +connection will be automatically re-established. */ +void ServerBrowserDisconnect(ServerBrowser sb); + +/* ServerBrowserState +------------------- +Returns the current state of the Server Browser object */ +SBState ServerBrowserState(ServerBrowser sb); + +/* ServerBrowserRemoveIP +------------------- +Removes a server from the list given an IP and query port */ +void ServerBrowserRemoveIP(ServerBrowser sb, const gsi_char* ip, + unsigned short port); + +/* ServerBrowserRemoveServer +------------------- +Removes a server from the list and releases all resources associated with it */ +void ServerBrowserRemoveServer(ServerBrowser sb, SBServer server); + +/* ServerBrowserHalt +------------------- +Stops a server list update in progress, clears any servers queued to be queried, +and disconneects from the master server. */ +void ServerBrowserHalt(ServerBrowser sb); + +/* ServerBrowserClear +------------------- +Removes all the servers from the list and frees all resources associated with +them. */ +void ServerBrowserClear(ServerBrowser sb); + +/* ServerBrowserErrorDesc +------------------- +Returns a human-readable error string for the given error code. */ +const gsi_char* ServerBrowserErrorDesc(ServerBrowser sb, SBError error); + +/* ServerBrowserListQueryError +------------------- +When a list query error occurs, as indicated by the sbc_queryerror callback, +this function allows you to obtain the human-readable error string for the error +(generally these errors are caused by errors in the filter string) */ +const gsi_char* ServerBrowserListQueryError(ServerBrowser sb); + +/* ServerBrowserGetServer +---------------------- +Returns the server at the specified index, or NULL if the index is out of bounds +*/ +SBServer ServerBrowserGetServer(ServerBrowser sb, int index); + +/* ServerBrowserGetServerByIP +---------------------- +Returns the SBServer with the specified IP, or NULL if the server is not in the +list */ +SBServer ServerBrowserGetServerByIP(ServerBrowser sb, const gsi_char* ip, + unsigned short port); + +/* ServerBrowserCount +------------------ +Returns the number of servers on the specified list. Indexing is 0 based, so +the actual server indexes are 0 <= valid index < Count */ +int ServerBrowserCount(ServerBrowser sb); + +/* ServerBrowserPendingQueryCount +------------------ +Returns the number of servers currently being queried or queued to be queried. +When this number is 0, the query engine is idle */ +int ServerBrowserPendingQueryCount(ServerBrowser sb); + +/* ServerBrowserGetMyPublicIP +------------------ +Returns the public IP address for this computer, as seen by an outside machine +(the master server). Use to determine if a server you want to connect to is on +the same private network or not. Only valid after a call to ServerListUpdate has +connected to the master server */ +char* ServerBrowserGetMyPublicIP(ServerBrowser sb); + +/* ServerBrowserGetMyPublicIPAddr +------------------ +Same as ServerBrowserGetMyPublicIP except that the address is returned in +standard network-byte-order form */ +unsigned int ServerBrowserGetMyPublicIPAddr(ServerBrowser sb); + +/* ServerBrowserSendNatNegotiateCookieToServer +------------------ +Sends a cookie value to the server for use with NAT Negotiation */ +SBError ServerBrowserSendNatNegotiateCookieToServer(ServerBrowser sb, + const gsi_char* ip, + unsigned short port, + int cookie); + +/* ServerBrowserSendMessageToServer +------------------ +Sends a game-specific message to a server */ +SBError ServerBrowserSendMessageToServer(ServerBrowser sb, const gsi_char* ip, + unsigned short port, const char* data, + int len); + +/* ServerBrowserConnectToServer +------------------ +Attempts to connect to the server, using natneg if necessary */ +SBError ServerBrowserConnectToServer(ServerBrowser sb, SBServer server, + SBConnectToServerCallback callback); + +/* Comparision types for the ServerBrowserSort function +int - assume the values are int and do an integer compare +float - assume the values are float and do a flot compare +strcase - assume the values are strings and do a case sensitive compare +stricase - assume the values are strings and do a case insensitive compare */ +typedef enum { + sbcm_int, + sbcm_float, + sbcm_strcase, + sbcm_stricase +} SBCompareMode; + +/* ServerBrowserSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void ServerBrowserSort(ServerBrowser sb, SBBool ascending, + const gsi_char* sortkey, SBCompareMode comparemode); + +/* ServerBrowserLANSetLocalAddr +------------------- +Sets the network adapter to use for LAN broadcasts (optional) */ +void ServerBrowserLANSetLocalAddr(ServerBrowser sb, const char* theAddr); + +/******************* +SBServer Object Functions +********************/ + +// Callback function used for enumerating the keys/values for a server +typedef void (*SBServerKeyEnumFn)(gsi_char* key, gsi_char* value, + void* instance); + +/* SBServerGetConnectionInfo +---------------- +Check if Nat Negotiation is requires, based off whether it is a lan game, public +ip present and several other facts. Returns an IP string to use for NatNeg, or +direct connect if possible Work for subsequent connection to this server, One of +three results will occur i) Lan game, connect using ipstring 2) Internet game, +connect using ipstring 3) nat traversal required, perform nat negotiation using +Nat SDK and this ipstring before connecting. + +return sb_true if further processing is required... i.e. NAT. sb_false if not. +fills an IP string +*/ +SBBool SBServerGetConnectionInfo(ServerBrowser gSB, SBServer server, + gsi_u16 PortToConnectTo, char* ipstring_out); + +/* SBServerHasPrivateAddress +---------------- +Indicates whether the master server has provided a private address for this +server */ +SBBool SBServerHasPrivateAddress(SBServer server); + +/* SBServerDirectConnect +---------------- +Indicates whether the server supports direct UDP connections (if false, NAT +Negotiation is required) */ +SBBool SBServerDirectConnect(SBServer server); + +/* SBServerGetPing +---------------- +Returns the ping for the specified server. Ping is measured as the latency from +the time a +query is sent to the server until the data is returned for that query */ +int SBServerGetPing(SBServer server); + +/* SBServerGetPublicAddress/SBServerGetPrivateAddress +------------------- +Returns the string, dotted IP address for the specified server +The "private" version is only valid when the server has a private address +available */ +char* SBServerGetPublicAddress(SBServer server); +char* SBServerGetPrivateAddress(SBServer server); + +/* SBServerGetPublicInetAddress/SBServerGetPrivateInetAddress +------------------- +Returns the network-ordered IP address for the specified server */ +unsigned int SBServerGetPublicInetAddress(SBServer server); +unsigned int SBServerGetPrivateInetAddress(SBServer server); + +/* SBServerGetPublicQueryPort/SBServerGetPrivateQueryPort +---------------- +Returns the "query" port for the specified server. If the game uses a seperate +"game" port, it can be retrieved via: SBServerGetIntValue(server,"hostport",0) +*/ +unsigned short SBServerGetPublicQueryPort(SBServer server); +unsigned short SBServerGetPrivateQueryPort(SBServer server); + +/* SBServerHasBasicKeys +---------------- +Returns SBTrue if a server has at least basic keys available for it. "Basic" +keys are those indicated in the ServerBrowserUpdate function. */ +SBBool SBServerHasBasicKeys(SBServer server); + +/* SBServerHasFullKeys +---------------- +Returns SBTrue if a server has full keys available for it. This includes all +server rules and player/team keys. */ +SBBool SBServerHasFullKeys(SBServer server); + +/* SBServerHasValidPing +---------------- +Returns SBTrue if a server has a valid ping value for it (otherwise the ping +will be 0) */ +SBBool SBServerHasValidPing(SBServer server); + +/* SBServerGet[]Value +------------------ +Returns the value for the specified key. If the key does not exist for the +given server, the default value is returned */ +const gsi_char* SBServerGetStringValue(SBServer server, const gsi_char* keyname, + const gsi_char* def); +int SBServerGetIntValue(SBServer server, const gsi_char* key, int idefault); +double SBServerGetFloatValue(SBServer server, const gsi_char* key, + double fdefault); +SBBool SBServerGetBoolValue(SBServer server, const gsi_char* key, + SBBool bdefault); + +/* SBServerGetPlayer[]Value / SBServerGetTeam[]Value +------------------ +Returns the value for the specified key on the specified player or team. If the +key does not exist for the given server, the default value is returned Player +keys take the form keyname_N where N is the player index, and team keys take the +form keyname_tN where N is the team index. You should only specify the keyname +for the key in the below functions. +*/ +const gsi_char* SBServerGetPlayerStringValue(SBServer server, int playernum, + const gsi_char* key, + const gsi_char* sdefault); +int SBServerGetPlayerIntValue(SBServer server, int playernum, + const gsi_char* key, int idefault); +double SBServerGetPlayerFloatValue(SBServer server, int playernum, + const gsi_char* key, double fdefault); + +const gsi_char* SBServerGetTeamStringValue(SBServer server, int teamnum, + const gsi_char* key, + const gsi_char* sdefault); +int SBServerGetTeamIntValue(SBServer server, int teamnum, const gsi_char* key, + int idefault); +double SBServerGetTeamFloatValue(SBServer server, int teamnum, + const gsi_char* key, double fdefault); + +/* SBServerEnumKeys +----------------- +Enumerates the keys/values for a given server by calling KeyEnumFn with each +key/value. The user-defined instance data will be passed to the KeyFn callback +*/ +void SBServerEnumKeys(SBServer server, SBServerKeyEnumFn KeyFn, void* instance); + +#ifdef __cplusplus +} +#endif diff --git a/source/gamespy/serverbrowsing/sb_serverlist.c b/source/gamespy/serverbrowsing/sb_serverlist.c new file mode 100644 index 000000000..d4c116de9 --- /dev/null +++ b/source/gamespy/serverbrowsing/sb_serverlist.c @@ -0,0 +1,1667 @@ +// clang-format off +#include "sb_internal.h" +#include "sb_ascii.h" +#include "sb_serverbrowsing.h" +// clang-format on + +#include + +#define SERVER_GROWBY 100 + +// for the master server info +#define INCOMING_BUFFER_SIZE 4096 + +#define MAX_OUTGOING_REQUEST_SIZE (MAX_FIELD_LIST_LEN + MAX_FILTER_LEN + 255) + +static SBServerList* g_sortserverlist; // global serverlist for sorting info!! + +// private function used to compare the key values based on a previously defined +// sortkey +static int prevKeyCompare(SBServer server1, SBServer server2) { + const char* prevsortkey = (const char*)g_sortserverlist->prevsortinfo.sortkey; + int diff; + double f; + // test which type of sort + switch (g_sortserverlist->prevsortinfo.comparemode) { + case sbcm_int: + diff = SBServerGetIntValueA(server1, prevsortkey, 0) - + SBServerGetIntValueA(server2, prevsortkey, 0); + break; + case sbcm_float: + f = SBServerGetFloatValueA(server1, prevsortkey, 0) - + SBServerGetFloatValueA(server2, prevsortkey, 0); + if (!g_sortserverlist->sortascending) + f = -f; + if ((float)f > (float)0.0) + return 1; + else if ((float)f < (float)0.0) + return -1; + else + return 0; + // break; + case sbcm_strcase: + diff = strcmp(SBServerGetStringValueA(server1, prevsortkey, ""), + SBServerGetStringValueA(server2, prevsortkey, "")); + break; + case sbcm_stricase: + diff = strcasecmp(SBServerGetStringValueA(server1, prevsortkey, ""), + SBServerGetStringValueA(server2, prevsortkey, "")); + break; + default: + return 0; + } + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + +/**** +Comparision Functions +***/ +static int GS_STATIC_CALLBACK IntKeyCompare(const void* entry1, + const void* entry2) { + SBServer server1 = *(SBServer*)entry1, server2 = *(SBServer*)entry2; + int diff; + const char* currsortkey = (const char*)g_sortserverlist->currsortinfo.sortkey; + + diff = SBServerGetIntValueA(server1, currsortkey, 0) - + SBServerGetIntValueA(server2, currsortkey, 0); + + if (diff == 0) // if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + +static int GS_STATIC_CALLBACK FloatKeyCompare(const void* entry1, + const void* entry2) { + SBServer server1 = *(SBServer*)entry1, server2 = *(SBServer*)entry2; + double f; + const char* currsortkey = (const char*)g_sortserverlist->currsortinfo.sortkey; + + f = SBServerGetFloatValueA(server1, currsortkey, 0) - + SBServerGetFloatValueA(server2, currsortkey, 0); + + // if equal, sort by previous sort value to retain earlier sort + if (!((float)f > (float)0.0) && !((float)f < (float)0.0)) + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + f = -f; + if ((float)f > (float)0.0) + return 1; + else if ((float)f < (float)0.0) + return -1; + else + return 0; +} + +static int GS_STATIC_CALLBACK StrCaseKeyCompare(const void* entry1, + const void* entry2) { + SBServer server1 = *(SBServer*)entry1, server2 = *(SBServer*)entry2; + int diff; + const char* currsortkey = (const char*)g_sortserverlist->currsortinfo.sortkey; + + diff = strcmp(SBServerGetStringValueA(server1, currsortkey, ""), + SBServerGetStringValueA(server2, currsortkey, "")); + + if (diff == 0) // if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + +static int GS_STATIC_CALLBACK StrNoCaseKeyCompare(const void* entry1, + const void* entry2) { + SBServer server1 = *(SBServer*)entry1, server2 = *(SBServer*)entry2; + int diff; + const char* currsortkey = (const char*)g_sortserverlist->currsortinfo.sortkey; + + diff = strcasecmp(SBServerGetStringValueA(server1, currsortkey, ""), + SBServerGetStringValueA(server2, currsortkey, "")); + + if (diff == 0) // if equal, sort by previous sort value to retain earlier sort + return prevKeyCompare(server1, server2); + + if (!g_sortserverlist->sortascending) + diff = -diff; + return diff; +} + +/* ServerListSort +----------------- +Sort the server list in either ascending or descending order using the +specified comparemode. +sortkey can be a normal server key, or "ping" or "hostaddr" */ +void SBServerListSort(SBServerList* slist, SBBool ascending, + SortInfo sortinfo) { + ArrayCompareFn comparator; + switch (sortinfo.comparemode) { + case sbcm_int: + comparator = IntKeyCompare; + break; + case sbcm_float: + comparator = FloatKeyCompare; + break; + case sbcm_strcase: + comparator = StrCaseKeyCompare; + break; + case sbcm_stricase: + comparator = StrNoCaseKeyCompare; + break; + default: + comparator = StrNoCaseKeyCompare; + } + + // set last used sortkey + if (_tcslen(slist->prevsortinfo.sortkey) == + 0) // set prev to current if not initialized + slist->prevsortinfo = sortinfo; + else if (strcmp((const char*)sortinfo.sortkey, + (const char*)slist->currsortinfo.sortkey) != 0) + slist->prevsortinfo = + slist->currsortinfo; // only set new sort if different than prev + + slist->currsortinfo = sortinfo; + slist->sortascending = ascending; + g_sortserverlist = slist; + ArraySort(slist->servers, comparator); +} + +void SBServerListAppendServer(SBServerList* slist, SBServer server) { + ArrayAppend(slist->servers, &server); + slist->ListCallback(slist, slc_serveradded, server, slist->instance); +} + +int SBServerListFindServer(SBServerList* slist, SBServer findserver) { + int numservers; + int i; + numservers = ArrayLength(slist->servers); + for (i = 0; i < numservers; i++) { + if (findserver == *(SBServer*)ArrayNth(slist->servers, i)) { + return i; + } + } + return -1; +} + +int SBServerListFindServerByIP(SBServerList* slist, goa_uint32 ip, + unsigned short port) { + int numservers; + SBServer server; + int i; + numservers = ArrayLength(slist->servers); + for (i = 0; i < numservers; i++) { + server = *(SBServer*)ArrayNth(slist->servers, i); + if (SBServerGetPublicInetAddress(server) == ip && + SBServerGetPublicQueryPortNBO(server) == port) { + return i; + } + } + return -1; +} + +// add to the singly linked list of dead servers +static void AddServerToDeadlist(SBServerList* slist, SBServer server) { + if (slist->deadlist == NULL) { + SBServerSetNext(server, NULL); + } else + SBServerSetNext(server, slist->deadlist); + slist->deadlist = server; +} + +void SBServerListRemoveAt(SBServerList* slist, int index) { + SBServer server = *(SBServer*)ArrayNth(slist->servers, index); + slist->ListCallback(slist, slc_serverdeleted, server, slist->instance); + // need to remove it... + ArrayDeleteAt(slist->servers, index); + // now add it to the dead list + AddServerToDeadlist(slist, server); +} + +int SBServerListCount(SBServerList* slist) { + return ArrayLength(slist->servers); +} + +SBServer SBServerListNth(SBServerList* slist, int i) { + return *(SBServer*)ArrayNth(slist->servers, i); +} + +void SBFreeDeadList(SBServerList* slist) { + SBServer server, next; + if (slist->deadlist == NULL) + return; + server = slist->deadlist; + while (server != NULL) { + next = SBServerGetNext(server); + SBServerFree(&server); + server = next; + } + slist->deadlist = NULL; +} + +void SBServerListClear(SBServerList* slist) { + // we need to add each server to the dead list so it can be freed after the + // clear + int i; + int nservers = ArrayLength(slist->servers); + for (i = 0; i < nservers; i++) { + AddServerToDeadlist(slist, *(SBServer*)ArrayNth(slist->servers, i)); + } + ArrayClear(slist->servers); + // now free the dead list + SBFreeDeadList(slist); +} + +void SBAllocateServerList(SBServerList* slist) { + slist->servers = ArrayNew( + sizeof(SBServer), SERVER_GROWBY, + NULL); // don't free the server automatically - it goes into the dead list + slist->deadlist = NULL; +} + +const char* SBRefStr(SBServerList* slist, const char* str) { + SBRefString ref, *val; + ref.str = str; + val = (SBRefString*)TableLookup(SBRefStrHash(slist), &ref); + if (val != NULL) { + val->refcount++; + return val->str; + } + + // else we need to add. + ref.str = goastrdup(str); + ref.refcount = 1; + TableEnter(SBRefStrHash(slist), &ref); + return ref.str; +} + +void SBReleaseStr(SBServerList* slist, const char* str) { + SBRefString ref, *val; + ref.str = str; + val = (SBRefString*)TableLookup(SBRefStrHash(slist), &ref); + assert(val != NULL); + if (val == NULL) + return; // not found! + val->refcount--; + if (val->refcount == 0) + TableRemove(SBRefStrHash(slist), &ref); +} + +#ifdef VENGINE_SUPPORT +#define FTABLE_ASSIGN +#include "../../VEngine/ve_gm3ftable.h" +#endif + +#ifdef VENGINE_SUPPORT +#define FTABLE_DEFINES +#include "../../VEngine/ve_gm3ftable.h" +#endif + +// global pointer to alternate master server name/IP +char* SBOverrideMasterServer = NULL; + +int NTSLengthSB(char* buf, int len) { + int i; + for (i = 0; i < len; i++) { + if (buf[i] == '\0') // found a full NTS + return i + 1; // return the length including the null + } + return -1; +} + +void SBServerListInit(SBServerList* slist, const char* queryForGamename, + const char* queryFromGamename, const char* queryFromKey, + int queryFromVersion, SBBool lanBrowse, + SBListCallBackFn callback, void* instance) { + assert(slist != NULL); + // 11-03-2004 : Added by Saad Nader + // fix for LANs and unnecessary availability check + /////////////////////////////////////////////////// + if (lanBrowse == SBFalse) { + if (__GSIACResult != GSIACAvailable) + return; + } + +#ifdef VENGINE_SUPPORT + SBServerListSetPointers(slist); +#endif + + slist->state = sl_disconnected; + SBAllocateServerList(slist); + SBRefStrHash(slist); // make sure it's initialized + strcpy(slist->queryforgamename, queryForGamename); + strcpy(slist->queryfromgamename, queryFromGamename); + strcpy(slist->queryfromkey, queryFromKey); + slist->ListCallback = callback; + slist->MaploopCallback = NULL; // populate when requested + assert(callback != NULL); + slist->instance = instance; + slist->mypublicip = 0; + slist->slsocket = INVALID_SOCKET; + slist->inbuffer = NULL; + slist->inbufferlen = 0; + slist->keylist = NULL; + slist->expectedelements = -1; + slist->numpopularvalues = 0; + slist->srcip = 0; + slist->fromgamever = queryFromVersion; + _tcscpy(slist->currsortinfo.sortkey, ""); + _tcscpy(slist->prevsortinfo.sortkey, ""); + SBSetLastListErrorPtr(slist, ""); + slist->mLanAdapterOverride = NULL; + slist->backendgameflags = QR2_USE_QUERY_CHALLENGE; + + srand((unsigned int)current_time()); + SocketStartUp(); +} + +static void ErrorDisconnect(SBServerList* slist) { + static char* QUERY_ERROR = "Query Error: "; + + // check to see if there is a Query Error string in the inbuffer + if ((slist->inbufferlen > 0) && + ((unsigned int)slist->inbufferlen > strlen(QUERY_ERROR)) && + (0 == strncmp(slist->inbuffer, QUERY_ERROR, strlen(QUERY_ERROR)))) { + // call the callback with queryerror first + SBSetLastListErrorPtr(slist, slist->inbuffer + strlen(QUERY_ERROR)); + slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance); + } + + // call the callback.. + slist->ListCallback(slist, slc_disconnected, SBNullServer, slist->instance); + SBServerListDisconnect(slist); +} + +#define MULTIPLIER -1664117991 +static int StringHash(const char* s, int numbuckets) { + goa_uint32 hashcode = 0; + while (*s != 0) { + hashcode = (goa_uint32)((int)hashcode * MULTIPLIER + tolower(*s)); + s++; + } + return (int)(hashcode % numbuckets); +} + +static SBError ServerListConnect(SBServerList* slist) { + struct sockaddr_in saddr; + struct hostent* hent; + char masterHostname[128]; + int masterIndex; + + masterIndex = StringHash(slist->queryforgamename, NUM_MASTER_SERVERS); + if (SBOverrideMasterServer != NULL) + strcpy(masterHostname, SBOverrideMasterServer); + else // use the default format... + sprintf(masterHostname, "%s.ms%d." GSI_DOMAIN_NAME, slist->queryforgamename, + masterIndex); + saddr.sin_family = AF_INET; + saddr.sin_port = htons(MSPORT2); + saddr.sin_addr.s_addr = inet_addr(masterHostname); + if (saddr.sin_addr.s_addr == INADDR_NONE) { + hent = gethostbyname(masterHostname); + if (!hent) + return sbe_dnserror; + memcpy(&saddr.sin_addr.s_addr, hent->h_addr_list[0], + sizeof(saddr.sin_addr.s_addr)); + } + + if (slist->slsocket == INVALID_SOCKET) { + slist->slsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (slist->slsocket == INVALID_SOCKET) + return sbe_socketerror; + } + if (connect(slist->slsocket, (struct sockaddr*)&saddr, sizeof saddr) != 0) { + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + return sbe_connecterror; + } + + // else we are connected + return sbe_noerror; +} + +static void BufferAddNTS(char** buffer, const char* str, int* len) { + int slen; + if (str == NULL) + str = ""; + slen = (int)strlen(str) + 1; + memcpy(*buffer, str, (size_t)slen); + (*len) += slen; + (*buffer) += slen; +} + +static void BufferAddByte(char** buffer, unsigned char bval, int* len) { + *(*buffer) = (char)bval; + (*len) += 1; + (*buffer) += 1; +} + +static void BufferAddInt(char** buffer, int ival, int* len) { + memcpy(*buffer, &ival, 4); + (*len) += 4; + (*buffer) += 4; +} + +// This a utility to write ival as a little-endian (PC) byte order number +static void BufferAddIntLE(char** buffer, int ival, int* len) { + unsigned int ivalNBO = htonl(ival); + unsigned int ivalBE = 0; + ivalBE |= ((0x000000FF & ivalNBO) << 24); + ivalBE |= ((0x0000FF00 & ivalNBO) << 8); + ivalBE |= ((0x00FF0000 & ivalNBO) >> 8); + ivalBE |= ((0xFF000000 & ivalNBO) >> 24); + BufferAddInt(buffer, (int)ivalBE, len); +} + +static void BufferAddData(char** buffer, char* data, int dlen, int* len) { + memcpy(*buffer, data, (size_t)dlen); + (*len) += dlen; + (*buffer) += dlen; +} + +#define CALCULATEODDMODE(buffer, i, oddmode) \ + ((buffer[i - 1] & 1) ^ (i & 1) ^ oddmode ^ (buffer[0] & 1) ^ \ + ((buffer[0] < 79) ? 1 : 0) ^ ((buffer[i - 1] < buffer[0]) ? 1 : 0)); +static void SetupListChallenge(SBServerList* slist) { + int i; + int oddmode; + + slist->mychallenge[0] = + (char)(33 + rand() % 93); // use chars in the range 33 - 125 + oddmode = 0; + for (i = 1; i < LIST_CHALLENGE_LEN; i++) { + oddmode = CALCULATEODDMODE(slist->mychallenge, i, oddmode); + slist->mychallenge[i] = + (char)(33 + rand() % 93); // use chars in the range 33 - 125 + // if oddmode make sure the char is odd, otherwise make sure it's even + if ((oddmode && (slist->mychallenge[i] & 1) == 0) || + (!oddmode && ((slist->mychallenge[i] & 1) == 1))) + slist->mychallenge[i]++; + } +} + +static SBError SendWithRetry(SBServerList* slist, char* data, int len) { + SBError err; + int ret = 0; + + int retryCount = 1; + + while (retryCount >= 0) { + retryCount--; + ret = send(slist->slsocket, data, len, 0); + if (ret <= 0 && retryCount >= 0) // error! try to reconnect + { + if (slist->inbufferlen > 0) + break; + else + SBServerListDisconnect(slist); + err = SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (err != sbe_noerror) { + ErrorDisconnect(slist); + return err; // couldn't reconnect + } + } else + break; // send ok + } + +#ifdef INSOCK + GSI_UNUSED(data); + GSI_UNUSED(len); +#endif + + if (ret <= 0) + return sbe_connecterror; + else + return sbe_noerror; +} + +/* +1 bytes - message type (0) +2 bytes - message length +Protocol Version - Byte +Requested Encoding - Byte +Query For Game - NTS +Query From Game - NTS +Server Challenge - 8 bytes +Filter - NTS +Field List - NTS +Options - 4 bytes +Send Fields for non-NAT servers too (TurboQuery!) +No-server-slist-requested (just initiate connection and/or receive push updates) +Push Live-Updates +Log Alternate Source IP +*/ +#define ALTERNATE_SOURCE_IP 8 + +SBError SBServerListConnectAndQuery(SBServerList* slist, const char* fieldList, + const char* serverFilter, int options, + int maxServers) { + SBError err; + int ret; + int requestLen; + unsigned short netLen; + char* requestBuf; + char outgoingRequest[MAX_OUTGOING_REQUEST_SIZE + 1]; + assert(slist->state == sl_disconnected); + + if (fieldList == NULL) + fieldList = ""; + if (serverFilter == NULL) + serverFilter = ""; + + if (strlen(fieldList) > MAX_FIELD_LIST_LEN) + return sbe_paramerror; + + if (strlen(serverFilter) > MAX_FILTER_LEN) + return sbe_paramerror; + + err = ServerListConnect(slist); + if (err != sbe_noerror) + return err; + + slist->queryoptions = options; + + // setup our challenge value + SetupListChallenge(slist); + + // skip the length field for now + requestLen = 2; + requestBuf = outgoingRequest + 2; + + BufferAddByte(&requestBuf, SERVER_LIST_REQUEST, &requestLen); + BufferAddByte(&requestBuf, LIST_PROTOCOL_VERSION, &requestLen); + BufferAddByte(&requestBuf, LIST_ENCODING_VERSION, &requestLen); + BufferAddIntLE(&requestBuf, slist->fromgamever, &requestLen); + BufferAddNTS(&requestBuf, slist->queryforgamename, &requestLen); + BufferAddNTS(&requestBuf, slist->queryfromgamename, &requestLen); + BufferAddData(&requestBuf, slist->mychallenge, LIST_CHALLENGE_LEN, + &requestLen); + BufferAddNTS(&requestBuf, serverFilter, &requestLen); + BufferAddNTS(&requestBuf, fieldList, &requestLen); + options = (int)htonl((unsigned int)options); + BufferAddInt(&requestBuf, options, &requestLen); + if (slist->queryoptions & ALTERNATE_SOURCE_IP) { + BufferAddInt(&requestBuf, (int)slist->srcip, &requestLen); + } + if (slist->queryoptions & LIMIT_RESULT_COUNT) { + BufferAddIntLE(&requestBuf, maxServers, &requestLen); + } + netLen = htons((unsigned short)requestLen); + memcpy(outgoingRequest, &netLen, 2); // set the length... + + // now send! + ret = send(slist->slsocket, outgoingRequest, requestLen, 0); + if (ret <= 0) { + SBServerListDisconnect(slist); + return sbe_connecterror; + } + slist->state = sl_mainlist; + slist->pstate = pi_cryptheader; + // allocate an incoming buffer + if (slist->inbuffer == NULL) { + slist->inbuffer = (char*)gsimalloc(INCOMING_BUFFER_SIZE); + if (slist->inbuffer == NULL) + return sbe_allocerror; + slist->inbufferlen = 0; + } + + return sbe_noerror; +} + +SBError SBServerListGetLANList(SBServerList* slist, unsigned short startport, + unsigned short endport, int queryversion) { + struct sockaddr_in saddr; + unsigned short i; + unsigned char qr2_echo_request[] = {QR2_MAGIC_1, QR2_MAGIC_2, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00}; + int qr2requestlen = sizeof(qr2_echo_request) / sizeof(qr2_echo_request[0]); + if (slist->state != sl_disconnected) + SBServerListDisconnect(slist); + + slist->slsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (slist->slsocket == INVALID_SOCKET) + return sbe_socketerror; + +// enable broadcasting where needed +#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION) + { + int optval = 1; + if (setsockopt(slist->slsocket, SOL_SOCKET, SO_BROADCAST, (char*)&optval, + sizeof(optval)) != 0) + return sbe_socketerror; + } +#endif + + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = gsiGetBroadcastIP(); // broadcast + + // Added for multihomed machines. Allows the user to specify which adapter to + // use + if (slist->mLanAdapterOverride != NULL) { + struct sockaddr_in aClassCAddr; + aClassCAddr.sin_family = AF_INET; + aClassCAddr.sin_addr.s_addr = inet_addr(slist->mLanAdapterOverride); + aClassCAddr.sin_port = 0; // bind to any port + if (0 != bind(slist->slsocket, (struct sockaddr*)&aClassCAddr, + sizeof(aClassCAddr))) + return sbe_socketerror; + } + + if (endport - startport > 500) // the max we will search + endport = (unsigned short)(startport + 500); + for (i = startport; i <= endport; i += 1) { + saddr.sin_port = htons(i); + if (queryversion == QVERSION_QR2) // send a QR2 echo request + sendto(slist->slsocket, (char*)qr2_echo_request, qr2requestlen, 0, + (struct sockaddr*)&saddr, sizeof(saddr)); + else // send a GOA echo request + sendto(slist->slsocket, "\\echo\\test", 10, 0, (struct sockaddr*)&saddr, + sizeof(saddr)); + } + slist->state = sl_lanbrowse; + slist->lanstarttime = current_time(); + return sbe_noerror; +} + +static void FreePopularValues(SBServerList* slist) { + int i; + for (i = 0; i < slist->numpopularvalues; i++) + SBReleaseStr(slist, slist->popularvalues[i]); + slist->numpopularvalues = 0; +} + +static void FreeKeyList(SBServerList* slist) { + int i; + if (slist->keylist == NULL) + return; + for (i = 0; i < ArrayLength(slist->keylist); i++) + SBReleaseStr(slist, ((KeyInfo*)ArrayNth(slist->keylist, i))->keyName); + + ArrayFree(slist->keylist); + slist->keylist = NULL; +} + +void SBServerListDisconnect(SBServerList* slist) { + if (slist->inbuffer != NULL) + gsifree(slist->inbuffer); + slist->inbuffer = NULL; + slist->inbufferlen = 0; + if (slist->slsocket != INVALID_SOCKET) + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + slist->state = sl_disconnected; + + FreeKeyList(slist); + slist->expectedelements = -1; + FreePopularValues(slist); +} + +void SBServerListCleanup(SBServerList* slist) { + SBServerListDisconnect(slist); + SBServerListClear(slist); + SBRefStrHashCleanup(slist); + if (slist->servers) + ArrayFree(slist->servers); + slist->servers = NULL; +} + +static void InitCryptKey(SBServerList* slist, char* key, int keylen) { + // combine our secret key, our challenge, and the server's challenge into a + // crypt key + int i; + int seckeylen = (int)strlen(slist->queryfromkey); + char* seckey = slist->queryfromkey; + for (i = 0; i < keylen; i++) { + slist->mychallenge[(i * seckey[i % seckeylen]) % LIST_CHALLENGE_LEN] ^= + (char)((slist->mychallenge[i % LIST_CHALLENGE_LEN] ^ key[i]) & 0xFF); + } + GOACryptInit(&(slist->cryptkey), (unsigned char*)(slist->mychallenge), + LIST_CHALLENGE_LEN); +} + +static int ServerSizeForFlags(int flags) { + int size = 5; // all servers are at least 5 .. + if (flags & PRIVATE_IP_FLAG) + size += 4; + if (flags & ICMP_IP_FLAG) + size += 4; + if (flags & NONSTANDARD_PORT_FLAG) + size += 2; + if (flags & NONSTANDARD_PRIVATE_PORT_FLAG) + size += 2; + return size; +} + +static int FullRulesPresent(char* buf, int len) { + int i; + while (len > 0 && *buf) { + i = NTSLengthSB(buf, len); + if (i < 0) + return 0; // no full key + buf += i; + len -= i; + i = NTSLengthSB(buf, len); + if (i < 0) + return 0; // no full value + buf += i; + len -= i; + } + if (len == 0) + return 0; // not even enough space for the null term + if (*buf == 0) + return 1; // all there + return 0; +} + +// checks to see if all the keys for the given servers are there +static int AllKeysPresent(SBServerList* slist, char* buf, int len) { + int numkeys; + int i; + int strindex; + numkeys = ArrayLength(slist->keylist); + for (i = 0; i < numkeys; i++) { + switch (((KeyInfo*)ArrayNth(slist->keylist, i))->keyType) { + case KEYTYPE_BYTE: + buf++; + len--; + break; + case KEYTYPE_SHORT: + buf += 2; + len -= 2; + break; + case KEYTYPE_STRING: + if (len < 1) + return 0; // not enough + strindex = (unsigned char)(buf[0]); + buf++; + len--; + if (strindex == 0xFF) // a NTS string + { + strindex = NTSLengthSB(buf, len); + if (strindex == -1) // not all there.. + return 0; + buf += strindex; + len -= strindex; + } // else it's a popular string - just the index is present + break; + default: + assert(0); + return 0; // error - unknown key type + } + if (len < 0) + return 0; // not enough.. + } + return 1; +} + +#define LAST_SERVER_MARKER "\xFF\xFF\xFF\xFF" +#define SERVER_MARKER_LEN 4 + +// parse only the IP/port from the server buffer +static void ParseServerIPPort(SBServerList* slist, char* buf, int len, + goa_uint32* ip, unsigned short* port) { + unsigned char flags; + if (len < 5) + return; // invalid buffer + flags = (unsigned char)buf[0]; + buf++; + len--; + memcpy(ip, buf, 4); + + buf += 4; + len -= 4; + + if (flags & NONSTANDARD_PORT_FLAG) { + if (len < 2) + return; // invalid buffer + memcpy(port, buf, 2); + } else + *port = slist->defaultport; +} + +// parse a server buffer +static int ParseServer(SBServerList* slist, SBServer server, char* buf, int len, + int usepopularlist) { + int numkeys; + int i; + short sval; + int strindex; + int holdlen = len; + goa_uint32 ip; + unsigned short port; + unsigned char flags; + + flags = (unsigned char)buf[0]; + SBServerSetFlags(server, flags); + buf++; + len--; + + // skip the IP, it's already set + buf += 4; + len -= 4; + + // skip the port, if needed + if (flags & NONSTANDARD_PORT_FLAG) { + buf += 2; + len -= 2; + } + + if (flags & PRIVATE_IP_FLAG) { + memcpy(&ip, buf, 4); + buf += 4; + len -= 4; + } else + ip = 0; + if (flags & NONSTANDARD_PRIVATE_PORT_FLAG) { + memcpy(&port, buf, 2); + buf += 2; + len -= 2; + } else + port = slist->defaultport; + SBServerSetPrivateAddr(server, ip, port); + if (flags & ICMP_IP_FLAG) { + memcpy(&ip, buf, 4); + buf += 4; + len -= 4; + SBServerSetICMPIP(server, ip); + } + + if (flags & HAS_KEYS_FLAG) // parse the keys + { + numkeys = ArrayLength(slist->keylist); + for (i = 0; i < numkeys; i++) { + KeyInfo* ki = (KeyInfo*)ArrayNth(slist->keylist, i); + switch (ki->keyType) { + case KEYTYPE_BYTE: + SBServerAddIntKeyValue(server, ki->keyName, (unsigned char)buf[0]); + buf++; + len--; + break; + case KEYTYPE_SHORT: + memcpy(&sval, buf, 2); + SBServerAddIntKeyValue(server, ki->keyName, + ntohs((unsigned short)sval)); + buf += 2; + len -= 2; + break; + case KEYTYPE_STRING: + if (usepopularlist) { + strindex = (unsigned char)(buf[0]); + buf++; + len--; + } else + strindex = 0xFF; + if (strindex == 0xFF) // a NTS string + { + SBServerAddKeyValue(server, ki->keyName, buf); + strindex = (int)strlen(buf) + 1; + buf += strindex; + len -= strindex; + } else // else it's a popular string - just the index is present + { + SBServerAddKeyValue(server, ki->keyName, + slist->popularvalues[strindex]); + } + break; + } + } + SBServerSetState( + server, (unsigned char)(SBServerGetState(server) | STATE_BASICKEYS)); + } + if (flags & HAS_FULL_RULES_FLAG) // got the full rules in there + { + while (*buf && len > 0) { + char* k = buf; + i = (int)strlen(k) + 1; + buf += i; + len -= i; + SBServerAddKeyValue(server, k, buf); + i = (int)strlen(buf) + 1; + buf += i; + len -= i; + } + buf++; + len--; // get the last null out + SBServerSetState( + server, (unsigned char)(SBServerGetState(server) | STATE_FULLKEYS)); + } + + // the server was an empty server message, thus it should be cleared of + // basic key and full key states + { + unsigned char state = SBServerGetState(server); + if ((flags & (HAS_FULL_RULES_FLAG | HAS_KEYS_FLAG)) == 0 && + ((state & STATE_BASICKEYS) | (state & STATE_FULLKEYS))) { + state &= (unsigned char)~(STATE_BASICKEYS | STATE_FULLKEYS); + SBServerSetState(server, state); + } + } + return holdlen - len; +} + +static int IncomingListParseServer(SBServerList* slist, char* buf, int len) { + int i; + goa_uint32 ip; + unsigned short port; + SBServer server; + unsigned char flags; + // fields depends on the flags.. + if (len < 1) + return 0; + flags = (unsigned char)(buf[0]); + i = ServerSizeForFlags(flags); + if (len < i) + return 0; + if (flags & HAS_KEYS_FLAG) { + if (!AllKeysPresent(slist, buf + i, len - i)) + return 0; + } + if (flags & HAS_FULL_RULES_FLAG) { + if (!FullRulesPresent(buf + i, len - i)) + return 0; + } + // else we have a whole server! + // see if it's the "last" server (0xffffffff) + if (memcmp(buf + 1, LAST_SERVER_MARKER, SERVER_MARKER_LEN) == 0) + return -1; + ParseServerIPPort(slist, buf, len, &ip, &port); + server = SBAllocServer(slist, ip, port); + if (SBIsNullServer(server)) + return -2; + i = ParseServer(slist, server, buf, len, 1); + SBServerListAppendServer(slist, server); + return i; +} + +const char* SBLastListErrorA(SBServerList* slist) { return slist->lasterror; } + +void SBSetLastListErrorPtr(SBServerList* slist, char* theError) { + slist->lasterror = theError; +} + +#define FIXED_HEADER_LEN 6 + +/* +--challenge header +1 byte - number of random bytes to follow xor 0xEC +random bytes +1 byte - length xor 0xEA +keylen bytes - server challenge +--fixed header +YOUR public IP - 4 bytes +Standard query port - 2 bytes - or 0xFFFF if the master does not know about this +gamename yet [rest only follows if they requested a server slist] +--key slist +Number of Keys - 1 byte +List of Keys - with key type +Key Type - 1 Byte (0 = string, 1 = byte, 2 = short) +KeyName - NTS +--unique value slist +number of unique string values - 1 byte +Table of Unique String Values (255 most popular string keys) +Key NTS +--servers +Server Table (for each server - terminated with IP of 0xFFFFFFFF, flags 0) +1 byte flags +-Connect negotiate support? +-Has private IP +-Unsolicited UDP support? +-Has ICMP IP +-Nonstandard Public Port +-Nonstandard Private Port +-Has Keys +4 bytes public ip +[optional] +4 bytes port +[optional] +4 bytes private IP +[optional] +4 bytes private port +[optional] +4 bytes ICMP IP +[optional - keys] +String values +1-bytes - value ID +or +FF + valuestring + 00 +Byte Values + 1 byte - value + Short values? + 2 bytes - value +*/ + +static SBError ProcessMainListData(SBServerList* slist) { + int reqlen; + int keyoffset; + int keylen; + char* inbuf = slist->inbuffer; + int inlen = slist->inbufferlen; + switch (slist->pstate) { + case pi_cryptheader: + if (inlen < 1) + break; + reqlen = (((unsigned char)(inbuf[0])) ^ 0xEC) + 2; + if (inlen < reqlen) + break; // not there yet + keyoffset = reqlen; + keylen = ((unsigned char)(inbuf[reqlen - 1])) ^ 0xEA; + reqlen += keylen; + if (inlen < reqlen) + break; // not there yet + // otherwise we have the whole crypt header and can init our crypt key + InitCryptKey(slist, inbuf + keyoffset, keylen); + slist->pstate = pi_fixedheader; + + // the first byte of the "random" data is actually + // the game options flag + memcpy(&slist->backendgameflags, &inbuf[1], 2); + slist->backendgameflags = ntohs(slist->backendgameflags); + + inbuf += reqlen; + inlen -= reqlen; + // decrypt any remaining data! + GOADecrypt(&(slist->cryptkey), (unsigned char*)inbuf, inlen); + // and fall through + case pi_fixedheader: + if (inlen < FIXED_HEADER_LEN) + break; + memcpy(&slist->mypublicip, inbuf, 4); + slist->ListCallback(slist, slc_publicipdetermined, SBNullServer, + slist->instance); + memcpy(&slist->defaultport, inbuf + 4, 2); + if (slist->defaultport == + 0xFFFF) // there was an error- grab the error string if present.. + { + if (NTSLengthSB(inbuf + FIXED_HEADER_LEN, inlen - FIXED_HEADER_LEN) == + -1) // not all there + break; + SBSetLastListErrorPtr(slist, inbuf + FIXED_HEADER_LEN); + // make the error callback + slist->ListCallback(slist, slc_queryerror, SBNullServer, slist->instance); + if (slist->inbuffer == NULL) + break; + } + inbuf += FIXED_HEADER_LEN; + inlen -= FIXED_HEADER_LEN; + if ((slist->queryoptions & NO_SERVER_LIST) || + slist->defaultport == 0xFFFF) { + slist->pstate = pi_finished; + slist->state = sl_connected; + break; // no more data expected (if we didn't request any more, or the + // server doesn't know about this game) + } + slist->pstate = pi_keylist; + slist->expectedelements = -1; + + // and fall through + case pi_keylist: + if (slist->expectedelements == + -1) // we haven't read the initial count of keys yet.. + { + if (inlen < 1) + break; + slist->expectedelements = (unsigned char)(inbuf[0]); + slist->keylist = ArrayNew(sizeof(KeyInfo), slist->expectedelements, NULL); + if (slist->keylist == NULL) // error + return sbe_allocerror; + inbuf++; + inlen--; + } + // try to read elements, up to the expected amount... + while (slist->expectedelements > ArrayLength(slist->keylist)) { + KeyInfo ki; + int keylen; + if (inlen < 2) + break; // can't possibly be a full key (keytype + string) + keylen = NTSLengthSB(inbuf + 1, inlen - 1); + if (keylen == -1) + break; // no full NTS string there + ki.keyType = (unsigned char)(inbuf[0]); + ki.keyName = SBRefStr(slist, inbuf + 1); + ArrayAppend(slist->keylist, &ki); + inbuf += keylen + 1; + inlen -= keylen + 1; + } + if (slist->expectedelements > ArrayLength(slist->keylist)) + break; // didn't read them all... + // else we have read all the keys, fall through.. + slist->pstate = pi_uniquevaluelist; + slist->expectedelements = -1; + case pi_uniquevaluelist: + if (slist->expectedelements == + -1) // we haven't read the # of unique values yet.. + { + if (inlen < 1) + break; + slist->expectedelements = (unsigned char)(inbuf[0]); + slist->numpopularvalues = 0; + inbuf++; + inlen--; + } + while (slist->expectedelements > slist->numpopularvalues) { + int keylen = NTSLengthSB(inbuf, inlen); + if (keylen == -1) + break; // no full NTS string + slist->popularvalues[slist->numpopularvalues++] = SBRefStr(slist, inbuf); + inbuf += keylen; + inlen -= keylen; + } + if (slist->expectedelements > slist->numpopularvalues) + break; // didn't read all the popular values + // else we've got them all - move on to servers! + slist->pstate = pi_servers; + case pi_servers: + if (inlen < 5) // not enough for a full servers + break; + do { + reqlen = IncomingListParseServer(slist, inbuf, inlen); + if (reqlen == -2) + return sbe_allocerror; + else if (reqlen == -1) // that was the last server! + { + inlen -= 5; + inbuf += 5; + slist->pstate = pi_finished; + slist->state = sl_connected; + slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, + slist->instance); + break; + } + inbuf += reqlen; + inlen -= reqlen; + if (slist->inbuffer == NULL) + reqlen = 0; // break out - they disconnected + } while (reqlen != 0); + break; + default: + break; + } + assert(inlen >= 0); + if (slist->inbuffer == NULL) + return sbe_noerror; // don't keep processing - they disconnected + if (inlen != 0) // need to shift it over.. + { + memmove(slist->inbuffer, inbuf, (size_t)inlen); + } + slist->inbufferlen = inlen; + // we could clear the key list here if we wanted + + return sbe_noerror; +} + +/* + Number of Keys - Byte + List of Keys - with key type + Key Type - 1 Byte (0 = string, 1 = byte, 2 = short) + KeyName - NTS +*/ +static SBError ProcessPushKeyList(SBServerList* slist, char* buf, int len) { + int i; + int numkeys; + // first byte is the # of keys + numkeys = (unsigned char)buf[0]; + buf++; + len--; + // set up our key list... + if (slist->keylist != NULL) // free the existing key list + { + FreeKeyList(slist); + } + slist->keylist = ArrayNew(sizeof(KeyInfo), numkeys, NULL); + if (slist->keylist == NULL) + return sbe_allocerror; + for (i = 0; i < numkeys; i++) { + int keylen; + KeyInfo ki; + if (len < 2) + return sbe_dataerror; // can't possibly be a full key (keytype + string) + keylen = NTSLengthSB(buf + 1, len - 1); + if (keylen == -1) + return sbe_dataerror; // no full NTS string there + ki.keyType = (unsigned char)(buf[0]); + ki.keyName = SBRefStr(slist, buf + 1); + ArrayAppend(slist->keylist, &ki); + buf += keylen + 1; + len -= keylen + 1; + } + return sbe_noerror; +} + +/* +Is Final Packet - Byte +Result Count - 1 bytes (up to 255 per packet) +Results + Player Name - NTS + 6 bytes - server addr + 4 bytes - last seen time (UTC Unix time) + gamename - NTS (empty if they are searching for only a single game) +*/ + +static SBError ProcessPlayerSearch(SBServerList* slist, char* buf, int len) { + unsigned char isFinal; + unsigned char resultCount; + unsigned int ip; + unsigned short port; + char* nick; + time_t lastSeen; + int i; + + if (len < 2) + return sbe_dataerror; // not a full packet + isFinal = (unsigned char)buf[0]; + resultCount = (unsigned char)buf[1]; + buf += 2; + len -= 2; + for (i = 0; i < resultCount; i++) { + int slen; + nick = buf; + slen = NTSLengthSB(buf, len); + if (slen == -1) + return sbe_dataerror; + buf += slen; + len -= slen; + if (len < 11) + return sbe_dataerror; // not a full entry + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + memcpy(&lastSeen, buf + 6, 4); + lastSeen = (time_t)ntohl((unsigned long)lastSeen); + buf += 10; + len -= 10; + slen = NTSLengthSB(buf, len); + if (slen == -1) + return sbe_dataerror; // not a valid gamename + slist->PlayerSearchCallback(slist, nick, ip, port, lastSeen, buf, + slist->instance); + // now skip the gamename + buf += slen; + len -= slen; + } + if (isFinal) // send a final callback + slist->PlayerSearchCallback(slist, NULL, 0, 0, 0, NULL, slist->instance); + + return sbe_noerror; +} +/* + Server IP - 4 bytes + Server Port - 2 bytes + Map change time - 4 bytes + Number of maps - 1 byte + Maps + Mapname - NTS +*/ + +static SBError ProcessMaploop(SBServerList* slist, char* buf, int len) { + time_t changeTime; + unsigned int ip; + unsigned short port; + SBServer server; + unsigned char mapCount; + int i; + char* mapList[MAX_MAPLOOP_LENGTH]; + if (len < 11) + return sbe_dataerror; // not a valid packet + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + i = SBServerListFindServerByIP(slist, ip, port); + if (i == -1) + return sbe_noerror; // not present any more + server = SBServerListNth(slist, i); + memcpy(&changeTime, buf + 6, 4); + changeTime = (time_t)ntohl((unsigned long)changeTime); + mapCount = (unsigned char)buf[10]; + buf += 11; + len -= 11; + for (i = 0; i < mapCount && i < MAX_MAPLOOP_LENGTH; i++) { + int maplen; + if (len < 1) + break; + maplen = NTSLengthSB(buf, len); + if (maplen == -1) + return sbe_dataerror; // not a full NTS string + mapList[i] = buf; + buf += maplen; + len -= maplen; + } + if (slist->MaploopCallback == NULL) + return sbe_noerror; + slist->MaploopCallback(slist, server, changeTime, i, mapList, + slist->instance); + return sbe_noerror; +} + +static SBError ProcessDeleteServer(SBServerList* slist, char* buf, int len) { + goa_uint32 ip; + unsigned short port; + int i; + + if (len < 6) + return sbe_dataerror; + // need to grab out the ip & port to find it in our list + memcpy(&ip, buf, 4); + memcpy(&port, buf + 4, 2); + // see if we can find the server in the list - if not, then add it + i = SBServerListFindServerByIP(slist, ip, port); + if (i == -1) + return sbe_noerror; + SBServerListRemoveAt(slist, i); + return sbe_noerror; +} + +/* +same as server list server format, except without using popular value lists +*/ +static SBError ProcessPushServer(SBServerList* slist, char* buf, int len) { + + SBServer server; + goa_uint32 ip; + unsigned short port; + int foundexisting; + int i; + + if (len < 5) + return sbe_dataerror; + // need to grab out the ip & port to find it in our list + ParseServerIPPort(slist, buf, len, &ip, &port); + + foundexisting = SBServerListFindServerByIP(slist, ip, port); + if (foundexisting == -1) // need to add it + { + server = SBAllocServer(slist, ip, port); + if (SBIsNullServer(server)) + return sbe_allocerror; + } else + server = SBServerListNth(slist, foundexisting); + i = ParseServer(slist, server, buf, len, 0); + if (i < 0) + return sbe_dataerror; // whole thing wasn't there + if (foundexisting == -1) + SBServerListAppendServer(slist, server); + slist->ListCallback(slist, slc_serverupdated, server, slist->instance); + return sbe_noerror; +} + +static SBError ProcessAdHocData(SBServerList* slist) { + unsigned short msglen; + int i; + SBError ret = sbe_noerror; + while (slist->inbufferlen >= + 3) // while there is at least 1 message in there.. (2 bytes for the + // size + 1 for the type) + { + // first two bytes are the length - see if we have the whole message.. + memcpy(&msglen, slist->inbuffer, 2); + msglen = ntohs(msglen); + if (msglen > INCOMING_BUFFER_SIZE) // ack! won't fit.. + { + ret = sbe_dataerror; + break; + } + if (slist->inbufferlen < msglen) + return sbe_noerror; // whole message not yet here + // else process the message + switch (slist->inbuffer[2]) { + case PUSH_KEYS_MESSAGE: + ret = ProcessPushKeyList(slist, slist->inbuffer + 3, msglen - 3); + break; + case PUSH_SERVER_MESSAGE: + ret = ProcessPushServer(slist, slist->inbuffer + 3, msglen - 3); + break; + case KEEPALIVE_MESSAGE: // just need to send it back.. + i = (int)send(slist->slsocket, slist->inbuffer, msglen, 0); + if (i <= 0) + return sbe_connecterror; + break; + case DELETE_SERVER_MESSAGE: + ret = ProcessDeleteServer(slist, slist->inbuffer + 3, msglen - 3); + break; + case MAPLOOP_MESSAGE: + ret = ProcessMaploop(slist, slist->inbuffer + 3, msglen - 3); + break; + case PLAYERSEARCH_MESSAGE: + ret = ProcessPlayerSearch(slist, slist->inbuffer + 3, msglen - 3); + break; + } + + slist->inbufferlen -= msglen; + assert(slist->inbufferlen >= 0); + if (slist->inbufferlen != 0 && + slist->inbuffer != NULL) // if anything is left, shift it over.. + { + memmove(slist->inbuffer, slist->inbuffer + msglen, + (size_t)slist->inbufferlen); + } + if (ret != sbe_noerror) + break; + } + if (ret != sbe_noerror) + ErrorDisconnect(slist); + return ret; +} + +static SBError ProcessIncomingData(SBServerList* slist) { + SBError err; + int len; + int oldlen; + + if (!CanReceiveOnSocket(slist->slsocket)) + return sbe_noerror; + + // append to data + oldlen = slist->inbufferlen; + len = recv(slist->slsocket, slist->inbuffer + slist->inbufferlen, + INCOMING_BUFFER_SIZE - slist->inbufferlen, 0); + if (gsiSocketIsError(len) || len == 0) { + ErrorDisconnect(slist); + return sbe_connecterror; + } + slist->inbufferlen += len; + err = sbe_noerror; + if (slist->state == sl_connected || + slist->pstate > pi_cryptheader) // decrypt any new data.. + { + GOADecrypt(&(slist->cryptkey), (unsigned char*)(slist->inbuffer + oldlen), + slist->inbufferlen - oldlen); + } + + if (slist->state == sl_mainlist) + err = ProcessMainListData(slist); + if (err != sbe_noerror) + return err; + // always need to check this after mainlistdata, in case some extra data has + // some in (e.g. key list for push) + if (slist->state == sl_connected && slist->inbufferlen > 0) + return ProcessAdHocData(slist); + return sbe_noerror; +} + +SBError SBGetServerRulesFromMaster(SBServerList* slist, goa_uint32 ip, + unsigned short port) { + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + + if (slist->state == sl_disconnected) // try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + + if (slist->state == sl_disconnected) + return sbe_connecterror; + + // the length + tmp = htons(msgLen); + memcpy(requestBuffer, &tmp, 2); + // the message type + requestBuffer[2] = SERVER_INFO_REQUEST; + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + return SendWithRetry(slist, requestBuffer, msgLen); +} + +SBError SBSendMessageToServer(SBServerList* slist, goa_uint32 ip, + unsigned short port, const char* data, int len) { + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + SBError ret; + int i; + + if (slist->state == sl_disconnected) // try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + + if (slist->state == sl_disconnected) + return sbe_connecterror; + + // the length + tmp = htons((unsigned short)(msgLen + len)); + memcpy(requestBuffer, &tmp, 2); + // the message type + requestBuffer[2] = SEND_MESSAGE_REQUEST; + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + ret = SendWithRetry(slist, requestBuffer, msgLen); + if (ret != sbe_noerror) + return ret; + i = send(slist->slsocket, data, len, 0); // send the actual push data + if (i < 0) + return sbe_connecterror; + return sbe_noerror; +} + +SBError SBSendPlayerSearchRequest(SBServerList* slist, char* searchName, + int searchOptions, int maxResults, + SBPlayerSearchCallbackFn callback) { + char requestBuffer[256]; + unsigned short msgLen = 11; + int namelen; + SBError ret; + + if (slist->state == sl_disconnected) // try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (slist->state == sl_disconnected) + return sbe_connecterror; + + slist->PlayerSearchCallback = callback; + + requestBuffer[2] = PLAYERSEARCH_REQUEST; + searchOptions = (int)htonl((unsigned int)searchOptions); + memcpy(requestBuffer + 3, &searchOptions, 4); + maxResults = (int)htonl((unsigned int)maxResults); + memcpy(requestBuffer + 7, &maxResults, 4); + namelen = (int)strlen(searchName); + msgLen = (unsigned short)(msgLen + (namelen + 1)); + if (msgLen > sizeof(requestBuffer)) + return sbe_paramerror; // search string too long + memcpy(requestBuffer + 11, searchName, (size_t)namelen + 1); + msgLen = htons((unsigned short)(msgLen)); + memcpy(requestBuffer, &msgLen, 2); + ret = SendWithRetry(slist, requestBuffer, ntohs(msgLen)); + if (ret != sbe_noerror) + return ret; + return sbe_noerror; +} + +SBError SBSendMaploopRequest(SBServerList* slist, SBServer server, + SBMaploopCallbackFn callback) { + char requestBuffer[16]; + unsigned short tmp; + unsigned short msgLen = 9; + unsigned short port; + unsigned int ip; + SBError ret; + + if (slist->state == sl_disconnected) // try to connect + SBServerListConnectAndQuery(slist, NULL, NULL, NO_SERVER_LIST, 0); + if (slist->state == sl_disconnected) + return sbe_connecterror; + + slist->MaploopCallback = callback; + // the length + tmp = htons((unsigned short)(msgLen)); + memcpy(requestBuffer, &tmp, 2); + // the message type + requestBuffer[2] = MAPLOOP_REQUEST; + ip = SBServerGetPublicInetAddress(server); + port = SBServerGetPublicQueryPortNBO(server); + memcpy(requestBuffer + 3, &ip, sizeof(ip)); + memcpy(requestBuffer + 7, &port, sizeof(port)); + ret = SendWithRetry(slist, requestBuffer, msgLen); + if (ret != sbe_noerror) + return ret; + return sbe_noerror; +} + +SBError SBSendNatNegotiateCookieToServer(SBServerList* slist, goa_uint32 ip, + unsigned short port, int cookie) { + unsigned char negotiateBuffer[NATNEG_MAGIC_LEN + 4]; + negotiateBuffer[0] = NN_MAGIC_0; + negotiateBuffer[1] = NN_MAGIC_1; + negotiateBuffer[2] = NN_MAGIC_2; + negotiateBuffer[3] = NN_MAGIC_3; + negotiateBuffer[4] = NN_MAGIC_4; + negotiateBuffer[5] = NN_MAGIC_5; + cookie = (int)htonl((unsigned int)cookie); + memcpy(negotiateBuffer + NATNEG_MAGIC_LEN, &cookie, 4); + return SBSendMessageToServer(slist, ip, port, (char*)negotiateBuffer, + NATNEG_MAGIC_LEN + 4); +} + +static SBError ProcessLanData(SBServerList* slist) { + __attribute__((aligned(32))) char + indata[1500]; // make sure we have enough room for a large UDP packet + struct sockaddr_in saddr; + int saddrlen = sizeof(saddr); + int error; + int foundexisting; + SBServer server; + + while (CanReceiveOnSocket(slist->slsocket)) // we break if the select fails + { + error = (int)recvfrom(slist->slsocket, indata, sizeof(indata) - 1, 0, + (struct sockaddr*)&saddr, &saddrlen); + if (gsiSocketIsError(error)) + continue; + // if we got data, then add it to the list... + foundexisting = SBServerListFindServerByIP(slist, saddr.sin_addr.s_addr, + saddr.sin_port); + if (foundexisting != -1) // already exists + continue; + server = SBAllocServer(slist, saddr.sin_addr.s_addr, saddr.sin_port); + if (SBIsNullServer(server)) + return sbe_allocerror; + SBServerSetFlags(server, UNSOLICITED_UDP_FLAG | NONSTANDARD_PORT_FLAG); + SBServerListAppendServer(slist, server); + } + if (current_time() - slist->lanstarttime > + SL_LAN_SEARCH_TIME) // done waiting for replies + { + closesocket(slist->slsocket); + slist->slsocket = INVALID_SOCKET; + slist->state = sl_disconnected; + slist->ListCallback(slist, slc_initiallistcomplete, SBNullServer, + slist->instance); + } + return sbe_noerror; +} + +SBError SBListThink(SBServerList* slist) { + SBFreeDeadList(slist); // free any pending deleted servers + switch (slist->state) { + case sl_disconnected: + break; + case sl_connected: + case sl_mainlist: + return ProcessIncomingData(slist); + // break; + case sl_lanbrowse: + return ProcessLanData(slist); + // break; + } + + return sbe_noerror; + // see if any data is available... +} diff --git a/source/platform/assert.h b/source/platform/assert.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/source/platform/assert.h @@ -0,0 +1 @@ +#pragma once diff --git a/source/platform/ctype.c b/source/platform/ctype.c new file mode 100644 index 000000000..efaa74d31 --- /dev/null +++ b/source/platform/ctype.c @@ -0,0 +1,18 @@ +const unsigned short __ctype_mapC[256] = { + cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, + cntl, csbl, ctsp, ctsp, ctsp, ctsp, cntl, cntl, + cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, + cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, + sblp, punc, punc, punc, punc, punc, punc, punc, + punc, punc, punc, punc, punc, punc, punc, punc, + dhex, dhex, dhex, dhex, dhex, dhex, dhex, dhex, + dhex, dhex, punc, punc, punc, punc, punc, punc, + punc, uhex, uhex, uhex, uhex, uhex, uhex, uppc, + uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, + uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, + uppc, uppc, uppc, punc, punc, punc, punc, punc, + punc, lhex, lhex, lhex, lhex, lhex, lhex, lowc, + lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, + lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, + lowc, lowc, lowc, punc, punc, punc, punc, cntl +}; diff --git a/source/platform/ctype.h b/source/platform/ctype.h index 90a65daeb..a8645ee84 100644 --- a/source/platform/ctype.h +++ b/source/platform/ctype.h @@ -1,37 +1,101 @@ #pragma once -#ifndef MSL_CTYPE_H -#define MSL_CTYPE_H - #include "rk_types.h" #ifdef __cplusplus extern "C" { #endif -struct __CMap { - u8 _0[16]; - const u8* to_lower_table; // 10 - const u8* to_upper_table; +struct _loc_ctype_cmpt { + char CmptName[8]; + const unsigned short* ctype_map_ptr; + const unsigned char* upper_map_ptr; + const unsigned char* lower_map_ptr; }; -struct __Locale { - u8 _[56]; - struct __CMap* cmap; - u8 _1[0x44-56-4]; + +struct __locale { + struct __locale* next_locale; + char locale_name[48]; + struct _loc_coll_cmpt* coll_cmpt_ptr; + struct _loc_ctype_cmpt* ctype_cmpt_ptr; + struct _loc_mon_cmpt* mon_cmpt_ptr; + struct _loc_num_cmpt* num_cmpt_ptr; + struct _loc_time_cmpt* time_cmpt_ptr; }; -extern struct __Locale _current_locale; -#define case_table (&_current_locale)->cmap->to_lower_table -#define up_case_table (&_current_locale)->cmap->to_upper_table -static inline int tolower(int x) { - return (x < 0 || x >= 256) ? x : (int)case_table[x]; +extern struct __locale _current_locale; + +inline int isalnum(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x9); +} +inline int isalpha(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x1); +} +inline int isblank(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x2); +} +inline int iscntrl(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x4); +} +inline int isdigit(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x8); +} +inline int isgraph(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x10); } -static inline int toupper(int x) { - return (x < 0 || x >= 256) ? x : (int)up_case_table[x]; +inline int islower(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x20); +} +inline int isprint(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x40); +} +inline int ispunct(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x80); +} +inline int isspace(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x0100); +} +inline int isupper(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x0200); +} +inline int isxdigit(int c) { + return ((c < 0) || (c >= 256)) + ? 0 + : (int)(_current_locale.ctype_cmpt_ptr->ctype_map_ptr[c] & 0x0400); +} +inline int tolower(int c) { + return ((c < 0) || (c >= 256)) + ? c + : (int)(_current_locale.ctype_cmpt_ptr->lower_map_ptr[c]); +} +inline int toupper(int c) { + return ((c < 0) || (c >= 256)) + ? c + : (int)(_current_locale.ctype_cmpt_ptr->upper_map_ptr[c]); } #ifdef __cplusplus } // extern "C" #endif - -#endif // MSL_CTYPE_H \ No newline at end of file diff --git a/source/platform/float.h b/source/platform/float.h index a853b39bd..7575fdf81 100644 --- a/source/platform/float.h +++ b/source/platform/float.h @@ -1,3 +1,3 @@ #pragma once -#define FLT_EPSILON (1.0f / 8388608.0f) \ No newline at end of file +#define FLT_EPSILON (1.0f / 8388608.0f) diff --git a/source/platform/limits.h b/source/platform/limits.h new file mode 100644 index 000000000..68cce52c9 --- /dev/null +++ b/source/platform/limits.h @@ -0,0 +1,18 @@ +#pragma once + +#define CHAR_BIT 8 +#define SCHAR_MIN -0x80 +#define SCHAR_MAX +0x7f +#define UCHAR_MAX 0xff +#define CHAR_MIN -0x80 +#define CHAR_MAX +0x7f +#define MB_LEN_MAX 16 +#define SHRT_MIN -0x8000 +#define SHRT_MAX +0x7fff +#define USHRT_MAX 0xffff +#define INT_MIN -0x80000000 +#define INT_MAX +0x7fffffff +#define UINT_MAX 0xffffffff +#define LONG_MIN -0x8000000000000000 +#define LONG_MAX +0x7fffffffffffffff +#define ULONG_MAX 0xffffffffffffffff diff --git a/source/platform/stdarg.h b/source/platform/stdarg.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/source/platform/stdarg.h @@ -0,0 +1 @@ +#pragma once diff --git a/source/platform/stdbool.h b/source/platform/stdbool.h index f16d5e3ca..4a9ba7902 100644 --- a/source/platform/stdbool.h +++ b/source/platform/stdbool.h @@ -1,13 +1,8 @@ #pragma once -#ifndef MSL_STDBOOL -#define MSL_STDBOOL - #ifndef __cplusplus - typedef _Bool bool; +typedef _Bool bool; - #define true ((_Bool)1) - #define false ((_Bool)0) +#define true ((_Bool)1) +#define false ((_Bool)0) #endif // __cplusplus - -#endif // MSL_STDBOOL \ No newline at end of file diff --git a/source/platform/stdint.h b/source/platform/stdint.h index 97bbd8a81..34115c2e8 100644 --- a/source/platform/stdint.h +++ b/source/platform/stdint.h @@ -1,8 +1,5 @@ #pragma once -#ifndef MSL_STDINT -#define MSL_STDINT - typedef unsigned long long uint64_t; typedef signed long long int64_t; @@ -14,5 +11,3 @@ typedef signed short int16_t; typedef unsigned char uint8_t; typedef signed char int8_t; - -#endif // MSL_STDINT \ No newline at end of file diff --git a/source/platform/stdio.h b/source/platform/stdio.h new file mode 100644 index 000000000..97af7871c --- /dev/null +++ b/source/platform/stdio.h @@ -0,0 +1,79 @@ +#pragma once + +#include + +#define FILENAME_MAX 256 + +typedef struct _FILE FILE; + +typedef unsigned long __file_handle; + +typedef unsigned long fpos_t; + +typedef void* __ref_con; +typedef void (*__idle_proc)(void); +typedef int (*__pos_proc)(__file_handle file, fpos_t* position, int mode, + __ref_con ref_con); +typedef int (*__io_proc)(__file_handle file, unsigned char* buff, size_t* count, + __ref_con ref_con); +typedef int (*__close_proc)(__file_handle file); + +typedef struct { + unsigned int open_mode : 2; + unsigned int io_mode : 3; + unsigned int buffer_mode : 2; + unsigned int file_kind : 3; + + unsigned int binary_io : 1; +} __file_modes; + +typedef struct { + unsigned int io_state : 3; + unsigned int free_buffer : 1; + unsigned char eof; + unsigned char error; +} __file_state; + +struct _FILE { + __file_handle handle; + __file_modes mode; + __file_state state; + + unsigned char char_buffer; + unsigned char char_buffer_overflow; + unsigned char ungetc_buffer[2]; + + unsigned long position; + unsigned char* buffer; + unsigned long buffer_size; + unsigned char* buffer_ptr; + unsigned long buffer_len; + unsigned long buffer_alignment; + unsigned long saved_buffer_len; + unsigned long buffer_pos; + __pos_proc position_proc; + __io_proc read_proc; + __io_proc write_proc; + __close_proc close_proc; + __ref_con ref_con; +}; + +#define EOF (-1) + +#define SEEK_CUR 1 +#define SEEK_END 2 +#define SEEK_SET 0 + +int sprintf(char* str, const char* format, ...); +int snprintf(char* s, size_t n, const char* format, ...); + +int sscanf(const char* str, const char* format, ...); + +FILE* fopen(const char* filename, const char* mode); +int fclose(FILE* stream); +int fseek(FILE* stream, long int offset, int origin); +long int ftell(FILE* stream); +size_t fread(void* ptr, size_t size, size_t count, FILE* stream); +void rewind(FILE* stream); + +int remove(const char* pathname); diff --git a/source/platform/stdlib.h b/source/platform/stdlib.h index c2a82910e..5fca03bac 100644 --- a/source/platform/stdlib.h +++ b/source/platform/stdlib.h @@ -1,5 +1,21 @@ #pragma once +#include + // Compiler intrinsic functions. #define abs(x) __abs(x) #define labs(x) __labs(x) + +long strtol(const char* restrict nptr, char** restrict endptr, int base); + +int atoi(const char* nptr); +long atol(const char* nptr); +long long atoll(const char* nptr); +long long atoq(const char* nptr); +double atof(const char* nptr); + +void qsort(void* base, size_t nitems, size_t size, + int (*compar)(const void*, const void*)); + +void srand(unsigned int seed); +int rand(void); diff --git a/source/platform/string.h b/source/platform/string.h index 91464bd4a..d271f46af 100644 --- a/source/platform/string.h +++ b/source/platform/string.h @@ -11,6 +11,28 @@ void* memcpy(void*, const void*, u32); // PAL: 0x80006038 void* memset(void*, s32, u32); +void* memmove(void*, const void*, size_t); + +int memcmp(const void* s1, const void* s2, size_t n); + +u32 strlen(const char*); + +char* strcpy(char* dst, const char* src); +char* strncpy(char* dest, const char* src, size_t n); +int strcmp(const char* s1, const char* s2); +int strncmp(const char* s1, const char* s2, size_t n); + +char* strchr(const char*, int); +char* strrchr(const char* str, int ch); + +char* strstr(const char* haystack, const char* needle); + +size_t strspn(const char* s, const char* accept); +size_t strcspn(const char* s, const char* reject); + +char* strcat(char* restrict dest, const char* restrict src); +char* strncat(char* restrict dest, const char* restrict src, size_t n); + #ifdef __cplusplus } // extern "C" #endif diff --git a/source/platform/strings.h b/source/platform/strings.h new file mode 100644 index 000000000..ccd429a53 --- /dev/null +++ b/source/platform/strings.h @@ -0,0 +1,12 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +int strcasecmp(const char* s1, const char* s2); +int strncasecmp(const char* s1, const char* s2, size_t n); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/source/platform/time.h b/source/platform/time.h new file mode 100644 index 000000000..64b289a95 --- /dev/null +++ b/source/platform/time.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +typedef s64 time_t; + +struct tm { + int tm_sec; + int tm_min; + int tm_hour; + int tm_mday; + int tm_mon; + int tm_year; + int tm_wday; + int tm_yday; + int tm_isdst; +}; diff --git a/source/platform/wchar.h b/source/platform/wchar.h new file mode 100644 index 000000000..a2490932a --- /dev/null +++ b/source/platform/wchar.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +typedef unsigned short wchar_t; + +u32 wcslen(const wchar_t*); +u32 wcsnlen_s(const wchar_t*, u32); + +wchar_t* wcscpy(wchar_t*, const wchar_t*); diff --git a/source/rk_types.h b/source/rk_types.h index b3042c665..a9e88d2c4 100644 --- a/source/rk_types.h +++ b/source/rk_types.h @@ -80,6 +80,7 @@ typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; +typedef uint32_t size_t; typedef volatile u8 vu8; typedef volatile u16 vu16; diff --git a/source/rvl/gx.h b/source/rvl/gx.h index ec4ae6a9b..330a6cf81 100644 --- a/source/rvl/gx.h +++ b/source/rvl/gx.h @@ -36,4 +36,4 @@ u32 GXSetDispCopyYScale(f32 y_scale_factor); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/source/rvl/ios/ios.h b/source/rvl/ios/ios.h new file mode 100644 index 000000000..0ba94fe9a --- /dev/null +++ b/source/rvl/ios/ios.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +s32 IOS_Open(const char*, u32); +s32 IOS_Close(u32); +s32 IOS_Ioctl(s32, s32, void*, u32, void*, u32); diff --git a/source/rvl/mem/rvlMemExpHeap.c b/source/rvl/mem/rvlMemExpHeap.c index 372aea9a9..3a53b7438 100644 --- a/source/rvl/mem/rvlMemExpHeap.c +++ b/source/rvl/mem/rvlMemExpHeap.c @@ -263,11 +263,6 @@ void* MEMAllocFromExpHeapEx(MEMHeapHandle heap, u32 size, int dir) { extern "C" { #endif -// TODO: Move to OS -u32 OSDisableInterrupts(void); -u32 OSEnableInterrupts(void); -u32 OSRestoreInterrupts(u32 level); - // Referenced by assembly. void _restgpr_26(); void _savegpr_26(); diff --git a/source/rvl/mem/rvlMemFrmHeap.cpp b/source/rvl/mem/rvlMemFrmHeap.cpp index a6cf96824..73623c4a1 100644 --- a/source/rvl/mem/rvlMemFrmHeap.cpp +++ b/source/rvl/mem/rvlMemFrmHeap.cpp @@ -112,11 +112,6 @@ void MEMFreeToFrmHeap(MEMHeapHandle heap, int mode) { extern "C" { #endif -// TODO: Move to OS -u32 OSDisableInterrupts(void); -u32 OSEnableInterrupts(void); -u32 OSRestoreInterrupts(u32 level); - #ifdef __cplusplus } #endif diff --git a/source/rvl/os/os.h b/source/rvl/os/os.h new file mode 100644 index 000000000..59fdbe22a --- /dev/null +++ b/source/rvl/os/os.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define OSRoundUp32B(x) (((u32)(x) + 32 - 1) & ~(32 - 1)) +#define OSRoundDown32B(x) (((u32)(x)) & ~(32 - 1)) + +typedef s64 OSTime; +typedef u32 OSTick; + +typedef struct OSCalendarTime { + int sec; + int min; + int hour; + int mday; + int mon; + int year; + int wday; + int yday; + + int msec; + int usec; +} OSCalendarTime; + +#define OSTicksToSeconds(ticks) ((ticks) / OS_TIMER_CLOCK) +#define OSTicksToMilliseconds(ticks) ((ticks) / (OS_TIMER_CLOCK / 1000)) + +OSTick OSGetTick(void); +OSTime OSGetTime(void); + +OSTime OSCalendarTimeToTicks(OSCalendarTime* td); +void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* td); + +int OSDisableInterrupts(void); +int OSEnableInterrupts(void); +int OSRestoreInterrupts(int level); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/os/osAlarm.h b/source/rvl/os/osAlarm.h new file mode 100644 index 000000000..132b8d66c --- /dev/null +++ b/source/rvl/os/osAlarm.h @@ -0,0 +1,26 @@ +#pragma once + +#include "os.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OSAlarm OSAlarm; +typedef void (*OSAlarmHandler)(OSAlarm* alarm, OSContext* context); + +struct OSAlarm { + OSAlarmHandler handler; + u32 tag; + OSTime fire; + OSAlarm* prev; + OSAlarm* next; + + // Periodic alarm + OSTime period; + OSTime start; +}; + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/os/osSemaphore.h b/source/rvl/os/osSemaphore.h new file mode 100644 index 000000000..94923570c --- /dev/null +++ b/source/rvl/os/osSemaphore.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include "os.h" +#include "osThread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OSSemaphore { + s32 count; + OSThreadQueue queue; +} OSSemaphore; + +void OSInitSemaphore(OSSemaphore* sem, s32 count); +s32 OSWaitSemaphore(OSSemaphore* sem); +s32 OSTryWaitSemaphore(OSSemaphore* sem); +s32 OSSignalSemaphore(OSSemaphore* sem); +s32 OSGetSemaphoreCount(OSSemaphore* sem); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index 5d1865ceb..150d3156f 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -2,6 +2,8 @@ #include +#include "os.h" + #ifdef __cplusplus extern "C" { #endif @@ -15,7 +17,8 @@ typedef struct OSThread { char _00[0x304]; char* stack_high; // 304 char* stack_low; // 308 - char _30c[0x318 - 0x30c]; + u32 error; // 30C + char _310[0x318 - 0x310]; } OSThread; typedef void* OSMessage; @@ -28,15 +31,52 @@ typedef struct { char _[0x18]; } OSMutex; +typedef struct OSThreadQueue OSThreadQueue; +struct OSThreadQueue { + OSThread* head; + OSThread* tail; +}; + +typedef struct OSContext { + // General-purpose registers + u32 gpr[32]; + + u32 cr; + u32 lr; + u32 ctr; + u32 xer; + + // Floating-point registers + f64 fpr[32]; + + u32 fpscr_pad; + u32 fpscr; + + // Exception handling registers + u32 srr0; + u32 srr1; + + // Context mode + u16 mode; // since UIMM is 16 bits in PPC + u16 state; // OR-ed OS_CONTEXT_STATE_* + + // Place Gekko regs at the end so we have minimal changes to + // existing code + u32 gqr[8]; + u32 psf_pad; + f64 psf[32]; + +} OSContext; + void OSInitMutex(OSMutex*); void OSLockMutex(OSMutex*); void OSUnlockMutex(OSMutex*); int OSCreateThread(OSThread* thread, void* (*callable)(void*), void* user_data, void* stack, u32 stack_size, s32 prio, u16 flag); - void OSCancelThread(OSThread*); void OSDetachThread(OSThread*); +s32 OSResumeThread(OSThread*); int OSIsThreadTerminated(OSThread*); OSThread* OSGetCurrentThread(); @@ -45,6 +85,8 @@ OSSwitchFunction OSSetSwitchThreadCallback(OSSwitchFunction callable); void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); +void OSSleepTicks(OSTime ticks); + #endif #ifdef __cplusplus } diff --git a/source/rvl/so/so.h b/source/rvl/so/so.h new file mode 100644 index 000000000..b2518459d --- /dev/null +++ b/source/rvl/so/so.h @@ -0,0 +1,382 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* (*SO_AllocFunc)(u32, s32); +typedef void (*SO_FreeFunc)(u32, void*, s32); + +typedef struct SOSysWork { + SO_AllocFunc allocFunc; // 0x00 + SO_FreeFunc freeFunc; // 0x04 + s32 rmState; // 0x08 + s32 rmFd; // 0x0C + u32 _unk10; // 0x10 + s32 allocCount; // 0x14 +} SOSysWork; + +typedef struct NETSoSocket { + int af; // 0x00 + int type; // 0x04 + int protocol; // 0x08 +} NETSoSocket; + +enum { + SO_INTERNAL_STATE_TERMINATED = 0, + SO_INTERNAL_STATE_READY = 1, + SO_INTERNAL_STATE_ACTIVE = 2 +}; + +enum { + SO_INTERNAL_RM_STATE_OPENED = 0, + SO_INTERNAL_RM_STATE_WORKING = -1, + SO_INTERNAL_RM_STATE_CLOSED = -2 +}; + +enum { SO_ERR_LINK_UP_TIMEOUT = -121 }; + +typedef enum NWC24Err { + NWC24_OK = 0, + NWC24_ERR_FATAL = -1, + NWC24_ERR_FAILED = -2, + NWC24_ERR_INVALID_VALUE = -3, + NWC24_ERR_NOT_SUPPORTED = -4, + NWC24_ERR_NULL = -5, + NWC24_ERR_FULL = -6, + NWC24_ERR_PROTECTED = -7, + NWC24_ERR_OVERFLOW = -8, + NWC24_ERR_LIB_NOT_OPENED = -9, + NWC24_ERR_LIB_OPENED = -10, + NWC24_ERR_NOMEM = -11, + NWC24_ERR_CONFIG = -12, + NWC24_ERR_NOT_FOUND = -13, + NWC24_ERR_BROKEN = -14, + NWC24_ERR_DONE = -15, + NWC24_ERR_FILE_OPEN = -16, + NWC24_ERR_FILE_CLOSE = -17, + NWC24_ERR_FILE_READ = -18, + NWC24_ERR_FILE_WRITE = -19, + NWC24_ERR_FILE_NOEXISTS = -20, + NWC24_ERR_FILE_OTHER = -21, + NWC24_ERR_MUTEX = -22, + NWC24_ERR_ALIGNMENT = -23, + NWC24_ERR_FORMAT = -24, + NWC24_ERR_STRING_END = -25, + NWC24_ERR_BUSY = -26, + NWC24_ERR_VER_MISMATCH = -27, + NWC24_ERR_HIDDEN = -28, + NWC24_ERR_INPROGRESS = -29, + NWC24_ERR_NOT_READY = -30, + NWC24_ERR_NETWORK = -31, + NWC24_ERR_SERVER = -32, + NWC24_ERR_CONFIG_NETWORK = -33, + NWC24_ERR_ID_NOEXISTS = -34, + NWC24_ERR_ID_GENERATED = -35, + NWC24_ERR_ID_REGISTERED = -36, + NWC24_ERR_ID_CRC = -37, + NWC24_ERR_NAND_CORRUPT = -38, + NWC24_ERR_DISABLED = -39, + NWC24_ERR_INVALID_OPERATION = -40, + NWC24_ERR_FILE_EXISTS = -41, + NWC24_ERR_INTERNAL_IPC = -42, + NWC24_ERR_INTERNAL_VF = -43, + NWC24_ERR_ID_NOT_REGISTERED = -44, + NWC24_ERR_VERIFY_SIGNATURE = -45, + NWC24_ERR_FILE_BROKEN = -46, + NWC24_ERR_INVALID_CHAR = -47, + NWC24_ERR_CANCELLED = -48, + NWC24_ERR_OLD_SYSTEM = -49 +} NWC24Err; + +// Careful. These are mostly wrong. +enum { + NCD_LINKSTATUS_WORKING = 1, + NCD_LINKSTATUS_NONE = 2, // ? + NCD_LINKSTATUS_WIRED = 3, // ? + NCD_LINKSTATUS_WIRELESS_DOWN = 4, // ? + NCD_LINKSTATUS_WIRELESS_UP = 5, // ? + NCD_RESULT_SUCCESS = 0, // ? + NCD_RESULT_FAILURE = -6, // ? + NCD_RESULT_FATAL_ERROR = -3, // ? + NCD_RESULT_INPROGRESS = -8 +}; + +enum { + NET_SO_SOCKET = 0xf, +}; + +enum { + SO_PF_INET6 = 0x17, +}; + +#define SO_SOL_SOCKET 0xffff +#define SO_SOL_IP 0 +#define SO_SOL_ICMP 1 +#define SO_SOL_TCP 6 +#define SO_SOL_UDP 17 + +#define SO_SOL_CONFIG 0xfffe + +#define SO_IPPROTO_ICMP 1 +#define SO_IPPROTO_IGMP 2 +#define SO_IPPROTO_TCP 6 +#define SO_IPPROTO_UDP 17 + +#define SO_CONFIG_FILTER_INPUT 0x1001 +#define SO_CONFIG_FILTER_OUTPUT 0x1002 + +#define SO_CONFIG_ERROR 0x1003 +#define SO_CONFIG_MAC_ADDRESS 0x1004 +#define SO_CONFIG_LINK_STATE 0x1005 +#define SO_CONFIG_INTERFACE_STATISTICS 0x1006 +#define SO_CONFIG_MUTE 0x1007 + +typedef struct IPInterface { + s32 type; + int up; + s32 err; +} IPInterface; + +typedef struct SOHostEnt { + char* name; + char** aliases; + s16 addrType; + s16 length; + u8** addrList; +} SOHostEnt; + +typedef struct SOAddrInfo SOAddrInfo; +struct SOAddrInfo { + int flags; + int family; + int sockType; + int protocol; + unsigned addrLen; + char* canonName; + void* addr; + SOAddrInfo* next; +}; + +// PAL: 0x80385ee0 @sdata (pointer) +// PAL: 0x802a2318 @data (string literal) +extern const char* __SO_VERSION; +// PAL: 0x80385ee8 @sdata (pointer) +// PAL: 0x802a24f8 @data (string literal) +extern const char* __SOCKET_VERSION; + +// PAL: 0x801ec088 +int SOFinish(void); +// PAL: 0x801ec184 +int SOStartup(void); +// PAL: 0x801ec190 +int SOStartupEx(int timeout); +// PAL: 0x801ec5b8 +int SOCleanup(void); +// PAL: 0x801ec768 +SOSysWork* SOiGetSysWork(void); +// PAL: 0x801ec774 +int SOiIsBufferAddrCheck(void); +// PAL: 0x801ec77c +int SOiIsInitialized(void); +// PAL: 0x801ec7cc +void* SOiAlloc(u32, s32); +// PAL: 0x801ec8b4 +void SOiFree(u32, void*, s32); +// PAL: 0x801ec8e8 +int SOiPrepare(const char*, s32*); +// PAL: 0x801ec9d0 +int SOiConclude(const char*, int); +// PAL: 0x801eca2c +int SOiPrepareTempRm(const char*, s32*, int*); +// PAL: 0x801ecd04 +int SOiConcludeTempRm(const char*, int, int); +// PAL: 0x801ecde8 +int SOiWaitForDHCPEx(int timeout); + +// PAL: 0x801ecf20 +int SOSocket(int, int, int); + +int SOGetInterfaceOpt(IPInterface*, int, int, void*, int*); + +long SOGetHostID(void); +SOHostEnt* SOGetHostByName(const char* name); +SOHostEnt* SOGetHostByAddr(const void* addr, int len, int type); +int SOGetNameInfo(const void* sockAddr, char* node, unsigned nodeLen, + char* service, unsigned serviceLen, int flags); +int SOGetAddrInfo(const char* nodeName, const char* servName, + const SOAddrInfo* hints, SOAddrInfo** res); +void SOFreeAddrInfo(SOAddrInfo* head); + +typedef struct SOInAddr { + u32 addr; + +} SOInAddr; + +typedef struct SOSockAddr { + u8 len; + u8 family; + u8 data[6]; + +} SOSockAddr; + +typedef struct SOSockAddrIn { + u8 len; + u8 family; + u16 port; + SOInAddr addr; +} SOSockAddrIn; + +typedef struct SOPollFD { + int fd; + int events; + int revents; +} SOPollFD; + +int SOInetAtoN(const char* cp, SOInAddr* inp); +char* SOInetNtoA(SOInAddr in); +int SOInetPtoN(int af, const char* src, void* dst); +const char* SOInetNtoP(int af, const void* src, char* dst, unsigned len); +u32 SONtoHl(u32 netlong); +u16 SONtoHs(u16 netshort); +u32 SOHtoNl(u32 hostlong); +u16 SOHtoNs(u16 hostshort); + +int SOSocket(int pf, int type, int protocol); +int SOClose(int s); +int SOListen(int s, int backlog); +int SOAccept(int s, void* sockAddr); +int SOBind(int s, const void* sockAddr); +int SOConnect(int s, const void* sockAddr); +int SOGetSockName(int s, void* sockAddr); +int SOGetPeerName(int s, void* sockAddr); +int SORecvFrom(int s, void* buf, int len, int flags, void* sockFrom); +int SORecv(int s, void* buf, int len, int flags); +int SORead(int s, void* buf, int len); +int SOSendTo(int s, const void* buf, int len, int flags, const void* sockTo); +int SOSend(int s, const void* buf, int len, int flags); +int SOWrite(int s, const void* buf, int len); +int SOFcntl(int s, int cmd, ...); +int SOShutdown(int s, int how); +int SOPoll(SOPollFD fds[], unsigned nfds, OSTime timeout); +int SOSockAtMark(int s); + +int SOGetSockOpt(int s, int level, int optname, void* optval, int* optlen); +int SOSetSockOpt(int s, int level, int optname, const void* optval, int optlen); +int SOGetInterfaceOpt(IPInterface* interface, int level, int optname, + void* optval, int* optlen); +int SOSetInterfaceOpt(IPInterface* interface, int level, int optname, + const void* optval, int optlen); + +#define SO_POLLRDNORM 0x0001 +#define SO_POLLRDBAND 0x0002 +#define SO_POLLPRI 0x0004 +#define SO_POLLWRNORM 0x0008 +#define SO_POLLWRBAND 0x0010 +#define SO_POLLERR 0x0020 +#define SO_POLLHUP 0x0040 +#define SO_POLLNVAL 0x0080 +#define SO_POLLIN (SO_POLLRDNORM | SO_POLLRDBAND) +#define SO_POLLOUT SO_POLLWRNORM + +#define SO_INFTIM (-1) + +#define SO_PF_INET 2 +#define SO_SOCK_STREAM 1 +#define SO_SOCK_DGRAM 2 + +enum SOError { + SO_SUCCESS = 0, + SO_EFATAL = 0x80000000, + SO_E2BIG = -1, + SO_EACCES = -2, + SO_EADDRINUSE = -3, + SO_EADDRNOTAVAIL = -4, + SO_EAFNOSUPPORT = -5, + SO_EAGAIN = -6, + SO_EALREADY = -7, + SO_EBADF = -8, + SO_EBADMSG = -9, + SO_EBUSY = -10, + SO_ECANCELED = -11, + SO_ECHILD = -12, + SO_ECONNABORTED = -13, + SO_ECONNREFUSED = -14, + SO_ECONNRESET = -15, + SO_EDEADLK = -16, + SO_EDESTADDRREQ = -17, + SO_EDOM = -18, + SO_EDQUOT = -19, + SO_EEXIST = -20, + SO_EFAULT = -21, + SO_EFBIG = -22, + SO_EHOSTUNREACH = -23, + SO_EIDRM = -24, + SO_EILSEQ = -25, + SO_EINPROGRESS = -26, + SO_EINTR = -27, + SO_EINVAL = -28, + SO_EIO = -29, + SO_EISCONN = -30, + SO_EISDIR = -31, + SO_ELOOP = -32, + SO_EMFILE = -33, + SO_EMLINK = -34, + SO_EMSGSIZE = -35, + SO_EMULTIHOP = -36, + SO_ENAMETOOLONG = -37, + SO_ENETDOWN = -38, + SO_ENETRESET = -39, + SO_ENETUNREACH = -40, + SO_ENFILE = -41, + SO_ENOBUFS = -42, + SO_ENODATA = -43, + SO_ENODEV = -44, + SO_ENOENT = -45, + SO_ENOEXEC = -46, + SO_ENOLCK = -47, + SO_ENOLINK = -48, + SO_ENOMEM = -49, + SO_ENOMSG = -50, + SO_ENOPROTOOPT = -51, + SO_ENOSPC = -52, + SO_ENOSR = -53, + SO_ENOSTR = -54, + SO_ENOSYS = -55, + SO_ENOTCONN = -56, + SO_ENOTDIR = -57, + SO_ENOTEMPTY = -58, + SO_ENOTSOCK = -59, + SO_ENOTSUP = -60, + SO_ENOTTY = -61, + SO_ENXIO = -62, + SO_EOPNOTSUPP = -63, + SO_EOVERFLOW = -64, + SO_EPERM = -65, + SO_EPIPE = -66, + SO_EPROTO = -67, + SO_EPROTONOSUPPORT = -68, + SO_EPROTOTYPE = -69, + SO_ERANGE = -70, + SO_EROFS = -71, + SO_ESPIPE = -72, + SO_ESRCH = -73, + SO_ESTALE = -74, + SO_ETIME = -75, + SO_ETIMEDOUT = -76, + SO_ETXTBSY = -77, + SO_EWOULDBLOCK = SO_EAGAIN, + SO_EXDEV = -78 +}; + +#define SO_INADDR_ANY ((u32)0x00000000) + +#define SO_BROADCAST 9 + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/so/soBasic.c b/source/rvl/so/soBasic.c new file mode 100644 index 000000000..95d262f95 --- /dev/null +++ b/source/rvl/so/soBasic.c @@ -0,0 +1,36 @@ +#include "so.h" + +#include + +// PAL: 0x80385ee8 @sdata (pointer) +// PAL: 0x802a24f8 @data (string literal) +const char* __SOCKET_VERSION = "<< RVL_SDK - SOCKET \trelease build: Dec 10 " + "2007 10:02:35 (0x4199_60831) >>"; + +// PAL: 0x80386d40 @sbss +// static int SO_Initialized = 0; + +int SOSocket2(int pf, int type, int protocol) { + int result; + s32 rmId; + + if ((result = SOiPrepare(0, &rmId)) == SO_SUCCESS) { + if (pf == SO_PF_INET6) { + result = SO_EAFNOSUPPORT; + } else { + NETSoSocket* soc = (NETSoSocket*)SOiAlloc(0xc, 0x20); + if (soc == NULL) { + result = SO_ENOMEM; + } else { + soc->af = pf; + soc->type = type; + soc->protocol = protocol; + result = (int)IOS_Ioctl(rmId, NET_SO_SOCKET, (void*)soc, + sizeof(NETSoSocket), NULL, 0); + SOiFree(0xc, soc, 0x20); + } + } + result = SOiConclude(0, result); + } + return result; +} diff --git a/source/rvl/so/soCommon.c b/source/rvl/so/soCommon.c new file mode 100644 index 000000000..f8e370ac5 --- /dev/null +++ b/source/rvl/so/soCommon.c @@ -0,0 +1,589 @@ +#include "so.h" + +#include + +#include +#include + +// PAL: 0x80385ee0 @sdata (pointer) +// PAL: 0x802a2318 @data (string literal) +const char* __SO_VERSION = + "<< RVL_SDK - SO \trelease build: Dec 10 2007 10:02:35 (0x4199_60831) >>"; + +// PAL: 0x80386d30 @sbss +static u8 soState = 0; +// PAL: 0x80357220 @bss +static SOSysWork soWork; +// PAL: 0x80386d34 @sbss +static s32 soError = 0; +// ??? +static int soRegistered = false; +// PAL: 0x80385ee4 @sdata +static int soBufAddrCheck = true; +// PAL: 0x802a2360 @data +static char NET_RM_SOCK[] = "/dev/net/ip/top"; + +void OSSleepTicks(s64); +s64 __OSGetSystemTime(void); + +s32 NCDGetLinkStatus(void); +s32 NWC24iStartupSocket(s32*); +s32 NWC24iCleanupSocket(s32*); +s32 NWC24iLockSocket(); +s32 NWC24iUnlockSocket(); + +int SOFinish(void) { + int result = 0; + int enabled = OSDisableInterrupts(); + + switch (soState) { + case SO_INTERNAL_STATE_TERMINATED: + result = SO_EALREADY; + break; + case SO_INTERNAL_STATE_READY: + if (soWork.rmState > SO_INTERNAL_RM_STATE_CLOSED) { + result = SO_EBUSY; + break; + } else if (soWork.allocCount > 1) { + result = SO_EAGAIN; + break; + } + soState = SO_INTERNAL_STATE_TERMINATED; + if (soWork._unk10 != 0 && soWork.freeFunc) { + soWork.allocCount--; + soWork.freeFunc(0x0b, (void*)soWork._unk10, 0x460); + } + break; + case SO_INTERNAL_STATE_ACTIVE: + result = SO_EINPROGRESS; + break; + } + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + OSRestoreInterrupts(enabled); + return result; +} + +int SOStartup(void) { return SOStartupEx(0xea60); } + +inline int SO_Startup_ErrWtf(s32 errNwc24, s32 exErr) { + s32 result = -0x1c; + switch (errNwc24) { + case 0: + result = 0; + break; + case -0x16: + case -0xd: + result = -0x30; + break; + case -0x21: + case -2: + result = exErr; + break; + case -1: + result = -0x80000000; + break; + case -0x1d: + result = -0x1a; + break; + } + return result; +} + +int SOStartupEx(int timeout) { + int result; + int enabled; + s32 exErr; + s64 limitTime; + s32 linkupRetryCount; + + limitTime = 0; + if (timeout != 0) + limitTime = __OSGetSystemTime() + ((timeout) * ((__OSBusClock / 4) / 1000)); + + linkupRetryCount = 4; + +begin_startup: + result = SO_SUCCESS; + + enabled = OSDisableInterrupts(); + + switch (soState) { + case SO_INTERNAL_STATE_TERMINATED: + default: + result = SO_ENETRESET; + break; + case SO_INTERNAL_STATE_ACTIVE: + result = SO_EALREADY; + break; + case SO_INTERNAL_STATE_READY: + if (soWork.rmState > SO_INTERNAL_RM_STATE_CLOSED) { + result = SO_EBUSY; + break; + } else if (!OSGetCurrentThread()) { + result = SO_EFATAL; + break; + } else { + int resultNCD; + + soWork.rmState = SO_INTERNAL_RM_STATE_WORKING; + (void)OSRestoreInterrupts(enabled); + + while (true) { + resultNCD = NCDGetLinkStatus(); + if (resultNCD != NCD_RESULT_INPROGRESS && + resultNCD != NCD_LINKSTATUS_WORKING) + break; + OSSleepTicks((((s64)100) * ((__OSBusClock / 4) / 1000))); + if (limitTime != 0 && limitTime < __OSGetSystemTime()) { + if (resultNCD == NCD_LINKSTATUS_WORKING) + result = SO_ERR_LINK_UP_TIMEOUT; + else + result = SO_EFATAL; + goto change_state; + } + } + if (resultNCD < NCD_RESULT_SUCCESS) { + result = SO_EFATAL; + goto change_state; + } else if (resultNCD == NCD_LINKSTATUS_NONE) { + result = SO_ENOENT; + goto change_state; + } + + // 0x801ec334 + while (true) { + soWork.rmFd = IOS_Open(NET_RM_SOCK, 0); + if (soWork.rmFd != -6) + break; + OSSleepTicks((((s64)100) * ((__OSBusClock / 4) / 1000))); + if (limitTime != 0 && limitTime < __OSGetSystemTime()) { + result = SO_EFATAL; + goto change_state; + } + }; + + if (soWork.rmFd < 0) { + result = SO_EFATAL; + goto change_state; + } else { + s32 errNwc24; + + result = SO_SUCCESS; + exErr = SO_SUCCESS; + + // PAL: 0x801ec3c0 + while (true) { + errNwc24 = NWC24iStartupSocket(&exErr); + if (errNwc24 != NWC24_ERR_INPROGRESS) + break; + OSSleepTicks((((s64)100) * ((__OSBusClock / 4) / 1000))); + if (limitTime != 0 && limitTime < __OSGetSystemTime()) { + result = SO_EFATAL; + break; + } + } + // PAL: 0x801ec41c + if (result == SO_SUCCESS) + result = SO_Startup_ErrWtf(errNwc24, exErr); + // PAL: 0x801ec470 + if (result != SO_SUCCESS) { + // PAL: 0x801ec478 + if (IOS_Close(soWork.rmFd) < 0) { + result = SO_EFATAL; + goto change_state; + } else { + soWork.rmFd = -1; + } + } + } + + // PAL: 0x801ec494 + change_state: + enabled = OSDisableInterrupts(); + if (result == SO_SUCCESS) { + soState = SO_INTERNAL_STATE_ACTIVE; + soWork.rmState = SO_INTERNAL_RM_STATE_OPENED; + } else { + soState = 1; + if (result != SO_EFATAL) + soWork.rmState = SO_INTERNAL_RM_STATE_CLOSED; + } + } + break; + } + + { + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + } + + // PAL: 0x801ec4e4 + (void)OSRestoreInterrupts(enabled); + + if (result == 0) { + s64 dhcpTimeOutTicks; + + if (limitTime != 0) { + dhcpTimeOutTicks = limitTime - __OSGetSystemTime(); + } else { + dhcpTimeOutTicks = 0; + } + if (limitTime != 0 && dhcpTimeOutTicks <= 0) { + result = -76; + } else { + result = SOiWaitForDHCPEx( + (int)((dhcpTimeOutTicks) / ((__OSBusClock / 4) / 1000))); + } + + if (result != 0) { + (void)SOCleanup(); + } + } + + { + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + } + + if (result == -112) { + linkupRetryCount--; + if (linkupRetryCount >= 0) { + goto begin_startup; + } + } + + return result; +} + +int SOCleanup(void) { + int result = SO_SUCCESS; + int enabled = OSDisableInterrupts(); + s32 exErr = SO_SUCCESS; + int errNwc24; + + switch (soState) { + case SO_INTERNAL_STATE_TERMINATED: + default: + result = SO_ENETRESET; + break; + case SO_INTERNAL_STATE_READY: + result = SO_EALREADY; + break; + case SO_INTERNAL_STATE_ACTIVE: + if (soWork.rmState < SO_INTERNAL_RM_STATE_OPENED) { + result = SO_EBUSY; + break; + } else if (!OSGetCurrentThread()) { + result = SO_EFATAL; + break; + } else { + soWork.rmState = SO_INTERNAL_RM_STATE_WORKING; + (void)OSRestoreInterrupts(enabled); + + errNwc24 = NWC24iCleanupSocket(&exErr); + result = SO_Startup_ErrWtf(errNwc24, exErr); + if (result != SO_SUCCESS) { + } else { + if (IOS_Close(soWork.rmFd) < 0) + result = SO_EFATAL; + else + soWork.rmFd = -1; + } + + enabled = OSDisableInterrupts(); + if (result == SO_SUCCESS) { + soState = SO_INTERNAL_STATE_READY; + soWork.rmState = SO_INTERNAL_RM_STATE_CLOSED; + } else { + if (result != SO_EFATAL) { + soState = SO_INTERNAL_STATE_ACTIVE; + soWork.rmState = SO_INTERNAL_RM_STATE_OPENED; + } + } + } + break; + } + + { + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + } + + OSRestoreInterrupts(enabled); + return result; +} + +SOSysWork* SOiGetSysWork(void) { return &soWork; } + +int SOiIsBufferAddrCheck(void) { return soBufAddrCheck; } + +int SOiIsInitialized(void) { + int result = false; + int enabled = OSDisableInterrupts(); + switch (soState) { + case SO_INTERNAL_STATE_READY: + case SO_INTERNAL_STATE_ACTIVE: + result = true; + break; + } + OSRestoreInterrupts(enabled); + return result; +} + +void* SOiAlloc(u32 name, s32 size) { + if (size > 0 && soWork.allocFunc) { + void* ptr = soWork.allocFunc(name, size); + if (ptr) { + soWork.allocCount++; + if (SOiIsBufferAddrCheck() && !(((u32)ptr & 0x1fffffff) >= 0x10000000 && + ((u32)ptr & 0x1fffffff) < 0x18000000)) { + SOiFree(name, ptr, size); + ptr = NULL; + } + } + return ptr; + } + return NULL; +} + +void SOiFree(u32 name, void* ptr, s32 size) { + if ((ptr != NULL) && (soWork.freeFunc != NULL)) { + soWork.allocCount--; + soWork.freeFunc(name, ptr, size); + } +} + +int SOiPrepare(const char* funcName, s32* pRmId) { + int result = SO_SUCCESS; + int enabled = OSDisableInterrupts(); + + switch (soState) { + case SO_INTERNAL_STATE_TERMINATED: + result = SO_ENETRESET; + break; + case SO_INTERNAL_STATE_READY: + default: + result = SO_EINVAL; + break; + case SO_INTERNAL_STATE_ACTIVE: + if (soWork.rmState < SO_INTERNAL_RM_STATE_OPENED) { + result = SO_EBUSY; + break; + } else if (!OSGetCurrentThread()) { + result = SO_EFATAL; + break; + } + *pRmId = soWork.rmFd; + break; + } + + if (result != SO_SUCCESS) { + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + } + (void)OSRestoreInterrupts(enabled); + return result; +} + +int SOiConclude(const char* funcName, int result) { + int enabled = OSDisableInterrupts(); + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + (void)OSRestoreInterrupts(enabled); + return result; +} + +int SOiPrepareTempRm(const char* funcName, s32* pRmId, int* pIsTempRm) { + int result = SO_SUCCESS; + int enabled = OSDisableInterrupts(); + int errNwc24; + + switch (soState) { + case SO_INTERNAL_STATE_TERMINATED: + result = SO_ENETRESET; + break; + case SO_INTERNAL_STATE_READY: + default: + if (soWork.rmState > SO_INTERNAL_RM_STATE_CLOSED) { + result = SO_EBUSY; + break; + } else if (!OSGetCurrentThread()) { + result = SO_EFATAL; + break; + } + soWork.rmState = SO_INTERNAL_RM_STATE_WORKING; + (void)OSRestoreInterrupts(enabled); + soWork.rmFd = IOS_Open(NET_RM_SOCK, 0); + if (soWork.rmFd < 0) { + enabled = OSDisableInterrupts(); + if (soWork.rmFd == -6) { + result = SO_EINPROGRESS; + soWork.rmState = SO_INTERNAL_RM_STATE_CLOSED; + } else { + result = SO_EFATAL; + } + } else { + *pRmId = soWork.rmFd; + if ((errNwc24 = NWC24iLockSocket()) == NWC24_OK) { + s32 errNcd = NCDGetLinkStatus(); + switch (errNcd) { + case 3: + case 4: + case 5: + result = (int)IOS_Ioctl(soWork.rmFd, 0x1f, NULL, 0, NULL, 0); + if (result == SO_SUCCESS) { + *pIsTempRm = true; + } else { + result = SOiConcludeTempRm(funcName, result, true); + } + break; + case -8: + result = SOiConcludeTempRm(funcName, SO_EINPROGRESS, true); + break; + case -1: + case -2: + result = SOiConcludeTempRm(funcName, SO_EFATAL, true); + break; + default: + result = SOiConcludeTempRm(funcName, SO_ENOLINK, true); + } + enabled = OSDisableInterrupts(); + } else { + switch (errNwc24) { + case NWC24_ERR_INPROGRESS: + result = SO_EINPROGRESS; + break; + case NWC24_ERR_FAILED: + result = SO_ENETRESET; + break; + case NWC24_ERR_DONE: + result = SO_ENOLINK; + break; + case NWC24_ERR_MUTEX: + case NWC24_ERR_FATAL: + default: + result = SO_EFATAL; + break; + } + if (IOS_Close(soWork.rmFd) < 0) { + result = SO_EFATAL; + } + enabled = OSDisableInterrupts(); + if (result != SO_EFATAL) { + soWork.rmFd = -1; + soWork.rmState = SO_INTERNAL_RM_STATE_CLOSED; + } + } + } + break; + case SO_INTERNAL_STATE_ACTIVE: + if (soWork.rmState < SO_INTERNAL_RM_STATE_OPENED) { + result = SO_EBUSY; + break; + } else if (!OSGetCurrentThread()) { + result = SO_EFATAL; + break; + } + *pIsTempRm = false; + *pRmId = soWork.rmFd; + break; + } + + if (result != SO_SUCCESS) { + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + } + (void)OSRestoreInterrupts(enabled); + return result; +} + +int SOiConcludeTempRm(const char* funcName, int result, int isTempRm) { + int enabled; + + if (isTempRm == true) { + switch (NWC24iUnlockSocket()) { + case NWC24_OK: + break; + case NWC24_ERR_INPROGRESS: + result = SO_EINPROGRESS; + break; + case NWC24_ERR_FATAL: + default: + result = SO_EFATAL; + } + if (IOS_Close(soWork.rmFd) < 0) { + result = SO_EFATAL; + } + enabled = OSDisableInterrupts(); + if (result != SO_EFATAL) { + soWork.rmFd = -1; + soWork.rmState = SO_INTERNAL_RM_STATE_CLOSED; + } + } else { + enabled = OSDisableInterrupts(); + } + OSThread* cur = OSGetCurrentThread(); + if (cur) + cur->error = result; + else + soError = result; + (void)OSRestoreInterrupts(enabled); + return result; +} + +int SOiWaitForDHCPEx(int timeout) { + int result = SO_SUCCESS; + int gioResult; + int ifError; + int ifErrorSize; + s64 limitTime; + + limitTime = 0; + if (timeout != 0) + limitTime = __OSGetSystemTime() + ((timeout) * ((__OSBusClock / 4) / 1000)); + + while (true) { + OSSleepTicks((((s64)10) * ((__OSBusClock / 4) / 1000))); + + ifErrorSize = sizeof(ifError); + gioResult = SOGetInterfaceOpt(NULL, 0xfffe, 0x1003, &ifError, &ifErrorSize); + if (gioResult != SO_SUCCESS) { + result = gioResult; + break; + } + if (gioResult == SO_SUCCESS && ifError != SO_SUCCESS) { + result = ifError; + break; + } + if (SOGetHostID() != 0) + break; + if (limitTime != 0 && limitTime < __OSGetSystemTime()) { + result = SO_ETIMEDOUT; + break; + } + } + + return result; +} diff --git a/source/rvl/ssl.h b/source/rvl/ssl.h new file mode 100644 index 000000000..1b82be5e4 --- /dev/null +++ b/source/rvl/ssl.h @@ -0,0 +1,40 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +enum SSLError { + SSL_ENONE = 0, + SSL_EFAILED = -1, + SSL_EWANT_READ = -2, + SSL_EWANT_WRITE = -3, + SSL_ESYSCALL = -5, + SSL_EZERO_RETURN = -6, + SSL_EWANT_CONNECT = -7, + SSL_ESSLID = -8, + SSL_EVERIFY_COMMON_NAME = -9, + SSL_EVERIFY_ROOT_CA = -10, + SSL_EVERIFY_CHAIN = -11, + SSL_EVERIFY_DATE = -12, + SSL_EGET_SERVER_CERT = -13 +}; + +int SSLNew(u32, const char*); +int SSLConnect(int, int); +int SSLDoHandshake(int); +int SSLDoHandshakeEx(int, char*, size_t); +int SSLRead(int, char*, size_t); +int SSLWrite(int, const char*, size_t); +int SSLShutdown(int); +int SSLSetClientCert(int, const char*, size_t, const char*, size_t); +int SSLSetClientCertDefault(int); +int SSLRemoveClientCert(int); +int SSLSetRootCA(int, const char*, size_t); +int SSLSetRootCADefault(int); +int SSLSetBuiltinRootCA(int, int); +int SSLSetBuiltinClientCert(int, int); + +#ifdef __cplusplus +} +#endif // __cplusplus diff --git a/sources.py b/sources.py index e3d045d6d..aaf80dfaf 100644 --- a/sources.py +++ b/sources.py @@ -18,6 +18,59 @@ compile_source("source/rvl/pad/rvlPadClamp.c", "out/rvlPadClamp.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/pad/rvlPad.c", "out/rvlPad.o", '4199_60831', RVL_OPTS + " -inline on,noauto ") compile_source("source/rvl/si/siBios.c", "out/siBios.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/so/soCommon.c", "out/soCommon.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/so/soBasic.c", "out/soBasic.o", '4199_60831', RVL_OPTS) + +compile_source("source/gamespy/darray.c", "out/darray.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/hashtable.c", "out/hashtable.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/md5c.c", "out/md5c.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/common/revolution/gsSocketRevolution.c", "out/gsSocketRevolution.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/common/gsAvailable.c", "out/gsAvailable.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/common/revolution/gsUtilRevolution.c", "out/gsUtilRevolution.o", '4199_60831', RVL_OPTS) +#compile_source("source/gamespy/common/gsPlatformUtil.c", "out/gsPlatformUtil.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/common/gsCore.c", "out/gsCore.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/common/gsUdpEngine.c", "out/gsUdpEngine.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/common/gsXML.c", "out/gsXML.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gp.c", "out/gp.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpi.c", "out/gpi.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiBuddy.c", "out/gpiBuddy.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiBuffer.c", "out/gpiBuffer.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiCallback.c", "out/gpiCallback.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiConnect.c", "out/gpiConnect.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiInfo.c", "out/gpiInfo.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiKeys.c", "out/gpiKeys.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiOperation.c", "out/gpiOperation.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiPeer.c", "out/gpiPeer.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiProfile.c", "out/gpiProfile.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiSearch.c", "out/gpiSearch.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiTransfer.c", "out/gpiTransfer.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiUnique.c", "out/gpiUnique.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gp/gpiUtility.c", "out/gpiUtility.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gt2/gt2Auth.c", "out/gt2Auth.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gt2/gt2Buffer.c", "out/gt2Buffer.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gt2/gt2Callback.c", "out/gt2Callback.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gt2/gt2Connection.c", "out/gt2Connection.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gt2/gt2Main.c", "out/gt2Main.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gt2/gt2Socket.c", "out/gt2Socket.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gt2/gt2Utility.c", "out/gt2Utility.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/qr2/qr2.c", "out/qr2.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/qr2/qr2RegKeys.c", "out/qr2RegKeys.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpBuffer.c", "out/ghttpBuffer.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpCallbacks.c", "out/ghttpCallbacks.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpCommon.c", "out/ghttpCommon.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpConnection.c", "out/ghttpConnection.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpEncryption.c", "out/ghttpEncryption.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpMain.c", "out/ghttpMain.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpPost.c", "out/ghttpPost.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/ghttp/ghttpProcess.c", "out/ghttpProcess.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gstats/gbucket.c", "out/gbucket.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/gstats/gstats.c", "out/gstats.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/serverbrowsing/sb_crypt.c", "out/sb_crypt.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/serverbrowsing/sb_queryengine.c", "out/sb_queryengine.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/serverbrowsing/sb_server.c", "out/sb_server.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/serverbrowsing/sb_serverlist.c", "out/sb_serverlist.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/serverbrowsing/sb_serverbrowsing.c", "out/sb_serverbrowsing.o", '4199_60831', RVL_OPTS) +compile_source("source/gamespy/sake/sakeMain.c", "out/sakeMain.o", '4199_60831', RVL_OPTS) compile_source("source/nw4r/math/mathTriangular.cpp", "out/mathTriangular.o", '4201_127', NW4R_OPTS) compile_source("source/nw4r/math/mathTypes.cpp", "out/mathTypes.o", '4201_127', NW4R_OPTS) From c97c9dc74aee609de1ac079ae8849ebf879c0e46 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Tue, 13 Jul 2021 07:05:28 +0200 Subject: [PATCH 097/477] [RVL] TPL (#25) * add RVL/TPL * rvl/tpl: fix code style --- mkwutil/verify_main_dol.py | 2 +- pack/dol.base.lcf | 3 +++ pack/dol_objects.txt | 13 +++++++--- pack/dol_slices.csv | 1 + source/rk_types.h | 2 +- source/rvl/gx/gx.h | 8 ++++++ source/rvl/gx/gxTexture.h | 18 +++++++++++++ source/rvl/os/os.h | 2 ++ source/rvl/tpl/tpl.c | 52 ++++++++++++++++++++++++++++++++++++++ source/rvl/tpl/tpl.h | 52 ++++++++++++++++++++++++++++++++++++++ sources.py | 1 + 11 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 source/rvl/gx/gxTexture.h create mode 100644 source/rvl/tpl/tpl.c create mode 100644 source/rvl/tpl/tpl.h diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py index df4b77633..acbe7a0bd 100644 --- a/mkwutil/verify_main_dol.py +++ b/mkwutil/verify_main_dol.py @@ -35,7 +35,7 @@ def verify_dol(reference, target): want_len = 2766496 if len(content) != want_len: print( - Fore.RED + "Mismatched file size: Got %d (%+d)" + Style.RESET_ALL + (Fore.RED + "Mismatched file size: Got %d (%+d)" + Style.RESET_ALL) % (len(content), len(content) - want_len) ) diff --git a/pack/dol.base.lcf b/pack/dol.base.lcf index 42ae0c1fa..924c61463 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.base.lcf @@ -262,6 +262,9 @@ GXSetDispCopyYScale=0x8016F8FC; GXSetFog=0x801722CC; GXInitFogAdjTable=0x801724F8; GXSetFogRangeAdj=0x80172658; +GXInitTexObj=0x801707f8; +GXInitTexObjCI=0x80170a04; +GXInitTexObjLOD=0x80170a4c; OSRegisterVersion = 0x801A0504; OSClearContext = 0x801A2098; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index dae189a42..9d4a9ec1b 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -179,10 +179,15 @@ out\dol\sbss_8038683c_80386998.o out\rvlPad.o out\dol\sbss_803869c4_803869f0.o out\siBios.o -out\dol\text_801b0180_801ec088.o -out\dol\data_8029ccd8_802a2318.o +out\dol\text_801b0180_801b7410.o +out\dol\data_8029ccd8_8029d058.o +out\dol\sdata_80385b28_80385c68.o +out\dol\sdata2_80388938_80388958.o +out\tpl.o +out\dol\text_801b7624_801ec088.o +out\dol\data_8029d083_802a2318.o out\dol\bss_80348230_80357220.o -out\dol\sdata_80385b28_80385ee0.o +out\dol\sdata_80385c6e_80385ee0.o out\dol\sbss_803869f8_80386d30.o out\soCommon.o out\dol\data_802a24f4_802a24f8.o @@ -208,7 +213,7 @@ out\dol\rodata_8025771a_80257740.o out\dol\data_802a30bc_802a30c0.o out\dol\bss_803832e4_80384320.o out\dol\sbss_80386e99_80386ea0.o -out\dol\sdata2_80388938_80388d68.o +out\dol\sdata2_80388960_80388d68.o out\eggHeap.o out\dol\text_80229fac_80239dfc.o out\eggQuat.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index a5ab194f9..34d8d6b2e 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -71,6 +71,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, 1,,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, 1,,source/rvl/si/siBios.c,,,,,,,,,,,,,,,,,,,,,0x803869f0,0x803869f8,,,, +1,,source/rvl/tpl/tpl.c,,,,,,,0x801b7410,0x801b7624,,,,,,,0x8029d058,0x8029d083,,,0x80385c68,0x80385c6e,,,0x80388958,0x80388960,, 1,,source/rvl/so/soCommon.c,,,,,,,0x801ec088,0x801ecf20,,,,,,,0x802a2318,0x802a24f4,0x80357220,0x80357238,0x80385ee0,0x80385ee8,0x80386D30,0x80386D38,,,, 1,,source/rvl/so/soBasic.c,,,,,,,0x801ecf20,0x801ecff4,,,,,,,0x802a24f8 ,0x802a2543,,,0x80385ee8,0x80385eeC,,,,,, 1,,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680 ,,,,,,,,,, diff --git a/source/rk_types.h b/source/rk_types.h index a9e88d2c4..1b1f90e0b 100644 --- a/source/rk_types.h +++ b/source/rk_types.h @@ -97,4 +97,4 @@ typedef volatile f32 vf32; typedef volatile f64 vf64; #define ROUND_UP(x, n) (((u32)(x) + n - 1) & ~(n - 1)) -#define ROUND_DOWN(x, n) (((u32)(x)) & ~(n - 1)) \ No newline at end of file +#define ROUND_DOWN(x, n) (((u32)(x)) & ~(n - 1)) diff --git a/source/rvl/gx/gx.h b/source/rvl/gx/gx.h index 70ba7d9ba..19c861d08 100644 --- a/source/rvl/gx/gx.h +++ b/source/rvl/gx/gx.h @@ -17,6 +17,14 @@ typedef struct _GXColor { u8 a; } GXColor; +typedef struct _GXTlutObj { + u32 _unk00[3]; +} GXTlutObj; + +typedef struct _GXTexObj { + u32 _unk00[8]; +} GXTexObj; + #ifdef __cplusplus } #endif diff --git a/source/rvl/gx/gxTexture.h b/source/rvl/gx/gxTexture.h new file mode 100644 index 000000000..08cffe17f --- /dev/null +++ b/source/rvl/gx/gxTexture.h @@ -0,0 +1,18 @@ +#pragma once + +#include "gx.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void GXInitTexObj(GXTexObj* obj, void* image_ptr, u16 width, u16 height, + int format, int wrap_s, int wrap_t, int mipmap); + +void GXInitTexObjLOD(GXTexObj* obj, int min_filt, int mag_filt, f32 min_lod, + f32 max_lod, f32 lod_bias, int bias_clamp, int do_edge_lod, + int max_aniso); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/os/os.h b/source/rvl/os/os.h index 59fdbe22a..8bfe101ff 100644 --- a/source/rvl/os/os.h +++ b/source/rvl/os/os.h @@ -39,6 +39,8 @@ int OSDisableInterrupts(void); int OSEnableInterrupts(void); int OSRestoreInterrupts(int level); +void OSPanic(char* file, int line, char* msg, ...); + #ifdef __cplusplus } #endif diff --git a/source/rvl/tpl/tpl.c b/source/rvl/tpl/tpl.c new file mode 100644 index 000000000..bdbaae80f --- /dev/null +++ b/source/rvl/tpl/tpl.c @@ -0,0 +1,52 @@ +#include "tpl.h" + +#include +#include + +void TPLBind(TPLPalette* pal) { + if (pal->version != 2142000) + OSPanic("TPL.c", 0x19, "invalid version number for texture palette"); + + pal->descriptors = + (TPLDescriptor*)(((u32)(pal->descriptors)) + ((u32)pal)); + + for (u16 i = 0; i < pal->num_descriptors; i++) { + if (pal->descriptors[i].header) { + pal->descriptors[i].header = + (TPLHeader*)(((u32)(pal->descriptors[i].header)) + + ((u32)pal)); + if (!(pal->descriptors[i].header->unpacked)) { + pal->descriptors[i].header->data = + (void*)((u32)(pal->descriptors[i].header->data) + + (u32)pal); + pal->descriptors[i].header->unpacked = 1; + } + } + if (pal->descriptors[i].clut_header) { + pal->descriptors[i].clut_header = + (TPLClutHeader*)((u32)(pal->descriptors[i].clut_header) + + (u32)pal); + if (!(pal->descriptors[i].clut_header->unpacked)) { + pal->descriptors[i].clut_header->data = + (void*)((u32)(pal->descriptors[i].clut_header->data) + (u32)pal); + pal->descriptors[i].clut_header->unpacked = 1; + } + } + } +} + +TPLDescriptor* TPLGet(TPLPalette* pal, u32 id) { + return &pal->descriptors[id % pal->num_descriptors]; +} + +void TPLGetGXTexObjFromPalette(TPLPalette* pal, GXTexObj* to, u32 id) { + TPLDescriptor* desc = TPLGet(pal, id); + int mipMap = desc->header->lod_min == desc->header->lod_max ? 0 : 1; + GXInitTexObj(to, desc->header->data, desc->header->width, + desc->header->height, desc->header->format, + desc->header->wrap_s, desc->header->wrap_t, mipMap); + GXInitTexObjLOD(to, desc->header->filter_min, + desc->header->filter_mag, desc->header->lod_min, + desc->header->lod_max, desc->header->lod_bias, 0, + desc->header->edge_lod_enabled, 0); +} diff --git a/source/rvl/tpl/tpl.h b/source/rvl/tpl/tpl.h new file mode 100644 index 000000000..602482391 --- /dev/null +++ b/source/rvl/tpl/tpl.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + u16 numEntries; + u8 unpacked; + u8 _; + int format; + void* data; +} TPLClutHeader; + +typedef struct { + u16 height; + u16 width; + u32 format; + void* data; + int wrap_s; + int wrap_t; + int filter_min; + int filter_mag; + float lod_bias; + u8 edge_lod_enabled; + u8 lod_min; + u8 lod_max; + u8 unpacked; +} TPLHeader; + +typedef struct { + TPLHeader* header; + TPLClutHeader* clut_header; +} TPLDescriptor; + +typedef struct { + u32 version; + u32 num_descriptors; + TPLDescriptor* descriptors; +} TPLPalette; + +void TPLBind(TPLPalette*); +TPLDescriptor* TPLGet(TPLPalette*, u32); + +void TPLGetGXTexObjFromPalette(TPLPalette*, GXTexObj*, u32); +void TPLGetGXTexObjFromPaletteCI(TPLPalette*, GXTexObj*, GXTlutObj*, int, u32); + +#ifdef __cplusplus +} +#endif diff --git a/sources.py b/sources.py index aaf80dfaf..180992729 100644 --- a/sources.py +++ b/sources.py @@ -18,6 +18,7 @@ compile_source("source/rvl/pad/rvlPadClamp.c", "out/rvlPadClamp.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/pad/rvlPad.c", "out/rvlPad.o", '4199_60831', RVL_OPTS + " -inline on,noauto ") compile_source("source/rvl/si/siBios.c", "out/siBios.o", '4199_60831', RVL_OPTS) +compile_source("source/rvl/tpl/tpl.c", "out/tpl.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/so/soCommon.c", "out/soCommon.o", '4199_60831', RVL_OPTS) compile_source("source/rvl/so/soBasic.c", "out/soBasic.o", '4199_60831', RVL_OPTS) From a844a9b6f1dd8f24a5e70be89a1fee6deefc8a81 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Tue, 13 Jul 2021 23:21:13 +0200 Subject: [PATCH 098/477] Generate LCF using Jinja (#26) * Generate LCF using Jinja * CI: Use requirements.txt --- .github/workflows/build.yml | 2 +- .gitignore | 1 + README.md | 31 ++- build.py | 10 +- mkwutil/format_symbols.py | 17 ++ mkwutil/gen_lcf.py | 35 ++-- mkwutil/symbols.py | 72 +++++++ pack/{dol.base.lcf => dol.lcf.j2} | 337 +----------------------------- pack/{rel.base.lcf => rel.lcf.j2} | 6 + pack/symbols.txt | 304 +++++++++++++++++++++++++++ requirements.txt | Bin 0 -> 402 bytes 11 files changed, 466 insertions(+), 349 deletions(-) create mode 100644 mkwutil/format_symbols.py create mode 100644 mkwutil/symbols.py rename pack/{dol.base.lcf => dol.lcf.j2} (52%) rename pack/{rel.base.lcf => rel.lcf.j2} (74%) create mode 100644 pack/symbols.txt create mode 100644 requirements.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00659bb8b..51352c416 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: architecture: 'x64' - name: Install dependencies - run: pip3 install capstone pyelftools colorama termcolor + run: pip3 install -r requirements.txt - name: Percent decompiled run: python3 -m mkwutil.percent_decompiled diff --git a/.gitignore b/.gitignore index 0f1411d4b..b0812bd0a 100644 --- a/.gitignore +++ b/.gitignore @@ -50,6 +50,7 @@ out # Python cache/compiled modules *.pyc __pycache__/ +/env/ tmp *.rel diff --git a/README.md b/README.md index 79529543c..b6ac36d60 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,37 @@ Every fully understood piece of reverse engineered data has been documented in a ## Dependencies - DevKitPro (for the ppc-eabi assembler) - CodeWarrior compilers (in `tools`) -- Python3: - - `pip insall pyelftools` - - `pip install capstone` - - `pip install colorama termcolor` +- Python 3 - Place a copy of Mario Kart Wii's PAL binaries: - `artifacts/orig/pal/main.dol` - `artifacts/orig/pal/StaticR.rel` +## Python Workspace + +### venv + +It is recommended to setup a Python virtual environment to simplify workspace setup. +A venv saves you from installing dependencies system- or user-wide. + +Run the setup steps once: + +```ps1 +# Initialize your venv in the ./env dir. +python3 -m venv env +# (Windows only) Allow executing the Activate script. +powershell -ExecutionPolicy Bypass -File ".\env\Scripts\Activate.ps1" +``` + +Then, each time you open a terminal, enter the venv: +* Windows: `.\env\Scripts\Activate.ps1` +* Good OSes: `source ./env/bin/activate` + +### Install dependencies + +```shell +pip install -r requirements.txt +``` + ## Building Run `python3 ./build.py` to build the game and verify build authenticity. Final results: diff --git a/build.py b/build.py index 0d89c8003..c2bdda2c3 100644 --- a/build.py +++ b/build.py @@ -202,10 +202,11 @@ def compile_sources(): def link_dol(o_files): # Generate LCF. - src_lcf_path = Path("pack", "dol.base.lcf") + src_lcf_path = Path("pack", "dol.lcf.j2") dst_lcf_path = Path("pack", "dol.lcf") slices_path = Path("pack", "dol_slices.csv") - gen_lcf(src_lcf_path, dst_lcf_path, o_files, slices_path) + symbols_path = Path("pack", "symbols.txt") + gen_lcf(src_lcf_path, dst_lcf_path, o_files, slices_path, symbols_path) # Create dest dir. dest_dir = Path("artifacts", "target", "pal") dest_dir.mkdir(parents=True, exist_ok=True) @@ -221,10 +222,11 @@ def link_dol(o_files): def link_rel(o_files): # Generate LCF. - src_lcf_path = Path("pack", "rel.base.lcf") + src_lcf_path = Path("pack", "rel.lcf.j2") dst_lcf_path = Path("pack", "rel.lcf") slices_path = Path("pack", "rel_slices.csv") - gen_lcf(src_lcf_path, dst_lcf_path, o_files, slices_path) + symbols_path = Path("pack", "symbols.txt") + gen_lcf(src_lcf_path, dst_lcf_path, o_files, slices_path, symbols_path) # Create dest dir. dest_dir = Path("artifacts", "target", "pal") dest_dir.mkdir(parents=True, exist_ok=True) diff --git a/mkwutil/format_symbols.py b/mkwutil/format_symbols.py new file mode 100644 index 000000000..e28015ce1 --- /dev/null +++ b/mkwutil/format_symbols.py @@ -0,0 +1,17 @@ +import argparse +import os +from pathlib import Path + +from mkwutil.symbols import SymbolsList + +parser = argparse.ArgumentParser(description="Reformats symbols.txt") +parser.add_argument("symbols", type=Path, help="Path to symbols.txt") +args = parser.parse_args() + +symbols = SymbolsList() +with open(args.symbols, "r") as f: + symbols.read(f) +temp_filename = args.symbols.with_name("." + args.symbols.name + ".tmp") +with open(temp_filename, "w", newline='') as f: + symbols.write(f) +os.replace(temp_filename, args.symbols) diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index d36df444e..81935827a 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -1,9 +1,12 @@ import argparse import csv +import jinja2 from pathlib import Path +from mkwutil.symbols import SymbolsList -def gen_lcf(src, dst, object_paths, slices_path): + +def gen_lcf(src, dst, object_paths, slices_path, symbols_path): # Read slices and search for stripped objects. stripped = set() for entry in csv.DictReader(open(slices_path, "r")): @@ -13,19 +16,22 @@ def gen_lcf(src, dst, object_paths, slices_path): if strip_opt.strip() != "1": continue stripped.add(Path(entry["name"]).stem) + # Read symbols list. + symbols = SymbolsList() + symbols.read(open(symbols_path, "r")) + # Create list of FORCEFILES. + force_files = [] + for obj_path in object_paths: + obj_path = Path(obj_path) + if obj_path.stem in stripped: + continue + force_files.append(str(obj_path.parent / (obj_path.stem + ".o"))) - lcf = "" - - with open(src, "r") as f: - lcf = f.read() - lcf += "\nFORCEFILES {\n" - for obj_path in object_paths: - obj_path = Path(obj_path) - if obj_path.stem in stripped: - continue - lcf += str(obj_path.parent / (obj_path.stem + ".o")) + "\n" - lcf += "}\n" - + # Compile template. + template = jinja2.Template(src.read_text()) + # Render template to string. + lcf = template.render({"symbols": list(symbols), "force_files": force_files}) + # Write out to final LCF. with open(dst, "w") as f: f.write(lcf) @@ -48,6 +54,9 @@ def gen_lcf(src, dst, object_paths, slices_path): parser.add_argument( "--slices", type=Path, required=True, help="Path to slices file" ) + parser.add_argument( + "--symbols", type=Path, required=True, help="Path to symbols.txt file" + ) args = parser.parse_args() # Read list of objects. diff --git a/mkwutil/symbols.py b/mkwutil/symbols.py new file mode 100644 index 000000000..fb82bce0e --- /dev/null +++ b/mkwutil/symbols.py @@ -0,0 +1,72 @@ +"""This module implements symbols file handling.""" + +import csv + + +class Symbol: + """A symbol is an address with a name.""" + + def __init__(self, addr, name): + self.addr = addr + self.name = name + + def __repr__(self): + return "%s=0x%08x" % (self.name, self.addr) + + +class SymbolsList: + """Simple list of symbols""" + + def __init__(self): + self._by_name = {} + self._by_addr = {} + + def __len__(self): + return len(self._by_addr) + + def __getitem__(self, addr): + return self._by_addr[addr] + + def get(self, addr): + """Looks up a symbol by address.""" + return self._by_addr.get(addr) + + def get_by_name(self, name): + """Looks up a symbol by name.""" + return self._by_name.get(name) + + def __iter__(self): + """Returns an iterator of all symbols sorted by address.""" + for addr in sorted(self._by_addr): + yield self._by_addr[addr] + + def put(self, entry): + """Inserts a symbol into the list.""" + assert isinstance(entry, Symbol) + self._by_addr[entry.addr] = entry + self._by_name[entry.name] = entry + + def __setitem__(self, addr, entry): + if isinstance(entry, str): + entry = Symbol(addr, entry) + assert addr == entry.addr + self.put(entry) + + def read(self, file): + """Reads a symbol list from a file.""" + reader = csv.reader(file, delimiter=" ") + for row in reader: + if len(row) == 0: + continue + assert len(row) == 2 + addr = int(row[0].strip(), 16) + assert 0 <= addr <= 0xFFFFFFFF + name = row[1].strip() + assert name != "" + self[addr] = name + + def write(self, file): + """Writes a symbol list to a file.""" + writer = csv.writer(file, delimiter=" ") + for sym in self: + writer.writerow(["0x%08x" % (sym.addr), sym.name]) diff --git a/pack/dol.base.lcf b/pack/dol.lcf.j2 similarity index 52% rename from pack/dol.base.lcf rename to pack/dol.lcf.j2 index 924c61463..b5dcffbd0 100644 --- a/pack/dol.base.lcf +++ b/pack/dol.lcf.j2 @@ -19,6 +19,7 @@ extabindex_ ALIGN(0x20):{} .sbss2 ALIGN(0x20):{} .stack ALIGN(0x100):{} } > text + _stack_addr = (_f_sbss2 + SIZEOF(.sbss2) + 65536 + 0x7) & ~0x7; _stack_end = _f_sbss2 + SIZEOF(.sbss2); _db_stack_addr = (_stack_addr + 0x2000); @@ -26,333 +27,15 @@ _db_stack_end = _stack_addr; __ArenaLo = (_db_stack_addr + 0x1f) & ~0x1f; __ArenaHi = 0x81700000; -__start = 0x800060A4; -__destroy_global_chain=0x80021350; -__div2i=0x800216F0; - -ftell=0x8000c21c; -fseek=0x8000c3e4; -rewind=0x8000c3e8; -fread=0x8000e610; -fwrite=0x8000e614; -fclose=0x8000ec5c; - -memmove=0x8000f1f0; -memchr=0x8000f2bc; -memcmp=0x8000f314; -qsort=0x80011b00; -srand=0x80011c90; -printf=0x800116e4; -vprintf=0x800117b0; -vsnprintf=0x8001182c; -vsprintf=0x800118b4; -snprintf=0x80011938; -sprintf=0x80011a2c; -sscanf=0x80013040; -strcpy=0x80013120; -strncpy=0x800131e0; -strcat=0x80013224; -strncat=0x80013250; -strchr=0x800133f8; -strcspn=0x80013428; -strstr=0x800135f0; -strlen=0x80021254; -strcmp=0x8001329c; -strncmp=0x800133b8; -strncasecmp=0x8001bc98; -strcasecmp=0x8001bc9c; -atof=0x80014990; -atoi=0x8001543c; -wcslen=0x80017998; -wcsncpy=0x800179d0; -wcscmp=0x80017a14; -cos=0x8001b590; -sin=0x8001ba98; -tan=0x8001bb64; -sqrt=0x8001bbf4; -__ctype_mapC=0x80270fd0; - -_savegpr_14=0x8002156C; -_savegpr_15=0x80021570; -_savegpr_16=0x80021574; -_savegpr_17=0x80021578; -_savegpr_18=0x8002157C; -_savegpr_19=0x80021580; -_savegpr_20=0x80021584; -_savegpr_21=0x80021588; -_savegpr_22=0x8002158C; -_savegpr_23=0x80021590; -_savegpr_24=0x80021594; -_savegpr_25=0x80021598; -_savegpr_26=0x8002159C; -_savegpr_27=0x800215A0; - -_restgpr_14=0x800215B8; -_restgpr_15=0x800215BC; -_restgpr_16=0x800215C0; -_restgpr_17=0x800215C4; -_restgpr_18=0x800215C8; -_restgpr_19=0x800215CC; -_restgpr_20=0x800215D0; -_restgpr_21=0x800215D4; -_restgpr_22=0x800215D8; -_restgpr_23=0x800215DC; -_restgpr_24=0x800215E0; -_restgpr_25=0x800215E4; -_restgpr_26=0x800215E8; -_restgpr_27=0x800215EC; -OSReport=0x801A25D0; -_current_locale=0x80271148; - -__register_global_object=0x80021338; -sqrt__Q23EGG5MathfFf=0x8022F80C; -frsqrt__Q23EGG5MathfFf=0x8022F85C; -__dt__Q23EGG8Vector3fFv=0x80009B40; -__dt__Q23EGG8Vector2fFv=0x80009B80; -GetRenderModeObj__Q34nw4r3g3d8G3DStateFv=0x80064440; - -SocketStartUp=0x800f24c0; -SocketShutDown=0x800f24c4; -current_time=0x800f24c8; -msleep=0x800f2510; -gethostbyname=0x800f164c; -gsCoreIsShutdown=0x800f4114; -gsimalloc=0x800f3860; -gsirealloc=0x800f3870; -gsifree=0x800f3884; -gsiSecondsToDate=0x800f254c; -gsiDateToSeconds=0x800f27b4; -gsiStartResolvingHostname=0x800f2104; -gsiCancelResolvingHostname=0x800f2238; -gsiGetResolvedIP=0x800f2300; - -gti2HandleESN=0x8010ad14; -gti2HandleServerChallenge=0x8010ae44; -gti2HandleClientResponse=0x8010b178; -gti2DeliverReliableMessage=0x8010b480; -gti2IncomingBufferMessageCompare=0x8010bc3c; -gti2BufferIncomingMessage=0x8010bc50; -gti2HandleReliableMessage=0x8010bdfc; -gti2HandleNack=0x8010c24c; -gti2HandleUnreliableMessage=0x8010c434; -gti2HandleMessage=0x8010c6fc; -gti2HandleConnectionReset=0x8010cb90; -gti2HandleHostUnreachable=0x8010cda8; -gti2ReceiveMessages=0x8010ced8; -gti2BeginReliableMessage=0x8010d124; -gti2SendClientChallenge=0x8010d4b8; -gti2SendAccept=0x8010d59c; -gti2SendReject=0x8010d664; -gti2SendClose=0x8010d758; -gti2SendKeepAlive=0x8010d820; -gti2SendAppUnreliable=0x8010d8e8; -gti2SendAck=0x8010da14; -gti2SendNack=0x8010dad8; -gti2SendClosed=0x8010dbcc; -gti2ResendMessage=0x8010dc84; -gti2Send=0x8010dd40; - -Util_RandSeed=0x800f2ec8; -Util_RandInt=0x800f2ee0; -rand=0x80011c70; -B64Decode=0x800f2f54; -B64Encode=0x800f3278; -B64DecodeLen=0x800f3484; -B64InitEncodeStream=0x800f3528; -B64EncodeStream=0x800f3538; -_UCS2CharToUTF8String=0x800f4680; -UTF8ToUCS2StringLen=0x800f47e4; -goastrdup=0x800f23f4; -_strlwr=0x800f2464; -SetSockBlocking=0x800f1bc8; -SetReceiveBufferSize=0x800f1c40; -CanReceiveOnSocket=0x800f1c9c; -CanSendOnSocket=0x800f1ce4; -getlocalhost=0x800f1d2c; - -NNMagicData=0x80385520; -NNFreeNegotiateList=0x8011a7c4; -NNBeginNegotiationWithSocket=0x8011ae3c; -NNCancel=0x8011b158; -NNProcessData=0x8011bf54; -NNThink=0x8011b718; - -ghiTrySendThenBuffer=0x8011248c; -ghiSendBufferedData=0x80111de8; - -sakeiInitRequest=0x8012249c; -sakeiFreeRequest=0x8012256c; -sakeiCheckSakeResult=0x80122570; -sakeiSoapCallback=0x8012279c; -sakeiSetupRequest=0x80122944; -sakeiStartRequest=0x80122a84; -sakeiValidateRequestFields=0x80122b18; -sakeiFillSoapRequestFieldValues=0x80122bdc; -sakeiCreateRecordValidateInput=0x80122efc; -sakeiCreateRecordFillSoapRequest=0x80122f48; -sakeiCreateRecordProcessSoapResponse=0x80122fa4; -sakeiStartCreateRecordRequest=0x80122fe8; -sakeiUpdateRecordFillSoapRequest=0x80123038; -sakeiStartUpdateRecordRequest=0x801230ac; -sakeiDeleteRecordFillSoapRequest=0x801230d4; -sakeiReadOutputRecords=0x80123138; -sakeiFreeOutputRecords=0x8012365c; -sakeiSearchForRecordsValidateInput=0x80123724; -sakeiSearchForRecordsFillSoapRequest=0x801237c4; -sakeiSearchForRecordsFreeData=0x80123964; -sakeiStartSearchForRecordsRequest=0x801239a0; -sakeiGetMyRecordsValidateInput=0x801239ac; -sakeiGetMyRecordsFillSoapRequest=0x80123a24; -sakeiGetMyRecordsProcessSoapResponse=0x80123ad4; -sakeiGetMyRecordsFreeData=0x80123aec; -sakeiStartGetMyRecordsRequest=0x80123b10; -sakeiGetSpecificRecordsFillSoapRequest=0x80123bbc; -sakeiGetSpecificRecordsProcessSoapResponse=0x80123cb8; -sakeiGetRandomRecordValidateInput=0x80123cf4; -sakeiGetRandomRecordFillSoapRequest=0x80123d6c; -sakeiGetRandomRecordProcessSoapResponse=0x80123e4c; -sakeiGetRandomRecordFreeData=0x80123ed8; - -CXInitUncompContextLZ=0x8015BEF0; -CXReadUncompLZ=0x8015BF24; -CXGetUncompressedSize=0x8015C2E0; - -IOS_OpenAsync=0x801937E0; -IOS_Open=0x801938F8; -IOS_Close=0x80193AD8; -IOS_Ioctl=0x80194290; - -OSPanic=0x801A2660; - -OSInitMutex=0x801A7EAC; - -OSLockMutex=0x801A7EE4; -OSUnlockMutex=0x801A7FC0; -OSGetTick=0x801AAD74; - -OSCreateThread=0x801A9E84; -OSIsThreadTerminated=0x801A98BC; -OSSetSwitchThreadCallback=0x801A95AC; -OSInitMessageQueue=0x801A72FC; -OSDetachThread=0x801AA4EC; -OSResumeThread=0x801aa58c; -OSSuspendThread=0x801aa824; -OSSleepThread=0x801aa9b8; -OSWakeupThread=0x801aaaa4; -OSCancelThread=0x801AA1D4; -onExit__Q23EGG6ThreadFv=0x80008E7C; -onEnter__Q23EGG6ThreadFv=0x80008E80; -OSGetCurrentThread=0x801A98B0; -OSSleepTicks=0x801AACA8; -OSGetTime=0x801aad5c; -__OSGetSystemTime=0x801AAD7C; -OSTicksToCalendarTime=0x801aafa8; - -DVDReadPrio=0x8015E834; -DVDOpen=0x8015E2BC; -DVDClose=0x8015E568; - -GXGetGPStatus=0x8016CEC4; -GXInit=0x8016B850; -GXGetYScaleFactor=0x8016F6CC; -GXGetNumXfbLines=0x8016F640; -GXSetDispCopySrc=0x8016F438; -GXSetDispCopyDst=0x8016F4B8; -GXSetDispCopyYScale=0x8016F8FC; -GXSetFog=0x801722CC; -GXInitFogAdjTable=0x801724F8; -GXSetFogRangeAdj=0x80172658; -GXInitTexObj=0x801707f8; -GXInitTexObjCI=0x80170a04; -GXInitTexObjLOD=0x80170a4c; - -OSRegisterVersion = 0x801A0504; -OSClearContext = 0x801A2098; -OSDisableInterrupts = 0x801A65AC; -OSEnableInterrupts = 0x801A65C0; -OSRestoreInterrupts = 0x801A65D4; -OSRegisterResetFunction = 0x801A8238; -OSSetWirelessID = 0x801A9260; -OSSetCurrentContext = 0x801A1E70; - -SIBusy = 0x801B254C; -SIIsChanBusy = 0x801B2568; -SIUnregisterPollingHandler = 0x801B2CF8; -SIGetStatus = 0x801B3050; -SITransfer = 0x801B33EC; -SISetCommand = 0x801B30C8; -SITransferCommands = 0x801B30DC; -SIEnablePolling = 0x801B3148; -SIDisablePolling = 0x801B31D0; -SIGetResponse = 0x801B323c; -SIGetType = 0x801B3808; -SIGetTypeAsync = 0x801B39BC; -SIRefreshSamplingRate = 0x801B3BA4; - -VIInit = 0x801B94A4; -VIWaitForRetrace = 0x801B99EC; -VIConfigure = 0x801B9F6C; -VIConfigurePan = 0x801BA650; -VIFlush = 0x801BA9A4; -VISetNextFrameBuffer = 0x801BAAB8; -VIGetNextFrameBuffer = 0x801BAB24; -VISetBlack = 0x801BAB2C; -VIGetRetraceCount = 0x801BABA4; -VIGetNextField = 0x801BABAC; -VIGetCurrentLine = 0x801BAC48; -VIGetTvFormat = 0x801BACD8; -VIGetDTVStatus = 0x801BAD38; -VISetTimeToDimming = 0x801BAFA8; - -sub_801BB0D0 = 0x801BB0D0; - -SCGetProgressiveMode=0x801B1D84; -SCGetEuRgb60Mode=0x801B1CAC; -SCGetAspectRatio=0x801B1BE4; - -NCDGetLinkStatus=0x801D0C6C; -NWC24iStartupSocket=0x801E5FD8; -NWC24iCleanupSocket=0x801E5FE8; -NWC24iLockSocket=0x801E5FF8; -NWC24iUnlockSocket=0x801E6008; -SOInetAtoN=0x801ed82c; -SOInetNtoA=0x801ed938; -SOGetHostByName=0x801edf00; -SOGetInterfaceOpt=0x801EE48C; -SOGetHostID=0x801EDE88; -SOSocket=0x801ecff4; -SOClose=0x801ed0e4; -SOBind=0x801ed188; -SOConnect=0x801ed270; -SOGetSockName=0x801ed358; -SORecvFrom=0x801ed454; -SORecv=0x801ed47c; -SOSendTo=0x801ed4a0; -SOSend=0x801ed4c8; -SOFcntl=0x801ed4ec; -SOShutdown=0x801ed61c; -SOPoll=0x801ed6d0; -SOSetSockOpt=0x801ee388; -SONtoHl=0x801ed98c; -SONtoHs=0x801ed990; -SOHtoNl=0x801ed998; -SOHtoNs=0x801ed99c; - -SSLNew=0x801ee668; -SSLConnect=0x801ee7c0; -SSLDoHandshake=0x801ee888; -SSLRead=0x801ee934; -SSLWrite=0x801eec04; -SSLShutdown=0x801eeec4; -SSLSetClientCert=0x801eef70; -SSLSetRootCA=0x801ef0dc; -SSLSetBuiltinRootCA=0x801ef224; -SSLSetBuiltinClientCert=0x801ef2ec; - -ParseSingleQR2Reply=0x8011ca4c; -ProcessIncomingReplies=0x8011cc3c; - -__cvt_fp2unsigned = 0x80021478; +{% for sym in symbols -%} +{{ sym.name }} = {{ "0x08%x" | format(sym.addr) }}; +{% endfor %} +} + +FORCEFILES { +{% for name in force_files -%} +{{ name }} +{% endfor %} } FORCEACTIVE { diff --git a/pack/rel.base.lcf b/pack/rel.lcf.j2 similarity index 74% rename from pack/rel.base.lcf rename to pack/rel.lcf.j2 index 0450c3622..a62bd5cc4 100644 --- a/pack/rel.base.lcf +++ b/pack/rel.lcf.j2 @@ -12,3 +12,9 @@ GROUP:{ .bss ALIGN(0x80):{} } > text } + +FORCEFILES { +{% for name in force_files -%} +{{ name }} +{% endfor %} +} diff --git a/pack/symbols.txt b/pack/symbols.txt new file mode 100644 index 000000000..52057ad9f --- /dev/null +++ b/pack/symbols.txt @@ -0,0 +1,304 @@ +0x800060a4 __start +0x80008e7c onExit__Q23EGG6ThreadFv +0x80008e80 onEnter__Q23EGG6ThreadFv +0x80009b40 __dt__Q23EGG8Vector3fFv +0x80009b80 __dt__Q23EGG8Vector2fFv +0x8000c21c ftell +0x8000c3e4 fseek +0x8000c3e8 rewind +0x8000e610 fread +0x8000e614 fwrite +0x8000ec5c fclose +0x8000f1f0 memmove +0x8000f2bc memchr +0x8000f314 memcmp +0x800116e4 printf +0x800117b0 vprintf +0x8001182c vsnprintf +0x800118b4 vsprintf +0x80011938 snprintf +0x80011a2c sprintf +0x80011b00 qsort +0x80011c70 rand +0x80011c90 srand +0x80013040 sscanf +0x80013120 strcpy +0x800131e0 strncpy +0x80013224 strcat +0x80013250 strncat +0x8001329c strcmp +0x800133b8 strncmp +0x800133f8 strchr +0x80013428 strcspn +0x800135f0 strstr +0x80014990 atof +0x8001543c atoi +0x80017998 wcslen +0x800179d0 wcsncpy +0x80017a14 wcscmp +0x8001b590 cos +0x8001ba98 sin +0x8001bb64 tan +0x8001bbf4 sqrt +0x8001bc98 strncasecmp +0x8001bc9c strcasecmp +0x80021254 strlen +0x80021338 __register_global_object +0x80021350 __destroy_global_chain +0x80021478 __cvt_fp2unsigned +0x8002156c _savegpr_14 +0x80021570 _savegpr_15 +0x80021574 _savegpr_16 +0x80021578 _savegpr_17 +0x8002157c _savegpr_18 +0x80021580 _savegpr_19 +0x80021584 _savegpr_20 +0x80021588 _savegpr_21 +0x8002158c _savegpr_22 +0x80021590 _savegpr_23 +0x80021594 _savegpr_24 +0x80021598 _savegpr_25 +0x8002159c _savegpr_26 +0x800215a0 _savegpr_27 +0x800215b8 _restgpr_14 +0x800215bc _restgpr_15 +0x800215c0 _restgpr_16 +0x800215c4 _restgpr_17 +0x800215c8 _restgpr_18 +0x800215cc _restgpr_19 +0x800215d0 _restgpr_20 +0x800215d4 _restgpr_21 +0x800215d8 _restgpr_22 +0x800215dc _restgpr_23 +0x800215e0 _restgpr_24 +0x800215e4 _restgpr_25 +0x800215e8 _restgpr_26 +0x800215ec _restgpr_27 +0x80021604 __div2u +0x800216f0 __div2i +0x80021828 __mod2u +0x8002190c __mod2i +0x80021a18 __shl2i +0x80021a3c __shr2u +0x80064440 GetRenderModeObj__Q34nw4r3g3d8G3DStateFv +0x800f164c gethostbyname +0x800f1bc8 SetSockBlocking +0x800f1c40 SetReceiveBufferSize +0x800f1c9c CanReceiveOnSocket +0x800f1ce4 CanSendOnSocket +0x800f1d2c getlocalhost +0x800f2104 gsiStartResolvingHostname +0x800f2238 gsiCancelResolvingHostname +0x800f2300 gsiGetResolvedIP +0x800f23f4 goastrdup +0x800f2464 _strlwr +0x800f24c0 SocketStartUp +0x800f24c4 SocketShutDown +0x800f24c8 current_time +0x800f2510 msleep +0x800f254c gsiSecondsToDate +0x800f27b4 gsiDateToSeconds +0x800f2ec8 Util_RandSeed +0x800f2ee0 Util_RandInt +0x800f2f54 B64Decode +0x800f3278 B64Encode +0x800f3484 B64DecodeLen +0x800f3528 B64InitEncodeStream +0x800f3538 B64EncodeStream +0x800f3860 gsimalloc +0x800f3870 gsirealloc +0x800f3884 gsifree +0x800f4114 gsCoreIsShutdown +0x800f4680 _UCS2CharToUTF8String +0x800f47e4 UTF8ToUCS2StringLen +0x8010ad14 gti2HandleESN +0x8010ae44 gti2HandleServerChallenge +0x8010b178 gti2HandleClientResponse +0x8010b480 gti2DeliverReliableMessage +0x8010bc3c gti2IncomingBufferMessageCompare +0x8010bc50 gti2BufferIncomingMessage +0x8010bdfc gti2HandleReliableMessage +0x8010c24c gti2HandleNack +0x8010c434 gti2HandleUnreliableMessage +0x8010c6fc gti2HandleMessage +0x8010cb90 gti2HandleConnectionReset +0x8010cda8 gti2HandleHostUnreachable +0x8010ced8 gti2ReceiveMessages +0x8010d124 gti2BeginReliableMessage +0x8010d4b8 gti2SendClientChallenge +0x8010d59c gti2SendAccept +0x8010d664 gti2SendReject +0x8010d758 gti2SendClose +0x8010d820 gti2SendKeepAlive +0x8010d8e8 gti2SendAppUnreliable +0x8010da14 gti2SendAck +0x8010dad8 gti2SendNack +0x8010dbcc gti2SendClosed +0x8010dc84 gti2ResendMessage +0x8010dd40 gti2Send +0x80111de8 ghiSendBufferedData +0x8011248c ghiTrySendThenBuffer +0x8011a7c4 NNFreeNegotiateList +0x8011ae3c NNBeginNegotiationWithSocket +0x8011b158 NNCancel +0x8011b718 NNThink +0x8011bf54 NNProcessData +0x8011ca4c ParseSingleQR2Reply +0x8011cc3c ProcessIncomingReplies +0x8012249c sakeiInitRequest +0x8012256c sakeiFreeRequest +0x80122570 sakeiCheckSakeResult +0x8012279c sakeiSoapCallback +0x80122944 sakeiSetupRequest +0x80122a84 sakeiStartRequest +0x80122b18 sakeiValidateRequestFields +0x80122bdc sakeiFillSoapRequestFieldValues +0x80122efc sakeiCreateRecordValidateInput +0x80122f48 sakeiCreateRecordFillSoapRequest +0x80122fa4 sakeiCreateRecordProcessSoapResponse +0x80122fe8 sakeiStartCreateRecordRequest +0x80123038 sakeiUpdateRecordFillSoapRequest +0x801230ac sakeiStartUpdateRecordRequest +0x801230d4 sakeiDeleteRecordFillSoapRequest +0x80123138 sakeiReadOutputRecords +0x8012365c sakeiFreeOutputRecords +0x80123724 sakeiSearchForRecordsValidateInput +0x801237c4 sakeiSearchForRecordsFillSoapRequest +0x80123964 sakeiSearchForRecordsFreeData +0x801239a0 sakeiStartSearchForRecordsRequest +0x801239ac sakeiGetMyRecordsValidateInput +0x80123a24 sakeiGetMyRecordsFillSoapRequest +0x80123ad4 sakeiGetMyRecordsProcessSoapResponse +0x80123aec sakeiGetMyRecordsFreeData +0x80123b10 sakeiStartGetMyRecordsRequest +0x80123bbc sakeiGetSpecificRecordsFillSoapRequest +0x80123cb8 sakeiGetSpecificRecordsProcessSoapResponse +0x80123cf4 sakeiGetRandomRecordValidateInput +0x80123d6c sakeiGetRandomRecordFillSoapRequest +0x80123e4c sakeiGetRandomRecordProcessSoapResponse +0x80123ed8 sakeiGetRandomRecordFreeData +0x8015bef0 CXInitUncompContextLZ +0x8015bf24 CXReadUncompLZ +0x8015c2e0 CXGetUncompressedSize +0x8015e2bc DVDOpen +0x8015e568 DVDClose +0x8015e834 DVDReadPrio +0x8016b850 GXInit +0x8016cec4 GXGetGPStatus +0x8016f438 GXSetDispCopySrc +0x8016f4b8 GXSetDispCopyDst +0x8016f640 GXGetNumXfbLines +0x8016f6cc GXGetYScaleFactor +0x8016f8fc GXSetDispCopyYScale +0x801707f8 GXInitTexObj +0x80170a04 GXInitTexObjCI +0x80170a4c GXInitTexObjLOD +0x801722cc GXSetFog +0x801724f8 GXInitFogAdjTable +0x80172658 GXSetFogRangeAdj +0x801937e0 IOS_OpenAsync +0x801938f8 IOS_Open +0x80193ad8 IOS_Close +0x80194290 IOS_Ioctl +0x801a0504 OSRegisterVersion +0x801a1e70 OSSetCurrentContext +0x801a2098 OSClearContext +0x801a25d0 OSReport +0x801a2660 OSPanic +0x801a65ac OSDisableInterrupts +0x801a65c0 OSEnableInterrupts +0x801a65d4 OSRestoreInterrupts +0x801a72fc OSInitMessageQueue +0x801a7eac OSInitMutex +0x801a7ee4 OSLockMutex +0x801a7fc0 OSUnlockMutex +0x801a8238 OSRegisterResetFunction +0x801a9260 OSSetWirelessID +0x801a95ac OSSetSwitchThreadCallback +0x801a98b0 OSGetCurrentThread +0x801a98bc OSIsThreadTerminated +0x801a9e84 OSCreateThread +0x801aa1d4 OSCancelThread +0x801aa4ec OSDetachThread +0x801aa58c OSResumeThread +0x801aa824 OSSuspendThread +0x801aa9b8 OSSleepThread +0x801aaaa4 OSWakeupThread +0x801aaca8 OSSleepTicks +0x801aad5c OSGetTime +0x801aad74 OSGetTick +0x801aad7c __OSGetSystemTime +0x801aafa8 OSTicksToCalendarTime +0x801b1be4 SCGetAspectRatio +0x801b1cac SCGetEuRgb60Mode +0x801b1d84 SCGetProgressiveMode +0x801b254c SIBusy +0x801b2568 SIIsChanBusy +0x801b2cf8 SIUnregisterPollingHandler +0x801b3050 SIGetStatus +0x801b30c8 SISetCommand +0x801b30dc SITransferCommands +0x801b3148 SIEnablePolling +0x801b31d0 SIDisablePolling +0x801b323c SIGetResponse +0x801b33ec SITransfer +0x801b3808 SIGetType +0x801b39bc SIGetTypeAsync +0x801b3ba4 SIRefreshSamplingRate +0x801b94a4 VIInit +0x801b99ec VIWaitForRetrace +0x801b9f6c VIConfigure +0x801ba650 VIConfigurePan +0x801ba9a4 VIFlush +0x801baab8 VISetNextFrameBuffer +0x801bab24 VIGetNextFrameBuffer +0x801bab2c VISetBlack +0x801baba4 VIGetRetraceCount +0x801babac VIGetNextField +0x801bac48 VIGetCurrentLine +0x801bacd8 VIGetTvFormat +0x801bad38 VIGetDTVStatus +0x801bafa8 VISetTimeToDimming +0x801bb0d0 sub_801BB0D0 +0x801d0c6c NCDGetLinkStatus +0x801e5fd8 NWC24iStartupSocket +0x801e5fe8 NWC24iCleanupSocket +0x801e5ff8 NWC24iLockSocket +0x801e6008 NWC24iUnlockSocket +0x801ecff4 SOSocket +0x801ed0e4 SOClose +0x801ed188 SOBind +0x801ed270 SOConnect +0x801ed358 SOGetSockName +0x801ed454 SORecvFrom +0x801ed47c SORecv +0x801ed4a0 SOSendTo +0x801ed4c8 SOSend +0x801ed4ec SOFcntl +0x801ed61c SOShutdown +0x801ed6d0 SOPoll +0x801ed82c SOInetAtoN +0x801ed938 SOInetNtoA +0x801ed98c SONtoHl +0x801ed990 SONtoHs +0x801ed998 SOHtoNl +0x801ed99c SOHtoNs +0x801ede88 SOGetHostID +0x801edf00 SOGetHostByName +0x801ee388 SOSetSockOpt +0x801ee48c SOGetInterfaceOpt +0x801ee668 SSLNew +0x801ee7c0 SSLConnect +0x801ee888 SSLDoHandshake +0x801ee934 SSLRead +0x801eec04 SSLWrite +0x801eeec4 SSLShutdown +0x801eef70 SSLSetClientCert +0x801ef0dc SSLSetRootCA +0x801ef224 SSLSetBuiltinRootCA +0x801ef2ec SSLSetBuiltinClientCert +0x8022f80c sqrt__Q23EGG5MathfFf +0x8022f85c frsqrt__Q23EGG5MathfFf +0x80270fd0 __ctype_mapC +0x80271148 _current_locale +0x80385520 NNMagicData diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..da09cabf35a35c82ad68989b0b05589232ecac34 GIT binary patch literal 402 zcmYk2T@Hdk5QF<{;!!m6lc*0~z~GZ7APAx&5J17ptL?}VLxyao-ETVkdB-Tx;({Ix zV$|62TVaU}E5sZ#B-U@yp`tf6M@UsJe{@tRsETlBIfh)0F<4R literal 0 HcmV?d00001 From 92dc3d759c6570ea53b09e92c3513c3768d2b642 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Jul 2021 20:48:07 +0200 Subject: [PATCH 099/477] Inline asm generator, improved graphic.py (#31) --- .gitignore | 10 +- README.md | 2 + asm/trkHeader.s | 6 + build.py | 143 +++--- diff.py | 58 ++- diff_settings.py | 22 +- glossary.md | 50 +++ mkwutil/__init__.py | 0 mkwutil/dol.py | 195 +++++--- mkwutil/dump_dol_header.py | 44 +- mkwutil/dump_elf_segments.py | 2 +- mkwutil/format_symbols.py | 6 +- mkwutil/gen_asm.py | 813 ++++++++++++++++++---------------- mkwutil/gen_asm/source.c.j2 | 27 ++ mkwutil/gen_lcf.py | 54 ++- mkwutil/graphic.py | 359 ++++----------- mkwutil/graphic/index.html.j2 | 65 +++ mkwutil/percent_decompiled.py | 195 ++++---- mkwutil/ppc_dis.py | 196 ++++---- mkwutil/rel.py | 14 +- mkwutil/sections.py | 62 +++ mkwutil/slices.py | 411 +++++++++++++++++ mkwutil/slices_test.py | 54 +++ mkwutil/sources.py | 174 ++++++++ mkwutil/symbols.py | 62 ++- mkwutil/verify_main_dol.py | 101 ++--- mkwutil/verify_object_file.py | 26 +- mkwutil/verify_staticr_rel.py | 18 +- pack/dol.lcf.j2 | 4 +- pack/dol_objects.txt | 87 ++-- pack/dol_segments.csv | 14 - pack/dol_slices.csv | 51 ++- pack/rel_objects.txt | 6 +- pack/rel_segments.csv | 7 - pack/symbols.txt | 82 ++++ requirements.txt | Bin 402 -> 1134 bytes source/decomp.h | 3 + sources.py | 107 ----- 38 files changed, 2186 insertions(+), 1344 deletions(-) create mode 100644 asm/trkHeader.s create mode 100644 glossary.md create mode 100644 mkwutil/__init__.py create mode 100644 mkwutil/gen_asm/source.c.j2 create mode 100644 mkwutil/graphic/index.html.j2 create mode 100644 mkwutil/sections.py create mode 100644 mkwutil/slices.py create mode 100644 mkwutil/slices_test.py create mode 100644 mkwutil/sources.py delete mode 100644 pack/dol_segments.csv delete mode 100644 pack/rel_segments.csv create mode 100644 source/decomp.h delete mode 100644 sources.py diff --git a/.gitignore b/.gitignore index b0812bd0a..590c4031c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,12 +31,8 @@ /pack/rel.lcf # Executables -mwasmeppc.exe -mwcceppc.exe -mwldeppc.exe -makedol.exe -powerpc-eabi-as.exe -elf2dol.exe +*.exe +*.dll *.out *.app @@ -51,6 +47,7 @@ out *.pyc __pycache__/ /env/ +.pytest_cache tmp *.rel @@ -58,6 +55,7 @@ tmp !/artifacts/orig/pal/rel/dol_rel.bin !/artifacts/orig/pal/rel/rel_abs.bin *.map +out.html # Compiler tools bundle tools.7z diff --git a/README.md b/README.md index b6ac36d60..880290dea 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ The dead-stripping feature can be re-enabled by: - [pack/rel_slices.csv](./pack/rel_slices.csv) - Entries must be sorted in the spreadsheet (current limitation). - After modifying slices, run `python3 -m mkwutil.gen_asm`. +- Add your new build target to `mkwutil/sources.py`. +- Run `build.py`. ## .rel support Most of Mario Kart Wii's game code is located inside a relocatable module (StaticR.rel for release builds). The decompilation builds this. diff --git a/asm/trkHeader.s b/asm/trkHeader.s new file mode 100644 index 000000000..74db413e8 --- /dev/null +++ b/asm/trkHeader.s @@ -0,0 +1,6 @@ +.include "macros.inc" + +.section .init, "ax" # { 80004000..80005f34 (init) } + +.asciz "Metrowerks Target Resident Kernel for PowerPC" +.skip 0xd2 diff --git a/build.py b/build.py index c2bdda2c3..de5a67062 100644 --- a/build.py +++ b/build.py @@ -1,4 +1,9 @@ -import csv +""" +Build script for Mario Kart Wii. +""" + + +from itertools import chain import os import os.path from pathlib import Path @@ -8,7 +13,12 @@ from multiprocessing.dummy import Pool as ThreadPool import multiprocessing -from mkwutil.gen_asm import read_slices +import colorama +from termcolor import colored + +from mkwutil.sources import SOURCES_DOL, SOURCES_REL +from mkwutil.slices import SliceTable +from mkwutil.sections import DOL_SECTIONS from mkwutil.verify_object_file import verify_object_file from mkwutil.gen_lcf import gen_lcf from mkwutil.pack_main_dol import pack_main_dol @@ -17,29 +27,28 @@ from mkwutil.verify_staticr_rel import verify_rel from mkwutil.percent_decompiled import percent_decompiled -import colorama -from colorama import Fore, Style - colorama.init() -dol_slices = read_slices("pack/dol_slices.csv", verbose=False) -dol_slices = { sl.obj_file : sl for sl in dol_slices } - # Remember which files are stripped. +dol_slices = SliceTable.load_dol_slices(sections=DOL_SECTIONS) stripped_files = set() -with open("pack/dol_slices.csv") as f: - rd = csv.DictReader(f) - for line in rd: - if line["strip"]: - stripped_files.add(line["name"]) +for _slice in dol_slices: + if "strip" in _slice.tags: + stripped_files.add(Path(_slice.name).stem) +dol_object_slices = dol_slices.object_slices() +# Rename objects to stem, not full path. +dol_object_slices.objects = { + Path(k).stem: v for k, v in dol_object_slices.objects.items() +} + -def native_binary(path): +def __native_binary(path): if sys.platform == "win32" or sys.platform == "msys": return path + ".exe" return path -def windows_binary(path): +def __windows_binary(path): if sys.platform == "win32" or sys.platform == "msys": return path return "wine " + path @@ -60,12 +69,12 @@ def windows_binary(path): sys.exit(1) -GAS = native_binary(os.path.join(DEVKITPPC, "bin", "powerpc-eabi-as")) +GAS = __native_binary(os.path.join(DEVKITPPC, "bin", "powerpc-eabi-as")) -MWLD = windows_binary(os.path.join("tools", "mwldeppc.exe")) +MWLD = __windows_binary(os.path.join("tools", "mwldeppc.exe")) CWCC_PATHS = { - "default": windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), + "default": __windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), # For the main game # August 17, 2007 # 4.2.0.1 Build 127 @@ -74,15 +83,15 @@ def windows_binary(path): # We don't have this, so we use build 142: # This version has the infuriating bug where random # nops are inserted into your code. - "4201_127": windows_binary(os.path.join(".", "tools", "4201_142", "mwcceppc.exe")), + "4201_127": __windows_binary(os.path.join(".", "tools", "4201_142", "mwcceppc.exe")), # For most of RVL # We actually have the correct version - "4199_60831": windows_binary( + "4199_60831": __windows_binary( os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") ), # For HBM/WPAD, NHTTP/SSL # We use build 60831 - "4199_60726": windows_binary( + "4199_60726": __windows_binary( os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") ), } @@ -117,18 +126,22 @@ def windows_binary(path): def compile_source_impl(src, dst, version="default", additional="-ipa file"): + """Compiles a source file.""" # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" if VERBOSE: print(command) subprocess.run(command, check=True) + gSourceQueue = [] + def compile_queued_sources(): + """Dispatches multiple threads to compile all queued sources.""" max_hw_concurrency = multiprocessing.cpu_count() - print(Fore.YELLOW + f"max_hw_concurrency={max_hw_concurrency}" + Style.RESET_ALL) - + print(colored(f"max_hw_concurrency={max_hw_concurrency}", color="yellow")) + pool = ThreadPool(max_hw_concurrency) pool.map(lambda s: compile_source_impl(*s), gSourceQueue) @@ -139,55 +152,71 @@ def compile_queued_sources(): # # colorama doesn't seem to work with multithreading # - for s in gSourceQueue: - src, dst = s[0:2] - if src in stripped_files: + for (src, dst, _, _) in gSourceQueue: + if src.stem in stripped_files: continue # Verify ELF file section sizes. - tha_slice = dol_slices.get(src) - if tha_slice: - verify_object_file(dst, src, tha_slice) + obj_slices = dol_object_slices.get(src.stem) + if obj_slices: + verify_object_file(dst, src, obj_slices) else: - print(Fore.YELLOW + "# Skipping slices verification on " + src + Style.RESET_ALL) + print(colored(f"Skipping slices verification on {src}", color="yellow")) gSourceQueue.clear() + # Queued -def compile_source(src, dst, version="default", additional="-ipa file"): +def compile_source(src, version="default", additional="-ipa file"): + """Compiles a C/C++ file.""" + dst = (Path("out") / src.parts[-1]).with_suffix(".o") + print(f'{colored("CC", "green")} {src}') gSourceQueue.append((src, dst, version, additional)) -def assemble(dst, src): + +def assemble(dst: Path, src: Path) -> None: + """Assembles a .s file.""" + print(f'{colored("AS", "green")} {src}') subprocess.run([GAS, src, "-mgekko", "-Iasm", "-o", dst], check=True, text=True) -def link(dst, objs, lcf, map_path, partial=False): - print(map_path) - cmd = [MWLD] + objs + ["-o", dst, "-lcf", lcf, "-fp", "hard", "-linkmode", "moreram", "-map", map_path] +def link( + dst: Path, objs: list[Path], lcf: Path, map_path: Path, partial: bool = False +) -> bool: + """Links an ELF.""" + print(f'{colored("LD", "green")} {dst}') + cmd = ( + [MWLD] + + objs + + [ + "-o", + dst, + "-lcf", + lcf, + "-fp", + "hard", + "-linkmode", + "moreram", + "-map", + map_path, + ] + ) if partial: cmd.append("-r") subprocess.run(cmd, check=True, text=True) -def make_obj(src): - substitutions = (".cpp", ".asm", ".c", ".s") - for sub in substitutions: - src = src.replace(sub, ".o") - return src - - def compile_sources(): + """Compiles all C/C++ and ASM files.""" out_dir = Path("out") out_dir.mkdir(exist_ok=True) - # compile sources - # TODO exec() is bad practice - with open("sources.py", "r") as sourcespy: - exec(sourcespy.read()) + for src in chain(SOURCES_DOL, SOURCES_REL): + compile_source(Path(src.src), src.cc, src.opts) compile_queued_sources() asm_files = [ - str(x.relative_to(os.getcwd())) + x.relative_to(os.getcwd()) for x in Path(os.path.join(os.getcwd(), "asm")).glob("**/*.s") ] @@ -195,12 +224,15 @@ def compile_sources(): (out_dir / "rel").mkdir(exist_ok=True) for asm in asm_files: - # Hack: Should use pathlib for this - out_o = "out" + asm[len("asm") :] - assemble(make_obj(out_o), asm) + out_o = Path("out") / asm.relative_to("asm").with_suffix(".o") + # Optimization: Do not assemble ASM files if the target object already exists. + if out_o.exists(): + continue + assemble(out_o, asm) -def link_dol(o_files): +def link_dol(o_files: list[Path]): + """Links main.dol.""" # Generate LCF. src_lcf_path = Path("pack", "dol.lcf.j2") dst_lcf_path = Path("pack", "dol.lcf") @@ -220,7 +252,8 @@ def link_dol(o_files): return dol_path -def link_rel(o_files): +def link_rel(o_files: list[Path]): + """Links StaticR.rel.""" # Generate LCF. src_lcf_path = Path("pack", "rel.lcf.j2") dst_lcf_path = Path("pack", "rel.lcf") @@ -242,12 +275,15 @@ def link_rel(o_files): def build(): + """Builds the game.""" Path("target").mkdir(exist_ok=True) dol_objects_path = Path("pack/dol_objects.txt") rel_objects_path = Path("pack/rel_objects.txt") dol_objects = open(dol_objects_path, "r").readlines() + dol_objects = [Path(x.strip()) for x in dol_objects] rel_objects = open(rel_objects_path, "r").readlines() + rel_objects = [Path(x.strip()) for x in rel_objects] compile_sources() @@ -261,4 +297,5 @@ def build(): percent_decompiled() -build() +if __name__ == "__main__": + build() diff --git a/diff.py b/diff.py index 10a96385c..0c7d4f0c4 100644 --- a/diff.py +++ b/diff.py @@ -540,7 +540,14 @@ def search_map_file( return cands[0] elif project.map_format == "mw": # ram elf rom object name - find = re.findall(re.compile(r' \S+ \S+ (\S+) (\S+) . ' + fn_name + r'(?: \(entry of \.(?:init|text)\))? \t(\S+)'), contents) + find = re.findall( + re.compile( + r" \S+ \S+ (\S+) (\S+) . " + + fn_name + + r"(?: \(entry of \.(?:init|text)\))? \t(\S+)" + ), + contents, + ) if len(find) > 1: fail(f"Found multiple occurrences of function {fn_name} in map file.") if len(find) == 1: @@ -557,7 +564,9 @@ def search_map_file( ] if len(objfiles) > 1: all_objects = "\n".join(objfiles) - fail(f"Found multiple objects of the same name {objname} in {project.mw_build_dir}, cannot determine which to diff against: \n{all_objects}") + fail( + f"Found multiple objects of the same name {objname} in {project.mw_build_dir}, cannot determine which to diff against: \n{all_objects}" + ) if len(objfiles) == 1: objfile = objfiles[0] # TODO Currently the ram-rom conversion only works for diffing ELF @@ -608,7 +617,11 @@ def dump_elf( return ( project.myimg, (objdump_flags + flags1, project.baseimg, None), - (objdump_flags + flags2 + maybe_get_objdump_source_flags(config), project.myimg, None), + ( + objdump_flags + flags2 + maybe_get_objdump_source_flags(config), + project.myimg, + None, + ), ) @@ -1301,12 +1314,21 @@ def do_diff(basedump: str, mydump: str, config: Config) -> List[OutputLine]: out2, branch2 = split_off_branch(line2.original) branchless1 = out1 branchless2 = out2 - out1, out2 = color_fields(arch.re_imm, out1, out2, lambda s: f"{Fore.LIGHTBLUE_EX}{s}{Style.RESET_ALL}") + out1, out2 = color_fields( + arch.re_imm, + out1, + out2, + lambda s: f"{Fore.LIGHTBLUE_EX}{s}{Style.RESET_ALL}", + ) same_relative_target = False if line1.branch_target is not None and line2.branch_target is not None: - relative_target1 = eval_line_num(line1.branch_target) - eval_line_num(line1.line_num) - relative_target2 = eval_line_num(line2.branch_target) - eval_line_num(line2.line_num) + relative_target1 = eval_line_num( + line1.branch_target + ) - eval_line_num(line1.line_num) + relative_target2 = eval_line_num( + line2.branch_target + ) - eval_line_num(line2.line_num) same_relative_target = relative_target1 == relative_target2 if not same_relative_target: @@ -1314,14 +1336,20 @@ def do_diff(basedump: str, mydump: str, config: Config) -> List[OutputLine]: out1 += branch1 out2 += branch2 - if normalize_imms(branchless1, arch) == normalize_imms(branchless2, arch): + if normalize_imms(branchless1, arch) == normalize_imms( + branchless2, arch + ): if not same_relative_target: # only imms differences sym_color = Fore.LIGHTBLUE_EX line_prefix = "i" else: - out1, out2 = color_fields(arch.re_sprel, out1, out2, sc3.color_symbol, sc4.color_symbol) - if normalize_stack(branchless1, arch) == normalize_stack(branchless2, arch): + out1, out2 = color_fields( + arch.re_sprel, out1, out2, sc3.color_symbol, sc4.color_symbol + ) + if normalize_stack(branchless1, arch) == normalize_stack( + branchless2, arch + ): # only stack differences (luckily stack and imm # differences can't be combined in MIPS, so we # don't have to think about that case) @@ -1329,7 +1357,9 @@ def do_diff(basedump: str, mydump: str, config: Config) -> List[OutputLine]: line_prefix = "s" else: # regs differences and maybe imms as well - out1, out2 = color_fields(arch.re_reg, out1, out2, sc1.color_symbol, sc2.color_symbol) + out1, out2 = color_fields( + arch.re_reg, out1, out2, sc1.color_symbol, sc2.color_symbol + ) line_color1 = line_color2 = sym_color = Fore.YELLOW line_prefix = "r" elif line1 and line2: @@ -1686,9 +1716,13 @@ def main() -> None: fail("Threeway diffing requires -w.") if args.diff_elf_symbol: - make_target, basecmd, mycmd = dump_elf(args.start, args.end, args.diff_elf_symbol, config, project) + make_target, basecmd, mycmd = dump_elf( + args.start, args.end, args.diff_elf_symbol, config, project + ) elif config.diff_obj: - make_target, basecmd, mycmd = dump_objfile(args.start, args.end, config, project) + make_target, basecmd, mycmd = dump_objfile( + args.start, args.end, config, project + ) else: make_target, basecmd, mycmd = dump_binary(args.start, args.end, config, project) diff --git a/diff_settings.py b/diff_settings.py index 3ea8e8d96..0b0cb6ed8 100644 --- a/diff_settings.py +++ b/diff_settings.py @@ -1,13 +1,17 @@ import os +from pathlib import Path -def apply(config, args): - config['mapfile'] = 'artifacts/target/pal/main.map' - config['myimg'] = 'artifacts/target/pal/main.dol' - config['baseimg'] = 'artifacts/orig/pal/main.dol' - config['makeflags'] = [] - config['source_directories'] = ['source'] +DEVKITPPC = Path(os.environ.get("DEVKITPPC", "./tools/devkitppc")) + + +def apply(config: dict, args): + config["mapfile"] = "artifacts/target/pal/main.map" + config["myimg"] = "artifacts/target/pal/main.dol" + config["baseimg"] = "artifacts/orig/pal/main.dol" + config["makeflags"] = [] + config["source_directories"] = ["source"] config["arch"] = "ppc" - config["map_format"] = "mw" # gnu or mw - config["mw_build_dir"] = "out" # only needed for mw map format + config["map_format"] = "mw" # gnu or mw + config["mw_build_dir"] = "out" # only needed for mw map format config["makeflags"] = [] - config["objdump_executable"] = os.environ['DEVKITPPC'] + "/bin/powerpc-eabi-objdump.exe" \ No newline at end of file + config["objdump_executable"] = DEVKITPPC / "bin" / "powerpc-eabi-objdump.exe" diff --git a/glossary.md b/glossary.md new file mode 100644 index 000000000..ab41c510b --- /dev/null +++ b/glossary.md @@ -0,0 +1,50 @@ +# Glossary + +### Section + +An area of program address space with a specific kind of content. + +| Name | Purpose | DOL | REL | +| ------------ | --------------------------------------- | --- | --- | +| `init` | Program entrypoint | Yes | No | +| `extab` | ? | Yes | ? | +| `extabindex` | ? | Yes | ? | +| `text` | Program code | Yes | Yes | +| `ctors` | Pointers to code to execute on startup | Yes | Yes | +| `dtors` | Pointers to code to execute on shutdown | Yes | Yes | +| `rodata` | Read-only data | Yes | Yes | +| `data` | Mutable data | Yes | Yes | +| `bss` | Zero-initialized data | Yes | Yes | +| `sdata` | Small mutable data (≤ 8 bytes) | Yes | No | +| `sbss` | Small zero-init. data (≤ 8 bytes) | Yes | No | +| `sdata2` | Small read-only data (≤ 8 bytes) | Yes | No | +| `sbss2` | Small read-only zero bytes | Yes | No | + +### Segment + +A part of the binary executable. Gets unpacked into one or more sections. + +The BSS segment is special. +Since it just contains zeros, only the address and length is saved. + +| Name | Sections | DOL | +| ------------ | ---------------------- | ------ | +| `init` | `init` | `0x00` | +| `text` | `text` | `0x01` | +| `extab` | `extab` | `0x07` | +| `extabindex` | `extabindex` | `0x08` | +| `ctors` | `ctors` | `0x09` | +| `dtors` | `dtors` | `0x0a` | +| `rodata` | `rodata` | `0x0b` | +| `data` | `data` | `0x0c` | +| `sdata` | `sdata` | `0x0d` | +| `sdata2` | `sdata2` | `0x0e` | +| `bss` | `bss`, `sdata`, `sbss` | `bss` | + +### Small data area (SDA) + +A special area in memory mainly used to load/store global 4 byte values (pointers, floating point literals) with a single instruction. +Register `r13` points into the middle of the SDA. +It is accessed using relative address. The offset is signed: +- Load: `lwz (r13)` +- Store: `stw (r13)` diff --git a/mkwutil/__init__.py b/mkwutil/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mkwutil/dol.py b/mkwutil/dol.py index 2b4f62d63..8450521d2 100644 --- a/mkwutil/dol.py +++ b/mkwutil/dol.py @@ -1,89 +1,140 @@ -import struct -from itertools import chain - +""" +DOL binary executable parser. +""" -read_u32 = lambda f: struct.unpack(">L", f.read(4))[0] -read_u16 = lambda f: struct.unpack(">H", f.read(2))[0] -read_u8 = lambda f: struct.unpack(">B", f.read(1))[0] +from pathlib import Path +import struct +from typing import Optional -class Segment: +class DolSegment: """Describes a DOL segment.""" - def __init__(self, begin: int, end: int): - assert isinstance(begin, int) and isinstance(end, int) - self.begin = begin - self.end = end + def __init__(self, index: int, start: int, stop: int, offset=None): + assert isinstance(start, int) and isinstance(stop, int) + assert start <= stop + self.index = index + self.start = start + self.stop = stop + self.offset = offset + self.data = None + + # Section names in MKW. + NAMES = [ + "init", # 0x00 + "text", # 0x01 + "", # 0x02 + "", # 0x03 + "", # 0x04 + "", # 0x05 + "", # 0x06 + "extab", # 0x07 + "extabindex", # 0x08 + "ctors", # 0x09 + "dtors", # 0x0a + "rodata", # 0x0b + "data", # 0x0c + "sdata", # 0xd + "sdata2", # 0x0e + ] def __repr__(self): - return "(%s, %s)" % (hex(self.begin), hex(self.end)) + return "%08x..%08x" % (self.start, self.stop) def empty(self): - return self.begin == self.end + """Returns whether the segment is empty.""" + return self.start == self.stop def size(self): - return self.end - self.begin + """Returns the length of the segment in bytes.""" + return self.stop - self.start + + def __len__(self): + return self.size() + + def __contains__(self, key): + return isinstance(key, int) and self.start <= key < self.stop + + def virtual_read(self, vaddr, size): + """Returns the bytes at the given virtual address.""" + if self.data is None: + return None + assert vaddr in self, f"Virtual address {vaddr} not within segment {self}" + offset = vaddr - self.start + assert ( + offset + size <= self.stop + ), f"Out-of-bounds read: {offset + size} > {self.stop}" + result = self.data[offset : offset + size] + assert len(result) == size + return result + + def name(self): + if self.index == -1: + return "bss" + try: + return self.NAMES[self.index] + except IndexError: + return "" class DolBinary: """Describes a DOL executable.""" - def __init__(self, file): - file = open(file, "rb") - self.text_ofs = [read_u32(file) for _ in range(7)] - self.data_ofs = [read_u32(file) for _ in range(11)] - - text_vaddr = [read_u32(file) for _ in range(7)] - data_vaddr = [read_u32(file) for _ in range(11)] - - self.text_size = [read_u32(file) for _ in range(7)] - self.data_size = [read_u32(file) for _ in range(11)] - - self.text_segs = [Segment(x, x + y) for x, y in zip(text_vaddr, self.text_size)] - self.data_segs = [Segment(x, x + y) for x, y in zip(data_vaddr, self.data_size)] - - bss_vaddr = read_u32(file) - bss_size = read_u32(file) - - self.bss = Segment(bss_vaddr, bss_vaddr + bss_size) - - self.entry_point = read_u32(file) - - max_vaddr = max(x.end for x in self.text_segs + self.data_segs) - self.image_base = 0x80000000 - self.image = bytearray(max_vaddr - self.image_base) - - for i in range(7): - if not self.text_size[i]: - continue - file.seek(self.text_ofs[i]) - offset = text_vaddr[i] - self.image_base - self.image[offset : offset + self.text_size[i]] = file.read( - self.text_size[i] - ) - - for i in range(11): - if not self.data_size[i]: - continue - file.seek(self.data_ofs[i]) - offset = data_vaddr[i] - self.image_base - self.image[offset : offset + self.data_size[i]] = file.read( - self.data_size[i] + SEGMENT_COUNT = 18 + + def __init__(self, file, read_body=True): + # Open file if path-like. + if isinstance(file, str) or isinstance(file, Path): + with open(file, "rb") as file: + return self.__init__(file, read_body) + # Read header. + segment_offsets = list( + bin[0] + for bin in struct.iter_unpack(">I", file.read(DolBinary.SEGMENT_COUNT * 4)) + ) + segment_addrs = list( + bin[0] + for bin in struct.iter_unpack(">I", file.read(DolBinary.SEGMENT_COUNT * 4)) + ) + segment_lens = list( + bin[0] + for bin in struct.iter_unpack(">I", file.read(DolBinary.SEGMENT_COUNT * 4)) + ) + self.segments: list[DolSegment] = [] + for i in range(0, DolBinary.SEGMENT_COUNT): + self.segments.append( + DolSegment( + i, + segment_addrs[i], + segment_addrs[i] + segment_lens[i], + segment_offsets[i], + ) ) - - def get_text_segment(self, num): - """Reads the binary content of a text segment.""" - offset = self.text_segs[num].begin - self.image_base - return self.image[offset : offset + self.text_size[num]] - - def get_data_segment(self, num): - """Reads the binary content of a data segment.""" - offset = self.data_segs[num].begin - self.image_base - return self.image[offset : offset + self.data_size[num]] - - def virtual_to_rom(self, vaddr): - for seg, ofs in chain(zip(self.text_segs, self.text_ofs), zip(self.data_segs, self.data_ofs)): - if vaddr >= seg.begin and vaddr <= seg.end: - return vaddr - seg.begin + ofs - - assert not "Address not found" \ No newline at end of file + bss_addr, bss_len = struct.unpack(">II", file.read(8)) + self.bss = DolSegment(-1, bss_addr, bss_addr + bss_len) + self.entry_point = struct.unpack(">I", file.read(4))[0] + # Determine bounds. + self.start = min(seg.start for seg in self.segments if len(seg) > 0) + self.stop = max(seg.stop for seg in self.segments if len(seg) > 0) + self.stop = max(self.stop, self.bss.stop) + # Read segment content. + if read_body: + for segment in self.segments: + if len(segment) <= 0: + continue + file.seek(segment.offset) + segment.data = file.read(segment.size()) + + def virtual_read(self, vaddr: int, size: int) -> Optional[bytes]: + """Returns the bytes at the given virtual address. Must span exactly one segment.""" + segment = next((seg for seg in self.segments if vaddr in seg), None) + if segment is None: + return None + return segment.virtual_read(vaddr, size) + + def virtual_to_rom(self, vaddr: int) -> Optional[int]: + """Returns the DOL offset given a virtual address.""" + for seg in self.segments: + if seg.start <= vaddr < self.stop: + return seg.offset + (vaddr - seg.start) + return None diff --git a/mkwutil/dump_dol_header.py b/mkwutil/dump_dol_header.py index ab2b7ac7c..5b5595c0a 100644 --- a/mkwutil/dump_dol_header.py +++ b/mkwutil/dump_dol_header.py @@ -2,25 +2,37 @@ from pathlib import Path import struct -parser = argparse.ArgumentParser() -parser.add_argument("dol", type=Path) -args = parser.parse_args() +from mkwutil.dol import DolBinary -section_count = 18 -with open(args.dol, "rb") as file: - offsets = list(struct.iter_unpack(">I", file.read(section_count * 4))) - addresses = list(struct.iter_unpack(">I", file.read(section_count * 4))) - lengths = list(struct.iter_unpack(">I", file.read(section_count * 4))) - bss_address = struct.unpack(">I", file.read(4)) - bss_length = struct.unpack(">I", file.read(4)) - entrypoint = struct.unpack(">I", file.read(4)) +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("dol", type=Path) + args = parser.parse_args() + with open(args.dol, "rb") as file: + dol = DolBinary(file, read_body=False) -print("entrypoint=%08x" % entrypoint[0]) -for i in range(section_count): + print("entrypoint=%08x" % (dol.entry_point)) + for i, segment in enumerate(dol.segments): + if segment.size() == 0: + print("section=0x%02x" % (i)) + continue + print( + "section=0x%02x offset=0x%08x start=0x%08x stop=0x%08x length=%08x" + % ( + i, + segment.offset, + segment.start, + segment.stop, + segment.size(), + ) + ) print( - "section=0x%02x offset=0x%08x address=0x%08x length=%08x" - % (i, offsets[i][0], addresses[i][0], lengths[i][0]) + "section=bss start=%08x stop=%08x length=%08x" + % (dol.bss.start, dol.bss.stop, dol.bss.size()) ) -print("section=bss address=%08x length=%08x" % (bss_address[0], bss_length[0])) + + +if __name__ == "__main__": + main() diff --git a/mkwutil/dump_elf_segments.py b/mkwutil/dump_elf_segments.py index a7fec3af3..7914e5c6d 100644 --- a/mkwutil/dump_elf_segments.py +++ b/mkwutil/dump_elf_segments.py @@ -6,7 +6,7 @@ parser.add_argument("elf", type=Path) args = parser.parse_args() -with open(args.elf, 'rb') as f: +with open(args.elf, "rb") as f: elf_file = ELFFile(f) for section in elf_file.iter_sections(): section_name = section.name.removeprefix(".") diff --git a/mkwutil/format_symbols.py b/mkwutil/format_symbols.py index e28015ce1..37daac54f 100644 --- a/mkwutil/format_symbols.py +++ b/mkwutil/format_symbols.py @@ -10,8 +10,8 @@ symbols = SymbolsList() with open(args.symbols, "r") as f: - symbols.read(f) + symbols.read_from(f) temp_filename = args.symbols.with_name("." + args.symbols.name + ".tmp") -with open(temp_filename, "w", newline='') as f: - symbols.write(f) +with open(temp_filename, "w", newline="") as f: + symbols.write_to(f) os.replace(temp_filename, args.symbols) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index de2a438c2..5b05a859e 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -1,387 +1,382 @@ +""" +Assembly file generators. +""" + import argparse -import csv -from contextlib import redirect_stdout -import os from pathlib import Path +import os import struct -from .ppc_dis import disasm_iter, disassemble_callback -from .dol import DolBinary, Segment - -from .rel import Rel, dump_staticr - - -read_u32 = lambda f: struct.unpack(">L", f.read(4))[0] -read_u16 = lambda f: struct.unpack(">H", f.read(2))[0] -read_u8 = lambda f: struct.unpack(">B", f.read(1))[0] - - -def read_segments_iter(name): - with open(name) as file: - reader = csv.DictReader(file) - for row in reader: - yield row["name"], Segment(int(row["start"], 16), int(row["end"], 16)) - - -def read_segments(name): - result = {} - for name, segment in read_segments_iter(name): - result[name] = segment - return result - - -class Slice: - def __init__(self, obj_file, segments): - self.obj_file = obj_file - self.segments = segments - - def __repr__(self): - return "Slice { %s, %u segs }" % (self.obj_file, len(self.segments)) - - -# Limitation: slices must be ordered -def read_slices(name, verbose=True): - lines = open(name).readlines() - reader = csv.DictReader(lines) - for row in reader: - if not row.pop("enabled"): - continue - if "strip" in row: - row.pop("strip") - name = row.pop("name") - segments = {} - - for cell, value in row.items(): - segment_attributes = ["Start", "End"] - seg_name = "" - seg_type = "" - for attr in segment_attributes: - if cell.endswith(attr): - seg_type = attr - seg_name = cell[: -len(attr)] - assert seg_name and seg_type - - if not value: +import jinja2 + +from mkwutil.ppc_dis import InlineInstruction, disasm_iter, label_name +from mkwutil.dol import DolBinary +from mkwutil.rel import Rel, dump_staticr +from mkwutil.symbols import Symbol, SymbolsList +from mkwutil.sections import DOL_SECTIONS, REL_SECTIONS, REL_SECTION_IDX, Section +from mkwutil.slices import Slice, SliceTable + + +jinja_env = jinja2.Environment( + loader=jinja2.PackageLoader("mkwutil", "gen_asm"), + autoescape=jinja2.select_autoescape(), +) +jinja_env.filters["addr"] = lambda x: "0x%08x" % (x) + + +class AsmGenerator: + """Generates assembly files.""" + + def __init__(self, data, _slice, symbols, output): + self.data = data + self.slice = _slice + self.symbols = symbols + self.output = output + + # TODO Define symbols in ASM + + def dump_bss(self): + """Writes a bss segment.""" + print(".skip 0x%x" % len(self.slice), file=self.output) + + def dump_data(self): + """Writes a data segment.""" + data = self.data + while len(data) >= 4: + int_val = struct.unpack(">I", data[:4])[0] + print(".4byte 0x%08X" % (int_val), file=self.output) + data = data[4:] + for byte_val in data: + print(".byte 0x%02x" % (byte_val), file=self.output) + + def dump_text(self): + """Writes a disassembled text segment.""" + for ins in disasm_iter(self.data, self.slice.start): + print(ins.disassemble(), file=self.output) + + def dump_section_body(self): + name = self.slice.section + if "bss" in name: + self.dump_bss() + elif name in ("text", "init"): + self.dump_text() + else: + self.dump_data() + + def format_segname(self, name): + if "extab" in name: + return name + "_" + return "." + name + + def dump_section_header(self): + """Writes the header to output.""" + # section permissions + perm = self.compute_perm() + print( + f""".include "macros.inc" + +.section {self.format_segname(self.slice.section)}, "{perm}" # {self.slice}""", + file=self.output, + ) + + def compute_perm(self): + """Computes the memory permissions.""" + name = self.slice.section + perm = "wa" + if name in ("text", "init"): + perm = "ax" + # if "bss" in name: + # perm = "ba" + if name == "rodata" or "2" in name: + perm = perm.replace("w", "") + return perm + + def dump_section(self): + """Writes unit header and body to output.""" + self.dump_section_header() + self.dump_section_body() + + +class CAsmGenerator: + """Generates C files with assembly functions.""" + + def __init__(self, data, _slice, symbols, output): + self.data = data + self.slice = _slice + self.symbols = symbols + self.own_symbols = self.symbols.slice(self.slice.start, self.slice.stop) + self.own_symbols.derive_sizes(self.slice.stop) + self.output = output + # The list of seen extern functions. + self.extern_functions_seen = set() + self.extern_functions = list() + + # TODO not a good name + def dump_section(self): + """Writes the C file to output.""" + addr = self.slice.start + functions = [] + for sym in self.own_symbols: + assert ( + sym.addr == addr + ), f"Currently at {hex(addr)} but next symbol is at {hex(sym.addr)}" + func_body = self.disassemble_function(sym) + functions.append( + { + "addr": sym.addr, + "size": sym.size, + "name": sym.name, + "inline_asm": func_body, + } + ) + addr += sym.size + assert ( + addr == self.slice.stop + ), f"Disassembled up to {hex(addr)} but slice goes to {hex(self.slice.stop)}" + # Sort extern functions. + self.extern_functions.sort(key=lambda sym: sym.addr) + # Write out to C file. + template = jinja_env.get_template("source.c.j2") + stream = template.stream( + functions=functions, extern_functions=self.extern_functions + ) + stream.dump(self.output) + + def disassemble_function(self, sym): + """Generates the inline assembly function body as a stream of lines.""" + assert isinstance(sym.size, int) + assert sym.size > 0 + # Grab the instructions. + data_start = sym.addr - self.slice.start + data_stop = data_start + sym.size + data = self.data[data_start:data_stop] + insns = list(disasm_iter(data, sym.addr)) + # Walk instructions to collect: + # jumps inside functions (labels) + # long jumps to other functions (extern symbol references) + labels = set() + for ins in insns: + branch_info = ins.disassemble_branch() + if branch_info is not None: + _, addr = branch_info + # If target address is within symbol, we have a label. + if sym.addr <= addr < sym.addr + sym.size: + labels.add(addr) + # And extern function declaration if: + # 1. Target address is not within translation unit + # 2. We haven't declared it previously + elif (addr not in self.own_symbols) and ( + addr not in self.extern_functions_seen + ): + # If it's a known symbol, use its name. + if addr in self.symbols: + self.extern_functions_seen.add(addr) + self.extern_functions.append(self.symbols[addr]) + # If the target address is unknown we still need to create a symbol. + else: + self.extern_functions_seen.add(addr) + ext_sym = Symbol(addr, "unk_%08x" % addr) + self.symbols.put(ext_sym) + self.extern_functions.append(ext_sym) + + sorted_labels = list(sorted(labels)) + # Disassemble instructions. + func_body = [] + for ins in insns: + # TODO is there a better way to specialize a Python object? + ins = InlineInstruction( + ins.address, ins.insn, ins.bytes, labels, self.symbols + ) + # Insert next label if address matches. + if len(sorted_labels) > 0 and sorted_labels[0] <= ins.address: + label = sorted_labels.pop(0) + func_body.append(f"{label_name(label)}:") + # Actual instruction. + func_body.append(f" {ins.disassemble()};") + return func_body + + +def get_asm_path(folder, _slice): + assert _slice.section is not None + return folder / ("%s_%08x_%08x.s" % (_slice.section, _slice.start, _slice.stop)) + + +class DOLSrcGenerator: + def __init__( + self, + slices: SliceTable, + dol: DolBinary, + symbols: SymbolsList, + dol_asm_dir: Path, + pack_dir: Path, + regen_asm: bool, + ): + self.slices = slices + self.slices.set_sections(DOL_SECTIONS) + self.dol = dol + self.symbols = symbols + self.dol_asm_dir = dol_asm_dir + self.pack_dir = pack_dir + self.regen_asm = regen_asm + self.dol_asm_sources = set() + self.dol_decomp_sources = set() + + def run(self): + """Runs ASM generation for main.dol.""" + for section in DOL_SECTIONS: + self.__process_section(section) + # Delete stale ASM files. + for path in self.dol_asm_dir.iterdir(): + if path.suffix != ".s": continue - - if not seg_name in segments: - segments[seg_name] = Segment(0, 0) - - if seg_type == "Start": - segments[seg_name].begin = int(value, 16) - elif seg_type == "End": - segments[seg_name].end = int(value, 16) - - if verbose: - print("#### %s %s" % (name, segments)) - yield Slice(name, segments) - - -def get_asm_path(name, gap, folder): - folder.mkdir(exist_ok=True) - return folder / ("%s_%08x_%08x.s" % (name, gap.begin, gap.end)) - - -def format_segname(name): - if "extab" in name: - return name + "_" - return "." + name - - -def read_u32b(filecontent, offset): - return ( - (filecontent[offset + 0] << 24) - | (filecontent[offset + 1] << 16) - | (filecontent[offset + 2] << 8) - | filecontent[offset + 3] - ) - - -# stdout must be redirected -def dump_bss(size): - print(".skip 0x%x" % size) - - -# stdout must be redirected -def dump_data(image, addr_start, seg): - for i in range(seg.begin, seg.end, 4): - if seg.end - i >= 4: - print(".4byte 0x%08X" % read_u32b(image, i - addr_start)) - continue - - for j in range(i, seg.end): - print(".byte 0x%02x" % image[j - addr_start]) - - -# stdout must be redirected -def dump_text(image, addr_start, seg): - disasm_iter( - image, seg.begin - addr_start, seg.begin, seg.size(), disassemble_callback - ) - - -def compute_perm(name): - perm = "wa" - if name == "text" or name == "init": - perm = "ax" - - # if "bss" in name: - # perm = "ba" - - if name == "rodata" or "2" in name: - perm = perm.replace("w", "") - - return perm - - -# stdout must be redirected -def dump_section_body(name, image, addr_start, seg): - if "bss" in name: - dump_bss(seg.size()) - return - - if name == "text" or name == "init": - dump_text(image, addr_start, seg) - return - - dump_data(image, addr_start, seg) - - -# stdout must be redirected -def dump_section_header(name, seg): - # section permissions - perm = compute_perm(name) - - print( - '\n.section %s, "%s" # 0x%08X - 0x%08X' - % (format_segname(name), perm, seg.begin, seg.end) - ) - - -# stdout must be redirected -def dump_section(name, image, addr_start, seg): - dump_section_header(name, seg) - dump_section_body(name, image, addr_start, seg) - - -# stdout must be redirected -def dump_object_file(image, addr_start, segments): - print('\n.include "macros.inc"') - - for segment_name, segment in segments: - dump_section(segment_name, image, addr_start, segment) - - -def disassemble_object_file(path, image, addr_start, segments): - if os.path.exists(path): - return # Don't bother updating existing file - with open(path, "w") as file: - with redirect_stdout(file): - dump_object_file(image, addr_start, segments) - - -def disasm(folder, name, image, addr_start, seg, is_data): - path = get_asm_path(name, seg, folder) - disassemble_object_file(path, image, addr_start, [(name, seg)]) - return path - - -def gen_start_segs(segments): - # Start segs: - # ['text']: (0, 0x8...) - start_seg = {} - for name, seg in segments.items(): - start_seg[name] = Segment(0, seg.begin) - - return start_seg - - -def gen_end_segs(segments): - # End segs: - # ['text']: (0x8..., 0) - end_seg = {} - for name, seg in segments.items(): - end_seg[name] = Segment(seg.end, 0) - - return end_seg - - -def find_gaps(all_slices): - last_segments = all_slices[0].segments - - # [1:] to skip initial (previously start_seg) - for slice_obj in all_slices[1:]: - obj_file = slice_obj.obj_file - slice = slice_obj.segments - for name, segment in slice.items(): - if last_segments[name].end != segment.begin: - # There's a gap! - - print( - "[.%s] Gap from %x to %x" - % (name, last_segments[name].end, segment.begin) - ) - yield name, Segment(last_segments[name].end, segment.begin) - - last_segments[name] = segment - if not obj_file.startswith("#"): - yield obj_file, None - - -def find_o_files(all_slices, folder): - """Returns all paths to object files that will assemble the binary.""" - for name, gap_seg in find_gaps(all_slices): - if gap_seg is None: - yield name, gap_seg, "??" - continue - path = get_asm_path(name, gap_seg, folder) - print(path) - path.stem.replace(".s", ".o") - yield name, gap_seg, path - - -def unpack_binary(folder, all_slices, image, addr_start): - # Disassemble all slices if they don't exist already. - # Keep track of the expected paths. - asm_paths = set() - for name, gap_seg, dest in find_o_files(all_slices, folder): - is_decompiled = gap_seg is None - - if not is_decompiled: - # print("name %s dest %s" % (name, dest)) - asm_path = disasm(folder, name, image, addr_start, gap_seg, False) - asm_paths.add(str(asm_path.relative_to(folder))) - kind = Path(dest.parent.name) # dol or rel - yield Path("out", kind, dest.stem + ".o") - - if is_decompiled: - object_name = Path(name).stem + ".o" - yield Path("out", object_name) - # Check with paths we actually have. - # Delete asm blobs that don't match those we just unpacked. - for path in folder.iterdir(): - have_path = path.relative_to(folder) - if have_path.suffix != ".s": - continue - if str(have_path) in asm_paths: - continue - print(f"Removing {path}") - os.remove(path) - - -def compute_end_cap(segments): - # Final 0x8 -> 0x8; second part ignored - end_seg = gen_end_segs(segments) - - end_slice = Slice("# 0x80 [finish] -> 0x80 [ignored]", end_seg) - - return end_slice - - -def compute_begin_cap(segments): - # Final 0x8 -> 0x8; second part ignored - start_seg = gen_start_segs(segments) - - start_slice = Slice("# 0 [ignored] -> 0x80 [start]", start_seg) - - return start_slice - - -def gen_cuts(slices, segments): - # Initial 0 -> 0x8; first part ignored - - start_slice = compute_begin_cap(segments) - end_slice = compute_end_cap(segments) - - return [start_slice] + slices + [end_slice] - - -def compute_cuts_from_spreadsheets(segments, decomplog): - # segments: binary descriptor, .text: 0x8..0x8 - # decomplog: slices, what decompiled code replaces - - slices = list(read_slices(decomplog)) - segments = read_segments(segments) - - return slices, segments, gen_cuts(slices, segments) - - -def unpack_base_dol(asm_dir, pack_dir, binary_dir): - base_dol = DolBinary(binary_dir / "main.dol") - - _, _, cuts = compute_cuts_from_spreadsheets( - pack_dir / "dol_segments.csv", - pack_dir / "dol_slices.csv", - ) - - # o_files - return list( - unpack_binary(asm_dir / "dol", cuts, base_dol.image, base_dol.image_base) - ) - - -## REL - - -def load_rel_binary(segments, binary_dir) -> (bytearray, int): - print(segments) - max_vaddr = max(segments[seg].end for seg in segments) - image_base = 0x80000000 - image = bytearray(max_vaddr - image_base) - - rel_segment_dir = binary_dir / "rel" - for segment in segments: - rel_segment_path = rel_segment_dir / (segment + ".bin") - with open(rel_segment_path, "rb") as file: - data = file.read() - - segment_data = segments[segment] - - start = segment_data.begin - end = segment_data.end - - data_len = len(data) # virtual - - for i in range(start, end): - # try: - # x = data[i - start] - # except: - # print(segment, hex(i), hex(start), hex(end),i - start, len(data)) - # print(end - (start + len(data))) - - # Hack for alignment (miss by 16) - if i - start >= data_len: - continue - image[i - image_base] = data[i - start] - - return image, image_base - - -def unpack_staticr_rel(asm_dir, pack_dir, binary_dir): - _, segments, cuts = compute_cuts_from_spreadsheets( - pack_dir / "rel_segments.csv", - pack_dir / "rel_slices.csv", - ) - - image, image_base = load_rel_binary(segments, binary_dir) - - # o_files - return list(unpack_binary(asm_dir / "rel", cuts, image, image_base)) - - -def unpack_everything(asm_dir, pack_dir, binary_dir): - """Unpack all ASM blobs into asm_dir.""" - dol_o_files = unpack_base_dol(asm_dir, pack_dir, binary_dir) - with open(pack_dir / "dol_objects.txt", "w") as file: - for path in dol_o_files: - print(path, file=file) - rel_o_files = unpack_staticr_rel(asm_dir, pack_dir, binary_dir) - with open(pack_dir / "rel_objects.txt", "w") as file: - for path in rel_o_files: - print(path, file=file) - - -if __name__ == "__main__": + if path.stem not in self.dol_asm_sources: + os.remove(path) + # Give all ASM slices a name. This makes the slice table unusable. + for _slice in self.slices: + if _slice.section is None: + continue + out_path = None + if not _slice.has_name(): + out_path = get_asm_path(Path("dol"), _slice) + else: + out_path = Path(Path(_slice.name).name) + out_path = Path("out") / out_path.with_suffix(".o") + _slice.name = str(out_path) + # Write list of objects for linker. + object_names = self.slices.object_slices().objects.keys() + with open(self.pack_dir / "dol_objects.txt", "w") as file: + for name in object_names: + print(Path(name), file=file) + + def __process_section(self, section: Section): + """Processes a program section and all its slices.""" + subtable = self.slices.slice(section.start, section.stop) + print(f".{section.name} ({section.type}): {subtable.count()} slices") + for _slice in subtable: + self.__process_slice(section, _slice) + + def __process_slice(self, section: Section, _slice: Slice): + """Process a slice in slices.csv or a gap.""" + print(f" {_slice}") + # Generate ASM file. + if not _slice.has_name(): + self.__gen_asm(section, _slice) + # Generate C code file. + # TODO Ideally this would work on the notion of objects instead of slices. + else: + source_path = Path(_slice.name) + if source_path.stem not in self.dol_decomp_sources: + self.dol_decomp_sources.add(source_path.stem) + if section.type == "code": + self.__gen_c(_slice) + + def __gen_c(self, _slice: Slice): + """Generates a C file with inline assembly if not exists.""" + # Generate C inline assembly. + c_path = Path(_slice.name) + if c_path.exists(): + return + c_path.parent.mkdir(parents=True, exist_ok=True) + print(f" => {_slice.name}") + data = self.dol.virtual_read(_slice.start, len(_slice)) + with open(c_path, "w") as file: + gen = CAsmGenerator(data, _slice, self.symbols, file) + gen.dump_section() + + def __gen_asm(self, section: Section, _slice: Slice): + """Generates an ASM file.""" + asm_path = get_asm_path(self.dol_asm_dir, _slice) + self.dol_asm_sources.add(asm_path.stem) + if not self.regen_asm and asm_path.exists(): + return + print(f" => {asm_path}") + with open(asm_path, "w") as asm_file: + data = ( + self.dol.virtual_read(_slice.start, len(_slice)) + if section.type != "bss" + else None + ) + gen = AsmGenerator(data, _slice, self.symbols, asm_file) + gen.dump_section() + + +class RELSrcGenerator: + def __init__( + self, + slices: SliceTable, + rel: Rel, + rel_asm_dir: Path, + rel_bin_dir: Path, + pack_dir: Path, + regen_asm: bool + ): + self.slices = slices + self.slices.set_sections(REL_SECTIONS) + self.rel = rel + self.rel_asm_dir = rel_asm_dir + self.rel_bin_dir = rel_bin_dir + self.pack_dir = pack_dir + self.regen_asm = regen_asm + self.rel_asm_sources = set() + + def run(self): + """Runs ASM generation for StaticR.rel""" + for section in REL_SECTIONS: + self.__process_section(section) + # Delete stale ASM files. + for path in self.rel_asm_dir.iterdir(): + if path.suffix != ".s": + continue + if path.stem not in self.rel_asm_sources: + os.remove(path) + # Give all ASM slices a name. This makes the slice table unusable. + for _slice in self.slices: + if _slice.section is None: + continue + out_path = None + if not _slice.has_name(): + out_path = get_asm_path(Path("rel"), _slice) + else: + out_path = Path(Path(_slice.name).name) + out_path = Path("out") / out_path.with_suffix(".o") + _slice.name = str(out_path) + # Write list of objects for linker. + object_names = self.slices.object_slices().objects.keys() + with open(self.pack_dir / "rel_objects.txt", "w") as file: + for name in object_names: + print(Path(name), file=file) + + def __process_section(self, section: Section): + """Processes a library section and all its slices.""" + subtable = self.slices.slice(section.start, section.stop) + print(f".{section.name} ({section.type}): {subtable.count()} slices") + for _slice in subtable: + self.__process_slice(section, _slice) + + def __process_slice(self, section: Section, _slice: Slice): + """Process a slice in slices.csv or a gap.""" + print(f" {_slice}") + # Generate ASM file. + if not _slice.has_name(): + self.__gen_asm(section, _slice) + + def __gen_asm(self, section: Section, _slice: Slice): + """Generates an ASM file.""" + asm_path = get_asm_path(self.rel_asm_dir, _slice) + self.rel_asm_sources.add(asm_path.stem) + if not self.regen_asm and asm_path.exists(): + return + print(f" => {asm_path}") + with open(asm_path, "w") as asm_file: + data = ( + self.rel.virtual_read(_slice.start, len(_slice), REL_SECTIONS, REL_SECTION_IDX) + if section.type != "bss" + else None + ) + gen = AsmGenerator(data, _slice, SymbolsList(), asm_file) + gen.dump_section() + +def main(): parser = argparse.ArgumentParser( description="Generate ASM blobs and linker object lists." ) @@ -395,21 +390,51 @@ def unpack_everything(asm_dir, pack_dir, binary_dir): default="./artifacts/orig/pal", help="Binary containing main.dol and StaticR.rel", ) + parser.add_argument("--regen_asm", action="store_true", help="Regenerate all ASM") args = parser.parse_args() args.asm_dir.mkdir(exist_ok=True) - # Feel free to move this around, dump staticr.rel segments - with open("artifacts/orig/pal/StaticR.rel", 'rb') as f: - dump_staticr(Rel(f), "artifacts/orig/pal/rel") - - # Write the macros file. - with open(args.asm_dir / "macros.inc", "w") as file: - file.write("# PowerPC Register Constants\n") - for i in range(0, 32): - file.write(".set r%i, %i\n" % (i, i)) - for i in range(0, 32): - file.write(".set f%i, %i\n" % (i, i)) - for i in range(0, 8): - file.write(".set qr%i, %i\n" % (i, i)) - - unpack_everything(args.asm_dir, args.pack_dir, args.binary_dir) + # Read symbol map. + symbols = SymbolsList() + symbols_path = args.pack_dir / "symbols.txt" + with open(symbols_path, "r") as file: + symbols.read_from(file) + + # Read DOL. + dol_path = args.binary_dir / "main.dol" + with open(dol_path, "rb") as file: + dol = DolBinary(file) + # Map out slices in DOL. + dol_slices = SliceTable(dol.start, dol.stop) + with open(args.pack_dir / "dol_slices.csv") as file: + dol_slices.read_from(file) + dol_slices = dol_slices.filter(SliceTable.ONLY_ENABLED) + # Disassemble DOL sections. + dol_asm_dir = args.asm_dir / "dol" + dol_asm_dir.mkdir(exist_ok=True) + dol_gen = DOLSrcGenerator( + dol_slices, dol, symbols, dol_asm_dir, args.pack_dir, args.regen_asm + ) + dol_gen.run() + + # Read REL. + rel_path = args.binary_dir / "StaticR.rel" + with open(rel_path, "rb") as file: + rel = Rel(file) + # Dump StaticR.rel segments. + rel_bin_dir = args.binary_dir / "rel" + dump_staticr(rel, rel_bin_dir) + # Map out slices in REL. + rel_slices = SliceTable.load_rel_slices(sections=REL_SECTIONS) + rel_slices.filter(SliceTable.ONLY_ENABLED) + # Disassemble REL sections. + rel_asm_dir = args.asm_dir / "rel" + rel_asm_dir.mkdir(exist_ok=True) + rel_gen = RELSrcGenerator( + rel_slices, rel, rel_asm_dir, rel_bin_dir, args.pack_dir, args.regen_asm + ) + rel_gen.run() + + +if __name__ == "__main__": + main() diff --git a/mkwutil/gen_asm/source.c.j2 b/mkwutil/gen_asm/source.c.j2 new file mode 100644 index 000000000..70615e281 --- /dev/null +++ b/mkwutil/gen_asm/source.c.j2 @@ -0,0 +1,27 @@ +#include "decomp.h" + +{% if extern_functions | length > 0 -%} +// Extern function references. +{%- for function in extern_functions %} +// PAL: {{ function.addr | addr }} +extern UNKNOWN_FUNCTION({{ function.name }}); +{%- endfor %} + +{% endif -%} +// Function declarations. +{%- for function in functions %} +UNKNOWN_FUNCTION({{ function.name }}); +{%- endfor %} + +{% for function in functions -%} +// Symbol: {{ function.name }} +// Function signature is unknown. +// PAL: {{ function.addr | addr }}..{{ (function.addr+function.size) | addr }} +asm UNKNOWN_FUNCTION({{ function.name }}) { + nofralloc; +{%- for line in function.inline_asm %} +{{ line }} +{%- endfor %} +} + +{% endfor -%} diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index 81935827a..8510ceca0 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -1,32 +1,60 @@ import argparse -import csv import jinja2 from pathlib import Path +import re -from mkwutil.symbols import SymbolsList +from elftools.elf.elffile import ELFFile +from mkwutil.slices import SliceTable +from mkwutil.symbols import Symbol, SymbolsList -def gen_lcf(src, dst, object_paths, slices_path, symbols_path): + +MATCH_UNK = re.compile(r"^unk_([0-9a-f]{8})$") + + +def gen_lcf( + src: Path, + dst: Path, + object_paths: list[Path], + slices_path: Path, + symbols_path: Path, +): + """Generates the LCF.""" # Read slices and search for stripped objects. + slices = SliceTable.load_path(slices_path) + slices = slices.filter(SliceTable.ONLY_ENABLED) stripped = set() - for entry in csv.DictReader(open(slices_path, "r")): - strip_opt = entry.get("strip") - if strip_opt is None: - continue - if strip_opt.strip() != "1": - continue - stripped.add(Path(entry["name"]).stem) + for _slice in slices: + if _slice.has_name() and "strip" in _slice.tags: + stripped.add(Path(_slice.name).stem) # Read symbols list. symbols = SymbolsList() - symbols.read(open(symbols_path, "r")) + symbols.read_from(open(symbols_path, "r")) + # Scan objects for references to unknown symbols (unk_XXX). + # We'll add those implicitly to LCF. + for obj_path in object_paths: + with open(obj_path, "rb") as file: + elf = ELFFile(file) + symtab = elf.get_section_by_name(".symtab") + for symbol in symtab.iter_symbols(): + match = MATCH_UNK.match(symbol.name) + if not match: + continue + addr = int(match.group(1), 16) + symbols.put(Symbol(addr, symbol.name)) + # Remove all symbols that fall into named slices. + # They will be included in the object files, not the linker command file. + for sym in symbols: + _slice, _ = slices.find(sym.addr) + assert _slice is not None + if _slice.has_name(): + del symbols[sym] # Create list of FORCEFILES. force_files = [] for obj_path in object_paths: - obj_path = Path(obj_path) if obj_path.stem in stripped: continue force_files.append(str(obj_path.parent / (obj_path.stem + ".o"))) - # Compile template. template = jinja2.Template(src.read_text()) # Render template to string. diff --git a/mkwutil/graphic.py b/mkwutil/graphic.py index ea2613a8b..0343eec56 100644 --- a/mkwutil/graphic.py +++ b/mkwutil/graphic.py @@ -1,314 +1,103 @@ -def html_emit_box(style, tooltip): - return "
" % (style, tooltip) - -def html_emit_page(boxes, boxes2): - return """ - - - - - - - -

DOL Decompiled

""" + boxes + """ -

DOL Libraries

""" + boxes2 + """ - -""" - -#
-#
- - -def build_box(width, label, color): - return html_emit_box("background-color:%s; width:%spx;" % (color, width), label) - -#CODE_COLOR = "#d5feff" -CODE_COLOR = "hsl(%s, 100, 91.8)" -UNK_COLOR = "#000000" - +import argparse import colorsys - -def make_code_color(hue): - rgb = colorsys.hsv_to_rgb(hue, 100/100, 91.8/100) - #print(rgb) - return rgb - - -def make_code_color_hex(hue): - vals = list(round(i * 255) for i in make_code_color(hue)) - - r = "#" - - for v in vals: - if len(hex(v)) == len(hex(3)): - r += "0" - r += hex(v)[2:] - - return r - -PX_FACTOR = 1 / 2000 - +from dataclasses import dataclass +from pathlib import Path import random -def box_from_code_seg(begin, end, label): - return build_box(abs(end - begin) * PX_FACTOR, label, make_code_color_hex(random.randint(0, 360)/360)) - -def box_from_gap_seg(begin, end): - return build_box(abs(end - begin) * PX_FACTOR, "gap", UNK_COLOR) - -SPLITS = [ - ["rvl/trk/rvlTrkMem.c",0x80005f34,0x80006068,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["nw4r/g3d/g3d_camera.cpp",0,0, 0,0, 0,0, 0x8006a0c0 ,0x8006a518, 0,0, 0,0, 0,0, 0,0, 0x802bd4ec,0x802bd4ec, 0,0, 0,0, 0x80387cac,0x80387cd8, 0,0], - ["nw4r/g3d/g3d_fog.cpp",0,0,0,0,0,0,0x800774d0,0x800775d0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80387d58,0x80387d5C,0,0], - ["nw4r/math/mathTriangular.cpp",0,0,0,0,0,0,0x80085110,0x80085578,0,0,0,0,0x80248010,0x80249020,0x80274148,0x80274250,0,0,0,0,0,0,0x80387e80,0x80387ea4,0,0], - ["nw4r/math/mathTypes.cpp",0,0,0,0,0,0,0x80085600,0x80085938,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80387ea8,0x80387eb4,0,0], - ["nw4r/ut/utList.cpp",0,0,0,0,0,0,0x800AEF60,0x800af1a0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["dwc/common/dwc_error.c",0,0,0,0,0,0,0x800CCB4C,0x800CCC80,0,0,0,0,0,0,0x80275700,0x80275758,0,0,0,0,0x803862A8,0x803862B0,0,0,0,0], - ["gamespy/darray.c",0,0,0,0,0,0,0x800ef378,0x800efdcc,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["gamespy/hashtable.c",0,0,0,0,0,0,0x800efdcc,0x800f0264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["gamespy/md5c.c",0,0,0,0,0,0,0x800f0264,0x800f118c,0,0,0,0,0x8024c6b8,0x8024c6c9,0x8027aca0,0x8027ace0,0,0,0,0,0,0,0,0,0,0], - ["gamespy/common/revolution/gsSocketRevolution.c",0,0,0,0,0,0,0x800f118c,0x800f164c,0,0,0,0,0,0,0,0,0,0,0,0,0x80386350,0x80386358,0,0,0,0], - ["gamespy/common/revolution/gsUtilRevolution.c",0,0,0,0,0,0,0x800f1f58,0x800f2048,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["gamespy/common/gsAvailable.c",0,0,0,0,0,0,0x800f38a4,0x800f3a20,0,0,0,0,0,0,0x8027ad58,0x8027ad79,0x802f2338,0x802f2410,0,0,0x80386358,0x8038635c,0,0,0,0], - ["gamespy/common/gsCore.c",0,0,0,0,0,0,0x800f3c08 ,0x800f41dc,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["gamespy/common/gsUdpEngine.c",0,0,0,0,0,0,0x800f489c,0x800f5a6c,0,0,0,0,0,0,0,0,0x802f2440,0x802f247c,0,0,0,0,0,0,0,0], - ["gamespy/common/gsXML.c",0,0,0,0,0,0,0x800f5a6c,0x800fb828,0,0,0,0,0,0,0x8027ad80,0x8027af18,0,0,0x803850a0,0x80385105,0,0,0,0,0,0], - ["gamespy/gp/gp.c",0,0,0,0,0,0,0x800fb828,0x800fc7d4,0,0,0,0,0,0,0x8027af18,0x8027b289,0,0,0x80385108,0x80385118,0,0,0,0,0,0], - ["gamespy/gp/gpi.c",0,0,0,0,0,0,0x800fc7d4,0x800fd160,0,0,0,0,0,0,0x8027b290,0x8027b30f,0,0,0x80385118,0x80385150,0,0,0,0,0,0], - ["gamespy/gp/gpiBuddy.c",0,0,0,0,0,0,0x800fd160,0x800fee90,0,0,0,0,0,0,0x8027b310,0x8027b453,0,0,0x80385150,0x803851ea,0,0,0,0,0,0], - ["gamespy/gp/gpiBuffer.c",0,0,0,0,0,0,0x800fee90,0x800ff8c4,0,0,0,0,0,0,0x8027b458,0x8027b4ba,0,0,0x803851f0,0x80385206,0,0,0,0,0,0], - ["gamespy/gp/gpiCallback.c",0,0,0,0,0,0,0x800ff8c4,0x800ffe28 ,0,0,0,0,0,0,0x8027b4c0,0x8027b4cf,0,0,0,0,0,0,0,0,0,0], - ["gamespy/gp/gpiConnect.c",0,0,0,0,0,0,0x800ffe28 ,0x80101470,0,0,0,0,0,0,0x8027b4d0,0x8027b876,0,0,0x80385208,0x8038529b,0,0,0,0,0,0], - ["gamespy/gp/gpiInfo.c",0,0,0,0,0,0,0x80101470,0x80103908,0,0,0,0,0,0,0x8027b878,0x8027bbce,0,0,0x803852a0,0x80385355,0,0,0x80388470,0x80388474,0,0], - ["gamespy/gp/gpiKeys.c",0,0,0,0,0,0,0x80103908,0x80103f70,0,0,0,0,0,0,0x8027bbd0,0x8027bc11,0,0,0x80385358,0x8038535a,0,0,0,0,0,0], - ["gamespy/gp/gpiOperation.c",0,0,0,0,0,0,0x80103f70,0x80104648,0,0,0,0,0,0,0x8027bc18,0x8027bc60,0,0,0,0,0,0,0,0,0,0], - ["gamespy/gp/gpiPeer.c",0,0,0,0,0,0,0x80104648,0x80105d54,0,0,0,0,0,0,0x8027bc60,0x8027bd2a,0,0,0x80385360,0x803853b9,0,0,0,0,0,0], - ["gamespy/sake/sakeMain.c",0,0,0,0,0,0,0x80121eec ,0x8012249c,0,0,0,0,0,0,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,0,0,0,0,0,0], - ["rvl/arc/rvlArchive.c",0,0,0,0,0,0,0x80124500,0x80124E80,0,0,0,0,0,0,0x8027E708,0x8027E772,0,0,0x803857F0,0x803857F6,0,0,0,0,0,0], - ["rvl/mem/rvlMemHeap.c",0,0,0,0,0,0,0x801981ec,0x80198798,0,0,0,0,0,0,0,0,0x80346cf0,0x80346d18,0,0,0x80386838,0x8038683C,0,0,0,0], - ["rvl/mem/rvlMemExpHeap.c",0,0,0,0,0,0,0x80198798,0x80199430,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["rvl/mem/rvlMemFrmHeap.c",0,0,0,0,0,0,0x80199430,0x801998A4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["rvl/mem/rvlMemUnitHeap.c",0,0,0,0,0,0,0x801998A4,0x80199b58,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["rvl/mem/rvlMemAllocator.c",0,0,0,0,0,0,0x80199b58,0x80199bf0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80388860,0x80388870,0,0], - ["rvl/mem/rvlMemList.c",0,0,0,0,0,0,0x80199BF0,0x80199D04,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["rvl/mtx/rvlMtx.c",0,0,0,0,0,0,0x80199d04,0x8019a9c4,0,0,0,0,0,0,0,0,0,0,0x80385a08,0x80385a10,0,0,0x80388870,0x80388890,0,0], - ["rvl/mtx/rvlMtx2.c",0,0,0,0,0,0,0x8019a9c4,0x8019abe4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80388890,0x803888a8,0,0], - ["rvl/mtx/rvlVec.c",0,0,0,0,0,0,0x8019abe4,0x8019ae08,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x803888a8,0x803888b4,0,0], - ["rvl/mtx/rvlQuat.c",0,0,0,0,0,0,0x8019ae08,0x8019b178,0,0,0,0,0x80252c78,0x80252c84,0,0,0,0,0,0,0,0,0x803888b8,0x803888cc,0,0], - ["rvl/so/soCommon.c",0,0,0,0,0,0,0x801ec088,0x801ecf20,0,0,0,0,0,0,0x802a2318,0x802a24f4,0x80357220,0x80357238,0x80385ee0,0x80385ee8,0x80386D30,0x80386D38,0,0,0,0], - ["rvl/so/soBasic.c",0,0,0,0,0,0,0x801ecf20,0x801ecff4,0,0,0,0,0,0,0x802a24f8 ,0x802a2543,0,0,0x80385ee8,0x80385eeC,0,0,0,0,0,0], - ["egg/core/eggAllocator.cpp",0,0,0,0,0,0,0x8020F62C,0x8020F6EC,0,0,0,0,0,0,0x802A2668,0x802A2680 ,0,0,0,0,0,0,0,0,0,0], - ["egg/core/eggArchive.cpp",0,0,0,0,0,0,0x8020F6EC,0x8020FCC4,0,0,0,0,0,0,0x802A2680,0x802A268C,0x803832D8,0x803832E4,0,0,0x80386D80,0x80386D84,0,0,0,0], - ["egg/core/eggDisposer.cpp",0,0,0,0,0,0,0x8021A0F0,0x8021A1B8,0,0,0,0,0,0,0x802A2B48,0x802A2B54 ,0,0,0,0,0,0,0,0,0,0], - ["egg/core/eggExpHeap.cpp",0,0,0,0,0,0,0x802269A8,0x80226F04,0,0,0,0,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,0,0,0,0,0,0,0,0,0,0], - ["egg/core/eggGraphicsFifo.cpp",0,0,0,0,0,0,0x80229540,0x802296A8,0,0,0,0,0,0,0x802A30B0,0x802A30BC,0,0,0,0,0x80386E90,0x80386E99,0,0,0,0], - ["egg/core/eggHeap.cpp",0,0,0,0,0,0,0x802296A8,0x80229FAC,0,0,0,0,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,0,0,0x80386EA0,0x80386EC0,0x80388D68 ,0x80388D80,0,0], - ["egg/math/eggQuat.cpp",0,0,0,0,0,0,0x80239DFC,0x80239E10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["egg/core/eggScene.cpp",0,0,0,0,0,0,0x8023AD10,0x8023ADDC,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], - ["egg/core/eggStreamDecomp.cpp",0,0,0,0,0,0,0x80242498,0x80242504,0,0,0,0,0,0,0x802A3F78,0x802A3F90,0,0,0,0,0,0,0,0,0,0], - ["egg/core/eggSystem.cpp",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0x80386F60,0x80386F64,0,0,0,0], - ["egg/core/eggThread.cpp",0,0,0,0,0,0,0x802432E0 ,0x80243754,0,0,0,0,0,0,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,0,0,0,0,0,0,0,0], - ["egg/core/eggUnitHeap.cpp",0,0,0,0,0,0,0x80243754,0x80243A00,0,0,0,0,0,0,0x802A3FD8,0x802A4004,0,0,0,0,0,0,0,0,0,0], - ["egg/math/eggVector.cpp",0,0,0,0,0,0,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,0,0,0,0,0,0,0x80384B70,0x80384BF4,0,0,0x80386F78,0x80386F90,0x803890F8 ,0x80389104,0,0], - ["egg/core/eggVideo.cpp",0,0,0,0,0,0,0x80243D18,0x80244074,0,0,0,0,0x802582E0,0x80258560,0,0,0,0,0,0,0,0,0x80389108,0x80389118,0,0] -] - - - -def child_of(parent, child): - if child[0] >= parent[1]: - return False - - if child[1] <= parent[0]: - return False - - return True - -def fs(a): - return "%x %x %s" % (a[0], a[1], a[2]) - - -class RangeMapper: - def __init__(self, begin, end): - self.segs = [ [ begin, end, "unk"] ] - self.begin = begin - self.end = end +import webbrowser +import jinja2 - def comp_total(self): - t = 0 - for s in self.segs: - t += abs(s[1] - s[0]) +from mkwutil.sections import DOL_LIBS, DOL_SECTIONS +from mkwutil.slices import Slice, SliceTable - if s[0] > s[1]: - return 0 - return t - - def assert_valid(self): - ctotal = self.comp_total() - rtotal = self.end - self.begin - - if ctotal == rtotal: - return - - print("INVALID: CTOTAL = %x, RTOTAL = %x" % (ctotal, rtotal)) - - assert False - - def find_parent(self, block): - for s in self.segs: - if child_of(s, block): - return s - - return None - - def add_mem_range(self, sec, file): - self.assert_valid() - - low, high = sec - - # Handle replace case - for s in self.segs: - if s[0] == low and s[1] == high: - # print("REPLACE") - s[0] = low - s[1] = high - s[2] = file - self.assert_valid() - self.segs = sorted(self.segs) - return - - - self.segs = sorted(self.segs) - - parent = self.find_parent(sec) - assert parent - - print("Splitting to add %s" % file) - - - parent_file = parent[2] - low_block = [parent[0], low, "unk"] - mid_block = [low, high, file] - top_block = [high, parent[1], "unk"] - - - - #print("Parent") - #print(fs(parent)) - #print("us") - #print(fs((sec[0], sec[1], file))) - #print(list(fs(a) for a in segs)) - assert low_block[0] <= low_block[1] - assert mid_block[0] <= mid_block[1] - assert top_block[0] <= top_block[1] - - if low_block[0] != low_block[1]: - self.segs.append(low_block) - - parent[0] = mid_block[0] - parent[1] = mid_block[1] - parent[2] = mid_block[2] +DOL_BEGIN = DOL_SECTIONS[0].start +DOL_END = DOL_SECTIONS[-1].stop +DOL_SIZE = DOL_END - DOL_BEGIN - if parent[0] == parent[1]: - print("Removing parent") - self.segs.remove(parent) - - if top_block[0] != top_block[1]: - self.segs.append(top_block) - - self.assert_valid() - self.segs = sorted(self.segs) +jinja_env = jinja2.Environment( + loader=jinja2.PackageLoader("mkwutil", "graphic"), + autoescape=jinja2.select_autoescape(), +) -def populate_mapper(mapper, splits): - for split in splits: - file = split[0] - for i in range(1, len(split), 2): - sec = [split[i], split[i + 1]] - if sec[0] == 0: - assert sec[1] == 0 - continue +def make_code_color(hue): + return colorsys.hsv_to_rgb(hue, 100 / 100, 91.8 / 100) - if sec[0] == sec[1]: - print("WARN: file %s invalidy configured, begin == end, begin != 0" % file) - continue - - assert sec[1] > sec[0] - mapper.add_mem_range(sec, file) +def make_code_color_hex(hue): + vals = list(round(i * 255) for i in make_code_color(hue)) + r = f"#%02x%02x%02x" % (vals[0], vals[1], vals[2]) + return r - for seg in mapper.segs: - perc = "-" * int(1 + abs(seg[0]-seg[1]) / 400000) - print("%s %s %s %s" % (perc, hex(seg[0]), hex(seg[1]), seg[2])) -def size_of_mapper(mapper): - n_code = 0 - n_unk = 0 +@dataclass +class Box: + """A graphical box to display.""" - for seg in mapper.segs: - if seg[2] == "unk": - n_unk += abs(seg[0] - seg[1]) - else: - n_code += abs(seg[0] - seg[1]) + width: str + color_code: str + tooltip: str - return n_code, n_unk + PX_FACTOR = 1 / 2000 + # CODE_COLOR = "#d5feff" + CODE_COLOR = "hsl(%s, 100, 91.8)" + UNK_COLOR = "#000000" -def boxlist_from_mapper(mapper): - boxes = [] + @staticmethod + def from_slice(_slice: Slice) -> "Box": + if not _slice.has_name(): + return Box(len(_slice) * Box.PX_FACTOR, Box.UNK_COLOR, "") + else: + return Box( + len(_slice) * Box.PX_FACTOR, + make_code_color_hex(random.randint(0, 360) / 360), + _slice.name, + ) - for seg in mapper.segs: - if seg[2] == "unk": - boxes.append(box_from_gap_seg(seg[0], seg[1])) - else: - boxes.append(box_from_code_seg(seg[0], seg[1], seg[2])) - return boxes +def size_of_mapper(slices: SliceTable) -> tuple[int, int]: + n_code = 0 + n_unk = 0 + for _slice in slices: + if not _slice.has_name(): + n_unk += len(_slice) + else: + n_code += len(_slice) + return n_code, n_unk -def percent_decomp_stats(mapper): - n_code, n_unk = size_of_mapper(mapper) - total = n_unk + n_code - print("Total: %s, real total: %s" % (total, DOL_SIZE)) +def percent_decomp_stats(slices: SliceTable) -> None: + """Prints percent stats of slices.""" + n_code, n_unk = size_of_mapper(slices) - print("Code&Data Percent: %s" % (100 * n_code / total)) + total = n_unk + n_code + print("Total: %s, real total: %s" % (total, DOL_SIZE)) -DOL_BEGIN = 0x80004000 -DOL_END = 0x8038917C + print("Code&Data Percent: %s" % (100 * n_code / total)) -DOL_SIZE = DOL_END - DOL_BEGIN def standard_boxes(): - mapper = RangeMapper(DOL_BEGIN, DOL_END) - populate_mapper(mapper, SPLITS) + slices = SliceTable.load_dol_slices(sections=DOL_SECTIONS) + return map(Box.from_slice, slices) - # percent_decomp_stats(mapper) - - return boxlist_from_mapper(mapper) - -LIB_SPLITS = [ - [ "NW4R", 0x80021BB0, 0x800BBB80], - [ "RFL", 0x800BBB80, 0x800CC7E4], - [ "DWC", 0x800CC7E4, 0x800EF378], - [ "SPY", 0x800EF378, 0x80123F88], - [ "RVL", 0x80123F88, 0x8020F62C], - [ "EGG", 0x8020F62C, 0x80244DD4] -] def lib_boxes(): - mapper = RangeMapper(DOL_BEGIN, DOL_END) - populate_mapper(mapper, LIB_SPLITS) - - return boxlist_from_mapper(mapper) - -page = html_emit_page("\n".join(standard_boxes()), "\n".join(lib_boxes())) - - -with open("out.html", 'w') as f: - f.write(page) \ No newline at end of file + slices = SliceTable(sections=DOL_SECTIONS) + for _slice in DOL_LIBS: + slices.add(_slice) + return map(Box.from_slice, slices) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("-o", "--output", type=Path, default="out.html") + parser.add_argument("-s", "--silent", action="store_true", help="Don't open web browser") + args = parser.parse_args() + with open(args.output, "w") as file: + jinja_env.get_template("index.html.j2").stream({ + "dol_decomp": standard_boxes(), + "dol_libraries": lib_boxes(), + }).dump(file) + if not args.silent: + webbrowser.open(args.output) diff --git a/mkwutil/graphic/index.html.j2 b/mkwutil/graphic/index.html.j2 new file mode 100644 index 000000000..7dd19c44b --- /dev/null +++ b/mkwutil/graphic/index.html.j2 @@ -0,0 +1,65 @@ + + + + + + + + +{% macro slices(boxes, tooltip_class) -%} +
+{% for box in boxes -%} + +{% if box.tooltip -%} +{{ box.tooltip }} +{% endif %} + +{% endfor %} +
+{%- endmacro %} + +

DOL Libraries

+{{ slices(dol_libraries, "rectangle-tooltip") }} + +

DOL Decompiled

+{{ slices(dol_decomp, "rectangle-tooltip rectangle-below") }} + + + diff --git a/mkwutil/percent_decompiled.py b/mkwutil/percent_decompiled.py index 0baacc42a..d26fac1fe 100644 --- a/mkwutil/percent_decompiled.py +++ b/mkwutil/percent_decompiled.py @@ -1,105 +1,51 @@ -import csv -import os +from copy import copy from pathlib import Path +import pytablewriter +from pytablewriter.style import Style +from termcolor import colored -def process_line(line): - name = "Untitled" - start = None - code_total = 0 - data_total = 0 +from mkwutil.slices import SliceTable +from mkwutil.sections import Section, REL_SECTIONS, DOL_SECTIONS, DOL_LIBS - for tag, entry in line.items(): - if tag == "enabled" and entry == 0: - return - elif tag == "name": - name = entry - continue - elif tag == "strip": - continue - - is_code = "text" in tag - if tag.endswith("Start"): - if not entry: - start = None - continue - - start = int(entry, 16) +def simple_count(slices: SliceTable) -> tuple[int, int]: + """Returns the number of code and data bytes part of named slices.""" + code_total, data_total = 0, 0 + for _slice in slices: + if not _slice.has_name(): continue - - if "End" in tag and start: - size = int(entry, 16) - start - - if is_code: - code_total += size - else: - data_total += size - - return name, code_total, data_total - - -def parse_slices(path): - with open(path, "r") as file: - reader = csv.DictReader(file) - for line in reader: - yield process_line(line) - - -def simple_count(path): - code_total = 0 - data_total = 0 - - for o_name, o_code_total, o_data_total in parse_slices(path): - code_total += o_code_total - data_total += o_data_total - + if _slice.section in ("init", "text"): + code_total += len(_slice) + else: + data_total += len(_slice) return code_total, data_total -def segments_of(path): - with open(path, "r") as file: - reader = csv.DictReader(file) - for line in reader: - is_code = "text" in line["name"] - yield is_code, int(line["end"], 16) - int(line["start"], 16) - - -def binary_total(path): - segments = list(segments_of(path)) - - num_code = sum(size if is_code else 0 for is_code, size in segments) - num_data = sum(size if not is_code else 0 for is_code, size in segments) - - return num_code, num_data +def binary_total(sections: list[Section]) -> tuple[int, int]: + """Returns the number of code and data bytes in the binary.""" + code_total, data_total = 0, 0 + for section in sections: + if section.name in ("init", "text"): + code_total += len(section) + else: + data_total += len(section) + return code_total, data_total def to_percent(frac): - return round(frac * 100_000) / 1000 + return "%7.3f%%" % (frac * 100) def analyze(prefix, progress, total): - # print("%s %s bytes (%s%%) of code, %s bytes (%s%%) of data decompiled" % ( - # prefix, - # progress[0], to_percent(progress[0] / total[0]) if total[0] else '', - # progress[1], to_percent(progress[1] / total[1]) if total[1] else '' - # )) - print( - "%s %s%% code, %s%% data decompiled" - % ( - prefix, - to_percent(progress[0] / total[0]) if total[0] else "", - to_percent(progress[1] / total[1]) if total[1] else "", - ) - ) + cells = [""] * 3 + cells[0] = prefix + if total[0]: + cells[1] = to_percent(progress[0] / total[0]) + if total[1]: + cells[2] = to_percent(progress[1] / total[1]) + return cells -# TODO: This system is garbage. -# Ideally we'd have -# -# { "name": "EGG", "start": ..., "end": ...} -# -# and it would generate everything for us -# def get_progress(slices, filter): progress = [0, 0] @@ -112,48 +58,63 @@ def get_progress(slices, filter): return progress + def percent_decompiled(dir="."): dir = Path(dir) - dol_slices_path = dir / "pack" / "dol_slices.csv" - dol_progress = simple_count(dol_slices_path) - dol_segments_path = dir / "pack" / "dol_segments.csv" - dol_total = binary_total(dol_segments_path) - analyze("[DOL]", dol_progress, dol_total) - - rvl_progress = get_progress(parse_slices(dol_slices_path), "rvl") - rvl_total = [0x8020F62C - 0x80123F88, None] - analyze(" -> [RVL]", rvl_progress, rvl_total) - - egg_progress = get_progress(parse_slices(dol_slices_path), "egg") - egg_total = [0x80244DD4 - 0x8020F62C, None] - analyze(" -> [EGG]", egg_progress, egg_total) - - spy_progress = get_progress(parse_slices(dol_slices_path), "gamespy") - spy_total = [0x80123F88 - 0x800EF378, None] - analyze(" -> [SPY]", spy_progress, spy_total) - - rel_slices_path = dir / "pack" / "rel_slices.csv" - rel_progress = simple_count(rel_slices_path) - rel_segments_path = dir / "pack" / "rel_segments.csv" - rel_total = binary_total(rel_segments_path) - analyze("[REL]", rel_progress, rel_total) + matrix = [] + # DOL progress. + dol_slices = SliceTable.load_dol_slices() + dol_progress = simple_count(dol_slices) + dol_total = binary_total(DOL_SECTIONS) + matrix.append(analyze("DOL", dol_progress, dol_total)) + # DOL Libraries. + for lib in DOL_LIBS: + assert lib.section == "text", "For now only text section per lib supported" + lib_progress = simple_count(dol_slices.slice(lib.start, lib.stop)) + lib_total = (len(lib), None) + matrix.append(analyze("> " + lib.name, lib_progress, lib_total)) + # REL progress. + rel_slices = SliceTable.load_rel_slices() + rel_progress = simple_count(rel_slices) + rel_total = binary_total(REL_SECTIONS) + matrix.append(analyze("REL", rel_progress, rel_total)) + # Total progress. def piecewise_add(x, y): return list(a + b for a, b in zip(x, y)) - analyze( - "--- main.dol + StaticR.rel ---\n", - piecewise_add(dol_progress, rel_progress), - piecewise_add(dol_total, rel_total), + footer_idx = len(matrix) + matrix.append( + [ + colored(cell, attrs=["bold"]) + for cell in analyze( + "TOTAL", + piecewise_add(dol_progress, rel_progress), + piecewise_add(dol_total, rel_total), + ) + ] ) + # Print table. + print("-" * 31) + writer = pytablewriter.BorderlessTableWriter( + headers=["part", "code", "data"], + column_styles=[ + Style(align="left"), + Style(align="right"), + Style(align="right"), + ], + margin=1, + value_matrix=matrix, + ) + writer.write_table() + print("-" * 31) - print("------") + # Player stats. print("Player:") print(" - %u BR (main.dol)" % (dol_progress[0] / dol_total[0] * 4999 + 5000)) print(" - %u VR (StaticR.rel)" % (rel_progress[0] / rel_total[0] * 4999 + 5000)) - - print("1 BR = %s lines of asm code." % (.1 * round(10 * dol_total[0] / 4999 / 4))) - print("1 VR = %s lines of asm code." % (.1 * round(10 * rel_total[0] / 4999 / 4))) + print("1 BR = %s lines of asm code." % (0.1 * round(10 * dol_total[0] / 4999 / 4))) + print("1 VR = %s lines of asm code." % (0.1 * round(10 * rel_total[0] / 4999 / 4))) if __name__ == "__main__": diff --git a/mkwutil/ppc_dis.py b/mkwutil/ppc_dis.py index f2038ef37..46c95f9b1 100644 --- a/mkwutil/ppc_dis.py +++ b/mkwutil/ppc_dis.py @@ -15,6 +15,7 @@ from capstone import * from capstone.ppc import * +import struct def sign_extend_16(value): @@ -98,45 +99,11 @@ def sign_extend_12(value): PPC_INS_MFASR, } -labels = set() -labelNames = {} - -def addr_to_label(addr, insn_addr): - if addr in labels: - if addr in labelNames: - return labelNames[addr] - else: - return "lbl_%08X" % addr - else: - return hex(addr - insn_addr) - - -def insn_to_text(insn, raw): +def insn_to_text_capstone(insn, raw): # Probably data, not a real instruction if insn.id == PPC_INS_BDNZ and (insn.bytes[0] & 1): return None - if insn.id in {PPC_INS_B, PPC_INS_BL, PPC_INS_BDZ, PPC_INS_BDNZ}: - return "%s %s" % ( - insn.mnemonic, - addr_to_label(insn.operands[0].imm, insn.address), - ) - elif insn.id == PPC_INS_BC: - branch_pred = "+" if (insn.bytes[1] & 0x20) else "" - if insn.operands[0].type == PPC_OP_IMM: - return "%s%s %s" % ( - insn.mnemonic, - branch_pred, - addr_to_label(insn.operands[0].imm, insn.address), - ) - elif insn.operands[1].type == PPC_OP_IMM: - return "%s%s %s, %s" % ( - insn.mnemonic, - branch_pred, - insn.reg_name(insn.operands[0].value.reg), - addr_to_label(insn.operands[1].imm, insn.address), - ) - # Sign-extend immediate values because Capstone is an idiot and doesn't do that automatically if insn.id in {PPC_INS_ADDI, PPC_INS_ADDIC, PPC_INS_SUBFIC, PPC_INS_MULLI} and ( insn.operands[2].imm & 0x8000 @@ -167,7 +134,7 @@ def insn_to_text(insn, raw): def disasm_ps(inst): - """Disassembles special ps instruction.""" + """Disassembles paired-singles instruction.""" RA = (inst >> 16) & 0x1F RB = (inst >> 11) & 0x1F FA = (inst >> 16) & 0x1F @@ -300,30 +267,11 @@ def disasm_mcrxr(inst): return "mcrxr cr%i" % crd -def disassemble_callback(filecontent, address, offset, insn, buf): - """Disassembles code.""" - - prefix_comment = "/* %08X %02X %02X %02X %02X */" % ( - address, - buf[0], - buf[1], - buf[2], - buf[3], - ) - # prefix_comment = '/* %08X */' % address +def insn_to_text(insn, raw): + """Disassembles instruction.""" asm = None - - def read_u32b(filecontent, offset): - return ( - (filecontent[offset + 0] << 24) - | (filecontent[offset + 1] << 16) - | (filecontent[offset + 2] << 8) - | filecontent[offset + 3] - ) - - raw = read_u32b(filecontent, offset) if insn is not None: - asm = insn_to_text(insn, raw) + asm = insn_to_text_capstone(insn, raw) else: # Capstone couldn't disassemble it idx = (raw & 0xFC000000) >> 26 idx2 = (raw & 0x000007FE) >> 1 @@ -346,7 +294,7 @@ def read_u32b(filecontent, offset): asm = disasm_ps_mem(raw, idx) if asm is None: asm = ".4byte 0x%08X /* unknown instruction */" % raw - print("%s\t%s" % (prefix_comment, asm)) + return asm # entryPoint = base.entry_point @@ -355,33 +303,117 @@ def read_u32b(filecontent, offset): # labelNames[entryPoint] = '__start' -def disasm_iter(filecontent, offset, address, size, callback): - """Calls callback for every instruction in the specified code section.""" +class Instruction: + """A Broadway CPU instruction and its location.""" - if size == 0: - return - start = address - end = address + size - while address < end: - code = filecontent[offset + (address - start) : offset + size] - for insn in cs.disasm(code, address): - address = insn.address - if insn.id in blacklistedInsns: - callback( - filecontent, address, offset + address - start, None, insn.bytes - ) - else: - callback( - filecontent, address, offset + address - start, insn, insn.bytes + def __init__(self, address, insn, bytes): + self.address = address + self.insn = insn + self.bytes = bytes + + def prefix_text(self): + # return '/* %08X */' % address + return "/* %08X %02X %02X %02X %02X */\t" % ( + self.address, + self.bytes[0], + self.bytes[1], + self.bytes[2], + self.bytes[3], + ) + + def disassemble(self): + return self.prefix_text() + self.disassemble_inner() + + def disassemble_inner(self): + """Disassembles code.""" + raw = struct.unpack(">I", self.bytes)[0] + asm = insn_to_text(self.insn, raw) + # Relative addresses for branch instructions. + branch_info = self.disassemble_branch() + if branch_info is not None: + branch_text, target_addr = branch_info + return f"{branch_text} {hex(target_addr-self.address)}" + return asm.strip() + + def disassemble_branch(self): + """Helper to split a branch-to-immediate instruction into text and address.""" + if self.insn is None: + return None + if self.insn.id in {PPC_INS_B, PPC_INS_BL, PPC_INS_BDZ, PPC_INS_BDNZ}: + return self.insn.mnemonic, self.insn.operands[0].imm + elif self.insn.id == PPC_INS_BC: + branch_pred = "+" if (self.insn.bytes[1] & 0x20) else "" + if self.insn.operands[0].type == PPC_OP_IMM: + return f"{self.insn.mnemonic}{branch_pred}", self.insn.operands[0].imm + elif self.insn.operands[1].type == PPC_OP_IMM: + reg_name = self.insn.reg_name(self.insn.operands[0].value.reg) + return ( + f"{self.insn.mnemonic}{branch_pred} {reg_name},", + self.insn.operands[1].imm, ) + return None + + +def label_name(addr): + return "lbl_%08x" % (addr) + + +class InlineInstruction(Instruction): + """A Broadway CPU instruction in the context of C/C++ inline assembly.""" + + def __init__(self, address, insn, bytes, labels, symbols): + super().__init__(address, insn, bytes) + self.labels = labels + self.symbols = symbols + + def reference_addr(self, addr): + """Get a nice reference to immediate address from operand (label or symbol).""" + if addr in self.labels: + return label_name(addr) + sym = self.symbols.get(addr) + if sym is not None: + return sym.name + return hex(addr) + + def disassemble_inner(self): + """Returns inline disassembly of instruction.""" + # Nice address reference for branch instructions. + branch_info = self.disassemble_branch() + if branch_info is not None: + branch_text, addr = branch_info + return f"{branch_text} {self.reference_addr(addr)}" + # Fall back to generic diassembly. + return super().disassemble_inner() + + def prefix_text(self): + """Override to have no prefix.""" + return "" + + +def disasm_iter(data, address): + """Returns an iterator over every instruction in the specified code section.""" + assert address % 4 == 0, "Code is not aligned" + assert len(data) % 4 == 0, "Odd code length" + # Repeatedly invoke capstone. + i = 0 + while i < len(data): + # Invoke capstone. + for insn in cs.disasm(data[i:], address): + address = insn.address + yield Instruction( + address, + insn if insn.id not in blacklistedInsns else None, + insn.bytes, + ) + i += 4 address += 4 - if address < end: - o = offset + address - start - callback( - filecontent, + if i < len(data): + # Capstone aborts at unknown instructions. + # Emit custom instruction. + yield Instruction( address, - offset + address - start, None, - filecontent[o : o + 4], + data[i : i + 4], ) address += 4 + i += 4 diff --git a/mkwutil/rel.py b/mkwutil/rel.py index 5b205d59d..a93d769e3 100644 --- a/mkwutil/rel.py +++ b/mkwutil/rel.py @@ -8,6 +8,7 @@ from os import PathLike from pathlib import Path import struct +from typing import Optional REL_BASE = 0x805102E0 @@ -149,6 +150,15 @@ def load_section(self, index, file): s.length = len(s.data) self.section_info[index] = s + def virtual_read(self, vaddr: int, size: int, sections, section_idx) -> Optional[bytes]: + # Find the virtual address section where vaddr falls into. + section_virtual_idx, section_virtual = next((seg for seg in enumerate(sections) if vaddr in seg[1]), None) + # Map to REL section number. + section_idx = section_idx[section_virtual_idx] + # Calculate addres. + relative_addr = vaddr - section_virtual.start + return self.section_info[section_idx].data[relative_addr:relative_addr+size] + class RelHeader: """Holds a .rel header.""" @@ -331,9 +341,7 @@ def reconstruct(self): return entry.getvalue() -def dump_staticr(rel, path): - _path = Path(path) - +def dump_staticr(rel: Rel, _path: Path) -> None: rel.dump_reloc(0, _path / "dol_rel.bin") rel.dump_reloc(1, _path / "rel_abs.bin") diff --git a/mkwutil/sections.py b/mkwutil/sections.py new file mode 100644 index 000000000..05a6f9e42 --- /dev/null +++ b/mkwutil/sections.py @@ -0,0 +1,62 @@ +from dataclasses import dataclass + +from mkwutil.slices import Slice + + +@dataclass +class Section: + name: str + type: str + start: int + stop: int + + def __post_init__(self): + assert self.start <= self.stop + + def size(self) -> int: + return self.stop - self.start + + def __len__(self) -> int: + return self.size() + + def __contains__(self, key) -> bool: + return isinstance(key, int) and self.start <= key < self.stop + + +DOL_SECTIONS = [ + Section("init", "code", 0x80004000, 0x80006460), + Section("extab", "data", 0x80006460, 0x80006A20), + Section("extabindex", "data", 0x80006A20, 0x800072C0), + Section("text", "code", 0x800072C0, 0x80244DE0), + Section("ctors", "data", 0x80244DE0, 0x80244E90), + Section("dtors", "data", 0x80244EA4, 0x80244EAC), + Section("rodata", "data", 0x80244EC0, 0x80258580), + Section("data", "data", 0x80258580, 0x802A4040), + Section("bss", "bss", 0x802A4080, 0x80384C00), + Section("sdata", "data", 0x80384C00, 0x80385FC0), + Section("sbss", "bss", 0x80385FC0, 0x80386FA0), + Section("sdata2", "data", 0x80386FA0, 0x80389140), + Section("sbss2", "bss", 0x80389140, 0x8038917C), +] + +DOL_LIBS = [ + Slice(name="DOL/NW4R", start=0x80021BB0, stop=0x800BBB80, section="text"), + Slice(name="DOL/RFL", start=0x800BBB80, stop=0x800CC7E4, section="text"), + Slice(name="DOL/DWC", start=0x800CC7E4, stop=0x800EF378, section="text"), + Slice(name="DOL/SPY", start=0x800EF378, stop=0x80123F88, section="text"), + Slice(name="DOL/RVL", start=0x80123F88, stop=0x8020F62C, section="text"), + Slice(name="DOL/EGG", start=0x8020F62C, stop=0x80244DD4, section="text"), +] + +# Virtual (loaded) REL sections. +REL_SECTIONS = [ + Section("text", "code", 0x805103B4, 0x8088F400), + Section("ctors", "data", 0x8088F400, 0x8088F704), + Section("dtors", "data", 0x8088F704, 0x8088F710), + Section("rodata", "data", 0x8088F710, 0x808B2BD0), + Section("data", "data", 0x808B2BD0, 0x808DD3D4), + Section("bss", "bss", 0x809BD6E0, 0x809C4F90), +] + +# Maps virtual REL section indexes to REL sections in file. +REL_SECTION_IDX = [1, 2, 3, 4, 5, 6] diff --git a/mkwutil/slices.py b/mkwutil/slices.py new file mode 100644 index 000000000..cb86b7102 --- /dev/null +++ b/mkwutil/slices.py @@ -0,0 +1,411 @@ +from bisect import bisect, bisect_left +from copy import copy +import csv +from dataclasses import dataclass, field +import math +from pathlib import Path +from typing import Callable, Generator, Optional + + +@dataclass +class Slice: + """A continuous memory region.""" + + start: int + stop: int + name: str = None + section: "Section" = None + tags: set[str] = field(default_factory=set) + + def __post_init__(self): + assert self.start <= self.stop + + def __contains__(self, key) -> bool: + if isinstance(key, int): + return self.start <= key < self.stop + if isinstance(key, type(self)): + return self.start <= key.start and self.stop >= key.stop + return False + + def __len__(self) -> int: + assert self.start <= self.stop, "Slice has negative length" + return self.stop - self.start + + def __eq__(self, other: "Slice") -> bool: + """Checks whether two slices occupy the same region.""" + if not isinstance(other, type(self)): + return False + return self.start == other.start and self.stop == other.stop + + def __gt__(self, other: "Slice") -> bool: + """Checks whether this slice starts after another slice.""" + if not isinstance(other, type(self)): + return False + return self.start > other.start + + def __repr__(self) -> str: + repr = "{ %08x..%08x" % (self.start, self.stop) + if self.name is not None: + repr += " " + self.name + if self.section is not None: + repr += " (" + self.section + ")" + repr += " }" + return repr + + def has_name(self) -> bool: + """Returns whether the slice is named. Unnamed slices get handled""" + return self.name is not None + + def __copy__(self) -> "Slice": + """Returns a copy of the slice.""" + return type(self)(self.start, self.stop, self.name, self.section, self.tags) + + +@dataclass +class ObjectSlices: + """ObjectSlices is an immutable view of slices grouped by slice name.""" + + objects: dict=field(default_factory=dict) + + def get(self, name: str) -> list[Slice]: + assert isinstance(name, str) + return self.objects.get(name) + + def __len__(self) -> int: + return self.objects.__len__() + + +class SliceTable: + """A list of contiguous slices for a given range.""" + + def __init__( + self, start=0x8000_0000, stop=math.inf, sections: Optional[list] = None + ): + if sections is not None: + start = sections[0].start + stop = sections[-1].stop + assert start < stop, "Non-positive slice table size" + self.slices = [Slice(start, stop)] + self.start = start + self.stop = stop + + def load_path(file_path, sections=None): + """Loads slices given a path to a CSV file.""" + if sections is not None: + this = SliceTable(start=sections[0].start, stop=sections[-1].stop) + else: + this = SliceTable() + with open(file_path, "r") as file: + this.read_from(file) + if sections is not None: + this.set_sections(sections) + return this + + @staticmethod + def load_dol_slices(sections=None) -> "SliceTable": + """Loads pack/dol_slices.csv in the default DOL region.""" + return SliceTable.load_path( + Path(__file__).parent / ".." / "pack" / "dol_slices.csv", sections=sections + ) + + @staticmethod + def load_rel_slices(sections=None) -> "SliceTable": + """Loads pack/rel_slices.csv in the default DOL region.""" + return SliceTable.load_path( + Path(__file__).parent / ".." / "pack" / "rel_slices.csv", sections=sections + ) + + def __contains__(self, _slice: Slice) -> bool: + """Returns whether the range of a slice lies within the table. + The table is not actually checked for membership.""" + return self.start <= _slice.start and self.stop >= _slice.stop + + def __iter__(self): + return self.slices.__iter__() + + def __len__(self) -> int: + return self.size() + + def size(self) -> int: + return self.stop - self.start + + def count(self) -> int: + return len(self.slices) + + def read_from(self, file) -> None: + for slice in SlicesCSVReader(file): + self.add(slice) + + def find(self, addr: int) -> tuple[Optional[Slice], Optional[int]]: + """Returns the slice the address falls into.""" + for i, slice in enumerate(self.slices): + if addr in slice: + return slice, i + return None, None + + def slice(self, start: int, stop: int) -> "SliceTable": + """Returns a copy of the slice table for a given range.""" + _, start_idx = self.find(start) + assert start_idx is not None, f"Start {hex(start)} lies outside table." + new_table = SliceTable(start, stop) + for _slice in self.slices[start_idx:]: + if _slice.start < start: + _slice = copy(_slice) + _slice.start = start + if _slice.start >= stop: + break + if _slice.stop > stop: + _slice = copy(_slice) + _slice.stop = stop + new_table.add(_slice) + break + new_table.add(_slice) + return new_table + + # def write_to(self, file): + # writer = SlicesCSVWriter(file) + # for slice in self.slices: + # if slice.name is not None: + # writer.write(slice) + + def find_parent(self, _slice: Slice) -> Optional[Slice]: + """Searches for a slice in the table containing the given slice.""" + i = bisect_left(self.slices, _slice) + if i < len(self.slices): + return self.slices[i] + return None + + def sum_named_slices(self) -> int: + """Returns the sum of the lengths of all named/known slices.""" + t = 0 + for s in self.segs: + if s.name is not None: + t += len(s) + return t + + def filter(self, filter_func: Callable[[Slice], bool]) -> "SliceTable": + """Returns a new slice table with only slices that passed the filter function.""" + # A more "pythonic" approach to this would be SliceTable([for x in table if x ...]) + filtered = SliceTable(self.start, self.stop) + for slice in self.slices: + if slice.name is not None and filter_func(slice): + filtered.add(slice) + return filtered + + # Filter function for SliceTable.filter + ONLY_ENABLED = lambda slice: "enabled" in slice.tags + + def add(self, _slice: Slice) -> None: + """Adds a slice to the table, changing gaps as appropriate. + Panics if a named slice overlaps with the slice to be inserted""" + assert isinstance(_slice, Slice) + assert(len(_slice) > 0), str(_slice) + assert _slice in self, "Slice %08x..%08x does not fit in table %08x..%08x" % ( + _slice.start, + _slice.stop, + self.start, + self.stop, + ) + # Find the slice in which the starting point falls. + i = bisect(self.slices, _slice) - 1 + target = self.slices[i] + # If the new slice does not fit in the target slice, + # the new slice overlaps at least two slices. + # Because of the invariant that no gaps can share a border, + # this means the new slice overlaps at least one named slice. + # TODO This is not true anymore, update logic to overwrite multiple slices. + assert ( + target.name is None and _slice in target + ), f"Overlapping slices:\n new={_slice}\nexisting={target}" + # Insert left gap. + if _slice.start > target.start: + self.slices.insert(i, Slice(target.start, _slice.start)) + i += 1 + # Insert new slice. + self.slices[i] = _slice + # Insert right gap. + if _slice.stop < target.stop: + self.slices.insert(i + 1, Slice(_slice.stop, target.stop)) + + def __repr__(self) -> str: + return ( + "[\n" + "\n".join([" " + repr(_slice) for _slice in self.slices]) + "\n]" + ) + + SECTION_ORDER = [ + "init", + "extab", + "extabindex", + "text", + "ctors", + "dtors", + "rodata", + "data", + "bss", + "sdata", + "sbss", + "sdata2", + "sbss2", + ] + + def object_slices(self, order: list[str] = SECTION_ORDER) -> ObjectSlices: + """Returns a dict of objects keyed by object name. + An object is a list of slices in different sections with the same name.""" + # Create sort buckets for each section. + # TODO A deque might be more appropriate. + buckets = [[] for _ in range(0, len(order))] + # Remember the sections of each object. + object_sections = dict() + # Transform slices list to buckets / objects. + for _slice in self.slices: + if not _slice.has_name(): + continue + buckets[order.index(_slice.section)].append(_slice) + sections = object_sections.get(_slice.name, set()) + sections.add(_slice.section) + object_sections[_slice.name] = sections + # Sort each bucket. + for bucket in buckets: + bucket.sort(key=lambda _slice: _slice.start) + # Merge buckets. + objects = ObjectSlices() + while sum(len(bucket) for bucket in buckets) > 0: + # Select next object name. + resolved_row = False + for i, bucket in enumerate(buckets): + if len(bucket) == 0: + continue + name = bucket[0].name + slices = [bucket[0]] + # Discover dependencies of slice. + deps_match = True + for j, dep_bucket in enumerate(buckets): + if i == j: + continue + if order[j] in object_sections[name]: + # If the dependency section doesn't contain our object, + # try another section first. + if dep_bucket[0].name != name: + deps_match = False + break + slices.append(dep_bucket[0]) + # All dependencies match with lowest addresses. + if deps_match: + j = 0 + for _slice in slices: + while ( + len(buckets[j]) == 0 + or buckets[j][0].section != _slice.section + ): + j += 1 + buckets[j].pop(0) + resolved_row = True + objects.objects[name] = slices + break + # If no bucket could be resolved, it's impossible to sort + assert resolved_row, "Merging failed at slices:\n" + "\n".join( + ( + f"{hex(bucket[0].start)} {order[i]} = {bucket[0]}" + for i, bucket in enumerate(buckets) + if len(bucket) > 0 + ) + ) + return objects + + def set_sections(self, sections: list) -> None: + """Sets the slices' sections based on the given list of sections. + The sections must be sorted.""" + i = 0 + for slice_idx, _slice in enumerate(self.slices): + assert i < len(sections) + # Move on to next section if slice begins outside bounds of last slice. + while sections[i].stop <= _slice.start: + i += 1 + assert i < len(sections) + # Split slice if it beings outside bounds of current slice. + # i.e. There's a gap between last and current slice. + if sections[i].start > _slice.start: + gap_slice = copy(_slice) + if sections[i].start == _slice.stop: + continue + _slice.start = sections[i].start + gap_slice.stop = _slice.start + gap_slice.name = None + gap_slice.section = None + assert len(gap_slice) > 0 + self.slices.insert(slice_idx, gap_slice) + slice_idx += 1 + # Split slice if slice ends outside bounds. + if _slice.stop > sections[i].stop: + assert ( + not _slice.has_name() + ), "Refusing to split named slice across sections" + assert i + 1 < len(sections), "Slice ends outside section table" + # Shrink left slice. + old_stop = _slice.stop + _slice.stop = sections[i].stop + assert len(_slice) > 0 + # Create right slice. + # This slice will be processed in the next iteration. + right_slice = copy(_slice) + right_slice.start = sections[i].stop + right_slice.stop = old_stop + assert len(right_slice) > 0 + self.slices.insert(slice_idx + 1, right_slice) + _slice.section = sections[i].name + + +class SlicesCSVReader: + """Reads a list of slices from slices.csv.""" + + def __init__(self, file): + self.reader = csv.reader(file) + # Read CSV header. + header = next(self.reader) + self.cols = len(header) + # The name field separates tags and ranges. + name_idx = header.index("name") + self.tag_idx = header[:name_idx] + section_fields = header[name_idx + 1 :] + assert ( + len(section_fields) > 0 and len(section_fields) % 2 == 0 + ), "Odd number of fields" + # Remember section names. + self.sections = [] + for i in range(0, len(section_fields), 2): + assert section_fields[i].endswith("Start") + assert section_fields[i + 1].endswith("End") + section = section_fields[i].removesuffix("Start") + assert section == section_fields[i + 1].removesuffix("End") + self.sections.append(section) + + def __iter__(self) -> Generator[Slice, None, None]: + """Returns all slices in the file.""" + for row in self.reader: + for slice in self.parse_row(row): + yield slice + + def parse_row(self, row: str) -> Generator[Slice, None, None]: + """Returns all slices in a row.""" + if len(row) == 0: + return + assert len(row) == self.cols, "Unexpected number of fields" + # Read flags. + flags = set() + for i, tag in enumerate(self.tag_idx): + if row[i] == "1": + flags.add(tag) + # Read name. + name = row[len(self.tag_idx)] + # Read section ranges. + ranges = row[len(self.tag_idx) + 1 :] + for i, section in enumerate(self.sections): + start_str, stop_str = ranges[2 * i : 2 * i + 2] + start_str = start_str.strip() + stop_str = stop_str.strip() + if start_str == "": + continue + assert stop_str != "" + start = int(start_str.strip(), 16) + stop = int(stop_str, 16) + yield Slice(start, stop, name, section, flags) diff --git a/mkwutil/slices_test.py b/mkwutil/slices_test.py new file mode 100644 index 000000000..b95ef7544 --- /dev/null +++ b/mkwutil/slices_test.py @@ -0,0 +1,54 @@ +from pathlib import Path +import pytest + +from mkwutil.slices import ObjectSlices, Slice, SliceTable + + +def test_slice_compare(): + assert Slice(0, 2) == Slice(0, 2) + assert not (Slice(0, 2) > Slice(0, 16)) + + +def test_slice_table(): + table = SliceTable(0, 16) + # Right gap insert. + table.add(Slice(0, 2, name="Sec1", section="text")) + # Make sure adding overlapping slices fails. + with pytest.raises(AssertionError): + table.add(Slice(1, 3, name="Sec2", section="text")) + # Light and right gap insert. + table.add(Slice(4, 6, name="Sec3", section="text")) + # Left gap insert. + table.add(Slice(14, 16, name="Sec4", section="text")) + assert list(table) == [ + Slice(0, 2), + Slice(2, 4), + Slice(4, 6), + Slice(6, 14), + Slice(14, 16), + ] + + +def test_dol_slices(): + table = SliceTable.load_dol_slices() + assert isinstance(table, SliceTable) + objs = table.object_slices() + assert isinstance(objs, ObjectSlices) + assert len(objs) > 10 + + +def test_rel_slices(): + table = SliceTable.load_rel_slices() + assert isinstance(table, SliceTable) + objs = table.object_slices() + assert isinstance(objs, ObjectSlices) + + +def test_object_slices_sort_names(): + objects = ObjectSlices() + objects.insert(Slice(name="s0", start=0x01, stop=0x02, section="text")) + objects.insert(Slice(name="s2", start=0x06, stop=0x07, section="data")) + objects.insert(Slice(name="s3", start=0x03, stop=0x04, section="text")) + objects.insert(Slice(name="s1", start=0x02, stop=0x03, section="text")) + objects.insert(Slice(name="s1", start=0x05, stop=0x06, section="data")) + assert objects.sorted_names(order=["text", "data"]) == ["s0", "s1", "s2", "s3"] diff --git a/mkwutil/sources.py b/mkwutil/sources.py new file mode 100644 index 000000000..066b20468 --- /dev/null +++ b/mkwutil/sources.py @@ -0,0 +1,174 @@ +""" +Lists the source code files that make up Mario Kart Wii. +""" + + +from dataclasses import dataclass +from itertools import chain + + +RVL_OPTS = '-ipa file' +EGG_OPTS = '-ipa function -rostr' +REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' +NW4R_OPTS = '-ipa file -inline auto -rostr -O4,p' + + +@dataclass +class Source: + src: str + cc: str + opts: str + + +# +# main.dol +# + + +SOURCES_TRK = [ + Source(src="source/rvl/trk/rvlTrkMem.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_ARC = [ + Source(src="source/rvl/arc/rvlArchive.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_MEM = [ + Source(src="source/rvl/mem/rvlMemHeap.cpp", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mem/rvlMemExpHeap.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mem/rvlMemFrmHeap.cpp", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mem/rvlMemUnitHeap.cpp", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mem/rvlMemAllocator.cpp", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mem/rvlMemList.cpp", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_MTX = [ + Source(src="source/rvl/mtx/rvlMtx.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mtx/rvlMtx2.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mtx/rvlVec.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/mtx/rvlQuat.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_PAD = [ + Source(src="source/rvl/pad/rvlPadClamp.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/pad/rvlPad.c", cc='4199_60831', opts=RVL_OPTS + " -inline on,noauto "), +] +SOURCES_RVL_SI = [ + Source(src="source/rvl/si/siBios.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_TPL = [ + Source(src="source/rvl/tpl/tpl.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_SO = [ + Source(src="source/rvl/so/soCommon.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/so/soBasic.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_DWC = [ + Source(src="source/dwc/common/dwc_error.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_SPY = [ + Source(src="source/gamespy/darray.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/hashtable.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/md5c.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/common/revolution/gsSocketRevolution.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/common/gsAvailable.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/common/revolution/gsUtilRevolution.c", cc='4199_60831', opts=RVL_OPTS), + # Source(src="source/gamespy/common/gsPlatformUtil.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/common/gsCore.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/common/gsUdpEngine.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/common/gsXML.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gp.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpi.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiBuddy.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiBuffer.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiCallback.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiConnect.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiInfo.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiKeys.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiOperation.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiPeer.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiProfile.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiSearch.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiTransfer.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiUnique.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gp/gpiUtility.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gt2/gt2Auth.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gt2/gt2Buffer.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gt2/gt2Callback.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gt2/gt2Connection.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gt2/gt2Main.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gt2/gt2Socket.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gt2/gt2Utility.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/qr2/qr2.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/qr2/qr2RegKeys.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpBuffer.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpCallbacks.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpCommon.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpConnection.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpEncryption.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpMain.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpPost.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/ghttp/ghttpProcess.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gstats/gbucket.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/gstats/gstats.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_crypt.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_queryengine.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_server.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_serverlist.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_serverbrowsing.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/sake/sakeMain.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_NW4R_MATH = [ + Source(src="source/nw4r/math/mathTriangular.cpp", cc='4201_127', opts=NW4R_OPTS), + Source(src="source/nw4r/math/mathTypes.cpp", cc='4201_127', opts=NW4R_OPTS), +] +SOURCES_NW4R_G3D = [ + Source(src="source/nw4r/g3d/g3d_camera.cpp", cc='4201_127', opts=NW4R_OPTS), + Source(src="source/nw4r/g3d/g3d_fog.cpp", cc='4201_127', opts=NW4R_OPTS), +] +SOURCES_NW4R_UT = [ + Source(src="source/nw4r/ut/utList.cpp", cc='4201_127', opts=NW4R_OPTS), +] +SOURCES_EGG_CORE = [ + Source(src="source/egg/core/eggAllocator.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), + Source(src="source/egg/core/eggArchive.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), + Source(src="source/egg/core/eggDisposer.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggExpHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), + Source(src="source/egg/core/eggGraphicsFifo.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -ipa file -use_lmw_stmw=on "), + Source(src="source/egg/core/eggStreamDecomp.cpp", cc='4201_127', opts=EGG_OPTS), + # Source(src="source/egg/core/eggSystem.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggThread.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggUnitHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), + Source(src="source/egg/core/eggVideo.cpp", cc='4201_127', opts=EGG_OPTS+ " -use_lmw_stmw=on "), + # Source(src="source/egg/core/eggXfb.cpp", cc='4201_127', opts=EGG_OPTS), + # Source(src="source/egg/core/eggXfbManager.cpp", cc='4201_127', opts=EGG_OPTS), +] +SOURCES_EGG_MATH = [ + Source(src="source/egg/math/eggQuat.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/math/eggVector.cpp", cc='4201_127', opts=EGG_OPTS), +] +SOURCES_DOL = list(chain( + SOURCES_TRK, + SOURCES_RVL_ARC, + SOURCES_RVL_MEM, + SOURCES_RVL_MTX, + SOURCES_RVL_PAD, + SOURCES_RVL_SI, + SOURCES_RVL_TPL, + SOURCES_RVL_SO, + SOURCES_RVL_DWC, + SOURCES_SPY, + SOURCES_NW4R_MATH, + SOURCES_NW4R_G3D, + SOURCES_NW4R_UT, + SOURCES_EGG_CORE, + SOURCES_EGG_MATH, +)) + +# +# StaticR.rel +# + +SOURCES_REL = [ + Source(src="source/game/ui/MessageGroup.cpp", cc='4201_127', opts=REL_OPTS), + Source(src="source/game/ui/ControlGroup.cpp", cc='4201_127', opts=REL_OPTS), + Source(src="source/game/ui/UIControl.cpp", cc='4201_127', opts=REL_OPTS), + Source(src="source/game/jmap/JmpResourceCourse.cpp", cc='4201_127', opts=REL_OPTS), +] diff --git a/mkwutil/symbols.py b/mkwutil/symbols.py index fb82bce0e..2e4fbd9ef 100644 --- a/mkwutil/symbols.py +++ b/mkwutil/symbols.py @@ -1,18 +1,28 @@ """This module implements symbols file handling.""" +from bisect import bisect_left import csv class Symbol: """A symbol is an address with a name.""" - def __init__(self, addr, name): + def __init__(self, addr, name, size=None): self.addr = addr self.name = name + self.size = size def __repr__(self): return "%s=0x%08x" % (self.name, self.addr) + def slice(self): + """Returns a slice object for address range occupied by the symbol.""" + assert size is not None + return slice(self.addr, self.addr + self.size) + + def __copy__(self): + return type(self)(self.addr, self.name, self.size) + class SymbolsList: """Simple list of symbols""" @@ -27,6 +37,9 @@ def __len__(self): def __getitem__(self, addr): return self._by_addr[addr] + def __contains__(self, key): + return (key in self._by_addr) or (key in self._by_name) + def get(self, addr): """Looks up a symbol by address.""" return self._by_addr.get(addr) @@ -37,9 +50,26 @@ def get_by_name(self, name): def __iter__(self): """Returns an iterator of all symbols sorted by address.""" - for addr in sorted(self._by_addr): + return self.items() + + def items(self, start=0, stop=-1): + """Returns an iterator of all symbols sorted by address with bounds by address.""" + sorted_by_addr = sorted(self._by_addr) + if start > 0: + # Binary search to determine first symbol with address >= start. + sorted_by_addr = sorted_by_addr[bisect_left(sorted_by_addr, start) :] + for addr in sorted_by_addr: + if stop > 0 and stop < addr: + break yield self._by_addr[addr] + def slice(self, start=0, stop=-1): + """Returns a symbol list with bounds by address.""" + new_list = SymbolsList() + for sym in self.items(start, stop): + new_list.put(sym) + return new_list + def put(self, entry): """Inserts a symbol into the list.""" assert isinstance(entry, Symbol) @@ -52,7 +82,21 @@ def __setitem__(self, addr, entry): assert addr == entry.addr self.put(entry) - def read(self, file): + def __delitem__(self, key): + if isinstance(key, int): + sym = self._by_addr.pop(key) + elif isinstance(key, Symbol): + sym = self._by_addr.pop(key.addr) + elif isinstance(key, str): + sym = self._by_name.pop(key) + self._by_addr.pop(sym.addr) + return + else: + assert False, "invalid key" + assert sym is not None + del self._by_name[sym.name] + + def read_from(self, file): """Reads a symbol list from a file.""" reader = csv.reader(file, delimiter=" ") for row in reader: @@ -65,8 +109,18 @@ def read(self, file): assert name != "" self[addr] = name - def write(self, file): + def write_to(self, file): """Writes a symbol list to a file.""" writer = csv.writer(file, delimiter=" ") for sym in self: writer.writerow(["0x%08x" % (sym.addr), sym.name]) + + def derive_sizes(self, stop): + """Fills in sizes of each symbol.""" + addrs = list(self._by_addr.keys()) + for i, addr in enumerate(addrs): + if i >= len(addrs) - 1: + sym_stop = stop + else: + sym_stop = self._by_addr[addrs[i + 1]].addr + self._by_addr[addr].size = sym_stop - self._by_addr[addr].addr diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py index acbe7a0bd..7ab2a5730 100644 --- a/mkwutil/verify_main_dol.py +++ b/mkwutil/verify_main_dol.py @@ -11,25 +11,33 @@ def format_segment(name, at, at2, want_size, have_size, tag): - return "%10s: at_src=0x%08x at_dst=0x%08x at_end=0x%08x want=0x%08x have=0x%08x [%s]" % ( - name, - at, - at2, - at + want_size, - want_size, - have_size, - tag, + return ( + "%10s: at_src=0x%08x at_dst=0x%08x at_end=0x%08x want=0x%08x have=0x%08x [%s]" + % ( + name, + at, + at2, + at + want_size, + want_size, + have_size, + tag, + ) ) -def verify_dol(reference, target): +def verify_dol(reference: Path, target: Path): """Verifies the target main.dol for authenticity.""" print("[DOL] Verifying...") content = open(target, "rb").read() ctx = hashlib.sha1(content) digest = ctx.hexdigest() if digest.lower() == "ac7d72448630ade7655fc8bc5fd7a6543cb53a49": - print(Fore.GREEN + Style.BRIGHT + "[DOL] Everything went okay! Output is matching! ^^" + Style.RESET_ALL) + print( + Fore.GREEN + + Style.BRIGHT + + "[DOL] Everything went okay! Output is matching! ^^" + + Style.RESET_ALL + ) return want_len = 2766496 @@ -39,72 +47,45 @@ def verify_dol(reference, target): % (len(content), len(content) - want_len) ) - good = DolBinary(reference) - bad = DolBinary(target) - - text_names = ["init", "text"] - data_names = [ - "extab", - "extabindex", - "ctors", - "dtors", - "rodata", - "data", - "sdata", - "sdata2", - ] + with open(reference, "rb") as file: + good = DolBinary(file) + with open(target, "rb") as file: + bad = DolBinary(file) - for i, sizes in enumerate(zip(good.text_size, bad.text_size)): - if sizes[0] == 0 and sizes[1] == 0: - continue - good_segment = good.get_text_segment(i) - bad_segment = bad.get_text_segment(i) - match = good_segment == bad_segment - tag = "OK" if match else "FAIL" - if len(good_segment) != len(bad_segment): - tag = "SIZE" - print( - format_segment( - text_names[i], - good.text_segs[i].begin, - bad.text_segs[i].begin, - sizes[0], - sizes[1], - tag, - ) - ) - for i, sizes in enumerate(zip(good.data_size, bad.data_size)): - if sizes[0] == 0 and sizes[1] == 0: + for i in range(0, DolBinary.SEGMENT_COUNT): + good_segment = good.segments[i] + bad_segment = bad.segments[i] + if len(good_segment) == 0: continue - good_segment = good.get_data_segment(i) - bad_segment = bad.get_data_segment(i) - match = good_segment == bad_segment + match = good_segment.data == bad_segment.data tag = "OK" if match else "FAIL" - if len(good_segment) != len(bad_segment): + if good_segment is not None and len(good_segment) != len(bad_segment): tag = "SIZE" print( format_segment( - data_names[i], - good.data_segs[i].begin, - bad.data_segs[i].begin, - sizes[0], - sizes[1], + good_segment.name(), + good_segment.start, + bad_segment.start, + len(good_segment), + len(bad_segment), tag, ) ) print( format_segment( "bss", - good.bss.begin, - bad.bss.begin, - good.bss.size(), - bad.bss.size(), - "OK" if good.bss.size() == bad.bss.size() else "SIZE", + good.bss.start, + bad.bss.stop, + len(good.bss), + len(bad.bss), + "OK" if len(good.bss) == len(bad.bss) else "SIZE", ) ) # TODO: Add diff'ing - print(Fore.RED + Style.BRIGHT + "[DOL] Oof: Output doesn't match." + Style.RESET_ALL) + print( + Fore.RED + Style.BRIGHT + "[DOL] Oof: Output doesn't match." + Style.RESET_ALL + ) if __name__ == "__main__": diff --git a/mkwutil/verify_object_file.py b/mkwutil/verify_object_file.py index 194001e3f..523266fe8 100644 --- a/mkwutil/verify_object_file.py +++ b/mkwutil/verify_object_file.py @@ -2,20 +2,30 @@ from termcolor import colored -def verify_object_file(dst, src, obj_slice): + +def verify_object_file(dst: str, src: str, obj_slices: list): + """Verifies the section sizes of an object.""" match = True - with open(dst, 'rb') as f: + with open(dst, "rb") as f: elf_file = ELFFile(f) for section in elf_file.iter_sections(): section_name = section.name.removeprefix(".") - part = obj_slice.segments.get(section_name) - if not part: + # Find slice that matches section name. + _slice = next( + (_slice for _slice in obj_slices if _slice.section == section_name), + None, + ) + if not _slice: continue - want_size = part.size() + want_size = len(_slice) have_size = section.data_size if want_size != have_size: match = False - warn ="[!] %s %s want=0x%x got=0x%x" % (src, section_name, want_size, have_size) - - print(colored(warn, 'red')) + warn = "[!] Size mismatch: %s %s want=0x%x got=0x%x" % ( + src, + section_name, + want_size, + have_size, + ) + print(colored(warn, "red")) return match diff --git a/mkwutil/verify_staticr_rel.py b/mkwutil/verify_staticr_rel.py index e979ce54d..51ac80297 100644 --- a/mkwutil/verify_staticr_rel.py +++ b/mkwutil/verify_staticr_rel.py @@ -7,20 +7,32 @@ import hashlib from pathlib import Path + def verify_rel(target): """Verifies the target StaticR.rel for authenticity.""" content = open(target, "rb").read() ctx = hashlib.sha1(content) digest = ctx.hexdigest() if digest.lower() == "887bcc076781f5b005cc317a6e3cc8fd5f911300": - print(Fore.GREEN + Style.BRIGHT + "[REL] Everything went okay! Output is matching! ^^" + Style.RESET_ALL) + print( + Fore.GREEN + + Style.BRIGHT + + "[REL] Everything went okay! Output is matching! ^^" + + Style.RESET_ALL + ) return want_len = 4903876 if len(content) != want_len: - print(Fore.RED + f"Mismatched file size: Got {len(content)} ({len(content)-want_len})" + Style.RESET_ALL) + print( + Fore.RED + + f"Mismatched file size: Got {len(content)} ({len(content)-want_len})" + + Style.RESET_ALL + ) - print(Fore.RED + Style.BRIGHT + "[REL] Oof: Output doesn't match." + Style.RESET_ALL) + print( + Fore.RED + Style.BRIGHT + "[REL] Oof: Output doesn't match." + Style.RESET_ALL + ) if __name__ == "__main__": diff --git a/pack/dol.lcf.j2 b/pack/dol.lcf.j2 index b5dcffbd0..8d513beec 100644 --- a/pack/dol.lcf.j2 +++ b/pack/dol.lcf.j2 @@ -28,7 +28,7 @@ __ArenaLo = (_db_stack_addr + 0x1f) & ~0x1f; __ArenaHi = 0x81700000; {% for sym in symbols -%} -{{ sym.name }} = {{ "0x08%x" | format(sym.addr) }}; +{{ sym.name }} = {{ "0x%08x" | format(sym.addr) }}; {% endfor %} } @@ -463,8 +463,6 @@ SBQueryEngineSetPublicIP SBEngineHaltUpdates SBEngineCleanup SBQueryEngineUpdateServer -ParseSingleQR2Reply -ProcessIncomingReplies SBQueryEngineThink SBQueryEngineAddQueryKey SBQueryEngineRemoveServerFromFIFOs diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 9d4a9ec1b..19cdbccc8 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -1,46 +1,54 @@ -out\dol\init_80004000_80005f34.o +out\trkHeader.o +out\dol\init_80004100_80005f34.o out\rvlTrkMem.o +out\dol\init_80006068_80006460.o +out\dol\extab_80006460_80006a20.o +out\dol\extabindex_80006a20_800072c0.o out\dol\text_800072c0_8006a0c0.o +out\dol\ctors_80244de0_80244e88.o +out\dol\dtors_80244ea4_80244eac.o +out\dol\rodata_80244ec0_80248010.o +out\dol\data_80258580_80274148.o out\dol\bss_802a4080_802bd4ec.o +out\dol\bss_802bd4ec_802f2338.o +out\dol\sdata_80384c00_803850a0.o +out\dol\sbss_80385fc0_803862a8.o out\dol\sdata2_80386fa0_80387cac.o out\g3d_camera.o out\dol\text_8006a518_800774d0.o out\dol\sdata2_80387cd8_80387d58.o out\g3d_fog.o out\dol\text_800775d0_80085110.o -out\dol\rodata_80244ec0_80248010.o -out\dol\data_80258580_80274148.o out\dol\sdata2_80387d5c_80387e80.o out\mathTriangular.o out\dol\text_80085578_80085600.o +out\dol\rodata_80249020_8024c6b8.o +out\dol\data_80274250_80275700.o out\dol\sdata2_80387ea4_80387ea8.o out\mathTypes.o out\dol\text_80085938_800aef60.o out\utList.o out\dol\text_800af1a0_800ccb4c.o -out\dol\data_80274250_80275700.o -out\dol\sbss_80385fc0_803862a8.o out\dwc_error.o out\dol\text_800ccc80_800ef378.o out\darray.o out\hashtable.o -out\dol\rodata_80249020_8024c6b8.o out\dol\data_80275758_8027aca0.o out\md5c.o +out\dol\rodata_8024c6c9_8024c6d0.o +out\dol\data_8027ace0_8027ad58.o out\dol\sbss_803862b0_80386350.o out\gsSocketRevolution.o out\dol\text_800f164c_800f1f58.o out\gsUtilRevolution.o out\dol\text_800f2048_800f38a4.o -out\dol\data_8027ace0_8027ad58.o -out\dol\bss_802bd4ec_802f2338.o out\gsAvailable.o out\dol\text_800f3a20_800f489c.o +out\dol\data_8027ad79_8027ad80.o out\dol\bss_802f2410_802f2440.o out\gsUdpEngine.o -out\dol\data_8027ad79_8027ad80.o -out\dol\sdata_80384c00_803850a0.o out\gsXML.o +out\dol\bss_802f247c_802f3480.o out\dol\sdata_80385105_80385108.o out\gp.o out\dol\data_8027b289_8027b290.o @@ -57,6 +65,7 @@ out\dol\sdata_80385206_80385208.o out\gpiConnect.o out\dol\data_8027b876_8027b878.o out\dol\sdata_8038529b_803852a0.o +out\dol\sbss_8038635c_80386360.o out\dol\sdata2_80387eb4_80388470.o out\gpiInfo.o out\dol\data_8027bbce_8027bbd0.o @@ -89,17 +98,16 @@ out\gt2Connection.o out\gt2Main.o out\dol\text_8010ad14_8010de30.o out\gt2Socket.o -out\dol\bss_802f247c_802f3480.o +out\dol\data_8027c2e9_8027c2f0.o out\dol\sdata_803854f8_80385508.o -out\dol\sbss_8038635c_80386360.o out\gt2Utility.o -out\dol\data_8027c2e9_8027c2f0.o out\dol\bss_802f34b0_802f34c0.o out\dol\sdata_80385519_80385520.o out\dol\sbss_80386364_80386368.o out\dol\sdata2_80388474_80388478.o out\qr2.o out\dol\data_8027d21b_8027d220.o +out\dol\bss_802f3624_802f3820.o out\dol\sdata_8038554a_80385550.o out\qr2RegKeys.o out\dol\sdata_803855c7_803855c8.o @@ -119,20 +127,20 @@ out\dol\data_8027d731_8027d738.o out\dol\sbss_8038638c_80386390.o out\dol\sdata2_8038847e_80388480.o out\ghttpPost.o -out\dol\rodata_8024c6c9_8024c6d0.o out\dol\data_8027d974_8027d978.o out\dol\sdata_803855ff_80385600.o out\ghttpProcess.o +out\dol\rodata_8024c6db_80252c78.o +out\dol\data_8027da43_8027da48.o +out\dol\sdata_80385655_80385658.o out\dol\sbss_803863a4_803863a8.o out\dol\sdata2_80388484_80388488.o out\gbucket.o -out\dol\data_8027da43_8027da48.o -out\dol\bss_802f3624_802f3820.o -out\dol\sdata_80385655_80385658.o out\gstats.o out\dol\text_8011a054_8011c10c.o out\sb_crypt.o out\dol\data_8027dca0_8027ddc0.o +out\dol\bss_802f3a20_802f3f40.o out\dol\sdata_803856be_803856f0.o out\sb_queryengine.o out\dol\data_8027dde5_8027dde8.o @@ -145,50 +153,49 @@ out\dol\sdata_8038572c_80385730.o out\dol\sbss_8038643c_80386440.o out\sb_serverlist.o out\dol\data_8027de44_8027de48.o -out\dol\bss_802f3a20_802f3f40.o out\sakeMain.o out\dol\text_8012249c_80124500.o out\dol\data_8027df3e_8027e708.o +out\dol\bss_802f4040_80346cf0.o out\dol\sdata_80385744_803857f0.o out\rvlArchive.o out\dol\text_80124e80_801981ec.o -out\dol\bss_802f4040_80346cf0.o +out\dol\data_8027e772_8029cc80.o +out\dol\sdata_803857f6_80385a08.o out\dol\sbss_80386448_80386838.o out\rvlMemHeap.o out\rvlMemExpHeap.o out\rvlMemFrmHeap.o out\rvlMemUnitHeap.o +out\dol\bss_80346d18_803481b0.o +out\dol\sbss_8038683c_80386998.o out\dol\sdata2_803884a4_80388860.o out\rvlMemAllocator.o out\rvlMemList.o -out\dol\sdata_803857f6_80385a08.o out\rvlMtx.o out\rvlMtx2.o out\rvlVec.o -out\dol\rodata_8024c6db_80252c78.o +out\dol\sdata_80385a10_80385b08.o out\dol\sdata2_803888b4_803888b8.o out\rvlQuat.o out\dol\text_8019b178_801ae5d8.o out\dol\rodata_80252c84_80252dd0.o out\dol\sdata2_803888cc_80388930.o out\rvlPadClamp.o -out\dol\data_8027e772_8029cc80.o -out\dol\bss_80346d18_803481b0.o -out\dol\sdata_80385a10_80385b08.o -out\dol\sbss_8038683c_80386998.o out\rvlPad.o -out\dol\sbss_803869c4_803869f0.o -out\siBios.o out\dol\text_801b0180_801b7410.o +out\dol\rodata_80252de6_80257700.o out\dol\data_8029ccd8_8029d058.o +out\dol\bss_80348230_80357220.o out\dol\sdata_80385b28_80385c68.o +out\dol\sbss_803869c4_803869f0.o +out\siBios.o +out\dol\sbss_803869f8_80386d30.o out\dol\sdata2_80388938_80388958.o out\tpl.o out\dol\text_801b7624_801ec088.o out\dol\data_8029d083_802a2318.o -out\dol\bss_80348230_80357220.o out\dol\sdata_80385c6e_80385ee0.o -out\dol\sbss_803869f8_80386d30.o out\soCommon.o out\dol\data_802a24f4_802a24f8.o out\soBasic.o @@ -196,28 +203,29 @@ out\dol\text_801ecff4_8020f62c.o out\dol\data_802a2543_802a2668.o out\eggAllocator.o out\dol\bss_80357238_803832d8.o +out\dol\sdata_80385eec_80385fc0.o out\dol\sbss_80386d38_80386d80.o out\eggArchive.o out\dol\text_8020fcc4_8021a0f0.o out\dol\data_802a268c_802a2b48.o out\eggDisposer.o out\dol\text_8021a1b8_802269a8.o -out\dol\rodata_80252de6_80257700.o out\dol\data_802a2b54_802a2ff8.o out\eggExpHeap.o out\dol\text_80226f04_80229540.o +out\dol\rodata_8025771a_80257740.o out\dol\data_802a3024_802a30b0.o +out\dol\bss_803832e4_80384320.o out\dol\sbss_80386d84_80386e90.o out\eggGraphicsFifo.o -out\dol\rodata_8025771a_80257740.o out\dol\data_802a30bc_802a30c0.o -out\dol\bss_803832e4_80384320.o out\dol\sbss_80386e99_80386ea0.o out\dol\sdata2_80388960_80388d68.o out\eggHeap.o out\dol\text_80229fac_80239dfc.o out\eggQuat.o out\dol\text_80239e10_80242498.o +out\dol\rodata_80257824_802582e0.o out\dol\data_802a30ec_802a3f78.o out\eggStreamDecomp.o out\dol\text_80242504_802432e0.o @@ -225,24 +233,17 @@ out\dol\data_802a3f90_802a3fc0.o out\dol\bss_80384348_80384b60.o out\eggThread.o out\eggUnitHeap.o -out\dol\ctors_80244de0_80244e88.o +out\dol\data_802a4004_802a4040.o out\dol\bss_80384b6c_80384b70.o out\dol\sbss_80386ec0_80386f78.o out\dol\sdata2_80388d80_803890f8.o out\eggVector.o -out\dol\rodata_80257824_802582e0.o +out\dol\ctors_80244e8c_80244e90.o +out\dol\bss_80384bf4_80384c00.o +out\dol\sbss_80386f90_80386fa0.o out\dol\sdata2_80389104_80389108.o out\eggVideo.o -out\dol\init_80006068_80006460.o -out\dol\extab_80006460_80006a20.o -out\dol\extabindex_80006a20_800072c0.o out\dol\text_80244074_80244de0.o -out\dol\ctors_80244e8c_80244e90.o -out\dol\dtors_80244ea4_80244eac.o out\dol\rodata_80258560_80258580.o -out\dol\data_802a4004_802a4040.o -out\dol\bss_80384bf4_80384c00.o -out\dol\sdata_80385eec_80385fc0.o -out\dol\sbss_80386f90_80386fa0.o out\dol\sdata2_80389118_80389140.o out\dol\sbss2_80389140_8038917c.o diff --git a/pack/dol_segments.csv b/pack/dol_segments.csv deleted file mode 100644 index 11e833132..000000000 --- a/pack/dol_segments.csv +++ /dev/null @@ -1,14 +0,0 @@ -name,type,start,end -init,code,80004000,80006460 -extab,data,80006460,80006A20 -extabindex,data,80006A20,800072C0 -text,code,800072C0,80244DE0 -ctors,data,80244DE0,80244E90 -dtors,data,80244EA4,80244EAC -rodata,data,80244EC0,80258580 -data,data,80258580,802A4040 -bss,bss,802A4080,80384C00 -sdata,data,80384C00,80385FC0 -sbss,bss,80385FC0,80386FA0 -sdata2,data,80386FA0,80389140 -sbss2,bss,80389140,8038917C diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 34d8d6b2e..1adc3a973 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,6 +1,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End +1,,asm/trkHeader.s,0x80004000,0x80004100,,,,,,,,,,,,,,,,,,,,,,,, 1,,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, -1,,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0 ,0x8006a518,,,,,,,,,0x802bd4ec,0x802bd4ec,,,,,0x80387cac,0x80387cd8,, +1,,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0,0x8006a518,,,,,,,,,,,,,,,0x80387cac,0x80387cd8,, 1,,source/nw4r/g3d/g3d_fog.cpp,,,,,,,0x800774d0,0x800775d0,,,,,,,,,,,,,,,0x80387d58,0x80387d5C,, 1,,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, 1,,source/nw4r/math/mathTypes.cpp,,,,,,,0x80085600,0x80085938,,,,,,,,,,,,,,,0x80387ea8,0x80387eb4,, @@ -11,17 +12,16 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/gamespy/md5c.c,,,,,,,0x800f0264,0x800f118c,,,,,0x8024c6b8,0x8024c6c9,0x8027aca0,0x8027ace0,,,,,,,,,, 1,,source/gamespy/common/revolution/gsSocketRevolution.c,,,,,,,0x800f118c,0x800f164c,,,,,,,,,,,,,0x80386350,0x80386358,,,, 1,,source/gamespy/common/revolution/gsUtilRevolution.c,,,,,,,0x800f1f58,0x800f2048,,,,,,,,,,,,,,,,,, -,,source/gamespy/common/gsPlatformUtil.c,,,,,,,0x800f1f58,0x800f3844,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/common/gsAvailable.c,,,,,,,0x800f38a4,0x800f3a20,,,,,,,0x8027ad58,0x8027ad79,0x802f2338,0x802f2410,,,0x80386358,0x8038635c,,,, -,1,source/gamespy/common/gsCore.c,,,,,,,0x800f3c08 ,0x800f41dc,,,,,,,,,,,,,,,,,, +,1,source/gamespy/common/gsCore.c,,,,,,,0x800f3c08,0x800f41dc,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/common/gsUdpEngine.c,,,,,,,0x800f489c,0x800f5a6c,,,,,,,,,0x802f2440,0x802f247c,,,,,,,, 1,1,source/gamespy/common/gsXML.c,,,,,,,0x800f5a6c,0x800fb828,,,,,,,0x8027ad80,0x8027af18,,,0x803850a0,0x80385105,,,,,, 1,1,source/gamespy/gp/gp.c,,,,,,,0x800fb828,0x800fc7d4,,,,,,,0x8027af18,0x8027b289,,,0x80385108,0x80385118,,,,,, 1,1,source/gamespy/gp/gpi.c,,,,,,,0x800fc7d4,0x800fd160,,,,,,,0x8027b290,0x8027b30f,,,0x80385118,0x80385150,,,,,, 1,1,source/gamespy/gp/gpiBuddy.c,,,,,,,0x800fd160,0x800fee90,,,,,,,0x8027b310,0x8027b453,,,0x80385150,0x803851ea,,,,,, 1,1,source/gamespy/gp/gpiBuffer.c,,,,,,,0x800fee90,0x800ff8c4,,,,,,,0x8027b458,0x8027b4ba,,,0x803851f0,0x80385206,,,,,, -1,1,source/gamespy/gp/gpiCallback.c,,,,,,,0x800ff8c4,0x800ffe28 ,,,,,,,0x8027b4c0,0x8027b4cf,,,,,,,,,, -1,1,source/gamespy/gp/gpiConnect.c,,,,,,,0x800ffe28 ,0x80101470,,,,,,,0x8027b4d0,0x8027b876,,,0x80385208,0x8038529b,,,,,, +1,1,source/gamespy/gp/gpiCallback.c,,,,,,,0x800ff8c4,0x800ffe28,,,,,,,0x8027b4c0,0x8027b4cf,,,,,,,,,, +1,1,source/gamespy/gp/gpiConnect.c,,,,,,,0x800ffe28,0x80101470,,,,,,,0x8027b4d0,0x8027b876,,,0x80385208,0x8038529b,,,,,, 1,1,source/gamespy/gp/gpiInfo.c,,,,,,,0x80101470,0x80103908,,,,,,,0x8027b878,0x8027bbce,,,0x803852a0,0x80385355,,,0x80388470,0x80388474,, 1,1,source/gamespy/gp/gpiKeys.c,,,,,,,0x80103908,0x80103f70,,,,,,,0x8027bbd0,0x8027bc11,,,0x80385358,0x8038535a,,,,,, 1,1,source/gamespy/gp/gpiOperation.c,,,,,,,0x80103f70,0x80104648,,,,,,,0x8027bc18,0x8027bc60,,,,,,,,,, @@ -36,34 +36,33 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,1,source/gamespy/gt2/gt2Callback.c,,,,,,,0x801099c4,0x8010a244,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/gt2/gt2Connection.c,,,,,,,0x8010a244,0x8010a918,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/gt2/gt2Main.c,,,,,,,0x8010a918,0x8010ad14,,,,,,,,,,,,,,,,,, -1,1,source/gamespy/gt2/gt2Socket.c,,,,,,,0x8010de30 ,0x8010e9c0,,,,,,,,,,,,,,,,,, +1,1,source/gamespy/gt2/gt2Socket.c,,,,,,,0x8010de30,0x8010e9c0,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/gt2/gt2Utility.c,,,,,,,0x8010e9c0,0x8010ecac,,,,,,,,,0x802f3480,0x802f34b0,0x80385508,0x80385519,0x80386360,0x80386364,,,, 1,1,source/gamespy/qr2/qr2.c,,,,,,,0x8010ecac,0x8011156c,,,,,,,0x8027c2f0,0x8027d21b,0x802f34c0,0x802f3624,0x80385520,0x8038554a,0x80386368,0x8038636c,0x80388478,0x8038847e,, 1,1,source/gamespy/qr2/qr2RegKeys.c,,,,,,,0x8011156c,0x801115fc,,,,,,,0x8027d220,0x8027d6f8,,,0x80385550,0x803855c7,,,,,, 1,1,source/gamespy/ghttp/ghttpBuffer.c,,,,,,,0x801115fc,0x80111de8,,,,,,,,,,,0x803855c8,0x803855d3,,,,,, 1,,source/gamespy/ghttp/ghttpCallbacks.c,,,,,,,0x80111f0c,0x8011202c,,,,,,,,,,,,,,,,,, -1,1,source/gamespy/ghttp/ghttpCommon.c,,,,,,,0x8011202c,0x8011248c ,,,,,,,,,,,0x803855d8,0x803855e0,0x80386370,0x80386378,,,, +1,1,source/gamespy/ghttp/ghttpCommon.c,,,,,,,0x8011202c,0x8011248c,,,,,,,,,,,0x803855d8,0x803855e0,0x80386370,0x80386378,,,, 1,1,source/gamespy/ghttp/ghttpConnection.c,,,,,,,0x801125c8,0x80112d14,,,,,,,0x8027d6f8,0x8027d701,,,,,0x80386378,0x80386388,,,, -1,1,source/gamespy/ghttp/ghttpEncryption.c,,,,,,,0x80112d14,0x8011312c ,,,,,,,0x8027d708,0x8027d711,,,,,,,,,, -1,1,source/gamespy/ghttp/ghttpMain.c,,,,,,,0x8011312c ,0x801139b0 ,,,,,,,0x8027d718,0x8027d731,,,0x803855e0,0x803855e1,0x80386388,0x8038638c,,,, +1,1,source/gamespy/ghttp/ghttpEncryption.c,,,,,,,0x80112d14,0x8011312c,,,,,,,0x8027d708,0x8027d711,,,,,,,,,, +1,1,source/gamespy/ghttp/ghttpMain.c,,,,,,,0x8011312c,0x801139b0,,,,,,,0x8027d718,0x8027d731,,,0x803855e0,0x803855e1,0x80386388,0x8038638c,,,, 1,1,source/gamespy/ghttp/ghttpPost.c,,,,,,,0x801139b0,0x80115384,,,,,,,0x8027d738,0x8027d974,,,0x803855e1,0x803855ff,0x80386390,0x803863a4,0x80388480,0x80388484,, -1,1,source/gamespy/ghttp/ghttpProcess.c,,,,,,,0x80115384,0x80116dd4 ,,,,,0x8024c6d0,0x8024c6db,0x8027d978,0x8027da43,,,0x80385600,0x80385655,,,,,, -1,1,source/gamespy/gstats/gbucket.c,,,,,,,0x80116dd4 ,0x80117f6c ,,,,,,,,,,,,,0x803863a8,0x803863c0,0x80388488,0x80388490,, -1,1,source/gamespy/gstats/gstats.c,,,,,,,0x80117f6c ,0x8011a054 ,,,,,,,0x8027da48,0x8027dca0,0x802f3820,0x802f3a20,0x80385658,0x803856be,0x803863c0,0x803863dc,,,, +1,1,source/gamespy/ghttp/ghttpProcess.c,,,,,,,0x80115384,0x80116dd4,,,,,0x8024c6d0,0x8024c6db,0x8027d978,0x8027da43,,,0x80385600,0x80385655,,,,,, +1,1,source/gamespy/gstats/gbucket.c,,,,,,,0x80116dd4,0x80117f6c,,,,,,,,,,,,,0x803863a8,0x803863c0,0x80388488,0x80388490,, +1,1,source/gamespy/gstats/gstats.c,,,,,,,0x80117f6c,0x8011a054,,,,,,,0x8027da48,0x8027dca0,0x802f3820,0x802f3a20,0x80385658,0x803856be,0x803863c0,0x803863dc,,,, 1,1,source/gamespy/serverbrowsing/sb_crypt.c,,,,,,,0x8011c10c,0x8011c540,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/serverbrowsing/sb_queryengine.c,,,,,,,0x8011c540,0x8011d024,,,,,,,0x8027ddc0,0x8027dde5,,,0x803856f0,0x803856f8,,,,,, 1,1,source/gamespy/serverbrowsing/sb_server.c,,,,,,,0x8011d024,0x8011dd04,,,,,,,0x8027dde8,0x8027ddf1,,,0x803856f8,0x80385721,0x80386430,0x8038643c,0x80388490,0x80388498,, 1,1,source/gamespy/serverbrowsing/sb_serverbrowsing.c,,,,,,,0x8011dd04,0x8011e518,,,,,,,0x8027ddf8,0x8027de18,,,0x80385728,0x8038572c,,,,,, -1,1,source/gamespy/serverbrowsing/sb_serverlist.c,,,,,,,0x8011e518,0x80121eec ,,,,,,,0x8027de18,0x8027de44,,,0x80385730,0x80385740,0x80386440,0x80386448,0x80388498,0x803884a4,, -1,1,source/gamespy/sake/sakeMain.c,,,,,,,0x80121eec ,0x8012249c,,,,,,,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,,,,,, +1,1,source/gamespy/serverbrowsing/sb_serverlist.c,,,,,,,0x8011e518,0x80121eec,,,,,,,0x8027de18,0x8027de44,,,0x80385730,0x80385740,0x80386440,0x80386448,0x80388498,0x803884a4,, +1,1,source/gamespy/sake/sakeMain.c,,,,,,,0x80121eec,0x8012249c,,,,,,,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,,,,,, 1,,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, -,,rxFrameHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, -1,,source/rvl/mem/rvlMemHeap.c,,,,,,,0x801981ec,0x80198798,,,,,,,,,0x80346cf0,0x80346d18,,,0x80386838,0x8038683C,,,, +1,,source/rvl/mem/rvlMemHeap.cpp,,,,,,,0x801981ec,0x80198798,,,,,,,,,0x80346cf0,0x80346d18,,,0x80386838,0x8038683C,,,, 1,,source/rvl/mem/rvlMemExpHeap.c,,,,,,,0x80198798,0x80199430,,,,,,,,,,,,,,,,,, -1,,source/rvl/mem/rvlMemFrmHeap.c,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, -1,,source/rvl/mem/rvlMemUnitHeap.c,,,,,,,0x801998A4,0x80199b58,,,,,,,,,,,,,,,,,, -1,,source/rvl/mem/rvlMemAllocator.c,,,,,,,0x80199b58,0x80199bf0,,,,,,,,,,,,,,,0x80388860,0x80388870,, -1,,source/rvl/mem/rvlMemList.c,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, +1,,source/rvl/mem/rvlMemFrmHeap.cpp,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, +1,,source/rvl/mem/rvlMemUnitHeap.cpp,,,,,,,0x801998A4,0x80199b58,,,,,,,,,,,,,,,,,, +1,,source/rvl/mem/rvlMemAllocator.cpp,,,,,,,0x80199b58,0x80199bf0,,,,,,,,,,,,,,,0x80388860,0x80388870,, +1,,source/rvl/mem/rvlMemList.cpp,,,,,,,0x80199BF0,0x80199D04,,,,,,,,,,,,,,,,,, 1,,source/rvl/mtx/rvlMtx.c,,,,,,,0x80199d04,0x8019a9c4,,,,,,,,,,,0x80385a08,0x80385a10,,,0x80388870,0x80388890,, 1,,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, 1,,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, @@ -73,20 +72,20 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/si/siBios.c,,,,,,,,,,,,,,,,,,,,,0x803869f0,0x803869f8,,,, 1,,source/rvl/tpl/tpl.c,,,,,,,0x801b7410,0x801b7624,,,,,,,0x8029d058,0x8029d083,,,0x80385c68,0x80385c6e,,,0x80388958,0x80388960,, 1,,source/rvl/so/soCommon.c,,,,,,,0x801ec088,0x801ecf20,,,,,,,0x802a2318,0x802a24f4,0x80357220,0x80357238,0x80385ee0,0x80385ee8,0x80386D30,0x80386D38,,,, -1,,source/rvl/so/soBasic.c,,,,,,,0x801ecf20,0x801ecff4,,,,,,,0x802a24f8 ,0x802a2543,,,0x80385ee8,0x80385eeC,,,,,, -1,,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680 ,,,,,,,,,, +1,,source/rvl/so/soBasic.c,,,,,,,0x801ecf20,0x801ecff4,,,,,,,0x802a24f8,0x802a2543,,,0x80385ee8,0x80385eeC,,,,,, +1,,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680,,,,,,,,,, 1,,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, -1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54 ,,,,,,,,,, +1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54,,,,,,,,,, 1,,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, 1,,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, -1,,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68 ,0x80388D80,, +1,,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68,0x80388D80,, 1,,source/egg/math/eggQuat.cpp,,,,,,,0x80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, ,,source/egg/core/eggScene.cpp,,,,,,,0x8023AD10,0x8023ADDC,,,,,,,,,,,,,,,,,, 1,,source/egg/core/eggStreamDecomp.cpp,,,,,,,0x80242498,0x80242504,,,,,,,0x802A3F78,0x802A3F90,,,,,,,,,, ,,source/egg/core/eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,0x80386F60,0x80386F64,,,, -1,,source/egg/core/eggThread.cpp,,,,,,,0x802432E0 ,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C ,,,,,,,, +1,,source/egg/core/eggThread.cpp,,,,,,,0x802432E0,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C,,,,,,,, 1,,source/egg/core/eggUnitHeap.cpp,,,,,,,0x80243754,0x80243A00,,,,,,,0x802A3FD8,0x802A4004,,,,,,,,,, -1,,source/egg/math/eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,0x80384B70,0x80384BF4,,,0x80386F78,0x80386F90,0x803890F8 ,0x80389104,, +1,,source/egg/math/eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,0x80384B70,0x80384BF4,,,0x80386F78,0x80386F90,0x803890F8,0x80389104,, 1,1,source/egg/core/eggVideo.cpp,,,,,,,0x80243D18,0x80244074,,,,,0x802582E0,0x80258560,,,,,,,,,0x80389108,0x80389118,, ,,eggXfb.cpp,,,,,,,0x80244160,0x80244200,,,,,,,,,,,,,,,,,, ,,eggXfbManager.cpp,,,,,,,0x80244200,0x802443AC,,,,,,,,,,,,,,,,,, diff --git a/pack/rel_objects.txt b/pack/rel_objects.txt index 24a23808c..1bccda207 100644 --- a/pack/rel_objects.txt +++ b/pack/rel_objects.txt @@ -1,12 +1,12 @@ out\rel\text_805103b4_80512694.o +out\rel\ctors_8088f400_8088f704.o +out\rel\dtors_8088f704_8088f710.o +out\rel\rodata_8088f710_808b2bd0.o out\rel\data_808b2bd0_808b2c30.o out\rel\bss_809bd6e0_809bd6e8.o out\JmpResourceCourse.o out\rel\text_805127ec_805f8b34.o out\MessageGroup.o out\rel\text_805f8b90_8088f400.o -out\rel\ctors_8088f400_8088f704.o -out\rel\dtors_8088f704_8088f710.o -out\rel\rodata_8088f710_808b2bd0.o out\rel\data_808b2c3c_808dd3d4.o out\rel\bss_809bd6ec_809c4f90.o diff --git a/pack/rel_segments.csv b/pack/rel_segments.csv deleted file mode 100644 index e58c3b7e6..000000000 --- a/pack/rel_segments.csv +++ /dev/null @@ -1,7 +0,0 @@ -name,type,start,end -text,code,805103B4,8088F400 -ctors,data,8088F400,8088F704 -dtors,data,8088F704,8088F710 -rodata,data,8088F710,808B2BD0 -data,data,808B2BD0,808DD3D4 -bss,bss,809BD6E0,809C4F90 \ No newline at end of file diff --git a/pack/symbols.txt b/pack/symbols.txt index 52057ad9f..2c568ab0f 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -1,3 +1,4 @@ +0x80006038 memset 0x800060a4 __start 0x80008e7c onExit__Q23EGG6ThreadFv 0x80008e80 onEnter__Q23EGG6ThreadFv @@ -200,6 +201,87 @@ 0x801938f8 IOS_Open 0x80193ad8 IOS_Close 0x80194290 IOS_Ioctl +0x8019b314 nandCreate +0x8019b43c NANDCreate +0x8019b4b0 NANDPrivateCreate +0x8019b524 NANDPrivateCreateAsync +0x8019b59c NANDDelete +0x8019b64c NANDPrivateDelete +0x8019b6e4 NANDPrivateDeleteAsync +0x8019b7a4 NANDRead +0x8019b80c NANDReadAsync +0x8019b884 NANDWrite +0x8019b8ec NANDWriteAsync +0x8019b964 NANDSeek +0x8019ba04 NANDSeekAsync +0x8019bab4 nandCreateDir +0x8019bbe0 NANDCreateDir +0x8019bc54 NANDPrivateCreateDir +0x8019bcc8 NANDPrivateCreateDirAsync +0x8019bd40 nandMove +0x8019bee8 NANDMove +0x8019bf4c NANDGetLength +0x8019bfd4 nandGetFileStatusAsyncCallback +0x8019c048 NANDGetLengthAsync +0x8019c0d8 nandComposePerm +0x8019c12c nandSplitPerm +0x8019c1b8 nandGetStatus +0x8019c30c nandGetStatusCallback +0x8019c380 NANDGetStatus +0x8019c3e4 NANDPrivateGetStatus +0x8019c448 NANDPrivateGetStatusAsync +0x8019c4cc nandSetStatus +0x8019c614 NANDSetStatus +0x8019c678 NANDPrivateSetStatus +0x8019c6dc NANDSetUserData +0x8019c6e4 NANDGetUserData +0x8019c6ec nandOpen +0x8019c800 NANDOpen +0x8019c88c NANDPrivateOpen +0x8019c918 NANDOpenAsync +0x8019c990 NANDPrivateOpenAsync +0x8019ca08 nandOpenCallback +0x8019ca80 NANDClose +0x8019caec NANDCloseAsync +0x8019cb74 NANDSafeOpen +0x8019cb80 nandSafeOpen +0x8019cf28 NANDSafeClose +0x8019d104 NANDPrivateSafeOpenAsync +0x8019d130 nandSafeOpenAsync +0x8019d298 nandSafeOpenCallback +0x8019d688 nandReadOpenCallback +0x8019d720 NANDSafeCloseAsync +0x8019d824 nandSafeCloseCallback +0x8019d9e8 nandReadCloseCallback +0x8019da44 nandCloseCallback +0x8019daa0 nandRemoveTailToken +0x8019db74 nandGetHeadToken +0x8019dc48 nandGetRelativeName +0x8019dce0 nandConvertPath +0x8019de1c nandIsPrivatePath +0x8019de50 nandIsUnderPrivatePath +0x8019dea8 nandIsInitialized +0x8019debc nandReportErrorCode +0x8019dec0 nandConvertErrorCode +0x8019e020 nandGenerateAbsPath +0x8019e0e8 nandGetParentDirectory +0x8019e18c NANDInit +0x8019e2b8 nandOnShutdown +0x8019e390 NANDGetCurrentDir +0x8019e40c NANDGetHomeDir +0x8019e460 nandCallback +0x8019e49c nandGetType +0x8019e770 NANDGetType +0x8019e7b4 NANDPrivateGetTypeAsync +0x8019e7fc nandGetTypeCallback +0x8019e874 nandGetHomeDir +0x8019e880 NANDInitBanner +0x8019e95c NANDSecretGetUsage +0x8019ea14 nandCalcUsage +0x8019ead0 NANDCheck +0x8019ebd8 reserveFileDescriptor +0x8019ec2c NANDLoggingAddMessageAsync +0x8019ed24 asyncRoutine 0x801a0504 OSRegisterVersion 0x801a1e70 OSSetCurrentContext 0x801a2098 OSClearContext diff --git a/requirements.txt b/requirements.txt index da09cabf35a35c82ad68989b0b05589232ecac34..66d6ec8cdc59fc993d6bc541f5810b12cf36628a 100644 GIT binary patch literal 1134 zcmY*Y+fKqz5Zq@IKgEU?M1AnhC!>jpUjU^Aw9uv;ZhpKvv%_gi$Vq8;c6N4m&fo9M zwzjji9j&#QHTI0($P#8N4&7FVe zY+Eoef8x=^GSAO#1x#xTM8j1)GSpT<3zcqgqE@m+#uk)q1`L969)A(89V>q&=u7A_As-z_S8o z;qTf7t8$Xk)*!wI!*Vd4)TlqkH8u504Y9%*>MHa?*Kl8QgZA*?eK+8w5;|q5Q}ufc zH#G-CG4P%GCfjtdD>BNJ^34=+^}{VFy7Eokbv^HqL7&h?#M1F{$u<5B^$$3_Co8W) z&Cz?Q=MM9JE{r2YdMB{clSlN83N^95OKn5GCQ3eF2f_C)?^9i0J??|uw{R={AsjKP x)mJK@A#`_r-0?hhz3Z-PDsq-KCl%Zq??b=)UgOcd#5=rzhUh)&OW+&k{{wt-rEUNK delta 32 ocmaFIF^PGC%ETO%i5nayCosB94r0=nEX8ax`4qFqWFHnK0Ky&$x&QzG diff --git a/source/decomp.h b/source/decomp.h new file mode 100644 index 000000000..75984d38e --- /dev/null +++ b/source/decomp.h @@ -0,0 +1,3 @@ +#pragma once + +#define UNKNOWN_FUNCTION(name) void name(void) diff --git a/sources.py b/sources.py deleted file mode 100644 index 180992729..000000000 --- a/sources.py +++ /dev/null @@ -1,107 +0,0 @@ -RVL_OPTS = '-ipa file' -EGG_OPTS = '-ipa function -rostr' -REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' -NW4R_OPTS = '-ipa file -inline auto -rostr -O4,p' - -compile_source("source/rvl/trk/rvlTrkMem.c", "out/rvlTrkMem.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/arc/rvlArchive.c", "out/rvlArchive.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemHeap.cpp", "out/rvlMemHeap.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemExpHeap.c", "out/rvlMemExpHeap.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemFrmHeap.cpp", "out/rvlMemFrmHeap.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemUnitHeap.cpp", "out/rvlMemUnitHeap.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemAllocator.cpp", "out/rvlMemAllocator.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mem/rvlMemList.cpp", "out/rvlMemList.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mtx/rvlMtx.c", "out/rvlMtx.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mtx/rvlMtx2.c", "out/rvlMtx2.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mtx/rvlVec.c", "out/rvlVec.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/mtx/rvlQuat.c", "out/rvlQuat.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/pad/rvlPadClamp.c", "out/rvlPadClamp.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/pad/rvlPad.c", "out/rvlPad.o", '4199_60831', RVL_OPTS + " -inline on,noauto ") -compile_source("source/rvl/si/siBios.c", "out/siBios.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/tpl/tpl.c", "out/tpl.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/so/soCommon.c", "out/soCommon.o", '4199_60831', RVL_OPTS) -compile_source("source/rvl/so/soBasic.c", "out/soBasic.o", '4199_60831', RVL_OPTS) - -compile_source("source/gamespy/darray.c", "out/darray.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/hashtable.c", "out/hashtable.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/md5c.c", "out/md5c.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/common/revolution/gsSocketRevolution.c", "out/gsSocketRevolution.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/common/gsAvailable.c", "out/gsAvailable.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/common/revolution/gsUtilRevolution.c", "out/gsUtilRevolution.o", '4199_60831', RVL_OPTS) -#compile_source("source/gamespy/common/gsPlatformUtil.c", "out/gsPlatformUtil.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/common/gsCore.c", "out/gsCore.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/common/gsUdpEngine.c", "out/gsUdpEngine.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/common/gsXML.c", "out/gsXML.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gp.c", "out/gp.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpi.c", "out/gpi.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiBuddy.c", "out/gpiBuddy.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiBuffer.c", "out/gpiBuffer.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiCallback.c", "out/gpiCallback.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiConnect.c", "out/gpiConnect.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiInfo.c", "out/gpiInfo.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiKeys.c", "out/gpiKeys.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiOperation.c", "out/gpiOperation.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiPeer.c", "out/gpiPeer.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiProfile.c", "out/gpiProfile.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiSearch.c", "out/gpiSearch.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiTransfer.c", "out/gpiTransfer.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiUnique.c", "out/gpiUnique.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gp/gpiUtility.c", "out/gpiUtility.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gt2/gt2Auth.c", "out/gt2Auth.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gt2/gt2Buffer.c", "out/gt2Buffer.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gt2/gt2Callback.c", "out/gt2Callback.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gt2/gt2Connection.c", "out/gt2Connection.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gt2/gt2Main.c", "out/gt2Main.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gt2/gt2Socket.c", "out/gt2Socket.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gt2/gt2Utility.c", "out/gt2Utility.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/qr2/qr2.c", "out/qr2.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/qr2/qr2RegKeys.c", "out/qr2RegKeys.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpBuffer.c", "out/ghttpBuffer.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpCallbacks.c", "out/ghttpCallbacks.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpCommon.c", "out/ghttpCommon.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpConnection.c", "out/ghttpConnection.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpEncryption.c", "out/ghttpEncryption.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpMain.c", "out/ghttpMain.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpPost.c", "out/ghttpPost.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/ghttp/ghttpProcess.c", "out/ghttpProcess.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gstats/gbucket.c", "out/gbucket.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/gstats/gstats.c", "out/gstats.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/serverbrowsing/sb_crypt.c", "out/sb_crypt.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/serverbrowsing/sb_queryengine.c", "out/sb_queryengine.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/serverbrowsing/sb_server.c", "out/sb_server.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/serverbrowsing/sb_serverlist.c", "out/sb_serverlist.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/serverbrowsing/sb_serverbrowsing.c", "out/sb_serverbrowsing.o", '4199_60831', RVL_OPTS) -compile_source("source/gamespy/sake/sakeMain.c", "out/sakeMain.o", '4199_60831', RVL_OPTS) - -compile_source("source/nw4r/math/mathTriangular.cpp", "out/mathTriangular.o", '4201_127', NW4R_OPTS) -compile_source("source/nw4r/math/mathTypes.cpp", "out/mathTypes.o", '4201_127', NW4R_OPTS) -compile_source("source/nw4r/g3d/g3d_camera.cpp", "out/g3d_camera.o", '4201_127', NW4R_OPTS) -compile_source("source/nw4r/g3d/g3d_fog.cpp", "out/g3d_fog.o", '4201_127', NW4R_OPTS) -compile_source("source/nw4r/ut/utList.cpp", "out/utList.o", '4201_127', NW4R_OPTS) - -compile_source("source/dwc/common/dwc_error.c", "out/dwc_error.o", '4199_60831', RVL_OPTS) - -compile_source("source/egg/core/eggAllocator.cpp", "out/eggAllocator.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") -compile_source("source/egg/core/eggArchive.cpp", "out/eggArchive.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") -compile_source("source/egg/core/eggDisposer.cpp", "out/eggDisposer.o", '4201_127', EGG_OPTS) -compile_source("source/egg/core/eggExpHeap.cpp", "out/eggExpHeap.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") -compile_source("source/egg/core/eggGraphicsFifo.cpp", "out/eggGraphicsFifo.o", '4201_127', EGG_OPTS) -compile_source("source/egg/core/eggHeap.cpp", "out/eggHeap.o", '4201_127', EGG_OPTS + " -ipa file -use_lmw_stmw=on ") -compile_source("source/egg/math/eggQuat.cpp", "out/eggQuat.o", '4201_127', EGG_OPTS) -compile_source("source/egg/core/eggStreamDecomp.cpp", "out/eggStreamDecomp.o", '4201_127', EGG_OPTS) -# compile_source("source/egg/core/eggSystem.cpp", "out/eggSystem.o", '4201_127', EGG_OPTS) -compile_source("source/egg/core/eggThread.cpp", "out/eggThread.o", '4201_127', EGG_OPTS) -compile_source("source/egg/core/eggUnitHeap.cpp", "out/eggUnitHeap.o", '4201_127', EGG_OPTS + " -use_lmw_stmw=on ") -compile_source("source/egg/core/eggVideo.cpp", "out/eggVideo.o", '4201_127', EGG_OPTS+ " -use_lmw_stmw=on ") -compile_source("source/egg/math/eggVector.cpp", "out/eggVector.o", '4201_127', EGG_OPTS) -# compile_source("source/egg/core/eggXfb.cpp", "out/eggXfb.o", '4201_127', EGG_OPTS) -# compile_source("source/egg/core/eggXfbManager.cpp", "out/eggXfbManager.o", '4201_127', EGG_OPTS) - -""" -StaticR.rel -""" - -compile_source("source/game/ui/MessageGroup.cpp", "out/MessageGroup.o", '4201_127', REL_OPTS) -compile_source("source/game/ui/ControlGroup.cpp", "out/ControlGroup.o", '4201_127', REL_OPTS) -compile_source("source/game/ui/UIControl.cpp", "out/UIControl.o", '4201_127', REL_OPTS) -compile_source("source/game/jmap/JmpResourceCourse.cpp", "out/JmpResourceCourse.o", '4201_127', REL_OPTS) From cc0e7474ca7560548eab2bb71c234c9053242100 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Jul 2021 21:12:50 +0200 Subject: [PATCH 100/477] Code-review followup for #31 (#38) * Fix build log * align trkHeader --- asm/trkHeader.s | 2 +- build.py | 23 +++++++++++++++-------- glossary.md | 4 ++-- mkwutil/gen_lcf.py | 1 + mkwutil/percent_decompiled.py | 1 - 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/asm/trkHeader.s b/asm/trkHeader.s index 74db413e8..6a3e3ed94 100644 --- a/asm/trkHeader.s +++ b/asm/trkHeader.s @@ -3,4 +3,4 @@ .section .init, "ax" # { 80004000..80005f34 (init) } .asciz "Metrowerks Target Resident Kernel for PowerPC" -.skip 0xd2 +.balign 0x100 diff --git a/build.py b/build.py index de5a67062..b86dadd42 100644 --- a/build.py +++ b/build.py @@ -10,7 +10,7 @@ import subprocess import sys -from multiprocessing.dummy import Pool as ThreadPool +from multiprocessing.dummy import Pool as ThreadPool, Lock import multiprocessing import colorama @@ -28,6 +28,7 @@ from mkwutil.percent_decompiled import percent_decompiled colorama.init() +print_mutex = Lock() # Remember which files are stripped. dol_slices = SliceTable.load_dol_slices(sections=DOL_SECTIONS) @@ -129,9 +130,16 @@ def compile_source_impl(src, dst, version="default", additional="-ipa file"): """Compiles a source file.""" # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" - if VERBOSE: - print(command) - subprocess.run(command, check=True) + process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + lines = list(iter(process.stdout.readline, "")) + with print_mutex: + print(f'{colored("CC", "green")} {src}') + if VERBOSE: + print(command) + for line in lines: + print(" " + line.strip()) + process.wait() + assert process.returncode == 0, f"{command} exited with returncode {process.returncode}" gSourceQueue = [] @@ -166,10 +174,9 @@ def compile_queued_sources(): # Queued -def compile_source(src, version="default", additional="-ipa file"): - """Compiles a C/C++ file.""" +def queue_compile_source(src, version="default", additional="-ipa file"): + """Queues a C/C++ file for compilation.""" dst = (Path("out") / src.parts[-1]).with_suffix(".o") - print(f'{colored("CC", "green")} {src}') gSourceQueue.append((src, dst, version, additional)) @@ -211,7 +218,7 @@ def compile_sources(): out_dir.mkdir(exist_ok=True) for src in chain(SOURCES_DOL, SOURCES_REL): - compile_source(Path(src.src), src.cc, src.opts) + queue_compile_source(Path(src.src), src.cc, src.opts) compile_queued_sources() diff --git a/glossary.md b/glossary.md index ab41c510b..a8cb88d54 100644 --- a/glossary.md +++ b/glossary.md @@ -7,8 +7,8 @@ An area of program address space with a specific kind of content. | Name | Purpose | DOL | REL | | ------------ | --------------------------------------- | --- | --- | | `init` | Program entrypoint | Yes | No | -| `extab` | ? | Yes | ? | -| `extabindex` | ? | Yes | ? | +| `extab` | Exception Table | Yes | No | +| `extabindex` | Exception Table | Yes | No | | `text` | Program code | Yes | Yes | | `ctors` | Pointers to code to execute on startup | Yes | Yes | | `dtors` | Pointers to code to execute on shutdown | Yes | Yes | diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index 8510ceca0..7164de1cd 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -36,6 +36,7 @@ def gen_lcf( with open(obj_path, "rb") as file: elf = ELFFile(file) symtab = elf.get_section_by_name(".symtab") + assert symtab is not None, "ELF has no symbol table" for symbol in symtab.iter_symbols(): match = MATCH_UNK.match(symbol.name) if not match: diff --git a/mkwutil/percent_decompiled.py b/mkwutil/percent_decompiled.py index d26fac1fe..93470b78b 100644 --- a/mkwutil/percent_decompiled.py +++ b/mkwutil/percent_decompiled.py @@ -83,7 +83,6 @@ def percent_decompiled(dir="."): def piecewise_add(x, y): return list(a + b for a, b in zip(x, y)) - footer_idx = len(matrix) matrix.append( [ colored(cell, attrs=["bold"]) From 8894a81e452e71cc2d71b894cb4ecf39d9d21a3d Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 21 Jul 2021 21:24:22 +0200 Subject: [PATCH 101/477] Regenerate ASM files (#33) --- pack/dol_objects.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 19cdbccc8..93f46d735 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -9,8 +9,7 @@ out\dol\ctors_80244de0_80244e88.o out\dol\dtors_80244ea4_80244eac.o out\dol\rodata_80244ec0_80248010.o out\dol\data_80258580_80274148.o -out\dol\bss_802a4080_802bd4ec.o -out\dol\bss_802bd4ec_802f2338.o +out\dol\bss_802a4080_802f2338.o out\dol\sdata_80384c00_803850a0.o out\dol\sbss_80385fc0_803862a8.o out\dol\sdata2_80386fa0_80387cac.o From 6c06b628c3e1a51ceda42f37ced9dab98fe31f22 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 21 Jul 2021 15:16:09 -0600 Subject: [PATCH 102/477] :recycle: Refactor gen_asm.py --- mkwutil/gen_asm.py | 258 ++++++++++++++++++++++++++++++--------------- 1 file changed, 174 insertions(+), 84 deletions(-) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index 5b05a859e..39265fc31 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -96,6 +96,8 @@ def dump_section(self): self.dump_section_header() self.dump_section_body() +def addr_in_sym(addr, sym): + return sym.addr <= addr < sym.addr + sym.size class CAsmGenerator: """Generates C files with assembly functions.""" @@ -111,15 +113,28 @@ def __init__(self, data, _slice, symbols, output): self.extern_functions_seen = set() self.extern_functions = list() + # Validated iterator of own_symbols + def __symbols(self): + for sym in self.own_symbols: + assert ( + sym.addr == addr + ), f"Currently at {hex(addr)} but next symbol is at {hex(sym.addr)}" + + yield sym + addr += sym.size + + assert ( + addr == self.slice.stop + ), f"Disassembled up to {hex(addr)} but slice goes to {hex(self.slice.stop)}" + + # TODO not a good name def dump_section(self): """Writes the C file to output.""" addr = self.slice.start functions = [] - for sym in self.own_symbols: - assert ( - sym.addr == addr - ), f"Currently at {hex(addr)} but next symbol is at {hex(sym.addr)}" + + for sym in self.__symbols(): func_body = self.disassemble_function(sym) functions.append( { @@ -129,10 +144,7 @@ def dump_section(self): "inline_asm": func_body, } ) - addr += sym.size - assert ( - addr == self.slice.stop - ), f"Disassembled up to {hex(addr)} but slice goes to {hex(self.slice.stop)}" + # Sort extern functions. self.extern_functions.sort(key=lambda sym: sym.addr) # Write out to C file. @@ -142,46 +154,53 @@ def dump_section(self): ) stream.dump(self.output) - def disassemble_function(self, sym): - """Generates the inline assembly function body as a stream of lines.""" - assert isinstance(sym.size, int) - assert sym.size > 0 - # Grab the instructions. - data_start = sym.addr - self.slice.start - data_stop = data_start + sym.size - data = self.data[data_start:data_stop] - insns = list(disasm_iter(data, sym.addr)) - # Walk instructions to collect: - # jumps inside functions (labels) - # long jumps to other functions (extern symbol references) - labels = set() + def __jumps_of(self, insns): for ins in insns: branch_info = ins.disassemble_branch() - if branch_info is not None: - _, addr = branch_info - # If target address is within symbol, we have a label. - if sym.addr <= addr < sym.addr + sym.size: - labels.add(addr) - # And extern function declaration if: - # 1. Target address is not within translation unit - # 2. We haven't declared it previously - elif (addr not in self.own_symbols) and ( - addr not in self.extern_functions_seen - ): - # If it's a known symbol, use its name. - if addr in self.symbols: - self.extern_functions_seen.add(addr) - self.extern_functions.append(self.symbols[addr]) - # If the target address is unknown we still need to create a symbol. - else: - self.extern_functions_seen.add(addr) - ext_sym = Symbol(addr, "unk_%08x" % addr) - self.symbols.put(ext_sym) - self.extern_functions.append(ext_sym) - - sorted_labels = list(sorted(labels)) - # Disassemble instructions. - func_body = [] + if branch_info is None: + continue + + yield branch_info + + def __name_addr(self, addr): + # If it's a known symbol, use its name. + if addr in self.symbols: + return self.symbols[addr] + + # If the target address is unknown we still need to create a symbol. + ext_sym = Symbol(addr, "unk_%08x" % addr) + self.symbols.put(ext_sym) + return ext_sym + + def __analyze_jumps_addr(self, addr, labels): + # If target address is within symbol, we have a label. + if addr_in_sym(addr, sym): + labels.add(addr) + return + + # This is within the translation unit + if addr in self.own_symbols: + return + + # Check if we haven't declared it previously + if addr not in self.extern_functions_seen: + self.extern_functions_seen.add(addr) + + sym_name = self.__name_addr(addr) + self.extern_functions.append(sym_name) + + # Walk instructions to collect: + # jumps inside functions (labels) + # long jumps to other functions (extern symbol references) + def __analyze_jumps(self, insns): + labels = set() + for _, addr in self.__jumps_of(insns): + self.__analyze_jumps_addr(addr, labels) + + return labels + + # Disassemble instructions. + def __disasm_instructions(self, labels, sorted_labels): for ins in insns: # TODO is there a better way to specialize a Python object? ins = InlineInstruction( @@ -190,9 +209,26 @@ def disassemble_function(self, sym): # Insert next label if address matches. if len(sorted_labels) > 0 and sorted_labels[0] <= ins.address: label = sorted_labels.pop(0) - func_body.append(f"{label_name(label)}:") + yield f"{label_name(label)}:" + # Actual instruction. - func_body.append(f" {ins.disassemble()};") + yield f" {ins.disassemble()};" + + + def disassemble_function(self, sym): + """Generates the inline assembly function body as a stream of lines.""" + assert isinstance(sym.size, int) + assert sym.size > 0 + # Grab the instructions. + data_start = sym.addr - self.slice.start + data_stop = data_start + sym.size + data = self.data[data_start:data_stop] + insns = list(disasm_iter(data, sym.addr)) + + labels = self.__analyze_jumps() + sorted_labels = list(sorted(labels)) + func_body = list(self.__disasm_instructions(labels, sorted_labels)) + return func_body @@ -221,33 +257,56 @@ def __init__( self.dol_asm_sources = set() self.dol_decomp_sources = set() - def run(self): - """Runs ASM generation for main.dol.""" - for section in DOL_SECTIONS: - self.__process_section(section) - # Delete stale ASM files. + # Delete stale ASM files. + def __prune_asm(self): for path in self.dol_asm_dir.iterdir(): if path.suffix != ".s": continue if path.stem not in self.dol_asm_sources: os.remove(path) - # Give all ASM slices a name. This makes the slice table unusable. + + def __stem_for_asm_slice(self, _slice): + if _slice.has_name(): + return Path(Path(_slice.name).name) + + return get_asm_path(Path("dol"), _slice) + + def __outpath_of_asm_slice(self, _slice): + # Wrong term maybe + stem = self.__stem_for_asm_slice(_slice) + out_path = Path("out") / stem.with_suffix(".o") + return str(out_path) + + # Iterator of all non-empty sections + def __slice_sections(self): for _slice in self.slices: if _slice.section is None: continue - out_path = None - if not _slice.has_name(): - out_path = get_asm_path(Path("dol"), _slice) - else: - out_path = Path(Path(_slice.name).name) - out_path = Path("out") / out_path.with_suffix(".o") - _slice.name = str(out_path) - # Write list of objects for linker. + + yield _slice + + # Give all ASM slices a name. This makes the slice table unusable. + def __name_asm_slices(self): + for _slice in self.__slice_sections(): + _slice.name = self.__outpath_of_asm_slice(_slice) + + # Write list of objects for linker. + def __write_objlist(self): object_names = self.slices.object_slices().objects.keys() with open(self.pack_dir / "dol_objects.txt", "w") as file: for name in object_names: print(Path(name), file=file) + def run(self): + """Runs ASM generation for main.dol.""" + for section in DOL_SECTIONS: + self.__process_section(section) + + self.__prune_asm() + self.__name_asm_slices() + self.__write_objlist() + + def __process_section(self, section: Section): """Processes a program section and all its slices.""" subtable = self.slices.slice(section.start, section.stop) @@ -255,15 +314,24 @@ def __process_section(self, section: Section): for _slice in subtable: self.__process_slice(section, _slice) + # If the slice should be output as a .s file + def __slice_dest_asm(self, _slice): + return not _slice.has_name() + + # If the slice should be output as a .c file + def __slice_dest_c(self, _slice): + return _slice.has_name() + def __process_slice(self, section: Section, _slice: Slice): """Process a slice in slices.csv or a gap.""" print(f" {_slice}") - # Generate ASM file. - if not _slice.has_name(): + + if self.__slice_dest_asm(_slice): self.__gen_asm(section, _slice) - # Generate C code file. + return + # TODO Ideally this would work on the notion of objects instead of slices. - else: + if self.__slice_dest_c(_slice): source_path = Path(_slice.name) if source_path.stem not in self.dol_decomp_sources: self.dol_decomp_sources.add(source_path.stem) @@ -276,6 +344,7 @@ def __gen_c(self, _slice: Slice): c_path = Path(_slice.name) if c_path.exists(): return + c_path.parent.mkdir(parents=True, exist_ok=True) print(f" => {_slice.name}") data = self.dol.virtual_read(_slice.start, len(_slice)) @@ -376,6 +445,40 @@ def __gen_asm(self, section: Section, _slice: Slice): gen = AsmGenerator(data, _slice, SymbolsList(), asm_file) gen.dump_section() +def __read_symbol_map(symbols_path): + symbols = SymbolsList() + + with open(symbols_path, "r") as file: + symbols.read_from(file) + + return symbols + + raise RuntimeError("Cannot find symbols") + +def __read_dol(dol_path): + with open(dol_path, "rb") as file: + dol = DolBinary(file) + + return dol + + raise RuntimeError("Cannot find DOL") + +# Map out slices in DOL. +def __read_enabled_slices(dol, slices_path): + dol_slices = SliceTable(dol.start, dol.stop) + with open(slices_path) as file: + dol_slices.read_from(file) + + return dol_slices.filter(SliceTable.ONLY_ENABLED) + + raise RuntimeError("Cannot find dol_slices.csv") + +def __read_rel(rel_path): + with open(rel_path, "rb") as file: + return Rel(file) + + raise RuntimeError("Cannot find StaticR.rel") + def main(): parser = argparse.ArgumentParser( description="Generate ASM blobs and linker object lists." @@ -394,21 +497,11 @@ def main(): args = parser.parse_args() args.asm_dir.mkdir(exist_ok=True) - # Read symbol map. - symbols = SymbolsList() - symbols_path = args.pack_dir / "symbols.txt" - with open(symbols_path, "r") as file: - symbols.read_from(file) + symbols = __read_symbol_map(args.pack_dir / "symbols.txt") - # Read DOL. - dol_path = args.binary_dir / "main.dol" - with open(dol_path, "rb") as file: - dol = DolBinary(file) - # Map out slices in DOL. - dol_slices = SliceTable(dol.start, dol.stop) - with open(args.pack_dir / "dol_slices.csv") as file: - dol_slices.read_from(file) - dol_slices = dol_slices.filter(SliceTable.ONLY_ENABLED) + dol = __read_dol(args.binary_dir / "main.dol") + dol_slices = __read_enabled_slices(dol, args.pack_dir / "dol_slices.csv") + # Disassemble DOL sections. dol_asm_dir = args.asm_dir / "dol" dol_asm_dir.mkdir(exist_ok=True) @@ -417,10 +510,7 @@ def main(): ) dol_gen.run() - # Read REL. - rel_path = args.binary_dir / "StaticR.rel" - with open(rel_path, "rb") as file: - rel = Rel(file) + rel = __read_rel(args.binary_dir / "StaticR.rel") # Dump StaticR.rel segments. rel_bin_dir = args.binary_dir / "rel" dump_staticr(rel, rel_bin_dir) From 9161b57a27798c5f06d7f52cfb972662fe7055a6 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 21 Jul 2021 19:56:44 -0600 Subject: [PATCH 103/477] :bug: Fix rel disasm + fail CI tests when mismatch --- mkwutil/rel.py | 8 +++++++- mkwutil/verify_main_dol.py | 3 +++ mkwutil/verify_staticr_rel.py | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/mkwutil/rel.py b/mkwutil/rel.py index a93d769e3..347f7b289 100644 --- a/mkwutil/rel.py +++ b/mkwutil/rel.py @@ -157,8 +157,14 @@ def virtual_read(self, vaddr: int, size: int, sections, section_idx) -> Optional section_idx = section_idx[section_virtual_idx] # Calculate addres. relative_addr = vaddr - section_virtual.start - return self.section_info[section_idx].data[relative_addr:relative_addr+size] + result = self.section_info[section_idx].data[relative_addr:relative_addr+size] + if len(result) == size: + return result + assert size >= len(result) + print("Warning: Size %s exceeds section size %s" % (size, len(result))) + + return result + bytes(size - len(result)) class RelHeader: """Holds a .rel header.""" diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py index 7ab2a5730..b6179fa3d 100644 --- a/mkwutil/verify_main_dol.py +++ b/mkwutil/verify_main_dol.py @@ -6,6 +6,7 @@ from colorama import Fore, Style import hashlib from pathlib import Path +import sys from .dol import DolBinary @@ -87,6 +88,8 @@ def verify_dol(reference: Path, target: Path): Fore.RED + Style.BRIGHT + "[DOL] Oof: Output doesn't match." + Style.RESET_ALL ) + sys.exit(1) + if __name__ == "__main__": parser = argparse.ArgumentParser() diff --git a/mkwutil/verify_staticr_rel.py b/mkwutil/verify_staticr_rel.py index 51ac80297..bda1d376e 100644 --- a/mkwutil/verify_staticr_rel.py +++ b/mkwutil/verify_staticr_rel.py @@ -6,6 +6,7 @@ from colorama import Fore, Style import hashlib from pathlib import Path +import sys def verify_rel(target): @@ -22,6 +23,8 @@ def verify_rel(target): ) return + + want_len = 4903876 if len(content) != want_len: print( @@ -34,6 +37,8 @@ def verify_rel(target): Fore.RED + Style.BRIGHT + "[REL] Oof: Output doesn't match." + Style.RESET_ALL ) + sys.exit(1) + if __name__ == "__main__": parser = argparse.ArgumentParser() From 87bad80f1f1e2633c678dac9e52666a7efdb06ec Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Thu, 22 Jul 2021 10:01:36 +0200 Subject: [PATCH 104/477] Fix CAsmGenerator (#39) --- mkwutil/gen_asm.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index 39265fc31..50724d9f2 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -114,7 +114,7 @@ def __init__(self, data, _slice, symbols, output): self.extern_functions = list() # Validated iterator of own_symbols - def __symbols(self): + def __symbols(self, addr): for sym in self.own_symbols: assert ( sym.addr == addr @@ -134,7 +134,7 @@ def dump_section(self): addr = self.slice.start functions = [] - for sym in self.__symbols(): + for sym in self.__symbols(addr): func_body = self.disassemble_function(sym) functions.append( { @@ -172,7 +172,7 @@ def __name_addr(self, addr): self.symbols.put(ext_sym) return ext_sym - def __analyze_jumps_addr(self, addr, labels): + def __analyze_jumps_addr(self, sym, addr, labels): # If target address is within symbol, we have a label. if addr_in_sym(addr, sym): labels.add(addr) @@ -192,15 +192,14 @@ def __analyze_jumps_addr(self, addr, labels): # Walk instructions to collect: # jumps inside functions (labels) # long jumps to other functions (extern symbol references) - def __analyze_jumps(self, insns): + def __analyze_jumps(self, sym, insns): labels = set() for _, addr in self.__jumps_of(insns): - self.__analyze_jumps_addr(addr, labels) - + self.__analyze_jumps_addr(sym, addr, labels) return labels # Disassemble instructions. - def __disasm_instructions(self, labels, sorted_labels): + def __disasm_instructions(self, insns, labels, sorted_labels): for ins in insns: # TODO is there a better way to specialize a Python object? ins = InlineInstruction( @@ -225,9 +224,9 @@ def disassemble_function(self, sym): data = self.data[data_start:data_stop] insns = list(disasm_iter(data, sym.addr)) - labels = self.__analyze_jumps() + labels = self.__analyze_jumps(sym, insns) sorted_labels = list(sorted(labels)) - func_body = list(self.__disasm_instructions(labels, sorted_labels)) + func_body = list(self.__disasm_instructions(insns, labels, sorted_labels)) return func_body From bd146341ec173642f7c840ae688ab86a0f6c484a Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Thu, 22 Jul 2021 19:29:16 +0200 Subject: [PATCH 105/477] percent_decompiled.py: Ignore inline asm (#44) * percent_decompiled.py: Ignore inline asm Closes #41 * CI: Don't run percent_decompiled before building * fix type annotation * clang-format * simplify SliceTable.remove() --- .github/workflows/build.yml | 3 - mkwutil/gen_asm/source.c.j2 | 1 + mkwutil/pack_main_dol.py | 9 ++- mkwutil/percent_decompiled.py | 40 ++++++++++- mkwutil/slices.py | 54 +++++++++++++- mkwutil/slices_test.py | 128 +++++++++++++++++++++++++++++++--- pack/dol.lcf.j2 | 5 ++ source/decomp.h | 7 ++ 8 files changed, 228 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 51352c416..5f67216b9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -15,9 +15,6 @@ jobs: - name: Install dependencies run: pip3 install -r requirements.txt - - name: Percent decompiled - run: python3 -m mkwutil.percent_decompiled - - name: Extract tooling env: TOOLS_URL: 'http://163.172.81.216:12369/tools.7z' diff --git a/mkwutil/gen_asm/source.c.j2 b/mkwutil/gen_asm/source.c.j2 index 70615e281..fbc16bdb0 100644 --- a/mkwutil/gen_asm/source.c.j2 +++ b/mkwutil/gen_asm/source.c.j2 @@ -17,6 +17,7 @@ UNKNOWN_FUNCTION({{ function.name }}); // Symbol: {{ function.name }} // Function signature is unknown. // PAL: {{ function.addr | addr }}..{{ (function.addr+function.size) | addr }} +MARK_BINARY_BLOB({{ function.name }}, {{ function.addr | addr }}, {{ (function.addr+function.size) | addr }}); asm UNKNOWN_FUNCTION({{ function.name }}) { nofralloc; {%- for line in function.inline_asm %} diff --git a/mkwutil/pack_main_dol.py b/mkwutil/pack_main_dol.py index bd5ff3335..96a29e143 100644 --- a/mkwutil/pack_main_dol.py +++ b/mkwutil/pack_main_dol.py @@ -8,14 +8,19 @@ from elftools.elf.elffile import ELFFile +def segment_is_dummy(seg): + """Returns whether segment contains dummy info.""" + return seg["p_vaddr"] == 0xA000_0000 # Binary blobs section + + def segment_is_text(seg): """Returns whether segment is executable text.""" - return seg["p_flags"] & P_FLAGS.PF_X == P_FLAGS.PF_X + return not segment_is_dummy(seg) and seg["p_flags"] & P_FLAGS.PF_X == P_FLAGS.PF_X def segment_is_data(seg): """Returns whether segment is data.""" - return not segment_is_text(seg) and not segment_is_bss(seg) + return not segment_is_dummy(seg) and not segment_is_text(seg) and not segment_is_bss(seg) def segment_is_bss(seg): diff --git a/mkwutil/percent_decompiled.py b/mkwutil/percent_decompiled.py index 93470b78b..335803e81 100644 --- a/mkwutil/percent_decompiled.py +++ b/mkwutil/percent_decompiled.py @@ -1,11 +1,14 @@ from copy import copy from pathlib import Path +import re +from typing import Generator +from elftools.elf.elffile import ELFFile, Section as ELFSection import pytablewriter from pytablewriter.style import Style from termcolor import colored -from mkwutil.slices import SliceTable +from mkwutil.slices import Slice, SliceTable from mkwutil.sections import Section, REL_SECTIONS, DOL_SECTIONS, DOL_LIBS @@ -59,12 +62,47 @@ def get_progress(slices, filter): return progress +MATCH_BINARY_BLOB = re.compile( + rb"BINARY_BLOB: (.+)\t(0x[0-9a-f]{8})\t(0x[0-9a-f]{8})\n" +) + + +def __load_binary_blob_slices(elf_file) -> Generator[Slice, None, None]: + elf = ELFFile(elf_file) + blobs: ELFSection = elf.get_section_by_name("binary_blobs") + if not blobs: + return + for match in MATCH_BINARY_BLOB.finditer(blobs.data()): + name = match.group(1).decode("ascii") + start, stop = int(match.group(2), 16), int(match.group(3), 16) + yield Slice(start, stop, name) + + +def load_binary_blob_slices(elf_path: Path) -> list[Slice]: + """Loads all inline assembly slices from the ELF.""" + with open(elf_path, "rb") as file: + return list(__load_binary_blob_slices(file)) + + +def mask_binary_blobs(main_slices: SliceTable, blob_slices: list[Slice]) -> None: + """Inserts a gap into the given slice table for each blob slice. + This is used to remove all inline ASM slices from the slice table, leaving only actual C/C++ code.""" + for _slice in blob_slices: + # print("Ignoring", _slice) + main_slices.remove(_slice=_slice) + # print(main_slices) + + def percent_decompiled(dir="."): dir = Path(dir) matrix = [] # DOL progress. dol_slices = SliceTable.load_dol_slices() + dol_blob_slices = load_binary_blob_slices( + dir / "artifacts" / "target" / "pal" / "main.elf" + ) + mask_binary_blobs(dol_slices, dol_blob_slices) dol_progress = simple_count(dol_slices) dol_total = binary_total(DOL_SECTIONS) matrix.append(analyze("DOL", dol_progress, dol_total)) diff --git a/mkwutil/slices.py b/mkwutil/slices.py index cb86b7102..47c27e713 100644 --- a/mkwutil/slices.py +++ b/mkwutil/slices.py @@ -18,6 +18,8 @@ class Slice: tags: set[str] = field(default_factory=set) def __post_init__(self): + assert isinstance(self.start, int) + assert isinstance(self.stop, int) assert self.start <= self.stop def __contains__(self, key) -> bool: @@ -65,7 +67,7 @@ def __copy__(self) -> "Slice": class ObjectSlices: """ObjectSlices is an immutable view of slices grouped by slice name.""" - objects: dict=field(default_factory=dict) + objects: dict = field(default_factory=dict) def get(self, name: str) -> list[Slice]: assert isinstance(name, str) @@ -79,7 +81,7 @@ class SliceTable: """A list of contiguous slices for a given range.""" def __init__( - self, start=0x8000_0000, stop=math.inf, sections: Optional[list] = None + self, start=0x8000_0000, stop=0x1_0000_0000, sections: Optional[list] = None ): if sections is not None: start = sections[0].start @@ -199,7 +201,7 @@ def add(self, _slice: Slice) -> None: """Adds a slice to the table, changing gaps as appropriate. Panics if a named slice overlaps with the slice to be inserted""" assert isinstance(_slice, Slice) - assert(len(_slice) > 0), str(_slice) + assert len(_slice) > 0, str(_slice) assert _slice in self, "Slice %08x..%08x does not fit in table %08x..%08x" % ( _slice.start, _slice.stop, @@ -227,6 +229,52 @@ def add(self, _slice: Slice) -> None: if _slice.stop < target.stop: self.slices.insert(i + 1, Slice(_slice.stop, target.stop)) + def remove(self, start: int = None, stop: int = None, _slice: Slice = None) -> None: + """Removes the specified range or slice and inserts a gap at its place.""" + if isinstance(_slice, Slice): + return self.__remove(_slice.start, _slice.stop) + else: + return self.__remove(start, stop) + + def __remove(self, start: int, stop: int) -> None: + assert isinstance(start, int) + assert isinstance(stop, int) + start = max(start, self.start) + stop = min(stop, self.stop) + # Search for the beginning of the gap. + begin_slice, i = self.find(start) + # If gap starts within named slice, create new gap slice. + if begin_slice.start < start and begin_slice.has_name(): + old_stop = begin_slice.stop + begin_slice.stop = start + old_slice = begin_slice + begin_slice = Slice(start, old_stop) + self.slices.insert(i + 1, begin_slice) + i += 2 + # If gap stops within named slice, create copy of original slice. + if old_stop > stop: + end_slice = copy(old_slice) + end_slice.start = stop + end_slice.stop = old_stop + self.slices.insert(i, end_slice) + # If bordering with a gap on the left, select gap. + elif begin_slice.start == start and i > 0 and not self.slices[i - 1].has_name(): + begin_slice = self.slices[i - 1] + else: + i += 1 + # Extend slice. + begin_slice.name = None + begin_slice.stop = stop + # Remove overlapping slices. + while i < len(self.slices) and self.slices[i].stop <= stop: + self.slices.pop(i) + if i < len(self.slices): + self.slices[i].start = stop + # If bordering with a right gap, merge gaps. + if i < len(self.slices) and not self.slices[i].has_name(): + begin_slice.stop = self.slices[i].stop + self.slices.pop(i) + def __repr__(self) -> str: return ( "[\n" + "\n".join([" " + repr(_slice) for _slice in self.slices]) + "\n]" diff --git a/mkwutil/slices_test.py b/mkwutil/slices_test.py index b95ef7544..ecc70377a 100644 --- a/mkwutil/slices_test.py +++ b/mkwutil/slices_test.py @@ -28,6 +28,124 @@ def test_slice_table(): Slice(14, 16), ] +def test_slice_table_remove_1(): + # Create a gap spanning some slices. + table = SliceTable(0, 10) + for x in range(2, 10, 2): + table.add(Slice(x, x+2, "slice")) + assert str(table) == """[ + { 00000000..00000002 } + { 00000002..00000004 slice } + { 00000004..00000006 slice } + { 00000006..00000008 slice } + { 00000008..0000000a slice } +]""" + table.remove(3, 7) + assert str(table) == """[ + { 00000000..00000002 } + { 00000002..00000003 slice } + { 00000003..00000007 } + { 00000007..00000008 slice } + { 00000008..0000000a slice } +]""" + + +def test_slice_table_remove_2(): + # Create a gap spanning the entire table. + table = SliceTable(0, 10) + for x in range(0, 10, 2): + table.add(Slice(x, x+2, "slice")) + table.remove(0, 10) + assert str(table) == """[ + { 00000000..0000000a } +]""" + +def test_slice_table_remove_3(): + # Create a gap where a gap already is. + table = SliceTable(0, 6) + for x in range(2, 6, 2): + table.add(Slice(x, x+2, "slice")) + table.remove(0, 2) + assert str(table) == """[ + { 00000000..00000002 } + { 00000002..00000004 slice } + { 00000004..00000006 slice } +]""" + + +def test_slice_table_remove_4(): + # Shoot a hole in a slice. + table = SliceTable(0, 10) + table.add(Slice(0, 10, "slice")) + table.remove(4, 5) + assert str(table) == """[ + { 00000000..00000004 slice } + { 00000004..00000005 } + { 00000005..0000000a slice } +]""" + +def test_slice_table_remove_5(): + # Remove exact slice. + table = SliceTable(0, 6) + for x in range(0, 6, 2): + table.add(Slice(x, x+2, "slice")) + table.remove(2, 4) + assert str(table) == """[ + { 00000000..00000002 slice } + { 00000002..00000004 } + { 00000004..00000006 slice } +]""" + +def test_slice_table_remove_6(): + # Remove slice prefix. + table = SliceTable(0, 6) + for x in range(2, 6, 2): + table.add(Slice(x, x+2, "slice")) + assert str(table) == """[ + { 00000000..00000002 } + { 00000002..00000004 slice } + { 00000004..00000006 slice } +]""" + table.remove(2, 3) + assert str(table) == """[ + { 00000000..00000003 } + { 00000003..00000004 slice } + { 00000004..00000006 slice } +]""" + +def test_slice_table_remove_7(): + # Remove slice prefix. + table = SliceTable(0, 6) + for x in range(2, 6, 2): + table.add(Slice(x, x+2, "slice")) + assert str(table) == """[ + { 00000000..00000002 } + { 00000002..00000004 slice } + { 00000004..00000006 slice } +]""" + table.remove(0, 3) + assert str(table) == """[ + { 00000000..00000003 } + { 00000003..00000004 slice } + { 00000004..00000006 slice } +]""" + +def test_slice_table_remove_8(): + # Remove slice suffix. + table = SliceTable(0, 6) + for x in range(0, 4, 2): + table.add(Slice(x, x+2, "slice")) + assert str(table) == """[ + { 00000000..00000002 slice } + { 00000002..00000004 slice } + { 00000004..00000006 } +]""" + table.remove(3, 4) + assert str(table) == """[ + { 00000000..00000002 slice } + { 00000002..00000003 slice } + { 00000003..00000006 } +]""" def test_dol_slices(): table = SliceTable.load_dol_slices() @@ -42,13 +160,3 @@ def test_rel_slices(): assert isinstance(table, SliceTable) objs = table.object_slices() assert isinstance(objs, ObjectSlices) - - -def test_object_slices_sort_names(): - objects = ObjectSlices() - objects.insert(Slice(name="s0", start=0x01, stop=0x02, section="text")) - objects.insert(Slice(name="s2", start=0x06, stop=0x07, section="data")) - objects.insert(Slice(name="s3", start=0x03, stop=0x04, section="text")) - objects.insert(Slice(name="s1", start=0x02, stop=0x03, section="text")) - objects.insert(Slice(name="s1", start=0x05, stop=0x06, section="data")) - assert objects.sorted_names(order=["text", "data"]) == ["s0", "s1", "s2", "s3"] diff --git a/pack/dol.lcf.j2 b/pack/dol.lcf.j2 index 8d513beec..422b26b8e 100644 --- a/pack/dol.lcf.j2 +++ b/pack/dol.lcf.j2 @@ -1,6 +1,8 @@ ENTRY(__start) MEMORY { text : origin = 0x80004000 +// Dummy address +binary_blobs : origin = 0xA0000000 } SECTIONS { GROUP:{ @@ -19,6 +21,9 @@ extabindex_ ALIGN(0x20):{} .sbss2 ALIGN(0x20):{} .stack ALIGN(0x100):{} } > text +GROUP:{ +binary_blobs ALIGN(0x20):{} +} > binary_blobs _stack_addr = (_f_sbss2 + SIZEOF(.sbss2) + 65536 + 0x7) & ~0x7; _stack_end = _f_sbss2 + SIZEOF(.sbss2); diff --git a/source/decomp.h b/source/decomp.h index 75984d38e..3e46a29e3 100644 --- a/source/decomp.h +++ b/source/decomp.h @@ -1,3 +1,10 @@ #pragma once #define UNKNOWN_FUNCTION(name) void name(void) + +#pragma section "binary_blobs" +#define SECTION_BINARY_BLOBS __declspec(section "binary_blobs") +#define MARK_BINARY_BLOB(name, start, stop) \ + SECTION_BINARY_BLOBS static const char MARK_BINARY_BLOB_##name[] = \ + "BINARY_BLOB: " #name "\t" #start "\t" #stop "\n" \ + __attribute__((force_export)) From b162b466d239c16031a2626764a99de1809e723d Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Thu, 22 Jul 2021 19:56:55 +0200 Subject: [PATCH 106/477] CI: Run pytest (#45) --- .github/workflows/test.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..31682ccc2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,19 @@ +--- +on: [push, pull_request] +name: Test +jobs: + build: + runs-on: windows-2019 + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + + - name: Install dependencies + run: pip3 install -r requirements.txt + + - name: Run tests + run: pytest -vv From 9c70276a42345754ceef1105a89a72711a5f44c0 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Thu, 22 Jul 2021 20:03:08 +0200 Subject: [PATCH 107/477] Inline ASM: RVL/NAND (#40) Closes #34 --- mkwutil/sources.py | 4 + pack/dol_objects.txt | 4 +- pack/dol_slices.csv | 1 + source/rvl/nand/nand.c | 5218 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 5226 insertions(+), 1 deletion(-) create mode 100644 source/rvl/nand/nand.c diff --git a/mkwutil/sources.py b/mkwutil/sources.py index 066b20468..e68854942 100644 --- a/mkwutil/sources.py +++ b/mkwutil/sources.py @@ -45,6 +45,9 @@ class Source: Source(src="source/rvl/mtx/rvlVec.c", cc='4199_60831', opts=RVL_OPTS), Source(src="source/rvl/mtx/rvlQuat.c", cc='4199_60831', opts=RVL_OPTS), ] +SOURCES_RVL_NAND = [ + Source(src="source/rvl/nand/nand.c", cc='4199_60831', opts=RVL_OPTS) +] SOURCES_RVL_PAD = [ Source(src="source/rvl/pad/rvlPadClamp.c", cc='4199_60831', opts=RVL_OPTS), Source(src="source/rvl/pad/rvlPad.c", cc='4199_60831', opts=RVL_OPTS + " -inline on,noauto "), @@ -149,6 +152,7 @@ class Source: SOURCES_RVL_ARC, SOURCES_RVL_MEM, SOURCES_RVL_MTX, + SOURCES_RVL_NAND, SOURCES_RVL_PAD, SOURCES_RVL_SI, SOURCES_RVL_TPL, diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 93f46d735..1368454c6 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -177,7 +177,9 @@ out\rvlVec.o out\dol\sdata_80385a10_80385b08.o out\dol\sdata2_803888b4_803888b8.o out\rvlQuat.o -out\dol\text_8019b178_801ae5d8.o +out\dol\text_8019b178_8019b314.o +out\nand.o +out\dol\text_8019f1a8_801ae5d8.o out\dol\rodata_80252c84_80252dd0.o out\dol\sdata2_803888cc_80388930.o out\rvlPadClamp.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 1adc3a973..57c10aa6e 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -67,6 +67,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, 1,,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, 1,,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b178,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888cc,, +1,,source/rvl/nand/nand.c,,,,,,,0x8019b314,0x8019f1a8,,,,,,,,,,,,,,,,,, 1,,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, 1,,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, 1,,source/rvl/si/siBios.c,,,,,,,,,,,,,,,,,,,,,0x803869f0,0x803869f8,,,, diff --git a/source/rvl/nand/nand.c b/source/rvl/nand/nand.c new file mode 100644 index 000000000..f5b5ae69b --- /dev/null +++ b/source/rvl/nand/nand.c @@ -0,0 +1,5218 @@ +#include "decomp.h" + +// Extern function references. +// PAL: 0x80006038 +extern UNKNOWN_FUNCTION(memset); +// PAL: 0x8001182c +extern UNKNOWN_FUNCTION(vsnprintf); +// PAL: 0x80011938 +extern UNKNOWN_FUNCTION(snprintf); +// PAL: 0x80011a2c +extern UNKNOWN_FUNCTION(sprintf); +// PAL: 0x80013120 +extern UNKNOWN_FUNCTION(strcpy); +// PAL: 0x800131e0 +extern UNKNOWN_FUNCTION(strncpy); +// PAL: 0x80013224 +extern UNKNOWN_FUNCTION(strcat); +// PAL: 0x8001329c +extern UNKNOWN_FUNCTION(strcmp); +// PAL: 0x800133b8 +extern UNKNOWN_FUNCTION(strncmp); +// PAL: 0x8001543c +extern UNKNOWN_FUNCTION(atoi); +// PAL: 0x800179d0 +extern UNKNOWN_FUNCTION(wcsncpy); +// PAL: 0x80017a14 +extern UNKNOWN_FUNCTION(wcscmp); +// PAL: 0x80021254 +extern UNKNOWN_FUNCTION(strlen); +// PAL: 0x80021590 +extern UNKNOWN_FUNCTION(_savegpr_23); +// PAL: 0x80021598 +extern UNKNOWN_FUNCTION(_savegpr_25); +// PAL: 0x8002159c +extern UNKNOWN_FUNCTION(_savegpr_26); +// PAL: 0x800215a0 +extern UNKNOWN_FUNCTION(_savegpr_27); +// PAL: 0x800215dc +extern UNKNOWN_FUNCTION(_restgpr_23); +// PAL: 0x800215e4 +extern UNKNOWN_FUNCTION(_restgpr_25); +// PAL: 0x800215e8 +extern UNKNOWN_FUNCTION(_restgpr_26); +// PAL: 0x800215ec +extern UNKNOWN_FUNCTION(_restgpr_27); +// PAL: 0x800216f0 +extern UNKNOWN_FUNCTION(__div2i); +// PAL: 0x801671d0 +extern UNKNOWN_FUNCTION(unk_801671d0); +// PAL: 0x80167224 +extern UNKNOWN_FUNCTION(unk_80167224); +// PAL: 0x80167904 +extern UNKNOWN_FUNCTION(unk_80167904); +// PAL: 0x8016799c +extern UNKNOWN_FUNCTION(unk_8016799c); +// PAL: 0x80169bcc +extern UNKNOWN_FUNCTION(unk_80169bcc); +// PAL: 0x80169e74 +extern UNKNOWN_FUNCTION(unk_80169e74); +// PAL: 0x80169f68 +extern UNKNOWN_FUNCTION(unk_80169f68); +// PAL: 0x8016a05c +extern UNKNOWN_FUNCTION(unk_8016a05c); +// PAL: 0x8016a1b0 +extern UNKNOWN_FUNCTION(unk_8016a1b0); +// PAL: 0x8016a2f8 +extern UNKNOWN_FUNCTION(unk_8016a2f8); +// PAL: 0x8016a3fc +extern UNKNOWN_FUNCTION(unk_8016a3fc); +// PAL: 0x8016a500 +extern UNKNOWN_FUNCTION(unk_8016a500); +// PAL: 0x8016a658 +extern UNKNOWN_FUNCTION(unk_8016a658); +// PAL: 0x8016a78c +extern UNKNOWN_FUNCTION(unk_8016a78c); +// PAL: 0x8016a864 +extern UNKNOWN_FUNCTION(unk_8016a864); +// PAL: 0x8016a934 +extern UNKNOWN_FUNCTION(unk_8016a934); +// PAL: 0x8016aa38 +extern UNKNOWN_FUNCTION(unk_8016aa38); +// PAL: 0x8016ab3c +extern UNKNOWN_FUNCTION(unk_8016ab3c); +// PAL: 0x8016ac74 +extern UNKNOWN_FUNCTION(unk_8016ac74); +// PAL: 0x8016ad68 +extern UNKNOWN_FUNCTION(unk_8016ad68); +// PAL: 0x8016ae5c +extern UNKNOWN_FUNCTION(unk_8016ae5c); +// PAL: 0x8016af24 +extern UNKNOWN_FUNCTION(unk_8016af24); +// PAL: 0x8016afdc +extern UNKNOWN_FUNCTION(unk_8016afdc); +// PAL: 0x8016b0ac +extern UNKNOWN_FUNCTION(unk_8016b0ac); +// PAL: 0x8016b16c +extern UNKNOWN_FUNCTION(unk_8016b16c); +// PAL: 0x8016b170 +extern UNKNOWN_FUNCTION(unk_8016b170); +// PAL: 0x8016b1fc +extern UNKNOWN_FUNCTION(unk_8016b1fc); +// PAL: 0x8016b21c +extern UNKNOWN_FUNCTION(unk_8016b21c); +// PAL: 0x8016b2c0 +extern UNKNOWN_FUNCTION(unk_8016b2c0); +// PAL: 0x8016b2e0 +extern UNKNOWN_FUNCTION(unk_8016b2e0); +// PAL: 0x8016b384 +extern UNKNOWN_FUNCTION(unk_8016b384); +// PAL: 0x8016b388 +extern UNKNOWN_FUNCTION(unk_8016b388); +// PAL: 0x8016b40c +extern UNKNOWN_FUNCTION(unk_8016b40c); +// PAL: 0x801a0504 +extern UNKNOWN_FUNCTION(OSRegisterVersion); +// PAL: 0x801a25d0 +extern UNKNOWN_FUNCTION(OSReport); +// PAL: 0x801a65ac +extern UNKNOWN_FUNCTION(OSDisableInterrupts); +// PAL: 0x801a65d4 +extern UNKNOWN_FUNCTION(OSRestoreInterrupts); +// PAL: 0x801a8238 +extern UNKNOWN_FUNCTION(OSRegisterResetFunction); +// PAL: 0x801aad5c +extern UNKNOWN_FUNCTION(OSGetTime); +// PAL: 0x801aafa8 +extern UNKNOWN_FUNCTION(OSTicksToCalendarTime); + +// Function declarations. +UNKNOWN_FUNCTION(nandCreate); +UNKNOWN_FUNCTION(NANDCreate); +UNKNOWN_FUNCTION(NANDPrivateCreate); +UNKNOWN_FUNCTION(NANDPrivateCreateAsync); +UNKNOWN_FUNCTION(NANDDelete); +UNKNOWN_FUNCTION(NANDPrivateDelete); +UNKNOWN_FUNCTION(NANDPrivateDeleteAsync); +UNKNOWN_FUNCTION(NANDRead); +UNKNOWN_FUNCTION(NANDReadAsync); +UNKNOWN_FUNCTION(NANDWrite); +UNKNOWN_FUNCTION(NANDWriteAsync); +UNKNOWN_FUNCTION(NANDSeek); +UNKNOWN_FUNCTION(NANDSeekAsync); +UNKNOWN_FUNCTION(nandCreateDir); +UNKNOWN_FUNCTION(NANDCreateDir); +UNKNOWN_FUNCTION(NANDPrivateCreateDir); +UNKNOWN_FUNCTION(NANDPrivateCreateDirAsync); +UNKNOWN_FUNCTION(nandMove); +UNKNOWN_FUNCTION(NANDMove); +UNKNOWN_FUNCTION(NANDGetLength); +UNKNOWN_FUNCTION(nandGetFileStatusAsyncCallback); +UNKNOWN_FUNCTION(NANDGetLengthAsync); +UNKNOWN_FUNCTION(nandComposePerm); +UNKNOWN_FUNCTION(nandSplitPerm); +UNKNOWN_FUNCTION(nandGetStatus); +UNKNOWN_FUNCTION(nandGetStatusCallback); +UNKNOWN_FUNCTION(NANDGetStatus); +UNKNOWN_FUNCTION(NANDPrivateGetStatus); +UNKNOWN_FUNCTION(NANDPrivateGetStatusAsync); +UNKNOWN_FUNCTION(nandSetStatus); +UNKNOWN_FUNCTION(NANDSetStatus); +UNKNOWN_FUNCTION(NANDPrivateSetStatus); +UNKNOWN_FUNCTION(NANDSetUserData); +UNKNOWN_FUNCTION(NANDGetUserData); +UNKNOWN_FUNCTION(nandOpen); +UNKNOWN_FUNCTION(NANDOpen); +UNKNOWN_FUNCTION(NANDPrivateOpen); +UNKNOWN_FUNCTION(NANDOpenAsync); +UNKNOWN_FUNCTION(NANDPrivateOpenAsync); +UNKNOWN_FUNCTION(nandOpenCallback); +UNKNOWN_FUNCTION(NANDClose); +UNKNOWN_FUNCTION(NANDCloseAsync); +UNKNOWN_FUNCTION(NANDSafeOpen); +UNKNOWN_FUNCTION(nandSafeOpen); +UNKNOWN_FUNCTION(NANDSafeClose); +UNKNOWN_FUNCTION(NANDPrivateSafeOpenAsync); +UNKNOWN_FUNCTION(nandSafeOpenAsync); +UNKNOWN_FUNCTION(nandSafeOpenCallback); +UNKNOWN_FUNCTION(nandReadOpenCallback); +UNKNOWN_FUNCTION(NANDSafeCloseAsync); +UNKNOWN_FUNCTION(nandSafeCloseCallback); +UNKNOWN_FUNCTION(nandReadCloseCallback); +UNKNOWN_FUNCTION(nandCloseCallback); +UNKNOWN_FUNCTION(nandRemoveTailToken); +UNKNOWN_FUNCTION(nandGetHeadToken); +UNKNOWN_FUNCTION(nandGetRelativeName); +UNKNOWN_FUNCTION(nandConvertPath); +UNKNOWN_FUNCTION(nandIsPrivatePath); +UNKNOWN_FUNCTION(nandIsUnderPrivatePath); +UNKNOWN_FUNCTION(nandIsInitialized); +UNKNOWN_FUNCTION(nandReportErrorCode); +UNKNOWN_FUNCTION(nandConvertErrorCode); +UNKNOWN_FUNCTION(nandGenerateAbsPath); +UNKNOWN_FUNCTION(nandGetParentDirectory); +UNKNOWN_FUNCTION(NANDInit); +UNKNOWN_FUNCTION(nandOnShutdown); +UNKNOWN_FUNCTION(NANDGetCurrentDir); +UNKNOWN_FUNCTION(NANDGetHomeDir); +UNKNOWN_FUNCTION(nandCallback); +UNKNOWN_FUNCTION(nandGetType); +UNKNOWN_FUNCTION(NANDGetType); +UNKNOWN_FUNCTION(NANDPrivateGetTypeAsync); +UNKNOWN_FUNCTION(nandGetTypeCallback); +UNKNOWN_FUNCTION(nandGetHomeDir); +UNKNOWN_FUNCTION(NANDInitBanner); +UNKNOWN_FUNCTION(NANDSecretGetUsage); +UNKNOWN_FUNCTION(nandCalcUsage); +UNKNOWN_FUNCTION(NANDCheck); +UNKNOWN_FUNCTION(reserveFileDescriptor); +UNKNOWN_FUNCTION(NANDLoggingAddMessageAsync); +UNKNOWN_FUNCTION(asyncRoutine); + +// Symbol: nandCreate +// Function signature is unknown. +// PAL: 0x8019b314..0x8019b43c +MARK_BINARY_BLOB(nandCreate, 0x8019b314, 0x8019b43c); +asm UNKNOWN_FUNCTION(nandCreate) { + nofralloc; + stwu r1, -0x70(r1); + mflr r0; + stw r0, 0x74(r1); + addi r11, r1, 0x70; + bl _savegpr_27; + li r0, 0; + mr r27, r4; + stw r0, 0x18(r1); + mr r4, r3; + mr r28, r5; + mr r29, r6; + stw r0, 0x1c(r1); + mr r30, r7; + mr r31, r8; + addi r3, r1, 0x18; + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + stw r0, 0x48(r1); + stw r0, 0x4c(r1); + stw r0, 0x50(r1); + stw r0, 0x54(r1); + stw r0, 0x10(r1); + stw r0, 0xc(r1); + stw r0, 8(r1); + bl nandGenerateAbsPath; + cmpwi r31, 0; + bne lbl_8019b3b8; + addi r3, r1, 0x18; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019b3b8; + li r3, -102; + b lbl_8019b424; +lbl_8019b3b8: + rlwinm. r0, r27, 0, 0x1b, 0x1b; + bne lbl_8019b3c8; + li r3, -101; + b lbl_8019b424; +lbl_8019b3c8: + mr r3, r27; + addi r4, r1, 0x10; + addi r5, r1, 0xc; + addi r6, r1, 8; + bl nandSplitPerm; + cmpwi r30, 0; + beq lbl_8019b40c; + lis r8, 0x801a; + lwz r5, 0x10(r1); + lwz r6, 0xc(r1); + mr r4, r28; + lwz r7, 8(r1); + mr r9, r29; + addi r3, r1, 0x18; + addi r8, r8, -7072; + bl unk_8016ad68; + b lbl_8019b424; +lbl_8019b40c: + lwz r5, 0x10(r1); + mr r4, r28; + lwz r6, 0xc(r1); + addi r3, r1, 0x18; + lwz r7, 8(r1); + bl unk_8016ac74; +lbl_8019b424: + addi r11, r1, 0x70; + bl _restgpr_27; + lwz r0, 0x74(r1); + mtlr r0; + addi r1, r1, 0x70; + blr; +} + +// Symbol: NANDCreate +// Function signature is unknown. +// PAL: 0x8019b43c..0x8019b4b0 +MARK_BINARY_BLOB(NANDCreate, 0x8019b43c, 0x8019b4b0); +asm UNKNOWN_FUNCTION(NANDCreate) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019b490; + mr r3, r29; + mr r4, r30; + mr r5, r31; + li r6, 0; + li r7, 0; + li r8, 0; + bl nandCreate; + bl nandConvertErrorCode; + b lbl_8019b494; +lbl_8019b490: + li r3, -128; +lbl_8019b494: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDPrivateCreate +// Function signature is unknown. +// PAL: 0x8019b4b0..0x8019b524 +MARK_BINARY_BLOB(NANDPrivateCreate, 0x8019b4b0, 0x8019b524); +asm UNKNOWN_FUNCTION(NANDPrivateCreate) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019b504; + mr r3, r29; + mr r4, r30; + mr r5, r31; + li r6, 0; + li r7, 0; + li r8, 1; + bl nandCreate; + bl nandConvertErrorCode; + b lbl_8019b508; +lbl_8019b504: + li r3, -128; +lbl_8019b508: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDPrivateCreateAsync +// Function signature is unknown. +// PAL: 0x8019b524..0x8019b59c +MARK_BINARY_BLOB(NANDPrivateCreateAsync, 0x8019b524, 0x8019b59c); +asm UNKNOWN_FUNCTION(NANDPrivateCreateAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019b560; + li r3, -128; + b lbl_8019b584; +lbl_8019b560: + stw r30, 4(r31); + mr r3, r27; + mr r4, r28; + mr r5, r29; + mr r6, r31; + li r7, 1; + li r8, 1; + bl nandCreate; + bl nandConvertErrorCode; +lbl_8019b584: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDDelete +// Function signature is unknown. +// PAL: 0x8019b59c..0x8019b64c +MARK_BINARY_BLOB(NANDDelete, 0x8019b59c, 0x8019b64c); +asm UNKNOWN_FUNCTION(NANDDelete) { + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + stw r0, 0x54(r1); + stw r31, 0x4c(r1); + mr r31, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019b5c4; + li r3, -128; + b lbl_8019b638; +lbl_8019b5c4: + li r0, 0; + mr r4, r31; + stw r0, 8(r1); + addi r3, r1, 8; + stw r0, 0xc(r1); + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + bl nandGenerateAbsPath; + addi r3, r1, 8; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019b62c; + li r3, -102; + b lbl_8019b634; +lbl_8019b62c: + addi r3, r1, 8; + bl unk_8016a78c; +lbl_8019b634: + bl nandConvertErrorCode; +lbl_8019b638: + lwz r0, 0x54(r1); + lwz r31, 0x4c(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; +} + +// Symbol: NANDPrivateDelete +// Function signature is unknown. +// PAL: 0x8019b64c..0x8019b6e4 +MARK_BINARY_BLOB(NANDPrivateDelete, 0x8019b64c, 0x8019b6e4); +asm UNKNOWN_FUNCTION(NANDPrivateDelete) { + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + stw r0, 0x54(r1); + stw r31, 0x4c(r1); + mr r31, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019b674; + li r3, -128; + b lbl_8019b6d0; +lbl_8019b674: + li r0, 0; + mr r4, r31; + stw r0, 8(r1); + addi r3, r1, 8; + stw r0, 0xc(r1); + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + bl nandGenerateAbsPath; + addi r3, r1, 8; + bl unk_8016a78c; + bl nandConvertErrorCode; +lbl_8019b6d0: + lwz r0, 0x54(r1); + lwz r31, 0x4c(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; +} + +// Symbol: NANDPrivateDeleteAsync +// Function signature is unknown. +// PAL: 0x8019b6e4..0x8019b7a4 +MARK_BINARY_BLOB(NANDPrivateDeleteAsync, 0x8019b6e4, 0x8019b7a4); +asm UNKNOWN_FUNCTION(NANDPrivateDeleteAsync) { + nofralloc; + stwu r1, -0x60(r1); + mflr r0; + stw r0, 0x64(r1); + stw r31, 0x5c(r1); + mr r31, r5; + stw r30, 0x58(r1); + mr r30, r4; + stw r29, 0x54(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019b71c; + li r3, -128; + b lbl_8019b788; +lbl_8019b71c: + stw r30, 4(r31); + li r0, 0; + mr r4, r29; + addi r3, r1, 8; + stw r0, 8(r1); + stw r0, 0xc(r1); + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + bl nandGenerateAbsPath; + lis r4, 0x801a; + mr r5, r31; + addi r3, r1, 8; + addi r4, r4, -7072; + bl unk_8016a864; + bl nandConvertErrorCode; +lbl_8019b788: + lwz r0, 0x64(r1); + lwz r31, 0x5c(r1); + lwz r30, 0x58(r1); + lwz r29, 0x54(r1); + mtlr r0; + addi r1, r1, 0x60; + blr; +} + +// Symbol: NANDRead +// Function signature is unknown. +// PAL: 0x8019b7a4..0x8019b80c +MARK_BINARY_BLOB(NANDRead, 0x8019b7a4, 0x8019b80c); +asm UNKNOWN_FUNCTION(NANDRead) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019b7ec; + lwz r3, 0(r29); + mr r4, r30; + mr r5, r31; + bl unk_8016b1fc; + bl nandConvertErrorCode; + b lbl_8019b7f0; +lbl_8019b7ec: + li r3, -128; +lbl_8019b7f0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDReadAsync +// Function signature is unknown. +// PAL: 0x8019b80c..0x8019b884 +MARK_BINARY_BLOB(NANDReadAsync, 0x8019b80c, 0x8019b884); +asm UNKNOWN_FUNCTION(NANDReadAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019b848; + li r3, -128; + b lbl_8019b86c; +lbl_8019b848: + stw r30, 4(r31); + lis r6, 0x801a; + mr r4, r28; + mr r5, r29; + lwz r3, 0(r27); + mr r7, r31; + addi r6, r6, -7072; + bl unk_8016b21c; + bl nandConvertErrorCode; +lbl_8019b86c: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDWrite +// Function signature is unknown. +// PAL: 0x8019b884..0x8019b8ec +MARK_BINARY_BLOB(NANDWrite, 0x8019b884, 0x8019b8ec); +asm UNKNOWN_FUNCTION(NANDWrite) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019b8cc; + lwz r3, 0(r29); + mr r4, r30; + mr r5, r31; + bl unk_8016b2c0; + bl nandConvertErrorCode; + b lbl_8019b8d0; +lbl_8019b8cc: + li r3, -128; +lbl_8019b8d0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDWriteAsync +// Function signature is unknown. +// PAL: 0x8019b8ec..0x8019b964 +MARK_BINARY_BLOB(NANDWriteAsync, 0x8019b8ec, 0x8019b964); +asm UNKNOWN_FUNCTION(NANDWriteAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019b928; + li r3, -128; + b lbl_8019b94c; +lbl_8019b928: + stw r30, 4(r31); + lis r6, 0x801a; + mr r4, r28; + mr r5, r29; + lwz r3, 0(r27); + mr r7, r31; + addi r6, r6, -7072; + bl unk_8016b2e0; + bl nandConvertErrorCode; +lbl_8019b94c: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDSeek +// Function signature is unknown. +// PAL: 0x8019b964..0x8019ba04 +MARK_BINARY_BLOB(NANDSeek, 0x8019b964, 0x8019ba04); +asm UNKNOWN_FUNCTION(NANDSeek) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019b99c; + li r3, -128; + b lbl_8019b9e8; +lbl_8019b99c: + cmpwi r31, 1; + lwz r3, 0(r29); + li r5, -1; + beq lbl_8019b9d0; + bge lbl_8019b9bc; + cmpwi r31, 0; + bge lbl_8019b9c8; + b lbl_8019b9dc; +lbl_8019b9bc: + cmpwi r31, 3; + bge lbl_8019b9dc; + b lbl_8019b9d8; +lbl_8019b9c8: + li r5, 0; + b lbl_8019b9dc; +lbl_8019b9d0: + li r5, 1; + b lbl_8019b9dc; +lbl_8019b9d8: + li r5, 2; +lbl_8019b9dc: + mr r4, r30; + bl unk_8016b16c; + bl nandConvertErrorCode; +lbl_8019b9e8: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDSeekAsync +// Function signature is unknown. +// PAL: 0x8019ba04..0x8019bab4 +MARK_BINARY_BLOB(NANDSeekAsync, 0x8019ba04, 0x8019bab4); +asm UNKNOWN_FUNCTION(NANDSeekAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019ba40; + li r3, -128; + b lbl_8019ba9c; +lbl_8019ba40: + stw r30, 4(r31); + cmpwi r29, 1; + li r5, -1; + lwz r3, 0(r27); + beq lbl_8019ba78; + bge lbl_8019ba64; + cmpwi r29, 0; + bge lbl_8019ba70; + b lbl_8019ba84; +lbl_8019ba64: + cmpwi r29, 3; + bge lbl_8019ba84; + b lbl_8019ba80; +lbl_8019ba70: + li r5, 0; + b lbl_8019ba84; +lbl_8019ba78: + li r5, 1; + b lbl_8019ba84; +lbl_8019ba80: + li r5, 2; +lbl_8019ba84: + lis r6, 0x801a; + mr r4, r28; + mr r7, r31; + addi r6, r6, -7072; + bl unk_8016b170; + bl nandConvertErrorCode; +lbl_8019ba9c: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandCreateDir +// Function signature is unknown. +// PAL: 0x8019bab4..0x8019bbe0 +MARK_BINARY_BLOB(nandCreateDir, 0x8019bab4, 0x8019bbe0); +asm UNKNOWN_FUNCTION(nandCreateDir) { + nofralloc; + stwu r1, -0x70(r1); + mflr r0; + stw r0, 0x74(r1); + addi r11, r1, 0x70; + bl _savegpr_27; + li r0, 0; + mr r27, r4; + stw r0, 0x18(r1); + mr r4, r3; + mr r28, r5; + mr r29, r6; + stw r0, 0x1c(r1); + mr r30, r7; + mr r31, r8; + addi r3, r1, 0x18; + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + stw r0, 0x48(r1); + stw r0, 0x4c(r1); + stw r0, 0x50(r1); + stw r0, 0x54(r1); + bl nandGenerateAbsPath; + cmpwi r31, 0; + bne lbl_8019bb4c; + addi r3, r1, 0x18; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019bb4c; + li r3, -102; + b lbl_8019bbc8; +lbl_8019bb4c: + rlwinm. r0, r27, 0, 0x1b, 0x1b; + bne lbl_8019bb5c; + li r3, -101; + b lbl_8019bbc8; +lbl_8019bb5c: + li r0, 0; + mr r3, r27; + stw r0, 0x10(r1); + addi r4, r1, 0x10; + addi r5, r1, 0xc; + addi r6, r1, 8; + stw r0, 0xc(r1); + stw r0, 8(r1); + bl nandSplitPerm; + cmpwi r30, 0; + beq lbl_8019bbb0; + lis r8, 0x801a; + lwz r5, 0x10(r1); + lwz r6, 0xc(r1); + mr r4, r28; + lwz r7, 8(r1); + mr r9, r29; + addi r3, r1, 0x18; + addi r8, r8, -7072; + bl unk_80169f68; + b lbl_8019bbc8; +lbl_8019bbb0: + lwz r5, 0x10(r1); + mr r4, r28; + lwz r6, 0xc(r1); + addi r3, r1, 0x18; + lwz r7, 8(r1); + bl unk_80169e74; +lbl_8019bbc8: + addi r11, r1, 0x70; + bl _restgpr_27; + lwz r0, 0x74(r1); + mtlr r0; + addi r1, r1, 0x70; + blr; +} + +// Symbol: NANDCreateDir +// Function signature is unknown. +// PAL: 0x8019bbe0..0x8019bc54 +MARK_BINARY_BLOB(NANDCreateDir, 0x8019bbe0, 0x8019bc54); +asm UNKNOWN_FUNCTION(NANDCreateDir) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019bc34; + mr r3, r29; + mr r4, r30; + mr r5, r31; + li r6, 0; + li r7, 0; + li r8, 0; + bl nandCreateDir; + bl nandConvertErrorCode; + b lbl_8019bc38; +lbl_8019bc34: + li r3, -128; +lbl_8019bc38: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDPrivateCreateDir +// Function signature is unknown. +// PAL: 0x8019bc54..0x8019bcc8 +MARK_BINARY_BLOB(NANDPrivateCreateDir, 0x8019bc54, 0x8019bcc8); +asm UNKNOWN_FUNCTION(NANDPrivateCreateDir) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019bca8; + mr r3, r29; + mr r4, r30; + mr r5, r31; + li r6, 0; + li r7, 0; + li r8, 1; + bl nandCreateDir; + bl nandConvertErrorCode; + b lbl_8019bcac; +lbl_8019bca8: + li r3, -128; +lbl_8019bcac: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDPrivateCreateDirAsync +// Function signature is unknown. +// PAL: 0x8019bcc8..0x8019bd40 +MARK_BINARY_BLOB(NANDPrivateCreateDirAsync, 0x8019bcc8, 0x8019bd40); +asm UNKNOWN_FUNCTION(NANDPrivateCreateDirAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019bd04; + li r3, -128; + b lbl_8019bd28; +lbl_8019bd04: + stw r30, 4(r31); + mr r3, r27; + mr r4, r28; + mr r5, r29; + mr r6, r31; + li r7, 1; + li r8, 1; + bl nandCreateDir; + bl nandConvertErrorCode; +lbl_8019bd28: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandMove +// Function signature is unknown. +// PAL: 0x8019bd40..0x8019bee8 +MARK_BINARY_BLOB(nandMove, 0x8019bd40, 0x8019bee8); +asm UNKNOWN_FUNCTION(nandMove) { + nofralloc; + stwu r1, -0xb0(r1); + mflr r0; + stw r0, 0xb4(r1); + li r0, 0; + stw r31, 0xac(r1); + mr r31, r7; + stw r30, 0xa8(r1); + mr r30, r6; + stw r29, 0xa4(r1); + mr r29, r5; + stw r28, 0xa0(r1); + mr r28, r4; + mr r4, r3; + addi r3, r1, 0x58; + stw r0, 0x58(r1); + stw r0, 0x5c(r1); + stw r0, 0x60(r1); + stw r0, 0x64(r1); + stw r0, 0x68(r1); + stw r0, 0x6c(r1); + stw r0, 0x70(r1); + stw r0, 0x74(r1); + stw r0, 0x78(r1); + stw r0, 0x7c(r1); + stw r0, 0x80(r1); + stw r0, 0x84(r1); + stw r0, 0x88(r1); + stw r0, 0x8c(r1); + stw r0, 0x90(r1); + stw r0, 0x94(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + stw r0, 0x48(r1); + stw r0, 0x4c(r1); + stw r0, 0x50(r1); + stw r0, 0x54(r1); + stw r0, 8(r1); + stw r0, 0xc(r1); + stw r0, 0x10(r1); + stb r0, 0x14(r1); + bl nandGenerateAbsPath; + addi r3, r1, 8; + addi r4, r1, 0x58; + bl nandGetRelativeName; + mr r4, r28; + addi r3, r1, 0x18; + bl nandGenerateAbsPath; + addi r3, r1, 0x18; + addi r4, r13, -29168; + bl strcmp; + cmpwi r3, 0; + bne lbl_8019be50; + addi r3, r1, 0x18; + addi r5, r1, 8; + addi r4, r13, -29164; + crclr 6; + bl sprintf; + b lbl_8019be68; +lbl_8019be50: + addi r3, r1, 0x18; + addi r4, r13, -29168; + bl strcat; + addi r3, r1, 0x18; + addi r4, r1, 8; + bl strcat; +lbl_8019be68: + cmpwi r31, 0; + bne lbl_8019be98; + addi r3, r1, 0x58; + bl nandIsPrivatePath; + cmpwi r3, 0; + bne lbl_8019be90; + addi r3, r1, 0x18; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019be98; +lbl_8019be90: + li r3, -102; + b lbl_8019bec8; +lbl_8019be98: + cmpwi r30, 0; + beq lbl_8019bebc; + lis r5, 0x801a; + mr r6, r29; + addi r3, r1, 0x58; + addi r4, r1, 0x18; + addi r5, r5, -7072; + bl unk_8016aa38; + b lbl_8019bec8; +lbl_8019bebc: + addi r3, r1, 0x58; + addi r4, r1, 0x18; + bl unk_8016a934; +lbl_8019bec8: + lwz r0, 0xb4(r1); + lwz r31, 0xac(r1); + lwz r30, 0xa8(r1); + lwz r29, 0xa4(r1); + lwz r28, 0xa0(r1); + mtlr r0; + addi r1, r1, 0xb0; + blr; +} + +// Symbol: NANDMove +// Function signature is unknown. +// PAL: 0x8019bee8..0x8019bf4c +MARK_BINARY_BLOB(NANDMove, 0x8019bee8, 0x8019bf4c); +asm UNKNOWN_FUNCTION(NANDMove) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019bf30; + mr r3, r30; + mr r4, r31; + li r5, 0; + li r6, 0; + li r7, 0; + bl nandMove; + bl nandConvertErrorCode; + b lbl_8019bf34; +lbl_8019bf30: + li r3, -128; +lbl_8019bf34: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDGetLength +// Function signature is unknown. +// PAL: 0x8019bf4c..0x8019bfd4 +MARK_BINARY_BLOB(NANDGetLength, 0x8019bf4c, 0x8019bfd4); +asm UNKNOWN_FUNCTION(NANDGetLength) { + nofralloc; + clrlwi r11, r1, 0x1b; + mr r12, r1; + subfic r11, r11, -96; + stwux r1, r1, r11; + mflr r0; + stw r0, 4(r12); + stw r31, -4(r12); + mr r31, r4; + stw r30, -8(r12); + mr r30, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019bf88; + li r3, -128; + b lbl_8019bfb8; +lbl_8019bf88: + lwz r3, 0(r30); + addi r4, r1, 0x20; + bl unk_8016afdc; + cmpwi r3, 0; + bne lbl_8019bfb4; + cmpwi r31, 0; + beq lbl_8019bfb4; + lwz r0, 0x20(r1); + stw r0, 0(r31); + b lbl_8019bfb4; + stw r0, 0(0); +lbl_8019bfb4: + bl nandConvertErrorCode; +lbl_8019bfb8: + lwz r10, 0(r1); + lwz r0, 4(r10); + lwz r31, -4(r10); + lwz r30, -8(r10); + mtlr r0; + mr r1, r10; + blr; +} + +// Symbol: nandGetFileStatusAsyncCallback +// Function signature is unknown. +// PAL: 0x8019bfd4..0x8019c048 +MARK_BINARY_BLOB(nandGetFileStatusAsyncCallback, 0x8019bfd4, 0x8019c048); +asm UNKNOWN_FUNCTION(nandGetFileStatusAsyncCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + addi r0, r4, 0x53; + rlwinm r6, r0, 0, 0, 0x1a; + stw r31, 0xc(r1); + mr r31, r4; + bne lbl_8019c020; + lwz r5, 0x74(r4); + cmpwi r5, 0; + beq lbl_8019c00c; + lwz r0, 0(r6); + stw r0, 0(r5); +lbl_8019c00c: + lwz r4, 0x78(r4); + cmpwi r4, 0; + beq lbl_8019c020; + lwz r0, 4(r6); + stw r0, 0(r4); +lbl_8019c020: + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDGetLengthAsync +// Function signature is unknown. +// PAL: 0x8019c048..0x8019c0d8 +MARK_BINARY_BLOB(NANDGetLengthAsync, 0x8019c048, 0x8019c0d8); +asm UNKNOWN_FUNCTION(NANDGetLengthAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r6; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019c088; + li r3, -128; + b lbl_8019c0b8; +lbl_8019c088: + li r3, 0; + addi r0, r31, 0x53; + stw r30, 4(r31); + lis r5, 0x801a; + mr r6, r31; + rlwinm r4, r0, 0, 0, 0x1a; + stw r29, 0x74(r31); + addi r5, r5, -16428; + stw r3, 0x78(r31); + lwz r3, 0(r28); + bl unk_8016b0ac; + bl nandConvertErrorCode; +lbl_8019c0b8: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandComposePerm +// Function signature is unknown. +// PAL: 0x8019c0d8..0x8019c12c +MARK_BINARY_BLOB(nandComposePerm, 0x8019c0d8, 0x8019c12c); +asm UNKNOWN_FUNCTION(nandComposePerm) { + nofralloc; + clrlwi. r0, r4, 0x1f; + li r7, 0; + beq lbl_8019c0e8; + ori r7, r7, 0x10; +lbl_8019c0e8: + rlwinm. r0, r4, 0, 0x1e, 0x1e; + beq lbl_8019c0f4; + ori r7, r7, 0x20; +lbl_8019c0f4: + clrlwi. r0, r5, 0x1f; + beq lbl_8019c100; + ori r7, r7, 4; +lbl_8019c100: + rlwinm. r0, r5, 0, 0x1e, 0x1e; + beq lbl_8019c10c; + ori r7, r7, 8; +lbl_8019c10c: + clrlwi. r0, r6, 0x1f; + beq lbl_8019c118; + ori r7, r7, 1; +lbl_8019c118: + rlwinm. r0, r6, 0, 0x1e, 0x1e; + beq lbl_8019c124; + ori r7, r7, 2; +lbl_8019c124: + stb r7, 0(r3); + blr; +} + +// Symbol: nandSplitPerm +// Function signature is unknown. +// PAL: 0x8019c12c..0x8019c1b8 +MARK_BINARY_BLOB(nandSplitPerm, 0x8019c12c, 0x8019c1b8); +asm UNKNOWN_FUNCTION(nandSplitPerm) { + nofralloc; + li r7, 0; + rlwinm. r0, r3, 0, 0x1b, 0x1b; + stw r7, 0(r4); + stw r7, 0(r5); + stw r7, 0(r6); + beq lbl_8019c150; + lwz r0, 0(r4); + ori r0, r0, 1; + stw r0, 0(r4); +lbl_8019c150: + rlwinm. r0, r3, 0, 0x1a, 0x1a; + beq lbl_8019c164; + lwz r0, 0(r4); + ori r0, r0, 2; + stw r0, 0(r4); +lbl_8019c164: + rlwinm. r0, r3, 0, 0x1d, 0x1d; + beq lbl_8019c178; + lwz r0, 0(r5); + ori r0, r0, 1; + stw r0, 0(r5); +lbl_8019c178: + rlwinm. r0, r3, 0, 0x1c, 0x1c; + beq lbl_8019c18c; + lwz r0, 0(r5); + ori r0, r0, 2; + stw r0, 0(r5); +lbl_8019c18c: + clrlwi. r0, r3, 0x1f; + beq lbl_8019c1a0; + lwz r0, 0(r6); + ori r0, r0, 1; + stw r0, 0(r6); +lbl_8019c1a0: + rlwinm. r0, r3, 0, 0x1e, 0x1e; + beqlr; + lwz r0, 0(r6); + ori r0, r0, 2; + stw r0, 0(r6); + blr; +} + +// Symbol: nandGetStatus +// Function signature is unknown. +// PAL: 0x8019c1b8..0x8019c30c +MARK_BINARY_BLOB(nandGetStatus, 0x8019c1b8, 0x8019c30c); +asm UNKNOWN_FUNCTION(nandGetStatus) { + nofralloc; + stwu r1, -0x70(r1); + mflr r0; + stw r0, 0x74(r1); + li r0, 0; + stw r31, 0x6c(r1); + mr r31, r7; + stw r30, 0x68(r1); + mr r30, r6; + stw r29, 0x64(r1); + mr r29, r5; + stw r28, 0x60(r1); + mr r28, r4; + mr r4, r3; + addi r3, r1, 0x20; + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + stw r0, 0x48(r1); + stw r0, 0x4c(r1); + stw r0, 0x50(r1); + stw r0, 0x54(r1); + stw r0, 0x58(r1); + stw r0, 0x5c(r1); + bl nandGenerateAbsPath; + cmpwi r31, 0; + bne lbl_8019c254; + addi r3, r1, 0x20; + bl nandIsUnderPrivatePath; + cmpwi r3, 0; + beq lbl_8019c254; + li r3, -102; + b lbl_8019c2ec; +lbl_8019c254: + cmpwi r30, 0; + beq lbl_8019c28c; + lis r10, 0x801a; + stw r29, 8(r1); + mr r4, r28; + addi r3, r1, 0x20; + addi r5, r28, 4; + addi r6, r29, 0x20; + addi r7, r29, 0x24; + addi r8, r29, 0x28; + addi r9, r29, 0x2c; + addi r10, r10, -15604; + bl unk_8016a658; + b lbl_8019c2ec; +lbl_8019c28c: + li r0, 0; + mr r4, r28; + stw r0, 0x1c(r1); + addi r3, r1, 0x20; + addi r5, r28, 4; + addi r6, r1, 0x1c; + stw r0, 0x18(r1); + addi r7, r1, 0x18; + addi r8, r1, 0x14; + addi r9, r1, 0x10; + stw r0, 0x14(r1); + stw r0, 0x10(r1); + bl unk_8016a500; + cmpwi r3, 0; + mr r31, r3; + bne lbl_8019c2e8; + lwz r4, 0x18(r1); + addi r3, r28, 7; + lwz r5, 0x14(r1); + lwz r6, 0x10(r1); + bl nandComposePerm; + lwz r0, 0x1c(r1); + stb r0, 6(r28); +lbl_8019c2e8: + mr r3, r31; +lbl_8019c2ec: + lwz r0, 0x74(r1); + lwz r31, 0x6c(r1); + lwz r30, 0x68(r1); + lwz r29, 0x64(r1); + lwz r28, 0x60(r1); + mtlr r0; + addi r1, r1, 0x70; + blr; +} + +// Symbol: nandGetStatusCallback +// Function signature is unknown. +// PAL: 0x8019c30c..0x8019c380 +MARK_BINARY_BLOB(nandGetStatusCallback, 0x8019c30c, 0x8019c380); +asm UNKNOWN_FUNCTION(nandGetStatusCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + bne lbl_8019c350; + lwz r5, 0x14(r4); + lwz r0, 0x20(r4); + addi r3, r5, 7; + stb r0, 6(r5); + lwz r4, 0x24(r4); + lwz r5, 0x28(r31); + lwz r6, 0x2c(r31); + bl nandComposePerm; +lbl_8019c350: + mr r3, r30; + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDGetStatus +// Function signature is unknown. +// PAL: 0x8019c380..0x8019c3e4 +MARK_BINARY_BLOB(NANDGetStatus, 0x8019c380, 0x8019c3e4); +asm UNKNOWN_FUNCTION(NANDGetStatus) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019c3c8; + mr r3, r30; + mr r4, r31; + li r5, 0; + li r6, 0; + li r7, 0; + bl nandGetStatus; + bl nandConvertErrorCode; + b lbl_8019c3cc; +lbl_8019c3c8: + li r3, -128; +lbl_8019c3cc: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDPrivateGetStatus +// Function signature is unknown. +// PAL: 0x8019c3e4..0x8019c448 +MARK_BINARY_BLOB(NANDPrivateGetStatus, 0x8019c3e4, 0x8019c448); +asm UNKNOWN_FUNCTION(NANDPrivateGetStatus) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019c42c; + mr r3, r30; + mr r4, r31; + li r5, 0; + li r6, 0; + li r7, 1; + bl nandGetStatus; + bl nandConvertErrorCode; + b lbl_8019c430; +lbl_8019c42c: + li r3, -128; +lbl_8019c430: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDPrivateGetStatusAsync +// Function signature is unknown. +// PAL: 0x8019c448..0x8019c4cc +MARK_BINARY_BLOB(NANDPrivateGetStatusAsync, 0x8019c448, 0x8019c4cc); +asm UNKNOWN_FUNCTION(NANDPrivateGetStatusAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r6; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019c488; + li r3, -128; + b lbl_8019c4ac; +lbl_8019c488: + stw r30, 4(r31); + mr r3, r28; + mr r4, r29; + mr r5, r31; + stw r29, 0x14(r31); + li r6, 1; + li r7, 1; + bl nandGetStatus; + bl nandConvertErrorCode; +lbl_8019c4ac: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandSetStatus +// Function signature is unknown. +// PAL: 0x8019c4cc..0x8019c614 +MARK_BINARY_BLOB(nandSetStatus, 0x8019c4cc, 0x8019c614); +asm UNKNOWN_FUNCTION(nandSetStatus) { + nofralloc; + stwu r1, -0x70(r1); + mflr r0; + stw r0, 0x74(r1); + li r0, 0; + stw r31, 0x6c(r1); + mr r31, r7; + stw r30, 0x68(r1); + mr r30, r6; + stw r29, 0x64(r1); + mr r29, r5; + stw r28, 0x60(r1); + mr r28, r4; + mr r4, r3; + addi r3, r1, 0x20; + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + stw r0, 0x48(r1); + stw r0, 0x4c(r1); + stw r0, 0x50(r1); + stw r0, 0x54(r1); + stw r0, 0x58(r1); + stw r0, 0x5c(r1); + bl nandGenerateAbsPath; + cmpwi r31, 0; + bne lbl_8019c568; + addi r3, r1, 0x20; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019c568; + li r3, -102; + b lbl_8019c5f4; +lbl_8019c568: + lbz r3, 7(r28); + rlwinm. r0, r3, 0, 0x1b, 0x1b; + bne lbl_8019c57c; + li r3, -101; + b lbl_8019c5f4; +lbl_8019c57c: + li r0, 0; + addi r4, r1, 0x18; + stw r0, 0x18(r1); + addi r5, r1, 0x14; + addi r6, r1, 0x10; + stw r0, 0x14(r1); + stw r0, 0x10(r1); + bl nandSplitPerm; + cmpwi r30, 0; + beq lbl_8019c5d4; + stw r29, 8(r1); + lis r10, 0x801a; + lwz r4, 0(r28); + addi r3, r1, 0x20; + lhz r5, 4(r28); + addi r10, r10, -7072; + lbz r6, 6(r28); + lwz r7, 0x18(r1); + lwz r8, 0x14(r1); + lwz r9, 0x10(r1); + bl unk_8016a3fc; + b lbl_8019c5f4; +lbl_8019c5d4: + lwz r4, 0(r28); + addi r3, r1, 0x20; + lhz r5, 4(r28); + lbz r6, 6(r28); + lwz r7, 0x18(r1); + lwz r8, 0x14(r1); + lwz r9, 0x10(r1); + bl unk_8016a2f8; +lbl_8019c5f4: + lwz r0, 0x74(r1); + lwz r31, 0x6c(r1); + lwz r30, 0x68(r1); + lwz r29, 0x64(r1); + lwz r28, 0x60(r1); + mtlr r0; + addi r1, r1, 0x70; + blr; +} + +// Symbol: NANDSetStatus +// Function signature is unknown. +// PAL: 0x8019c614..0x8019c678 +MARK_BINARY_BLOB(NANDSetStatus, 0x8019c614, 0x8019c678); +asm UNKNOWN_FUNCTION(NANDSetStatus) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019c65c; + mr r3, r30; + mr r4, r31; + li r5, 0; + li r6, 0; + li r7, 0; + bl nandSetStatus; + bl nandConvertErrorCode; + b lbl_8019c660; +lbl_8019c65c: + li r3, -128; +lbl_8019c660: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDPrivateSetStatus +// Function signature is unknown. +// PAL: 0x8019c678..0x8019c6dc +MARK_BINARY_BLOB(NANDPrivateSetStatus, 0x8019c678, 0x8019c6dc); +asm UNKNOWN_FUNCTION(NANDPrivateSetStatus) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + bl nandIsInitialized; + cmpwi r3, 0; + beq lbl_8019c6c0; + mr r3, r30; + mr r4, r31; + li r5, 0; + li r6, 0; + li r7, 1; + bl nandSetStatus; + bl nandConvertErrorCode; + b lbl_8019c6c4; +lbl_8019c6c0: + li r3, -128; +lbl_8019c6c4: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDSetUserData +// Function signature is unknown. +// PAL: 0x8019c6dc..0x8019c6e4 +MARK_BINARY_BLOB(NANDSetUserData, 0x8019c6dc, 0x8019c6e4); +asm UNKNOWN_FUNCTION(NANDSetUserData) { + nofralloc; + stw r4, 0(r3); + blr; +} + +// Symbol: NANDGetUserData +// Function signature is unknown. +// PAL: 0x8019c6e4..0x8019c6ec +MARK_BINARY_BLOB(NANDGetUserData, 0x8019c6e4, 0x8019c6ec); +asm UNKNOWN_FUNCTION(NANDGetUserData) { + nofralloc; + lwz r3, 0(r3); + blr; +} + +// Symbol: nandOpen +// Function signature is unknown. +// PAL: 0x8019c6ec..0x8019c800 +MARK_BINARY_BLOB(nandOpen, 0x8019c6ec, 0x8019c800); +asm UNKNOWN_FUNCTION(nandOpen) { + nofralloc; + stwu r1, -0x60(r1); + mflr r0; + stw r0, 0x64(r1); + addi r11, r1, 0x60; + bl _savegpr_27; + li r0, 0; + mr r27, r4; + stw r0, 8(r1); + mr r4, r3; + mr r28, r5; + mr r29, r6; + stw r0, 0xc(r1); + mr r30, r7; + addi r3, r1, 8; + li r31, 0; + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + bl nandGenerateAbsPath; + cmpwi r30, 0; + bne lbl_8019c784; + addi r3, r1, 8; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019c784; + li r3, -102; + b lbl_8019c7e8; +lbl_8019c784: + cmpwi r27, 2; + beq lbl_8019c7b4; + bge lbl_8019c79c; + cmpwi r27, 1; + bge lbl_8019c7ac; + b lbl_8019c7b8; +lbl_8019c79c: + cmpwi r27, 4; + bge lbl_8019c7b8; + li r31, 3; + b lbl_8019c7b8; +lbl_8019c7ac: + li r31, 1; + b lbl_8019c7b8; +lbl_8019c7b4: + li r31, 2; +lbl_8019c7b8: + cmpwi r29, 0; + beq lbl_8019c7dc; + lis r5, 0x801a; + mr r4, r31; + mr r6, r28; + addi r3, r1, 8; + addi r5, r5, -13816; + bl unk_8016af24; + b lbl_8019c7e8; +lbl_8019c7dc: + mr r4, r31; + addi r3, r1, 8; + bl unk_8016ae5c; +lbl_8019c7e8: + addi r11, r1, 0x60; + bl _restgpr_27; + lwz r0, 0x64(r1); + mtlr r0; + addi r1, r1, 0x60; + blr; +} + +// Symbol: NANDOpen +// Function signature is unknown. +// PAL: 0x8019c800..0x8019c88c +MARK_BINARY_BLOB(NANDOpen, 0x8019c800, 0x8019c88c); +asm UNKNOWN_FUNCTION(NANDOpen) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019c838; + li r3, -128; + b lbl_8019c870; +lbl_8019c838: + mr r3, r29; + mr r4, r31; + li r5, 0; + li r6, 0; + li r7, 0; + bl nandOpen; + cmpwi r3, 0; + blt lbl_8019c86c; + li r0, 1; + stw r3, 0(r30); + li r3, 0; + stb r0, 0x8a(r30); + b lbl_8019c870; +lbl_8019c86c: + bl nandConvertErrorCode; +lbl_8019c870: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDPrivateOpen +// Function signature is unknown. +// PAL: 0x8019c88c..0x8019c918 +MARK_BINARY_BLOB(NANDPrivateOpen, 0x8019c88c, 0x8019c918); +asm UNKNOWN_FUNCTION(NANDPrivateOpen) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019c8c4; + li r3, -128; + b lbl_8019c8fc; +lbl_8019c8c4: + mr r3, r29; + mr r4, r31; + li r5, 0; + li r6, 0; + li r7, 1; + bl nandOpen; + cmpwi r3, 0; + blt lbl_8019c8f8; + li r0, 1; + stw r3, 0(r30); + li r3, 0; + stb r0, 0x8a(r30); + b lbl_8019c8fc; +lbl_8019c8f8: + bl nandConvertErrorCode; +lbl_8019c8fc: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDOpenAsync +// Function signature is unknown. +// PAL: 0x8019c918..0x8019c990 +MARK_BINARY_BLOB(NANDOpenAsync, 0x8019c918, 0x8019c990); +asm UNKNOWN_FUNCTION(NANDOpenAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019c954; + li r3, -128; + b lbl_8019c978; +lbl_8019c954: + stw r30, 4(r31); + mr r3, r27; + mr r4, r29; + mr r5, r31; + stw r28, 8(r31); + li r6, 1; + li r7, 0; + bl nandOpen; + bl nandConvertErrorCode; +lbl_8019c978: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDPrivateOpenAsync +// Function signature is unknown. +// PAL: 0x8019c990..0x8019ca08 +MARK_BINARY_BLOB(NANDPrivateOpenAsync, 0x8019c990, 0x8019ca08); +asm UNKNOWN_FUNCTION(NANDPrivateOpenAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019c9cc; + li r3, -128; + b lbl_8019c9f0; +lbl_8019c9cc: + stw r30, 4(r31); + mr r3, r27; + mr r4, r29; + mr r5, r31; + stw r28, 8(r31); + li r6, 1; + li r7, 1; + bl nandOpen; + bl nandConvertErrorCode; +lbl_8019c9f0: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandOpenCallback +// Function signature is unknown. +// PAL: 0x8019ca08..0x8019ca80 +MARK_BINARY_BLOB(nandOpenCallback, 0x8019ca08, 0x8019ca80); +asm UNKNOWN_FUNCTION(nandOpenCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + blt lbl_8019ca58; + lwz r5, 8(r4); + li r6, 2; + li r0, 1; + stw r3, 0(r5); + li r3, 0; + lwz r5, 8(r4); + stb r6, 0x89(r5); + lwz r5, 8(r4); + stb r0, 0x8a(r5); + lwz r12, 4(r4); + mtctr r12; + bctrl; + b lbl_8019ca6c; +lbl_8019ca58: + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; +lbl_8019ca6c: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDClose +// Function signature is unknown. +// PAL: 0x8019ca80..0x8019caec +MARK_BINARY_BLOB(NANDClose, 0x8019ca80, 0x8019caec); +asm UNKNOWN_FUNCTION(NANDClose) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019caa8; + li r3, -128; + b lbl_8019cad8; +lbl_8019caa8: + lbz r0, 0x8a(r31); + cmplwi r0, 1; + beq lbl_8019cabc; + li r3, -8; + b lbl_8019cad8; +lbl_8019cabc: + lwz r3, 0(r31); + bl unk_8016b384; + cmpwi r3, 0; + bne lbl_8019cad4; + li r0, 2; + stb r0, 0x8a(r31); +lbl_8019cad4: + bl nandConvertErrorCode; +lbl_8019cad8: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDCloseAsync +// Function signature is unknown. +// PAL: 0x8019caec..0x8019cb74 +MARK_BINARY_BLOB(NANDCloseAsync, 0x8019caec, 0x8019cb74); +asm UNKNOWN_FUNCTION(NANDCloseAsync) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019cb24; + li r3, -128; + b lbl_8019cb58; +lbl_8019cb24: + lbz r0, 0x8a(r29); + cmplwi r0, 1; + beq lbl_8019cb38; + li r3, -8; + b lbl_8019cb58; +lbl_8019cb38: + stw r30, 4(r31); + lis r4, 0x801a; + mr r5, r31; + stw r29, 8(r31); + addi r4, r4, -9660; + lwz r3, 0(r29); + bl unk_8016b388; + bl nandConvertErrorCode; +lbl_8019cb58: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDSafeOpen +// Function signature is unknown. +// PAL: 0x8019cb74..0x8019cb80 +MARK_BINARY_BLOB(NANDSafeOpen, 0x8019cb74, 0x8019cb80); +asm UNKNOWN_FUNCTION(NANDSafeOpen) { + nofralloc; + li r8, 0; + li r9, 0; + b nandSafeOpen; +} + +// Symbol: nandSafeOpen +// Function signature is unknown. +// PAL: 0x8019cb80..0x8019cf28 +MARK_BINARY_BLOB(nandSafeOpen, 0x8019cb80, 0x8019cf28); +asm UNKNOWN_FUNCTION(nandSafeOpen) { + nofralloc; + stwu r1, -0x90(r1); + mflr r0; + stw r0, 0x94(r1); + addi r11, r1, 0x90; + bl _savegpr_25; + mr r25, r3; + mr r28, r4; + mr r27, r5; + mr r29, r6; + mr r30, r7; + mr r26, r8; + mr r31, r9; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019cbc4; + li r3, -128; + b lbl_8019cf10; +lbl_8019cbc4: + cmpwi r31, 0; + beq lbl_8019cbdc; + clrlwi. r0, r30, 0x12; + beq lbl_8019cbdc; + li r3, -8; + b lbl_8019cf10; +lbl_8019cbdc: + li r0, 0; + stb r27, 0x88(r28); + mr r4, r25; + addi r3, r28, 8; + stb r0, 0x89(r28); + bl nandGenerateAbsPath; + cmpwi r26, 0; + bne lbl_8019cc14; + addi r3, r28, 8; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019cc14; + li r3, -1; + b lbl_8019cf10; +lbl_8019cc14: + cmplwi r27, 1; + bne lbl_8019cc68; + addi r3, r28, 8; + li r4, 1; + bl unk_8016ae5c; + cmpwi r3, 0; + blt lbl_8019cc60; + li r0, 2; + cmpwi r31, 0; + stw r3, 0(r28); + stb r0, 0x89(r28); + bne lbl_8019cc50; + li r0, 3; + stb r0, 0x8a(r28); + b lbl_8019cc58; +lbl_8019cc50: + li r0, 5; + stb r0, 0x8a(r28); +lbl_8019cc58: + li r3, 0; + b lbl_8019cf10; +lbl_8019cc60: + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019cc68: + addi r0, r27, 0xfe; + clrlwi r0, r0, 0x18; + cmplwi r0, 1; + bgt lbl_8019cf0c; + li r0, 0; + lis r3, 0x8029; + stw r0, 0x20(r1); + addi r3, r3, -5648; + li r26, -1; + li r4, 0; + stw r0, 0x24(r1); + li r5, 3; + li r6, 3; + li r7, 3; + stw r0, 0x28(r1); + stb r0, 0x2c(r1); + bl unk_80169e74; + cmpwi r3, 0; + beq lbl_8019ccc4; + cmpwi r3, -105; + beq lbl_8019ccc4; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019ccc4: + li r0, 1; + addi r3, r28, 8; + stb r0, 0x89(r28); + addi r4, r1, 0x1c; + addi r5, r1, 8; + addi r6, r1, 0x18; + addi r7, r1, 0x14; + addi r8, r1, 0x10; + addi r9, r1, 0xc; + bl unk_8016a500; + cmpwi r3, 0; + beq lbl_8019ccfc; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019ccfc: + addi r3, r28, 8; + li r4, 1; + bl unk_8016ae5c; + cmpwi r3, 0; + stw r3, 4(r28); + bge lbl_8019cd1c; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019cd1c: + li r0, 2; + cmpwi r31, 0; + stb r0, 0x89(r28); + bne lbl_8019cd8c; + bl OSDisableInterrupts; + lwz r26, -0x63c0(r13); + addi r0, r26, 1; + stw r0, -0x63c0(r13); + bl OSRestoreInterrupts; + lis r5, 0x8029; + mr r6, r26; + addi r3, r1, 0x30; + addi r4, r13, -29160; + addi r5, r5, -5648; + crclr 6; + bl sprintf; + addi r3, r1, 0x30; + li r4, 0; + li r5, 3; + li r6, 0; + li r7, 0; + bl unk_80169e74; + cmpwi r3, 0; + beq lbl_8019cd84; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019cd84: + li r0, 3; + stb r0, 0x89(r28); +lbl_8019cd8c: + addi r3, r1, 0x20; + addi r4, r28, 8; + bl nandGetRelativeName; + cmpwi r31, 0; + bne lbl_8019cdc8; + lis r4, 0x8029; + lis r5, 0x8029; + mr r6, r26; + addi r3, r28, 0x48; + addi r4, r4, -5636; + addi r5, r5, -5648; + addi r7, r1, 0x20; + crclr 6; + bl sprintf; + b lbl_8019cde4; +lbl_8019cdc8: + lis r5, 0x8029; + addi r3, r28, 0x48; + addi r5, r5, -5648; + addi r6, r1, 0x20; + addi r4, r13, -29152; + crclr 6; + bl sprintf; +lbl_8019cde4: + lwz r4, 0x18(r1); + addi r3, r28, 0x48; + lwz r5, 0x14(r1); + lwz r6, 0x10(r1); + lwz r7, 0xc(r1); + bl unk_8016ac74; + cmpwi r3, 0; + beq lbl_8019ce0c; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019ce0c: + li r0, 4; + cmplwi r27, 2; + stb r0, 0x89(r28); + bne lbl_8019ce30; + addi r3, r28, 0x48; + li r4, 2; + bl unk_8016ae5c; + stw r3, 0(r28); + b lbl_8019ce48; +lbl_8019ce30: + cmplwi r27, 3; + bne lbl_8019ce48; + addi r3, r28, 0x48; + li r4, 3; + bl unk_8016ae5c; + stw r3, 0(r28); +lbl_8019ce48: + lwz r3, 0(r28); + cmpwi r3, 0; + bge lbl_8019ce5c; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019ce5c: + li r0, 5; + lwz r26, 4(r28); + stb r0, 0x89(r28); + mr r27, r3; +lbl_8019ce6c: + mr r3, r26; + mr r4, r29; + mr r5, r30; + bl unk_8016b1fc; + cmpwi r3, 0; + mr r5, r3; + bne lbl_8019ce90; + li r5, 0; + b lbl_8019ceb0; +lbl_8019ce90: + bge lbl_8019ce98; + b lbl_8019ceb0; +lbl_8019ce98: + mr r3, r27; + mr r4, r29; + bl unk_8016b2c0; + cmpwi r3, 0; + bge lbl_8019ce6c; + mr r5, r3; +lbl_8019ceb0: + cmpwi r5, 0; + beq lbl_8019cec4; + mr r3, r5; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019cec4: + lwz r3, 0(r28); + li r4, 0; + li r5, 0; + bl unk_8016b16c; + cmpwi r3, 0; + beq lbl_8019cee4; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019cee4: + cmpwi r31, 0; + beq lbl_8019cef8; + li r0, 5; + stb r0, 0x8a(r28); + b lbl_8019cf00; +lbl_8019cef8: + li r0, 3; + stb r0, 0x8a(r28); +lbl_8019cf00: + li r3, 0; + bl nandConvertErrorCode; + b lbl_8019cf10; +lbl_8019cf0c: + li r3, -8; +lbl_8019cf10: + addi r11, r1, 0x90; + bl _restgpr_25; + lwz r0, 0x94(r1); + mtlr r0; + addi r1, r1, 0x90; + blr; +} + +// Symbol: NANDSafeClose +// Function signature is unknown. +// PAL: 0x8019cf28..0x8019d104 +MARK_BINARY_BLOB(NANDSafeClose, 0x8019cf28, 0x8019d104); +asm UNKNOWN_FUNCTION(NANDSafeClose) { + nofralloc; + li r4, 0; + b lbl_8019cf30; +lbl_8019cf30: + stwu r1, -0x60(r1); + mflr r0; + stw r0, 0x64(r1); + li r0, 0; + stw r31, 0x5c(r1); + stw r30, 0x58(r1); + mr r30, r4; + stw r29, 0x54(r1); + mr r29, r3; + stw r0, 8(r1); + stw r0, 0xc(r1); + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019cfa8; + li r3, -128; + b lbl_8019d0e8; +lbl_8019cfa8: + lbz r0, 0x8a(r29); + cmplwi r0, 3; + bne lbl_8019cfbc; + cmpwi r30, 0; + beq lbl_8019cfd4; +lbl_8019cfbc: + cmplwi r0, 5; + bne lbl_8019cfcc; + cmpwi r30, 0; + bne lbl_8019cfd4; +lbl_8019cfcc: + li r3, -8; + b lbl_8019d0e8; +lbl_8019cfd4: + lbz r3, 0x88(r29); + cmplwi r3, 1; + bne lbl_8019d01c; + lwz r3, 0(r29); + bl unk_8016b384; + cmpwi r3, 0; + bne lbl_8019d014; + li r0, 7; + cmpwi r30, 0; + stb r0, 0x89(r29); + bne lbl_8019d00c; + li r0, 4; + stb r0, 0x8a(r29); + b lbl_8019d014; +lbl_8019d00c: + li r0, 6; + stb r0, 0x8a(r29); +lbl_8019d014: + bl nandConvertErrorCode; + b lbl_8019d0e8; +lbl_8019d01c: + addi r0, r3, 0xfe; + clrlwi r0, r0, 0x18; + cmplwi r0, 1; + bgt lbl_8019d0d4; + lwz r3, 0(r29); + bl unk_8016b384; + cmpwi r3, 0; + beq lbl_8019d044; + bl nandConvertErrorCode; + b lbl_8019d0e8; +lbl_8019d044: + li r31, 6; + lwz r3, 4(r29); + stb r31, 0x89(r29); + bl unk_8016b384; + cmpwi r3, 0; + beq lbl_8019d064; + bl nandConvertErrorCode; + b lbl_8019d0e8; +lbl_8019d064: + li r0, 7; + addi r3, r29, 0x48; + stb r0, 0x89(r29); + addi r4, r29, 8; + bl unk_8016a934; + cmpwi r3, 0; + beq lbl_8019d088; + bl nandConvertErrorCode; + b lbl_8019d0e8; +lbl_8019d088: + li r0, 8; + cmpwi r30, 0; + stb r0, 0x89(r29); + bne lbl_8019d0c8; + addi r3, r1, 8; + addi r4, r29, 0x48; + bl nandGetParentDirectory; + addi r3, r1, 8; + bl unk_8016a78c; + cmpwi r3, 0; + bne lbl_8019d0cc; + li r4, 9; + li r0, 4; + stb r4, 0x89(r29); + stb r0, 0x8a(r29); + b lbl_8019d0cc; +lbl_8019d0c8: + stb r31, 0x8a(r29); +lbl_8019d0cc: + bl nandConvertErrorCode; + b lbl_8019d0e8; +lbl_8019d0d4: + lis r3, 0x8029; + addi r3, r3, -5624; + crclr 6; + bl OSReport; + li r3, -8; +lbl_8019d0e8: + lwz r0, 0x64(r1); + lwz r31, 0x5c(r1); + lwz r30, 0x58(r1); + lwz r29, 0x54(r1); + mtlr r0; + addi r1, r1, 0x60; + blr; +} + +// Symbol: NANDPrivateSafeOpenAsync +// Function signature is unknown. +// PAL: 0x8019d104..0x8019d130 +MARK_BINARY_BLOB(NANDPrivateSafeOpenAsync, 0x8019d104, 0x8019d130); +asm UNKNOWN_FUNCTION(NANDPrivateSafeOpenAsync) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + li r10, 1; + stw r0, 0x14(r1); + li r0, 0; + stw r0, 8(r1); + bl nandSafeOpenAsync; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandSafeOpenAsync +// Function signature is unknown. +// PAL: 0x8019d130..0x8019d298 +MARK_BINARY_BLOB(nandSafeOpenAsync, 0x8019d130, 0x8019d298); +asm UNKNOWN_FUNCTION(nandSafeOpenAsync) { + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_23; + lwz r31, 0x38(r1); + mr r23, r3; + mr r24, r4; + mr r25, r5; + mr r26, r6; + mr r27, r7; + mr r28, r8; + mr r29, r9; + mr r30, r10; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019d17c; + li r3, -128; + b lbl_8019d280; +lbl_8019d17c: + cmpwi r31, 0; + beq lbl_8019d194; + clrlwi. r0, r27, 0x12; + beq lbl_8019d194; + li r3, -8; + b lbl_8019d280; +lbl_8019d194: + li r0, 0; + stb r25, 0x88(r24); + mr r4, r23; + addi r3, r24, 8; + stb r0, 0x89(r24); + stw r31, 0xb8(r29); + bl nandGenerateAbsPath; + cmpwi r30, 0; + bne lbl_8019d1d0; + addi r3, r24, 8; + bl nandIsPrivatePath; + cmpwi r3, 0; + beq lbl_8019d1d0; + li r3, -1; + b lbl_8019d280; +lbl_8019d1d0: + cmplwi r25, 1; + bne lbl_8019d210; + lis r5, 0x801a; + stw r24, 8(r29); + mr r6, r29; + addi r3, r24, 8; + stw r28, 4(r29); + addi r5, r5, -10616; + li r4, 1; + bl unk_8016af24; + cmpwi r3, 0; + bne lbl_8019d208; + li r3, 0; + b lbl_8019d280; +lbl_8019d208: + bl nandConvertErrorCode; + b lbl_8019d280; +lbl_8019d210: + addi r0, r25, 0xfe; + clrlwi r0, r0, 0x18; + cmplwi r0, 1; + bgt lbl_8019d27c; + li r31, 0; + lis r3, 0x8029; + lis r8, 0x801a; + stw r24, 8(r29); + mr r9, r29; + addi r3, r3, -5648; + stw r28, 4(r29); + addi r8, r8, -11624; + li r4, 0; + li r5, 3; + stw r31, 0x7c(r29); + li r6, 3; + li r7, 3; + stw r26, 0x80(r29); + stw r27, 0x84(r29); + bl unk_80169f68; + cmpwi r3, 0; + bne lbl_8019d26c; + b lbl_8019d274; +lbl_8019d26c: + bl nandConvertErrorCode; + mr r31, r3; +lbl_8019d274: + mr r3, r31; + b lbl_8019d280; +lbl_8019d27c: + li r3, -8; +lbl_8019d280: + addi r11, r1, 0x30; + bl _restgpr_23; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; +} + +// Symbol: nandSafeOpenCallback +// Function signature is unknown. +// PAL: 0x8019d298..0x8019d688 +MARK_BINARY_BLOB(nandSafeOpenCallback, 0x8019d298, 0x8019d688); +asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { + nofralloc; + stwu r1, -0x70(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x74(r1); + stw r31, 0x6c(r1); + stw r30, 0x68(r1); + mr r30, r4; + bge lbl_8019d2cc; + cmpwi r3, -105; + bne lbl_8019d65c; + lwz r0, 0x7c(r4); + cmpwi r0, 0; + bne lbl_8019d65c; +lbl_8019d2cc: + lwz r0, 0x7c(r4); + li r6, -117; + lwz r31, 8(r4); + cmpwi r0, 0; + bne lbl_8019d2e8; + li r0, 1; + stb r0, 0x89(r31); +lbl_8019d2e8: + lwz r0, 0x7c(r4); + cmpwi r0, 2; + bne lbl_8019d300; + stw r3, 4(r31); + li r0, 2; + stb r0, 0x89(r31); +lbl_8019d300: + lwz r5, 0x7c(r4); + cmpwi r5, 2; + bne lbl_8019d324; + lwz r0, 0xb8(r4); + cmpwi r0, 0; + beq lbl_8019d324; + addi r0, r5, 2; + stw r0, 0x7c(r4); + b lbl_8019d330; +lbl_8019d324: + lwz r5, 0x7c(r4); + addi r0, r5, 1; + stw r0, 0x7c(r4); +lbl_8019d330: + lwz r0, 0x7c(r4); + cmpwi r0, 1; + bne lbl_8019d370; + lis r10, 0x801a; + stw r4, 8(r1); + addi r3, r31, 8; + addi r5, r4, 0x1c; + addi r6, r4, 0x20; + addi r7, r4, 0x24; + addi r8, r4, 0x28; + addi r9, r4, 0x2c; + addi r10, r10, -11624; + addi r4, r4, 0x18; + bl unk_8016a658; + mr r6, r3; + b lbl_8019d638; +lbl_8019d370: + cmpwi r0, 2; + bne lbl_8019d398; + lis r5, 0x801a; + mr r6, r30; + addi r3, r31, 8; + li r4, 1; + addi r5, r5, -11624; + bl unk_8016af24; + mr r6, r3; + b lbl_8019d638; +lbl_8019d398: + cmpwi r0, 3; + bne lbl_8019d400; + bl OSDisableInterrupts; + lwz r31, -0x63c0(r13); + addi r0, r31, 1; + stw r0, -0x63c0(r13); + bl OSRestoreInterrupts; + lis r5, 0x8029; + stw r31, 0x8c(r30); + mr r6, r31; + addi r3, r1, 0x20; + addi r5, r5, -5648; + addi r4, r13, -29160; + crclr 6; + bl sprintf; + lis r8, 0x801a; + mr r9, r30; + addi r3, r1, 0x20; + li r4, 0; + addi r8, r8, -11624; + li r5, 3; + li r6, 0; + li r7, 0; + bl unk_80169f68; + mr r6, r3; + b lbl_8019d638; +lbl_8019d400: + cmpwi r0, 4; + bne lbl_8019d498; + addi r3, r1, 0x10; + addi r4, r31, 8; + bl nandGetRelativeName; + lwz r0, 0xb8(r30); + cmpwi r0, 0; + bne lbl_8019d450; + li r0, 3; + lis r4, 0x8029; + stb r0, 0x89(r31); + lis r5, 0x8029; + addi r3, r31, 0x48; + addi r4, r4, -5636; + lwz r6, 0x8c(r30); + addi r5, r5, -5648; + addi r7, r1, 0x10; + crclr 6; + bl sprintf; + b lbl_8019d46c; +lbl_8019d450: + lis r5, 0x8029; + addi r3, r31, 0x48; + addi r5, r5, -5648; + addi r6, r1, 0x10; + addi r4, r13, -29152; + crclr 6; + bl sprintf; +lbl_8019d46c: + lis r8, 0x801a; + lwz r4, 0x20(r30); + lwz r5, 0x24(r30); + mr r9, r30; + lwz r6, 0x28(r30); + addi r3, r31, 0x48; + lwz r7, 0x2c(r30); + addi r8, r8, -11624; + bl unk_8016ad68; + mr r6, r3; + b lbl_8019d638; +lbl_8019d498: + cmpwi r0, 5; + bne lbl_8019d504; + li r0, 4; + stb r0, 0x89(r31); + lbz r0, 0x88(r31); + cmplwi r0, 2; + bne lbl_8019d4d4; + lis r5, 0x801a; + mr r6, r30; + addi r3, r31, 0x48; + li r4, 2; + addi r5, r5, -11624; + bl unk_8016af24; + mr r6, r3; + b lbl_8019d638; +lbl_8019d4d4: + cmplwi r0, 3; + bne lbl_8019d4fc; + lis r5, 0x801a; + mr r6, r30; + addi r3, r31, 0x48; + li r4, 3; + addi r5, r5, -11624; + bl unk_8016af24; + mr r6, r3; + b lbl_8019d638; +lbl_8019d4fc: + li r6, -117; + b lbl_8019d638; +lbl_8019d504: + cmpwi r0, 6; + bne lbl_8019d544; + stw r3, 0(r31); + li r3, 5; + lis r6, 0x801a; + li r0, 7; + stb r3, 0x89(r31); + mr r7, r30; + addi r6, r6, -11624; + stw r0, 0x7c(r4); + lwz r4, 0x80(r4); + lwz r3, 4(r31); + lwz r5, 0x84(r30); + bl unk_8016b21c; + mr r6, r3; + b lbl_8019d638; +lbl_8019d544: + cmpwi r0, 7; + bne lbl_8019d570; + lis r6, 0x801a; + lwz r3, 4(r31); + lwz r4, 0x80(r4); + mr r7, r30; + lwz r5, 0x84(r30); + addi r6, r6, -11624; + bl unk_8016b21c; + mr r6, r3; + b lbl_8019d638; +lbl_8019d570: + cmpwi r0, 8; + bne lbl_8019d5d4; + cmpwi r3, 0; + ble lbl_8019d5ac; + li r0, 6; + lis r6, 0x801a; + stw r0, 0x7c(r4); + mr r5, r3; + lwz r4, 0x80(r4); + mr r7, r30; + lwz r3, 0(r31); + addi r6, r6, -11624; + bl unk_8016b2e0; + mr r6, r3; + b lbl_8019d638; +lbl_8019d5ac: + bne lbl_8019d638; + lis r6, 0x801a; + lwz r3, 0(r31); + mr r7, r30; + li r4, 0; + addi r6, r6, -11624; + li r5, 0; + bl unk_8016b170; + mr r6, r3; + b lbl_8019d638; +lbl_8019d5d4: + cmpwi r0, 9; + bne lbl_8019d638; + cmpwi r3, 0; + bne lbl_8019d620; + lwz r0, 0xb8(r4); + cmpwi r0, 0; + bne lbl_8019d5fc; + li r0, 3; + stb r0, 0x8a(r31); + b lbl_8019d604; +lbl_8019d5fc: + li r0, 5; + stb r0, 0x8a(r31); +lbl_8019d604: + li r3, 0; + bl nandConvertErrorCode; + lwz r12, 4(r30); + mr r4, r30; + mtctr r12; + bctrl; + b lbl_8019d670; +lbl_8019d620: + bl nandConvertErrorCode; + lwz r12, 4(r30); + mr r4, r30; + mtctr r12; + bctrl; + b lbl_8019d670; +lbl_8019d638: + cmpwi r6, 0; + beq lbl_8019d670; + mr r3, r6; + bl nandConvertErrorCode; + lwz r12, 4(r30); + mr r4, r30; + mtctr r12; + bctrl; + b lbl_8019d670; +lbl_8019d65c: + bl nandConvertErrorCode; + lwz r12, 4(r30); + mr r4, r30; + mtctr r12; + bctrl; +lbl_8019d670: + lwz r0, 0x74(r1); + lwz r31, 0x6c(r1); + lwz r30, 0x68(r1); + mtlr r0; + addi r1, r1, 0x70; + blr; +} + +// Symbol: nandReadOpenCallback +// Function signature is unknown. +// PAL: 0x8019d688..0x8019d720 +MARK_BINARY_BLOB(nandReadOpenCallback, 0x8019d688, 0x8019d720); +asm UNKNOWN_FUNCTION(nandReadOpenCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + blt lbl_8019d6f8; + lwz r5, 8(r4); + li r0, 2; + stw r3, 0(r5); + lwz r3, 8(r4); + stb r0, 0x89(r3); + lwz r0, 0xb8(r4); + cmpwi r0, 0; + bne lbl_8019d6d4; + lwz r3, 8(r4); + li r0, 3; + stb r0, 0x8a(r3); + b lbl_8019d6e0; +lbl_8019d6d4: + lwz r3, 8(r4); + li r0, 5; + stb r0, 0x8a(r3); +lbl_8019d6e0: + lwz r12, 4(r31); + mr r4, r31; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019d70c; +lbl_8019d6f8: + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; +lbl_8019d70c: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDSafeCloseAsync +// Function signature is unknown. +// PAL: 0x8019d720..0x8019d824 +MARK_BINARY_BLOB(NANDSafeCloseAsync, 0x8019d720, 0x8019d824); +asm UNKNOWN_FUNCTION(NANDSafeCloseAsync) { + nofralloc; + li r6, 0; + b lbl_8019d728; +lbl_8019d728: + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r6; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019d768; + li r3, -128; + b lbl_8019d804; +lbl_8019d768: + lbz r0, 0x8a(r28); + cmplwi r0, 3; + bne lbl_8019d77c; + cmpwi r31, 0; + beq lbl_8019d794; +lbl_8019d77c: + cmplwi r0, 5; + bne lbl_8019d78c; + cmpwi r31, 0; + bne lbl_8019d794; +lbl_8019d78c: + li r3, -8; + b lbl_8019d804; +lbl_8019d794: + stw r31, 0xb8(r30); + lbz r3, 0x88(r28); + cmplwi r3, 1; + bne lbl_8019d7c4; + stw r28, 8(r30); + lis r4, 0x801a; + mr r5, r30; + stw r29, 4(r30); + addi r4, r4, -9752; + lwz r3, 0(r28); + bl unk_8016b388; + b lbl_8019d800; +lbl_8019d7c4: + addi r0, r3, 0xfe; + clrlwi r0, r0, 0x18; + cmplwi r0, 1; + bgt lbl_8019d7fc; + li r0, 0xa; + lis r4, 0x801a; + stw r28, 8(r30); + mr r5, r30; + addi r4, r4, -10204; + stw r29, 4(r30); + stw r0, 0x7c(r30); + lwz r3, 0(r28); + bl unk_8016b388; + b lbl_8019d800; +lbl_8019d7fc: + li r3, -101; +lbl_8019d800: + bl nandConvertErrorCode; +lbl_8019d804: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandSafeCloseCallback +// Function signature is unknown. +// PAL: 0x8019d824..0x8019d9e8 +MARK_BINARY_BLOB(nandSafeCloseCallback, 0x8019d824, 0x8019d9e8); +asm UNKNOWN_FUNCTION(nandSafeCloseCallback) { + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x54(r1); + stw r31, 0x4c(r1); + mr r31, r4; + bne lbl_8019d9c0; + lwz r0, 0x7c(r4); + li r6, -117; + lwz r7, 8(r4); + cmpwi r0, 0xc; + bne lbl_8019d85c; + li r0, 8; + stb r0, 0x89(r7); +lbl_8019d85c: + lwz r5, 0x7c(r4); + cmpwi r5, 0xc; + bne lbl_8019d880; + lwz r0, 0xb8(r4); + cmpwi r0, 0; + beq lbl_8019d880; + addi r0, r5, 2; + stw r0, 0x7c(r4); + b lbl_8019d88c; +lbl_8019d880: + lwz r5, 0x7c(r4); + addi r0, r5, 1; + stw r0, 0x7c(r4); +lbl_8019d88c: + lwz r0, 0x7c(r4); + cmpwi r0, 0xb; + bne lbl_8019d8bc; + li r0, 6; + lis r4, 0x801a; + stb r0, 0x89(r7); + mr r5, r31; + addi r4, r4, -10204; + lwz r3, 4(r7); + bl unk_8016b388; + mr r6, r3; + b lbl_8019d99c; +lbl_8019d8bc: + cmpwi r0, 0xc; + bne lbl_8019d8ec; + li r0, 7; + lis r5, 0x801a; + stb r0, 0x89(r7); + mr r6, r31; + addi r3, r7, 0x48; + addi r4, r7, 8; + addi r5, r5, -10204; + bl unk_8016aa38; + mr r6, r3; + b lbl_8019d99c; +lbl_8019d8ec: + cmpwi r0, 0xd; + bne lbl_8019d960; + li r0, 0; + addi r3, r1, 8; + stw r0, 8(r1); + addi r4, r7, 0x48; + stw r0, 0xc(r1); + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + bl nandGetParentDirectory; + lis r4, 0x801a; + mr r5, r31; + addi r3, r1, 8; + addi r4, r4, -10204; + bl unk_8016a864; + mr r6, r3; + b lbl_8019d99c; +lbl_8019d960: + cmpwi r0, 0xe; + bne lbl_8019d99c; + lwz r0, 0xb8(r4); + cmpwi r0, 0; + bne lbl_8019d97c; + li r0, 9; + stb r0, 0x89(r7); +lbl_8019d97c: + li r0, 4; + stb r0, 0x8a(r7); + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + b lbl_8019d9d4; +lbl_8019d99c: + cmpwi r6, 0; + beq lbl_8019d9d4; + mr r3, r6; + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + b lbl_8019d9d4; +lbl_8019d9c0: + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; +lbl_8019d9d4: + lwz r0, 0x54(r1); + lwz r31, 0x4c(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; +} + +// Symbol: nandReadCloseCallback +// Function signature is unknown. +// PAL: 0x8019d9e8..0x8019da44 +MARK_BINARY_BLOB(nandReadCloseCallback, 0x8019d9e8, 0x8019da44); +asm UNKNOWN_FUNCTION(nandReadCloseCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + bne lbl_8019da1c; + lwz r5, 8(r4); + li r6, 7; + li r0, 4; + stb r6, 0x89(r5); + lwz r4, 8(r4); + stb r0, 0x8a(r4); +lbl_8019da1c: + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandCloseCallback +// Function signature is unknown. +// PAL: 0x8019da44..0x8019daa0 +MARK_BINARY_BLOB(nandCloseCallback, 0x8019da44, 0x8019daa0); +asm UNKNOWN_FUNCTION(nandCloseCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + bne lbl_8019da78; + lwz r5, 8(r4); + li r6, 7; + li r0, 2; + stb r6, 0x89(r5); + lwz r4, 8(r4); + stb r0, 0x8a(r4); +lbl_8019da78: + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandRemoveTailToken +// Function signature is unknown. +// PAL: 0x8019daa0..0x8019db74 +MARK_BINARY_BLOB(nandRemoveTailToken, 0x8019daa0, 0x8019db74); +asm UNKNOWN_FUNCTION(nandRemoveTailToken) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + lbz r0, 0(r4); + stw r31, 0x1c(r1); + cmpwi r0, 0x2f; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bne lbl_8019daec; + lbz r0, 1(r4); + extsb. r0, r0; + bne lbl_8019daec; + li r4, 0x2f; + li r0, 0; + stb r4, 0(r3); + stb r0, 1(r3); + b lbl_8019db58; +lbl_8019daec: + mr r3, r30; + bl strlen; + addic. r31, r3, -1; + addi r0, r31, 1; + add r3, r30, r31; + mtctr r0; + blt lbl_8019db58; +lbl_8019db08: + lbz r0, 0(r3); + cmpwi r0, 0x2f; + bne lbl_8019db4c; + cmpwi r31, 0; + beq lbl_8019db38; + mr r3, r29; + mr r4, r30; + mr r5, r31; + bl strncpy; + li r0, 0; + stbx r0, r29, r31; + b lbl_8019db58; +lbl_8019db38: + li r3, 0x2f; + li r0, 0; + stb r3, 0(r29); + stb r0, 1(r29); + b lbl_8019db58; +lbl_8019db4c: + addi r31, r31, -1; + addi r3, r3, -1; + bdnz lbl_8019db08; +lbl_8019db58: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandGetHeadToken +// Function signature is unknown. +// PAL: 0x8019db74..0x8019dc48 +MARK_BINARY_BLOB(nandGetHeadToken, 0x8019db74, 0x8019dc48); +asm UNKNOWN_FUNCTION(nandGetHeadToken) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r31, r5; + li r30, 0; + b lbl_8019dc20; +lbl_8019dba0: + lbz r0, 0(r31); + extsb r0, r0; + cmpwi r0, 0x2f; + bne lbl_8019dbf0; + mr r3, r27; + mr r4, r29; + mr r5, r30; + bl strncpy; + add r4, r30, r29; + li r3, 0; + lbz r0, 1(r4); + stbx r3, r27, r30; + extsb. r0, r0; + bne lbl_8019dbe0; + stb r3, 0(r28); + b lbl_8019dc30; +lbl_8019dbe0: + mr r3, r28; + addi r4, r4, 1; + bl strcpy; + b lbl_8019dc30; +lbl_8019dbf0: + cmpwi r0, 0; + bne lbl_8019dc18; + mr r3, r27; + mr r4, r29; + mr r5, r30; + bl strncpy; + li r0, 0; + stbx r0, r27, r30; + stb r0, 0(r28); + b lbl_8019dc30; +lbl_8019dc18: + addi r30, r30, 1; + addi r31, r31, 1; +lbl_8019dc20: + mr r3, r29; + bl strlen; + cmplw r30, r3; + ble lbl_8019dba0; +lbl_8019dc30: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandGetRelativeName +// Function signature is unknown. +// PAL: 0x8019dc48..0x8019dce0 +MARK_BINARY_BLOB(nandGetRelativeName, 0x8019dc48, 0x8019dce0); +asm UNKNOWN_FUNCTION(nandGetRelativeName) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + addi r3, r13, -29140; + bl strcmp; + cmpwi r3, 0; + bne lbl_8019dc84; + mr r3, r30; + addi r4, r13, -29136; + bl strcpy; + b lbl_8019dcc8; +lbl_8019dc84: + mr r3, r31; + bl strlen; + addic. r4, r3, -1; + addi r0, r4, 1; + add r3, r31, r4; + mtctr r0; + blt lbl_8019dcb8; +lbl_8019dca0: + lbz r0, 0(r3); + cmpwi r0, 0x2f; + beq lbl_8019dcb8; + addi r4, r4, -1; + addi r3, r3, -1; + bdnz lbl_8019dca0; +lbl_8019dcb8: + add r4, r31, r4; + mr r3, r30; + addi r4, r4, 1; + bl strcpy; +lbl_8019dcc8: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandConvertPath +// Function signature is unknown. +// PAL: 0x8019dce0..0x8019de1c +MARK_BINARY_BLOB(nandConvertPath, 0x8019dce0, 0x8019de1c); +asm UNKNOWN_FUNCTION(nandConvertPath) { + nofralloc; +lbl_8019dce0: + stwu r1, -0x220(r1); + mflr r0; + stw r0, 0x224(r1); + stw r31, 0x21c(r1); + mr r31, r5; + stw r30, 0x218(r1); + mr r30, r4; + stw r29, 0x214(r1); + mr r29, r3; + mr r3, r31; + bl strlen; + cmpwi r3, 0; + bne lbl_8019dd24; + mr r3, r29; + mr r4, r30; + bl strcpy; + b lbl_8019de00; +lbl_8019dd24: + mr r5, r31; + addi r3, r1, 0x188; + addi r4, r1, 0x108; + bl nandGetHeadToken; + addi r3, r1, 0x188; + addi r4, r13, -29132; + bl strcmp; + cmpwi r3, 0; + bne lbl_8019dd5c; + mr r3, r29; + mr r4, r30; + addi r5, r1, 0x108; + bl lbl_8019dce0; + b lbl_8019de00; +lbl_8019dd5c: + addi r3, r1, 0x188; + addi r4, r13, -29128; + bl strcmp; + cmpwi r3, 0; + bne lbl_8019dd90; + mr r4, r30; + addi r3, r1, 0x88; + bl nandRemoveTailToken; + mr r3, r29; + addi r4, r1, 0x88; + addi r5, r1, 0x108; + bl lbl_8019dce0; + b lbl_8019de00; +lbl_8019dd90: + lbz r0, 0x188(r1); + extsb. r0, r0; + beq lbl_8019ddf4; + mr r3, r30; + addi r4, r13, -29140; + bl strcmp; + cmpwi r3, 0; + bne lbl_8019ddc8; + addi r3, r1, 8; + addi r5, r1, 0x188; + addi r4, r13, -29124; + crclr 6; + bl sprintf; + b lbl_8019dde0; +lbl_8019ddc8: + mr r5, r30; + addi r3, r1, 8; + addi r6, r1, 0x188; + addi r4, r13, -29120; + crclr 6; + bl sprintf; +lbl_8019dde0: + mr r3, r29; + addi r4, r1, 8; + addi r5, r1, 0x108; + bl lbl_8019dce0; + b lbl_8019de00; +lbl_8019ddf4: + mr r3, r29; + mr r4, r30; + bl strcpy; +lbl_8019de00: + lwz r0, 0x224(r1); + lwz r31, 0x21c(r1); + lwz r30, 0x218(r1); + lwz r29, 0x214(r1); + mtlr r0; + addi r1, r1, 0x220; + blr; +} + +// Symbol: nandIsPrivatePath +// Function signature is unknown. +// PAL: 0x8019de1c..0x8019de50 +MARK_BINARY_BLOB(nandIsPrivatePath, 0x8019de1c, 0x8019de50); +asm UNKNOWN_FUNCTION(nandIsPrivatePath) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + lis r4, 0x8029; + li r5, 8; + stw r0, 0x14(r1); + addi r4, r4, -5424; + bl strncmp; + cntlzw r0, r3; + srwi r3, r0, 5; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandIsUnderPrivatePath +// Function signature is unknown. +// PAL: 0x8019de50..0x8019dea8 +MARK_BINARY_BLOB(nandIsUnderPrivatePath, 0x8019de50, 0x8019dea8); +asm UNKNOWN_FUNCTION(nandIsUnderPrivatePath) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + lis r4, 0x8029; + li r5, 9; + stw r0, 0x14(r1); + addi r4, r4, -5412; + stw r31, 0xc(r1); + mr r31, r3; + bl strncmp; + cmpwi r3, 0; + bne lbl_8019de90; + lbz r0, 9(r31); + extsb. r0, r0; + beq lbl_8019de90; + li r3, 1; + b lbl_8019de94; +lbl_8019de90: + li r3, 0; +lbl_8019de94: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandIsInitialized +// Function signature is unknown. +// PAL: 0x8019dea8..0x8019debc +MARK_BINARY_BLOB(nandIsInitialized, 0x8019dea8, 0x8019debc); +asm UNKNOWN_FUNCTION(nandIsInitialized) { + nofralloc; + lwz r3, -0x63b8(r13); + addi r0, r3, -2; + cntlzw r0, r0; + srwi r3, r0, 5; + blr; +} + +// Symbol: nandReportErrorCode +// Function signature is unknown. +// PAL: 0x8019debc..0x8019dec0 +MARK_BINARY_BLOB(nandReportErrorCode, 0x8019debc, 0x8019dec0); +asm UNKNOWN_FUNCTION(nandReportErrorCode) { + nofralloc; + blr; +} + +// Symbol: nandConvertErrorCode +// Function signature is unknown. +// PAL: 0x8019dec0..0x8019e020 +MARK_BINARY_BLOB(nandConvertErrorCode, 0x8019dec0, 0x8019e020); +asm UNKNOWN_FUNCTION(nandConvertErrorCode) { + nofralloc; + clrlwi r11, r1, 0x1a; + mr r12, r1; + subfic r11, r11, -768; + stwux r1, r1, r11; + mflr r0; + lis r4, 0x8025; + stw r0, 4(r12); + addi r4, r4, 0x2c88; + li r0, 0x29; + addi r6, r1, 0x13c; + stw r31, -4(r12); + lis r31, 0x8029; + addi r31, r31, -5600; + addi r5, r4, -4; + stw r30, -8(r12); + stw r29, -0xc(r12); + mr r29, r3; + mtctr r0; +lbl_8019df08: + lwz r4, 4(r5); + lwzu r0, 8(r5); + stw r4, 4(r6); + stwu r0, 8(r6); + bdnz lbl_8019df08; + cmpwi r3, 0; + li r30, 0; + li r4, 0; + blt lbl_8019df34; + mr r3, r29; + b lbl_8019e000; +lbl_8019df34: + li r0, 0x29; + addi r5, r1, 0x140; + mtctr r0; +lbl_8019df40: + lwzx r0, r5, r4; + cmpw r3, r0; + bne lbl_8019dfb4; + cmpwi r3, -114; + beq lbl_8019df74; + cmpwi r3, -116; + beq lbl_8019df74; + cmpwi r3, -117; + beq lbl_8019df74; + cmpwi r3, -9; + beq lbl_8019df74; + cmpwi r3, -12; + bne lbl_8019df98; +lbl_8019df74: + mr r5, r29; + addi r3, r1, 0xc0; + addi r4, r31, 0xc8; + crclr 6; + bl sprintf; + addi r4, r1, 0xc0; + li r3, 0; + crclr 6; + bl NANDLoggingAddMessageAsync; +lbl_8019df98: + mr r3, r29; + bl nandReportErrorCode; + addi r0, r30, 1; + addi r3, r1, 0x140; + slwi r0, r0, 2; + lwzx r3, r3, r0; + b lbl_8019e000; +lbl_8019dfb4: + addi r30, r30, 2; + addi r4, r4, 8; + bdnz lbl_8019df40; + mr r4, r29; + addi r3, r31, 0xdc; + crclr 6; + bl OSReport; + mr r5, r29; + addi r3, r1, 0x40; + addi r4, r31, 0x110; + crclr 6; + bl sprintf; + addi r4, r1, 0x40; + li r3, 0; + crclr 6; + bl NANDLoggingAddMessageAsync; + mr r3, r29; + bl nandReportErrorCode; + li r3, -64; +lbl_8019e000: + lwz r10, 0(r1); + lwz r0, 4(r10); + lwz r31, -4(r10); + lwz r30, -8(r10); + lwz r29, -0xc(r10); + mtlr r0; + mr r1, r10; + blr; +} + +// Symbol: nandGenerateAbsPath +// Function signature is unknown. +// PAL: 0x8019e020..0x8019e0e8 +MARK_BINARY_BLOB(nandGenerateAbsPath, 0x8019e020, 0x8019e0e8); +asm UNKNOWN_FUNCTION(nandGenerateAbsPath) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + mr r3, r31; + bl strlen; + cmpwi r3, 0; + bne lbl_8019e05c; + mr r3, r30; + addi r4, r13, -29136; + bl strcpy; + b lbl_8019e0d0; +lbl_8019e05c: + lbz r0, 0(r31); + cmpwi r0, 0x2f; + bne lbl_8019e070; + li r0, 0; + b lbl_8019e074; +lbl_8019e070: + li r0, 1; +lbl_8019e074: + cmpwi r0, 0; + beq lbl_8019e094; + lis r4, 0x8029; + mr r3, r30; + mr r5, r31; + addi r4, r4, -5504; + bl nandConvertPath; + b lbl_8019e0d0; +lbl_8019e094: + mr r3, r30; + mr r4, r31; + bl strcpy; + mr r3, r30; + bl strlen; + cmpwi r3, 0; + beq lbl_8019e0d0; + add r4, r3, r30; + lbz r0, -1(r4); + cmpwi r0, 0x2f; + bne lbl_8019e0d0; + addic. r0, r3, -1; + beq lbl_8019e0d0; + li r0, 0; + stb r0, -1(r4); +lbl_8019e0d0: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandGetParentDirectory +// Function signature is unknown. +// PAL: 0x8019e0e8..0x8019e18c +MARK_BINARY_BLOB(nandGetParentDirectory, 0x8019e0e8, 0x8019e18c); +asm UNKNOWN_FUNCTION(nandGetParentDirectory) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + mr r3, r30; + bl strlen; + addi r0, r3, 1; + mr r31, r3; + add r4, r30, r3; + mtctr r0; + cmpwi r3, 0; + blt lbl_8019e140; +lbl_8019e128: + lbz r0, 0(r4); + cmpwi r0, 0x2f; + beq lbl_8019e140; + addi r31, r31, -1; + addi r4, r4, -1; + bdnz lbl_8019e128; +lbl_8019e140: + cmpwi r31, 0; + bne lbl_8019e158; + mr r3, r29; + addi r4, r13, -29140; + bl strcpy; + b lbl_8019e170; +lbl_8019e158: + mr r3, r29; + mr r4, r30; + mr r5, r31; + bl strncpy; + li r0, 0; + stbx r0, r29, r31; +lbl_8019e170: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDInit +// Function signature is unknown. +// PAL: 0x8019e18c..0x8019e2b8 +MARK_BINARY_BLOB(NANDInit, 0x8019e18c, 0x8019e2b8); +asm UNKNOWN_FUNCTION(NANDInit) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + lis r31, 0x8029; + addi r31, r31, -5600; + stw r30, 0x18(r1); + bl OSDisableInterrupts; + lwz r0, -0x63b8(r13); + cmpwi r0, 1; + bne lbl_8019e1c4; + bl OSRestoreInterrupts; + li r3, -3; + b lbl_8019e2a0; +lbl_8019e1c4: + cmpwi r0, 2; + bne lbl_8019e1d8; + bl OSRestoreInterrupts; + li r3, 0; + b lbl_8019e2a0; +lbl_8019e1d8: + li r0, 1; + stw r0, -0x63b8(r13); + bl OSRestoreInterrupts; + bl unk_80169bcc; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8019e288; + bl unk_801671d0; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8019e210; + addi r3, r1, 8; + bl unk_8016799c; + mr r30, r3; +lbl_8019e210: + cmpwi r30, 0; + bne lbl_8019e230; + lis r5, 0x8034; + lwz r3, 8(r1); + lwz r4, 0xc(r1); + addi r5, r5, 0x6d20; + bl unk_80167904; + mr r30, r3; +lbl_8019e230: + cmpwi r30, 0; + bne lbl_8019e248; + lis r4, 0x8034; + addi r3, r31, 0x60; + addi r4, r4, 0x6d20; + bl strcpy; +lbl_8019e248: + bl unk_80167224; + cmpwi r30, 0; + beq lbl_8019e260; + addi r3, r31, 0x130; + crclr 6; + bl OSReport; +lbl_8019e260: + addi r3, r31, 0xa0; + bl OSRegisterResetFunction; + bl OSDisableInterrupts; + li r0, 2; + stw r0, -0x63b8(r13); + bl OSRestoreInterrupts; + lwz r3, -0x71d8(r13); + bl OSRegisterVersion; + li r3, 0; + b lbl_8019e2a0; +lbl_8019e288: + bl OSDisableInterrupts; + li r0, 0; + stw r0, -0x63b8(r13); + bl OSRestoreInterrupts; + mr r3, r30; + bl nandConvertErrorCode; +lbl_8019e2a0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: nandOnShutdown +// Function signature is unknown. +// PAL: 0x8019e2b8..0x8019e390 +MARK_BINARY_BLOB(nandOnShutdown, 0x8019e2b8, 0x8019e390); +asm UNKNOWN_FUNCTION(nandOnShutdown) { + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + cmpwi r3, 0; + bne lbl_8019e368; + cmplwi r4, 2; + bne lbl_8019e360; + li r26, 0; + stw r26, 8(r1); + bl OSGetTime; + lis r5, 0x801a; + mr r27, r4; + mr r28, r3; + addi r4, r1, 8; + addi r3, r5, -7292; + bl unk_8016b40c; + lis r3, 0x1062; + lis r30, 0x8000; + addi r29, r3, 0x4dd3; + li r31, 0x1f4; + b lbl_8019e320; +lbl_8019e314: + lwz r0, 8(r1); + cmpwi r0, 0; + bne lbl_8019e360; +lbl_8019e320: + bl OSGetTime; + lwz r0, 0xf8(r30); + subfc r4, r27, r4; + subfe r3, r28, r3; + li r5, 0; + srwi r0, r0, 2; + mulhwu r0, r29, r0; + srwi r6, r0, 6; + bl __div2i; + xoris r0, r3, 0x8000; + xoris r5, r26, 0x8000; + subfc r3, r31, r4; + subfe r5, r5, r0; + subfe r5, r0, r0; + neg. r5, r5; + bne lbl_8019e314; +lbl_8019e360: + li r3, 1; + b lbl_8019e36c; +lbl_8019e368: + li r3, 1; +lbl_8019e36c: + addi r11, r1, 0x30; + bl _restgpr_26; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + li r0, 1; + stw r0, 0(r4); + blr; +} + +// Symbol: NANDGetCurrentDir +// Function signature is unknown. +// PAL: 0x8019e390..0x8019e40c +MARK_BINARY_BLOB(NANDGetCurrentDir, 0x8019e390, 0x8019e40c); +asm UNKNOWN_FUNCTION(NANDGetCurrentDir) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + lwz r0, -0x63b8(r13); + cmpwi r0, 2; + bne lbl_8019e3bc; + li r0, 1; + b lbl_8019e3c0; +lbl_8019e3bc: + li r0, 0; +lbl_8019e3c0: + cmpwi r0, 0; + bne lbl_8019e3d0; + li r3, -128; + b lbl_8019e3f4; +lbl_8019e3d0: + bl OSDisableInterrupts; + lis r4, 0x8029; + mr r31, r3; + mr r3, r30; + addi r4, r4, -5504; + bl strcpy; + mr r3, r31; + bl OSRestoreInterrupts; + li r3, 0; +lbl_8019e3f4: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDGetHomeDir +// Function signature is unknown. +// PAL: 0x8019e40c..0x8019e460 +MARK_BINARY_BLOB(NANDGetHomeDir, 0x8019e40c, 0x8019e460); +asm UNKNOWN_FUNCTION(NANDGetHomeDir) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + lwz r0, -0x63b8(r13); + cmpwi r0, 2; + bne lbl_8019e42c; + li r0, 1; + b lbl_8019e430; +lbl_8019e42c: + li r0, 0; +lbl_8019e430: + cmpwi r0, 0; + bne lbl_8019e440; + li r3, -128; + b lbl_8019e450; +lbl_8019e440: + lis r4, 0x8034; + addi r4, r4, 0x6d20; + bl strcpy; + li r3, 0; +lbl_8019e450: + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandCallback +// Function signature is unknown. +// PAL: 0x8019e460..0x8019e49c +MARK_BINARY_BLOB(nandCallback, 0x8019e460, 0x8019e49c); +asm UNKNOWN_FUNCTION(nandCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandGetType +// Function signature is unknown. +// PAL: 0x8019e49c..0x8019e770 +MARK_BINARY_BLOB(nandGetType, 0x8019e49c, 0x8019e770); +asm UNKNOWN_FUNCTION(nandGetType) { + nofralloc; + stwu r1, -0x70(r1); + mflr r0; + stw r0, 0x74(r1); + addi r11, r1, 0x70; + bl _savegpr_27; + mr r27, r3; + mr r30, r4; + mr r28, r5; + mr r29, r6; + mr r31, r7; + bl strlen; + cmpwi r3, 0; + bne lbl_8019e4d8; + li r3, -101; + b lbl_8019e758; +lbl_8019e4d8: + cmpwi r29, 0; + beq lbl_8019e5e4; + mr r3, r27; + bl strlen; + cmpwi r3, 0; + bne lbl_8019e500; + addi r3, r28, 0x34; + addi r4, r13, -29136; + bl strcpy; + b lbl_8019e574; +lbl_8019e500: + lbz r0, 0(r27); + cmpwi r0, 0x2f; + bne lbl_8019e514; + li r0, 0; + b lbl_8019e518; +lbl_8019e514: + li r0, 1; +lbl_8019e518: + cmpwi r0, 0; + beq lbl_8019e538; + lis r4, 0x8029; + mr r5, r27; + addi r3, r28, 0x34; + addi r4, r4, -5504; + bl nandConvertPath; + b lbl_8019e574; +lbl_8019e538: + mr r4, r27; + addi r3, r28, 0x34; + bl strcpy; + addi r3, r28, 0x34; + bl strlen; + cmpwi r3, 0; + beq lbl_8019e574; + add r4, r3, r28; + lbz r0, 0x33(r4); + cmpwi r0, 0x2f; + bne lbl_8019e574; + addic. r0, r3, -1; + beq lbl_8019e574; + li r0, 0; + stb r0, 0x33(r4); +lbl_8019e574: + cmpwi r31, 0; + bne lbl_8019e5c0; + lis r4, 0x8029; + addi r3, r28, 0x34; + addi r4, r4, -5412; + li r5, 9; + bl strncmp; + cmpwi r3, 0; + bne lbl_8019e5ac; + lbz r0, 0x3d(r28); + extsb. r0, r0; + beq lbl_8019e5ac; + li r0, 1; + b lbl_8019e5b0; +lbl_8019e5ac: + li r0, 0; +lbl_8019e5b0: + cmpwi r0, 0; + beq lbl_8019e5c0; + li r3, -102; + b lbl_8019e758; +lbl_8019e5c0: + lis r6, 0x801a; + stw r30, 0x88(r28); + mr r7, r28; + addi r3, r28, 0x34; + addi r5, r28, 0x30; + addi r6, r6, -6148; + li r4, 0; + bl unk_8016a1b0; + b lbl_8019e758; +lbl_8019e5e4: + li r0, 0; + mr r3, r27; + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + stw r0, 0x48(r1); + stw r0, 0x4c(r1); + bl strlen; + cmpwi r3, 0; + bne lbl_8019e648; + addi r3, r1, 0x10; + addi r4, r13, -29136; + bl strcpy; + b lbl_8019e6c0; +lbl_8019e648: + lbz r0, 0(r27); + cmpwi r0, 0x2f; + bne lbl_8019e65c; + li r0, 0; + b lbl_8019e660; +lbl_8019e65c: + li r0, 1; +lbl_8019e660: + cmpwi r0, 0; + beq lbl_8019e680; + lis r4, 0x8029; + mr r5, r27; + addi r3, r1, 0x10; + addi r4, r4, -5504; + bl nandConvertPath; + b lbl_8019e6c0; +lbl_8019e680: + mr r4, r27; + addi r3, r1, 0x10; + bl strcpy; + addi r3, r1, 0x10; + bl strlen; + cmpwi r3, 0; + beq lbl_8019e6c0; + addi r0, r1, 0x10; + add r4, r3, r0; + lbz r0, -1(r4); + cmpwi r0, 0x2f; + bne lbl_8019e6c0; + addic. r0, r3, -1; + beq lbl_8019e6c0; + li r0, 0; + stb r0, -1(r4); +lbl_8019e6c0: + cmpwi r31, 0; + bne lbl_8019e70c; + lis r4, 0x8029; + addi r3, r1, 0x10; + addi r4, r4, -5412; + li r5, 9; + bl strncmp; + cmpwi r3, 0; + bne lbl_8019e6f8; + lbz r0, 0x19(r1); + extsb. r0, r0; + beq lbl_8019e6f8; + li r0, 1; + b lbl_8019e6fc; +lbl_8019e6f8: + li r0, 0; +lbl_8019e6fc: + cmpwi r0, 0; + beq lbl_8019e70c; + li r3, -102; + b lbl_8019e758; +lbl_8019e70c: + li r0, 0; + addi r3, r1, 0x10; + stw r0, 8(r1); + addi r5, r1, 8; + li r4, 0; + bl unk_8016a05c; + cmpwi r3, 0; + beq lbl_8019e734; + cmpwi r3, -102; + bne lbl_8019e744; +lbl_8019e734: + li r0, 2; + li r3, 0; + stb r0, 0(r30); + b lbl_8019e758; +lbl_8019e744: + cmpwi r3, -101; + bne lbl_8019e758; + li r0, 1; + li r3, 0; + stb r0, 0(r30); +lbl_8019e758: + addi r11, r1, 0x70; + bl _restgpr_27; + lwz r0, 0x74(r1); + mtlr r0; + addi r1, r1, 0x70; + blr; +} + +// Symbol: NANDGetType +// Function signature is unknown. +// PAL: 0x8019e770..0x8019e7b4 +MARK_BINARY_BLOB(NANDGetType, 0x8019e770, 0x8019e7b4); +asm UNKNOWN_FUNCTION(NANDGetType) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + lwz r0, -0x63b8(r13); + cmpwi r0, 2; + beq lbl_8019e790; + li r3, -128; + b lbl_8019e7a4; +lbl_8019e790: + li r5, 0; + li r6, 0; + li r7, 0; + bl nandGetType; + bl nandConvertErrorCode; +lbl_8019e7a4: + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDPrivateGetTypeAsync +// Function signature is unknown. +// PAL: 0x8019e7b4..0x8019e7fc +MARK_BINARY_BLOB(NANDPrivateGetTypeAsync, 0x8019e7b4, 0x8019e7fc); +asm UNKNOWN_FUNCTION(NANDPrivateGetTypeAsync) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + lwz r0, -0x63b8(r13); + cmpwi r0, 2; + beq lbl_8019e7d4; + li r3, -128; + b lbl_8019e7ec; +lbl_8019e7d4: + stw r5, 4(r6); + mr r5, r6; + li r6, 1; + li r7, 1; + bl nandGetType; + bl nandConvertErrorCode; +lbl_8019e7ec: + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandGetTypeCallback +// Function signature is unknown. +// PAL: 0x8019e7fc..0x8019e874 +MARK_BINARY_BLOB(nandGetTypeCallback, 0x8019e7fc, 0x8019e874); +asm UNKNOWN_FUNCTION(nandGetTypeCallback) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + beq lbl_8019e820; + cmpwi r3, -102; + bne lbl_8019e834; +lbl_8019e820: + lwz r4, 0x88(r4); + li r0, 2; + li r3, 0; + stb r0, 0(r4); + b lbl_8019e84c; +lbl_8019e834: + cmpwi r3, -101; + bne lbl_8019e84c; + lwz r4, 0x88(r4); + li r0, 1; + li r3, 0; + stb r0, 0(r4); +lbl_8019e84c: + bl nandConvertErrorCode; + lwz r12, 4(r31); + mr r4, r31; + mtctr r12; + bctrl; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: nandGetHomeDir +// Function signature is unknown. +// PAL: 0x8019e874..0x8019e880 +MARK_BINARY_BLOB(nandGetHomeDir, 0x8019e874, 0x8019e880); +asm UNKNOWN_FUNCTION(nandGetHomeDir) { + nofralloc; + lis r3, 0x8034; + addi r3, r3, 0x6d20; + blr; +} + +// Symbol: NANDInitBanner +// Function signature is unknown. +// PAL: 0x8019e880..0x8019e95c +MARK_BINARY_BLOB(NANDInitBanner, 0x8019e880, 0x8019e95c); +asm UNKNOWN_FUNCTION(NANDInitBanner) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + lis r7, 1; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r6; + stw r30, 0x18(r1); + mr r30, r5; + addi r5, r7, -3936; + stw r29, 0x14(r1); + mr r29, r4; + li r4, 0; + stw r28, 0x10(r1); + mr r28, r3; + bl memset; + lis r3, 0x5749; + stw r29, 4(r28); + addi r0, r3, 0x424e; + addi r4, r13, -29112; + stw r0, 0(r28); + mr r3, r30; + bl wcscmp; + cmpwi r3, 0; + bne lbl_8019e8f4; + addi r3, r28, 0x20; + addi r4, r13, -29108; + li r5, 0x20; + bl wcsncpy; + b lbl_8019e904; +lbl_8019e8f4: + mr r4, r30; + addi r3, r28, 0x20; + li r5, 0x20; + bl wcsncpy; +lbl_8019e904: + mr r3, r31; + addi r4, r13, -29112; + bl wcscmp; + cmpwi r3, 0; + bne lbl_8019e92c; + addi r3, r28, 0x60; + addi r4, r13, -29108; + li r5, 0x20; + bl wcsncpy; + b lbl_8019e93c; +lbl_8019e92c: + mr r4, r31; + addi r3, r28, 0x60; + li r5, 0x20; + bl wcsncpy; +lbl_8019e93c: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDSecretGetUsage +// Function signature is unknown. +// PAL: 0x8019e95c..0x8019ea14 +MARK_BINARY_BLOB(NANDSecretGetUsage, 0x8019e95c, 0x8019ea14); +asm UNKNOWN_FUNCTION(NANDSecretGetUsage) { + nofralloc; + stwu r1, -0x60(r1); + mflr r0; + stw r0, 0x64(r1); + stw r31, 0x5c(r1); + mr r31, r5; + stw r30, 0x58(r1); + mr r30, r4; + stw r29, 0x54(r1); + mr r29, r3; + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019e994; + li r3, -128; + b lbl_8019e9f8; +lbl_8019e994: + li r0, 0; + mr r4, r29; + stw r0, 8(r1); + addi r3, r1, 8; + stw r0, 0xc(r1); + stw r0, 0x10(r1); + stw r0, 0x14(r1); + stw r0, 0x18(r1); + stw r0, 0x1c(r1); + stw r0, 0x20(r1); + stw r0, 0x24(r1); + stw r0, 0x28(r1); + stw r0, 0x2c(r1); + stw r0, 0x30(r1); + stw r0, 0x34(r1); + stw r0, 0x38(r1); + stw r0, 0x3c(r1); + stw r0, 0x40(r1); + stw r0, 0x44(r1); + bl nandGenerateAbsPath; + mr r4, r30; + mr r5, r31; + addi r3, r1, 8; + bl unk_8016ab3c; + bl nandConvertErrorCode; +lbl_8019e9f8: + lwz r0, 0x64(r1); + lwz r31, 0x5c(r1); + lwz r30, 0x58(r1); + lwz r29, 0x54(r1); + mtlr r0; + addi r1, r1, 0x60; + blr; +} + +// Symbol: nandCalcUsage +// Function signature is unknown. +// PAL: 0x8019ea14..0x8019ead0 +MARK_BINARY_BLOB(nandCalcUsage, 0x8019ea14, 0x8019ead0); +asm UNKNOWN_FUNCTION(nandCalcUsage) { + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + li r31, 0; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + stw r31, 0(r3); + li r3, -117; + stw r31, 0(r4); + b lbl_8019eaa4; +lbl_8019ea50: + stw r31, 0xc(r1); + addi r4, r1, 0xc; + addi r5, r1, 8; + stw r31, 8(r1); + lwz r3, 0(r30); + bl unk_8016ab3c; + cmpwi r3, 0; + bne lbl_8019ea94; + lwz r4, 0(r28); + lwz r0, 0xc(r1); + add r0, r4, r0; + stw r0, 0(r28); + lwz r4, 0(r29); + lwz r0, 8(r1); + add r0, r4, r0; + stw r0, 0(r29); + b lbl_8019eaa0; +lbl_8019ea94: + cmpwi r3, -106; + bne lbl_8019eab0; + li r3, 0; +lbl_8019eaa0: + addi r30, r30, 4; +lbl_8019eaa4: + lwz r0, 0(r30); + cmpwi r0, 0; + bne lbl_8019ea50; +lbl_8019eab0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +} + +// Symbol: NANDCheck +// Function signature is unknown. +// PAL: 0x8019ead0..0x8019ebd8 +MARK_BINARY_BLOB(NANDCheck, 0x8019ead0, 0x8019ebd8); +asm UNKNOWN_FUNCTION(NANDCheck) { + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + li r0, -1; + stw r31, 0x2c(r1); + mr r31, r5; + stw r30, 0x28(r1); + mr r30, r4; + stw r29, 0x24(r1); + mr r29, r3; + stw r0, 0x14(r1); + stw r0, 0x10(r1); + stw r0, 0xc(r1); + stw r0, 8(r1); + bl nandIsInitialized; + cmpwi r3, 0; + bne lbl_8019eb1c; + li r3, -128; + b lbl_8019ebbc; +lbl_8019eb1c: + bl nandGetHomeDir; + addi r4, r1, 0x14; + addi r5, r1, 0x10; + bl unk_8016ab3c; + cmpwi r3, 0; + beq lbl_8019eb3c; + bl nandConvertErrorCode; + b lbl_8019ebbc; +lbl_8019eb3c: + lis r5, 0x8029; + addi r3, r1, 0xc; + addi r4, r1, 8; + addi r5, r5, -5136; + bl nandCalcUsage; + cmpwi r3, 0; + beq lbl_8019eb60; + bl nandConvertErrorCode; + b lbl_8019ebbc; +lbl_8019eb60: + lwz r0, 0x14(r1); + li r5, 0; + lwz r4, 8(r1); + add r0, r0, r29; + lwz r3, 0xc(r1); + cmplwi r0, 0x400; + lwz r0, 0x10(r1); + ble lbl_8019eb84; + ori r5, r5, 1; +lbl_8019eb84: + add r0, r0, r30; + cmplwi r0, 0x21; + ble lbl_8019eb94; + ori r5, r5, 2; +lbl_8019eb94: + add r0, r3, r29; + cmplwi r0, 0x4400; + ble lbl_8019eba4; + ori r5, r5, 4; +lbl_8019eba4: + add r0, r4, r30; + cmplwi r0, 0xfa0; + ble lbl_8019ebb4; + ori r5, r5, 8; +lbl_8019ebb4: + stw r5, 0(r31); + li r3, 0; +lbl_8019ebbc: + lwz r0, 0x34(r1); + lwz r31, 0x2c(r1); + lwz r30, 0x28(r1); + lwz r29, 0x24(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; +} + +// Symbol: reserveFileDescriptor +// Function signature is unknown. +// PAL: 0x8019ebd8..0x8019ec2c +MARK_BINARY_BLOB(reserveFileDescriptor, 0x8019ebd8, 0x8019ec2c); +asm UNKNOWN_FUNCTION(reserveFileDescriptor) { + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + bl OSDisableInterrupts; + lwz r0, -0x71a0(r13); + cmpwi r0, -255; + bne lbl_8019ec08; + li r0, -254; + li r31, 0; + stw r0, -0x71a0(r13); + b lbl_8019ec0c; +lbl_8019ec08: + li r31, 1; +lbl_8019ec0c: + bl OSRestoreInterrupts; + cntlzw r0, r31; + lwz r31, 0xc(r1); + srwi r3, r0, 5; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} + +// Symbol: NANDLoggingAddMessageAsync +// Function signature is unknown. +// PAL: 0x8019ec2c..0x8019ed24 +MARK_BINARY_BLOB(NANDLoggingAddMessageAsync, 0x8019ec2c, 0x8019ed24); +asm UNKNOWN_FUNCTION(NANDLoggingAddMessageAsync) { + nofralloc; + stwu r1, -0x80(r1); + mflr r0; + stw r0, 0x84(r1); + stw r31, 0x7c(r1); + mr r31, r4; + stw r30, 0x78(r1); + mr r30, r3; + bne cr1, lbl_8019ec6c; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_8019ec6c: + stw r3, 8(r1); + stw r4, 0xc(r1); + stw r5, 0x10(r1); + stw r6, 0x14(r1); + stw r7, 0x18(r1); + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + bl reserveFileDescriptor; + cmpwi r3, 0; + bne lbl_8019eca0; + li r3, 0; + b lbl_8019ed0c; +lbl_8019eca0: + addi r4, r1, 0x88; + addi r0, r1, 8; + lis r5, 0x200; + lis r3, 0x8034; + stw r5, 0x68(r1); + addi r6, r1, 0x68; + mr r5, r31; + addi r3, r3, 0x6d80; + stw r4, 0x6c(r1); + li r4, 0x100; + stw r0, 0x70(r1); + bl vsnprintf; + li r0, 1; + lis r3, 0x8029; + lis r5, 0x801a; + stw r30, -0x63b0(r13); + addi r3, r3, -5088; + li r4, 3; + stw r0, -0x63ac(r13); + addi r5, r5, -4828; + li r6, 0; + bl unk_8016af24; + cmpwi r3, 0; + bne lbl_8019ed08; + li r3, 1; + b lbl_8019ed0c; +lbl_8019ed08: + li r3, 0; +lbl_8019ed0c: + lwz r0, 0x84(r1); + lwz r31, 0x7c(r1); + lwz r30, 0x78(r1); + mtlr r0; + addi r1, r1, 0x80; + blr; +} + +// Symbol: asyncRoutine +// Function signature is unknown. +// PAL: 0x8019ed24..0x8019f1a8 +MARK_BINARY_BLOB(asyncRoutine, 0x8019ed24, 0x8019f1a8); +asm UNKNOWN_FUNCTION(asyncRoutine) { + nofralloc; + stwu r1, -0x90(r1); + mflr r0; + stw r0, 0x94(r1); + stw r31, 0x8c(r1); + mr r31, r3; + stw r30, 0x88(r1); + lis r30, 0x8034; + addi r30, r30, 0x6d80; + lwz r4, -0x63ac(r13); + addi r0, r4, 1; + cmpwi r0, 2; + stw r0, -0x63ac(r13); + bne lbl_8019edbc; + cmpwi r3, 0; + blt lbl_8019eda0; + lis r6, 0x801a; + stw r3, -0x71a0(r13); + addi r6, r6, -4828; + li r4, 0; + li r5, 0; + li r7, 0; + bl unk_8016b170; + cmpwi r3, 0; + beq lbl_8019f190; + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019eda0: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019edbc: + cmpwi r0, 3; + bne lbl_8019ee28; + cmpwi r3, 0; + bne lbl_8019ee0c; + lis r6, 0x801a; + lwz r3, -0x71a0(r13); + addi r4, r30, 0x100; + li r5, 0x100; + addi r6, r6, -4828; + li r7, 0; + bl unk_8016b21c; + cmpwi r3, 0; + beq lbl_8019f190; + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019ee0c: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019ee28: + cmpwi r0, 4; + bne lbl_8019ee94; + cmpwi r3, 0x100; + bne lbl_8019ee78; + lis r6, 0x801a; + lwz r3, -0x71a0(r13); + addi r6, r6, -4828; + li r4, 0; + li r5, 0; + li r7, 0; + bl unk_8016b170; + cmpwi r3, 0; + beq lbl_8019f190; + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019ee78: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019ee94: + cmpwi r0, 5; + bne lbl_8019eff0; + cmpwi r3, 0; + bne lbl_8019efd4; + addi r3, r30, 0x100; + li r0, 0; + stb r0, 0xff(r3); + bl atoi; + mr r31, r3; + addi r3, r30, 0x200; + li r4, 0x20; + li r5, 0xfe; + bl memset; + bl OSGetTime; + addi r5, r1, 0x58; + bl OSTicksToCalendarTime; + bl nandGetHomeDir; + mr r4, r3; + addi r3, r1, 0x18; + addi r4, r4, 7; + li r5, 0x11; + bl strncpy; + li r0, 0x2d; + li r4, 0; + stb r4, 0x29(r1); + lis r3, 0x8208; + lis r5, 0x8029; + addi r4, r1, 0x18; + stb r0, 0x20(r1); + addi r0, r3, 0x2083; + lwz r3, 0x5c(r1); + mulhw r0, r0, r31; + addi r6, r30, 0; + stw r3, 8(r1); + addi r5, r5, -5060; + lwz r3, 0x58(r1); + add r0, r0, r31; + stw r3, 0xc(r1); + srawi r0, r0, 5; + addi r3, r30, 0x200; + stw r4, 0x10(r1); + srwi r4, r0, 0x1f; + add r0, r0, r4; + stw r6, 0x14(r1); + mulli r0, r0, 0x3f; + li r4, 0x100; + lwz r8, 0x68(r1); + subf r6, r0, r31; + lwz r7, 0x6c(r1); + lwz r9, 0x64(r1); + addi r6, r6, 1; + lwz r10, 0x60(r1); + addi r8, r8, 1; + crclr 6; + bl snprintf; + cmpwi r3, 0x100; + bge lbl_8019ef84; + addi r4, r30, 0x200; + li r0, 0x20; + stbx r0, r4, r3; +lbl_8019ef84: + addi r4, r30, 0x200; + li r3, 0xd; + li r0, 0xa; + lis r6, 0x801a; + stb r3, 0xfe(r4); + addi r6, r6, -4828; + lwz r3, -0x71a0(r13); + li r5, 0x100; + stb r0, 0xff(r4); + li r7, 0; + bl unk_8016b2e0; + cmpwi r3, 0; + beq lbl_8019f190; + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019efd4: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019eff0: + cmpwi r0, 6; + bne lbl_8019f068; + cmpwi r3, 0x100; + bne lbl_8019f04c; + addi r3, r30, 0x100; + bl atoi; + mr r0, r3; + lis r6, 0x801a; + lwz r3, -0x71a0(r13); + slwi r4, r0, 8; + addi r6, r6, -4828; + li r5, 0; + li r7, 0; + bl unk_8016b170; + cmpwi r3, 0; + beq lbl_8019f190; + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019f04c: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019f068: + cmpwi r0, 7; + bne lbl_8019f0e0; + addi r3, r30, 0x100; + bl atoi; + slwi r0, r3, 8; + cmpw r31, r0; + bne lbl_8019f0c4; + lis r6, 0x801a; + lwz r3, -0x71a0(r13); + addi r4, r30, 0x200; + li r5, 0x100; + addi r6, r6, -4828; + li r7, 0; + bl unk_8016b2e0; + cmpwi r3, 0; + beq lbl_8019f190; + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019f0c4: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019f0e0: + cmpwi r0, 8; + bne lbl_8019f144; + cmpwi r3, 0x100; + bne lbl_8019f128; + lis r4, 0x801a; + lwz r3, -0x71a0(r13); + addi r4, r4, -4828; + li r5, 0; + bl unk_8016b388; + cmpwi r3, 0; + beq lbl_8019f190; + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019f128: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019f144: + cmpwi r0, 9; + bne lbl_8019f190; + cmpwi r3, 0; + bne lbl_8019f178; + lwz r12, -0x63b0(r13); + li r0, -255; + stw r0, -0x71a0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 1; + mtctr r12; + bctrl; + b lbl_8019f190; +lbl_8019f178: + lwz r12, -0x63b0(r13); + cmpwi r12, 0; + beq lbl_8019f190; + li r3, 0; + mtctr r12; + bctrl; +lbl_8019f190: + lwz r0, 0x94(r1); + lwz r31, 0x8c(r1); + lwz r30, 0x88(r1); + mtlr r0; + addi r1, r1, 0x90; + blr; +} + From 3374e177247958162e3b9c242c6a9836d40804a3 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Thu, 22 Jul 2021 20:10:57 +0200 Subject: [PATCH 108/477] CI: Run mkwutil.graphic (#46) * CI: Run mkwutil.graphic * CI: switch graphic.py to Ubuntu --- .github/workflows/pages.yml | 27 +++++++++++++++++++++++++++ mkwutil/graphic.py | 8 +++++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 000000000..9e5ae89b3 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,27 @@ +--- +on: [push, pull_request] +name: GitHub Pages +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + + - name: Install dependencies + run: pip3 install -r requirements.txt + + - name: Run graphic.py + run: python3 -m mkwutil.graphic + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@4.1.4 + with: + branch: gh-pages + folder: out/website + clean: true + dry-run: ${{ github.ref != 'refs/heads/master' }} diff --git a/mkwutil/graphic.py b/mkwutil/graphic.py index 0343eec56..e98632c35 100644 --- a/mkwutil/graphic.py +++ b/mkwutil/graphic.py @@ -91,13 +91,15 @@ def lib_boxes(): if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-o", "--output", type=Path, default="out.html") + parser.add_argument("-o", "--output", type=Path, default="./out/website", help="Output dir") parser.add_argument("-s", "--silent", action="store_true", help="Don't open web browser") args = parser.parse_args() - with open(args.output, "w") as file: + args.output.mkdir(parents=True, exist_ok=True) + index_path = args.output / "index.html" + with open(index_path, "w") as file: jinja_env.get_template("index.html.j2").stream({ "dol_decomp": standard_boxes(), "dol_libraries": lib_boxes(), }).dump(file) if not args.silent: - webbrowser.open(args.output) + webbrowser.open(index_path) From a6eac70be7ae2f8f294637b4a8098a0fe6cdabdf Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Thu, 22 Jul 2021 23:40:58 +0200 Subject: [PATCH 109/477] Clean up RVL/NAND (#49) * Clean up nand.c * lol * Disable clang-format in inline asm * lol --- mkwutil/gen_asm/source.c.j2 | 2 + source/decomp.h | 32 ++++++ source/platform/ctype.c | 28 +++--- source/platform/stdio.h | 3 + source/platform/wchar.h | 6 ++ source/rvl/nand/nand.c | 188 ++++++++---------------------------- source/rvl/nand/nand.h | 85 ++++++++++++++++ 7 files changed, 179 insertions(+), 165 deletions(-) create mode 100644 source/rvl/nand/nand.h diff --git a/mkwutil/gen_asm/source.c.j2 b/mkwutil/gen_asm/source.c.j2 index fbc16bdb0..42cfc283f 100644 --- a/mkwutil/gen_asm/source.c.j2 +++ b/mkwutil/gen_asm/source.c.j2 @@ -19,10 +19,12 @@ UNKNOWN_FUNCTION({{ function.name }}); // PAL: {{ function.addr | addr }}..{{ (function.addr+function.size) | addr }} MARK_BINARY_BLOB({{ function.name }}, {{ function.addr | addr }}, {{ (function.addr+function.size) | addr }}); asm UNKNOWN_FUNCTION({{ function.name }}) { +// clang-format off nofralloc; {%- for line in function.inline_asm %} {{ line }} {%- endfor %} +// clang-format on } {% endfor -%} diff --git a/source/decomp.h b/source/decomp.h index 3e46a29e3..d6655d3fd 100644 --- a/source/decomp.h +++ b/source/decomp.h @@ -8,3 +8,35 @@ SECTION_BINARY_BLOBS static const char MARK_BINARY_BLOB_##name[] = \ "BINARY_BLOB: " #name "\t" #start "\t" #stop "\n" \ __attribute__((force_export)) + +// Compiler intrinsics. + +// PAL: 0x80021590 +extern void _savegpr_23(void); +// PAL: 0x80021594 +extern void _savegpr_24(void); +// PAL: 0x80021598 +extern void _savegpr_25(void); +// PAL: 0x8002159c +extern void _savegpr_26(void); +// PAL: 0x800215a0 +extern void _savegpr_27(void); +// PAL: 0x800215dc +extern void _restgpr_23(void); +// PAL: 0x800215e0 +extern void _restgpr_24(void); +// PAL: 0x800215e4 +extern void _restgpr_25(void); +// PAL: 0x800215e8 +extern void _restgpr_26(void); +// PAL: 0x800215ec +extern void _restgpr_27(void); + +extern void __div2u(void); +// PAL: 0x800216f0 +extern void __div2i(void); +extern void __mod2u(void); +extern void __mod2i(void); +extern void __shl2i(void); +extern void __shr2u(void); +extern void __shr2i(void); diff --git a/source/platform/ctype.c b/source/platform/ctype.c index efaa74d31..6b18c826d 100644 --- a/source/platform/ctype.c +++ b/source/platform/ctype.c @@ -1,18 +1,12 @@ const unsigned short __ctype_mapC[256] = { - cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, - cntl, csbl, ctsp, ctsp, ctsp, ctsp, cntl, cntl, - cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, - cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, - sblp, punc, punc, punc, punc, punc, punc, punc, - punc, punc, punc, punc, punc, punc, punc, punc, - dhex, dhex, dhex, dhex, dhex, dhex, dhex, dhex, - dhex, dhex, punc, punc, punc, punc, punc, punc, - punc, uhex, uhex, uhex, uhex, uhex, uhex, uppc, - uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, - uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, - uppc, uppc, uppc, punc, punc, punc, punc, punc, - punc, lhex, lhex, lhex, lhex, lhex, lhex, lowc, - lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, - lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, - lowc, lowc, lowc, punc, punc, punc, punc, cntl -}; + cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, csbl, ctsp, ctsp, + ctsp, ctsp, cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, + cntl, cntl, cntl, cntl, cntl, cntl, cntl, cntl, sblp, punc, punc, punc, + punc, punc, punc, punc, punc, punc, punc, punc, punc, punc, punc, punc, + dhex, dhex, dhex, dhex, dhex, dhex, dhex, dhex, dhex, dhex, punc, punc, + punc, punc, punc, punc, punc, uhex, uhex, uhex, uhex, uhex, uhex, uppc, + uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, uppc, + uppc, uppc, uppc, uppc, uppc, uppc, uppc, punc, punc, punc, punc, punc, + punc, lhex, lhex, lhex, lhex, lhex, lhex, lowc, lowc, lowc, lowc, lowc, + lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, lowc, + lowc, lowc, lowc, punc, punc, punc, punc, cntl}; diff --git a/source/platform/stdio.h b/source/platform/stdio.h index 97af7871c..911e06d8f 100644 --- a/source/platform/stdio.h +++ b/source/platform/stdio.h @@ -64,8 +64,11 @@ struct _FILE { #define SEEK_END 2 #define SEEK_SET 0 +typedef char* va_list; + int sprintf(char* str, const char* format, ...); int snprintf(char* s, size_t n, const char* format, ...); +int vsnprintf(char* s, size_t n, const char* format, va_list arg); int sscanf(const char* str, const char* format, ...); diff --git a/source/platform/wchar.h b/source/platform/wchar.h index a2490932a..d1082f319 100644 --- a/source/platform/wchar.h +++ b/source/platform/wchar.h @@ -8,3 +8,9 @@ u32 wcslen(const wchar_t*); u32 wcsnlen_s(const wchar_t*, u32); wchar_t* wcscpy(wchar_t*, const wchar_t*); + +// PAL: 0x800179d0 +wchar_t* wcsncpy(wchar_t* dest, const wchar_t* src, size_t n); + +// PAL: 0x80017a14 +int wcscmp(const wchar_t* s1, const wchar_t* s2); diff --git a/source/rvl/nand/nand.c b/source/rvl/nand/nand.c index f5b5ae69b..a8f8f2055 100644 --- a/source/rvl/nand/nand.c +++ b/source/rvl/nand/nand.c @@ -1,50 +1,14 @@ +#include "nand.h" #include "decomp.h" +#include +#include +#include +#include + +#include + // Extern function references. -// PAL: 0x80006038 -extern UNKNOWN_FUNCTION(memset); -// PAL: 0x8001182c -extern UNKNOWN_FUNCTION(vsnprintf); -// PAL: 0x80011938 -extern UNKNOWN_FUNCTION(snprintf); -// PAL: 0x80011a2c -extern UNKNOWN_FUNCTION(sprintf); -// PAL: 0x80013120 -extern UNKNOWN_FUNCTION(strcpy); -// PAL: 0x800131e0 -extern UNKNOWN_FUNCTION(strncpy); -// PAL: 0x80013224 -extern UNKNOWN_FUNCTION(strcat); -// PAL: 0x8001329c -extern UNKNOWN_FUNCTION(strcmp); -// PAL: 0x800133b8 -extern UNKNOWN_FUNCTION(strncmp); -// PAL: 0x8001543c -extern UNKNOWN_FUNCTION(atoi); -// PAL: 0x800179d0 -extern UNKNOWN_FUNCTION(wcsncpy); -// PAL: 0x80017a14 -extern UNKNOWN_FUNCTION(wcscmp); -// PAL: 0x80021254 -extern UNKNOWN_FUNCTION(strlen); -// PAL: 0x80021590 -extern UNKNOWN_FUNCTION(_savegpr_23); -// PAL: 0x80021598 -extern UNKNOWN_FUNCTION(_savegpr_25); -// PAL: 0x8002159c -extern UNKNOWN_FUNCTION(_savegpr_26); -// PAL: 0x800215a0 -extern UNKNOWN_FUNCTION(_savegpr_27); -// PAL: 0x800215dc -extern UNKNOWN_FUNCTION(_restgpr_23); -// PAL: 0x800215e4 -extern UNKNOWN_FUNCTION(_restgpr_25); -// PAL: 0x800215e8 -extern UNKNOWN_FUNCTION(_restgpr_26); -// PAL: 0x800215ec -extern UNKNOWN_FUNCTION(_restgpr_27); -// PAL: 0x800216f0 -extern UNKNOWN_FUNCTION(__div2i); // PAL: 0x801671d0 extern UNKNOWN_FUNCTION(unk_801671d0); // PAL: 0x80167224 @@ -115,68 +79,37 @@ extern UNKNOWN_FUNCTION(unk_8016b40c); extern UNKNOWN_FUNCTION(OSRegisterVersion); // PAL: 0x801a25d0 extern UNKNOWN_FUNCTION(OSReport); -// PAL: 0x801a65ac -extern UNKNOWN_FUNCTION(OSDisableInterrupts); -// PAL: 0x801a65d4 -extern UNKNOWN_FUNCTION(OSRestoreInterrupts); // PAL: 0x801a8238 extern UNKNOWN_FUNCTION(OSRegisterResetFunction); -// PAL: 0x801aad5c -extern UNKNOWN_FUNCTION(OSGetTime); -// PAL: 0x801aafa8 -extern UNKNOWN_FUNCTION(OSTicksToCalendarTime); // Function declarations. UNKNOWN_FUNCTION(nandCreate); -UNKNOWN_FUNCTION(NANDCreate); UNKNOWN_FUNCTION(NANDPrivateCreate); UNKNOWN_FUNCTION(NANDPrivateCreateAsync); -UNKNOWN_FUNCTION(NANDDelete); UNKNOWN_FUNCTION(NANDPrivateDelete); UNKNOWN_FUNCTION(NANDPrivateDeleteAsync); -UNKNOWN_FUNCTION(NANDRead); -UNKNOWN_FUNCTION(NANDReadAsync); -UNKNOWN_FUNCTION(NANDWrite); -UNKNOWN_FUNCTION(NANDWriteAsync); -UNKNOWN_FUNCTION(NANDSeek); -UNKNOWN_FUNCTION(NANDSeekAsync); UNKNOWN_FUNCTION(nandCreateDir); -UNKNOWN_FUNCTION(NANDCreateDir); UNKNOWN_FUNCTION(NANDPrivateCreateDir); UNKNOWN_FUNCTION(NANDPrivateCreateDirAsync); UNKNOWN_FUNCTION(nandMove); -UNKNOWN_FUNCTION(NANDMove); -UNKNOWN_FUNCTION(NANDGetLength); UNKNOWN_FUNCTION(nandGetFileStatusAsyncCallback); -UNKNOWN_FUNCTION(NANDGetLengthAsync); UNKNOWN_FUNCTION(nandComposePerm); UNKNOWN_FUNCTION(nandSplitPerm); UNKNOWN_FUNCTION(nandGetStatus); UNKNOWN_FUNCTION(nandGetStatusCallback); -UNKNOWN_FUNCTION(NANDGetStatus); UNKNOWN_FUNCTION(NANDPrivateGetStatus); UNKNOWN_FUNCTION(NANDPrivateGetStatusAsync); UNKNOWN_FUNCTION(nandSetStatus); -UNKNOWN_FUNCTION(NANDSetStatus); UNKNOWN_FUNCTION(NANDPrivateSetStatus); -UNKNOWN_FUNCTION(NANDSetUserData); -UNKNOWN_FUNCTION(NANDGetUserData); UNKNOWN_FUNCTION(nandOpen); -UNKNOWN_FUNCTION(NANDOpen); UNKNOWN_FUNCTION(NANDPrivateOpen); -UNKNOWN_FUNCTION(NANDOpenAsync); UNKNOWN_FUNCTION(NANDPrivateOpenAsync); UNKNOWN_FUNCTION(nandOpenCallback); -UNKNOWN_FUNCTION(NANDClose); -UNKNOWN_FUNCTION(NANDCloseAsync); -UNKNOWN_FUNCTION(NANDSafeOpen); UNKNOWN_FUNCTION(nandSafeOpen); -UNKNOWN_FUNCTION(NANDSafeClose); UNKNOWN_FUNCTION(NANDPrivateSafeOpenAsync); UNKNOWN_FUNCTION(nandSafeOpenAsync); UNKNOWN_FUNCTION(nandSafeOpenCallback); UNKNOWN_FUNCTION(nandReadOpenCallback); -UNKNOWN_FUNCTION(NANDSafeCloseAsync); UNKNOWN_FUNCTION(nandSafeCloseCallback); UNKNOWN_FUNCTION(nandReadCloseCallback); UNKNOWN_FUNCTION(nandCloseCallback); @@ -191,20 +124,14 @@ UNKNOWN_FUNCTION(nandReportErrorCode); UNKNOWN_FUNCTION(nandConvertErrorCode); UNKNOWN_FUNCTION(nandGenerateAbsPath); UNKNOWN_FUNCTION(nandGetParentDirectory); -UNKNOWN_FUNCTION(NANDInit); UNKNOWN_FUNCTION(nandOnShutdown); -UNKNOWN_FUNCTION(NANDGetCurrentDir); -UNKNOWN_FUNCTION(NANDGetHomeDir); UNKNOWN_FUNCTION(nandCallback); UNKNOWN_FUNCTION(nandGetType); -UNKNOWN_FUNCTION(NANDGetType); UNKNOWN_FUNCTION(NANDPrivateGetTypeAsync); UNKNOWN_FUNCTION(nandGetTypeCallback); UNKNOWN_FUNCTION(nandGetHomeDir); -UNKNOWN_FUNCTION(NANDInitBanner); UNKNOWN_FUNCTION(NANDSecretGetUsage); UNKNOWN_FUNCTION(nandCalcUsage); -UNKNOWN_FUNCTION(NANDCheck); UNKNOWN_FUNCTION(reserveFileDescriptor); UNKNOWN_FUNCTION(NANDLoggingAddMessageAsync); UNKNOWN_FUNCTION(asyncRoutine); @@ -296,10 +223,9 @@ asm UNKNOWN_FUNCTION(nandCreate) { } // Symbol: NANDCreate -// Function signature is unknown. // PAL: 0x8019b43c..0x8019b4b0 MARK_BINARY_BLOB(NANDCreate, 0x8019b43c, 0x8019b4b0); -asm UNKNOWN_FUNCTION(NANDCreate) { +asm s32 NANDCreate(const char*, u8, u8) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -414,10 +340,9 @@ asm UNKNOWN_FUNCTION(NANDPrivateCreateAsync) { } // Symbol: NANDDelete -// Function signature is unknown. // PAL: 0x8019b59c..0x8019b64c MARK_BINARY_BLOB(NANDDelete, 0x8019b59c, 0x8019b64c); -asm UNKNOWN_FUNCTION(NANDDelete) { +asm s32 NANDDelete(const char*) { nofralloc; stwu r1, -0x50(r1); mflr r0; @@ -576,10 +501,9 @@ asm UNKNOWN_FUNCTION(NANDPrivateDeleteAsync) { } // Symbol: NANDRead -// Function signature is unknown. // PAL: 0x8019b7a4..0x8019b80c MARK_BINARY_BLOB(NANDRead, 0x8019b7a4, 0x8019b80c); -asm UNKNOWN_FUNCTION(NANDRead) { +asm s32 NANDRead(NANDFileInfo*, void*, u32) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -612,10 +536,9 @@ asm UNKNOWN_FUNCTION(NANDRead) { } // Symbol: NANDReadAsync -// Function signature is unknown. // PAL: 0x8019b80c..0x8019b884 MARK_BINARY_BLOB(NANDReadAsync, 0x8019b80c, 0x8019b884); -asm UNKNOWN_FUNCTION(NANDReadAsync) { +asm s32 NANDReadAsync(NANDFileInfo*, void*, u32, NANDCallback) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -652,10 +575,9 @@ asm UNKNOWN_FUNCTION(NANDReadAsync) { } // Symbol: NANDWrite -// Function signature is unknown. // PAL: 0x8019b884..0x8019b8ec MARK_BINARY_BLOB(NANDWrite, 0x8019b884, 0x8019b8ec); -asm UNKNOWN_FUNCTION(NANDWrite) { +asm s32 NANDWrite(NANDFileInfo*, const void*, u32) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -688,10 +610,9 @@ asm UNKNOWN_FUNCTION(NANDWrite) { } // Symbol: NANDWriteAsync -// Function signature is unknown. // PAL: 0x8019b8ec..0x8019b964 MARK_BINARY_BLOB(NANDWriteAsync, 0x8019b8ec, 0x8019b964); -asm UNKNOWN_FUNCTION(NANDWriteAsync) { +asm s32 NANDWriteAsync(NANDFileInfo*, const void*, u32, NANDCallback) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -728,10 +649,9 @@ asm UNKNOWN_FUNCTION(NANDWriteAsync) { } // Symbol: NANDSeek -// Function signature is unknown. // PAL: 0x8019b964..0x8019ba04 MARK_BINARY_BLOB(NANDSeek, 0x8019b964, 0x8019ba04); -asm UNKNOWN_FUNCTION(NANDSeek) { +asm s32 NANDSeek(NANDFileInfo*, s32, s32) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -783,10 +703,9 @@ asm UNKNOWN_FUNCTION(NANDSeek) { } // Symbol: NANDSeekAsync -// Function signature is unknown. // PAL: 0x8019ba04..0x8019bab4 MARK_BINARY_BLOB(NANDSeekAsync, 0x8019ba04, 0x8019bab4); -asm UNKNOWN_FUNCTION(NANDSeekAsync) { +asm s32 NANDSeekAsync(NANDFileInfo*, s32, s32, NANDCallback) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -929,10 +848,9 @@ asm UNKNOWN_FUNCTION(nandCreateDir) { } // Symbol: NANDCreateDir -// Function signature is unknown. // PAL: 0x8019bbe0..0x8019bc54 MARK_BINARY_BLOB(NANDCreateDir, 0x8019bbe0, 0x8019bc54); -asm UNKNOWN_FUNCTION(NANDCreateDir) { +asm s32 NANDCreateDir(const char*, u8, u8) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -1167,10 +1085,9 @@ asm UNKNOWN_FUNCTION(nandMove) { } // Symbol: NANDMove -// Function signature is unknown. // PAL: 0x8019bee8..0x8019bf4c MARK_BINARY_BLOB(NANDMove, 0x8019bee8, 0x8019bf4c); -asm UNKNOWN_FUNCTION(NANDMove) { +asm s32 NANDMove(const char*, const char*) { nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -1202,10 +1119,9 @@ asm UNKNOWN_FUNCTION(NANDMove) { } // Symbol: NANDGetLength -// Function signature is unknown. // PAL: 0x8019bf4c..0x8019bfd4 MARK_BINARY_BLOB(NANDGetLength, 0x8019bf4c, 0x8019bfd4); -asm UNKNOWN_FUNCTION(NANDGetLength) { +asm s32 NANDGetLength(NANDFileInfo*, u32*) { nofralloc; clrlwi r11, r1, 0x1b; mr r12, r1; @@ -1286,10 +1202,10 @@ asm UNKNOWN_FUNCTION(nandGetFileStatusAsyncCallback) { } // Symbol: NANDGetLengthAsync -// Function signature is unknown. // PAL: 0x8019c048..0x8019c0d8 MARK_BINARY_BLOB(NANDGetLengthAsync, 0x8019c048, 0x8019c0d8); -asm UNKNOWN_FUNCTION(NANDGetLengthAsync) { +asm s32 NANDGetLengthAsync(NANDFileInfo*, u32*, NANDCallback, + NANDCommandBlock*) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -1550,10 +1466,9 @@ asm UNKNOWN_FUNCTION(nandGetStatusCallback) { } // Symbol: NANDGetStatus -// Function signature is unknown. // PAL: 0x8019c380..0x8019c3e4 MARK_BINARY_BLOB(NANDGetStatus, 0x8019c380, 0x8019c3e4); -asm UNKNOWN_FUNCTION(NANDGetStatus) { +asm s32 NANDGetStatus(const char*, NANDStatus*) { nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -1757,10 +1672,9 @@ asm UNKNOWN_FUNCTION(nandSetStatus) { } // Symbol: NANDSetStatus -// Function signature is unknown. // PAL: 0x8019c614..0x8019c678 MARK_BINARY_BLOB(NANDSetStatus, 0x8019c614, 0x8019c678); -asm UNKNOWN_FUNCTION(NANDSetStatus) { +asm s32 NANDSetStatus(const char*, const NANDStatus*) { nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -1827,24 +1741,14 @@ asm UNKNOWN_FUNCTION(NANDPrivateSetStatus) { } // Symbol: NANDSetUserData -// Function signature is unknown. // PAL: 0x8019c6dc..0x8019c6e4 -MARK_BINARY_BLOB(NANDSetUserData, 0x8019c6dc, 0x8019c6e4); -asm UNKNOWN_FUNCTION(NANDSetUserData) { - nofralloc; - stw r4, 0(r3); - blr; +void NANDSetUserData(NANDCommandBlock* block, void* userData) { + block->userData = userData; } // Symbol: NANDGetUserData -// Function signature is unknown. // PAL: 0x8019c6e4..0x8019c6ec -MARK_BINARY_BLOB(NANDGetUserData, 0x8019c6e4, 0x8019c6ec); -asm UNKNOWN_FUNCTION(NANDGetUserData) { - nofralloc; - lwz r3, 0(r3); - blr; -} +void* NANDGetUserData(const NANDCommandBlock* block) { return block->userData; } // Symbol: nandOpen // Function signature is unknown. @@ -1931,10 +1835,9 @@ asm UNKNOWN_FUNCTION(nandOpen) { } // Symbol: NANDOpen -// Function signature is unknown. // PAL: 0x8019c800..0x8019c88c MARK_BINARY_BLOB(NANDOpen, 0x8019c800, 0x8019c88c); -asm UNKNOWN_FUNCTION(NANDOpen) { +asm s32 NANDOpen(const char*, NANDFileInfo*, u8) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -2023,10 +1926,10 @@ asm UNKNOWN_FUNCTION(NANDPrivateOpen) { } // Symbol: NANDOpenAsync -// Function signature is unknown. // PAL: 0x8019c918..0x8019c990 MARK_BINARY_BLOB(NANDOpenAsync, 0x8019c918, 0x8019c990); -asm UNKNOWN_FUNCTION(NANDOpenAsync) { +asm s32 NANDOpenAsync(const char*, NANDFileInfo*, u8, NANDCallback, + NANDCommandBlock*) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -2143,10 +2046,9 @@ asm UNKNOWN_FUNCTION(nandOpenCallback) { } // Symbol: NANDClose -// Function signature is unknown. // PAL: 0x8019ca80..0x8019caec MARK_BINARY_BLOB(NANDClose, 0x8019ca80, 0x8019caec); -asm UNKNOWN_FUNCTION(NANDClose) { +asm s32 NANDClose(NANDFileInfo*) { nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -2185,7 +2087,7 @@ asm UNKNOWN_FUNCTION(NANDClose) { // Function signature is unknown. // PAL: 0x8019caec..0x8019cb74 MARK_BINARY_BLOB(NANDCloseAsync, 0x8019caec, 0x8019cb74); -asm UNKNOWN_FUNCTION(NANDCloseAsync) { +asm s32 NANDCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -2227,10 +2129,9 @@ asm UNKNOWN_FUNCTION(NANDCloseAsync) { } // Symbol: NANDSafeOpen -// Function signature is unknown. // PAL: 0x8019cb74..0x8019cb80 MARK_BINARY_BLOB(NANDSafeOpen, 0x8019cb74, 0x8019cb80); -asm UNKNOWN_FUNCTION(NANDSafeOpen) { +asm s32 NANDSafeOpen(const char*, NANDFileInfo*, u8, void*, u32) { nofralloc; li r8, 0; li r9, 0; @@ -2508,10 +2409,9 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { } // Symbol: NANDSafeClose -// Function signature is unknown. // PAL: 0x8019cf28..0x8019d104 MARK_BINARY_BLOB(NANDSafeClose, 0x8019cf28, 0x8019d104); -asm UNKNOWN_FUNCTION(NANDSafeClose) { +asm s32 NANDSafeClose(NANDFileInfo*) { nofralloc; li r4, 0; b lbl_8019cf30; @@ -3110,10 +3010,9 @@ asm UNKNOWN_FUNCTION(nandReadOpenCallback) { } // Symbol: NANDSafeCloseAsync -// Function signature is unknown. // PAL: 0x8019d720..0x8019d824 MARK_BINARY_BLOB(NANDSafeCloseAsync, 0x8019d720, 0x8019d824); -asm UNKNOWN_FUNCTION(NANDSafeCloseAsync) { +asm s32 NANDSafeCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*) { nofralloc; li r6, 0; b lbl_8019d728; @@ -3959,10 +3858,9 @@ asm UNKNOWN_FUNCTION(nandGetParentDirectory) { } // Symbol: NANDInit -// Function signature is unknown. // PAL: 0x8019e18c..0x8019e2b8 MARK_BINARY_BLOB(NANDInit, 0x8019e18c, 0x8019e2b8); -asm UNKNOWN_FUNCTION(NANDInit) { +asm s32 NANDInit(void) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -4117,10 +4015,9 @@ asm UNKNOWN_FUNCTION(nandOnShutdown) { } // Symbol: NANDGetCurrentDir -// Function signature is unknown. // PAL: 0x8019e390..0x8019e40c MARK_BINARY_BLOB(NANDGetCurrentDir, 0x8019e390, 0x8019e40c); -asm UNKNOWN_FUNCTION(NANDGetCurrentDir) { +asm s32 NANDGetCurrentDir(char[64]) { nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -4160,10 +4057,9 @@ asm UNKNOWN_FUNCTION(NANDGetCurrentDir) { } // Symbol: NANDGetHomeDir -// Function signature is unknown. // PAL: 0x8019e40c..0x8019e460 MARK_BINARY_BLOB(NANDGetHomeDir, 0x8019e40c, 0x8019e460); -asm UNKNOWN_FUNCTION(NANDGetHomeDir) { +asm s32 NANDGetHomeDir(char[64]) { nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -4426,10 +4322,9 @@ asm UNKNOWN_FUNCTION(nandGetType) { } // Symbol: NANDGetType -// Function signature is unknown. // PAL: 0x8019e770..0x8019e7b4 MARK_BINARY_BLOB(NANDGetType, 0x8019e770, 0x8019e7b4); -asm UNKNOWN_FUNCTION(NANDGetType) { +asm s32 NANDGetType(const char*, u8*) { nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -4533,10 +4428,9 @@ asm UNKNOWN_FUNCTION(nandGetHomeDir) { } // Symbol: NANDInitBanner -// Function signature is unknown. // PAL: 0x8019e880..0x8019e95c MARK_BINARY_BLOB(NANDInitBanner, 0x8019e880, 0x8019e95c); -asm UNKNOWN_FUNCTION(NANDInitBanner) { +asm void NANDInitBanner(NANDBanner*, u32, const u16*, const u16*) { nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -4716,10 +4610,9 @@ asm UNKNOWN_FUNCTION(nandCalcUsage) { } // Symbol: NANDCheck -// Function signature is unknown. // PAL: 0x8019ead0..0x8019ebd8 MARK_BINARY_BLOB(NANDCheck, 0x8019ead0, 0x8019ebd8); -asm UNKNOWN_FUNCTION(NANDCheck) { +asm s32 NANDCheck(u32, u32, u32*) { nofralloc; stwu r1, -0x30(r1); mflr r0; @@ -5215,4 +5108,3 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { addi r1, r1, 0x90; blr; } - diff --git a/source/rvl/nand/nand.h b/source/rvl/nand/nand.h new file mode 100644 index 000000000..5fb42c0d9 --- /dev/null +++ b/source/rvl/nand/nand.h @@ -0,0 +1,85 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*NANDCallback)(s32); + +typedef struct NANDFileInfo { + s32 fd; +} NANDFileInfo; + +typedef struct NANDStatus { + u32 uid; + u16 gid; + u8 attr; + u8 perm; +} NANDStatus; + +typedef struct NANDCommandBlock { + void* userData; +} NANDCommandBlock; + +typedef struct { + u32 magic; // 0x0000 + u32 flag; // 0x0004 + u16 animSpeed; // 0x0008 + u8 _unk_0a[22]; // 0x000A + u16 comment[2][32]; // 0x0020 + u8 banner[24576]; // 0x00a0 + u8 icon[8][4608]; // 0x60a0 +} NANDBanner; + +s32 NANDInit(void); + +s32 NANDCreate(const char*, u8, u8); +s32 NANDCreateDir(const char*, u8, u8); + +s32 NANDDelete(const char*); + +s32 NANDOpen(const char*, NANDFileInfo*, u8); +s32 NANDOpenAsync(const char*, NANDFileInfo*, u8, NANDCallback, + NANDCommandBlock*); + +s32 NANDSafeOpen(const char*, NANDFileInfo*, u8, void*, u32); +s32 NANDSafeClose(NANDFileInfo*); +s32 NANDSafeCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*); + +s32 NANDClose(NANDFileInfo*); +s32 NANDCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*); + +s32 NANDRead(NANDFileInfo*, void*, u32); +s32 NANDReadAsync(NANDFileInfo*, void*, u32, NANDCallback); + +s32 NANDWrite(NANDFileInfo*, const void*, u32); +s32 NANDWriteAsync(NANDFileInfo*, const void*, u32, NANDCallback); + +s32 NANDSeek(NANDFileInfo*, s32, s32); +s32 NANDSeekAsync(NANDFileInfo*, s32, s32, NANDCallback); + +s32 NANDMove(const char*, const char*); + +s32 NANDGetLength(NANDFileInfo*, u32*); +s32 NANDGetLengthAsync(NANDFileInfo*, u32*, NANDCallback, NANDCommandBlock*); + +s32 NANDGetStatus(const char*, NANDStatus*); +s32 NANDSetStatus(const char*, const NANDStatus*); + +s32 NANDGetType(const char*, u8*); + +void NANDSetUserData(NANDCommandBlock*, void*); +void* NANDGetUserData(const NANDCommandBlock*); + +s32 NANDGetCurrentDir(char[64]); +s32 NANDGetHomeDir(char[64]); + +void NANDInitBanner(NANDBanner*, u32, const u16*, const u16*); + +s32 NANDCheck(u32, u32, u32*); + +#ifdef __cplusplus +} +#endif From 720f940322c3591664552bb3fe31ca8f0a863b5c Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 23 Jul 2021 10:04:11 +0200 Subject: [PATCH 110/477] gen_asm: Generate C headers (#50) --- mkwutil/gen_asm.py | 62 +++++++++++++++++++++++-------------- mkwutil/gen_asm/source.c.j2 | 6 +--- mkwutil/gen_asm/source.h.j2 | 17 ++++++++++ 3 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 mkwutil/gen_asm/source.h.j2 diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index 50724d9f2..aa43f59c6 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -96,19 +96,22 @@ def dump_section(self): self.dump_section_header() self.dump_section_body() + def addr_in_sym(addr, sym): return sym.addr <= addr < sym.addr + sym.size + class CAsmGenerator: """Generates C files with assembly functions.""" - def __init__(self, data, _slice, symbols, output): + def __init__(self, data, _slice, symbols, out_h, out_c): self.data = data self.slice = _slice self.symbols = symbols self.own_symbols = self.symbols.slice(self.slice.start, self.slice.stop) self.own_symbols.derive_sizes(self.slice.stop) - self.output = output + self.out_h = out_h + self.out_c = out_c # The list of seen extern functions. self.extern_functions_seen = set() self.extern_functions = list() @@ -119,21 +122,20 @@ def __symbols(self, addr): assert ( sym.addr == addr ), f"Currently at {hex(addr)} but next symbol is at {hex(sym.addr)}" - + yield sym addr += sym.size - + assert ( addr == self.slice.stop ), f"Disassembled up to {hex(addr)} but slice goes to {hex(self.slice.stop)}" - # TODO not a good name def dump_section(self): """Writes the C file to output.""" addr = self.slice.start functions = [] - + for sym in self.__symbols(addr): func_body = self.disassemble_function(sym) functions.append( @@ -149,10 +151,14 @@ def dump_section(self): self.extern_functions.sort(key=lambda sym: sym.addr) # Write out to C file. template = jinja_env.get_template("source.c.j2") - stream = template.stream( - functions=functions, extern_functions=self.extern_functions - ) - stream.dump(self.output) + template.stream( + header=Path(self.out_h.name).name, + functions=functions, + extern_functions=self.extern_functions, + ).dump(self.out_c) + # Write out to H file. + template = jinja_env.get_template("source.h.j2") + template.stream(functions=functions).dump(self.out_h) def __jumps_of(self, insns): for ins in insns: @@ -164,7 +170,7 @@ def __jumps_of(self, insns): def __name_addr(self, addr): # If it's a known symbol, use its name. - if addr in self.symbols: + if addr in self.symbols: return self.symbols[addr] # If the target address is unknown we still need to create a symbol. @@ -213,7 +219,6 @@ def __disasm_instructions(self, insns, labels, sorted_labels): # Actual instruction. yield f" {ins.disassemble()};" - def disassemble_function(self, sym): """Generates the inline assembly function body as a stream of lines.""" assert isinstance(sym.size, int) @@ -225,7 +230,7 @@ def disassemble_function(self, sym): insns = list(disasm_iter(data, sym.addr)) labels = self.__analyze_jumps(sym, insns) - sorted_labels = list(sorted(labels)) + sorted_labels = list(sorted(labels)) func_body = list(self.__disasm_instructions(insns, labels, sorted_labels)) return func_body @@ -269,7 +274,7 @@ def __stem_for_asm_slice(self, _slice): return Path(Path(_slice.name).name) return get_asm_path(Path("dol"), _slice) - + def __outpath_of_asm_slice(self, _slice): # Wrong term maybe stem = self.__stem_for_asm_slice(_slice) @@ -301,10 +306,9 @@ def run(self): for section in DOL_SECTIONS: self.__process_section(section) - self.__prune_asm() + self.__prune_asm() self.__name_asm_slices() self.__write_objlist() - def __process_section(self, section: Section): """Processes a program section and all its slices.""" @@ -324,7 +328,7 @@ def __slice_dest_c(self, _slice): def __process_slice(self, section: Section, _slice: Slice): """Process a slice in slices.csv or a gap.""" print(f" {_slice}") - + if self.__slice_dest_asm(_slice): self.__gen_asm(section, _slice) return @@ -343,12 +347,15 @@ def __gen_c(self, _slice: Slice): c_path = Path(_slice.name) if c_path.exists(): return + h_path = c_path.with_suffix(".h") + if h_path.exists(): + return c_path.parent.mkdir(parents=True, exist_ok=True) print(f" => {_slice.name}") data = self.dol.virtual_read(_slice.start, len(_slice)) - with open(c_path, "w") as file: - gen = CAsmGenerator(data, _slice, self.symbols, file) + with open(h_path, "w") as h_file, open(c_path, "w") as c_file: + gen = CAsmGenerator(data, _slice, self.symbols, h_file, c_file) gen.dump_section() def __gen_asm(self, section: Section, _slice: Slice): @@ -376,7 +383,7 @@ def __init__( rel_asm_dir: Path, rel_bin_dir: Path, pack_dir: Path, - regen_asm: bool + regen_asm: bool, ): self.slices = slices self.slices.set_sections(REL_SECTIONS) @@ -412,7 +419,7 @@ def run(self): object_names = self.slices.object_slices().objects.keys() with open(self.pack_dir / "rel_objects.txt", "w") as file: for name in object_names: - print(Path(name), file=file) + print(Path(name), file=file) def __process_section(self, section: Section): """Processes a library section and all its slices.""" @@ -437,13 +444,16 @@ def __gen_asm(self, section: Section, _slice: Slice): print(f" => {asm_path}") with open(asm_path, "w") as asm_file: data = ( - self.rel.virtual_read(_slice.start, len(_slice), REL_SECTIONS, REL_SECTION_IDX) + self.rel.virtual_read( + _slice.start, len(_slice), REL_SECTIONS, REL_SECTION_IDX + ) if section.type != "bss" else None ) gen = AsmGenerator(data, _slice, SymbolsList(), asm_file) gen.dump_section() + def __read_symbol_map(symbols_path): symbols = SymbolsList() @@ -454,6 +464,7 @@ def __read_symbol_map(symbols_path): raise RuntimeError("Cannot find symbols") + def __read_dol(dol_path): with open(dol_path, "rb") as file: dol = DolBinary(file) @@ -462,22 +473,25 @@ def __read_dol(dol_path): raise RuntimeError("Cannot find DOL") + # Map out slices in DOL. def __read_enabled_slices(dol, slices_path): dol_slices = SliceTable(dol.start, dol.stop) with open(slices_path) as file: dol_slices.read_from(file) - + return dol_slices.filter(SliceTable.ONLY_ENABLED) raise RuntimeError("Cannot find dol_slices.csv") + def __read_rel(rel_path): with open(rel_path, "rb") as file: return Rel(file) raise RuntimeError("Cannot find StaticR.rel") + def main(): parser = argparse.ArgumentParser( description="Generate ASM blobs and linker object lists." @@ -500,7 +514,7 @@ def main(): dol = __read_dol(args.binary_dir / "main.dol") dol_slices = __read_enabled_slices(dol, args.pack_dir / "dol_slices.csv") - + # Disassemble DOL sections. dol_asm_dir = args.asm_dir / "dol" dol_asm_dir.mkdir(exist_ok=True) diff --git a/mkwutil/gen_asm/source.c.j2 b/mkwutil/gen_asm/source.c.j2 index 42cfc283f..4af69d3f9 100644 --- a/mkwutil/gen_asm/source.c.j2 +++ b/mkwutil/gen_asm/source.c.j2 @@ -1,4 +1,4 @@ -#include "decomp.h" +#include "{{ header }}" {% if extern_functions | length > 0 -%} // Extern function references. @@ -8,10 +8,6 @@ extern UNKNOWN_FUNCTION({{ function.name }}); {%- endfor %} {% endif -%} -// Function declarations. -{%- for function in functions %} -UNKNOWN_FUNCTION({{ function.name }}); -{%- endfor %} {% for function in functions -%} // Symbol: {{ function.name }} diff --git a/mkwutil/gen_asm/source.h.j2 b/mkwutil/gen_asm/source.h.j2 new file mode 100644 index 000000000..b0cf72c67 --- /dev/null +++ b/mkwutil/gen_asm/source.h.j2 @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif +{% for function in functions %} +// PAL: {{ function.addr | addr }}..{{ (function.addr+function.size) | addr }} +UNKNOWN_FUNCTION({{ function.name }}); +{%- endfor %} + +#ifdef __cplusplus +} +#endif From 960579fbf38bd6f0e4b5687c1b5065f526da9578 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 23 Jul 2021 11:09:15 +0200 Subject: [PATCH 111/477] RVL/MTX: Add C_QUATSlerp (#56) --- pack/dol_objects.txt | 3 +-- pack/dol_slices.csv | 2 +- pack/symbols.txt | 1 + source/platform/math.h | 6 ++++++ source/rvl/mtx/mtx.h | 1 + source/rvl/mtx/rvlQuat.c | 34 +++++++++++++++++++++++++++++++--- source/rvl/pad/rvlPadClamp.c | 5 ++--- 7 files changed, 43 insertions(+), 9 deletions(-) diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 1368454c6..c473d4d68 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -177,11 +177,10 @@ out\rvlVec.o out\dol\sdata_80385a10_80385b08.o out\dol\sdata2_803888b4_803888b8.o out\rvlQuat.o -out\dol\text_8019b178_8019b314.o out\nand.o out\dol\text_8019f1a8_801ae5d8.o out\dol\rodata_80252c84_80252dd0.o -out\dol\sdata2_803888cc_80388930.o +out\dol\sdata2_803888d0_80388930.o out\rvlPadClamp.o out\rvlPad.o out\dol\text_801b0180_801b7410.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 57c10aa6e..fa8173639 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -66,7 +66,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/mtx/rvlMtx.c,,,,,,,0x80199d04,0x8019a9c4,,,,,,,,,,,0x80385a08,0x80385a10,,,0x80388870,0x80388890,, 1,,source/rvl/mtx/rvlMtx2.c,,,,,,,0x8019a9c4,0x8019abe4,,,,,,,,,,,,,,,0x80388890,0x803888a8,, 1,,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, -1,,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b178,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888cc,, +1,,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b314,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888d0,, 1,,source/rvl/nand/nand.c,,,,,,,0x8019b314,0x8019f1a8,,,,,,,,,,,,,,,,,, 1,,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, 1,,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index 2c568ab0f..bf826a070 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -40,6 +40,7 @@ 0x8001b590 cos 0x8001ba98 sin 0x8001bb64 tan +0x8001bbdc acos 0x8001bbf4 sqrt 0x8001bc98 strncasecmp 0x8001bc9c strcasecmp diff --git a/source/platform/math.h b/source/platform/math.h index 44c986362..cb590ce06 100644 --- a/source/platform/math.h +++ b/source/platform/math.h @@ -15,6 +15,12 @@ inline f32 cosf(f32 x) { return (float)cos(x); }; f64 tan(f64); inline f32 tanf(f32 x) { return (float)tan(x); }; +f64 sqrt(f64); +inline f32 sqrtf(f32 x) { return (f32)sqrt(x); } + +f64 acos(f64); +inline f32 acosf(f32 x) { return (f32)acos(x); } + #ifdef __cplusplus } // extern "C" #endif diff --git a/source/rvl/mtx/mtx.h b/source/rvl/mtx/mtx.h index 3b99e5f9d..d372f89ac 100644 --- a/source/rvl/mtx/mtx.h +++ b/source/rvl/mtx/mtx.h @@ -106,6 +106,7 @@ void C_QUATMtx(Quaternion*, const Mtx); // PAL: 0x8019b114 void C_QUATLerp(const Quaternion*, const Quaternion*, Quaternion*, f32); // PAL: 0x8019b178 +void C_QUATSlerp(const Quaternion*, const Quaternion*, Quaternion*, f32); #ifdef __cplusplus } diff --git a/source/rvl/mtx/rvlQuat.c b/source/rvl/mtx/rvlQuat.c index 9312d45df..be94a30fb 100644 --- a/source/rvl/mtx/rvlQuat.c +++ b/source/rvl/mtx/rvlQuat.c @@ -1,5 +1,7 @@ #include "mtx.h" +#include + void PSQUATMultiply(const register Quaternion* quat1, const register Quaternion* quat2, register Quaternion* out) { @@ -132,9 +134,6 @@ void PSQUATInverse(const register Quaternion* src, register Quaternion* inv) { } } -double sqrt(double); -inline float sqrtf(float x) { return (float)sqrt(x); } - void C_QUATMtx(Quaternion* r, const Mtx m) { f32 vv0, vv1; s32 i, j, k; @@ -176,3 +175,32 @@ void C_QUATLerp(const Quaternion* quat1, const Quaternion* quat2, out->z = f * (quat2->z - quat1->z) + quat1->z; out->w = f * (quat2->w - quat1->w) + quat1->w; } + +void C_QUATSlerp(const Quaternion* quat1, const Quaternion* quat2, + Quaternion* out, f32 f) { + f32 vv1, vv2, vv3, vv4, vv5; + + vv3 = quat1->x * quat2->x + quat1->y * quat2->y + quat1->z * quat2->z + + quat1->w * quat2->w; + vv5 = 1.0F; + + if (vv3 < 0.0F) { + vv3 = -vv3; + vv5 = -vv5; + } + + if (vv3 <= 1.0F - 0.00001f) { + vv1 = acosf(vv3); + vv2 = sinf(vv1); + vv4 = sinf((1.0F - f) * vv1) / vv2; + vv5 *= sinf(f * vv1) / vv2; + } else { + vv4 = 1.0F - f; + vv5 = vv5 * f; + } + + out->x = vv4 * quat1->x + vv5 * quat2->x; + out->y = vv4 * quat1->y + vv5 * quat2->y; + out->z = vv4 * quat1->z + vv5 * quat2->z; + out->w = vv4 * quat1->w + vv5 * quat2->w; +} diff --git a/source/rvl/pad/rvlPadClamp.c b/source/rvl/pad/rvlPadClamp.c index 7dcf7ef14..db380859b 100644 --- a/source/rvl/pad/rvlPadClamp.c +++ b/source/rvl/pad/rvlPadClamp.c @@ -1,5 +1,7 @@ #include "pad.h" +#include + static const PADClampRegion PADClampRegionV1 = { 30, 180, 15, 72, 47, 15, 59, 37, 62, 50, }; @@ -8,9 +10,6 @@ static const PADClampRegion PADClampRegionV2 = { 0, 180, 0, 87, 62, 0, 74, 52, 80, 68, }; -f64 sqrt(f64); -inline f32 sqrtf(f32 x) { return (f32)sqrt(x); } - // Clamps a Vec2 into a circle with bounds min <= len(vec) <= r void PAD_ClampCircle(s8* outX, s8* outY, s8 r, s8 min) { int x = *outX; From e0cae350a1a6d8a18561bd4e17ea8f2793dae79e Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 24 Jul 2021 15:47:36 +0200 Subject: [PATCH 112/477] Add mkwutil.dump_symbols and add more symbols (#62) --- mkwutil/dump_symbols.py | 91 +++++ pack/symbols.txt | 719 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 810 insertions(+) create mode 100644 mkwutil/dump_symbols.py diff --git a/mkwutil/dump_symbols.py b/mkwutil/dump_symbols.py new file mode 100644 index 000000000..6d24a3a2d --- /dev/null +++ b/mkwutil/dump_symbols.py @@ -0,0 +1,91 @@ +import argparse +from dataclasses import dataclass +from pathlib import Path +import sys +from typing import Generator, Optional + +from elftools.elf.elffile import ELFFile +from elftools.elf.sections import StringTableSection, Symbol as ELFSymbol + +from mkwutil.symbols import Symbol, SymbolsList + + +@dataclass +class SectionInfo: + index: int + offset: int + + @staticmethod + def from_elf(elf: ELFFile, name: str) -> Optional["SectionInfo"]: + for i, section in enumerate(elf.iter_sections()): + if section.name == name: + return SectionInfo(i, section.header["sh_addr"]) + + +def get_code_sections(elf: ELFFile) -> Generator[SectionInfo, None, None]: + for name in (".init", ".text"): + sec_info = SectionInfo.from_elf(elf, name) + if not sec_info: + continue + yield sec_info + + +def process_symbol( + sym: ELFSymbol, strtab: StringTableSection, code_secs: list[SectionInfo] +) -> Optional[Symbol]: + sym_type = sym.entry["st_info"]["type"] + sym_name = strtab.get_string(sym["st_name"]) + if len(sym_name) == 0: + return + if sym_type != "STT_FUNC": + return + sec = next(sec for sec in code_secs if sec.index == sym["st_shndx"]) + if not sec: + return + # Output + addr = sym["st_value"] + size = sym["st_size"] + return Symbol(addr, sym_name, size) + + +def process_elf(elf: ELFFile, out): + strtab = elf.get_section_by_name(".strtab") # String table + symtab = elf.get_section_by_name(".symtab") # Symbol table + code_sections = list(get_code_sections(elf)) + + symbols = SymbolsList() + + for elf_sym in symtab.iter_symbols(): + sym = process_symbol(elf_sym, strtab, code_sections) + if not sym: + continue + symbols.put(sym) + + symbols.write_to(out) + + +def main(): + parser = argparse.ArgumentParser( + description="Dumps symbols in ELF to symbols.txt format" + ) + parser.add_argument("elf", type=Path) + parser.add_argument( + "-o", + "--output", + type=Path, + required=False, + help="Path to output symbol list to", + ) + args = parser.parse_args() + + with open(args.elf, "rb") as file: + elf = ELFFile(file) + if args.output: + with open(args.output, "w", newline="") as out_file: + process_elf(elf, out_file) + else: + process_elf(elf, sys.stdout) + + +if __name__ == "__main__": + main() diff --git a/pack/symbols.txt b/pack/symbols.txt index bf826a070..6fa48c7f7 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -1,3 +1,5 @@ +0x80005f34 memcpy +0x80005f84 __fill_mem 0x80006038 memset 0x800060a4 __start 0x80008e7c onExit__Q23EGG6ThreadFv @@ -83,12 +85,94 @@ 0x80021a18 __shl2i 0x80021a3c __shr2u 0x80064440 GetRenderModeObj__Q34nw4r3g3d8G3DStateFv +0x8006a0c0 __ct__Q34nw4r3g3d6CameraFPQ34nw4r3g3d10CameraData +0x8006a0d0 Init__Q34nw4r3g3d6CameraFv +0x8006a120 Init__Q34nw4r3g3d6CameraFUsUsUsUsUsUs +0x8006a240 SetPosition__Q34nw4r3g3d6CameraFfff +0x8006a270 SetPosition__Q34nw4r3g3d6CameraFRCQ34nw4r4math4VEC3 +0x8006a2b0 SetPosture__Q34nw4r3g3d6CameraFRCQ44nw4r3g3d6Camera11PostureInfo +0x8006a4d0 SetCameraMtxDirectly__Q34nw4r3g3d6CameraFRCQ34nw4r4math5MTX34 +0x800774d0 __ct__Q34nw4r3g3d3FogFPQ34nw4r3g3d7FogData +0x800774e0 Init__Q34nw4r3g3d3FogFv +0x80077550 CopyTo__Q34nw4r3g3d3FogCFPv +0x800775b0 SetFogRangeAdjParam__Q34nw4r3g3d3FogFUsUsRCQ34nw4r4math5MTX44 +0x80085110 SinFIdx__Q24nw4r4mathFf +0x80085180 CosFIdx__Q24nw4r4mathFf +0x800851e0 SinCosFIdx__Q24nw4r4mathFPfPff +0x80085250 AtanFIdx__Q24nw4r4mathFf +0x800853c0 Atan2FIdx__Q24nw4r4mathFff +0x80085600 MTX33Identity__Q24nw4r4mathFPQ34nw4r4math5MTX33 +0x80085630 MTX34ToMTX33__Q24nw4r4mathFPQ34nw4r4math5MTX33PCQ34nw4r4math5MTX34 +0x80085670 MTX34InvTranspose__Q24nw4r4mathFPQ34nw4r4math5MTX33PCQ34nw4r4math5MTX34 +0x80085740 MTX34Zero__Q24nw4r4mathFPQ34nw4r4math5MTX34 +0x80085760 MTX34Mult__Q24nw4r4mathFPQ34nw4r4math5MTX34PCQ34nw4r4math5MTX34f +0x800857b0 MTX34Scale__Q24nw4r4mathFPQ34nw4r4math5MTX34PCQ34nw4r4math5MTX34PCQ34nw4r4math4VEC3 +0x80085810 MTX34Trans__Q24nw4r4mathFPQ34nw4r4math5MTX34PCQ34nw4r4math5MTX34PCQ34nw4r4math4VEC3 +0x80085880 MTX34MAdd__Q24nw4r4mathFPQ34nw4r4math5MTX34fPCQ34nw4r4math5MTX34PCQ34nw4r4math5MTX34 +0x80085900 MTX34RotAxisFIdx__Q24nw4r4mathFPQ34nw4r4math5MTX34PCQ34nw4r4math4VEC3f +0x800aef60 List_Init__Q24nw4r2utFPQ34nw4r2ut4ListUs +0x800aef80 List_Append__Q24nw4r2utFPQ34nw4r2ut4ListPv +0x800aeff0 List_Insert__Q24nw4r2utFPQ34nw4r2ut4ListPvPv +0x800af110 List_Remove__Q24nw4r2utFPQ34nw4r2ut4ListPv +0x800af180 List_GetNext__Q24nw4r2utFPCQ34nw4r2ut4ListPCv +0x800ccb4c DWC_GetLastError +0x800ccb64 DWC_GetLastErrorEx +0x800ccc38 DWC_ClearError +0x800ccc54 DWCi_IsError +0x800ccc68 DWCi_SetError +0x800ef378 ArrayNew +0x800ef414 ArrayFree +0x800ef4a8 ArrayLength +0x800ef4b0 ArrayNth +0x800ef4e0 ArrayAppend +0x800ef604 ArrayInsertSorted +0x800ef784 ArrayRemoveAt +0x800ef830 ArrayDeleteAt +0x800ef920 ArrayReplaceAt +0x800ef9d8 ArraySort +0x800ef9f0 ArraySearch +0x800efb88 ArrayMapBackwards +0x800efc24 ArrayMapBackwards2 +0x800efcc8 ArrayClear +0x800efdcc TableNew +0x800efde8 TableNew2 +0x800efe8c TableFree +0x800eff08 TableCount +0x800eff90 TableEnter +0x800f0038 TableRemove +0x800f00dc TableLookup +0x800f017c TableMapSafe +0x800f01e8 TableMapSafe2 +0x800f0264 MD5Update +0x800f0350 MD5Transform +0x800f0e08 MD5Digest +0x800f118c socket +0x800f11c4 closesocket +0x800f11f8 shutdown +0x800f122c bind +0x800f12a4 connect +0x800f1308 recv +0x800f1340 recvfrom +0x800f13a0 send +0x800f13d4 sendto +0x800f1454 setsockopt +0x800f1488 getsockname +0x800f14e4 inet_addr +0x800f151c GOAGetLastError +0x800f1524 GSISocketSelect 0x800f164c gethostbyname 0x800f1bc8 SetSockBlocking 0x800f1c40 SetReceiveBufferSize 0x800f1c9c CanReceiveOnSocket 0x800f1ce4 CanSendOnSocket 0x800f1d2c getlocalhost +0x800f1f58 gsiInterlockedIncrement +0x800f1fa0 gsiInterlockedDecrement +0x800f1fe8 gsiInitializeCriticalSection +0x800f1fec gsiEnterCriticalSection +0x800f1ff0 gsiLeaveCriticalSection +0x800f1ff4 gsiDeleteCriticalSection +0x800f1ff8 gsiTimeInSec 0x800f2104 gsiStartResolvingHostname 0x800f2238 gsiCancelResolvingHostname 0x800f2300 gsiGetResolvedIP @@ -110,9 +194,266 @@ 0x800f3860 gsimalloc 0x800f3870 gsirealloc 0x800f3884 gsifree +0x800f38a4 GSIStartAvailableCheckA 0x800f4114 gsCoreIsShutdown 0x800f4680 _UCS2CharToUTF8String 0x800f47e4 UTF8ToUCS2StringLen +0x800f489c gsUdpMsgHandlerFree +0x800f48a4 gsUdpMsgHandlerCompare +0x800f48ac gsUdpMsgHandlerCompare2 +0x800f48bc gsUdpRemotePeerCompare +0x800f48f0 gsUdpRemotePeerCompare2 +0x800f490c gsUdpSocketError +0x800f49ac gsUdpClosedRoutingCB +0x800f4afc gsUdpConnectedRoutingCB +0x800f4d28 gsUdpPingRoutingCB +0x800f4e24 gsUdpReceivedRoutingCB +0x800f4f54 gsUdpUnrecognizedMsgCB +0x800f4fe8 gsUdpConnAttemptCB +0x800f5144 gsUdpEngineIsInitialized +0x800f5154 gsUdpEngineInitialize +0x800f52c8 gsUdpEngineGetPeerState +0x800f5374 gsUdpEngineStartTalkingToPeer +0x800f5580 gsUdpEngineSendMessage +0x800f56c4 gsUdpEngineThink +0x800f5708 gsUdpEngineShutdown +0x800f576c gsUdpEngineGetLocalPort +0x800f5790 gsUdpEngineAddMsgHandler +0x800f587c gsUdpEngineRemoveMsgHandler +0x800f591c gsUdpEngineNoMoreMsgHandlers +0x800f5964 gsUdpEngineNoApp +0x800f59e4 gsUdpEngineGetPeerOutBufferFreeSpace +0x800f5a6c gsXmlCreateStreamWriter +0x800f618c gsXmlCreateStreamReader +0x800f6244 gsXmlParseBuffer +0x800f62c8 gsXmlFreeWriter +0x800f6300 gsXmlFreeReader +0x800f6340 gsXmlCloseWriter +0x800f6440 gsiXmlUtilElementFree +0x800f6444 gsiXmlUtilAttributeFree +0x800f6448 gsXmlWriterGetData +0x800f6450 gsXmlWriterGetDataLength +0x800f6458 gsiXmlUtilParseElement +0x800f6d34 gsiXmlUtilParseName +0x800f6f60 gsiXmlUtilParseString +0x800f70d0 gsiXmlUtilDecodeString +0x800f7528 gsXmlWriteOpenTag +0x800f7834 gsXmlWriteCloseTag +0x800f7bc0 gsXmlWriteStringElement +0x800f82a0 gsXmlWriteUnicodeStringElement +0x800f8a3c gsXmlWriteIntElement +0x800f9170 gsXmlWriteFloatElement +0x800f98a0 gsXmlWriteBase64BinaryElement +0x800fa00c gsXmlWriteDateTimeElement +0x800fa774 gsiXmlUtilWriteXmlSafeString +0x800fad2c gsXmlMoveToStart +0x800fad40 gsXmlMoveToNext +0x800fadd4 gsXmlMoveToParent +0x800fae70 gsXmlMoveToChild +0x800faf38 gsXmlReadChildAsString +0x800fb014 gsXmlReadChildAsStringNT +0x800fb0b8 gsXmlReadChildAsHexBinary +0x800fb26c gsXmlReadChildAsBase64Binary +0x800fb394 gsXmlReadChildAsInt +0x800fb478 gsXmlReadChildAsDateTimeElement +0x800fb5ac gsXmlReadChildAsFloat +0x800fb694 gsiXmlUtilTagMatches +0x800fb778 gsXmlCountChildren +0x800fb828 gpInitialize +0x800fb854 gpDestroy +0x800fb874 gpProcess +0x800fb8b0 gpSetCallback +0x800fb920 gpConnectPreAuthenticatedA +0x800fba4c gpDisconnect +0x800fbaa8 gpProfileSearchA +0x800fbb78 gpGetInfo +0x800fbc58 gpSetInfosA +0x800fbcc8 gpSendBuddyRequestA +0x800fbe38 gpAuthBuddyRequest +0x800fbea8 gpDenyBuddyRequest +0x800fbfa0 gpGetNumBuddies +0x800fbfe8 gpGetBuddyStatus +0x800fc1c0 gpGetBuddyIndex +0x800fc2a4 gpIsBuddy +0x800fc364 gpDeleteBuddy +0x800fc3ec gpSetStatusA +0x800fc614 gpSendBuddyMessageA +0x800fc6b0 gpGetReversBuddiesList +0x800fc788 gpGetLoginTicket +0x800fc7d4 gpiInitialize +0x800fc9a0 gpiDestroy +0x800fc9fc gpiResetProfile +0x800fca1c gpiReset +0x800fcc64 gpiProcessConnectionManager +0x800fcfbc gpiProcess +0x800fd160 gpiProcessRecvBuddyMessage +0x800fdc4c gpiProcessRecvBuddyStatusInfo +0x800fe2f8 gpiSendServerBuddyMessage +0x800fe3ec gpiSendBuddyMessage +0x800fe5a4 gpiBuddyHandleKeyRequest +0x800fe648 gpiBuddyHandleKeyReply +0x800fea64 gpiAuthBuddyRequest +0x800febd4 gpiFixBuddyIndices +0x800fec20 gpiDeleteBuddy +0x800fee90 gpiAppendCharToBuffer +0x800fef38 gpiAppendStringToBufferLen +0x800ff014 gpiAppendStringToBuffer +0x800ff070 gpiAppendIntToBuffer +0x800ff0d0 gpiSendData +0x800ff1d4 gpiSendOrBufferChar +0x800ff1dc gpiSendOrBufferStringLenToPeer +0x800ff2f4 gpiSendOrBufferString +0x800ff350 gpiRecvToBuffer +0x800ff498 gpiSendFromBuffer +0x800ff598 gpiSendBufferToPeer +0x800ff6f0 gpiReadMessageFromBuffer +0x800ff844 gpiClipBufferToPosition +0x800ff8c4 gpiCallErrorCallback +0x800ff984 gpiAddCallback +0x800ffa54 gpiCallCallback +0x800ffd08 gpiProcessCallbacks +0x800ffe28 gpiStartConnect +0x80100150 gpiConnect +0x801003a4 gpiSendLogin +0x801007dc gpiSendNewuser +0x801009fc gpiProcessConnect +0x80101088 gpiCheckConnect +0x80101128 gpiDisconnectCleanupProfile +0x801012b8 gpiDisconnect +0x80101470 gpiIsValidDate +0x801015d4 gpiInfoCacheToArg +0x80101810 gpiProcessGetInfo +0x801020d0 gpiAddLocalInfo +0x801021e4 gpiSetInfoi +0x80102a24 gpiSetInfos +0x80103534 gpiSendGetInfo +0x801035f8 gpiGetInfo +0x80103834 gpiFreeInfoCache +0x80103908 gpiStatusInfoKeyFree +0x80103954 gpiStatusInfoKeysInit +0x801039c4 gpiStatusInfoKeysDestroy +0x80103a04 gpiStatusInfoKeyCompFunc +0x80103a10 gpiStatusInfoAddKey +0x80103aac gpiStatusInfoSetKey +0x80103b64 gpiStatusInfoCheckKey +0x80103c0c gpiSaveKeysToBuffer +0x80103f70 gpiFailedOpCallback +0x80104348 gpiAddOperation +0x80104428 gpiDestroyOperation +0x801044c8 gpiRemoveOperation +0x80104514 gpiFindOperationByID +0x80104564 gpiOperationsAreBlocking +0x801045a4 gpiProcessOperation +0x80104648 gpiProcessPeerInitiatingConnection +0x80104998 gpiProcessPeerAcceptingConnection +0x80104bb4 gpiPeerSendMessages +0x80104c98 gpiProcessPeerConnected +0x80104ffc gpiCheckTimedOutPeerOperations +0x801050fc gpiDestroyPeer +0x80105164 gpiRemovePeer +0x80105274 gpiProcessPeers +0x801053b8 gpiGetPeerByProfile +0x801053e4 gpiFreeMessage +0x8010541c gpiAddPeer +0x801054f0 gpiPeerGetSig +0x80105578 gpiPeerStartConnect +0x80105694 gpiPeerAddMessage +0x80105810 gpiPeerStartTransferMessage +0x80105888 gpiPeerFinishTransferMessage +0x80105998 gpiPeerLeftCallback +0x80105a28 gpiPeerMessageCallback +0x80105ba8 gpiPeerAcceptedCallback +0x80105c64 gpiPeerPingReplyCallback +0x80105c68 gpiPeerAddOp +0x80105cc0 gpiPeerRemoveOp +0x80105d54 gpiProfilesTableHash +0x80105d68 gpiProfilesTableCompare +0x80105d78 gpiProfilesTableFree +0x80105e94 gpiInitProfiles +0x80105efc gpiProcessNewProfile +0x80106084 gpiProfileListAdd +0x80106158 gpiGetProfile +0x801061ac gpiProcessDeleteProfle +0x801062ec gpiRemoveProfileByID +0x80106338 gpiRemoveProfile +0x80106344 gpiCheckProfileForUser +0x801063d0 gpiFindProfileByUser +0x80106458 gpiProfileMapCallback +0x80106478 gpiProfileMap +0x801064c0 gpiCheckForBuddy +0x80106518 gpiFindBuddy +0x80106574 gpiRemoveBuddyStatus +0x801065c8 gpiRemoveBuddyStatusInfo +0x80106640 gpiCanFreeProfile +0x8010669c gpiStartProfileSearch +0x8010686c gpiInitSearchData +0x80106958 gpiProfileSearch +0x80106be8 gpiOthersBuddyList +0x80106ce8 gpiProcessSearch +0x801089e8 gpiProcessSearches +0x80108b38 gpiHandleTransferMessage +0x80108c20 gpiProcessRegisterUniqueNick +0x80108d4c gpiProcessRegisterCdKey +0x80108e78 strzcpy +0x80108ebc gpiCheckForError +0x80108fb4 gpiValueForKey +0x80109060 gpiCheckSocketConnect +0x8010914c gpiReadKeyAndValue +0x80109330 gpiSetError +0x80109380 gpiSetErrorString +0x801093bc gpiEncodeString +0x8010945c gti2GetChallenge +0x8010956c gti2GetResponse +0x8010975c gti2CheckResponse +0x80109820 gti2AllocateBuffer +0x80109878 gti2GetBufferFreeSpace +0x80109888 gti2BufferWriteByte +0x801098a0 gti2BufferWriteUShort +0x801098cc gti2BufferWriteData +0x80109958 gti2BufferShorten +0x801099c4 gti2SocketErrorCallback +0x80109a58 gti2ConnectAttemptCallback +0x80109b40 gti2ConnectedCallback +0x80109c1c gti2ReceivedCallback +0x80109cf4 gti2ClosedCallback +0x80109db0 gti2PingCallback +0x80109e6c gti2SendFilterCallback +0x80109f74 gti2ReceiveFilterCallback +0x8010a07c gti2DumpCallback +0x8010a178 gti2UnrecognizedMessageCallback +0x8010a244 gti2NewOutgoingConnection +0x8010a298 gti2NewIncomingConnection +0x8010a2ec gti2StartConnectionAttempt +0x8010a3c0 gti2AcceptConnection +0x8010a464 gti2RejectConnection +0x8010a4cc gti2ConnectionSendData +0x8010a528 gti2CheckTimeout +0x8010a624 gti2ConnectionThink +0x8010a770 gti2CloseConnection +0x8010a818 gti2ConnectionClosed +0x8010a874 gti2ConnectionCleanup +0x8010a918 gt2CreateSocket +0x8010a920 gt2CloseSocket +0x8010a964 gt2Think +0x8010a9b0 gt2Listen +0x8010a9b4 gt2Accept +0x8010a9b8 gt2Reject +0x8010a9bc gt2Connect +0x8010ab64 gt2Send +0x8010ac60 gt2CloseConnectionHard +0x8010ac68 gti2CloseAllConnectionsHardMap +0x8010ac74 gt2CloseAllConnectionsHard +0x8010ac88 gt2GetConnectionState +0x8010acbc gt2GetRemoteIP +0x8010acc4 gt2GetRemotePort +0x8010accc gt2GetLocalIP +0x8010acd4 gt2GetLocalPort +0x8010acdc gt2GetOutgoingBufferSize +0x8010ace4 gt2GetOutgoingBufferFreeSpace +0x8010acf4 gt2GetSocketSOCKET +0x8010acfc gt2SetUnrecognizedMessageCallback +0x8010ad04 gt2SetConnectionData +0x8010ad0c gt2GetConnectionData 0x8010ad14 gti2HandleESN 0x8010ae44 gti2HandleServerChallenge 0x8010b178 gti2HandleClientResponse @@ -138,15 +479,258 @@ 0x8010dbcc gti2SendClosed 0x8010dc84 gti2ResendMessage 0x8010dd40 gti2Send +0x8010de30 gti2ConnectionHash +0x8010de50 gti2ConnectionCompare +0x8010de84 gti2ConnectionFree +0x8010de8c gti2SocketFindConnection +0x8010ded8 gti2CreateSocket +0x8010e110 gti2CloseSocket +0x8010e174 gti2Listen +0x8010e17c gti2NewSocketConnection +0x8010e3f0 gti2FreeSocketConnection +0x8010e4bc gti2SocketSend +0x8010e6d8 gti2SocketConnectionsThinkMap +0x8010e7f4 gti2SocketConnectionsThink +0x8010e840 gti2FreeClosedConnections +0x8010e930 gti2SocketError +0x8010e9c0 gt2AddressToString +0x8010eaa4 gt2StringToAddress +0x8010ec48 gti2MessageCheck +0x8010ecac qr2_init_socketA +0x8010ef1c qr2_register_natneg_callback +0x8010ef30 qr2_register_clientmessage_callback +0x8010ef44 qr2_register_publicaddress_callback +0x8010ef58 qr2_think +0x8010f070 qr2_check_queries +0x8010f12c qr2_check_send_heartbeat +0x8010f240 qr2_send_statechanged +0x8010f2b4 qr2_shutdown +0x8010f354 qr2_keybuffer_add +0x8010f394 qr2_buffer_add_int +0x8010f434 qr2_buffer_addA +0x8010f4cc get_sockaddrin +0x8010f5b0 gs_encode +0x8010f748 gs_encrypt +0x8010fb34 qr_build_partial_query_reply +0x8010fe48 qr_build_split_query_reply +0x80110190 qr_process_query +0x80110350 qr_process_client_message +0x80110494 qr2_process_ip_verify +0x80110720 qr2_parse_queryA +0x80110db8 send_keepalive +0x80110e2c send_heartbeat +0x8011156c qr2_internal_is_master_only_key +0x801115d4 qr2_register_keyA +0x801115fc ghiResizeBuffer +0x8011167c ghiInitBuffer +0x80111778 ghiInitFixedBuffer +0x801117f0 ghiFreeBuffer +0x80111850 ghiAppendDataToBuffer +0x801119c0 ghiEncryptDataToBuffer +0x80111b98 ghiAppendHeaderToBuffer +0x80111c40 ghiAppendCharToBuffer +0x80111d78 ghiAppendIntToBuffer +0x80111dc4 ghiResetBuffer 0x80111de8 ghiSendBufferedData +0x80111f0c ghiCallCompletedCallback +0x80111f94 ghiCallProgressCallback +0x80111fcc ghiCallPostCallback +0x8011202c ghiCreateLock +0x80112030 ghiFreeLock +0x80112034 ghiLock +0x80112038 ghiUnlock +0x8011203c ghiDecryptReceivedData +0x8011218c ghiDoReceive +0x80112348 ghiDoSend 0x8011248c ghiTrySendThenBuffer +0x801125c8 ghiFindFreeSlot +0x801126d4 ghiNewConnection +0x80112898 ghiFreeConnection +0x80112a0c ghiRequestToConnection +0x80112a7c ghiEnumConnections +0x80112b08 ghiRedirectConnection +0x80112c4c ghiCleanupConnections +0x80112d14 ghttpSetRequestEncryptionEngine +0x80112e40 ghiEncryptorSslInitFunc +0x80112f20 ghiEncryptorSslCleanupFunc +0x80112f90 ghiEncryptorSslStartFunc +0x80113030 ghiEncryptorSslEncryptSend +0x8011309c ghiEncryptorSslDecryptRecv +0x8011311c ghiEncryptorSslEncryptFunc +0x80113124 ghiEncryptorSslDecryptFunc +0x8011312c ghiHandleStatus +0x801131ec ghiProcessConnection +0x80113388 ghttpStartup +0x801133d8 ghttpCleanup +0x80113434 ghttpGetExA +0x8011364c ghttpPostA +0x80113670 ghttpPostExA +0x80113814 ghttpThink +0x80113820 ghttpRequestThink +0x80113858 ghttpCancelRequest +0x80113884 ghttpGetHeaders +0x801138e8 ghttpSetMaxRecvTime +0x80113920 ghttpNewPost +0x80113924 ghttpPostSetAutoFree +0x80113934 ghttpFreePost +0x80113944 ghttpPostAddFileFromMemoryA +0x801139ac ghttpPostAddXml +0x801139b0 ghiPostDataFree +0x80113a54 ghiNewPost +0x80113adc ghiPostSetAutoFree +0x80113ae4 ghiIsPostAutoFree +0x80113aec ghiFreePost +0x80113b24 ghiPostAddFileFromMemory +0x80113c14 ghiPostAddXml +0x80113c70 ghiPostGetContentType +0x80113ccc ghiPostGetNoFilesContentLength +0x80113d90 ghiPostGetHasFilesContentLength +0x80114110 ghiPostStateInit +0x801141cc ghiPostInitState +0x80114398 ghiPostCleanupState +0x80114474 ghiPostStringStateDoPosting +0x80114620 ghiPostXmlStateDoPosting +0x801147c0 ghiPostFileDiskStateDoPosting +0x80114900 ghiPostFileMemoryStateDoPosting +0x80114b1c ghiPostStateDoPosting +0x80115138 ghiPostDoPosting +0x80115384 ghiParseURL +0x80115530 ghiDoSocketInit +0x80115634 ghiDoHostLookup +0x80115738 ghiDoLookupPending +0x801157d8 ghiDoConnecting +0x80115a34 ghiDoSecuringSession +0x80115c08 ghiDoSendingRequest +0x80115ef4 ghiDoPosting +0x80115fec ghiDoWaiting +0x801160a4 ghiParseStatus +0x801161bc ghiDoReceivingStatus +0x8011634c ghiDeliverIncomingFileData +0x80116430 ghiProcessIncomingFileData +0x8011669c ghiDoReceivingHeaders +0x80116c4c ghiDoReceivingFile +0x80116dd4 BucketNew +0x80116f24 BucketSet +0x80117040 BucketAdd +0x801173e4 BucketSub +0x80117650 BucketMult +0x801178b4 BucketDiv +0x80117b20 BucketConcat +0x80117ca8 BucketAvg 0x8011a7c4 NNFreeNegotiateList 0x8011ae3c NNBeginNegotiationWithSocket 0x8011b158 NNCancel 0x8011b718 NNThink 0x8011bf54 NNProcessData +0x8011c834 SBQueryEngineInit +0x8011c8d4 SBQueryEngineSetPublicIP +0x8011c8dc SBEngineHaltUpdates +0x8011c8fc SBEngineCleanup +0x8011c950 SBQueryEngineUpdateServer 0x8011ca4c ParseSingleQR2Reply 0x8011cc3c ProcessIncomingReplies +0x8011ce38 SBQueryEngineThink +0x8011cfb4 SBQueryEngineAddQueryKey +0x8011cfd4 SBQueryEngineRemoveServerFromFIFOs +0x8011d024 RefStringHash +0x8011d098 RefStringCompare +0x8011d0a4 RefStringFree +0x8011d0ac SBRefStrHash +0x8011d104 SBRefStrHashCleanup +0x8011d148 SBServerFree +0x8011d188 SBServerAddKeyValue +0x8011d1e4 SBServerAddIntKeyValue +0x8011d254 SBServerGetStringValueA +0x8011d2b0 SBServerGetIntValueA +0x8011d3d4 SBServerGetFloatValueA +0x8011d448 SBServerGetPublicAddress +0x8011d474 SBServerGetPublicInetAddress +0x8011d47c SBServerGetPublicQueryPort +0x8011d484 SBServerGetPublicQueryPortNBO +0x8011d48c SBServerHasPrivateAddress +0x8011d498 SBServerGetPrivateAddress +0x8011d4c4 SBServerGetPrivateInetAddress +0x8011d4cc SBServerGetPrivateQueryPort +0x8011d4d4 SBServerSetNext +0x8011d4dc SBServerGetNext +0x8011d4e4 SBServerParseKeyVals +0x8011d6b0 SBServerParseQR2FullKeysSingle +0x8011d8a4 SBServerParseQR2FullKeysSplit +0x8011db20 KeyValFree +0x8011db60 KeyValHashKey +0x8011dbd4 KeyValCompareKey +0x8011dbfc SBAllocServer +0x8011dcc4 SBServerSetFlags +0x8011dccc SBServerSetPrivateAddr +0x8011dcd8 SBServerSetICMPIP +0x8011dce0 SBServerSetState +0x8011dce8 SBServerGetState +0x8011dcf0 SBIsNullServer +0x8011dd04 ListCallback +0x8011df3c EngineCallback +0x8011e038 ServerBrowserNewA +0x8011e11c ServerBrowserFree +0x8011e15c ServerBrowserBeginUpdate2 +0x8011e2a0 ServerBrowserLimitUpdateA +0x8011e2ac ServerBrowserSendMessageToServerA +0x8011e314 ServerBrowserSendNatNegotiateCookieToServerA +0x8011e384 ServerBrowserRemoveServer +0x8011e3c8 ServerBrowserThink +0x8011e3fc ServerBrowserClear +0x8011e43c ServerBrowserState +0x8011e480 ServerBrowserGetServer +0x8011e488 ServerBrowserCount +0x8011e490 ServerBrowserSortA +0x8011e510 ServerBrowserGetMyPublicIPAddr +0x8011e518 prevKeyCompare +0x8011e6a8 IntKeyCompare +0x8011e744 FloatKeyCompare +0x8011e81c StrCaseKeyCompare +0x8011e8c0 StrNoCaseKeyCompare +0x8011e964 SBServerListSort +0x8011ead0 SBServerListFindServer +0x8011eb58 SBServerListRemoveAt +0x8011ebf0 SBServerListCount +0x8011ebf8 SBServerListNth +0x8011ec20 SBServerListClear +0x8011ecfc SBRefStr +0x8011ed84 SBReleaseStr +0x8011ede4 NTSLengthSB +0x8011ee1c SBServerListInit +0x8011ef34 ServerListConnect +0x8011f0c0 SendWithRetry +0x8011f45c SBServerListConnectAndQuery +0x8011f95c SBServerListDisconnect +0x8011fac8 SBServerListCleanup +0x8011fcf8 FullRulesPresent +0x8011fdd0 AllKeysPresent +0x8011ff08 ParseServer +0x801201d8 IncomingListParseServer +0x80120394 ProcessMainListData +0x80120cb8 ProcessPushKeyList +0x80120e90 ProcessPlayerSearch +0x80121054 ProcessMaploop +0x80121224 ProcessPushServer +0x801213d4 ProcessAdHocData +0x80121824 ProcessIncomingData +0x80121afc SBSendMessageToServer +0x80121c00 SBSendNatNegotiateCookieToServer +0x80121ca8 ProcessLanData +0x80121e40 SBListThink +0x80121eec sakeStartup +0x80121f74 sakeShutdown +0x80121f78 sakeSetGame +0x80121fd8 sakeSetProfile +0x80122020 sakeGetStartRequestResult +0x80122028 sakeCreateRecord +0x801220a4 sakeUpdateRecord +0x80122120 sakeSearchForRecords +0x8012219c sakeGetMyRecords +0x80122218 sakeSetFileDownloadURL +0x8012225c sakeGetFileDownloadURL +0x80122328 sakeSetFileUploadURL +0x8012236c sakeGetFileResultFromHeaders +0x80122418 sakeGetFileIdFromHeaders 0x8012249c sakeiInitRequest 0x8012256c sakeiFreeRequest 0x80122570 sakeiCheckSakeResult @@ -179,6 +763,19 @@ 0x80123d6c sakeiGetRandomRecordFillSoapRequest 0x80123e4c sakeiGetRandomRecordProcessSoapResponse 0x80123ed8 sakeiGetRandomRecordFreeData +0x80124500 ARCInitHandle +0x801245a0 ARCOpen +0x80124844 ARCFastOpen +0x80124894 ARCConvertPathToEntrynum +0x80124af8 __rvlMakePathRecursive +0x80124cc0 ARCGetStartAddrInMem +0x80124cd4 ARCGetStartOffset +0x80124cdc ARCGetLength +0x80124ce4 ARCClose +0x80124cec ARCChangeDir +0x80124d44 ARCOpenDir +0x80124dc0 ARCReadDir +0x80124e78 ARCCloseDir 0x8015bef0 CXInitUncompContextLZ 0x8015bf24 CXReadUncompLZ 0x8015c2e0 CXGetUncompressedSize @@ -202,6 +799,90 @@ 0x801938f8 IOS_Open 0x80193ad8 IOS_Close 0x80194290 IOS_Ioctl +0x801981ec MEM_FindHeap__FP7MEMListPCv +0x8019832c MEMiInitHeapHead +0x801984ec MEMiFinalizeHeap +0x80198658 MEMFindContainHeap +0x80198798 MEM_ExpAllocNewBlock +0x8019899c MEM_ExpAllocFromHead +0x80198a78 MEM_ExpAllocFromTail +0x80198b40 MEM_ExpRecycleRegion +0x80198ca8 MEMCreateExpHeapEx +0x80198d58 MEMDestroyExpHeap +0x80198d88 MEMAllocFromExpHeapEx +0x80198e38 MEMResizeForMBlockExpHeap +0x80199038 MEMFreeToExpHeap +0x80199104 MEMGetTotalFreeSizeForExpHeap +0x80199180 MEMGetAllocatableSizeForExpHeapEx +0x80199258 MEMSetGroupIDForExpHeap +0x801992a8 MEMVisitAllocatedForExpHeap +0x80199344 MEMGetSizeForMBlockExpHeap +0x8019934c MEMGetGroupIDForMBlockExpHeap +0x80199358 MEMAdjustExpHeap +0x80199430 MEMCreateFrmHeapEx +0x801994b4 MEMDestroyFrmHeap +0x801994e4 MEMAllocFromFrmHeapEx +0x80199604 MEMFreeToFrmHeap +0x801996a4 MEMGetAllocatableSizeForFrmHeapEx +0x8019971c MEMRecordStateForFrmHeap +0x801997f0 MEMFreeByStateToFrmHeap +0x801998a4 MEMCreateUnitHeapEx +0x80199a00 MEMDestroyUnitHeap +0x80199a30 MEMAllocFromUnitHeap +0x80199ac4 MEMFreeToUnitHeap +0x80199b34 MEMCalcHeapSizeForUnitHeap +0x80199b58 MEM_AllocForExpHeap_ +0x80199b68 MEM_FreeForExpHeap_ +0x80199b70 MEM_AllocForUnitHeap +0x80199b90 MEM_FreeForUnitHeap +0x80199b98 MEMAllocFromAllocator +0x80199ba8 MEMFreeToAllocator +0x80199bb8 MEMInitAllocatorForExpHeap +0x80199bd4 MEMInitAllocatorForUnitHeap +0x80199bf0 MEMInitList +0x80199c08 MEMAppendListObject +0x80199c78 MEMRemoveListObject +0x80199ce4 MEMGetNextListObject +0x80199d04 PSMTXIdentity +0x80199d30 PSMTXCopy +0x80199d64 PSMTXConcat +0x80199e30 PSMTXConcatArray +0x80199fc8 PSMTXInverse +0x8019a0c0 PSMTXInvXpose +0x8019a188 PSMTXRotRad +0x8019a204 PSMTXRotTrig +0x8019a2b4 __PSMTXRotAxisRadInternal +0x8019a364 PSMTXRotAxisRad +0x8019a3e0 PSMTXTrans +0x8019a414 PSMTXTransApply +0x8019a460 PSMTXScale +0x8019a488 PSMTXScaleApply +0x8019a4e0 PSMTXQuat +0x8019a584 C_MTXLookAt +0x8019a6f8 C_MTXLightFrustum +0x8019a79c C_MTXLightPerspective +0x8019a894 C_MTXLightOrtho +0x8019a91c PSMTXMultVec +0x8019a970 PSMTXMultVecSR +0x8019a9c4 C_MTXFrustum +0x8019aa60 C_MTXPerspective +0x8019ab4c C_MTXOrtho +0x8019abe4 PSVECAdd +0x8019ac08 PSVECScale +0x8019ac24 PSVECNormalize +0x8019ac68 PSVECMag +0x8019acac PSVECDotProduct +0x8019accc PSVECCrossProduct +0x8019ad08 C_VECHalfAngle +0x8019ade0 PSVECSquareDistance +0x8019ae08 PSQUATMultiply +0x8019ae64 PSQUATScale +0x8019ae80 PSQUATDotProduct +0x8019aea0 PSQUATNormalize +0x8019aef4 PSQUATInverse +0x8019af48 C_QUATMtx +0x8019b114 C_QUATLerp +0x8019b178 C_QUATSlerp 0x8019b314 nandCreate 0x8019b43c NANDCreate 0x8019b4b0 NANDPrivateCreate @@ -312,6 +993,26 @@ 0x801aad74 OSGetTick 0x801aad7c __OSGetSystemTime 0x801aafa8 OSTicksToCalendarTime +0x801ae5d8 PAD_ClampCircle +0x801ae6f4 PADClampCircle +0x801ae7dc PADClampCircle2 +0x801ae880 PAD_UpdateOrigin +0x801aea24 PADOriginCallback +0x801aeae4 PADOriginUpdateCallback +0x801aebac PADProbeCallback +0x801aec80 PADTypeAndStatusCallback +0x801aefa0 PADReceiveCheckCallback +0x801af0dc PADReset +0x801af1e4 PADRecalibrate +0x801af2f0 PADInit +0x801af44c PADRead +0x801af908 PADControlMotor +0x801af9c0 SPEC0_MakeStatus +0x801afad8 SPEC1_MakeStatus +0x801afbf0 SPEC2_MakeStatus +0x801afffc PAD_OnReset +0x801b00c4 PAD_SamplingHandler +0x801b0124 __PADDisableRecalibration 0x801b1be4 SCGetAspectRatio 0x801b1cac SCGetEuRgb60Mode 0x801b1d84 SCGetProgressiveMode @@ -328,6 +1029,9 @@ 0x801b3808 SIGetType 0x801b39bc SIGetTypeAsync 0x801b3ba4 SIRefreshSamplingRate +0x801b7410 TPLBind +0x801b7524 TPLGet +0x801b7544 TPLGetGXTexObjFromPalette 0x801b94a4 VIInit 0x801b99ec VIWaitForRetrace 0x801b9f6c VIConfigure @@ -348,6 +1052,21 @@ 0x801e5fe8 NWC24iCleanupSocket 0x801e5ff8 NWC24iLockSocket 0x801e6008 NWC24iUnlockSocket +0x801ec088 SOFinish +0x801ec184 SOStartup +0x801ec190 SOStartupEx +0x801ec5b8 SOCleanup +0x801ec768 SOiGetSysWork +0x801ec774 SOiIsBufferAddrCheck +0x801ec77c SOiIsInitialized +0x801ec7cc SOiAlloc +0x801ec8b4 SOiFree +0x801ec8e8 SOiPrepare +0x801ec9d0 SOiConclude +0x801eca2c SOiPrepareTempRm +0x801ecd04 SOiConcludeTempRm +0x801ecde8 SOiWaitForDHCPEx +0x801ecf20 SOSocket2 0x801ecff4 SOSocket 0x801ed0e4 SOClose 0x801ed188 SOBind From 0e8f466d5b82630b4651b6fb846515b0f5f640fe Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 24 Jul 2021 15:51:33 +0200 Subject: [PATCH 113/477] Make mkwutil.graphic deterministic Closes #52 --- mkwutil/graphic.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/mkwutil/graphic.py b/mkwutil/graphic.py index e98632c35..df0b1378d 100644 --- a/mkwutil/graphic.py +++ b/mkwutil/graphic.py @@ -11,6 +11,9 @@ from mkwutil.slices import Slice, SliceTable +random.seed("OwO") + + DOL_BEGIN = DOL_SECTIONS[0].start DOL_END = DOL_SECTIONS[-1].stop DOL_SIZE = DOL_END - DOL_BEGIN @@ -89,17 +92,24 @@ def lib_boxes(): slices.add(_slice) return map(Box.from_slice, slices) + if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("-o", "--output", type=Path, default="./out/website", help="Output dir") - parser.add_argument("-s", "--silent", action="store_true", help="Don't open web browser") + parser.add_argument( + "-o", "--output", type=Path, default="./out/website", help="Output dir" + ) + parser.add_argument( + "-s", "--silent", action="store_true", help="Don't open web browser" + ) args = parser.parse_args() args.output.mkdir(parents=True, exist_ok=True) index_path = args.output / "index.html" with open(index_path, "w") as file: - jinja_env.get_template("index.html.j2").stream({ - "dol_decomp": standard_boxes(), - "dol_libraries": lib_boxes(), - }).dump(file) + jinja_env.get_template("index.html.j2").stream( + { + "dol_decomp": standard_boxes(), + "dol_libraries": lib_boxes(), + } + ).dump(file) if not args.silent: webbrowser.open(index_path) From 936d2656d27a5d81ce49f9137783a42bec9e2229 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 24 Jul 2021 23:41:03 +0200 Subject: [PATCH 114/477] Fix all warnings (#64) * Fix all warnings Closes #54 * globally disable "function has no prototype" warning --- build.py | 19 ++- mkwutil/sources.py | 101 +++++++------- source/dwc/common/dwc_error.c | 11 +- source/dwc/common/dwc_error.h | 13 ++ source/dwc/common/dwc_init.c | 167 ++++++++++++----------- source/dwc/common/dwc_init.h | 15 +- source/dwc/common/dwc_memfunc.c | 149 +++++++++----------- source/dwc/common/dwc_memfunc.h | 51 ++++--- source/dwc/common/dwci_error.h | 2 - source/dwc/common/dwci_memfunc.h | 14 +- source/gamespy/GP/gpiSearch.c | 4 +- source/gamespy/common/gsDebug.h | 69 ++-------- source/gamespy/common/gsPlatformSocket.h | 8 +- source/gamespy/common/gsXML.h | 5 +- source/gamespy/qr2/qr2.c | 3 +- source/platform/math.h | 5 + source/rvl/so/so.h | 2 + source/rvl/so/soCommon.c | 6 +- 18 files changed, 301 insertions(+), 343 deletions(-) create mode 100644 source/dwc/common/dwc_error.h diff --git a/build.py b/build.py index b86dadd42..015105f76 100644 --- a/build.py +++ b/build.py @@ -75,7 +75,9 @@ def __windows_binary(path): MWLD = __windows_binary(os.path.join("tools", "mwldeppc.exe")) CWCC_PATHS = { - "default": __windows_binary(os.path.join(".", "tools", "4199_60831", "mwcceppc.exe")), + "default": __windows_binary( + os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") + ), # For the main game # August 17, 2007 # 4.2.0.1 Build 127 @@ -84,7 +86,9 @@ def __windows_binary(path): # We don't have this, so we use build 142: # This version has the infuriating bug where random # nops are inserted into your code. - "4201_127": __windows_binary(os.path.join(".", "tools", "4201_142", "mwcceppc.exe")), + "4201_127": __windows_binary( + os.path.join(".", "tools", "4201_142", "mwcceppc.exe") + ), # For most of RVL # We actually have the correct version "4199_60831": __windows_binary( @@ -115,10 +119,11 @@ def __windows_binary(path): "-Cpp_exceptions off", "-RTTI off", '-pragma "cats off"', # ??? + '-pragma "warning off(10178)"', # suppress "function has no prototype" # "-pragma \"aggressive_inline on\"", # "-pragma \"auto_inline on\"", "-inline auto", - "-w notinlined -W noimplicitconv", + "-w notinlined -W noimplicitconv -w nounwanted", "-nostdinc", "-msgstyle gcc -lang=c99 -DREVOKART", "-func_align 4", @@ -130,7 +135,9 @@ def compile_source_impl(src, dst, version="default", additional="-ipa file"): """Compiles a source file.""" # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" - process = subprocess.Popen(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + process = subprocess.Popen( + command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) lines = list(iter(process.stdout.readline, "")) with print_mutex: print(f'{colored("CC", "green")} {src}') @@ -139,7 +146,9 @@ def compile_source_impl(src, dst, version="default", additional="-ipa file"): for line in lines: print(" " + line.strip()) process.wait() - assert process.returncode == 0, f"{command} exited with returncode {process.returncode}" + assert ( + process.returncode == 0 + ), f"{command} exited with returncode {process.returncode}" gSourceQueue = [] diff --git a/mkwutil/sources.py b/mkwutil/sources.py index e68854942..9a8d2b8c8 100644 --- a/mkwutil/sources.py +++ b/mkwutil/sources.py @@ -8,6 +8,7 @@ RVL_OPTS = '-ipa file' +SPY_OPTS = RVL_OPTS + " -w nounusedexpr -w nounusedarg" EGG_OPTS = '-ipa function -rostr' REL_OPTS = '-ipa file -rostr -sdata 0 -sdata2 0' NW4R_OPTS = '-ipa file -inline auto -rostr -O4,p' @@ -66,56 +67,56 @@ class Source: Source(src="source/dwc/common/dwc_error.c", cc='4199_60831', opts=RVL_OPTS), ] SOURCES_SPY = [ - Source(src="source/gamespy/darray.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/hashtable.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/md5c.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/common/revolution/gsSocketRevolution.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/common/gsAvailable.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/common/revolution/gsUtilRevolution.c", cc='4199_60831', opts=RVL_OPTS), - # Source(src="source/gamespy/common/gsPlatformUtil.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/common/gsCore.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/common/gsUdpEngine.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/common/gsXML.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gp.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpi.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiBuddy.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiBuffer.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiCallback.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiConnect.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiInfo.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiKeys.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiOperation.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiPeer.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiProfile.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiSearch.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiTransfer.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiUnique.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gp/gpiUtility.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gt2/gt2Auth.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gt2/gt2Buffer.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gt2/gt2Callback.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gt2/gt2Connection.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gt2/gt2Main.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gt2/gt2Socket.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gt2/gt2Utility.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/qr2/qr2.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/qr2/qr2RegKeys.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpBuffer.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpCallbacks.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpCommon.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpConnection.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpEncryption.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpMain.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpPost.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/ghttp/ghttpProcess.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gstats/gbucket.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/gstats/gstats.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/serverbrowsing/sb_crypt.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/serverbrowsing/sb_queryengine.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/serverbrowsing/sb_server.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/serverbrowsing/sb_serverlist.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/serverbrowsing/sb_serverbrowsing.c", cc='4199_60831', opts=RVL_OPTS), - Source(src="source/gamespy/sake/sakeMain.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/gamespy/darray.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/hashtable.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/md5c.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/common/revolution/gsSocketRevolution.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/common/gsAvailable.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/common/revolution/gsUtilRevolution.c", cc='4199_60831', opts=SPY_OPTS), + # Source(src="source/gamespy/common/gsPlatformUtil.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/common/gsCore.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/common/gsUdpEngine.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/common/gsXML.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gp.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpi.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiBuddy.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiBuffer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiCallback.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiConnect.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiInfo.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiKeys.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiOperation.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiPeer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiProfile.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiSearch.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiTransfer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiUnique.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gp/gpiUtility.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gt2/gt2Auth.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gt2/gt2Buffer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gt2/gt2Callback.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gt2/gt2Connection.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gt2/gt2Main.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gt2/gt2Socket.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gt2/gt2Utility.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/qr2/qr2.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/qr2/qr2RegKeys.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpBuffer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpCallbacks.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpCommon.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpConnection.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpEncryption.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpMain.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpPost.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/ghttp/ghttpProcess.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gstats/gbucket.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/gstats/gstats.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_crypt.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_queryengine.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_server.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_serverlist.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/serverbrowsing/sb_serverbrowsing.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/sake/sakeMain.c", cc='4199_60831', opts=SPY_OPTS), ] SOURCES_NW4R_MATH = [ Source(src="source/nw4r/math/mathTriangular.cpp", cc='4201_127', opts=NW4R_OPTS), diff --git a/source/dwc/common/dwc_error.c b/source/dwc/common/dwc_error.c index 3cb0d2203..eb3349fc1 100644 --- a/source/dwc/common/dwc_error.c +++ b/source/dwc/common/dwc_error.c @@ -1,9 +1,6 @@ -#include -#include +#include "dwc_error.h" -#ifdef __cplusplus -extern "C" { -#endif +#include "dwci_error.h" //! @brief The last error code encountered. //! @@ -95,7 +92,3 @@ void DWCi_SetError(int lastError, int errorCode) { DWC_Printf(-1, "FATALERROR_SET\n"); #endif } - -#ifdef __cplusplus -} -#endif diff --git a/source/dwc/common/dwc_error.h b/source/dwc/common/dwc_error.h new file mode 100644 index 000000000..3dc55672e --- /dev/null +++ b/source/dwc/common/dwc_error.h @@ -0,0 +1,13 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +int DWC_GetLastError(int* errorCode); +int DWC_GetLastErrorEx(int* errorCode, int* errorType); +void DWC_ClearError(); + +#ifdef __cplusplus +} +#endif diff --git a/source/dwc/common/dwc_init.c b/source/dwc/common/dwc_init.c index dc8a2a1a1..28e9ad1e8 100644 --- a/source/dwc/common/dwc_init.c +++ b/source/dwc/common/dwc_init.c @@ -1,124 +1,129 @@ #include // dev -#include -#include -#include #include #include +#include +#include +#include #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif static u32 dwci_gamecode; #ifdef DEBUG -#define DWC_VERSION_STRING "<< RVL_SDK - DWC \tdebug build: Jul 30 2008 11:48:37 (0x4199_60831) >>" +#define DWC_VERSION_STRING \ + "<< RVL_SDK - DWC \tdebug build: Jul 30 2008 11:48:37 (0x4199_60831) >>" #else // MKW version -#define DWC_VERSION_STRING "<< RVL_SDK - DWC \trelease build: Mar 1 2008 21:50:41 (0x4199_60831) >>" +#define DWC_VERSION_STRING \ + "<< RVL_SDK - DWC \trelease build: Mar 1 2008 21:50:41 (0x4199_60831) >>" #endif // no release equivalent yet #define DWC_SDK_STRING "2_0_3_SDK3" -#define DWC_PRINT_VERSION() { \ - DWC_Printf(8, "================================\n"); \ - DWC_Printf(8, "- %s\n", DWC_SDK_STRING); \ - DWC_Printf(8, "--------------------------------\n"); } - -#define DWC_PRINT_DEVSVR() { \ - DWC_Printf(8, "\n"); \ - DWC_Printf(8, "********************************\n"); \ - DWC_Printf(8, "*YOU ARE USING THE DEBUG SERVER*\n"); \ - DWC_Printf(8, "********************************\n"); \ - DWC_Printf(8, "*\n"); \ - DWC_Printf(8, "* Please note that you need to\n"); \ - DWC_Printf(8, "* switch it to the RELEASE\n"); \ - DWC_Printf(8, "* server with your FINALROM!\n"); \ - DWC_Printf(8, "*\n"); \ - DWC_Printf(8, "*******************************\n"); \ - DWC_Printf(8, "\n"); } - -#define DWC_PRINT_DEMO() { \ - DWC_Printf(8, "\n"); \ - DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ - DWC_Printf(8, "x IS THIS DEMO? x\n"); \ - DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ - DWC_Printf(8, "x\n"); \ - DWC_Printf(8, "x You are using the gamecode for\n"); \ - DWC_Printf(8, "x demos. It is not allowed\n"); \ - DWC_Printf(8, "x using this gamecode except\n"); \ - DWC_Printf(8, "x in demos and testing.\n"); \ - DWC_Printf(8, "x\n"); \ - DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ - DWC_Printf(8, "\n"); } - - -#define DWC_SDKDEV_SERVER_HOSTNAME "sdkdev.gamespy.com" +#define DWC_PRINT_VERSION() \ + { \ + DWC_Printf(8, "================================\n"); \ + DWC_Printf(8, "- %s\n", DWC_SDK_STRING); \ + DWC_Printf(8, "--------------------------------\n"); \ + } + +#define DWC_PRINT_DEVSVR() \ + { \ + DWC_Printf(8, "\n"); \ + DWC_Printf(8, "********************************\n"); \ + DWC_Printf(8, "*YOU ARE USING THE DEBUG SERVER*\n"); \ + DWC_Printf(8, "********************************\n"); \ + DWC_Printf(8, "*\n"); \ + DWC_Printf(8, "* Please note that you need to\n"); \ + DWC_Printf(8, "* switch it to the RELEASE\n"); \ + DWC_Printf(8, "* server with your FINALROM!\n"); \ + DWC_Printf(8, "*\n"); \ + DWC_Printf(8, "*******************************\n"); \ + DWC_Printf(8, "\n"); \ + } + +#define DWC_PRINT_DEMO() \ + { \ + DWC_Printf(8, "\n"); \ + DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ + DWC_Printf(8, "x IS THIS DEMO? x\n"); \ + DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ + DWC_Printf(8, "x\n"); \ + DWC_Printf(8, "x You are using the gamecode for\n"); \ + DWC_Printf(8, "x demos. It is not allowed\n"); \ + DWC_Printf(8, "x using this gamecode except\n"); \ + DWC_Printf(8, "x in demos and testing.\n"); \ + DWC_Printf(8, "x\n"); \ + DWC_Printf(8, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n"); \ + DWC_Printf(8, "\n"); \ + } + +#define DWC_SDKDEV_SERVER_HOSTNAME "sdkdev.gamespy.com" #define DWC_GAMESTATS_SERVER_HOSTNAME "gamestats.gs.nintendowifi.net" -int DWC_Init(DWC_AuthServer authSvr, const char* gameName, u32 gameCode, DWCAllocEx allocator, DWCFreeEx freeer) -{ - +int DWC_Init(DWC_AuthServer authSvr, const char* gameName, u32 gameCode, + DWCAllocEx allocator, DWCFreeEx freeer) { - OSRegisterVersion(DWC_VERSION_STRING); + OSRegisterVersion(DWC_VERSION_STRING); - DWCiAssert(authSvr != DWC_SVR_PROHIBITED, "dwc_init.c", 135, "invalid auth server\n"); - DWCiAssert(gameCode, "dwc_init.c", 138, "invalid gamecode\n"); + DWCiAssert(authSvr != DWC_SVR_PROHIBITED, "dwc_init.c", 135, + "invalid auth server\n"); + DWCiAssert(gameCode, "dwc_init.c", 138, "invalid gamecode\n"); - DWCi_SetMemFunc(allocator, freeer); + DWCi_SetMemFunc(allocator, freeer); #ifdef DEBUG - DWC_SetReportLevel(0xffffffff); - DWCi_MemWatch_init(); + DWC_SetReportLevel(0xffffffff); + DWCi_MemWatch_init(); - DWC_PRINT_VERSION(); + DWC_PRINT_VERSION(); - if (authSvr != 1) - DWC_PRINT_DEVSVR(); + if (authSvr != 1) + DWC_PRINT_DEVSVR(); - if (gameCode == 'NTRJ') - DWC_PRINT_DEMO(); + if (gameCode == 'NTRJ') + DWC_PRINT_DEMO(); - if (gameName && !strcmp(gameName, "dwctest")) - DWC_PRINT_DEMO(); + if (gameName && !strcmp(gameName, "dwctest")) + DWC_PRINT_DEMO(); #endif - DWCi_Auth_InitInterface(authSvr); + DWCi_Auth_InitInterface(authSvr); - dwci_gamecode = gameName; + dwci_gamecode = gameName; - gsiMemoryCallbacksSet(&DWCi_GsMalloc, &DWCi_GsFree, &DWCi_GsRealloc, &DWCi_GsMemalign); + gsiMemoryCallbacksSet(&DWCi_GsMalloc, &DWCi_GsFree, &DWCi_GsRealloc, + &DWCi_GsMemalign); - DWCiAssert(strlen(gameName) <= 255, "dwc_init.c", 230, "gamename is too long\n"); + DWCiAssert(strlen(gameName) <= 255, "dwc_init.c", 230, + "gamename is too long\n"); - strcpy(&gcd_gamename, gameName); + strcpy(&gcd_gamename, gameName); - if (authSvr == 0) - strcpy(&StatsServerHostname, DWC_SDKDEV_SERVER_HOSTNAME); - else - strcpy(&StatsServerHostname, DWC_GAMESTATS_SERVER_HOSTNAME); - - DWCi_Np_SetInitFlag(1); - DWCi_Np_GetConsoleId(); + if (authSvr == 0) + strcpy(&StatsServerHostname, DWC_SDKDEV_SERVER_HOSTNAME); + else + strcpy(&StatsServerHostname, DWC_GAMESTATS_SERVER_HOSTNAME); - return 0; + DWCi_Np_SetInitFlag(1); + DWCi_Np_GetConsoleId(); + + return 0; } -void DWC_Shutdown() -{ - gsiMemoryCallbacksSet(&DWCi_GsMalloc, &DWCi_GsFree, &DWCi_GsRealloc, &DWCi_GsMemalign); - gethostbyname("clear"); - DWCi_CfReset(); - DWCi_MemWatch_finish(); - DWCi_Np_SetInitFlag(0); +void DWC_Shutdown() { + gsiMemoryCallbacksSet(&DWCi_GsMalloc, &DWCi_GsFree, &DWCi_GsRealloc, + &DWCi_GsMemalign); + gethostbyname("clear"); + DWCi_CfReset(); + DWCi_MemWatch_finish(); + DWCi_Np_SetInitFlag(0); } -u32 DWCi_GetGamecode() -{ - return dwci_gamecode; -} +u32 DWCi_GetGamecode() { return dwci_gamecode; } #ifdef __cplusplus } #endif diff --git a/source/dwc/common/dwc_init.h b/source/dwc/common/dwc_init.h index 3c9b591aa..0fa102519 100644 --- a/source/dwc/common/dwc_init.h +++ b/source/dwc/common/dwc_init.h @@ -1,23 +1,22 @@ #pragma once -#include #include - +#include #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif typedef enum { - DWC_SVR_DEV, - DWC_SVR_RELEASE, - DWC_SVR_PROHIBITED // test + DWC_SVR_DEV, + DWC_SVR_RELEASE, + DWC_SVR_PROHIBITED // test } DWC_AuthServer; //! @brief Initialize the DWC library. //! //! @param[in] authSvr TODO... -int DWC_Init(DWC_AuthServer authSvr, const char* gameName, u32 gameCode, DWCAllocEx allocator, DWCFreeEx freeer); +int DWC_Init(DWC_AuthServer authSvr, const char* gameName, u32 gameCode, + DWCAllocEx allocator, DWCFreeEx freeer); void DWC_Shutdown(); u32 DWCi_GetGamecode(); #ifdef __cplusplus diff --git a/source/dwc/common/dwc_memfunc.c b/source/dwc/common/dwc_memfunc.c index 924432f27..855ea3a5e 100644 --- a/source/dwc/common/dwc_memfunc.c +++ b/source/dwc/common/dwc_memfunc.c @@ -1,129 +1,112 @@ #include "dwc_memfunc.h" #include "dwci_memfunc.h" -#include #include +#include #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif static DWCAllocEx s_alloc; static DWCFreeEx s_free; +DWCi_AllocateHeader* DWCi_GetAllocateHeader(void* block) { + DWCiAssert(*(u32*)((u32)block - 32) == DWCi_MEMFUNC_SIGNATURE, + "dwc_memfunc.c", 56, + "Failed assertion header->signature == DWCi_MEMFUNC_SIGNATURE"); -DWCi_AllocateHeader* DWCi_GetAllocateHeader(void* block) -{ - DWCiAssert(*(u32*)((u32)block - 32) == DWCi_MEMFUNC_SIGNATURE, "dwc_memfunc.c", 56, "Failed assertion header->signature == DWCi_MEMFUNC_SIGNATURE"); - - return (DWCi_AllocateHeader*)((u32)block - sizeof(DWCi_AllocateHeader)); + return (DWCi_AllocateHeader*)((u32)block - sizeof(DWCi_AllocateHeader)); } -void* DWCi_SetAllocateHeader(DWCi_AllocateHeader* blockHeader, u32 size) -{ - blockHeader->magic = DWCi_MEMFUNC_SIGNATURE; - blockHeader->size = size; - return (void*)&blockHeader[1]; +void* DWCi_SetAllocateHeader(DWCi_AllocateHeader* blockHeader, u32 size) { + blockHeader->magic = DWCi_MEMFUNC_SIGNATURE; + blockHeader->size = size; + return (void*)&blockHeader[1]; } -u32 DWCi_GetAllocateSize(void* block) -{ - DWCi_AllocateHeader* pHeader = DWCi_GetAllocateHeade(block); +u32 DWCi_GetAllocateSize(void* block) { + DWCi_AllocateHeader* pHeader = DWCi_GetAllocateHeade(block); - DWCiAssert(pHeader, "dwc_memfunc.c", 75, "Failed assertion header != NULL"); + DWCiAssert(pHeader, "dwc_memfunc.c", 75, "Failed assertion header != NULL"); - return pHeader->size; -} -void DWCi_SetMemFunc(DWCAllocEx allocator, DWCFreeEx freer) -{ - s_alloc = allocator; - s_free = freer; + return pHeader->size; } -void* DWC_Alloc(DWCAllocType type, u32 size) -{ - DWC_AllocEx(type, size, 32); +void DWCi_SetMemFunc(DWCAllocEx allocator, DWCFreeEx freer) { + s_alloc = allocator; + s_free = freer; } +void* DWC_Alloc(DWCAllocType type, u32 size) { DWC_AllocEx(type, size, 32); } // TODO: Figure out what is in release and what isn't -void* DWC_AllocEx(DWCAllocType type, u32 size, int align) -{ - void* block; +void* DWC_AllocEx(DWCAllocType type, u32 size, int align) { + void* block; - DWCiAssert(DWCi_Np_GetInitFlag(), "dwc_memfunc.c", 158, "DWC hasn't initialized yet."); + DWCiAssert(DWCi_Np_GetInitFlag(), "dwc_memfunc.c", 158, + "DWC hasn't initialized yet."); - DWCiAssert(s_alloc, "dwc_memfunc.c", 160, "DWC_MEMFUNC: Allocator function isn't defined.\n"); - DWCiAssert(align <= 32 && align >= -32, "dwc_memfunc.c", 161, "DWC_Alloc() dosen't support to align over 32 bytes order"); + DWCiAssert(s_alloc, "dwc_memfunc.c", 160, + "DWC_MEMFUNC: Allocator function isn't defined.\n"); + DWCiAssert(align <= 32 && align >= -32, "dwc_memfunc.c", 161, + "DWC_Alloc() dosen't support to align over 32 bytes order"); - block = s_alloc(type, size + sizeof(DWCi_AllocateHeader), align); + block = s_alloc(type, size + sizeof(DWCi_AllocateHeader), align); #ifdef DEBUG - DWCi_MemWatch_add(block, size + sizeof(DWCi_AllocateHeader), type); + DWCi_MemWatch_add(block, size + sizeof(DWCi_AllocateHeader), type); #endif - return block ? DWCi_SetAllocateHeader(block, size) : NULL; + return block ? DWCi_SetAllocateHeader(block, size) : NULL; } -void DWC_Free(DWCAllocType name, void* block, u32 size) -{ - DWCiAssert(DWCi_Np_GetInitFlag(), "dwc_memfunc.c", 191, "DWC hasn't initialized yet."); +void DWC_Free(DWCAllocType name, void* block, u32 size) { + DWCiAssert(DWCi_Np_GetInitFlag(), "dwc_memfunc.c", 191, + "DWC hasn't initialized yet."); - DWCiAssert(s_free, "dwc_memfunc.c", 193, "DWC_MEMFUNC: Free function isn't defined.\n"); + DWCiAssert(s_free, "dwc_memfunc.c", 193, + "DWC_MEMFUNC: Free function isn't defined.\n"); - if (block) - { - block = (void*)DWCi_GetAllocateHeader(block); + if (block) { + block = (void*)DWCi_GetAllocateHeader(block); #ifdef DEBUG - DWCi_MemWatch_del(block); + DWCi_MemWatch_del(block); #endif - s_free(name, block, size); - } + s_free(name, block, size); + } } -void* DWC_Realloc(DWCAllocType type, void* block, u32 before, u32 after) -{ - DWC_ReallocEx(type, block, before, after, 32); +void* DWC_Realloc(DWCAllocType type, void* block, u32 before, u32 after) { + DWC_ReallocEx(type, block, before, after, 32); } -void* DWC_ReallocEx(DWCAllocType type, void* block, u32 before, u32 after, int align) -{ - u32 allocSize; - void* newBlock = DWC_AllocEx(type, after, align); - - if (newBlock == NULL) - return 0; - - if (block) - { - // TODO: Double check this - allocSize = DWCi_GetAllocateSize(block); - - - DWCi_Np_CpuCopy8(block, newBlock, allocSize > after ? after : allocSize); - DWC_Free(type, block, allocSize); - } - return newBlock; +void* DWC_ReallocEx(DWCAllocType type, void* block, u32 before, u32 after, + int align) { + u32 allocSize; + void* newBlock = DWC_AllocEx(type, after, align); + + if (newBlock == NULL) + return 0; + + if (block) { + // TODO: Double check this + allocSize = DWCi_GetAllocateSize(block); + + DWCi_Np_CpuCopy8(block, newBlock, allocSize > after ? after : allocSize); + DWC_Free(type, block, allocSize); + } + return newBlock; } -void* DWCi_GsMalloc(u32 size) -{ - return DWC_Alloc(DWC_ALLOCTYPE_GS, size); -} -void* DWCi_GsRealloc(void* block, u32 size) -{ - DWC_Realloc(DWC_ALLOCTYPE_GS, block, size, size); -} -void* DWCi_GsFree(void* block) -{ - DWC_Free(DWC_ALLOCTYPE_GS, block, 0); +void* DWCi_GsMalloc(u32 size) { return DWC_Alloc(DWC_ALLOCTYPE_GS, size); } +void* DWCi_GsRealloc(void* block, u32 size) { + DWC_Realloc(DWC_ALLOCTYPE_GS, block, size, size); } -void DWCi_GsMemalign(int align, u32 size) -{ - DWC_AllocEx(DWC_ALLOCTYPE_GS, size, align); +void* DWCi_GsFree(void* block) { DWC_Free(DWC_ALLOCTYPE_GS, block, 0); } +void DWCi_GsMemalign(int align, u32 size) { + DWC_AllocEx(DWC_ALLOCTYPE_GS, size, align); } // later in object.. (header??) -void DWCi_Np_CpuCopy8(void* src, void* dst, u32 size) -{ - DWCi_Np_CPUCopyFast(dst, src, size); +void DWCi_Np_CpuCopy8(void* src, void* dst, u32 size) { + DWCi_Np_CPUCopyFast(dst, src, size); } #ifdef __cplusplus diff --git a/source/dwc/common/dwc_memfunc.h b/source/dwc/common/dwc_memfunc.h index 81fa721bd..ebd9acb94 100644 --- a/source/dwc/common/dwc_memfunc.h +++ b/source/dwc/common/dwc_memfunc.h @@ -3,37 +3,36 @@ #include #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif -typedef enum -{ - DWC_ALLOCTYPE_AUTH, - DWC_ALLOCTYPE_AC, - DWC_ALLOCTYPE_BM, - DWC_ALLOCTYPE_UTIL, - DWC_ALLOCTYPE_BASE, - DWC_ALLOCTYPE_LANMATCH, - DWC_ALLOCTYPE_GHTTP, - DWC_ALLOCTYPE_RANKING, - DWC_ALLOCTYPE_ENC, - DWC_ALLOCTYPE_GS, - DWC_ALLOCTYPE_ND, - DWC_ALLOCTYPE_OPTION_CF, - DWC_ALLOCTYPE_NHTTP, - DWC_ALLOCTYPE_MAIL, - DWC_ALLOCTYPE_NUM +typedef enum { + DWC_ALLOCTYPE_AUTH, + DWC_ALLOCTYPE_AC, + DWC_ALLOCTYPE_BM, + DWC_ALLOCTYPE_UTIL, + DWC_ALLOCTYPE_BASE, + DWC_ALLOCTYPE_LANMATCH, + DWC_ALLOCTYPE_GHTTP, + DWC_ALLOCTYPE_RANKING, + DWC_ALLOCTYPE_ENC, + DWC_ALLOCTYPE_GS, + DWC_ALLOCTYPE_ND, + DWC_ALLOCTYPE_OPTION_CF, + DWC_ALLOCTYPE_NHTTP, + DWC_ALLOCTYPE_MAIL, + DWC_ALLOCTYPE_NUM } DWCAllocType; -typedef void* (*DWCAllocEx)(DWCAllocType name, u32 size, int align); -typedef void (*DWCFreeEx )(DWCAllocType name, void* ptr, u32 size ); +typedef void* (*DWCAllocEx)(DWCAllocType name, u32 size, int align); +typedef void (*DWCFreeEx)(DWCAllocType name, void* ptr, u32 size); -void* DWC_Alloc (DWCAllocType name, u32 size); -void* DWC_AllocEx (DWCAllocType name, u32 size, int align); -void DWC_Free (DWCAllocType name, void* ptr, u32 size); -void* DWC_Realloc (DWCAllocType name, void* ptr, u32 oldsize, u32 newsize); -void* DWC_ReallocEx (DWCAllocType name, void* ptr, u32 oldsize, u32 newsize, int align); +void* DWC_Alloc(DWCAllocType name, u32 size); +void* DWC_AllocEx(DWCAllocType name, u32 size, int align); +void DWC_Free(DWCAllocType name, void* ptr, u32 size); +void* DWC_Realloc(DWCAllocType name, void* ptr, u32 oldsize, u32 newsize); +void* DWC_ReallocEx(DWCAllocType name, void* ptr, u32 oldsize, u32 newsize, + int align); #ifdef __cplusplus } diff --git a/source/dwc/common/dwci_error.h b/source/dwc/common/dwci_error.h index 1a85becf8..3cb0fb7d0 100644 --- a/source/dwc/common/dwci_error.h +++ b/source/dwc/common/dwci_error.h @@ -4,10 +4,8 @@ extern "C" { #endif - enum dwcError { DWCErrorNone = 0, DWCErrorFatal = 9 }; - //! @brief @return Return if there is an error. //! int DWCi_IsError(); diff --git a/source/dwc/common/dwci_memfunc.h b/source/dwc/common/dwci_memfunc.h index db8bb9709..94a57197d 100644 --- a/source/dwc/common/dwci_memfunc.h +++ b/source/dwc/common/dwci_memfunc.h @@ -4,17 +4,15 @@ #include #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif #define DWCi_MEMFUNC_SIGNATURE 'DWCM' -typedef struct -{ - u32 magic; // 00 DWCi_MEMFUNC_SIGNATURE - u32 size; // 04 - u32 _[6]; // 08 +typedef struct { + u32 magic; // 00 DWCi_MEMFUNC_SIGNATURE + u32 size; // 04 + u32 _[6]; // 08 } DWCi_AllocateHeader; DWCi_AllocateHeader* DWCi_GetAllocateHeader(void* block); @@ -28,7 +26,7 @@ void DWCi_SetMemFunc(DWCAllocEx allocator, DWCFreeEx freer); void* DWCi_GsMalloc(u32 size); void* DWCi_GsRealloc(void* block, u32 size); void* DWCi_GsFree(void* block); -void DWCi_GsMemalign(int align, u32 size); +void DWCi_GsMemalign(int align, u32 size); #ifdef __cplusplus } diff --git a/source/gamespy/GP/gpiSearch.c b/source/gamespy/GP/gpiSearch.c index d0ca17f57..d815ecf6f 100644 --- a/source/gamespy/GP/gpiSearch.c +++ b/source/gamespy/GP/gpiSearch.c @@ -956,8 +956,8 @@ static GPResult gpiProcessSearch(GPConnection* connection, if (arg->numNicks <= 0) continue; - // Add it. - ////////// + // Add it. + ////////// tempPtr = gsirealloc(arg->uniquenicks, sizeof(char*) * arg->numNicks); if (tempPtr == NULL) diff --git a/source/gamespy/common/gsDebug.h b/source/gamespy/common/gsDebug.h index 08c475cea..3ab0a19ce 100644 --- a/source/gamespy/common/gsDebug.h +++ b/source/gamespy/common/gsDebug.h @@ -85,71 +85,22 @@ extern char* gGSIDebugLevelStrings[GSIDebugLevel_Count]; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Only include static data and functions if GSI_COMMON_DEBUG is defined -#ifndef GSI_COMMON_DEBUG -#define gsDebugFormat -#define gsDebugVaList +#define gsDebugFormat(...) \ + _Pragma("push"); \ + _Pragma("warn_no_side_effect off"); \ + (__VA_ARGS__); \ + _Pragma("pop") +#define gsDebugVaList(...) \ + _Pragma("push"); \ + _Pragma("warn_no_side_effect off"); \ + (__VA_ARGS__); \ + _Pragma("pop") #define gsDebugBinary #define gsSetDebugLevel #define gsSetDebugFile #define gsOpenDebugFile #define gsGetDebugFile #define gsSetDebugCallback -#else - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// User supplied debug function, will receive debug text -typedef void (*GSIDebugCallback)(GSIDebugCategory, GSIDebugType, GSIDebugLevel, - const char*, va_list); - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// Global debug instance -typedef struct GSIDebugInstance { - FILE* mGSIDebugFile; - GSIDebugCallback mDebugCallback; - gsi_i32 mInitialized; - - GSICriticalSection mDebugCrit; - - GSIDebugLevel mGSIDebugLevel[GSIDebugCat_Count][GSIDebugType_Count]; -} GSIDebugInstance; - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// Logging functions -void gsDebugFormat(GSIDebugCategory theCat, GSIDebugType theType, - GSIDebugLevel theLevel, const char* theTokenStr, ...); - -void gsDebugVaList(GSIDebugCategory theCat, GSIDebugType theType, - GSIDebugLevel theLevel, const char* theTokenStr, - va_list theParams); - -void gsDebugBinary(GSIDebugCategory theCat, GSIDebugType theType, - GSIDebugLevel theLevel, const char* theBuffer, - gsi_i32 theLength); - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -// Output functions -void gsSetDebugLevel(GSIDebugCategory theCat, GSIDebugType theType, - GSIDebugLevel theLevel); - -// Set the output file (NULL for no file) -void gsSetDebugFile(FILE* theFile); - -// Open and set the debug file -FILE* gsOpenDebugFile(const char* theFileName); - -// Retrieve the debug file -FILE* gsGetDebugFile(); - -// Set a callback to be triggered with debug output -void gsSetDebugCallback(GSIDebugCallback theCallback); - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -#endif // GSI_COMMON_DEBUG #if defined(__LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) || \ defined(c_plusplus) diff --git a/source/gamespy/common/gsPlatformSocket.h b/source/gamespy/common/gsPlatformSocket.h index abbb9c7aa..7ecc5d9a0 100644 --- a/source/gamespy/common/gsPlatformSocket.h +++ b/source/gamespy/common/gsPlatformSocket.h @@ -115,10 +115,10 @@ int setsockopt(SOCKET sock, int level, int optname, const char* optval, int optlen); #define gethostbyaddr(a, l, t) SOGetHostByAddr(a, l, t) -// The original gamespy SDK defines gethostbyname as an alternative name of SOGetHostByName. -// Mario Kart Wii's code doesn't do this. -// gethostbyname is a wrapper that internally calls SOGetHostByName. -// Probably used to hijack certain hostnames like localhost. +// The original gamespy SDK defines gethostbyname as an alternative name of +// SOGetHostByName. Mario Kart Wii's code doesn't do this. gethostbyname is a +// wrapper that internally calls SOGetHostByName. Probably used to hijack +// certain hostnames like localhost. SOHostEnt* gethostbyname(const char* name); // thread safe DNS lookups diff --git a/source/gamespy/common/gsXML.h b/source/gamespy/common/gsXML.h index 142d99cf0..ddc476841 100644 --- a/source/gamespy/common/gsXML.h +++ b/source/gamespy/common/gsXML.h @@ -104,8 +104,9 @@ gsi_bool gsXmlReadChildAsString(GSXmlStreamReader stream, const char* matchtag, gsi_bool gsXmlReadChildAsStringNT(GSXmlStreamReader stream, const char* matchtag, char valueOut[], int maxLen); -// gsi_bool gsXmlReadChildAsUnicodeString (GSXmlStreamReader stream, const -// char * matchtag, gsi_char ** valueOut, int * lenOut); +gsi_bool gsXmlReadChildAsUnicodeString(GSXmlStreamReader stream, + const char* matchtag, + gsi_char** valueOut, int* lenOut); gsi_bool gsXmlReadChildAsUnicodeStringNT(GSXmlStreamReader stream, const char* matchtag, gsi_char valueOut[], int maxLen); diff --git a/source/gamespy/qr2/qr2.c b/source/gamespy/qr2/qr2.c index 3efcb046f..ba5f7df22 100644 --- a/source/gamespy/qr2/qr2.c +++ b/source/gamespy/qr2/qr2.c @@ -435,7 +435,8 @@ void qr2_think(qr2_t qrec) { // Linker will remove this function through dead-stripping. // We use this to force some strings to be defined. -void define_some_strings() { +void qr2_define_some_strings(); +void qr2_define_some_strings() { char* x; void (*foo)(void); x = "Received %d bytes on query socket\r\n"; // 0x8027d0a4 diff --git a/source/platform/math.h b/source/platform/math.h index cb590ce06..edb438175 100644 --- a/source/platform/math.h +++ b/source/platform/math.h @@ -6,6 +6,9 @@ extern "C" { #endif +#pragma push +#pragma warning off(10216) + f64 sin(f64); inline f32 sinf(f32 x) { return (float)sin(x); }; @@ -21,6 +24,8 @@ inline f32 sqrtf(f32 x) { return (f32)sqrt(x); } f64 acos(f64); inline f32 acosf(f32 x) { return (f32)acos(x); } +#pragma pop + #ifdef __cplusplus } // extern "C" #endif diff --git a/source/rvl/so/so.h b/source/rvl/so/so.h index b2518459d..37f644be7 100644 --- a/source/rvl/so/so.h +++ b/source/rvl/so/so.h @@ -201,6 +201,8 @@ int SOiWaitForDHCPEx(int timeout); // PAL: 0x801ecf20 int SOSocket(int, int, int); +int SOSocket2(int pf, int type, int protocol); + int SOGetInterfaceOpt(IPInterface*, int, int, void*, int*); long SOGetHostID(void); diff --git a/source/rvl/so/soCommon.c b/source/rvl/so/soCommon.c index f8e370ac5..5505df485 100644 --- a/source/rvl/so/soCommon.c +++ b/source/rvl/so/soCommon.c @@ -366,7 +366,7 @@ void SOiFree(u32 name, void* ptr, s32 size) { } } -int SOiPrepare(const char* funcName, s32* pRmId) { +int SOiPrepare(const char* /* funcName */, s32* pRmId) { int result = SO_SUCCESS; int enabled = OSDisableInterrupts(); @@ -401,7 +401,7 @@ int SOiPrepare(const char* funcName, s32* pRmId) { return result; } -int SOiConclude(const char* funcName, int result) { +int SOiConclude(const char* /* funcName */, int result) { int enabled = OSDisableInterrupts(); OSThread* cur = OSGetCurrentThread(); if (cur) @@ -519,7 +519,7 @@ int SOiPrepareTempRm(const char* funcName, s32* pRmId, int* pIsTempRm) { return result; } -int SOiConcludeTempRm(const char* funcName, int result, int isTempRm) { +int SOiConcludeTempRm(const char* /* funcName */, int result, int isTempRm) { int enabled; if (isTempRm == true) { From 12161cc0854251967992c12e54f27fe3dc2dbb40 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 25 Jul 2021 01:39:55 +0200 Subject: [PATCH 115/477] Split ISFS (#68) --- mkwutil/sources.py | 4 + pack/dol_objects.txt | 4 +- pack/dol_slices.csv | 1 + pack/symbols.txt | 96 ++ source/decomp.h | 9 + source/egg/core/eggHeap.cpp | 2 +- source/rvl/arc/binary_format.h | 3 +- source/rvl/fs/fs.c | 2051 ++++++++++++++++++++++++++++++++ source/rvl/fs/fs.h | 74 ++ source/rvl/ios/ios.h | 11 + source/rvl/nand/nand.c | 319 +++-- source/rvl/os/osError.h | 13 + 12 files changed, 2452 insertions(+), 135 deletions(-) create mode 100644 source/rvl/fs/fs.c create mode 100644 source/rvl/fs/fs.h create mode 100644 source/rvl/os/osError.h diff --git a/mkwutil/sources.py b/mkwutil/sources.py index 9a8d2b8c8..4ef08b0e4 100644 --- a/mkwutil/sources.py +++ b/mkwutil/sources.py @@ -32,6 +32,9 @@ class Source: SOURCES_RVL_ARC = [ Source(src="source/rvl/arc/rvlArchive.c", cc='4199_60831', opts=RVL_OPTS), ] +SOURCES_RVL_FS = [ + Source(src="source/rvl/fs/fs.c", cc='4199_60831', opts=RVL_OPTS), +] SOURCES_RVL_MEM = [ Source(src="source/rvl/mem/rvlMemHeap.cpp", cc='4199_60831', opts=RVL_OPTS), Source(src="source/rvl/mem/rvlMemExpHeap.c", cc='4199_60831', opts=RVL_OPTS), @@ -151,6 +154,7 @@ class Source: SOURCES_DOL = list(chain( SOURCES_TRK, SOURCES_RVL_ARC, + SOURCES_RVL_FS, SOURCES_RVL_MEM, SOURCES_RVL_MTX, SOURCES_RVL_NAND, diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index c473d4d68..ded5805c9 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -158,7 +158,9 @@ out\dol\data_8027df3e_8027e708.o out\dol\bss_802f4040_80346cf0.o out\dol\sdata_80385744_803857f0.o out\rvlArchive.o -out\dol\text_80124e80_801981ec.o +out\dol\text_80124e80_80169bcc.o +out\fs.o +out\dol\text_8016b49c_801981ec.o out\dol\data_8027e772_8029cc80.o out\dol\sdata_803857f6_80385a08.o out\dol\sbss_80386448_80386838.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index fa8173639..a2cce6932 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -57,6 +57,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,1,source/gamespy/serverbrowsing/sb_serverlist.c,,,,,,,0x8011e518,0x80121eec,,,,,,,0x8027de18,0x8027de44,,,0x80385730,0x80385740,0x80386440,0x80386448,0x80388498,0x803884a4,, 1,1,source/gamespy/sake/sakeMain.c,,,,,,,0x80121eec,0x8012249c,,,,,,,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,,,,,, 1,,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, +1,,source/rvl/fs/fs.c,,,,,,,0x80169bcc,0x8016b49c,,,,,,,,,,,,,,,,,, 1,,source/rvl/mem/rvlMemHeap.cpp,,,,,,,0x801981ec,0x80198798,,,,,,,,,0x80346cf0,0x80346d18,,,0x80386838,0x8038683C,,,, 1,,source/rvl/mem/rvlMemExpHeap.c,,,,,,,0x80198798,0x80199430,,,,,,,,,,,,,,,,,, 1,,source/rvl/mem/rvlMemFrmHeap.cpp,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index 6fa48c7f7..a777121ea 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -782,6 +782,36 @@ 0x8015e2bc DVDOpen 0x8015e568 DVDClose 0x8015e834 DVDReadPrio +0x80169bcc ISFS_OpenLib +0x80169cf4 _isfsFuncCb +0x80169e74 ISFS_CreateDir +0x80169f68 ISFS_CreateDirAsync +0x8016a05c ISFS_ReadDir +0x8016a1b0 ISFS_ReadDirAsync +0x8016a2f8 ISFS_SetAttr +0x8016a3fc ISFS_SetAttrAsync +0x8016a500 ISFS_GetAttr +0x8016a658 ISFS_GetAttrAsync +0x8016a78c ISFS_Delete +0x8016a864 ISFS_DeleteAsync +0x8016a934 ISFS_Rename +0x8016aa38 ISFS_RenameAsync +0x8016ab3c ISFS_GetUsage +0x8016ac74 ISFS_CreateFile +0x8016ad68 ISFS_CreateFileAsync +0x8016ae5c ISFS_Open +0x8016af24 ISFS_OpenAsync +0x8016afdc ISFS_GetFileStats +0x8016b0ac ISFS_GetFileStatsAsync +0x8016b16c ISFS_Seek +0x8016b170 ISFS_SeekAsync +0x8016b1fc ISFS_Read +0x8016b21c ISFS_ReadAsync +0x8016b2c0 ISFS_Write +0x8016b2e0 ISFS_WriteAsync +0x8016b384 ISFS_Close +0x8016b388 ISFS_CloseAsync +0x8016b40c ISFS_ShutdownAsync 0x8016b850 GXInit 0x8016cec4 GXGetGPStatus 0x8016f438 GXSetDispCopySrc @@ -795,10 +825,43 @@ 0x801722cc GXSetFog 0x801724f8 GXInitFogAdjTable 0x80172658 GXSetFogRangeAdj +0x80192f7c IPCInit +0x80192fc8 IPCReInit +0x80193010 IPCReadReg +0x80193020 IPCWriteReg +0x80193030 IPCGetBufferHi +0x80193038 IPCGetBufferLo +0x80193040 IPCSetBufferLo +0x80193048 strnlen +0x80193074 IpcReplyHandler +0x801932cc IPCInterruptHandler +0x80193478 IPCCltInit +0x8019352c IPCCltReInit +0x801935a0 __ios_Ipc2 0x801937e0 IOS_OpenAsync 0x801938f8 IOS_Open +0x80193a18 IOS_CloseAsync 0x80193ad8 IOS_Close +0x80193b80 IOS_ReadAsync +0x80193c80 IOS_Read +0x80193d88 IOS_WriteAsync +0x80193e88 IOS_Write +0x80193f90 IOS_SeekAsync +0x80194070 IOS_Seek +0x80194158 IOS_IoctlAsync 0x80194290 IOS_Ioctl +0x801943c0 __ios_Ioctlv +0x801944fc IOS_IoctlvAsync +0x801945e0 IOS_Ioctlv +0x801946bc IOS_IoctlvReboot +0x801949b8 iosCreateHeap +0x80194ae8 __iosAlloc +0x80194cec iosAllocAligned +0x80194cf0 iosFree +0x80194edc IPCiProfInit +0x80194f94 IPCiProfQueueReq +0x80195014 IPCiProfAck +0x80195024 IPCiProfReply 0x801981ec MEM_FindHeap__FP7MEMListPCv 0x8019832c MEMiInitHeapHead 0x801984ec MEMiFinalizeHeap @@ -965,13 +1028,46 @@ 0x8019ec2c NANDLoggingAddMessageAsync 0x8019ed24 asyncRoutine 0x801a0504 OSRegisterVersion +0x801a15ec DCEnable +0x801a1600 DCInvalidateRange +0x801a162c DCFlushRange +0x801a165c DCStoreRange +0x801a168c DCFlushRangeNoSync +0x801a16b8 DCStoreRangeNoSync +0x801a16e4 DCZeroRange +0x801a1710 ICInvalidateRange +0x801a1744 ICFlashInvalidate +0x801a1754 ICEnable +0x801a1768 __LCEnable +0x801a1834 LCEnable +0x801a186c LCDisable +0x801a1894 LCLoadBlocks +0x801a18b8 LCStoreBlocks +0x801a18dc LCStoreData +0x801a197c LCQueueLength +0x801a1988 LCQueueWait +0x801a199c DMAErrorHandler 0x801a1e70 OSSetCurrentContext 0x801a2098 OSClearContext 0x801a25d0 OSReport 0x801a2660 OSPanic +0x801a6114 OSGetFontTexel +0x801a63a4 OSGetFontTexture +0x801a64f4 OSGetFontWidth 0x801a65ac OSDisableInterrupts 0x801a65c0 OSEnableInterrupts 0x801a65d4 OSRestoreInterrupts +0x801a65f8 __OSSetInterruptHandler +0x801a660c __OSGetInterruptHandler +0x801a661c __OSInterruptInit +0x801a66e0 SetInterruptMask +0x801a693c __OSMaskInterrupts +0x801a69bc __OSUnmaskInterrupts +0x801a6a3c __OSDispatchInterrupt +0x801a6ce0 ExternalInterruptHandler +0x801a6d30 OSNotifyLink +0x801a6d34 OSNotifyPreLink +0x801a6d38 OSNotifyPostLink 0x801a72fc OSInitMessageQueue 0x801a7eac OSInitMutex 0x801a7ee4 OSLockMutex diff --git a/source/decomp.h b/source/decomp.h index d6655d3fd..d3ba0b12f 100644 --- a/source/decomp.h +++ b/source/decomp.h @@ -11,6 +11,10 @@ // Compiler intrinsics. +// PAL: 0x80021588 +extern void _savegpr_21(void); +// PAL: 0x8002158C +extern void _savegpr_22(void); // PAL: 0x80021590 extern void _savegpr_23(void); // PAL: 0x80021594 @@ -21,6 +25,11 @@ extern void _savegpr_25(void); extern void _savegpr_26(void); // PAL: 0x800215a0 extern void _savegpr_27(void); + +// PAL: 0x800215d4 +extern void _restgpr_21(void); +// PAL: 0x800215d8 +extern void _restgpr_22(void); // PAL: 0x800215dc extern void _restgpr_23(void); // PAL: 0x800215e0 diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp index 328bd931a..b42731b4e 100644 --- a/source/egg/core/eggHeap.cpp +++ b/source/egg/core/eggHeap.cpp @@ -10,9 +10,9 @@ #include #include #include +#include #include -extern "C" void OSReport(const char*, ...); extern "C" struct OSThread* OSGetCurrentThread(); namespace EGG { diff --git a/source/rvl/arc/binary_format.h b/source/rvl/arc/binary_format.h index 6dc599975..dc19699e6 100644 --- a/source/rvl/arc/binary_format.h +++ b/source/rvl/arc/binary_format.h @@ -3,8 +3,9 @@ #include // expects #include // bool +#include + void OSPanic(...); -void OSReport(...); struct rvlArchiveNode { union { diff --git a/source/rvl/fs/fs.c b/source/rvl/fs/fs.c new file mode 100644 index 000000000..361f08842 --- /dev/null +++ b/source/rvl/fs/fs.c @@ -0,0 +1,2051 @@ +#include "fs.h" + +#include + +#include +#include + +// Extern function references. +// PAL: 0x80193030 +extern UNKNOWN_FUNCTION(IPCGetBufferHi); +// PAL: 0x80193038 +extern UNKNOWN_FUNCTION(IPCGetBufferLo); +// PAL: 0x80193040 +extern UNKNOWN_FUNCTION(IPCSetBufferLo); +// PAL: 0x80193048 +extern UNKNOWN_FUNCTION(strnlen); +// PAL: 0x80193a18 +extern UNKNOWN_FUNCTION(IOS_CloseAsync); +// PAL: 0x80193b80 +extern UNKNOWN_FUNCTION(IOS_ReadAsync); +// PAL: 0x80193c80 +extern UNKNOWN_FUNCTION(IOS_Read); +// PAL: 0x80193d88 +extern UNKNOWN_FUNCTION(IOS_WriteAsync); +// PAL: 0x80193e88 +extern UNKNOWN_FUNCTION(IOS_Write); +// PAL: 0x80193f90 +extern UNKNOWN_FUNCTION(IOS_SeekAsync); +// PAL: 0x80194070 +extern UNKNOWN_FUNCTION(IOS_Seek); +// PAL: 0x80194158 +extern UNKNOWN_FUNCTION(IOS_IoctlAsync); +// PAL: 0x801944fc +extern UNKNOWN_FUNCTION(IOS_IoctlvAsync); +// PAL: 0x801945e0 +extern UNKNOWN_FUNCTION(IOS_Ioctlv); +// PAL: 0x801949b8 +extern UNKNOWN_FUNCTION(iosCreateHeap); +// PAL: 0x80194cec +extern UNKNOWN_FUNCTION(iosAllocAligned); +// PAL: 0x80194cf0 +extern UNKNOWN_FUNCTION(iosFree); + +// Symbol: ISFS_OpenLib +// Function signature is unknown. +// PAL: 0x80169bcc..0x80169cf4 +MARK_BINARY_BLOB(ISFS_OpenLib, 0x80169bcc, 0x80169cf4); +asm UNKNOWN_FUNCTION(ISFS_OpenLib) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + li r31, 0; + stw r30, 8(r1); + lwz r0, -0x6490(r13); + cmpwi r0, 0; + bne lbl_80169c00; + bl IPCGetBufferLo; + stw r3, -0x6484(r13); + bl IPCGetBufferHi; + stw r3, -0x6480(r13); +lbl_80169c00: + lwz r3, -0x6484(r13); + lwz r0, -0x6490(r13); + addi r3, r3, 0x1f; + rlwinm r3, r3, 0, 0, 0x1a; + cmpwi r0, 0; + stw r3, -0x648c(r13); + bne lbl_80169c44; + lwz r0, -0x6480(r13); + addi r4, r3, 0x40; + cmplw r4, r0; + ble lbl_80169c44; + lis r3, 0x8029; + addi r3, r3, -25312; + crclr 6; + bl OSReport; + li r31, -22; + b lbl_80169cd8; +lbl_80169c44: + addi r4, r13, -29400; + bl strcpy; + lwz r3, -0x648c(r13); + li r4, 0; + bl IOS_Open; + cmpwi r3, 0; + stw r3, -0x72e0(r13); + bge lbl_80169c6c; + mr r31, r3; + b lbl_80169cd8; +lbl_80169c6c: + lwz r4, -0x6490(r13); + lwz r30, -0x648c(r13); + cmpwi r4, 0; + bne lbl_80169ca4; + lwz r0, -0x6480(r13); + addi r3, r30, 0x1540; + cmplw r3, r0; + ble lbl_80169ca4; + lis r3, 0x8029; + addi r3, r3, -25312; + crclr 6; + bl OSReport; + li r31, -22; + b lbl_80169cd8; +lbl_80169ca4: + cmpwi r4, 0; + bne lbl_80169cbc; + addi r3, r30, 0x1540; + bl IPCSetBufferLo; + li r0, 1; + stw r0, -0x6490(r13); +lbl_80169cbc: + mr r3, r30; + li r4, 0x1540; + bl iosCreateHeap; + cmpwi r3, 0; + stw r3, -0x647c(r13); + bge lbl_80169cd8; + li r31, -22; +lbl_80169cd8: + mr r3, r31; + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: _isfsFuncCb +// Function signature is unknown. +// PAL: 0x80169cf4..0x80169e74 +MARK_BINARY_BLOB(_isfsFuncCb, 0x80169cf4, 0x80169e74); +asm UNKNOWN_FUNCTION(_isfsFuncCb) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi cr1, r3, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + blt cr1, lbl_80169e20; + lwz r0, 0x108(r4); + cmpwi r0, 3; + beq lbl_80169d80; + bge lbl_80169d38; + cmpwi r0, 1; + beq lbl_80169d48; + bge lbl_80169d5c; + b lbl_80169e20; +lbl_80169d38: + cmpwi r0, 5; + beq lbl_80169e10; + bge lbl_80169e20; + b lbl_80169dd8; +lbl_80169d48: + bne cr1, lbl_80169e20; + lwz r3, 0x10c(r4); + li r5, 0x1c; + bl memcpy; + b lbl_80169e20; +lbl_80169d5c: + bne cr1, lbl_80169e20; + addi r0, r4, 0x3f; + lwz r3, 0x10c(r4); + rlwinm r4, r0, 0, 0, 0x1a; + addi r0, r4, 0x5f; + rlwinm r4, r0, 0, 0, 0x1a; + lwz r0, 0(r4); + stw r0, 0(r3); + b lbl_80169e20; +lbl_80169d80: + bne cr1, lbl_80169e20; + addi r0, r4, 0x5f; + lwz r3, 0x10c(r4); + rlwinm r5, r0, 0, 0, 0x1a; + lwz r0, 0(r5); + stw r0, 0(r3); + lhz r0, 4(r5); + lwz r3, 0x110(r4); + sth r0, 0(r3); + lbz r0, 0x49(r5); + lwz r3, 0x114(r4); + stw r0, 0(r3); + lbz r0, 0x46(r5); + lwz r3, 0x118(r4); + stw r0, 0(r3); + lbz r0, 0x47(r5); + lwz r3, 0x11c(r4); + stw r0, 0(r3); + lbz r0, 0x48(r5); + lwz r3, 0x120(r4); + stw r0, 0(r3); + b lbl_80169e20; +lbl_80169dd8: + bne cr1, lbl_80169e20; + addi r0, r4, 0x3f; + lwz r3, 0x10c(r4); + rlwinm r5, r0, 0, 0, 0x1a; + addi r0, r5, 0x5f; + rlwinm r6, r0, 0, 0, 0x1a; + lwz r5, 0(r6); + addi r0, r6, 0x23; + rlwinm r6, r0, 0, 0, 0x1a; + stw r5, 0(r3); + lwz r0, 0(r6); + lwz r3, 0x110(r4); + stw r0, 0(r3); + b lbl_80169e20; +lbl_80169e10: + bne cr1, lbl_80169e20; + lwz r3, 0x10c(r4); + li r5, 8; + bl memcpy; +lbl_80169e20: + li r0, 0; + stw r0, -0x6488(r13); + lwz r12, 0x100(r31); + cmpwi r12, 0; + beq lbl_80169e44; + mr r3, r30; + lwz r4, 0x104(r31); + mtctr r12; + bctrl; +lbl_80169e44: + cmpwi r31, 0; + beq lbl_80169e58; + lwz r3, -0x647c(r13); + mr r4, r31; + bl iosFree; +lbl_80169e58: + mr r3, r30; + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: ISFS_CreateDir +// Function signature is unknown. +// PAL: 0x80169e74..0x80169f68 +MARK_BINARY_BLOB(ISFS_CreateDir, 0x80169e74, 0x80169f68); +asm UNKNOWN_FUNCTION(ISFS_CreateDir) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_25; + cmpwi r3, 0; + mr r25, r3; + mr r26, r4; + mr r27, r5; + mr r28, r6; + mr r29, r7; + li r30, 0; + beq lbl_80169ec8; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_80169ec8; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_80169ed0; +lbl_80169ec8: + li r31, -101; + b lbl_80169f34; +lbl_80169ed0: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_80169ef4; + li r31, -22; + b lbl_80169f34; +lbl_80169ef4: + mr r4, r25; + addi r5, r31, 1; + addi r3, r3, 6; + bl memcpy; + stb r26, 0x49(r30); + mr r5, r30; + li r4, 3; + li r6, 0x4c; + stb r27, 0x46(r30); + li r7, 0; + li r8, 0; + stb r28, 0x47(r30); + stb r29, 0x48(r30); + lwz r3, -0x72e0(r13); + bl IOS_Ioctl; + mr r31, r3; +lbl_80169f34: + cmpwi r30, 0; + beq lbl_80169f4c; + beq lbl_80169f4c; + lwz r3, -0x647c(r13); + mr r4, r30; + bl iosFree; +lbl_80169f4c: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_25; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_CreateDirAsync +// Function signature is unknown. +// PAL: 0x80169f68..0x8016a05c +MARK_BINARY_BLOB(ISFS_CreateDirAsync, 0x80169f68, 0x8016a05c); +asm UNKNOWN_FUNCTION(ISFS_CreateDirAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_23; + cmpwi r3, 0; + mr r23, r3; + mr r24, r4; + mr r25, r5; + mr r26, r6; + mr r27, r7; + mr r28, r8; + mr r29, r9; + beq lbl_80169fc0; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_80169fc0; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_80169fc8; +lbl_80169fc0: + li r3, -101; + b lbl_8016a044; +lbl_80169fc8: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_80169fec; + li r3, -118; + b lbl_8016a044; +lbl_80169fec: + stw r28, 0x100(r3); + li r0, 0; + mr r4, r23; + addi r5, r31, 1; + stw r29, 0x104(r3); + stw r0, 0x108(r3); + addi r3, r3, 6; + bl memcpy; + stb r24, 0x49(r30); + lis r9, 0x8017; + mr r5, r30; + mr r10, r30; + stb r25, 0x46(r30); + addi r9, r9, -25356; + li r4, 3; + li r6, 0x4c; + stb r26, 0x47(r30); + li r7, 0; + li r8, 0; + stb r27, 0x48(r30); + lwz r3, -0x72e0(r13); + bl IOS_IoctlAsync; +lbl_8016a044: + addi r11, r1, 0x30; + bl _restgpr_23; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_ReadDir +// Function signature is unknown. +// PAL: 0x8016a05c..0x8016a1b0 +MARK_BINARY_BLOB(ISFS_ReadDir, 0x8016a05c, 0x8016a1b0); +asm UNKNOWN_FUNCTION(ISFS_ReadDir) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_26; + cmpwi r3, 0; + mr r26, r3; + mr r27, r4; + mr r28, r5; + li r29, 0; + beq lbl_8016a0b8; + cmpwi r5, 0; + beq lbl_8016a0b8; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a0b8; + clrlwi. r0, r4, 0x1b; + bne lbl_8016a0b8; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016a0c0; +lbl_8016a0b8: + li r31, -101; + b lbl_8016a17c; +lbl_8016a0c0: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r29, r3; + bne lbl_8016a0e4; + li r31, -22; + b lbl_8016a17c; +lbl_8016a0e4: + addi r0, r3, 0x3f; + mr r4, r26; + rlwinm r30, r0, 0, 0, 0x1a; + addi r5, r31, 1; + mr r3, r30; + bl memcpy; + stw r30, 0(r29); + li r3, 0x40; + addi r0, r30, 0x5f; + cmpwi r27, 0; + stw r3, 4(r29); + rlwinm r30, r0, 0, 0, 0x1a; + li r3, 4; + stw r30, 8(r29); + stw r3, 0xc(r29); + beq lbl_8016a150; + lwz r0, 0(r28); + li r5, 2; + li r6, 2; + stw r0, 0(r30); + stw r27, 0x10(r29); + lwz r0, 0(r28); + mulli r0, r0, 0xd; + stw r0, 0x14(r29); + stw r30, 0x18(r29); + stw r3, 0x1c(r29); + b lbl_8016a158; +lbl_8016a150: + li r5, 1; + li r6, 1; +lbl_8016a158: + lwz r3, -0x72e0(r13); + mr r7, r29; + li r4, 4; + bl IOS_Ioctlv; + cmpwi r3, 0; + mr r31, r3; + bne lbl_8016a17c; + lwz r0, 0(r30); + stw r0, 0(r28); +lbl_8016a17c: + cmpwi r29, 0; + beq lbl_8016a194; + beq lbl_8016a194; + lwz r3, -0x647c(r13); + mr r4, r29; + bl iosFree; +lbl_8016a194: + addi r11, r1, 0x20; + mr r3, r31; + bl _restgpr_26; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_ReadDirAsync +// Function signature is unknown. +// PAL: 0x8016a1b0..0x8016a2f8 +MARK_BINARY_BLOB(ISFS_ReadDirAsync, 0x8016a1b0, 0x8016a2f8); +asm UNKNOWN_FUNCTION(ISFS_ReadDirAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_25; + cmpwi r3, 0; + mr r25, r3; + mr r26, r4; + mr r27, r5; + mr r28, r6; + mr r29, r7; + beq lbl_8016a210; + cmpwi r5, 0; + beq lbl_8016a210; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a210; + clrlwi. r0, r4, 0x1b; + bne lbl_8016a210; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016a218; +lbl_8016a210: + li r3, -101; + b lbl_8016a2e0; +lbl_8016a218: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016a23c; + li r3, -118; + b lbl_8016a2e0; +lbl_8016a23c: + stw r28, 0x100(r3); + li r6, 2; + addi r0, r3, 0x3f; + mr r4, r25; + stw r29, 0x104(r3); + rlwinm r29, r0, 0, 0, 0x1a; + addi r5, r31, 1; + stw r6, 0x108(r3); + stw r27, 0x10c(r3); + mr r3, r29; + bl memcpy; + stw r29, 0(r30); + li r3, 0x40; + addi r0, r29, 0x5f; + cmpwi r26, 0; + stw r3, 4(r30); + rlwinm r4, r0, 0, 0, 0x1a; + li r3, 4; + stw r4, 8(r30); + stw r3, 0xc(r30); + beq lbl_8016a2bc; + lwz r0, 0(r27); + li r5, 2; + li r6, 2; + stw r0, 0(r4); + stw r26, 0x10(r30); + lwz r0, 0(r27); + mulli r0, r0, 0xd; + stw r0, 0x14(r30); + stw r4, 0x18(r30); + stw r3, 0x1c(r30); + b lbl_8016a2c4; +lbl_8016a2bc: + li r5, 1; + li r6, 1; +lbl_8016a2c4: + lis r8, 0x8017; + lwz r3, -0x72e0(r13); + mr r7, r30; + mr r9, r30; + addi r8, r8, -25356; + li r4, 4; + bl IOS_IoctlvAsync; +lbl_8016a2e0: + addi r11, r1, 0x30; + bl _restgpr_25; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_SetAttr +// Function signature is unknown. +// PAL: 0x8016a2f8..0x8016a3fc +MARK_BINARY_BLOB(ISFS_SetAttr, 0x8016a2f8, 0x8016a3fc); +asm UNKNOWN_FUNCTION(ISFS_SetAttr) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_23; + cmpwi r3, 0; + mr r23, r3; + mr r24, r4; + mr r25, r5; + mr r26, r6; + mr r27, r7; + mr r28, r8; + mr r29, r9; + li r30, 0; + beq lbl_8016a354; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a354; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016a35c; +lbl_8016a354: + li r31, -101; + b lbl_8016a3c8; +lbl_8016a35c: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016a380; + li r31, -22; + b lbl_8016a3c8; +lbl_8016a380: + mr r4, r23; + addi r5, r31, 1; + addi r3, r3, 6; + bl memcpy; + stw r24, 0(r30); + mr r5, r30; + li r4, 5; + li r6, 0x4c; + sth r25, 4(r30); + li r7, 0; + li r8, 0; + stb r26, 0x49(r30); + stb r27, 0x46(r30); + stb r28, 0x47(r30); + stb r29, 0x48(r30); + lwz r3, -0x72e0(r13); + bl IOS_Ioctl; + mr r31, r3; +lbl_8016a3c8: + cmpwi r30, 0; + beq lbl_8016a3e0; + beq lbl_8016a3e0; + lwz r3, -0x647c(r13); + mr r4, r30; + bl iosFree; +lbl_8016a3e0: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_23; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_SetAttrAsync +// Function signature is unknown. +// PAL: 0x8016a3fc..0x8016a500 +MARK_BINARY_BLOB(ISFS_SetAttrAsync, 0x8016a3fc, 0x8016a500); +asm UNKNOWN_FUNCTION(ISFS_SetAttrAsync) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + mflr r0; + stw r0, 0x44(r1); + addi r11, r1, 0x40; + bl _savegpr_21; + cmpwi r3, 0; + lwz r29, 0x48(r1); + mr r21, r3; + mr r22, r4; + mr r23, r5; + mr r24, r6; + mr r25, r7; + mr r26, r8; + mr r27, r9; + mr r28, r10; + beq lbl_8016a45c; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a45c; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016a464; +lbl_8016a45c: + li r3, -101; + b lbl_8016a4e8; +lbl_8016a464: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016a488; + li r3, -118; + b lbl_8016a4e8; +lbl_8016a488: + stw r28, 0x100(r3); + li r0, 0; + mr r4, r21; + addi r5, r31, 1; + stw r29, 0x104(r3); + stw r0, 0x108(r3); + addi r3, r3, 6; + bl memcpy; + stw r22, 0(r30); + lis r9, 0x8017; + mr r5, r30; + mr r10, r30; + sth r23, 4(r30); + addi r9, r9, -25356; + li r4, 5; + li r6, 0x4c; + stb r24, 0x49(r30); + li r7, 0; + li r8, 0; + stb r25, 0x46(r30); + stb r26, 0x47(r30); + stb r27, 0x48(r30); + lwz r3, -0x72e0(r13); + bl IOS_IoctlAsync; +lbl_8016a4e8: + addi r11, r1, 0x40; + bl _restgpr_21; + lwz r0, 0x44(r1); + mtlr r0; + addi r1, r1, 0x40; + blr; + // clang-format on +} + +// Symbol: ISFS_GetAttr +// Function signature is unknown. +// PAL: 0x8016a500..0x8016a658 +MARK_BINARY_BLOB(ISFS_GetAttr, 0x8016a500, 0x8016a658); +asm UNKNOWN_FUNCTION(ISFS_GetAttr) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_23; + cmpwi r3, 0; + mr r30, r3; + mr r23, r4; + mr r24, r5; + mr r25, r6; + mr r26, r7; + mr r27, r8; + mr r28, r9; + li r29, 0; + beq lbl_8016a58c; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a58c; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + beq lbl_8016a58c; + cmpwi r23, 0; + beq lbl_8016a58c; + cmpwi r24, 0; + beq lbl_8016a58c; + cmpwi r25, 0; + beq lbl_8016a58c; + cmpwi r26, 0; + beq lbl_8016a58c; + cmpwi r27, 0; + beq lbl_8016a58c; + cmpwi r28, 0; + bne lbl_8016a594; +lbl_8016a58c: + li r31, -101; + b lbl_8016a624; +lbl_8016a594: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r29, r3; + bne lbl_8016a5b8; + li r31, -22; + b lbl_8016a624; +lbl_8016a5b8: + mr r4, r30; + addi r5, r31, 1; + bl memcpy; + addi r0, r29, 0x5f; + lwz r3, -0x72e0(r13); + rlwinm r30, r0, 0, 0, 0x1a; + mr r5, r29; + mr r7, r30; + li r4, 6; + li r6, 0x40; + li r8, 0x4c; + bl IOS_Ioctl; + cmpwi r3, 0; + mr r31, r3; + bne lbl_8016a624; + lwz r0, 0(r30); + stw r0, 0(r23); + lhz r0, 4(r30); + sth r0, 0(r24); + lbz r0, 0x49(r30); + stw r0, 0(r25); + lbz r0, 0x46(r30); + stw r0, 0(r26); + lbz r0, 0x47(r30); + stw r0, 0(r27); + lbz r0, 0x48(r30); + stw r0, 0(r28); +lbl_8016a624: + cmpwi r29, 0; + beq lbl_8016a63c; + beq lbl_8016a63c; + lwz r3, -0x647c(r13); + mr r4, r29; + bl iosFree; +lbl_8016a63c: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_23; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_GetAttrAsync +// Function signature is unknown. +// PAL: 0x8016a658..0x8016a78c +MARK_BINARY_BLOB(ISFS_GetAttrAsync, 0x8016a658, 0x8016a78c); +asm UNKNOWN_FUNCTION(ISFS_GetAttrAsync) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + mflr r0; + stw r0, 0x44(r1); + addi r11, r1, 0x40; + bl _savegpr_21; + cmpwi r3, 0; + lwz r29, 0x48(r1); + mr r21, r3; + mr r22, r4; + mr r23, r5; + mr r24, r6; + mr r25, r7; + mr r26, r8; + mr r27, r9; + mr r28, r10; + beq lbl_8016a6e8; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a6e8; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + beq lbl_8016a6e8; + cmpwi r22, 0; + beq lbl_8016a6e8; + cmpwi r23, 0; + beq lbl_8016a6e8; + cmpwi r24, 0; + beq lbl_8016a6e8; + cmpwi r25, 0; + beq lbl_8016a6e8; + cmpwi r26, 0; + beq lbl_8016a6e8; + cmpwi r27, 0; + bne lbl_8016a6f0; +lbl_8016a6e8: + li r3, -101; + b lbl_8016a774; +lbl_8016a6f0: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016a714; + li r3, -118; + b lbl_8016a774; +lbl_8016a714: + stw r22, 0x10c(r3); + li r0, 3; + mr r4, r21; + addi r5, r31, 1; + stw r23, 0x110(r3); + stw r24, 0x114(r3); + stw r25, 0x118(r3); + stw r26, 0x11c(r3); + stw r27, 0x120(r3); + stw r28, 0x100(r3); + stw r29, 0x104(r3); + stw r0, 0x108(r3); + bl memcpy; + addi r0, r30, 0x5f; + lis r9, 0x8017; + lwz r3, -0x72e0(r13); + mr r5, r30; + mr r10, r30; + rlwinm r7, r0, 0, 0, 0x1a; + addi r9, r9, -25356; + li r4, 6; + li r6, 0x40; + li r8, 0x4c; + bl IOS_IoctlAsync; +lbl_8016a774: + addi r11, r1, 0x40; + bl _restgpr_21; + lwz r0, 0x44(r1); + mtlr r0; + addi r1, r1, 0x40; + blr; + // clang-format on +} + +// Symbol: ISFS_Delete +// Function signature is unknown. +// PAL: 0x8016a78c..0x8016a864 +MARK_BINARY_BLOB(ISFS_Delete, 0x8016a78c, 0x8016a864); +asm UNKNOWN_FUNCTION(ISFS_Delete) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + mr r29, r3; + beq lbl_8016a7d4; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a7d4; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016a7dc; +lbl_8016a7d4: + li r31, -101; + b lbl_8016a82c; +lbl_8016a7dc: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016a800; + li r31, -22; + b lbl_8016a82c; +lbl_8016a800: + mr r4, r29; + addi r5, r31, 1; + bl memcpy; + lwz r3, -0x72e0(r13); + mr r5, r30; + li r4, 7; + li r6, 0x40; + li r7, 0; + li r8, 0; + bl IOS_Ioctl; + mr r31, r3; +lbl_8016a82c: + cmpwi r30, 0; + beq lbl_8016a844; + beq lbl_8016a844; + lwz r3, -0x647c(r13); + mr r4, r30; + bl iosFree; +lbl_8016a844: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_DeleteAsync +// Function signature is unknown. +// PAL: 0x8016a864..0x8016a934 +MARK_BINARY_BLOB(ISFS_DeleteAsync, 0x8016a864, 0x8016a934); +asm UNKNOWN_FUNCTION(ISFS_DeleteAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + cmpwi r3, 0; + mr r27, r3; + mr r28, r4; + mr r29, r5; + beq lbl_8016a8ac; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a8ac; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016a8b4; +lbl_8016a8ac: + li r3, -101; + b lbl_8016a91c; +lbl_8016a8b4: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016a8d8; + li r3, -118; + b lbl_8016a91c; +lbl_8016a8d8: + mr r4, r27; + addi r5, r31, 1; + bl memcpy; + stw r28, 0x100(r30); + lis r9, 0x8017; + li r0, 0; + mr r5, r30; + stw r29, 0x104(r30); + mr r10, r30; + addi r9, r9, -25356; + li r4, 7; + stw r0, 0x108(r30); + li r6, 0x40; + li r7, 0; + li r8, 0; + lwz r3, -0x72e0(r13); + bl IOS_IoctlAsync; +lbl_8016a91c: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_Rename +// Function signature is unknown. +// PAL: 0x8016a934..0x8016aa38 +MARK_BINARY_BLOB(ISFS_Rename, 0x8016a934, 0x8016aa38); +asm UNKNOWN_FUNCTION(ISFS_Rename) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + cmpwi r3, 0; + mr r27, r3; + mr r28, r4; + li r29, 0; + beq lbl_8016a99c; + cmpwi r4, 0; + beq lbl_8016a99c; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016a99c; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r30, r3; + beq lbl_8016a99c; + mr r3, r28; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016a9a4; +lbl_8016a99c: + li r30, -101; + b lbl_8016aa04; +lbl_8016a9a4: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r29, r3; + bne lbl_8016a9c8; + li r30, -22; + b lbl_8016aa04; +lbl_8016a9c8: + mr r4, r27; + addi r5, r30, 1; + bl memcpy; + mr r4, r28; + addi r3, r29, 0x40; + addi r5, r31, 1; + bl memcpy; + lwz r3, -0x72e0(r13); + mr r5, r29; + li r4, 8; + li r6, 0x80; + li r7, 0; + li r8, 0; + bl IOS_Ioctl; + mr r30, r3; +lbl_8016aa04: + cmpwi r29, 0; + beq lbl_8016aa1c; + beq lbl_8016aa1c; + lwz r3, -0x647c(r13); + mr r4, r29; + bl iosFree; +lbl_8016aa1c: + addi r11, r1, 0x20; + mr r3, r30; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_RenameAsync +// Function signature is unknown. +// PAL: 0x8016aa38..0x8016ab3c +MARK_BINARY_BLOB(ISFS_RenameAsync, 0x8016aa38, 0x8016ab3c); +asm UNKNOWN_FUNCTION(ISFS_RenameAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_25; + cmpwi r3, 0; + mr r25, r3; + mr r26, r4; + mr r27, r5; + mr r28, r6; + beq lbl_8016aaa4; + cmpwi r4, 0; + beq lbl_8016aaa4; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016aaa4; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r30, r3; + beq lbl_8016aaa4; + mr r3, r26; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016aaac; +lbl_8016aaa4: + li r3, -101; + b lbl_8016ab24; +lbl_8016aaac: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r29, r3; + bne lbl_8016aad0; + li r3, -118; + b lbl_8016ab24; +lbl_8016aad0: + stw r27, 0x100(r3); + li r0, 0; + mr r4, r25; + addi r5, r30, 1; + stw r28, 0x104(r3); + stw r0, 0x108(r3); + bl memcpy; + mr r4, r26; + addi r3, r29, 0x40; + addi r5, r31, 1; + bl memcpy; + lis r9, 0x8017; + lwz r3, -0x72e0(r13); + mr r5, r29; + mr r10, r29; + addi r9, r9, -25356; + li r4, 8; + li r6, 0x80; + li r7, 0; + li r8, 0; + bl IOS_IoctlAsync; +lbl_8016ab24: + addi r11, r1, 0x30; + bl _restgpr_25; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_GetUsage +// Function signature is unknown. +// PAL: 0x8016ab3c..0x8016ac74 +MARK_BINARY_BLOB(ISFS_GetUsage, 0x8016ab3c, 0x8016ac74); +asm UNKNOWN_FUNCTION(ISFS_GetUsage) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_26; + cmpwi r3, 0; + mr r30, r3; + mr r26, r4; + mr r27, r5; + li r28, 0; + beq lbl_8016ab98; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016ab98; + cmpwi r4, 0; + beq lbl_8016ab98; + cmpwi r5, 0; + beq lbl_8016ab98; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016aba0; +lbl_8016ab98: + li r31, -101; + b lbl_8016ac40; +lbl_8016aba0: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r28, r3; + bne lbl_8016abc4; + li r31, -22; + b lbl_8016ac40; +lbl_8016abc4: + addi r0, r3, 0x37; + mr r4, r30; + rlwinm r29, r0, 0, 0, 0x1a; + addi r5, r31, 1; + mr r3, r29; + bl memcpy; + stw r29, 0(r28); + li r4, 0x40; + addi r3, r29, 0x5f; + li r0, 4; + stw r4, 4(r28); + rlwinm r30, r3, 0, 0, 0x1a; + addi r3, r30, 0x23; + mr r7, r28; + stw r30, 8(r28); + rlwinm r29, r3, 0, 0, 0x1a; + li r4, 0xc; + li r5, 1; + stw r0, 0xc(r28); + li r6, 2; + stw r29, 0x10(r28); + stw r0, 0x14(r28); + lwz r3, -0x72e0(r13); + bl IOS_Ioctlv; + cmpwi r3, 0; + mr r31, r3; + bne lbl_8016ac40; + lwz r0, 0(r30); + stw r0, 0(r26); + lwz r0, 0(r29); + stw r0, 0(r27); +lbl_8016ac40: + cmpwi r28, 0; + beq lbl_8016ac58; + beq lbl_8016ac58; + lwz r3, -0x647c(r13); + mr r4, r28; + bl iosFree; +lbl_8016ac58: + addi r11, r1, 0x20; + mr r3, r31; + bl _restgpr_26; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_CreateFile +// Function signature is unknown. +// PAL: 0x8016ac74..0x8016ad68 +MARK_BINARY_BLOB(ISFS_CreateFile, 0x8016ac74, 0x8016ad68); +asm UNKNOWN_FUNCTION(ISFS_CreateFile) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_25; + cmpwi r3, 0; + mr r25, r3; + mr r26, r4; + mr r27, r5; + mr r28, r6; + mr r29, r7; + li r30, 0; + beq lbl_8016acc8; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016acc8; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016acd0; +lbl_8016acc8: + li r31, -101; + b lbl_8016ad34; +lbl_8016acd0: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016acf4; + li r31, -22; + b lbl_8016ad34; +lbl_8016acf4: + mr r4, r25; + addi r5, r31, 1; + addi r3, r3, 6; + bl memcpy; + stb r26, 0x49(r30); + mr r5, r30; + li r4, 9; + li r6, 0x4c; + stb r27, 0x46(r30); + li r7, 0; + li r8, 0; + stb r28, 0x47(r30); + stb r29, 0x48(r30); + lwz r3, -0x72e0(r13); + bl IOS_Ioctl; + mr r31, r3; +lbl_8016ad34: + cmpwi r30, 0; + beq lbl_8016ad4c; + beq lbl_8016ad4c; + lwz r3, -0x647c(r13); + mr r4, r30; + bl iosFree; +lbl_8016ad4c: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_25; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_CreateFileAsync +// Function signature is unknown. +// PAL: 0x8016ad68..0x8016ae5c +MARK_BINARY_BLOB(ISFS_CreateFileAsync, 0x8016ad68, 0x8016ae5c); +asm UNKNOWN_FUNCTION(ISFS_CreateFileAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_23; + cmpwi r3, 0; + mr r23, r3; + mr r24, r4; + mr r25, r5; + mr r26, r6; + mr r27, r7; + mr r28, r8; + mr r29, r9; + beq lbl_8016adc0; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + blt lbl_8016adc0; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016adc8; +lbl_8016adc0: + li r3, -101; + b lbl_8016ae44; +lbl_8016adc8: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016adec; + li r3, -118; + b lbl_8016ae44; +lbl_8016adec: + stw r28, 0x100(r3); + li r0, 0; + mr r4, r23; + addi r5, r31, 1; + stw r29, 0x104(r3); + stw r0, 0x108(r3); + addi r3, r3, 6; + bl memcpy; + stb r24, 0x49(r30); + lis r9, 0x8017; + mr r5, r30; + mr r10, r30; + stb r25, 0x46(r30); + addi r9, r9, -25356; + li r4, 9; + li r6, 0x4c; + stb r26, 0x47(r30); + li r7, 0; + li r8, 0; + stb r27, 0x48(r30); + lwz r3, -0x72e0(r13); + bl IOS_IoctlAsync; +lbl_8016ae44: + addi r11, r1, 0x30; + bl _restgpr_23; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: ISFS_Open +// Function signature is unknown. +// PAL: 0x8016ae5c..0x8016af24 +MARK_BINARY_BLOB(ISFS_Open, 0x8016ae5c, 0x8016af24); +asm UNKNOWN_FUNCTION(ISFS_Open) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + beq lbl_8016aea0; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016aea8; +lbl_8016aea0: + li r31, -101; + b lbl_8016aee8; +lbl_8016aea8: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016aecc; + li r31, -22; + b lbl_8016aee8; +lbl_8016aecc: + mr r4, r28; + addi r5, r31, 1; + bl memcpy; + mr r3, r30; + mr r4, r29; + bl IOS_Open; + mr r31, r3; +lbl_8016aee8: + cmpwi r30, 0; + beq lbl_8016af00; + beq lbl_8016af00; + lwz r3, -0x647c(r13); + mr r4, r30; + bl iosFree; +lbl_8016af00: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_OpenAsync +// Function signature is unknown. +// PAL: 0x8016af24..0x8016afdc +MARK_BINARY_BLOB(ISFS_OpenAsync, 0x8016af24, 0x8016afdc); +asm UNKNOWN_FUNCTION(ISFS_OpenAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_26; + cmpwi r3, 0; + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r29, r6; + beq lbl_8016af64; + li r4, 0x40; + bl strnlen; + cmplwi r3, 0x40; + mr r31, r3; + bne lbl_8016af6c; +lbl_8016af64: + li r3, -101; + b lbl_8016afc4; +lbl_8016af6c: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016af90; + li r3, -118; + b lbl_8016afc4; +lbl_8016af90: + stw r28, 0x100(r3); + li r0, 0; + mr r4, r26; + addi r5, r31, 1; + stw r29, 0x104(r3); + stw r0, 0x108(r3); + bl memcpy; + lis r5, 0x8017; + mr r3, r30; + mr r4, r27; + mr r6, r30; + addi r5, r5, -25356; + bl IOS_OpenAsync; +lbl_8016afc4: + addi r11, r1, 0x20; + bl _restgpr_26; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_GetFileStats +// Function signature is unknown. +// PAL: 0x8016afdc..0x8016b0ac +MARK_BINARY_BLOB(ISFS_GetFileStats, 0x8016afdc, 0x8016b0ac); +asm UNKNOWN_FUNCTION(ISFS_GetFileStats) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + cmpwi r4, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r3; + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + mr r29, r4; + beq lbl_8016b010; + clrlwi. r0, r4, 0x1b; + beq lbl_8016b018; +lbl_8016b010: + li r31, -101; + b lbl_8016b074; +lbl_8016b018: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + mr r30, r3; + bne lbl_8016b03c; + li r31, -22; + b lbl_8016b074; +lbl_8016b03c: + mr r3, r31; + mr r7, r30; + li r4, 0xb; + li r5, 0; + li r6, 0; + li r8, 8; + bl IOS_Ioctl; + cmpwi r3, 0; + mr r31, r3; + bne lbl_8016b074; + mr r3, r29; + mr r4, r30; + li r5, 8; + bl memcpy; +lbl_8016b074: + cmpwi r30, 0; + beq lbl_8016b08c; + beq lbl_8016b08c; + lwz r3, -0x647c(r13); + mr r4, r30; + bl iosFree; +lbl_8016b08c: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_GetFileStatsAsync +// Function signature is unknown. +// PAL: 0x8016b0ac..0x8016b16c +MARK_BINARY_BLOB(ISFS_GetFileStatsAsync, 0x8016b0ac, 0x8016b16c); +asm UNKNOWN_FUNCTION(ISFS_GetFileStatsAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + cmpwi r4, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r6; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + beq lbl_8016b0e8; + clrlwi. r0, r4, 0x1b; + beq lbl_8016b0f0; +lbl_8016b0e8: + li r3, -101; + b lbl_8016b14c; +lbl_8016b0f0: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + bne lbl_8016b110; + li r3, -118; + b lbl_8016b14c; +lbl_8016b110: + stw r30, 0x100(r3); + lis r9, 0x8017; + li r0, 5; + mr r7, r3; + stw r31, 0x104(r3); + mr r10, r3; + addi r9, r9, -25356; + li r4, 0xb; + stw r0, 0x108(r3); + li r5, 0; + li r6, 0; + li r8, 8; + stw r29, 0x10c(r3); + mr r3, r28; + bl IOS_IoctlAsync; +lbl_8016b14c: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_Seek +// Function signature is unknown. +// PAL: 0x8016b16c..0x8016b170 +MARK_BINARY_BLOB(ISFS_Seek, 0x8016b16c, 0x8016b170); +asm UNKNOWN_FUNCTION(ISFS_Seek) { + nofralloc; + b IOS_Seek; +} + +// Symbol: ISFS_SeekAsync +// Function signature is unknown. +// PAL: 0x8016b170..0x8016b1fc +MARK_BINARY_BLOB(ISFS_SeekAsync, 0x8016b170, 0x8016b1fc); +asm UNKNOWN_FUNCTION(ISFS_SeekAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + mr r27, r3; + lwz r3, -0x647c(r13); + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + bne lbl_8016b1b8; + li r3, -118; + b lbl_8016b1e4; +lbl_8016b1b8: + stw r30, 0x100(r3); + lis r6, 0x8017; + li r0, 0; + mr r4, r28; + stw r31, 0x104(r3); + mr r5, r29; + mr r7, r3; + addi r6, r6, -25356; + stw r0, 0x108(r3); + mr r3, r27; + bl IOS_SeekAsync; +lbl_8016b1e4: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_Read +// Function signature is unknown. +// PAL: 0x8016b1fc..0x8016b21c +MARK_BINARY_BLOB(ISFS_Read, 0x8016b1fc, 0x8016b21c); +asm UNKNOWN_FUNCTION(ISFS_Read) { + // clang-format off + nofralloc; + cmpwi r4, 0; + beq lbl_8016b20c; + clrlwi. r0, r4, 0x1b; + beq lbl_8016b214; +lbl_8016b20c: + li r3, -101; + blr; +lbl_8016b214: + b IOS_Read; + blr; + // clang-format on +} + +// Symbol: ISFS_ReadAsync +// Function signature is unknown. +// PAL: 0x8016b21c..0x8016b2c0 +MARK_BINARY_BLOB(ISFS_ReadAsync, 0x8016b21c, 0x8016b2c0); +asm UNKNOWN_FUNCTION(ISFS_ReadAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + cmpwi r4, 0; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + beq lbl_8016b254; + clrlwi. r0, r4, 0x1b; + beq lbl_8016b25c; +lbl_8016b254: + li r3, -101; + b lbl_8016b2a8; +lbl_8016b25c: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + bne lbl_8016b27c; + li r3, -118; + b lbl_8016b2a8; +lbl_8016b27c: + stw r30, 0x100(r3); + lis r6, 0x8017; + li r0, 0; + mr r4, r28; + stw r31, 0x104(r3); + mr r5, r29; + mr r7, r3; + addi r6, r6, -25356; + stw r0, 0x108(r3); + mr r3, r27; + bl IOS_ReadAsync; +lbl_8016b2a8: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_Write +// Function signature is unknown. +// PAL: 0x8016b2c0..0x8016b2e0 +MARK_BINARY_BLOB(ISFS_Write, 0x8016b2c0, 0x8016b2e0); +asm UNKNOWN_FUNCTION(ISFS_Write) { + // clang-format off + nofralloc; + cmpwi r4, 0; + beq lbl_8016b2d0; + clrlwi. r0, r4, 0x1b; + beq lbl_8016b2d8; +lbl_8016b2d0: + li r3, -101; + blr; +lbl_8016b2d8: + b IOS_Write; + blr; + // clang-format on +} + +// Symbol: ISFS_WriteAsync +// Function signature is unknown. +// PAL: 0x8016b2e0..0x8016b384 +MARK_BINARY_BLOB(ISFS_WriteAsync, 0x8016b2e0, 0x8016b384); +asm UNKNOWN_FUNCTION(ISFS_WriteAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + cmpwi r4, 0; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r30, r6; + mr r31, r7; + beq lbl_8016b318; + clrlwi. r0, r4, 0x1b; + beq lbl_8016b320; +lbl_8016b318: + li r3, -101; + b lbl_8016b36c; +lbl_8016b320: + lwz r3, -0x647c(r13); + li r4, 0x140; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + bne lbl_8016b340; + li r3, -118; + b lbl_8016b36c; +lbl_8016b340: + stw r30, 0x100(r3); + lis r6, 0x8017; + li r0, 0; + mr r4, r28; + stw r31, 0x104(r3); + mr r5, r29; + mr r7, r3; + addi r6, r6, -25356; + stw r0, 0x108(r3); + mr r3, r27; + bl IOS_WriteAsync; +lbl_8016b36c: + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_Close +// Function signature is unknown. +// PAL: 0x8016b384..0x8016b388 +MARK_BINARY_BLOB(ISFS_Close, 0x8016b384, 0x8016b388); +asm UNKNOWN_FUNCTION(ISFS_Close) { + // clang-format off + nofralloc; + b IOS_Close; + // clang-format on +} + +// Symbol: ISFS_CloseAsync +// Function signature is unknown. +// PAL: 0x8016b388..0x8016b40c +MARK_BINARY_BLOB(ISFS_CloseAsync, 0x8016b388, 0x8016b40c); +asm UNKNOWN_FUNCTION(ISFS_CloseAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + li r5, 0x20; + stw r30, 0x18(r1); + mr r30, r4; + li r4, 0x140; + stw r29, 0x14(r1); + mr r29, r3; + lwz r3, -0x647c(r13); + bl iosAllocAligned; + cmpwi r3, 0; + bne lbl_8016b3cc; + li r3, -118; + b lbl_8016b3f0; +lbl_8016b3cc: + stw r30, 0x100(r3); + lis r4, 0x8017; + li r0, 0; + mr r5, r3; + stw r31, 0x104(r3); + addi r4, r4, -25356; + stw r0, 0x108(r3); + mr r3, r29; + bl IOS_CloseAsync; +lbl_8016b3f0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ISFS_ShutdownAsync +// Function signature is unknown. +// PAL: 0x8016b40c..0x8016b49c +MARK_BINARY_BLOB(ISFS_ShutdownAsync, 0x8016b40c, 0x8016b49c); +asm UNKNOWN_FUNCTION(ISFS_ShutdownAsync) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + li r5, 0x20; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + li r4, 0x140; + stw r30, 8(r1); + mr r30, r3; + lwz r3, -0x647c(r13); + bl iosAllocAligned; + lwz r0, -0x72e0(r13); + cmpwi r0, 0; + bge lbl_8016b44c; + li r3, -101; + b lbl_8016b484; +lbl_8016b44c: + stw r30, 0x100(r3); + lis r9, 0x8017; + li r0, 0; + mr r10, r3; + stw r31, 0x104(r3); + addi r9, r9, -25356; + li r4, 0xd; + li r5, 0; + stw r0, 0x108(r3); + li r6, 0; + li r7, 0; + li r8, 0; + lwz r3, -0x72e0(r13); + bl IOS_IoctlAsync; +lbl_8016b484: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} diff --git a/source/rvl/fs/fs.h b/source/rvl/fs/fs.h new file mode 100644 index 000000000..fa8cced76 --- /dev/null +++ b/source/rvl/fs/fs.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x80169bcc..0x80169cf4 +UNKNOWN_FUNCTION(ISFS_OpenLib); +// PAL: 0x80169cf4..0x80169e74 +UNKNOWN_FUNCTION(_isfsFuncCb); +// PAL: 0x80169e74..0x80169f68 +UNKNOWN_FUNCTION(ISFS_CreateDir); +// PAL: 0x80169f68..0x8016a05c +UNKNOWN_FUNCTION(ISFS_CreateDirAsync); +// PAL: 0x8016a05c..0x8016a1b0 +UNKNOWN_FUNCTION(ISFS_ReadDir); +// PAL: 0x8016a1b0..0x8016a2f8 +UNKNOWN_FUNCTION(ISFS_ReadDirAsync); +// PAL: 0x8016a2f8..0x8016a3fc +UNKNOWN_FUNCTION(ISFS_SetAttr); +// PAL: 0x8016a3fc..0x8016a500 +UNKNOWN_FUNCTION(ISFS_SetAttrAsync); +// PAL: 0x8016a500..0x8016a658 +UNKNOWN_FUNCTION(ISFS_GetAttr); +// PAL: 0x8016a658..0x8016a78c +UNKNOWN_FUNCTION(ISFS_GetAttrAsync); +// PAL: 0x8016a78c..0x8016a864 +UNKNOWN_FUNCTION(ISFS_Delete); +// PAL: 0x8016a864..0x8016a934 +UNKNOWN_FUNCTION(ISFS_DeleteAsync); +// PAL: 0x8016a934..0x8016aa38 +UNKNOWN_FUNCTION(ISFS_Rename); +// PAL: 0x8016aa38..0x8016ab3c +UNKNOWN_FUNCTION(ISFS_RenameAsync); +// PAL: 0x8016ab3c..0x8016ac74 +UNKNOWN_FUNCTION(ISFS_GetUsage); +// PAL: 0x8016ac74..0x8016ad68 +UNKNOWN_FUNCTION(ISFS_CreateFile); +// PAL: 0x8016ad68..0x8016ae5c +UNKNOWN_FUNCTION(ISFS_CreateFileAsync); +// PAL: 0x8016ae5c..0x8016af24 +UNKNOWN_FUNCTION(ISFS_Open); +// PAL: 0x8016af24..0x8016afdc +UNKNOWN_FUNCTION(ISFS_OpenAsync); +// PAL: 0x8016afdc..0x8016b0ac +UNKNOWN_FUNCTION(ISFS_GetFileStats); +// PAL: 0x8016b0ac..0x8016b16c +UNKNOWN_FUNCTION(ISFS_GetFileStatsAsync); +// PAL: 0x8016b16c..0x8016b170 +UNKNOWN_FUNCTION(ISFS_Seek); +// PAL: 0x8016b170..0x8016b1fc +UNKNOWN_FUNCTION(ISFS_SeekAsync); +// PAL: 0x8016b1fc..0x8016b21c +UNKNOWN_FUNCTION(ISFS_Read); +// PAL: 0x8016b21c..0x8016b2c0 +UNKNOWN_FUNCTION(ISFS_ReadAsync); +// PAL: 0x8016b2c0..0x8016b2e0 +UNKNOWN_FUNCTION(ISFS_Write); +// PAL: 0x8016b2e0..0x8016b384 +UNKNOWN_FUNCTION(ISFS_WriteAsync); +// PAL: 0x8016b384..0x8016b388 +UNKNOWN_FUNCTION(ISFS_Close); +// PAL: 0x8016b388..0x8016b40c +UNKNOWN_FUNCTION(ISFS_CloseAsync); +// PAL: 0x8016b40c..0x8016b49c +UNKNOWN_FUNCTION(ISFS_ShutdownAsync); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/ios/ios.h b/source/rvl/ios/ios.h index 0ba94fe9a..f2ffb5170 100644 --- a/source/rvl/ios/ios.h +++ b/source/rvl/ios/ios.h @@ -2,6 +2,17 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + +typedef s32 (*IOSCallback) (s32 errCode, void * data); + s32 IOS_Open(const char*, u32); +s32 IOS_OpenAsync(const char*, u32, IOSCallback); s32 IOS_Close(u32); s32 IOS_Ioctl(s32, s32, void*, u32, void*, u32); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/nand/nand.c b/source/rvl/nand/nand.c index a8f8f2055..10b678245 100644 --- a/source/rvl/nand/nand.c +++ b/source/rvl/nand/nand.c @@ -6,7 +6,9 @@ #include #include +#include #include +#include // Extern function references. // PAL: 0x801671d0 @@ -17,68 +19,20 @@ extern UNKNOWN_FUNCTION(unk_80167224); extern UNKNOWN_FUNCTION(unk_80167904); // PAL: 0x8016799c extern UNKNOWN_FUNCTION(unk_8016799c); -// PAL: 0x80169bcc -extern UNKNOWN_FUNCTION(unk_80169bcc); -// PAL: 0x80169e74 -extern UNKNOWN_FUNCTION(unk_80169e74); -// PAL: 0x80169f68 -extern UNKNOWN_FUNCTION(unk_80169f68); -// PAL: 0x8016a05c -extern UNKNOWN_FUNCTION(unk_8016a05c); -// PAL: 0x8016a1b0 -extern UNKNOWN_FUNCTION(unk_8016a1b0); -// PAL: 0x8016a2f8 -extern UNKNOWN_FUNCTION(unk_8016a2f8); -// PAL: 0x8016a3fc -extern UNKNOWN_FUNCTION(unk_8016a3fc); -// PAL: 0x8016a500 -extern UNKNOWN_FUNCTION(unk_8016a500); -// PAL: 0x8016a658 -extern UNKNOWN_FUNCTION(unk_8016a658); -// PAL: 0x8016a78c -extern UNKNOWN_FUNCTION(unk_8016a78c); -// PAL: 0x8016a864 -extern UNKNOWN_FUNCTION(unk_8016a864); -// PAL: 0x8016a934 -extern UNKNOWN_FUNCTION(unk_8016a934); -// PAL: 0x8016aa38 -extern UNKNOWN_FUNCTION(unk_8016aa38); -// PAL: 0x8016ab3c -extern UNKNOWN_FUNCTION(unk_8016ab3c); -// PAL: 0x8016ac74 -extern UNKNOWN_FUNCTION(unk_8016ac74); -// PAL: 0x8016ad68 -extern UNKNOWN_FUNCTION(unk_8016ad68); -// PAL: 0x8016ae5c -extern UNKNOWN_FUNCTION(unk_8016ae5c); -// PAL: 0x8016af24 -extern UNKNOWN_FUNCTION(unk_8016af24); -// PAL: 0x8016afdc -extern UNKNOWN_FUNCTION(unk_8016afdc); -// PAL: 0x8016b0ac -extern UNKNOWN_FUNCTION(unk_8016b0ac); // PAL: 0x8016b16c -extern UNKNOWN_FUNCTION(unk_8016b16c); -// PAL: 0x8016b170 -extern UNKNOWN_FUNCTION(unk_8016b170); +extern UNKNOWN_FUNCTION(ISFS_Seek); // PAL: 0x8016b1fc -extern UNKNOWN_FUNCTION(unk_8016b1fc); +extern UNKNOWN_FUNCTION(ISFS_Read); // PAL: 0x8016b21c -extern UNKNOWN_FUNCTION(unk_8016b21c); +extern UNKNOWN_FUNCTION(ISFS_ReadAsync); // PAL: 0x8016b2c0 -extern UNKNOWN_FUNCTION(unk_8016b2c0); +extern UNKNOWN_FUNCTION(ISFS_Write); // PAL: 0x8016b2e0 -extern UNKNOWN_FUNCTION(unk_8016b2e0); +extern UNKNOWN_FUNCTION(ISFS_WriteAsync); // PAL: 0x8016b384 -extern UNKNOWN_FUNCTION(unk_8016b384); -// PAL: 0x8016b388 -extern UNKNOWN_FUNCTION(unk_8016b388); -// PAL: 0x8016b40c -extern UNKNOWN_FUNCTION(unk_8016b40c); +extern UNKNOWN_FUNCTION(ISFS_Close); // PAL: 0x801a0504 extern UNKNOWN_FUNCTION(OSRegisterVersion); -// PAL: 0x801a25d0 -extern UNKNOWN_FUNCTION(OSReport); // PAL: 0x801a8238 extern UNKNOWN_FUNCTION(OSRegisterResetFunction); @@ -141,6 +95,7 @@ UNKNOWN_FUNCTION(asyncRoutine); // PAL: 0x8019b314..0x8019b43c MARK_BINARY_BLOB(nandCreate, 0x8019b314, 0x8019b43c); asm UNKNOWN_FUNCTION(nandCreate) { + // clang-format off nofralloc; stwu r1, -0x70(r1); mflr r0; @@ -204,7 +159,7 @@ asm UNKNOWN_FUNCTION(nandCreate) { mr r9, r29; addi r3, r1, 0x18; addi r8, r8, -7072; - bl unk_8016ad68; + bl ISFS_CreateFileAsync; b lbl_8019b424; lbl_8019b40c: lwz r5, 0x10(r1); @@ -212,7 +167,7 @@ asm UNKNOWN_FUNCTION(nandCreate) { lwz r6, 0xc(r1); addi r3, r1, 0x18; lwz r7, 8(r1); - bl unk_8016ac74; + bl ISFS_CreateFile; lbl_8019b424: addi r11, r1, 0x70; bl _restgpr_27; @@ -220,6 +175,7 @@ asm UNKNOWN_FUNCTION(nandCreate) { mtlr r0; addi r1, r1, 0x70; blr; + // clang-format on } // Symbol: NANDCreate @@ -258,13 +214,14 @@ asm s32 NANDCreate(const char*, u8, u8) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: NANDPrivateCreate -// Function signature is unknown. // PAL: 0x8019b4b0..0x8019b524 MARK_BINARY_BLOB(NANDPrivateCreate, 0x8019b4b0, 0x8019b524); asm UNKNOWN_FUNCTION(NANDPrivateCreate) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -297,13 +254,14 @@ asm UNKNOWN_FUNCTION(NANDPrivateCreate) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: NANDPrivateCreateAsync -// Function signature is unknown. // PAL: 0x8019b524..0x8019b59c MARK_BINARY_BLOB(NANDPrivateCreateAsync, 0x8019b524, 0x8019b59c); asm UNKNOWN_FUNCTION(NANDPrivateCreateAsync) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -337,6 +295,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateCreateAsync) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: NANDDelete @@ -383,7 +342,7 @@ asm s32 NANDDelete(const char*) { b lbl_8019b634; lbl_8019b62c: addi r3, r1, 8; - bl unk_8016a78c; + bl ISFS_Delete; lbl_8019b634: bl nandConvertErrorCode; lbl_8019b638: @@ -395,10 +354,10 @@ asm s32 NANDDelete(const char*) { } // Symbol: NANDPrivateDelete -// Function signature is unknown. // PAL: 0x8019b64c..0x8019b6e4 MARK_BINARY_BLOB(NANDPrivateDelete, 0x8019b64c, 0x8019b6e4); asm UNKNOWN_FUNCTION(NANDPrivateDelete) { + // clang-format off nofralloc; stwu r1, -0x50(r1); mflr r0; @@ -432,7 +391,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateDelete) { stw r0, 0x44(r1); bl nandGenerateAbsPath; addi r3, r1, 8; - bl unk_8016a78c; + bl ISFS_Delete; bl nandConvertErrorCode; lbl_8019b6d0: lwz r0, 0x54(r1); @@ -440,13 +399,14 @@ asm UNKNOWN_FUNCTION(NANDPrivateDelete) { mtlr r0; addi r1, r1, 0x50; blr; + // clang-format on } // Symbol: NANDPrivateDeleteAsync -// Function signature is unknown. // PAL: 0x8019b6e4..0x8019b7a4 MARK_BINARY_BLOB(NANDPrivateDeleteAsync, 0x8019b6e4, 0x8019b7a4); asm UNKNOWN_FUNCTION(NANDPrivateDeleteAsync) { + // clang-format off nofralloc; stwu r1, -0x60(r1); mflr r0; @@ -488,7 +448,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateDeleteAsync) { mr r5, r31; addi r3, r1, 8; addi r4, r4, -7072; - bl unk_8016a864; + bl ISFS_DeleteAsync; bl nandConvertErrorCode; lbl_8019b788: lwz r0, 0x64(r1); @@ -498,6 +458,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateDeleteAsync) { mtlr r0; addi r1, r1, 0x60; blr; + // clang-format on } // Symbol: NANDRead @@ -520,7 +481,7 @@ asm s32 NANDRead(NANDFileInfo*, void*, u32) { lwz r3, 0(r29); mr r4, r30; mr r5, r31; - bl unk_8016b1fc; + bl ISFS_Read; bl nandConvertErrorCode; b lbl_8019b7f0; lbl_8019b7ec: @@ -563,7 +524,7 @@ asm s32 NANDReadAsync(NANDFileInfo*, void*, u32, NANDCallback) { lwz r3, 0(r27); mr r7, r31; addi r6, r6, -7072; - bl unk_8016b21c; + bl ISFS_ReadAsync; bl nandConvertErrorCode; lbl_8019b86c: addi r11, r1, 0x20; @@ -594,7 +555,7 @@ asm s32 NANDWrite(NANDFileInfo*, const void*, u32) { lwz r3, 0(r29); mr r4, r30; mr r5, r31; - bl unk_8016b2c0; + bl ISFS_Write; bl nandConvertErrorCode; b lbl_8019b8d0; lbl_8019b8cc: @@ -637,7 +598,7 @@ asm s32 NANDWriteAsync(NANDFileInfo*, const void*, u32, NANDCallback) { lwz r3, 0(r27); mr r7, r31; addi r6, r6, -7072; - bl unk_8016b2e0; + bl ISFS_WriteAsync; bl nandConvertErrorCode; lbl_8019b94c: addi r11, r1, 0x20; @@ -690,7 +651,7 @@ asm s32 NANDSeek(NANDFileInfo*, s32, s32) { li r5, 2; lbl_8019b9dc: mr r4, r30; - bl unk_8016b16c; + bl ISFS_Seek; bl nandConvertErrorCode; lbl_8019b9e8: lwz r0, 0x24(r1); @@ -749,7 +710,7 @@ asm s32 NANDSeekAsync(NANDFileInfo*, s32, s32, NANDCallback) { mr r4, r28; mr r7, r31; addi r6, r6, -7072; - bl unk_8016b170; + bl ISFS_SeekAsync; bl nandConvertErrorCode; lbl_8019ba9c: addi r11, r1, 0x20; @@ -765,6 +726,7 @@ asm s32 NANDSeekAsync(NANDFileInfo*, s32, s32, NANDCallback) { // PAL: 0x8019bab4..0x8019bbe0 MARK_BINARY_BLOB(nandCreateDir, 0x8019bab4, 0x8019bbe0); asm UNKNOWN_FUNCTION(nandCreateDir) { + // clang-format off nofralloc; stwu r1, -0x70(r1); mflr r0; @@ -829,7 +791,7 @@ asm UNKNOWN_FUNCTION(nandCreateDir) { mr r9, r29; addi r3, r1, 0x18; addi r8, r8, -7072; - bl unk_80169f68; + bl ISFS_CreateDirAsync; b lbl_8019bbc8; lbl_8019bbb0: lwz r5, 0x10(r1); @@ -837,7 +799,7 @@ asm UNKNOWN_FUNCTION(nandCreateDir) { lwz r6, 0xc(r1); addi r3, r1, 0x18; lwz r7, 8(r1); - bl unk_80169e74; + bl ISFS_CreateDir; lbl_8019bbc8: addi r11, r1, 0x70; bl _restgpr_27; @@ -845,6 +807,7 @@ asm UNKNOWN_FUNCTION(nandCreateDir) { mtlr r0; addi r1, r1, 0x70; blr; + // clang-format on } // Symbol: NANDCreateDir @@ -890,6 +853,7 @@ asm s32 NANDCreateDir(const char*, u8, u8) { // PAL: 0x8019bc54..0x8019bcc8 MARK_BINARY_BLOB(NANDPrivateCreateDir, 0x8019bc54, 0x8019bcc8); asm UNKNOWN_FUNCTION(NANDPrivateCreateDir) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -922,6 +886,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateCreateDir) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: NANDPrivateCreateDirAsync @@ -929,6 +894,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateCreateDir) { // PAL: 0x8019bcc8..0x8019bd40 MARK_BINARY_BLOB(NANDPrivateCreateDirAsync, 0x8019bcc8, 0x8019bd40); asm UNKNOWN_FUNCTION(NANDPrivateCreateDirAsync) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -962,6 +928,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateCreateDirAsync) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: nandMove @@ -969,6 +936,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateCreateDirAsync) { // PAL: 0x8019bd40..0x8019bee8 MARK_BINARY_BLOB(nandMove, 0x8019bd40, 0x8019bee8); asm UNKNOWN_FUNCTION(nandMove) { + // clang-format off nofralloc; stwu r1, -0xb0(r1); mflr r0; @@ -1067,12 +1035,12 @@ asm UNKNOWN_FUNCTION(nandMove) { addi r3, r1, 0x58; addi r4, r1, 0x18; addi r5, r5, -7072; - bl unk_8016aa38; + bl ISFS_RenameAsync; b lbl_8019bec8; lbl_8019bebc: addi r3, r1, 0x58; addi r4, r1, 0x18; - bl unk_8016a934; + bl ISFS_Rename; lbl_8019bec8: lwz r0, 0xb4(r1); lwz r31, 0xac(r1); @@ -1082,6 +1050,7 @@ asm UNKNOWN_FUNCTION(nandMove) { mtlr r0; addi r1, r1, 0xb0; blr; + // clang-format on } // Symbol: NANDMove @@ -1141,7 +1110,7 @@ asm s32 NANDGetLength(NANDFileInfo*, u32*) { lbl_8019bf88: lwz r3, 0(r30); addi r4, r1, 0x20; - bl unk_8016afdc; + bl ISFS_GetFileStats; cmpwi r3, 0; bne lbl_8019bfb4; cmpwi r31, 0; @@ -1167,6 +1136,7 @@ asm s32 NANDGetLength(NANDFileInfo*, u32*) { // PAL: 0x8019bfd4..0x8019c048 MARK_BINARY_BLOB(nandGetFileStatusAsyncCallback, 0x8019bfd4, 0x8019c048); asm UNKNOWN_FUNCTION(nandGetFileStatusAsyncCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -1199,6 +1169,7 @@ asm UNKNOWN_FUNCTION(nandGetFileStatusAsyncCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: NANDGetLengthAsync @@ -1234,7 +1205,7 @@ asm s32 NANDGetLengthAsync(NANDFileInfo*, u32*, NANDCallback, addi r5, r5, -16428; stw r3, 0x78(r31); lwz r3, 0(r28); - bl unk_8016b0ac; + bl ISFS_GetFileStatsAsync; bl nandConvertErrorCode; lbl_8019c0b8: lwz r0, 0x24(r1); @@ -1252,6 +1223,7 @@ asm s32 NANDGetLengthAsync(NANDFileInfo*, u32*, NANDCallback, // PAL: 0x8019c0d8..0x8019c12c MARK_BINARY_BLOB(nandComposePerm, 0x8019c0d8, 0x8019c12c); asm UNKNOWN_FUNCTION(nandComposePerm) { + // clang-format off nofralloc; clrlwi. r0, r4, 0x1f; li r7, 0; @@ -1280,6 +1252,7 @@ asm UNKNOWN_FUNCTION(nandComposePerm) { lbl_8019c124: stb r7, 0(r3); blr; + // clang-format on } // Symbol: nandSplitPerm @@ -1287,6 +1260,7 @@ asm UNKNOWN_FUNCTION(nandComposePerm) { // PAL: 0x8019c12c..0x8019c1b8 MARK_BINARY_BLOB(nandSplitPerm, 0x8019c12c, 0x8019c1b8); asm UNKNOWN_FUNCTION(nandSplitPerm) { + // clang-format off nofralloc; li r7, 0; rlwinm. r0, r3, 0, 0x1b, 0x1b; @@ -1328,6 +1302,7 @@ asm UNKNOWN_FUNCTION(nandSplitPerm) { ori r0, r0, 2; stw r0, 0(r6); blr; + // clang-format on } // Symbol: nandGetStatus @@ -1335,6 +1310,7 @@ asm UNKNOWN_FUNCTION(nandSplitPerm) { // PAL: 0x8019c1b8..0x8019c30c MARK_BINARY_BLOB(nandGetStatus, 0x8019c1b8, 0x8019c30c); asm UNKNOWN_FUNCTION(nandGetStatus) { + // clang-format off nofralloc; stwu r1, -0x70(r1); mflr r0; @@ -1388,7 +1364,7 @@ asm UNKNOWN_FUNCTION(nandGetStatus) { addi r8, r29, 0x28; addi r9, r29, 0x2c; addi r10, r10, -15604; - bl unk_8016a658; + bl ISFS_GetAttrAsync; b lbl_8019c2ec; lbl_8019c28c: li r0, 0; @@ -1403,7 +1379,7 @@ asm UNKNOWN_FUNCTION(nandGetStatus) { addi r9, r1, 0x10; stw r0, 0x14(r1); stw r0, 0x10(r1); - bl unk_8016a500; + bl ISFS_GetAttr; cmpwi r3, 0; mr r31, r3; bne lbl_8019c2e8; @@ -1425,6 +1401,7 @@ asm UNKNOWN_FUNCTION(nandGetStatus) { mtlr r0; addi r1, r1, 0x70; blr; + // clang-format on } // Symbol: nandGetStatusCallback @@ -1432,6 +1409,7 @@ asm UNKNOWN_FUNCTION(nandGetStatus) { // PAL: 0x8019c30c..0x8019c380 MARK_BINARY_BLOB(nandGetStatusCallback, 0x8019c30c, 0x8019c380); asm UNKNOWN_FUNCTION(nandGetStatusCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -1463,6 +1441,7 @@ asm UNKNOWN_FUNCTION(nandGetStatusCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: NANDGetStatus @@ -1504,6 +1483,7 @@ asm s32 NANDGetStatus(const char*, NANDStatus*) { // PAL: 0x8019c3e4..0x8019c448 MARK_BINARY_BLOB(NANDPrivateGetStatus, 0x8019c3e4, 0x8019c448); asm UNKNOWN_FUNCTION(NANDPrivateGetStatus) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -1532,6 +1512,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateGetStatus) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: NANDPrivateGetStatusAsync @@ -1539,6 +1520,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateGetStatus) { // PAL: 0x8019c448..0x8019c4cc MARK_BINARY_BLOB(NANDPrivateGetStatusAsync, 0x8019c448, 0x8019c4cc); asm UNKNOWN_FUNCTION(NANDPrivateGetStatusAsync) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -1575,6 +1557,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateGetStatusAsync) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: nandSetStatus @@ -1582,6 +1565,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateGetStatusAsync) { // PAL: 0x8019c4cc..0x8019c614 MARK_BINARY_BLOB(nandSetStatus, 0x8019c4cc, 0x8019c614); asm UNKNOWN_FUNCTION(nandSetStatus) { + // clang-format off nofralloc; stwu r1, -0x70(r1); mflr r0; @@ -1649,7 +1633,7 @@ asm UNKNOWN_FUNCTION(nandSetStatus) { lwz r7, 0x18(r1); lwz r8, 0x14(r1); lwz r9, 0x10(r1); - bl unk_8016a3fc; + bl ISFS_SetAttrAsync; b lbl_8019c5f4; lbl_8019c5d4: lwz r4, 0(r28); @@ -1659,7 +1643,7 @@ asm UNKNOWN_FUNCTION(nandSetStatus) { lwz r7, 0x18(r1); lwz r8, 0x14(r1); lwz r9, 0x10(r1); - bl unk_8016a2f8; + bl ISFS_SetAttr; lbl_8019c5f4: lwz r0, 0x74(r1); lwz r31, 0x6c(r1); @@ -1669,6 +1653,7 @@ asm UNKNOWN_FUNCTION(nandSetStatus) { mtlr r0; addi r1, r1, 0x70; blr; + // clang-format on } // Symbol: NANDSetStatus @@ -1710,6 +1695,7 @@ asm s32 NANDSetStatus(const char*, const NANDStatus*) { // PAL: 0x8019c678..0x8019c6dc MARK_BINARY_BLOB(NANDPrivateSetStatus, 0x8019c678, 0x8019c6dc); asm UNKNOWN_FUNCTION(NANDPrivateSetStatus) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -1738,6 +1724,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateSetStatus) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: NANDSetUserData @@ -1755,6 +1742,7 @@ void* NANDGetUserData(const NANDCommandBlock* block) { return block->userData; } // PAL: 0x8019c6ec..0x8019c800 MARK_BINARY_BLOB(nandOpen, 0x8019c6ec, 0x8019c800); asm UNKNOWN_FUNCTION(nandOpen) { + // clang-format off nofralloc; stwu r1, -0x60(r1); mflr r0; @@ -1819,12 +1807,12 @@ asm UNKNOWN_FUNCTION(nandOpen) { mr r6, r28; addi r3, r1, 8; addi r5, r5, -13816; - bl unk_8016af24; + bl ISFS_OpenAsync; b lbl_8019c7e8; lbl_8019c7dc: mr r4, r31; addi r3, r1, 8; - bl unk_8016ae5c; + bl ISFS_Open; lbl_8019c7e8: addi r11, r1, 0x60; bl _restgpr_27; @@ -1832,6 +1820,7 @@ asm UNKNOWN_FUNCTION(nandOpen) { mtlr r0; addi r1, r1, 0x60; blr; + // clang-format on } // Symbol: NANDOpen @@ -1884,6 +1873,7 @@ asm s32 NANDOpen(const char*, NANDFileInfo*, u8) { // PAL: 0x8019c88c..0x8019c918 MARK_BINARY_BLOB(NANDPrivateOpen, 0x8019c88c, 0x8019c918); asm UNKNOWN_FUNCTION(NANDPrivateOpen) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -1923,6 +1913,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateOpen) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: NANDOpenAsync @@ -1970,6 +1961,7 @@ asm s32 NANDOpenAsync(const char*, NANDFileInfo*, u8, NANDCallback, // PAL: 0x8019c990..0x8019ca08 MARK_BINARY_BLOB(NANDPrivateOpenAsync, 0x8019c990, 0x8019ca08); asm UNKNOWN_FUNCTION(NANDPrivateOpenAsync) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -2003,6 +1995,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateOpenAsync) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: nandOpenCallback @@ -2010,6 +2003,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateOpenAsync) { // PAL: 0x8019ca08..0x8019ca80 MARK_BINARY_BLOB(nandOpenCallback, 0x8019ca08, 0x8019ca80); asm UNKNOWN_FUNCTION(nandOpenCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -2043,6 +2037,7 @@ asm UNKNOWN_FUNCTION(nandOpenCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: NANDClose @@ -2068,7 +2063,7 @@ asm s32 NANDClose(NANDFileInfo*) { b lbl_8019cad8; lbl_8019cabc: lwz r3, 0(r31); - bl unk_8016b384; + bl ISFS_Close; cmpwi r3, 0; bne lbl_8019cad4; li r0, 2; @@ -2084,7 +2079,6 @@ asm s32 NANDClose(NANDFileInfo*) { } // Symbol: NANDCloseAsync -// Function signature is unknown. // PAL: 0x8019caec..0x8019cb74 MARK_BINARY_BLOB(NANDCloseAsync, 0x8019caec, 0x8019cb74); asm s32 NANDCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*) { @@ -2116,7 +2110,7 @@ asm s32 NANDCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*) { stw r29, 8(r31); addi r4, r4, -9660; lwz r3, 0(r29); - bl unk_8016b388; + bl ISFS_CloseAsync; bl nandConvertErrorCode; lbl_8019cb58: lwz r0, 0x24(r1); @@ -2143,6 +2137,7 @@ asm s32 NANDSafeOpen(const char*, NANDFileInfo*, u8, void*, u32) { // PAL: 0x8019cb80..0x8019cf28 MARK_BINARY_BLOB(nandSafeOpen, 0x8019cb80, 0x8019cf28); asm UNKNOWN_FUNCTION(nandSafeOpen) { + // clang-format off nofralloc; stwu r1, -0x90(r1); mflr r0; @@ -2188,7 +2183,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { bne lbl_8019cc68; addi r3, r28, 8; li r4, 1; - bl unk_8016ae5c; + bl ISFS_Open; cmpwi r3, 0; blt lbl_8019cc60; li r0, 2; @@ -2225,7 +2220,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { li r7, 3; stw r0, 0x28(r1); stb r0, 0x2c(r1); - bl unk_80169e74; + bl ISFS_CreateDir; cmpwi r3, 0; beq lbl_8019ccc4; cmpwi r3, -105; @@ -2242,7 +2237,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { addi r7, r1, 0x14; addi r8, r1, 0x10; addi r9, r1, 0xc; - bl unk_8016a500; + bl ISFS_GetAttr; cmpwi r3, 0; beq lbl_8019ccfc; bl nandConvertErrorCode; @@ -2250,7 +2245,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { lbl_8019ccfc: addi r3, r28, 8; li r4, 1; - bl unk_8016ae5c; + bl ISFS_Open; cmpwi r3, 0; stw r3, 4(r28); bge lbl_8019cd1c; @@ -2278,7 +2273,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { li r5, 3; li r6, 0; li r7, 0; - bl unk_80169e74; + bl ISFS_CreateDir; cmpwi r3, 0; beq lbl_8019cd84; bl nandConvertErrorCode; @@ -2316,7 +2311,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { lwz r5, 0x14(r1); lwz r6, 0x10(r1); lwz r7, 0xc(r1); - bl unk_8016ac74; + bl ISFS_CreateFile; cmpwi r3, 0; beq lbl_8019ce0c; bl nandConvertErrorCode; @@ -2328,7 +2323,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { bne lbl_8019ce30; addi r3, r28, 0x48; li r4, 2; - bl unk_8016ae5c; + bl ISFS_Open; stw r3, 0(r28); b lbl_8019ce48; lbl_8019ce30: @@ -2336,7 +2331,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { bne lbl_8019ce48; addi r3, r28, 0x48; li r4, 3; - bl unk_8016ae5c; + bl ISFS_Open; stw r3, 0(r28); lbl_8019ce48: lwz r3, 0(r28); @@ -2353,7 +2348,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { mr r3, r26; mr r4, r29; mr r5, r30; - bl unk_8016b1fc; + bl ISFS_Read; cmpwi r3, 0; mr r5, r3; bne lbl_8019ce90; @@ -2365,7 +2360,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { lbl_8019ce98: mr r3, r27; mr r4, r29; - bl unk_8016b2c0; + bl ISFS_Write; cmpwi r3, 0; bge lbl_8019ce6c; mr r5, r3; @@ -2379,7 +2374,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { lwz r3, 0(r28); li r4, 0; li r5, 0; - bl unk_8016b16c; + bl ISFS_Seek; cmpwi r3, 0; beq lbl_8019cee4; bl nandConvertErrorCode; @@ -2406,6 +2401,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpen) { mtlr r0; addi r1, r1, 0x90; blr; + // clang-format on } // Symbol: NANDSafeClose @@ -2465,7 +2461,7 @@ asm s32 NANDSafeClose(NANDFileInfo*) { cmplwi r3, 1; bne lbl_8019d01c; lwz r3, 0(r29); - bl unk_8016b384; + bl ISFS_Close; cmpwi r3, 0; bne lbl_8019d014; li r0, 7; @@ -2487,7 +2483,7 @@ asm s32 NANDSafeClose(NANDFileInfo*) { cmplwi r0, 1; bgt lbl_8019d0d4; lwz r3, 0(r29); - bl unk_8016b384; + bl ISFS_Close; cmpwi r3, 0; beq lbl_8019d044; bl nandConvertErrorCode; @@ -2496,7 +2492,7 @@ asm s32 NANDSafeClose(NANDFileInfo*) { li r31, 6; lwz r3, 4(r29); stb r31, 0x89(r29); - bl unk_8016b384; + bl ISFS_Close; cmpwi r3, 0; beq lbl_8019d064; bl nandConvertErrorCode; @@ -2506,7 +2502,7 @@ asm s32 NANDSafeClose(NANDFileInfo*) { addi r3, r29, 0x48; stb r0, 0x89(r29); addi r4, r29, 8; - bl unk_8016a934; + bl ISFS_Rename; cmpwi r3, 0; beq lbl_8019d088; bl nandConvertErrorCode; @@ -2520,7 +2516,7 @@ asm s32 NANDSafeClose(NANDFileInfo*) { addi r4, r29, 0x48; bl nandGetParentDirectory; addi r3, r1, 8; - bl unk_8016a78c; + bl ISFS_Delete; cmpwi r3, 0; bne lbl_8019d0cc; li r4, 9; @@ -2554,6 +2550,7 @@ asm s32 NANDSafeClose(NANDFileInfo*) { // PAL: 0x8019d104..0x8019d130 MARK_BINARY_BLOB(NANDPrivateSafeOpenAsync, 0x8019d104, 0x8019d130); asm UNKNOWN_FUNCTION(NANDPrivateSafeOpenAsync) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -2566,6 +2563,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateSafeOpenAsync) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandSafeOpenAsync @@ -2573,6 +2571,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateSafeOpenAsync) { // PAL: 0x8019d130..0x8019d298 MARK_BINARY_BLOB(nandSafeOpenAsync, 0x8019d130, 0x8019d298); asm UNKNOWN_FUNCTION(nandSafeOpenAsync) { + // clang-format off nofralloc; stwu r1, -0x30(r1); mflr r0; @@ -2626,7 +2625,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenAsync) { stw r28, 4(r29); addi r5, r5, -10616; li r4, 1; - bl unk_8016af24; + bl ISFS_OpenAsync; cmpwi r3, 0; bne lbl_8019d208; li r3, 0; @@ -2654,7 +2653,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenAsync) { li r7, 3; stw r26, 0x80(r29); stw r27, 0x84(r29); - bl unk_80169f68; + bl ISFS_CreateDirAsync; cmpwi r3, 0; bne lbl_8019d26c; b lbl_8019d274; @@ -2673,6 +2672,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenAsync) { mtlr r0; addi r1, r1, 0x30; blr; + // clang-format on } // Symbol: nandSafeOpenCallback @@ -2680,6 +2680,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenAsync) { // PAL: 0x8019d298..0x8019d688 MARK_BINARY_BLOB(nandSafeOpenCallback, 0x8019d298, 0x8019d688); asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { + // clang-format off nofralloc; stwu r1, -0x70(r1); mflr r0; @@ -2737,7 +2738,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { addi r9, r4, 0x2c; addi r10, r10, -11624; addi r4, r4, 0x18; - bl unk_8016a658; + bl ISFS_GetAttrAsync; mr r6, r3; b lbl_8019d638; lbl_8019d370: @@ -2748,7 +2749,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { addi r3, r31, 8; li r4, 1; addi r5, r5, -11624; - bl unk_8016af24; + bl ISFS_OpenAsync; mr r6, r3; b lbl_8019d638; lbl_8019d398: @@ -2775,7 +2776,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { li r5, 3; li r6, 0; li r7, 0; - bl unk_80169f68; + bl ISFS_CreateDirAsync; mr r6, r3; b lbl_8019d638; lbl_8019d400: @@ -2816,7 +2817,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { addi r3, r31, 0x48; lwz r7, 0x2c(r30); addi r8, r8, -11624; - bl unk_8016ad68; + bl ISFS_CreateFileAsync; mr r6, r3; b lbl_8019d638; lbl_8019d498: @@ -2832,7 +2833,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { addi r3, r31, 0x48; li r4, 2; addi r5, r5, -11624; - bl unk_8016af24; + bl ISFS_OpenAsync; mr r6, r3; b lbl_8019d638; lbl_8019d4d4: @@ -2843,7 +2844,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { addi r3, r31, 0x48; li r4, 3; addi r5, r5, -11624; - bl unk_8016af24; + bl ISFS_OpenAsync; mr r6, r3; b lbl_8019d638; lbl_8019d4fc: @@ -2863,7 +2864,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { lwz r4, 0x80(r4); lwz r3, 4(r31); lwz r5, 0x84(r30); - bl unk_8016b21c; + bl ISFS_ReadAsync; mr r6, r3; b lbl_8019d638; lbl_8019d544: @@ -2875,7 +2876,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { mr r7, r30; lwz r5, 0x84(r30); addi r6, r6, -11624; - bl unk_8016b21c; + bl ISFS_ReadAsync; mr r6, r3; b lbl_8019d638; lbl_8019d570: @@ -2891,7 +2892,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { mr r7, r30; lwz r3, 0(r31); addi r6, r6, -11624; - bl unk_8016b2e0; + bl ISFS_WriteAsync; mr r6, r3; b lbl_8019d638; lbl_8019d5ac: @@ -2902,7 +2903,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { li r4, 0; addi r6, r6, -11624; li r5, 0; - bl unk_8016b170; + bl ISFS_SeekAsync; mr r6, r3; b lbl_8019d638; lbl_8019d5d4: @@ -2957,6 +2958,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { mtlr r0; addi r1, r1, 0x70; blr; + // clang-format on } // Symbol: nandReadOpenCallback @@ -2964,6 +2966,7 @@ asm UNKNOWN_FUNCTION(nandSafeOpenCallback) { // PAL: 0x8019d688..0x8019d720 MARK_BINARY_BLOB(nandReadOpenCallback, 0x8019d688, 0x8019d720); asm UNKNOWN_FUNCTION(nandReadOpenCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -3007,6 +3010,7 @@ asm UNKNOWN_FUNCTION(nandReadOpenCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: NANDSafeCloseAsync @@ -3058,7 +3062,7 @@ asm s32 NANDSafeCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*) { stw r29, 4(r30); addi r4, r4, -9752; lwz r3, 0(r28); - bl unk_8016b388; + bl ISFS_CloseAsync; b lbl_8019d800; lbl_8019d7c4: addi r0, r3, 0xfe; @@ -3073,7 +3077,7 @@ asm s32 NANDSafeCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*) { stw r29, 4(r30); stw r0, 0x7c(r30); lwz r3, 0(r28); - bl unk_8016b388; + bl ISFS_CloseAsync; b lbl_8019d800; lbl_8019d7fc: li r3, -101; @@ -3095,6 +3099,7 @@ asm s32 NANDSafeCloseAsync(NANDFileInfo*, NANDCallback, NANDCommandBlock*) { // PAL: 0x8019d824..0x8019d9e8 MARK_BINARY_BLOB(nandSafeCloseCallback, 0x8019d824, 0x8019d9e8); asm UNKNOWN_FUNCTION(nandSafeCloseCallback) { + // clang-format off nofralloc; stwu r1, -0x50(r1); mflr r0; @@ -3134,7 +3139,7 @@ asm UNKNOWN_FUNCTION(nandSafeCloseCallback) { mr r5, r31; addi r4, r4, -10204; lwz r3, 4(r7); - bl unk_8016b388; + bl ISFS_CloseAsync; mr r6, r3; b lbl_8019d99c; lbl_8019d8bc: @@ -3147,7 +3152,7 @@ asm UNKNOWN_FUNCTION(nandSafeCloseCallback) { addi r3, r7, 0x48; addi r4, r7, 8; addi r5, r5, -10204; - bl unk_8016aa38; + bl ISFS_RenameAsync; mr r6, r3; b lbl_8019d99c; lbl_8019d8ec: @@ -3177,7 +3182,7 @@ asm UNKNOWN_FUNCTION(nandSafeCloseCallback) { mr r5, r31; addi r3, r1, 8; addi r4, r4, -10204; - bl unk_8016a864; + bl ISFS_DeleteAsync; mr r6, r3; b lbl_8019d99c; lbl_8019d960: @@ -3219,6 +3224,7 @@ asm UNKNOWN_FUNCTION(nandSafeCloseCallback) { mtlr r0; addi r1, r1, 0x50; blr; + // clang-format on } // Symbol: nandReadCloseCallback @@ -3226,6 +3232,7 @@ asm UNKNOWN_FUNCTION(nandSafeCloseCallback) { // PAL: 0x8019d9e8..0x8019da44 MARK_BINARY_BLOB(nandReadCloseCallback, 0x8019d9e8, 0x8019da44); asm UNKNOWN_FUNCTION(nandReadCloseCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -3251,6 +3258,7 @@ asm UNKNOWN_FUNCTION(nandReadCloseCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandCloseCallback @@ -3258,6 +3266,7 @@ asm UNKNOWN_FUNCTION(nandReadCloseCallback) { // PAL: 0x8019da44..0x8019daa0 MARK_BINARY_BLOB(nandCloseCallback, 0x8019da44, 0x8019daa0); asm UNKNOWN_FUNCTION(nandCloseCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -3283,6 +3292,7 @@ asm UNKNOWN_FUNCTION(nandCloseCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandRemoveTailToken @@ -3290,6 +3300,7 @@ asm UNKNOWN_FUNCTION(nandCloseCallback) { // PAL: 0x8019daa0..0x8019db74 MARK_BINARY_BLOB(nandRemoveTailToken, 0x8019daa0, 0x8019db74); asm UNKNOWN_FUNCTION(nandRemoveTailToken) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -3349,6 +3360,7 @@ asm UNKNOWN_FUNCTION(nandRemoveTailToken) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: nandGetHeadToken @@ -3356,6 +3368,7 @@ asm UNKNOWN_FUNCTION(nandRemoveTailToken) { // PAL: 0x8019db74..0x8019dc48 MARK_BINARY_BLOB(nandGetHeadToken, 0x8019db74, 0x8019dc48); asm UNKNOWN_FUNCTION(nandGetHeadToken) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -3416,6 +3429,7 @@ asm UNKNOWN_FUNCTION(nandGetHeadToken) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: nandGetRelativeName @@ -3423,6 +3437,7 @@ asm UNKNOWN_FUNCTION(nandGetHeadToken) { // PAL: 0x8019dc48..0x8019dce0 MARK_BINARY_BLOB(nandGetRelativeName, 0x8019dc48, 0x8019dce0); asm UNKNOWN_FUNCTION(nandGetRelativeName) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -3466,6 +3481,7 @@ asm UNKNOWN_FUNCTION(nandGetRelativeName) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandConvertPath @@ -3473,6 +3489,7 @@ asm UNKNOWN_FUNCTION(nandGetRelativeName) { // PAL: 0x8019dce0..0x8019de1c MARK_BINARY_BLOB(nandConvertPath, 0x8019dce0, 0x8019de1c); asm UNKNOWN_FUNCTION(nandConvertPath) { + // clang-format off nofralloc; lbl_8019dce0: stwu r1, -0x220(r1); @@ -3561,6 +3578,7 @@ asm UNKNOWN_FUNCTION(nandConvertPath) { mtlr r0; addi r1, r1, 0x220; blr; + // clang-format on } // Symbol: nandIsPrivatePath @@ -3568,6 +3586,7 @@ asm UNKNOWN_FUNCTION(nandConvertPath) { // PAL: 0x8019de1c..0x8019de50 MARK_BINARY_BLOB(nandIsPrivatePath, 0x8019de1c, 0x8019de50); asm UNKNOWN_FUNCTION(nandIsPrivatePath) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -3582,6 +3601,7 @@ asm UNKNOWN_FUNCTION(nandIsPrivatePath) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandIsUnderPrivatePath @@ -3589,6 +3609,7 @@ asm UNKNOWN_FUNCTION(nandIsPrivatePath) { // PAL: 0x8019de50..0x8019dea8 MARK_BINARY_BLOB(nandIsUnderPrivatePath, 0x8019de50, 0x8019dea8); asm UNKNOWN_FUNCTION(nandIsUnderPrivatePath) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -3614,6 +3635,7 @@ asm UNKNOWN_FUNCTION(nandIsUnderPrivatePath) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandIsInitialized @@ -3621,12 +3643,14 @@ asm UNKNOWN_FUNCTION(nandIsUnderPrivatePath) { // PAL: 0x8019dea8..0x8019debc MARK_BINARY_BLOB(nandIsInitialized, 0x8019dea8, 0x8019debc); asm UNKNOWN_FUNCTION(nandIsInitialized) { + // clang-format off nofralloc; lwz r3, -0x63b8(r13); addi r0, r3, -2; cntlzw r0, r0; srwi r3, r0, 5; blr; + // clang-format on } // Symbol: nandReportErrorCode @@ -3634,8 +3658,10 @@ asm UNKNOWN_FUNCTION(nandIsInitialized) { // PAL: 0x8019debc..0x8019dec0 MARK_BINARY_BLOB(nandReportErrorCode, 0x8019debc, 0x8019dec0); asm UNKNOWN_FUNCTION(nandReportErrorCode) { + // clang-format off nofralloc; blr; + // clang-format on } // Symbol: nandConvertErrorCode @@ -3643,6 +3669,7 @@ asm UNKNOWN_FUNCTION(nandReportErrorCode) { // PAL: 0x8019dec0..0x8019e020 MARK_BINARY_BLOB(nandConvertErrorCode, 0x8019dec0, 0x8019e020); asm UNKNOWN_FUNCTION(nandConvertErrorCode) { + // clang-format off nofralloc; clrlwi r11, r1, 0x1a; mr r12, r1; @@ -3739,6 +3766,7 @@ asm UNKNOWN_FUNCTION(nandConvertErrorCode) { mtlr r0; mr r1, r10; blr; + // clang-format on } // Symbol: nandGenerateAbsPath @@ -3746,6 +3774,7 @@ asm UNKNOWN_FUNCTION(nandConvertErrorCode) { // PAL: 0x8019e020..0x8019e0e8 MARK_BINARY_BLOB(nandGenerateAbsPath, 0x8019e020, 0x8019e0e8); asm UNKNOWN_FUNCTION(nandGenerateAbsPath) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -3802,6 +3831,7 @@ asm UNKNOWN_FUNCTION(nandGenerateAbsPath) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandGetParentDirectory @@ -3809,6 +3839,7 @@ asm UNKNOWN_FUNCTION(nandGenerateAbsPath) { // PAL: 0x8019e0e8..0x8019e18c MARK_BINARY_BLOB(nandGetParentDirectory, 0x8019e0e8, 0x8019e18c); asm UNKNOWN_FUNCTION(nandGetParentDirectory) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -3855,6 +3886,7 @@ asm UNKNOWN_FUNCTION(nandGetParentDirectory) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: NANDInit @@ -3886,7 +3918,7 @@ asm s32 NANDInit(void) { li r0, 1; stw r0, -0x63b8(r13); bl OSRestoreInterrupts; - bl unk_80169bcc; + bl ISFS_OpenLib; cmpwi r3, 0; mr r30, r3; bne lbl_8019e288; @@ -3952,6 +3984,7 @@ asm s32 NANDInit(void) { // PAL: 0x8019e2b8..0x8019e390 MARK_BINARY_BLOB(nandOnShutdown, 0x8019e2b8, 0x8019e390); asm UNKNOWN_FUNCTION(nandOnShutdown) { + // clang-format off nofralloc; stwu r1, -0x30(r1); mflr r0; @@ -3970,7 +4003,7 @@ asm UNKNOWN_FUNCTION(nandOnShutdown) { mr r28, r3; addi r4, r1, 8; addi r3, r5, -7292; - bl unk_8016b40c; + bl ISFS_ShutdownAsync; lis r3, 0x1062; lis r30, 0x8000; addi r29, r3, 0x4dd3; @@ -4012,6 +4045,7 @@ asm UNKNOWN_FUNCTION(nandOnShutdown) { li r0, 1; stw r0, 0(r4); blr; + // clang-format on } // Symbol: NANDGetCurrentDir @@ -4086,6 +4120,7 @@ asm s32 NANDGetHomeDir(char[64]) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandCallback @@ -4093,6 +4128,7 @@ asm s32 NANDGetHomeDir(char[64]) { // PAL: 0x8019e460..0x8019e49c MARK_BINARY_BLOB(nandCallback, 0x8019e460, 0x8019e49c); asm UNKNOWN_FUNCTION(nandCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -4109,6 +4145,7 @@ asm UNKNOWN_FUNCTION(nandCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandGetType @@ -4116,6 +4153,7 @@ asm UNKNOWN_FUNCTION(nandCallback) { // PAL: 0x8019e49c..0x8019e770 MARK_BINARY_BLOB(nandGetType, 0x8019e49c, 0x8019e770); asm UNKNOWN_FUNCTION(nandGetType) { + // clang-format off nofralloc; stwu r1, -0x70(r1); mflr r0; @@ -4206,7 +4244,7 @@ asm UNKNOWN_FUNCTION(nandGetType) { addi r5, r28, 0x30; addi r6, r6, -6148; li r4, 0; - bl unk_8016a1b0; + bl ISFS_ReadDirAsync; b lbl_8019e758; lbl_8019e5e4: li r0, 0; @@ -4296,7 +4334,7 @@ asm UNKNOWN_FUNCTION(nandGetType) { stw r0, 8(r1); addi r5, r1, 8; li r4, 0; - bl unk_8016a05c; + bl ISFS_ReadDir; cmpwi r3, 0; beq lbl_8019e734; cmpwi r3, -102; @@ -4319,6 +4357,7 @@ asm UNKNOWN_FUNCTION(nandGetType) { mtlr r0; addi r1, r1, 0x70; blr; + // clang-format on } // Symbol: NANDGetType @@ -4352,6 +4391,7 @@ asm s32 NANDGetType(const char*, u8*) { // PAL: 0x8019e7b4..0x8019e7fc MARK_BINARY_BLOB(NANDPrivateGetTypeAsync, 0x8019e7b4, 0x8019e7fc); asm UNKNOWN_FUNCTION(NANDPrivateGetTypeAsync) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -4373,6 +4413,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateGetTypeAsync) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandGetTypeCallback @@ -4380,6 +4421,7 @@ asm UNKNOWN_FUNCTION(NANDPrivateGetTypeAsync) { // PAL: 0x8019e7fc..0x8019e874 MARK_BINARY_BLOB(nandGetTypeCallback, 0x8019e7fc, 0x8019e874); asm UNKNOWN_FUNCTION(nandGetTypeCallback) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -4414,6 +4456,7 @@ asm UNKNOWN_FUNCTION(nandGetTypeCallback) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: nandGetHomeDir @@ -4421,10 +4464,12 @@ asm UNKNOWN_FUNCTION(nandGetTypeCallback) { // PAL: 0x8019e874..0x8019e880 MARK_BINARY_BLOB(nandGetHomeDir, 0x8019e874, 0x8019e880); asm UNKNOWN_FUNCTION(nandGetHomeDir) { + // clang-format off nofralloc; lis r3, 0x8034; addi r3, r3, 0x6d20; blr; + // clang-format on } // Symbol: NANDInitBanner @@ -4498,6 +4543,7 @@ asm void NANDInitBanner(NANDBanner*, u32, const u16*, const u16*) { // PAL: 0x8019e95c..0x8019ea14 MARK_BINARY_BLOB(NANDSecretGetUsage, 0x8019e95c, 0x8019ea14); asm UNKNOWN_FUNCTION(NANDSecretGetUsage) { + // clang-format off nofralloc; stwu r1, -0x60(r1); mflr r0; @@ -4537,7 +4583,7 @@ asm UNKNOWN_FUNCTION(NANDSecretGetUsage) { mr r4, r30; mr r5, r31; addi r3, r1, 8; - bl unk_8016ab3c; + bl ISFS_GetUsage; bl nandConvertErrorCode; lbl_8019e9f8: lwz r0, 0x64(r1); @@ -4547,6 +4593,7 @@ asm UNKNOWN_FUNCTION(NANDSecretGetUsage) { mtlr r0; addi r1, r1, 0x60; blr; + // clang-format on } // Symbol: nandCalcUsage @@ -4554,6 +4601,7 @@ asm UNKNOWN_FUNCTION(NANDSecretGetUsage) { // PAL: 0x8019ea14..0x8019ead0 MARK_BINARY_BLOB(nandCalcUsage, 0x8019ea14, 0x8019ead0); asm UNKNOWN_FUNCTION(nandCalcUsage) { + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -4576,7 +4624,7 @@ asm UNKNOWN_FUNCTION(nandCalcUsage) { addi r5, r1, 8; stw r31, 8(r1); lwz r3, 0(r30); - bl unk_8016ab3c; + bl ISFS_GetUsage; cmpwi r3, 0; bne lbl_8019ea94; lwz r4, 0(r28); @@ -4607,6 +4655,7 @@ asm UNKNOWN_FUNCTION(nandCalcUsage) { mtlr r0; addi r1, r1, 0x20; blr; + // clang-format on } // Symbol: NANDCheck @@ -4637,7 +4686,7 @@ asm s32 NANDCheck(u32, u32, u32*) { bl nandGetHomeDir; addi r4, r1, 0x14; addi r5, r1, 0x10; - bl unk_8016ab3c; + bl ISFS_GetUsage; cmpwi r3, 0; beq lbl_8019eb3c; bl nandConvertErrorCode; @@ -4695,6 +4744,7 @@ asm s32 NANDCheck(u32, u32, u32*) { // PAL: 0x8019ebd8..0x8019ec2c MARK_BINARY_BLOB(reserveFileDescriptor, 0x8019ebd8, 0x8019ec2c); asm UNKNOWN_FUNCTION(reserveFileDescriptor) { + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -4719,6 +4769,7 @@ asm UNKNOWN_FUNCTION(reserveFileDescriptor) { mtlr r0; addi r1, r1, 0x10; blr; + // clang-format on } // Symbol: NANDLoggingAddMessageAsync @@ -4726,6 +4777,7 @@ asm UNKNOWN_FUNCTION(reserveFileDescriptor) { // PAL: 0x8019ec2c..0x8019ed24 MARK_BINARY_BLOB(NANDLoggingAddMessageAsync, 0x8019ec2c, 0x8019ed24); asm UNKNOWN_FUNCTION(NANDLoggingAddMessageAsync) { + // clang-format off nofralloc; stwu r1, -0x80(r1); mflr r0; @@ -4779,7 +4831,7 @@ asm UNKNOWN_FUNCTION(NANDLoggingAddMessageAsync) { stw r0, -0x63ac(r13); addi r5, r5, -4828; li r6, 0; - bl unk_8016af24; + bl ISFS_OpenAsync; cmpwi r3, 0; bne lbl_8019ed08; li r3, 1; @@ -4793,6 +4845,7 @@ asm UNKNOWN_FUNCTION(NANDLoggingAddMessageAsync) { mtlr r0; addi r1, r1, 0x80; blr; + // clang-format on } // Symbol: asyncRoutine @@ -4800,6 +4853,7 @@ asm UNKNOWN_FUNCTION(NANDLoggingAddMessageAsync) { // PAL: 0x8019ed24..0x8019f1a8 MARK_BINARY_BLOB(asyncRoutine, 0x8019ed24, 0x8019f1a8); asm UNKNOWN_FUNCTION(asyncRoutine) { + // clang-format off nofralloc; stwu r1, -0x90(r1); mflr r0; @@ -4822,7 +4876,7 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { li r4, 0; li r5, 0; li r7, 0; - bl unk_8016b170; + bl ISFS_SeekAsync; cmpwi r3, 0; beq lbl_8019f190; lwz r12, -0x63b0(r13); @@ -4851,7 +4905,7 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { li r5, 0x100; addi r6, r6, -4828; li r7, 0; - bl unk_8016b21c; + bl ISFS_ReadAsync; cmpwi r3, 0; beq lbl_8019f190; lwz r12, -0x63b0(r13); @@ -4880,7 +4934,7 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { li r4, 0; li r5, 0; li r7, 0; - bl unk_8016b170; + bl ISFS_SeekAsync; cmpwi r3, 0; beq lbl_8019f190; lwz r12, -0x63b0(r13); @@ -4970,7 +5024,7 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { li r5, 0x100; stb r0, 0xff(r4); li r7, 0; - bl unk_8016b2e0; + bl ISFS_WriteAsync; cmpwi r3, 0; beq lbl_8019f190; lwz r12, -0x63b0(r13); @@ -5002,7 +5056,7 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { addi r6, r6, -4828; li r5, 0; li r7, 0; - bl unk_8016b170; + bl ISFS_SeekAsync; cmpwi r3, 0; beq lbl_8019f190; lwz r12, -0x63b0(r13); @@ -5034,7 +5088,7 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { li r5, 0x100; addi r6, r6, -4828; li r7, 0; - bl unk_8016b2e0; + bl ISFS_WriteAsync; cmpwi r3, 0; beq lbl_8019f190; lwz r12, -0x63b0(r13); @@ -5061,7 +5115,7 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { lwz r3, -0x71a0(r13); addi r4, r4, -4828; li r5, 0; - bl unk_8016b388; + bl ISFS_CloseAsync; cmpwi r3, 0; beq lbl_8019f190; lwz r12, -0x63b0(r13); @@ -5107,4 +5161,5 @@ asm UNKNOWN_FUNCTION(asyncRoutine) { mtlr r0; addi r1, r1, 0x90; blr; + // clang-format on } diff --git a/source/rvl/os/osError.h b/source/rvl/os/osError.h new file mode 100644 index 000000000..ab6306782 --- /dev/null +++ b/source/rvl/os/osError.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void OSReport(const char* msg, ...); + +#ifdef __cplusplus +} +#endif From 6c544bdfc546ea7be7a365ec63d0b1536bb6a758 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 28 Jul 2021 00:20:54 +0200 Subject: [PATCH 116/477] Split RVL/IPC (#69) * Splitting: RVL/IPC/ipcclt.c * Splitting: RVL/IPC/ipcMain.c * Decomp: RVL/IPC/ipcMain.c * remove unfinished code oops --- mkwutil/gen_asm.py | 2 +- mkwutil/sources.py | 5 + mkwutil/symbols.py | 2 +- pack/dol_objects.txt | 8 +- pack/dol_slices.csv | 2 + pack/symbols.txt | 4 + source/rvl/ipc/ipcMain.c | 62 ++ source/rvl/ipc/ipcMain.h | 28 + source/rvl/ipc/ipcclt.c | 2060 ++++++++++++++++++++++++++++++++++++++ source/rvl/ipc/ipcclt.h | 58 ++ 10 files changed, 2227 insertions(+), 4 deletions(-) create mode 100644 source/rvl/ipc/ipcMain.c create mode 100644 source/rvl/ipc/ipcMain.h create mode 100644 source/rvl/ipc/ipcclt.c create mode 100644 source/rvl/ipc/ipcclt.h diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index aa43f59c6..3ba40472c 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -222,7 +222,7 @@ def __disasm_instructions(self, insns, labels, sorted_labels): def disassemble_function(self, sym): """Generates the inline assembly function body as a stream of lines.""" assert isinstance(sym.size, int) - assert sym.size > 0 + assert sym.size > 0, f"Empty symbol: {sym}" # Grab the instructions. data_start = sym.addr - self.slice.start data_stop = data_start + sym.size diff --git a/mkwutil/sources.py b/mkwutil/sources.py index 4ef08b0e4..6bb44583c 100644 --- a/mkwutil/sources.py +++ b/mkwutil/sources.py @@ -35,6 +35,10 @@ class Source: SOURCES_RVL_FS = [ Source(src="source/rvl/fs/fs.c", cc='4199_60831', opts=RVL_OPTS), ] +SOURCES_RVL_IPC = [ + Source(src="source/rvl/ipc/ipcclt.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/ipc/ipcMain.c", cc='4199_60831', opts=RVL_OPTS), +] SOURCES_RVL_MEM = [ Source(src="source/rvl/mem/rvlMemHeap.cpp", cc='4199_60831', opts=RVL_OPTS), Source(src="source/rvl/mem/rvlMemExpHeap.c", cc='4199_60831', opts=RVL_OPTS), @@ -155,6 +159,7 @@ class Source: SOURCES_TRK, SOURCES_RVL_ARC, SOURCES_RVL_FS, + SOURCES_RVL_IPC, SOURCES_RVL_MEM, SOURCES_RVL_MTX, SOURCES_RVL_NAND, diff --git a/mkwutil/symbols.py b/mkwutil/symbols.py index 2e4fbd9ef..421f861e1 100644 --- a/mkwutil/symbols.py +++ b/mkwutil/symbols.py @@ -59,7 +59,7 @@ def items(self, start=0, stop=-1): # Binary search to determine first symbol with address >= start. sorted_by_addr = sorted_by_addr[bisect_left(sorted_by_addr, start) :] for addr in sorted_by_addr: - if stop > 0 and stop < addr: + if stop > 0 and stop <= addr: break yield self._by_addr[addr] diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index ded5805c9..026d13b59 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -160,10 +160,14 @@ out\dol\sdata_80385744_803857f0.o out\rvlArchive.o out\dol\text_80124e80_80169bcc.o out\fs.o -out\dol\text_8016b49c_801981ec.o +out\dol\text_8016b49c_80192f7c.o out\dol\data_8027e772_8029cc80.o out\dol\sdata_803857f6_80385a08.o -out\dol\sbss_80386448_80386838.o +out\dol\sbss_80386448_803867e8.o +out\ipcMain.o +out\ipcclt.o +out\dol\text_801949b8_801981ec.o +out\dol\sbss_803867fc_80386838.o out\rvlMemHeap.o out\rvlMemExpHeap.o out\rvlMemFrmHeap.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index a2cce6932..5e12df268 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -58,6 +58,8 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,1,source/gamespy/sake/sakeMain.c,,,,,,,0x80121eec,0x8012249c,,,,,,,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,,,,,, 1,,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, 1,,source/rvl/fs/fs.c,,,,,,,0x80169bcc,0x8016b49c,,,,,,,,,,,,,,,,,, +1,,source/rvl/ipc/ipcMain.c,,,,,,,0x80192f7c,0x80193048,,,,,,,,,,,,,0x803867e8,0x803867fc,,,, +1,,source/rvl/ipc/ipcclt.c,,,,,,,0x80193048,0x801949b8,,,,,,,,,,,,,,,,,, 1,,source/rvl/mem/rvlMemHeap.cpp,,,,,,,0x801981ec,0x80198798,,,,,,,,,0x80346cf0,0x80346d18,,,0x80386838,0x8038683C,,,, 1,,source/rvl/mem/rvlMemExpHeap.c,,,,,,,0x80198798,0x80199430,,,,,,,,,,,,,,,,,, 1,,source/rvl/mem/rvlMemFrmHeap.cpp,,,,,,,0x80199430,0x801998A4,,,,,,,,,,,,,,,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index a777121ea..b3bf813cb 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -812,6 +812,7 @@ 0x8016b384 ISFS_Close 0x8016b388 ISFS_CloseAsync 0x8016b40c ISFS_ShutdownAsync +0x8016b388 ISFS_CloseAsync 0x8016b850 GXInit 0x8016cec4 GXGetGPStatus 0x8016f438 GXSetDispCopySrc @@ -1089,6 +1090,9 @@ 0x801aad74 OSGetTick 0x801aad7c __OSGetSystemTime 0x801aafa8 OSTicksToCalendarTime +0x801ab648 __OSGetIPCBufferHi +0x801ab650 __OSGetIPCBufferLo +0x801ab658 __OSInitIPCBuffer 0x801ae5d8 PAD_ClampCircle 0x801ae6f4 PADClampCircle 0x801ae7dc PADClampCircle2 diff --git a/source/rvl/ipc/ipcMain.c b/source/rvl/ipc/ipcMain.c new file mode 100644 index 000000000..56957b183 --- /dev/null +++ b/source/rvl/ipc/ipcMain.c @@ -0,0 +1,62 @@ +#include "ipcMain.h" + +// Extern function references. +// PAL: 0x801ab648 +extern void* __OSGetIPCBufferHi(void); +// PAL: 0x801ab650 +extern void* __OSGetIPCBufferLo(void); + +// Hardware registers. +u32 HOLLYWOOD_REGS[137] : 0xcd000000; + +// Static vars. +static void* IPCAltBufferHi; +static void* IPCAltBufferLo; +static void* IPCBufferHi; +static void* IPCBufferLo; +static u8 IPC_Initialized; + +// Symbol: IPCInit +// PAL: 0x80192f7c..0x80192fc8 +void IPCInit(void) { + if (IPC_Initialized) + return; + + IPCAltBufferHi = __OSGetIPCBufferHi(); + IPCAltBufferLo = __OSGetIPCBufferLo(); + IPCBufferHi = IPCAltBufferHi; + IPCBufferLo = IPCAltBufferLo; + + IPC_Initialized = 1; +} + +// Symbol: IPCReInit +// PAL: 0x80192fc8..0x80193010 +void IPCReInit(void) { + IPC_Initialized = 0; + IPCAltBufferHi = __OSGetIPCBufferHi(); + IPCAltBufferLo = __OSGetIPCBufferLo(); + IPCBufferHi = IPCAltBufferHi; + IPCBufferLo = IPCAltBufferLo; + IPC_Initialized = 1; +} + +// Symbol: IPCReadReg +// PAL: 0x80193010..0x80193020 +u32 IPCReadReg(u32 reg) { return HOLLYWOOD_REGS[reg]; } + +// Symbol: IPCWriteReg +// PAL: 0x80193020..0x80193030 +void IPCWriteReg(u32 reg, u32 data) { HOLLYWOOD_REGS[reg] = data; } + +// Symbol: IPCGetBufferHi +// PAL: 0x80193030..0x80193038 +void* IPCGetBufferHi(void) { return IPCBufferHi; } + +// Symbol: IPCGetBufferLo +// PAL: 0x80193038..0x80193040 +void* IPCGetBufferLo(void) { return IPCBufferLo; } + +// Symbol: IPCSetBufferLo +// PAL: 0x80193040..0x80193048 +void IPCSetBufferLo(void* addr) { IPCBufferLo = addr; } diff --git a/source/rvl/ipc/ipcMain.h b/source/rvl/ipc/ipcMain.h new file mode 100644 index 000000000..e0f98de4c --- /dev/null +++ b/source/rvl/ipc/ipcMain.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x80192f7c..0x80192fc8 +void IPCInit(void); +// PAL: 0x80192fc8..0x80193010 +void IPCReInit(void); +// PAL: 0x80193010..0x80193020 +u32 IPCReadReg(u32); +// PAL: 0x80193020..0x80193030 +void IPCWriteReg(u32, u32); +// PAL: 0x80193030..0x80193038 +void* IPCGetBufferHi(void); +// PAL: 0x80193038..0x80193040 +void* IPCGetBufferLo(void); +// PAL: 0x80193040..0x80193048 +void IPCSetBufferLo(void*); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/ipc/ipcclt.c b/source/rvl/ipc/ipcclt.c new file mode 100644 index 000000000..a82d6c806 --- /dev/null +++ b/source/rvl/ipc/ipcclt.c @@ -0,0 +1,2060 @@ +#include "ipcclt.h" + +#include + +#include + +#include "ipcMain.h" + +// Extern function references. +// PAL: 0x801949b8 +extern UNKNOWN_FUNCTION(iosCreateHeap); +// PAL: 0x80194cec +extern UNKNOWN_FUNCTION(iosAllocAligned); +// PAL: 0x80194cf0 +extern UNKNOWN_FUNCTION(iosFree); +// PAL: 0x80194edc +extern UNKNOWN_FUNCTION(IPCiProfInit); +// PAL: 0x80194f94 +extern UNKNOWN_FUNCTION(IPCiProfQueueReq); +// PAL: 0x80195014 +extern UNKNOWN_FUNCTION(IPCiProfAck); +// PAL: 0x80195024 +extern UNKNOWN_FUNCTION(IPCiProfReply); +// PAL: 0x801a1600 +extern UNKNOWN_FUNCTION(unk_801a1600); +// PAL: 0x801a162c +extern UNKNOWN_FUNCTION(unk_801a162c); +// PAL: 0x801a1e70 +extern UNKNOWN_FUNCTION(OSSetCurrentContext); +// PAL: 0x801a2098 +extern UNKNOWN_FUNCTION(OSClearContext); +// PAL: 0x801a65f8 +extern UNKNOWN_FUNCTION(unk_801a65f8); +// PAL: 0x801a69bc +extern UNKNOWN_FUNCTION(unk_801a69bc); +// PAL: 0x801a98a0 +extern UNKNOWN_FUNCTION(unk_801a98a0); +// PAL: 0x801aa9b8 +extern UNKNOWN_FUNCTION(OSSleepThread); +// PAL: 0x801aaaa4 +extern UNKNOWN_FUNCTION(OSWakeupThread); + +// Symbol: strnlen +// Function signature is unknown. +// PAL: 0x80193048..0x80193074 +MARK_BINARY_BLOB(strnlen, 0x80193048, 0x80193074); +asm UNKNOWN_FUNCTION(strnlen) { + // clang-format off + nofralloc; + mr r5, r3; + b lbl_80193054; +lbl_80193050: + addi r5, r5, 1; +lbl_80193054: + lbz r0, 0(r5); + cmpwi r0, 0; + beq lbl_8019306c; + cmpwi r4, 0; + addi r4, r4, -1; + bne lbl_80193050; +lbl_8019306c: + subf r3, r3, r5; + blr; + // clang-format on +} + +// Symbol: IpcReplyHandler +// Function signature is unknown. +// PAL: 0x80193074..0x801932cc +MARK_BINARY_BLOB(IpcReplyHandler, 0x80193074, 0x801932cc); +asm UNKNOWN_FUNCTION(IpcReplyHandler) { + // clang-format off + nofralloc; + stwu r1, -0x2e0(r1); + mflr r0; + li r3, 2; + stw r0, 0x2e4(r1); + stw r31, 0x2dc(r1); + stw r30, 0x2d8(r1); + mr r30, r4; + stw r29, 0x2d4(r1); + stw r28, 0x2d0(r1); + bl IPCReadReg; + cmpwi r3, 0; + beq lbl_801932ac; + addis r31, r3, 0x8000; + li r3, 1; + bl IPCReadReg; + rlwinm r0, r3, 0, 0x1a, 0x1b; + li r3, 1; + ori r4, r0, 4; + bl IPCWriteReg; + lis r0, 0x4000; + lis r3, 0xcd00; + stw r0, 0x30(r3); + mr r3, r31; + li r4, 0x20; + bl unk_801a1600; + lwz r0, 8(r31); + cmpwi r0, 6; + beq lbl_80193134; + bge lbl_801930f4; + cmpwi r0, 3; + beq lbl_80193100; + b lbl_80193230; +lbl_801930f4: + cmpwi r0, 8; + bge lbl_80193230; + b lbl_8019316c; +lbl_80193100: + lwz r3, 0xc(r31); + cmpwi r3, 0; + beq lbl_80193114; + addis r0, r3, 0x8000; + b lbl_80193118; +lbl_80193114: + li r0, 0; +lbl_80193118: + stw r0, 0xc(r31); + lwz r4, 4(r31); + cmpwi r4, 0; + ble lbl_80193230; + lwz r3, 0xc(r31); + bl unk_801a1600; + b lbl_80193230; +lbl_80193134: + lwz r3, 0x18(r31); + cmpwi r3, 0; + beq lbl_80193148; + addis r0, r3, 0x8000; + b lbl_8019314c; +lbl_80193148: + li r0, 0; +lbl_8019314c: + stw r0, 0x18(r31); + lwz r3, 0x10(r31); + lwz r4, 0x14(r31); + bl unk_801a1600; + lwz r3, 0x18(r31); + lwz r4, 0x1c(r31); + bl unk_801a1600; + b lbl_80193230; +lbl_8019316c: + lwz r3, 0x18(r31); + cmpwi r3, 0; + beq lbl_80193180; + addis r3, r3, 0x8000; + b lbl_80193184; +lbl_80193180: + li r3, 0; +lbl_80193184: + stw r3, 0x18(r31); + lwz r4, 0x10(r31); + lwz r0, 0x14(r31); + add r0, r4, r0; + slwi r4, r0, 3; + bl unk_801a1600; + li r28, 0; + li r29, 0; + b lbl_801931e8; +lbl_801931a8: + lwz r3, 0x18(r31); + lwzx r3, r3, r29; + cmpwi r3, 0; + beq lbl_801931c0; + addis r0, r3, 0x8000; + b lbl_801931c4; +lbl_801931c0: + li r0, 0; +lbl_801931c4: + lwz r3, 0x18(r31); + stwx r0, r3, r29; + lwz r3, 0x18(r31); + add r4, r3, r29; + lwzx r3, r3, r29; + lwz r4, 4(r4); + bl unk_801a1600; + addi r28, r28, 1; + addi r29, r29, 8; +lbl_801931e8: + lwz r3, 0x10(r31); + lwz r0, 0x14(r31); + add r0, r3, r0; + cmplw r28, r0; + blt lbl_801931a8; + lwz r0, -0x6400(r13); + cmpwi r0, 0; + beq lbl_80193230; + lwz r0, -0x63f8(r13); + cmplw r0, r31; + bne lbl_80193230; + lwz r3, -0x7270(r13); + li r0, 0; + stw r0, -0x6400(r13); + cmpwi r3, 1; + bge lbl_80193230; + addi r0, r3, 1; + stw r0, -0x7270(r13); +lbl_80193230: + lwz r0, 0x20(r31); + cmpwi r0, 0; + beq lbl_80193280; + addi r3, r1, 8; + bl OSClearContext; + addi r3, r1, 8; + bl OSSetCurrentContext; + lwz r12, 0x20(r31); + lwz r3, 4(r31); + lwz r4, 0x24(r31); + mtctr r12; + bctrl; + addi r3, r1, 8; + bl OSClearContext; + mr r3, r30; + bl OSSetCurrentContext; + lwz r3, -0x726c(r13); + mr r4, r31; + bl iosFree; + b lbl_80193288; +lbl_80193280: + addi r3, r31, 0x2c; + bl OSWakeupThread; +lbl_80193288: + li r3, 1; + bl IPCReadReg; + rlwinm r0, r3, 0, 0x1a, 0x1b; + li r3, 1; + ori r4, r0, 8; + bl IPCWriteReg; + lwz r4, 8(r31); + mr r3, r31; + bl IPCiProfReply; +lbl_801932ac: + lwz r0, 0x2e4(r1); + lwz r31, 0x2dc(r1); + lwz r30, 0x2d8(r1); + lwz r29, 0x2d4(r1); + lwz r28, 0x2d0(r1); + mtlr r0; + addi r1, r1, 0x2e0; + blr; + // clang-format on +} + +// Symbol: IPCInterruptHandler +// Function signature is unknown. +// PAL: 0x801932cc..0x80193478 +MARK_BINARY_BLOB(IPCInterruptHandler, 0x801932cc, 0x80193478); +asm UNKNOWN_FUNCTION(IPCInterruptHandler) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + li r3, 1; + bl IPCReadReg; + andi. r0, r3, 0x14; + cmplwi r0, 0x14; + bne lbl_80193308; + mr r3, r30; + mr r4, r31; + bl IpcReplyHandler; +lbl_80193308: + li r3, 1; + bl IPCReadReg; + andi. r0, r3, 0x22; + cmplwi r0, 0x22; + bne lbl_80193460; + li r3, 1; + bl IPCReadReg; + rlwinm r0, r3, 0, 0x1a, 0x1b; + li r3, 1; + ori r4, r0, 2; + bl IPCWriteReg; + lis r0, 0x4000; + lis r3, 0xcd00; + stw r0, 0x30(r3); + lwz r31, -0x7270(r13); + cmpwi r31, 1; + bge lbl_80193358; + addi r31, r31, 1; + stw r31, -0x7270(r13); + bl IPCiProfAck; +lbl_80193358: + cmpwi r31, 0; + ble lbl_80193460; + lwz r0, -0x6400(r13); + cmpwi r0, 0; + beq lbl_801933a0; + lwz r3, -0x63fc(r13); + li r0, 0; + stw r0, 4(r3); + lwz r3, -0x63fc(r13); + stw r0, -0x6400(r13); + addi r3, r3, 0x2c; + bl OSWakeupThread; + li r3, 1; + bl IPCReadReg; + rlwinm r0, r3, 0, 0x1a, 0x1b; + li r3, 1; + ori r4, r0, 8; + bl IPCWriteReg; +lbl_801933a0: + lis r4, 0x8034; + addi r3, r4, 0x55c0; + lwz r4, 0x55c0(r4); + lwz r0, 4(r3); + cmplw r0, r4; + bge lbl_801933c0; + subf r0, r4, r0; + b lbl_801933cc; +lbl_801933c0: + subf r0, r4, r0; + cntlzw r0, r0; + srwi r0, r0, 5; +lbl_801933cc: + cmpwi r0, 0; + bne lbl_80193460; + lis r3, 0x8034; + addi r3, r3, 0x55c0; + lwz r0, 8(r3); + slwi r0, r0, 2; + add r3, r3, r0; + lwz r4, 0x10(r3); + cmpwi r4, 0; + beq lbl_80193460; + lwz r0, 0x28(r4); + cmpwi r0, 0; + beq lbl_8019340c; + lwz r3, -0x7270(r13); + addi r0, r3, -1; + stw r0, -0x7270(r13); +lbl_8019340c: + addis r4, r4, 0x8000; + li r3, 0; + bl IPCWriteReg; + lis r7, 0x8034; + lwz r3, -0x7270(r13); + addi r6, r7, 0x55c0; + lwz r4, 0x55c0(r7); + lwz r5, 8(r6); + addi r0, r3, -1; + stw r0, -0x7270(r13); + addi r4, r4, 1; + addi r0, r5, 1; + li r3, 1; + clrlwi r0, r0, 0x1c; + stw r4, 0x55c0(r7); + stw r0, 8(r6); + bl IPCReadReg; + rlwinm r0, r3, 0, 0x1a, 0x1b; + li r3, 1; + ori r4, r0, 1; + bl IPCWriteReg; +lbl_80193460: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: IPCCltInit +// Function signature is unknown. +// PAL: 0x80193478..0x8019352c +MARK_BINARY_BLOB(IPCCltInit, 0x80193478, 0x8019352c); +asm UNKNOWN_FUNCTION(IPCCltInit) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + lwz r0, -0x63f4(r13); + stw r31, 0x1c(r1); + cmpwi r0, 0; + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + bne lbl_8019350c; + li r0, 1; + stw r0, -0x63f4(r13); + bl IPCInit; + bl IPCGetBufferLo; + mr r29, r3; + addi r31, r3, 0x1000; + bl IPCGetBufferHi; + cmplw r31, r3; + ble lbl_801934cc; + li r30, -22; + b lbl_8019350c; +lbl_801934cc: + mr r3, r29; + li r4, 0x1000; + bl iosCreateHeap; + stw r3, -0x726c(r13); + mr r3, r31; + bl IPCSetBufferLo; + lis r4, 0x8019; + li r3, 0x1b; + addi r4, r4, 0x32cc; + bl unk_801a65f8; + li r3, 0x10; + bl unk_801a69bc; + li r3, 1; + li r4, 0x38; + bl IPCWriteReg; + bl IPCiProfInit; +lbl_8019350c: + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IPCCltReInit +// Function signature is unknown. +// PAL: 0x8019352c..0x801935a0 +MARK_BINARY_BLOB(IPCCltReInit, 0x8019352c, 0x801935a0); +asm UNKNOWN_FUNCTION(IPCCltReInit) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + bl IPCGetBufferLo; + mr r29, r3; + addi r31, r3, 0x800; + bl IPCGetBufferHi; + cmplw r31, r3; + ble lbl_80193568; + li r30, -22; + b lbl_80193580; +lbl_80193568: + mr r3, r29; + li r4, 0x800; + bl iosCreateHeap; + stw r3, -0x726c(r13); + mr r3, r31; + bl IPCSetBufferLo; +lbl_80193580: + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __ios_Ipc2 +// Function signature is unknown. +// PAL: 0x801935a0..0x801937e0 +MARK_BINARY_BLOB(__ios_Ipc2, 0x801935a0, 0x801937e0); +asm UNKNOWN_FUNCTION(__ios_Ipc2) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bne lbl_801935d4; + li r31, -4; + b lbl_801937a0; +lbl_801935d4: + cmpwi r4, 0; + bne lbl_801935e4; + addi r3, r3, 0x2c; + bl unk_801a98a0; +lbl_801935e4: + mr r3, r28; + li r4, 0x20; + bl unk_801a162c; + bl OSDisableInterrupts; + lis r4, 0x8034; + mr r30, r3; + addi r3, r4, 0x55c0; + lwz r4, 0x55c0(r4); + lwz r0, 4(r3); + li r31, 0; + cmplw r0, r4; + bge lbl_8019361c; + subf r0, r4, r0; + b lbl_80193638; +lbl_8019361c: + subf r4, r4, r0; + li r3, 0x10; + addi r0, r4, -16; + orc r3, r4, r3; + srwi r0, r0, 1; + subf r0, r0, r3; + srwi r0, r0, 0x1f; +lbl_80193638: + cmpwi r0, 0; + beq lbl_80193648; + li r31, -8; + b lbl_80193688; +lbl_80193648: + lis r6, 0x8034; + mr r3, r28; + addi r6, r6, 0x55c0; + lwz r0, 0xc(r6); + slwi r0, r0, 2; + add r4, r6, r0; + stw r28, 0x10(r4); + lwz r5, 0xc(r6); + lwz r4, 4(r6); + addi r0, r5, 1; + clrlwi r5, r0, 0x1c; + addi r0, r4, 1; + stw r5, 0xc(r6); + stw r0, 4(r6); + lwz r4, 8(r28); + bl IPCiProfQueueReq; +lbl_80193688: + cmpwi r31, 0; + beq lbl_801936b0; + mr r3, r30; + bl OSRestoreInterrupts; + cmpwi r29, 0; + beq lbl_801937a0; + lwz r3, -0x726c(r13); + mr r4, r28; + bl iosFree; + b lbl_801937a0; +lbl_801936b0: + lwz r0, -0x7270(r13); + cmpwi r0, 0; + ble lbl_8019377c; + lis r4, 0x8034; + addi r3, r4, 0x55c0; + lwz r4, 0x55c0(r4); + lwz r0, 4(r3); + cmplw r0, r4; + bge lbl_801936dc; + subf r0, r4, r0; + b lbl_801936e8; +lbl_801936dc: + subf r0, r4, r0; + cntlzw r0, r0; + srwi r0, r0, 5; +lbl_801936e8: + cmpwi r0, 0; + bne lbl_8019377c; + lis r3, 0x8034; + addi r3, r3, 0x55c0; + lwz r0, 8(r3); + slwi r0, r0, 2; + add r3, r3, r0; + lwz r4, 0x10(r3); + cmpwi r4, 0; + beq lbl_8019377c; + lwz r0, 0x28(r4); + cmpwi r0, 0; + beq lbl_80193728; + lwz r3, -0x7270(r13); + addi r0, r3, -1; + stw r0, -0x7270(r13); +lbl_80193728: + addis r4, r4, 0x8000; + li r3, 0; + bl IPCWriteReg; + lis r7, 0x8034; + lwz r3, -0x7270(r13); + addi r6, r7, 0x55c0; + lwz r4, 0x55c0(r7); + lwz r5, 8(r6); + addi r0, r3, -1; + stw r0, -0x7270(r13); + addi r4, r4, 1; + addi r0, r5, 1; + li r3, 1; + clrlwi r0, r0, 0x1c; + stw r4, 0x55c0(r7); + stw r0, 8(r6); + bl IPCReadReg; + rlwinm r0, r3, 0, 0x1a, 0x1b; + li r3, 1; + ori r4, r0, 1; + bl IPCWriteReg; +lbl_8019377c: + cmpwi r29, 0; + bne lbl_8019378c; + addi r3, r28, 0x2c; + bl OSSleepThread; +lbl_8019378c: + mr r3, r30; + bl OSRestoreInterrupts; + cmpwi r29, 0; + bne lbl_801937a0; + lwz r31, 4(r28); +lbl_801937a0: + cmpwi r28, 0; + beq lbl_801937bc; + cmpwi r29, 0; + bne lbl_801937bc; + lwz r3, -0x726c(r13); + mr r4, r28; + bl iosFree; +lbl_801937bc: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_OpenAsync +// Function signature is unknown. +// PAL: 0x801937e0..0x801938f8 +MARK_BINARY_BLOB(IOS_OpenAsync, 0x801937e0, 0x801938f8); +asm UNKNOWN_FUNCTION(IOS_OpenAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_27; + addic. r0, r1, 8; + mr r27, r3; + mr r28, r4; + mr r29, r5; + mr r31, r6; + li r30, 0; + bne lbl_80193818; + li r30, -4; + b lbl_80193860; +lbl_80193818: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_8019383c; + li r30, -22; + b lbl_80193860; +lbl_8019383c: + stw r29, 0x20(r3); + li r5, 0; + li r0, 1; + lwz r4, 8(r1); + stw r31, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r5, 8(r3); +lbl_80193860: + cmpwi r30, 0; + bne lbl_801938dc; + lwz r31, 8(r1); + li r30, 0; + cmpwi r31, 0; + bne lbl_80193880; + li r30, -4; + b lbl_801938c4; +lbl_80193880: + mr r3, r27; + li r4, 0x40; + b lbl_80193890; +lbl_8019388c: + addi r3, r3, 1; +lbl_80193890: + lbz r0, 0(r3); + cmpwi r0, 0; + beq lbl_801938a8; + cmpwi r4, 0; + addi r4, r4, -1; + bne lbl_8019388c; +lbl_801938a8: + subf r4, r27, r3; + mr r3, r27; + addi r4, r4, 1; + bl unk_801a162c; + addis r0, r27, 0x8000; + stw r0, 0xc(r31); + stw r28, 0x10(r31); +lbl_801938c4: + cmpwi r30, 0; + bne lbl_801938dc; + lwz r3, 8(r1); + mr r4, r29; + bl __ios_Ipc2; + mr r30, r3; +lbl_801938dc: + addi r11, r1, 0x30; + mr r3, r30; + bl _restgpr_27; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: IOS_Open +// Function signature is unknown. +// PAL: 0x801938f8..0x80193a18 +MARK_BINARY_BLOB(IOS_Open, 0x801938f8, 0x80193a18); +asm UNKNOWN_FUNCTION(IOS_Open) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addic. r0, r1, 8; + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bne lbl_80193930; + li r30, -4; + b lbl_80193978; +lbl_80193930: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193954; + li r30, -22; + b lbl_80193978; +lbl_80193954: + li r5, 0; + li r0, 1; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r5, 8(r3); +lbl_80193978: + cmpwi r30, 0; + bne lbl_801939f4; + lwz r31, 8(r1); + li r30, 0; + cmpwi r31, 0; + bne lbl_80193998; + li r30, -4; + b lbl_801939dc; +lbl_80193998: + mr r3, r28; + li r4, 0x40; + b lbl_801939a8; +lbl_801939a4: + addi r3, r3, 1; +lbl_801939a8: + lbz r0, 0(r3); + cmpwi r0, 0; + beq lbl_801939c0; + cmpwi r4, 0; + addi r4, r4, -1; + bne lbl_801939a4; +lbl_801939c0: + subf r4, r28, r3; + mr r3, r28; + addi r4, r4, 1; + bl unk_801a162c; + addis r0, r28, 0x8000; + stw r0, 0xc(r31); + stw r29, 0x10(r31); +lbl_801939dc: + cmpwi r30, 0; + bne lbl_801939f4; + lwz r3, 8(r1); + li r4, 0; + bl __ios_Ipc2; + mr r30, r3; +lbl_801939f4: + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_CloseAsync +// Function signature is unknown. +// PAL: 0x80193a18..0x80193ad8 +MARK_BINARY_BLOB(IOS_CloseAsync, 0x80193a18, 0x80193ad8); +asm UNKNOWN_FUNCTION(IOS_CloseAsync) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addic. r0, r1, 8; + stw r31, 0x1c(r1); + li r31, 0; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bne lbl_80193a54; + li r31, -4; + b lbl_80193a9c; +lbl_80193a54: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193a78; + li r31, -22; + b lbl_80193a9c; +lbl_80193a78: + stw r29, 0x20(r3); + li r5, 0; + li r0, 2; + lwz r4, 8(r1); + stw r30, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r28, 8(r3); +lbl_80193a9c: + cmpwi r31, 0; + bne lbl_80193ab4; + lwz r3, 8(r1); + mr r4, r29; + bl __ios_Ipc2; + mr r31, r3; +lbl_80193ab4: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_Close +// Function signature is unknown. +// PAL: 0x80193ad8..0x80193b80 +MARK_BINARY_BLOB(IOS_Close, 0x80193ad8, 0x80193b80); +asm UNKNOWN_FUNCTION(IOS_Close) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addic. r0, r1, 8; + stw r31, 0x1c(r1); + li r31, 0; + stw r30, 0x18(r1); + mr r30, r3; + bne lbl_80193b04; + li r31, -4; + b lbl_80193b4c; +lbl_80193b04: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193b28; + li r31, -22; + b lbl_80193b4c; +lbl_80193b28: + li r5, 0; + li r0, 2; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r30, 8(r3); +lbl_80193b4c: + cmpwi r31, 0; + bne lbl_80193b64; + lwz r3, 8(r1); + li r4, 0; + bl __ios_Ipc2; + mr r31, r3; +lbl_80193b64: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_ReadAsync +// Function signature is unknown. +// PAL: 0x80193b80..0x80193c80 +MARK_BINARY_BLOB(IOS_ReadAsync, 0x80193b80, 0x80193c80); +asm UNKNOWN_FUNCTION(IOS_ReadAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + addic. r0, r1, 8; + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r29, r6; + mr r31, r7; + li r30, 0; + bne lbl_80193bbc; + li r30, -4; + b lbl_80193c04; +lbl_80193bbc: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193be0; + li r30, -22; + b lbl_80193c04; +lbl_80193be0: + stw r29, 0x20(r3); + li r5, 0; + li r0, 3; + lwz r4, 8(r1); + stw r31, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r26, 8(r3); +lbl_80193c04: + cmpwi r30, 0; + bne lbl_80193c64; + lwz r31, 8(r1); + li r30, 0; + cmpwi r31, 0; + bne lbl_80193c24; + li r30, -4; + b lbl_80193c4c; +lbl_80193c24: + mr r3, r27; + mr r4, r28; + bl unk_801a1600; + cmpwi r27, 0; + beq lbl_80193c40; + addis r0, r27, 0x8000; + b lbl_80193c44; +lbl_80193c40: + li r0, 0; +lbl_80193c44: + stw r0, 0xc(r31); + stw r28, 0x10(r31); +lbl_80193c4c: + cmpwi r30, 0; + bne lbl_80193c64; + lwz r3, 8(r1); + mr r4, r29; + bl __ios_Ipc2; + mr r30, r3; +lbl_80193c64: + addi r11, r1, 0x30; + mr r3, r30; + bl _restgpr_26; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: IOS_Read +// Function signature is unknown. +// PAL: 0x80193c80..0x80193d88 +MARK_BINARY_BLOB(IOS_Read, 0x80193c80, 0x80193d88); +asm UNKNOWN_FUNCTION(IOS_Read) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addic. r0, r1, 8; + stw r31, 0x1c(r1); + mr r31, r3; + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + mr r29, r5; + stw r28, 0x10(r1); + mr r28, r4; + bne lbl_80193cbc; + li r30, -4; + b lbl_80193d04; +lbl_80193cbc: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193ce0; + li r30, -22; + b lbl_80193d04; +lbl_80193ce0: + li r5, 0; + li r0, 3; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r31, 8(r3); +lbl_80193d04: + cmpwi r30, 0; + bne lbl_80193d64; + lwz r31, 8(r1); + li r30, 0; + cmpwi r31, 0; + bne lbl_80193d24; + li r30, -4; + b lbl_80193d4c; +lbl_80193d24: + mr r3, r28; + mr r4, r29; + bl unk_801a1600; + cmpwi r28, 0; + beq lbl_80193d40; + addis r0, r28, 0x8000; + b lbl_80193d44; +lbl_80193d40: + li r0, 0; +lbl_80193d44: + stw r0, 0xc(r31); + stw r29, 0x10(r31); +lbl_80193d4c: + cmpwi r30, 0; + bne lbl_80193d64; + lwz r3, 8(r1); + li r4, 0; + bl __ios_Ipc2; + mr r30, r3; +lbl_80193d64: + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_WriteAsync +// Function signature is unknown. +// PAL: 0x80193d88..0x80193e88 +MARK_BINARY_BLOB(IOS_WriteAsync, 0x80193d88, 0x80193e88); +asm UNKNOWN_FUNCTION(IOS_WriteAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + addic. r0, r1, 8; + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r29, r6; + mr r30, r7; + li r31, 0; + bne lbl_80193dc4; + li r31, -4; + b lbl_80193e0c; +lbl_80193dc4: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193de8; + li r31, -22; + b lbl_80193e0c; +lbl_80193de8: + stw r29, 0x20(r3); + li r5, 0; + li r0, 4; + lwz r4, 8(r1); + stw r30, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r26, 8(r3); +lbl_80193e0c: + cmpwi r31, 0; + bne lbl_80193e6c; + lwz r5, 8(r1); + li r31, 0; + cmpwi r5, 0; + bne lbl_80193e2c; + li r31, -4; + b lbl_80193e54; +lbl_80193e2c: + cmpwi r27, 0; + beq lbl_80193e3c; + addis r0, r27, 0x8000; + b lbl_80193e40; +lbl_80193e3c: + li r0, 0; +lbl_80193e40: + stw r0, 0xc(r5); + mr r3, r27; + mr r4, r28; + stw r28, 0x10(r5); + bl unk_801a162c; +lbl_80193e54: + cmpwi r31, 0; + bne lbl_80193e6c; + lwz r3, 8(r1); + mr r4, r29; + bl __ios_Ipc2; + mr r31, r3; +lbl_80193e6c: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_26; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: IOS_Write +// Function signature is unknown. +// PAL: 0x80193e88..0x80193f90 +MARK_BINARY_BLOB(IOS_Write, 0x80193e88, 0x80193f90); +asm UNKNOWN_FUNCTION(IOS_Write) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addic. r0, r1, 8; + stw r31, 0x1c(r1); + li r31, 0; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bne lbl_80193ec4; + li r31, -4; + b lbl_80193f0c; +lbl_80193ec4: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193ee8; + li r31, -22; + b lbl_80193f0c; +lbl_80193ee8: + li r5, 0; + li r0, 4; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r28, 8(r3); +lbl_80193f0c: + cmpwi r31, 0; + bne lbl_80193f6c; + lwz r5, 8(r1); + li r31, 0; + cmpwi r5, 0; + bne lbl_80193f2c; + li r31, -4; + b lbl_80193f54; +lbl_80193f2c: + cmpwi r29, 0; + beq lbl_80193f3c; + addis r0, r29, 0x8000; + b lbl_80193f40; +lbl_80193f3c: + li r0, 0; +lbl_80193f40: + stw r0, 0xc(r5); + mr r3, r29; + mr r4, r30; + stw r30, 0x10(r5); + bl unk_801a162c; +lbl_80193f54: + cmpwi r31, 0; + bne lbl_80193f6c; + lwz r3, 8(r1); + li r4, 0; + bl __ios_Ipc2; + mr r31, r3; +lbl_80193f6c: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_SeekAsync +// Function signature is unknown. +// PAL: 0x80193f90..0x80194070 +MARK_BINARY_BLOB(IOS_SeekAsync, 0x80193f90, 0x80194070); +asm UNKNOWN_FUNCTION(IOS_SeekAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + addic. r0, r1, 8; + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r29, r6; + mr r30, r7; + li r31, 0; + bne lbl_80193fcc; + li r31, -4; + b lbl_80194014; +lbl_80193fcc: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80193ff0; + li r31, -22; + b lbl_80194014; +lbl_80193ff0: + stw r29, 0x20(r3); + li r5, 0; + li r0, 5; + lwz r4, 8(r1); + stw r30, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r26, 8(r3); +lbl_80194014: + cmpwi r31, 0; + bne lbl_80194054; + lwz r3, 8(r1); + li r31, 0; + cmpwi r3, 0; + bne lbl_80194034; + li r31, -4; + b lbl_8019403c; +lbl_80194034: + stw r27, 0xc(r3); + stw r28, 0x10(r3); +lbl_8019403c: + cmpwi r31, 0; + bne lbl_80194054; + lwz r3, 8(r1); + mr r4, r29; + bl __ios_Ipc2; + mr r31, r3; +lbl_80194054: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_26; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: IOS_Seek +// Function signature is unknown. +// PAL: 0x80194070..0x80194158 +MARK_BINARY_BLOB(IOS_Seek, 0x80194070, 0x80194158); +asm UNKNOWN_FUNCTION(IOS_Seek) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addic. r0, r1, 8; + stw r31, 0x1c(r1); + li r31, 0; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bne lbl_801940ac; + li r31, -4; + b lbl_801940f4; +lbl_801940ac: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_801940d0; + li r31, -22; + b lbl_801940f4; +lbl_801940d0: + li r5, 0; + li r0, 5; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r28, 8(r3); +lbl_801940f4: + cmpwi r31, 0; + bne lbl_80194134; + lwz r3, 8(r1); + li r31, 0; + cmpwi r3, 0; + bne lbl_80194114; + li r31, -4; + b lbl_8019411c; +lbl_80194114: + stw r29, 0xc(r3); + stw r30, 0x10(r3); +lbl_8019411c: + cmpwi r31, 0; + bne lbl_80194134; + lwz r3, 8(r1); + li r4, 0; + bl __ios_Ipc2; + mr r31, r3; +lbl_80194134: + mr r3, r31; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_IoctlAsync +// Function signature is unknown. +// PAL: 0x80194158..0x80194290 +MARK_BINARY_BLOB(IOS_IoctlAsync, 0x80194158, 0x80194290); +asm UNKNOWN_FUNCTION(IOS_IoctlAsync) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + mflr r0; + stw r0, 0x44(r1); + addi r11, r1, 0x40; + bl _savegpr_23; + addic. r0, r1, 8; + mr r23, r3; + mr r24, r4; + mr r25, r5; + mr r26, r6; + mr r27, r7; + mr r28, r8; + mr r29, r9; + mr r30, r10; + li r31, 0; + bne lbl_801941a0; + li r31, -4; + b lbl_801941e8; +lbl_801941a0: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_801941c4; + li r31, -22; + b lbl_801941e8; +lbl_801941c4: + stw r29, 0x20(r3); + li r5, 0; + li r0, 6; + lwz r4, 8(r1); + stw r30, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r23, 8(r3); +lbl_801941e8: + cmpwi r31, 0; + bne lbl_80194274; + lwz r5, 8(r1); + li r31, 0; + cmpwi r5, 0; + bne lbl_80194208; + li r31, -4; + b lbl_8019425c; +lbl_80194208: + cmpwi r27, 0; + stw r24, 0xc(r5); + beq lbl_8019421c; + addis r0, r27, 0x8000; + b lbl_80194220; +lbl_8019421c: + li r0, 0; +lbl_80194220: + stw r0, 0x18(r5); + cmpwi r25, 0; + stw r28, 0x1c(r5); + beq lbl_80194238; + addis r0, r25, 0x8000; + b lbl_8019423c; +lbl_80194238: + li r0, 0; +lbl_8019423c: + stw r0, 0x10(r5); + mr r3, r25; + mr r4, r26; + stw r26, 0x14(r5); + bl unk_801a162c; + mr r3, r27; + mr r4, r28; + bl unk_801a162c; +lbl_8019425c: + cmpwi r31, 0; + bne lbl_80194274; + lwz r3, 8(r1); + mr r4, r29; + bl __ios_Ipc2; + mr r31, r3; +lbl_80194274: + addi r11, r1, 0x40; + mr r3, r31; + bl _restgpr_23; + lwz r0, 0x44(r1); + mtlr r0; + addi r1, r1, 0x40; + blr; + // clang-format on +} + +// Symbol: IOS_Ioctl +// Function signature is unknown. +// PAL: 0x80194290..0x801943c0 +MARK_BINARY_BLOB(IOS_Ioctl, 0x80194290, 0x801943c0); +asm UNKNOWN_FUNCTION(IOS_Ioctl) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_25; + addic. r0, r1, 8; + mr r25, r3; + mr r26, r4; + mr r27, r5; + mr r28, r6; + mr r29, r7; + mr r30, r8; + li r31, 0; + bne lbl_801942d0; + li r31, -4; + b lbl_80194318; +lbl_801942d0: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_801942f4; + li r31, -22; + b lbl_80194318; +lbl_801942f4: + li r5, 0; + li r0, 6; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r25, 8(r3); +lbl_80194318: + cmpwi r31, 0; + bne lbl_801943a4; + lwz r5, 8(r1); + li r31, 0; + cmpwi r5, 0; + bne lbl_80194338; + li r31, -4; + b lbl_8019438c; +lbl_80194338: + cmpwi r29, 0; + stw r26, 0xc(r5); + beq lbl_8019434c; + addis r0, r29, 0x8000; + b lbl_80194350; +lbl_8019434c: + li r0, 0; +lbl_80194350: + stw r0, 0x18(r5); + cmpwi r27, 0; + stw r30, 0x1c(r5); + beq lbl_80194368; + addis r0, r27, 0x8000; + b lbl_8019436c; +lbl_80194368: + li r0, 0; +lbl_8019436c: + stw r0, 0x10(r5); + mr r3, r27; + mr r4, r28; + stw r28, 0x14(r5); + bl unk_801a162c; + mr r3, r29; + mr r4, r30; + bl unk_801a162c; +lbl_8019438c: + cmpwi r31, 0; + bne lbl_801943a4; + lwz r3, 8(r1); + li r4, 0; + bl __ios_Ipc2; + mr r31, r3; +lbl_801943a4: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_25; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: __ios_Ioctlv +// Function signature is unknown. +// PAL: 0x801943c0..0x801944fc +MARK_BINARY_BLOB(__ios_Ioctlv, 0x801943c0, 0x801944fc); +asm UNKNOWN_FUNCTION(__ios_Ioctlv) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_26; + cmpwi r3, 0; + mr r29, r3; + mr r30, r7; + li r31, 0; + bne lbl_801943f0; + li r31, -4; + b lbl_801944e0; +lbl_801943f0: + stw r4, 0xc(r3); + slwi r27, r5, 3; + li r26, 0; + li r28, 0; + stw r5, 0x10(r3); + stw r6, 0x14(r3); + stw r7, 0x18(r3); + b lbl_80194454; +lbl_80194410: + lwz r3, 0x18(r29); + add r0, r28, r27; + add r4, r3, r0; + lwzx r3, r3, r0; + lwz r4, 4(r4); + bl unk_801a162c; + lwz r4, 0x18(r29); + add r3, r28, r27; + lwzx r5, r4, r3; + cmpwi r5, 0; + beq lbl_80194444; + addis r0, r5, 0x8000; + b lbl_80194448; +lbl_80194444: + li r0, 0; +lbl_80194448: + stwx r0, r4, r3; + addi r28, r28, 8; + addi r26, r26, 1; +lbl_80194454: + lwz r0, 0x14(r29); + cmplw r26, r0; + blt lbl_80194410; + li r27, 0; + li r28, 0; + b lbl_801944a8; +lbl_8019446c: + lwz r0, 0x18(r29); + add r4, r0, r28; + lwzx r3, r28, r0; + lwz r4, 4(r4); + bl unk_801a162c; + lwz r3, 0x18(r29); + lwzx r4, r3, r28; + cmpwi r4, 0; + beq lbl_80194498; + addis r0, r4, 0x8000; + b lbl_8019449c; +lbl_80194498: + li r0, 0; +lbl_8019449c: + stwx r0, r3, r28; + addi r28, r28, 8; + addi r27, r27, 1; +lbl_801944a8: + lwz r4, 0x10(r29); + cmplw r27, r4; + blt lbl_8019446c; + lwz r0, 0x14(r29); + lwz r3, 0x18(r29); + add r0, r4, r0; + slwi r4, r0, 3; + bl unk_801a162c; + cmpwi r30, 0; + beq lbl_801944d8; + addis r0, r30, 0x8000; + b lbl_801944dc; +lbl_801944d8: + li r0, 0; +lbl_801944dc: + stw r0, 0x18(r29); +lbl_801944e0: + addi r11, r1, 0x20; + mr r3, r31; + bl _restgpr_26; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: IOS_IoctlvAsync +// Function signature is unknown. +// PAL: 0x801944fc..0x801945e0 +MARK_BINARY_BLOB(IOS_IoctlvAsync, 0x801944fc, 0x801945e0); +asm UNKNOWN_FUNCTION(IOS_IoctlvAsync) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_24; + addic. r0, r1, 8; + mr r24, r3; + mr r25, r4; + mr r26, r5; + mr r27, r6; + mr r28, r7; + mr r29, r8; + mr r30, r9; + li r31, 0; + bne lbl_80194540; + li r31, -4; + b lbl_80194588; +lbl_80194540: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80194564; + li r31, -22; + b lbl_80194588; +lbl_80194564: + stw r29, 0x20(r3); + li r5, 0; + li r0, 7; + lwz r4, 8(r1); + stw r30, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r24, 8(r3); +lbl_80194588: + cmpwi r31, 0; + bne lbl_801945c4; + lwz r3, 8(r1); + mr r4, r25; + mr r5, r26; + mr r6, r27; + mr r7, r28; + bl __ios_Ioctlv; + cmpwi r3, 0; + mr r31, r3; + bne lbl_801945c4; + lwz r3, 8(r1); + mr r4, r29; + bl __ios_Ipc2; + mr r31, r3; +lbl_801945c4: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_24; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: IOS_Ioctlv +// Function signature is unknown. +// PAL: 0x801945e0..0x801946bc +MARK_BINARY_BLOB(IOS_Ioctlv, 0x801945e0, 0x801946bc); +asm UNKNOWN_FUNCTION(IOS_Ioctlv) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + addic. r0, r1, 8; + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r29, r6; + mr r30, r7; + li r31, 0; + bne lbl_8019461c; + li r31, -4; + b lbl_80194664; +lbl_8019461c: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80194640; + li r31, -22; + b lbl_80194664; +lbl_80194640: + li r5, 0; + li r0, 7; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r26, 8(r3); +lbl_80194664: + cmpwi r31, 0; + bne lbl_801946a0; + lwz r3, 8(r1); + mr r4, r27; + mr r5, r28; + mr r6, r29; + mr r7, r30; + bl __ios_Ioctlv; + cmpwi r3, 0; + mr r31, r3; + bne lbl_801946a0; + lwz r3, 8(r1); + li r4, 0; + bl __ios_Ipc2; + mr r31, r3; +lbl_801946a0: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_26; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: IOS_IoctlvReboot +// Function signature is unknown. +// PAL: 0x801946bc..0x801949b8 +MARK_BINARY_BLOB(IOS_IoctlvReboot, 0x801946bc, 0x801949b8); +asm UNKNOWN_FUNCTION(IOS_IoctlvReboot) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r30, r6; + mr r29, r7; + bl OSDisableInterrupts; + lwz r0, -0x6400(r13); + cmpwi r0, 0; + beq lbl_80194700; + bl OSRestoreInterrupts; + li r31, -10; + b lbl_8019499c; +lbl_80194700: + li r0, 1; + stw r0, -0x6400(r13); + bl OSRestoreInterrupts; + addic. r0, r1, 8; + li r31, 0; + bne lbl_80194720; + li r31, -4; + b lbl_80194768; +lbl_80194720: + lwz r3, -0x726c(r13); + li r4, 0x40; + li r5, 0x20; + bl iosAllocAligned; + cmpwi r3, 0; + stw r3, 8(r1); + bne lbl_80194744; + li r31, -22; + b lbl_80194768; +lbl_80194744: + li r5, 0; + li r0, 7; + stw r5, 0x20(r3); + lwz r4, 8(r1); + stw r5, 0x24(r4); + lwz r4, 8(r1); + stw r5, 0x28(r4); + stw r0, 0(r3); + stw r26, 8(r3); +lbl_80194768: + cmpwi r31, 0; + bne lbl_80194974; + lwz r3, 8(r1); + li r0, 1; + mr r4, r27; + mr r5, r28; + stw r3, -0x63f8(r13); + mr r6, r30; + mr r7, r29; + stw r0, 0x28(r3); + lwz r3, 8(r1); + bl __ios_Ioctlv; + cmpwi r3, 0; + mr r31, r3; + bne lbl_80194974; + lis r29, 0x8034; + lwz r4, 8(r1); + addi r3, r29, 0x5620; + li r5, 0x40; + bl memcpy; + addi r3, r29, 0x5620; + lwz r29, 8(r1); + stw r3, -0x63fc(r13); + addi r3, r3, 0x2c; + bl unk_801a98a0; + mr r3, r29; + li r4, 0x20; + bl unk_801a162c; + bl OSDisableInterrupts; + lis r4, 0x8034; + mr r30, r3; + addi r3, r4, 0x55c0; + lwz r0, 0x55c0(r4); + lwz r3, 4(r3); + li r31, 0; + cmplw r3, r0; + bge lbl_80194808; + subfic r0, r0, 0; + add r0, r0, r3; + b lbl_80194824; +lbl_80194808: + subf r4, r0, r3; + li r3, 0x10; + addi r0, r4, -16; + orc r3, r4, r3; + srwi r0, r0, 1; + subf r0, r0, r3; + srwi r0, r0, 0x1f; +lbl_80194824: + cmpwi r0, 0; + beq lbl_80194834; + li r31, -8; + b lbl_80194874; +lbl_80194834: + lis r6, 0x8034; + mr r3, r29; + addi r6, r6, 0x55c0; + lwz r0, 0xc(r6); + slwi r0, r0, 2; + add r4, r6, r0; + stw r29, 0x10(r4); + lwz r5, 0xc(r6); + lwz r4, 4(r6); + addi r0, r5, 1; + clrlwi r5, r0, 0x1c; + addi r0, r4, 1; + stw r5, 0xc(r6); + stw r0, 4(r6); + lwz r4, 8(r29); + bl IPCiProfQueueReq; +lbl_80194874: + cmpwi r31, 0; + beq lbl_80194888; + mr r3, r30; + bl OSRestoreInterrupts; + b lbl_80194974; +lbl_80194888: + lwz r0, -0x7270(r13); + cmpwi r0, 0; + ble lbl_80194958; + lis r4, 0x8034; + addi r3, r4, 0x55c0; + lwz r0, 0x55c0(r4); + lwz r3, 4(r3); + cmplw r3, r0; + bge lbl_801948b8; + subfic r0, r0, 0; + add r0, r0, r3; + b lbl_801948c4; +lbl_801948b8: + subf r0, r0, r3; + cntlzw r0, r0; + srwi r0, r0, 5; +lbl_801948c4: + cmpwi r0, 0; + bne lbl_80194958; + lis r3, 0x8034; + addi r3, r3, 0x55c0; + lwz r0, 8(r3); + slwi r0, r0, 2; + add r3, r3, r0; + lwz r4, 0x10(r3); + cmpwi r4, 0; + beq lbl_80194958; + lwz r0, 0x28(r4); + cmpwi r0, 0; + beq lbl_80194904; + lwz r3, -0x7270(r13); + addi r0, r3, -1; + stw r0, -0x7270(r13); +lbl_80194904: + addis r4, r4, 0x8000; + li r3, 0; + bl IPCWriteReg; + lis r7, 0x8034; + lwz r3, -0x7270(r13); + addi r6, r7, 0x55c0; + lwz r4, 0x55c0(r7); + lwz r5, 8(r6); + addi r0, r3, -1; + stw r0, -0x7270(r13); + addi r4, r4, 1; + addi r0, r5, 1; + li r3, 1; + clrlwi r0, r0, 0x1c; + stw r4, 0x55c0(r7); + stw r0, 8(r6); + bl IPCReadReg; + rlwinm r0, r3, 0, 0x1a, 0x1b; + li r3, 1; + ori r4, r0, 1; + bl IPCWriteReg; +lbl_80194958: + lwz r3, -0x63fc(r13); + addi r3, r3, 0x2c; + bl OSSleepThread; + mr r3, r30; + bl OSRestoreInterrupts; + lwz r3, -0x63fc(r13); + lwz r31, 4(r3); +lbl_80194974: + lwz r4, 8(r1); + li r0, 0; + stw r0, -0x6400(r13); + cmpwi r4, 0; + stw r0, -0x63f8(r13); + beq lbl_8019499c; + cmpwi r31, 0; + beq lbl_8019499c; + lwz r3, -0x726c(r13); + bl iosFree; +lbl_8019499c: + addi r11, r1, 0x30; + mr r3, r31; + bl _restgpr_26; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} diff --git a/source/rvl/ipc/ipcclt.h b/source/rvl/ipc/ipcclt.h new file mode 100644 index 000000000..d3530b3b4 --- /dev/null +++ b/source/rvl/ipc/ipcclt.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x80193048..0x80193074 +UNKNOWN_FUNCTION(strnlen); +// PAL: 0x80193074..0x801932cc +UNKNOWN_FUNCTION(IpcReplyHandler); +// PAL: 0x801932cc..0x80193478 +UNKNOWN_FUNCTION(IPCInterruptHandler); +// PAL: 0x80193478..0x8019352c +UNKNOWN_FUNCTION(IPCCltInit); +// PAL: 0x8019352c..0x801935a0 +UNKNOWN_FUNCTION(IPCCltReInit); +// PAL: 0x801935a0..0x801937e0 +UNKNOWN_FUNCTION(__ios_Ipc2); +// PAL: 0x801937e0..0x801938f8 +UNKNOWN_FUNCTION(IOS_OpenAsync); +// PAL: 0x801938f8..0x80193a18 +UNKNOWN_FUNCTION(IOS_Open); +// PAL: 0x80193a18..0x80193ad8 +UNKNOWN_FUNCTION(IOS_CloseAsync); +// PAL: 0x80193ad8..0x80193b80 +UNKNOWN_FUNCTION(IOS_Close); +// PAL: 0x80193b80..0x80193c80 +UNKNOWN_FUNCTION(IOS_ReadAsync); +// PAL: 0x80193c80..0x80193d88 +UNKNOWN_FUNCTION(IOS_Read); +// PAL: 0x80193d88..0x80193e88 +UNKNOWN_FUNCTION(IOS_WriteAsync); +// PAL: 0x80193e88..0x80193f90 +UNKNOWN_FUNCTION(IOS_Write); +// PAL: 0x80193f90..0x80194070 +UNKNOWN_FUNCTION(IOS_SeekAsync); +// PAL: 0x80194070..0x80194158 +UNKNOWN_FUNCTION(IOS_Seek); +// PAL: 0x80194158..0x80194290 +UNKNOWN_FUNCTION(IOS_IoctlAsync); +// PAL: 0x80194290..0x801943c0 +UNKNOWN_FUNCTION(IOS_Ioctl); +// PAL: 0x801943c0..0x801944fc +UNKNOWN_FUNCTION(__ios_Ioctlv); +// PAL: 0x801944fc..0x801945e0 +UNKNOWN_FUNCTION(IOS_IoctlvAsync); +// PAL: 0x801945e0..0x801946bc +UNKNOWN_FUNCTION(IOS_Ioctlv); +// PAL: 0x801946bc..0x801949b8 +UNKNOWN_FUNCTION(IOS_IoctlvReboot); + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 36dda20e0d89c03c7b2d95398227807f119f055d Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Wed, 28 Jul 2021 20:12:09 +0200 Subject: [PATCH 117/477] Start decompiling KartComponent (#73) --- mkwutil/sources.py | 1 + pack/rel_objects.txt | 9 ++++++--- pack/rel_slices.csv | 1 + source/game/kart/KartComponent.cpp | 20 ++++++++++++++++++++ source/game/kart/KartComponent.hpp | 24 ++++++++++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 source/game/kart/KartComponent.cpp create mode 100644 source/game/kart/KartComponent.hpp diff --git a/mkwutil/sources.py b/mkwutil/sources.py index 6bb44583c..197c91cb9 100644 --- a/mkwutil/sources.py +++ b/mkwutil/sources.py @@ -185,4 +185,5 @@ class Source: Source(src="source/game/ui/ControlGroup.cpp", cc='4201_127', opts=REL_OPTS), Source(src="source/game/ui/UIControl.cpp", cc='4201_127', opts=REL_OPTS), Source(src="source/game/jmap/JmpResourceCourse.cpp", cc='4201_127', opts=REL_OPTS), + Source(src="source/game/kart/KartComponent.cpp", cc='4201_127', opts=REL_OPTS), ] diff --git a/pack/rel_objects.txt b/pack/rel_objects.txt index 1bccda207..47125aed0 100644 --- a/pack/rel_objects.txt +++ b/pack/rel_objects.txt @@ -5,8 +5,11 @@ out\rel\rodata_8088f710_808b2bd0.o out\rel\data_808b2bd0_808b2c30.o out\rel\bss_809bd6e0_809bd6e8.o out\JmpResourceCourse.o -out\rel\text_805127ec_805f8b34.o +out\rel\text_805127ec_80590128.o +out\rel\data_808b2c3c_808dd3d4.o +out\rel\bss_809bd6ec_809c1900.o +out\KartComponent.o +out\rel\text_805901d0_805f8b34.o out\MessageGroup.o out\rel\text_805f8b90_8088f400.o -out\rel\data_808b2c3c_808dd3d4.o -out\rel\bss_809bd6ec_809c4f90.o +out\rel\bss_809c1910_809c4f90.o diff --git a/pack/rel_slices.csv b/pack/rel_slices.csv index e03fa68f1..e6ba92504 100644 --- a/pack/rel_slices.csv +++ b/pack/rel_slices.csv @@ -1,3 +1,4 @@ enabled,name,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd 1,source/game/jmap/JmpResourceCourse.cpp,0x80512694,0x805127ec,,,,,,,0x808B2C30,0x808B2C3C,0x809BD6E8,0x809BD6EC +1,source/game/kart/KartComponent.cpp,0x80590128,0x805901d0,,,,,,,,,0x809c1900,0x809c1910 1,source/game/ui/MessageGroup.cpp,0x805F8B34,0x805F8B90,,,,,,,,,, diff --git a/source/game/kart/KartComponent.cpp b/source/game/kart/KartComponent.cpp new file mode 100644 index 000000000..952ac05cc --- /dev/null +++ b/source/game/kart/KartComponent.cpp @@ -0,0 +1,20 @@ +#include "KartComponent.hpp" + +namespace Kart { + +void KartComponent::initList() { List_Init(&sList, 4); } + +void KartComponent::setupInList(KartAccessor* accessor) { + KartComponent* component = nullptr; + while ((component = (KartComponent*)List_GetNext(&sList, component))) { + component->mAccessor = accessor; + } +} + +KartComponent::KartComponent() { + mAccessor = nullptr; + + List_Append(&sList, this); +} + +} // namespace Kart diff --git a/source/game/kart/KartComponent.hpp b/source/game/kart/KartComponent.hpp new file mode 100644 index 000000000..d0ab430b6 --- /dev/null +++ b/source/game/kart/KartComponent.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace Kart { + +class KartAccessor; + +class KartComponent { +public: + static void initList(); + + static void setupInList(KartAccessor* accessor); + + KartComponent(); + +private: + static nw4r::ut::List sList; + + KartAccessor* mAccessor; + nw4r::ut::Node mNode; +}; + +} // namespace Kart From 0514989a420cb92f7987ecf65a3bf5c7c91091a6 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Wed, 28 Jul 2021 12:19:59 -0600 Subject: [PATCH 118/477] offsetof macro (closes #74) --- source/egg/core/eggArchive.hpp | 5 +++-- source/egg/core/eggDisposer.hpp | 7 ++++--- source/egg/core/eggDvdFile.cpp | 2 +- source/egg/core/eggDvdFile.hpp | 2 ++ source/egg/core/eggHeap.cpp | 6 ++++-- source/egg/core/eggHeap.hpp | 4 +--- source/game/host_system/ParameterFile.cpp | 2 +- source/game/host_system/ParameterFile.hpp | 2 +- source/game/kart/KartComponent.cpp | 5 ++++- source/platform/cstddef | 3 +++ source/platform/stddef.h | 5 +++++ 11 files changed, 29 insertions(+), 14 deletions(-) create mode 100644 source/platform/cstddef create mode 100644 source/platform/stddef.h diff --git a/source/egg/core/eggArchive.hpp b/source/egg/core/eggArchive.hpp index 8f9c99d31..1ce644351 100644 --- a/source/egg/core/eggArchive.hpp +++ b/source/egg/core/eggArchive.hpp @@ -6,6 +6,7 @@ #pragma once #include +#include #include #include @@ -102,7 +103,7 @@ class Archive : public Disposer // sizeof=60,0x3C mStatus = NOT_LOADED; mArcHandle.reset(); if (!sIsArchiveListInitialized) { - nw4r::ut::List_Init(&sArchiveList, 52); + nw4r::ut::List_Init(&sArchiveList, offsetof(Archive, mLink)); sIsArchiveListInitialized = true; } appendList(this); @@ -129,7 +130,7 @@ class Archive : public Disposer // sizeof=60,0x3C int mRefCount; //!< [+0x14] set to 1 in ct LowArchive mArcHandle; // 0x18 - char _unk[8]; + nw4r::ut::Node mLink; }; } // namespace EGG \ No newline at end of file diff --git a/source/egg/core/eggDisposer.hpp b/source/egg/core/eggDisposer.hpp index 4810c2cf0..c3842e9f9 100644 --- a/source/egg/core/eggDisposer.hpp +++ b/source/egg/core/eggDisposer.hpp @@ -5,6 +5,8 @@ #pragma once +#include + namespace EGG { class Heap; @@ -31,9 +33,8 @@ class Disposer { Heap* mContainHeap; //!< [+0x04] Heap that contains the instance of this //!< disposer. - // All base classes appear to have this, unused. - char _08[4]; // unseen - char _0C[4]; // unseen +public: + nw4r::ut::Node mLink; }; } // namespace EGG diff --git a/source/egg/core/eggDvdFile.cpp b/source/egg/core/eggDvdFile.cpp index f454ae2fc..186f4afb7 100644 --- a/source/egg/core/eggDvdFile.cpp +++ b/source/egg/core/eggDvdFile.cpp @@ -8,7 +8,7 @@ nw4r::ut::List DvdFile::sDvdList; void DvdFile::initialize() { if (!sIsInitialized) { - nw4r::ut::List_Init(&sDvdList, 200); + nw4r::ut::List_Init(&sDvdList, offsetof(DvdFile, mNode)); sIsInitialized = true; } } diff --git a/source/egg/core/eggDvdFile.hpp b/source/egg/core/eggDvdFile.hpp index 336461100..408bbd78c 100644 --- a/source/egg/core/eggDvdFile.hpp +++ b/source/egg/core/eggDvdFile.hpp @@ -86,5 +86,7 @@ class DvdFile : public File { OSMessage _C0; OSThread* _C4; + + nw4r::ut::Node mNode; }; } // namespace EGG diff --git a/source/egg/core/eggHeap.cpp b/source/egg/core/eggHeap.cpp index b42731b4e..11c0030cc 100644 --- a/source/egg/core/eggHeap.cpp +++ b/source/egg/core/eggHeap.cpp @@ -5,6 +5,8 @@ #define HEAP_PRIVATE public +#include + #include #include #include @@ -35,7 +37,7 @@ Thread* Heap::sAllocatableThread; #define SIZE_MB ((float)0x100000) void Heap::initialize() { - nw4r::ut::List_Init(&sHeapList, 32); + nw4r::ut::List_Init(&sHeapList, offsetof(Heap, mNode)); OSInitMutex(&sRootMutex); sIsHeapListInitialized = true; } @@ -46,7 +48,7 @@ Heap::Heap(MEMiHeapHead* pHeap) : Disposer(), mHeapHandle(pHeap) { mFlag = 0; // Initialize child heap linked list. - nw4r::ut::List_Init(&mChildren, 8); + nw4r::ut::List_Init(&mChildren, offsetof(Disposer, mLink)); // The static Heap members (set by initialize()) must be configured first. EGG_ASSERT(sIsHeapListInitialized, "eggHeap.cpp", 63, diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 9baa30ed6..6a0abae2d 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -120,13 +120,11 @@ class Heap : public Disposer { }; u16 mFlag; // 2b implicit pad? - //! [+0x20, +0x24] unseen treeki -- globalLink - u32 _20; - u32 _24; //! @details List of child disposers. //! When Heap::dispose() is called, ~Disposer() will be called for all //! children. + nw4r::ut::Node mNode; //!< [+0x20] nw4r::ut::List mChildren; //!< [+0x28] sizeof=0xC const char* mName; //!< [+0x034] set to "NoName" in ctor diff --git a/source/game/host_system/ParameterFile.cpp b/source/game/host_system/ParameterFile.cpp index 51c9faf06..13cdb85f5 100644 --- a/source/game/host_system/ParameterFile.cpp +++ b/source/game/host_system/ParameterFile.cpp @@ -8,7 +8,7 @@ namespace System { ParameterFile::ParameterFile(const char* path, u32 other) : mPath(path) { mTotalAllocated = 0; _18 = other; - nw4r::ut::List_Init(&mStrings, 0); + nw4r::ut::List_Init(&mStrings, offsetof(StringView, mNode)); } ParameterFile::~ParameterFile() { diff --git a/source/game/host_system/ParameterFile.hpp b/source/game/host_system/ParameterFile.hpp index 7b6e17142..b89a1287c 100644 --- a/source/game/host_system/ParameterFile.hpp +++ b/source/game/host_system/ParameterFile.hpp @@ -35,8 +35,8 @@ class ParameterFile { inline char* data() { return mData; } inline u32 size() const { return mSize; } + nw4r::ut::Node mNode; private: - nw4r::ut::Link m_link; char* mData; u32 mSize; }; diff --git a/source/game/kart/KartComponent.cpp b/source/game/kart/KartComponent.cpp index 952ac05cc..7d0850d6d 100644 --- a/source/game/kart/KartComponent.cpp +++ b/source/game/kart/KartComponent.cpp @@ -1,8 +1,11 @@ #include "KartComponent.hpp" +#include namespace Kart { -void KartComponent::initList() { List_Init(&sList, 4); } +void KartComponent::initList() { + List_Init(&sList, offsetof(KartComponent, mNode)); +} void KartComponent::setupInList(KartAccessor* accessor) { KartComponent* component = nullptr; diff --git a/source/platform/cstddef b/source/platform/cstddef new file mode 100644 index 000000000..51b8c6cc4 --- /dev/null +++ b/source/platform/cstddef @@ -0,0 +1,3 @@ +#pragma once + +#include \ No newline at end of file diff --git a/source/platform/stddef.h b/source/platform/stddef.h new file mode 100644 index 000000000..0769e953d --- /dev/null +++ b/source/platform/stddef.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +#define offsetof(type, m) ((size_t)&(((type*)0)->m)) \ No newline at end of file From 8c1759cc553d58ba1d298147ffb0d35918673d9e Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 28 Jul 2021 21:45:53 +0200 Subject: [PATCH 119/477] Python Restructure (#75) * Move dol.py, rel.py to lib * Move sources.py out of mkwutil * mkwutil.progress * project.py, moved more around * Finish project.py * Fix Python package problems * Update sources.py Co-authored-by: riidefi <34194588+riidefi@users.noreply.github.com> --- .github/workflows/pages.yml | 2 +- .github/workflows/test.yml | 5 +- .gitignore | 1 + README.md | 6 ++ build.py | 10 +-- diff.py | 2 +- mkwutil/gen_asm.py | 60 ++++-------------- mkwutil/gen_lcf.py | 4 +- mkwutil/lib/binary_blob.py | 35 ++++++++++ mkwutil/{ => lib}/dol.py | 0 mkwutil/{ => lib}/ppc_dis.py | 0 mkwutil/{ => lib}/rel.py | 0 mkwutil/{ => lib}/rel_header.bin | Bin mkwutil/{ => lib}/slices.py | 14 ---- mkwutil/{ => lib}/symbols.py | 0 mkwutil/pack_staticr_rel.py | 2 +- mkwutil/{ => progress}/graphic.py | 7 +- mkwutil/{ => progress}/graphic/index.html.j2 | 0 mkwutil/{ => progress}/percent_decompiled.py | 43 ++----------- mkwutil/project.py | 60 ++++++++++++++++++ mkwutil/rel_repack.py | 2 +- mkwutil/sections.py | 2 +- mkwutil/{ => tools}/dump_dol_header.py | 2 +- mkwutil/{ => tools}/dump_elf_segments.py | 0 mkwutil/{ => tools}/dump_symbols.py | 0 mkwutil/{ => tools}/format_symbols.py | 0 mkwutil/verify_main_dol.py | 2 +- setup.py | 3 + mkwutil/sources.py => sources.py | 0 .../slices_test.py => tests/test_slices.py | 16 +---- 30 files changed, 146 insertions(+), 132 deletions(-) create mode 100644 mkwutil/lib/binary_blob.py rename mkwutil/{ => lib}/dol.py (100%) rename mkwutil/{ => lib}/ppc_dis.py (100%) rename mkwutil/{ => lib}/rel.py (100%) rename mkwutil/{ => lib}/rel_header.bin (100%) rename mkwutil/{ => lib}/slices.py (96%) rename mkwutil/{ => lib}/symbols.py (100%) rename mkwutil/{ => progress}/graphic.py (92%) rename mkwutil/{ => progress}/graphic/index.html.j2 (100%) rename mkwutil/{ => progress}/percent_decompiled.py (71%) create mode 100644 mkwutil/project.py rename mkwutil/{ => tools}/dump_dol_header.py (95%) rename mkwutil/{ => tools}/dump_elf_segments.py (100%) rename mkwutil/{ => tools}/dump_symbols.py (100%) rename mkwutil/{ => tools}/format_symbols.py (100%) create mode 100644 setup.py rename mkwutil/sources.py => sources.py (100%) rename mkwutil/slices_test.py => tests/test_slices.py (89%) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 9e5ae89b3..4961ac6bc 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -16,7 +16,7 @@ jobs: run: pip3 install -r requirements.txt - name: Run graphic.py - run: python3 -m mkwutil.graphic + run: python3 -m mkwutil.progress.graphic - name: Deploy uses: JamesIves/github-pages-deploy-action@4.1.4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31682ccc2..6e8bcc3ae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ on: [push, pull_request] name: Test jobs: - build: + test: runs-on: windows-2019 steps: - uses: actions/checkout@v2 @@ -14,6 +14,9 @@ jobs: - name: Install dependencies run: pip3 install -r requirements.txt + + - name: Install mkwutil in editable mode + run: pip3 install -e . - name: Run tests run: pytest -vv diff --git a/.gitignore b/.gitignore index 590c4031c..2949f224e 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ out __pycache__/ /env/ .pytest_cache +*.egg-info tmp *.rel diff --git a/README.md b/README.md index 880290dea..875871c4d 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ Then, each time you open a terminal, enter the venv: pip install -r requirements.txt ``` +### Unit testing + +We use [pytest](https://pytest.org). +`pytest` requires the `mkwutil` package to be installed in editable mode. +Run `pip install -e .` to do that. + ## Building Run `python3 ./build.py` to build the game and verify build authenticity. Final results: diff --git a/build.py b/build.py index 015105f76..d5b7d1bda 100644 --- a/build.py +++ b/build.py @@ -16,8 +16,8 @@ import colorama from termcolor import colored -from mkwutil.sources import SOURCES_DOL, SOURCES_REL -from mkwutil.slices import SliceTable +from sources import SOURCES_DOL, SOURCES_REL +from mkwutil.lib.slices import SliceTable from mkwutil.sections import DOL_SECTIONS from mkwutil.verify_object_file import verify_object_file from mkwutil.gen_lcf import gen_lcf @@ -25,13 +25,15 @@ from mkwutil.pack_staticr_rel import pack_staticr_rel from mkwutil.verify_main_dol import verify_dol from mkwutil.verify_staticr_rel import verify_rel -from mkwutil.percent_decompiled import percent_decompiled +from mkwutil.progress.percent_decompiled import percent_decompiled + +from mkwutil.project import load_dol_slices colorama.init() print_mutex = Lock() # Remember which files are stripped. -dol_slices = SliceTable.load_dol_slices(sections=DOL_SECTIONS) +dol_slices = load_dol_slices(sections=DOL_SECTIONS) stripped_files = set() for _slice in dol_slices: if "strip" in _slice.tags: diff --git a/diff.py b/diff.py index 0c7d4f0c4..28332518b 100644 --- a/diff.py +++ b/diff.py @@ -574,7 +574,7 @@ def search_map_file( # At this time it is recommended to always use -o when running the diff # script as this mode does not make use of the ram-rom conversion. - from mkwutil import dol + from mkwutil.lib import dol maindol = dol.DolBinary(project.myimg) return objfile, maindol.virtual_to_rom(ram) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index 3ba40472c..bdbdbf9e9 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -9,13 +9,14 @@ import jinja2 -from mkwutil.ppc_dis import InlineInstruction, disasm_iter, label_name -from mkwutil.dol import DolBinary -from mkwutil.rel import Rel, dump_staticr -from mkwutil.symbols import Symbol, SymbolsList +from mkwutil.lib.ppc_dis import InlineInstruction, disasm_iter, label_name +from mkwutil.lib.dol import DolBinary +from mkwutil.lib.rel import Rel, dump_staticr +from mkwutil.lib.symbols import Symbol, SymbolsList from mkwutil.sections import DOL_SECTIONS, REL_SECTIONS, REL_SECTION_IDX, Section -from mkwutil.slices import Slice, SliceTable +from mkwutil.lib.slices import Slice, SliceTable +from mkwutil.project import * jinja_env = jinja2.Environment( loader=jinja2.PackageLoader("mkwutil", "gen_asm"), @@ -453,45 +454,6 @@ def __gen_asm(self, section: Section, _slice: Slice): gen = AsmGenerator(data, _slice, SymbolsList(), asm_file) gen.dump_section() - -def __read_symbol_map(symbols_path): - symbols = SymbolsList() - - with open(symbols_path, "r") as file: - symbols.read_from(file) - - return symbols - - raise RuntimeError("Cannot find symbols") - - -def __read_dol(dol_path): - with open(dol_path, "rb") as file: - dol = DolBinary(file) - - return dol - - raise RuntimeError("Cannot find DOL") - - -# Map out slices in DOL. -def __read_enabled_slices(dol, slices_path): - dol_slices = SliceTable(dol.start, dol.stop) - with open(slices_path) as file: - dol_slices.read_from(file) - - return dol_slices.filter(SliceTable.ONLY_ENABLED) - - raise RuntimeError("Cannot find dol_slices.csv") - - -def __read_rel(rel_path): - with open(rel_path, "rb") as file: - return Rel(file) - - raise RuntimeError("Cannot find StaticR.rel") - - def main(): parser = argparse.ArgumentParser( description="Generate ASM blobs and linker object lists." @@ -510,10 +472,10 @@ def main(): args = parser.parse_args() args.asm_dir.mkdir(exist_ok=True) - symbols = __read_symbol_map(args.pack_dir / "symbols.txt") + symbols = read_symbol_map(args.pack_dir / "symbols.txt") - dol = __read_dol(args.binary_dir / "main.dol") - dol_slices = __read_enabled_slices(dol, args.pack_dir / "dol_slices.csv") + dol = read_dol(args.binary_dir / "main.dol") + dol_slices = read_enabled_slices(dol, args.pack_dir / "dol_slices.csv") # Disassemble DOL sections. dol_asm_dir = args.asm_dir / "dol" @@ -523,12 +485,12 @@ def main(): ) dol_gen.run() - rel = __read_rel(args.binary_dir / "StaticR.rel") + rel = read_rel(args.binary_dir / "StaticR.rel") # Dump StaticR.rel segments. rel_bin_dir = args.binary_dir / "rel" dump_staticr(rel, rel_bin_dir) # Map out slices in REL. - rel_slices = SliceTable.load_rel_slices(sections=REL_SECTIONS) + rel_slices = load_rel_slices(sections=REL_SECTIONS) rel_slices.filter(SliceTable.ONLY_ENABLED) # Disassemble REL sections. rel_asm_dir = args.asm_dir / "rel" diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index 7164de1cd..83246d945 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -5,8 +5,8 @@ from elftools.elf.elffile import ELFFile -from mkwutil.slices import SliceTable -from mkwutil.symbols import Symbol, SymbolsList +from mkwutil.lib.slices import SliceTable +from mkwutil.lib.symbols import Symbol, SymbolsList MATCH_UNK = re.compile(r"^unk_([0-9a-f]{8})$") diff --git a/mkwutil/lib/binary_blob.py b/mkwutil/lib/binary_blob.py new file mode 100644 index 000000000..c42dc7e26 --- /dev/null +++ b/mkwutil/lib/binary_blob.py @@ -0,0 +1,35 @@ +import re +from typing import Generator + +from mkwutil.lib.slices import * + +from elftools.elf.elffile import ELFFile, Section as ELFSection + +MATCH_BINARY_BLOB = re.compile( + rb"BINARY_BLOB: (.+)\t(0x[0-9a-f]{8})\t(0x[0-9a-f]{8})\n" +) + +def __load_binary_blob_slices(elf_file) -> Generator[Slice, None, None]: + elf = ELFFile(elf_file) + blobs: ELFSection = elf.get_section_by_name("binary_blobs") + if not blobs: + return + for match in MATCH_BINARY_BLOB.finditer(blobs.data()): + name = match.group(1).decode("ascii") + start, stop = int(match.group(2), 16), int(match.group(3), 16) + yield Slice(start, stop, name) + + +def load_binary_blob_slices(elf_path: Path) -> list[Slice]: + """Loads all inline assembly slices from the ELF.""" + with open(elf_path, "rb") as file: + return list(__load_binary_blob_slices(file)) + + +def mask_binary_blobs(main_slices: SliceTable, blob_slices: list[Slice]) -> None: + """Inserts a gap into the given slice table for each blob slice. + This is used to remove all inline ASM slices from the slice table, leaving only actual C/C++ code.""" + for _slice in blob_slices: + # print("Ignoring", _slice) + main_slices.remove(_slice=_slice) + # print(main_slices) diff --git a/mkwutil/dol.py b/mkwutil/lib/dol.py similarity index 100% rename from mkwutil/dol.py rename to mkwutil/lib/dol.py diff --git a/mkwutil/ppc_dis.py b/mkwutil/lib/ppc_dis.py similarity index 100% rename from mkwutil/ppc_dis.py rename to mkwutil/lib/ppc_dis.py diff --git a/mkwutil/rel.py b/mkwutil/lib/rel.py similarity index 100% rename from mkwutil/rel.py rename to mkwutil/lib/rel.py diff --git a/mkwutil/rel_header.bin b/mkwutil/lib/rel_header.bin similarity index 100% rename from mkwutil/rel_header.bin rename to mkwutil/lib/rel_header.bin diff --git a/mkwutil/slices.py b/mkwutil/lib/slices.py similarity index 96% rename from mkwutil/slices.py rename to mkwutil/lib/slices.py index 47c27e713..1335783f1 100644 --- a/mkwutil/slices.py +++ b/mkwutil/lib/slices.py @@ -103,20 +103,6 @@ def load_path(file_path, sections=None): this.set_sections(sections) return this - @staticmethod - def load_dol_slices(sections=None) -> "SliceTable": - """Loads pack/dol_slices.csv in the default DOL region.""" - return SliceTable.load_path( - Path(__file__).parent / ".." / "pack" / "dol_slices.csv", sections=sections - ) - - @staticmethod - def load_rel_slices(sections=None) -> "SliceTable": - """Loads pack/rel_slices.csv in the default DOL region.""" - return SliceTable.load_path( - Path(__file__).parent / ".." / "pack" / "rel_slices.csv", sections=sections - ) - def __contains__(self, _slice: Slice) -> bool: """Returns whether the range of a slice lies within the table. The table is not actually checked for membership.""" diff --git a/mkwutil/symbols.py b/mkwutil/lib/symbols.py similarity index 100% rename from mkwutil/symbols.py rename to mkwutil/lib/symbols.py diff --git a/mkwutil/pack_staticr_rel.py b/mkwutil/pack_staticr_rel.py index 44e15ba4b..7ef771cc1 100644 --- a/mkwutil/pack_staticr_rel.py +++ b/mkwutil/pack_staticr_rel.py @@ -8,7 +8,7 @@ from elftools.elf.elffile import ELFFile -from .rel import Rel, RelSection +from .lib.rel import Rel, RelSection R_PPC_NONE = 0 diff --git a/mkwutil/graphic.py b/mkwutil/progress/graphic.py similarity index 92% rename from mkwutil/graphic.py rename to mkwutil/progress/graphic.py index df0b1378d..46dcc85d8 100644 --- a/mkwutil/graphic.py +++ b/mkwutil/progress/graphic.py @@ -8,7 +8,8 @@ import jinja2 from mkwutil.sections import DOL_LIBS, DOL_SECTIONS -from mkwutil.slices import Slice, SliceTable +from mkwutil.lib.slices import Slice, SliceTable +from mkwutil.project import load_dol_slices random.seed("OwO") @@ -20,7 +21,7 @@ jinja_env = jinja2.Environment( - loader=jinja2.PackageLoader("mkwutil", "graphic"), + loader=jinja2.PackageLoader("mkwutil", "progress/graphic"), autoescape=jinja2.select_autoescape(), ) @@ -82,7 +83,7 @@ def percent_decomp_stats(slices: SliceTable) -> None: def standard_boxes(): - slices = SliceTable.load_dol_slices(sections=DOL_SECTIONS) + slices = load_dol_slices(sections=DOL_SECTIONS) return map(Box.from_slice, slices) diff --git a/mkwutil/graphic/index.html.j2 b/mkwutil/progress/graphic/index.html.j2 similarity index 100% rename from mkwutil/graphic/index.html.j2 rename to mkwutil/progress/graphic/index.html.j2 diff --git a/mkwutil/percent_decompiled.py b/mkwutil/progress/percent_decompiled.py similarity index 71% rename from mkwutil/percent_decompiled.py rename to mkwutil/progress/percent_decompiled.py index 335803e81..3870d9ab6 100644 --- a/mkwutil/percent_decompiled.py +++ b/mkwutil/progress/percent_decompiled.py @@ -3,14 +3,15 @@ import re from typing import Generator -from elftools.elf.elffile import ELFFile, Section as ELFSection import pytablewriter from pytablewriter.style import Style from termcolor import colored -from mkwutil.slices import Slice, SliceTable +from mkwutil.lib.slices import Slice, SliceTable from mkwutil.sections import Section, REL_SECTIONS, DOL_SECTIONS, DOL_LIBS +from mkwutil.project import * + def simple_count(slices: SliceTable) -> tuple[int, int]: """Returns the number of code and data bytes part of named slices.""" @@ -62,46 +63,14 @@ def get_progress(slices, filter): return progress -MATCH_BINARY_BLOB = re.compile( - rb"BINARY_BLOB: (.+)\t(0x[0-9a-f]{8})\t(0x[0-9a-f]{8})\n" -) - - -def __load_binary_blob_slices(elf_file) -> Generator[Slice, None, None]: - elf = ELFFile(elf_file) - blobs: ELFSection = elf.get_section_by_name("binary_blobs") - if not blobs: - return - for match in MATCH_BINARY_BLOB.finditer(blobs.data()): - name = match.group(1).decode("ascii") - start, stop = int(match.group(2), 16), int(match.group(3), 16) - yield Slice(start, stop, name) - - -def load_binary_blob_slices(elf_path: Path) -> list[Slice]: - """Loads all inline assembly slices from the ELF.""" - with open(elf_path, "rb") as file: - return list(__load_binary_blob_slices(file)) - - -def mask_binary_blobs(main_slices: SliceTable, blob_slices: list[Slice]) -> None: - """Inserts a gap into the given slice table for each blob slice. - This is used to remove all inline ASM slices from the slice table, leaving only actual C/C++ code.""" - for _slice in blob_slices: - # print("Ignoring", _slice) - main_slices.remove(_slice=_slice) - # print(main_slices) - def percent_decompiled(dir="."): dir = Path(dir) matrix = [] # DOL progress. - dol_slices = SliceTable.load_dol_slices() - dol_blob_slices = load_binary_blob_slices( - dir / "artifacts" / "target" / "pal" / "main.elf" - ) + dol_slices = load_dol_slices() + dol_blob_slices = load_dol_binary_blob_slices(dir) mask_binary_blobs(dol_slices, dol_blob_slices) dol_progress = simple_count(dol_slices) dol_total = binary_total(DOL_SECTIONS) @@ -113,7 +82,7 @@ def percent_decompiled(dir="."): lib_total = (len(lib), None) matrix.append(analyze("> " + lib.name, lib_progress, lib_total)) # REL progress. - rel_slices = SliceTable.load_rel_slices() + rel_slices = load_rel_slices() rel_progress = simple_count(rel_slices) rel_total = binary_total(REL_SECTIONS) matrix.append(analyze("REL", rel_progress, rel_total)) diff --git a/mkwutil/project.py b/mkwutil/project.py new file mode 100644 index 000000000..7bcb55cdf --- /dev/null +++ b/mkwutil/project.py @@ -0,0 +1,60 @@ +from mkwutil.lib.binary_blob import * +from mkwutil.lib.dol import DolBinary +from mkwutil.lib.rel import Rel +from mkwutil.lib.slices import SliceTable +from mkwutil.lib.symbols import SymbolsList +from pathlib import Path + +def load_dol_slices(sections=None) -> "SliceTable": + """Loads pack/dol_slices.csv in the default DOL region.""" + return SliceTable.load_path( + Path(__file__).parent / ".." / "pack" / "dol_slices.csv", sections=sections + ) + +def load_rel_slices(sections=None) -> "SliceTable": + """Loads pack/rel_slices.csv in the default DOL region.""" + return SliceTable.load_path( + Path(__file__).parent / ".." / "pack" / "rel_slices.csv", sections=sections + ) + +def load_dol_binary_blob_slices(dir_="."): + dir_ = Path(dir_) + + path = dir_ / "artifacts" / "target" / "pal" / "main.elf" + return load_binary_blob_slices(path) + + +def read_symbol_map(symbols_path): + symbols = SymbolsList() + + with open(symbols_path, "r") as file: + symbols.read_from(file) + + return symbols + + raise RuntimeError("Cannot find symbols") + + +def read_dol(dol_path): + with open(dol_path, "rb") as file: + return DolBinary(file) + + raise RuntimeError("Cannot find DOL") + + +# Map out slices in DOL. +def read_enabled_slices(dol, slices_path): + dol_slices = SliceTable(dol.start, dol.stop) + with open(slices_path) as file: + dol_slices.read_from(file) + + return dol_slices.filter(SliceTable.ONLY_ENABLED) + + raise RuntimeError("Cannot find dol_slices.csv") + + +def read_rel(rel_path): + with open(rel_path, "rb") as file: + return Rel(file) + + raise RuntimeError("Cannot find StaticR.rel") diff --git a/mkwutil/rel_repack.py b/mkwutil/rel_repack.py index af91f2ee9..727f4f92b 100644 --- a/mkwutil/rel_repack.py +++ b/mkwutil/rel_repack.py @@ -5,7 +5,7 @@ import argparse -from mkw.rel import Rel, dump_staticr, reconstruct_staticr +from mkw.lib.rel import Rel, dump_staticr, reconstruct_staticr parser = argparse.ArgumentParser(description="Repack StaticR.Rel") parser.add_argument( diff --git a/mkwutil/sections.py b/mkwutil/sections.py index 05a6f9e42..a7ce3bd43 100644 --- a/mkwutil/sections.py +++ b/mkwutil/sections.py @@ -1,6 +1,6 @@ from dataclasses import dataclass -from mkwutil.slices import Slice +from mkwutil.lib.slices import Slice @dataclass diff --git a/mkwutil/dump_dol_header.py b/mkwutil/tools/dump_dol_header.py similarity index 95% rename from mkwutil/dump_dol_header.py rename to mkwutil/tools/dump_dol_header.py index 5b5595c0a..198b76954 100644 --- a/mkwutil/dump_dol_header.py +++ b/mkwutil/tools/dump_dol_header.py @@ -2,7 +2,7 @@ from pathlib import Path import struct -from mkwutil.dol import DolBinary +from mkwutil.lib.dol import DolBinary def main(): diff --git a/mkwutil/dump_elf_segments.py b/mkwutil/tools/dump_elf_segments.py similarity index 100% rename from mkwutil/dump_elf_segments.py rename to mkwutil/tools/dump_elf_segments.py diff --git a/mkwutil/dump_symbols.py b/mkwutil/tools/dump_symbols.py similarity index 100% rename from mkwutil/dump_symbols.py rename to mkwutil/tools/dump_symbols.py diff --git a/mkwutil/format_symbols.py b/mkwutil/tools/format_symbols.py similarity index 100% rename from mkwutil/format_symbols.py rename to mkwutil/tools/format_symbols.py diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py index b6179fa3d..1fb2e386b 100644 --- a/mkwutil/verify_main_dol.py +++ b/mkwutil/verify_main_dol.py @@ -8,7 +8,7 @@ from pathlib import Path import sys -from .dol import DolBinary +from .lib.dol import DolBinary def format_segment(name, at, at2, want_size, have_size, tag): diff --git a/setup.py b/setup.py new file mode 100644 index 000000000..87f9029c7 --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup, find_packages + +setup(name="mkwutil", packages=find_packages()) diff --git a/mkwutil/sources.py b/sources.py similarity index 100% rename from mkwutil/sources.py rename to sources.py diff --git a/mkwutil/slices_test.py b/tests/test_slices.py similarity index 89% rename from mkwutil/slices_test.py rename to tests/test_slices.py index ecc70377a..4645e18ac 100644 --- a/mkwutil/slices_test.py +++ b/tests/test_slices.py @@ -1,7 +1,7 @@ from pathlib import Path import pytest -from mkwutil.slices import ObjectSlices, Slice, SliceTable +from mkwutil.lib.slices import Slice, SliceTable def test_slice_compare(): @@ -146,17 +146,3 @@ def test_slice_table_remove_8(): { 00000002..00000003 slice } { 00000003..00000006 } ]""" - -def test_dol_slices(): - table = SliceTable.load_dol_slices() - assert isinstance(table, SliceTable) - objs = table.object_slices() - assert isinstance(objs, ObjectSlices) - assert len(objs) > 10 - - -def test_rel_slices(): - table = SliceTable.load_rel_slices() - assert isinstance(table, SliceTable) - objs = table.object_slices() - assert isinstance(objs, ObjectSlices) From d8ea64b9320a9467410793e1a837e876d4410c68 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 28 Jul 2021 23:09:47 +0200 Subject: [PATCH 120/477] CI: Remove dependency on 7-zip (#77) Closes #47 --- .github/workflows/build.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f67216b9..25da129ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,19 +17,24 @@ jobs: - name: Extract tooling env: - TOOLS_URL: 'http://163.172.81.216:12369/tools.7z' + TOOLS_URL: 'https://cdn.discordapp.com/attachments/767477506861170710/870047437757239356/tools.zip' run: | # Download tooling - Invoke-WebRequest "$Env:TOOLS_URL" -OutFile .\tools.7z - $hash = (Get-FileHash '.\tools.7z').Hash - if ($hash -ne '0AC8F3CE1AA968EB83400D080519472EC590FBF613E5D55A204D6CF946012FAB') + Invoke-WebRequest "$Env:TOOLS_URL" -OutFile .\tools.zip + $hash = (Get-FileHash '.\tools.zip').Hash + if ($hash -ne '3FB4330838A6FAC6D35BE9AFDF486EF1F0760E0AA6AE1B4A7CE37802BF1EC7DD') { echo "Invalid hash: $hash" exit 1 } - # Extract using 7-zip - choco install 7zip - 7z.exe x tools.7z -aoa -otools/ + # Extract + Expand-Archive tools.zip + # Fix paths (tools/tools => tools) + Move-Item '.\tools' '.\tools2' + Move-Item '.\tools2\tools' '.\tools' + # Move blobs + Move-Item '.\tools\blobs\main.dol' '.\artifacts\orig\pal\main.dol' + Move-Item '.\tools\blobs\StaticR.rel' '.\artifacts\orig\pal\StaticR.rel' - name: Build mkw shell: bash From 9ea68391121d3dca971a1c787de8b2de264e0b51 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 28 Jul 2021 23:29:38 +0200 Subject: [PATCH 121/477] CI: Setup venv and cache (#78) Improves build speed by about ~20s by caching the Python venv. --- .github/workflows/build.yml | 14 +++++++++++--- .github/workflows/pages.yml | 12 ++++++++++-- .github/workflows/test.yml | 16 ++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25da129ef..e07dbabed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,8 +12,16 @@ jobs: python-version: '3.x' architecture: 'x64' - - name: Install dependencies - run: pip3 install -r requirements.txt + - uses: syphar/restore-virtualenv@v1 + id: cache-virtualenv + with: + requirement_files: requirements.txt + + - uses: syphar/restore-pip-download-cache@v1 + if: steps.cache-virtualenv.outputs.cache-hit != 'true' + + - run: pip install -r requirements.txt + if: steps.cache-virtualenv.outputs.cache-hit != 'true' - name: Extract tooling env: @@ -38,4 +46,4 @@ jobs: - name: Build mkw shell: bash - run: python3 ./build.py + run: python ./build.py diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4961ac6bc..f4a96d8a4 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -12,8 +12,16 @@ jobs: python-version: '3.x' architecture: 'x64' - - name: Install dependencies - run: pip3 install -r requirements.txt + - uses: syphar/restore-virtualenv@v1 + id: cache-virtualenv + with: + requirement_files: requirements.txt + + - uses: syphar/restore-pip-download-cache@v1 + if: steps.cache-virtualenv.outputs.cache-hit != 'true' + + - run: pip install -r requirements.txt + if: steps.cache-virtualenv.outputs.cache-hit != 'true' - name: Run graphic.py run: python3 -m mkwutil.progress.graphic diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6e8bcc3ae..3b0489a1c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,11 +12,19 @@ jobs: python-version: '3.x' architecture: 'x64' - - name: Install dependencies - run: pip3 install -r requirements.txt - + - uses: syphar/restore-virtualenv@v1 + id: cache-virtualenv + with: + requirement_files: requirements.txt + + - uses: syphar/restore-pip-download-cache@v1 + if: steps.cache-virtualenv.outputs.cache-hit != 'true' + + - run: pip install -r requirements.txt + if: steps.cache-virtualenv.outputs.cache-hit != 'true' + - name: Install mkwutil in editable mode - run: pip3 install -e . + run: pip install -e . - name: Run tests run: pytest -vv From c96650ba5ca012da1bfe9fe4a1ec0529ee3d081e Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Wed, 28 Jul 2021 23:50:25 +0200 Subject: [PATCH 122/477] CI: Run gen_asm (#80) --- .github/workflows/build.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e07dbabed..4a5a0e290 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,6 +43,21 @@ jobs: # Move blobs Move-Item '.\tools\blobs\main.dol' '.\artifacts\orig\pal\main.dol' Move-Item '.\tools\blobs\StaticR.rel' '.\artifacts\orig\pal\StaticR.rel' + # Clean up + Remove-Item '.\tools.zip' + Remove-Item -LiteralPath 'tools2' -Force -Recurse + + - name: Re-generate assembly + run: python -m mkwutil.gen_asm + + # https://stackoverflow.com/a/56389437 + - name: Verify assembly still the same + run: | + $ChangedFiles = $(git status --porcelain | Measure-Object | Select-Object -expand Count) + if ($ChangedFiles -gt 0) { + git status --porcelain + throw "Found $ChangedFiles changed files! Did you run gen_asm?" + } - name: Build mkw shell: bash From 258350b69c7302b388d52ef3b28a975341245e9c Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Thu, 29 Jul 2021 00:49:37 +0200 Subject: [PATCH 123/477] Remove asm from tree (#82) --- .github/workflows/build.yml | 18 ++++++-------- .gitignore | 7 +++--- README.md | 1 - build.py | 11 ++++++++- mkwutil/gen_asm.py | 49 +++++++++++++++++-------------------- 5 files changed, 44 insertions(+), 42 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4a5a0e290..55127b75e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,17 +47,15 @@ jobs: Remove-Item '.\tools.zip' Remove-Item -LiteralPath 'tools2' -Force -Recurse - - name: Re-generate assembly - run: python -m mkwutil.gen_asm + - uses: actions/cache@v2 + with: + path: asm/dol + key: asm-dol - # https://stackoverflow.com/a/56389437 - - name: Verify assembly still the same - run: | - $ChangedFiles = $(git status --porcelain | Measure-Object | Select-Object -expand Count) - if ($ChangedFiles -gt 0) { - git status --porcelain - throw "Found $ChangedFiles changed files! Did you run gen_asm?" - } + - uses: actions/cache@v2 + with: + path: asm/rel + key: asm-rel - name: Build mkw shell: bash diff --git a/.gitignore b/.gitignore index 2949f224e..1b03f4115 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,10 @@ /pack/dol.lcf /pack/rel.lcf +# Auto-generated assembly +/asm/dol/ +/asm/rel/ + # Executables *.exe *.dll @@ -58,9 +62,6 @@ tmp *.map out.html -# Compiler tools bundle -tools.7z - # Editors .vscode/ *.bndb diff --git a/README.md b/README.md index 875871c4d..654d15119 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,6 @@ The dead-stripping feature can be re-enabled by: - [pack/dol_slices.csv](./pack/dol_slices.csv) - [pack/rel_slices.csv](./pack/rel_slices.csv) - Entries must be sorted in the spreadsheet (current limitation). -- After modifying slices, run `python3 -m mkwutil.gen_asm`. - Add your new build target to `mkwutil/sources.py`. - Run `build.py`. diff --git a/build.py b/build.py index d5b7d1bda..a91a94981 100644 --- a/build.py +++ b/build.py @@ -3,6 +3,7 @@ """ +import argparse from itertools import chain import os import os.path @@ -26,9 +27,17 @@ from mkwutil.verify_main_dol import verify_dol from mkwutil.verify_staticr_rel import verify_rel from mkwutil.progress.percent_decompiled import percent_decompiled - +from mkwutil.gen_asm import gen_asm from mkwutil.project import load_dol_slices + +parser = argparse.ArgumentParser(description="Build main.dol and StaticR.rel.") +parser.add_argument("--regen_asm", action="store_true", help="Regenerate all ASM") +args = parser.parse_args() +# Start by running gen_asm. +gen_asm(args.regen_asm) + + colorama.init() print_mutex = Lock() diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index bdbdbf9e9..dff2674e8 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -454,52 +454,47 @@ def __gen_asm(self, section: Section, _slice: Slice): gen = AsmGenerator(data, _slice, SymbolsList(), asm_file) gen.dump_section() -def main(): - parser = argparse.ArgumentParser( - description="Generate ASM blobs and linker object lists." - ) - parser.add_argument("--asm_dir", type=Path, default="./asm", help="Path to ASM dir") - parser.add_argument( - "--pack_dir", type=Path, default="./pack", help="Path to link instructions dir" - ) - parser.add_argument( - "--binary_dir", - type=Path, - default="./artifacts/orig/pal", - help="Binary containing main.dol and StaticR.rel", - ) - parser.add_argument("--regen_asm", action="store_true", help="Regenerate all ASM") - args = parser.parse_args() - args.asm_dir.mkdir(exist_ok=True) - symbols = read_symbol_map(args.pack_dir / "symbols.txt") +def gen_asm(regen_asm=False): + asm_dir = Path("./asm") + pack_dir = Path("./pack") + binary_dir = Path("./artifacts/orig/pal") - dol = read_dol(args.binary_dir / "main.dol") - dol_slices = read_enabled_slices(dol, args.pack_dir / "dol_slices.csv") + asm_dir.mkdir(exist_ok=True) + + symbols = read_symbol_map(pack_dir / "symbols.txt") + + dol = read_dol(binary_dir / "main.dol") + dol_slices = read_enabled_slices(dol, pack_dir / "dol_slices.csv") # Disassemble DOL sections. - dol_asm_dir = args.asm_dir / "dol" + dol_asm_dir = asm_dir / "dol" dol_asm_dir.mkdir(exist_ok=True) dol_gen = DOLSrcGenerator( - dol_slices, dol, symbols, dol_asm_dir, args.pack_dir, args.regen_asm + dol_slices, dol, symbols, dol_asm_dir, pack_dir, regen_asm ) dol_gen.run() - rel = read_rel(args.binary_dir / "StaticR.rel") + rel = read_rel(binary_dir / "StaticR.rel") # Dump StaticR.rel segments. - rel_bin_dir = args.binary_dir / "rel" + rel_bin_dir = binary_dir / "rel" dump_staticr(rel, rel_bin_dir) # Map out slices in REL. rel_slices = load_rel_slices(sections=REL_SECTIONS) rel_slices.filter(SliceTable.ONLY_ENABLED) # Disassemble REL sections. - rel_asm_dir = args.asm_dir / "rel" + rel_asm_dir = asm_dir / "rel" rel_asm_dir.mkdir(exist_ok=True) rel_gen = RELSrcGenerator( - rel_slices, rel, rel_asm_dir, rel_bin_dir, args.pack_dir, args.regen_asm + rel_slices, rel, rel_asm_dir, rel_bin_dir, pack_dir, regen_asm ) rel_gen.run() if __name__ == "__main__": - main() + parser = argparse.ArgumentParser( + description="Generate ASM blobs and linker object lists." + ) + parser.add_argument("--regen_asm", action="store_true", help="Regenerate all ASM") + args = parser.parse_args() + gen_asm(args.regen_asm) From fd650fe01160e83c8b257eecb238c8a47090b1e4 Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Thu, 29 Jul 2021 12:27:42 +0200 Subject: [PATCH 124/477] Fix capitalization of some gamespy file names (#84) This was making the build fail on case-sensitive systems such as Linux. --- pack/dol.lcf.j2 | 2 +- pack/dol_slices.csv | 32 ++++++++++++++++---------------- sources.py | 32 ++++++++++++++++---------------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pack/dol.lcf.j2 b/pack/dol.lcf.j2 index 422b26b8e..4a5a8fce1 100644 --- a/pack/dol.lcf.j2 +++ b/pack/dol.lcf.j2 @@ -356,7 +356,7 @@ qr2_buffer_addA qr2_parse_queryA qr2_check_queries_indata -// qr2RegKeys +// qr2regkeys qr2_internal_is_master_only_key qr2_register_keyA diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 5e12df268..f2b2f9166 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -16,21 +16,21 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin ,1,source/gamespy/common/gsCore.c,,,,,,,0x800f3c08,0x800f41dc,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/common/gsUdpEngine.c,,,,,,,0x800f489c,0x800f5a6c,,,,,,,,,0x802f2440,0x802f247c,,,,,,,, 1,1,source/gamespy/common/gsXML.c,,,,,,,0x800f5a6c,0x800fb828,,,,,,,0x8027ad80,0x8027af18,,,0x803850a0,0x80385105,,,,,, -1,1,source/gamespy/gp/gp.c,,,,,,,0x800fb828,0x800fc7d4,,,,,,,0x8027af18,0x8027b289,,,0x80385108,0x80385118,,,,,, -1,1,source/gamespy/gp/gpi.c,,,,,,,0x800fc7d4,0x800fd160,,,,,,,0x8027b290,0x8027b30f,,,0x80385118,0x80385150,,,,,, -1,1,source/gamespy/gp/gpiBuddy.c,,,,,,,0x800fd160,0x800fee90,,,,,,,0x8027b310,0x8027b453,,,0x80385150,0x803851ea,,,,,, -1,1,source/gamespy/gp/gpiBuffer.c,,,,,,,0x800fee90,0x800ff8c4,,,,,,,0x8027b458,0x8027b4ba,,,0x803851f0,0x80385206,,,,,, -1,1,source/gamespy/gp/gpiCallback.c,,,,,,,0x800ff8c4,0x800ffe28,,,,,,,0x8027b4c0,0x8027b4cf,,,,,,,,,, -1,1,source/gamespy/gp/gpiConnect.c,,,,,,,0x800ffe28,0x80101470,,,,,,,0x8027b4d0,0x8027b876,,,0x80385208,0x8038529b,,,,,, -1,1,source/gamespy/gp/gpiInfo.c,,,,,,,0x80101470,0x80103908,,,,,,,0x8027b878,0x8027bbce,,,0x803852a0,0x80385355,,,0x80388470,0x80388474,, -1,1,source/gamespy/gp/gpiKeys.c,,,,,,,0x80103908,0x80103f70,,,,,,,0x8027bbd0,0x8027bc11,,,0x80385358,0x8038535a,,,,,, -1,1,source/gamespy/gp/gpiOperation.c,,,,,,,0x80103f70,0x80104648,,,,,,,0x8027bc18,0x8027bc60,,,,,,,,,, -1,1,source/gamespy/gp/gpiPeer.c,,,,,,,0x80104648,0x80105d54,,,,,,,0x8027bc60,0x8027bd2a,,,0x80385360,0x803853b9,,,,,, -1,1,source/gamespy/gp/gpiProfile.c,,,,,,,0x80105d54,0x8010669c,,,,,,,0x8027bd30,0x8027bee5,,,0x803853c0,0x803853ce,,,,,, -1,1,source/gamespy/gp/gpiSearch.c,,,,,,,0x8010669c,0x80108b38,,,,,,,0x8027bee8,0x8027c204,,,0x803853d0,0x803854bb,,,,,, -1,1,source/gamespy/gp/gpiTransfer.c,,,,,,,0x80108b38,0x80108c20,,,,,,,0x8027c208,0x8027c229,,,0x803854c0,0x803854cb,,,,,, -1,1,source/gamespy/gp/gpiUnique.c,,,,,,,0x80108c20,0x80108e78,,,,,,,0x8027c230,0x8027c26f,,,0x803854d0,0x803854dd,,,,,, -1,1,source/gamespy/gp/gpiUtility.c,,,,,,,0x80108e78,0x8010945c,,,,,,,0x8027c270,0x8027c2c5,,,0x803854e0,0x803854f8,,,,,, +1,1,source/gamespy/GP/gp.c,,,,,,,0x800fb828,0x800fc7d4,,,,,,,0x8027af18,0x8027b289,,,0x80385108,0x80385118,,,,,, +1,1,source/gamespy/GP/gpi.c,,,,,,,0x800fc7d4,0x800fd160,,,,,,,0x8027b290,0x8027b30f,,,0x80385118,0x80385150,,,,,, +1,1,source/gamespy/GP/gpiBuddy.c,,,,,,,0x800fd160,0x800fee90,,,,,,,0x8027b310,0x8027b453,,,0x80385150,0x803851ea,,,,,, +1,1,source/gamespy/GP/gpiBuffer.c,,,,,,,0x800fee90,0x800ff8c4,,,,,,,0x8027b458,0x8027b4ba,,,0x803851f0,0x80385206,,,,,, +1,1,source/gamespy/GP/gpiCallback.c,,,,,,,0x800ff8c4,0x800ffe28,,,,,,,0x8027b4c0,0x8027b4cf,,,,,,,,,, +1,1,source/gamespy/GP/gpiConnect.c,,,,,,,0x800ffe28,0x80101470,,,,,,,0x8027b4d0,0x8027b876,,,0x80385208,0x8038529b,,,,,, +1,1,source/gamespy/GP/gpiInfo.c,,,,,,,0x80101470,0x80103908,,,,,,,0x8027b878,0x8027bbce,,,0x803852a0,0x80385355,,,0x80388470,0x80388474,, +1,1,source/gamespy/GP/gpiKeys.c,,,,,,,0x80103908,0x80103f70,,,,,,,0x8027bbd0,0x8027bc11,,,0x80385358,0x8038535a,,,,,, +1,1,source/gamespy/GP/gpiOperation.c,,,,,,,0x80103f70,0x80104648,,,,,,,0x8027bc18,0x8027bc60,,,,,,,,,, +1,1,source/gamespy/GP/gpiPeer.c,,,,,,,0x80104648,0x80105d54,,,,,,,0x8027bc60,0x8027bd2a,,,0x80385360,0x803853b9,,,,,, +1,1,source/gamespy/GP/gpiProfile.c,,,,,,,0x80105d54,0x8010669c,,,,,,,0x8027bd30,0x8027bee5,,,0x803853c0,0x803853ce,,,,,, +1,1,source/gamespy/GP/gpiSearch.c,,,,,,,0x8010669c,0x80108b38,,,,,,,0x8027bee8,0x8027c204,,,0x803853d0,0x803854bb,,,,,, +1,1,source/gamespy/GP/gpiTransfer.c,,,,,,,0x80108b38,0x80108c20,,,,,,,0x8027c208,0x8027c229,,,0x803854c0,0x803854cb,,,,,, +1,1,source/gamespy/GP/gpiUnique.c,,,,,,,0x80108c20,0x80108e78,,,,,,,0x8027c230,0x8027c26f,,,0x803854d0,0x803854dd,,,,,, +1,1,source/gamespy/GP/gpiUtility.c,,,,,,,0x80108e78,0x8010945c,,,,,,,0x8027c270,0x8027c2c5,,,0x803854e0,0x803854f8,,,,,, 1,1,source/gamespy/gt2/gt2Auth.c,,,,,,,0x8010945c,0x80109820,,,,,,,0x8027c2c8,0x8027c2e9,,,,,,,,,, 1,1,source/gamespy/gt2/gt2Buffer.c,,,,,,,0x80109820,0x801099c4,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/gt2/gt2Callback.c,,,,,,,0x801099c4,0x8010a244,,,,,,,,,,,,,,,,,, @@ -39,7 +39,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,1,source/gamespy/gt2/gt2Socket.c,,,,,,,0x8010de30,0x8010e9c0,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/gt2/gt2Utility.c,,,,,,,0x8010e9c0,0x8010ecac,,,,,,,,,0x802f3480,0x802f34b0,0x80385508,0x80385519,0x80386360,0x80386364,,,, 1,1,source/gamespy/qr2/qr2.c,,,,,,,0x8010ecac,0x8011156c,,,,,,,0x8027c2f0,0x8027d21b,0x802f34c0,0x802f3624,0x80385520,0x8038554a,0x80386368,0x8038636c,0x80388478,0x8038847e,, -1,1,source/gamespy/qr2/qr2RegKeys.c,,,,,,,0x8011156c,0x801115fc,,,,,,,0x8027d220,0x8027d6f8,,,0x80385550,0x803855c7,,,,,, +1,1,source/gamespy/qr2/qr2regkeys.c,,,,,,,0x8011156c,0x801115fc,,,,,,,0x8027d220,0x8027d6f8,,,0x80385550,0x803855c7,,,,,, 1,1,source/gamespy/ghttp/ghttpBuffer.c,,,,,,,0x801115fc,0x80111de8,,,,,,,,,,,0x803855c8,0x803855d3,,,,,, 1,,source/gamespy/ghttp/ghttpCallbacks.c,,,,,,,0x80111f0c,0x8011202c,,,,,,,,,,,,,,,,,, 1,1,source/gamespy/ghttp/ghttpCommon.c,,,,,,,0x8011202c,0x8011248c,,,,,,,,,,,0x803855d8,0x803855e0,0x80386370,0x80386378,,,, diff --git a/sources.py b/sources.py index 197c91cb9..63dd760bc 100644 --- a/sources.py +++ b/sources.py @@ -84,21 +84,21 @@ class Source: Source(src="source/gamespy/common/gsCore.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/common/gsUdpEngine.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/common/gsXML.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gp.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpi.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiBuddy.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiBuffer.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiCallback.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiConnect.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiInfo.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiKeys.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiOperation.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiPeer.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiProfile.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiSearch.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiTransfer.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiUnique.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/gp/gpiUtility.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gp.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpi.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiBuddy.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiBuffer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiCallback.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiConnect.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiInfo.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiKeys.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiOperation.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiPeer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiProfile.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiSearch.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiTransfer.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiUnique.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/GP/gpiUtility.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/gt2/gt2Auth.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/gt2/gt2Buffer.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/gt2/gt2Callback.c", cc='4199_60831', opts=SPY_OPTS), @@ -107,7 +107,7 @@ class Source: Source(src="source/gamespy/gt2/gt2Socket.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/gt2/gt2Utility.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/qr2/qr2.c", cc='4199_60831', opts=SPY_OPTS), - Source(src="source/gamespy/qr2/qr2RegKeys.c", cc='4199_60831', opts=SPY_OPTS), + Source(src="source/gamespy/qr2/qr2regkeys.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/ghttp/ghttpBuffer.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/ghttp/ghttpCallbacks.c", cc='4199_60831', opts=SPY_OPTS), Source(src="source/gamespy/ghttp/ghttpCommon.c", cc='4199_60831', opts=SPY_OPTS), From aa5b84c139923b181208eb7152293f98f04df3cb Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 29 Jul 2021 15:26:27 -0600 Subject: [PATCH 125/477] [OSX] Add crossover.sh, run compile/link as shell We might want to revisit shell=True --- build.py | 71 +++++++++++++++++++++----------------- mkwutil/tools/crossover.sh | 10 ++++++ 2 files changed, 50 insertions(+), 31 deletions(-) create mode 100755 mkwutil/tools/crossover.sh diff --git a/build.py b/build.py index a91a94981..7e9eace3e 100644 --- a/build.py +++ b/build.py @@ -63,6 +63,9 @@ def __native_binary(path): def __windows_binary(path): if sys.platform == "win32" or sys.platform == "msys": return path + if sys.platform == "darwin": + return os.path.abspath("./mkwutil/tools/crossover.sh") + " " + path + return "wine " + path @@ -111,43 +114,49 @@ def __windows_binary(path): os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") ), } -CWCC_OPT = " ".join( - [ - "-nodefaults", - "-align powerpc", - "-enc SJIS", - "-c", - # "-I-", - "-gccinc", - "-i ./source/ -i ./source/platform", - # "-inline deferred", - "-proc gekko", - "-enum int", - "-O4,p", - "-inline auto", - "-W all", - "-fp hardware", - "-Cpp_exceptions off", - "-RTTI off", - '-pragma "cats off"', # ??? - '-pragma "warning off(10178)"', # suppress "function has no prototype" - # "-pragma \"aggressive_inline on\"", - # "-pragma \"auto_inline on\"", - "-inline auto", - "-w notinlined -W noimplicitconv -w nounwanted", - "-nostdinc", - "-msgstyle gcc -lang=c99 -DREVOKART", - "-func_align 4", - ] -) + +CW_ARGS = [ + "-nodefaults", + "-align powerpc", + "-enc SJIS", + "-c", + # "-I-", + "-gccinc", + "-i ./source/ -i ./source/platform", + # "-inline deferred", + "-proc gekko", + "-enum int", + "-O4,p", + "-inline auto", + "-W all", + "-fp hardware", + "-Cpp_exceptions off", + "-RTTI off", + #'-pragma "cats off"', # ??? + # "-pragma \"aggressive_inline on\"", + # "-pragma \"auto_inline on\"", + "-inline auto", + "-w notinlined -W noimplicitconv -w nounwanted", + "-nostdinc", + "-msgstyle gcc -lang=c99 -DREVOKART", + "-func_align 4", +] + +# Hack: $@ doesn't behave properly with this +if sys.platform != "darwin": + # suppress "function has no prototype + CW_ARGS.append("-pragma \"warning off(10178)\"") + +CWCC_OPT = " ".join(CW_ARGS) def compile_source_impl(src, dst, version="default", additional="-ipa file"): + return """Compiles a source file.""" # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" process = subprocess.Popen( - command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True ) lines = list(iter(process.stdout.readline, "")) with print_mutex: @@ -229,7 +238,7 @@ def link( ) if partial: cmd.append("-r") - subprocess.run(cmd, check=True, text=True) + subprocess.run(" ".join(str(x) for x in cmd), check=True, text=True, shell=True) def compile_sources(): diff --git a/mkwutil/tools/crossover.sh b/mkwutil/tools/crossover.sh new file mode 100755 index 000000000..e0428b7f9 --- /dev/null +++ b/mkwutil/tools/crossover.sh @@ -0,0 +1,10 @@ +export CX_ROOT="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver" +export CX_BOTTLE="mwcceppc.exe" +export WINEPREFIX="/Library/Application Support/CrossOver/Bottles/mwcceppc.exe" +export PATH="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin:/usr/bin:/bin:/usr/sbin:/sbin" +export DYLD_LIBRARY_PATH="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/lib64:/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/lib32on64" +export WINEDLLPATH="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/lib32on64/wine" +export WINELOADER="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin/wineloader32on64" +export WINESERVER="/Applications/CrossOver.app/Contents/SharedSupport/CrossOver/bin/wineserver" +export DISPLAY="/private/tmp/com.apple.launchd.GhHRSLMEeg/org.xquartz:0" +/applications/crossover.app/Contents/SharedSupport/CrossOver/bin/wine $@ From bff7ea59a5b694ec46dce1006529b6a54744be94 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 29 Jul 2021 15:30:22 -0600 Subject: [PATCH 126/477] [OSX] Support building on M1: Fix LCF formatting, fix build.py --- build.py | 1 - mkwutil/gen_lcf.py | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/build.py b/build.py index 7e9eace3e..801e9482a 100644 --- a/build.py +++ b/build.py @@ -151,7 +151,6 @@ def __windows_binary(path): def compile_source_impl(src, dst, version="default", additional="-ipa file"): - return """Compiles a source file.""" # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index 83246d945..c2d839438 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -11,6 +11,9 @@ MATCH_UNK = re.compile(r"^unk_([0-9a-f]{8})$") +# LCF must use forward slashes on linux/osx +def format_path(p): + return "\"" + str(p).replace('\\', '/') + "\"" def gen_lcf( src: Path, @@ -55,7 +58,7 @@ def gen_lcf( for obj_path in object_paths: if obj_path.stem in stripped: continue - force_files.append(str(obj_path.parent / (obj_path.stem + ".o"))) + force_files.append(format_path(obj_path.parent / (obj_path.stem + ".o"))) # Compile template. template = jinja2.Template(src.read_text()) # Render template to string. From 2cf61e850a964385660c69165dcad84585d9402b Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 29 Jul 2021 15:34:50 -0600 Subject: [PATCH 127/477] [WIN] Use old LCF path formatting on Windows --- mkwutil/gen_lcf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index c2d839438..1fc584aa3 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -2,6 +2,7 @@ import jinja2 from pathlib import Path import re +import sys from elftools.elf.elffile import ELFFile @@ -13,6 +14,9 @@ # LCF must use forward slashes on linux/osx def format_path(p): + if sys.platform == "win32" or sys.platform == "msys": + return str(p) + return "\"" + str(p).replace('\\', '/') + "\"" def gen_lcf( From 5881de1f8574532da9e0d8f6297e0ba232ddc7b5 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 29 Jul 2021 18:26:20 -0600 Subject: [PATCH 128/477] Mark classes noncopyable where appropriate --- source/egg/core/eggArchive.cpp | 1 - source/egg/core/eggArchive.hpp | 4 +++- source/egg/core/eggDisposer.hpp | 3 +++ source/egg/core/eggDvdFile.hpp | 4 ++++ source/egg/core/eggExpHeap.hpp | 5 ++--- source/egg/core/eggHeap.hpp | 22 +++++++++++++--------- source/egg/core/eggThread.hpp | 9 +++++---- source/egg/util/eggCntFile.hpp | 5 +++++ source/game/kart/KartComponent.cpp | 4 +--- source/game/kart/KartComponent.hpp | 2 ++ 10 files changed, 38 insertions(+), 21 deletions(-) diff --git a/source/egg/core/eggArchive.cpp b/source/egg/core/eggArchive.cpp index 62bc9f2df..5681d20ae 100644 --- a/source/egg/core/eggArchive.cpp +++ b/source/egg/core/eggArchive.cpp @@ -10,7 +10,6 @@ namespace EGG { bool Archive::sIsArchiveListInitialized; -// PAL: 0x803832d8 nw4r::ut::List Archive::sArchiveList; Archive::~Archive() { removeList(this); } diff --git a/source/egg/core/eggArchive.hpp b/source/egg/core/eggArchive.hpp index 1ce644351..f78128ee2 100644 --- a/source/egg/core/eggArchive.hpp +++ b/source/egg/core/eggArchive.hpp @@ -79,7 +79,8 @@ class Archive : public Disposer // sizeof=60,0x3C //! static Archive* mountNoFastGet(void* pArcStart, Heap* pHeap, int align); - //! @brief Unmount an archive. (Set the status as NOT_LOADED and decrease refcount) + //! @brief Unmount an archive. (Set the status as NOT_LOADED and decrease + //! refcount) //! void unmount(); @@ -108,6 +109,7 @@ class Archive : public Disposer // sizeof=60,0x3C } appendList(this); } + inline Archive(const Archive&) {} virtual ~Archive() override; //!< [vt+0x00] //! @brief Append an archive to the static archive list. diff --git a/source/egg/core/eggDisposer.hpp b/source/egg/core/eggDisposer.hpp index c3842e9f9..85e2ae374 100644 --- a/source/egg/core/eggDisposer.hpp +++ b/source/egg/core/eggDisposer.hpp @@ -29,6 +29,9 @@ class Disposer { //! Disposer(); +private: + inline Disposer(const Disposer&) {} + private: Heap* mContainHeap; //!< [+0x04] Heap that contains the instance of this //!< disposer. diff --git a/source/egg/core/eggDvdFile.hpp b/source/egg/core/eggDvdFile.hpp index 408bbd78c..0f3503eec 100644 --- a/source/egg/core/eggDvdFile.hpp +++ b/source/egg/core/eggDvdFile.hpp @@ -17,6 +17,10 @@ class DvdFile : public File { //! @brief Sets mIsOpen to false then calls CT DvdFile(); +private: + inline DvdFile(const DvdFile&) {} + +public: //! @brief Closes the file on the DVD. ~DvdFile() override; diff --git a/source/egg/core/eggExpHeap.hpp b/source/egg/core/eggExpHeap.hpp index def6b3b3e..4c6f4713a 100644 --- a/source/egg/core/eggExpHeap.hpp +++ b/source/egg/core/eggExpHeap.hpp @@ -5,8 +5,8 @@ #pragma once -#include #include +#include namespace EGG { @@ -20,8 +20,8 @@ class ExpHeap : public Heap { void addSize(u16 groupID, u32 size); }; - // Always inline in MKW? ExpHeap(MEMHeapHandle heapHandle) : Heap(heapHandle) {} + virtual ~ExpHeap() override; //! @brief Create an EGG ExpHeap and wrapped MEM ExpHeap in a certain region. @@ -34,7 +34,6 @@ class ExpHeap : public Heap { //! static ExpHeap* create(void* block, u32 size, u16 attr) __attribute__((never_inline)); - //! @brief Create a new ExpHeap as a child of an existing heap. //! diff --git a/source/egg/core/eggHeap.hpp b/source/egg/core/eggHeap.hpp index 6a0abae2d..1da10e3db 100644 --- a/source/egg/core/eggHeap.hpp +++ b/source/egg/core/eggHeap.hpp @@ -78,13 +78,13 @@ class Heap : public Disposer { virtual u32 getAllocatableSize(s32 align) = 0; // [vt+0x24] virtual u32 adjust() = 0; // [vt+0x28] -HEAP_PRIVATE: - //! @brief Static linked-list of heaps. - //! - //! @details When a heap is created, it is appended to this list. - //! When it is deleted, it is removed. - //! - static nw4r::ut::List sHeapList; + HEAP_PRIVATE : + //! @brief Static linked-list of heaps. + //! + //! @details When a heap is created, it is appended to this list. + //! When it is deleted, it is removed. + //! + static nw4r::ut::List sHeapList; //! @brief Root heap mutex. //! @@ -124,7 +124,7 @@ class Heap : public Disposer { //! @details List of child disposers. //! When Heap::dispose() is called, ~Disposer() will be called for all //! children. - nw4r::ut::Node mNode; //!< [+0x20] + nw4r::ut::Node mNode; //!< [+0x20] nw4r::ut::List mChildren; //!< [+0x28] sizeof=0xC const char* mName; //!< [+0x034] set to "NoName" in ctor @@ -164,6 +164,10 @@ class Heap : public Disposer { //! Heap(MEMiHeapHead* heapHandle); +private: + inline Heap(const Heap&) {} + +public: //! @brief Allocate a block of memory in a heap. //! //! @details TODO @@ -263,7 +267,7 @@ class Heap : public Disposer { #ifdef RII_CLIENT return 0; #else - return (int) mHeapHandle->arena_end; + return (int)mHeapHandle->arena_end; #endif } }; diff --git a/source/egg/core/eggThread.hpp b/source/egg/core/eggThread.hpp index 7ca5f5d79..a2262b04e 100644 --- a/source/egg/core/eggThread.hpp +++ b/source/egg/core/eggThread.hpp @@ -25,7 +25,6 @@ class Thread { virtual void onEnter(); /*{}*/ //!< [vt+0x14] virtual void onExit(); /*{}*/ //!< [vt+0x10] - //! @brief A constructor. //! //! @details Creates an EGG::Thread and OSThread from arguments. @@ -52,6 +51,10 @@ class Thread { //! Thread(OSThread* osThread, int msgCount); +private: + inline Thread(const Thread&) {} + +public: //! @brief Find the (first) EGG::Thread that matches the provided osThread. //! //! @details Iterate through the static thread list (sThreadList) to find the @@ -121,9 +124,7 @@ class Thread { static void* start(void* eggThread); //! When not NULL will override the heap used for allocations. - inline Heap* getAllocatableHeap() { - return mAlloctableHeap; - } + inline Heap* getAllocatableHeap() { return mAlloctableHeap; } private: // List of all registered threads. diff --git a/source/egg/util/eggCntFile.hpp b/source/egg/util/eggCntFile.hpp index caf9a0d28..be53faa17 100644 --- a/source/egg/util/eggCntFile.hpp +++ b/source/egg/util/eggCntFile.hpp @@ -13,6 +13,11 @@ static BOOL gCurrentCntFile; class CntFile { public: CntFile(); + +private: + inline CntFile(const CntFile&) {} + +public: virtual ~CntFile(); virtual bool open(); // 0 diff --git a/source/game/kart/KartComponent.cpp b/source/game/kart/KartComponent.cpp index 7d0850d6d..c7ad70d09 100644 --- a/source/game/kart/KartComponent.cpp +++ b/source/game/kart/KartComponent.cpp @@ -14,9 +14,7 @@ void KartComponent::setupInList(KartAccessor* accessor) { } } -KartComponent::KartComponent() { - mAccessor = nullptr; - +KartComponent::KartComponent() : mAccessor(nullptr) { List_Append(&sList, this); } diff --git a/source/game/kart/KartComponent.hpp b/source/game/kart/KartComponent.hpp index d0ab430b6..7ea886c40 100644 --- a/source/game/kart/KartComponent.hpp +++ b/source/game/kart/KartComponent.hpp @@ -15,6 +15,8 @@ class KartComponent { KartComponent(); private: + inline KartComponent(const KartComponent&) {} + static nw4r::ut::List sList; KartAccessor* mAccessor; From d85eaae05aec87f407c1e1815578d49f5bbac490 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 30 Jul 2021 04:07:47 -0600 Subject: [PATCH 129/477] Add RKScene.hpp --- source/game/RKScene.hpp | 103 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 source/game/RKScene.hpp diff --git a/source/game/RKScene.hpp b/source/game/RKScene.hpp new file mode 100644 index 000000000..8b94ae2ef --- /dev/null +++ b/source/game/RKScene.hpp @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +// TODO: These likely align with the resource archives in Scene\UI + +enum RKSceneID { + // --- + RK_SCENE_ID_GAME_START = 0, //!< Called when REL first loaded; "Title" might + //!< be a more fitting name* + RK_SCENE_ID_MAIN_MENU = 1, //!< "メニューシーン" -> "Menu Scene" + RK_SCENE_ID_RACE = 2, //!< "レースシーン" -> "Race Scene" + + RK_SCENE_ID_MULTI = 4, //!< "地球儀シーン" -> "Globe Scene" + + // --- + RK_SCENE_ID_BOOTSTRAP = + 5, //!< Root scene loaded immediately after initializing the system. + // --- + + RK_SCENE_ID_FLAG_OPEN = 12 //!< "フラグオープンシーン" -> "Flag Open Scene" + // --- +}; + +enum RKHeapTag { + RK_HEAP_TAG_DEFAULT = 0x0, + RK_HEAP_TAG_RACE_DATA = 0x1, + RK_HEAP_TAG_MISC_SINGLETON = 0x2, // Rendering? + RK_HEAP_TAG_3 = 0x3, + RK_HEAP_TAG_RACE_GEO_OBJ = 0x4, + RK_HEAP_TAG_RACE_JMAP_AND_MDL = 0x5, + RK_HEAP_TAG_MENU = 0x6, + RK_HEAP_TAG_EFFECT = 0x7, + RK_HEAP_TAG_AUDIO = 0x8, + RK_HEAP_TAG_9 = 0x9, + RK_HEAP_TAG_RES = 0xA, + RK_HEAP_TAG_HBM = 0xB, + RK_HEAP_TAG_12 = 0xC, + RK_HEAP_TAG_NETWORKING = 0xD, +}; + +struct HeapCollection { + enum eHeapId { HEAP_ID_MEM1, HEAP_ID_MEM2, HEAP_ID_DEBUG, HEAP_ID_NUM }; + EGG::ExpHeap* mpHeaps[HEAP_ID_NUM]; + EGG::ExpHeap::GroupSizeRecord mRecords[HEAP_ID_NUM]; + + void setGroupIdAll(s32 id); // RKHeapTag + + inline HeapCollection() { + mpHeaps[HeapCollection::HEAP_ID_MEM1] = NULL; + mpHeaps[HeapCollection::HEAP_ID_MEM2] = NULL; + mpHeaps[HeapCollection::HEAP_ID_DEBUG] = NULL; + } +}; + +class RKScene : public EGG::Scene { +public: + virtual ~RKScene() override {} + inline RKScene() { setHeaps(); } + inline void dummy(EGG::ExpHeap*); + inline void setHeaps() { + EGG::ExpHeap* a[3]; + + a[0] = downcast(getA()); + a[1] = downcast(getB()); + a[2] = downcast(getC()); + setm1(a[0]); + setm2(a[1]); + setd(a[2]); + } + inline EGG::Heap* getA() { + return mHeapCollection.mpHeaps[HeapCollection::HEAP_ID_MEM1]; + } + inline EGG::Heap* getB() { + return mHeapCollection.mpHeaps[HeapCollection::HEAP_ID_MEM2]; + } + inline EGG::Heap* getC() { + return mHeapCollection.mpHeaps[HeapCollection::HEAP_ID_DEBUG]; + } + inline EGG::ExpHeap* getDbgHeap() { + return downcast(mHeapCollection.mpHeaps[HeapCollection::HEAP_ID_DEBUG]); + } + inline void setm1(EGG::ExpHeap* a) { + mHeapCollection.mpHeaps[HeapCollection::HEAP_ID_MEM1] = a; + } + inline void setm2(EGG::ExpHeap* a) { + mHeapCollection.mpHeaps[HeapCollection::HEAP_ID_MEM2] = a; + } + inline void setd(EGG::ExpHeap* a) { + mHeapCollection.mpHeaps[HeapCollection::HEAP_ID_DEBUG] = a; + } + // Perhaps not the best spot + inline EGG::ExpHeap* downcast(EGG::Heap* pHeap) { + if (pHeap && pHeap->getHeapKind() == EGG::Heap::HEAP_KIND_EXPANDED) + return reinterpret_cast(pHeap); + else + return 0; + } + HeapCollection mHeapCollection; // 0x30 + // 0xc3c + unk8 _c3c[0xc70 - 0xc3c]; +}; From d4949865c435c6123be6b4380c2e9a40570d6a4f Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 30 Jul 2021 13:16:09 +0200 Subject: [PATCH 130/477] CI: Add Linux build (#85) * CI: Add Linux build * Redo tools.zip * attempt to fix LCF --- .github/workflows/build.yml | 82 +++++- .github/workflows/lint.yml | 14 +- .github/workflows/pages.yml | 10 +- .gitignore | 1 + build.py | 14 +- mkwutil/gen_asm.py | 6 +- mkwutil/gen_lcf.py | 7 +- pack/dol_objects.txt | 510 ++++++++++++++++++------------------ pack/rel.lcf.j2 | 2 +- pack/rel_objects.txt | 30 +-- 10 files changed, 387 insertions(+), 289 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55127b75e..f7d584967 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,12 +1,20 @@ --- -on: [push, pull_request] +on: + push: + paths-ignore: + - 'tests/**' + - '*.md' + pull_request: + paths-ignore: + - 'tests/**' + - '*.md' name: Build jobs: - build: + build_windows: runs-on: windows-2019 steps: - uses: actions/checkout@v2 - + - uses: actions/setup-python@v2 with: python-version: '3.x' @@ -25,12 +33,12 @@ jobs: - name: Extract tooling env: - TOOLS_URL: 'https://cdn.discordapp.com/attachments/767477506861170710/870047437757239356/tools.zip' + TOOLS_URL: 'https://cdn.discordapp.com/attachments/767477506861170710/870608469558980649/tools.zip' run: | # Download tooling Invoke-WebRequest "$Env:TOOLS_URL" -OutFile .\tools.zip $hash = (Get-FileHash '.\tools.zip').Hash - if ($hash -ne '3FB4330838A6FAC6D35BE9AFDF486EF1F0760E0AA6AE1B4A7CE37802BF1EC7DD') + if ($hash -ne '2D78EEB2B90A2E57DB4B5DC55F37468B115B344498B8875D2EC8ADDE5AB8B243') { echo "Invalid hash: $hash" exit 1 @@ -60,3 +68,67 @@ jobs: - name: Build mkw shell: bash run: python ./build.py + + build_linux: + runs-on: ubuntu-latest + steps: + - name: Install deps + run: | + sudo dpkg --add-architecture i386 + # --- Debian Wine + sudo apt-get update + sudo apt-get install -y --no-install-recommends wine wine32 unzip + # --- WineHQ is broken + # wget -nc https://dl.winehq.org/wine-builds/winehq.key + # sudo apt-key add winehq.key + # rm winehq.key + # sudo add-apt-repository 'deb https://dl.winehq.org/wine-builds/ubuntu/ focal main' + # sudo apt-get update + # sudo apt-get install -y --no-install-recommends winehq-stable unzip + + - uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: '3.x' + architecture: 'x64' + + - uses: syphar/restore-virtualenv@v1 + id: cache-virtualenv-linux + with: + requirement_files: requirements.txt + + - uses: syphar/restore-pip-download-cache@v1 + if: steps.cache-virtualenv.outputs.cache-hit != 'true' + + - run: pip install -r requirements.txt + if: steps.cache-virtualenv.outputs.cache-hit != 'true' + + - name: Extract tooling + env: + TOOLS_URL: 'https://cdn.discordapp.com/attachments/767477506861170710/870608469558980649/tools.zip' + run: | + curl -sSf "$TOOLS_URL" -o ./tools.zip + echo "2D78EEB2B90A2E57DB4B5DC55F37468B115B344498B8875D2EC8ADDE5AB8B243 tools.zip" | sha256sum --check --status + unzip -o tools.zip + chmod +x ./tools/devkitppc/bin/powerpc-eabi-as + mv ./tools/blobs/* ./artifacts/orig/pal + rm tools.zip + + - uses: actions/cache@v2 + with: + path: asm/dol + key: asm-dol + + - uses: actions/cache@v2 + with: + path: asm/rel + key: asm-rel + + - name: Build mkw + shell: bash + run: | + # Create virtual display. + Xvfb :0 -screen 0 640x480x16 & + export DISPLAY=:0.0 + python ./build.py -j 1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fc8f063a5..af17c5cbd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,5 +1,17 @@ --- -on: [push, pull_request] +on: + push: + paths: + - '**.h' + - '**.c' + - '**.hpp' + - '**.cpp' + pull_request: + paths: + - '**.h' + - '**.c' + - '**.hpp' + - '**.cpp' name: Lint jobs: lint-clang-format: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index f4a96d8a4..be2699e8a 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -1,5 +1,13 @@ --- -on: [push, pull_request] +on: + push: + paths-ignore: + - 'tests/**' + - '*.md' + pull_request: + paths-ignore: + - 'tests/**' + - '*.md' name: GitHub Pages jobs: build: diff --git a/.gitignore b/.gitignore index 1b03f4115..9cbcaf06c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,7 @@ /asm/rel/ # Executables +**/bin/* *.exe *.dll *.out diff --git a/build.py b/build.py index 801e9482a..f4eb7746e 100644 --- a/build.py +++ b/build.py @@ -33,6 +33,13 @@ parser = argparse.ArgumentParser(description="Build main.dol and StaticR.rel.") parser.add_argument("--regen_asm", action="store_true", help="Regenerate all ASM") +parser.add_argument( + "-j", + "--concurrency", + type=int, + default=multiprocessing.cpu_count(), + help="Compile concurrency", +) args = parser.parse_args() # Start by running gen_asm. gen_asm(args.regen_asm) @@ -145,7 +152,7 @@ def __windows_binary(path): # Hack: $@ doesn't behave properly with this if sys.platform != "darwin": # suppress "function has no prototype - CW_ARGS.append("-pragma \"warning off(10178)\"") + CW_ARGS.append('-pragma "warning off(10178)"') CWCC_OPT = " ".join(CW_ARGS) @@ -175,10 +182,9 @@ def compile_source_impl(src, dst, version="default", additional="-ipa file"): def compile_queued_sources(): """Dispatches multiple threads to compile all queued sources.""" - max_hw_concurrency = multiprocessing.cpu_count() - print(colored(f"max_hw_concurrency={max_hw_concurrency}", color="yellow")) + print(colored(f"max_hw_concurrency={args.concurrency}", color="yellow")) - pool = ThreadPool(max_hw_concurrency) + pool = ThreadPool(args.concurrency) pool.map(lambda s: compile_source_impl(*s), gSourceQueue) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index dff2674e8..902fe1c20 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -3,7 +3,7 @@ """ import argparse -from pathlib import Path +from pathlib import Path, PurePosixPath import os import struct @@ -300,7 +300,7 @@ def __write_objlist(self): object_names = self.slices.object_slices().objects.keys() with open(self.pack_dir / "dol_objects.txt", "w") as file: for name in object_names: - print(Path(name), file=file) + print(PurePosixPath(Path(name)), file=file) def run(self): """Runs ASM generation for main.dol.""" @@ -420,7 +420,7 @@ def run(self): object_names = self.slices.object_slices().objects.keys() with open(self.pack_dir / "rel_objects.txt", "w") as file: for name in object_names: - print(Path(name), file=file) + print(PurePosixPath(Path(name)), file=file) def __process_section(self, section: Section): """Processes a library section and all its slices.""" diff --git a/mkwutil/gen_lcf.py b/mkwutil/gen_lcf.py index 1fc584aa3..999a8d3f3 100644 --- a/mkwutil/gen_lcf.py +++ b/mkwutil/gen_lcf.py @@ -1,6 +1,6 @@ import argparse import jinja2 -from pathlib import Path +from pathlib import Path, PurePosixPath, PureWindowsPath import re import sys @@ -15,9 +15,8 @@ # LCF must use forward slashes on linux/osx def format_path(p): if sys.platform == "win32" or sys.platform == "msys": - return str(p) - - return "\"" + str(p).replace('\\', '/') + "\"" + return str(PureWindowsPath(p)) + return f'"{PurePosixPath(p)}"' def gen_lcf( src: Path, diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 026d13b59..4dad7a3db 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -1,255 +1,255 @@ -out\trkHeader.o -out\dol\init_80004100_80005f34.o -out\rvlTrkMem.o -out\dol\init_80006068_80006460.o -out\dol\extab_80006460_80006a20.o -out\dol\extabindex_80006a20_800072c0.o -out\dol\text_800072c0_8006a0c0.o -out\dol\ctors_80244de0_80244e88.o -out\dol\dtors_80244ea4_80244eac.o -out\dol\rodata_80244ec0_80248010.o -out\dol\data_80258580_80274148.o -out\dol\bss_802a4080_802f2338.o -out\dol\sdata_80384c00_803850a0.o -out\dol\sbss_80385fc0_803862a8.o -out\dol\sdata2_80386fa0_80387cac.o -out\g3d_camera.o -out\dol\text_8006a518_800774d0.o -out\dol\sdata2_80387cd8_80387d58.o -out\g3d_fog.o -out\dol\text_800775d0_80085110.o -out\dol\sdata2_80387d5c_80387e80.o -out\mathTriangular.o -out\dol\text_80085578_80085600.o -out\dol\rodata_80249020_8024c6b8.o -out\dol\data_80274250_80275700.o -out\dol\sdata2_80387ea4_80387ea8.o -out\mathTypes.o -out\dol\text_80085938_800aef60.o -out\utList.o -out\dol\text_800af1a0_800ccb4c.o -out\dwc_error.o -out\dol\text_800ccc80_800ef378.o -out\darray.o -out\hashtable.o -out\dol\data_80275758_8027aca0.o -out\md5c.o -out\dol\rodata_8024c6c9_8024c6d0.o -out\dol\data_8027ace0_8027ad58.o -out\dol\sbss_803862b0_80386350.o -out\gsSocketRevolution.o -out\dol\text_800f164c_800f1f58.o -out\gsUtilRevolution.o -out\dol\text_800f2048_800f38a4.o -out\gsAvailable.o -out\dol\text_800f3a20_800f489c.o -out\dol\data_8027ad79_8027ad80.o -out\dol\bss_802f2410_802f2440.o -out\gsUdpEngine.o -out\gsXML.o -out\dol\bss_802f247c_802f3480.o -out\dol\sdata_80385105_80385108.o -out\gp.o -out\dol\data_8027b289_8027b290.o -out\gpi.o -out\dol\data_8027b30f_8027b310.o -out\gpiBuddy.o -out\dol\data_8027b453_8027b458.o -out\dol\sdata_803851ea_803851f0.o -out\gpiBuffer.o -out\dol\data_8027b4ba_8027b4c0.o -out\gpiCallback.o -out\dol\data_8027b4cf_8027b4d0.o -out\dol\sdata_80385206_80385208.o -out\gpiConnect.o -out\dol\data_8027b876_8027b878.o -out\dol\sdata_8038529b_803852a0.o -out\dol\sbss_8038635c_80386360.o -out\dol\sdata2_80387eb4_80388470.o -out\gpiInfo.o -out\dol\data_8027bbce_8027bbd0.o -out\dol\sdata_80385355_80385358.o -out\gpiKeys.o -out\dol\data_8027bc11_8027bc18.o -out\gpiOperation.o -out\dol\sdata_8038535a_80385360.o -out\gpiPeer.o -out\dol\data_8027bd2a_8027bd30.o -out\dol\sdata_803853b9_803853c0.o -out\gpiProfile.o -out\dol\data_8027bee5_8027bee8.o -out\dol\sdata_803853ce_803853d0.o -out\gpiSearch.o -out\dol\data_8027c204_8027c208.o -out\dol\sdata_803854bb_803854c0.o -out\gpiTransfer.o -out\dol\data_8027c229_8027c230.o -out\dol\sdata_803854cb_803854d0.o -out\gpiUnique.o -out\dol\data_8027c26f_8027c270.o -out\dol\sdata_803854dd_803854e0.o -out\gpiUtility.o -out\dol\data_8027c2c5_8027c2c8.o -out\gt2Auth.o -out\gt2Buffer.o -out\gt2Callback.o -out\gt2Connection.o -out\gt2Main.o -out\dol\text_8010ad14_8010de30.o -out\gt2Socket.o -out\dol\data_8027c2e9_8027c2f0.o -out\dol\sdata_803854f8_80385508.o -out\gt2Utility.o -out\dol\bss_802f34b0_802f34c0.o -out\dol\sdata_80385519_80385520.o -out\dol\sbss_80386364_80386368.o -out\dol\sdata2_80388474_80388478.o -out\qr2.o -out\dol\data_8027d21b_8027d220.o -out\dol\bss_802f3624_802f3820.o -out\dol\sdata_8038554a_80385550.o -out\qr2RegKeys.o -out\dol\sdata_803855c7_803855c8.o -out\ghttpBuffer.o -out\dol\text_80111de8_80111f0c.o -out\ghttpCallbacks.o -out\dol\sdata_803855d3_803855d8.o -out\dol\sbss_8038636c_80386370.o -out\ghttpCommon.o -out\dol\text_8011248c_801125c8.o -out\ghttpConnection.o -out\dol\data_8027d701_8027d708.o -out\ghttpEncryption.o -out\dol\data_8027d711_8027d718.o -out\ghttpMain.o -out\dol\data_8027d731_8027d738.o -out\dol\sbss_8038638c_80386390.o -out\dol\sdata2_8038847e_80388480.o -out\ghttpPost.o -out\dol\data_8027d974_8027d978.o -out\dol\sdata_803855ff_80385600.o -out\ghttpProcess.o -out\dol\rodata_8024c6db_80252c78.o -out\dol\data_8027da43_8027da48.o -out\dol\sdata_80385655_80385658.o -out\dol\sbss_803863a4_803863a8.o -out\dol\sdata2_80388484_80388488.o -out\gbucket.o -out\gstats.o -out\dol\text_8011a054_8011c10c.o -out\sb_crypt.o -out\dol\data_8027dca0_8027ddc0.o -out\dol\bss_802f3a20_802f3f40.o -out\dol\sdata_803856be_803856f0.o -out\sb_queryengine.o -out\dol\data_8027dde5_8027dde8.o -out\dol\sbss_803863dc_80386430.o -out\sb_server.o -out\dol\data_8027ddf1_8027ddf8.o -out\dol\sdata_80385721_80385728.o -out\sb_serverbrowsing.o -out\dol\sdata_8038572c_80385730.o -out\dol\sbss_8038643c_80386440.o -out\sb_serverlist.o -out\dol\data_8027de44_8027de48.o -out\sakeMain.o -out\dol\text_8012249c_80124500.o -out\dol\data_8027df3e_8027e708.o -out\dol\bss_802f4040_80346cf0.o -out\dol\sdata_80385744_803857f0.o -out\rvlArchive.o -out\dol\text_80124e80_80169bcc.o -out\fs.o -out\dol\text_8016b49c_80192f7c.o -out\dol\data_8027e772_8029cc80.o -out\dol\sdata_803857f6_80385a08.o -out\dol\sbss_80386448_803867e8.o -out\ipcMain.o -out\ipcclt.o -out\dol\text_801949b8_801981ec.o -out\dol\sbss_803867fc_80386838.o -out\rvlMemHeap.o -out\rvlMemExpHeap.o -out\rvlMemFrmHeap.o -out\rvlMemUnitHeap.o -out\dol\bss_80346d18_803481b0.o -out\dol\sbss_8038683c_80386998.o -out\dol\sdata2_803884a4_80388860.o -out\rvlMemAllocator.o -out\rvlMemList.o -out\rvlMtx.o -out\rvlMtx2.o -out\rvlVec.o -out\dol\sdata_80385a10_80385b08.o -out\dol\sdata2_803888b4_803888b8.o -out\rvlQuat.o -out\nand.o -out\dol\text_8019f1a8_801ae5d8.o -out\dol\rodata_80252c84_80252dd0.o -out\dol\sdata2_803888d0_80388930.o -out\rvlPadClamp.o -out\rvlPad.o -out\dol\text_801b0180_801b7410.o -out\dol\rodata_80252de6_80257700.o -out\dol\data_8029ccd8_8029d058.o -out\dol\bss_80348230_80357220.o -out\dol\sdata_80385b28_80385c68.o -out\dol\sbss_803869c4_803869f0.o -out\siBios.o -out\dol\sbss_803869f8_80386d30.o -out\dol\sdata2_80388938_80388958.o -out\tpl.o -out\dol\text_801b7624_801ec088.o -out\dol\data_8029d083_802a2318.o -out\dol\sdata_80385c6e_80385ee0.o -out\soCommon.o -out\dol\data_802a24f4_802a24f8.o -out\soBasic.o -out\dol\text_801ecff4_8020f62c.o -out\dol\data_802a2543_802a2668.o -out\eggAllocator.o -out\dol\bss_80357238_803832d8.o -out\dol\sdata_80385eec_80385fc0.o -out\dol\sbss_80386d38_80386d80.o -out\eggArchive.o -out\dol\text_8020fcc4_8021a0f0.o -out\dol\data_802a268c_802a2b48.o -out\eggDisposer.o -out\dol\text_8021a1b8_802269a8.o -out\dol\data_802a2b54_802a2ff8.o -out\eggExpHeap.o -out\dol\text_80226f04_80229540.o -out\dol\rodata_8025771a_80257740.o -out\dol\data_802a3024_802a30b0.o -out\dol\bss_803832e4_80384320.o -out\dol\sbss_80386d84_80386e90.o -out\eggGraphicsFifo.o -out\dol\data_802a30bc_802a30c0.o -out\dol\sbss_80386e99_80386ea0.o -out\dol\sdata2_80388960_80388d68.o -out\eggHeap.o -out\dol\text_80229fac_80239dfc.o -out\eggQuat.o -out\dol\text_80239e10_80242498.o -out\dol\rodata_80257824_802582e0.o -out\dol\data_802a30ec_802a3f78.o -out\eggStreamDecomp.o -out\dol\text_80242504_802432e0.o -out\dol\data_802a3f90_802a3fc0.o -out\dol\bss_80384348_80384b60.o -out\eggThread.o -out\eggUnitHeap.o -out\dol\data_802a4004_802a4040.o -out\dol\bss_80384b6c_80384b70.o -out\dol\sbss_80386ec0_80386f78.o -out\dol\sdata2_80388d80_803890f8.o -out\eggVector.o -out\dol\ctors_80244e8c_80244e90.o -out\dol\bss_80384bf4_80384c00.o -out\dol\sbss_80386f90_80386fa0.o -out\dol\sdata2_80389104_80389108.o -out\eggVideo.o -out\dol\text_80244074_80244de0.o -out\dol\rodata_80258560_80258580.o -out\dol\sdata2_80389118_80389140.o -out\dol\sbss2_80389140_8038917c.o +out/trkHeader.o +out/dol/init_80004100_80005f34.o +out/rvlTrkMem.o +out/dol/init_80006068_80006460.o +out/dol/extab_80006460_80006a20.o +out/dol/extabindex_80006a20_800072c0.o +out/dol/text_800072c0_8006a0c0.o +out/dol/ctors_80244de0_80244e88.o +out/dol/dtors_80244ea4_80244eac.o +out/dol/rodata_80244ec0_80248010.o +out/dol/data_80258580_80274148.o +out/dol/bss_802a4080_802f2338.o +out/dol/sdata_80384c00_803850a0.o +out/dol/sbss_80385fc0_803862a8.o +out/dol/sdata2_80386fa0_80387cac.o +out/g3d_camera.o +out/dol/text_8006a518_800774d0.o +out/dol/sdata2_80387cd8_80387d58.o +out/g3d_fog.o +out/dol/text_800775d0_80085110.o +out/dol/sdata2_80387d5c_80387e80.o +out/mathTriangular.o +out/dol/text_80085578_80085600.o +out/dol/rodata_80249020_8024c6b8.o +out/dol/data_80274250_80275700.o +out/dol/sdata2_80387ea4_80387ea8.o +out/mathTypes.o +out/dol/text_80085938_800aef60.o +out/utList.o +out/dol/text_800af1a0_800ccb4c.o +out/dwc_error.o +out/dol/text_800ccc80_800ef378.o +out/darray.o +out/hashtable.o +out/dol/data_80275758_8027aca0.o +out/md5c.o +out/dol/rodata_8024c6c9_8024c6d0.o +out/dol/data_8027ace0_8027ad58.o +out/dol/sbss_803862b0_80386350.o +out/gsSocketRevolution.o +out/dol/text_800f164c_800f1f58.o +out/gsUtilRevolution.o +out/dol/text_800f2048_800f38a4.o +out/gsAvailable.o +out/dol/text_800f3a20_800f489c.o +out/dol/data_8027ad79_8027ad80.o +out/dol/bss_802f2410_802f2440.o +out/gsUdpEngine.o +out/gsXML.o +out/dol/bss_802f247c_802f3480.o +out/dol/sdata_80385105_80385108.o +out/gp.o +out/dol/data_8027b289_8027b290.o +out/gpi.o +out/dol/data_8027b30f_8027b310.o +out/gpiBuddy.o +out/dol/data_8027b453_8027b458.o +out/dol/sdata_803851ea_803851f0.o +out/gpiBuffer.o +out/dol/data_8027b4ba_8027b4c0.o +out/gpiCallback.o +out/dol/data_8027b4cf_8027b4d0.o +out/dol/sdata_80385206_80385208.o +out/gpiConnect.o +out/dol/data_8027b876_8027b878.o +out/dol/sdata_8038529b_803852a0.o +out/dol/sbss_8038635c_80386360.o +out/dol/sdata2_80387eb4_80388470.o +out/gpiInfo.o +out/dol/data_8027bbce_8027bbd0.o +out/dol/sdata_80385355_80385358.o +out/gpiKeys.o +out/dol/data_8027bc11_8027bc18.o +out/gpiOperation.o +out/dol/sdata_8038535a_80385360.o +out/gpiPeer.o +out/dol/data_8027bd2a_8027bd30.o +out/dol/sdata_803853b9_803853c0.o +out/gpiProfile.o +out/dol/data_8027bee5_8027bee8.o +out/dol/sdata_803853ce_803853d0.o +out/gpiSearch.o +out/dol/data_8027c204_8027c208.o +out/dol/sdata_803854bb_803854c0.o +out/gpiTransfer.o +out/dol/data_8027c229_8027c230.o +out/dol/sdata_803854cb_803854d0.o +out/gpiUnique.o +out/dol/data_8027c26f_8027c270.o +out/dol/sdata_803854dd_803854e0.o +out/gpiUtility.o +out/dol/data_8027c2c5_8027c2c8.o +out/gt2Auth.o +out/gt2Buffer.o +out/gt2Callback.o +out/gt2Connection.o +out/gt2Main.o +out/dol/text_8010ad14_8010de30.o +out/gt2Socket.o +out/dol/data_8027c2e9_8027c2f0.o +out/dol/sdata_803854f8_80385508.o +out/gt2Utility.o +out/dol/bss_802f34b0_802f34c0.o +out/dol/sdata_80385519_80385520.o +out/dol/sbss_80386364_80386368.o +out/dol/sdata2_80388474_80388478.o +out/qr2.o +out/dol/data_8027d21b_8027d220.o +out/dol/bss_802f3624_802f3820.o +out/dol/sdata_8038554a_80385550.o +out/qr2regkeys.o +out/dol/sdata_803855c7_803855c8.o +out/ghttpBuffer.o +out/dol/text_80111de8_80111f0c.o +out/ghttpCallbacks.o +out/dol/sdata_803855d3_803855d8.o +out/dol/sbss_8038636c_80386370.o +out/ghttpCommon.o +out/dol/text_8011248c_801125c8.o +out/ghttpConnection.o +out/dol/data_8027d701_8027d708.o +out/ghttpEncryption.o +out/dol/data_8027d711_8027d718.o +out/ghttpMain.o +out/dol/data_8027d731_8027d738.o +out/dol/sbss_8038638c_80386390.o +out/dol/sdata2_8038847e_80388480.o +out/ghttpPost.o +out/dol/data_8027d974_8027d978.o +out/dol/sdata_803855ff_80385600.o +out/ghttpProcess.o +out/dol/rodata_8024c6db_80252c78.o +out/dol/data_8027da43_8027da48.o +out/dol/sdata_80385655_80385658.o +out/dol/sbss_803863a4_803863a8.o +out/dol/sdata2_80388484_80388488.o +out/gbucket.o +out/gstats.o +out/dol/text_8011a054_8011c10c.o +out/sb_crypt.o +out/dol/data_8027dca0_8027ddc0.o +out/dol/bss_802f3a20_802f3f40.o +out/dol/sdata_803856be_803856f0.o +out/sb_queryengine.o +out/dol/data_8027dde5_8027dde8.o +out/dol/sbss_803863dc_80386430.o +out/sb_server.o +out/dol/data_8027ddf1_8027ddf8.o +out/dol/sdata_80385721_80385728.o +out/sb_serverbrowsing.o +out/dol/sdata_8038572c_80385730.o +out/dol/sbss_8038643c_80386440.o +out/sb_serverlist.o +out/dol/data_8027de44_8027de48.o +out/sakeMain.o +out/dol/text_8012249c_80124500.o +out/dol/data_8027df3e_8027e708.o +out/dol/bss_802f4040_80346cf0.o +out/dol/sdata_80385744_803857f0.o +out/rvlArchive.o +out/dol/text_80124e80_80169bcc.o +out/fs.o +out/dol/text_8016b49c_80192f7c.o +out/dol/data_8027e772_8029cc80.o +out/dol/sdata_803857f6_80385a08.o +out/dol/sbss_80386448_803867e8.o +out/ipcMain.o +out/ipcclt.o +out/dol/text_801949b8_801981ec.o +out/dol/sbss_803867fc_80386838.o +out/rvlMemHeap.o +out/rvlMemExpHeap.o +out/rvlMemFrmHeap.o +out/rvlMemUnitHeap.o +out/dol/bss_80346d18_803481b0.o +out/dol/sbss_8038683c_80386998.o +out/dol/sdata2_803884a4_80388860.o +out/rvlMemAllocator.o +out/rvlMemList.o +out/rvlMtx.o +out/rvlMtx2.o +out/rvlVec.o +out/dol/sdata_80385a10_80385b08.o +out/dol/sdata2_803888b4_803888b8.o +out/rvlQuat.o +out/nand.o +out/dol/text_8019f1a8_801ae5d8.o +out/dol/rodata_80252c84_80252dd0.o +out/dol/sdata2_803888d0_80388930.o +out/rvlPadClamp.o +out/rvlPad.o +out/dol/text_801b0180_801b7410.o +out/dol/rodata_80252de6_80257700.o +out/dol/data_8029ccd8_8029d058.o +out/dol/bss_80348230_80357220.o +out/dol/sdata_80385b28_80385c68.o +out/dol/sbss_803869c4_803869f0.o +out/siBios.o +out/dol/sbss_803869f8_80386d30.o +out/dol/sdata2_80388938_80388958.o +out/tpl.o +out/dol/text_801b7624_801ec088.o +out/dol/data_8029d083_802a2318.o +out/dol/sdata_80385c6e_80385ee0.o +out/soCommon.o +out/dol/data_802a24f4_802a24f8.o +out/soBasic.o +out/dol/text_801ecff4_8020f62c.o +out/dol/data_802a2543_802a2668.o +out/eggAllocator.o +out/dol/bss_80357238_803832d8.o +out/dol/sdata_80385eec_80385fc0.o +out/dol/sbss_80386d38_80386d80.o +out/eggArchive.o +out/dol/text_8020fcc4_8021a0f0.o +out/dol/data_802a268c_802a2b48.o +out/eggDisposer.o +out/dol/text_8021a1b8_802269a8.o +out/dol/data_802a2b54_802a2ff8.o +out/eggExpHeap.o +out/dol/text_80226f04_80229540.o +out/dol/rodata_8025771a_80257740.o +out/dol/data_802a3024_802a30b0.o +out/dol/bss_803832e4_80384320.o +out/dol/sbss_80386d84_80386e90.o +out/eggGraphicsFifo.o +out/dol/data_802a30bc_802a30c0.o +out/dol/sbss_80386e99_80386ea0.o +out/dol/sdata2_80388960_80388d68.o +out/eggHeap.o +out/dol/text_80229fac_80239dfc.o +out/eggQuat.o +out/dol/text_80239e10_80242498.o +out/dol/rodata_80257824_802582e0.o +out/dol/data_802a30ec_802a3f78.o +out/eggStreamDecomp.o +out/dol/text_80242504_802432e0.o +out/dol/data_802a3f90_802a3fc0.o +out/dol/bss_80384348_80384b60.o +out/eggThread.o +out/eggUnitHeap.o +out/dol/data_802a4004_802a4040.o +out/dol/bss_80384b6c_80384b70.o +out/dol/sbss_80386ec0_80386f78.o +out/dol/sdata2_80388d80_803890f8.o +out/eggVector.o +out/dol/ctors_80244e8c_80244e90.o +out/dol/bss_80384bf4_80384c00.o +out/dol/sbss_80386f90_80386fa0.o +out/dol/sdata2_80389104_80389108.o +out/eggVideo.o +out/dol/text_80244074_80244de0.o +out/dol/rodata_80258560_80258580.o +out/dol/sdata2_80389118_80389140.o +out/dol/sbss2_80389140_8038917c.o diff --git a/pack/rel.lcf.j2 b/pack/rel.lcf.j2 index a62bd5cc4..7be4222d3 100644 --- a/pack/rel.lcf.j2 +++ b/pack/rel.lcf.j2 @@ -16,5 +16,5 @@ GROUP:{ FORCEFILES { {% for name in force_files -%} {{ name }} -{% endfor %} +{% endfor -%} } diff --git a/pack/rel_objects.txt b/pack/rel_objects.txt index 47125aed0..ced7a13bf 100644 --- a/pack/rel_objects.txt +++ b/pack/rel_objects.txt @@ -1,15 +1,15 @@ -out\rel\text_805103b4_80512694.o -out\rel\ctors_8088f400_8088f704.o -out\rel\dtors_8088f704_8088f710.o -out\rel\rodata_8088f710_808b2bd0.o -out\rel\data_808b2bd0_808b2c30.o -out\rel\bss_809bd6e0_809bd6e8.o -out\JmpResourceCourse.o -out\rel\text_805127ec_80590128.o -out\rel\data_808b2c3c_808dd3d4.o -out\rel\bss_809bd6ec_809c1900.o -out\KartComponent.o -out\rel\text_805901d0_805f8b34.o -out\MessageGroup.o -out\rel\text_805f8b90_8088f400.o -out\rel\bss_809c1910_809c4f90.o +out/rel/text_805103b4_80512694.o +out/rel/ctors_8088f400_8088f704.o +out/rel/dtors_8088f704_8088f710.o +out/rel/rodata_8088f710_808b2bd0.o +out/rel/data_808b2bd0_808b2c30.o +out/rel/bss_809bd6e0_809bd6e8.o +out/JmpResourceCourse.o +out/rel/text_805127ec_80590128.o +out/rel/data_808b2c3c_808dd3d4.o +out/rel/bss_809bd6ec_809c1900.o +out/KartComponent.o +out/rel/text_805901d0_805f8b34.o +out/MessageGroup.o +out/rel/text_805f8b90_8088f400.o +out/rel/bss_809c1910_809c4f90.o From 7f6fb395a230683f063c7838570d2be697e015c3 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 30 Jul 2021 13:16:59 +0200 Subject: [PATCH 131/477] Improve speed of Linux CI --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7d584967..5ffb279cc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -131,4 +131,4 @@ jobs: # Create virtual display. Xvfb :0 -screen 0 640x480x16 & export DISPLAY=:0.0 - python ./build.py -j 1 + python ./build.py -j 32 From 97ac9ec6c19d0a9a13682eeccc3d771d4dadbb30 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 30 Jul 2021 14:24:13 +0200 Subject: [PATCH 132/477] Rewrite SliceTable.remove() --- mkwutil/lib/slices.py | 110 +++++++++++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 34 deletions(-) diff --git a/mkwutil/lib/slices.py b/mkwutil/lib/slices.py index 1335783f1..3a987a7a0 100644 --- a/mkwutil/lib/slices.py +++ b/mkwutil/lib/slices.py @@ -223,43 +223,85 @@ def remove(self, start: int = None, stop: int = None, _slice: Slice = None) -> N return self.__remove(start, stop) def __remove(self, start: int, stop: int) -> None: - assert isinstance(start, int) - assert isinstance(stop, int) + """Creates a gap spanning the given range.""" start = max(start, self.start) stop = min(stop, self.stop) - # Search for the beginning of the gap. - begin_slice, i = self.find(start) - # If gap starts within named slice, create new gap slice. - if begin_slice.start < start and begin_slice.has_name(): - old_stop = begin_slice.stop - begin_slice.stop = start - old_slice = begin_slice - begin_slice = Slice(start, old_stop) - self.slices.insert(i + 1, begin_slice) - i += 2 - # If gap stops within named slice, create copy of original slice. - if old_stop > stop: - end_slice = copy(old_slice) - end_slice.start = stop - end_slice.stop = old_stop - self.slices.insert(i, end_slice) - # If bordering with a gap on the left, select gap. - elif begin_slice.start == start and i > 0 and not self.slices[i - 1].has_name(): - begin_slice = self.slices[i - 1] + # Remove slice by slice. + _, idx = self.find(start) + while idx < len(self.slices) and self.slices[idx].start < stop: + target = self.slices[idx] + next_stop = min(stop, target.stop) + idx = self.__remove_slice(idx, max(start, target.start), next_stop) + idx += 1 + + def __remove_slice(self, idx: int, new_start: int, new_stop: int) -> int: + """Creates a gap in the slice at the given index. + Returns the index of the gap slice.""" + target = self.slices[idx] + assert new_start < new_stop + assert target.start <= new_start + assert target.stop >= new_stop + # Remove entire slice. + if target.start == new_start and target.stop == new_stop: + _slice = copy(target) + _slice.name = None + self.slices[idx] = _slice + idx = self.__merge_left(idx) + idx = self.__merge_right(idx) + return idx + # Remove left part. + elif target.start == new_start: + gap = copy(target) + gap.name = None + gap.stop = new_stop + part = copy(target) + part.start = new_stop + self.slices[idx] = gap + self.slices.insert(idx + 1, part) + return self.__merge_left(idx) + # Remove right part. + elif target.stop == new_stop: + part = copy(target) + part.stop = new_start + gap = copy(target) + gap.name = None + gap.start = new_start + self.slices[idx] = part + self.slices.insert(idx + 1, gap) + idx += 1 + return self.__merge_right(idx) + # Remove middle part. else: - i += 1 - # Extend slice. - begin_slice.name = None - begin_slice.stop = stop - # Remove overlapping slices. - while i < len(self.slices) and self.slices[i].stop <= stop: - self.slices.pop(i) - if i < len(self.slices): - self.slices[i].start = stop - # If bordering with a right gap, merge gaps. - if i < len(self.slices) and not self.slices[i].has_name(): - begin_slice.stop = self.slices[i].stop - self.slices.pop(i) + part_l = copy(target) + part_l.stop = new_start + gap = copy(target) + gap.name = None + gap.start = new_start + gap.stop = new_stop + part_r = copy(target) + part_r.start = new_stop + self.slices[idx] = part_l + self.slices.insert(idx + 1, gap) + self.slices.insert(idx + 2, part_r) + idx += 1 + return idx + + def __merge_left(self, idx: int) -> int: + """Merges gap slice at current index with possible left gap neighbor.""" + assert not self.slices[idx].has_name() + if idx > 0 and not self.slices[idx - 1].has_name(): + self.slices[idx - 1].stop = self.slices[idx].stop + self.slices.pop(idx) + return idx - 1 + return idx + + def __merge_right(self, idx: int) -> int: + """Merges gap slice at current index with possible right gap neighbor.""" + assert not self.slices[idx].has_name() + if idx + 1 < len(self.slices) and not self.slices[idx + 1].has_name(): + self.slices[idx + 1].start = self.slices[idx].start + self.slices.pop(idx) + return idx def __repr__(self) -> str: return ( From 21a6190aceb3817238a266c9f2cfd78ecf20690b Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Fri, 30 Jul 2021 23:31:23 +0200 Subject: [PATCH 133/477] percent_decompiled: Add split percentages (#88) * percent_decompiled: Add split percentages * percent_decompiled: remove 'data split' column * add HTML table * CI: Merge build and pages jobs * Fix build.py --- .github/workflows/build.yml | 11 ++ .github/workflows/pages.yml | 43 ------ build.py | 4 +- mkwutil/lib/slices.py | 5 + mkwutil/progress/graphic.py | 23 +++ mkwutil/progress/graphic/index.html.j2 | 6 + mkwutil/progress/percent_decompiled.py | 198 ++++++++++++++++--------- mkwutil/project.py | 9 +- requirements.txt | Bin 1134 -> 1168 bytes tests/test_slices.py | 18 +++ 10 files changed, 196 insertions(+), 121 deletions(-) delete mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ffb279cc..815a81d68 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -132,3 +132,14 @@ jobs: Xvfb :0 -screen 0 640x480x16 & export DISPLAY=:0.0 python ./build.py -j 32 + + - name: Run graphic.py + run: python -m mkwutil.progress.graphic + + - name: Deploy + uses: JamesIves/github-pages-deploy-action@4.1.4 + with: + branch: gh-pages + folder: out/website + clean: true + dry-run: ${{ github.ref != 'refs/heads/master' }} diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index be2699e8a..000000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -on: - push: - paths-ignore: - - 'tests/**' - - '*.md' - pull_request: - paths-ignore: - - 'tests/**' - - '*.md' -name: GitHub Pages -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - uses: actions/setup-python@v2 - with: - python-version: '3.x' - architecture: 'x64' - - - uses: syphar/restore-virtualenv@v1 - id: cache-virtualenv - with: - requirement_files: requirements.txt - - - uses: syphar/restore-pip-download-cache@v1 - if: steps.cache-virtualenv.outputs.cache-hit != 'true' - - - run: pip install -r requirements.txt - if: steps.cache-virtualenv.outputs.cache-hit != 'true' - - - name: Run graphic.py - run: python3 -m mkwutil.progress.graphic - - - name: Deploy - uses: JamesIves/github-pages-deploy-action@4.1.4 - with: - branch: gh-pages - folder: out/website - clean: true - dry-run: ${{ github.ref != 'refs/heads/master' }} diff --git a/build.py b/build.py index f4eb7746e..4539381e7 100644 --- a/build.py +++ b/build.py @@ -26,7 +26,7 @@ from mkwutil.pack_staticr_rel import pack_staticr_rel from mkwutil.verify_main_dol import verify_dol from mkwutil.verify_staticr_rel import verify_rel -from mkwutil.progress.percent_decompiled import percent_decompiled +from mkwutil.progress.percent_decompiled import build_stats from mkwutil.gen_asm import gen_asm from mkwutil.project import load_dol_slices @@ -335,7 +335,7 @@ def build(): verify_dol(orig_dol_path, target_dol_path) verify_rel(target_rel_path) - percent_decompiled() + build_stats(Path()).print() if __name__ == "__main__": diff --git a/mkwutil/lib/slices.py b/mkwutil/lib/slices.py index 3a987a7a0..438b7cb1b 100644 --- a/mkwutil/lib/slices.py +++ b/mkwutil/lib/slices.py @@ -91,6 +91,11 @@ def __init__( self.start = start self.stop = stop + def __copy__(self) -> "SliceTable": + table = SliceTable(self.start, self.stop) + table.slices = list(map(copy, self.slices)) + return table + def load_path(file_path, sections=None): """Loads slices given a path to a CSV file.""" if sections is not None: diff --git a/mkwutil/progress/graphic.py b/mkwutil/progress/graphic.py index 46dcc85d8..8b61bb483 100644 --- a/mkwutil/progress/graphic.py +++ b/mkwutil/progress/graphic.py @@ -1,15 +1,19 @@ import argparse import colorsys from dataclasses import dataclass +from io import StringIO from pathlib import Path import random import webbrowser import jinja2 +from pytablewriter import HtmlTableWriter +from pytablewriter.style import Style from mkwutil.sections import DOL_LIBS, DOL_SECTIONS from mkwutil.lib.slices import Slice, SliceTable from mkwutil.project import load_dol_slices +from mkwutil.progress.percent_decompiled import build_stats random.seed("OwO") @@ -94,6 +98,24 @@ def lib_boxes(): return map(Box.from_slice, slices) +def percent_decompiled_table() -> str: + stats = build_stats(Path()) + writer = HtmlTableWriter( + headers=["part", "code split", "code decomp", "data decomp"], + column_styles=[ + Style(align="left"), + Style(align="right"), + Style(align="right"), + Style(align="right"), + ], + margin=10, + value_matrix=stats.matrix, + ) + writer.stream = StringIO() + writer.write_table() + return writer.stream.getvalue() + + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( @@ -108,6 +130,7 @@ def lib_boxes(): with open(index_path, "w") as file: jinja_env.get_template("index.html.j2").stream( { + "percents_table": percent_decompiled_table(), "dol_decomp": standard_boxes(), "dol_libraries": lib_boxes(), } diff --git a/mkwutil/progress/graphic/index.html.j2 b/mkwutil/progress/graphic/index.html.j2 index 7dd19c44b..3e9c6b949 100644 --- a/mkwutil/progress/graphic/index.html.j2 +++ b/mkwutil/progress/graphic/index.html.j2 @@ -36,10 +36,16 @@ html { background-color: black; visibility: visible; } +th { + padding: .2em 1em; +} +

Progress

+{{ percents_table }} + {% macro slices(boxes, tooltip_class) -%}
{% for box in boxes -%} diff --git a/mkwutil/progress/percent_decompiled.py b/mkwutil/progress/percent_decompiled.py index 3870d9ab6..1ab204a32 100644 --- a/mkwutil/progress/percent_decompiled.py +++ b/mkwutil/progress/percent_decompiled.py @@ -1,13 +1,11 @@ from copy import copy from pathlib import Path -import re -from typing import Generator import pytablewriter from pytablewriter.style import Style from termcolor import colored -from mkwutil.lib.slices import Slice, SliceTable +from mkwutil.lib.slices import SliceTable from mkwutil.sections import Section, REL_SECTIONS, DOL_SECTIONS, DOL_LIBS from mkwutil.project import * @@ -37,91 +35,147 @@ def binary_total(sections: list[Section]) -> tuple[int, int]: return code_total, data_total -def to_percent(frac): +def __to_percent(frac): return "%7.3f%%" % (frac * 100) -def analyze(prefix, progress, total): - cells = [""] * 3 - cells[0] = prefix - if total[0]: - cells[1] = to_percent(progress[0] / total[0]) - if total[1]: - cells[2] = to_percent(progress[1] / total[1]) +def analyze( + name: str, + split_code: int, + decomp_code: int, + decomp_data: int, + total_code: int, + total_data: int, +) -> list: + cells = [""] * 4 + cells[0] = name + if total_code: + cells[1] = __to_percent(split_code / total_code) + cells[2] = __to_percent(decomp_code / total_code) + if total_data: + cells[3] = __to_percent(decomp_data / total_data) return cells -def get_progress(slices, filter): - progress = [0, 0] - for o_name, o_code_total, o_data_total in slices: - if not filter in o_name: - continue - - progress[0] += o_code_total - progress[1] += o_data_total - - return progress - - - -def percent_decompiled(dir="."): - dir = Path(dir) - +@dataclass +class Stats: + matrix: list + dol_decomp_code: int + dol_total_code: int + rel_decomp_code: int + rel_total_code: int + + def print(self): + # Make last row bold. + self.matrix[-1] = list( + map(lambda x: colored(x, attrs=["bold"]), self.matrix[-1]) + ) + # Print table. + print("-" * 50) + writer = pytablewriter.BorderlessTableWriter( + headers=["part", "code split", "code decomp", "data decomp"], + column_styles=[ + Style(align="left"), + Style(align="right"), + Style(align="right"), + Style(align="right"), + ], + margin=1, + value_matrix=self.matrix, + ) + writer.write_table() + print("-" * 50) + + # Player stats. + print("Player:") + print( + " - %u BR (main.dol)" + % (self.dol_decomp_code / self.dol_total_code * 4999 + 5000) + ) + print( + " - %u VR (StaticR.rel)" + % (self.rel_decomp_code / self.rel_total_code * 4999 + 5000) + ) + print( + "1 BR = %s lines of asm code." + % (0.1 * round(10 * self.dol_total_code / 4999 / 4)) + ) + print( + "1 VR = %s lines of asm code." + % (0.1 * round(10 * self.rel_total_code / 4999 / 4)) + ) + + +def build_stats(dir: Path) -> Stats: matrix = [] # DOL progress. - dol_slices = load_dol_slices() + dol_split_slices = load_dol_slices() dol_blob_slices = load_dol_binary_blob_slices(dir) - mask_binary_blobs(dol_slices, dol_blob_slices) - dol_progress = simple_count(dol_slices) - dol_total = binary_total(DOL_SECTIONS) - matrix.append(analyze("DOL", dol_progress, dol_total)) + dol_decomp_slices = copy(dol_split_slices) + mask_binary_blobs(dol_decomp_slices, dol_blob_slices) + dol_split_code, _ = simple_count(dol_split_slices) + dol_decomp_code, dol_decomp_data = simple_count(dol_decomp_slices) + dol_total_code, dol_total_data = binary_total(DOL_SECTIONS) + matrix.append( + analyze( + "DOL", + dol_split_code, + dol_decomp_code, + dol_decomp_data, + dol_total_code, + dol_total_data, + ) + ) # DOL Libraries. for lib in DOL_LIBS: assert lib.section == "text", "For now only text section per lib supported" - lib_progress = simple_count(dol_slices.slice(lib.start, lib.stop)) - lib_total = (len(lib), None) - matrix.append(analyze("> " + lib.name, lib_progress, lib_total)) + lib_split_slices = dol_split_slices.slice(lib.start, lib.stop) + lib_decomp_slices = dol_decomp_slices.slice(lib.start, lib.stop) + lib_split_code, _ = simple_count(lib_split_slices) + lib_decomp_code, _ = simple_count(lib_decomp_slices) + lib_total_code = len(lib) + matrix.append( + analyze( + "> " + lib.name, + lib_split_code, + lib_decomp_code, + None, + lib_total_code, + None, + ) + ) # REL progress. - rel_slices = load_rel_slices() - rel_progress = simple_count(rel_slices) - rel_total = binary_total(REL_SECTIONS) - matrix.append(analyze("REL", rel_progress, rel_total)) - # Total progress. - def piecewise_add(x, y): - return list(a + b for a, b in zip(x, y)) - + rel_split_slices = load_rel_slices() + rel_blob_slices = load_rel_binary_blob_slices(dir) + rel_decomp_slices = copy(rel_split_slices) + mask_binary_blobs(rel_decomp_slices, rel_blob_slices) + rel_split_code, _ = simple_count(rel_split_slices) + rel_decomp_code, rel_decomp_data = simple_count(rel_decomp_slices) + rel_total_code, rel_total_data = binary_total(REL_SECTIONS) matrix.append( - [ - colored(cell, attrs=["bold"]) - for cell in analyze( - "TOTAL", - piecewise_add(dol_progress, rel_progress), - piecewise_add(dol_total, rel_total), - ) - ] + analyze( + "REL", + rel_split_code, + rel_decomp_code, + rel_decomp_data, + rel_total_code, + rel_total_data, + ) ) - # Print table. - print("-" * 31) - writer = pytablewriter.BorderlessTableWriter( - headers=["part", "code", "data"], - column_styles=[ - Style(align="left"), - Style(align="right"), - Style(align="right"), - ], - margin=1, - value_matrix=matrix, + matrix.append( + analyze( + "TOTAL", + dol_split_code + rel_split_code, + dol_decomp_code + rel_decomp_code, + dol_decomp_data + rel_decomp_data, + dol_total_code + rel_total_code, + dol_total_data + rel_total_data, + ) + ) + return Stats( + matrix, dol_decomp_code, dol_total_code, rel_decomp_code, rel_total_code ) - writer.write_table() - print("-" * 31) - - # Player stats. - print("Player:") - print(" - %u BR (main.dol)" % (dol_progress[0] / dol_total[0] * 4999 + 5000)) - print(" - %u VR (StaticR.rel)" % (rel_progress[0] / rel_total[0] * 4999 + 5000)) - print("1 BR = %s lines of asm code." % (0.1 * round(10 * dol_total[0] / 4999 / 4))) - print("1 VR = %s lines of asm code." % (0.1 * round(10 * rel_total[0] / 4999 / 4))) if __name__ == "__main__": - percent_decompiled() + build_stats(Path()).print() diff --git a/mkwutil/project.py b/mkwutil/project.py index 7bcb55cdf..90d56d3ee 100644 --- a/mkwutil/project.py +++ b/mkwutil/project.py @@ -17,12 +17,13 @@ def load_rel_slices(sections=None) -> "SliceTable": Path(__file__).parent / ".." / "pack" / "rel_slices.csv", sections=sections ) -def load_dol_binary_blob_slices(dir_="."): - dir_ = Path(dir_) - - path = dir_ / "artifacts" / "target" / "pal" / "main.elf" +def load_dol_binary_blob_slices(dir: Path): + path = dir / "artifacts" / "target" / "pal" / "main.elf" return load_binary_blob_slices(path) +def load_rel_binary_blob_slices(dir: Path): + path = dir / "artifacts" / "target" / "pal" / "StaticR.elf" + return load_binary_blob_slices(path) def read_symbol_map(symbols_path): symbols = SymbolsList() diff --git a/requirements.txt b/requirements.txt index 66d6ec8cdc59fc993d6bc541f5810b12cf36628a..16cc689993a43d4e81c1389ab5730272e4060f6b 100644 GIT binary patch delta 38 scmaFIF@bY~45MTULq0<;LncEWLn1>7Ln?zU5E?P)F_>)@W{hSA0JG-@`~Uy| delta 12 TcmbQh`Ho|Q4C7`y#vo<@94-T- diff --git a/tests/test_slices.py b/tests/test_slices.py index 4645e18ac..39f33b049 100644 --- a/tests/test_slices.py +++ b/tests/test_slices.py @@ -1,3 +1,4 @@ +from copy import copy from pathlib import Path import pytest @@ -146,3 +147,20 @@ def test_slice_table_remove_8(): { 00000002..00000003 slice } { 00000003..00000006 } ]""" + +def test_slice_table_copy(): + table_1 = SliceTable(0, 6) + table_1.add(Slice(0, 2, "slice1")) + table_1.add(Slice(2, 4, "slice2")) + table_2 = copy(table_1) + table_1.slices[0].name = "sliceX" + assert str(table_1) == """[ + { 00000000..00000002 sliceX } + { 00000002..00000004 slice2 } + { 00000004..00000006 } +]""" + assert str(table_2) == """[ + { 00000000..00000002 slice1 } + { 00000002..00000004 slice2 } + { 00000004..00000006 } +]""" From 2e2bb19bdbdb33cfe1f96a83ef81eefa1d4e6d6e Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 30 Jul 2021 18:18:53 -0600 Subject: [PATCH 134/477] :art: Add REL, sections to graphic.py (closes #79) --- mkwutil/progress/graphic.py | 28 ++++++++++++++++++++++---- mkwutil/progress/graphic/index.html.j2 | 9 +++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/mkwutil/progress/graphic.py b/mkwutil/progress/graphic.py index 8b61bb483..376b7d090 100644 --- a/mkwutil/progress/graphic.py +++ b/mkwutil/progress/graphic.py @@ -10,9 +10,9 @@ from pytablewriter import HtmlTableWriter from pytablewriter.style import Style -from mkwutil.sections import DOL_LIBS, DOL_SECTIONS +from mkwutil.sections import DOL_LIBS, DOL_SECTIONS, REL_SECTIONS from mkwutil.lib.slices import Slice, SliceTable -from mkwutil.project import load_dol_slices +import mkwutil.project as project from mkwutil.progress.percent_decompiled import build_stats @@ -85,11 +85,28 @@ def percent_decomp_stats(slices: SliceTable) -> None: print("Code&Data Percent: %s" % (100 * n_code / total)) - def standard_boxes(): - slices = load_dol_slices(sections=DOL_SECTIONS) + slices = project.load_dol_slices(sections=DOL_SECTIONS) + return map(Box.from_slice, slices) + +def rel_boxes(): + slices = project.load_rel_slices(sections=REL_SECTIONS) + return map(Box.from_slice, slices) + +def section_to_slice(s): + return Slice(name=s.name, start=s.start, stop=s.stop, section=s.type) + +def dol_section_boxes(): + slices = SliceTable(sections=DOL_SECTIONS) + for s in DOL_SECTIONS: + slices.add(section_to_slice(s)) return map(Box.from_slice, slices) +def rel_section_boxes(): + slices = SliceTable(sections=REL_SECTIONS) + for s in REL_SECTIONS: + slices.add(section_to_slice(s)) + return map(Box.from_slice, slices) def lib_boxes(): slices = SliceTable(sections=DOL_SECTIONS) @@ -133,6 +150,9 @@ def percent_decompiled_table() -> str: "percents_table": percent_decompiled_table(), "dol_decomp": standard_boxes(), "dol_libraries": lib_boxes(), + "dol_sections": dol_section_boxes(), + "rel_decomp": rel_boxes(), + "rel_sections": rel_section_boxes(), } ).dump(file) if not args.silent: diff --git a/mkwutil/progress/graphic/index.html.j2 b/mkwutil/progress/graphic/index.html.j2 index 3e9c6b949..0f47dc406 100644 --- a/mkwutil/progress/graphic/index.html.j2 +++ b/mkwutil/progress/graphic/index.html.j2 @@ -67,5 +67,14 @@ th {

DOL Decompiled

{{ slices(dol_decomp, "rectangle-tooltip rectangle-below") }} +

DOL Sections

+{{ slices(dol_sections, "rectangle-tooltip") }} + +

REL Decompiled

+{{ slices(rel_decomp, "rectangle-tooltip rectangle-below") }} + +

REL Sections

+{{ slices(rel_sections, "rectangle-tooltip") }} + From c73957873f5b3b3a3e9ac8ffbc5fb62deefc02ec Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 31 Jul 2021 08:28:48 +0200 Subject: [PATCH 135/477] graphic: add borders to table (#91) --- mkwutil/progress/graphic/index.html.j2 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mkwutil/progress/graphic/index.html.j2 b/mkwutil/progress/graphic/index.html.j2 index 0f47dc406..6854ef11e 100644 --- a/mkwutil/progress/graphic/index.html.j2 +++ b/mkwutil/progress/graphic/index.html.j2 @@ -36,7 +36,11 @@ html { background-color: black; visibility: visible; } -th { +table { + border-collapse: collapse; +} +td, th { + border: 1px solid #ddd; padding: .2em 1em; } From 047cdb515d68c3639bbfbf66b3d78896614a510a Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 31 Jul 2021 01:00:30 -0600 Subject: [PATCH 136/477] [build] Support single-file incrementals --- build.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/build.py b/build.py index 4539381e7..7f444da6b 100644 --- a/build.py +++ b/build.py @@ -40,6 +40,11 @@ default=multiprocessing.cpu_count(), help="Compile concurrency", ) +parser.add_argument( + "--single_file", + type=str, + default=None, + help="For quick iteration on a single file before we get full incremental builds") args = parser.parse_args() # Start by running gen_asm. gen_asm(args.regen_asm) @@ -184,7 +189,11 @@ def compile_queued_sources(): """Dispatches multiple threads to compile all queued sources.""" print(colored(f"max_hw_concurrency={args.concurrency}", color="yellow")) - pool = ThreadPool(args.concurrency) + if not len(gSourceQueue): + print(colored("No sources to compile", color="red")) + return + + pool = ThreadPool(min(args.concurrency, len(gSourceQueue))) pool.map(lambda s: compile_source_impl(*s), gSourceQueue) @@ -254,6 +263,11 @@ def compile_sources(): for src in chain(SOURCES_DOL, SOURCES_REL): queue_compile_source(Path(src.src), src.cc, src.opts) + if args.single_file: + print(colored('[NOTE] Only compiling sources matching "%s".' % args.single_file, "red")) + global gSourceQueue + gSourceQueue = list(filter(lambda x: args.single_file in str(x[0]), gSourceQueue)) + compile_queued_sources() asm_files = [ From b2c98ee426e7dce99f54503c84d76bdd100394ad Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 31 Jul 2021 11:04:10 +0200 Subject: [PATCH 137/477] Attempt to fix build.py Wine buffering --- build.py | 88 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/build.py b/build.py index 4539381e7..f01b32345 100644 --- a/build.py +++ b/build.py @@ -8,6 +8,7 @@ import os import os.path from pathlib import Path +from random import randbytes import subprocess import sys @@ -67,15 +68,6 @@ def __native_binary(path): return path -def __windows_binary(path): - if sys.platform == "win32" or sys.platform == "msys": - return path - if sys.platform == "darwin": - return os.path.abspath("./mkwutil/tools/crossover.sh") + " " + path - - return "wine " + path - - VERBOSE = False DEVKITPPC = os.environ.get("DEVKITPPC") @@ -93,12 +85,10 @@ def __windows_binary(path): GAS = __native_binary(os.path.join(DEVKITPPC, "bin", "powerpc-eabi-as")) -MWLD = __windows_binary(os.path.join("tools", "mwldeppc.exe")) +MWLD = os.path.join("tools", "mwldeppc.exe") CWCC_PATHS = { - "default": __windows_binary( - os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") - ), + "default": os.path.join(".", "tools", "4199_60831", "mwcceppc.exe"), # For the main game # August 17, 2007 # 4.2.0.1 Build 127 @@ -107,19 +97,13 @@ def __windows_binary(path): # We don't have this, so we use build 142: # This version has the infuriating bug where random # nops are inserted into your code. - "4201_127": __windows_binary( - os.path.join(".", "tools", "4201_142", "mwcceppc.exe") - ), + "4201_127": os.path.join(".", "tools", "4201_142", "mwcceppc.exe"), # For most of RVL # We actually have the correct version - "4199_60831": __windows_binary( - os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") - ), + "4199_60831": os.path.join(".", "tools", "4199_60831", "mwcceppc.exe"), # For HBM/WPAD, NHTTP/SSL # We use build 60831 - "4199_60726": __windows_binary( - os.path.join(".", "tools", "4199_60831", "mwcceppc.exe") - ), + "4199_60726": os.path.join(".", "tools", "4199_60831", "mwcceppc.exe"), } CW_ARGS = [ @@ -157,24 +141,62 @@ def __windows_binary(path): CWCC_OPT = " ".join(CW_ARGS) +def run_windows_cmd(cmd: str) -> tuple[list[str], int]: + """Runs a shell command and returns the stdout lines.""" + if sys.platform == "win32" or sys.platform == "msys": + return __run_windows_cmd_win32(cmd) + return __run_windows_cmd_wine(cmd) + + +def __run_windows_cmd_win32(cmd: str) -> tuple[list[str], int]: + process = subprocess.Popen( + cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + lines = process.stdout.readlines() + process.wait() + return lines, process.returncode + + +def __unix_tmp_file(): + name = f"mkw-build-{randbytes(16).hex()}.log" + # Wine with /dev/shm is slower than /tmp on Linux! + tmp_dir = Path("/tmp") + file_path = tmp_dir / name + file = open(file_path, "w+") + os.unlink(file_path) # ensure file gets removed after build + return file + + +def __run_windows_cmd_wine(cmd: str) -> tuple[list[str], int]: + if sys.platform == "darwin": + compat = os.path.abspath("./mkwutil/tools/crossover.sh") + else: + compat = "wine" + cmd = f"{compat} {cmd}" + with __unix_tmp_file() as stdout: + process = subprocess.run( + cmd, text=True, stdout=stdout, stderr=subprocess.STDOUT, shell=True + ) + stdout.seek(0, 0) + return stdout.readlines(), process.returncode + + +def __assert_command_success(returncode, command): + assert returncode == 0, f"{command} exited with returncode {returncode}" + + def compile_source_impl(src, dst, version="default", additional="-ipa file"): """Compiles a source file.""" # Compile ELF object file. command = f"{CWCC_PATHS[version]} {CWCC_OPT + ' ' + additional} {src} -o {dst}" - process = subprocess.Popen( - command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True - ) - lines = list(iter(process.stdout.readline, "")) + lines, returncode = run_windows_cmd(command) with print_mutex: print(f'{colored("CC", "green")} {src}') if VERBOSE: print(command) for line in lines: print(" " + line.strip()) - process.wait() - assert ( - process.returncode == 0 - ), f"{command} exited with returncode {process.returncode}" + __assert_command_success(returncode, command) gSourceQueue = [] @@ -243,7 +265,11 @@ def link( ) if partial: cmd.append("-r") - subprocess.run(" ".join(str(x) for x in cmd), check=True, text=True, shell=True) + command = " ".join(map(str, cmd)) + lines, returncode = run_windows_cmd(command) + for line in lines: + print(line) + __assert_command_success(returncode, command) def compile_sources(): From 473c562e14d3cb467a04618a5ae55827c36ae749 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 31 Jul 2021 12:32:12 +0200 Subject: [PATCH 138/477] Optimize gen_asm --- mkwutil/lib/rel.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/mkwutil/lib/rel.py b/mkwutil/lib/rel.py index 347f7b289..ab0cd4619 100644 --- a/mkwutil/lib/rel.py +++ b/mkwutil/lib/rel.py @@ -150,14 +150,20 @@ def load_section(self, index, file): s.length = len(s.data) self.section_info[index] = s - def virtual_read(self, vaddr: int, size: int, sections, section_idx) -> Optional[bytes]: + def virtual_read( + self, vaddr: int, size: int, sections, section_idx + ) -> Optional[bytes]: # Find the virtual address section where vaddr falls into. - section_virtual_idx, section_virtual = next((seg for seg in enumerate(sections) if vaddr in seg[1]), None) + section_virtual_idx, section_virtual = next( + (seg for seg in enumerate(sections) if vaddr in seg[1]), None + ) # Map to REL section number. section_idx = section_idx[section_virtual_idx] # Calculate addres. relative_addr = vaddr - section_virtual.start - result = self.section_info[section_idx].data[relative_addr:relative_addr+size] + result = self.section_info[section_idx].data[ + relative_addr : relative_addr + size + ] if len(result) == size: return result @@ -166,6 +172,7 @@ def virtual_read(self, vaddr: int, size: int, sections, section_idx) -> Optional return result + bytes(size - len(result)) + class RelHeader: """Holds a .rel header.""" @@ -318,9 +325,7 @@ def __init__(self, f=None, rel_offset=None): def reconstruct(self, file): for entry_bin in self.entries: # write R_RVL_SECT - file.write(write_u16(0)) - file.write(write_u8(202)) - file.write(write_u8(entry_bin)) # section id + file.write(struct.pack(">HBB", 0, 202, entry_bin)) # section id # write R_RVL_NOP file.write(b"\0" * 4) @@ -333,18 +338,11 @@ def reconstruct(self, file): class RelRelEntry: def __init__(self, f): - self.offset = read_u16(f) - self.type = read_u8(f) - self.section = read_u8(f) - self.addend = read_u32(f) + data = f.read(struct.calcsize(">HBBL")) + self.offset, self.type, self.section, self.addend = struct.unpack(">HBBL", data) def reconstruct(self): - entry = io.BytesIO() - entry.write(write_u16(self.offset)) - entry.write(write_u8(self.type)) - entry.write(write_u8(self.section)) - entry.write(write_u32(self.addend)) - return entry.getvalue() + return struct.pack(">HBBL", self.offset, self.type, self.section, self.addend) def dump_staticr(rel: Rel, _path: Path) -> None: From 35e9302736eeaa22030354bd8229dfb9aca789c2 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 31 Jul 2021 12:58:57 +0200 Subject: [PATCH 139/477] Fix line endings The EditorConfig specified CRLF, but this repo uses LF. requirements.txt also had wrong line endings. --- .editorconfig | 2 +- requirements.txt | Bin 1168 -> 1102 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 4d7ddb500..f5b53044e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,7 +1,7 @@ root = true [*] -end_of_line = crlf +end_of_line = lf [*.{h,c,hpp,cpp}] indent_style = space diff --git a/requirements.txt b/requirements.txt index 16cc689993a43d4e81c1389ab5730272e4060f6b..dadaf3551281565203b79a002eb455a956624998 100644 GIT binary patch delta 168 zcmbQhd5&X(!bByFi9RwDvlJ%w$W2_MF!7w+#CKYgr5F_^+c3&aj$u@q+`uR^c^;$6 zXG(87h$Un1S^yW>%hjf>~kmD`ut1d@M?njaZ~6`?1JO&S6nt H;$i>*=VL2+ delta 283 zcmX@dF@bY}0weE4c}*bUE(;`*6%~0IxEPWdG8hsWiWpKDQW;7ZY=O{(L65-zjCm(6 zR0OI#C=Vp=Ycujr=41r&Cu=eS1tz;QDg(((Mxa@fTNzb> Date: Sat, 31 Jul 2021 12:54:57 +0200 Subject: [PATCH 140/477] Add per-section rel verification Closes #90. --- build.py | 3 ++- mkwutil/lib/rel.py | 21 +++++++++-------- mkwutil/lib/verify_binary.py | 21 +++++++++++++++++ mkwutil/project.py | 2 +- mkwutil/rel_repack.py | 2 +- mkwutil/verify_main_dol.py | 22 +++--------------- mkwutil/verify_staticr_rel.py | 43 ++++++++++++++++++++++++++++------- 7 files changed, 75 insertions(+), 39 deletions(-) create mode 100644 mkwutil/lib/verify_binary.py diff --git a/build.py b/build.py index eac938a5d..19561dc3f 100644 --- a/build.py +++ b/build.py @@ -369,11 +369,12 @@ def build(): compile_sources() orig_dol_path = Path("artifacts", "orig", "pal", "main.dol") + orig_rel_path = Path("artifacts", "orig", "pal", "StaticR.rel") target_dol_path = link_dol(dol_objects) target_rel_path = link_rel(rel_objects) verify_dol(orig_dol_path, target_dol_path) - verify_rel(target_rel_path) + verify_rel(orig_rel_path, target_rel_path) build_stats(Path()).print() diff --git a/mkwutil/lib/rel.py b/mkwutil/lib/rel.py index ab0cd4619..3dcaa7a75 100644 --- a/mkwutil/lib/rel.py +++ b/mkwutil/lib/rel.py @@ -26,7 +26,7 @@ class Rel: """Holds a .rel library.""" - def __init__(self, file=None): + def __init__(self, file=None, verbose=False): if file is None: module_path = Path(__file__).parent with open(module_path / "rel_header.bin", "rb") as rel_header: @@ -42,13 +42,13 @@ def __init__(self, file=None): # section info stuff self.section_info = [None] * self.header.num_sections for i in range(0, self.header.num_sections): - section = RelSection(file, self.header.section_info_offset, i) + section = RelSection(file, self.header.section_info_offset, i, verbose) self.section_info[i] = section # dumb way to divide by 8 and keep int self.imps = [None] * (self.header.imp_size >> 3) for i in range(0, self.header.imp_size >> 3): - self.imps[i] = RelImpEntry(file, self.header.imp_offset, i) + self.imps[i] = RelImpEntry(file, self.header.imp_offset, i, verbose) # assertion i guess assert self.header.rel_offset == self.imps[0].offset @@ -233,7 +233,7 @@ def reconstruct(self): class RelSection: - def __init__(self, f=None, section_info_offset=None, sid=None): + def __init__(self, f=None, section_info_offset=None, sid=None, verbose=False): if f is None: self.offset = 0 self.unk = False @@ -253,10 +253,12 @@ def __init__(self, f=None, section_info_offset=None, sid=None): # data # skip empty shit and bss - print( - f"section {sid}: {self.offset:X} {self.executable} {self.unk:X} {self.length:X}" - ) + if verbose: + print( + f"section {sid}: {self.offset:X} {self.executable} {self.unk:X} {self.length:X}" + ) if self.offset == 0 or self.length == 0: + self.data = None return f.seek(self.offset, os.SEEK_SET) @@ -278,7 +280,7 @@ def reconstruct_table_entry(self): class RelImpEntry: - def __init__(self, f=None, imp_offset=None, sid=None): + def __init__(self, f=None, imp_offset=None, sid=None, verbose=False): if f is None: self.module_id = 0 self.offset = 0 @@ -288,7 +290,8 @@ def __init__(self, f=None, imp_offset=None, sid=None): f.seek(imp_offset + (sid * 8), os.SEEK_SET) self.module_id = read_u32(f) self.offset = read_u32(f) - print(f"imp {sid}: {self.module_id:X} {self.offset:X}") + if verbose: + print(f"imp {sid}: {self.module_id:X} {self.offset:X}") # funny name diff --git a/mkwutil/lib/verify_binary.py b/mkwutil/lib/verify_binary.py new file mode 100644 index 000000000..d1f6a2c7c --- /dev/null +++ b/mkwutil/lib/verify_binary.py @@ -0,0 +1,21 @@ +import hashlib + +def check_hash(content, expected): + ctx = hashlib.sha1(content) + digest = ctx.hexdigest() + return digest.lower() == expected + + +def format_segment(name, at, at2, want_size, have_size, tag): + return ( + "%10s: at_src=0x%08x at_dst=0x%08x at_end=0x%08x want=0x%08x have=0x%08x [%s]" + % ( + name, + at, + at2, + at + want_size, + want_size, + have_size, + tag, + ) + ) diff --git a/mkwutil/project.py b/mkwutil/project.py index 90d56d3ee..f137d51f0 100644 --- a/mkwutil/project.py +++ b/mkwutil/project.py @@ -56,6 +56,6 @@ def read_enabled_slices(dol, slices_path): def read_rel(rel_path): with open(rel_path, "rb") as file: - return Rel(file) + return Rel(file, True) raise RuntimeError("Cannot find StaticR.rel") diff --git a/mkwutil/rel_repack.py b/mkwutil/rel_repack.py index 727f4f92b..9b14f5626 100644 --- a/mkwutil/rel_repack.py +++ b/mkwutil/rel_repack.py @@ -32,7 +32,7 @@ if args.dump: print(f"Dumping {args.rel} to {args.dir}") with open(args.rel, "rb") as file: - rel = Rel(file) + rel = Rel(file, True) dump_staticr(rel, args.dir) if args.reconstruct: diff --git a/mkwutil/verify_main_dol.py b/mkwutil/verify_main_dol.py index 1fb2e386b..d411c20fe 100644 --- a/mkwutil/verify_main_dol.py +++ b/mkwutil/verify_main_dol.py @@ -4,35 +4,19 @@ import argparse from colorama import Fore, Style -import hashlib from pathlib import Path import sys from .lib.dol import DolBinary - - -def format_segment(name, at, at2, want_size, have_size, tag): - return ( - "%10s: at_src=0x%08x at_dst=0x%08x at_end=0x%08x want=0x%08x have=0x%08x [%s]" - % ( - name, - at, - at2, - at + want_size, - want_size, - have_size, - tag, - ) - ) +from .lib.verify_binary import * def verify_dol(reference: Path, target: Path): """Verifies the target main.dol for authenticity.""" print("[DOL] Verifying...") + content = open(target, "rb").read() - ctx = hashlib.sha1(content) - digest = ctx.hexdigest() - if digest.lower() == "ac7d72448630ade7655fc8bc5fd7a6543cb53a49": + if check_hash(content, "ac7d72448630ade7655fc8bc5fd7a6543cb53a49"): print( Fore.GREEN + Style.BRIGHT diff --git a/mkwutil/verify_staticr_rel.py b/mkwutil/verify_staticr_rel.py index bda1d376e..b10fbf9e4 100644 --- a/mkwutil/verify_staticr_rel.py +++ b/mkwutil/verify_staticr_rel.py @@ -4,17 +4,20 @@ import argparse from colorama import Fore, Style -import hashlib from pathlib import Path import sys +from .lib.rel import Rel +from .lib.verify_binary import * +from .sections import REL_SECTIONS, REL_SECTION_IDX -def verify_rel(target): + +def verify_rel(reference: Path, target: Path): """Verifies the target StaticR.rel for authenticity.""" + print("[REL] Verifying...") + content = open(target, "rb").read() - ctx = hashlib.sha1(content) - digest = ctx.hexdigest() - if digest.lower() == "887bcc076781f5b005cc317a6e3cc8fd5f911300": + if check_hash(content, "887bcc076781f5b005cc317a6e3cc8fd5f911300"): print( Fore.GREEN + Style.BRIGHT @@ -23,8 +26,6 @@ def verify_rel(target): ) return - - want_len = 4903876 if len(content) != want_len: print( @@ -33,6 +34,29 @@ def verify_rel(target): + Style.RESET_ALL ) + with open(reference, "rb") as file: + good = Rel(file) + with open(target, "rb") as file: + bad = Rel(file) + + for i, idx in enumerate(REL_SECTION_IDX): + good_section = good.section_info[idx] + bad_section = bad.section_info[idx] + match = good_section.data == bad_section.data + tag = "OK" if match else "FAIL" + if good_section.length != bad_section.length: + tag = "SIZE" + print( + format_segment( + REL_SECTIONS[i].name, + good_section.offset, + bad_section.offset, + good_section.length, + bad_section.length, + tag, + ) + ) + print( Fore.RED + Style.BRIGHT + "[REL] Oof: Output doesn't match." + Style.RESET_ALL ) @@ -43,7 +67,10 @@ def verify_rel(target): if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( - "--target", type=Path, required=True, help="Path to target main.dol" + "--reference", type=Path, required=True, help="Path to reference StaticR.rel" + ) + parser.add_argument( + "--target", type=Path, required=True, help="Path to target StaticR.rel" ) args = parser.parse_args() From 2c7fccf323f00545515ce5aef29e60e5ef478d06 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 31 Jul 2021 14:02:41 +0200 Subject: [PATCH 141/477] README.md: add build badge --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 654d15119..37ddf584c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # mkw + +![build badge](https://github.com/riidefi/mkw/actions/workflows/build.yml/badge.svg?branch=master) + A matching decompilation of Mario Kart Wii. All code in this repository will compile 1:1 to the original game. It produces the following files: mkw_pal.dol: `sha1: ac7d72448630ade7655fc8bc5fd7a6543cb53a49` From e5b8be1f972bac650236aa4a4c07d03b8fb758a3 Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Sat, 31 Jul 2021 19:01:30 +0200 Subject: [PATCH 142/477] Fix rel rodata virtual address and remove hack (#100) --- mkwutil/pack_staticr_rel.py | 5 ----- mkwutil/sections.py | 2 +- pack/rel_objects.txt | 2 +- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/mkwutil/pack_staticr_rel.py b/mkwutil/pack_staticr_rel.py index 7ef771cc1..45b71ffd8 100644 --- a/mkwutil/pack_staticr_rel.py +++ b/mkwutil/pack_staticr_rel.py @@ -60,11 +60,6 @@ def pack_staticr_rel(elf_path, rel_path, orig_dir): rel.section_info[5] = read_elf_sec(elf, ".data") rel.section_info[6] = read_elf_sec(elf, ".bss") - # .rodata padding hack - rodata = rel.section_info[4] - rodata.data = rodata.data[:-16] - rodata.length = len(rodata.data) - # Jump to _Unresolved _unresolved = 0x805553B0 text = rel.section_info[1] diff --git a/mkwutil/sections.py b/mkwutil/sections.py index a7ce3bd43..863596ff3 100644 --- a/mkwutil/sections.py +++ b/mkwutil/sections.py @@ -53,7 +53,7 @@ def __contains__(self, key) -> bool: Section("text", "code", 0x805103B4, 0x8088F400), Section("ctors", "data", 0x8088F400, 0x8088F704), Section("dtors", "data", 0x8088F704, 0x8088F710), - Section("rodata", "data", 0x8088F710, 0x808B2BD0), + Section("rodata", "data", 0x8088F720, 0x808B2BD0), Section("data", "data", 0x808B2BD0, 0x808DD3D4), Section("bss", "bss", 0x809BD6E0, 0x809C4F90), ] diff --git a/pack/rel_objects.txt b/pack/rel_objects.txt index ced7a13bf..8f35c0571 100644 --- a/pack/rel_objects.txt +++ b/pack/rel_objects.txt @@ -1,7 +1,7 @@ out/rel/text_805103b4_80512694.o out/rel/ctors_8088f400_8088f704.o out/rel/dtors_8088f704_8088f710.o -out/rel/rodata_8088f710_808b2bd0.o +out/rel/rodata_8088f720_808b2bd0.o out/rel/data_808b2bd0_808b2c30.o out/rel/bss_809bd6e0_809bd6e8.o out/JmpResourceCourse.o From 838933c17413cda9cd3f78a176b56605f37f4bd1 Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Sat, 31 Jul 2021 22:50:56 +0200 Subject: [PATCH 143/477] Decompile Util::Random (#98) --- pack/rel_objects.txt | 10 ++++++--- pack/rel_slices.csv | 1 + source/game/util/Random.cpp | 43 +++++++++++++++++++++++++++++++++++++ source/game/util/Random.hpp | 32 +++++++++++++++++++++++++++ sources.py | 1 + 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 source/game/util/Random.cpp create mode 100644 source/game/util/Random.hpp diff --git a/pack/rel_objects.txt b/pack/rel_objects.txt index 8f35c0571..905d59e28 100644 --- a/pack/rel_objects.txt +++ b/pack/rel_objects.txt @@ -1,12 +1,16 @@ out/rel/text_805103b4_80512694.o out/rel/ctors_8088f400_8088f704.o out/rel/dtors_8088f704_8088f710.o -out/rel/rodata_8088f720_808b2bd0.o +out/rel/rodata_8088f720_80891370.o out/rel/data_808b2bd0_808b2c30.o out/rel/bss_809bd6e0_809bd6e8.o out/JmpResourceCourse.o -out/rel/text_805127ec_80590128.o -out/rel/data_808b2c3c_808dd3d4.o +out/rel/text_805127ec_80555464.o +out/rel/data_808b2c3c_808b42e0.o +out/Random.o +out/rel/text_8055572c_80590128.o +out/rel/rodata_80891380_808b2bd0.o +out/rel/data_808b42ec_808dd3d4.o out/rel/bss_809bd6ec_809c1900.o out/KartComponent.o out/rel/text_805901d0_805f8b34.o diff --git a/pack/rel_slices.csv b/pack/rel_slices.csv index e6ba92504..6e9d5e6b4 100644 --- a/pack/rel_slices.csv +++ b/pack/rel_slices.csv @@ -1,4 +1,5 @@ enabled,name,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd 1,source/game/jmap/JmpResourceCourse.cpp,0x80512694,0x805127ec,,,,,,,0x808B2C30,0x808B2C3C,0x809BD6E8,0x809BD6EC +1,source/game/util/Random.cpp,0x80555464,0x8055572c,,,,,0x80891370,0x80891380,0x808b42e0,0x808b42ec,, 1,source/game/kart/KartComponent.cpp,0x80590128,0x805901d0,,,,,,,,,0x809c1900,0x809c1910 1,source/game/ui/MessageGroup.cpp,0x805F8B34,0x805F8B90,,,,,,,,,, diff --git a/source/game/util/Random.cpp b/source/game/util/Random.cpp new file mode 100644 index 000000000..756244beb --- /dev/null +++ b/source/game/util/Random.cpp @@ -0,0 +1,43 @@ +#include "Random.hpp" + +#include + +namespace Util { + +Random::Random() { + mX = 0; + mSeed = 0; + reseed(); +} + +void Random::reseed() { + u32 tick = OSGetTick(); + u32 seed = (tick & 0xfff) << 0x10 | tick >> 0x10; + mX = seed; + mSeed = seed; +} + +Random::Random(u32 seed) { + mX = seed; + mSeed = seed; +} + +Random::~Random() {} + +u32 Random::nextU32() { + mX = 7567025607324980273 * mX + 5279421; + return mX >> 0x20; +} + +u32 Random::nextU32(u32 range) { + mX = 7567025607324980273 * mX + 5279421; + return ((mX >> 0x20) * range) >> 0x20; +} + +const f32 Random::mul = 2.3283064e-10f; // 1 / (2 ^ 32) + +f32 Random::nextF32() { return Random::mul * nextU32(); } + +f32 Random::nextF32(f32 range) { return range * nextF32(); } + +} // namespace Util diff --git a/source/game/util/Random.hpp b/source/game/util/Random.hpp new file mode 100644 index 000000000..48d7a6fa3 --- /dev/null +++ b/source/game/util/Random.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace Util { + +class Random { +public: + Random(); + + void reseed(); + + Random(u32 seed); + + virtual ~Random(); + + u32 nextU32(); + + u32 nextU32(u32 range); + + f32 nextF32(); + + f32 nextF32(f32 range); + +private: + static const f32 mul; + + u64 mX; + u64 mSeed; +}; + +} // namespace Util diff --git a/sources.py b/sources.py index 63dd760bc..27c708d8f 100644 --- a/sources.py +++ b/sources.py @@ -185,5 +185,6 @@ class Source: Source(src="source/game/ui/ControlGroup.cpp", cc='4201_127', opts=REL_OPTS), Source(src="source/game/ui/UIControl.cpp", cc='4201_127', opts=REL_OPTS), Source(src="source/game/jmap/JmpResourceCourse.cpp", cc='4201_127', opts=REL_OPTS), + Source(src="source/game/util/Random.cpp", cc='4201_127', opts=REL_OPTS), Source(src="source/game/kart/KartComponent.cpp", cc='4201_127', opts=REL_OPTS), ] From e78f353d3b4386cb72b8cda394cdbd0c36a5f2da Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 31 Jul 2021 22:51:51 +0200 Subject: [PATCH 144/477] HACK.md: Document mkwutil (#97) --- structure.md => HACK.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) rename structure.md => HACK.md (60%) diff --git a/structure.md b/HACK.md similarity index 60% rename from structure.md rename to HACK.md index b6aef76bc..f8a90f3d3 100644 --- a/structure.md +++ b/HACK.md @@ -12,6 +12,10 @@ Contains the reference artifacts from the original game, and the generated final - `./artifacts/target/pal/main.dol`: Ouptut main executable. - `./artifacts/target/pal/StaticR.rel`: Output library. +### `./build.py`: Main build script + +Uses `mkwutil` to rebuild MKW from decompiled sources and verifies the result. + ### `./pack`: Link instructions Specifies which sections are decompiled and holds information needed to reconstruct final binaries. @@ -54,6 +58,31 @@ Compiled / assembled object files. Default path of various compiler tooling. -### `./mkwutil`: Python utilities +### `./mkwutil`: Python code + +The `mkwutil` Python package contains tooling for building and verifying artifacts. + +The Python files here implement parts of the build process. +You can run them individually using `python -m mkwutil.`. +Please refer to their `--help` section or source code for usage instructions. + +#### `./mkwutil/lib`: Shared library code + +Various Python utilities. +- `./slices.py` maintains a memory map of code / data areas. + Its primary purpose is to keep track which memory ranges are decompiled. + Data is persisted using the `slices.csv` format. +- `./symbols.py`: Symbol list +- `./dol.py`: main.dol binary file format +- `./rel.py`: *.rel binary file format +- `./binary_blob.py`: Binary blob (inline ASM) masking + to improve `percent_decompiled` accuracy. +- `./verify_binary.py`: Binary verification subroutines + +#### `./mkwutil/progress`: Decompile progress + +Tools to calculate and visualize the progress of the project. + +#### `./mkwutil/tools`: Miscellaneous tools -Various Python utilities packed in a module. +Dumping ground for tools outside of the build process. From 677bcce7ee11c28df103d046a60ffaa405ca1a9e Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 1 Aug 2021 00:04:51 +0200 Subject: [PATCH 145/477] CI: Upload badge artifact --- .github/workflows/build.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 815a81d68..c92aca516 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -133,6 +133,15 @@ jobs: export DISPLAY=:0.0 python ./build.py -j 32 + - id: get_progress + name: Get progress + run: python -m mkwutil.progress.percent_decompiled --short > badge_progress.txt + + - uses: actions/upload-artifact@v2 + with: + name: badge_progress + path: badge_progress.txt + - name: Run graphic.py run: python -m mkwutil.progress.graphic From b9649d79255d0b671e6befed1a48c8080fe2b2eb Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 31 Jul 2021 13:35:08 +0200 Subject: [PATCH 146/477] percent_decompiled: add short mode --- mkwutil/progress/percent_decompiled.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/mkwutil/progress/percent_decompiled.py b/mkwutil/progress/percent_decompiled.py index 1ab204a32..f9b9e5faa 100644 --- a/mkwutil/progress/percent_decompiled.py +++ b/mkwutil/progress/percent_decompiled.py @@ -1,3 +1,4 @@ +import argparse from copy import copy from pathlib import Path @@ -177,5 +178,21 @@ def build_stats(dir: Path) -> Stats: ) +def __main(): + parser = argparse.ArgumentParser() + parser.add_argument( + "-s", "--short", action="store_true", help="Only print percentage" + ) + args = parser.parse_args() + stats = build_stats(Path()) + if args.short: + progress = (stats.dol_decomp_code + stats.rel_decomp_code) / ( + stats.dol_total_code + stats.rel_total_code + ) + print(__to_percent(progress).strip()) + else: + stats.print() + + if __name__ == "__main__": - build_stats(Path()).print() + __main() From 2290c0c3cf2775ba5cac8d4667950fc3849130dc Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 1 Aug 2021 00:59:22 +0200 Subject: [PATCH 147/477] Separate DOL, REL badges --- .github/workflows/build.yml | 17 ++++++++++++----- mkwutil/progress/percent_decompiled.py | 12 +++++++++--- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c92aca516..409576ecb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -133,14 +133,21 @@ jobs: export DISPLAY=:0.0 python ./build.py -j 32 - - id: get_progress - name: Get progress - run: python -m mkwutil.progress.percent_decompiled --short > badge_progress.txt + - name: Get DOL progress + run: python -m mkwutil.progress.percent_decompiled --short --part DOL > badge_dol_progress.txt - uses: actions/upload-artifact@v2 with: - name: badge_progress - path: badge_progress.txt + name: badge_dol_progress + path: badge_dol_progress.txt + + - name: Get REL progress + run: python -m mkwutil.progress.percent_decompiled --short --part REL > badge_rel_progress.txt + + - uses: actions/upload-artifact@v2 + with: + name: badge_rel_progress + path: badge_rel_progress.txt - name: Run graphic.py run: python -m mkwutil.progress.graphic diff --git a/mkwutil/progress/percent_decompiled.py b/mkwutil/progress/percent_decompiled.py index f9b9e5faa..835cd02bb 100644 --- a/mkwutil/progress/percent_decompiled.py +++ b/mkwutil/progress/percent_decompiled.py @@ -183,12 +183,18 @@ def __main(): parser.add_argument( "-s", "--short", action="store_true", help="Only print percentage" ) + parser.add_argument("--part", choices=["DOL", "REL"], default=None) args = parser.parse_args() stats = build_stats(Path()) if args.short: - progress = (stats.dol_decomp_code + stats.rel_decomp_code) / ( - stats.dol_total_code + stats.rel_total_code - ) + if args.part == "DOL": + progress = stats.dol_decomp_code / stats.dol_total_code + elif args.part == "REL": + progress = stats.rel_decomp_code / stats.rel_total_code + else: + decomp_code = stats.dol_decomp_code + stats.rel_decomp_code + total_code = stats.dol_total_code + stats.rel_total_code + progress = decomp_code / total_code print(__to_percent(progress).strip()) else: stats.print() From 57f2f3869022f95de8ad9443773470fbb88dc900 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 1 Aug 2021 01:16:32 +0200 Subject: [PATCH 148/477] README.md: add progress badges Closes #99 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 37ddf584c..c2de9bcf0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # mkw ![build badge](https://github.com/riidefi/mkw/actions/workflows/build.yml/badge.svg?branch=master) +![dol progress badge](https://us-central1-mkw-re.cloudfunctions.net/GenBadgeHTTP?repo=riidefi/mkw&branch=master&run=build&badge=dol_progress&subject=DOL) +![rel progress badge](https://us-central1-mkw-re.cloudfunctions.net/GenBadgeHTTP?repo=riidefi/mkw&branch=master&run=build&badge=rel_progress&subject=REL) A matching decompilation of Mario Kart Wii. All code in this repository will compile 1:1 to the original game. It produces the following files: From d3bd4b22ca425cffbae0ec938784a5b810dc96c2 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 31 Jul 2021 17:49:26 -0600 Subject: [PATCH 149/477] [HostSys] Decompile SystemResource.cpp --- pack/dol_objects.txt | 7 ++++-- pack/dol_slices.csv | 1 + source/game/host_system/SystemResource.cpp | 26 +++++++++++++++++++++ source/game/host_system/SystemResources.hpp | 25 ++++++++++++++++++++ sources.py | 4 ++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 source/game/host_system/SystemResource.cpp create mode 100644 source/game/host_system/SystemResources.hpp diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 4dad7a3db..dc9d99ce9 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -4,10 +4,13 @@ out/rvlTrkMem.o out/dol/init_80006068_80006460.o out/dol/extab_80006460_80006a20.o out/dol/extabindex_80006a20_800072c0.o -out/dol/text_800072c0_8006a0c0.o +out/dol/text_800072c0_80007f50.o out/dol/ctors_80244de0_80244e88.o out/dol/dtors_80244ea4_80244eac.o -out/dol/rodata_80244ec0_80248010.o +out/dol/rodata_80244ec0_80245010.o +out/SystemResource.o +out/dol/text_80007f7c_8006a0c0.o +out/dol/rodata_802450b0_80248010.o out/dol/data_80258580_80274148.o out/dol/bss_802a4080_802f2338.o out/dol/sdata_80384c00_803850a0.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index f2b2f9166..875899262 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,6 +1,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End 1,,asm/trkHeader.s,0x80004000,0x80004100,,,,,,,,,,,,,,,,,,,,,,,, 1,,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, +1,,source/game/host_system/SystemResource.cpp,,,,,,,0x80007f50,0x80007f7c,,,,,0x80245010,0x802450b0,,,,,,,,,,,, 1,,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0,0x8006a518,,,,,,,,,,,,,,,0x80387cac,0x80387cd8,, 1,,source/nw4r/g3d/g3d_fog.cpp,,,,,,,0x800774d0,0x800775d0,,,,,,,,,,,,,,,0x80387d58,0x80387d5C,, 1,,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, diff --git a/source/game/host_system/SystemResource.cpp b/source/game/host_system/SystemResource.cpp new file mode 100644 index 000000000..16a822d92 --- /dev/null +++ b/source/game/host_system/SystemResource.cpp @@ -0,0 +1,26 @@ +#include "SystemResources.hpp" + +namespace System { +namespace Resource { + +struct SystemResourceEntry { + const char* path; + u32 id; +}; + +const SystemResourceEntry sSystemResources[] = { + {"/contents/RFLRes01.arc", 4}, + {"/contents/HomeButton.arc", 5}, + {"/contents/HomeButtonSe.arc", 6}, + {"/contents/globe.arc", 7}, + // Channel only + {"/RKChRes.arc", 8}}; + +const char* GetResourcePath(eSystemResource idx) { + return sSystemResources[idx].path; +} + +u32 GetResourceID(eSystemResource idx) { return sSystemResources[idx].id; } + +} // namespace Resource +} // namespace System diff --git a/source/game/host_system/SystemResources.hpp b/source/game/host_system/SystemResources.hpp new file mode 100644 index 000000000..e6f0677ce --- /dev/null +++ b/source/game/host_system/SystemResources.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace System { + +enum eSystemResource { + SYS_RES_RFL, + SYS_RES_HOME_BUTTON, + SYS_RES_HOME_BUTTON_SE, + SYS_RES_GLOBE, + SYS_RES_CHANNEL, // ChRes + + SYS_RES_NUM_MAIN = SYS_RES_CHANNEL +}; + +namespace Resource { + +const char* GetResourcePath(eSystemResource idx); +u32 GetResourceID(eSystemResource idx); + +} // namespace Resource + +} // namespace System + \ No newline at end of file diff --git a/sources.py b/sources.py index 27c708d8f..95cb6c2ae 100644 --- a/sources.py +++ b/sources.py @@ -25,6 +25,9 @@ class Source: # main.dol # +SOURCES_HOSTSYS = [ + Source(src="source/game/host_system/SystemResource.cpp", cc='4201_127', opts=REL_OPTS), +] SOURCES_TRK = [ Source(src="source/rvl/trk/rvlTrkMem.c", cc='4199_60831', opts=RVL_OPTS), @@ -156,6 +159,7 @@ class Source: Source(src="source/egg/math/eggVector.cpp", cc='4201_127', opts=EGG_OPTS), ] SOURCES_DOL = list(chain( + SOURCES_HOSTSYS, SOURCES_TRK, SOURCES_RVL_ARC, SOURCES_RVL_FS, From e9e77e7db14f5c01ee42f68469fbfb62bc5ae727 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 31 Jul 2021 18:22:37 -0600 Subject: [PATCH 150/477] [EGG] Start eggSceneManager.cpp (rest needs matching) --- mkwutil/tools/dump_symbols.py | 2 +- mkwutil/tools/format_symbols.py | 2 +- pack/dol_objects.txt | 9 +- pack/dol_slices.csv | 1 + pack/symbols.txt | 1 + source/egg/core/eggColorFader.hpp | 4 +- source/egg/core/eggSceneManager.cpp | 420 ++++++++++++++++++++ source/egg/core/eggSceneManager.hpp | 246 ++++++++++++ source/game/host_system/SystemResources.hpp | 1 - sources.py | 1 + 10 files changed, 679 insertions(+), 8 deletions(-) create mode 100644 source/egg/core/eggSceneManager.cpp create mode 100644 source/egg/core/eggSceneManager.hpp diff --git a/mkwutil/tools/dump_symbols.py b/mkwutil/tools/dump_symbols.py index 6d24a3a2d..0963aab2f 100644 --- a/mkwutil/tools/dump_symbols.py +++ b/mkwutil/tools/dump_symbols.py @@ -7,7 +7,7 @@ from elftools.elf.elffile import ELFFile from elftools.elf.sections import StringTableSection, Symbol as ELFSymbol -from mkwutil.symbols import Symbol, SymbolsList +from mkwutil.lib.symbols import Symbol, SymbolsList @dataclass diff --git a/mkwutil/tools/format_symbols.py b/mkwutil/tools/format_symbols.py index 37daac54f..6f79e03d9 100644 --- a/mkwutil/tools/format_symbols.py +++ b/mkwutil/tools/format_symbols.py @@ -2,7 +2,7 @@ import os from pathlib import Path -from mkwutil.symbols import SymbolsList +from mkwutil.lib.symbols import SymbolsList parser = argparse.ArgumentParser(description="Reformats symbols.txt") parser.add_argument("symbols", type=Path, help="Path to symbols.txt") diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index dc9d99ce9..b04b0f391 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -233,18 +233,21 @@ out/dol/sdata2_80388960_80388d68.o out/eggHeap.o out/dol/text_80229fac_80239dfc.o out/eggQuat.o -out/dol/text_80239e10_80242498.o +out/dol/text_80239e10_8023addc.o out/dol/rodata_80257824_802582e0.o out/dol/data_802a30ec_802a3f78.o +out/dol/bss_80384348_80384b60.o +out/dol/sbss_80386ec0_80386f00.o +out/eggSceneManager.o +out/dol/text_8023ae4c_80242498.o out/eggStreamDecomp.o out/dol/text_80242504_802432e0.o out/dol/data_802a3f90_802a3fc0.o -out/dol/bss_80384348_80384b60.o out/eggThread.o out/eggUnitHeap.o out/dol/data_802a4004_802a4040.o out/dol/bss_80384b6c_80384b70.o -out/dol/sbss_80386ec0_80386f78.o +out/dol/sbss_80386f10_80386f78.o out/dol/sdata2_80388d80_803890f8.o out/eggVector.o out/dol/ctors_80244e8c_80244e90.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 875899262..97b957d4c 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -86,6 +86,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68,0x80388D80,, 1,,source/egg/math/eggQuat.cpp,,,,,,,0x80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, ,,source/egg/core/eggScene.cpp,,,,,,,0x8023AD10,0x8023ADDC,,,,,,,,,,,,,,,,,, +1,,source/egg/core/eggSceneManager.cpp,,,,,,,0x8023ADDC,0x8023AE4C,,,,,,,,,,,,,0x80386F00,0x80386F10,,,, 1,,source/egg/core/eggStreamDecomp.cpp,,,,,,,0x80242498,0x80242504,,,,,,,0x802A3F78,0x802A3F90,,,,,,,,,, ,,source/egg/core/eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,0x80386F60,0x80386F64,,,, 1,,source/egg/core/eggThread.cpp,,,,,,,0x802432E0,0x80243754,,,,,,,0x802A3FC0,0x802A3FD8,0x80384B60,0x80384B6C,,,,,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index b3bf813cb..fc8d1a0a6 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -1203,4 +1203,5 @@ 0x8022f85c frsqrt__Q23EGG5MathfFf 0x80270fd0 __ctype_mapC 0x80271148 _current_locale +0x802a3e08 __vt__Q23EGG12SceneManager 0x80385520 NNMagicData diff --git a/source/egg/core/eggColorFader.hpp b/source/egg/core/eggColorFader.hpp index 98d0b2910..aa5b84f89 100644 --- a/source/egg/core/eggColorFader.hpp +++ b/source/egg/core/eggColorFader.hpp @@ -1,6 +1,6 @@ #pragma once -#include -#include +#include +#include namespace EGG { class Fader { diff --git a/source/egg/core/eggSceneManager.cpp b/source/egg/core/eggSceneManager.cpp new file mode 100644 index 000000000..71898d5e6 --- /dev/null +++ b/source/egg/core/eggSceneManager.cpp @@ -0,0 +1,420 @@ +#include +#include +#include + +namespace EGG { + +u32 SceneManager::sHeapOptionFlg; +Heap* SceneManager::sHeapMem1_ForCreateScene; +Heap* SceneManager::sHeapMem2_ForCreateScene; +Heap* SceneManager::sHeapDebug_ForCreateScene; + +SceneManager::SceneManager(SceneCreator* creator) { + mSceneCreator = creator; + mCurrentScene = nullptr; + _1C = -1; + _18 = -1; + mNextSceneID = -1; + mTransitionStatus = STATUS_IDLE; + mCurrentFader = nullptr; + bUseMem2 = true; + createDefaultFader(); +} +#if 0 +bool SceneManager::fadeIn() { return mCurrentFader->fadeIn(); } + +// calculate the scene and fader +void SceneManager::calc() { + this->calcCurrentScene(); + this->calcCurrentFader(); +} + +// draw the scene and fader +void SceneManager::draw() { + this->drawCurrentScene(); + this->drawCurrentFader(); +} + +void SceneManager::reinitCurrentScene() { + if (mCurrentScene) + mCurrentScene->reinit(); +} + +bool SceneManager::reinitCurrentSceneAfterFadeOut() { + bool returnValue = false; + + if (this->mTransitionStatus == STATUS_IDLE && + this->mCurrentFader->fadeOut()) { + this->mTransitionStatus = STATUS_REINITIALIZE; + returnValue = true; + } + + return returnValue; +} + +// Exit current scene to parent, then manuver to sibling scene ID +// Not a real symbol +void SceneManager::ChangeUncleScene(int ID) { + while (this->mCurrentScene) { + // destroy current scene then go to parent + this->destroyCurrentSceneNoIncoming(true); + } + this->changeSiblingScene(ID); +} + +void SceneManager::changeSiblingScene(int ID) { + this->mNextSceneID = ID; + changeSiblingScene(); +} + +bool SceneManager::changeSiblingSceneAfterFadeOut(int ID) { + bool returnValue = false; + if (this->mTransitionStatus == -1 && !this->mCurrentFader->fadeOut()) { + this->mNextSceneID = ID; + this->mTransitionStatus = STATUS_CHANGE_SIBLING_SCENE; + returnValue = true; + } + + return returnValue; +} + +void SceneManager::changeSiblingScene() { + Scene* curScene = this->mCurrentScene; // r4 + + Scene* parentScene = curScene ? curScene->getParentScene() : NULL; // r31 + + if (curScene) { + this->destroyScene(curScene); + this->mCurrentScene = NULL; + + s32 r30 = mNextSceneID; + this->setupNextSceneID(); + this->createScene(r30, parentScene); + } +} + +void SceneManager::createScene(int ID, Scene* parentScene) { + Heap* pParentHeap_Mem1; // r26 + Heap* pParentHeap_Mem2; // r25 + Heap* pParentHeap_Debug; // r24 + + // if the scene is not NULL (I imagine only NULL for root?), we want to + // build off that + if (parentScene) { + pParentHeap_Mem1 = parentScene->getHeap_Mem1(); + pParentHeap_Mem2 = parentScene->getHeap_Mem2(); + pParentHeap_Debug = parentScene->getHeap_Debug(); + } + // otherwise, grab the system heaps + else { + BaseSystem* sys = EGG::ConfigurationData::sConfigData; + pParentHeap_Mem1 = sys->mRootHeapMem1; + pParentHeap_Mem2 = sys->mRootHeapMem2; + pParentHeap_Debug = sys->mRootHeapDebug; + } + + Heap* pNewHeap = bUseMem2 ? pParentHeap_Mem2 : pParentHeap_Mem1; // r28 + + WiiSportsAssert(pParentHeap_Mem1 && pParentHeap_Mem2, "eggSceneManager.cpp", + 267, "pParentHeap_Mem1 && pParentHeap_Mem2"); + + // same val from "DAME DAME!" + int r27 = pNewHeap->mFlag & 1; + if (r27) + pNewHeap->mFlag &= ~1; + + ExpHeap* r23 = ExpHeap::create(-1, pNewHeap, sHeapOptionFlg); // r23 + + ExpHeap* pNewHeap_Debug = NULL; // r22 + ExpHeap* pNewHeap_Mem1; // r25; overwrites pParentHeap_Mem2 + ExpHeap* pNewHeap_Mem2; // r26; overwrites pParentHeap_Mem1 + + if (pNewHeap == pParentHeap_Mem2) { + pNewHeap_Mem1 = ExpHeap::create(-1, pParentHeap_Mem1, sHeapOptionFlg); + pNewHeap_Mem2 = (EGG::ExpHeap*)r23; + } else { + pNewHeap_Mem2 = ExpHeap::create(-1, pParentHeap_Mem2, sHeapOptionFlg); + pNewHeap_Mem1 = (EGG::ExpHeap*)r23; + } + // If we have a debug heap, we want to create a child debug heap + // The system debug heap will in normal game logic be created if and only if + // MEM2 size exceeds retail size. Since the root entry, would build off + // this, we would not expect this code to run on a game running on a non + // development unit + if (pParentHeap_Debug) + pNewHeap_Debug = ExpHeap::create(-1, pParentHeap_Debug, sHeapOptionFlg); + + // Set static heap pointers + sHeapMem1_ForCreateScene = pNewHeap_Mem1; + sHeapMem2_ForCreateScene = pNewHeap_Mem2; + sHeapDebug_ForCreateScene = pNewHeap_Debug; + + WiiSportsAssert(pNewHeap && pNewHeap_Mem1 && pNewHeap_Mem2, + "eggSceneManager.cpp", 299, + "pNewHeap && pNewHeap_Mem1 && pNewHeap_Mem2"); + + // if we unset that value above, we need to restore it + if (r27) + pNewHeap->mFlag |= 1; + + // Let's make the new heap the current heap + r23->becomeCurrentHeap(); + + // Create the scene through the scene creator. + WiiSportsAssert(this->mSceneCreator, "eggSceneManager.cpp", 311, + "mSceneCreator"); + Scene* pNewScene = this->mSceneCreator->create(ID); // r3 + WiiSportsAssert(pNewScene, "eggSceneManager.cpp", 313, "pNewScene"); + + // Inline setters below + if (parentScene) + parentScene->setChildScene(pNewScene); + this->mCurrentScene = pNewScene; + pNewScene->setSceneID(ID); + pNewScene->setParentScene(parentScene); + pNewScene->setSceneMgr(this); + pNewScene->enter(); +} +void SceneManager::createChildScene(int ID, Scene* pScene) { + this->outgoingParentScene(pScene); + this->mNextSceneID = ID; + this->setupNextSceneID(); + this->createScene(ID, pScene); +} +// TODO: match to real symbol +bool SceneManager::destroyCurrentSceneNoIncoming(bool destroyRootIfNoParent) { + bool result = false; // r30 + + Scene* curScene = mCurrentScene; // r5 + if (curScene == NULL) + return false; + + Scene* parent = curScene->getParentScene(); // r31 + if (parent) { + this->destroyScene(parent->getChildScene()); + this->mNextSceneID = parent->getSceneID(); + this->setupNextSceneID(); + return true; + } else if (destroyRootIfNoParent) { + this->destroyScene(curScene); + this->mNextSceneID = -1; + this->setupNextSceneID(); + } + return false; +} +bool SceneManager::destroyToSelectSceneID(int ID) { + Scene* parent = this->findParentScene(ID); // r31 + + // This ain't it, chief! + if (parent == NULL) + return false; + // inline destroyCurrentSceneNoIncoming? + while (parent->getSceneID() != this->_18) { + Scene* parent; // r30 + if (this->mCurrentScene && + (parent = this->mCurrentScene->getParentScene())) { + this->destroyScene(parent->getChildScene()); + this->mNextSceneID = parent->getSceneID(); + this->setupNextSceneID(); + } + } + this->incomingCurrentScene(); + + return true; +} +void SceneManager::destroyScene(Scene* pScene) { + pScene->exit(); + GXFlush(); + GXDrawDone(); // GXDraw in IDA? + + // If we have a child scene, destroy that and so on + if (pScene->getChildScene()) + this->destroyScene(pScene->getChildScene()); + GXFlush(); + GXDrawDone(); + + Scene* parent = pScene->getParentScene(); // r31 + this->mSceneCreator->destroy(pScene->getSceneID()); + this->mCurrentScene = NULL; + + // If we're not the root scene, delete ourselves from our parent + // and make the current scene our parent + if (parent) { + parent->setChildScene(NULL); + this->mCurrentScene = parent; + } + + // If the scene has a debug heap, destroy it + if (pScene->getHeap_Debug()) + pScene->getHeap_Debug()->destroy(); + + // ??? + if (pScene->getHeap_Mem1() != pScene->getHeap()) { + if (pScene->getHeap_Mem2() == pScene->getHeap()) { + pScene->getHeap_Mem1()->destroy(); + pScene->getHeap_Mem2()->destroy(); + } + // If pScene pScene->getHeap_Mem1() != pScene->getHeap() != + // pScene->getHeap_Mem2() we do nothing + } else { + pScene->getHeap_Mem2()->destroy(); + pScene->getHeap_Debug()->destroy(); + } + Heap* r31_second; + // If we have a parent, use it's heap + if (parent) { + r31_second = parent->getHeap(); + } + // If we're root, rederive it + else { + r31_second = this->bUseMem2 ? BaseSystem::sConfigData->mRootHeapMem2 + : BaseSystem::sConfigData->mRootHeapMem1; + } + GXFlush(); + GXDrawDone(); + r31_second->becomeCurrentHeap(); + GXFlush(); + GXDrawDone(); + r31_second->becomeCurrentHeap(); +} +void SceneManager::incomingCurrentScene() { + if (this->mCurrentScene) + this->mCurrentScene->incoming_childDestroy(); +} +void SceneManager::calcCurrentScene() { + if (this->mCurrentScene) + this->mCurrentScene->calc(); +} + +void SceneManager::calcCurrentFader() { + if (mCurrentFader == NULL || mCurrentFader->calc() == 0) + return; + switch (this->mTransitionStatus) { + case 0: { + int r29 = this->mNextSceneID; + Scene* curScene; // r4 + while (curScene = this->mCurrentScene) // r4 + { + if (curScene == NULL) + continue; + Scene* r28 = curScene->getParentScene(); // r28 + if (r28) { + this->destroyScene(r28->getChildScene()); + this->mNextSceneID = r28->getSceneID(); + this->setupNextSceneID(); + } else { + this->destroyScene(curScene); + this->mNextSceneID = -1; + this->setupNextSceneID(); + } + } + this->mNextSceneID = r29; + EGG::Scene* r30 = curScene ? curScene->getParentScene() : 0; + if (curScene) { + this->destroyScene(curScene); + this->mCurrentScene = 0; + } + + this->mNextSceneID = r29; + this->setupNextSceneID(); + this->createScene(r29, r30); + break; + } + case STATUS_CHANGE_SIBLING_SCENE: { + // Identical to changeSiblingScene + Scene* r30 = this->mCurrentScene ? mCurrentScene->getParentScene() : 0; + if (mCurrentScene) { + this->destroyScene(r30); + this->mCurrentScene = NULL; + } + int r29 = this->mNextSceneID; + this->setupNextSceneID(); + this->createScene(r29, r30); + break; + } + case 2: + // fields have changed a lot, cant trust this + WiiSportsAssert(this->pParent, "eggSceneManager.cpp", 701, "pParent"); + WiiSportsAssert(this->pParent->getChildScene(), "eggSceneManager.cpp", 704, + "pParent->getChildScene()() == NULL"); + this->outgoingParentScene(this->pParent); + this->setupNextSceneID(); + this->createScene(this->_18, this->pParent); + break; + case 4: + // TODO + if (Scene* s = this->findParentScene(this->mNextSceneID)) // r30 + { + while (s->getSceneID() != this->_18) { + if (this->mCurrentScene == 0) + continue; + Scene* r28 = this->mCurrentScene->getParentScene(); + if (r28 == 0) + continue; + this->destroyScene(r28); + this->mNextSceneID = this->bUseMem2; + this->setupNextSceneID(); + } + if (this->mCurrentScene == 0) + break; + this->mCurrentScene->incoming_childDestroy(); + } + + break; + case STATUS_REINITIALIZE: + if (mCurrentScene) + mCurrentScene->reinit(); + break; + } + this->mTransitionStatus = STATUS_IDLE; +} +void SceneManager::drawCurrentScene() { + if (!mCurrentScene) + return; + + mCurrentScene->draw(); + + Display* pSystemDisplay = BaseSystem::sConfigData->getDisplay(); // r31 + + // flag name likely wrong + if (BaseSystem::sConfigData->getVideo()->mFlag & + Video::VIDEO_FLAG_IS_NOT_BLACKED_OUT) + if (!(pSystemDisplay->mScreenStateFlag & + Display::SCREEN_STATE_FLAG_TOGGLE_BLACK_OUT)) + pSystemDisplay->mScreenStateFlag |= + Display::SCREEN_STATE_FLAG_TOGGLE_BLACK_OUT; +} +void SceneManager::drawCurrentFader() { + if (mCurrentFader) + mCurrentFader->draw(); +} +void SceneManager::createDefaultFader() { + mCurrentFader = new ColorFader(0.0f, 0.0f, 640.0f, 480.0f, 0 /*color*/, + Fader::ESTATUS_OPAQUE); +} +void SceneManager::setupNextSceneID() { + this->_1C = this->_18; + this->_18 = this->mNextSceneID; + this->mNextSceneID = -1; +} + +void SceneManager::outgoingParentScene(Scene* pScene) { + pScene->outgoing_childCreate(); +} + +Scene* SceneManager::findParentScene(int ID) { + bool found = false; // r5 + Scene* scene; // r3 + + for (scene = mCurrentScene->getParentScene(); scene != NULL; + scene = scene->getParentScene()) { + if (scene->getSceneID() == ID) { + found = true; + break; + } + } + + return found ? scene : NULL; +} +#endif +} // namespace EGG diff --git a/source/egg/core/eggSceneManager.hpp b/source/egg/core/eggSceneManager.hpp new file mode 100644 index 000000000..06d872317 --- /dev/null +++ b/source/egg/core/eggSceneManager.hpp @@ -0,0 +1,246 @@ +#pragma once + +#include +#include +#include + +namespace EGG { + +class SceneCreator { +public: + virtual Scene* + create(int SceneID) = 0; //!< [vt+0x08] @see SceneManager::createScene + virtual void destroy(int SceneID) = 0; //!< [vt+0x0C] +}; + +class SceneManager { +public: + // -1 -> idle? + // 3 -> fading out (can see game -> black) + enum eAfterFadeType { + STATUS_IDLE = -1, // guess + // big logic + STATUS_0 = 0, + // destroyScene then + // set in changeSiblingSceneAfterFadeOut + //! [1] Change to a sibling scene @see changeSIblingSceneAfterFadeOut, + //! calcCurrentFader + STATUS_CHANGE_SIBLING_SCENE = 1, + // createScene with _18 and _10 + STATUS_2 = 2, + //! [3] When set, in calcFader, reinitCurrentScene + STATUS_REINITIALIZE = 3, + STATUS_4 = 4 + }; + +public: // .sbss + //! @brief Option to use when creating Scene ExpHeaps + static u32 sHeapOptionFlg; + static Heap* sHeapMem1_ForCreateScene; + static Heap* sHeapMem2_ForCreateScene; + static Heap* sHeapDebug_ForCreateScene; + +public: + //! @brief [vt+0x08] Calculate the current fader and scene. + //! + virtual void calc(); + + //! @brief [vt+0x0C] Draw the current fader and scene. + //! + virtual void draw(); + + //! @brief [vt+0x10] Calculate the current scene. + //! + virtual void calcCurrentScene(); + + //! @brief [vt+0x14] Calculate the current fader and post-fade operations. + //! Overrided implementations must also implement all post-fade status + //! handling. + //! + virtual void calcCurrentFader(); + + //! @brief [vt+0x18] Draws the current scene. + //! + virtual void drawCurrentScene(); + + //! @brief [vt+0x1C] Draw the current fader if it exists, otherwise do + //! nothing. + //! + virtual void drawCurrentFader(); + + //! @brief [vt+0x20] Create the default fader for this manager + //! + //! @details Default fader spans from (0,0) to (640,480), is pure black, and + //! is configured in the OPAQUE state + //! + virtual void createDefaultFader(); + +public: + //! @brief Constructor + //! + //! @param[in] creator Scene creator to use for this manager when + //! creating new scenes. + //! + //! @remarks Nintendo appears to never use this argument, opting instead to + //! set it later directly. Whether this was with an inline setter or not we'll + //! never know. + //! + SceneManager(SceneCreator* creator); + + //! @brief Fade into the scene + //! + //! @pre mCurrentFader *must* be set. + //! + //! @returns Whether or not the operation did anything. + //! + bool fadeIn(); + + //! @brief Re-initialize current scene + //! + //! @pre mCurrentScene is not NULL. + //! + void reinitCurrentScene(); + + //! @brief Fade out, and set the status flag to reinitialize the current scene + //! the next frame + //! + //! @pre + //! - mTransitionStatus is STATUS_IDLE. + //! - mCurrentFader *must* not be null. (not checked) + //! - The screen is not fading or blocked. (mCurrentFader->mStatus + //!== ESTATUS_HIDDEN + //! + //! @returns Whether or not the operation was successful (including fading + //! out) + //! + bool reinitCurrentSceneAfterFadeOut(); + + // Exit current scene to parent, then manuver to sibling scene ID + // TODO: Match to real symbol + //! @brief Destroy the current scene, then manuever to a sibling of the + //! parent. + //! + //! @param[in] ID Scene ID to switch to after destroying current scene + //! completely. + //! + void ChangeUncleScene(int ID); + + //! @brief Change to a sibling scene with the specified ID. + //! + //! @param[in] ID Scene ID of sibling to switch to. Must have same parent. + //! + void changeSiblingScene(int ID); + + //! @brief Fade out, and set the status flag to change the current scene the + //! next frame + //! + //! @param[in] ID Scene ID to switch to after destroying current scene + //! completely. + //! + bool changeSiblingSceneAfterFadeOut(int ID); + + //! @brief Change to a sibling scene specified by mNextSceneID. + //! + //! @pre + //! - mCurrentScene is not NULL. (checked) + //! - mParentScene is not NULL. (checked in createScene). + //! + void changeSiblingScene(); + + //! @brief Create a scene with the specified ID and parent. + //! + //! @param[in] ID ID of scene to create. + //! @param[in] pParentScene Pointer to scene parent (NULL if root). + //! + //! @pre System heaps are initialized if root, otherwise parent heaps must be + //! initialized. + //! + void createScene(int ID, Scene* pParentScene); + + //! @brief Create a child scene with the specified ID and parent. + //! + //! @param[in] ID ID of child scene to create. + //! @param[in] pParentScene Pointer to scene parent (NULL if root). + //! + //! @pre System heaps are initialized if root, otherwise parent heaps must be + //! initialized. + //! + void createChildScene(int ID, Scene* pScene); + + //! @brief Destroy the current scene and optionally switch to a NULL scene. + //! + //! @param[in] destroyRootIfNoParent If the current scene does not have a + //! parent, should this function still proceed. If false and the scene has no + //! parent, nothing will be done. + //! + //! @pre mCurrentScene is not NULL. (checked) + //! + //! @returns Whether or not operations succeeed. If the current scene or + //! current scene parent is NULL, false. + //! + bool destroyCurrentSceneNoIncoming(bool destroyRootIfNoParent); + + //! @brief Climb up parent links, setting the current scene to the ID matched + //! scene. + //! + //! @param[in] ID of the scene to find. This must exist in the parent links as + //! a parent, grandparent, etc. + //! + //! @pre findParentScene preconditions and a valid parent/grandparent/etc has + //! the ID specified. + //! + bool destroyToSelectSceneID(int ID); + + //! @brief Destroy the scene pScene and all children recursively. + //! + //! @param[in] pScene Scene to destroy. + //! + //! @remarks The GP is flushed and synced after major changes. + //! + void destroyScene(Scene* pScene); + + //! @brief If mCurrentScene set, call the respective virtual function for an + //! incoming child creation. + //! + void incomingCurrentScene(); + + //! @brief Set the scene indices based on mNextSceneID for creation. + //! + void setupNextSceneID(); + + //! @brief If mCurrentScene set, call the respective virtual function for an + //! outgoing child creation. + void outgoingParentScene(Scene* pScene); + + //! @brief Search up parent links from the current scene to the root + //! for a parent scene that matches ID. + //! + //! @param[in] ID Target ID of parent to find. + //! + //! @returns Pointer to found parent scene, otherwise NULL. + //! + Scene* findParentScene(int ID); + +public: + SceneCreator* mSceneCreator; //!< [+0x04] + u32 _08; // unseen + + Scene* mCurrentScene; //!< [+0x0C] + + // actually, pROotScene maybe? + Scene* pParent; //!< [+0x10] name from wii sports assert. might be wrong. when + //!< STATE_2, create this scene with argument _18 + + s32 mNextSceneID; //!< [+0x14] set in matched inline "setNextSceneID" set to + //!< -1 in ct. tied to mCurrentScene->OC. child? + s32 _18; // set to -1 in ct. tied to _10 brother next? + s32 _1C; // set to -1 in ct + + eAfterFadeType mTransitionStatus; //!< [+0x20] -1 in ct + ColorFader* mCurrentFader; //!< [+0x24] header fn placement proves it's a ptr + //!< to ColorFader not Fader + u32 bUseMem2; //!< [+0x28] If 1, use MEM2 heap for scenes when creating. + //!< Otherwise, use MEM1. +}; + +} // namespace EGG diff --git a/source/game/host_system/SystemResources.hpp b/source/game/host_system/SystemResources.hpp index e6f0677ce..cb44e649c 100644 --- a/source/game/host_system/SystemResources.hpp +++ b/source/game/host_system/SystemResources.hpp @@ -22,4 +22,3 @@ u32 GetResourceID(eSystemResource idx); } // namespace Resource } // namespace System - \ No newline at end of file diff --git a/sources.py b/sources.py index 95cb6c2ae..ab050f11f 100644 --- a/sources.py +++ b/sources.py @@ -146,6 +146,7 @@ class Source: Source(src="source/egg/core/eggExpHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggGraphicsFifo.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -ipa file -use_lmw_stmw=on "), + Source(src="source/egg/core/eggSceneManager.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggStreamDecomp.cpp", cc='4201_127', opts=EGG_OPTS), # Source(src="source/egg/core/eggSystem.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggThread.cpp", cc='4201_127', opts=EGG_OPTS), From c7bb2bcfc79a43676be54a5729d003db69644cc6 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 31 Jul 2021 18:47:04 -0600 Subject: [PATCH 151/477] [EGG] Decompile eggScene.cpp --- mkwutil/lib/slices.py | 3 ++ pack/dol_objects.txt | 6 ++-- pack/dol_slices.csv | 2 +- pack/symbols.txt | 7 +++++ source/egg/core/eggExpHeap.cpp | 2 +- source/egg/core/eggGraphicsFifo.cpp | 4 +-- source/egg/core/eggScene.cpp | 8 +++--- source/egg/core/eggScene.hpp | 35 ++++++++++++++--------- source/egg/core/eggTaskThread.cpp | 4 +-- source/game/host_system/ParameterFile.hpp | 2 +- sources.py | 1 + 11 files changed, 48 insertions(+), 26 deletions(-) diff --git a/mkwutil/lib/slices.py b/mkwutil/lib/slices.py index 438b7cb1b..9cce7070d 100644 --- a/mkwutil/lib/slices.py +++ b/mkwutil/lib/slices.py @@ -486,6 +486,9 @@ def parse_row(self, row: str) -> Generator[Slice, None, None]: stop_str = stop_str.strip() if start_str == "": continue + if stop_str == "": + print(f"column {i} start_str {start_str} stop_str {stop_str}") + assert stop_str != "" start = int(start_str.strip(), 16) stop = int(stop_str, 16) diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index b04b0f391..5e486135f 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -233,9 +233,11 @@ out/dol/sdata2_80388960_80388d68.o out/eggHeap.o out/dol/text_80229fac_80239dfc.o out/eggQuat.o -out/dol/text_80239e10_8023addc.o +out/dol/text_80239e10_8023ad10.o out/dol/rodata_80257824_802582e0.o -out/dol/data_802a30ec_802a3f78.o +out/dol/data_802a30ec_802a3de0.o +out/eggScene.o +out/dol/data_802a3e08_802a3f78.o out/dol/bss_80384348_80384b60.o out/dol/sbss_80386ec0_80386f00.o out/eggSceneManager.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 97b957d4c..a0c26f350 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -85,7 +85,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, 1,,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68,0x80388D80,, 1,,source/egg/math/eggQuat.cpp,,,,,,,0x80239DFC,0x80239E10,,,,,,,,,,,,,,,,,, -,,source/egg/core/eggScene.cpp,,,,,,,0x8023AD10,0x8023ADDC,,,,,,,,,,,,,,,,,, +1,,source/egg/core/eggScene.cpp,,,,,,,0x8023AD10,0x8023ADDC,,,,,,,0x802A3DE0,0x802A3E08,,,,,,,,,, 1,,source/egg/core/eggSceneManager.cpp,,,,,,,0x8023ADDC,0x8023AE4C,,,,,,,,,,,,,0x80386F00,0x80386F10,,,, 1,,source/egg/core/eggStreamDecomp.cpp,,,,,,,0x80242498,0x80242504,,,,,,,0x802A3F78,0x802A3F90,,,,,,,,,, ,,source/egg/core/eggSystem.cpp,,,,,,,,,,,,,,,,,,,,,0x80386F60,0x80386F64,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index fc8d1a0a6..9918c7fa1 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -1,3 +1,10 @@ +0x80007F34 outgoing_childCreate__Q23EGG5SceneFv +0x80007F38 incoming_childDestroy__Q23EGG5SceneFv +0x80007F3C reinit__Q23EGG5SceneFv +0x80007F40 exit__Q23EGG5SceneFv +0x80007F44 enter__Q23EGG5SceneFv +0x80007F48 draw__Q23EGG5SceneFv +0x80007F4C calc__Q23EGG5SceneFv 0x80005f34 memcpy 0x80005f84 __fill_mem 0x80006038 memset diff --git a/source/egg/core/eggExpHeap.cpp b/source/egg/core/eggExpHeap.cpp index 37d6a6e72..ce9b23064 100644 --- a/source/egg/core/eggExpHeap.cpp +++ b/source/egg/core/eggExpHeap.cpp @@ -3,7 +3,7 @@ * @brief EGG wraper for expanded heaps implementation. */ -#include +#include #include #include diff --git a/source/egg/core/eggGraphicsFifo.cpp b/source/egg/core/eggGraphicsFifo.cpp index b19824c70..cc1c0b240 100644 --- a/source/egg/core/eggGraphicsFifo.cpp +++ b/source/egg/core/eggGraphicsFifo.cpp @@ -1,5 +1,5 @@ -#include -#include +#include +#include #include #include diff --git a/source/egg/core/eggScene.cpp b/source/egg/core/eggScene.cpp index 7fc9a7f33..82878a615 100644 --- a/source/egg/core/eggScene.cpp +++ b/source/egg/core/eggScene.cpp @@ -1,11 +1,11 @@ -#include -#include -#include +#include +#include +#include namespace EGG { Scene::Scene() { - _10 = Heap::sCurrentHeap; + _10 = Heap::getCurrentHeap(); mHeapMem1 = SceneManager::sHeapMem1_ForCreateScene; mHeapMem2 = SceneManager::sHeapMem2_ForCreateScene; mHeapDebug = SceneManager::sHeapDebug_ForCreateScene; diff --git a/source/egg/core/eggScene.hpp b/source/egg/core/eggScene.hpp index 2a2bfa7e7..cf6a871b0 100644 --- a/source/egg/core/eggScene.hpp +++ b/source/egg/core/eggScene.hpp @@ -1,32 +1,41 @@ #pragma once -#include #include #include +#include namespace EGG { class SceneManager; +// HACK: Only the first instance of this symbol will be kept +#ifndef EGGSCENE_INLINEBODY +#define EGGSCENE_INLINEBODY ; +#else +#undef EGGSCENE_INLINEBODY +#define EGGSCENE_INLINEBODY \ + {} +#endif + class Scene : public Disposer { public: virtual ~Scene(); //! [vt+0x08] Virtual destructor. - virtual void calc() {} //! [vt+0x0C] - virtual void draw() {} //! [vt+0x10] - virtual void enter() {} //! [vt+0x14] - virtual void exit() {} //! [vt+0x18] - virtual void reinit() {} //! [vt+0x1C] - virtual void incoming_childDestroy() {} //! [vt+0x20] - virtual void outgoing_childCreate() {} //! [vt+0x24] + virtual void calc() EGGSCENE_INLINEBODY //! [vt+0x0C] + virtual void draw() EGGSCENE_INLINEBODY //! [vt+0x10] + virtual void enter() EGGSCENE_INLINEBODY //! [vt+0x14] + virtual void exit() EGGSCENE_INLINEBODY //! [vt+0x18] + virtual void reinit() EGGSCENE_INLINEBODY //! [vt+0x1C] + virtual void incoming_childDestroy() EGGSCENE_INLINEBODY //! [vt+0x20] + virtual void outgoing_childCreate() EGGSCENE_INLINEBODY //! [vt+0x24] - //! @brief Constructor - Scene(); + //! @brief Constructor + Scene(); private: // -- vt + inherited mContainHeap from Disposer -- - int _08; // TODO get name from SceneManager usage - Scene* _0C; // TODO get name from SceneManager usage + // int _08; // TODO get name from SceneManager usage + // Scene* _0C; // TODO get name from SceneManager usage Heap* _10; // something like mCreatorHeap? set to Heap::sCurrentHeap in ct Heap* mHeapMem1; //!< [+0x14] @@ -40,7 +49,7 @@ class Scene : public Disposer { int mID; //!< [+0x28] SceneManager* pSceneMgr; //!< [+0x2C] @see SceneManager::createScene -public: +public: // inlines. actually above exit and below enter inline void setSceneMgr(SceneManager* mgr) { this->pSceneMgr = mgr; } inline void setParentScene(Scene* scene) { this->pParentScene = scene; } diff --git a/source/egg/core/eggTaskThread.cpp b/source/egg/core/eggTaskThread.cpp index 9e16f972b..32274cd92 100644 --- a/source/egg/core/eggTaskThread.cpp +++ b/source/egg/core/eggTaskThread.cpp @@ -3,8 +3,8 @@ * @brief TODO. */ -#include -#include +#include +#include #include #include diff --git a/source/game/host_system/ParameterFile.hpp b/source/game/host_system/ParameterFile.hpp index b89a1287c..2315e5069 100644 --- a/source/game/host_system/ParameterFile.hpp +++ b/source/game/host_system/ParameterFile.hpp @@ -6,7 +6,7 @@ #pragma once #include -#include +#include #include namespace System { diff --git a/sources.py b/sources.py index ab050f11f..6e96fabbb 100644 --- a/sources.py +++ b/sources.py @@ -146,6 +146,7 @@ class Source: Source(src="source/egg/core/eggExpHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggGraphicsFifo.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -ipa file -use_lmw_stmw=on "), + Source(src="source/egg/core/eggScene.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggSceneManager.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggStreamDecomp.cpp", cc='4201_127', opts=EGG_OPTS), # Source(src="source/egg/core/eggSystem.cpp", cc='4201_127', opts=EGG_OPTS), From fb166ac53af2358a905ef218c9afe3bb2812fddd Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Sat, 31 Jul 2021 18:47:19 -0600 Subject: [PATCH 152/477] [lib] Fix dol virtual_to_rom and diffing by symbol --- mkwutil/lib/dol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mkwutil/lib/dol.py b/mkwutil/lib/dol.py index 8450521d2..93fcf2102 100644 --- a/mkwutil/lib/dol.py +++ b/mkwutil/lib/dol.py @@ -135,6 +135,6 @@ def virtual_read(self, vaddr: int, size: int) -> Optional[bytes]: def virtual_to_rom(self, vaddr: int) -> Optional[int]: """Returns the DOL offset given a virtual address.""" for seg in self.segments: - if seg.start <= vaddr < self.stop: + if vaddr >= seg.start and vaddr <= seg.stop: return seg.offset + (vaddr - seg.start) return None From dee3ddf99fd443346fd14d97b256a31123efbb30 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sun, 1 Aug 2021 03:33:13 +0200 Subject: [PATCH 153/477] MSL libc, EABI, init_registers (#103) * add wchar.c * add eabi.c * add restgpr * __div2u * finish eabi.c * add trk/start.c * fix tabs --- mkwutil/lib/slices.py | 2 +- pack/dol_objects.txt | 10 +- pack/dol_slices.csv | 3 + source/decomp.h | 28 +++ source/platform/eabi.c | 536 ++++++++++++++++++++++++++++++++++++++++ source/platform/wchar.c | 53 ++++ source/platform/wchar.h | 1 - source/rvl/trk/start.c | 46 ++++ sources.py | 6 + 9 files changed, 681 insertions(+), 4 deletions(-) create mode 100644 source/platform/eabi.c create mode 100644 source/platform/wchar.c create mode 100644 source/rvl/trk/start.c diff --git a/mkwutil/lib/slices.py b/mkwutil/lib/slices.py index 9cce7070d..aa9e924c6 100644 --- a/mkwutil/lib/slices.py +++ b/mkwutil/lib/slices.py @@ -488,7 +488,7 @@ def parse_row(self, row: str) -> Generator[Slice, None, None]: continue if stop_str == "": print(f"column {i} start_str {start_str} stop_str {stop_str}") - + assert stop_str != "" start = int(start_str.strip(), 16) stop = int(stop_str, 16) diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 5e486135f..cad8e3022 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -1,7 +1,9 @@ out/trkHeader.o out/dol/init_80004100_80005f34.o out/rvlTrkMem.o -out/dol/init_80006068_80006460.o +out/dol/init_80006068_80006210.o +out/start.o +out/dol/init_800062a0_80006460.o out/dol/extab_80006460_80006a20.o out/dol/extabindex_80006a20_800072c0.o out/dol/text_800072c0_80007f50.o @@ -9,7 +11,11 @@ out/dol/ctors_80244de0_80244e88.o out/dol/dtors_80244ea4_80244eac.o out/dol/rodata_80244ec0_80245010.o out/SystemResource.o -out/dol/text_80007f7c_8006a0c0.o +out/dol/text_80007f7c_80017998.o +out/wchar.o +out/dol/text_80017a48_8002156c.o +out/eabi.o +out/dol/text_80021ba8_8006a0c0.o out/dol/rodata_802450b0_80248010.o out/dol/data_80258580_80274148.o out/dol/bss_802a4080_802f2338.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index a0c26f350..94ca26c32 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -1,7 +1,10 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabindexEnd,textStart,textEnd,ctorsStart,ctorsEnd,dtorsStart,dtorsEnd,rodataStart,rodataEnd,dataStart,dataEnd,bssStart,bssEnd,sdataStart,sdataEnd,sbssStart,sbssEnd,sdata2Start,sdata2End,sbss2Start,sbss2End 1,,asm/trkHeader.s,0x80004000,0x80004100,,,,,,,,,,,,,,,,,,,,,,,, 1,,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, +1,,source/rvl/trk/start.c,0x80006210,0x800062a0 ,,,,,,,,,,,,,,,,,,,,,,,, 1,,source/game/host_system/SystemResource.cpp,,,,,,,0x80007f50,0x80007f7c,,,,,0x80245010,0x802450b0,,,,,,,,,,,, +1,,source/platform/wchar.c,,,,,,,0x80017998,0x80017a48,,,,,,,,,,,,,,,,,, +1,,source/platform/eabi.c,,,,,,,0x8002156c,0x80021ba8,,,,,,,,,,,,,,,,,, 1,,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0,0x8006a518,,,,,,,,,,,,,,,0x80387cac,0x80387cd8,, 1,,source/nw4r/g3d/g3d_fog.cpp,,,,,,,0x800774d0,0x800775d0,,,,,,,,,,,,,,,0x80387d58,0x80387d5C,, 1,,source/nw4r/math/mathTriangular.cpp,,,,,,,0x80085110,0x80085578,,,,,0x80248010,0x80249020,0x80274148,0x80274250,,,,,,,0x80387e80,0x80387ea4,, diff --git a/source/decomp.h b/source/decomp.h index d3ba0b12f..b4b180eab 100644 --- a/source/decomp.h +++ b/source/decomp.h @@ -11,6 +11,20 @@ // Compiler intrinsics. +// PAL: 0x8002156C +extern void _savegpr_14(void); +// PAL: 0x80021570 +extern void _savegpr_15(void); +// PAL: 0x80021574 +extern void _savegpr_16(void); +// PAL: 0x80021578 +extern void _savegpr_17(void); +// PAL: 0x8002157C +extern void _savegpr_18(void); +// PAL: 0x80021580 +extern void _savegpr_19(void); +// PAL: 0x80021584 +extern void _savegpr_20(void); // PAL: 0x80021588 extern void _savegpr_21(void); // PAL: 0x8002158C @@ -26,6 +40,20 @@ extern void _savegpr_26(void); // PAL: 0x800215a0 extern void _savegpr_27(void); +// PAL: 0x800215b8 +extern void _restgpr_14(void); +// PAL: 0x800215bc +extern void _restgpr_15(void); +// PAL: 0x800215c0 +extern void _restgpr_16(void); +// PAL: 0x800215c4 +extern void _restgpr_17(void); +// PAL: 0x800215c8 +extern void _restgpr_18(void); +// PAL: 0x800215cc +extern void _restgpr_19(void); +// PAL: 0x800215d0 +extern void _restgpr_20(void); // PAL: 0x800215d4 extern void _restgpr_21(void); // PAL: 0x800215d8 diff --git a/source/platform/eabi.c b/source/platform/eabi.c new file mode 100644 index 000000000..304a2820d --- /dev/null +++ b/source/platform/eabi.c @@ -0,0 +1,536 @@ +#include "decomp.h" + +static asm void __save_gpr(void) { + // clang-format off + nofralloc; +entry _savegpr_14 + stw r14,-72(r11); +entry _savegpr_15 + stw r15,-68(r11); +entry _savegpr_16 + stw r16,-64(r11); +entry _savegpr_17 + stw r17,-60(r11); +entry _savegpr_18 + stw r18,-56(r11); +entry _savegpr_19 + stw r19,-52(r11); +entry _savegpr_20 + stw r20,-48(r11); +entry _savegpr_21 + stw r21,-44(r11); +entry _savegpr_22 + stw r22,-40(r11); +entry _savegpr_23 + stw r23,-36(r11); +entry _savegpr_24 + stw r24,-32(r11); +entry _savegpr_25 + stw r25,-28(r11); +entry _savegpr_26 + stw r26,-24(r11); +entry _savegpr_27 + stw r27,-20(r11); + stw r28,-16(r11); + stw r29,-12(r11); + stw r30,-8(r11); + stw r31,-4(r11); + blr; + // clang-format on +} + +static asm void __rest_gpr(void) { + // clang-format off + nofralloc; +entry _restgpr_14 + lwz r14,-72(r11); +entry _restgpr_15 + lwz r15,-68(r11); +entry _restgpr_16 + lwz r16,-64(r11); +entry _restgpr_17 + lwz r17,-60(r11); +entry _restgpr_18 + lwz r18,-56(r11); +entry _restgpr_19 + lwz r19,-52(r11); +entry _restgpr_20 + lwz r20,-48(r11); +entry _restgpr_21 + lwz r21,-44(r11); +entry _restgpr_22 + lwz r22,-40(r11); +entry _restgpr_23 + lwz r23,-36(r11); +entry _restgpr_24 + lwz r24,-32(r11); +entry _restgpr_25 + lwz r25,-28(r11); +entry _restgpr_26 + lwz r26,-24(r11); +entry _restgpr_27 + lwz r27,-20(r11); + lwz r28,-16(r11); + lwz r29,-12(r11); + lwz r30,-8(r11); + lwz r31,-4(r11); + blr; + // clang-format on +} + +asm void __div2u(void) { + // clang-format off + nofralloc; + cmpwi cr0, r3, 0; + cntlzw r0, r3; + cntlzw r9, r4; + bne cr0, lab1; + addi r0, r9, 32; +lab1: + cmpwi cr0, r5, 0; + cntlzw r9, r5; + cntlzw r10, r6; + bne cr0, lab2; + addi r9, r10, 32; +lab2: + cmpw cr0, r0, r9; + subfic r10, r0, 64; + bgt cr0, lab9; + addi r9, r9, 1; + subfic r9, r9, 64; + add r0, r0, r9; + subf r9, r9, r10; + mtctr r9; + cmpwi cr0, r9, 32; + addi r7, r9, -32; + blt cr0, lab3; + srw r8, r3, r7; + li r7, 0; + b lab4; +lab3: + srw r8, r4, r9; + subfic r7, r9, 32; + slw r7, r3, r7; + or r8, r8, r7; + srw r7, r3, r9; +lab4: + cmpwi cr0, r0, 32; + addic r9, r0, -32; + blt cr0, lab5; + slw r3, r4, r9; + li r4, 0; + b lab6; +lab5: + slw r3, r3, r0; + subfic r9, r0, 32; + srw r9, r4, r9; + or r3, r3, r9; + slw r4, r4, r0; +lab6: + li r10, -1; + addic r7, r7, 0; +lab7: + adde r4, r4, r4; + adde r3, r3, r3; + adde r8, r8, r8; + adde r7, r7, r7; + subfc r0, r6, r8; + subfe. r9, r5, r7; + blt cr0, lab8; + mr r8, r0; + mr r7, r9; + addic r0, r10, 1; +lab8: + bdnz lab7; + adde r4, r4, r4; + adde r3, r3, r3; + blr; +lab9: + li r4, 0; + li r3, 0; + blr; + // clang-format on +} + +asm void __div2i(void) { + // clang-format off + nofralloc; + stwu SP, -16(SP); + rlwinm. r9, r3, 0, 0, 0; + beq cr0, lab11; + subfic r4, r4, 0; + subfze r3, r3; +lab11: + stw r9, 8(r1); + rlwinm. r10, r5, 0, 0, 0; + beq cr0, lab12; + subfic r6, r6, 0; + subfze r5, r5; +lab12: + stw r10, 12(r1); + cmpwi cr0, r3, 0; + cntlzw r0, r3; + cntlzw r9, r4; + bne cr0, lab1; + addi r0, r9, 32; +lab1: + cmpwi cr0, r5, 0; + cntlzw r9, r5; + cntlzw r10, r6; + bne cr0, lab2; + addi r9, r10, 32; +lab2: + cmpw cr0, r0, r9; + subfic r10, r0, 64; + bgt cr0, lab9; + addi r9, r9, 1; + subfic r9, r9, 64; + add r0, r0, r9; + subf r9, r9, r10; + mtctr r9; + cmpwi cr0, r9, 32; + addi r7, r9, -32; + blt cr0, lab3; + srw r8, r3, r7; + li r7, 0; + b lab4; +lab3: + srw r8, r4, r9; + subfic r7, r9, 32; + slw r7, r3, r7; + or r8, r8, r7; + srw r7, r3, r9; +lab4: + cmpwi cr0, r0, 32; + addic r9, r0, -32; + blt cr0, lab5; + slw r3, r4, r9; + li r4, 0; + b lab6; +lab5: + slw r3, r3, r0; + subfic r9, r0, 32; + srw r9, r4, r9; + or r3, r3, r9; + slw r4, r4, r0; +lab6: + li r10, -1; + addic r7, r7, 0; +lab7: + adde r4, r4, r4; + adde r3, r3, r3; + adde r8, r8, r8; + adde r7, r7, r7; + subfc r0, r6, r8; + subfe.r9, r5, r7; + blt cr0, lab8; + mr r8, r0; + mr r7, r9; + addic r0, r10, 1; +lab8: + bdnz lab7; + adde r4, r4, r4; + adde r3, r3, r3; + lwz r9, 8(r1); + lwz r10, 12(r1); + xor. r7, r9, r10; + beq cr0, lab10; + cmpwi cr0, r9, 0; + subfic r4, r4, 0; + subfze r3, r3; +lab10: + b lab13; +lab9: + li r4, 0; + li r3, 0; +lab13: + addi SP, SP, 16; + blr; + // clang-format on +} + +asm void __mod2u(void) { + // clang-format off + nofralloc; + cmpwi cr0, r3, 0; + cntlzw r0, r3; + cntlzw r9, r4; + bne cr0, lab1; + addi r0, r9, 32; +lab1: + cmpwi cr0, r5, 0; + cntlzw r9, r5; + cntlzw r10, r6; + bne cr0, lab2; + addi r9, r10, 32; +lab2: + cmpw cr0, r0, r9; + subfic r10, r0, 64; + bgt cr0, lab9; + addi r9, r9, 1; + subfic r9, r9, 64; + add r0, r0, r9; + subf r9, r9, r10; + mtctr r9; + cmpwi cr0, r9, 32; + addi r7, r9, -32; + blt cr0, lab3; + srw r8, r3, r7; + li r7, 0; + b lab4; +lab3: + srw r8, r4, r9; + subfic r7, r9, 32; + slw r7, r3, r7; + or r8, r8, r7; + srw r7, r3, r9; +lab4: + cmpwi cr0, r0, 32; + addic r9, r0, -32; + blt cr0, lab5; + slw r3, r4, r9; + li r4, 0; + b lab6; +lab5: + slw r3, r3, r0; + subfic r9, r0, 32; + srw r9, r4, r9; + or r3, r3, r9; + slw r4, r4, r0; +lab6: + li r10, -1; + addic r7, r7, 0; +lab7: + adde r4, r4, r4; + adde r3, r3, r3; + adde r8, r8, r8; + adde r7, r7, r7; + subfc r0, r6, r8; + subfe. r9, r5, r7; + blt cr0, lab8; + mr r8, r0; + mr r7, r9; + addic r0, r10, 1; +lab8: + bdnz lab7; + mr r4, r8; + mr r3, r7; + blr; +lab9: + blr; + // clang-format on +} + +asm void __mod2i(void) { + // clang-format off + nofralloc; + cmpwi cr7, r3, 0; + bge cr7, lab11; + subfic r4, r4, 0; + subfze r3, r3; +lab11: + cmpwi cr0, r5, 0; + bge cr0, lab12; + subfic r6, r6, 0; + subfze r5, r5; +lab12: + cmpwi cr0, r3, 0; + cntlzw r0, r3; + cntlzw r9, r4; + bne cr0, lab1; + addi r0, r9, 32; +lab1: + cmpwi cr0, r5, 0; + cntlzw r9, r5; + cntlzw r10, r6; + bne cr0, lab2; + addi r9, r10, 32; +lab2: + cmpw cr0, r0, r9; + subfic r10, r0, 64; + bgt cr0, lab9; + addi r9, r9, 1; + subfic r9, r9, 64; + add r0, r0, r9; + subf r9, r9, r10; + mtctr r9; + cmpwi cr0, r9, 32; + addi r7, r9, -32; + blt cr0, lab3; + srw r8, r3, r7; + li r7, 0; + b lab4; +lab3: + srw r8, r4, r9; + subfic r7, r9, 32; + slw r7, r3, r7; + or r8, r8, r7; + srw r7, r3, r9; +lab4: + cmpwi cr0, r0, 32; + addic r9, r0, -32; + blt cr0, lab5; + slw r3, r4, r9; + li r4, 0; + b lab6; +lab5: + slw r3, r3, r0; + subfic r9, r0, 32; + srw r9, r4, r9; + or r3, r3, r9; + slw r4, r4, r0; +lab6: + li r10, -1; + addic r7, r7, 0; +lab7: + adde r4, r4, r4; + adde r3, r3, r3; + adde r8, r8, r8; + adde r7, r7, r7; + subfc r0, r6, r8; + subfe. r9, r5, r7; + blt cr0, lab8; + mr r8, r0; + mr r7, r9; + addic r0, r10, 1; +lab8: + bdnz lab7; + mr r4, r8; + mr r3, r7; +lab9: + bge cr7, no_adjust; + subfic r4, r4, 0; + subfze r3, r3; +no_adjust: + blr; + // clang-format on +} + +asm void __shl2i(void) { + nofralloc; + subfic r8, r5, 32; + subic r9, r5, 32; + slw r3, r3, r5; + srw r10, r4, r8; + or r3, r3, r10; + slw r10, r4, r9; + or r3, r3, r10; + slw r4, r4, r5; + blr; +} + +asm void __shr2u(void) { + nofralloc; + subfic r8, r5, 32; + subic r9, r5, 32; + srw r4, r4, r5; + slw r10, r3, r8; + or r4, r4, r10; + srw r10, r3, r9; + or r4, r4, r10; + srw r3, r3, r5; + blr; +} + +// PAL: 0x80021a60 +asm void __cvt_ull_flt(void) { + // clang-format off + nofralloc; + stwu SP, -16(SP); + or. r7, r3, r4; + li r6, 0; + beq cr0, lab3; + cntlzw r7, r3; + cntlzw r8, r4; + rlwinm r9, r7, 26, 0, 4; + srawi r9, r9, 31; + and r9, r9, r8; + add r7, r7, r9; + subfic r8, r7, 32; + subic r9, r7, 32; + slw r3, r3, r7; + srw r10, r4, r8; + or r3, r3, r10; + slw r10, r4, r9; + or r3, r3, r10; + slw r4, r4, r7; + sub r6, r6, r7; + rlwinm r7, r4, 0, 21, 31; + cmpi cr0, r7, 0x400; + addi r6, r6, 1086; + blt lab2; + bgt lab1; + rlwinm. r7, r4, 0, 20, 20; + beq lab2; +lab1: + addic r4, r4, 0x0800; + addze r3, r3; + addze r6, r6; +lab2: + rlwinm r4, r4, 21, 0, 31; + rlwimi r4, r3, 21, 0, 10; + rlwinm r3, r3, 21, 12, 31; + rlwinm r6, r6, 20, 0, 11; + or r3, r6, r3; +lab3: + stw r3, 8(r1); + stw r4, 12(r1); + lfd f1, 8(r1); + frsp f1, f1; + addi SP, SP, 16; + blr; + // clang-format on +} + +asm void __cvt_dbl_ull(void) { + // clang-format off + nofralloc; + stwu SP, -16(SP); + stfd f1, 8(r1); + lwz r3, 8(r1); + lwz r4, 12(r1); + rlwinm r5, r3, 12, 21, 31; + cmpli cr0, 0, r5, 1023; + bge cr0, lab5; +lab1: + li r3, 0; + li r4, 0; + b lab4; +lab5: + rlwinm. r6, r3, 0, 0, 0; + bne cr0, lab1; + rlwinm r3, r3, 0, 12, 31; + oris r3, r3, 0x0010; + addi r5, r5, -1075; + cmpwi cr0, r5, 0; + bge cr0, lab2; + neg r5, r5; + subfic r8, r5, 32; + subic r9, r5, 32; + srw r4, r4, r5; + slw r10, r3, r8; + or r4, r4, r10; + srw r10, r3, r9; + or r4, r4, r10; + srw r3, r3, r5; + b lab4; +lab2: + cmpwi cr0, r5, 11; + ble + lab3; + li r3, -1; + li r4, -1; + b lab4; +lab3: + subfic r8, r5, 32; + subic r9, r5, 32; + slw r3, r3, r5; + srw r10, r4, r8; + or r3, r3, r10; + slw r10, r4, r9; + or r3, r3, r10; + slw r4, r4, r5; +lab4: + addi SP, SP, 16; + blr; + // clang-format on +} diff --git a/source/platform/wchar.c b/source/platform/wchar.c new file mode 100644 index 000000000..744c20fc8 --- /dev/null +++ b/source/platform/wchar.c @@ -0,0 +1,53 @@ +#include "wchar.h" + +#include + +// Symbol: wcslen +// PAL: 0x80017998..0x800179d0 +u32 wcslen(const wchar_t* str) { + u32 len = -1; + const wchar_t* data = str - 1; + do { + len++; + } while (*++data); + return len; +} + +// Symbol: wcscpy +// PAL: 0x800179b4..0x800179d0 +wchar_t* wcscpy(wchar_t* dst, const wchar_t* src) { + const wchar_t* p1 = (wchar_t*)src - 1; + wchar_t* p2 = (wchar_t*)dst - 1; + while (*++p2 = *++p1) { + } + return (dst); +} + +// Symbol: wcsncpy +// PAL: 0x800179d0..0x80017a14 +wchar_t* wcsncpy(wchar_t* dst, const wchar_t* src, size_t n) { + const wchar_t* p1 = src - 1; + wchar_t* p2 = dst - 1; + wchar_t zero = 0; + n++; + while (--n) { + if (!(*++p2 = *++p1)) { + while (--n) + *++p2 = 0; + break; + } + } + return dst; +} + +// Symbol: wcscmp +// PAL: 0x80017a14..0x80017a48 +int wcscmp(const wchar_t* s1, const wchar_t* s2) { + const wchar_t* p1 = (wchar_t*)s1 - 1; + const wchar_t* p2 = (wchar_t*)s2 - 1; + wchar_t c1, c2; + while ((c1 = *++p1) == (c2 = *++p2)) + if (!c1) + return 0; + return c1 - c2; +} diff --git a/source/platform/wchar.h b/source/platform/wchar.h index d1082f319..ec827a08a 100644 --- a/source/platform/wchar.h +++ b/source/platform/wchar.h @@ -5,7 +5,6 @@ typedef unsigned short wchar_t; u32 wcslen(const wchar_t*); -u32 wcsnlen_s(const wchar_t*, u32); wchar_t* wcscpy(wchar_t*, const wchar_t*); diff --git a/source/rvl/trk/start.c b/source/rvl/trk/start.c new file mode 100644 index 000000000..0a54c4ebf --- /dev/null +++ b/source/rvl/trk/start.c @@ -0,0 +1,46 @@ +#include + +void init_registers(); + +static u32 _STACK_BASE_ : 0x80399180; +static u32 _SDA_BASE_ : 0x8038cc00; +static u32 _SDA2_BASE_ : 0x8038efa0; + +__declspec(section ".init") asm void init_registers(void) { + li r0, 0x0; + li r3, 0x0; + li r4, 0x0; + li r5, 0x0; + li r6, 0x0; + li r7, 0x0; + li r8, 0x0; + li r9, 0x0; + li r10, 0x0; + li r11, 0x0; + li r12, 0x0; + li r14, 0x0; + li r15, 0x0; + li r16, 0x0; + li r17, 0x0; + li r18, 0x0; + li r19, 0x0; + li r20, 0x0; + li r21, 0x0; + li r22, 0x0; + li r23, 0x0; + li r24, 0x0; + li r25, 0x0; + li r26, 0x0; + li r27, 0x0; + li r28, 0x0; + li r29, 0x0; + li r30, 0x0; + li r31, 0x0; + lis r1, _STACK_BASE_ @h; + ori r1, r1, _STACK_BASE_ @l; + lis r2, _SDA2_BASE_ @h; + ori r2, r2, _SDA2_BASE_ @l; + lis r13, _SDA_BASE_ @h; + ori r13, r13, _SDA_BASE_ @l; + blr; +} diff --git a/sources.py b/sources.py index 6e96fabbb..196c824ad 100644 --- a/sources.py +++ b/sources.py @@ -31,6 +31,11 @@ class Source: SOURCES_TRK = [ Source(src="source/rvl/trk/rvlTrkMem.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/trk/start.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_MSL_LIBC = [ + Source(src="source/platform/wchar.c", cc='4201_127', opts=RVL_OPTS), + Source(src="source/platform/eabi.c", cc='4201_127', opts=RVL_OPTS), ] SOURCES_RVL_ARC = [ Source(src="source/rvl/arc/rvlArchive.c", cc='4199_60831', opts=RVL_OPTS), @@ -163,6 +168,7 @@ class Source: SOURCES_DOL = list(chain( SOURCES_HOSTSYS, SOURCES_TRK, + SOURCES_MSL_LIBC, SOURCES_RVL_ARC, SOURCES_RVL_FS, SOURCES_RVL_IPC, From b000cb29f5c250c3d6531926e219f2eadcb87b1f Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Tue, 3 Aug 2021 01:22:08 +0200 Subject: [PATCH 154/477] Split part CRT/EABI/OS (#105) * rand.c * split qsort * split mem.c * split printf, mem * split ansi_files.c * split scanf.c, va_arg.c * add ansi_fp, float * split ExceptionPPC, __init_cpp_exceptions, global_destructor_chain * Decomp exception runtime * split osReset.c * split osThread * split osInterrupt * Partial decompile osInterrupt * fix weird symbol * simplify code --- build.py | 1 + mkwutil/gen_asm.py | 4 +- mkwutil/sections.py | 2 +- pack/dol.lcf.j2 | 1 + pack/dol_objects.txt | 57 +- pack/dol_slices.csv | 16 + pack/symbols.txt | 871 ++++++- source/decomp.h | 68 +- source/egg/core/eggThread.cpp | 2 + source/gamespy/GP/gpiUtility.c | 1 - source/gamespy/common/gsDebug.c | 1 - source/gamespy/common/gsDebug.h | 1 - source/gamespy/common/gsPlatformThread.h | 2 +- .../common/revolution/gsThreadRevolution.c | 1 + source/hardware.h | 25 + source/platform/ExceptionPPC.cpp | 37 + source/platform/ExceptionPPC.h | 19 + source/platform/__init_cpp_exceptions.cpp | 43 + source/platform/__init_cpp_exceptions.h | 18 + source/platform/__ppc_eabi_linker.h | 13 + source/platform/ansi_files.c | 149 ++ source/platform/ansi_files.h | 20 + source/platform/ansi_fp.c | 1877 +++++++++++++++ source/platform/ansi_fp.h | 34 + source/platform/eabi.h | 71 + source/platform/float.c | 105 + source/platform/global_destructor_chain.c | 35 + source/platform/global_destructor_chain.h | 27 + source/platform/math.h | 11 + source/platform/mem.c | 111 + source/platform/mem_cpy.c | 245 ++ source/platform/mem_cpy.h | 22 + source/platform/printf.c | 2017 +++++++++++++++++ source/platform/printf.h | 27 + source/platform/qsort.c | 116 + source/platform/rand.c | 12 + source/platform/scanf.c | 1523 +++++++++++++ source/platform/stdarg.h | 1 - source/platform/stddef.h | 2 +- source/platform/stdio.h | 1 + source/platform/stdlib.h | 3 + source/platform/string.h | 2 + source/platform/va_arg.c | 67 + source/platform/va_arg.h | 16 + source/rvl/ipc/ipcMain.c | 9 +- source/rvl/ipc/ipcclt.c | 20 +- source/rvl/mem/memAllocator.h | 4 +- source/rvl/nand/nand.c | 5 +- source/rvl/os/os.h | 4 - source/rvl/os/osException.h | 19 + source/rvl/os/osInterrupt.c | 497 ++++ source/rvl/os/osInterrupt.h | 122 + source/rvl/os/osMessage.h | 13 + source/rvl/os/osMutex.h | 19 + source/rvl/os/osReset.c | 771 +++++++ source/rvl/os/osReset.h | 38 + source/rvl/os/osThread.c | 1977 ++++++++++++++++ source/rvl/os/osThread.h | 56 +- source/rvl/pad/rvlPad.c | 4 +- source/rvl/so/soCommon.c | 1 + sources.py | 26 +- 61 files changed, 11125 insertions(+), 137 deletions(-) create mode 100644 source/hardware.h create mode 100644 source/platform/ExceptionPPC.cpp create mode 100644 source/platform/ExceptionPPC.h create mode 100644 source/platform/__init_cpp_exceptions.cpp create mode 100644 source/platform/__init_cpp_exceptions.h create mode 100644 source/platform/__ppc_eabi_linker.h create mode 100644 source/platform/ansi_files.c create mode 100644 source/platform/ansi_files.h create mode 100644 source/platform/ansi_fp.c create mode 100644 source/platform/ansi_fp.h create mode 100644 source/platform/eabi.h create mode 100644 source/platform/float.c create mode 100644 source/platform/global_destructor_chain.c create mode 100644 source/platform/global_destructor_chain.h create mode 100644 source/platform/mem.c create mode 100644 source/platform/mem_cpy.c create mode 100644 source/platform/mem_cpy.h create mode 100644 source/platform/printf.c create mode 100644 source/platform/printf.h create mode 100644 source/platform/qsort.c create mode 100644 source/platform/rand.c create mode 100644 source/platform/scanf.c delete mode 100644 source/platform/stdarg.h create mode 100644 source/platform/va_arg.c create mode 100644 source/platform/va_arg.h create mode 100644 source/rvl/os/osException.h create mode 100644 source/rvl/os/osInterrupt.c create mode 100644 source/rvl/os/osInterrupt.h create mode 100644 source/rvl/os/osMessage.h create mode 100644 source/rvl/os/osMutex.h create mode 100644 source/rvl/os/osReset.c create mode 100644 source/rvl/os/osReset.h create mode 100644 source/rvl/os/osThread.c diff --git a/build.py b/build.py index 19561dc3f..ca4c2e6e1 100644 --- a/build.py +++ b/build.py @@ -136,6 +136,7 @@ def __native_binary(path): "-nostdinc", "-msgstyle gcc -lang=c99 -DREVOKART", "-func_align 4", + "-sym dwarf-2", ] # Hack: $@ doesn't behave properly with this diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index 902fe1c20..2c30d3f3d 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -65,7 +65,9 @@ def dump_section_body(self): self.dump_data() def format_segname(self, name): - if "extab" in name: + if name in ("extab", "extabindex"): + # The linker is supposed to auto-generate those. + # It will crash if we try to feed it those sections with object files. return name + "_" return "." + name diff --git a/mkwutil/sections.py b/mkwutil/sections.py index 863596ff3..606b6e378 100644 --- a/mkwutil/sections.py +++ b/mkwutil/sections.py @@ -29,7 +29,7 @@ def __contains__(self, key) -> bool: Section("extabindex", "data", 0x80006A20, 0x800072C0), Section("text", "code", 0x800072C0, 0x80244DE0), Section("ctors", "data", 0x80244DE0, 0x80244E90), - Section("dtors", "data", 0x80244EA4, 0x80244EAC), + Section("dtors", "data", 0x80244EA0, 0x80244EAC), Section("rodata", "data", 0x80244EC0, 0x80258580), Section("data", "data", 0x80258580, 0x802A4040), Section("bss", "bss", 0x802A4080, 0x80384C00), diff --git a/pack/dol.lcf.j2 b/pack/dol.lcf.j2 index 4a5a8fce1..305c8d31d 100644 --- a/pack/dol.lcf.j2 +++ b/pack/dol.lcf.j2 @@ -31,6 +31,7 @@ _db_stack_addr = (_stack_addr + 0x2000); _db_stack_end = _stack_addr; __ArenaLo = (_db_stack_addr + 0x1f) & ~0x1f; __ArenaHi = 0x81700000; +_eti_init_info_ = 0x80007290; {% for sym in symbols -%} {{ sym.name }} = {{ "0x%08x" | format(sym.addr) }}; diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index cad8e3022..394bb35ca 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -7,20 +7,45 @@ out/dol/init_800062a0_80006460.o out/dol/extab_80006460_80006a20.o out/dol/extabindex_80006a20_800072c0.o out/dol/text_800072c0_80007f50.o -out/dol/ctors_80244de0_80244e88.o -out/dol/dtors_80244ea4_80244eac.o out/dol/rodata_80244ec0_80245010.o out/SystemResource.o -out/dol/text_80007f7c_80017998.o +out/dol/text_80007f7c_8000c948.o +out/ansi_files.o +out/ansi_fp.o +out/dol/text_8000e418_8000ef04.o +out/float.o +out/dol/text_8000eff8_8000f1f0.o +out/mem.o +out/mem_cpy.o +out/dol/text_8000f630_800100e4.o +out/printf.o +out/qsort.o +out/dol/rodata_802450b0_80248010.o +out/dol/data_80258580_80274148.o +out/dol/bss_802a4080_802a6968.o +out/dol/sdata_80384c00_80384c38.o +out/rand.o +out/scanf.o +out/dol/text_80013108_80017998.o out/wchar.o -out/dol/text_80017a48_8002156c.o +out/dol/text_80017a48_80020dd8.o +out/ExceptionPPC.o +out/dol/text_80020e34_800211e4.o +out/dol/bss_802a6974_802a6978.o +out/dol/sdata_80384c3c_80384c48.o +out/__init_cpp_exceptions.o +out/dol/text_80021254_80021270.o +out/va_arg.o +out/dol/ctors_80244de4_80244e88.o +out/dol/dtors_80244ea8_80244eac.o +out/dol/sdata_80384c4c_803850a0.o +out/dol/sbss_80385fc0_803860a8.o +out/global_destructor_chain.o +out/dol/text_800213e4_8002156c.o out/eabi.o out/dol/text_80021ba8_8006a0c0.o -out/dol/rodata_802450b0_80248010.o -out/dol/data_80258580_80274148.o -out/dol/bss_802a4080_802f2338.o -out/dol/sdata_80384c00_803850a0.o -out/dol/sbss_80385fc0_803862a8.o +out/dol/bss_802a6c78_802f2338.o +out/dol/sbss_803860b0_803862a8.o out/dol/sdata2_80386fa0_80387cac.o out/g3d_camera.o out/dol/text_8006a518_800774d0.o @@ -170,7 +195,7 @@ out/rvlArchive.o out/dol/text_80124e80_80169bcc.o out/fs.o out/dol/text_8016b49c_80192f7c.o -out/dol/data_8027e772_8029cc80.o +out/dol/data_8027e772_80290600.o out/dol/sdata_803857f6_80385a08.o out/dol/sbss_80386448_803867e8.o out/ipcMain.o @@ -182,7 +207,7 @@ out/rvlMemExpHeap.o out/rvlMemFrmHeap.o out/rvlMemUnitHeap.o out/dol/bss_80346d18_803481b0.o -out/dol/sbss_8038683c_80386998.o +out/dol/sbss_8038683c_803868e8.o out/dol/sdata2_803884a4_80388860.o out/rvlMemAllocator.o out/rvlMemList.o @@ -193,8 +218,16 @@ out/dol/sdata_80385a10_80385b08.o out/dol/sdata2_803888b4_803888b8.o out/rvlQuat.o out/nand.o -out/dol/text_8019f1a8_801ae5d8.o +out/dol/text_8019f1a8_801a65ac.o +out/osInterrupt.o +out/dol/text_801a6d30_801a8238.o +out/osReset.o +out/dol/text_801a8a80_801a95ac.o +out/osThread.o +out/dol/text_801aad5c_801ae5d8.o out/dol/rodata_80252c84_80252dd0.o +out/dol/data_80290630_8029cc80.o +out/dol/sbss_803868fc_80386998.o out/dol/sdata2_803888d0_80388930.o out/rvlPadClamp.o out/rvlPad.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 94ca26c32..4d2a2574e 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -3,7 +3,20 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/trk/rvlTrkMem.c,0x80005f34,0x80006068,,,,,,,,,,,,,,,,,,,,,,,, 1,,source/rvl/trk/start.c,0x80006210,0x800062a0 ,,,,,,,,,,,,,,,,,,,,,,,, 1,,source/game/host_system/SystemResource.cpp,,,,,,,0x80007f50,0x80007f7c,,,,,0x80245010,0x802450b0,,,,,,,,,,,, +1,,source/platform/ansi_files.c,,,,,,,0x8000c948,0x8000cadc,,,,,,,,,,,,,,,,,, +1,,source/platform/ansi_fp.c,,,,,,,0x8000cadc,0x8000e418,,,,,,,,,,,,,,,,,, +1,,source/platform/float.c,,,,,,,0x8000ef04,0x8000eff8,,,,,,,,,,,,,,,,,, +1,,source/platform/mem.c,,,,,,,0x8000f1f0,0x8000f360,,,,,,,,,,,,,,,,,, +1,,source/platform/mem_cpy.c,,,,,,,0x8000f360,0x8000f630,,,,,,,,,,,,,,,,,, +1,,source/platform/printf.c,,,,,,,0x800100e4,0x80011b00,,,,,,,,,,,,,,,,,, +1,,source/platform/qsort.c,,,,,,,0x80011b00,0x80011c70,,,,,,,,,,,,,,,,,, +1,,source/platform/rand.c,,,,,,,0x80011c70,0x80011c98,,,,,,,,,,,0x80384c38,0x80384c3c,,,,,, +1,,source/platform/scanf.c,,,,,,,0x80011c98,0x80013108,,,,,,,,,,,,,,,,,, 1,,source/platform/wchar.c,,,,,,,0x80017998,0x80017a48,,,,,,,,,,,,,,,,,, +1,,source/platform/ExceptionPPC.cpp,,,,,,,0x80020dd8,0x80020e34,,,,,,,,,0x802a6968,0x802a6974,,,,,,,, +1,,source/platform/__init_cpp_exceptions.cpp,,,,,,,0x800211e4,0x80021254,0x80244de0,0x80244de4,0x80244ea0,0x80244ea8,,,,,,,0x80384c48,0x80384c4c,,,,,, +1,,source/platform/va_arg.c,,,,,,,0x80021270,0x80021338,,,,,,,,,,,,,,,,,, +1,,source/platform/global_destructor_chain.c,,,,,,,0x80021338,0x800213e4,,,,,,,,,0x802a6978,0x802a6c78,,,0x803860a8,0x803860b0,,,, 1,,source/platform/eabi.c,,,,,,,0x8002156c,0x80021ba8,,,,,,,,,,,,,,,,,, 1,,source/nw4r/g3d/g3d_camera.cpp,,,,,,,0x8006a0c0,0x8006a518,,,,,,,,,,,,,,,0x80387cac,0x80387cd8,, 1,,source/nw4r/g3d/g3d_fog.cpp,,,,,,,0x800774d0,0x800775d0,,,,,,,,,,,,,,,0x80387d58,0x80387d5C,, @@ -75,6 +88,9 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, 1,,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b314,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888d0,, 1,,source/rvl/nand/nand.c,,,,,,,0x8019b314,0x8019f1a8,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osInterrupt.c,,,,,,,0x801a65ac ,0x801a6d30 ,,,,,,,0x80290600,0x80290630,,,,,0x803868e8,0x803868fc,,,, +1,,source/rvl/os/osReset.c,,,,,,,0x801a8238 ,0x801a8a80 ,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osThread.c,,,,,,,0x801a95ac ,0x801aad5c ,,,,,,,,,,,,,,,,,, 1,,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, 1,,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, 1,,source/rvl/si/siBios.c,,,,,,,,,,,,,,,,,,,,,0x803869f0,0x803869f8,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index 9918c7fa1..fccd225bf 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -1,27 +1,61 @@ -0x80007F34 outgoing_childCreate__Q23EGG5SceneFv -0x80007F38 incoming_childDestroy__Q23EGG5SceneFv -0x80007F3C reinit__Q23EGG5SceneFv -0x80007F40 exit__Q23EGG5SceneFv -0x80007F44 enter__Q23EGG5SceneFv -0x80007F48 draw__Q23EGG5SceneFv -0x80007F4C calc__Q23EGG5SceneFv 0x80005f34 memcpy 0x80005f84 __fill_mem 0x80006038 memset +0x80006090 __set_debug_bba +0x8000609c __get_debug_bba 0x800060a4 __start +0x80006210 __init_registers +0x800062a0 __init_data +0x80006348 __init_hardware +0x8000636c __flush_cache +0x80007f34 outgoing_childCreate__Q23EGG5SceneFv +0x80007f38 incoming_childDestroy__Q23EGG5SceneFv +0x80007f3c reinit__Q23EGG5SceneFv +0x80007f40 exit__Q23EGG5SceneFv +0x80007f44 enter__Q23EGG5SceneFv +0x80007f48 draw__Q23EGG5SceneFv +0x80007f4c calc__Q23EGG5SceneFv 0x80008e7c onExit__Q23EGG6ThreadFv 0x80008e80 onEnter__Q23EGG6ThreadFv +0x80008ef0 main 0x80009b40 __dt__Q23EGG8Vector3fFv 0x80009b80 __dt__Q23EGG8Vector2fFv 0x8000c21c ftell 0x8000c3e4 fseek 0x8000c3e8 rewind +0x8000c948 __close_all +0x8000c9ec __flush_line_buffered_output_files +0x8000ca70 __flush_all +0x8000cadc __ull2dec +0x8000cbb8 __timesdec +0x8000ce40 __str2dec +0x8000cf2c __two_exp +0x8000d298 __equals_dec +0x8000d37c __less_dec +0x8000d47c __minus_dec +0x8000d998 __num2dec_internal +0x8000dafc __num2dec +0x8000dc9c __dec2num 0x8000e610 fread 0x8000e614 fwrite 0x8000ec5c fclose +0x8000ef04 __fpclassifyf +0x8000ef64 __signbitd +0x8000ef7c __fpclassifyd 0x8000f1f0 memmove 0x8000f2bc memchr +0x8000f2e8 __memrchr 0x8000f314 memcmp +0x8000f360 __copy_longs_aligned +0x8000f41c __copy_longs_rev_aligned +0x8000f4c4 __copy_longs_unaligned +0x8000f584 __copy_longs_rev_unaligned +0x800100e4 double2hex +0x800104b4 round_decimal +0x800105dc float2str +0x80010d74 __pformatter +0x80011620 __FileWrite +0x80011678 __StringWrite 0x800116e4 printf 0x800117b0 vprintf 0x8001182c vsnprintf @@ -31,6 +65,9 @@ 0x80011b00 qsort 0x80011c70 rand 0x80011c90 srand +0x80011c98 parse_format +0x80012320 __sformatter +0x80012fb8 __StringRead 0x80013040 sscanf 0x80013120 strcpy 0x800131e0 strncpy @@ -42,20 +79,35 @@ 0x80013428 strcspn 0x800135f0 strstr 0x80014990 atof +0x800149ec __strtoul +0x800152a8 strtoul 0x8001543c atoi 0x80017998 wcslen 0x800179d0 wcsncpy 0x80017a14 wcscmp +0x8001b564 copysign 0x8001b590 cos +0x8001b7a8 frexp +0x8001b830 ldexp 0x8001ba98 sin 0x8001bb64 tan 0x8001bbdc acos +0x8001bbf0 pow 0x8001bbf4 sqrt 0x8001bc98 strncasecmp 0x8001bc9c strcasecmp +0x80020d20 __sys_alloc +0x80020dd8 __register_fragment +0x80020e0c __unregister_fragment +0x80020ff4 __construct_array +0x800210ec __destroy_arr +0x800211e4 __init_cpp_exceptions +0x80021220 __fini_cpp_exceptions 0x80021254 strlen +0x80021270 __va_arg 0x80021338 __register_global_object 0x80021350 __destroy_global_chain +0x80021398 __register_atexit 0x80021478 __cvt_fp2unsigned 0x8002156c _savegpr_14 0x80021570 _savegpr_15 @@ -91,6 +143,7 @@ 0x8002190c __mod2i 0x80021a18 __shl2i 0x80021a3c __shr2u +0x80021b00 __cvt_dbl_ull 0x80064440 GetRenderModeObj__Q34nw4r3g3d8G3DStateFv 0x8006a0c0 __ct__Q34nw4r3g3d6CameraFPQ34nw4r3g3d10CameraData 0x8006a0d0 Init__Q34nw4r3g3d6CameraFv @@ -789,6 +842,116 @@ 0x8015e2bc DVDOpen 0x8015e568 DVDClose 0x8015e834 DVDReadPrio +0x801628cc DVDReadAbsAsyncPrio +0x801629b0 DVDInquiryAsync +0x80162a88 DVDGetCommandBlockStatus +0x80162b50 DVDGetDriveStatus +0x80162bec DVDSetAutoInvalidation +0x80162bfc DVDPause +0x80162c38 DVDResume +0x80162c88 DVDCancelAsync +0x80162fec DVDCancel +0x801630d0 DVDCancelAll +0x8016321c DVDGetCurrentDiskID +0x80163224 __BS2DVDLowCallback +0x8016322c __DVDGetCoverStatus +0x801632f4 __DVDResetWithNoSpinup +0x80163338 __DVDPrepareResetAsync +0x80163460 __DVDPrepareReset +0x80163594 __DVDTestAlarm +0x801635b4 __DVDStopMotorAsync +0x801635bc __DVDRestartMotor +0x801635c0 __DVDClearWaitingQueue +0x801635f8 __DVDPushWaitingQueue +0x80163660 __DVDPopWaitingQueue +0x80163700 __DVDCheckWaitingQueue +0x80163758 __DVDGetNextWaitingQueue +0x801637b8 __DVDDequeueWaitingQueue +0x80163818 cbForNandClose +0x80163844 cbForNandWrite +0x80163a88 cbForNandSeek2 +0x80163b44 cbForNandRead +0x80163c2c cbForNandSeek0 +0x80163ccc cbForNandSeek1 +0x80163dcc cbForNandOpen +0x80163ee8 cbForNandCreate +0x80163f90 cbForNandCreateDir +0x801640b4 __DVDStoreErrorCode +0x8016411c DVDCompareDiskID +0x8016420c DVDGenerateDiskID +0x80164294 __DVDShowFatalMessage +0x80164364 DVDSetAutoFatalMessaging +0x801643c0 __DVDGetAutoFatalMessaging +0x801643d4 __DVDPrintFatalMessage +0x801643fc __DVDCheckDevice +0x801647f8 DVDLowFinalize +0x80164848 DVDLowInit +0x80164aac DVDLowReadDiskID +0x80164c34 DVDLowOpenPartition +0x80164e9c DVDLowOpenPartitionWithTmdAndTicketView +0x80165100 DVDLowGetNoDiscBufferSizes +0x8016530c DVDLowGetNoDiscOpenPartitionParams +0x801655a4 DVDLowClosePartition +0x80165708 DVDLowUnencryptedRead +0x8016589c DVDLowStopMotor +0x80165a30 DVDLowInquiry +0x80165b98 DVDLowRequestError +0x80165d00 DVDLowSetSpinupFlag +0x80165d0c DVDLowReset +0x80165e88 DVDLowAudioBufferConfig +0x8016601c DVDLowReportKey +0x801661b0 DVDLowSetMaximumRotation +0x80166330 DVDLowRead +0x801664e0 DVDLowSeek +0x8016665c DVDLowGetCoverRegister +0x80166678 DVDLowPrepareCoverRegister +0x801667e0 DVDLowPrepareStatusRegister +0x80166948 DVDLowGetImmBufferReg +0x80166954 DVDLowUnmaskStatusInterrupts +0x8016695c DVDLowMaskCoverInterrupt +0x80166964 DVDLowClearCoverInterrupt +0x80166ac8 __DVDLowTestAlarm +0x80166ad0 ENCiCheckParameters +0x80166bc8 ENCiCheckBreakType +0x80166bfc ENCiWriteBreakType +0x80166d0c ENCConvertStringTo +0x80166fbc ENCiConvertUnicodeToSjis +0x801670b4 ENCiFindSjisFromUnicode +0x801671d0 ESP_InitLib +0x80167224 ESP_CloseLib +0x8016726c ESP_LaunchTitle +0x80167300 ESP_ReadContentFile +0x801673a0 ESP_SeekContentFile +0x8016742c ESP_ListTitleContentsOnCard +0x80167544 ESP_GetTicketViews +0x8016765c ESP_DiGetTicketView +0x80167700 ESP_DiGetTmd +0x801677f0 ESP_GetTmdView +0x80167904 ESP_GetDataDir +0x8016799c ESP_GetTitleId +0x80167a34 ESP_GetConsumption +0x80167b08 EUARTInit +0x80167c04 InitializeUART +0x80167c4c WriteUARTN +0x80167e78 SetExiInterruptMask +0x80167f68 EXIImm +0x801681e4 EXIImmEx +0x80168288 EXIDma +0x80168380 EXISync +0x801685fc EXISetExiCallback +0x80168680 __EXIProbe +0x80168800 EXIAttach +0x80168918 EXIDetach +0x801689d0 EXISelect +0x80168b00 EXIDeselect +0x80168c00 EXIIntrruptHandler +0x80168cb8 TCIntrruptHandler +0x80168ed0 EXTIntrruptHandler +0x80168fa0 EXIInit +0x80169164 EXILock +0x80169260 EXIUnlock +0x80169338 UnlockedHandler +0x80169360 EXIGetID 0x80169bcc ISFS_OpenLib 0x80169cf4 _isfsFuncCb 0x80169e74 ISFS_CreateDir @@ -819,20 +982,203 @@ 0x8016b384 ISFS_Close 0x8016b388 ISFS_CloseAsync 0x8016b40c ISFS_ShutdownAsync -0x8016b388 ISFS_CloseAsync +0x8016b49c __GXDefaultTexRegionCallback +0x8016b5b4 __GXShutdown +0x8016b720 __GXInitRevisionBits 0x8016b850 GXInit +0x8016bd54 __GXInitGX +0x8016c668 GXCPInterruptHandler +0x8016c7c8 GXInitFifoBase +0x8016c854 CPGPLinkCheck +0x8016c94c GXSetCPUFifo +0x8016cb2c GXSetGPFifo +0x8016cdbc __GXSaveFifo +0x8016cebc __GXIsGPFifoReady 0x8016cec4 GXGetGPStatus +0x8016cf10 GXGetCPUFifo +0x8016cfa0 GXGetGPFifo +0x8016d030 GXGetFifoPtrs +0x8016d044 GXGetFifoCount +0x8016d04c GXGetFifoWrap +0x8016d054 GXSetBreakPtCallback +0x8016d098 GXEnableBreakPt +0x8016d138 GXDisableBreakPt +0x8016d180 __GXFifoInit +0x8016d1fc __GXCleanGPFifo +0x8016d39c GXGetCurrentGXThread +0x8016d3a4 GXSetVtxDesc +0x8016d608 GXSetVtxDescv +0x8016d814 __GXSetVCD +0x8016d8c4 __GXCalculateVLim +0x8016d9f0 GXGetVtxDesc +0x8016dba4 GXGetVtxDescv +0x8016dc34 GXClearVtxDesc +0x8016dc68 GXSetVtxAttrFmt +0x8016de08 GXSetVtxAttrFmtv +0x8016dfcc __GXSetVAT +0x8016e04c GXGetVtxAttrFmt +0x8016e2b8 GXGetVtxAttrFmtv +0x8016e32c GXSetArray +0x8016e36c GXInvalidateVtxCache +0x8016e37c GXSetTexCoordGen2 +0x8016e5a4 GXSetNumTexGens +0x8016e5c8 GXSetMisc +0x8016e654 GXFlush +0x8016e6b0 GXResetWriteGatherPipe +0x8016e6e4 __GXAbort +0x8016e848 GXAbortFrame +0x8016e9fc GXSetDrawSync +0x8016eab0 GXDraw +0x8016eb94 GXPokeAlphaMode +0x8016eba4 GXPokeAlphaRead +0x8016ebbc GXPokeAlphaUpdate +0x8016ebd0 GXPokeBlendMode +0x8016ec2c GXPokeColorUpdate +0x8016ec40 GXPokeDstAlpha +0x8016ec58 GXPokeDither +0x8016ec6c GXPokeZMode +0x8016ec88 GXSetDrawSyncCallback +0x8016eccc GXTokenInterruptHandler +0x8016ed50 GXSetDrawDoneCallback +0x8016ed94 GXFinishInterruptHandler +0x8016ee14 __GXPEInit +0x8016ee78 __GXSetDirtyState +0x8016f0f0 GXBegin +0x8016f23c __GXSendFlushPrim +0x8016f314 GXSetLineWidth +0x8016f348 GXSetPointSize +0x8016f37c GXEnableTexOffsets +0x8016f3b8 GXSetCullMode +0x8016f3e0 GXSetCoPlanar +0x8016f414 __GXSetGenMode 0x8016f438 GXSetDispCopySrc +0x8016f478 GXSetTexCopySrc 0x8016f4b8 GXSetDispCopyDst +0x8016f4dc GXSetTexCopyDst +0x8016f5f8 GXSetDispCopyFrame2Field +0x8016f618 GXSetCopyClamp 0x8016f640 GXGetNumXfbLines 0x8016f6cc GXGetYScaleFactor 0x8016f8fc GXSetDispCopyYScale +0x8016f9c8 GXSetCopyClear +0x8016fa40 GXSetCopyFilter +0x8016fc24 GXSetDispCopyGamma +0x8016fc38 GXCopyDisp +0x8016fd74 GXCopyTex +0x8016fecc GXClearBoundingBox +0x8016ff04 GXInitLightAttn +0x8016ff20 GXInitLightAttnA +0x8016ff30 GXInitLightAttnK +0x8016ff40 GXInitLightSpot +0x801700c8 GXInitLightDistAttn +0x80170198 GXInitLightPos +0x801701a8 GXGetLightPos +0x801701c4 GXInitLightDir +0x801701e0 GXGetLightDir +0x80170208 GXInitSpecularDir +0x80170314 GXInitLightColor +0x80170320 GXLoadLightObjImm +0x8017039c GXSetChanAmbColor +0x80170474 GXSetChanMatColor +0x8017054c GXSetNumChans +0x80170570 GXSetChanCtrl +0x80170614 GXGetTexBufferSize +0x80170738 __GetImageTileCount 0x801707f8 GXInitTexObj 0x80170a04 GXInitTexObjCI 0x80170a4c GXInitTexObjLOD +0x80170b50 GXInitTexObjWrapMode +0x80170b6c GXInitTexObjFilter +0x80170b94 GXInitTexObjLODBias +0x80170bf8 GXGetTexObjAll +0x80170c5c GXGetTexObjData +0x80170c68 GXGetTexObjWidth +0x80170c7c GXGetTexObjHeight +0x80170c90 GXGetTexObjFmt +0x80170c98 GXGetTexObjWrapS +0x80170ca4 GXGetTexObjWrapT +0x80170cb0 GXGetTexObjMipMap +0x80170cbc GXGetTexObjLODAll +0x80170da0 GXGetTexObjMinFilt +0x80170db4 GXGetTexObjMagFilt +0x80170dc8 GXLoadTexObjPreLoaded +0x80170f2c GXLoadTexObj +0x80170f80 GXInitTlutObj +0x80170fa8 GXLoadTlut +0x8017103c GXInitTexCacheRegion +0x801710f0 GXInitTlutRegion +0x80171110 GXInvalidateTexAll +0x80171158 GXSetTexRegionCallback +0x8017116c GXSetTlutRegionCallback +0x80171180 GXSetTexCoordScaleManually +0x801711fc GXSetTexCoordBias +0x80171260 __SetSURegs +0x801712f0 __GXSetSUTexRegs +0x80171458 __GXSetTmemConfig +0x801717ac GXSetTevIndirect +0x80171814 GXSetIndTexMtx +0x80171968 GXSetIndTexCoordScale +0x80171a6c GXSetIndTexOrder +0x80171b38 GXSetNumIndStages +0x80171b58 GXSetTevDirect +0x80171ba0 GXSetTevIndWarp +0x80171bf4 __GXUpdateBPMask +0x80171bf8 __GXSetIndirectMask +0x80171c28 __GXFlushTextureState +0x80171c4c GXSetTevOp +0x80171ce0 GXSetTevColorIn +0x80171d20 GXSetTevAlphaIn +0x80171d60 GXSetTevColorOp +0x80171db8 GXSetTevAlphaOp +0x80171e10 GXSetTevColor +0x80171e70 GXSetTevColorS10 +0x80171ed4 GXSetTevKColor +0x80171f30 GXSetTevKColorSel +0x80171f80 GXSetTevKAlphaSel +0x80171fd0 GXSetTevSwapMode +0x8017200c GXSetTevSwapModeTable +0x80172088 GXSetAlphaCompare +0x801720c0 GXSetZTexture +0x8017214c GXSetTevOrder +0x801722a8 GXSetNumTevStages 0x801722cc GXSetFog 0x801724f8 GXInitFogAdjTable 0x80172658 GXSetFogRangeAdj +0x8017277c GXSetBlendMode +0x801727cc GXSetColorUpdate +0x801727f8 GXSetAlphaUpdate +0x80172824 GXSetZMode +0x80172858 GXSetZCompLoc +0x80172888 GXSetPixelFmt +0x80172930 GXSetDither +0x8017295c GXSetDstAlpha +0x8017298c GXSetFieldMask +0x801729c0 GXSetFieldMode +0x80172a30 GXDrawSphere +0x80172e00 GXBeginDisplayList +0x80172eb4 GXEndDisplayList +0x80172f64 GXCallDisplayList +0x80172fd8 __GXSetProjection +0x8017301c GXSetProjection +0x80173080 GXSetProjectionv +0x801730cc GXGetProjectionv +0x8017310c GXLoadPosMtxImm +0x8017315c GXLoadPosMtxIndx +0x80173188 GXLoadNrmMtxImm +0x801731e0 GXLoadNrmMtxIndx3x3 +0x80173214 GXSetCurrentMtx +0x80173234 GXLoadTexMtxImm +0x801732e8 __GXSetViewport +0x80173378 GXSetViewportJitter +0x801733b4 GXSetViewport +0x80173400 GXSetZScaleOffset +0x80173430 GXSetScissor +0x80173498 GXGetScissor +0x801734e0 GXSetScissorBoxOffset +0x8017351c GXSetClipMode +0x80173544 __GXSetMatrixIndex +0x801735cc GXSetGPMetric +0x80173df8 GXClearGPMetric 0x80192f7c IPCInit 0x80192fc8 IPCReInit 0x80193010 IPCReadReg @@ -1035,7 +1381,61 @@ 0x8019ebd8 reserveFileDescriptor 0x8019ec2c NANDLoggingAddMessageAsync 0x8019ed24 asyncRoutine +0x8019f1a8 __OSFPRInit +0x8019f2d0 __OSGetIOSRev +0x8019f33c OSGetConsoleType +0x8019f5c0 ClearArena +0x8019f79c ClearMEM2Arena +0x8019f980 InquiryCallback +0x8019f9bc ReportOSInfo +0x8019fc68 OSInit +0x801a00e0 OSExceptionInit +0x801a0360 __OSDBIntegrator +0x801a0384 __OSDBJump +0x801a0388 __OSSetExceptionHandler +0x801a039c __OSGetExceptionHandler +0x801a03ac OSExceptionVector +0x801a0404 __DBVECTOR +0x801a0414 __OSEVSetNumber +0x801a0448 OSDefaultExceptionHandler +0x801a04a0 __OSPSInit +0x801a04f4 __OSGetDIConfig 0x801a0504 OSRegisterVersion +0x801a0514 OSGetAppGamename +0x801a0598 OSGetAppType +0x801a05b8 __OSInitAlarm +0x801a0610 OSCreateAlarm +0x801a0620 InsertAlarm +0x801a0870 OSSetAlarm +0x801a08e0 OSSetPeriodicAlarm +0x801a0964 OSCancelAlarm +0x801a0a7c DecrementerExceptionCallback +0x801a0ca8 DecrementerExceptionHandler +0x801a0d00 OnReset +0x801a0d8c OSSetAlarmUserData +0x801a0d94 OSGetAlarmUserData +0x801a0d9c DLInsert +0x801a0e48 OSAllocFromHeap +0x801a0f40 OSFreeToHeap +0x801a0fb8 set_CurrHeap +0x801a0fc8 OSInitAlloc +0x801a1038 OSCreateHeap +0x801a10a4 OSGetMEM1ArenaHi +0x801a10ac OSGetMEM2ArenaHi +0x801a10b4 OSGetArenaHi +0x801a10bc OSGetMEM1ArenaLo +0x801a10c4 OSGetMEM2ArenaLo +0x801a10cc OSGetArenaLo +0x801a10d4 OSSetArenaHi_0 +0x801a10dc OSSetMEM2ArenaHi +0x801a10e4 OSSetArenaHi +0x801a10ec OSSetArenaLo_0 +0x801a10f4 OSSetMEM2ArenaLo +0x801a10fc OSSetArenaLo +0x801a1104 OSAllocFromMEM1ArenaLo +0x801a1138 __AIClockInit +0x801a1358 __OSInitAudioSystem +0x801a1520 __OSStopAudioSystem 0x801a15ec DCEnable 0x801a1600 DCInvalidateRange 0x801a162c DCFlushRange @@ -1055,10 +1455,39 @@ 0x801a197c LCQueueLength 0x801a1988 LCQueueWait 0x801a199c DMAErrorHandler +0x801a1ae4 __OSCacheInit +0x801a1c1c __OSLoadFPUContext +0x801a1d40 __OSSaveFPUContext +0x801a1e68 OSSaveFPUContext 0x801a1e70 OSSetCurrentContext +0x801a1ecc OSGetCurrentContext +0x801a1ed8 OSSaveContext +0x801a1f58 OSLoadContext +0x801a2030 OSGetStackPointer +0x801a2038 OSSwitchFiber +0x801a2068 OSSwitchFiberEx 0x801a2098 OSClearContext +0x801a20bc OSInitContext +0x801a2178 OSDumpContext +0x801a23d8 OSSwitchFPUContext +0x801a245c __OSContextInit +0x801a24a4 OSFillFPUContext 0x801a25d0 OSReport +0x801a265c OSVReport 0x801a2660 OSPanic +0x801a278c OSSetErrorHandler +0x801a2a14 __OSUnhandledException +0x801a2e84 Utf16ToArg +0x801a2fdc PackInstallerArgs +0x801a31a8 Run +0x801a31f0 __OSGetExecParams +0x801a321c OSExec +0x801a3888 __OSLaunchMenu +0x801a394c __OSLaunchDisk +0x801a3cec OSLaunchDiskl +0x801a3e00 __OSBootDolSimple +0x801a4648 __OSBootDol +0x801a4828 OSLaunchInstaller 0x801a6114 OSGetFontTexel 0x801a63a4 OSGetFontTexture 0x801a64f4 OSGetFontWidth @@ -1080,26 +1509,111 @@ 0x801a7eac OSInitMutex 0x801a7ee4 OSLockMutex 0x801a7fc0 OSUnlockMutex -0x801a8238 OSRegisterResetFunction +0x801a8088 __OSUnlockAllMutex +0x801a80f4 OSTryLockMutex +0x801a81b0 OSInitCond +0x801a81b4 OSSignalCond +0x801a81b8 __OSReboot +0x801a8224 OSGetSaveRegion +0x801a8238 OSRegisterShutdownFunction +0x801a82c0 __OSCallShutdownFunctions +0x801a8370 __OSShutdownDevices +0x801a8500 __OSGetDiscState +0x801a856c OSShutdownSystem +0x801a8688 OSRestart +0x801a8758 __OSReturnToMenu +0x801a8858 OSReturnToMenu +0x801a8898 OSReturnToSetting +0x801a8954 __OSReturnToMenuForError +0x801a89f8 __OSHotResetForError +0x801a8a50 OSGetResetCode +0x801a8a80 OSResetSystem +0x801a8a9c WriteSramCallback +0x801a8bd4 __OSInitSram +0x801a8dd4 UnlockSram +0x801a90b4 __OSSyncSram +0x801a90c4 __OSReadROM +0x801a91e8 OSGetWirelessID 0x801a9260 OSSetWirelessID +0x801a92fc __OSGetRTCFlags +0x801a9418 __OSClearRTCFlags +0x801a9528 SystemCallVector +0x801a9548 __OSInitSystemCall 0x801a95ac OSSetSwitchThreadCallback +0x801a961c __OSThreadInit +0x801a98a0 OSInitThreadQueue 0x801a98b0 OSGetCurrentThread 0x801a98bc OSIsThreadTerminated +0x801a98e8 OSDisableScheduler +0x801a9924 OSEnableScheduler +0x801a9960 UnsetRun +0x801a99c8 __OSGetEffectivePriority +0x801a9a04 SetEffectivePriority +0x801a9bb8 __OSPromoteThread +0x801a9c08 SelectThread +0x801a9e30 __OSReschedule +0x801a9e48 OSYieldThread 0x801a9e84 OSCreateThread +0x801aa0f0 OSExitThread 0x801aa1d4 OSCancelThread +0x801aa3ac OSJoinThread 0x801aa4ec OSDetachThread 0x801aa58c OSResumeThread 0x801aa824 OSSuspendThread 0x801aa9b8 OSSleepThread 0x801aaaa4 OSWakeupThread +0x801aab98 OSSetThreadPriority 0x801aaca8 OSSleepTicks 0x801aad5c OSGetTime 0x801aad74 OSGetTick 0x801aad7c __OSGetSystemTime +0x801aade0 __OSTimeToSystemTime +0x801aae38 GetDates 0x801aafa8 OSTicksToCalendarTime +0x801ab170 OSCalendarTimeToTicks +0x801ab410 OSUTF8to32 +0x801ab520 OSUTF16to32 +0x801ab590 OSUTF32toANSI +0x801ab608 OSUTF32toSJIS 0x801ab648 __OSGetIPCBufferHi 0x801ab650 __OSGetIPCBufferLo 0x801ab658 __OSInitIPCBuffer +0x801ab670 OSSetResetCallback +0x801ab75c OSSetPowerCallback +0x801ab848 __OSInitSTM +0x801ab960 __OSShutdownToSBY +0x801ab9d8 __OSHotReset +0x801aba48 __OSSetVIForceDimming +0x801abb40 __OSSetIdleLEDMode +0x801abb80 __OSUnRegisterStateEvent +0x801abbf8 __OSVIDimReplyHandler +0x801abd64 PlayRecordAlarmCallback +0x801abd70 PlayRecordCallback +0x801ac220 __OSStartPlayRecord +0x801ac274 __OSStopPlayRecord +0x801ac45c __OSWriteStateFlags +0x801ac540 __OSReadStateFlags +0x801ac668 __OSInitNet +0x801ac71c __OSCreateNandbootInfo +0x801ac7cc __OSWriteNandbootInfo +0x801ac924 OSGetLaunchCode +0x801ac930 OSGetReturnCode +0x801ac93c OSPlayTimeIsLimited +0x801ac9dc __OSPlayTimeFadeLastAIDCallback +0x801acb98 __OSWriteExpiredFlag +0x801accac __OSPlayTimeRebootThread +0x801aceb4 __OSGetPlayTime +0x801ad1d4 OSCheckInstall +0x801ad428 __OSCheckCompanyCode +0x801ad53c __OSCheckTmdSysVersion +0x801ad610 __OSGetValidTicketIndex +0x801ad800 __OSRelaunchTitle +0x801ada00 __OSLaunchTitle +0x801adb1c LaunchCommon +0x801adf60 OSLaunchTitlel +0x801ae06c __OSReturnToMenul +0x801ae174 OSGetTitleStatus +0x801ae4a0 OSIsTitleInstalled 0x801ae5d8 PAD_ClampCircle 0x801ae6f4 PADClampCircle 0x801ae7dc PADClampCircle2 @@ -1120,9 +1634,58 @@ 0x801afffc PAD_OnReset 0x801b00c4 PAD_SamplingHandler 0x801b0124 __PADDisableRecalibration +0x801b0180 SCInit +0x801b0220 SCCheckStatus +0x801b033c SCReloadConfFileAsync +0x801b0450 OpenCallbackFromReload +0x801b0520 ReadCallbackFromReload +0x801b0608 CloseCallbackFromReload +0x801b0694 FinishFromReload +0x801b07d4 ParseConfBuf +0x801b0a20 UnpackItem +0x801b0bb8 DeleteItemByID +0x801b0d48 CreateItemByID +0x801b0fc0 SCFindByteArrayItem +0x801b10a0 SCReplaceByteArrayItem +0x801b11c4 SCReplaceIntegerItem +0x801b12dc SCFindU8Item +0x801b13b0 SCFindS8Item +0x801b1484 SCFindU32Item +0x801b1558 SCFindBoolItem +0x801b1658 SCReplaceU8Item +0x801b1684 __SCFlushSyncCallback +0x801b1690 SCFlushAsync +0x801b18ac MyNandCallback 0x801b1be4 SCGetAspectRatio +0x801b1c38 SCGetDisplayOffsetH 0x801b1cac SCGetEuRgb60Mode +0x801b1d00 SCGetIdleMode +0x801b1d0c SCGetLanguage +0x801b1d78 SCGetParentalControl 0x801b1d84 SCGetProgressiveMode +0x801b1dd8 SCGetScreenSaverMode +0x801b1e2c SCGetSoundMode +0x801b1e90 SCGetCounterBias +0x801b1ed0 SCGetBtDeviceInfoArray +0x801b1edc SCSetBtDeviceInfoArray +0x801b1ee8 SCGetBtCmpDevInfoArray +0x801b1ef4 SCSetBtCmpDevInfoArray +0x801b1f00 SCGetBtDpdSensibility +0x801b1f68 SCGetWpadMotorMode +0x801b1fbc SCSetWpadMotorMode +0x801b1fc4 SCGetWpadSensorBarPosition +0x801b2018 SCGetWpadSpeakerVolume +0x801b206c SCSetWpadSpeakerVolume +0x801b2074 SCGetSimpleAddressData +0x801b2130 SCGetNetContentRestrictions +0x801b216c SCGetEULA +0x801b21a8 SCGetWCFlags +0x801b21e4 SCGetFreeChannelAppCount +0x801b2234 __SCF1 +0x801b23a0 SCGetProductArea +0x801b2424 SCGetProductCode +0x801b2460 SCGetProductSN +0x801b24c8 SCGetProductGameRegion 0x801b254c SIBusy 0x801b2568 SIIsChanBusy 0x801b2cf8 SIUnregisterPollingHandler @@ -1136,11 +1699,34 @@ 0x801b3808 SIGetType 0x801b39bc SIGetTypeAsync 0x801b3ba4 SIRefreshSamplingRate +0x801b3bac THPVideoDecode +0x801b3e6c __THPReadFrameHeader +0x801b3fa0 __THPReadScaneHeader +0x801b40b4 __THPReadQuantizationTable +0x801b444c __THPReadHuffmanTableSpecification +0x801b4810 __THPPrepBitStream +0x801b4a58 __THPDecompressYUV +0x801b4b5c __THPDecompressiMCURow512x448 +0x801b4da8 __THPInverseDCTNoYPos +0x801b5234 __THPInverseDCTY8 +0x801b56c8 __THPDecompressiMCURow640x480 +0x801b5918 __THPDecompressiMCURowNxN +0x801b5b74 __THPHuffDecodeDCTCompY +0x801b61d0 __THPHuffDecodeDCTCompU +0x801b6858 __THPHuffDecodeDCTCompV +0x801b6ee0 THPInit 0x801b7410 TPLBind 0x801b7524 TPLGet 0x801b7544 TPLGetGXTexObjFromPalette +0x801b88e4 __VIRetraceHandler +0x801b90f4 VISetPreRetraceCallback +0x801b9138 VISetPostRetraceCallback +0x801b917c getTiming +0x801b9294 __VIInit 0x801b94a4 VIInit 0x801b99ec VIWaitForRetrace +0x801b9a40 setFbbRegs +0x801b9dd8 setVerticalRegs 0x801b9f6c VIConfigure 0x801ba650 VIConfigurePan 0x801ba9a4 VIFlush @@ -1152,9 +1738,276 @@ 0x801bac48 VIGetCurrentLine 0x801bacd8 VIGetTvFormat 0x801bad38 VIGetDTVStatus +0x801bad74 __VIDisplayPositionToXY 0x801bafa8 VISetTimeToDimming 0x801bb0d0 sub_801BB0D0 +0x801cb988 DEBUGPrint +0x801cb9d8 App_MEMalloc +0x801cba28 App_MEMfree +0x801d0a1c NCDGetCurrentIfConfig +0x801d0b14 NCDGetCurrentIpConfig 0x801d0c6c NCDGetLinkStatus +0x801d0d70 NCDiGetWirelessMacAddress +0x801d0e98 NCDiGetEnabledConfigList +0x801d11a8 LockRight +0x801d1298 NETGetUniversalCalendar +0x801d137c NETiGetConnectionTypeFromConfigList +0x801d15f4 NETGetStartupErrorCode +0x801d1674 GetStartupErrorCode +0x801d17ec get_REX_PPC_REVOEX_string +0x801d17f8 NETMemCpy +0x801d1ba0 NETMemSet +0x801d1ca0 NETCalcCRC32 +0x801d1dd4 NETMD5Init +0x801d1e14 NETMD5Update +0x801d1f04 NETMD5GetDigest +0x801d202c ProcessBlock +0x801d24f4 NETSHA1Init +0x801d2544 NETSHA1Update +0x801d25f8 NETSHA1GetDigest +0x801d2730 NETSHA1iProcessBlock +0x801d2d08 NETHMACUpdate +0x801d2ebc NHTTPi_InitBgnEndInfo +0x801d2eec NHTTPi_alloc +0x801d2f48 NHTTPi_free +0x801d2f8c NHTTPi_SetError +0x801d2f94 NHTTPi_SetSSLError +0x801d2f9c NHTTPi_GetError +0x801d2fa4 NHTTPi_Startup +0x801d3134 NHTTPi_CleanupAsync +0x801d3224 addHdrList +0x801d3360 NHTTPi_getHdrFromList +0x801d33a8 NHTTP_AddHeaderField +0x801d33c8 NHTTP_AddPostDataAscii +0x801d3554 NHTTPi_InitListInfo +0x801d3564 NHTTPi_setReqQueue +0x801d3630 NHTTPi_freeReqQueue +0x801d374c NHTTPi_allFreeReqQueue +0x801d37a0 NHTTPi_getReqFromReqQueue +0x801d37a4 NHTTPi_InitMutexInfo +0x801d37b0 NHTTPi_initLockReqList +0x801d37f0 NHTTPi_exitLockReqList +0x801d37f4 NHTTPi_lockReqList +0x801d37f8 NHTTPi_unlockReqList +0x801d37fc NHTTPi_createCommThread +0x801d3890 NHTTPi_destroyCommThread +0x801d38d8 NHTTPi_idleCommThread +0x801d3900 NHTTPi_kickCommThread +0x801d390c NHTTPi_CheckCurrentThread +0x801d39a4 NHTTPi_CommThreadProc +0x801d39c8 NHTTPi_findNextLineHdrRecvBuf +0x801d3bc0 NHTTPi_skipSpaceHdrRecvBuf +0x801d3cb8 NHTTPi_compareTokenN_HdrRecvBuf +0x801d3ea8 NHTTPi_loadFromHdrRecvBuf +0x801d3fec NHTTPi_isRecvBufFull +0x801d4008 NHTTPi_RecvBuf +0x801d4028 NHTTPi_RecvBufN +0x801d4064 NHTTPi_InitRequestInfo +0x801d4070 NHTTP_CreateRequest +0x801d462c NHTTP_DestroyRequest +0x801d4730 NHTTPi_destroyRequestObject +0x801d4808 NHTTP_SendRequestAsync +0x801d48c8 NHTTP_CancelRequestAsync +0x801d498c NHTTPi_cancelAllRequests +0x801d4a34 NHTTP_DestroyResponse +0x801d4b00 NHTTPi_getHeaderValue +0x801d4c34 NHTTPi_checkKeepAlive +0x801d4c8c NHTTPi_SocOpen +0x801d4d18 NHTTPi_SocClose +0x801d4d84 NHTTPi_SocConnect +0x801d4e44 NHTTPi_SocSSLConnect +0x801d4ff4 NHTTPi_SocRecv_sub +0x801d5100 NHTTPi_SocRecv +0x801d51dc NHTTPi_SocSend_sub +0x801d5374 NHTTPi_SocSend +0x801d5420 NHTTPi_SocCancel +0x801d5474 NHTTPi_resolveHostname +0x801d54e8 NHTTPi_memcpy +0x801d54ec NHTTPi_strlen +0x801d54f0 NHTTPi_strcmp +0x801d54f4 NHTTPi_memclr +0x801d5500 NHTTPi_strnicmp +0x801d55cc NHTTPi_getUrlEncodedSize +0x801d563c NHTTPi_getUrlEncodedSize2 +0x801d56b0 NHTTPi_encodeUrlChar +0x801d5750 NHTTPi_strToHex +0x801d5874 NHTTPi_strToInt +0x801d5914 NHTTPi_intToStr +0x801d5a90 NHTTPi_compareToken +0x801d5b44 NHTTPi_strtonum +0x801d5bd4 NHTTPi_memfind +0x801d5c80 NHTTPi_Base64Encode +0x801d5e5c NHTTPi_InitThreadInfo +0x801d5e68 NHTTPi_IsCreateCommThreadMessageQueueOn +0x801d5e74 NHTTPi_IsCreateCommThreadMessageQueue +0x801d5e7c NHTTPi_CheckHeaderEnd +0x801d5f44 NHTTPi_SaveBuf +0x801d6024 NHTTPi_GetPostContentlength +0x801d6128 NHTTPi_SendPostData +0x801d62b8 NHTTPi_BufFull +0x801d6384 NHTTPi_SendData +0x801d6430 NHTTPi_SendProxyConnectMethod +0x801d66d8 NHTTPi_RecvProxyConnectHeader +0x801d68d0 NHTTPi_SendHeaderList +0x801d69c8 NHTTPi_SendProcPostDataRaw +0x801d6b50 NHTTPi_SendProcPostDataBinary +0x801d6ef4 NHTTPi_SendProcPostDataAscii +0x801d71b8 NHTTPi_ThreadReqEnd +0x801d7338 NHTTPi_ThreadExecReqQueue +0x801d73f0 NHTTPi_ThreadHostAddrProc +0x801d7544 NHTTPi_ThreadConnectProc +0x801d7700 NHTTPi_ThreadProxyProc +0x801d7854 NHTTPi_ThreadSendProc +0x801d7cf0 NHTTPi_ThreadRecvHeaderProc +0x801d7edc NHTTPi_ThreadParseHeaderProc +0x801d820c NHTTPi_ThreadRecvBodyProc +0x801d87fc NHTTPi_CommThreadProcMain +0x801d8a00 __NHTTPCreateConnection +0x801d8b48 NHTTPStartConnection +0x801d8bd4 NHTTPGetBodyBuffer +0x801d8c70 NHTTPGetUserParam +0x801d8ce4 NHTTPGetConnectionError +0x801d8d30 NHTTPStartup +0x801d8dbc NHTTPCleanupAsync +0x801d8df0 NHTTPGetError +0x801d8e14 NHTTPi_TemplateConnectionCallback +0x801d8ff8 __NHTTPCreateRequest +0x801d9004 __NHTTPCreateRequestEx +0x801d90d4 NHTTPAddHeaderField +0x801d9198 NHTTPAddPostDataAscii +0x801d925c NHTTPSendRequestAsync +0x801d92b8 NHTTPCancelRequestAsync +0x801d92f8 NHTTPDestroyResponse +0x801d937c NHTTPGetBodyAll +0x801d93e4 NHTTPGetResultCode +0x801d9444 NHTTPSetVerifyOption +0x801d94a0 NHTTPSetProxy +0x801d9610 NHTTPSetProxyDefault +0x801d9738 NHTTPSetRootCADefault +0x801d9798 NHTTPSetClientCertDefault +0x801d9808 NHTTPi_NotifyCompletion +0x801d9884 NHTTPi_ControlConnectionList +0x801d9994 NHTTPi_CommitConnectionList +0x801d99c4 NHTTPi_OmitConnectionList +0x801d99f4 NHTTPi_Connection2Request +0x801d9a2c NHTTPi_Connection2Response +0x801d9a64 NHTTPi_Request2Connection +0x801d9a6c NHTTPi_Response2Connection +0x801d9a74 NHTTPi_GetConnection +0x801d9a7c NHTTPi_GetRequest +0x801d9ac0 NHTTPi_GetResponse +0x801d9b04 NHTTPi_GetConnectionListLength +0x801d9b24 NHTTPi_PostSendCallback +0x801d9c0c NHTTPi_BufferFullCallback +0x801d9d24 NHTTPi_ReceivedCallback +0x801d9e3c NHTTPi_CompleteCallback +0x801d9e94 NHTTPi_GetSystemInfoP +0x801d9efc NHTTPi_GetBgnEndInfoP +0x801d9f00 NHTTPi_GetListInfoP +0x801d9f08 NHTTPi_GetReqInfoP +0x801d9f10 NHTTPi_GetThreadInfoP +0x801d9f18 NHTTPi_GetMutexInfoP +0x801d9f20 NHTTPi_SetVirtualContentLength +0x801d9f30 NHTTPi_GetVirtualContentLength +0x801d9f48 NHTTPi_InitConnectionList +0x801d9fa8 Mail_strcpy +0x801d9fd8 Mail_strlen +0x801d9ffc STD_strnlen +0x801da02c Mail_memcpy +0x801da030 Mail_memset +0x801da034 Mail_strncpy +0x801da090 Mail_strcat +0x801da0e0 Mail_strncat +0x801da140 Util_xtoi +0x801da19c Mail_tolower +0x801da1b4 convNum +0x801da71c Mail_sprintf +0x801da7a8 Mail_vsprintf +0x801dac68 NWC24FOpen +0x801dad5c NWC24iFOpenNand +0x801dae94 NWC24iFOpenVF +0x801dafbc NWC24FClose +0x801db0a4 NWC24iFCloseNand +0x801db154 NWC24FSeek +0x801db344 NWC24FRead +0x801db514 NWC24FWrite +0x801db6ec NWC24FGetLength +0x801db7a8 NWC24FDeleteVF +0x801db828 NWC24MountVF +0x801db930 NWC24UnmountVF +0x801db9b0 NWC24CheckSizeVF +0x801dba44 NWC24CreateVF +0x801dbdfc BufferedWrite +0x801dbf90 BufferedWriteFlush +0x801dc100 BufferedRead +0x801dc2ec NWC24GetMyUserId +0x801dc454 NWC24iConfigOpen +0x801dc4cc NWC24iConfigReload +0x801dc5c0 NWC24iConfigFlush +0x801dc740 NWC24GetAccountDomain +0x801dc74c NWC24GetMBoxDir +0x801dc7bc NWC24GetAppId +0x801dc808 NWC24GetGroupId +0x801dc8ac NWC24GetIdCreationStage +0x801dc8c0 CheckConfig +0x801dc9ac NWC24Data_Init +0x801dc9bc NWC24Data_SetDataP +0x801dc9c8 NWC24Date_Init +0x801dc9f8 NWC24iConvIdToStr +0x801dcbd4 NWC24iCheckStringLength +0x801dcc50 NWC24iStrLCpy +0x801dccc4 NWC24iRegister +0x801dccfc NWC24OpenLib +0x801dcd1c NWC24OpenLibInternal +0x801dcee8 NWC24CloseLib +0x801dcf54 NWC24IsMsgLibOpened +0x801dcf68 NWC24IsMsgLibOpenedByTool +0x801dcf7c NWC24IsMsgLibOpenBlocking +0x801dcf90 NWC24BlockOpenMsgLib +0x801dd030 NWC24iSetNewMsgArrived +0x801dd220 NWC24GetErrorCode +0x801dd228 NWC24iSetErrorCode +0x801dd3d4 AnalyzeErrorCode +0x801dd76c ReadSavedErrorCode +0x801dd818 NWC24InitMsgObj +0x801dda08 NWC24SetMsgToId +0x801dda6c NWC24SetMsgToAddr +0x801ddb34 NWC24SetMsgSubject +0x801ddba4 NWC24SetMsgText +0x801ddcdc NWC24SetMsgAttached +0x801ddf44 NWC24SetMsgTag +0x801ddf84 NWC24SetMsgFaceData +0x801ddfe0 NWC24SetMsgAltName +0x801de064 NWC24SetMsgMBNoReply +0x801de0bc NWC24SetMsgMBRegDate +0x801de190 NWC24SetMsgMBDelay +0x801de24c NWC24SetMsgDWCId +0x801de27c NWC24SetMsgLedPatternRaw +0x801de348 NWC24SetMsgExecScript +0x801de3a8 NWC24GetMsgType +0x801de420 NWC24GetMsgNumAttached +0x801de43c NWC24GetMsgAttachedSize +0x801de470 NWC24GetMsgAttachedType +0x801de4ec NWC24GetMsgId +0x801de50c NWC24GetMsgTag +0x801de51c NWC24GetMsgFromId +0x801de548 NWC24GetMsgNumTo +0x801de574 NWC24GetMsgDWCId +0x801de5a0 NWC24GetMsgDate +0x801de5d0 NWC24SetMsgDesignatedTime +0x801de66c NWC24GetNumMsgs +0x801de6f0 NWC24GetMsgObj +0x801de974 NWC24iIsMsgObjReadable +0x801dea58 NWC24DeleteMsg +0x801deab8 NWC24CheckMsgBoxSpace +0x801deba8 NWC24iOpenMBox +0x801ded3c NWC24iMBoxGetCtrlFilePath +0x801def4c NWC24iMBoxOpenStoredMsg +0x801df024 NWC24iMBoxCloseMsg +0x801df0d8 NWC24iMBoxCancelMsg +0x801df2dc NWC24iMBoxAddMsgObj +0x801df4a8 NWC24iMBoxFlushHeader +0x801df5f8 NWC24iMBoxCheck 0x801e5fd8 NWC24iStartupSocket 0x801e5fe8 NWC24iCleanupSocket 0x801e5ff8 NWC24iLockSocket diff --git a/source/decomp.h b/source/decomp.h index b4b180eab..ac4526de8 100644 --- a/source/decomp.h +++ b/source/decomp.h @@ -10,70 +10,4 @@ __attribute__((force_export)) // Compiler intrinsics. - -// PAL: 0x8002156C -extern void _savegpr_14(void); -// PAL: 0x80021570 -extern void _savegpr_15(void); -// PAL: 0x80021574 -extern void _savegpr_16(void); -// PAL: 0x80021578 -extern void _savegpr_17(void); -// PAL: 0x8002157C -extern void _savegpr_18(void); -// PAL: 0x80021580 -extern void _savegpr_19(void); -// PAL: 0x80021584 -extern void _savegpr_20(void); -// PAL: 0x80021588 -extern void _savegpr_21(void); -// PAL: 0x8002158C -extern void _savegpr_22(void); -// PAL: 0x80021590 -extern void _savegpr_23(void); -// PAL: 0x80021594 -extern void _savegpr_24(void); -// PAL: 0x80021598 -extern void _savegpr_25(void); -// PAL: 0x8002159c -extern void _savegpr_26(void); -// PAL: 0x800215a0 -extern void _savegpr_27(void); - -// PAL: 0x800215b8 -extern void _restgpr_14(void); -// PAL: 0x800215bc -extern void _restgpr_15(void); -// PAL: 0x800215c0 -extern void _restgpr_16(void); -// PAL: 0x800215c4 -extern void _restgpr_17(void); -// PAL: 0x800215c8 -extern void _restgpr_18(void); -// PAL: 0x800215cc -extern void _restgpr_19(void); -// PAL: 0x800215d0 -extern void _restgpr_20(void); -// PAL: 0x800215d4 -extern void _restgpr_21(void); -// PAL: 0x800215d8 -extern void _restgpr_22(void); -// PAL: 0x800215dc -extern void _restgpr_23(void); -// PAL: 0x800215e0 -extern void _restgpr_24(void); -// PAL: 0x800215e4 -extern void _restgpr_25(void); -// PAL: 0x800215e8 -extern void _restgpr_26(void); -// PAL: 0x800215ec -extern void _restgpr_27(void); - -extern void __div2u(void); -// PAL: 0x800216f0 -extern void __div2i(void); -extern void __mod2u(void); -extern void __mod2i(void); -extern void __shl2i(void); -extern void __shr2u(void); -extern void __shr2i(void); +#include diff --git a/source/egg/core/eggThread.cpp b/source/egg/core/eggThread.cpp index a89f14ef5..d98c031b8 100644 --- a/source/egg/core/eggThread.cpp +++ b/source/egg/core/eggThread.cpp @@ -2,6 +2,8 @@ #include #include +#include + namespace EGG { nw4r::ut::List Thread::sThreadList; diff --git a/source/gamespy/GP/gpiUtility.c b/source/gamespy/GP/gpiUtility.c index bccb9f4a0..e3deca72e 100644 --- a/source/gamespy/GP/gpiUtility.c +++ b/source/gamespy/GP/gpiUtility.c @@ -14,7 +14,6 @@ Please see the GameSpy Presence SDK documentation for more information // INCLUDES ////////// #include "gpi.h" -#include #include #include #include diff --git a/source/gamespy/common/gsDebug.c b/source/gamespy/common/gsDebug.c index 43beb0735..1a18f1fae 100644 --- a/source/gamespy/common/gsDebug.c +++ b/source/gamespy/common/gsDebug.c @@ -3,7 +3,6 @@ #include "gsDebug.h" #include "gsCommon.h" //#include -//#include // THIS FILE ONLY INCLUDED WHEN USING GAMESPY DEBUG FUNCTIONS // (don't put this above the header includes or VC will whine diff --git a/source/gamespy/common/gsDebug.h b/source/gamespy/common/gsDebug.h index 3ab0a19ce..d15bd264f 100644 --- a/source/gamespy/common/gsDebug.h +++ b/source/gamespy/common/gsDebug.h @@ -11,7 +11,6 @@ // Todo: // Allow user to specify IP to send debug output to (remote log for PS2) //#include "nonport.h" -#include #if defined(__LANGUAGE_C_PLUS_PLUS) || defined(__cplusplus) || \ defined(c_plusplus) diff --git a/source/gamespy/common/gsPlatformThread.h b/source/gamespy/common/gsPlatformThread.h index 34f8700d4..8d9995a9f 100644 --- a/source/gamespy/common/gsPlatformThread.h +++ b/source/gamespy/common/gsPlatformThread.h @@ -4,8 +4,8 @@ #include "gsPlatform.h" +#include #include -#include #ifdef __cplusplus extern "C" { diff --git a/source/gamespy/common/revolution/gsThreadRevolution.c b/source/gamespy/common/revolution/gsThreadRevolution.c index d9ea9b796..d1021b1b6 100644 --- a/source/gamespy/common/revolution/gsThreadRevolution.c +++ b/source/gamespy/common/revolution/gsThreadRevolution.c @@ -3,6 +3,7 @@ #include "../gsCommon.h" #include +#include // Begin of Threading for Revolution /////////////////////////////////////////////////////////////////////////////// diff --git a/source/hardware.h b/source/hardware.h new file mode 100644 index 000000000..5abe41270 --- /dev/null +++ b/source/hardware.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Hardware registers. +volatile u32 HOLLYWOOD_REGS[137] : 0xcd000000; + +volatile u16 __CPRegs[0x33] : 0xcc000000; +volatile u16 __VIRegs[0x3b] : 0xcc002000; +volatile u32 __PIRegs[0x0c] : 0xcc003000; +volatile u16 __MEMRegs[0x40] : 0xcc004000; +volatile u16 __DSPRegs[0x20] : 0xcc005000; +volatile u32 __ACRRegs[0x89] : 0xcd000000; +volatile u32 __DIRegs[0x10] : 0xcc006000; +volatile u32 __SIRegs[0x40] : 0xcc006400; +volatile u32 __EXIRegs[0x10] : 0xcd006800; +volatile u32 __AIRegs[0x08] : 0xcd006c00; + +#ifdef __cplusplus +} +#endif diff --git a/source/platform/ExceptionPPC.cpp b/source/platform/ExceptionPPC.cpp new file mode 100644 index 000000000..697973a84 --- /dev/null +++ b/source/platform/ExceptionPPC.cpp @@ -0,0 +1,37 @@ +#include "ExceptionPPC.h" + +typedef struct ProcessInfo { + __eti_init_info* exception; + char* TOC; + int active; +} ProcessInfo; + +static ProcessInfo fragmentinfo[1]; + +// Symbol: __register_fragment +// PAL: 0x80020dd8..0x80020e0c +int __register_fragment(struct __eti_init_info* info, char* TOC) { + ProcessInfo* f; + int i; + for (i = 0, f = fragmentinfo; i < 1; ++i, ++f) { + if (f->active == 0) { + f->exception = info; + f->TOC = TOC; + f->active = 1; + return i; + } + } + return -1; +} + +// Symbol: __unregister_fragment +// PAL: 0x80020e0c..0x80020e34 +void __unregister_fragment(int fragmentID) { + ProcessInfo* f; + if (fragmentID >= 0 && fragmentID < 1) { + f = &fragmentinfo[fragmentID]; + f->exception = 0; + f->TOC = 0; + f->active = 0; + } +} diff --git a/source/platform/ExceptionPPC.h b/source/platform/ExceptionPPC.h new file mode 100644 index 000000000..4ea372787 --- /dev/null +++ b/source/platform/ExceptionPPC.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "__ppc_eabi_linker.h" +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x80020dd8..0x80020e0c +int __register_fragment(struct __eti_init_info* info, char* TOC); +// PAL: 0x80020e0c..0x80020e34 +void __unregister_fragment(int fragmentID); + +#ifdef __cplusplus +} +#endif diff --git a/source/platform/__init_cpp_exceptions.cpp b/source/platform/__init_cpp_exceptions.cpp new file mode 100644 index 000000000..e31c99a12 --- /dev/null +++ b/source/platform/__init_cpp_exceptions.cpp @@ -0,0 +1,43 @@ +#include "__init_cpp_exceptions.h" + +#include "ExceptionPPC.h" +#include "global_destructor_chain.h" + +static int fragmentID = -2; + +static inline void __exception_info_constants(void** info, char** R2) { + register char* temp; + asm { mr temp, r2; } + *R2 = temp; + *info = (void*)_eti_init_info_; +} + +// Symbol: __init_cpp_exceptions +// PAL: 0x800211e4..0x80021220 +void __init_cpp_exceptions(void) { + char* R2; + void* info; + if (fragmentID == -2) { + __exception_info_constants(&info, &R2); + fragmentID = __register_fragment((struct __eti_init_info*)info, R2); + } +} + +// Symbol: __fini_cpp_exceptions +// PAL: 0x80021220..0x80021254 +void __fini_cpp_exceptions(void) { + if (fragmentID != -2) { + __unregister_fragment(fragmentID); + fragmentID = -2; + } +} + +#pragma section ".ctors$10" +__declspec(section ".ctors$10") extern void* const + __init_cpp_exceptions_reference = __init_cpp_exceptions; +#pragma section ".dtors$10" +__declspec(section ".dtors$10") extern void* const + __destroy_global_chain_reference = __destroy_global_chain; +#pragma section ".dtors$15" +__declspec(section ".dtors$15") extern void* const + __fini_cpp_exceptions_reference = __fini_cpp_exceptions; diff --git a/source/platform/__init_cpp_exceptions.h b/source/platform/__init_cpp_exceptions.h new file mode 100644 index 000000000..a487ea319 --- /dev/null +++ b/source/platform/__init_cpp_exceptions.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x800211e4..0x80021220 +void __init_cpp_exceptions(void); +// PAL: 0x80021220..0x80021254 +void __fini_cpp_exceptions(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/platform/__ppc_eabi_linker.h b/source/platform/__ppc_eabi_linker.h new file mode 100644 index 000000000..7ba9b8e0c --- /dev/null +++ b/source/platform/__ppc_eabi_linker.h @@ -0,0 +1,13 @@ +#pragma once + +typedef struct __eti_init_info { + void* eti_start; + void* eti_end; + void* code_start; + unsigned long code_size; +} __eti_init_info; + +// _eti_init_info is a linker-generated symbol. +// Since we don't have proper extab/extabindex sections, +// trying to reference it is going to fail. +__declspec(section ".init") extern __eti_init_info _eti_init_info_[]; diff --git a/source/platform/ansi_files.c b/source/platform/ansi_files.c new file mode 100644 index 000000000..fa6e04855 --- /dev/null +++ b/source/platform/ansi_files.c @@ -0,0 +1,149 @@ +#include "ansi_files.h" + +#include + +// Extern function references. +// PAL: 0x8000c818 +extern UNKNOWN_FUNCTION(unk_8000c818); +// PAL: 0x8000ed18 +extern UNKNOWN_FUNCTION(unk_8000ed18); + +// Symbol: __close_all +// PAL: 0x8000c948..0x8000c9ec +MARK_BINARY_BLOB(__close_all, 0x8000c948, 0x8000c9ec); +asm void __close_all(void) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + li r31, 0; + stw r30, 0x18(r1); + li r30, 3; + stw r29, 0x14(r1); + lis r29, 0x8027; + addi r29, r29, 0xcf0; + b lbl_8000c9c8; +lbl_8000c974: + lwz r0, 4(r29); + rlwinm. r0, r0, 0xa, 0x1d, 0x1f; + beq lbl_8000c988; + mr r3, r29; + bl fclose; +lbl_8000c988: + mr r3, r29; + lwz r29, 0x4c(r29); + lbz r0, 0xc(r3); + cmpwi r0, 0; + beq lbl_8000c9a4; + bl unk_8000c818; + b lbl_8000c9c8; +lbl_8000c9a4: + lwz r0, 4(r3); + cmpwi r29, 0; + rlwimi r0, r30, 0x16, 7, 9; + stw r0, 4(r3); + beq lbl_8000c9c8; + lbz r0, 0xc(r29); + cmpwi r0, 0; + beq lbl_8000c9c8; + stw r31, 0x4c(r3); +lbl_8000c9c8: + cmpwi r29, 0; + bne lbl_8000c974; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __flush_line_buffered_output_files +// Function signature is unknown. +// PAL: 0x8000c9ec..0x8000ca70 +MARK_BINARY_BLOB(__flush_line_buffered_output_files, 0x8000c9ec, 0x8000ca70); +asm int __flush_line_buffered_output_files(void) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + li r31, 0; + stw r30, 8(r1); + lis r30, 0x8027; + addi r30, r30, 0xcf0; + b lbl_8000ca4c; +lbl_8000ca10: + lwz r3, 4(r30); + rlwinm. r0, r3, 0xa, 0x1d, 0x1f; + beq lbl_8000ca48; + rlwinm. r0, r3, 7, 0x1f, 0x1f; + beq lbl_8000ca48; + lwz r0, 8(r30); + srwi r0, r0, 0x1d; + cmplwi r0, 1; + bne lbl_8000ca48; + mr r3, r30; + bl unk_8000ed18; + cmpwi r3, 0; + beq lbl_8000ca48; + li r31, -1; +lbl_8000ca48: + lwz r30, 0x4c(r30); +lbl_8000ca4c: + cmpwi r30, 0; + bne lbl_8000ca10; + mr r3, r31; + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __flush_all +// Function signature is unknown. +// PAL: 0x8000ca70..0x8000cadc +MARK_BINARY_BLOB(__flush_all, 0x8000ca70, 0x8000cadc); +asm int __flush_all(void) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + li r31, 0; + stw r30, 8(r1); + lis r30, 0x8027; + addi r30, r30, 0xcf0; + b lbl_8000cab8; +lbl_8000ca94: + lwz r0, 4(r30); + rlwinm. r0, r0, 0xa, 0x1d, 0x1f; + beq lbl_8000cab4; + mr r3, r30; + bl unk_8000ed18; + cmpwi r3, 0; + beq lbl_8000cab4; + li r31, -1; +lbl_8000cab4: + lwz r30, 0x4c(r30); +lbl_8000cab8: + cmpwi r30, 0; + bne lbl_8000ca94; + mr r3, r31; + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} diff --git a/source/platform/ansi_files.h b/source/platform/ansi_files.h new file mode 100644 index 000000000..b15c5eaa4 --- /dev/null +++ b/source/platform/ansi_files.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x8000c948..0x8000c9ec +void __close_all(void); +// PAL: 0x8000c9ec..0x8000ca70 +int __flush_line_buffered_output_files(void); +// PAL: 0x8000ca70..0x8000cadc +int __flush_all(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/platform/ansi_fp.c b/source/platform/ansi_fp.c new file mode 100644 index 000000000..640980832 --- /dev/null +++ b/source/platform/ansi_fp.c @@ -0,0 +1,1877 @@ +#include "ansi_fp.h" + +#include + +// Symbol: __ull2dec +// Function signature is unknown. +// PAL: 0x8000cadc..0x8000cbb8 +MARK_BINARY_BLOB(__ull2dec, 0x8000cadc, 0x8000cbb8); +asm UNKNOWN_FUNCTION(__ull2dec) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + li r0, 0; + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r6; + stw r29, 0x14(r1); + mr r29, r3; + stb r0, 0(r3); + stb r0, 4(r3); + b lbl_8000cb54; +lbl_8000cb10: + mr r3, r31; + mr r4, r30; + li r6, 0xa; + li r5, 0; + bl __mod2u; + lbz r8, 4(r29); + mr r3, r31; + li r6, 0xa; + li r5, 0; + add r7, r29, r8; + addi r0, r8, 1; + stb r4, 5(r7); + mr r4, r30; + stb r0, 4(r29); + bl __div2u; + mr r30, r4; + mr r31, r3; +lbl_8000cb54: + or. r0, r30, r31; + bne lbl_8000cb10; + lbz r0, 4(r29); + addi r4, r29, 5; + add r3, r29, r0; + addi r3, r3, 5; + b lbl_8000cb84; +lbl_8000cb70: + lbz r5, 0(r4); + lbz r0, 0(r3); + stb r0, 0(r4); + addi r4, r4, 1; + stb r5, 0(r3); +lbl_8000cb84: + addi r3, r3, -1; + cmplw r4, r3; + blt lbl_8000cb70; + lbz r3, 4(r29); + addi r0, r3, -1; + sth r0, 2(r29); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __timesdec +// Function signature is unknown. +// PAL: 0x8000cbb8..0x8000ce40 +MARK_BINARY_BLOB(__timesdec, 0x8000cbb8, 0x8000ce40); +asm UNKNOWN_FUNCTION(__timesdec) { + // clang-format off + nofralloc; + stwu r1, -0x60(r1); + lis r6, 0xcccd; + lbz r8, 4(r4); + addi r9, r6, -13107; + stw r31, 0x5c(r1); + addi r0, r1, 8; + lbz r7, 4(r5); + li r11, 0; + stw r30, 0x58(r1); + add r12, r8, r7; + stw r29, 0x54(r1); + addi r12, r12, -1; + add r6, r0, r12; + addi r6, r6, 1; + stb r11, 0(r3); + mr r0, r6; + b lbl_8000cd28; +lbl_8000cbfc: + lbz r7, 4(r5); + addi r31, r7, -1; + subf r7, r31, r12; + addic. r30, r7, -1; + bge lbl_8000cc18; + li r30, 0; + addi r31, r12, -1; +lbl_8000cc18: + lbz r7, 4(r4); + add r8, r5, r31; + addi r31, r31, 1; + add r10, r4, r30; + subf r7, r30, r7; + addi r29, r8, 5; + cmpw r31, r7; + addi r30, r10, 5; + ble lbl_8000cc40; + mr r31, r7; +lbl_8000cc40: + cmpwi r31, 0; + ble lbl_8000cd08; + rlwinm. r7, r31, 0x1d, 3, 0x1f; + mtctr r7; + beq lbl_8000cce8; +lbl_8000cc54: + lbz r10, 0(r30); + lbz r8, 0(r29); + mullw r7, r10, r8; + lbz r10, 1(r30); + lbz r8, -1(r29); + add r11, r11, r7; + mullw r7, r10, r8; + lbz r10, 2(r30); + lbz r8, -2(r29); + add r11, r11, r7; + mullw r7, r10, r8; + lbz r10, 3(r30); + lbz r8, -3(r29); + add r11, r11, r7; + mullw r7, r10, r8; + lbz r10, 4(r30); + lbz r8, -4(r29); + add r11, r11, r7; + mullw r7, r10, r8; + lbz r10, 5(r30); + lbz r8, -5(r29); + add r11, r11, r7; + mullw r7, r10, r8; + lbz r10, 6(r30); + lbz r8, -6(r29); + add r11, r11, r7; + mullw r7, r10, r8; + lbz r10, 7(r30); + lbz r8, -7(r29); + addi r30, r30, 8; + addi r29, r29, -8; + add r11, r11, r7; + mullw r7, r10, r8; + add r11, r11, r7; + bdnz lbl_8000cc54; + andi. r31, r31, 7; + beq lbl_8000cd08; +lbl_8000cce8: + mtctr r31; +lbl_8000ccec: + lbz r10, 0(r30); + addi r30, r30, 1; + lbz r8, 0(r29); + addi r29, r29, -1; + mullw r7, r10, r8; + add r11, r11, r7; + bdnz lbl_8000ccec; +lbl_8000cd08: + mulhwu r8, r9, r11; + addi r12, r12, -1; + mr r7, r8; + srwi r8, r8, 3; + mulli r8, r8, 0xa; + subf r8, r8, r11; + stbu r8, -1(r6); + srwi r11, r7, 3; +lbl_8000cd28: + cmpwi r12, 0; + bgt lbl_8000cbfc; + lha r7, 2(r4); + cmpwi r11, 0; + lha r4, 2(r5); + add r4, r7, r4; + sth r4, 2(r3); + beq lbl_8000cd58; + stbu r11, -1(r6); + extsh r4, r4; + addi r4, r4, 1; + sth r4, 2(r3); +lbl_8000cd58: + li r7, 0; + b lbl_8000cd74; +lbl_8000cd60: + add r4, r3, r7; + lbz r5, 0(r6); + stb r5, 5(r4); + addi r7, r7, 1; + addi r6, r6, 1; +lbl_8000cd74: + cmpwi r7, 0x24; + bge lbl_8000cd84; + cmplw r6, r0; + blt lbl_8000cd60; +lbl_8000cd84: + cmplw r6, r0; + stb r7, 4(r3); + bge lbl_8000ce2c; + lbz r4, 0(r6); + cmplwi r4, 5; + blt lbl_8000ce2c; + bne lbl_8000cdd4; + addi r5, r6, 1; + subf r4, r5, r0; + mtctr r4; + cmplw r5, r0; + bge lbl_8000cdc8; +lbl_8000cdb4: + lbz r0, 0(r5); + cmpwi r0, 0; + bne lbl_8000cdd4; + addi r5, r5, 1; + bdnz lbl_8000cdb4; +lbl_8000cdc8: + lbz r0, -1(r6); + clrlwi. r0, r0, 0x1f; + beq lbl_8000ce2c; +lbl_8000cdd4: + lbz r4, 4(r3); + addi r6, r3, 5; + li r0, 0; + add r5, r6, r4; + addi r5, r5, -1; +lbl_8000cde8: + lbz r4, 0(r5); + cmplwi r4, 9; + bge lbl_8000ce00; + addi r0, r4, 1; + stb r0, 0(r5); + b lbl_8000ce2c; +lbl_8000ce00: + cmplw r5, r6; + bne lbl_8000ce20; + li r0, 1; + stb r0, 0(r5); + lha r4, 2(r3); + addi r0, r4, 1; + sth r0, 2(r3); + b lbl_8000ce2c; +lbl_8000ce20: + stb r0, 0(r5); + addi r5, r5, -1; + b lbl_8000cde8; +lbl_8000ce2c: + lwz r31, 0x5c(r1); + lwz r30, 0x58(r1); + lwz r29, 0x54(r1); + addi r1, r1, 0x60; + blr; + // clang-format on +} + +// Symbol: __str2dec +// Function signature is unknown. +// PAL: 0x8000ce40..0x8000cf2c +MARK_BINARY_BLOB(__str2dec, 0x8000ce40, 0x8000cf2c); +asm UNKNOWN_FUNCTION(__str2dec) { + // clang-format off + nofralloc; + li r0, 0; + sth r5, 2(r3); + li r7, 0; + stb r0, 0(r3); + b lbl_8000ce6c; +lbl_8000ce54: + lbz r6, 0(r4); + add r5, r3, r7; + addi r4, r4, 1; + addi r7, r7, 1; + addi r0, r6, -48; + stb r0, 5(r5); +lbl_8000ce6c: + cmpwi r7, 0x24; + bge lbl_8000ce80; + lbz r0, 0(r4); + extsb. r0, r0; + bne lbl_8000ce54; +lbl_8000ce80: + lbz r0, 0(r4); + stb r7, 4(r3); + extsb. r0, r0; + beqlr; + cmpwi r0, 5; + bltlr; + bgt lbl_8000ced0; + addi r5, r4, 1; + b lbl_8000ceb4; +lbl_8000cea4: + extsb r0, r4; + cmpwi r0, 0x30; + bne lbl_8000ced0; + addi r5, r5, 1; +lbl_8000ceb4: + lbz r4, 0(r5); + extsb. r0, r4; + bne lbl_8000cea4; + add r4, r7, r3; + lbz r0, 4(r4); + clrlwi. r0, r0, 0x1f; + beqlr; +lbl_8000ced0: + lbz r4, 4(r3); + addi r6, r3, 5; + li r0, 0; + add r5, r6, r4; + addi r5, r5, -1; +lbl_8000cee4: + lbz r4, 0(r5); + cmplwi r4, 9; + bge lbl_8000cefc; + addi r0, r4, 1; + stb r0, 0(r5); + blr; +lbl_8000cefc: + cmplw r5, r6; + bne lbl_8000cf1c; + li r0, 1; + stb r0, 0(r5); + lha r4, 2(r3); + addi r0, r4, 1; + sth r0, 2(r3); + blr; +lbl_8000cf1c: + stb r0, 0(r5); + addi r5, r5, -1; + b lbl_8000cee4; + blr; + // clang-format on +} + +// Symbol: __two_exp +// Function signature is unknown. +// PAL: 0x8000cf2c..0x8000d298 +MARK_BINARY_BLOB(__two_exp, 0x8000cf2c, 0x8000d298); +asm UNKNOWN_FUNCTION(__two_exp) { + // clang-format off + nofralloc; +lbl_8000cf2c: + stwu r1, -0x70(r1); + mflr r0; + stw r0, 0x74(r1); + addi r0, r4, 0x40; + cmplwi r0, 0x48; + stw r31, 0x6c(r1); + mr r31, r3; + stw r30, 0x68(r1); + mr r30, r4; + bgt lbl_8000d160; + lis r5, 0x8027; + slwi r0, r0, 2; + addi r5, r5, 0xe30; + lwzx r5, r5, r0; + mtctr r5; + bctr; + lis r4, 0x8024; + li r5, -20; + addi r4, r4, 0x6040; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -16; + addi r4, r4, 0x6040; + addi r4, r4, 0x2e; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -10; + addi r4, r4, 0x6040; + addi r4, r4, 0x55; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -5; + addi r4, r4, 0x6040; + addi r4, r4, 0x6d; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -3; + addi r4, r4, 0x6040; + addi r4, r4, 0x7a; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -3; + addi r4, r4, 0x6040; + addi r4, r4, 0x81; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -2; + addi r4, r4, 0x6040; + addi r4, r4, 0x87; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -2; + addi r4, r4, 0x6040; + addi r4, r4, 0x8d; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -2; + addi r4, r4, 0x6040; + addi r4, r4, 0x92; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -1; + addi r4, r4, 0x6040; + addi r4, r4, 0x96; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -1; + addi r4, r4, 0x6040; + addi r4, r4, 0x9a; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, -1; + addi r4, r4, 0x6040; + addi r4, r4, 0x9d; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 0; + addi r4, r4, 0x6040; + addi r4, r4, 0x9f; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 0; + addi r4, r4, 0x6040; + addi r4, r4, 0xa1; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 0; + addi r4, r4, 0x6040; + addi r4, r4, 0xa3; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 0; + addi r4, r4, 0x6040; + addi r4, r4, 0xa5; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 1; + addi r4, r4, 0x6040; + addi r4, r4, 0xa7; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 1; + addi r4, r4, 0x6040; + addi r4, r4, 0xaa; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 1; + addi r4, r4, 0x6040; + addi r4, r4, 0xad; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 2; + addi r4, r4, 0x6040; + addi r4, r4, 0xb0; + bl __str2dec; + b lbl_8000d280; + lis r4, 0x8024; + li r5, 2; + addi r4, r4, 0x6040; + addi r4, r4, 0xb4; + bl __str2dec; + b lbl_8000d280; +lbl_8000d160: + srwi r0, r4, 0x1f; + addi r3, r1, 0x34; + add r0, r0, r4; + srawi r4, r0, 1; + bl lbl_8000cf2c; + addi r4, r1, 0x34; + mr r3, r31; + mr r5, r4; + bl __timesdec; + clrlwi. r0, r30, 0x1f; + beq lbl_8000d280; + lhz r3, 0(r31); + cmpwi r30, 0; + lhz r0, 2(r31); + sth r0, 0xa(r1); + sth r3, 8(r1); + lhz r3, 4(r31); + lhz r0, 6(r31); + sth r0, 0xe(r1); + sth r3, 0xc(r1); + lhz r3, 8(r31); + lhz r0, 0xa(r31); + sth r0, 0x12(r1); + sth r3, 0x10(r1); + lhz r3, 0xc(r31); + lhz r0, 0xe(r31); + sth r0, 0x16(r1); + sth r3, 0x14(r1); + lhz r3, 0x10(r31); + lhz r0, 0x12(r31); + sth r0, 0x1a(r1); + sth r3, 0x18(r1); + lhz r3, 0x14(r31); + lhz r0, 0x16(r31); + sth r0, 0x1e(r1); + sth r3, 0x1c(r1); + lhz r3, 0x18(r31); + lhz r0, 0x1a(r31); + sth r0, 0x22(r1); + sth r3, 0x20(r1); + lhz r3, 0x1c(r31); + lhz r0, 0x1e(r31); + sth r0, 0x26(r1); + sth r3, 0x24(r1); + lhz r3, 0x20(r31); + lhz r0, 0x22(r31); + sth r0, 0x2a(r1); + sth r3, 0x28(r1); + lhz r3, 0x24(r31); + lhz r0, 0x26(r31); + sth r0, 0x2e(r1); + sth r3, 0x2c(r1); + lhz r0, 0x28(r31); + sth r0, 0x30(r1); + ble lbl_8000d258; + lis r4, 0x8024; + addi r3, r1, 0x34; + addi r4, r4, 0x6040; + li r5, 0; + addi r4, r4, 0xa1; + bl __str2dec; + b lbl_8000d270; +lbl_8000d258: + lis r4, 0x8024; + addi r3, r1, 0x34; + addi r4, r4, 0x6040; + li r5, -1; + addi r4, r4, 0x9d; + bl __str2dec; +lbl_8000d270: + mr r3, r31; + addi r4, r1, 8; + addi r5, r1, 0x34; + bl __timesdec; +lbl_8000d280: + lwz r0, 0x74(r1); + lwz r31, 0x6c(r1); + lwz r30, 0x68(r1); + mtlr r0; + addi r1, r1, 0x70; + blr; + // clang-format on +} + +// Symbol: __equals_dec +// Function signature is unknown. +// PAL: 0x8000d298..0x8000d37c +MARK_BINARY_BLOB(__equals_dec, 0x8000d298, 0x8000d37c); +asm UNKNOWN_FUNCTION(__equals_dec) { + // clang-format off + nofralloc; + lbz r5, 5(r3); + cmpwi r5, 0; + bne lbl_8000d2b4; + lbz r0, 5(r4); + cntlzw r0, r0; + srwi r3, r0, 5; + blr; +lbl_8000d2b4: + lbz r0, 5(r4); + cmpwi r0, 0; + bne lbl_8000d2cc; + cntlzw r0, r5; + srwi r3, r0, 5; + blr; +lbl_8000d2cc: + lha r5, 2(r3); + lha r0, 2(r4); + cmpw r5, r0; + bne lbl_8000d374; + lbz r7, 4(r3); + lbz r0, 4(r4); + mr r9, r7; + cmpw r7, r0; + ble lbl_8000d2f4; + mr r9, r0; +lbl_8000d2f4: + li r8, 0; + mtctr r9; + cmpwi r9, 0; + ble lbl_8000d32c; +lbl_8000d304: + add r6, r3, r8; + add r5, r4, r8; + lbz r6, 5(r6); + lbz r0, 5(r5); + cmplw r6, r0; + beq lbl_8000d324; + li r3, 0; + blr; +lbl_8000d324: + addi r8, r8, 1; + bdnz lbl_8000d304; +lbl_8000d32c: + cmpw r9, r7; + bne lbl_8000d338; + mr r3, r4; +lbl_8000d338: + lbz r4, 4(r3); + subf r0, r8, r4; + mtctr r0; + cmpw r8, r4; + bge lbl_8000d36c; +lbl_8000d34c: + add r4, r3, r8; + lbz r0, 5(r4); + cmpwi r0, 0; + beq lbl_8000d364; + li r3, 0; + blr; +lbl_8000d364: + addi r8, r8, 1; + bdnz lbl_8000d34c; +lbl_8000d36c: + li r3, 1; + blr; +lbl_8000d374: + li r3, 0; + blr; + // clang-format on +} + +// Symbol: __less_dec +// Function signature is unknown. +// PAL: 0x8000d37c..0x8000d47c +MARK_BINARY_BLOB(__less_dec, 0x8000d37c, 0x8000d47c); +asm UNKNOWN_FUNCTION(__less_dec) { + // clang-format off + nofralloc; + lbz r0, 5(r3); + cmpwi r0, 0; + bne lbl_8000d39c; + lbz r3, 5(r4); + neg r0, r3; + or r0, r0, r3; + srwi r3, r0, 0x1f; + blr; +lbl_8000d39c: + lbz r0, 5(r4); + cmpwi r0, 0; + bne lbl_8000d3b0; + li r3, 0; + blr; +lbl_8000d3b0: + lha r5, 2(r4); + lha r0, 2(r3); + cmpw r0, r5; + bne lbl_8000d464; + lbz r7, 4(r3); + lbz r0, 4(r4); + mr r9, r7; + cmpw r7, r0; + ble lbl_8000d3d8; + mr r9, r0; +lbl_8000d3d8: + li r8, 0; + mtctr r9; + cmpwi r9, 0; + ble lbl_8000d420; +lbl_8000d3e8: + add r6, r4, r8; + add r5, r3, r8; + lbz r6, 5(r6); + lbz r0, 5(r5); + cmplw r0, r6; + bge lbl_8000d408; + li r3, 1; + blr; +lbl_8000d408: + cmplw r6, r0; + bge lbl_8000d418; + li r3, 0; + blr; +lbl_8000d418: + addi r8, r8, 1; + bdnz lbl_8000d3e8; +lbl_8000d420: + cmpw r9, r7; + bne lbl_8000d45c; + lbz r3, 4(r4); + subf r0, r8, r3; + mtctr r0; + cmpw r8, r3; + bge lbl_8000d45c; +lbl_8000d43c: + add r3, r4, r8; + lbz r0, 5(r3); + cmpwi r0, 0; + beq lbl_8000d454; + li r3, 1; + blr; +lbl_8000d454: + addi r8, r8, 1; + bdnz lbl_8000d43c; +lbl_8000d45c: + li r3, 0; + blr; +lbl_8000d464: + xor r0, r5, r0; + srawi r3, r0, 1; + and r0, r0, r5; + subf r0, r0, r3; + srwi r3, r0, 0x1f; + blr; + // clang-format on +} + +// Symbol: __minus_dec +// Function signature is unknown. +// PAL: 0x8000d47c..0x8000d998 +MARK_BINARY_BLOB(__minus_dec, 0x8000d47c, 0x8000d998); +asm UNKNOWN_FUNCTION(__minus_dec) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + lhz r12, 0x1a(r4); + stmw r19, 0xc(r1); + lhz r19, 0(r4); + lhz r20, 2(r4); + lhz r21, 4(r4); + lhz r22, 6(r4); + lhz r23, 8(r4); + lhz r24, 0xa(r4); + lhz r25, 0xc(r4); + lhz r26, 0xe(r4); + lhz r27, 0x10(r4); + lhz r28, 0x12(r4); + lhz r29, 0x14(r4); + lhz r30, 0x16(r4); + lhz r31, 0x18(r4); + lhz r11, 0x1c(r4); + lhz r10, 0x1e(r4); + lhz r9, 0x20(r4); + lhz r8, 0x22(r4); + lhz r7, 0x24(r4); + lhz r6, 0x26(r4); + lhz r0, 0x28(r4); + sth r19, 0(r3); + sth r20, 2(r3); + sth r21, 4(r3); + sth r22, 6(r3); + sth r23, 8(r3); + sth r24, 0xa(r3); + sth r25, 0xc(r3); + sth r26, 0xe(r3); + sth r27, 0x10(r3); + sth r28, 0x12(r3); + sth r29, 0x14(r3); + sth r30, 0x16(r3); + sth r31, 0x18(r3); + sth r12, 0x1a(r3); + sth r11, 0x1c(r3); + sth r10, 0x1e(r3); + sth r9, 0x20(r3); + sth r8, 0x22(r3); + sth r7, 0x24(r3); + sth r6, 0x26(r3); + sth r0, 0x28(r3); + lbz r0, 5(r5); + cmpwi r0, 0; + beq lbl_8000d98c; + lbz r8, 4(r3); + lbz r0, 4(r5); + cmpw r8, r0; + bge lbl_8000d54c; + mr r8, r0; +lbl_8000d54c: + lha r4, 2(r5); + lha r0, 2(r3); + subf r0, r4, r0; + add r8, r8, r0; + cmpwi r8, 0x24; + ble lbl_8000d568; + li r8, 0x24; +lbl_8000d568: + li r7, 0; + b lbl_8000d584; +lbl_8000d570: + lbz r6, 4(r3); + add r4, r3, r6; + addi r6, r6, 1; + stb r7, 5(r4); + stb r6, 4(r3); +lbl_8000d584: + lbz r4, 4(r3); + cmpw r4, r8; + blt lbl_8000d570; + lbz r7, 4(r5); + addi r4, r3, 5; + add r6, r4, r8; + add r7, r7, r0; + cmpw r7, r8; + bge lbl_8000d5ac; + add r6, r4, r7; +lbl_8000d5ac: + subf r7, r4, r6; + addi r9, r5, 5; + subf r7, r0, r7; + add r10, r9, r7; + mr r11, r10; + b lbl_8000d6e4; +lbl_8000d5c4: + lbzu r8, -1(r6); + lbzu r7, -1(r10); + cmplw r8, r7; + bge lbl_8000d6d4; + addi r12, r6, -1; + b lbl_8000d5e0; +lbl_8000d5dc: + addi r12, r12, -1; +lbl_8000d5e0: + lbz r7, 0(r12); + cmpwi r7, 0; + beq lbl_8000d5dc; + cmplw r12, r6; + subf r8, r12, r6; + beq lbl_8000d6d4; + rlwinm. r7, r8, 0x1d, 3, 0x1f; + mtctr r7; + beq lbl_8000d6b4; +lbl_8000d604: + lbz r7, 0(r12); + addi r7, r7, -1; + stb r7, 0(r12); + lbz r7, 1(r12); + addi r7, r7, 0xa; + clrlwi r7, r7, 0x18; + addi r7, r7, -1; + stb r7, 1(r12); + lbz r7, 2(r12); + addi r7, r7, 0xa; + clrlwi r7, r7, 0x18; + addi r7, r7, -1; + stb r7, 2(r12); + lbz r7, 3(r12); + addi r7, r7, 0xa; + clrlwi r7, r7, 0x18; + addi r7, r7, -1; + stb r7, 3(r12); + lbz r7, 4(r12); + addi r7, r7, 0xa; + clrlwi r7, r7, 0x18; + addi r7, r7, -1; + stb r7, 4(r12); + lbz r7, 5(r12); + addi r7, r7, 0xa; + clrlwi r7, r7, 0x18; + addi r7, r7, -1; + stb r7, 5(r12); + lbz r7, 6(r12); + addi r7, r7, 0xa; + clrlwi r7, r7, 0x18; + addi r7, r7, -1; + stb r7, 6(r12); + lbz r7, 7(r12); + addi r7, r7, 0xa; + clrlwi r7, r7, 0x18; + addi r7, r7, -1; + stb r7, 7(r12); + lbz r7, 8(r12); + addi r7, r7, 0xa; + stbu r7, 8(r12); + bdnz lbl_8000d604; + andi. r8, r8, 7; + beq lbl_8000d6d4; +lbl_8000d6b4: + mtctr r8; +lbl_8000d6b8: + lbz r7, 0(r12); + addi r7, r7, -1; + stb r7, 0(r12); + lbz r7, 1(r12); + addi r7, r7, 0xa; + stbu r7, 1(r12); + bdnz lbl_8000d6b8; +lbl_8000d6d4: + lbz r8, 0(r10); + lbz r7, 0(r6); + subf r7, r8, r7; + stb r7, 0(r6); +lbl_8000d6e4: + cmplw r6, r4; + ble lbl_8000d6f4; + cmplw r10, r9; + bgt lbl_8000d5c4; +lbl_8000d6f4: + lbz r8, 4(r5); + subf r9, r9, r11; + cmpw r9, r8; + bge lbl_8000d888; + lbz r7, 0(r11); + li r10, 0; + cmplwi r7, 5; + bge lbl_8000d71c; + li r10, 1; + b lbl_8000d768; +lbl_8000d71c: + bne lbl_8000d768; + add r5, r5, r8; + addi r6, r11, 1; + addi r7, r5, 5; + subf r5, r6, r7; + mtctr r5; + cmplw r6, r7; + bge lbl_8000d750; +lbl_8000d73c: + lbz r5, 0(r6); + cmpwi r5, 0; + bne lbl_8000d888; + addi r6, r6, 1; + bdnz lbl_8000d73c; +lbl_8000d750: + add r5, r4, r9; + add r6, r0, r5; + lbzu r0, -1(r6); + clrlwi. r0, r0, 0x1f; + beq lbl_8000d768; + li r10, 1; +lbl_8000d768: + cmpwi r10, 0; + beq lbl_8000d888; + lbz r0, 0(r6); + cmplwi r0, 1; + bge lbl_8000d87c; + addi r8, r6, -1; + b lbl_8000d788; +lbl_8000d784: + addi r8, r8, -1; +lbl_8000d788: + lbz r0, 0(r8); + cmpwi r0, 0; + beq lbl_8000d784; + cmplw r8, r6; + subf r5, r8, r6; + beq lbl_8000d87c; + rlwinm. r0, r5, 0x1d, 3, 0x1f; + mtctr r0; + beq lbl_8000d85c; +lbl_8000d7ac: + lbz r7, 0(r8); + addi r0, r7, -1; + stb r0, 0(r8); + lbz r7, 1(r8); + addi r0, r7, 0xa; + clrlwi r7, r0, 0x18; + addi r0, r7, -1; + stb r0, 1(r8); + lbz r7, 2(r8); + addi r0, r7, 0xa; + clrlwi r7, r0, 0x18; + addi r0, r7, -1; + stb r0, 2(r8); + lbz r7, 3(r8); + addi r0, r7, 0xa; + clrlwi r7, r0, 0x18; + addi r0, r7, -1; + stb r0, 3(r8); + lbz r7, 4(r8); + addi r0, r7, 0xa; + clrlwi r7, r0, 0x18; + addi r0, r7, -1; + stb r0, 4(r8); + lbz r7, 5(r8); + addi r0, r7, 0xa; + clrlwi r7, r0, 0x18; + addi r0, r7, -1; + stb r0, 5(r8); + lbz r7, 6(r8); + addi r0, r7, 0xa; + clrlwi r7, r0, 0x18; + addi r0, r7, -1; + stb r0, 6(r8); + lbz r7, 7(r8); + addi r0, r7, 0xa; + clrlwi r7, r0, 0x18; + addi r0, r7, -1; + stb r0, 7(r8); + lbz r7, 8(r8); + addi r0, r7, 0xa; + stbu r0, 8(r8); + bdnz lbl_8000d7ac; + andi. r5, r5, 7; + beq lbl_8000d87c; +lbl_8000d85c: + mtctr r5; +lbl_8000d860: + lbz r7, 0(r8); + addi r0, r7, -1; + stb r0, 0(r8); + lbz r7, 1(r8); + addi r0, r7, 0xa; + stbu r0, 1(r8); + bdnz lbl_8000d860; +lbl_8000d87c: + lbz r5, 0(r6); + addi r0, r5, -1; + stb r0, 0(r6); +lbl_8000d888: + mr r7, r4; + b lbl_8000d894; +lbl_8000d890: + addi r7, r7, 1; +lbl_8000d894: + lbz r0, 0(r7); + cmpwi r0, 0; + beq lbl_8000d890; + cmplw r7, r4; + ble lbl_8000d954; + lbz r0, 4(r3); + subf r6, r4, r7; + clrlwi r8, r6, 0x18; + lha r5, 2(r3); + add r6, r4, r0; + cmplw r7, r6; + subf r0, r8, r5; + sth r0, 2(r3); + subf r5, r7, r6; + bge lbl_8000d948; + rlwinm. r0, r5, 0x1d, 3, 0x1f; + mtctr r0; + beq lbl_8000d930; +lbl_8000d8dc: + lbz r0, 0(r7); + stb r0, 0(r4); + lbz r0, 1(r7); + stb r0, 1(r4); + lbz r0, 2(r7); + stb r0, 2(r4); + lbz r0, 3(r7); + stb r0, 3(r4); + lbz r0, 4(r7); + stb r0, 4(r4); + lbz r0, 5(r7); + stb r0, 5(r4); + lbz r0, 6(r7); + stb r0, 6(r4); + lbz r0, 7(r7); + addi r7, r7, 8; + stb r0, 7(r4); + addi r4, r4, 8; + bdnz lbl_8000d8dc; + andi. r5, r5, 7; + beq lbl_8000d948; +lbl_8000d930: + mtctr r5; +lbl_8000d934: + lbz r0, 0(r7); + addi r7, r7, 1; + stb r0, 0(r4); + addi r4, r4, 1; + bdnz lbl_8000d934; +lbl_8000d948: + lbz r0, 4(r3); + subf r0, r8, r0; + stb r0, 4(r3); +lbl_8000d954: + lbz r0, 4(r3); + addi r4, r3, 5; + add r5, r4, r0; + subf r0, r4, r5; + mtctr r0; + cmplw r5, r4; + ble lbl_8000d980; +lbl_8000d970: + lbzu r0, -1(r5); + cmpwi r0, 0; + bne lbl_8000d980; + bdnz lbl_8000d970; +lbl_8000d980: + subf r4, r4, r5; + addi r0, r4, 1; + stb r0, 4(r3); +lbl_8000d98c: + lmw r19, 0xc(r1); + addi r1, r1, 0x40; + blr; + // clang-format on +} + +// Symbol: __num2dec_internal +// Function signature is unknown. +// PAL: 0x8000d998..0x8000dafc +MARK_BINARY_BLOB(__num2dec_internal, 0x8000d998, 0x8000dafc); +asm UNKNOWN_FUNCTION(__num2dec_internal) { + // clang-format off + nofralloc; + stwu r1, -0x90(r1); + mflr r0; + stw r0, 0x94(r1); + stfd f31, 0x88(r1); + fmr f31, f1; + stw r31, 0x84(r1); + stw r30, 0x80(r1); + mr r30, r3; + stw r29, 0x7c(r1); + bl __signbitd; + lfd f0, -0x7f70(r2); + neg r0, r3; + or r0, r0, r3; + fcmpu cr0, f0, f31; + srwi r0, r0, 0x1f; + extsb r31, r0; + bne lbl_8000d9f8; + li r3, 0; + li r0, 1; + stb r31, 0(r30); + sth r3, 2(r30); + stb r0, 4(r30); + stb r3, 5(r30); + b lbl_8000dadc; +lbl_8000d9f8: + fmr f1, f31; + bl __fpclassifyd; + cmpwi r3, 2; + bgt lbl_8000da3c; + fmr f1, f31; + li r3, 0; + li r0, 1; + stb r31, 0(r30); + sth r3, 2(r30); + stb r0, 4(r30); + bl __fpclassifyd; + cmpwi r3, 1; + li r0, 0x49; + bne lbl_8000da34; + li r0, 0x4e; +lbl_8000da34: + stb r0, 5(r30); + b lbl_8000dadc; +lbl_8000da3c: + cmpwi r31, 0; + beq lbl_8000da48; + fneg f31, f31; +lbl_8000da48: + fmr f1, f31; + addi r3, r1, 8; + bl frexp; + stfd f1, 0x10(r1); + fmr f31, f1; + lwz r4, 0x14(r1); + lwz r3, 0x10(r1); + addi r0, r4, -1; + cmpwi r4, 0; + andc r0, r0, r4; + oris r3, r3, 0x10; + cntlzw r0, r0; + subfic r4, r0, 0x20; + bne lbl_8000da94; + addi r0, r3, -1; + andc r0, r0, r3; + cntlzw r0, r0; + subfic r3, r0, 0x20; + addi r4, r3, 0x20; +lbl_8000da94: + lwz r0, 8(r1); + subfic r29, r4, 0x35; + addi r3, r1, 0x18; + subf r4, r29, r0; + bl __two_exp; + fmr f1, f31; + mr r3, r29; + bl ldexp; + bl __cvt_dbl_ull; + mr r5, r3; + mr r6, r4; + addi r3, r1, 0x44; + bl __ull2dec; + mr r3, r30; + addi r4, r1, 0x44; + addi r5, r1, 0x18; + bl __timesdec; + stb r31, 0(r30); +lbl_8000dadc: + lwz r0, 0x94(r1); + lfd f31, 0x88(r1); + lwz r31, 0x84(r1); + lwz r30, 0x80(r1); + lwz r29, 0x7c(r1); + mtlr r0; + addi r1, r1, 0x90; + blr; + // clang-format on +} + +// Symbol: __num2dec +// Function signature is unknown. +// PAL: 0x8000dafc..0x8000dc9c +MARK_BINARY_BLOB(__num2dec, 0x8000dafc, 0x8000dc9c); +asm UNKNOWN_FUNCTION(__num2dec) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + lha r31, 2(r3); + stw r30, 8(r1); + mr r30, r4; + mr r3, r30; + bl __num2dec_internal; + lbz r0, 5(r30); + cmplwi r0, 9; + bgt lbl_8000dc84; + cmpwi r31, 0x24; + ble lbl_8000db38; + li r31, 0x24; +lbl_8000db38: + cmpwi r31, 0; + ble lbl_8000dc24; + lbz r0, 4(r30); + cmpw r31, r0; + bge lbl_8000dc24; + addi r4, r30, 5; + lbzx r0, r4, r31; + add r3, r4, r31; + cmplwi r0, 5; + ble lbl_8000db68; + li r4, 1; + b lbl_8000dbc4; +lbl_8000db68: + bge lbl_8000db74; + li r4, -1; + b lbl_8000dbc4; +lbl_8000db74: + lbz r0, 4(r30); + addi r3, r3, 1; + add r4, r4, r0; + subf r0, r3, r4; + mtctr r0; + cmplw r3, r4; + bge lbl_8000dbac; +lbl_8000db90: + lbz r0, 0(r3); + cmpwi r0, 0; + beq lbl_8000dba4; + li r4, 1; + b lbl_8000dbc4; +lbl_8000dba4: + addi r3, r3, 1; + bdnz lbl_8000db90; +lbl_8000dbac: + add r3, r31, r30; + li r4, -1; + lbz r0, 4(r3); + clrlwi. r0, r0, 0x1f; + beq lbl_8000dbc4; + li r4, 1; +lbl_8000dbc4: + cmpwi r4, 0; + stb r31, 4(r30); + blt lbl_8000dc24; + addi r4, r30, 5; + li r0, 0; + add r5, r4, r31; + addi r5, r5, -1; +lbl_8000dbe0: + lbz r3, 0(r5); + cmplwi r3, 9; + bge lbl_8000dbf8; + addi r0, r3, 1; + stb r0, 0(r5); + b lbl_8000dc24; +lbl_8000dbf8: + cmplw r5, r4; + bne lbl_8000dc18; + li r0, 1; + stb r0, 0(r5); + lha r3, 2(r30); + addi r0, r3, 1; + sth r0, 2(r30); + b lbl_8000dc24; +lbl_8000dc18: + stb r0, 0(r5); + addi r5, r5, -1; + b lbl_8000dbe0; +lbl_8000dc24: + li r5, 0; + b lbl_8000dc40; +lbl_8000dc2c: + lbz r4, 4(r30); + add r3, r30, r4; + addi r0, r4, 1; + stb r5, 5(r3); + stb r0, 4(r30); +lbl_8000dc40: + lbz r3, 4(r30); + cmpw r3, r31; + blt lbl_8000dc2c; + lha r0, 2(r30); + addi r3, r3, -1; + li r5, 0; + subf r0, r3, r0; + sth r0, 2(r30); + b lbl_8000dc78; +lbl_8000dc64: + add r4, r30, r5; + addi r5, r5, 1; + lbz r3, 5(r4); + addi r0, r3, 0x30; + stb r0, 5(r4); +lbl_8000dc78: + lbz r0, 4(r30); + cmpw r5, r0; + blt lbl_8000dc64; +lbl_8000dc84: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __dec2num +// Function signature is unknown. +// PAL: 0x8000dc9c..0x8000e418 +MARK_BINARY_BLOB(__dec2num, 0x8000dc9c, 0x8000e418); +asm UNKNOWN_FUNCTION(__dec2num) { + // clang-format off + nofralloc; + stwu r1, -0x180(r1); + mflr r0; + stw r0, 0x184(r1); + stfd f31, 0x170(r1); + psq_st f31, 376(r1), 0, 0; + addi r11, r1, 0x170; + bl _savegpr_19; + lbz r0, 4(r3); + lis r4, 0x4330; + stw r4, 0x128(r1); + mr r27, r3; + cmpwi r0, 0; + stw r4, 0x130(r1); + bne lbl_8000dcf8; + lbz r0, 0(r3); + extsb. r0, r0; + bne lbl_8000dce8; + lfd f2, -0x7f68(r2); + b lbl_8000dcec; +lbl_8000dce8: + lfd f2, -0x7f60(r2); +lbl_8000dcec: + lfd f1, -0x7f70(r2); + bl copysign; + b lbl_8000e414; +lbl_8000dcf8: + lbz r0, 5(r3); + cmpwi r0, 0x30; + beq lbl_8000dd18; + cmpwi r0, 0x49; + beq lbl_8000dd3c; + cmpwi r0, 0x4e; + beq lbl_8000dd64; + b lbl_8000dda4; +lbl_8000dd18: + lbz r0, 0(r3); + extsb. r0, r0; + bne lbl_8000dd2c; + lfd f2, -0x7f68(r2); + b lbl_8000dd30; +lbl_8000dd2c: + lfd f2, -0x7f60(r2); +lbl_8000dd30: + lfd f1, -0x7f70(r2); + bl copysign; + b lbl_8000e414; +lbl_8000dd3c: + lbz r0, 0(r3); + extsb. r0, r0; + bne lbl_8000dd50; + lfd f2, -0x7f68(r2); + b lbl_8000dd54; +lbl_8000dd50: + lfd f2, -0x7f60(r2); +lbl_8000dd54: + lis r3, 0x8038; + lfs f1, 0x4c24(r3); + bl copysign; + b lbl_8000e414; +lbl_8000dd64: + lbz r0, 0(r3); + li r4, 0; + lis r3, 0x7ff0; + stw r4, 0x1c(r1); + extsb. r0, r0; + stw r3, 0x18(r1); + beq lbl_8000dd90; + lis r0, 0x8000; + stw r4, 0x1c(r1); + oris r0, r0, 0x7ff0; + stw r0, 0x18(r1); +lbl_8000dd90: + lwz r0, 0x18(r1); + oris r0, r0, 8; + stw r0, 0x18(r1); + lfd f1, 0x18(r1); + b lbl_8000e414; +lbl_8000dda4: + lhz r0, 4(r3); + addi r4, r1, 0x101; + sth r0, 0x100(r1); + lhz r19, 0(r3); + lbz r0, 0x100(r1); + lhz r20, 2(r3); + add r28, r4, r0; + lhz r31, 6(r3); + lhz r30, 8(r3); + cmplw cr1, r4, r28; + lhz r29, 0xa(r3); + lhz r26, 0xc(r3); + lhz r25, 0xe(r3); + lhz r24, 0x10(r3); + lhz r23, 0x12(r3); + lhz r22, 0x14(r3); + lhz r21, 0x16(r3); + lhz r12, 0x18(r3); + lhz r11, 0x1a(r3); + lhz r10, 0x1c(r3); + lhz r9, 0x1e(r3); + lhz r8, 0x20(r3); + lhz r7, 0x22(r3); + lhz r6, 0x24(r3); + lhz r5, 0x26(r3); + lhz r0, 0x28(r3); + sth r19, 0xfc(r1); + sth r20, 0xfe(r1); + sth r31, 0x102(r1); + sth r30, 0x104(r1); + sth r29, 0x106(r1); + sth r26, 0x108(r1); + sth r25, 0x10a(r1); + sth r24, 0x10c(r1); + sth r23, 0x10e(r1); + sth r22, 0x110(r1); + sth r21, 0x112(r1); + sth r12, 0x114(r1); + sth r11, 0x116(r1); + sth r10, 0x118(r1); + sth r9, 0x11a(r1); + sth r8, 0x11c(r1); + sth r7, 0x11e(r1); + sth r6, 0x120(r1); + sth r5, 0x122(r1); + sth r0, 0x124(r1); + bge cr1, lbl_8000df18; + subf r0, r4, r28; + addi r3, r28, -8; + cmpwi r0, 8; + ble lbl_8000def4; + bgt cr1, lbl_8000def4; + addi r0, r3, 7; + subf r0, r4, r0; + srwi r0, r0, 3; + mtctr r0; + cmplw r4, r3; + bge lbl_8000def4; +lbl_8000de8c: + lbz r3, 0(r4); + addi r0, r3, -48; + stb r0, 0(r4); + lbz r3, 1(r4); + addi r0, r3, -48; + stb r0, 1(r4); + lbz r3, 2(r4); + addi r0, r3, -48; + stb r0, 2(r4); + lbz r3, 3(r4); + addi r0, r3, -48; + stb r0, 3(r4); + lbz r3, 4(r4); + addi r0, r3, -48; + stb r0, 4(r4); + lbz r3, 5(r4); + addi r0, r3, -48; + stb r0, 5(r4); + lbz r3, 6(r4); + addi r0, r3, -48; + stb r0, 6(r4); + lbz r3, 7(r4); + addi r0, r3, -48; + stb r0, 7(r4); + addi r4, r4, 8; + bdnz lbl_8000de8c; +lbl_8000def4: + subf r0, r4, r28; + mtctr r0; + cmplw r4, r28; + bge lbl_8000df18; +lbl_8000df04: + lbz r3, 0(r4); + addi r0, r3, -48; + stb r0, 0(r4); + addi r4, r4, 1; + bdnz lbl_8000df04; +lbl_8000df18: + lha r6, 0xfe(r1); + lis r4, 0x8024; + lbz r0, 0x100(r1); + addi r4, r4, 0x6040; + addi r3, r1, 0xd0; + li r5, 0x134; + add r6, r0, r6; + addi r4, r4, 0xb8; + addi r0, r6, -1; + sth r0, 0xfe(r1); + extsh r29, r0; + bl __str2dec; + addi r3, r1, 0xd0; + addi r4, r1, 0xfc; + bl __less_dec; + cmpwi r3, 0; + beq lbl_8000df84; + lbz r0, 0(r27); + extsb. r0, r0; + bne lbl_8000df70; + lfd f2, -0x7f68(r2); + b lbl_8000df74; +lbl_8000df70: + lfd f2, -0x7f60(r2); +lbl_8000df74: + lis r3, 0x8038; + lfs f1, 0x4c24(r3); + bl copysign; + b lbl_8000e414; +lbl_8000df84: + lbz r0, 0x101(r1); + lis r7, 0x8027; + stw r0, 0x12c(r1); + addi r4, r1, 0x102; + lfd f2, -0x7f48(r2); + addi r7, r7, 0xf58; + lfd f0, 0x128(r1); + lis r3, 0x8000; + fsub f31, f0, f2; + b lbl_8000e0e8; +lbl_8000dfac: + subf r5, r4, r28; + li r10, 0; + slwi r0, r5, 0x1d; + srwi r5, r5, 0x1f; + subf r0, r5, r0; + rotlwi r0, r0, 3; + add. r6, r0, r5; + bne lbl_8000dfd0; + li r6, 8; +lbl_8000dfd0: + cmpwi cr1, r6, 0; + li r5, 0; + ble cr1, lbl_8000e0b0; + cmpwi r6, 8; + addi r8, r6, -8; + ble lbl_8000e088; + li r9, 0; + blt cr1, lbl_8000e000; + addi r0, r3, -2; + cmpw r6, r0; + bgt lbl_8000e000; + li r9, 1; +lbl_8000e000: + cmpwi r9, 0; + beq lbl_8000e088; + addi r0, r8, 7; + srwi r0, r0, 3; + mtctr r0; + cmpwi r8, 0; + ble lbl_8000e088; +lbl_8000e01c: + mulli r0, r10, 0xa; + lbz r9, 0(r4); + lbz r8, 1(r4); + addi r5, r5, 8; + lbz r21, 2(r4); + add r0, r9, r0; + mulli r0, r0, 0xa; + lbz r12, 3(r4); + lbz r11, 4(r4); + lbz r10, 5(r4); + add r0, r8, r0; + lbz r9, 6(r4); + mulli r0, r0, 0xa; + lbz r8, 7(r4); + addi r4, r4, 8; + add r0, r21, r0; + mulli r0, r0, 0xa; + add r0, r12, r0; + mulli r0, r0, 0xa; + add r0, r11, r0; + mulli r0, r0, 0xa; + add r0, r10, r0; + mulli r0, r0, 0xa; + add r0, r9, r0; + mulli r0, r0, 0xa; + add r10, r8, r0; + bdnz lbl_8000e01c; +lbl_8000e088: + subf r0, r5, r6; + mtctr r0; + cmpw r5, r6; + bge lbl_8000e0b0; +lbl_8000e098: + mulli r0, r10, 0xa; + lbz r8, 0(r4); + addi r5, r5, 1; + addi r4, r4, 1; + add r10, r8, r0; + bdnz lbl_8000e098; +lbl_8000e0b0: + slwi r0, r6, 3; + stw r10, 0x134(r1); + add r5, r7, r0; + cmpwi r10, 0; + lfd f1, -8(r5); + lfd f0, 0x130(r1); + fmul f1, f31, f1; + fsub f0, f0, f2; + fadd f0, f1, f0; + beq lbl_8000e0e0; + fcmpu cr0, f1, f0; + beq lbl_8000e0f0; +lbl_8000e0e0: + fmr f31, f0; + subf r29, r6, r29; +lbl_8000e0e8: + cmplw r4, r28; + blt lbl_8000dfac; +lbl_8000e0f0: + cmpwi r29, 0; + bge lbl_8000e120; + neg r0, r29; + lfd f2, -0x7f40(r2); + xoris r0, r0, 0x8000; + stw r0, 0x12c(r1); + lfd f1, -0x7f58(r2); + lfd f0, 0x128(r1); + fsub f2, f0, f2; + bl pow; + fdiv f31, f31, f1; + b lbl_8000e140; +lbl_8000e120: + xoris r0, r29, 0x8000; + stw r0, 0x134(r1); + lfd f2, -0x7f40(r2); + lfd f0, 0x130(r1); + lfd f1, -0x7f58(r2); + fsub f2, f0, f2; + bl pow; + fmul f31, f31, f1; +lbl_8000e140: + fmr f1, f31; + mr r3, r29; + bl ldexp; + fmr f31, f1; + stfd f1, 0x10(r1); + bl __fpclassifyd; + cmpwi r3, 2; + bne lbl_8000e168; + lfd f31, -0x7f50(r2); + stfd f31, 0x10(r1); +lbl_8000e168: + fmr f1, f31; + addi r3, r1, 0xa4; + li r27, 0; + bl __num2dec_internal; + addi r3, r1, 0xa4; + addi r4, r1, 0xfc; + bl __equals_dec; + cmpwi r3, 0; + bne lbl_8000e400; + addi r3, r1, 0xa4; + addi r4, r1, 0xfc; + bl __less_dec; + cmpwi r3, 0; + beq lbl_8000e1a4; + li r27, 1; +lbl_8000e1a4: + cntlzw r0, r27; + stfd f31, 8(r1); + srwi r28, r0, 5; + li r29, 1; + li r30, 0; + li r31, -1; +lbl_8000e1bc: + cmpwi r28, 0; + bne lbl_8000e1f0; + lwz r0, 0xc(r1); + lwz r3, 8(r1); + addc r0, r0, r29; + stw r0, 0xc(r1); + adde r0, r3, r30; + stw r0, 8(r1); + lfd f1, 8(r1); + bl __fpclassifyd; + cmpwi r3, 2; + beq lbl_8000e400; + b lbl_8000e208; +lbl_8000e1f0: + lwz r0, 0xc(r1); + lwz r3, 8(r1); + addc r0, r0, r31; + stw r0, 0xc(r1); + adde r0, r3, r31; + stw r0, 8(r1); +lbl_8000e208: + lfd f1, 8(r1); + addi r3, r1, 0x78; + bl __num2dec_internal; + cmpwi r27, 0; + beq lbl_8000e230; + addi r3, r1, 0x78; + addi r4, r1, 0xfc; + bl __less_dec; + cmpwi r3, 0; + beq lbl_8000e3a0; +lbl_8000e230: + cmpwi r27, 0; + bne lbl_8000e33c; + addi r3, r1, 0xfc; + addi r4, r1, 0x78; + bl __less_dec; + cmpwi r3, 0; + bne lbl_8000e33c; + fmr f0, f31; + lfd f31, 8(r1); + lwz r21, 0xa4(r1); + lwz r12, 0x78(r1); + lwz r22, 0xa8(r1); + lwz r11, 0x7c(r1); + lwz r23, 0xac(r1); + lwz r10, 0x80(r1); + lwz r24, 0xb0(r1); + lwz r9, 0x84(r1); + lwz r25, 0xb4(r1); + lwz r8, 0x88(r1); + lwz r26, 0xb8(r1); + lwz r7, 0x8c(r1); + lwz r31, 0xbc(r1); + lwz r6, 0x90(r1); + lwz r30, 0xc0(r1); + lwz r5, 0x94(r1); + lwz r29, 0xc4(r1); + lwz r4, 0x98(r1); + lwz r28, 0xc8(r1); + lwz r3, 0x9c(r1); + lhz r27, 0xcc(r1); + lhz r0, 0xa0(r1); + stw r21, 0x4c(r1); + stw r22, 0x50(r1); + stw r23, 0x54(r1); + stw r24, 0x58(r1); + stw r25, 0x5c(r1); + stw r26, 0x60(r1); + stw r31, 0x64(r1); + stw r30, 0x68(r1); + stw r29, 0x6c(r1); + stw r28, 0x70(r1); + sth r27, 0x74(r1); + stw r12, 0xa4(r1); + stw r11, 0xa8(r1); + stw r10, 0xac(r1); + stw r9, 0xb0(r1); + stw r8, 0xb4(r1); + stw r7, 0xb8(r1); + stw r6, 0xbc(r1); + stw r5, 0xc0(r1); + stw r4, 0xc4(r1); + stw r3, 0xc8(r1); + sth r0, 0xcc(r1); + stw r21, 0x78(r1); + stw r22, 0x7c(r1); + stw r23, 0x80(r1); + stw r24, 0x84(r1); + stw r25, 0x88(r1); + stw r26, 0x8c(r1); + stw r31, 0x90(r1); + stw r30, 0x94(r1); + stw r29, 0x98(r1); + stw r28, 0x9c(r1); + sth r27, 0xa0(r1); + stfd f31, 0x10(r1); + stfd f0, 8(r1); + b lbl_8000e3a0; +lbl_8000e33c: + lwz r12, 0x78(r1); + lwz r11, 0x7c(r1); + lwz r10, 0x80(r1); + lwz r9, 0x84(r1); + lwz r8, 0x88(r1); + lwz r7, 0x8c(r1); + lwz r6, 0x90(r1); + lwz r5, 0x94(r1); + lwz r4, 0x98(r1); + lwz r3, 0x9c(r1); + lhz r0, 0xa0(r1); + lfd f31, 8(r1); + stw r12, 0xa4(r1); + stw r11, 0xa8(r1); + stw r10, 0xac(r1); + stw r9, 0xb0(r1); + stw r8, 0xb4(r1); + stw r7, 0xb8(r1); + stw r6, 0xbc(r1); + stw r5, 0xc0(r1); + stw r4, 0xc4(r1); + stw r3, 0xc8(r1); + sth r0, 0xcc(r1); + stfd f31, 0x10(r1); + b lbl_8000e1bc; +lbl_8000e3a0: + addi r3, r1, 0x4c; + addi r4, r1, 0xfc; + addi r5, r1, 0xa4; + bl __minus_dec; + addi r3, r1, 0x20; + addi r4, r1, 0x78; + addi r5, r1, 0xfc; + bl __minus_dec; + addi r3, r1, 0x4c; + addi r4, r1, 0x20; + bl __equals_dec; + cmpwi r3, 0; + beq lbl_8000e3e8; + lwz r0, 0x14(r1); + clrlwi. r0, r0, 0x1f; + beq lbl_8000e400; + lfd f31, 8(r1); + b lbl_8000e400; +lbl_8000e3e8: + addi r3, r1, 0x4c; + addi r4, r1, 0x20; + bl __less_dec; + cmpwi r3, 0; + bne lbl_8000e400; + lfd f31, 8(r1); +lbl_8000e400: + lbz r0, 0xfc(r1); + extsb. r0, r0; + beq lbl_8000e410; + fneg f31, f31; +lbl_8000e410: + fmr f1, f31; +lbl_8000e414: + psq_l f31, 376(r1), 0, 0; + // clang-format on +} diff --git a/source/platform/ansi_fp.h b/source/platform/ansi_fp.h new file mode 100644 index 000000000..363c652a8 --- /dev/null +++ b/source/platform/ansi_fp.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x8000cadc..0x8000cbb8 +UNKNOWN_FUNCTION(__ull2dec); +// PAL: 0x8000cbb8..0x8000ce40 +UNKNOWN_FUNCTION(__timesdec); +// PAL: 0x8000ce40..0x8000cf2c +UNKNOWN_FUNCTION(__str2dec); +// PAL: 0x8000cf2c..0x8000d298 +UNKNOWN_FUNCTION(__two_exp); +// PAL: 0x8000d298..0x8000d37c +UNKNOWN_FUNCTION(__equals_dec); +// PAL: 0x8000d37c..0x8000d47c +UNKNOWN_FUNCTION(__less_dec); +// PAL: 0x8000d47c..0x8000d998 +UNKNOWN_FUNCTION(__minus_dec); +// PAL: 0x8000d998..0x8000dafc +UNKNOWN_FUNCTION(__num2dec_internal); +// PAL: 0x8000dafc..0x8000dc9c +UNKNOWN_FUNCTION(__num2dec); +// PAL: 0x8000dc9c..0x8000e418 +UNKNOWN_FUNCTION(__dec2num); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/platform/eabi.h b/source/platform/eabi.h new file mode 100644 index 000000000..ab16207f0 --- /dev/null +++ b/source/platform/eabi.h @@ -0,0 +1,71 @@ +#pragma once + +// PAL: 0x8002156C +extern void _savegpr_14(void); +// PAL: 0x80021570 +extern void _savegpr_15(void); +// PAL: 0x80021574 +extern void _savegpr_16(void); +// PAL: 0x80021578 +extern void _savegpr_17(void); +// PAL: 0x8002157C +extern void _savegpr_18(void); +// PAL: 0x80021580 +extern void _savegpr_19(void); +// PAL: 0x80021584 +extern void _savegpr_20(void); +// PAL: 0x80021588 +extern void _savegpr_21(void); +// PAL: 0x8002158C +extern void _savegpr_22(void); +// PAL: 0x80021590 +extern void _savegpr_23(void); +// PAL: 0x80021594 +extern void _savegpr_24(void); +// PAL: 0x80021598 +extern void _savegpr_25(void); +// PAL: 0x8002159c +extern void _savegpr_26(void); +// PAL: 0x800215a0 +extern void _savegpr_27(void); + +// PAL: 0x800215b8 +extern void _restgpr_14(void); +// PAL: 0x800215bc +extern void _restgpr_15(void); +// PAL: 0x800215c0 +extern void _restgpr_16(void); +// PAL: 0x800215c4 +extern void _restgpr_17(void); +// PAL: 0x800215c8 +extern void _restgpr_18(void); +// PAL: 0x800215cc +extern void _restgpr_19(void); +// PAL: 0x800215d0 +extern void _restgpr_20(void); +// PAL: 0x800215d4 +extern void _restgpr_21(void); +// PAL: 0x800215d8 +extern void _restgpr_22(void); +// PAL: 0x800215dc +extern void _restgpr_23(void); +// PAL: 0x800215e0 +extern void _restgpr_24(void); +// PAL: 0x800215e4 +extern void _restgpr_25(void); +// PAL: 0x800215e8 +extern void _restgpr_26(void); +// PAL: 0x800215ec +extern void _restgpr_27(void); + +extern void __div2u(void); +// PAL: 0x800216f0 +extern void __div2i(void); +extern void __mod2u(void); +extern void __mod2i(void); +extern void __shl2i(void); +extern void __shr2u(void); +extern void __shr2i(void); + +// PAL: 0x80021b00 +void __cvt_dbl_ull(void); diff --git a/source/platform/float.c b/source/platform/float.c new file mode 100644 index 000000000..9183a2568 --- /dev/null +++ b/source/platform/float.c @@ -0,0 +1,105 @@ +#include "float.h" + +#include + +// Symbol: __fpclassifyf +// Function signature is unknown. +// PAL: 0x8000ef04..0x8000ef64 +MARK_BINARY_BLOB(__fpclassifyf, 0x8000ef04, 0x8000ef64); +asm UNKNOWN_FUNCTION(__fpclassifyf) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + stfs f1, 8(r1); + lwz r3, 8(r1); + rlwinm r4, r3, 0, 1, 8; + addis r0, r4, 0x8080; + cmplwi r0, 0; + beq lbl_8000ef2c; + cmpwi r4, 0; + beq lbl_8000ef44; + b lbl_8000ef58; +lbl_8000ef2c: + clrlwi r3, r3, 9; + neg r0, r3; + or r0, r0, r3; + srawi r3, r0, 0x1f; + addi r3, r3, 2; + b lbl_8000ef5c; +lbl_8000ef44: + clrlwi. r0, r3, 9; + li r3, 3; + beq lbl_8000ef5c; + li r3, 5; + b lbl_8000ef5c; +lbl_8000ef58: + li r3, 4; +lbl_8000ef5c: + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __signbitd +// Function signature is unknown. +// PAL: 0x8000ef64..0x8000ef7c +MARK_BINARY_BLOB(__signbitd, 0x8000ef64, 0x8000ef7c); +asm UNKNOWN_FUNCTION(__signbitd) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + stfd f1, 8(r1); + lwz r0, 8(r1); + rlwinm r3, r0, 0, 0, 0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __fpclassifyd +// PAL: 0x8000ef7c..0x8000eff8 +MARK_BINARY_BLOB(__fpclassifyd, 0x8000ef7c, 0x8000eff8); +asm UNKNOWN_FUNCTION(__fpclassifyd) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + stfd f1, 8(r1); + lwz r3, 8(r1); + rlwinm r4, r3, 0, 1, 0xb; + addis r0, r4, 0x8010; + cmplwi r0, 0; + beq lbl_8000efa4; + cmpwi r4, 0; + beq lbl_8000efc8; + b lbl_8000efec; +lbl_8000efa4: + clrlwi. r0, r3, 0xc; + bne lbl_8000efb8; + lwz r0, 0xc(r1); + cmpwi r0, 0; + beq lbl_8000efc0; +lbl_8000efb8: + li r3, 1; + b lbl_8000eff0; +lbl_8000efc0: + li r3, 2; + b lbl_8000eff0; +lbl_8000efc8: + clrlwi. r0, r3, 0xc; + bne lbl_8000efdc; + lwz r0, 0xc(r1); + cmpwi r0, 0; + beq lbl_8000efe4; +lbl_8000efdc: + li r3, 5; + b lbl_8000eff0; +lbl_8000efe4: + li r3, 3; + b lbl_8000eff0; +lbl_8000efec: + li r3, 4; +lbl_8000eff0: + addi r1, r1, 0x10; + blr; + // clang-format on +} diff --git a/source/platform/global_destructor_chain.c b/source/platform/global_destructor_chain.c new file mode 100644 index 000000000..477bfba7e --- /dev/null +++ b/source/platform/global_destructor_chain.c @@ -0,0 +1,35 @@ +#include "global_destructor_chain.h" + +DestructorChain* __global_destructor_chain; + +// Symbol: __register_global_object +// PAL: 0x80021338..0x80021350 +void* __register_global_object(void* object, void* destructor, void* reg) { + ((DestructorChain*)reg)->succ = __global_destructor_chain; + ((DestructorChain*)reg)->destructor = destructor; + ((DestructorChain*)reg)->object = object; + __global_destructor_chain = (DestructorChain*)reg; + return object; +} + +// Symbol: __destroy_global_chain +// PAL: 0x80021350..0x80021398 +void __destroy_global_chain(void) { + DestructorChain* dtors; + while ((dtors = __global_destructor_chain) != 0L) { + __global_destructor_chain = dtors->succ; + (((void (*)(void*, int))dtors->destructor)(dtors->object, -1)); + } +} + +static DestructorChain atexit_funcs[64]; +static long atexit_curr_func = 0; + +// Symbol: __register_atexit +// PAL: 0x80021398..0x800213e4 +int __register_atexit(void (*func)(void)) { + if (atexit_curr_func == 64) + return -1; + __register_global_object(0, func, &atexit_funcs[atexit_curr_func++]); + return 0; +} diff --git a/source/platform/global_destructor_chain.h b/source/platform/global_destructor_chain.h new file mode 100644 index 000000000..3d06a276e --- /dev/null +++ b/source/platform/global_destructor_chain.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DestructorChain { + struct DestructorChain* succ; + void* destructor; + void* object; +} DestructorChain; + +// PAL: 0x80021338..0x80021350 +void* __register_global_object(void* object, void* destructor, + void* registration); +// PAL: 0x80021350..0x80021398 +void __destroy_global_chain(void); +// PAL: 0x80021398..0x800213e4 +int __register_atexit(void (*)(void)); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/platform/math.h b/source/platform/math.h index edb438175..e340d3d8b 100644 --- a/source/platform/math.h +++ b/source/platform/math.h @@ -24,6 +24,17 @@ inline f32 sqrtf(f32 x) { return (f32)sqrt(x); } f64 acos(f64); inline f32 acosf(f32 x) { return (f32)acos(x); } +double frexp(double x, int* eptr); +double ldexp(double x, int exp); + +double pow(double); + +double copysign(double x, double y); + +int __fpclassifyf(float); +int __fpclassifyd(double); +int __signbitd(double); + #pragma pop #ifdef __cplusplus diff --git a/source/platform/mem.c b/source/platform/mem.c new file mode 100644 index 000000000..cdae18ea2 --- /dev/null +++ b/source/platform/mem.c @@ -0,0 +1,111 @@ +#include + +#include +#include + +#include "mem_cpy.h" + +// Symbol: memmove +// PAL: 0x8000f1f0..0x8000f2bc +MARK_BINARY_BLOB(memmove, 0x8000f1f0, 0x8000f2bc); +asm void* memmove(void*, const void*, size_t) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + xor r6, r3, r4; + cmplwi r5, 0x20; + stw r0, 0x14(r1); + cntlzw r0, r6; + slw r0, r3, r0; + stw r31, 0xc(r1); + mr r31, r3; + srwi r7, r0, 0x1f; + blt lbl_8000f258; + clrlwi. r0, r6, 0x1e; + beq lbl_8000f23c; + cmpwi r7, 0; + bne lbl_8000f234; + bl __copy_longs_unaligned; + b lbl_8000f250; +lbl_8000f234: + bl __copy_longs_rev_unaligned; + b lbl_8000f250; +lbl_8000f23c: + cmpwi r7, 0; + bne lbl_8000f24c; + bl __copy_longs_aligned; + b lbl_8000f250; +lbl_8000f24c: + bl __copy_longs_rev_aligned; +lbl_8000f250: + mr r3, r31; + b lbl_8000f2a8; +lbl_8000f258: + cmpwi r7, 0; + bne lbl_8000f284; + addi r4, r4, -1; + addi r3, r3, -1; + addi r5, r5, 1; + b lbl_8000f278; +lbl_8000f270: + lbzu r0, 1(r4); + stbu r0, 1(r3); +lbl_8000f278: + addic. r5, r5, -1; + bne lbl_8000f270; + b lbl_8000f2a4; +lbl_8000f284: + add r4, r4, r5; + add r3, r3, r5; + addi r5, r5, 1; + b lbl_8000f29c; +lbl_8000f294: + lbzu r0, -1(r4); + stbu r0, -1(r3); +lbl_8000f29c: + addic. r5, r5, -1; + bne lbl_8000f294; +lbl_8000f2a4: + mr r3, r31; +lbl_8000f2a8: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: memchr +// PAL: 0x8000f2bc..0x8000f2e8 +void* memchr(void* ptr, int value, size_t num) { + const u8* str; + u32 v = value & 0xff; + for (str = (u8*)ptr - 1, num++; --num;) + if ((*++str & 0xff) == v) + return (void*)str; + return NULL; +} + +// Symbol: __memrchr +// PAL: 0x8000f2e8..0x8000f314 +void* __memrchr(const void* ptr, int value, size_t num) { + const u8* str; + u32 v = value & 0xff; + for (str = (u8*)(ptr) + num, num++; --num;) + if (*--str == v) + return (void*)str; + return NULL; +} + +// Symbol: memcmp +// PAL: 0x8000f314..0x8000f360 +int memcmp(const void* s1, const void* s2, size_t n) { + const u8* p1; + const u8* p2; + for (p1 = (const u8*)(s1)-1, p2 = (const u8*)(s2)-1, n++; --n;) + if (*++p1 != *++p2) + return *p1 < *p2 ? -1 : 1; + return 0; +} diff --git a/source/platform/mem_cpy.c b/source/platform/mem_cpy.c new file mode 100644 index 000000000..d3a6ca446 --- /dev/null +++ b/source/platform/mem_cpy.c @@ -0,0 +1,245 @@ +#include "mem_cpy.h" + +// Symbol: __copy_longs_aligned +// Function signature is unknown. +// PAL: 0x8000f360..0x8000f41c +MARK_BINARY_BLOB(__copy_longs_aligned, 0x8000f360, 0x8000f41c); +asm UNKNOWN_FUNCTION(__copy_longs_aligned) { + // clang-format off + nofralloc; + neg r0, r3; + addi r7, r4, -1; + clrlwi. r6, r0, 0x1e; + addi r3, r3, -1; + beq lbl_8000f388; + subf r5, r6, r5; +lbl_8000f378: + lbzu r0, 1(r7); + addic. r6, r6, -1; + stbu r0, 1(r3); + bne lbl_8000f378; +lbl_8000f388: + rlwinm. r4, r5, 0x1b, 5, 0x1f; + addi r6, r7, -3; + addi r3, r3, -3; + beq lbl_8000f3e0; +lbl_8000f398: + lwz r0, 4(r6); + addic. r4, r4, -1; + stw r0, 4(r3); + lwz r0, 8(r6); + stw r0, 8(r3); + lwz r0, 0xc(r6); + stw r0, 0xc(r3); + lwz r0, 0x10(r6); + stw r0, 0x10(r3); + lwz r0, 0x14(r6); + stw r0, 0x14(r3); + lwz r0, 0x18(r6); + stw r0, 0x18(r3); + lwz r0, 0x1c(r6); + stw r0, 0x1c(r3); + lwzu r0, 0x20(r6); + stwu r0, 0x20(r3); + bne lbl_8000f398; +lbl_8000f3e0: + rlwinm. r4, r5, 0x1e, 0x1d, 0x1f; + beq lbl_8000f3f8; +lbl_8000f3e8: + lwzu r0, 4(r6); + addic. r4, r4, -1; + stwu r0, 4(r3); + bne lbl_8000f3e8; +lbl_8000f3f8: + clrlwi. r5, r5, 0x1e; + addi r4, r6, 3; + addi r3, r3, 3; + beqlr; +lbl_8000f408: + lbzu r0, 1(r4); + addic. r5, r5, -1; + stbu r0, 1(r3); + bne lbl_8000f408; + blr; + // clang-format on +} + +// Symbol: __copy_longs_rev_aligned +// Function signature is unknown. +// PAL: 0x8000f41c..0x8000f4c4 +MARK_BINARY_BLOB(__copy_longs_rev_aligned, 0x8000f41c, 0x8000f4c4); +asm UNKNOWN_FUNCTION(__copy_longs_rev_aligned) { + // clang-format off + nofralloc; + add r6, r3, r5; + add r4, r4, r5; + clrlwi. r3, r6, 0x1e; + beq lbl_8000f440; + subf r5, r3, r5; +lbl_8000f430: + lbzu r0, -1(r4); + addic. r3, r3, -1; + stbu r0, -1(r6); + bne lbl_8000f430; +lbl_8000f440: + rlwinm. r3, r5, 0x1b, 5, 0x1f; + beq lbl_8000f490; +lbl_8000f448: + lwz r0, -4(r4); + addic. r3, r3, -1; + stw r0, -4(r6); + lwz r0, -8(r4); + stw r0, -8(r6); + lwz r0, -0xc(r4); + stw r0, -0xc(r6); + lwz r0, -0x10(r4); + stw r0, -0x10(r6); + lwz r0, -0x14(r4); + stw r0, -0x14(r6); + lwz r0, -0x18(r4); + stw r0, -0x18(r6); + lwz r0, -0x1c(r4); + stw r0, -0x1c(r6); + lwzu r0, -0x20(r4); + stwu r0, -0x20(r6); + bne lbl_8000f448; +lbl_8000f490: + rlwinm. r3, r5, 0x1e, 0x1d, 0x1f; + beq lbl_8000f4a8; +lbl_8000f498: + lwzu r0, -4(r4); + addic. r3, r3, -1; + stwu r0, -4(r6); + bne lbl_8000f498; +lbl_8000f4a8: + clrlwi. r5, r5, 0x1e; + beqlr; +lbl_8000f4b0: + lbzu r0, -1(r4); + addic. r5, r5, -1; + stbu r0, -1(r6); + bne lbl_8000f4b0; + blr; + // clang-format on +} + +// Symbol: __copy_longs_unaligned +// Function signature is unknown. +// PAL: 0x8000f4c4..0x8000f584 +MARK_BINARY_BLOB(__copy_longs_unaligned, 0x8000f4c4, 0x8000f584); +asm UNKNOWN_FUNCTION(__copy_longs_unaligned) { + // clang-format off + nofralloc; + neg r0, r3; + addi r7, r4, -1; + clrlwi. r6, r0, 0x1e; + addi r3, r3, -1; + beq lbl_8000f4ec; + subf r5, r6, r5; +lbl_8000f4dc: + lbzu r0, 1(r7); + addic. r6, r6, -1; + stbu r0, 1(r3); + bne lbl_8000f4dc; +lbl_8000f4ec: + addi r0, r7, 1; + addi r4, r3, -3; + clrlwi r10, r0, 0x1e; + srwi r6, r5, 3; + subf r7, r10, r7; + rlwinm r11, r0, 3, 0x1b, 0x1c; + lwzu r8, 1(r7); + subfic r12, r11, 0x20; +lbl_8000f50c: + lwz r9, 4(r7); + slw r3, r8, r11; + addic. r6, r6, -1; + srw r0, r9, r12; + or r0, r3, r0; + stw r0, 4(r4); + slw r3, r9, r11; + lwzu r8, 8(r7); + srw r0, r8, r12; + or r0, r3, r0; + stwu r0, 8(r4); + bne lbl_8000f50c; + rlwinm. r0, r5, 0, 0x1d, 0x1d; + beq lbl_8000f558; + lwzu r0, 4(r7); + slw r3, r8, r11; + srw r0, r0, r12; + or r0, r3, r0; + stwu r0, 4(r4); +lbl_8000f558: + clrlwi. r5, r5, 0x1e; + addi r6, r7, 3; + addi r3, r4, 3; + beqlr; + subfic r0, r10, 4; + subf r6, r0, r6; +lbl_8000f570: + lbzu r0, 1(r6); + addic. r5, r5, -1; + stbu r0, 1(r3); + bne lbl_8000f570; + blr; + // clang-format on +} + +// Symbol: __copy_longs_rev_unaligned +// Function signature is unknown. +// PAL: 0x8000f584..0x8000f630 +MARK_BINARY_BLOB(__copy_longs_rev_unaligned, 0x8000f584, 0x8000f630); +asm UNKNOWN_FUNCTION(__copy_longs_rev_unaligned) { + // clang-format off + nofralloc; + add r12, r3, r5; + add r11, r4, r5; + clrlwi. r3, r12, 0x1e; + beq lbl_8000f5a8; + subf r5, r3, r5; +lbl_8000f598: + lbzu r0, -1(r11); + addic. r3, r3, -1; + stbu r0, -1(r12); + bne lbl_8000f598; +lbl_8000f5a8: + rlwinm r9, r11, 3, 0x1b, 0x1c; + clrlwi r8, r11, 0x1e; + subfic r10, r9, 0x20; + srwi r4, r5, 3; + subfic r0, r8, 4; + add r11, r11, r0; + lwzu r6, -4(r11); +lbl_8000f5c4: + lwz r7, -4(r11); + srw r0, r6, r10; + addic. r4, r4, -1; + slw r3, r7, r9; + or r0, r3, r0; + stw r0, -4(r12); + srw r0, r7, r10; + lwzu r6, -8(r11); + slw r3, r6, r9; + or r0, r3, r0; + stwu r0, -8(r12); + bne lbl_8000f5c4; + rlwinm. r0, r5, 0, 0x1d, 0x1d; + beq lbl_8000f610; + lwzu r3, -4(r11); + srw r0, r6, r10; + slw r3, r3, r9; + or r0, r3, r0; + stwu r0, -4(r12); +lbl_8000f610: + clrlwi. r5, r5, 0x1e; + beqlr; + add r11, r11, r8; +lbl_8000f61c: + lbzu r0, -1(r11); + addic. r5, r5, -1; + stbu r0, -1(r12); + bne lbl_8000f61c; + blr; + // clang-format on +} diff --git a/source/platform/mem_cpy.h b/source/platform/mem_cpy.h new file mode 100644 index 000000000..a4cd87682 --- /dev/null +++ b/source/platform/mem_cpy.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x8000f360..0x8000f41c +UNKNOWN_FUNCTION(__copy_longs_aligned); +// PAL: 0x8000f41c..0x8000f4c4 +UNKNOWN_FUNCTION(__copy_longs_rev_aligned); +// PAL: 0x8000f4c4..0x8000f584 +UNKNOWN_FUNCTION(__copy_longs_unaligned); +// PAL: 0x8000f584..0x8000f630 +UNKNOWN_FUNCTION(__copy_longs_rev_unaligned); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/platform/printf.c b/source/platform/printf.c new file mode 100644 index 000000000..bc9049fbe --- /dev/null +++ b/source/platform/printf.c @@ -0,0 +1,2017 @@ +#include "printf.h" + +#include + +#include "va_arg.h" + +// Extern function references. +// PAL: 0x8000dafc +extern UNKNOWN_FUNCTION(__num2dec); +// PAL: 0x8000e954 +extern UNKNOWN_FUNCTION(unk_8000e954); +// PAL: 0x8000f138 +extern UNKNOWN_FUNCTION(unk_8000f138); +// PAL: 0x8000f640 +extern UNKNOWN_FUNCTION(unk_8000f640); +// PAL: 0x8000fbfc +extern UNKNOWN_FUNCTION(unk_8000fbfc); +// PAL: 0x8000fe34 +extern UNKNOWN_FUNCTION(unk_8000fe34); +// PAL: 0x80013108 +extern UNKNOWN_FUNCTION(unk_80013108); +// PAL: 0x800155d8 +extern UNKNOWN_FUNCTION(unk_800155d8); + +// Symbol: double2hex +// Function signature is unknown. +// PAL: 0x800100e4..0x800104b4 +MARK_BINARY_BLOB(double2hex, 0x800100e4, 0x800104b4); +asm UNKNOWN_FUNCTION(double2hex) { + // clang-format off + nofralloc; + stwu r1, -0x90(r1); + mflr r0; + stw r0, 0x94(r1); + stfd f31, 0x80(r1); + psq_st f31, 136(r1), 0, 0; + lis r5, 0x8027; + lwz r0, 0xc(r4); + fmr f31, f1; + stw r31, 0x7c(r1); + cmpwi r0, 0x1fd; + stw r30, 0x78(r1); + mr r30, r4; + stw r29, 0x74(r1); + mr r29, r3; + stw r28, 0x70(r1); + lwz r5, 0xf98(r5); + stfd f1, 8(r1); + lbz r31, 0(r5); + ble lbl_80010138; + li r3, 0; + b lbl_8001048c; +lbl_80010138: + li r28, 0; + li r0, 0x20; + stb r28, 0x10(r1); + addi r3, r1, 0x10; + addi r4, r1, 0x38; + sth r0, 0x12(r1); + bl __num2dec; + lbz r0, 0x3d(r1); + cmpwi r0, 0x30; + beq lbl_80010174; + cmpwi r0, 0x49; + beq lbl_8001017c; + cmpwi r0, 0x4e; + beq lbl_80010208; + b lbl_80010298; +lbl_80010174: + sth r28, 0x3a(r1); + b lbl_80010298; +lbl_8001017c: + lbz r0, 0x38(r1); + extsb. r0, r0; + beq lbl_800101c4; + lbz r0, 5(r30); + addi r28, r29, -5; + cmplwi r0, 0x41; + bne lbl_800101ac; + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + bl strcpy; + b lbl_80010200; +lbl_800101ac: + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + addi r4, r4, 5; + bl strcpy; + b lbl_80010200; +lbl_800101c4: + lbz r0, 5(r30); + addi r28, r29, -4; + cmplwi r0, 0x41; + bne lbl_800101ec; + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + addi r4, r4, 0xa; + bl strcpy; + b lbl_80010200; +lbl_800101ec: + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + addi r4, r4, 0xe; + bl strcpy; +lbl_80010200: + mr r3, r28; + b lbl_8001048c; +lbl_80010208: + lbz r0, 0x38(r1); + extsb. r0, r0; + beq lbl_80010254; + lbz r0, 5(r30); + addi r28, r29, -5; + cmplwi r0, 0x41; + bne lbl_8001023c; + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + addi r4, r4, 0x12; + bl strcpy; + b lbl_80010290; +lbl_8001023c: + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + addi r4, r4, 0x17; + bl strcpy; + b lbl_80010290; +lbl_80010254: + lbz r0, 5(r30); + addi r28, r29, -4; + cmplwi r0, 0x41; + bne lbl_8001027c; + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + addi r4, r4, 0x1c; + bl strcpy; + b lbl_80010290; +lbl_8001027c: + lis r4, 0x8024; + mr r3, r28; + addi r4, r4, 0x6628; + addi r4, r4, 0x20; + bl strcpy; +lbl_80010290: + mr r3, r28; + b lbl_8001048c; +lbl_80010298: + lbz r0, 9(r1); + li r8, 0; + lbz r3, 8(r1); + li r9, 1; + slwi r0, r0, 0x11; + li r7, 0x64; + rlwimi r0, r3, 0x19, 0, 6; + stb r9, 0x28(r1); + srwi r5, r0, 0x15; + mr r4, r29; + neg r0, r5; + stb r9, 0x29(r1); + or r3, r0, r5; + addi r0, r5, -1023; + stb r8, 0x2a(r1); + srawi r3, r3, 0x1f; + addi r5, r1, 0x18; + stb r8, 0x2b(r1); + and r3, r0, r3; + lwz r6, 0x28(r1); + stb r8, 0x2c(r1); + stb r7, 0x2d(r1); + lwz r0, 0x2c(r1); + stw r8, 0x30(r1); + stw r9, 0x34(r1); + stw r6, 0x18(r1); + stw r0, 0x1c(r1); + stw r8, 0x20(r1); + stw r9, 0x24(r1); + bl unk_8000fbfc; + lbz r0, 5(r30); + cmplwi r0, 0x61; + bne lbl_80010328; + li r0, 0x70; + stbu r0, -1(r3); + b lbl_80010330; +lbl_80010328: + li r0, 0x50; + stbu r0, -1(r3); +lbl_80010330: + lwz r0, 0xc(r30); + addi r8, r1, 8; + slwi r4, r0, 2; + addi r9, r4, 0xb; + mtctr r0; + cmpwi r0, 1; + blt lbl_800103e4; +lbl_8001034c: + cmpwi r9, 0x40; + bge lbl_800103d4; + srawi r4, r9, 3; + addi r0, r9, -4; + add r7, r8, r4; + clrlwi r6, r9, 0x1d; + rlwinm r4, r9, 0, 0, 0x1c; + rlwinm r0, r0, 0, 0, 0x1c; + lbz r5, 0(r7); + subfic r6, r6, 7; + cmpw r4, r0; + sraw r0, r5, r6; + clrlwi r4, r0, 0x18; + beq lbl_80010398; + lbz r0, -1(r7); + slwi r0, r0, 8; + sraw r0, r0, r6; + or r0, r4, r0; + clrlwi r4, r0, 0x18; +lbl_80010398: + clrlwi r4, r4, 0x1c; + cmplwi r4, 0xa; + bge lbl_800103b0; + addi r0, r4, 0x30; + clrlwi r4, r0, 0x18; + b lbl_800103d8; +lbl_800103b0: + lbz r0, 5(r30); + cmplwi r0, 0x61; + bne lbl_800103c8; + addi r0, r4, 0x57; + clrlwi r4, r0, 0x18; + b lbl_800103d8; +lbl_800103c8: + addi r0, r4, 0x37; + clrlwi r4, r0, 0x18; + b lbl_800103d8; +lbl_800103d4: + li r4, 0x30; +lbl_800103d8: + stbu r4, -1(r3); + addi r9, r9, -4; + bdnz lbl_8001034c; +lbl_800103e4: + lwz r0, 0xc(r30); + cmpwi r0, 0; + bne lbl_800103fc; + lbz r0, 3(r30); + cmpwi r0, 0; + beq lbl_80010400; +lbl_800103fc: + stbu r31, -1(r3); +lbl_80010400: + fabs f1, f31; + lfd f0, -0x7f20(r2); + fcmpu cr0, f0, f1; + beq lbl_8001041c; + li r0, 0x31; + stbu r0, -1(r3); + b lbl_80010424; +lbl_8001041c: + li r0, 0x30; + stbu r0, -1(r3); +lbl_80010424: + lbz r0, 5(r30); + cmplwi r0, 0x61; + bne lbl_8001043c; + li r0, 0x78; + stbu r0, -1(r3); + b lbl_80010444; +lbl_8001043c: + li r0, 0x58; + stbu r0, -1(r3); +lbl_80010444: + li r0, 0x30; + stbu r0, -1(r3); + lbz r0, 0x38(r1); + extsb. r0, r0; + beq lbl_80010464; + li r0, 0x2d; + stbu r0, -1(r3); + b lbl_8001048c; +lbl_80010464: + lbz r0, 1(r30); + cmplwi r0, 1; + bne lbl_8001047c; + li r0, 0x2b; + stbu r0, -1(r3); + b lbl_8001048c; +lbl_8001047c: + cmplwi r0, 2; + bne lbl_8001048c; + li r0, 0x20; + stbu r0, -1(r3); +lbl_8001048c: + psq_l f31, 136(r1), 0, 0; + lwz r0, 0x94(r1); + lfd f31, 0x80(r1); + lwz r31, 0x7c(r1); + lwz r30, 0x78(r1); + lwz r29, 0x74(r1); + lwz r28, 0x70(r1); + mtlr r0; + addi r1, r1, 0x90; + blr; + // clang-format on +} + +// Symbol: round_decimal +// Function signature is unknown. +// PAL: 0x800104b4..0x800105dc +MARK_BINARY_BLOB(round_decimal, 0x800104b4, 0x800105dc); +asm UNKNOWN_FUNCTION(round_decimal) { + // clang-format off + nofralloc; + cmpwi r4, 0; + bge lbl_800104d8; +lbl_800104bc: + li r5, 0; + li r4, 1; + li r0, 0x30; + sth r5, 2(r3); + stb r4, 4(r3); + stb r0, 5(r3); + blr; +lbl_800104d8: + lbz r7, 4(r3); + cmpw r4, r7; + bgelr; + add r6, r3, r4; + lbz r5, 5(r6); + addi r8, r6, 5; + addi r0, r5, -48; + extsb r6, r0; + cmpwi r6, 5; + bne lbl_8001053c; + add r5, r3, r7; + addi r5, r5, 5; +lbl_80010508: + addi r5, r5, -1; + cmplw r5, r8; + ble lbl_80010520; + lbz r0, 0(r5); + cmpwi r0, 0x30; + beq lbl_80010508; +lbl_80010520: + cmplw r5, r8; + bne lbl_80010534; + lbz r0, -1(r8); + clrlwi r5, r0, 0x1f; + b lbl_80010550; +lbl_80010534: + li r5, 1; + b lbl_80010550; +lbl_8001053c: + xori r0, r6, 5; + srawi r5, r0, 1; + and r0, r0, r6; + subf r0, r0, r5; + srwi r5, r0, 0x1f; +lbl_80010550: + mtctr r4; + cmpwi r4, 0; + beq lbl_800105a4; +lbl_8001055c: + lbzu r0, -1(r8); + add r5, r0, r5; + addi r0, r5, -48; + extsb r6, r0; + xori r0, r6, 9; + srawi r5, r0, 1; + and r0, r0, r6; + subf r0, r0, r5; + rlwinm. r5, r0, 1, 0x1f, 0x1f; + bne lbl_8001058c; + cmpwi r6, 0; + bne lbl_80010594; +lbl_8001058c: + addi r4, r4, -1; + b lbl_800105a0; +lbl_80010594: + addi r0, r6, 0x30; + stb r0, 0(r8); + b lbl_800105a4; +lbl_800105a0: + bdnz lbl_8001055c; +lbl_800105a4: + cmpwi r5, 0; + beq lbl_800105cc; + lha r5, 2(r3); + li r4, 1; + li r0, 0x31; + stb r4, 4(r3); + addi r4, r5, 1; + sth r4, 2(r3); + stb r0, 5(r3); + blr; +lbl_800105cc: + cmpwi r4, 0; + beq lbl_800104bc; + stb r4, 4(r3); + blr; + // clang-format on +} + +// Symbol: float2str +// Function signature is unknown. +// PAL: 0x800105dc..0x80010d74 +MARK_BINARY_BLOB(float2str, 0x800105dc, 0x80010d74); +asm UNKNOWN_FUNCTION(float2str) { + // clang-format off + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + lis r5, 0x8027; + stw r0, 0x54(r1); + lwz r0, 0xc(r4); + stfd f31, 0x48(r1); + fmr f31, f1; + cmpwi r0, 0x1fd; + stw r31, 0x44(r1); + stw r30, 0x40(r1); + stw r29, 0x3c(r1); + mr r29, r4; + stw r28, 0x38(r1); + mr r28, r3; + lwz r5, 0xf98(r5); + lbz r30, 0(r5); + ble lbl_80010628; + li r3, 0; + b lbl_80010d50; +lbl_80010628: + li r3, 0; + li r0, 0x20; + stb r3, 8(r1); + addi r3, r1, 8; + addi r4, r1, 0xc; + sth r0, 0xa(r1); + bl __num2dec; + lbz r0, 0x10(r1); + addi r5, r1, 0x11; + add r5, r5, r0; + b lbl_8001066c; +lbl_80010654: + lbz r4, 0x10(r1); + lha r3, 0xe(r1); + addi r0, r4, -1; + stb r0, 0x10(r1); + addi r0, r3, 1; + sth r0, 0xe(r1); +lbl_8001066c: + lbz r0, 0x10(r1); + cmplwi r0, 1; + ble lbl_80010684; + lbzu r0, -1(r5); + cmpwi r0, 0x30; + beq lbl_80010654; +lbl_80010684: + lbz r0, 0x11(r1); + cmpwi r0, 0x30; + beq lbl_800106a4; + cmpwi r0, 0x49; + beq lbl_800106b0; + cmpwi r0, 0x4e; + beq lbl_800107b4; + b lbl_800108bc; +lbl_800106a4: + li r0, 0; + sth r0, 0xe(r1); + b lbl_800108bc; +lbl_800106b0: + lfd f0, -0x7f20(r2); + fcmpo cr0, f31, f0; + bge lbl_80010734; + lbz r0, 5(r29); + addi r31, r28, -5; + li r3, 1; + cmplwi r0, 0xff; + bgt lbl_800106d4; + li r3, 0; +lbl_800106d4: + cmpwi r3, 0; + beq lbl_800106e4; + li r0, 0; + b lbl_80010700; +lbl_800106e4: + lis r3, 0x8027; + slwi r0, r0, 1; + addi r3, r3, 0x1148; + lwz r3, 0x38(r3); + lwz r3, 8(r3); + lhzx r0, r3, r0; + rlwinm r0, r0, 0, 0x16, 0x16; +lbl_80010700: + cmpwi r0, 0; + beq lbl_8001071c; + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + bl strcpy; + b lbl_800107ac; +lbl_8001071c: + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + addi r4, r4, 5; + bl strcpy; + b lbl_800107ac; +lbl_80010734: + lbz r0, 5(r29); + addi r31, r28, -4; + li r3, 1; + cmplwi r0, 0xff; + bgt lbl_8001074c; + li r3, 0; +lbl_8001074c: + cmpwi r3, 0; + beq lbl_8001075c; + li r0, 0; + b lbl_80010778; +lbl_8001075c: + lis r3, 0x8027; + slwi r0, r0, 1; + addi r3, r3, 0x1148; + lwz r3, 0x38(r3); + lwz r3, 8(r3); + lhzx r0, r3, r0; + rlwinm r0, r0, 0, 0x16, 0x16; +lbl_80010778: + cmpwi r0, 0; + beq lbl_80010798; + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + addi r4, r4, 0xa; + bl strcpy; + b lbl_800107ac; +lbl_80010798: + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + addi r4, r4, 0xe; + bl strcpy; +lbl_800107ac: + mr r3, r31; + b lbl_80010d50; +lbl_800107b4: + lbz r0, 0xc(r1); + extsb. r0, r0; + beq lbl_8001083c; + lbz r0, 5(r29); + addi r31, r28, -5; + li r3, 1; + cmplwi r0, 0xff; + bgt lbl_800107d8; + li r3, 0; +lbl_800107d8: + cmpwi r3, 0; + beq lbl_800107e8; + li r0, 0; + b lbl_80010804; +lbl_800107e8: + lis r3, 0x8027; + slwi r0, r0, 1; + addi r3, r3, 0x1148; + lwz r3, 0x38(r3); + lwz r3, 8(r3); + lhzx r0, r3, r0; + rlwinm r0, r0, 0, 0x16, 0x16; +lbl_80010804: + cmpwi r0, 0; + beq lbl_80010824; + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + addi r4, r4, 0x12; + bl strcpy; + b lbl_800108b4; +lbl_80010824: + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + addi r4, r4, 0x17; + bl strcpy; + b lbl_800108b4; +lbl_8001083c: + lbz r0, 5(r29); + addi r31, r28, -4; + li r3, 1; + cmplwi r0, 0xff; + bgt lbl_80010854; + li r3, 0; +lbl_80010854: + cmpwi r3, 0; + beq lbl_80010864; + li r0, 0; + b lbl_80010880; +lbl_80010864: + lis r3, 0x8027; + slwi r0, r0, 1; + addi r3, r3, 0x1148; + lwz r3, 0x38(r3); + lwz r3, 8(r3); + lhzx r0, r3, r0; + rlwinm r0, r0, 0, 0x16, 0x16; +lbl_80010880: + cmpwi r0, 0; + beq lbl_800108a0; + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + addi r4, r4, 0x1c; + bl strcpy; + b lbl_800108b4; +lbl_800108a0: + lis r4, 0x8024; + mr r3, r31; + addi r4, r4, 0x6628; + addi r4, r4, 0x20; + bl strcpy; +lbl_800108b4: + mr r3, r31; + b lbl_80010d50; +lbl_800108bc: + lha r4, 0xe(r1); + li r0, 0; + lbz r3, 0x10(r1); + addi r31, r28, -1; + lbz r5, 5(r29); + add r3, r3, r4; + addi r3, r3, -1; + sth r3, 0xe(r1); + cmpwi r5, 0x67; + stb r0, -1(r28); + beq lbl_80010914; + cmpwi r5, 0x47; + beq lbl_80010914; + cmpwi r5, 0x65; + beq lbl_800109cc; + cmpwi r5, 0x45; + beq lbl_800109cc; + cmpwi r5, 0x66; + beq lbl_80010b30; + cmpwi r5, 0x46; + beq lbl_80010b30; + b lbl_80010d4c; +lbl_80010914: + lwz r4, 0xc(r29); + lbz r0, 0x10(r1); + cmpw r0, r4; + ble lbl_8001092c; + addi r3, r1, 0xc; + bl round_decimal; +lbl_8001092c: + lha r4, 0xe(r1); + cmpwi r4, -4; + blt lbl_80010944; + lwz r3, 0xc(r29); + cmpw r4, r3; + blt lbl_80010990; +lbl_80010944: + lbz r0, 3(r29); + cmpwi r0, 0; + beq lbl_80010960; + lwz r3, 0xc(r29); + addi r0, r3, -1; + stw r0, 0xc(r29); + b lbl_8001096c; +lbl_80010960: + lbz r3, 0x10(r1); + addi r0, r3, -1; + stw r0, 0xc(r29); +lbl_8001096c: + lbz r0, 5(r29); + cmplwi r0, 0x67; + bne lbl_80010984; + li r0, 0x65; + stb r0, 5(r29); + b lbl_800109cc; +lbl_80010984: + li r0, 0x45; + stb r0, 5(r29); + b lbl_800109cc; +lbl_80010990: + lbz r0, 3(r29); + cmpwi r0, 0; + beq lbl_800109ac; + addi r0, r4, 1; + subf r0, r0, r3; + stw r0, 0xc(r29); + b lbl_80010b30; +lbl_800109ac: + lbz r0, 0x10(r1); + addi r3, r4, 1; + subf. r0, r3, r0; + stw r0, 0xc(r29); + bge lbl_80010b30; + li r0, 0; + stw r0, 0xc(r29); + b lbl_80010b30; +lbl_800109cc: + lwz r3, 0xc(r29); + lbz r0, 0x10(r1); + addi r4, r3, 1; + cmpw r0, r4; + ble lbl_800109e8; + addi r3, r1, 0xc; + bl round_decimal; +lbl_800109e8: + lha r6, 0xe(r1); + li r8, 0x2b; + cmpwi r6, 0; + bge lbl_80010a00; + neg r6, r6; + li r8, 0x2d; +lbl_80010a00: + lis r3, 0x6666; + li r7, 0; + addi r5, r3, 0x6667; + b lbl_80010a44; +lbl_80010a10: + mulhw r0, r5, r6; + addi r7, r7, 1; + srawi r3, r0, 2; + srwi r4, r3, 0x1f; + srawi r0, r0, 2; + add r3, r3, r4; + mulli r4, r3, 0xa; + srwi r3, r0, 0x1f; + subf r4, r4, r6; + add r6, r0, r3; + addi r0, r4, 0x30; + stb r0, -1(r31); + addi r31, r31, -1; +lbl_80010a44: + cmpwi r6, 0; + bne lbl_80010a10; + cmpwi r7, 2; + blt lbl_80010a10; + stb r8, -1(r31); + lbz r0, 5(r29); + stbu r0, -2(r31); + lwz r3, 0xc(r29); + subf r0, r31, r28; + add r0, r3, r0; + cmpwi r0, 0x1fd; + ble lbl_80010a7c; + li r3, 0; + b lbl_80010d50; +lbl_80010a7c: + lbz r4, 0x10(r1); + addi r0, r3, 1; + cmpw r4, r0; + bge lbl_80010aa8; + addi r3, r3, 2; + li r0, 0x30; + subf r3, r4, r3; + b lbl_80010aa0; +lbl_80010a9c: + stbu r0, -1(r31); +lbl_80010aa0: + addic. r3, r3, -1; + bne lbl_80010a9c; +lbl_80010aa8: + lbz r3, 0x10(r1); + addi r4, r1, 0x11; + add r4, r4, r3; + b lbl_80010ac0; +lbl_80010ab8: + lbzu r0, -1(r4); + stbu r0, -1(r31); +lbl_80010ac0: + addic. r3, r3, -1; + bne lbl_80010ab8; + lwz r0, 0xc(r29); + cmpwi r0, 0; + bne lbl_80010ae0; + lbz r0, 3(r29); + cmpwi r0, 0; + beq lbl_80010ae4; +lbl_80010ae0: + stbu r30, -1(r31); +lbl_80010ae4: + lbz r0, 0x11(r1); + stbu r0, -1(r31); + lbz r0, 0xc(r1); + extsb. r0, r0; + beq lbl_80010b04; + li r0, 0x2d; + stbu r0, -1(r31); + b lbl_80010d4c; +lbl_80010b04: + lbz r0, 1(r29); + cmplwi r0, 1; + bne lbl_80010b1c; + li r0, 0x2b; + stbu r0, -1(r31); + b lbl_80010d4c; +lbl_80010b1c: + cmplwi r0, 2; + bne lbl_80010d4c; + li r0, 0x20; + stbu r0, -1(r31); + b lbl_80010d4c; +lbl_80010b30: + lbz r4, 0x10(r1); + lha r5, 0xe(r1); + subf r3, r5, r4; + addic. r8, r3, -1; + bge lbl_80010b48; + li r8, 0; +lbl_80010b48: + lwz r0, 0xc(r29); + cmpw r8, r0; + ble lbl_80010b7c; + subf r0, r0, r8; + addi r3, r1, 0xc; + subf r4, r0, r4; + bl round_decimal; + lbz r4, 0x10(r1); + lha r5, 0xe(r1); + subf r3, r5, r4; + addic. r8, r3, -1; + bge lbl_80010b7c; + li r8, 0; +lbl_80010b7c: + addic. r7, r5, 1; + bge lbl_80010b88; + li r7, 0; +lbl_80010b88: + add r0, r7, r8; + cmpwi r0, 0x1fd; + ble lbl_80010b9c; + li r3, 0; + b lbl_80010d50; +lbl_80010b9c: + addi r6, r1, 0x11; + li r5, 0; + add r6, r6, r4; + li r3, 0x30; + b lbl_80010bb8; +lbl_80010bb0: + stbu r3, -1(r31); + addi r5, r5, 1; +lbl_80010bb8: + lwz r0, 0xc(r29); + subf r0, r8, r0; + cmpw r5, r0; + blt lbl_80010bb0; + li r3, 0; + b lbl_80010bdc; +lbl_80010bd0: + lbzu r0, -1(r6); + addi r3, r3, 1; + stbu r0, -1(r31); +lbl_80010bdc: + cmpw r3, r8; + bge lbl_80010bf0; + lbz r0, 0x10(r1); + cmpw r3, r0; + blt lbl_80010bd0; +lbl_80010bf0: + cmpw r3, r8; + subf r3, r3, r8; + li r4, 0x30; + bge lbl_80010c44; + rlwinm. r0, r3, 0x1d, 3, 0x1f; + mtctr r0; + beq lbl_80010c38; +lbl_80010c0c: + stb r4, -1(r31); + stb r4, -2(r31); + stb r4, -3(r31); + stb r4, -4(r31); + stb r4, -5(r31); + stb r4, -6(r31); + stb r4, -7(r31); + stbu r4, -8(r31); + bdnz lbl_80010c0c; + andi. r3, r3, 7; + beq lbl_80010c44; +lbl_80010c38: + mtctr r3; +lbl_80010c3c: + stbu r4, -1(r31); + bdnz lbl_80010c3c; +lbl_80010c44: + lwz r0, 0xc(r29); + cmpwi r0, 0; + bne lbl_80010c5c; + lbz r0, 3(r29); + cmpwi r0, 0; + beq lbl_80010c60; +lbl_80010c5c: + stbu r30, -1(r31); +lbl_80010c60: + cmpwi r7, 0; + beq lbl_80010d04; + li r4, 0; + li r3, 0x30; + b lbl_80010c7c; +lbl_80010c74: + stbu r3, -1(r31); + addi r4, r4, 1; +lbl_80010c7c: + lbz r0, 0x10(r1); + subf r0, r0, r7; + cmpw r4, r0; + blt lbl_80010c74; + cmpw r4, r7; + subf r3, r4, r7; + bge lbl_80010d0c; + rlwinm. r0, r3, 0x1d, 3, 0x1f; + mtctr r0; + beq lbl_80010cf0; +lbl_80010ca4: + lbz r0, -1(r6); + stb r0, -1(r31); + lbz r0, -2(r6); + stb r0, -2(r31); + lbz r0, -3(r6); + stb r0, -3(r31); + lbz r0, -4(r6); + stb r0, -4(r31); + lbz r0, -5(r6); + stb r0, -5(r31); + lbz r0, -6(r6); + stb r0, -6(r31); + lbz r0, -7(r6); + stb r0, -7(r31); + lbzu r0, -8(r6); + stbu r0, -8(r31); + bdnz lbl_80010ca4; + andi. r3, r3, 7; + beq lbl_80010d0c; +lbl_80010cf0: + mtctr r3; +lbl_80010cf4: + lbzu r0, -1(r6); + stbu r0, -1(r31); + bdnz lbl_80010cf4; + b lbl_80010d0c; +lbl_80010d04: + li r0, 0x30; + stbu r0, -1(r31); +lbl_80010d0c: + lbz r0, 0xc(r1); + extsb. r0, r0; + beq lbl_80010d24; + li r0, 0x2d; + stbu r0, -1(r31); + b lbl_80010d4c; +lbl_80010d24: + lbz r0, 1(r29); + cmplwi r0, 1; + bne lbl_80010d3c; + li r0, 0x2b; + stbu r0, -1(r31); + b lbl_80010d4c; +lbl_80010d3c: + cmplwi r0, 2; + bne lbl_80010d4c; + li r0, 0x20; + stbu r0, -1(r31); +lbl_80010d4c: + mr r3, r31; +lbl_80010d50: + lwz r0, 0x54(r1); + lfd f31, 0x48(r1); + lwz r31, 0x44(r1); + lwz r30, 0x40(r1); + lwz r29, 0x3c(r1); + lwz r28, 0x38(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; + // clang-format on +} + +// Symbol: __pformatter +// Function signature is unknown. +// PAL: 0x80010d74..0x80011620 +MARK_BINARY_BLOB(__pformatter, 0x80010d74, 0x80011620); +asm UNKNOWN_FUNCTION(__pformatter) { + // clang-format off + nofralloc; + stwu r1, -0x2d0(r1); + mflr r0; + stw r0, 0x2d4(r1); + stmw r15, 0x28c(r1); + li r24, 0x20; + lis r22, 0x8024; + mr r25, r3; + mr r26, r4; + mr r15, r5; + mr r27, r6; + mr r28, r7; + addi r22, r22, 0x6628; + addi r21, r1, 0x27f; + li r17, 0; + lis r20, 0x8027; + li r23, 0x25; + stb r24, 9(r1); + b lbl_800115fc; +lbl_80010dbc: + mr r3, r15; + li r4, 0x25; + bl strchr; + cmpwi r3, 0; + mr r16, r3; + bne lbl_80010e10; + mr r3, r15; + bl strlen; + cmpwi r3, 0; + mr r5, r3; + add r17, r17, r3; + beq lbl_80011608; + mr r12, r25; + mr r3, r26; + mr r4, r15; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_80011608; + li r3, -1; + b lbl_8001160c; +lbl_80010e10: + subf. r5, r15, r3; + add r17, r17, r5; + beq lbl_80010e40; + mr r12, r25; + mr r3, r26; + mr r4, r15; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_80010e40; + li r3, -1; + b lbl_8001160c; +lbl_80010e40: + mr r3, r16; + mr r4, r27; + addi r5, r1, 0x70; + bl unk_8000f640; + lbz r4, 0x75(r1); + mr r15, r3; + addi r0, r4, -37; + cmplwi r0, 0x53; + bgt lbl_8001141c; + addi r3, r20, 0x1270; + slwi r0, r0, 2; + lwzx r3, r3, r0; + mtctr r3; + bctr; + lbz r0, 0x74(r1); + cmplwi r0, 3; + bne lbl_80010e98; + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); + b lbl_80010f20; +lbl_80010e98: + cmplwi r0, 4; + bne lbl_80010eb8; + mr r3, r27; + li r4, 2; + bl __va_arg; + lwz r30, 0(r3); + lwz r29, 4(r3); + b lbl_80010f20; +lbl_80010eb8: + cmplwi r0, 6; + bne lbl_80010ed8; + mr r3, r27; + li r4, 2; + bl __va_arg; + lwz r30, 0(r3); + lwz r29, 4(r3); + b lbl_80010f20; +lbl_80010ed8: + cmplwi r0, 7; + bne lbl_80010ef4; + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); + b lbl_80010f20; +lbl_80010ef4: + cmplwi r0, 8; + bne lbl_80010f10; + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); + b lbl_80010f20; +lbl_80010f10: + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); +lbl_80010f20: + lbz r0, 0x74(r1); + cmplwi r0, 2; + bne lbl_80010f30; + extsh r31, r31; +lbl_80010f30: + cmplwi r0, 1; + bne lbl_80010f3c; + extsb r31, r31; +lbl_80010f3c: + cmplwi r0, 4; + beq lbl_80010f4c; + cmplwi r0, 6; + bne lbl_80010f90; +lbl_80010f4c: + lwz r9, 0x70(r1); + mr r4, r29; + lwz r8, 0x74(r1); + mr r3, r30; + lwz r7, 0x78(r1); + addi r5, r1, 0x280; + lwz r0, 0x7c(r1); + addi r6, r1, 0x60; + stw r9, 0x60(r1); + stw r8, 0x64(r1); + stw r7, 0x68(r1); + stw r0, 0x6c(r1); + bl unk_8000fe34; + cmpwi r3, 0; + mr r18, r3; + beq lbl_8001141c; + b lbl_80010fcc; +lbl_80010f90: + lwz r8, 0x70(r1); + mr r3, r31; + lwz r7, 0x74(r1); + addi r4, r1, 0x280; + lwz r6, 0x78(r1); + addi r5, r1, 0x50; + lwz r0, 0x7c(r1); + stw r8, 0x50(r1); + stw r7, 0x54(r1); + stw r6, 0x58(r1); + stw r0, 0x5c(r1); + bl unk_8000fbfc; + cmpwi r3, 0; + mr r18, r3; + beq lbl_8001141c; +lbl_80010fcc: + subf r19, r18, r21; + b lbl_80011460; + lbz r0, 0x74(r1); + cmplwi r0, 3; + bne lbl_80010ff4; + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); + b lbl_8001107c; +lbl_80010ff4: + cmplwi r0, 4; + bne lbl_80011014; + mr r3, r27; + li r4, 2; + bl __va_arg; + lwz r30, 0(r3); + lwz r29, 4(r3); + b lbl_8001107c; +lbl_80011014: + cmplwi r0, 6; + bne lbl_80011034; + mr r3, r27; + li r4, 2; + bl __va_arg; + lwz r30, 0(r3); + lwz r29, 4(r3); + b lbl_8001107c; +lbl_80011034: + cmplwi r0, 7; + bne lbl_80011050; + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); + b lbl_8001107c; +lbl_80011050: + cmplwi r0, 8; + bne lbl_8001106c; + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); + b lbl_8001107c; +lbl_8001106c: + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r31, 0(r3); +lbl_8001107c: + lbz r0, 0x74(r1); + cmplwi r0, 2; + bne lbl_8001108c; + clrlwi r31, r31, 0x10; +lbl_8001108c: + cmplwi r0, 1; + bne lbl_80011098; + clrlwi r31, r31, 0x18; +lbl_80011098: + cmplwi r0, 4; + beq lbl_800110a8; + cmplwi r0, 6; + bne lbl_800110ec; +lbl_800110a8: + lwz r9, 0x70(r1); + mr r4, r29; + lwz r8, 0x74(r1); + mr r3, r30; + lwz r7, 0x78(r1); + addi r5, r1, 0x280; + lwz r0, 0x7c(r1); + addi r6, r1, 0x40; + stw r9, 0x40(r1); + stw r8, 0x44(r1); + stw r7, 0x48(r1); + stw r0, 0x4c(r1); + bl unk_8000fe34; + cmpwi r3, 0; + mr r18, r3; + beq lbl_8001141c; + b lbl_80011128; +lbl_800110ec: + lwz r8, 0x70(r1); + mr r3, r31; + lwz r7, 0x74(r1); + addi r4, r1, 0x280; + lwz r6, 0x78(r1); + addi r5, r1, 0x30; + lwz r0, 0x7c(r1); + stw r8, 0x30(r1); + stw r7, 0x34(r1); + stw r6, 0x38(r1); + stw r0, 0x3c(r1); + bl unk_8000fbfc; + cmpwi r3, 0; + mr r18, r3; + beq lbl_8001141c; +lbl_80011128: + subf r19, r18, r21; + b lbl_80011460; + lbz r0, 0x74(r1); + cmplwi r0, 9; + bne lbl_80011150; + mr r3, r27; + li r4, 3; + bl __va_arg; + lfd f1, 0(r3); + b lbl_80011160; +lbl_80011150: + mr r3, r27; + li r4, 3; + bl __va_arg; + lfd f1, 0(r3); +lbl_80011160: + lwz r7, 0x70(r1); + addi r3, r1, 0x280; + lwz r6, 0x74(r1); + addi r4, r1, 0x20; + lwz r5, 0x78(r1); + lwz r0, 0x7c(r1); + stw r7, 0x20(r1); + stw r6, 0x24(r1); + stw r5, 0x28(r1); + stw r0, 0x2c(r1); + bl float2str; + cmpwi r3, 0; + mr r18, r3; + beq lbl_8001141c; + subf r19, r3, r21; + b lbl_80011460; + lbz r0, 0x74(r1); + cmplwi r0, 9; + bne lbl_800111c0; + mr r3, r27; + li r4, 3; + bl __va_arg; + lfd f1, 0(r3); + b lbl_800111d0; +lbl_800111c0: + mr r3, r27; + li r4, 3; + bl __va_arg; + lfd f1, 0(r3); +lbl_800111d0: + lwz r7, 0x70(r1); + addi r3, r1, 0x280; + lwz r6, 0x74(r1); + addi r4, r1, 0x10; + lwz r5, 0x78(r1); + lwz r0, 0x7c(r1); + stw r7, 0x10(r1); + stw r6, 0x14(r1); + stw r5, 0x18(r1); + stw r0, 0x1c(r1); + bl double2hex; + cmpwi r3, 0; + mr r18, r3; + beq lbl_8001141c; + subf r19, r3, r21; + b lbl_80011460; + lbz r0, 0x74(r1); + cmplwi r0, 5; + bne lbl_8001127c; + mr r3, r27; + li r4, 1; + bl __va_arg; + cmpwi r28, 0; + lwz r4, 0(r3); + beq lbl_80011254; + cmpwi r4, 0; + bne lbl_80011254; + li r3, 0; + li r4, 0; + li r5, -1; + bl unk_80013108; + li r3, -1; + b lbl_8001160c; +lbl_80011254: + cmpwi r4, 0; + bne lbl_80011260; + addi r4, r13, -32720; +lbl_80011260: + addi r3, r1, 0x80; + li r5, 0x200; + bl unk_8000f138; + cmpwi r3, 0; + blt lbl_8001141c; + addi r18, r1, 0x80; + b lbl_8001128c; +lbl_8001127c: + mr r3, r27; + li r4, 1; + bl __va_arg; + lwz r18, 0(r3); +lbl_8001128c: + cmpwi r28, 0; + beq lbl_800112b4; + cmpwi r18, 0; + bne lbl_800112b4; + li r3, 0; + li r4, 0; + li r5, -1; + bl unk_80013108; + li r3, -1; + b lbl_8001160c; +lbl_800112b4: + cmpwi r18, 0; + bne lbl_800112c0; + addi r18, r22, 0x24; +lbl_800112c0: + lbz r0, 0x73(r1); + cmpwi r0, 0; + beq lbl_800112f4; + lbz r0, 0x72(r1); + lbz r19, 0(r18); + addi r18, r18, 1; + cmpwi r0, 0; + beq lbl_80011460; + lwz r0, 0x7c(r1); + cmpw r19, r0; + ble lbl_80011460; + mr r19, r0; + b lbl_80011460; +lbl_800112f4: + lbz r0, 0x72(r1); + cmpwi r0, 0; + beq lbl_80011324; + lwz r19, 0x7c(r1); + mr r3, r18; + li r4, 0; + mr r5, r19; + bl memchr; + cmpwi r3, 0; + beq lbl_80011460; + subf r19, r18, r3; + b lbl_80011460; +lbl_80011324: + mr r3, r18; + bl strlen; + mr r19, r3; + b lbl_80011460; + cmpwi r28, 0; + beq lbl_80011354; + li r3, 0; + li r4, 0; + li r5, -1; + bl unk_80013108; + li r3, -1; + b lbl_8001160c; +lbl_80011354: + mr r3, r27; + li r4, 1; + bl __va_arg; + lbz r0, 0x74(r1); + lwz r3, 0(r3); + cmpwi r0, 0; + beq lbl_800113a4; + cmpwi r0, 2; + beq lbl_800113ac; + cmpwi r0, 3; + beq lbl_800113b4; + cmpwi r0, 6; + beq lbl_800113bc; + cmpwi r0, 7; + beq lbl_800113cc; + cmpwi r0, 8; + beq lbl_800113d4; + cmpwi r0, 4; + beq lbl_800113dc; + b lbl_800115fc; +lbl_800113a4: + stw r17, 0(r3); + b lbl_800115fc; +lbl_800113ac: + sth r17, 0(r3); + b lbl_800115fc; +lbl_800113b4: + stw r17, 0(r3); + b lbl_800115fc; +lbl_800113bc: + stw r17, 4(r3); + srawi r0, r17, 0x1f; + stw r0, 0(r3); + b lbl_800115fc; +lbl_800113cc: + stw r17, 0(r3); + b lbl_800115fc; +lbl_800113d4: + stw r17, 0(r3); + b lbl_800115fc; +lbl_800113dc: + stw r17, 4(r3); + srawi r0, r17, 0x1f; + stw r0, 0(r3); + b lbl_800115fc; + mr r3, r27; + addi r18, r1, 0x80; + li r4, 1; + bl __va_arg; + lwz r0, 0(r3); + li r19, 1; + stb r0, 0x80(r1); + b lbl_80011460; + stb r23, 0x80(r1); + addi r18, r1, 0x80; + li r19, 1; + b lbl_80011460; +lbl_8001141c: + mr r3, r16; + bl strlen; + cmpwi r3, 0; + mr r5, r3; + add r17, r17, r3; + beq lbl_80011458; + mr r12, r25; + mr r3, r26; + mr r4, r16; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_80011458; + li r3, -1; + b lbl_8001160c; +lbl_80011458: + mr r3, r17; + b lbl_8001160c; +lbl_80011460: + lbz r0, 0x70(r1); + mr r16, r19; + cmpwi r0, 0; + beq lbl_8001157c; + cmplwi r0, 2; + li r3, 0x20; + bne lbl_80011480; + li r3, 0x30; +lbl_80011480: + stb r3, 9(r1); + lbz r0, 0(r18); + extsb r0, r0; + cmpwi r0, 0x2b; + beq lbl_800114a4; + cmpwi r0, 0x2d; + beq lbl_800114a4; + cmpwi r0, 0x20; + bne lbl_800114e0; +lbl_800114a4: + extsb r0, r3; + cmpwi r0, 0x30; + bne lbl_800114e0; + mr r12, r25; + mr r3, r26; + mr r4, r18; + li r5, 1; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_800114d8; + li r3, -1; + b lbl_8001160c; +lbl_800114d8: + addi r18, r18, 1; + addi r19, r19, -1; +lbl_800114e0: + lbz r0, 0x70(r1); + cmplwi r0, 2; + bne lbl_80011570; + lbz r0, 0x75(r1); + cmplwi r0, 0x61; + beq lbl_80011500; + cmplwi r0, 0x41; + bne lbl_80011570; +lbl_80011500: + cmpwi r19, 2; + bge lbl_80011510; + li r3, -1; + b lbl_8001160c; +lbl_80011510: + mr r12, r25; + mr r3, r26; + mr r4, r18; + li r5, 2; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_80011538; + li r3, -1; + b lbl_8001160c; +lbl_80011538: + addi r19, r19, -2; + addi r18, r18, 2; + b lbl_80011570; +lbl_80011544: + mr r12, r25; + mr r3, r26; + addi r4, r1, 9; + li r5, 1; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_8001156c; + li r3, -1; + b lbl_8001160c; +lbl_8001156c: + addi r16, r16, 1; +lbl_80011570: + lwz r0, 0x78(r1); + cmpw r16, r0; + blt lbl_80011544; +lbl_8001157c: + cmpwi r19, 0; + beq lbl_800115ac; + mr r12, r25; + mr r3, r26; + mr r4, r18; + mr r5, r19; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_800115ac; + li r3, -1; + b lbl_8001160c; +lbl_800115ac: + lbz r0, 0x70(r1); + cmpwi r0, 0; + bne lbl_800115f8; + b lbl_800115ec; +lbl_800115bc: + mr r12, r25; + mr r3, r26; + stb r24, 8(r1); + addi r4, r1, 8; + li r5, 1; + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_800115e8; + li r3, -1; + b lbl_8001160c; +lbl_800115e8: + addi r16, r16, 1; +lbl_800115ec: + lwz r0, 0x78(r1); + cmpw r16, r0; + blt lbl_800115bc; +lbl_800115f8: + add r17, r17, r16; +lbl_800115fc: + lbz r0, 0(r15); + extsb. r0, r0; + bne lbl_80010dbc; +lbl_80011608: + mr r3, r17; +lbl_8001160c: + lmw r15, 0x28c(r1); + lwz r0, 0x2d4(r1); + mtlr r0; + addi r1, r1, 0x2d0; + blr; + // clang-format on +} + +// Symbol: __FileWrite +// Function signature is unknown. +// PAL: 0x80011620..0x80011678 +MARK_BINARY_BLOB(__FileWrite, 0x80011620, 0x80011678); +asm UNKNOWN_FUNCTION(__FileWrite) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r5; + stw r30, 8(r1); + mr r30, r3; + mr r3, r4; + li r4, 1; + mr r6, r30; + bl unk_8000e954; + cmplw r31, r3; + bne lbl_80011658; + b lbl_8001165c; +lbl_80011658: + li r30, 0; +lbl_8001165c: + mr r3, r30; + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __StringWrite +// Function signature is unknown. +// PAL: 0x80011678..0x800116e4 +MARK_BINARY_BLOB(__StringWrite, 0x80011678, 0x800116e4); +asm UNKNOWN_FUNCTION(__StringWrite) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + lwz r6, 8(r3); + lwz r7, 4(r3); + add r0, r6, r5; + cmplw r0, r7; + subf r31, r6, r7; + bgt lbl_800116ac; + mr r31, r5; +lbl_800116ac: + lwz r0, 0(r3); + mr r5, r31; + add r3, r0, r6; + bl memcpy; + lwz r0, 8(r30); + li r3, 1; + add r0, r0, r31; + stw r0, 8(r30); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: printf +// Function signature is unknown. +// PAL: 0x800116e4..0x800117b0 +MARK_BINARY_BLOB(printf, 0x800116e4, 0x800117b0); +asm UNKNOWN_FUNCTION(printf) { + // clang-format off + nofralloc; + stwu r1, -0x80(r1); + mflr r0; + stw r0, 0x84(r1); + stw r31, 0x7c(r1); + stw r30, 0x78(r1); + mr r30, r3; + bne cr1, lbl_80011720; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_80011720: + lis r31, 0x8027; + stw r4, 0xc(r1); + addi r31, r31, 0xcf0; + li r4, -1; + stw r3, 8(r1); + addi r3, r31, 0x50; + stw r5, 0x10(r1); + stw r6, 0x14(r1); + stw r7, 0x18(r1); + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + bl unk_800155d8; + cmpwi r3, 0; + blt lbl_80011764; + li r3, -1; + b lbl_80011798; +lbl_80011764: + addi r4, r1, 0x88; + addi r0, r1, 8; + lis r5, 0x100; + lis r3, 0x8001; + stw r5, 0x68(r1); + addi r6, r1, 0x68; + mr r5, r30; + addi r3, r3, 0x1620; + stw r4, 0x6c(r1); + addi r4, r31, 0x50; + li r7, 0; + stw r0, 0x70(r1); + bl __pformatter; +lbl_80011798: + lwz r0, 0x84(r1); + lwz r31, 0x7c(r1); + lwz r30, 0x78(r1); + mtlr r0; + addi r1, r1, 0x80; + blr; + // clang-format on +} + +// Symbol: vprintf +// Function signature is unknown. +// PAL: 0x800117b0..0x8001182c +MARK_BINARY_BLOB(vprintf, 0x800117b0, 0x8001182c); +asm UNKNOWN_FUNCTION(vprintf) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + lis r31, 0x8027; + addi r31, r31, 0xcf0; + stw r30, 0x18(r1); + mr r30, r4; + li r4, -1; + stw r29, 0x14(r1); + mr r29, r3; + addi r3, r31, 0x50; + bl unk_800155d8; + cmpwi r3, 0; + blt lbl_800117f4; + li r3, -1; + b lbl_80011810; +lbl_800117f4: + lis r3, 0x8001; + mr r5, r29; + mr r6, r30; + addi r4, r31, 0x50; + addi r3, r3, 0x1620; + li r7, 0; + bl __pformatter; +lbl_80011810: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: vsnprintf +// PAL: 0x8001182c..0x800118b4 +MARK_BINARY_BLOB(vsnprintf, 0x8001182c, 0x800118b4); +asm int vsnprintf(char* s, size_t n, const char* format, va_list arg) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + lis r7, 0x8001; + stw r0, 0x34(r1); + stw r31, 0x2c(r1); + li r31, 0; + stw r30, 0x28(r1); + mr r30, r4; + stw r29, 0x24(r1); + mr r29, r3; + stw r3, 8(r1); + addi r3, r7, 0x1678; + li r7, 0; + stw r4, 0xc(r1); + addi r4, r1, 8; + stw r31, 0x10(r1); + bl __pformatter; + cmpwi r29, 0; + beq lbl_80011898; + cmplw r3, r30; + bge lbl_80011888; + stbx r31, r29, r3; + b lbl_80011898; +lbl_80011888: + cmpwi r30, 0; + beq lbl_80011898; + add r4, r29, r30; + stb r31, -1(r4); +lbl_80011898: + lwz r0, 0x34(r1); + lwz r31, 0x2c(r1); + lwz r30, 0x28(r1); + lwz r29, 0x24(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: vsprintf +// PAL: 0x800118b4..0x80011938 +MARK_BINARY_BLOB(vsprintf, 0x800118b4, 0x80011938); +asm int vsprintf(char* s, const char* format, va_list arg) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + mr r6, r5; + mr r5, r4; + stw r0, 0x34(r1); + lis r7, 0x8001; + addi r4, r1, 8; + stw r31, 0x2c(r1); + li r31, 0; + stw r30, 0x28(r1); + li r30, -1; + stw r29, 0x24(r1); + mr r29, r3; + stw r3, 8(r1); + addi r3, r7, 0x1678; + li r7, 0; + stw r30, 0xc(r1); + stw r31, 0x10(r1); + bl __pformatter; + cmpwi r29, 0; + beq lbl_8001191c; + cmplw r3, r30; + bge lbl_80011918; + stbx r31, r29, r3; + b lbl_8001191c; +lbl_80011918: + stb r31, -2(r29); +lbl_8001191c: + lwz r0, 0x34(r1); + lwz r31, 0x2c(r1); + lwz r30, 0x28(r1); + lwz r29, 0x24(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: snprintf +// PAL: 0x80011938..0x80011a2c +MARK_BINARY_BLOB(snprintf, 0x80011938, 0x80011a2c); +asm int snprintf(char* s, size_t n, const char* format, ...) { + // clang-format off + nofralloc; + stwu r1, -0x90(r1); + mflr r0; + stw r0, 0x94(r1); + stw r31, 0x8c(r1); + stw r30, 0x88(r1); + stw r29, 0x84(r1); + mr r29, r4; + stw r28, 0x80(r1); + mr r28, r3; + bne cr1, lbl_80011980; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_80011980: + addi r12, r1, 0x98; + addi r0, r1, 8; + lis r30, 0x300; + stw r7, 0x18(r1); + li r31, 0; + addi r7, r1, 0x74; + stw r6, 0x14(r1); + mr r6, r7; + lis r11, 0x8001; + li r7, 0; + stw r5, 0x10(r1); + stw r3, 8(r1); + stw r3, 0x68(r1); + addi r3, r11, 0x1678; + stw r4, 0xc(r1); + stw r4, 0x6c(r1); + addi r4, r1, 0x68; + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + stw r30, 0x74(r1); + stw r12, 0x78(r1); + stw r0, 0x7c(r1); + stw r31, 0x70(r1); + bl __pformatter; + cmpwi r28, 0; + beq lbl_80011a0c; + cmplw r3, r29; + bge lbl_800119fc; + stbx r31, r28, r3; + b lbl_80011a0c; +lbl_800119fc: + cmpwi r29, 0; + beq lbl_80011a0c; + add r4, r28, r29; + stb r31, -1(r4); +lbl_80011a0c: + lwz r0, 0x94(r1); + lwz r31, 0x8c(r1); + lwz r30, 0x88(r1); + lwz r29, 0x84(r1); + lwz r28, 0x80(r1); + mtlr r0; + addi r1, r1, 0x90; + blr; + // clang-format on +} + +// Symbol: sprintf +// PAL: 0x80011a2c..0x80011b00 +MARK_BINARY_BLOB(sprintf, 0x80011a2c, 0x80011b00); +asm int sprintf(char* str, const char* format, ...) { + // clang-format off + nofralloc; + stwu r1, -0xa0(r1); + mflr r0; + stw r0, 0xa4(r1); + stmw r27, 0x8c(r1); + mr r27, r3; + bne cr1, lbl_80011a64; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_80011a64: + addi r12, r1, 0xa8; + addi r0, r1, 8; + lis r29, 0x200; + stw r5, 0x10(r1); + li r30, -1; + li r31, 0; + stw r7, 0x18(r1); + addi r28, r1, 0x74; + lis r11, 0x8001; + mr r5, r4; + stw r4, 0xc(r1); + addi r4, r1, 0x68; + li r7, 0; + stw r6, 0x14(r1); + mr r6, r28; + stw r3, 8(r1); + stw r3, 0x68(r1); + addi r3, r11, 0x1678; + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + stw r29, 0x74(r1); + stw r12, 0x78(r1); + stw r0, 0x7c(r1); + stw r30, 0x6c(r1); + stw r31, 0x70(r1); + bl __pformatter; + cmpwi r27, 0; + beq lbl_80011aec; + cmplw r3, r30; + bge lbl_80011ae8; + stbx r31, r27, r3; + b lbl_80011aec; +lbl_80011ae8: + stb r31, -2(r27); +lbl_80011aec: + lmw r27, 0x8c(r1); + lwz r0, 0xa4(r1); + mtlr r0; + addi r1, r1, 0xa0; + blr; + // clang-format on +} diff --git a/source/platform/printf.h b/source/platform/printf.h new file mode 100644 index 000000000..23607b253 --- /dev/null +++ b/source/platform/printf.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x800100e4..0x800104b4 +UNKNOWN_FUNCTION(double2hex); +// PAL: 0x800104b4..0x800105dc +UNKNOWN_FUNCTION(round_decimal); +// PAL: 0x800105dc..0x80010d74 +UNKNOWN_FUNCTION(float2str); +// PAL: 0x80010d74..0x80011620 +UNKNOWN_FUNCTION(__pformatter); +// PAL: 0x80011620..0x80011678 +UNKNOWN_FUNCTION(__FileWrite); +// PAL: 0x80011678..0x800116e4 +UNKNOWN_FUNCTION(__StringWrite); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/platform/qsort.c b/source/platform/qsort.c new file mode 100644 index 000000000..89f473d6c --- /dev/null +++ b/source/platform/qsort.c @@ -0,0 +1,116 @@ +#include "stdlib.h" + +#include + +// Symbol: qsort +// PAL: 0x80011b00..0x80011c70 +MARK_BINARY_BLOB(qsort, 0x80011b00, 0x80011c70); +asm void qsort(void* base, size_t nitems, size_t size, + int (*compar)(const void*, const void*)) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + mflr r0; + cmplwi r4, 2; + stw r0, 0x44(r1); + stmw r19, 0xc(r1); + mr r27, r3; + mr r28, r5; + mr r29, r6; + blt lbl_80011c5c; + srwi r7, r4, 1; + slwi r0, r5, 1; + addi r31, r7, 1; + addi r6, r4, -1; + addi r7, r31, -1; + mr r30, r4; + mullw r4, r5, r7; + subf r25, r0, r5; + mullw r0, r5, r6; + add r23, r3, r4; + mullw r26, r31, r5; + add r22, r3, r0; +lbl_80011b54: + cmplwi r31, 1; + ble lbl_80011b6c; + subf r26, r28, r26; + subf r23, r28, r23; + addi r31, r31, -1; + b lbl_80011ba8; +lbl_80011b6c: + addi r3, r22, -1; + addi r4, r23, -1; + addi r5, r28, 1; + b lbl_80011b90; +lbl_80011b7c: + lbz r6, 1(r4); + lbz r0, 1(r3); + stbu r0, 1(r4); + extsb r6, r6; + stbu r6, 1(r3); +lbl_80011b90: + addic. r5, r5, -1; + bne lbl_80011b7c; + addi r30, r30, -1; + cmplwi r30, 1; + beq lbl_80011c5c; + subf r22, r28, r22; +lbl_80011ba8: + add r0, r26, r25; + mr r24, r31; + add r20, r27, r0; + b lbl_80011c4c; +lbl_80011bb8: + slwi r24, r24, 1; + mr r21, r20; + addi r0, r24, -1; + mullw r0, r28, r0; + cmplw r24, r30; + add r20, r27, r0; + bge lbl_80011bfc; + add r19, r20, r28; + mr r12, r29; + mr r3, r20; + mr r4, r19; + mtctr r12; + bctrl; + cmpwi r3, 0; + bge lbl_80011bfc; + mr r20, r19; + addi r24, r24, 1; +lbl_80011bfc: + mr r12, r29; + mr r3, r21; + mr r4, r20; + mtctr r12; + bctrl; + cmpwi r3, 0; + bge lbl_80011b54; + addi r3, r20, -1; + addi r4, r21, -1; + addi r5, r28, 1; + b lbl_80011c44; +lbl_80011c28: + lbz r6, 1(r4); + lbz r0, 1(r3); + stb r0, 1(r4); + extsb r6, r6; + addi r4, r4, 1; + stb r6, 1(r3); + addi r3, r3, 1; +lbl_80011c44: + addic. r5, r5, -1; + bne lbl_80011c28; +lbl_80011c4c: + slwi r0, r24, 1; + cmplw r0, r30; + ble lbl_80011bb8; + b lbl_80011b54; +lbl_80011c5c: + lmw r19, 0xc(r1); + lwz r0, 0x44(r1); + mtlr r0; + addi r1, r1, 0x40; + blr; + // clang-format on +} diff --git a/source/platform/rand.c b/source/platform/rand.c new file mode 100644 index 000000000..b4b99f3e2 --- /dev/null +++ b/source/platform/rand.c @@ -0,0 +1,12 @@ +#include "stdlib.h" + +#include + +static u32 rand_state = 1; + +int rand(void) { + rand_state = rand_state * 0x41c64e6d + 0x3039; + return (int)((rand_state & 0x7fff0000) >> 0x10); +} + +void srand(unsigned int seed) { rand_state = seed; } diff --git a/source/platform/scanf.c b/source/platform/scanf.c new file mode 100644 index 000000000..5a53388ae --- /dev/null +++ b/source/platform/scanf.c @@ -0,0 +1,1523 @@ +#include +#include + +#include "va_arg.h" + +// Extern function references. +// PAL: 0x8000eff8 +extern UNKNOWN_FUNCTION(unk_8000eff8); +// PAL: 0x8001365c +extern UNKNOWN_FUNCTION(unk_8001365c); +// PAL: 0x80014e00 +extern UNKNOWN_FUNCTION(unk_80014e00); + +// Local functions. +// PAL: 0x80011c98..0x80012320 +UNKNOWN_FUNCTION(parse_format); +// PAL: 0x80012320..0x80012fb8 +UNKNOWN_FUNCTION(__sformatter); +// PAL: 0x80012fb8..0x80013040 +UNKNOWN_FUNCTION(__StringRead); + +// Symbol: parse_format +// Function signature is unknown. +// PAL: 0x80011c98..0x80012320 +MARK_BINARY_BLOB(parse_format, 0x80011c98, 0x80012320); +asm UNKNOWN_FUNCTION(parse_format) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + lis r6, 0x8024; + lbzu r5, 1(r3); + stw r31, 0x3c(r1); + extsb r5, r5; + stw r30, 0x38(r1); + cmpwi r5, 0x25; + lwzu r0, 0x6650(r6); + stw r0, 8(r1); + lwz r30, 4(r6); + lwz r31, 8(r6); + lwz r12, 0xc(r6); + lwz r11, 0x10(r6); + lwz r10, 0x14(r6); + lwz r9, 0x18(r6); + lwz r8, 0x1c(r6); + lwz r7, 0x20(r6); + lwz r6, 0x24(r6); + stw r30, 0xc(r1); + stw r31, 0x10(r1); + stw r12, 0x14(r1); + stw r11, 0x18(r1); + stw r10, 0x1c(r1); + stw r9, 0x20(r1); + stw r8, 0x24(r1); + stw r7, 0x28(r1); + stw r6, 0x2c(r1); + bne lbl_80011d40; + stb r5, 0xb(r1); + addi r3, r3, 1; + lwz r0, 8(r1); + stw r0, 0(r4); + stw r30, 4(r4); + stw r31, 8(r4); + stw r12, 0xc(r4); + stw r11, 0x10(r4); + stw r10, 0x14(r4); + stw r9, 0x18(r4); + stw r8, 0x1c(r4); + stw r7, 0x20(r4); + stw r6, 0x24(r4); + b lbl_80012310; +lbl_80011d40: + cmpwi r5, 0x2a; + bne lbl_80011d58; + lbzu r5, 1(r3); + li r0, 1; + stb r0, 8(r1); + extsb r5, r5; +lbl_80011d58: + cmplwi r5, 0xff; + li r0, 1; + bgt lbl_80011d68; + li r0, 0; +lbl_80011d68: + cmpwi r0, 0; + beq lbl_80011d78; + li r0, 0; + b lbl_80011d94; +lbl_80011d78: + lis r6, 0x8027; + slwi r0, r5, 1; + addi r6, r6, 0x1148; + lwz r6, 0x38(r6); + lwz r6, 8(r6); + lhzx r0, r6, r0; + rlwinm r0, r0, 0, 0x1c, 0x1c; +lbl_80011d94: + cmpwi r0, 0; + beq lbl_80011e74; + lis r6, 0x8027; + li r0, 0; + addi r6, r6, 0x1148; + stw r0, 0xc(r1); + lwz r7, 0x38(r6); +lbl_80011db0: + lwz r0, 0xc(r1); + li r8, 1; + mulli r0, r0, 0xa; + add r6, r5, r0; + lbzu r5, 1(r3); + addi r0, r6, -48; + stw r0, 0xc(r1); + extsb r5, r5; + cmplwi r5, 0xff; + bgt lbl_80011ddc; + li r8, 0; +lbl_80011ddc: + cmpwi r8, 0; + beq lbl_80011dec; + li r0, 0; + b lbl_80011dfc; +lbl_80011dec: + lwz r6, 8(r7); + slwi r0, r5, 1; + lhzx r0, r6, r0; + rlwinm r0, r0, 0, 0x1c, 0x1c; +lbl_80011dfc: + cmpwi r0, 0; + bne lbl_80011db0; + lwz r6, 0xc(r1); + cmpwi r6, 0; + bne lbl_80011e6c; + li r0, 0xff; + stb r0, 0xb(r1); + addi r3, r3, 1; + lwz r0, 8(r1); + stw r0, 0(r4); + stw r6, 4(r4); + lwz r5, 0x10(r1); + lwz r0, 0x14(r1); + stw r0, 0xc(r4); + stw r5, 8(r4); + lwz r5, 0x18(r1); + lwz r0, 0x1c(r1); + stw r0, 0x14(r4); + stw r5, 0x10(r4); + lwz r5, 0x20(r1); + lwz r0, 0x24(r1); + stw r0, 0x1c(r4); + stw r5, 0x18(r4); + lwz r5, 0x28(r1); + lwz r0, 0x2c(r1); + stw r0, 0x24(r4); + stw r5, 0x20(r4); + b lbl_80012310; +lbl_80011e6c: + li r0, 1; + stb r0, 9(r1); +lbl_80011e74: + cmpwi r5, 0x68; + li r7, 1; + beq lbl_80011eac; + cmpwi r5, 0x6c; + beq lbl_80011ed8; + cmpwi r5, 0x4c; + beq lbl_80011f04; + cmpwi r5, 0x6a; + beq lbl_80011f10; + cmpwi r5, 0x7a; + beq lbl_80011f1c; + cmpwi r5, 0x74; + beq lbl_80011f28; + b lbl_80011f34; +lbl_80011eac: + lbz r0, 1(r3); + li r6, 2; + stb r6, 0xa(r1); + extsb r6, r0; + cmpwi r6, 0x68; + bne lbl_80011f38; + li r0, 1; + stb r0, 0xa(r1); + mr r5, r6; + addi r3, r3, 1; + b lbl_80011f38; +lbl_80011ed8: + lbz r0, 1(r3); + li r6, 3; + stb r6, 0xa(r1); + extsb r6, r0; + cmpwi r6, 0x6c; + bne lbl_80011f38; + li r0, 7; + stb r0, 0xa(r1); + mr r5, r6; + addi r3, r3, 1; + b lbl_80011f38; +lbl_80011f04: + li r0, 9; + stb r0, 0xa(r1); + b lbl_80011f38; +lbl_80011f10: + li r0, 4; + stb r0, 0xa(r1); + b lbl_80011f38; +lbl_80011f1c: + li r0, 5; + stb r0, 0xa(r1); + b lbl_80011f38; +lbl_80011f28: + li r0, 6; + stb r0, 0xa(r1); + b lbl_80011f38; +lbl_80011f34: + li r7, 0; +lbl_80011f38: + cmpwi r7, 0; + beq lbl_80011f48; + lbzu r5, 1(r3); + extsb r5, r5; +lbl_80011f48: + addi r0, r5, -65; + stb r5, 0xb(r1); + cmplwi r0, 0x37; + bgt lbl_800122b4; + lis r5, 0x8027; + slwi r0, r0, 2; + addi r5, r5, 0x13c0; + lwzx r5, r5, r0; + mtctr r5; + bctr; + lbz r0, 0xa(r1); + cmplwi r0, 9; + bne lbl_800122bc; + li r0, 0xff; + stb r0, 0xb(r1); + b lbl_800122bc; + lbz r5, 0xa(r1); + addi r0, r5, 0xfc; + clrlwi r0, r0, 0x18; + cmplwi r0, 3; + ble lbl_80011fb8; + addi r0, r5, 0xff; + clrlwi r0, r0, 0x18; + cmplwi r0, 1; + ble lbl_80011fb8; + cmplwi r5, 3; + beq lbl_80011fc4; + b lbl_800122bc; +lbl_80011fb8: + li r0, 0xff; + stb r0, 0xb(r1); + b lbl_800122bc; +lbl_80011fc4: + li r0, 8; + stb r0, 0xa(r1); + b lbl_800122bc; + li r5, 3; + li r0, 0x78; + stb r5, 0xa(r1); + stb r0, 0xb(r1); + b lbl_800122bc; + lbz r0, 0xa(r1); + cmplwi r0, 3; + bne lbl_80011ffc; + li r0, 0xa; + stb r0, 0xa(r1); + b lbl_800122bc; +lbl_80011ffc: + cmpwi r0, 0; + beq lbl_800122bc; + li r0, 0xff; + stb r0, 0xb(r1); + b lbl_800122bc; + lbz r0, 0xa(r1); + cmplwi r0, 3; + bne lbl_80012028; + li r0, 0xa; + stb r0, 0xa(r1); + b lbl_80012038; +lbl_80012028: + cmpwi r0, 0; + beq lbl_80012038; + li r0, 0xff; + stb r0, 0xb(r1); +lbl_80012038: + li r6, 0xff; + li r5, 0xc1; + li r0, 0xfe; + stb r6, 0x10(r1); + stb r6, 0x12(r1); + stb r6, 0x13(r1); + stb r6, 0x15(r1); + stb r6, 0x16(r1); + stb r6, 0x17(r1); + stb r6, 0x18(r1); + stb r6, 0x19(r1); + stb r6, 0x1a(r1); + stb r6, 0x1b(r1); + stb r6, 0x1c(r1); + stb r6, 0x1d(r1); + stb r6, 0x1e(r1); + stb r6, 0x1f(r1); + stb r6, 0x20(r1); + stb r6, 0x21(r1); + stb r6, 0x22(r1); + stb r6, 0x23(r1); + stb r6, 0x24(r1); + stb r6, 0x25(r1); + stb r6, 0x26(r1); + stb r6, 0x27(r1); + stb r6, 0x28(r1); + stb r6, 0x29(r1); + stb r6, 0x2a(r1); + stb r6, 0x2b(r1); + stb r6, 0x2c(r1); + stb r6, 0x2d(r1); + stb r6, 0x2e(r1); + stb r6, 0x2f(r1); + stb r5, 0x11(r1); + stb r0, 0x14(r1); + b lbl_800122bc; + lbz r0, 0xa(r1); + cmplwi r0, 3; + bne lbl_800120e0; + li r0, 0xa; + stb r0, 0xa(r1); + b lbl_800120f0; +lbl_800120e0: + cmpwi r0, 0; + beq lbl_800120f0; + li r0, 0xff; + stb r0, 0xb(r1); +lbl_800120f0: + lbzu r10, 1(r3); + li r11, 0; + extsb r10, r10; + cmpwi r10, 0x5e; + bne lbl_80012110; + lbzu r10, 1(r3); + li r11, 1; + extsb r10, r10; +lbl_80012110: + cmpwi r10, 0x5d; + bne lbl_8001212c; + lbz r0, 0x1b(r1); + lbzu r10, 1(r3); + ori r0, r0, 0x20; + stb r0, 0x1b(r1); + extsb r10, r10; +lbl_8001212c: + addi r8, r1, 8; + li r5, 1; + b lbl_800121b4; +lbl_80012138: + rlwinm r6, r10, 0x1d, 0x1b, 0x1f; + lbz r0, 1(r3); + add r9, r8, r6; + clrlwi r6, r10, 0x1d; + lbz r7, 8(r9); + slw r6, r5, r6; + cmpwi r0, 0x2d; + or r6, r7, r6; + stb r6, 8(r9); + bne lbl_800121ac; + lbz r9, 2(r3); + extsb. r9, r9; + beq lbl_800121ac; + cmpwi r9, 0x5d; + beq lbl_800121ac; + b lbl_80012194; +lbl_80012178: + rlwinm r6, r10, 0x1d, 0x1b, 0x1f; + clrlwi r0, r10, 0x1d; + add r7, r8, r6; + lbz r6, 8(r7); + slw r0, r5, r0; + or r0, r6, r0; + stb r0, 8(r7); +lbl_80012194: + addi r10, r10, 1; + cmpw r10, r9; + ble lbl_80012178; + lbzu r10, 3(r3); + extsb r10, r10; + b lbl_800121b4; +lbl_800121ac: + lbzu r10, 1(r3); + extsb r10, r10; +lbl_800121b4: + cmpwi r10, 0; + beq lbl_800121c8; + cmpwi r10, 0x5d; + beq lbl_800121d4; + b lbl_80012138; +lbl_800121c8: + li r0, 0xff; + stb r0, 0xb(r1); + b lbl_800122bc; +lbl_800121d4: + cmpwi r11, 0; + beq lbl_800122bc; + li r0, 2; + addi r5, r1, 0x10; + mtctr r0; +lbl_800121e8: + lbz r0, 0(r5); + nor r0, r0, r0; + stb r0, 0(r5); + lbz r0, 1(r5); + nor r0, r0, r0; + stb r0, 1(r5); + lbz r0, 2(r5); + nor r0, r0, r0; + stb r0, 2(r5); + lbz r0, 3(r5); + nor r0, r0, r0; + stb r0, 3(r5); + lbz r0, 4(r5); + nor r0, r0, r0; + stb r0, 4(r5); + lbz r0, 5(r5); + nor r0, r0, r0; + stb r0, 5(r5); + lbz r0, 6(r5); + nor r0, r0, r0; + stb r0, 6(r5); + lbz r0, 7(r5); + nor r0, r0, r0; + stb r0, 7(r5); + lbz r0, 8(r5); + nor r0, r0, r0; + stb r0, 8(r5); + lbz r0, 9(r5); + nor r0, r0, r0; + stb r0, 9(r5); + lbz r0, 0xa(r5); + nor r0, r0, r0; + stb r0, 0xa(r5); + lbz r0, 0xb(r5); + nor r0, r0, r0; + stb r0, 0xb(r5); + lbz r0, 0xc(r5); + nor r0, r0, r0; + stb r0, 0xc(r5); + lbz r0, 0xd(r5); + nor r0, r0, r0; + stb r0, 0xd(r5); + lbz r0, 0xe(r5); + nor r0, r0, r0; + stb r0, 0xe(r5); + lbz r0, 0xf(r5); + nor r0, r0, r0; + stb r0, 0xf(r5); + addi r5, r5, 0x10; + bdnz lbl_800121e8; + b lbl_800122bc; +lbl_800122b4: + li r0, 0xff; + stb r0, 0xb(r1); +lbl_800122bc: + lwz r5, 8(r1); + addi r3, r3, 1; + lwz r0, 0xc(r1); + stw r0, 4(r4); + stw r5, 0(r4); + lwz r5, 0x10(r1); + lwz r0, 0x14(r1); + stw r0, 0xc(r4); + stw r5, 8(r4); + lwz r5, 0x18(r1); + lwz r0, 0x1c(r1); + stw r0, 0x14(r4); + stw r5, 0x10(r4); + lwz r5, 0x20(r1); + lwz r0, 0x24(r1); + stw r0, 0x1c(r4); + stw r5, 0x18(r4); + lwz r5, 0x28(r1); + lwz r0, 0x2c(r1); + stw r0, 0x24(r4); + stw r5, 0x20(r4); +lbl_80012310: + lwz r31, 0x3c(r1); + lwz r30, 0x38(r1); + addi r1, r1, 0x40; + blr; + // clang-format on +} + +// Symbol: __sformatter +// Function signature is unknown. +// PAL: 0x80012320..0x80012fb8 +MARK_BINARY_BLOB(__sformatter, 0x80012320, 0x80012fb8); +asm UNKNOWN_FUNCTION(__sformatter) { + // clang-format off + nofralloc; + stwu r1, -0xb0(r1); + mflr r0; + stw r0, 0xb4(r1); + addi r11, r1, 0xb0; + bl _savegpr_15; + lis r8, 0x8027; + li r0, 0; + stw r0, 0x5c(r1); + li r0, 0; + mr r26, r3; + mr r27, r4; + stw r6, 8(r1); + mr r17, r5; + mr r28, r7; + addi r25, r1, 0x20; + stw r0, 0x58(r1); + addi r23, r8, 0x1148; + li r29, 0; + li r21, 0; + li r20, 0; + li r19, 0; + li r24, 1; + b lbl_80012f60; +lbl_8001237c: + cmplwi r22, 0xff; + li r0, 1; + bgt lbl_8001238c; + li r0, 0; +lbl_8001238c: + cmpwi r0, 0; + beq lbl_8001239c; + li r0, 0; + b lbl_800123b0; +lbl_8001239c: + lwz r3, 0x38(r23); + slwi r0, r22, 1; + lwz r3, 8(r3); + lhzx r0, r3, r0; + rlwinm r0, r0, 0, 0x17, 0x17; +lbl_800123b0: + cmpwi r0, 0; + beq lbl_80012488; + lwz r4, 0x38(r23); +lbl_800123bc: + lbzu r0, 1(r17); + li r3, 1; + extsb r0, r0; + cmplwi r0, 0xff; + bgt lbl_800123d4; + li r3, 0; +lbl_800123d4: + cmpwi r3, 0; + beq lbl_800123e4; + li r0, 0; + b lbl_800123f4; +lbl_800123e4: + lwz r3, 8(r4); + slwi r0, r0, 1; + lhzx r0, r3, r0; + rlwinm r0, r0, 0, 0x17, 0x17; +lbl_800123f4: + cmpwi r0, 0; + bne lbl_800123bc; + cmpwi r29, 0; + bne lbl_80012f60; + b lbl_8001240c; +lbl_80012408: + addi r21, r21, 1; +lbl_8001240c: + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + extsb r0, r3; + stb r3, 0xc(r1); + cmplwi r0, 0xff; + li r4, 1; + bgt lbl_8001243c; + li r4, 0; +lbl_8001243c: + cmpwi r4, 0; + beq lbl_8001244c; + li r0, 0; + b lbl_80012460; +lbl_8001244c: + lwz r4, 0x38(r23); + slwi r0, r0, 1; + lwz r4, 8(r4); + lhzx r0, r4, r0; + rlwinm r0, r0, 0, 0x17, 0x17; +lbl_80012460: + cmpwi r0, 0; + bne lbl_80012408; + clrlwi r4, r3, 0x18; + mr r12, r26; + mr r3, r27; + li r5, 1; + extsb r4, r4; + mtctr r12; + bctrl; + b lbl_80012f60; +lbl_80012488: + cmpwi r22, 0x25; + beq lbl_80012500; + cmpwi r29, 0; + bne lbl_80012500; + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + extsb r4, r3; + clrlwi r0, r22, 0x18; + cmpw r0, r4; + stb r3, 0xc(r1); + beq lbl_800124f4; + clrlwi r4, r3, 0x18; + mr r12, r26; + mr r3, r27; + li r5, 1; + extsb r4, r4; + mtctr r12; + bctrl; + cmpwi r28, 0; + beq lbl_80012f6c; + li r29, 1; + addi r17, r17, 1; + b lbl_80012f60; +lbl_800124f4: + addi r21, r21, 1; + addi r17, r17, 1; + b lbl_80012f60; +lbl_80012500: + mr r3, r17; + addi r4, r1, 0x20; + bl parse_format; + lbz r0, 0x20(r1); + mr r17, r3; + cmpwi r0, 0; + bne lbl_8001253c; + lbz r0, 0x23(r1); + cmplwi r0, 0x25; + beq lbl_8001253c; + lwz r3, 8(r1); + li r4, 1; + bl __va_arg; + lwz r22, 0(r3); + b lbl_80012540; +lbl_8001253c: + li r22, 0; +lbl_80012540: + lbz r0, 0x23(r1); + cmplwi r0, 0x6e; + beq lbl_80012580; + cmpwi r29, 0; + bne lbl_80012580; + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 2; + mtctr r12; + bctrl; + cmpwi r3, 0; + beq lbl_80012580; + cmpwi r28, 0; + beq lbl_80012f6c; + li r29, 1; +lbl_80012580: + lbz r3, 0x23(r1); + addi r0, r3, -37; + cmplwi r0, 0x53; + bgt lbl_80012f6c; + lis r3, 0x8027; + slwi r0, r0, 2; + addi r3, r3, 0x14e0; + lwzx r3, r3, r0; + mtctr r3; + bctr; + li r3, 0xa; + b lbl_800125b4; + li r3, 0; +lbl_800125b4: + cmpwi r29, 0; + beq lbl_800125d4; + li r0, 0; + stw r0, 0x5c(r1); + li r0, 0; + li r16, 0; + stw r0, 0x58(r1); + b lbl_800126c8; +lbl_800125d4: + lbz r0, 0x22(r1); + cmplwi r0, 7; + beq lbl_800125e8; + cmplwi r0, 4; + bne lbl_80012610; +lbl_800125e8: + lwz r4, 0x24(r1); + mr r5, r26; + mr r6, r27; + addi r7, r1, 0x18; + addi r8, r1, 0x14; + addi r9, r1, 0x10; + bl unk_80014e00; + stw r4, 0x54(r1); + stw r3, 0x50(r1); + b lbl_80012630; +lbl_80012610: + lwz r4, 0x24(r1); + mr r5, r26; + mr r6, r27; + addi r7, r1, 0x18; + addi r8, r1, 0x14; + addi r9, r1, 0x10; + bl __strtoul; + mr r15, r3; +lbl_80012630: + lwz r3, 0x18(r1); + cmpwi r3, 0; + bne lbl_80012660; + cmpwi r28, 0; + beq lbl_80012f6c; + li r0, 0; + stw r0, 0x5c(r1); + li r0, 0; + li r29, 1; + stw r0, 0x58(r1); + li r16, 0; + b lbl_800126c8; +lbl_80012660: + lbz r0, 0x22(r1); + add r21, r21, r3; + cmplwi r0, 7; + beq lbl_80012678; + cmplwi r0, 4; + bne lbl_800126b4; +lbl_80012678: + lwz r0, 0x14(r1); + cmpwi r0, 0; + beq lbl_800126a0; + lwz r0, 0x54(r1); + subfic r0, r0, 0; + stw r0, 0x5c(r1); + lwz r0, 0x50(r1); + subfze r0, r0; + stw r0, 0x58(r1); + b lbl_800126c8; +lbl_800126a0: + lwz r0, 0x54(r1); + stw r0, 0x5c(r1); + lwz r0, 0x50(r1); + stw r0, 0x58(r1); + b lbl_800126c8; +lbl_800126b4: + lwz r0, 0x14(r1); + mr r16, r15; + cmpwi r0, 0; + beq lbl_800126c8; + neg r16, r15; +lbl_800126c8: + cmpwi r22, 0; + beq lbl_80012754; + lbz r0, 0x22(r1); + cmplwi r0, 7; + bgt lbl_80012748; + lis r3, 0x8027; + slwi r0, r0, 2; + addi r3, r3, 0x14c0; + lwzx r3, r3, r0; + mtctr r3; + bctr; + stw r16, 0(r22); + b lbl_80012748; + stb r16, 0(r22); + b lbl_80012748; + sth r16, 0(r22); + b lbl_80012748; + stw r16, 0(r22); + b lbl_80012748; + lwz r0, 0x5c(r1); + stw r0, 4(r22); + lwz r0, 0x58(r1); + stw r0, 0(r22); + b lbl_80012748; + stw r16, 0(r22); + b lbl_80012748; + stw r16, 0(r22); + b lbl_80012748; + lwz r0, 0x5c(r1); + stw r0, 4(r22); + lwz r0, 0x58(r1); + stw r0, 0(r22); +lbl_80012748: + cmpwi r29, 0; + bne lbl_80012754; + addi r20, r20, 1; +lbl_80012754: + addi r19, r19, 1; + b lbl_80012f60; + li r3, 8; + b lbl_80012770; + li r3, 0xa; + b lbl_80012770; + li r3, 0x10; +lbl_80012770: + cmpwi r29, 0; + beq lbl_80012790; + li r0, 0; + stw r0, 0x54(r1); + li r0, 0; + li r15, 0; + stw r0, 0x50(r1); + b lbl_80012860; +lbl_80012790: + lbz r0, 0x22(r1); + cmplwi r0, 7; + beq lbl_800127a4; + cmplwi r0, 4; + bne lbl_800127cc; +lbl_800127a4: + lwz r4, 0x24(r1); + mr r5, r26; + mr r6, r27; + addi r7, r1, 0x18; + addi r8, r1, 0x14; + addi r9, r1, 0x10; + bl unk_80014e00; + stw r4, 0x54(r1); + stw r3, 0x50(r1); + b lbl_800127ec; +lbl_800127cc: + lwz r4, 0x24(r1); + mr r5, r26; + mr r6, r27; + addi r7, r1, 0x18; + addi r8, r1, 0x14; + addi r9, r1, 0x10; + bl __strtoul; + mr r15, r3; +lbl_800127ec: + lwz r3, 0x18(r1); + cmpwi r3, 0; + bne lbl_8001281c; + cmpwi r28, 0; + beq lbl_80012f6c; + li r0, 0; + stw r0, 0x54(r1); + li r0, 0; + li r29, 1; + stw r0, 0x50(r1); + li r15, 0; + b lbl_80012860; +lbl_8001281c: + lwz r0, 0x14(r1); + add r21, r21, r3; + cmpwi r0, 0; + beq lbl_80012860; + lbz r0, 0x22(r1); + cmplwi r0, 7; + bne lbl_80012850; + lwz r0, 0x54(r1); + subfic r0, r0, 0; + stw r0, 0x54(r1); + lwz r0, 0x50(r1); + subfze r0, r0; + stw r0, 0x50(r1); +lbl_80012850: + lbz r0, 0x22(r1); + cmplwi r0, 7; + beq lbl_80012860; + neg r15, r15; +lbl_80012860: + cmpwi r22, 0; + beq lbl_800128ec; + lbz r0, 0x22(r1); + cmplwi r0, 7; + bgt lbl_800128e0; + lis r3, 0x8027; + slwi r0, r0, 2; + addi r3, r3, 0x14a0; + lwzx r3, r3, r0; + mtctr r3; + bctr; + stw r15, 0(r22); + b lbl_800128e0; + stb r15, 0(r22); + b lbl_800128e0; + sth r15, 0(r22); + b lbl_800128e0; + stw r15, 0(r22); + b lbl_800128e0; + lwz r0, 0x54(r1); + stw r0, 4(r22); + lwz r0, 0x50(r1); + stw r0, 0(r22); + b lbl_800128e0; + stw r15, 0(r22); + b lbl_800128e0; + stw r15, 0(r22); + b lbl_800128e0; + lwz r0, 0x54(r1); + stw r0, 4(r22); + lwz r0, 0x50(r1); + stw r0, 0(r22); +lbl_800128e0: + cmpwi r29, 0; + bne lbl_800128ec; + addi r20, r20, 1; +lbl_800128ec: + addi r19, r19, 1; + b lbl_80012f60; + cmpwi r29, 0; + beq lbl_80012908; + lis r3, 0x8038; + lfs f1, 0x4c20(r3); + b lbl_80012948; +lbl_80012908: + lwz r3, 0x24(r1); + mr r4, r26; + mr r5, r27; + addi r6, r1, 0x18; + addi r7, r1, 0x10; + bl unk_8001365c; + lwz r0, 0x18(r1); + cmpwi r0, 0; + bne lbl_80012944; + cmpwi r28, 0; + beq lbl_80012f6c; + lis r3, 0x8038; + li r29, 1; + lfs f1, 0x4c20(r3); + b lbl_80012948; +lbl_80012944: + add r21, r21, r0; +lbl_80012948: + cmpwi r22, 0; + beq lbl_80012994; + lbz r0, 0x22(r1); + cmpwi r0, 0; + beq lbl_80012970; + cmpwi r0, 8; + beq lbl_8001297c; + cmpwi r0, 9; + beq lbl_80012984; + b lbl_80012988; +lbl_80012970: + frsp f0, f1; + stfs f0, 0(r22); + b lbl_80012988; +lbl_8001297c: + stfd f1, 0(r22); + b lbl_80012988; +lbl_80012984: + stfd f1, 0(r22); +lbl_80012988: + cmpwi r29, 0; + bne lbl_80012994; + addi r20, r20, 1; +lbl_80012994: + addi r19, r19, 1; + b lbl_80012f60; + lbz r0, 0x21(r1); + cmpwi r0, 0; + bne lbl_800129ac; + stw r24, 0x24(r1); +lbl_800129ac: + cmpwi r22, 0; + beq lbl_80012adc; + cmpwi r28, 0; + beq lbl_800129d0; + lwz r3, 8(r1); + li r31, 1; + li r4, 1; + bl __va_arg; + lwz r30, 0(r3); +lbl_800129d0: + cmpwi r29, 0; + li r0, 0; + stw r0, 0x18(r1); + beq lbl_800129f4; + cmpwi r30, 0; + beq lbl_80012f60; + li r0, 0; + stb r0, 0(r22); + b lbl_80012f60; +lbl_800129f4: + stw r22, 0x4c(r1); + b lbl_80012a38; +lbl_800129fc: + lbz r0, 0x22(r1); + stb r3, 0xc(r1); + cmplwi r0, 0xa; + bne lbl_80012a24; + mr r3, r22; + addi r4, r1, 0xc; + li r5, 1; + bl unk_8000eff8; + addi r22, r22, 2; + b lbl_80012a2c; +lbl_80012a24: + stb r3, 0(r22); + addi r22, r22, 1; +lbl_80012a2c: + lwz r3, 0x18(r1); + addi r0, r3, 1; + stw r0, 0x18(r1); +lbl_80012a38: + lwz r3, 0x24(r1); + cmpwi r3, 0; + addi r3, r3, -1; + stw r3, 0x24(r1); + beq lbl_80012a8c; + cmpwi r28, 0; + beq lbl_80012a68; + xor r0, r30, r0; + cntlzw r0, r0; + slw r0, r30, r0; + rlwinm. r31, r0, 1, 0x1f, 0x1f; + beq lbl_80012a8c; +lbl_80012a68: + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + cmpwi r3, -1; + mr r18, r3; + bne lbl_800129fc; +lbl_80012a8c: + lwz r0, 0x18(r1); + stb r18, 0xc(r1); + cmpwi r0, 0; + beq lbl_80012aac; + cmpwi r28, 0; + beq lbl_80012ad0; + cmpwi r31, 0; + bne lbl_80012ad0; +lbl_80012aac: + cmpwi r28, 0; + beq lbl_80012f6c; + cmpwi r30, 0; + li r29, 1; + beq lbl_80012f60; + lwz r3, 0x4c(r1); + li r0, 0; + stb r0, 0(r3); + b lbl_80012f60; +lbl_80012ad0: + add r21, r21, r0; + addi r20, r20, 1; + b lbl_80012b40; +lbl_80012adc: + li r0, 0; + stw r0, 0x18(r1); + b lbl_80012af8; +lbl_80012ae8: + lwz r4, 0x18(r1); + stb r3, 0xc(r1); + addi r0, r4, 1; + stw r0, 0x18(r1); +lbl_80012af8: + lwz r3, 0x24(r1); + cmpwi r3, 0; + addi r0, r3, -1; + stw r0, 0x24(r1); + beq lbl_80012b30; + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + cmpwi r3, -1; + mr r18, r3; + bne lbl_80012ae8; +lbl_80012b30: + lwz r0, 0x18(r1); + stb r18, 0xc(r1); + cmpwi r0, 0; + beq lbl_80012f6c; +lbl_80012b40: + addi r19, r19, 1; + b lbl_80012f60; + cmpwi r29, 0; + bne lbl_80012f60; + b lbl_80012b58; +lbl_80012b54: + addi r21, r21, 1; +lbl_80012b58: + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + extsb r0, r3; + stb r3, 0xc(r1); + cmplwi r0, 0xff; + li r4, 1; + bgt lbl_80012b88; + li r4, 0; +lbl_80012b88: + cmpwi r4, 0; + beq lbl_80012b98; + li r0, 0; + b lbl_80012bac; +lbl_80012b98: + lwz r4, 0x38(r23); + slwi r0, r0, 1; + lwz r4, 8(r4); + lhzx r0, r4, r0; + rlwinm r0, r0, 0, 0x17, 0x17; +lbl_80012bac: + cmpwi r0, 0; + bne lbl_80012b54; + clrlwi r0, r3, 0x18; + extsb r4, r0; + cmpwi r4, 0x25; + beq lbl_80012be8; + mr r12, r26; + mr r3, r27; + li r5, 1; + mtctr r12; + bctrl; + cmpwi r28, 0; + beq lbl_80012f6c; + li r29, 1; + b lbl_80012f60; +lbl_80012be8: + addi r21, r21, 1; + b lbl_80012f60; + cmpwi r29, 0; + bne lbl_80012c94; + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + stb r3, 0xc(r1); + b lbl_80012c38; +lbl_80012c18: + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + addi r21, r21, 1; + bctrl; + stb r3, 0xc(r1); +lbl_80012c38: + clrlwi r4, r3, 0x18; + li r3, 1; + extsb r0, r4; + cmplwi r0, 0xff; + bgt lbl_80012c50; + li r3, 0; +lbl_80012c50: + cmpwi r3, 0; + beq lbl_80012c60; + li r0, 0; + b lbl_80012c74; +lbl_80012c60: + lwz r3, 0x38(r23); + slwi r0, r0, 1; + lwz r3, 8(r3); + lhzx r0, r3, r0; + rlwinm r0, r0, 0, 0x17, 0x17; +lbl_80012c74: + cmpwi r0, 0; + bne lbl_80012c18; + mr r12, r26; + mr r3, r27; + extsb r4, r4; + li r5, 1; + mtctr r12; + bctrl; +lbl_80012c94: + cmpwi r22, 0; + beq lbl_80012e28; + cmpwi r28, 0; + beq lbl_80012cbc; + lwz r3, 8(r1); + li r31, 1; + li r4, 1; + bl __va_arg; + lwz r3, 0(r3); + addi r30, r3, -1; +lbl_80012cbc: + cmpwi r29, 0; + li r0, 0; + stw r0, 0x18(r1); + beq lbl_80012ce0; + cmpwi r30, 0; + beq lbl_80012f60; + li r0, 0; + stb r0, 0(r22); + b lbl_80012f60; +lbl_80012ce0: + stw r22, 0x48(r1); + b lbl_80012d44; +lbl_80012ce8: + rlwinm r0, r3, 0x1d, 0x1b, 0x1f; + clrlwi r5, r3, 0x1d; + add r4, r25, r0; + stb r3, 0xc(r1); + lbz r0, 8(r4); + slw r4, r24, r5; + clrlwi r3, r3, 0x18; + and. r0, r4, r0; + beq lbl_80012d9c; + lbz r0, 0x22(r1); + cmplwi r0, 0xa; + bne lbl_80012d30; + mr r3, r22; + addi r4, r1, 0xc; + li r5, 1; + bl unk_8000eff8; + addi r22, r22, 2; + b lbl_80012d38; +lbl_80012d30: + stb r3, 0(r22); + addi r22, r22, 1; +lbl_80012d38: + lwz r3, 0x18(r1); + addi r0, r3, 1; + stw r0, 0x18(r1); +lbl_80012d44: + lwz r3, 0x24(r1); + cmpwi r3, 0; + addi r3, r3, -1; + stw r3, 0x24(r1); + beq lbl_80012d9c; + cmpwi r28, 0; + beq lbl_80012d78; + subf r4, r0, r30; + orc r3, r30, r0; + srwi r0, r4, 1; + subf r0, r0, r3; + rlwinm. r31, r0, 1, 0x1f, 0x1f; + beq lbl_80012d9c; +lbl_80012d78: + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + cmpwi r3, -1; + mr r18, r3; + bne lbl_80012ce8; +lbl_80012d9c: + lwz r3, 0x18(r1); + stb r18, 0xc(r1); + cmpwi r3, 0; + beq lbl_80012dbc; + cmpwi r28, 0; + beq lbl_80012dfc; + cmpwi r31, 0; + bne lbl_80012dfc; +lbl_80012dbc: + clrlwi r4, r18, 0x18; + mr r12, r26; + mr r3, r27; + li r5, 1; + extsb r4, r4; + mtctr r12; + bctrl; + cmpwi r28, 0; + beq lbl_80012f6c; + cmpwi r30, 0; + li r29, 1; + beq lbl_80012f60; + lwz r3, 0x48(r1); + li r0, 0; + stb r0, 0(r3); + b lbl_80012f60; +lbl_80012dfc: + lbz r0, 0x22(r1); + add r21, r21, r3; + cmplwi r0, 0xa; + bne lbl_80012e18; + li r0, 0; + sth r0, 0(r22); + b lbl_80012e20; +lbl_80012e18: + li r0, 0; + stb r0, 0(r22); +lbl_80012e20: + addi r20, r20, 1; + b lbl_80012ecc; +lbl_80012e28: + li r0, 0; + stw r0, 0x18(r1); + b lbl_80012e60; +lbl_80012e34: + rlwinm r0, r3, 0x1d, 0x1b, 0x1f; + clrlwi r5, r3, 0x1d; + add r4, r25, r0; + stb r3, 0xc(r1); + lbz r0, 8(r4); + slw r3, r24, r5; + and. r0, r3, r0; + beq lbl_80012e98; + lwz r3, 0x18(r1); + addi r0, r3, 1; + stw r0, 0x18(r1); +lbl_80012e60: + lwz r3, 0x24(r1); + cmpwi r3, 0; + addi r0, r3, -1; + stw r0, 0x24(r1); + beq lbl_80012e98; + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 0; + mtctr r12; + bctrl; + cmpwi r3, -1; + mr r18, r3; + bne lbl_80012e34; +lbl_80012e98: + lwz r0, 0x18(r1); + stb r18, 0xc(r1); + cmpwi r0, 0; + bne lbl_80012ec8; + clrlwi r4, r18, 0x18; + mr r12, r26; + mr r3, r27; + li r5, 1; + extsb r4, r4; + mtctr r12; + bctrl; + b lbl_80012f60; +lbl_80012ec8: + add r21, r21, r0; +lbl_80012ecc: + lwz r0, 0x24(r1); + cmpwi r0, 0; + blt lbl_80012ef4; + lbz r4, 0xc(r1); + mr r12, r26; + mr r3, r27; + li r5, 1; + extsb r4, r4; + mtctr r12; + bctrl; +lbl_80012ef4: + addi r19, r19, 1; + b lbl_80012f60; + cmpwi r22, 0; + beq lbl_80012f60; + lbz r0, 0x22(r1); + cmpwi r0, 0; + beq lbl_80012f34; + cmpwi r0, 2; + beq lbl_80012f3c; + cmpwi r0, 3; + beq lbl_80012f44; + cmpwi r0, 1; + beq lbl_80012f4c; + cmpwi r0, 7; + beq lbl_80012f54; + b lbl_80012f60; +lbl_80012f34: + stw r21, 0(r22); + b lbl_80012f60; +lbl_80012f3c: + sth r21, 0(r22); + b lbl_80012f60; +lbl_80012f44: + stw r21, 0(r22); + b lbl_80012f60; +lbl_80012f4c: + stb r21, 0(r22); + b lbl_80012f60; +lbl_80012f54: + stw r21, 4(r22); + srawi r0, r21, 0x1f; + stw r0, 0(r22); +lbl_80012f60: + lbz r0, 0(r17); + extsb. r22, r0; + bne lbl_8001237c; +lbl_80012f6c: + mr r12, r26; + mr r3, r27; + li r4, 0; + li r5, 2; + mtctr r12; + bctrl; + cmpwi r3, 0; + beq lbl_80012f9c; + cmpwi r19, 0; + bne lbl_80012f9c; + li r3, -1; + b lbl_80012fa0; +lbl_80012f9c: + mr r3, r20; +lbl_80012fa0: + addi r11, r1, 0xb0; + bl _restgpr_15; + lwz r0, 0xb4(r1); + mtlr r0; + addi r1, r1, 0xb0; + blr; + // clang-format on +} + +// Symbol: __StringRead +// Function signature is unknown. +// PAL: 0x80012fb8..0x80013040 +MARK_BINARY_BLOB(__StringRead, 0x80012fb8, 0x80013040); +asm UNKNOWN_FUNCTION(__StringRead) { + // clang-format off + nofralloc; + cmpwi r5, 0; + beq lbl_80012fd4; + cmpwi r5, 1; + beq lbl_80013004; + cmpwi r5, 2; + beq lbl_80013030; + b lbl_80013038; +lbl_80012fd4: + lwz r4, 0(r3); + lbz r5, 0(r4); + extsb. r0, r5; + bne lbl_80012ff4; + li r0, 1; + stw r0, 4(r3); + li r3, -1; + blr; +lbl_80012ff4: + addi r0, r4, 1; + stw r0, 0(r3); + mr r3, r5; + blr; +lbl_80013004: + lwz r0, 4(r3); + cmpwi r0, 0; + bne lbl_80013020; + lwz r5, 0(r3); + addi r0, r5, -1; + stw r0, 0(r3); + b lbl_80013028; +lbl_80013020: + li r0, 0; + stw r0, 4(r3); +lbl_80013028: + mr r3, r4; + blr; +lbl_80013030: + lwz r3, 4(r3); + blr; +lbl_80013038: + li r3, 0; + blr; + // clang-format on +} + +// Symbol: sscanf +// PAL: 0x80013040..0x80013108 +MARK_BINARY_BLOB(sscanf, 0x80013040, 0x80013108); +asm int sscanf(const char* str, const char* format, ...) { + // clang-format off + nofralloc; + stwu r1, -0x90(r1); + mflr r0; + stw r0, 0x94(r1); + stw r31, 0x8c(r1); + bne cr1, lbl_80013074; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_80013074: + cmpwi r3, 0; + addi r11, r1, 0x98; + addi r0, r1, 8; + lis r12, 0x200; + stw r3, 8(r1); + addi r31, r1, 0x70; + stw r4, 0xc(r1); + stw r5, 0x10(r1); + stw r6, 0x14(r1); + stw r7, 0x18(r1); + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + stw r12, 0x70(r1); + stw r11, 0x74(r1); + stw r0, 0x78(r1); + stw r3, 0x68(r1); + beq lbl_800130c8; + lbz r0, 0(r3); + extsb. r0, r0; + bne lbl_800130d0; +lbl_800130c8: + li r3, -1; + b lbl_800130f4; +lbl_800130d0: + li r0, 0; + lis r3, 0x8001; + mr r5, r4; + stw r0, 0x6c(r1); + mr r6, r31; + addi r3, r3, 0x2fb8; + addi r4, r1, 0x68; + li r7, 0; + bl __sformatter; +lbl_800130f4: + lwz r0, 0x94(r1); + lwz r31, 0x8c(r1); + mtlr r0; + addi r1, r1, 0x90; + blr; + // clang-format on +} diff --git a/source/platform/stdarg.h b/source/platform/stdarg.h deleted file mode 100644 index 6f70f09be..000000000 --- a/source/platform/stdarg.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/source/platform/stddef.h b/source/platform/stddef.h index 0769e953d..d89ad0e43 100644 --- a/source/platform/stddef.h +++ b/source/platform/stddef.h @@ -2,4 +2,4 @@ #include -#define offsetof(type, m) ((size_t)&(((type*)0)->m)) \ No newline at end of file +#define offsetof(type, m) ((size_t) & (((type*)0)->m)) \ No newline at end of file diff --git a/source/platform/stdio.h b/source/platform/stdio.h index 911e06d8f..459a797ec 100644 --- a/source/platform/stdio.h +++ b/source/platform/stdio.h @@ -69,6 +69,7 @@ typedef char* va_list; int sprintf(char* str, const char* format, ...); int snprintf(char* s, size_t n, const char* format, ...); int vsnprintf(char* s, size_t n, const char* format, va_list arg); +int vsprintf(char* s, const char* format, va_list arg); int sscanf(const char* str, const char* format, ...); diff --git a/source/platform/stdlib.h b/source/platform/stdlib.h index 5fca03bac..1728374c7 100644 --- a/source/platform/stdlib.h +++ b/source/platform/stdlib.h @@ -7,6 +7,9 @@ #define labs(x) __labs(x) long strtol(const char* restrict nptr, char** restrict endptr, int base); +unsigned long int strtoul(const char* str, char** endptr, int base); +unsigned long __strtoul(int, int, int (*)(void*, int, int), void*, int*, int*, + int*); int atoi(const char* nptr); long atol(const char* nptr); diff --git a/source/platform/string.h b/source/platform/string.h index d271f46af..5dff4e509 100644 --- a/source/platform/string.h +++ b/source/platform/string.h @@ -12,6 +12,8 @@ void* memcpy(void*, const void*, u32); void* memset(void*, s32, u32); void* memmove(void*, const void*, size_t); +void* memchr(void* ptr, int value, size_t num); +void* __memrchr(const void* ptr, int value, size_t num); int memcmp(const void* s1, const void* s2, size_t n); diff --git a/source/platform/va_arg.c b/source/platform/va_arg.c new file mode 100644 index 000000000..36b97c704 --- /dev/null +++ b/source/platform/va_arg.c @@ -0,0 +1,67 @@ +#include "va_arg.h" + +// Symbol: __va_arg +// Function signature is unknown. +// PAL: 0x80021270..0x80021338 +MARK_BINARY_BLOB(__va_arg, 0x80021270, 0x80021338); +asm UNKNOWN_FUNCTION(__va_arg) { + // clang-format off + nofralloc; + lbz r7, 0(r3); + cmpwi r4, 3; + mr r6, r3; + li r0, 8; + extsb r7, r7; + li r8, 4; + li r9, 1; + li r5, 0; + li r10, 0; + li r11, 4; + bne lbl_800212b4; + lbz r7, 1(r3); + addi r6, r3, 1; + li r8, 8; + li r10, 0x20; + extsb r7, r7; + li r11, 8; +lbl_800212b4: + cmpwi r4, 2; + bne lbl_800212d4; + clrlwi. r0, r7, 0x1f; + li r8, 8; + li r0, 7; + beq lbl_800212d0; + li r5, 1; +lbl_800212d0: + li r9, 2; +lbl_800212d4: + cmpw r7, r0; + bge lbl_800212fc; + add r7, r7, r5; + lwz r0, 8(r3); + mullw r5, r7, r11; + add r3, r0, r10; + add r0, r7, r9; + stb r0, 0(r6); + add r5, r5, r3; + b lbl_80021324; +lbl_800212fc: + li r0, 8; + stb r0, 0(r6); + addi r0, r8, -1; + lwz r5, 4(r3); + nor r6, r0, r0; + add r5, r8, r5; + addi r0, r5, -1; + and r5, r6, r0; + add r0, r5, r8; + stw r0, 4(r3); +lbl_80021324: + cmpwi r4, 0; + bne lbl_80021330; + lwz r5, 0(r5); +lbl_80021330: + mr r3, r5; + blr; + // clang-format on +} diff --git a/source/platform/va_arg.h b/source/platform/va_arg.h new file mode 100644 index 000000000..0eac3d616 --- /dev/null +++ b/source/platform/va_arg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x80021270..0x80021338 +UNKNOWN_FUNCTION(__va_arg); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/ipc/ipcMain.c b/source/rvl/ipc/ipcMain.c index 56957b183..a1f11177f 100644 --- a/source/rvl/ipc/ipcMain.c +++ b/source/rvl/ipc/ipcMain.c @@ -1,14 +1,13 @@ #include "ipcMain.h" +#include + // Extern function references. // PAL: 0x801ab648 extern void* __OSGetIPCBufferHi(void); // PAL: 0x801ab650 extern void* __OSGetIPCBufferLo(void); -// Hardware registers. -u32 HOLLYWOOD_REGS[137] : 0xcd000000; - // Static vars. static void* IPCAltBufferHi; static void* IPCAltBufferLo; @@ -43,11 +42,11 @@ void IPCReInit(void) { // Symbol: IPCReadReg // PAL: 0x80193010..0x80193020 -u32 IPCReadReg(u32 reg) { return HOLLYWOOD_REGS[reg]; } +u32 IPCReadReg(u32 reg) { return __ACRRegs[reg]; } // Symbol: IPCWriteReg // PAL: 0x80193020..0x80193030 -void IPCWriteReg(u32 reg, u32 data) { HOLLYWOOD_REGS[reg] = data; } +void IPCWriteReg(u32 reg, u32 data) { __ACRRegs[reg] = data; } // Symbol: IPCGetBufferHi // PAL: 0x80193030..0x80193038 diff --git a/source/rvl/ipc/ipcclt.c b/source/rvl/ipc/ipcclt.c index a82d6c806..24131475f 100644 --- a/source/rvl/ipc/ipcclt.c +++ b/source/rvl/ipc/ipcclt.c @@ -3,6 +3,8 @@ #include #include +#include +#include #include "ipcMain.h" @@ -29,16 +31,6 @@ extern UNKNOWN_FUNCTION(unk_801a162c); extern UNKNOWN_FUNCTION(OSSetCurrentContext); // PAL: 0x801a2098 extern UNKNOWN_FUNCTION(OSClearContext); -// PAL: 0x801a65f8 -extern UNKNOWN_FUNCTION(unk_801a65f8); -// PAL: 0x801a69bc -extern UNKNOWN_FUNCTION(unk_801a69bc); -// PAL: 0x801a98a0 -extern UNKNOWN_FUNCTION(unk_801a98a0); -// PAL: 0x801aa9b8 -extern UNKNOWN_FUNCTION(OSSleepThread); -// PAL: 0x801aaaa4 -extern UNKNOWN_FUNCTION(OSWakeupThread); // Symbol: strnlen // Function signature is unknown. @@ -404,9 +396,9 @@ asm UNKNOWN_FUNCTION(IPCCltInit) { lis r4, 0x8019; li r3, 0x1b; addi r4, r4, 0x32cc; - bl unk_801a65f8; + bl __OSSetInterruptHandler; li r3, 0x10; - bl unk_801a69bc; + bl __OSUnmaskInterrupts; li r3, 1; li r4, 0x38; bl IPCWriteReg; @@ -488,7 +480,7 @@ asm UNKNOWN_FUNCTION(__ios_Ipc2) { cmpwi r4, 0; bne lbl_801935e4; addi r3, r3, 0x2c; - bl unk_801a98a0; + bl OSInitThreadQueue; lbl_801935e4: mr r3, r28; li r4, 0x20; @@ -1921,7 +1913,7 @@ asm UNKNOWN_FUNCTION(IOS_IoctlvReboot) { lwz r29, 8(r1); stw r3, -0x63fc(r13); addi r3, r3, 0x2c; - bl unk_801a98a0; + bl OSInitThreadQueue; mr r3, r29; li r4, 0x20; bl unk_801a162c; diff --git a/source/rvl/mem/memAllocator.h b/source/rvl/mem/memAllocator.h index 8250db8d8..48808805c 100644 --- a/source/rvl/mem/memAllocator.h +++ b/source/rvl/mem/memAllocator.h @@ -2,7 +2,9 @@ #include -#include "rvl/os/osThread.h" +#include +#include +#include #include "rvlMemList.h" #ifdef __cplusplus diff --git a/source/rvl/nand/nand.c b/source/rvl/nand/nand.c index 10b678245..8be0daee7 100644 --- a/source/rvl/nand/nand.c +++ b/source/rvl/nand/nand.c @@ -9,6 +9,7 @@ #include #include #include +#include // Extern function references. // PAL: 0x801671d0 @@ -34,7 +35,7 @@ extern UNKNOWN_FUNCTION(ISFS_Close); // PAL: 0x801a0504 extern UNKNOWN_FUNCTION(OSRegisterVersion); // PAL: 0x801a8238 -extern UNKNOWN_FUNCTION(OSRegisterResetFunction); +extern UNKNOWN_FUNCTION(OSRegisterShutdownFunction); // Function declarations. UNKNOWN_FUNCTION(nandCreate); @@ -3954,7 +3955,7 @@ asm s32 NANDInit(void) { bl OSReport; lbl_8019e260: addi r3, r31, 0xa0; - bl OSRegisterResetFunction; + bl OSRegisterShutdownFunction; bl OSDisableInterrupts; li r0, 2; stw r0, -0x63b8(r13); diff --git a/source/rvl/os/os.h b/source/rvl/os/os.h index 8bfe101ff..8d40ed44c 100644 --- a/source/rvl/os/os.h +++ b/source/rvl/os/os.h @@ -35,10 +35,6 @@ OSTime OSGetTime(void); OSTime OSCalendarTimeToTicks(OSCalendarTime* td); void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* td); -int OSDisableInterrupts(void); -int OSEnableInterrupts(void); -int OSRestoreInterrupts(int level); - void OSPanic(char* file, int line, char* msg, ...); #ifdef __cplusplus diff --git a/source/rvl/os/osException.h b/source/rvl/os/osException.h new file mode 100644 index 000000000..9424821d3 --- /dev/null +++ b/source/rvl/os/osException.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#include "os.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*__OSExceptionHandler)(u8 exception, OSContext* context); + +// PAL: 0x801a0388 +__OSExceptionHandler __OSSetExceptionHandler(u8 exception, + __OSExceptionHandler handler); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/os/osInterrupt.c b/source/rvl/os/osInterrupt.c new file mode 100644 index 000000000..211d71e5d --- /dev/null +++ b/source/rvl/os/osInterrupt.c @@ -0,0 +1,497 @@ +#include "osInterrupt.h" + +#include +#include + +#include "osException.h" + +// Extern function references. +// PAL: 0x801a1f58 +extern void OSLoadContext(OSContext* context); +// PAL: 0x801a98e8 +extern UNKNOWN_FUNCTION(OSDisableScheduler); +// PAL: 0x801a9924 +extern UNKNOWN_FUNCTION(OSEnableScheduler); +// PAL: 0x801a9e30 +extern UNKNOWN_FUNCTION(__OSReschedule); + +static __OSInterruptHandler* OSInterruptHandlerTable; +static u32 OSInterruptPrioTable[] = { + 0x00000100, 0x00000040, 0xf8000000, 0x00000200, 0x00000080, 0x00000010, + 0x00003000, 0x00000020, 0x03ff8c00, 0x04000000, 0x00004000, 0xffffffff, +}; + +#define OS_INTERRUPTTABLE_ADDR 0x3040 +#define OS_INTERRUPTMASK_ADDR 0x00C4 +#define OS_UINTERRUPTMASK_ADDR 0x00C8 + +static void ExternalInterruptHandler(register u8 exception, + register OSContext* context); + +// Symbol: OSDisableInterrupts +// PAL: 0x801a65ac..0x801a65c0 +asm int OSDisableInterrupts(void) { + nofralloc; + mfmsr r3; + rlwinm r4, r3, 0, 0x11, 0xf; + mtmsr r4; + rlwinm r3, r3, 0x11, 0x1f, 0x1f; + blr; +} + +// Symbol: OSEnableInterrupts +// PAL: 0x801a65c0..0x801a65d4 +asm int OSEnableInterrupts(void) { + nofralloc; + mfmsr r3; + ori r4, r3, 0x8000; + mtmsr r4; + rlwinm r3, r3, 0x11, 0x1f, 0x1f; + blr; +} + +// Symbol: OSRestoreInterrupts +// PAL: 0x801a65d4..0x801a65f8 +asm int OSRestoreInterrupts(int level) { + nofralloc; + cmpwi r3, 0; + mfmsr r4; + beq _disable; + ori r5, r4, 0x8000; + b _restore; +_disable: + rlwinm r5, r4, 0, 0x11, 0xf; +_restore: + mtmsr r5; + rlwinm r3, r4, 0x11, 0x1f, 0x1f; + blr; +} + +// Symbol: __OSSetInterruptHandler +// PAL: 0x801a65f8..0x801a660c +__OSInterruptHandler __OSSetInterruptHandler(s16 interrupt, + __OSInterruptHandler handler) { + __OSInterruptHandler old = OSInterruptHandlerTable[interrupt]; + OSInterruptHandlerTable[interrupt] = handler; + return old; +} + +// Symbol: __OSGetInterruptHandler +// PAL: 0x801a660c..0x801a661c +__OSInterruptHandler __OSGetInterruptHandler(s16 interrupt) { + return OSInterruptHandlerTable[interrupt]; +} + +inline void* OSPhysicalToCached(u32 addr) { return (void*)(addr + 0x80000000); } + +// Symbol: __OSInterruptInit +// PAL: 0x801a661c..0x801a66e0 +void __OSInterruptInit(void) { + OSInterruptHandlerTable = (__OSInterruptHandler*)OSPhysicalToCached(0x3040); + memset((void*)OSInterruptHandlerTable, 0, 32 * sizeof(__OSInterruptHandler)); + *(u32*)OSPhysicalToCached(0x00C4) = 0; + *(u32*)OSPhysicalToCached(0x00C8) = 0; + __PIRegs[0x004 / 4] = 0xf0; + __ACRRegs[0x034 / 4] = 0x40000000; + __OSMaskInterrupts(0xfffffff0u); + __OSSetExceptionHandler(4, ExternalInterruptHandler); +} + +// Symbol: SetInterruptMask +// PAL: 0x801a66e0..0x801a693c +MARK_BINARY_BLOB(SetInterruptMask, 0x801a66e0, 0x801a693c); +static asm u32 SetInterruptMask(u32 mask, u32 current) { + // clang-format off + nofralloc; + cntlzw r0, r3; + cmpwi r0, 0xc; + bge lbl_801a670c; + cmpwi r0, 8; + beq lbl_801a67bc; + bge lbl_801a67e8; + cmpwi r0, 5; + bge lbl_801a677c; + cmpwi r0, 0; + bge lbl_801a672c; + blr; +lbl_801a670c: + cmpwi r0, 0x11; + bge lbl_801a6720; + cmpwi r0, 0xf; + bge lbl_801a6870; + b lbl_801a682c; +lbl_801a6720: + cmpwi r0, 0x1c; + bgelr; + b lbl_801a68a4; +lbl_801a672c: + rlwinm. r0, r4, 0, 0, 0; + li r5, 0; + bne lbl_801a673c; + ori r5, r5, 1; +lbl_801a673c: + rlwinm. r0, r4, 0, 1, 1; + bne lbl_801a6748; + ori r5, r5, 2; +lbl_801a6748: + rlwinm. r0, r4, 0, 2, 2; + bne lbl_801a6754; + ori r5, r5, 4; +lbl_801a6754: + rlwinm. r0, r4, 0, 3, 3; + bne lbl_801a6760; + ori r5, r5, 8; +lbl_801a6760: + rlwinm. r0, r4, 0, 4, 4; + bne lbl_801a676c; + ori r5, r5, 0x10; +lbl_801a676c: + lis r4, 0xcc00; + clrlwi r3, r3, 5; + sth r5, 0x401c(r4); + blr; +lbl_801a677c: + lis r5, 0xcc00; + rlwinm. r0, r4, 0, 5, 5; + lhz r5, 0x500a(r5); + rlwinm r5, r5, 0, 0x1d, 0x16; + bne lbl_801a6794; + ori r5, r5, 0x10; +lbl_801a6794: + rlwinm. r0, r4, 0, 6, 6; + bne lbl_801a67a0; + ori r5, r5, 0x40; +lbl_801a67a0: + rlwinm. r0, r4, 0, 7, 7; + bne lbl_801a67ac; + ori r5, r5, 0x100; +lbl_801a67ac: + lis r4, 0xcc00; + rlwinm r3, r3, 0, 8, 4; + sth r5, 0x500a(r4); + blr; +lbl_801a67bc: + rlwinm. r0, r4, 0, 8, 8; + lis r4, 0xcd00; + lwz r5, 0x6c00(r4); + li r0, -45; + and r5, r5, r0; + bne lbl_801a67d8; + ori r5, r5, 4; +lbl_801a67d8: + lis r4, 0xcd00; + rlwinm r3, r3, 0, 9, 7; + stw r5, 0x6c00(r4); + blr; +lbl_801a67e8: + rlwinm. r0, r4, 0, 9, 9; + lis r5, 0xcd00; + lwz r5, 0x6800(r5); + li r0, -11280; + and r5, r5, r0; + bne lbl_801a6804; + ori r5, r5, 1; +lbl_801a6804: + rlwinm. r0, r4, 0, 0xa, 0xa; + bne lbl_801a6810; + ori r5, r5, 4; +lbl_801a6810: + rlwinm. r0, r4, 0, 0xb, 0xb; + bne lbl_801a681c; + ori r5, r5, 0x400; +lbl_801a681c: + lis r4, 0xcd00; + rlwinm r3, r3, 0, 0xc, 8; + stw r5, 0x6800(r4); + blr; +lbl_801a682c: + rlwinm. r0, r4, 0, 0xc, 0xc; + lis r5, 0xcd00; + lwz r5, 0x6814(r5); + li r0, -3088; + and r5, r5, r0; + bne lbl_801a6848; + ori r5, r5, 1; +lbl_801a6848: + rlwinm. r0, r4, 0, 0xd, 0xd; + bne lbl_801a6854; + ori r5, r5, 4; +lbl_801a6854: + rlwinm. r0, r4, 0, 0xe, 0xe; + bne lbl_801a6860; + ori r5, r5, 0x400; +lbl_801a6860: + lis r4, 0xcd00; + rlwinm r3, r3, 0, 0xf, 0xb; + stw r5, 0x6814(r4); + blr; +lbl_801a6870: + lis r5, 0xcd00; + rlwinm. r0, r4, 0, 0xf, 0xf; + lwz r5, 0x6828(r5); + rlwinm r5, r5, 0, 0, 0x1b; + bne lbl_801a6888; + ori r5, r5, 1; +lbl_801a6888: + rlwinm. r0, r4, 0, 0x10, 0x10; + bne lbl_801a6894; + ori r5, r5, 4; +lbl_801a6894: + lis r4, 0xcd00; + rlwinm r3, r3, 0, 0x11, 0xe; + stw r5, 0x6828(r4); + blr; +lbl_801a68a4: + rlwinm. r0, r4, 0, 0x11, 0x11; + li r5, 0xf0; + bne lbl_801a68b4; + ori r5, r5, 0x800; +lbl_801a68b4: + rlwinm. r0, r4, 0, 0x14, 0x14; + bne lbl_801a68c0; + ori r5, r5, 8; +lbl_801a68c0: + rlwinm. r0, r4, 0, 0x15, 0x15; + bne lbl_801a68cc; + ori r5, r5, 4; +lbl_801a68cc: + rlwinm. r0, r4, 0, 0x16, 0x16; + bne lbl_801a68d8; + ori r5, r5, 2; +lbl_801a68d8: + rlwinm. r0, r4, 0, 0x17, 0x17; + bne lbl_801a68e4; + ori r5, r5, 1; +lbl_801a68e4: + rlwinm. r0, r4, 0, 0x18, 0x18; + bne lbl_801a68f0; + ori r5, r5, 0x100; +lbl_801a68f0: + rlwinm. r0, r4, 0, 0x19, 0x19; + bne lbl_801a68fc; + ori r5, r5, 0x1000; +lbl_801a68fc: + rlwinm. r0, r4, 0, 0x12, 0x12; + bne lbl_801a6908; + ori r5, r5, 0x200; +lbl_801a6908: + rlwinm. r0, r4, 0, 0x13, 0x13; + bne lbl_801a6914; + ori r5, r5, 0x400; +lbl_801a6914: + rlwinm. r0, r4, 0, 0x1a, 0x1a; + bne lbl_801a6920; + ori r5, r5, 0x2000; +lbl_801a6920: + rlwinm. r0, r4, 0, 0x1b, 0x1b; + bne lbl_801a692c; + ori r5, r5, 0x4000; +lbl_801a692c: + lis r4, 0xcc00; + rlwinm r3, r3, 0, 0x1c, 0x10; + stw r5, 0x3004(r4); + blr; + // clang-format on +} + +// Symbol: __OSMaskInterrupts +// PAL: 0x801a693c..0x801a69bc +u32 __OSMaskInterrupts(u32 global) { + int enabled; + u32 prev; + u32 local; + u32 mask; + + enabled = OSDisableInterrupts(); + prev = *(u32*)OSPhysicalToCached(OS_INTERRUPTMASK_ADDR); + local = *(u32*)OSPhysicalToCached(OS_UINTERRUPTMASK_ADDR); + mask = ~(prev | local) & global; + global |= prev; + *(u32*)OSPhysicalToCached(OS_INTERRUPTMASK_ADDR) = global; + while (mask) + mask = SetInterruptMask(mask, global | local); + OSRestoreInterrupts(enabled); + return prev; +} + +// Symbol: __OSUnmaskInterrupts +// PAL: 0x801a69bc..0x801a6a3c +MARK_BINARY_BLOB(__OSUnmaskInterrupts, 0x801a69bc, 0x801a6a3c); +asm u32 __OSUnmaskInterrupts(u32) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r3; + stw r30, 0x18(r1); + stw r29, 0x14(r1); + bl OSDisableInterrupts; + lis r4, 0x8000; + mr r30, r3; + lwz r29, 0xc4(r4); + lwz r5, 0xc8(r4); + or r0, r29, r5; + and r3, r31, r0; + andc r31, r29, r31; + stw r31, 0xc4(r4); + or r31, r31, r5; + b lbl_801a6a0c; +lbl_801a6a04: + mr r4, r31; + bl SetInterruptMask; +lbl_801a6a0c: + cmpwi r3, 0; + bne lbl_801a6a04; + mr r3, r30; + bl OSRestoreInterrupts; + lwz r31, 0x1c(r1); + mr r3, r29; + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +volatile OSTime __OSLastInterruptTime; +volatile s16 __OSLastInterrupt; +volatile u32 __OSLastInterruptSrr0; + +// Symbol: __OSDispatchInterrupt +// PAL: 0x801a6a3c..0x801a6ce0 +void __OSDispatchInterrupt(u8, OSContext* context) { + u32 intsr = __PIRegs[0]; + intsr &= ~0x00010000; + u32 mask = __PIRegs[1]; + if (intsr == 0 || (intsr & mask) == 0) + OSLoadContext(context); + u32 reg; + u32 bits = 0; + if (intsr & 0x00000080) { + reg = __MEMRegs[0x0000000f]; + if (reg & 0x00000001) + bits |= (0x80000000u >> 0); + if (reg & 0x00000002) + bits |= (0x80000000u >> 1); + if (reg & 0x00000004) + bits |= (0x80000000u >> 2); + if (reg & 0x00000008) + bits |= (0x80000000u >> 3); + if (reg & 0x00000010) + bits |= (0x80000000u >> 4); + } + if (intsr & 0x00000040) { + reg = __DSPRegs[0x00000005]; + if (reg & 0x00000008) + bits |= (0x80000000u >> 5); + if (reg & 0x00000020) + bits |= (0x80000000u >> 6); + if (reg & 0x00000080) + bits |= (0x80000000u >> 7); + } + if (intsr & 0x00000020) { + reg = __AIRegs[0x00000000]; + if (reg & 0x00000008) + bits |= (0x80000000u >> 8); + } + if (intsr & 0x00000010) { + reg = __EXIRegs[0x00000000]; + if (reg & 0x00000002) + bits |= (0x80000000u >> 9); + if (reg & 0x00000008) + bits |= (0x80000000u >> 10); + if (reg & 0x00000800) + bits |= (0x80000000u >> 11); + reg = __EXIRegs[0x00000005]; + if (reg & 0x00000002) + bits |= (0x80000000u >> 12); + if (reg & 0x00000008) + bits |= (0x80000000u >> 13); + if (reg & 0x00000800) + bits |= (0x80000000u >> 14); + reg = __EXIRegs[0x0000000a]; + if (reg & 0x00000002) + bits |= (0x80000000u >> 15); + if (reg & 0x00000008) + bits |= (0x80000000u >> 16); + } + if (intsr & 0x00002000) + bits |= (0x80000000u >> 26); + if (intsr & 0x00001000) + bits |= (0x80000000u >> 25); + if (intsr & 0x00000400) + bits |= (0x80000000u >> 19); + if (intsr & 0x00000200) + bits |= (0x80000000u >> 18); + if (intsr & 0x00000100) + bits |= (0x80000000u >> 24); + if (intsr & 0x00000008) + bits |= (0x80000000u >> 20); + if (intsr & 0x00000004) + bits |= (0x80000000u >> 21); + if (intsr & 0x00000002) + bits |= (0x80000000u >> 22); + if (intsr & 0x00000800) + bits |= (0x80000000u >> 17); + if (intsr & 0x00000001) + bits |= (0x80000000u >> 23); + if (intsr & 0x00004000) + bits |= (0x80000000u >> 27); + u32 local5 = bits & ~(*(u32*)OSPhysicalToCached(0x00C4) | + *(u32*)OSPhysicalToCached(0x00C8)); + if (local5) { + u32* prio; + s16 interrupt; + for (prio = OSInterruptPrioTable;; ++prio) { + if (local5 & *prio) { + interrupt = (u32)__cntlzw(local5 & *prio); + break; + } + } + __OSInterruptHandler handler = __OSGetInterruptHandler(interrupt); + if (handler) { + if (4 < interrupt) { + __OSLastInterrupt = interrupt; + __OSLastInterruptTime = OSGetTime(); + __OSLastInterruptSrr0 = context->srr0; + } + OSDisableScheduler(); + handler(interrupt, context); + OSEnableScheduler(); + __OSReschedule(); + OSLoadContext(context); + } + } + OSLoadContext(context); +} + +// Symbol: ExternalInterruptHandler +// PAL: 0x801a6ce0..0x801a6d30 +static asm void ExternalInterruptHandler(register u8 exception, + register OSContext* context) { + nofralloc; + stw r0, 0(r4); + stw r1, 4(r4); + stw r2, 8(r4); + stmw r6, 0x18(r4); + mfspr r0, 0x391; + stw r0, 0x1a8(r4); + mfspr r0, 0x392; + stw r0, 0x1ac(r4); + mfspr r0, 0x393; + stw r0, 0x1b0(r4); + mfspr r0, 0x394; + stw r0, 0x1b4(r4); + mfspr r0, 0x395; + stw r0, 0x1b8(r4); + mfspr r0, 0x396; + stw r0, 0x1bc(r4); + mfspr r0, 0x397; + stw r0, 0x1c0(r4); + stwu r1, -8(r1); + b __OSDispatchInterrupt; +} diff --git a/source/rvl/os/osInterrupt.h b/source/rvl/os/osInterrupt.h new file mode 100644 index 000000000..ae9b90170 --- /dev/null +++ b/source/rvl/os/osInterrupt.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include + +#include "osThread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define __OS_INTERRUPT_MEM_0 0 +#define __OS_INTERRUPT_MEM_1 1 +#define __OS_INTERRUPT_MEM_2 2 +#define __OS_INTERRUPT_MEM_3 3 +#define __OS_INTERRUPT_MEM_ADDRESS 4 +#define __OS_INTERRUPT_DSP_AI 5 +#define __OS_INTERRUPT_DSP_ARAM 6 +#define __OS_INTERRUPT_DSP_DSP 7 +#define __OS_INTERRUPT_AI_AI 8 +#define __OS_INTERRUPT_EXI_0_EXI 9 +#define __OS_INTERRUPT_EXI_0_TC 10 +#define __OS_INTERRUPT_EXI_0_EXT 11 +#define __OS_INTERRUPT_EXI_1_EXI 12 +#define __OS_INTERRUPT_EXI_1_TC 13 +#define __OS_INTERRUPT_EXI_1_EXT 14 +#define __OS_INTERRUPT_EXI_2_EXI 15 +#define __OS_INTERRUPT_EXI_2_TC 16 +#define __OS_INTERRUPT_PI_CP 17 +#define __OS_INTERRUPT_PI_PE_TOKEN 18 +#define __OS_INTERRUPT_PI_PE_FINISH 19 +#define __OS_INTERRUPT_PI_SI 20 +#define __OS_INTERRUPT_PI_DI 21 +#define __OS_INTERRUPT_PI_RSW 22 +#define __OS_INTERRUPT_PI_ERROR 23 +#define __OS_INTERRUPT_PI_VI 24 +#define __OS_INTERRUPT_PI_DEBUG 25 +#define __OS_INTERRUPT_PI_HSP 26 +#define __OS_INTERRUPT_PI_ACR 27 +#define __OS_INTERRUPT_MAX 32 + +#define OS_INTERRUPTMASK_MEM_0 (0x80000000u >> __OS_INTERRUPT_MEM_0) +#define OS_INTERRUPTMASK_MEM_1 (0x80000000u >> __OS_INTERRUPT_MEM_1) +#define OS_INTERRUPTMASK_MEM_2 (0x80000000u >> __OS_INTERRUPT_MEM_2) +#define OS_INTERRUPTMASK_MEM_3 (0x80000000u >> __OS_INTERRUPT_MEM_3) +#define OS_INTERRUPTMASK_MEM_ADDRESS (0x80000000u >> __OS_INTERRUPT_MEM_ADDRESS) +#define OS_INTERRUPTMASK_MEM \ + (OS_INTERRUPTMASK_MEM_0 | OS_INTERRUPTMASK_MEM_1 | OS_INTERRUPTMASK_MEM_2 | \ + OS_INTERRUPTMASK_MEM_3 | OS_INTERRUPTMASK_MEM_ADDRESS) +#define OS_INTERRUPTMASK_DSP_AI (0x80000000u >> __OS_INTERRUPT_DSP_AI) +#define OS_INTERRUPTMASK_DSP_ARAM (0x80000000u >> __OS_INTERRUPT_DSP_ARAM) +#define OS_INTERRUPTMASK_DSP_DSP (0x80000000u >> __OS_INTERRUPT_DSP_DSP) +#define OS_INTERRUPTMASK_DSP \ + (OS_INTERRUPTMASK_DSP_AI | OS_INTERRUPTMASK_DSP_ARAM | \ + OS_INTERRUPTMASK_DSP_DSP) +#define OS_INTERRUPTMASK_AI_AI (0x80000000u >> __OS_INTERRUPT_AI_AI) +#define OS_INTERRUPTMASK_AI (OS_INTERRUPTMASK_AI_AI) +#define OS_INTERRUPTMASK_EXI_0_EXI (0x80000000u >> __OS_INTERRUPT_EXI_0_EXI) +#define OS_INTERRUPTMASK_EXI_0_TC (0x80000000u >> __OS_INTERRUPT_EXI_0_TC) +#define OS_INTERRUPTMASK_EXI_0_EXT (0x80000000u >> __OS_INTERRUPT_EXI_0_EXT) +#define OS_INTERRUPTMASK_EXI_0 \ + (OS_INTERRUPTMASK_EXI_0_EXI | OS_INTERRUPTMASK_EXI_0_TC | \ + OS_INTERRUPTMASK_EXI_0_EXT) +#define OS_INTERRUPTMASK_EXI_1_EXI (0x80000000u >> __OS_INTERRUPT_EXI_1_EXI) +#define OS_INTERRUPTMASK_EXI_1_TC (0x80000000u >> __OS_INTERRUPT_EXI_1_TC) +#define OS_INTERRUPTMASK_EXI_1_EXT (0x80000000u >> __OS_INTERRUPT_EXI_1_EXT) +#define OS_INTERRUPTMASK_EXI_1 \ + (OS_INTERRUPTMASK_EXI_1_EXI | OS_INTERRUPTMASK_EXI_1_TC | \ + OS_INTERRUPTMASK_EXI_1_EXT) +#define OS_INTERRUPTMASK_EXI_2_EXI (0x80000000u >> __OS_INTERRUPT_EXI_2_EXI) +#define OS_INTERRUPTMASK_EXI_2_TC (0x80000000u >> __OS_INTERRUPT_EXI_2_TC) +#define OS_INTERRUPTMASK_EXI \ + (OS_INTERRUPTMASK_EXI_0_EXI | OS_INTERRUPTMASK_EXI_0_TC | \ + OS_INTERRUPTMASK_EXI_0_EXT | OS_INTERRUPTMASK_EXI_1_EXI | \ + OS_INTERRUPTMASK_EXI_1_TC | OS_INTERRUPTMASK_EXI_1_EXT | \ + OS_INTERRUPTMASK_EXI_2_EXI | OS_INTERRUPTMASK_EXI_2_TC) +#define OS_INTERRUPTMASK_PI_PE_TOKEN (0x80000000u >> __OS_INTERRUPT_PI_PE_TOKEN) +#define OS_INTERRUPTMASK_PI_PE_FINISH \ + (0x80000000u >> __OS_INTERRUPT_PI_PE_FINISH) +#define OS_INTERRUPTMASK_PI_PE \ + (OS_INTERRUPTMASK_PI_PE_TOKEN | OS_INTERRUPTMASK_PI_PE_FINISH) +#define OS_INTERRUPTMASK_PI_CP (0x80000000u >> __OS_INTERRUPT_PI_CP) +#define OS_INTERRUPTMASK_PI_SI (0x80000000u >> __OS_INTERRUPT_PI_SI) +#define OS_INTERRUPTMASK_PI_DI (0x80000000u >> __OS_INTERRUPT_PI_DI) +#define OS_INTERRUPTMASK_PI_RSW (0x80000000u >> __OS_INTERRUPT_PI_RSW) +#define OS_INTERRUPTMASK_PI_ERROR (0x80000000u >> __OS_INTERRUPT_PI_ERROR) +#define OS_INTERRUPTMASK_PI_VI (0x80000000u >> __OS_INTERRUPT_PI_VI) +#define OS_INTERRUPTMASK_PI_DEBUG (0x80000000u >> __OS_INTERRUPT_PI_DEBUG) +#define OS_INTERRUPTMASK_PI_HSP (0x80000000u >> __OS_INTERRUPT_PI_HSP) +#define OS_INTERRUPTMASK_PI_ACR (0x80000000u >> __OS_INTERRUPT_PI_ACR) +#define OS_INTERRUPTMASK_PI \ + (OS_INTERRUPTMASK_PI_CP | OS_INTERRUPTMASK_PI_SI | OS_INTERRUPTMASK_PI_DI | \ + OS_INTERRUPTMASK_PI_RSW | OS_INTERRUPTMASK_PI_ERROR | \ + OS_INTERRUPTMASK_PI_VI | OS_INTERRUPTMASK_PI_PE_TOKEN | \ + OS_INTERRUPTMASK_PI_PE_FINISH | OS_INTERRUPTMASK_PI_DEBUG | \ + OS_INTERRUPTMASK_PI_HSP | OS_INTERRUPTMASK_PI_ACR) + +typedef void (*__OSInterruptHandler)(s16 interrupt, OSContext* context); + +// PAL: 0x801a65ac..0x801a65c0 +int OSDisableInterrupts(void); +// PAL: 0x801a65c0..0x801a65d4 +int OSEnableInterrupts(void); +// PAL: 0x801a65d4..0x801a65f8 +int OSRestoreInterrupts(int level); +// PAL: 0x801a65f8..0x801a660c +__OSInterruptHandler __OSSetInterruptHandler(s16 interrupt, + __OSInterruptHandler handler); +// PAL: 0x801a660c..0x801a661c +__OSInterruptHandler __OSGetInterruptHandler(s16 interrupt); +// PAL: 0x801a661c..0x801a66e0 +void __OSInterruptInit(void); +// PAL: 0x801a693c..0x801a69bc +u32 __OSMaskInterrupts(u32); +// PAL: 0x801a69bc..0x801a6a3c +u32 __OSUnmaskInterrupts(u32); +// PAL: 0x801a6a3c..0x801a6ce0 +void __OSDispatchInterrupt(u8 exception, OSContext* context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osMessage.h b/source/rvl/os/osMessage.h new file mode 100644 index 000000000..0a053897c --- /dev/null +++ b/source/rvl/os/osMessage.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/os/osMutex.h b/source/rvl/os/osMutex.h new file mode 100644 index 000000000..75edd9986 --- /dev/null +++ b/source/rvl/os/osMutex.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char _[0x18]; +} OSMutex; + +void OSInitMutex(OSMutex*); +void OSLockMutex(OSMutex*); +void OSUnlockMutex(OSMutex*); + +#ifdef __cplusplus +} +#endif diff --git a/source/rvl/os/osReset.c b/source/rvl/os/osReset.c new file mode 100644 index 000000000..8c758c434 --- /dev/null +++ b/source/rvl/os/osReset.c @@ -0,0 +1,771 @@ +#include "osReset.h" + +#include + +#include "os.h" +#include "osError.h" +#include "osInterrupt.h" +#include "osThread.h" + +// Extern function references. +// PAL: 0x8016322c +extern UNKNOWN_FUNCTION(__DVDGetCoverStatus); +// PAL: 0x80163460 +extern UNKNOWN_FUNCTION(__DVDPrepareReset); +// PAL: 0x8019f2d0 +extern UNKNOWN_FUNCTION(__OSGetIOSRev); +// PAL: 0x801a0598 +extern UNKNOWN_FUNCTION(OSGetAppType); +// PAL: 0x801a1520 +extern UNKNOWN_FUNCTION(__OSStopAudioSystem); +// PAL: 0x801a186c +extern UNKNOWN_FUNCTION(LCDisable); +// PAL: 0x801a3888 +extern UNKNOWN_FUNCTION(__OSLaunchMenu); +// PAL: 0x801a81b8 +extern UNKNOWN_FUNCTION(__OSReboot); +// PAL: 0x801a90b4 +extern UNKNOWN_FUNCTION(__OSSyncSram); +// PAL: 0x801a92fc +extern UNKNOWN_FUNCTION(__OSGetRTCFlags); +// PAL: 0x801a9418 +extern UNKNOWN_FUNCTION(__OSClearRTCFlags); +// PAL: 0x801a98e8 +extern UNKNOWN_FUNCTION(OSDisableScheduler); +// PAL: 0x801a9924 +extern UNKNOWN_FUNCTION(OSEnableScheduler); +// PAL: 0x801ab848 +extern UNKNOWN_FUNCTION(__OSInitSTM); +// PAL: 0x801ab960 +extern UNKNOWN_FUNCTION(__OSShutdownToSBY); +// PAL: 0x801ab9d8 +extern UNKNOWN_FUNCTION(__OSHotReset); +// PAL: 0x801abb80 +extern UNKNOWN_FUNCTION(__OSUnRegisterStateEvent); +// PAL: 0x801ac274 +extern UNKNOWN_FUNCTION(__OSStopPlayRecord); +// PAL: 0x801ac45c +extern UNKNOWN_FUNCTION(__OSWriteStateFlags); +// PAL: 0x801ac540 +extern UNKNOWN_FUNCTION(__OSReadStateFlags); +// PAL: 0x801ad800 +extern UNKNOWN_FUNCTION(__OSRelaunchTitle); +// PAL: 0x801ae06c +extern UNKNOWN_FUNCTION(__OSReturnToMenul); +// PAL: 0x801b0124 +extern UNKNOWN_FUNCTION(__PADDisableRecalibration); +// PAL: 0x801b0180 +extern UNKNOWN_FUNCTION(SCInit); +// PAL: 0x801b0220 +extern UNKNOWN_FUNCTION(SCCheckStatus); +// PAL: 0x801b1d00 +extern UNKNOWN_FUNCTION(SCGetIdleMode); +// PAL: 0x801bcd4c +extern UNKNOWN_FUNCTION(unk_801bcd4c); + +// Symbol: OSRegisterShutdownFunction +// Function signature is unknown. +// PAL: 0x801a8238..0x801a82c0 +MARK_BINARY_BLOB(OSRegisterShutdownFunction, 0x801a8238, 0x801a82c0); +asm UNKNOWN_FUNCTION(OSRegisterShutdownFunction) { + // clang-format off + nofralloc; + lwz r5, -0x62f0(r13); + b lbl_801a8244; +lbl_801a8240: + lwz r5, 8(r5); +lbl_801a8244: + cmpwi r5, 0; + beq lbl_801a825c; + lwz r4, 4(r5); + lwz r0, 4(r3); + cmplw r4, r0; + ble lbl_801a8240; +lbl_801a825c: + cmpwi r5, 0; + bne lbl_801a8298; + addi r4, r13, -25328; + lwz r4, 4(r4); + cmpwi r4, 0; + bne lbl_801a827c; + stw r3, -0x62f0(r13); + b lbl_801a8280; +lbl_801a827c: + stw r3, 8(r4); +lbl_801a8280: + li r0, 0; + stw r4, 0xc(r3); + addi r4, r13, -25328; + stw r0, 8(r3); + stw r3, 4(r4); + blr; +lbl_801a8298: + stw r5, 8(r3); + lwz r4, 0xc(r5); + stw r3, 0xc(r5); + cmpwi r4, 0; + stw r4, 0xc(r3); + bne lbl_801a82b8; + stw r3, -0x62f0(r13); + blr; +lbl_801a82b8: + stw r3, 8(r4); + blr; + // clang-format on +} + +// Symbol: __OSCallShutdownFunctions +// Function signature is unknown. +// PAL: 0x801a82c0..0x801a8370 +MARK_BINARY_BLOB(__OSCallShutdownFunctions, 0x801a82c0, 0x801a8370); +asm UNKNOWN_FUNCTION(__OSCallShutdownFunctions) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + li r30, 0; + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + li r3, 0; + lwz r31, -0x62f0(r13); + b lbl_801a8330; +lbl_801a82f4: + cmpwi r30, 0; + beq lbl_801a8308; + lwz r0, 4(r31); + cmplw r3, r0; + bne lbl_801a8338; +lbl_801a8308: + lwz r12, 0(r31); + mr r3, r28; + mr r4, r29; + mtctr r12; + bctrl; + cntlzw r0, r3; + lwz r3, 4(r31); + srwi r0, r0, 5; + lwz r31, 8(r31); + or r30, r30, r0; +lbl_801a8330: + cmpwi r31, 0; + bne lbl_801a82f4; +lbl_801a8338: + bl __OSSyncSram; + cntlzw r0, r3; + lwz r31, 0x1c(r1); + srwi r0, r0, 5; + or r30, r30, r0; + cntlzw r0, r30; + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + srwi r3, r0, 5; + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSShutdownDevices +// Function signature is unknown. +// PAL: 0x801a8370..0x801a8500 +MARK_BINARY_BLOB(__OSShutdownDevices, 0x801a8370, 0x801a8500); +asm UNKNOWN_FUNCTION(__OSShutdownDevices) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_27; + cmpwi r3, 5; + mr r27, r3; + bge lbl_801a839c; + cmpwi r3, 0; + beq lbl_801a83a4; + b lbl_801a83ac; +lbl_801a839c: + cmpwi r3, 7; + bge lbl_801a83ac; +lbl_801a83a4: + li r28, 0; + b lbl_801a83b0; +lbl_801a83ac: + li r28, 1; +lbl_801a83b0: + bl __OSStopAudioSystem; + cmpwi r28, 0; + bne lbl_801a83c8; + li r3, 1; + bl __PADDisableRecalibration; + mr r29, r3; +lbl_801a83c8: + lwz r30, -0x62f0(r13); + li r3, 0; + li r31, 0; + b lbl_801a8414; +lbl_801a83d8: + cmpwi r31, 0; + beq lbl_801a83ec; + lwz r0, 4(r30); + cmplw r3, r0; + bne lbl_801a841c; +lbl_801a83ec: + lwz r12, 0(r30); + mr r4, r27; + li r3, 0; + mtctr r12; + bctrl; + cntlzw r0, r3; + lwz r3, 4(r30); + srwi r0, r0, 5; + lwz r30, 8(r30); + or r31, r31, r0; +lbl_801a8414: + cmpwi r30, 0; + bne lbl_801a83d8; +lbl_801a841c: + bl __OSSyncSram; + cntlzw r0, r3; + srwi r0, r0, 5; + or. r31, r31, r0; + bne lbl_801a83c8; +lbl_801a8430: + bl __OSSyncSram; + cmpwi r3, 0; + beq lbl_801a8430; + bl OSDisableInterrupts; + lwz r31, -0x62f0(r13); + li r3, 0; + li r30, 0; + b lbl_801a848c; +lbl_801a8450: + cmpwi r30, 0; + beq lbl_801a8464; + lwz r0, 4(r31); + cmplw r3, r0; + bne lbl_801a8494; +lbl_801a8464: + lwz r12, 0(r31); + mr r4, r27; + li r3, 1; + mtctr r12; + bctrl; + cntlzw r0, r3; + lwz r3, 4(r31); + srwi r0, r0, 5; + lwz r31, 8(r31); + or r30, r30, r0; +lbl_801a848c: + cmpwi r31, 0; + bne lbl_801a8450; +lbl_801a8494: + bl __OSSyncSram; + bl LCDisable; + cmpwi r28, 0; + bne lbl_801a84ac; + mr r3, r29; + bl __PADDisableRecalibration; +lbl_801a84ac: + lis r3, 0x8000; + lwz r3, 0xdc(r3); + b lbl_801a84e0; +lbl_801a84b8: + lhz r0, 0x2c8(r3); + lwz r30, 0x2fc(r3); + cmpwi r0, 4; + beq lbl_801a84d8; + bge lbl_801a84dc; + cmpwi r0, 1; + beq lbl_801a84d8; + b lbl_801a84dc; +lbl_801a84d8: + bl OSCancelThread; +lbl_801a84dc: + mr r3, r30; +lbl_801a84e0: + cmpwi r3, 0; + bne lbl_801a84b8; + addi r11, r1, 0x20; + bl _restgpr_27; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSGetDiscState +// Function signature is unknown. +// PAL: 0x801a8500..0x801a856c +MARK_BINARY_BLOB(__OSGetDiscState, 0x801a8500, 0x801a856c); +asm UNKNOWN_FUNCTION(__OSGetDiscState) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r3; + bl __DVDGetCoverStatus; + cmplwi r3, 2; + beq lbl_801a8528; + li r3, 3; + b lbl_801a8558; +lbl_801a8528: + cmplwi r31, 1; + bne lbl_801a8554; + addi r3, r1, 8; + bl __OSGetRTCFlags; + cmpwi r3, 0; + beq lbl_801a8554; + lwz r0, 8(r1); + cmpwi r0, 0; + bne lbl_801a8554; + li r3, 1; + b lbl_801a8558; +lbl_801a8554: + li r3, 2; +lbl_801a8558: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSShutdownSystem +// Function signature is unknown. +// PAL: 0x801a856c..0x801a8688 +MARK_BINARY_BLOB(OSShutdownSystem, 0x801a856c, 0x801a8688); +asm UNKNOWN_FUNCTION(OSShutdownSystem) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + mflr r0; + li r4, 0; + li r5, 2; + stw r0, 0x44(r1); + addi r3, r1, 8; + stw r31, 0x3c(r1); + bl memset; + bl SCInit; +lbl_801a8590: + bl SCCheckStatus; + cmplwi r3, 1; + beq lbl_801a8590; + addi r3, r1, 8; + bl SCGetIdleMode; + bl __OSStopPlayRecord; + bl __OSUnRegisterStateEvent; + bl __DVDPrepareReset; + addi r3, r1, 0x18; + bl __OSReadStateFlags; + lbz r31, 0x1e(r1); + bl __DVDGetCoverStatus; + cmplwi r3, 2; + beq lbl_801a85d0; + li r3, 3; + b lbl_801a8600; +lbl_801a85d0: + cmplwi r31, 1; + bne lbl_801a85fc; + addi r3, r1, 0xc; + bl __OSGetRTCFlags; + cmpwi r3, 0; + beq lbl_801a85fc; + lwz r0, 0xc(r1); + cmpwi r0, 0; + bne lbl_801a85fc; + li r3, 1; + b lbl_801a8600; +lbl_801a85fc: + li r3, 2; +lbl_801a8600: + lbz r0, 8(r1); + stb r3, 0x1e(r1); + cmplwi r0, 1; + bne lbl_801a861c; + li r0, 5; + stb r0, 0x1d(r1); + b lbl_801a8624; +lbl_801a861c: + li r0, 1; + stb r0, 0x1d(r1); +lbl_801a8624: + bl __OSClearRTCFlags; + addi r3, r1, 0x18; + bl __OSWriteStateFlags; + addi r3, r1, 0x10; + bl __OSGetIOSRev; + lbz r0, 8(r1); + cmplwi r0, 1; + bne lbl_801a8664; + li r0, 1; + stw r0, -0x62f4(r13); + bl OSDisableScheduler; + li r3, 5; + bl __OSShutdownDevices; + bl OSEnableScheduler; + bl __OSLaunchMenu; + b lbl_801a8674; +lbl_801a8664: + bl OSDisableScheduler; + li r3, 2; + bl __OSShutdownDevices; + bl __OSShutdownToSBY; +lbl_801a8674: + lwz r0, 0x44(r1); + lwz r31, 0x3c(r1); + mtlr r0; + addi r1, r1, 0x40; + blr; + // clang-format on +} + +// Symbol: OSRestart +// Function signature is unknown. +// PAL: 0x801a8688..0x801a8758 +MARK_BINARY_BLOB(OSRestart, 0x801a8688, 0x801a8758); +asm UNKNOWN_FUNCTION(OSRestart) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + bl OSGetAppType; + mr r31, r3; + bl __OSStopPlayRecord; + bl __OSUnRegisterStateEvent; + clrlwi r0, r31, 0x18; + cmplwi r0, 0x81; + bne lbl_801a86d4; + bl OSDisableScheduler; + li r3, 4; + bl __OSShutdownDevices; + bl OSEnableScheduler; + bl __OSRelaunchTitle; + b lbl_801a86f8; +lbl_801a86d4: + cmplwi r0, 0x80; + bne lbl_801a86f8; + bl OSDisableScheduler; + li r3, 4; + bl __OSShutdownDevices; + bl OSEnableScheduler; + lwz r4, -0x62f8(r13); + mr r3, r30; + bl __OSReboot; +lbl_801a86f8: + bl OSDisableScheduler; + li r3, 1; + bl __OSShutdownDevices; + lwz r0, -0x63a4(r13); + cmpwi r0, 0; + bne lbl_801a871c; + lwz r0, -0x6334(r13); + cmpwi r0, 0; + beq lbl_801a8720; +lbl_801a871c: + bl __OSInitSTM; +lbl_801a8720: + bl __OSHotReset; + lis r3, 0x8029; + lis r5, 0x8029; + addi r3, r3, 0x668; + li r4, 0x3d0; + addi r5, r5, 0x674; + crclr 6; + bl OSPanic; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSReturnToMenu +// Function signature is unknown. +// PAL: 0x801a8758..0x801a8858 +MARK_BINARY_BLOB(__OSReturnToMenu, 0x801a8758, 0x801a8858); +asm UNKNOWN_FUNCTION(__OSReturnToMenu) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + mflr r0; + stw r0, 0x44(r1); + stw r31, 0x3c(r1); + stw r30, 0x38(r1); + mr r30, r3; + bl __OSStopPlayRecord; + bl __OSUnRegisterStateEvent; + bl __DVDPrepareReset; + addi r3, r1, 0x10; + bl __OSReadStateFlags; + lbz r31, 0x16(r1); + bl __DVDGetCoverStatus; + cmplwi r3, 2; + beq lbl_801a879c; + li r3, 3; + b lbl_801a87cc; +lbl_801a879c: + cmplwi r31, 1; + bne lbl_801a87c8; + addi r3, r1, 8; + bl __OSGetRTCFlags; + cmpwi r3, 0; + beq lbl_801a87c8; + lwz r0, 8(r1); + cmpwi r0, 0; + bne lbl_801a87c8; + li r3, 1; + b lbl_801a87cc; +lbl_801a87c8: + li r3, 2; +lbl_801a87cc: + li r0, 3; + stb r3, 0x16(r1); + stb r0, 0x15(r1); + stb r30, 0x17(r1); + bl __OSClearRTCFlags; + addi r3, r1, 0x10; + bl __OSWriteStateFlags; + bl OSDisableScheduler; + li r3, 5; + bl __OSShutdownDevices; + bl OSEnableScheduler; + bl __OSLaunchMenu; + bl OSDisableScheduler; + bl unk_801bcd4c; + lwz r0, -0x63a4(r13); + cmpwi r0, 0; + bne lbl_801a881c; + lwz r0, -0x6334(r13); + cmpwi r0, 0; + beq lbl_801a8820; +lbl_801a881c: + bl __OSInitSTM; +lbl_801a8820: + bl __OSHotReset; + lis r3, 0x8029; + lis r5, 0x8029; + addi r3, r3, 0x668; + li r4, 0x3d0; + addi r5, r5, 0x674; + crclr 6; + bl OSPanic; + lwz r0, 0x44(r1); + lwz r31, 0x3c(r1); + lwz r30, 0x38(r1); + mtlr r0; + addi r1, r1, 0x40; + blr; + // clang-format on +} + +// Symbol: OSReturnToMenu +// Function signature is unknown. +// PAL: 0x801a8858..0x801a8898 +MARK_BINARY_BLOB(OSReturnToMenu, 0x801a8858, 0x801a8898); +asm UNKNOWN_FUNCTION(OSReturnToMenu) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + li r3, 0; + stw r0, 0x14(r1); + bl __OSReturnToMenu; + lis r3, 0x8029; + lis r5, 0x8029; + addi r3, r3, 0x668; + li r4, 0x345; + addi r5, r5, 0x6a0; + crclr 6; + bl OSPanic; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSReturnToSetting +// Function signature is unknown. +// PAL: 0x801a8898..0x801a8954 +MARK_BINARY_BLOB(OSReturnToSetting, 0x801a8898, 0x801a8954); +asm UNKNOWN_FUNCTION(OSReturnToSetting) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmplwi r3, 7; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + lis r31, 0x8029; + addi r31, r31, 0x668; + bgt lbl_801a8908; + lis r4, 0x8029; + slwi r0, r3, 2; + addi r4, r4, 0x820; + lwzx r4, r4, r0; + mtctr r4; + bctr; + addi r31, r31, 0xa0; + b lbl_801a892c; + addi r31, r31, 0xc0; + b lbl_801a892c; + addi r31, r31, 0xdc; + b lbl_801a892c; + addi r31, r31, 0xf4; + b lbl_801a892c; + addi r31, r31, 0x124; + b lbl_801a892c; + addi r31, r31, 0x144; + b lbl_801a892c; + addi r31, r31, 0x16c; + b lbl_801a892c; +lbl_801a8908: + mr r4, r3; + addi r3, r31, 0x188; + crclr 6; + bl OSReport; + addi r3, r31, 0; + li r4, 0x391; + addi r5, r13, -28968; + crclr 6; + bl OSPanic; +lbl_801a892c: + mr r4, r31; + li r3, 1; + li r5, 0; + crclr 6; + bl __OSReturnToMenul; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSReturnToMenuForError +// Function signature is unknown. +// PAL: 0x801a8954..0x801a89f8 +MARK_BINARY_BLOB(__OSReturnToMenuForError, 0x801a8954, 0x801a89f8); +asm UNKNOWN_FUNCTION(__OSReturnToMenuForError) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r3, r1, 8; + stw r31, 0x2c(r1); + lis r31, 0x8029; + addi r31, r31, 0x668; + bl __OSReadStateFlags; + li r3, 2; + li r0, 3; + stb r3, 0xe(r1); + stb r0, 0xd(r1); + bl __OSClearRTCFlags; + addi r3, r1, 8; + bl __OSWriteStateFlags; + bl __OSLaunchMenu; + bl OSDisableScheduler; + bl unk_801bcd4c; + lwz r0, -0x63a4(r13); + cmpwi r0, 0; + bne lbl_801a89b4; + lwz r0, -0x6334(r13); + cmpwi r0, 0; + beq lbl_801a89b8; +lbl_801a89b4: + bl __OSInitSTM; +lbl_801a89b8: + bl __OSHotReset; + addi r3, r31, 0; + addi r5, r31, 0xc; + li r4, 0x3d0; + crclr 6; + bl OSPanic; + addi r3, r31, 0; + addi r5, r31, 0x1d8; + li r4, 0x3b8; + crclr 6; + bl OSPanic; + lwz r0, 0x34(r1); + lwz r31, 0x2c(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: __OSHotResetForError +// Function signature is unknown. +// PAL: 0x801a89f8..0x801a8a50 +MARK_BINARY_BLOB(__OSHotResetForError, 0x801a89f8, 0x801a8a50); +asm UNKNOWN_FUNCTION(__OSHotResetForError) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + lwz r0, -0x63a4(r13); + cmpwi r0, 0; + bne lbl_801a8a1c; + lwz r0, -0x6334(r13); + cmpwi r0, 0; + beq lbl_801a8a20; +lbl_801a8a1c: + bl __OSInitSTM; +lbl_801a8a20: + bl __OSHotReset; + lis r3, 0x8029; + lis r5, 0x8029; + addi r3, r3, 0x668; + li r4, 0x3d0; + addi r5, r5, 0x674; + crclr 6; + bl OSPanic; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSGetResetCode +// Function signature is unknown. +// PAL: 0x801a8a50..0x801a8a80 +MARK_BINARY_BLOB(OSGetResetCode, 0x801a8a50, 0x801a8a80); +asm UNKNOWN_FUNCTION(OSGetResetCode) { + // clang-format off + nofralloc; + lis r3, 0x8034; + lwz r0, 0x7080(r3); + cmpwi r0, 0; + beq lbl_801a8a70; + addi r3, r3, 0x7080; + lwz r0, 4(r3); + oris r3, r0, 0x8000; + blr; +lbl_801a8a70: + lis r3, 0xcc00; + lwz r0, 0x3024(r3); + srwi r3, r0, 3; + blr; + // clang-format on +} diff --git a/source/rvl/os/osReset.h b/source/rvl/os/osReset.h new file mode 100644 index 000000000..1418f5698 --- /dev/null +++ b/source/rvl/os/osReset.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a8238..0x801a82c0 +UNKNOWN_FUNCTION(OSRegisterShutdownFunction); +// PAL: 0x801a82c0..0x801a8370 +UNKNOWN_FUNCTION(__OSCallShutdownFunctions); +// PAL: 0x801a8370..0x801a8500 +UNKNOWN_FUNCTION(__OSShutdownDevices); +// PAL: 0x801a8500..0x801a856c +UNKNOWN_FUNCTION(__OSGetDiscState); +// PAL: 0x801a856c..0x801a8688 +UNKNOWN_FUNCTION(OSShutdownSystem); +// PAL: 0x801a8688..0x801a8758 +UNKNOWN_FUNCTION(OSRestart); +// PAL: 0x801a8758..0x801a8858 +UNKNOWN_FUNCTION(__OSReturnToMenu); +// PAL: 0x801a8858..0x801a8898 +UNKNOWN_FUNCTION(OSReturnToMenu); +// PAL: 0x801a8898..0x801a8954 +UNKNOWN_FUNCTION(OSReturnToSetting); +// PAL: 0x801a8954..0x801a89f8 +UNKNOWN_FUNCTION(__OSReturnToMenuForError); +// PAL: 0x801a89f8..0x801a8a50 +UNKNOWN_FUNCTION(__OSHotResetForError); +// PAL: 0x801a8a50..0x801a8a80 +UNKNOWN_FUNCTION(OSGetResetCode); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osThread.c b/source/rvl/os/osThread.c new file mode 100644 index 000000000..1bc897ddb --- /dev/null +++ b/source/rvl/os/osThread.c @@ -0,0 +1,1977 @@ +#include "osThread.h" + +#include "os.h" +#include "osInterrupt.h" + +// Extern function references. +// PAL: 0x801a0610 +extern UNKNOWN_FUNCTION(OSCreateAlarm); +// PAL: 0x801a0870 +extern UNKNOWN_FUNCTION(OSSetAlarm); +// PAL: 0x801a0964 +extern UNKNOWN_FUNCTION(OSCancelAlarm); +// PAL: 0x801a0cf8 +extern UNKNOWN_FUNCTION(unk_801a0cf8); +// PAL: 0x801a0d8c +extern UNKNOWN_FUNCTION(OSSetAlarmUserData); +// PAL: 0x801a0d94 +extern UNKNOWN_FUNCTION(OSGetAlarmUserData); +// PAL: 0x801a1e70 +extern UNKNOWN_FUNCTION(OSSetCurrentContext); +// PAL: 0x801a1ecc +extern UNKNOWN_FUNCTION(OSGetCurrentContext); +// PAL: 0x801a1ed8 +extern UNKNOWN_FUNCTION(OSSaveContext); +// PAL: 0x801a1f58 +extern void OSLoadContext ( OSContext* context ); +// PAL: 0x801a2030 +extern UNKNOWN_FUNCTION(OSGetStackPointer); +// PAL: 0x801a2098 +extern UNKNOWN_FUNCTION(OSClearContext); +// PAL: 0x801a20bc +extern UNKNOWN_FUNCTION(OSInitContext); +// PAL: 0x801a8088 +extern UNKNOWN_FUNCTION(__OSUnlockAllMutex); + +// Symbol: OSSetSwitchThreadCallback +// PAL: 0x801a95ac..0x801a961c +MARK_BINARY_BLOB(OSSetSwitchThreadCallback, 0x801a95ac, 0x801a961c); +asm OSSwitchFunction OSSetSwitchThreadCallback(OSSwitchFunction callable) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + bl OSDisableInterrupts; + cmpwi r30, 0; + lwz r31, -0x7120(r13); + beq lbl_801a95d8; + b lbl_801a95e0; +lbl_801a95d8: + lis r30, 0x801b; + addi r30, r30, -27224; +lbl_801a95e0: + stw r30, -0x7120(r13); + bl OSRestoreInterrupts; + lis r3, 0x801b; + addi r3, r3, -27224; + cmplw r31, r3; + bne lbl_801a9600; + li r3, 0; + b lbl_801a9604; +lbl_801a9600: + mr r3, r31; +lbl_801a9604: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSThreadInit +// Function signature is unknown. +// PAL: 0x801a961c..0x801a98a0 +MARK_BINARY_BLOB(__OSThreadInit, 0x801a961c, 0x801a98a0); +asm UNKNOWN_FUNCTION(__OSThreadInit) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + li r3, 2; + li r6, 1; + stw r0, 0x24(r1); + li r4, 0; + li r5, 0x10; + li r0, -1; + stw r31, 0x1c(r1); + lis r31, 0x8034; + addi r31, r31, 0x7498; + stw r30, 0x18(r1); + addi r30, r31, 0; + stw r29, 0x14(r1); + lis r29, 0x8000; + sth r3, 0x2c8(r30); + mr r3, r30; + sth r6, 0x2ca(r30); + stw r5, 0x2d4(r30); + stw r5, 0x2d0(r30); + stw r4, 0x2cc(r30); + stw r0, 0x2d8(r30); + stw r4, 0x2f0(r30); + stw r4, 0x2ec(r30); + stw r4, 0x2e8(r30); + stw r4, 0x2f8(r30); + stw r4, 0x2f4(r30); + stw r30, 0xd8(r29); + bl OSClearContext; + mr r3, r30; + bl OSSetCurrentContext; + lis r4, 0x803a; + lis r5, 0x8039; + addi r4, r4, -28288; + lis r3, 0xdeae; + addi r5, r5, -28292; + stw r4, 0x304(r30); + addi r0, r3, -17730; + mr r4, r30; + stw r5, 0x308(r30); + stw r0, 0(r5); + lwz r12, -0x7120(r13); + lwz r3, 0xe4(r29); + mtctr r12; + bctrl; + stw r30, 0xe4(r29); + bl OSGetStackPointer; + lwz r4, 0xe4(r29); + lwz r4, 0x308(r4); + addi r8, r4, 4; + cmplw cr1, r8, r3; + bge cr1, lbl_801a97a8; + subf r5, r8, r3; + addi r6, r3, -32; + addi r4, r5, 3; + srawi r0, r4, 2; + addze r0, r0; + cmpwi r0, 8; + ble lbl_801a9780; + li r7, 0; + bgt cr1, lbl_801a9734; + rlwinm. r0, r5, 0, 0, 0; + li r5, 1; + bne lbl_801a9728; + rlwinm. r0, r4, 0, 0, 0; + beq lbl_801a9728; + li r5, 0; +lbl_801a9728: + cmpwi r5, 0; + beq lbl_801a9734; + li r7, 1; +lbl_801a9734: + cmpwi r7, 0; + beq lbl_801a9780; + addi r0, r6, 0x1f; + li r4, 0; + subf r0, r8, r0; + srwi r0, r0, 5; + mtctr r0; + cmplw r8, r6; + bge lbl_801a9780; +lbl_801a9758: + stw r4, 0(r8); + stw r4, 4(r8); + stw r4, 8(r8); + stw r4, 0xc(r8); + stw r4, 0x10(r8); + stw r4, 0x14(r8); + stw r4, 0x18(r8); + stw r4, 0x1c(r8); + addi r8, r8, 0x20; + bdnz lbl_801a9758; +lbl_801a9780: + addi r0, r3, 3; + li r4, 0; + subf r0, r8, r0; + srwi r0, r0, 2; + mtctr r0; + cmplw r8, r3; + bge lbl_801a97a8; +lbl_801a979c: + stw r4, 0(r8); + addi r8, r8, 4; + bdnz lbl_801a979c; +lbl_801a97a8: + li r3, 0; + li r0, 2; + stw r3, -0x62e0(r13); + addi r4, r31, 0x318; + stw r3, -0x62e4(r13); + mtctr r0; +lbl_801a97c0: + stw r3, 4(r4); + stw r3, 0(r4); + stw r3, 0xc(r4); + stw r3, 8(r4); + stw r3, 0x14(r4); + stw r3, 0x10(r4); + stw r3, 0x1c(r4); + stw r3, 0x18(r4); + stw r3, 0x24(r4); + stw r3, 0x20(r4); + stw r3, 0x2c(r4); + stw r3, 0x28(r4); + stw r3, 0x34(r4); + stw r3, 0x30(r4); + stw r3, 0x3c(r4); + stw r3, 0x38(r4); + stw r3, 0x44(r4); + stw r3, 0x40(r4); + stw r3, 0x4c(r4); + stw r3, 0x48(r4); + stw r3, 0x54(r4); + stw r3, 0x50(r4); + stw r3, 0x5c(r4); + stw r3, 0x58(r4); + stw r3, 0x64(r4); + stw r3, 0x60(r4); + stw r3, 0x6c(r4); + stw r3, 0x68(r4); + stw r3, 0x74(r4); + stw r3, 0x70(r4); + stw r3, 0x7c(r4); + stw r3, 0x78(r4); + addi r4, r4, 0x80; + bdnz lbl_801a97c0; + li r4, 0; + lis r3, 0x8000; + stw r4, 0xe0(r3); + stw r4, 0xdc(r3); + stw r30, 0xdc(r3); + b lbl_801a9864; + stw r30, 0x2fc(r4); +lbl_801a9864: + li r29, 0; + stw r4, 0x300(r30); + lis r4, 0x8000; + addi r3, r31, 0x418; + stw r29, 0x2fc(r30); + stw r30, 0xe0(r4); + bl OSClearContext; + stw r29, -0x62e8(r13); + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSInitThreadQueue +// Function signature is unknown. +// PAL: 0x801a98a0..0x801a98b0 +MARK_BINARY_BLOB(OSInitThreadQueue, 0x801a98a0, 0x801a98b0); +asm UNKNOWN_FUNCTION(OSInitThreadQueue) { + // clang-format off + nofralloc; + li r0, 0; + stw r0, 4(r3); + stw r0, 0(r3); + blr; + // clang-format on +} + +// Symbol: OSGetCurrentThread +// PAL: 0x801a98b0..0x801a98b4 +MARK_BINARY_BLOB(OSGetCurrentThread, 0x801a98b0, 0x801a98b4); +asm OSThread* OSGetCurrentThread() { + // clang-format off + nofralloc; + lis r3, 0x8000; + lwz r3, 0xe4(r3); + blr; + // clang-format on +} + +// Symbol: OSIsThreadTerminated +// PAL: 0x801a98bc..0x801a98e8 +MARK_BINARY_BLOB(OSIsThreadTerminated, 0x801a98bc, 0x801a98e8); +asm int OSIsThreadTerminated(OSThread*) { + // clang-format off + nofralloc; + lhz r0, 0x2c8(r3); + li r3, 1; + cmplwi r0, 8; + beq lbl_801a98d8; + cmpwi r0, 0; + beq lbl_801a98d8; + li r3, 0; +lbl_801a98d8: + neg r0, r3; + or r0, r0, r3; + srwi r3, r0, 0x1f; + blr; + // clang-format on +} + +// Symbol: OSDisableScheduler +// Function signature is unknown. +// PAL: 0x801a98e8..0x801a9924 +MARK_BINARY_BLOB(OSDisableScheduler, 0x801a98e8, 0x801a9924); +asm UNKNOWN_FUNCTION(OSDisableScheduler) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + bl OSDisableInterrupts; + lwz r31, -0x62e8(r13); + addi r0, r31, 1; + stw r0, -0x62e8(r13); + bl OSRestoreInterrupts; + mr r3, r31; + lwz r31, 0xc(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSEnableScheduler +// Function signature is unknown. +// PAL: 0x801a9924..0x801a9960 +MARK_BINARY_BLOB(OSEnableScheduler, 0x801a9924, 0x801a9960); +asm UNKNOWN_FUNCTION(OSEnableScheduler) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + bl OSDisableInterrupts; + lwz r31, -0x62e8(r13); + addi r0, r31, -1; + stw r0, -0x62e8(r13); + bl OSRestoreInterrupts; + mr r3, r31; + lwz r31, 0xc(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: UnsetRun +// Function signature is unknown. +// PAL: 0x801a9960..0x801a99c8 +MARK_BINARY_BLOB(UnsetRun, 0x801a9960, 0x801a99c8); +asm UNKNOWN_FUNCTION(UnsetRun) { + // clang-format off + nofralloc; + lwz r5, 0x2e0(r3); + lwz r4, 0x2dc(r3); + cmpwi r5, 0; + lwz r6, 0x2e4(r3); + bne lbl_801a997c; + stw r6, 4(r4); + b lbl_801a9980; +lbl_801a997c: + stw r6, 0x2e4(r5); +lbl_801a9980: + cmpwi r6, 0; + bne lbl_801a9990; + stw r5, 0(r4); + b lbl_801a9994; +lbl_801a9990: + stw r5, 0x2e0(r6); +lbl_801a9994: + lwz r0, 0(r4); + cmpwi r0, 0; + bne lbl_801a99bc; + lwz r0, 0x2d0(r3); + li r4, 1; + lwz r5, -0x62e0(r13); + subfic r0, r0, 0x1f; + slw r0, r4, r0; + andc r0, r5, r0; + stw r0, -0x62e0(r13); +lbl_801a99bc: + li r0, 0; + stw r0, 0x2dc(r3); + blr; + // clang-format on +} + +// Symbol: __OSGetEffectivePriority +// Function signature is unknown. +// PAL: 0x801a99c8..0x801a9a04 +MARK_BINARY_BLOB(__OSGetEffectivePriority, 0x801a99c8, 0x801a9a04); +asm UNKNOWN_FUNCTION(__OSGetEffectivePriority) { + // clang-format off + nofralloc; + lwz r4, 0x2d4(r3); + lwz r3, 0x2f4(r3); + b lbl_801a99f4; +lbl_801a99d4: + lwz r5, 0(r3); + cmpwi r5, 0; + beq lbl_801a99f0; + lwz r0, 0x2d0(r5); + cmpw r0, r4; + bge lbl_801a99f0; + mr r4, r0; +lbl_801a99f0: + lwz r3, 0x10(r3); +lbl_801a99f4: + cmpwi r3, 0; + bne lbl_801a99d4; + mr r3, r4; + blr; + // clang-format on +} + +// Symbol: SetEffectivePriority +// Function signature is unknown. +// PAL: 0x801a9a04..0x801a9bb8 +MARK_BINARY_BLOB(SetEffectivePriority, 0x801a9a04, 0x801a9bb8); +asm UNKNOWN_FUNCTION(SetEffectivePriority) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + lhz r0, 0x2c8(r3); + cmpwi r0, 3; + beq lbl_801a9b9c; + bge lbl_801a9a40; + cmpwi r0, 1; + beq lbl_801a9a4c; + bge lbl_801a9b90; + b lbl_801a9b9c; +lbl_801a9a40: + cmpwi r0, 5; + bge lbl_801a9b9c; + b lbl_801a9ab8; +lbl_801a9a4c: + bl UnsetRun; + lis r3, 0x8034; + slwi r0, r31, 3; + addi r3, r3, 0x77b0; + stw r31, 0x2d0(r30); + add r3, r3, r0; + stw r3, 0x2dc(r30); + lwz r4, 4(r3); + cmpwi r4, 0; + bne lbl_801a9a7c; + stw r30, 0(r3); + b lbl_801a9a80; +lbl_801a9a7c: + stw r30, 0x2e0(r4); +lbl_801a9a80: + li r0, 0; + stw r4, 0x2e4(r30); + lwz r4, 0x2dc(r30); + li r3, 1; + stw r0, 0x2e0(r30); + stw r30, 4(r4); + lwz r0, 0x2d0(r30); + lwz r4, -0x62e0(r13); + subfic r0, r0, 0x1f; + slw r0, r3, r0; + or r0, r4, r0; + stw r0, -0x62e0(r13); + stw r3, -0x62e4(r13); + b lbl_801a9b9c; +lbl_801a9ab8: + lwz r6, 0x2e0(r3); + lwz r7, 0x2e4(r3); + cmpwi r6, 0; + bne lbl_801a9ad4; + lwz r5, 0x2dc(r3); + stw r7, 4(r5); + b lbl_801a9ad8; +lbl_801a9ad4: + stw r7, 0x2e4(r6); +lbl_801a9ad8: + cmpwi r7, 0; + bne lbl_801a9aec; + lwz r5, 0x2dc(r3); + stw r6, 0(r5); + b lbl_801a9af0; +lbl_801a9aec: + stw r6, 0x2e0(r7); +lbl_801a9af0: + stw r4, 0x2d0(r3); + lwz r5, 0x2dc(r3); + lwz r6, 0(r5); + b lbl_801a9b04; +lbl_801a9b00: + lwz r6, 0x2e0(r6); +lbl_801a9b04: + cmpwi r6, 0; + beq lbl_801a9b1c; + lwz r4, 0x2d0(r6); + lwz r0, 0x2d0(r3); + cmpw r4, r0; + ble lbl_801a9b00; +lbl_801a9b1c: + cmpwi r6, 0; + bne lbl_801a9b54; + lwz r4, 4(r5); + cmpwi r4, 0; + bne lbl_801a9b38; + stw r3, 0(r5); + b lbl_801a9b3c; +lbl_801a9b38: + stw r3, 0x2e0(r4); +lbl_801a9b3c: + li r0, 0; + stw r4, 0x2e4(r3); + lwz r4, 0x2dc(r3); + stw r0, 0x2e0(r3); + stw r3, 4(r4); + b lbl_801a9b7c; +lbl_801a9b54: + stw r6, 0x2e0(r3); + lwz r4, 0x2e4(r6); + stw r3, 0x2e4(r6); + cmpwi r4, 0; + stw r4, 0x2e4(r3); + bne lbl_801a9b78; + lwz r4, 0x2dc(r3); + stw r3, 0(r4); + b lbl_801a9b7c; +lbl_801a9b78: + stw r3, 0x2e0(r4); +lbl_801a9b7c: + lwz r3, 0x2f0(r3); + cmpwi r3, 0; + beq lbl_801a9b9c; + lwz r3, 8(r3); + b lbl_801a9ba0; +lbl_801a9b90: + li r0, 1; + stw r0, -0x62e4(r13); + stw r4, 0x2d0(r3); +lbl_801a9b9c: + li r3, 0; +lbl_801a9ba0: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSPromoteThread +// Function signature is unknown. +// PAL: 0x801a9bb8..0x801a9c08 +MARK_BINARY_BLOB(__OSPromoteThread, 0x801a9bb8, 0x801a9c08); +asm UNKNOWN_FUNCTION(__OSPromoteThread) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; +lbl_801a9bcc: + lwz r0, 0x2cc(r3); + cmpwi r0, 0; + bgt lbl_801a9bf4; + lwz r0, 0x2d0(r3); + cmpw r0, r31; + ble lbl_801a9bf4; + mr r4, r31; + bl SetEffectivePriority; + cmpwi r3, 0; + bne lbl_801a9bcc; +lbl_801a9bf4: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: SelectThread +// Function signature is unknown. +// PAL: 0x801a9c08..0x801a9e30 +MARK_BINARY_BLOB(SelectThread, 0x801a9c08, 0x801a9e30); +asm UNKNOWN_FUNCTION(SelectThread) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + lwz r0, -0x62e8(r13); + cmpwi r0, 0; + ble lbl_801a9c34; + li r3, 0; + b lbl_801a9e18; +lbl_801a9c34: + bl OSGetCurrentContext; + lis r4, 0x8000; + lwz r5, 0xe4(r4); + cmplw r3, r5; + beq lbl_801a9c50; + li r3, 0; + b lbl_801a9e18; +lbl_801a9c50: + cmpwi r5, 0; + beq lbl_801a9d18; + lhz r0, 0x2c8(r5); + cmplwi r0, 2; + bne lbl_801a9cf4; + cmpwi r30, 0; + bne lbl_801a9c88; + lwz r3, -0x62e0(r13); + lwz r0, 0x2d0(r5); + cntlzw r3, r3; + cmpw r0, r3; + bgt lbl_801a9c88; + li r3, 0; + b lbl_801a9e18; +lbl_801a9c88: + li r0, 1; + lis r3, 0x8034; + sth r0, 0x2c8(r5); + addi r3, r3, 0x77b0; + lwz r0, 0x2d0(r5); + slwi r0, r0, 3; + add r3, r3, r0; + stw r3, 0x2dc(r5); + lwz r4, 4(r3); + cmpwi r4, 0; + bne lbl_801a9cbc; + stw r5, 0(r3); + b lbl_801a9cc0; +lbl_801a9cbc: + stw r5, 0x2e0(r4); +lbl_801a9cc0: + stw r4, 0x2e4(r5); + li r0, 0; + li r3, 1; + stw r0, 0x2e0(r5); + lwz r4, 0x2dc(r5); + stw r5, 4(r4); + lwz r0, 0x2d0(r5); + lwz r4, -0x62e0(r13); + subfic r0, r0, 0x1f; + slw r0, r3, r0; + or r0, r4, r0; + stw r0, -0x62e0(r13); + stw r3, -0x62e4(r13); +lbl_801a9cf4: + lhz r0, 0x1a2(r5); + rlwinm. r0, r0, 0, 0x1e, 0x1e; + bne lbl_801a9d18; + mr r3, r5; + bl OSSaveContext; + cmpwi r3, 0; + beq lbl_801a9d18; + li r3, 0; + b lbl_801a9e18; +lbl_801a9d18: + lwz r0, -0x62e0(r13); + cmpwi r0, 0; + bne lbl_801a9d7c; + lwz r12, -0x7120(r13); + lis r31, 0x8000; + lwz r3, 0xe4(r31); + li r4, 0; + mtctr r12; + bctrl; + li r0, 0; + lis r3, 0x8034; + stw r0, 0xe4(r31); + addi r3, r3, 0x78b0; + bl OSSetCurrentContext; +lbl_801a9d50: + bl OSEnableInterrupts; +lbl_801a9d54: + lwz r0, -0x62e0(r13); + cmpwi r0, 0; + beq lbl_801a9d54; + bl OSDisableInterrupts; + lwz r0, -0x62e0(r13); + cmpwi r0, 0; + beq lbl_801a9d50; + lis r3, 0x8034; + addi r3, r3, 0x78b0; + bl OSClearContext; +lbl_801a9d7c: + li r4, 0; + lis r3, 0x8034; + stw r4, -0x62e4(r13); + addi r3, r3, 0x77b0; + lwz r0, -0x62e0(r13); + cntlzw r5, r0; + slwi r0, r5, 3; + lwzux r30, r3, r0; + lwz r6, 0x2e0(r30); + cmpwi r6, 0; + bne lbl_801a9db0; + stw r4, 4(r3); + b lbl_801a9db4; +lbl_801a9db0: + stw r4, 0x2e4(r6); +lbl_801a9db4: + cmpwi r6, 0; + stw r6, 0(r3); + bne lbl_801a9dd8; + subfic r0, r5, 0x1f; + li r3, 1; + lwz r4, -0x62e0(r13); + slw r0, r3, r0; + andc r0, r4, r0; + stw r0, -0x62e0(r13); +lbl_801a9dd8: + li r3, 0; + li r0, 2; + stw r3, 0x2dc(r30); + lis r31, 0x8000; + mr r4, r30; + sth r0, 0x2c8(r30); + lwz r12, -0x7120(r13); + lwz r3, 0xe4(r31); + mtctr r12; + bctrl; + stw r30, 0xe4(r31); + mr r3, r30; + bl OSSetCurrentContext; + mr r3, r30; + bl OSLoadContext; + mr r3, r30; +lbl_801a9e18: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSReschedule +// Function signature is unknown. +// PAL: 0x801a9e30..0x801a9e48 +MARK_BINARY_BLOB(__OSReschedule, 0x801a9e30, 0x801a9e48); +asm UNKNOWN_FUNCTION(__OSReschedule) { + // clang-format off + nofralloc; + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beqlr; + li r3, 0; + b SelectThread; + blr; + // clang-format on +} + +// Symbol: OSYieldThread +// Function signature is unknown. +// PAL: 0x801a9e48..0x801a9e84 +MARK_BINARY_BLOB(OSYieldThread, 0x801a9e48, 0x801a9e84); +asm UNKNOWN_FUNCTION(OSYieldThread) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + bl OSDisableInterrupts; + mr r31, r3; + li r3, 1; + bl SelectThread; + mr r3, r31; + bl OSRestoreInterrupts; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSCreateThread +// PAL: 0x801a9e84..0x801aa0f0 +MARK_BINARY_BLOB(OSCreateThread, 0x801a9e84, 0x801aa0f0); +asm int OSCreateThread(OSThread* thread, void* (*callable)(void*), + void* user_data, void* stack, u32 stack_size, s32 prio, + u16 flag) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_26; + cmpwi r8, 0; + mr r31, r3; + mr r26, r5; + mr r27, r6; + mr r28, r7; + blt lbl_801a9eb8; + cmpwi r8, 0x1f; + ble lbl_801a9ec0; +lbl_801a9eb8: + li r3, 0; + b lbl_801aa0d8; +lbl_801a9ec0: + li r30, 0; + li r7, 1; + clrlwi r0, r9, 0x1f; + li r29, -1; + rlwinm r6, r6, 0, 0, 0x1c; + sth r7, 0x2c8(r3); + addi r5, r6, -8; + sth r0, 0x2ca(r3); + stw r8, 0x2d4(r3); + stw r8, 0x2d0(r3); + stw r7, 0x2cc(r3); + stw r29, 0x2d8(r3); + stw r30, 0x2f0(r3); + stw r30, 0x2ec(r3); + stw r30, 0x2e8(r3); + stw r30, 0x2f8(r3); + stw r30, 0x2f4(r3); + stw r30, -8(r6); + stw r30, -4(r6); + bl OSInitContext; + lis r5, 0x801b; + subf r4, r28, r27; + addi r5, r5, -24336; + lis r3, 0xdeae; + stw r5, 0x84(r31); + addi r0, r3, -17730; + stw r26, 0xc(r31); + stw r27, 0x304(r31); + stw r4, 0x308(r31); + stw r0, 0(r4); + stw r30, 0x30c(r31); + stw r30, 0x310(r31); + stw r30, 0x314(r31); + bl OSDisableInterrupts; + lis r4, 0x8034; + addi r4, r4, 0x70f0; + lwz r0, 0x40(r4); + cmpwi r0, 0; + beq lbl_801aa0a0; + lwz r5, 0x19c(r31); + li r0, 2; + lhz r4, 0x1a2(r31); + addi r6, r31, 0x90; + ori r5, r5, 0x900; + addi r7, r31, 0x1c8; + ori r4, r4, 1; + stw r5, 0x19c(r31); + sth r4, 0x1a2(r31); + lwz r4, -0x7158(r13); + rlwinm r4, r4, 0, 0x18, 0x1c; + ori r4, r4, 4; + stw r4, 0x194(r31); + mtctr r0; +lbl_801a9f94: + stw r29, 4(r6); + stw r29, 0(r6); + stw r29, 4(r7); + stw r29, 0(r7); + stw r29, 0xc(r6); + stw r29, 8(r6); + stw r29, 0xc(r7); + stw r29, 8(r7); + stw r29, 0x14(r6); + stw r29, 0x10(r6); + stw r29, 0x14(r7); + stw r29, 0x10(r7); + stw r29, 0x1c(r6); + stw r29, 0x18(r6); + stw r29, 0x1c(r7); + stw r29, 0x18(r7); + stw r29, 0x24(r6); + stw r29, 0x20(r6); + stw r29, 0x24(r7); + stw r29, 0x20(r7); + stw r29, 0x2c(r6); + stw r29, 0x28(r6); + stw r29, 0x2c(r7); + stw r29, 0x28(r7); + stw r29, 0x34(r6); + stw r29, 0x30(r6); + stw r29, 0x34(r7); + stw r29, 0x30(r7); + stw r29, 0x3c(r6); + stw r29, 0x38(r6); + stw r29, 0x3c(r7); + stw r29, 0x38(r7); + stw r29, 0x44(r6); + stw r29, 0x40(r6); + stw r29, 0x44(r7); + stw r29, 0x40(r7); + stw r29, 0x4c(r6); + stw r29, 0x48(r6); + stw r29, 0x4c(r7); + stw r29, 0x48(r7); + stw r29, 0x54(r6); + stw r29, 0x50(r6); + stw r29, 0x54(r7); + stw r29, 0x50(r7); + stw r29, 0x5c(r6); + stw r29, 0x58(r6); + stw r29, 0x5c(r7); + stw r29, 0x58(r7); + stw r29, 0x64(r6); + stw r29, 0x60(r6); + stw r29, 0x64(r7); + stw r29, 0x60(r7); + stw r29, 0x6c(r6); + stw r29, 0x68(r6); + stw r29, 0x6c(r7); + stw r29, 0x68(r7); + stw r29, 0x74(r6); + stw r29, 0x70(r6); + stw r29, 0x74(r7); + stw r29, 0x70(r7); + stw r29, 0x7c(r6); + stw r29, 0x78(r6); + addi r6, r6, 0x80; + stw r29, 0x7c(r7); + stw r29, 0x78(r7); + addi r7, r7, 0x80; + bdnz lbl_801a9f94; +lbl_801aa0a0: + lis r4, 0x8000; + lwz r5, 0xe0(r4); + cmpwi r5, 0; + bne lbl_801aa0b8; + stw r31, 0xdc(r4); + b lbl_801aa0bc; +lbl_801aa0b8: + stw r31, 0x2fc(r5); +lbl_801aa0bc: + li r0, 0; + stw r5, 0x300(r31); + lis r4, 0x8000; + stw r0, 0x2fc(r31); + stw r31, 0xe0(r4); + bl OSRestoreInterrupts; + li r3, 1; +lbl_801aa0d8: + addi r11, r1, 0x20; + bl _restgpr_26; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSExitThread +// Function signature is unknown. +// PAL: 0x801aa0f0..0x801aa1d4 +MARK_BINARY_BLOB(OSExitThread, 0x801aa0f0, 0x801aa1d4); +asm UNKNOWN_FUNCTION(OSExitThread) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + stw r28, 0x10(r1); + mr r28, r3; + bl OSDisableInterrupts; + lis r31, 0x8000; + mr r29, r3; + lwz r30, 0xe4(r31); + mr r3, r30; + bl OSClearContext; + lhz r0, 0x2ca(r30); + clrlwi. r0, r0, 0x1f; + beq lbl_801aa174; + lwz r4, 0x2fc(r30); + lwz r3, 0x300(r30); + cmpwi r4, 0; + bne lbl_801aa14c; + stw r3, 0xe0(r31); + b lbl_801aa150; +lbl_801aa14c: + stw r3, 0x300(r4); +lbl_801aa150: + cmpwi r3, 0; + bne lbl_801aa164; + lis r3, 0x8000; + stw r4, 0xdc(r3); + b lbl_801aa168; +lbl_801aa164: + stw r4, 0x2fc(r3); +lbl_801aa168: + li r0, 0; + sth r0, 0x2c8(r30); + b lbl_801aa180; +lbl_801aa174: + li r0, 8; + sth r0, 0x2c8(r30); + stw r28, 0x2d8(r30); +lbl_801aa180: + mr r3, r30; + bl __OSUnlockAllMutex; + addi r3, r30, 0x2e8; + bl OSWakeupThread; + li r0, 1; + stw r0, -0x62e4(r13); + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beq lbl_801aa1ac; + li r3, 0; + bl SelectThread; +lbl_801aa1ac: + mr r3, r29; + bl OSRestoreInterrupts; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSCancelThread +// PAL: 0x801aa1d4..0x801aa3ac +MARK_BINARY_BLOB(OSCancelThread, 0x801aa1d4, 0x801aa3ac); +asm void OSCancelThread(OSThread*) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + bl OSDisableInterrupts; + lhz r0, 0x2c8(r30); + mr r31, r3; + cmpwi r0, 3; + beq lbl_801aa2fc; + bge lbl_801aa214; + cmpwi r0, 1; + beq lbl_801aa220; + bge lbl_801aa238; + b lbl_801aa2fc; +lbl_801aa214: + cmpwi r0, 5; + bge lbl_801aa2fc; + b lbl_801aa244; +lbl_801aa220: + lwz r0, 0x2cc(r30); + cmpwi r0, 0; + bgt lbl_801aa308; + mr r3, r30; + bl UnsetRun; + b lbl_801aa308; +lbl_801aa238: + li r0, 1; + stw r0, -0x62e4(r13); + b lbl_801aa308; +lbl_801aa244: + lwz r4, 0x2e0(r30); + lwz r5, 0x2e4(r30); + cmpwi r4, 0; + bne lbl_801aa260; + lwz r3, 0x2dc(r30); + stw r5, 4(r3); + b lbl_801aa264; +lbl_801aa260: + stw r5, 0x2e4(r4); +lbl_801aa264: + cmpwi r5, 0; + bne lbl_801aa278; + lwz r3, 0x2dc(r30); + stw r4, 0(r3); + b lbl_801aa27c; +lbl_801aa278: + stw r4, 0x2e0(r5); +lbl_801aa27c: + lwz r0, 0x2cc(r30); + li r3, 0; + stw r3, 0x2dc(r30); + cmpwi r0, 0; + bgt lbl_801aa308; + lwz r3, 0x2f0(r30); + cmpwi r3, 0; + beq lbl_801aa308; + lwz r3, 8(r3); +lbl_801aa2a0: + lwz r0, 0x2cc(r3); + cmpwi r0, 0; + bgt lbl_801aa308; + lwz r4, 0x2d4(r3); + lwz r5, 0x2f4(r3); + b lbl_801aa2d8; +lbl_801aa2b8: + lwz r6, 0(r5); + cmpwi r6, 0; + beq lbl_801aa2d4; + lwz r0, 0x2d0(r6); + cmpw r0, r4; + bge lbl_801aa2d4; + mr r4, r0; +lbl_801aa2d4: + lwz r5, 0x10(r5); +lbl_801aa2d8: + cmpwi r5, 0; + bne lbl_801aa2b8; + lwz r0, 0x2d0(r3); + cmpw r0, r4; + beq lbl_801aa308; + bl SetEffectivePriority; + cmpwi r3, 0; + bne lbl_801aa2a0; + b lbl_801aa308; +lbl_801aa2fc: + mr r3, r31; + bl OSRestoreInterrupts; + b lbl_801aa394; +lbl_801aa308: + mr r3, r30; + bl OSClearContext; + lhz r0, 0x2ca(r30); + clrlwi. r0, r0, 0x1f; + beq lbl_801aa360; + lwz r4, 0x2fc(r30); + lwz r5, 0x300(r30); + cmpwi r4, 0; + bne lbl_801aa338; + lis r3, 0x8000; + stw r5, 0xe0(r3); + b lbl_801aa33c; +lbl_801aa338: + stw r5, 0x300(r4); +lbl_801aa33c: + cmpwi r5, 0; + bne lbl_801aa350; + lis r3, 0x8000; + stw r4, 0xdc(r3); + b lbl_801aa354; +lbl_801aa350: + stw r4, 0x2fc(r5); +lbl_801aa354: + li r0, 0; + sth r0, 0x2c8(r30); + b lbl_801aa368; +lbl_801aa360: + li r0, 8; + sth r0, 0x2c8(r30); +lbl_801aa368: + mr r3, r30; + bl __OSUnlockAllMutex; + addi r3, r30, 0x2e8; + bl OSWakeupThread; + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beq lbl_801aa38c; + li r3, 0; + bl SelectThread; +lbl_801aa38c: + mr r3, r31; + bl OSRestoreInterrupts; +lbl_801aa394: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSJoinThread +// Function signature is unknown. +// PAL: 0x801aa3ac..0x801aa4ec +MARK_BINARY_BLOB(OSJoinThread, 0x801aa3ac, 0x801aa4ec); +asm UNKNOWN_FUNCTION(OSJoinThread) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r3; + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r4; + bl OSDisableInterrupts; + lhz r0, 0x2ca(r31); + mr r30, r3; + clrlwi. r0, r0, 0x1f; + bne lbl_801aa458; + lhz r0, 0x2c8(r31); + cmplwi r0, 8; + beq lbl_801aa458; + lwz r0, 0x2e8(r31); + cmpwi r0, 0; + bne lbl_801aa458; + addi r3, r31, 0x2e8; + bl OSSleepThread; + lhz r0, 0x2c8(r31); + cmpwi r0, 0; + bne lbl_801aa414; + li r0, 0; + b lbl_801aa440; +lbl_801aa414: + lis r3, 0x8000; + lwz r3, 0xdc(r3); + b lbl_801aa434; +lbl_801aa420: + cmplw r31, r3; + bne lbl_801aa430; + li r0, 1; + b lbl_801aa440; +lbl_801aa430: + lwz r3, 0x2fc(r3); +lbl_801aa434: + cmpwi r3, 0; + bne lbl_801aa420; + li r0, 0; +lbl_801aa440: + cmpwi r0, 0; + bne lbl_801aa458; + mr r3, r30; + bl OSRestoreInterrupts; + li r3, 0; + b lbl_801aa4d0; +lbl_801aa458: + lhz r0, 0x2c8(r31); + cmplwi r0, 8; + bne lbl_801aa4c4; + cmpwi r29, 0; + beq lbl_801aa474; + lwz r0, 0x2d8(r31); + stw r0, 0(r29); +lbl_801aa474: + lwz r4, 0x2fc(r31); + lwz r5, 0x300(r31); + cmpwi r4, 0; + bne lbl_801aa490; + lis r3, 0x8000; + stw r5, 0xe0(r3); + b lbl_801aa494; +lbl_801aa490: + stw r5, 0x300(r4); +lbl_801aa494: + cmpwi r5, 0; + bne lbl_801aa4a8; + lis r3, 0x8000; + stw r4, 0xdc(r3); + b lbl_801aa4ac; +lbl_801aa4a8: + stw r4, 0x2fc(r5); +lbl_801aa4ac: + li r0, 0; + mr r3, r30; + sth r0, 0x2c8(r31); + bl OSRestoreInterrupts; + li r3, 1; + b lbl_801aa4d0; +lbl_801aa4c4: + mr r3, r30; + bl OSRestoreInterrupts; + li r3, 0; +lbl_801aa4d0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSDetachThread +// PAL: 0x801aa4ec..0x801aa58c +MARK_BINARY_BLOB(OSDetachThread, 0x801aa4ec, 0x801aa58c); +asm void OSDetachThread(OSThread*) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + bl OSDisableInterrupts; + lhz r0, 0x2c8(r30); + mr r31, r3; + lhz r4, 0x2ca(r30); + cmplwi r0, 8; + ori r3, r4, 1; + sth r3, 0x2ca(r30); + bne lbl_801aa564; + lwz r4, 0x2fc(r30); + lwz r5, 0x300(r30); + cmpwi r4, 0; + bne lbl_801aa540; + lis r3, 0x8000; + stw r5, 0xe0(r3); + b lbl_801aa544; +lbl_801aa540: + stw r5, 0x300(r4); +lbl_801aa544: + cmpwi r5, 0; + bne lbl_801aa558; + lis r3, 0x8000; + stw r4, 0xdc(r3); + b lbl_801aa55c; +lbl_801aa558: + stw r4, 0x2fc(r5); +lbl_801aa55c: + li r0, 0; + sth r0, 0x2c8(r30); +lbl_801aa564: + addi r3, r30, 0x2e8; + bl OSWakeupThread; + mr r3, r31; + bl OSRestoreInterrupts; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSResumeThread +// PAL: 0x801aa58c..0x801aa824 +MARK_BINARY_BLOB(OSResumeThread, 0x801aa58c, 0x801aa824); +asm s32 OSResumeThread(OSThread*) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r3; + bl OSDisableInterrupts; + lwz r30, 0x2cc(r29); + mr r31, r3; + addic. r0, r30, -1; + stw r0, 0x2cc(r29); + bge lbl_801aa5cc; + li r0, 0; + stw r0, 0x2cc(r29); + b lbl_801aa7fc; +lbl_801aa5cc: + bne lbl_801aa7fc; + lhz r0, 0x2c8(r29); + cmpwi r0, 4; + beq lbl_801aa688; + bge lbl_801aa7e8; + cmpwi r0, 1; + beq lbl_801aa5ec; + b lbl_801aa7e8; +lbl_801aa5ec: + lwz r5, 0x2d4(r29); + lwz r4, 0x2f4(r29); + b lbl_801aa618; +lbl_801aa5f8: + lwz r3, 0(r4); + cmpwi r3, 0; + beq lbl_801aa614; + lwz r0, 0x2d0(r3); + cmpw r0, r5; + bge lbl_801aa614; + mr r5, r0; +lbl_801aa614: + lwz r4, 0x10(r4); +lbl_801aa618: + cmpwi r4, 0; + bne lbl_801aa5f8; + lis r3, 0x8034; + slwi r0, r5, 3; + addi r3, r3, 0x77b0; + stw r5, 0x2d0(r29); + add r3, r3, r0; + stw r3, 0x2dc(r29); + lwz r4, 4(r3); + cmpwi r4, 0; + bne lbl_801aa64c; + stw r29, 0(r3); + b lbl_801aa650; +lbl_801aa64c: + stw r29, 0x2e0(r4); +lbl_801aa650: + li r0, 0; + stw r4, 0x2e4(r29); + lwz r4, 0x2dc(r29); + li r3, 1; + stw r0, 0x2e0(r29); + stw r29, 4(r4); + lwz r0, 0x2d0(r29); + lwz r4, -0x62e0(r13); + subfic r0, r0, 0x1f; + slw r0, r3, r0; + or r0, r4, r0; + stw r0, -0x62e0(r13); + stw r3, -0x62e4(r13); + b lbl_801aa7e8; +lbl_801aa688: + lwz r4, 0x2e0(r29); + lwz r5, 0x2e4(r29); + cmpwi r4, 0; + bne lbl_801aa6a4; + lwz r3, 0x2dc(r29); + stw r5, 4(r3); + b lbl_801aa6a8; +lbl_801aa6a4: + stw r5, 0x2e4(r4); +lbl_801aa6a8: + cmpwi r5, 0; + bne lbl_801aa6bc; + lwz r3, 0x2dc(r29); + stw r4, 0(r3); + b lbl_801aa6c0; +lbl_801aa6bc: + stw r4, 0x2e0(r5); +lbl_801aa6c0: + lwz r0, 0x2d4(r29); + lwz r4, 0x2f4(r29); + b lbl_801aa6ec; +lbl_801aa6cc: + lwz r3, 0(r4); + cmpwi r3, 0; + beq lbl_801aa6e8; + lwz r3, 0x2d0(r3); + cmpw r3, r0; + bge lbl_801aa6e8; + mr r0, r3; +lbl_801aa6e8: + lwz r4, 0x10(r4); +lbl_801aa6ec: + cmpwi r4, 0; + bne lbl_801aa6cc; + stw r0, 0x2d0(r29); + lwz r4, 0x2dc(r29); + lwz r5, 0(r4); + b lbl_801aa708; +lbl_801aa704: + lwz r5, 0x2e0(r5); +lbl_801aa708: + cmpwi r5, 0; + beq lbl_801aa720; + lwz r3, 0x2d0(r5); + lwz r0, 0x2d0(r29); + cmpw r3, r0; + ble lbl_801aa704; +lbl_801aa720: + cmpwi r5, 0; + bne lbl_801aa758; + lwz r3, 4(r4); + cmpwi r3, 0; + bne lbl_801aa73c; + stw r29, 0(r4); + b lbl_801aa740; +lbl_801aa73c: + stw r29, 0x2e0(r3); +lbl_801aa740: + li r0, 0; + stw r3, 0x2e4(r29); + lwz r3, 0x2dc(r29); + stw r0, 0x2e0(r29); + stw r29, 4(r3); + b lbl_801aa780; +lbl_801aa758: + stw r5, 0x2e0(r29); + lwz r3, 0x2e4(r5); + stw r29, 0x2e4(r5); + cmpwi r3, 0; + stw r3, 0x2e4(r29); + bne lbl_801aa77c; + lwz r3, 0x2dc(r29); + stw r29, 0(r3); + b lbl_801aa780; +lbl_801aa77c: + stw r29, 0x2e0(r3); +lbl_801aa780: + lwz r3, 0x2f0(r29); + cmpwi r3, 0; + beq lbl_801aa7e8; + lwz r3, 8(r3); +lbl_801aa790: + lwz r0, 0x2cc(r3); + cmpwi r0, 0; + bgt lbl_801aa7e8; + lwz r4, 0x2d4(r3); + lwz r5, 0x2f4(r3); + b lbl_801aa7c8; +lbl_801aa7a8: + lwz r6, 0(r5); + cmpwi r6, 0; + beq lbl_801aa7c4; + lwz r0, 0x2d0(r6); + cmpw r0, r4; + bge lbl_801aa7c4; + mr r4, r0; +lbl_801aa7c4: + lwz r5, 0x10(r5); +lbl_801aa7c8: + cmpwi r5, 0; + bne lbl_801aa7a8; + lwz r0, 0x2d0(r3); + cmpw r0, r4; + beq lbl_801aa7e8; + bl SetEffectivePriority; + cmpwi r3, 0; + bne lbl_801aa790; +lbl_801aa7e8: + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beq lbl_801aa7fc; + li r3, 0; + bl SelectThread; +lbl_801aa7fc: + mr r3, r31; + bl OSRestoreInterrupts; + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSSuspendThread +// Function signature is unknown. +// PAL: 0x801aa824..0x801aa9b8 +MARK_BINARY_BLOB(OSSuspendThread, 0x801aa824, 0x801aa9b8); +asm UNKNOWN_FUNCTION(OSSuspendThread) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r3; + bl OSDisableInterrupts; + lwz r30, 0x2cc(r29); + mr r31, r3; + addi r0, r30, 1; + cmpwi r30, 0; + stw r0, 0x2cc(r29); + bne lbl_801aa990; + lhz r0, 0x2c8(r29); + cmpwi r0, 3; + beq lbl_801aa97c; + bge lbl_801aa87c; + cmpwi r0, 1; + beq lbl_801aa898; + bge lbl_801aa888; + b lbl_801aa97c; +lbl_801aa87c: + cmpwi r0, 5; + bge lbl_801aa97c; + b lbl_801aa8a4; +lbl_801aa888: + li r0, 1; + stw r0, -0x62e4(r13); + sth r0, 0x2c8(r29); + b lbl_801aa97c; +lbl_801aa898: + mr r3, r29; + bl UnsetRun; + b lbl_801aa97c; +lbl_801aa8a4: + lwz r4, 0x2e0(r29); + lwz r5, 0x2e4(r29); + cmpwi r4, 0; + bne lbl_801aa8c0; + lwz r3, 0x2dc(r29); + stw r5, 4(r3); + b lbl_801aa8c4; +lbl_801aa8c0: + stw r5, 0x2e4(r4); +lbl_801aa8c4: + cmpwi r5, 0; + bne lbl_801aa8d8; + lwz r3, 0x2dc(r29); + stw r4, 0(r3); + b lbl_801aa8dc; +lbl_801aa8d8: + stw r4, 0x2e0(r5); +lbl_801aa8dc: + li r0, 0x20; + lwz r3, 0x2dc(r29); + stw r0, 0x2d0(r29); + lwz r4, 4(r3); + cmpwi r4, 0; + bne lbl_801aa8fc; + stw r29, 0(r3); + b lbl_801aa900; +lbl_801aa8fc: + stw r29, 0x2e0(r4); +lbl_801aa900: + li r0, 0; + stw r4, 0x2e4(r29); + lwz r3, 0x2dc(r29); + stw r0, 0x2e0(r29); + stw r29, 4(r3); + lwz r3, 0x2f0(r29); + cmpwi r3, 0; + beq lbl_801aa97c; + lwz r3, 8(r3); +lbl_801aa924: + lwz r0, 0x2cc(r3); + cmpwi r0, 0; + bgt lbl_801aa97c; + lwz r4, 0x2d4(r3); + lwz r5, 0x2f4(r3); + b lbl_801aa95c; +lbl_801aa93c: + lwz r6, 0(r5); + cmpwi r6, 0; + beq lbl_801aa958; + lwz r0, 0x2d0(r6); + cmpw r0, r4; + bge lbl_801aa958; + mr r4, r0; +lbl_801aa958: + lwz r5, 0x10(r5); +lbl_801aa95c: + cmpwi r5, 0; + bne lbl_801aa93c; + lwz r0, 0x2d0(r3); + cmpw r0, r4; + beq lbl_801aa97c; + bl SetEffectivePriority; + cmpwi r3, 0; + bne lbl_801aa924; +lbl_801aa97c: + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beq lbl_801aa990; + li r3, 0; + bl SelectThread; +lbl_801aa990: + mr r3, r31; + bl OSRestoreInterrupts; + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSSleepThread +// Function signature is unknown. +// PAL: 0x801aa9b8..0x801aaaa4 +MARK_BINARY_BLOB(OSSleepThread, 0x801aa9b8, 0x801aaaa4); +asm UNKNOWN_FUNCTION(OSSleepThread) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + bl OSDisableInterrupts; + lis r4, 0x8000; + li r0, 4; + lwz r4, 0xe4(r4); + mr r31, r3; + sth r0, 0x2c8(r4); + stw r30, 0x2dc(r4); + lwz r5, 0(r30); + b lbl_801aa9f8; +lbl_801aa9f4: + lwz r5, 0x2e0(r5); +lbl_801aa9f8: + cmpwi r5, 0; + beq lbl_801aaa10; + lwz r3, 0x2d0(r5); + lwz r0, 0x2d0(r4); + cmpw r3, r0; + ble lbl_801aa9f4; +lbl_801aaa10: + cmpwi r5, 0; + bne lbl_801aaa44; + lwz r3, 4(r30); + cmpwi r3, 0; + bne lbl_801aaa2c; + stw r4, 0(r30); + b lbl_801aaa30; +lbl_801aaa2c: + stw r4, 0x2e0(r3); +lbl_801aaa30: + stw r3, 0x2e4(r4); + li r0, 0; + stw r0, 0x2e0(r4); + stw r4, 4(r30); + b lbl_801aaa68; +lbl_801aaa44: + stw r5, 0x2e0(r4); + lwz r3, 0x2e4(r5); + stw r4, 0x2e4(r5); + cmpwi r3, 0; + stw r3, 0x2e4(r4); + bne lbl_801aaa64; + stw r4, 0(r30); + b lbl_801aaa68; +lbl_801aaa64: + stw r4, 0x2e0(r3); +lbl_801aaa68: + li r0, 1; + stw r0, -0x62e4(r13); + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beq lbl_801aaa84; + li r3, 0; + bl SelectThread; +lbl_801aaa84: + mr r3, r31; + bl OSRestoreInterrupts; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSWakeupThread +// Function signature is unknown. +// PAL: 0x801aaaa4..0x801aab98 +MARK_BINARY_BLOB(OSWakeupThread, 0x801aaaa4, 0x801aab98); +asm UNKNOWN_FUNCTION(OSWakeupThread) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + bl OSDisableInterrupts; + lis r5, 0x8034; + mr r31, r3; + addi r5, r5, 0x77b0; + li r6, 0; + li r3, 1; + b lbl_801aab58; +lbl_801aaad8: + lwz r4, 0x2e0(r8); + cmpwi r4, 0; + bne lbl_801aaaec; + stw r6, 4(r30); + b lbl_801aaaf0; +lbl_801aaaec: + stw r6, 0x2e4(r4); +lbl_801aaaf0: + stw r4, 0(r30); + sth r3, 0x2c8(r8); + lwz r0, 0x2cc(r8); + cmpwi r0, 0; + bgt lbl_801aab58; + lwz r0, 0x2d0(r8); + slwi r0, r0, 3; + add r4, r5, r0; + stw r4, 0x2dc(r8); + lwz r7, 4(r4); + cmpwi r7, 0; + bne lbl_801aab28; + stw r8, 0(r4); + b lbl_801aab2c; +lbl_801aab28: + stw r8, 0x2e0(r7); +lbl_801aab2c: + stw r7, 0x2e4(r8); + stw r6, 0x2e0(r8); + lwz r4, 0x2dc(r8); + stw r8, 4(r4); + lwz r0, 0x2d0(r8); + lwz r4, -0x62e0(r13); + subfic r0, r0, 0x1f; + slw r0, r3, r0; + or r0, r4, r0; + stw r0, -0x62e0(r13); + stw r3, -0x62e4(r13); +lbl_801aab58: + lwz r8, 0(r30); + cmpwi r8, 0; + bne lbl_801aaad8; + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beq lbl_801aab78; + li r3, 0; + bl SelectThread; +lbl_801aab78: + mr r3, r31; + bl OSRestoreInterrupts; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSSetThreadPriority +// Function signature is unknown. +// PAL: 0x801aab98..0x801aaca8 +MARK_BINARY_BLOB(OSSetThreadPriority, 0x801aab98, 0x801aaca8); +asm UNKNOWN_FUNCTION(OSSetThreadPriority) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + cmpwi r4, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + blt lbl_801aabc8; + cmpwi r4, 0x1f; + ble lbl_801aabd0; +lbl_801aabc8: + li r3, 0; + b lbl_801aac68; +lbl_801aabd0: + bl OSDisableInterrupts; + lwz r0, 0x2d4(r29); + mr r31, r3; + cmpw r0, r30; + beq lbl_801aac5c; + stw r30, 0x2d4(r29); +lbl_801aabe8: + lwz r0, 0x2cc(r29); + cmpwi r0, 0; + bgt lbl_801aac48; + lwz r4, 0x2d4(r29); + lwz r3, 0x2f4(r29); + b lbl_801aac20; +lbl_801aac00: + lwz r5, 0(r3); + cmpwi r5, 0; + beq lbl_801aac1c; + lwz r0, 0x2d0(r5); + cmpw r0, r4; + bge lbl_801aac1c; + mr r4, r0; +lbl_801aac1c: + lwz r3, 0x10(r3); +lbl_801aac20: + cmpwi r3, 0; + bne lbl_801aac00; + lwz r0, 0x2d0(r29); + cmpw r0, r4; + beq lbl_801aac48; + mr r3, r29; + bl SetEffectivePriority; + cmpwi r3, 0; + mr r29, r3; + bne lbl_801aabe8; +lbl_801aac48: + lwz r0, -0x62e4(r13); + cmpwi r0, 0; + beq lbl_801aac5c; + li r3, 0; + bl SelectThread; +lbl_801aac5c: + mr r3, r31; + bl OSRestoreInterrupts; + li r3, 1; +lbl_801aac68: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + bl OSGetAlarmUserData; + bl OSResumeThread; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSSleepTicks +// PAL: 0x801aaca8..0x801aad5c +MARK_BINARY_BLOB(OSSleepTicks, 0x801aaca8, 0x801aad5c); +asm void OSSleepTicks(OSTime ticks) { + // clang-format off + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + stw r0, 0x54(r1); + stw r31, 0x4c(r1); + stw r30, 0x48(r1); + stw r29, 0x44(r1); + mr r29, r3; + stw r28, 0x40(r1); + mr r28, r4; + bl OSDisableInterrupts; + lis r4, 0x8000; + mr r30, r3; + lwz r31, 0xe4(r4); + cmpwi r31, 0; + bne lbl_801aacec; + bl OSRestoreInterrupts; + b lbl_801aad3c; +lbl_801aacec: + addi r3, r1, 8; + bl OSCreateAlarm; + mr r4, r31; + addi r3, r1, 8; + bl unk_801a0cf8; + mr r4, r31; + addi r3, r1, 8; + bl OSSetAlarmUserData; + lis r7, 0x801b; + mr r6, r28; + mr r5, r29; + addi r3, r1, 8; + addi r7, r7, -21372; + bl OSSetAlarm; + mr r3, r31; + bl OSSuspendThread; + addi r3, r1, 8; + bl OSCancelAlarm; + mr r3, r30; + bl OSRestoreInterrupts; +lbl_801aad3c: + lwz r0, 0x54(r1); + lwz r31, 0x4c(r1); + lwz r30, 0x48(r1); + lwz r29, 0x44(r1); + lwz r28, 0x40(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; + // clang-format on +} diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index 150d3156f..c01bbb3e0 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "os.h" @@ -27,10 +28,6 @@ typedef struct OSMessageQueue { char _[0x20]; } OSMessageQueue; -typedef struct { - char _[0x18]; -} OSMutex; - typedef struct OSThreadQueue OSThreadQueue; struct OSThreadQueue { OSThread* head; @@ -68,25 +65,64 @@ typedef struct OSContext { } OSContext; -void OSInitMutex(OSMutex*); -void OSLockMutex(OSMutex*); -void OSUnlockMutex(OSMutex*); - +// PAL: 0x801a9e84..0x801aa0f0 int OSCreateThread(OSThread* thread, void* (*callable)(void*), void* user_data, void* stack, u32 stack_size, s32 prio, u16 flag); +// PAL: 0x801aa1d4..0x801aa3ac void OSCancelThread(OSThread*); +// PAL: 0x801aa4ec..0x801aa58c void OSDetachThread(OSThread*); +// PAL: 0x801aa58c..0x801aa824 s32 OSResumeThread(OSThread*); +// PAL: 0x801a98bc..0x801a98e8 int OSIsThreadTerminated(OSThread*); +// PAL: 0x801a98b0..0x801a98b4 OSThread* OSGetCurrentThread(); typedef void (*OSSwitchFunction)(OSThread*, OSThread*); +// PAL: 0x801a95ac..0x801a961c OSSwitchFunction OSSetSwitchThreadCallback(OSSwitchFunction callable); -void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); - +// PAL: 0x801aaca8..0x801aad5c void OSSleepTicks(OSTime ticks); +// PAL: 0x801a961c..0x801a98a0 +UNKNOWN_FUNCTION(__OSThreadInit); +// PAL: 0x801a98a0..0x801a98b0 +UNKNOWN_FUNCTION(OSInitThreadQueue); +// PAL: 0x801a98b4..0x801a98bc +UNKNOWN_FUNCTION(GetFont__Q34nw4r3lyt7TextBoxCFv); +// PAL: 0x801a98e8..0x801a9924 +UNKNOWN_FUNCTION(OSDisableScheduler); +// PAL: 0x801a9924..0x801a9960 +UNKNOWN_FUNCTION(OSEnableScheduler); +// PAL: 0x801a9960..0x801a99c8 +UNKNOWN_FUNCTION(UnsetRun); +// PAL: 0x801a99c8..0x801a9a04 +UNKNOWN_FUNCTION(__OSGetEffectivePriority); +// PAL: 0x801a9a04..0x801a9bb8 +UNKNOWN_FUNCTION(SetEffectivePriority); +// PAL: 0x801a9bb8..0x801a9c08 +UNKNOWN_FUNCTION(__OSPromoteThread); +// PAL: 0x801a9c08..0x801a9e30 +UNKNOWN_FUNCTION(SelectThread); +// PAL: 0x801a9e30..0x801a9e48 +UNKNOWN_FUNCTION(__OSReschedule); +// PAL: 0x801a9e48..0x801a9e84 +UNKNOWN_FUNCTION(OSYieldThread); +// PAL: 0x801aa0f0..0x801aa1d4 +UNKNOWN_FUNCTION(OSExitThread); +// PAL: 0x801aa3ac..0x801aa4ec +UNKNOWN_FUNCTION(OSJoinThread); +// PAL: 0x801aa824..0x801aa9b8 +UNKNOWN_FUNCTION(OSSuspendThread); +// PAL: 0x801aa9b8..0x801aaaa4 +UNKNOWN_FUNCTION(OSSleepThread); +// PAL: 0x801aaaa4..0x801aab98 +UNKNOWN_FUNCTION(OSWakeupThread); +// PAL: 0x801aab98..0x801aaca8 +UNKNOWN_FUNCTION(OSSetThreadPriority); + #endif #ifdef __cplusplus } diff --git a/source/rvl/pad/rvlPad.c b/source/rvl/pad/rvlPad.c index b0bd5be26..aaa5a1293 100644 --- a/source/rvl/pad/rvlPad.c +++ b/source/rvl/pad/rvlPad.c @@ -12,7 +12,7 @@ void OSSetCurrentContext(OSContext*); void OSClearContext(OSContext*); void OSRegisterVersion(const char*); s64 OSGetTime(); -void OSRegisterResetFunction(OSResetFunctionInfo* info); +void OSRegisterShutdownFunction(OSResetFunctionInfo* info); // Broadway / IOS global locations: https://wiibrew.org/wiki/Memory_Map u8 oslow_30e3 : (0x800030e3); u16 oslow_30e0 : (0x800030e0); @@ -331,7 +331,7 @@ int PADInit(void) { (0x4du << 24) | (chan << 22) | ((oslow_30e0 & 0x3fffu) << 8); } SIRefreshSamplingRate(); - OSRegisterResetFunction(&PAD_ResetFunctionInfo); + OSRegisterShutdownFunction(&PAD_ResetFunctionInfo); return PADReset(0xf0000000); } diff --git a/source/rvl/so/soCommon.c b/source/rvl/so/soCommon.c index 5505df485..d84c5a457 100644 --- a/source/rvl/so/soCommon.c +++ b/source/rvl/so/soCommon.c @@ -3,6 +3,7 @@ #include #include +#include #include // PAL: 0x80385ee0 @sdata (pointer) diff --git a/sources.py b/sources.py index 196c824ad..29053935e 100644 --- a/sources.py +++ b/sources.py @@ -7,6 +7,7 @@ from itertools import chain +MSL_LIBC_OPTS = '-ipa file' RVL_OPTS = '-ipa file' SPY_OPTS = RVL_OPTS + " -w nounusedexpr -w nounusedarg" EGG_OPTS = '-ipa function -rostr' @@ -34,8 +35,21 @@ class Source: Source(src="source/rvl/trk/start.c", cc='4199_60831', opts=RVL_OPTS), ] SOURCES_MSL_LIBC = [ - Source(src="source/platform/wchar.c", cc='4201_127', opts=RVL_OPTS), - Source(src="source/platform/eabi.c", cc='4201_127', opts=RVL_OPTS), + Source(src="source/platform/ansi_files.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/ansi_fp.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/float.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/mem.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/mem_cpy.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/printf.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/qsort.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/rand.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/scanf.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/wchar.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/ExceptionPPC.cpp", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/__init_cpp_exceptions.cpp", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/va_arg.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/eabi.c", cc='4201_127', opts=MSL_LIBC_OPTS), + Source(src="source/platform/global_destructor_chain.c", cc='4201_127', opts=MSL_LIBC_OPTS), ] SOURCES_RVL_ARC = [ Source(src="source/rvl/arc/rvlArchive.c", cc='4199_60831', opts=RVL_OPTS), @@ -62,7 +76,12 @@ class Source: Source(src="source/rvl/mtx/rvlQuat.c", cc='4199_60831', opts=RVL_OPTS), ] SOURCES_RVL_NAND = [ - Source(src="source/rvl/nand/nand.c", cc='4199_60831', opts=RVL_OPTS) + Source(src="source/rvl/nand/nand.c", cc='4199_60831', opts=RVL_OPTS), +] +SOURCES_RVL_OS = [ + Source(src="source/rvl/os/osInterrupt.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osReset.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osThread.c", cc='4199_60831', opts=RVL_OPTS), ] SOURCES_RVL_PAD = [ Source(src="source/rvl/pad/rvlPadClamp.c", cc='4199_60831', opts=RVL_OPTS), @@ -175,6 +194,7 @@ class Source: SOURCES_RVL_MEM, SOURCES_RVL_MTX, SOURCES_RVL_NAND, + SOURCES_RVL_OS, SOURCES_RVL_PAD, SOURCES_RVL_SI, SOURCES_RVL_TPL, From 74f8f7f1ea57a4cb1f880b07b8b8391e9994fcd8 Mon Sep 17 00:00:00 2001 From: Richard Patel Date: Sat, 7 Aug 2021 04:01:00 +0200 Subject: [PATCH 155/477] More RVL/OS splitting (#106) * More splitting * split osAlloc * split osArena * decomp osArena * optimize code * decomp osAlloc.c * add osAudio, osCache * add ppcArch * optimize forward decls * split osFatal * split osFont * split osLink * split osMemory * fix split osMemory * fix splits fix * split osSync * split osRtc, osReboot * revert rvl/tpl * fix build --- pack/dol_objects.txt | 41 +- pack/dol_slices.csv | 17 + pack/symbols.txt | 94 ++- source/egg/core/eggThread.hpp | 1 + source/platform/eabi.h | 2 + source/platform/printf.c | 5 +- source/platform/stdio.h | 1 + source/rvl/arc/binary_format.h | 2 - source/rvl/base/ppcArch.c | 223 ++++++ source/rvl/base/ppcArch.h | 62 ++ source/rvl/ipc/ipcclt.c | 52 +- source/rvl/os/os.h | 2 - source/rvl/os/osAlarm.c | 227 +++++++ source/rvl/os/osAlarm.h | 44 +- source/rvl/os/osAlloc.c | 150 +++++ source/rvl/os/osAlloc.h | 38 ++ source/rvl/os/osArena.c | 65 ++ source/rvl/os/osArena.h | 38 ++ source/rvl/os/osAudio.c | 365 ++++++++++ source/rvl/os/osAudio.h | 20 + source/rvl/os/osCache.c | 522 ++++++++++++++ source/rvl/os/osCache.h | 54 ++ source/rvl/os/osContext.c | 657 ++++++++++++++++++ source/rvl/os/osContext.h | 80 +++ source/rvl/os/osError.c | 636 ++++++++++++++++++ source/rvl/os/osError.h | 16 + source/rvl/os/osException.h | 4 +- source/rvl/os/osFatal.c | 923 +++++++++++++++++++++++++ source/rvl/os/osFatal.h | 22 + source/rvl/os/osFont.c | 1156 ++++++++++++++++++++++++++++++++ source/rvl/os/osFont.h | 34 + source/rvl/os/osInterrupt.c | 2 - source/rvl/os/osInterrupt.h | 88 +-- source/rvl/os/osLink.c | 462 +++++++++++++ source/rvl/os/osLink.h | 68 ++ source/rvl/os/osMemory.c | 785 ++++++++++++++++++++++ source/rvl/os/osMemory.h | 50 ++ source/rvl/os/osMessage.c | 238 +++++++ source/rvl/os/osMessage.h | 17 +- source/rvl/os/osMutex.c | 287 ++++++++ source/rvl/os/osMutex.h | 14 +- source/rvl/os/osReboot.c | 60 ++ source/rvl/os/osReboot.h | 18 + source/rvl/os/osReset.c | 2 +- source/rvl/os/osReset.h | 9 +- source/rvl/os/osRtc.c | 829 +++++++++++++++++++++++ source/rvl/os/osRtc.h | 32 + source/rvl/os/osSync.c | 57 ++ source/rvl/os/osSync.h | 18 + source/rvl/os/osThread.c | 30 +- source/rvl/os/osThread.h | 38 -- source/rvl/pad/pad.h | 6 +- source/rvl/pad/rvlPad.c | 9 +- source/rvl/tpl/tpl.c | 2 +- sources.py | 20 + 55 files changed, 8467 insertions(+), 227 deletions(-) create mode 100644 source/rvl/base/ppcArch.c create mode 100644 source/rvl/base/ppcArch.h create mode 100644 source/rvl/os/osAlarm.c create mode 100644 source/rvl/os/osAlloc.c create mode 100644 source/rvl/os/osAlloc.h create mode 100644 source/rvl/os/osArena.c create mode 100644 source/rvl/os/osArena.h create mode 100644 source/rvl/os/osAudio.c create mode 100644 source/rvl/os/osAudio.h create mode 100644 source/rvl/os/osCache.c create mode 100644 source/rvl/os/osCache.h create mode 100644 source/rvl/os/osContext.c create mode 100644 source/rvl/os/osContext.h create mode 100644 source/rvl/os/osError.c create mode 100644 source/rvl/os/osFatal.c create mode 100644 source/rvl/os/osFatal.h create mode 100644 source/rvl/os/osFont.c create mode 100644 source/rvl/os/osFont.h create mode 100644 source/rvl/os/osLink.c create mode 100644 source/rvl/os/osLink.h create mode 100644 source/rvl/os/osMemory.c create mode 100644 source/rvl/os/osMemory.h create mode 100644 source/rvl/os/osMessage.c create mode 100644 source/rvl/os/osMutex.c create mode 100644 source/rvl/os/osReboot.c create mode 100644 source/rvl/os/osReboot.h create mode 100644 source/rvl/os/osRtc.c create mode 100644 source/rvl/os/osRtc.h create mode 100644 source/rvl/os/osSync.c create mode 100644 source/rvl/os/osSync.h diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 394bb35ca..c6ed75748 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -192,10 +192,13 @@ out/dol/data_8027df3e_8027e708.o out/dol/bss_802f4040_80346cf0.o out/dol/sdata_80385744_803857f0.o out/rvlArchive.o -out/dol/text_80124e80_80169bcc.o +out/dol/text_80124e80_8012e564.o +out/dol/data_8027e772_802826a0.o +out/ppcArch.o +out/dol/text_8012e6d4_80169bcc.o out/fs.o out/dol/text_8016b49c_80192f7c.o -out/dol/data_8027e772_80290600.o +out/dol/data_802826d7_8028f058.o out/dol/sdata_803857f6_80385a08.o out/dol/sbss_80386448_803867e8.o out/ipcMain.o @@ -207,25 +210,47 @@ out/rvlMemExpHeap.o out/rvlMemFrmHeap.o out/rvlMemUnitHeap.o out/dol/bss_80346d18_803481b0.o -out/dol/sbss_8038683c_803868e8.o +out/dol/sbss_8038683c_803868a0.o out/dol/sdata2_803884a4_80388860.o out/rvlMemAllocator.o out/rvlMemList.o out/rvlMtx.o out/rvlMtx2.o out/rvlVec.o -out/dol/sdata_80385a10_80385b08.o +out/dol/sdata_80385a10_80385a98.o out/dol/sdata2_803888b4_803888b8.o out/rvlQuat.o out/nand.o -out/dol/text_8019f1a8_801a65ac.o +out/dol/text_8019f1a8_801a05b8.o +out/osAlarm.o +out/osAlloc.o +out/dol/rodata_80252c84_80252dd0.o +out/dol/data_8028f068_8028f320.o +out/dol/sdata_80385a9c_80385aa0.o +out/osArena.o +out/osAudio.o +out/osCache.o +out/osContext.o +out/osError.o +out/dol/text_801a2e84_801a4aa4.o +out/osFatal.o +out/osFont.o +out/dol/data_8028f4d8_80290600.o +out/dol/sdata_80385aa8_80385b08.o +out/dol/sbss_803868c0_803868e8.o out/osInterrupt.o -out/dol/text_801a6d30_801a8238.o +out/osLink.o +out/osMessage.o +out/osMemory.o +out/osMutex.o +out/osReboot.o out/osReset.o -out/dol/text_801a8a80_801a95ac.o +out/dol/text_801a8a80_801a8a9c.o +out/osRtc.o +out/osSync.o +out/dol/text_801a95a8_801a95ac.o out/osThread.o out/dol/text_801aad5c_801ae5d8.o -out/dol/rodata_80252c84_80252dd0.o out/dol/data_80290630_8029cc80.o out/dol/sbss_803868fc_80386998.o out/dol/sdata2_803888d0_80388930.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 4d2a2574e..86a4f5633 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -74,6 +74,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,1,source/gamespy/serverbrowsing/sb_serverlist.c,,,,,,,0x8011e518,0x80121eec,,,,,,,0x8027de18,0x8027de44,,,0x80385730,0x80385740,0x80386440,0x80386448,0x80388498,0x803884a4,, 1,1,source/gamespy/sake/sakeMain.c,,,,,,,0x80121eec,0x8012249c,,,,,,,0x8027de48,0x8027df3e,0x802f3f40,0x802f4040,0x80385740,0x80385744,,,,,, 1,,source/rvl/arc/rvlArchive.c,,,,,,,0x80124500,0x80124E80,,,,,,,0x8027E708,0x8027E772,,,0x803857F0,0x803857F6,,,,,, +1,,source/rvl/base/ppcArch.c,,,,,,,0x8012e564,0x8012e6d4,,,,,,,0x802826a0,0x802826d7,,,,,,,,,, 1,,source/rvl/fs/fs.c,,,,,,,0x80169bcc,0x8016b49c,,,,,,,,,,,,,,,,,, 1,,source/rvl/ipc/ipcMain.c,,,,,,,0x80192f7c,0x80193048,,,,,,,,,,,,,0x803867e8,0x803867fc,,,, 1,,source/rvl/ipc/ipcclt.c,,,,,,,0x80193048,0x801949b8,,,,,,,,,,,,,,,,,, @@ -88,8 +89,24 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/mtx/rvlVec.c,,,,,,,0x8019abe4,0x8019ae08,,,,,,,,,,,,,,,0x803888a8,0x803888b4,, 1,,source/rvl/mtx/rvlQuat.c,,,,,,,0x8019ae08,0x8019b314,,,,,0x80252c78,0x80252c84,,,,,,,,,0x803888b8,0x803888d0,, 1,,source/rvl/nand/nand.c,,,,,,,0x8019b314,0x8019f1a8,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osAlarm.c,,,,,,,0x801a05b8,0x801a0d9c,,,,,,,0x8028f058,0x8028f068,,,,,0x803868a0,0x803868a8,,,, +1,,source/rvl/os/osAlloc.c,,,,,,,0x801a0d9c ,0x801a10a4 ,,,,,,,,,,,0x80385a98,0x80385a9c,0x803868a8,0x803868b8,,,, +1,,source/rvl/os/osArena.c,,,,,,,0x801a10a4 ,0x801a1138,,,,,,,,,,,0x80385aa0,0x80385aa8,0x803868b8,0x803868c0,,,, +1,,source/rvl/os/osAudio.c,,,,,,,0x801a1138,0x801a15ec,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osCache.c,,,,,,,0x801a15ec,0x801a1c1c ,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osContext.c,,,,,,,0x801a1c1c ,0x801a25d0 ,,,,,,,0x8028f320,0x8028f4d8,,,,,,,,,, +1,,source/rvl/os/osError.c,,,,,,,0x801a25d0 ,0x801a2e84 ,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osFatal.c,,,,,,,0x801a4aa4 ,0x801a56dc ,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osFont.c,,,,,,,0x801a56dc ,0x801a65ac ,,,,,,,,,,,,,,,,,, 1,,source/rvl/os/osInterrupt.c,,,,,,,0x801a65ac ,0x801a6d30 ,,,,,,,0x80290600,0x80290630,,,,,0x803868e8,0x803868fc,,,, +1,,source/rvl/os/osLink.c,,,,,,,0x801a6d30 ,0x801a72fc ,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osMessage.c,,,,,,,0x801a72fc ,0x801a75d0 ,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osMemory.c,,,,,,,0x801a75d0 ,0x801a7eac,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osMutex.c,,,,,,,0x801a7eac,0x801a81b8,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osReboot.c,,,,,,,0x801a81b8,0x801a8238 ,,,,,,,,,,,,,,,,,, 1,,source/rvl/os/osReset.c,,,,,,,0x801a8238 ,0x801a8a80 ,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osRtc.c,,,,,,,0x801a8a9c ,0x801a9528,,,,,,,,,,,,,,,,,, +1,,source/rvl/os/osSync.c,,,,,,,0x801a9528,0x801a95a8,,,,,,,,,,,,,,,,,, 1,,source/rvl/os/osThread.c,,,,,,,0x801a95ac ,0x801aad5c ,,,,,,,,,,,,,,,,,, 1,,source/rvl/pad/rvlPadClamp.c,,,,,,,0x801ae5d8,0x801ae880,,,,,0x80252dd0,0x80252de6,,,,,,,,,0x80388930,0x80388938,, 1,,source/rvl/pad/rvlPad.c,,,,,,,0x801ae880,0x801b0180,,,,,,,0x8029cc80,0x8029ccd8,0x803481b0,0x80348230,0x80385b08,0x80385b28,0x80386998,0x803869C4,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index fccd225bf..7f711f917 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -836,9 +836,53 @@ 0x80124d44 ARCOpenDir 0x80124dc0 ARCReadDir 0x80124e78 ARCCloseDir +0x8012e564 PPCMfmsr +0x8012e56c PPCMtmsr +0x8012e574 PPCMfhid0 +0x8012e57c PPCMthid0 +0x8012e584 PPCMfl2cr +0x8012e58c PPCMtl2cr +0x8012e594 PPCMtdec +0x8012e59c PPCSync +0x8012e5a4 PPCHalt +0x8012e5b8 PPCMtmmcr0 +0x8012e5c0 PPCMtmmcr1 +0x8012e5c8 PPCMtpmc1 +0x8012e5d0 PPCMtpmc2 +0x8012e5d8 PPCMtpmc3 +0x8012e5e0 PPCMtpmc4 +0x8012e5e8 PPCMffpscr +0x8012e608 PPCMtfpscr +0x8012e630 PPCMfhid2 +0x8012e638 PPCMthid2 +0x8012e640 PPCMfwpar +0x8012e64c PPCMtwpar +0x8012e654 PPCDisableSpeculation +0x8012e67c PPCSetFpNonIEEEMode +0x8012e684 PPCMthid4 0x8015bef0 CXInitUncompContextLZ 0x8015bf24 CXReadUncompLZ 0x8015c2e0 CXGetUncompressedSize +0x8015d314 DBInit +0x8015d33c __DBExceptionDestinationAux +0x8015d384 __DBExceptionDestination +0x8015d394 __DBIsExceptionMarked +0x8015d3ac DBPrintf +0x8015d3fc DSPCheckMailToDSP +0x8015d40c DSPCheckMailFromDSP +0x8015d41c DSPReadMailFromDSP +0x8015d430 DSPSendMailToDSP +0x8015d444 DSPInit +0x8015d504 DSPCheckInit +0x8015d50c DSPAddTask +0x8015d57c DSPAssertTask +0x8015d638 __DSP_debug_printf +0x8015d688 __DSPHandler +0x8015dabc __DSP_exec_task +0x8015dc60 __DSP_boot_task +0x8015ddec __DSP_insert_task +0x8015de88 __DSP_remove_task +0x8015df1c __DVDFSInit 0x8015e2bc DVDOpen 0x8015e568 DVDClose 0x8015e834 DVDReadPrio @@ -1405,19 +1449,20 @@ 0x801a0598 OSGetAppType 0x801a05b8 __OSInitAlarm 0x801a0610 OSCreateAlarm -0x801a0620 InsertAlarm +0x801a0620 OS_Alarm_InsertAlarm 0x801a0870 OSSetAlarm 0x801a08e0 OSSetPeriodicAlarm 0x801a0964 OSCancelAlarm -0x801a0a7c DecrementerExceptionCallback -0x801a0ca8 DecrementerExceptionHandler -0x801a0d00 OnReset +0x801a0a7c OS_Alarm_DecrementerExceptionCallback +0x801a0ca8 OS_Alarm_DecrementerExceptionHandler +0x801a0cf8 OSSetAlarmTag +0x801a0d00 OS_Alarm_OnReset 0x801a0d8c OSSetAlarmUserData 0x801a0d94 OSGetAlarmUserData 0x801a0d9c DLInsert 0x801a0e48 OSAllocFromHeap 0x801a0f40 OSFreeToHeap -0x801a0fb8 set_CurrHeap +0x801a0fb8 OSSetCurrentHeap 0x801a0fc8 OSInitAlloc 0x801a1038 OSCreateHeap 0x801a10a4 OSGetMEM1ArenaHi @@ -1426,10 +1471,10 @@ 0x801a10bc OSGetMEM1ArenaLo 0x801a10c4 OSGetMEM2ArenaLo 0x801a10cc OSGetArenaLo -0x801a10d4 OSSetArenaHi_0 +0x801a10d4 OSSetMEM1ArenaHi 0x801a10dc OSSetMEM2ArenaHi 0x801a10e4 OSSetArenaHi -0x801a10ec OSSetArenaLo_0 +0x801a10ec OSSetMEM1ArenaLo 0x801a10f4 OSSetMEM2ArenaLo 0x801a10fc OSSetArenaLo 0x801a1104 OSAllocFromMEM1ArenaLo @@ -1488,6 +1533,17 @@ 0x801a3e00 __OSBootDolSimple 0x801a4648 __OSBootDol 0x801a4828 OSLaunchInstaller +0x801a4aa4 ScreenReport +0x801a4dc8 ConfigureVideo +0x801a4ec4 OSFatal +0x801a50dc Halt +0x801a56dc GetFontCode +0x801a5810 Decode +0x801a59b4 OSSetFontEncode +0x801a5a34 ReadFont +0x801a5d34 OSLoadFont +0x801a5e5c ParseStringS +0x801a5f58 ParseStringW 0x801a6114 OSGetFontTexel 0x801a63a4 OSGetFontTexture 0x801a64f4 OSGetFontWidth @@ -1505,7 +1561,31 @@ 0x801a6d30 OSNotifyLink 0x801a6d34 OSNotifyPreLink 0x801a6d38 OSNotifyPostLink +0x801a6d3c Relocate +0x801a6ffc Link +0x801a72dc OSLink 0x801a72fc OSInitMessageQueue +0x801a735c OSSendMessage +0x801a7424 OSReceiveMessage +0x801a7500 OSJamMessage +0x801a75d0 OSGetPhysicalMem1Size +0x801a75dc OSGetPhysicalMem2Size +0x801a75e8 OSGetConsoleSimulatedMem1Size +0x801a75f4 OSGetConsoleSimulatedMem2Size +0x801a7600 OnShutdown +0x801a763c MEMIntrruptHandler +0x801a7684 OSProtectRange +0x801a774c ConfigMEM1_24MB +0x801a77cc ConfigMEM1_48MB +0x801a784c ConfigMEM2_52MB +0x801a792c ConfigMEM2_56MB +0x801a7a0c ConfigMEM2_64MB +0x801a7ab8 ConfigMEM2_112MB +0x801a7b98 ConfigMEM2_128MB +0x801a7c44 ConfigMEM_ES1_0 +0x801a7c94 RealMode +0x801a7cac BATConfig +0x801a7dfc __OSInitMemoryProtection 0x801a7eac OSInitMutex 0x801a7ee4 OSLockMutex 0x801a7fc0 OSUnlockMutex diff --git a/source/egg/core/eggThread.hpp b/source/egg/core/eggThread.hpp index a2262b04e..0118aca6d 100644 --- a/source/egg/core/eggThread.hpp +++ b/source/egg/core/eggThread.hpp @@ -7,6 +7,7 @@ #include #include +#include #include namespace EGG { diff --git a/source/platform/eabi.h b/source/platform/eabi.h index ab16207f0..4084b5e0d 100644 --- a/source/platform/eabi.h +++ b/source/platform/eabi.h @@ -69,3 +69,5 @@ extern void __shr2i(void); // PAL: 0x80021b00 void __cvt_dbl_ull(void); + +unsigned long __cvt_fp2unsigned(double d); diff --git a/source/platform/printf.c b/source/platform/printf.c index bc9049fbe..bc94eeb3b 100644 --- a/source/platform/printf.c +++ b/source/platform/printf.c @@ -2,11 +2,10 @@ #include +#include "ansi_fp.h" #include "va_arg.h" // Extern function references. -// PAL: 0x8000dafc -extern UNKNOWN_FUNCTION(__num2dec); // PAL: 0x8000e954 extern UNKNOWN_FUNCTION(unk_8000e954); // PAL: 0x8000f138 @@ -1750,7 +1749,7 @@ asm UNKNOWN_FUNCTION(printf) { // Function signature is unknown. // PAL: 0x800117b0..0x8001182c MARK_BINARY_BLOB(vprintf, 0x800117b0, 0x8001182c); -asm UNKNOWN_FUNCTION(vprintf) { +asm int vprintf(const char* format, va_list arg) { // clang-format off nofralloc; stwu r1, -0x20(r1); diff --git a/source/platform/stdio.h b/source/platform/stdio.h index 459a797ec..017685eaa 100644 --- a/source/platform/stdio.h +++ b/source/platform/stdio.h @@ -69,6 +69,7 @@ typedef char* va_list; int sprintf(char* str, const char* format, ...); int snprintf(char* s, size_t n, const char* format, ...); int vsnprintf(char* s, size_t n, const char* format, va_list arg); +int vprintf(const char* format, va_list arg); int vsprintf(char* s, const char* format, va_list arg); int sscanf(const char* str, const char* format, ...); diff --git a/source/rvl/arc/binary_format.h b/source/rvl/arc/binary_format.h index dc19699e6..de4810b3f 100644 --- a/source/rvl/arc/binary_format.h +++ b/source/rvl/arc/binary_format.h @@ -5,8 +5,6 @@ #include -void OSPanic(...); - struct rvlArchiveNode { union { struct { diff --git a/source/rvl/base/ppcArch.c b/source/rvl/base/ppcArch.c new file mode 100644 index 000000000..75add5341 --- /dev/null +++ b/source/rvl/base/ppcArch.c @@ -0,0 +1,223 @@ +#include "ppcArch.h" + +#include + +// Symbol: PPCMfmsr +// PAL: 0x8012e564..0x8012e56c +asm u32 PPCMfmsr() { + nofralloc; + mfmsr r3; + blr; +} + +// Symbol: PPCMtmsr +// PAL: 0x8012e56c..0x8012e574 +asm void PPCMtmsr(u32) { + nofralloc; + mtmsr r3; + blr; +} + +// Symbol: PPCMfhid0 +// PAL: 0x8012e574..0x8012e57c +asm u32 PPCMfhid0() { + nofralloc; + mfspr r3, 0x3f0; + blr; +} + +// Symbol: PPCMthid0 +// PAL: 0x8012e57c..0x8012e584 +asm void PPCMthid0(u32) { + nofralloc; + mtspr 0x3f0, r3; + blr; +} + +// Symbol: PPCMfl2cr +// PAL: 0x8012e584..0x8012e58c +asm u32 PPCMfl2cr() { + nofralloc; + mfspr r3, 0x3f9; + blr; +} + +// Symbol: PPCMtl2cr +// PAL: 0x8012e58c..0x8012e594 +asm void PPCMtl2cr(u32) { + nofralloc; + mtspr 0x3f9, r3; + blr; +} + +// Symbol: PPCMtdec +// PAL: 0x8012e594..0x8012e59c +asm void PPCMtdec(u32) { + nofralloc; + mtspr 0x16, r3; + blr; +} + +// Symbol: PPCSync +// PAL: 0x8012e59c..0x8012e5a4 +asm void PPCSync() { + nofralloc; + sc; + blr; +} + +// Symbol: PPCHalt +// PAL: 0x8012e5a4..0x8012e5b8 +MARK_BINARY_BLOB(PPCHalt, 0x8012e5a4, 0x8012e5b8); +asm void PPCHalt() { + // clang-format off + nofralloc; + sync; +lbl_8012e5a8: + nop; + li r3, 0; + nop; + b lbl_8012e5a8; + // clang-format on +} + +// Symbol: PPCMtmmcr0 +// PAL: 0x8012e5b8..0x8012e5c0 +asm void PPCMtmmcr0(u32) { + nofralloc; + mtspr 0x3b8, r3; + blr; +} + +// Symbol: PPCMtmmcr1 +// PAL: 0x8012e5c0..0x8012e5c8 +asm void PPCMtmmcr1(u32) { + nofralloc; + mtspr 0x3bc, r3; + blr; +} + +// Symbol: PPCMtpmc1 +// PAL: 0x8012e5c8..0x8012e5d0 +asm void PPCMtpmc1(u32) { + nofralloc; + mtspr 0x3b9, r3; + blr; +} + +// Symbol: PPCMtpmc2 +// PAL: 0x8012e5d0..0x8012e5d8 +asm void PPCMtpmc2(u32) { + nofralloc; + mtspr 0x3ba, r3; + blr; +} + +// Symbol: PPCMtpmc3 +// PAL: 0x8012e5d8..0x8012e5e0 +asm void PPCMtpmc3(u32) { + nofralloc; + mtspr 0x3bd, r3; + blr; +} + +// Symbol: PPCMtpmc4 +// PAL: 0x8012e5e0..0x8012e5e8 +asm void PPCMtpmc4(u32) { + nofralloc; + mtspr 0x3be, r3; + blr; +} + +// Symbol: PPCMffpscr +// PAL: 0x8012e5e8..0x8012e608 +MARK_BINARY_BLOB(PPCMffpscr, 0x8012e5e8, 0x8012e608); +asm u32 PPCMffpscr() { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + stfd f31, 0x18(r1); + mffs f31; + stfd f31, 8(r1); + lfd f31, 0x18(r1); + lwz r3, 0xc(r1); + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: PPCMtfpscr +// PAL: 0x8012e608..0x8012e630 +MARK_BINARY_BLOB(PPCMtfpscr, 0x8012e608, 0x8012e630); +asm void PPCMtfpscr(u32) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + stfd f31, 0x18(r1); + li r4, 0; + stw r3, 0xc(r1); + stw r4, 8(r1); + lfd f31, 8(r1); + mtfsf 0xff, f31; + lfd f31, 0x18(r1); + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: PPCMfhid2 +// PAL: 0x8012e630..0x8012e638 +asm u32 PPCMfhid2() { + nofralloc; + mfspr r3, 0x398; + blr; +} + +// Symbol: PPCMthid2 +// PAL: 0x8012e638..0x8012e640 +asm void PPCMthid2(register u32 x) { + nofralloc; + mtspr 0x398, x; + blr; +} + +// Symbol: PPCMfwpar +// PAL: 0x8012e640..0x8012e64c +asm u32 PPCMfwpar() { + nofralloc; + sync; + mfspr r3, 0x399; + blr; +} + +// Symbol: PPCMtwpar +// PAL: 0x8012e64c..0x8012e654 +asm void PPCMtwpar(register u32 x) { + nofralloc; + mtspr 0x399, x; + blr; +} + +// Symbol: PPCDisableSpeculation +// PAL: 0x8012e654..0x8012e67c +void PPCDisableSpeculation() { PPCMthid0(PPCMfhid0() | 0x00000200); } + +// Symbol: PPCSetFpNonIEEEMode +// PAL: 0x8012e67c..0x8012e684 +asm void PPCSetFpNonIEEEMode() { + nofralloc; + mtfsb1 0x1d; + blr; +} + +// Symbol: PPCMthid4 +// PAL: 0x8012e684..0x8012e6d4 +void PPCMthid4(register u32 x) { + if (x & 0x80000000) { + asm { mtspr 0x3f3, x; } + } else { + OSReport("H4A should not be cleared because of Broadway errata.\n"); + x |= 0x80000000; + asm { mtspr 0x3f3, x; } + } +} diff --git a/source/rvl/base/ppcArch.h b/source/rvl/base/ppcArch.h new file mode 100644 index 000000000..311e84e7a --- /dev/null +++ b/source/rvl/base/ppcArch.h @@ -0,0 +1,62 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x8012e564..0x8012e56c +u32 PPCMfmsr(); +// PAL: 0x8012e56c..0x8012e574 +void PPCMtmsr(u32); +// PAL: 0x8012e574..0x8012e57c +u32 PPCMfhid0(); +// PAL: 0x8012e57c..0x8012e584 +void PPCMthid0(u32); +// PAL: 0x8012e584..0x8012e58c +u32 PPCMfl2cr(); +// PAL: 0x8012e58c..0x8012e594 +void PPCMtl2cr(u32); +// PAL: 0x8012e594..0x8012e59c +void PPCMtdec(u32); +// PAL: 0x8012e59c..0x8012e5a4 +void PPCSync(); +// PAL: 0x8012e5a4..0x8012e5b8 +void PPCHalt(); +// PAL: 0x8012e5b8..0x8012e5c0 +void PPCMtmmcr0(u32); +// PAL: 0x8012e5c0..0x8012e5c8 +void PPCMtmmcr1(u32); +// PAL: 0x8012e5c8..0x8012e5d0 +void PPCMtpmc1(u32); +// PAL: 0x8012e5d0..0x8012e5d8 +void PPCMtpmc2(u32); +// PAL: 0x8012e5d8..0x8012e5e0 +void PPCMtpmc3(u32); +// PAL: 0x8012e5e0..0x8012e5e8 +void PPCMtpmc4(u32); +// PAL: 0x8012e5e8..0x8012e608 +u32 PPCMffpscr(); +// PAL: 0x8012e608..0x8012e630 +void PPCMtfpscr(u32); +// PAL: 0x8012e630..0x8012e638 +u32 PPCMfhid2(); +// PAL: 0x8012e638..0x8012e640 +void PPCMthid2(u32); +// PAL: 0x8012e640..0x8012e64c +u32 PPCMfwpar(); +// PAL: 0x8012e64c..0x8012e654 +void PPCMtwpar(u32); +// PAL: 0x8012e654..0x8012e67c +void PPCDisableSpeculation(); +// PAL: 0x8012e67c..0x8012e684 +void PPCSetFpNonIEEEMode(); +// PAL: 0x8012e684..0x8012e6d4 +void PPCMthid4(u32); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/ipc/ipcclt.c b/source/rvl/ipc/ipcclt.c index 24131475f..aea2dfd63 100644 --- a/source/rvl/ipc/ipcclt.c +++ b/source/rvl/ipc/ipcclt.c @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include @@ -23,14 +25,6 @@ extern UNKNOWN_FUNCTION(IPCiProfQueueReq); extern UNKNOWN_FUNCTION(IPCiProfAck); // PAL: 0x80195024 extern UNKNOWN_FUNCTION(IPCiProfReply); -// PAL: 0x801a1600 -extern UNKNOWN_FUNCTION(unk_801a1600); -// PAL: 0x801a162c -extern UNKNOWN_FUNCTION(unk_801a162c); -// PAL: 0x801a1e70 -extern UNKNOWN_FUNCTION(OSSetCurrentContext); -// PAL: 0x801a2098 -extern UNKNOWN_FUNCTION(OSClearContext); // Symbol: strnlen // Function signature is unknown. @@ -87,7 +81,7 @@ asm UNKNOWN_FUNCTION(IpcReplyHandler) { stw r0, 0x30(r3); mr r3, r31; li r4, 0x20; - bl unk_801a1600; + bl DCInvalidateRange; lwz r0, 8(r31); cmpwi r0, 6; beq lbl_80193134; @@ -113,7 +107,7 @@ asm UNKNOWN_FUNCTION(IpcReplyHandler) { cmpwi r4, 0; ble lbl_80193230; lwz r3, 0xc(r31); - bl unk_801a1600; + bl DCInvalidateRange; b lbl_80193230; lbl_80193134: lwz r3, 0x18(r31); @@ -127,10 +121,10 @@ asm UNKNOWN_FUNCTION(IpcReplyHandler) { stw r0, 0x18(r31); lwz r3, 0x10(r31); lwz r4, 0x14(r31); - bl unk_801a1600; + bl DCInvalidateRange; lwz r3, 0x18(r31); lwz r4, 0x1c(r31); - bl unk_801a1600; + bl DCInvalidateRange; b lbl_80193230; lbl_8019316c: lwz r3, 0x18(r31); @@ -146,7 +140,7 @@ asm UNKNOWN_FUNCTION(IpcReplyHandler) { lwz r0, 0x14(r31); add r0, r4, r0; slwi r4, r0, 3; - bl unk_801a1600; + bl DCInvalidateRange; li r28, 0; li r29, 0; b lbl_801931e8; @@ -166,7 +160,7 @@ asm UNKNOWN_FUNCTION(IpcReplyHandler) { add r4, r3, r29; lwzx r3, r3, r29; lwz r4, 4(r4); - bl unk_801a1600; + bl DCInvalidateRange; addi r28, r28, 1; addi r29, r29, 8; lbl_801931e8: @@ -484,7 +478,7 @@ asm UNKNOWN_FUNCTION(__ios_Ipc2) { lbl_801935e4: mr r3, r28; li r4, 0x20; - bl unk_801a162c; + bl DCFlushRange; bl OSDisableInterrupts; lis r4, 0x8034; mr r30, r3; @@ -691,7 +685,7 @@ asm UNKNOWN_FUNCTION(IOS_OpenAsync) { subf r4, r27, r3; mr r3, r27; addi r4, r4, 1; - bl unk_801a162c; + bl DCFlushRange; addis r0, r27, 0x8000; stw r0, 0xc(r31); stw r28, 0x10(r31); @@ -780,7 +774,7 @@ asm UNKNOWN_FUNCTION(IOS_Open) { subf r4, r28, r3; mr r3, r28; addi r4, r4, 1; - bl unk_801a162c; + bl DCFlushRange; addis r0, r28, 0x8000; stw r0, 0xc(r31); stw r29, 0x10(r31); @@ -976,7 +970,7 @@ asm UNKNOWN_FUNCTION(IOS_ReadAsync) { lbl_80193c24: mr r3, r27; mr r4, r28; - bl unk_801a1600; + bl DCInvalidateRange; cmpwi r27, 0; beq lbl_80193c40; addis r0, r27, 0x8000; @@ -1058,7 +1052,7 @@ asm UNKNOWN_FUNCTION(IOS_Read) { lbl_80193d24: mr r3, r28; mr r4, r29; - bl unk_801a1600; + bl DCInvalidateRange; cmpwi r28, 0; beq lbl_80193d40; addis r0, r28, 0x8000; @@ -1151,7 +1145,7 @@ asm UNKNOWN_FUNCTION(IOS_WriteAsync) { mr r3, r27; mr r4, r28; stw r28, 0x10(r5); - bl unk_801a162c; + bl DCFlushRange; lbl_80193e54: cmpwi r31, 0; bne lbl_80193e6c; @@ -1233,7 +1227,7 @@ asm UNKNOWN_FUNCTION(IOS_Write) { mr r3, r29; mr r4, r30; stw r30, 0x10(r5); - bl unk_801a162c; + bl DCFlushRange; lbl_80193f54: cmpwi r31, 0; bne lbl_80193f6c; @@ -1476,10 +1470,10 @@ asm UNKNOWN_FUNCTION(IOS_IoctlAsync) { mr r3, r25; mr r4, r26; stw r26, 0x14(r5); - bl unk_801a162c; + bl DCFlushRange; mr r3, r27; mr r4, r28; - bl unk_801a162c; + bl DCFlushRange; lbl_8019425c: cmpwi r31, 0; bne lbl_80194274; @@ -1572,10 +1566,10 @@ asm UNKNOWN_FUNCTION(IOS_Ioctl) { mr r3, r27; mr r4, r28; stw r28, 0x14(r5); - bl unk_801a162c; + bl DCFlushRange; mr r3, r29; mr r4, r30; - bl unk_801a162c; + bl DCFlushRange; lbl_8019438c: cmpwi r31, 0; bne lbl_801943a4; @@ -1628,7 +1622,7 @@ asm UNKNOWN_FUNCTION(__ios_Ioctlv) { add r4, r3, r0; lwzx r3, r3, r0; lwz r4, 4(r4); - bl unk_801a162c; + bl DCFlushRange; lwz r4, 0x18(r29); add r3, r28, r27; lwzx r5, r4, r3; @@ -1654,7 +1648,7 @@ asm UNKNOWN_FUNCTION(__ios_Ioctlv) { add r4, r0, r28; lwzx r3, r28, r0; lwz r4, 4(r4); - bl unk_801a162c; + bl DCFlushRange; lwz r3, 0x18(r29); lwzx r4, r3, r28; cmpwi r4, 0; @@ -1675,7 +1669,7 @@ asm UNKNOWN_FUNCTION(__ios_Ioctlv) { lwz r3, 0x18(r29); add r0, r4, r0; slwi r4, r0, 3; - bl unk_801a162c; + bl DCFlushRange; cmpwi r30, 0; beq lbl_801944d8; addis r0, r30, 0x8000; @@ -1916,7 +1910,7 @@ asm UNKNOWN_FUNCTION(IOS_IoctlvReboot) { bl OSInitThreadQueue; mr r3, r29; li r4, 0x20; - bl unk_801a162c; + bl DCFlushRange; bl OSDisableInterrupts; lis r4, 0x8034; mr r30, r3; diff --git a/source/rvl/os/os.h b/source/rvl/os/os.h index 8d40ed44c..127e0bb32 100644 --- a/source/rvl/os/os.h +++ b/source/rvl/os/os.h @@ -35,8 +35,6 @@ OSTime OSGetTime(void); OSTime OSCalendarTimeToTicks(OSCalendarTime* td); void OSTicksToCalendarTime(OSTime ticks, OSCalendarTime* td); -void OSPanic(char* file, int line, char* msg, ...); - #ifdef __cplusplus } #endif diff --git a/source/rvl/os/osAlarm.c b/source/rvl/os/osAlarm.c new file mode 100644 index 000000000..68c993f8c --- /dev/null +++ b/source/rvl/os/osAlarm.c @@ -0,0 +1,227 @@ +#include "osAlarm.h" + +#include "osException.h" +#include "osInterrupt.h" +#include "osReset.h" + +// Extern function references. +// PAL: 0x8012e594 +extern void PPCMtdec(u32); +// PAL: 0x80163594 +extern int __DVDTestAlarm(const OSAlarm*); +// PAL: 0x801a9e30 +extern void __OSReschedule(); +// PAL: 0x801aad7c +extern OSTime __OSGetSystemTime(); +// PAL: 0x801aade0 +extern OSTime __OSTimeToSystemTime(OSTime); + +static struct OS_AlarmQueue { + OSAlarm* head; + OSAlarm* tail; +} alarmQueue; + +static OSShutdownFunctionInfo shutdownFunctionInfo = { + (u32)OS_Alarm_OnReset, + 0xffffffff, +}; + +static inline void OS_AlarmSetTimer(OSAlarm* a) { + OSTime x = a->fire - __OSGetSystemTime(); + if (x < 0) + PPCMtdec(0); + else if (x < 0x80000000) + PPCMtdec((u32)x); + else + PPCMtdec(0x7fffffff); +} + +// Symbol: __OSInitAlarm +// PAL: 0x801a05b8..0x801a0610 +void __OSInitAlarm() { + if (__OSGetExceptionHandler(8) != OS_Alarm_DecrementerExceptionHandler) { + alarmQueue.head = alarmQueue.tail = 0; + __OSSetExceptionHandler(8, OS_Alarm_DecrementerExceptionHandler); + OSRegisterShutdownFunction(&shutdownFunctionInfo); + } +} + +// Symbol: OSCreateAlarm +// PAL: 0x801a0610..0x801a0620 +void OSCreateAlarm(OSAlarm* a) { + a->handler = NULL; + a->tag = 0; +} + +// Symbol: OS_Alarm_InsertAlarm +// PAL: 0x801a0620..0x801a0870 +void OS_Alarm_InsertAlarm(OSAlarm* a, OSTime t, OSAlarmHandler handler) { + if (0 < a->repeat) { + OSTime now = __OSGetSystemTime(); + t = a->begin; + if (a->begin < now) + t += a->repeat * ((now - a->begin) / a->repeat + 1); + } + a->handler = handler; + a->fire = t; + OSAlarm* succ; + OSAlarm* pred; + for (succ = alarmQueue.head; succ; succ = succ->succ) { + if (succ->fire <= t) + continue; + a->pred = succ->pred; + succ->pred = a; + a->succ = succ; + pred = a->pred; + if (pred) { + pred->succ = a; + } else { + alarmQueue.head = a; + OS_AlarmSetTimer(a); + } + return; + } + a->succ = NULL; + pred = alarmQueue.tail; + alarmQueue.tail = a; + a->pred = pred; + if (pred) { + pred->succ = a; + } else { + alarmQueue.head = alarmQueue.tail = a; + OS_AlarmSetTimer(a); + } +} + +// Symbol: OSSetAlarm +// PAL: 0x801a0870..0x801a08e0 +void OSSetAlarm(OSAlarm* a, OSTime time, OSAlarmHandler handler) { + int interrupts = OSDisableInterrupts(); + a->repeat = 0; + OS_Alarm_InsertAlarm(a, __OSGetSystemTime() + time, handler); + OSRestoreInterrupts(interrupts); +} + +// Symbol: OSSetPeriodicAlarm +// PAL: 0x801a08e0..0x801a0964 +void OSSetPeriodicAlarm(OSAlarm* a, OSTime start, OSTime interval, + OSAlarmHandler handler) { + int interrupts = OSDisableInterrupts(); + a->repeat = interval; + a->begin = __OSTimeToSystemTime(start); + OS_Alarm_InsertAlarm(a, 0, handler); + OSRestoreInterrupts(interrupts); +} + +// Symbol: OSCancelAlarm +// PAL: 0x801a0964..0x801a0a7c +void OSCancelAlarm(OSAlarm* a) { + int interrupts = OSDisableInterrupts(); + if (!a->handler) { + OSRestoreInterrupts(interrupts); + return; + } + OSAlarm* succ = a->succ; + if (!succ) + alarmQueue.tail = a->pred; + else + succ->pred = a->pred; + if (a->pred) + a->pred->succ = succ; + else { + alarmQueue.head = succ; + if (succ) + OS_AlarmSetTimer(succ); + } + a->handler = NULL; + OSRestoreInterrupts(interrupts); +} + +// Symbol: OS_Alarm_DecrementerExceptionCallback +// PAL: 0x801a0a7c..0x801a0ca8 +void OS_Alarm_DecrementerExceptionCallback(u8, OSContext* context) { + OSTime now = __OSGetSystemTime(); + OSAlarm* a = alarmQueue.head; + if (!a) + OSLoadContext(context); + if (now < a->fire) { + OS_AlarmSetTimer(a); + OSLoadContext(context); + } + OSAlarm* succ = a->succ; + alarmQueue.head = succ; + if (!succ) + alarmQueue.tail = NULL; + else + succ->pred = NULL; + OSAlarmHandler handler = a->handler; + a->handler = NULL; + if (0 < a->repeat) + OS_Alarm_InsertAlarm(a, 0, handler); + if (alarmQueue.head) + OS_AlarmSetTimer(alarmQueue.head); + OSDisableScheduler(); + OSContext exceptionCtx; + OSClearContext(&exceptionCtx); + OSSetCurrentContext(&exceptionCtx); + handler(a, context); + OSClearContext(&exceptionCtx); + OSSetCurrentContext(context); + OSEnableScheduler(); + __OSReschedule(); + OSLoadContext(context); +} + +// Symbol: OS_Alarm_DecrementerExceptionHandler +// PAL: 0x801a0ca8..0x801a0cf8 +asm void OS_Alarm_DecrementerExceptionHandler(u8 exception, + register OSContext* context) { + nofralloc; + stw r0, 0(r4); + stw r1, 4(r4); + stw r2, 8(r4); + stmw r6, 0x18(context); + mfspr r0, 0x391; + stw r0, 0x1a8(context); + mfspr r0, 0x392; + stw r0, 0x1ac(context); + mfspr r0, 0x393; + stw r0, 0x1b0(context); + mfspr r0, 0x394; + stw r0, 0x1b4(context); + mfspr r0, 0x395; + stw r0, 0x1b8(context); + mfspr r0, 0x396; + stw r0, 0x1bc(context); + mfspr r0, 0x397; + stw r0, 0x1c0(context); + stwu r1, -8(r1); + b OS_Alarm_DecrementerExceptionCallback; +} + +// Symbol: OSSetAlarmTag +// PAL: 0x801a0cf8..0x801a0d00 +void OSSetAlarmTag(OSAlarm* a, u32 tag) { a->tag = tag; } + +// Symbol: OS_Alarm_OnReset +// PAL: 0x801a0d00..0x801a0d8c +int OS_Alarm_OnReset(int arg1, u32 arg2) { + if (arg1) { + OSAlarm* a; + OSAlarm* succ; + for (a = alarmQueue.head, succ = a ? a->succ : NULL; a; + a = succ, succ = a ? a->succ : NULL) { + if (!__DVDTestAlarm(a)) + OSCancelAlarm(a); + } + } + return 1; +} + +// Symbol: OSSetAlarmUserData +// PAL: 0x801a0d8c..0x801a0d94 +void OSSetAlarmUserData(OSAlarm* a, void* data) { a->data = data; } + +// Symbol: OSGetAlarmUserData +// PAL: 0x801a0d94..0x801a0d9c +void* OSGetAlarmUserData(OSAlarm* a) { return a->data; } diff --git a/source/rvl/os/osAlarm.h b/source/rvl/os/osAlarm.h index 132b8d66c..25a24baf0 100644 --- a/source/rvl/os/osAlarm.h +++ b/source/rvl/os/osAlarm.h @@ -1,26 +1,54 @@ #pragma once +#include +#include + #include "os.h" +#include "osContext.h" #ifdef __cplusplus extern "C" { #endif typedef struct OSAlarm OSAlarm; -typedef void (*OSAlarmHandler)(OSAlarm* alarm, OSContext* context); +typedef void (*OSAlarmHandler)(OSAlarm*, OSContext*); struct OSAlarm { OSAlarmHandler handler; u32 tag; OSTime fire; - OSAlarm* prev; - OSAlarm* next; - - // Periodic alarm - OSTime period; - OSTime start; + OSAlarm* pred; + OSAlarm* succ; + OSTime repeat; + OSTime begin; + void* data; }; +// PAL: 0x801a05b8..0x801a0610 +void __OSInitAlarm(); +// PAL: 0x801a0610..0x801a0620 +void OSCreateAlarm(OSAlarm*); +// PAL: 0x801a0620..0x801a0870 +void OS_Alarm_InsertAlarm(OSAlarm*, OSTime, OSAlarmHandler); +// PAL: 0x801a0870..0x801a08e0 +void OSSetAlarm(OSAlarm*, OSTime, OSAlarmHandler); +// PAL: 0x801a08e0..0x801a0964 +void OSSetPeriodicAlarm(OSAlarm*, OSTime, OSTime, OSAlarmHandler); +// PAL: 0x801a0964..0x801a0a7c +void OSCancelAlarm(OSAlarm*); +// PAL: 0x801a0a7c..0x801a0ca8 +void OS_Alarm_DecrementerExceptionCallback(u8 exception, OSContext* context); +// PAL: 0x801a0ca8..0x801a0cf8 +void OS_Alarm_DecrementerExceptionHandler(u8 exception, OSContext* context); +// PAL: 0x801a0cf8..0x801a0d00 +void OSSetAlarmTag(OSAlarm*, u32); +// PAL: 0x801a0d00..0x801a0d8c +int OS_Alarm_OnReset(int, u32); +// PAL: 0x801a0d8c..0x801a0d94 +void OSSetAlarmUserData(OSAlarm*, void*); +// PAL: 0x801a0d94..0x801a0d9c +void* OSGetAlarmUserData(OSAlarm*); + #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/source/rvl/os/osAlloc.c b/source/rvl/os/osAlloc.c new file mode 100644 index 000000000..a9d6b7d5e --- /dev/null +++ b/source/rvl/os/osAlloc.c @@ -0,0 +1,150 @@ +#include "osAlloc.h" + +volatile int __OSCurrentHeap = -1; + +static OSAllocHeap* OS_AllocHeapArray; +static int OS_AllocNumHeaps; +static void* OS_AllocArenaStart; +static void* OS_AllocArenaStop; + +// Symbol: DLInsert +// PAL: 0x801a0d9c..0x801a0e48 +OSAllocCell* DLInsert(OSAllocCell* list, OSAllocCell* cell) { + OSAllocCell* pred; + OSAllocCell* succ; + for (succ = list, pred = NULL; succ; pred = succ, succ = succ->succ) + if (cell <= succ) + break; + cell->succ = succ; + cell->pred = pred; + if (succ) { + succ->pred = cell; + if ((char*)cell + cell->size == (char*)succ) { + cell->size += succ->size; + cell->succ = succ = succ->succ; + if (succ) + succ->pred = cell; + } + } + if (pred) { + pred->succ = cell; + if ((char*)pred + pred->size == (char*)cell) { + pred->size += cell->size; + pred->succ = succ; + if (succ) + succ->pred = pred; + } + return list; + } else { + return cell; + } +} + +static inline OSAllocCell* DLExtract(OSAllocCell* list, OSAllocCell* cell) { + if (cell->succ) + cell->succ->pred = cell->pred; + if (!cell->pred) { + return cell->succ; + } else { + cell->pred->succ = cell->succ; + return list; + } +} + +static inline OSAllocCell* DLAddFront(OSAllocCell* list, OSAllocCell* cell) { + cell->succ = list; + cell->pred = NULL; + if (list) + list->pred = cell; + return cell; +} + +// Symbol: OSAllocFromHeap +// PAL: 0x801a0e48..0x801a0f40 +void* OSAllocFromHeap(int heapIdx, u32 size) { + OSAllocHeap* heap = &OS_AllocHeapArray[heapIdx]; + size += 0x20; + size = (size + 0x1f) & 0xffffffe0; + OSAllocCell* cell; + for (cell = heap->free; cell != NULL; cell = cell->succ) + if ((s32)size <= cell->size) + break; + if (!cell) + return NULL; + int rightBytes = cell->size - (int)size; + if ((u32)rightBytes < 64) { + heap->free = DLExtract(heap->free, cell); + } else { + cell->size = (int)size; + OSAllocCell* rightCell = (OSAllocCell*)((char*)cell + size); + rightCell->size = rightBytes; + rightCell->pred = cell->pred; + rightCell->succ = cell->succ; + if (rightCell->succ) + rightCell->succ->pred = rightCell; + if (rightCell->pred) { + rightCell->pred->succ = rightCell; + } else { + heap->free = rightCell; + } + } + heap->allocated = DLAddFront(heap->allocated, cell); + return (void*)((char*)cell + 32); +} + +// Symbol: OSFreeToHeap +// PAL: 0x801a0f40..0x801a0fb8 +void OSFreeToHeap(int heapIdx, void* data) { + OSAllocCell* cell = (OSAllocCell*)((u32)data - 0x20); + OSAllocHeap* heap = &OS_AllocHeapArray[heapIdx]; + heap->allocated = DLExtract(heap->allocated, cell); + heap->free = DLInsert(heap->free, cell); +} + +// Symbol: OSSetCurrentHeap +// PAL: 0x801a0fb8..0x801a0fc8 +int OSSetCurrentHeap(int newHeap) { + int oldHeap = __OSCurrentHeap; + __OSCurrentHeap = newHeap; + return oldHeap; +} + +// Symbol: OSInitAlloc +// PAL: 0x801a0fc8..0x801a1038 +void* OSInitAlloc(void* a1, void* a2, int count) { + u32 size = sizeof(OSAllocHeap) * count; + OS_AllocHeapArray = a1; + OS_AllocNumHeaps = count; + for (int i = 0; i < OS_AllocNumHeaps; i++) { + OSAllocHeap* heap = &OS_AllocHeapArray[i]; + heap->size = -1; + heap->free = heap->allocated = NULL; + } + __OSCurrentHeap = -1; + a1 = (void*)((char*)OS_AllocHeapArray + size); + a1 = (void*)(((u32)a1 + 31) & ~31); + OS_AllocArenaStart = a1; + OS_AllocArenaStop = (void*)(((u32)a2) & ~31); + return a1; +} + +// Symbol: OSCreateHeap +// PAL: 0x801a1038..0x801a10a4 +int OSCreateHeap(void* start, void* stop) { + start = (void*)(((u32)start + 0x1f) & 0xffffffe0); + stop = (void*)((u32)stop & 0xffffffe0); + for (int i = 0; i < OS_AllocNumHeaps; i++) { + OSAllocHeap* heap = &OS_AllocHeapArray[i]; + if (heap->size < 0) { + heap->size = (char*)stop - (char*)start; + OSAllocCell* cell = (OSAllocCell*)start; + cell->pred = NULL; + cell->succ = NULL; + cell->size = heap->size; + heap->free = cell; + heap->allocated = 0; + return i; + } + } + return -1; +} diff --git a/source/rvl/os/osAlloc.h b/source/rvl/os/osAlloc.h new file mode 100644 index 000000000..53a187e5f --- /dev/null +++ b/source/rvl/os/osAlloc.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OSAllocCell OSAllocCell; +struct OSAllocCell { + OSAllocCell* pred; + OSAllocCell* succ; + s32 size; +}; +typedef struct OSAllocHeap { + s32 size; + OSAllocCell* free; + OSAllocCell* allocated; +} OSAllocHeap; + +// PAL: 0x801a0d9c..0x801a0e48 +OSAllocCell* DLInsert(OSAllocCell* list, OSAllocCell* cell); +// PAL: 0x801a0e48..0x801a0f40 +void* OSAllocFromHeap(int, u32); +// PAL: 0x801a0f40..0x801a0fb8 +void OSFreeToHeap(int, void*); +// PAL: 0x801a0fb8..0x801a0fc8 +int OSSetCurrentHeap(int); +// PAL: 0x801a0fc8..0x801a1038 +void* OSInitAlloc(void*, void*, int); +// PAL: 0x801a1038..0x801a10a4 +int OSCreateHeap(void*, void*); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osArena.c b/source/rvl/os/osArena.c new file mode 100644 index 000000000..9b30d774d --- /dev/null +++ b/source/rvl/os/osArena.c @@ -0,0 +1,65 @@ +#include "osArena.h" + +static void* __OSMEM1ArenaHi = (void*)0x00000000; +static void* __OSMEM1ArenaLo = (void*)0xffffffff; +static void* __OSMEM2ArenaHi = (void*)0x00000000; +static void* __OSMEM2ArenaLo = (void*)0xffffffff; + +// Symbol: OSGetMEM1ArenaHi +// PAL: 0x801a10a4..0x801a10ac +void* OSGetMEM1ArenaHi() { return __OSMEM1ArenaHi; } + +// Symbol: OSGetMEM2ArenaHi +// PAL: 0x801a10ac..0x801a10b4 +void* OSGetMEM2ArenaHi() { return __OSMEM2ArenaHi; } + +// Symbol: OSGetArenaHi +// PAL: 0x801a10b4..0x801a10bc +void* OSGetArenaHi() { return __OSMEM1ArenaHi; } + +// Symbol: OSGetMEM1ArenaLo +// PAL: 0x801a10bc..0x801a10c4 +void* OSGetMEM1ArenaLo() { return __OSMEM1ArenaLo; } + +// Symbol: OSGetMEM2ArenaLo +// PAL: 0x801a10c4..0x801a10cc +void* OSGetMEM2ArenaLo() { return __OSMEM2ArenaLo; } + +// Symbol: OSGetArenaLo +// PAL: 0x801a10cc..0x801a10d4 +void* OSGetArenaLo() { return __OSMEM1ArenaLo; } + +// Symbol: OSSetMEM1ArenaHi +// PAL: 0x801a10d4..0x801a10dc +void OSSetMEM1ArenaHi(void* x) { __OSMEM1ArenaHi = x; } + +// Symbol: OSSetMEM2ArenaHi +// PAL: 0x801a10dc..0x801a10e4 +void OSSetMEM2ArenaHi(void* x) { __OSMEM2ArenaHi = x; } + +// Symbol: OSSetArenaHi +// PAL: 0x801a10e4..0x801a10ec +void OSSetArenaHi(void* x) { __OSMEM1ArenaHi = x; } + +// Symbol: OSSetMEM1ArenaLo +// PAL: 0x801a10ec..0x801a10f4 +void OSSetMEM1ArenaLo(void* x) { __OSMEM1ArenaLo = x; } + +// Symbol: OSSetMEM2ArenaLo +// PAL: 0x801a10f4..0x801a10fc +void OSSetMEM2ArenaLo(void* x) { __OSMEM2ArenaLo = x; } + +// Symbol: OSSetArenaLo +// PAL: 0x801a10fc..0x801a1104 +void OSSetArenaLo(void* x) { __OSMEM1ArenaLo = x; } + +// Symbol: OSAllocFromMEM1ArenaLo +// PAL: 0x801a1104..0x801a1138 +void* OSAllocFromMEM1ArenaLo(u32 size, u32 align) { + void* lo = OSGetMEM1ArenaLo(); + u8* arenaLo = lo = (void*)(((u32)(lo) + (align)-1) & ~((align)-1)); + arenaLo += size; + arenaLo = (u8*)(((u32)(arenaLo) + (align)-1) & ~((align)-1)); + OSSetMEM1ArenaLo(arenaLo); + return lo; +} diff --git a/source/rvl/os/osArena.h b/source/rvl/os/osArena.h new file mode 100644 index 000000000..9eff16ce7 --- /dev/null +++ b/source/rvl/os/osArena.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a10a4..0x801a10ac +void* OSGetMEM1ArenaHi(); +// PAL: 0x801a10ac..0x801a10b4 +void* OSGetMEM2ArenaHi(); +// PAL: 0x801a10b4..0x801a10bc +void* OSGetArenaHi(); +// PAL: 0x801a10bc..0x801a10c4 +void* OSGetMEM1ArenaLo(); +// PAL: 0x801a10c4..0x801a10cc +void* OSGetMEM2ArenaLo(); +// PAL: 0x801a10cc..0x801a10d4 +void* OSGetArenaLo(); +// PAL: 0x801a10d4..0x801a10dc +void OSSetMEM1ArenaHi(void*); +// PAL: 0x801a10dc..0x801a10e4 +void OSSetMEM2ArenaHi(void*); +// PAL: 0x801a10e4..0x801a10ec +void OSSetArenaHi(void*); +// PAL: 0x801a10ec..0x801a10f4 +void OSSetMEM1ArenaLo(void*); +// PAL: 0x801a10f4..0x801a10fc +void OSSetMEM2ArenaLo(void*); +// PAL: 0x801a10fc..0x801a1104 +void OSSetArenaLo(void*); +// PAL: 0x801a1104..0x801a1138 +void* OSAllocFromMEM1ArenaLo(u32 size, u32 align); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osAudio.c b/source/rvl/os/osAudio.c new file mode 100644 index 000000000..180f8b1bf --- /dev/null +++ b/source/rvl/os/osAudio.c @@ -0,0 +1,365 @@ +#include "osAudio.h" + +#include + +#include "os.h" +#include "osArena.h" +#include "osCache.h" + +// Symbol: __AIClockInit +// Function signature is unknown. +// PAL: 0x801a1138..0x801a1358 +MARK_BINARY_BLOB(__AIClockInit, 0x801a1138, 0x801a1358); +asm UNKNOWN_FUNCTION(__AIClockInit) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + lis r4, 0xcd80; + stw r0, 0x24(r1); + slwi r0, r3, 8; + stw r31, 0x1c(r1); + mr r31, r3; + stw r30, 0x18(r1); + stw r29, 0x14(r1); + stw r28, 0x10(r1); + lwz r5, 0x180(r4); + rlwinm r5, r5, 0, 0x18, 0x16; + or r5, r5, r0; + rlwinm r0, r5, 0, 0x19, 0x17; + stw r0, 0x180(r4); + lwz r0, 0x1d0(r4); + clrlwi r0, r0, 2; + stw r0, 0x1d0(r4); + bl OSGetTick; + lis r4, 0x431c; + mr r28, r3; + addi r30, r4, -8573; + lis r29, 0x8000; +lbl_801a1194: + bl OSGetTick; + lwz r0, 0xf8(r29); + subf r3, r28, r3; + slwi r3, r3, 3; + srwi r0, r0, 2; + mulhwu r0, r30, r0; + srwi r0, r0, 0xf; + divwu r0, r3, r0; + cmplwi r0, 0x64; + blt lbl_801a1194; + cmpwi r31, 0; + bne lbl_801a11ec; + lis r4, 0xcd80; + lis r3, 0xf804; + lwz r5, 0x1cc(r4); + addi r0, r3, -64; + rlwinm r3, r5, 0, 0x1a, 0xd; + ori r3, r3, 0xfc0; + and r0, r3, r0; + oris r0, r0, 0x464; + stw r0, 0x1cc(r4); + b lbl_801a1210; +lbl_801a11ec: + lis r3, 0xcd80; + lwz r0, 0x1cc(r3); + rlwinm r0, r0, 0, 0x1a, 0xd; + ori r0, r0, 0xffc0; + rlwinm r0, r0, 0, 0, 0x19; + ori r0, r0, 0xe; + rlwinm r0, r0, 0, 0xe, 4; + oris r0, r0, 0x4b0; + stw r0, 0x1cc(r3); +lbl_801a1210: + bl OSGetTick; + lis r4, 0x431c; + mr r31, r3; + addi r30, r4, -8573; + lis r29, 0x8000; +lbl_801a1224: + bl OSGetTick; + lwz r0, 0xf8(r29); + subf r3, r31, r3; + slwi r3, r3, 3; + srwi r0, r0, 2; + mulhwu r0, r30, r0; + srwi r0, r0, 0xf; + divwu r0, r3, r0; + cmplwi r0, 0x64; + blt lbl_801a1224; + lis r3, 0xcd80; + lwz r0, 0x1d0(r3); + rlwinm r0, r0, 0, 4, 2; + stw r0, 0x1d0(r3); + bl OSGetTick; + lis r4, 0x431c; + mr r31, r3; + addi r30, r4, -8573; + lis r29, 0x8000; +lbl_801a1270: + bl OSGetTick; + lwz r0, 0xf8(r29); + subf r3, r31, r3; + slwi r3, r3, 3; + srwi r0, r0, 2; + mulhwu r0, r30, r0; + srwi r0, r0, 0xf; + divwu r0, r3, r0; + cmplwi r0, 0x3e8; + blt lbl_801a1270; + lis r3, 0xcd80; + lwz r0, 0x1d0(r3); + rlwinm r0, r0, 0, 2, 0; + oris r0, r0, 0x4000; + stw r0, 0x1d0(r3); + bl OSGetTick; + lis r4, 0x431c; + mr r31, r3; + addi r30, r4, -8573; + lis r29, 0x8000; +lbl_801a12c0: + bl OSGetTick; + lwz r0, 0xf8(r29); + subf r3, r31, r3; + slwi r3, r3, 3; + srwi r0, r0, 2; + mulhwu r0, r30, r0; + srwi r0, r0, 0xf; + divwu r0, r3, r0; + cmplwi r0, 0x3e8; + blt lbl_801a12c0; + lis r3, 0xcd80; + lwz r0, 0x1d0(r3); + clrlwi r0, r0, 1; + oris r0, r0, 0x8000; + stw r0, 0x1d0(r3); + bl OSGetTick; + lis r4, 0x431c; + mr r29, r3; + addi r30, r4, -8573; + lis r31, 0x8000; +lbl_801a1310: + bl OSGetTick; + lwz r0, 0xf8(r31); + subf r3, r29, r3; + slwi r3, r3, 3; + srwi r0, r0, 2; + mulhwu r0, r30, r0; + srwi r0, r0, 0xf; + divwu r0, r3, r0; + cmplwi r0, 0x3e8; + blt lbl_801a1310; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSInitAudioSystem +// Function signature is unknown. +// PAL: 0x801a1358..0x801a1520 +MARK_BINARY_BLOB(__OSInitAudioSystem, 0x801a1358, 0x801a1520); +asm UNKNOWN_FUNCTION(__OSInitAudioSystem) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + lwz r0, -0x63a8(r13); + cmpwi r0, 0; + bne lbl_801a137c; + li r3, 1; + bl __AIClockInit; +lbl_801a137c: + bl OSGetArenaHi; + lis r4, 0x8100; + li r5, 0x80; + addi r3, r3, -128; + bl memcpy; + lis r4, 0x8029; + lis r3, 0x8100; + addi r4, r4, -3992; + li r5, 0x80; + bl memcpy; + lis r3, 0x8100; + li r4, 0x80; + bl DCFlushRange; + lis r3, 0xcc00; + li r0, 0x43; + sth r0, 0x5012(r3); + li r0, 0x8ac; + sth r0, 0x500a(r3); + lhz r0, 0x500a(r3); + ori r0, r0, 1; + sth r0, 0x500a(r3); +lbl_801a13d0: + lhz r0, 0x500a(r3); + clrlwi. r0, r0, 0x1f; + bne lbl_801a13d0; + li r0, 0; + lis r4, 0xcc00; + sth r0, 0x5000(r4); +lbl_801a13e8: + lhz r3, 0x5004(r4); + lhz r0, 0x5006(r4); + rlwimi r0, r3, 0x10, 0, 0xf; + rlwinm. r0, r0, 0, 0, 0; + bne lbl_801a13e8; + lis r4, 0xcc00; + lis r0, 0x100; + stw r0, 0x5020(r4); + li r3, 0; + li r0, 0x20; + stw r3, 0x5024(r4); + stw r0, 0x5028(r4); + lhz r5, 0x500a(r4); + b lbl_801a1424; +lbl_801a1420: + lhz r5, 0x500a(r4); +lbl_801a1424: + rlwinm. r0, r5, 0, 0x1a, 0x1a; + beq lbl_801a1420; + lis r3, 0xcc00; + sth r5, 0x500a(r3); + bl OSGetTick; + mr r31, r3; +lbl_801a143c: + bl OSGetTick; + subf r0, r31, r3; + cmpwi r0, 0x892; + blt lbl_801a143c; + lis r4, 0xcc00; + lis r0, 0x100; + stw r0, 0x5020(r4); + li r3, 0; + li r0, 0x20; + stw r3, 0x5024(r4); + stw r0, 0x5028(r4); + lhz r5, 0x500a(r4); + b lbl_801a1474; +lbl_801a1470: + lhz r5, 0x500a(r4); +lbl_801a1474: + rlwinm. r0, r5, 0, 0x1a, 0x1a; + beq lbl_801a1470; + lis r3, 0xcc00; + sth r5, 0x500a(r3); + lhz r0, 0x500a(r3); + rlwinm r0, r0, 0, 0x15, 0x13; + sth r0, 0x500a(r3); +lbl_801a1490: + lhz r0, 0x500a(r3); + rlwinm. r0, r0, 0, 0x15, 0x15; + bne lbl_801a1490; + lis r3, 0xcc00; + lhz r0, 0x500a(r3); + rlwinm r0, r0, 0, 0x1e, 0x1c; + sth r0, 0x500a(r3); + lhz r0, 0x5004(r3); + b lbl_801a14b8; +lbl_801a14b4: + lhz r0, 0x5004(r3); +lbl_801a14b8: + rlwinm. r0, r0, 0, 0x10, 0x10; + beq lbl_801a14b4; + lis r4, 0xcc00; + li r0, 0x8ac; + lhz r3, 0x5006(r4); + lhz r3, 0x500a(r4); + ori r3, r3, 4; + sth r3, 0x500a(r4); + sth r0, 0x500a(r4); + lhz r0, 0x500a(r4); + ori r0, r0, 1; + sth r0, 0x500a(r4); +lbl_801a14e8: + lhz r0, 0x500a(r4); + clrlwi. r0, r0, 0x1f; + bne lbl_801a14e8; + bl OSGetArenaHi; + mr r4, r3; + lis r3, 0x8100; + addi r4, r4, -128; + li r5, 0x80; + bl memcpy; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSStopAudioSystem +// Function signature is unknown. +// PAL: 0x801a1520..0x801a15ec +MARK_BINARY_BLOB(__OSStopAudioSystem, 0x801a1520, 0x801a15ec); +asm UNKNOWN_FUNCTION(__OSStopAudioSystem) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + lis r3, 0xcc00; + stw r0, 0x14(r1); + li r0, 0x804; + stw r31, 0xc(r1); + sth r0, 0x500a(r3); + lhz r0, 0x5036(r3); + clrlwi r0, r0, 0x11; + sth r0, 0x5036(r3); + lhz r0, 0x500a(r3); + b lbl_801a1554; +lbl_801a1550: + lhz r0, 0x500a(r3); +lbl_801a1554: + rlwinm. r0, r0, 0, 0x15, 0x15; + bne lbl_801a1550; + lis r3, 0xcc00; + lhz r0, 0x500a(r3); + b lbl_801a156c; +lbl_801a1568: + lhz r0, 0x500a(r3); +lbl_801a156c: + rlwinm. r0, r0, 0, 0x16, 0x16; + bne lbl_801a1568; + lis r4, 0xcc00; + li r0, 0x8ac; + sth r0, 0x500a(r4); + li r0, 0; + sth r0, 0x5000(r4); +lbl_801a1588: + lhz r3, 0x5004(r4); + lhz r0, 0x5006(r4); + rlwimi r0, r3, 0x10, 0, 0xf; + rlwinm. r0, r0, 0, 0, 0; + bne lbl_801a1588; + bl OSGetTick; + mr r31, r3; +lbl_801a15a4: + bl OSGetTick; + subf r0, r31, r3; + cmpwi r0, 0x2c; + blt lbl_801a15a4; + lis r3, 0xcc00; + lhz r0, 0x500a(r3); + ori r0, r0, 1; + sth r0, 0x500a(r3); + lhz r0, 0x500a(r3); + b lbl_801a15d0; +lbl_801a15cc: + lhz r0, 0x500a(r3); +lbl_801a15d0: + clrlwi. r0, r0, 0x1f; + bne lbl_801a15cc; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} diff --git a/source/rvl/os/osAudio.h b/source/rvl/os/osAudio.h new file mode 100644 index 000000000..daa6717f2 --- /dev/null +++ b/source/rvl/os/osAudio.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a1138..0x801a1358 +UNKNOWN_FUNCTION(__AIClockInit); +// PAL: 0x801a1358..0x801a1520 +UNKNOWN_FUNCTION(__OSInitAudioSystem); +// PAL: 0x801a1520..0x801a15ec +UNKNOWN_FUNCTION(__OSStopAudioSystem); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osCache.c b/source/rvl/os/osCache.c new file mode 100644 index 000000000..c3f497b83 --- /dev/null +++ b/source/rvl/os/osCache.c @@ -0,0 +1,522 @@ +#include "osCache.h" + +#include + +#include "osContext.h" +#include "osError.h" +#include "osInterrupt.h" + +// Extern function references. +// PAL: 0x8015d3ac +extern UNKNOWN_FUNCTION(DBPrintf); + +// Symbol: DCEnable +// PAL: 0x801a15ec..0x801a1600 +asm void DCEnable(void) { + nofralloc; + sync; + mfspr r3, 0x3f0; + ori r3, r3, 0x4000; + mtspr 0x3f0, r3; + blr; +} + +// Symbol: DCInvalidateRange +// PAL: 0x801a1600..0x801a162c +asm void DCInvalidateRange(register void* addr, register u32 size) { + nofralloc; + cmplwi size, 0; + blelr; + clrlwi r5, addr, 0x1b; + add size, size, r5; + addi size, size, 0x1f; + srwi size, size, 5; + mtctr size; +_again: + dcbi 0, addr; + addi addr, addr, 0x20; + bdnz _again; + blr; +} + +// Symbol: DCFlushRange +// PAL: 0x801a162c..0x801a165c +asm void DCFlushRange(register void* addr, register u32 size) { + nofralloc; + cmplwi size, 0; + blelr; + clrlwi r5, addr, 0x1b; + add size, size, r5; + addi size, size, 0x1f; + srwi size, size, 5; + mtctr size; +_again: + dcbf 0, addr; + addi addr, addr, 0x20; + bdnz _again; + sc; + blr; +} + +// Symbol: DCStoreRange +// PAL: 0x801a165c..0x801a168c +asm void DCStoreRange(register void* addr, register u32 size) { + nofralloc; + cmplwi size, 0; + blelr; + clrlwi r5, addr, 0x1b; + add size, size, r5; + addi size, size, 0x1f; + srwi size, size, 5; + mtctr size; +_again: + dcbst 0, addr; + addi addr, addr, 0x20; + bdnz _again; + sc; + blr; +} + +// Symbol: DCFlushRangeNoSync +// PAL: 0x801a168c..0x801a16b8 +asm void DCFlushRangeNoSync(register void* addr, register u32 size) { + nofralloc; + cmplwi size, 0; + blelr; + clrlwi r5, addr, 0x1b; + add size, size, r5; + addi size, size, 0x1f; + srwi size, size, 5; + mtctr size; +_again: + dcbf 0, addr; + addi addr, addr, 0x20; + bdnz _again; + blr; +} + +// Symbol: DCStoreRangeNoSync +// PAL: 0x801a16b8..0x801a16e4 +asm void DCStoreRangeNoSync(register void* addr, register u32 size) { + nofralloc; + cmplwi size, 0; + blelr; + clrlwi r5, addr, 0x1b; + add size, size, r5; + addi size, size, 0x1f; + srwi size, size, 5; + mtctr size; +_again: + dcbst 0, addr; + addi addr, addr, 0x20; + bdnz _again; + blr; +} + +// Symbol: DCZeroRange +// PAL: 0x801a16e4..0x801a1710 +asm void DCZeroRange(register void* addr, register u32 size) { + nofralloc; + cmplwi size, 0; + blelr; + clrlwi r5, addr, 0x1b; + add size, size, r5; + addi size, size, 0x1f; + srwi size, size, 5; + mtctr size; +_again: + dcbz 0, addr; + addi addr, addr, 0x20; + bdnz _again; + blr; +} + +// Symbol: ICInvalidateRange +// PAL: 0x801a1710..0x801a1744 +asm void ICInvalidateRange(register void* addr, register u32 size) { + nofralloc; + cmplwi size, 0; + blelr; + clrlwi r5, addr, 0x1b; + add size, size, r5; + addi size, size, 0x1f; + srwi size, size, 5; + mtctr size; +_again: + icbi 0, addr; + addi addr, addr, 0x20; + bdnz _again; + sync; + isync; + blr; +} + +// Symbol: ICFlashInvalidate +// PAL: 0x801a1744..0x801a1754 +asm void ICFlashInvalidate(void) { + nofralloc; + mfspr r3, 0x3f0; + ori r3, r3, 0x800; + mtspr 0x3f0, r3; + blr; +} + +// Symbol: ICEnable +// PAL: 0x801a1754..0x801a1768 +asm void ICEnable(void) { + nofralloc; + isync; + mfspr r3, 0x3f0; + ori r3, r3, 0x8000; + mtspr 0x3f0, r3; + blr; +} + +// Symbol: __LCEnable +// PAL: 0x801a1768..0x801a1834 +asm void __LCEnable(void) { + nofralloc; + mfmsr r5; + ori r5, r5, 0x1000; + mtmsr r5; + lis r3, 0x8000; + li r4, 0x400; + mtctr r4; +lbl_801a1780: + dcbt 0, r3; + dcbst 0, r3; + addi r3, r3, 0x20; + bdnz lbl_801a1780; + mfspr r4, 0x398; + oris r4, r4, 0x100f; + mtspr 0x398, r4; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + lis r3, 0xe000; + ori r3, r3, 2; + mtdbatl 3, r3; + ori r3, r3, 0x1fe; + mtdbatu 3, r3; + isync; + lis r3, 0xe000; + li r6, 0x200; + mtctr r6; + li r6, 0; +lbl_801a17f4: + dcbz_l r6, r3; + addi r3, r3, 0x20; + bdnz lbl_801a17f4; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + nop; + blr; +} + +// Symbol: LCEnable +// PAL: 0x801a1834..0x801a186c +void LCEnable(void) { + int interrupts = OSDisableInterrupts(); + __LCEnable(); + OSRestoreInterrupts(interrupts); +} + +// Symbol: LCDisable +// PAL: 0x801a186c..0x801a1894 +asm void LCDisable(void) { + nofralloc; + lis r3, 0xe000; + li r4, 0x200; + mtctr r4; +_again: + dcbi 0, r3; + addi r3, r3, 0x20; + bdnz _again; + mfspr r4, 0x398; + rlwinm r4, r4, 0, 4, 2; + mtspr 0x398, r4; + blr; +} + +// Symbol: LCLoadBlocks +// PAL: 0x801a1894..0x801a18b8 +asm void LCLoadBlocks(register void* dst, register void* src, + register u32 num) { + nofralloc; + rlwinm r6, num, 0x1e, 0x1b, 0x1f; + clrlwi src, src, 3; + or r6, r6, src; + mtspr 0x39a, r6; + rlwinm r6, num, 2, 0x1c, 0x1d; + or r6, r6, dst; + ori r6, r6, 0x12; + mtspr 0x39b, r6; + blr; +} + +// Symbol: LCStoreBlocks +// PAL: 0x801a18b8..0x801a18dc +asm void LCStoreBlocks(register void* dst, register void* src, + register u32 num) { + nofralloc; + rlwinm r6, num, 0x1e, 0x1b, 0x1f; + clrlwi dst, dst, 3; + or r6, r6, dst; + mtspr 0x39a, r6; + rlwinm r6, num, 2, 0x1c, 0x1d; + or r6, r6, src; + ori r6, r6, 2; + mtspr 0x39b, r6; + blr; +} + +// Symbol: LCStoreData +// PAL: 0x801a18dc..0x801a197c +u32 LCStoreData(void* dst, void* src, u32 size) { + u32 blocks = (size + 31) / 32; + u32 txs = (blocks + 0x7f) >> 7; + while (blocks > 0) { + if (blocks < 0x80) { + LCStoreBlocks(dst, src, blocks); + blocks = 0; + } else { + LCStoreBlocks(dst, src, 0); + blocks -= 0x80; + dst = (void*)((u32)dst + 0x1000); + src = (void*)((u32)src + 0x1000); + } + } + return txs; +} + +// Symbol: LCQueueLength +// PAL: 0x801a197c..0x801a1988 +asm u32 LCQueueLength(void) { + nofralloc; + mfspr r4, 0x398; + rlwinm r3, r4, 8, 0x1c, 0x1f; + blr; +} + +// Symbol: LCQueueWait +// PAL: 0x801a1988..0x801a199c +asm void LCQueueWait(register u32 size) { + nofralloc; +_again: + mfspr r4, 0x398; + rlwinm r4, r4, 8, 0x1c, 0x1f; + cmpw r4, size; + bgt _again; + blr; +} + +// Symbol: DMAErrorHandler +// Function signature is unknown. +// PAL: 0x801a199c..0x801a1ae4 +MARK_BINARY_BLOB(DMAErrorHandler, 0x801a199c, 0x801a1ae4); +asm UNKNOWN_FUNCTION(DMAErrorHandler) { + // clang-format off + nofralloc; + stwu r1, -0x80(r1); + mflr r0; + stw r0, 0x84(r1); + stw r31, 0x7c(r1); + stw r30, 0x78(r1); + stw r29, 0x74(r1); + mr r29, r4; + bne cr1, lbl_801a19dc; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_801a19dc: + lis r31, 0x8029; + stw r3, 8(r1); + addi r31, r31, -3864; + stw r4, 0xc(r1); + stw r5, 0x10(r1); + stw r6, 0x14(r1); + stw r7, 0x18(r1); + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + bl PPCMfhid2; + mr r30, r3; + addi r3, r31, 0x30; + crclr 6; + bl OSReport; + lwz r5, 0x19c(r29); + mr r4, r30; + addi r3, r31, 0x48; + crclr 6; + bl OSReport; + rlwinm. r0, r30, 0, 8, 0xb; + beq lbl_801a1a40; + lwz r0, 0x19c(r29); + rlwinm. r0, r0, 0, 0xa, 0xa; + bne lbl_801a1a58; +lbl_801a1a40: + addi r3, r31, 0x68; + crclr 6; + bl OSReport; + mr r3, r29; + bl OSDumpContext; + bl PPCHalt; +lbl_801a1a58: + addi r3, r31, 0x98; + crclr 6; + bl OSReport; + addi r3, r31, 0xd4; + crclr 6; + bl OSReport; + rlwinm. r0, r30, 0, 8, 8; + beq lbl_801a1a84; + addi r3, r31, 0x10c; + crclr 6; + bl OSReport; +lbl_801a1a84: + rlwinm. r0, r30, 0, 9, 9; + beq lbl_801a1a98; + addi r3, r31, 0x14c; + crclr 6; + bl OSReport; +lbl_801a1a98: + rlwinm. r0, r30, 0, 0xa, 0xa; + beq lbl_801a1aac; + addi r3, r31, 0x178; + crclr 6; + bl OSReport; +lbl_801a1aac: + rlwinm. r0, r30, 0, 0xb, 0xb; + beq lbl_801a1ac0; + addi r3, r31, 0x198; + crclr 6; + bl OSReport; +lbl_801a1ac0: + mr r3, r30; + bl PPCMthid2; + lwz r0, 0x84(r1); + lwz r31, 0x7c(r1); + lwz r30, 0x78(r1); + lwz r29, 0x74(r1); + mtlr r0; + addi r1, r1, 0x80; + blr; + // clang-format on +} + +// Symbol: __OSCacheInit +// Function signature is unknown. +// PAL: 0x801a1ae4..0x801a1c1c +MARK_BINARY_BLOB(__OSCacheInit, 0x801a1ae4, 0x801a1c1c); +asm UNKNOWN_FUNCTION(__OSCacheInit) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + lis r31, 0x8029; + addi r31, r31, -3864; + stw r30, 8(r1); + bl PPCMfhid0; + rlwinm. r0, r3, 0, 0x10, 0x10; + bne lbl_801a1b1c; + bl ICEnable; + addi r3, r31, 0x1b4; + crclr 6; + bl DBPrintf; +lbl_801a1b1c: + bl PPCMfhid0; + rlwinm. r0, r3, 0, 0x11, 0x11; + bne lbl_801a1b38; + bl DCEnable; + addi r3, r31, 0x1d0; + crclr 6; + bl DBPrintf; +lbl_801a1b38: + bl PPCMfl2cr; + rlwinm. r0, r3, 0, 0, 0; + bne lbl_801a1be8; + bl PPCMfmsr; + mr r30, r3; + sync; + li r3, 0x30; + bl PPCMtmsr; + sync; + sync; + bl PPCMfl2cr; + clrlwi r3, r3, 1; + bl PPCMtl2cr; + sync; + sync; + bl PPCMfl2cr; + clrlwi r3, r3, 1; + bl PPCMtl2cr; + sync; + bl PPCMfl2cr; + oris r3, r3, 0x20; + bl PPCMtl2cr; +lbl_801a1b90: + bl PPCMfl2cr; + clrlwi. r0, r3, 0x1f; + bne lbl_801a1b90; + bl PPCMfl2cr; + rlwinm r3, r3, 0, 0xb, 9; + bl PPCMtl2cr; + b lbl_801a1bb8; +lbl_801a1bac: + addi r3, r31, 0; + crclr 6; + bl DBPrintf; +lbl_801a1bb8: + bl PPCMfl2cr; + clrlwi. r0, r3, 0x1f; + bne lbl_801a1bac; + mr r3, r30; + bl PPCMtmsr; + bl PPCMfl2cr; + oris r0, r3, 0x8000; + rlwinm r3, r0, 0, 0xb, 9; + bl PPCMtl2cr; + addi r3, r31, 0x1ec; + crclr 6; + bl DBPrintf; +lbl_801a1be8: + lis r4, 0x801a; + li r3, 1; + addi r4, r4, 0x199c; + bl OSSetErrorHandler; + addi r3, r31, 0x204; + crclr 6; + bl DBPrintf; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} diff --git a/source/rvl/os/osCache.h b/source/rvl/os/osCache.h new file mode 100644 index 000000000..49dcd4f10 --- /dev/null +++ b/source/rvl/os/osCache.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a15ec..0x801a1600 +void DCEnable(void); +// PAL: 0x801a1600..0x801a162c +void DCInvalidateRange(void*, u32); +// PAL: 0x801a162c..0x801a165c +void DCFlushRange(void*, u32); +// PAL: 0x801a165c..0x801a168c +void DCStoreRange(void*, u32); +// PAL: 0x801a168c..0x801a16b8 +void DCFlushRangeNoSync(void*, u32); +// PAL: 0x801a16b8..0x801a16e4 +void DCStoreRangeNoSync(void*, u32); +// PAL: 0x801a16e4..0x801a1710 +void DCZeroRange(void*, u32); +// PAL: 0x801a1710..0x801a1744 +void ICInvalidateRange(void*, u32); +// PAL: 0x801a1744..0x801a1754 +void ICFlashInvalidate(void); +// PAL: 0x801a1754..0x801a1768 +void ICEnable(void); +// PAL: 0x801a1768..0x801a1834 +void __LCEnable(void); +// PAL: 0x801a1834..0x801a186c +void LCEnable(void); +// PAL: 0x801a186c..0x801a1894 +void LCDisable(void); +// PAL: 0x801a1894..0x801a18b8 +void LCLoadBlocks(void* dst, void* src, u32 num); +// PAL: 0x801a18b8..0x801a18dc +void LCStoreBlocks(void* dst, void* src, u32 num); +// PAL: 0x801a18dc..0x801a197c +u32 LCStoreData(void* dst, void* src, u32 num); +// PAL: 0x801a197c..0x801a1988 +u32 LCQueueLength(void); +// PAL: 0x801a1988..0x801a199c +void LCQueueWait(u32); +// PAL: 0x801a199c..0x801a1ae4 +UNKNOWN_FUNCTION(DMAErrorHandler); +// PAL: 0x801a1ae4..0x801a1c1c +UNKNOWN_FUNCTION(__OSCacheInit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osContext.c b/source/rvl/os/osContext.c new file mode 100644 index 000000000..deb9a7178 --- /dev/null +++ b/source/rvl/os/osContext.c @@ -0,0 +1,657 @@ +#include "osContext.h" + +#include "osError.h" +#include "osException.h" +#include "osInterrupt.h" + +// Extern function references. +// PAL: 0x8015d3ac +extern UNKNOWN_FUNCTION(DBPrintf); + +volatile OSContext* __OSCurrentContext : 0x800000d4; +volatile OSContext* __OSFPUContext : 0x800000d8; + +// Symbol: __OSLoadFPUContext +// Function signature is unknown. +// PAL: 0x801a1c1c..0x801a1d40 +MARK_BINARY_BLOB(__OSLoadFPUContext, 0x801a1c1c, 0x801a1d40); +asm UNKNOWN_FUNCTION(__OSLoadFPUContext) { + // clang-format off + nofralloc; + lhz r5, 0x1a2(r4); + clrlwi. r5, r5, 0x1f; + beq lbl_801a1d3c; + lfd f0, 0x190(r4); + mtfsf 0xff, f0; + mfspr r5, 0x398; + rlwinm. r5, r5, 3, 0x1f, 0x1f; + beq lbl_801a1cbc; + psq_l f0, 456(r4), 0, 0; + psq_l f1, 464(r4), 0, 0; + psq_l f2, 472(r4), 0, 0; + psq_l f3, 480(r4), 0, 0; + psq_l f4, 488(r4), 0, 0; + psq_l f5, 496(r4), 0, 0; + psq_l f6, 504(r4), 0, 0; + psq_l f7, 512(r4), 0, 0; + psq_l f8, 520(r4), 0, 0; + psq_l f9, 528(r4), 0, 0; + psq_l f10, 536(r4), 0, 0; + psq_l f11, 544(r4), 0, 0; + psq_l f12, 552(r4), 0, 0; + psq_l f13, 560(r4), 0, 0; + psq_l f14, 568(r4), 0, 0; + psq_l f15, 576(r4), 0, 0; + psq_l f16, 584(r4), 0, 0; + psq_l f17, 592(r4), 0, 0; + psq_l f18, 600(r4), 0, 0; + psq_l f19, 608(r4), 0, 0; + psq_l f20, 616(r4), 0, 0; + psq_l f21, 624(r4), 0, 0; + psq_l f22, 632(r4), 0, 0; + psq_l f23, 640(r4), 0, 0; + psq_l f24, 648(r4), 0, 0; + psq_l f25, 656(r4), 0, 0; + psq_l f26, 664(r4), 0, 0; + psq_l f27, 672(r4), 0, 0; + psq_l f28, 680(r4), 0, 0; + psq_l f29, 688(r4), 0, 0; + psq_l f30, 696(r4), 0, 0; + psq_l f31, 704(r4), 0, 0; +lbl_801a1cbc: + lfd f0, 0x90(r4); + lfd f1, 0x98(r4); + lfd f2, 0xa0(r4); + lfd f3, 0xa8(r4); + lfd f4, 0xb0(r4); + lfd f5, 0xb8(r4); + lfd f6, 0xc0(r4); + lfd f7, 0xc8(r4); + lfd f8, 0xd0(r4); + lfd f9, 0xd8(r4); + lfd f10, 0xe0(r4); + lfd f11, 0xe8(r4); + lfd f12, 0xf0(r4); + lfd f13, 0xf8(r4); + lfd f14, 0x100(r4); + lfd f15, 0x108(r4); + lfd f16, 0x110(r4); + lfd f17, 0x118(r4); + lfd f18, 0x120(r4); + lfd f19, 0x128(r4); + lfd f20, 0x130(r4); + lfd f21, 0x138(r4); + lfd f22, 0x140(r4); + lfd f23, 0x148(r4); + lfd f24, 0x150(r4); + lfd f25, 0x158(r4); + lfd f26, 0x160(r4); + lfd f27, 0x168(r4); + lfd f28, 0x170(r4); + lfd f29, 0x178(r4); + lfd f30, 0x180(r4); + lfd f31, 0x188(r4); +lbl_801a1d3c: + blr; + // clang-format on +} + +// Symbol: __OSSaveFPUContext +// Function signature is unknown. +// PAL: 0x801a1d40..0x801a1e68 +MARK_BINARY_BLOB(__OSSaveFPUContext, 0x801a1d40, 0x801a1e68); +asm UNKNOWN_FUNCTION(__OSSaveFPUContext) { + // clang-format off + nofralloc; + lhz r3, 0x1a2(r5); + ori r3, r3, 1; + sth r3, 0x1a2(r5); + stfd f0, 0x90(r5); + stfd f1, 0x98(r5); + stfd f2, 0xa0(r5); + stfd f3, 0xa8(r5); + stfd f4, 0xb0(r5); + stfd f5, 0xb8(r5); + stfd f6, 0xc0(r5); + stfd f7, 0xc8(r5); + stfd f8, 0xd0(r5); + stfd f9, 0xd8(r5); + stfd f10, 0xe0(r5); + stfd f11, 0xe8(r5); + stfd f12, 0xf0(r5); + stfd f13, 0xf8(r5); + stfd f14, 0x100(r5); + stfd f15, 0x108(r5); + stfd f16, 0x110(r5); + stfd f17, 0x118(r5); + stfd f18, 0x120(r5); + stfd f19, 0x128(r5); + stfd f20, 0x130(r5); + stfd f21, 0x138(r5); + stfd f22, 0x140(r5); + stfd f23, 0x148(r5); + stfd f24, 0x150(r5); + stfd f25, 0x158(r5); + stfd f26, 0x160(r5); + stfd f27, 0x168(r5); + stfd f28, 0x170(r5); + stfd f29, 0x178(r5); + stfd f30, 0x180(r5); + stfd f31, 0x188(r5); + mffs f0; + stfd f0, 0x190(r5); + lfd f0, 0x90(r5); + mfspr r3, 0x398; + rlwinm. r3, r3, 3, 0x1f, 0x1f; + beq lbl_801a1e64; + psq_st f0, 456(r5), 0, 0; + psq_st f1, 464(r5), 0, 0; + psq_st f2, 472(r5), 0, 0; + psq_st f3, 480(r5), 0, 0; + psq_st f4, 488(r5), 0, 0; + psq_st f5, 496(r5), 0, 0; + psq_st f6, 504(r5), 0, 0; + psq_st f7, 512(r5), 0, 0; + psq_st f8, 520(r5), 0, 0; + psq_st f9, 528(r5), 0, 0; + psq_st f10, 536(r5), 0, 0; + psq_st f11, 544(r5), 0, 0; + psq_st f12, 552(r5), 0, 0; + psq_st f13, 560(r5), 0, 0; + psq_st f14, 568(r5), 0, 0; + psq_st f15, 576(r5), 0, 0; + psq_st f16, 584(r5), 0, 0; + psq_st f17, 592(r5), 0, 0; + psq_st f18, 600(r5), 0, 0; + psq_st f19, 608(r5), 0, 0; + psq_st f20, 616(r5), 0, 0; + psq_st f21, 624(r5), 0, 0; + psq_st f22, 632(r5), 0, 0; + psq_st f23, 640(r5), 0, 0; + psq_st f24, 648(r5), 0, 0; + psq_st f25, 656(r5), 0, 0; + psq_st f26, 664(r5), 0, 0; + psq_st f27, 672(r5), 0, 0; + psq_st f28, 680(r5), 0, 0; + psq_st f29, 688(r5), 0, 0; + psq_st f30, 696(r5), 0, 0; + psq_st f31, 704(r5), 0, 0; +lbl_801a1e64: + blr; + // clang-format on +} + +// Symbol: OSSaveFPUContext +// PAL: 0x801a1e68..0x801a1e70 +asm void OSSaveFPUContext(register OSContext* ctx) { + nofralloc; + addi r5, ctx, 0; + b __OSSaveFPUContext; +} + +// Symbol: OSSetCurrentContext +// PAL: 0x801a1e70..0x801a1ecc +MARK_BINARY_BLOB(OSSetCurrentContext, 0x801a1e70, 0x801a1ecc); +asm void OSSetCurrentContext(OSContext* context) { + // clang-format off + nofralloc; + lis r4, 0x8000; + stw r3, 0xd4(r4); + clrlwi r5, r3, 2; + stw r5, 0xc0(r4); + lwz r5, 0xd8(r4); + cmpw r5, r3; + bne lbl_801a1ea8; + lwz r6, 0x19c(r3); + ori r6, r6, 0x2000; + stw r6, 0x19c(r3); + mfmsr r6; + ori r6, r6, 2; + mtmsr r6; + blr; +lbl_801a1ea8: + lwz r6, 0x19c(r3); + rlwinm r6, r6, 0, 0x13, 0x11; + stw r6, 0x19c(r3); + mfmsr r6; + rlwinm r6, r6, 0, 0x13, 0x11; + ori r6, r6, 2; + mtmsr r6; + isync; + blr; + // clang-format on +} + +// Symbol: OSGetCurrentContext +// PAL: 0x801a1ecc..0x801a1ed8 +OSContext* OSGetCurrentContext(void) { return (OSContext*)__OSCurrentContext; } + +// Symbol: OSSaveContext +// Function signature is unknown. +// PAL: 0x801a1ed8..0x801a1f58 +MARK_BINARY_BLOB(OSSaveContext, 0x801a1ed8, 0x801a1f58); +asm UNKNOWN_FUNCTION(OSSaveContext) { + // clang-format off + nofralloc; + stmw r13, 0x34(r3); + mfspr r0, 0x391; + stw r0, 0x1a8(r3); + mfspr r0, 0x392; + stw r0, 0x1ac(r3); + mfspr r0, 0x393; + stw r0, 0x1b0(r3); + mfspr r0, 0x394; + stw r0, 0x1b4(r3); + mfspr r0, 0x395; + stw r0, 0x1b8(r3); + mfspr r0, 0x396; + stw r0, 0x1bc(r3); + mfspr r0, 0x397; + stw r0, 0x1c0(r3); + mfcr r0; + stw r0, 0x80(r3); + mflr r0; + stw r0, 0x84(r3); + stw r0, 0x198(r3); + mfmsr r0; + stw r0, 0x19c(r3); + mfctr r0; + stw r0, 0x88(r3); + mfxer r0; + stw r0, 0x8c(r3); + stw r1, 4(r3); + stw r2, 8(r3); + li r0, 1; + stw r0, 0xc(r3); + li r3, 0; + blr; + // clang-format on +} + +// Symbol: OSLoadContext +// Function signature is unknown. +// PAL: 0x801a1f58..0x801a2030 +MARK_BINARY_BLOB(OSLoadContext, 0x801a1f58, 0x801a2030); +asm void OSLoadContext(OSContext* context) { + // clang-format off + nofralloc; + lis r4, 0x801a; + lwz r6, 0x198(r3); + addi r5, r4, 0x65ac; + cmplw r6, r5; + ble lbl_801a1f80; + lis r4, 0x801a; + addi r0, r4, 0x65b8; + cmplw r6, r0; + bge lbl_801a1f80; + stw r5, 0x198(r3); +lbl_801a1f80: + lwz r0, 0(r3); + lwz r1, 4(r3); + lwz r2, 8(r3); + lhz r4, 0x1a2(r3); + rlwinm. r5, r4, 0, 0x1e, 0x1e; + beq lbl_801a1fa8; + rlwinm r4, r4, 0, 0x1f, 0x1d; + sth r4, 0x1a2(r3); + lmw r5, 0x14(r3); + b lbl_801a1fac; +lbl_801a1fa8: + lmw r13, 0x34(r3); +lbl_801a1fac: + lwz r4, 0x1a8(r3); + mtspr 0x391, r4; + lwz r4, 0x1ac(r3); + mtspr 0x392, r4; + lwz r4, 0x1b0(r3); + mtspr 0x393, r4; + lwz r4, 0x1b4(r3); + mtspr 0x394, r4; + lwz r4, 0x1b8(r3); + mtspr 0x395, r4; + lwz r4, 0x1bc(r3); + mtspr 0x396, r4; + lwz r4, 0x1c0(r3); + mtspr 0x397, r4; + lwz r4, 0x80(r3); + mtcrf 0xff, r4; + lwz r4, 0x84(r3); + mtlr r4; + lwz r4, 0x88(r3); + mtctr r4; + lwz r4, 0x8c(r3); + mtxer r4; + mfmsr r4; + rlwinm r4, r4, 0, 0x11, 0xf; + rlwinm r4, r4, 0, 0x1f, 0x1d; + mtmsr r4; + lwz r4, 0x198(r3); + mtspr 0x1a, r4; + lwz r4, 0x19c(r3); + mtspr 0x1b, r4; + lwz r4, 0x10(r3); + lwz r3, 0xc(r3); + rfi; + // clang-format on +} + +// Symbol: OSGetStackPointer +// PAL: 0x801a2030..0x801a2038 +asm u32 OSGetStackPointer(void) { + nofralloc; + mr r3, r1; + blr; +} + +// Symbol: OSSwitchFiber +// Function signature is unknown. +// PAL: 0x801a2038..0x801a2068 +MARK_BINARY_BLOB(OSSwitchFiber, 0x801a2038, 0x801a2068); +asm UNKNOWN_FUNCTION(OSSwitchFiber) { + // clang-format off + nofralloc; + mflr r0; + mr r5, r1; + stwu r5, -8(r4); + mr r1, r4; + stw r0, 4(r5); + mtlr r3; + blrl; + lwz r5, 0(r1); + lwz r0, 4(r5); + mtlr r0; + mr r1, r5; + blr; + // clang-format on +} + +// Symbol: OSSwitchFiberEx +// Function signature is unknown. +// PAL: 0x801a2068..0x801a2098 +MARK_BINARY_BLOB(OSSwitchFiberEx, 0x801a2068, 0x801a2098); +asm UNKNOWN_FUNCTION(OSSwitchFiberEx) { + // clang-format off + nofralloc; + mflr r0; + mr r9, r1; + stwu r9, -8(r8); + mr r1, r8; + stw r0, 4(r9); + mtlr r7; + blrl; + lwz r5, 0(r1); + lwz r0, 4(r5); + mtlr r0; + mr r1, r5; + blr; + // clang-format on +} + +// Symbol: OSClearContext +// PAL: 0x801a2098..0x801a20bc +void OSClearContext(OSContext* context) { + context->mode = 0; + context->state = 0; + if (context == __OSFPUContext) + __OSFPUContext = NULL; +} + +// Symbol: OSInitContext +// PAL: 0x801a20bc..0x801a2178 +MARK_BINARY_BLOB(OSInitContext, 0x801a20bc, 0x801a2178); +asm void OSInitContext(OSContext* context, u32 pc, u32 sp) { + // clang-format off + nofralloc; + stw r4, 0x198(r3); + stw r5, 4(r3); + li r11, 0; + ori r11, r11, 0x9032; + stw r11, 0x19c(r3); + li r0, 0; + stw r0, 0x80(r3); + stw r0, 0x8c(r3); + stw r2, 8(r3); + stw r13, 0x34(r3); + stw r0, 0xc(r3); + stw r0, 0x10(r3); + stw r0, 0x14(r3); + stw r0, 0x18(r3); + stw r0, 0x1c(r3); + stw r0, 0x20(r3); + stw r0, 0x24(r3); + stw r0, 0x28(r3); + stw r0, 0x2c(r3); + stw r0, 0x30(r3); + stw r0, 0x38(r3); + stw r0, 0x3c(r3); + stw r0, 0x40(r3); + stw r0, 0x44(r3); + stw r0, 0x48(r3); + stw r0, 0x4c(r3); + stw r0, 0x50(r3); + stw r0, 0x54(r3); + stw r0, 0x58(r3); + stw r0, 0x5c(r3); + stw r0, 0x60(r3); + stw r0, 0x64(r3); + stw r0, 0x68(r3); + stw r0, 0x6c(r3); + stw r0, 0x70(r3); + stw r0, 0x74(r3); + stw r0, 0x78(r3); + stw r0, 0x7c(r3); + stw r0, 0x1a4(r3); + stw r0, 0x1a8(r3); + stw r0, 0x1ac(r3); + stw r0, 0x1b0(r3); + stw r0, 0x1b4(r3); + stw r0, 0x1b8(r3); + stw r0, 0x1bc(r3); + stw r0, 0x1c0(r3); + b OSClearContext; + // clang-format on +} + +// Symbol: OSDumpContext +// PAL: 0x801a2178..0x801a23d8 +void OSDumpContext(OSContext* context) { + OSReport( + "------------------------- Context 0x%08x -------------------------\n", + context); + for (u32 i = 0; i < 16; ++i) + OSReport("r%-2d = 0x%08x (%14d) r%-2d = 0x%08x (%14d)\n", i, + context->gpr[i], context->gpr[i], i + 16, context->gpr[i + 16], + context->gpr[i + 16]); + OSReport("LR = 0x%08x CR = 0x%08x\n", context->lr, + context->cr); + OSReport("SRR0 = 0x%08x SRR1 = 0x%08x\n", context->srr0, + context->srr1); + OSReport("\nGQRs----------\n"); + for (u32 i = 0; i < 4; ++i) + OSReport("gqr%d = 0x%08x \t gqr%d = 0x%08x\n", i, context->gqr[i], i + 4, + context->gqr[i + 4]); + u32 i; + if (context->state & 0x1u) { + int interrupts = OSDisableInterrupts(); + OSContext* oldCtx = OSGetCurrentContext(); + OSContext newCtx; + OSClearContext(&newCtx); + OSSetCurrentContext(&newCtx); + OSReport("\n\nFPRs----------\n"); + for (i = 0; i < 32; i += 2) + OSReport("fr%d \t= %d \t fr%d \t= %d\n", i, (u32)context->fpr[i], i + 1, + (u32)context->fpr[i + 1]); + OSReport("\n\nPSFs----------\n"); + for (i = 0; i < 32; i += 2) + OSReport("ps%d \t= 0x%x \t ps%d \t= 0x%x\n", i, (u32)context->psf[i], + i + 1, (u32)context->psf[i + 1]); + OSClearContext(&newCtx); + OSSetCurrentContext(oldCtx); + OSRestoreInterrupts(interrupts); + } + OSReport("\nAddress: Back Chain LR Save\n"); + u32* p; + for (i = 0, p = (u32*)context->gpr[1]; p && (u32)p != 0xffffffff && i++ < 16; + p = (u32*)*p) { + OSReport("0x%08x: 0x%08x 0x%08x\n", p, p[0], p[1]); + } +} + +// Symbol: OSSwitchFPUContext +// Function signature is unknown. +// PAL: 0x801a23d8..0x801a245c +MARK_BINARY_BLOB(OSSwitchFPUContext, 0x801a23d8, 0x801a245c); +asm UNKNOWN_FUNCTION(OSSwitchFPUContext) { + // clang-format off + nofralloc; + mfmsr r5; + ori r5, r5, 0x2000; + mtmsr r5; + isync; + lwz r5, 0x19c(r4); + ori r5, r5, 0x2000; + mtspr 0x1b, r5; + lis r3, 0x8000; + lwz r5, 0xd8(r3); + stw r4, 0xd8(r3); + cmpw r5, r4; + beq lbl_801a2418; + cmpwi r5, 0; + beq lbl_801a2414; + bl __OSSaveFPUContext; +lbl_801a2414: + bl __OSLoadFPUContext; +lbl_801a2418: + lwz r3, 0x80(r4); + mtcrf 0xff, r3; + lwz r3, 0x84(r4); + mtlr r3; + lwz r3, 0x198(r4); + mtspr 0x1a, r3; + lwz r3, 0x88(r4); + mtctr r3; + lwz r3, 0x8c(r4); + mtxer r3; + lhz r3, 0x1a2(r4); + rlwinm r3, r3, 0, 0x1f, 0x1d; + sth r3, 0x1a2(r4); + lwz r5, 0x14(r4); + lwz r3, 0xc(r4); + lwz r4, 0x10(r4); + rfi; + // clang-format on +} + +// Symbol: __OSContextInit +// Function signature is unknown. +// PAL: 0x801a245c..0x801a24a4 +MARK_BINARY_BLOB(__OSContextInit, 0x801a245c, 0x801a24a4); +asm UNKNOWN_FUNCTION(__OSContextInit) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + lis r4, 0x801a; + li r3, 7; + stw r0, 0x14(r1); + addi r4, r4, 0x23d8; + bl __OSSetExceptionHandler; + li r0, 0; + lis r4, 0x8000; + lis r3, 0x8029; + stw r0, 0xd8(r4); + addi r3, r3, -2856; + crclr 6; + bl DBPrintf; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSFillFPUContext +// Function signature is unknown. +// PAL: 0x801a24a4..0x801a25d0 +MARK_BINARY_BLOB(OSFillFPUContext, 0x801a24a4, 0x801a25d0); +asm UNKNOWN_FUNCTION(OSFillFPUContext) { + // clang-format off + nofralloc; + mfmsr r5; + ori r5, r5, 0x2000; + mtmsr r5; + isync; + stfd f0, 0x90(r3); + stfd f1, 0x98(r3); + stfd f2, 0xa0(r3); + stfd f3, 0xa8(r3); + stfd f4, 0xb0(r3); + stfd f5, 0xb8(r3); + stfd f6, 0xc0(r3); + stfd f7, 0xc8(r3); + stfd f8, 0xd0(r3); + stfd f9, 0xd8(r3); + stfd f10, 0xe0(r3); + stfd f11, 0xe8(r3); + stfd f12, 0xf0(r3); + stfd f13, 0xf8(r3); + stfd f14, 0x100(r3); + stfd f15, 0x108(r3); + stfd f16, 0x110(r3); + stfd f17, 0x118(r3); + stfd f18, 0x120(r3); + stfd f19, 0x128(r3); + stfd f20, 0x130(r3); + stfd f21, 0x138(r3); + stfd f22, 0x140(r3); + stfd f23, 0x148(r3); + stfd f24, 0x150(r3); + stfd f25, 0x158(r3); + stfd f26, 0x160(r3); + stfd f27, 0x168(r3); + stfd f28, 0x170(r3); + stfd f29, 0x178(r3); + stfd f30, 0x180(r3); + stfd f31, 0x188(r3); + mffs f0; + stfd f0, 0x190(r3); + lfd f0, 0x90(r3); + mfspr r5, 0x398; + rlwinm. r5, r5, 3, 0x1f, 0x1f; + beq lbl_801a25cc; + psq_st f0, 456(r3), 0, 0; + psq_st f1, 464(r3), 0, 0; + psq_st f2, 472(r3), 0, 0; + psq_st f3, 480(r3), 0, 0; + psq_st f4, 488(r3), 0, 0; + psq_st f5, 496(r3), 0, 0; + psq_st f6, 504(r3), 0, 0; + psq_st f7, 512(r3), 0, 0; + psq_st f8, 520(r3), 0, 0; + psq_st f9, 528(r3), 0, 0; + psq_st f10, 536(r3), 0, 0; + psq_st f11, 544(r3), 0, 0; + psq_st f12, 552(r3), 0, 0; + psq_st f13, 560(r3), 0, 0; + psq_st f14, 568(r3), 0, 0; + psq_st f15, 576(r3), 0, 0; + psq_st f16, 584(r3), 0, 0; + psq_st f17, 592(r3), 0, 0; + psq_st f18, 600(r3), 0, 0; + psq_st f19, 608(r3), 0, 0; + psq_st f20, 616(r3), 0, 0; + psq_st f21, 624(r3), 0, 0; + psq_st f22, 632(r3), 0, 0; + psq_st f23, 640(r3), 0, 0; + psq_st f24, 648(r3), 0, 0; + psq_st f25, 656(r3), 0, 0; + psq_st f26, 664(r3), 0, 0; + psq_st f27, 672(r3), 0, 0; + psq_st f28, 680(r3), 0, 0; + psq_st f29, 688(r3), 0, 0; + psq_st f30, 696(r3), 0, 0; + psq_st f31, 704(r3), 0, 0; +lbl_801a25cc: + blr; + // clang-format on +} diff --git a/source/rvl/os/osContext.h b/source/rvl/os/osContext.h new file mode 100644 index 000000000..4d2db3a20 --- /dev/null +++ b/source/rvl/os/osContext.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include "osThread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OSContext { + // General-purpose registers + u32 gpr[32]; + + u32 cr; + u32 lr; + u32 ctr; + u32 xer; + + // Floating-point registers + f64 fpr[32]; + + u32 fpscr_pad; + u32 fpscr; + + // Exception handling registers + u32 srr0; + u32 srr1; + + // Context mode + u16 mode; // since UIMM is 16 bits in PPC + u16 state; // OR-ed OS_CONTEXT_STATE_* + + // Place Gekko regs at the end so we have minimal changes to + // existing code + u32 gqr[8]; + u32 psf_pad; + f64 psf[32]; + +} OSContext; + +// PAL: 0x801a1c1c..0x801a1d40 +UNKNOWN_FUNCTION(__OSLoadFPUContext); +// PAL: 0x801a1d40..0x801a1e68 +UNKNOWN_FUNCTION(__OSSaveFPUContext); +// PAL: 0x801a1e68..0x801a1e70 +void OSLoadFPUContext(OSContext*); +// PAL: 0x801a1e68..0x801a1e70 +void OSSaveFPUContext(OSContext* ctx); +// PAL: 0x801a1e70..0x801a1ecc +void OSSetCurrentContext(OSContext* context); +// PAL: 0x801a1ecc..0x801a1ed8 +OSContext* OSGetCurrentContext(void); +// PAL: 0x801a1ed8..0x801a1f58 +UNKNOWN_FUNCTION(OSSaveContext); +// PAL: 0x801a1f58..0x801a2030 +void OSLoadContext(OSContext* context); +// PAL: 0x801a2030..0x801a2038 +u32 OSGetStackPointer(void); +// PAL: 0x801a2038..0x801a2068 +UNKNOWN_FUNCTION(OSSwitchFiber); +// PAL: 0x801a2068..0x801a2098 +UNKNOWN_FUNCTION(OSSwitchFiberEx); +// PAL: 0x801a2098..0x801a20bc +void OSClearContext(OSContext* context); +// PAL: 0x801a20bc..0x801a2178 +void OSInitContext(OSContext* context, u32 pc, u32 sp); +// PAL: 0x801a2178..0x801a23d8 +void OSDumpContext(OSContext*); +// PAL: 0x801a23d8..0x801a245c +UNKNOWN_FUNCTION(OSSwitchFPUContext); +// PAL: 0x801a245c..0x801a24a4 +UNKNOWN_FUNCTION(__OSContextInit); +// PAL: 0x801a24a4..0x801a25d0 +UNKNOWN_FUNCTION(OSFillFPUContext); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osError.c b/source/rvl/os/osError.c new file mode 100644 index 000000000..3ca0abe64 --- /dev/null +++ b/source/rvl/os/osError.c @@ -0,0 +1,636 @@ +#include "osError.h" + +#include +#include + +#include "os.h" +#include "osContext.h" +#include "osInterrupt.h" +#include "osThread.h" + +// Symbol: OSReport +// PAL: 0x801a25d0..0x801a265c +MARK_BINARY_BLOB(OSReport, 0x801a25d0, 0x801a265c); +asm void OSReport(const char* msg, ...) { + // clang-format off + nofralloc; + stwu r1, -0x80(r1); + mflr r0; + stw r0, 0x84(r1); + stw r31, 0x7c(r1); + bne cr1, lbl_801a2604; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_801a2604: + addi r11, r1, 0x88; + addi r0, r1, 8; + lis r12, 0x100; + stw r3, 8(r1); + addi r31, r1, 0x68; + stw r4, 0xc(r1); + mr r4, r31; + stw r5, 0x10(r1); + stw r6, 0x14(r1); + stw r7, 0x18(r1); + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + stw r12, 0x68(r1); + stw r11, 0x6c(r1); + stw r0, 0x70(r1); + bl vprintf; + lwz r0, 0x84(r1); + lwz r31, 0x7c(r1); + mtlr r0; + addi r1, r1, 0x80; + blr; + // clang-format on +} + +// Symbol: OSVReport +// PAL: 0x801a265c..0x801a2660 +void OSVReport(const char* msg, va_list arg) { vprintf(msg, arg); } + +// Symbol: OSPanic +// PAL: 0x801a2660..0x801a278c +MARK_BINARY_BLOB(OSPanic, 0x801a2660, 0x801a278c); +asm void OSPanic(const char* file, int line, const char* msg, ...) { + // clang-format off + nofralloc; + stwu r1, -0x90(r1); + mflr r0; + stw r0, 0x94(r1); + stw r31, 0x8c(r1); + stw r30, 0x88(r1); + mr r30, r4; + stw r29, 0x84(r1); + mr r29, r5; + stw r28, 0x80(r1); + mr r28, r3; + bne cr1, lbl_801a26ac; + stfd f1, 0x28(r1); + stfd f2, 0x30(r1); + stfd f3, 0x38(r1); + stfd f4, 0x40(r1); + stfd f5, 0x48(r1); + stfd f6, 0x50(r1); + stfd f7, 0x58(r1); + stfd f8, 0x60(r1); +lbl_801a26ac: + lis r31, 0x8029; + stw r3, 8(r1); + addi r31, r31, -2816; + stw r4, 0xc(r1); + stw r5, 0x10(r1); + stw r6, 0x14(r1); + stw r7, 0x18(r1); + stw r8, 0x1c(r1); + stw r9, 0x20(r1); + stw r10, 0x24(r1); + bl OSDisableInterrupts; + addi r5, r1, 0x98; + addi r0, r1, 8; + lis r3, 0x300; + stw r5, 0x6c(r1); + addi r4, r1, 0x68; + stw r3, 0x68(r1); + mr r3, r29; + stw r0, 0x70(r1); + bl vprintf; + mr r4, r28; + mr r5, r30; + addi r3, r31, 0; + crclr 6; + bl OSReport; + addi r3, r31, 0x18; + crclr 6; + bl OSReport; + li r30, 0; + bl OSGetStackPointer; + mr r29, r3; + b lbl_801a2748; +lbl_801a272c: + lwz r5, 0(r29); + mr r4, r29; + lwz r6, 4(r29); + addi r3, r31, 0x40; + crclr 6; + bl OSReport; + lwz r29, 0(r29); +lbl_801a2748: + cmpwi r29, 0; + beq lbl_801a2768; + addis r0, r29, 1; + cmplwi r0, 0xffff; + beq lbl_801a2768; + cmplwi r30, 0x10; + addi r30, r30, 1; + blt lbl_801a272c; +lbl_801a2768: + bl PPCHalt; + lwz r0, 0x94(r1); + lwz r31, 0x8c(r1); + lwz r30, 0x88(r1); + lwz r29, 0x84(r1); + lwz r28, 0x80(r1); + mtlr r0; + addi r1, r1, 0x90; + blr; + // clang-format on +} + +// Symbol: OSSetErrorHandler +// PAL: 0x801a278c..0x801a2a14 +MARK_BINARY_BLOB(OSSetErrorHandler, 0x801a278c, 0x801a2a14); +asm OSErrorHandler OSSetErrorHandler(u16 error, OSErrorHandler handler) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r3; + stw r28, 0x10(r1); + mr r28, r4; + bl OSDisableInterrupts; + lis r4, 0x8034; + rlwinm r0, r29, 2, 0xe, 0x1d; + addi r4, r4, 0x70f0; + cmplwi r29, 0x10; + lwzx r30, r4, r0; + mr r29, r3; + stwx r28, r4, r0; + bne lbl_801a29e8; + bl PPCMfmsr; + mr r31, r3; + ori r3, r3, 0x2000; + bl PPCMtmsr; + bl PPCMffpscr; + cmpwi r28, 0; + beq lbl_801a2984; + lis r5, 0x8000; + lis r4, 0x6006; + lwz r9, 0xdc(r5); + addi r5, r4, -1793; + li r8, -1; + li r7, 4; + li r0, 2; + b lbl_801a2968; +lbl_801a2810: + lwz r4, 0x19c(r9); + ori r4, r4, 0x900; + stw r4, 0x19c(r9); + lhz r6, 0x1a2(r9); + clrlwi. r4, r6, 0x1f; + bne lbl_801a294c; + ori r4, r6, 1; + addi r6, r9, 0x90; + sth r4, 0x1a2(r9); + addi r4, r9, 0x1c8; + mtctr r0; +lbl_801a283c: + stw r8, 4(r6); + stw r8, 0(r6); + stw r8, 4(r4); + stw r8, 0(r4); + stw r8, 0xc(r6); + stw r8, 8(r6); + stw r8, 0xc(r4); + stw r8, 8(r4); + stw r8, 0x14(r6); + stw r8, 0x10(r6); + stw r8, 0x14(r4); + stw r8, 0x10(r4); + stw r8, 0x1c(r6); + stw r8, 0x18(r6); + stw r8, 0x1c(r4); + stw r8, 0x18(r4); + stw r8, 0x24(r6); + stw r8, 0x20(r6); + stw r8, 0x24(r4); + stw r8, 0x20(r4); + stw r8, 0x2c(r6); + stw r8, 0x28(r6); + stw r8, 0x2c(r4); + stw r8, 0x28(r4); + stw r8, 0x34(r6); + stw r8, 0x30(r6); + stw r8, 0x34(r4); + stw r8, 0x30(r4); + stw r8, 0x3c(r6); + stw r8, 0x38(r6); + stw r8, 0x3c(r4); + stw r8, 0x38(r4); + stw r8, 0x44(r6); + stw r8, 0x40(r6); + stw r8, 0x44(r4); + stw r8, 0x40(r4); + stw r8, 0x4c(r6); + stw r8, 0x48(r6); + stw r8, 0x4c(r4); + stw r8, 0x48(r4); + stw r8, 0x54(r6); + stw r8, 0x50(r6); + stw r8, 0x54(r4); + stw r8, 0x50(r4); + stw r8, 0x5c(r6); + stw r8, 0x58(r6); + stw r8, 0x5c(r4); + stw r8, 0x58(r4); + stw r8, 0x64(r6); + stw r8, 0x60(r6); + stw r8, 0x64(r4); + stw r8, 0x60(r4); + stw r8, 0x6c(r6); + stw r8, 0x68(r6); + stw r8, 0x6c(r4); + stw r8, 0x68(r4); + stw r8, 0x74(r6); + stw r8, 0x70(r6); + stw r8, 0x74(r4); + stw r8, 0x70(r4); + stw r8, 0x7c(r6); + stw r8, 0x78(r6); + addi r6, r6, 0x80; + stw r8, 0x7c(r4); + stw r8, 0x78(r4); + addi r4, r4, 0x80; + bdnz lbl_801a283c; + stw r7, 0x194(r9); +lbl_801a294c: + lwz r4, -0x7158(r13); + lwz r6, 0x194(r9); + rlwinm r4, r4, 0, 0x18, 0x1c; + or r4, r6, r4; + and r4, r4, r5; + stw r4, 0x194(r9); + lwz r9, 0x2fc(r9); +lbl_801a2968: + cmpwi r9, 0; + bne lbl_801a2810; + lwz r0, -0x7158(r13); + ori r31, r31, 0x900; + rlwinm r0, r0, 0, 0x18, 0x1c; + or r4, r3, r0; + b lbl_801a29d0; +lbl_801a2984: + lis r5, 0x8000; + lis r4, 0x6006; + lwz r6, 0xdc(r5); + addi r4, r4, -1793; + li r5, -2305; + b lbl_801a29bc; +lbl_801a299c: + lwz r0, 0x19c(r6); + and r0, r0, r5; + stw r0, 0x19c(r6); + lwz r0, 0x194(r6); + rlwinm r0, r0, 0, 0x1d, 0x17; + and r0, r0, r4; + stw r0, 0x194(r6); + lwz r6, 0x2fc(r6); +lbl_801a29bc: + cmpwi r6, 0; + bne lbl_801a299c; + li r0, -2305; + rlwinm r4, r3, 0, 0x1d, 0x17; + and r31, r31, r0; +lbl_801a29d0: + lis r3, 0x6006; + addi r0, r3, -1793; + and r3, r4, r0; + bl PPCMtfpscr; + mr r3, r31; + bl PPCMtmsr; +lbl_801a29e8: + mr r3, r29; + bl OSRestoreInterrupts; + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSUnhandledException +// PAL: 0x801a2a14..0x801a2e84 +MARK_BINARY_BLOB(__OSUnhandledException, 0x801a2a14, 0x801a2e84); +asm void __OSUnhandledException(u8, OSContext*, u32, u32) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_23; + lis r31, 0x8029; + mr r25, r3; + mr r26, r4; + mr r27, r5; + mr r28, r6; + addi r31, r31, -2816; + bl OSGetTime; + lwz r5, 0x19c(r26); + mr r29, r4; + mr r30, r3; + rlwinm. r0, r5, 0, 0x1e, 0x1e; + bne lbl_801a2a6c; + mr r4, r25; + addi r3, r31, 0x5c; + crclr 6; + bl OSReport; + b lbl_801a2bc8; +lbl_801a2a6c: + cmplwi r25, 6; + bne lbl_801a2b5c; + rlwinm. r0, r5, 0, 0xb, 0xb; + beq lbl_801a2b5c; + lis r3, 0x8034; + addi r3, r3, 0x70f0; + lwz r0, 0x40(r3); + cmpwi r0, 0; + beq lbl_801a2b5c; + li r25, 0x10; + bl PPCMfmsr; + mr r23, r3; + ori r3, r3, 0x2000; + bl PPCMtmsr; + lis r3, 0x8000; + lwz r3, 0xd8(r3); + cmpwi r3, 0; + beq lbl_801a2ab8; + bl OSSaveFPUContext; +lbl_801a2ab8: + bl PPCMffpscr; + lis r24, 0x6006; + addi r0, r24, -1793; + and r3, r3, r0; + bl PPCMtfpscr; + mr r3, r23; + bl PPCMtmsr; + lis r23, 0x8000; + lwz r0, 0xd8(r23); + cmplw r0, r26; + bne lbl_801a2b40; + bl OSDisableScheduler; + mr r4, r26; + mr r5, r27; + mr r6, r28; + li r3, 0x10; + crclr 6; + lis r7, 0x8034; + addi r7, r7, 0x70f0; + lwz r12, 0x40(r7); + mtctr r12; + bctrl; + lwz r4, 0x19c(r26); + li r3, 0; + addi r0, r24, -1793; + rlwinm r4, r4, 0, 0x13, 0x11; + stw r4, 0x19c(r26); + stw r3, 0xd8(r23); + lwz r3, 0x194(r26); + and r0, r3, r0; + stw r0, 0x194(r26); + bl OSEnableScheduler; + bl __OSReschedule; + b lbl_801a2b54; +lbl_801a2b40: + lwz r3, 0x19c(r26); + li r0, 0; + rlwinm r3, r3, 0, 0x13, 0x11; + stw r3, 0x19c(r26); + stw r0, 0xd8(r23); +lbl_801a2b54: + mr r3, r26; + bl OSLoadContext; +lbl_801a2b5c: + lis r24, 0x8034; + rlwinm r23, r25, 2, 0x16, 0x1d; + addi r24, r24, 0x70f0; + lwzx r0, r24, r23; + cmpwi r0, 0; + beq lbl_801a2ba8; + bl OSDisableScheduler; + mr r3, r25; + mr r4, r26; + mr r5, r27; + mr r6, r28; + crclr 6; + lwzx r12, r24, r23; + mtctr r12; + bctrl; + bl OSEnableScheduler; + bl __OSReschedule; + mr r3, r26; + bl OSLoadContext; +lbl_801a2ba8: + cmplwi r25, 8; + bne lbl_801a2bb8; + mr r3, r26; + bl OSLoadContext; +lbl_801a2bb8: + mr r4, r25; + addi r3, r31, 0x7c; + crclr 6; + bl OSReport; +lbl_801a2bc8: + addi r3, r13, -29012; + crclr 6; + bl OSReport; + mr r3, r26; + bl OSDumpContext; + mr r4, r27; + mr r5, r28; + addi r3, r31, 0x94; + crclr 6; + bl OSReport; + mr r6, r29; + mr r5, r30; + addi r3, r31, 0xc8; + crclr 6; + bl OSReport; + cmplwi r25, 0xf; + bgt lbl_801a2ccc; + lis r3, 0x8029; + slwi r0, r25, 2; + addi r3, r3, -2084; + lwzx r3, r3, r0; + mtctr r3; + bctr; + lwz r4, 0x198(r26); + mr r5, r28; + addi r3, r31, 0xd8; + crclr 6; + bl OSReport; + b lbl_801a2ccc; + lwz r4, 0x198(r26); + addi r3, r31, 0x138; + crclr 6; + bl OSReport; + b lbl_801a2ccc; + lwz r4, 0x198(r26); + mr r5, r28; + addi r3, r31, 0x184; + crclr 6; + bl OSReport; + b lbl_801a2ccc; + lwz r4, 0x198(r26); + mr r5, r28; + addi r3, r31, 0x1e8; + crclr 6; + bl OSReport; + b lbl_801a2ccc; + addi r3, r13, -29012; + crclr 6; + bl OSReport; + lis r25, 0xcc00; + addi r3, r31, 0x248; + lhz r4, 0x5030(r25); + lhz r5, 0x5032(r25); + crclr 6; + bl OSReport; + lhz r4, 0x5020(r25); + addi r3, r31, 0x268; + lhz r5, 0x5022(r25); + crclr 6; + bl OSReport; + lis r4, 0xcd00; + addi r3, r31, 0x288; + lwz r4, 0x6014(r4); + crclr 6; + bl OSReport; +lbl_801a2ccc: + lha r4, -0x6314(r13); + addi r3, r31, 0x2a4; + lwz r5, -0x6318(r13); + lwz r7, -0x6310(r13); + lwz r8, -0x630c(r13); + crclr 6; + bl OSReport; + bl PPCHalt; + addi r11, r1, 0x30; + bl _restgpr_23; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_25; + mr r25, r4; + mr r29, r5; + mr r30, r3; + li r4, 0; + li r5, 0x2000; + bl memset; + cmpwi r25, 0; + bne lbl_801a2d44; + li r0, 0; + stw r0, 8(r30); + b lbl_801a2e68; +lbl_801a2d44: + slwi r0, r25, 2; + mr r31, r25; + addi r26, r30, 0x2000; + add r28, r29, r0; + b lbl_801a2d80; +lbl_801a2d58: + lwz r27, 0(r28); + mr r3, r27; + bl strlen; + addi r0, r3, 1; + mr r4, r27; + subf r26, r0, r26; + mr r3, r26; + bl strcpy; + subf r0, r30, r26; + stw r0, 0(r28); +lbl_801a2d80: + addic. r25, r25, -1; + addi r28, r28, -4; + bge lbl_801a2d58; + addic. r3, r31, 1; + subf r0, r30, r26; + rlwinm r4, r0, 0, 0, 0x1d; + li r7, 0; + add r6, r30, r4; + slwi r0, r3, 2; + subf r6, r0, r6; + beq lbl_801a2e58; + cmplwi r3, 8; + addi r3, r31, -7; + ble lbl_801a2e24; + addi r0, r3, 7; + mr r4, r29; + srwi r0, r0, 3; + mr r5, r6; + mtctr r0; + cmplwi r3, 0; + ble lbl_801a2e24; +lbl_801a2dd4: + lwz r0, 0(r4); + addi r7, r7, 8; + stw r0, 0(r5); + lwz r0, 4(r4); + stw r0, 4(r5); + lwz r0, 8(r4); + stw r0, 8(r5); + lwz r0, 0xc(r4); + stw r0, 0xc(r5); + lwz r0, 0x10(r4); + stw r0, 0x10(r5); + lwz r0, 0x14(r4); + stw r0, 0x14(r5); + lwz r0, 0x18(r4); + stw r0, 0x18(r5); + lwz r0, 0x1c(r4); + addi r4, r4, 0x20; + stw r0, 0x1c(r5); + addi r5, r5, 0x20; + bdnz lbl_801a2dd4; +lbl_801a2e24: + addi r3, r31, 1; + slwi r5, r7, 2; + subf r0, r7, r3; + add r4, r29, r5; + add r5, r6, r5; + mtctr r0; + cmplw r7, r3; + bge lbl_801a2e58; +lbl_801a2e44: + lwz r0, 0(r4); + addi r4, r4, 4; + stw r0, 0(r5); + addi r5, r5, 4; + bdnz lbl_801a2e44; +lbl_801a2e58: + addi r0, r6, -4; + stw r31, -4(r6); + subf r0, r30, r0; + stw r0, 8(r30); +lbl_801a2e68: + addi r11, r1, 0x30; + li r3, 1; + bl _restgpr_25; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} diff --git a/source/rvl/os/osError.h b/source/rvl/os/osError.h index ab6306782..df4a4aa3a 100644 --- a/source/rvl/os/osError.h +++ b/source/rvl/os/osError.h @@ -1,12 +1,28 @@ #pragma once +#include #include +#include + +#include "osContext.h" + #ifdef __cplusplus extern "C" { #endif +typedef void (*OSErrorHandler)(s16 error, OSContext* context, ...); + +// PAL: 0x801a25d0..0x801a265c void OSReport(const char* msg, ...); +// PAL: 0x801a265c..0x801a2660 +void OSVReport(const char* msg, va_list arg); +// PAL: 0x801a2660..0x801a278c +void OSPanic(const char* file, int line, const char* msg, ...); +// PAL: 0x801a278c..0x801a2a14 +OSErrorHandler OSSetErrorHandler(u16 error, OSErrorHandler handler); +// PAL: 0x801a2a14..0x801a2e84 +void __OSUnhandledException(u8, OSContext*, u32, u32); #ifdef __cplusplus } diff --git a/source/rvl/os/osException.h b/source/rvl/os/osException.h index 9424821d3..76a01dcaa 100644 --- a/source/rvl/os/osException.h +++ b/source/rvl/os/osException.h @@ -2,7 +2,7 @@ #include -#include "os.h" +#include "osThread.h" #ifdef __cplusplus extern "C" { @@ -10,6 +10,8 @@ extern "C" { typedef void (*__OSExceptionHandler)(u8 exception, OSContext* context); +__OSExceptionHandler __OSGetExceptionHandler(u8 exception); + // PAL: 0x801a0388 __OSExceptionHandler __OSSetExceptionHandler(u8 exception, __OSExceptionHandler handler); diff --git a/source/rvl/os/osFatal.c b/source/rvl/os/osFatal.c new file mode 100644 index 000000000..9ba0b7f02 --- /dev/null +++ b/source/rvl/os/osFatal.c @@ -0,0 +1,923 @@ +#include "osFatal.h" + +#include +#include + +#include "os.h" +#include "osArena.h" +#include "osAudio.h" +#include "osCache.h" +#include "osContext.h" +#include "osError.h" +#include "osException.h" +#include "osInterrupt.h" +#include "osReset.h" + +// Extern function references. +// PAL: 0x80168380 +extern UNKNOWN_FUNCTION(EXISync); +// PAL: 0x801685fc +extern UNKNOWN_FUNCTION(EXISetExiCallback); +// PAL: 0x80168b00 +extern UNKNOWN_FUNCTION(EXIDeselect); +// PAL: 0x80169164 +extern UNKNOWN_FUNCTION(EXILock); +// PAL: 0x80169260 +extern UNKNOWN_FUNCTION(EXIUnlock); +// PAL: 0x8016e848 +extern UNKNOWN_FUNCTION(GXAbortFrame); +// PAL: 0x801a5d34 +extern UNKNOWN_FUNCTION(OSLoadFont); +// PAL: 0x801a6114 +extern UNKNOWN_FUNCTION(OSGetFontTexel); +// PAL: 0x801b90f4 +extern UNKNOWN_FUNCTION(VISetPreRetraceCallback); +// PAL: 0x801b9138 +extern UNKNOWN_FUNCTION(VISetPostRetraceCallback); +// PAL: 0x801b94a4 +extern UNKNOWN_FUNCTION(VIInit); +// PAL: 0x801b9f6c +extern UNKNOWN_FUNCTION(VIConfigure); +// PAL: 0x801ba650 +extern UNKNOWN_FUNCTION(VIConfigurePan); +// PAL: 0x801ba9a4 +extern UNKNOWN_FUNCTION(VIFlush); +// PAL: 0x801baab8 +extern UNKNOWN_FUNCTION(VISetNextFrameBuffer); +// PAL: 0x801bab2c +extern UNKNOWN_FUNCTION(VISetBlack); +// PAL: 0x801baba4 +extern UNKNOWN_FUNCTION(VIGetRetraceCount); +// PAL: 0x801bacd8 +extern UNKNOWN_FUNCTION(VIGetTvFormat); + +// Symbol: ScreenReport +// Function signature is unknown. +// PAL: 0x801a4aa4..0x801a4dc8 +MARK_BINARY_BLOB(ScreenReport, 0x801a4aa4, 0x801a4dc8); +asm UNKNOWN_FUNCTION(ScreenReport) { + // clang-format off + nofralloc; + stwu r1, -0x170(r1); + mflr r0; + stw r0, 0x174(r1); + addi r11, r1, 0x170; + bl _savegpr_17; + mr r22, r3; + mr r23, r4; + mr r24, r6; + mr r25, r7; + mr r26, r8; + mr r27, r9; + addi r31, r5, -24; + addi r30, r4, -48; + lis r18, 0x8081; + lis r19, 0x8889; + li r17, 0; + li r21, 0x18; + li r20, 3; +lbl_801a4aec: + cmpw r31, r26; + blt lbl_801a4db0; + mullw r0, r26, r23; + mr r28, r25; + add r0, r25, r0; + slwi r0, r0, 1; + add r29, r22, r0; + b lbl_801a4da4; +lbl_801a4b0c: + extsb r0, r3; + cmpwi r0, 0xa; + bne lbl_801a4b24; + add r26, r26, r27; + addi r10, r10, 1; + b lbl_801a4aec; +lbl_801a4b24: + cmpw r30, r28; + bge lbl_801a4b34; + add r26, r26, r27; + b lbl_801a4aec; +lbl_801a4b34: + li r6, 0; + li r7, 0; + mtctr r20; +lbl_801a4b40: + clrlwi r0, r6, 0x1d; + addi r3, r6, 1; + add r0, r0, r7; + addi r4, r1, 0x10; + slwi r0, r0, 2; + addi r5, r6, 2; + add r4, r4, r0; + addi r8, r6, 3; + srwi r0, r3, 3; + stw r17, 0(r4); + mulli r0, r0, 0x18; + clrlwi r3, r3, 0x1d; + stw r17, 0x20(r4); + addi r9, r6, 4; + addi r11, r6, 6; + add r0, r3, r0; + stw r17, 0x40(r4); + slwi r0, r0, 2; + srwi r4, r5, 3; + addi r3, r1, 0x10; + add r3, r3, r0; + clrlwi r5, r5, 0x1d; + stw r17, 0(r3); + mulli r4, r4, 0x18; + addi r0, r6, 5; + stw r17, 0x20(r3); + addi r7, r7, 0x18; + add r5, r5, r4; + slwi r12, r5, 2; + stw r17, 0x40(r3); + srwi r5, r8, 3; + addi r4, r1, 0x10; + stwux r17, r4, r12; + mulli r3, r5, 0x18; + clrlwi r12, r8, 0x1d; + stw r17, 0x20(r4); + srwi r8, r9, 3; + add r12, r12, r3; + addi r5, r6, 7; + stw r17, 0x40(r4); + addi r3, r1, 0x10; + slwi r12, r12, 2; + clrlwi r9, r9, 0x1d; + add r3, r3, r12; + addi r4, r1, 0x10; + stw r17, 0(r3); + mulli r8, r8, 0x18; + srwi r12, r0, 3; + stw r17, 0x20(r3); + clrlwi r0, r0, 0x1d; + add r8, r9, r8; + srwi r9, r11, 3; + slwi r8, r8, 2; + stw r17, 0x40(r3); + add r4, r4, r8; + addi r3, r1, 0x10; + stw r17, 0(r4); + mulli r12, r12, 0x18; + clrlwi r11, r11, 0x1d; + stw r17, 0x20(r4); + srwi r8, r5, 3; + add r0, r0, r12; + addi r6, r6, 8; + stw r17, 0x40(r4); + slwi r0, r0, 2; + addi r4, r1, 0x10; + stwux r17, r3, r0; + mulli r0, r9, 0x18; + clrlwi r9, r5, 0x1d; + stw r17, 0x20(r3); + mr r5, r4; + add r0, r11, r0; + stw r17, 0x40(r3); + slwi r0, r0, 2; + stwux r17, r4, r0; + mulli r0, r8, 0x18; + stw r17, 0x20(r4); + add r0, r9, r0; + stw r17, 0x40(r4); + slwi r0, r0, 2; + stwux r17, r5, r0; + stw r17, 0x20(r5); + stw r17, 0x40(r5); + bdnz lbl_801a4b40; + mr r3, r10; + addi r4, r1, 0x10; + addi r7, r1, 8; + li r5, 0; + li r6, 6; + bl OSGetFontTexel; + mr r10, r3; + li r7, 0; + li r3, 0; +lbl_801a4cb4: + srwi r0, r7, 3; + clrlwi r6, r7, 0x1d; + mulli r0, r0, 0x18; + addi r5, r1, 0x10; + mr r4, r28; + li r8, 0; + add r0, r6, r0; + slwi r0, r0, 2; + add r5, r5, r0; + mtctr r21; +lbl_801a4cdc: + rlwinm r6, r8, 2, 0, 0x1a; + clrlwi r0, r8, 0x1d; + subfic r0, r0, 7; + lwzx r6, r5, r6; + slwi r0, r0, 2; + srw r0, r6, r0; + clrlwi. r9, r0, 0x1c; + beq lbl_801a4d78; + lbz r6, 0(r24); + add r0, r8, r3; + slwi r12, r0, 1; + addi r11, r18, -32639; + mullw r9, r6, r9; + clrlwi. r0, r4, 0x1f; + add r6, r29, r12; + addi r0, r19, -30583; + mulli r9, r9, 0xef; + mulhw r11, r11, r9; + add r9, r11, r9; + srawi r9, r9, 7; + srwi r11, r9, 0x1f; + add r9, r9, r11; + mulhw r0, r0, r9; + add r0, r0, r9; + srawi r0, r0, 3; + srwi r9, r0, 0x1f; + add r9, r0, r9; + addi r0, r9, 0x10; + stbx r0, r29, r12; + beq lbl_801a4d68; + lbz r9, 1(r24); + lbz r0, 2(r24); + stb r9, -1(r6); + stb r0, 1(r6); + b lbl_801a4d78; +lbl_801a4d68: + lbz r9, 2(r24); + lbz r0, 1(r24); + stb r9, -1(r6); + stb r0, 1(r6); +lbl_801a4d78: + addi r4, r4, 1; + addi r8, r8, 1; + bdnz lbl_801a4cdc; + addi r7, r7, 1; + add r3, r3, r23; + cmplwi r7, 0x18; + blt lbl_801a4cb4; + lwz r3, 8(r1); + slwi r0, r3, 1; + add r28, r28, r3; + add r29, r29, r0; +lbl_801a4da4: + lbz r3, 0(r10); + extsb. r0, r3; + bne lbl_801a4b0c; +lbl_801a4db0: + addi r11, r1, 0x170; + bl _restgpr_17; + lwz r0, 0x174(r1); + mtlr r0; + addi r1, r1, 0x170; + blr; + // clang-format on +} + +// Symbol: ConfigureVideo +// Function signature is unknown. +// PAL: 0x801a4dc8..0x801a4ec4 +MARK_BINARY_BLOB(ConfigureVideo, 0x801a4dc8, 0x801a4ec4); +asm UNKNOWN_FUNCTION(ConfigureVideo) { + // clang-format off + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + li r6, 0x1e0; + li r5, 0x28; + stw r0, 0x54(r1); + li r0, 0x280; + sth r3, 0xc(r1); + sth r6, 0xe(r1); + sth r4, 0x10(r1); + sth r5, 0x12(r1); + sth r0, 0x16(r1); + sth r4, 0x18(r1); + bl VIGetTvFormat; + cmpwi r3, 2; + beq lbl_801a4e24; + bge lbl_801a4e18; + cmpwi r3, 0; + beq lbl_801a4e24; + bge lbl_801a4e80; + b lbl_801a4e98; +lbl_801a4e18: + cmpwi r3, 5; + beq lbl_801a4e64; + b lbl_801a4e98; +lbl_801a4e24: + lis r3, 0xcc00; + lhz r0, 0x206c(r3); + clrlwi. r0, r0, 0x1f; + beq lbl_801a4e4c; + li r0, 0; + li r3, 2; + stw r3, 8(r1); + sth r0, 0x14(r1); + stw r0, 0x1c(r1); + b lbl_801a4e98; +lbl_801a4e4c: + li r3, 0; + li r0, 1; + stw r3, 8(r1); + sth r3, 0x14(r1); + stw r0, 0x1c(r1); + b lbl_801a4e98; +lbl_801a4e64: + li r4, 0x14; + li r3, 0; + li r0, 1; + stw r4, 8(r1); + sth r3, 0x14(r1); + stw r0, 0x1c(r1); + b lbl_801a4e98; +lbl_801a4e80: + li r4, 4; + li r3, 0x2f; + li r0, 1; + stw r4, 8(r1); + sth r3, 0x14(r1); + stw r0, 0x1c(r1); +lbl_801a4e98: + addi r3, r1, 8; + bl VIConfigure; + li r3, 0; + li r4, 0; + li r5, 0x280; + li r6, 0x1e0; + bl VIConfigurePan; + lwz r0, 0x54(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; + // clang-format on +} + +// Symbol: OSFatal +// Function signature is unknown. +// PAL: 0x801a4ec4..0x801a50dc +MARK_BINARY_BLOB(OSFatal, 0x801a4ec4, 0x801a50dc); +asm UNKNOWN_FUNCTION(OSFatal) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_24; + mr r27, r3; + mr r28, r4; + mr r29, r5; + bl OSDisableInterrupts; + bl OSDisableScheduler; + lis r24, 0x8034; + addi r3, r24, 0x7160; + bl OSClearContext; + addi r3, r24, 0x7160; + bl OSSetCurrentContext; + bl __OSStopAudioSystem; + bl VIInit; + li r3, 0x80; + bl __OSUnmaskInterrupts; + li r3, 1; + bl VISetBlack; + bl VIFlush; + li r3, 0; + bl VISetPreRetraceCallback; + li r3, 0; + bl VISetPostRetraceCallback; + bl OSEnableInterrupts; + bl VIGetRetraceCount; + mr r24, r3; +lbl_801a4f38: + bl VIGetRetraceCount; + subf r0, r24, r3; + cmpwi r0, 1; + blt lbl_801a4f38; + bl OSGetTime; + lis r5, 0x1062; + mr r30, r4; + mr r31, r3; + lis r25, 0x8000; + addi r24, r5, 0x4dd3; + li r26, 0; +lbl_801a4f64: + li r3, 0; + li r4, 0; + bl __OSCallShutdownFunctions; + cmpwi r3, 0; + bne lbl_801a4fb4; + bl OSGetTime; + lwz r0, 0xf8(r25); + subfc r6, r30, r4; + subfe r5, r31, r3; + xoris r4, r26, 0x8000; + srwi r0, r0, 2; + mulhwu r3, r24, r0; + xoris r0, r5, 0x8000; + srwi r3, r3, 6; + mulli r3, r3, 0x3e8; + subfc r3, r3, r6; + subfe r4, r4, r0; + subfe r4, r0, r0; + neg. r4, r4; + bne lbl_801a4f64; +lbl_801a4fb4: + bl OSDisableInterrupts; + li r3, 1; + li r4, 0; + bl __OSCallShutdownFunctions; + li r3, 0; + li r4, 0; + bl EXISetExiCallback; + li r3, 2; + li r4, 0; + bl EXISetExiCallback; + b lbl_801a4ff8; +lbl_801a4fe0: + li r3, 0; + bl EXISync; + li r3, 0; + bl EXIDeselect; + li r3, 0; + bl EXIUnlock; +lbl_801a4ff8: + li r3, 0; + li r4, 1; + li r5, 0; + bl EXILock; + cmpwi r3, 0; + beq lbl_801a4fe0; + li r3, 0; + bl EXIUnlock; + lis r3, 0xcd00; +lbl_801a501c: + lwz r0, 0x680c(r3); + clrlwi r0, r0, 0x1f; + cmplwi r0, 1; + beq lbl_801a501c; + lis r4, 0x801a; + li r3, 8; + addi r4, r4, 0x448; + bl __OSSetExceptionHandler; + bl GXAbortFrame; + lis r3, 0x8140; + bl OSSetArenaLo; + lis r4, 0x8000; + lwz r3, 0x38(r4); + cmpwi r3, 0; + bne lbl_801a5064; + lwz r3, 0x3110(r4); + bl OSSetArenaHi; + b lbl_801a5068; +lbl_801a5064: + bl OSSetArenaHi; +lbl_801a5068: + lbz r9, 0(r27); + lis r10, 0x8034; + lbz r8, 1(r27); + stbu r9, 0x7428(r10); + lbz r7, 2(r27); + lbz r6, 3(r27); + lbz r5, 0(r28); + lbz r4, 1(r28); + lbz r3, 2(r28); + lbz r0, 3(r28); + stb r8, 1(r10); + stb r7, 2(r10); + stb r6, 3(r10); + stb r5, 4(r10); + stb r4, 5(r10); + stb r3, 6(r10); + stb r0, 7(r10); + stw r29, 8(r10); + bl OSGetArenaHi; + lis r5, 0x801a; + mr r4, r3; + addi r3, r5, 0x50dc; + bl OSSwitchFiber; + addi r11, r1, 0x30; + bl _restgpr_24; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: Halt +// Function signature is unknown. +// PAL: 0x801a50dc..0x801a56dc +MARK_BINARY_BLOB(Halt, 0x801a50dc, 0x801a56dc); +asm UNKNOWN_FUNCTION(Halt) { + // clang-format off + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + stw r0, 0x54(r1); + lis r0, 0x4330; + stw r31, 0x4c(r1); + stw r30, 0x48(r1); + stw r29, 0x44(r1); + stw r28, 0x40(r1); + stw r0, 0x20(r1); + stw r0, 0x28(r1); + bl OSEnableInterrupts; + lis r29, 0x8034; + addi r29, r29, 0x7428; + lwz r28, 8(r29); + mr r3, r28; + bl strlen; + addi r30, r3, 1; + li r4, 0x20; + mr r3, r30; + bl OSAllocFromMEM1ArenaLo; + mr r4, r28; + mr r5, r30; + bl memmove; + stw r3, 8(r29); + lis r3, 0xa; + addi r3, r3, 0x1004; + li r4, 0x20; + bl OSAllocFromMEM1ArenaLo; + mr r31, r3; + bl OSGetArenaLo; + mr r4, r3; + mr r3, r31; + bl OSLoadFont; + lis r3, 9; + li r4, 0x20; + addi r3, r3, 0x6000; + bl OSAllocFromMEM1ArenaLo; + lbz r4, 6(r29); + mr r30, r3; + lbz r5, 4(r29); + stw r4, 0x24(r1); + lbz r3, 5(r29); + stw r5, 0x2c(r1); + lfd f4, 0x20(r1); + lfd f2, 0x28(r1); + lfd f0, -0x6690(r2); + stw r3, 0x24(r1); + fsubs f3, f2, f0; + lfs f2, -0x66bc(r2); + lfd f1, 0x20(r1); + fsubs f7, f4, f0; + stw r4, 0x2c(r1); + fsubs f5, f1, f0; + lfd f1, 0x28(r1); + fmuls f6, f2, f3; + stw r5, 0x24(r1); + lfs f4, -0x66b8(r2); + fsubs f3, f1, f0; + lfd f2, 0x20(r1); + fmuls f4, f4, f5; + stw r3, 0x2c(r1); + lfs f5, -0x66c0(r2); + fsubs f9, f2, f0; + lfd f1, 0x28(r1); + fmuls f13, f5, f7; + lfs f11, -0x66b0(r2); + fadds f12, f6, f4; + lfs f8, -0x66ac(r2); + fsubs f7, f1, f0; + stw r5, 0x24(r1); + fmuls f10, f11, f3; + lfs f6, -0x66a8(r2); + lfd f1, 0x20(r1); + fmuls f8, f8, f9; + fmuls f6, f6, f7; + stw r3, 0x2c(r1); + fsubs f5, f1, f0; + lfd f1, 0x28(r1); + stw r4, 0x24(r1); + fsubs f4, f1, f0; + lfs f3, -0x66a4(r2); + lfd f2, 0x20(r1); + fmuls f5, f11, f5; + lfs f1, -0x66a0(r2); + fsubs f0, f2, f0; + fmuls f2, f3, f4; + lbz r0, 7(r29); + fadds f3, f13, f12; + fsubs f4, f8, f6; + lfs f9, -0x66c4(r2); + fsubs f2, f5, f2; + fmuls f0, f1, f0; + lfs f5, -0x66c8(r2); + fadds f6, f9, f3; + fadds f1, f10, f4; + lfs f3, -0x66b4(r2); + fsubs f0, f2, f0; + fadds f4, f5, f6; + lfs f7, -0x669c(r2); + fadds f1, f3, f1; + fadds f0, f3, f0; + stb r5, 0x18(r1); + fcmpo cr0, f4, f7; + fadds f2, f5, f1; + stb r3, 0x19(r1); + fadds f1, f5, f0; + stb r4, 0x1a(r1); + stb r0, 0x1b(r1); + ble lbl_801a5294; + b lbl_801a52a8; +lbl_801a5294: + fcmpo cr0, f4, f9; + bge lbl_801a52a0; + b lbl_801a52a4; +lbl_801a52a0: + fmr f9, f4; +lbl_801a52a4: + fmr f7, f9; +lbl_801a52a8: + fctiwz f0, f7; + lfs f3, -0x6698(r2); + fcmpo cr0, f2, f3; + stfd f0, 0x30(r1); + lwz r0, 0x34(r1); + stb r0, 0xc(r1); + ble lbl_801a52c8; + b lbl_801a52dc; +lbl_801a52c8: + lfs f3, -0x66c4(r2); + fcmpo cr0, f2, f3; + bge lbl_801a52d8; + b lbl_801a52dc; +lbl_801a52d8: + fmr f3, f2; +lbl_801a52dc: + fctiwz f0, f3; + lfs f2, -0x6698(r2); + fcmpo cr0, f1, f2; + stfd f0, 0x30(r1); + lwz r0, 0x34(r1); + stb r0, 0xd(r1); + ble lbl_801a52fc; + b lbl_801a5310; +lbl_801a52fc: + lfs f2, -0x66c4(r2); + fcmpo cr0, f1, f2; + bge lbl_801a530c; + b lbl_801a5310; +lbl_801a530c: + fmr f2, f1; +lbl_801a5310: + fctiwz f0, f2; + li r0, 0; + stb r0, 0xf(r1); + mr r4, r30; + li r5, 0; + li r0, 0x14; + stfd f0, 0x30(r1); + lwz r3, 0x34(r1); + stb r3, 0xe(r1); + lwz r3, 0xc(r1); + stw r3, 0x1c(r1); + lbz r6, 0x1d(r1); + lbz r7, 0x1e(r1); + lbz r3, 0x1c(r1); +lbl_801a5348: + mtctr r0; +lbl_801a534c: + stb r3, 0(r4); + stb r6, 1(r4); + stb r3, 2(r4); + stb r7, 3(r4); + stb r3, 4(r4); + stb r6, 5(r4); + stb r3, 6(r4); + stb r7, 7(r4); + stb r3, 8(r4); + stb r6, 9(r4); + stb r3, 0xa(r4); + stb r7, 0xb(r4); + stb r3, 0xc(r4); + stb r6, 0xd(r4); + stb r3, 0xe(r4); + stb r7, 0xf(r4); + stb r3, 0x10(r4); + stb r6, 0x11(r4); + stb r3, 0x12(r4); + stb r7, 0x13(r4); + stb r3, 0x14(r4); + stb r6, 0x15(r4); + stb r3, 0x16(r4); + stb r7, 0x17(r4); + stb r3, 0x18(r4); + stb r6, 0x19(r4); + stb r3, 0x1a(r4); + stb r7, 0x1b(r4); + stb r3, 0x1c(r4); + stb r6, 0x1d(r4); + stb r3, 0x1e(r4); + stb r7, 0x1f(r4); + stb r3, 0x20(r4); + stb r6, 0x21(r4); + stb r3, 0x22(r4); + stb r7, 0x23(r4); + stb r3, 0x24(r4); + stb r6, 0x25(r4); + stb r3, 0x26(r4); + stb r7, 0x27(r4); + stb r3, 0x28(r4); + stb r6, 0x29(r4); + stb r3, 0x2a(r4); + stb r7, 0x2b(r4); + stb r3, 0x2c(r4); + stb r6, 0x2d(r4); + stb r3, 0x2e(r4); + stb r7, 0x2f(r4); + stb r3, 0x30(r4); + stb r6, 0x31(r4); + stb r3, 0x32(r4); + stb r7, 0x33(r4); + stb r3, 0x34(r4); + stb r6, 0x35(r4); + stb r3, 0x36(r4); + stb r7, 0x37(r4); + stb r3, 0x38(r4); + stb r6, 0x39(r4); + stb r3, 0x3a(r4); + stb r7, 0x3b(r4); + stb r3, 0x3c(r4); + stb r6, 0x3d(r4); + stb r3, 0x3e(r4); + stb r7, 0x3f(r4); + addi r4, r4, 0x40; + bdnz lbl_801a534c; + addi r5, r5, 1; + cmpwi r5, 0x1e0; + blt lbl_801a5348; + mr r3, r30; + bl VISetNextFrameBuffer; + li r3, 0x280; + li r4, 0x1e0; + bl ConfigureVideo; + bl VIFlush; + bl VIGetRetraceCount; + mr r28, r3; +lbl_801a5480: + bl VIGetRetraceCount; + subf r0, r28, r3; + cmpwi r0, 2; + blt lbl_801a5480; + lbz r3, 2(r29); + lbz r5, 0(r29); + stw r3, 0x2c(r1); + lbz r4, 1(r29); + stw r5, 0x24(r1); + lfd f4, 0x28(r1); + lfd f2, 0x20(r1); + lfd f0, -0x6690(r2); + stw r4, 0x2c(r1); + fsubs f3, f2, f0; + lfs f2, -0x66bc(r2); + lfd f1, 0x28(r1); + fsubs f7, f4, f0; + stw r3, 0x24(r1); + fsubs f5, f1, f0; + lfd f1, 0x20(r1); + fmuls f6, f2, f3; + stw r5, 0x2c(r1); + lfs f4, -0x66b8(r2); + fsubs f3, f1, f0; + lfd f2, 0x28(r1); + fmuls f4, f4, f5; + stw r4, 0x24(r1); + lfs f5, -0x66c0(r2); + fsubs f9, f2, f0; + lfd f1, 0x20(r1); + fmuls f13, f5, f7; + lfs f11, -0x66b0(r2); + fadds f12, f6, f4; + lfs f8, -0x66ac(r2); + fsubs f7, f1, f0; + stw r5, 0x2c(r1); + fmuls f10, f11, f3; + lfs f6, -0x66a8(r2); + lfd f1, 0x28(r1); + fmuls f8, f8, f9; + fmuls f6, f6, f7; + stw r4, 0x24(r1); + fsubs f5, f1, f0; + lfd f1, 0x20(r1); + stw r3, 0x2c(r1); + fsubs f4, f1, f0; + lfs f3, -0x66a4(r2); + lfd f2, 0x28(r1); + fmuls f5, f11, f5; + lfs f1, -0x66a0(r2); + fsubs f0, f2, f0; + fmuls f2, f3, f4; + lbz r0, 3(r29); + fadds f3, f13, f12; + fsubs f4, f8, f6; + lfs f9, -0x66c4(r2); + fsubs f2, f5, f2; + fmuls f0, f1, f0; + lfs f5, -0x66c8(r2); + fadds f6, f9, f3; + fadds f1, f10, f4; + lfs f3, -0x66b4(r2); + fsubs f0, f2, f0; + fadds f4, f5, f6; + lfs f7, -0x669c(r2); + fadds f1, f3, f1; + fadds f0, f3, f0; + stb r5, 0x10(r1); + fcmpo cr0, f4, f7; + fadds f2, f5, f1; + stb r4, 0x11(r1); + fadds f1, f5, f0; + stb r3, 0x12(r1); + stb r0, 0x13(r1); + ble lbl_801a55b0; + b lbl_801a55c4; +lbl_801a55b0: + fcmpo cr0, f4, f9; + bge lbl_801a55bc; + b lbl_801a55c0; +lbl_801a55bc: + fmr f9, f4; +lbl_801a55c0: + fmr f7, f9; +lbl_801a55c4: + fctiwz f0, f7; + lfs f3, -0x6698(r2); + fcmpo cr0, f2, f3; + stfd f0, 0x30(r1); + lwz r0, 0x34(r1); + stb r0, 8(r1); + ble lbl_801a55e4; + b lbl_801a55f8; +lbl_801a55e4: + lfs f3, -0x66c4(r2); + fcmpo cr0, f2, f3; + bge lbl_801a55f4; + b lbl_801a55f8; +lbl_801a55f4: + fmr f3, f2; +lbl_801a55f8: + fctiwz f0, f3; + lfs f2, -0x6698(r2); + fcmpo cr0, f1, f2; + stfd f0, 0x30(r1); + lwz r0, 0x34(r1); + stb r0, 9(r1); + ble lbl_801a5618; + b lbl_801a562c; +lbl_801a5618: + lfs f2, -0x66c4(r2); + fcmpo cr0, f1, f2; + bge lbl_801a5628; + b lbl_801a562c; +lbl_801a5628: + fmr f2, f1; +lbl_801a562c: + fctiwz f0, f2; + li r0, 0; + stb r0, 0xb(r1); + mr r3, r30; + lwz r10, 8(r29); + addi r6, r1, 0x14; + stfd f0, 0x30(r1); + li r4, 0x280; + li r5, 0x1e0; + li r7, 0x30; + lwz r0, 0x34(r1); + li r8, 0x64; + stb r0, 0xa(r1); + lwz r0, 8(r1); + stw r0, 0x14(r1); + lhz r9, 0xe(r31); + bl ScreenReport; + lis r4, 9; + mr r3, r30; + addi r4, r4, 0x6000; + bl DCFlushRange; + li r3, 0; + bl VISetBlack; + bl VIFlush; + bl VIGetRetraceCount; + mr r30, r3; +lbl_801a5694: + bl VIGetRetraceCount; + subf r0, r30, r3; + cmpwi r0, 1; + blt lbl_801a5694; + bl OSDisableInterrupts; + lwz r4, 8(r29); + addi r3, r13, -28984; + crclr 6; + bl OSReport; + bl PPCHalt; + lwz r0, 0x54(r1); + lwz r31, 0x4c(r1); + lwz r30, 0x48(r1); + lwz r29, 0x44(r1); + lwz r28, 0x40(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; + // clang-format on +} diff --git a/source/rvl/os/osFatal.h b/source/rvl/os/osFatal.h new file mode 100644 index 000000000..87a0e26c5 --- /dev/null +++ b/source/rvl/os/osFatal.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a4aa4..0x801a4dc8 +UNKNOWN_FUNCTION(ScreenReport); +// PAL: 0x801a4dc8..0x801a4ec4 +UNKNOWN_FUNCTION(ConfigureVideo); +// PAL: 0x801a4ec4..0x801a50dc +UNKNOWN_FUNCTION(OSFatal); +// PAL: 0x801a50dc..0x801a56dc +UNKNOWN_FUNCTION(Halt); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osFont.c b/source/rvl/os/osFont.c new file mode 100644 index 000000000..c01c79a9d --- /dev/null +++ b/source/rvl/os/osFont.c @@ -0,0 +1,1156 @@ +#include "osFont.h" + +// Extern function references. +// PAL: 0x801a90c4 +extern UNKNOWN_FUNCTION(__OSReadROM); +// PAL: 0x801ab410 +extern UNKNOWN_FUNCTION(OSUTF8to32); +// PAL: 0x801ab520 +extern UNKNOWN_FUNCTION(OSUTF16to32); +// PAL: 0x801ab590 +extern UNKNOWN_FUNCTION(OSUTF32toANSI); +// PAL: 0x801ab608 +extern UNKNOWN_FUNCTION(OSUTF32toSJIS); + +// Symbol: GetFontCode +// Function signature is unknown. +// PAL: 0x801a56dc..0x801a5810 +MARK_BINARY_BLOB(GetFontCode, 0x801a56dc, 0x801a5810); +asm UNKNOWN_FUNCTION(GetFontCode) { + // clang-format off + nofralloc; + cmplwi r3, 1; + bne lbl_801a57f0; + cmplwi r4, 0x20; + blt lbl_801a570c; + cmplwi r4, 0xdf; + bgt lbl_801a570c; + addi r0, r4, -32; + lis r3, 0x8029; + slwi r0, r0, 1; + addi r3, r3, -1296; + lhzx r3, r3, r0; + blr; +lbl_801a570c: + cmplwi r4, 0x889e; + ble lbl_801a5778; + cmplwi r4, 0x9872; + bgt lbl_801a5778; + rlwinm r3, r4, 0x18, 0x18, 0x1f; + clrlwi r4, r4, 0x18; + addi r3, r3, -136; + li r0, 0; + cmplwi r4, 0x40; + mulli r3, r3, 0xbc; + blt lbl_801a574c; + cmplwi r4, 0xfc; + bgt lbl_801a574c; + cmplwi r4, 0x7f; + beq lbl_801a574c; + li r0, 1; +lbl_801a574c: + cmpwi r0, 0; + bne lbl_801a575c; + li r3, 0; + blr; +lbl_801a575c: + addi r4, r4, -64; + cmpwi r4, 0x40; + blt lbl_801a576c; + addi r4, r4, -1; +lbl_801a576c: + add r3, r3, r4; + addi r3, r3, 0x2be; + blr; +lbl_801a5778: + cmplwi r4, 0x8140; + blt lbl_801a5808; + cmplwi r4, 0x879e; + bge lbl_801a5808; + rlwinm r3, r4, 0x18, 0x18, 0x1f; + clrlwi r4, r4, 0x18; + addi r3, r3, -129; + li r0, 0; + cmplwi r4, 0x40; + mulli r3, r3, 0xbc; + blt lbl_801a57b8; + cmplwi r4, 0xfc; + bgt lbl_801a57b8; + cmplwi r4, 0x7f; + beq lbl_801a57b8; + li r0, 1; +lbl_801a57b8: + cmpwi r0, 0; + bne lbl_801a57c8; + li r3, 0; + blr; +lbl_801a57c8: + addi r4, r4, -64; + cmpwi r4, 0x40; + blt lbl_801a57d8; + addi r4, r4, -1; +lbl_801a57d8: + add r0, r3, r4; + lis r3, 0x8029; + slwi r0, r0, 1; + addi r3, r3, -912; + lhzx r3, r3, r0; + blr; +lbl_801a57f0: + cmplwi r4, 0x20; + ble lbl_801a5808; + cmplwi r4, 0xff; + bgt lbl_801a5808; + addi r3, r4, -32; + blr; +lbl_801a5808: + li r3, 0; + blr; + // clang-format on +} + +// Symbol: Decode +// Function signature is unknown. +// PAL: 0x801a5810..0x801a59b4 +MARK_BINARY_BLOB(Decode, 0x801a5810, 0x801a59b4); +asm UNKNOWN_FUNCTION(Decode) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_26; + lwz r5, 0xc(r3); + addi r30, r3, 0x10; + lwz r0, 4(r3); + li r8, 0; + add r29, r3, r5; + lwz r9, 8(r3); + li r11, 0; + lis r5, 0x8000; +lbl_801a5844: + cmpwi r11, 0; + bne lbl_801a5858; + lwz r12, 0(r30); + li r11, 0x20; + addi r30, r30, 4; +lbl_801a5858: + rlwinm. r6, r12, 0, 0, 0; + beq lbl_801a5874; + lbz r6, 0(r29); + addi r29, r29, 1; + stbx r6, r4, r8; + addi r8, r8, 1; + b lbl_801a598c; +lbl_801a5874: + add r7, r3, r9; + lbzx r6, r3, r9; + lbz r7, 1(r7); + addi r9, r9, 2; + rlwimi r7, r6, 8, 0x10, 0x17; + srawi. r10, r7, 0xc; + clrlwi r6, r7, 0x14; + subf r7, r6, r8; + bne lbl_801a58a8; + lbz r6, 0(r29); + addi r29, r29, 1; + addi r10, r6, 0x12; + b lbl_801a58ac; +lbl_801a58a8: + addi r10, r10, 2; +lbl_801a58ac: + cmpwi cr1, r10, 0; + li r6, 0; + ble cr1, lbl_801a598c; + cmpwi r10, 8; + addi r27, r10, -8; + ble lbl_801a5958; + li r28, 0; + blt cr1, lbl_801a58dc; + addi r26, r5, -2; + cmpw r10, r26; + bgt lbl_801a58dc; + li r28, 1; +lbl_801a58dc: + cmpwi r28, 0; + beq lbl_801a5958; + addi r31, r27, 7; + add r28, r4, r8; + srwi r31, r31, 3; + mtctr r31; + cmpwi r27, 0; + ble lbl_801a5958; +lbl_801a58fc: + add r26, r4, r7; + add r27, r8, r4; + lbz r31, -1(r26); + addi r8, r8, 8; + addi r6, r6, 8; + stb r31, 0(r28); + addi r28, r28, 8; + lbzx r31, r4, r7; + addi r7, r7, 8; + stb r31, 1(r27); + lbz r31, 1(r26); + stb r31, 2(r27); + lbz r31, 2(r26); + stb r31, 3(r27); + lbz r31, 3(r26); + stb r31, 4(r27); + lbz r31, 4(r26); + stb r31, 5(r27); + lbz r31, 5(r26); + stb r31, 6(r27); + lbz r31, 6(r26); + stb r31, 7(r27); + bdnz lbl_801a58fc; +lbl_801a5958: + subf r31, r6, r10; + add r28, r4, r8; + mtctr r31; + cmpw r6, r10; + bge lbl_801a598c; +lbl_801a596c: + add r10, r4, r7; + addi r6, r6, 1; + lbz r10, -1(r10); + addi r8, r8, 1; + addi r7, r7, 1; + stb r10, 0(r28); + addi r28, r28, 1; + bdnz lbl_801a596c; +lbl_801a598c: + cmpw r8, r0; + slwi r12, r12, 1; + addi r11, r11, -1; + blt lbl_801a5844; + addi r11, r1, 0x20; + bl _restgpr_26; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSSetFontEncode +// Function signature is unknown. +// PAL: 0x801a59b4..0x801a5a34 +MARK_BINARY_BLOB(OSSetFontEncode, 0x801a59b4, 0x801a5a34); +asm UNKNOWN_FUNCTION(OSSetFontEncode) { + // clang-format off + nofralloc; + lhz r0, -0x7130(r13); + cmplwi r0, 0xffff; + beq lbl_801a59c4; + b lbl_801a5a08; +lbl_801a59c4: + lis r4, 0x8000; + lwz r0, 0xcc(r4); + cmpwi r0, 0; + beq lbl_801a59dc; + blt lbl_801a59f0; + b lbl_801a59f0; +lbl_801a59dc: + lis r4, 0xcc00; + lhz r0, 0x206e(r4); + rlwinm r0, r0, 0x1f, 0x1f, 0x1f; + sth r0, -0x7130(r13); + b lbl_801a59f8; +lbl_801a59f0: + li r0, 0; + sth r0, -0x7130(r13); +lbl_801a59f8: + lis r4, 0x801a; + clrlwi r0, r0, 0x10; + addi r4, r4, 0x5e5c; + stw r4, -0x6328(r13); +lbl_801a5a08: + cmplwi cr1, r3, 5; + bgt cr1, lbl_801a5a2c; + cmplwi r3, 3; + sth r3, -0x7130(r13); + blt lbl_801a5a2c; + bgt cr1, lbl_801a5a2c; + lis r3, 0x801a; + addi r3, r3, 0x5f58; + stw r3, -0x6328(r13); +lbl_801a5a2c: + mr r3, r0; + blr; + // clang-format on +} + +// Symbol: ReadFont +// Function signature is unknown. +// PAL: 0x801a5a34..0x801a5d34 +MARK_BINARY_BLOB(ReadFont, 0x801a5a34, 0x801a5d34); +asm UNKNOWN_FUNCTION(ReadFont) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_25; + cmplwi r4, 1; + mr r28, r3; + mr r29, r4; + mr r26, r5; + bne lbl_801a5ab4; + lis r4, 0x1b; + lis r3, 5; + mr r27, r28; + addi r31, r4, -256; + addi r30, r3, -12288; + b lbl_801a5aa8; +lbl_801a5a74: + cmpwi r30, 0x100; + li r25, 0x100; + bgt lbl_801a5a84; + mr r25, r30; +lbl_801a5a84: + subf r30, r25, r30; +lbl_801a5a88: + mr r3, r27; + mr r4, r25; + mr r5, r31; + bl __OSReadROM; + cmpwi r3, 0; + beq lbl_801a5a88; + add r31, r31, r25; + add r27, r27, r25; +lbl_801a5aa8: + cmpwi r30, 0; + bgt lbl_801a5a74; + b lbl_801a5b04; +lbl_801a5ab4: + lis r3, 0x20; + mr r27, r28; + addi r31, r3, -12544; + li r30, 0x3000; + b lbl_801a5afc; +lbl_801a5ac8: + cmpwi r30, 0x100; + li r25, 0x100; + bgt lbl_801a5ad8; + mr r25, r30; +lbl_801a5ad8: + subf r30, r25, r30; +lbl_801a5adc: + mr r3, r27; + mr r4, r25; + mr r5, r31; + bl __OSReadROM; + cmpwi r3, 0; + beq lbl_801a5adc; + add r31, r31, r25; + add r27, r27, r25; +lbl_801a5afc: + cmpwi r30, 0; + bgt lbl_801a5ac8; +lbl_801a5b04: + lbz r0, 0(r28); + cmplwi r0, 0x59; + bne lbl_801a5b30; + lbz r0, 1(r28); + cmplwi r0, 0x61; + bne lbl_801a5b30; + lbz r0, 2(r28); + cmplwi r0, 0x79; + bne lbl_801a5b30; + lwz r27, 4(r28); + b lbl_801a5b34; +lbl_801a5b30: + li r27, 0; +lbl_801a5b34: + cmpwi r27, 0; + bne lbl_801a5b44; + li r3, 0; + b lbl_801a5d1c; +lbl_801a5b44: + mr r3, r28; + mr r4, r26; + bl Decode; + cmplwi r29, 1; + bne lbl_801a5d18; + mr r3, r29; + lhz r28, -0x6688(r2); + lhz r29, -0x6686(r2); + li r4, 0x54; + lhz r30, -0x6684(r2); + lhz r31, -0x6682(r2); + bl GetFontCode; + lhz r9, 0x1a(r26); + lhz r4, 0x1c(r26); + lhz r0, 0x1e(r26); + mullw r10, r9, r4; + lwz r5, 0x24(r26); + rlwinm r4, r0, 2, 0, 0x1a; + lhz r6, 0x12(r26); + rlwinm r0, r0, 3, 0x1f, 0x1f; + lhz r7, 0x10(r26); + divw r12, r3, r10; + add r0, r0, r4; + add r4, r26, r5; + lwz r8, 0x14(r26); + srawi r11, r0, 1; + mullw r0, r12, r10; + subf r3, r0, r3; + divw r5, r3, r9; + mullw r0, r5, r9; + mullw r5, r5, r6; + subf r25, r0, r3; + addi r6, r5, 4; + mullw r25, r25, r7; + addi r10, r5, 5; + srawi r3, r6, 3; + slwi r0, r6, 0x1d; + addze r9, r3; + srwi r7, r6, 0x1f; + mullw r12, r12, r8; + srawi r3, r25, 3; + subf r0, r7, r0; + addze r8, r3; + rotlwi r3, r0, 3; + slwi r0, r25, 0x1d; + srwi r6, r25, 0x1f; + srwi r12, r12, 1; + mullw r9, r11, r9; + add r7, r3, r7; + subf r0, r6, r0; + add r4, r4, r12; + rotlwi r3, r0, 3; + slwi r0, r8, 4; + add r3, r3, r6; + add r9, r4, r9; + slwi r8, r7, 1; + slwi r6, r10, 0x1d; + srawi r3, r3, 2; + add r9, r9, r0; + add r9, r9, r8; + srwi r7, r10, 0x1f; + addze r3, r3; + addi r8, r5, 6; + sthx r28, r9, r3; + subf r6, r7, r6; + rotlwi r6, r6, 3; + add r6, r6, r7; + lhz r11, 0x1e(r26); + slwi r9, r6, 1; + srwi r7, r8, 0x1f; + rlwinm r12, r11, 2, 0, 0x1a; + rlwinm r11, r11, 3, 0x1f, 0x1f; + add r11, r11, r12; + slwi r6, r8, 0x1d; + srawi r11, r11, 1; + srawi r10, r10, 3; + subf r6, r7, r6; + addze r10, r10; + rotlwi r6, r6, 3; + add r6, r6, r7; + mullw r10, r11, r10; + slwi r6, r6, 1; + add r7, r4, r10; + add r7, r7, r0; + add r7, r7, r9; + sthx r29, r7, r3; + lhz r7, 0x1e(r26); + rlwinm r9, r7, 2, 0, 0x1a; + rlwinm r7, r7, 3, 0x1f, 0x1f; + add r7, r7, r9; + srawi r9, r7, 1; + srawi r7, r8, 3; + addze r7, r7; + mullw r7, r9, r7; + add r7, r4, r7; + add r7, r7, r0; + add r7, r7, r6; + sthx r30, r7, r3; + addi r7, r5, 7; + slwi r5, r7, 0x1d; + lhz r8, 0x1e(r26); + srwi r6, r7, 0x1f; + subf r5, r6, r5; + rlwinm r9, r8, 2, 0, 0x1a; + rlwinm r8, r8, 3, 0x1f, 0x1f; + add r8, r8, r9; + rotlwi r5, r5, 3; + srawi r8, r8, 1; + srawi r7, r7, 3; + add r5, r5, r6; + addze r6, r7; + mullw r6, r8, r6; + slwi r5, r5, 1; + add r4, r4, r6; + add r4, r4, r0; + add r4, r4, r5; + sthx r31, r4, r3; +lbl_801a5d18: + mr r3, r27; +lbl_801a5d1c: + addi r11, r1, 0x30; + bl _restgpr_25; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: OSLoadFont +// Function signature is unknown. +// PAL: 0x801a5d34..0x801a5e5c +MARK_BINARY_BLOB(OSLoadFont, 0x801a5d34, 0x801a5e5c); +asm UNKNOWN_FUNCTION(OSLoadFont) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + mr r5, r3; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r4; + lhz r0, -0x7130(r13); + cmplwi r0, 0xffff; + beq lbl_801a5d60; + b lbl_801a5da4; +lbl_801a5d60: + lis r4, 0x8000; + lwz r0, 0xcc(r4); + cmpwi r0, 0; + beq lbl_801a5d78; + blt lbl_801a5d8c; + b lbl_801a5d8c; +lbl_801a5d78: + lis r4, 0xcc00; + lhz r0, 0x206e(r4); + rlwinm r0, r0, 0x1f, 0x1f, 0x1f; + sth r0, -0x7130(r13); + b lbl_801a5d94; +lbl_801a5d8c: + li r0, 0; + sth r0, -0x7130(r13); +lbl_801a5d94: + lis r4, 0x801a; + clrlwi r0, r0, 0x10; + addi r4, r4, 0x5e5c; + stw r4, -0x6328(r13); +lbl_801a5da4: + clrlwi r0, r0, 0x10; + cmpwi r0, 2; + beq lbl_801a5e3c; + bge lbl_801a5dc4; + cmpwi r0, 0; + beq lbl_801a5dd0; + bge lbl_801a5de8; + b lbl_801a5e3c; +lbl_801a5dc4: + cmpwi r0, 6; + bge lbl_801a5e3c; + b lbl_801a5e00; +lbl_801a5dd0: + stw r3, -0x631c(r13); + mr r3, r30; + li r4, 0; + bl ReadFont; + mr r31, r3; + b lbl_801a5e40; +lbl_801a5de8: + stw r3, -0x6320(r13); + mr r3, r30; + li r4, 1; + bl ReadFont; + mr r31, r3; + b lbl_801a5e40; +lbl_801a5e00: + stw r3, -0x631c(r13); + mr r3, r30; + li r4, 0; + bl ReadFont; + cmpwi r3, 0; + mr r31, r3; + beq lbl_801a5e40; + lwz r0, -0x631c(r13); + li r4, 1; + add r5, r0, r3; + mr r3, r30; + stw r5, -0x6320(r13); + bl ReadFont; + add r31, r31, r3; + b lbl_801a5e40; +lbl_801a5e3c: + li r31, 0; +lbl_801a5e40: + mr r3, r31; + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: ParseStringS +// Function signature is unknown. +// PAL: 0x801a5e5c..0x801a5f58 +MARK_BINARY_BLOB(ParseStringS, 0x801a5e5c, 0x801a5f58); +asm UNKNOWN_FUNCTION(ParseStringS) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 1; + li r7, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r6; + stw r30, 8(r1); + mr r30, r4; + beq lbl_801a5eac; + bge lbl_801a5f2c; + cmpwi r3, 0; + bge lbl_801a5e94; + b lbl_801a5f2c; +lbl_801a5e94: + lbz r7, 0(r4); + lwz r6, -0x631c(r13); + cmpwi r7, 0; + beq lbl_801a5f2c; + addi r30, r4, 1; + b lbl_801a5f2c; +lbl_801a5eac: + lbz r7, 0(r4); + lwz r6, -0x6320(r13); + cmpwi r7, 0; + beq lbl_801a5f2c; + cmplwi r7, 0x81; + addi r30, r4, 1; + li r0, 0; + blt lbl_801a5ed4; + cmplwi r7, 0x9f; + ble lbl_801a5ee4; +lbl_801a5ed4: + cmplwi r7, 0xe0; + blt lbl_801a5ee8; + cmplwi r7, 0xfc; + bgt lbl_801a5ee8; +lbl_801a5ee4: + li r0, 1; +lbl_801a5ee8: + cmpwi r0, 0; + beq lbl_801a5f2c; + lbz r4, 0(r30); + li r0, 0; + cmplwi r4, 0x40; + blt lbl_801a5f14; + cmplwi r4, 0xfc; + bgt lbl_801a5f14; + cmplwi r4, 0x7f; + beq lbl_801a5f14; + li r0, 1; +lbl_801a5f14: + cmpwi r0, 0; + beq lbl_801a5f2c; + lbz r0, 0(r30); + rlwimi r0, r7, 8, 8, 0x17; + addi r30, r30, 1; + clrlwi r7, r0, 0x10; +lbl_801a5f2c: + stw r6, 0(r5); + mr r4, r7; + bl GetFontCode; + stw r3, 0(r31); + mr r3, r30; + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: ParseStringW +// Function signature is unknown. +// PAL: 0x801a5f58..0x801a6114 +MARK_BINARY_BLOB(ParseStringW, 0x801a5f58, 0x801a6114); +asm UNKNOWN_FUNCTION(ParseStringW) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_26; + li r30, 0; + cmpwi r3, 3; + stw r30, 8(r1); + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r29, r6; + beq lbl_801a6054; + bge lbl_801a5fa8; + cmpwi r3, 1; + beq lbl_801a5fd0; + bge lbl_801a6090; + cmpwi r3, 0; + bge lbl_801a5fb8; + b lbl_801a6090; +lbl_801a5fa8: + cmpwi r3, 5; + beq lbl_801a607c; + bge lbl_801a6090; + b lbl_801a6068; +lbl_801a5fb8: + lbz r30, 0(r4); + lwz r31, -0x631c(r13); + cmpwi r30, 0; + beq lbl_801a6090; + addi r27, r4, 1; + b lbl_801a6090; +lbl_801a5fd0: + lbz r30, 0(r4); + lwz r31, -0x6320(r13); + cmpwi r30, 0; + beq lbl_801a6090; + cmplwi r30, 0x81; + addi r27, r4, 1; + li r0, 0; + blt lbl_801a5ff8; + cmplwi r30, 0x9f; + ble lbl_801a6008; +lbl_801a5ff8: + cmplwi r30, 0xe0; + blt lbl_801a600c; + cmplwi r30, 0xfc; + bgt lbl_801a600c; +lbl_801a6008: + li r0, 1; +lbl_801a600c: + cmpwi r0, 0; + beq lbl_801a6090; + lbz r3, 0(r27); + li r0, 0; + cmplwi r3, 0x40; + blt lbl_801a6038; + cmplwi r3, 0xfc; + bgt lbl_801a6038; + cmplwi r3, 0x7f; + beq lbl_801a6038; + li r0, 1; +lbl_801a6038: + cmpwi r0, 0; + beq lbl_801a6090; + lbz r0, 0(r27); + rlwimi r0, r30, 8, 8, 0x17; + addi r27, r27, 1; + clrlwi r30, r0, 0x10; + b lbl_801a6090; +lbl_801a6054: + mr r3, r27; + addi r4, r1, 8; + bl OSUTF8to32; + mr r27, r3; + b lbl_801a6090; +lbl_801a6068: + mr r3, r27; + addi r4, r1, 8; + bl OSUTF16to32; + mr r27, r3; + b lbl_801a6090; +lbl_801a607c: + lwz r0, 0(r4); + cmpwi r0, 0; + stw r0, 8(r1); + beq lbl_801a6090; + addi r27, r4, 4; +lbl_801a6090: + lwz r3, 8(r1); + cmpwi r3, 0; + beq lbl_801a60e4; + lwz r31, -0x631c(r13); + li r26, 0; + bl OSUTF32toANSI; + clrlwi. r30, r3, 0x18; + beq lbl_801a60c8; + lwz r0, -0x6324(r13); + cmpwi r0, 0; + beq lbl_801a60e4; + lwz r0, 8(r1); + cmplwi r0, 0x7f; + bgt lbl_801a60e4; +lbl_801a60c8: + lwz r3, 8(r1); + bl OSUTF32toSJIS; + clrlwi. r0, r3, 0x10; + mr r30, r3; + beq lbl_801a60e4; + li r26, 1; + lwz r31, -0x6320(r13); +lbl_801a60e4: + stw r31, 0(r28); + mr r3, r26; + clrlwi r4, r30, 0x10; + bl GetFontCode; + stw r3, 0(r29); + addi r11, r1, 0x30; + mr r3, r27; + bl _restgpr_26; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: OSGetFontTexel +// Function signature is unknown. +// PAL: 0x801a6114..0x801a63a4 +MARK_BINARY_BLOB(OSGetFontTexel, 0x801a6114, 0x801a63a4); +asm UNKNOWN_FUNCTION(OSGetFontTexel) { + // clang-format off + nofralloc; + stwu r1, -0x50(r1); + mflr r0; + stw r0, 0x54(r1); + addi r11, r1, 0x50; + bl _savegpr_17; + lhz r0, -0x7130(r13); + mr r29, r4; + mr r30, r5; + mr r17, r6; + cmplwi r0, 0xffff; + mr r31, r7; + beq lbl_801a6148; + b lbl_801a618c; +lbl_801a6148: + lis r4, 0x8000; + lwz r0, 0xcc(r4); + cmpwi r0, 0; + beq lbl_801a6160; + blt lbl_801a6174; + b lbl_801a6174; +lbl_801a6160: + lis r4, 0xcc00; + lhz r0, 0x206e(r4); + rlwinm r0, r0, 0x1f, 0x1f, 0x1f; + sth r0, -0x7130(r13); + b lbl_801a617c; +lbl_801a6174: + li r0, 0; + sth r0, -0x7130(r13); +lbl_801a617c: + lis r4, 0x801a; + clrlwi r0, r0, 0x10; + addi r4, r4, 0x5e5c; + stw r4, -0x6328(r13); +lbl_801a618c: + lwz r12, -0x6328(r13); + mr r4, r3; + clrlwi r3, r0, 0x10; + addi r5, r1, 0xc; + addi r6, r1, 8; + mtctr r12; + bctrl; + lwz r4, 0xc(r1); + slwi r0, r17, 2; + srawi r0, r0, 3; + lwz r11, 8(r1); + lhz r9, 0x1a(r4); + addze r0, r0; + lhz r5, 0x1c(r4); + slwi r8, r0, 5; + lwz r6, 0x24(r4); + addi r23, r4, 0x2c; + mullw r10, r9, r5; + lwz r5, 0x14(r4); + add r22, r4, r6; + lhz r7, 0x12(r4); + lhz r6, 0x10(r4); + li r24, 0; + divw r12, r11, r10; + mullw r0, r12, r10; + subf r11, r0, r11; + divw r10, r11, r9; + mullw r9, r10, r9; + mullw r0, r12, r5; + subf r26, r9, r11; + srwi r0, r0, 1; + mullw r27, r10, r7; + add r22, r22, r0; + mullw r26, r26, r6; + b lbl_801a6360; +lbl_801a6218: + add r7, r27, r24; + slwi r0, r24, 0x1d; + srawi r5, r7, 3; + srwi r4, r24, 0x1f; + addze r9, r5; + slwi r6, r7, 0x1d; + srawi r5, r24, 3; + srwi r7, r7, 0x1f; + addze r5, r5; + subf r0, r4, r0; + mullw r5, r5, r8; + subf r6, r7, r6; + rotlwi r0, r0, 3; + rotlwi r6, r6, 3; + add r0, r0, r4; + add r4, r6, r7; + slwi r10, r4, 1; + add r11, r29, r5; + slwi r12, r0, 2; + li r25, 0; + b lbl_801a634c; +lbl_801a626c: + lhz r4, 0x1e(r4); + add r28, r30, r25; + add r5, r26, r25; + li r0, 0xf0; + rlwinm r6, r4, 2, 0, 0x1a; + rlwinm r4, r4, 3, 0x1f, 0x1f; + add r4, r4, r6; + slwi r20, r28, 0x1d; + srawi r7, r4, 1; + srwi r19, r28, 0x1f; + mullw r7, r7, r9; + slwi r6, r5, 0x1d; + srwi r4, r5, 0x1f; + srawi r18, r5, 3; + subf r6, r4, r6; + clrlwi r21, r28, 0x1f; + rotlwi r17, r6, 3; + addze r6, r18; + add r17, r17, r4; + subf r20, r19, r20; + xor r21, r21, r19; + slwi r5, r5, 0x1e; + srawi r18, r17, 2; + rotlwi r20, r20, 3; + subf r17, r4, r5; + subf. r21, r19, r21; + addze r5, r18; + add r20, r20, r19; + srawi r18, r28, 3; + rotlwi r17, r17, 2; + add r7, r22, r7; + slwi r6, r6, 4; + add r7, r7, r6; + addze r19, r18; + add r7, r7, r10; + srwi r21, r20, 0x1f; + add r28, r17, r4; + slwi r6, r19, 5; + add r4, r21, r20; + lbzx r7, r7, r5; + add r6, r11, r6; + slwi r21, r28, 1; + srawi r4, r4, 1; + subfic r5, r21, 6; + add r6, r6, r12; + sraw r5, r7, r5; + lbzx r7, r6, r4; + clrlwi r5, r5, 0x1e; + lbzx r5, r23, r5; + beq lbl_801a6338; + li r0, 0xf; +lbl_801a6338: + and r0, r5, r0; + addi r25, r25, 1; + clrlwi r0, r0, 0x18; + or r0, r7, r0; + stbx r0, r6, r4; +lbl_801a634c: + lwz r4, 0xc(r1); + lhz r0, 0x10(r4); + cmpw r25, r0; + blt lbl_801a626c; + addi r24, r24, 1; +lbl_801a6360: + lhz r0, 0x12(r4); + cmpw r24, r0; + blt lbl_801a6218; + cmpwi r31, 0; + beq lbl_801a638c; + lwz r5, 0xc(r1); + lwz r4, 8(r1); + lhz r0, 0x22(r5); + add r0, r5, r0; + lbzx r0, r4, r0; + stw r0, 0(r31); +lbl_801a638c: + addi r11, r1, 0x50; + bl _restgpr_17; + lwz r0, 0x54(r1); + mtlr r0; + addi r1, r1, 0x50; + blr; + // clang-format on +} + +// Symbol: OSGetFontTexture +// Function signature is unknown. +// PAL: 0x801a63a4..0x801a64f4 +MARK_BINARY_BLOB(OSGetFontTexture, 0x801a63a4, 0x801a64f4); +asm UNKNOWN_FUNCTION(OSGetFontTexture) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r7; + stw r30, 0x18(r1); + mr r30, r6; + stw r29, 0x14(r1); + mr r29, r5; + stw r28, 0x10(r1); + mr r28, r4; + lhz r0, -0x7130(r13); + cmplwi r0, 0xffff; + beq lbl_801a63e0; + b lbl_801a6424; +lbl_801a63e0: + lis r4, 0x8000; + lwz r0, 0xcc(r4); + cmpwi r0, 0; + beq lbl_801a63f8; + blt lbl_801a640c; + b lbl_801a640c; +lbl_801a63f8: + lis r4, 0xcc00; + lhz r0, 0x206e(r4); + rlwinm r0, r0, 0x1f, 0x1f, 0x1f; + sth r0, -0x7130(r13); + b lbl_801a6414; +lbl_801a640c: + li r0, 0; + sth r0, -0x7130(r13); +lbl_801a6414: + lis r4, 0x801a; + clrlwi r0, r0, 0x10; + addi r4, r4, 0x5e5c; + stw r4, -0x6328(r13); +lbl_801a6424: + lwz r12, -0x6328(r13); + mr r4, r3; + clrlwi r3, r0, 0x10; + addi r5, r1, 0xc; + addi r6, r1, 8; + mtctr r12; + bctrl; + lwz r7, 0xc(r1); + cmpwi r31, 0; + lwz r6, 8(r1); + lhz r5, 0x1a(r7); + lhz r4, 0x1c(r7); + lwz r0, 0x24(r7); + mullw r5, r5, r4; + lwz r4, 0x14(r7); + add r0, r7, r0; + divw r6, r6, r5; + mullw r4, r4, r6; + add r0, r4, r0; + stw r0, 0(r28); + lwz r8, 0xc(r1); + lwz r4, 8(r1); + lhz r7, 0x1a(r8); + lhz r5, 0x1c(r8); + mullw r6, r6, r7; + lhz r0, 0x10(r8); + mullw r5, r6, r5; + subf r5, r5, r4; + divw r6, r5, r7; + mullw r4, r6, r7; + subf r4, r4, r5; + mullw r0, r4, r0; + stw r0, 0(r29); + lwz r4, 0xc(r1); + lhz r0, 0x12(r4); + mullw r0, r6, r0; + stw r0, 0(r30); + beq lbl_801a64d4; + lwz r5, 0xc(r1); + lwz r4, 8(r1); + lhz r0, 0x22(r5); + add r0, r5, r0; + lbzx r0, r4, r0; + stw r0, 0(r31); +lbl_801a64d4: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSGetFontWidth +// Function signature is unknown. +// PAL: 0x801a64f4..0x801a65ac +MARK_BINARY_BLOB(OSGetFontWidth, 0x801a64f4, 0x801a65ac); +asm UNKNOWN_FUNCTION(OSGetFontWidth) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r4; + lhz r0, -0x7130(r13); + cmplwi r0, 0xffff; + beq lbl_801a6518; + b lbl_801a655c; +lbl_801a6518: + lis r4, 0x8000; + lwz r0, 0xcc(r4); + cmpwi r0, 0; + beq lbl_801a6530; + blt lbl_801a6544; + b lbl_801a6544; +lbl_801a6530: + lis r4, 0xcc00; + lhz r0, 0x206e(r4); + rlwinm r0, r0, 0x1f, 0x1f, 0x1f; + sth r0, -0x7130(r13); + b lbl_801a654c; +lbl_801a6544: + li r0, 0; + sth r0, -0x7130(r13); +lbl_801a654c: + lis r4, 0x801a; + clrlwi r0, r0, 0x10; + addi r4, r4, 0x5e5c; + stw r4, -0x6328(r13); +lbl_801a655c: + lwz r12, -0x6328(r13); + mr r4, r3; + clrlwi r3, r0, 0x10; + addi r5, r1, 0xc; + addi r6, r1, 8; + mtctr r12; + bctrl; + cmpwi r31, 0; + beq lbl_801a6598; + lwz r5, 0xc(r1); + lwz r4, 8(r1); + lhz r0, 0x22(r5); + add r0, r5, r0; + lbzx r0, r4, r0; + stw r0, 0(r31); +lbl_801a6598: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} diff --git a/source/rvl/os/osFont.h b/source/rvl/os/osFont.h new file mode 100644 index 000000000..beecae31e --- /dev/null +++ b/source/rvl/os/osFont.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a56dc..0x801a5810 +UNKNOWN_FUNCTION(GetFontCode); +// PAL: 0x801a5810..0x801a59b4 +UNKNOWN_FUNCTION(Decode); +// PAL: 0x801a59b4..0x801a5a34 +UNKNOWN_FUNCTION(OSSetFontEncode); +// PAL: 0x801a5a34..0x801a5d34 +UNKNOWN_FUNCTION(ReadFont); +// PAL: 0x801a5d34..0x801a5e5c +UNKNOWN_FUNCTION(OSLoadFont); +// PAL: 0x801a5e5c..0x801a5f58 +UNKNOWN_FUNCTION(ParseStringS); +// PAL: 0x801a5f58..0x801a6114 +UNKNOWN_FUNCTION(ParseStringW); +// PAL: 0x801a6114..0x801a63a4 +UNKNOWN_FUNCTION(OSGetFontTexel); +// PAL: 0x801a63a4..0x801a64f4 +UNKNOWN_FUNCTION(OSGetFontTexture); +// PAL: 0x801a64f4..0x801a65ac +UNKNOWN_FUNCTION(OSGetFontWidth); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osInterrupt.c b/source/rvl/os/osInterrupt.c index 211d71e5d..77985e6c6 100644 --- a/source/rvl/os/osInterrupt.c +++ b/source/rvl/os/osInterrupt.c @@ -6,8 +6,6 @@ #include "osException.h" // Extern function references. -// PAL: 0x801a1f58 -extern void OSLoadContext(OSContext* context); // PAL: 0x801a98e8 extern UNKNOWN_FUNCTION(OSDisableScheduler); // PAL: 0x801a9924 diff --git a/source/rvl/os/osInterrupt.h b/source/rvl/os/osInterrupt.h index ae9b90170..6d58845c8 100644 --- a/source/rvl/os/osInterrupt.h +++ b/source/rvl/os/osInterrupt.h @@ -3,98 +3,12 @@ #include #include -#include "osThread.h" +#include "osContext.h" #ifdef __cplusplus extern "C" { #endif -#define __OS_INTERRUPT_MEM_0 0 -#define __OS_INTERRUPT_MEM_1 1 -#define __OS_INTERRUPT_MEM_2 2 -#define __OS_INTERRUPT_MEM_3 3 -#define __OS_INTERRUPT_MEM_ADDRESS 4 -#define __OS_INTERRUPT_DSP_AI 5 -#define __OS_INTERRUPT_DSP_ARAM 6 -#define __OS_INTERRUPT_DSP_DSP 7 -#define __OS_INTERRUPT_AI_AI 8 -#define __OS_INTERRUPT_EXI_0_EXI 9 -#define __OS_INTERRUPT_EXI_0_TC 10 -#define __OS_INTERRUPT_EXI_0_EXT 11 -#define __OS_INTERRUPT_EXI_1_EXI 12 -#define __OS_INTERRUPT_EXI_1_TC 13 -#define __OS_INTERRUPT_EXI_1_EXT 14 -#define __OS_INTERRUPT_EXI_2_EXI 15 -#define __OS_INTERRUPT_EXI_2_TC 16 -#define __OS_INTERRUPT_PI_CP 17 -#define __OS_INTERRUPT_PI_PE_TOKEN 18 -#define __OS_INTERRUPT_PI_PE_FINISH 19 -#define __OS_INTERRUPT_PI_SI 20 -#define __OS_INTERRUPT_PI_DI 21 -#define __OS_INTERRUPT_PI_RSW 22 -#define __OS_INTERRUPT_PI_ERROR 23 -#define __OS_INTERRUPT_PI_VI 24 -#define __OS_INTERRUPT_PI_DEBUG 25 -#define __OS_INTERRUPT_PI_HSP 26 -#define __OS_INTERRUPT_PI_ACR 27 -#define __OS_INTERRUPT_MAX 32 - -#define OS_INTERRUPTMASK_MEM_0 (0x80000000u >> __OS_INTERRUPT_MEM_0) -#define OS_INTERRUPTMASK_MEM_1 (0x80000000u >> __OS_INTERRUPT_MEM_1) -#define OS_INTERRUPTMASK_MEM_2 (0x80000000u >> __OS_INTERRUPT_MEM_2) -#define OS_INTERRUPTMASK_MEM_3 (0x80000000u >> __OS_INTERRUPT_MEM_3) -#define OS_INTERRUPTMASK_MEM_ADDRESS (0x80000000u >> __OS_INTERRUPT_MEM_ADDRESS) -#define OS_INTERRUPTMASK_MEM \ - (OS_INTERRUPTMASK_MEM_0 | OS_INTERRUPTMASK_MEM_1 | OS_INTERRUPTMASK_MEM_2 | \ - OS_INTERRUPTMASK_MEM_3 | OS_INTERRUPTMASK_MEM_ADDRESS) -#define OS_INTERRUPTMASK_DSP_AI (0x80000000u >> __OS_INTERRUPT_DSP_AI) -#define OS_INTERRUPTMASK_DSP_ARAM (0x80000000u >> __OS_INTERRUPT_DSP_ARAM) -#define OS_INTERRUPTMASK_DSP_DSP (0x80000000u >> __OS_INTERRUPT_DSP_DSP) -#define OS_INTERRUPTMASK_DSP \ - (OS_INTERRUPTMASK_DSP_AI | OS_INTERRUPTMASK_DSP_ARAM | \ - OS_INTERRUPTMASK_DSP_DSP) -#define OS_INTERRUPTMASK_AI_AI (0x80000000u >> __OS_INTERRUPT_AI_AI) -#define OS_INTERRUPTMASK_AI (OS_INTERRUPTMASK_AI_AI) -#define OS_INTERRUPTMASK_EXI_0_EXI (0x80000000u >> __OS_INTERRUPT_EXI_0_EXI) -#define OS_INTERRUPTMASK_EXI_0_TC (0x80000000u >> __OS_INTERRUPT_EXI_0_TC) -#define OS_INTERRUPTMASK_EXI_0_EXT (0x80000000u >> __OS_INTERRUPT_EXI_0_EXT) -#define OS_INTERRUPTMASK_EXI_0 \ - (OS_INTERRUPTMASK_EXI_0_EXI | OS_INTERRUPTMASK_EXI_0_TC | \ - OS_INTERRUPTMASK_EXI_0_EXT) -#define OS_INTERRUPTMASK_EXI_1_EXI (0x80000000u >> __OS_INTERRUPT_EXI_1_EXI) -#define OS_INTERRUPTMASK_EXI_1_TC (0x80000000u >> __OS_INTERRUPT_EXI_1_TC) -#define OS_INTERRUPTMASK_EXI_1_EXT (0x80000000u >> __OS_INTERRUPT_EXI_1_EXT) -#define OS_INTERRUPTMASK_EXI_1 \ - (OS_INTERRUPTMASK_EXI_1_EXI | OS_INTERRUPTMASK_EXI_1_TC | \ - OS_INTERRUPTMASK_EXI_1_EXT) -#define OS_INTERRUPTMASK_EXI_2_EXI (0x80000000u >> __OS_INTERRUPT_EXI_2_EXI) -#define OS_INTERRUPTMASK_EXI_2_TC (0x80000000u >> __OS_INTERRUPT_EXI_2_TC) -#define OS_INTERRUPTMASK_EXI \ - (OS_INTERRUPTMASK_EXI_0_EXI | OS_INTERRUPTMASK_EXI_0_TC | \ - OS_INTERRUPTMASK_EXI_0_EXT | OS_INTERRUPTMASK_EXI_1_EXI | \ - OS_INTERRUPTMASK_EXI_1_TC | OS_INTERRUPTMASK_EXI_1_EXT | \ - OS_INTERRUPTMASK_EXI_2_EXI | OS_INTERRUPTMASK_EXI_2_TC) -#define OS_INTERRUPTMASK_PI_PE_TOKEN (0x80000000u >> __OS_INTERRUPT_PI_PE_TOKEN) -#define OS_INTERRUPTMASK_PI_PE_FINISH \ - (0x80000000u >> __OS_INTERRUPT_PI_PE_FINISH) -#define OS_INTERRUPTMASK_PI_PE \ - (OS_INTERRUPTMASK_PI_PE_TOKEN | OS_INTERRUPTMASK_PI_PE_FINISH) -#define OS_INTERRUPTMASK_PI_CP (0x80000000u >> __OS_INTERRUPT_PI_CP) -#define OS_INTERRUPTMASK_PI_SI (0x80000000u >> __OS_INTERRUPT_PI_SI) -#define OS_INTERRUPTMASK_PI_DI (0x80000000u >> __OS_INTERRUPT_PI_DI) -#define OS_INTERRUPTMASK_PI_RSW (0x80000000u >> __OS_INTERRUPT_PI_RSW) -#define OS_INTERRUPTMASK_PI_ERROR (0x80000000u >> __OS_INTERRUPT_PI_ERROR) -#define OS_INTERRUPTMASK_PI_VI (0x80000000u >> __OS_INTERRUPT_PI_VI) -#define OS_INTERRUPTMASK_PI_DEBUG (0x80000000u >> __OS_INTERRUPT_PI_DEBUG) -#define OS_INTERRUPTMASK_PI_HSP (0x80000000u >> __OS_INTERRUPT_PI_HSP) -#define OS_INTERRUPTMASK_PI_ACR (0x80000000u >> __OS_INTERRUPT_PI_ACR) -#define OS_INTERRUPTMASK_PI \ - (OS_INTERRUPTMASK_PI_CP | OS_INTERRUPTMASK_PI_SI | OS_INTERRUPTMASK_PI_DI | \ - OS_INTERRUPTMASK_PI_RSW | OS_INTERRUPTMASK_PI_ERROR | \ - OS_INTERRUPTMASK_PI_VI | OS_INTERRUPTMASK_PI_PE_TOKEN | \ - OS_INTERRUPTMASK_PI_PE_FINISH | OS_INTERRUPTMASK_PI_DEBUG | \ - OS_INTERRUPTMASK_PI_HSP | OS_INTERRUPTMASK_PI_ACR) - typedef void (*__OSInterruptHandler)(s16 interrupt, OSContext* context); // PAL: 0x801a65ac..0x801a65c0 diff --git a/source/rvl/os/osLink.c b/source/rvl/os/osLink.c new file mode 100644 index 000000000..f9eee1d4f --- /dev/null +++ b/source/rvl/os/osLink.c @@ -0,0 +1,462 @@ +#include "osLink.h" + +#include + +#include "osCache.h" +#include "osError.h" + +// Symbol: OSNotifyLink +// PAL: 0x801a6d30..0x801a6d34 +void OSNotifyLink(OSModuleInfo*) {} + +// Symbol: OSNotifyPreLink +// PAL: 0x801a6d34..0x801a6d38 +void OSNotifyPreLink(OSModuleInfo*) {} + +// Symbol: OSNotifyPostLink +// PAL: 0x801a6d38..0x801a6d3c +void OSNotifyPostLink(OSModuleHeader*, OSModuleHeader*) {} + +// Symbol: Relocate +// PAL: 0x801a6d3c..0x801a6ffc +MARK_BINARY_BLOB(Relocate, 0x801a6d3c, 0x801a6ffc); +asm int Relocate(OSModuleHeader*, OSModuleHeader*) { + // clang-format off + nofralloc; + stwu r1, -0x30(r1); + mflr r0; + stw r0, 0x34(r1); + addi r11, r1, 0x30; + bl _savegpr_23; + cmpwi r3, 0; + mr r26, r3; + mr r27, r4; + beq lbl_801a6d68; + lwz r31, 0(r3); + b lbl_801a6d6c; +lbl_801a6d68: + li r31, 0; +lbl_801a6d6c: + lwz r24, 0x28(r4); + lwz r0, 0x2c(r4); + add r3, r24, r0; + addi r0, r3, 7; + subf r0, r24, r0; + srwi r0, r0, 3; + mtctr r0; + cmplw r24, r3; + bge lbl_801a6da4; +lbl_801a6d90: + lwz r0, 0(r24); + cmplw r0, r31; + beq lbl_801a6dac; + addi r24, r24, 8; + bdnz lbl_801a6d90; +lbl_801a6da4: + li r3, 0; + b lbl_801a6fe4; +lbl_801a6dac: + mr r3, r26; + mr r4, r27; + bl OSNotifyPreLink; + lwz r30, 4(r24); + li r29, 0; + lis r25, 0x8029; + b lbl_801a6fa0; +lbl_801a6dc8: + lhz r0, 0(r30); + cmpwi r31, 0; + add r28, r28, r0; + beq lbl_801a6df0; + lbz r0, 3(r30); + lwz r3, 0x10(r26); + slwi r0, r0, 3; + lwzx r0, r3, r0; + rlwinm r3, r0, 0, 0, 0x1e; + b lbl_801a6df4; +lbl_801a6df0: + li r3, 0; +lbl_801a6df4: + cmpwi r4, 6; + beq lbl_801a6ebc; + bge lbl_801a6e2c; + cmpwi r4, 2; + beq lbl_801a6e6c; + bge lbl_801a6e1c; + cmpwi r4, 0; + beq lbl_801a6f9c; + bge lbl_801a6e5c; + b lbl_801a6f90; +lbl_801a6e1c: + cmpwi r4, 4; + beq lbl_801a6e98; + bge lbl_801a6ea8; + b lbl_801a6e88; +lbl_801a6e2c: + cmpwi r4, 0xc9; + beq lbl_801a6f9c; + bge lbl_801a6e50; + cmpwi r4, 0xa; + beq lbl_801a6ef4; + blt lbl_801a6ed8; + cmpwi r4, 0xe; + bge lbl_801a6f90; + b lbl_801a6f14; +lbl_801a6e50: + cmpwi r4, 0xcb; + bge lbl_801a6f90; + b lbl_801a6f34; +lbl_801a6e5c: + lwz r0, 4(r30); + add r0, r3, r0; + stw r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6e6c: + lwz r0, 4(r30); + lwz r4, 0(r28); + add r3, r3, r0; + rlwinm r0, r4, 0, 0x1e, 5; + rlwimi r0, r3, 0, 6, 0x1d; + stw r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6e88: + lwz r0, 4(r30); + add r0, r3, r0; + sth r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6e98: + lwz r0, 4(r30); + add r0, r3, r0; + sth r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6ea8: + lwz r0, 4(r30); + add r0, r3, r0; + srwi r0, r0, 0x10; + sth r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6ebc: + lwz r0, 4(r30); + add r0, r3, r0; + srwi r3, r0, 0x10; + rlwinm r0, r0, 0x11, 0x1f, 0x1f; + add r0, r3, r0; + sth r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6ed8: + lwz r0, 4(r30); + lwz r4, 0(r28); + add r3, r3, r0; + rlwinm r0, r4, 0, 0x1e, 0xf; + rlwimi r0, r3, 0, 0x10, 0x1d; + stw r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6ef4: + lwz r0, 4(r30); + lwz r4, 0(r28); + add r0, r3, r0; + subf r3, r28, r0; + rlwinm r0, r4, 0, 0x1e, 5; + rlwimi r0, r3, 0, 6, 0x1d; + stw r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6f14: + lwz r0, 4(r30); + lwz r4, 0(r28); + add r0, r3, r0; + subf r3, r28, r0; + rlwinm r0, r4, 0, 0x1e, 0xf; + rlwimi r0, r3, 0, 0x10, 0x1d; + stw r0, 0(r28); + b lbl_801a6f9c; +lbl_801a6f34: + lbz r0, 3(r30); + cmpwi r29, 0; + lwz r3, 0x10(r27); + slwi r0, r0, 3; + add r23, r3, r0; + lwzx r0, r3, r0; + rlwinm r28, r0, 0, 0, 0x1e; + beq lbl_801a6f74; + lwz r0, 0(r29); + lwz r4, 4(r29); + rlwinm r24, r0, 0, 0, 0x1e; + mr r3, r24; + bl DCFlushRange; + lwz r4, 4(r29); + mr r3, r24; + bl ICInvalidateRange; +lbl_801a6f74: + lwz r0, 0(r23); + clrlwi. r0, r0, 0x1f; + beq lbl_801a6f88; + mr r29, r23; + b lbl_801a6f9c; +lbl_801a6f88: + li r29, 0; + b lbl_801a6f9c; +lbl_801a6f90: + addi r3, r25, 0x630; + crclr 6; + bl OSReport; +lbl_801a6f9c: + addi r30, r30, 8; +lbl_801a6fa0: + lbz r4, 2(r30); + cmplwi r4, 0xcb; + bne lbl_801a6dc8; + cmpwi r29, 0; + beq lbl_801a6fd4; + lwz r0, 0(r29); + lwz r4, 4(r29); + rlwinm r25, r0, 0, 0, 0x1e; + mr r3, r25; + bl DCFlushRange; + lwz r4, 4(r29); + mr r3, r25; + bl ICInvalidateRange; +lbl_801a6fd4: + mr r3, r26; + mr r4, r27; + bl OSNotifyPostLink; + li r3, 1; +lbl_801a6fe4: + addi r11, r1, 0x30; + bl _restgpr_23; + lwz r0, 0x34(r1); + mtlr r0; + addi r1, r1, 0x30; + blr; + // clang-format on +} + +// Symbol: Link +// PAL: 0x801a6ffc..0x801a72dc +MARK_BINARY_BLOB(Link, 0x801a6ffc, 0x801a72dc); +asm int Link(OSModuleInfo* info, void* bss, int fixed) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + li r0, 0; + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + stw r28, 0x10(r1); + lwz r6, 0x1c(r3); + stb r0, 0x33(r3); + cmplwi r6, 3; + bgt lbl_801a7078; + cmplwi r6, 2; + blt lbl_801a7080; + lwz r5, 0x40(r3); + cmpwi r5, 0; + beq lbl_801a705c; + divwu r0, r3, r5; + mullw r0, r0, r5; + subf. r0, r0, r3; + bne lbl_801a7078; +lbl_801a705c: + lwz r5, 0x44(r3); + cmpwi r5, 0; + beq lbl_801a7080; + divwu r0, r4, r5; + mullw r0, r0, r5; + subf. r0, r0, r4; + beq lbl_801a7080; +lbl_801a7078: + li r3, 0; + b lbl_801a72bc; +lbl_801a7080: + lis r5, 0x8000; + lwz r6, 0x30cc(r5); + cmpwi r6, 0; + bne lbl_801a7098; + stw r3, 0x30c8(r5); + b lbl_801a709c; +lbl_801a7098: + stw r3, 4(r6); +lbl_801a709c: + li r0, 0; + stw r6, 8(r3); + lis r5, 0x8000; + stw r0, 4(r3); + stw r3, 0x30cc(r5); + lwz r0, 0x10(r3); + lwz r6, 0x24(r3); + add r7, r0, r3; + lwz r5, 0x28(r3); + lwz r0, 0x1c(r3); + add r6, r6, r3; + add r5, r5, r3; + stw r7, 0x10(r3); + cmplwi r0, 3; + stw r6, 0x24(r3); + stw r5, 0x28(r3); + blt lbl_801a70ec; + lwz r0, 0x48(r3); + add r0, r0, r3; + stw r0, 0x48(r3); +lbl_801a70ec: + li r6, 1; + li r5, 8; + b lbl_801a7134; +lbl_801a70f8: + lwz r0, 0x10(r3); + add r7, r0, r5; + lwzx r0, r5, r0; + cmpwi r0, 0; + beq lbl_801a7118; + add r0, r0, r3; + stw r0, 0(r7); + b lbl_801a712c; +lbl_801a7118: + lwz r0, 4(r7); + cmpwi r0, 0; + beq lbl_801a712c; + stb r6, 0x33(r3); + stw r4, 0(r7); +lbl_801a712c: + addi r5, r5, 8; + addi r6, r6, 1; +lbl_801a7134: + lwz r0, 0xc(r3); + cmplw r6, r0; + blt lbl_801a70f8; + lwz r5, 0x28(r3); + b lbl_801a7158; +lbl_801a7148: + lwz r0, 4(r5); + add r0, r0, r3; + stw r0, 4(r5); + addi r5, r5, 8; +lbl_801a7158: + lwz r4, 0x28(r3); + lwz r0, 0x2c(r3); + add r0, r4, r0; + cmplw r5, r0; + blt lbl_801a7148; + lbz r0, 0x30(r3); + cmpwi r0, 0; + beq lbl_801a7194; + lwz r4, 0x10(r3); + rlwinm r0, r0, 3, 0x15, 0x1c; + lwz r5, 0x34(r3); + lwzx r0, r4, r0; + rlwinm r0, r0, 0, 0, 0x1e; + add r0, r5, r0; + stw r0, 0x34(r3); +lbl_801a7194: + lbz r0, 0x31(r3); + cmpwi r0, 0; + beq lbl_801a71bc; + lwz r4, 0x10(r3); + rlwinm r0, r0, 3, 0x15, 0x1c; + lwz r5, 0x38(r3); + lwzx r0, r4, r0; + rlwinm r0, r0, 0, 0, 0x1e; + add r0, r5, r0; + stw r0, 0x38(r3); +lbl_801a71bc: + lbz r0, 0x32(r3); + cmpwi r0, 0; + beq lbl_801a71e4; + lwz r4, 0x10(r3); + rlwinm r0, r0, 3, 0x15, 0x1c; + lwz r5, 0x3c(r3); + lwzx r0, r4, r0; + rlwinm r0, r0, 0, 0, 0x1e; + add r0, r5, r0; + stw r0, 0x3c(r3); +lbl_801a71e4: + lis r4, 0x8000; + lwz r4, 0x30d0(r4); + cmpwi r4, 0; + beq lbl_801a7200; + lwz r0, 0x14(r3); + add r0, r0, r4; + stw r0, 0x14(r3); +lbl_801a7200: + mr r4, r29; + li r3, 0; + bl Relocate; + lis r3, 0x8000; + lwz r28, 0x30c8(r3); + b lbl_801a723c; +lbl_801a7218: + mr r3, r29; + mr r4, r28; + bl Relocate; + cmplw r28, r29; + beq lbl_801a7238; + mr r3, r28; + mr r4, r29; + bl Relocate; +lbl_801a7238: + lwz r28, 4(r28); +lbl_801a723c: + cmpwi r28, 0; + bne lbl_801a7218; + cmpwi r31, 0; + beq lbl_801a72a0; + lwz r4, 0x28(r29); + lwz r0, 0x2c(r29); + mr r5, r4; + add r3, r4, r0; + addi r0, r3, 7; + subf r0, r4, r0; + srwi r0, r0, 3; + mtctr r0; + cmplw r4, r3; + bge lbl_801a72a0; +lbl_801a7274: + lwz r3, 0(r5); + cmpwi r3, 0; + beq lbl_801a728c; + lwz r0, 0(r29); + cmplw r3, r0; + bne lbl_801a7298; +lbl_801a728c: + subf r0, r4, r5; + stw r0, 0x2c(r29); + b lbl_801a72a0; +lbl_801a7298: + addi r5, r5, 8; + bdnz lbl_801a7274; +lbl_801a72a0: + lwz r5, 0x20(r29); + mr r3, r30; + li r4, 0; + bl memset; + mr r3, r29; + bl OSNotifyLink; + li r3, 1; +lbl_801a72bc: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSLink +// PAL: 0x801a72dc..0x801a72e4 +int OSLink(OSModuleInfo* info, void* bss) { return Link(info, bss, 0); } + +OSModuleQueue __OSModuleInfoList : 0x800030c8; +const void* __OSStringTable : 0x800030d0; + +// Symbol: __OSModuleInit +// PAL: 0x801a72e4..0x801a72fc +void __OSModuleInit(void) { + __OSModuleInfoList.head = __OSModuleInfoList.tail = 0; + __OSStringTable = 0; +} diff --git a/source/rvl/os/osLink.h b/source/rvl/os/osLink.h new file mode 100644 index 000000000..02053083f --- /dev/null +++ b/source/rvl/os/osLink.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct OSModuleInfo OSModuleInfo; + +typedef struct OSModuleLink { + OSModuleInfo* succ; + OSModuleInfo* pred; +} OSModuleLink; + +typedef struct OSModuleQueue { + OSModuleInfo* head; + OSModuleInfo* tail; +} OSModuleQueue; + +struct OSModuleInfo { + u32 id; + OSModuleLink head; + u32 sections; + u32 sectionInfoOffset; + u32 nameOffset; + u32 nameLen; + u32 version; +}; + +typedef struct OSModuleHeader { + OSModuleInfo info; + u32 bssSize; + u32 relOffset; + u32 impOffset; + u32 impSize; + u8 prologSection; + u8 epilogSection; + u8 unresolvedSection; + u8 bssSection; + u32 prolog; + u32 epilog; + u32 unresolved; + u32 align; + u32 bssAlign; + u32 fixSize; +} OSModuleHeader; + +// PAL: 0x801a6d30..0x801a6d34 +void OSNotifyLink(OSModuleInfo*); +// PAL: 0x801a6d34..0x801a6d38 +void OSNotifyPreLink(OSModuleInfo*); +// PAL: 0x801a6d38..0x801a6d3c +void OSNotifyPostLink(OSModuleHeader*, OSModuleHeader*); +// PAL: 0x801a6d3c..0x801a6ffc +int Relocate(OSModuleHeader*, OSModuleHeader*); +// PAL: 0x801a6ffc..0x801a72dc +int Link(OSModuleInfo* info, void* bss, int fixed); +// PAL: 0x801a72dc..0x801a72e4 +int OSLink(OSModuleInfo* info, void* bss); +// PAL: 0x801a72e4..0x801a72fc +void __OSModuleInit(void); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osMemory.c b/source/rvl/os/osMemory.c new file mode 100644 index 000000000..e5bda51cd --- /dev/null +++ b/source/rvl/os/osMemory.c @@ -0,0 +1,785 @@ +#include "osMemory.h" + +// Extern function references. +// PAL: 0x8002159c +extern UNKNOWN_FUNCTION(_savegpr_26); +// PAL: 0x800215e8 +extern UNKNOWN_FUNCTION(_restgpr_26); +// PAL: 0x801a1600 +extern UNKNOWN_FUNCTION(DCInvalidateRange); +// PAL: 0x801a162c +extern UNKNOWN_FUNCTION(DCFlushRange); +// PAL: 0x801a2a14 +extern UNKNOWN_FUNCTION(__OSUnhandledException); +// PAL: 0x801a65ac +extern UNKNOWN_FUNCTION(OSDisableInterrupts); +// PAL: 0x801a65d4 +extern UNKNOWN_FUNCTION(OSRestoreInterrupts); +// PAL: 0x801a65f8 +extern UNKNOWN_FUNCTION(__OSSetInterruptHandler); +// PAL: 0x801a693c +extern UNKNOWN_FUNCTION(__OSMaskInterrupts); +// PAL: 0x801a69bc +extern UNKNOWN_FUNCTION(__OSUnmaskInterrupts); +// PAL: 0x801a8238 +extern UNKNOWN_FUNCTION(OSRegisterShutdownFunction); + +// Symbol: OSGetPhysicalMem1Size +// Function signature is unknown. +// PAL: 0x801a75d0..0x801a75dc +MARK_BINARY_BLOB(OSGetPhysicalMem1Size, 0x801a75d0, 0x801a75dc); +asm UNKNOWN_FUNCTION(OSGetPhysicalMem1Size) { + // clang-format off + nofralloc; + lis r3, 0x8000; + lwz r3, 0x3100(r3); + blr; + // clang-format on +} + +// Symbol: OSGetPhysicalMem2Size +// Function signature is unknown. +// PAL: 0x801a75dc..0x801a75e8 +MARK_BINARY_BLOB(OSGetPhysicalMem2Size, 0x801a75dc, 0x801a75e8); +asm UNKNOWN_FUNCTION(OSGetPhysicalMem2Size) { + // clang-format off + nofralloc; + lis r3, 0x8000; + lwz r3, 0x3118(r3); + blr; + // clang-format on +} + +// Symbol: OSGetConsoleSimulatedMem1Size +// Function signature is unknown. +// PAL: 0x801a75e8..0x801a75f4 +MARK_BINARY_BLOB(OSGetConsoleSimulatedMem1Size, 0x801a75e8, 0x801a75f4); +asm UNKNOWN_FUNCTION(OSGetConsoleSimulatedMem1Size) { + // clang-format off + nofralloc; + lis r3, 0x8000; + lwz r3, 0x3104(r3); + blr; + // clang-format on +} + +// Symbol: OSGetConsoleSimulatedMem2Size +// Function signature is unknown. +// PAL: 0x801a75f4..0x801a7600 +MARK_BINARY_BLOB(OSGetConsoleSimulatedMem2Size, 0x801a75f4, 0x801a7600); +asm UNKNOWN_FUNCTION(OSGetConsoleSimulatedMem2Size) { + // clang-format off + nofralloc; + lis r3, 0x8000; + lwz r3, 0x311c(r3); + blr; + // clang-format on +} + +// Symbol: OnShutdown +// Function signature is unknown. +// PAL: 0x801a7600..0x801a763c +MARK_BINARY_BLOB(OnShutdown, 0x801a7600, 0x801a763c); +asm UNKNOWN_FUNCTION(OnShutdown) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x14(r1); + beq lbl_801a7628; + li r0, 0xff; + lis r3, 0xcc00; + sth r0, 0x4010(r3); + lis r3, 0xf000; + bl __OSMaskInterrupts; +lbl_801a7628: + lwz r0, 0x14(r1); + li r3, 1; + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: MEMIntrruptHandler +// Function signature is unknown. +// PAL: 0x801a763c..0x801a7684 +MARK_BINARY_BLOB(MEMIntrruptHandler, 0x801a763c, 0x801a7684); +asm UNKNOWN_FUNCTION(MEMIntrruptHandler) { + // clang-format off + nofralloc; + lis r8, 0xcc00; + lis r3, 0x8034; + lhz r5, 0x401e(r8); + li r0, 0; + lhz r7, 0x4024(r8); + addi r3, r3, 0x70f0; + lhz r6, 0x4022(r8); + rlwimi r6, r7, 0x10, 6, 0xf; + sth r0, 0x4020(r8); + lwz r12, 0x3c(r3); + cmpwi r12, 0; + beq lbl_801a767c; + li r3, 0xf; + crclr 6; + mtctr r12; + bctr; +lbl_801a767c: + li r3, 0xf; + b __OSUnhandledException; + // clang-format on +} + +// Symbol: OSProtectRange +// Function signature is unknown. +// PAL: 0x801a7684..0x801a774c +MARK_BINARY_BLOB(OSProtectRange, 0x801a7684, 0x801a774c); +asm UNKNOWN_FUNCTION(OSProtectRange) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + addi r11, r1, 0x20; + bl _savegpr_26; + cmplwi r3, 4; + mr r26, r3; + bge lbl_801a7734; + add r3, r4, r5; + rlwinm r28, r4, 0, 0, 0x15; + addi r0, r3, 0x3ff; + clrlwi r27, r6, 0x1e; + rlwinm r30, r0, 0, 0, 0x15; + mr r3, r28; + subf r4, r28, r30; + bl DCFlushRange; + bl OSDisableInterrupts; + lis r0, 0x8000; + mr r29, r3; + srw r31, r0, r26; + mr r3, r31; + bl __OSMaskInterrupts; + lis r5, 0xcc00; + slwi r0, r26, 2; + add r4, r5, r0; + rlwinm r3, r30, 0x16, 0x10, 0x1f; + rlwinm r0, r28, 0x16, 0x10, 0x1f; + slwi r6, r26, 1; + sth r0, 0x4000(r4); + li r0, 3; + cmplwi r27, 3; + sth r3, 0x4002(r4); + slw r3, r0, r6; + slw r0, r27, r6; + lhz r4, 0x4010(r5); + andc r3, r4, r3; + clrlwi r3, r3, 0x10; + or r0, r3, r0; + sth r0, 0x4010(r5); + beq lbl_801a772c; + mr r3, r31; + bl __OSUnmaskInterrupts; +lbl_801a772c: + mr r3, r29; + bl OSRestoreInterrupts; +lbl_801a7734: + addi r11, r1, 0x20; + bl _restgpr_26; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: ConfigMEM1_24MB +// Function signature is unknown. +// PAL: 0x801a774c..0x801a77cc +MARK_BINARY_BLOB(ConfigMEM1_24MB, 0x801a774c, 0x801a77cc); +asm UNKNOWN_FUNCTION(ConfigMEM1_24MB) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0; + addi r4, r4, 2; + lis r3, 0x8000; + addi r3, r3, 0x1ff; + lis r6, 0x100; + addi r6, r6, 2; + lis r5, 0x8100; + addi r5, r5, 0xff; + isync; + mtdbatu 0, r7; + mtdbatl 0, r4; + mtdbatu 0, r3; + isync; + mtibatu 0, r7; + mtibatl 0, r4; + mtibatu 0, r3; + isync; + mtdbatu 2, r7; + mtdbatl 2, r6; + mtdbatu 2, r5; + isync; + mtibatu 2, r7; + mtibatl 2, r6; + mtibatu 2, r5; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: ConfigMEM1_48MB +// Function signature is unknown. +// PAL: 0x801a77cc..0x801a784c +MARK_BINARY_BLOB(ConfigMEM1_48MB, 0x801a77cc, 0x801a784c); +asm UNKNOWN_FUNCTION(ConfigMEM1_48MB) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0; + addi r4, r4, 2; + lis r3, 0x8000; + addi r3, r3, 0x3ff; + lis r6, 0x200; + addi r6, r6, 2; + lis r5, 0x8200; + addi r5, r5, 0x1ff; + isync; + mtdbatu 0, r7; + mtdbatl 0, r4; + mtdbatu 0, r3; + isync; + mtibatu 0, r7; + mtibatl 0, r4; + mtibatu 0, r3; + isync; + mtdbatu 2, r7; + mtdbatl 2, r6; + mtdbatu 2, r5; + isync; + mtibatu 2, r7; + mtibatl 2, r6; + mtibatu 2, r5; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: ConfigMEM2_52MB +// Function signature is unknown. +// PAL: 0x801a784c..0x801a792c +MARK_BINARY_BLOB(ConfigMEM2_52MB, 0x801a784c, 0x801a792c); +asm UNKNOWN_FUNCTION(ConfigMEM2_52MB) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0x1000; + addi r4, r4, 2; + lis r3, 0x9000; + addi r3, r3, 0x3ff; + lis r6, 0x1000; + addi r6, r6, 0x2a; + lis r5, 0xd000; + addi r5, r5, 0x7ff; + isync; + mtspr 0x238, r7; + mtspr 0x239, r4; + mtspr 0x238, r3; + isync; + mtspr 0x230, r7; + mtspr 0x231, r4; + mtspr 0x230, r3; + isync; + mtspr 0x23a, r7; + mtspr 0x23b, r6; + mtspr 0x23a, r5; + isync; + mtspr 0x232, r7; + mtspr 0x233, r7; + isync; + lis r4, 0x1200; + addi r4, r4, 2; + lis r3, 0x9200; + addi r3, r3, 0x1ff; + lis r6, 0x1300; + addi r6, r6, 2; + lis r5, 0x9300; + addi r5, r5, 0x7f; + isync; + mtspr 0x23c, r7; + mtspr 0x23d, r4; + mtspr 0x23c, r3; + isync; + mtspr 0x234, r7; + mtspr 0x235, r4; + mtspr 0x234, r3; + isync; + mtspr 0x23e, r7; + mtspr 0x23f, r6; + mtspr 0x23e, r5; + isync; + mtspr 0x236, r7; + mtspr 0x237, r6; + mtspr 0x236, r5; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: ConfigMEM2_56MB +// Function signature is unknown. +// PAL: 0x801a792c..0x801a7a0c +MARK_BINARY_BLOB(ConfigMEM2_56MB, 0x801a792c, 0x801a7a0c); +asm UNKNOWN_FUNCTION(ConfigMEM2_56MB) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0x1000; + addi r4, r4, 2; + lis r3, 0x9000; + addi r3, r3, 0x3ff; + lis r6, 0x1000; + addi r6, r6, 0x2a; + lis r5, 0xd000; + addi r5, r5, 0x7ff; + isync; + mtspr 0x238, r7; + mtspr 0x239, r4; + mtspr 0x238, r3; + isync; + mtspr 0x230, r7; + mtspr 0x231, r4; + mtspr 0x230, r3; + isync; + mtspr 0x23a, r7; + mtspr 0x23b, r6; + mtspr 0x23a, r5; + isync; + mtspr 0x232, r7; + mtspr 0x233, r7; + isync; + lis r4, 0x1200; + addi r4, r4, 2; + lis r3, 0x9200; + addi r3, r3, 0x1ff; + lis r6, 0x1300; + addi r6, r6, 2; + lis r5, 0x9300; + addi r5, r5, 0xff; + isync; + mtspr 0x23c, r7; + mtspr 0x23d, r4; + mtspr 0x23c, r3; + isync; + mtspr 0x234, r7; + mtspr 0x235, r4; + mtspr 0x234, r3; + isync; + mtspr 0x23e, r7; + mtspr 0x23f, r6; + mtspr 0x23e, r5; + isync; + mtspr 0x236, r7; + mtspr 0x237, r6; + mtspr 0x236, r5; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: ConfigMEM2_64MB +// Function signature is unknown. +// PAL: 0x801a7a0c..0x801a7ab8 +MARK_BINARY_BLOB(ConfigMEM2_64MB, 0x801a7a0c, 0x801a7ab8); +asm UNKNOWN_FUNCTION(ConfigMEM2_64MB) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0x1000; + addi r4, r4, 2; + lis r3, 0x9000; + addi r3, r3, 0x7ff; + lis r6, 0x1000; + addi r6, r6, 0x2a; + lis r5, 0xd000; + addi r5, r5, 0x7ff; + isync; + mtspr 0x238, r7; + mtspr 0x239, r4; + mtspr 0x238, r3; + isync; + mtspr 0x230, r7; + mtspr 0x231, r4; + mtspr 0x230, r3; + isync; + mtspr 0x23a, r7; + mtspr 0x23b, r6; + mtspr 0x23a, r5; + isync; + mtspr 0x232, r7; + mtspr 0x233, r7; + isync; + mtspr 0x234, r7; + mtspr 0x235, r7; + isync; + mtspr 0x236, r7; + mtspr 0x237, r7; + isync; + mtspr 0x23c, r7; + mtspr 0x23d, r7; + isync; + mtspr 0x23e, r7; + mtspr 0x23f, r7; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: ConfigMEM2_112MB +// Function signature is unknown. +// PAL: 0x801a7ab8..0x801a7b98 +MARK_BINARY_BLOB(ConfigMEM2_112MB, 0x801a7ab8, 0x801a7b98); +asm UNKNOWN_FUNCTION(ConfigMEM2_112MB) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0x1000; + addi r4, r4, 2; + lis r3, 0x9000; + addi r3, r3, 0x7ff; + lis r6, 0x1000; + addi r6, r6, 0x2a; + lis r5, 0xd000; + addi r5, r5, 0xfff; + isync; + mtspr 0x238, r7; + mtspr 0x239, r4; + mtspr 0x238, r3; + isync; + mtspr 0x230, r7; + mtspr 0x231, r4; + mtspr 0x230, r3; + isync; + mtspr 0x23a, r7; + mtspr 0x23b, r6; + mtspr 0x23a, r5; + isync; + mtspr 0x232, r7; + mtspr 0x233, r7; + isync; + lis r4, 0x1400; + addi r4, r4, 2; + lis r3, 0x9400; + addi r3, r3, 0x3ff; + lis r6, 0x1600; + addi r6, r6, 2; + lis r5, 0x9600; + addi r5, r5, 0x1ff; + isync; + mtspr 0x23c, r7; + mtspr 0x23d, r4; + mtspr 0x23c, r3; + isync; + mtspr 0x234, r7; + mtspr 0x235, r4; + mtspr 0x234, r3; + isync; + mtspr 0x23e, r7; + mtspr 0x23f, r6; + mtspr 0x23e, r5; + isync; + mtspr 0x236, r7; + mtspr 0x237, r6; + mtspr 0x236, r5; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: ConfigMEM2_128MB +// Function signature is unknown. +// PAL: 0x801a7b98..0x801a7c44 +MARK_BINARY_BLOB(ConfigMEM2_128MB, 0x801a7b98, 0x801a7c44); +asm UNKNOWN_FUNCTION(ConfigMEM2_128MB) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0x1000; + addi r4, r4, 2; + lis r3, 0x9000; + addi r3, r3, 0xfff; + lis r6, 0x1000; + addi r6, r6, 0x2a; + lis r5, 0xd000; + addi r5, r5, 0xfff; + isync; + mtspr 0x238, r7; + mtspr 0x239, r4; + mtspr 0x238, r3; + isync; + mtspr 0x230, r7; + mtspr 0x231, r4; + mtspr 0x230, r3; + isync; + mtspr 0x23a, r7; + mtspr 0x23b, r6; + mtspr 0x23a, r5; + isync; + mtspr 0x232, r7; + mtspr 0x233, r7; + isync; + mtspr 0x234, r7; + mtspr 0x235, r7; + isync; + mtspr 0x236, r7; + mtspr 0x237, r7; + isync; + mtspr 0x23c, r7; + mtspr 0x23d, r7; + isync; + mtspr 0x23e, r7; + mtspr 0x23f, r7; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: ConfigMEM_ES1_0 +// Function signature is unknown. +// PAL: 0x801a7c44..0x801a7c94 +MARK_BINARY_BLOB(ConfigMEM_ES1_0, 0x801a7c44, 0x801a7c94); +asm UNKNOWN_FUNCTION(ConfigMEM_ES1_0) { + // clang-format off + nofralloc; + li r7, 0; + lis r4, 0; + addi r4, r4, 2; + lis r3, 0x8000; + addi r3, r3, 0xfff; + isync; + mtdbatu 0, r7; + mtdbatl 0, r4; + mtdbatu 0, r3; + isync; + mtibatu 0, r7; + mtibatl 0, r4; + mtibatu 0, r3; + isync; + mfmsr r3; + ori r3, r3, 0x30; + mtspr 0x1b, r3; + mflr r3; + mtspr 0x1a, r3; + rfi; + // clang-format on +} + +// Symbol: RealMode +// Function signature is unknown. +// PAL: 0x801a7c94..0x801a7cac +MARK_BINARY_BLOB(RealMode, 0x801a7c94, 0x801a7cac); +asm UNKNOWN_FUNCTION(RealMode) { + // clang-format off + nofralloc; + clrlwi r3, r3, 2; + mtspr 0x1a, r3; + mfmsr r3; + rlwinm r3, r3, 0, 0x1c, 0x19; + mtspr 0x1b, r3; + rfi; + // clang-format on +} + +// Symbol: BATConfig +// Function signature is unknown. +// PAL: 0x801a7cac..0x801a7dfc +MARK_BINARY_BLOB(BATConfig, 0x801a7cac, 0x801a7dfc); +asm UNKNOWN_FUNCTION(BATConfig) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + lis r3, 0x8000; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + lwz r0, 0x3138(r3); + cmpwi r0, 0; + bne lbl_801a7ce8; + lis r0, 0x801a; + addic. r0, r0, 0x75d0; + bne lbl_801a7ce8; + lis r3, 0x801a; + addi r3, r3, 0x7c44; + bl RealMode; + b lbl_801a7de8; +lbl_801a7ce8: + lis r3, 0x8000; + lwz r31, 0x3104(r3); + lwz r0, 0x3100(r3); + cmplw r31, r0; + bge lbl_801a7d20; + addis r0, r31, 0xfe80; + cmplwi r0, 0; + bne lbl_801a7d20; + lis r3, 0x8180; + lis r4, 0x180; + bl DCInvalidateRange; + li r0, 2; + lis r3, 0xcc00; + sth r0, 0x4028(r3); +lbl_801a7d20: + lis r0, 0x180; + cmplw r31, r0; + bgt lbl_801a7d3c; + lis r3, 0x801a; + addi r3, r3, 0x774c; + bl RealMode; + b lbl_801a7d54; +lbl_801a7d3c: + lis r0, 0x300; + cmplw r31, r0; + bgt lbl_801a7d54; + lis r3, 0x801a; + addi r3, r3, 0x77cc; + bl RealMode; +lbl_801a7d54: + lis r3, 0x8000; + lis r0, 0x400; + lwz r4, 0x311c(r3); + lwz r3, 0x3120(r3); + cmplw r4, r0; + bgt lbl_801a7db4; + lis r0, 0x9340; + cmplw r3, r0; + bgt lbl_801a7d88; + lis r3, 0x801a; + addi r3, r3, 0x784c; + bl RealMode; + b lbl_801a7de8; +lbl_801a7d88: + lis r0, 0x9380; + cmplw r3, r0; + bgt lbl_801a7da4; + lis r3, 0x801a; + addi r3, r3, 0x792c; + bl RealMode; + b lbl_801a7de8; +lbl_801a7da4: + lis r3, 0x801a; + addi r3, r3, 0x7a0c; + bl RealMode; + b lbl_801a7de8; +lbl_801a7db4: + lis r0, 0x800; + cmplw r4, r0; + bgt lbl_801a7de8; + lis r0, 0x9700; + cmplw r3, r0; + bgt lbl_801a7ddc; + lis r3, 0x801a; + addi r3, r3, 0x7ab8; + bl RealMode; + b lbl_801a7de8; +lbl_801a7ddc: + lis r3, 0x801a; + addi r3, r3, 0x7b98; + bl RealMode; +lbl_801a7de8: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSInitMemoryProtection +// Function signature is unknown. +// PAL: 0x801a7dfc..0x801a7eac +MARK_BINARY_BLOB(__OSInitMemoryProtection, 0x801a7dfc, 0x801a7eac); +asm UNKNOWN_FUNCTION(__OSInitMemoryProtection) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + bl OSDisableInterrupts; + lis r4, 0xcc00; + li r0, 0; + sth r0, 0x4020(r4); + li r0, 0xff; + mr r30, r3; + lis r3, 0xf000; + sth r0, 0x4010(r4); + bl __OSMaskInterrupts; + lis r31, 0x801a; + li r3, 0; + addi r4, r31, 0x763c; + bl __OSSetInterruptHandler; + addi r4, r31, 0x763c; + li r3, 1; + bl __OSSetInterruptHandler; + addi r4, r31, 0x763c; + li r3, 2; + bl __OSSetInterruptHandler; + addi r4, r31, 0x763c; + li r3, 3; + bl __OSSetInterruptHandler; + addi r4, r31, 0x763c; + li r3, 4; + bl __OSSetInterruptHandler; + lis r3, 0x8029; + addi r3, r3, 0x658; + bl OSRegisterShutdownFunction; + bl BATConfig; + lis r3, 0x800; + bl __OSUnmaskInterrupts; + mr r3, r30; + bl OSRestoreInterrupts; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} diff --git a/source/rvl/os/osMemory.h b/source/rvl/os/osMemory.h new file mode 100644 index 000000000..34666c020 --- /dev/null +++ b/source/rvl/os/osMemory.h @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a75d0..0x801a75dc +UNKNOWN_FUNCTION(OSGetPhysicalMem1Size); +// PAL: 0x801a75dc..0x801a75e8 +UNKNOWN_FUNCTION(OSGetPhysicalMem2Size); +// PAL: 0x801a75e8..0x801a75f4 +UNKNOWN_FUNCTION(OSGetConsoleSimulatedMem1Size); +// PAL: 0x801a75f4..0x801a7600 +UNKNOWN_FUNCTION(OSGetConsoleSimulatedMem2Size); +// PAL: 0x801a7600..0x801a763c +UNKNOWN_FUNCTION(OnShutdown); +// PAL: 0x801a763c..0x801a7684 +UNKNOWN_FUNCTION(MEMIntrruptHandler); +// PAL: 0x801a7684..0x801a774c +UNKNOWN_FUNCTION(OSProtectRange); +// PAL: 0x801a774c..0x801a77cc +UNKNOWN_FUNCTION(ConfigMEM1_24MB); +// PAL: 0x801a77cc..0x801a784c +UNKNOWN_FUNCTION(ConfigMEM1_48MB); +// PAL: 0x801a784c..0x801a792c +UNKNOWN_FUNCTION(ConfigMEM2_52MB); +// PAL: 0x801a792c..0x801a7a0c +UNKNOWN_FUNCTION(ConfigMEM2_56MB); +// PAL: 0x801a7a0c..0x801a7ab8 +UNKNOWN_FUNCTION(ConfigMEM2_64MB); +// PAL: 0x801a7ab8..0x801a7b98 +UNKNOWN_FUNCTION(ConfigMEM2_112MB); +// PAL: 0x801a7b98..0x801a7c44 +UNKNOWN_FUNCTION(ConfigMEM2_128MB); +// PAL: 0x801a7c44..0x801a7c94 +UNKNOWN_FUNCTION(ConfigMEM_ES1_0); +// PAL: 0x801a7c94..0x801a7cac +UNKNOWN_FUNCTION(RealMode); +// PAL: 0x801a7cac..0x801a7dfc +UNKNOWN_FUNCTION(BATConfig); +// PAL: 0x801a7dfc..0x801a7eac +UNKNOWN_FUNCTION(__OSInitMemoryProtection); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osMessage.c b/source/rvl/os/osMessage.c new file mode 100644 index 000000000..9f03a20dd --- /dev/null +++ b/source/rvl/os/osMessage.c @@ -0,0 +1,238 @@ +#include "osMessage.h" + +#include "osInterrupt.h" +#include "osThread.h" + +// Symbol: OSInitMessageQueue +// PAL: 0x801a72fc..0x801a735c +MARK_BINARY_BLOB(OSInitMessageQueue, 0x801a72fc, 0x801a735c); +asm void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, + s32 capacity) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl OSInitThreadQueue; + addi r3, r29, 8; + bl OSInitThreadQueue; + li r0, 0; + stw r30, 0x10(r29); + stw r31, 0x14(r29); + stw r0, 0x18(r29); + stw r0, 0x1c(r29); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSSendMessage +// Function signature is unknown. +// PAL: 0x801a735c..0x801a7424 +MARK_BINARY_BLOB(OSSendMessage, 0x801a735c, 0x801a7424); +asm UNKNOWN_FUNCTION(OSSendMessage) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bl OSDisableInterrupts; + mr r30, r3; + clrlwi r31, r31, 0x1f; + b lbl_801a73b4; +lbl_801a7394: + cmpwi r31, 0; + bne lbl_801a73ac; + mr r3, r30; + bl OSRestoreInterrupts; + li r3, 0; + b lbl_801a7404; +lbl_801a73ac: + mr r3, r28; + bl OSSleepThread; +lbl_801a73b4: + lwz r4, 0x1c(r28); + lwz r6, 0x14(r28); + cmpw r6, r4; + ble lbl_801a7394; + lwz r0, 0x18(r28); + addi r3, r28, 8; + lwz r5, 0x10(r28); + add r4, r0, r4; + divw r0, r4, r6; + mullw r0, r0, r6; + subf r0, r0, r4; + slwi r0, r0, 2; + stwx r29, r5, r0; + lwz r4, 0x1c(r28); + addi r0, r4, 1; + stw r0, 0x1c(r28); + bl OSWakeupThread; + mr r3, r30; + bl OSRestoreInterrupts; + li r3, 1; +lbl_801a7404: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSReceiveMessage +// Function signature is unknown. +// PAL: 0x801a7424..0x801a7500 +MARK_BINARY_BLOB(OSReceiveMessage, 0x801a7424, 0x801a7500); +asm UNKNOWN_FUNCTION(OSReceiveMessage) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r3; + stw r30, 0x18(r1); + mr r30, r5; + stw r29, 0x14(r1); + stw r28, 0x10(r1); + mr r28, r4; + bl OSDisableInterrupts; + mr r29, r3; + clrlwi r30, r30, 0x1f; + b lbl_801a747c; +lbl_801a745c: + cmpwi r30, 0; + bne lbl_801a7474; + mr r3, r29; + bl OSRestoreInterrupts; + li r3, 0; + b lbl_801a74e0; +lbl_801a7474: + addi r3, r31, 8; + bl OSSleepThread; +lbl_801a747c: + lwz r0, 0x1c(r31); + cmpwi r0, 0; + beq lbl_801a745c; + cmpwi r28, 0; + beq lbl_801a74a4; + lwz r0, 0x18(r31); + lwz r3, 0x10(r31); + slwi r0, r0, 2; + lwzx r0, r3, r0; + stw r0, 0(r28); +lbl_801a74a4: + lwz r4, 0x18(r31); + mr r3, r31; + lwz r6, 0x14(r31); + addi r7, r4, 1; + lwz r4, 0x1c(r31); + divw r5, r7, r6; + addi r0, r4, -1; + stw r0, 0x1c(r31); + mullw r0, r5, r6; + subf r0, r0, r7; + stw r0, 0x18(r31); + bl OSWakeupThread; + mr r3, r29; + bl OSRestoreInterrupts; + li r3, 1; +lbl_801a74e0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSJamMessage +// Function signature is unknown. +// PAL: 0x801a7500..0x801a75d0 +MARK_BINARY_BLOB(OSJamMessage, 0x801a7500, 0x801a75d0); +asm UNKNOWN_FUNCTION(OSJamMessage) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r4; + stw r28, 0x10(r1); + mr r28, r3; + bl OSDisableInterrupts; + mr r30, r3; + clrlwi r31, r31, 0x1f; + b lbl_801a7558; +lbl_801a7538: + cmpwi r31, 0; + bne lbl_801a7550; + mr r3, r30; + bl OSRestoreInterrupts; + li r3, 0; + b lbl_801a75b0; +lbl_801a7550: + mr r3, r28; + bl OSSleepThread; +lbl_801a7558: + lwz r6, 0x14(r28); + lwz r0, 0x1c(r28); + cmpw r6, r0; + ble lbl_801a7538; + lwz r0, 0x18(r28); + addi r3, r28, 8; + lwz r4, 0x10(r28); + add r5, r6, r0; + addi r5, r5, -1; + divw r0, r5, r6; + mullw r0, r0, r6; + subf r0, r0, r5; + stw r0, 0x18(r28); + slwi r0, r0, 2; + stwx r29, r4, r0; + lwz r4, 0x1c(r28); + addi r0, r4, 1; + stw r0, 0x1c(r28); + bl OSWakeupThread; + mr r3, r30; + bl OSRestoreInterrupts; + li r3, 1; +lbl_801a75b0: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} diff --git a/source/rvl/os/osMessage.h b/source/rvl/os/osMessage.h index 0a053897c..de53871a2 100644 --- a/source/rvl/os/osMessage.h +++ b/source/rvl/os/osMessage.h @@ -2,12 +2,27 @@ #include +#include "decomp.h" + #ifdef __cplusplus extern "C" { #endif +typedef void* OSMessage; + +typedef struct OSMessageQueue { + char _[0x20]; +} OSMessageQueue; + +// PAL: 0x801a72fc..0x801a735c void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); +// PAL: 0x801a735c..0x801a7424 +UNKNOWN_FUNCTION(OSSendMessage); +// PAL: 0x801a7424..0x801a7500 +UNKNOWN_FUNCTION(OSReceiveMessage); +// PAL: 0x801a7500..0x801a75d0 +UNKNOWN_FUNCTION(OSJamMessage); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/source/rvl/os/osMutex.c b/source/rvl/os/osMutex.c new file mode 100644 index 000000000..09992452b --- /dev/null +++ b/source/rvl/os/osMutex.c @@ -0,0 +1,287 @@ +#include "osMutex.h" + +#include "osInterrupt.h" +#include "osThread.h" + +// Symbol: OSInitMutex +// PAL: 0x801a7eac..0x801a7ee4 +MARK_BINARY_BLOB(OSInitMutex, 0x801a7eac, 0x801a7ee4); +asm void OSInitMutex(OSMutex*) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r3; + bl OSInitThreadQueue; + li r0, 0; + stw r0, 8(r31); + stw r0, 0xc(r31); + lwz r31, 0xc(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSLockMutex +// PAL: 0x801a7ee4..0x801a7fc0 +MARK_BINARY_BLOB(OSLockMutex, 0x801a7ee4, 0x801a7fc0); +asm void OSLockMutex(OSMutex*) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + stw r28, 0x10(r1); + mr r28, r3; + bl OSDisableInterrupts; + mr r29, r3; + bl OSGetCurrentThread; + mr r30, r3; + li r31, 0; +lbl_801a7f18: + lwz r0, 8(r28); + cmpwi r0, 0; + bne lbl_801a7f60; + lwz r3, 0xc(r28); + stw r30, 8(r28); + addi r0, r3, 1; + stw r0, 0xc(r28); + lwz r3, 0x2f8(r30); + cmpwi r3, 0; + bne lbl_801a7f48; + stw r28, 0x2f4(r30); + b lbl_801a7f4c; +lbl_801a7f48: + stw r28, 0x10(r3); +lbl_801a7f4c: + li r0, 0; + stw r3, 0x14(r28); + stw r0, 0x10(r28); + stw r28, 0x2f8(r30); + b lbl_801a7f98; +lbl_801a7f60: + cmplw r0, r30; + bne lbl_801a7f78; + lwz r3, 0xc(r28); + addi r0, r3, 1; + stw r0, 0xc(r28); + b lbl_801a7f98; +lbl_801a7f78: + stw r28, 0x2f0(r30); + lwz r3, 8(r28); + lwz r4, 0x2d0(r30); + bl __OSPromoteThread; + mr r3, r28; + bl OSSleepThread; + stw r31, 0x2f0(r30); + b lbl_801a7f18; +lbl_801a7f98: + mr r3, r29; + bl OSRestoreInterrupts; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r28, 0x10(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSUnlockMutex +// PAL: 0x801a7fc0..0x801a8088 +MARK_BINARY_BLOB(OSUnlockMutex, 0x801a7fc0, 0x801a8088); +asm void OSUnlockMutex(OSMutex*) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r3; + bl OSDisableInterrupts; + mr r31, r3; + bl OSGetCurrentThread; + lwz r0, 8(r29); + mr r30, r3; + cmplw r0, r3; + bne lbl_801a8064; + lwz r0, 0xc(r29); + addic. r0, r0, -1; + stw r0, 0xc(r29); + bne lbl_801a8064; + lwz r4, 0x10(r29); + lwz r5, 0x14(r29); + cmpwi r4, 0; + bne lbl_801a8020; + stw r5, 0x2f8(r3); + b lbl_801a8024; +lbl_801a8020: + stw r5, 0x14(r4); +lbl_801a8024: + cmpwi r5, 0; + bne lbl_801a8034; + stw r4, 0x2f4(r3); + b lbl_801a8038; +lbl_801a8034: + stw r4, 0x10(r5); +lbl_801a8038: + li r0, 0; + stw r0, 8(r29); + lwz r4, 0x2d0(r3); + lwz r0, 0x2d4(r3); + cmpw r4, r0; + bge lbl_801a805c; + mr r3, r30; + bl __OSGetEffectivePriority; + stw r3, 0x2d0(r30); +lbl_801a805c: + mr r3, r29; + bl OSWakeupThread; +lbl_801a8064: + mr r3, r31; + bl OSRestoreInterrupts; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSUnlockAllMutex +// Function signature is unknown. +// PAL: 0x801a8088..0x801a80f4 +MARK_BINARY_BLOB(__OSUnlockAllMutex, 0x801a8088, 0x801a80f4); +asm UNKNOWN_FUNCTION(__OSUnlockAllMutex) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + li r31, 0; + stw r30, 8(r1); + mr r30, r3; + b lbl_801a80d0; +lbl_801a80a8: + lwz r4, 0x10(r3); + cmpwi r4, 0; + bne lbl_801a80bc; + stw r31, 0x2f8(r30); + b lbl_801a80c0; +lbl_801a80bc: + stw r31, 0x14(r4); +lbl_801a80c0: + stw r4, 0x2f4(r30); + stw r31, 0xc(r3); + stw r31, 8(r3); + bl OSWakeupThread; +lbl_801a80d0: + lwz r3, 0x2f4(r30); + cmpwi r3, 0; + bne lbl_801a80a8; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSTryLockMutex +// Function signature is unknown. +// PAL: 0x801a80f4..0x801a81b0 +MARK_BINARY_BLOB(OSTryLockMutex, 0x801a80f4, 0x801a81b0); +asm UNKNOWN_FUNCTION(OSTryLockMutex) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + mr r29, r3; + bl OSDisableInterrupts; + mr r31, r3; + bl OSGetCurrentThread; + lwz r0, 8(r29); + cmpwi r0, 0; + bne lbl_801a8168; + lwz r4, 0xc(r29); + stw r3, 8(r29); + addi r0, r4, 1; + stw r0, 0xc(r29); + lwz r4, 0x2f8(r3); + cmpwi r4, 0; + bne lbl_801a814c; + stw r29, 0x2f4(r3); + b lbl_801a8150; +lbl_801a814c: + stw r29, 0x10(r4); +lbl_801a8150: + li r0, 0; + stw r4, 0x14(r29); + li r30, 1; + stw r0, 0x10(r29); + stw r29, 0x2f8(r3); + b lbl_801a8188; +lbl_801a8168: + cmplw r0, r3; + bne lbl_801a8184; + lwz r3, 0xc(r29); + li r30, 1; + addi r0, r3, 1; + stw r0, 0xc(r29); + b lbl_801a8188; +lbl_801a8184: + li r30, 0; +lbl_801a8188: + mr r3, r31; + bl OSRestoreInterrupts; + mr r3, r30; + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSInitCond +// Function signature is unknown. +// PAL: 0x801a81b0..0x801a81b4 +MARK_BINARY_BLOB(OSInitCond, 0x801a81b0, 0x801a81b4); +asm UNKNOWN_FUNCTION(OSInitCond) { + // clang-format off + nofralloc; + b OSInitThreadQueue; + // clang-format on +} + +// Symbol: OSSignalCond +// Function signature is unknown. +// PAL: 0x801a81b4..0x801a81b8 +MARK_BINARY_BLOB(OSSignalCond, 0x801a81b4, 0x801a81b8); +asm UNKNOWN_FUNCTION(OSSignalCond) { + // clang-format off + nofralloc; + b OSWakeupThread; + // clang-format on +} diff --git a/source/rvl/os/osMutex.h b/source/rvl/os/osMutex.h index 75edd9986..4ad4ed579 100644 --- a/source/rvl/os/osMutex.h +++ b/source/rvl/os/osMutex.h @@ -1,5 +1,6 @@ #pragma once +#include #include #ifdef __cplusplus @@ -10,10 +11,21 @@ typedef struct { char _[0x18]; } OSMutex; +// PAL: 0x801a7eac..0x801a7ee4 void OSInitMutex(OSMutex*); +// PAL: 0x801a7ee4..0x801a7fc0 void OSLockMutex(OSMutex*); +// PAL: 0x801a7fc0..0x801a8088 void OSUnlockMutex(OSMutex*); +// PAL: 0x801a8088..0x801a80f4 +UNKNOWN_FUNCTION(__OSUnlockAllMutex); +// PAL: 0x801a80f4..0x801a81b0 +UNKNOWN_FUNCTION(OSTryLockMutex); +// PAL: 0x801a81b0..0x801a81b4 +UNKNOWN_FUNCTION(OSInitCond); +// PAL: 0x801a81b4..0x801a81b8 +UNKNOWN_FUNCTION(OSSignalCond); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/source/rvl/os/osReboot.c b/source/rvl/os/osReboot.c new file mode 100644 index 000000000..1e23d50ff --- /dev/null +++ b/source/rvl/os/osReboot.c @@ -0,0 +1,60 @@ +#include "osReboot.h" + +#include "osArena.h" +#include "osInterrupt.h" + +// Extern function references. +// PAL: 0x801a4648 +extern UNKNOWN_FUNCTION(__OSBootDol); + +// Symbol: __OSReboot +// Function signature is unknown. +// PAL: 0x801a81b8..0x801a8224 +MARK_BINARY_BLOB(__OSReboot, 0x801a81b8, 0x801a8224); +asm UNKNOWN_FUNCTION(__OSReboot) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r4; + stw r30, 0x18(r1); + mr r30, r3; + bl OSDisableInterrupts; + lis r3, 0x8128; + bl OSSetArenaLo; + lis r3, 0x812f; + bl OSSetArenaHi; + li r0, 0; + lis r6, 0x8000; + stw r0, 8(r1); + mr r3, r31; + oris r4, r30, 0x8000; + addi r5, r1, 8; + lwz r0, 0x3194(r6); + stw r0, -0x6340(r13); + bl __OSBootDol; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSGetSaveRegion +// Function signature is unknown. +// PAL: 0x801a8224..0x801a8238 +MARK_BINARY_BLOB(OSGetSaveRegion, 0x801a8224, 0x801a8238); +asm UNKNOWN_FUNCTION(OSGetSaveRegion) { + // clang-format off + nofralloc; + lwz r0, -0x6300(r13); + stw r0, 0(r3); + lwz r0, -0x62fc(r13); + stw r0, 0(r4); + blr; + // clang-format on +} diff --git a/source/rvl/os/osReboot.h b/source/rvl/os/osReboot.h new file mode 100644 index 000000000..53b00e5bb --- /dev/null +++ b/source/rvl/os/osReboot.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a81b8..0x801a8224 +UNKNOWN_FUNCTION(__OSReboot); +// PAL: 0x801a8224..0x801a8238 +UNKNOWN_FUNCTION(OSGetSaveRegion); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osReset.c b/source/rvl/os/osReset.c index 8c758c434..7c089f298 100644 --- a/source/rvl/os/osReset.c +++ b/source/rvl/os/osReset.c @@ -67,7 +67,7 @@ extern UNKNOWN_FUNCTION(unk_801bcd4c); // Function signature is unknown. // PAL: 0x801a8238..0x801a82c0 MARK_BINARY_BLOB(OSRegisterShutdownFunction, 0x801a8238, 0x801a82c0); -asm UNKNOWN_FUNCTION(OSRegisterShutdownFunction) { +asm void OSRegisterShutdownFunction(OSShutdownFunctionInfo* info) { // clang-format off nofralloc; lwz r5, -0x62f0(r13); diff --git a/source/rvl/os/osReset.h b/source/rvl/os/osReset.h index 1418f5698..1a3c1a01d 100644 --- a/source/rvl/os/osReset.h +++ b/source/rvl/os/osReset.h @@ -8,8 +8,15 @@ extern "C" { #endif +typedef struct OSShutdownFunctionInfo { + u32 func; + u32 priority; + u32 succ; + u32 prev; +} OSShutdownFunctionInfo; + // PAL: 0x801a8238..0x801a82c0 -UNKNOWN_FUNCTION(OSRegisterShutdownFunction); +extern void OSRegisterShutdownFunction(OSShutdownFunctionInfo* info); // PAL: 0x801a82c0..0x801a8370 UNKNOWN_FUNCTION(__OSCallShutdownFunctions); // PAL: 0x801a8370..0x801a8500 diff --git a/source/rvl/os/osRtc.c b/source/rvl/os/osRtc.c new file mode 100644 index 000000000..85c00a1ec --- /dev/null +++ b/source/rvl/os/osRtc.c @@ -0,0 +1,829 @@ +#include "osRtc.h" + +#include "osCache.h" +#include "osInterrupt.h" + +// Extern function references. +// PAL: 0x80167f68 +extern UNKNOWN_FUNCTION(EXIImm); +// PAL: 0x801681e4 +extern UNKNOWN_FUNCTION(EXIImmEx); +// PAL: 0x80168288 +extern UNKNOWN_FUNCTION(EXIDma); +// PAL: 0x80168380 +extern UNKNOWN_FUNCTION(EXISync); +// PAL: 0x801689d0 +extern UNKNOWN_FUNCTION(EXISelect); +// PAL: 0x80168b00 +extern UNKNOWN_FUNCTION(EXIDeselect); +// PAL: 0x80169164 +extern UNKNOWN_FUNCTION(EXILock); +// PAL: 0x80169260 +extern UNKNOWN_FUNCTION(EXIUnlock); + +// Symbol: WriteSramCallback +// Function signature is unknown. +// PAL: 0x801a8a9c..0x801a8bd4 +MARK_BINARY_BLOB(WriteSramCallback, 0x801a8a9c, 0x801a8bd4); +asm UNKNOWN_FUNCTION(WriteSramCallback) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + lis r6, 0x8034; + lis r5, 0x801b; + stw r0, 0x24(r1); + addi r6, r6, 0x7440; + li r3, 0; + addi r5, r5, -30052; + stw r31, 0x1c(r1); + li r4, 1; + stw r30, 0x18(r1); + stw r29, 0x14(r1); + lwz r31, 0x40(r6); + subfic r29, r31, 0x40; + add r30, r6, r31; + bl EXILock; + cmpwi r3, 0; + bne lbl_801a8aec; + li r0, 0; + b lbl_801a8b9c; +lbl_801a8aec: + li r3, 0; + li r4, 1; + li r5, 3; + bl EXISelect; + cmpwi r3, 0; + bne lbl_801a8b14; + li r3, 0; + bl EXIUnlock; + li r0, 0; + b lbl_801a8b9c; +lbl_801a8b14: + slwi r3, r31, 6; + addi r4, r1, 8; + addi r0, r3, 0x100; + li r5, 4; + oris r0, r0, 0xa000; + li r3, 0; + stw r0, 8(r1); + li r6, 1; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r31, r0, 5; + bl EXISync; + cntlzw r0, r3; + mr r4, r30; + srwi r0, r0, 5; + mr r5, r29; + or r29, r31, r0; + li r3, 0; + li r6, 1; + bl EXIImmEx; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r29, r29, r0; + bl EXIDeselect; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r29, r29, r0; + bl EXIUnlock; + cntlzw r0, r29; + srwi r0, r0, 5; +lbl_801a8b9c: + lis r3, 0x8034; + cmpwi r0, 0; + addi r3, r3, 0x7440; + stw r0, 0x4c(r3); + beq lbl_801a8bb8; + li r0, 0x40; + stw r0, 0x40(r3); +lbl_801a8bb8: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSInitSram +// Function signature is unknown. +// PAL: 0x801a8bd4..0x801a8dd4 +MARK_BINARY_BLOB(__OSInitSram, 0x801a8bd4, 0x801a8dd4); +asm UNKNOWN_FUNCTION(__OSInitSram) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + li r4, 0x40; + stw r0, 0x24(r1); + li r0, 0; + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + lis r30, 0x8034; + addi r30, r30, 0x7440; + stw r0, 0x44(r30); + mr r3, r30; + stw r0, 0x48(r30); + bl DCInvalidateRange; + li r3, 0; + li r4, 1; + li r5, 0; + bl EXILock; + cmpwi r3, 0; + bne lbl_801a8c28; + li r3, 0; + b lbl_801a8cec; +lbl_801a8c28: + li r3, 0; + li r4, 1; + li r5, 3; + bl EXISelect; + cmpwi r3, 0; + bne lbl_801a8c50; + li r3, 0; + bl EXIUnlock; + li r3, 0; + b lbl_801a8cec; +lbl_801a8c50: + lis r3, 0x2000; + addi r4, r1, 8; + addi r0, r3, 0x100; + li r5, 4; + stw r0, 8(r1); + li r3, 0; + li r6, 1; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r31, r0, 5; + bl EXISync; + cntlzw r0, r3; + mr r4, r30; + srwi r0, r0, 5; + li r3, 0; + or r30, r31, r0; + li r5, 0x40; + li r6, 0; + li r7, 0; + bl EXIDma; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r30, r30, r0; + bl EXISync; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r30, r30, r0; + bl EXIDeselect; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r30, r30, r0; + bl EXIUnlock; + cntlzw r0, r30; + srwi r3, r0, 5; +lbl_801a8cec: + lis r31, 0x8034; + li r0, 0x40; + addi r31, r31, 0x7440; + stw r3, 0x4c(r31); + stw r0, 0x40(r31); + bl OSDisableInterrupts; + lwz r0, 0x48(r31); + cmpwi r0, 0; + beq lbl_801a8d1c; + bl OSRestoreInterrupts; + li r3, 0; + b lbl_801a8d2c; +lbl_801a8d1c: + li r0, 1; + stw r3, 0x44(r31); + addi r3, r31, 0x14; + stw r0, 0x48(r31); +lbl_801a8d2c: + lhz r30, 0x28(r3); + li r3, 0; + li r4, 0x14; + bl UnlockSram; + rlwinm r0, r30, 0, 0x11, 0x15; + cmplwi r0, 0x5000; + beq lbl_801a8d54; + rlwinm r0, r30, 0, 0x18, 0x19; + cmplwi r0, 0xc0; + bne lbl_801a8d58; +lbl_801a8d54: + li r30, 0; +lbl_801a8d58: + bl OSDisableInterrupts; + lis r4, 0x8034; + addi r4, r4, 0x7440; + lwz r0, 0x48(r4); + cmpwi r0, 0; + beq lbl_801a8d7c; + bl OSRestoreInterrupts; + li r5, 0; + b lbl_801a8d8c; +lbl_801a8d7c: + li r0, 1; + stw r3, 0x44(r4); + addi r5, r4, 0x14; + stw r0, 0x48(r4); +lbl_801a8d8c: + lhz r0, 0x28(r5); + clrlwi r3, r30, 0x10; + cmplw r3, r0; + bne lbl_801a8dac; + li r3, 0; + li r4, 0x14; + bl UnlockSram; + b lbl_801a8dbc; +lbl_801a8dac: + sth r30, 0x28(r5); + li r3, 1; + li r4, 0x14; + bl UnlockSram; +lbl_801a8dbc: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: UnlockSram +// Function signature is unknown. +// PAL: 0x801a8dd4..0x801a90b4 +MARK_BINARY_BLOB(UnlockSram, 0x801a8dd4, 0x801a90b4); +asm UNKNOWN_FUNCTION(UnlockSram) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + cmpwi r3, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + stw r29, 0x14(r1); + beq lbl_801a907c; + cmpwi r4, 0; + bne lbl_801a8f28; + lis r8, 0x8034; + addi r8, r8, 0x7440; + lbz r3, 0x13(r8); + clrlwi r0, r3, 0x1e; + cmplwi r0, 2; + ble lbl_801a8e1c; + rlwinm r0, r3, 0, 0x18, 0x1d; + stb r0, 0x13(r8); +lbl_801a8e1c: + lis r3, 0x8034; + li r0, 0; + addi r3, r3, 0x7440; + addi r7, r8, 0xc; + addi r5, r3, 0x14; + sth r0, 2(r8); + addi r3, r5, 1; + subf r3, r7, r3; + cmplw r7, r5; + sth r0, 0(r8); + srwi r3, r3, 1; + bge lbl_801a8f28; + rlwinm. r0, r3, 0x1e, 2, 0x1f; + mtctr r0; + beq lbl_801a8ef8; +lbl_801a8e58: + lhz r6, 0(r8); + lhz r0, 0(r7); + lhz r5, 2(r8); + add r0, r6, r0; + sth r0, 0(r8); + clrlwi r6, r0, 0x10; + lhz r0, 0(r7); + nor r0, r0, r0; + add r0, r5, r0; + sth r0, 2(r8); + clrlwi r5, r0, 0x10; + lhz r0, 2(r7); + add r0, r6, r0; + sth r0, 0(r8); + clrlwi r6, r0, 0x10; + lhz r0, 2(r7); + nor r0, r0, r0; + add r0, r5, r0; + sth r0, 2(r8); + clrlwi r5, r0, 0x10; + lhz r0, 4(r7); + add r0, r6, r0; + sth r0, 0(r8); + clrlwi r6, r0, 0x10; + lhz r0, 4(r7); + nor r0, r0, r0; + add r0, r5, r0; + sth r0, 2(r8); + clrlwi r5, r0, 0x10; + lhz r0, 6(r7); + add r0, r6, r0; + sth r0, 0(r8); + lhz r0, 6(r7); + addi r7, r7, 8; + nor r0, r0, r0; + add r0, r5, r0; + sth r0, 2(r8); + bdnz lbl_801a8e58; + andi. r3, r3, 3; + beq lbl_801a8f28; +lbl_801a8ef8: + mtctr r3; +lbl_801a8efc: + lhz r6, 0(r8); + lhz r0, 0(r7); + lhz r5, 2(r8); + add r0, r6, r0; + sth r0, 0(r8); + lhz r0, 0(r7); + addi r7, r7, 2; + nor r0, r0, r0; + add r0, r5, r0; + sth r0, 2(r8); + bdnz lbl_801a8efc; +lbl_801a8f28: + lis r3, 0x8034; + addi r3, r3, 0x7440; + lwz r0, 0x40(r3); + cmplw r4, r0; + bge lbl_801a8f40; + stw r4, 0x40(r3); +lbl_801a8f40: + lis r4, 0x8034; + addi r4, r4, 0x7440; + lwz r0, 0x40(r4); + cmplwi r0, 0x14; + bgt lbl_801a8f78; + lhz r3, 0x3c(r4); + rlwinm r0, r3, 0, 0x11, 0x15; + cmplwi r0, 0x5000; + beq lbl_801a8f70; + rlwinm r0, r3, 0, 0x18, 0x19; + cmplwi r0, 0xc0; + bne lbl_801a8f78; +lbl_801a8f70: + li r0, 0; + sth r0, 0x3c(r4); +lbl_801a8f78: + lis r6, 0x8034; + lis r5, 0x801b; + addi r6, r6, 0x7440; + li r3, 0; + lwz r31, 0x40(r6); + addi r5, r5, -30052; + li r4, 1; + subfic r29, r31, 0x40; + add r30, r6, r31; + bl EXILock; + cmpwi r3, 0; + bne lbl_801a8fb0; + li r0, 0; + b lbl_801a9060; +lbl_801a8fb0: + li r3, 0; + li r4, 1; + li r5, 3; + bl EXISelect; + cmpwi r3, 0; + bne lbl_801a8fd8; + li r3, 0; + bl EXIUnlock; + li r0, 0; + b lbl_801a9060; +lbl_801a8fd8: + slwi r3, r31, 6; + addi r4, r1, 8; + addi r0, r3, 0x100; + li r5, 4; + oris r0, r0, 0xa000; + li r3, 0; + stw r0, 8(r1); + li r6, 1; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r31, r0, 5; + bl EXISync; + cntlzw r0, r3; + mr r4, r30; + srwi r0, r0, 5; + mr r5, r29; + or r29, r31, r0; + li r3, 0; + li r6, 1; + bl EXIImmEx; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r29, r29, r0; + bl EXIDeselect; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r29, r29, r0; + bl EXIUnlock; + cntlzw r0, r29; + srwi r0, r0, 5; +lbl_801a9060: + lis r3, 0x8034; + cmpwi r0, 0; + addi r3, r3, 0x7440; + stw r0, 0x4c(r3); + beq lbl_801a907c; + li r0, 0x40; + stw r0, 0x40(r3); +lbl_801a907c: + lis r31, 0x8034; + li r0, 0; + addi r31, r31, 0x7440; + stw r0, 0x48(r31); + lwz r3, 0x44(r31); + bl OSRestoreInterrupts; + lwz r3, 0x4c(r31); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSSyncSram +// Function signature is unknown. +// PAL: 0x801a90b4..0x801a90c4 +MARK_BINARY_BLOB(__OSSyncSram, 0x801a90b4, 0x801a90c4); +asm UNKNOWN_FUNCTION(__OSSyncSram) { + // clang-format off + nofralloc; + lis r3, 0x8034; + addi r3, r3, 0x7440; + lwz r3, 0x4c(r3); + blr; + // clang-format on +} + +// Symbol: __OSReadROM +// Function signature is unknown. +// PAL: 0x801a90c4..0x801a91e8 +MARK_BINARY_BLOB(__OSReadROM, 0x801a90c4, 0x801a91e8); +asm UNKNOWN_FUNCTION(__OSReadROM) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r5; + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl DCInvalidateRange; + li r3, 0; + li r4, 1; + li r5, 0; + bl EXILock; + cmpwi r3, 0; + bne lbl_801a910c; + li r3, 0; + b lbl_801a91cc; +lbl_801a910c: + li r3, 0; + li r4, 1; + li r5, 3; + bl EXISelect; + cmpwi r3, 0; + bne lbl_801a9134; + li r3, 0; + bl EXIUnlock; + li r3, 0; + b lbl_801a91cc; +lbl_801a9134: + slwi r0, r31, 6; + addi r4, r1, 8; + stw r0, 8(r1); + li r3, 0; + li r5, 4; + li r6, 1; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r31, r0, 5; + bl EXISync; + cntlzw r0, r3; + mr r4, r29; + srwi r0, r0, 5; + mr r5, r30; + or r31, r31, r0; + li r3, 0; + li r6, 0; + li r7, 0; + bl EXIDma; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXISync; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXIDeselect; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXIUnlock; + cntlzw r0, r31; + srwi r3, r0, 5; +lbl_801a91cc: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: OSGetWirelessID +// Function signature is unknown. +// PAL: 0x801a91e8..0x801a9260 +MARK_BINARY_BLOB(OSGetWirelessID, 0x801a91e8, 0x801a9260); +asm UNKNOWN_FUNCTION(OSGetWirelessID) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r3; + bl OSDisableInterrupts; + lis r4, 0x8034; + addi r4, r4, 0x7440; + lwz r0, 0x48(r4); + cmpwi r0, 0; + beq lbl_801a9220; + bl OSRestoreInterrupts; + li r5, 0; + b lbl_801a9230; +lbl_801a9220: + li r0, 1; + stw r3, 0x44(r4); + addi r5, r4, 0x14; + stw r0, 0x48(r4); +lbl_801a9230: + slwi r0, r31, 1; + li r3, 0; + add r5, r5, r0; + li r4, 0x14; + lhz r31, 0x1c(r5); + bl UnlockSram; + mr r3, r31; + lwz r31, 0xc(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: OSSetWirelessID +// Function signature is unknown. +// PAL: 0x801a9260..0x801a92fc +MARK_BINARY_BLOB(OSSetWirelessID, 0x801a9260, 0x801a92fc); +asm UNKNOWN_FUNCTION(OSSetWirelessID) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + bl OSDisableInterrupts; + lis r4, 0x8034; + addi r4, r4, 0x7440; + lwz r0, 0x48(r4); + cmpwi r0, 0; + beq lbl_801a92a0; + bl OSRestoreInterrupts; + li r3, 0; + b lbl_801a92b0; +lbl_801a92a0: + li r0, 1; + stw r3, 0x44(r4); + addi r3, r4, 0x14; + stw r0, 0x48(r4); +lbl_801a92b0: + slwi r0, r30, 1; + add r3, r3, r0; + lhz r0, 0x1c(r3); + cmplw r31, r0; + beq lbl_801a92d8; + sth r31, 0x1c(r3); + li r3, 1; + li r4, 0x14; + bl UnlockSram; + b lbl_801a92e4; +lbl_801a92d8: + li r3, 0; + li r4, 0x14; + bl UnlockSram; +lbl_801a92e4: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: __OSGetRTCFlags +// Function signature is unknown. +// PAL: 0x801a92fc..0x801a9418 +MARK_BINARY_BLOB(__OSGetRTCFlags, 0x801a92fc, 0x801a9418); +asm UNKNOWN_FUNCTION(__OSGetRTCFlags) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + li r4, 1; + li r5, 0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + mr r30, r3; + li r3, 0; + bl EXILock; + cmpwi r3, 0; + bne lbl_801a9334; + li r3, 0; + b lbl_801a9400; +lbl_801a9334: + li r3, 0; + li r4, 1; + li r5, 3; + bl EXISelect; + cmpwi r3, 0; + bne lbl_801a935c; + li r3, 0; + bl EXIUnlock; + li r3, 0; + b lbl_801a9400; +lbl_801a935c: + lis r3, 0x2100; + addi r4, r1, 8; + addi r0, r3, 0x800; + li r5, 4; + stw r0, 8(r1); + li r3, 0; + li r6, 1; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r31, r0, 5; + bl EXISync; + cntlzw r0, r3; + addi r4, r1, 8; + srwi r0, r0, 5; + li r3, 0; + or r31, r31, r0; + li r5, 4; + li r6, 0; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXISync; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXIDeselect; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXIUnlock; + lwz r4, 8(r1); + cntlzw r0, r31; + srwi r3, r0, 5; + stw r4, 0(r30); +lbl_801a9400: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: __OSClearRTCFlags +// Function signature is unknown. +// PAL: 0x801a9418..0x801a9528 +MARK_BINARY_BLOB(__OSClearRTCFlags, 0x801a9418, 0x801a9528); +asm UNKNOWN_FUNCTION(__OSClearRTCFlags) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + li r3, 0; + li r4, 1; + stw r0, 0x24(r1); + li r0, 0; + li r5, 0; + stw r31, 0x1c(r1); + stw r0, 8(r1); + bl EXILock; + cmpwi r3, 0; + bne lbl_801a9450; + li r3, 0; + b lbl_801a9514; +lbl_801a9450: + li r3, 0; + li r4, 1; + li r5, 3; + bl EXISelect; + cmpwi r3, 0; + bne lbl_801a9478; + li r3, 0; + bl EXIUnlock; + li r3, 0; + b lbl_801a9514; +lbl_801a9478: + lis r3, 0xa100; + addi r4, r1, 0xc; + addi r0, r3, 0x800; + li r5, 4; + stw r0, 0xc(r1); + li r3, 0; + li r6, 1; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r31, r0, 5; + bl EXISync; + cntlzw r0, r3; + addi r4, r1, 8; + srwi r0, r0, 5; + li r3, 0; + or r31, r31, r0; + li r5, 4; + li r6, 1; + li r7, 0; + bl EXIImm; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXISync; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXIDeselect; + cntlzw r0, r3; + li r3, 0; + srwi r0, r0, 5; + or r31, r31, r0; + bl EXIUnlock; + cntlzw r0, r31; + srwi r3, r0, 5; +lbl_801a9514: + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} diff --git a/source/rvl/os/osRtc.h b/source/rvl/os/osRtc.h new file mode 100644 index 000000000..12f22521c --- /dev/null +++ b/source/rvl/os/osRtc.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a8a9c..0x801a8bd4 +UNKNOWN_FUNCTION(WriteSramCallback); +// PAL: 0x801a8bd4..0x801a8dd4 +UNKNOWN_FUNCTION(__OSInitSram); +// PAL: 0x801a8dd4..0x801a90b4 +UNKNOWN_FUNCTION(UnlockSram); +// PAL: 0x801a90b4..0x801a90c4 +UNKNOWN_FUNCTION(__OSSyncSram); +// PAL: 0x801a90c4..0x801a91e8 +UNKNOWN_FUNCTION(__OSReadROM); +// PAL: 0x801a91e8..0x801a9260 +UNKNOWN_FUNCTION(OSGetWirelessID); +// PAL: 0x801a9260..0x801a92fc +UNKNOWN_FUNCTION(OSSetWirelessID); +// PAL: 0x801a92fc..0x801a9418 +UNKNOWN_FUNCTION(__OSGetRTCFlags); +// PAL: 0x801a9418..0x801a9528 +UNKNOWN_FUNCTION(__OSClearRTCFlags); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osSync.c b/source/rvl/os/osSync.c new file mode 100644 index 000000000..70c8954c0 --- /dev/null +++ b/source/rvl/os/osSync.c @@ -0,0 +1,57 @@ +#include "osSync.h" + +#include + +#include "osCache.h" + +// Symbol: SystemCallVector +// Function signature is unknown. +// PAL: 0x801a9528..0x801a9548 +MARK_BINARY_BLOB(SystemCallVector, 0x801a9528, 0x801a9548); +asm UNKNOWN_FUNCTION(SystemCallVector) { + // clang-format off + nofralloc; + mfspr r9, 0x3f0; + ori r10, r9, 8; + mtspr 0x3f0, r10; + isync; + sync; + mtspr 0x3f0, r9; + rfi; + nop; + // clang-format on +} + +// Symbol: __OSInitSystemCall +// Function signature is unknown. +// PAL: 0x801a9548..0x801a95a8 +MARK_BINARY_BLOB(__OSInitSystemCall, 0x801a9548, 0x801a95a8); +asm UNKNOWN_FUNCTION(__OSInitSystemCall) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + lis r4, 0x801b; + lis r5, 0x801b; + stw r0, 0x14(r1); + addi r4, r4, -27352; + addi r5, r5, -27324; + stw r31, 0xc(r1); + lis r31, 0x8000; + addi r3, r31, 0xc00; + subf r5, r4, r5; + bl memcpy; + addi r3, r31, 0xc00; + li r4, 0x100; + bl DCFlushRangeNoSync; + sync; + addi r3, r31, 0xc00; + li r4, 0x100; + bl ICInvalidateRange; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} diff --git a/source/rvl/os/osSync.h b/source/rvl/os/osSync.h new file mode 100644 index 000000000..7cd7248e5 --- /dev/null +++ b/source/rvl/os/osSync.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x801a9528..0x801a9548 +UNKNOWN_FUNCTION(SystemCallVector); +// PAL: 0x801a9548..0x801a95a8 +UNKNOWN_FUNCTION(__OSInitSystemCall); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/os/osThread.c b/source/rvl/os/osThread.c index 1bc897ddb..0a89b05f5 100644 --- a/source/rvl/os/osThread.c +++ b/source/rvl/os/osThread.c @@ -1,35 +1,11 @@ #include "osThread.h" #include "os.h" +#include "osAlarm.h" +#include "osContext.h" #include "osInterrupt.h" // Extern function references. -// PAL: 0x801a0610 -extern UNKNOWN_FUNCTION(OSCreateAlarm); -// PAL: 0x801a0870 -extern UNKNOWN_FUNCTION(OSSetAlarm); -// PAL: 0x801a0964 -extern UNKNOWN_FUNCTION(OSCancelAlarm); -// PAL: 0x801a0cf8 -extern UNKNOWN_FUNCTION(unk_801a0cf8); -// PAL: 0x801a0d8c -extern UNKNOWN_FUNCTION(OSSetAlarmUserData); -// PAL: 0x801a0d94 -extern UNKNOWN_FUNCTION(OSGetAlarmUserData); -// PAL: 0x801a1e70 -extern UNKNOWN_FUNCTION(OSSetCurrentContext); -// PAL: 0x801a1ecc -extern UNKNOWN_FUNCTION(OSGetCurrentContext); -// PAL: 0x801a1ed8 -extern UNKNOWN_FUNCTION(OSSaveContext); -// PAL: 0x801a1f58 -extern void OSLoadContext ( OSContext* context ); -// PAL: 0x801a2030 -extern UNKNOWN_FUNCTION(OSGetStackPointer); -// PAL: 0x801a2098 -extern UNKNOWN_FUNCTION(OSClearContext); -// PAL: 0x801a20bc -extern UNKNOWN_FUNCTION(OSInitContext); // PAL: 0x801a8088 extern UNKNOWN_FUNCTION(__OSUnlockAllMutex); @@ -1948,7 +1924,7 @@ asm void OSSleepTicks(OSTime ticks) { bl OSCreateAlarm; mr r4, r31; addi r3, r1, 8; - bl unk_801a0cf8; + bl OSSetAlarmTag; mr r4, r31; addi r3, r1, 8; bl OSSetAlarmUserData; diff --git a/source/rvl/os/osThread.h b/source/rvl/os/osThread.h index c01bbb3e0..5a3c8faaf 100644 --- a/source/rvl/os/osThread.h +++ b/source/rvl/os/osThread.h @@ -10,7 +10,6 @@ extern "C" { #endif #ifndef RII_CLIENT -u32 OSGetTick(); u32 __OSBusClock : (0x800000F8); #define OS_TIMER_CLOCK (__OSBusClock >> 2) @@ -22,49 +21,12 @@ typedef struct OSThread { char _310[0x318 - 0x310]; } OSThread; -typedef void* OSMessage; - -typedef struct OSMessageQueue { - char _[0x20]; -} OSMessageQueue; - typedef struct OSThreadQueue OSThreadQueue; struct OSThreadQueue { OSThread* head; OSThread* tail; }; -typedef struct OSContext { - // General-purpose registers - u32 gpr[32]; - - u32 cr; - u32 lr; - u32 ctr; - u32 xer; - - // Floating-point registers - f64 fpr[32]; - - u32 fpscr_pad; - u32 fpscr; - - // Exception handling registers - u32 srr0; - u32 srr1; - - // Context mode - u16 mode; // since UIMM is 16 bits in PPC - u16 state; // OR-ed OS_CONTEXT_STATE_* - - // Place Gekko regs at the end so we have minimal changes to - // existing code - u32 gqr[8]; - u32 psf_pad; - f64 psf[32]; - -} OSContext; - // PAL: 0x801a9e84..0x801aa0f0 int OSCreateThread(OSThread* thread, void* (*callable)(void*), void* user_data, void* stack, u32 stack_size, s32 prio, u16 flag); diff --git a/source/rvl/pad/pad.h b/source/rvl/pad/pad.h index 7299f5d8d..86f060d1f 100644 --- a/source/rvl/pad/pad.h +++ b/source/rvl/pad/pad.h @@ -3,14 +3,12 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif -typedef struct OSContext { - u32 unk[178]; -} OSContext; - // https://github.com/devkitPro/libogc/blob/bc4b778d558915aa40676e33514c4c9ba2af66b8/gc/ogc/pad.h#L46 // Size: 0x0B (arrays padded to 0x0C) typedef struct PADStatus { diff --git a/source/rvl/pad/rvlPad.c b/source/rvl/pad/rvlPad.c index aaa5a1293..849c3aae3 100644 --- a/source/rvl/pad/rvlPad.c +++ b/source/rvl/pad/rvlPad.c @@ -1,5 +1,8 @@ #include "pad.h" +#include +#include + // TODO: Move to osMisc.h typedef int (*OSResetFunction)(int final); typedef struct OSResetFunctionInfo { @@ -8,11 +11,10 @@ typedef struct OSResetFunctionInfo { u32 unk_08; u32 unk_0A; } OSResetFunctionInfo; -void OSSetCurrentContext(OSContext*); -void OSClearContext(OSContext*); + void OSRegisterVersion(const char*); -s64 OSGetTime(); void OSRegisterShutdownFunction(OSResetFunctionInfo* info); + // Broadway / IOS global locations: https://wiibrew.org/wiki/Memory_Map u8 oslow_30e3 : (0x800030e3); u16 oslow_30e0 : (0x800030e0); @@ -21,7 +23,6 @@ u16 oslow_30e0 : (0x800030e0); #include - // PAL: 0x80385b08 @sdata (pointer) // PAL: 0x8029cc80 @data (string literal) static const char* __PAD_VERSION = diff --git a/source/rvl/tpl/tpl.c b/source/rvl/tpl/tpl.c index bdbaae80f..19df56696 100644 --- a/source/rvl/tpl/tpl.c +++ b/source/rvl/tpl/tpl.c @@ -1,7 +1,7 @@ #include "tpl.h" #include -#include +#include void TPLBind(TPLPalette* pal) { if (pal->version != 2142000) diff --git a/sources.py b/sources.py index 29053935e..8b98645fe 100644 --- a/sources.py +++ b/sources.py @@ -54,6 +54,9 @@ class Source: SOURCES_RVL_ARC = [ Source(src="source/rvl/arc/rvlArchive.c", cc='4199_60831', opts=RVL_OPTS), ] +SOURCES_RVL_BASE = [ + Source(src="source/rvl/base/ppcArch.c", cc='4199_60831', opts=RVL_OPTS), +] SOURCES_RVL_FS = [ Source(src="source/rvl/fs/fs.c", cc='4199_60831', opts=RVL_OPTS), ] @@ -79,8 +82,24 @@ class Source: Source(src="source/rvl/nand/nand.c", cc='4199_60831', opts=RVL_OPTS), ] SOURCES_RVL_OS = [ + Source(src="source/rvl/os/osAlarm.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osAlloc.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osArena.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osAudio.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osCache.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osContext.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osError.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osFatal.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osFont.c", cc='4199_60831', opts=RVL_OPTS), Source(src="source/rvl/os/osInterrupt.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osLink.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osMemory.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osMessage.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osMutex.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osReboot.c", cc='4199_60831', opts=RVL_OPTS), Source(src="source/rvl/os/osReset.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osRtc.c", cc='4199_60831', opts=RVL_OPTS), + Source(src="source/rvl/os/osSync.c", cc='4199_60831', opts=RVL_OPTS), Source(src="source/rvl/os/osThread.c", cc='4199_60831', opts=RVL_OPTS), ] SOURCES_RVL_PAD = [ @@ -189,6 +208,7 @@ class Source: SOURCES_TRK, SOURCES_MSL_LIBC, SOURCES_RVL_ARC, + SOURCES_RVL_BASE, SOURCES_RVL_FS, SOURCES_RVL_IPC, SOURCES_RVL_MEM, From 189b0afa12a6460c71a6e559e220e0277d8f0111 Mon Sep 17 00:00:00 2001 From: Seeky Date: Mon, 9 Aug 2021 01:21:56 +0100 Subject: [PATCH 156/477] Fix disabling rel slices (#108) --- mkwutil/gen_asm.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index 2c30d3f3d..a15a02162 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -482,8 +482,7 @@ def gen_asm(regen_asm=False): rel_bin_dir = binary_dir / "rel" dump_staticr(rel, rel_bin_dir) # Map out slices in REL. - rel_slices = load_rel_slices(sections=REL_SECTIONS) - rel_slices.filter(SliceTable.ONLY_ENABLED) + rel_slices = load_rel_slices(sections=REL_SECTIONS).filter(SliceTable.ONLY_ENABLED) # Disassemble REL sections. rel_asm_dir = asm_dir / "rel" rel_asm_dir.mkdir(exist_ok=True) From aeea415180794478a93ad3c8cb0c1436d19c40c6 Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Tue, 10 Aug 2021 19:50:05 +0200 Subject: [PATCH 157/477] Finish decompiling eggDvdFile (#109) --- pack/dol.lcf.j2 | 24 +++++++-- pack/dol_objects.txt | 13 +++-- pack/dol_slices.csv | 1 + pack/symbols.txt | 3 ++ source/egg/core/eggArchive.cpp | 4 +- source/egg/core/eggDvdFile.cpp | 94 +++++++++++++++++++--------------- source/egg/core/eggDvdFile.hpp | 94 ++++++++++++++++------------------ source/egg/core/eggFile.hpp | 24 +++++---- source/rvl/os/osMessage.c | 10 ++-- source/rvl/os/osMessage.h | 11 ++-- source/rvl/rvlDvd.h | 54 ++++++++++++++++--- sources.py | 1 + 12 files changed, 205 insertions(+), 128 deletions(-) diff --git a/pack/dol.lcf.j2 b/pack/dol.lcf.j2 index 305c8d31d..445d485ea 100644 --- a/pack/dol.lcf.j2 +++ b/pack/dol.lcf.j2 @@ -45,11 +45,6 @@ FORCEFILES { } FORCEACTIVE { - -initialize__Q23EGG5VideoFPC15GXRenderModeObjPCPC15GXRenderModeObj -getTickPerVRetrace__Q23EGG5VideoFUl -getTickPerVRetrace__Q23EGG5VideoFv - // gsAvailable GSIStartAvailableCheckA //GSIAvailableCheckThink @@ -535,4 +530,23 @@ sakeSetFileUploadURL sakeGetFileResultFromHeaders sakeGetFileIdFromHeaders +// eggDvdFile +initialize__Q23EGG7DvdFileFv +__ct__Q23EGG7DvdFileFv +__dt__Q23EGG7DvdFileFv +initiate__Q23EGG7DvdFileFv +open__Q23EGG7DvdFileFl +open__Q23EGG7DvdFileFPCc +open__Q23EGG7DvdFileFPCcPv +close__Q23EGG7DvdFileFv +readData__Q23EGG7DvdFileFPvll +writeData__Q23EGG7DvdFileFPCvll +sync__Q23EGG7DvdFileFv +doneProcess__Q23EGG7DvdFileFlP11DVDFileInfo +getFileSize__Q23EGG7DvdFileCFv + +// eggVideo +initialize__Q23EGG5VideoFPC15GXRenderModeObjPCPC15GXRenderModeObj +getTickPerVRetrace__Q23EGG5VideoFUl +getTickPerVRetrace__Q23EGG5VideoFv } diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index c6ed75748..7f1d283a4 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -282,14 +282,19 @@ out/eggArchive.o out/dol/text_8020fcc4_8021a0f0.o out/dol/data_802a268c_802a2b48.o out/eggDisposer.o -out/dol/text_8021a1b8_802269a8.o -out/dol/data_802a2b54_802a2ff8.o +out/dol/text_8021a1b8_8022231c.o +out/dol/data_802a2b54_802a2da0.o +out/dol/bss_803832e4_80384190.o +out/dol/sbss_80386d84_80386e18.o +out/eggDvdFile.o +out/dol/text_802226d8_802269a8.o +out/dol/data_802a2dc8_802a2ff8.o out/eggExpHeap.o out/dol/text_80226f04_80229540.o out/dol/rodata_8025771a_80257740.o out/dol/data_802a3024_802a30b0.o -out/dol/bss_803832e4_80384320.o -out/dol/sbss_80386d84_80386e90.o +out/dol/bss_803841a0_80384320.o +out/dol/sbss_80386e20_80386e90.o out/eggGraphicsFifo.o out/dol/data_802a30bc_802a30c0.o out/dol/sbss_80386e99_80386ea0.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 86a4f5633..1c381f050 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -117,6 +117,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680,,,,,,,,,, 1,,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, 1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54,,,,,,,,,, +1,1,source/egg/core/eggDvdFile.cpp,,,,,,,0x8022231c,0x802226d8,,,,,,,0x802a2da0,0x802a2dc8,0x80384190,0x803841a0,,,0x80386e18,0x80386e20,,,, 1,,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, 1,,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, 1,,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68,0x80388D80,, diff --git a/pack/symbols.txt b/pack/symbols.txt index 7f711f917..ad898cd2e 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -883,8 +883,11 @@ 0x8015ddec __DSP_insert_task 0x8015de88 __DSP_remove_task 0x8015df1c __DVDFSInit +0x8015df4c DVDConvertPathToEntrynum +0x8015e254 DVDFastOpen 0x8015e2bc DVDOpen 0x8015e568 DVDClose +0x8015e74c DVDReadAsyncPrio 0x8015e834 DVDReadPrio 0x801628cc DVDReadAbsAsyncPrio 0x801629b0 DVDInquiryAsync diff --git a/source/egg/core/eggArchive.cpp b/source/egg/core/eggArchive.cpp index 5681d20ae..5b974d47f 100644 --- a/source/egg/core/eggArchive.cpp +++ b/source/egg/core/eggArchive.cpp @@ -109,7 +109,7 @@ void Archive::getFile(const char* path, FileInfo* pInfo) { // Reads header from u8 file on disc. Then reads header and allocates file based // on that filesize. void* Archive::loadFromDisc(const char* path, Heap* pHeap, int align) { - rvlDvdFile dvdFileInfo; + DVDFileInfo dvdFileInfo; int alignRounded = align > 0 ? -32 : 32; // r31 if (!DVDOpen(path, &dvdFileInfo)) return nullptr; @@ -119,7 +119,7 @@ void* Archive::loadFromDisc(const char* path, Heap* pHeap, int align) { sizeof(rvlArchiveHeader), alignRounded); // r30 if (readHeader != nullptr) { - if (DVDReadPrio(&dvdFileInfo, readHeader, 32, 0, 2) >= 32) { + if ((u32)DVDReadPrio(&dvdFileInfo, readHeader, 32, 0, 2) >= 32) { u32 arcSize = ROUND_UP(readHeader->nodes.size + sizeof(rvlArchiveHeader), 32); ARC = pHeap->alloc(arcSize, align); // r31 diff --git a/source/egg/core/eggDvdFile.cpp b/source/egg/core/eggDvdFile.cpp index 186f4afb7..9f348e095 100644 --- a/source/egg/core/eggDvdFile.cpp +++ b/source/egg/core/eggDvdFile.cpp @@ -1,5 +1,6 @@ -#include -#include +#include + +#include namespace EGG { @@ -13,82 +14,93 @@ void DvdFile::initialize() { } } -DvdFile::DvdFile() : mIsOpen(false) { create(); } +DvdFile::DvdFile() { initiate(); } DvdFile::~DvdFile() { close(); } void DvdFile::initiate() { - this->_78 = this; - OSInitMutex(&this->Mutex_08); - OSInitMutex(&this->Mutex_20); - OSInitMessageQueue(&this->MessageQueue_A0, &this->_C0, 1); - OSInitMessageQueue(&this->MessageQueue_7C, &this->_9C, 1); - - this->_C4 = 0; - this->_38 = 0; + mFileInfo.dvdFile = this; + OSInitMutex(&mMutex); + OSInitMutex(&_20); + OSInitMessageQueue(&mMsgQueue, &mMsg, 1); + OSInitMessageQueue(&_7C, &_9C, 1); + mThread = nullptr; + _38 = 0; } bool DvdFile::open(s32 entryNum) { - if (!this->mIsOpen && entryNum != -1) { - if (mFileInfo.open(entryNum)) { + if (!mIsOpen && entryNum != -1) { + mIsOpen = DVDFastOpen(entryNum, &mFileInfo); + if (mIsOpen) { nw4r::ut::List_Append(&sDvdList, this); - DVDGetCommandBlockStatus(&mFileInfo.cb); + DVDGetFileInfoStatus(&mFileInfo); } } return mIsOpen; } -bool DvdFile::open(const char* filename) { - return this->open(DVDConvertPathToEntrynum(filename)); +bool DvdFile::open(const char* path) { + s32 entrynum = DVDConvertPathToEntrynum(path); + return open(entrynum); } -bool DvdFile::open(char* path) { return open((const char*)path); } +bool DvdFile::open(const char* path, void*) { return open(path); } void DvdFile::close() { - if (this->mIsOpen && DVDClose(&this->mFileInfo)) { - this->mIsOpen = false; + if (mIsOpen && DVDClose(&mFileInfo)) { + mIsOpen = false; nw4r::ut::List_Remove(&sDvdList, this); } } -int DvdFile::readData(void* addr, int len, int offset) { - OSLockMutex(&this->Mutex_08); - if (this->_C4 != 0) { - OSUnlockMutex(&this->Mutex_08); +s32 DvdFile::readData(void* buffer, s32 length, s32 offset) { + OSLockMutex(&mMutex); + + if (mThread != nullptr) { + OSUnlockMutex(&mMutex); return -1; } - this->_C4 = OSGetCurrentThread(); - int r31 = (void*)-1; - if (DVDReadAsyncPrio(&this->mFileInfo, addr, len, offset, - (DVDCallback)&doneProcess, 2)) - r31 = this->sync(); - this->_C4 = 0; - OSUnlockMutex(&this->Mutex_08); - return r31; + + mThread = OSGetCurrentThread(); + int result = -1; + if (DVDReadAsyncPrio(&mFileInfo, buffer, length, offset, &doneProcess, 2)) { + result = sync(); + } + mThread = nullptr; + + OSUnlockMutex(&mMutex); + return result; } -int DvdFile::writeData(const void*, int, int) { +s32 DvdFile::writeData(const void* buffer, s32 length, s32 offset) { + UNUSED_PARAM(buffer) + UNUSED_PARAM(length) + UNUSED_PARAM(offset) + return -1; // You can't write to the Dvd! } -void DvdFile::sync() { - OSLockMutex(&this->Mutex_08); +s32 DvdFile::sync() { + OSLockMutex(&mMutex); OSMessage message; - OSReceiveMessage(&this->MessageQueue_A0, &message, 1); + OSReceiveMessage(&mMsgQueue, &message, OS_MESSAGE_BLOCK); - _C4 = 0; + mThread = nullptr; - OSUnlockMutex(&this->Mutex_08); + OSUnlockMutex(&mMutex); - return message; + s32 result = reinterpret_cast(message); + return result; } -void DvdFile::doneProcess(int result, DVDFileInfo* fileInfo) { - OSSendMessage(&fileInfo->_3C->_A0, this, 0); +void DvdFile::doneProcess(s32 result, DVDFileInfo* fileInfo) { + DvdFile* dvdFile = static_cast(fileInfo)->dvdFile; + OSMessage message = reinterpret_cast(result); + OSSendMessage(&dvdFile->mMsgQueue, message, OS_MESSAGE_NOBLOCK); } -int DvdFile::getFileSize() const { return mFileInfo.length; } +u32 DvdFile::getFileSize() const { return mFileInfo.length; } } // namespace EGG diff --git a/source/egg/core/eggDvdFile.hpp b/source/egg/core/eggDvdFile.hpp index 0f3503eec..4781cb8d3 100644 --- a/source/egg/core/eggDvdFile.hpp +++ b/source/egg/core/eggDvdFile.hpp @@ -1,20 +1,22 @@ #pragma once -#include +#include + #include -#include -typedef void* OSMessage; +#include +#include +#include +#include namespace EGG { class DvdFile : public File { public: //! @brief Initialize static members. - //! static void initialize(); - //! @brief Sets mIsOpen to false then calls CT + //! @brief Sets mIsOpen to false then calls initiate. DvdFile(); private: @@ -24,73 +26,65 @@ class DvdFile : public File { //! @brief Closes the file on the DVD. ~DvdFile() override; - void initiate(); - - //! @brief Opens a file given the entry number. - //! - //! @returns If the file successfully opened (mIsOpen) - //! - bool open(s32 entryNum) override; - //! @brief Opens a file given the path. //! //! @details Calculates the entry number and calls down to open(int entryNum) //! //! @returns Whether or not the file successfully opened. - //! bool open(const char* path) override; - //! @brief just calls down to open(const char* path) - bool open(char* path) override; - - //! @brief Closes the file and removes this* from sDvdList + //! @brief Closes the file and removes this* from sDvdList. void close() override; - //! @brief Reads the data synchronously????. - //! TODO. FINISH THIS - int readData(void* addr, int len, int offset) override; + //! @brief Reads the data synchronously. + //! + //! @returns The number of bytes read on success, -1 on error. + s32 readData(void* buffer, s32 length, s32 offset) override; //! @brief Always fails. The DVD is read-only. - int writeData(const void*, int, int) override; - - //! @brief TODO - int sync(); - - //! @brief Callback to DVDReadAsyncPrio - //! @brief TODO - static void doneProcess(int result, DVDFileInfo* fileInfo); + //! + //! @returns Always -1. + s32 writeData(const void* buffer, s32 length, s32 offset) override; //! @brief Get the filesize. + //! //! @returns The length attribute of the DVD fileInfo. - int getFileSize() const override; + u32 getFileSize() const override; + + //! @brief Opens a file given the entry number. + //! + //! @returns If the file successfully opened (mIsOpen). + virtual bool open(s32 entrynum); + + //! @brief Just calls down to open(const char* path). + //! + //! @returns If the file successfully opened (mIsOpen). + virtual bool open(const char* path, void*); private: + void initiate(); + + s32 sync(); + + static void doneProcess(s32 result, DVDFileInfo* fileInfo); + static bool sIsInitialized; static nw4r::ut::List sDvdList; - struct FileInfo : public DVDFileInfo { - bool fastOpen(s32 resolved) { - return DVDFastOpen(resolved, this); - } + DvdFile* dvdFile; }; - bool mIsOpen; // 04 - // 3B implicit pad - OSMutex Mutex_08; // sizeof=0x18 - OSMutex Mutex_20; // sizeof=0x18 - int _38; // set to 0 in ct - FileInfo mFileInfo; // [+0x3C] sizeof=0x3C - DvdFile* _78; // set to this* in ct - - OSMessageQueue MessageQueue_7C; // sizeof=0x20 - OSMessage _9C; - - OSMessageQueue MessageQueue_A0; // sizeof=0x20 - OSMessage _C0; - - OSThread* _C4; - + OSMutex mMutex; + OSMutex _20; // unused + unk32 _38; // unused + FileInfo mFileInfo; + OSMessageQueue _7C; // unused + OSMessage _9C; // unused + OSMessageQueue mMsgQueue; + OSMessage mMsg; + OSThread* mThread; nw4r::ut::Node mNode; }; + } // namespace EGG diff --git a/source/egg/core/eggFile.hpp b/source/egg/core/eggFile.hpp index a141834f3..95e295862 100644 --- a/source/egg/core/eggFile.hpp +++ b/source/egg/core/eggFile.hpp @@ -1,19 +1,23 @@ #pragma once +#include + namespace EGG { class File { public: - virtual ~File() = 0; //!< [vt+0x08] - virtual int open(const char* path) = 0; //!< [vt+0x0C] - virtual void close() = 0; //!< [vt+0x10] - virtual int readData(void*, int, int) = 0; //!< [vt+0x14] - virtual int writeData(const void*, int, int) = 0; //!< [vt+0x18] - virtual int getFileSize() const = 0; //!< [vt+0x1C] + File() : mIsOpen(false) {} + virtual ~File() {} + virtual bool open(const char* path) = 0; + virtual void close() = 0; + virtual s32 readData(void* buffer, s32 length, s32 offset) = 0; + virtual s32 writeData(const void* buffer, s32 length, s32 offset) = 0; + virtual u32 getFileSize() const = 0; + + bool mIsOpen; - // Look DVD added.. - virtual int open(int entryNum) = 0; //!< [vt+0x20] - virtual int open(char* path) = 0; //!< [vt+0x24] +private: + u8 pad[3]; }; -} // namespace EGG \ No newline at end of file +} // namespace EGG diff --git a/source/rvl/os/osMessage.c b/source/rvl/os/osMessage.c index 9f03a20dd..eb29ecaf6 100644 --- a/source/rvl/os/osMessage.c +++ b/source/rvl/os/osMessage.c @@ -6,8 +6,8 @@ // Symbol: OSInitMessageQueue // PAL: 0x801a72fc..0x801a735c MARK_BINARY_BLOB(OSInitMessageQueue, 0x801a72fc, 0x801a735c); -asm void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, - s32 capacity) { +asm void OSInitMessageQueue(OSMessageQueue* mq, OSMessage* msgArray, + s32 msgCount) { // clang-format off nofralloc; stwu r1, -0x20(r1); @@ -38,10 +38,9 @@ asm void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, } // Symbol: OSSendMessage -// Function signature is unknown. // PAL: 0x801a735c..0x801a7424 MARK_BINARY_BLOB(OSSendMessage, 0x801a735c, 0x801a7424); -asm UNKNOWN_FUNCTION(OSSendMessage) { +asm int OSSendMessage(OSMessageQueue* mq, OSMessage msg, s32 flags) { // clang-format off nofralloc; stwu r1, -0x20(r1); @@ -102,10 +101,9 @@ asm UNKNOWN_FUNCTION(OSSendMessage) { } // Symbol: OSReceiveMessage -// Function signature is unknown. // PAL: 0x801a7424..0x801a7500 MARK_BINARY_BLOB(OSReceiveMessage, 0x801a7424, 0x801a7500); -asm UNKNOWN_FUNCTION(OSReceiveMessage) { +asm int OSReceiveMessage(OSMessageQueue* mq, OSMessage* msg, s32 flags) { // clang-format off nofralloc; stwu r1, -0x20(r1); diff --git a/source/rvl/os/osMessage.h b/source/rvl/os/osMessage.h index de53871a2..9e1433561 100644 --- a/source/rvl/os/osMessage.h +++ b/source/rvl/os/osMessage.h @@ -14,15 +14,18 @@ typedef struct OSMessageQueue { char _[0x20]; } OSMessageQueue; +#define OS_MESSAGE_NOBLOCK 0 +#define OS_MESSAGE_BLOCK 1 + // PAL: 0x801a72fc..0x801a735c -void OSInitMessageQueue(OSMessageQueue* queue, OSMessage* buffer, s32 capacity); +void OSInitMessageQueue(OSMessageQueue* mq, OSMessage* msgArray, s32 msgCount); // PAL: 0x801a735c..0x801a7424 -UNKNOWN_FUNCTION(OSSendMessage); +int OSSendMessage(OSMessageQueue* mq, OSMessage msg, s32 flags); // PAL: 0x801a7424..0x801a7500 -UNKNOWN_FUNCTION(OSReceiveMessage); +int OSReceiveMessage(OSMessageQueue* mq, OSMessage* msg, s32 flags); // PAL: 0x801a7500..0x801a75d0 UNKNOWN_FUNCTION(OSJamMessage); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/source/rvl/rvlDvd.h b/source/rvl/rvlDvd.h index 58502ce60..d1aac617f 100644 --- a/source/rvl/rvlDvd.h +++ b/source/rvl/rvlDvd.h @@ -4,14 +4,56 @@ extern "C" { #endif -struct rvlDvdFile { - char _[0x3c]; +typedef struct DVDDiskID { + char gameName[4]; + char company[2]; + u8 diskNumber; + u8 gameVersion; + u8 streaming; + u8 streamingBufSize; + u8 padding[14]; + u32 rvlMagic; + u32 gcMagic; +} DVDDiskID; + +typedef struct DVDCommandBlock DVDCommandBlock; +typedef void (*DVDCBCallback)(s32 result, DVDCommandBlock* block); + +typedef struct DVDCommandBlock { + DVDCommandBlock* next; + DVDCommandBlock* prev; + u32 command; + s32 state; + u32 offset; + u32 length; + void* addr; + u32 currTransferSize; + u32 transferredSize; + DVDDiskID* id; + DVDCBCallback callback; + void* userData; +} DVDCommandBlock; + +typedef struct DVDFileInfo DVDFileInfo; + +typedef void (*DVDCallback)(s32 result, DVDFileInfo* fileInfo); + +struct DVDFileInfo { + DVDCommandBlock cb; + u32 startAddr; + u32 length; + DVDCallback callback; }; -unk32 DVDOpen(const char*, rvlDvdFile*); -u32 DVDReadPrio(rvlDvdFile*, void*, u32, unk32, unk32); -unk32 DVDClose(rvlDvdFile*); +int DVDOpen(const char* fileName, DVDFileInfo* fileInfo); +int DVDFastOpen(s32 entrynum, DVDFileInfo* fileInfo); +s32 DVDReadPrio(DVDFileInfo* fileInfo, void* addr, s32 length, s32 offset, s32 prio); +int DVDReadAsyncPrio(DVDFileInfo* fileInfo, void* addr, s32 length, s32 offset, DVDCallback callback, s32 prio); +int DVDClose(DVDFileInfo* fileInfo); +s32 DVDGetCommandBlockStatus(const DVDCommandBlock* block); +#define DVDGetFileInfoStatus(fileinfo) DVDGetCommandBlockStatus(&(fileinfo)->cb) +s32 DVDConvertPathToEntrynum(const char* pathPtr); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/sources.py b/sources.py index 8b98645fe..878ce4d20 100644 --- a/sources.py +++ b/sources.py @@ -186,6 +186,7 @@ class Source: Source(src="source/egg/core/eggAllocator.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggArchive.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggDisposer.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggDvdFile.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggExpHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggGraphicsFifo.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -ipa file -use_lmw_stmw=on "), From 0f4c5258d8fa19403fe71f3cdc2740ed4f42a80a Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 12 Aug 2021 12:35:27 -0600 Subject: [PATCH 158/477] Split eggViewport, eggXfb, eggXfbManager --- mkwutil/gen_asm.py | 9 +- pack/dol_objects.txt | 5 +- pack/dol_slices.csv | 5 +- pack/symbols.txt | 11 ++ source/egg/core/eggViewport.cpp | 97 ++++++++++++++++ source/egg/core/eggViewport.hpp | 2 + source/egg/core/eggXfb.cpp | 67 +++++++++++ source/egg/core/eggXfb.hpp | 18 +++ source/egg/core/eggXfbManager.cpp | 177 ++++++++++++++++++++++++++++++ source/egg/core/eggXfbManager.hpp | 22 ++++ source/egg/math/eggBoundBox.hpp | 2 + sources.py | 5 +- 12 files changed, 412 insertions(+), 8 deletions(-) create mode 100644 source/egg/core/eggViewport.cpp create mode 100644 source/egg/core/eggXfb.cpp create mode 100644 source/egg/core/eggXfb.hpp create mode 100644 source/egg/core/eggXfbManager.cpp create mode 100644 source/egg/core/eggXfbManager.hpp diff --git a/mkwutil/gen_asm.py b/mkwutil/gen_asm.py index a15a02162..99751f094 100644 --- a/mkwutil/gen_asm.py +++ b/mkwutil/gen_asm.py @@ -107,7 +107,7 @@ def addr_in_sym(addr, sym): class CAsmGenerator: """Generates C files with assembly functions.""" - def __init__(self, data, _slice, symbols, out_h, out_c): + def __init__(self, data, _slice, symbols, out_h, out_c, cpp_mode): self.data = data self.slice = _slice self.symbols = symbols @@ -115,6 +115,7 @@ def __init__(self, data, _slice, symbols, out_h, out_c): self.own_symbols.derive_sizes(self.slice.stop) self.out_h = out_h self.out_c = out_c + self.cpp_mode = cpp_mode # The list of seen extern functions. self.extern_functions_seen = set() self.extern_functions = list() @@ -150,6 +151,8 @@ def dump_section(self): } ) + # TODO: extern "C" rather than just extern when self.cpp_mode + # Sort extern functions. self.extern_functions.sort(key=lambda sym: sym.addr) # Write out to C file. @@ -350,7 +353,7 @@ def __gen_c(self, _slice: Slice): c_path = Path(_slice.name) if c_path.exists(): return - h_path = c_path.with_suffix(".h") + h_path = c_path.with_suffix(".h" if str(c_path).endswith(".c") else ".hpp") if h_path.exists(): return @@ -358,7 +361,7 @@ def __gen_c(self, _slice: Slice): print(f" => {_slice.name}") data = self.dol.virtual_read(_slice.start, len(_slice)) with open(h_path, "w") as h_file, open(c_path, "w") as c_file: - gen = CAsmGenerator(data, _slice, self.symbols, h_file, c_file) + gen = CAsmGenerator(data, _slice, self.symbols, h_file, c_file, not str(c_path).endswith(".c")) gen.dump_section() def __gen_asm(self, section: Section, _slice: Slice): diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 7f1d283a4..06960a14c 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -326,7 +326,10 @@ out/dol/bss_80384bf4_80384c00.o out/dol/sbss_80386f90_80386fa0.o out/dol/sdata2_80389104_80389108.o out/eggVideo.o -out/dol/text_80244074_80244de0.o +out/eggViewport.o +out/eggXfb.o +out/eggXfbManager.o +out/dol/text_802443ac_80244de0.o out/dol/rodata_80258560_80258580.o out/dol/sdata2_80389118_80389140.o out/dol/sbss2_80389140_8038917c.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 1c381f050..339f3a723 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -130,5 +130,6 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/egg/core/eggUnitHeap.cpp,,,,,,,0x80243754,0x80243A00,,,,,,,0x802A3FD8,0x802A4004,,,,,,,,,, 1,,source/egg/math/eggVector.cpp,,,,,,,0x80243A00,0x80243D18,0x80244E88,0x80244E8C,,,,,,,0x80384B70,0x80384BF4,,,0x80386F78,0x80386F90,0x803890F8,0x80389104,, 1,1,source/egg/core/eggVideo.cpp,,,,,,,0x80243D18,0x80244074,,,,,0x802582E0,0x80258560,,,,,,,,,0x80389108,0x80389118,, -,,eggXfb.cpp,,,,,,,0x80244160,0x80244200,,,,,,,,,,,,,,,,,, -,,eggXfbManager.cpp,,,,,,,0x80244200,0x802443AC,,,,,,,,,,,,,,,,,, +1,,source/egg/core/eggViewport.cpp,,,,,,,0x80244074,0x80244160,,,,,,,,,,,,,,,,,, +1,,source/egg/core/eggXfb.cpp,,,,,,,0x80244160,0x80244200,,,,,,,,,,,,,,,,,, +1,,source/egg/core/eggXfbManager.cpp,,,,,,,0x80244200,0x802443AC,,,,,,,,,,,,,,,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index ad898cd2e..058da0622 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -1,3 +1,14 @@ +0x80244074 __ct__Q23EGG8ViewportFv +0x802440B4 set__Q23EGG8ViewportFiiii +0x80244134 eggViewport_Calc_TODO +0x80244160 __ct__Q23EGG3XfbFPQ23EGG4Heap +0x802441EC EGG__Xfb__CalcXfbSize +0x80244200 attach__Q23EGG10XfbManagerFPQ23EGG3Xfb +0x80244268 copyEFB__Q23EGG10XfbManagerFb +0x802442E8 setNextFrameBuffer__Q23EGG10XfbManagerFv +0x80244350 postVRetrace__Q23EGG10XfbManagerFv +0x802145C0 __ct__Q23EGG10BoundBox2fFv +0x80229E04 __nwa__FUlPQ23EGG4Heapi 0x80005f34 memcpy 0x80005f84 __fill_mem 0x80006038 memset diff --git a/source/egg/core/eggViewport.cpp b/source/egg/core/eggViewport.cpp new file mode 100644 index 000000000..36c5a5333 --- /dev/null +++ b/source/egg/core/eggViewport.cpp @@ -0,0 +1,97 @@ +#include "eggViewport.hpp" + +// Extern function references. +// PAL: 0x802145c0 +extern "C" UNKNOWN_FUNCTION(__ct__Q23EGG10BoundBox2fFv); + +// Symbol: __ct__Q23EGG8ViewportFv +// Function signature is unknown. +// PAL: 0x80244074..0x802440b4 +MARK_BINARY_BLOB(__ct__Q23EGG8ViewportFv, 0x80244074, 0x802440b4); +asm UNKNOWN_FUNCTION(__ct__Q23EGG8ViewportFv) { +// clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r3; + bl __ct__Q23EGG10BoundBox2fFv; + lfs f0, -0x5e88(r2); + mr r3, r31; + stfs f0, 0x10(r31); + stfs f0, 0x14(r31); + stfs f0, 0x18(r31); + lwz r31, 0xc(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +// clang-format on +} + +extern "C" void eggViewport_Calc_TODO(); + +// Symbol: set__Q23EGG8ViewportFiiii +// Function signature is unknown. +// PAL: 0x802440b4..0x80244134 +MARK_BINARY_BLOB(set__Q23EGG8ViewportFiiii, 0x802440b4, 0x80244134); +asm UNKNOWN_FUNCTION(set__Q23EGG8ViewportFiiii) { +// clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + lis r9, 0x4330; + xoris r8, r4, 0x8000; + stw r9, 8(r1); + xoris r5, r5, 0x8000; + lfd f4, -0x5e80(r2); + xoris r4, r6, 0x8000; + stw r8, 0xc(r1); + lfd f0, 8(r1); + stw r9, 0x10(r1); + fsubs f3, f0, f4; + stw r0, 0x24(r1); + xoris r0, r7, 0x8000; + stw r5, 0x14(r1); + lfd f0, 0x10(r1); + stw r4, 0xc(r1); + fsubs f2, f0, f4; + stw r0, 0x14(r1); + lfd f1, 8(r1); + lfd f0, 0x10(r1); + fsubs f1, f1, f4; + stfs f3, 0(r3); + fsubs f0, f0, f4; + stfs f2, 4(r3); + stfs f1, 8(r3); + stfs f0, 0xc(r3); + bl eggViewport_Calc_TODO; + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; +// clang-format on +} + +// Symbol: eggViewport_Calc_TODO +// Function signature is unknown. +// PAL: 0x80244134..0x80244160 +MARK_BINARY_BLOB(eggViewport_Calc_TODO, 0x80244134, 0x80244160); +asm UNKNOWN_FUNCTION(eggViewport_Calc_TODO) { +// clang-format off + nofralloc; + lfs f3, 8(r3); + lfs f2, 0(r3); + lfs f1, 0xc(r3); + lfs f0, 4(r3); + fsubs f2, f3, f2; + fsubs f0, f1, f0; + stfs f2, 0x10(r3); + stfs f0, 0x14(r3); + fdivs f0, f2, f0; + stfs f0, 0x18(r3); + blr; +// clang-format on +} + diff --git a/source/egg/core/eggViewport.hpp b/source/egg/core/eggViewport.hpp index d4e91591e..85939da32 100644 --- a/source/egg/core/eggViewport.hpp +++ b/source/egg/core/eggViewport.hpp @@ -8,6 +8,8 @@ #include #include +#include "decomp.h" + namespace EGG { // assumed names diff --git a/source/egg/core/eggXfb.cpp b/source/egg/core/eggXfb.cpp new file mode 100644 index 000000000..cb26baefd --- /dev/null +++ b/source/egg/core/eggXfb.cpp @@ -0,0 +1,67 @@ +#include "eggXfb.hpp" + +// Extern function references. +// PAL: 0x80229e04 +extern "C" UNKNOWN_FUNCTION(__nwa__FUlPQ23EGG4Heapi); + +// Symbol: __ct__Q23EGG3XfbFPQ23EGG4Heap +// Function signature is unknown. +// PAL: 0x80244160..0x802441ec +MARK_BINARY_BLOB(__ct__Q23EGG3XfbFPQ23EGG4Heap, 0x80244160, 0x802441ec); +asm UNKNOWN_FUNCTION(__ct__Q23EGG3XfbFPQ23EGG4Heap) { +// clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + lwz r3, -0x5ca0(r13); + lwz r12, 0(r3); + lwz r12, 8(r12); + mtctr r12; + bctrl; + lwz r3, 0(r3); + lhz r4, 8(r3); + lhz r3, 4(r3); + sth r3, 0(r30); + sth r4, 2(r30); + bl EGG__Xfb__CalcXfbSize; + cmpwi r31, 0; + bne lbl_802441b4; + lwz r31, -0x5d60(r13); +lbl_802441b4: + mr r4, r31; + li r5, 0x20; + bl __nwa__FUlPQ23EGG4Heapi; + li r0, 0; + stw r3, 4(r30); + mr r3, r30; + stw r0, 0xc(r30); + stw r0, 8(r30); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +// clang-format on +} + +// Symbol: EGG__Xfb__CalcXfbSize +// Function signature is unknown. +// PAL: 0x802441ec..0x80244200 +MARK_BINARY_BLOB(EGG__Xfb__CalcXfbSize, 0x802441ec, 0x80244200); +asm UNKNOWN_FUNCTION(EGG__Xfb__CalcXfbSize) { +// clang-format off + nofralloc; + addi r0, r3, 0xf; + rlwinm r0, r0, 0, 0x10, 0x1b; + mullw r0, r0, r4; + slwi r3, r0, 1; + blr; +// clang-format on +} + diff --git a/source/egg/core/eggXfb.hpp b/source/egg/core/eggXfb.hpp new file mode 100644 index 000000000..bc34367af --- /dev/null +++ b/source/egg/core/eggXfb.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x80244160..0x802441ec +UNKNOWN_FUNCTION(__ct__Q23EGG3XfbFPQ23EGG4Heap); +// PAL: 0x802441ec..0x80244200 +UNKNOWN_FUNCTION(EGG__Xfb__CalcXfbSize); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/egg/core/eggXfbManager.cpp b/source/egg/core/eggXfbManager.cpp new file mode 100644 index 000000000..ba0226c88 --- /dev/null +++ b/source/egg/core/eggXfbManager.cpp @@ -0,0 +1,177 @@ +#include "eggXfbManager.hpp" + +// Extern function references. +// PAL: 0x8016e654 +extern "C" UNKNOWN_FUNCTION(GXFlush); +// PAL: 0x8016fc38 +extern "C" UNKNOWN_FUNCTION(GXCopyDisp); +// PAL: 0x801727cc +extern "C" UNKNOWN_FUNCTION(GXSetColorUpdate); +// PAL: 0x801727f8 +extern "C" UNKNOWN_FUNCTION(GXSetAlphaUpdate); +// PAL: 0x80172824 +extern "C" UNKNOWN_FUNCTION(GXSetZMode); +// PAL: 0x801a65ac +extern "C" UNKNOWN_FUNCTION(OSDisableInterrupts); +// PAL: 0x801a65d4 +extern "C" UNKNOWN_FUNCTION(OSRestoreInterrupts); +// PAL: 0x801ba9a4 +extern "C" UNKNOWN_FUNCTION(VIFlush); +// PAL: 0x801baab8 +extern "C" UNKNOWN_FUNCTION(VISetNextFrameBuffer); +// PAL: 0x801bab24 +extern "C" UNKNOWN_FUNCTION(VIGetNextFrameBuffer); + +// Symbol: attach__Q23EGG10XfbManagerFPQ23EGG3Xfb +// Function signature is unknown. +// PAL: 0x80244200..0x80244268 +MARK_BINARY_BLOB(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb, 0x80244200, 0x80244268); +asm UNKNOWN_FUNCTION(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb) { +// clang-format off + nofralloc; + cmpwi r4, 0; + li r0, 0; + beq lbl_80244260; + lwz r5, 0(r3); + cmpwi r5, 0; + bne lbl_8024422c; + stw r4, 0(r3); + stw r4, 4(r3); + stw r4, 8(r4); + stw r4, 0xc(r4); + b lbl_8024425c; +lbl_8024422c: + lwz r5, 0xc(r5); + stw r4, 8(r5); + lwz r5, 0(r3); + lwz r0, 0xc(r5); + stw r0, 0xc(r4); + lwz r5, 0(r3); + stw r4, 0xc(r5); + lwz r0, 0(r3); + stw r0, 8(r4); + lwz r4, 0(r3); + lwz r0, 8(r4); + stw r0, 4(r3); +lbl_8024425c: + li r0, 1; +lbl_80244260: + mr r3, r0; + blr; +// clang-format on +} + +// Symbol: copyEFB__Q23EGG10XfbManagerFb +// Function signature is unknown. +// PAL: 0x80244268..0x802442e8 +MARK_BINARY_BLOB(copyEFB__Q23EGG10XfbManagerFb, 0x80244268, 0x802442e8); +asm UNKNOWN_FUNCTION(copyEFB__Q23EGG10XfbManagerFb) { +// clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + cmpwi r4, 0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r4; + stw r30, 8(r1); + mr r30, r3; + beq lbl_802442ac; + li r3, 1; + li r4, 7; + li r5, 1; + bl GXSetZMode; + li r3, 1; + bl GXSetAlphaUpdate; + li r3, 1; + bl GXSetColorUpdate; +lbl_802442ac: + lwz r3, 4(r30); + clrlwi r4, r31, 0x18; + lwz r3, 4(r3); + bl GXCopyDisp; + bl GXFlush; + lwz r3, 4(r30); + stw r3, 8(r30); + lwz r0, 8(r3); + stw r0, 4(r30); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +// clang-format on +} + +// Symbol: setNextFrameBuffer__Q23EGG10XfbManagerFv +// Function signature is unknown. +// PAL: 0x802442e8..0x80244350 +MARK_BINARY_BLOB(setNextFrameBuffer__Q23EGG10XfbManagerFv, 0x802442e8, 0x80244350); +asm UNKNOWN_FUNCTION(setNextFrameBuffer__Q23EGG10XfbManagerFv) { +// clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + bl OSDisableInterrupts; + lwz r4, 8(r30); + mr r31, r3; + cmpwi r4, 0; + beq lbl_80244330; + lwz r3, 4(r4); + bl VISetNextFrameBuffer; + bl VIFlush; + lwz r3, 8(r30); + li r0, 0; + stw r3, 0xc(r30); + stw r0, 8(r30); +lbl_80244330: + mr r3, r31; + bl OSRestoreInterrupts; + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +// clang-format on +} + +// Symbol: postVRetrace__Q23EGG10XfbManagerFv +// Function signature is unknown. +// PAL: 0x80244350..0x802443ac +MARK_BINARY_BLOB(postVRetrace__Q23EGG10XfbManagerFv, 0x80244350, 0x802443ac); +asm UNKNOWN_FUNCTION(postVRetrace__Q23EGG10XfbManagerFv) { +// clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + stw r30, 8(r1); + mr r30, r3; + lwz r4, 0xc(r3); + cmpwi r4, 0; + beq lbl_80244394; + lwz r31, 4(r4); + bl VIGetNextFrameBuffer; + cmplw r31, r3; + bne lbl_80244394; + lwz r3, 0xc(r30); + li r0, 0; + stw r3, 0(r30); + stw r0, 0xc(r30); +lbl_80244394: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + lwz r30, 8(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +// clang-format on +} + diff --git a/source/egg/core/eggXfbManager.hpp b/source/egg/core/eggXfbManager.hpp new file mode 100644 index 000000000..8c926e26c --- /dev/null +++ b/source/egg/core/eggXfbManager.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x80244200..0x80244268 +UNKNOWN_FUNCTION(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb); +// PAL: 0x80244268..0x802442e8 +UNKNOWN_FUNCTION(copyEFB__Q23EGG10XfbManagerFb); +// PAL: 0x802442e8..0x80244350 +UNKNOWN_FUNCTION(setNextFrameBuffer__Q23EGG10XfbManagerFv); +// PAL: 0x80244350..0x802443ac +UNKNOWN_FUNCTION(postVRetrace__Q23EGG10XfbManagerFv); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/egg/math/eggBoundBox.hpp b/source/egg/math/eggBoundBox.hpp index 4294ece9b..4ec0373a4 100644 --- a/source/egg/math/eggBoundBox.hpp +++ b/source/egg/math/eggBoundBox.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace EGG { struct BoundBox2f { diff --git a/sources.py b/sources.py index 878ce4d20..5081335f8 100644 --- a/sources.py +++ b/sources.py @@ -197,8 +197,9 @@ class Source: Source(src="source/egg/core/eggThread.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggUnitHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggVideo.cpp", cc='4201_127', opts=EGG_OPTS+ " -use_lmw_stmw=on "), - # Source(src="source/egg/core/eggXfb.cpp", cc='4201_127', opts=EGG_OPTS), - # Source(src="source/egg/core/eggXfbManager.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggViewport.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggXfb.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggXfbManager.cpp", cc='4201_127', opts=EGG_OPTS), ] SOURCES_EGG_MATH = [ Source(src="source/egg/math/eggQuat.cpp", cc='4201_127', opts=EGG_OPTS), From 4fc55341666558455797146cba70a8b6a3efae6b Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 12 Aug 2021 14:05:34 -0600 Subject: [PATCH 159/477] [EGG] Decompile eggCntFIle.cpp, split eggAsyncDisplay.cpp --- pack/dol_objects.txt | 15 +- pack/dol_slices.csv | 4 +- pack/symbols.txt | 18 + source/egg/core/eggAsyncDisplay.cpp | 768 ++++++++++++++++++++++++++++ source/egg/core/eggAsyncDisplay.hpp | 40 ++ source/egg/core/eggViewport.cpp | 13 +- source/egg/core/eggXfb.cpp | 9 +- source/egg/core/eggXfbManager.cpp | 23 +- source/egg/util/eggCntFile.cpp | 87 +++- source/egg/util/eggCntFile.hpp | 48 +- source/platform/eabi.h | 8 + source/rvl/cnt.h | 24 + sources.py | 2 + 13 files changed, 1000 insertions(+), 59 deletions(-) create mode 100644 source/egg/core/eggAsyncDisplay.cpp create mode 100644 source/egg/core/eggAsyncDisplay.hpp create mode 100644 source/rvl/cnt.h diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index 06960a14c..ac5901173 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -279,13 +279,20 @@ out/dol/bss_80357238_803832d8.o out/dol/sdata_80385eec_80385fc0.o out/dol/sbss_80386d38_80386d80.o out/eggArchive.o -out/dol/text_8020fcc4_8021a0f0.o -out/dol/data_802a268c_802a2b48.o +out/dol/text_8020fcc4_8020fcd4.o +out/eggAsyncDisplay.o +out/dol/text_802104ec_80214e68.o +out/dol/data_802a268c_802a2a08.o +out/dol/bss_803832e4_80383500.o +out/dol/sbss_80386d84_80386dc8.o +out/eggCntFile.o +out/dol/text_80215168_8021a0f0.o +out/dol/data_802a2a28_802a2b48.o out/eggDisposer.o out/dol/text_8021a1b8_8022231c.o out/dol/data_802a2b54_802a2da0.o -out/dol/bss_803832e4_80384190.o -out/dol/sbss_80386d84_80386e18.o +out/dol/bss_80383510_80384190.o +out/dol/sbss_80386dcc_80386e18.o out/eggDvdFile.o out/dol/text_802226d8_802269a8.o out/dol/data_802a2dc8_802a2ff8.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 339f3a723..03299535a 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -116,7 +116,9 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/rvl/so/soBasic.c,,,,,,,0x801ecf20,0x801ecff4,,,,,,,0x802a24f8,0x802a2543,,,0x80385ee8,0x80385eeC,,,,,, 1,,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680,,,,,,,,,, 1,,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, -1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54,,,,,,,,,, +1,,source/egg/core/eggAsyncDisplay.cpp,,,,,,,8020FCD4,802104EC,,,,,,,,,,,,,,,,,, +1,,source/egg/util/eggCntFile.cpp,,,,,,,0x80214E68,0x80215168,,,,,,,0x802A2A08,0x802A2A28,,,,,0x80386DC8,0x80386DCC,,,, +1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54,0x80383500,0x80383510,,,,,,,, 1,1,source/egg/core/eggDvdFile.cpp,,,,,,,0x8022231c,0x802226d8,,,,,,,0x802a2da0,0x802a2dc8,0x80384190,0x803841a0,,,0x80386e18,0x80386e20,,,, 1,,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, 1,,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, diff --git a/pack/symbols.txt b/pack/symbols.txt index 058da0622..dfadb6bbd 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -1,3 +1,21 @@ +0x8015BCEC CNTConvertPathToEntrynum +0x8015BC80 contentFastOpenNAND +0x8015BD98 contentCloseNAND +0x8015BCF8 contentReadNAND +0x8015BCF0 CNTGetLength +0x8020FCD4 PostRetraceCallback +0x8020FCDC DrawDoneCallback +0x8020FD10 AlarmHandler_0 +0x8020FD18 __ct__Q23EGG12AsyncDisplayFUc +0x8020FD8C startSyncNTSC__Q23EGG12AsyncDisplayFUc +0x8020FE24 beginFrame__Q23EGG12AsyncDisplayFv +0x8020FF98 beginRender__Q23EGG12AsyncDisplayFv +0x8020FF9C endRender__Q23EGG12AsyncDisplayFv +0x80210024 postVRetrace__Q23EGG12AsyncDisplayFv +0x8021008C alarmHandler__Q23EGG12AsyncDisplayFv +0x802100A0 clearEFB__Q23EGG12AsyncDisplayFv +0x80210124 getTickPerFrame__Q23EGG12AsyncDisplayFv +0x8021013C clearEFB__Q23EGG12AsyncDisplayFUsUsUsUsUsUsQ34nw4r2ut5Color 0x80244074 __ct__Q23EGG8ViewportFv 0x802440B4 set__Q23EGG8ViewportFiiii 0x80244134 eggViewport_Calc_TODO diff --git a/source/egg/core/eggAsyncDisplay.cpp b/source/egg/core/eggAsyncDisplay.cpp new file mode 100644 index 000000000..b7e24dc58 --- /dev/null +++ b/source/egg/core/eggAsyncDisplay.cpp @@ -0,0 +1,768 @@ +#include "eggAsyncDisplay.hpp" + +// extern function references. +extern "C" UNKNOWN_FUNCTION(GXSetVtxDesc); +// PAL: 0x8016dc34 +extern "C" UNKNOWN_FUNCTION(GXClearVtxDesc); +// PAL: 0x8016dc68 +extern "C" UNKNOWN_FUNCTION(GXSetVtxAttrFmt); +// PAL: 0x8016e37c +extern "C" UNKNOWN_FUNCTION(GXSetTexCoordGen2); +// PAL: 0x8016e5a4 +extern "C" UNKNOWN_FUNCTION(GXSetNumTexGens); +// PAL: 0x8016eab0 +extern "C" UNKNOWN_FUNCTION(GXDraw); +// PAL: 0x8016ed50 +extern "C" UNKNOWN_FUNCTION(GXSetDrawDoneCallback); +// PAL: 0x8016f0f0 +extern "C" UNKNOWN_FUNCTION(GXBegin); +// PAL: 0x8016f3b8 +extern "C" UNKNOWN_FUNCTION(GXSetCullMode); +// PAL: 0x8017054c +extern "C" UNKNOWN_FUNCTION(GXSetNumChans); +// PAL: 0x80170570 +extern "C" UNKNOWN_FUNCTION(GXSetChanCtrl); +// PAL: 0x801707f8 +extern "C" UNKNOWN_FUNCTION(GXInitTexObj); +// PAL: 0x80170a4c +extern "C" UNKNOWN_FUNCTION(GXInitTexObjLOD); +// PAL: 0x80170f2c +extern "C" UNKNOWN_FUNCTION(GXLoadTexObj); +// PAL: 0x80171ce0 +extern "C" UNKNOWN_FUNCTION(GXSetTevColorIn); +// PAL: 0x80171d20 +extern "C" UNKNOWN_FUNCTION(GXSetTevAlphaIn); +// PAL: 0x80171d60 +extern "C" UNKNOWN_FUNCTION(GXSetTevColorOp); +// PAL: 0x80171db8 +extern "C" UNKNOWN_FUNCTION(GXSetTevAlphaOp); +// PAL: 0x80171e10 +extern "C" UNKNOWN_FUNCTION(GXSetTevColor); +// PAL: 0x80172088 +extern "C" UNKNOWN_FUNCTION(GXSetAlphaCompare); +// PAL: 0x801720c0 +extern "C" UNKNOWN_FUNCTION(GXSetZTexture); +// PAL: 0x8017214c +extern "C" UNKNOWN_FUNCTION(GXSetTevOrder); +// PAL: 0x801722a8 +extern "C" UNKNOWN_FUNCTION(GXSetNumTevStages); +// PAL: 0x8017277c +extern "C" UNKNOWN_FUNCTION(GXSetBlendMode); +// PAL: 0x801727f8 +extern "C" UNKNOWN_FUNCTION(GXSetAlphaUpdate); +// PAL: 0x80172824 +extern "C" UNKNOWN_FUNCTION(GXSetZMode); +// PAL: 0x80172858 +extern "C" UNKNOWN_FUNCTION(GXSetZCompLoc); +// PAL: 0x8017295c +extern "C" UNKNOWN_FUNCTION(GXSetDstAlpha); +// PAL: 0x8017301c +extern "C" UNKNOWN_FUNCTION(GXSetProjection); +// PAL: 0x80173214 +extern "C" UNKNOWN_FUNCTION(GXSetCurrentMtx); +// PAL: 0x801733b4 +extern "C" UNKNOWN_FUNCTION(GXSetViewport); +// PAL: 0x80173430 +extern "C" UNKNOWN_FUNCTION(GXSetScissor); +// PAL: 0x8019ab4c +extern "C" UNKNOWN_FUNCTION(C_MTXOrtho); +// PAL: 0x801a08e0 +extern "C" UNKNOWN_FUNCTION(OSSetPeriodicAlarm); +// PAL: 0x801a65ac +extern "C" UNKNOWN_FUNCTION(OSDisableInterrupts); +// PAL: 0x801a65d4 +extern "C" UNKNOWN_FUNCTION(OSRestoreInterrupts); +// PAL: 0x801a98a0 +extern "C" UNKNOWN_FUNCTION(OSInitThreadQueue); +// PAL: 0x801aa9b8 +extern "C" UNKNOWN_FUNCTION(OSSleepThread); +// PAL: 0x801aaaa4 +extern "C" UNKNOWN_FUNCTION(OSWakeupThread); +// PAL: 0x801aad74 +extern "C" UNKNOWN_FUNCTION(OSGetTick); +// PAL: 0x801b9138 +extern "C" UNKNOWN_FUNCTION(VISetPostRetraceCallback); +// PAL: 0x801bab2c +extern "C" UNKNOWN_FUNCTION(VISetBlack); +// PAL: 0x80219e68 +extern "C" UNKNOWN_FUNCTION(unk_80219e68); +// PAL: 0x80219fb4 +extern "C" UNKNOWN_FUNCTION(unk_80219fb4); +// PAL: 0x8021a06c +extern "C" UNKNOWN_FUNCTION(unk_8021a06c); +// PAL: 0x8023040c +extern "C" UNKNOWN_FUNCTION(unk_8023040c); +// PAL: 0x80243e70 +extern "C" UNKNOWN_FUNCTION(getTickPerVRetrace__Q23EGG5VideoFUl); +// PAL: 0x80243ed0 +extern "C" UNKNOWN_FUNCTION(getTickPerVRetrace__Q23EGG5VideoFv); +// PAL: 0x802442e8 +extern "C" UNKNOWN_FUNCTION(setNextFrameBuffer__Q23EGG10XfbManagerFv); +// PAL: 0x80244350 +extern "C" UNKNOWN_FUNCTION(postVRetrace__Q23EGG10XfbManagerFv); + +// Symbol: PostRetraceCallback +// Function signature is unknown. +// PAL: 0x8020fcd4..0x8020fcdc +MARK_BINARY_BLOB(PostRetraceCallback, 0x8020fcd4, 0x8020fcdc); +asm UNKNOWN_FUNCTION(PostRetraceCallback) { + // clang-format off + nofralloc; + lwz r3, -0x5e70(r13); + b postVRetrace__Q23EGG12AsyncDisplayFv; + // clang-format on +} + +// Symbol: DrawDoneCallback +// Function signature is unknown. +// PAL: 0x8020fcdc..0x8020fd10 +MARK_BINARY_BLOB(DrawDoneCallback, 0x8020fcdc, 0x8020fd10); +asm UNKNOWN_FUNCTION(DrawDoneCallback) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + lwz r3, -0x5ca0(r13); + lwz r12, 0(r3); + lwz r12, 0x14(r12); + mtctr r12; + bctrl; + bl setNextFrameBuffer__Q23EGG10XfbManagerFv; + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: AlarmHandler_0 +// Function signature is unknown. +// PAL: 0x8020fd10..0x8020fd18 +MARK_BINARY_BLOB(AlarmHandler_0, 0x8020fd10, 0x8020fd18); +asm UNKNOWN_FUNCTION(AlarmHandler_0) { + // clang-format off + nofralloc; + lwz r3, -0x5e70(r13); + b alarmHandler__Q23EGG12AsyncDisplayFv; + // clang-format on +} + +// Symbol: __ct__Q23EGG12AsyncDisplayFUc +// Function signature is unknown. +// PAL: 0x8020fd18..0x8020fd8c +MARK_BINARY_BLOB(__ct__Q23EGG12AsyncDisplayFUc, 0x8020fd18, 0x8020fd8c); +asm UNKNOWN_FUNCTION(__ct__Q23EGG12AsyncDisplayFUc) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r3; + bl unk_80219e68; + lfs f0, -0x6580(r2); + lis r3, 0x802a; + li r4, 0; + li r0, 1; + addi r3, r3, 0x26e0; + stw r3, 4(r31); + addi r3, r31, 0x58; + stw r4, 0x60(r31); + stfs f0, 0x64(r31); + stw r4, 0x68(r31); + stw r4, 0x6c(r31); + stb r0, 0x70(r31); + stw r31, -0x5e70(r13); + bl OSInitThreadQueue; + lis r3, 0x8021; + addi r3, r3, -812; + bl VISetPostRetraceCallback; + mr r3, r31; + lwz r31, 0xc(r1); + lwz r0, 0x14(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: startSyncNTSC__Q23EGG12AsyncDisplayFUc +// Function signature is unknown. +// PAL: 0x8020fd8c..0x8020fe24 +MARK_BINARY_BLOB(startSyncNTSC__Q23EGG12AsyncDisplayFUc, 0x8020fd8c, + 0x8020fe24); +asm UNKNOWN_FUNCTION(startSyncNTSC__Q23EGG12AsyncDisplayFUc) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + stw r30, 0x18(r1); + mr r30, r4; + stw r29, 0x14(r1); + mr r29, r3; + bl OSDisableInterrupts; + lwz r0, 0x60(r29); + mr r31, r3; + cmpwi r0, 0; + bne lbl_8020fe00; + li r0, 1; + stb r30, 0x70(r29); + li r3, 0; + stw r0, 0x60(r29); + bl getTickPerVRetrace__Q23EGG5VideoFUl; + stw r3, 0x74(r29); + bl getTickPerVRetrace__Q23EGG5VideoFv; + stw r3, 0x78(r29); + lis r9, 0x8021; + lwz r8, 0x74(r29); + addi r3, r29, 0x28; + addi r9, r9, -752; + li r6, 0; + li r5, 0; + li r7, 0; + bl OSSetPeriodicAlarm; +lbl_8020fe00: + mr r3, r31; + bl OSRestoreInterrupts; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r29, 0x14(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: beginFrame__Q23EGG12AsyncDisplayFv +// Function signature is unknown. +// PAL: 0x8020fe24..0x8020ff98 +MARK_BINARY_BLOB(beginFrame__Q23EGG12AsyncDisplayFv, 0x8020fe24, 0x8020ff98); +asm UNKNOWN_FUNCTION(beginFrame__Q23EGG12AsyncDisplayFv) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r3; + stw r30, 0x18(r1); + lwz r4, 0x68(r3); + lwz r0, 0x6c(r3); + subf r30, r4, r0; +lbl_8020fe48: + addi r3, r31, 0x58; + bl OSSleepThread; + lbz r0, 8(r31); + addi r30, r30, 1; + cmplw r30, r0; + blt lbl_8020fe48; + lwz r0, 0x6c(r31); + mr r3, r31; + stw r0, 0x68(r31); + bl unk_8021a06c; + lwz r0, 0x60(r31); + cmpwi r0, 1; + bne lbl_8020ff08; + lwz r3, 0x74(r31); + lbz r0, 0x70(r31); + lwz r30, 0x78(r31); + mullw r0, r3, r0; + b lbl_8020fe94; +lbl_8020fe90: + add r30, r30, r0; +lbl_8020fe94: + cmpw r30, r0; + blt lbl_8020fe90; + bl OSGetTick; + lwz r0, 0x80(r31); + lwz r4, 0x74(r31); + subf r3, r0, r3; + stw r3, 0x7c(r31); + subf r0, r4, r30; + subf. r3, r3, r0; + bge lbl_8020fec0; + add r3, r3, r4; +lbl_8020fec0: + lis r0, 0x4330; + xoris r3, r3, 0x8000; + stw r3, 0xc(r1); + lfd f1, -0x6578(r2); + stw r0, 8(r1); + lfd f2, -0x6570(r2); + lfd f0, 8(r1); + stw r4, 0x14(r1); + fsubs f3, f0, f1; + lfs f0, -0x6580(r2); + stw r0, 0x10(r1); + lfd f1, 0x10(r1); + fsubs f1, f1, f2; + fdivs f1, f3, f1; + stfs f1, 0x64(r31); + fcmpo cr0, f1, f0; + ble lbl_8020ff08; + stfs f0, 0x64(r31); +lbl_8020ff08: + lbz r0, 9(r31); + clrlwi. r0, r0, 0x1f; + beq lbl_8020ff74; + lwz r3, -0x5ca0(r13); + lwz r12, 0(r3); + lwz r12, 8(r12); + mtctr r12; + bctrl; + lbz r0, 4(r3); + mr r30, r3; + clrlwi r0, r0, 0x1f; + cntlzw r0, r0; + srwi r3, r0, 5; + bl VISetBlack; + lbz r0, 4(r30); + clrlwi. r0, r0, 0x1f; + bne lbl_8020ff5c; + lbz r0, 4(r30); + ori r0, r0, 1; + stb r0, 4(r30); + b lbl_8020ff68; +lbl_8020ff5c: + lbz r0, 4(r30); + rlwinm r0, r0, 0, 0x18, 0x1e; + stb r0, 4(r30); +lbl_8020ff68: + lbz r0, 9(r31); + rlwinm r0, r0, 0, 0x18, 0x1e; + stb r0, 9(r31); +lbl_8020ff74: + lwz r3, 0x10(r31); + addi r0, r3, 1; + stw r0, 0x10(r31); + lwz r31, 0x1c(r1); + lwz r30, 0x18(r1); + lwz r0, 0x24(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: beginRender__Q23EGG12AsyncDisplayFv +// Function signature is unknown. +// PAL: 0x8020ff98..0x8020ff9c +MARK_BINARY_BLOB(beginRender__Q23EGG12AsyncDisplayFv, 0x8020ff98, 0x8020ff9c); +asm UNKNOWN_FUNCTION(beginRender__Q23EGG12AsyncDisplayFv) { + // clang-format off + nofralloc; + b GXDraw; + // clang-format on +} + +// Symbol: endRender__Q23EGG12AsyncDisplayFv +// Function signature is unknown. +// PAL: 0x8020ff9c..0x80210024 +MARK_BINARY_BLOB(endRender__Q23EGG12AsyncDisplayFv, 0x8020ff9c, 0x80210024); +asm UNKNOWN_FUNCTION(endRender__Q23EGG12AsyncDisplayFv) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r3; + lwz r3, -0x5ca0(r13); + lwz r12, 0(r3); + lwz r12, 0x14(r12); + mtctr r12; + bctrl; + lwz r5, 4(r3); + li r4, 0; + lwz r0, 0xc(r3); + cmplw r5, r0; + beq lbl_8020ffe8; + lwz r0, 0(r3); + cmplw r5, r0; + beq lbl_8020ffe8; + li r4, 1; +lbl_8020ffe8: + cmpwi r4, 0; + beq lbl_80210008; + mr r3, r31; + bl unk_80219fb4; + lis r3, 0x8021; + addi r3, r3, -804; + bl GXSetDrawDoneCallback; + b lbl_80210010; +lbl_80210008: + mr r3, r31; + bl clearEFB__Q23EGG12AsyncDisplayFv; +lbl_80210010: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: postVRetrace__Q23EGG12AsyncDisplayFv +// Function signature is unknown. +// PAL: 0x80210024..0x8021008c +MARK_BINARY_BLOB(postVRetrace__Q23EGG12AsyncDisplayFv, 0x80210024, 0x8021008c); +asm UNKNOWN_FUNCTION(postVRetrace__Q23EGG12AsyncDisplayFv) { + // clang-format off + nofralloc; + stwu r1, -0x10(r1); + mflr r0; + stw r0, 0x14(r1); + stw r31, 0xc(r1); + mr r31, r3; + lwz r3, -0x5ca0(r13); + lwz r12, 0(r3); + lwz r12, 0x14(r12); + mtctr r12; + bctrl; + bl postVRetrace__Q23EGG10XfbManagerFv; + bl OSGetTick; + lwz r0, 0x60(r31); + stw r3, 0x80(r31); + cmpwi r0, 0; + bne lbl_80210078; + lwz r4, 0x6c(r31); + addi r3, r31, 0x58; + addi r0, r4, 1; + stw r0, 0x6c(r31); + bl OSWakeupThread; +lbl_80210078: + lwz r0, 0x14(r1); + lwz r31, 0xc(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; + // clang-format on +} + +// Symbol: alarmHandler__Q23EGG12AsyncDisplayFv +// Function signature is unknown. +// PAL: 0x8021008c..0x802100a0 +MARK_BINARY_BLOB(alarmHandler__Q23EGG12AsyncDisplayFv, 0x8021008c, 0x802100a0); +asm UNKNOWN_FUNCTION(alarmHandler__Q23EGG12AsyncDisplayFv) { + // clang-format off + nofralloc; + lwz r4, 0x6c(r3); + addi r0, r4, 1; + stw r0, 0x6c(r3); + addi r3, r3, 0x58; + b OSWakeupThread; + // clang-format on +} + +// Symbol: clearEFB__Q23EGG12AsyncDisplayFv +// Function signature is unknown. +// PAL: 0x802100a0..0x80210124 +MARK_BINARY_BLOB(clearEFB__Q23EGG12AsyncDisplayFv, 0x802100a0, 0x80210124); +asm UNKNOWN_FUNCTION(clearEFB__Q23EGG12AsyncDisplayFv) { + // clang-format off + nofralloc; + stwu r1, -0x20(r1); + mflr r0; + stw r0, 0x24(r1); + stw r31, 0x1c(r1); + mr r31, r3; + lwz r3, -0x5ca0(r13); + lwz r12, 0(r3); + lwz r12, 8(r12); + mtctr r12; + bctrl; + lwz r4, 0(r3); + mr r3, r31; + lbz r0, 0x14(r31); + addi r10, r1, 8; + stb r0, 8(r1); + li r6, 0; + li r7, 0; + lbz r0, 0x15(r31); + stb r0, 9(r1); + lbz r0, 0x16(r31); + stb r0, 0xa(r1); + lbz r0, 0x17(r31); + stb r0, 0xb(r1); + lhz r5, 6(r4); + lhz r4, 4(r4); + mr r9, r5; + mr r8, r4; + bl clearEFB__Q23EGG12AsyncDisplayFUsUsUsUsUsUsQ34nw4r2ut5Color; + lwz r0, 0x24(r1); + lwz r31, 0x1c(r1); + mtlr r0; + addi r1, r1, 0x20; + blr; + // clang-format on +} + +// Symbol: getTickPerFrame__Q23EGG12AsyncDisplayFv +// Function signature is unknown. +// PAL: 0x80210124..0x8021013c +MARK_BINARY_BLOB(getTickPerFrame__Q23EGG12AsyncDisplayFv, 0x80210124, + 0x8021013c); +asm UNKNOWN_FUNCTION(getTickPerFrame__Q23EGG12AsyncDisplayFv) { + // clang-format off + nofralloc; + lwz r0, 0x60(r3); + cmpwi r0, 1; + bne lbl_80210138; + li r3, 0; + b getTickPerVRetrace__Q23EGG5VideoFUl; +lbl_80210138: + b getTickPerVRetrace__Q23EGG5VideoFv; + // clang-format on +} + +// Symbol: clearEFB__Q23EGG12AsyncDisplayFUsUsUsUsUsUsQ34nw4r2ut5Color +// Function signature is unknown. +// PAL: 0x8021013c..0x802104ec +MARK_BINARY_BLOB(clearEFB__Q23EGG12AsyncDisplayFUsUsUsUsUsUsQ34nw4r2ut5Color, + 0x8021013c, 0x802104ec); +asm UNKNOWN_FUNCTION( + clearEFB__Q23EGG12AsyncDisplayFUsUsUsUsUsUsQ34nw4r2ut5Color) { + // clang-format off + nofralloc; + stwu r1, -0x80(r1); + mflr r0; + stw r0, 0x84(r1); + addi r11, r1, 0x80; + bl _savegpr_24; + lis r0, 0x4330; + lis r31, 0x8038; + mr r25, r5; + mr r26, r6; + mr r27, r7; + mr r28, r8; + mr r29, r9; + mr r30, r10; + stw r0, 0x50(r1); + lis r5, 0x802a; + mr r24, r4; + addi r3, r31, 0x32e8; + addi r4, r5, 0x26a0; + stw r0, 0x58(r1); + li r5, 4; + li r6, 4; + li r7, 0x16; + li r8, 1; + li r9, 1; + li r10, 0; + bl GXInitTexObj; + lfs f1, -0x6568(r2); + addi r3, r31, 0x32e8; + li r4, 0; + li r5, 0; + fmr f2, f1; + li r6, 0; + fmr f3, f1; + li r7, 0; + li r8, 0; + bl GXInitTexObjLOD; + stw r25, 0x54(r1); + addi r3, r1, 0x10; + lfs f1, -0x6568(r2); + stw r24, 0x5c(r1); + lfd f4, -0x6570(r2); + fmr f3, f1; + lfd f2, 0x50(r1); + fmr f5, f1; + lfd f0, 0x58(r1); + fsubs f2, f2, f4; + lfs f6, -0x6580(r2); + fsubs f4, f0, f4; + bl C_MTXOrtho; + addi r3, r1, 0x10; + li r4, 1; + bl GXSetProjection; + stw r24, 0x54(r1); + lfs f1, -0x6568(r2); + stw r25, 0x5c(r1); + lfd f4, -0x6570(r2); + fmr f2, f1; + lfd f3, 0x50(r1); + fmr f5, f1; + lfd f0, 0x58(r1); + fsubs f3, f3, f4; + lfs f6, -0x6580(r2); + fsubs f4, f0, f4; + bl GXSetViewport; + mr r5, r24; + mr r6, r25; + li r3, 0; + li r4, 0; + bl GXSetScissor; + lis r3, 0x8038; + li r4, 0; + addi r3, r3, 0x4370; + bl unk_8023040c; + li r3, 0; + bl GXSetCurrentMtx; + bl GXClearVtxDesc; + li r3, 9; + li r4, 1; + bl GXSetVtxDesc; + li r3, 0xd; + li r4, 1; + bl GXSetVtxDesc; + li r3, 0; + li r4, 9; + li r5, 0; + li r6, 2; + li r7, 0; + bl GXSetVtxAttrFmt; + li r3, 0; + li r4, 0xd; + li r5, 1; + li r6, 0; + li r7, 0; + bl GXSetVtxAttrFmt; + li r3, 0; + bl GXSetNumChans; + li r3, 4; + li r4, 0; + li r5, 0; + li r6, 0; + li r7, 0; + li r8, 0; + li r9, 2; + bl GXSetChanCtrl; + li r3, 5; + li r4, 0; + li r5, 0; + li r6, 0; + li r7, 0; + li r8, 0; + li r9, 2; + bl GXSetChanCtrl; + li r3, 1; + bl GXSetNumTexGens; + li r3, 0; + li r4, 1; + li r5, 4; + li r6, 0x3c; + li r7, 0; + li r8, 0x7d; + bl GXSetTexCoordGen2; + addi r3, r31, 0x32e8; + li r4, 0; + bl GXLoadTexObj; + li r3, 1; + bl GXSetNumTevStages; + lbz r7, 0(r30); + addi r4, r1, 8; + lbz r6, 1(r30); + li r3, 1; + lbz r5, 2(r30); + lbz r0, 3(r30); + stb r7, 8(r1); + stb r6, 9(r1); + stb r5, 0xa(r1); + stb r0, 0xb(r1); + bl GXSetTevColor; + li r3, 0; + li r4, 0; + li r5, 0; + li r6, 0xff; + bl GXSetTevOrder; + li r3, 0; + li r4, 0xf; + li r5, 0xf; + li r6, 0xf; + li r7, 2; + bl GXSetTevColorIn; + li r3, 0; + li r4, 0; + li r5, 0; + li r6, 0; + li r7, 1; + li r8, 0; + bl GXSetTevColorOp; + li r3, 0; + li r4, 7; + li r5, 7; + li r6, 7; + li r7, 1; + bl GXSetTevAlphaIn; + li r3, 0; + li r4, 0; + li r5, 0; + li r6, 0; + li r7, 1; + li r8, 0; + bl GXSetTevAlphaOp; + li r3, 7; + li r4, 0; + li r5, 1; + li r6, 7; + li r7, 0; + bl GXSetAlphaCompare; + li r3, 2; + li r4, 0x16; + li r5, 0; + bl GXSetZTexture; + li r3, 0; + bl GXSetZCompLoc; + li r3, 0; + li r4, 0; + li r5, 0; + li r6, 5; + bl GXSetBlendMode; + li r3, 1; + bl GXSetAlphaUpdate; + lbz r4, 3(r30); + li r3, 1; + bl GXSetDstAlpha; + li r3, 1; + li r4, 7; + li r5, 1; + bl GXSetZMode; + li r3, 2; + bl GXSetCullMode; + li r3, 0x80; + li r4, 0; + li r5, 4; + bl GXBegin; + lis r8, 0xcc01; + li r7, 0; + sth r26, -0x8000(r8); + add r3, r26, r28; + li r6, 1; + add r0, r27, r29; + sth r27, -0x8000(r8); + li r4, 0x16; + li r5, 0; + stb r7, -0x8000(r8); + stb r7, -0x8000(r8); + sth r3, -0x8000(r8); + sth r27, -0x8000(r8); + stb r6, -0x8000(r8); + stb r7, -0x8000(r8); + sth r3, -0x8000(r8); + li r3, 0; + sth r0, -0x8000(r8); + stb r6, -0x8000(r8); + stb r6, -0x8000(r8); + sth r26, -0x8000(r8); + sth r0, -0x8000(r8); + stb r7, -0x8000(r8); + stb r6, -0x8000(r8); + bl GXSetZTexture; + li r3, 1; + bl GXSetZCompLoc; + lbz r4, 3(r30); + li r3, 0; + bl GXSetDstAlpha; + addi r11, r1, 0x80; + bl _restgpr_24; + lwz r0, 0x84(r1); + mtlr r0; + addi r1, r1, 0x80; + blr; + // clang-format on +} diff --git a/source/egg/core/eggAsyncDisplay.hpp b/source/egg/core/eggAsyncDisplay.hpp new file mode 100644 index 000000000..8dc57d229 --- /dev/null +++ b/source/egg/core/eggAsyncDisplay.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "decomp.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// PAL: 0x8020fcd4..0x8020fcdc +UNKNOWN_FUNCTION(PostRetraceCallback); +// PAL: 0x8020fcdc..0x8020fd10 +UNKNOWN_FUNCTION(DrawDoneCallback); +// PAL: 0x8020fd10..0x8020fd18 +UNKNOWN_FUNCTION(AlarmHandler_0); +// PAL: 0x8020fd18..0x8020fd8c +UNKNOWN_FUNCTION(__ct__Q23EGG12AsyncDisplayFUc); +// PAL: 0x8020fd8c..0x8020fe24 +UNKNOWN_FUNCTION(startSyncNTSC__Q23EGG12AsyncDisplayFUc); +// PAL: 0x8020fe24..0x8020ff98 +UNKNOWN_FUNCTION(beginFrame__Q23EGG12AsyncDisplayFv); +// PAL: 0x8020ff98..0x8020ff9c +UNKNOWN_FUNCTION(beginRender__Q23EGG12AsyncDisplayFv); +// PAL: 0x8020ff9c..0x80210024 +UNKNOWN_FUNCTION(endRender__Q23EGG12AsyncDisplayFv); +// PAL: 0x80210024..0x8021008c +UNKNOWN_FUNCTION(postVRetrace__Q23EGG12AsyncDisplayFv); +// PAL: 0x8021008c..0x802100a0 +UNKNOWN_FUNCTION(alarmHandler__Q23EGG12AsyncDisplayFv); +// PAL: 0x802100a0..0x80210124 +UNKNOWN_FUNCTION(clearEFB__Q23EGG12AsyncDisplayFv); +// PAL: 0x80210124..0x8021013c +UNKNOWN_FUNCTION(getTickPerFrame__Q23EGG12AsyncDisplayFv); +// PAL: 0x8021013c..0x802104ec +UNKNOWN_FUNCTION(clearEFB__Q23EGG12AsyncDisplayFUsUsUsUsUsUsQ34nw4r2ut5Color); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/egg/core/eggViewport.cpp b/source/egg/core/eggViewport.cpp index 36c5a5333..5c4b3b8ff 100644 --- a/source/egg/core/eggViewport.cpp +++ b/source/egg/core/eggViewport.cpp @@ -9,7 +9,7 @@ extern "C" UNKNOWN_FUNCTION(__ct__Q23EGG10BoundBox2fFv); // PAL: 0x80244074..0x802440b4 MARK_BINARY_BLOB(__ct__Q23EGG8ViewportFv, 0x80244074, 0x802440b4); asm UNKNOWN_FUNCTION(__ct__Q23EGG8ViewportFv) { -// clang-format off + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -27,7 +27,7 @@ asm UNKNOWN_FUNCTION(__ct__Q23EGG8ViewportFv) { mtlr r0; addi r1, r1, 0x10; blr; -// clang-format on + // clang-format on } extern "C" void eggViewport_Calc_TODO(); @@ -37,7 +37,7 @@ extern "C" void eggViewport_Calc_TODO(); // PAL: 0x802440b4..0x80244134 MARK_BINARY_BLOB(set__Q23EGG8ViewportFiiii, 0x802440b4, 0x80244134); asm UNKNOWN_FUNCTION(set__Q23EGG8ViewportFiiii) { -// clang-format off + // clang-format off nofralloc; stwu r1, -0x20(r1); mflr r0; @@ -71,7 +71,7 @@ asm UNKNOWN_FUNCTION(set__Q23EGG8ViewportFiiii) { mtlr r0; addi r1, r1, 0x20; blr; -// clang-format on + // clang-format on } // Symbol: eggViewport_Calc_TODO @@ -79,7 +79,7 @@ asm UNKNOWN_FUNCTION(set__Q23EGG8ViewportFiiii) { // PAL: 0x80244134..0x80244160 MARK_BINARY_BLOB(eggViewport_Calc_TODO, 0x80244134, 0x80244160); asm UNKNOWN_FUNCTION(eggViewport_Calc_TODO) { -// clang-format off + // clang-format off nofralloc; lfs f3, 8(r3); lfs f2, 0(r3); @@ -92,6 +92,5 @@ asm UNKNOWN_FUNCTION(eggViewport_Calc_TODO) { fdivs f0, f2, f0; stfs f0, 0x18(r3); blr; -// clang-format on + // clang-format on } - diff --git a/source/egg/core/eggXfb.cpp b/source/egg/core/eggXfb.cpp index cb26baefd..50df0738e 100644 --- a/source/egg/core/eggXfb.cpp +++ b/source/egg/core/eggXfb.cpp @@ -9,7 +9,7 @@ extern "C" UNKNOWN_FUNCTION(__nwa__FUlPQ23EGG4Heapi); // PAL: 0x80244160..0x802441ec MARK_BINARY_BLOB(__ct__Q23EGG3XfbFPQ23EGG4Heap, 0x80244160, 0x802441ec); asm UNKNOWN_FUNCTION(__ct__Q23EGG3XfbFPQ23EGG4Heap) { -// clang-format off + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -47,7 +47,7 @@ asm UNKNOWN_FUNCTION(__ct__Q23EGG3XfbFPQ23EGG4Heap) { mtlr r0; addi r1, r1, 0x10; blr; -// clang-format on + // clang-format on } // Symbol: EGG__Xfb__CalcXfbSize @@ -55,13 +55,12 @@ asm UNKNOWN_FUNCTION(__ct__Q23EGG3XfbFPQ23EGG4Heap) { // PAL: 0x802441ec..0x80244200 MARK_BINARY_BLOB(EGG__Xfb__CalcXfbSize, 0x802441ec, 0x80244200); asm UNKNOWN_FUNCTION(EGG__Xfb__CalcXfbSize) { -// clang-format off + // clang-format off nofralloc; addi r0, r3, 0xf; rlwinm r0, r0, 0, 0x10, 0x1b; mullw r0, r0, r4; slwi r3, r0, 1; blr; -// clang-format on + // clang-format on } - diff --git a/source/egg/core/eggXfbManager.cpp b/source/egg/core/eggXfbManager.cpp index ba0226c88..27392009d 100644 --- a/source/egg/core/eggXfbManager.cpp +++ b/source/egg/core/eggXfbManager.cpp @@ -25,9 +25,10 @@ extern "C" UNKNOWN_FUNCTION(VIGetNextFrameBuffer); // Symbol: attach__Q23EGG10XfbManagerFPQ23EGG3Xfb // Function signature is unknown. // PAL: 0x80244200..0x80244268 -MARK_BINARY_BLOB(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb, 0x80244200, 0x80244268); +MARK_BINARY_BLOB(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb, 0x80244200, + 0x80244268); asm UNKNOWN_FUNCTION(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb) { -// clang-format off + // clang-format off nofralloc; cmpwi r4, 0; li r0, 0; @@ -58,7 +59,7 @@ asm UNKNOWN_FUNCTION(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb) { lbl_80244260: mr r3, r0; blr; -// clang-format on + // clang-format on } // Symbol: copyEFB__Q23EGG10XfbManagerFb @@ -66,7 +67,7 @@ asm UNKNOWN_FUNCTION(attach__Q23EGG10XfbManagerFPQ23EGG3Xfb) { // PAL: 0x80244268..0x802442e8 MARK_BINARY_BLOB(copyEFB__Q23EGG10XfbManagerFb, 0x80244268, 0x802442e8); asm UNKNOWN_FUNCTION(copyEFB__Q23EGG10XfbManagerFb) { -// clang-format off + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -101,15 +102,16 @@ asm UNKNOWN_FUNCTION(copyEFB__Q23EGG10XfbManagerFb) { mtlr r0; addi r1, r1, 0x10; blr; -// clang-format on + // clang-format on } // Symbol: setNextFrameBuffer__Q23EGG10XfbManagerFv // Function signature is unknown. // PAL: 0x802442e8..0x80244350 -MARK_BINARY_BLOB(setNextFrameBuffer__Q23EGG10XfbManagerFv, 0x802442e8, 0x80244350); +MARK_BINARY_BLOB(setNextFrameBuffer__Q23EGG10XfbManagerFv, 0x802442e8, + 0x80244350); asm UNKNOWN_FUNCTION(setNextFrameBuffer__Q23EGG10XfbManagerFv) { -// clang-format off + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -138,7 +140,7 @@ asm UNKNOWN_FUNCTION(setNextFrameBuffer__Q23EGG10XfbManagerFv) { mtlr r0; addi r1, r1, 0x10; blr; -// clang-format on + // clang-format on } // Symbol: postVRetrace__Q23EGG10XfbManagerFv @@ -146,7 +148,7 @@ asm UNKNOWN_FUNCTION(setNextFrameBuffer__Q23EGG10XfbManagerFv) { // PAL: 0x80244350..0x802443ac MARK_BINARY_BLOB(postVRetrace__Q23EGG10XfbManagerFv, 0x80244350, 0x802443ac); asm UNKNOWN_FUNCTION(postVRetrace__Q23EGG10XfbManagerFv) { -// clang-format off + // clang-format off nofralloc; stwu r1, -0x10(r1); mflr r0; @@ -172,6 +174,5 @@ asm UNKNOWN_FUNCTION(postVRetrace__Q23EGG10XfbManagerFv) { mtlr r0; addi r1, r1, 0x10; blr; -// clang-format on + // clang-format on } - diff --git a/source/egg/util/eggCntFile.cpp b/source/egg/util/eggCntFile.cpp index 5b483b43a..56fc0ca72 100644 --- a/source/egg/util/eggCntFile.cpp +++ b/source/egg/util/eggCntFile.cpp @@ -1,38 +1,99 @@ -#include +#include namespace EGG { -CntFile::CntFile() {} -CntFile::~CntFile() {} +static nw4r::ut::List gCntFileList; +static int gCurrentCntFile; -bool CntFile::open() { return false; } +#ifdef NON_MATCHING +CntFile::CntFile() : mOpen(false) { initThreading(); } +#else +extern "C" void initThreading__Q23EGG7CntFileFv(void*); +extern "C" asm void* __ct__Q23EGG7CntFileFv(void*) { + stwu r1, -16(r1); + mflr r0; + lis r4, -32726; + stw r0, 20(r1); + li r0, 0; + addi r4, r4, 10760; + stw r31, 12(r1); + mr r31, r3; + stb r0, 4(r3); + stw r4, 0(r3); + bl initThreading__Q23EGG7CntFileFv; + mr r3, r31; + lwz r31, 12(r1); + lwz r0, 20(r1); + mtlr r0; + addi r1, r1, 0x10; + blr; +} +#endif +CntFile::~CntFile() { close(); } + +void CntFile::initThreading() { + this->_50 = this; + this->_4C = 0; + OSInitMutex(&this->_08); + OSInitMutex(&this->_20); + OSInitMessageQueue(&this->_78, &this->_98, 1); + OSInitMessageQueue(&this->_54, &this->_74, 1); + this->_9C = 0; + this->_38 = 0; +} + +bool CntFile::spawnFileHandle(const char* path, void* cnt_handle) { + int entry_num = CNTConvertPathToEntrynum(cnt_handle, path); + if (!this->mOpen && entry_num != -1) { + int cnt_file = contentFastOpenNAND(cnt_handle, entry_num, &this->mFileHnd); + if (!cnt_file) { + this->mOpen = 1; + nw4r::ut::List_Append(&gCntFileList, this); + this->_4C = cnt_handle; + } else { + this->mOpen = 0; + this->_4C = 0; + } + gCurrentCntFile = cnt_file; + } + return this->mOpen; +} void CntFile::close() { - if (!_04) { + if (!mOpen) { return; } - bool returnVal = contentCloseNAND(&_3C); + int result = contentCloseNAND(&mFileHnd); _4C = false; - if (!returnVal) { - _04 = false; + if (!result) { + mOpen = false; nw4r::ut::List_Remove(&gCntFileList, this); } - gCurrentCntFile = returnVal; + gCurrentCntFile = result; } -void CntFile::readData(void* fileBuffer, u32 length, s32 offset) { +int CntFile::readData(void* fileBuffer, u32 length, s32 offset) { OSLockMutex(&_08); if (_9C) { OSUnlockMutex(&_08); - return; + return -1; } _9C = OSGetCurrentThread(); - const s32 readCode = contentReadNAND(&_3C, fileBuffer, length, offset); - gCurrentCntFile = readCode == 0; + const s32 result = contentReadNAND(&mFileHnd, fileBuffer, length, offset); + // TODO: Pattern? + gCurrentCntFile = (result >> 31) & result; + + _9C = 0; + OSUnlockMutex(&_08); + return result; } +int CntFile::writeData(const void*, int, int) { return -1; } +int CntFile::open(const char*) { return 0; } +u32 CntFile::getFileSize() const { return CNTGetLength(&mFileHnd); } + } // namespace EGG diff --git a/source/egg/util/eggCntFile.hpp b/source/egg/util/eggCntFile.hpp index be53faa17..3de33a0e7 100644 --- a/source/egg/util/eggCntFile.hpp +++ b/source/egg/util/eggCntFile.hpp @@ -1,15 +1,14 @@ #pragma once -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include namespace EGG { -static nw4r::ut::List gCntFileList; -static BOOL gCurrentCntFile; - class CntFile { public: CntFile(); @@ -17,21 +16,34 @@ class CntFile { private: inline CntFile(const CntFile&) {} + // Name not final + void initThreading(); + // Name not final + bool spawnFileHandle(const char* path, void* cnt_handle); + public: virtual ~CntFile(); - virtual bool open(); // 0 - virtual void close(); // 4 - virtual void readData(void* fileBuffer, u32 length, s32 offset); // 8 + virtual int open(const char*); + virtual void close(); + virtual int readData(void* fileBuffer, u32 length, s32 offset); + virtual int writeData(const void*, int, int); + virtual u32 getFileSize() const; private: - bool _04; // 4 - OSMutex _08; // 8 - unk32 _38; // 38 - CNTFileInfoNAND _3C; // 3C - char _40[0x10]; // 40 - unk32 _4C; // 4C - char _50[0x50]; // 50 - OSThread* _9C; // 9C + bool mOpen; + char _p[3]; + OSMutex _08; + OSMutex _20; + u32 _38; + CNTFileInfoNAND mFileHnd; + void* _4C; + void* _50; + + OSMessageQueue _54; + OSMessage _74; + OSMessageQueue _78; + OSMessage _98; + OSThread* _9C; }; } // namespace EGG diff --git a/source/platform/eabi.h b/source/platform/eabi.h index 4084b5e0d..a4bf93aee 100644 --- a/source/platform/eabi.h +++ b/source/platform/eabi.h @@ -1,5 +1,9 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + // PAL: 0x8002156C extern void _savegpr_14(void); // PAL: 0x80021570 @@ -71,3 +75,7 @@ extern void __shr2i(void); void __cvt_dbl_ull(void); unsigned long __cvt_fp2unsigned(double d); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/rvl/cnt.h b/source/rvl/cnt.h new file mode 100644 index 000000000..1715efee2 --- /dev/null +++ b/source/rvl/cnt.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct CNTFileInfoNAND { + void* parent; + u32 start; + u32 len; + s32 ofs; +}; + +int CNTConvertPathToEntrynum(void* handle, const char* path); +int contentFastOpenNAND(void* handle, int entry_num, void* file_handle); +int contentCloseNAND(void* file_handle); +int contentReadNAND(void* file_handle, void* buf, u32 len, s32 ofs); +u32 CNTGetLength(const void* file_handle); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/sources.py b/sources.py index 5081335f8..c581e7be5 100644 --- a/sources.py +++ b/sources.py @@ -185,6 +185,8 @@ class Source: SOURCES_EGG_CORE = [ Source(src="source/egg/core/eggAllocator.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggArchive.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), + Source(src="source/egg/core/eggAsyncDisplay.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/util/eggCntFile.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggDisposer.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggDvdFile.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggExpHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), From 87b80b13bae656507b17b253d2b606c9375db928 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Thu, 12 Aug 2021 14:21:05 -0600 Subject: [PATCH 160/477] [EGG] Adapt CntFile to inherit File --- pack/dol.lcf.j2 | 11 +++++++++ pack/dol_slices.csv | 2 +- source/egg/util/eggCntFile.cpp | 45 +++++++++------------------------- source/egg/util/eggCntFile.hpp | 15 ++++++------ 4 files changed, 30 insertions(+), 43 deletions(-) diff --git a/pack/dol.lcf.j2 b/pack/dol.lcf.j2 index 445d485ea..86c70e52a 100644 --- a/pack/dol.lcf.j2 +++ b/pack/dol.lcf.j2 @@ -549,4 +549,15 @@ getFileSize__Q23EGG7DvdFileCFv initialize__Q23EGG5VideoFPC15GXRenderModeObjPCPC15GXRenderModeObj getTickPerVRetrace__Q23EGG5VideoFUl getTickPerVRetrace__Q23EGG5VideoFv + +// eggCntFile +__ct__Q23EGG7CntFileFv +__dt__Q23EGG7CntFileFv +initThreading__Q23EGG7CntFileFv +spawnFileHandle__Q23EGG7CntFileFPCcPv +close__Q23EGG7CntFileFv +readData__Q23EGG7CntFileFPvll +writeData__Q23EGG7CntFileFPCvll +open__Q23EGG7CntFileFPCc +getFileSize__Q23EGG7CntFileCFv } diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index 03299535a..f5c0652d0 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -117,7 +117,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,,source/egg/core/eggAllocator.cpp,,,,,,,0x8020F62C,0x8020F6EC,,,,,,,0x802A2668,0x802A2680,,,,,,,,,, 1,,source/egg/core/eggArchive.cpp,,,,,,,0x8020F6EC,0x8020FCC4,,,,,,,0x802A2680,0x802A268C,0x803832D8,0x803832E4,,,0x80386D80,0x80386D84,,,, 1,,source/egg/core/eggAsyncDisplay.cpp,,,,,,,8020FCD4,802104EC,,,,,,,,,,,,,,,,,, -1,,source/egg/util/eggCntFile.cpp,,,,,,,0x80214E68,0x80215168,,,,,,,0x802A2A08,0x802A2A28,,,,,0x80386DC8,0x80386DCC,,,, +1,1,source/egg/util/eggCntFile.cpp,,,,,,,0x80214E68,0x80215168,,,,,,,0x802A2A08,0x802A2A28,,,,,0x80386DC8,0x80386DCC,,,, 1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54,0x80383500,0x80383510,,,,,,,, 1,1,source/egg/core/eggDvdFile.cpp,,,,,,,0x8022231c,0x802226d8,,,,,,,0x802a2da0,0x802a2dc8,0x80384190,0x803841a0,,,0x80386e18,0x80386e20,,,, 1,,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, diff --git a/source/egg/util/eggCntFile.cpp b/source/egg/util/eggCntFile.cpp index 56fc0ca72..1227cdb3d 100644 --- a/source/egg/util/eggCntFile.cpp +++ b/source/egg/util/eggCntFile.cpp @@ -5,30 +5,7 @@ namespace EGG { static nw4r::ut::List gCntFileList; static int gCurrentCntFile; -#ifdef NON_MATCHING -CntFile::CntFile() : mOpen(false) { initThreading(); } -#else -extern "C" void initThreading__Q23EGG7CntFileFv(void*); -extern "C" asm void* __ct__Q23EGG7CntFileFv(void*) { - stwu r1, -16(r1); - mflr r0; - lis r4, -32726; - stw r0, 20(r1); - li r0, 0; - addi r4, r4, 10760; - stw r31, 12(r1); - mr r31, r3; - stb r0, 4(r3); - stw r4, 0(r3); - bl initThreading__Q23EGG7CntFileFv; - mr r3, r31; - lwz r31, 12(r1); - lwz r0, 20(r1); - mtlr r0; - addi r1, r1, 0x10; - blr; -} -#endif +CntFile::CntFile() { initThreading(); } CntFile::~CntFile() { close(); } void CntFile::initThreading() { @@ -44,23 +21,23 @@ void CntFile::initThreading() { bool CntFile::spawnFileHandle(const char* path, void* cnt_handle) { int entry_num = CNTConvertPathToEntrynum(cnt_handle, path); - if (!this->mOpen && entry_num != -1) { + if (!this->mIsOpen && entry_num != -1) { int cnt_file = contentFastOpenNAND(cnt_handle, entry_num, &this->mFileHnd); if (!cnt_file) { - this->mOpen = 1; + this->mIsOpen = 1; nw4r::ut::List_Append(&gCntFileList, this); this->_4C = cnt_handle; } else { - this->mOpen = 0; + this->mIsOpen = 0; this->_4C = 0; } gCurrentCntFile = cnt_file; } - return this->mOpen; + return this->mIsOpen; } void CntFile::close() { - if (!mOpen) { + if (!mIsOpen) { return; } @@ -68,14 +45,14 @@ void CntFile::close() { _4C = false; if (!result) { - mOpen = false; + mIsOpen = false; nw4r::ut::List_Remove(&gCntFileList, this); } gCurrentCntFile = result; } -int CntFile::readData(void* fileBuffer, u32 length, s32 offset) { +s32 CntFile::readData(void* buffer, s32 length, s32 offset) { OSLockMutex(&_08); if (_9C) { OSUnlockMutex(&_08); @@ -83,7 +60,7 @@ int CntFile::readData(void* fileBuffer, u32 length, s32 offset) { } _9C = OSGetCurrentThread(); - const s32 result = contentReadNAND(&mFileHnd, fileBuffer, length, offset); + const s32 result = contentReadNAND(&mFileHnd, buffer, length, offset); // TODO: Pattern? gCurrentCntFile = (result >> 31) & result; @@ -92,8 +69,8 @@ int CntFile::readData(void* fileBuffer, u32 length, s32 offset) { return result; } -int CntFile::writeData(const void*, int, int) { return -1; } -int CntFile::open(const char*) { return 0; } +s32 CntFile::writeData(const void*, s32, s32) { return -1; } +bool CntFile::open(const char*) { return false; } u32 CntFile::getFileSize() const { return CNTGetLength(&mFileHnd); } } // namespace EGG diff --git a/source/egg/util/eggCntFile.hpp b/source/egg/util/eggCntFile.hpp index 3de33a0e7..b6759395f 100644 --- a/source/egg/util/eggCntFile.hpp +++ b/source/egg/util/eggCntFile.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -9,7 +10,7 @@ namespace EGG { -class CntFile { +class CntFile : public File { public: CntFile(); @@ -24,15 +25,13 @@ class CntFile { public: virtual ~CntFile(); - virtual int open(const char*); - virtual void close(); - virtual int readData(void* fileBuffer, u32 length, s32 offset); - virtual int writeData(const void*, int, int); - virtual u32 getFileSize() const; + bool open(const char* path) override; + void close() override; + s32 readData(void* buffer, s32 length, s32 offset) override; + s32 writeData(const void* buffer, s32 length, s32 offset) override; + u32 getFileSize() const override; private: - bool mOpen; - char _p[3]; OSMutex _08; OSMutex _20; u32 _38; From 8040ed7ad86e56f2def3ac290de0a4a88d62ca39 Mon Sep 17 00:00:00 2001 From: Pablo Stebler Date: Sat, 14 Aug 2021 03:57:26 +0200 Subject: [PATCH 161/477] Decompile eggDvdRipper (#111) * [WIP] Decompile eggDvdRipper * Match second loadToMainRAM * Start matching fourth decomp function * Add documentation for the 3rd and 4th methods Co-authored-by: riidefi <34194588+riidefi@users.noreply.github.com> --- pack/dol_objects.txt | 8 +- pack/dol_slices.csv | 1 + pack/symbols.txt | 6 + source/egg/core/eggDvdFile.hpp | 2 + source/egg/core/eggDvdRipper.cpp | 423 ++++++++++++++++++++++++++++ source/egg/core/eggDvdRipper.hpp | 158 +++++++++++ source/egg/core/eggStreamDecomp.cpp | 12 +- source/egg/core/eggStreamDecomp.hpp | 24 +- source/rvl/rvlDvd.h | 3 + source/rvl/vi.h | 4 +- sources.py | 1 + 11 files changed, 620 insertions(+), 22 deletions(-) create mode 100644 source/egg/core/eggDvdRipper.cpp create mode 100644 source/egg/core/eggDvdRipper.hpp diff --git a/pack/dol_objects.txt b/pack/dol_objects.txt index ac5901173..e346b9590 100644 --- a/pack/dol_objects.txt +++ b/pack/dol_objects.txt @@ -276,7 +276,7 @@ out/dol/text_801ecff4_8020f62c.o out/dol/data_802a2543_802a2668.o out/eggAllocator.o out/dol/bss_80357238_803832d8.o -out/dol/sdata_80385eec_80385fc0.o +out/dol/sdata_80385eec_80385f88.o out/dol/sbss_80386d38_80386d80.o out/eggArchive.o out/dol/text_8020fcc4_8020fcd4.o @@ -294,14 +294,16 @@ out/dol/data_802a2b54_802a2da0.o out/dol/bss_80383510_80384190.o out/dol/sbss_80386dcc_80386e18.o out/eggDvdFile.o -out/dol/text_802226d8_802269a8.o +out/eggDvdRipper.o +out/dol/text_80222ccc_802269a8.o out/dol/data_802a2dc8_802a2ff8.o out/eggExpHeap.o out/dol/text_80226f04_80229540.o out/dol/rodata_8025771a_80257740.o out/dol/data_802a3024_802a30b0.o out/dol/bss_803841a0_80384320.o -out/dol/sbss_80386e20_80386e90.o +out/dol/sdata_80385f89_80385fc0.o +out/dol/sbss_80386e28_80386e90.o out/eggGraphicsFifo.o out/dol/data_802a30bc_802a30c0.o out/dol/sbss_80386e99_80386ea0.o diff --git a/pack/dol_slices.csv b/pack/dol_slices.csv index f5c0652d0..ba39ed24a 100644 --- a/pack/dol_slices.csv +++ b/pack/dol_slices.csv @@ -120,6 +120,7 @@ enabled,strip,name,initStart,initEnd,extabStart,extabEnd,extabindexStart,extabin 1,1,source/egg/util/eggCntFile.cpp,,,,,,,0x80214E68,0x80215168,,,,,,,0x802A2A08,0x802A2A28,,,,,0x80386DC8,0x80386DCC,,,, 1,,source/egg/core/eggDisposer.cpp,,,,,,,0x8021A0F0,0x8021A1B8,,,,,,,0x802A2B48,0x802A2B54,0x80383500,0x80383510,,,,,,,, 1,1,source/egg/core/eggDvdFile.cpp,,,,,,,0x8022231c,0x802226d8,,,,,,,0x802a2da0,0x802a2dc8,0x80384190,0x803841a0,,,0x80386e18,0x80386e20,,,, +1,,source/egg/core/eggDvdRipper.cpp,,,,,,,0x802226d8,0x80222ccc,,,,,,,,,,,0x80385f88,0x80385f89,0x80386e20,0x80386e28,,,, 1,,source/egg/core/eggExpHeap.cpp,,,,,,,0x802269A8,0x80226F04,,,,,0x80257700,0x8025771A,0x802A2FF8,0x802A3024,,,,,,,,,, 1,,source/egg/core/eggGraphicsFifo.cpp,,,,,,,0x80229540,0x802296A8,,,,,,,0x802A30B0,0x802A30BC,,,,,0x80386E90,0x80386E99,,,, 1,,source/egg/core/eggHeap.cpp,,,,,,,0x802296A8,0x80229FAC,,,,,0x80257740,0x80257824,0x802A30C0,0x802a30ec,0x80384320,0x80384348,,,0x80386EA0,0x80386EC0,0x80388D68,0x80388D80,, diff --git a/pack/symbols.txt b/pack/symbols.txt index dfadb6bbd..9192b1f64 100644 --- a/pack/symbols.txt +++ b/pack/symbols.txt @@ -2171,6 +2171,12 @@ 0x801ef0dc SSLSetRootCA 0x801ef224 SSLSetBuiltinRootCA 0x801ef2ec SSLSetBuiltinClientCert +0x8022235c __ct__Q23EGG7DvdFileFv +0x802223a0 __dt__Q23EGG7DvdFileFv +0x802226d8 loadToMainRAM__Q23EGG9DvdRipperFPCcPUcPQ23EGG4HeapQ33EGG9DvdRipper15EAllocDirectionUlPUlPUl +0x8022277c loadToMainRAM__Q23EGG9DvdRipperFPQ23EGG7DvdFilePUcPQ23EGG4HeapQ33EGG9DvdRipper15EAllocDirectionUlPUlPUl +0x8022293c loadToMainRAMDecomp__Q23EGG9DvdRipperFPCcPQ23EGG12StreamDecompPUcPQ23EGG4HeapQ33EGG9DvdRipper15EAllocDirectionUlUlUl +0x802229e8 loadToMainRAMDecomp__Q23EGG9DvdRipperFPQ23EGG7DvdFilePQ23EGG12StreamDecompPUcPQ23EGG4HeapQ33EGG9DvdRipper15EAllocDirectionUlUlUl 0x8022f80c sqrt__Q23EGG5MathfFf 0x8022f85c frsqrt__Q23EGG5MathfFf 0x80270fd0 __ctype_mapC diff --git a/source/egg/core/eggDvdFile.hpp b/source/egg/core/eggDvdFile.hpp index 4781cb8d3..864cf9c57 100644 --- a/source/egg/core/eggDvdFile.hpp +++ b/source/egg/core/eggDvdFile.hpp @@ -61,6 +61,8 @@ class DvdFile : public File { //! @returns If the file successfully opened (mIsOpen). virtual bool open(const char* path, void*); + inline DVDFileInfo* getFileInfo() { return &mFileInfo; } + private: void initiate(); diff --git a/source/egg/core/eggDvdRipper.cpp b/source/egg/core/eggDvdRipper.cpp new file mode 100644 index 000000000..738ffc68b --- /dev/null +++ b/source/egg/core/eggDvdRipper.cpp @@ -0,0 +1,423 @@ +/** + * @file + * @brief Implementations for the EGG DVD ripper. + */ + +#include + +#include +#include + +namespace EGG { + +DvdRipper::DvdRipperCallback DvdRipper::sCallback = nullptr; + +bool DvdRipper::sErrorRetry = true; + +u8* DvdRipper::loadToMainRAM(const char* path, u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32* amountRead, u32* fileSize) { + DvdFile dvdFile; + if (!dvdFile.open(path)) { + return nullptr; + } + + return loadToMainRAM(&dvdFile, dst, heap, allocDirection, offset, amountRead, + fileSize); +} + +inline DvdRipper::Arg::Arg() { + _04 = 0; + _08 = 0; + _0c = 1; + _10 = 0; + _14 = _18; +} + +u8* DvdRipper::loadToMainRAM(DvdFile* dvdFile, u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32* amountRead, u32* fileSize) { + u32 roundedFileSize, roundedFileSize2; + bool allocatedFromHeap = false; + + if (sCallback != nullptr) { + Arg arg; + sCallback(arg); + } + + u32 exactFileSize = dvdFile->getFileSize(); + if (fileSize != nullptr) { + *fileSize = exactFileSize; + } + roundedFileSize = ROUND_UP(exactFileSize, 32); + + if (dst == nullptr) { + s32 align = allocDirection == ALLOC_DIR_TOP ? 32 : -32; + dst = Heap::alloc(roundedFileSize - offset, heap, align); + allocatedFromHeap = true; + } + + if (dst == nullptr) { + return nullptr; + } + + if (offset != 0) { + u8 buf[64]; + u8* buf_ptr = (u8*)ROUND_UP(&buf, 32); + while (true) { + s32 result = DVDRead(dvdFile->getFileInfo(), buf_ptr, 32, offset); + if (result >= 0) { + break; + } + + if (result == DVD_RESULT_CANCELED || !sErrorRetry) { + if (allocatedFromHeap) { + Heap::free(dst, nullptr); + } + return nullptr; + } + + VIWaitForRetrace(); + } + + DCInvalidateRange(buf_ptr, 32); + } + + roundedFileSize2 = roundedFileSize - offset; + while (true) { + s32 result = DVDRead(dvdFile->getFileInfo(), dst, roundedFileSize2, offset); + if (result >= 0) { + break; + } + + if (result == DVD_RESULT_CANCELED || !sErrorRetry) { + if (allocatedFromHeap) { + Heap::free(dst, nullptr); + } + return nullptr; + } + + VIWaitForRetrace(); + } + + if (amountRead) { + *amountRead = roundedFileSize2; + } + + return dst; +} + +u8* DvdRipper::loadToMainRAMDecomp(const char* path, StreamDecomp* streamDecomp, + u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32 size, u32 maxChunkSize) { + DvdFile dvdFile; + if (!dvdFile.open(path)) { + return nullptr; + } + + return loadToMainRAMDecomp(&dvdFile, streamDecomp, dst, heap, allocDirection, + offset, size, maxChunkSize); +} + +#ifdef NON_MATCHING +u8* DvdRipper::loadToMainRAMDecomp(DvdFile* dvdFile, StreamDecomp* streamDecomp, + u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32 size, u32 maxChunkSize) { + s32 uncompressedAlign, compressedAlign; + bool allocatedFromHeap = false; + + // Setup aligns + uncompressedAlign = allocDirection == ALLOC_DIR_TOP ? 32 : -32; + compressedAlign = allocDirection == ALLOC_DIR_TOP ? -32 : 32; + + // Compute the compressed size to read from the file + dvdFile->getFileSize(); // From debug print? + u32 exactFileSize = dvdFile->getFileSize(); + u32 compressedSize = ROUND_UP(exactFileSize, 32) - offset; + if (size != 0) { + compressedSize = size; + } + + // Allocate memory for the header + void* header = heap->alloc(streamDecomp->getHeaderSize(), compressedAlign); + if (header == nullptr) { + return nullptr; + } + + // Read the header + s32 result = DVDRead(dvdFile->getFileInfo(), header, + streamDecomp->getHeaderSize(), offset); + if (result != static_cast(streamDecomp->getHeaderSize())) { + // Memory leak in this case! + return nullptr; + } + + // Get the size of the uncompressed data + u32 uncompressedSize = streamDecomp->getUncompressedSize(header); + heap->free(header); + + // Allocate memory for the uncompressed data if necessary + if (dst == nullptr) { + dst = (u8*)heap->alloc(uncompressedSize, uncompressedAlign); + allocatedFromHeap = true; + } + if (dst == nullptr) { + return nullptr; + } + + // Allocate memory for the compressed data + void* chunk = heap->alloc(maxChunkSize, compressedAlign); + if (chunk == nullptr) { + if (allocatedFromHeap) { + heap->free(dst); + } + return nullptr; + } + + // Read and decompress the data + u32 chunkOffset = 0; + streamDecomp->init(dst, compressedSize); + while (true) { + u32 remainingSize = compressedSize - chunkOffset; + u32 chunkSize = remainingSize < maxChunkSize ? remainingSize : maxChunkSize; + + s32 result = DVDRead(dvdFile->getFileInfo(), chunk, ROUND_UP(chunkSize, 32), + offset + chunkOffset); + if (result < 0) { + heap->free(chunk); + if (allocatedFromHeap) { + heap->free(dst); + } + return nullptr; + } + + if (streamDecomp->decomp(chunk, result)) { + break; + } + + chunkOffset += result; + } + + heap->free(chunk); + return dst; +} +#else +MARK_BINARY_BLOB( + loadToMainRAMDecomp__Q23EGG9DvdRipperFPQ23EGG7DvdFilePQ23EGG12StreamDecompPUcPQ23EGG4HeapQ33EGG9DvdRipper15EAllocDirectionUlUlUl, + 0x802229e8, 0x80222ccc); +asm u8* DvdRipper::loadToMainRAMDecomp(DvdFile* dvdFile, + StreamDecomp* streamDecomp, u8* dst, + Heap* heap, + EAllocDirection allocDirection, + u32 offset, u32 size, u32 maxChunkSize) { + // clang-format off + nofralloc; + stwu r1, -0x40(r1); + mflr r0; + cmpwi r7, 1; + stw r0, 0x44(r1); + stmw r20, 0x10(r1); + mr r26, r3; + mr r27, r4; + mr r28, r5; + mr r29, r6; + mr r30, r8; + mr r20, r9; + mr r31, r10; + li r22, 0; + li r24, -32; + bne lbl_80222a28; + li r24, 0x20; +lbl_80222a28: + cmpwi r7, 1; + li r23, 0x20; + bne lbl_80222a38; + li r23, -32; +lbl_80222a38: + lwz r12, 0(r26); + mr r3, r26; + lwz r12, 0x1c(r12); + mtctr r12; + bctrl; + lwz r12, 0(r26); + mr r3, r26; + lwz r12, 0x1c(r12); + mtctr r12; + bctrl; + addi r0, r3, 0x1f; + cmpwi r20, 0; + rlwinm r0, r0, 0, 0, 0x1a; + subf r21, r30, r0; + beq lbl_80222a78; + mr r21, r20; +lbl_80222a78: + lwz r12, 0(r27); + mr r3, r27; + lwz r12, 0x10(r12); + mtctr r12; + bctrl; + lwz r12, 0(r29); + mr r4, r3; + mr r3, r29; + mr r5, r23; + lwz r12, 0x14(r12); + mtctr r12; + bctrl; + cmpwi r3, 0; + mr r20, r3; + bne lbl_80222abc; + li r3, 0; + b lbl_80222cb8; +lbl_80222abc: + lwz r12, 0(r27); + mr r3, r27; + lwz r12, 0x10(r12); + mtctr r12; + bctrl; + mr r5, r3; + mr r4, r20; + mr r6, r30; + addi r3, r26, 0x3c; + li r7, 2; + bl DVDReadPrio; + lwz r12, 0(r27); + mr r25, r3; + mr r3, r27; + lwz r12, 0x10(r12); + mtctr r12; + bctrl; + cmpw r25, r3; + bne lbl_80222b28; + lwz r12, 0(r27); + mr r3, r27; + mr r4, r20; + lwz r12, 0x14(r12); + mtctr r12; + bctrl; + mr r25, r3; + b lbl_80222b30; +lbl_80222b28: + li r3, 0; + b lbl_80222cb8; +lbl_80222b30: + lwz r12, 0(r29); + mr r3, r29; + mr r4, r20; + lwz r12, 0x18(r12); + mtctr r12; + bctrl; + cmpwi r28, 0; + bne lbl_80222b74; + lwz r12, 0(r29); + mr r3, r29; + mr r4, r25; + mr r5, r24; + lwz r12, 0x14(r12); + mtctr r12; + bctrl; + mr r28, r3; + li r22, 1; +lbl_80222b74: + cmpwi r28, 0; + bne lbl_80222b84; + li r3, 0; + b lbl_80222cb8; +lbl_80222b84: + lwz r12, 0(r29); + mr r3, r29; + mr r4, r31; + mr r5, r23; + lwz r12, 0x14(r12); + mtctr r12; + bctrl; + cmpwi r3, 0; + mr r20, r3; + beq lbl_80222c8c; + lwz r12, 0(r27); + mr r3, r27; + mr r4, r28; + mr r5, r21; + lwz r12, 8(r12); + li r23, 0; + mtctr r12; + bctrl; +lbl_80222bcc: + subf r5, r23, r21; + mr r4, r20; + cmplw r5, r31; + addi r3, r26, 0x3c; + blt lbl_80222be4; + mr r5, r31; +lbl_80222be4: + addi r0, r5, 0x1f; + add r6, r30, r23; + rlwinm r5, r0, 0, 0, 0x1a; + li r7, 2; + bl DVDReadPrio; + cmpwi r3, 0; + mr r24, r3; + bge lbl_80222c44; + lwz r12, 0(r29); + mr r3, r29; + mr r4, r20; + lwz r12, 0x18(r12); + mtctr r12; + bctrl; + cmpwi r22, 0; + beq lbl_80222c3c; + lwz r12, 0(r29); + mr r3, r29; + mr r4, r28; + lwz r12, 0x18(r12); + mtctr r12; + bctrl; +lbl_80222c3c: + li r3, 0; + b lbl_80222cb8; +lbl_80222c44: + lwz r12, 0(r27); + mr r3, r27; + mr r4, r20; + mr r5, r24; + lwz r12, 0xc(r12); + mtctr r12; + bctrl; + cmpwi r3, 0; + bne lbl_80222c70; + add r23, r23, r24; + b lbl_80222bcc; +lbl_80222c70: + lwz r12, 0(r29); + mr r3, r29; + mr r4, r20; + lwz r12, 0x18(r12); + mtctr r12; + bctrl; + b lbl_80222cb4; +lbl_80222c8c: + cmpwi r22, 0; + beq lbl_80222cac; + lwz r12, 0(r29); + mr r3, r29; + mr r4, r28; + lwz r12, 0x18(r12); + mtctr r12; + bctrl; +lbl_80222cac: + li r3, 0; + b lbl_80222cb8; +lbl_80222cb4: + mr r3, r28; +lbl_80222cb8: + lmw r20, 0x10(r1); + lwz r0, 0x44(r1); + mtlr r0; + addi r1, r1, 0x40; + blr; + // clang-format on +} +#endif + +} // namespace EGG diff --git a/source/egg/core/eggDvdRipper.hpp b/source/egg/core/eggDvdRipper.hpp new file mode 100644 index 000000000..b22236c4c --- /dev/null +++ b/source/egg/core/eggDvdRipper.hpp @@ -0,0 +1,158 @@ +/** + * @file + * @brief Headers for the EGG DVD ripper. + */ + +#pragma once + +#include +#include +#include + +namespace EGG { + +class DvdRipper { +public: + class Arg { + public: + Arg(); + + private: + unk32 _00; + unk32 _04; + unk32 _08; + unk32 _0c; + unk32 _10; + unk8* _14; + unk8 _18[8]; + }; + + typedef void (*DvdRipperCallback)(const Arg& arg); + +private: + static DvdRipperCallback sCallback; + static bool sErrorRetry; //!< If true, the game will retry non-canceled + //!< DVDRead attempts. + +public: + //! @brief Describes the direction of allocating new blocks in a free memory + //! region. + //! + enum EAllocDirection { + ALLOC_DIR_PAD, // Unseen/unhandled so far + ALLOC_DIR_TOP, //!< [1] Negative alignment; allocate block from top of free + //!< block. + ALLOC_DIR_BOTTOM //!< [2] Positive alignment; allocate block from bottom of + //!< free block. + }; + + //! @brief Load a file on the disc to main RAM given the path. + //! + //! @param[in] path Path to the file on the disc. + //! @param[in,out] dst Destination buffer to write file. If + //! nullptr, an appropriately sized block of memory will be allocated. + //! Otherwise the pre-allocated block MUST be big enough to store the file. + //! @param[in] heap Heap to use if allocating a block. + //! Cannot be nullptr if dst is also nullptr. + //! @param[in] allocDirection Allocation direction of dst if it's + //! nullptr. + //! @param[in] offset Offset of the file on the disc to start + //! reading from. + //! @param[out] amountRead If not nullptr, will be set to the + //! amount of bytes read from the disc. + //! @param[out] fileSize If not nullptr, will be set to the file + //! size of the file on disc (unrounded/offset). + //! + //! @returns Pointer to the ripped file, or nullptr in case of failure. + //! + static u8* loadToMainRAM(const char* path, u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32* amountRead, u32* fileSize); + + //! @brief Load a file on the disc to main RAM given the DvdFile wrapper. + //! + //! @param[in] dvdFile Pointer to the DvdFile wrapper for the + //! file on the disc. + //! @param[in,out] dst Destination buffer to write file. If + //! nullptr, an appropriately sized block of memory will be allocated. + //! Otherwise the pre-allocated block MUST be big enough to store the file. + //! @param[in] heap Heap to use if allocating a block. + //! Cannot be nullptr if dst is also nullptr. + //! @param[in] allocDirection Allocation direction of dst if it's + //! nullptr. + //! @param[in] offset Offset of the file on the disc to start + //! reading from. + //! @param[out] amountRead If not nullptr, will be set to the + //! amount of bytes read from the disc. + //! @param[out] fileSize If not nullptr, will be set to the file + //! size of the file on disc (unrounded/offset). + //! + //! @returns Pointer to the ripped file, or nullptr in case of failure. + //! + static u8* loadToMainRAM(DvdFile* dvdFile, u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32* amountRead, u32* fileSize); + + //! @brief Load and decompress a file on the disc to main RAM given the path. + //! + //! @param[in] path Path to the file on the disc. + //! @param[in] streamDecomp Uninitialized decompressor. + //! @param[in,out] dst Destination buffer to write file. If + //! nullptr, an appropriately sized block of memory will be allocated. + //! Otherwise the pre-allocated block MUST be big enough to store the + //! DECOMPRESSED file. + //! @param[in] heap Heap to use if allocating a block. + //! Cannot be nullptr if dst is also nullptr. + //! @param[in] allocDirection Allocation direction of dst if it's + //! nullptr. + //! @param[in] offset Offset of the file on the disc to start + //! reading from. + //! @param[in] size If non zero, size of the file to read. + //! The sum of offset and size MUST NOT be larger than the compressed file + //! size. + //! @param[in] maxChunkSize The maximum size of the temporary buffer + //! that will be allocated to read the compressed file. + //! + //! @returns Pointer to the ripped and decompressed file, or nullptr in case + //! of failure. + //! + static u8* loadToMainRAMDecomp(const char* path, StreamDecomp* streamDecomp, + u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32 size, u32 maxChunkSize); + + //! @brief Load and decompress a file on the disc to main RAM given the + //! DvdFile wrapper. + //! + //! @param[in] dvdFile Pointer to the DvdFile wrapper for the + //! file on the disc. + //! @param[in] streamDecomp Uninitialized decompressor. + //! @param[in,out] dst Destination buffer to write file. If + //! nullptr, an appropriately sized block of memory will be allocated. + //! Otherwise the pre-allocated block MUST be big enough to store the + //! DECOMPRESSED file. + //! @param[in] heap Heap to use if allocating a block. + //! Cannot be nullptr if dst is also nullptr. + //! @param[in] allocDirection Allocation direction of dst if it's + //! nullptr. + //! @param[in] offset Offset of the file on the disc to start + //! reading from. + //! @param[in] size If non zero, size of the file to read. + //! The sum of offset and size MUST NOT be larger than the compressed file + //! size. + //! @param[in] maxChunkSize The maximum size of the temporary buffer + //! that will be allocated to read the compressed file. + //! + //! @returns Pointer to the ripped and decompressed file, or nullptr in case + //! of failure. + //! + static u8* loadToMainRAMDecomp(DvdFile* dvdFile, StreamDecomp* streamDecomp, + u8* dst, Heap* heap, + EAllocDirection allocDirection, u32 offset, + u32 size, u32 maxChunkSize); + +private: + DvdRipper() {} +}; + +} // namespace EGG diff --git a/source/egg/core/eggStreamDecomp.cpp b/source/egg/core/eggStreamDecomp.cpp index f9be0c7a1..74d8e0d3b 100644 --- a/source/egg/core/eggStreamDecomp.cpp +++ b/source/egg/core/eggStreamDecomp.cpp @@ -7,21 +7,21 @@ namespace EGG { -bool LZStreamDecomp::initialize(void* dst, unk arg3) { +bool LZStreamDecomp::init(void* dst, u32 maxCompressedSize) { mpDst = dst; - _08 = arg3; + mMaxCompressedSize = maxCompressedSize; CXInitUncompContextLZ(&mContext, dst); return true; } -bool LZStreamDecomp::process(const void* src, u32 len) { +bool LZStreamDecomp::decomp(const void* src, u32 len) { return CXReadUncompLZ(&mContext, src, len) == CXResultSuccess; } -u32 LZStreamDecomp::getDataOffset() { return 32; } +u32 LZStreamDecomp::getHeaderSize() { return 32; } -u32 LZStreamDecomp::getExpandSize(const void* src) { +u32 LZStreamDecomp::getUncompressedSize(const void* src) { return CXGetUncompressedSize(src); } -} // namespace EGG \ No newline at end of file +} // namespace EGG diff --git a/source/egg/core/eggStreamDecomp.hpp b/source/egg/core/eggStreamDecomp.hpp index a856261fe..cb95d0ee8 100644 --- a/source/egg/core/eggStreamDecomp.hpp +++ b/source/egg/core/eggStreamDecomp.hpp @@ -14,26 +14,26 @@ namespace EGG { //! @brief Interface for streamed decompression //! -class IStreamDecomp { +class StreamDecomp { public: - virtual bool initialize(void* dst, unk arg3) = 0; - virtual bool process(const void* src, u32 len) = 0; - virtual u32 getDataOffset() = 0; - virtual u32 getExpandSize(const void* src) = 0; + virtual bool init(void* dst, u32 maxCompressedSize) = 0; + virtual bool decomp(const void* src, u32 len) = 0; + virtual u32 getHeaderSize() = 0; + virtual u32 getUncompressedSize(const void* src) = 0; }; -class LZStreamDecomp : public IStreamDecomp { +class LZStreamDecomp : public StreamDecomp { public: - bool initialize(void* dst, unk arg3) override; - bool process(const void* src, u32 len) override; - u32 getDataOffset() override; - u32 getExpandSize(const void* src) override; + bool init(void* dst, u32 maxCompressedSize) override; + bool decomp(const void* src, u32 len) override; + u32 getHeaderSize() override; + u32 getUncompressedSize(const void* src) override; private: void* mpDst; //!< [+0x04] Pointer to the decompressed destination buffer. - unk _08; //!< [+0x08] Second argument of constructor -- unused. + u32 mMaxCompressedSize; //!< [+0x08] Not used by the LZ decompressor. CXUncompContextLZ mContext; //!< [+0x0C] CX streaming LZ decompression context. }; -} // namespace EGG \ No newline at end of file +} // namespace EGG diff --git a/source/rvl/rvlDvd.h b/source/rvl/rvlDvd.h index d1aac617f..78c103454 100644 --- a/source/rvl/rvlDvd.h +++ b/source/rvl/rvlDvd.h @@ -45,9 +45,12 @@ struct DVDFileInfo { DVDCallback callback; }; +#define DVD_RESULT_CANCELED -3 + int DVDOpen(const char* fileName, DVDFileInfo* fileInfo); int DVDFastOpen(s32 entrynum, DVDFileInfo* fileInfo); s32 DVDReadPrio(DVDFileInfo* fileInfo, void* addr, s32 length, s32 offset, s32 prio); +#define DVDRead(fileInfo, addr, length, offset) DVDReadPrio((fileInfo), (addr), (length), (offset), 2) int DVDReadAsyncPrio(DVDFileInfo* fileInfo, void* addr, s32 length, s32 offset, DVDCallback callback, s32 prio); int DVDClose(DVDFileInfo* fileInfo); s32 DVDGetCommandBlockStatus(const DVDCommandBlock* block); diff --git a/source/rvl/vi.h b/source/rvl/vi.h index e0ba9a961..3a0ffec06 100644 --- a/source/rvl/vi.h +++ b/source/rvl/vi.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #ifdef __cplusplus @@ -56,4 +58,4 @@ unsigned int VIGetTvFormat(); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/sources.py b/sources.py index c581e7be5..e0ffbf58f 100644 --- a/sources.py +++ b/sources.py @@ -189,6 +189,7 @@ class Source: Source(src="source/egg/util/eggCntFile.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggDisposer.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggDvdFile.cpp", cc='4201_127', opts=EGG_OPTS), + Source(src="source/egg/core/eggDvdRipper.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggExpHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -use_lmw_stmw=on "), Source(src="source/egg/core/eggGraphicsFifo.cpp", cc='4201_127', opts=EGG_OPTS), Source(src="source/egg/core/eggHeap.cpp", cc='4201_127', opts=EGG_OPTS + " -ipa file -use_lmw_stmw=on "), From 020df290ca04cb82a57c2629273154fca16595c2 Mon Sep 17 00:00:00 2001 From: riidefi <34194588+riidefi@users.noreply.github.com> Date: Fri, 13 Aug 2021 23:30:12 -0600 Subject: [PATCH 162/477] Add Doxyfile --- .gitignore | 2 + Doxyfile | 2657 ++++++++++++++++++++++++++++++++++++++++++++++ doxygen_dark.css | 583 ++++++++++ 3 files changed, 3242 insertions(+) create mode 100644 Doxyfile create mode 100644 doxygen_dark.css diff --git a/.gitignore b/.gitignore index 9cbcaf06c..a92d68c08 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,5 @@ out.html .vscode/ *.bndb .~lock.* + +doxygen \ No newline at end of file diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 000000000..f935babc4 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,2657 @@ +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = mkw + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doxygen + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = YES + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = YES + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = C:\Users\rii\Documents\dev\mkw\source + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f \ + *.for \ + *.tcl \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = doxygen_dark.css + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = YES + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /