mirror of https://github.com/zeldaret/tmc.git
Remove python asset_extractor
This commit is contained in:
parent
02d9bda5db
commit
24e1a6091c
|
@ -1,2 +0,0 @@
|
|||
!asset_extractor.py
|
||||
!assets/*.py
|
|
@ -1 +0,0 @@
|
|||
all:
|
|
@ -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()
|
|
@ -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)
|
|
@ -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 ''
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
Loading…
Reference in New Issue