mirror of
https://github.com/pret/pokemon-reverse-engineering-tools.git
synced 2026-04-25 07:36:47 -05:00
Remove the graphics dump code from gfx.py.
This commit is contained in:
parent
8ed04a61e0
commit
90a3365a80
563
pokemontools/dump_gfx.py
Normal file
563
pokemontools/dump_gfx.py
Normal file
|
|
@ -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)
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user