Remove python asset_extractor

This commit is contained in:
octorock 2021-11-19 12:45:07 +01:00
parent 02d9bda5db
commit 24e1a6091c
8 changed files with 0 additions and 524 deletions

View File

@ -1,2 +0,0 @@
!asset_extractor.py
!assets/*.py

View File

@ -1 +0,0 @@
all:

View File

@ -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()

View File

@ -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)

View File

@ -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 ''

View File

@ -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)

View File

@ -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)

View File

@ -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)