diff options
author | yenatch <yenatch@gmail.com> | 2016-01-28 21:34:38 -0500 |
---|---|---|
committer | yenatch <yenatch@gmail.com> | 2016-01-28 21:34:38 -0500 |
commit | 90a3365a80dcf8b39c2c392e4756f47570b4cca5 (patch) | |
tree | 50de297d57cee2b155f22ffd68628adc97e15867 | |
parent | 8ed04a61e0d02937d0930ffad43feeb0c1f98ee7 (diff) |
Remove the graphics dump code from gfx.py.
-rw-r--r-- | pokemontools/dump_gfx.py | 563 | ||||
-rw-r--r-- | pokemontools/gfx.py | 576 |
2 files changed, 563 insertions, 576 deletions
diff --git a/pokemontools/dump_gfx.py b/pokemontools/dump_gfx.py new file mode 100644 index 0000000..372b8c3 --- /dev/null +++ b/pokemontools/dump_gfx.py @@ -0,0 +1,563 @@ +import operator + +from gfx import * +from pokemon_constants import pokemon_constants +import trainers +import romstr + +def load_rom(filename=config.rom_path): + rom = romstr.RomStr.load(filename=filename) + return bytearray(rom) + +def rom_offset(bank, address): + if address < 0x4000 or address >= 0x8000: + return address + return bank * 0x4000 + address - 0x4000 * bool(bank) + +sizes = [ + 5, 6, 7, 5, 6, 7, 5, 6, 7, 5, 5, 7, 5, 5, 7, 5, + 6, 7, 5, 6, 5, 7, 5, 7, 5, 7, 5, 6, 5, 6, 7, 5, + 6, 7, 5, 6, 6, 7, 5, 6, 5, 7, 5, 6, 7, 5, 7, 5, + 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 6, 7, 5, 6, + 7, 5, 7, 7, 5, 6, 7, 5, 6, 5, 6, 6, 6, 7, 5, 7, + 5, 6, 6, 5, 7, 6, 7, 5, 7, 5, 7, 7, 6, 6, 7, 6, + 7, 5, 7, 5, 5, 7, 7, 5, 6, 7, 6, 7, 6, 7, 7, 7, + 6, 6, 7, 5, 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 7, + 6, 7, 7, 5, 5, 6, 6, 6, 6, 5, 6, 5, 6, 7, 7, 7, + 7, 7, 5, 6, 7, 7, 5, 5, 6, 7, 5, 6, 7, 5, 6, 7, + 6, 6, 5, 7, 6, 6, 5, 7, 7, 6, 6, 5, 5, 5, 5, 7, + 5, 6, 5, 6, 7, 7, 5, 7, 6, 7, 5, 6, 7, 5, 5, 6, + 6, 5, 6, 6, 6, 6, 7, 6, 5, 6, 7, 5, 7, 6, 6, 7, + 6, 6, 5, 7, 5, 6, 6, 5, 7, 5, 6, 5, 6, 6, 5, 6, + 6, 7, 7, 6, 7, 7, 5, 7, 6, 7, 7, 5, 7, 5, 6, 6, + 6, 7, 7, 7, 7, 5, 6, 7, 7, 7, 5, +] + +def make_sizes(num_monsters=251): + """ + Front pics have specified sizes. + """ + rom = load_rom() + base_stats = 0x51424 + + address = base_stats + 0x11 # pic size + sizes = rom[address : address + 0x20 * num_monsters : 0x20] + sizes = map(lambda x: str(x & 0xf), sizes) + return '\n'.join(' ' * 8 + ', '.join(split(sizes, 16))) + + +def decompress_fx_by_id(i, fxs=0xcfcf6): + rom = load_rom() + addr = fxs + i * 4 + + num_tiles = rom[addr] + bank = rom[addr+1] + address = rom[addr+3] * 0x100 + rom[addr+2] + + offset = rom_offset(bank, address) + fx = Decompressed(rom, start=offset) + return fx + +def rip_compressed_fx(dest='gfx/fx', num_fx=40, fxs=0xcfcf6): + for i in xrange(num_fx): + name = '%.3d' % i + fx = decompress_fx_by_id(i, fxs) + filename = os.path.join(dest, name + '.2bpp.lz') + to_file(filename, fx.compressed_data) + + +monsters = 0x120000 +num_monsters = 251 + +unowns = 0x124000 +num_unowns = 26 +unown_dex = 201 + +def decompress_monster_by_id(rom, mon=0, face='front', crystal=True): + """ + For Unown, use decompress_unown_by_id instead. + """ + if crystal: + bank_offset = 0x36 + else: + bank_offset = 0 + + address = monsters + (mon * 2 + {'front': 0, 'back': 1}.get(face, 0)) * 3 + bank = rom[address] + bank_offset + address = rom[address+2] * 0x100 + rom[address+1] + address = bank * 0x4000 + (address - (0x4000 * bool(bank))) + monster = Decompressed(rom, start=address) + return monster + +def rip_compressed_monster_pics(rom, dest='gfx/pics/', face='both', num_mons=num_monsters, crystal=True): + """ + Extract <num_mons> compressed Pokemon pics from <rom> to directory <dest>. + """ + for mon in range(num_mons): + + mon_name = pokemon_constants[mon + 1].lower().replace('__','_') + size = sizes[mon] + + if mon + 1 == unown_dex: + rip_compressed_unown_pics( + rom=rom, + dest=dest, + face=face, + num_letters=num_unowns, + mon_name=mon_name, + size=size, + crystal=crystal, + ) + + if face in ['front', 'both']: + monster = decompress_monster_by_id(rom, mon, 'front', crystal) + filename = 'front.{0}x{0}.2bpp.lz'.format(size) + path = os.path.join(dest, mon_name, filename) + to_file(path, monster.compressed_data) + + if face in ['back', 'both']: + monster = decompress_monster_by_id(rom, mon, 'back', crystal) + filename = 'back.6x6.2bpp.lz' + path = os.path.join(dest, mon_name, filename) + to_file(path, monster.compressed_data) + +def decompress_unown_by_id(rom, letter, face='front', crystal=True): + if crystal: + bank_offset = 0x36 + else: + bank_offset = 0 + + address = unowns + (letter * 2 + {'front': 0, 'back': 1}.get(face, 0)) * 3 + bank = rom[address] + bank_offset + address = rom[address+2] * 0x100 + rom[address+1] + address = (bank * 0x4000) + (address - (0x4000 * bool(bank))) + unown = Decompressed(rom, start=address) + return unown + +def rip_compressed_unown_pics(rom, dest='gfx/pics/', face='both', num_letters=num_unowns, mon_name='unown', size=sizes[201], crystal=True): + """ + Extract <num_letters> compressed Unown pics from <rom> to directory <dest>. + """ + for letter in range(num_letters): + name = mon_name + '_{}'.format(chr(ord('A') + letter)) + + if face in ['front', 'both']: + unown = decompress_unown_by_id(rom, letter, 'front', crystal) + filename = 'front.{0}x{0}.2bpp.lz'.format(size) + path = os.path.join(dest, name, filename) + to_file(path, unown.compressed_data) + + if face in ['back', 'both']: + unown = decompress_unown_by_id(rom, letter, 'back', crystal) + filename = 'back.6x6.2bpp.lz' + path = os.path.join(dest, name, filename) + to_file(path, unown.compressed_data) + + +trainers_offset = 0x128000 +num_trainers = 67 +trainer_names = [t['constant'] for i, t in trainers.trainer_group_names.items()] + +def decompress_trainer_by_id(rom, i, crystal=True): + rom = load_rom() + if crystal: + bank_offset = 0x36 + else: + bank_offset = 0 + + address = trainers_offset + i * 3 + bank = rom[address] + bank_offset + address = rom[address+2] * 0x100 + rom[address+1] + address = rom_offset(bank, address) + trainer = Decompressed(rom, start=address) + return trainer + +def rip_compressed_trainer_pics(rom): + for t in xrange(num_trainers): + trainer_name = trainer_names[t].lower().replace('_','') + trainer = decompress_trainer_by_id(t) + filename = os.path.join('gfx/trainers/', trainer_name + '.6x6.2bpp.lz') + to_file(filename, trainer.compressed_data) + + +# in order of use (besides repeats) +intro_gfx = [ + ('logo', 0x109407), + ('unowns', 0xE5F5D), + ('pulse', 0xE634D), + ('background', 0xE5C7D), + ('pichu_wooper', 0xE592D), + ('suicune_run', 0xE555D), + ('suicune_jump', 0xE6DED), + ('unown_back', 0xE785D), + ('suicune_close', 0xE681D), + ('suicune_back', 0xE72AD), + ('crystal_unowns', 0xE662D), +] + +intro_tilemaps = [ + ('001', 0xE641D), + ('002', 0xE63DD), + ('003', 0xE5ECD), + ('004', 0xE5E6D), + ('005', 0xE647D), + ('006', 0xE642D), + ('007', 0xE655D), + ('008', 0xE649D), + ('009', 0xE76AD), + ('010', 0xE764D), + ('011', 0xE6D0D), + ('012', 0xE6C3D), + ('013', 0xE778D), + ('014', 0xE76BD), + ('015', 0xE676D), + ('017', 0xE672D), +] + +def rip_compressed_intro(rom, dest='gfx/intro'): + + for name, address in intro_gfx: + filename = os.path.join(dest, name + '.2bpp.lz') + rip_compressed_gfx(rom, address, filename) + + for name, address in intro_tilemaps: + filename = os.path.join(dest, name + '.tilemap.lz') + rip_compressed_gfx(rom, address, filename) + + +title_gfx = [ + ('suicune', 0x10EF46), + ('logo', 0x10F326), + ('crystal', 0x10FCEE), +] + +def rip_compressed_title(rom, dest='gfx/title'): + for name, address in title_gfx: + filename = os.path.join(dest, name + '.2bpp.lz') + rip_compressed_gfx(rom, address, filename) + + +def rip_compressed_tilesets(rom, dest='gfx/tilesets'): + tileset_headers = 0x4d596 + len_tileset = 15 + num_tilesets = 0x25 + + for tileset in xrange(num_tilesets): + addr = tileset * len_tileset + tileset_headers + + bank = rom[addr] + address = rom[addr + 2] * 0x100 + rom[addr + 1] + offset = rom_offset(bank, address) + + filename = os.path.join(dest, tileset_name + '.2bpp.lz') + rip_compressed_gfx(rom, address, filename) + + +misc_pics = [ + ('player', 0x2BA1A, '6x6'), + ('dude', 0x2BBAA, '6x6'), +] + +misc = [ + ('town_map', 0xF8BA0), + ('pokegear', 0x1DE2E4), + ('pokegear_sprites', 0x914DD), +] + +def rip_compressed_misc(rom, dest='gfx/misc'): + for name, address in misc: + filename = os.path.join(dest, name+ '.2bpp.lz') + rip_compressed_gfx(rom, address, filename) + for name, address, dimensions in misc_pics: + filename = os.path.join(dest, name + '.' + dimensions + '.2bpp.lz') + rip_compressed_gfx(rom, address, filename) + + +def rip_compressed_gfx(rom, address, filename): + gfx = Decompressed(rom, start=address) + to_file(filename, gfx.compressed_data) + + +def rip_bulk_gfx(rom, dest='gfx', crystal=True): + rip_compressed_monster_pics(rom, dest=os.path.join(dest, 'pics'), crystal=crystal) + rip_compressed_trainer_pics(rom, dest=os.path.join(dest, 'trainers'), crystal=crystal) + rip_compressed_fx (rom, dest=os.path.join(dest, 'fx')) + rip_compressed_intro (rom, dest=os.path.join(dest, 'intro')) + rip_compressed_title (rom, dest=os.path.join(dest, 'title')) + rip_compressed_tilesets (rom, dest=os.path.join(dest, 'tilesets')) + rip_compressed_misc (rom, dest=os.path.join(dest, 'misc')) + + +def get_pic_animation(tmap, w, h): + """ + Generate pic animation data from a combined tilemap of each frame. + """ + frame_text = '' + bitmask_text = '' + + frames = list(split(tmap, w * h)) + base = frames.pop(0) + bitmasks = [] + + for i in xrange(len(frames)): + frame_text += '\tdw .frame{}\n'.format(i + 1) + + for i, frame in enumerate(frames): + bitmask = map(operator.ne, frame, base) + if bitmask not in bitmasks: + bitmasks.append(bitmask) + which_bitmask = bitmasks.index(bitmask) + + mask = iter(bitmask) + masked_frame = filter(lambda _: mask.next(), frame) + + frame_text += '.frame{}\n'.format(i + 1) + frame_text += '\tdb ${:02x} ; bitmask\n'.format(which_bitmask) + if masked_frame: + frame_text += '\tdb {}\n'.format(', '.join( + map('${:02x}'.format, masked_frame) + )) + + for i, bitmask in enumerate(bitmasks): + bitmask_text += '; {}\n'.format(i) + for byte in split(bitmask, 8): + byte = int(''.join(map(int.__repr__, reversed(byte))), 2) + bitmask_text += '\tdb %{:08b}\n'.format(byte) + + return frame_text, bitmask_text + + +def dump_pic_animations(addresses={'bitmasks': 'BitmasksPointers', 'frames': 'FramesPointers'}, pokemon=pokemon_constants, rom=None): + """ + The code to dump pic animations from rom is mysteriously absent. + Here it is again, but now it dumps images instead of text. + Said text can then be derived from the images. + """ + + if rom is None: rom = load_rom() + + # Labels can be passed in instead of raw addresses. + for which, offset in addresses.items(): + if type(offset) is str: + for line in open('pokecrystal.sym').readlines(): + if offset in line.split(): + addresses[which] = rom_offset(*map(lambda x: int(x, 16), line[:7].split(':'))) + break + + for i, name in pokemon.items(): + if name.lower() == 'unown': continue + + i -= 1 + + directory = os.path.join('gfx', 'pics', name.lower()) + size = sizes[i] + + if i > 151 - 1: + bank = 0x36 + else: + bank = 0x35 + address = addresses['frames'] + i * 2 + address = rom_offset(bank, rom[address] + rom[address + 1] * 0x100) + addrs = [] + while address not in addrs: + addr = rom[address] + rom[address + 1] * 0x100 + addrs.append(rom_offset(bank, addr)) + address += 2 + num_frames = len(addrs) + + # To go any further, we need bitmasks. + # Bitmasks need the number of frames, which we now have. + + bank = 0x34 + address = addresses['bitmasks'] + i * 2 + address = rom_offset(bank, rom[address] + rom[address + 1] * 0x100) + length = size ** 2 + num_bytes = (length + 7) / 8 + bitmasks = [] + for _ in xrange(num_frames): + bitmask = [] + bytes_ = rom[ address : address + num_bytes ] + for byte in bytes_: + bits = map(int, bin(byte)[2:].zfill(8)) + bits.reverse() + bitmask += bits + bitmasks.append(bitmask) + address += num_bytes + + # Back to frames: + frames = [] + for addr in addrs: + bitmask = bitmasks[rom[addr]] + num_tiles = len(filter(int, bitmask)) + frame = (rom[addr], rom[addr + 1 : addr + 1 + num_tiles]) + frames.append(frame) + + tmap = range(length) * (len(frames) + 1) + for i, frame in enumerate(frames): + bitmask = bitmasks[frame[0]] + tiles = (x for x in frame[1]) + for j, bit in enumerate(bitmask): + if bit: + tmap[(i + 1) * length + j] = tiles.next() + + filename = os.path.join(directory, 'front.{0}x{0}.2bpp.lz'.format(size)) + tiles = get_tiles(Decompressed(open(filename).read()).output) + new_tiles = map(tiles.__getitem__, tmap) + new_image = connect(new_tiles) + filename = os.path.splitext(filename)[0] + to_file(filename, new_image) + export_2bpp_to_png(filename) + + +def mass_to_png(directory='gfx'): + # greyscale + for root, dirs, files in os.walk('./gfx/'): + convert_to_png(map(lambda x: os.path.join(root, x), files)) + +def mass_to_colored_png(directory='gfx'): + # greyscale, unless a palette is detected + for root, dirs, files in os.walk(directory): + for name in files: + + if os.path.splitext(name)[1] == '.2bpp': + pal = None + if 'pics' in root: + pal = 'normal.pal' + elif 'trainers' in root: + pal = os.path.splitext(name)[0] + '.pal' + if pal != None: + pal = os.path.join(root, pal) + export_2bpp_to_png(os.path.join(root, name), pal_file=pal) + + elif os.path.splitext(name)[1] == '.1bpp': + export_1bpp_to_png(os.path.join(root, name)) + + +def append_terminator_to_lzs(directory='gfx'): + """ + Add a terminator to any lz files that were extracted without one. + """ + for root, dirs, files in os.walk(directory): + for filename in files: + path = os.path.join(root, filename) + if os.path.splitext(path)[1] == '.lz': + data = bytearray(open(path,'rb').read()) + + # don't mistake padding for a missing terminator + i = 1 + while data[-i] == 0: + i += 1 + + if data[-i] != 0xff: + data += [0xff] + with open(path, 'wb') as out: + out.write(data) + + +def expand_binary_pic_palettes(directory): + """ + Add white and black to palette files with fewer than 4 colors. + + Pokemon Crystal only defines two colors for a pic palette to + save space, filling in black/white at runtime. + Instead of managing palette files of varying length, black + and white are added to pic palettes and excluded from incbins. + """ + for root, dirs, files in os.walk(directory): + if os.path.join(directory, 'pics') in root or os.path.join(directory, '/trainers') in root: + for name in files: + if os.path.splitext(name)[1] == '.pal': + filename = os.path.join(root, name) + palette = bytearray(open(filename, 'rb').read()) + w = bytearray([0xff, 0x7f]) + b = bytearray([0x00, 0x00]) + if len(palette) == 4: + with open(filename, 'wb') as out: + out.write(w + palette + b) + + +def dump_monster_pals(): + rom = load_rom() + + pals = 0xa8d6 + pal_length = 0x4 + for mon in range(251): + + name = pokemon_constants[mon+1].title().replace('_','') + num = str(mon+1).zfill(3) + dir = 'gfx/pics/'+num+'/' + + address = pals + mon*pal_length*2 + + + pal_data = [] + for byte in range(pal_length): + pal_data.append(rom[address]) + address += 1 + + filename = 'normal.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (15 - len(name)) + #print name+'Palette:'+spacing+' INCBIN "'+dir+filename+'"' + + + pal_data = [] + for byte in range(pal_length): + pal_data.append(rom[address]) + address += 1 + + filename = 'shiny.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (10 - len(name)) + #print name+'ShinyPalette:'+spacing+' INCBIN "'+dir+filename+'"' + + +def dump_trainer_pals(): + rom = load_rom() + + pals = 0xb0d2 + pal_length = 0x4 + for trainer in range(67): + + name = trainers.trainer_group_names[trainer+1]['constant'].title().replace('_','') + num = str(trainer).zfill(3) + dir = 'gfx/trainers/' + + address = pals + trainer*pal_length + + pal_data = [] + for byte in range(pal_length): + pal_data.append(rom[address]) + address += 1 + + filename = num+'.pal' + to_file('../'+dir+filename, pal_data) + + spacing = ' ' * (12 - len(name)) + print name+'Palette:'+spacing+' INCBIN"'+dir+filename+'"' + + +def get_uncompressed_gfx(start, num_tiles, filename): + """ + Grab tiles directly from rom and write to file. + """ + rom = load_rom() + bytes_per_tile = 0x10 + length = num_tiles * bytes_per_tile + end = start + length + image = rom[start:end] + to_file(filename, image) + +def rgb_from_rom(address, length=0x80): + rom = load_rom() + return convert_binary_pal_to_text(rom[address:address+length]) + +def decompress_from_address(address, filename='de.2bpp'): + """ + Write decompressed data from an address to a 2bpp file. + """ + rom = load_rom() + image = Decompressed(rom, start=address) + to_file(filename, image.output) diff --git a/pokemontools/gfx.py b/pokemontools/gfx.py index ea0ea19..3f221d6 100644 --- a/pokemontools/gfx.py +++ b/pokemontools/gfx.py @@ -5,29 +5,13 @@ import sys import png from math import sqrt, floor, ceil import argparse -import operator import configuration config = configuration.Config() -from pokemon_constants import pokemon_constants -import trainers -import romstr - from lz import Compressed, Decompressed - -def load_rom(filename=config.rom_path): - rom = romstr.RomStr.load(filename=filename) - return bytearray(rom) - -def rom_offset(bank, address): - if address < 0x4000 or address >= 0x8000: - return address - return bank * 0x4000 + address - 0x4000 * bool(bank) - - def split(list_, interval): """ Split a list by length. @@ -196,292 +180,6 @@ def to_file(filename, data): file.close() - - - -sizes = [ - 5, 6, 7, 5, 6, 7, 5, 6, 7, 5, 5, 7, 5, 5, 7, 5, - 6, 7, 5, 6, 5, 7, 5, 7, 5, 7, 5, 6, 5, 6, 7, 5, - 6, 7, 5, 6, 6, 7, 5, 6, 5, 7, 5, 6, 7, 5, 7, 5, - 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 7, 5, 6, 7, 5, 6, - 7, 5, 7, 7, 5, 6, 7, 5, 6, 5, 6, 6, 6, 7, 5, 7, - 5, 6, 6, 5, 7, 6, 7, 5, 7, 5, 7, 7, 6, 6, 7, 6, - 7, 5, 7, 5, 5, 7, 7, 5, 6, 7, 6, 7, 6, 7, 7, 7, - 6, 6, 7, 5, 6, 6, 7, 6, 6, 6, 7, 6, 6, 6, 7, 7, - 6, 7, 7, 5, 5, 6, 6, 6, 6, 5, 6, 5, 6, 7, 7, 7, - 7, 7, 5, 6, 7, 7, 5, 5, 6, 7, 5, 6, 7, 5, 6, 7, - 6, 6, 5, 7, 6, 6, 5, 7, 7, 6, 6, 5, 5, 5, 5, 7, - 5, 6, 5, 6, 7, 7, 5, 7, 6, 7, 5, 6, 7, 5, 5, 6, - 6, 5, 6, 6, 6, 6, 7, 6, 5, 6, 7, 5, 7, 6, 6, 7, - 6, 6, 5, 7, 5, 6, 6, 5, 7, 5, 6, 5, 6, 6, 5, 6, - 6, 7, 7, 6, 7, 7, 5, 7, 6, 7, 7, 5, 7, 5, 6, 6, - 6, 7, 7, 7, 7, 5, 6, 7, 7, 7, 5, -] - -def make_sizes(num_monsters=251): - """ - Front pics have specified sizes. - """ - rom = load_rom() - base_stats = 0x51424 - - address = base_stats + 0x11 # pic size - sizes = rom[address : address + 0x20 * num_monsters : 0x20] - sizes = map(lambda x: str(x & 0xf), sizes) - return '\n'.join(' ' * 8 + ', '.join(split(sizes, 16))) - - -def decompress_fx_by_id(i, fxs=0xcfcf6): - rom = load_rom() - addr = fxs + i * 4 - - num_tiles = rom[addr] - bank = rom[addr+1] - address = rom[addr+3] * 0x100 + rom[addr+2] - - offset = rom_offset(bank, address) - fx = Decompressed(rom, start=offset) - return fx - -def rip_compressed_fx(dest='gfx/fx', num_fx=40, fxs=0xcfcf6): - for i in xrange(num_fx): - name = '%.3d' % i - fx = decompress_fx_by_id(i, fxs) - filename = os.path.join(dest, name + '.2bpp.lz') - to_file(filename, fx.compressed_data) - - -monsters = 0x120000 -num_monsters = 251 - -unowns = 0x124000 -num_unowns = 26 -unown_dex = 201 - -def decompress_monster_by_id(rom, mon=0, face='front', crystal=True): - """ - For Unown, use decompress_unown_by_id instead. - """ - if crystal: - bank_offset = 0x36 - else: - bank_offset = 0 - - address = monsters + (mon * 2 + {'front': 0, 'back': 1}.get(face, 0)) * 3 - bank = rom[address] + bank_offset - address = rom[address+2] * 0x100 + rom[address+1] - address = bank * 0x4000 + (address - (0x4000 * bool(bank))) - monster = Decompressed(rom, start=address) - return monster - -def rip_compressed_monster_pics(rom, dest='gfx/pics/', face='both', num_mons=num_monsters, crystal=True): - """ - Extract <num_mons> compressed Pokemon pics from <rom> to directory <dest>. - """ - for mon in range(num_mons): - - mon_name = pokemon_constants[mon + 1].lower().replace('__','_') - size = sizes[mon] - - if mon + 1 == unown_dex: - rip_compressed_unown_pics( - rom=rom, - dest=dest, - face=face, - num_letters=num_unowns, - mon_name=mon_name, - size=size, - crystal=crystal, - ) - - if face in ['front', 'both']: - monster = decompress_monster_by_id(rom, mon, 'front', crystal) - filename = 'front.{0}x{0}.2bpp.lz'.format(size) - path = os.path.join(dest, mon_name, filename) - to_file(path, monster.compressed_data) - - if face in ['back', 'both']: - monster = decompress_monster_by_id(rom, mon, 'back', crystal) - filename = 'back.6x6.2bpp.lz' - path = os.path.join(dest, mon_name, filename) - to_file(path, monster.compressed_data) - -def decompress_unown_by_id(rom, letter, face='front', crystal=True): - if crystal: - bank_offset = 0x36 - else: - bank_offset = 0 - - address = unowns + (letter * 2 + {'front': 0, 'back': 1}.get(face, 0)) * 3 - bank = rom[address] + bank_offset - address = rom[address+2] * 0x100 + rom[address+1] - address = (bank * 0x4000) + (address - (0x4000 * bool(bank))) - unown = Decompressed(rom, start=address) - return unown - -def rip_compressed_unown_pics(rom, dest='gfx/pics/', face='both', num_letters=num_unowns, mon_name='unown', size=sizes[201], crystal=True): - """ - Extract <num_letters> compressed Unown pics from <rom> to directory <dest>. - """ - for letter in range(num_letters): - name = mon_name + '_{}'.format(chr(ord('A') + letter)) - - if face in ['front', 'both']: - unown = decompress_unown_by_id(rom, letter, 'front', crystal) - filename = 'front.{0}x{0}.2bpp.lz'.format(size) - path = os.path.join(dest, name, filename) - to_file(path, unown.compressed_data) - - if face in ['back', 'both']: - unown = decompress_unown_by_id(rom, letter, 'back', crystal) - filename = 'back.6x6.2bpp.lz' - path = os.path.join(dest, name, filename) - to_file(path, unown.compressed_data) - - -trainers_offset = 0x128000 -num_trainers = 67 -trainer_names = [t['constant'] for i, t in trainers.trainer_group_names.items()] - -def decompress_trainer_by_id(rom, i, crystal=True): - rom = load_rom() - if crystal: - bank_offset = 0x36 - else: - bank_offset = 0 - - address = trainers_offset + i * 3 - bank = rom[address] + bank_offset - address = rom[address+2] * 0x100 + rom[address+1] - address = rom_offset(bank, address) - trainer = Decompressed(rom, start=address) - return trainer - -def rip_compressed_trainer_pics(rom): - for t in xrange(num_trainers): - trainer_name = trainer_names[t].lower().replace('_','') - trainer = decompress_trainer_by_id(t) - filename = os.path.join('gfx/trainers/', trainer_name + '.6x6.2bpp.lz') - to_file(filename, trainer.compressed_data) - - -# in order of use (besides repeats) -intro_gfx = [ - ('logo', 0x109407), - ('unowns', 0xE5F5D), - ('pulse', 0xE634D), - ('background', 0xE5C7D), - ('pichu_wooper', 0xE592D), - ('suicune_run', 0xE555D), - ('suicune_jump', 0xE6DED), - ('unown_back', 0xE785D), - ('suicune_close', 0xE681D), - ('suicune_back', 0xE72AD), - ('crystal_unowns', 0xE662D), -] - -intro_tilemaps = [ - ('001', 0xE641D), - ('002', 0xE63DD), - ('003', 0xE5ECD), - ('004', 0xE5E6D), - ('005', 0xE647D), - ('006', 0xE642D), - ('007', 0xE655D), - ('008', 0xE649D), - ('009', 0xE76AD), - ('010', 0xE764D), - ('011', 0xE6D0D), - ('012', 0xE6C3D), - ('013', 0xE778D), - ('014', 0xE76BD), - ('015', 0xE676D), - ('017', 0xE672D), -] - -def rip_compressed_intro(rom, dest='gfx/intro'): - - for name, address in intro_gfx: - filename = os.path.join(dest, name + '.2bpp.lz') - rip_compressed_gfx(rom, address, filename) - - for name, address in intro_tilemaps: - filename = os.path.join(dest, name + '.tilemap.lz') - rip_compressed_gfx(rom, address, filename) - - -title_gfx = [ - ('suicune', 0x10EF46), - ('logo', 0x10F326), - ('crystal', 0x10FCEE), -] - -def rip_compressed_title(rom, dest='gfx/title'): - for name, address in title_gfx: - filename = os.path.join(dest, name + '.2bpp.lz') - rip_compressed_gfx(rom, address, filename) - - -def rip_compressed_tilesets(rom, dest='gfx/tilesets'): - tileset_headers = 0x4d596 - len_tileset = 15 - num_tilesets = 0x25 - - for tileset in xrange(num_tilesets): - addr = tileset * len_tileset + tileset_headers - - bank = rom[addr] - address = rom[addr + 2] * 0x100 + rom[addr + 1] - offset = rom_offset(bank, address) - - filename = os.path.join(dest, tileset_name + '.2bpp.lz') - rip_compressed_gfx(rom, address, filename) - - -misc_pics = [ - ('player', 0x2BA1A, '6x6'), - ('dude', 0x2BBAA, '6x6'), -] - -misc = [ - ('town_map', 0xF8BA0), - ('pokegear', 0x1DE2E4), - ('pokegear_sprites', 0x914DD), -] - -def rip_compressed_misc(rom, dest='gfx/misc'): - for name, address in misc: - filename = os.path.join(dest, name+ '.2bpp.lz') - rip_compressed_gfx(rom, address, filename) - for name, address, dimensions in misc_pics: - filename = os.path.join(dest, name + '.' + dimensions + '.2bpp.lz') - rip_compressed_gfx(rom, address, filename) - - -def rip_compressed_gfx(rom, address, filename): - gfx = Decompressed(rom, start=address) - to_file(filename, gfx.compressed_data) - - -def rip_bulk_gfx(rom, dest='gfx', crystal=True): - rip_compressed_monster_pics(rom, dest=os.path.join(dest, 'pics'), crystal=crystal) - rip_compressed_trainer_pics(rom, dest=os.path.join(dest, 'trainers'), crystal=crystal) - rip_compressed_fx (rom, dest=os.path.join(dest, 'fx')) - rip_compressed_intro (rom, dest=os.path.join(dest, 'intro')) - rip_compressed_title (rom, dest=os.path.join(dest, 'title')) - rip_compressed_tilesets (rom, dest=os.path.join(dest, 'tilesets')) - rip_compressed_misc (rom, dest=os.path.join(dest, 'misc')) - - -def decompress_from_address(address, filename='de.2bpp'): - """ - Write decompressed data from an address to a 2bpp file. - """ - rom = load_rom() - image = Decompressed(rom, start=address) - to_file(filename, image.output) - - def decompress_file(filein, fileout=None): image = bytearray(open(filein).read()) de = Decompressed(image) @@ -500,20 +198,6 @@ def compress_file(filein, fileout=None): to_file(fileout, lz.output) - -def get_uncompressed_gfx(start, num_tiles, filename): - """ - Grab tiles directly from rom and write to file. - """ - rom = load_rom() - bytes_per_tile = 0x10 - length = num_tiles * bytes_per_tile - end = start + length - image = rom[start:end] - to_file(filename, image) - - - def bin_to_rgb(word): red = word & 0b11111 word >>= 5 @@ -522,10 +206,6 @@ def bin_to_rgb(word): blue = word & 0b11111 return (red, green, blue) -def rgb_from_rom(address, length=0x80): - rom = load_rom() - return convert_binary_pal_to_text(rom[address:address+length]) - def convert_binary_pal_to_text_by_filename(filename): pal = bytearray(open(filename).read()) return convert_binary_pal_to_text(pal) @@ -557,70 +237,6 @@ def rewrite_binary_pals_to_text(filenames): out.write(pal_text) -def dump_monster_pals(): - rom = load_rom() - - pals = 0xa8d6 - pal_length = 0x4 - for mon in range(251): - - name = pokemon_constants[mon+1].title().replace('_','') - num = str(mon+1).zfill(3) - dir = 'gfx/pics/'+num+'/' - - address = pals + mon*pal_length*2 - - - pal_data = [] - for byte in range(pal_length): - pal_data.append(rom[address]) - address += 1 - - filename = 'normal.pal' - to_file('../'+dir+filename, pal_data) - - spacing = ' ' * (15 - len(name)) - #print name+'Palette:'+spacing+' INCBIN "'+dir+filename+'"' - - - pal_data = [] - for byte in range(pal_length): - pal_data.append(rom[address]) - address += 1 - - filename = 'shiny.pal' - to_file('../'+dir+filename, pal_data) - - spacing = ' ' * (10 - len(name)) - #print name+'ShinyPalette:'+spacing+' INCBIN "'+dir+filename+'"' - - -def dump_trainer_pals(): - rom = load_rom() - - pals = 0xb0d2 - pal_length = 0x4 - for trainer in range(67): - - name = trainers.trainer_group_names[trainer+1]['constant'].title().replace('_','') - num = str(trainer).zfill(3) - dir = 'gfx/trainers/' - - address = pals + trainer*pal_length - - pal_data = [] - for byte in range(pal_length): - pal_data.append(rom[address]) - address += 1 - - filename = num+'.pal' - to_file('../'+dir+filename, pal_data) - - spacing = ' ' * (12 - len(name)) - print name+'Palette:'+spacing+' INCBIN"'+dir+filename+'"' - - - def flatten(planar): """ Flatten planar 2bpp image data into a quaternary pixel map. @@ -639,7 +255,6 @@ def flatten(planar): strips += strip return strips - def to_lines(image, width): """ Convert a tiled quaternary pixel map to lines of quaternary pixels. @@ -893,127 +508,6 @@ def convert_2bpp_to_png(image, **kwargs): return width, height, palette, greyscale, bitdepth, px_map -def get_pic_animation(tmap, w, h): - """ - Generate pic animation data from a combined tilemap of each frame. - """ - frame_text = '' - bitmask_text = '' - - frames = list(split(tmap, w * h)) - base = frames.pop(0) - bitmasks = [] - - for i in xrange(len(frames)): - frame_text += '\tdw .frame{}\n'.format(i + 1) - - for i, frame in enumerate(frames): - bitmask = map(operator.ne, frame, base) - if bitmask not in bitmasks: - bitmasks.append(bitmask) - which_bitmask = bitmasks.index(bitmask) - - mask = iter(bitmask) - masked_frame = filter(lambda _: mask.next(), frame) - - frame_text += '.frame{}\n'.format(i + 1) - frame_text += '\tdb ${:02x} ; bitmask\n'.format(which_bitmask) - if masked_frame: - frame_text += '\tdb {}\n'.format(', '.join( - map('${:02x}'.format, masked_frame) - )) - - for i, bitmask in enumerate(bitmasks): - bitmask_text += '; {}\n'.format(i) - for byte in split(bitmask, 8): - byte = int(''.join(map(int.__repr__, reversed(byte))), 2) - bitmask_text += '\tdb %{:08b}\n'.format(byte) - - return frame_text, bitmask_text - - -def dump_pic_animations(addresses={'bitmasks': 'BitmasksPointers', 'frames': 'FramesPointers'}, pokemon=pokemon_constants, rom=None): - """ - The code to dump pic animations from rom is mysteriously absent. - Here it is again, but now it dumps images instead of text. - Said text can then be derived from the images. - """ - - if rom is None: rom = load_rom() - - # Labels can be passed in instead of raw addresses. - for which, offset in addresses.items(): - if type(offset) is str: - for line in open('pokecrystal.sym').readlines(): - if offset in line.split(): - addresses[which] = rom_offset(*map(lambda x: int(x, 16), line[:7].split(':'))) - break - - for i, name in pokemon.items(): - if name.lower() == 'unown': continue - - i -= 1 - - directory = os.path.join('gfx', 'pics', name.lower()) - size = sizes[i] - - if i > 151 - 1: - bank = 0x36 - else: - bank = 0x35 - address = addresses['frames'] + i * 2 - address = rom_offset(bank, rom[address] + rom[address + 1] * 0x100) - addrs = [] - while address not in addrs: - addr = rom[address] + rom[address + 1] * 0x100 - addrs.append(rom_offset(bank, addr)) - address += 2 - num_frames = len(addrs) - - # To go any further, we need bitmasks. - # Bitmasks need the number of frames, which we now have. - - bank = 0x34 - address = addresses['bitmasks'] + i * 2 - address = rom_offset(bank, rom[address] + rom[address + 1] * 0x100) - length = size ** 2 - num_bytes = (length + 7) / 8 - bitmasks = [] - for _ in xrange(num_frames): - bitmask = [] - bytes_ = rom[ address : address + num_bytes ] - for byte in bytes_: - bits = map(int, bin(byte)[2:].zfill(8)) - bits.reverse() - bitmask += bits - bitmasks.append(bitmask) - address += num_bytes - - # Back to frames: - frames = [] - for addr in addrs: - bitmask = bitmasks[rom[addr]] - num_tiles = len(filter(int, bitmask)) - frame = (rom[addr], rom[addr + 1 : addr + 1 + num_tiles]) - frames.append(frame) - - tmap = range(length) * (len(frames) + 1) - for i, frame in enumerate(frames): - bitmask = bitmasks[frame[0]] - tiles = (x for x in frame[1]) - for j, bit in enumerate(bitmask): - if bit: - tmap[(i + 1) * length + j] = tiles.next() - - filename = os.path.join(directory, 'front.{0}x{0}.2bpp.lz'.format(size)) - tiles = get_tiles(Decompressed(open(filename).read()).output) - new_tiles = map(tiles.__getitem__, tmap) - new_image = connect(new_tiles) - filename = os.path.splitext(filename)[0] - to_file(filename, new_image) - export_2bpp_to_png(filename) - - def export_png_to_2bpp(filein, fileout=None, palout=None, **kwargs): arguments = { @@ -1259,7 +753,6 @@ def png_to_lz(filein): to_file(name+'.2bpp'+'.lz', Compressed(image).output) - def convert_2bpp_to_1bpp(data): """ Convert planar 2bpp image data to 1bpp. Assume images are two colors. @@ -1322,73 +815,6 @@ def png_to_1bpp(filename, **kwargs): return convert_2bpp_to_1bpp(image) -def mass_to_png(directory='gfx'): - # greyscale - for root, dirs, files in os.walk('./gfx/'): - convert_to_png(map(lambda x: os.path.join(root, x), files)) - -def mass_to_colored_png(directory='gfx'): - # greyscale, unless a palette is detected - for root, dirs, files in os.walk(directory): - for name in files: - - if os.path.splitext(name)[1] == '.2bpp': - pal = None - if 'pics' in root: - pal = 'normal.pal' - elif 'trainers' in root: - pal = os.path.splitext(name)[0] + '.pal' - if pal != None: - pal = os.path.join(root, pal) - export_2bpp_to_png(os.path.join(root, name), pal_file=pal) - - elif os.path.splitext(name)[1] == '.1bpp': - export_1bpp_to_png(os.path.join(root, name)) - - -def append_terminator_to_lzs(directory='gfx'): - """ - Add a terminator to any lz files that were extracted without one. - """ - for root, dirs, files in os.walk(directory): - for filename in files: - path = os.path.join(root, filename) - if os.path.splitext(path)[1] == '.lz': - data = bytearray(open(path,'rb').read()) - - # don't mistake padding for a missing terminator - i = 1 - while data[-i] == 0: - i += 1 - - if data[-i] != 0xff: - data += [0xff] - with open(path, 'wb') as out: - out.write(data) - - -def expand_binary_pic_palettes(directory): - """ - Add white and black to palette files with fewer than 4 colors. - - Pokemon Crystal only defines two colors for a pic palette to - save space, filling in black/white at runtime. - Instead of managing palette files of varying length, black - and white are added to pic palettes and excluded from incbins. - """ - for root, dirs, files in os.walk(directory): - if os.path.join(directory, 'pics') in root or os.path.join(directory, '/trainers') in root: - for name in files: - if os.path.splitext(name)[1] == '.pal': - filename = os.path.join(root, name) - palette = bytearray(open(filename, 'rb').read()) - w = bytearray([0xff, 0x7f]) - b = bytearray([0x00, 0x00]) - if len(palette) == 4: - with open(filename, 'wb') as out: - out.write(w + palette + b) - - def convert_to_2bpp(filenames=[]): for filename in filenames: filename, name, extension = try_decompress(filename) @@ -1471,7 +897,5 @@ def main(): method(args.filenames) - if __name__ == "__main__": main() - |