Replaces the config target with wpak, which handles every known pak or a
single one named on the command line. Map icon .tga entries are stored
unencrypted (sb.exe 0xAACA60).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A .wpak is a zip whose entries are each Blowfish CFB-64 encrypted with
a zero IV and the client's hardcoded key (sb.exe 0x54DEC0). Pure Python
Blowfish, no new dependencies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client's use flag name table (sb.exe 0x16C9728) has None at bit 0,
so USE_SELF is bit 1 through EMPTY_NEWITEM at bit 4. Names in the JSON
were one bit low.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pack now writes <Name>.cache, or <Name>.cache.1, .2, ... if that file
already exists (replaces the fixed .cache.new name). Unpack writes each
target folder under ARCANE_DUMP the same way, e.g. COBJECTS.1. The
output folder is created only after the cache loads, so a missing cache
does not leave an empty numbered folder.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dumps made before the signed-read change store values like -1 as
4294967295. write_long now converts 2147483648..4294967295 to the
signed equivalent (same bytes), so existing community dumps still
pack. Verified: all 10332 files of an older dump repack identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
List every field now read signed, the grant key hashing, the effect on
the sample cache, compatibility with older dumps, related parser fixes
and open items, for readers of the unpacked JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Power and skill grants read every sub-array as (u32 key, int32 value)
(sb.exe 0x6D7640). Skill keys were already hashed; attr and power keys
now are too, and the values plus attribute/skill/resource amounts and
power action arguments are read signed. Shared pair read/write and key
hashing moved into helpers; load_json no longer mutates its input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add ResStream.read_long/write_long and use them for every CObjects
field the client loads with ResStream_ReadInt32 (sb.exe 0x7E1700)
as a value: rank info, attribute/skill/resource amounts, requirements,
door/deed/container/key fields, natural attacks, hardpoints, sound
event types and sparse VAL_LONG (LoadSaveBehavior<long>). Hash/enum
fields, list counts and flag words keep unsigned reads.
The binary is unchanged; JSON now shows e.g. -1 instead of 4294967295
(rank_hirelings, rank_rank) and -10 instead of 4294967286 (attr_adj).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
save_binary called stream.read_dword(value) instead of write_dword,
so packing any container raised a TypeError. load_json called
super().load_json() without data, so the item base never loaded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the bool after USE_TRIGGER / LOOT_TRIGGER is set, the client
(sb.exe 0xC46DE0) instantiates a trigger object by name and loads
extra data from the stream. The parser silently ignored the branch,
which would desync the rest of the template. Raise instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The client loads MerchantData as ArcCacheID + ArcCacheID + int32
(20 bytes, sb.exe 0x4859B0). save_binary wrote the final field as a
qword, corrupting any packed object carrying merchant sparse data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>