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>
218 lines
9.5 KiB
Markdown
218 lines
9.5 KiB
Markdown
```
|
|
• ▌ ▄ ·. ▄▄▄▄· ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄▄▄ ▄▄▄ ▄▄▄·▄▄▄
|
|
·██ ▐███▪▐█ ▀█▪ ▀▄.▀·██▪ ██ ██ •██ ▪ ▀▄ █· ▐█ ▄█▀▄ █·▪
|
|
▐█ ▌▐▌▐█·▐█▀▀█▄ ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄ ██▀·▐▀▀▄ ▄█▀▄
|
|
██ ██▌▐█▌██▄▪▐█ ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌ ▐█▪·•▐█•█▌▐█▌.▐▌
|
|
▀▀ █▪▀▀▀·▀▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ .▀ .▀ ▀ ▀█▄▀▪
|
|
Magicbane Emulator Project © 2013 - 2022
|
|
www.magicbane.com
|
|
```
|
|
|
|
# COBJECTS JSON format changes
|
|
|
|
This note covers the COBJECTS JSON produced by `unpack cobjects` after the
|
|
client parser audit on branch `mbeditor-update`. It is written for anything
|
|
that reads or writes that JSON: the server COBJECTS loader, database imports,
|
|
the GUI, and hand edits.
|
|
|
|
The binary `.cache` format did not change. Every resource in
|
|
`cachefiles/CObjects.cache` still round-trips byte for byte.
|
|
|
|
## Summary
|
|
|
|
| Change | Effect on the JSON | Action for readers |
|
|
|---|---|---|
|
|
| Signed 32-bit fields | Values that were `4294967295` are now `-1`, `4294967286` is now `-10`, and so on | Accept negative integers |
|
|
| Grant attribute and power keys | First element of each pair is now a name string when the hash is known | Accept a string or an integer; convert names with `string_to_hash` |
|
|
|
|
Old JSON dumps still pack. `4294967295` and `-1` are the same four bytes, and
|
|
`ResStream.write_long` accepts either form: values from `2147483648` to
|
|
`4294967295` are converted to their signed equivalent before packing. Integer
|
|
grant keys in old dumps also pack unchanged. Re-running `unpack cobjects` is
|
|
only needed to see the new signed values and grant key names in the JSON.
|
|
|
|
## 1. Signed 32-bit fields
|
|
|
|
The client reads 4-byte integers through two entry points. Values read with
|
|
`ResStream_ReadInt32` (sb.exe `0x7E1700`) are now loaded with
|
|
`ResStream.read_long` and written with `write_long`. Fields the client reads
|
|
through the folded ATL wrapper (`0x54C700`, used for hash and enum fields), list
|
|
counts, and the item flag words (`0x7E1720`, not yet classified) stay unsigned.
|
|
|
|
Fields marked *(enum)* are written to JSON as a name, so their JSON text does
|
|
not change.
|
|
|
|
### ArcObj (all object types)
|
|
|
|
| JSON path | Notes |
|
|
|---|---|
|
|
| `obj_sound_events[].sound_type` | *(enum)* |
|
|
| `obj_arc_hardpoint_list[][0]`, `[][1]` | the two integers before the 10 floats |
|
|
| `obj_sparse_data.<tag>` and `rune_sparse_data.<tag>` for `VAL_LONG` tags | client type `LoadSaveBehavior<long>`; tags: `ADMIN_SNOOPLEVEL`, `GEN_TABLE_ID`, `ADMIN_REMOVEEQUIPMENTEFFECTSLEVEL`, `trainerSkillCost`, `ADMIN_TOKENREADACCESSLEVEL`, `MAX_POWER_LEVEL`, `ADMIN_ACCESSLEVEL`, `banker`, `expLost`, `ADMIN_CHANNELACCESSLEVEL`, `ADMIN_TOKENWRITEACCESSLEVEL`, `ADMIN_REMOVEPOWEREFFECTSLEVEL` |
|
|
|
|
### ArcCombatObj (static, item, rune, character and subclasses)
|
|
|
|
| JSON path | Notes |
|
|
|---|---|
|
|
| `combat_powers[][1]` | value; `[0]` stays unsigned |
|
|
|
|
### ArcStaticObject
|
|
|
|
| JSON path |
|
|
|---|
|
|
| `static_sound.sound_chance`, `static_sound.sound_reserved` |
|
|
|
|
### ArcItem (also rune, container, deed, key)
|
|
|
|
| JSON path | Notes |
|
|
|---|---|
|
|
| `item_type` | *(enum)* |
|
|
| `item_eq_slots_value` | |
|
|
| `item_eq_slots_or`, `item_eq_slots_and` | *(bitmask written as names)* |
|
|
| `item_value`, `item_wt` | |
|
|
| `item_parry_anim_id` | |
|
|
| `item_defense_rating` | armor only |
|
|
| `item_initial_charges` | |
|
|
| `item_level_req`, `item_rank_req` | |
|
|
| `item_sex_req` | *(enum)* |
|
|
| `item_sheath_slot` | *(enum)* |
|
|
| `item_bane_rank` | |
|
|
| `item_user_power_action[].arguments[]` | `power` stays a hash |
|
|
| `item_skill_req[].skill_level` | |
|
|
| `item_attr_req[].attr_value` | |
|
|
| `item_resource_costs[].resource_value` | |
|
|
| `item_offering_info[].offering_value`, `item_offering_adjustments[].offering_value` | |
|
|
| `item_power_action[].power_actions[].power_arguments[]` | |
|
|
| `item_power_grant[]` | see section 2 |
|
|
| `item_weapon.weapon_combat_idle_anim` | |
|
|
| `item_weapon.weapon_damage[].damage_type` | *(enum)* |
|
|
| `item_weapon.weapon_damage[].damage_min`, `damage_max` | |
|
|
| `item_weapon.weapon_attack_anim_right[][]`, `weapon_attack_anim_left[][]` | |
|
|
|
|
### ArcRune
|
|
|
|
| JSON path | Notes |
|
|
|---|---|
|
|
| `rune_type` | *(enum)* |
|
|
| `rune_sex` | *(enum)* |
|
|
| `rune_creation_cost`, `rune_rank`, `rune_pracs_per_level` | |
|
|
| `rune_health`, `rune_mana`, `rune_stamina` | |
|
|
| `rune_attack`, `rune_defense`, `rune_level` | |
|
|
| `rune_enemy_gender[]` | |
|
|
| `rune_skill_adj[].skill_adjusts[][]` | `skill_type` stays a hash |
|
|
| `rune_attr_adj[].attr_value`, `rune_max_attr_adj[].attr_value` | seen as `-10` and `-5` in the sample cache |
|
|
| `rune_naturalattacks[][2]`, `[3]`, `[6]`, `[7]`, `[8]`, `[9]` | the integer slots; floats and the projectile id are unchanged |
|
|
| `rune_skill_grant[]` | see section 2 |
|
|
|
|
### ArcDoorObject (also doors inside structures)
|
|
|
|
| JSON path | Notes |
|
|
|---|---|
|
|
| `door_type`, `door_movement_axis`, `door_swing_direction` | *(enum)* |
|
|
| `door_locked` | |
|
|
|
|
### ArcDungeonUnitObject, ArcDungeonExitObject, ArcDungeonStairObject
|
|
|
|
| JSON path |
|
|
|---|
|
|
| `dungeon_connectable_edges[]` |
|
|
|
|
### ArcContainerObject
|
|
|
|
| JSON path |
|
|
|---|
|
|
| `container_open`, `container_locked`, `container_locked_rank`, `container_barred`, `container_barred_rank`, `container_trapped`, `container_trappe_rank` |
|
|
|
|
`container_inventory_contents[]` follows the inventory rules below.
|
|
|
|
### ArcDeed
|
|
|
|
| JSON path |
|
|
|---|
|
|
| `deed_type`, `deed_employment`, `deed_start_rank`, `deed_name_lookup` |
|
|
|
|
### ArcKey
|
|
|
|
| JSON path |
|
|
|---|
|
|
| `key_setting` |
|
|
|
|
### Inventory entries (rune and container contents)
|
|
|
|
| JSON path |
|
|
|---|
|
|
| `table[1]` (booty table entries) |
|
|
|
|
### ArcCityAssetTemplate (also `template_embed_template`)
|
|
|
|
| JSON path | Notes |
|
|
|---|---|
|
|
| `template_max_ranks`, `template_start_rank`, `template_asset_type`, `template_trade_icon`, `template_landmark` | |
|
|
| `template_creator[]`, `template_terrain[]`, `template_valid_npc_type[]`, `template_valid_npc_cat[]` | |
|
|
| `template_cap_info[][0]`, `[][1]` | `[2]` is an ArcCacheID |
|
|
| `template_event_rules[][0]` | `[1]` stays unsigned |
|
|
| `template_resource_limit[][1]`, `[][2]` | `[0]` stays unsigned |
|
|
| `template_rank_info[].rank_hirelings`, `rank_shrines`, `rank_spires`, `rank_barracks`, `rank_rank`, `rank_health` | `rank_hirelings` and `rank_rank` are `-1` in many sample records |
|
|
| `template_rank_info[].rank_placement_limit[][1]` | `[0]` stays unsigned |
|
|
|
|
## 2. Grant keys
|
|
|
|
Power grants (`item_power_grant[]`) and skill grants (`rune_skill_grant[]`)
|
|
each carry three lists of `[key, value]` pairs. The client reads every pair as
|
|
an unsigned key and a signed value (sb.exe `0x6D7640`).
|
|
|
|
| JSON path | Before | After |
|
|
|---|---|---|
|
|
| `power_granted_skills[][0]`, `skill_granted_skills[][0]` | name string or integer | unchanged |
|
|
| `power_granted_attrs[][0]`, `skill_granted_attrs[][0]` | integer | name string when the hash is known, otherwise integer |
|
|
| `power_granted_powers[][0]`, `skill_granted_powers[][0]` | integer | name string when the hash is known, otherwise integer (example: `428458144` becomes `"HLR-021"`) |
|
|
| every `[][1]` in those lists | unsigned | signed |
|
|
| `power_value`, `skill_value` | unsigned | signed |
|
|
|
|
`power_monster_types` and `skill_monster_types` are unchanged.
|
|
|
|
A reader that needs the numeric key should run it through
|
|
`arcane.enums.hashes.string_to_hash`, which returns integers unchanged.
|
|
|
|
## Sample data impact
|
|
|
|
From `cachefiles/CObjects.cache`, comparing JSON from `master` with this branch:
|
|
|
|
| JSON path | Records changed | Example |
|
|
|---|---|---|
|
|
| `ASSET.template_rank_info[].rank_hirelings` (incl. embedded templates) | 539 | `4294967295` → `-1` |
|
|
| `ASSET.template_rank_info[].rank_rank` | 182 | `4294967295` → `-1` |
|
|
| `RUNE.item_power_grant[].power_granted_powers[][0]` | 100 | `428458144` → `"HLR-021"` |
|
|
| `RUNE.rune_attr_adj[].attr_value` | 17 | `4294967286` → `-10` |
|
|
| `RUNE.rune_max_attr_adj[].attr_value` | 12 | `4294967291` → `-5` |
|
|
|
|
Nothing else differs in that file.
|
|
|
|
## Related parser fixes on the same branch
|
|
|
|
These do not change the JSON layout but affect packing:
|
|
|
|
- Sparse `MERCHANT_DATA` now writes its last field as 4 bytes, matching the
|
|
client (sb.exe `0x4859B0`). Packing it previously corrupted the resource.
|
|
- `ArcContainerObject` packs again; `save_binary` used read calls and
|
|
`load_json` skipped the item base class.
|
|
- `ArcCityAssetTemplate` raises `NotImplementedError` when
|
|
`template_unknown_check1` or `template_unknown_check2` is true. The client
|
|
loads extra trigger-object data in that case, which this tool does not
|
|
support. No record in the sample cache sets either flag.
|
|
|
|
## Open items
|
|
|
|
- The signed/unsigned split depends on which client read entry point each
|
|
field uses. That split is marked `[MB] GUESS:` in IDA and *unverified* in
|
|
`MBDATA/docs/client-map.md`.
|
|
- `item_flags`, `item_use_flags`, structure floor/level/trigger numbers and
|
|
region content behavior/state are read through `0x7E1720`, which is not yet
|
|
classified. They stay unsigned.
|
|
- Region byte fields (`region_stairs`, `region_unknown2`, `region_unknown3`)
|
|
default to `0xFF` in the client and may be signed; they are still read as
|
|
unsigned bytes.
|
|
- `cachefiles/CObjects.cache` holds 286 resource ids twice (identical bytes).
|
|
`unpack` keeps one copy, so a repacked cache has 10,334 entries instead of
|
|
10,620.
|