ChocolateBird/scripts/loaders/SpriteLoader.gd

325 lines
13 KiB
GDScript

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 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_tile_palettes']['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
func load_snes_rom(rom: File):
snes_load_battle_sprites(rom)
snes_load_worldmap(rom)
snes_load_map_sprites(rom)
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