49 lines
3.3 KiB
Markdown
49 lines
3.3 KiB
Markdown
# UE3 packages (`*.upk`)
|
|
|
|
Arkham City's 2,650 packages in `BmGame\CookedPCConsole` are Unreal Engine 3 packages, version 805,
|
|
licensee 101. 2,647 are fully LZO-compressed, 2 are stored uncompressed, and `UnrealX.upk` (512 bytes)
|
|
is not a readable package. ackit reads and patches them with `ackit/upk.py` and `ackit/lzo.py` (no external
|
|
tools). Soundbanks inside them: `banks.md`.
|
|
|
|
## Summary (file start, little-endian)
|
|
|
|
| field | notes |
|
|
|-------|-------|
|
|
| u32 tag `0x9E2A83C1`, u16 version 805, u16 licensee 101, i32 TotalHeaderSize | |
|
|
| FString FolderName | i32 length incl. NUL, then bytes (`None`) |
|
|
| 12 x i32 | PackageFlags, NameCount, NameOffset, ExportCount, ExportOffset, ImportCount, ImportOffset, DependsOffset, ImportExportGuidsOffset, ImportGuidsCount, ExportGuidsCount, ThumbnailTableOffset |
|
|
| 16 bytes Guid | |
|
|
| i32 N + N x {ExportCount, NameCount, NetObjectCount} | generations (N = 1) |
|
|
| u32 EngineVersion 7748, u32 CookerVersion, u32 CompressionFlags | 2 = LZO, 0 = none |
|
|
| i32 C + C x {i32 UncompressedOffset, UncompressedSize, CompressedOffset, CompressedSize} | chunk table |
|
|
| u32 PackageSource, ... | not used by ackit |
|
|
|
|
All offsets in the summary (names, exports, ...) refer to the **uncompressed** package. The uncompressed
|
|
package = the summary without the chunk table, followed by the chunks' data at their UncompressedOffset
|
|
(the first chunk starts right after the summary, at NameOffset).
|
|
|
|
## Name, import and export tables (uncompressed package, at NameOffset / ImportOffset / ExportOffset)
|
|
- Name: FString (positive length: ANSI incl. NUL; negative: UTF-16) + u64 flags.
|
|
- Import (28 bytes): ClassPackage (name index, number), ClassName (index, number), i32 OuterIndex,
|
|
ObjectName (index, number).
|
|
- Export: i32 ClassIndex, SuperIndex, OuterIndex, ObjectName (index, number), ArchetypeIndex, **one extra u32
|
|
(licensee 101)**, u64 ObjectFlags, i32 SerialSize, SerialOffset, u32 ExportFlags, i32 count + count x i32
|
|
(net objects), 16-byte guid, u32 package flags: 72 bytes when the net-object list is empty. Checked on every
|
|
export of the game. A name with number n is shown as `NAME_<n-1>` (e.g. the speech event `..._04`): such full
|
|
names exist only in the export table. `Package.tables()` returns names, imports and exports.
|
|
|
|
## Compressed chunks
|
|
Each chunk (at CompressedOffset): `u32 tag, u32 block size (0x20000), u32 compressed sum, u32 uncompressed
|
|
sum`, then one `{u32 compressed size, u32 uncompressed size}` per block, then the blocks. Every block is an
|
|
independent LZO1X stream. In all game packages the chunks follow each other without gaps, start right
|
|
after the summary and end at the end of the file (checked for every package).
|
|
|
|
## How ackit reads and writes them
|
|
- Positions of sounds are offsets in the uncompressed package, the same as in a decompressed copy
|
|
(e.g. Gildor's decompress.exe output). Reading decompresses only the blocks that are needed.
|
|
- Writing (`Package.patched`): every changed block is stored again as a single LZO literal run (a valid
|
|
LZO1X stream without matches, `lzo.compress_stored`), the chunk headers of the changed chunks and the
|
|
CompressedOffset/CompressedSize of all following chunks are updated; every other byte is copied, so a
|
|
package without changes is rebuilt byte-for-byte. Replacements never change the uncompressed layout
|
|
(sounds are replaced in place), so names, exports and their offsets stay valid.
|