Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion coilsnake/assets/modulelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ eb.MapSpriteModule
eb.DoorModule
eb.SpriteGroupModule
eb.MiscTablesModule
eb.ExpandedTablesModule
eb.SwirlModule
eb.TownMapIconModule
eb.DeathScreenModule
Expand Down
70 changes: 0 additions & 70 deletions coilsnake/modules/eb/ExpandedTablesModule.py

This file was deleted.

224 changes: 145 additions & 79 deletions coilsnake/modules/eb/MiscTablesModule.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,123 @@

from coilsnake.model.eb.table import eb_table_from_offset
from coilsnake.modules.eb.EbModule import EbModule
from coilsnake.util.common.yml import convert_values_to_hex_repr, replace_field_in_yml, yml_load, yml_dump
from coilsnake.util.eb.pointer import from_snes_address
from coilsnake.util.common.yml import (convert_values_to_hex_repr,
replace_field_in_yml, yml_dump,
yml_load)
from coilsnake.util.eb.pointer import (AsmPointerReference, XlPointerReference,
from_snes_address, to_snes_address)

log = logging.getLogger(__name__)

# this decorator is used to calculate the free ranges instead of hardcoding them.
# in 3.9 - 3.11, it's possible to combine @property and @classmethod, so we COULD just do that...
# however, for the sake for maintainability, a >3.11-style solution is here.
class classproperty:
def __init__(self, func):
self.fget = func
def __get__(self, instance, owner):
return self.fget(owner)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm misunderstanding something, you should be able to combine @staticmethod and @property, instead of using @classmethod.

This is the only spot in CoilSnake where FREE_RANGES is read from:

for free_range in module_class.FREE_RANGES:
and it's not called on an instance of the class

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been almost a year so I don't recall the details, but I remember this was giving me a headache. That means I probably landed on this for a reason, so a change would need to be tested

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something I didn't take into account going into this review was that TABLE_OFFSETS here is a member of the MiscTablesModule class, meaning it might not be as easy to refer to without a self or type(self) or whatever. (You could make TABLE_OFFSETS be a global variable to compensate, but then that would require changing every instance of self.TABLE_OFFSETS in the rest of the code, and probably make Mother 2 support harder down the line.)

A lot of bikeshedding is possible here since the code will probably need to change again once Mother 2 support is added. For now, I went with a simpler solution that doesn't involve any property methods, suggested by Coop. (I just double checked, and the function that initializes FREE_RANGES is only called once, so that's not a concern at least.)



class MiscTablesModule(EbModule):
NAME = "Miscellaneous Tables"
TABLE_OFFSETS = [
0xC3FD8D, # Attract mode text
0xD5F645, # Timed Item Delivery
0xE12F8A, # Photographer
0xD5EA77, # Condiment Table
0xD5EBAB, # Scripted Teleport Destination Table
0xD5F2FB, # Hotspots Table
0xC3F2B5, # Playable Character Graphics Control Table
0xD58A50, # PSI Abilities
0xD57B68, # Battle Actions Table
0xD5EA5B, # Statistic Growth Variables
0xD58F49, # Level-up EXP Table
0xD5F5F5, # Initial Stats Table
0xD57880, # PSI Teleport Destination Table
0xD57AAE, # Phone Contacts Table
0xD576B2, # Store Inventory Table
0xD5F4BB, # Timed Item Transformations
0xD5F4CF, # Don't Care
0xD55000, # Item Data
0xC23109, # Consolation Item
0xC3E250, # Windows
]
NAME = "Misc Tables"
# Key: offset
# Value: a list of PointerReference-like objects to apply patches to expand the table,
# or a falsy value. A falsy value will be considered an unexpandable table.
TABLE_OFFSETS = {
0xD58D7A: [ # PSI NAMES
AsmPointerReference(0x1c423)
],
0xCF8985: [ # NPC Configuration Table
AsmPointerReference(0x023E5), # in func C0222B/ Load NPCs
XlPointerReference(0x0C32E), # in func C0C30C
AsmPointerReference(0x131CA), # in func C13187/Talk to
AsmPointerReference(0x1327F), # in func C1323B/Check NPC
AsmPointerReference(0x1332F), # in func C1323B/Check NPC
XlPointerReference(0x1AD77), # in func C1AD42
AsmPointerReference(0x1B20B), # in func C1AF74/Use overworld item
AsmPointerReference(0x464C1), # in func C464B5/Create prepared NPC
AsmPointerReference(0x46831), # in func C4681A
AsmPointerReference(0x46930), # in func C46914
],
0xC3FD8D: False, # Attract mode text
0xD5F645: False, # Timed Item Delivery
0xE12F8A: False, # Photographer
0xD5EA77: False, # Condiment Table
0xD5EBAB: False, # Scripted Teleport Destination Table
0xD5F2FB: False, # Hotspots Table
0xC3F2B5: False, # Playable Character Graphics Control Table
0xD58A50: False, # PSI Abilities
0xD57B68: False, # Battle Actions Table
0xD5EA5B: False, # Statistic Growth Variables
0xD58F49: False, # Level-up EXP Table
0xD5F5F5: False, # Initial Stats Table
0xD57880: False, # PSI Teleport Destination Table
0xD57AAE: False, # Phone Contacts Table
0xD576B2: False, # Store Inventory Table
0xD5F4BB: False, # Timed Item Transformations
0xD5F4CF: False, # Don't Care
0xD55000: False, # Item Data
0xC23109: False, # Consolation Item
0xC3E250: False, # Windows
}

# we're going to calculate FREE_RANGES at runtime instead of hardcoding it.
# makes the code easier to maintain.
@classproperty # for an explanation of this decorator, see above
def FREE_RANGES(cls):
ranges = []
for offset, patches in cls.TABLE_OFFSETS.items():
if not patches: continue # non-expandable, doesn't free up space
table = eb_table_from_offset(offset)
ranges.append((from_snes_address(offset), from_snes_address(offset+table.size-1)))
Comment thread
PhoenixBound marked this conversation as resolved.
Outdated
return ranges


def __init__(self):
super(MiscTablesModule, self).__init__()
self.tables = [(from_snes_address(x), eb_table_from_offset(x)) for x in self.TABLE_OFFSETS]
self.tables = dict()
for table_offset in MiscTablesModule.TABLE_OFFSETS:
self.tables[table_offset] = eb_table_from_offset(table_offset)

def read_from_rom(self, rom):
for offset, table in self.tables:
table.from_block(rom, offset)
for offset, table in self.tables.items():
table.from_block(rom, from_snes_address(offset))

def write_to_rom(self, rom):
for offset, table in self.tables:
table.to_block(rom, offset)
for offset, table in self.tables.items():
# unexpanded tables should remain in place
if self.TABLE_OFFSETS[offset]:
new_table_offset = rom.allocate(size=table.size)
else:
new_table_offset = from_snes_address(offset)

table.to_block(rom, new_table_offset)
log.info("Writing table '{}' @ ".format(table.name) + hex(to_snes_address(new_table_offset)))
# Write pointers for expansion, only if there is a list of pointers. Otherwise consider it unexpanded
if self.TABLE_OFFSETS[offset]:
for pointer in self.TABLE_OFFSETS[offset]:
if pointer.validate_structure(rom):
pointer.write(rom, to_snes_address(new_table_offset))
else:
log.warn("Table relocation at %#x failed structure check - skipping...", pointer.offset)

def read_from_project(self, resource_open):
for _, table in self.tables:
for offset, table in self.tables.items():
with resource_open(table.name.lower(), "yml", True) as f:
log.debug("Reading {}.yml".format(table.name.lower()))
table.from_yml_file(f)
# expanded table - recreate it with our new number of rows
if self.TABLE_OFFSETS[offset]:
log.debug("Reading {}.yml as an expanded table".format(table.name.lower()))
yml_rep = yml_load(f)
num_rows = len(yml_rep)
table.recreate(num_rows=num_rows)
table.from_yml_rep(yml_rep)
# unexpanded table - standard process
else:
table.from_yml_file(f)

Comment thread
PhoenixBound marked this conversation as resolved.
Outdated
def write_to_project(self, resource_open):
for _, table in self.tables:
for table in self.tables.values():
with resource_open(table.name.lower(), "yml", True) as f:
table.to_yml_file(f)

Expand All @@ -61,20 +127,20 @@ def upgrade_project(self, old_version, new_version, rom, resource_open_r, resour
return
elif old_version == 3:
replace_field_in_yml(resource_name="item_configuration_table",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Effect",
new_key="Action")
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Effect",
new_key="Action")
replace_field_in_yml(resource_name="psi_ability_table",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Effect",
new_key="Action")
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Effect",
new_key="Action")
replace_field_in_yml(resource_name="psi_ability_table",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="PSI Name",
value_map={0: None,
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="PSI Name",
value_map={0: None,
1: 0,
2: 1,
3: 2,
Expand All @@ -98,22 +164,22 @@ def upgrade_project(self, old_version, new_version, rom, resource_open_r, resour
resource_delete("sound_stone_palette")

self.upgrade_project(old_version=old_version + 1,
new_version=new_version,
rom=rom,
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
resource_delete=resource_delete)
new_version=new_version,
rom=rom,
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
resource_delete=resource_delete)
elif old_version == 2:
replace_field_in_yml(resource_name="timed_delivery_table",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Suitable Area Text Pointer",
new_key="Delivery Success Text Pointer")
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Suitable Area Text Pointer",
new_key="Delivery Success Text Pointer")
replace_field_in_yml(resource_name="timed_delivery_table",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Unsuitable Area Text Pointer",
new_key="Delivery Failure Text Pointer")
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Unsuitable Area Text Pointer",
new_key="Delivery Failure Text Pointer")

with resource_open_r("timed_delivery_table", "yml", True) as f:
out = yml_load(f)
Expand All @@ -125,32 +191,32 @@ def upgrade_project(self, old_version, new_version, rom, resource_open_r, resour
f.write(yml_str_rep)

self.upgrade_project(old_version=old_version + 1,
new_version=new_version,
rom=rom,
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
resource_delete=resource_delete)
new_version=new_version,
rom=rom,
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
resource_delete=resource_delete)
elif old_version == 1:
replace_field_in_yml(resource_name="psi_ability_table",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Target",
new_key="Usability Outside of Battle",
value_map={"Nobody": "Other",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Target",
new_key="Usability Outside of Battle",
value_map={"Nobody": "Other",
"Enemies": "Unusable",
"Allies": "Usable"})
replace_field_in_yml(resource_name="battle_action_table",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Direction",
value_map={"Party": "Enemy",
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
key="Direction",
value_map={"Party": "Enemy",
"Enemy": "Party"})

self.upgrade_project(old_version=old_version + 1,
new_version=new_version,
rom=rom,
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
resource_delete=resource_delete)
new_version=new_version,
rom=rom,
resource_open_r=resource_open_r,
resource_open_w=resource_open_w,
resource_delete=resource_delete)
else:
self.upgrade_project(old_version + 1, new_version, rom, resource_open_r, resource_open_w, resource_delete)
self.upgrade_project(old_version + 1, new_version, rom, resource_open_r, resource_open_w, resource_delete)