-
Notifications
You must be signed in to change notification settings - Fork 16
How to recompile a game?
This page shows how to set up a port from scratch
- dotnet 10 SDK
- The game disc as a
.cue - Function names for the game. a decompilation project is the easiest source. Without one, the recompiler can still find functions, but you do more work by hand (see Function maps).
A port lives in its own folder, not in RecompOne's folder. RecompOne is just a submodule of your project. you have to reference the runtime from your project.
MyPort/
config/
mygame.json > config that drives the recompiler
funcmaps/ > generated function maps (address - name)
patches/ > your C# patches
disc/ > the game disc (*do NOT commit* this)
generated/ > recompiled C# (*do NOT commit* this)
Program.cs > entry point
MyPort.csproj > references RecompOne.Runtime (or any other lib you include)
Two folders must stay out of version control:
-
disc/holds copyrighted game data. -
generated/holds recompiled game code, which is derived from that data. this may be subject to copyright, you should regenerate it from the disc to create a build (or automate this via ci/cd) instead of committing it
You have to add both to .gitignore
The config is a JSON file that tells the recompiler what to read and where to write A minimal config looks like this:
{
"game": { "id": "SLUS-1234", "name": "MyGame", "output": "../generated" },
"cue": "../disc/MyGame.cue",
"funcMap": "funcmaps/main.json",
"overlays": [],
"patches": []
}Fields:
-
game.outputwhere the recompiler writes the C#. Point it atgenerated/. -
cuepath to the disc cue. -
mainthe boot executable inside the disc. -
funcMapthe function map for the main executable (see below). Useelf+/ormapinstead if you want to read a decomp ELF directly. -
overlaysextra code files the game loads at runtime. -
patcheshooks into game functions (see Patches).
other useful fields:
-
stubsfunctions to replace with a no-op) -
ignoredaddresses to skip -
linearSweepfind functions by scanning, when the map is incomplete, be aware that this can read data as code, use with care) -
debugmakes the recompiler generate prints for each function so you can know exactly what function path was taken at any point -
functions[]explicit addresses for functions to be recompiled
An overlay is a section of code that gets loaded in and out of the memory as needed, most PS1 games split code into several overlays that load over each others, to declare each one in the config:
"overlays": [
{
"name": "ovl",
"file": "OVL.BIN",
"base": "0x800A0000",
"funcMap": "funcmaps/ovl.json"
}
]-
namethe id the recompiler uses to generate the filename -
filethe overlay file path on the disc -
basethe address the overlay loads at -
funcMap/elf+/ormapused to help finding function boundaries and names
for overlays that are not plain files on the disc, use lba, offset, skip, size, and decrypt to describe where the bytes are and whether they are encrypted (more decryption modes need to be added in the future probably)
The recompiler needs to know where functions start and if possible what they are called. It reads this from a function map wich is a JSON file that maps addresses and names
here you have two options (see bellow)
Point elf and/or map(elfs are preferable) at the decomp output, the recompiler reads them at recompile time, this is the simplest path, but it teis your project to the decomp
{ "elf": "../mygame-decomp/mygame.elf", "map": "../mygame-decomp/mygame.map" }You do not have to depend on the decomp at recompile time, you can generate a function map JSON once, commit it, and point funcMap at it:
dotnet run --project RecompOne.Recompiler --generate-function-file -elf mygame.elf -map mygame.map -out funcmaps/main.json
Use -rebase <hex> if the overlay loads at a different address than the ELF assumes. generate one file per overlay.
After this your project builds from the committed maps alone
In case the game you want to target has no decomp availbale you can enable linearSweep so the recompiler finds function boundaries by scanning then you get generic names and no automatic SDK detection, so you must route SDK calls to the runtime by hand (see below)
LinearSweep can also be used in cases where a decomp does exist but an overlay havent been decomped yet
Games call the PSYQ SDK (libgpu, libpad, libspu) the runtime reimplements somme of these. When your function map has the real SDK names, the recompiler recognizes them and routes each call to the runtime automatically.
If you use linearSweep or an incomplete map, the SDK names are missing, you need to map each SDK adress to its runtime counterpart yourself, using patches with a replace that targets the runtime function
Prefer a named map if avaible. it removes this work
Run the recompiler on your config:
dotnet build RecompOne.Recompiler -c Release
dotnet run --project RecompOne.Recompiler -c Release --no-build -- config/mygame.json
This writes C# into generated/. Then build your project:
dotnet build MyPort.csproj
The entry point is small, the recompiler will generate one if this doesnt exists, this also works for hooking custom stuff and etc:
using RecompOne.Runtime.Memory;
using Recompiled;
var m = new PSMemory();
Entry.Run(m, args.Length > 0 ? args[0] : null);
return 0;A patch is a C# method that runs around a game function. Write the method in patches/, then wire it in the config, keep in mind the recompiled code is akin to the original assembly so modding is more close to rom modding than source modding.
The method takes the CPU context and memory:
using RecompOne.Runtime.Context;
using RecompOne.Runtime.Memory;
namespace Recompiled;
public static class MyPatch
{
public static void OnRoomLoad(CpuContext c, IMemory m)
{
// custom code goes here
}
}Wire it with a patches entry:
{
"overlay": "ovl",
"function": "LoadRoomLayer",
"mode": "post",
"target": "Recompiled.MyPatch.OnRoomLoad"
}-
overlaythe overlay the function is in. -
functionthe function name (or useaddresswith a hex value). -
targetthe fully qualified C# method. -
mode:-
preruns before the function. usebooland returnfalseto skip the original, if it's void it acts like a true returning function. -
postruns after the function. -
replacereplaces the function completly
-
Patches are part of your project, so they compile into the game binary. be aware that changing a patch rebuilds only the binary while changing the config needs a rerun on the recompiler to apply it.
- External Modding [todo]