ChocolateBird/scripts/loaders/save_loader.gd

91 lines
3.2 KiB
GDScript

extends Node
const STRUCT := preload('res://scripts/struct.gd')
const SLOT_IN_USE := 0xE41B
var struct_types := STRUCT.get_base_structarraytypes()
# FFV SRAM is 4 slots of 0x700byte save files
# $0000-$06FF - Save Slot 1
# $0700-$0DFF - Save Slot 2
# $0E00-$14FF - Save Slot 3
# $1500-$1BFF - Save Slot 4
# $1C00-$1FEF - Empty
# $1FF0-$1FF7 - Checksum (2 bytes per slot)
# $1FF8-$1FFF - use table - contains $E41B if a slot is in use (deleting a save will just change this)
# Checksum just sums up every 16bit word from 0x000-0x600 of the save slot, using the carry flag.
# The carry flag means that it is not strictly modulo arithmetic, but you have to add 1 every time you pass 0xFFFF
# Offsets within save slot
# $000:600 = $500:B00 in WRAM (0x7E0500:0x7E0B00) only values that don't match are things like game frame timer which increments immediately after load
#
# $000-$04F - Character Slot 1
# $050=$09F - Character Slot 2
# $0A0=$0EF - Character Slot 3
# $0F0=$13F - Character Slot 4
# $447-$449 - Current Gil
#
# $5D8 - Current Map X position
# $5D9 - Current Map Y position
#
# $600-$6FF - Appears to be unused and zeroed out, not used for checksum. Maybe we can use it for cool metadata? :)
func get_slot_checksum(slot: StreamPeerBuffer) -> int:
slot.seek(0)
var checksum := 0
for i in 0x600: # Last 0x100 bytes aren't checksummed
checksum += slot.get_u16()
if checksum > 0xFFFF: # Addition of shorts can only overflow
checksum &= 0xFFFF
if i < 0x5FF:
checksum += 1 # TODO: confirm this carry flag edge case is correct!
return checksum
func get_struct(buffer: StreamPeer, struct_name: String) -> Dictionary:
# Does not seek to the start of the buffer.
# Make sure it is set to where you want to start reading.
if not (struct_name in struct_types):
print_debug('Attempted to get undeclared struct: "%s"' % struct_name)
return {}
return struct_types[struct_name].get_value(buffer, [0, 0])
func get_save_slot(sram: File, slot_id: int):
var buffer := StreamPeerBuffer.new()
sram.seek(0x700 * slot_id)
buffer.set_data_array(sram.get_buffer(0x700))
return buffer
func save_slot(sram: File, slot_id: int, slot: StreamPeerBuffer):
sram.seek(0x700 * slot_id)
sram.store_buffer(slot.data_array)
sram.seek(0x1FF0 + (slot_id*2))
sram.store_16(get_slot_checksum(slot))
sram.seek(0x1FF8 + (slot_id*2))
sram.store_16(SLOT_IN_USE)
func delete_save_slot(sram: File, slot_id: int):
sram.seek(0x1FF8 + (slot_id*2))
sram.store_16(0)
func _ready():
var file := File.new()
var error = file.open('res://data/SNES_save.tsv', File.READ)
if error == OK:
var current_struct: STRUCT.Struct
var line_num := 0 # Currently only used for step-through debugging
while file.get_position() < file.get_len():
var line := file.get_csv_line('\t')
line_num += 1
var size = line.size()
if size < 2:
continue
# Size is at least 2
var type := line[0]
var label := line[1]
if type == 'struct':
# New struct declaration
current_struct = STRUCT.Struct.new()
struct_types[label] = current_struct
elif type and label:
# TODO: Maybe store the trailing comments somewhere?
current_struct.members.append([label, STRUCT.get_structarraytype(type, struct_types)])
file.close()