Extending
Adding an asset class
Anything whose payload lives in the native tail can get a serializer. Implement NativeSerializer
in a new src/native/<class>.rs:
pub struct MyAssetSer;
impl NativeSerializer for MyAssetSer {
fn class_name(&self) -> &'static str { "MyAsset" }
fn read(&self, ctx: &NativeReadCtx) -> Result<NativeRead> {
// ctx.blob — the native tail (everything after the "None" property)
// ctx.props — the already-decoded tagged properties
// ctx.ver — package version, for version gates
// ctx.db — schema database, if --game-root was given
// ctx.self_ref / ctx.class_ref — this export, and its class
let payload = /* parse ctx.blob */;
Ok(NativeRead::just(NativePayload::MyAsset(payload)))
}
fn emit_external(&self, payload: &NativePayload, dir: &Path, stem: &str)
-> Result<Vec<PathBuf>>
{
// write <stem>.<ext> into dir, return the paths — they become @sidecar lines
}
fn inject_external(&self, ctx: &mut NativeInjectCtx) -> Result<bool> {
// find your extension in ctx.sidecars, read it from ctx.sidecar_dir,
// and rewrite ctx.native_tail (or ctx.props, for property-backed payloads)
// return true if you changed anything
}
}
Then:
- Add a variant to
NativePayloadand a label invariant_label(). - Register it in
NativeRegistry::standard(). - Add a rendering arm in
pseudo::render_nativeso the@native(...)block prints something useful.
Registration is by class name, matched through the chain
A serializer registered for a base class automatically covers subclasses, because the registry
walks the class chain before falling back to the export's literal class name. An alias for a
second class name can be inserted into the registry map directly — that's how GFxMovieInfo
reuses the SwfMovie implementation.
If the payload is a property, not a tail
SwfMovie is the model: read() pulls the bytes out of ctx.props and reports the property name
in consumed_props, which makes the emitter print RawData = @sidecar instead of a giant byte
array. inject_external() then writes back into ctx.props rather than ctx.native_tail.
Supporting another engine version
All version knowledge lives in versions.rs as VER_* constants compared against p_ver. To
support a build whose layout differs:
- Add a named constant for the version the change landed in.
- Gate the field where it's read, and mirror the gate in the corresponding write path — headers and properties both have one.
Serialization order matters more than the constant's value; when in doubt, compare against the UE3 source for the two versions that bracket the target.
Cooked-for-console layouts are not exercised
SchemaParseCtx carries a cooked_for_console flag and the meta-class deserializers honour it
by omitting editor-only fields, but it is currently always constructed as false. Console
packages will need that wired to a real detection before their script objects parse correctly.
Adding a compression codec
utils/decompress.rs handles the chunk framing generically — only the block-level call is
codec-specific. Add an arm to decompress_chunk for CompressionMethod::Zlib or Lzx; the
surrounding two-level chunk walk, byte-swap detection and size bookkeeping already work.
Adding a metatype
If a UE3 reflection class isn't handled, parse_export_schema returns None for it and the
database stores an OpaqueChild — the children walk steps over it but its contents are invisible.
To support it:
- Write a reader following the
UField/UStructheader conventions in Schema System. - Add a
SchemaEntryvariant and wire it intoas_struct_header()andnext()so the graph walks still work. - Add a dispatch arm keyed on the class name.
- Add a
summarize_entryarm soschema-dumpprints it.
Code conventions
- All binary I/O goes through
byteorderwith an explicit endianness. - Errors are
std::io::Resultenriched with context strings — no error framework in the hot path. - Anything version-dependent is a named constant, never a bare number.
- Count-prefixed reads validate the count against remaining bytes before allocating; keep that habit, it's the main defence against a mis-parse allocating gigabytes.
- Decoders that can't verify their result should degrade to
Rawrather than guess. Round-trip safety beats prettier output.