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)