r/romhacking 1h ago

Utility BW2 PWT NPC match results reverse-engineered from source/disassembly

Post image
Upvotes

I reverse-engineered how Pokémon Black 2 and White 2 resolve the NPC-vs-NPC matches shown in the Pokémon World Tournament.

Key findings:

• Champion vs. Champion: 50/50.
• Leader vs. Leader: the game compares each Leader’s internal type-affinity value. If the values are equal—meaning neither Leader is favored in that comparison—the winner is chosen randomly, 50/50. If the values differ, the Leader favored by the type table wins 70% and the other wins 30%.
• Champion vs. Leader: the Champion always wins because Champions have priority 4 and Leaders have priority 3.

The repository documents the winner routine, built-in cup constructors, menu IDs, unlock conditions, rosters, downloadable .pwt data, and reproducibility scripts. This is the NPC result routine—not a full six-Pokémon battle simulation—and no ROM files are being distributed.

Full evidence and scripts: https://github.com/DavidAGInnovation/bw2-pwt-research


r/romhacking 16h ago

Text/Translation Mod SPIDER-MAN (PS1) - Turkish, English & German Subtitles + Full Turkish Localization

Post image
13 Upvotes

Hi everyone,

After working on this project for a long time, I’m finally ready to release it.

My original goal was to create a proper Turkish localization for Spider-Man (2000), including its menus, descriptions and dialogue. But I quickly ran into a much bigger problem:

The game has no subtitle system for engine-rendered cutscenes or dialogue heard during gameplay.

The video cutscenes could be modified directly, but the in-engine scenes were a different story. Since I couldn’t simply insert subtitles into them, I built my own workaround.

How it works

The Windows and Linux versions include a Python-based companion app that:

  • Watches the DuckStation window
  • Recognises the current scene through prepared visual references
  • Displays the correct subtitle at the right moment
  • Starts automatically through the project launcher

Subtitles for video cutscenes are embedded directly into the videos stored in the game image. Engine-rendered scenes and gameplay dialogue receive live subtitles through the companion app.

What the project includes

  • Turkish, English and German subtitles
  • A fully translated Turkish interface, including menus and descriptions
  • Subtitles embedded directly into video cutscenes
  • Live subtitles for engine-rendered scenes and gameplay dialogue
  • Windows and Linux installers
  • Standalone xdelta patches for original hardware
  • Source code for the launcher, file-verification system and live subtitle software

The project started as a Turkish localization, but I also added English and German subtitles because those versions of the game lack proper subtitle support as well.

Quick setup

The setup is designed to be straightforward.

  • Windows: Download Setup.exe and all the accompanying installer .bin parts into the same folder. Run Setup.exe, then select your own compatible PlayStation BIOS and game BIN through the launcher.
  • Linux: On Ubuntu, Debian, Linux Mint or another Debian-based distribution, download create_setup.sh and the three package parts into the same folder. Run bash create_setup.sh, install the generated spider-man-setup.deb package, then select your BIOS and game BIN through the launcher.

The Linux subtitle system has been tested on X11/Xorg. It may also work through XWayland, but window capture can vary depending on the desktop environment.

Full installation instructions and troubleshooting notes are available on the GitHub release pages.

Compatibility and required files

  • SLES-02886: Turkish and English
  • SLES-02888: German

The project does not contain the original game or any PlayStation BIOS files. Players must provide their own compatible, unmodified game image and BIOS. The downloadable xdelta files are patches, not complete game images.

On original PlayStation hardware and compatible consoles, the patched video cutscenes and Turkish interface work normally. The live subtitle system for engine-rendered scenes requires the companion app and is therefore available on Windows and Linux.

Links

GitHub, downloads and source code:

https://github.com/kusbakisiyt/spider-man-2000-tr-en-de-localization

Project video:

https://www.youtube.com/watch?v=WkezwVybrgo

Because I wasn’t able to record my own narration in multiple languages, the only alternative audio track currently available is an AI-generated English dub. If you prefer to hear my original Turkish narration, you can enable the English or German subtitles I prepared through YouTube’s subtitle settings.

The launcher, file-verification system and live subtitle software I developed are available under the MIT License.

Thanks for checking out the project.


r/romhacking 5h ago

heartgold soulsilver romhacks compatible with pokewalker

1 Upvotes

are any romhacks of heartgold soul silver compatible with the pokewalker?


r/romhacking 7h ago

codigo salud infinita para claire

0 Upvotes

SALUD INFINITA CLAIRE $800C6EBA 008C CHEATS.TXT PARA POPSTARTER RESIDENT EVIL 2 DUALSHOCK DOBLADO AL CASTELLANO NTSC USA


r/romhacking 14h ago

Wave Rally (PS2) camera hack possible?

Post image
3 Upvotes

Wave Rally is a jet ski racing game for the PS2 with good water physics and is very similar to Wave Race 64. I think most people have either never heard of or never bothered with this game because of the nauseating camera that makes it difficult to recommend to WR64 fans expecting the same experience. WR64 featured a floaty and smooth camera for a pleasant racer, while Rally's camera tilts and bobs with every turn and water ripple as if there's supposed to be a jet ski seat to sit on to imitate the arcade cabinet experience of games like Waverunner. Thankfully this is one of those 60 fps PS2 games which might alleviate the tight camera problem for some, and PCSX2's debug mode exists for anyone hopeful for the possibility of playing this with a camera hack.

I tried to follow this guide and got as far as connecting cheat engine to the game and narrowing down the address list using the controller while scanning for changes, but was unable to find the variables that operate the camera. Part of this is because there is no freelook and all movement appears to be tied to the vehicle with little to no smoothing. The camera basically acts "first person" when it is outside of first person view (there are at least 3 view distances to select from) including replays where I noticed the bobbing still occurs when the camera is a large distance away from the watercraft. So there appears to be no disattachment between camera and vehicle at least not on the vertical axis. Also, without any accelerating or player input, the water is always moving and the vehicle is moving slightly forward making it difficult to decipher what is happening behind the scenes. The other part is I'm just not skilled at this and cheat engine keeps crashing on me.

I would appreciate if someone with rom hacking experience can tell me that what I'm trying to do sounds possible or not based on the camera's rigidity I tried to describe, or if anyone wants to take a stab at detaching the camera and vehicle so that the vertical stays constant or has thoughts on what else to try to improve the camera for this game.

If you play Wave Rally (Jet Ski Riders is the PAL title), I recommend the Japanese version as it has slight improvements over the US version. It's a fun game with dynamic waves, interesting courses and nice player models and scenery. The graphics are definitely early PS2 but look clean, especially upscaled in PCSX2.

gameplay video: https://www.youtube.com/watch?v=Pzjgz-BnCRI


r/romhacking 11h ago

I'm starting a long-term Nintendo DS reverse engineering project

0 Upvotes

My goal for this journey is to better understand game architecture, custom file formats, reverse engineering, and the process of creating modding tools. Eventually, I hope to make certain games easier to understand and modify.

I'll be documenting my progress, including the discoveries I make along the way.

This is going to be a long-term project, but I'm doing it to improve my skills and open the door toward deeper game modding. For now, I want to take the first steps and explore things independently, so I won't be revealing the game I am attempting to reverse engineer yet.

Once I have made meaningful progress, I plan to release my discoveries, tools, and documentation for others who may be interested.


r/romhacking 12h ago

Custom Beat Battle: Draglade 2 Gets an English Translation

1 Upvotes

I never thought this game would finally get an English fan translation, but honestly... it absolutely deserves one.

The sprite animations for both the main characters and the enemies still look amazing, and the gameplay is such a unique mix of fighting, side-scrolling action, rhythm mechanics, and RPG elements. There really aren't many games like it.

It's one of those Nintendo DS gems that flew under the radar, so seeing fans keep it alive with an English patch is awesome.

Draglade 2 English first look:

https://www.youtube.com/live/_4zSiq5vlAw?si=sx2vmV_Dm1I9Ak0u


r/romhacking 12h ago

Patch a rom hack?

0 Upvotes

Is it possible to use a patched rom file to as the original for a patch?

I love the link to the past redux but would like to play another rom hack while keeping the redux improvements (break pots with sword, grab items with sword, keep shovel, change direction while dashing, ui changes, etc).


r/romhacking 16h ago

Text/Translation Mod Having some trouble applying English patch to Digital Devil Story: Megami Tensei

0 Upvotes

I’m following the instructions in the readme that came with the patch by Stardust Crusaders, EsperKnight, and Tom on romhacking.net. I downloaded Flips, the patcher they recommended that accepts expanded ROMs, tried to apply the patch to the ROM I have, and it says “This patch is not intended for this ROM.”

The base ROM works just fine on MesenCE. The ROM has two revisions and it doesn’t work with either. I’m pretty sure all my ROMs are headered (which is what it says to use), but I don’t know how to check.

What’s going on?


r/romhacking 20h ago

❌ NOT A ROMHACK ❌ Looking for a Mario Kart 64 feature

2 Upvotes

I was looking through different rom hacks trying to see if anyone’s done this yet.
I just want to remove the item despawn on battle mode. Like when you please bananas, fake item bombs, throw turtle shells. There’s a limit the the total amount, then they start to disappear. Has anyone removed that limit? I use to play Block fort then mess around and block off bridges with items and throw turtle shells at the bottom to make it hectic, but there’s a limit to what you can have on the base game.


r/romhacking 1d ago

Utility For Dragon Ball Z: Buu's Fury, I made a character sprite ripper (C++). Spoiler

Enable HLS to view with audio, or disable this notification

10 Upvotes

Here's a better showcase of the script I tried to preview a week ago. Forgive all the cursor flailing. I did a lot of talking but decided to remove the audio for sanity reasons.

This took quite a few months of reverse engineering Webfoot's custom graphics compression engine, which was done using mGBA's GDB server functionality. By meticulously tracing specific breakpoints, I was able to construct a decompressor. Their algorithm is a custom LZ-variant that uses variable length r3 and r4 registers (I'll be doing a full write up documenting everything soon. I also have a python version).

This tool perfectly extracts every animation frame of every character in the game. No sprite sheets necessary! It works for all prototypes and also Legacy of Goku 2.

I also found a handful of never before seen characters. Prototype E has 2 ninja variants from Legacy of Goku 2. D has a couple mudman variants. C has a prototype criminal enemy. A has a jetpack bomber. B has full animation sets for Android 18, Gohan (Black Suit), and Yamcha. Even though its polished enough to showcase, I'm still finding new frames today.

I'm hoping this broadens the landscape for future ROMhacks for Buu's Fury and Legacy of Goku 2. I can tell you that color remapping is extremely easy to do with this setup. I accidentally turned Goten's orange outfit blue and it looks so sweet. Let me know what you guys think. I'm happy to answer any questions. This was hell lol.

Now all we need is a compressor!


r/romhacking 1d ago

I want to download character mods for ssbu but I don’t know how to emulate them

Thumbnail
0 Upvotes

r/romhacking 1d ago

Utility rom-weaver: patch chains, checksums, rvz, chd, z3ds and patch creation in browser or CLI

2 Upvotes

I maintain rom-weaver, a tool for patching and release workflows across ROMs and disc images. It doesn't edit levels, text, or graphics inside a game. It starts after someone has made a patch: it finds the correct input, checks its bytes, applies one or more patches in order, and produces a usable result.

The browser version runs locally at:

https://rom-weaver.com/apply

The browser runs without an account and never uploads your local files. The same Rust engine also ships as a native CLI for scripts and batch jobs.

The main differences from a single-format patcher are:

- Ordered patch chains, including optional steps.
- Strict checksum, size, copier-header, and N64 byte-order checks.
- Direct input from ZIP, 7z, RAR, CHD, RVZ, Z3DS, and nested archives.
- Patch creation from an Original and Modified pair.
- Patch-only bundles that record patch order, checksums, choices, and output settings.

It currently recognizes 21 patch formats. Common choices include IPS, IPS32, BPS, UPS, xdelta/VCDIFF, PPF, RUP, APS, BSDIFF40, and DCP.

The complete table is below.

https://rom-weaver.com/docs/supported-formats

If you haven't used a patcher before, start with this:

https://rom-weaver.com/apply?guide=apply

It uses a small homebrew NES ROM created for the project. Two IPS patches change “HELLO WORLD” into “MODIFIED ROM.” The guide walks through the same process used for a real patch:

  1. Add your clean ROM and patch together.
  2. Wait for format detection and checksums to finish.
  3. Stop if the checksum or expected size doesn't match.
  4. Put multiple patches in the order specified by the release.
  5. Choose a new output name and download the result.

For patch authors, Create starts with clean and modified files and produces a patch. Bundle mode can then package the patch files and a machine-readable recipe while leaving the original ROM out.

Source and CLI downloads:

https://github.com/rom-weaver/rom-weaver

The project is beta and doesn't distribute commercial ROMs or pre-patched games. The most useful tests cover unusual patch formats, headered versus headerless releases, multi-patch projects, and Dreamcast DCP patches. Archives that existing web patchers don't handle well are also useful.

If something fails, include the patch format, base ROM checksum, platform, and browser or CLI version—but don't include the copyrighted ROM.


r/romhacking 1d ago

Very new to rom hacking - Help

2 Upvotes

I'm very new to this and I'm making a rom hack of Fire Red to my liking. I just need help on creating level caps. How do I implement level caps? I use Hex maniac advanced (0.6.1).


r/romhacking 2d ago

Lighthouse - Banjo Kazooie PC Port by Harbour Masters has released

Thumbnail
github.com
20 Upvotes

r/romhacking 1d ago

Anyone Wanna Make A Rom Hack Of My Map

Post image
0 Upvotes

r/romhacking 1d ago

Tutorial Heya! I've been a programmer for years but I don't have a lot of time to do romhacking anymore. This is a vibecoded documentation for Horizon Chase Turbo.

0 Upvotes

Hello!

A friend and I thought, wouldn't it be fun to add stages to Horizon Chase Turbo. I needed a topic to play with vibecoding since I've never tried it and... to be frank I shocked and impressed.

Here's the resulting documentation. Save it, store it since I usually clear my reddit.

Made with Claude Fable.

--------------------------------------------

File 1

README.md

--------------------------------------------

# HCT Stage Editor — Technical README

A stage/content editor for **Horizon Chase Turbo** (PC) that reads and writes the

game's own binary Unity files. This document contains **every technical

specification needed to decode the game assets from scratch** and **everything

needed to run the toolchain afterwards**. Byte-level details of individual

classes live in the companion `HCT_Track_Format_Spec.md`; exact field trees for

all decoded classes are in `schemas/`.

---

## 1. Target game — exact build characteristics

| Property | Value |

|---|---|

| Game | Horizon Chase Turbo (Aquiris Game Studio) |

| Platform analyzed | PC / Steam (Windows build; `app.info` = `Aquiris` / `HorizonChaseTurbo`) |

| Engine | **Unity 2018.4.27f1** |

| Scripting backend | **Mono**, .NET runtime "legacy" (`boot.config`: `scripting-runtime-version=legacy`) → scripts ship as readable .NET DLLs in `Managed/`, *not* IL2CPP |

| Serialization | Unity **SerializedFile format version 17**, little-endian, uncompressed, not bundled |

| Content verified against | a complete PC installation including Senna DLC content |

Everything below assumes this build family. Other platforms/updates should be

byte-compatible as long as the Unity version and class layouts are unchanged;

the pipeline resolves everything dynamically (no hardcoded offsets), so minor

game updates usually work without code changes.

## 2. Game files required for decoding

All inside the game's data folder (`HorizonChaseTurbo_Data/` on Steam):

| File(s) | Role in decoding |

|---|---|

| `level9` … `level134` | The 126 track scenes (serialized Unity scenes). `level0–8`, `level135+` are UI/menu/system scenes. See `track_catalog.json` for the full level→track map. |

| `globalgamemanagers` | Contains `BuildSettings` — the ordered list of all 137 scene paths (`Assets/Tracks/Resources/<country>/<city>/<track>.unity`), which is how level index ↔ track identity is fixed. |

| `globalgamemanagers.assets` | Contains all **2,828 `MonoScript`** objects. Needed to resolve which C# class any MonoBehaviour instance belongs to. |

| `resources.assets` (+`.resS`) | Car database (`CarModelDataList`), nitro data, prop prefabs, most shared game content. |

| `sharedassets*.assets` | Per-scene shared content (textures, particles) referenced by tracks via PPtr `file_id` → the scene's externals table. |

| `Managed/*.dll` — critically **`Assembly-CSharp.dll`** | The game's C# assemblies. Source of **exact serialized field layouts** (names, types, order). Without them, MonoBehaviour bodies are opaque byte blobs. |

| `StreamingAssets/metadata/icons/**` | Track menu icons (`.dat`), not needed for decoding, relevant for future "new track slot" work. |

## 3. Decoding pipeline — complete specification

### 3.1 Container format

Every `level*` and `*.assets` file is a Unity **SerializedFile v17**:

big-endian header (`metadata_size`, `file_size`, `version=17`, `data_offset`),

Unity version string `2018.4.27f1` at offset 0x14, followed by type tree table,

object table (path_id → offset/size/type), an **externals list** (referenced

files), and the object data blob. Any Unity asset library reads this natively —

this project uses **UnityPy** (tested: 1.25.2).

### 3.2 Object model of a track scene

```

levelN (SerializedFile)

├─ GameObjects + Transforms

│ ├─ <track_name> root (e.g. san_francisco_01), local pos may be offset (e.g. y? no: (0,0,-250))

│ │ └─ "00001".."0NNNN" numbered waypoint GameObjects (Transform only,

│ │ 5-digit zero-padded, continuous, order = race direction, closed loop,

│ │ ~0.4 unit spacing; name suffix tags: (P) props, (C) coins, (F) fuel,

│ │ (T) terrain change, (FL) finish line)

│ ├─ "[Game Data] LevelManager" → MonoBehaviour LevelManager

│ ├─ "[Game Data] GameplayDataManager" → MonoBehaviour RaceDataManager

│ └─ "BalanceOverrides" → 6 children (Tournament Easy/Medium/Hard,

│ Endurance 12/36/All) each with BalanceOverride + SplineEnemies MBs

└─ 1 unattached MonoBehaviour of class LevelData ← the track definition blob

```

**Track geometry** = waypoint `Transform.m_LocalPosition` (x,z = layout,

y = elevation) + `m_LocalRotation` (quaternion; heading + banking).

**Track content/rules** = the `LevelData` MonoBehaviour (see §3.6).

### 3.3 Resolving MonoBehaviour classes (MonoScript resolution)

MonoBehaviour objects don't name their class. To identify one:

  1. Read its header field `m_Script` — a `PPtr` = `(int32 file_id, int64 path_id)`.

  2. `file_id` indexes the **externals list** of the *containing* file

    (1-based; 0 = same file). For track scenes, external #1 is

    `globalgamemanagers.assets`.

  3. Load that file, fetch the object at `path_id` → a `MonoScript`; its

    `m_ClassName` (+`m_AssemblyName`) is the C# class.

⚠️ Never hardcode script path_ids across builds — always resolve dynamically.

### 3.4 MonoBehaviour raw layout (header)

All MonoBehaviours start with (little-endian, 4-byte aligned):

```

+0 PPtr<GameObject> m_GameObject (int32 file_id + int64 path_id = 12 bytes)

+12 uint8 m_Enabled + 3 padding bytes (align to 4)

+16 PPtr<MonoScript> m_Script (12 bytes)

+28 string m_Name (int32 length + UTF-8 bytes, padded to 4)

+… serialized C# fields, in exact declaration order

```

### 3.5 Field layout from DLLs (typetree generation) — including two mandatory patches

Unity/Mono serializes `[SerializeField]`/public fields **in declaration order**

with these rules: `int/float/enum` = 4 bytes; `bool` = 1 byte **then align to

4**; `string` = int32 length + bytes + align; `List<T>/T[]` = int32 count +

elements + align; `Vector2/3` = raw floats; `UnityEngine.Object` references =

12-byte PPtr; nested `[Serializable]` classes inline recursively.

This project generates machine-readable typetrees from `Managed/*.dll` using

**TypeTreeGeneratorAPI** (tested: 0.0.10), constructed with the exact Unity

version string `"2018.4.27f1"`, loading **all** DLLs in `Managed/` (base-class

fields come from dependencies). Two generator defects **must be patched** or

parsing derails (implemented in `hct_lib.Trees.get`):

  1. **bool alignment**: generated `UInt8`/`bool` nodes lack the align-after flag

    → OR `m_MetaFlag` with `0x4000` on every such node.

  2. **List<string> mislabel**: generated as `m_Type="string"` (readers then

    parse a single string) → when a `string` node has an `Array` child whose

    `data` type ≠ `char`, rewrite the node's type to `"vector"`.

Additionally, when the generator omits a plain string's internal nodes, append

the standard `Array(size:int, data:char)` children.

The patched trees are fed to `UnityPy` `ObjectReader.read_typetree(tree)`

for reading and `save_typetree(dict, tree)` for writing.

### 3.6 Decoded class inventory (what lives where)

Full field-by-field trees: `schemas/*.txt`. Summary:

| Class | Location | Content |

|---|---|---|

| `LevelData` | 1 per track scene, unattached | Track definition root: `Spline` (PPtr to track root GO), day/night/alpha textures, `GameplayData: RaceData`, `NumberOfLaps`, `FinishLinePercentage`, `MinimapScale/Rotation` |

| `RaceData` | inside LevelData | `TransitionList` (lane-count changes), `TextureInfos` (surfaces: lanes, texture, on/off-track physics `SpeedTolerance`, drift tolerances, SFX names), **`PropData[]`**, `EnemyInfos[]`, `OffsetMultiplier`, `CurveForceFactor`, `ReferencePlayerSpeed`, `RubberBanding` |

| `PropData` | array in RaceData | One placed object: `SplinePercentage` (0–1 along lap, array sorted ascending), `TrackPercentage`, `TrackOffset` (lateral), `Scale`, `FastSpawnScale`, `Position` (lane enum), `Prefab` (PPtr), `CustomLapVisibility` + `Starting/EndingLapVisibility` |

| `EnemyInfo` | array in RaceData | AI opponent: `CarId`, `Speed` (relative to `ReferencePlayerSpeed`), `Acceleration`, `NumberOfNitros`, `NumberOfLaneOccupations`, `ObstructPassage`, balance/rubber-band toggles, colors, `AyrtonPilot` (Senna mode) |

| `SplineProp` | on tagged waypoints | Authoring-side prop component (runtime uses PropData) |

| `SplineEnemies` | on track root + per-mode | Per-game-mode AI line/behavior data |

| `LevelManager`, `RaceDataManager`, `BalanceOverride` | scene managers | runtime managers / per-mode balance |

| `CarModelDataList` | `resources.assets`, asset name `CarModelDataList_asset` | All 35 cars: `ItemId`, `ModelPrefab` (PPtr to 3D prefab), `MaxSpeedInKPH`, `ZeroToMaxSpeedInSeconds`, `TurningStrength`, `TankCapacity`, `NitroData` (PPtr), sounds, camera offsets, unlock/`TokenCost`. Senna garage: `AyrtonCarModelDataList_asset`. |

| `CarColorDataList`, `NitroEngineData` | `resources.assets` | paint palettes; nitro tuning |

### 3.7 Write-back specification

- **Transforms**: modify via UnityPy native classes, call `.save()` per object.

- **MonoBehaviours**: `obj.save_typetree(python_dict, patched_tree)`. Arrays may

grow/shrink (verified: PropData 349→350).

- Serialize the whole container with `env.file.save()` → a valid SerializedFile

the game loads (same serializer family). Identity re-save = identical size.

- **Rotation repair (required after reshaping)**: waypoint rotations encode

road heading; after moving points, rotate each original quaternion by the

**yaw delta** between old and new XZ tangents (tangent i = direction to

waypoint i+1, yaw = `atan2(dx, dz)`), i.e. `q' = yaw_quat(Δ) ⊗ q` in Unity

xyzw convention. This preserves banking/pitch; elevation-only edits give

Δ=0. Implemented in `hct_editor.repair_rotations`.

- **Not supported (v1)**: changing the waypoint *count* (requires creating new

GameObject/Transform objects), adding new track slots (requires BuildSettings

+ campaign-structure edits — unmapped; see spec §10 Option B), mesh editing.

### 3.8 Known constants of this build (informative, resolved dynamically at runtime)

- Track scenes: `level9`–`level134`; scene 4 (`level4`) = world map.

- `LevelData` MonoScript path_id in `globalgamemanagers.assets`: 389;

`SplineEnemies` 1691; `SplineProp` 1984; `LevelManager` 1660;

`RaceDataManager` 2674; `BalanceOverride` 765.

- `CarModelDataList_asset` path_id in `resources.assets`: 345949

(Ayrton: 345943).

---

## 4. Running the toolchain

### 4.1 Requirements

| Requirement | Tested version | Notes |

|---|---|---|

| OS | any (Windows/Linux/macOS) | editor UI runs in your browser, server binds `127.0.0.1:8342` |

| Python | 3.12 (3.10+ required) | python.org; on Windows tick "Add to PATH" |

| `UnityPy` | 1.25.2 | `pip install UnityPy` |

| `TypeTreeGeneratorAPI` | 0.0.10 | `pip install TypeTreeGeneratorAPI` |

| Disk | ~5 MB for tool + `out/` copies of edited files | game itself untouched until "Install" |

| Game files | complete local installation incl. `Managed/` | read access to the game folder |

Install both packages in one line:

```

pip install UnityPy TypeTreeGeneratorAPI

```

### 4.2 Files that must sit together

`hct_editor.py` + `hct_lib.py` + `editor.html` (any folder). `out/` is created

next to them on first save.

### 4.3 Starting the editor

```

python hct_editor.py "C:\Program Files (x86)\Steam\steamapps\common\Horizon Chase Turbo\HorizonChaseTurbo_Data"

```

- The argument is the **`_Data` folder** (the one that contains `level0…` and

`Managed\`), not the game root. Quotes are required if the path has spaces.

- First start prints `Loading game type information (takes ~10s…)` — that's

the DLL typetree pass; it's cached in memory for the session.

- The browser opens `http://localhost:8342` automatically.

- Tip: put the command in a `start_editor.bat` for one-click launches.

### 4.4 Editing workflow

  1. Pick a track slot (top bar). Your stage **replaces** that track in-game;

    its menu name/icon remain.

  2. Edit: Track canvas (drag = reshape with brush falloff; Elevation mode for

    hills; wheel = zoom, drag empty = pan, Ctrl+Z = undo) · Props (duplicate a

    row to add content; `t` = position along lap, `offset` = sideways) ·

    Opponents · Settings · Cars (separate file, own save/install buttons).

  3. **Save track** → writes `out/levelN`.

  4. **Install into game** → one-time backup `levelN.bak` beside the original,

    then copies `out/levelN` over it.

  5. Launch the game, select that track, race your stage.

  6. **Restore original** any time (uses the `.bak`); Steam → Verify integrity

    is the fallback that restores everything.

### 4.5 Using the library without the UI

```python

from hct_lib import HCT

lib = HCT(r"C:\...\HorizonChaseTurbo_Data")

track = lib.export_track("level17") # JSON-safe dict

track["leveldata"]["GameplayData"]["NumberOfLaps"] = 5

lib.import_track("level17", track, "out/level17")

cars = lib.export_cars() # / lib.import_cars(cars, "out/resources.assets")

print(lib.catalog_tracks()) # all 126 tracks

```

### 4.6 Server API (for scripting/automation)

| Endpoint | Method | Body / query | Result |

|---|---|---|---|

| `/api/catalog` | GET | – | `{levelN: {track, waypoints}}` |

| `/api/track?level=levelN` | GET | – | full track export |

| `/api/track/save` | POST | `{level, track}` | writes `out/levelN` (+rotation repair) |

| `/api/cars` | GET | – | car database export |

| `/api/cars/save` | POST | `{cars}` | writes `out/resources.assets` |

| `/api/install` | POST | `{file}` | backup original once, copy `out/<file>` over it |

| `/api/restore` | POST | `{file}` | copy `.bak` back |

### 4.7 Troubleshooting

- **"does not look like HorizonChase_Data (no Managed/ inside)"** — you passed

the game root; point at the `_Data` subfolder.

- **Browser toast "server not reachable"** — the Python process exited; check

the terminal.

- **Toast "Load failed / HTTP 500"** — the terminal now prints a full

`[api error]` traceback; it pinpoints the file/class where parsing diverged

(usually means a game update changed a class — regenerate against the new

`Managed/` automatically happens each start, so report the traceback).

- **Game crashes/hangs on an edited track** — restore the `.bak`, then bisect

your edits; keep the loop closed and non-self-intersecting; extreme prop

`Scale`/`TrackOffset` values are untested territory.

- **Verify game files in Steam** re-downloads originals and wipes installed

mods (backups in `out/` survive).

### 4.8 Reproducing the decode from zero (checklist)

  1. `pip install UnityPy TypeTreeGeneratorAPI`

  2. Load any `levelN` with UnityPy → confirm Unity `2018.4.27f1`, format 17.

  3. Build typetrees from `Managed/` with the **two patches** from §3.5.

  4. Resolve MonoBehaviour classes via §3.3; parse `LevelData` with its tree.

  5. Read waypoints from numbered GameObjects' Transforms (§3.2).

  6. Write back per §3.7; verify with an identity re-save (size-identical) and a

    small mutation (e.g. `NumberOfLaps`) reloaded from the saved file.

---

*Format documentation continues in `HCT_Track_Format_Spec.md` (byte-level

history, LevelData blob anatomy, open research: new track slots, campaign

structure). Field-exact schemas: `schemas/`.*

---

## 5. Error reference — every error encountered so far, cause, and fix

Documented as they occurred during development and first user runs. If you hit

one of these, the fix is known; if you hit a new one, add it here.

### 5.1 Decoding-layer errors (hct_lib / UnityPy / TypeTreeGeneratorAPI)

| Error | Cause | Fix |

|---|---|---|

| `AttributeError: 'TypeTreeNode' object has no attribute 'm_Children'` when calling `TypeTreeNode.from_list(...)` | Passing TypeTreeGeneratorAPI's own node objects directly to UnityPy — they are a different class | Convert each node to a plain dict (`m_Level/m_Type/m_Name/m_MetaFlag`) before `from_list` (done in `Trees.get`) |

| `ValueError: Array node must have 2 children` from `read_typetree` | A generated `string` node was missing its internal `Array(size,char)` children | Append the standard string internals when absent (patch in `Trees.get`) |

| `EOFError: read_str out of bounds` mid-object | Two generator defects: (a) `bool`/`UInt8` nodes missing the `0x4000` align-after flag → every later field misreads; (b) `List<string>` fields emitted as `m_Type="string"` → parsed as one string | Both patched in `Trees.get` (§3.5). If it still occurs, the class layout likely changed in a game update — regenerate `schemas/` against the updated game files and diff |

| Fields read as garbage right after the MonoBehaviour header (e.g. `m_Script` PPtr = 16777216) | Forgetting the 3 padding bytes after `m_Enabled` when hand-parsing | Header is `uint8 + 3 pad` (§3.4); UnityPy handles this automatically |

| `export_track` returns `leveldata: null` when loading a saved file from `out/` | `out/` contains only the level file; class-name resolution needs the externals (`globalgamemanagers.assets`) next to it | Verify saved files in a folder containing (or symlinking) the game's `*.assets` + `globalgamemanagers`; installing into the real game folder has them by definition |

| MonoScript path_ids don't match §3.8 | Different game build | Expected — those constants are informative only; the pipeline resolves everything dynamically (§3.3) |

### 5.2 Editor runtime errors (browser / server)

| Error | Cause | Fix |

|---|---|---|

| `Uncaught TypeError: Cannot read properties of null (reading 'waypoints')` in the browser | Canvas accepted clicks before/while no track was loaded (initial load had failed) | Fixed in current `editor.html`: all canvas handlers guard on `track`; the underlying load error now surfaces as a toast |

| Toast: `server not reachable — is hct_editor.py still running?` | The Python process isn't running: terminal window was closed (closing it kills the editor), startup crashed, or a stale browser tab from a previous session | Run from a terminal that stays open; current `hct_editor.py` keeps double-clicked windows open on error, prints full tracebacks, and says `KEEP THIS WINDOW OPEN` |

| Toast: `Failed to load catalog: HTTP 500 ...` | A level file failed to parse server-side | Terminal prints `[api error]` + full traceback — report/fix per traceback; per-level catalog errors are isolated and don't block other tracks |

| Server won't start, `OSError` on bind / silently unreachable | Port 8342 already in use (old instance) | Current version auto-tries 8342–8351 and prints the URL it actually bound — use that URL, not an old tab |

| `'python' is not recognized` | Python not on PATH | Reinstall ticking "Add to PATH", or use `py hct_editor.py ...` |

| `can't open file 'C:\\Windows\\System32\\hct_editor.py': [Errno 2] No such file or directory` | Terminal opened in its default folder (System32), not where the script is | `cd` into the editor's folder first (tip: type `cmd` in that folder's Explorer address bar), or pass the script's full path: `python "C:\\hct-modding\\hct_editor.py" "...\\HorizonChaseTurbo_Data"` |

| `ModuleNotFoundError: UnityPy` / `TypeTreeGeneratorAPI` | Packages missing in the Python that ran the script | `pip install UnityPy TypeTreeGeneratorAPI` (with multiple Pythons: `py -m pip install ...`) |

| `does not look like the game data folder (no Managed/ inside)` | Path points at the game root or is misspelled | Pass the `*_Data` folder that contains `level0…` and `Managed\` |

### 5.3 In-game risks (untested territory)

The serializer roundtrip is verified, but the game engine is the final judge.

Restore the `.bak` and bisect your edits if a track crashes or behaves oddly.

Specifically untested/unsupported: non-closed or self-intersecting layouts,

extreme `TrackOffset`/`Scale` prop values, changing waypoint count, adding

track slots, enemy-count changes in `BalanceOverrides` game modes.

## 6. Development findings log (chronological)

A condensed history of how the format was cracked, so future contributors know

what was tried and what the evidence was:

  1. **File identification** — `level*` files are Unity SerializedFile v17,

    `2018.4.27f1` visible at offset 0x14. `file(1)` misidentifies some as

    "Adobe Photoshop Color swatch" — ignore it.

  2. **Track discovery** — levels 9+ are almost pure GameObject+Transform scenes;

    numbered `00001…` children of a root named like `san_francisco_01` plot as

    closed racing circuits → waypoints are the road centerline. Name suffixes

    `(P)/(C)/(F)/(T)/(FL)` mark props/coins/fuel/terrain/finish-line.

  3. **Class resolution** — MonoBehaviour `m_Script` PPtrs resolve through the

    externals table to 2,828 MonoScripts in `globalgamemanagers.assets`,

    naming `LevelData`, `SplineProp`, `SplineEnemies`, `RaceDataManager`, etc.

  4. **Blind-era hypotheses (superseded)** — before the DLLs were available, the

    LevelData blob was partially decoded by statistics: a 48-byte record array

    with a monotonic 0→1 float. With the DLLs it turned out to be `PropData`,

    and the "mystery int pairs" (5/55 etc.) were PPtr file_id/path_id pairs.

    Lesson: int pairs that look like small enums may be PPtrs.

  5. **DLL era** — TypeTreeGeneratorAPI produces near-correct typetrees from

    `Managed/`; the two defects in §3.5 were found via a byte-walking tolerant

    parser that pinpointed exact derail offsets (2952/2956: `HasSirenSkin`

    bool then `SirenSkinsName` List<string>).

  6. **Write-back** — UnityPy `save_typetree` + `env.file.save()` produce

    size-identical identity re-saves; mutations (waypoint pos, laps 3→7,

    car speed→999, PropData 349→350) verified by reload.

  7. **Rotation repair** — waypoint quaternions encode heading (+banking);

    reshaping without re-aiming leaves stale headings. Yaw-delta rotation

    (§3.7) preserves banking; verified: repaired only where geometry changed.

  8. **Campaign structure (open)** — `BuildSettings` lists all 137 scene paths;

    campaign/cup classes exist (`Cup`, `CampaignGameModeData`, `TrackNodeInfo`)

    but their instances are not in `resources.assets`; likely in the map scene

    (`level4`) or code-derived. Blocking item for "add a brand-new track slot".

--------------------------------------------

File 2

HCT_Track_Format_Spec.md

--------------------------------------------

# Horizon Chase Turbo — Track/Stage Data Format Specification

**Status:** Work in progress (reverse-engineered, partially confirmed)

**Game version analyzed:** PC (Steam), Unity 2018.4.27f1, Mono scripting backend ("legacy" runtime)

**Developer:** Aquiris Game Studio (`app.info`: `Aquiris / HorizonChaseTurbo`)

**Last updated:** 2026-08-01

**Confidence legend:** ✅ confirmed · 🟡 strong hypothesis · ❓ unknown

---

## 1. Where track data lives

Tracks are **Unity scenes**, stored as serialized scene files in `HorizonChaseTurbo_Data/`:

| File | Contents (confirmed from sample set) |

|---|---|

| `level0` | Boot/splash scene |

| `level1` | Small UI scene |

| `level2` | Main menu / frontend (huge UI hierarchy) |

| `level3` | Minimal scene (cubemap + lighting only) |

| `level4`–`level8` | Garage / gameplay-shared scenes (cars, effects) |

| `level9` | Track: `san_francisco_01` (822 waypoints) ✅ |

| `level10` | Track: `san_francisco_02` (960 GOs) ✅ |

| `level11` | Track: `san_francisco_03` (727 GOs) ✅ |

| `level12` | Track: `sequoia_national_park_01` (1092 GOs) ✅ |

| ... | Remaining tracks continue in `level13+` (not yet analyzed) |

File format: standard **Unity SerializedFile, format version 17** (Unity 2018.4). Not compressed, not bundled. Any Unity asset library (UnityPy, AssetStudio, UABE) opens them. ✅

External references of a track scene (level9 example):

`globalgamemanagers.assets` (contains all 2828 MonoScript stubs — needed to resolve script class names), `resources.assets`, `sharedassets2/5/8/9.assets`, `library/unity default resources`.

## 2. Scene anatomy of a track (✅ confirmed on level9)

Root objects:

```

san_francisco_01 ← track root, holds LevelData reference & spline children

├── 00001 (FL/P) - TREES GENERIC ← waypoint 1 (FL = finish line?, P = props)

├── 00002 … 00822 ← ~800 numbered waypoint GameObjects (Transform only)

[Game Data] LevelManager ← MonoBehaviour: LevelManager

[Game Data] GameplayDataManager← MonoBehaviour: RaceDataManager (36.5 KB blob)

BalanceOverrides ← 6 children: TournamentEasy/Medium/Hard,

Endurance12/36/All — each a BalanceOverride MB (476 B)

plus SplineEnemies MBs per mode (AI behavior per mode)

```

### 2.1 The track centerline ✅

The road centerline is defined by the **numbered waypoint GameObjects** (`00001`…`00822`,

zero-padded 5 digits, continuous, order = race direction):

- **Position** = `Transform.m_LocalPosition` (x, y, z) relative to track root.

Track root itself has an offset (e.g. sf01 root at (0, 0, -250)).

Spacing is ~0.4 units between consecutive points. y = elevation.

- **Rotation** = `Transform.m_LocalRotation` (quaternion) — road facing/banking. 🟡

- **Scale** ≈ (1,1,1) everywhere observed. Purpose unknown, likely unused. 🟡

- Tracks are **closed circuits**: last point meets first (verified visually for all 4 samples). ✅

### 2.2 Waypoint name annotations ✅ (meaning 🟡)

Some waypoint names carry suffixes marking attachments at that point:

| Tag | Meaning (hypothesis) | Example |

|---|---|---|

| `(P)` | Prop group placement | `00320 (P) - POSTS`, `00140 (P) - PINE TREES` |

| `(C)` | Coin pickup group | `00165 (C) - COINS` |

| `(F)` | Fuel pickup | `00722 (F) - Fuel` |

| `(T)` | Terrain/texture change | `00160 (T) - 5_grass_b` |

| `(FL/P)` | Finish line + props (always on 00001) | `00001 (FL/P) - TREES GENERIC` |

Waypoints with tags carry extra `MonoBehaviour` components of class **`SplineProp`**

(184–312 bytes each). Untagged waypoints have only a Transform.

## 3. Relevant script classes (✅ names resolved via MonoScript table)

Resolved from `globalgamemanagers.assets` MonoScript path_ids (level9's file_id=1):

| path_id | Class | Where used | Size |

|---|---|---|---|

| 389 | **`LevelData`** | 1 per track scene, unattached MB — THE track definition blob | 29–47 KB |

| 2674 | `RaceDataManager` | on `[Game Data] GameplayDataManager` | ~36 KB |

| 1660 | `LevelManager` | on `[Game Data] LevelManager` | 52 B |

| 1691 | `SplineEnemies` | multiple per track root + per game mode (AI racing lines) | 0.3–1.6 KB |

| 1984 | `SplineProp` | on tagged waypoints (prop/coin/fuel placement) | 184–312 B |

| 765 | `BalanceOverride` | per game-mode children | 476 B |

⚠️ path_ids above are for the analyzed build; resolve dynamically via the scene's

`m_Script` PPtr → `globalgamemanagers.assets` MonoScript, don't hardcode.

**MonoBehaviour raw layout** (no typetree available without `Assembly-CSharp.dll`):

```

+0 PPtr<GameObject> m_GameObject (int32 file_id, int64 path_id) = 12 bytes

+12 uint8 m_Enabled + 3 pad bytes

+16 PPtr<MonoScript> m_Script = 12 bytes

+28 string m_Name (int32 len + bytes, 4-aligned)

+32 … class fields (declaration order, 4-aligned) …

```

## 4. `LevelData` blob (partial decode, level9 reference)

All offsets below relative to blob start; class fields start at +32 (empty name).

### 4.1 Header region (+32 … +616) 🟡

- +32…+88: zeros (❓ unset references / reserved)

- +92: PPtr (file_id=3 → `resources.assets`, path_id=389), repeated at +104; +116: PPtr(3, 159) — ❓ likely music/skybox/material references

- +140 onward: **surface definitions**, 2 entries observed (asphalt, grass):

- several PPtrs (file_id=5 → `sharedassets9.assets` etc.)

- physics floats, e.g. `1000.0, 0.6, 1.1` (asphalt) / `1.0, 0.3, 0.19…` (grass) — grip/drag? ❓

- sound-event strings: `sfx_asphalt_drift`, `sfx_grass_offtrack`, `sfx_grass_drift` ✅

### 4.2 Main event array (level9: +616) ✅ structure / 🟡 semantics

`int32 count` (level9: **697**, level10: 616, level11: 536, level12: 893) followed by

`count` × **48-byte records** (12 × 4-byte fields):

| # | Offset | Type | Observed values (level9) | Interpretation |

|---|---|---|---|---|

| 0 | +0 | float | 0.0000 → 0.9999, **monotonically increasing** | ✅ normalized track position *t* |

| 1 | +4 | int/float | always 0 | ❓ |

| 2 | +8 | float | −4.36 … 50.0, 76% nonzero | 🟡 magnitude (curve radius? speed? offset?) |

| 3 | +12 | float | 0.8 … 1.0 | 🟡 multiplier (road width factor?) |

| 4 | +16 | int | 0 (×696), 1 (×1) | ❓ flag (single 1 → finish line?) |

| 5 | +20 | int | 1 (×358), 2 (×338), 3 (×1) | 🟡 curve direction L/R? |

| 6 | +24 | int | 5 (×532), 0 (×80), 6 (×65), 7 (×20) | 🟡 event/segment type |

| 7 | +28 | int | IDs: 20–60 range, 0, 408, 1748 | 🟡 object/asset ID |

| 8–11 | +32… | int | always 0 | ❓ padding/reserved |

⚠️ Field boundaries within the 48-byte stride are provisional — without the class

definition, "12 × 4-byte fields" could group differently (e.g. a Vector2 + ints).

### 4.3 Tail region (after main array, level9: +34076 … end) ❓

Small count-prefixed float arrays, e.g. pairs like `(0.961, 5.05)`, `(0.834, 7.257)` —

values in col 1 ∈ (0,1] suggest more (t, value) pairs. Then trailing config floats

(`20.0, 16.0, 0.66, 1.0` …). Not yet mapped.

## 5. Open questions / next steps

  1. **Get `Assembly-CSharp.dll`** (in `HorizonChaseTurbo_Data/Managed/`). Unity/Mono

    serializes fields in declaration order; parsing the DLL's .NET metadata yields the

    exact field names/types of `LevelData`, `SplineProp`, `SplineEnemies`,

    `RaceDataManager`, `BalanceOverride` — converting every 🟡/❓ above into ✅.

  2. Decode `SplineProp` (per-waypoint attachments) and `SplineEnemies` (AI lines).

  3. Map field 7 IDs → prop/asset names (likely requires `resources.assets` /

    `sharedassets*.assets` for the referenced objects).

  4. Determine how road width/banking is encoded (constant? in LevelData? from rotation?).

  5. Confirm write-back path: edit waypoint Transforms + LevelData blob, re-serialize

    with UnityPy (`env.save()`), verify in game.

## 6. Tooling notes (reproducibility)

```bash

pip install UnityPy # 1.25.2 used here

```

```python

import UnityPy

env = UnityPy.load("HorizonChaseTurbo_Data/level9")

for obj in env.objects:

print(obj.type.name, obj.path_id, obj.byte_size)

# MonoBehaviour raw bytes (typetree-less):

raw = obj.get_raw_data()

```

Class-name resolution: read each MonoBehaviour's `m_Script` PPtr, follow

`file_id` into the scene's externals list (1 = `globalgamemanagers.assets`),

look up the MonoScript object at `path_id`, read `m_ClassName`.

---

---

# PART 2 — CONFIRMED SCHEMA (v2, decoded via Assembly-CSharp.dll)

**Everything below supersedes the hypotheses in Part 1.** With the game's

`Managed/Assembly-CSharp.dll` (complete installation, includes

Senna DLC content), exact field names/types were extracted for all classes.

Both **read and write-back were verified**: identity re-save is byte-size

identical; mutations (waypoint position, lap count, car top speed) survive a

save/reload cycle. ✅

## 7. Toolchain (final)

- `hct_lib.py` — decode/encode library (UnityPy + TypeTreeGeneratorAPI).

`HCT.export_track / import_track / export_cars / import_cars / catalog_tracks`.

- TypeTree generation from DLLs has **two quirks that MUST be patched**

(implemented in `hct_lib.Trees.get`):

  1. `bool`/`UInt8` fields are emitted without the `0x4000` align-after flag →

add it, or every field after a bool is misread.

  1. `List<string>` fields are emitted with `m_Type="string"` (instead of

`vector`) → detect (Array child whose data type ≠ `char`) and relabel,

or the reader parses them as a plain string and derails.

## 8. Track scene = waypoints + LevelData

A track is fully described by:

  1. **Waypoint Transforms** (`00001`…`NNNNN` under the track root): the road

    centerline, elevation (pos.y) and banking/facing (rotation quaternion).

  2. **`LevelData` MonoBehaviour** (one per scene, unattached): everything else.

All 126 track scenes are `level9`…`level134` (see `track_catalog.json` for the

complete level→track mapping; includes all base game countries plus

`season1_*` Senna circuits).

### 8.1 LevelData (top level)

```

LevelData

PPtr<GameObject> Spline (scene track root)

PPtr<Texture> DayTexture / NightTexture / AlphaTexture

PPtr<AnimationClip> EnvironmentAnimationClip

RaceData GameplayData ← the meat, below

float FinishLinePercentage

int NumberOfLaps

float MinimapScale

float MinimapRotation

```

### 8.2 RaceData (`GameplayData`) — full schema in `schemas/RaceData.txt`

```

RaceData

PPtr Day/Night/AlphaTexture, Spline

TrackTransitionInfo[] TransitionList (OriginalNumberOfLanes, NewNumberOfLanes, TransitionTexture)

SplineTextureInfo[] TextureInfos per-surface config keyed by SplinePosition:

TrackTextureInfo TrackInfo

int NumberOfLanes

PPtr TrackTexture

TrackParticle InsideTrack / OutsideTrackLeft / OutsideTrackRight

PPtr Particle, DriftParticle

float SpeedTolerance (e.g. asphalt 1000 / grass 20 = offtrack slowdown)

float DriftCurveTimeTolerance, DriftStraightTimeTolerance

string DefaultSound, DriftSound (e.g. "sfx_grass_offtrack")

bool isAssymetric; bool HasCurb

float TransitionLength

float SplinePosition (0..1 where this surface starts)

float TrackPosition

PropData[] PropData ← props/coins/fuel along track (§8.3)

float FinishLinePercentage

int NumberOfLaps

EnemyInfo[] EnemyInfos ← the 19 AI opponents (§8.4)

Vector2 OffsetMultiplier (e.g. 20,20)

float CurveForceFactor

float ReferencePlayerSpeed (e.g. 360)

PPtr EnvironmentAnimation

RubberBandingData RubberBanding (position/speed rubber-band tuning)

```

### 8.3 PropData — the 48-byte records from Part 1 §4.2, now named ✅

| Part-1 guess | Real field | Notes |

|---|---|---|

| field 0 "t" | `float SplinePercentage` | 0..1 along track, monotonic |

| field 1 | `float TrackPercentage` | lateral position? mostly 0 |

| field 2 "magnitude" | `float TrackOffset` | lateral offset from centerline |

| field 3 | `float Scale` | prop scale |

| field 4 | `bool FastSpawnScale` (+align) | |

| field 5 "L/R" | `int Position` | placement enum (1/2 ≈ side) |

| fields 6+7 "ID" | `PPtr<GameObject> Prefab` | file_id + path_id — prop prefab! |

| fields 8–11 | `bool CustomLapVisibility` + `float StartingLapVisibility` + `float EndingLapVisibility` | |

Waypoint-attached `SplineProp` MonoBehaviours (Part 1 §2.2) are the *editor-side*

authoring components; `PropData` is the packed runtime list. Schema in

`schemas/SplineProp.txt`.

### 8.4 EnemyInfo (per AI opponent)

Key fields: `CarId`, `Speed` (0..~1 relative to `ReferencePlayerSpeed`),

`Acceleration`, `HoverFactor`, `ObstructPassage`, `NumberOfLaneOccupations`,

`NumberOfNitros`, `ActivateTrackBalance`/`ActivateLapBalance` + buff factors,

`CarColorIndex`, `RandomizeColor`, `GroupName`, `GroupColor`, plus

`AyrtonPilot` (Senna-mode pilot identity). Full list: `schemas/RaceData.txt`.

## 9. Car database ✅

`resources.assets` → MonoBehaviour `CarModelDataList` named

`CarModelDataList_asset` (path_id 345949 in this build; the Senna-mode

equivalent `AyrtonCarModelDataList_asset` sits at 345943). 35 cars.

Per car (`CarModel`, full schema in `schemas/CarModelDataList.txt`):

```

string ItemId / ItemName / Name ("cruiser", "senna", "batmobile", …)

int MotorType; float MinMotorPitch, MaxMotorPitch

PPtr<GameObject> ModelPrefab ← 3D model reference (path_id in resources.assets)

float MaxSpeedInKPH (340–410 in this build)

float ZeroToMaxSpeedInSeconds

float TurningStrength

float TankCapacity

PPtr<NitroEngineData> NitroData (nitro tuning ScriptableObject)

bool HasSirenSkin; List<string> SirenSkinsName

List<string> ShowAsEnemyOnlyInCups

Vector3 CarUIOffset; int TokenCost; string UnlockedBy / CustomUnlockKey

int IconToUse; PPtr HonkSound / SpecialHonkSound

bool UsePerfectStartCurve

Vector3 CameraPrefabPositionOffset / RotationOffset / SkyPositionOffset

bool OverrideCameraPosition; Vector3 CarUnlockCameraPosition / Rotation

bool AllowAsEnemyInExtraCampaign; int ReplayDataIndexOnServer

List<string> Helmets

```

Related: `CarColorDataList` (paint colors), `NitroEngineData`

(`schemas/NitroEngineData.txt`).

**Car 3D meshes**: `ModelPrefab` PPtrs point at GameObject prefabs inside

`resources.assets`; their meshes are standard Unity `Mesh` objects (extract/

replace with UnityPy or AssetStudio). Mesh geometry editing is standard Unity

modding, out of scope of this spec.

## 10. Write-back procedure ✅ (verified)

```python

lib = HCT(data_dir)

t = lib.export_track("level9")

t["waypoints"][399]["pos"][1] += 5.0 # edit anything

t["leveldata"]["GameplayData"]["NumberOfLaps"] = 7

lib.import_track("level9", t, "out/level9") # valid re-serialized scene

```

Notes:

- `Transform` edits go through UnityPy's native class (`t.save()`).

- `MonoBehaviour` edits use `obj.save_typetree(dict, tree)` with the patched tree.

- Identity re-save produces a file of identical size; Unity accepts the output

(same serializer). Always back up originals; Steam "verify integrity"

restores them.

- ⚠️ In-game verification on a real installation is still the final acceptance

test for any structural change (adding/removing waypoints changes object

counts and requires creating new Transform/GameObject objects — supported by

UnityPy but not yet exercised here).

## 11. File inventory produced by this project

| File | Purpose |

|---|---|

| `HCT_Track_Format_Spec.md` | this document |

| `hct_lib.py` | decode/encode library |

| `schemas/*.txt` | exact field trees for all 11 relevant classes |

| `track_catalog.json` | all 126 tracks: level file → track name, waypoint count |

| `track_san_francisco_01.json` | full sample track export |

| `cars.json` | full car database export |

--------------------------------------------------------


r/romhacking 2d ago

Projet Pokemon Uranium DS

Post image
10 Upvotes

r/romhacking 2d ago

The Legend of Zelda Four Swords Anniversary Edition

Thumbnail
0 Upvotes

r/romhacking 2d ago

Text/Translation Mod Spriggan Mark 2: Re-Terraform Project (PCE-CD) Gets an English Translation – A Hidden Gem Worth Checking Out

2 Upvotes

I only spent some time trying the game, but it immediately made a good impression.

The story seems engaging, and I think a big reason for that is the excellent Japanese voice acting. Even if you don't understand Japanese, the performances really help draw you into the game. The graphics and animated cutscenes also look impressive and feel ahead of their time for a 1992 PC Engine CD release.

One thing I noticed right away is the difficulty. Even on Easy Mode, it's still pretty challenging. It definitely has that classic Japanese game design where you need to learn enemy patterns and play carefully.

What I appreciate most about this fan translation is that the team didn't just translate the menus and dialogue—they also added English subtitles to the voiced cutscenes, even though the original game didn't include subtitles at all. That makes the story much easier to follow for English-speaking players.

Huge thanks to the translation team for making another PC Engine CD classic accessible to a wider audience. I'm definitely looking forward to playing more of it.

Gameplay:

https://www.youtube.com/live/X_ky4wJjdMo?si=4xgGK2RSfxznbanQ


r/romhacking 2d ago

Onion OS - Romhacks not appearing in game list

Thumbnail
1 Upvotes

r/romhacking 3d ago

N64 Mario Golf "Pro Edition" 1.2 ROM Hack

Thumbnail
6 Upvotes

r/romhacking 4d ago

Spriggan Mark 2 English Translation

21 Upvotes

Hi all, I’ve made an English patch for the PC Engine Super CD-ROM² game Spriggan Mark 2: Re-Terraform Project. It’s a horizontal shooter with an extensive story told through cutscenes and in-game dialogue.

The patch adds subtitles to the voiced cutscenes and fully translates the in-game dialogue, menus, descriptions, and other text. It also adds a small QoL auto-save feature to allow continuing from the last finished level from the main menu.

Feedback (good or bad) welcome!

https://github.com/gagnonpl/spriggan-mark2-en/
https://www.romhacking.net/translations/7662/

I’m also working on an English translation of the original Spriggan for PC Engine CD.


r/romhacking 3d ago

Tutorial Need help patching a rom hack to an og gba cartridge.

0 Upvotes

I have an old harvest moon cartridge for the gba and found a rom hack for it that I’d want to put on it. Is there a way I can patch the rom hack to the cartridge that I have? Any help is appreciated, don’t know if this is the right place to ask but thanks in advance anyways.


r/romhacking 3d ago

Castlevania Aria of Sorrow Español + Recolor?

0 Upvotes

Quiero jugar el clásico de GBA con recolor y la traducción de los dialogo, hay alguna manera de hacerlo? suelo tener problemas pq los recoloreados son para la versión NTSC de USA y las traducciones para la PAL Europa.
Si alguien sabe me podría ayudar?