diff --git a/tools/asset_extractor/.gitignore b/tools/asset_extractor/.gitignore deleted file mode 100644 index 8eee4c12..00000000 --- a/tools/asset_extractor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -!asset_extractor.py -!assets/*.py \ No newline at end of file diff --git a/tools/asset_extractor/Makefile b/tools/asset_extractor/Makefile deleted file mode 100644 index ff13f80b..00000000 --- a/tools/asset_extractor/Makefile +++ /dev/null @@ -1 +0,0 @@ -all: \ No newline at end of file diff --git a/tools/asset_extractor/asset_extractor.py b/tools/asset_extractor/asset_extractor.py deleted file mode 100644 index a8d87072..00000000 --- a/tools/asset_extractor/asset_extractor.py +++ /dev/null @@ -1,247 +0,0 @@ -from pathlib import Path -import os -import sys -import subprocess -from distutils.util import strtobool -import json - -from assets.frame_obj_lists import FrameObjLists -from assets.animation import Animation -from assets.exit_list import ExitList -from assets.sprite_frame import SpriteFrame - -verbose = False - -def extract_assets(variant, assets_folder): - print(f'Extract assets from {variant}.', flush=True) - map = { - 'USA': 'baserom.gba', - 'EU': 'baserom_eu.gba', - 'JP': 'baserom_jp.gba', - 'DEMO_USA': 'baserom_demo.gba', - 'DEMO_JP': 'baserom_demo_jp.gba', - } - - if not os.path.exists(map[variant]): - print(f'Error: Baserom {map[variant]} is missing.', file=sys.stderr) - exit(1) - - baserom = None - baserom_path = map[variant] - with open(baserom_path, 'rb') as file: - baserom = bytearray(file.read()) - - - # Handle all json config files in the assets folder - configs = [x for x in os.listdir('assets') if x.endswith('.json')] # TODO this would break with a folder that is named .json - - print(configs) - - for config in configs: - path = os.path.join('assets', config) - config_modified = os.path.getmtime(path) - - with open(path) as file: - current_offset = 0 - #print('Parsing yaml...', flush=True) - #assets = yaml.safe_load(file) - #print('done', flush=True) - print(f'Parsing {config}...', flush=True) - assets = json.load(file) - print('done', flush=True) - for asset in assets: - if 'offsets' in asset: # Offset definition - if variant in asset['offsets']: - current_offset = asset['offsets'][variant] - elif 'path' in asset: # Asset definition - - if 'variants' in asset: - if variant not in asset['variants']: - # This asset is not used in the current variant - continue - - path = os.path.join(assets_folder, asset['path']) - - extract_file = False - - if os.path.isfile(path): - file_modified = os.path.getmtime(path) - if file_modified < config_modified: - if verbose: - print(f'{path} was created before the config was modified.') - extract_file = True - # TODO Extract when source file (depends on type) was modified after target file - #print(f'{file_modified} {config_modified}') - else: - if verbose: - print(f'{path} does not yet exist.') - extract_file = True - - - if extract_file: - if verbose: - print(f'Extracting {path}...') - - start = 0 - if 'start' in asset: - # Apply offset to the start of the USA variant - start = asset['start'] + current_offset - elif 'starts' in asset: - # Use start for the current variant - start = asset['starts'][variant] - - mode = '' - if 'type' in asset: - mode = asset['type'] - - Path(os.path.dirname(path)).mkdir(parents=True, exist_ok=True) - - if 'size' in asset: # The asset has a size and want to be extracted first. - size = asset['size'] # TODO can different sizes for the different variants ever occur? - - with open(path, 'wb') as output: - output.write(baserom[start:start+size]) - # If an asset has no size, the extraction tool reads the baserom iself. - - options = asset['options'] if 'options' in asset else [] - - if mode == 'tileset': - extract_tileset(path) - elif mode == 'palette': - extract_palette(path) - elif mode == 'graphic' or mode == 'gfx': - extract_graphic(path, options) - elif mode == 'midi': - extract_midi(path, baserom_path, start, options) - elif mode == 'aif': - extract_aif(path, options) - elif mode == 'frame_obj_lists': - frame_obj_lists = FrameObjLists(path, start, size, options) - frame_obj_lists.extract_binary(baserom) - elif mode == 'animation': - animation = Animation(path, start, size, options) - animation.extract_binary(baserom) - elif mode == 'exit_list': - exit_list = ExitList(path, start, size, options) - exit_list.extract_binary(baserom) - elif mode == 'sprite_frames': - sprite_frames = SpriteFrame(path, start, size, options) - sprite_frames.extract_binary(baserom) - elif mode == 'unknown': - pass - elif mode != '': - print(f'Asset type {mode} not yet implemented') - - - - -def run_gbagfx(path_in, path_out, options): - subprocess.check_call([os.path.join('tools', 'gbagfx', 'gbagfx'), path_in, path_out] + options) - -def extract_tileset(path): - assert(path.endswith('.4bpp.lz') or path.endswith('.8bpp.lz')) - base = path[0:-8] - - bpp_extension = path[-8:-3] - # subprocess.call(['cp', path, path+'.bkp']) - run_gbagfx(path, base+bpp_extension, []) # decompress - run_gbagfx(base+bpp_extension, base+'.png', ['-mwidth', '32']) # convert to png - # TODO automatically generate tileset entries from tileset_headers.s - # TODO Possible to set the correct palette? Or not, because there is a set of palettes that can be chosen and the correct palette is only defined by the metatile? - -def extract_palette(path): - assert(path.endswith('.gbapal')) - base = path[0:-7] - run_gbagfx(path, base+'.pal', []) - -def extract_graphic(path, options): - if path.endswith('.4bpp'): - base = path[0:-5] - elif path.endswith('.4bpp.lz'): - base = path[0:-8] - elif path.endswith('.8bpp'): - base = path[0:-5] - elif path.endswith('.8bpp.lz'): - base = path[0:-8] - else: - assert False, f'Invalid graphic extension {path}' - params = [] - for key in options: - params.append('-'+key) - params.append(str(options[key])) - run_gbagfx(path, base+'.png', params) - -def extract_midi(path, baserom_path, start, options): - assert(path.endswith('.s')) - base = path[0:-2] - - common_params = [] - agb2mid_params = [] - - exactGateTime = True # Set exactGateTime by default - - for key in options: - if key == 'group' or key == 'G': - common_params.append('-G') - common_params.append(str(options[key])) - elif key == 'priority' or key == 'P': - common_params.append('-P') - common_params.append(str(options[key])) - elif key == 'reverb' or key == 'R': - common_params.append('-R') - common_params.append(str(options[key])) - elif key == 'nominator': - agb2mid_params.append('-n') - agb2mid_params.append(str(options[key])) - elif key == 'denominator': - agb2mid_params.append('-d') - agb2mid_params.append(str(options[key])) - elif key == 'timeChanges': - changes = options['timeChanges'] - if isinstance(changes, list): - # Multiple time changes - for change in changes: - agb2mid_params.append('-t') - agb2mid_params.append(str(change['nominator'])) - agb2mid_params.append(str(change['denominator'])) - agb2mid_params.append(str(change['time'])) - else: - agb2mid_params.append('-t') - agb2mid_params.append(str(changes['nominator'])) - agb2mid_params.append(str(changes['denominator'])) - agb2mid_params.append(str(changes['time'])) - elif key == 'exactGateTime': - if options[key] == 1: - exactGateTime = True - elif options[key] == 0: - exactGateTime = False - else: - exactGateTime = strtobool(options[key]) - else: - common_params.append('-'+key) - common_params.append(str(options[key])) - - if exactGateTime: - common_params.append('-E') - - # To midi - subprocess.check_call([os.path.join('tools', 'agb2mid', 'agb2mid'), baserom_path, hex(start), baserom_path, base+'.mid'] + common_params + agb2mid_params) - # To assembly (TODO only do in build step, not if only extracting) - subprocess.check_call([os.path.join('tools', 'mid2agb', 'mid2agb'), base+'.mid', path] + common_params) - -def extract_aif(path, options): - assert(path.endswith('.bin')) - base = path[0:-4] - subprocess.check_call([os.path.join('tools', 'aif2pcm', 'aif2pcm'), path, base+'.aif']) - - -def main(): - if len(sys.argv) == 1: - extract_assets('USA') - elif len(sys.argv) == 3: - extract_assets(sys.argv[1].upper(), sys.argv[2]) - else: - print('Usage: asset_extractor.py VARIANT BUILD_FOLDER') - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/tools/asset_extractor/assets/animation.py b/tools/asset_extractor/assets/animation.py deleted file mode 100644 index f38cd4ff..00000000 --- a/tools/asset_extractor/assets/animation.py +++ /dev/null @@ -1,32 +0,0 @@ -from assets.base import BaseAsset, Reader, opt_param - -class Animation(BaseAsset): - def __init__(self, path: str, addr: int, size: int, options: any) -> None: - super().__init__(path, addr, size, options) - - def extract_binary(self, rom: bytearray) -> None: - reader = Reader(rom[self.addr:self.addr+self.size]) - lines = [] - end_of_animation = False - while not end_of_animation and reader.cursor+3 < self.size: - frame_index = reader.read_u8() - keyframe_duration = reader.read_u8() - bitfield = reader.read_u8() - bitfield2 = reader.read_u8() - - end_of_animation = bitfield2 & 0x80 != 0 - line = f'\tkeyframe frame_index={frame_index}' - line += opt_param('duration', '0', str(keyframe_duration)) - line += opt_param('bitfield', '0x0', hex(bitfield)) - line += opt_param('bitfield2', '0x0', hex(bitfield2)) - lines.append(line + '\n') - if not end_of_animation: - lines.append('@ TODO why no terminator?\n') - while reader.cursor < self.size: - keyframe_count = reader.read_u8() - lines.append(f'\t.byte {keyframe_count} @ keyframe count\n') - - assert(self.path.endswith('.bin')) - path = self.path[0:-4] + '.s' - with open(path, 'w') as file: - file.writelines(lines) \ No newline at end of file diff --git a/tools/asset_extractor/assets/base.py b/tools/asset_extractor/assets/base.py deleted file mode 100644 index a9398b41..00000000 --- a/tools/asset_extractor/assets/base.py +++ /dev/null @@ -1,56 +0,0 @@ -ROM_OFFSET = 0x8000000 - -class BaseAsset: - def __init__(self, path: str, addr: int, size: int, options: any) -> None: - self.path = path - self.addr = addr - self.size = size - self.options = options - - def extract_binary(self, rom: bytearray) -> None: - pass - - def convert(self) -> None: - pass - -class Reader: - def __init__(self, data: bytearray) -> None: - self.data = data - self.cursor = 0 - self.bitfield = 0 - self.bitfield_remaining = 0 - - def read_u8(self) -> int: - val = self.data[self.cursor] - self.cursor += 1 - return val - - def read_s8(self) -> int: - val = self.data[self.cursor] - self.cursor += 1 - if val > 127: - return val-256 - else: - return val - - def read_u16(self) -> int: - val = self.data[self.cursor:self.cursor+2] - self.cursor += 2 - return int.from_bytes(val, 'little') - - def read_s16(self) -> int: - val = self.read_u16() - if val > 32768: - return val - 65536 - else: - return val - - def read_u32(self) -> int: - val = self.data[self.cursor:self.cursor+4] - self.cursor += 4 - return int.from_bytes(val, 'little') - -def opt_param(name: str, default: str, value: str) -> str: - if value != default: - return f', {name}={value}' - return '' \ No newline at end of file diff --git a/tools/asset_extractor/assets/exit_list.py b/tools/asset_extractor/assets/exit_list.py deleted file mode 100644 index c7c92ffe..00000000 --- a/tools/asset_extractor/assets/exit_list.py +++ /dev/null @@ -1,55 +0,0 @@ -from assets.base import BaseAsset, Reader, opt_param - -class ExitList(BaseAsset): - def __init__(self, path: str, addr: int, size: int, options: any) -> None: - super().__init__(path, addr, size, options) - - def extract_binary(self, rom: bytearray) -> None: - reader = Reader(rom[self.addr:self.addr+self.size]) - - lines = [] - - while reader.cursor < self.size: - transition_type = reader.read_u16() - x_pos = reader.read_u16() - y_pos = reader.read_u16() - dest_x = reader.read_u16() - dest_y = reader.read_u16() - screen_edge = reader.read_u8() - dest_area = reader.read_u8() - dest_room = reader.read_u8() - unknown_2 = reader.read_u8() - unknown_3 = reader.read_u8() - unknown_4 = reader.read_u8() - unknown_5 = reader.read_u16() - padding_1 = reader.read_u16() - if transition_type == 0xffff: - lines.append(f'\texit_list_end\n') - break - # lines.append(f'\t.2byte {transition_type} @ transition_type\n') - # lines.append(f'\t.2byte {x_pos}, {y_pos} @ pos\n') - # lines.append(f'\t.2byte {dest_x}, {dest_y} @ dest\n') - # lines.append(f'\t.byte {screen_edge} @ screen edge\n') - # lines.append(f'\t.byte {dest_area} @ screen edge\n') - # lines.append(f'\t.byte {dest_room} @ screen edge\n') - # lines.append(f'\t.byte {unknown_2}, {unknown_3}, {unknown_4} @ unknown\n') - # lines.append(f'\t.2byte {unknown_5}, {padding_1} @ unknown\n') - line = f'\texit transition={transition_type}' - line += opt_param('x', '0x0', hex(x_pos)) - line += opt_param('y', '0x0', hex(y_pos)) - line += opt_param('destX', '0x0', hex(dest_x)) - line += opt_param('destY', '0x0', hex(dest_y)) - line += opt_param('screenEdge', '0x0', hex(screen_edge)) - line += opt_param('destArea', '0x0', hex(dest_area)) - line += opt_param('destRoom', '0x0', hex(dest_room)) - line += opt_param('unknown', '0x0', hex(unknown_2)) - line += opt_param('unknown2', '0x0', hex(unknown_3)) - line += opt_param('unknown3', '0x0', hex(unknown_4)) - line += opt_param('unknown4', '0x0', hex(unknown_5)) - line += opt_param('padding', '0x0', hex(padding_1)) - lines.append(line + '\n') - - assert(self.path.endswith('.bin')) - path = self.path[0:-4] + '.s' - with open(path, 'w') as file: - file.writelines(lines) \ No newline at end of file diff --git a/tools/asset_extractor/assets/frame_obj_lists.py b/tools/asset_extractor/assets/frame_obj_lists.py deleted file mode 100644 index 5b284aa1..00000000 --- a/tools/asset_extractor/assets/frame_obj_lists.py +++ /dev/null @@ -1,105 +0,0 @@ -from assets.base import BaseAsset, Reader, opt_param - -class FrameObjLists(BaseAsset): - def __init__(self, path: str, addr: int, size: int, options: any) -> None: - super().__init__(path, addr, size, options) - - def extract_binary(self, rom: bytearray) -> None: - reader = Reader(rom[self.addr:self.addr+self.size]) - - first_level = [] - second_level = [] - - lines = [] - lines.append('@ First level of offsets\n') - while True: - if reader.cursor in first_level: - #print(f'first_level up to: {reader.cursor}') - break - pointer = reader.read_u32() - first_level.append(pointer) - lines.append(f'\t.4byte {hex(pointer)}\n') - - #print(first_level) - lines.append('\n@ Second level of offsets\n') - while True: - #print(reader.cursor) - #if reader.cursor >= 24372: - #print(f'>< second_level up to: {reader.cursor}') - # - # break - if reader.cursor in second_level: - #print(f'second_level up to: {reader.cursor}') - break - pointer = reader.read_u32() - second_level.append(pointer) - lines.append(f'\t.4byte {hex(pointer)}\n') - #print(second_level) - - obj_lists = [] - last_second_level = max(second_level) - lines.append('\n@ Frame obj lists\n') - while True: - if reader.cursor > last_second_level: - #print(f'No longer in second level: {reader.cursor}') - break - if reader.cursor not in second_level: - #print(f'{reader.cursor} not in second_level {num_objects}') - next = -1 - for i in second_level: - if i > reader.cursor: - if next == -1 or i < next: - next = i - - diff = next-reader.cursor - #print(f'Skipping forward to {next} (+{diff})') - lines.append(f'@ Skipping {diff} bytes\n') - bytes = [] - for i in range(diff): - bytes.append(reader.read_u8()) - lines.append('\t.byte ' + ', '.join(str(x) for x in bytes) + '\n') - num_objects = reader.read_u8() - lines.append(f'\t.byte {num_objects} @ num_objs\n') - if num_objects > 200: - #print(f'num_objects: {num_objects} @{reader.cursor}/{last_second_level}') - break - list = [] - #print(num_objects) - for i in range(num_objects): - x_offset = reader.read_s8() - y_offset = reader.read_s8() - bitfield = reader.read_u8() - bitfield2 = reader.read_u16() - - # lines.append(f'\t.byte {x_offset}, {y_offset}, {hex(bitfield)}\n') - # lines.append(f'\t.2byte {hex(bitfield2)}\n') - - # bitfield - override_entity_palette_index = (bitfield & 0x01) != 0 - # Bit 02 seems unused. - h_flip = (bitfield & 0x04) != 0 - v_flip = (bitfield & 0x08) != 0 - size = (bitfield & 0x30) >> 4 - shape = (bitfield & 0xC0) >> 6 - - # bitfield2 - first_gfx_tile_offset = bitfield2 & 0x03FF - priority = (bitfield2 & 0x0C00) >> 10 - palette_index = (bitfield2 & 0xF000) >> 12 - - - # print(x_offset, y_offset, bitfield, bitfield2) - # print(override_entity_palette_index, h_flip, v_flip, size, shape) - # print(first_gfx_tile_offset, priority, palette_index) - line = f'\tobj x={hex(x_offset)}, y={hex(y_offset)}' - line += opt_param('bitfield', '0x0', hex(bitfield)) - line += opt_param('bitfield2', '0x0', hex(bitfield2)) - lines.append(line + '\n') - list.append({}) - # print() - obj_lists.append(list) - #print(len(obj_lists)) - assert(self.path.endswith('.bin')) - path = self.path[0:-4] + '.s' - with open(path, 'w') as file: - file.writelines(lines) \ No newline at end of file diff --git a/tools/asset_extractor/assets/sprite_frame.py b/tools/asset_extractor/assets/sprite_frame.py deleted file mode 100644 index 7efc9bcd..00000000 --- a/tools/asset_extractor/assets/sprite_frame.py +++ /dev/null @@ -1,26 +0,0 @@ -from assets.base import BaseAsset, Reader, opt_param - -class SpriteFrame(BaseAsset): - def __init__(self, path: str, addr: int, size: int, options: any) -> None: - super().__init__(path, addr, size, options) - - def extract_binary(self, rom: bytearray) -> None: - reader = Reader(rom[self.addr:self.addr+self.size]) - i = 0 - lines = [] - while reader.cursor < self.size: - num_gfx_tiles = reader.read_u8() - unk = reader.read_u8() - first_gfx_tile_index = reader.read_u16() - - line = f'\tsprite_frame first_tile_index={hex(first_gfx_tile_index)}' - line += opt_param('num_tiles', '0', str(num_gfx_tiles)) - line += opt_param('unknown', '0x0', hex(unk)) - lines.append(line + '\n') - assert(unk == 0 or unk == 1 or unk == 0xff) - i += 1 - - assert(self.path.endswith('.bin')) - path = self.path[0:-4] + '.s' - with open(path, 'w') as file: - file.writelines(lines) \ No newline at end of file