Refactor a bunch of stuff
This commit is contained in:
parent
e87942c731
commit
1533e16c4f
254
ff5reader.py
254
ff5reader.py
|
@ -23,6 +23,7 @@ from itertools import chain
|
||||||
from array import array
|
from array import array
|
||||||
import time
|
import time
|
||||||
import functools
|
import functools
|
||||||
|
from typing import Iterable
|
||||||
|
|
||||||
from includes.helpers import *
|
from includes.helpers import *
|
||||||
from includes.qthelpers import *
|
from includes.qthelpers import *
|
||||||
|
@ -34,12 +35,13 @@ from includes.snestile import (
|
||||||
Canvas, Canvas_Indexed
|
Canvas, Canvas_Indexed
|
||||||
)
|
)
|
||||||
from includes.snes import *
|
from includes.snes import *
|
||||||
import includes.const as const
|
import includes.ff5.const as const
|
||||||
|
from includes.ff5.strings import StringBlock, RPGe_Dialogue_Width
|
||||||
|
from includes.ff5.strings import Strings as FFVStrings
|
||||||
import includes.ff4 as ff4
|
import includes.ff4 as ff4
|
||||||
|
import includes.ff5 as ff5
|
||||||
import includes.ff6 as ff6
|
import includes.ff6 as ff6
|
||||||
|
|
||||||
filename_en = 'Final Fantasy V (Japan) [En by RPGe v1.1].sfc'
|
|
||||||
filename_jp = 'Final Fantasy V (Japan).sfc'
|
|
||||||
filename_jp_ff4 = 'Final Fantasy IV (Japan) (Rev A).sfc'
|
filename_jp_ff4 = 'Final Fantasy IV (Japan) (Rev A).sfc'
|
||||||
filename_jp_ff6 = 'Final Fantasy VI (Japan).sfc'
|
filename_jp_ff6 = 'Final Fantasy VI (Japan).sfc'
|
||||||
|
|
||||||
|
@ -53,17 +55,11 @@ class FF5Reader(QMainWindow):
|
||||||
QMainWindow.__init__(self, None)
|
QMainWindow.__init__(self, None)
|
||||||
perfcount()
|
perfcount()
|
||||||
print('Reading ROMs')
|
print('Reading ROMs')
|
||||||
with open(filename_en, 'rb') as file:
|
ROM_en = ff5.files.ROM_RPGe
|
||||||
ROM_en = file.read()
|
ROM_jp = ff5.files.ROM_SNES
|
||||||
print(len(ROM_en), filename_en)
|
ROM_FF4jp = load_raw(filename_jp_ff4)
|
||||||
with open(filename_jp, 'rb') as file:
|
ROM_FF6jp = load_raw(filename_jp_ff6)
|
||||||
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)
|
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)
|
print(len(ROM_FF6jp), filename_jp_ff6)
|
||||||
perfcount()
|
perfcount()
|
||||||
|
|
||||||
|
@ -75,61 +71,14 @@ class FF5Reader(QMainWindow):
|
||||||
'glyphs_jp_l': generate_glyphs_large(ROM_jp, 0x03E800),
|
'glyphs_jp_l': generate_glyphs_large(ROM_jp, 0x03E800),
|
||||||
'glyphs_kanji': generate_glyphs_large(ROM_jp, 0x1BD000, 0x1AA), # Kanji are unchanged in EN version
|
'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, ROM_en, ROM_jp, **self.glyph_sprites)
|
make_string_img_list = functools.partial(_make_string_img_list, **self.glyph_sprites)
|
||||||
perfcount()
|
perfcount()
|
||||||
|
|
||||||
stringlist_headers = ['Address', 'ID', 'Name']
|
imglist_headers = ['ID', 'EN Pointer', 'EN Address', 'EN String', 'EN Img', 'JP Pointer', 'JP Address', 'JP String', 'JP Img']
|
||||||
imglist_headers = stringlist_headers + ['Img', 'Name JP', 'Img JP']
|
|
||||||
|
|
||||||
print('Generating Strings')
|
print('Generating String Images')
|
||||||
zone_names = make_string_img_list(0x107000, 2, 0x100, start_str=0x270000, start_jp_str=0x107200, indirect=True, large=True)
|
string_images = {k: make_string_img_list(*FFVStrings.blocks_SNES_RPGe[k], large=config.get('dialog')) for k,config in FFVStrings.config.items()}
|
||||||
menu_strings = make_string_img_list(0xF987, 2, 139, start_str=0x270000, start_jp_str=0x0000, indirect=True)
|
|
||||||
items = make_string_img_list(0x111380, 9, 256)
|
|
||||||
magics = make_string_img_list(0x111C80, 6, 87)
|
|
||||||
more_magics = make_string_img_list(0x111E8A, 9, 73)
|
|
||||||
enemy_names = make_string_img_list(0x200050, 10, 0x180, 0x105C00, 8)
|
|
||||||
character_names = make_string_img_list(0x115500, 6, 5)
|
|
||||||
job_names = make_string_img_list(0x115600, 8, 22)
|
|
||||||
ability_names = make_string_img_list(0x116200, 8, 33)
|
|
||||||
battle_commands = make_string_img_list(0x201150, 7, 0x60, 0x115800, 5)
|
|
||||||
perfcount()
|
perfcount()
|
||||||
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)
|
|
||||||
perfcount()
|
|
||||||
|
|
||||||
def split_tilesets(data):
|
|
||||||
tilesets = [(data & 0x00003F),
|
|
||||||
(data & 0x000FC0) >> 6,
|
|
||||||
(data & 0x03F000) >> 12,
|
|
||||||
(data & 0xFC0000) >> 18]
|
|
||||||
return ' '.join([hex(i,2) for i in tilesets])
|
|
||||||
def split_blockmaps(data):
|
|
||||||
blockmaps = [(data & 0x000003FF) - 1,
|
|
||||||
((data & 0x000FFC00) >> 10) - 1,
|
|
||||||
((data & 0x3FF00000) >> 20) - 1]
|
|
||||||
return ' '.join([hex(i,3) for i in blockmaps])
|
|
||||||
|
|
||||||
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 - 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
|
|
||||||
('Blockset', 1, None), # 08
|
|
||||||
('Tilesets', 3, split_tilesets), # 09 0A 0B
|
|
||||||
('Blockmaps', 4, split_blockmaps), # 0C 0D 0E 0F
|
|
||||||
(hex(16), 1, None), # 10
|
|
||||||
(hex(17), 1, None), # 11
|
|
||||||
(hex(18), 1, None), # 12
|
|
||||||
(hex(19), 1, None), # 13
|
|
||||||
(hex(20), 1, None), # 14
|
|
||||||
(hex(21), 1, None), # 15
|
|
||||||
('Palette', 1, None), # 16
|
|
||||||
(hex(23), 1, None), # 17
|
|
||||||
(hex(24), 1, None), # 18
|
|
||||||
('Music', 1, const.BGM_Tracks)] # 19
|
|
||||||
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)]
|
|
||||||
|
|
||||||
battle_bg_structure = [('Tileset', 1, None),
|
battle_bg_structure = [('Tileset', 1, None),
|
||||||
('Palette 1', 1, None),
|
('Palette 1', 1, None),
|
||||||
|
@ -170,7 +119,7 @@ class FF5Reader(QMainWindow):
|
||||||
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) + string_images['enemy_names'][i][3:5])
|
||||||
|
|
||||||
perfcount()
|
perfcount()
|
||||||
print('Generating map tiles')
|
print('Generating map tiles')
|
||||||
|
@ -286,23 +235,22 @@ class FF5Reader(QMainWindow):
|
||||||
self.ff6widget.addTab(make_px_table(self.portraits_ff6, cols=19, scale=2), 'Character Portraits')
|
self.ff6widget.addTab(make_px_table(self.portraits_ff6, cols=19, scale=2), 'Character Portraits')
|
||||||
|
|
||||||
|
|
||||||
structs_tab.addTab(make_table(zone_headers, zone_data, True), 'Zones')
|
structs_tab.addTab(make_table(ff5.ZoneData.zone_headers, ff5.ZoneData.get_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(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')
|
||||||
|
|
||||||
strings_tab.addTab(make_table(imglist_headers, menu_strings, row_labels=False), 'Menu Strings')
|
strings_tab.addTab(make_table(imglist_headers, string_images['menu_strings'], row_labels=False), 'Menu Strings')
|
||||||
strings_tab.addTab(make_table(imglist_headers, items, row_labels=False), 'Items')
|
strings_tab.addTab(make_table(imglist_headers, string_images['items'], row_labels=False), 'Items')
|
||||||
strings_tab.addTab(make_table(imglist_headers, magics+more_magics, row_labels=False), 'Magics')
|
strings_tab.addTab(make_table(imglist_headers, string_images['magics']+string_images['magics2'], row_labels=False), 'Magics')
|
||||||
#strings_tab.addTab(make_table(imglist_headers, more_magics, row_labels=False), 'More Magics')
|
strings_tab.addTab(make_table(imglist_headers, string_images['enemy_names'], row_labels=False), 'Enemy Names')
|
||||||
strings_tab.addTab(make_table(imglist_headers, enemy_names, row_labels=False), 'Enemy Names')
|
strings_tab.addTab(make_table(imglist_headers, string_images['character_names'], row_labels=False), 'Character Names')
|
||||||
strings_tab.addTab(make_table(imglist_headers, character_names, row_labels=False), 'Character Names')
|
strings_tab.addTab(make_table(imglist_headers, string_images['job_names'], row_labels=False), 'Job Names')
|
||||||
strings_tab.addTab(make_table(imglist_headers, job_names, row_labels=False), 'Job Names')
|
strings_tab.addTab(make_table(imglist_headers, string_images['ability_names'], row_labels=False), 'Ability Names')
|
||||||
strings_tab.addTab(make_table(imglist_headers, ability_names, row_labels=False), 'Ability Names')
|
strings_tab.addTab(make_table(imglist_headers, string_images['battle_commands'], row_labels=False), 'Battle Commands')
|
||||||
strings_tab.addTab(make_table(imglist_headers, battle_commands, row_labels=False), 'Battle Commands')
|
strings_tab.addTab(make_table(imglist_headers, string_images['zone_names'], True, scale=1), 'Zone Names')
|
||||||
strings_tab.addTab(make_table(imglist_headers, zone_names, True, scale=1), 'Zone Names')
|
strings_tab.addTab(make_table(imglist_headers, string_images['dialogue'], scale=1), 'Dialogue')
|
||||||
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()
|
||||||
|
@ -337,19 +285,17 @@ class FF5Reader(QMainWindow):
|
||||||
The painting logic here needs to be moved into includes.snestile at some point.
|
The painting logic here needs to be moved into includes.snestile at some point.
|
||||||
Once that is done, these functions will be moved to includes.snes which should not have qt dependencies.
|
Once that is done, these functions will be moved to includes.snes which should not have qt dependencies.
|
||||||
'''
|
'''
|
||||||
def make_string_img_small(bytestring, glyphs, jp=False):
|
def make_string_img_small(bytestring: Iterable[int], glyphs, handle_dakuten=False):
|
||||||
'''
|
'''
|
||||||
JP version is not as fancy as this with dakuten, it just puts them on the row above and clips.
|
JP version is not as fancy as this with dakuten, it just puts them on the row above and clips.
|
||||||
'''
|
'''
|
||||||
if len(bytestring) < 1:
|
if len(bytestring) < 1:
|
||||||
raise ValueError('Empty bytestring was passed')
|
return None
|
||||||
string = ''
|
|
||||||
img = QImage(len(bytestring)*8, 10, QImage.Format_RGB16)
|
img = QImage(len(bytestring)*8, 10, QImage.Format_RGB16)
|
||||||
img.fill(bg_color)
|
img.fill(bg_color)
|
||||||
painter = QtGui.QPainter(img)
|
painter = QtGui.QPainter(img)
|
||||||
if jp:
|
if handle_dakuten:
|
||||||
for x, j in enumerate(bytestring):
|
for x, j in enumerate(bytestring):
|
||||||
string = string + const.Glyphs_JP2[j]
|
|
||||||
if 0x20 <= j < 0x52:
|
if 0x20 <= j < 0x52:
|
||||||
if j > 0x48:
|
if j > 0x48:
|
||||||
painter.drawPixmap(x*8, 2, glyphs[j+0x17])
|
painter.drawPixmap(x*8, 2, glyphs[j+0x17])
|
||||||
|
@ -361,33 +307,20 @@ def make_string_img_small(bytestring, glyphs, jp=False):
|
||||||
painter.drawPixmap(x*8, 2, glyphs[j])
|
painter.drawPixmap(x*8, 2, glyphs[j])
|
||||||
else:
|
else:
|
||||||
for x, j in enumerate(bytestring):
|
for x, j in enumerate(bytestring):
|
||||||
string = string + const.Glyphs[j]
|
|
||||||
painter.drawPixmap(x*8, 1, glyphs[j])
|
painter.drawPixmap(x*8, 1, glyphs[j])
|
||||||
del painter
|
del painter
|
||||||
return string, QPixmap.fromImage(img)
|
return QPixmap.fromImage(img)
|
||||||
|
|
||||||
|
|
||||||
def make_string_img_large(bytestring, glyphs, glyphs_kanji, macros=None, jp=False):
|
def make_string_img_large(glyph_ids: Iterable[int], glyphs, glyphs_kanji=None):
|
||||||
'''
|
'''
|
||||||
This is how we decipher dialogue data, which has multiple lines, macro expansions and kanji.
|
This is how we decipher dialogue data, which has multiple lines, macro expansions and kanji.
|
||||||
English characters have varying widths. In the japanese version, everything is fullwidth (16px)
|
English characters have varying widths. In the japanese version, everything is fullwidth (16px)
|
||||||
Kanji aren't used in English dialogue but the cost is likely the same in checking either way.
|
Kanji aren't used in English dialogue but the cost is likely the same in checking either way.
|
||||||
'''
|
'''
|
||||||
if len(bytestring) < 1:
|
if len(glyph_ids) < 1:
|
||||||
raise ValueError('Empty bytestring was passed')
|
return None
|
||||||
|
|
||||||
newstring = []
|
|
||||||
bytes = iter(bytestring)
|
|
||||||
for b in bytes:
|
|
||||||
if b in const.DoubleChars:
|
|
||||||
b2 = next(bytes)
|
|
||||||
newstring.append((b<<8) + b2)
|
|
||||||
elif macros and b in macros:
|
|
||||||
newstring.extend(macros[b])
|
|
||||||
else:
|
|
||||||
newstring.append(b)
|
|
||||||
|
|
||||||
string = ''
|
|
||||||
# Because the length of the input has little bearing on the size of the image thanks to linebreaks and macros, we overprovision then clip away.
|
# Because the length of the input has little bearing on the size of the image thanks to linebreaks and macros, we overprovision then clip away.
|
||||||
max_width = 256 # This seems to check out, but the EN dialogue has linebreaks virtually everywhere anyway
|
max_width = 256 # This seems to check out, but the EN dialogue has linebreaks virtually everywhere anyway
|
||||||
max_height = 1024 # I've seen up to 58 rows in EN, 36 in JP. Stay safe.
|
max_height = 1024 # I've seen up to 58 rows in EN, 36 in JP. Stay safe.
|
||||||
|
@ -396,96 +329,63 @@ def make_string_img_large(bytestring, glyphs, glyphs_kanji, macros=None, jp=Fals
|
||||||
painter = QtGui.QPainter(img)
|
painter = QtGui.QPainter(img)
|
||||||
|
|
||||||
x = xmax = y = 0
|
x = xmax = y = 0
|
||||||
for j in newstring:
|
if glyphs_kanji is not None: # jp
|
||||||
if x >= max_width: # Wrap on long line
|
for j in glyph_ids:
|
||||||
string += '[wr]\n'
|
if x >= max_width: # Wrap on long line
|
||||||
xmax = max_width # Can't go higher than this anyway
|
xmax = max_width # Can't go higher than this anyway
|
||||||
x = 0
|
x = 0
|
||||||
y += 16
|
y += 16
|
||||||
if j == 0x01: # Line break
|
|
||||||
string += '[br]\n'
|
if j == 0x01: # Line break
|
||||||
xmax = x if x > xmax else xmax
|
xmax = x if x > xmax else xmax
|
||||||
x = 0
|
x = 0
|
||||||
y += 16
|
y += 16
|
||||||
elif 0x1E00 <= j < 0x1FAA: # Kanji live in this range
|
elif 0x1E00 <= j < 0x1FAA: # Kanji live in this range
|
||||||
string += const.Glyphs_Kanji[j-0x1E00]
|
painter.drawPixmap(x, y+2, glyphs_kanji[j-0x1E00])
|
||||||
painter.drawPixmap(x, y+2, glyphs_kanji[j-0x1E00])
|
x += 16
|
||||||
x += 16
|
elif 0x13 <= j <= 0xFF: # Everything remaining outside this is a control char
|
||||||
elif j < 0x13 or j > 0xFF: # Everything remaining outside this is a control char
|
|
||||||
string += '[0x{:02X}]'.format(j)
|
|
||||||
else:
|
|
||||||
if jp:
|
|
||||||
string += const.Glyphs_JP_large[j]
|
|
||||||
painter.drawPixmap(x, y+2, glyphs[j])
|
painter.drawPixmap(x, y+2, glyphs[j])
|
||||||
x += 16
|
x += 16
|
||||||
else:
|
else: # en
|
||||||
string += const.Glyphs[j]
|
for j in glyph_ids:
|
||||||
|
if x >= max_width: # Wrap on long line
|
||||||
|
xmax = max_width # Can't go higher than this anyway
|
||||||
|
x = 0
|
||||||
|
y += 16
|
||||||
|
|
||||||
|
if j == 0x01: # Line break
|
||||||
|
xmax = x if x > xmax else xmax
|
||||||
|
x = 0
|
||||||
|
y += 16
|
||||||
|
elif 0x13 <= j <= 0xFF: # Everything outside this is a control char
|
||||||
painter.drawPixmap(x, y+4, glyphs[j])
|
painter.drawPixmap(x, y+4, glyphs[j])
|
||||||
x += const.Dialogue_Width[j]
|
x += RPGe_Dialogue_Width[j]
|
||||||
|
|
||||||
del painter
|
del painter
|
||||||
xmax = x if x > xmax else xmax
|
xmax = x if x > xmax else xmax
|
||||||
return string, QPixmap.fromImage(img.copy(0, 0, xmax, y+16))
|
return QPixmap.fromImage(img.copy(0, 0, xmax, y+16))
|
||||||
|
|
||||||
|
|
||||||
def _make_string_img_list(rom_e, rom_j, start, length, num,
|
def _make_string_img_list(jp: StringBlock, en: StringBlock, large=False,
|
||||||
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,
|
glyphs_en_s=None, glyphs_en_l=None,
|
||||||
glyphs_jp_s=None, glyphs_jp_l=None, glyphs_kanji=None):
|
glyphs_jp_s=None, glyphs_jp_l=None, glyphs_kanji=None):
|
||||||
start_jp = start if start_jp is None else start_jp
|
num = len(en)
|
||||||
len_jp = length if len_jp is None else len_jp
|
print(len(en), len(jp))
|
||||||
start_str = start if start_str is None else start_str
|
|
||||||
start_jp_str = start_str if start_jp_str is None else start_jp_str
|
|
||||||
stringlist = []
|
stringlist = []
|
||||||
id_digits = hex_length(num-1)
|
id_digits = hex_length(num-1)
|
||||||
|
|
||||||
if indirect:
|
for id in range(num):
|
||||||
for id in range(num):
|
str_en = en.decoded[id]
|
||||||
en = start + (id*length)
|
str_jp = jp.decoded[id]
|
||||||
jp = start_jp + (id*len_jp)
|
if large:
|
||||||
en_start = int.from_bytes(rom_e[en:en+length],'little') + start_str
|
img_en = make_string_img_large(en.glyphs[id], glyphs_en_l)
|
||||||
en_end = int.from_bytes(rom_e[en+length:en+(length*2)],'little') + start_str
|
img_jp = make_string_img_large(jp.glyphs[id], glyphs_jp_l, glyphs_kanji)
|
||||||
if en_start >= 0xC00000: # SNES memory space has the ROM starting at 0xC00000 in HiROM mode.
|
else:
|
||||||
en_start -= 0xC00000
|
img_en = make_string_img_small(en.glyphs[id], glyphs_en_s)
|
||||||
en_end -= 0xC00000
|
img_jp = make_string_img_small(jp.glyphs[id], glyphs_jp_s, handle_dakuten=True)
|
||||||
jp_start = int.from_bytes(rom_j[jp:jp+len_jp],'little') + start_jp_str
|
# ['ID', 'EN Pointer', 'EN Address', 'EN String', 'EN Img', 'JP Pointer', 'JP Address', 'JP String', 'JP Img']
|
||||||
jp_end = int.from_bytes(rom_j[jp+len_jp:jp+(len_jp*2)],'little') + start_jp_str
|
stringlist.append([hex(id, id_digits), hex(en.pointer_address[id].start, 6), hex(en.string_address[id].start, 6), str_en, img_en,
|
||||||
if jp_start >= 0xC00000: # SNES memory space has the ROM starting at 0xC00000 in HiROM mode.
|
hex(jp.pointer_address[id].start, 6), hex(jp.string_address[id].start, 6), str_jp, img_jp])
|
||||||
jp_start -= 0xC00000
|
|
||||||
jp_end -= 0xC00000
|
|
||||||
if (en_end == start_str) or (jp_end == start_jp_str):
|
|
||||||
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_e[en_start:en_end], glyphs_en_l, glyphs_kanji, macros_en)
|
|
||||||
else:
|
|
||||||
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_j[jp_start:jp_end], glyphs_jp_l, glyphs_kanji, macros_jp, jp=True)
|
|
||||||
else:
|
|
||||||
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
|
|
||||||
stringlist.append([
|
|
||||||
hex(en, 6), hex(id, id_digits),
|
|
||||||
str_en, img_en, str_jp, img_jp, hex(jp_start, 6)
|
|
||||||
])
|
|
||||||
else:
|
|
||||||
for id in range(num):
|
|
||||||
j1 = start + (id*length)
|
|
||||||
j2 = start_jp + (id*len_jp)
|
|
||||||
if large:
|
|
||||||
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_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
|
return stringlist
|
||||||
|
|
||||||
last_perfcount = None
|
last_perfcount = None
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
'''
|
|
||||||
This file is part of ff5reader.
|
|
||||||
|
|
||||||
ff5reader is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
ff5reader is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with ff5reader. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
'''
|
|
||||||
small_palette = [0xFF000000, 0x00000080, 0xFF808080, 0xFFFFFFFF]
|
|
||||||
dialogue_palette = [0xFF000080, 0xFFFFFFFF]
|
|
||||||
mono_palette = [0xFF000000, 0xFFFFFFFF]
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
'「', '」', '.', '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[0xD2] = '。'
|
|
||||||
Glyphs_JP[0xE3] = '[洋剣]'
|
|
||||||
Glyphs_JP[0xE7:0xF0] = ['[刂]', '[槍]', '[鉞]', '[刀]', '[棒]', '[杖]', '[弓]', '♪', '[鞭]']
|
|
||||||
Glyphs_JP2 = list(Glyphs_JP) # Japanese glyphs using the dakuten encoding
|
|
||||||
Glyphs_JP2[0x20:0x53] = [
|
|
||||||
'バ','ば','ビ','び', 'ブ','ぶ','ベ','べ', 'ボ','ぼ','ガ','が', 'ギ','ぎ','グ','ぐ', # 0x20
|
|
||||||
'ゲ','げ','ゴ','ご', 'ザ','ざ','ジ','じ', 'ズ','ず','ゼ','ぜ', 'ゾ','ぞ','ダ','だ', # 0x30
|
|
||||||
'ヂ','ぢ','ヅ','づ', 'デ','で','ド','ど', 'ヴ', # 0x40-0x49
|
|
||||||
'パ','ぱ','ピ','ぴ', 'プ','ぷ','ペ','ぺ', 'ポ','ぽ'] # 0x49-0x53
|
|
||||||
Glyphs_JP_large = list(Glyphs_JP2) # Large glyphs are subtly different again
|
|
||||||
Glyphs_JP_large[0xC7] = '⋯'
|
|
||||||
Glyphs_JP_large[0xE0:0xEB] = ['←','→','+','、', '◯', '『', 'F', '°C', '・', '(', ')']
|
|
||||||
Glyphs_JP_large[0xFF] = ' '
|
|
||||||
|
|
||||||
Glyphs_Kanji = (
|
|
||||||
'王', '行', '力', '様', '飛', '世', '界', '封', # 0x000
|
|
||||||
'入', '城', '竜', '士', '船', '印', '海', '父', # 0x008
|
|
||||||
'人', '見', '魔', '物', '大', '者', '本', '何', # 0x010
|
|
||||||
'戦', '出', '気', '手', '言', '石', '守', '辺', # 0x018
|
|
||||||
'無', '心', '間', '前', '風', '帰', '私', '生', # 0x020
|
|
||||||
'年', '武', '器', '兵', '火', '使', '中', '急', # 0x028
|
|
||||||
'時', '森', '来', '砂', '動', '女', '待', '臣', # 0x030
|
|
||||||
'地', '助', '当', '今', '上', '悪', '泉', '騎', # 0x038
|
|
||||||
|
|
||||||
'思', '持', '変', '水', '塔', '草', '仲', '復', # 0x040
|
|
||||||
'目', '作', '分', '知', '機', '一', '姫', '最', # 0x048
|
|
||||||
'聞', '械', '神', '流', '乗', '取', '町', '殿', # 0x050
|
|
||||||
'事', '空', '勇', '村', '早', '子', '格', '納', # 0x058
|
|
||||||
'伝', '消', '書', '長', '話', '合', '所', '場', # 0x060
|
|
||||||
'脱', '北', '後', '全', '忍', '獣', '詩', '吟', # 0x068
|
|
||||||
'落', '自', '階', '説', '残', '親', '活', '休', # 0x070
|
|
||||||
'姉', '破', '土', '度', '記', '発', '赤', '侍', # 0x078
|
|
||||||
|
|
||||||
'死', '国', '旅', '完', '小', '林', '古', '図', # 0x080
|
|
||||||
'礼', '商', '島', '邪', '部', '狩', '精', '姿', # 0x088
|
|
||||||
'防', '向', '先', '解', '板', '下', '台', '賢', # 0x090
|
|
||||||
'対', '木', '成', '命', '配', '法', '飲', '回', # 0x098
|
|
||||||
'願', '門', '東', '開', '貸', '増', '危', '舘', # 0x0A0
|
|
||||||
'道', '身', '老', '西', '近', '層', '第', '青', # 0x0A8
|
|
||||||
'光', '読', '外', '理', '強', '同', '谷', '負', # 0x0B0
|
|
||||||
'意', '学', '攻', '屋', '体', '運', '河', '聖', # 0x0B8
|
|
||||||
|
|
||||||
'必', '南', '方', '黒', '絶', '食', '艇', '山', # 0x0C0
|
|
||||||
'穴', '名', '受', '暁', '傷', '少', '鼻', '倉', # 0x0C8
|
|
||||||
'然', '的', '男', '用', '酒', '安', '現', '代', # 0x0D0
|
|
||||||
'立', '具', '育', '続', '通', '会', '庫', '飼', # 0x0D8
|
|
||||||
'日', '窟', '砲', '広', '化', '博', '以', '兄', # 0x0E0
|
|
||||||
'室', '洞', '別', '番', '昔', '住', '吸', '去', # 0x0E8
|
|
||||||
'宝', '毒', '売', '好', '枝', '踊', '月', '巣', # 0x0F0
|
|
||||||
'底', '明', '感', '宿', '召', '喚', '決', '形', # 0x0F8
|
|
||||||
|
|
||||||
'団', '異', '初', '険', '家', '息', '連', '集', # 0x100
|
|
||||||
'墓', '著', '跡', '遺', '内', '悲', '教', '返', # 0x108
|
|
||||||
'頭', '再', '高', '借', '登', '走', '過', '敵', # 0x110
|
|
||||||
'鏡', '愛', '魚', '燃', '幅', '音', '血', '滝', # 0x118
|
|
||||||
'次', '閉', '求', '声', '箱', '軍', '泣', '港', # 0x120
|
|
||||||
'冊', '秒', '憶', '潜', '爆', '恋', '石', '左', # 0x128
|
|
||||||
'役', '放', '衛', '母', '建', '角', '信', '師', # 0x130
|
|
||||||
'元', '失', '字', '歩', '遊', '頂', '震', '収', # 0x138
|
|
||||||
|
|
||||||
'追', '暗', '橋', '起', '約', '束', '文', '反', # 0x140
|
|
||||||
'絵', '能', '歌', '弱', '境', '波', '針', '千', # 0x148
|
|
||||||
'炎', '望', '雨', '金', '希', '花', '不', '白', # 0x150
|
|
||||||
'剣', '陸', '勝', '烏', '笛', '究', '三', '探', # 0x158
|
|
||||||
'夜', '病', '半', '美', '敗', '友', '研', '予', # 0x160
|
|
||||||
'支', '々', '除', '数', '店', '翼', '充', '填', # 0x168
|
|
||||||
'薬', '弟', '経', '験', '値', '闘', '得', '闇', # 0x170
|
|
||||||
'混', '乱', '点', '速', '閃', '射', '斬', '鉄', # 0x178
|
|
||||||
|
|
||||||
'宣', '告', '電', '磁', '圧', '昇', '重', '倍', # 0x180
|
|
||||||
'妖', '刀', '拡', '散', '影', '御', '正', '漠', # 0x188
|
|
||||||
'特', '胸', '永', '遠', '議', '浮', '眠', '逃', # 0x190
|
|
||||||
'滅', '囗', '極', '湖', '利', '周', '緑', '切', # 0x198
|
|
||||||
'紙', '胸', '平', '和', '宇', '宙', '則', '孤', # 0x1A0
|
|
||||||
'溝', ' ') # 0x1A8
|
|
||||||
|
|
||||||
Dialogue_Macros_EN = {
|
|
||||||
0x02: [0x61, 0x7A, 0x8B, 0x8D, 0x93], # expands to Bartz (or whatever his name is)
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialogue_Macros_JP = {
|
|
||||||
# Is 0x00 a wait for input marker?
|
|
||||||
# 0x01 is linebreak
|
|
||||||
0x02: [0x20, 0xBC, 0x82], # 0x02 expands to Bartz's name バッツ. Used for his dialogue in EN, only used for other chars in JP.
|
|
||||||
0x03: [0x6E, 0xA8, 0x78, 0x7E, 0xAA], # 0x03 is クリスタル
|
|
||||||
0x04: [0x7E, 0x8C, 0x6E, 0xC5, 0xB8], # expands to タイクーン
|
|
||||||
0x06: [0x37, 0xBF], # expands to じゃ
|
|
||||||
0x07: [0x8D, 0xAB], # expands to いる
|
|
||||||
0x08: [0xFF, 0xFF, 0xFF, 0xFF], # 4 spaces
|
|
||||||
0x09: [0xFF, 0xFF, 0xFF], # 3 spaces
|
|
||||||
0x0A: [0xFF, 0xFF], # 2 spaces
|
|
||||||
0x0B: [0x1E12, 0x1E13], # expands to 魔物
|
|
||||||
# 0x0C appears to be a pause in delivery - affects previous char
|
|
||||||
0x0D: [0x1E24, 0x9B, 0x1E52, 0x1E57], # expands to 風の神殿
|
|
||||||
0x0E: [0x1E04, 0x1E0A], # expands to 飛竜
|
|
||||||
# 0x0F - unknown (invisible control char)
|
|
||||||
# 0x10 is a gil substitution
|
|
||||||
# 0x11 and 0x12 appear to be item (obtained) substitutions
|
|
||||||
0x13: [0x1E07, 0x1E0D], # expands to 封印
|
|
||||||
0x14: [0x76, 0x46, 0xD0], # Cid speaking - シド「
|
|
||||||
0x15: [0x9E, 0x46, 0xD0], # Mid speaking - ミド「
|
|
||||||
0x16: [0x1E05, 0x1E06], # expands to 世界
|
|
||||||
# 0x17 uses the next byte for pause duration (seconds?)
|
|
||||||
0x18: [0x8E, 0x6E, 0x78, 0x44, 0x78], # expands to エクスデス
|
|
||||||
0x19: [0xAC, 0x92, 0xD0], # Lenna speaking - レナ「
|
|
||||||
0x1A: [0x2A, 0xA6, 0x64, 0xD0], # Galuf speaking - ガラフ「
|
|
||||||
0x1B: [0x64, 0xC4, 0xA8, 0x78, 0xD0], # Faris speaking - ファリス「
|
|
||||||
0x1C: [0x6E, 0xAA, 0xAA, 0xD0], # Krile/Kara speaking - クルル「
|
|
||||||
0x1D: [0x91, 0x37, 0x8D, 0x81, 0xBF, 0xB9], # expands to おじいちゃん
|
|
||||||
# 0x1E-0x1F form kanji with the next byte
|
|
||||||
# 0x20-0xCC are standard character set
|
|
||||||
0xCD: [0xC9, 0xC9], # % (0xCD) to !!
|
|
||||||
# 0xCE is /
|
|
||||||
0xCF: [0xBD, 0x85], # : (0xCF) appears to expand to って
|
|
||||||
# 0xD0-0xD4 are 「」。AB
|
|
||||||
0xD5: [0x1E1B, 0x95, 0x1E08, 0xAD], # expands to 手に入れ
|
|
||||||
# 0xD6, 0xD7, 0xD8 are YLR
|
|
||||||
0xD9: [0x93, 0x8D], # expands to ない
|
|
||||||
# 0xDA-0xDC are HMP
|
|
||||||
0xDD: [0xC7, 0xC7], # S (0xDD) to ……
|
|
||||||
0xDE: [0x3F, 0x8D, 0x37, 0xC3, 0x89, 0x25], # C (0xDE) to だいじょうぶ
|
|
||||||
0xDF: [0x61, 0xE3], # T (0xDF) to は、
|
|
||||||
0xE0: [0xB9, 0x3F], # expands to んだ
|
|
||||||
0xE1: [0x85, 0x8D], # expands to てい
|
|
||||||
0xE2: [0x77, 0x7F], # expands to した
|
|
||||||
# 0xE3 is 、
|
|
||||||
0xE4: [0x77, 0x85], # ◯ (0xE4) appears to expand to して
|
|
||||||
# 0xE5 is used for Bartz speaking in JP. This only appears as 『
|
|
||||||
0xE6: [0x91, 0x1E0F, 0x1E03], # F (0xE6) appears to expand to otousan (お父様)
|
|
||||||
0xE7: [0xC9, 0xCB], # °C (0xE7) to !? - yes this is the wrong order interrobang
|
|
||||||
0xE8: [0x45, 0x79], # ・ (0xE8) appears to expand to です
|
|
||||||
# 0xE9, 0xEA are ()
|
|
||||||
0xEB: [0x73, 0x9B], # expands to この
|
|
||||||
0xEC: [0x9B, 0x1E02], # expands to の力
|
|
||||||
0xED: [0x70, 0xAA, 0x2A, 0xC5], # expands to ケルガー
|
|
||||||
0xEE: [0x1E86, 0x1ED7, 0x1E87, 0x1E62, 0x1EA7], # expands to 古代図書舘 (ancient library?)
|
|
||||||
0xEF: [0x1E1C, 0xBD, 0x85], # expands to 言って
|
|
||||||
0xF0: [0x1E2B, 0x1E0B, 0xD0], # soldier speaking - 兵士「
|
|
||||||
0xF1: [0x6B, 0xA7], # expands to から
|
|
||||||
0xF2: [0x1E2C, 0x6A, 0x1E0C], # expands to 火カ船
|
|
||||||
0xF3: [0x1E0E, 0x3D, 0x6F], # expands to 海ぞく
|
|
||||||
0xF4: [0x8D, 0x37, 0xC3, 0x89], # expands to いじょう
|
|
||||||
0xF5: [0x2B, 0xE3], # expands to が、
|
|
||||||
0xF6: [0x7F, 0x81], # expands to たち
|
|
||||||
0xF7: [0x7F, 0x9B], # expands to たの
|
|
||||||
0xF8: [0x9D, 0x79], # expands to ます
|
|
||||||
0xF9: [0x6F, 0x3F, 0x75, 0x8D], # expands to ください
|
|
||||||
0xFA: [0x6B, 0xBD, 0x7F], # expands to かった
|
|
||||||
0xFB: [0x7F, 0xC9], # expands to た!
|
|
||||||
0xFC: [0x95, 0xE3], # expands to に、
|
|
||||||
0xFD: [0x8D, 0x93, 0x8D, 0x6B, 0xA7, 0x93, 0xB9, 0x3F], # expands to いないからなんだ
|
|
||||||
0xFE: [0x1F20, 0x1F38, 0x9B, 0x61, 0x35, 0x9D], # expands to 次元のはざま
|
|
||||||
# 0xFF is space
|
|
||||||
}
|
|
||||||
DoubleChars = set([0x17, 0x1E, 0x1F]) # 0x1E and 0x1F are kanji, 0x17 is a pause marker
|
|
||||||
|
|
||||||
Dialogue_Width = [4 for i in range(256)]
|
|
||||||
Dialogue_Width[0x50:0xB1] = [a+1 for a in [
|
|
||||||
5, 2, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, # 0x50
|
|
||||||
6, 6, 5, 6, 5, 5, 6, 6, 2, 6, 7, 5, 10,7, 6, 6, # 0x60
|
|
||||||
6, 6, 6, 6, 6, 6, 10,6, 6, 6, 6, 6, 5, 6, 6, 5, # 0x70
|
|
||||||
6, 6, 2, 5, 6, 2, 10,6, 6, 6, 6, 5, 5, 4, 6, 6, # 0x80
|
|
||||||
10,6, 6, 6, 5, 7, 6, 5, 5, 2, 5, 2, 2, 2, 3, 3, # 0x90
|
|
||||||
5, 2, 6, 2, 7, 8, 0, 0, 6, 9, 2, 5, 8, 7, 7, 8, # 0xA0
|
|
||||||
9
|
|
||||||
]]
|
|
||||||
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
BGM_Tracks_Safe = [t.replace('/', '-') for t in BGM_Tracks]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
npc_layer_count = 0x200
|
|
||||||
npc_layer_structure = [
|
|
||||||
("Dialogue/trigger ID", 1, None),
|
|
||||||
("0x01", 1, None),
|
|
||||||
("Sprite ID", 1, None),
|
|
||||||
("X", 1, None),
|
|
||||||
("Y", 1, None),
|
|
||||||
("Move Pattern", 1, None),
|
|
||||||
("Palette", 1, None)
|
|
||||||
]
|
|
||||||
npc_layer_headers = ["Ptr Address", "Layer", "Data Address"] + [x[0] for x in npc_layer_structure]
|
|
||||||
|
|
||||||
zone_count = 0x200
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
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
|
|
@ -0,0 +1,256 @@
|
||||||
|
|
||||||
|
\n
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
バ
|
||||||
|
ば
|
||||||
|
ビ
|
||||||
|
び
|
||||||
|
ブ
|
||||||
|
ぶ
|
||||||
|
ベ
|
||||||
|
べ
|
||||||
|
ボ
|
||||||
|
ぼ
|
||||||
|
ガ
|
||||||
|
が
|
||||||
|
ギ
|
||||||
|
ぎ
|
||||||
|
グ
|
||||||
|
ぐ
|
||||||
|
ゲ
|
||||||
|
げ
|
||||||
|
ゴ
|
||||||
|
ご
|
||||||
|
ザ
|
||||||
|
ざ
|
||||||
|
ジ
|
||||||
|
じ
|
||||||
|
ズ
|
||||||
|
ず
|
||||||
|
ゼ
|
||||||
|
ぜ
|
||||||
|
ゾ
|
||||||
|
ぞ
|
||||||
|
ダ
|
||||||
|
だ
|
||||||
|
ヂ
|
||||||
|
ぢ
|
||||||
|
ヅ
|
||||||
|
づ
|
||||||
|
デ
|
||||||
|
で
|
||||||
|
ド
|
||||||
|
ど
|
||||||
|
ヴ
|
||||||
|
パ
|
||||||
|
ぱ
|
||||||
|
ピ
|
||||||
|
ぴ
|
||||||
|
プ
|
||||||
|
ぷ
|
||||||
|
ペ
|
||||||
|
"
|
||||||
|
'
|
||||||
|
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
_m
|
||||||
|
_H
|
||||||
|
_P
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
E
|
||||||
|
F
|
||||||
|
G
|
||||||
|
H
|
||||||
|
I
|
||||||
|
J
|
||||||
|
K
|
||||||
|
L
|
||||||
|
M
|
||||||
|
N
|
||||||
|
O
|
||||||
|
P
|
||||||
|
Q
|
||||||
|
R
|
||||||
|
S
|
||||||
|
T
|
||||||
|
U
|
||||||
|
V
|
||||||
|
W
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
Z
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
i
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
m
|
||||||
|
n
|
||||||
|
o
|
||||||
|
p
|
||||||
|
q
|
||||||
|
r
|
||||||
|
s
|
||||||
|
t
|
||||||
|
u
|
||||||
|
v
|
||||||
|
w
|
||||||
|
x
|
||||||
|
y
|
||||||
|
z
|
||||||
|
il
|
||||||
|
it
|
||||||
|
|
||||||
|
li
|
||||||
|
ll
|
||||||
|
'
|
||||||
|
"
|
||||||
|
:
|
||||||
|
;
|
||||||
|
,
|
||||||
|
(
|
||||||
|
)
|
||||||
|
/
|
||||||
|
!
|
||||||
|
?
|
||||||
|
.
|
||||||
|
ti
|
||||||
|
fi
|
||||||
|
ラ
|
||||||
|
ら
|
||||||
|
pe
|
||||||
|
l
|
||||||
|
'
|
||||||
|
"
|
||||||
|
if
|
||||||
|
lt
|
||||||
|
tl
|
||||||
|
ir
|
||||||
|
tt
|
||||||
|
や
|
||||||
|
ユ
|
||||||
|
ゆ
|
||||||
|
ヨ
|
||||||
|
よ
|
||||||
|
ワ
|
||||||
|
わ
|
||||||
|
ン
|
||||||
|
ん
|
||||||
|
ヲ
|
||||||
|
を
|
||||||
|
ッ
|
||||||
|
っ
|
||||||
|
ャ
|
||||||
|
ゃ
|
||||||
|
ュ
|
||||||
|
ゅ
|
||||||
|
ョ
|
||||||
|
ょ
|
||||||
|
ァ
|
||||||
|
ー
|
||||||
|
ィ
|
||||||
|
⋯
|
||||||
|
ぅ
|
||||||
|
!
|
||||||
|
ェ
|
||||||
|
?
|
||||||
|
ォ
|
||||||
|
%
|
||||||
|
/
|
||||||
|
:
|
||||||
|
「
|
||||||
|
」
|
||||||
|
。
|
||||||
|
A
|
||||||
|
B
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
L
|
||||||
|
R
|
||||||
|
E
|
||||||
|
H
|
||||||
|
M
|
||||||
|
P
|
||||||
|
S
|
||||||
|
C
|
||||||
|
T
|
||||||
|
←
|
||||||
|
→
|
||||||
|
+
|
||||||
|
、
|
||||||
|
◯
|
||||||
|
『
|
||||||
|
F
|
||||||
|
°C
|
||||||
|
・
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
|
||||||
|
\n
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
バ
|
||||||
|
ば
|
||||||
|
ビ
|
||||||
|
び
|
||||||
|
ブ
|
||||||
|
ぶ
|
||||||
|
ベ
|
||||||
|
べ
|
||||||
|
ボ
|
||||||
|
ぼ
|
||||||
|
ガ
|
||||||
|
が
|
||||||
|
ギ
|
||||||
|
ぎ
|
||||||
|
グ
|
||||||
|
ぐ
|
||||||
|
ゲ
|
||||||
|
げ
|
||||||
|
ゴ
|
||||||
|
ご
|
||||||
|
ザ
|
||||||
|
ざ
|
||||||
|
ジ
|
||||||
|
じ
|
||||||
|
ズ
|
||||||
|
ず
|
||||||
|
ゼ
|
||||||
|
ぜ
|
||||||
|
ゾ
|
||||||
|
ぞ
|
||||||
|
ダ
|
||||||
|
だ
|
||||||
|
ヂ
|
||||||
|
ぢ
|
||||||
|
ヅ
|
||||||
|
づ
|
||||||
|
デ
|
||||||
|
で
|
||||||
|
ド
|
||||||
|
ど
|
||||||
|
ヴ
|
||||||
|
パ
|
||||||
|
ぱ
|
||||||
|
ピ
|
||||||
|
ぴ
|
||||||
|
プ
|
||||||
|
ぷ
|
||||||
|
ペ
|
||||||
|
ぺ
|
||||||
|
ポ
|
||||||
|
ぽ
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
_m
|
||||||
|
_H
|
||||||
|
_P
|
||||||
|
ハ
|
||||||
|
は
|
||||||
|
ヒ
|
||||||
|
ひ
|
||||||
|
フ
|
||||||
|
ふ
|
||||||
|
ヘ
|
||||||
|
へ
|
||||||
|
ホ
|
||||||
|
ほ
|
||||||
|
カ
|
||||||
|
か
|
||||||
|
キ
|
||||||
|
き
|
||||||
|
ク
|
||||||
|
く
|
||||||
|
ケ
|
||||||
|
け
|
||||||
|
コ
|
||||||
|
こ
|
||||||
|
サ
|
||||||
|
さ
|
||||||
|
シ
|
||||||
|
し
|
||||||
|
ス
|
||||||
|
す
|
||||||
|
セ
|
||||||
|
せ
|
||||||
|
ソ
|
||||||
|
そ
|
||||||
|
タ
|
||||||
|
た
|
||||||
|
チ
|
||||||
|
ち
|
||||||
|
ツ
|
||||||
|
つ
|
||||||
|
テ
|
||||||
|
て
|
||||||
|
ト
|
||||||
|
と
|
||||||
|
ウ
|
||||||
|
う
|
||||||
|
ア
|
||||||
|
あ
|
||||||
|
イ
|
||||||
|
い
|
||||||
|
エ
|
||||||
|
え
|
||||||
|
オ
|
||||||
|
お
|
||||||
|
ナ
|
||||||
|
な
|
||||||
|
ニ
|
||||||
|
に
|
||||||
|
ヌ
|
||||||
|
ぬ
|
||||||
|
ネ
|
||||||
|
ね
|
||||||
|
ノ
|
||||||
|
の
|
||||||
|
マ
|
||||||
|
ま
|
||||||
|
ミ
|
||||||
|
み
|
||||||
|
ム
|
||||||
|
む
|
||||||
|
メ
|
||||||
|
め
|
||||||
|
モ
|
||||||
|
も
|
||||||
|
ラ
|
||||||
|
ら
|
||||||
|
リ
|
||||||
|
り
|
||||||
|
ル
|
||||||
|
る
|
||||||
|
レ
|
||||||
|
れ
|
||||||
|
ロ
|
||||||
|
ろ
|
||||||
|
ヤ
|
||||||
|
や
|
||||||
|
ユ
|
||||||
|
ゆ
|
||||||
|
ヨ
|
||||||
|
よ
|
||||||
|
ワ
|
||||||
|
わ
|
||||||
|
ン
|
||||||
|
ん
|
||||||
|
ヲ
|
||||||
|
を
|
||||||
|
ッ
|
||||||
|
っ
|
||||||
|
ャ
|
||||||
|
ゃ
|
||||||
|
ュ
|
||||||
|
ゅ
|
||||||
|
ョ
|
||||||
|
ょ
|
||||||
|
ァ
|
||||||
|
ー
|
||||||
|
ィ
|
||||||
|
⋯
|
||||||
|
ぅ
|
||||||
|
!
|
||||||
|
ェ
|
||||||
|
?
|
||||||
|
ォ
|
||||||
|
%
|
||||||
|
/
|
||||||
|
:
|
||||||
|
「
|
||||||
|
」
|
||||||
|
。
|
||||||
|
A
|
||||||
|
B
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
L
|
||||||
|
R
|
||||||
|
E
|
||||||
|
H
|
||||||
|
M
|
||||||
|
P
|
||||||
|
S
|
||||||
|
C
|
||||||
|
T
|
||||||
|
←
|
||||||
|
→
|
||||||
|
+
|
||||||
|
、
|
||||||
|
◯
|
||||||
|
『
|
||||||
|
F
|
||||||
|
°C
|
||||||
|
・
|
||||||
|
(
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,426 @@
|
||||||
|
王
|
||||||
|
行
|
||||||
|
力
|
||||||
|
様
|
||||||
|
飛
|
||||||
|
世
|
||||||
|
界
|
||||||
|
封
|
||||||
|
入
|
||||||
|
城
|
||||||
|
竜
|
||||||
|
士
|
||||||
|
船
|
||||||
|
印
|
||||||
|
海
|
||||||
|
父
|
||||||
|
人
|
||||||
|
見
|
||||||
|
魔
|
||||||
|
物
|
||||||
|
大
|
||||||
|
者
|
||||||
|
本
|
||||||
|
何
|
||||||
|
戦
|
||||||
|
出
|
||||||
|
気
|
||||||
|
手
|
||||||
|
言
|
||||||
|
石
|
||||||
|
守
|
||||||
|
辺
|
||||||
|
無
|
||||||
|
心
|
||||||
|
間
|
||||||
|
前
|
||||||
|
風
|
||||||
|
帰
|
||||||
|
私
|
||||||
|
生
|
||||||
|
年
|
||||||
|
武
|
||||||
|
器
|
||||||
|
兵
|
||||||
|
火
|
||||||
|
使
|
||||||
|
中
|
||||||
|
急
|
||||||
|
時
|
||||||
|
森
|
||||||
|
来
|
||||||
|
砂
|
||||||
|
動
|
||||||
|
女
|
||||||
|
待
|
||||||
|
臣
|
||||||
|
地
|
||||||
|
助
|
||||||
|
当
|
||||||
|
今
|
||||||
|
上
|
||||||
|
悪
|
||||||
|
泉
|
||||||
|
騎
|
||||||
|
思
|
||||||
|
持
|
||||||
|
変
|
||||||
|
水
|
||||||
|
塔
|
||||||
|
草
|
||||||
|
仲
|
||||||
|
復
|
||||||
|
目
|
||||||
|
作
|
||||||
|
分
|
||||||
|
知
|
||||||
|
機
|
||||||
|
一
|
||||||
|
姫
|
||||||
|
最
|
||||||
|
聞
|
||||||
|
械
|
||||||
|
神
|
||||||
|
流
|
||||||
|
乗
|
||||||
|
取
|
||||||
|
町
|
||||||
|
殿
|
||||||
|
事
|
||||||
|
空
|
||||||
|
勇
|
||||||
|
村
|
||||||
|
早
|
||||||
|
子
|
||||||
|
格
|
||||||
|
納
|
||||||
|
伝
|
||||||
|
消
|
||||||
|
書
|
||||||
|
長
|
||||||
|
話
|
||||||
|
合
|
||||||
|
所
|
||||||
|
場
|
||||||
|
脱
|
||||||
|
北
|
||||||
|
後
|
||||||
|
全
|
||||||
|
忍
|
||||||
|
獣
|
||||||
|
詩
|
||||||
|
吟
|
||||||
|
落
|
||||||
|
自
|
||||||
|
階
|
||||||
|
説
|
||||||
|
残
|
||||||
|
親
|
||||||
|
活
|
||||||
|
休
|
||||||
|
姉
|
||||||
|
破
|
||||||
|
土
|
||||||
|
度
|
||||||
|
記
|
||||||
|
発
|
||||||
|
赤
|
||||||
|
侍
|
||||||
|
死
|
||||||
|
国
|
||||||
|
旅
|
||||||
|
完
|
||||||
|
小
|
||||||
|
林
|
||||||
|
古
|
||||||
|
図
|
||||||
|
礼
|
||||||
|
商
|
||||||
|
島
|
||||||
|
邪
|
||||||
|
部
|
||||||
|
狩
|
||||||
|
精
|
||||||
|
姿
|
||||||
|
防
|
||||||
|
向
|
||||||
|
先
|
||||||
|
解
|
||||||
|
板
|
||||||
|
下
|
||||||
|
台
|
||||||
|
賢
|
||||||
|
対
|
||||||
|
木
|
||||||
|
成
|
||||||
|
命
|
||||||
|
配
|
||||||
|
法
|
||||||
|
飲
|
||||||
|
回
|
||||||
|
願
|
||||||
|
門
|
||||||
|
東
|
||||||
|
開
|
||||||
|
貸
|
||||||
|
増
|
||||||
|
危
|
||||||
|
舘
|
||||||
|
道
|
||||||
|
身
|
||||||
|
老
|
||||||
|
西
|
||||||
|
近
|
||||||
|
層
|
||||||
|
第
|
||||||
|
青
|
||||||
|
光
|
||||||
|
読
|
||||||
|
外
|
||||||
|
理
|
||||||
|
強
|
||||||
|
同
|
||||||
|
谷
|
||||||
|
負
|
||||||
|
意
|
||||||
|
学
|
||||||
|
攻
|
||||||
|
屋
|
||||||
|
体
|
||||||
|
運
|
||||||
|
河
|
||||||
|
聖
|
||||||
|
必
|
||||||
|
南
|
||||||
|
方
|
||||||
|
黒
|
||||||
|
絶
|
||||||
|
食
|
||||||
|
艇
|
||||||
|
山
|
||||||
|
穴
|
||||||
|
名
|
||||||
|
受
|
||||||
|
暁
|
||||||
|
傷
|
||||||
|
少
|
||||||
|
鼻
|
||||||
|
倉
|
||||||
|
然
|
||||||
|
的
|
||||||
|
男
|
||||||
|
用
|
||||||
|
酒
|
||||||
|
安
|
||||||
|
現
|
||||||
|
代
|
||||||
|
立
|
||||||
|
具
|
||||||
|
育
|
||||||
|
続
|
||||||
|
通
|
||||||
|
会
|
||||||
|
庫
|
||||||
|
飼
|
||||||
|
日
|
||||||
|
窟
|
||||||
|
砲
|
||||||
|
広
|
||||||
|
化
|
||||||
|
博
|
||||||
|
以
|
||||||
|
兄
|
||||||
|
室
|
||||||
|
洞
|
||||||
|
別
|
||||||
|
番
|
||||||
|
昔
|
||||||
|
住
|
||||||
|
吸
|
||||||
|
去
|
||||||
|
宝
|
||||||
|
毒
|
||||||
|
売
|
||||||
|
好
|
||||||
|
枝
|
||||||
|
踊
|
||||||
|
月
|
||||||
|
巣
|
||||||
|
底
|
||||||
|
明
|
||||||
|
感
|
||||||
|
宿
|
||||||
|
召
|
||||||
|
喚
|
||||||
|
決
|
||||||
|
形
|
||||||
|
団
|
||||||
|
異
|
||||||
|
初
|
||||||
|
険
|
||||||
|
家
|
||||||
|
息
|
||||||
|
連
|
||||||
|
集
|
||||||
|
墓
|
||||||
|
著
|
||||||
|
跡
|
||||||
|
遺
|
||||||
|
内
|
||||||
|
悲
|
||||||
|
教
|
||||||
|
返
|
||||||
|
頭
|
||||||
|
再
|
||||||
|
高
|
||||||
|
借
|
||||||
|
登
|
||||||
|
走
|
||||||
|
過
|
||||||
|
敵
|
||||||
|
鏡
|
||||||
|
愛
|
||||||
|
魚
|
||||||
|
燃
|
||||||
|
幅
|
||||||
|
音
|
||||||
|
血
|
||||||
|
滝
|
||||||
|
次
|
||||||
|
閉
|
||||||
|
求
|
||||||
|
声
|
||||||
|
箱
|
||||||
|
軍
|
||||||
|
泣
|
||||||
|
港
|
||||||
|
冊
|
||||||
|
秒
|
||||||
|
憶
|
||||||
|
潜
|
||||||
|
爆
|
||||||
|
恋
|
||||||
|
石
|
||||||
|
左
|
||||||
|
役
|
||||||
|
放
|
||||||
|
衛
|
||||||
|
母
|
||||||
|
建
|
||||||
|
角
|
||||||
|
信
|
||||||
|
師
|
||||||
|
元
|
||||||
|
失
|
||||||
|
字
|
||||||
|
歩
|
||||||
|
遊
|
||||||
|
頂
|
||||||
|
震
|
||||||
|
収
|
||||||
|
追
|
||||||
|
暗
|
||||||
|
橋
|
||||||
|
起
|
||||||
|
約
|
||||||
|
束
|
||||||
|
文
|
||||||
|
反
|
||||||
|
絵
|
||||||
|
能
|
||||||
|
歌
|
||||||
|
弱
|
||||||
|
境
|
||||||
|
波
|
||||||
|
針
|
||||||
|
千
|
||||||
|
炎
|
||||||
|
望
|
||||||
|
雨
|
||||||
|
金
|
||||||
|
希
|
||||||
|
花
|
||||||
|
不
|
||||||
|
白
|
||||||
|
剣
|
||||||
|
陸
|
||||||
|
勝
|
||||||
|
烏
|
||||||
|
笛
|
||||||
|
究
|
||||||
|
三
|
||||||
|
探
|
||||||
|
夜
|
||||||
|
病
|
||||||
|
半
|
||||||
|
美
|
||||||
|
敗
|
||||||
|
友
|
||||||
|
研
|
||||||
|
予
|
||||||
|
支
|
||||||
|
々
|
||||||
|
除
|
||||||
|
数
|
||||||
|
店
|
||||||
|
翼
|
||||||
|
充
|
||||||
|
填
|
||||||
|
薬
|
||||||
|
弟
|
||||||
|
経
|
||||||
|
験
|
||||||
|
値
|
||||||
|
闘
|
||||||
|
得
|
||||||
|
闇
|
||||||
|
混
|
||||||
|
乱
|
||||||
|
点
|
||||||
|
速
|
||||||
|
閃
|
||||||
|
射
|
||||||
|
斬
|
||||||
|
鉄
|
||||||
|
宣
|
||||||
|
告
|
||||||
|
電
|
||||||
|
磁
|
||||||
|
圧
|
||||||
|
昇
|
||||||
|
重
|
||||||
|
倍
|
||||||
|
妖
|
||||||
|
刀
|
||||||
|
拡
|
||||||
|
散
|
||||||
|
影
|
||||||
|
御
|
||||||
|
正
|
||||||
|
漠
|
||||||
|
特
|
||||||
|
胸
|
||||||
|
永
|
||||||
|
遠
|
||||||
|
議
|
||||||
|
浮
|
||||||
|
眠
|
||||||
|
逃
|
||||||
|
滅
|
||||||
|
囗
|
||||||
|
極
|
||||||
|
湖
|
||||||
|
利
|
||||||
|
周
|
||||||
|
緑
|
||||||
|
切
|
||||||
|
紙
|
||||||
|
胸
|
||||||
|
平
|
||||||
|
和
|
||||||
|
宇
|
||||||
|
宙
|
||||||
|
則
|
||||||
|
孤
|
||||||
|
溝
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
E
|
||||||
|
F
|
||||||
|
G
|
||||||
|
H
|
||||||
|
I
|
||||||
|
J
|
||||||
|
K
|
||||||
|
L
|
||||||
|
M
|
||||||
|
N
|
||||||
|
O
|
||||||
|
P
|
||||||
|
Q
|
||||||
|
R
|
||||||
|
S
|
||||||
|
T
|
||||||
|
U
|
||||||
|
V
|
||||||
|
W
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
Z
|
||||||
|
[stone]
|
||||||
|
[toad]
|
||||||
|
[mini]
|
||||||
|
[float]
|
||||||
|
[poison]
|
||||||
|
[KO]
|
||||||
|
[blind]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
_m
|
||||||
|
_H
|
||||||
|
_P
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
E
|
||||||
|
F
|
||||||
|
G
|
||||||
|
H
|
||||||
|
I
|
||||||
|
J
|
||||||
|
K
|
||||||
|
L
|
||||||
|
M
|
||||||
|
N
|
||||||
|
O
|
||||||
|
P
|
||||||
|
Q
|
||||||
|
R
|
||||||
|
S
|
||||||
|
T
|
||||||
|
U
|
||||||
|
V
|
||||||
|
W
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
Z
|
||||||
|
a
|
||||||
|
b
|
||||||
|
c
|
||||||
|
d
|
||||||
|
e
|
||||||
|
f
|
||||||
|
g
|
||||||
|
h
|
||||||
|
i
|
||||||
|
j
|
||||||
|
k
|
||||||
|
l
|
||||||
|
m
|
||||||
|
n
|
||||||
|
o
|
||||||
|
p
|
||||||
|
q
|
||||||
|
r
|
||||||
|
s
|
||||||
|
t
|
||||||
|
u
|
||||||
|
v
|
||||||
|
w
|
||||||
|
x
|
||||||
|
y
|
||||||
|
z
|
||||||
|
il
|
||||||
|
it
|
||||||
|
|
||||||
|
li
|
||||||
|
ll
|
||||||
|
'
|
||||||
|
"
|
||||||
|
:
|
||||||
|
;
|
||||||
|
,
|
||||||
|
(
|
||||||
|
)
|
||||||
|
/
|
||||||
|
!
|
||||||
|
?
|
||||||
|
.
|
||||||
|
ti
|
||||||
|
fi
|
||||||
|
Bl
|
||||||
|
a
|
||||||
|
pe
|
||||||
|
l
|
||||||
|
'
|
||||||
|
"
|
||||||
|
if
|
||||||
|
lt
|
||||||
|
tl
|
||||||
|
ir
|
||||||
|
tt
|
||||||
|
や
|
||||||
|
ユ
|
||||||
|
ゆ
|
||||||
|
ヨ
|
||||||
|
よ
|
||||||
|
ワ
|
||||||
|
わ
|
||||||
|
ン
|
||||||
|
ん
|
||||||
|
ヲ
|
||||||
|
を
|
||||||
|
[key]
|
||||||
|
[shoe]
|
||||||
|
◆
|
||||||
|
[hammer]
|
||||||
|
⛺
|
||||||
|
[ribbon]
|
||||||
|
[potion]
|
||||||
|
[shirt]
|
||||||
|
♪
|
||||||
|
-
|
||||||
|
[shuriken]
|
||||||
|
‥
|
||||||
|
[scroll]
|
||||||
|
!
|
||||||
|
[claw]
|
||||||
|
?
|
||||||
|
[glove]
|
||||||
|
%
|
||||||
|
/
|
||||||
|
:
|
||||||
|
「
|
||||||
|
」
|
||||||
|
.
|
||||||
|
A
|
||||||
|
B
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
L
|
||||||
|
R
|
||||||
|
E
|
||||||
|
H
|
||||||
|
M
|
||||||
|
P
|
||||||
|
S
|
||||||
|
C
|
||||||
|
T
|
||||||
|
↑
|
||||||
|
→
|
||||||
|
+
|
||||||
|
[sword]
|
||||||
|
[wh.mag]
|
||||||
|
[blk.mag]
|
||||||
|
🕒
|
||||||
|
[knife]
|
||||||
|
[spear]
|
||||||
|
[axe]
|
||||||
|
[katana]
|
||||||
|
[rod]
|
||||||
|
[staff]
|
||||||
|
[bow]
|
||||||
|
[harp]
|
||||||
|
[whip]
|
||||||
|
[bell]
|
||||||
|
[shield]
|
||||||
|
[helmet]
|
||||||
|
[armor]
|
||||||
|
[ring]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
A
|
||||||
|
B
|
||||||
|
C
|
||||||
|
D
|
||||||
|
E
|
||||||
|
F
|
||||||
|
G
|
||||||
|
H
|
||||||
|
I
|
||||||
|
J
|
||||||
|
K
|
||||||
|
L
|
||||||
|
M
|
||||||
|
N
|
||||||
|
O
|
||||||
|
P
|
||||||
|
Q
|
||||||
|
R
|
||||||
|
S
|
||||||
|
T
|
||||||
|
U
|
||||||
|
V
|
||||||
|
W
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
Z
|
||||||
|
[stone]
|
||||||
|
[toad]
|
||||||
|
[mini]
|
||||||
|
[float]
|
||||||
|
[poison]
|
||||||
|
[KO]
|
||||||
|
[blind]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
_m
|
||||||
|
_H
|
||||||
|
_P
|
||||||
|
ハ
|
||||||
|
は
|
||||||
|
ヒ
|
||||||
|
ひ
|
||||||
|
フ
|
||||||
|
ふ
|
||||||
|
ヘ
|
||||||
|
へ
|
||||||
|
ホ
|
||||||
|
ほ
|
||||||
|
カ
|
||||||
|
か
|
||||||
|
キ
|
||||||
|
き
|
||||||
|
ク
|
||||||
|
く
|
||||||
|
ケ
|
||||||
|
け
|
||||||
|
コ
|
||||||
|
こ
|
||||||
|
サ
|
||||||
|
さ
|
||||||
|
シ
|
||||||
|
し
|
||||||
|
ス
|
||||||
|
す
|
||||||
|
セ
|
||||||
|
せ
|
||||||
|
ソ
|
||||||
|
そ
|
||||||
|
タ
|
||||||
|
た
|
||||||
|
チ
|
||||||
|
ち
|
||||||
|
ツ
|
||||||
|
つ
|
||||||
|
テ
|
||||||
|
て
|
||||||
|
ト
|
||||||
|
と
|
||||||
|
ウ
|
||||||
|
う
|
||||||
|
ア
|
||||||
|
あ
|
||||||
|
イ
|
||||||
|
い
|
||||||
|
エ
|
||||||
|
え
|
||||||
|
オ
|
||||||
|
お
|
||||||
|
ナ
|
||||||
|
な
|
||||||
|
ニ
|
||||||
|
に
|
||||||
|
ヌ
|
||||||
|
ぬ
|
||||||
|
ネ
|
||||||
|
ね
|
||||||
|
ノ
|
||||||
|
の
|
||||||
|
マ
|
||||||
|
ま
|
||||||
|
ミ
|
||||||
|
み
|
||||||
|
ム
|
||||||
|
む
|
||||||
|
メ
|
||||||
|
め
|
||||||
|
モ
|
||||||
|
も
|
||||||
|
ラ
|
||||||
|
ら
|
||||||
|
リ
|
||||||
|
り
|
||||||
|
ル
|
||||||
|
る
|
||||||
|
レ
|
||||||
|
れ
|
||||||
|
ロ
|
||||||
|
ろ
|
||||||
|
ヤ
|
||||||
|
や
|
||||||
|
ユ
|
||||||
|
ゆ
|
||||||
|
ヨ
|
||||||
|
よ
|
||||||
|
ワ
|
||||||
|
わ
|
||||||
|
ン
|
||||||
|
ん
|
||||||
|
ヲ
|
||||||
|
を
|
||||||
|
ッ
|
||||||
|
っ
|
||||||
|
ャ
|
||||||
|
ゃ
|
||||||
|
ュ
|
||||||
|
ゅ
|
||||||
|
ョ
|
||||||
|
ょ
|
||||||
|
ァ
|
||||||
|
ー
|
||||||
|
ィ
|
||||||
|
‥
|
||||||
|
ぅ
|
||||||
|
!
|
||||||
|
ェ
|
||||||
|
?
|
||||||
|
ォ
|
||||||
|
%
|
||||||
|
/
|
||||||
|
:
|
||||||
|
「
|
||||||
|
」
|
||||||
|
。
|
||||||
|
A
|
||||||
|
B
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
L
|
||||||
|
R
|
||||||
|
E
|
||||||
|
H
|
||||||
|
M
|
||||||
|
P
|
||||||
|
S
|
||||||
|
C
|
||||||
|
T
|
||||||
|
↑
|
||||||
|
→
|
||||||
|
+
|
||||||
|
[洋剣]
|
||||||
|
[wh.mag]
|
||||||
|
[blk.mag]
|
||||||
|
🕒
|
||||||
|
[刂]
|
||||||
|
[槍]
|
||||||
|
[鉞]
|
||||||
|
[刀]
|
||||||
|
[棒]
|
||||||
|
[杖]
|
||||||
|
[弓]
|
||||||
|
♪
|
||||||
|
[鞭]
|
||||||
|
[bell]
|
||||||
|
[shield]
|
||||||
|
[helmet]
|
||||||
|
[armor]
|
||||||
|
[ring]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
バ
|
||||||
|
ば
|
||||||
|
ビ
|
||||||
|
び
|
||||||
|
ブ
|
||||||
|
ぶ
|
||||||
|
ベ
|
||||||
|
べ
|
||||||
|
ボ
|
||||||
|
ぼ
|
||||||
|
ガ
|
||||||
|
が
|
||||||
|
ギ
|
||||||
|
ぎ
|
||||||
|
グ
|
||||||
|
ぐ
|
||||||
|
ゲ
|
||||||
|
げ
|
||||||
|
ゴ
|
||||||
|
ご
|
||||||
|
ザ
|
||||||
|
ざ
|
||||||
|
ジ
|
||||||
|
じ
|
||||||
|
ズ
|
||||||
|
ず
|
||||||
|
ゼ
|
||||||
|
ぜ
|
||||||
|
ゾ
|
||||||
|
ぞ
|
||||||
|
ダ
|
||||||
|
だ
|
||||||
|
ヂ
|
||||||
|
ぢ
|
||||||
|
ヅ
|
||||||
|
づ
|
||||||
|
デ
|
||||||
|
で
|
||||||
|
ド
|
||||||
|
ど
|
||||||
|
ヴ
|
||||||
|
パ
|
||||||
|
ぱ
|
||||||
|
ピ
|
||||||
|
ぴ
|
||||||
|
プ
|
||||||
|
ぷ
|
||||||
|
ペ
|
||||||
|
ぺ
|
||||||
|
ポ
|
||||||
|
ぽ
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
_m
|
||||||
|
_H
|
||||||
|
_P
|
||||||
|
ハ
|
||||||
|
は
|
||||||
|
ヒ
|
||||||
|
ひ
|
||||||
|
フ
|
||||||
|
ふ
|
||||||
|
ヘ
|
||||||
|
へ
|
||||||
|
ホ
|
||||||
|
ほ
|
||||||
|
カ
|
||||||
|
か
|
||||||
|
キ
|
||||||
|
き
|
||||||
|
ク
|
||||||
|
く
|
||||||
|
ケ
|
||||||
|
け
|
||||||
|
コ
|
||||||
|
こ
|
||||||
|
サ
|
||||||
|
さ
|
||||||
|
シ
|
||||||
|
し
|
||||||
|
ス
|
||||||
|
す
|
||||||
|
セ
|
||||||
|
せ
|
||||||
|
ソ
|
||||||
|
そ
|
||||||
|
タ
|
||||||
|
た
|
||||||
|
チ
|
||||||
|
ち
|
||||||
|
ツ
|
||||||
|
つ
|
||||||
|
テ
|
||||||
|
て
|
||||||
|
ト
|
||||||
|
と
|
||||||
|
ウ
|
||||||
|
う
|
||||||
|
ア
|
||||||
|
あ
|
||||||
|
イ
|
||||||
|
い
|
||||||
|
エ
|
||||||
|
え
|
||||||
|
オ
|
||||||
|
お
|
||||||
|
ナ
|
||||||
|
な
|
||||||
|
ニ
|
||||||
|
に
|
||||||
|
ヌ
|
||||||
|
ぬ
|
||||||
|
ネ
|
||||||
|
ね
|
||||||
|
ノ
|
||||||
|
の
|
||||||
|
マ
|
||||||
|
ま
|
||||||
|
ミ
|
||||||
|
み
|
||||||
|
ム
|
||||||
|
む
|
||||||
|
メ
|
||||||
|
め
|
||||||
|
モ
|
||||||
|
も
|
||||||
|
ラ
|
||||||
|
ら
|
||||||
|
リ
|
||||||
|
り
|
||||||
|
ル
|
||||||
|
る
|
||||||
|
レ
|
||||||
|
れ
|
||||||
|
ロ
|
||||||
|
ろ
|
||||||
|
ヤ
|
||||||
|
や
|
||||||
|
ユ
|
||||||
|
ゆ
|
||||||
|
ヨ
|
||||||
|
よ
|
||||||
|
ワ
|
||||||
|
わ
|
||||||
|
ン
|
||||||
|
ん
|
||||||
|
ヲ
|
||||||
|
を
|
||||||
|
ッ
|
||||||
|
っ
|
||||||
|
ャ
|
||||||
|
ゃ
|
||||||
|
ュ
|
||||||
|
ゅ
|
||||||
|
ョ
|
||||||
|
ょ
|
||||||
|
ァ
|
||||||
|
ー
|
||||||
|
ィ
|
||||||
|
‥
|
||||||
|
ぅ
|
||||||
|
!
|
||||||
|
ェ
|
||||||
|
?
|
||||||
|
ォ
|
||||||
|
%
|
||||||
|
/
|
||||||
|
:
|
||||||
|
「
|
||||||
|
」
|
||||||
|
。
|
||||||
|
A
|
||||||
|
B
|
||||||
|
X
|
||||||
|
Y
|
||||||
|
L
|
||||||
|
R
|
||||||
|
E
|
||||||
|
H
|
||||||
|
M
|
||||||
|
P
|
||||||
|
S
|
||||||
|
C
|
||||||
|
T
|
||||||
|
↑
|
||||||
|
→
|
||||||
|
+
|
||||||
|
[洋剣]
|
||||||
|
[wh.mag]
|
||||||
|
[blk.mag]
|
||||||
|
🕒
|
||||||
|
[刂]
|
||||||
|
[槍]
|
||||||
|
[鉞]
|
||||||
|
[刀]
|
||||||
|
[棒]
|
||||||
|
[杖]
|
||||||
|
[弓]
|
||||||
|
♪
|
||||||
|
[鞭]
|
||||||
|
[bell]
|
||||||
|
[shield]
|
||||||
|
[helmet]
|
||||||
|
[armor]
|
||||||
|
[ring]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
from . import const, files, strings
|
||||||
|
from ..helpers import hex, parse_struct
|
||||||
|
|
||||||
|
class ZoneData:
|
||||||
|
def split_tilesets(data):
|
||||||
|
tilesets = [(data & 0x00003F),
|
||||||
|
(data & 0x000FC0) >> 6,
|
||||||
|
(data & 0x03F000) >> 12,
|
||||||
|
(data & 0xFC0000) >> 18]
|
||||||
|
return ' '.join([hex(i,2) for i in tilesets])
|
||||||
|
|
||||||
|
def split_blockmaps(data):
|
||||||
|
blockmaps = [(data & 0x000003FF) - 1,
|
||||||
|
((data & 0x000FFC00) >> 10) - 1,
|
||||||
|
((data & 0x3FF00000) >> 20) - 1]
|
||||||
|
return ' '.join([hex(i,3) for i in blockmaps])
|
||||||
|
|
||||||
|
zone_structure = [('NPC Layer', 2, None), # 00 01
|
||||||
|
('Name', 1, strings.Strings.blocks_RPGe['zone_names'].decoded), # 02
|
||||||
|
('ShadowFlags', 1, None), # 03
|
||||||
|
('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
|
||||||
|
('Blockset', 1, None), # 08
|
||||||
|
('Tilesets', 3, split_tilesets), # 09 0A 0B
|
||||||
|
('Blockmaps', 4, split_blockmaps), # 0C 0D 0E 0F
|
||||||
|
(hex(16), 1, None), # 10
|
||||||
|
(hex(17), 1, None), # 11
|
||||||
|
(hex(18), 1, None), # 12
|
||||||
|
(hex(19), 1, None), # 13
|
||||||
|
(hex(20), 1, None), # 14
|
||||||
|
(hex(21), 1, None), # 15
|
||||||
|
('Palette', 1, None), # 16
|
||||||
|
(hex(23), 1, None), # 17
|
||||||
|
(hex(24), 1, None), # 18
|
||||||
|
('Music', 1, const.BGM_Tracks)] # 19
|
||||||
|
zone_headers = ['Address'] + [z[0] for z in zone_structure]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_data(cls):
|
||||||
|
return [parse_struct(files.ROM_RPGe, 0x0E9C00 + (i*0x1A), cls.zone_structure) for i in range(const.zone_count)]
|
|
@ -0,0 +1,47 @@
|
||||||
|
'''
|
||||||
|
This file is part of ff5reader.
|
||||||
|
|
||||||
|
ff5reader is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
ff5reader is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with ff5reader. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
from ..helpers import load_table
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
dir = Path(__file__).parent
|
||||||
|
# print(dir)
|
||||||
|
|
||||||
|
|
||||||
|
# Font palettes (probably move these later)
|
||||||
|
small_palette = [0xFF000000, 0x00000080, 0xFF808080, 0xFFFFFFFF]
|
||||||
|
dialogue_palette = [0xFF000080, 0xFFFFFFFF]
|
||||||
|
mono_palette = [0xFF000000, 0xFFFFFFFF]
|
||||||
|
|
||||||
|
|
||||||
|
BGM_Tracks = load_table(dir/'BGM_Titles.txt')
|
||||||
|
BGM_Tracks_Safe = [t.replace('/', '-') for t in BGM_Tracks] # For saving files
|
||||||
|
|
||||||
|
|
||||||
|
npc_layer_count = 0x200
|
||||||
|
npc_layer_structure = [
|
||||||
|
("Dialogue/trigger ID", 1, None),
|
||||||
|
("0x01", 1, None),
|
||||||
|
("Sprite ID", 1, None),
|
||||||
|
("X", 1, None),
|
||||||
|
("Y", 1, None),
|
||||||
|
("Move Pattern", 1, None),
|
||||||
|
("Palette", 1, None)
|
||||||
|
]
|
||||||
|
npc_layer_headers = ["Ptr Address", "Layer", "Data Address"] + [x[0] for x in npc_layer_structure]
|
||||||
|
|
||||||
|
|
||||||
|
zone_count = 0x200
|
|
@ -0,0 +1,7 @@
|
||||||
|
from ..helpers import load_raw
|
||||||
|
|
||||||
|
filename_RPGe = 'Final Fantasy V (Japan) [En by RPGe v1.1].sfc'
|
||||||
|
filename_SNES = 'Final Fantasy V (Japan).sfc'
|
||||||
|
|
||||||
|
ROM_RPGe = load_raw(filename_RPGe)
|
||||||
|
ROM_SNES = load_raw(filename_SNES)
|
|
@ -0,0 +1,12 @@
|
||||||
|
name num_entries address snes_address bytes snes_bytes rpge_ptr_offset snes_ptr_offset dialog
|
||||||
|
ability_names 33 0x116200 8
|
||||||
|
battle_commands 96 0x201150 0x115800 7 5
|
||||||
|
character_names 5 0x115500 6
|
||||||
|
dialogue 0x900 0x2013F0 0x082220 3 2 0x000000 0x0A0000 True
|
||||||
|
enemy_names 384 0x200050 0x105C00 10 8
|
||||||
|
items 0x100 0x111380 9
|
||||||
|
job_names 22 0x115600 8
|
||||||
|
magics 87 0x111C80 6
|
||||||
|
magics2 73 0x111E8A 9
|
||||||
|
menu_strings 139 0x00F987 2 0x270000 0x000000
|
||||||
|
zone_names 0x100 0x107000 2 0x270000 0x107200 True
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
from ..helpers import get_slices, get_contiguous_address_slices, load_table, load_tsv
|
||||||
|
from . import files
|
||||||
|
from typing import Callable, Iterable
|
||||||
|
from pathlib import Path
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class StringBlock:
|
||||||
|
raw: bytes
|
||||||
|
glyphs: Iterable[int]
|
||||||
|
decoded: str
|
||||||
|
string_address: int
|
||||||
|
pointer_address: int
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.raw)
|
||||||
|
|
||||||
|
|
||||||
|
dir = Path(__file__).parent
|
||||||
|
#print(dir)
|
||||||
|
|
||||||
|
|
||||||
|
# Transcription of RPGe glyph tiles
|
||||||
|
Glyphs_small_RPGe = load_table(dir/'Glyphs_small_RPGe.txt')
|
||||||
|
Glyphs_dialog_RPGe = load_table(dir/'Glyphs_dialog_RPGe.txt')
|
||||||
|
Glyphs_small_SNES = load_table(dir/'Glyphs_small_SNES.txt')
|
||||||
|
Glyphs_small_dakuten_SNES = load_table(dir/'Glyphs_small_dakuten_SNES.txt')
|
||||||
|
Glyphs_dialog_SNES = load_table(dir/'Glyphs_dialog_SNES.txt')
|
||||||
|
Glyphs_dialog_kanji_SNES = load_table(dir/'Glyphs_dialog_kanji_SNES.txt')
|
||||||
|
|
||||||
|
|
||||||
|
class DialogMacros:
|
||||||
|
expansions_SNES = {
|
||||||
|
# Is 0x00 a wait for input marker?
|
||||||
|
# 0x01 is linebreak
|
||||||
|
0x02: [0x20, 0xBC, 0x82], # 0x02 expands to Bartz's name バッツ. Used for his dialogue in EN, only used for other chars in JP.
|
||||||
|
0x03: [0x6E, 0xA8, 0x78, 0x7E, 0xAA], # 0x03 is クリスタル
|
||||||
|
0x04: [0x7E, 0x8C, 0x6E, 0xC5, 0xB8], # expands to タイクーン
|
||||||
|
0x06: [0x37, 0xBF], # expands to じゃ
|
||||||
|
0x07: [0x8D, 0xAB], # expands to いる
|
||||||
|
0x08: [0xFF, 0xFF, 0xFF, 0xFF], # 4 spaces
|
||||||
|
0x09: [0xFF, 0xFF, 0xFF], # 3 spaces
|
||||||
|
0x0A: [0xFF, 0xFF], # 2 spaces
|
||||||
|
0x0B: [0x1E12, 0x1E13], # expands to 魔物
|
||||||
|
# 0x0C appears to be a pause in delivery - affects previous char
|
||||||
|
0x0D: [0x1E24, 0x9B, 0x1E52, 0x1E57], # expands to 風の神殿
|
||||||
|
0x0E: [0x1E04, 0x1E0A], # expands to 飛竜
|
||||||
|
# 0x0F - unknown (invisible control char)
|
||||||
|
# 0x10 is a gil substitution
|
||||||
|
# 0x11 and 0x12 appear to be item (obtained) substitutions
|
||||||
|
0x13: [0x1E07, 0x1E0D], # expands to 封印
|
||||||
|
0x14: [0x76, 0x46, 0xD0], # Cid speaking - シド「
|
||||||
|
0x15: [0x9E, 0x46, 0xD0], # Mid speaking - ミド「
|
||||||
|
0x16: [0x1E05, 0x1E06], # expands to 世界
|
||||||
|
# 0x17 uses the next byte for pause duration (seconds?)
|
||||||
|
0x18: [0x8E, 0x6E, 0x78, 0x44, 0x78], # expands to エクスデス
|
||||||
|
0x19: [0xAC, 0x92, 0xD0], # Lenna speaking - レナ「
|
||||||
|
0x1A: [0x2A, 0xA6, 0x64, 0xD0], # Galuf speaking - ガラフ「
|
||||||
|
0x1B: [0x64, 0xC4, 0xA8, 0x78, 0xD0], # Faris speaking - ファリス「
|
||||||
|
0x1C: [0x6E, 0xAA, 0xAA, 0xD0], # Krile/Kara speaking - クルル「
|
||||||
|
0x1D: [0x91, 0x37, 0x8D, 0x81, 0xBF, 0xB9], # expands to おじいちゃん
|
||||||
|
# 0x1E-0x1F form kanji with the next byte
|
||||||
|
# 0x20-0xCC are standard character set
|
||||||
|
0xCD: [0xC9, 0xC9], # % (0xCD) to !!
|
||||||
|
# 0xCE is /
|
||||||
|
0xCF: [0xBD, 0x85], # : (0xCF) appears to expand to って
|
||||||
|
# 0xD0-0xD4 are 「」。AB
|
||||||
|
0xD5: [0x1E1B, 0x95, 0x1E08, 0xAD], # expands to 手に入れ
|
||||||
|
# 0xD6, 0xD7, 0xD8 are YLR
|
||||||
|
0xD9: [0x93, 0x8D], # expands to ない
|
||||||
|
# 0xDA-0xDC are HMP
|
||||||
|
0xDD: [0xC7, 0xC7], # S (0xDD) to ……
|
||||||
|
0xDE: [0x3F, 0x8D, 0x37, 0xC3, 0x89, 0x25], # C (0xDE) to だいじょうぶ
|
||||||
|
0xDF: [0x61, 0xE3], # T (0xDF) to は、
|
||||||
|
0xE0: [0xB9, 0x3F], # expands to んだ
|
||||||
|
0xE1: [0x85, 0x8D], # expands to てい
|
||||||
|
0xE2: [0x77, 0x7F], # expands to した
|
||||||
|
# 0xE3 is 、
|
||||||
|
0xE4: [0x77, 0x85], # ◯ (0xE4) appears to expand to して
|
||||||
|
# 0xE5 is used for Bartz speaking in JP. This only appears as 『
|
||||||
|
0xE6: [0x91, 0x1E0F, 0x1E03], # F (0xE6) appears to expand to otousan (お父様)
|
||||||
|
0xE7: [0xC9, 0xCB], # °C (0xE7) to !? - yes this is the wrong order interrobang
|
||||||
|
0xE8: [0x45, 0x79], # ・ (0xE8) appears to expand to です
|
||||||
|
# 0xE9, 0xEA are ()
|
||||||
|
0xEB: [0x73, 0x9B], # expands to この
|
||||||
|
0xEC: [0x9B, 0x1E02], # expands to の力
|
||||||
|
0xED: [0x70, 0xAA, 0x2A, 0xC5], # expands to ケルガー
|
||||||
|
0xEE: [0x1E86, 0x1ED7, 0x1E87, 0x1E62, 0x1EA7], # expands to 古代図書舘 (ancient library?)
|
||||||
|
0xEF: [0x1E1C, 0xBD, 0x85], # expands to 言って
|
||||||
|
0xF0: [0x1E2B, 0x1E0B, 0xD0], # soldier speaking - 兵士「
|
||||||
|
0xF1: [0x6B, 0xA7], # expands to から
|
||||||
|
0xF2: [0x1E2C, 0x6A, 0x1E0C], # expands to 火カ船
|
||||||
|
0xF3: [0x1E0E, 0x3D, 0x6F], # expands to 海ぞく
|
||||||
|
0xF4: [0x8D, 0x37, 0xC3, 0x89], # expands to いじょう
|
||||||
|
0xF5: [0x2B, 0xE3], # expands to が、
|
||||||
|
0xF6: [0x7F, 0x81], # expands to たち
|
||||||
|
0xF7: [0x7F, 0x9B], # expands to たの
|
||||||
|
0xF8: [0x9D, 0x79], # expands to ます
|
||||||
|
0xF9: [0x6F, 0x3F, 0x75, 0x8D], # expands to ください
|
||||||
|
0xFA: [0x6B, 0xBD, 0x7F], # expands to かった
|
||||||
|
0xFB: [0x7F, 0xC9], # expands to た!
|
||||||
|
0xFC: [0x95, 0xE3], # expands to に、
|
||||||
|
0xFD: [0x8D, 0x93, 0x8D, 0x6B, 0xA7, 0x93, 0xB9, 0x3F], # expands to いないからなんだ
|
||||||
|
0xFE: [0x1F20, 0x1F38, 0x9B, 0x61, 0x35, 0x9D], # expands to 次元のはざま
|
||||||
|
# 0xFF is space
|
||||||
|
}
|
||||||
|
|
||||||
|
# XX: Maybe convert this to subclasses with the same method signature. Don't care enough yet.
|
||||||
|
def expand_RPGe_dialog(raw: bytes) -> list[int]:
|
||||||
|
output = []
|
||||||
|
it = iter(raw)
|
||||||
|
while (n := next(it, None)) is not None:
|
||||||
|
if n == 0x02: # expands to Bartz (or whatever his name is)
|
||||||
|
output += [0x61, 0x7A, 0x8B, 0x8D, 0x93]
|
||||||
|
elif n == 0x17: # Pause marker
|
||||||
|
pause_duration = next(it, None) # Consume the duration
|
||||||
|
# For now, we just strip these
|
||||||
|
output.append(n)
|
||||||
|
return output
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def expand_SNES_dialog(cls, raw: bytes) -> list[int]:
|
||||||
|
output = []
|
||||||
|
it = iter(raw)
|
||||||
|
while (n := next(it, None)) is not None:
|
||||||
|
if n == 0x02: # expands to Bartz (or whatever his name is)
|
||||||
|
output += [0x61, 0x7A, 0x8B, 0x8D, 0x93]
|
||||||
|
elif n == 0x17: # Pause marker
|
||||||
|
pause_duration = next(it, None) # Consume the duration
|
||||||
|
# For now, we just strip these
|
||||||
|
elif n in (0x1E, 0x1F): # Kanji prefix
|
||||||
|
output.append((n<<8) + next(it, 0)) # Convert e.g. 0x1E, 0x2C to 0x1E2C
|
||||||
|
else:
|
||||||
|
output += cls.expansions_SNES.get(n, [n])
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
RPGe_Dialogue_Width = [4 for i in range(256)]
|
||||||
|
RPGe_Dialogue_Width[0x50:0xB1] = [a+1 for a in [
|
||||||
|
5, 2, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 8, # 0x50
|
||||||
|
6, 6, 5, 6, 5, 5, 6, 6, 2, 6, 7, 5, 10, 7, 6, 6, # 0x60
|
||||||
|
6, 6, 6, 6, 6, 6,10, 6, 6, 6, 6, 6, 5, 6, 6, 5, # 0x70
|
||||||
|
6, 6, 2, 5, 6, 2,10, 6, 6, 6, 6, 5, 5, 4, 6, 6, # 0x80
|
||||||
|
10, 6, 6, 6, 5, 7, 6, 5, 5, 2, 5, 2, 2, 2, 3, 3, # 0x90
|
||||||
|
5, 2, 6, 2, 7, 8, 0, 0, 6, 9, 2, 5, 8, 7, 7, 8, # 0xA0
|
||||||
|
9
|
||||||
|
]]
|
||||||
|
|
||||||
|
|
||||||
|
def decode_RPGe_small(glyph: int) -> str:
|
||||||
|
return Glyphs_small_RPGe[glyph]
|
||||||
|
|
||||||
|
|
||||||
|
def decode_RPGe_dialog(glyph: int) -> str:
|
||||||
|
return Glyphs_dialog_RPGe[glyph]
|
||||||
|
|
||||||
|
|
||||||
|
def decode_SNES_small(glyph: int) -> str:
|
||||||
|
return Glyphs_small_SNES[glyph]
|
||||||
|
# return Glyphs_small_dakuten_SNES[glyph]
|
||||||
|
|
||||||
|
|
||||||
|
def decode_SNES_dialog(glyph: int) -> str:
|
||||||
|
if glyph > 0xFF:
|
||||||
|
kanji_idx = glyph - 0x1E00
|
||||||
|
if kanji_idx < 0x1AA: # Hardcoded len(Glyphs_dialog_kanji_SNES)
|
||||||
|
return Glyphs_dialog_kanji_SNES[kanji_idx]
|
||||||
|
return f'[0x{glyph:04x}]'
|
||||||
|
return Glyphs_dialog_SNES[glyph]
|
||||||
|
|
||||||
|
|
||||||
|
def decode_glyphs(glyphs: list[str], glyph_decoder: Callable[[int], str]) -> str:
|
||||||
|
return ''.join(glyph_decoder(c) for c in glyphs)
|
||||||
|
|
||||||
|
|
||||||
|
def make_snes_jp_en_strings(data: dict[str, object]) -> tuple[StringBlock, StringBlock]:
|
||||||
|
print(data)
|
||||||
|
indirect_offset_jp = data.get('snes_ptr_offset')
|
||||||
|
indirect_offset_en = data.get('rpge_ptr_offset')
|
||||||
|
pointer_slices_jp = get_slices(data.get('snes_address', data['address']), data.get('snes_bytes', data['bytes']), data['num_entries'])
|
||||||
|
pointer_slices_en = get_slices(data['address'], data['bytes'], data['num_entries'])
|
||||||
|
|
||||||
|
if indirect_offset_jp is not None:
|
||||||
|
address_slices_jp = get_contiguous_address_slices(files.ROM_SNES, pointer_slices_jp, indirect_offset_jp)
|
||||||
|
else:
|
||||||
|
address_slices_jp = pointer_slices_jp
|
||||||
|
if indirect_offset_en is not None:
|
||||||
|
address_slices_en = get_contiguous_address_slices(files.ROM_RPGe, pointer_slices_en, indirect_offset_en)
|
||||||
|
else:
|
||||||
|
address_slices_en = pointer_slices_en
|
||||||
|
raws_jp = [files.ROM_SNES[s] for s in address_slices_jp]
|
||||||
|
raws_en = [files.ROM_RPGe[s] for s in address_slices_en]
|
||||||
|
|
||||||
|
if data.get('dialog'):
|
||||||
|
glyphs_jp = [DialogMacros.expand_SNES_dialog(raw) for raw in raws_jp]
|
||||||
|
glyphs_en = [DialogMacros.expand_RPGe_dialog(raw) for raw in raws_en]
|
||||||
|
strings_jp = [decode_glyphs(glyphs, decode_SNES_dialog) for glyphs in glyphs_jp]
|
||||||
|
strings_en = [decode_glyphs(glyphs, decode_RPGe_dialog) for glyphs in glyphs_en]
|
||||||
|
else:
|
||||||
|
glyphs_jp = raws_jp
|
||||||
|
glyphs_en = raws_en
|
||||||
|
strings_jp = [decode_glyphs(glyphs, decode_SNES_small) for glyphs in glyphs_jp]
|
||||||
|
strings_en = [decode_glyphs(glyphs, decode_RPGe_small) for glyphs in glyphs_en]
|
||||||
|
return StringBlock(raws_jp, glyphs_jp, strings_jp, address_slices_jp, pointer_slices_jp), StringBlock(raws_en, glyphs_en, strings_en, address_slices_en, pointer_slices_en)
|
||||||
|
|
||||||
|
|
||||||
|
class Strings:
|
||||||
|
config = load_tsv(dir/'string_blocks.tsv')
|
||||||
|
blocks_SNES_RPGe = {k: make_snes_jp_en_strings(v) for k,v in config.items()}
|
||||||
|
blocks_SNES = {k:v[0] for k,v in blocks_SNES_RPGe.items()}
|
||||||
|
blocks_RPGe = {k:v[1] for k,v in blocks_SNES_RPGe.items()}
|
|
@ -1,182 +1,259 @@
|
||||||
'''
|
'''
|
||||||
This file is part of ff5reader.
|
This file is part of ff5reader.
|
||||||
|
|
||||||
ff5reader is free software: you can redistribute it and/or modify
|
ff5reader is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
(at your option) any later version.
|
(at your option) any later version.
|
||||||
|
|
||||||
ff5reader is distributed in the hope that it will be useful,
|
ff5reader is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
GNU General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with ff5reader. If not, see <http://www.gnu.org/licenses/>.
|
along with ff5reader. If not, see <http://www.gnu.org/licenses/>.
|
||||||
'''
|
'''
|
||||||
|
from ast import literal_eval
|
||||||
|
|
||||||
HEX_PREFIX = '#' # '#' '$' or '0x' are also nice
|
HEX_PREFIX = '#' # '#' '$' or '0x' are also nice
|
||||||
|
|
||||||
|
|
||||||
def divceil(numerator, denominator):
|
def divceil(numerator: int, denominator: int) -> int:
|
||||||
'''
|
'''
|
||||||
Reverse floor division for fast ceil
|
Reverse floor division for fast ceil
|
||||||
'''
|
'''
|
||||||
return -(-numerator // denominator)
|
return -(-numerator // denominator)
|
||||||
|
|
||||||
|
|
||||||
def hex_length(i):
|
def hex_length(i: int) -> int:
|
||||||
'''
|
'''
|
||||||
String length of hexadecimal representation of integer
|
String length of hexadecimal representation of integer
|
||||||
'''
|
'''
|
||||||
return divceil(i.bit_length(), 4)
|
return divceil(i.bit_length(), 4)
|
||||||
|
|
||||||
|
|
||||||
def hex(num, digits=2):
|
def hex(num: int, digits: int = 2) -> str:
|
||||||
'''
|
'''
|
||||||
Consolidate hex formatting for consistency
|
Consolidate hex formatting for consistency
|
||||||
'''
|
'''
|
||||||
#return '{:0{}X}₁₆'.format(num, digits)
|
#return '{:0{}X}₁₆'.format(num, digits)
|
||||||
return HEX_PREFIX + '{:0{}X}'.format(num, digits)
|
return HEX_PREFIX + '{:0{}X}'.format(num, digits)
|
||||||
|
|
||||||
|
|
||||||
def indirect(rom, start, length=2, endian='little'):
|
def indirect(rom: bytes, start: int, length: int = 2, endian: str = 'little') -> int:
|
||||||
'''
|
'''
|
||||||
Read little-endian value at start address in rom
|
Read little-endian value at start address in rom
|
||||||
'''
|
'''
|
||||||
return int.from_bytes(rom[start:start+length], endian)
|
return int.from_bytes(rom[start:start+length], endian)
|
||||||
|
|
||||||
|
def indirect2(rom: bytes, slice, endian: str = 'little') -> int:
|
||||||
|
return int.from_bytes(rom[slice], endian)
|
||||||
|
|
||||||
|
def memory_address_to_rom_address(address: int) -> int:
|
||||||
|
# SNES memory space in HiROM Mode:
|
||||||
|
# 0xC00000 to 0xFDFFFF are a view of the ROM, i.e. banks $C0 to $FD.
|
||||||
|
# Banks $80-$BF mirror banks $00-3F, which have a partial view to the ROM.
|
||||||
|
# We don't care about potential RAM addresses at low offsets,
|
||||||
|
# so let's just pretend $00-$3F, $40-$7F, $80-$BF and $C0-$FF all map to $00-$3F of the ROM
|
||||||
|
return address & 0x3FFFFF
|
||||||
|
|
||||||
|
|
||||||
def parse_struct(rom, offset, structure):
|
def get_slices(start_address: int, each_length: int, num_strings: int) -> list:
|
||||||
'''
|
return [slice(start_address+(each_length*id), start_address+(each_length*(id+1))) for id in range(num_strings)]
|
||||||
Read in a section of rom with a given structure, output a list
|
|
||||||
'''
|
|
||||||
out = [hex(offset, 6)]
|
|
||||||
j = 0
|
|
||||||
for title, length, handler in structure:
|
|
||||||
val = indirect(rom, offset+j, length=length)
|
|
||||||
if callable(handler):
|
|
||||||
out.append(handler(val))
|
|
||||||
elif handler and val < len(handler):
|
|
||||||
out.append(handler[val])
|
|
||||||
else:
|
|
||||||
out.append(hex(val, length*2))
|
|
||||||
j += length
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def decompress_lzss(rom, start, header=False, length=None):
|
def get_contiguous_address_slices(rom: bytes, slices, indirect_offset: int = 0) -> list:
|
||||||
'''
|
pointers = [indirect2(rom, s) + indirect_offset for s in slices]
|
||||||
Algorithm from http://slickproductions.org/slickwiki/index.php/Noisecross:Final_Fantasy_V_Compression
|
output = []
|
||||||
'''
|
for ptr, ptr_next in zip(pointers[:-1], pointers[1:]):
|
||||||
ptr = start
|
if ptr_next < ptr:
|
||||||
if length:
|
break
|
||||||
uncompressed_length = length
|
start = memory_address_to_rom_address(ptr)
|
||||||
else:
|
end = memory_address_to_rom_address(ptr_next)
|
||||||
uncompressed_length = indirect(rom, start)
|
output.append(slice(start, end))
|
||||||
ptr += 2
|
return output
|
||||||
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_lzss_FFVa(rom, start, header=False, length=None):
|
def get_bytestring_slices(rom: bytes, start_address: int, each_length: int, num_strings: int, indirect_offset=None, indirect_null_terminated=False) -> list:
|
||||||
'''
|
if indirect_offset is not None:
|
||||||
Oops, it's just GBA BIOS decompression functions
|
if not indirect_null_terminated:
|
||||||
see https://web.archive.org/web/20130323133944/http://nocash.emubase.de/gbatek.htm#biosdecompressionfunctions
|
pointers = [indirect(rom, address, each_length) + indirect_offset for address in range(start_address, start_address + (each_length * num_strings), each_length)]
|
||||||
'''
|
output = []
|
||||||
ptr = start
|
for ptr, ptr_next in zip(pointers[:-1], pointers[1:]):
|
||||||
if length:
|
if ptr_next < ptr:
|
||||||
uncompressed_length = length
|
break
|
||||||
else:
|
start = memory_address_to_rom_address(ptr)
|
||||||
uncompressed_length = indirect(rom, start, endian='big')
|
end = memory_address_to_rom_address(ptr_next)
|
||||||
ptr += 2
|
output.append(slice(start, end))
|
||||||
output = []
|
return output
|
||||||
while len(output) < uncompressed_length:
|
else:
|
||||||
bitmap_byte = rom[ptr]
|
def get_indirect(address: int) -> bytes:
|
||||||
ptr += 1
|
ptr = memory_address_to_rom_address(indirect(rom, address, each_length) + indirect_offset)
|
||||||
for i in reversed(range(8)):
|
# While previously we used the start address of the next string, this is not necessarily correct.
|
||||||
bit = (bitmap_byte >> i) & 1
|
# Scan for a zero-byte end-of-string marker and pay the extra cycles.
|
||||||
if not bit:
|
end_ptr = ptr
|
||||||
b = rom[ptr]
|
# print(ptr)
|
||||||
ptr += 1
|
while rom[end_ptr] > 0:
|
||||||
output.append(b)
|
end_ptr += 1
|
||||||
else:
|
return slice(ptr, end_ptr)
|
||||||
b1 = rom[ptr]
|
return [get_indirect(start_address+(each_length*id)) for id in range(num_strings)]
|
||||||
b2 = rom[ptr+1]
|
else:
|
||||||
ptr += 2
|
return [slice(start_address+(each_length*id), start_address+(each_length*(id+1))) for id in range(num_strings)]
|
||||||
length = ((b1 & 0xF0) >> 4) + 3
|
|
||||||
trackback = -1 - (b2 + ((b1 & 0x0F) << 8))
|
|
||||||
try:
|
|
||||||
for j in range(length):
|
|
||||||
output.append(output[trackback])
|
|
||||||
except:
|
|
||||||
print(len(output), f'0x{ptr:X}', f'0x{b1:02X}{b2:02X}', trackback)
|
|
||||||
raise
|
|
||||||
print(f'0x{ptr:X}')
|
|
||||||
return bytes(output[:uncompressed_length])
|
|
||||||
|
|
||||||
|
|
||||||
def findall(rom, string):
|
def get_bytestrings(rom: bytes, start_address: int, each_length: int, num_strings: int, indirect_offset=None, indirect_null_terminated=False) -> list[bytes]:
|
||||||
results = []
|
return [rom[s] for s in get_bytestring_slices(rom, start_address, each_length, num_strings, indirect_offset, indirect_null_terminated)]
|
||||||
start = 0
|
|
||||||
while True:
|
|
||||||
val = rom.find(string, start)
|
|
||||||
if val < 0:
|
|
||||||
return results
|
|
||||||
results.append(val)
|
|
||||||
start = val + 1
|
|
||||||
|
|
||||||
|
|
||||||
def parse_ips(data):
|
def load_table(filename: str) -> tuple[str]:
|
||||||
assert data[:5] == b'PATCH' and data[-3:] == b'EOF', 'File header and footer missing!'
|
with open(filename, 'r') as f:
|
||||||
patches = {}
|
return tuple(literal_eval(f'"{line}"') if line.startswith('\\') else line for line in f.read().rstrip('\n').split('\n'))
|
||||||
ptr = 5
|
|
||||||
while ptr < len(data)-6:
|
def __cast_string_to_object(input: str) -> object:
|
||||||
address = int.from_bytes(data[ptr:ptr+3], 'big')
|
if len(input) == 0:
|
||||||
length = int.from_bytes(data[ptr+3:ptr+5], 'big')
|
return None
|
||||||
if length > 0:
|
try:
|
||||||
payload = data[ptr+5:ptr+5+length]
|
return literal_eval(input)
|
||||||
ptr += 5 + length
|
except:
|
||||||
else:
|
return input # Unescaped string
|
||||||
repeats = data[ptr+5:ptr+7]
|
|
||||||
payload = data[ptr+7] * repeats
|
def load_tsv(filename: str) -> dict[dict[str, str]]:
|
||||||
ptr += 8
|
with open(filename, 'r') as f:
|
||||||
patches[address] = payload
|
header, *lines = f.read().rstrip('\n').split('\n')
|
||||||
return patches
|
first_column_name, *headers = header.split('\t')
|
||||||
|
# Cheeky Py3.8 one-liner
|
||||||
|
# return {(s := line.split('\t'))[0]:dict(zip(headers, s[1:])) for line in lines}
|
||||||
|
output = {}
|
||||||
|
for line in lines:
|
||||||
|
name, *values = line.split('\t')
|
||||||
|
output[name] = dict((h,__cast_string_to_object(v)) for h,v in zip(headers, values) if len(v) > 0)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def load_raw(filename: str) -> bytes:
|
||||||
with open('2564 - Final Fantasy V Advance (U)(Independent).gba', 'rb') as file:
|
with open(filename, 'rb') as f:
|
||||||
ROM = file.read()
|
return f.read()
|
||||||
landmark = ROM.find(b'FINAL FANTASY V ADVANCE SYGMAB')
|
|
||||||
try:
|
|
||||||
with open('Final Fantasy V Advance (Europe) (En,Fr,De,Es,It)-spritehack.ips', 'rb') as file:
|
def parse_struct(rom: int, offset: int, structure: list[tuple[str, int, object]]):
|
||||||
spritehack_ips = file.read()
|
'''
|
||||||
print('spritehack_ips loaded')
|
Read in a section of rom with a given structure, output a list
|
||||||
except:
|
'''
|
||||||
pass
|
out = [hex(offset, 6)]
|
||||||
|
j = 0
|
||||||
|
for title, length, handler in structure:
|
||||||
|
val = indirect(rom, offset+j, length=length)
|
||||||
|
if callable(handler):
|
||||||
|
out.append(handler(val))
|
||||||
|
elif handler and val < len(handler):
|
||||||
|
out.append(handler[val])
|
||||||
|
else:
|
||||||
|
out.append(hex(val, length*2))
|
||||||
|
j += length
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def decompress_lzss(rom: bytes, start: int, header: bool = False, length=None) -> bytes:
|
||||||
|
'''
|
||||||
|
Algorithm from http://slickproductions.org/slickwiki/index.php/Noisecross:Final_Fantasy_V_Compression
|
||||||
|
'''
|
||||||
|
ptr = start
|
||||||
|
if length:
|
||||||
|
uncompressed_length = length
|
||||||
|
else:
|
||||||
|
uncompressed_length = indirect(rom, start)
|
||||||
|
ptr += 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_lzss_FFVa(rom: bytes, start: int, header: bool = False, length=None) -> bytes:
|
||||||
|
'''
|
||||||
|
Oops, it's just GBA BIOS decompression functions
|
||||||
|
see https://web.archive.org/web/20130323133944/http://nocash.emubase.de/gbatek.htm#biosdecompressionfunctions
|
||||||
|
'''
|
||||||
|
ptr = start
|
||||||
|
if length:
|
||||||
|
uncompressed_length = length
|
||||||
|
else:
|
||||||
|
uncompressed_length = indirect(rom, start, endian='big')
|
||||||
|
ptr += 2
|
||||||
|
output = []
|
||||||
|
while len(output) < uncompressed_length:
|
||||||
|
bitmap_byte = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
for i in reversed(range(8)):
|
||||||
|
bit = (bitmap_byte >> i) & 1
|
||||||
|
if not bit:
|
||||||
|
b = rom[ptr]
|
||||||
|
ptr += 1
|
||||||
|
output.append(b)
|
||||||
|
else:
|
||||||
|
b1 = rom[ptr]
|
||||||
|
b2 = rom[ptr+1]
|
||||||
|
ptr += 2
|
||||||
|
length = ((b1 & 0xF0) >> 4) + 3
|
||||||
|
trackback = -1 - (b2 + ((b1 & 0x0F) << 8))
|
||||||
|
try:
|
||||||
|
for j in range(length):
|
||||||
|
output.append(output[trackback])
|
||||||
|
except:
|
||||||
|
print(len(output), f'0x{ptr:X}', f'0x{b1:02X}{b2:02X}', trackback)
|
||||||
|
raise
|
||||||
|
print(f'0x{ptr:X}')
|
||||||
|
return bytes(output[:uncompressed_length])
|
||||||
|
|
||||||
|
|
||||||
|
def findall(rom: bytes, string: str) -> list[int]:
|
||||||
|
results = []
|
||||||
|
start = 0
|
||||||
|
while True:
|
||||||
|
val = rom.find(string, start)
|
||||||
|
if val < 0:
|
||||||
|
return results
|
||||||
|
results.append(val)
|
||||||
|
start = val + 1
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ips(data: bytes):
|
||||||
|
assert data[:5] == b'PATCH' and data[-3:] == b'EOF', 'File header and footer missing!'
|
||||||
|
patches = {}
|
||||||
|
ptr = 5
|
||||||
|
while ptr < len(data)-6:
|
||||||
|
address = int.from_bytes(data[ptr:ptr+3], 'big')
|
||||||
|
length = int.from_bytes(data[ptr+3:ptr+5], 'big')
|
||||||
|
if length > 0:
|
||||||
|
payload = data[ptr+5:ptr+5+length]
|
||||||
|
ptr += 5 + length
|
||||||
|
else:
|
||||||
|
repeats = data[ptr+5:ptr+7]
|
||||||
|
payload = data[ptr+7] * repeats
|
||||||
|
ptr += 8
|
||||||
|
patches[address] = payload
|
||||||
|
return patches
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
import os
|
import os
|
||||||
from array import array
|
from array import array
|
||||||
from struct import unpack
|
from struct import unpack
|
||||||
import includes.const as const
|
import includes.ff5.const as ff5const
|
||||||
|
|
||||||
pyqt_version = 0
|
pyqt_version = 0
|
||||||
skip_pyqt5 = "PYQT4" in os.environ
|
skip_pyqt5 = "PYQT4" in os.environ
|
||||||
|
@ -155,7 +155,7 @@ def create_tritile(data):
|
||||||
img = QImage(16, 12, QImage.Format_Indexed8)
|
img = QImage(16, 12, QImage.Format_Indexed8)
|
||||||
imgbits = img.bits()
|
imgbits = img.bits()
|
||||||
imgbits.setsize(img.byteCount())
|
imgbits.setsize(img.byteCount())
|
||||||
img.setColorTable(const.dialogue_palette)
|
img.setColorTable(ff5const.dialogue_palette)
|
||||||
tile = array('B', range(192))
|
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))]:
|
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)] = (data[row + (p*12)] >> b & 1)
|
tile[(7-b) + (row*16) + (p*8)] = (data[row + (p*12)] >> b & 1)
|
||||||
|
@ -177,7 +177,7 @@ def create_quadtile(data, ltr=False):
|
||||||
del painter
|
del painter
|
||||||
return QPixmap.fromImage(img)
|
return QPixmap.fromImage(img)
|
||||||
|
|
||||||
def generate_glyphs(rom, offset, num=0x100, palette=const.small_palette):
|
def generate_glyphs(rom, offset, num=0x100, palette=ff5const.small_palette):
|
||||||
spritelist = []
|
spritelist = []
|
||||||
for i in range(num):
|
for i in range(num):
|
||||||
j = offset + (i*16)
|
j = offset + (i*16)
|
||||||
|
|
|
@ -25,7 +25,7 @@ Trailing 8 bytes are 16 4bit nibbles that make up the compressed samples.
|
||||||
import sys
|
import sys
|
||||||
from midiutil import MIDIFile
|
from midiutil import MIDIFile
|
||||||
from includes.helpers import indirect, hex
|
from includes.helpers import indirect, hex
|
||||||
from includes.const import BGM_Tracks_Safe
|
from includes.ff5.const import BGM_Tracks_Safe
|
||||||
import struct
|
import struct
|
||||||
import wave
|
import wave
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue