extends Node # Portability for GLES2 vs GLES3 stuff 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 # Glyphs const offset_glyphs_small := 0x1FF000 # 0x100 entries, 2bpp const offset_glyphs_dialog := 0x03E800 # 0x100 entries, 1bpp const offset_glyphs_kanji := 0x1BD000 # 0x1AA entries, 1bpp # Images 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 num_Character_Battle_Sprite_Layouts := 11 # Arrays to store sprites in var worldmap_palette_imgs = [] var worldmap_palette_textures = [] var worldmap_tile_individual_imgs = [] var worldmap_tile_atlas_textures = [] var worldmap_block_tile_ids = [] var worldmap_block_individual_imgs = [] var worldmap_block_individual_textures = [] var worldmap_block_atlas_imgs = [] var worldmap_block_atlas_textures = [] # var worldmap_tile_atlas_textures = [] #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 = {} var character_status_palette_replacements = { 'poison': {4: '$7EDB = #DEB5FF', 8: '$4DD3 = #9C739C'}, # Also kneel 'zombie': {3: '$7FFF = #FFFFFF', 4: '$3AF5 = #ADBD73', 8: '$3210 = #848463'}, # Also kneel in menu # Darkness None 'old': {5: '$5294 = #A5A5A5', 9: '$4210 = #848484'}, # Sleep None, Paralyze None, Charm None 'berserk': {4: '$013F = #FF4A00', 8: '$001F = #FF0000'}, # Mute None, Image None 'wall': {1: '$6A60 = #009CD6'}, # Strobe between normal ($1084 = #212121) and this aqua at 30Hz (2 frames each) 'armor': {1: '$031F = #FFC600'}, # Strobe between normal ($1084 = #212121) and this orange at 30Hz (2 frames each) 'shell': {1: '$0B64 = #21DE10'}, # Strobe between normal ($1084 = #212121) and this green 30Hz (2 frames each) 'stop': {1: '$001A = #D60000'}, # Strobe between normal ($1084 = #212121) and this aqua at 30Hz (2 frames each) 'haste': {1: '$017F = #FF5A00'}, # Strobe between normal ($1084 = #212121) and this orange at 30Hz (2 frames each) 'slow': {1: '$7FFF = #FFFFFF'}, # Strobe between normal ($1084 = #212121) and this green 30Hz (2 frames each) } static func ByteArray(size: int) -> PoolByteArray: var arr := PoolByteArray() arr.resize(size) return arr static func texture_from_image(image: Image, flags: int = 0) -> ImageTexture: var tex = ImageTexture.new() tex.create_from_image(image, flags) return tex 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 static func generate_palette_rgbf(rom: File, offset: int, length: int = 16) -> Image: rom.seek(offset) var img := Image.new() var rows := length/16 img.create(16, rows, false, Image.FORMAT_RGBF) img.lock() for y in rows: for x in 16: var color = bgr555_to_color(rom.get_16()) img.set_pixel(x, y, color) img.unlock() return img 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) 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(16, length/16, false, Image.FORMAT_RGB8, data) return img 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): 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(16, length/16, false, Image.FORMAT_RGBA5551, data) return img static func generate_palette(rom: File, offset: int, length: int = 16) -> Image: if length < 16: length = 16 return generate_palette_rgb8(rom, offset, length) static func generate_palette_from_colorarray(colors: PoolColorArray, format:=Image.FORMAT_RGBF) -> Image: var img := Image.new() var rows := len(colors)/16 img.create(16, rows, false, format) img.lock() var i := 0 for y in rows: for x in 16: img.set_pixel(x, y, colors[i]) i += 1 img.unlock() return img 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) var tile = 0 for y in tile_images.size()/16: for x in 16: image.blit_rect(tile_images[tile], r, Vector2(x*8, y*8)) tile += 1 return image # func snes_load_worldmap(rom: File): # # Load Worldmap Graphics # 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, 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(Common.SNES_PSX_addresses['worldmap_tiles.bias']['SNES'] + (world_ts*0x100)) # for pal in 256: # tile_palettes.append(rom.get_8()) # var tile_images = [] # # var tile_textures = [] # 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_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 # tile_images[0x97] = tile_images[0x88] # tile_images[0x98] = tile_images[0x87] # worldmap_tile_individual_imgs.append(tile_images) # worldmap_tile_atlas_textures.append(texture_from_image(make_tile_atlas(tile_images))) # # Block definitions # var block_images = [] # var block_textures = [] # 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() # image.create(16, 16, false, INDEX_FORMAT) # for tile in 4: # rom.seek(block_bank_start + block + (tile * 0xC0)) # Horrible interleaving scheme # var src_idx := rom.get_8() # block_tile_ids.append(src_idx) # if src_idx < tile_count: # image.blit_rect(tile_images[src_idx], Rect2(0, 0, 8, 8), Vector2((tile%2)*8, (tile/2)*8)) # block_images.append(image) # block_textures.append(texture_from_image(image)) # worldmap_block_individual_imgs.append(block_images) # worldmap_block_individual_textures.append(block_textures) # worldmap_block_tile_ids.append(block_tile_ids) # # Make block atlas # image = Image.new() # image.create(16*16, 16*12, false, INDEX_FORMAT) # for block in 0xC0: # image.blit_rect(block_images[block], Rect2(0, 0, 16, 16), Vector2((block%16)*16, (block/16)*16)) # worldmap_block_atlas_imgs.append(image) # worldmap_block_atlas_textures.append(texture_from_image(image)) # # # DEBUG: Make tile atlas # # image = Image.new() # # image.create(16*8, (tile_count/16)*8, false, INDEX_FORMAT) # # for tile in tile_count: # # image.blit_rect(tile_images[tile], Rect2(0, 0, 8, 8), Vector2((tile%16)*8, (tile/16)*8)) # # worldmap_tile_atlas_textures.append(texture_from_image(image)) # MapLoader.update_worldmap_block_tile_ids(worldmap_block_tile_ids) # func snes_load_battle_sprites(rom: File): # # Load Battle sprites # 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_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): # 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, 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_graphics.get_tile(rom, Common.SNES_PSX_addresses['tiles_fist']['SNES'], 24)) # var json_sprite_blocks: Dictionary = Common.load_json('res://data/sprite_blocks.json') # This needs error handling later # var sprite_blocks := {} # func snes_load_map_sprites(rom: File): # # Main palettes # var map_palettes = [] # for i in 4: # map_palettes.append(generate_palette(rom, 0x1FFC00 + 32*i)) # for k in json_sprite_blocks.keys(): # var v = json_sprite_blocks[k] # var start: int = v['start'].hex_to_int() # var bpp := int(v['bpp']) # var definitions = v['definitions'] # var total_tiles := 0 # for defn in definitions: # total_tiles += defn[0] * defn[1] * defn[2] # print_debug('Processing sprite block "%s" - starts at $%06X with %d %dbpp tiles' % [k, start, total_tiles, bpp]) # rom.seek(start) # var tiles := [] # match bpp: # 1: # for i in total_tiles: # tiles.append(snes_graphics._1plane_to_tile(rom.get_buffer(8))) # 2: # for i in total_tiles: # tiles.append(snes_graphics._2plane_to_tile(rom.get_buffer(16))) # 3: # for i in total_tiles: # tiles.append(snes_graphics._3plane_to_tile(rom.get_buffer(24))) # 4: # for i in total_tiles: # 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 static func bias_tile(unbiased: PoolByteArray, bias: int) -> Image: var image := Image.new() var biased = ByteArray(64) for i in 64: biased[i] = unbiased[i] + bias image.create_from_data(8, 8, false, INDEX_FORMAT, biased) return image # func load_snes_rom(rom: File): # snes_load_battle_sprites(rom) # snes_load_worldmap(rom) # # snes_load_map_sprites(rom) func load_from_structs(data: Dictionary): # Load Battle sprites for character_tiles in data.character_battle_sprite_tiles: for job_tiles in character_tiles: var strip_image = Image.new() # Should probably refactor later strip_image.create(16, 24 * num_Character_Battle_Sprite_Layouts, false, INDEX_FORMAT) for sprite in num_Character_Battle_Sprite_Layouts: var sprite_tiles: PoolByteArray = data.character_battle_sprite_layouts[sprite] for i in 6: strip_image.blit_rect(job_tiles[sprite_tiles[i]], Rect2(0, 0, 8, 8), Vector2((i%2) * 8, ((i/2) * 8) + (sprite * 24))) strip_images.append(strip_image) strip_textures.append(texture_from_image(strip_image)) # Character Battle Sprite Palettes for character_palettes in data.character_battle_sprite_palettes: for job_palette in character_palettes: character_battle_sprite_palette_textures.append(texture_from_image(generate_palette_from_colorarray(job_palette))) character_battle_sprite_palette_disabled_texture = texture_from_image(generate_palette_from_colorarray(data.character_battle_sprite_disabled_palette)) character_battle_sprite_palette_stone_texture = texture_from_image(generate_palette_from_colorarray(data.character_battle_sprite_stone_palette)) weapon_textures['Fist'] = texture_from_image(data.tiles_fist) # Load World Map # Load Worldmap Graphics 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_from_colorarray(data.worldmap_palettes[world_ts]) worldmap_palette_imgs.append(image) worldmap_palette_textures.append(texture_from_image(image)) var tile_biases: PoolByteArray = data.worldmap_tiles.bias[world_ts] var tile_images_unbiased = data.worldmap_tiles[world_ts] var tile_images := [] for i in tile_count: tile_images.append(bias_tile(tile_images_unbiased[i].get_data(), tile_biases[i])) if world_ts == 0: # Waterfall hack: lay it out vertically, pushing out dummy tiles tile_images[0x97] = tile_images[0x88] tile_images[0x98] = tile_images[0x87] worldmap_tile_individual_imgs.append(tile_images) worldmap_tile_atlas_textures.append(texture_from_image(make_tile_atlas(tile_images))) # Block definitions var block_images = [] var block_textures = [] var block_tile_ids := PoolByteArray() for block in 0xC0: # 192 blocks per world tileset image = Image.new() image.create(16, 16, false, INDEX_FORMAT) for i in 4: var src_idx: int = data.worldmap_blocks[world_ts][i][block] block_tile_ids.append(src_idx) if src_idx < tile_count: image.blit_rect(tile_images[src_idx], Rect2(0, 0, 8, 8), Vector2((i%2)*8, (i/2)*8)) block_images.append(image) block_textures.append(texture_from_image(image)) worldmap_block_individual_imgs.append(block_images) worldmap_block_individual_textures.append(block_textures) worldmap_block_tile_ids.append(block_tile_ids) # Make block atlas # image = Image.new() # image.create(16*16, 16*12, false, INDEX_FORMAT) # for block in 0xC0: # image.blit_rect(block_images[block], Rect2(0, 0, 16, 16), Vector2((block%16)*16, (block/16)*16)) # worldmap_block_atlas_imgs.append(image) # worldmap_block_atlas_textures.append(texture_from_image(image)) # # DEBUG: Make tile atlas # image = Image.new() # image.create(16*8, (tile_count/16)*8, false, INDEX_FORMAT) # for tile in tile_count: # image.blit_rect(tile_images[tile], Rect2(0, 0, 8, 8), Vector2((tile%16)*8, (tile/16)*8)) # worldmap_tile_atlas_textures.append(texture_from_image(image)) MapLoader.update_worldmap_block_tile_ids(worldmap_block_tile_ids) 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 = 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_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): 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(): #pass