Bug
The PSSI (Song Structure) tag parser in anlz/structs.py uses Const(24, Int32ub) as the first field of the tag content:
AnlzTagSongStructure = Struct(
"len_entry_bytes" / Const(24, Int32ub),
"len_entries" / Int16ub,
...
)
This interprets bytes 0-3 of the tag content as a single 32-bit constant 0x00000018. However, the actual binary layout is two separate 16-bit fields:
| Offset |
Size |
Field |
| 0-1 |
Int16ub |
version (0 or 1) |
| 2-3 |
Int16ub |
entry_size (24 = 0x0018) |
| 4-5 |
Int16ub |
entry_count |
| 6-7 |
Int16ub |
mood |
For version=0, bytes 0-3 are 00 00 00 18, which happens to match Const(24, Int32ub). But for version=1, bytes 0-3 would be 00 01 00 18 = 0x00010018, causing a ConstError.
Hardware devices that parse ANLZ files validate version > 1 and reject with an "unsupported version" error, confirming that both version 0 and version 1 are valid tag versions.
Suggested Fix
AnlzTagSongStructure = Struct(
"version" / Int16ub, # 0 or 1
"len_entry_bytes" / Const(24, Int16ub), # always 0x0018
"len_entries" / Int16ub,
...
)
The XOR deobfuscation mask start index also depends on the version field: mask_start = version % 19.
Additional context
The XOR deobfuscation mask is derived from the ASCII string "Kanzen-niRikaishita" (19 bytes). Each byte of the base mask is seed_char + 0x80, and entry_count is added per-byte at runtime. This matches your existing static mask + entry_count formulation — just a different (equivalent) way to express the same computation. Documenting the seed string may be useful for understanding future format changes.
Bug
The PSSI (Song Structure) tag parser in
anlz/structs.pyusesConst(24, Int32ub)as the first field of the tag content:This interprets bytes 0-3 of the tag content as a single 32-bit constant
0x00000018. However, the actual binary layout is two separate 16-bit fields:For version=0, bytes 0-3 are
00 00 00 18, which happens to matchConst(24, Int32ub). But for version=1, bytes 0-3 would be00 01 00 18=0x00010018, causing aConstError.Hardware devices that parse ANLZ files validate
version > 1and reject with an "unsupported version" error, confirming that both version 0 and version 1 are valid tag versions.Suggested Fix
The XOR deobfuscation mask start index also depends on the version field:
mask_start = version % 19.Additional context
The XOR deobfuscation mask is derived from the ASCII string
"Kanzen-niRikaishita"(19 bytes). Each byte of the base mask isseed_char + 0x80, andentry_countis added per-byte at runtime. This matches your existing static mask + entry_count formulation — just a different (equivalent) way to express the same computation. Documenting the seed string may be useful for understanding future format changes.