Parse mobile inventory contents as client InstanceInfo

The inventory read when mobile_has_content is set overwrote one
attribute per field, never loaded its power action list and had no
save or JSON methods. InstanceInfo follows InstanceInfo_ctorFromStream
(sb.exe 0x674810); field names come from InstanceInfo::ParseKeyword
(0x66FA60). Power action entries (0x54F680) keep the item power action
id/arguments layout; their remaining fields are unknownN.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MagicBotandClaude Opus 5.5 committed 2026-10-24 10:57:11 -04:00
1 parent cf976a9363
commit 90656a4ab5
2 files changed
+276 -69

No files matched your search

@@ -0,0 +1,273 @@
# • ▌ ▄ ·. ▄▄▄▄· ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄▄▄ ▄▄▄ ▄▄▄·▄▄▄
# ·██ ▐███▪▐█ ▀█▪ ▀▄.▀·██▪ ██ ██ •██ ▪ ▀▄ █· ▐█ ▄█▀▄ █·▪
# ▐█ ▌▐▌▐█·▐█▀▀█▄ ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄ ██▀·▐▀▀▄ ▄█▀▄
# ██ ██▌▐█▌██▄▪▐█ ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌ ▐█▪·•▐█•█▌▐█▌.▐▌
# ▀▀ █▪▀▀▀·▀▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ .▀ .▀ ▀ ▀█▄▀▪
# Magicbane Emulator Project © 2013 - 2022
# www.magicbane.com
from collections import OrderedDict
from arcane.enums.hashes import hash_to_string, string_to_hash
from arcane.util import ResStream
class PowerActionInfo:
def load_binary(self, stream: ResStream):
self.power_action_id = stream.read_dword()
self.power_action_arguments = [
stream.read_long(),
stream.read_long(),
]
self.power_action_has_cache_id = stream.read_bool()
if self.power_action_has_cache_id:
self.power_action_cache_id = stream.read_qword()
else:
self.power_action_unknown1 = stream.read_dword()
self.power_action_unknown2 = stream.read_string()
self.power_action_unknown3 = stream.read_float()
def save_binary(self, stream: ResStream):
stream.write_dword(self.power_action_id)
stream.write_long(self.power_action_arguments[0])
stream.write_long(self.power_action_arguments[1])
stream.write_bool(self.power_action_has_cache_id)
if self.power_action_has_cache_id:
stream.write_qword(self.power_action_cache_id)
else:
stream.write_dword(self.power_action_unknown1)
stream.write_string(self.power_action_unknown2)
stream.write_float(self.power_action_unknown3)
def save_json(self):
data = OrderedDict()
data['power_action_id'] = hash_to_string(self.power_action_id)
data['power_action_arguments'] = self.power_action_arguments
data['power_action_has_cache_id'] = self.power_action_has_cache_id
if self.power_action_has_cache_id:
data['power_action_cache_id'] = self.power_action_cache_id
else:
data['power_action_unknown1'] = self.power_action_unknown1
data['power_action_unknown2'] = self.power_action_unknown2
data['power_action_unknown3'] = self.power_action_unknown3
return data
def load_json(self, data):
self.power_action_id = string_to_hash(data['power_action_id'])
self.power_action_arguments = data['power_action_arguments']
self.power_action_has_cache_id = data['power_action_has_cache_id']
if self.power_action_has_cache_id:
self.power_action_cache_id = data['power_action_cache_id']
else:
self.power_action_unknown1 = data['power_action_unknown1']
self.power_action_unknown2 = data['power_action_unknown2']
self.power_action_unknown3 = data['power_action_unknown3']
class InstanceInfo:
def load_binary(self, stream: ResStream):
self.instance_template_id = stream.read_qword()
self.instance_id = stream.read_qword()
self.instance_location = stream.read_tuple()
self.instance_scale = stream.read_tuple()
self.instance_orientation = [stream.read_float() for _ in range(4)]
self.instance_container_id = stream.read_qword()
self.instance_level_number = stream.read_long()
self.instance_room_number = stream.read_long()
self.instance_color = stream.read_long()
self.instance_gravity = stream.read_bool()
self.instance_name_override = stream.read_string()
self.instance_has_health = stream.read_bool()
if self.instance_has_health:
self.instance_health_full = stream.read_float()
self.instance_health_current = stream.read_float()
self.instance_has_item_data = stream.read_bool()
if self.instance_has_item_data:
self.instance_maker_id = stream.read_qword()
self.instance_value = stream.read_long()
self.instance_mod_value = stream.read_long()
self.instance_drop_chance = stream.read_float()
self.instance_flags = stream.read_dword()
self.instance_charges = stream.read_long()
num_power_actions = stream.read_dword()
self.instance_power_actions = [PowerActionInfo() for _ in range(num_power_actions)]
for power_action in self.instance_power_actions:
power_action.load_binary(stream)
self.instance_stack_count = stream.read_long()
self.instance_has_key_data = stream.read_bool()
if self.instance_has_key_data:
self.instance_key_ids = [stream.read_qword() for _ in range(3)]
self.instance_key_setting = stream.read_long()
self.instance_has_deed_data = stream.read_bool()
if self.instance_has_deed_data:
self.instance_deed_id = stream.read_qword()
self.instance_deed_rank = stream.read_long()
self.instance_has_platform_data = stream.read_bool()
if self.instance_has_platform_data:
self.instance_has_platform = stream.read_bool()
self.instance_platform_height = stream.read_float()
self.instance_damage_flags = stream.read_dword()
self.instance_map_type = stream.read_long()
self.instance_has_guild_crest = stream.read_bool()
if self.instance_has_guild_crest:
self.instance_guild_crest = [stream.read_long() for _ in range(5)]
self.instance_has_nation_crest = stream.read_bool()
if self.instance_has_nation_crest:
self.instance_nation_crest = [stream.read_long() for _ in range(5)]
self.instance_is_descriptor = stream.read_bool()
def save_binary(self, stream: ResStream):
stream.write_qword(self.instance_template_id)
stream.write_qword(self.instance_id)
stream.write_tuple(self.instance_location)
stream.write_tuple(self.instance_scale)
for value in self.instance_orientation:
stream.write_float(value)
stream.write_qword(self.instance_container_id)
stream.write_long(self.instance_level_number)
stream.write_long(self.instance_room_number)
stream.write_long(self.instance_color)
stream.write_bool(self.instance_gravity)
stream.write_string(self.instance_name_override)
stream.write_bool(self.instance_has_health)
if self.instance_has_health:
stream.write_float(self.instance_health_full)
stream.write_float(self.instance_health_current)
stream.write_bool(self.instance_has_item_data)
if self.instance_has_item_data:
stream.write_qword(self.instance_maker_id)
stream.write_long(self.instance_value)
stream.write_long(self.instance_mod_value)
stream.write_float(self.instance_drop_chance)
stream.write_dword(self.instance_flags)
stream.write_long(self.instance_charges)
stream.write_dword(len(self.instance_power_actions))
for power_action in self.instance_power_actions:
power_action.save_binary(stream)
stream.write_long(self.instance_stack_count)
stream.write_bool(self.instance_has_key_data)
if self.instance_has_key_data:
for key_id in self.instance_key_ids:
stream.write_qword(key_id)
stream.write_long(self.instance_key_setting)
stream.write_bool(self.instance_has_deed_data)
if self.instance_has_deed_data:
stream.write_qword(self.instance_deed_id)
stream.write_long(self.instance_deed_rank)
stream.write_bool(self.instance_has_platform_data)
if self.instance_has_platform_data:
stream.write_bool(self.instance_has_platform)
stream.write_float(self.instance_platform_height)
stream.write_dword(self.instance_damage_flags)
stream.write_long(self.instance_map_type)
stream.write_bool(self.instance_has_guild_crest)
if self.instance_has_guild_crest:
for value in self.instance_guild_crest:
stream.write_long(value)
stream.write_bool(self.instance_has_nation_crest)
if self.instance_has_nation_crest:
for value in self.instance_nation_crest:
stream.write_long(value)
stream.write_bool(self.instance_is_descriptor)
def save_json(self):
data = OrderedDict()
data['instance_template_id'] = self.instance_template_id
data['instance_id'] = self.instance_id
data['instance_location'] = self.instance_location
data['instance_scale'] = self.instance_scale
data['instance_orientation'] = self.instance_orientation
data['instance_container_id'] = self.instance_container_id
data['instance_level_number'] = self.instance_level_number
data['instance_room_number'] = self.instance_room_number
data['instance_color'] = self.instance_color
data['instance_gravity'] = self.instance_gravity
data['instance_name_override'] = self.instance_name_override
data['instance_has_health'] = self.instance_has_health
if self.instance_has_health:
data['instance_health_full'] = self.instance_health_full
data['instance_health_current'] = self.instance_health_current
data['instance_has_item_data'] = self.instance_has_item_data
if self.instance_has_item_data:
data['instance_maker_id'] = self.instance_maker_id
data['instance_value'] = self.instance_value
data['instance_mod_value'] = self.instance_mod_value
data['instance_drop_chance'] = self.instance_drop_chance
data['instance_flags'] = self.instance_flags
data['instance_charges'] = self.instance_charges
data['instance_power_actions'] = [action.save_json() for action in self.instance_power_actions]
data['instance_stack_count'] = self.instance_stack_count
data['instance_has_key_data'] = self.instance_has_key_data
if self.instance_has_key_data:
data['instance_key_ids'] = self.instance_key_ids
data['instance_key_setting'] = self.instance_key_setting
data['instance_has_deed_data'] = self.instance_has_deed_data
if self.instance_has_deed_data:
data['instance_deed_id'] = self.instance_deed_id
data['instance_deed_rank'] = self.instance_deed_rank
data['instance_has_platform_data'] = self.instance_has_platform_data
if self.instance_has_platform_data:
data['instance_has_platform'] = self.instance_has_platform
data['instance_platform_height'] = self.instance_platform_height
data['instance_damage_flags'] = self.instance_damage_flags
data['instance_map_type'] = self.instance_map_type
data['instance_has_guild_crest'] = self.instance_has_guild_crest
if self.instance_has_guild_crest:
data['instance_guild_crest'] = self.instance_guild_crest
data['instance_has_nation_crest'] = self.instance_has_nation_crest
if self.instance_has_nation_crest:
data['instance_nation_crest'] = self.instance_nation_crest
data['instance_is_descriptor'] = self.instance_is_descriptor
return data
def load_json(self, data):
self.instance_template_id = data['instance_template_id']
self.instance_id = data['instance_id']
self.instance_location = data['instance_location']
self.instance_scale = data['instance_scale']
self.instance_orientation = data['instance_orientation']
self.instance_container_id = data['instance_container_id']
self.instance_level_number = data['instance_level_number']
self.instance_room_number = data['instance_room_number']
self.instance_color = data['instance_color']
self.instance_gravity = data['instance_gravity']
self.instance_name_override = data['instance_name_override']
self.instance_has_health = data['instance_has_health']
if self.instance_has_health:
self.instance_health_full = data['instance_health_full']
self.instance_health_current = data['instance_health_current']
self.instance_has_item_data = data['instance_has_item_data']
if self.instance_has_item_data:
self.instance_maker_id = data['instance_maker_id']
self.instance_value = data['instance_value']
self.instance_mod_value = data['instance_mod_value']
self.instance_drop_chance = data['instance_drop_chance']
self.instance_flags = data['instance_flags']
self.instance_charges = data['instance_charges']
self.instance_power_actions = []
for action_data in data['instance_power_actions']:
action = PowerActionInfo()
action.load_json(action_data)
self.instance_power_actions.append(action)
self.instance_stack_count = data['instance_stack_count']
self.instance_has_key_data = data['instance_has_key_data']
if self.instance_has_key_data:
self.instance_key_ids = data['instance_key_ids']
self.instance_key_setting = data['instance_key_setting']
self.instance_has_deed_data = data['instance_has_deed_data']
if self.instance_has_deed_data:
self.instance_deed_id = data['instance_deed_id']
self.instance_deed_rank = data['instance_deed_rank']
self.instance_has_platform_data = data['instance_has_platform_data']
if self.instance_has_platform_data:
self.instance_has_platform = data['instance_has_platform']
self.instance_platform_height = data['instance_platform_height']
self.instance_damage_flags = data['instance_damage_flags']
self.instance_map_type = data['instance_map_type']
self.instance_has_guild_crest = data['instance_has_guild_crest']
if self.instance_has_guild_crest:
self.instance_guild_crest = data['instance_guild_crest']
self.instance_has_nation_crest = data['instance_has_nation_crest']
if self.instance_has_nation_crest:
self.instance_nation_crest = data['instance_nation_crest']
self.instance_is_descriptor = data['instance_is_descriptor']
@@ -14,73 +14,7 @@ from arcane.objects.ArcRune import Group
from arcane.objects.common.Inventory import Inventory
from arcane.util import ResStream
from .ArcBasicZoneObjectInfo import ArcBasicZoneObjectInfo
class Unknown:
def load_binary(self, stream: ResStream):
self.unknown_u = stream.read_dword()
self.unknown_u = stream.read_dword()
self.unknown_u = stream.read_dword()
self.unknown_check = stream.read_bool()
if self.unknown_check:
self.unknown_u = stream.read_qword()
else:
self.unknown_u = stream.read_dword()
self.unknown_u = stream.read_string()
self.unknown_u = stream.read_float()
class InventoryContents:
def load_binary(self, stream: ResStream):
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_tuple()
self.mobile_inventory_u = stream.read_tuple()
self.mobile_inventory_u = [stream.read_float() for _ in range(4)]
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_u = stream.read_bool()
self.mobile_inventory_u = stream.read_string()
self.mobile_inventory_check = stream.read_bool()
if self.mobile_inventory_check:
self.mobile_inventory_u = stream.read_float()
self.mobile_inventory_u = stream.read_float()
self.mobile_inventory_check = stream.read_bool()
if self.mobile_inventory_check:
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_u = stream.read_float()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_u = stream.read_dword()
num = stream.read_dword()
self.mobile_inventory_u = [Unknown() for _ in range(num)]
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_check = stream.read_bool()
if self.mobile_inventory_check:
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_check = stream.read_bool()
if self.mobile_inventory_check:
self.mobile_inventory_u = stream.read_qword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_check = stream.read_bool()
if self.mobile_inventory_check:
self.mobile_inventory_u = stream.read_bool()
self.mobile_inventory_u = stream.read_float()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_u = stream.read_dword()
self.mobile_inventory_check = stream.read_bool()
if self.mobile_inventory_check:
self.mobile_inventory_u = [stream.read_dword() for _ in range(5)]
self.mobile_inventory_check = stream.read_bool()
if self.mobile_inventory_check:
self.mobile_inventory_u = [stream.read_dword() for _ in range(5)]
self.mobile_inventory_u = stream.read_bool()
from .InstanceInfo import InstanceInfo
class MobileInfo:
@@ -115,7 +49,7 @@ class MobileInfo:
self.mobile_has_content = stream.read_bool()
if self.mobile_has_content:
num_contents = stream.read_dword()
self.mobile_inventory_contents = [InventoryContents() for _ in range(num_contents)]
self.mobile_inventory_contents = [InstanceInfo() for _ in range(num_contents)]
for content in self.mobile_inventory_contents:
content.load_binary(stream)
num_equipments = stream.read_dword()
@@ -306,7 +240,7 @@ class MobileInfo:
if self.mobile_has_content:
self.mobile_inventory_contents = []
for content_data in data['mobile_inventory_contents']:
content = InventoryContents()
content = InstanceInfo()
content.load_json(content_data)
self.mobile_inventory_contents.append(content)
self.mobile_equipment = data['mobile_equipment']