diff --git a/const.py b/const.py new file mode 100644 index 0000000..f0440eb --- /dev/null +++ b/const.py @@ -0,0 +1,92 @@ +''' +No license for now +''' + +Glyphs = ( + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x00 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x10 + 'A','B','C','D', 'E','F','G','H', 'I','J','K','L', 'M','N','O','P', # 0x20 + 'Q','R','S','T','U','V','W','X','Y','Z','[stone]','[toad]','[mini]','[float]','[poison]','[KO]', # 0x30 + '[blind]',' ',' ',' ',' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x40 + ' ',' ',' ','0', '1','2','3','4', '5','6','7','8', '9','_m','_H','_P', # 0x50 + 'A','B','C','D', 'E','F','G','H', 'I','J','K','L', 'M','N','O','P', # 0x60 + 'Q','R','S','T', 'U','V','W','X', 'Y','Z','a','b', 'c','d','e','f', # 0x70 + 'g','h','i','j', 'k','l','m','n', 'o','p','q','r', 's','t','u','v', # 0x80 + 'w','x','y','z', 'il','it',' ','li', 'll','\'','"',':', ';',',','(',')', # 0x90 + '/','!','?','.', 'ti','fi','Bl','a', 'pe','l','\'','"', 'if','lt','tl','ir', # 0xA0 + 'tt','や','ユ','ゆ', 'ヨ', 'よ', 'ワ', 'わ', 'ン', 'ん', 'ヲ','を', '[key]', '[shoe]', '◆', '[hammer]', # 0xB0 + '⛺', '[ribbon]', '[potion]', '[shirt]', '♪', '-', '[shuriken]', '‥', '[scroll]', '!', '[claw]', '?', '[glove]', '%', '/', ':', # 0xC0 + '「', '」', '0', 'A', 'B', 'X', 'Y', 'L', 'R', 'E', 'H', 'M', 'P', 'S', 'C', 'T', # 0xD0 + '↑', '→', '+', '[sword]', '[wh.mag]', '[blk.mag]', '🕒', '[knife]', '[spear]', '[axe]', '[katana]', '[rod]', '[staff]', '[bow]', '[harp]', '[whip]', # 0xE0 + '[bell]', '[shield]', '[helmet]', '[armor]', '[ring]', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ') # F0 +Glyphs_JP = list(Glyphs) # Transcription of the japanese glyph tiles +Glyphs_JP[0x60:0xCD] = [ + 'ハ','は','ヒ','ひ', 'フ','ふ','ヘ','へ', 'ホ','ほ','カ','か', 'キ','き','ク','く', # 0x60 + 'ケ','け','コ','こ', 'サ','さ','シ','し', 'ス','す','セ','せ', 'ソ','そ','タ','た', # 0x70 + 'チ','ち','ツ','つ', 'テ','て','ト','と', 'ウ','う','ア','あ', 'イ','い','エ','え', # 0x80 + 'オ','お','ナ','な', 'ニ','に','ヌ','ぬ', 'ネ','ね','ノ','の', 'マ','ま','ミ','み', # 0x90 + 'ム','む','メ','め', 'モ','も','ラ','ら', 'リ','り','ル','る', 'レ','れ','ロ','ろ', # 0xA0 + 'ヤ','や','ユ','ゆ', 'ヨ','よ','ワ','わ', 'ン','ん','ヲ','を', 'ッ','っ','ャ','ゃ', # 0xB0 + 'ュ','ゅ','ョ','ょ', 'ァ','ー','ィ', '‥', 'ぅ','!','ェ','?', 'ォ'] # 0xC0 +Glyphs_JP[0xE3] = '[洋剣]' +Glyphs_JP[0xE7:0xF0] = ['[刂]', '[槍]', '[鉞]', '[刀]', '[棒]', '[杖]', '[弓]', '♪', '[鞭]'] +Glyphs_JP2 = list(Glyphs_JP) # Japanese glyphs using the dakuten encoding +Glyphs_JP2[0x20:0x52] = [ + 'バ','ば','ビ','び', 'ブ','ぶ','ベ','べ', 'ボ','ぼ','ガ','が', 'ギ','ぎ','グ','ぐ', # 0x20 + 'ゲ','げ','ゴ','ご', 'ザ','ざ','ジ','じ', 'ズ','ず','ゼ','ぜ', 'ゾ','ぞ','ダ','だ', # 0x30 + 'ヂ','ぢ','ヅ','づ', 'デ','で','ド','ど', # 0x40-0x48 + 'パ','ぱ','ピ','ぴ', 'プ','ぷ','ペ','ぺ', 'ポ','ぽ'] # 0x48-0x52 +Glyphs_JP_large = list(Glyphs_JP2) # Large glyphs are subtly different again +Glyphs_JP_large[0xE0:0xEB] = ['←','→','+','、', '◯', '『', 'F', '°C', '・', '(', ')'] + +Glyphs_Kanji = ( # TODO: finish this + '王','行','力','様', ' ',' ',' ',' ', '入','城',' ','士', ' ',' ',' ','父', # 0x000 + '人','見',' ',' ', '大',' ',' ','何', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x010 + ' ','心','間',' ', '風',' ',' ',' ', ' ',' ',' ',' ', '火',' ',' ',' ', # 0x020 + ' ',' ',' ',' ', ' ',' ',' ',' ', '地',' ',' ',' ', ' ',' ',' ',' ', # 0x030 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','一',' ',' ', # 0x040 + ' ',' ','神',' ', ' ',' ','殿',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x050 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x060 + ' ',' ','階',' ', ' ',' ',' ',' ', ' ',' ','土',' ', ' ',' ',' ',' ', # 0x070 + + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x080 + ' ',' ',' ',' ', ' ','下',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x090 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0A0 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0B0 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','少',' ',' ', # 0x0C0 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0D0 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0E0 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0F0 + + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x100 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x110 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x120 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x130 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x140 + '炎',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x150 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x160 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x170 + + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','刀',' ',' ', ' ',' ',' ',' ', # 0x180 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x190 + ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ') # 0x1A0 + +BGM_Tracks = ( + "Ahead on our way", "The Fierce Battle", "A Presentiment", "Go Go Boko!", + "Pirates Ahoy", "Tenderness in the Air", "Fate in Haze", "Moogle theme", + "Prelude/Crystal Room", "The Last Battle", "Requiem", "Nostalgia", + "Cursed Earths", "Lenna's Theme", "Victory's Fanfare", "Deception", + "The Day Will Come", "Nothing?", "ExDeath's Castle", "My Home, Sweet Home", + "Waltz Suomi", "Sealed Away", "The Four Warriors of Dawn", "Danger", + "The Fire Powered Ship", "As I Feel, You Feel", "Mambo de Chocobo!", "Music Box", + "Intension of the Earth", "The Dragon Spreads its Wings", "Beyond the Deep Blue Sea", "The Prelude of Empty Skies", + "Searching the Light", "Harvest", "Battle with Gilgamesh", "Four Valiant Hearts", + "The Book of Sealings", "What?", "Hurry! Hurry!", "Unknown Lands", + "The Airship", "Fanfare 1", "Fanfare 2", "The Battle", + "Walking the Snowy Mountains", "The Evil Lord Exdeath", "The Castle of Dawn", "I'm a Dancer", + "Reminiscence", "Run!", "The Ancient Library", "Royal Palace", + "Good Night!", "Piano Lesson 1", "Piano Lesson 2", "Piano Lesson 3", + "Piano Lesson 4", "Piano Lesson 5", "Piano Lesson 6", "Piano Lesson 7", + "Piano Lesson 8", "Musica Machina", "Meteor falling?", "The Land Unknown", + "The Decisive Battle", "The Silent Beyond", "Dear Friends", "Final Fantasy", + "A New Origin", "Chirping sound") diff --git a/ff5reader.py b/ff5reader.py index b7e1b44..cb4f4eb 100644 --- a/ff5reader.py +++ b/ff5reader.py @@ -7,10 +7,12 @@ import sys import os import struct from array import array -#from heximg import HexImg +from snestile import create_tile, create_tritile +import const +import time pyqt_version = 0 -skip_pyqt5 = True # "PYQT4" in os.environ +skip_pyqt5 = "PYQT4" in os.environ if not skip_pyqt5: try: @@ -35,7 +37,7 @@ if not skip_pyqt5: except ImportError: print("Couldn't import Qt5 dependencies. " "Make sure you installed the PyQt5 package.") -if pyqt_version is 0: +if pyqt_version == 0: try: import sip sip.setapi('QVariant', 2) @@ -70,11 +72,6 @@ if not monofont.fixedPitch(): monofont.setStyleHint(QFont.TypeWriter) if not monofont.fixedPitch(): monofont.setFamily("Monospace") - #"Ubuntu Mono", - #"DejaVu Sans Mono", - #"Liberation Mono", - #"courier"]) -#monofont.setPixelSize(8) def divceil(numerator, denominator): # Reverse floor division for ceil @@ -86,186 +83,24 @@ def hex_length(i): filename = "Final Fantasy V (Japan) [En by RPGe v1.1].sfc" with open(filename, 'rb') as file1: ROM = file1.read() -print(len(ROM)) +print(len(ROM), filename) filename2 = "Final Fantasy V (Japan).sfc" with open(filename2, 'rb') as file2: ROM2 = file2.read() -print(len(ROM2)) +print(len(ROM2), filename2) -col_palette = [QColor( 0, 0, 0), - QColor( 0, 0,128,0), - QColor(128,128,128), - QColor(255,255,255)] +col_palette = [QColor(0, 0, 0), QColor(0, 0, 128, 0), + QColor(128, 128, 128), QColor(255, 255, 255)] bg_color = QColor(0, 0, 128) -#for i in range(4, 256): - #col_palette.append(QColor(i, i, i)) - -def create_tile(bytes): - ''' - Creates a QPixmap of a SNES tile. DO NOT USE OUTSIDE OF QApplication CONTEXT - ''' - planes = len(bytes)//8 - tile = array('B', range(64)) - img = QImage(8, 8, QImage.Format_Indexed8) - imgbits = img.bits() - imgbits.setsize(img.byteCount()) - if planes == 0: - raise ValueError("Empty bytes passed") - if planes == 1: - img.setColorTable([0x00000080, 0xFFFFFFFF]) - t_ptr = 0 - for j, x in [(j,x) for j in range(8) for x in reversed(range(8))]: - tile[t_ptr] = (bytes[j] >> x & 1) - t_ptr += 1 - else: - img.setColorTable([c.rgba() for c in col_palette]) - t_ptr = 0 - for j, x in [(j,x) for j in range(0, 16, 2) for x in reversed(range(8))]: - tile[t_ptr] = (bytes[j] >> x & 1) | ((bytes[j+1] >> x & 1) << 1) - t_ptr += 1 - t_ptr = 0 - if planes == 3: - for j, x in [(j,x) for j in range(16, 24, 1) for x in reversed(range(8))]: - tile[t_ptr] |= ((bytes[j] >> x & 1) << 2) - t_ptr += 1 - elif planes >= 4: - for j, x in [(j,x) for j in range(16, 32, 2) for x in reversed(range(8))]: - tile[t_ptr] |= ((bytes[j] >> x & 1) << 2) | ((bytes[j+1] >> x & 1) << 3) - t_ptr += 1 - if planes == 8: - t_ptr = 0 - for j, x in [(j,x) for j in range(32, 48, 2) for x in reversed(range(8))]: - tile[t_ptr] |= ((bytes[j] >> x & 1) << 4) | ((bytes[j+1] >> x & 1) << 5) \ - | ((bytes[j+16] >> x & 1) << 6) | ((bytes[j+17] >> x & 1) << 7) - t_ptr += 1 - imgbits[:64] = tile - return QPixmap.fromImage(img) - -def create_tritile(bytes): - img = QImage(16, 12, QImage.Format_Indexed8) - imgbits = img.bits() - imgbits.setsize(img.byteCount()) - img.setColorTable([0xFF000080, 0xFFFFFFFF]) - tile = array('B', range(192)) - for p, row, b in [(p,j,b) for p in range(2) for j in range(12) for b in reversed(range(8))]: - tile[(7-b) + (row*16) + (p*8)] = (bytes[row + (p*12)] >> b & 1) - imgbits[:192] = tile - return QPixmap.fromImage(img) - -def create_quadtile(bytes, ltr=False): - img = QImage(16, 16, QImage.Format_ARGB32_Premultiplied) - img.fill(QColor(0,0,0,0)) - painter = QtGui.QPainter(img) - painter.drawPixmap(0, 0, create_tile(bytes[0:8])) - painter.drawPixmap(8, 8, create_tile(bytes[24:32])) - if ltr: - painter.drawPixmap(8, 0, create_tile(bytes[8:16])) - painter.drawPixmap(0, 8, create_tile(bytes[16:24])) - else: - painter.drawPixmap(0, 8, create_tile(bytes[8:16])) - painter.drawPixmap(8, 0, create_tile(bytes[16:24])) - del painter - return QPixmap.fromImage(img) glyph_sprites = [] -glyph_sprites2 = [] +glyph_sprites_jp = [] glyph_sprites_large = [] glyph_sprites_kanji = [] -Glyphs = ( - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x00 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x10 - 'A','B','C','D', 'E','F','G','H', 'I','J','K','L', 'M','N','O','P', # 0x20 - 'Q','R','S','T','U','V','W','X','Y','Z','[stone]','[toad]','[mini]','[float]','[poison]','[KO]', # 0x30 - '[blind]',' ',' ',' ',' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x40 - ' ',' ',' ','0', '1','2','3','4', '5','6','7','8', '9','_m','_H','_P', # 0x50 - 'A','B','C','D', 'E','F','G','H', 'I','J','K','L', 'M','N','O','P', # 0x60 - 'Q','R','S','T', 'U','V','W','X', 'Y','Z','a','b', 'c','d','e','f', # 0x70 - 'g','h','i','j', 'k','l','m','n', 'o','p','q','r', 's','t','u','v', # 0x80 - 'w','x','y','z', 'il','it',' ','li', 'll','\'','"',':', ';',',','(',')', # 0x90 - '/','!','?','.', 'ti','fi','Bl','a', 'pe','l','\'','"', 'if','lt','tl','ir', # 0xA0 - 'tt','や','ユ','ゆ', 'ヨ', 'よ', 'ワ', 'わ', 'ン', 'ん', 'ヲ','を', '[key]', '[shoe]', '◆', '[hammer]', # 0xB0 - '⛺', '[ribbon]', '[potion]', '[shirt]', '♪', '-', '[shuriken]', '‥', '[scroll]', '!', '[claw]', '?', '[glove]', '%', '/', ':', # 0xC0 - '「', '」', '0', 'A', 'B', 'X', 'Y', 'L', 'R', 'E', 'H', 'M', 'P', 'S', 'C', 'T', # 0xD0 - '↑', '→', '+', '[sword]', '[wh.mag]', '[blk.mag]', '🕒', '[knife]', '[spear]', '[axe]', '[katana]', '[rod]', '[staff]', '[bow]', '[harp]', '[whip]', # 0xE0 - '[bell]', '[shield]', '[helmet]', '[armor]', '[ring]', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ') # F0 -Glyphs_JP = list(Glyphs) # Transcription of the japanese glyph tiles -Glyphs_JP[0x60:0xCD] = [ - 'ハ','は','ヒ','ひ', 'フ','ふ','ヘ','へ', 'ホ','ほ','カ','か', 'キ','き','ク','く', # 0x60 - 'ケ','け','コ','こ', 'サ','さ','シ','し', 'ス','す','セ','せ', 'ソ','そ','タ','た', # 0x70 - 'チ','ち','ツ','つ', 'テ','て','ト','と', 'ウ','う','ア','あ', 'イ','い','エ','え', # 0x80 - 'オ','お','ナ','な', 'ニ','に','ヌ','ぬ', 'ネ','ね','ノ','の', 'マ','ま','ミ','み', # 0x90 - 'ム','む','メ','め', 'モ','も','ラ','ら', 'リ','り','ル','る', 'レ','れ','ロ','ろ', # 0xA0 - 'ヤ','や','ユ','ゆ', 'ヨ','よ','ワ','わ', 'ン','ん','ヲ','を', 'ッ','っ','ャ','ゃ', # 0xB0 - 'ュ','ゅ','ョ','ょ', 'ァ','ー','ィ', '‥', 'ぅ','!','ェ','?', 'ォ'] # 0xC0 -Glyphs_JP[0xE3] = '[洋剣]' -Glyphs_JP[0xE7:0xF0] = ['[刂]', '[槍]', '[鉞]', '[刀]', '[棒]', '[杖]', '[弓]', '♪', '[鞭]'] -Glyphs_JP2 = list(Glyphs_JP) # Japanese glyphs using the dakuten encoding -Glyphs_JP2[0x20:0x52] = [ - 'バ','ば','ビ','び', 'ブ','ぶ','ベ','べ', 'ボ','ぼ','ガ','が', 'ギ','ぎ','グ','ぐ', # 0x20 - 'ゲ','げ','ゴ','ご', 'ザ','ざ','ジ','じ', 'ズ','ず','ゼ','ぜ', 'ゾ','ぞ','ダ','だ', # 0x30 - 'ヂ','ぢ','ヅ','づ', 'デ','で','ド','ど', # 0x40-0x48 - 'パ','ぱ','ピ','ぴ', 'プ','ぷ','ペ','ぺ', 'ポ','ぽ'] # 0x48-0x52 -Glyphs_JP_large = list(Glyphs_JP2) # Large glyphs are subtly different again -Glyphs_JP_large[0xE0:0xEB] = ['←','→','+','、', '◯', '『', 'F', '°C', '・', '(', ')'] - -Glyphs_Kanji = ( - '王','行','力','様', ' ',' ',' ',' ', '入','城',' ','士', ' ',' ',' ','父', # 0x000 - '人','見',' ',' ', '大',' ',' ','何', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x010 - ' ','心','間',' ', '風',' ',' ',' ', ' ',' ',' ',' ', '火',' ',' ',' ', # 0x020 - ' ',' ',' ',' ', ' ',' ',' ',' ', '地',' ',' ',' ', ' ',' ',' ',' ', # 0x030 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','一',' ',' ', # 0x040 - ' ',' ','神',' ', ' ',' ','殿',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x050 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x060 - ' ',' ','階',' ', ' ',' ',' ',' ', ' ',' ','土',' ', ' ',' ',' ',' ', # 0x070 - - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x080 - ' ',' ',' ',' ', ' ','下',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x090 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0A0 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0B0 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','少',' ',' ', # 0x0C0 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0D0 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0E0 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x0F0 - - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x100 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x110 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x120 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x130 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x140 - '炎',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x150 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x160 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x170 - - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ','刀',' ',' ', ' ',' ',' ',' ', # 0x180 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ',' ',' ', # 0x190 - ' ',' ',' ',' ', ' ',' ',' ',' ', ' ',' ') # 0x1A0 - -BGM_Tracks = ( - "Ahead on our way", "The Fierce Battle", "A Presentiment", "Go Go Boko!", - "Pirates Ahoy", "Tenderness in the Air", "Fate in Haze", "Moogle theme", - "Prelude/Crystal Room", "The Last Battle", "Requiem", "Nostalgia", - "Cursed Earths", "Lenna's Theme", "Victory's Fanfare", "Deception", - "The Day Will Come", "Nothing?", "ExDeath's Castle", "My Home, Sweet Home", - "Waltz Suomi", "Sealed Away", "The Four Warriors of Dawn", "Danger", - "The Fire Powered Ship", "As I Feel, You Feel", "Mambo de Chocobo!", "Music Box", - "Intension of the Earth", "The Dragon Spreads its Wings", "Beyond the Deep Blue Sea", "The Prelude of Empty Skies", - "Searching the Light", "Harvest", "Battle with Gilgamesh", "Four Valiant Hearts", - "The Book of Sealings", "What?", "Hurry! Hurry!", "Unknown Lands", - "The Airship", "Fanfare 1", "Fanfare 2", "The Battle", - "Walking the Snowy Mountains", "The Evil Lord Exdeath", "The Castle of Dawn", "I'm a Dancer", - "Reminiscence", "Run!", "The Ancient Library", "Royal Palace", - "Good Night!", "Piano Lesson 1", "Piano Lesson 2", "Piano Lesson 3", - "Piano Lesson 4", "Piano Lesson 5", "Piano Lesson 6", "Piano Lesson 7", - "Piano Lesson 8", "Musica Machina", "Meteor falling?", "The Land Unknown", - "The Decisive Battle", "The Silent Beyond", "Dear Friends", "Final Fantasy", - "A New Origin", "Chirping sound") - stringlist_headers = ["Address", "ID", "Name"] imglist_headers = stringlist_headers + ["Img", "Name JP", "Img JP"] - - npc_layer_count = 0x200 npc_layer_structure = [("Dialogue/trigger ID", 1, None), ("0x01", 1, None), @@ -277,7 +112,6 @@ npc_layer_structure = [("Dialogue/trigger ID", 1, None), npc_layer_headers = ["Address", "Layer"] + [x[0] for x in npc_layer_structure] - class FF5Reader(QMainWindow): """ Main GUI class @@ -285,7 +119,7 @@ class FF5Reader(QMainWindow): def __init__(self): QMainWindow.__init__(self, None) generate_glyphs(ROM, glyph_sprites, 0x11F000) - generate_glyphs(ROM2, glyph_sprites2, 0x11F000) + generate_glyphs(ROM2, glyph_sprites_jp, 0x11F000) generate_glyphs_large(ROM2, glyph_sprites_large, 0x03E800) generate_glyphs_large(ROM2, glyph_sprites_kanji, 0x1BD000, 0x1AA) global zone_names @@ -319,7 +153,7 @@ class FF5Reader(QMainWindow): ("Palette", 1, None), ("0x17", 1, None), ("0x18", 1, None), - ("Music", 1, BGM_Tracks)] + ("Music", 1, const.BGM_Tracks)] zone_headers = ["Address"] + [z[0] for z in zone_structure] zone_data = [] @@ -333,7 +167,7 @@ class FF5Reader(QMainWindow): if z[2] and val < len(z[2]): zone_data[-1].append(z[2][val][2]) else: - zone_data[-1].append("0x{0:0{1}X}".format(val, z[1]*2)) + zone_data[-1].append("0x{:0{}X}".format(val, z[1]*2)) j += z[1] npc_layers = [] @@ -352,7 +186,7 @@ class FF5Reader(QMainWindow): if z[2] and val < len(z[2]): npc_layers[-1].append(z[2][val]) else: - npc_layers[-1].append("0x{0:0{1}X}".format(val, z[1]*2)) + npc_layers[-1].append("0x{:0{}X}".format(val, z[1]*2)) j += z[1] dialogue = make_string_img_list(0x2013F0, 3, 0x500, start_jp=0x082220, len_jp=2, start_str=0x0, start_jp_str=0x0A0000, indirect=True, large=True) # start_str=0x210000 @@ -360,7 +194,7 @@ class FF5Reader(QMainWindow): self.tabwidget = QTabWidget() self.enemy_sprites = QFrame() self.tabwidget.addTab(make_pixmap_table(glyph_sprites, 4), "Glyphs (EN)") - self.tabwidget.addTab(make_pixmap_table(glyph_sprites2, 4), "Glyphs (JP)") + self.tabwidget.addTab(make_pixmap_table(glyph_sprites_jp, 4), "Glyphs (JP)") self.tabwidget.addTab(make_pixmap_table(glyph_sprites_large, 2), "Glyphs (Large)") self.tabwidget.addTab(make_pixmap_table(glyph_sprites_kanji, 2), "Glyphs (Kanji)") self.tabwidget.addTab(self.enemy_sprites, "Enemy Sprites") @@ -384,7 +218,7 @@ class FF5Reader(QMainWindow): def generate_glyphs(rom, spritelist, offset, num=0x100): for i in range(num): j = offset + (i*16) - spritelist.append(create_tile(rom[j:j+16])) + spritelist.append(create_tile(rom[j:j+16], col_palette)) def generate_glyphs_large(rom, spritelist, offset, num=0x100): for i in range(num): @@ -400,19 +234,19 @@ def make_string_img(bytestring, jp=False): painter = QtGui.QPainter(img) if jp: for x, j in enumerate(bytestring): - string = string + Glyphs_JP2[j] + string = string + const.Glyphs_JP2[j] if 0x20 <= j < 0x52: if j > 0x48: - painter.drawPixmap(x*8, 2, glyph_sprites2[j+0x17]) - painter.drawPixmap(x*8+1,-5, glyph_sprites2[0x52]) + painter.drawPixmap(x*8, 2, glyph_sprites_jp[j+0x17]) + painter.drawPixmap(x*8+1,-5, glyph_sprites_jp[0x52]) else: - painter.drawPixmap(x*8, 2, glyph_sprites2[j+0x40]) - painter.drawPixmap(x*8+1,-6, glyph_sprites2[0x51]) + painter.drawPixmap(x*8, 2, glyph_sprites_jp[j+0x40]) + painter.drawPixmap(x*8+1,-6, glyph_sprites_jp[0x51]) else: - painter.drawPixmap(x*8, 2, glyph_sprites2[j]) + painter.drawPixmap(x*8, 2, glyph_sprites_jp[j]) else: for x, j in enumerate(bytestring): - string = string + Glyphs[j] + string = string + const.Glyphs[j] painter.drawPixmap(x*8, 1, glyph_sprites[j]) del painter return string, QPixmap.fromImage(img) @@ -436,6 +270,7 @@ def make_string_img_large(bytestring): j = bytestring[i] ''' There's a lot of dialogue substitutions, this is going to be annoying and/or messy. + TODO: The best way of going about this would be preprocessing the string, I think. Is 0x00 a wait for input marker? 0x03 is クリスタル 0x0A is a speaker tab, perhaps linebreak as well? @@ -492,15 +327,15 @@ def make_string_img_large(bytestring): string = string + '[0x{:02X}]'.format(j) continue elif j == 0x1E: - string = string + Glyphs_Kanji[bytestring[i+1]] + string = string + const.Glyphs_Kanji[bytestring[i+1]] painter.drawPixmap(x*16, (y*14)+1, glyph_sprites_kanji[bytestring[i+1]]) next(it) elif j == 0x1F: - string = string + Glyphs_Kanji[0x100 + bytestring[i+1]] + string = string + const.Glyphs_Kanji[0x100 + bytestring[i+1]] painter.drawPixmap(x*16, (y*14)+1, glyph_sprites_kanji[0x100 + bytestring[i+1]]) next(it) else: - string = string + Glyphs_JP_large[j] + string = string + const.Glyphs_JP_large[j] painter.drawPixmap(x*16, (y*14)+1, glyph_sprites_large[j]) x += 1 if (j == 0xD0) or (j == 0xE5): @@ -549,7 +384,7 @@ def make_string_img_list(start, length, num, start_jp=None, len_jp=None, start_s except ValueError: print("ID: {} \tRef.0x{:06X} 0x{:06X} \tRange EN: 0x{:06X}-0x{:06X} \tRange JP: 0x{:06X}-0x{:06X}".format(id, en, jp, en_start, en_end, jp_start, jp_end)) raise - stringlist.append(["0x{:06X}".format(en), "0x{0:0{1}X}".format(id, id_digits), string, img, string_JP, img_JP, "0x{:06X}".format(jp_start)]) + stringlist.append(["0x{:06X}".format(en), "0x{:0{}X}".format(id, id_digits), string, img, string_JP, img_JP, "0x{:06X}".format(jp_start)]) else: for id in range(num): j1 = start + (id*length) @@ -559,29 +394,26 @@ def make_string_img_list(start, length, num, start_jp=None, len_jp=None, start_s string_JP, img_JP = make_string_img_large(ROM2[j2:j2+len_jp]) else: string_JP, img_JP = make_string_img(ROM2[j2:j2+len_jp], jp=True) - stringlist.append(["0x{:06X}".format(j1), "0x{0:0{1}X}".format(id, id_digits), string, img, string_JP, img_JP]) + stringlist.append(["0x{:06X}".format(j1), "0x{:0{}X}".format(id, id_digits), string, img, string_JP, img_JP]) return stringlist def make_table(headers, items, sortable=False, row_labels=True): """ Helper function to tabulate 2d lists """ - table = QTableWidget() + cols = len(headers) rows = len(items) rd = hex_length(rows-1) - cols = len(headers) - table.setRowCount(rows) + table = QTableWidget(rows, cols) if row_labels: - table.setVerticalHeaderLabels(['0x{0:0{1}X}'.format(v, rd) for v in range(rows)]) + table.setVerticalHeaderLabels(['0x{:0{}X}'.format(v, rd) for v in range(rows)]) else: table.verticalHeader().setVisible(False) - table.setColumnCount(cols) table.setHorizontalHeaderLabels(headers) 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()): - pixmap_scaled = item.scaled(item.size() * 2) lab = QLabel() - lab.setPixmap(pixmap_scaled) + lab.setPixmap(item.scaled(item.size() * 2)) table.setCellWidget(row, col, lab) elif item is not None: q_item = QTableWidgetItem(item) @@ -604,19 +436,16 @@ def make_table(headers, items, sortable=False, row_labels=True): return table def make_pixmap_table(items, scale=4): - table = QTableWidget() - rows = divceil(len(items), 16) - rd = hex_length(rows-1)+1 cols = 16 - table.setRowCount(rows) - table.setVerticalHeaderLabels(['0x{0:0{1}X}'.format(v*16, rd) for v in range(rows)]) - table.setColumnCount(cols) + rows = divceil(len(items), cols) + rd = hex_length(rows-1)+1 + table = QTableWidget(rows, cols) + table.setVerticalHeaderLabels(['0x{:0{}X}'.format(v*cols, rd) for v in range(rows)]) table.setHorizontalHeaderLabels(['0x{:X}'.format(v) for v in range(cols)]) for i in range(len(items)): item = items[i] - pixmap_scaled = item.scaled(item.size() * scale) lab = QLabel() - lab.setPixmap(pixmap_scaled) + lab.setPixmap(item.scaled(item.size() * scale)) table.setCellWidget(i // cols, i % cols, lab) table.resizeColumnsToContents() return table @@ -624,7 +453,6 @@ def make_pixmap_table(items, scale=4): def main(): app = QApplication(sys.argv) - mainwindow = FF5Reader() sys.exit(app.exec_()) diff --git a/snestile.py b/snestile.py new file mode 100644 index 0000000..1aa9d63 --- /dev/null +++ b/snestile.py @@ -0,0 +1,124 @@ +''' +No license for now +''' +import os +from array import array + +pyqt_version = 0 +skip_pyqt5 = "PYQT4" in os.environ + +if not skip_pyqt5: + try: + from PyQt5 import QtGui + from PyQt5.QtGui import QImage, QPixmap, QColor, QPainter + pyqt_version = 5 + except ImportError: + print("Missing PyQt5, trying PyQt4...") +if pyqt_version == 0: + try: + from PyQt4 import QtGui + from PyQt4.QtGui import QImage, QPixmap, QColor, QPainter + pyqt_version = 4 + except ImportError: + print("Missing PyQt4 dependencies") + raise + + +def create_tile(bytes, palette): + ''' + Creates a QPixmap of a SNES tile. DO NOT USE OUTSIDE OF QApplication CONTEXT + ''' + planes = len(bytes)//8 + tile = array('B', range(64)) + img = QImage(8, 8, QImage.Format_Indexed8) + imgbits = img.bits() + imgbits.setsize(img.byteCount()) + if planes == 0: + raise ValueError("Empty bytes passed") + if planes == 1: + img.setColorTable([0x00000080, 0xFFFFFFFF]) + for i, (j, x) in enumerate([(j,x) for j in range(8) for x in reversed(range(8))]): + tile[i] = (bytes[j] >> x & 1) + else: + img.setColorTable([c.rgba() for c in palette]) + for i, (j, x) in enumerate([(j,x) for j in range(0, 16, 2) for x in reversed(range(8))]): + tile[i] = (bytes[j] >> x & 1) | ((bytes[j+1] >> x & 1) << 1) + if planes == 3: + for i, (j, x) in enumerate([(j,x) for j in range(16, 24, 1) for x in reversed(range(8))]): + tile[i] |= ((bytes[j] >> x & 1) << 2) + elif planes >= 4: + for i, (j, x) in enumerate([(j,x) for j in range(16, 32, 2) for x in reversed(range(8))]): + tile[i] |= ((bytes[j] >> x & 1) << 2) | ((bytes[j+1] >> x & 1) << 3) + if planes == 8: + for i, (j, x) in enumerate([(j,x) for j in range(32, 48, 2) for x in reversed(range(8))]): + tile[i] |= ((bytes[j] >> x & 1) << 4) | ((bytes[j+1] >> x & 1) << 5) \ + | ((bytes[j+16] >> x & 1) << 6) | ((bytes[j+17] >> x & 1) << 7) + imgbits[:64] = tile + return QPixmap.fromImage(img) + +def create_tile_old(bytes, palette): + ''' + Creates a QPixmap of a SNES tile. DO NOT USE OUTSIDE OF QApplication CONTEXT + ''' + planes = len(bytes)//8 + tile = array('B', range(64)) + img = QImage(8, 8, QImage.Format_Indexed8) + imgbits = img.bits() + imgbits.setsize(img.byteCount()) + if planes == 0: + raise ValueError("Empty bytes passed") + if planes == 1: + img.setColorTable([0x00000080, 0xFFFFFFFF]) + t_ptr = 0 + for j, x in [(j,x) for j in range(8) for x in reversed(range(8))]: + tile[t_ptr] = (bytes[j] >> x & 1) + t_ptr += 1 + else: + img.setColorTable([c.rgba() for c in palette]) + t_ptr = 0 + for j, x in [(j,x) for j in range(0, 16, 2) for x in reversed(range(8))]: + tile[t_ptr] = (bytes[j] >> x & 1) | ((bytes[j+1] >> x & 1) << 1) + t_ptr += 1 + t_ptr = 0 + if planes == 3: + for j, x in [(j,x) for j in range(16, 24, 1) for x in reversed(range(8))]: + tile[t_ptr] |= ((bytes[j] >> x & 1) << 2) + t_ptr += 1 + elif planes >= 4: + for j, x in [(j,x) for j in range(16, 32, 2) for x in reversed(range(8))]: + tile[t_ptr] |= ((bytes[j] >> x & 1) << 2) | ((bytes[j+1] >> x & 1) << 3) + t_ptr += 1 + if planes == 8: + t_ptr = 0 + for j, x in [(j,x) for j in range(32, 48, 2) for x in reversed(range(8))]: + tile[t_ptr] |= ((bytes[j] >> x & 1) << 4) | ((bytes[j+1] >> x & 1) << 5) \ + | ((bytes[j+16] >> x & 1) << 6) | ((bytes[j+17] >> x & 1) << 7) + t_ptr += 1 + imgbits[:64] = tile + return QPixmap.fromImage(img) + +def create_tritile(bytes): + img = QImage(16, 12, QImage.Format_Indexed8) + imgbits = img.bits() + imgbits.setsize(img.byteCount()) + img.setColorTable([0xFF000080, 0xFFFFFFFF]) + tile = array('B', range(192)) + for p, row, b in [(p,j,b) for p in range(2) for j in range(12) for b in reversed(range(8))]: + tile[(7-b) + (row*16) + (p*8)] = (bytes[row + (p*12)] >> b & 1) + imgbits[:192] = tile + return QPixmap.fromImage(img) + +def create_quadtile(bytes, ltr=False): + img = QImage(16, 16, QImage.Format_ARGB32_Premultiplied) + img.fill(QColor(0,0,0,0)) + painter = QtGui.QPainter(img) + painter.drawPixmap(0, 0, create_tile(bytes[0:8])) + painter.drawPixmap(8, 8, create_tile(bytes[24:32])) + if ltr: + painter.drawPixmap(8, 0, create_tile(bytes[8:16])) + painter.drawPixmap(0, 8, create_tile(bytes[16:24])) + else: + painter.drawPixmap(0, 8, create_tile(bytes[8:16])) + painter.drawPixmap(8, 0, create_tile(bytes[16:24])) + del painter + return QPixmap.fromImage(img)