SA-SDK

SA-SDK

A C++ modding SDK where every offset traces back to disassembly.

SA-SDK gives you two things: a library for reading the archive formats San Andreas ships with, and a header-only runtime layer for mods that run inside the game. Every struct in it is generated from SAEncyclopedia's verified tables, so none of the offsets is a guess. Everything comes in through one header.

#include <sasdk/sasdk.hpp>
using namespace sasdk;

void on_attach() {
    Module::bind();                               // rebase every address once
    for (auto* v : sa10us::vehicle_pool())        // typed live pool iterator
        v->m_fHealth = 1000.0f;                    // +offset verified by disassembly
}

The data library

Plain C++20 with no dependencies, and it builds and tests on any 64-bit host with no game present. Each parser was checked against the retail files:

ParserReadsChecked against retail
GxtArchiveTABL / TKEY / TDAT text, binary search by key hash127 tables, 16,588 keys in american.gxt
ImgArchiveVER2 archives: open, find and read by name32-byte table of contents, 2,048-byte sectors
ColArchiveCOL2 collision: spheres, boxes, triangles, linesVerified geometry element sizes
RwStreamRenderWare chunks for DFF, TXD and IFP12-byte chunk header, full type enum, nested children
FxpArchiveParticle projects, full hierarchy82 systems, 161 emitters, 4,120 curves, 6,563 keyframes
IdeArchive / IplArchiveObject definitions and world placementobjs, tobj, weap, inst, cull, enex and more
SaveArchiveSave blocks with per-block accessBLOCK tag framing, additive checksum
#include <sasdk/data/img.hpp>
using namespace sasdk::data;

Result<ImgArchive> img = ImgArchive::open("gta3.img");
if (img)
    if (auto entry = img->find("infernus.dff"))
        std::vector<std::byte> dff = img->read(*entry);

The runtime layer

The struct database is generated by a script from the encyclopedia's verified tables. It holds 302 schemas across a 6,545-line generated file, and there is a static_assert on every one, so a size mismatch is a compile error rather than a crash at runtime. Each schema carries its tier: confirmed by disassembly, verified against the executable, or reasoned. Members whose names were stripped from the game keep an honest field_0xNN rather than an invented one.

On top of the database sit typed globals, live pool iterators, and hooks:

// typed access to game memory, rebased automatically
Global<int> frames{ 0xB7CB4C };
ArrayView<CPed*> players = sa10us::players();

// live pools: peds, vehicles, objects
for (auto* p : sa10us::ped_pool())
    if (p->m_nType == PED_TYPE_COP) { /* ... */ }

// a 5-byte inline hook, lifted when the object is destroyed
InlineHook hook{ fn::CPad_Update, &detour };

Calls into the game go through call_cdecl and call_thiscall against rebased addresses, with nine typed function handles under fn:: for the ones that are verified. Everything that can fail returns Result<T>; nothing throws.

Some findings it encodes

The database carries a few results that correct long-standing community figures, each proven by the disassembly rather than assumed: CHandlingData is 0xE0 bytes, not 0xD4; CExplosion has 21 types; and m_nVehicleFlags resolves into 52 named bitflags.

Adding it to a project

The runtime layer is header-only: add include/ to your include path and build a 32-bit .asi, since the retail game is x86. For the file formats, link the small data library too; it is happy on a 64-bit host with no game around.

target_include_directories(my_mod PRIVATE ${SASDK_DIR}/include)

add_subdirectory(${SASDK_DIR} sasdk)
target_link_libraries(my_mod PRIVATE SASDK::game)   # or SASDK::data for files

The Plugin SDK, with its hand-written structures, is the long-standing choice for San Andreas plugins. SA-SDK takes the other route: every offset is generated from disassembly-verified data and guarded by static_assert, and it adds an offline format layer that you can test without the game.