Cross-game car-archive editor for Xbox 360-era Forza titles (FM4, FH1). Import a car, swap parts between donors, retexture, deploy as an FH1 DLC package — without touching the base game.
Alpha. FM4 import is solid. FM4 → FH1 DLC deploy works in-game on the cars exercised so far (Alfa 8C, Audi R8). Carbin transcode coverage is partial (main mesh shipped; LOD0-only sections, damage translation still open). GUI is hand-rolled on raw SDL3 and rough around the edges. Expect sharp corners.
Mike Davis - pioneer of forza re and creator of Forza Studio. His methods where invaluble at game exports Warshack - UDLC creator, dlc structure and documentation SoulBrix - FM4 exporter tool - reference
- Import an FM4 or FH1 car archive (
.zip) into a working tree (glTF + decoded.pngtextures + per-car DB rows +carslot.json). - Browse mounted game libraries, render the active car on a pedestal, edit Data_Car stats, swap parts between donors via grab / place.
- Export back to the same game as a byte-equal or edit-respliced zip, or port to FH1 as a stand-alone DLC package that drops into Xenia's content tree.
- Build (see below) and launch
./build/carbin-garage. - Settings → Game folders: point at your FM4 and/or FH1 install
roots. Settings → Xenia content path: point at
<xenia>/content/(the directory containing<profile-id>/<title-id>/…). Saved to~/.config/carbin-garage/config.json. - Bottom dropup row lists each mounted game's roster + your
working/tree. Right-click a game-source car → Import to working/. Right-click aworking/car → Load from working/ to render it. - Right pane shows the active car's parts in tabs. Right-click a
part → Grab; right-click the slot to overwrite → Replace
with grabbed. Donor geometry overwrites host bytes; host slot tag
is preserved; an undo snapshot is taken;
edits[]audit logged. - Left pane edits Data_Car stats (mass, power curve, gearing, …).
Save writes overrides into
working/<slug>/carslot.json. - Bottom-middle export palette: pick a target game + mode
(same-game
export-to, FH1 DLCport-to-dlc) and click Export.
The GUI calls the same orchestrators. The CLI is the way in for anything not yet wired into the UI (notably the FH1 xex patch).
carbin-garage mount <game-folder>
carbin-garage import <car.zip> --profile fm4
carbin-garage port-to-dlc <working/slug> fh1 --donor <fh1-slug> \
--content <xenia-content-dir>
carbin-garage --help lists every verb. The roadmap and per-verb
context live in docs/ROADMAP.md and docs/APPLET_ARCHITECTURE.md.
Requires Nim ≥ 2.2. Devuan/Debian/Ubuntu deps in build-deps.sh.
./download-deps.sh # vendors SDL3 / SDL3_image / SDL3_ttf / libmspack / stb / cgltf / bcdec
./build-deps.sh # static libs into vendor/
./release.sh --local --skip-deps
A reproducible static binary build (Ubuntu 20.04 base, glibc compat)
lives in docker-build/Dockerfile — that's what GitHub Actions uses
for releases.
GPL-3.0-or-later. See LICENSE. This is a fan-made,
non-commercial reverse-engineering tool; not affiliated with or
endorsed by Microsoft, Turn 10, or Playground Games.
This section sketches the moving parts and points at docs/ for the
byte-level truth. The READMEs in docs/ are the source of authority;
this is just a map.
src/carbin_garage/core/— pure-Nim format codecs:carbin/(parser + emitter, FM4 TypeId 3 + FH1 TypeId 5),xds.nim(Xenon BC1/3/5 textures, decode + encode + retile),zip21.nim+zip21_writer.nim(PKZip + method 21 LZX, mixed-mode writer),lzx.nim+lzx_encode.nim(libmspack read, wimlib write — partial; see below),cardb.nim(per-car gamedb.slt snippet),xex2/+xex2_patches.nim(xex2 unpacker / repacker / integrity-bypass patcher).src/carbin_garage/orchestrator/— verb-level pipelines:importwc,exportto,portto,portto_dlc,patchxex,scan,mount.src/gui/— SDL3 shell. Hand-rolled on raw SDL3 +src/render/(ui_solid/ui_text/ui_circle/texturedshaders,OrbitCamera). No Dear ImGui or Nuklear.profiles/*.json— per-game offsets, paths, IDs. The codec is data-driven; add a new Forza by writing a profile.
The carbin format ships in two flavors — FM4 (TypeId 3) and FH1
(TypeId 5) — that share a §-structure but disagree on header
expansion, vertex stride (32 → 28 in FH1), m_NumBoneWeights
pre-pool block placement, a +4-byte cvFour → cvFive subsection
upconvert, and an extra lod0VCount × 4 post-pool stream on FH1
lod0 / cockpit. Cross-game transcode is donor splice: keep the
target's scaffolding, re-quantize the source's section bytes into
the donor's slots. See:
docs/FH1_CARBIN_MASTER.md+docs/FH1_CARBIN_CONDENSED.mddocs/FM4_CARBIN_MASTER.md+docs/FM4_CARBIN_CONDENSED.mddocs/FH1_CARBIN_TYPEID5.md(FH1-only deltas)docs/CARBIN_TRANSCODE.md(the donor-splice strategy + per-section validation gate)docs/FH1_VS_FM4_OVERVIEW.mdfor the high-level diffdocs/FH1_PHYSICSDEFINITION_BIN.md(donor-bin passthrough policy — cross-game ports never synthesize the physics blob)docs/FH1_PHYSICS_DB.mdfor the gamedb.slt schema
The FH1 runtime has very specific opinions about DLC content. Getting any of these wrong causes silent skip-on-load:
Data_Car.Idmust fall in the base range (FH1: 249..1568); IDs above that are silently ignored.merge.sltmust be written withpage_size=1024ANDschema_format_number=1(header byte 0x2C). Modern SQLite defaults are silently rejected.- The Xenia
.headersidecar (332 bytes) atHeaders/00000002/<packageId>.headeris required for enumeration. - DLC content must be loose files under
cars_pri_<id>/<MediaName>/…, NOT a.zip. The 360 zipmount layer doesn't recurse into overlays. - Package id =
<TitleID-hex><dlcId-as-8-decimal-digits>; overlay dir is<dlcId>_pri_99/, NOT<packageId>_pri_99/. - Every CDH entry needs a
0x1123extra field (FH1's zip21 reader requires it; handled incore/zip21_writer.nim). - We use
merge.slt, not edits to basegamedb.slt. Directgamedb.sltwrites were ruled out after ~6h of in-game iteration: the audio init's SQL chain returns 0 rows, SQL CE substitutes its error string into the asset path, and the renderer tears on open-world spawn. DLC merge.slt loads via a different code path and works.
See docs/PLAN_DLC_PIVOT.md for the full reasoning trail.
FH1's default.xex carries a lookup table that hashes 8 media files
(including gamedb.slt) and refuses dirty-disc-modified copies.
patch-xex is a pure-Nim xex2 unpacker / repacker that scrambles
those 8 filename strings in .rdata and splices a hardcoded 16 KiB
known-good optional header (rsa_signature zeroed, header_hash
recomputed) baked in via staticRead. Output is byte-equal to the
community-patched reference xex when given the same scramble values.
carbin-garage patch-xex <path-to-default.xex> # idempotent
carbin-garage patch-xex <path-to-default.xex> --restore
Not yet wired into the GUI; run it once from the CLI before any
port-to deploy. The DLC path (port-to-dlc) doesn't need it.
Implementation: src/carbin_garage/core/xex2/ + core/xex2_patches.nim
vendor/xex2_templates/fh1_header.bin.
libmspack reads method-21 LZX zip entries fine. Writing them is
partial: wimlib's lzx_compress is wired with four CAB-LZX
compatibility patches and works for single-chunk inputs ≤ 64 KiB.
Multi-chunk encoding desyncs at chunk boundaries because wimlib's
match-finder + recent_offsets aren't streamably persistent. Until
that lands, core/zip21_writer.nim is a mixed-method writer:
unchanged donor entries pass through as method-21 verbatim; edited
entries emit as method-0 (stored). In-game tolerance of method-0
entries was the question that gated this approach — empirically:
fine.
All native deps are vendored under vendor/ and built static so the
release ships as a single binary:
- SDL3, SDL3_image, SDL3_ttf — windowing, image decode, TTF rasterisation
- libmspack — LZX decompression (zip method 21)
- wimlib — LZX compression (patched for CAB-LZX framing;
see
patches/wimlib_lzx_cab_compat.patch) - cgltf — glTF parse-validate at emit time
- bcdec — BCn block decoding
- stb_dxt + stb_image + stb_image_write — BCn encoding
- PNG IO
- Space Mono — UI font
Vendored sources are pristine; behavior changes go through patches/
applied by download-deps.sh. csrc/ holds the C shims that bridge
into Nim.
docs/ROADMAP.md— current state + next-slice work, refreshed oftendocs/APPLET_ARCHITECTURE.md— the architecture destination, phase plandocs/PROBES.md— validation probes (codec roundtrip, port-shape, etc.)docs/FORZA_LZX_FORMAT.md— CAB-LZX vs. WIM-LZX framing notesprobe/— Nim + Python validation harnesses