2023-07-28 23:11:26 +09:30
extends Node
const SLOT_IN_USE : = 0xE41B
2023-07-29 01:53:03 +09:30
var schema : = { }
2023-07-28 23:11:26 +09:30
# 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
2023-07-29 01:53:03 +09:30
checksum += slot . get_u16 ( )
2023-07-28 23:11:26 +09:30
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_character ( slot : StreamPeerBuffer , character_slot_id : int ) - > Dictionary :
var character : = { }
slot . seek ( 0x50 * character_slot_id )
2023-07-29 01:53:03 +09:30
var b : = slot . get_u8 ( )
2023-07-28 23:11:26 +09:30
character [ ' id ' ] = b & 0x07 # 0Bartz/1Lenna/2Galuf/3Faris/4Krile/5/6/7
character [ ' gender ' ] = ( b >> 3 ) & 1 # 0 = male, 1 = female
# Two unknown, possibly unused bits
character [ ' present ' ] = ( b >> 6 ) & 1 # 0 = absent?
character [ ' back_row ' ] = ( b >> 7 ) & 1
2023-07-29 01:53:03 +09:30
character [ ' job_id ' ] = slot . get_u8 ( )
character [ ' level ' ] = slot . get_u8 ( )
character [ ' experience ' ] = slot . get_u16 ( ) | ( slot . get_u8 ( ) << 16 )
character [ ' hp_current ' ] = slot . get_u16 ( )
character [ ' hp_max ' ] = slot . get_u16 ( )
character [ ' mp_current ' ] = slot . get_u16 ( )
character [ ' mp_max ' ] = slot . get_u16 ( )
character [ ' equipped_head ' ] = slot . get_u8 ( )
character [ ' equipped_body ' ] = slot . get_u8 ( )
character [ ' equipped_acc ' ] = slot . get_u8 ( )
character [ ' equipped_rh_shield ' ] = slot . get_u8 ( )
character [ ' equipped_lh_shield ' ] = slot . get_u8 ( )
character [ ' equipped_rh_weapon ' ] = slot . get_u8 ( )
character [ ' equipped_lh_weapon ' ] = slot . get_u8 ( )
character [ ' caught_monster ' ] = slot . get_u8 ( )
character [ ' ability_1 ' ] = slot . get_u8 ( )
character [ ' ability_2 ' ] = slot . get_u8 ( )
character [ ' ability_3 ' ] = slot . get_u8 ( )
character [ ' ability_4 ' ] = slot . get_u8 ( )
character [ ' action_flags ' ] = slot . get_u8 ( )
character [ ' damage_mod ' ] = slot . get_u8 ( )
character [ ' innates ' ] = slot . get_u16 ( )
character [ ' magic_element_up ' ] = slot . get_u8 ( )
character [ ' equip_weight ' ] = slot . get_u8 ( )
character [ ' base_strength ' ] = slot . get_u8 ( )
character [ ' base_agility ' ] = slot . get_u8 ( )
character [ ' base_stamina ' ] = slot . get_u8 ( )
character [ ' base_magic ' ] = slot . get_u8 ( )
character [ ' current_strength ' ] = slot . get_u8 ( )
character [ ' current_agility ' ] = slot . get_u8 ( )
character [ ' current_stamina ' ] = slot . get_u8 ( )
character [ ' current_magic ' ] = slot . get_u8 ( )
character [ ' evasion ' ] = slot . get_u8 ( )
character [ ' defense ' ] = slot . get_u8 ( )
character [ ' magic_evasion ' ] = slot . get_u8 ( )
character [ ' magic_defense ' ] = slot . get_u8 ( )
character [ ' elemental_absorb ' ] = slot . get_u8 ( )
character [ ' elemental_evade ' ] = slot . get_u8 ( )
character [ ' elemental_immune ' ] = slot . get_u8 ( )
character [ ' elemental_half ' ] = slot . get_u8 ( )
character [ ' elemental_weakness ' ] = slot . get_u8 ( )
character [ ' resistance_status_1 ' ] = slot . get_u8 ( )
character [ ' resistance_status_2 ' ] = slot . get_u8 ( )
character [ ' resistance_status_3 ' ] = slot . get_u8 ( )
character [ ' specialty_weapon ' ] = slot . get_u8 ( )
character [ ' specialty_equipment ' ] = slot . get_u8 ( )
character [ ' current_job_level ' ] = slot . get_u8 ( )
character [ ' current_job_abp ' ] = slot . get_u16 ( )
character [ ' spell_level_1 ' ] = slot . get_u8 ( )
character [ ' spell_level_2 ' ] = slot . get_u8 ( )
character [ ' spell_level_3 ' ] = slot . get_u8 ( )
character [ ' equipment_category ' ] = slot . get_u32 ( )
character [ ' attack ' ] = slot . get_u16 ( )
character [ ' attack_id_reaction_unused ' ] = slot . get_u8 ( )
character [ ' unk1 ' ] = slot . get_u8 ( )
character [ ' unk2 ' ] = slot . get_u8 ( )
character [ ' unk3 ' ] = slot . get_u8 ( )
character [ ' freelancer_strength ' ] = slot . get_u8 ( )
character [ ' freelancer_agility ' ] = slot . get_u8 ( )
character [ ' freelancer_stamina ' ] = slot . get_u8 ( )
character [ ' freelancer_magic ' ] = slot . get_u8 ( )
character [ ' freelancer_innates ' ] = slot . get_u16 ( )
2023-07-28 23:11:26 +09:30
return character
2023-07-29 01:53:03 +09:30
func get_struct ( buffer : StreamPeer , struct_name : String ) - > Dictionary :
# As this is recursive, it 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 schema ) :
print_debug ( ' Attempted to get undeclared struct: " %s " ' % struct_name )
return { }
var struct : = { }
var leftover_bits : = 0
var leftover_bits_value : = 0
for line in schema [ struct_name ] :
var t : String = line [ 0 ]
var k : String = line [ 1 ]
match t :
' 8 ' :
struct [ k ] = buffer . get_u8 ( )
' 16 ' :
struct [ k ] = buffer . get_u16 ( )
' 24 ' :
struct [ k ] = buffer . get_u16 ( ) | ( buffer . get_u8 ( ) << 16 )
' 32 ' :
struct [ k ] = buffer . get_u32 ( )
_ :
var arr_split = t . split ( ' [ ' )
match arr_split . size ( ) :
1 :
# Check if it's an odd number of bits
var b : = int ( t )
if b > 0 :
# Check if we have leftover bits to fill this order
while leftover_bits < b :
leftover_bits_value |= buffer . get_u8 ( ) << leftover_bits
leftover_bits += 8
struct [ k ] = leftover_bits_value & ( ( 1 << b ) - 1 )
leftover_bits_value = leftover_bits_value >> b
leftover_bits -= b
else :
# It's a struct
struct [ k ] = get_struct ( buffer , t )
2 :
var n0 : = int ( arr_split [ 1 ] . trim_suffix ( ' ] ' ) )
var arr = [ ]
if arr_split [ 0 ] == ' 8 ' :
arr = PoolByteArray ( )
for i in n0 :
arr . append ( buffer . get_u8 ( ) )
else :
for i in n0 :
arr . append ( get_struct ( buffer , arr_split [ 0 ] ) )
struct [ k ] = arr
3 :
var n0 : = int ( arr_split [ 1 ] . trim_suffix ( ' ] ' ) )
var n1 : = int ( arr_split [ 2 ] . trim_suffix ( ' ] ' ) )
var arr0 : = [ ]
if arr_split [ 0 ] == ' 8 ' :
for i in n0 :
var arr1 : = PoolByteArray ( )
for j in n1 :
arr1 . append ( buffer . get_u8 ( ) )
arr0 . append ( arr1 )
else :
for i in n0 :
var arr1 : = [ ]
for j in n1 :
arr1 . append ( get_struct ( buffer , arr_split [ 0 ] ) )
arr0 . append ( arr1 )
struct [ k ] = arr0
var s :
print_debug ( ' struct value array dims of %d are not yet supported (struct " %s " ) ' % [ s , struct_name ] )
return struct
2023-07-28 23:11:26 +09:30
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 )
2023-07-29 01:53:03 +09:30
func _ready ( ) :
var file : = File . new ( )
var error = file . open ( ' res://data/SNES_save.txt ' , File . READ )
if error == OK :
var current_struct_name = null
var current_struct = [ ]
var line_num : = 0
while file . get_position ( ) < file . get_len ( ) :
var line : = file . get_csv_line ( ' \t ' )
line_num += 1
var size = line . size ( )
if size < 2 :
if current_struct_name :
# Store struct we just finished declaring
schema [ current_struct_name ] = current_struct
current_struct = [ ]
current_struct_name = null
continue
# if size < 2:
# print_debug('Malformed schema file: line %d - size %d - "%s"' % [line_num, line.size(), line])
# continue
# Size is at least 2
if line [ 0 ] == ' struct ' :
# New struct declaration
if current_struct_name :
# Store one we just finished declaring
schema [ current_struct_name ] = current_struct
current_struct = [ ]
current_struct_name = line [ 1 ]
else :
# TODO: Maybe store the trailing comments somewhere?
current_struct . append ( line )
# Make sure we saved the final struct
if current_struct_name :
schema [ current_struct_name ] = current_struct
file . close ( )