JSRF-Decompilation/decompile/src/XDK/Xapi/xapi0.c
KeybadeBlox e58f774d82 Add various data structures
Stuff needed to get Core.obj delinking again.
2026-03-20 23:29:02 -04:00

171 lines
5.2 KiB
C

/* JSRF Decompilation: XDK/Xapi/xapi0.c
Entrypoint for Xbox programs.
Awkwardly, the placement in the .xbe suggests this is not part of the C runtime
(which is normally what provides mainCRTStartup()), but rather part of Xapi.
It's been named xapi0 in analogy to crt0 in a C runtime.
*/
#include "../CRT/stddef.h"
#include "../Win32.h"
// Things that should get declared in a header somewhere
#define XLD_LAUNCH_DASHBOARD_ERROR 1
#define XLD_ERROR_INVALID_XBE 1
extern struct IMAGE_TLS_DIRECTORY {
DWORD StartAddressOfRawData;
DWORD EndAddressOfRawData;
DWORD * AddressOfIndex;
void * AddressOfCallbacks;
DWORD SizeOfZeroFill;
DWORD Characteristics;
} _tls_used;
extern int _tls_array;
extern int _tls_index;
extern int XapiTlsSize;
DWORD __stdcall mainXapiStartup(LPVOID lpThreadParameter);
void __stdcall XapiInitProcess(void);
BOOL __stdcall CloseHandle (HANDLE hHandle);
typedef DWORD (__stdcall * LPTHREAD_START_ROUTINE)(LPVOID lpThreadParameter);
HANDLE __stdcall CreateThread(
void * lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
DWORD * lpThreadId
);
void __stdcall XapiBootToDash(
DWORD dwReason,
DWORD dwParameter1,
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. 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: 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 assembly. 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 assembly to C 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);
// main() is not supposed to return, so error out if it does
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
// Status: matching
void mainCRTStartup(void) {
/* The true entrypoint of the game, spawning a thread for the rest to run in
The linker automatically sets this function to the entrypoint.
*/
HANDLE thread;
// Figure out available thread-local storage (used by CreateThread())
XapiTlsSize = 4 + ((
(_tls_used.EndAddressOfRawData - _tls_used.StartAddressOfRawData) +
_tls_used.SizeOfZeroFill
) + 15 & 0xFFFFFFF0); // Round up to nearest 0x10
*_tls_used.AddressOfIndex = -1 * XapiTlsSize/4;
// Launch program as a thread
thread = CreateThread(NULL, 0, mainXapiStartup, NULL, 0, NULL);
// Boot back to dashboard with an error message if launching failed
if (thread == NULL)
XapiBootToDash(XLD_LAUNCH_DASHBOARD_ERROR, XLD_ERROR_INVALID_XBE, 0);
CloseHandle(thread);
}