mirror of
https://github.com/misternebula/quantum-space-buddies.git
synced 2025-01-01 12:08:52 +00:00
414 lines
11 KiB
C#
414 lines
11 KiB
C#
using Microsoft.Xbox;
|
|
using Newtonsoft.Json;
|
|
using QSB.Utility;
|
|
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 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; private set; }
|
|
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 (!isInitialized)
|
|
{
|
|
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
|
|
};
|
|
isInitialized = true;
|
|
return;
|
|
}
|
|
|
|
OnProfileSignInComplete?.Invoke(ProfileManagerSignInResult.COMPLETE);
|
|
OnProfileReadDone?.Invoke();
|
|
|
|
DebugLog.DebugWrite("INITIALIZED");
|
|
}
|
|
|
|
public void PreInitialize()
|
|
{
|
|
if (_preInitialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_fileOpsBusyLocks = 0;
|
|
_pendingGameSave = null;
|
|
_pendingSettingsSave = null;
|
|
_pendingGfxSettingsSave = null;
|
|
_pendingInputActionsSave = null;
|
|
_preInitialized = true;
|
|
}
|
|
|
|
public void InvokeProfileSignInComplete() =>
|
|
OnProfileSignInComplete?.Invoke(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)
|
|
{
|
|
DebugLog.DebugWrite("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)
|
|
{
|
|
DebugLog.DebugWrite($"LoadGame blobName:{blobName}");
|
|
_fileOpsBusyLocks++;
|
|
Gdk.Helpers.LoadSaveData(blobName);
|
|
}
|
|
|
|
private void WriteSaveToStorage(QSBX1SaveData saveData, string blobName)
|
|
{
|
|
DebugLog.DebugWrite("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--;
|
|
DebugLog.DebugWrite("[GDK] save to blob " + blobName + " complete");
|
|
}
|
|
|
|
private void OnGameSaveFailed(object sender, string blobName)
|
|
{
|
|
_fileOpsBusyLocks--;
|
|
DebugLog.DebugWrite("[GDK] save to blob " + blobName + " failed");
|
|
}
|
|
|
|
private void OnGameSaveLoaded(object sender, string blobName, GameSaveLoadedArgs saveData)
|
|
{
|
|
_fileOpsBusyLocks--;
|
|
DebugLog.DebugWrite("[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 tempSaveData = _jsonSerializer.Deserialize<QSBX1SaveData>(jsonTextReader);
|
|
if (_isLoadingGameBlob)
|
|
{
|
|
if (tempSaveData != null)
|
|
{
|
|
if (tempSaveData.gameSave == null)
|
|
{
|
|
DebugLog.DebugWrite("[GDK] tempSaveData.gameSave is null (oh no)");
|
|
}
|
|
|
|
if (tempSaveData.gameMultSave == null)
|
|
{
|
|
DebugLog.DebugWrite("[GDK] tempSaveData.gameMultSave is null (oh no)");
|
|
}
|
|
|
|
_saveData.gameSave = tempSaveData.gameSave ?? new GameSave();
|
|
_saveData.gameMultSave = tempSaveData.gameMultSave ?? new GameSave();
|
|
}
|
|
else
|
|
{
|
|
DebugLog.DebugWrite("[GDK] tempSaveData is null (oh no)");
|
|
_saveData.gameSave = new GameSave();
|
|
_saveData.gameMultSave = new GameSave();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (tempSaveData != null)
|
|
{
|
|
_saveData.gfxSettings = tempSaveData.gfxSettings ?? new GraphicSettings(true);
|
|
_saveData.settings = tempSaveData.settings ?? new SettingsSave();
|
|
_saveData.inputActionsJson = tempSaveData.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();
|
|
}
|
|
|
|
DebugLog.DebugWrite(string.Format("after settings load, _saveData.gameSave is null: {0}", _saveData.gameSave == null));
|
|
DebugLog.DebugWrite(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;
|
|
OnProfileReadDone?.Invoke();
|
|
DebugLog.DebugWrite("LOADED SETTINGS BLOB");
|
|
}
|
|
}
|
|
|
|
private void OnGameSaveLoadFailed(object sender, string blobName)
|
|
{
|
|
DebugLog.DebugWrite("OnGameSaveLoadFailed");
|
|
_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;
|
|
OnProfileReadDone?.Invoke();
|
|
DebugLog.DebugWrite("LOADING SETTINGS BLOB - FROM FAILED GAME LOAD");
|
|
}
|
|
}
|
|
|
|
[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;
|
|
}
|
|
}
|
|
}
|