2023-08-14 21:48:18 +09:30
|
|
|
const NUM_COLUMNS := 32
|
|
|
|
const NUM_ROWS := 20
|
|
|
|
const LENGTH := NUM_COLUMNS * NUM_ROWS # 0x500 / 2 # Expanded length 0x500, RLE step produces 0x280 tile mappings
|
|
|
|
const NUM_BATTLE_BG_TILESETS := 21 # These combine with a RAM skip amount to form a subset used as a tile atlas
|
|
|
|
const NUM_BATTLE_BG_TILEMAPS := 28
|
|
|
|
const NUM_BATTLE_BG_FLIPSETS := 9
|
|
|
|
const IMAGE_FORMAT := Image.FORMAT_LA8
|
|
|
|
const compression = preload('res://scripts/loaders/snes/compression.gd')
|
2023-08-12 22:56:01 +09:30
|
|
|
|
2023-08-14 21:48:18 +09:30
|
|
|
|
|
|
|
class TileMapping:
|
|
|
|
var tile_index: int
|
|
|
|
var palette: int
|
|
|
|
var priority: bool
|
|
|
|
var h_flip: bool
|
|
|
|
var v_flip: bool
|
|
|
|
|
|
|
|
static func from_tilemap_word(w: int) -> TileMapping:
|
|
|
|
var t := TileMapping.new()
|
|
|
|
t.tile_index = w & 0x03FF
|
|
|
|
t.palette = (w & 0x1C00) >> 10
|
|
|
|
t.priority = bool(w & 0x2000)
|
|
|
|
t.h_flip = bool(w & 0x4000)
|
|
|
|
t.v_flip = bool(w & 0x8000)
|
|
|
|
return t
|
|
|
|
|
|
|
|
static func from_battle_byte(b: int) -> TileMapping:
|
|
|
|
var t := TileMapping.new()
|
|
|
|
t.tile_index = b & 0x7F # In-game this gets |= 0x80, as the BG tiles are from 0x80 to 0xFF
|
|
|
|
t.palette = b >> 7 # In-game this gets incremented by 1, as the BG palettes are #1 and #2 leaving #0 for UI elements
|
|
|
|
return t
|
|
|
|
|
|
|
|
func serialize(buffer: StreamPeer) -> void:
|
|
|
|
# 8bit for tile_index
|
|
|
|
# 6bit for palette
|
|
|
|
# 1bit for each flip
|
|
|
|
# Do nothing with priority, should have two textures for each layer depending on it
|
|
|
|
buffer.put_u8(self.tile_index)
|
|
|
|
var byte2 := self.palette
|
|
|
|
if self.h_flip:
|
|
|
|
byte2 += 0x40
|
|
|
|
if self.v_flip:
|
|
|
|
byte2 += 0x80
|
|
|
|
buffer.put_u8(byte2)
|
|
|
|
|
|
|
|
|
|
|
|
static func decompress_battle_tilemap(buffer: StreamPeer) -> Array:
|
2023-08-12 22:56:01 +09:30
|
|
|
# Decompresses the tilemap for a battle background.
|
|
|
|
# Battle BGs use a type of RLE with 2byte repeat and 1byte incremental repeat.
|
2023-08-12 23:12:28 +09:30
|
|
|
var mappings := []
|
|
|
|
while len(mappings) < LENGTH:
|
2023-08-12 22:56:01 +09:30
|
|
|
var byte := buffer.get_u8()
|
|
|
|
if byte != 0xFF:
|
2023-08-12 23:12:28 +09:30
|
|
|
mappings.append(TileMapping.from_battle_byte(byte))
|
2023-08-12 22:56:01 +09:30
|
|
|
else: # Byte begins a repeat code
|
|
|
|
var repeat_code := buffer.get_u8()
|
|
|
|
var repeat := repeat_code & 0x3F
|
|
|
|
byte = buffer.get_u8()
|
|
|
|
if repeat_code & 0x80: # Repeat 2 tiles
|
2023-08-12 23:12:28 +09:30
|
|
|
var byte2 := buffer.get_u8()
|
2023-08-12 22:56:01 +09:30
|
|
|
for i in repeat:
|
2023-08-12 23:12:28 +09:30
|
|
|
mappings.append(TileMapping.from_battle_byte(byte))
|
|
|
|
mappings.append(TileMapping.from_battle_byte(byte2))
|
2023-08-12 22:56:01 +09:30
|
|
|
else: # Incremental repeat
|
|
|
|
var inc := buffer.get_u8()
|
|
|
|
if repeat_code & 0x40: # Negative increment
|
|
|
|
inc = -inc
|
|
|
|
for i in repeat:
|
2023-08-12 23:12:28 +09:30
|
|
|
mappings.append(TileMapping.from_battle_byte(byte))
|
|
|
|
byte += inc
|
|
|
|
return mappings
|
2023-08-12 22:56:01 +09:30
|
|
|
|
2023-08-14 21:48:18 +09:30
|
|
|
static func apply_battle_tilemap_flips(buffer: StreamPeer, tilemap: Array):
|
2023-08-12 22:56:01 +09:30
|
|
|
var tile_i := 0
|
|
|
|
while tile_i < LENGTH:
|
|
|
|
var a := buffer.get_u8()
|
|
|
|
if a == 0x00: # Skip N*8 tiles
|
|
|
|
tile_i += buffer.get_u8() * 8
|
|
|
|
else:
|
|
|
|
# Each bit is a tile, 1 means flip horizontally
|
|
|
|
for b in range(7, -1, -1):
|
2023-08-12 23:12:28 +09:30
|
|
|
tilemap[tile_i].h_flip = bool((a>>b) & 0x01)
|
2023-08-12 22:56:01 +09:30
|
|
|
tile_i += 1
|
|
|
|
|
2023-08-14 21:48:18 +09:30
|
|
|
static func add_anim_palettes(buffer: StreamPeer, palettes: Array):
|
|
|
|
# TODO: check if the very first entry is added too
|
|
|
|
var pal1_id: int = palettes[0][0]
|
|
|
|
var pal2_id: int = palettes[0][1]
|
|
|
|
while true:
|
|
|
|
var b := buffer.get_u8()
|
|
|
|
if b == 0xFF:
|
|
|
|
break
|
|
|
|
if b & 0x80:
|
|
|
|
pal2_id = b & 0x7F
|
|
|
|
else:
|
|
|
|
pal1_id = b
|
|
|
|
palettes.append([pal1_id, pal2_id])
|
2023-08-12 22:56:01 +09:30
|
|
|
|
2023-08-14 21:48:18 +09:30
|
|
|
static func get_anim_tiles(buffer: StreamPeer) -> Array:
|
|
|
|
var output := []
|
|
|
|
while true:
|
|
|
|
var b := buffer.get_u8() # 2 MSb frame number (0-3), 6 bit tile index to replace
|
|
|
|
var b2 := buffer.get_u8() # tile index to copy from
|
|
|
|
if (b == 0xFF) or (b2 == 0xFF):
|
|
|
|
break
|
|
|
|
output.append([b, b2])
|
|
|
|
return output
|
2023-08-12 22:56:01 +09:30
|
|
|
|
2023-08-14 21:48:18 +09:30
|
|
|
static func array_of_tilemappings_to_image(tilemappings: Array) -> Image:
|
|
|
|
var out := Image.new()
|
|
|
|
var buffer := StreamPeerBuffer.new()
|
|
|
|
for tilemapping in tilemappings:
|
|
|
|
tilemapping.serialize(buffer)
|
|
|
|
out.create_from_data(NUM_COLUMNS, NUM_ROWS, false, IMAGE_FORMAT, buffer.data_array)
|
|
|
|
return out
|
2023-08-12 23:12:28 +09:30
|
|
|
|
2023-08-14 21:48:18 +09:30
|
|
|
static func get_all_battle_background_tilesets(buffer: StreamPeer, tileset_offsets: Array, skip_offsets: Array) -> Array:
|
|
|
|
# Convert these to 4bpp tiles to create your atlases
|
|
|
|
var raw_tilesets := {} # key is offset, value is decompressed tiles
|
|
|
|
var tilesets := []
|
|
|
|
for i in NUM_BATTLE_BG_TILESETS:
|
|
|
|
var skip: int = skip_offsets[i] - 0x7FC000
|
|
|
|
var offset: int = tileset_offsets[i] - 0xC00000
|
|
|
|
if not (offset in raw_tilesets):
|
|
|
|
buffer.seek(offset)
|
|
|
|
raw_tilesets[offset] = compression.decompress_lzss(buffer)
|
|
|
|
var raw: PoolByteArray = raw_tilesets[offset]
|
|
|
|
var skipped_raw := raw.subarray(skip, -1)
|
|
|
|
tilesets.append(skipped_raw)
|
|
|
|
return tilesets
|
|
|
|
|
|
|
|
static func get_all_battle_background_tilemaps(buffer: StreamPeerBuffer, data: Dictionary) -> Array:
|
|
|
|
var tbl_battle_backgrounds: Array = data.tbl_battle_backgrounds
|
|
|
|
var tilesets = get_all_battle_background_tilesets(buffer, data.ptrs_battle_background_tilesets, data.ptrs_battle_background_tileset_skips)
|
|
|
|
|
|
|
|
var tilemaps = []
|
|
|
|
for i in NUM_BATTLE_BG_TILEMAPS:
|
|
|
|
buffer.seek(data.ptrs_battle_background_tilemaps[i] + 0x140000)
|
|
|
|
tilemaps.append(decompress_battle_tilemap(buffer))
|
|
|
|
|
|
|
|
var output := []
|
|
|
|
for tbl in tbl_battle_backgrounds:
|
|
|
|
var out := {}
|
|
|
|
out.tileset = tilesets[tbl.tileset_id]
|
|
|
|
out.palette_ids = [tbl.palette_ids]
|
|
|
|
var tilemap = tilemaps[tbl.tilemap_id]
|
|
|
|
if tbl.tilemap_flips_id < NUM_BATTLE_BG_FLIPSETS: # 0xFF means no flips
|
|
|
|
buffer.seek(data.ptrs_battle_background_tilemap_flips[tbl.tilemap_flips_id] + 0x140000)
|
|
|
|
apply_battle_tilemap_flips(buffer, tilemap)
|
|
|
|
out.tilemap_image = array_of_tilemappings_to_image(tilemap)
|
|
|
|
if tbl.palcycle_id < 0xFF:
|
|
|
|
buffer.seek(data.ptrs_battle_background_palette_animations[tbl.palcycle_id] + 0x140000)
|
|
|
|
add_anim_palettes(buffer, out.palette_ids)
|
|
|
|
if tbl.tilecycle_id > 0:
|
|
|
|
buffer.seek(data.ptrs_battle_background_tile_animations[tbl.tilecycle_id] + 0x140000)
|
|
|
|
out.animated_tiles = get_anim_tiles(buffer)
|
|
|
|
output.append(out)
|
|
|
|
|
|
|
|
return output
|