forked from mirror/Archipelago
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
551 lines
20 KiB
Python
551 lines
20 KiB
Python
"""Library functions for pointer tables (Heavily WIP)."""
|
|
|
|
import js
|
|
import zlib
|
|
import gzip
|
|
from typing import Dict, List, Tuple, Union
|
|
from enum import IntEnum, auto
|
|
from randomizer.Patching.Patcher import ROM, LocalROM
|
|
from randomizer.Patching.Library.DataTypes import float_to_hex
|
|
|
|
|
|
class TableNames(IntEnum):
|
|
"""Pointer Table Enum."""
|
|
|
|
MusicMIDI = 0
|
|
MapGeometry = auto()
|
|
MapWalls = auto()
|
|
MapFloors = auto()
|
|
ModelTwoGeometry = auto()
|
|
ActorGeometry = auto()
|
|
Unknown6 = auto()
|
|
TexturesUncompressed = auto()
|
|
Cutscenes = auto()
|
|
Setups = auto()
|
|
InstanceScripts = auto()
|
|
Animations = auto()
|
|
Text = auto()
|
|
Unknown13 = auto()
|
|
TexturesHUD = auto()
|
|
Paths = auto()
|
|
Spawners = auto()
|
|
DKTVInputs = auto()
|
|
Triggers = auto()
|
|
Unknown19 = auto()
|
|
Unknown20 = auto()
|
|
Autowalks = auto()
|
|
Unknown22 = auto()
|
|
Exits = auto()
|
|
RaceCheckpoints = auto()
|
|
TexturesGeometry = auto()
|
|
UncompressedFileSizes = auto()
|
|
Unknown27 = auto()
|
|
Unknown28 = auto()
|
|
Unknown29 = auto()
|
|
Unknown30 = auto()
|
|
Unknown31 = auto()
|
|
|
|
|
|
class TableData:
|
|
"""Class to store information regarding a pointer table."""
|
|
|
|
def __init__(
|
|
self,
|
|
vanilla_compressed: bool,
|
|
rando_compressed: bool,
|
|
decode_function=None,
|
|
encode_function=None,
|
|
):
|
|
"""Initialize with given variables."""
|
|
self.vanilla_compressed = vanilla_compressed
|
|
self.rando_compressed = rando_compressed
|
|
self.decode_function = decode_function
|
|
self.encode_function = encode_function
|
|
|
|
|
|
def decoder_exits(data: bytes) -> dict:
|
|
"""Decode the exit data."""
|
|
exit_count = int(len(data) / 10)
|
|
ret = {"exits": []}
|
|
for exit_index in range(exit_count):
|
|
exit_data = {}
|
|
ret["exits"].append(exit_data)
|
|
return ret
|
|
|
|
|
|
table_functions = {
|
|
TableNames.MusicMIDI: TableData(False, False, None, None),
|
|
TableNames.MapGeometry: TableData(False, False, None, None),
|
|
TableNames.MapWalls: TableData(False, False, None, None),
|
|
TableNames.MapFloors: TableData(False, False, None, None),
|
|
TableNames.ModelTwoGeometry: TableData(False, False, None, None),
|
|
TableNames.ActorGeometry: TableData(False, False, None, None),
|
|
TableNames.Unknown6: TableData(False, False, None, None),
|
|
TableNames.TexturesUncompressed: TableData(False, False, None, None),
|
|
TableNames.Cutscenes: TableData(False, False, None, None),
|
|
TableNames.Setups: TableData(False, False, None, None),
|
|
TableNames.InstanceScripts: TableData(False, False, None, None),
|
|
TableNames.Animations: TableData(False, False, None, None),
|
|
TableNames.Text: TableData(False, False, None, None),
|
|
TableNames.Unknown13: TableData(False, False, None, None),
|
|
TableNames.TexturesHUD: TableData(False, False, None, None),
|
|
TableNames.Paths: TableData(False, False, None, None),
|
|
TableNames.Spawners: TableData(False, False, None, None),
|
|
TableNames.DKTVInputs: TableData(False, False, None, None),
|
|
TableNames.Triggers: TableData(False, False, None, None),
|
|
TableNames.Unknown19: TableData(False, False, None, None),
|
|
TableNames.Unknown20: TableData(False, False, None, None),
|
|
TableNames.Autowalks: TableData(False, False, None, None),
|
|
TableNames.Unknown22: TableData(False, False, None, None),
|
|
TableNames.Exits: TableData(False, False, None, None),
|
|
TableNames.RaceCheckpoints: TableData(False, False, None, None),
|
|
TableNames.TexturesGeometry: TableData(False, False, None, None),
|
|
TableNames.UncompressedFileSizes: TableData(False, False, None, None),
|
|
TableNames.Unknown27: TableData(False, False, None, None),
|
|
TableNames.Unknown28: TableData(False, False, None, None),
|
|
TableNames.Unknown29: TableData(False, False, None, None),
|
|
TableNames.Unknown30: TableData(False, False, None, None),
|
|
TableNames.Unknown31: TableData(False, False, None, None),
|
|
}
|
|
|
|
|
|
class PointerTableFile:
|
|
"""Class to store information about a pointer table file."""
|
|
|
|
def __init__(self, table: TableNames, start: int, end: int, is_compressed: bool = None):
|
|
"""Initialize with given parameters."""
|
|
self.start = start
|
|
self.end = end
|
|
self.size = end - start
|
|
self.is_compressed = is_compressed
|
|
if is_compressed is None:
|
|
self.is_compressed = table_functions[table].rando_compressed
|
|
|
|
|
|
def getPointerLocation(table: TableNames, file_index: int) -> int:
|
|
"""Get the address of a pointer table file."""
|
|
return js.pointer_addresses[table]["entries"][file_index]["pointing_to"]
|
|
|
|
|
|
def getPointerFile(table: TableNames, file_index: int, is_compressed: bool = None) -> PointerTableFile:
|
|
"""Get pointer table file information."""
|
|
start = getPointerLocation(table, file_index)
|
|
if "compressed_size" in js.pointer_addresses[table]["entries"][file_index]:
|
|
file_size = js.pointer_addresses[table]["entries"][file_index]["compressed_size"]
|
|
else:
|
|
file_end = getPointerLocation(table, file_index + 1)
|
|
file_size = file_end - start
|
|
end = start + file_size
|
|
return PointerTableFile(table, start, end, is_compressed)
|
|
|
|
|
|
def getPointerData(ROM_COPY: LocalROM | ROM, table: TableNames, file_index: int) -> bytes:
|
|
"""Get the data inside a pointer table file."""
|
|
ref_data = getPointerFile(table, file_index)
|
|
ROM_COPY.seek(ref_data.start)
|
|
data = ROM_COPY.readBytes(ref_data.size)
|
|
if ref_data.is_compressed:
|
|
return zlib.decompress(data, (15 + 32))
|
|
return data
|
|
|
|
|
|
def writePointerFile(ROM_COPY: LocalROM | ROM, table: TableNames, file_index: int, data: bytes, is_compressed: bool = False):
|
|
"""Write data to a pointer file."""
|
|
ref_file = getPointerFile(table, file_index)
|
|
if is_compressed:
|
|
data = gzip.compress(data, compresslevel=9)
|
|
if len(data) > ref_file.size:
|
|
raise Exception(f"Attempted to write data to a file slot which isn't big enough.\n- Table: {table}\n- File {file_index}\n- Attempt size {hex(len(data))}\n- Capacity: {hex(ref_file.size)}")
|
|
ROM_COPY.seek(ref_file.start)
|
|
ROM_COPY.writeBytes(data)
|
|
|
|
|
|
def decodeFile(ROM_COPY: LocalROM | ROM, table: TableNames, file_index: int) -> dict:
|
|
"""Decode a pointer table file."""
|
|
function_data = table_functions.get(table, (None, None))
|
|
file_data = getPointerData(ROM_COPY, table, file_index)
|
|
if function_data.decode_function is None:
|
|
return {
|
|
"data": file_data,
|
|
}
|
|
return function_data.decode_function(file_data)
|
|
|
|
|
|
def encodeFile(ROM_COPY: LocalROM | ROM, table: TableNames, file_index: int, data: dict):
|
|
"""Encode a pointer table file."""
|
|
function_data = table_functions.get(table, (None, None))
|
|
ref_data = getPointerFile(table, file_index)
|
|
if function_data.encode_function is None:
|
|
writePointerFile(ROM_COPY, table, file_index, data["data"], ref_data.is_compressed)
|
|
return
|
|
output_data = function_data.encode_function(data)
|
|
writePointerFile(ROM_COPY, table, file_index, output_data, ref_data.is_compressed)
|
|
|
|
|
|
def getRawFile(ROM_COPY: Union[ROM, LocalROM], table_index: TableNames, file_index: int, compressed: bool):
|
|
"""Get raw file from ROM."""
|
|
file_start = getPointerLocation(table_index, file_index)
|
|
if "compressed_size" in js.pointer_addresses[table_index]["entries"][file_index]:
|
|
file_size = js.pointer_addresses[table_index]["entries"][file_index]["compressed_size"]
|
|
if file_size is None:
|
|
return bytes(bytearray([]))
|
|
else:
|
|
file_end = getPointerLocation(table_index, file_index + 1)
|
|
file_size = file_end - file_start
|
|
ROM_COPY.seek(file_start)
|
|
data = ROM_COPY.readBytes(file_size)
|
|
if compressed:
|
|
data = zlib.decompress(data, (15 + 32))
|
|
return data
|
|
|
|
|
|
def writeRawFile(table_index: TableNames, file_index: int, compressed: bool, data: bytearray, ROM_COPY):
|
|
"""Write raw file from ROM."""
|
|
file_start = getPointerLocation(table_index, file_index)
|
|
if "compressed_size" in js.pointer_addresses[table_index]["entries"][file_index]:
|
|
file_size = js.pointer_addresses[table_index]["entries"][file_index]["compressed_size"]
|
|
else:
|
|
file_end = getPointerLocation(table_index, file_index + 1)
|
|
file_size = file_end - file_start
|
|
write_data = bytes(data)
|
|
if compressed:
|
|
write_data = gzip.compress(bytes(data), compresslevel=9)
|
|
if len(write_data) > file_size:
|
|
raise Exception(f"Cannot write file {file_index} in table {table_index} to ROM as it's too big.")
|
|
ROM_COPY.seek(file_start)
|
|
ROM_COPY.writeBytes(write_data)
|
|
|
|
|
|
icon_db = {
|
|
0x0: "waterfall_tall",
|
|
0x1: "waterfall_short",
|
|
0x2: "water",
|
|
0x3: "lava",
|
|
0x4: "sparkles",
|
|
0x5: "pop_explosion",
|
|
0x6: "lava_explosion",
|
|
0x7: "green_leaf?",
|
|
0x8: "brown_smoke_explosion",
|
|
0x9: "small_explosion",
|
|
0xA: "solar_flare?",
|
|
0xB: "splash",
|
|
0xC: "bubble",
|
|
0xD: "purple_sparkle",
|
|
0xE: "yellow_sparkle",
|
|
0xF: "green_sparkle",
|
|
0x10: "purple_sparkle",
|
|
0x11: "yellow_sparkle",
|
|
0x12: "green_sparkle",
|
|
0x13: "large_smoke_explosion",
|
|
0x14: "pink_implosion",
|
|
0x15: "brown_horizontal_spinning_plank",
|
|
0x16: "birch_horizontal_spinning_plank",
|
|
0x17: "brown_vertical_spinning_plank",
|
|
0x18: "star_water_ripple",
|
|
0x19: "circle_water_ripple",
|
|
0x1A: "small_smoke_explosion",
|
|
0x1B: "static_star",
|
|
0x1C: "static_z",
|
|
0x1D: "white_flare?",
|
|
0x1E: "static_rain?",
|
|
0x1F: "medium_smoke_explosion",
|
|
0x20: "bouncing_melon",
|
|
0x21: "vertical_rolling_melon",
|
|
0x22: "red_flare?",
|
|
0x23: "sparks",
|
|
0x24: "peanut",
|
|
0x25: "star_flare?",
|
|
0x26: "peanut_shell",
|
|
0x27: "small_explosion",
|
|
0x28: "large_smoke_implosion",
|
|
0x29: "blue_lazer",
|
|
0x2A: "pineapple",
|
|
0x2B: "fireball",
|
|
0x2C: "orange",
|
|
0x2D: "grape",
|
|
0x2E: "grape_splatter",
|
|
0x2F: "tnt_sparkle",
|
|
0x30: "fire_explosion",
|
|
0x31: "small_fireball",
|
|
0x32: "diddy_coin",
|
|
0x33: "chunky_coin",
|
|
0x34: "lanky_coin",
|
|
0x35: "dk_coin",
|
|
0x36: "tiny_coin",
|
|
0x37: "dk_coloured_banana",
|
|
0x38: "film",
|
|
0x39: "bouncing_orange",
|
|
0x3A: "crystal_coconut",
|
|
0x3B: "gb",
|
|
0x3C: "banana_medal",
|
|
0x3D: "diddy_coloured_banana",
|
|
0x3E: "chunky_coloured_banana",
|
|
0x3F: "lanky_coloured_banana",
|
|
0x40: "dk_coloured_banana",
|
|
0x41: "tiny_coloured_banana",
|
|
0x42: "exploded_krash_barrel_enemy",
|
|
0x43: "white_explosion_thing",
|
|
0x44: "coconut",
|
|
0x45: "coconut_shell",
|
|
0x46: "spinning_watermelon_slice",
|
|
0x47: "tooth",
|
|
0x48: "ammo_crate",
|
|
0x49: "race_coin",
|
|
0x4A: "lanky_bp",
|
|
0x4B: "cannonball",
|
|
0x4C: "crystal_coconut",
|
|
0x4D: "feather",
|
|
0x4E: "guitar_gazump",
|
|
0x4F: "bongo_blast",
|
|
0x50: "saxophone",
|
|
0x51: "triangle",
|
|
0x52: "trombone",
|
|
0x53: "waving_yellow_double_eighth_note",
|
|
0x54: "waving_yellow_single_eighth_note",
|
|
0x55: "waving_green_single_eighth_note",
|
|
0x56: "waving_purple_double_eighth_note",
|
|
0x57: "waving_red_double_eighth_note",
|
|
0x58: "waving_red_single_eighth_note",
|
|
0x59: "waving_white_double_eighth_note",
|
|
0x5A: "diddy_bp",
|
|
0x5B: "chunky_bp",
|
|
0x5C: "dk_bp",
|
|
0x5D: "tiny_bp",
|
|
0x5E: "spinning_sparkle",
|
|
0x5F: "static_rain?",
|
|
0x60: "translucent_water",
|
|
0x61: "unk61",
|
|
0x62: "black_screen",
|
|
0x63: "white_cloud",
|
|
0x64: "thin_lazer",
|
|
0x65: "blue_bubble",
|
|
0x66: "white_faded_circle",
|
|
0x67: "white_circle",
|
|
0x68: "grape_particle?",
|
|
0x69: "spinning_blue_sparkle",
|
|
0x6A: "white_smoke_explosion",
|
|
0x6B: "l-r_joystick",
|
|
0x6C: "fire_wall",
|
|
0x6D: "static_rain_bubble",
|
|
0x6E: "a_button",
|
|
0x6F: "b_button",
|
|
0x70: "z_button",
|
|
0x71: "c_down_button",
|
|
0x72: "c_up_button",
|
|
0x73: "c_left_button",
|
|
0x74: "acid",
|
|
0x75: "acid_explosion",
|
|
0x76: "race_hoop",
|
|
0x77: "acid_goop?",
|
|
0x78: "unk78",
|
|
0x79: "broken_bridge?",
|
|
0x7A: "white_pole?",
|
|
0x7B: "bridge_chip?",
|
|
0x7C: "wooden_beam_with_rivets",
|
|
0x7D: "chunky_bunch",
|
|
0x7E: "diddy_bunch",
|
|
0x7F: "lanky_bunch",
|
|
0x80: "dk_bunch",
|
|
0x81: "tiny_bunch",
|
|
0x82: "chunky_balloon",
|
|
0x83: "diddy_balloon",
|
|
0x84: "dk_balloon",
|
|
0x85: "lanky_balloon",
|
|
0x86: "tiny_balloon",
|
|
0x87: "r_button",
|
|
0x88: "l_button",
|
|
0x89: "fairy",
|
|
0x8A: "boss_key",
|
|
0x8B: "crown",
|
|
0x8C: "rareware_coin",
|
|
0x8D: "nintendo_coin",
|
|
0x8E: "no_symbol",
|
|
0x8F: "headphones",
|
|
0x90: "opaque_blue_water",
|
|
0x91: "start_button",
|
|
0x92: "white_question_mark",
|
|
0x93: "candy_face",
|
|
0x94: "cranky_face",
|
|
0x95: "snide_face",
|
|
0x96: "funky_face",
|
|
0x97: "left_arrow",
|
|
0x98: "white_spark?",
|
|
0x99: "black_boulder_chunk",
|
|
0x9A: "green_boulder_chunk",
|
|
0x9B: "wood_chip",
|
|
0x9C: "snowflake/dandelion",
|
|
0x9D: "static_water?",
|
|
0x9E: "spinning_leaf",
|
|
0x9F: "flashing_water?",
|
|
0xA0: "rainbow_coin",
|
|
0xA1: "shockwave_orange_particle",
|
|
0xA2: "implosion?",
|
|
0xA3: "rareware_employee_face",
|
|
0xA4: "smoke",
|
|
0xA5: "static_smoke?",
|
|
0xA6: "barrel_bottom_chunk",
|
|
0xA7: "scoff_face",
|
|
0xA8: "multicoloured_bunch",
|
|
0xA9: "dk_face",
|
|
0xAA: "diddy_face",
|
|
0xAB: "lanky_face",
|
|
0xAC: "tiny_face",
|
|
0xAD: "chunky_face",
|
|
0xAE: "fairy_tick",
|
|
0xAF: "wrinkly",
|
|
}
|
|
|
|
|
|
def grabText(ROM_COPY: Union[ROM, LocalROM], file_index: int) -> List[List[Dict[str, List[str]]]]:
|
|
"""Pull text from ROM with a particular file index."""
|
|
file_start = getPointerLocation(TableNames.Text, file_index)
|
|
ROM_COPY.seek(file_start + 0)
|
|
count = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
text = []
|
|
text_data = []
|
|
text_start = (count * 0xF) + 3
|
|
data_start = 1
|
|
for i in range(count):
|
|
ROM_COPY.seek(file_start + data_start)
|
|
section_1_count = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
section_2_count = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
section_3_count = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
ROM_COPY.seek(file_start + data_start + 5)
|
|
start = int.from_bytes(ROM_COPY.readBytes(2), "big")
|
|
int.from_bytes(ROM_COPY.readBytes(2), "big")
|
|
block_start = 1
|
|
blocks = []
|
|
for k in range(section_1_count):
|
|
ROM_COPY.seek(file_start + data_start + block_start)
|
|
sec2ct = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
offset = 0
|
|
if (sec2ct & 4) != 0:
|
|
offset += 4
|
|
text_blocks = []
|
|
if (sec2ct & 1) == 0:
|
|
if (sec2ct & 2) != 0:
|
|
ROM_COPY.seek(file_start + data_start + block_start + offset + 1)
|
|
sec3ct = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
for j in range(sec3ct):
|
|
_block = block_start + 2 + offset + (4 * j) - 1
|
|
ROM_COPY.seek(file_start + data_start + _block)
|
|
_pos = int.from_bytes(ROM_COPY.readBytes(2), "big")
|
|
ROM_COPY.seek(file_start + data_start + _block)
|
|
_dat = int.from_bytes(ROM_COPY.readBytes(4), "big")
|
|
text_blocks.append(
|
|
{
|
|
"type": "sprite",
|
|
"position": _pos,
|
|
"data": hex(_dat),
|
|
"sprite": icon_db[(_dat >> 8) & 0xFF],
|
|
}
|
|
)
|
|
added = block_start + 2 + offset + (4 * sec3ct) + 4
|
|
else:
|
|
ROM_COPY.seek(file_start + data_start + block_start + offset + 1)
|
|
sec3ct = int.from_bytes(ROM_COPY.readBytes(1), "big")
|
|
for j in range(sec3ct):
|
|
_block = block_start + 2 + offset + (8 * j) - 1
|
|
ROM_COPY.seek(file_start + data_start + _block + 3)
|
|
_start = int.from_bytes(ROM_COPY.readBytes(2), "big")
|
|
ROM_COPY.seek(file_start + data_start + _block + 5)
|
|
_size = int.from_bytes(ROM_COPY.readBytes(2), "big")
|
|
text_blocks.append({"type": "normal", "start": _start, "size": _size})
|
|
added = block_start + 2 + offset + (8 * sec3ct) + 4
|
|
# print(f"File {file_index}, Textbox {i}, section {k}")
|
|
blocks.append(
|
|
{
|
|
"block_start": hex(block_start + data_start),
|
|
"section2count": sec2ct,
|
|
"section3count": sec3ct,
|
|
"offset": offset,
|
|
"text": text_blocks,
|
|
}
|
|
)
|
|
block_start = added
|
|
ROM_COPY.seek(file_start + data_start)
|
|
if added < data_start:
|
|
info = b""
|
|
else:
|
|
info = ROM_COPY.readBytes(added - data_start)
|
|
text_data.append(
|
|
{
|
|
"arr": info,
|
|
"text": blocks,
|
|
"section1count": section_1_count,
|
|
"section2count": section_2_count,
|
|
"section3count": section_3_count,
|
|
"data_start": hex(data_start),
|
|
}
|
|
)
|
|
text_start += added - data_start
|
|
data_start += block_start
|
|
for item in text_data:
|
|
text_block = []
|
|
# print(item)
|
|
for item2 in item["text"]:
|
|
# print(item2)
|
|
temp = []
|
|
for item3 in item2["text"]:
|
|
if item3["type"] == "normal":
|
|
start = item3["start"] + data_start + 2
|
|
# print(hex(start))
|
|
start + item3["size"]
|
|
ROM_COPY.seek(file_start + start)
|
|
temp.append(ROM_COPY.readBytes(item3["size"]).decode())
|
|
elif item3["type"] == "sprite":
|
|
temp.append(item3["sprite"])
|
|
# print(fh.read(item3["size"]))
|
|
text_block.append(temp)
|
|
text.append(text_block)
|
|
formatted_text = []
|
|
for t in text:
|
|
y = []
|
|
for x in t:
|
|
y.append({"text": x})
|
|
formatted_text.append(y)
|
|
return formatted_text
|
|
|
|
|
|
def writeText(ROM_COPY: Union[ROM, LocalROM], file_index: int, text: List[Union[List[Dict[str, List[str]]], Tuple[Dict[str, List[str]]]]]) -> None:
|
|
"""Write the text to ROM."""
|
|
text_start = getPointerLocation(TableNames.Text, file_index)
|
|
ROM_COPY.seek(text_start)
|
|
ROM_COPY.writeBytes(bytearray([len(text)]))
|
|
position = 0
|
|
for textbox in text:
|
|
ROM_COPY.writeBytes(len(textbox).to_bytes(1, "big"))
|
|
for block in textbox:
|
|
# Get Icon State
|
|
icon_id = -1
|
|
for string in block["text"]:
|
|
if string in icon_db.values():
|
|
for icon in icon_db:
|
|
if icon_db[icon] == string:
|
|
icon_id = icon
|
|
if icon_id > -1:
|
|
ROM_COPY.writeBytes(bytearray([2, 1]))
|
|
ROM_COPY.writeBytes(icon_id.to_bytes(2, "big"))
|
|
ROM_COPY.writeBytes(bytearray([0, 0]))
|
|
else:
|
|
ROM_COPY.writeBytes(bytearray([1, len(block["text"])]))
|
|
for string in block["text"]:
|
|
ROM_COPY.writeBytes(position.to_bytes(4, "big"))
|
|
ROM_COPY.writeBytes(len(string).to_bytes(2, "big"))
|
|
ROM_COPY.writeBytes(bytearray([0, 0]))
|
|
position += len(string)
|
|
unk0 = 0
|
|
if "unk0" in block:
|
|
unk0 = block["unk0"]
|
|
ROM_COPY.writeBytes(int(float_to_hex(unk0), 16).to_bytes(4, "big"))
|
|
ROM_COPY.writeBytes(bytearray(position.to_bytes(2, "big")))
|
|
for textbox in text:
|
|
for block in textbox:
|
|
is_icon = False
|
|
for string in block["text"]:
|
|
if string in icon_db.values():
|
|
is_icon = True
|
|
if not is_icon:
|
|
for string in block["text"]:
|
|
ROM_COPY.writeBytes(string.encode("ascii"))
|