Skip to content

GameCode

Bartholomew Ho edited this page Nov 10, 2026 · 1 revision

Reading game memory safely

Most mods never need this page: the FH2 classes read the game for you. It's for when you go lower, with your own hooks and addresses.

Why reads need care in .NET

In C++, a mod can wrap a risky read in __try and carry on if the memory isn't there. .NET can't: an access violation can't be caught, and the runtime ends the game. Game data you didn't get from the game just now may be gone: an area that streamed out, a car that left, the HUD after a teleport. Read anything like that with the safe reads below.

Data the game hands you in a hook (its arguments, the object it's working on) is alive while the hook runs: read that directly, it's faster.

Reads that can't crash

New in 0.10.0. Each is a copy through the operating system, so a bad address gives you "not readable" instead of a crash.

Member What it does
GameCode.TryRead(nint address, byte[] into) Copies the bytes; false (nothing read) if any of them isn't readable. A Span<byte> overload too.
GameCode.TryRead<T>(nint address, out T value) A value of type T (C#).
GameCode.ReadSafe<T>(nint address) The value, or 0 / null if it isn't readable (C#).
GameCode.ReadPointerSafe(nint address, int offset = 0) A pointer, or 0.
GameCode.ReadInt32Safe(nint address, int offset = 0) An Integer, or 0.
GameCode.ReadSingleSafe(nint address, int offset = 0) A Single, or 0.
GameCode.IsReadable(nint address, int size = 8) True if that much memory is there right now.
GameCode.ReadStdString(nint address) The text of one of the game's std::strings, or "" if it isn't readable.
nint car = ...;   // remembered from an earlier frame: may be gone now
if (GameCode.TryRead<int>(car + 0x4D08, out var kind) && kind == 0)
    Log("still the player's car");
Dim car As IntPtr = ...   ' remembered from an earlier frame: may be gone now
If GameCode.ReadInt32Safe(car, &H4D08) = 0 Then Log("still the player's car")

These cost a system call each. Use them for pointers you kept, not for every read in a hot loop.

Checking code before you hook it

Before hooking or calling the game's code, check its first bytes, so that on another build of the game your mod switches itself off instead of crashing:

Member What it does
GameCode.Matches(nint address, byte[] expected) The bytes in memory are expected.
GameCode.MatchesBuild(nint address, byte[] expected) The same, but still true where another mod has hooked that code first: it then reads the game's exe on disk. New in 0.10.0.
GameCode.SlotMatchesBuild(nint slot, nint function) A function pointer (a vtable entry) is function, or another mod has hooked it and the exe on disk has function there. New in 0.10.0.
GameCode.ShippedBytes(nint address, Span<byte> into) The bytes as the exe on disk has them. New in 0.10.0.
GameCode.Hex("48 8B C4") Bytes from text, for the checks above.

Use MatchesBuild for code other mods hook too: with Matches, your mod would refuse to start whenever another one was loaded first.

nint b = ModPlatform.WaitForCode();
if (!GameCode.MatchesBuild(b + 0x384C00, GameCode.Hex("48 8B C4 55 56 57 41 54 41 55")))
{
    Log("not the game build this mod was made for; off");
    return;
}

See also

Clone this wiki locally