Added FF4 character battle sprites

This commit is contained in:
Luke Hubmayer-Werner 2018-03-19 17:13:04 +10:30
parent 2d286d48ef
commit d32ca0608e
1 changed files with 500 additions and 431 deletions

289
ff5reader.py Normal file → Executable file
View File

@ -1,4 +1,4 @@
#!python3 -i #!/usr/bin/python3 -i
''' '''
No license for now No license for now
''' '''
@ -13,9 +13,10 @@ import const
import time import time
pyqt_version = 0 pyqt_version = 0
skip_pyqt5 = "PYQT4" in os.environ skip_pyqt5 = 'PYQT4' in os.environ
filename_en = "Final Fantasy V (Japan) [En by RPGe v1.1].sfc" filename_en = 'Final Fantasy V (Japan) [En by RPGe v1.1].sfc'
filename_jp = "Final Fantasy V (Japan).sfc" filename_jp = 'Final Fantasy V (Japan).sfc'
filename_jp_ff4 = 'Final Fantasy IV (Japan) (Rev A).sfc'
if not skip_pyqt5: if not skip_pyqt5:
try: try:
@ -39,7 +40,7 @@ if not skip_pyqt5:
pyqt_version = 5 pyqt_version = 5
except ImportError: except ImportError:
print("Couldn't import Qt5 dependencies. " print("Couldn't import Qt5 dependencies. "
"Make sure you installed the PyQt5 package.") 'Make sure you installed the PyQt5 package.')
if pyqt_version == 0: if pyqt_version == 0:
try: try:
import sip import sip
@ -66,18 +67,20 @@ if pyqt_version == 0:
pyqt_version = 4 pyqt_version = 4
except ImportError: except ImportError:
print("Couldn't import Qt dependencies. " print("Couldn't import Qt dependencies. "
"Make sure you installed the PyQt4 package.") 'Make sure you installed the PyQt4 package.')
sys.exit(-1) sys.exit(-1)
bg_color = QColor(0, 0, 128) bg_color = QColor(0, 0, 128)
bg_trans = QColor(0, 0, 0, 0) bg_trans = QColor(0, 0, 0, 0)
HEX_PREFIX = '#' # '$' or '0x' are also nice
monofont = QFont() monofont = QFont()
monofont.setStyleHint(QFont.Monospace) monofont.setStyleHint(QFont.Monospace)
if not monofont.fixedPitch(): if not monofont.fixedPitch():
monofont.setStyleHint(QFont.TypeWriter) monofont.setStyleHint(QFont.TypeWriter)
if not monofont.fixedPitch(): if not monofont.fixedPitch():
monofont.setFamily("Monospace") monofont.setFamily('Monospace')
def divceil(numerator, denominator): def divceil(numerator, denominator):
# Reverse floor division for ceil # Reverse floor division for ceil
@ -86,15 +89,18 @@ def divceil(numerator, denominator):
def hex_length(i): def hex_length(i):
return divceil(i.bit_length(), 4) return divceil(i.bit_length(), 4)
with open(filename_en, 'rb') as file1: with open(filename_en, 'rb') as file:
ROM_en = file1.read() ROM_en = file.read()
print(len(ROM_en), filename_en) print(len(ROM_en), filename_en)
with open(filename_jp, 'rb') as file2: with open(filename_jp, 'rb') as file:
ROM_jp = file2.read() 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_jp), filename_jp) print(len(ROM_jp), filename_jp)
stringlist_headers = ["Address", "ID", "Name"] stringlist_headers = ['Address', 'ID', 'Name']
imglist_headers = stringlist_headers + ["Img", "Name JP", "Img JP"] imglist_headers = stringlist_headers + ['Img', 'Name JP', 'Img JP']
class FF5Reader(QMainWindow): class FF5Reader(QMainWindow):
@ -122,32 +128,32 @@ class FF5Reader(QMainWindow):
battle_commands = make_string_img_list(0x201150, 7, 0x60, 0x115800, 5) battle_commands = make_string_img_list(0x201150, 7, 0x60, 0x115800, 5)
dialogue = make_string_img_list(0x2013F0, 3, 0x900, start_jp=0x082220, len_jp=2, start_str=0x0, start_jp_str=0x0A0000, indirect=True, large=True, macros_en=const.Dialogue_Macros_EN, macros_jp=const.Dialogue_Macros_JP) dialogue = make_string_img_list(0x2013F0, 3, 0x900, start_jp=0x082220, len_jp=2, start_str=0x0, start_jp_str=0x0A0000, indirect=True, large=True, macros_en=const.Dialogue_Macros_EN, macros_jp=const.Dialogue_Macros_JP)
zone_structure = [("NPC Layer", 2, None), zone_structure = [('NPC Layer', 2, None),
("Name", 1, [z[2] for z in zone_names]), ('Name', 1, [z[2] for z in zone_names]),
("ShadowFlags", 1, None), ('ShadowFlags', 1, None),
("0x04", 1, None), (hex(4, 2), 1, None),
("0x05", 1, None), (hex(5, 2), 1, None),
("Flags 0x06", 1, None), ('Flags '+hex(6,2),1, None),
("0x07", 1, None), (hex(7, 2), 1, None),
("Tileset", 1, None), ('Tileset', 1, None),
("Tileset2", 2, None), ('Tileset2', 2, None),
#("0x0A", 1, None), #('0x0A', 1, None),
("0x0B", 1, None), (hex(11, 2), 1, None),
("Collision Layer",1, None), ('Collision Layer',1, None),
("0x0D", 1, None), (hex(13, 2), 1, None),
("0x0E", 1, None), (hex(14, 2), 1, None),
("0x0F", 1, None), (hex(15, 2), 1, None),
("0x10", 1, None), (hex(16, 2), 1, None),
("0x11", 1, None), (hex(17, 2), 1, None),
("0x12", 1, None), (hex(18, 2), 1, None),
("0x13", 1, None), (hex(19, 2), 1, None),
("0x14", 1, None), (hex(20, 2), 1, None),
("0x15", 1, None), (hex(21, 2), 1, None),
("Palette", 1, None), ('Palette', 1, None),
("0x17", 1, None), (hex(23, 2), 1, None),
("0x18", 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 = [] zone_data = []
for i in range(const.zone_count): for i in range(const.zone_count):
@ -160,8 +166,7 @@ class FF5Reader(QMainWindow):
offset = 0x0F0000 + (i*2) offset = 0x0F0000 + (i*2)
pointer = 0x0F0000 + int.from_bytes(ROM_en[offset:offset+2],'little') pointer = 0x0F0000 + int.from_bytes(ROM_en[offset:offset+2],'little')
length = int.from_bytes(ROM_en[offset+2:offset+4],'little') - int.from_bytes(ROM_en[offset:offset+2],'little') length = int.from_bytes(ROM_en[offset+2:offset+4],'little') - int.from_bytes(ROM_en[offset:offset+2],'little')
tileset_data.append(('0x{:02X}'.format(i), '0x{:06X}'.format(offset), tileset_data.append((hex(i, 2), hex(offset, 6), hex(pointer, 6), hex(length, 4)))
'0x{:06X}'.format(pointer), '0x{:04X}'.format(length)))
npc_layers = [] npc_layers = []
offset = 0x0E59C0 offset = 0x0E59C0
@ -172,28 +177,28 @@ class FF5Reader(QMainWindow):
npcs = (next - start) // 7 npcs = (next - start) // 7
for npc in range(npcs): for npc in range(npcs):
address = start + (npc*7) address = start + (npc*7)
npc_layers.append(["0x{0:06X}".format(i), "0x{0:03X}".format(layer)] + parse_struct(ROM_en, address, const.npc_layer_structure)) npc_layers.append([hex(i, 6), hex(layer, 3)] + parse_struct(ROM_en, address, const.npc_layer_structure))
enemy_tile_layouts = [] #enemy_tile_layouts = []
address = 0x10D004 #address = 0x10D004
for i in range(0x66): #for i in range(0x66):
offset = address + (i*8) #offset = address + (i*8)
img = QImage(8, 8, QImage.Format_Mono) #img = QImage(8, 8, QImage.Format_Mono)
img.setColorTable(const.mono_palette) #img.setColorTable(const.mono_palette)
for i in range(8): #for i in range(8):
ptr = img.scanLine(i) #ptr = img.scanLine(i)
ptr.setsize(32) #ptr.setsize(32)
ptr[0:1] = ROM_en[offset+i:offset+i+1] #ptr[0:1] = ROM_en[offset+i:offset+i+1]
pixmap = QPixmap.fromImage(img) #pixmap = QPixmap.fromImage(img)
enemy_tile_layouts.append(pixmap.scaled(16, 16)) #enemy_tile_layouts.append(pixmap.scaled(16, 16))
enemy_sprite_data = [] enemy_sprite_data = []
enemy_sprite_structure = [ enemy_sprite_structure = [
("Sprite data offset", 2, None), ('Sprite data offset', 2, None),
("Multiple things", 2, None), ('Multiple things', 2, None),
("Tile Layout ID", 1, enemy_tile_layouts) ('Tile Layout ID', 1, None)
] ]
enemy_sprite_headers = ["Address"]+[i[0] for i in enemy_sprite_structure]+["EN Name","EN Name"] enemy_sprite_headers = ['Address']+[i[0] for i in enemy_sprite_structure]+['EN Name','EN Name']
address = 0x14B180 address = 0x14B180
for i in range(0x180): for i in range(0x180):
enemy_sprite_data.append(parse_struct(ROM_en, address + (i*5), enemy_sprite_structure) + enemy_names[i][2:4]) enemy_sprite_data.append(parse_struct(ROM_en, address + (i*5), enemy_sprite_structure) + enemy_names[i][2:4])
@ -201,39 +206,45 @@ class FF5Reader(QMainWindow):
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)
enemy_sprites = make_enemy_sprites(ROM_en) enemy_sprites = make_enemy_sprites(ROM_en)
self.battle_strips_ff4 = make_character_battle_sprites_ff4(ROM_FF4jp)
enemy_sprites_named = [stack_labels(s, d[-2]) for s, d in zip(enemy_sprites, enemy_sprite_data)]
self.tabwidget = QTabWidget() self.tabwidget = QTabWidget()
strings_tab = QTabWidget() strings_tab = QTabWidget()
structs_tab = QTabWidget() structs_tab = QTabWidget()
sprites_tab = QTabWidget() sprites_tab = QTabWidget()
self.tabwidget.addTab(strings_tab, "Strings") self.tabwidget.addTab(strings_tab, 'Strings')
self.tabwidget.addTab(structs_tab, "Structs") self.tabwidget.addTab(structs_tab, 'Structs')
self.tabwidget.addTab(sprites_tab, "Images") self.tabwidget.addTab(sprites_tab, 'Images')
sprites_tab.addTab(make_pixmap_table(glyph_sprites_en_small, scale=4), "Glyphs (EN)") sprites_tab.addTab(make_pixmap_table(glyph_sprites_en_small, scale=4), 'Glyphs (EN)')
sprites_tab.addTab(make_pixmap_table(glyph_sprites_en_large, scale=2), "Glyphs (Dialogue EN)") sprites_tab.addTab(make_pixmap_table(glyph_sprites_en_large, scale=2), 'Glyphs (Dialogue EN)')
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(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, scale=1), "Enemy Sprites") #sprites_tab.addTab(make_pixmap_table(enemy_sprites, scale=1), 'Enemy Sprites')
sprites_tab.addTab(make_pixmap_table(enemy_sprites_named, cols=32, scale=1), 'Enemy Sprites')
sprites_tab.addTab(make_pixmap_table(self.battle_strips_ff4, cols=16, scale=2), 'FF4 Character Battle Sprites')
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(const.npc_layer_headers, npc_layers, True), "NPC Layers")
structs_tab.addTab(make_table(enemy_sprite_headers, enemy_sprite_data, True), "Enemy Sprites")
strings_tab.addTab(make_table(imglist_headers, items, row_labels=False), "Items") structs_tab.addTab(make_table(zone_headers, zone_data, True), 'Zones')
strings_tab.addTab(make_table(imglist_headers, magics, row_labels=False), "Magics") structs_tab.addTab(make_table(tileset_headers, tileset_data, True), 'Tilesets')
strings_tab.addTab(make_table(imglist_headers, more_magics, row_labels=False), "More Magics") structs_tab.addTab(make_table(const.npc_layer_headers, npc_layers, True), 'NPC Layers')
strings_tab.addTab(make_table(imglist_headers, enemy_names, row_labels=False), "Enemy Names") structs_tab.addTab(make_table(enemy_sprite_headers, enemy_sprite_data, True), 'Enemy Sprites')
strings_tab.addTab(make_table(imglist_headers, character_names, row_labels=False), "Character Names")
strings_tab.addTab(make_table(imglist_headers, job_names, row_labels=False), "Job Names") strings_tab.addTab(make_table(imglist_headers, items, row_labels=False), 'Items')
strings_tab.addTab(make_table(imglist_headers, ability_names, row_labels=False), "Ability Names") strings_tab.addTab(make_table(imglist_headers, magics, row_labels=False), 'Magics')
strings_tab.addTab(make_table(imglist_headers, battle_commands, row_labels=False), "Battle Commands") strings_tab.addTab(make_table(imglist_headers, more_magics, row_labels=False), 'More Magics')
strings_tab.addTab(make_table(imglist_headers, zone_names, True, scale=1), "Zone Names") strings_tab.addTab(make_table(imglist_headers, enemy_names, row_labels=False), 'Enemy Names')
strings_tab.addTab(make_table(imglist_headers+['JP address'], dialogue, scale=1), "Dialogue") strings_tab.addTab(make_table(imglist_headers, character_names, row_labels=False), 'Character Names')
strings_tab.addTab(make_table(imglist_headers, job_names, row_labels=False), 'Job Names')
strings_tab.addTab(make_table(imglist_headers, ability_names, row_labels=False), 'Ability Names')
strings_tab.addTab(make_table(imglist_headers, battle_commands, row_labels=False), 'Battle Commands')
strings_tab.addTab(make_table(imglist_headers, zone_names, True, scale=1), 'Zone Names')
strings_tab.addTab(make_table(imglist_headers+['JP address'], dialogue, scale=1), 'Dialogue')
self.string_decoder = QWidget() self.string_decoder = QWidget()
self.decoder_input = QLineEdit() self.decoder_input = QLineEdit()
@ -241,7 +252,7 @@ class FF5Reader(QMainWindow):
self.decoder_layout = QVBoxLayout() self.decoder_layout = QVBoxLayout()
self.decoder_layout.addWidget(self.decoder_input) self.decoder_layout.addWidget(self.decoder_input)
self.string_decoder.setLayout(self.decoder_layout) self.string_decoder.setLayout(self.decoder_layout)
strings_tab.addTab(self.string_decoder, "String Decoder") strings_tab.addTab(self.string_decoder, 'String Decoder')
layout = QHBoxLayout() layout = QHBoxLayout()
layout.addWidget(self.tabwidget) layout.addWidget(self.tabwidget)
@ -265,8 +276,8 @@ class FF5Reader(QMainWindow):
class Canvas: class Canvas:
def __init__(self, rows, columns, color=bg_trans): def __init__(self, cols, rows, color=bg_trans):
self.image = QImage(8*rows, 8*columns, QImage.Format_ARGB32_Premultiplied) self.image = QImage(8*cols, 8*rows, QImage.Format_ARGB32_Premultiplied)
self.image.fill(color) self.image.fill(color)
self.painter = QtGui.QPainter(self.image) self.painter = QtGui.QPainter(self.image)
self.max_x = 1 self.max_x = 1
@ -289,14 +300,14 @@ class Canvas:
def parse_struct(rom, offset, structure): def parse_struct(rom, offset, structure):
out = ["0x{:06X}".format(offset)] out = [hex(offset, 6)]
j = 0 j = 0
for z in structure: for z in structure:
val = int.from_bytes(rom[offset+j:offset+j+z[1]],'little') val = int.from_bytes(rom[offset+j:offset+j+z[1]],'little')
if z[2] and val < len(z[2]): if z[2] and val < len(z[2]):
out.append(z[2][val]) out.append(z[2][val])
else: else:
out.append("0x{:0{}X}".format(val, z[1]*2)) out.append(hex(val, z[1]*2))
j += z[1] j += z[1]
return out return out
@ -333,19 +344,41 @@ def make_enemy_sprites(rom):
return sprites return sprites
def make_battle_strip(rom, palette_address, tile_address, num_tiles):
palette = generate_palette(rom, palette_address)
# We don't want the background drawn, so we'll make that colour transparent
palette[0] = 0
battle_strip = Canvas(2, divceil(num_tiles, 2)) # KO sprites are here which means more tiles than FFV
for j in range(num_tiles):
offset = tile_address+(j*32)
battle_strip.draw_pixmap(j%2, j//2, create_tile(rom[offset:offset+32], palette))
return battle_strip.pixmap()
def make_character_battle_sprites_ff4(rom):
tile_address = 0x0D0000
pig_tile_address = 0x0D7000
golbez_tile_address = 0x0D7600
anna_tile_address = 0x0D7960
palette_address = 0x0E7D00
golbez_palette_address = 0x0E7EC0
anna_palette_address = 0x0E7EE0
battle_strips = []
for i in range(0, 14*32, 32): # 14 regular characters. Pig, Golbez and Anna follow with different tile spacing and palette order.
battle_strips.append(make_battle_strip(rom, palette_address+i, tile_address+(i*64), 64)) # KO sprites are here which means more tiles per strip than FFV
battle_strips.append(make_battle_strip(rom, golbez_palette_address, golbez_tile_address, 27))
battle_strips.append(make_battle_strip(rom, anna_palette_address, anna_tile_address, 14))
for i in range(0, 16*32, 32): # 16 pigs.
battle_strips.append(make_battle_strip(rom, palette_address+i, pig_tile_address, 48))
return battle_strips
def make_character_battle_sprites(rom): def make_character_battle_sprites(rom):
tile_address = 0x120000 tile_address = 0x120000
palette_address = 0x14A3C0 palette_address = 0x14A3C0
battle_strips = [] battle_strips = []
for i in range(0, 110*32, 32): for i in range(0, (22*5)*32, 32): # 22 jobs 5 characters
palette = generate_palette(rom, palette_address + i) battle_strips.append(make_battle_strip(rom, palette_address+i, tile_address+(i*48), 48))
# We don't want the background drawn, so we'll make that colour transparent
palette[0] = 0
battle_strip = Canvas(2, 24)
for j in range(48):
offset = tile_address+(i*48)+(j*32)
battle_strip.draw_pixmap(j%2, j//2, create_tile(rom[offset:offset+32], palette))
battle_strips.append(battle_strip.pixmap())
return battle_strips return battle_strips
@ -504,8 +537,8 @@ def make_string_img_list(start, length, num, start_jp=None, len_jp=None, start_s
str_jp = '' str_jp = ''
img_jp = None img_jp = None
stringlist.append([ stringlist.append([
"0x{:06X}".format(en), "0x{:0{}X}".format(id, id_digits), hex(en, 6), hex(id, id_digits),
str_en, img_en, str_jp, img_jp, "0x{:06X}".format(jp_start) str_en, img_en, str_jp, img_jp, hex(jp_start, 6)
]) ])
else: else:
for id in range(num): for id in range(num):
@ -517,7 +550,7 @@ def make_string_img_list(start, length, num, start_jp=None, len_jp=None, start_s
else: else:
str_en, img_en = make_string_img_small(ROM_en[j1:j1+length]) str_en, img_en = make_string_img_small(ROM_en[j1:j1+length])
str_jp, img_jp = make_string_img_small(ROM_jp[j2:j2+len_jp], jp=True) str_jp, img_jp = make_string_img_small(ROM_jp[j2:j2+len_jp], jp=True)
stringlist.append(["0x{:06X}".format(j1), "0x{:0{}X}".format(id, id_digits), str_en, img_en, str_jp, img_jp]) stringlist.append([hex(j1, 6), hex(id, id_digits), str_en, img_en, str_jp, img_jp])
return stringlist return stringlist
@ -533,26 +566,34 @@ def table_size_to_contents(table):
def make_table(headers, items, sortable=False, row_labels=True, scale=2): def make_table(headers, items, sortable=False, row_labels=True, scale=2):
""" '''
Helper function to tabulate 2d lists Helper function to tabulate 2d lists
""" '''
cols = len(headers) cols = len(headers)
rows = len(items) rows = len(items)
rd = hex_length(rows-1) rd = hex_length(rows-1)
table = QTableWidget(rows, cols) table = QTableWidget(rows, cols)
if row_labels: if row_labels:
table.setVerticalHeaderLabels(['0x{:0{}X}'.format(v, rd) for v in range(rows)]) table.setVerticalHeaderLabels([hex(v, rd) for v in range(rows)])
else: else:
table.verticalHeader().setVisible(False) table.verticalHeader().setVisible(False)
table.setHorizontalHeaderLabels(headers) table.setHorizontalHeaderLabels(headers)
for row, col, item in [(x,y,items[x][y]) for x in range(rows) for y in range(cols)]: for row, col, item in [(x,y,items[x][y]) for x in range(rows) for y in range(cols)]:
if type(item) == type(QPixmap()): if isinstance(item, QWidget):
table.setCellWidget(row, col, item)
elif isinstance(item, QPixmap):
pix = item.scaled(item.size() * scale)
lab = QLabel() lab = QLabel()
lab.setPixmap(item.scaled(item.size() * scale)) lab.setPixmap(pix)
table.setCellWidget(row, col, lab) table.setCellWidget(row, col, lab)
elif item is not None: elif item is not None:
if item.endswith('₁₆'):
s = '<tt>{}</tt><sub>16</sub>'.format(item[:-2])
lab = QLabel(s)
table.setCellWidget(row, col, lab)
else:
q_item = QTableWidgetItem(item) q_item = QTableWidgetItem(item)
if item[:2] == "0x": if item.startswith(HEX_PREFIX):
q_item.setFont(monofont) q_item.setFont(monofont)
table.setItem(row, col, q_item) table.setItem(row, col, q_item)
table_size_to_contents(table) table_size_to_contents(table)
@ -567,10 +608,12 @@ def make_pixmap_table(items, cols=16, scale=4):
rd = hex_length(rows-1)+1 rd = hex_length(rows-1)+1
cd = hex_length(cols-1) cd = hex_length(cols-1)
table = QTableWidget(rows, cols) table = QTableWidget(rows, cols)
table.setVerticalHeaderLabels(['0x{:0{}X}'.format(v*cols, rd) for v in range(rows)]) table.setVerticalHeaderLabels([hex(v*cols, rd) for v in range(rows)])
table.setHorizontalHeaderLabels(['0x{:0{}X}'.format(v, cd) for v in range(cols)]) table.setHorizontalHeaderLabels([hex(v, cd) for v in range(cols)])
for i in range(len(items)): for i, item in enumerate(items):
item = items[i] if isinstance(item, QWidget):
table.setCellWidget(i // cols, i % cols, item)
elif isinstance(item, QPixmap):
lab = QLabel() lab = QLabel()
lab.setPixmap(item.scaled(item.size() * scale)) lab.setPixmap(item.scaled(item.size() * scale))
lab.setAlignment(QtCore.Qt.AlignCenter) lab.setAlignment(QtCore.Qt.AlignCenter)
@ -579,6 +622,32 @@ def make_pixmap_table(items, cols=16, scale=4):
return table return table
def stack_labels(*items):
w = QWidget()
w.setContentsMargins(0, 0, 0, 0)
l = QVBoxLayout()
l.setAlignment(QtCore.Qt.AlignCenter)
l.setSpacing(0)
l.setContentsMargins(0, 0, 0, 0)
for item in items:
lab = QLabel()
if isinstance(item, QPixmap):
lab.setPixmap(item)
else:
lab.setText(item.strip())
lab.setAlignment(QtCore.Qt.AlignCenter)
lab.setMargin(0)
l.addWidget(lab)
w.setLayout(l)
return w
def hex(num, digits):
# Consolidate formatting for consistency
#return '{:0{}X}₁₆'.format(num, digits)
return HEX_PREFIX + '{:0{}X}'.format(num, digits)
def main(): def main():
app = QApplication(sys.argv) app = QApplication(sys.argv)
mainwindow = FF5Reader() mainwindow = FF5Reader()