extends Node signal loading_stage_updated(stage) # String const ff5_dialog := preload('res://scripts/loaders/snes/ff5_dialog.gd') var SNES_block_addresses := Common.load_tsv('res://data/5/string_blocks.tsv') var tables_raw = {} var tables = {} var glyph_tables := { 'RPGe_dialog': Common.load_glyph_table('res://data/5/glyphs/SNES_dialog_RPGe.txt'), 'RPGe_small': Common.load_glyph_table('res://data/5/glyphs/SNES_small_RPGe.txt'), 'SNES_dialog': Common.load_glyph_table('res://data/5/glyphs/SNES_dialog.txt'), 'SNES_kanji': Common.load_glyph_table('res://data/5/glyphs/SNES_dialog_kanji.txt'), 'SNES_small': Common.load_glyph_table('res://data/5/glyphs/SNES_small.txt'), } static func decode_string(bytes, glyph_table: PoolStringArray, trim_trailing_whitespace: bool = true) -> String: # Trivial conversion for small text # Dialog requires multibyte handling including kanji and other macros var output = '' for c in bytes: output += glyph_table[c] return output.trim_suffix(' ') if trim_trailing_whitespace else output func decode_array(array, glyph_table, trim_trailing_whitespace: bool = true) -> PoolStringArray: if glyph_table is String: glyph_table = glyph_tables[glyph_table] var output = PoolStringArray() for s in array: output.append(decode_string(s, glyph_table, trim_trailing_whitespace)) return output func _load_block(block_name: String, buffer: StreamPeerBuffer, is_RPGe: bool = false) -> void: var block: Dictionary = SNES_block_addresses[block_name] var glyph_table_small: PoolStringArray = glyph_tables.RPGe_small if is_RPGe else glyph_tables.SNES_small var glyph_table_dialog: PoolStringArray = glyph_tables.RPGe_dialog if is_RPGe else glyph_tables.SNES_dialog var glyph_table_kanji: PoolStringArray = glyph_tables.SNES_kanji var raw_strings := [] var strings := PoolStringArray() var num_entries: int = block.num_entries var l1_width: int = block.bytes if (not is_RPGe) and block.snes_bytes: l1_width = block.snes_bytes var l1_address: int = block.address if (not is_RPGe) and block.snes_address: l1_address = block.snes_address buffer.seek(l1_address) var ptr_offset = block.rpge_ptr_offset if is_RPGe else block.snes_ptr_offset if ptr_offset is int: var ptrs = PoolIntArray() match l1_width: 1: for i in num_entries: ptrs.append(ptr_offset + buffer.get_u8()) 2: for i in num_entries: ptrs.append(ptr_offset + buffer.get_u16()) # Bank wrapping 3: for i in num_entries: ptrs.append(ptr_offset + buffer.get_u16() + (buffer.get_u8() << 16)) # Bank wrapping _: assert(false, 'Indirect l1_width of %d is not possible' % l1_width) if block.null_terminated: for i in num_entries: buffer.seek(ptrs[i] & 0x3FFFFF) # Bank wrapping var bytes = PoolByteArray() while true: var b = buffer.get_u8() if b == 0: break bytes.append(b) raw_strings.append(bytes) else: for i in num_entries-1: var size: int = ptrs[i+1] - ptrs[i] if size > 0: buffer.seek(ptrs[i] & 0x3FFFFF) # Bank wrapping raw_strings.append(buffer.get_data(size)[1]) elif size == 0: raw_strings.append(PoolByteArray()) else: print_debug('String pointer mismatch: "%s" index %d: 0x%06X:0x%06X, effective size of %d bytes' % [block_name, i, ptrs[i], ptrs[i+1], size]) break else: # Get first level of data for i in num_entries: raw_strings.append(buffer.get_data(l1_width)[1]) # Decode if block.dialog: for raw in raw_strings: strings.append(ff5_dialog.decode_string_dialog(raw, glyph_table_dialog, glyph_table_kanji)) else: for raw in raw_strings: strings.append(decode_string(raw, glyph_table_small)) self.tables_raw[block_name] = raw_strings self.tables[block_name] = strings func load_snes_rom(buffer: StreamPeerBuffer, is_RPGe: bool = false) -> void: emit_signal('loading_stage_updated', 'load_snes_rom called') for block_name in SNES_block_addresses: emit_signal('loading_stage_updated', 'Loading string block "%s"'%block_name) var scenetree := get_tree() if scenetree: yield(scenetree, 'idle_frame') self._load_block(block_name, buffer, is_RPGe) func get_ability_name(id: int) -> String: if id < 128: return self.tables.battle_commands[id] else: return self.tables.ability_names[id-128] func get_ability_desc(id: int) -> String: # TODO: revisit for GBA if id < 78: return self.tables.job_and_ability_descs[id+22] elif (id >= 128) and (id <= 161): return self.tables.job_and_ability_descs[(id-128)+100] else: assert(false, 'ability id %d out of description ranges' % id) return 'ability id %d out of description ranges' % id func get_inventory_item_desc(id: int) -> String: var desc_id := 0 if id < 0x80: desc_id = RomLoader.snes_data.tbl_weapons[id].description elif id < 0xE0: desc_id = RomLoader.snes_data.tbl_armors[id-0x80].description else: desc_id = RomLoader.snes_data.tbl_items[id-0xE0].description return self.tables.item_descriptions[desc_id] func get_job_name(id: int) -> String: return self.tables.job_names[id] func get_job_desc(id: int) -> String: return self.tables.job_and_ability_descs[id] func _ready() -> void: pass