extends Node var shader_material = load('res://palette_mat.tres') var ROM_filename := 'FF5_SCC_WepTweaks_Inus_Dash.sfc' # 'Final Fantasy V (Japan).sfc' const offset_Character_Battle_Sprite_Tiles: int = 0x120000 const offset_Character_Battle_Sprite_Palettes: int = 0x14A3C0 const offset_Character_Battle_Sprite_Layouts: int = 0x14B997 const num_Character_Battle_Sprite_Layouts: int = 11 const offset_Character_Battle_Sprite_Disabled_Palette: int = 0x00F867 const offset_Character_Battle_Sprite_Stone_Palette: int = 0x00F807 const offset_Tiles_Fist: int = 0x11D710 # 3bpp tile #var character_battle_sprite_tiles = [] #var character_battle_sprite_palette_imgs = [] var character_battle_sprite_palette_textures = [] var character_battle_sprite_palette_disabled_texture: ImageTexture var character_battle_sprite_palette_stone_texture: ImageTexture var weapon_textures = {} func texture_from_image(image: Image, flags: int = 0) -> ImageTexture: var tex = ImageTexture.new() tex.create_from_image(image, flags) tex.flags = Texture.FLAG_CONVERT_TO_LINEAR return tex 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 func generate_palette_rgbf(rom: File, offset: int, length: int = 16) -> Image: rom.seek(offset) var img := Image.new() img.create(length, 1, false, Image.FORMAT_RGBF) img.lock() for i in range(length): var color = bgr555_to_color(rom.get_16()) img.set_pixel(i, 0, color) img.unlock() return img func generate_palette_rgb8(rom: File, offset: int, length: int = 16) -> Image: # Implicit sRGB -> linear conversion on ImageTexture creation from RGB8 Image ruins this rom.seek(offset) var data = ByteArray(length*3) # img.lock() for i in length: var color := bgr555_to_color(rom.get_16()) var j: int = i*3 data[j] = color.r8 data[j+1] = color.g8 data[j+2] = color.b8 var img := Image.new() img.create_from_data(length, 1, false, Image.FORMAT_RGB8, data) return img 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): var bgr555 := rom.get_16() var r := bgr555 & 0x1F var g := (bgr555 >> 5) & 0x1F var b := (bgr555 >> 10) & 0x1F var rgb5_a1 := (r << 11) | (g << 6) | (b << 1) | 1 data[i*2] = rgb5_a1 & 0xFF data[(i*2)+1] = rgb5_a1 >> 8 var img := Image.new() img.create_from_data(length, 1, false, Image.FORMAT_RGBA5551, data) return img func generate_palette(rom: File, offset: int, length: int = 16) -> Image: return generate_palette_rgb5_a1(rom, offset, length) func ByteArray(size: int) -> PoolByteArray: var arr = PoolByteArray() arr.resize(size) return arr 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, Image.FORMAT_R8, tdata) return tile 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, Image.FORMAT_R8, 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, Image.FORMAT_R8, 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, Image.FORMAT_R8, 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, Image.FORMAT_R8, 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 load_snes_rom(filename: String): var rom := File.new() var _error = rom.open(filename, File.READ) rom.seek(offset_Character_Battle_Sprite_Layouts) 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)) var strip_image = Image.new() strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, Image.FORMAT_R8) for i in range(6 * num_Character_Battle_Sprite_Layouts): strip_image.blit_rect(tiles[battle_strip_layouts[i]], Rect2(0, 0, 8, 8), Vector2((i%2) * 8, (i/2) * 8)) strip_images.append(strip_image) strip_textures.append(texture_from_image(strip_image)) # 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)) weapon_textures['Fist'] = texture_from_image(snes_get_tile(rom, offset_Tiles_Fist, 24)) SoundLoader.load_samples(rom) func gba_LZ77_decompress(rom: File, address: int) -> PoolByteArray: rom.seek(address) var header := rom.get_32() assert (header & 0x10 == 0x10) var length := header >> 8 var output := ByteArray(length) var ptr := 0 while ptr < length: var bitmap := rom.get_8() for i in range(8): if (bitmap >> (7-i)) & 1: # Buffer substitution var h1 := rom.get_8() var h2 := rom.get_8() var copy_len := 3 + (h1 >> 4) var copy_ptr := ptr - 1 - (((h1 & 0x0F)<<8) + h2) for j in range(copy_len): output[ptr] = output[copy_ptr] copy_ptr += 1 ptr += 1 if ptr >= length: return output else: # Literal byte output[ptr] = rom.get_8() ptr += 1 if ptr >= length: return output return output const gba_marker := 'FINAL FANTASY V ADVANCE SYGMAB' const gba_marker_pos_US := 0x12FE10 const gba_marker_pos_EU := 0x131294 const gba_offset_battle_sprite_tiles := 504 const gba_offset_battle_sprite_palettes := 1064 const gba_offset_battle_sprite_layouts := 9435 func load_gba_rom(filename: String): var rom := File.new() var offset_marker: int var result = rom.open(filename, File.READ) if result != 0: print_debug('ROM failed to open: ', result) return rom.seek(gba_marker_pos_US) if rom.get_buffer(len(gba_marker)).get_string_from_ascii() == gba_marker: offset_marker = gba_marker_pos_US else: rom.seek(gba_marker_pos_EU) if rom.get_buffer(len(gba_marker)).get_string_from_ascii() == gba_marker: offset_marker = gba_marker_pos_EU else: print_debug('ROM does not match known US/EU ROMs, aborting') return # Battle Sprite Tiles rom.seek(offset_marker + gba_offset_battle_sprite_layouts) var battle_strip_layouts = rom.get_buffer(num_Character_Battle_Sprite_Layouts * 6) for strip in range(0, 26*5): var tiles = [] rom.seek(offset_marker + gba_offset_battle_sprite_tiles + strip*4) var address = rom.get_32() - 0x08000000 var tiledata = gba_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))) var strip_image = Image.new() strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, Image.FORMAT_R8) for i in range(6 * num_Character_Battle_Sprite_Layouts): strip_image.blit_rect(tiles[battle_strip_layouts[i]], Rect2(0, 0, 8, 8), Vector2((i%2) * 8, (i/2) * 8)) strip_images.append(strip_image) strip_textures.append(texture_from_image(strip_image)) # Character Battle Sprite Palettes for palette in range(0, 26*5): rom.seek(offset_marker + gba_offset_battle_sprite_palettes + palette*4) character_battle_sprite_palette_textures.append(texture_from_image(generate_palette(rom, rom.get_32() - 0x08000000 + 8))) # 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)) var strip_images = [] var strip_textures = [] func _ready(): load_snes_rom(ROM_filename) #load_gba_rom('2564 - Final Fantasy V Advance (U)(Independent).gba')