Bug: to-zen panics with "index out of bounds" on legacy assets re-serialized by UAssetAPI, even with zero content changes
Summary
retoc to-zen panics with index out of bounds: the len is 22 but the index is 96 inside legacy_asset.rs when converting a .uasset/.uexp pair that was extracted from a Zen container via UAssetGUI/UAssetAPI (not via retoc to-legacy), then written back out by UAssetAPI without any content changes at all. This rules out any specific edit as the cause — the panic appears to stem from a structural difference between what UAssetAPI writes for a legacy asset and what retoc's legacy parser expects.
Environment
- Game: "Deluded" (early access, cooked with Unreal Engine 5.0 Early Access)
- retoc version tested: both the latest GitHub release binary, and a fresh build from
main (commit as of 2026-08-21) — same panic in both, only the line number shifted (1267 in release build → 1281 in fresh main build)
- Container header version override used:
--override-container-header-version Initial
--version UE5_0
- Asset was originally extracted from
.utoc/.ucas (Zen format) using UAssetGUI v1.1.0 (UAssetAPI commit 8b8687a), which has native Zen container reading support, opening the .utoc directly (this bypasses retoc to-legacy, which fails on this same container with an unrelated error — see "Additional context" below).
Steps to reproduce
- Open the game's
.utoc container directly in UAssetGUI v1.1.0 (native Zen support, no retoc to-legacy involved at this step).
- Use
Utils -> Extract all in UAssetGUI to dump all packages as legacy .uasset/.uexp pairs.
- Take a single extracted pair (e.g.
DI_TheVialOfThirst.uasset / .uexp, a Dialogue-type asset), open it via UAssetAPI in a small C# program:
var asset = new UAsset(path, EngineVersion.VER_UE5_0EA);
// no property edits made at all
asset.PackageFlags |= EPackageFlags.PKG_Cooked; // see note below
asset.Write(outPath);
- Place the resulting
.uasset/.uexp under <root>/SRTE/Content/SRTP/Quests/Prologue/TheVialOfThirst/.
- Run:
retoc.exe --override-container-header-version Initial to-zen "<root>" "<output>" --version UE5_0
Actual result
[00:00:00] ---------------------------------------- 0/1 SRTE/Content/SRTP/Quests/Prologue/TheVialOfThirst/DI
thread '<unnamed>' (26036) panicked at retoc\src\legacy_asset.rs:1281:51:
index out of bounds: the len is 22 but the index is 96
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Expected result
The unmodified round-tripped asset should convert back to Zen format successfully (or at least fail with a graceful error rather than a panic).
Key diagnostic finding
I first suspected this was caused by editing TextPropertyData.CultureInvariantString (localization/translation work — the replacement string differs in byte length from the original). However, I isolated the issue by running the exact same asset through UAssetAPI with zero content changes (just new UAsset() immediately followed by Write(), plus the PKG_Cooked flag fix below) — and got the identical panic (len is 22, index is 96), ruling out string length / content changes as the cause. This points to a structural/serialization mismatch between UAssetAPI's legacy writer and retoc's legacy parser (legacy_asset.rs), rather than anything specific to my edits.
Additional context: PKG_Cooked flag
Separately, I noticed that .uasset files produced by UAssetAPI's Write() (via asset.Write(path), no explicit flag manipulation) do not retain the PKG_Cooked package flag, causing:
Error: Detected absent PKG_Cooked flag in legacy package summary. Uncooked assets cannot be converted to Zen. Are you sure the asset has been Cooked?
I worked around this by explicitly doing asset.PackageFlags |= EPackageFlags.PKG_Cooked; before Write(). This may be a separate bug in UAssetAPI (not retoc), but I'm noting it here since it's part of the reproduction chain and someone else hitting the same panic will likely hit this first.
Also worth noting: retoc to-legacy fails on the original Zen container
For context on why I used UAssetGUI's native Zen reader instead of retoc to-legacy in step 1: running retoc to-legacy directly on this game's original .utoc/.ucas fails partway through with:
Failed to convert <path>.uasset: container "SRTE-WindowsNoEditor" does not contain FIoChunkId { chunk_id: "000000000000000000000007", chunk_type: LoaderInitialLoadMeta }
This happens because the game's global.utoc is unusually minimal (only 3 chunks, container_flags: 0x0, no compression/indexing). This may or may not be related to the to-zen panic above, but I'm including it in case the two are connected (e.g. both stemming from how this game's atypical global container is handled).
Files
I can provide the exact DI_TheVialOfThirst.uasset/.uexp pair (both the untouched round-trip version and the original Zen-extracted version) if useful for debugging — let me know the best way to attach them.
Bug:
to-zenpanics with "index out of bounds" on legacy assets re-serialized by UAssetAPI, even with zero content changesSummary
retoc to-zenpanics withindex out of bounds: the len is 22 but the index is 96insidelegacy_asset.rswhen converting a.uasset/.uexppair that was extracted from a Zen container via UAssetGUI/UAssetAPI (not viaretoc to-legacy), then written back out by UAssetAPI without any content changes at all. This rules out any specific edit as the cause — the panic appears to stem from a structural difference between what UAssetAPI writes for a legacy asset and what retoc's legacy parser expects.Environment
main(commit as of 2026-08-21) — same panic in both, only the line number shifted (1267 in release build → 1281 in freshmainbuild)--override-container-header-version Initial--version UE5_0.utoc/.ucas(Zen format) using UAssetGUI v1.1.0 (UAssetAPI commit8b8687a), which has native Zen container reading support, opening the.utocdirectly (this bypassesretoc to-legacy, which fails on this same container with an unrelated error — see "Additional context" below).Steps to reproduce
.utoccontainer directly in UAssetGUI v1.1.0 (native Zen support, noretoc to-legacyinvolved at this step).Utils -> Extract allin UAssetGUI to dump all packages as legacy.uasset/.uexppairs.DI_TheVialOfThirst.uasset/.uexp, aDialogue-type asset), open it via UAssetAPI in a small C# program:.uasset/.uexpunder<root>/SRTE/Content/SRTP/Quests/Prologue/TheVialOfThirst/.Actual result
Expected result
The unmodified round-tripped asset should convert back to Zen format successfully (or at least fail with a graceful error rather than a panic).
Key diagnostic finding
I first suspected this was caused by editing
TextPropertyData.CultureInvariantString(localization/translation work — the replacement string differs in byte length from the original). However, I isolated the issue by running the exact same asset through UAssetAPI with zero content changes (justnew UAsset()immediately followed byWrite(), plus thePKG_Cookedflag fix below) — and got the identical panic (len is 22, index is 96), ruling out string length / content changes as the cause. This points to a structural/serialization mismatch between UAssetAPI's legacy writer and retoc's legacy parser (legacy_asset.rs), rather than anything specific to my edits.Additional context:
PKG_CookedflagSeparately, I noticed that
.uassetfiles produced by UAssetAPI'sWrite()(viaasset.Write(path), no explicit flag manipulation) do not retain thePKG_Cookedpackage flag, causing:I worked around this by explicitly doing
asset.PackageFlags |= EPackageFlags.PKG_Cooked;beforeWrite(). This may be a separate bug in UAssetAPI (not retoc), but I'm noting it here since it's part of the reproduction chain and someone else hitting the same panic will likely hit this first.Also worth noting:
retoc to-legacyfails on the original Zen containerFor context on why I used UAssetGUI's native Zen reader instead of
retoc to-legacyin step 1: runningretoc to-legacydirectly on this game's original.utoc/.ucasfails partway through with:This happens because the game's
global.utocis unusually minimal (only 3 chunks,container_flags: 0x0, no compression/indexing). This may or may not be related to theto-zenpanic above, but I'm including it in case the two are connected (e.g. both stemming from how this game's atypical global container is handled).Files
I can provide the exact
DI_TheVialOfThirst.uasset/.uexppair (both the untouched round-trip version and the original Zen-extracted version) if useful for debugging — let me know the best way to attach them.