Skip to content

Commit

Permalink
0.16.1
Browse files Browse the repository at this point in the history
  • Loading branch information
ethteck committed Jul 24, 2023
1 parent 49ab5ec commit aab26aa
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 72 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# splat Release Notes

### 0.16.1
* Various changes so that series of image and palette subsegments can have `auto` rom addresses (as long as the first can find its rom address from the parent segment or its own definition)

### 0.16.0

* Add option `detect_redundant_function_end`. It tries to detect redundant and unreferenced functions ends and merge them together.
Expand Down Expand Up @@ -370,7 +373,7 @@ The `auto_all_sections` option, when set to true, will automatically add `all_`
* Code and ASM modes have been combined into the `code` mode
* BREAKING: The `name` attribute of a segment now should no longer be a subdirectory but rather a meaningful name for the segment which will be used as the name of the linker section. If your `name` was previously a directory, please change it into a `dir`.
* BREAKING: `subsections` has been renamed to `subsegments`
* New `dir` segment attribute specifies a subdirectory into which files will be saved. You can combine `dir` ("foo") with a subsection file name containing a subdirectory ("bar/out"), and the paths will be joined (foo/bar/out.c)
* New `dir` segment attribute specifies a subdirectory into which files will be saved. You can combine `dir` ("foo") with a subsegment name containing a subdirectory ("bar/out"), and the paths will be joined (foo/bar/out.c)
* If the `dir` attribute is specified but the `name` isn't, the `name` becomes `dir` with directory separation slashes replaced with underscores (foo/bar/baz -> foo_bar_baz)
* BREAKING: Many configuration options have been renamed. `_dir` options have been changed to the suffix `_path`.
* BREAKING: Assets (non-code, like `bin` and images) are now placed in the directory `asset_path` (defaults to `assets`).
Expand Down
38 changes: 24 additions & 14 deletions segtypes/common/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,12 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]:
# Mark any manually added dot types
cur_section = None

for i, subsection_yaml in enumerate(segment_yaml["subsegments"]):
for i, subsegment_yaml in enumerate(segment_yaml["subsegments"]):
# endpos marker
if isinstance(subsection_yaml, list) and len(subsection_yaml) == 1:
if isinstance(subsegment_yaml, list) and len(subsegment_yaml) == 1:
continue

typ = Segment.parse_segment_type(subsection_yaml)
typ = Segment.parse_segment_type(subsegment_yaml)
if typ.startswith("all_"):
typ = typ[4:]
if not typ.startswith("."):
Expand Down Expand Up @@ -218,15 +218,15 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]:

inserts = self.find_inserts(found_sections)

last_rom_end = 0
last_rom_end = None

for i, subsection_yaml in enumerate(segment_yaml["subsegments"]):
for i, subsegment_yaml in enumerate(segment_yaml["subsegments"]):
# endpos marker
if isinstance(subsection_yaml, list) and len(subsection_yaml) == 1:
if isinstance(subsegment_yaml, list) and len(subsegment_yaml) == 1:
continue

typ = Segment.parse_segment_type(subsection_yaml)
start = Segment.parse_segment_start(subsection_yaml)
typ = Segment.parse_segment_type(subsegment_yaml)
start = Segment.parse_segment_start(subsegment_yaml)

# Add dummy segments to be expanded later
if typ.startswith("all_"):
Expand All @@ -253,11 +253,21 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]:

end = self.get_next_seg_start(i, segment_yaml["subsegments"])

if (
isinstance(start, int)
and isinstance(prev_start, int)
and start < prev_start
):
if start is None:
# Attempt to infer the start address
if i == 0:
# The start address of this segment is the start address of the group
start = self.rom_start
else:
# The start address is the end address of the previous segment
start = last_rom_end

if start is not None and end is None:
est_size = segment_class.estimate_size(subsegment_yaml)
if est_size is not None:
end = start + est_size

if start is not None and prev_start is not None and start < prev_start:
log.error(
f"Error: Group segment {self.name} contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})"
)
Expand All @@ -274,7 +284,7 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]:
end = last_rom_end

segment: Segment = Segment.from_yaml(
segment_class, subsection_yaml, start, end, vram
segment_class, subsegment_yaml, start, end, vram
)

segment.sibling = base_segments.get(segment.name, None)
Expand Down
30 changes: 20 additions & 10 deletions segtypes/common/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,33 @@ def parse_subsegments(self, yaml) -> List[Segment]:
prev_start: Optional[int] = -1
last_rom_end = 0

for i, subsection_yaml in enumerate(yaml["subsegments"]):
for i, subsegment_yaml in enumerate(yaml["subsegments"]):
# endpos marker
if isinstance(subsection_yaml, list) and len(subsection_yaml) == 1:
if isinstance(subsegment_yaml, list) and len(subsegment_yaml) == 1:
continue

typ = Segment.parse_segment_type(subsection_yaml)
start = Segment.parse_segment_start(subsection_yaml)
typ = Segment.parse_segment_type(subsegment_yaml)
start = Segment.parse_segment_start(subsegment_yaml)

segment_class = Segment.get_class_for_type(typ)

end = self.get_next_seg_start(i, yaml["subsegments"])

if (
isinstance(start, int)
and isinstance(prev_start, int)
and start < prev_start
):
if start is None:
# Attempt to infer the start address
if i == 0:
# The start address of this segment is the start address of the group
start = self.rom_start
else:
# The start address is the end address of the previous segment
start = last_rom_end

if start is not None and end is None:
est_size = segment_class.estimate_size(subsegment_yaml)
if est_size is not None:
end = start + est_size

if start is not None and prev_start is not None and start < prev_start:
log.error(
f"Error: Group segment {self.name} contains subsegments which are out of ascending rom order (0x{prev_start:X} followed by 0x{start:X})"
)
Expand All @@ -82,7 +92,7 @@ def parse_subsegments(self, yaml) -> List[Segment]:
end = last_rom_end

segment: Segment = Segment.from_yaml(
segment_class, subsection_yaml, start, end, vram
segment_class, subsegment_yaml, start, end, vram
)
segment.parent = self
if segment.special_vram_segment:
Expand Down
67 changes: 41 additions & 26 deletions segtypes/n64/img.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Type, Optional
from typing import Dict, List, Tuple, Type, Optional, Union

from n64img.image import Image
from util import log, options
Expand All @@ -8,6 +8,15 @@


class N64SegImg(N64Segment):
@staticmethod
def parse_dimensions(yaml: Union[Dict, List]) -> Tuple[int, int]:
if isinstance(yaml, dict):
return yaml["width"], yaml["height"]
else:
if len(yaml) < 5:
log.error(f"Error: {yaml} is missing width and height parameters")
return yaml[3], yaml[4]

def __init__(
self,
rom_start: Optional[int],
Expand All @@ -29,46 +38,38 @@ def __init__(
yaml=yaml,
)

self.n64img: Image = img_cls(None, 0, 0)
if rom_start is None:
log.error(f"Error: {type} segment {name} rom start could not be determined")

if isinstance(yaml, dict):
if self.extract:
self.width = yaml["width"]
self.height = yaml["height"]
self.n64img: Image = img_cls(b"", 0, 0)

if isinstance(yaml, dict):
self.n64img.flip_h = bool(yaml.get("flip_x", False))
self.n64img.flip_v = bool(yaml.get("flip_y", False))
else:
if self.extract:
if len(yaml) < 5:
log.error(
f"Error: {self.name} is missing width and height parameters"
)
self.width = yaml[3]
self.height = yaml[4]

self.width, self.height = self.parse_dimensions(yaml)

self.n64img.width = self.width
self.n64img.height = self.height

self.check_len()

def check_len(self) -> None:
if self.extract:
expected_len = int(self.n64img.size())
assert isinstance(self.rom_start, int)
assert isinstance(self.rom_end, int)
assert isinstance(self.subalign, int)
actual_len = self.rom_end - self.rom_start
if actual_len > expected_len and actual_len - expected_len > self.subalign:
log.error(
f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}\n(hint: add a 'bin' segment after it)"
)
expected_len = int(self.n64img.size())
assert isinstance(self.rom_start, int)
assert isinstance(self.rom_end, int)
assert isinstance(self.subalign, int)
actual_len = self.rom_end - self.rom_start
if actual_len > expected_len and actual_len - expected_len > self.subalign:
log.error(
f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}\n(hint: add a 'bin' segment after it)"
)

def out_path(self) -> Path:
return options.opts.asset_path / self.dir / f"{self.name}.png"

def should_split(self) -> bool:
return self.extract and options.opts.is_mode_active("img")
return options.opts.is_mode_active("img")

def split(self, rom_bytes):
path = self.out_path()
Expand All @@ -77,8 +78,22 @@ def split(self, rom_bytes):
assert isinstance(self.rom_start, int)
assert isinstance(self.rom_end, int)

if self.n64img.data is None:
if self.n64img.data == b"":
self.n64img.data = rom_bytes[self.rom_start : self.rom_end]
self.n64img.write(path)

self.log(f"Wrote {self.name} to {path}")

@staticmethod
def estimate_size(yaml: Union[Dict, List]) -> int:
width, height = N64SegImg.parse_dimensions(yaml)
typ = N64Segment.parse_segment_type(yaml)

if typ == "ci4" or typ == "i4" or typ == "ia4":
return width * height // 2
elif typ in ("ia16", "rgba16"):
return width * height * 2
elif typ == "rgba32":
return width * height * 4
else:
return width * height
41 changes: 24 additions & 17 deletions segtypes/n64/palette.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from itertools import zip_longest
from pathlib import Path
from typing import List, Optional, Tuple, TYPE_CHECKING
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING, Union

from util import log, options
from util.color import unpack_color
Expand All @@ -16,6 +16,9 @@ def iter_in_groups(iterable, n, fillvalue=None):
return zip_longest(*args, fillvalue=fillvalue)


VALID_SIZES = [0x20, 0x40, 0x80, 0x100, 0x200]


class N64SegPalette(N64Segment):
require_unique_name = False

Expand All @@ -40,18 +43,21 @@ def __init__(self, *args, **kwargs):
f"segment {self.name} needs to know where it ends; add a position marker [0xDEADBEEF] after it"
)

if self.max_length() and isinstance(self.rom_end, int):
expected_len = int(self.max_length())
assert isinstance(self.rom_end, int)
assert isinstance(self.rom_start, int)
assert isinstance(self.subalign, int)
if not isinstance(self.yaml, dict) or "size" not in self.yaml:
assert self.rom_end is not None
assert self.rom_start is not None
actual_len = self.rom_end - self.rom_start
if (
actual_len > expected_len
and actual_len - expected_len > self.subalign
):

hint_msg = "(hint: add a 'bin' segment after it or specify the size in the segment)"

if actual_len > VALID_SIZES[-1]:
log.error(
f"Error: {self.name} (0x{actual_len:X} bytes) is too long, max 0x{VALID_SIZES[-1]:X})\n{hint_msg}"
)

if actual_len not in VALID_SIZES:
log.error(
f"Error: {self.name} should end at 0x{self.rom_start + expected_len:X}, but it ends at 0x{self.rom_end:X}\n(hint: add a 'bin' segment after it)"
f"Error: {self.name} (0x{actual_len:X} bytes) is not a valid palette size ({', '.join(hex(s) for s in VALID_SIZES)})\n{hint_msg}"
)

def split(self, rom_bytes):
Expand All @@ -66,9 +72,6 @@ def split(self, rom_bytes):
self.raster.extract = False

def parse_palette(self, rom_bytes) -> List[Tuple[int, int, int, int]]:
assert isinstance(self.rom_start, int)
assert isinstance(self.rom_end, int)

data = rom_bytes[self.rom_start : self.rom_end]
palette = []

Expand All @@ -77,9 +80,6 @@ def parse_palette(self, rom_bytes) -> List[Tuple[int, int, int, int]]:

return palette

def max_length(self):
return 256 * 2

def out_path(self) -> Path:
return options.opts.asset_path / self.dir / f"{self.name}.png"

Expand All @@ -97,3 +97,10 @@ def get_linker_entries(self):
self.get_linker_section(),
)
]

@staticmethod
def estimate_size(yaml: Union[Dict, List]) -> int:
if isinstance(yaml, dict):
if "size" in yaml:
return int(yaml["size"])
return 0x20
7 changes: 4 additions & 3 deletions segtypes/segment.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ def is_rodata() -> bool:
def is_noload() -> bool:
return False

@staticmethod
def estimate_size(yaml: Union[Dict, List]) -> Optional[int]:
return None

@property
def needs_symbols(self) -> bool:
return False
Expand Down Expand Up @@ -455,9 +459,6 @@ def log(self, msg):
def warn(self, msg: str):
self.warnings.append(msg)

def max_length(self):
return None

@staticmethod
def get_default_name(addr) -> str:
return f"{addr:X}"
Expand Down
2 changes: 1 addition & 1 deletion split.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from segtypes.segment import Segment
from util import log, options, palettes, symbols, relocs

VERSION = "0.16.0"
VERSION = "0.16.1"

parser = argparse.ArgumentParser(
description="Split a rom given a rom, a config, and output directory"
Expand Down

0 comments on commit aab26aa

Please sign in to comment.