Asset Payloads
Classes whose data lives outside the tagged property stream get a dedicated serializer. Each one reads the native tail, optionally writes a sidecar file, and knows how to inject an edited sidecar back on the way in.
| Class | Sidecar | Round-trip |
|---|---|---|
Texture2D |
.dds |
yes |
SwfMovie, GFxMovieInfo |
.gfx |
yes |
SoundNodeWave |
.ogg / .wav / .mp3 / .xma |
extract only |
Serializers are resolved through the class chain, so one registered for a base class also covers its subclasses.
Bulk data
Everything below is built on UE3's bulk-data header:
u32 flags
i32 element count
i32 size on disk
i32 offset in file
... payload — inline only if BULKDATA_StoreInSeparateFile (0x1) is clear
| Flag | Meaning |
|---|---|
0x01 |
payload lives in a separate file (a .tfc) |
0x02 |
zlib compressed |
0x04 |
force single element serialization |
0x08 |
single use |
0x10 |
LZO compressed |
0x40 |
store only payload |
0x80 |
LZX compressed |
Texture2D
The tail is: a source-art bulk block, then an indirect mip array, then a TFC GUID
(p_ver ≥ 567), then cached PVRTC mips (p_ver ≥ 674), then any trailing bytes.
Each mip is a bulk block plus its sizeX / sizeY. A mip is classified as:
| Source | When |
|---|---|
inline |
payload was stored in the export |
tfc('<stem>') |
separate-file flag set and a size on disk — resolved from the TextureFileCacheName property against the .tfc index |
missing |
neither, or the TFC couldn't be found |
The pixel format comes from the Format property (PF_DXT1, PF_DXT3, PF_DXT5, PF_A8R8G8B8,
PF_G8, PF_BC5, PF_FloatRGBA). BC5 and FloatRGBA are written with a DX10 extension header;
the rest use classic FourCC or RGB/luminance masks. Mips are sorted largest-first and written as one
.dds.
Compressed TFC payloads are skipped
A mip stored in a .tfc with a compression flag set isn't inflated — the mip is marked
missing and left out of the .dds. Uncompressed TFC payloads work.
Injecting a texture
On repack, the .dds is decoded and matched to the original mips by dimensions. Each matched
mip is rewritten inline: payload replaced, the separate-file and compression flags cleared, sizes
updated, offset zeroed. The TFC GUID is then zeroed so the game stops looking in the cache file.
To change a texture's resolution, update SizeX and SizeY in the .uo to match your DDS's top
mip — that authorises a full mip-list replacement. Without it, a DDS whose top mip matches no
original mip is an error, deliberately.
SwfMovie / GFxMovieInfo
Unlike the other two, the payload isn't in the native tail at all — it's the RawData tagged
property. Extraction pulls the bytes out (either a decoded byte array, or a raw blob with its
4-byte count prefix stripped) and writes <name>.gfx, marking the property @sidecar in the
.uo.
The magic is checked against GFX, CFX, CWS, FWS, ZWS. A mismatch is a warning, not a
failure.
Injecting writes the bytes back into RawData, stripping a stale count prefix if present. If the
property doesn't exist on the export, a new ArrayProperty tag is created for it.
SoundNodeWave
The tail is four consecutive bulk blocks: raw PCM, compressed PC, compressed Xbox 360, compressed
PS3. Metadata (NumChannels, SampleRate, Duration, ChannelOffsets, ChannelSizes) comes from
the tagged properties.
Each block's contents are sniffed to pick an extension:
| Sniff | Extension |
|---|---|
OggS |
.ogg |
RIFF/WAVE |
.wav (or .xma if an XMA chunk is present in the first 64 bytes) |
ID3 or an MPEG frame sync |
.mp3 |
| anything else | .bin |
Raw PCM is wrapped in a 16-bit WAV header using the sample rate and channel count from the
properties. If SampleRate is zero it's written as headerless .raw.pcm instead.
Sound is extract-only
SoundNodeWave has no injector. Replacing audio requires editing the native tail by hand.
Font generation
create-font builds a UE3 Font from a TrueType file rather than extracting one.
Glyphs are rasterized at size × dpi / 72 into pages of --tex-width × --tex-height, packed
row by row with the configured padding, overflowing into additional pages as needed. Each page
becomes a Texture2D .uo + .dds pair; the font itself becomes a .uo carrying the character
map, per-glyph UV cells, page indices, vertical offsets, em scale, ascent and descent.
The character set defaults to printable ASCII plus Ukrainian Cyrillic; override it with --chars.
--game-root is required so the target export path can be resolved and the generated properties
typed against the real Font and Texture2D classes.
Fallbacks
If a class has no registered serializer:
- Empty tail → nothing emitted.
- With a schema — decode the tail as a positional walk of the class's
CPF_Nativeproperties (see Property Decoding). - Otherwise — preserve the tail verbatim and render it as
@native(Raw)with a byte count and a 64-byte hex preview.
The raw path is lossless: the packer reuses those bytes untouched, so an object it doesn't understand can still be round-tripped safely.