3. Decode a resource format
The game's data is a set of serialised engine objects: dialogs, building templates, start conditions, sounds, meshes and many more. A behaviour often needs one of them. If Un-Banished cannot read it yet, you add a reader to crates/formats. This guide explains how.
Game data files explains the containers. This guide assumes you know that a resource is a .crs file inside a .pkg package.
What is already solved
You do not need to work these out again:
- A
.pkgpackage holds entries compressed with LZF.pkg::Archivemerges the four packages in this order of priority:data1,patch1,patch0,data0. - A
.crsresource is a series of blocks. Each block is0xaaaaaaaa | size | version | payload | 0xbbbbbbbb. The outer payload starts with the magic number0x5412a91f, the type id, the file version and a list of dependencies. Then there is one nested block for each level of the class hierarchy, base class first, as the class'sSerialize(Stream&)writes them. - The type id is the CRC-32 of the class name in UTF-16LE.
- A string is a
u32length, then the characters. If bit 31 of the length is set, the characters are 8-bit; if not, they are UTF-16. Both include the terminating zero. - An external reference is a
bool(present or not), then a string. - A reference names an object inside a resource file, for example
Path/File.rsc:resource. Inside the package, the same entry is namedPath\File.crs.
The full details are in FINDINGS.md, sections 2 and 3, and in tools/crs/crs.py.
Steps
- Find the serialiser. Read the type id from the file's header, or look it up in
tools/schema/types.json. Then find the class'sSerializefunction in the Runtime or the Application (Trace). Write down every read, in order, with its type and the version checks (if (version > 1)). - Write a prototype in Python. Python is faster to change, and the tools can run it over every file of the type. Add a decoder to
tools/crs/types/<area>.py:pythonThe reader hasfrom ..crs import decoder from .ui import ext @decoder('ClassName') def class_name(r, header): # FUN_1400xxxxx: the serialiser this follows. with r.block(): # one block per class level return {'count': r.u32(), 'name': r.string(), 'target': ext(r)}u32,i32,f32,bool,stringandbytes. The helpersext(r)andext_list(r)read external references. Then validate it over all files:bashEvery file must decode, and every byte of each block must be read. Bytes left over mean a field you missed.python3 -m tools.crs.validate game-data/extracted ClassName - Port it to Rust, in
crates/formats/src/<area>.rs(game.rsfor gameplay resources,ui.rsfor the interface). Write a plainpub structwhose fields have doc comments (the offset in the runtime object, and the meaning), and a functionpub fn name(data: &[u8]) -> Result<T>. Use thecrs::Reader. Add no dependencies. Name the serialiser's address in the doc comment. - Write a corpus test,
crates/formats/tests/<name>.rs.toolbar.rsis a model. Open the four packages fromgame-data/original/WinData. If they are missing, print a message and return. Select the entries by their normalised name, parse every one, check the count (names.len() > N), and check one known value.bashcargo test --release -p banished-formats --test <name> - Optional: an inspector. A small example program that prints a parsed resource helps the next person.
crates/formats/examples/dialog_info.rsis a model:bashcargo run -p banished-formats --example dialog_info -- game-data/original/WinData \ "Dialog/Toolbar.rsc:resource" - Document the layout in a document in
docs/: a table of the fields, the versions, and the type ids.
Common mistakes
- Files in the same folder can be of different classes. For example,
Game\Toolbar_resource.crsis anExternalList, not aToolbarItem. Filter by type id. - Two empty strings next to each other look like a single
u32zero. Check against a file where the values are not empty. - A
boolis one byte. An enum is au32or ani32. A colour is often fourf32values or a packedu32. Confirm the type in the serialiser. Do not guess it from the bytes. - Never commit bytes from a resource as a test fixture. Tests read your own game data.