Direct Access to Trek.exe's Variable and Structures
If you are interfacing code with Trek.exe (e.g. using DLL injection), accessing Trek.exe's data can be a real pain-in-the-butt. However, the gcc compiler has a nice trick that let's us directly reference data from Trek.exe without using pointers. We do this by defining the symbols during the linking phase.
First declare the symbol in your C program with external linkage:
Code: Select all
extern unsigned char MuddCheats;
Code: Select all
MuddCheats = 1;
When compiling, pass --defsym=SYMBOL=ADDRESS as an option to the linker (with -Wl, if calling gcc):--defsym creates a global symbol in the output file, containing the absolute address given by an expression.
Code: Select all
gcc -Wl,--defsym=_MuddCheats=0x5B1A90 -mdll your.c -o your.dll
If all is working correctly, you should not get any undefined reference errors.
On an Industrial Scale
The above approach works fine for defining a few symbols, but what about when we need a lot? Just use --just-symbols=FILE
Code: Select all
gcc -Wl,--just-symbols=symbols yoursource.c -o yourapp
Code: Select all
_MuddCheats = 0x5B1A90;
_galaxy = 0x5a36b0;
_GCurrentGame = 0x5a28d8;
_GAliens = 0x5a2010;
That's it! Now you can directly access memory from Trek.exe from your code, without having to result to ugly pointer hacks.
Calling Trek.exe Functions from Your Code: A Tale of Two Compilers
As some of you coders already know, Trek.exe was compiled using the Watcom compiler. Watcom was very popular for compiling DOS video games back in the nineties, for it generated highly efficient code (at the time). Now the company that made Watcom scrapped the compiler. It's still available, in open source form, but it's quite outdated, and lacks many modern features that gcc has (like C99/C++11 support).
Anyway, Watcom has a unique register-based calling convention. Let me explain.
Here's a C function prototype:
Code: Select all
int find_root (int a, int b, int c, int d, int e);
Code: Select all
x = find_root (1, 2, 3, 4, 5);
Code: Select all
push 5
push 4
push 3
push 2
push 1
call find_root
add esp, 0x10
Code: Select all
push 5
mov ecx, 4
mov ebx, 3
mov edx, 2
mov eax, 1
call find_root
Some More Magic
gcc has some limited support for register-based calling conventions, as it just happens to pass the first two arguments in the same order as Watcom! How convenient for us. Let me reiterate: only the first two are the same as Watcom's (eax, edx). So you'll have to resort to inline assembly for functions with three or more parameters. Ugh!
You can flag a function to use the register-based calling convention using this function attribute in gcc: __attribute__((regparm(2))). Let's use Trek.exe's UI_Screens_SwitchScreen() function as an example:
Code: Select all
extern void UI_Screens_SwitchScreen (int screen, int data) __attribute__(regparm(2));
Code: Select all
_UI_Screens_SwitchScreen = 0x4B9180;
Code: Select all
UI_Screens_SwitchScreen (12, 0);
Overriding code in Trek.exe is pretty easy -- except for memory protection of course. If you try to write new code into Trek.exe code section (really .text section), it will crash with an access violation. So you need to make the memory pages writable first. Here's some code to do that:
Code: Select all
// Safe replacement for memcpy that is aware of memory protection.
void SafeCopy (void *dest, const void *src, size_t n)
{
// Make the memory page writable.
DWORD OldProtect;
VirtualProtect (dest, n, PAGE_READWRITE, &OldProtect);
// Apply the patch:
memcpy (dest, src, n);
// Restore memory page protection.
VirtualProtect (dest, n, OldProtect, NULL);
}
Code: Select all
// Replace code at the given address by far jumping to the new address.
// It will overwrite 10 bytes at the given address to do so.
void MonkeyPatch (unsigned int offset, void * targetAddr)
{
/* Far jump to absolute target address.
* This takes 10 bytes, two for the opcode, 4 bytes for the jump
* address (at trampoline+2), which points to (trampoline + 6),
* which contains the absolute address of the target function.
*/
unsigned char * trampoline = (unsigned char *) offset;
// First make the memory page writable!
DWORD OldProtection;
VirtualProtect (trampoline, 10, PAGE_READWRITE, &OldProtection);
// Write the jump instruction:
trampoline[0] = 0xff;
trampoline[1] = 0x25;
*(void**)(trampoline+2) = trampoline + 6;
*(void**)(trampoline+6) = targetAddr;
// Restore memory page protection:
VirtualProtect (trampoline, 10, OldProtection, NULL);
}
Code: Select all
MonkeyPatch( 0x458260, ProcessTurn );
Let's say we want to call the InitGraphics() function from our code:
Code: Select all
int InitGraphics (HWND hwnd, uint32_t resnum, const char *mode3d, uint32_t language, void *callback, uint32_t data);
Code: Select all
static int __wrap_InitGraphics (HWND hwnd, uint32_t resnum, const char *mode3d, uint32_t language, void *callback, uint32_t data)
{
uint32_t target = 0x513636; // real address of InitGraphics
int retval;
asm (
"push %6;"
"push %5;"
"movl %4, %%ecx;"
"movl %3, %%ebx;"
"movl %2, %%edx;"
"movl %1, %%eax;"
"call *%7;" // indirect absolute call
"movl %%eax, %0;" // return value
: "=r"(retval) // output operands
: // input operands
"m"(hwnd),
"m"(resnum),
"m"(mode3d),
"m"(language),
"m"(callback),
"m"(data),
"r"(target)
: "%eax", "%ebx", "%ecx", "%edx" // clobbered registers
);
return retval;
}
What about overriding such a function? You'd have to write a thunk (taking no parameters) using inline assembly to pull the arguments from the registers and stack, and then pass them to your own implementation. I leave it as an exercise to the reader