Extract Equipment data as JSON

This commit is contained in:
Luciano Ciccariello 2023-03-18 14:16:56 +00:00
parent 63d6e3cf20
commit ed940319d6
10 changed files with 308 additions and 75 deletions

View File

@ -325,6 +325,9 @@ $(BUILD_DIR)/$(ASSETS_DIR)/%.tiledef.json.o: $(ASSETS_DIR)/%.tiledef.json
$(BUILD_DIR)/$(ASSETS_DIR)/%.spriteparts.json.o: $(ASSETS_DIR)/%.spriteparts.json
./tools/splat_ext/spriteparts.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(LD) -r -b binary -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(BUILD_DIR)/$(ASSETS_DIR)/%.equipment.json.o: $(ASSETS_DIR)/%.equipment.json
./tools/splat_ext/equipment.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(LD) -r -b binary -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.bin
$(BUILD_DIR)/$(ASSETS_DIR)/%.spritepartslist.json.o: $(ASSETS_DIR)/%.spritepartslist.json
./tools/splat_ext/spritepartslist.py $< $(BUILD_DIR)/$(ASSETS_DIR)/$*.s
$(AS) $(AS_FLAGS) -o $(BUILD_DIR)/$(ASSETS_DIR)/$*.o $(BUILD_DIR)/$(ASSETS_DIR)/$*.s

View File

@ -33,7 +33,7 @@ segments:
- [0x2BC0, data]
- [0x3C40, data]
- [0x4A00, data]
- [0x4B04, raw, equipment_data]
- [0x4B04, equipment, equipments]
- [0x7718, raw, accessory_data]
- [0x8000, data]
- [0x8900, data]

View File

@ -691,33 +691,35 @@ typedef struct {
// D_800A4B04 it is assumed the equip data starts from here
// https://github.com/3snowp7im/SotN-Randomizer/blob/master/src/stats.js
typedef struct {
/* 800a4b38 */ const char* name;
/* 800a4b3C */ const char* description;
/* 800a4b40 */ u16 attack;
/* 800a4b42 */ u16 defense;
/* 800a4b44 */ u16 element;
/* 800a4b46 */ u8 unk0E;
/* 800a4b46 */ u8 entId;
/* 800a4b48 */ u16 unk10;
/* 800a4b4A */ u16 unk12;
/* 800a4b4C */ u16 unk14;
/* 800a4b4E */ u16 unk16;
/* 800a4b50 */ u8 unk18;
/* 800a4b51 */ u8 isConsumable;
/* 800a4b52 */ u16 unk1A;
/* 800a4b54 */ u16 unk1C;
/* 800a4b56 */ u16 unk1E;
/* 800a4b58 */ u16 unk20;
/* 800a4b5A */ u16 unk22;
/* 800a4b5C */ u16 mpUsage;
/* 800a4b5E */ u16 unk26;
/* 800a4b60 */ u8 unk28; // somewhat range-related
/* 800a4b61 */ u8 unk29;
/* 800a4b62 */ u16 unk2A;
/* 800a4b64 */ u16 icon;
/* 800a4b66 */ u16 palette;
/* 800a4b68 */ u16 unk30;
/* 800a4b6A */ u16 unk32;
/* 0x00 */ const char* name;
/* 0x04 */ const char* description;
/* 0x08 */ s16 attack;
/* 0x0A */ s16 defense;
/* 0x0C */ u16 element;
/* 0x0E */ u8 damageScale;
/* 0x0F */ u8 weaponId;
/* 0x10 */ u16 unk10;
/* 0x12 */ u8 playerAnim;
/* 0x13 */ u8 unk13;
/* 0x14 */ u8 unk14;
/* 0x15 */ u8 lockDuration;
/* 0x16 */ u16 chainable;
/* 0x18 */ u8 specialMove;
/* 0x19 */ u8 isConsumable;
/* 0x1A */ u8 enemyInvincibilityFrames;
/* 0x1B */ u8 unk1B;
/* 0x1C */ u16 unk1C;
/* 0x1E */ u16 unk1E;
/* 0x20 */ u16 unk20;
/* 0x22 */ u16 unk22;
/* 0x24 */ u16 mpUsage;
/* 0x26 */ u16 stunFrames;
/* 0x28 */ u16 hitType;
/* 0x2A */ u16 hitEffect;
/* 0x2C */ u16 icon;
/* 0x2E */ u16 palette;
/* 0x30 */ u16 criticalRate;
/* 0x32 */ u16 unk32;
} Equipment; /* size=0x34 */
// Defines armor, cloak and rings

142
tools/splat_ext/equipment.py Executable file
View File

@ -0,0 +1,142 @@
#!/usr/bin/python3
import json
import os
import sys
from typing import Optional
from pathlib import Path
sys.path.append(f"{os.getcwd()}/tools/n64splat")
sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from util import options, log
from segtypes.n64.segment import N64Segment
import utils
item_size = 0x34 # sizeof(Equipment)
def serialize_equipment(content: str) -> bytearray:
obj = json.loads(content)
item_count = len(obj)
serialized_data = bytearray()
for i in range(0, item_count):
item = obj[i]
serialized_data += utils.from_ptr_str(item["name_addr"])
serialized_data += utils.from_ptr_str(item["desc_addr"])
serialized_data += utils.from_16(item["attack"])
serialized_data += utils.from_16(item["defense"])
serialized_data += utils.from_16(item["element"])
serialized_data += utils.from_u8(item["damageScale"])
serialized_data += utils.from_u8(item["weaponId"])
serialized_data += utils.from_16(item["unk10"])
serialized_data += utils.from_u8(item["playerAnim"])
serialized_data += utils.from_u8(item["unk13"])
serialized_data += utils.from_u8(item["unk14"])
serialized_data += utils.from_u8(item["lockDuration"])
serialized_data += utils.from_16(item["chainable"])
serialized_data += utils.from_u8(item["specialMove"])
serialized_data += utils.from_bool(item["isConsumable"])
serialized_data += utils.from_u8(item["enemyInvincibilityFrames"])
serialized_data += utils.from_u8(item["unk1B"])
serialized_data += utils.from_16(item["unk1C"])
serialized_data += utils.from_16(item["unk1E"])
serialized_data += utils.from_16(item["unk20"])
serialized_data += utils.from_16(item["unk22"])
serialized_data += utils.from_16(item["mpUsage"])
serialized_data += utils.from_16(item["stunFrames"])
serialized_data += utils.from_16(item["hitType"])
serialized_data += utils.from_16(item["hitEffect"])
serialized_data += utils.from_16(item["menuIcon"])
serialized_data += utils.from_16(item["menuPalette"])
serialized_data += utils.from_16(item["criticalRate"])
serialized_data += utils.from_16(item["unk32"])
expected_data_size = item_count * item_size
assert (len(serialized_data) == expected_data_size)
return serialized_data
class PSXSegEquipment(N64Segment):
def __init__(self, rom_start, rom_end, type, name, vram_start, args, yaml):
super().__init__(rom_start, rom_end, type, name, vram_start, args, yaml),
def out_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / self.name
def src_path(self) -> Optional[Path]:
return options.opts.asset_path / self.dir / f"{self.name}.equipment.json"
def split(self, rom_bytes):
path = self.src_path()
path.parent.mkdir(parents=True, exist_ok=True)
data = self.parse_equipment(
rom_bytes[self.rom_start:self.rom_end], rom_bytes)
with open(path, "w") as f:
f.write(json.dumps(data, indent=4))
def parse_equipment(self, data: bytearray, rom: bytearray) -> list:
def get_ptr_data(src_ptr_data):
return rom[utils.to_u32(src_ptr_data) - (self.vram_start - self.rom_start):]
count = int(len(data) / item_size)
expected_data_size = count * item_size
if len(data) != expected_data_size:
log.write(
f"data for '{self.name}' is {expected_data_size - len(data)} too long. Data might look incorrect.", status="warn")
items = []
for i in range(0, count):
item_data = data[i * item_size:][:item_size]
item = {
# debugging stuff
"id": i,
"id_hex": hex(i)[2:].upper(),
"ram_addr": hex(self.vram_start + i * item_size)[2:].upper(),
"name_resolved": utils.sotn_menu_name_to_str(get_ptr_data(item_data[0x00:])),
"desc_resolved": utils.sotn_menu_desc_to_str(get_ptr_data(item_data[0x04:])),
# debugging stuff ends
"name_addr": utils.to_ptr_str(item_data[0x00:]),
"desc_addr": utils.to_ptr_str(item_data[0x04:]),
"attack": utils.to_s16(item_data[0x08:]),
"defense": utils.to_s16(item_data[0x0A:]),
"element": utils.to_u16(item_data[0x0C:]),
"damageScale": utils.to_u8(item_data[0x0E:]),
"weaponId": utils.to_u8(item_data[0x0F:]),
"unk10": utils.to_u16(item_data[0x10:]),
"playerAnim": utils.to_u8(item_data[0x12:]),
"unk13": utils.to_u8(item_data[0x13:]),
"unk14": utils.to_u8(item_data[0x14:]),
"lockDuration": utils.to_u8(item_data[0x15:]),
"chainable": utils.to_u16(item_data[0x16:]),
"specialMove": utils.to_u8(item_data[0x18:]),
"isConsumable": utils.to_bool(item_data[0x19:]),
"enemyInvincibilityFrames": utils.to_u8(item_data[0x1A:]),
"unk1B": utils.to_u8(item_data[0x1B:]),
"unk1C": utils.to_u16(item_data[0x1C:]),
"unk1E": utils.to_u16(item_data[0x1E:]),
"unk20": utils.to_u16(item_data[0x20:]),
"unk22": utils.to_u16(item_data[0x22:]),
"mpUsage": utils.to_u16(item_data[0x24:]),
"stunFrames": utils.to_u16(item_data[0x26:]),
"hitType": utils.to_u16(item_data[0x28:]),
"hitEffect": utils.to_u16(item_data[0x2A:]),
"menuIcon": utils.to_u16(item_data[0x2C:]),
"menuPalette": utils.to_u16(item_data[0x2E:]),
"criticalRate": utils.to_u16(item_data[0x30:]),
"unk32": utils.to_u16(item_data[0x32:]),
}
items.append(item)
return items
if __name__ == "__main__":
input_file_name = sys.argv[1]
output_file_name = sys.argv[2]
with open(input_file_name, "r") as f_in:
data = serialize_equipment(f_in.read())
with open(output_file_name, "wb") as f_out:
f_out.write(data)

View File

@ -1,6 +1,5 @@
#!/usr/bin/python3
import ctypes
import json
import os
import sys
@ -11,23 +10,21 @@ sys.path.append(f"{os.getcwd()}/tools/n64splat")
sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from util import options
from segtypes.n64.segment import N64Segment
import utils
item_size = 0xA # sizeof(LayoutObject)
def parse_layoutobj(data: bytearray) -> list:
def to_s16(data):
return ctypes.c_short(data[0] | (data[1] << 8)).value
count = int(len(data) / item_size)
items = []
for i in range(0, count):
item = {
"x": to_s16(data[i * item_size + 0:]),
"y": to_s16(data[i * item_size + 2:]),
"objectId": to_s16(data[i * item_size + 4:]),
"objectRoomIndex": to_s16(data[i * item_size + 6:]),
"subId": to_s16(data[i * item_size + 8:]),
"x": utils.to_s16(data[i * item_size + 0:]),
"y": utils.to_s16(data[i * item_size + 2:]),
"objectId": utils.to_s16(data[i * item_size + 4:]),
"objectRoomIndex": utils.to_s16(data[i * item_size + 6:]),
"subId": utils.to_s16(data[i * item_size + 8:]),
}
if item["x"] == -1 and item["y"] == -1:
break

View File

@ -1,6 +1,5 @@
#!/usr/bin/python3
import ctypes
import json
import os
import sys
@ -53,7 +52,6 @@ class PSXSegRoomdef(N64Segment):
f.write(json.dumps(data, indent=4))
def parse_roomdef(self, data: bytearray) -> list:
count = int(len(data) / item_size)
expected_data_size = count * item_size + 4
if len(data) != expected_data_size:

View File

@ -1,6 +1,5 @@
#!/usr/bin/python3
import ctypes
import json
import os
import sys
@ -11,29 +10,27 @@ sys.path.append(f"{os.getcwd()}/tools/n64splat")
sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from util import options
from segtypes.n64.segment import N64Segment
import utils
item_size = 0x18 # sizeof(SpritePart)
def parse_spriteparts(data: bytearray) -> list:
def to_s16(data):
return ctypes.c_short(data[0] | (data[1] << 8)).value
count = to_s16(data[0:])
count = utils.to_s16(data[0:])
items = []
for i in range(0, count):
items.append({
"flags": to_s16(data[2 + i * item_size + 0:]),
"offsetx": to_s16(data[2 + i * item_size + 2:]),
"offsety": to_s16(data[2 + i * item_size + 4:]),
"width": to_s16(data[2 + i * item_size + 6:]),
"height": to_s16(data[2 + i * item_size + 8:]),
"clut": to_s16(data[2 + i * item_size + 10:]),
"tileset": to_s16(data[2 + i * item_size + 12:]),
"left": to_s16(data[2 + i * item_size + 14:]),
"top": to_s16(data[2 + i * item_size + 16:]),
"right": to_s16(data[2 + i * item_size + 18:]),
"bottom": to_s16(data[2 + i * item_size + 20:]),
"flags": utils.to_s16(data[2 + i * item_size + 0:]),
"offsetx": utils.to_s16(data[2 + i * item_size + 2:]),
"offsety": utils.to_s16(data[2 + i * item_size + 4:]),
"width": utils.to_s16(data[2 + i * item_size + 6:]),
"height": utils.to_s16(data[2 + i * item_size + 8:]),
"clut": utils.to_s16(data[2 + i * item_size + 10:]),
"tileset": utils.to_s16(data[2 + i * item_size + 12:]),
"left": utils.to_s16(data[2 + i * item_size + 14:]),
"top": utils.to_s16(data[2 + i * item_size + 16:]),
"right": utils.to_s16(data[2 + i * item_size + 18:]),
"bottom": utils.to_s16(data[2 + i * item_size + 20:]),
})
return items

View File

@ -13,6 +13,8 @@ sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from util import options
from segtypes.n64.segment import N64Segment
from util.symbols import spim_context
import utils
def generate_assembly_spritepartslist(writer: io.BufferedWriter, name: str, content: str):
obj = json.loads(content)
@ -39,24 +41,18 @@ class PSXSegSpritepartslist(N64Segment):
path = self.src_path()
path.parent.mkdir(parents=True, exist_ok=True)
data = self.parse_spritepartslist(rom_bytes[self.rom_start:self.rom_end])
data = self.parse_spritepartslist(
rom_bytes[self.rom_start:self.rom_end])
with open(path, "w") as f:
f.write(json.dumps(data, indent=4))
def parse_spritepartslist(self, data: bytearray):
def to_u32(data):
return ctypes.c_uint32(
data[0] |
(data[1] << 8) |
(data[2] << 16) |
(data[3] << 24)).value
sprite_parts_list = [
0
]
idx = 1
while True:
addr = to_u32(data[idx * 4:])
addr = utils.to_u32(data[idx * 4:])
idx += 1
if addr == 0:
break

View File

@ -13,6 +13,8 @@ sys.path.append(f"{os.getcwd()}/tools/splat_ext")
from util import options
from segtypes.n64.segment import N64Segment
from util.symbols import spim_context
import utils
def generate_assembly_tiledef(writer: io.BufferedWriter, name: str, content: str):
obj = json.loads(content)
@ -46,18 +48,11 @@ class PSXSegTiledef(N64Segment):
f.write(json.dumps(data, indent=4))
def parse_tiledef(self, data: bytearray):
def to_u32(data):
return ctypes.c_uint32(
data[0] |
(data[1] << 8) |
(data[2] << 16) |
(data[3] << 24)).value
return {
"gfxPage": self.get_symbol(to_u32(data[0:])).given_name,
"gfxIndex": self.get_symbol(to_u32(data[4:])).given_name,
"clut": self.get_symbol(to_u32(data[8:])).given_name,
"collision": self.get_symbol(to_u32(data[12:])).given_name,
"gfxPage": self.get_symbol(utils.to_u32(data[0:])).given_name,
"gfxIndex": self.get_symbol(utils.to_u32(data[4:])).given_name,
"clut": self.get_symbol(utils.to_u32(data[8:])).given_name,
"collision": self.get_symbol(utils.to_u32(data[12:])).given_name,
}

103
tools/splat_ext/utils.py Normal file
View File

@ -0,0 +1,103 @@
import ctypes
subchar81_dict = {
0x44: 0x2E, # '.'
0x43: 0x2C, # ','
0x49: 0x21, # '!'
0x66: 0x27, # '''
0x68: 0x22, # '"'
0x69: 0x28, # '('
0x6A: 0x29, # ')'
0x6D: 0x5B, # '['
0x6E: 0x5D, # ']'
0x7C: 0x2D, # '-'
0x93: 0x25 # '%'
}
def from_s32(num):
return num.to_bytes(4, byteorder='little', signed=True)
def to_s32(data):
return int.from_bytes(data, byteorder='little', signed=True)
def from_u32(num):
return bytes([(num >> i) & 0xff for i in range(0, 32, 8)])
def to_u32(data):
return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24)
def from_16(num):
return bytes([num & 0xff, (num >> 8) & 0xff])
def to_s16(data):
return ctypes.c_int16(data[0] | (data[1] << 8)).value
def to_u16(data):
return data[0] | (data[1] << 8)
def from_u8(num):
return bytes([num])
def to_u8(data):
return data[0]
def from_bool(val):
return bytes([int(val)])
def to_bool(data):
return bool(data[0])
def from_ptr_str(ptr_str):
num = int(ptr_str, 16)
return from_u32(num)
def to_ptr_str(data):
return f"0x{to_u32(data):08X}"
def sotn_menu_name_to_str(data: bytearray) -> str:
end_of_str = data.find(0xFF)
assert (end_of_str >= 0)
if end_of_str == 0:
return ""
utf8 = bytearray(data[:end_of_str])
for i in range(len(utf8)):
utf8[i] += 0x20
return utf8.decode('utf-8')
def sotn_menu_desc_to_str(data: bytearray) -> str:
utf8 = []
while data[0] not in {0xFF, 0x00}:
if data[0] == 0x81:
ch = subchar81_dict.get(data[1])
if ch is None:
raise Exception(
f"subchar {data[0]:02X} {data[1]:02X} not recognised")
data = data[2:]
elif data[0] == 0x82:
if 0x4F <= data[1] <= 0x58:
ch = data[1] - 0x4F + 0x30 # from '0' to '9'
else:
raise Exception(
f"subchar {data[0]:02X} {data[1]:02X} not recognised")
data = data[2:]
else:
ch = data[0]
data = data[1:]
utf8.append(ch)
return bytes(utf8).decode('utf-8')