355 lines
13 KiB
GDScript
355 lines
13 KiB
GDScript
extends SceneTree
|
|
|
|
const MainScene: PackedScene = preload("res://main/main.tscn")
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
root.size = Vector2i(1280, 720)
|
|
var main: Node = MainScene.instantiate()
|
|
root.add_child(main)
|
|
for _frame: int in 4:
|
|
await process_frame
|
|
if not bool(main.get("_application_initialized")):
|
|
main.call("_activate_selected_data_path", "", true)
|
|
for _frame: int in 8:
|
|
await process_frame
|
|
assert(bool(main.get("_application_initialized")))
|
|
|
|
var save_manager := main.get("_save_manager") as PlayerSaveManager
|
|
var player := main.get("_player") as Player
|
|
var data_root := main.get("_data_root") as PlayerDataRoot
|
|
assert(save_manager != null and player != null and data_root != null)
|
|
assert(save_manager.initialize_new_game(864209))
|
|
save_manager.set_autosave_enabled(true)
|
|
assert(player.wallet.restore_balance(4321))
|
|
assert(player.bag.add_item(&"coffee"))
|
|
assert(save_manager.save_now())
|
|
|
|
var save_path: String = str(save_manager.get("_save_path"))
|
|
assert(save_path.ends_with("player/player_save.nfsave"))
|
|
assert(FileAccess.file_exists(save_path))
|
|
assert(not FileAccess.file_exists(save_path.get_base_dir().path_join(
|
|
"player_save.json"
|
|
)))
|
|
_assert_opaque(save_path)
|
|
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"
|
|
)
|
|
var exported: Dictionary = save_manager.export_progression_archive(
|
|
archive_path
|
|
)
|
|
assert(bool(exported.get("ok", false)))
|
|
assert(FileAccess.file_exists(archive_path))
|
|
_assert_opaque(archive_path)
|
|
var inspected: Dictionary = save_manager.inspect_progression_archive(
|
|
archive_path
|
|
)
|
|
assert(bool(inspected.get("ok", false)))
|
|
assert(int(inspected.get("wallet_balance", -1)) == 4321)
|
|
assert(int(inspected.get("world_seed", -1)) == 864209)
|
|
|
|
assert(player.wallet.restore_balance(7))
|
|
assert(save_manager.save_now())
|
|
var imported: Dictionary = save_manager.import_progression_archive(
|
|
archive_path
|
|
)
|
|
assert(bool(imported.get("ok", false)))
|
|
assert(save_manager.load_player_data())
|
|
assert(player.wallet.get_balance() == 4321)
|
|
assert(save_manager.get_world_seed() == 864209)
|
|
|
|
var legacy_data: Dictionary = (
|
|
ProgressionSaveCodec.read_local_save(save_path)["data"]
|
|
)
|
|
assert(save_manager.delete_progression_save())
|
|
var legacy_path: String = save_path.get_base_dir().path_join(
|
|
"player_save.json"
|
|
)
|
|
var legacy_file := FileAccess.open(legacy_path, FileAccess.WRITE)
|
|
assert(legacy_file != null)
|
|
legacy_file.store_string(JSON.stringify(legacy_data, "\t"))
|
|
legacy_file.close()
|
|
assert(save_manager.load_player_data())
|
|
assert(FileAccess.file_exists(save_path))
|
|
assert(not FileAccess.file_exists(legacy_path))
|
|
_assert_opaque(save_path)
|
|
|
|
save_manager.set_autosave_enabled(false)
|
|
var save_slots := main.get("_save_slots") as PlayerSaveSlotCatalog
|
|
assert(save_slots != null)
|
|
assert(save_slots.configure(data_root, save_manager))
|
|
var adopted_slots: Array[Dictionary] = save_slots.list_slots()
|
|
assert(adopted_slots.size() == 1)
|
|
var adopted_slot_id: String = str(adopted_slots.front().get("slot_id", ""))
|
|
assert(bool(adopted_slots.front().get("legacy", false)))
|
|
assert(bool(adopted_slots.front().get("has_save", false)))
|
|
|
|
var duplicated: Dictionary = save_slots.duplicate_slot(
|
|
adopted_slot_id,
|
|
"another island",
|
|
WorldLayout.GENERATED,
|
|
97531,
|
|
)
|
|
assert(bool(duplicated.get("ok", false)))
|
|
var duplicated_slot_id: String = str(duplicated.get("slot_id", ""))
|
|
var duplicate_summary: Dictionary = save_slots.get_slot(duplicated_slot_id)
|
|
assert(int(duplicate_summary.get("wallet_balance", -1)) == 4321)
|
|
assert(int(duplicate_summary.get("world_seed", -1)) == 97531)
|
|
assert(save_slots.activate_slot(duplicated_slot_id))
|
|
assert(save_manager.load_player_data())
|
|
assert(player.wallet.get_balance() == 4321)
|
|
assert(save_manager.get_world_seed() == 97531)
|
|
assert(save_slots.rename_slot(duplicated_slot_id, "renamed island"))
|
|
|
|
var slot_archive: String = data_root.progression_backup_directory().path_join(
|
|
"save-slot-export.nfsave"
|
|
)
|
|
assert(bool(save_slots.export_slot(
|
|
duplicated_slot_id,
|
|
slot_archive,
|
|
).get("ok", false)))
|
|
var imported_slot: Dictionary = save_slots.import_slot(
|
|
slot_archive,
|
|
"imported island",
|
|
)
|
|
assert(bool(imported_slot.get("ok", false)))
|
|
var imported_slot_id: String = str(imported_slot.get("slot_id", ""))
|
|
assert(not save_slots.get_slot(imported_slot_id).is_empty())
|
|
assert(save_slots.delete_slot(duplicated_slot_id))
|
|
assert(save_slots.get_slot(duplicated_slot_id).is_empty())
|
|
assert(save_slots.list_slots().size() == 2)
|
|
|
|
var settings_panels: Array[Node] = main.find_children(
|
|
"*", "SettingsPanel", true, false
|
|
)
|
|
assert(settings_panels.size() == 2)
|
|
for settings_panel: SettingsPanel in settings_panels:
|
|
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 := (
|
|
settings_panel.get("_data_folder_dialog") as FileDialog
|
|
)
|
|
assert(data_folder_dialog != null)
|
|
assert(data_folder_dialog.current_dir == unchanged_root)
|
|
data_folder_dialog.hide()
|
|
settings_panel.call("_change_data_folder", unchanged_root + "/")
|
|
await process_frame
|
|
assert(data_root.root_path == unchanged_root)
|
|
assert(
|
|
(settings_panel.get_node("%SettingsFeedback") as Label).text
|
|
== "this data folder is already active."
|
|
)
|
|
var save_slots_page := main.find_child(
|
|
"SaveSlotsPage", true, false
|
|
) as SaveSlotsPage
|
|
assert(save_slots_page != null)
|
|
assert(save_slots_page.get_node("%ExportSlotButton") is Button)
|
|
assert(save_slots_page.get_node("%ImportSlotButton") is Button)
|
|
|
|
var unchanged_migration: Dictionary = (
|
|
PortableDataMigration.migrate_active_to(
|
|
data_root, unchanged_root + "/"
|
|
)
|
|
)
|
|
assert(bool(unchanged_migration.get("ok", false)))
|
|
assert(not bool(unchanged_migration.get("changed", true)))
|
|
assert(data_root.root_path == unchanged_root)
|
|
|
|
var migrated_root: String = data_root.root_path.get_base_dir().path_join(
|
|
"progression-migrated-data"
|
|
)
|
|
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."
|
|
)
|
|
var migrated_save: String = migrated_root.path_join(
|
|
"player/player_save.nfsave"
|
|
)
|
|
assert(FileAccess.file_exists(migrated_save))
|
|
assert(bool(
|
|
ProgressionSaveCodec.read_local_save(migrated_save).get("ok", false)
|
|
))
|
|
assert(FileAccess.file_exists(migrated_root.path_join(
|
|
"player/save_slots.json"
|
|
)))
|
|
var migrated_imported_slot: String = migrated_root.path_join(
|
|
"player/saves/%s.nfsave" % imported_slot_id
|
|
)
|
|
assert(FileAccess.file_exists(migrated_imported_slot))
|
|
assert(bool(
|
|
ProgressionSaveCodec.read_local_save(migrated_imported_slot).get(
|
|
"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:
|
|
await process_frame
|
|
await create_timer(0.1).timeout
|
|
print("Progression archive validation: PASS")
|
|
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())
|
|
var encoded_hex: String = bytes.hex_encode()
|
|
assert("save_version".to_utf8_buffer().hex_encode() not in encoded_hex)
|
|
assert("wallet".to_utf8_buffer().hex_encode() not in encoded_hex)
|