Added full SNES save file serialization
Ticked off SNES save support on the README.md
This commit is contained in:
parent
36d025e18c
commit
a27736f917
|
@ -18,9 +18,7 @@ You can currently load up your SNES ROM of Final Fantasy FFV and look at battle
|
|||
Following systems are ordered by vague overarching priority:
|
||||
|
||||
### Save/Load System
|
||||
I know I called this an "Interactive Save Editor" but I haven't started that yet.
|
||||
- [x] SNES loading
|
||||
- [ ] SNES saving (should be easy enough, soon™)
|
||||
- [x] SNES loading and saving
|
||||
- [ ] PSX Support (wasn't identical to SNES with different offset ;_; )
|
||||
- [ ] GBA Support (note, does not imply full asset ripping)
|
||||
- [ ] Steam Pixel Remaster Support (I haven't bought this yet so low prio) (note, does not imply full asset ripping)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
extends Node
|
||||
#warning-ignore-all:return_value_discarded
|
||||
const STRUCT := preload('res://scripts/struct.gd')
|
||||
const SLOT_IN_USE := 0xE41B
|
||||
var struct_types := STRUCT.get_base_structarraytypes()
|
||||
|
@ -53,18 +54,57 @@ func put_struct(buffer: StreamPeer, struct_name: String, data: Dictionary):
|
|||
return
|
||||
struct_types[struct_name].put_value(buffer, data, [0, 0])
|
||||
|
||||
func deserialize_save_slot(bytes: PoolByteArray) -> Dictionary:
|
||||
var buffer = StreamPeerBuffer.new()
|
||||
buffer.data_array = bytes
|
||||
func deserialize_save_slot(buffer: StreamPeerBuffer) -> Dictionary:
|
||||
return struct_types['Save_slot'].get_value(buffer, [0, 0])
|
||||
|
||||
func serialize_save_slot(data: Dictionary) -> PoolByteArray:
|
||||
var buffer = StreamPeerBuffer.new()
|
||||
func serialize_save_slot(data: Dictionary) -> StreamPeerBuffer:
|
||||
var buffer := StreamPeerBuffer.new()
|
||||
struct_types['Save_slot'].put_value(buffer, data, [0, 0])
|
||||
var padding := PoolByteArray()
|
||||
padding.resize(0x100)
|
||||
padding.fill(0)
|
||||
buffer.put_data(padding)
|
||||
return buffer
|
||||
|
||||
func make_snes_save_file(slot_dicts: Array) -> PoolByteArray:
|
||||
# Pass a length 4 array of dictionaries.
|
||||
# Falsey entries will be zeroed out slots, with the active flag at the end also zeroed.
|
||||
assert(len(slot_dicts) == 4)
|
||||
var buffer := StreamPeerBuffer.new()
|
||||
var zeroes := PoolByteArray()
|
||||
zeroes.resize(0x700)
|
||||
zeroes.fill(0)
|
||||
var checksums := []
|
||||
for dict in slot_dicts:
|
||||
if dict:
|
||||
var slot := serialize_save_slot(dict)
|
||||
var checksum := get_slot_checksum(slot)
|
||||
buffer.put_data(slot.data_array)
|
||||
if slot.data_array == zeroes:
|
||||
checksums.append(-1)
|
||||
else:
|
||||
checksums.append(checksum)
|
||||
else:
|
||||
buffer.put_data(zeroes)
|
||||
checksums.append(-1)
|
||||
# Pad from $1C00 == 7168 to $1FF0 == 8160
|
||||
buffer.put_data(zeroes.subarray(0, 991)) # BEWARE: SUBARRAY IS INCLUSIVE
|
||||
# Mystery byte TODO: INVESTIGATE LATER
|
||||
buffer.put_8(1)
|
||||
# Pad from $1FE1 == 8161 to $1FF0 == 8176
|
||||
buffer.put_data(zeroes.subarray(0, 14)) # BEWARE: SUBARRAY IS INCLUSIVE
|
||||
# Checksums
|
||||
for c in checksums:
|
||||
if c > -1:
|
||||
buffer.put_u16(c)
|
||||
else:
|
||||
buffer.put_u16(0)
|
||||
# Active flag
|
||||
for c in checksums:
|
||||
if c > -1:
|
||||
buffer.put_u16(SLOT_IN_USE)
|
||||
else:
|
||||
buffer.put_u16(0)
|
||||
return buffer.data_array
|
||||
|
||||
func get_save_slot(sram: File, slot_id: int) -> StreamPeerBuffer:
|
||||
|
|
|
@ -2,6 +2,7 @@ extends Control
|
|||
|
||||
var dir_user := Directory.new()
|
||||
const P_TESTDATA := 'user://test_data/'
|
||||
const FILENAME_TEST_SAVE := 'res://test.srm'
|
||||
# Shared state between tests
|
||||
var save_slot_buffers = []
|
||||
var save_slot_dicts = []
|
||||
|
@ -12,7 +13,7 @@ func test(label, input):
|
|||
else:
|
||||
print('FAILURE: ' + label)
|
||||
|
||||
func load_snes_savefile(filename: String = 'res://test.srm'):
|
||||
func load_snes_savefile(filename: String = FILENAME_TEST_SAVE):
|
||||
var save_file := File.new()
|
||||
match save_file.open(filename, File.READ):
|
||||
OK:
|
||||
|
@ -22,7 +23,7 @@ func load_snes_savefile(filename: String = 'res://test.srm'):
|
|||
return
|
||||
for i in 4:
|
||||
self.save_slot_buffers.append(SaveLoader.get_save_slot(save_file, i))
|
||||
self.save_slot_dicts.append(SaveLoader.deserialize_save_slot(self.save_slot_buffers[i].data_array))
|
||||
self.save_slot_dicts.append(SaveLoader.deserialize_save_slot(self.save_slot_buffers[i]))
|
||||
print('Loaded test save file')
|
||||
|
||||
func generate_known_good_results():
|
||||
|
@ -61,23 +62,43 @@ func test_save_loading() -> bool:
|
|||
print_debug('Failed to load known savefile results "%s"' % filename)
|
||||
return false
|
||||
|
||||
func test_save_serialization() -> bool:
|
||||
func test_save_slot_serialization() -> bool:
|
||||
if not self.save_slot_dicts:
|
||||
print_debug('test savefile not loaded')
|
||||
return false
|
||||
for i in 4:
|
||||
var bytes = SaveLoader.serialize_save_slot(self.save_slot_dicts[i])
|
||||
var bytes = SaveLoader.serialize_save_slot(self.save_slot_dicts[i]).data_array
|
||||
if bytes != self.save_slot_buffers[i].data_array:
|
||||
print_debug('Slot %d failed to serialize correctly, rescanning' % i)
|
||||
for j in 0x700:
|
||||
var b1: int = bytes[j]
|
||||
var b2: int = self.save_slot_buffers[i].data_array[j]
|
||||
if b1 != b2:
|
||||
print_debug('Mismatch occurs at byte %d: %d vs %d' % [j, b1, b2])
|
||||
print_debug('Mismatch occurs at byte $%04X (%d): $%02X (%d) vs $%02X (%d)' % [j, j, b1, b1, b2, b2])
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func test_snes_save_serialization() -> bool:
|
||||
if not self.save_slot_dicts:
|
||||
print_debug('test savefile not loaded')
|
||||
return false
|
||||
var bytes := SaveLoader.make_snes_save_file(self.save_slot_dicts)
|
||||
var file := File.new()
|
||||
match file.open(FILENAME_TEST_SAVE, File.READ):
|
||||
OK:
|
||||
var orig_bytes := file.get_buffer(file.get_len())
|
||||
if orig_bytes != bytes:
|
||||
print_debug('SNES Save File failed to serialize correctly, rescanning')
|
||||
for j in 0x2000:
|
||||
var b1: int = bytes[j]
|
||||
var b2: int = orig_bytes[j]
|
||||
if b1 != b2:
|
||||
print_debug('Mismatch occurs at byte $%04X (%d): $%02X (%d) vs $%02X (%d)' % [j, j, b1, b1, b2, b2])
|
||||
return false
|
||||
var error:
|
||||
print_debug('Failed to open test.srm for reading: %d' % error)
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
|
@ -89,5 +110,6 @@ func _ready() -> void:
|
|||
print_debug('Failed to open user directory')
|
||||
# generate_known_good_results() # Uncomment this to get your sample on first run
|
||||
test('SNES save file loaded to array of dictionaries', test_save_loading())
|
||||
test('SNES save file slots serialized from dictionaries', test_save_serialization())
|
||||
test('SNES save file slots serialized from dictionaries', test_save_slot_serialization())
|
||||
test('SNES save file serialized to original source bytes', test_snes_save_serialization())
|
||||
get_tree().quit()
|
||||
|
|
Loading…
Reference in New Issue