diff --git a/data/SNES_PSX_addresses.tsv b/data/SNES_PSX_addresses.tsv new file mode 100644 index 0000000..c9df8e1 --- /dev/null +++ b/data/SNES_PSX_addresses.tsv @@ -0,0 +1,12 @@ +Label SNES PSX 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 Also 0x199835 +tiles_fist 0x11D710 /btl/ff5_btl.bin:0x021D10 Also /mnu/men_bin.eng:0x00D910 diff --git a/globals.gd b/globals.gd index 301de4d..99e8003 100644 --- a/globals.gd +++ b/globals.gd @@ -1,5 +1,7 @@ extends Node +const INDEX_FORMAT := Image.FORMAT_L8 + var time = 0.0 var time_mult = 1.0 diff --git a/scripts/loaders/common.gd b/scripts/loaders/common.gd index da8cf9b..d862607 100644 --- a/scripts/loaders/common.gd +++ b/scripts/loaders/common.gd @@ -1,6 +1,6 @@ extends Node -func load_json(filename: String) -> Dictionary: +static func load_json(filename: String) -> Dictionary: var file := File.new() var error := file.open(filename, File.READ) if error == OK: @@ -11,3 +11,28 @@ func load_json(filename: String) -> Dictionary: print_debug(result.error_string) print_debug(result.error_line) return {} + +static func load_tsv(filename: String, delimiter: String = '\t') -> Dictionary: + var file := File.new() + var error := file.open(filename, File.READ) + if error == OK: + var headers := file.get_csv_line(delimiter) + var n := headers.size() + var output = {} + while file.get_position() < file.get_len(): + var line := file.get_csv_line(delimiter) + var entry := {} + for i in range(1, n): + if line.size() > i: + var token := line[i] + entry[headers[i]] = token + if token.begins_with('0x'): + var hex := token.hex_to_int() + if hex > 0: + entry[headers[i]] = hex + output[line[0]] = entry + return output + print_debug(error) + return {} + +var SNES_PSX_addresses := load_tsv('res://data/SNES_PSX_addresses.tsv') diff --git a/scripts/loaders/gba/graphics.gd b/scripts/loaders/gba/graphics.gd new file mode 100644 index 0000000..02bac05 --- /dev/null +++ b/scripts/loaders/gba/graphics.gd @@ -0,0 +1,15 @@ +const INDEX_FORMAT := globals.INDEX_FORMAT + +static func ByteArray(size: int) -> PoolByteArray: + var arr := PoolByteArray() + arr.resize(size) + return arr + +static func _4bpp_to_tile(data: PoolByteArray) -> Image: + var tdata := ByteArray(64) + for i in range(32): + tdata[i*2] = data[i] % 16 + tdata[i*2+1] = data[i] / 16 + var tile := Image.new() + tile.create_from_data(8, 8, false, INDEX_FORMAT, tdata) + return tile diff --git a/scripts/loaders/snes/graphics.gd b/scripts/loaders/snes/graphics.gd new file mode 100644 index 0000000..fc64558 --- /dev/null +++ b/scripts/loaders/snes/graphics.gd @@ -0,0 +1,75 @@ +var INDEX_FORMAT := globals.INDEX_FORMAT + +static func ByteArray(size: int) -> PoolByteArray: + var arr := PoolByteArray() + arr.resize(size) + return arr + +static func mode7_to_tile(data: PoolByteArray) -> Image: + # Easy one, it's just straight data left-to-right, top-to-bottom + var tile := Image.new() + tile.create_from_data(8, 8, false, Image.FORMAT_L8, data) + return tile + +static func mode7_compressed_to_tile(data: PoolByteArray, tile_palette: int = 0) -> Image: + # 4 bits per pixel. + var tdata := ByteArray(64) + for i in 32: + var b := data[i] + var j: int = i*2 + tdata[j] = (b & 0x0F) | tile_palette + tdata[j+1] = (b >> 4) | tile_palette + return mode7_to_tile(tdata) + +static func _4plane_to_tile(data: PoolByteArray) -> Image: + var tdata := ByteArray(64) + for i in range(64): + var j = (i/8)*2 + var x = 7 - (i%8) + tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) | ((data[j+16] >> x & 1)<<2) | ((data[j+17] >> x & 1)<<3) + var tile := Image.new() + tile.create_from_data(8, 8, false, Image.FORMAT_L8, tdata) + return tile + +static func _3plane_to_tile(data: PoolByteArray) -> Image: + var tdata := ByteArray(64) + for i in range(64): + var j = (i/8)*2 + var x = 7 - (i%8) + tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) | ((data[(i/8)+16] >> x & 1)<<2) + var tile := Image.new() + tile.create_from_data(8, 8, false, Image.FORMAT_L8, tdata) + return tile + +static func _2plane_to_tile(data: PoolByteArray) -> Image: + var tdata := ByteArray(64) + for i in range(64): + var j = (i/8)*2 + var x = 7 - (i%8) + tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) + var tile := Image.new() + tile.create_from_data(8, 8, false, Image.FORMAT_L8, tdata) + return tile + +static func _1plane_to_tile(data: PoolByteArray) -> Image: + var tdata := ByteArray(64) + for i in range(64): + var x = 7 - (i%8) + tdata[i] = (data[i/8] >> x & 1) + var tile := Image.new() + tile.create_from_data(8, 8, false, Image.FORMAT_L8, tdata) + return tile + +static func get_tile(rom: File, offset: int, length: int) -> Image: + rom.seek(offset) + var data := rom.get_buffer(length) + var planes := length / 8 + match planes: + 4: + return _4plane_to_tile(data) + 3: + return _3plane_to_tile(data) + 2: + return _2plane_to_tile(data) + _: + return _1plane_to_tile(data) diff --git a/scripts/loaders/sprite_loader.gd b/scripts/loaders/sprite_loader.gd index c7a61a5..1681914 100644 --- a/scripts/loaders/sprite_loader.gd +++ b/scripts/loaders/sprite_loader.gd @@ -1,10 +1,10 @@ extends Node - -var shader_material = load('res://palette_mat.tres') - # Portability for GLES2 vs GLES3 stuff -const INDEX_FORMAT := Image.FORMAT_L8 +const INDEX_FORMAT := globals.INDEX_FORMAT +const snes_graphics := preload('res://scripts/loaders/snes/graphics.gd') +const gba_graphics := preload('res://scripts/loaders/gba/graphics.gd') +var shader_material = load('res://palette_mat.tres') # Offsets @@ -17,23 +17,8 @@ const offset_glyphs_kanji := 0x1BD000 # 0x1AA entries, 1bpp const offset_title_ffv := 0x031243 # Type 02 headed compression, expands to 0x1000 bytes const offset_title_dragon := 0x032D22 # Type 02 compression, expands to 0x1200 bytes const offset_title_dragon_OAM := 0x033342 # Type 02 headed compression, expands to 0x0120 bytes - -const offset_locations_bg_palettes := 0x03BB00 # 2bytes * 128 colors * 43 sets -const offset_worldmap_blocks := 0x0FF0C0 # 0x0FF3C0 0x0FF6C0 -const offset_worldmap_tile_palettes := 0x0FF9C0 # 0x0FFAC0 0x0FFBC0 Length 0x100 -const offset_worldmap_palettes := 0x0FFCC0 # 0x0FFDC0 0x0FFEC0 Length 0x100 -const offset_worldmap_tiles := 0x1B8000 # 0x1BA000 0x1BC000 - -const offset_Character_Battle_Sprite_Tiles := 0x120000 -const offset_Character_Battle_Sprite_Palettes := 0x14A3C0 -const offset_Character_Battle_Sprite_Layouts := 0x14B997 const num_Character_Battle_Sprite_Layouts := 11 -const offset_Character_Battle_Sprite_Disabled_Palette := 0x00F867 -const offset_Character_Battle_Sprite_Stone_Palette := 0x00F807 - -const offset_Tiles_Fist := 0x11D710 # 3bpp tile - # Arrays to store sprites in var worldmap_palette_imgs = [] var worldmap_palette_textures = [] @@ -53,17 +38,17 @@ var character_battle_sprite_palette_disabled_texture: ImageTexture var character_battle_sprite_palette_stone_texture: ImageTexture var weapon_textures = {} -func ByteArray(size: int) -> PoolByteArray: +static func ByteArray(size: int) -> PoolByteArray: var arr := PoolByteArray() arr.resize(size) return arr -func texture_from_image(image: Image, flags: int = 0) -> ImageTexture: +static func texture_from_image(image: Image, flags: int = 0) -> ImageTexture: var tex = ImageTexture.new() tex.create_from_image(image, flags) return tex -func bgr555_to_color(short: int) -> Color: +static func bgr555_to_color(short: int) -> Color: var color = Color() color.a = 1 color.r = ((short & 0x1F) / 31.0) @@ -71,7 +56,7 @@ func bgr555_to_color(short: int) -> Color: color.b = (((short >> 10) & 0x1F) / 31.0) return color -func generate_palette_rgbf(rom: File, offset: int, length: int = 16) -> Image: +static func generate_palette_rgbf(rom: File, offset: int, length: int = 16) -> Image: rom.seek(offset) var img := Image.new() var rows := length/16 @@ -84,7 +69,7 @@ func generate_palette_rgbf(rom: File, offset: int, length: int = 16) -> Image: img.unlock() return img -func generate_palette_rgb8(rom: File, offset: int, length: int = 16) -> Image: +static func generate_palette_rgb8(rom: File, offset: int, length: int = 16) -> Image: # Safe for GLES2 only! # Implicit sRGB -> linear conversion on ImageTexture creation from RGB8 Image ruins this in GLES3 mode rom.seek(offset) @@ -100,7 +85,7 @@ func generate_palette_rgb8(rom: File, offset: int, length: int = 16) -> Image: img.create_from_data(16, length/16, false, Image.FORMAT_RGB8, data) return img -func generate_palette_rgb5_a1(rom: File, offset: int, length: int = 16) -> Image: +static func generate_palette_rgb5_a1(rom: File, offset: int, length: int = 16) -> Image: var data = ByteArray(length*2) rom.seek(offset) for i in range(length): @@ -115,91 +100,12 @@ func generate_palette_rgb5_a1(rom: File, offset: int, length: int = 16) -> Image img.create_from_data(16, length/16, false, Image.FORMAT_RGBA5551, data) return img -func generate_palette(rom: File, offset: int, length: int = 16) -> Image: +static func generate_palette(rom: File, offset: int, length: int = 16) -> Image: if length < 16: length = 16 return generate_palette_rgb8(rom, offset, length) -func gba_4bpp_to_tile(data: PoolByteArray) -> Image: - var tdata := ByteArray(64) - for i in range(32): - tdata[i*2] = data[i] % 16 - tdata[i*2+1] = data[i] / 16 - var tile := Image.new() - tile.create_from_data(8, 8, false, INDEX_FORMAT, tdata) - return tile - -func snes_mode7_to_tile(data: PoolByteArray) -> Image: - # Easy one, it's just straight data left-to-right, top-to-bottom - var tile := Image.new() - tile.create_from_data(8, 8, false, INDEX_FORMAT, data) - return tile - -func snes_mode7_compressed_to_tile(data: PoolByteArray, tile_palette: int = 0) -> Image: - # 4 bits per pixel. - var tdata := ByteArray(64) - for i in 32: - var b := data[i] - var j: int = i*2 - tdata[j] = (b & 0x0F) | tile_palette - tdata[j+1] = (b >> 4) | tile_palette - return snes_mode7_to_tile(tdata) - -func snes_4plane_to_tile(data: PoolByteArray) -> Image: - var tdata := ByteArray(64) - for i in range(64): - var j = (i/8)*2 - var x = 7 - (i%8) - tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) | ((data[j+16] >> x & 1)<<2) | ((data[j+17] >> x & 1)<<3) - var tile := Image.new() - tile.create_from_data(8, 8, false, INDEX_FORMAT, tdata) - return tile - -func snes_3plane_to_tile(data: PoolByteArray) -> Image: - var tdata := ByteArray(64) - for i in range(64): - var j = (i/8)*2 - var x = 7 - (i%8) - tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) | ((data[(i/8)+16] >> x & 1)<<2) - var tile := Image.new() - tile.create_from_data(8, 8, false, INDEX_FORMAT, tdata) - return tile - -func snes_2plane_to_tile(data: PoolByteArray) -> Image: - var tdata := ByteArray(64) - for i in range(64): - var j = (i/8)*2 - var x = 7 - (i%8) - tdata[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1)<<1) - var tile := Image.new() - tile.create_from_data(8, 8, false, INDEX_FORMAT, tdata) - return tile - -func snes_1plane_to_tile(data: PoolByteArray) -> Image: - var tdata := ByteArray(64) - for i in range(64): - var x = 7 - (i%8) - tdata[i] = (data[i/8] >> x & 1) - var tile := Image.new() - tile.create_from_data(8, 8, false, INDEX_FORMAT, tdata) - return tile - -func snes_get_tile(rom: File, offset: int, length: int) -> Image: - rom.seek(offset) - var data := rom.get_buffer(length) - var planes := length / 8 - match planes: - 4: - return snes_4plane_to_tile(data) - 3: - return snes_3plane_to_tile(data) - 2: - return snes_2plane_to_tile(data) - _: - return snes_1plane_to_tile(data) - - -func make_tile_atlas(tile_images) -> Image: +static func make_tile_atlas(tile_images) -> Image: var r := Rect2(0, 0, 8, 8) var image = Image.new() image.create(128, 128, false, INDEX_FORMAT) @@ -216,21 +122,21 @@ func snes_load_worldmap(rom: File): var worldmap_tile_counts = [256, 256, 128] # Only 128 underwater tiles for world_ts in 3: # Bartz/Combined World, Galuf World, Underwater (world tilesets, not to be confused with the 5 world maps) var tile_count: int = worldmap_tile_counts[world_ts] - var image := generate_palette(rom, offset_worldmap_palettes + (world_ts*0x100), 0x100) + var image := generate_palette(rom, Common.SNES_PSX_addresses['worldmap_palettes']['SNES'] + (world_ts*0x100), 0x100) worldmap_palette_imgs.append(image) worldmap_palette_textures.append(texture_from_image(image)) var tile_palettes = [] - rom.seek(offset_worldmap_tile_palettes + (world_ts*0x100)) + rom.seek(Common.SNES_PSX_addresses['worldmap_tile_palettes']['SNES'] + (world_ts*0x100)) for pal in 256: tile_palettes.append(rom.get_8()) var tile_images = [] # var tile_textures = [] - rom.seek(offset_worldmap_tiles + (world_ts*0x2000)) + rom.seek(Common.SNES_PSX_addresses['worldmap_tiles']['SNES'] + (world_ts*0x2000)) for tile in tile_count: var tiledata := rom.get_buffer(32) - image = snes_mode7_compressed_to_tile(tiledata, tile_palettes[tile]) + image = snes_graphics.mode7_compressed_to_tile(tiledata, tile_palettes[tile]) tile_images.append(image) # tile_textures.append(texture_from_image(image)) if world_ts == 0: # Waterfall hack: lay it out vertically, pushing out dummy tiles @@ -243,7 +149,7 @@ func snes_load_worldmap(rom: File): # Block definitions var block_images = [] var block_textures = [] - var block_bank_start: int = offset_worldmap_blocks + (world_ts*0x300) + var block_bank_start: int = Common.SNES_PSX_addresses['worldmap_blocks']['SNES'] + (world_ts*0x300) var block_tile_ids := PoolByteArray() for block in 0xC0: # 192 blocks per world tileset image = Image.new() @@ -279,13 +185,13 @@ func snes_load_worldmap(rom: File): func snes_load_battle_sprites(rom: File): # Load Battle sprites - rom.seek(offset_Character_Battle_Sprite_Layouts) + rom.seek(Common.SNES_PSX_addresses['character_battle_sprite_layouts']['SNES']) var battle_strip_layouts = rom.get_buffer(num_Character_Battle_Sprite_Layouts * 6) # Character Battle Sprite Tiles for strip in range(0, 22*5): var tiles = [] for i in range(0, 32*48, 32): - tiles.append(snes_get_tile(rom, offset_Character_Battle_Sprite_Tiles + (strip*32*48) + i, 32)) + tiles.append(snes_graphics.get_tile(rom, Common.SNES_PSX_addresses['character_battle_sprite_tiles']['SNES'] + (strip*32*48) + i, 32)) var strip_image = Image.new() strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, INDEX_FORMAT) for i in range(6 * num_Character_Battle_Sprite_Layouts): @@ -295,11 +201,11 @@ func snes_load_battle_sprites(rom: File): # Character Battle Sprite Palettes for palette in range(0, 22*5): - character_battle_sprite_palette_textures.append(texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Palettes + (palette*32)))) - character_battle_sprite_palette_disabled_texture = texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Disabled_Palette)) - character_battle_sprite_palette_stone_texture = texture_from_image(generate_palette(rom, offset_Character_Battle_Sprite_Stone_Palette)) + character_battle_sprite_palette_textures.append(texture_from_image(generate_palette(rom, Common.SNES_PSX_addresses['character_battle_sprite_palettes']['SNES'] + (palette*32)))) + character_battle_sprite_palette_disabled_texture = texture_from_image(generate_palette(rom, Common.SNES_PSX_addresses['character_battle_sprite_disabled_palette']['SNES'])) + character_battle_sprite_palette_stone_texture = texture_from_image(generate_palette(rom, Common.SNES_PSX_addresses['character_battle_sprite_stone_palette']['SNES'])) - weapon_textures['Fist'] = texture_from_image(snes_get_tile(rom, offset_Tiles_Fist, 24)) + weapon_textures['Fist'] = texture_from_image(snes_graphics.get_tile(rom, Common.SNES_PSX_addresses['tiles_fist']['SNES'], 24)) var json_sprite_blocks := Common.load_json('res://data/sprite_blocks.json') @@ -312,7 +218,7 @@ func snes_load_map_sprites(rom: File): for k in json_sprite_blocks.keys(): var v = json_sprite_blocks[k] - var start := int(v['start']) + var start: int = v['start'].hex_to_int() var bpp := int(v['bpp']) var definitions = v['definitions'] var total_tiles := 0 @@ -324,16 +230,16 @@ func snes_load_map_sprites(rom: File): match bpp: 1: for i in total_tiles: - tiles.append(snes_1plane_to_tile(rom.get_buffer(8))) + tiles.append(snes_graphics._1plane_to_tile(rom.get_buffer(8))) 2: for i in total_tiles: - tiles.append(snes_2plane_to_tile(rom.get_buffer(16))) + tiles.append(snes_graphics._2plane_to_tile(rom.get_buffer(16))) 3: for i in total_tiles: - tiles.append(snes_3plane_to_tile(rom.get_buffer(24))) + tiles.append(snes_graphics._3plane_to_tile(rom.get_buffer(24))) 4: for i in total_tiles: - tiles.append(snes_4plane_to_tile(rom.get_buffer(32))) + tiles.append(snes_graphics._4plane_to_tile(rom.get_buffer(32))) _: print_debug('Invalid bpp "%s" in sprite blocks json' % bpp) sprite_blocks[k] = tiles @@ -380,7 +286,7 @@ func load_gba_rom(filename: String): var tiledata = CommonGBA.LZ77_decompress(rom, address) tiledata.append(0) # Slicing to end of array causes an engine crash. For shame! for i in range(8, 57*32+8, 32): - tiles.append(gba_4bpp_to_tile(tiledata.subarray(i, i+32))) + tiles.append(gba_graphics._4bpp_to_tile(tiledata.subarray(i, i+32))) var strip_image = Image.new() strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, INDEX_FORMAT) for i in range(6 * num_Character_Battle_Sprite_Layouts):