using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Reptile;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")]
[assembly: AssemblyCompany("SaveDataAPI")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("My first plugin")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("SaveDataAPI")]
[assembly: AssemblyTitle("SaveDataAPI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace SaveDataAPI
{
public abstract class ADataSaver
{
public CustomSaveData SaveData;
public abstract void PrepareSave();
public abstract void OnLoad();
public abstract void Write(BinaryWriter writer);
public abstract void Read(BinaryReader reader);
}
public class CustomSaveData : ISaveable
{
private class OrphanedData
{
public string AssemblyName;
public string TypeFullName;
public byte[] Data;
public OrphanedData(string assemblyName, string typeFullName, byte[] data)
{
AssemblyName = assemblyName;
TypeFullName = typeFullName;
Data = data;
}
}
private List<ADataSaver> _savers = new List<ADataSaver>();
private const string FileName = "CustomGameProgress{0}.cbrp";
private const string BackupFileName = "CustomGameProgress{0}_backup.cbrp";
private ATransaction _lastLoadTransaction;
private int _slotID;
private List<OrphanedData> _orphanedSavers = new List<OrphanedData>();
public bool BusyLoading
{
get
{
if (_lastLoadTransaction == null)
{
return false;
}
if (_lastLoadTransaction.IsDone)
{
return false;
}
return true;
}
}
public int Slot => _slotID;
public List<ADataSaver> Savers
{
get
{
lock (_savers)
{
return new List<ADataSaver>(_savers);
}
}
set
{
lock (_savers)
{
_savers = value;
}
}
}
public CustomSaveData(int slot)
{
_slotID = slot;
}
public void Write(BinaryWriter writer)
{
lock (_savers)
{
writer.Write((byte)0);
writer.Write(_savers.Count + _orphanedSavers.Count);
foreach (ADataSaver saver in _savers)
{
Type type = saver.GetType();
writer.Write(type.Assembly.GetName().Name);
writer.Write(type.FullName);
long position = writer.BaseStream.Position;
writer.Write(0);
saver.Write(writer);
long position2 = writer.BaseStream.Position;
writer.Seek((int)position, SeekOrigin.Begin);
writer.Write((int)(position2 - position - 4));
writer.Seek((int)position2, SeekOrigin.Begin);
}
foreach (OrphanedData orphanedSaver in _orphanedSavers)
{
writer.Write(orphanedSaver.AssemblyName);
writer.Write(orphanedSaver.TypeFullName);
writer.Write(orphanedSaver.Data.Length);
writer.Write(orphanedSaver.Data);
}
}
}
public void Read(BinaryReader reader)
{
lock (_savers)
{
reader.ReadByte();
int num = reader.ReadInt32();
for (int i = 0; i < num; i++)
{
string text = reader.ReadString();
string text2 = reader.ReadString();
int count = reader.ReadInt32();
long position = reader.BaseStream.Position;
Assembly assembly = FindAssemblyByName(text);
Type type = null;
bool flag = false;
if (assembly == null)
{
flag = true;
}
else
{
type = assembly.GetType(text2);
if (type == null)
{
flag = true;
}
else if (!typeof(ADataSaver).IsAssignableFrom(type))
{
flag = true;
}
}
if (flag)
{
Plugin.Instance.GetLogger().LogWarning((object)$"Found orphaned data for custom save slot {Slot}: {text2}, {text}");
if (Plugin.Instance.KeepOrphanedData)
{
Plugin.Instance.GetLogger().LogWarning((object)"Keeping it around.");
byte[] data2 = reader.ReadBytes(count);
AddOrphanedData(text, text2, data2);
}
else
{
Plugin.Instance.GetLogger().LogWarning((object)"Disposing of it.");
reader.ReadBytes(count);
}
continue;
}
try
{
GetSaver(type).Read(reader);
}
catch (Exception arg)
{
Plugin.Instance.GetLogger().LogError((object)$"Failed to read custom save data for Saver type: {text2}, {text}. {arg}");
reader.BaseStream.Position = position;
byte[] data3 = reader.ReadBytes(count);
if (Plugin.Instance.KeepOrphanedData)
{
Plugin.Instance.GetLogger().LogWarning((object)"Keeping it around as orphaned data.");
AddOrphanedData(text, text2, data3);
}
}
}
}
void AddOrphanedData(string assemblyName, string typeName, byte[] data)
{
OrphanedData item = new OrphanedData(assemblyName, typeName, data);
_orphanedSavers.Add(item);
}
}
public void DeleteOrphanedSaver(string assemblyName, string typeFullName)
{
foreach (OrphanedData item in new List<OrphanedData>(_orphanedSavers))
{
if (item.AssemblyName == assemblyName && item.TypeFullName == typeFullName)
{
_orphanedSavers.Remove(item);
break;
}
}
}
public T GetDataSaver<T>() where T : ADataSaver
{
lock (_savers)
{
foreach (ADataSaver saver in _savers)
{
if (saver.GetType() == typeof(T))
{
return (T)saver;
}
}
}
return null;
}
internal void OnLoad()
{
lock (_savers)
{
foreach (ADataSaver saver in _savers)
{
saver.OnLoad();
}
}
}
internal LoadTransaction<CustomSaveData> LoadData(string filename)
{
lock (_savers)
{
PrepareSaversForLoad();
LoadTransaction<CustomSaveData> val = Core.Instance.Platform.Storage.LoadDataFromStorage<CustomSaveData>(this, filename, "CustomData", (FormatType)1);
Core.Instance.SaveManager.AddLoadTransactionToTracker((ATransaction)(object)val);
_lastLoadTransaction = (ATransaction)(object)val;
return val;
}
}
internal SaveTransaction<CustomSaveData> SaveData(string filename)
{
lock (_savers)
{
PrepareSaversForSave();
SaveTransaction<CustomSaveData> val = Core.Instance.Platform.Storage.SaveDataAtStorage<CustomSaveData>(this, filename, "CustomData", (FormatType)1);
Core.Instance.SaveManager.AddSaveTransactionToTracker((ATransaction)(object)val);
return val;
}
}
internal static string GetFilename(int slotId)
{
return $"CustomGameProgress{slotId}.cbrp";
}
internal static string GetBackupFilename(int slotId)
{
return $"CustomGameProgress{slotId}_backup.cbrp";
}
private void PrepareSaversForLoad()
{
_savers.Clear();
foreach (Type saver in CustomSaveManager.Savers)
{
ADataSaver item = CreateSaver(saver);
_savers.Add(item);
}
}
private void PrepareSaversForSave()
{
_savers.Clear();
foreach (Type saver in CustomSaveManager.Savers)
{
ADataSaver aDataSaver = CreateSaver(saver);
aDataSaver.PrepareSave();
_savers.Add(aDataSaver);
}
}
private ADataSaver CreateSaver(Type type)
{
ADataSaver obj = (ADataSaver)Activator.CreateInstance(type);
obj.SaveData = this;
return obj;
}
private ADataSaver GetSaver(Type type)
{
foreach (ADataSaver saver in _savers)
{
if (saver.GetType() == type)
{
return saver;
}
}
return null;
}
private Assembly FindAssemblyByName(string name)
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
if (assembly.GetName().Name == name)
{
return assembly;
}
}
return null;
}
}
public static class CustomSaveManager
{
internal static List<Type> Savers = new List<Type>();
internal static bool StageInitializedCalledForCurrentSaveYet = true;
private static Dictionary<int, CustomSaveData> SaveDataBySlotID = new Dictionary<int, CustomSaveData>();
public static CustomSaveData CurrentSaveData
{
get
{
if (!Core.Instance.SaveManager.HasCurrentSaveSlot)
{
return null;
}
return GetSaveDataForSlot(Core.Instance.SaveManager.CurrentSaveSlot.saveSlotId);
}
}
public static CustomSaveData GetSaveDataForSlot(int slot)
{
if (SaveDataBySlotID.TryGetValue(slot, out var value))
{
return value;
}
return CreateSaveDataForSlot(slot);
}
public static void SetSaveDataForSlot(int slotId, CustomSaveData saveData)
{
SaveDataBySlotID[slotId] = saveData;
}
public static void DeleteSaveDataForSlot(int slotId)
{
DeleteData(CustomSaveData.GetFilename(slotId));
RemoveSaveDataForSlot(slotId);
}
internal static void RegisterSavers()
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i = 0; i < assemblies.Length; i++)
{
Type[] types = assemblies[i].GetTypes();
foreach (Type type in types)
{
if (typeof(ADataSaver).IsAssignableFrom(type) && !type.IsAbstract)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Registered saver {type}");
}
Savers.Add(type);
}
}
}
}
internal static void Core_OnCoreInitialized()
{
//IL_0020: Unknown result type (might be due to invalid IL or missing references)
//IL_002a: Expected O, but got Unknown
((ATransaction)Core.Instance.Platform.Storage.CreateDirectory("CustomData")).OnTransactionDone += new TransactionDoneDelegate(OnCreateDirectoryFinished);
RegisterSavers();
}
private static void OnCreateDirectoryFinished(ATransaction transaction)
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Expected O, but got Unknown
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Invalid comparison between Unknown and I4
//IL_0039: Unknown result type (might be due to invalid IL or missing references)
//IL_0043: Expected O, but got Unknown
ManualLogSource logger = Plugin.Instance.GetLogger();
transaction.OnTransactionDone -= new TransactionDoneDelegate(OnCreateDirectoryFinished);
if ((int)transaction.Status == 7)
{
logger.LogWarning((object)"Failed to create CustomData directory, trying again.");
transaction.OnTransactionDone += new TransactionDoneDelegate(OnCreateDirectoryFinished);
transaction.ScheduleForReset();
Core.Instance.Platform.Storage.EnqueueTransaction(transaction);
}
else if (transaction.HasError)
{
logger.LogError((object)"Failed to create CustomData directory.");
}
else if (DebugSettings.Verbose)
{
logger.LogInfo((object)"Created CustomData directory.");
}
}
internal static void StageManager_OnStageInitialized()
{
if (!StageInitializedCalledForCurrentSaveYet)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)"Calling StageInitialize. New save loaded.");
}
StageInitializedCalledForCurrentSaveYet = true;
CurrentSaveData?.OnLoad();
}
}
internal static DeleteTransaction DeleteData(string filename)
{
return Core.Instance.Platform.Storage.DeleteDataFromStorage(filename, "CustomData");
}
internal static void RemoveSaveDataForSlot(int slotId)
{
if (SaveDataBySlotID.ContainsKey(slotId))
{
SaveDataBySlotID.Remove(slotId);
}
}
internal static CustomSaveData CreateSaveDataForSlot(int slot)
{
CustomSaveData customSaveData = new CustomSaveData(slot);
SaveDataBySlotID[slot] = customSaveData;
return customSaveData;
}
}
internal static class DebugSettings
{
public static bool Verbose;
}
[BepInPlugin("org.lazyduchess.plugins.brc.savedataapi", "SaveData API", "1.0.1.0")]
[BepInProcess("Bomb Rush Cyberfunk.exe")]
internal class Plugin : BaseUnityPlugin
{
public static Plugin Instance;
private const string GUID = "org.lazyduchess.plugins.brc.savedataapi";
private const string Name = "SaveData API";
private const string Version = "1.0.1.0";
private ConfigEntry<bool> _keepOrphanedData;
internal bool KeepOrphanedData => _keepOrphanedData.Value;
private void Awake()
{
//IL_000d: Unknown result type (might be due to invalid IL or missing references)
//IL_0017: Expected O, but got Unknown
//IL_001e: Unknown result type (might be due to invalid IL or missing references)
//IL_0028: Expected O, but got Unknown
//IL_004e: Unknown result type (might be due to invalid IL or missing references)
Instance = this;
try
{
Core.OnCoreInitialized += new OnCoreInitializedHandler(CustomSaveManager.Core_OnCoreInitialized);
StageManager.OnStageInitialized += new OnStageInitializedDelegate(CustomSaveManager.StageManager_OnStageInitialized);
_keepOrphanedData = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "KeepOrphanedData", true, "If true, custom save data from mods that were removed, accidentally or otherwise, will be kept around, so progress won't be lost if the mod is reinstalled later.");
new Harmony("org.lazyduchess.plugins.brc.savedataapi").PatchAll();
((BaseUnityPlugin)this).Logger.LogInfo((object)"SaveData API 1.0.1.0 was initialized!");
}
catch (Exception arg)
{
((BaseUnityPlugin)this).Logger.LogError((object)string.Format("Failed to initialize {0} {1}: {2}", "SaveData API", "1.0.1.0", arg));
}
}
public ManualLogSource GetLogger()
{
return ((BaseUnityPlugin)this).Logger;
}
}
public static class PluginInfo
{
public const string PLUGIN_GUID = "SaveDataAPI";
public const string PLUGIN_NAME = "SaveDataAPI";
public const string PLUGIN_VERSION = "1.0.0";
}
}
namespace SaveDataAPI.Patches
{
[HarmonyPatch(typeof(SaveSlotHandler))]
internal class SaveSlotHandlerPatch
{
[HarmonyPostfix]
[HarmonyPatch("SaveSaveSlot")]
private static void SaveSaveSlot_Postfix(int saveSlotId)
{
if (saveSlotId > -1)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Saving custom data for Slot {saveSlotId}");
}
CustomSaveData saveDataForSlot = CustomSaveManager.GetSaveDataForSlot(saveSlotId);
string filename = CustomSaveData.GetFilename(saveSlotId);
saveDataForSlot.SaveData(filename);
}
}
[HarmonyPostfix]
[HarmonyPatch("SaveSaveSlotBackup")]
private static void SaveSaveSlotBackup_Postfix(int slotId)
{
if (slotId > -1)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Saving backup custom data for Slot {slotId}");
}
CustomSaveData saveDataForSlot = CustomSaveManager.GetSaveDataForSlot(slotId);
string backupFilename = CustomSaveData.GetBackupFilename(slotId);
saveDataForSlot.SaveData(backupFilename);
}
}
[HarmonyPrefix]
[HarmonyPatch("LoadSaveSlot")]
private static void LoadSaveSlot_Prefix(string fileName)
{
int slotFromFilename = GetSlotFromFilename(fileName);
if (slotFromFilename > -1)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Loading custom data for Slot {slotFromFilename}");
}
CustomSaveManager.GetSaveDataForSlot(slotFromFilename).LoadData(CustomSaveData.GetFilename(slotFromFilename));
}
}
[HarmonyPrefix]
[HarmonyPatch("LoadSaveSlotBackup")]
private static void LoadSaveSlotBackup_Prefix(SaveSlotData saveSlotData, string saveSlotFileName)
{
int slotFromFilename = GetSlotFromFilename(saveSlotFileName);
if (slotFromFilename > -1)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Loading backup custom data for Slot {slotFromFilename}");
}
CustomSaveManager.GetSaveDataForSlot(slotFromFilename).LoadData(CustomSaveData.GetBackupFilename(slotFromFilename));
}
}
[HarmonyPrefix]
[HarmonyPatch("DeleteSaveSlot")]
private static void DeleteSaveSlot_Prefix(int slotId)
{
if (slotId > -1)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Deleting custom data for Slot {slotId}");
}
CustomSaveManager.RemoveSaveDataForSlot(slotId);
CustomSaveManager.DeleteData(CustomSaveData.GetFilename(slotId));
}
}
[HarmonyPrefix]
[HarmonyPatch("DeleteSaveSlotDataBackup")]
private static void DeleteSaveSlotDataBackup_Prefix(int slotId)
{
if (slotId > -1)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Deleting backup custom data for Slot {slotId}");
}
CustomSaveManager.DeleteData(CustomSaveData.GetBackupFilename(slotId));
}
}
[HarmonyPrefix]
[HarmonyPatch("AddNewSaveSlot")]
private static void AddNewSaveSlot_Prefix(int saveSlotId)
{
if (saveSlotId > -1)
{
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Creating custom data for Slot {saveSlotId}");
}
CustomSaveManager.CreateSaveDataForSlot(saveSlotId);
}
}
[HarmonyPrefix]
[HarmonyPatch("SetCurrentSaveSlotDataBySlotId")]
private static void SetCurrentSaveSlotDataBySlotId_Postfix(int saveSlotId)
{
if (saveSlotId > -1)
{
CustomSaveManager.StageInitializedCalledForCurrentSaveYet = false;
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)$"Current save slot set to {saveSlotId}. Will call initialization on stage load.");
}
}
}
private static int GetSlotFromFilename(string filename)
{
bool flag = false;
string text = "";
for (int i = 0; i < filename.Length; i++)
{
if (char.IsDigit(filename[i]))
{
text += filename[i];
flag = true;
}
else if (flag)
{
break;
}
}
if (string.IsNullOrEmpty(text))
{
return -1;
}
if (DebugSettings.Verbose)
{
Plugin.Instance.GetLogger().LogInfo((object)("Slot ID for " + filename + ": " + text));
}
return int.Parse(text);
}
}
}