Stunt GP reversing

Any discussion of software that doesn't fit into any category goes here.
Post Reply
Halamix2
Posts: 11
Joined: Sat Dec 10, 2022 6:39 pm
Location: Poland
Contact:

Stunt GP reversing

Post by Halamix2 »

A small series of adventures in Stunt GP reversing, I want to make this thread a series of short posts about reversing it basically from scratch, from the grounds up.
I hope this series may help somebody who wants to reverse engineer another game

What was done so far
  • Some chaotic reversing of some binaries in Ghidra, without any plan
  • some tools: (https://github.com/StuntKit)
    • Tools to unpack/repack game archives and textures
    • Basic Blender importer
    • some quality-of-life mods (widescreen support, remove random freezes, basic debug info etc.)
The game community started creating remakes in Unity/Unreal, but without the original physics engine and other stuff, as it was not reverse engineered.

Basic info about the game
  • Game uses custom engine, except for the PS2 version, where RenderWare is used for most of the computations (except physics I guess); AFAIK the game was released in the Europe (PAL), US (NTSC) and Japan (NTSC again). PS2 versions contain debug info
  • Windows was released in more countries, the most known versions to me are the international one with support for ~5 languages, and a Polish one (which has full UTF-16 support, but is lacking in other features, e.g language selector or credits menu were cut off)
    • Each language version contains two main executables, StuntGP_D3D and StuntGP_Glide, one uses DirectX 6, the other one Glide, the Voodoo API (similar to trimmed OpenGL)
A man with a plan
I want to mostly/completely reverse engineer a PC version of the game.
Steps I want to take for now:
  • Load a PS2 PAL version into Ghidra to copy function names over to a PC version
  • Load an international PC Glide version and use it as a main version for reversing for couple of reasons:
    • graphic functions are loaded from external .dll, so all names and argument types are well known and available from the start
    • DirectX has a C++-y interface, so I'd have to deal with object fields, vtables and such (I'll have to deal with this anyway cause DirectSound and DirectInput)
    • clear separation of graphical API from other APIS (DirectInput and other Direct* stuff)
  • try to poke around, write some stuff down, think about next steps
    • label well known functions (srand, sprintf...); copy function names from the PS2 version where possible (main loop probably will be very different)
    • Create struct and other datatypes definitions (in a separate .gdt file)
Stuff I'll probably have to do later anyway, bu is not necessary now:
  • set up MSVC 6.0 compiler to compile small programs/parts of program to see in Ghidra for comparison
  • Hunt down where I've found windows_vs6_32.gdt file; this is basically a must have for reversing anything that uses DirectX 1-7 APIs; which I luckily still have on my PC
Last edited by Halamix2 on Mon Sep 09, 2024 8:02 pm, edited 1 time in total.
Halamix2
Posts: 11
Joined: Sat Dec 10, 2022 6:39 pm
Location: Poland
Contact:

Re: Stunt GP reversing

Post by Halamix2 »

Small additional info about the PC version and MSVC: I've used Detect it Easy to determine which version of MSVC was used (MSVC 6.00.8168): https://github.com/horsicq/DIE-engine/releases
For the PS2 version and Ghidra I'll use the Ghidra Emotionengine reloaded extension: https://github.com/chaoticgd/ghidra-emo ... e-reloaded
Halamix2
Posts: 11
Joined: Sat Dec 10, 2022 6:39 pm
Location: Poland
Contact:

Re: Stunt GP reversing

Post by Halamix2 »

As expected, the topmost functions are way different, but the underlying ones are quite similar.
I've listed all strings, chose some interesting ones (VehicleConfig v0.5, and a path to a default config), and found functions in both binaries that uses each string, so I was able to copy over known name from the PS2 version to the PC version.
I've also noticed that while Ghidra couldn't demangle function names (https://en.wikipedia.org/wiki/Name_mangling), I can do it manually and for instance, replace "Game_HandleFadeInit_All__FUiUiUiUiUiUif" with "Game_HandleFadeInit_All", and set up the function params to 6 uints and one float. So not only I'm able to recover names of many functions, I can also recover some of the parameter types as well! While the mangling schema is not known to me, I can at least deduce param types, but I've yet to see if the return type is recoverable as well

PS2 version also have a ton of debug logs still intact, and names of the original files, making process even easier (apparently the game was written in C++, which is not obvious on the PC version, there are no vftables or any similar traits I'd expect of a C++ program)

Edit: after some thought it looks like GCC 2.9 mangling, I'll have to find demangler for that.
I went into trouble of compiling c++filt from binutils-2.11.94, and it's working beautifully:

Code: Select all

./cxxfilt -s gnu Game_HandleFadeInit_All__FUiUiUiUiUiUif
Game_HandleFadeInit_All(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, float)

./cxxfilt -s gnu set_matrix_identity__FP6Matrix
set_matrix_identity(Matrix *)
EDIT2: Enabling "Use Deprecated Demangler" option in the "Demangler GNU" Ghidra options helped, and Ghidra was able to successfully demangle function names and apply them to function parameters
Halamix2
Posts: 11
Joined: Sat Dec 10, 2022 6:39 pm
Location: Poland
Contact:

Re: Stunt GP reversing

Post by Halamix2 »

I've been doing several things simultaneously and I forgot to update you all:

Recompilation environment
One thing I've did was to set up an environment where I can compile MSVC 6 programs, preferably as close as possible to the game's dev environment.
As a base I've used modified CMakeLists.txt from the Lego Isle decomp (https://github.com/isledecomp/isle).
Some of the changes include:
  • I don't need tools like clang or recomp compatibility score for now
  • I'm using MSVC600 from https://github.com/itsmattkc/MSVC600
    • In one of my previous posts I've used Detect it Easy to find the mopst probable used compiler version, which is available in the repo, but I had to checkout the first commit instead of the latest one
  • Unlike in Isle, Stunt GP uses some modified compilation flags
    • To get them more or less correctly I've started from default flags for a Visual Studio 6 Win32 API .exe, compiled Hello World and compared entry() function (the one BEFORE WinMain) to the game's one.
    • Some of the changes included flags /ML (single-threaded; this was a trial and error, I knew std library was compiled in and entry() looked wrong with /MT flag), /EHs (disable exception handling for everything, keep it just for try/throw; this one was visible as additional "__xcptfilter" filter at the end of entry()), and /Gr since Ghidra picked up a lot of __fastcalls
    • I got list of available flags from https://learn.microsoft.com/en-us/previ ... 3(v=vs.60)
  • After setting up this environment, and compiling some programs with common C functions (e.g strstr, strlen etc.) I was able to find and double-check in the game .exe if I was correct in naming them
  • With this set up I was also able to add DirectX6 SDK and tried to compile some basic examples to confirm if it works
    • The game always lists DirectX7 as required version, but the length of certain DirectDraw structs is incorrect, and only corresponds to the 6.X version
Ghidra and basic types
While I have a magic windows_vs6_32.gdt file, it doesn't solve all my problems, as it lacks DirectInput structs, which are really needed here.
For now I was able to prepare a small .prf parse profile that parses DirectX6 source headers, it requires this mystical windows_vs6_32.gdt file to function, but it's ok for now.
In the future the best way (but also the most cumbersome) would be to generate a new .gdt file from MSVC6 and dx6 header files combined.

With the windows_vs6_32.gdt file and newly generated dx6.gdt file I was able to put proper types on DirectDrawCreate/DirectInputCreate/DirectSoundCreate functions, which replaced "DirectInputStruct+0x1234()" with "DirectInputStruct>lpVtbl->EnumDevices()" in the decompilation view
Post Reply