ChocolateBird/scripts/loaders/StringLoader.gd

137 lines
4.8 KiB
GDScript

extends Node
const ff5_dialog := preload('res://scripts/loaders/snes/ff5_dialog.gd')
var SNES_block_addresses := Common.load_tsv('res://data/string_blocks.tsv')
var tables_raw = {}
var tables = {}
var glyph_tables := {
'RPGe_dialog': Common.load_glyph_table('res://data/glyph_tables/Glyphs_dialog_RPGe.txt'),
'RPGe_small': Common.load_glyph_table('res://data/glyph_tables/Glyphs_small_RPGe.txt'),
'SNES_dialog': Common.load_glyph_table('res://data/glyph_tables/Glyphs_dialog_SNES.txt'),
'SNES_kanji': Common.load_glyph_table('res://data/glyph_tables/Glyphs_dialog_kanji_SNES.txt'),
'SNES_small': Common.load_glyph_table('res://data/glyph_tables/Glyphs_small_SNES.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:
for block_name in SNES_block_addresses:
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