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 /pokemontools/dump_gfx.py | |
parent | 8ed04a61e0d02937d0930ffad43feeb0c1f98ee7 (diff) |
Remove the graphics dump code from gfx.py.
Diffstat (limited to 'pokemontools/dump_gfx.py')
-rw-r--r-- | pokemontools/dump_gfx.py | 563 |
1 files changed, 563 insertions, 0 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) |