Various fixes and optimisations for zone map generation.

This commit is contained in:
Luke Hubmayer-Werner 2018-03-31 18:21:29 +10:30
parent ce55ca8ab7
commit 20c7e28e38
3 changed files with 122 additions and 110 deletions

View File

@ -97,19 +97,6 @@ if pyqt_version == 0:
'Make sure you installed the PyQt4 package.')
sys.exit(-1)
with open(filename_en, 'rb') as file:
ROM_en = file.read()
print(len(ROM_en), filename_en)
with open(filename_jp, 'rb') as file:
ROM_jp = file.read()
print(len(ROM_jp), filename_jp)
with open(filename_jp_ff4, 'rb') as file:
ROM_FF4jp = file.read()
print(len(ROM_FF4jp), filename_jp_ff4)
with open(filename_jp_ff6, 'rb') as file:
ROM_FF6jp = file.read()
print(len(ROM_FF6jp), filename_jp_ff6)
class FF5Reader(QMainWindow):
'''
@ -118,6 +105,21 @@ class FF5Reader(QMainWindow):
def __init__(self):
QMainWindow.__init__(self, None)
perfcount()
print('Reading ROMs')
with open(filename_en, 'rb') as file:
ROM_en = file.read()
print(len(ROM_en), filename_en)
with open(filename_jp, 'rb') as file:
ROM_jp = file.read()
print(len(ROM_jp), filename_jp)
with open(filename_jp_ff4, 'rb') as file:
ROM_FF4jp = file.read()
print(len(ROM_FF4jp), filename_jp_ff4)
with open(filename_jp_ff6, 'rb') as file:
ROM_FF6jp = file.read()
print(len(ROM_FF6jp), filename_jp_ff6)
perfcount()
print('Generating Glyphs')
self.glyph_sprites = {
'glyphs_en_s': generate_glyphs(ROM_en, 0x11F000),
@ -126,7 +128,7 @@ class FF5Reader(QMainWindow):
'glyphs_jp_l': generate_glyphs_large(ROM_jp, 0x03E800),
'glyphs_kanji': generate_glyphs_large(ROM_jp, 0x1BD000, 0x1AA), # Kanji are unchanged in EN version
}
make_string_img_list = functools.partial(_make_string_img_list, **self.glyph_sprites)
make_string_img_list = functools.partial(_make_string_img_list, ROM_en, ROM_jp, **self.glyph_sprites)
perfcount()
stringlist_headers = ['Address', 'ID', 'Name']
@ -161,7 +163,7 @@ class FF5Reader(QMainWindow):
zone_structure = [('NPC Layer', 2, None), # 00 01
('Name', 1, [z[2] for z in zone_names]), # 02
('ShadowFlags', 1, None), # 03
('Graphic maths', 1, None), # 04
('Graphic maths', 1, None), # 04 - MSb animation-related, 6 LSbs are ID for table in 0x005BB8 which writes to $2131-$2133 (Color Math Designation, Subscreen BG color)
('Tile properties', 1, None), # 05
('Flags '+hex(6), 1, None), # 06
(hex(7), 1, None), # 07
@ -255,11 +257,14 @@ class FF5Reader(QMainWindow):
blockmaps = get_blockmaps(ROM_jp)
field_blocks = []
zone_pxs = []
block_cache = {'misses': 0, 'p_hits': 0, 'i_hits': 0}
zone_px_cache = {'misses': 0, 'hits': 0}
for z in zones:
blocks = make_field_map_blocks_px(ROM_jp, z, field_tiles, field_minitiles, field_blocksets)
field_blocks.append(stitch_tileset_px([b.all for b in blocks]))
#zone_pxs += make_zone_pxs(blocks, [blockmaps[b] for b in z.blockmaps if b!=-1])
zone_pxs += make_zone_pxs2(blocks, blockmaps, z)
blocks, miniblocks = make_field_map_blocks_px(ROM_jp, z, field_tiles, field_minitiles, field_blocksets, block_cache)
field_blocks.append(stitch_tileset_px([b.all for b in blocks+miniblocks]))
zone_pxs += make_zone_pxs(blocks, miniblocks, blockmaps, z, zone_px_cache)
print('Block cache results: {misses} misses, {p_hits} full hits, {i_hits} palette misses'.format(**block_cache))
print('Zone pixmap cache results: {misses} misses, {hits} hits'.format(**zone_px_cache))
perfcount()
print('Generating Battle backgrounds')
@ -464,7 +469,7 @@ def make_string_img_large(bytestring, glyphs, glyphs_kanji, macros=None, jp=Fals
return string, QPixmap.fromImage(img.copy(0, 0, xmax, y+16))
def _make_string_img_list(start, length, num,
def _make_string_img_list(rom_e, rom_j, start, length, num,
start_jp=None, len_jp=None, start_str=None, start_jp_str=None,
indirect=False, large=False, macros_en=None, macros_jp=None,
glyphs_en_s=None, glyphs_en_l=None,
@ -480,13 +485,13 @@ def _make_string_img_list(start, length, num,
for id in range(num):
en = start + (id*length)
jp = start_jp + (id*len_jp)
en_start = int.from_bytes(ROM_en[en:en+length],'little') + start_str
en_end = int.from_bytes(ROM_en[en+length:en+(length*2)],'little') + start_str
en_start = int.from_bytes(rom_e[en:en+length],'little') + start_str
en_end = int.from_bytes(rom_e[en+length:en+(length*2)],'little') + start_str
if en_start >= 0xC00000: # SNES memory space has the ROM starting at 0xC00000 in HiROM mode.
en_start -= 0xC00000
en_end -= 0xC00000
jp_start = int.from_bytes(ROM_jp[jp:jp+len_jp],'little') + start_jp_str
jp_end = int.from_bytes(ROM_jp[jp+len_jp:jp+(len_jp*2)],'little') + start_jp_str
jp_start = int.from_bytes(rom_j[jp:jp+len_jp],'little') + start_jp_str
jp_end = int.from_bytes(rom_j[jp+len_jp:jp+(len_jp*2)],'little') + start_jp_str
if jp_start >= 0xC00000: # SNES memory space has the ROM starting at 0xC00000 in HiROM mode.
jp_start -= 0xC00000
jp_end -= 0xC00000
@ -494,17 +499,17 @@ def _make_string_img_list(start, length, num,
break
try: # When dealing with pointer redirection we might end up passing empty strings
if large:
str_en, img_en = make_string_img_large(ROM_en[en_start:en_end], glyphs_en_l, glyphs_kanji, macros_en)
str_en, img_en = make_string_img_large(rom_e[en_start:en_end], glyphs_en_l, glyphs_kanji, macros_en)
else:
str_en, img_en = make_string_img_small(ROM_en[en_start:en_end], glyphs_en_s)
str_en, img_en = make_string_img_small(rom_e[en_start:en_end], glyphs_en_s)
except ValueError:
str_en = ''
img_en = None
try:
if large:
str_jp, img_jp = make_string_img_large(ROM_jp[jp_start:jp_end], glyphs_jp_l, glyphs_kanji, macros_jp, jp=True)
str_jp, img_jp = make_string_img_large(rom_j[jp_start:jp_end], glyphs_jp_l, glyphs_kanji, macros_jp, jp=True)
else:
str_jp, img_jp = make_string_img_small(ROM_jp[jp_start:jp_end], glyphs_jp_s, jp=True)
str_jp, img_jp = make_string_img_small(rom_j[jp_start:jp_end], glyphs_jp_s, jp=True)
except ValueError:
str_jp = ''
img_jp = None
@ -517,11 +522,11 @@ def _make_string_img_list(start, length, num,
j1 = start + (id*length)
j2 = start_jp + (id*len_jp)
if large:
str_en, img_en = make_string_img_large(ROM_en[j1:j1+length], glyphs_en_l, glyphs_kanji, macros_en)
str_jp, img_jp = make_string_img_large(ROM_jp[j2:j2+len_jp], glyphs_jp_l, glyphs_kanji, macros_jp, jp=True)
str_en, img_en = make_string_img_large(rom_e[j1:j1+length], glyphs_en_l, glyphs_kanji, macros_en)
str_jp, img_jp = make_string_img_large(rom_j[j2:j2+len_jp], glyphs_jp_l, glyphs_kanji, macros_jp, jp=True)
else:
str_en, img_en = make_string_img_small(ROM_en[j1:j1+length], glyphs_en_s)
str_jp, img_jp = make_string_img_small(ROM_jp[j2:j2+len_jp], glyphs_jp_s, jp=True)
str_en, img_en = make_string_img_small(rom_e[j1:j1+length], glyphs_en_s)
str_jp, img_jp = make_string_img_small(rom_j[j2:j2+len_jp], glyphs_jp_s, jp=True)
stringlist.append([hex(j1, 6), hex(id, id_digits), str_en, img_en, str_jp, img_jp])
return stringlist

View File

@ -22,8 +22,9 @@ from includes.helpers import *
from includes.snestile import *
from collections import namedtuple
Zone = namedtuple('Zone', 'npcs name shadowflags blockset tilesets blockmaps palette music')
Zone = namedtuple('Zone', 'npcs name shadowflags blockset tilesets blockmaps pal palette music')
Block = namedtuple('Block', 'priority0 priority1 all')
TileMapping = namedtuple('TileMapping', 'tile palette h_flip v_flip priority')
def parse_zone(rom, id, start_address=0x0E9C00):
ptr = start_address+(id*0x1A)
@ -40,10 +41,22 @@ def parse_zone(rom, id, start_address=0x0E9C00):
blockmaps = [(blockmaps_b & 0x000003FF) - 1,
((blockmaps_b & 0x000FFC00) >> 10) - 1,
((blockmaps_b & 0x3FF00000) >> 20) - 1]
pal_address = 0x03BB00 + (rom[ptr+0x16]<<8)
pal = rom[ptr+0x16]
pal_address = 0x03BB00 + (pal<<8)
palettes = [generate_palette(rom, pal_address+i*32, transparent=True) for i in range(8)]
music = rom[ptr+0x19]
return Zone(npcs, name, shadowflags, blockset, tilesets, blockmaps, palettes, music)
return Zone(npcs, name, shadowflags, blockset, tilesets, blockmaps, pal, palettes, music)
'''
This is a bit of a mess of pointer chains for now, so generalising it will have to wait.
UPDATE: i2-i7 merely obtain a zone ID. Whoops.
'''
#i2 = indirect(rom, 0x0E2400 + id*2)
#i3 = indirect(rom, 0x0E2402 + i2)*2
#i4 = indirect(rom, 0x18E080 + i3)
#i5 = indirect(rom, 0x18E081 + i4+4)*3
#i6 = indirect(rom, 0x083320 + i5)
#i7 = indirect(rom, 0x080001 + i6) & 0x03FF
def make_battle_strip(rom, palette_address, tile_address, num_tiles, bpp=4):
@ -204,36 +217,14 @@ def stitch_tileset_px(tiles_px):
canvas.draw_pixmap(i%16, i//16, tile)
return canvas.pixmap()
def get_field_map_tiles(rom, id):
'''
This is a bit of a mess of pointer chains for now, so generalising it will have to wait.
Palette selection is probably determined by the tilemap which is outside the scope of this, so we'll just use #1.
UPDATE: i2-i7 merely obtain a zone ID. Whoops.
'''
#i2 = indirect(rom, 0x0E2400 + id*2)
#i3 = indirect(rom, 0x0E2402 + i2)*2
#i4 = indirect(rom, 0x18E080 + i3)
#i5 = indirect(rom, 0x18E081 + i4+4)*3
#i6 = indirect(rom, 0x083320 + i5)
#i7 = indirect(rom, 0x080001 + i6) & 0x03FF
i8 = id * 0x1A
tilesets = indirect(rom, 0x0E9C09 + i8, length=3)
tile_index_0 = (tilesets & 0x00003F) # (indirect(rom, 0x0E9C09 + i8) & 0x003F)
tile_index_1 = (tilesets & 0x000FC0) >> 6 # (indirect(rom, 0x0E9C09 + i8) & 0x0FC0)>>6
tile_index_2 = (tilesets & 0x03F000) >> 12 # (indirect(rom, 0x0E9C0A + i8) & 0x03F0)>>4
minitile_index = (tilesets & 0xFC0000) >> 18 # (indirect(rom, 0x0E9C0A + i8) & 0x03F0)>>4
pal_offset = indirect(rom, 0x0E9C16 + i8) * 0x100
palette_address = 0x03BB00 + pal_offset
palettes = [generate_palette(rom, palette_address+i*32, transparent=True) for i in range(8)]
return tile_index_0, tile_index_1, tile_index_2, minitile_index, palettes
def make_field_map_tile_pixmap(rom, id, st_tiles, st_minitiles):
*tiles, minitile, palettes = get_field_map_tiles(rom, id)
p = palettes[1]
#*tiles, minitile, palettes = get_field_map_tiles(rom, id)
zone = parse_zone(rom, id)
p = zone.palette[1]
canvas = Canvas(16, 64)
for i, ts in enumerate(tiles):
for i, ts in enumerate(zone.tilesets[:-1]):
canvas.draw_pixmap(0, i*16, st_tiles[ts].pixmap(p))
canvas.draw_pixmap(0, 48, st_minitiles[minitile].pixmap(p))
canvas.draw_pixmap(0, 48, st_minitiles[zone.tilesets[-1]].pixmap(p))
return canvas.pixmap()
def get_field_map_block_layouts(rom, id, start_address=0x0F0000):
@ -244,26 +235,42 @@ def get_field_map_block_layouts(rom, id, start_address=0x0F0000):
output.append([data[j+i+k] for j in range(0, 0x800, 0x200) for k in range(2)])
return output
def make_field_map_blocks_px(rom, zone, tilesets, minitilesets, blocksets):
*i_tiles, i_minitiles = zone.tilesets
tiles = tilesets[i_tiles[0]] + tilesets[i_tiles[1]] + tilesets[i_tiles[2]]
tiles += minitilesets[i_minitiles]
blockset = blocksets[zone.blockset]
blocks = [Block(*[b.pixmap(zone.palette) for b in make_block(tm, tiles)]) for tm in blockset]
return blocks
def make_field_map_blocks_px(rom, zone, tilesets, minitilesets, blocksets, cache):
cache_key_i = '{} {}'.format(' '.join([str(i) for i in zone.tilesets]), zone.blockset)
cache_key_p = '{} {}'.format(cache_key_i, zone.pal)
if cache_key_p in cache:
cache['p_hits'] += 1
return cache[cache_key_p]
elif cache_key_i in cache:
cache['i_hits'] += 1
blocks, miniblocks = cache[cache_key_i]
else:
cache['misses'] += 1
*i_tiles, i_minitiles = zone.tilesets
tiles = tilesets[i_tiles[0]] + tilesets[i_tiles[1]] + tilesets[i_tiles[2]]
minitiles = minitilesets[i_minitiles]
blockset = blocksets[zone.blockset]
blockset_parsed = [[parse_tileset_word(tm[i*2:(i+1)*2]) for i in range(len(tm)//2)] for tm in blockset]
blocks = [Block(*make_block(bs, tiles)) for bs in blockset_parsed]
miniblocks = [Block(*make_block(bs, minitiles, tile_modulo=len(minitiles))) for bs in blockset_parsed]
cache[cache_key_i] = (blocks, miniblocks)
blocks_px = [Block(*[i.pixmap(zone.palette) for i in b]) for b in blocks]
miniblocks_px = [Block(*[i.pixmap(zone.palette) for i in b]) for b in miniblocks]
cache[cache_key_p] = (blocks_px, miniblocks_px)
return blocks_px, miniblocks_px
def make_block(tilemap, tiles, cols=2, rows=2, tile_adjust=0, pal_adjust=0, tile_modulo=0x1000):
canvases = (Canvas_Indexed(cols, rows), Canvas_Indexed(cols, rows), Canvas_Indexed(cols, rows))
for i in range(len(tilemap)//2):
tile_index, p, h_flip, v_flip, priority = parse_tileset_word(tilemap[i*2:(i+1)*2])
for i, tm in enumerate(tilemap):
x = i % cols
y = i //cols
try:
tile = tiles[(tile_index+tile_adjust)%tile_modulo]
canvases[priority].draw_tile(x, y, tile, h_flip, v_flip, p+pal_adjust)
canvases[2].draw_tile(x, y, tile, h_flip, v_flip, p+pal_adjust)
tile = tiles[(tm.tile+tile_adjust)%tile_modulo]
canvases[tm.priority].draw_tile(x, y, tile, tm.h_flip, tm.v_flip, tm.palette+pal_adjust)
canvases[2].draw_tile(x, y, tile, tm.h_flip, tm.v_flip, tm.palette+pal_adjust)
except BaseException as e:
print(e, p, hex(tile_index,2), hex(tile_adjust,2), hex(tile_index+tile_adjust,2))
print(e, tm.palette, hex(tm.tile,2), hex(tile_adjust,2), hex(tm.tile+tile_adjust,2))
return canvases
def get_blockmaps(rom, start_address=0x0B0000, num=0x148):
@ -277,36 +284,36 @@ def get_blockmaps(rom, start_address=0x0B0000, num=0x148):
blockmaps = [decompress_lzss(rom, ptr) for ptr in ptrs]
return blockmaps
def make_zone_pxs(blocks, blockmaps):
output = []
for bm in blockmaps:
def make_zone_pxs(blocks, miniblocks, blockmaps, zone, cache):
cache_key = '{} {} {}'.format(' '.join([str(i) for i in zone.blockmaps+zone.tilesets]), zone.blockset, zone.pal)
if cache_key in cache:
cache['hits'] += 1
return cache[cache_key]
else:
cache['misses'] += 1
output = []
layers = [None, None, None, None, None, None] # bg1.0 bg1.1 bg2.0 bg2.1 bg3.0 bg3.1
order = [4, 2, 0, 3, 1, 5] # Draw order from http://problemkaputt.de/fullsnes.htm#snespictureprocessingunitppu
for i, i_b in enumerate(zone.blockmaps):
if i_b == -1:
output.append(None)
else:
canvases = (Canvas(64, 64, tilesize=16), Canvas(64, 64, tilesize=16), Canvas(64, 64, tilesize=16))
_blocks = blocks if i < 2 else miniblocks
for j, b in enumerate(blockmaps[i_b]):
block = _blocks[b]
canvases[0].draw_pixmap(j%64, j//64, block.priority0)
canvases[1].draw_pixmap(j%64, j//64, block.priority1)
canvases[2].draw_pixmap(j%64, j//64, block.all)
output.append(canvases[2].pixmap())
layers[i*2:(i+1)*2] = canvases[0:2]
canvas = Canvas(64, 64, tilesize=16)
for i, b in enumerate(bm):
canvas.draw_pixmap(i%64, i//64, blocks[b])
for i in order:
if layers[i]:
canvas.draw_pixmap(0, 0, layers[i].pixmap())
output.append(canvas.pixmap())
return output
def make_zone_pxs2(blocks, blockmaps, zone):
output = []
layers = [None, None, None, None, None, None] # bg1.0 bg1.1 bg2.0 bg2.1 bg3.0 bg3.1
order = [4, 2, 0, 3, 1, 5] # Draw order from http://problemkaputt.de/fullsnes.htm#snespictureprocessingunitppu
for i, i_b in enumerate(zone.blockmaps):
if i_b == -1:
output.append(None)
else:
canvases = (Canvas(64, 64, tilesize=16), Canvas(64, 64, tilesize=16), Canvas(64, 64, tilesize=16))
for j, b in enumerate(blockmaps[i_b]):
canvases[0].draw_pixmap(j%64, j//64, blocks[b].priority0)
canvases[1].draw_pixmap(j%64, j//64, blocks[b].priority1)
canvases[2].draw_pixmap(j%64, j//64, blocks[b].all)
output.append(canvases[2].pixmap())
layers[i*2:(i+1)*2] = canvases[0:2]
canvas = Canvas(64, 64, tilesize=16)
for i in order:
if layers[i]:
canvas.draw_pixmap(0, 0, layers[i].pixmap())
output.append(canvas.pixmap())
return output
cache[cache_key] = output
return output
def decompress_battle_tilemap(rom, address):
'''
@ -371,7 +378,7 @@ def parse_tileset_word(data):
priority = (b & 0x20) >> 5
h_flip = (b & 0x40) >> 6
v_flip = (b & 0x80) >> 7
return tile_index, palette, h_flip, v_flip, priority
return TileMapping(tile_index, palette, h_flip, v_flip, priority)
def make_tilemap_canvas(tilemap, tiles, cols=64, rows=64, tile_adjust=0, pal_adjust=-1, tile_modulo=0x80):
'''
@ -380,7 +387,7 @@ def make_tilemap_canvas(tilemap, tiles, cols=64, rows=64, tile_adjust=0, pal_adj
'''
canvas = Canvas_Indexed(cols, rows)
for i in range(len(tilemap)//2):
tile_index, p, h_flip, v_flip, priority = parse_tileset_word(tilemap[i*2:(i+1)*2])
tm = parse_tileset_word(tilemap[i*2:(i+1)*2])
if cols > 32:
x = (i % 32) + 32*((i//1024) % 2)
y = (i //32) - 32*((i//1024) % 2)
@ -388,10 +395,10 @@ def make_tilemap_canvas(tilemap, tiles, cols=64, rows=64, tile_adjust=0, pal_adj
x = i % cols
y = i //cols
try:
tile = tiles[(tile_index+tile_adjust)%tile_modulo]
canvas.draw_tile(x, y, tile, h_flip, v_flip, p+pal_adjust)
tile = tiles[(tm.tile+tile_adjust)%tile_modulo]
canvas.draw_tile(x, y, tile, tm.h_flip, tm.v_flip, tm.palette+pal_adjust)
except BaseException as e:
print(e, p, hex(tile_index,2), hex(tile_adjust,2), hex(tile_index+tile_adjust,2))
print(e, tm.palette, hex(tm.tile,2), hex(tile_adjust,2), hex(tm.tile+tile_adjust,2))
return canvas
def make_tilemap_pixmap(tilemap, tiles, palettes, cols=64, rows=64, tile_adjust=0, pal_adjust=-1):

View File

@ -254,12 +254,12 @@ class Canvas_Indexed:
self.max_col = 1
self.max_row = 1
def draw_tile(self, col, row, image, h_flip=False, v_flip=False, palette=0):
def draw_tile(self, col, row, image, h_flip=False, v_flip=False, palette=0, bpp=4):
image = image.mirrored(h_flip, v_flip)
imgbits = image.bits()
imgbits.setsize(image.byteCount())
if palette:
p = palette<<4
p = palette<<bpp
imgbits[:] = bytes([int(i[0])|p for i in imgbits])
x = col*self.tilesize
y = row*self.tilesize