From 877c5d90e46ac99a2840cfb22e89dac3b5e17d4d Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Mon, 7 Aug 2023 01:17:45 +0930 Subject: [PATCH] Use our save struct DSL for some ROM things --- data/SNES_PSX_addresses.tsv | 39 +++++++++--------- scripts/loaders/RomLoader.gd | 27 +++++++++++++ scripts/loaders/SpriteLoader.gd | 4 +- scripts/loaders/snes/graphics.gd | 8 ++++ scripts/loaders/snes/structs.gd | 68 ++++++++++++++++++++++++++++++++ scripts/struct.gd | 4 +- 6 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 scripts/loaders/snes/structs.gd diff --git a/data/SNES_PSX_addresses.tsv b/data/SNES_PSX_addresses.tsv index 8fac894..fbe21a7 100644 --- a/data/SNES_PSX_addresses.tsv +++ b/data/SNES_PSX_addresses.tsv @@ -1,23 +1,24 @@ Label SNES PSX_file PSX_offset format Comment -locations_bg_palettes 0x03BB00 /nar/ff5_binx.bin 0x03BF80 -worldmap_blocks 0x0FF0C0 /nar/ff5_binx.bin 0x040300 -worldmap_tile_palettes 0x0FF9C0 /nar/ff5_bin3.bin 0x03FB00 -worldmap_palettes 0x0FFCC0 /nar/ff5_binx.bin 0x040000 -worldmap_tiles 0x1B8000 /nar/ff5_bin3.bin 0x039B00 -character_battle_sprite_tiles 0x120000 /mnu/men_bin.eng 0x010200 -character_battle_sprite_palettes 0x14A3C0 /btl/ff5_btl.bin 0x0273C0 Also /mnu/men_bin.eng:0x03A5C0 -character_battle_sprite_layouts 0x14B997 /btl/ff5_btl.bin 0x028997 -character_battle_sprite_disabled_palette 0x00F867 /mnu/memsave.bin 0x000034 -character_battle_sprite_stone_palette 0x00F807 N/A N/A Also 0x199835 -tiles_fist 0x11D710 /btl/ff5_btl.bin 0x021D10 Also /mnu/men_bin.eng:0x00D910 -tbl_weapons 0x110000 length 0x600 -tbl_armors 0x110600 length 0x480 -tbl_items 0x110A80 length 0x100 -tbl_magic 0x110B80 length 0x800 -tbl_equip_types 0x112480 64 of ... length 0x100 - Item Equipment type definitions (64x4 bytes, 2B weapon, 2B armor) -tbl_armors_elem_def 0x112580 64 of ... length 0x140 - Item Armor Element defense 64x(5B - absorb, evade, immunity, half, weakness) -tbl_armors_status 0x1126C0 64 of ... length 0x1C0 - Item Armor Status defense 64x(7B - 4B Initial, 3B Immune) -tbl_item_magic_prices 0x112A00 length 0x300 +locations_bg_palettes 0x03BB00 /nar/ff5_binx.bin 0x03BF80 43 of Palette128Of555 +worldmap_blocks 0x0FF0C0 /nar/ff5_binx.bin 0x040300 3 of 192 of 4 of u8 +worldmap_tiles.bias 0x0FF9C0 /nar/ff5_bin3.bin 0x03FB00 3 of 256 of u8 Add to each pixel of the mode7c tiles +worldmap_palettes 0x0FFCC0 /nar/ff5_binx.bin 0x040000 3 of Palette128Of555 +worldmap_tiles 0x1B8000 /nar/ff5_bin3.bin 0x039B00 3 of 256 of TileSNESMode7c Add the biases +character_battle_sprite_tiles 0x120000 /mnu/men_bin.eng 0x010200 5 of 22 of 48 of TileSNES4bpp +character_battle_sprite_palettes 0x14A3C0 /btl/ff5_btl.bin 0x0273C0 5 of 22 of Palette16Of555 Also /mnu/men_bin.eng:0x03A5C0 +character_battle_sprite_layouts 0x14B997 /btl/ff5_btl.bin 0x028997 11 of 6 of u8 +character_battle_sprite_disabled_palette 0x00F867 /mnu/memsave.bin 0x000034 Palette16Of555 +character_battle_sprite_stone_palette 0x00F807 N/A N/A Palette16Of555 Also 0x199835 +tiles_fist 0x11D710 /btl/ff5_btl.bin 0x021D10 TileSNES3bpp Also /mnu/men_bin.eng:0x00D910 +tbl_weapons 0x110000 128 of 12 of u8 length 0x600 +tbl_armors 0x110600 96 of 12 of u8 length 0x480 +tbl_items 0x110A80 32 of 8 of u8 length 0x100 +tbl_magic 0x110B80 256 of 8 of u8 length 0x800 +tbl_equip_types 0x112480 64 of 4 of u8 length 0x100 - Item Equipment type definitions (64x4 bytes, 2B weapon, 2B armor) +tbl_armors_elem_def 0x112580 64 of 5 of u8 length 0x140 - Item Armor Element defense 64x(5B - absorb, evade, immunity, half, weakness) +tbl_armors_status 0x1126C0 64 of 7 of u8 length 0x1C0 - Item Armor Status defense 64x(7B - 4B Initial, 3B Immune) +tbl_prices_items 0x112A00 256 of ItemCost length 0x200 +tbl_prices_magic 0x112C00 128 of ItemCost length 0x100 tbl_charlevels_exp 0x115000 99 of u24 tbl_charlevels_hp_base 0x115129 99 of u16 tbl_charlevels_mp_base 0x1151EF 99 of u16 diff --git a/scripts/loaders/RomLoader.gd b/scripts/loaders/RomLoader.gd index be2af51..6471766 100644 --- a/scripts/loaders/RomLoader.gd +++ b/scripts/loaders/RomLoader.gd @@ -1,5 +1,8 @@ extends Node +const STRUCT := preload('res://scripts/struct.gd') +const STRUCT_SNES := preload('res://scripts/loaders/snes/structs.gd') +var structdefs := {} const loader_cd_image := preload('res://scripts/loaders/cd/image.gd') var psx_productcode_regex := RegEx.new() const psx_ff5_productcodes = [ @@ -13,15 +16,36 @@ var ROM_filename := 'FF5_SCC_WepTweaks_Inus_Dash.sfc' # 'Final Fantasy V (Japan var GBA_filename := '2564 - Final Fantasy V Advance (U)(Independent).gba' var rom_snes := File.new() +var snes_data := {} var thread := Thread.new() func load_snes_rom(filename: String): var error := rom_snes.open(filename, File.READ) if error == OK: + # Copy entire SNES ROM to a buffer for StreamPeerBuffer usage. + # Unfortunately, the File API is different and slightly worse than the StreamPeer API. + var bytes := rom_snes.get_buffer(rom_snes.get_len()) + var buffer = StreamPeerBuffer.new() + buffer.data_array = bytes SpriteLoader.load_snes_rom(rom_snes) MapLoader.load_snes_rom(rom_snes) StringLoader.load_snes_rom(rom_snes, true) + # Don't do this concurrently, avoid file pointer conflicts var _thread_error = thread.start(SoundLoader, 'parse_rom', rom_snes) + # Can concurrently work with the preloaded StreamPeerBuffer though + for key in Common.SNES_PSX_addresses: + var d = Common.SNES_PSX_addresses[key] + if d.format: + var s: STRUCT.StructType + if d.format in structdefs: + s = structdefs[d.format] + else: + s = STRUCT.get_structarraytype(d.format, structdefs) + structdefs[d.format] = s + if not s: + assert(false, 'Invalid StructType: "%s"' % d.format) + buffer.seek(d.SNES) + snes_data[key] = s.get_value(buffer, [0, 0]) func load_psx_folder(_dirname: String): pass @@ -42,6 +66,9 @@ func load_psx_image(filename: String): func _ready(): + structdefs.merge(STRUCT.get_base_structarraytypes()) + structdefs.merge(STRUCT_SNES.get_structtypes()) + STRUCT.parse_struct_definitions_from_tsv_filename('res://data/SNES_save.tsv', structdefs) var _error := psx_productcode_regex.compile('(S[A-Z]{3}_\\d{3}\\.\\d{2});(\\d)') load_snes_rom(ROM_filename) diff --git a/scripts/loaders/SpriteLoader.gd b/scripts/loaders/SpriteLoader.gd index c09be97..549e46a 100644 --- a/scripts/loaders/SpriteLoader.gd +++ b/scripts/loaders/SpriteLoader.gd @@ -143,7 +143,7 @@ func snes_load_worldmap(rom: File): worldmap_palette_textures.append(texture_from_image(image)) var tile_palettes = [] - rom.seek(Common.SNES_PSX_addresses['worldmap_tile_palettes']['SNES'] + (world_ts*0x100)) + rom.seek(Common.SNES_PSX_addresses['worldmap_tiles.bias']['SNES'] + (world_ts*0x100)) for pal in 256: tile_palettes.append(rom.get_8()) @@ -264,7 +264,7 @@ func snes_load_map_sprites(rom: File): func load_snes_rom(rom: File): snes_load_battle_sprites(rom) snes_load_worldmap(rom) - snes_load_map_sprites(rom) + # snes_load_map_sprites(rom) const gba_marker := 'FINAL FANTASY V ADVANCE SYGMAB' diff --git a/scripts/loaders/snes/graphics.gd b/scripts/loaders/snes/graphics.gd index fc64558..863f5c7 100644 --- a/scripts/loaders/snes/graphics.gd +++ b/scripts/loaders/snes/graphics.gd @@ -73,3 +73,11 @@ static func get_tile(rom: File, offset: int, length: int) -> Image: return _2plane_to_tile(data) _: return _1plane_to_tile(data) + +static func bgr555_to_color(short: int) -> Color: + var color = Color() + color.a = 1 + color.r = ((short & 0x1F) / 31.0) + color.g = (((short >> 5) & 0x1F) / 31.0) + color.b = (((short >> 10) & 0x1F) / 31.0) + return color diff --git a/scripts/loaders/snes/structs.gd b/scripts/loaders/snes/structs.gd new file mode 100644 index 0000000..fe1d46c --- /dev/null +++ b/scripts/loaders/snes/structs.gd @@ -0,0 +1,68 @@ +#warning-ignore-all:shadowed_variable +#warning-ignore-all:unused_argument +const STRUCT := preload('res://scripts/struct.gd') +const GRAPHICS := preload('res://scripts/loaders/snes/graphics.gd') + +class TileSNESMode7 extends STRUCT.StructType: + func get_value(buffer: StreamPeer, leftover_bits: Array): + return GRAPHICS.mode7_to_tile(buffer.get_data(64)) + +class TileSNESMode7c extends STRUCT.StructType: + # Note that this will give a palette offset of 0, you will need to postprocess the values. + func get_value(buffer: StreamPeer, leftover_bits: Array): + return GRAPHICS.mode7_compressed_to_tile(buffer.get_data(32)[1]) + +class TileSNES4bpp extends STRUCT.StructType: + func get_value(buffer: StreamPeer, leftover_bits: Array): + return GRAPHICS._4plane_to_tile(buffer.get_data(32)[1]) + +class TileSNES3bpp extends STRUCT.StructType: + func get_value(buffer: StreamPeer, leftover_bits: Array): + return GRAPHICS._3plane_to_tile(buffer.get_data(24)[1]) + +class TileSNES2bpp extends STRUCT.StructType: + func get_value(buffer: StreamPeer, leftover_bits: Array): + return GRAPHICS._2plane_to_tile(buffer.get_data(16)[1]) + +class TileSNES1bpp extends STRUCT.StructType: + func get_value(buffer: StreamPeer, leftover_bits: Array): + return GRAPHICS._1plane_to_tile(buffer.get_data(8)[1]) + +class ColorBGR555 extends STRUCT.StructType: + func get_value(buffer: StreamPeer, leftover_bits: Array): + return GRAPHICS.bgr555_to_color(buffer.get_u16()) + +class PaletteOf555 extends STRUCT.StructType: + var length: int = 16 + func _init(length: int): + self.length = length + func get_value(buffer: StreamPeer, leftover_bits: Array): + var array := PoolColorArray() + for i in self.length: + array.append(GRAPHICS.bgr555_to_color(buffer.get_u16())) + return array + +class ItemCost extends STRUCT.StructType: + func get_value(buffer: StreamPeer, leftover_bits: Array): + var b0 := buffer.get_u8() + var mantissa := buffer.get_u8() + var exponent := b0 & 0x3F + var is_sellable := (b0 & 0x80) == 0 + var is_sellprice_5gil := (b0 & 0x40) == 0x40 + var buy_price := mantissa * int(pow(10, exponent)) + var sell_price := -1 if not is_sellable else (5 if is_sellprice_5gil else buy_price/2) + return {'buy_price': buy_price, 'sell_price': sell_price} + +static func get_structtypes() -> Dictionary: + return { + 'TileSNESMode7': TileSNESMode7.new(), + 'TileSNESMode7c': TileSNESMode7c.new(), + 'TileSNES4bpp': TileSNES4bpp.new(), + 'TileSNES3bpp': TileSNES3bpp.new(), + 'TileSNES2bpp': TileSNES2bpp.new(), + 'TileSNES1bpp': TileSNES1bpp.new(), + 'ColorBGR555': ColorBGR555.new(), + 'Palette16Of555': PaletteOf555.new(16), + 'Palette128Of555': PaletteOf555.new(128), + 'ItemCost': ItemCost.new(), + } diff --git a/scripts/struct.gd b/scripts/struct.gd index 60415b9..f386b23 100644 --- a/scripts/struct.gd +++ b/scripts/struct.gd @@ -4,9 +4,9 @@ # array is used for reference semantics as get and put operations may mutate it class StructType: func get_value(buffer: StreamPeer, leftover_bits: Array): - return + assert(false, 'Deserialization not implemented') func put_value(buffer: StreamPeer, value, leftover_bits: Array): - return + assert(false, 'Serialization not implemented') class U8 extends StructType: func get_value(buffer: StreamPeer, leftover_bits: Array):