Preserve data across the Straywild transition

This commit is contained in:
Alexander Sellite 2026-08-25 22:18:20 -04:00
parent 737d1cf2e0
commit 98a27e2f53
28 changed files with 577 additions and 94 deletions

View file

@ -39,6 +39,15 @@ func _run() -> void:
var decoded: Dictionary = ProgressionSaveCodec.read_local_save(save_path)
assert(bool(decoded.get("ok", false)))
assert(int((decoded["data"] as Dictionary)["save_version"]) == 10)
var legacy_container_path: String = data_root.progression_backup_directory().path_join(
"netfishing-container.nfsave"
)
_assert_legacy_progression_container(
legacy_container_path,
decoded["data"] as Dictionary,
)
_assert_legacy_data_manifest(data_root.root_path.get_base_dir())
_assert_legacy_identity_backup(main, data_root)
var archive_path: String = data_root.progression_backup_directory().path_join(
"progression-test.nfsave"
@ -135,6 +144,9 @@ func _run() -> void:
assert(settings_panel.get_node_or_null("%ExportProgression") == null)
assert(settings_panel.get_node_or_null("%ImportProgression") == null)
var settings_panel: SettingsPanel = settings_panels.front() as SettingsPanel
settings_panel.set(
"_presentation_mode", SettingsPanel.PresentationMode.TITLE_EMBEDDED
)
var unchanged_root: String = data_root.root_path
settings_panel.call("_choose_data_folder")
var data_folder_dialog := (
@ -169,11 +181,14 @@ func _run() -> void:
var migrated_root: String = data_root.root_path.get_base_dir().path_join(
"progression-migrated-data"
)
var data_migration: Dictionary = PortableDataMigration.migrate_active_to(
data_root, migrated_root
settings_panel.call("_change_data_folder", migrated_root)
for _frame: int in 4:
await process_frame
assert(data_root.root_path == migrated_root)
assert(
(settings_panel.get_node("%SettingsFeedback") as Label).text
== "data folder changed."
)
assert(bool(data_migration.get("ok", false)))
assert(bool(data_migration.get("changed", false)))
var migrated_save: String = migrated_root.path_join(
"player/player_save.nfsave"
)
@ -193,6 +208,18 @@ func _run() -> void:
"ok", false
)
))
var live_slots: Array[Dictionary] = save_slots.list_slots()
assert(live_slots.size() == 2)
var live_created: Dictionary = save_slots.create_empty_slot(
"live root switch"
)
assert(bool(live_created.get("ok", false)))
var live_slot_id: String = str(live_created.get("slot_id", ""))
assert(save_slots.rename_slot(live_slot_id, "live root ready"))
assert(
str(save_slots.get_slot(live_slot_id).get("display_name", ""))
== "live root ready"
)
main.queue_free()
for _frame: int in 4:
@ -202,6 +229,124 @@ func _run() -> void:
quit()
func _assert_legacy_progression_container(
path: String,
payload: Dictionary,
) -> void:
var payload_json: String = JSON.stringify(payload)
var envelope: Dictionary = {
"magic": ProgressionSaveCodec.LEGACY_MAGIC,
"format_version": ProgressionSaveCodec.FORMAT_VERSION,
"kind": ProgressionSaveCodec.LOCAL_KIND,
"game_version": "0.18.1-alpha",
"created_at_unix": 1,
"payload_sha256": PortableFileGuard.hash_bytes(
payload_json.to_utf8_buffer()
),
"payload_json": payload_json,
}
var file: FileAccess = FileAccess.open_encrypted_with_pass(
path,
FileAccess.WRITE,
ProgressionSaveCodec.CONTAINER_PASSPHRASE,
)
assert(file != null)
file.store_string(JSON.stringify(envelope))
file.close()
var decoded: Dictionary = ProgressionSaveCodec.read_local_save(path)
assert(bool(decoded.get("ok", false)))
assert(bool(decoded.get("legacy_container", false)))
func _assert_legacy_data_manifest(parent: String) -> void:
var legacy_root: String = parent.path_join("netfishing-manifest-root")
assert(DirAccess.make_dir_recursive_absolute(legacy_root) == OK)
var legacy_id: String = "0123456789abcdef0123456789abcdef"
var legacy_manifest: Dictionary = {
"format_version": PlayerDataRoot.MANIFEST_VERSION,
"layout_version": PlayerDataRoot.LAYOUT_VERSION,
"application": PlayerDataRoot.LEGACY_APPLICATION_ID,
"root_id": legacy_id,
"created_at_unix": 1,
"last_opened_at_unix": 1,
}
var legacy_path: String = legacy_root.path_join(
PlayerDataRoot.LEGACY_MANIFEST_FILENAME
)
var file := FileAccess.open(legacy_path, FileAccess.WRITE)
assert(file != null)
file.store_string(JSON.stringify(legacy_manifest, "\t"))
file.close()
var legacy_data_root := PlayerDataRoot.new()
root.add_child(legacy_data_root)
assert(legacy_data_root.use_existing_root(legacy_root))
assert(legacy_data_root.root_id == legacy_id)
assert(FileAccess.file_exists(legacy_path))
var canonical_path: String = legacy_root.path_join(
PlayerDataRoot.MANIFEST_FILENAME
)
assert(FileAccess.file_exists(canonical_path))
var parser := JSON.new()
assert(parser.parse(FileAccess.get_file_as_string(canonical_path)) == OK)
assert((parser.data as Dictionary).get("application") == PlayerDataRoot.APPLICATION_ID)
legacy_data_root.queue_free()
func _assert_legacy_identity_backup(main: Node, data_root: PlayerDataRoot) -> void:
var player_identity := main.get("_player_identity") as PlayerIdentityStore
var identity_backups := main.get("_identity_backups") as IdentityBackupService
assert(player_identity != null and player_identity.is_ready())
assert(identity_backups != null)
var material: Dictionary = player_identity.export_identity_material()
var fingerprint: String = str(material.get("fingerprint", ""))
var private_key := CryptoKey.new()
assert(private_key.load_from_string(str(material.get("private_pem", ""))) == OK)
var canonical: PackedByteArray = NetworkIdentityCrypto.canonical_bytes_for_prefix(
NetworkIdentityCrypto.LEGACY_DOMAIN_PREFIX,
"identity_backup_self_test",
["player", fingerprint],
)
var context := HashingContext.new()
assert(context.start(HashingContext.HASH_SHA256) == OK)
context.update(canonical)
var signature: PackedByteArray = Crypto.new().sign(
HashingContext.HASH_SHA256,
context.finish(),
private_key,
)
var envelope: Dictionary = {
"magic": IdentityBackupService.LEGACY_MAGIC,
"format_version": IdentityBackupService.FORMAT_VERSION,
"identity_type": "player",
"algorithm": NetworkIdentityCrypto.ALGORITHM,
"private_pem": material.get("private_pem", ""),
"public_pem": material.get("public_pem", ""),
"fingerprint": fingerprint,
"created_at_unix": 1,
"source_device_id": data_root.device_id,
"self_signature": Marshalls.raw_to_base64(signature),
}
var backup_path: String = data_root.identity_backup_directory().path_join(
"netfishing-player.nfidentity"
)
var passphrase := "legacy-test-passphrase"
var file: FileAccess = FileAccess.open_encrypted_with_pass(
backup_path,
FileAccess.WRITE,
passphrase,
)
assert(file != null)
file.store_string(JSON.stringify(envelope))
file.close()
var inspected: Dictionary = identity_backups.inspect_backup(
backup_path,
passphrase,
"player",
)
assert(bool(inspected.get("ok", false)))
assert(str(inspected.get("fingerprint", "")) == fingerprint)
func _assert_opaque(path: String) -> void:
var bytes: PackedByteArray = PortableFileGuard.read_bytes(path)
assert(not bytes.is_empty())