Compare commits
20
Commits
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
.idea/
|
||||||
|
cachefiles/
|
||||||
|
__pycache__/
|
||||||
|
*.wpak
|
||||||
|
*.wpak.*
|
||||||
|
*.old
|
||||||
|
ARCANE_DUMP/
|
||||||
@@ -18,6 +18,7 @@ Team Magicbane presents to the community technology allowing MagicBox administra
|
|||||||
to fully modify their game using nothing more than notepad.exe. <br>
|
to fully modify their game using nothing more than notepad.exe. <br>
|
||||||
|
|
||||||
- Tool directly converts Shadowbane .cache file data to and from a high fidelity pure JSON representation.
|
- Tool directly converts Shadowbane .cache file data to and from a high fidelity pure JSON representation.
|
||||||
|
- Shadowbane .wpak archives are decrypted to and from their plain text config files.
|
||||||
- Media such as textures and sound are converted in-situ.
|
- Media such as textures and sound are converted in-situ.
|
||||||
- Enumerations are used throughout hiding from the user ugly signed ulong values.
|
- Enumerations are used throughout hiding from the user ugly signed ulong values.
|
||||||
- Written in Python and open-source allowing modification and extension with ease.
|
- Written in Python and open-source allowing modification and extension with ease.
|
||||||
@@ -38,7 +39,8 @@ mbEditorPro requires pillow for image manipulation and tqdm for metrics.
|
|||||||
mbEditorPro.py unpack cobjects
|
mbEditorPro.py unpack cobjects
|
||||||
mbEditorPro.py unpack all
|
mbEditorPro.py unpack all
|
||||||
```
|
```
|
||||||
JSON output will be placed into the folder **ARCANE DUMP** <br>
|
JSON output will be placed into the folder **ARCANE_DUMP** <br>
|
||||||
|
Unpack only reads the plain-named cache files (e.g. CObjects.cache). If a target folder already exists, a numbered folder is created instead (e.g. COBJECTS.1) so earlier output is never overwritten. <br>
|
||||||
|
|
||||||
```
|
```
|
||||||
mbEditorPro.py pack textures
|
mbEditorPro.py pack textures
|
||||||
@@ -46,6 +48,21 @@ mbEditorPro.py pack all
|
|||||||
```
|
```
|
||||||
|
|
||||||
New binary cache file(s) will be built from the JSON and output into the current directory. <br>
|
New binary cache file(s) will be built from the JSON and output into the current directory. <br>
|
||||||
|
Pack only reads from the un-numbered folders (e.g. COBJECTS). If a cache file already exists, a numbered copy is written instead (e.g. CObjects.cache.1). <br>
|
||||||
|
|
||||||
|
### wpak files
|
||||||
|
|
||||||
|
```
|
||||||
|
mbEditorPro.py unpack wpak
|
||||||
|
mbEditorPro.py unpack wpak Config
|
||||||
|
mbEditorPro.py pack wpak
|
||||||
|
mbEditorPro.py pack wpak Config
|
||||||
|
```
|
||||||
|
|
||||||
|
A .wpak is a zip archive whose entries are each Blowfish encrypted (.tga icons are stored unencrypted).
|
||||||
|
Unpack decrypts each pak into **ARCANE_DUMP/<name>** (e.g. ARCANE_DUMP/Config), one file per entry.
|
||||||
|
Pack encrypts each folder back into <name>.wpak. Name a single pak to work on just that one.
|
||||||
|
The same numbering rules as the caches apply. <br>
|
||||||
|
|
||||||
### Limitations
|
### Limitations
|
||||||
|
|
||||||
@@ -63,10 +80,9 @@ The tool generating pure JSON has the following benefits:
|
|||||||
|
|
||||||
### Future
|
### Future
|
||||||
|
|
||||||
mbEditorPro will be the cornerstone of Magicbane's data project. The Team's goals are threefold:
|
mbEditorPro will be the cornerstone of Magicbane's data project. The Team's goals are twofold:
|
||||||
|
|
||||||
- Ship MagicBox 1.5 with both Aerynth and Vorringia mapsets.
|
- Ship MagicBox 1.5 with both Aerynth and Vorringia mapsets.
|
||||||
- Integrate Magicbane's wpak extractor technology into the tool.
|
|
||||||
- Use data for a detailed audit of Magicbane's database.<br>
|
- Use data for a detailed audit of Magicbane's database.<br>
|
||||||
|
|
||||||
### More Information
|
### More Information
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
```
|
|
||||||
• ▌ ▄ ·. ▄▄▄▄· ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄▄▄ ▄▄▄ ▄▄▄·▄▄▄
|
|
||||||
·██ ▐███▪▐█ ▀█▪ ▀▄.▀·██▪ ██ ██ •██ ▪ ▀▄ █· ▐█ ▄█▀▄ █·▪
|
|
||||||
▐█ ▌▐▌▐█·▐█▀▀█▄ ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄ ██▀·▐▀▀▄ ▄█▀▄
|
|
||||||
██ ██▌▐█▌██▄▪▐█ ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌ ▐█▪·•▐█•█▌▐█▌.▐▌
|
|
||||||
▀▀ █▪▀▀▀·▀▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ .▀ .▀ ▀ ▀█▄▀▪
|
|
||||||
Magicbane Emulator Project © 2013 - 2022
|
|
||||||
www.magicbane.com
|
|
||||||
```
|
|
||||||
|
|
||||||
# mbEditorPro v 2.0
|
|
||||||
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
|
|
||||||
Team Magicbane presents to the community technology allowing MagicBox administrators<br>
|
|
||||||
to fully modify their game using nothing more than notepad.exe. <br>
|
|
||||||
|
|
||||||
- Tool directly converts Shadowbane .cache file data to and from a high fidelity pure JSON representation.
|
|
||||||
- Media such as textures and sound are converted in-situ.
|
|
||||||
- Enumerations are used throughout hiding from the user ugly signed ulong values.
|
|
||||||
- Written in Python and open-source allowing modification and extension with ease.
|
|
||||||
- Want to raise all weapon damage by x%; Just modify item values as they are being parsed!
|
|
||||||
|
|
||||||
The tool built with Eldritch Technologies parses **all Shadowbane .cache files** giving the user complete control over the game<br>
|
|
||||||
|
|
||||||
Team Magicbane aims to deliver you the best crackhead free MMO technology around; all for free!
|
|
||||||
|
|
||||||
### Requirements:
|
|
||||||
|
|
||||||
mbEditorPro requires pillow for image manipulation and tqdm for metrics.
|
|
||||||
|
|
||||||
```$ pip install -r requirements.txt```
|
|
||||||
|
|
||||||
### Usage:
|
|
||||||
```
|
|
||||||
mbEditorPro.py unpack cobjects
|
|
||||||
mbEditorPro.py unpack all
|
|
||||||
```
|
|
||||||
JSON output will be placed into the folder **ARCANE DUMP** <br>
|
|
||||||
|
|
||||||
```
|
|
||||||
mbEditorPro.py pack textures
|
|
||||||
mbEditorPro.py pack all
|
|
||||||
```
|
|
||||||
|
|
||||||
New binary cache file(s) will be built from the JSON and output into the current directory. <br>
|
|
||||||
|
|
||||||
### Limitations
|
|
||||||
|
|
||||||
Hirelings, ZoneEvents and Mobile Inventory are not parsed or written as there were no <br>
|
|
||||||
examples in the .cache files to begin with. The game does seem to support them, however.
|
|
||||||
|
|
||||||
### JSON
|
|
||||||
|
|
||||||
The tool generating pure JSON has the following benefits:
|
|
||||||
|
|
||||||
- It's ubiquitous and the tooling is mature. The standard interop exchange format.
|
|
||||||
- Human and machine-readable.
|
|
||||||
- Ease of importing into MySQL or other databases.
|
|
||||||
- Use as an intermediate representation to build a GUI World Editor or FBX importer.
|
|
||||||
|
|
||||||
### Future
|
|
||||||
|
|
||||||
mbEditorPro will be the cornerstone of Magicbane's data project. The Team's goals are threefold:
|
|
||||||
|
|
||||||
- Ship MagicBox 1.5 with both Aerynth and Vorringia mapsets.
|
|
||||||
- Integrate Magicbane's wpak extractor technology into the tool.
|
|
||||||
- Use data for a detailed audit of Magicbane's database.<br>
|
|
||||||
|
|
||||||
### More Information
|
|
||||||
|
|
||||||
See the mbEditorPro2.0 Wiki for more detailed information.
|
|
||||||
|
|
||||||
@@ -218,12 +218,13 @@ STRING_TO_ITEM_TYPE = {
|
|||||||
'REALMCHARTER': ITEM_TYPE_REALMCHARTER,
|
'REALMCHARTER': ITEM_TYPE_REALMCHARTER,
|
||||||
}
|
}
|
||||||
|
|
||||||
ITEM_USE_FLAGS_NONE = 0
|
# Bit order from the client's use flag name table (sb.exe 0x16C9728)
|
||||||
ITEM_USE_FLAGS_USE_SELF = 1 << 0
|
ITEM_USE_FLAGS_NONE = 1 << 0
|
||||||
ITEM_USE_FLAGS_USE_TARGET = 1 << 1
|
ITEM_USE_FLAGS_USE_SELF = 1 << 1
|
||||||
ITEM_USE_FLAGS_EMPTY_DESTROY = 1 << 2
|
ITEM_USE_FLAGS_USE_TARGET = 1 << 2
|
||||||
ITEM_USE_FLAGS_EMPTY_NEWITEM = 1 << 3
|
ITEM_USE_FLAGS_EMPTY_DESTROY = 1 << 3
|
||||||
ITEM_USE_FLAGS_NUM = 4
|
ITEM_USE_FLAGS_EMPTY_NEWITEM = 1 << 4
|
||||||
|
ITEM_USE_FLAGS_NUM = 5
|
||||||
|
|
||||||
ITEM_USE_FLAGS_TO_STRING = {
|
ITEM_USE_FLAGS_TO_STRING = {
|
||||||
ITEM_USE_FLAGS_NONE: 'NONE',
|
ITEM_USE_FLAGS_NONE: 'NONE',
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ from arcane.util import ResStream
|
|||||||
|
|
||||||
class RankInfo:
|
class RankInfo:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.rank_hirelings = stream.read_dword()
|
self.rank_hirelings = stream.read_long()
|
||||||
self.rank_shrines = stream.read_dword()
|
self.rank_shrines = stream.read_long()
|
||||||
self.rank_spires = stream.read_dword()
|
self.rank_spires = stream.read_long()
|
||||||
self.rank_barracks = stream.read_dword()
|
self.rank_barracks = stream.read_long()
|
||||||
self.rank_rank = stream.read_dword()
|
self.rank_rank = stream.read_long()
|
||||||
self.rank_health = stream.read_dword()
|
self.rank_health = stream.read_long()
|
||||||
self.rank_automatic = stream.read_bool()
|
self.rank_automatic = stream.read_bool()
|
||||||
self.rank_energy_k1 = stream.read_float()
|
self.rank_energy_k1 = stream.read_float()
|
||||||
self.rank_energy_k2 = stream.read_float()
|
self.rank_energy_k2 = stream.read_float()
|
||||||
@@ -35,17 +35,17 @@ class RankInfo:
|
|||||||
self.rank_placement_limit = [
|
self.rank_placement_limit = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_dword(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_placement_limits)
|
] for _ in range(num_placement_limits)
|
||||||
]
|
]
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.rank_hirelings)
|
stream.write_long(self.rank_hirelings)
|
||||||
stream.write_dword(self.rank_shrines)
|
stream.write_long(self.rank_shrines)
|
||||||
stream.write_dword(self.rank_spires)
|
stream.write_long(self.rank_spires)
|
||||||
stream.write_dword(self.rank_barracks)
|
stream.write_long(self.rank_barracks)
|
||||||
stream.write_dword(self.rank_rank)
|
stream.write_long(self.rank_rank)
|
||||||
stream.write_dword(self.rank_health)
|
stream.write_long(self.rank_health)
|
||||||
stream.write_bool(self.rank_automatic)
|
stream.write_bool(self.rank_automatic)
|
||||||
stream.write_float(self.rank_energy_k1)
|
stream.write_float(self.rank_energy_k1)
|
||||||
stream.write_float(self.rank_energy_k2)
|
stream.write_float(self.rank_energy_k2)
|
||||||
@@ -58,7 +58,7 @@ class RankInfo:
|
|||||||
stream.write_dword(len(self.rank_placement_limit))
|
stream.write_dword(len(self.rank_placement_limit))
|
||||||
for placement in self.rank_placement_limit:
|
for placement in self.rank_placement_limit:
|
||||||
stream.write_dword(placement[0])
|
stream.write_dword(placement[0])
|
||||||
stream.write_dword(placement[1])
|
stream.write_long(placement[1])
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.rank_hirelings = data['rank_hirelings']
|
self.rank_hirelings = data['rank_hirelings']
|
||||||
@@ -95,11 +95,11 @@ class RankInfo:
|
|||||||
|
|
||||||
class ArcCityAssetTemplate():
|
class ArcCityAssetTemplate():
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.template_max_ranks = stream.read_dword()
|
self.template_max_ranks = stream.read_long()
|
||||||
self.template_start_rank = stream.read_dword()
|
self.template_start_rank = stream.read_long()
|
||||||
self.template_asset_type = stream.read_dword()
|
self.template_asset_type = stream.read_long()
|
||||||
self.template_trade_icon = stream.read_dword()
|
self.template_trade_icon = stream.read_long()
|
||||||
self.template_landmark = stream.read_dword()
|
self.template_landmark = stream.read_long()
|
||||||
self.template_is_maintenance = stream.read_bool()
|
self.template_is_maintenance = stream.read_bool()
|
||||||
self.template_has_keys = stream.read_bool()
|
self.template_has_keys = stream.read_bool()
|
||||||
self.template_use_hardpoints = stream.read_bool()
|
self.template_use_hardpoints = stream.read_bool()
|
||||||
@@ -130,14 +130,17 @@ class ArcCityAssetTemplate():
|
|||||||
self.template_energy_set = stream.read_dword()
|
self.template_energy_set = stream.read_dword()
|
||||||
self.template_use_trigger = stream.read_string()
|
self.template_use_trigger = stream.read_string()
|
||||||
|
|
||||||
|
# When set, the client builds a trigger object named by the
|
||||||
|
# preceding string and loads its data from the stream. The
|
||||||
|
# trigger formats are not implemented; fail rather than desync.
|
||||||
self.template_unknown_check1 = stream.read_bool()
|
self.template_unknown_check1 = stream.read_bool()
|
||||||
if self.template_unknown_check1:
|
if self.template_unknown_check1:
|
||||||
pass
|
raise NotImplementedError('use trigger object data is not supported')
|
||||||
self.template_loot_trigger = stream.read_string()
|
self.template_loot_trigger = stream.read_string()
|
||||||
|
|
||||||
self.template_unknown_check2 = stream.read_bool()
|
self.template_unknown_check2 = stream.read_bool()
|
||||||
if self.template_unknown_check2:
|
if self.template_unknown_check2:
|
||||||
pass
|
raise NotImplementedError('loot trigger object data is not supported')
|
||||||
|
|
||||||
self.has_embedded_template = stream.read_bool()
|
self.has_embedded_template = stream.read_bool()
|
||||||
if self.has_embedded_template:
|
if self.has_embedded_template:
|
||||||
@@ -145,19 +148,19 @@ class ArcCityAssetTemplate():
|
|||||||
self.template_embed_template.load_binary(stream)
|
self.template_embed_template.load_binary(stream)
|
||||||
num_creators = stream.read_dword()
|
num_creators = stream.read_dword()
|
||||||
self.template_creator = [
|
self.template_creator = [
|
||||||
stream.read_dword() for _ in range(num_creators)
|
stream.read_long() for _ in range(num_creators)
|
||||||
]
|
]
|
||||||
num_terrains = stream.read_dword()
|
num_terrains = stream.read_dword()
|
||||||
self.template_terrain = [
|
self.template_terrain = [
|
||||||
stream.read_dword() for _ in range(num_terrains)
|
stream.read_long() for _ in range(num_terrains)
|
||||||
]
|
]
|
||||||
num_valid_npc_types = stream.read_dword()
|
num_valid_npc_types = stream.read_dword()
|
||||||
self.template_valid_npc_type = [
|
self.template_valid_npc_type = [
|
||||||
stream.read_dword() for _ in range(num_valid_npc_types)
|
stream.read_long() for _ in range(num_valid_npc_types)
|
||||||
]
|
]
|
||||||
num_valid_npc_cat = stream.read_dword()
|
num_valid_npc_cat = stream.read_dword()
|
||||||
self.template_valid_npc_cat = [
|
self.template_valid_npc_cat = [
|
||||||
stream.read_dword() for _ in range(num_valid_npc_cat)
|
stream.read_long() for _ in range(num_valid_npc_cat)
|
||||||
]
|
]
|
||||||
num_rank_info = stream.read_dword()
|
num_rank_info = stream.read_dword()
|
||||||
self.template_rank_info = [RankInfo() for _ in range(num_rank_info)]
|
self.template_rank_info = [RankInfo() for _ in range(num_rank_info)]
|
||||||
@@ -166,15 +169,15 @@ class ArcCityAssetTemplate():
|
|||||||
num_cap_info = stream.read_dword()
|
num_cap_info = stream.read_dword()
|
||||||
self.template_cap_info = [
|
self.template_cap_info = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_qword(),
|
stream.read_qword(),
|
||||||
] for _ in range(num_cap_info)
|
] for _ in range(num_cap_info)
|
||||||
]
|
]
|
||||||
num_event_rules = stream.read_dword()
|
num_event_rules = stream.read_dword()
|
||||||
self.template_event_rules = [
|
self.template_event_rules = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_dword(),
|
||||||
] for _ in range(num_event_rules)
|
] for _ in range(num_event_rules)
|
||||||
]
|
]
|
||||||
@@ -195,18 +198,18 @@ class ArcCityAssetTemplate():
|
|||||||
self.template_resource_limit = [
|
self.template_resource_limit = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_dword(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_resource_limit)
|
] for _ in range(num_resource_limit)
|
||||||
]
|
]
|
||||||
self.template_unknown = stream.read_dword()
|
self.template_unknown = stream.read_dword()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.template_max_ranks)
|
stream.write_long(self.template_max_ranks)
|
||||||
stream.write_dword(self.template_start_rank)
|
stream.write_long(self.template_start_rank)
|
||||||
stream.write_dword(self.template_asset_type)
|
stream.write_long(self.template_asset_type)
|
||||||
stream.write_dword(self.template_trade_icon)
|
stream.write_long(self.template_trade_icon)
|
||||||
stream.write_dword(self.template_landmark)
|
stream.write_long(self.template_landmark)
|
||||||
stream.write_bool(self.template_is_maintenance)
|
stream.write_bool(self.template_is_maintenance)
|
||||||
stream.write_bool(self.template_has_keys)
|
stream.write_bool(self.template_has_keys)
|
||||||
stream.write_bool(self.template_use_hardpoints)
|
stream.write_bool(self.template_use_hardpoints)
|
||||||
@@ -240,27 +243,27 @@ class ArcCityAssetTemplate():
|
|||||||
self.template_embed_template.save_binary(stream)
|
self.template_embed_template.save_binary(stream)
|
||||||
stream.write_dword(len(self.template_creator))
|
stream.write_dword(len(self.template_creator))
|
||||||
for creator in self.template_creator:
|
for creator in self.template_creator:
|
||||||
stream.write_dword(creator)
|
stream.write_long(creator)
|
||||||
stream.write_dword(len(self.template_terrain))
|
stream.write_dword(len(self.template_terrain))
|
||||||
for terrain in self.template_terrain:
|
for terrain in self.template_terrain:
|
||||||
stream.write_dword(terrain)
|
stream.write_long(terrain)
|
||||||
stream.write_dword(len(self.template_valid_npc_type))
|
stream.write_dword(len(self.template_valid_npc_type))
|
||||||
for npc_type in self.template_valid_npc_type:
|
for npc_type in self.template_valid_npc_type:
|
||||||
stream.write_dword(npc_type)
|
stream.write_long(npc_type)
|
||||||
stream.write_dword(len(self.template_valid_npc_cat))
|
stream.write_dword(len(self.template_valid_npc_cat))
|
||||||
for npc_cat in self.template_valid_npc_cat:
|
for npc_cat in self.template_valid_npc_cat:
|
||||||
stream.write_dword(npc_cat)
|
stream.write_long(npc_cat)
|
||||||
stream.write_dword(len(self.template_rank_info))
|
stream.write_dword(len(self.template_rank_info))
|
||||||
for rank_info in self.template_rank_info:
|
for rank_info in self.template_rank_info:
|
||||||
rank_info.save_binary(stream)
|
rank_info.save_binary(stream)
|
||||||
stream.write_dword(len(self.template_cap_info))
|
stream.write_dword(len(self.template_cap_info))
|
||||||
for cap in self.template_cap_info:
|
for cap in self.template_cap_info:
|
||||||
stream.write_dword(cap[0])
|
stream.write_long(cap[0])
|
||||||
stream.write_dword(cap[1])
|
stream.write_long(cap[1])
|
||||||
stream.write_qword(cap[2])
|
stream.write_qword(cap[2])
|
||||||
stream.write_dword(len(self.template_event_rules))
|
stream.write_dword(len(self.template_event_rules))
|
||||||
for event in self.template_event_rules:
|
for event in self.template_event_rules:
|
||||||
stream.write_dword(event[0])
|
stream.write_long(event[0])
|
||||||
stream.write_dword(event[1])
|
stream.write_dword(event[1])
|
||||||
stream.write_dword(len(self.template_architecture))
|
stream.write_dword(len(self.template_architecture))
|
||||||
for arch in self.template_architecture:
|
for arch in self.template_architecture:
|
||||||
@@ -274,8 +277,8 @@ class ArcCityAssetTemplate():
|
|||||||
stream.write_dword(len(self.template_resource_limit))
|
stream.write_dword(len(self.template_resource_limit))
|
||||||
for resource in self.template_resource_limit:
|
for resource in self.template_resource_limit:
|
||||||
stream.write_dword(resource[0])
|
stream.write_dword(resource[0])
|
||||||
stream.write_dword(resource[1])
|
stream.write_long(resource[1])
|
||||||
stream.write_dword(resource[2])
|
stream.write_long(resource[2])
|
||||||
stream.write_dword(self.template_unknown)
|
stream.write_dword(self.template_unknown)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class ArcCombatObj(ArcObj):
|
|||||||
self.combat_powers = [
|
self.combat_powers = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_dword(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_powers)
|
] for _ in range(num_powers)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class ArcCombatObj(ArcObj):
|
|||||||
stream.write_dword(len(self.combat_powers))
|
stream.write_dword(len(self.combat_powers))
|
||||||
for power in self.combat_powers:
|
for power in self.combat_powers:
|
||||||
stream.write_dword(power[0])
|
stream.write_dword(power[0])
|
||||||
stream.write_dword(power[1])
|
stream.write_long(power[1])
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
super().load_json(data)
|
super().load_json(data)
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ from .common.Inventory import Inventory
|
|||||||
class ArcContainerObject(ArcItem):
|
class ArcContainerObject(ArcItem):
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
super().load_binary(stream)
|
super().load_binary(stream)
|
||||||
self.container_open = stream.read_dword()
|
self.container_open = stream.read_long()
|
||||||
self.container_locked = stream.read_dword()
|
self.container_locked = stream.read_long()
|
||||||
self.container_locked_rank = stream.read_dword()
|
self.container_locked_rank = stream.read_long()
|
||||||
self.container_barred = stream.read_dword()
|
self.container_barred = stream.read_long()
|
||||||
self.container_barred_rank = stream.read_dword()
|
self.container_barred_rank = stream.read_long()
|
||||||
self.container_trapped = stream.read_dword()
|
self.container_trapped = stream.read_long()
|
||||||
self.container_trappe_rank = stream.read_dword()
|
self.container_trappe_rank = stream.read_long()
|
||||||
num_contents = stream.read_dword()
|
num_contents = stream.read_dword()
|
||||||
self.container_inventory_contents = [Inventory() for _ in range(num_contents)]
|
self.container_inventory_contents = [Inventory() for _ in range(num_contents)]
|
||||||
for content in self.container_inventory_contents:
|
for content in self.container_inventory_contents:
|
||||||
@@ -28,19 +28,19 @@ class ArcContainerObject(ArcItem):
|
|||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
super().save_binary(stream)
|
super().save_binary(stream)
|
||||||
stream.read_dword(self.container_open)
|
stream.write_long(self.container_open)
|
||||||
stream.read_dword(self.container_locked)
|
stream.write_long(self.container_locked)
|
||||||
stream.read_dword(self.container_locked_rank)
|
stream.write_long(self.container_locked_rank)
|
||||||
stream.read_dword(self.container_barred)
|
stream.write_long(self.container_barred)
|
||||||
stream.read_dword(self.container_barred_rank)
|
stream.write_long(self.container_barred_rank)
|
||||||
stream.read_dword(self.container_trapped)
|
stream.write_long(self.container_trapped)
|
||||||
stream.read_dword(self.container_trappe_rank)
|
stream.write_long(self.container_trappe_rank)
|
||||||
stream.read_dword(len(self.container_inventory_contents))
|
stream.write_dword(len(self.container_inventory_contents))
|
||||||
for content in self.container_inventory_contents:
|
for content in self.container_inventory_contents:
|
||||||
content.save_binary(stream)
|
content.save_binary(stream)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
super().load_json()
|
super().load_json(data)
|
||||||
self.container_open = data['container_open']
|
self.container_open = data['container_open']
|
||||||
self.container_locked = data['container_locked']
|
self.container_locked = data['container_locked']
|
||||||
self.container_locked_rank = data['container_locked_rank']
|
self.container_locked_rank = data['container_locked_rank']
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ from .ArcItem import ArcItem
|
|||||||
class ArcDeed(ArcItem):
|
class ArcDeed(ArcItem):
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
super().load_binary(stream)
|
super().load_binary(stream)
|
||||||
self.deed_type = stream.read_dword()
|
self.deed_type = stream.read_long()
|
||||||
self.deed_furniture_id = stream.read_qword()
|
self.deed_furniture_id = stream.read_qword()
|
||||||
self.deed_target_id = stream.read_qword()
|
self.deed_target_id = stream.read_qword()
|
||||||
self.deed_employment = stream.read_dword()
|
self.deed_employment = stream.read_long()
|
||||||
self.deed_start_rank = stream.read_dword()
|
self.deed_start_rank = stream.read_long()
|
||||||
self.deed_name_lookup = stream.read_dword()
|
self.deed_name_lookup = stream.read_long()
|
||||||
self.deed_indoors = stream.read_bool()
|
self.deed_indoors = stream.read_bool()
|
||||||
self.deed_is_fortress = stream.read_bool()
|
self.deed_is_fortress = stream.read_bool()
|
||||||
self.deed_namelookup_val = stream.read_float()
|
self.deed_namelookup_val = stream.read_float()
|
||||||
@@ -33,12 +33,12 @@ class ArcDeed(ArcItem):
|
|||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
super().save_binary(stream)
|
super().save_binary(stream)
|
||||||
stream.write_dword(self.deed_type)
|
stream.write_long(self.deed_type)
|
||||||
stream.write_qword(self.deed_furniture_id)
|
stream.write_qword(self.deed_furniture_id)
|
||||||
stream.write_qword(self.deed_target_id)
|
stream.write_qword(self.deed_target_id)
|
||||||
stream.write_dword(self.deed_employment)
|
stream.write_long(self.deed_employment)
|
||||||
stream.write_dword(self.deed_start_rank)
|
stream.write_long(self.deed_start_rank)
|
||||||
stream.write_dword(self.deed_name_lookup)
|
stream.write_long(self.deed_name_lookup)
|
||||||
stream.write_bool(self.deed_indoors)
|
stream.write_bool(self.deed_indoors)
|
||||||
stream.write_bool(self.deed_is_fortress)
|
stream.write_bool(self.deed_is_fortress)
|
||||||
stream.write_float(self.deed_namelookup_val)
|
stream.write_float(self.deed_namelookup_val)
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ from arcane.util import ResStream
|
|||||||
class ArcDoorObject():
|
class ArcDoorObject():
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.door_id = stream.read_qword()
|
self.door_id = stream.read_qword()
|
||||||
self.door_type = stream.read_dword()
|
self.door_type = stream.read_long()
|
||||||
self.door_movement_axis = stream.read_dword()
|
self.door_movement_axis = stream.read_long()
|
||||||
self.door_swing_direction = stream.read_dword()
|
self.door_swing_direction = stream.read_long()
|
||||||
self.door_min_time_to_stay_open = stream.read_float()
|
self.door_min_time_to_stay_open = stream.read_float()
|
||||||
self.door_max_time_to_stay_open = stream.read_float()
|
self.door_max_time_to_stay_open = stream.read_float()
|
||||||
self.door_swing_time = stream.read_float()
|
self.door_swing_time = stream.read_float()
|
||||||
@@ -26,16 +26,16 @@ class ArcDoorObject():
|
|||||||
self.door_slide_direction = stream.read_tuple()
|
self.door_slide_direction = stream.read_tuple()
|
||||||
self.door_slide_start = stream.read_tuple()
|
self.door_slide_start = stream.read_tuple()
|
||||||
self.door_slide_end = stream.read_tuple()
|
self.door_slide_end = stream.read_tuple()
|
||||||
self.door_locked = stream.read_dword()
|
self.door_locked = stream.read_long()
|
||||||
self.door_linked_to = stream.read_qword()
|
self.door_linked_to = stream.read_qword()
|
||||||
self.door_key = stream.read_qword()
|
self.door_key = stream.read_qword()
|
||||||
self.door_sound_event = stream.read_qword()
|
self.door_sound_event = stream.read_qword()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_qword(self.door_id)
|
stream.write_qword(self.door_id)
|
||||||
stream.write_dword(self.door_type)
|
stream.write_long(self.door_type)
|
||||||
stream.write_dword(self.door_movement_axis)
|
stream.write_long(self.door_movement_axis)
|
||||||
stream.write_dword(self.door_swing_direction)
|
stream.write_long(self.door_swing_direction)
|
||||||
stream.write_float(self.door_min_time_to_stay_open)
|
stream.write_float(self.door_min_time_to_stay_open)
|
||||||
stream.write_float(self.door_max_time_to_stay_open)
|
stream.write_float(self.door_max_time_to_stay_open)
|
||||||
stream.write_float(self.door_swing_time)
|
stream.write_float(self.door_swing_time)
|
||||||
@@ -44,7 +44,7 @@ class ArcDoorObject():
|
|||||||
stream.write_tuple(self.door_slide_direction)
|
stream.write_tuple(self.door_slide_direction)
|
||||||
stream.write_tuple(self.door_slide_start)
|
stream.write_tuple(self.door_slide_start)
|
||||||
stream.write_tuple(self.door_slide_end)
|
stream.write_tuple(self.door_slide_end)
|
||||||
stream.write_dword(self.door_locked)
|
stream.write_long(self.door_locked)
|
||||||
stream.write_qword(self.door_linked_to)
|
stream.write_qword(self.door_linked_to)
|
||||||
stream.write_qword(self.door_key)
|
stream.write_qword(self.door_key)
|
||||||
stream.write_qword(self.door_sound_event)
|
stream.write_qword(self.door_sound_event)
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ class ArcDungeonUnitObject(ArcStructureObject):
|
|||||||
super().load_binary(stream)
|
super().load_binary(stream)
|
||||||
num_connectables = stream.read_dword()
|
num_connectables = stream.read_dword()
|
||||||
self.dungeon_connectable_edges = [
|
self.dungeon_connectable_edges = [
|
||||||
stream.read_dword() for _ in range(num_connectables)
|
stream.read_long() for _ in range(num_connectables)
|
||||||
]
|
]
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
super().save_binary(stream)
|
super().save_binary(stream)
|
||||||
stream.write_dword(len(self.dungeon_connectable_edges))
|
stream.write_dword(len(self.dungeon_connectable_edges))
|
||||||
for edge in self.dungeon_connectable_edges:
|
for edge in self.dungeon_connectable_edges:
|
||||||
stream.write_dword(edge)
|
stream.write_long(edge)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
super().load_json(data)
|
super().load_json(data)
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ from .common.Race import RaceRequired
|
|||||||
|
|
||||||
class Damage:
|
class Damage:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.damage_type = stream.read_dword()
|
self.damage_type = stream.read_long()
|
||||||
self.damage_min = stream.read_dword()
|
self.damage_min = stream.read_long()
|
||||||
self.damage_max = stream.read_dword()
|
self.damage_max = stream.read_long()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.damage_type)
|
stream.write_long(self.damage_type)
|
||||||
stream.write_dword(self.damage_min)
|
stream.write_long(self.damage_min)
|
||||||
stream.write_dword(self.damage_max)
|
stream.write_long(self.damage_max)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.damage_type = STRING_TO_DAMAGE[data['damage_type']]
|
self.damage_type = STRING_TO_DAMAGE[data['damage_type']]
|
||||||
@@ -45,11 +45,11 @@ class Damage:
|
|||||||
class Offerings:
|
class Offerings:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.offering_type = stream.read_string()
|
self.offering_type = stream.read_string()
|
||||||
self.offering_value = stream.read_dword()
|
self.offering_value = stream.read_long()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_string(self.offering_type)
|
stream.write_string(self.offering_type)
|
||||||
stream.write_dword(self.offering_value)
|
stream.write_long(self.offering_value)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.offering_type = data['offering_type']
|
self.offering_type = data['offering_type']
|
||||||
@@ -68,7 +68,7 @@ class Weapon:
|
|||||||
self.weapon_max_range = stream.read_float()
|
self.weapon_max_range = stream.read_float()
|
||||||
self.weapon_projectile_id = stream.read_qword()
|
self.weapon_projectile_id = stream.read_qword()
|
||||||
self.weapon_projectile_speed = stream.read_float()
|
self.weapon_projectile_speed = stream.read_float()
|
||||||
self.weapon_combat_idle_anim = stream.read_dword()
|
self.weapon_combat_idle_anim = stream.read_long()
|
||||||
num_damages = stream.read_dword()
|
num_damages = stream.read_dword()
|
||||||
self.weapon_damage = [Damage() for _ in range(num_damages)]
|
self.weapon_damage = [Damage() for _ in range(num_damages)]
|
||||||
for damage in self.weapon_damage:
|
for damage in self.weapon_damage:
|
||||||
@@ -76,15 +76,15 @@ class Weapon:
|
|||||||
num_right_anims = stream.read_dword()
|
num_right_anims = stream.read_dword()
|
||||||
self.weapon_attack_anim_right = [
|
self.weapon_attack_anim_right = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_right_anims)
|
] for _ in range(num_right_anims)
|
||||||
]
|
]
|
||||||
num_left_anims = stream.read_dword()
|
num_left_anims = stream.read_dword()
|
||||||
self.weapon_attack_anim_left = [
|
self.weapon_attack_anim_left = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_left_anims)
|
] for _ in range(num_left_anims)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -93,18 +93,18 @@ class Weapon:
|
|||||||
stream.write_float(self.weapon_max_range)
|
stream.write_float(self.weapon_max_range)
|
||||||
stream.write_qword(self.weapon_projectile_id)
|
stream.write_qword(self.weapon_projectile_id)
|
||||||
stream.write_float(self.weapon_projectile_speed)
|
stream.write_float(self.weapon_projectile_speed)
|
||||||
stream.write_dword(self.weapon_combat_idle_anim)
|
stream.write_long(self.weapon_combat_idle_anim)
|
||||||
stream.write_dword(len(self.weapon_damage))
|
stream.write_dword(len(self.weapon_damage))
|
||||||
for damage in self.weapon_damage:
|
for damage in self.weapon_damage:
|
||||||
damage.save_binary(stream)
|
damage.save_binary(stream)
|
||||||
stream.write_dword(len(self.weapon_attack_anim_right))
|
stream.write_dword(len(self.weapon_attack_anim_right))
|
||||||
for anim in self.weapon_attack_anim_right:
|
for anim in self.weapon_attack_anim_right:
|
||||||
stream.write_dword(anim[0])
|
stream.write_long(anim[0])
|
||||||
stream.write_dword(anim[1])
|
stream.write_long(anim[1])
|
||||||
stream.write_dword(len(self.weapon_attack_anim_left))
|
stream.write_dword(len(self.weapon_attack_anim_left))
|
||||||
for anim in self.weapon_attack_anim_left:
|
for anim in self.weapon_attack_anim_left:
|
||||||
stream.write_dword(anim[0])
|
stream.write_long(anim[0])
|
||||||
stream.write_dword(anim[1])
|
stream.write_long(anim[1])
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.weapon_wepspeed = data['weapon_wepspeed']
|
self.weapon_wepspeed = data['weapon_wepspeed']
|
||||||
@@ -138,14 +138,14 @@ class Weapon:
|
|||||||
class ArcItem(ArcCombatObj):
|
class ArcItem(ArcCombatObj):
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
super().load_binary(stream)
|
super().load_binary(stream)
|
||||||
self.item_type = stream.read_dword()
|
self.item_type = stream.read_long()
|
||||||
self.item_eq_slots_value = stream.read_dword()
|
self.item_eq_slots_value = stream.read_long()
|
||||||
self.item_eq_slots_or = stream.read_dword()
|
self.item_eq_slots_or = stream.read_long()
|
||||||
self.item_eq_slots_and = stream.read_dword()
|
self.item_eq_slots_and = stream.read_long()
|
||||||
self.item_eq_slots_type = stream.read_bool()
|
self.item_eq_slots_type = stream.read_bool()
|
||||||
self.item_takeable = stream.read_bool()
|
self.item_takeable = stream.read_bool()
|
||||||
self.item_value = stream.read_dword()
|
self.item_value = stream.read_long()
|
||||||
self.item_wt = stream.read_dword()
|
self.item_wt = stream.read_long()
|
||||||
self.item_passive_defense_mod = stream.read_float()
|
self.item_passive_defense_mod = stream.read_float()
|
||||||
self.item_base_name = stream.read_string()
|
self.item_base_name = stream.read_string()
|
||||||
self.item_dsc = stream.read_string()
|
self.item_dsc = stream.read_string()
|
||||||
@@ -153,7 +153,7 @@ class ArcItem(ArcCombatObj):
|
|||||||
self.item_health_full = stream.read_float()
|
self.item_health_full = stream.read_float()
|
||||||
self.item_skill_used = stream.read_dword()
|
self.item_skill_used = stream.read_dword()
|
||||||
self.item_skill_mastery_used = stream.read_dword()
|
self.item_skill_mastery_used = stream.read_dword()
|
||||||
self.item_parry_anim_id = stream.read_dword()
|
self.item_parry_anim_id = stream.read_long()
|
||||||
num_adjustments = stream.read_dword()
|
num_adjustments = stream.read_dword()
|
||||||
self.item_offering_info = [Offerings() for _ in range(num_adjustments)]
|
self.item_offering_info = [Offerings() for _ in range(num_adjustments)]
|
||||||
for offering in self.item_offering_info:
|
for offering in self.item_offering_info:
|
||||||
@@ -167,11 +167,11 @@ class ArcItem(ArcCombatObj):
|
|||||||
|
|
||||||
if self.item_type == ITEM_TYPE_ARMOR:
|
if self.item_type == ITEM_TYPE_ARMOR:
|
||||||
self.item_bulk_factor = stream.read_float()
|
self.item_bulk_factor = stream.read_float()
|
||||||
self.item_defense_rating = stream.read_dword()
|
self.item_defense_rating = stream.read_long()
|
||||||
self.item_flags = stream.read_dword()
|
self.item_flags = stream.read_dword()
|
||||||
self.item_use_flags = stream.read_dword()
|
self.item_use_flags = stream.read_dword()
|
||||||
self.item_post_item_id = stream.read_qword()
|
self.item_post_item_id = stream.read_qword()
|
||||||
self.item_initial_charges = stream.read_dword()
|
self.item_initial_charges = stream.read_long()
|
||||||
self.item_book_arcana = stream.read_dword()
|
self.item_book_arcana = stream.read_dword()
|
||||||
num_skills = stream.read_dword()
|
num_skills = stream.read_dword()
|
||||||
self.item_skill_req = [SkillLevel() for _ in range(num_skills)]
|
self.item_skill_req = [SkillLevel() for _ in range(num_skills)]
|
||||||
@@ -187,15 +187,15 @@ class ArcItem(ArcCombatObj):
|
|||||||
self.item_attr_req = [AttributeValue() for _ in range(num_attrs)]
|
self.item_attr_req = [AttributeValue() for _ in range(num_attrs)]
|
||||||
for attr in self.item_attr_req:
|
for attr in self.item_attr_req:
|
||||||
attr.load_binary(stream)
|
attr.load_binary(stream)
|
||||||
self.item_level_req = stream.read_dword()
|
self.item_level_req = stream.read_long()
|
||||||
self.item_rank_req = stream.read_dword()
|
self.item_rank_req = stream.read_long()
|
||||||
self.item_sex_req = stream.read_dword()
|
self.item_sex_req = stream.read_long()
|
||||||
num_user_actions = stream.read_dword()
|
num_user_actions = stream.read_dword()
|
||||||
self.item_user_power_action = [
|
self.item_user_power_action = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_dword(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_user_actions)
|
] for _ in range(num_user_actions)
|
||||||
]
|
]
|
||||||
num_power_grants = stream.read_dword()
|
num_power_grants = stream.read_dword()
|
||||||
@@ -209,7 +209,7 @@ class ArcItem(ArcCombatObj):
|
|||||||
|
|
||||||
self.item_sheathable = stream.read_bool()
|
self.item_sheathable = stream.read_bool()
|
||||||
if self.item_sheathable:
|
if self.item_sheathable:
|
||||||
self.item_sheath_slot = stream.read_dword()
|
self.item_sheath_slot = stream.read_long()
|
||||||
self.item_sheath_offset = stream.read_tuple()
|
self.item_sheath_offset = stream.read_tuple()
|
||||||
self.item_sheath_rotation = stream.read_tuple()
|
self.item_sheath_rotation = stream.read_tuple()
|
||||||
|
|
||||||
@@ -227,19 +227,19 @@ class ArcItem(ArcCombatObj):
|
|||||||
self.item_resource_costs = [ResourceInfo() for _ in range(num_costs)]
|
self.item_resource_costs = [ResourceInfo() for _ in range(num_costs)]
|
||||||
for res in self.item_resource_costs:
|
for res in self.item_resource_costs:
|
||||||
res.load_binary(stream)
|
res.load_binary(stream)
|
||||||
self.item_bane_rank = stream.read_dword()
|
self.item_bane_rank = stream.read_long()
|
||||||
self.item_ignore_saved_actions = stream.read_bool()
|
self.item_ignore_saved_actions = stream.read_bool()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
super().save_binary(stream)
|
super().save_binary(stream)
|
||||||
stream.write_dword(self.item_type)
|
stream.write_long(self.item_type)
|
||||||
stream.write_dword(self.item_eq_slots_value)
|
stream.write_long(self.item_eq_slots_value)
|
||||||
stream.write_dword(self.item_eq_slots_or)
|
stream.write_long(self.item_eq_slots_or)
|
||||||
stream.write_dword(self.item_eq_slots_and)
|
stream.write_long(self.item_eq_slots_and)
|
||||||
stream.write_bool(self.item_eq_slots_type)
|
stream.write_bool(self.item_eq_slots_type)
|
||||||
stream.write_bool(self.item_takeable)
|
stream.write_bool(self.item_takeable)
|
||||||
stream.write_dword(self.item_value)
|
stream.write_long(self.item_value)
|
||||||
stream.write_dword(self.item_wt)
|
stream.write_long(self.item_wt)
|
||||||
stream.write_float(self.item_passive_defense_mod)
|
stream.write_float(self.item_passive_defense_mod)
|
||||||
stream.write_string(self.item_base_name)
|
stream.write_string(self.item_base_name)
|
||||||
stream.write_string(self.item_dsc)
|
stream.write_string(self.item_dsc)
|
||||||
@@ -247,7 +247,7 @@ class ArcItem(ArcCombatObj):
|
|||||||
stream.write_float(self.item_health_full)
|
stream.write_float(self.item_health_full)
|
||||||
stream.write_dword(self.item_skill_used)
|
stream.write_dword(self.item_skill_used)
|
||||||
stream.write_dword(self.item_skill_mastery_used)
|
stream.write_dword(self.item_skill_mastery_used)
|
||||||
stream.write_dword(self.item_parry_anim_id)
|
stream.write_long(self.item_parry_anim_id)
|
||||||
stream.write_dword(len(self.item_offering_info))
|
stream.write_dword(len(self.item_offering_info))
|
||||||
for offering in self.item_offering_info:
|
for offering in self.item_offering_info:
|
||||||
offering.save_binary(stream)
|
offering.save_binary(stream)
|
||||||
@@ -257,11 +257,11 @@ class ArcItem(ArcCombatObj):
|
|||||||
stream.write_dword(self.item_secondary_attr)
|
stream.write_dword(self.item_secondary_attr)
|
||||||
if self.item_type == ITEM_TYPE_ARMOR:
|
if self.item_type == ITEM_TYPE_ARMOR:
|
||||||
stream.write_float(self.item_bulk_factor)
|
stream.write_float(self.item_bulk_factor)
|
||||||
stream.write_dword(self.item_defense_rating)
|
stream.write_long(self.item_defense_rating)
|
||||||
stream.write_dword(self.item_flags)
|
stream.write_dword(self.item_flags)
|
||||||
stream.write_dword(self.item_use_flags)
|
stream.write_dword(self.item_use_flags)
|
||||||
stream.write_qword(self.item_post_item_id)
|
stream.write_qword(self.item_post_item_id)
|
||||||
stream.write_dword(self.item_initial_charges)
|
stream.write_long(self.item_initial_charges)
|
||||||
stream.write_dword(self.item_book_arcana)
|
stream.write_dword(self.item_book_arcana)
|
||||||
stream.write_dword(len(self.item_skill_req))
|
stream.write_dword(len(self.item_skill_req))
|
||||||
for skill in self.item_skill_req:
|
for skill in self.item_skill_req:
|
||||||
@@ -272,14 +272,14 @@ class ArcItem(ArcCombatObj):
|
|||||||
stream.write_dword(len(self.item_attr_req))
|
stream.write_dword(len(self.item_attr_req))
|
||||||
for attr in self.item_attr_req:
|
for attr in self.item_attr_req:
|
||||||
attr.save_binary(stream)
|
attr.save_binary(stream)
|
||||||
stream.write_dword(self.item_level_req)
|
stream.write_long(self.item_level_req)
|
||||||
stream.write_dword(self.item_rank_req)
|
stream.write_long(self.item_rank_req)
|
||||||
stream.write_dword(self.item_sex_req)
|
stream.write_long(self.item_sex_req)
|
||||||
stream.write_dword(len(self.item_user_power_action))
|
stream.write_dword(len(self.item_user_power_action))
|
||||||
for power in self.item_user_power_action:
|
for power in self.item_user_power_action:
|
||||||
stream.write_dword(power[0])
|
stream.write_dword(power[0])
|
||||||
stream.write_dword(power[1])
|
stream.write_long(power[1])
|
||||||
stream.write_dword(power[2])
|
stream.write_long(power[2])
|
||||||
stream.write_dword(len(self.item_power_grant))
|
stream.write_dword(len(self.item_power_grant))
|
||||||
for action in self.item_power_grant:
|
for action in self.item_power_grant:
|
||||||
action.save_binary(stream)
|
action.save_binary(stream)
|
||||||
@@ -288,7 +288,7 @@ class ArcItem(ArcCombatObj):
|
|||||||
grant.save_binary(stream)
|
grant.save_binary(stream)
|
||||||
stream.write_bool(self.item_sheathable)
|
stream.write_bool(self.item_sheathable)
|
||||||
if self.item_sheathable:
|
if self.item_sheathable:
|
||||||
stream.write_dword(self.item_sheath_slot)
|
stream.write_long(self.item_sheath_slot)
|
||||||
stream.write_tuple(self.item_sheath_offset)
|
stream.write_tuple(self.item_sheath_offset)
|
||||||
stream.write_tuple(self.item_sheath_rotation)
|
stream.write_tuple(self.item_sheath_rotation)
|
||||||
stream.write_bool(self.item_has_stub)
|
stream.write_bool(self.item_has_stub)
|
||||||
@@ -301,7 +301,7 @@ class ArcItem(ArcCombatObj):
|
|||||||
stream.write_dword(len(self.item_resource_costs))
|
stream.write_dword(len(self.item_resource_costs))
|
||||||
for res in self.item_resource_costs:
|
for res in self.item_resource_costs:
|
||||||
res.save_binary(stream)
|
res.save_binary(stream)
|
||||||
stream.write_dword(self.item_bane_rank)
|
stream.write_long(self.item_bane_rank)
|
||||||
stream.write_bool(self.item_ignore_saved_actions)
|
stream.write_bool(self.item_ignore_saved_actions)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ class ArcKey(ArcItem):
|
|||||||
self.key_keyval = stream.read_qword()
|
self.key_keyval = stream.read_qword()
|
||||||
self.key_keyval2 = stream.read_qword()
|
self.key_keyval2 = stream.read_qword()
|
||||||
self.key_keyval3 = stream.read_qword()
|
self.key_keyval3 = stream.read_qword()
|
||||||
self.key_setting = stream.read_dword()
|
self.key_setting = stream.read_long()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
super().save_binary(stream)
|
super().save_binary(stream)
|
||||||
stream.write_qword(self.key_keyval)
|
stream.write_qword(self.key_keyval)
|
||||||
stream.write_qword(self.key_keyval2)
|
stream.write_qword(self.key_keyval2)
|
||||||
stream.write_qword(self.key_keyval3)
|
stream.write_qword(self.key_keyval3)
|
||||||
stream.write_dword(self.key_setting)
|
stream.write_long(self.key_setting)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
super().load_json(data)
|
super().load_json(data)
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ from .common.SparseData import SparseData
|
|||||||
|
|
||||||
class SoundEvent:
|
class SoundEvent:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.sound_type = stream.read_dword()
|
self.sound_type = stream.read_long()
|
||||||
self.sound_event = stream.read_qword()
|
self.sound_event = stream.read_qword()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.sound_type)
|
stream.write_long(self.sound_type)
|
||||||
stream.write_qword(self.sound_event)
|
stream.write_qword(self.sound_event)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
@@ -55,8 +55,8 @@ class ArcObj:
|
|||||||
num_hard_points = stream.read_dword()
|
num_hard_points = stream.read_dword()
|
||||||
self.obj_arc_hardpoint_list = [
|
self.obj_arc_hardpoint_list = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
[stream.read_float() for _ in range(10)]
|
[stream.read_float() for _ in range(10)]
|
||||||
] for _ in range(num_hard_points)
|
] for _ in range(num_hard_points)
|
||||||
]
|
]
|
||||||
@@ -84,8 +84,8 @@ class ArcObj:
|
|||||||
sound_event.save_binary(stream)
|
sound_event.save_binary(stream)
|
||||||
stream.write_dword(len(self.obj_arc_hardpoint_list))
|
stream.write_dword(len(self.obj_arc_hardpoint_list))
|
||||||
for n, m, points in self.obj_arc_hardpoint_list:
|
for n, m, points in self.obj_arc_hardpoint_list:
|
||||||
stream.write_dword(n)
|
stream.write_long(n)
|
||||||
stream.write_dword(m)
|
stream.write_long(m)
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
stream.write_float(points[i])
|
stream.write_float(points[i])
|
||||||
self.obj_sparse_data.save_binary(stream)
|
self.obj_sparse_data.save_binary(stream)
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ class SkillAdjust:
|
|||||||
num_adjusts = stream.read_dword()
|
num_adjusts = stream.read_dword()
|
||||||
self.skill_adjusts = [
|
self.skill_adjusts = [
|
||||||
[
|
[
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_adjusts)
|
] for _ in range(num_adjusts)
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -57,8 +57,8 @@ class SkillAdjust:
|
|||||||
stream.write_dword(self.skill_type)
|
stream.write_dword(self.skill_type)
|
||||||
stream.write_dword(len(self.skill_adjusts))
|
stream.write_dword(len(self.skill_adjusts))
|
||||||
for skill in self.skill_adjusts:
|
for skill in self.skill_adjusts:
|
||||||
stream.write_dword(skill[0])
|
stream.write_long(skill[0])
|
||||||
stream.write_dword(skill[1])
|
stream.write_long(skill[1])
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.skill_type = string_to_hash(data['skill_type'])
|
self.skill_type = string_to_hash(data['skill_type'])
|
||||||
@@ -98,23 +98,23 @@ class BodyPart:
|
|||||||
class ArcRune(ArcItem):
|
class ArcRune(ArcItem):
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
super().load_binary(stream)
|
super().load_binary(stream)
|
||||||
self.rune_type = stream.read_dword()
|
self.rune_type = stream.read_long()
|
||||||
self.rune_sub_type = stream.read_dword()
|
self.rune_sub_type = stream.read_dword()
|
||||||
self.rune_is_standard_character_creation = stream.read_bool()
|
self.rune_is_standard_character_creation = stream.read_bool()
|
||||||
self.rune_creation_cost = stream.read_dword()
|
self.rune_creation_cost = stream.read_long()
|
||||||
self.rune_rank = stream.read_dword()
|
self.rune_rank = stream.read_long()
|
||||||
self.rune_pracs_per_level = stream.read_dword()
|
self.rune_pracs_per_level = stream.read_long()
|
||||||
self.rune_exp_req_to_level = stream.read_float()
|
self.rune_exp_req_to_level = stream.read_float()
|
||||||
self.rune_sex = stream.read_dword()
|
self.rune_sex = stream.read_long()
|
||||||
self.rune_class_icon = stream.read_qword()
|
self.rune_class_icon = stream.read_qword()
|
||||||
self.rune_health = stream.read_dword()
|
self.rune_health = stream.read_long()
|
||||||
self.rune_mana = stream.read_dword()
|
self.rune_mana = stream.read_long()
|
||||||
self.rune_stamina = stream.read_dword()
|
self.rune_stamina = stream.read_long()
|
||||||
self.rune_min_damage = stream.read_float()
|
self.rune_min_damage = stream.read_float()
|
||||||
self.rune_max_damage = stream.read_float()
|
self.rune_max_damage = stream.read_float()
|
||||||
self.rune_attack = stream.read_dword()
|
self.rune_attack = stream.read_long()
|
||||||
self.rune_defense = stream.read_dword()
|
self.rune_defense = stream.read_long()
|
||||||
self.rune_level = stream.read_dword()
|
self.rune_level = stream.read_long()
|
||||||
self.rune_speed = [
|
self.rune_speed = [
|
||||||
stream.read_float() for _ in range(7)
|
stream.read_float() for _ in range(7)
|
||||||
]
|
]
|
||||||
@@ -142,7 +142,7 @@ class ArcRune(ArcItem):
|
|||||||
]
|
]
|
||||||
num_genders = stream.read_dword()
|
num_genders = stream.read_dword()
|
||||||
self.rune_enemy_gender = [
|
self.rune_enemy_gender = [
|
||||||
stream.read_dword() for _ in range(num_genders)
|
stream.read_long() for _ in range(num_genders)
|
||||||
]
|
]
|
||||||
num_skill_grants = stream.read_dword()
|
num_skill_grants = stream.read_dword()
|
||||||
self.rune_skill_grant = [SkillGrant() for _ in range(num_skill_grants)]
|
self.rune_skill_grant = [SkillGrant() for _ in range(num_skill_grants)]
|
||||||
@@ -165,14 +165,14 @@ class ArcRune(ArcItem):
|
|||||||
[
|
[
|
||||||
stream.read_float(),
|
stream.read_float(),
|
||||||
stream.read_float(),
|
stream.read_float(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_qword(),
|
stream.read_qword(),
|
||||||
stream.read_float(),
|
stream.read_float(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
] for _ in range(num_natural_attacks)
|
] for _ in range(num_natural_attacks)
|
||||||
]
|
]
|
||||||
num_enchants = stream.read_dword()
|
num_enchants = stream.read_dword()
|
||||||
@@ -210,23 +210,23 @@ class ArcRune(ArcItem):
|
|||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
super().save_binary(stream)
|
super().save_binary(stream)
|
||||||
stream.write_dword(self.rune_type)
|
stream.write_long(self.rune_type)
|
||||||
stream.write_dword(self.rune_sub_type)
|
stream.write_dword(self.rune_sub_type)
|
||||||
stream.write_bool(self.rune_is_standard_character_creation)
|
stream.write_bool(self.rune_is_standard_character_creation)
|
||||||
stream.write_dword(self.rune_creation_cost)
|
stream.write_long(self.rune_creation_cost)
|
||||||
stream.write_dword(self.rune_rank)
|
stream.write_long(self.rune_rank)
|
||||||
stream.write_dword(self.rune_pracs_per_level)
|
stream.write_long(self.rune_pracs_per_level)
|
||||||
stream.write_float(self.rune_exp_req_to_level)
|
stream.write_float(self.rune_exp_req_to_level)
|
||||||
stream.write_dword(self.rune_sex)
|
stream.write_long(self.rune_sex)
|
||||||
stream.write_qword(self.rune_class_icon)
|
stream.write_qword(self.rune_class_icon)
|
||||||
stream.write_dword(self.rune_health)
|
stream.write_long(self.rune_health)
|
||||||
stream.write_dword(self.rune_mana)
|
stream.write_long(self.rune_mana)
|
||||||
stream.write_dword(self.rune_stamina)
|
stream.write_long(self.rune_stamina)
|
||||||
stream.write_float(self.rune_min_damage)
|
stream.write_float(self.rune_min_damage)
|
||||||
stream.write_float(self.rune_max_damage)
|
stream.write_float(self.rune_max_damage)
|
||||||
stream.write_dword(self.rune_attack)
|
stream.write_long(self.rune_attack)
|
||||||
stream.write_dword(self.rune_defense)
|
stream.write_long(self.rune_defense)
|
||||||
stream.write_dword(self.rune_level)
|
stream.write_long(self.rune_level)
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
stream.write_float(self.rune_speed[i])
|
stream.write_float(self.rune_speed[i])
|
||||||
self.rune_group.save_binary(stream)
|
self.rune_group.save_binary(stream)
|
||||||
@@ -248,7 +248,7 @@ class ArcRune(ArcItem):
|
|||||||
stream.write_dword(monster)
|
stream.write_dword(monster)
|
||||||
stream.write_dword(len(self.rune_enemy_gender))
|
stream.write_dword(len(self.rune_enemy_gender))
|
||||||
for gender in self.rune_enemy_gender:
|
for gender in self.rune_enemy_gender:
|
||||||
stream.write_dword(gender)
|
stream.write_long(gender)
|
||||||
stream.write_dword(len(self.rune_skill_grant))
|
stream.write_dword(len(self.rune_skill_grant))
|
||||||
for grant in self.rune_skill_grant:
|
for grant in self.rune_skill_grant:
|
||||||
grant.save_binary(stream)
|
grant.save_binary(stream)
|
||||||
@@ -265,14 +265,14 @@ class ArcRune(ArcItem):
|
|||||||
for attack in self.rune_naturalattacks:
|
for attack in self.rune_naturalattacks:
|
||||||
stream.write_float(attack[0])
|
stream.write_float(attack[0])
|
||||||
stream.write_float(attack[1])
|
stream.write_float(attack[1])
|
||||||
stream.write_dword(attack[2])
|
stream.write_long(attack[2])
|
||||||
stream.write_dword(attack[3])
|
stream.write_long(attack[3])
|
||||||
stream.write_qword(attack[4])
|
stream.write_qword(attack[4])
|
||||||
stream.write_float(attack[5])
|
stream.write_float(attack[5])
|
||||||
stream.write_dword(attack[6])
|
stream.write_long(attack[6])
|
||||||
stream.write_dword(attack[7])
|
stream.write_long(attack[7])
|
||||||
stream.write_dword(attack[8])
|
stream.write_long(attack[8])
|
||||||
stream.write_dword(attack[9])
|
stream.write_long(attack[9])
|
||||||
stream.write_dword(len(self.rune_enchantment_type))
|
stream.write_dword(len(self.rune_enchantment_type))
|
||||||
for enchantment in self.rune_enchantment_type:
|
for enchantment in self.rune_enchantment_type:
|
||||||
stream.write_dword(enchantment)
|
stream.write_dword(enchantment)
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ class Sound:
|
|||||||
self.sound_max_dist = stream.read_float()
|
self.sound_max_dist = stream.read_float()
|
||||||
self.sound_dist = stream.read_bool()
|
self.sound_dist = stream.read_bool()
|
||||||
self.sound_loop = stream.read_bool()
|
self.sound_loop = stream.read_bool()
|
||||||
self.sound_chance = stream.read_dword()
|
self.sound_chance = stream.read_long()
|
||||||
self.sound_interval = stream.read_float()
|
self.sound_interval = stream.read_float()
|
||||||
self.sound_reserved = stream.read_dword()
|
self.sound_reserved = stream.read_long()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_qword(self.sound_0)
|
stream.write_qword(self.sound_0)
|
||||||
@@ -31,9 +31,9 @@ class Sound:
|
|||||||
stream.write_float(self.sound_max_dist)
|
stream.write_float(self.sound_max_dist)
|
||||||
stream.write_bool(self.sound_dist)
|
stream.write_bool(self.sound_dist)
|
||||||
stream.write_bool(self.sound_loop)
|
stream.write_bool(self.sound_loop)
|
||||||
stream.write_dword(self.sound_chance)
|
stream.write_long(self.sound_chance)
|
||||||
stream.write_float(self.sound_interval)
|
stream.write_float(self.sound_interval)
|
||||||
stream.write_dword(self.sound_reserved)
|
stream.write_long(self.sound_reserved)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.sound_0 = data['sound_0']
|
self.sound_0 = data['sound_0']
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# • ▌ ▄ ·. ▄▄▄▄· ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄▄▄ ▄▄▄ ▄▄▄·▄▄▄
|
# • ▌ ▄ ·. ▄▄▄▄· ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄▄▄ ▄▄▄ ▄▄▄·▄▄▄
|
||||||
# ·██ ▐███▪▐█ ▀█▪ ▀▄.▀·██▪ ██ ██ •██ ▪ ▀▄ █· ▐█ ▄█▀▄ █·▪
|
# ·██ ▐███▪▐█ ▀█▪ ▀▄.▀·██▪ ██ ██ •██ ▪ ▀▄ █· ▐█ ▄█▀▄ █·▪
|
||||||
# ▐█ ▌▐▌▐█·▐█▀▀█▄ ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄ ██▀·▐▀▀▄ ▄█▀▄
|
# ▐█ ▌▐▌▐█·▐█▀▀█▄ ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄ ██▀·▐▀▀▄ ▄█▀▄
|
||||||
# ██ ██▌▐█▌██▄▪▐█ ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌ ▐█▪·•▐█•█▌▐█▌.▐▌
|
# ██ ██▌▐█▌██▄▪▐█ ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌ ▐█▪·•▐█•█▌▐█▌.▐▌
|
||||||
# ▀▀ █▪▀▀▀·▀▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ .▀ .▀ ▀ ▀█▄▀▪
|
# ▀▀ █▪▀▀▀·▀▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ .▀ .▀ ▀ ▀█▄▀▪
|
||||||
@@ -15,11 +15,11 @@ from arcane.util import ResStream
|
|||||||
class AttributeValue:
|
class AttributeValue:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.attr_type = stream.read_dword()
|
self.attr_type = stream.read_dword()
|
||||||
self.attr_value = stream.read_dword()
|
self.attr_value = stream.read_long()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.attr_type)
|
stream.write_dword(self.attr_type)
|
||||||
stream.write_dword(self.attr_value)
|
stream.write_long(self.attr_value)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.attr_type = string_to_hash(data['attr_type'])
|
self.attr_type = string_to_hash(data['attr_type'])
|
||||||
@@ -35,13 +35,13 @@ class AttributeValue:
|
|||||||
class PowerArgument:
|
class PowerArgument:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.power_arguments = [
|
self.power_arguments = [
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
]
|
]
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.power_arguments[0])
|
stream.write_long(self.power_arguments[0])
|
||||||
stream.write_dword(self.power_arguments[1])
|
stream.write_long(self.power_arguments[1])
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.power_arguments = data['power_arguments']
|
self.power_arguments = data['power_arguments']
|
||||||
@@ -55,11 +55,11 @@ class PowerArgument:
|
|||||||
class SkillLevel:
|
class SkillLevel:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.skill_type = stream.read_dword()
|
self.skill_type = stream.read_dword()
|
||||||
self.skill_level = stream.read_dword()
|
self.skill_level = stream.read_long()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.skill_type)
|
stream.write_dword(self.skill_type)
|
||||||
stream.write_dword(self.skill_level)
|
stream.write_long(self.skill_level)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.skill_type = string_to_hash(data['skill_type'])
|
self.skill_type = string_to_hash(data['skill_type'])
|
||||||
@@ -75,11 +75,11 @@ class SkillLevel:
|
|||||||
class ResourceInfo:
|
class ResourceInfo:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.resource_type = stream.read_dword()
|
self.resource_type = stream.read_dword()
|
||||||
self.resource_value = stream.read_dword()
|
self.resource_value = stream.read_long()
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.resource_type)
|
stream.write_dword(self.resource_type)
|
||||||
stream.write_dword(self.resource_value)
|
stream.write_long(self.resource_value)
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.resource_type = string_to_hash(data['resource_type'])
|
self.resource_type = string_to_hash(data['resource_type'])
|
||||||
@@ -124,31 +124,44 @@ class PowerAction:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def _read_granted_pairs(stream: ResStream):
|
||||||
|
num_pairs = stream.read_dword()
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
stream.read_dword(),
|
||||||
|
stream.read_long(),
|
||||||
|
] for _ in range(num_pairs)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _write_granted_pairs(stream: ResStream, pairs):
|
||||||
|
stream.write_dword(len(pairs))
|
||||||
|
for pair in pairs:
|
||||||
|
stream.write_dword(pair[0])
|
||||||
|
stream.write_long(pair[1])
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_pair_keys(pairs):
|
||||||
|
pairs_copy = copy.deepcopy(pairs)
|
||||||
|
for pair in pairs_copy:
|
||||||
|
pair[0] = hash_to_string(pair[0])
|
||||||
|
return pairs_copy
|
||||||
|
|
||||||
|
|
||||||
|
def _unhash_pair_keys(pairs):
|
||||||
|
pairs_copy = copy.deepcopy(pairs)
|
||||||
|
for pair in pairs_copy:
|
||||||
|
pair[0] = string_to_hash(pair[0])
|
||||||
|
return pairs_copy
|
||||||
|
|
||||||
|
|
||||||
class PowerGrant:
|
class PowerGrant:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.power_type = stream.read_dword()
|
self.power_type = stream.read_dword()
|
||||||
self.power_value = stream.read_dword()
|
self.power_value = stream.read_long()
|
||||||
num_attrs = stream.read_dword()
|
self.power_granted_attrs = _read_granted_pairs(stream)
|
||||||
self.power_granted_attrs = [
|
self.power_granted_skills = _read_granted_pairs(stream)
|
||||||
[
|
self.power_granted_powers = _read_granted_pairs(stream)
|
||||||
stream.read_dword(),
|
|
||||||
stream.read_dword(),
|
|
||||||
] for _ in range(num_attrs)
|
|
||||||
]
|
|
||||||
num_skills = stream.read_dword()
|
|
||||||
self.power_granted_skills = [
|
|
||||||
[
|
|
||||||
stream.read_dword(),
|
|
||||||
stream.read_dword(),
|
|
||||||
] for _ in range(num_skills)
|
|
||||||
]
|
|
||||||
num_powers = stream.read_dword()
|
|
||||||
self.power_granted_powers = [
|
|
||||||
[
|
|
||||||
stream.read_dword(),
|
|
||||||
stream.read_dword(),
|
|
||||||
] for _ in range(num_powers)
|
|
||||||
]
|
|
||||||
num_monster_types = stream.read_dword()
|
num_monster_types = stream.read_dword()
|
||||||
self.power_monster_types = [
|
self.power_monster_types = [
|
||||||
stream.read_dword() for _ in range(num_monster_types)
|
stream.read_dword() for _ in range(num_monster_types)
|
||||||
@@ -156,19 +169,10 @@ class PowerGrant:
|
|||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.power_type)
|
stream.write_dword(self.power_type)
|
||||||
stream.write_dword(self.power_value)
|
stream.write_long(self.power_value)
|
||||||
stream.write_dword(len(self.power_granted_attrs))
|
_write_granted_pairs(stream, self.power_granted_attrs)
|
||||||
for attr in self.power_granted_attrs:
|
_write_granted_pairs(stream, self.power_granted_skills)
|
||||||
stream.write_dword(attr[0])
|
_write_granted_pairs(stream, self.power_granted_powers)
|
||||||
stream.write_dword(attr[1])
|
|
||||||
stream.write_dword(len(self.power_granted_skills))
|
|
||||||
for skill in self.power_granted_skills:
|
|
||||||
stream.write_dword(skill[0])
|
|
||||||
stream.write_dword(skill[1])
|
|
||||||
stream.write_dword(len(self.power_granted_powers))
|
|
||||||
for power in self.power_granted_powers:
|
|
||||||
stream.write_dword(power[0])
|
|
||||||
stream.write_dword(power[1])
|
|
||||||
stream.write_dword(len(self.power_monster_types))
|
stream.write_dword(len(self.power_monster_types))
|
||||||
for value in self.power_monster_types:
|
for value in self.power_monster_types:
|
||||||
stream.write_dword(value)
|
stream.write_dword(value)
|
||||||
@@ -176,24 +180,18 @@ class PowerGrant:
|
|||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.power_type = string_to_hash(data['power_type'])
|
self.power_type = string_to_hash(data['power_type'])
|
||||||
self.power_value = data['power_value']
|
self.power_value = data['power_value']
|
||||||
self.power_granted_attrs = data['power_granted_attrs']
|
self.power_granted_attrs = _unhash_pair_keys(data['power_granted_attrs'])
|
||||||
grants_copy = data['power_granted_skills']
|
self.power_granted_skills = _unhash_pair_keys(data['power_granted_skills'])
|
||||||
for subarr in grants_copy:
|
self.power_granted_powers = _unhash_pair_keys(data['power_granted_powers'])
|
||||||
subarr[0] = string_to_hash(subarr[0])
|
|
||||||
self.power_granted_skills = grants_copy
|
|
||||||
self.power_granted_powers = data['power_granted_powers']
|
|
||||||
self.power_monster_types = data['power_monster_types']
|
self.power_monster_types = data['power_monster_types']
|
||||||
|
|
||||||
def save_json(self):
|
def save_json(self):
|
||||||
data = OrderedDict()
|
data = OrderedDict()
|
||||||
data['power_type'] = hash_to_string(self.power_type)
|
data['power_type'] = hash_to_string(self.power_type)
|
||||||
data['power_value'] = self.power_value
|
data['power_value'] = self.power_value
|
||||||
data['power_granted_attrs'] = self.power_granted_attrs
|
data['power_granted_attrs'] = _hash_pair_keys(self.power_granted_attrs)
|
||||||
grants_copy = copy.deepcopy(self.power_granted_skills)
|
data['power_granted_skills'] = _hash_pair_keys(self.power_granted_skills)
|
||||||
for subarr in grants_copy:
|
data['power_granted_powers'] = _hash_pair_keys(self.power_granted_powers)
|
||||||
subarr[0] = hash_to_string(subarr[0])
|
|
||||||
data['power_granted_skills'] = grants_copy
|
|
||||||
data['power_granted_powers'] = self.power_granted_powers
|
|
||||||
data['power_monster_types'] = self.power_monster_types
|
data['power_monster_types'] = self.power_monster_types
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -201,28 +199,10 @@ class PowerGrant:
|
|||||||
class SkillGrant:
|
class SkillGrant:
|
||||||
def load_binary(self, stream: ResStream):
|
def load_binary(self, stream: ResStream):
|
||||||
self.skill_type = stream.read_dword()
|
self.skill_type = stream.read_dword()
|
||||||
self.skill_value = stream.read_dword()
|
self.skill_value = stream.read_long()
|
||||||
num_attrs = stream.read_dword()
|
self.skill_granted_attrs = _read_granted_pairs(stream)
|
||||||
self.skill_granted_attrs = [
|
self.skill_granted_skills = _read_granted_pairs(stream)
|
||||||
[
|
self.skill_granted_powers = _read_granted_pairs(stream)
|
||||||
stream.read_dword(),
|
|
||||||
stream.read_dword(),
|
|
||||||
] for _ in range(num_attrs)
|
|
||||||
]
|
|
||||||
num_skills = stream.read_dword()
|
|
||||||
self.skill_granted_skills = [
|
|
||||||
[
|
|
||||||
stream.read_dword(),
|
|
||||||
stream.read_dword(),
|
|
||||||
] for _ in range(num_skills)
|
|
||||||
]
|
|
||||||
num_powers = stream.read_dword()
|
|
||||||
self.skill_granted_powers = [
|
|
||||||
[
|
|
||||||
stream.read_dword(),
|
|
||||||
stream.read_dword(),
|
|
||||||
] for _ in range(num_powers)
|
|
||||||
]
|
|
||||||
num_monster_types = stream.read_dword()
|
num_monster_types = stream.read_dword()
|
||||||
self.skill_monster_types = [
|
self.skill_monster_types = [
|
||||||
stream.read_dword() for _ in range(num_monster_types)
|
stream.read_dword() for _ in range(num_monster_types)
|
||||||
@@ -230,19 +210,10 @@ class SkillGrant:
|
|||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
stream.write_dword(self.skill_type)
|
stream.write_dword(self.skill_type)
|
||||||
stream.write_dword(self.skill_value)
|
stream.write_long(self.skill_value)
|
||||||
stream.write_dword(len(self.skill_granted_attrs))
|
_write_granted_pairs(stream, self.skill_granted_attrs)
|
||||||
for attr in self.skill_granted_attrs:
|
_write_granted_pairs(stream, self.skill_granted_skills)
|
||||||
stream.write_dword(attr[0])
|
_write_granted_pairs(stream, self.skill_granted_powers)
|
||||||
stream.write_dword(attr[1])
|
|
||||||
stream.write_dword(len(self.skill_granted_skills))
|
|
||||||
for skill in self.skill_granted_skills:
|
|
||||||
stream.write_dword(skill[0])
|
|
||||||
stream.write_dword(skill[1])
|
|
||||||
stream.write_dword(len(self.skill_granted_powers))
|
|
||||||
for power in self.skill_granted_powers:
|
|
||||||
stream.write_dword(power[0])
|
|
||||||
stream.write_dword(power[1])
|
|
||||||
stream.write_dword(len(self.skill_monster_types))
|
stream.write_dword(len(self.skill_monster_types))
|
||||||
for value in self.skill_monster_types:
|
for value in self.skill_monster_types:
|
||||||
stream.write_dword(value)
|
stream.write_dword(value)
|
||||||
@@ -250,23 +221,17 @@ class SkillGrant:
|
|||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.skill_type = string_to_hash(data['skill_type'])
|
self.skill_type = string_to_hash(data['skill_type'])
|
||||||
self.skill_value = data['skill_value']
|
self.skill_value = data['skill_value']
|
||||||
self.skill_granted_attrs = data['skill_granted_attrs']
|
self.skill_granted_attrs = _unhash_pair_keys(data['skill_granted_attrs'])
|
||||||
grants_copy = data['skill_granted_skills']
|
self.skill_granted_skills = _unhash_pair_keys(data['skill_granted_skills'])
|
||||||
for subarr in grants_copy:
|
self.skill_granted_powers = _unhash_pair_keys(data['skill_granted_powers'])
|
||||||
subarr[0] = string_to_hash(subarr[0])
|
|
||||||
self.skill_granted_skills = grants_copy
|
|
||||||
self.skill_granted_powers = data['skill_granted_powers']
|
|
||||||
self.skill_monster_types = data['skill_monster_types']
|
self.skill_monster_types = data['skill_monster_types']
|
||||||
|
|
||||||
def save_json(self):
|
def save_json(self):
|
||||||
data = OrderedDict()
|
data = OrderedDict()
|
||||||
data['skill_type'] = hash_to_string(self.skill_type)
|
data['skill_type'] = hash_to_string(self.skill_type)
|
||||||
data['skill_value'] = self.skill_value
|
data['skill_value'] = self.skill_value
|
||||||
data['skill_granted_attrs'] = self.skill_granted_attrs
|
data['skill_granted_attrs'] = _hash_pair_keys(self.skill_granted_attrs)
|
||||||
grants_copy = copy.deepcopy(self.skill_granted_skills)
|
data['skill_granted_skills'] = _hash_pair_keys(self.skill_granted_skills)
|
||||||
for subarr in grants_copy:
|
data['skill_granted_powers'] = _hash_pair_keys(self.skill_granted_powers)
|
||||||
subarr[0] = hash_to_string(subarr[0])
|
|
||||||
data['skill_granted_skills'] = grants_copy
|
|
||||||
data['skill_granted_powers'] = self.skill_granted_powers
|
|
||||||
data['skill_monster_types'] = self.skill_monster_types
|
data['skill_monster_types'] = self.skill_monster_types
|
||||||
return data
|
return data
|
||||||
@@ -30,7 +30,7 @@ class Inventory:
|
|||||||
elif self.inventory_type == INVENTORY_TYPE_BOOTYTABLE:
|
elif self.inventory_type == INVENTORY_TYPE_BOOTYTABLE:
|
||||||
self.inventory_table = [
|
self.inventory_table = [
|
||||||
stream.read_float(),
|
stream.read_float(),
|
||||||
stream.read_dword(),
|
stream.read_long(),
|
||||||
]
|
]
|
||||||
|
|
||||||
def save_binary(self, stream: ResStream):
|
def save_binary(self, stream: ResStream):
|
||||||
@@ -44,7 +44,7 @@ class Inventory:
|
|||||||
stream.write_qword(self.inventory_items[1])
|
stream.write_qword(self.inventory_items[1])
|
||||||
elif self.inventory_type == INVENTORY_TYPE_BOOTYTABLE:
|
elif self.inventory_type == INVENTORY_TYPE_BOOTYTABLE:
|
||||||
stream.write_float(self.inventory_table[0])
|
stream.write_float(self.inventory_table[0])
|
||||||
stream.write_dword(self.inventory_table[1])
|
stream.write_long(self.inventory_table[1])
|
||||||
|
|
||||||
def load_json(self, data):
|
def load_json(self, data):
|
||||||
self.inventory_type = STRING_TO_INVENTORY_TYPE[data['type']]
|
self.inventory_type = STRING_TO_INVENTORY_TYPE[data['type']]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class SparseData:
|
|||||||
value = None
|
value = None
|
||||||
|
|
||||||
if sparse_type == SPARSE_VAL_LONG:
|
if sparse_type == SPARSE_VAL_LONG:
|
||||||
value = stream.read_dword()
|
value = stream.read_long()
|
||||||
|
|
||||||
elif sparse_type == SPARSE_VAL_FLOAT:
|
elif sparse_type == SPARSE_VAL_FLOAT:
|
||||||
value = stream.read_float()
|
value = stream.read_float()
|
||||||
@@ -65,7 +65,7 @@ class SparseData:
|
|||||||
|
|
||||||
sparse_type = SPARSE_TAG_TO_SPARSE_TYPE[tag]
|
sparse_type = SPARSE_TAG_TO_SPARSE_TYPE[tag]
|
||||||
if sparse_type == SPARSE_VAL_LONG:
|
if sparse_type == SPARSE_VAL_LONG:
|
||||||
stream.write_dword(value)
|
stream.write_long(value)
|
||||||
|
|
||||||
elif sparse_type == SPARSE_VAL_FLOAT:
|
elif sparse_type == SPARSE_VAL_FLOAT:
|
||||||
stream.write_float(value)
|
stream.write_float(value)
|
||||||
@@ -85,7 +85,7 @@ class SparseData:
|
|||||||
elif sparse_type == SPARSE_REF_MERCHANT_DATA:
|
elif sparse_type == SPARSE_REF_MERCHANT_DATA:
|
||||||
stream.write_qword(value[0])
|
stream.write_qword(value[0])
|
||||||
stream.write_qword(value[1])
|
stream.write_qword(value[1])
|
||||||
stream.write_qword(value[2])
|
stream.write_dword(value[2])
|
||||||
|
|
||||||
elif sparse_type == SPARSE_REF_ARC_CACHE_ID:
|
elif sparse_type == SPARSE_REF_ARC_CACHE_ID:
|
||||||
stream.write_qword(value)
|
stream.write_qword(value)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
# Magicbane Emulator Project © 2013 - 2022
|
# Magicbane Emulator Project © 2013 - 2022
|
||||||
# www.magicbane.com
|
# www.magicbane.com
|
||||||
|
|
||||||
|
import os
|
||||||
import struct
|
import struct
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
@@ -81,7 +82,21 @@ def write_bytes(cache_file, value):
|
|||||||
cache_file.write(value)
|
cache_file.write(value)
|
||||||
|
|
||||||
|
|
||||||
|
def next_available_path(path):
|
||||||
|
# Returns path if unused, otherwise the first free path.1, path.2, ...
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
index = 1
|
||||||
|
while os.path.exists(f'{path}.{index}'):
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return f'{path}.{index}'
|
||||||
|
|
||||||
|
|
||||||
def save_cache_file(filename, resources, compress=True):
|
def save_cache_file(filename, resources, compress=True):
|
||||||
|
filename = next_available_path(filename)
|
||||||
|
print('Packing to ' + filename)
|
||||||
cache_file = open(filename, 'wb')
|
cache_file = open(filename, 'wb')
|
||||||
|
|
||||||
num_resources = len(resources)
|
num_resources = len(resources)
|
||||||
|
|||||||
@@ -0,0 +1,184 @@
|
|||||||
|
# • ▌ ▄ ·. ▄▄▄▄· ▄▄▄ .·▄▄▄▄ ▪ ▄▄▄▄▄ ▄▄▄ ▄▄▄·▄▄▄
|
||||||
|
# ·██ ▐███▪▐█ ▀█▪ ▀▄.▀·██▪ ██ ██ •██ ▪ ▀▄ █· ▐█ ▄█▀▄ █·▪
|
||||||
|
# ▐█ ▌▐▌▐█·▐█▀▀█▄ ▐▀▀▪▄▐█· ▐█▌▐█· ▐█.▪ ▄█▀▄ ▐▀▀▄ ██▀·▐▀▀▄ ▄█▀▄
|
||||||
|
# ██ ██▌▐█▌██▄▪▐█ ▐█▄▄▌██. ██ ▐█▌ ▐█▌·▐█▌.▐▌▐█•█▌ ▐█▪·•▐█•█▌▐█▌.▐▌
|
||||||
|
# ▀▀ █▪▀▀▀·▀▀▀▀ ▀▀▀ ▀▀▀▀▀• ▀▀▀ ▀▀▀ ▀█▄▀▪.▀ ▀ .▀ .▀ ▀ ▀█▄▀▪
|
||||||
|
# Magicbane Emulator Project © 2013 - 2022
|
||||||
|
# www.magicbane.com
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from .ArcFileCache import next_available_path
|
||||||
|
|
||||||
|
# A .wpak is a zip (client uses minizip, ArcWPakFile 0x54D3C0) whose entries are
|
||||||
|
# each Blowfish CFB-64 encrypted with a zero IV (BF_cfb64_encrypt 0xCD9000),
|
||||||
|
# except image entries.
|
||||||
|
WPAK_KEY = bytes([
|
||||||
|
0x85, 0x71, 0x40, 0x3C, 0x14, 0x50, 0x0B, 0x52,
|
||||||
|
0x73, 0x2D, 0x10, 0x08, 0x63, 0x59, 0x5B, 0xAA,
|
||||||
|
]) # hardcoded at 0x54DEC0
|
||||||
|
|
||||||
|
WPAK_COMPRESS_LEVEL = 9 # zipOpenNewFileInZip level in ArcWPakFile writer
|
||||||
|
|
||||||
|
# Map icon paks keep their .tga entries unencrypted (LoadMapZoneData 0xAACA60).
|
||||||
|
WPAK_PLAIN_EXTENSIONS = ('.tga',)
|
||||||
|
|
||||||
|
MASK32 = 0xFFFFFFFF
|
||||||
|
|
||||||
|
|
||||||
|
def pi_fraction_words(count):
|
||||||
|
# Blowfish's initial P-array and S-boxes are the hex digits of pi's fraction.
|
||||||
|
guard_bits = 64
|
||||||
|
bits = count * 32 + guard_bits
|
||||||
|
one = 1 << bits
|
||||||
|
|
||||||
|
def arctan_inverse(x):
|
||||||
|
x_squared = x * x
|
||||||
|
term = one // x
|
||||||
|
total = term
|
||||||
|
sign = -1
|
||||||
|
n = 1
|
||||||
|
while term:
|
||||||
|
term //= x_squared
|
||||||
|
total += sign * (term // (2 * n + 1))
|
||||||
|
sign = -sign
|
||||||
|
n += 1
|
||||||
|
return total
|
||||||
|
|
||||||
|
pi = 4 * (4 * arctan_inverse(5) - arctan_inverse(239))
|
||||||
|
fraction = (pi - (3 << bits)) >> guard_bits
|
||||||
|
|
||||||
|
return [(fraction >> (32 * (count - 1 - i))) & MASK32 for i in range(count)]
|
||||||
|
|
||||||
|
|
||||||
|
class ArcBlowfish:
|
||||||
|
p_array = None
|
||||||
|
s_boxes = None
|
||||||
|
|
||||||
|
def __init__(self, key):
|
||||||
|
words = pi_fraction_words(18 + 4 * 256)
|
||||||
|
self.p_array = words[:18]
|
||||||
|
self.s_boxes = [words[18 + 256 * i: 18 + 256 * (i + 1)] for i in range(4)]
|
||||||
|
self.set_key(key)
|
||||||
|
|
||||||
|
def set_key(self, key):
|
||||||
|
left = 0
|
||||||
|
right = 0
|
||||||
|
|
||||||
|
for i in range(18):
|
||||||
|
key_word = 0
|
||||||
|
for j in range(4):
|
||||||
|
key_word = (key_word << 8) | key[(i * 4 + j) % len(key)]
|
||||||
|
self.p_array[i] ^= key_word
|
||||||
|
|
||||||
|
for i in range(0, 18, 2):
|
||||||
|
left, right = self.encrypt_block(left, right)
|
||||||
|
self.p_array[i] = left
|
||||||
|
self.p_array[i + 1] = right
|
||||||
|
|
||||||
|
for s_box in self.s_boxes:
|
||||||
|
for i in range(0, 256, 2):
|
||||||
|
left, right = self.encrypt_block(left, right)
|
||||||
|
s_box[i] = left
|
||||||
|
s_box[i + 1] = right
|
||||||
|
|
||||||
|
def encrypt_block(self, left, right):
|
||||||
|
p_array = self.p_array
|
||||||
|
s0, s1, s2, s3 = self.s_boxes
|
||||||
|
|
||||||
|
for i in range(16):
|
||||||
|
left ^= p_array[i]
|
||||||
|
feistel = ((s0[left >> 24] + s1[(left >> 16) & 0xFF]) & MASK32) ^ s2[(left >> 8) & 0xFF]
|
||||||
|
right ^= (feistel + s3[left & 0xFF]) & MASK32
|
||||||
|
left, right = right, left
|
||||||
|
|
||||||
|
return right ^ p_array[17], left ^ p_array[16]
|
||||||
|
|
||||||
|
def keystream_block(self, iv):
|
||||||
|
left, right = self.encrypt_block(
|
||||||
|
int.from_bytes(iv[:4], 'big'),
|
||||||
|
int.from_bytes(iv[4:], 'big')
|
||||||
|
)
|
||||||
|
return left.to_bytes(4, 'big') + right.to_bytes(4, 'big')
|
||||||
|
|
||||||
|
def cfb64_decrypt(self, data):
|
||||||
|
output = bytearray(len(data))
|
||||||
|
iv = bytes(8)
|
||||||
|
|
||||||
|
for offset in range(0, len(data), 8):
|
||||||
|
keystream = self.keystream_block(iv)
|
||||||
|
cipher_block = data[offset:offset + 8]
|
||||||
|
for i, cipher_byte in enumerate(cipher_block):
|
||||||
|
output[offset + i] = cipher_byte ^ keystream[i]
|
||||||
|
iv = cipher_block
|
||||||
|
|
||||||
|
return bytes(output)
|
||||||
|
|
||||||
|
def cfb64_encrypt(self, data):
|
||||||
|
output = bytearray(len(data))
|
||||||
|
iv = bytes(8)
|
||||||
|
|
||||||
|
for offset in range(0, len(data), 8):
|
||||||
|
keystream = self.keystream_block(iv)
|
||||||
|
plain_block = data[offset:offset + 8]
|
||||||
|
for i, plain_byte in enumerate(plain_block):
|
||||||
|
output[offset + i] = plain_byte ^ keystream[i]
|
||||||
|
iv = bytes(output[offset:offset + 8])
|
||||||
|
|
||||||
|
return bytes(output)
|
||||||
|
|
||||||
|
|
||||||
|
WPAK_CIPHER = ArcBlowfish(WPAK_KEY)
|
||||||
|
|
||||||
|
|
||||||
|
def is_encrypted_entry(filename):
|
||||||
|
return not filename.lower().endswith(WPAK_PLAIN_EXTENSIONS)
|
||||||
|
|
||||||
|
|
||||||
|
def load_wpak_file(filename):
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
with zipfile.ZipFile(filename) as wpak_file:
|
||||||
|
for info in wpak_file.infolist():
|
||||||
|
if info.is_dir():
|
||||||
|
continue
|
||||||
|
data = wpak_file.read(info)
|
||||||
|
if is_encrypted_entry(info.filename):
|
||||||
|
data = WPAK_CIPHER.cfb64_decrypt(data)
|
||||||
|
entries.append([info.filename, data])
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def load_wpak_directory(directory):
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
# Client finds entries by name (unzLocateFile 0xCD7920), so order only needs to be stable.
|
||||||
|
for filename in sorted(os.listdir(directory), key=str.upper):
|
||||||
|
filepath = os.path.join(directory, filename)
|
||||||
|
if not os.path.isfile(filepath):
|
||||||
|
continue
|
||||||
|
with open(filepath, 'rb') as fp:
|
||||||
|
entries.append([filename, fp.read()])
|
||||||
|
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def build_wpak_bytes(entries, wpak_file):
|
||||||
|
with zipfile.ZipFile(wpak_file, 'w') as archive:
|
||||||
|
for filename, data in entries:
|
||||||
|
info = zipfile.ZipInfo(filename, date_time=time.localtime()[:6])
|
||||||
|
info.compress_type = zipfile.ZIP_DEFLATED
|
||||||
|
if is_encrypted_entry(filename):
|
||||||
|
data = WPAK_CIPHER.cfb64_encrypt(data)
|
||||||
|
archive.writestr(info, data, compresslevel=WPAK_COMPRESS_LEVEL)
|
||||||
|
|
||||||
|
|
||||||
|
def save_wpak_file(filename, entries):
|
||||||
|
filename = next_available_path(filename)
|
||||||
|
print('Packing to ' + filename)
|
||||||
|
|
||||||
|
with open(filename, 'wb') as wpak_file:
|
||||||
|
build_wpak_bytes(entries, wpak_file)
|
||||||
@@ -30,6 +30,10 @@ class ResStream:
|
|||||||
data = self.buffer.read(4)
|
data = self.buffer.read(4)
|
||||||
return struct.unpack('<I', data)[0]
|
return struct.unpack('<I', data)[0]
|
||||||
|
|
||||||
|
def read_long(self):
|
||||||
|
data = self.buffer.read(4)
|
||||||
|
return struct.unpack('<i', data)[0]
|
||||||
|
|
||||||
def read_qword(self):
|
def read_qword(self):
|
||||||
dwords = struct.unpack('<II', self.buffer.read(8))
|
dwords = struct.unpack('<II', self.buffer.read(8))
|
||||||
buffer = struct.pack('<II', dwords[1], dwords[0])
|
buffer = struct.pack('<II', dwords[1], dwords[0])
|
||||||
@@ -72,6 +76,13 @@ class ResStream:
|
|||||||
data = struct.pack('<I', value)
|
data = struct.pack('<I', value)
|
||||||
self.buffer.write(data)
|
self.buffer.write(data)
|
||||||
|
|
||||||
|
def write_long(self, value):
|
||||||
|
# Older dumps stored signed fields as unsigned; both forms are the same bytes.
|
||||||
|
if 0x7FFFFFFF < value <= 0xFFFFFFFF:
|
||||||
|
value -= 0x100000000
|
||||||
|
data = struct.pack('<i', value)
|
||||||
|
self.buffer.write(data)
|
||||||
|
|
||||||
def write_qword(self, value):
|
def write_qword(self, value):
|
||||||
qword = struct.pack('<Q', value)
|
qword = struct.pack('<Q', value)
|
||||||
dwords = [
|
dwords = [
|
||||||
|
|||||||
@@ -6,5 +6,6 @@
|
|||||||
# Magicbane Emulator Project © 2013 - 2022
|
# Magicbane Emulator Project © 2013 - 2022
|
||||||
# www.magicbane.com
|
# www.magicbane.com
|
||||||
|
|
||||||
from .ArcFileCache import load_cache_file, save_cache_file
|
from .ArcFileCache import load_cache_file, save_cache_file, next_available_path
|
||||||
|
from .ArcWpak import load_wpak_file, load_wpak_directory, save_wpak_file, build_wpak_bytes
|
||||||
from .ResStream import ResStream
|
from .ResStream import ResStream
|
||||||
+116
-44
@@ -85,9 +85,33 @@ EDITOR_TARGETS = [
|
|||||||
'skeleton',
|
'skeleton',
|
||||||
'terrain',
|
'terrain',
|
||||||
'render',
|
'render',
|
||||||
|
'wpak',
|
||||||
'all'
|
'all'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Each pak unpacks to ARCANE_DUMP/<name> and packs back to <name>.wpak.
|
||||||
|
WPAK_NAMES = [
|
||||||
|
'AerynthIcons',
|
||||||
|
'Config',
|
||||||
|
'DalgothIcons',
|
||||||
|
'Environment',
|
||||||
|
'GeneralItemTables',
|
||||||
|
'ItemTables',
|
||||||
|
'ModTables',
|
||||||
|
'ModTypeTables',
|
||||||
|
'Transitions',
|
||||||
|
'VendorENGLISH',
|
||||||
|
'VorringiaIcons'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def create_unpack_directory(base_directory):
|
||||||
|
# Never unpack over an existing folder; use the next free numbered name.
|
||||||
|
directory = next_available_path(base_directory)
|
||||||
|
os.makedirs(directory)
|
||||||
|
print('Unpacking to ' + directory)
|
||||||
|
return directory
|
||||||
|
|
||||||
|
|
||||||
def init_cobjects():
|
def init_cobjects():
|
||||||
for obj_type in COBJECTS_MAP:
|
for obj_type in COBJECTS_MAP:
|
||||||
@@ -146,9 +170,11 @@ def init_render():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_cobjects():
|
def unpack_cobjects():
|
||||||
init_cobjects()
|
|
||||||
|
|
||||||
resources = load_cache_file('CObjects.cache')
|
resources = load_cache_file('CObjects.cache')
|
||||||
|
directory = create_unpack_directory(COBJECTS_DIRECTORY)
|
||||||
|
|
||||||
|
for obj_type in COBJECTS_MAP:
|
||||||
|
os.makedirs(os.path.join(directory, OBJECT_TYPE_TO_STRING[obj_type]))
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
@@ -158,7 +184,7 @@ def unpack_cobjects():
|
|||||||
|
|
||||||
arc_in = COBJECTS_MAP[obj_type]()
|
arc_in = COBJECTS_MAP[obj_type]()
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
COBJECTS_DIRECTORY,
|
directory,
|
||||||
OBJECT_TYPE_TO_STRING[obj_type],
|
OBJECT_TYPE_TO_STRING[obj_type],
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
@@ -193,7 +219,7 @@ def pack_cobjects():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('CObjects.cache.new', resources)
|
save_cache_file('CObjects.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_cobjects():
|
def test_cobjects():
|
||||||
@@ -221,16 +247,15 @@ def test_cobjects():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_czones():
|
def unpack_czones():
|
||||||
init_czones()
|
|
||||||
|
|
||||||
resources = load_cache_file('CZone.cache')
|
resources = load_cache_file('CZone.cache')
|
||||||
|
directory = create_unpack_directory(CZONE_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_zone = ArcZone()
|
arc_zone = ArcZone()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
CZONE_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
arc_zone.load_binary(in_stream)
|
arc_zone.load_binary(in_stream)
|
||||||
@@ -257,7 +282,7 @@ def pack_czones():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('CZone.cache.new', resources)
|
save_cache_file('CZone.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_czones():
|
def test_czones():
|
||||||
@@ -280,16 +305,15 @@ def test_czones():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_sound():
|
def unpack_sound():
|
||||||
init_sound()
|
|
||||||
|
|
||||||
resources = load_cache_file('Sound.cache')
|
resources = load_cache_file('Sound.cache')
|
||||||
|
directory = create_unpack_directory(SOUND_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_sound = ArcSound()
|
arc_sound = ArcSound()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
SOUND_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.wav'
|
f'{res_id:d}.wav'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -319,7 +343,7 @@ def pack_sound():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Sound.cache.new', resources, False)
|
save_cache_file('Sound.cache', resources, False)
|
||||||
|
|
||||||
|
|
||||||
def test_sound():
|
def test_sound():
|
||||||
@@ -350,16 +374,15 @@ def test_sound():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_texture():
|
def unpack_texture():
|
||||||
init_texture()
|
|
||||||
|
|
||||||
print('Decompressing Texture Cache...')
|
print('Decompressing Texture Cache...')
|
||||||
resources = load_cache_file('Textures.cache')
|
resources = load_cache_file('Textures.cache')
|
||||||
|
directory = create_unpack_directory(TEXTURE_DIRECTORY)
|
||||||
|
|
||||||
print('Writing images')
|
print('Writing images')
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
TEXTURE_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.tga'
|
f'{res_id:d}.tga'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -386,7 +409,7 @@ def pack_texture():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Textures.cache.new', resources)
|
save_cache_file('Textures.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_texture():
|
def test_texture():
|
||||||
@@ -418,14 +441,13 @@ def test_texture():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_terrain():
|
def unpack_terrain():
|
||||||
init_terrain()
|
|
||||||
|
|
||||||
resources = load_cache_file('TerrainAlpha.cache')
|
resources = load_cache_file('TerrainAlpha.cache')
|
||||||
|
directory = create_unpack_directory(TERRAIN_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
TERRAIN_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.tga'
|
f'{res_id:d}.tga'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -452,7 +474,7 @@ def pack_terrain():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('TerrainAlpha.cache.new', resources)
|
save_cache_file('TerrainAlpha.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_terrain():
|
def test_terrain():
|
||||||
@@ -486,16 +508,15 @@ def test_terrain():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_mesh():
|
def unpack_mesh():
|
||||||
init_mesh()
|
|
||||||
|
|
||||||
resources = load_cache_file('Mesh.cache')
|
resources = load_cache_file('Mesh.cache')
|
||||||
|
directory = create_unpack_directory(MESH_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_mesh = ArcMesh()
|
arc_mesh = ArcMesh()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
MESH_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
arc_mesh.load_binary(in_stream)
|
arc_mesh.load_binary(in_stream)
|
||||||
@@ -522,7 +543,7 @@ def pack_mesh():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Mesh.cache.new', resources)
|
save_cache_file('Mesh.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_mesh():
|
def test_mesh():
|
||||||
@@ -545,16 +566,15 @@ def test_mesh():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_visual():
|
def unpack_visual():
|
||||||
init_visual()
|
|
||||||
|
|
||||||
resources = load_cache_file('Visual.cache')
|
resources = load_cache_file('Visual.cache')
|
||||||
|
directory = create_unpack_directory(VISUAL_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_visual = ArcVisual()
|
arc_visual = ArcVisual()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
VISUAL_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
arc_visual.load_binary(in_stream)
|
arc_visual.load_binary(in_stream)
|
||||||
@@ -581,7 +601,7 @@ def pack_visual():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Visual.cache.new', resources)
|
save_cache_file('Visual.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_visual():
|
def test_visual():
|
||||||
@@ -604,16 +624,15 @@ def test_visual():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_motion():
|
def unpack_motion():
|
||||||
init_motion()
|
|
||||||
|
|
||||||
resources = load_cache_file('Motion.cache')
|
resources = load_cache_file('Motion.cache')
|
||||||
|
directory = create_unpack_directory(MOTION_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_motion = ArcMotion()
|
arc_motion = ArcMotion()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
MOTION_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
arc_motion.load_binary(in_stream)
|
arc_motion.load_binary(in_stream)
|
||||||
@@ -640,7 +659,7 @@ def pack_motion():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Motion.cache.new', resources)
|
save_cache_file('Motion.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_motion():
|
def test_motion():
|
||||||
@@ -663,16 +682,15 @@ def test_motion():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_tile():
|
def unpack_tile():
|
||||||
init_tile()
|
|
||||||
|
|
||||||
resources = load_cache_file('Tile.cache')
|
resources = load_cache_file('Tile.cache')
|
||||||
|
directory = create_unpack_directory(TILE_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_tile = ArcTileManager()
|
arc_tile = ArcTileManager()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
TILE_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
arc_tile.load_binary(in_stream)
|
arc_tile.load_binary(in_stream)
|
||||||
@@ -699,7 +717,7 @@ def pack_tile():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Tile.cache.new', resources)
|
save_cache_file('Tile.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_tile():
|
def test_tile():
|
||||||
@@ -722,16 +740,15 @@ def test_tile():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_skeleton():
|
def unpack_skeleton():
|
||||||
init_skeleton()
|
|
||||||
|
|
||||||
resources = load_cache_file('Skeleton.cache')
|
resources = load_cache_file('Skeleton.cache')
|
||||||
|
directory = create_unpack_directory(SKELETON_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_skeleton = ArcSkeleton()
|
arc_skeleton = ArcSkeleton()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
SKELETON_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
arc_skeleton.load_binary(in_stream)
|
arc_skeleton.load_binary(in_stream)
|
||||||
@@ -759,7 +776,7 @@ def pack_skeleton():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Skeleton.cache.new', resources)
|
save_cache_file('Skeleton.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_skeleton():
|
def test_skeleton():
|
||||||
@@ -782,16 +799,15 @@ def test_skeleton():
|
|||||||
|
|
||||||
|
|
||||||
def unpack_render():
|
def unpack_render():
|
||||||
init_render()
|
|
||||||
|
|
||||||
resources = load_cache_file('render.cache')
|
resources = load_cache_file('render.cache')
|
||||||
|
directory = create_unpack_directory(RENDER_DIRECTORY)
|
||||||
|
|
||||||
with tqdm(total=len(resources)) as pBar:
|
with tqdm(total=len(resources)) as pBar:
|
||||||
for res_id, data in resources:
|
for res_id, data in resources:
|
||||||
arc_render = ArcRender()
|
arc_render = ArcRender()
|
||||||
in_stream = ResStream(data)
|
in_stream = ResStream(data)
|
||||||
filepath = os.path.join(
|
filepath = os.path.join(
|
||||||
RENDER_DIRECTORY,
|
directory,
|
||||||
f'{res_id:d}.json'
|
f'{res_id:d}.json'
|
||||||
)
|
)
|
||||||
arc_render.load_binary(in_stream)
|
arc_render.load_binary(in_stream)
|
||||||
@@ -819,7 +835,7 @@ def pack_render():
|
|||||||
|
|
||||||
resources.append([res_id, out_stream.get_bytes()])
|
resources.append([res_id, out_stream.get_bytes()])
|
||||||
|
|
||||||
save_cache_file('Render.cache.new', resources)
|
save_cache_file('Render.cache', resources)
|
||||||
|
|
||||||
|
|
||||||
def test_render():
|
def test_render():
|
||||||
@@ -841,6 +857,61 @@ def test_render():
|
|||||||
print(res_id, in_stream.buffer.tell(), out_stream.buffer.tell())
|
print(res_id, in_stream.buffer.tell(), out_stream.buffer.tell())
|
||||||
|
|
||||||
|
|
||||||
|
def selected_wpak_names():
|
||||||
|
# An optional third argument picks a single pak, e.g. "unpack wpak Config".
|
||||||
|
return sys.argv[3:] or WPAK_NAMES
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_wpak():
|
||||||
|
for wpak_name in selected_wpak_names():
|
||||||
|
wpak_filename = wpak_name + '.wpak'
|
||||||
|
if not os.path.isfile(wpak_filename):
|
||||||
|
print('***Skipping: ' + wpak_filename + ' not found.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
entries = load_wpak_file(wpak_filename)
|
||||||
|
directory = create_unpack_directory(os.path.join(TARGET_DIRECTORY, wpak_name))
|
||||||
|
|
||||||
|
with tqdm(total=len(entries)) as pBar:
|
||||||
|
for filename, data in entries:
|
||||||
|
filepath = os.path.join(directory, filename)
|
||||||
|
with open(filepath, 'wb') as fp:
|
||||||
|
fp.write(data)
|
||||||
|
pBar.update(1)
|
||||||
|
|
||||||
|
|
||||||
|
def pack_wpak():
|
||||||
|
for wpak_name in selected_wpak_names():
|
||||||
|
directory = os.path.join(TARGET_DIRECTORY, wpak_name)
|
||||||
|
if not os.path.isdir(directory):
|
||||||
|
print('***Skipping: ' + directory + ' not found.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
entries = load_wpak_directory(directory)
|
||||||
|
save_wpak_file(wpak_name + '.wpak', entries)
|
||||||
|
|
||||||
|
|
||||||
|
def test_wpak():
|
||||||
|
for wpak_name in selected_wpak_names():
|
||||||
|
wpak_filename = wpak_name + '.wpak'
|
||||||
|
if not os.path.isfile(wpak_filename):
|
||||||
|
print('***Skipping: ' + wpak_filename + ' not found.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
print(wpak_filename)
|
||||||
|
entries = load_wpak_file(wpak_filename)
|
||||||
|
out_stream = io.BytesIO()
|
||||||
|
|
||||||
|
build_wpak_bytes(entries, out_stream)
|
||||||
|
repacked = dict(load_wpak_file(out_stream))
|
||||||
|
|
||||||
|
for filename, data in entries:
|
||||||
|
try:
|
||||||
|
assert repacked[filename] == data
|
||||||
|
except:
|
||||||
|
print(filename, len(data), len(repacked.get(filename, b'')))
|
||||||
|
|
||||||
|
|
||||||
def unpack_all():
|
def unpack_all():
|
||||||
for cache_type in EDITOR_TARGETS[:-1]:
|
for cache_type in EDITOR_TARGETS[:-1]:
|
||||||
method = 'unpack_' + cache_type + '()'
|
method = 'unpack_' + cache_type + '()'
|
||||||
@@ -882,6 +953,7 @@ def usage():
|
|||||||
print('')
|
print('')
|
||||||
print('example: unpack texture')
|
print('example: unpack texture')
|
||||||
print('example: pack texture')
|
print('example: pack texture')
|
||||||
|
print('example: unpack wpak Config')
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user