From 823b19371ccdf6dcef61f226edcd4973fa8f6222 Mon Sep 17 00:00:00 2001 From: KeybadeBlox Date: Sun, 22 Feb 2026 14:04:18 -0500 Subject: [PATCH] Fully decompile xapi0 --- decompile/objdiff.json | 2 +- decompile/src/XDK/Xapi/xapi0.c | 103 ++++++++++++++++++++++++++++++--- readme.md | 2 +- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/decompile/objdiff.json b/decompile/objdiff.json index 573b0d4..083041a 100644 --- a/decompile/objdiff.json +++ b/decompile/objdiff.json @@ -50,7 +50,7 @@ "target_path": "target/XDK/Xapi/xapi0.obj", "base_path": "src/XDK/Xapi/xapi0.obj", "metadata": { - "complete": false, + "complete": true, "source_path": "src/XDK/Xapi/xapi0.c" } } diff --git a/decompile/src/XDK/Xapi/xapi0.c b/decompile/src/XDK/Xapi/xapi0.c index 78bfefc..d726005 100644 --- a/decompile/src/XDK/Xapi/xapi0.c +++ b/decompile/src/XDK/Xapi/xapi0.c @@ -21,11 +21,14 @@ extern struct IMAGE_TLS_DIRECTORY { DWORD SizeOfZeroFill; DWORD Characteristics; } _tls_used; +extern int _tls_array; +extern int _tls_index; extern int XapiTlsSize; DWORD __stdcall mainXapiStartup(LPVOID lpThreadParameter); -BOOL __stdcall CloseHandle (HANDLE hHandle ); +void __stdcall XapiInitProcess(void); +BOOL __stdcall CloseHandle (HANDLE hHandle); typedef DWORD (__stdcall * LPTHREAD_START_ROUTINE)(LPVOID lpThreadParameter); HANDLE __stdcall CreateThread( @@ -43,18 +46,102 @@ void __stdcall XapiBootToDash( DWORD dwParameter2 ); +void __stdcall _rtinit(void); +void __stdcall _cinit (void); + // Every program is supposed to have a main(), so we can just assume its -// existence with a declaration here -int main(void); +// existence with a declaration here. Interestingly, it's called with +// arguments here despite being declared without any on the developer's side. +int main( + int argc, + char * * argv, + char * * envp +); // Address: 0x00147FB4 -// Status: unimplemented -DWORD __stdcall mainXapiStartup(LPVOID lpThreadParameter) { -/* Runs some Xbox-specific initialization and then calls main() */ - main(); - return 0; +// Status: matching +__declspec(naked) DWORD __stdcall mainXapiStartup(LPVOID lpThreadParameter) { +/* Runs some Xbox-specific initialization and then calls main() +It appears to be truly impossible in Visual C++ 7.0 to access the FS register +without writing assembly, and also for inline assembly to write to a variable +in a register rather than on the stack. Thus, the only apparent way to make +the middle part of the function match is through writing the instructions +directly. Given that the C parts of this function are so simple that a human +and a compiler would reasonably produce the exact same code, and this function +exhibits other oddities mentioned in the body, it's likely this whole function +was originally written purely in assemblys. In the spirit of decompilation, +however, we'll lift what we can into C. +*/ + XapiInitProcess(); + + // Honestly unsure what's being done here, as FS doesn't seem to follow + // the same structure as 32-bit Windows + __asm { + mov eax, fs:[0x20] + mov eax, [eax+0x250] + test eax, eax + je zero + mov ecx, [eax+0x24] + jmp store + + /* + All this jumping around is strange, as it could easily be a + single branch, which is very plain to see if written out as C + (in fact, while MSVC's behaviour hasn't been tested, it + wouldn't take a very smart compiler to optimize this out if it + was written this way in C). It seems likely this is awkward + human-written assembly. + */ + zero: + xor ecx, ecx + + store: + test ecx, ecx + je end + + /* + This section is suspect because of how it preserves edi despite + it not being used anywhere else, as if it was a separately + written function. If it was a function, it must have been + inlined, but it couldn't be because it must be called from + assembly to use the value in ecx that never went on the stack + (as passing data from C to assembly has to do). Notably, this + function's calling convention does require edi to be preserved, + but if left up to the compiler, it will push and pop edi in the + prologue and epilogue, not here (which this function must be + declared as naked to avoid). + + It may be that this function was originally written entirely in + assembly and declared naked, with the author deciding to + preserve edi only in this one section using it. + */ + push edi + mov eax, fs:[0x28] + mov edi, fs:[_tls_array] + mov edx, [_tls_index] + mov edx, [edi + edx*0x4] + sub edx, [eax + 0x28] + mov byte ptr [ecx], 0x1 + add edx, _tls_array + mov [ecx + 0x4], edx + pop edi + + end: + }; + + _rtinit(); + _cinit(); + main(0, NULL, NULL); + + XapiBootToDash(XLD_LAUNCH_DASHBOARD_ERROR, XLD_ERROR_INVALID_XBE, 0); + + // Return 0 to satisfy signature required for thread functions + _asm { + xor eax, eax + ret 4 + }; } // Address: 0x00148023 diff --git a/readme.md b/readme.md index 70ccc22..25abed9 100644 --- a/readme.md +++ b/readme.md @@ -3,7 +3,7 @@ A matching decompilation of the Xbox game Jet Set Radio Future. ## Progress - Delinking progress: 1.03% (26559 out of 2574172 bytes in XBE address space) -- Decompilation progress: 19.0% (32 out of the 168 functions delinked so far) +- Decompilation progress: 19.6% (33 out of the 168 functions delinked so far) - **Estimated total progress: 0.20%** (previous two multiplied together) ## Roadmap