Some battle backgrounds working. TODO: fix the terrain tile flip patterns, terrain load edge case.
This commit is contained in:
parent
b8a05f8648
commit
995be34e00
208
ff5reader.py
208
ff5reader.py
|
@ -170,11 +170,18 @@ class FF5Reader(QMainWindow):
|
||||||
(hex(24, 2), 1, None),
|
(hex(24, 2), 1, None),
|
||||||
('Music', 1, const.BGM_Tracks)]
|
('Music', 1, const.BGM_Tracks)]
|
||||||
zone_headers = ['Address'] + [z[0] for z in zone_structure]
|
zone_headers = ['Address'] + [z[0] for z in zone_structure]
|
||||||
|
zone_data = [parse_struct(ROM_en, 0x0E9C00 + (i*0x1A), zone_structure) for i in range(const.zone_count)]
|
||||||
|
|
||||||
zone_data = []
|
battle_bg_structure = [('ImageID', 1, None),
|
||||||
for i in range(const.zone_count):
|
('ColorID 1', 1, None),
|
||||||
offset = 0x0E9C00 + (i*0x1A)
|
('ColorID 2', 1, None),
|
||||||
zone_data.append(parse_struct(ROM_en, offset, zone_structure))
|
('TerrainID', 1, None),
|
||||||
|
('TerrainFlipID', 1, None),
|
||||||
|
(hex(5, 1), 1, None),
|
||||||
|
('Movement', 1, None),
|
||||||
|
(hex(7, 1), 1, None),]
|
||||||
|
battle_bg_headers = ['Address'] + [z[0] for z in battle_bg_structure]
|
||||||
|
battle_bg_data = [parse_struct(ROM_jp, 0x14BA21 + (i*8), battle_bg_structure) for i in range(34)]
|
||||||
|
|
||||||
tileset_headers = ("ID", "Offset", "Pointer", "Expected Length")
|
tileset_headers = ("ID", "Offset", "Pointer", "Expected Length")
|
||||||
tileset_data = []
|
tileset_data = []
|
||||||
|
@ -209,16 +216,21 @@ class FF5Reader(QMainWindow):
|
||||||
perfcount()
|
perfcount()
|
||||||
print('Generating map tiles')
|
print('Generating map tiles')
|
||||||
|
|
||||||
world_tiles = [make_worldmap_tiles(ROM_jp, 0x0FF0C0+(i*0x300), 0x1B8000+(i*0x2000), 0x0FF9C0+(i*0x100), length=l) for i, l in enumerate([256, 256, 256])]
|
worldmap_palettes = [generate_palette(ROM_jp, 0x0FFCC0+(i*0x100), length=0x160, transparent=True) for i in range(3)]
|
||||||
worldpixmaps = [make_worldmap_pixmap(ROM_jp, 0, 0x0FFCC0+(0*0x100), world_tiles[0]),
|
|
||||||
make_worldmap_pixmap(ROM_jp, 1, 0x0FFCC0+(1*0x100), world_tiles[1]),
|
|
||||||
make_worldmap_pixmap(ROM_jp, 2, 0x0FFCC0+(0*0x100), world_tiles[0]),
|
|
||||||
make_worldmap_pixmap(ROM_jp, 3, 0x0FFCC0+(2*0x100), world_tiles[2]),
|
|
||||||
make_worldmap_pixmap(ROM_jp, 4, 0x0FFCC0+(2*0x100), world_tiles[2])]
|
|
||||||
|
|
||||||
worldmap_tiles = make_worldmap_subtiles_pixmap(ROM_jp, 0x1B8000, 0x0FF9C0, 0x0FFCC0)
|
world_tiles = [make_worldmap_tiles(ROM_jp, 0x0FF0C0+(i*0x300), 0x1B8000+(i*0x2000), 0x0FF9C0+(i*0x100)) for i in range(3)]
|
||||||
worldmap_tiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BA000, 0x0FFAC0, 0x0FFDC0)
|
worldpixmaps = [make_worldmap_pixmap(ROM_jp, i, 0x0FFCC0+(t*0x100), world_tiles[t]) for i, t in enumerate([0, 1, 0, 2, 2])]
|
||||||
worldmap_tiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BC000, 0x0FFBC0, 0x0FFEC0, length=128)
|
world_tiles_pixmaps = []
|
||||||
|
for i, tiles in enumerate(world_tiles):
|
||||||
|
a = []
|
||||||
|
for t in tiles:
|
||||||
|
t.setColorTable(worldmap_palettes[i])
|
||||||
|
a.append(QPixmap.fromImage(t))
|
||||||
|
world_tiles_pixmaps.append(a)
|
||||||
|
perfcount()
|
||||||
|
worldmap_subtiles = make_worldmap_subtiles_pixmap(ROM_jp, 0x1B8000, 0x0FF9C0, 0x0FFCC0)
|
||||||
|
worldmap_subtiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BA000, 0x0FFAC0, 0x0FFDC0)
|
||||||
|
worldmap_subtiles += make_worldmap_subtiles_pixmap(ROM_jp, 0x1BC000, 0x0FFBC0, 0x0FFEC0, length=128)
|
||||||
perfcount()
|
perfcount()
|
||||||
field_tiles = make_all_field_tiles(ROM_jp)
|
field_tiles = make_all_field_tiles(ROM_jp)
|
||||||
field_minitiles = make_all_field_minitiles(ROM_jp)
|
field_minitiles = make_all_field_minitiles(ROM_jp)
|
||||||
|
@ -228,6 +240,9 @@ class FF5Reader(QMainWindow):
|
||||||
perfcount()
|
perfcount()
|
||||||
fieldmap_tiles = [make_field_map_tile_pixmap(ROM_jp, i, st_field_tiles, st_field_minitiles) for i in range(const.zone_count)]
|
fieldmap_tiles = [make_field_map_tile_pixmap(ROM_jp, i, st_field_tiles, st_field_minitiles) for i in range(const.zone_count)]
|
||||||
perfcount()
|
perfcount()
|
||||||
|
print('Generating Battle backgrounds')
|
||||||
|
battle_bgs = make_battle_backgrounds(ROM_jp)
|
||||||
|
perfcount()
|
||||||
print('Generating other sprites')
|
print('Generating other sprites')
|
||||||
self.battle_strips = make_character_battle_sprites(ROM_en)
|
self.battle_strips = make_character_battle_sprites(ROM_en)
|
||||||
status_strips = make_character_status_sprites(ROM_en)
|
status_strips = make_character_status_sprites(ROM_en)
|
||||||
|
@ -263,9 +278,13 @@ class FF5Reader(QMainWindow):
|
||||||
sprites_tab.addTab(make_pixmap_table(glyph_sprites_jp_small, scale=4), 'Glyphs (JP)')
|
sprites_tab.addTab(make_pixmap_table(glyph_sprites_jp_small, scale=4), 'Glyphs (JP)')
|
||||||
sprites_tab.addTab(make_pixmap_table(glyph_sprites_jp_large, scale=2), 'Glyphs (Large JP)')
|
sprites_tab.addTab(make_pixmap_table(glyph_sprites_jp_large, scale=2), 'Glyphs (Large JP)')
|
||||||
sprites_tab.addTab(make_pixmap_table(glyph_sprites_kanji, scale=2), 'Glyphs (Kanji)')
|
sprites_tab.addTab(make_pixmap_table(glyph_sprites_kanji, scale=2), 'Glyphs (Kanji)')
|
||||||
sprites_tab.addTab(make_pixmap_table(worldmap_tiles, cols=16, scale=4), 'Worldmap Tiles')
|
sprites_tab.addTab(make_pixmap_table(worldmap_subtiles, cols=16, scale=4), 'Worldmap Subtiles')
|
||||||
|
sprites_tab.addTab(make_pixmap_table(world_tiles_pixmaps[0], cols=16, scale=4), 'World 1 Tiles')
|
||||||
|
sprites_tab.addTab(make_pixmap_table(world_tiles_pixmaps[1], cols=16, scale=4), 'World 2 Tiles')
|
||||||
|
sprites_tab.addTab(make_pixmap_table(world_tiles_pixmaps[2], cols=16, scale=4), 'Underwater Tiles')
|
||||||
sprites_tab.addTab(make_pixmap_table(worldpixmaps, cols=1, scale=1, large=True), 'Worldmaps')
|
sprites_tab.addTab(make_pixmap_table(worldpixmaps, cols=1, scale=1, large=True), 'Worldmaps')
|
||||||
sprites_tab.addTab(make_pixmap_table(fieldmap_tiles, cols=8, scale=2), 'Fieldmap Tiles')
|
sprites_tab.addTab(make_pixmap_table(fieldmap_tiles, cols=8, scale=2), 'Fieldmap Tiles')
|
||||||
|
sprites_tab.addTab(make_pixmap_table(battle_bgs, cols=8, scale=2), 'Battle BGs')
|
||||||
sprites_tab.addTab(make_pixmap_table(self.battle_strips, cols=22, scale=2), 'Character Battle Sprites')
|
sprites_tab.addTab(make_pixmap_table(self.battle_strips, cols=22, scale=2), 'Character Battle Sprites')
|
||||||
sprites_tab.addTab(make_pixmap_table(status_strips, cols=22, scale=2), 'Status Sprites')
|
sprites_tab.addTab(make_pixmap_table(status_strips, cols=22, scale=2), 'Status Sprites')
|
||||||
sprites_tab.addTab(make_pixmap_table(enemy_sprites_named, cols=32, scale=1), 'Enemy Sprites')
|
sprites_tab.addTab(make_pixmap_table(enemy_sprites_named, cols=32, scale=1), 'Enemy Sprites')
|
||||||
|
@ -279,6 +298,7 @@ class FF5Reader(QMainWindow):
|
||||||
|
|
||||||
structs_tab.addTab(make_table(zone_headers, zone_data, True), 'Zones')
|
structs_tab.addTab(make_table(zone_headers, zone_data, True), 'Zones')
|
||||||
structs_tab.addTab(make_table(tileset_headers, tileset_data, True), 'Tilesets')
|
structs_tab.addTab(make_table(tileset_headers, tileset_data, True), 'Tilesets')
|
||||||
|
structs_tab.addTab(make_table(battle_bg_headers, battle_bg_data, True), 'BattleBGs')
|
||||||
structs_tab.addTab(make_table(const.npc_layer_headers, npc_layers, True), 'NPC Layers')
|
structs_tab.addTab(make_table(const.npc_layer_headers, npc_layers, True), 'NPC Layers')
|
||||||
structs_tab.addTab(make_table(enemy_sprite_headers, enemy_sprite_data, True), 'Enemy Sprites')
|
structs_tab.addTab(make_table(enemy_sprite_headers, enemy_sprite_data, True), 'Enemy Sprites')
|
||||||
|
|
||||||
|
@ -487,6 +507,166 @@ def make_field_map_tile_pixmap(rom, id, st_tiles, st_minitiles):
|
||||||
canvas.draw_pixmap(0, 48, st_minitiles[minitile].pixmap(p))
|
canvas.draw_pixmap(0, 48, st_minitiles[minitile].pixmap(p))
|
||||||
return canvas.pixmap()
|
return canvas.pixmap()
|
||||||
|
|
||||||
|
def decompress_lzss(rom, start, header=False):
|
||||||
|
'''
|
||||||
|
Algorithm from http://slickproductions.org/slickwiki/index.php/Noisecross:Final_Fantasy_V_Compression
|
||||||
|
'''
|
||||||
|
uncompressed_length = indirect(rom, start)
|
||||||
|
ptr = start+2
|
||||||
|
output = []
|
||||||
|
buffer = [0 for i in range(0x800)]
|
||||||
|
buffer_p = 0x07DE
|
||||||
|
while len(output) < uncompressed_length:
|
||||||
|
bitmap_byte = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
for i in range(8):
|
||||||
|
bit = (bitmap_byte >> i) & 1
|
||||||
|
if bit:
|
||||||
|
b = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
output.append(b)
|
||||||
|
buffer[buffer_p] = b
|
||||||
|
buffer_p = (buffer_p+1) % 0x800
|
||||||
|
else:
|
||||||
|
b1 = rom[ptr]
|
||||||
|
b2 = rom[ptr+1]
|
||||||
|
ptr += 2
|
||||||
|
offset = b1|((b2 & 0xE0)<<3)
|
||||||
|
length = b2 & 0x1F
|
||||||
|
for j in range(length+3):
|
||||||
|
b = buffer[offset]
|
||||||
|
output.append(b)
|
||||||
|
buffer[buffer_p] = b
|
||||||
|
buffer_p = (buffer_p+1) % 0x800
|
||||||
|
offset = (offset+1) % 0x800
|
||||||
|
return bytes(output[:uncompressed_length])
|
||||||
|
|
||||||
|
def decompress_battle_terrain(rom, address):
|
||||||
|
'''
|
||||||
|
Decompresses the tilemap for a battle background.
|
||||||
|
'''
|
||||||
|
length = 0x500
|
||||||
|
output = [0 for i in range(length)]
|
||||||
|
o1 = []
|
||||||
|
ptr = address
|
||||||
|
while len(o1) < length//2:
|
||||||
|
a = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
if a != 0xFF:
|
||||||
|
o1.append(a)
|
||||||
|
else:
|
||||||
|
repeat = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
if repeat & 0x80: # Repeat 2 tiles
|
||||||
|
repeat &= 0x3F
|
||||||
|
a, b = rom[ptr:ptr+2]
|
||||||
|
ptr += 2
|
||||||
|
o1 += [a, b]*repeat
|
||||||
|
else:
|
||||||
|
if repeat & 0x40:
|
||||||
|
pass # TODO: trace this out
|
||||||
|
else: # Incremental repeat
|
||||||
|
repeat &= 0x3F
|
||||||
|
a,inc = rom[ptr:ptr+2]
|
||||||
|
ptr += 2
|
||||||
|
o1 += [a+(i*inc) for i in range(repeat)]
|
||||||
|
o2 = [4*(1+(i>>7)) for i in o1]
|
||||||
|
output[::2] = [i|0x80 for i in o1[:length//2]]
|
||||||
|
output[1::2] = [i&0xDF for i in o2[:length//2]]
|
||||||
|
return bytes(output)
|
||||||
|
|
||||||
|
def apply_battle_terrain_flips(rom, id, battle_terrain):
|
||||||
|
if id==0xFF:
|
||||||
|
return battle_terrain
|
||||||
|
ptr = indirect(rom, 0x14C736+(id*2))+0x140000
|
||||||
|
length = len(battle_terrain)//2
|
||||||
|
output = list(battle_terrain)
|
||||||
|
buffer = []
|
||||||
|
|
||||||
|
while len(buffer) < length:
|
||||||
|
a = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
if a == 0x00:
|
||||||
|
skip = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
buffer += [0]*skip*4
|
||||||
|
else:
|
||||||
|
for b in reversed(range(0, 8, 2)):
|
||||||
|
buffer.append((a>>b)&0x03)
|
||||||
|
|
||||||
|
for i in range(len(battle_terrain)//2):
|
||||||
|
output[i*2+1] |= (buffer[i] << 6)
|
||||||
|
return bytes(output)
|
||||||
|
|
||||||
|
def make_tilemap_pixmap(tilemap, tiles, palettes, tile_adjust=0):
|
||||||
|
'''
|
||||||
|
Battle bg is 64x64 map size, 8x8 tile size
|
||||||
|
4bpp tiles
|
||||||
|
'''
|
||||||
|
canvas = Canvas(64, 64)
|
||||||
|
for i in range(len(tilemap)//2):
|
||||||
|
a, b = tilemap[i*2:(i+1)*2]
|
||||||
|
tile_index = a|((b & 0x02) << 8)
|
||||||
|
p = (b & 0x1C) >> 2
|
||||||
|
priority = (b & 0x20) >> 5
|
||||||
|
h_flip = (b & 0x40) >> 6
|
||||||
|
v_flip = (b & 0x80) >> 7
|
||||||
|
|
||||||
|
x = (i % 32) + 32*((i//1024) % 2)
|
||||||
|
y = (i //32) - 32*((i//1024) % 2)
|
||||||
|
try:
|
||||||
|
palette = palettes[p]
|
||||||
|
tile = tiles[(tile_index+tile_adjust)%0x80]
|
||||||
|
tile.setColorTable(palette)
|
||||||
|
tile_px = QPixmap.fromImage(tile)
|
||||||
|
canvas.draw_pixmap(x, y, tile_px, h_flip, v_flip)
|
||||||
|
except BaseException as e:
|
||||||
|
print(e, p, hex(tile_index,2), hex(tile_adjust,2), hex(tile_index+tile_adjust,2))
|
||||||
|
return canvas.pixmap(True)
|
||||||
|
|
||||||
|
def make_battle_backgrounds(rom):
|
||||||
|
'''
|
||||||
|
21 pointers in memory for the compressed data of the tilesets.
|
||||||
|
Most of these are not unique, and only a subset of the resulting block is used.
|
||||||
|
The block appears to get DMA'd to 0x0400 in VRAM
|
||||||
|
|
||||||
|
Terrain gets DMA'd to 0x2000 (size 0x500) in VRAM from 0x7f0000 in RAM
|
||||||
|
'''
|
||||||
|
palettes = [generate_palette(rom, 0x14BB31+(i*0x20)) for i in range(84)]
|
||||||
|
battle_bgs = []
|
||||||
|
for i in range(34):
|
||||||
|
bg = {
|
||||||
|
'tiles_id': rom[0x14BA21+(i*8)],
|
||||||
|
'pal1_id': rom[0x14BA22+(i*8)],
|
||||||
|
'pal2_id': rom[0x14BA23+(i*8)],
|
||||||
|
'terrain_id': rom[0x14BA24+(i*8)],
|
||||||
|
'terrain_flips_id': rom[0x14BA25+(i*8)],
|
||||||
|
}
|
||||||
|
bg['palette'] = [palettes[0], palettes[bg['pal1_id']], palettes[bg['pal2_id']]]
|
||||||
|
battle_bgs.append(bg)
|
||||||
|
|
||||||
|
tiles_pointer_start = 0x184196
|
||||||
|
tiles_RAM_pointer_start = 0x184157
|
||||||
|
tiles_pointers = [indirect(rom, tiles_pointer_start+(i*3), length=3)-0xC00000 for i in range(21)]
|
||||||
|
tiles_raw = [decompress_lzss(rom, p) for p in tiles_pointers]
|
||||||
|
tiles_skips = [indirect(rom, tiles_RAM_pointer_start+(i*3), length=3)-0x7FC000 for i in range(21)]
|
||||||
|
tiles = []
|
||||||
|
for raw, skip in zip(tiles_raw, tiles_skips):
|
||||||
|
r = raw[skip:]
|
||||||
|
tiles.append([create_tile_indexed(r[i*32:(i+1)*32]) for i in range(len(r)//32)])
|
||||||
|
|
||||||
|
terrain_pointer_start = 0x14C86D
|
||||||
|
terrain_pointers = [indirect(rom, terrain_pointer_start+(i*2))+0x140000 for i in range(28)]
|
||||||
|
terrains = [decompress_battle_terrain(rom, p) for p in terrain_pointers]
|
||||||
|
|
||||||
|
pixmaps = []
|
||||||
|
for bg in battle_bgs:
|
||||||
|
terrain = apply_battle_terrain_flips(rom, bg['terrain_flips_id'], terrains[bg['terrain_id']])
|
||||||
|
pixmaps.append(make_tilemap_pixmap(terrain, tiles[bg['tiles_id']], bg['palette']))
|
||||||
|
#[make_tilemap_pixmap(terrains[5], tiles[2], palettes)]
|
||||||
|
return pixmaps
|
||||||
|
|
||||||
|
|
||||||
def make_battle_strip(rom, palette_address, tile_address, num_tiles, bpp=4):
|
def make_battle_strip(rom, palette_address, tile_address, num_tiles, bpp=4):
|
||||||
if isinstance(palette_address, int):
|
if isinstance(palette_address, int):
|
||||||
palette = generate_palette(rom, palette_address, transparent=True)
|
palette = generate_palette(rom, palette_address, transparent=True)
|
||||||
|
|
12
snestile.py
12
snestile.py
|
@ -210,8 +210,16 @@ class Canvas:
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
del self.painter
|
del self.painter
|
||||||
|
|
||||||
def draw_pixmap(self, col, row, pixmap):
|
def draw_pixmap(self, col, row, pixmap, h_flip=False, v_flip=False):
|
||||||
self.painter.drawPixmap(col*8, row*8, pixmap)
|
if h_flip or v_flip:
|
||||||
|
return
|
||||||
|
h_s = -1 if h_flip else 1
|
||||||
|
v_s = -1 if v_flip else 1
|
||||||
|
x = (col+h_flip)*8*h_s
|
||||||
|
y = (row+v_flip)*8*v_s
|
||||||
|
self.painter.scale(h_s, v_s)
|
||||||
|
self.painter.drawPixmap(x, y, pixmap)
|
||||||
|
self.painter.scale(h_s, v_s) # Invert it again to restore it to normal
|
||||||
if col > self.max_x:
|
if col > self.max_x:
|
||||||
self.max_x = col
|
self.max_x = col
|
||||||
if row > self.max_y:
|
if row > self.max_y:
|
||||||
|
|
Loading…
Reference in New Issue