diff --git a/QSB/Patches/PatchVendor.cs b/QSB/GameVendor.cs similarity index 62% rename from QSB/Patches/PatchVendor.cs rename to QSB/GameVendor.cs index 613f1704..d13fef44 100644 --- a/QSB/Patches/PatchVendor.cs +++ b/QSB/GameVendor.cs @@ -1,9 +1,9 @@ using System; -namespace QSB.Patches; +namespace QSB; [Flags] -public enum PatchVendor +public enum GameVendor { None = 0, Epic = 1, diff --git a/QSB/Menus/MenuManager.cs b/QSB/Menus/MenuManager.cs index 636803a0..eab7d1e1 100644 --- a/QSB/Menus/MenuManager.cs +++ b/QSB/Menus/MenuManager.cs @@ -134,7 +134,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart var text = QSBCore.DebugSettings.UseKcpTransport ? QSBLocalization.Current.PublicIPAddress : QSBLocalization.Current.ProductUserID; ConnectPopup.SetUpPopup(text, InputLibrary.menuConfirm, InputLibrary.cancel, new ScreenPrompt(QSBLocalization.Current.Connect), new ScreenPrompt(QSBLocalization.Current.Cancel), false); ConnectPopup.SetInputFieldPlaceholderText(text); - ExistingNewCopyPopup.SetUpPopup(QSBLocalization.Current.HostExistingOrNew, + ExistingNewCopyPopup.SetUpPopup(QSBLocalization.Current.HostExistingOrNewOrCopy, InputLibrary.menuConfirm, InputLibrary.confirm2, InputLibrary.signalscope, @@ -370,7 +370,7 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart TwoButtonInfoPopup.OnPopupConfirm += () => OnCloseInfoPopup(true); TwoButtonInfoPopup.OnPopupCancel += () => OnCloseInfoPopup(false); - ExistingNewCopyPopup = CreateFourChoicePopup(QSBLocalization.Current.HostExistingOrNew, + ExistingNewCopyPopup = CreateFourChoicePopup(QSBLocalization.Current.HostExistingOrNewOrCopy, QSBLocalization.Current.ExistingSave, QSBLocalization.Current.NewSave, QSBLocalization.Current.CopySave, @@ -381,7 +381,18 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart { DebugLog.DebugWrite("Replacing multiplayer save with singleplayer save"); QSBCore.IsInMultiplayer = true; - StandaloneProfileManager.SharedInstance.SaveGame(QSBProfileManager._currentProfile.gameSave, null, null, null); + + if (QSBCore.IsStandalone) + { + var currentProfile = QSBStandaloneProfileManager.SharedInstance.currentProfile; + QSBStandaloneProfileManager.SharedInstance.SaveGame(currentProfile.gameSave, null, null, null); + } + else + { + var gameSave = QSBMSStoreProfileManager.SharedInstance.currentProfileGameSave; + QSBMSStoreProfileManager.SharedInstance.SaveGame(gameSave, null, null, null); + } + Host(false); }; @@ -394,7 +405,18 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart { DebugLog.DebugWrite("Replacing multiplayer save with singleplayer save"); QSBCore.IsInMultiplayer = true; - StandaloneProfileManager.SharedInstance.SaveGame(QSBProfileManager._currentProfile.gameSave, null, null, null); + + if (QSBCore.IsStandalone) + { + var currentProfile = QSBStandaloneProfileManager.SharedInstance.currentProfile; + QSBStandaloneProfileManager.SharedInstance.SaveGame(currentProfile.gameSave, null, null, null); + } + else + { + var gameSave = QSBMSStoreProfileManager.SharedInstance.currentProfileGameSave; + QSBMSStoreProfileManager.SharedInstance.SaveGame(gameSave, null, null, null); + } + Host(false); }; @@ -544,9 +566,20 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart private void PreHost() { - var profile = QSBProfileManager._currentProfile; - var doesSingleplayerSaveExist = profile.gameSave.loopCount > 1; - var doesMultiplayerSaveExist = profile.multiplayerGameSave.loopCount > 1; + bool doesSingleplayerSaveExist = false; + bool doesMultiplayerSaveExist = false; + if (!QSBCore.IsStandalone) + { + var manager = QSBMSStoreProfileManager.SharedInstance; + doesSingleplayerSaveExist = manager.currentProfileGameSave.loopCount > 1; + doesMultiplayerSaveExist = manager.currentProfileMultiplayerGameSave.loopCount > 1; + } + else + { + var profile = QSBStandaloneProfileManager.SharedInstance.currentProfile; + doesSingleplayerSaveExist = profile.gameSave.loopCount > 1; + doesMultiplayerSaveExist = profile.multiplayerGameSave.loopCount > 1; + } if (doesSingleplayerSaveExist && doesMultiplayerSaveExist) { @@ -587,8 +620,16 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart else { DebugLog.DebugWrite("Loading multiplayer game..."); - var profile = QSBProfileManager._currentProfile; - PlayerData.Init(profile.multiplayerGameSave, profile.settingsSave, profile.graphicsSettings, profile.inputJSON); + if (QSBCore.IsStandalone) + { + var profile = QSBStandaloneProfileManager.SharedInstance.currentProfile; + PlayerData.Init(profile.multiplayerGameSave, profile.settingsSave, profile.graphicsSettings, profile.inputJSON); + } + else + { + var manager = QSBMSStoreProfileManager.SharedInstance; + PlayerData.Init(manager.currentProfileMultiplayerGameSave, manager.currentProfileGameSettings, manager.currentProfileGraphicsSettings, manager.currentProfileInputJSON); + } } _intentionalDisconnect = false; @@ -629,8 +670,16 @@ internal class MenuManager : MonoBehaviour, IAddComponentOnStart QSBCore.IsInMultiplayer = true; _intentionalDisconnect = false; - var profile = QSBProfileManager._currentProfile; - PlayerData.Init(profile.multiplayerGameSave, profile.settingsSave, profile.graphicsSettings, profile.inputJSON); + if (QSBCore.IsStandalone) + { + var profile = QSBStandaloneProfileManager.SharedInstance.currentProfile; + PlayerData.Init(profile.multiplayerGameSave, profile.settingsSave, profile.graphicsSettings, profile.inputJSON); + } + else + { + var manager = QSBMSStoreProfileManager.SharedInstance; + PlayerData.Init(manager.currentProfileMultiplayerGameSave, manager.currentProfileGameSettings, manager.currentProfileGraphicsSettings, manager.currentProfileInputJSON); + } var address = ConnectPopup.GetInputText(); if (address == string.Empty) diff --git a/QSB/Patches/QSBPatch.cs b/QSB/Patches/QSBPatch.cs index 1a7ec665..42853c67 100644 --- a/QSB/Patches/QSBPatch.cs +++ b/QSB/Patches/QSBPatch.cs @@ -8,7 +8,7 @@ public abstract class QSBPatch { public abstract QSBPatchTypes Type { get; } - public virtual PatchVendor PatchVendor => PatchVendor.Epic | PatchVendor.Steam | PatchVendor.Gamepass; + public virtual GameVendor PatchVendor => GameVendor.Epic | GameVendor.Steam | GameVendor.Gamepass; public void DoPatches(Harmony instance) => instance.PatchAll(GetType()); diff --git a/QSB/Patches/QSBPatchManager.cs b/QSB/Patches/QSBPatchManager.cs index 8dfa7b4d..e49f88f3 100644 --- a/QSB/Patches/QSBPatchManager.cs +++ b/QSB/Patches/QSBPatchManager.cs @@ -14,7 +14,6 @@ public static class QSBPatchManager private static readonly List _patchList = new(); private static readonly List _patchedTypes = new(); - private static PatchVendor _vendor = PatchVendor.None; public static Dictionary TypeToInstance = new(); @@ -30,30 +29,6 @@ public static class QSBPatchManager TypeToInstance.Add(type, new Harmony(type.ToString())); } - var gameAssemblyTypes = typeof(AstroObject).Assembly.GetTypes(); - var isEpic = gameAssemblyTypes.Any(x => x.Name == "EpicEntitlementRetriever"); - var isSteam = gameAssemblyTypes.Any(x => x.Name == "SteamEntitlementRetriever"); - var isUWP = gameAssemblyTypes.Any(x => x.Name == "MSStoreEntitlementRetriever"); - - if (isEpic && !isSteam && !isUWP) - { - _vendor = PatchVendor.Epic; - } - else if (!isEpic && isSteam && !isUWP) - { - _vendor = PatchVendor.Steam; - } - else if (!isEpic && !isSteam && isUWP) - { - _vendor = PatchVendor.Gamepass; - } - else - { - // ??? - DebugLog.ToConsole($"FATAL - Could not determine game vendor.", MessageType.Fatal); - } - - DebugLog.DebugWrite($"Determined game vendor as {_vendor}", MessageType.Info); DebugLog.DebugWrite("Patch Manager ready.", MessageType.Success); } @@ -67,7 +42,7 @@ public static class QSBPatchManager OnPatchType?.SafeInvoke(type); //DebugLog.DebugWrite($"Patch block {Enum.GetName(typeof(QSBPatchTypes), type)}", MessageType.Info); - foreach (var patch in _patchList.Where(x => x.Type == type && x.PatchVendor.HasFlag(_vendor))) + foreach (var patch in _patchList.Where(x => x.Type == type && x.PatchVendor.HasFlag(QSBCore.GameVendor))) { //DebugLog.DebugWrite($" - Patching in {patch.GetType().Name}", MessageType.Info); try diff --git a/QSB/QSB.csproj b/QSB/QSB.csproj index 0891399e..597afd06 100644 --- a/QSB/QSB.csproj +++ b/QSB/QSB.csproj @@ -90,7 +90,7 @@ - + diff --git a/QSB/QSBCore.cs b/QSB/QSBCore.cs index dd53d3e7..b3cdefae 100644 --- a/QSB/QSBCore.cs +++ b/QSB/QSBCore.cs @@ -6,6 +6,7 @@ using QSB.Localization; using QSB.Menus; using QSB.Patches; using QSB.QuantumSync; +using QSB.SaveSync; using QSB.Utility; using QSB.WorldSync; using System; @@ -55,6 +56,11 @@ public class QSBCore : ModBehaviour Application.version.Split('.').Take(3).Join(delimiter: "."); public static bool DLCInstalled => EntitlementsManager.IsDlcOwned() == EntitlementsManager.AsyncOwnershipStatus.Owned; public static bool IncompatibleModsAllowed { get; private set; } + public static GameVendor GameVendor { get; private set; } = GameVendor.None; + public static bool IsStandalone => GameVendor is GameVendor.Epic or GameVendor.Steam; + public static IProfileManager ProfileManager => IsStandalone + ? QSBStandaloneProfileManager.SharedInstance + : QSBMSStoreProfileManager.SharedInstance; public static IMenuAPI MenuApi { get; private set; } public static DebugSettings DebugSettings { get; private set; } = new(); public static Storage Storage { get; private set; } = new(); @@ -70,6 +76,34 @@ public class QSBCore : ModBehaviour "Vesper.AutoResume" }; + private static void DetermineGameVendor() + { + var gameAssemblyTypes = typeof(AstroObject).Assembly.GetTypes(); + var isEpic = gameAssemblyTypes.Any(x => x.Name == "EpicEntitlementRetriever"); + var isSteam = gameAssemblyTypes.Any(x => x.Name == "SteamEntitlementRetriever"); + var isUWP = gameAssemblyTypes.Any(x => x.Name == "MSStoreEntitlementRetriever"); + + if (isEpic && !isSteam && !isUWP) + { + GameVendor = GameVendor.Epic; + } + else if (!isEpic && isSteam && !isUWP) + { + GameVendor = GameVendor.Steam; + } + else if (!isEpic && !isSteam && isUWP) + { + GameVendor = GameVendor.Gamepass; + } + else + { + // ??? + DebugLog.ToConsole($"FATAL - Could not determine game vendor.", MessageType.Fatal); + } + + DebugLog.DebugWrite($"Determined game vendor as {GameVendor}", MessageType.Info); + } + public void Awake() { EpicRerouter.ModSide.Interop.Go(); @@ -77,6 +111,11 @@ public class QSBCore : ModBehaviour // no, we cant localize this - languages are loaded after the splash screen UIHelper.ReplaceUI(UITextType.PleaseUseController, "Quantum Space Buddies is best experienced with friends..."); + + DetermineGameVendor(); + + QSBPatchManager.Init(); + QSBPatchManager.DoPatchType(QSBPatchTypes.OnModStart); } public void Start() @@ -133,7 +172,6 @@ public class QSBCore : ModBehaviour return; } - QSBPatchManager.Init(); DeterministicManager.Init(); QSBLocalization.Init(); @@ -144,8 +182,6 @@ public class QSBCore : ModBehaviour QSBWorldSync.Managers = components.OfType().ToArray(); QSBPatchManager.OnPatchType += OnPatchType; QSBPatchManager.OnUnpatchType += OnUnpatchType; - - QSBPatchManager.DoPatchType(QSBPatchTypes.OnModStart); } private static void OnPatchType(QSBPatchTypes type) diff --git a/QSB/QSBNetworkManager.cs b/QSB/QSBNetworkManager.cs index 66e68d2f..78fb5359 100644 --- a/QSB/QSBNetworkManager.cs +++ b/QSB/QSBNetworkManager.cs @@ -102,7 +102,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart base.Awake(); InitPlayerName(); - StandaloneProfileManager.SharedInstance.OnProfileSignInComplete += _ => InitPlayerName(); + QSBCore.ProfileManager.OnProfileSignInComplete += _ => InitPlayerName(); playerPrefab = QSBCore.NetworkAssetBundle.LoadAsset("Assets/Prefabs/NETWORK_Player_Body.prefab"); playerPrefab.GetRequiredComponent().SetValue("m_AssetId", 1.ToGuid().ToString("N")); @@ -157,15 +157,22 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart { try { - var titleScreenManager = FindObjectOfType(); - var profileManager = titleScreenManager._profileManager; - if (profileManager.GetType().Name == "MSStoreProfileManager") + if (!QSBCore.IsStandalone) { - PlayerName = (string)profileManager.GetType().GetProperty("userDisplayName").GetValue(profileManager); + PlayerName = QSBMSStoreProfileManager.SharedInstance.userDisplayName; } else { - PlayerName = QSBProfileManager._currentProfile.profileName; + var currentProfile = QSBStandaloneProfileManager.SharedInstance.currentProfile; + + if (currentProfile == null) + { + // probably havent created a profile yet + Delay.RunWhen(() => QSBStandaloneProfileManager.SharedInstance.currentProfile != null, () => InitPlayerName()); + return; + } + + PlayerName = currentProfile.profileName; } } catch (Exception ex) diff --git a/QSB/SaveSync/Patches/InGameProfileMenuManagerPatches.cs b/QSB/SaveSync/Patches/InGameProfileMenuManagerPatches.cs new file mode 100644 index 00000000..f57636c9 --- /dev/null +++ b/QSB/SaveSync/Patches/InGameProfileMenuManagerPatches.cs @@ -0,0 +1,33 @@ +using HarmonyLib; +using QSB.Patches; + +namespace QSB.SaveSync.Patches; + +[HarmonyPatch(typeof(InGameProfileMenuManager))] +internal class InGameProfileMenuManagerPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + + [HarmonyPrefix] + [HarmonyPatch(nameof(InGameProfileMenuManager.InitializeOnAwake))] + public static bool InitializeOnAwake(InGameProfileMenuManager __instance) + { + if (!__instance._initialized) + { + TextTranslation.Get().OnLanguageChanged += __instance.UpdateLanguage; + __instance.UpdateLanguage(); + __instance._profileManager = QSBCore.ProfileManager; + __instance._profileManager.OnProfileSignInComplete += __instance.OnProfileSignInComplete; + __instance._profileManager.OnProfileSignOutComplete += __instance.OnProfileSignOutComplete; + __instance._profileManager.OnProfileReadDone += __instance.OnProfileReadDone; + __instance._returnToGameSubmitAction.OnSubmitAction += __instance.OnResumeGameBtnSubmit; + __instance._returnToTitleSubmitAction.OnSubmitAction += __instance.OnTitleSubmitAction; + LoadManager.OnStartSceneLoad += __instance.OnStartSceneLoad; + LoadManager.OnCompleteSceneLoad += __instance.OnCompleteSceneLoad; + GlobalMessenger.AddListener("PlayerResurrection", new Callback(__instance.OnPlayerResurrection)); + __instance._initialized = true; + } + + return false; + } +} diff --git a/QSB/SaveSync/Patches/PlayerDataPatches.cs b/QSB/SaveSync/Patches/PlayerDataPatches.cs new file mode 100644 index 00000000..bdf88b09 --- /dev/null +++ b/QSB/SaveSync/Patches/PlayerDataPatches.cs @@ -0,0 +1,54 @@ +using HarmonyLib; +using QSB.Patches; +using UnityEngine; + +namespace QSB.SaveSync.Patches; + +[HarmonyPatch(typeof(PlayerData))] +internal class PlayerDataPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerData.ResetGame))] + public static bool ResetGame() + { + PlayerData._currentGameSave = new GameSave(); + QSBCore.ProfileManager.SaveGame(PlayerData._currentGameSave, null, null, null); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerData.SaveCurrentGame))] + public static bool SaveCurrentGame() + { + PlayerData._currentGameSave.version = Application.version; + QSBCore.ProfileManager.SaveGame(PlayerData._currentGameSave, null, null, null); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerData.SaveInputSettings))] + public static bool SaveInputSettings() + { + QSBCore.ProfileManager.SaveGame(null, null, null, PlayerData.inputJSON); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerData.SaveSettings))] + public static bool SaveSettings() + { + QSBCore.ProfileManager.SaveGame(null, PlayerData._settingsSave, PlayerData._graphicsSettings, PlayerData.inputJSON); + return false; + } + + // this is actually still StandaloneProfileManager in the gamepass dll. game bug? + [HarmonyPrefix] + [HarmonyPatch(nameof(PlayerData.IsBusy))] + public static bool IsBusy(ref bool __result) + { + __result = QSBCore.ProfileManager.isBusyWithFileOps; + return false; + } +} diff --git a/QSB/SaveSync/Patches/ProfileManagerPatches.cs b/QSB/SaveSync/Patches/ProfileManagerPatches.cs deleted file mode 100644 index 144e8949..00000000 --- a/QSB/SaveSync/Patches/ProfileManagerPatches.cs +++ /dev/null @@ -1,1056 +0,0 @@ -using HarmonyLib; -using Newtonsoft.Json; -using QSB.Patches; -using QSB.Utility; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using UnityEngine; -using UnityEngine.Rendering; -using UnityEngine.UI; - -namespace QSB.SaveSync.Patches; - -/// -/// all these patches are to use QSBProfileData instead of the existing profile in order to support a separate multiplayer save -/// -internal class ProfileManagerPatches : QSBPatch -{ - public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; - public override PatchVendor PatchVendor => PatchVendor.Steam | PatchVendor.Epic; - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.LoadSaveFilesFromProfiles))] - public static bool LoadSaveFilesFromProfiles(StandaloneProfileManager __instance) - { - __instance.MarkBusyWithFileOps(isBusy: true); - foreach (var profile in QSBProfileManager._profiles) - { - var path = __instance._profilesPath + "/" + profile.profileName; - GameSave saveData = null; - GameSave multSaveData = null; - SettingsSave settingsData = null; - GraphicSettings graphicsData = null; - var inputJSON = ""; - if (Directory.Exists(path)) - { - Stream stream = null; - var directoryInfo = new DirectoryInfo(path); - profile.brokenSaveData = __instance.TryLoadSaveData(null, ref stream, "data.owsave", directoryInfo, out saveData); - profile.brokenMultSaveData = __instance.TryLoadSaveData(null, ref stream, "dataMult.owsave", directoryInfo, out multSaveData); - profile.brokenSettingsData = __instance.TryLoadSaveData(null, ref stream, "player.owsett", directoryInfo, out settingsData); - profile.brokenGfxSettingsData = __instance.TryLoadSaveData(null, ref stream, "graphics.owsett", directoryInfo, out graphicsData); - profile.brokenRebindingData = __instance.TryLoadInputBindingsSave(null, ref stream, directoryInfo, out inputJSON); - } - - var profilePath = __instance._profileBackupPath + "/" + profile.profileName; - var savePath = profilePath + "/data.owsave"; - var multSavePath = profilePath + "/dataMult.owsave"; - var settingsPath = profilePath + "/player.owsett"; - var graphicsPath = profilePath + "/graphics.owsett"; - var inputsPath = profilePath + "/input_new.owsett"; - - if (saveData == null) - { - profile.brokenSaveData = File.Exists(savePath); - saveData = new GameSave(); - UnityEngine.Debug.LogError("Could not find game save for " + profile.profileName); - } - - if (multSaveData == null) - { - profile.brokenMultSaveData = File.Exists(multSavePath); - multSaveData = new GameSave(); - UnityEngine.Debug.LogError("Could not find multiplayer game save for " + profile.profileName); - } - - if (settingsData == null) - { - profile.brokenSettingsData = File.Exists(settingsPath); - settingsData = new SettingsSave(); - UnityEngine.Debug.LogError("Could not find game settings for " + profile.profileName); - } - - if (graphicsData == null) - { - profile.brokenGfxSettingsData = File.Exists(graphicsPath); - graphicsData = new GraphicSettings(init: true); - UnityEngine.Debug.LogError("Could not find graphics settings for " + profile.profileName); - } - - if (inputJSON == "") - { - profile.brokenRebindingData = File.Exists(inputsPath); - inputJSON = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); - UnityEngine.Debug.LogError("Could not find input action settings for " + profile.profileName); - } - - profile.gameSave = saveData; - profile.multiplayerGameSave = multSaveData; - profile.settingsSave = settingsData; - profile.graphicsSettings = graphicsData; - profile.inputJSON = inputJSON; - } - - __instance.MarkBusyWithFileOps(isBusy: false); - if (__instance.CurrentProfileHasBrokenData()) - { - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnBrokenDataExists)); - } - - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileReadDone)); - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.LoadProfiles))] - public static bool LoadProfiles(StandaloneProfileManager __instance) - { - __instance.MarkBusyWithFileOps(true); - QSBProfileManager._profiles.Clear(); - if (Directory.Exists(__instance._profilesPath)) - { - QSBProfileData profileData = null; - Stream stream = null; - var files = new DirectoryInfo(__instance._profilesPath).GetFiles("*.owprofile"); - foreach (var fileInfo in files) - { - DebugLog.DebugWrite(fileInfo.Name); - try - { - stream = null; - stream = File.Open(fileInfo.FullName, FileMode.Open); - var jsonTextReader = new JsonTextReader(new StreamReader(stream)); - try - { - profileData = __instance._jsonSerializer.Deserialize(jsonTextReader); - } - catch - { - stream.Position = 0L; - profileData = (QSBProfileData)__instance._binaryFormatter.Deserialize(stream); - } - finally - { - jsonTextReader.Close(); - } - - if (profileData == null) - { - DebugLog.DebugWrite("Profile at " + fileInfo.FullName + " null. Skipping."); - } - else - { - QSBProfileManager._profiles.Add(profileData); - } - } - catch (Exception ex) - { - DebugLog.ToConsole("[" + ex.Message + "] Failed loading profile at " + fileInfo.Name, OWML.Common.MessageType.Error); - stream?.Close(); - } - } - } - else - { - DebugLog.DebugWrite($"{__instance._profilesPath} does not exist"); - } - - __instance.MarkBusyWithFileOps(false); - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.CurrentProfileHasBrokenData))] - public static bool CurrentProfileHasBrokenData(StandaloneProfileManager __instance, out bool __result) - { - var currentProfile = QSBProfileManager._currentProfile; - - if (currentProfile == null) - { - Debug.LogError("QSBProfileManager.CurrentProfileHasBrokenData We should never get here outside of the Unity Editor"); - __result = false; - return false; - } - - __result = currentProfile.brokenSaveData - || currentProfile.brokenMultSaveData - || currentProfile.brokenSettingsData - || currentProfile.brokenGfxSettingsData - || currentProfile.brokenRebindingData; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.BackupExistsForBrokenData))] - public static bool BackupExistsForBrokenData(StandaloneProfileManager __instance, out bool __result) - { - var text = __instance._profileBackupPath + "/" + QSBProfileManager._currentProfile.profileName; - var savePath = text + "/data.owsave"; - var multSavePath = text + "/dataMult.owsave"; - var settingsPath = text + "/player.owsett"; - var graphicsPath = text + "/graphics.owsett"; - var inputsPath = text + "/input_new.owsett"; - - if (QSBProfileManager._currentProfile.brokenSaveData && File.Exists(savePath)) - { - __result = true; - return false; - } - - if (QSBProfileManager._currentProfile.brokenMultSaveData && File.Exists(multSavePath)) - { - __result = true; - return false; - } - - if (QSBProfileManager._currentProfile.brokenSettingsData && File.Exists(settingsPath)) - { - __result = true; - return false; - } - - if (QSBProfileManager._currentProfile.brokenGfxSettingsData && File.Exists(graphicsPath)) - { - __result = true; - return false; - } - - if (QSBProfileManager._currentProfile.brokenRebindingData && File.Exists(inputsPath)) - { - __result = true; - return false; - } - - __result = false; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.RestoreCurrentProfileBackup))] - public static bool RestoreCurrentProfileBackup(StandaloneProfileManager __instance) - { - __instance.MarkBusyWithFileOps(isBusy: true); - var profilePath = __instance._profilesPath + "/" + QSBProfileManager._currentProfile.profileName; - var savePath = profilePath + "/data.owsave"; - var multSavePath = profilePath + "/dataMult.owsave"; - var settingsPath = profilePath + "/player.owsett"; - var graphicsPath = profilePath + "/graphics.owsett"; - var inputsPath = profilePath + "/input_new.owsett"; - - var profileBackupPath = __instance._profileBackupPath + "/" + QSBProfileManager._currentProfile.profileName; - var saveBackupPath = profileBackupPath + "/data.owsave"; - var multSaveBackupPath = profileBackupPath + "/dataMult.owsave"; - var settingsBackupPath = profileBackupPath + "/player.owsett"; - var graphicsBackupPath = profileBackupPath + "/graphics.owsett"; - var inputsBackupPath = profileBackupPath + "/input_new.owsett"; - - Stream stream = null; - try - { - if (!Directory.Exists(__instance._profilesPath)) - { - Directory.CreateDirectory(__instance._profilesPath); - } - - if (!Directory.Exists(__instance._profileTempPath)) - { - Directory.CreateDirectory(__instance._profileTempPath); - } - - if (!Directory.Exists(__instance._profileBackupPath)) - { - Directory.CreateDirectory(__instance._profileBackupPath); - } - - if (!Directory.Exists(profilePath)) - { - Directory.CreateDirectory(profilePath); - } - - if (!Directory.Exists(profileBackupPath)) - { - Directory.CreateDirectory(profileBackupPath); - } - - var di = new DirectoryInfo(profileBackupPath); - - if (QSBProfileManager._currentProfile.brokenSaveData && File.Exists(saveBackupPath)) - { - QSBProfileManager._currentProfile.gameSave = LoadAndCopyBackupSave("data.owsave", saveBackupPath, savePath); - } - - if (QSBProfileManager._currentProfile.brokenMultSaveData && File.Exists(multSaveBackupPath)) - { - QSBProfileManager._currentProfile.multiplayerGameSave = LoadAndCopyBackupSave("dataMult.owsave", multSaveBackupPath, multSavePath); - } - - if (QSBProfileManager._currentProfile.brokenSettingsData && File.Exists(settingsBackupPath)) - { - QSBProfileManager._currentProfile.settingsSave = LoadAndCopyBackupSave("player.owsett", settingsBackupPath, settingsPath); - } - - if (QSBProfileManager._currentProfile.brokenGfxSettingsData && File.Exists(graphicsBackupPath)) - { - QSBProfileManager._currentProfile.graphicsSettings = LoadAndCopyBackupSave("graphics.owsett", graphicsBackupPath, graphicsPath); - } - - if (QSBProfileManager._currentProfile.brokenRebindingData && File.Exists(inputsBackupPath)) - { - __instance.TryLoadInputBindingsSave(null, ref stream, di, out var inputJSON); - if (inputJSON != "") - { - QSBProfileManager._currentProfile.inputJSON = inputJSON; - File.Copy(inputsBackupPath, inputsPath, overwrite: true); - } - else - { - UnityEngine.Debug.LogError("Could not load backup input bindings save."); - } - - stream?.Close(); - stream = null; - } - - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnBackupDataRestored)); - - T LoadAndCopyBackupSave(string fileName, string backupPath, string fullPath) where T : class - { - __instance.TryLoadSaveData(null, ref stream, fileName, di, out var saveData); - if (saveData != null) - { - File.Copy(backupPath, fullPath, overwrite: true); - } - else - { - UnityEngine.Debug.LogError("Could not load backup " + typeof(T).Name + " save."); - } - - stream?.Close(); - stream = null; - return saveData; - } - } - catch (Exception ex) - { - stream?.Close(); - UnityEngine.Debug.LogError("Exception during backup restore: " + ex.Message); - __instance.MarkBusyWithFileOps(isBusy: false); - } - - __instance.MarkBusyWithFileOps(isBusy: false); - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.TrySaveProfile))] - public static bool TrySaveProfile() - { - DebugLog.DebugWrite($"Error - StandaloneProfileManager.TrySaveProfile should not be used anymore." + - $"{Environment.NewLine}Called by : {Environment.NewLine}{Environment.StackTrace}", OWML.Common.MessageType.Error); - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.SaveGame))] - public static bool SaveGame(StandaloneProfileManager __instance, GameSave gameSave, SettingsSave settSave, GraphicSettings graphicSettings, string inputBindings) - { - if (__instance.isBusyWithFileOps || LoadManager.IsBusy()) - { - __instance._pendingGameSave = gameSave; - __instance._pendingSettingsSave = settSave; - __instance._pendingGfxSettingsSave = graphicSettings; - __instance._pendingInputJSONSave = inputBindings; - } - else - { - TrySaveProfile(QSBProfileManager._currentProfile, gameSave, settSave, graphicSettings, inputBindings); - } - - return false; - } - - private static bool TrySaveProfile(QSBProfileData profileData, GameSave gameSave, SettingsSave settingsSave, GraphicSettings graphicsSettings, string inputJson) - { - var profileManager = StandaloneProfileManager.SharedInstance; - - profileManager.MarkBusyWithFileOps(isBusy: true); - var profilePath = profileManager._profilesPath + "/" + profileData.profileName; - var profileManifestPath = profileManager._profilesPath + "/" + profileData.profileName + ".owprofile"; - var saveDataPath = profilePath + "/data.owsave"; - var multSaveDataPath = profilePath + "/dataMult.owsave"; - var settingsPath = profilePath + "/player.owsett"; - var graphicsPath = profilePath + "/graphics.owsett"; - var inputsPath = profilePath + "/input_new.owsett"; - - var tempProfilePath = profileManager._profileTempPath + "/GameData"; - var tempProfileManifestPath = profileManager._profileTempPath + "/CurrentProfile.owprofile"; - var tempSaveDataPath = tempProfilePath + "/data.owsave"; - var tempMultSaveDataPath = tempProfilePath + "/dataMult.owsave"; - var tempSettingsPath = tempProfilePath + "/player.owsett"; - var tempGraphicsPath = tempProfilePath + "/graphics.owsett"; - var tempInputsPath = tempProfilePath + "/input_new.owsett"; - - var backupProfilePath = profileManager._profileBackupPath + "/" + profileData.profileName; - var backupSaveDataPath = backupProfilePath + "/data.owsave"; - var backupMultSaveDataPath = backupProfilePath + "/dataMult.owsave"; - var backupSettingsPath = backupProfilePath + "/player.owsett"; - var backupGraphicsPath = backupProfilePath + "/graphics.owsett"; - var backupInputsPath = backupProfilePath + "/input_new.owsett"; - - Stream stream = null; - try - { - // Create folders if they don't exist - - if (!Directory.Exists(profileManager._profilesPath)) - { - Directory.CreateDirectory(profileManager._profilesPath); - } - - if (!Directory.Exists(profileManager._profileTempPath)) - { - Directory.CreateDirectory(profileManager._profileTempPath); - } - - if (!Directory.Exists(profileManager._profileBackupPath)) - { - Directory.CreateDirectory(profileManager._profileBackupPath); - } - - if (!Directory.Exists(profilePath)) - { - Directory.CreateDirectory(profilePath); - } - - if (!Directory.Exists(tempProfilePath)) - { - Directory.CreateDirectory(tempProfilePath); - } - - if (!Directory.Exists(backupProfilePath)) - { - Directory.CreateDirectory(backupProfilePath); - } - - // create temp files - - SaveData(tempProfileManifestPath, profileData); - if (gameSave != null) - { - if (QSBCore.IsInMultiplayer) - { - profileData.multiplayerGameSave = SaveData(tempMultSaveDataPath, gameSave); - } - else - { - profileData.gameSave = SaveData(tempSaveDataPath, gameSave); - } - } - - if (settingsSave != null) - { - profileData.settingsSave = SaveData(tempSettingsPath, settingsSave); - } - - if (graphicsSettings != null) - { - profileData.graphicsSettings = SaveData(tempGraphicsPath, graphicsSettings); - } - - if (inputJson != null) - { - File.WriteAllText(tempInputsPath, inputJson); - profileData.inputJSON = inputJson; - } - - // create backups of old files - - if (File.Exists(saveDataPath)) - { - File.Copy(saveDataPath, backupSaveDataPath, overwrite: true); - } - - if (File.Exists(multSaveDataPath)) - { - File.Copy(multSaveDataPath, backupMultSaveDataPath, overwrite: true); - } - - if (File.Exists(settingsPath)) - { - File.Copy(settingsPath, backupSettingsPath, overwrite: true); - } - - if (File.Exists(graphicsPath)) - { - File.Copy(graphicsPath, backupGraphicsPath, overwrite: true); - } - - if (File.Exists(inputsPath)) - { - File.Copy(inputsPath, backupInputsPath, overwrite: true); - } - - // delete old files and move temp files - - File.Delete(profileManifestPath); - File.Move(tempProfileManifestPath, profileManifestPath); - - if (gameSave != null) - { - if (QSBCore.IsInMultiplayer) - { - File.Delete(multSaveDataPath); - File.Move(tempMultSaveDataPath, multSaveDataPath); - } - else - { - File.Delete(saveDataPath); - File.Move(tempSaveDataPath, saveDataPath); - } - } - - if (settingsSave != null) - { - File.Delete(settingsPath); - File.Move(tempSettingsPath, settingsPath); - } - - if (graphicsSettings != null) - { - File.Delete(graphicsPath); - File.Move(tempGraphicsPath, graphicsPath); - } - - if (inputJson != null) - { - File.Delete(inputsPath); - File.Move(tempInputsPath, inputsPath); - } - - Debug.Log("Wrote save data to file for " + profileData.profileName); - profileManager.RaiseEvent(nameof(StandaloneProfileManager.OnProfileDataSaved), true); - } - catch (Exception ex) - { - if (stream != null) - { - stream.Close(); - } - - profileManager.RaiseEvent(nameof(StandaloneProfileManager.OnProfileDataSaved), false); - - Debug.LogError("[" + ex.Message + "] Error saving file for " + profileData.profileName); - profileManager.MarkBusyWithFileOps(isBusy: false); - return false; - } - - profileManager.MarkBusyWithFileOps(isBusy: false); - return true; - - T SaveData(string filePath, T data) - { - stream = File.Open(filePath, FileMode.Create); - using (JsonWriter jsonWriter = new JsonTextWriter(new StreamWriter(stream))) - { - profileManager._jsonSerializer.Serialize(jsonWriter, data); - } - - stream = null; - return data; - } - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.InitializeProfileData))] - public static bool InitializeProfileData(StandaloneProfileManager __instance) - { - __instance.LoadProfiles(); - QSBProfileManager._currentProfile = QSBProfileManager.mostRecentProfile; - if (QSBProfileManager._currentProfile == null) - { - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnNoProfilesExist)); - } - else - { - __instance.LoadSaveFilesFromProfiles(); - } - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.currentProfileGameSave), MethodType.Getter)] - public static bool CurrentProfileGameSave(out GameSave __result) - { - __result = QSBProfileManager._currentProfile?.gameSave; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.currentProfileGameSettings), MethodType.Getter)] - public static bool CurrentProfileGameSettings(out SettingsSave __result) - { - __result = QSBProfileManager._currentProfile?.settingsSave; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.currentProfileGraphicsSettings), MethodType.Getter)] - public static bool CurrentProfileGraphicsSettings(out GraphicSettings __result) - { - __result = QSBProfileManager._currentProfile?.graphicsSettings; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.currentProfileInputJSON), MethodType.Getter)] - public static bool CurrentProfileInputJSON(out string __result) - { - __result = QSBProfileManager._currentProfile?.inputJSON; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), "get_currentProfile")] - public static bool CurrentProfile(out StandaloneProfileManager.ProfileData __result) - { - DebugLog.DebugWrite($"Error - StandaloneProfileManager.currentProfile should not be used anymore." + - $"{Environment.NewLine}Called by : {Environment.NewLine}{Environment.StackTrace}", OWML.Common.MessageType.Error); - __result = null; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), "get_mostRecentProfile")] - public static bool MostRecentProfile(out StandaloneProfileManager.ProfileData __result) - { - DebugLog.DebugWrite($"Error - StandaloneProfileManager.mostRecentProfile should not be used anymore." + - $"{Environment.NewLine}Called by : {Environment.NewLine}{Environment.StackTrace}", OWML.Common.MessageType.Error); - __result = null; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), "get_profiles")] - public static bool Profiles(out List __result) - { - DebugLog.DebugWrite($"Error - StandaloneProfileManager.profiles should not be used anymore." + - $"{Environment.NewLine}Called by : {Environment.NewLine}{Environment.StackTrace}", OWML.Common.MessageType.Error); - __result = null; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.TryCreateProfile))] - public static bool TryCreateProfile(StandaloneProfileManager __instance, string profileName, out bool __result) - { - bool savedProfile = __instance.ValidateProfileName(profileName); - if (savedProfile) - { - bool noProfilesExist = QSBProfileManager._profiles.Count == 0; - QSBProfileData profileData = new QSBProfileData(); - profileData.profileName = profileName; - profileData.lastModifiedTime = DateTime.UtcNow; - GameSave gameSave = new GameSave(); - GameSave multGameSave = new GameSave(); - SettingsSave settingsSave = new SettingsSave(); - GraphicSettings graphicSettings = __instance.currentProfileGraphicsSettings; - if (graphicSettings == null) - { - graphicSettings = new GraphicSettings(init: true); - } - string text = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); - QSBProfileManager._profiles.Add(profileData); - profileData.gameSave = gameSave; - profileData.multiplayerGameSave = multGameSave; - profileData.settingsSave = settingsSave; - profileData.graphicsSettings = graphicSettings; - profileData.inputJSON = text; - savedProfile = TrySaveProfile(profileData, gameSave, settingsSave, graphicSettings, text); - if (savedProfile) - { - if (QSBProfileManager._currentProfile != null && QSBProfileManager._currentProfile.profileName != string.Empty) - { - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileSignOutComplete)); - } - - QSBProfileManager._currentProfile = profileData; - if (noProfilesExist) - { - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileSignInComplete), ProfileManagerSignInResult.COMPLETE); - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileReadDone)); - } - else - { - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileSignInComplete), ProfileManagerSignInResult.COMPLETE); - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileReadDone)); - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnUpdatePlayerProfiles)); - } - } - else - { - __instance.DeleteProfile(profileName); - } - } - - __result = savedProfile; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.DeleteProfile))] - public static bool DeleteProfile(StandaloneProfileManager __instance, string profileName) - { - Debug.Log("DeleteProfile"); - bool flag = false; - QSBProfileData profileData = new QSBProfileData(); - profileData.profileName = string.Empty; - for (int i = 0; i < QSBProfileManager._profiles.Count; i++) - { - if (profileName == QSBProfileManager._profiles[i].profileName) - { - profileData = QSBProfileManager._profiles[i]; - flag = true; - break; - } - } - if (!flag) - { - return false; - } - __instance.MarkBusyWithFileOps(isBusy: true); - string profileManifestPath = __instance._profilesPath + "/" + profileData.profileName + ".owprofile"; - string profilePath = __instance._profilesPath + "/" + profileData.profileName; - string gameSavePath = profilePath + "/data.owsave"; - string multGameSavePath = profilePath + "/dataMult.owsave"; - string settingsPath = profilePath + "/player.owsett"; - string graphicsPath = profilePath + "/graphics.owsett"; - string oldInputsPath = profilePath + "/input.owsett"; - string inputsPath = profilePath + "/input_new.owsett"; - - string backupProfilePath = __instance._profileBackupPath + "/" + profileData.profileName; - string backupGameSave = backupProfilePath + "/data.owsave"; - string backupMultGameSave = backupProfilePath + "/dataMult.owsave"; - string backupSettingsPath = backupProfilePath + "/player.owsett"; - string backupGraphicsPath = backupProfilePath + "/graphics.owsett"; - string backupOldInputsPath = backupProfilePath + "/input.owsett"; - string backupInputsPath = backupProfilePath + "/input_new.owsett"; - Stream stream = null; - try - { - if (File.Exists(profileManifestPath)) - { - File.Delete(profileManifestPath); - Debug.Log("Delete " + profileManifestPath); - } - - if (File.Exists(gameSavePath)) - { - File.Delete(gameSavePath); - Debug.Log("Delete " + gameSavePath); - } - - if (File.Exists(multGameSavePath)) - { - File.Delete(multGameSavePath); - Debug.Log("Delete " + multGameSavePath); - } - - if (File.Exists(settingsPath)) - { - File.Delete(settingsPath); - Debug.Log("Delete " + settingsPath); - } - - if (File.Exists(graphicsPath)) - { - File.Delete(graphicsPath); - Debug.Log("Delete " + graphicsPath); - } - - if (File.Exists(oldInputsPath)) - { - File.Delete(oldInputsPath); - Debug.Log("Delete " + oldInputsPath); - } - - if (File.Exists(inputsPath)) - { - File.Delete(inputsPath); - Debug.Log("Delete " + inputsPath); - } - - if (File.Exists(backupGameSave)) - { - File.Delete(backupGameSave); - Debug.Log("Delete " + backupGameSave); - } - - if (File.Exists(backupMultGameSave)) - { - File.Delete(backupMultGameSave); - Debug.Log("Delete " + backupMultGameSave); - } - - if (File.Exists(backupSettingsPath)) - { - File.Delete(backupSettingsPath); - Debug.Log("Delete " + backupSettingsPath); - } - - if (File.Exists(backupGraphicsPath)) - { - File.Delete(backupGraphicsPath); - Debug.Log("Delete " + backupGraphicsPath); - } - - if (File.Exists(backupOldInputsPath)) - { - File.Delete(backupOldInputsPath); - Debug.Log("Delete " + backupOldInputsPath); - } - - if (File.Exists(backupInputsPath)) - { - File.Delete(backupInputsPath); - Debug.Log("Delete " + backupInputsPath); - } - - QSBProfileManager._profiles.Remove(profileData); - string[] files = Directory.GetFiles(profilePath); - string[] directories = Directory.GetDirectories(profilePath); - if (files.Length == 0 && directories.Length == 0) - { - Directory.Delete(profilePath); - } - else - { - Debug.LogWarning(" Directory not empty. Cannot delete. "); - } - - if (Directory.Exists(backupProfilePath)) - { - files = Directory.GetFiles(backupProfilePath); - directories = Directory.GetDirectories(backupProfilePath); - if (files.Length == 0 && directories.Length == 0) - { - Directory.Delete(backupProfilePath); - } - else - { - Debug.LogWarning("Backup Directory not empty. Cannot delete."); - } - } - - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnUpdatePlayerProfiles)); - } - catch (Exception ex) - { - stream?.Close(); - Debug.LogError("[" + ex.Message + "] Failed to delete all profile data"); - __instance.MarkBusyWithFileOps(isBusy: false); - } - - __instance.MarkBusyWithFileOps(isBusy: false); - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.SwitchProfile))] - public static bool SwitchProfile(StandaloneProfileManager __instance, string profileName, out bool __result) - { - __instance.LoadSaveFilesFromProfiles(); - bool flag = false; - for (int i = 0; i < QSBProfileManager._profiles.Count; i++) - { - if (profileName == QSBProfileManager._profiles[i].profileName) - { - if (QSBProfileManager._currentProfile != null && QSBProfileManager._currentProfile.profileName != string.Empty) - { - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileSignOutComplete)); - } - - QSBProfileManager._currentProfile = QSBProfileManager._profiles[i]; - flag = true; - break; - } - } - - if (flag) - { - QSBProfileManager._currentProfile.lastModifiedTime = DateTime.UtcNow; - TrySaveProfile(QSBProfileManager._currentProfile, null, null, null, null); - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileSignInComplete), ProfileManagerSignInResult.COMPLETE); - - if (__instance.CurrentProfileHasBrokenData()) - { - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnBrokenDataExists)); - __result = false; - return false; - } - - __instance.RaiseEvent(nameof(StandaloneProfileManager.OnProfileReadDone)); - } - - __result = true; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.ValidateProfileName))] - public static bool ValidateProfileName(StandaloneProfileManager __instance, string profileName, out bool __result) - { - bool result = true; - if (profileName == "") - { - result = false; - } - else if (profileName.Length > 16) - { - result = false; - } - else if (QSBProfileManager._profiles.Count > 0) - { - for (int i = 0; i < QSBProfileManager._profiles.Count; i++) - { - if (QSBProfileManager._profiles[i].profileName == profileName) - { - result = false; - } - } - } - - __result = result; - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(StandaloneProfileManager), nameof(StandaloneProfileManager.PerformPendingSaveOperation))] - public static bool PerformPendingSaveOperation(StandaloneProfileManager __instance) - { - if (!__instance.isBusyWithFileOps && !LoadManager.IsBusy()) - { - TrySaveProfile(QSBProfileManager._currentProfile, __instance._pendingGameSave, __instance._pendingSettingsSave, __instance._pendingGfxSettingsSave, __instance._pendingInputJSONSave); - __instance._pendingGameSave = null; - __instance._pendingSettingsSave = null; - __instance._pendingGfxSettingsSave = null; - __instance._pendingInputJSONSave = ""; - } - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(ProfileMenuManager), nameof(ProfileMenuManager.PopulateProfiles))] - public static bool PopulateProfiles(ProfileMenuManager __instance) - { - if (__instance._listProfileElements == null) - { - __instance._listProfileElements = new List(); - } - else - { - for (var i = 0; i < __instance._listProfileElements.Count; i++) - { - var requiredComponent = __instance._listProfileElements[i].GetRequiredComponent(); - __instance.ClearProfileElementListeners(requiredComponent); - UnityEngine.Object.Destroy(__instance._listProfileElements[i]); - } - - __instance._listProfileElements.Clear(); - } - - if (__instance._listProfileUIElementLookup == null) - { - __instance._listProfileUIElementLookup = new List(); - } - else - { - __instance._listProfileUIElementLookup.Clear(); - } - - var array = QSBProfileManager._profiles.ToArray(); - var profileName = QSBProfileManager._currentProfile.profileName; - var num = 0; - Selectable selectable = null; - for (var j = 0; j < array.Length; j++) - { - if (!(array[j].profileName == profileName)) - { - var gameObject = UnityEngine.Object.Instantiate(__instance._profileItemTemplate); - gameObject.gameObject.SetActive(value: true); - gameObject.transform.SetParent(__instance._profileListRoot.transform); - gameObject.transform.localScale = new Vector3(1f, 1f, 1f); - var componentsInChildren = gameObject.gameObject.GetComponentsInChildren(); - for (var k = 0; k < componentsInChildren.Length; k++) - { - __instance._fontController.AddTextElement(componentsInChildren[k]); - } - - num++; - var requiredComponent2 = gameObject.GetRequiredComponent(); - var requiredComponent3 = requiredComponent2.GetRequiredComponent(); - __instance.SetUpProfileElementListeners(requiredComponent2); - requiredComponent2.SetLabelText(array[j].profileName); - var component = requiredComponent2.GetButtonOne().GetComponent(); - if (component != null) - { - __instance._fontController.AddTextElement(component); - } - - component = requiredComponent2.GetButtonTwo().GetComponent(); - if (component != null) - { - __instance._fontController.AddTextElement(component); - } - - if (num == 1) - { - var navigation = __instance._createProfileButton.navigation; - navigation.selectOnDown = gameObject.GetRequiredComponent(); - __instance._createProfileButton.navigation = navigation; - var navigation2 = requiredComponent3.navigation; - navigation2.selectOnUp = __instance._createProfileButton; - requiredComponent3.navigation = navigation2; - } - else - { - var navigation3 = requiredComponent3.navigation; - var navigation4 = selectable.navigation; - navigation3.selectOnUp = selectable; - navigation3.selectOnDown = null; - navigation4.selectOnDown = requiredComponent3; - requiredComponent3.navigation = navigation3; - selectable.navigation = navigation4; - } - - __instance._listProfileElements.Add(gameObject); - selectable = requiredComponent3; - var profileElementLookup = new ProfileMenuManager.ProfileElementLookup - { - profileName = array[j].profileName, - lastModifiedTime = array[j].lastModifiedTime, - confirmSwitchAction = requiredComponent2.GetSubmitActionOne() as SubmitActionConfirm, - confirmDeleteAction = requiredComponent2.GetSubmitActionTwo() as SubmitActionConfirm - }; - __instance._listProfileUIElementLookup.Add(profileElementLookup); - } - } - - return false; - } - - [HarmonyPrefix] - [HarmonyPatch(typeof(ProfileMenuManager), nameof(ProfileMenuManager.SetCurrentProfileLabel))] - public static bool SetCurrentProfileName(ProfileMenuManager __instance) - { - __instance._currenProfileLabel.text = UITextLibrary.GetString(UITextType.MenuProfile) + " " + QSBProfileManager._currentProfile.profileName; - return false; - } -} diff --git a/QSB/SaveSync/Patches/ProfileManagerUpdaterPatches.cs b/QSB/SaveSync/Patches/ProfileManagerUpdaterPatches.cs new file mode 100644 index 00000000..da1d832b --- /dev/null +++ b/QSB/SaveSync/Patches/ProfileManagerUpdaterPatches.cs @@ -0,0 +1,19 @@ +using HarmonyLib; +using QSB.Patches; + +namespace QSB.SaveSync.Patches; + +[HarmonyPatch(typeof(ProfileManagerUpdater))] +internal class ProfileManagerUpdaterPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileManagerUpdater.Start))] + public static bool Start(ProfileManagerUpdater __instance) + { + __instance._profileManager = QSBCore.ProfileManager; + __instance.enabled = true; + return false; + } +} diff --git a/QSB/SaveSync/Patches/ProfileMenuManagerPatches.cs b/QSB/SaveSync/Patches/ProfileMenuManagerPatches.cs new file mode 100644 index 00000000..73948444 --- /dev/null +++ b/QSB/SaveSync/Patches/ProfileMenuManagerPatches.cs @@ -0,0 +1,212 @@ +using HarmonyLib; +using QSB.Patches; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UI; + +namespace QSB.SaveSync.Patches; + +[HarmonyPatch(typeof(ProfileMenuManager))] +internal class ProfileMenuManagerPatches : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + public override GameVendor PatchVendor => GameVendor.Epic | GameVendor.Steam; + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.OnCreateProfileConfirm))] + public static bool OnCreateProfileConfirm(ProfileMenuManager __instance) + { + __instance._inputPopupActivated = false; + var inputPopup = __instance._createProfileAction.GetInputPopup(); + inputPopup.OnPopupValidate -= __instance.OnCreateProfileValidate; + inputPopup.OnInputPopupValidateChar -= __instance.OnValidateChar; + __instance._createProfileAction.OnSubmitAction -= __instance.OnCreateProfileConfirm; + QSBStandaloneProfileManager.SharedInstance.TryCreateProfile(__instance._createProfileAction.GetInputString()); + inputPopup.CloseMenuOnOk(true); + __instance.PopulateProfiles(); + __instance.SetCurrentProfileLabel(); + inputPopup.EnableMenu(false); + if (__instance._firstTimeProfileCreation) + { + __instance._firstTimeProfileCreation = false; + __instance.UpdatePopupPrompts(); + } + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.OnCreateProfileValidate))] + public static bool OnCreateProfileValidate(ProfileMenuManager __instance, ref bool __result) + { + var inputPopup = __instance._createProfileAction.GetInputPopup(); + __result = QSBStandaloneProfileManager.SharedInstance.ValidateProfileName(inputPopup.GetInputText()); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.OnDeleteProfile))] + public static bool OnDeleteProfile(ProfileMenuManager __instance) + { + if (__instance._lastSelectedProfileAction != null) + { + __instance._deleteProfileConfirmPopup = null; + QSBStandaloneProfileManager.SharedInstance.DeleteProfile(__instance._lastSelectedProfileAction.GetLabelText()); + __instance.PopulateProfiles(); + __instance._lastSelectedProfileAction = null; + Locator.GetMenuInputModule().SelectOnNextUpdate(__instance._createProfileButton); + } + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.OnSwitchProfile))] + public static bool OnSwitchProfile(ProfileMenuManager __instance) + { + if (__instance._lastSelectedProfileAction != null) + { + __instance._switchProfileConfirmPopup = null; + if (QSBStandaloneProfileManager.SharedInstance.SwitchProfile(__instance._lastSelectedProfileAction.GetLabelText())) + { + __instance.PopulateProfiles(); + __instance.SetCurrentProfileLabel(); + __instance._lastSelectedProfileAction = null; + Locator.GetMenuInputModule().SelectOnNextUpdate(__instance._createProfileButton); + return false; + } + + QSBStandaloneProfileManager.SharedInstance.OnBackupDataRestored += __instance.OnSwitchProfileDataRecoveryCompleted; + } + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.OnSwitchProfileDataRecoveryCompleted))] + public static bool OnSwitchProfileDataRecoveryCompleted(ProfileMenuManager __instance) + { + QSBStandaloneProfileManager.SharedInstance.OnBackupDataRestored -= __instance.OnSwitchProfileDataRecoveryCompleted; + __instance.PopulateProfiles(); + __instance.SetCurrentProfileLabel(); + __instance._lastSelectedProfileAction = null; + Locator.GetMenuInputModule().SelectOnNextUpdate(__instance._createProfileButton); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.OnValidateChar))] + public static bool OnValidateChar(ProfileMenuManager __instance, char c, ref bool __result) + { + __result = __instance._createProfileAction.GetInputPopup().GetInputText().Length < QSBStandaloneProfileManager.SharedInstance.profileNameCharacterLimit + && QSBStandaloneProfileManager.SharedInstance.IsValidCharacterForProfileName(c); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.PopulateProfiles))] + public static bool PopulateProfiles(ProfileMenuManager __instance) + { + if (__instance._listProfileElements == null) + { + __instance._listProfileElements = new List(); + } + else + { + for (int i = 0; i < __instance._listProfileElements.Count; i++) + { + TwoButtonActionElement requiredComponent = __instance._listProfileElements[i].GetRequiredComponent(); + __instance.ClearProfileElementListeners(requiredComponent); + Object.Destroy(__instance._listProfileElements[i]); + } + __instance._listProfileElements.Clear(); + } + + if (__instance._listProfileUIElementLookup == null) + { + __instance._listProfileUIElementLookup = new List(); + } + else + { + __instance._listProfileUIElementLookup.Clear(); + } + + var array = QSBStandaloneProfileManager.SharedInstance.profiles.ToArray(); + var profileName = QSBStandaloneProfileManager.SharedInstance.currentProfile.profileName; + var num = 0; + Selectable selectable = null; + for (int j = 0; j < array.Length; j++) + { + if (!(array[j].profileName == profileName)) + { + GameObject gameObject = Object.Instantiate(__instance._profileItemTemplate); + gameObject.gameObject.SetActive(true); + gameObject.transform.SetParent(__instance._profileListRoot.transform); + gameObject.transform.localScale = new Vector3(1f, 1f, 1f); + Text[] componentsInChildren = gameObject.gameObject.GetComponentsInChildren(); + for (int k = 0; k < componentsInChildren.Length; k++) + { + __instance._fontController.AddTextElement(componentsInChildren[k], true, true, false); + } + + num++; + TwoButtonActionElement requiredComponent2 = gameObject.GetRequiredComponent(); + Selectable requiredComponent3 = requiredComponent2.GetRequiredComponent(); + __instance.SetUpProfileElementListeners(requiredComponent2); + requiredComponent2.SetLabelText(array[j].profileName); + Text component = requiredComponent2.GetButtonOne().GetComponent(); + if (component != null) + { + __instance._fontController.AddTextElement(component, true, true, false); + } + + component = requiredComponent2.GetButtonTwo().GetComponent(); + if (component != null) + { + __instance._fontController.AddTextElement(component, true, true, false); + } + + if (num == 1) + { + Navigation navigation = __instance._createProfileButton.navigation; + navigation.selectOnDown = gameObject.GetRequiredComponent(); + __instance._createProfileButton.navigation = navigation; + Navigation navigation2 = requiredComponent3.navigation; + navigation2.selectOnUp = __instance._createProfileButton; + requiredComponent3.navigation = navigation2; + } + else + { + Navigation navigation3 = requiredComponent3.navigation; + Navigation navigation4 = selectable.navigation; + navigation3.selectOnUp = selectable; + navigation3.selectOnDown = null; + navigation4.selectOnDown = requiredComponent3; + requiredComponent3.navigation = navigation3; + selectable.navigation = navigation4; + } + + __instance._listProfileElements.Add(gameObject); + selectable = requiredComponent3; + ProfileMenuManager.ProfileElementLookup profileElementLookup = new ProfileMenuManager.ProfileElementLookup(); + profileElementLookup.profileName = array[j].profileName; + profileElementLookup.lastModifiedTime = array[j].lastModifiedTime; + profileElementLookup.confirmSwitchAction = requiredComponent2.GetSubmitActionOne() as SubmitActionConfirm; + profileElementLookup.confirmDeleteAction = requiredComponent2.GetSubmitActionTwo() as SubmitActionConfirm; + __instance._listProfileUIElementLookup.Add(profileElementLookup); + } + } + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ProfileMenuManager.SetCurrentProfileLabel))] + public static bool SetCurrentProfileLabel(ProfileMenuManager __instance) + { + __instance._currenProfileLabel.text = UITextLibrary.GetString(UITextType.MenuProfile) + + " " + + QSBStandaloneProfileManager.SharedInstance.currentProfile.profileName; + return false; + } +} diff --git a/QSB/SaveSync/Patches/TitleScreenManagerPatchesCommon.cs b/QSB/SaveSync/Patches/TitleScreenManagerPatchesCommon.cs new file mode 100644 index 00000000..2239b43a --- /dev/null +++ b/QSB/SaveSync/Patches/TitleScreenManagerPatchesCommon.cs @@ -0,0 +1,90 @@ +using HarmonyLib; +using QSB.Patches; +using QSB.Utility; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace QSB.SaveSync.Patches; + +[HarmonyPatch(typeof(TitleScreenManager))] +internal class TitleScreenManagerPatchesCommon : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + + [HarmonyPrefix] + [HarmonyPatch(nameof(TitleScreenManager.Awake))] + public static bool Awake(TitleScreenManager __instance) + { + __instance._profileManager = QSBCore.ProfileManager; + __instance._profileManager.PreInitialize(); + LoadManager.OnStartSceneLoad += __instance.OnStartSceneLoad; + LoadManager.OnCompleteSceneLoad += __instance.OnCompleteSceneLoad; + MenuStackManager.SharedInstance.OnMenuPush += __instance.OnMenuPush; + MenuStackManager.SharedInstance.OnMenuPop += __instance.OnMenuPop; + __instance._resumeGameTextSetter = __instance._resumeGameObject.GetComponentInChildren(); + __instance.InitializePopupPrompts(); + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(TitleScreenManager.InitializeProfileManagerCallbacks))] + public static bool InitializeProfileManagerCallbacks(TitleScreenManager __instance) + { + if (QSBCore.IsStandalone) + { + QSBStandaloneProfileManager.SharedInstance.OnNoProfilesExist += __instance.OnNoStandaloneProfilesExist; + QSBStandaloneProfileManager.SharedInstance.OnUpdatePlayerProfiles += __instance.OnUpdatePlayerProfiles; + QSBStandaloneProfileManager.SharedInstance.OnBrokenDataExists += __instance.OnBrokenDataExists; + } + else + { + QSBMSStoreProfileManager.SharedInstance.OnBrokenDataExists += __instance.OnBrokenDataExists; + } + + __instance._profileManager.OnProfileSignInStart += __instance.OnProfileSignInStart; + __instance._profileManager.OnProfileSignInComplete += __instance.OnProfileSignInComplete; + __instance._profileManager.OnProfileSignOutStart += __instance.OnProfileSignOutStart; + __instance._profileManager.OnProfileSignOutComplete += __instance.OnProfileSignOutComplete; + __instance._profileManager.OnProfileReadDone += __instance.OnProfileManagerReadDone; + __instance._profileManager.Initialize(); + + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(TitleScreenManager.OnDestroy))] + public static bool OnDestroy(TitleScreenManager __instance) + { + if (QSBCore.IsStandalone) + { + QSBStandaloneProfileManager.SharedInstance.OnNoProfilesExist -= __instance.OnNoStandaloneProfilesExist; + QSBStandaloneProfileManager.SharedInstance.OnUpdatePlayerProfiles -= __instance.OnUpdatePlayerProfiles; + QSBStandaloneProfileManager.SharedInstance.OnBrokenDataExists -= __instance.OnBrokenDataExists; + } + else + { + QSBMSStoreProfileManager.SharedInstance.OnBrokenDataExists -= __instance.OnBrokenDataExists; + } + + __instance._profileManager.OnProfileSignInStart -= __instance.OnProfileSignInStart; + __instance._profileManager.OnProfileSignInComplete -= __instance.OnProfileSignInComplete; + __instance._profileManager.OnProfileSignOutStart -= __instance.OnProfileSignOutStart; + __instance._profileManager.OnProfileSignOutComplete -= __instance.OnProfileSignOutComplete; + __instance._profileManager.OnProfileReadDone -= __instance.OnProfileManagerReadDone; + LoadManager.OnStartSceneLoad -= __instance.OnStartSceneLoad; + LoadManager.OnCompleteSceneLoad -= __instance.OnCompleteSceneLoad; + TextTranslation.Get().OnLanguageChanged -= __instance.OnLanguageChanged; + __instance._newGameAction.OnSubmitAction -= __instance.OnNewGameSubmit; + __instance._newGameAction.OnPostSetupPopup -= __instance.OnNewGameSetupPopup; + __instance._resetGameAction.OnSubmitAction -= __instance.OnResetGameSubmit; + __instance._accountPickerSubmitAction.OnAccountPickerSubmitEvent -= __instance.OnAccountPickerSubmitEvent; + MenuStackManager.SharedInstance.OnMenuPush -= __instance.OnMenuPush; + MenuStackManager.SharedInstance.OnMenuPop -= __instance.OnMenuPop; + + return false; + } +} diff --git a/QSB/SaveSync/Patches/TitleScreenManagerPatchesGamepass.cs b/QSB/SaveSync/Patches/TitleScreenManagerPatchesGamepass.cs new file mode 100644 index 00000000..56249c2b --- /dev/null +++ b/QSB/SaveSync/Patches/TitleScreenManagerPatchesGamepass.cs @@ -0,0 +1,23 @@ +using HarmonyLib; +using QSB.Patches; +using System.Reflection; +using UnityEngine.UI; + +namespace QSB.SaveSync.Patches; + +[HarmonyPatch(typeof(TitleScreenManager))] +internal class TitleScreenManagerPatchesGamepass : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + public override GameVendor PatchVendor => GameVendor.Gamepass; + + [HarmonyPrefix] + [HarmonyPatch("SetUserAccountDisplayInfo")] + public static bool SetUserAccountDisplayInfo(TitleScreenManager __instance) + { + var text = (Text)__instance.GetType().GetField("_gamertagDisplay", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(__instance); + text.text = ""; // no idea why, mobius be like + text.text = QSBMSStoreProfileManager.SharedInstance.userDisplayName; + return false; + } +} diff --git a/QSB/SaveSync/Patches/TitleScreenManagerPatchesStandalone.cs b/QSB/SaveSync/Patches/TitleScreenManagerPatchesStandalone.cs new file mode 100644 index 00000000..7a9219fe --- /dev/null +++ b/QSB/SaveSync/Patches/TitleScreenManagerPatchesStandalone.cs @@ -0,0 +1,45 @@ +using HarmonyLib; +using QSB.Patches; + +namespace QSB.SaveSync.Patches; + +[HarmonyPatch(typeof(TitleScreenManager))] +internal class TitleScreenManagerPatchesStandalone : QSBPatch +{ + public override QSBPatchTypes Type => QSBPatchTypes.OnModStart; + public override GameVendor PatchVendor => GameVendor.Epic | GameVendor.Steam; + + [HarmonyPrefix] + [HarmonyPatch(nameof(TitleScreenManager.OnBrokenDataExists))] + public static bool OnBrokenDataExists(TitleScreenManager __instance) + { + __instance._titleMenuRaycastBlocker.blocksRaycasts = false; + __instance._inputModule.EnableInputs(); + __instance._waitingOnBrokenDataResponse = true; + var flag = QSBStandaloneProfileManager.SharedInstance.BackupExistsForBrokenData(); + var text = UITextLibrary.GetString(UITextType.SaveRestore_CorruptedMsg); + if (flag) + { + text = text + " " + UITextLibrary.GetString(UITextType.SaveRestore_LoadPreviousMsg); + } + + __instance._okCancelPopup.ResetPopup(); + __instance._okCancelPopup.SetUpPopup(text, InputLibrary.confirm, InputLibrary.cancel, __instance._confirmActionPrompt, __instance._cancelActionPrompt, true, flag); + __instance._okCancelPopup.OnPopupConfirm += __instance.OnUserConfirmRestoreData; + __instance._okCancelPopup.OnPopupCancel += __instance.OnUserCancelRestoreData; + __instance._okCancelPopup.EnableMenu(true); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(TitleScreenManager.OnUserConfirmRestoreData))] + public static bool OnUserConfirmRestoreData(TitleScreenManager __instance) + { + __instance._waitingOnBrokenDataResponse = false; + QSBStandaloneProfileManager.SharedInstance.RestoreCurrentProfileBackup(); + __instance.OnProfileManagerReadDone(); + __instance._okCancelPopup.OnPopupConfirm -= __instance.OnUserConfirmRestoreData; + __instance._okCancelPopup.OnPopupCancel -= __instance.OnUserCancelRestoreData; + return false; + } +} diff --git a/QSB/SaveSync/QSBMSStoreProfileManager.cs b/QSB/SaveSync/QSBMSStoreProfileManager.cs new file mode 100644 index 00000000..3780b4d7 --- /dev/null +++ b/QSB/SaveSync/QSBMSStoreProfileManager.cs @@ -0,0 +1,429 @@ +using Microsoft.Xbox; +using Newtonsoft.Json; +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Xml.Serialization; +using UnityEngine; +using UnityEngine.InputSystem; + +namespace QSB.SaveSync; + +internal class QSBMSStoreProfileManager : IProfileManager +{ + private const string OW_SAVE_CONTAINER_NAME = "GameSave"; + private const string OW_GAME_SAVE_BLOB_NAME = "Outer Wilds Converted"; + private const string OW_GAME_SETTINGS_BLOB_NAME = "PCGameSettings"; + + private static QSBMSStoreProfileManager _sharedInstance; + private QSBX1SaveData _saveData; + private const string c_containerName = "OuterWildsConnectedStorage"; + private GameSave _pendingGameSave; + private SettingsSave _pendingSettingsSave; + private GraphicSettings _pendingGfxSettingsSave; + private string _pendingInputActionsSave = ""; + private JsonSerializer _jsonSerializer; + private bool _initialized; + private int _fileOpsBusyLocks; + private bool _preInitialized; + private bool _isLoadingGameBlob; + private bool _isLoadingSettingsBlob; + + public static QSBMSStoreProfileManager SharedInstance + { + get + { + if (_sharedInstance == null) + { + _sharedInstance = new QSBMSStoreProfileManager(); + } + + return _sharedInstance; + } + } + + public GameSave currentProfileGameSave => _saveData.gameSave; + public GameSave currentProfileMultiplayerGameSave => _saveData.gameMultSave; + public SettingsSave currentProfileGameSettings => _saveData.settings; + public GraphicSettings currentProfileGraphicsSettings => _saveData.gfxSettings; + public string currentProfileInputJSON => _saveData.inputActionsJson; + public bool isInitialized { get; } + public bool isBusyWithFileOps => _fileOpsBusyLocks > 0; + public bool hasPendingSaveOperation => _pendingGameSave != null || _pendingSettingsSave != null || _pendingGfxSettingsSave != null || _pendingInputActionsSave != null; + public bool saveSystemAvailable { get; private set; } + public string userDisplayName => Gdk.Helpers.currentGamertag; + + public delegate void BrokenDataExistsEvent(); + + public event BrokenDataExistsEvent OnBrokenDataExists; + public event ProfileDataSavedEvent OnProfileDataSaved; + public event ProfileReadDoneEvent OnProfileReadDone; + public event ProfileSignInCompleteEvent OnProfileSignInComplete; + public event ProfileSignInStartEvent OnProfileSignInStart; + public event ProfileSignOutCompleteEvent OnProfileSignOutComplete; + public event ProfileSignOutStartEvent OnProfileSignOutStart; + + public void Initialize() + { + if (!_initialized) + { + Gdk.Helpers.SignIn(); + SpinnerUI.Show(); + Debug.Log("MSStoreProfileManager.Initialize"); + Gdk.Helpers.OnGameSaveSucceeded += OnGameSaveComplete; + Gdk.Helpers.OnGameSaveFailed += OnGameSaveFailed; + Gdk.Helpers.OnGameSaveLoaded += OnGameSaveLoaded; + Gdk.Helpers.OnGameSaveLoadFailed += OnGameSaveLoadFailed; + Achievements.Init(); + var serializationBinder = new VersionDeserializationBinder(); + _jsonSerializer = new JsonSerializer + { + SerializationBinder = serializationBinder + }; + _initialized = true; + return; + } + + OnProfileSignInComplete?.Invoke(ProfileManagerSignInResult.COMPLETE); + + var onProfileReadDone = OnProfileReadDone; + if (onProfileReadDone == null) + { + return; + } + + onProfileReadDone(); + } + + public void PreInitialize() + { + if (_preInitialized) + { + return; + } + + _fileOpsBusyLocks = 0; + _pendingGameSave = null; + _pendingSettingsSave = null; + _pendingGfxSettingsSave = null; + _pendingInputActionsSave = null; + _preInitialized = true; + } + + public void InvokeProfileSignInComplete() + { + var onProfileSignInComplete = OnProfileSignInComplete; + if (onProfileSignInComplete == null) + { + return; + } + + onProfileSignInComplete(ProfileManagerSignInResult.COMPLETE); + } + + public void InvokeSaveSetupComplete() + { + saveSystemAvailable = true; + _isLoadingGameBlob = true; + _saveData = new QSBX1SaveData(); + LoadGame(OW_GAME_SAVE_BLOB_NAME); + } + + public void InitializeForEditor() + { + } + + public void SaveGame(GameSave gameSave, SettingsSave settSave, GraphicSettings gfxSettings, string inputJSON) + { + Debug.Log("MSStoreProfileManager.SaveGame"); + if (isBusyWithFileOps || LoadManager.IsBusy()) + { + _pendingGameSave = gameSave; + _pendingSettingsSave = settSave; + _pendingGfxSettingsSave = gfxSettings; + _pendingInputActionsSave = inputJSON; + return; + } + + var gameSaveData = new QSBX1SaveData(); + var settingsSaveData = new QSBX1SaveData(); + var saveGameSave = false; + if (gameSave != null) + { + saveGameSave = true; + + if (QSBCore.IsInMultiplayer) + { + _saveData.gameMultSave = gameSave; + gameSaveData.gameMultSave = gameSave; + } + else + { + _saveData.gameSave = gameSave; + gameSaveData.gameSave = gameSave; + } + } + + var saveGameSettings = false; + if (settSave != null) + { + saveGameSettings = true; + _saveData.settings = settSave; + settingsSaveData.settings = settSave; + } + else + { + settingsSaveData.settings = _saveData.settings; + } + + if (gfxSettings != null) + { + saveGameSettings = true; + _saveData.gfxSettings = gfxSettings; + settingsSaveData.gfxSettings = gfxSettings; + } + else + { + settingsSaveData.gfxSettings = _saveData.gfxSettings; + } + + if (!string.IsNullOrEmpty(inputJSON)) + { + saveGameSettings = true; + _saveData.inputActionsJson = inputJSON; + settingsSaveData.inputActionsJson = inputJSON; + } + else if (!string.IsNullOrEmpty(_saveData.inputActionsJson)) + { + settingsSaveData.inputActionsJson = _saveData.inputActionsJson; + } + else + { + settingsSaveData.inputActionsJson = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); + } + + if (saveGameSave) + { + WriteSaveToStorage(gameSaveData, OW_GAME_SAVE_BLOB_NAME); + } + + if (saveGameSettings) + { + WriteSaveToStorage(settingsSaveData, OW_GAME_SETTINGS_BLOB_NAME); + } + } + + private void LoadGame(string blobName) + { + _fileOpsBusyLocks++; + Gdk.Helpers.LoadSaveData(blobName); + } + + private void WriteSaveToStorage(QSBX1SaveData saveData, string blobName) + { + Debug.Log("Saving to storage: " + blobName); + _fileOpsBusyLocks++; + var memoryStream = new MemoryStream(); + using (JsonWriter jsonWriter = new JsonTextWriter(new StreamWriter(memoryStream))) + { + _jsonSerializer.Serialize(jsonWriter, saveData); + } + + var buffer = memoryStream.GetBuffer(); + Gdk.Helpers.Save(buffer, blobName); + } + + public void PerformPendingSaveOperation() + { + if (!isBusyWithFileOps && !LoadManager.IsBusy()) + { + SaveGame(_pendingGameSave, _pendingSettingsSave, _pendingGfxSettingsSave, _pendingInputActionsSave); + _pendingGameSave = null; + _pendingSettingsSave = null; + _pendingGfxSettingsSave = null; + _pendingInputActionsSave = null; + } + } + + private void OnGameSaveComplete(object sender, string blobName) + { + _fileOpsBusyLocks--; + Debug.Log("[GDK] save to blob " + blobName + " complete"); + } + + private void OnGameSaveFailed(object sender, string blobName) + { + _fileOpsBusyLocks--; + Debug.Log("[GDK] save to blob " + blobName + " failed"); + } + + private void OnGameSaveLoaded(object sender, string blobName, GameSaveLoadedArgs saveData) + { + _fileOpsBusyLocks--; + Debug.Log("[GDK] save file load complete! blob name: " + blobName); + var memoryStream = new MemoryStream(saveData.Data); + memoryStream.Seek(0L, SeekOrigin.Begin); + using (var jsonTextReader = new JsonTextReader(new StreamReader(memoryStream))) + { + var x1SaveData = _jsonSerializer.Deserialize(jsonTextReader); + if (_isLoadingGameBlob) + { + if (x1SaveData != null) + { + if (x1SaveData.gameSave == null) + { + Debug.Log("[GDK] tempSaveData.gameSave is null (oh no)"); + } + + _saveData.gameSave = x1SaveData.gameSave ?? new GameSave(); + } + else + { + Debug.Log("[GDK] tempSaveData is null (oh no)"); + _saveData.gameSave = new GameSave(); + } + } + else + { + if (x1SaveData != null) + { + _saveData.gfxSettings = x1SaveData.gfxSettings ?? new GraphicSettings(true); + _saveData.settings = x1SaveData.settings ?? new SettingsSave(); + _saveData.inputActionsJson = x1SaveData.inputActionsJson ?? ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); + } + else + { + _saveData.gfxSettings = new GraphicSettings(true); + _saveData.settings = new SettingsSave(); + _saveData.inputActionsJson = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); + } + + Debug.Log(string.Format("after settings load, _saveData.gameSave is null: {0}", _saveData.gameSave == null)); + Debug.Log(string.Format("_saveData loopCount: {0}", _saveData.gameSave.loopCount)); + } + } + + if (_isLoadingGameBlob) + { + _isLoadingGameBlob = false; + LoadGame(OW_GAME_SETTINGS_BLOB_NAME); + _isLoadingSettingsBlob = true; + return; + } + + if (_isLoadingSettingsBlob) + { + _isLoadingSettingsBlob = false; + var onProfileReadDone = OnProfileReadDone; + if (onProfileReadDone == null) + { + return; + } + + onProfileReadDone(); + } + } + + private void OnGameSaveLoadFailed(object sender, string blobName) + { + _fileOpsBusyLocks--; + if (_isLoadingGameBlob) + { + _saveData.gameSave = new GameSave(); + SaveGame(_saveData.gameSave, null, null, null); + _isLoadingGameBlob = false; + LoadGame(OW_GAME_SETTINGS_BLOB_NAME); + _isLoadingSettingsBlob = true; + return; + } + + if (_isLoadingSettingsBlob) + { + _saveData.settings = new SettingsSave(); + _saveData.gfxSettings = new GraphicSettings(true); + _saveData.inputActionsJson = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); + SaveGame(null, _saveData.settings, _saveData.gfxSettings, _saveData.inputActionsJson); + _isLoadingSettingsBlob = false; + var onProfileReadDone = OnProfileReadDone; + if (onProfileReadDone == null) + { + return; + } + + onProfileReadDone(); + } + } + + [Serializable] + public class QSBX1SaveData + { + [XmlElement("gameSave")] + public GameSave gameSave; + + [XmlElement("gameMultSave")] + [OptionalField(VersionAdded = 5)] + public GameSave gameMultSave; + + [XmlElement("settings")] + public SettingsSave settings; + + [XmlElement("gfxSettings")] + [OptionalField(VersionAdded = 2)] + public GraphicSettings gfxSettings; + + [OptionalField(VersionAdded = 3)] + [NonSerialized] + public InputRebindableData bindingSettings; + + [OptionalField(VersionAdded = 4)] + public string inputActionsPacked; + + private InputActionAsset _inputActionsSave; + + [JsonIgnore] + public string inputActionsJson + { + get => inputActionsPacked; + set + { + inputActionsPacked = value; + if (!string.IsNullOrEmpty(inputActionsPacked)) + { + _inputActionsSave = InputActionAsset.FromJson(inputActionsPacked); + return; + } + + _inputActionsSave = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions; + } + } + + [JsonIgnore] + public InputActionAsset inputActionsSave + { + get + { + if (_inputActionsSave == null && !string.IsNullOrEmpty(inputActionsPacked)) + { + try + { + _inputActionsSave = InputActionAsset.FromJson(inputActionsPacked); + } + catch (Exception) + { + _inputActionsSave = null; + } + } + + return _inputActionsSave; + } + } + + [OnDeserializing] + private void SetDefaultValuesOnDeserializing(StreamingContext context) + { + gfxSettings = null; + bindingSettings = null; + inputActionsPacked = null; + } + } +} diff --git a/QSB/SaveSync/QSBProfileManager.cs b/QSB/SaveSync/QSBProfileManager.cs deleted file mode 100644 index 3c7a26c4..00000000 --- a/QSB/SaveSync/QSBProfileManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -using QSB.Utility; -using System.Collections.Generic; - -namespace QSB.SaveSync; - -public static class QSBProfileManager -{ - public static readonly List _profiles = new(); - public static QSBProfileData _currentProfile; - public static QSBProfileData mostRecentProfile - => _profiles.MaxBy(x => x.lastModifiedTime); -} diff --git a/QSB/SaveSync/QSBStandaloneProfileManager.cs b/QSB/SaveSync/QSBStandaloneProfileManager.cs new file mode 100644 index 00000000..376e7c38 --- /dev/null +++ b/QSB/SaveSync/QSBStandaloneProfileManager.cs @@ -0,0 +1,1121 @@ +using Newtonsoft.Json; +using QSB.Utility; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Serialization.Formatters.Binary; +using UnityEngine; + +namespace QSB.SaveSync; + +internal class QSBStandaloneProfileManager : IProfileManager +{ + private static QSBStandaloneProfileManager s_instance; + + private const string _saveDirectory = "/SteamSaves"; + private const string _backupDirectory = "/Backup"; + private const string _tempDirectory = "/Temp"; + private const string _gameSaveFilename = "data.owsave"; + private const string _gameSaveMultFilename = "data_mult.owsave"; + private const string _gameSettingsFilename = "player.owsett"; + private const string _gfxSettingsFilename = "graphics.owsett"; + private const string _legacyInputBindingSettingsFilename = "input.owsett"; + private const string _inputActionsSettingsFilename = "input_new.owsett"; + private const int _profileNameCharLimit = 16; + + private string _profilesPath; + private string _profileTempPath; + private string _profileBackupPath; + private int _fileOpsBusyLocks; + private GameSave _pendingGameSave; + private SettingsSave _pendingSettingsSave; + private GraphicSettings _pendingGfxSettingsSave; + private string _pendingInputJSONSave = ""; + private BinaryFormatter _binaryFormatter; + private JsonSerializer _jsonSerializer; + + public static QSBStandaloneProfileManager SharedInstance + { + get + { + if (s_instance == null) + { + s_instance = new QSBStandaloneProfileManager(); + } + + return s_instance; + } + } + + public GameSave currentProfileGameSave => currentProfile?.gameSave; + + public SettingsSave currentProfileGameSettings => currentProfile?.settingsSave; + + public GraphicSettings currentProfileGraphicsSettings => currentProfile?.graphicsSettings; + + public string currentProfileInputJSON => currentProfile?.inputJSON; + + public QSBProfileData currentProfile { get; private set; } + + public QSBProfileData mostRecentProfile + => (from profile in profiles + orderby profile.lastModifiedTime descending + select profile).FirstOrDefault(); + + public int profileNameCharacterLimit => _profileNameCharLimit; + + public List profiles { get; private set; } + + public int numberOfProfiles => profiles.Count; + + public bool isInitialized => currentProfileGameSave != null; + + public bool isBusyWithFileOps => _fileOpsBusyLocks > 0; + + public bool hasPendingSaveOperation => _pendingGameSave != null + || _pendingSettingsSave != null + || _pendingGfxSettingsSave != null + || _pendingInputJSONSave != ""; + + + public int profileCharacterLimit => _profileNameCharLimit; + + public delegate void NoProfilesExistEvent(); + public delegate void BrokenDataExistsEvent(); + public delegate void BackupDataRestoredEvent(); + public delegate void UpdatePlayerProfilesEvent(); + + public event NoProfilesExistEvent OnNoProfilesExist; + public event BrokenDataExistsEvent OnBrokenDataExists; + public event BackupDataRestoredEvent OnBackupDataRestored; + public event UpdatePlayerProfilesEvent OnUpdatePlayerProfiles; + public event ProfileSignInCompleteEvent OnProfileSignInComplete; + public event ProfileReadDoneEvent OnProfileReadDone; + public event ProfileDataSavedEvent OnProfileDataSaved; + public event ProfileSignOutCompleteEvent OnProfileSignOutComplete; + public event ProfileSignInStartEvent OnProfileSignInStart; + public event ProfileSignOutStartEvent OnProfileSignOutStart; + public event ControllerDisconnectedEvent OnControllerDisconnected; + public event ControllerReconnectedEvent OnControllerReconnected; + + public void PreInitialize() + { + _fileOpsBusyLocks = 0; + _pendingGameSave = null; + _pendingSettingsSave = null; + _pendingGfxSettingsSave = null; + _pendingInputJSONSave = ""; + } + + public void Initialize() + { + _profilesPath = Application.persistentDataPath + _saveDirectory; + _profileBackupPath = Application.persistentDataPath + _backupDirectory; + _profileTempPath = Application.persistentDataPath + _tempDirectory; + profiles = new List(); + var versionDeserializationBinder = new VersionDeserializationBinder(); + _jsonSerializer = new JsonSerializer + { + SerializationBinder = versionDeserializationBinder + }; + _binaryFormatter = new BinaryFormatter + { + Binder = versionDeserializationBinder + }; + Achievements.Init(); + InitializeProfileData(); + } + + public void InitializeForEditor() + { + _profilesPath = Application.persistentDataPath + _saveDirectory; + _profileBackupPath = Application.persistentDataPath + _backupDirectory; + _profileTempPath = Application.persistentDataPath + _tempDirectory; + profiles = new List(); + var versionDeserializationBinder = new VersionDeserializationBinder(); + _jsonSerializer = new JsonSerializer + { + SerializationBinder = versionDeserializationBinder + }; + _binaryFormatter = new BinaryFormatter + { + Binder = versionDeserializationBinder + }; + MarkBusyWithFileOps(true); + profiles.Clear(); + LoadProfiles(); + LoadSaveFilesFromProfiles(); + var flag = false; + for (var i = 0; i < profiles.Count; i++) + { + if (profiles[i].profileName == "Debug") + { + currentProfile = profiles[i]; + flag = true; + break; + } + } + + if (!flag) + { + TryCreateProfile("Debug"); + } + + MarkBusyWithFileOps(false); + PlayerData.Init(currentProfileGameSave, currentProfileGameSettings, currentProfileGraphicsSettings, currentProfileInputJSON); + } + + private void MarkBusyWithFileOps(bool isBusy) + { + if (isBusy) + { + _fileOpsBusyLocks++; + return; + } + + if (_fileOpsBusyLocks <= 0) + { + Debug.LogWarning("No File I/O lock to remove!"); + return; + } + + _fileOpsBusyLocks--; + } + + public void PerformPendingSaveOperation() + { + if (!isBusyWithFileOps && !LoadManager.IsBusy()) + { + TrySaveProfile(currentProfile, _pendingGameSave, _pendingSettingsSave, _pendingGfxSettingsSave, _pendingInputJSONSave); + _pendingGameSave = null; + _pendingSettingsSave = null; + _pendingGfxSettingsSave = null; + _pendingInputJSONSave = ""; + } + } + + public void SaveGame(GameSave gameSave, SettingsSave settSave, GraphicSettings graphicSettings, string inputBindings) + { + if (isBusyWithFileOps || LoadManager.IsBusy()) + { + _pendingGameSave = gameSave; + _pendingSettingsSave = settSave; + _pendingGfxSettingsSave = graphicSettings; + _pendingInputJSONSave = inputBindings; + return; + } + + TrySaveProfile(currentProfile, gameSave, settSave, graphicSettings, inputBindings); + } + + private void InitializeProfileData() + { + LoadProfiles(); + currentProfile = mostRecentProfile; + if (currentProfile != null) + { + LoadSaveFilesFromProfiles(); + return; + } + + var onNoProfilesExist = OnNoProfilesExist; + if (onNoProfilesExist == null) + { + return; + } + + onNoProfilesExist(); + } + + private void LoadSaveFilesFromProfiles() + { + MarkBusyWithFileOps(isBusy: true); + foreach (var profile in profiles) + { + var path = _profilesPath + "/" + profile.profileName; + GameSave saveData = null; + GameSave multSaveData = null; + SettingsSave settingsData = null; + GraphicSettings graphicsData = null; + var inputJSON = ""; + if (Directory.Exists(path)) + { + Stream stream = null; + var directoryInfo = new DirectoryInfo(path); + profile.brokenSaveData = TryLoadSaveData(ref stream, _gameSaveFilename, directoryInfo, out saveData); + profile.brokenMultSaveData = TryLoadSaveData(ref stream, _gameSaveMultFilename, directoryInfo, out multSaveData); + profile.brokenSettingsData = TryLoadSaveData(ref stream, _gameSettingsFilename, directoryInfo, out settingsData); + profile.brokenGfxSettingsData = TryLoadSaveData(ref stream, _gfxSettingsFilename, directoryInfo, out graphicsData); + profile.brokenRebindingData = TryLoadInputBindingsSave(ref stream, directoryInfo, out inputJSON); + } + + var profilePath = _profileBackupPath + "/" + profile.profileName; + var savePath = profilePath + "/" + _gameSaveFilename; + var multSavePath = profilePath + "/" + _gameSaveMultFilename; + var settingsPath = profilePath + "/" + _gameSettingsFilename; + var graphicsPath = profilePath + "/" + _gfxSettingsFilename; + var inputsPath = profilePath + "/" + _inputActionsSettingsFilename; + + if (saveData == null) + { + profile.brokenSaveData = File.Exists(savePath); + saveData = new GameSave(); + Debug.LogError("Could not find game save for " + profile.profileName); + } + + if (multSaveData == null) + { + profile.brokenMultSaveData = File.Exists(multSavePath); + multSaveData = new GameSave(); + Debug.LogError("Could not find multiplayer game save for " + profile.profileName); + } + + if (settingsData == null) + { + profile.brokenSettingsData = File.Exists(settingsPath); + settingsData = new SettingsSave(); + Debug.LogError("Could not find game settings for " + profile.profileName); + } + + if (graphicsData == null) + { + profile.brokenGfxSettingsData = File.Exists(graphicsPath); + graphicsData = new GraphicSettings(init: true); + Debug.LogError("Could not find graphics settings for " + profile.profileName); + } + + if (inputJSON == "") + { + profile.brokenRebindingData = File.Exists(inputsPath); + inputJSON = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); + Debug.LogError("Could not find input action settings for " + profile.profileName); + } + + profile.gameSave = saveData; + profile.multiplayerGameSave = multSaveData; + profile.settingsSave = settingsData; + profile.graphicsSettings = graphicsData; + profile.inputJSON = inputJSON; + } + + MarkBusyWithFileOps(isBusy: false); + if (CurrentProfileHasBrokenData()) + { + OnBrokenDataExists?.Invoke(); + } + + OnProfileReadDone?.Invoke(); + } + + private bool TryLoadSaveData(ref Stream stream, string fileName, DirectoryInfo directoryInfo, out T saveData) + { + saveData = default; + var flag = true; + var files = directoryInfo.GetFiles(fileName); + if (files.Length != 0) + { + stream = null; + if (TryOpenFile(files[0].FullName, ref stream)) + { + var jsonTextReader = new JsonTextReader(new StreamReader(stream)); + flag = !TryDeserializeJson(jsonTextReader, out saveData); + if (flag) + { + stream.Position = 0L; + flag = !TryDeserializeBinary(stream, out saveData); + } + + jsonTextReader.Close(); + } + } + + return flag; + } + + private bool TryLoadInputBindingsSave(ref Stream stream, DirectoryInfo directoryInfo, out string inputJSON) + { + inputJSON = null; + var result = true; + var files = directoryInfo.GetFiles(_inputActionsSettingsFilename); + if (files.Length != 0) + { + stream = null; + if (TryOpenFile(files[0].FullName, ref stream)) + { + result = !TryDeserializeJsonAsInputActionsData(stream, out inputJSON); + } + + var stream2 = stream; + if (stream2 != null) + { + stream2.Close(); + } + } + + return result; + } + + private bool TryOpenFile(string fullPath, ref Stream dataStream) + { + bool result; + try + { + dataStream = File.Open(fullPath, FileMode.Open); + result = true; + } + catch (Exception ex) + { + Debug.LogError("[" + ex.Message + "] Failed loading opening file " + fullPath); + result = false; + } + + return result; + } + + private bool TryDeserializeBinary(Stream dataStream, out T saveData) + { + bool result; + try + { + saveData = default; + saveData = (T)_binaryFormatter.Deserialize(dataStream); + Debug.Log("Successfully read " + typeof(T).Name + " save data as binary"); + result = true; + } + catch (Exception ex) + { + saveData = default; + Debug.LogError(string.Concat(new string[] + { + "[", + ex.Message, + "] Deserialization error for binary ", + typeof(T).Name, + " save data" + })); + result = false; + } + + return result; + } + + private bool TryDeserializeJson(JsonTextReader jsonReader, out T rebindingData) + { + bool result; + try + { + rebindingData = _jsonSerializer.Deserialize(jsonReader); + result = true; + } + catch (Exception) + { + rebindingData = default; + Debug.LogWarning("Could not read " + typeof(T).Name + " save data as JSON, it might be in binary so giving that a try."); + result = false; + } + + return result; + } + + private bool TryDeserializeJsonAsInputActionsData(Stream dataStream, out string inputJSON) + { + bool result; + try + { + using var streamReader = new StreamReader(dataStream); + var text = streamReader.ReadToEnd(); + inputJSON = text; + Debug.Log("Successfully read Input Bindings save data as JSON"); + result = true; + } + catch (Exception ex) + { + inputJSON = null; + Debug.LogError("[" + ex.Message + "] Deserialization error for Input Actions Save"); + result = false; + } + + return result; + } + + public bool CurrentProfileHasBrokenData() + { + if (currentProfile == null) + { + Debug.LogError("QSBStandaloneProfileManager.CurrentProfileHasBrokenData We should never get here outside of the Unity Editor"); + return false; + } + + return currentProfile.brokenSaveData || currentProfile.brokenMultSaveData || currentProfile.brokenSettingsData || currentProfile.brokenGfxSettingsData || currentProfile.brokenRebindingData; + } + + public bool BackupExistsForBrokenData() + { + var text = _profileBackupPath + "/" + currentProfile.profileName; + var savePath = text + "/" + _gameSaveFilename; + var multSavePath = text + "/" + _gameSaveMultFilename; + var settingsPath = text + "/" + _gameSettingsFilename; + var graphicsPath = text + "/" + _gfxSettingsFilename; + var inputsPath = text + "/" + _inputActionsSettingsFilename; + + return (currentProfile.brokenSaveData && File.Exists(savePath)) + || (currentProfile.brokenMultSaveData && File.Exists(multSavePath)) + || (currentProfile.brokenSettingsData && File.Exists(settingsPath)) + || (currentProfile.brokenGfxSettingsData && File.Exists(graphicsPath)) + || (currentProfile.brokenRebindingData && File.Exists(inputsPath)); + } + + private void LoadProfiles() + { + MarkBusyWithFileOps(true); + profiles.Clear(); + if (Directory.Exists(_profilesPath)) + { + QSBProfileData profileData = null; + Stream stream = null; + var files = new DirectoryInfo(_profilesPath).GetFiles("*.owprofile"); + foreach (var fileInfo in files) + { + DebugLog.DebugWrite(fileInfo.Name); + try + { + stream = null; + stream = File.Open(fileInfo.FullName, FileMode.Open); + var jsonTextReader = new JsonTextReader(new StreamReader(stream)); + try + { + profileData = _jsonSerializer.Deserialize(jsonTextReader); + } + catch + { + stream.Position = 0L; + profileData = (QSBProfileData)_binaryFormatter.Deserialize(stream); + } + finally + { + jsonTextReader.Close(); + } + + if (profileData == null) + { + DebugLog.DebugWrite("Profile at " + fileInfo.FullName + " null. Skipping."); + } + else + { + profiles.Add(profileData); + } + } + catch (Exception ex) + { + DebugLog.ToConsole("[" + ex.Message + "] Failed loading profile at " + fileInfo.Name, OWML.Common.MessageType.Error); + stream?.Close(); + } + } + } + else + { + DebugLog.DebugWrite($"{_profilesPath} does not exist"); + } + + MarkBusyWithFileOps(false); + } + + public void RestoreCurrentProfileBackup() + { + MarkBusyWithFileOps(isBusy: true); + var profilePath = _profilesPath + "/" + currentProfile.profileName; + var savePath = profilePath + "/" + _gameSaveFilename; + var multSavePath = profilePath + "/" + _gameSaveMultFilename; + var settingsPath = profilePath + "/" + _gameSettingsFilename; + var graphicsPath = profilePath + "/" + _gfxSettingsFilename; + var inputsPath = profilePath + "/" + _inputActionsSettingsFilename; + + var profileBackupPath = _profileBackupPath + "/" + currentProfile.profileName; + var saveBackupPath = profileBackupPath + "/" + _gameSaveFilename; + var multSaveBackupPath = profileBackupPath + "/" + _gameSaveMultFilename; + var settingsBackupPath = profileBackupPath + "/" + _gameSettingsFilename; + var graphicsBackupPath = profileBackupPath + "/" + _gfxSettingsFilename; + var inputsBackupPath = profileBackupPath + "/" + _inputActionsSettingsFilename; + + Stream stream = null; + try + { + if (!Directory.Exists(_profilesPath)) + { + Directory.CreateDirectory(_profilesPath); + } + + if (!Directory.Exists(_profileTempPath)) + { + Directory.CreateDirectory(_profileTempPath); + } + + if (!Directory.Exists(_profileBackupPath)) + { + Directory.CreateDirectory(_profileBackupPath); + } + + if (!Directory.Exists(profilePath)) + { + Directory.CreateDirectory(profilePath); + } + + if (!Directory.Exists(profileBackupPath)) + { + Directory.CreateDirectory(profileBackupPath); + } + + var di = new DirectoryInfo(profileBackupPath); + + if (currentProfile.brokenSaveData && File.Exists(saveBackupPath)) + { + currentProfile.gameSave = LoadAndCopyBackupSave(_gameSaveFilename, saveBackupPath, savePath); + } + + if (currentProfile.brokenMultSaveData && File.Exists(multSaveBackupPath)) + { + currentProfile.multiplayerGameSave = LoadAndCopyBackupSave(_gameSaveMultFilename, multSaveBackupPath, multSavePath); + } + + if (currentProfile.brokenSettingsData && File.Exists(settingsBackupPath)) + { + currentProfile.settingsSave = LoadAndCopyBackupSave(_gameSettingsFilename, settingsBackupPath, settingsPath); + } + + if (currentProfile.brokenGfxSettingsData && File.Exists(graphicsBackupPath)) + { + currentProfile.graphicsSettings = LoadAndCopyBackupSave(_gfxSettingsFilename, graphicsBackupPath, graphicsPath); + } + + if (currentProfile.brokenRebindingData && File.Exists(inputsBackupPath)) + { + TryLoadInputBindingsSave(ref stream, di, out var inputJSON); + if (inputJSON != "") + { + currentProfile.inputJSON = inputJSON; + File.Copy(inputsBackupPath, inputsPath, overwrite: true); + } + else + { + Debug.LogError("Could not load backup input bindings save."); + } + + stream?.Close(); + stream = null; + } + + OnBackupDataRestored?.Invoke(); + + T LoadAndCopyBackupSave(string fileName, string backupPath, string fullPath) where T : class + { + TryLoadSaveData(ref stream, fileName, di, out var saveData); + if (saveData != null) + { + File.Copy(backupPath, fullPath, overwrite: true); + } + else + { + Debug.LogError("Could not load backup " + typeof(T).Name + " save."); + } + + stream?.Close(); + stream = null; + return saveData; + } + } + catch (Exception ex) + { + stream?.Close(); + Debug.LogError("Exception during backup restore: " + ex.Message); + MarkBusyWithFileOps(isBusy: false); + } + + MarkBusyWithFileOps(isBusy: false); + } + + private bool TrySaveProfile(QSBProfileData profileData, GameSave gameSave, SettingsSave settingsSave, GraphicSettings graphicsSettings, string inputJson) + { + MarkBusyWithFileOps(isBusy: true); + var profilePath = _profilesPath + "/" + profileData.profileName; + var profileManifestPath = _profilesPath + "/" + profileData.profileName + ".owprofile"; + var saveDataPath = profilePath + "/" + _gameSaveFilename; + var multSaveDataPath = profilePath + "/" + _gameSaveMultFilename; + var settingsPath = profilePath + "/" + _gameSettingsFilename; + var graphicsPath = profilePath + "/" + _gfxSettingsFilename; + var inputsPath = profilePath + "/" + _inputActionsSettingsFilename; + + var tempProfilePath = _profileTempPath + "/GameData"; + var tempProfileManifestPath = _profileTempPath + "/CurrentProfile.owprofile"; + var tempSaveDataPath = tempProfilePath + "/" + _gameSaveFilename; + var tempMultSaveDataPath = tempProfilePath + "/" + _gameSaveMultFilename; + var tempSettingsPath = tempProfilePath + "/" + _gameSettingsFilename; + var tempGraphicsPath = tempProfilePath + "/" + _gfxSettingsFilename; + var tempInputsPath = tempProfilePath + "/" + _inputActionsSettingsFilename; + + var backupProfilePath = _profileBackupPath + "/" + profileData.profileName; + var backupSaveDataPath = backupProfilePath + "/" + _gameSaveFilename; + var backupMultSaveDataPath = backupProfilePath + "/" + _gameSaveMultFilename; + var backupSettingsPath = backupProfilePath + "/" + _gameSettingsFilename; + var backupGraphicsPath = backupProfilePath + "/" + _gfxSettingsFilename; + var backupInputsPath = backupProfilePath + "/" + _inputActionsSettingsFilename; + + Stream stream = null; + try + { + // Create folders if they don't exist + + if (!Directory.Exists(_profilesPath)) + { + Directory.CreateDirectory(_profilesPath); + } + + if (!Directory.Exists(_profileTempPath)) + { + Directory.CreateDirectory(_profileTempPath); + } + + if (!Directory.Exists(_profileBackupPath)) + { + Directory.CreateDirectory(_profileBackupPath); + } + + if (!Directory.Exists(profilePath)) + { + Directory.CreateDirectory(profilePath); + } + + if (!Directory.Exists(tempProfilePath)) + { + Directory.CreateDirectory(tempProfilePath); + } + + if (!Directory.Exists(backupProfilePath)) + { + Directory.CreateDirectory(backupProfilePath); + } + + // create temp files + + SaveData(tempProfileManifestPath, profileData); + if (gameSave != null) + { + if (QSBCore.IsInMultiplayer) + { + profileData.multiplayerGameSave = SaveData(tempMultSaveDataPath, gameSave); + } + else + { + profileData.gameSave = SaveData(tempSaveDataPath, gameSave); + } + } + + if (settingsSave != null) + { + profileData.settingsSave = SaveData(tempSettingsPath, settingsSave); + } + + if (graphicsSettings != null) + { + profileData.graphicsSettings = SaveData(tempGraphicsPath, graphicsSettings); + } + + if (inputJson != null) + { + File.WriteAllText(tempInputsPath, inputJson); + profileData.inputJSON = inputJson; + } + + // create backups of old files + + if (File.Exists(saveDataPath)) + { + File.Copy(saveDataPath, backupSaveDataPath, overwrite: true); + } + + if (File.Exists(multSaveDataPath)) + { + File.Copy(multSaveDataPath, backupMultSaveDataPath, overwrite: true); + } + + if (File.Exists(settingsPath)) + { + File.Copy(settingsPath, backupSettingsPath, overwrite: true); + } + + if (File.Exists(graphicsPath)) + { + File.Copy(graphicsPath, backupGraphicsPath, overwrite: true); + } + + if (File.Exists(inputsPath)) + { + File.Copy(inputsPath, backupInputsPath, overwrite: true); + } + + // delete old files and move temp files + + File.Delete(profileManifestPath); + File.Move(tempProfileManifestPath, profileManifestPath); + + if (gameSave != null) + { + if (QSBCore.IsInMultiplayer) + { + File.Delete(multSaveDataPath); + File.Move(tempMultSaveDataPath, multSaveDataPath); + } + else + { + File.Delete(saveDataPath); + File.Move(tempSaveDataPath, saveDataPath); + } + } + + if (settingsSave != null) + { + File.Delete(settingsPath); + File.Move(tempSettingsPath, settingsPath); + } + + if (graphicsSettings != null) + { + File.Delete(graphicsPath); + File.Move(tempGraphicsPath, graphicsPath); + } + + if (inputJson != null) + { + File.Delete(inputsPath); + File.Move(tempInputsPath, inputsPath); + } + + OnProfileDataSaved?.Invoke(true); + } + catch (Exception ex) + { + if (stream != null) + { + stream.Close(); + } + + OnProfileDataSaved?.Invoke(false); + + Debug.LogError("[" + ex.Message + "] Error saving file for " + profileData.profileName); + MarkBusyWithFileOps(isBusy: false); + return false; + } + + MarkBusyWithFileOps(isBusy: false); + return true; + + T SaveData(string filePath, T data) + { + stream = File.Open(filePath, FileMode.Create); + using (JsonWriter jsonWriter = new JsonTextWriter(new StreamWriter(stream))) + { + _jsonSerializer.Serialize(jsonWriter, data); + } + + stream = null; + return data; + } + } + + public bool IsValidCharacterForProfileName(char inputChar) + { + if (char.IsWhiteSpace(inputChar)) + { + return false; + } + + var invalidFileNameChars = Path.GetInvalidFileNameChars(); + for (var i = 0; i < invalidFileNameChars.Length; i++) + { + if (invalidFileNameChars[i] == inputChar) + { + return false; + } + } + + return inputChar != '.'; + } + + public bool ValidateProfileName(string profileName) + { + var result = true; + if (profileName == "") + { + result = false; + } + else if (profileName.Length > 16) + { + result = false; + } + else if (profiles.Count > 0) + { + for (var i = 0; i < profiles.Count; i++) + { + if (profiles[i].profileName == profileName) + { + result = false; + } + } + } + + return result; + } + + public bool TryCreateProfile(string profileName) + { + var savedProfile = ValidateProfileName(profileName); + if (savedProfile) + { + var noProfilesExist = profiles.Count == 0; + var profileData = new QSBProfileData + { + profileName = profileName, + lastModifiedTime = DateTime.UtcNow + }; + var gameSave = new GameSave(); + var multGameSave = new GameSave(); + var settingsSave = new SettingsSave(); + var graphicSettings = currentProfileGraphicsSettings; + if (graphicSettings == null) + { + graphicSettings = new GraphicSettings(init: true); + } + + var text = ((InputManager)OWInput.SharedInputManager).commandManager.DefaultInputActions.ToJson(); + profiles.Add(profileData); + profileData.gameSave = gameSave; + profileData.multiplayerGameSave = multGameSave; + profileData.settingsSave = settingsSave; + profileData.graphicsSettings = graphicSettings; + profileData.inputJSON = text; + savedProfile = TrySaveProfile(profileData, gameSave, settingsSave, graphicSettings, text); + if (savedProfile) + { + if (currentProfile != null && currentProfile.profileName != string.Empty) + { + OnProfileSignOutComplete?.Invoke(); + } + + currentProfile = profileData; + if (noProfilesExist) + { + OnProfileSignInComplete?.Invoke(ProfileManagerSignInResult.COMPLETE); + OnProfileReadDone?.Invoke(); + } + else + { + OnProfileSignInComplete?.Invoke(ProfileManagerSignInResult.COMPLETE); + OnProfileReadDone?.Invoke(); + OnUpdatePlayerProfiles?.Invoke(); + } + } + else + { + DeleteProfile(profileName); + } + } + + return savedProfile; + } + + public bool SwitchProfile(string profileName) + { + LoadSaveFilesFromProfiles(); + var flag = false; + for (var i = 0; i < profiles.Count; i++) + { + if (profileName == profiles[i].profileName) + { + if (currentProfile != null && currentProfile.profileName != string.Empty && OnProfileSignOutComplete != null) + { + OnProfileSignOutComplete(); + } + + currentProfile = profiles[i]; + flag = true; + break; + } + } + + if (flag) + { + currentProfile.lastModifiedTime = DateTime.UtcNow; + TrySaveProfile(currentProfile, null, null, null, null); + OnProfileSignInComplete?.Invoke(ProfileManagerSignInResult.COMPLETE); + + if (CurrentProfileHasBrokenData() && OnBrokenDataExists != null) + { + OnBrokenDataExists(); + return false; + } + + OnProfileReadDone?.Invoke(); + } + + return true; + } + + public void DeleteProfile(string profileName) + { + Debug.Log("DeleteProfile"); + var flag = false; + var profileData = new QSBProfileData + { + profileName = string.Empty + }; + for (var i = 0; i < profiles.Count; i++) + { + if (profileName == profiles[i].profileName) + { + profileData = profiles[i]; + flag = true; + break; + } + } + + if (!flag) + { + return; + } + + MarkBusyWithFileOps(isBusy: true); + var profileManifestPath = _profilesPath + "/" + profileData.profileName + ".owprofile"; + var profilePath = _profilesPath + "/" + profileData.profileName; + var gameSavePath = profilePath + "/" + _gameSaveFilename; + var multGameSavePath = profilePath + "/" + _gameSaveMultFilename; + var settingsPath = profilePath + "/" + _gameSettingsFilename; + var graphicsPath = profilePath + "/" + _gfxSettingsFilename; + var oldInputsPath = profilePath + "/" + _legacyInputBindingSettingsFilename; + var inputsPath = profilePath + "/" + _inputActionsSettingsFilename; + + var backupProfilePath = _profileBackupPath + "/" + profileData.profileName; + var backupGameSave = backupProfilePath + "/" + _gameSaveFilename; + var backupMultGameSave = backupProfilePath + "/" + _gameSaveMultFilename; + var backupSettingsPath = backupProfilePath + "/" + _gameSettingsFilename; + var backupGraphicsPath = backupProfilePath + "/" + _gfxSettingsFilename; + var backupOldInputsPath = backupProfilePath + "/" + _legacyInputBindingSettingsFilename; + var backupInputsPath = backupProfilePath + "/" + _inputActionsSettingsFilename; + Stream stream = null; + try + { + if (File.Exists(profileManifestPath)) + { + File.Delete(profileManifestPath); + Debug.Log("Delete " + profileManifestPath); + } + + if (File.Exists(gameSavePath)) + { + File.Delete(gameSavePath); + Debug.Log("Delete " + gameSavePath); + } + + if (File.Exists(multGameSavePath)) + { + File.Delete(multGameSavePath); + Debug.Log("Delete " + multGameSavePath); + } + + if (File.Exists(settingsPath)) + { + File.Delete(settingsPath); + Debug.Log("Delete " + settingsPath); + } + + if (File.Exists(graphicsPath)) + { + File.Delete(graphicsPath); + Debug.Log("Delete " + graphicsPath); + } + + if (File.Exists(oldInputsPath)) + { + File.Delete(oldInputsPath); + Debug.Log("Delete " + oldInputsPath); + } + + if (File.Exists(inputsPath)) + { + File.Delete(inputsPath); + Debug.Log("Delete " + inputsPath); + } + + if (File.Exists(backupGameSave)) + { + File.Delete(backupGameSave); + Debug.Log("Delete " + backupGameSave); + } + + if (File.Exists(backupMultGameSave)) + { + File.Delete(backupMultGameSave); + Debug.Log("Delete " + backupMultGameSave); + } + + if (File.Exists(backupSettingsPath)) + { + File.Delete(backupSettingsPath); + Debug.Log("Delete " + backupSettingsPath); + } + + if (File.Exists(backupGraphicsPath)) + { + File.Delete(backupGraphicsPath); + Debug.Log("Delete " + backupGraphicsPath); + } + + if (File.Exists(backupOldInputsPath)) + { + File.Delete(backupOldInputsPath); + Debug.Log("Delete " + backupOldInputsPath); + } + + if (File.Exists(backupInputsPath)) + { + File.Delete(backupInputsPath); + Debug.Log("Delete " + backupInputsPath); + } + + profiles.Remove(profileData); + var files = Directory.GetFiles(profilePath); + var directories = Directory.GetDirectories(profilePath); + if (files.Length == 0 && directories.Length == 0) + { + Directory.Delete(profilePath); + } + else + { + Debug.LogWarning(" Directory not empty. Cannot delete. "); + } + + if (Directory.Exists(backupProfilePath)) + { + files = Directory.GetFiles(backupProfilePath); + directories = Directory.GetDirectories(backupProfilePath); + if (files.Length == 0 && directories.Length == 0) + { + Directory.Delete(backupProfilePath); + } + else + { + Debug.LogWarning("Backup Directory not empty. Cannot delete."); + } + } + + OnUpdatePlayerProfiles?.Invoke(); + } + catch (Exception ex) + { + stream?.Close(); + Debug.LogError("[" + ex.Message + "] Failed to delete all profile data"); + MarkBusyWithFileOps(isBusy: false); + } + + MarkBusyWithFileOps(isBusy: false); + } +}