Accessories extracted and building (#264)

Like Equipment, this will take the accessories, extract to a json in the
assets directory, and then when building the game, read items from that
json. Full list of changes required for this capability, and their
reasons/purposes:

Addition of entry in the makefile for the accessory json. This is
literally just the equipment one copied and the name changed.

Adjustment of splat extraction. Accessory data runs all the way up until
a bunch of strings which are now in 8258.data.s. Previously, the file
8000.data.s was created, and was just a whole bunch of uninterpreted
.word values. The accessory table runs all the way up to 0x8258.
Happily, this means there is no longer any gap between understood and
non-understood data in this region of the ROM.

Accessory.py is extremely similar to the existing Equipment.py. As
stated in the large comment at the top of this script, it would be neat
if we could combine the two scripts to one script that would process all
equippables in the game. For now, this is functional. Tidying this up
could be a nice future task, especially for someone new to the project
who is more comfortable with Python than C.

Finally, the dictionary of punctuation in the utils.py has been
expanded. The question mark shows up in the description of the Alucart
Mail "Resists fire, lightning, ice?", while the plus signs show up in
many items which provide stat bonuses that have descriptions like
"DEF+15". These symbols were previously unknown.

Feels very nice to now have equipment and accessories both extracting to
human-editable forms!
This commit is contained in:
bismurphy 2023-06-19 14:12:20 -04:00 committed by GitHub
parent 69efed1321
commit 60293618e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 114 additions and 2 deletions

View File

@ -528,6 +528,9 @@ $(BUILD_DIR)/$(ASSETS_DIR)/%.spriteparts.json.o: $(ASSETS_DIR)/%.spriteparts.jso
$(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)/%.accessory.json.o: $(ASSETS_DIR)/%.accessory.json
./tools/splat_ext/accessory.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

@ -35,8 +35,8 @@ segments:
- [0x3C40, data]
- [0x4A00, data]
- [0x4B04, equipment, equipments]
- [0x7718, raw, accessory_data]
- [0x8000, data]
- [0x7718, accessory, accessory_data]
- [0x8258, data]
- [0x8900, data]
- [0xCEB0, data]
- [0xD670, rodata] # not rodata but data

107
tools/splat_ext/accessory.py Executable file
View File

@ -0,0 +1,107 @@
#!/usr/bin/python3
#Note: This file was created by bismurphy. It is effectively the same as
#equipment.py, just with the current best-known state of the accessory
#struct subbed into the two locations where it comes up.
#Longer term, would be cool if equipment and accessories could be parsed
#by the same Python script, rather than tons of duplicate code.
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 = 0x20 # sizeof(Accessory)
def serialize_accessory(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_u32(item["unk08"])
serialized_data += utils.from_u32(item["unk0C"])
serialized_data += utils.from_u32(item["unk10"])
serialized_data += utils.from_u32(item["unk14"])
serialized_data += utils.from_16(item["menuIcon"])
serialized_data += utils.from_16(item["menuPalette"])
serialized_data += utils.from_u32(item["unk1C"])
expected_data_size = item_count * item_size
assert (len(serialized_data) == expected_data_size)
return serialized_data
class PSXSegAccessory(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}.accessory.json"
def split(self, rom_bytes):
path = self.src_path()
path.parent.mkdir(parents=True, exist_ok=True)
data = self.parse_accessory(
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_accessory(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:]),
"unk08": utils.to_u32(item_data[0x08:]),
"unk0C": utils.to_u32(item_data[0x0C:]),
"unk10": utils.to_u32(item_data[0x10:]),
"unk14": utils.to_u32(item_data[0x14:]),
"menuIcon": utils.to_u16(item_data[0x18:]),
"menuPalette": utils.to_u16(item_data[0x1A:]),
"unk1C": utils.to_u32(item_data[0x1C:]),
}
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_accessory(f_in.read())
with open(output_file_name, "wb") as f_out:
f_out.write(data)

View File

@ -3,6 +3,7 @@ import ctypes
subchar81_dict = {
0x44: 0x2E, # '.'
0x43: 0x2C, # ','
0x48: 0x3F, # '?'
0x49: 0x21, # '!'
0x66: 0x27, # '''
0x68: 0x22, # '"'
@ -10,6 +11,7 @@ subchar81_dict = {
0x6A: 0x29, # ')'
0x6D: 0x5B, # '['
0x6E: 0x5D, # ']'
0x7B: 0x2B, # '+'
0x7C: 0x2D, # '-'
0x93: 0x25 # '%'
}