1 Approach
Kenshi
- Binary:
kenshi_x64.exe1.0.68 andOgreMain_x64.dll. Full Ghidra 12.2 analysis (about an hour, JDK 25). - Names from KenshiLib's reconstructed headers. Its addresses map to this build with a constant shift per region (+0x40, +0x780).
- String anchors:
"target_opposite"→ the XML loader,"Created Morph "→ the mesh cache. - Formats: GameData (
.base,.mod,.bod2), OGRE.meshv1.8/1.100 and.skeleton, including two exporter bugs in chunk lengths. - Shader:
character.hlslships as source.
Saints Row: The Third
- The Linux port's exe is in an encrypted container, so the install was switched to the Windows depot under Proton. The packfiles are identical.
- Binary:
SaintsRowTheThird_DX11.exe(32-bit, no symbols). Ghidra analysis takes about 5 minutes. - Anchors: magic numbers (
0x1337BEEF,0x0BADBEEF), the constant 32767.0, Lua binding names (pcr_*). - Shaders: DXBC pulled from
.fxo_pc_dx11, then DXBC → SPIR-V → GLSL with vkd3d-compiler and spirv-cross. - Creator UI: plain Lua in
store_character.str2_pc.
The Sims
- A downloaded DVD image said to carry malware: everything was handled statically. Python
zipfile,unshieldandunrar-freeunpacked it, ClamAV and a PE sweep checked it, and nothing was ever run. - Binary: the unprotected no-CD
Sims.exe1.91 (32-bit; the DVD's own exe is SafeDisc-encrypted). It was verified to be the game minus SafeDisc. Ghidra analysis takes about 5 minutes. - Anchors: three-letter strings (
fat,fit,skn,lgt,med,drk) found by absolute-address search, and MSVC RTTI class names (cWinDesignCharacter) resolved to vtables. - Behaviour: SimAntics code in
Baby.iffdecoded with a small BHAV reader, connecting births to the Make New Character primitive. - Every binary format has a text twin (CMX/BCF, SKN/BMF), which made decoding and checking easy.
2 Verification
| Test | What it compares | Result |
|---|---|---|
| Kenshi NPC faces | Generic NPC pose weights, every race and gender, Python vs JS | 555 faces, 0 mismatches |
| Kenshi Body geometry | Bone sizes, positional sizes, posture and skinned vertices for male, female, Shek and Hive meshes | 0 bone-size mismatches, vertices within 3·10⁻⁶ |
| SR3 Creator and NPC maths | 50 RNG draws, 4 preset/randomise cases (110 values, all key weights, 23,931 vertex offsets, normal-map weights), 6 NPC spawns | 0 mismatches, bit for bit |
| SR3 mesh parser | All 1151 item meshes. Submesh VIDs against the table's VID_List | All parse. 1211 of 1254 VID lists match, and the rest are table slips |
17-gamedata.quack_5_Male0…4, or Saints Row's
male_asian + obese compared face to face.Bugs found by looking
Several mistakes were only caught by rendering and looking. They're worth recording because each one was a wrong reading of a format:
Mirrored clothing text
The game is left-handed and three.js right-handed. Mirroring X fixes it, along with the triangle winding (three.js flips that itself) and the bitangent sign.

A stray vertex on the headset
52 item meshes have two vertex buffers, so each morph target has two blocks with buffer-relative indices. Reading one block per target misplaced every later target.

- Floating NPC heads: geometry groups were missing.
- Black NPC faces: heads were exported without tangents, so the shader normalised zero vectors.
- Transparent hair: two shipped textures carry brightness in the opacity channel.
- A wrong sign in Ghidra's output: the decompiler dropped an
xorpssign flip in the key-weight function, found in the disassembly.
3 Formats at a glance
Kenshi
| Format | Notes |
|---|---|
| GameData | int32 + length-prefixed strings. Records hold bools, floats, ints, vec3/4, strings, filenames and references. Later files override fields by stringID |
| OGRE mesh | Poses = (name, submesh, [(vertex, offset)]), no normals. Head island at v ∈ [−1, 0] |
| OGRE skeleton | Bone chunk lengths exclude the header. Animation tracks: chunks 0x4100 / 0x4110 |
| Editor XML | <Config gender><Category><Range|ColourRange> |
Saints Row: The Third
| Format | Notes |
|---|---|
.vpp_pc v6 | Packfiles. Per-file zlib streams without a final block, or one condensed stream |
.xtbl | XML tables with an embedded schema |
.ccmesh_pc/.gcmesh_pc | CPU header + GPU data. Static-mesh header 0x424BD00D, render blocks, a submesh VID table at the end, and multiple vertex buffers |
.cmorph_pc | 0x1337BEEF / 0x0BADBEEF. A block per target per vertex buffer, with int16 deltas and per-axis scales |
| peg v13 | Textures: DXT1/3/5, A8R8G8B8. Normal maps are DXT5nm |
.le_strings | A bucketed hash table: Volition-CRC key, then UTF-16 text |
The Sims
| Format | Notes |
|---|---|
.far | FAR!byAZ v1: uncompressed files plus a manifest (size, size, offset, name) |
.cmx / .cmx.bcf | Skeletons, suits (bone, mesh) and animation headers; length-byte strings. Bone rotations are stored inverted |
.skn / .bmf | Faces, per-bone vertex ranges, UVs, blend weights (/32768) and bone-space vertices; the binary form swaps each blend pair |
.iff 2.5 | Big-endian chunk headers, little-endian data. STR# 200 = body strings; BHAV = SimAntics code |
4 The demos
Each game also got a browser demo: a character creator rebuilt in three.js on top of the JavaScript re-implementation. The demos include every option each game has, because showing the full extent of the variety is the point. They're built from the games' own meshes and textures, so they aren't published.
Kenshi creator ↗
About 230 assets (≈55 MB): 30 races, every head, hair, beard, slider and posture clip, the physique factors, Randomise, and the generic NPC faces.
Saints Row creator ↗
About 3000 assets (≈350 MB): presets, the triangle, 110 sliders, 55 skins, 51 hair styles × 35 colours, every layer and tattoo, 572 items with dyes and flags, and 223 NPC types.
Sims creator ↗
About 3500 assets (≈111 MB): every head, body and outfit for each gender, age and skin tone; townies and children with the game's RNGs; all 314 shipped characters and 35 pets.
5 Function maps
Kenshi: kenshi_x64.exe 1.0.68 and OgreMain_x64.dll
| Address | Function |
|---|---|
| 0x140077090 | AppearanceManager::loadEditorXMLData |
| 0x140071440 / 0x140071520 / 0x1400717d0 | DataRange::clamp / DataRangePose::clamp / DataRangeVector::clamp |
| 0x14006e340 | DataRangeVector::getValue (skin ramp) |
| 0x14006fcf0 / 0x140070010 / 0x140070460 | randomiseRanges (plain / pose / vector) |
| 0x14006e050 / 0x14006e080 | randomFeatureSingle |
| 0x14006e1a0 | getRandomValue (weighted pick) |
| 0x14007b310 / 0x14007b7c0 / 0x14007b940 / 0x14007adc0 | randomFace / randomPoses / randomBody / randomHair |
| 0x14007f040 | randomiseAll |
| 0x14007c070 | getMorphedBodyMesh (NPC face cache and seeding) |
| 0x140071ac0 | createMorphedMesh (bake) |
| 0x140071dc0 | getBodyMeshForEditor |
| 0x140581770 | RootObjectFactory::process (spawn, morph index) |
| 0x140539020 | AppearanceBase::buildBody (faces weirdness, alwaysUnique) |
| 0x14052e6c0 | AppearanceHuman::updateProportions (all bone sizes and positional sizes) |
| 0x14052cf80 | AppearanceHuman::setPosture (postures, shoulder set, neck set clip times) |
| 0x1409b0f40 | lerp(t, a, b) (physique factors) |
| 0x140533b50 | AppearanceHuman::updateCharaterTexture |
| 0x140531430 | AppearanceHuman::setupCharacterEntityTexture (skintone uniform) |
| 0x140531040 / 0x140532600 | AppearanceBase::setHairTexture / setupHairMaterial |
| 0x140532490 / 0x14052d480 | AppearanceBase::attachItem_Hair / attachEntity (skeleton sharing) |
| 0x140074680 | AppearanceManager::setupFaces (head and hair lists) |
| 0x1405eb5c0 | CharacterEditWindow::updateAppearanceSliderPose |
| 0x1409b1c10 / 0x1409b32b0 | rand01 / roundHalfAway |
| OgreMain 0x1801e20e0 / 0x1801e1dc0 | OldBone::updateFromParentImpl / _getOffsetTransform |
Saints Row: The Third: SaintsRowTheThird_DX11.exe (names are mine)
| Address | Function |
|---|---|
| 0x00da2920 / 0x00da2a90 | Volition CRC of a string (lower-cased) |
| 0x009f5c60 → 0x009f5af0 → 0x009f5960 → 0x009f5850 | parse player_creation.xtbl: Morph_Set → Morph_Info → Morph_Key |
| 0x0074d2a0 / 0x00e532a0 | .cmorph_pc header check / pointer fix-up (0x1337BEEF, 0x0BADBEEF) |
| 0x0074d7a0 / 0x0074d320 | load a .cmorph_pc (+ .gmorph_pc) by name |
| 0x009f65c0 | weights of one slider's keys + delta accumulation |
| 0x009f7cc0 | per-frame player morph update (subtract old, add new) |
| 0x009f6a80 / 0x009f5200 | bake base + deltas into the vertex buffer / float → int16 |
| 0x009f4da0 | set a slider value |
| 0x009f4e00 | reset all sliders to Initial_Value |
| 0x00835d90 | apply a player preset (mesh, reset, grid, figure, colours) |
| 0x008367d0 | register the pcr_* Lua functions |
| 0x00836640 / 0x00833fe0 / 0x00831700 / 0x00834bc0 | Lua pcr_apply_preset / pcr_apply_morph_value / pcr_report_avail_morphs / pcr_randomize_eye_colors |
| 0x00833860 | apply a morph value (special case for body gender) |
| 0x009f90e0 / 0x009fafd0 | normal-map strengths / parse player_creation_normal_maps.xtbl |
| 0x009fa0c0 | normal-map blend pass (fat → fat^¼, base = 1 − …) |
| 0x009fa8e0 | diffuse composite pass (skin HSB, then layers by Layer) |
| 0x009fb1d0 | customisation init: render targets, cust_normal_map_blend parameters, default layers |
| 0x00832690 / 0x00833780 / 0x009f9760 | skin colours: parse / apply / specular parameters |
| 0x00834cd0 / 0x00833460 | hair colours: parse / bind Hair_* shader constants |
| 0x00daf560 / 0x00daf5b0 / 0x00daf6c0 | MT19937 seed / next / fill the 0x2000-entry table |
| 0x00daf7d0 / 0x00daf850 / 0x00daf890 | rand01 / rand_int / rand_float |
| 0x00e7dc00 / 0x00e7d730 | register Lua maths functions / Lua rand_float |
| 0x00be2d70 | parse character_definitions.xtbl (heads, hair balls, props, height) |
| 0x00be6bf0 / 0x00be76a0 | parse npc_color_palette.xtbl / pick primary-secondary-tertiary |
| 0x00950660 | NPC appearance on creation: palette, props, head, hair shader ball, material colours |
| 0x009fc810 / 0x009fc980 | parse a wear option's Obscured_VIDs / VID_List |
| 0x00745560 | load the four colour pools (character, hair, makeup, tattoo) and derived colours |
| 0x007454b0 | get colour pool n (count, entries) |
| 0x00db1290 | read an xtbl Color (R, G, B / 255) |
| 0x008344c0 | Lua pcr_report_composite_colors (pool 2 = makeup) |
The Sims: Sims.exe 1.91 (no-CD build)
| Address | Function |
|---|---|
| 0x408c57 | cached CAS candidate list for (skin, gender, age, kind) |
| 0x40a3c6 / 0x4096c5 | build the head / body lists from name patterns |
| 0x409953 … 0x40a1af | formal, swimwear, pyjamas, winter, high fashion lists |
| 0x47274f / 0x4727bd / 0x472855 | pattern engine: literal / alternative / any n |
| 0x4728e9 / 0x472b1d | prefix match length / best match (ratio of matched length) |
| 0x40a5ea / 0x4eadc1 | validate outfits against build / assign defaults |
| 0x40afbd | nude, underwear and hand body strings |
| 0x545b04 / 0x546fd4 | cWinDesignCharacter: create / command handler |
| 0x48432f / 0x4843bd | zodiac from personality / set a sign's preset |
| 0x5bc6cb / 0x5bc722 / 0x46f556 | engine RNG seed / draw; simulation RNG draw |
| 0x42a9d1 / 0x426392 | townie pool top-up / generic townie |
| 0x44ae48 | Make New Character (SimAntics primitive 19) |
6 Behind these pages
This site summarises longer write-ups, one per game, plus the full malware check of the Sims download. They have the full formulas, every table and the decompiled functions themselves, with the shaders for Saints Row and the decoded SimAntics for The Sims. They stay private along with the demos, because they quote the games' code and data at length.