[ Beneath the Waves ]

TDR: Biohazard 2

article and software by Ben Lincoln

 

This is a basic walkthrough of decompiling the 1997-10-30 beta build of Biohazard 2 using Ghidra and This Dust Remembers What It Once Was.

  1. From the Biohazard 2 prototype ISO, copy PSX.EXE, and MAIN.SYM to your working directory.
  2. In your working directory, rename PSX.EXE to MAIN.EXE.
  3. Open a command prompt or PowerShell prompt and change directory to your working directory.
  4. Convert MAIN.EXE to ELF format by running the following command:

    PlayStationELFConverter.exe --exe2elf MAIN.EXE MAIN.ELF > PlayStationELFConverter_Log.txt 2>&1

  5. Generate the JSON version of the debug symbols by running the following command:

    SymDumpTE.exe --rename-for-compatibility --json MAIN.SYM MAIN.json > SymDumpTE_Log.txt 2>&1

  6. Generate the monolithic header and Ghidra Java scripts by running the following command (the --ignore-non-ascii-labels flag is because this game has a huge number of non-ASCII labels which the toolchain can't handle):

    CreateSkeleton.exe --create-playstation-memory --assume-sn-gp-base --name MAIN --externs-to-labels --ignore-non-ascii-labels --output Output MAIN.json > CreateSkeleton_Log.txt 2>&1

  7. Unlike the other walkthroughs, you don't need to correct any issues in the JSON or .H files in order to proceed. However, because Biohazard 2 has an extremely large number of functions, you are, unfortunately, going to have to manually split the MAINTDRDefineFunctions.java script into two pieces. This process is detailed below. Skip down to that section, and come back here once you're done with it and have created MAINTDRDefineFunctions01.java and MAINTDRDefineFunctions02.java. I'm going to try to remove the need to do this even for oddball games like this one in a future version of TDR.
  8. Launch Ghidra, and create a new project. For the base directory, use the Output directory created by TDR. For the project name, use MAIN.
  9. Import the ELF file you generated earlier. Ghidra will default to 64-bit MIPS, which is wrong. Click the ... button next to the Language field. Scroll up in the list and choose MIPS/default/32/little/default processor architecture, which will show up as MIPS:LE:32:default:default in the import file window. Click OK to begin the import.
  10. Close the import summary dialogue.
  11. Double-click on MAIN.ELF in the project list.
  12. An Analyze prompt will appear. Click No, because you don't want that to happen until the debug symbols have been imported.
  13. From the Edit menu, choose Tool Options.
  14. Expand Decompiler, and select Analysis. Uncheck Eliminate unreachable code. Click OK.
  15. From the File menu, choose Parse C Source option. Click the green plus sign button. Open the MAIN.H file in the Output/source-stubs directory. Click Parse to Program.
  16. Click Parse to Program. Click Continue. Click Continue?.
  17. After a moment, you should receive a message indicating that the header has been parsed successfully. If you don't, make sure you resolved any naming conflicts in the JSON file, re-run the CreateSkeleton.exe above, and then re-import the MAIN.H file. Otherwise, Click OK, then click Dismiss.
  18. Copy the MAINTDRDefineFunctions01.java, MAINTDRDefineFunctions02.java and MAINTDRDecompile.java scripts from the Output/ghidra_files/ directory into your own Ghidra scripts directory (probably something like C:\Users\yourname\ghidra_scripts).
  19. In Ghidra, from the Window menu, choose Script Manager option.
  20. In the Script Manager window, click on the the MAINTDRDefineFunctions01.java entry, then click the green-and-white play button in the upper-right corner of the window.
  21. After a noticeable delay, you should see a MAINTDRDefineFunctions01.java> Finished! message in the console at the bottom of the main Ghidra window.
  22. In the Script Manager window, click on the the MAINTDRDefineFunctions02.java entry, then click the green-and-white play button in the upper-right corner of the window.
  23. After a noticeable delay, you should see a MAINTDRDefineFunctions02.java> Finished! message in the console at the bottom of the main Ghidra window.
  24. From the Analysis menu, choose Auto Analyze 'MAIN.ELF'. Check the Decompiler Parameter ID box if it's not already checked. Switch to the MIPS Constant Reference Analyzer section. Uncheck Recover global GP register writes if it's checked. Optionally, check Attempt to recover switch tables. Click Analyze.
  25. Wait for the analysis to complete (progress is in the lower-right corner of the main Ghidra window.
  26. This should be enough for a basic demonstration of the toolchain. However, if you're really trying to fully reverse-engineer the game, at this point, you would do all of that work in Ghidra. That will take awhile, and is outside the scope of this walkthrough.
  27. When you're ready to proceed with generating source code, go back to the Script Manager window, and run the MAINTDRDecompile.java script.
  28. Wait for the decompilation to happen. This will usually take awhile. You should see a MAINTDRDecompile.java> Finished! message in the console at the bottom of the main Ghidra window when it's complete.
  29. Back in the command prompt, create another set of C source code files which contain the decompiled functions output by Ghidra by running the following command:

    PopulateSkeleton.exe --name MAIN --input-json MAIN.json --input-source Output\MAIN.C --output Output > PopulateSkeleton_Log.txt 2>&1

  30. Examine the contents of Output/source-decompiled, which should contain TDR's best attempt at reconstructing the original source code in all of the separate files that were originally used. Anything not matched to one of those files will be placed in Unmatched_Decompiled_Functions.C instead.

Splitting the MAINTDRDefineFunctions.java Script

As discussed above, due to the overwhelmingly huge number of functions in Biohazard 2, you're going to have to manually split the MAINTDRDefineFunctions.java script into two pieces, or Ghidra will error out rather than run it.

First, make two copies of MAINTDRDefineFunctions.java. Name one MAINTDRDefineFunctions01.java, and the other MAINTDRDefineFunctions02.java.

In MAINTDRDefineFunctions01.java, change the line which reads:

public class MAINTDRDefineFunctions extends GhidraScript

To read:

public class MAINTDRDefineFunctions01 extends GhidraScript

Delete everything between this line:

// Import functions without function pointer parameters (begin)

...and this line:

// Import functions with function pointer parameters (end)

Then delete everything between this line:

// Function import subroutines for functions without function pointer parameters (begin)

...and this line:

// Function import subroutines for functions with function pointer parameters (end)

Save that file and close it.

In MAINTDRDefineFunctions02.java, change the line which reads:

public class MAINTDRDefineFunctions extends GhidraScript

To read:

public class MAINTDRDefineFunctions02 extends GhidraScript

Delete everything between this line:

SetRegisterAtAddress(ctx, "sp", 0x8007134C, 0x801FFFF0L);

...and this line:

// get most data types

Then delete everything between this line:

// Define labels (begin)

...and this line:

// Define labels (end)

Then delete everything between this line:

// Label creation subroutines (begin)

...and this line:

// Label creation subroutines (end)

Save the file and close it.

Proceed with parsing the header file in Ghidra and running the generated scripts, as discussed above.

 
[ Page Icon ]