For programmers, this C program should explain it well enough:
Code: Select all
#include <windows.h>
// Function pointer set to trek.exe's main() function.
static void (* start_botf)(void) = 0x54AC6A;
void main()
{
HMODULE DllBase = LoadLibrary( "trek.exe" );
assert( DllBase == 0x400000 );
// BotF will exit the whole process when done, so no code after this would be called.
start_botf();
}
The only requirement: when compiling and linking the loader program, you MUST specify a base offset that doesn't conflict with the memory addresses used in BotF. Trek.exe loads at address 0x400000, so if you choose, say, 0x800000 as the base address, this program will run BotF. If you don't do this, the relocation table will be used to move Trek.exe to a different location in memory, making most existing patches useless.
If using the OpenWatcom compiler, here's the commands needed to compile and link:
Code: Select all
wcc386 loader.c
wcl386 -l=WIN95 -fe=loader.exe -\"OPTION OFFSET=0x800000\" loader.o
So you can access variables and functions in Trek.exe through pointers. Just set them equal to the asm offsets in IDA Pro.
Patching Trek.exe in memory
If you tried to patch code in a running program, it would crash out. This is because the memory is protected from being overwritten. But we can change the properties in Trek.exe to make the code writable, so we can patch it while it's running. Basically, all we need to do is patch a single byte in Trek.exe. You could use special tools, like CFF Explorer, but I'll just tell you which byte to set in trek.exe:
Code: Select all
At offset 0x019f, change C0 to E0
Code: Select all
// Allows us to replace functions at the given address by far jumping
// to our target address. It requires 10 bytes to do.
void monkeyPatch (unsigned int offset, void * targetAddr)
{
unsigned char * trampoline = offset;
trampoline[0] = 0xff;
trampoline[1] = 0x25;
*(void**)(trampoline+2) = trampoline + 6; // address of the address to jump to
*(void**)(trampoline+6) = targetAddr; // address of the target code
}
Code: Select all
// This function takes in a 2D array of zeroes, and we write 1 where we want a star.
// Note that the grid is 5*5 times larger than the actual map.
void Universe_MkGal_GalaxyGrid (unsigned char **grid, int shape)
{
// Get MapLongEdge*5 and MapShortEdge*5
const size_t galW = *(unsigned *)(0x5CB304 + 0x40);
const size_t galH = *(unsigned *)(0x5CB304 + 0x38);
size_t X, Y;
// Top-left
for (Y = 0; Y < 10; Y++)
for (X = 0; X < 10; X++) {
grid[Y][X] = 1;
}
// Top-right
for (Y = 0; Y < 10; Y++)
for (X = galW - 10; X < galW; X++) {
grid[Y][X] = 1;
}
// Bottom-right
for (Y = galH - 10; Y < galH; Y++)
for (X = galW - 10; X < galW; X++) {
grid[Y][X] = 1;
}
// Bottom-left
for (Y = galH - 10; Y < galH; Y++)
for (X = 0; X < 10; X++) {
grid[Y][X] = 1;
}
// Center
for (Y = galH/2 - 5; Y < galH/2 + 5; Y++)
for (X = galW/2 - 5; X < galW/2 + 5; X++) {
grid[Y][X] = 1;
}
}
Code: Select all
monkeyPatch( 0x4AF9A0, Universe_MkGal_GalaxyGrid );
It places empires in the corners and center, and leaves no stars in-between the empires. Works on large galaxies too
This should allow for some interesting patches, with no need to modify Trek.exe.