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:
| Parser | Reads | Checked against retail |
|---|---|---|
GxtArchive | TABL / TKEY / TDAT text, binary search by key hash | 127 tables, 16,588 keys in american.gxt |
ImgArchive | VER2 archives: open, find and read by name | 32-byte table of contents, 2,048-byte sectors |
ColArchive | COL2 collision: spheres, boxes, triangles, lines | Verified geometry element sizes |
RwStream | RenderWare chunks for DFF, TXD and IFP | 12-byte chunk header, full type enum, nested children |
FxpArchive | Particle projects, full hierarchy | 82 systems, 161 emitters, 4,120 curves, 6,563 keyframes |
IdeArchive / IplArchive | Object definitions and world placement | objs, tobj, weap, inst, cull, enex and more |
SaveArchive | Save blocks with per-block access | BLOCK 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.
