Decompiled source of ProperSave v3.0.3
plugins/ProperSave/ProperSave.dll
Decompiled 17 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using BiggerBazaar; using HG; using HG.Reflection; using IL.RoR2; using Mono.Cecil; using Mono.Cecil.Cil; using MonoMod.Cil; using MonoMod.RuntimeDetour.HookGen; using On.RoR2; using On.RoR2.UI; using PSTinyJson; using ProperSave.Components; using ProperSave.Data; using ProperSave.Old; using ProperSave.Old.Data; using ProperSave.Old.SaveData; using ProperSave.Old.SaveData.Artifacts; using ProperSave.Old.SaveData.Runs; using ProperSave.SaveData; using ProperSave.SaveData.Artifacts; using ProperSave.SaveData.Runs; using ProperSave.TinyJson; using ProperSave.Utils; using R2API; using RoR2; using RoR2.Artifacts; using RoR2.CharacterAI; using RoR2.ContentManagement; using RoR2.ExpansionManagement; using RoR2.Networking; using RoR2.Skills; using RoR2.Stats; using RoR2.UI; using ShareSuite; using TMPro; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.UI; using Zio; using Zio.FileSystems; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: OptIn] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("3.0.3.0")] namespace PSTinyJson { public static class JSONParser { [ThreadStatic] private static Stack<List<string>> splitArrayPool; [ThreadStatic] private static StringBuilder stringBuilder; [ThreadStatic] private static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache; [ThreadStatic] private static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache; public static T FromJson<T>(this string json) { return (T)json.FromJson(typeof(T)); } public static object FromJson(this string json, Type type) { if (propertyInfoCache == null) { propertyInfoCache = new Dictionary<Type, Dictionary<string, PropertyInfo>>(); } if (fieldInfoCache == null) { fieldInfoCache = new Dictionary<Type, Dictionary<string, FieldInfo>>(); } if (stringBuilder == null) { stringBuilder = new StringBuilder(); } if (splitArrayPool == null) { splitArrayPool = new Stack<List<string>>(); } stringBuilder.Length = 0; for (int i = 0; i < json.Length; i++) { char c = json[i]; if (c == '"') { i = AppendUntilStringEnd(appendEscapeCharacter: true, i, json); } else if (!char.IsWhiteSpace(c)) { stringBuilder.Append(c); } } return ParseValue(type, stringBuilder.ToString()); } private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json) { stringBuilder.Append(json[startIdx]); for (int i = startIdx + 1; i < json.Length; i++) { if (json[i] == '\\') { if (appendEscapeCharacter) { stringBuilder.Append(json[i]); } stringBuilder.Append(json[i + 1]); i++; } else { if (json[i] == '"') { stringBuilder.Append(json[i]); return i; } stringBuilder.Append(json[i]); } } return json.Length - 1; } private static List<string> Split(string json) { List<string> list = ((splitArrayPool.Count > 0) ? splitArrayPool.Pop() : new List<string>()); list.Clear(); if (json.Length == 2) { return list; } int num = 0; stringBuilder.Length = 0; for (int i = 1; i < json.Length - 1; i++) { switch (json[i]) { case '[': case '{': num++; break; case ']': case '}': num--; break; case '"': i = AppendUntilStringEnd(appendEscapeCharacter: true, i, json); continue; case ',': case ':': if (num == 0) { list.Add(stringBuilder.ToString()); stringBuilder.Length = 0; continue; } break; } stringBuilder.Append(json[i]); } list.Add(stringBuilder.ToString()); return list; } internal static object ParseValue(Type type, string json) { if (type == typeof(string)) { if (json.Length <= 2) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(json.Length); for (int i = 1; i < json.Length - 1; i++) { if (json[i] == '\\' && i + 1 < json.Length - 1) { int num = "\"\\nrtbf/".IndexOf(json[i + 1]); if (num >= 0) { stringBuilder.Append("\"\\\n\r\t\b\f/"[num]); i++; continue; } if (json[i + 1] == 'u' && i + 5 < json.Length - 1) { uint result = 0u; if (uint.TryParse(json.Substring(i + 2, 4), NumberStyles.AllowHexSpecifier, null, out result)) { stringBuilder.Append((char)result); i += 5; continue; } } } stringBuilder.Append(json[i]); } return stringBuilder.ToString(); } if (type.IsPrimitive) { return Convert.ChangeType(json, type, CultureInfo.InvariantCulture); } if (type == typeof(decimal)) { decimal.TryParse(json, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2); return result2; } if (json == "null") { return null; } if (type.IsEnum) { if (json[0] == '"') { json = json.Substring(1, json.Length - 2); try { return Enum.Parse(type, json, ignoreCase: false); } catch { return 0; } } return Convert.ChangeType(json, Enum.GetUnderlyingType(type)); } if (type.IsArray) { Type elementType = type.GetElementType(); if (json[0] != '[' || json[json.Length - 1] != ']') { return null; } List<string> list = Split(json); Array array = Array.CreateInstance(elementType, list.Count); for (int j = 0; j < list.Count; j++) { array.SetValue(ParseValue(elementType, list[j]), j); } splitArrayPool.Push(list); return array; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { Type type2 = type.GetGenericArguments()[0]; if (json[0] != '[' || json[json.Length - 1] != ']') { return null; } List<string> list2 = Split(json); IList list3 = (IList)type.GetConstructor(new Type[1] { typeof(int) }).Invoke(new object[1] { list2.Count }); for (int k = 0; k < list2.Count; k++) { list3.Add(ParseValue(type2, list2[k])); } splitArrayPool.Push(list2); return list3; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { Type[] genericArguments = type.GetGenericArguments(); Type type3 = genericArguments[0]; Type type4 = genericArguments[1]; if (type3 != typeof(string)) { return null; } if (json[0] != '{' || json[json.Length - 1] != '}') { return null; } List<string> list4 = Split(json); if (list4.Count % 2 != 0) { return null; } IDictionary dictionary = (IDictionary)type.GetConstructor(new Type[1] { typeof(int) }).Invoke(new object[1] { list4.Count / 2 }); for (int l = 0; l < list4.Count; l += 2) { if (list4[l].Length > 2) { string key = list4[l].Substring(1, list4[l].Length - 2); object value = ParseValue(type4, list4[l + 1]); dictionary.Add(key, value); } } return dictionary; } if (type == typeof(object)) { return ParseAnonymousValue(json); } if (json[0] == '{' && json[json.Length - 1] == '}') { return ParseObject(type, json); } return null; } private static object ParseAnonymousValue(string json) { if (json.Length == 0) { return null; } if (json[0] == '{' && json[json.Length - 1] == '}') { List<string> list = Split(json); if (list.Count % 2 != 0) { return null; } Dictionary<string, object> dictionary = new Dictionary<string, object>(list.Count / 2); for (int i = 0; i < list.Count; i += 2) { dictionary.Add(list[i].Substring(1, list[i].Length - 2), ParseAnonymousValue(list[i + 1])); } return dictionary; } if (json[0] == '[' && json[json.Length - 1] == ']') { List<string> list2 = Split(json); List<object> list3 = new List<object>(list2.Count); for (int j = 0; j < list2.Count; j++) { list3.Add(ParseAnonymousValue(list2[j])); } return list3; } if (json[0] == '"' && json[json.Length - 1] == '"') { return json.Substring(1, json.Length - 2).Replace("\\", string.Empty); } if (char.IsDigit(json[0]) || json[0] == '-') { if (json.Contains(".")) { double.TryParse(json, NumberStyles.Float, CultureInfo.InvariantCulture, out var result); return result; } int.TryParse(json, out var result2); return result2; } if (json == "true") { return true; } if (json == "false") { return false; } return null; } private static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo { Dictionary<string, T> dictionary = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase); foreach (T val in members) { if (val.IsDefined(typeof(IgnoreDataMemberAttribute), inherit: true)) { continue; } string name = val.Name; if (val.IsDefined(typeof(DataMemberAttribute), inherit: true)) { DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(val, typeof(DataMemberAttribute), inherit: true); if (!string.IsNullOrEmpty(dataMemberAttribute.Name)) { name = dataMemberAttribute.Name; } } dictionary.Add(name, val); } return dictionary; } private static object ParseObject(Type type, string json) { object uninitializedObject = FormatterServices.GetUninitializedObject(type); List<string> list = Split(json); if (list.Count % 2 != 0) { return uninitializedObject; } if (!fieldInfoCache.TryGetValue(type, out var value)) { value = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); fieldInfoCache.Add(type, value); } if (!propertyInfoCache.TryGetValue(type, out var value2)) { value2 = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy)); propertyInfoCache.Add(type, value2); } for (int i = 0; i < list.Count; i += 2) { if (list[i].Length > 2) { string key = list[i].Substring(1, list[i].Length - 2); string json2 = list[i + 1]; PropertyInfo value4; if (value.TryGetValue(key, out var value3)) { DiscoverObjectTypeAttribute customAttribute = value3.GetCustomAttribute<DiscoverObjectTypeAttribute>(); value3.SetValue(uninitializedObject, ParseValue(customAttribute?.GetObjectType(uninitializedObject) ?? value3.FieldType, json2)); } else if (value2.TryGetValue(key, out value4)) { DiscoverObjectTypeAttribute customAttribute2 = value4.GetCustomAttribute<DiscoverObjectTypeAttribute>(); value4.SetValue(uninitializedObject, ParseValue(customAttribute2?.GetObjectType(uninitializedObject) ?? value4.PropertyType, json2), null); } } } return uninitializedObject; } } public static class JSONWriter { public static string ToJson(this object item) { StringBuilder stringBuilder = new StringBuilder(); AppendValue(stringBuilder, item); return stringBuilder.ToString(); } private static void AppendValue(StringBuilder stringBuilder, object item) { if (item == null) { stringBuilder.Append("null"); return; } Type type = item.GetType(); if (type == typeof(string)) { stringBuilder.Append('"'); string text = (string)item; for (int i = 0; i < text.Length; i++) { if (text[i] < ' ' || text[i] == '"' || text[i] == '\\') { stringBuilder.Append('\\'); int num = "\"\\\n\r\t\b\f".IndexOf(text[i]); if (num >= 0) { stringBuilder.Append("\"\\nrtbf"[num]); } else { stringBuilder.AppendFormat("u{0:X4}", (uint)text[i]); } } else { stringBuilder.Append(text[i]); } } stringBuilder.Append('"'); return; } if (type == typeof(byte) || type == typeof(int) || type == typeof(long) || type == typeof(uint) || type == typeof(ulong)) { stringBuilder.Append(item.ToString()); return; } if (type == typeof(float)) { stringBuilder.Append(((float)item).ToString(CultureInfo.InvariantCulture)); return; } if (type == typeof(double)) { stringBuilder.Append(((double)item).ToString(CultureInfo.InvariantCulture)); return; } if (type == typeof(bool)) { stringBuilder.Append(((bool)item) ? "true" : "false"); return; } if (type.IsEnum) { stringBuilder.Append('"'); stringBuilder.Append(item.ToString()); stringBuilder.Append('"'); return; } if (item is IList) { stringBuilder.Append('['); bool flag = true; IList list = item as IList; for (int j = 0; j < list.Count; j++) { if (flag) { flag = false; } else { stringBuilder.Append(','); } AppendValue(stringBuilder, list[j]); } stringBuilder.Append(']'); return; } if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >)) { if (type.GetGenericArguments()[0] != typeof(string)) { stringBuilder.Append("{}"); return; } stringBuilder.Append('{'); IDictionary dictionary = item as IDictionary; bool flag2 = true; foreach (object key in dictionary.Keys) { if (flag2) { flag2 = false; } else { stringBuilder.Append(','); } stringBuilder.Append('"'); stringBuilder.Append((string)key); stringBuilder.Append("\":"); AppendValue(stringBuilder, dictionary[key]); } stringBuilder.Append('}'); return; } stringBuilder.Append('{'); bool flag3 = true; FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy); for (int k = 0; k < fields.Length; k++) { if (fields[k].IsDefined(typeof(IgnoreDataMemberAttribute), inherit: true)) { continue; } object value = fields[k].GetValue(item); if (value != null) { if (flag3) { flag3 = false; } else { stringBuilder.Append(','); } stringBuilder.Append('"'); stringBuilder.Append(GetMemberName(fields[k])); stringBuilder.Append("\":"); AppendValue(stringBuilder, value); } } PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy); for (int l = 0; l < properties.Length; l++) { if (!properties[l].CanRead || properties[l].IsDefined(typeof(IgnoreDataMemberAttribute), inherit: true)) { continue; } object value2 = properties[l].GetValue(item, null); if (value2 != null) { if (flag3) { flag3 = false; } else { stringBuilder.Append(','); } stringBuilder.Append('"'); stringBuilder.Append(GetMemberName(properties[l])); stringBuilder.Append("\":"); AppendValue(stringBuilder, value2); } } stringBuilder.Append('}'); } private static string GetMemberName(MemberInfo member) { if (member.IsDefined(typeof(DataMemberAttribute), inherit: true)) { DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(member, typeof(DataMemberAttribute), inherit: true); if (!string.IsNullOrEmpty(dataMemberAttribute.Name)) { return dataMemberAttribute.Name; } } return member.Name; } } } namespace ProperSave { public static class Extensions { public static int DifferenceCount<T>(this IEnumerable<T> collection, IEnumerable<T> second) { List<T> list = second.ToList(); int num = 0; foreach (T item in collection) { if (!list.Remove(item)) { num++; } } return num + list.Count; } public static int AddOrIndexOf<T>(this List<T> list, T value) { int num = list.IndexOf(value); if (num < 0) { list.Add(value); return list.Count - 1; } return num; } } public static class LanguageConsts { public static readonly string PROPER_SAVE_TITLE_CONTINUE_DESC = "PROPER_SAVE_TITLE_CONTINUE_DESC"; public static readonly string PROPER_SAVE_TITLE_CONTINUE = "PROPER_SAVE_TITLE_CONTINUE"; public static readonly string PROPER_SAVE_TITLE_LOAD = "PROPER_SAVE_TITLE_LOAD"; public static readonly string PROPER_SAVE_CHAT_SAVE = "PROPER_SAVE_CHAT_SAVE"; public static readonly string PROPER_SAVE_QUIT_DIALOG_SAVED = "PROPER_SAVE_QUIT_DIALOG_SAVED"; public static readonly string PROPER_SAVE_QUIT_DIALOG_SAVED_BEFORE = "PROPER_SAVE_QUIT_DIALOG_SAVED_BEFORE"; public static readonly string PROPER_SAVE_QUIT_DIALOG_NOT_SAVED = "PROPER_SAVE_QUIT_DIALOG_NOT_SAVED"; public static readonly string PROPER_SAVE_TOOLTIP_LOAD_TITLE = "PROPER_SAVE_TOOLTIP_LOAD_TITLE"; public static readonly string PROPER_SAVE_TOOLTIP_LOAD_DESCRIPTION_BODY = "PROPER_SAVE_TOOLTIP_LOAD_DESCRIPTION_BODY"; public static readonly string PROPER_SAVE_TOOLTIP_LOAD_DESCRIPTION_CHARACTER = "PROPER_SAVE_TOOLTIP_LOAD_DESCRIPTION_CHARACTER"; public static readonly string PROPER_SAVE_TOOLTIP_LOAD_CONTENT_MISMATCH = "PROPER_SAVE_TOOLTIP_LOAD_CONTENT_MISMATCH"; public static readonly string PROPER_SAVE_CHAT_SAVE_FAILED = "PROPER_SAVE_CHAT_SAVE_FAILED"; } public static class Loading { [CompilerGenerated] private sealed class <LoadForceCoroutine>d__22 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadForceCoroutine>d__22(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; Console.instance.SubmitCmd((NetworkUser)null, "host 0", false); <>2__current = (object)new WaitUntil((Func<bool>)(() => (Object)(object)PreGameController.instance != (Object)null)); <>1__state = 1; return true; case 1: <>1__state = -1; PreGameController.instance.StartRun(); return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } [CompilerGenerated] private sealed class <LoadLobby>d__20 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadLobby>d__20(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) if (<>1__state != 0) { return false; } <>1__state = -1; if ((Object)(object)PreGameController.instance == (Object)null) { ProperSavePlugin.InstanceLogger.LogInfo((object)"PreGameController instance not found"); return false; } NetworkManagerSystem singleton = NetworkManagerSystem.singleton; if (singleton != null && singleton.desiredHost.hostingParameters.listen && !PlatformSystems.lobbyManager.ownsLobby) { ProperSavePlugin.InstanceLogger.LogInfo((object)"You must be a lobby leader to load the game"); return false; } SaveFileMetadata currentLobbySaveMetadata = SaveFileMetadata.GetCurrentLobbySaveMetadata(); if (currentLobbySaveMetadata == null) { ProperSavePlugin.InstanceLogger.LogInfo((object)"Save file for current users is not found"); return false; } UPath? filePath = currentLobbySaveMetadata.FilePath; if (!filePath.HasValue) { ProperSavePlugin.InstanceLogger.LogInfo((object)"Metadata doesn't contain file name for the save file"); return false; } if (!ProperSavePlugin.SavesFileSystem.FileExists(filePath.Value)) { ProperSavePlugin.InstanceLogger.LogInfo((object)$"File \"{filePath}\" is not found"); return false; } currentLobbySaveMetadata.ReadBody(); ProperSavePlugin.CurrentSave = currentLobbySaveMetadata; IsLoading = true; if (currentLobbySaveMetadata.Header.ContentHash != ProperSavePlugin.ContentHash) { ProperSavePlugin.InstanceLogger.LogWarning((object)"Loading run but content mismatch detected which may result in errors"); } PreGameController.instance.StartRun(); return false; } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private static bool isLoading; public static bool IsLoading { get { return isLoading; } internal set { if (isLoading != value) { isLoading = value; if (isLoading) { Loading.OnLoadingStarted?.Invoke(CurrentSave); } else { Loading.OnLoadingEnded?.Invoke(CurrentSave); } } } } public static bool FirstRunStage { get; internal set; } public static SaveFile CurrentSave => ProperSavePlugin.CurrentSave?.Body; public static event Action<SaveFile> OnLoadingStarted; public static event Action<SaveFile> OnLoadingEnded; internal static void RegisterHooks() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown Run.Start += new Manipulator(RunStart); TeamManager.Start += new hook_Start(TeamManagerStart); } internal static void UnregisterHooks() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown Run.Start -= new Manipulator(RunStart); TeamManager.Start -= new hook_Start(TeamManagerStart); } private static void TeamManagerStart(orig_Start orig, TeamManager self) { orig.Invoke(self); if (IsLoading) { CurrentSave.LoadTeam(); IsLoading = false; } } private static void RunStart(ILContext il) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) ILCursor val = new ILCursor(il); val.EmitDelegate<Func<bool>>((Func<bool>)delegate { FirstRunStage = true; if (IsLoading) { ProperSavePlugin.InstanceLogger.LogInfo((object)("Loading save file " + ProperSavePlugin.CurrentSave.FileName)); CurrentSave.LoadRun(); CurrentSave.LoadArtifacts(); CurrentSave.LoadPlayers(); } else { ProperSavePlugin.CurrentSave = null; } return IsLoading; }); val.Emit(OpCodes.Brfalse, val.Next); val.Emit(OpCodes.Ret); } [IteratorStateMachine(typeof(<LoadLobby>d__20))] internal static IEnumerator LoadLobby() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadLobby>d__20(0); } [ConCommand(/*Could not decode attribute arguments.*/)] internal static void LoadForce(ConCommandArgs args) { string text = ((ConCommandArgs)(ref args)).TryGetArgString(0); if (string.IsNullOrWhiteSpace(text) || !File.Exists(text)) { Debug.LogError((object)"Incorrect path"); return; } SaveFileMetadata saveFileMetadata = new SaveFileMetadata(); try { saveFileMetadata.ReadForce(text); ProperSavePlugin.CurrentSave = saveFileMetadata; IsLoading = true; } catch (Exception ex) { Debug.LogWarning((object)("Failed to load save file at path \"" + text + "\"")); ProperSavePlugin.InstanceLogger.LogError((object)ex); ResetLoading(); } if (saveFileMetadata.Header.ContentHash != ProperSavePlugin.ContentHash) { ProperSavePlugin.InstanceLogger.LogWarning((object)"Loading run but content mismatch detected which may result in errors"); } if (Object.op_Implicit((Object)(object)PreGameController.instance)) { if (NetworkUser.readOnlyInstancesList.Count > 0) { Debug.LogWarning((object)"Force loading only allowed for 1 player in lobby"); ResetLoading(); } else { PreGameController.instance.StartRun(); } } else { ((MonoBehaviour)ProperSavePlugin.Instance).StartCoroutine(LoadForceCoroutine()); } static void ResetLoading() { ProperSavePlugin.CurrentSave = null; IsLoading = false; } } [IteratorStateMachine(typeof(<LoadForceCoroutine>d__22))] private static IEnumerator LoadForceCoroutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadForceCoroutine>d__22(0); } } internal static class LobbyUI { private static GameObject lobbyButton; private static GameObject lobbySubmenuLegend; private static GameObject lobbyGlyphAndDescription; private static TooltipProvider tooltipProvider; private static GamepadTooltipProvider gamepadTooltipProvider; private static SaveFileMetadata lastFileMetadata; private static TooltipContent lastTooltipContent; public static void RegisterHooks() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown CharacterSelectController.Awake += new hook_Awake(CharacterSelectControllerAwake); NetworkUser.onPostNetworkUserStart += new NetworkUserGenericDelegate(NetworkUserOnPostNetworkUserStart); NetworkUser.onNetworkUserLost += new NetworkUserGenericDelegate(NetworkUserOnNetworkUserLost); } public static void UnregisterHooks() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown CharacterSelectController.Awake -= new hook_Awake(CharacterSelectControllerAwake); NetworkUser.onPostNetworkUserStart -= new NetworkUserGenericDelegate(NetworkUserOnPostNetworkUserStart); NetworkUser.onNetworkUserLost -= new NetworkUserGenericDelegate(NetworkUserOnNetworkUserLost); } private static void NetworkUserOnNetworkUserLost(NetworkUser networkUser) { UpdateLobbyControls(networkUser); } private static void NetworkUserOnPostNetworkUserStart(NetworkUser networkUser) { UpdateLobbyControls(); } private static void CharacterSelectControllerAwake(orig_Awake orig, CharacterSelectController self) { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Expected O, but got Unknown //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Invalid comparison between Unknown and I4 //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01fe: Expected O, but got Unknown //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Expected O, but got Unknown //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_032e: Expected O, but got Unknown //IL_033b: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Expected O, but got Unknown try { GameObject gameObject = ((Component)((Component)self).transform.GetChild(2).GetChild(4).GetChild(0)).gameObject; lobbyButton = Object.Instantiate<GameObject>(gameObject, gameObject.transform.parent); InputSourceFilter[] components = ((Component)self).GetComponents<InputSourceFilter>(); foreach (InputSourceFilter val in components) { if ((int)val.requiredInputSource == 0) { Array.Resize(ref val.objectsToFilter, val.objectsToFilter.Length + 1); val.objectsToFilter[val.objectsToFilter.Length - 1] = lobbyButton; break; } } ((Object)lobbyButton).name = "[ProperSave] Load"; tooltipProvider = lobbyButton.AddComponent<TooltipProvider>(); RectTransform component = lobbyButton.GetComponent<RectTransform>(); component.anchorMin = new Vector2(1f, 1.5f); component.anchorMax = new Vector2(1f, 1.5f); HGButton component2 = lobbyButton.GetComponent<HGButton>(); component2.hoverToken = LanguageConsts.PROPER_SAVE_TITLE_CONTINUE_DESC; lobbyButton.GetComponent<LanguageTextMeshController>().token = LanguageConsts.PROPER_SAVE_TITLE_LOAD; ((Button)component2).onClick = new ButtonClickedEvent(); ((UnityEvent)((Button)component2).onClick).AddListener(new UnityAction(LoadOnInputEvent)); GameObject gameObject2 = ((Component)((Component)self).transform.GetChild(2).GetChild(4).GetChild(1)).gameObject; lobbySubmenuLegend = Object.Instantiate<GameObject>(gameObject2, gameObject2.transform.parent); components = ((Component)self).GetComponents<InputSourceFilter>(); foreach (InputSourceFilter val2 in components) { if ((int)val2.requiredInputSource == 1) { Array.Resize(ref val2.objectsToFilter, val2.objectsToFilter.Length + 1); val2.objectsToFilter[val2.objectsToFilter.Length - 1] = lobbySubmenuLegend; break; } } ((Object)lobbySubmenuLegend).name = "[ProperSave] SubmenuLegend"; UIJuice component3 = lobbySubmenuLegend.GetComponent<UIJuice>(); OnEnableEvent component4 = lobbySubmenuLegend.GetComponent<OnEnableEvent>(); ((UnityEventBase)component4.action).RemoveAllListeners(); component4.action.AddListener(new UnityAction(component3.TransitionPanFromTop)); component4.action.AddListener(new UnityAction(component3.TransitionAlphaFadeIn)); RectTransform component5 = lobbySubmenuLegend.GetComponent<RectTransform>(); component5.anchorMin = new Vector2(1f, 1f); component5.anchorMax = new Vector2(1f, 2f); lobbyGlyphAndDescription = ((Component)lobbySubmenuLegend.transform.GetChild(0)).gameObject; lobbyGlyphAndDescription.SetActive(true); InputBindingDisplayController component6 = ((Component)lobbyGlyphAndDescription.transform.GetChild(0)).GetComponent<InputBindingDisplayController>(); component6.actionName = "UISubmitTertiary"; Transform child = lobbyGlyphAndDescription.transform.GetChild(1); LanguageTextMeshController component7 = ((Component)lobbyGlyphAndDescription.transform.GetChild(2)).GetComponent<LanguageTextMeshController>(); Object.Destroy((Object)(object)((Component)child).gameObject); component7.token = LanguageConsts.PROPER_SAVE_TITLE_LOAD; for (int j = 1; j < lobbySubmenuLegend.transform.childCount; j++) { Object.Destroy((Object)(object)((Component)lobbySubmenuLegend.transform.GetChild(j)).gameObject); } HoldGamepadInputEvent holdGamepadInputEvent = ((Component)self).gameObject.AddComponent<HoldGamepadInputEvent>(); ((HGGamepadInputEvent)holdGamepadInputEvent).actionName = "UISubmitTertiary"; ((HGGamepadInputEvent)holdGamepadInputEvent).enabledObjectsIfActive = Array.Empty<GameObject>(); ((HGGamepadInputEvent)holdGamepadInputEvent).actionEvent = new UnityEvent(); ((HGGamepadInputEvent)holdGamepadInputEvent).actionEvent.AddListener(new UnityAction(LoadOnInputEvent)); gamepadTooltipProvider = ((Component)component6).gameObject.AddComponent<GamepadTooltipProvider>(); gamepadTooltipProvider.inputEvent = holdGamepadInputEvent; UpdateLobbyControls(); } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogWarning((object)"Failed while adding lobby buttons"); ProperSavePlugin.InstanceLogger.LogError((object)ex); } orig.Invoke(self); } private static void LoadOnInputEvent() { if ((Object)(object)Run.instance != (Object)null) { ProperSavePlugin.InstanceLogger.LogInfo((object)"Can't load while run is active"); } else if (Loading.IsLoading) { ProperSavePlugin.InstanceLogger.LogInfo((object)"Already loading"); } else { ((MonoBehaviour)ProperSavePlugin.Instance).StartCoroutine(Loading.LoadLobby()); } } private static void UpdateLobbyControls(NetworkUser exceptUser = null) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) SaveFileMetadata currentLobbySaveMetadata = SaveFileMetadata.GetCurrentLobbySaveMetadata(exceptUser); bool flag = PlatformSystems.lobbyManager.isInLobby == PlatformSystems.lobbyManager.ownsLobby && currentLobbySaveMetadata != null && currentLobbySaveMetadata.FilePath.HasValue && ProperSavePlugin.SavesFileSystem.FileExists(currentLobbySaveMetadata.FilePath.Value); if (currentLobbySaveMetadata != lastFileMetadata) { lastFileMetadata = currentLobbySaveMetadata; try { if (currentLobbySaveMetadata != null) { TooltipContent val = default(TooltipContent); val.titleToken = LanguageConsts.PROPER_SAVE_TOOLTIP_LOAD_TITLE; val.overrideBodyText = GetSaveDescription(currentLobbySaveMetadata); val.titleColor = Color.black; val.disableBodyRichText = false; lastTooltipContent = val; } else { lastTooltipContent = default(TooltipContent); } } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogWarning((object)"Failed to get information about save file"); ProperSavePlugin.InstanceLogger.LogError((object)ex); flag = false; } } try { if (Object.op_Implicit((Object)(object)lobbyButton)) { HGButton component = lobbyButton.GetComponent<HGButton>(); if (Object.op_Implicit((Object)(object)component)) { ((Selectable)component).interactable = flag; } } if (Object.op_Implicit((Object)(object)tooltipProvider)) { tooltipProvider.SetContent(lastTooltipContent); } } catch { } try { if (Object.op_Implicit((Object)(object)lobbyGlyphAndDescription)) { Color color = (Color)(flag ? Color.white : new Color(0.3f, 0.3f, 0.3f)); ((Graphic)((Component)lobbyGlyphAndDescription.transform.GetChild(0)).GetComponent<HGTextMeshProUGUI>()).color = color; ((Graphic)((Component)lobbyGlyphAndDescription.transform.GetChild(1)).GetComponent<HGTextMeshProUGUI>()).color = color; } if (Object.op_Implicit((Object)(object)gamepadTooltipProvider)) { ((TooltipProvider)gamepadTooltipProvider).SetContent(lastTooltipContent); } } catch { } } private static string GetSaveDescription(SaveFileMetadata saveMetadata) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) SaveFileHeader header = saveMetadata.Header; StringBuilder stringBuilder = new StringBuilder(); HeaderUserData[] users = header.Users; foreach (HeaderUserData userData in users) { NetworkUser val = ((IEnumerable<NetworkUser>)NetworkUser.readOnlyInstancesList).FirstOrDefault((Func<NetworkUser, bool>)delegate(NetworkUser user) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) NetworkUserId val3 = userData.UserId.Load(); return ((NetworkUserId)(ref val3)).Equals(user.id); }); SurvivorDef val2 = SurvivorCatalog.FindSurvivorDefFromBody(BodyCatalog.GetBodyPrefab(userData.Body)); stringBuilder.Append(Language.GetStringFormatted(LanguageConsts.PROPER_SAVE_TOOLTIP_LOAD_DESCRIPTION_CHARACTER, new object[2] { val?.userName, ((Object)(object)val2 != (Object)null) ? Language.GetString(val2.displayNameToken) : "" })); } SceneDef sceneDefFromSceneName = SceneCatalog.GetSceneDefFromSceneName(header.SceneName); DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(header.Difficulty); int time = header.Time; int stageClearCount = header.StageClearCount; return Language.GetStringFormatted(LanguageConsts.PROPER_SAVE_TOOLTIP_LOAD_DESCRIPTION_BODY, new object[6] { stringBuilder.ToString(), Object.op_Implicit((Object)(object)sceneDefFromSceneName) ? Language.GetString(sceneDefFromSceneName.nameToken) : "", (stageClearCount + 1).ToString(), $"{time / 60:00}:{time % 60:00}", (difficultyDef != null) ? Language.GetString(difficultyDef.nameToken) : "", (header.ContentHash != ProperSavePlugin.ContentHash) ? Language.GetString(LanguageConsts.PROPER_SAVE_TOOLTIP_LOAD_CONTENT_MISMATCH) : "" }); } } internal class LostNetworkUser : MonoBehaviour { private static readonly Dictionary<CharacterMaster, LostNetworkUser> lostUsers = new Dictionary<CharacterMaster, LostNetworkUser>(); private CharacterMaster master; public uint lunarCoins; public NetworkUserId userID; public BodyIndex bodyIndexPreference; private void Awake() { master = ((Component)this).GetComponent<CharacterMaster>(); lostUsers[master] = this; } private void OnDestroy() { lostUsers.Remove(master); } public static bool TryGetUser(CharacterMaster master, out LostNetworkUser lostUser) { if (!Object.op_Implicit((Object)(object)master) || !lostUsers.TryGetValue(master, out lostUser)) { lostUser = null; return false; } return true; } public static void Subscribe() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown NetworkUser.onNetworkUserLost += new NetworkUserGenericDelegate(OnNetworkUserLost); } public static void Unsubscribe() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown NetworkUser.onNetworkUserLost -= new NetworkUserGenericDelegate(OnNetworkUserLost); } private static void OnNetworkUserLost(NetworkUser networkUser) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) if (Object.op_Implicit((Object)(object)networkUser.master)) { LostNetworkUser lostNetworkUser = ((Component)networkUser.master).gameObject.AddComponent<LostNetworkUser>(); lostNetworkUser.lunarCoins = networkUser.lunarCoins; lostNetworkUser.userID = networkUser.id; lostNetworkUser.bodyIndexPreference = networkUser.bodyIndexPreference; } } } internal static class ModCompat { [CompilerGenerated] private sealed class <LoadShareSuiteMoneyInternal>d__23 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public uint money; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <LoadShareSuiteMoneyInternal>d__23(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitUntil((Func<bool>)(() => !MoneySharingHooks.MapTransitionActive)); <>1__state = 1; return true; case 1: <>1__state = -1; MoneySharingHooks.SharedMoneyValue = (int)money; return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public const string BiggerBazaarGUID = "com.MagnusMagnuson.BiggerBazaar"; public const string ShareSuiteGUID = "com.funkfrog_sipondo.sharesuite"; public static bool IsBBLoaded { get; private set; } public static bool IsSSLoaded { get; private set; } public static bool IsR2APIDifficultyLoaded { get; private set; } private static Dictionary<MethodInfo, Action<ILContext>> RegisteredILHooks { get; } = new Dictionary<MethodInfo, Action<ILContext>>(); public static void GatherLoadedPlugins() { IsBBLoaded = Chainloader.PluginInfos.ContainsKey("com.MagnusMagnuson.BiggerBazaar"); IsSSLoaded = Chainloader.PluginInfos.ContainsKey("com.funkfrog_sipondo.sharesuite"); IsR2APIDifficultyLoaded = Chainloader.PluginInfos.ContainsKey("com.bepis.r2api.difficulty"); } public static void RegisterHooks() { if (IsBBLoaded) { try { RegisterBBHooks(); } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogError((object)"Failed to add support for BiggerBazaar"); ProperSavePlugin.InstanceLogger.LogError((object)ex); } } } public static void UnregisterHooks() { foreach (KeyValuePair<MethodInfo, Action<ILContext>> registeredILHook in RegisteredILHooks) { HookEndpointManager.Unmodify((MethodBase)registeredILHook.Key, (Delegate)registeredILHook.Value); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void RegisterBBHooks() { MethodInfo method = typeof(BiggerBazaar).Assembly.GetType("BiggerBazaar.Bazaar").GetMethod("StartBazaar", BindingFlags.Instance | BindingFlags.Public); Action<ILContext> action = BBHook; HookEndpointManager.Modify((MethodBase)method, (Delegate)action); RegisteredILHooks.Add(method, action); } private static void BBHook(ILContext il) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) Type type = typeof(BiggerBazaar).Assembly.GetType("BiggerBazaar.Bazaar"); ILCursor val = new ILCursor(il); int index = val.Index; val.Index = index + 1; Instruction next = val.Next; val.Emit(OpCodes.Call, (MethodBase)typeof(Loading).GetProperty("FirstRunStage").GetGetMethod()); val.Emit(OpCodes.Brfalse, next); val.Emit(OpCodes.Ldarg_0); val.Emit(OpCodes.Call, (MethodBase)type.GetMethod("ResetBazaarPlayers")); val.Emit(OpCodes.Ldarg_0); val.Emit(OpCodes.Call, (MethodBase)type.GetMethod("CalcDifficultyCoefficient")); } public static void LoadShareSuiteMoney(uint money) { try { if (IsSSLoaded) { ((MonoBehaviour)ProperSavePlugin.Instance).StartCoroutine(LoadShareSuiteMoneyInternal(money)); } } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogError((object)ex); } } [MethodImpl(MethodImplOptions.NoInlining)] [IteratorStateMachine(typeof(<LoadShareSuiteMoneyInternal>d__23))] private static IEnumerator LoadShareSuiteMoneyInternal(uint money) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadShareSuiteMoneyInternal>d__23(0) { money = money }; } public static void ShareSuiteMapTransition() { try { if (IsSSLoaded) { ShareSuiteMapTransitionInternal(); } } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogError((object)ex); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void ShareSuiteMapTransitionInternal() { MoneySharingHooks.MapTransitionActive = true; } [MethodImpl(MethodImplOptions.NoInlining)] internal static DifficultyIndex FindR2APIDifficultyIndex(string nameToken) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) foreach (var (result, val3) in DifficultyAPI.difficultyDefinitions) { if (val3.nameToken == nameToken) { return result; } } return (DifficultyIndex)(-1); } } [BepInPlugin("com.KingEnderBrine.ProperSave", "Proper Save", "3.0.3")] public class ProperSavePlugin : BaseUnityPlugin { public const string GUID = "com.KingEnderBrine.ProperSave"; public const string Name = "Proper Save"; public const string Version = "3.0.3"; private static readonly char[] invalidSubDirectoryCharacters = new char[3] { '\\', '/', '.' }; internal static ProperSavePlugin Instance { get; private set; } internal static ManualLogSource InstanceLogger { get { ProperSavePlugin instance = Instance; if (instance == null) { return null; } return ((BaseUnityPlugin)instance).Logger; } } internal static FileSystem SavesFileSystem { get; private set; } internal static UPath SavesPath { get; private set; } = UPath.op_Implicit("/ProperSave") / UPath.op_Implicit("Saves"); private static string SavesDirectory { get; set; } internal static SaveFileMetadata CurrentSave { get; set; } internal static string ContentHash { get; private set; } internal static ConfigEntry<bool> UseCloudStorage { get; private set; } internal static ConfigEntry<string> CloudStorageSubDirectory { get; private set; } internal static ConfigEntry<string> UserSavesDirectory { get; private set; } internal static ConfigEntry<bool> Resilient { get; private set; } private void Start() { Instance = this; UseCloudStorage = ((BaseUnityPlugin)this).Config.Bind<bool>("Main", "UseCloudStorage", false, "Store files in Steam/EpicGames cloud. Enabling this feature would not preserve current saves and disabling it wouldn't clear the cloud."); CloudStorageSubDirectory = ((BaseUnityPlugin)this).Config.Bind<string>("Main", "CloudStorageSubDirectory", "", "Sub directory name for cloud storage. Changing it allows to use different save files for different mod profiles."); UserSavesDirectory = ((BaseUnityPlugin)this).Config.Bind<string>("Main", "SavesDirectory", "", "Directory where save files will be stored. \"ProperSave\" directory will be created in the directory you have specified. If the directory doesn't exist the default one will be used."); Resilient = ((BaseUnityPlugin)this).Config.Bind<bool>("Main", "Resilient", true, "Save file type. True - entries from catalogs will be saved by name instead of index, which should have less issue on mod list change, but has bigger save file size. False - the old way, entries from catalogs are saved by index, which works for non-changing mod list, save file size is much lower."); RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, (Action)delegate { InitSaveFileSystem(); ProperSave.Old.SaveFileMetadata.MigrateAll(); SaveFileMetadata.PopulateSavesMetadata(); }); ModCompat.GatherLoadedPlugins(); ModCompat.RegisterHooks(); Saving.RegisterHooks(); Loading.RegisterHooks(); LobbyUI.RegisterHooks(); LostNetworkUser.Subscribe(); Language.collectLanguageRootFolders += CollectLanguageRootFolders; ContentManager.onContentPacksAssigned += ContentManagerOnContentPacksAssigned; } private void InitSaveFileSystem() { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Expected O, but got Unknown if (UseCloudStorage.Value) { SavesFileSystem = RoR2Application.cloudStorage; if (!string.IsNullOrWhiteSpace(CloudStorageSubDirectory.Value)) { if (CloudStorageSubDirectory.Value.IndexOfAny(invalidSubDirectoryCharacters) != -1) { ((BaseUnityPlugin)this).Logger.LogError((object)"Config entry \"CloudStorageSubDirectory\" contains invalid characters. Falling back to default location."); } else { SavesPath /= UPath.op_Implicit(CloudStorageSubDirectory.Value); } } return; } if (!string.IsNullOrWhiteSpace(UserSavesDirectory.Value)) { if (!Directory.Exists(UserSavesDirectory.Value)) { ((BaseUnityPlugin)this).Logger.LogError((object)"SavesDirectory from the config doesn't exists, using Application.persistentDataPath"); SavesDirectory = Application.persistentDataPath; } else { SavesDirectory = UserSavesDirectory.Value; } } else { SavesDirectory = Application.persistentDataPath; } if (string.IsNullOrWhiteSpace(SavesDirectory)) { ((BaseUnityPlugin)this).Logger.LogError((object)"Application.persistentDataPath is empty. Use SavesDirectory config option to specify a folder."); } PhysicalFileSystem val = new PhysicalFileSystem(); SavesFileSystem = (FileSystem)new SubFileSystem((IFileSystem)(object)val, ((FileSystem)val).ConvertPathFromInternal(SavesDirectory), true); } private void Destroy() { Instance = null; ModCompat.UnregisterHooks(); Saving.UnregisterHooks(); Loading.UnregisterHooks(); LobbyUI.UnregisterHooks(); LostNetworkUser.Unsubscribe(); Language.collectLanguageRootFolders -= CollectLanguageRootFolders; ContentManager.onContentPacksAssigned -= ContentManagerOnContentPacksAssigned; } public void CollectLanguageRootFolders(List<string> folders) { folders.Add(Path.Combine(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location), "Language")); } private void ContentManagerOnContentPacksAssigned(ReadOnlyArray<ReadOnlyContentPack> contentPacks) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) MD5 mD = MD5.Create(); StringWriter writer = new StringWriter(); try { Enumerator<ReadOnlyContentPack> enumerator = contentPacks.GetEnumerator(); try { while (enumerator.MoveNext()) { ReadOnlyContentPack current = enumerator.Current; writer.Write(((ReadOnlyContentPack)(ref current)).identifier); writer.Write(';'); WriteCollection<ArtifactDef>(((ReadOnlyContentPack)(ref current)).artifactDefs, "artifactDefs"); WriteCollection<GameObject>(((ReadOnlyContentPack)(ref current)).bodyPrefabs, "bodyPrefabs"); WriteCollection<EquipmentDef>(((ReadOnlyContentPack)(ref current)).equipmentDefs, "equipmentDefs"); WriteCollection<DroneDef>(((ReadOnlyContentPack)(ref current)).droneDefs, "droneDefs"); WriteCollection<ExpansionDef>(((ReadOnlyContentPack)(ref current)).expansionDefs, "expansionDefs"); WriteCollection<GameObject>(((ReadOnlyContentPack)(ref current)).gameModePrefabs, "gameModePrefabs"); WriteCollection<ItemDef>(((ReadOnlyContentPack)(ref current)).itemDefs, "itemDefs"); WriteCollection<ItemTierDef>(((ReadOnlyContentPack)(ref current)).itemTierDefs, "itemTierDefs"); WriteCollection<GameObject>(((ReadOnlyContentPack)(ref current)).masterPrefabs, "masterPrefabs"); WriteCollection<SceneDef>(((ReadOnlyContentPack)(ref current)).sceneDefs, "sceneDefs"); WriteCollection<SkillDef>(((ReadOnlyContentPack)(ref current)).skillDefs, "skillDefs"); WriteCollection<SkillFamily>(((ReadOnlyContentPack)(ref current)).skillFamilies, "skillFamilies"); WriteCollection<SurvivorDef>(((ReadOnlyContentPack)(ref current)).survivorDefs, "survivorDefs"); WriteCollection<UnlockableDef>(((ReadOnlyContentPack)(ref current)).unlockableDefs, "unlockableDefs"); } } finally { ((IDisposable)enumerator).Dispose(); } ContentHash = Convert.ToBase64String(mD.ComputeHash(Encoding.UTF8.GetBytes(writer.ToString()))); } finally { if (writer != null) { ((IDisposable)writer).Dispose(); } } void WriteCollection<T>(ReadOnlyNamedAssetCollection<T> collection, string collectionName) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) writer.Write(collectionName); int num = 0; AssetEnumerator<T> enumerator2 = collection.GetEnumerator(); try { while (enumerator2.MoveNext()) { T current2 = enumerator2.Current; writer.Write(num); writer.Write('_'); writer.Write(collection.GetAssetName(current2) ?? string.Empty); writer.Write(';'); num++; } } finally { ((IDisposable)enumerator2).Dispose(); } } } } public class SaveFile { internal static readonly int currentVersion = 1; public ProperSave.SaveData.RunData RunData { get; set; } public ProperSave.SaveData.TeamData TeamData { get; set; } public ProperSave.SaveData.RunArtifactsData RunArtifactsData { get; set; } public ProperSave.SaveData.ArtifactsData ArtifactsData { get; set; } public List<ProperSave.SaveData.PlayerData> PlayersData { get; set; } = new List<ProperSave.SaveData.PlayerData>(); public Dictionary<string, ProperSave.Data.ModdedData> ModdedData { get; set; } = new Dictionary<string, ProperSave.Data.ModdedData>(); public static event Action<Dictionary<string, object>> OnGatherSaveData; internal SaveFile() { } internal void FillFromCurrentRun() { RunData = ProperSave.SaveData.RunData.Create(); TeamData = ProperSave.SaveData.TeamData.Create(); RunArtifactsData = ProperSave.SaveData.RunArtifactsData.Create(); ArtifactsData = ProperSave.SaveData.ArtifactsData.Create(); foreach (PlayerCharacterMasterController instance in PlayerCharacterMasterController.instances) { LostNetworkUser lostUser = null; if (Object.op_Implicit((Object)(object)instance.networkUser) || LostNetworkUser.TryGetUser(instance.master, out lostUser)) { PlayersData.Add(ProperSave.SaveData.PlayerData.Create(instance, lostUser)); } } Dictionary<string, object> dictionary = new Dictionary<string, object>(); Delegate[] array = SaveFile.OnGatherSaveData?.GetInvocationList(); if (array != null) { Delegate[] array2 = array; foreach (Delegate @delegate in array2) { try { ((Action<Dictionary<string, object>>)@delegate)(dictionary); } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogError((object)ex); } } } ModdedData = dictionary.ToDictionary((KeyValuePair<string, object> el) => el.Key, (KeyValuePair<string, object> el) => new ProperSave.Data.ModdedData { ObjectType = el.Value.GetType().AssemblyQualifiedName, Value = el.Value }); } internal void LoadRun() { try { LegacyResourcesAPI.Load<GameObject>("Prefabs/PositionIndicators/TeleporterChargingPositionIndicator", true); } catch { } RunData.LoadData(); } internal void LoadArtifacts() { RunArtifactsData.LoadData(); ArtifactsData.LoadData(); } internal void LoadTeam() { TeamData.LoadData(); } internal void LoadPlayers() { if (NetworkUser.readOnlyInstancesList.Count == 1) { ProperSave.SaveData.PlayerData playerData = PlayersData.FirstOrDefault(); if (playerData != null) { NetworkUser player = NetworkUser.readOnlyInstancesList.FirstOrDefault(); playerData.LoadPlayer(player); } return; } List<ProperSave.SaveData.PlayerData> list = PlayersData.ToList(); foreach (NetworkUser user in NetworkUser.readOnlyInstancesList) { ProperSave.SaveData.PlayerData playerData2 = list.FirstOrDefault(delegate(ProperSave.SaveData.PlayerData el) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) NetworkUserId val = el.userId.Load(); return ((NetworkUserId)(ref val)).Equals(user.id); }); if (playerData2 != null) { list.Remove(playerData2); playerData2.LoadPlayer(user); } } } public T GetModdedData<T>(string key) { return (T)ModdedData[key].Value; } internal static SaveFile Read(BinaryReader reader) { SaveFile saveFile = new SaveFile(); int version = reader.ReadInt32(); bool resilient = reader.ReadBoolean(); long offset = reader.ReadInt64(); long position = reader.BaseStream.Position; reader.BaseStream.Seek(offset, SeekOrigin.Begin); string[] array = new string[reader.ReadInt32()]; for (int i = 0; i < array.Length; i++) { array[i] = reader.ReadString(); } long position2 = reader.BaseStream.Position; reader.BaseStream.Seek(position, SeekOrigin.Begin); ReaderContext context = new ReaderContext { Reader = reader, Resilient = resilient, SharedStrings = array, Version = version }; saveFile.ArtifactsData = ProperSave.SaveData.ArtifactsData.Read(context); saveFile.RunData = ProperSave.SaveData.RunData.Read(context); saveFile.RunArtifactsData = ProperSave.SaveData.RunArtifactsData.Read(context); saveFile.TeamData = ProperSave.SaveData.TeamData.Read(context); int num = reader.ReadInt32(); for (int j = 0; j < num; j++) { saveFile.PlayersData.Add(ProperSave.SaveData.PlayerData.Read(context)); } saveFile.ModdedData = ReadModdedData(context); reader.BaseStream.Seek(position2, SeekOrigin.Begin); return saveFile; } private static Dictionary<string, ProperSave.Data.ModdedData> ReadModdedData(ReaderContext context) { return context.Reader.ReadString().FromJson<Dictionary<string, ProperSave.Data.ModdedData>>(); } internal void Write(BinaryWriter writer, bool resilient) { WriterContext writerContext = new WriterContext { Writer = writer, SharedStrings = new List<string>(), Resilient = resilient }; writer.Write(currentVersion); writer.Write(resilient); long position = writer.BaseStream.Position; writer.Write(0L); ArtifactsData.Write(writerContext); RunData.Write(writerContext); RunArtifactsData.Write(writerContext); TeamData.Write(writerContext); writer.Write(PlayersData.Count); for (int i = 0; i < PlayersData.Count; i++) { PlayersData[i].Write(writerContext); } WriteModdedData(writerContext); long position2 = writer.BaseStream.Position; writer.Write(writerContext.SharedStrings.Count); for (int j = 0; j < writerContext.SharedStrings.Count; j++) { writer.Write(writerContext.SharedStrings[j]); } writer.BaseStream.Seek(position, SeekOrigin.Begin); writer.Write(position2); writer.BaseStream.Seek(writer.BaseStream.Length, SeekOrigin.Begin); } private void WriteModdedData(WriterContext context) { context.Writer.Write(ModdedData.ToJson()); } } public class SaveFileHeader { internal static readonly int currentVersion = 1; public DateTime SaveDate { get; internal set; } public string UserProfileId { get; internal set; } public HeaderUserData[] Users { get; internal set; } public GameModeIndex GameMode { get; internal set; } public string SceneName { get; internal set; } public DifficultyIndex Difficulty { get; internal set; } public int Time { get; internal set; } public int StageClearCount { get; internal set; } public string ContentHash { get; internal set; } public void FillFromCurrentRun() { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) Users = (from el in PlayerCharacterMasterController.instances.Select(HeaderUserData.Create) where el != null select el).ToArray(); UserProfileId = LocalUserManager.readOnlyLocalUsersList[0].userProfile.fileName; GameMode = Run.instance.gameModeIndex; ContentHash = ProperSavePlugin.ContentHash; Difficulty = Run.instance.selectedDifficulty; Scene activeScene = SceneManager.GetActiveScene(); SceneName = ((Scene)(ref activeScene)).name; StageClearCount = Run.instance.stageClearCount; RunStopwatch runStopwatch = Run.instance.runStopwatch; Time = (runStopwatch.isPaused ? ((int)runStopwatch.offsetFromFixedTime) : ((int)(Run.instance.fixedTime + runStopwatch.offsetFromFixedTime))); } internal static SaveFileHeader Read(BinaryReader reader) { //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) SaveFileHeader saveFileHeader = new SaveFileHeader(); int version = reader.ReadInt32(); bool resilient = reader.ReadBoolean(); long offset = reader.ReadInt64(); long position = reader.BaseStream.Position; reader.BaseStream.Seek(offset, SeekOrigin.Begin); string[] array = new string[reader.ReadInt32()]; for (int i = 0; i < array.Length; i++) { array[i] = reader.ReadString(); } long position2 = reader.BaseStream.Position; reader.BaseStream.Seek(position, SeekOrigin.Begin); ReaderContext context = new ReaderContext { Reader = reader, Resilient = resilient, SharedStrings = array, Version = version }; saveFileHeader.SaveDate = new DateTime(reader.ReadInt64(), DateTimeKind.Utc); saveFileHeader.UserProfileId = reader.ReadString(); saveFileHeader.Users = new HeaderUserData[reader.ReadInt32()]; for (int j = 0; j < saveFileHeader.Users.Length; j++) { saveFileHeader.Users[j] = HeaderUserData.Read(context); } saveFileHeader.GameMode = SharedIndexHelpers.ResolveGameMode(reader.ReadInt32(), context); saveFileHeader.SceneName = reader.ReadString(); saveFileHeader.Difficulty = SharedIndexHelpers.ResolveDifficulty(reader.ReadInt32(), context); saveFileHeader.Time = reader.ReadInt32(); saveFileHeader.StageClearCount = reader.ReadInt32(); saveFileHeader.ContentHash = reader.ReadString(); reader.BaseStream.Seek(position2, SeekOrigin.Begin); return saveFileHeader; } internal void Write(BinaryWriter writer, bool resilient) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) WriterContext writerContext = new WriterContext { Writer = writer, SharedStrings = new List<string>(), Resilient = resilient }; writer.Write(currentVersion); writer.Write(resilient); long position = writer.BaseStream.Position; writer.Write(0L); writer.Write(DateTime.UtcNow.Ticks); writer.Write(UserProfileId); writer.Write(Users.Length); for (int i = 0; i < Users.Length; i++) { Users[i].Write(writerContext); } writer.Write(SharedIndexHelpers.FromGameMode(GameMode, writerContext)); writer.Write(SceneName); writer.Write(SharedIndexHelpers.FromDifficulty(Difficulty, writerContext)); writer.Write(Time); writer.Write(StageClearCount); writer.Write(ContentHash); long position2 = writer.BaseStream.Position; writer.Write(writerContext.SharedStrings.Count); for (int j = 0; j < writerContext.SharedStrings.Count; j++) { writer.Write(writerContext.SharedStrings[j]); } writer.BaseStream.Seek(position, SeekOrigin.Begin); writer.Write(position2); writer.BaseStream.Seek(writer.BaseStream.Length, SeekOrigin.Begin); } } public class SaveFileMetadata { [CompilerGenerated] private sealed class <>c__DisplayClass26_0 { public GameModeIndex gameMode; public List<NetworkUserId> users; internal bool <GetCurrentLobbySaveMetadata>b__1(SaveFileMetadata el) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (el.Header.Users.Length != users.Count || el.Header.GameMode != gameMode) { return false; } return users.DifferenceCount(el.Header.Users.Select((HeaderUserData e) => (e?.UserId?.Load()).GetValueOrDefault())) == 0; } } public string FileName { get; internal set; } public bool ForceLoad { get; internal set; } public SaveFileHeader Header { get; internal set; } public long BodyOffset { get; internal set; } public SaveFile Body { get; internal set; } public UPath? FilePath => string.IsNullOrEmpty(FileName) ? UPath.op_Implicit((string)null) : (ProperSavePlugin.SavesPath / UPath.op_Implicit(FileName + ".bin")); internal static List<SaveFileMetadata> SavesMetadata { get; } = new List<SaveFileMetadata>(); internal void FillMetadataForCurrentLobby() { //IL_006a: Unknown result type (might be due to invalid IL or missing references) ForceLoad = false; Header = new SaveFileHeader(); Body = new SaveFile(); BodyOffset = 0L; Header.FillFromCurrentRun(); Body.FillFromCurrentRun(); if (FileName == null) { do { FileName = Guid.NewGuid().ToString(); } while (ProperSavePlugin.SavesFileSystem.FileExists(FilePath.Value)); } } internal static SaveFileMetadata GetCurrentLobbySaveMetadata(NetworkUser exceptUser = null) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Invalid comparison between Unknown and I4 //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Expected O, but got I4 //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Invalid comparison between Unknown and I4 //IL_009b->IL009b: Incompatible stack types: O vs I4 //IL_0091->IL009b: Incompatible stack types: O vs I4 //IL_0085->IL009b: Incompatible stack types: I4 vs O //IL_0085->IL009b: Incompatible stack types: O vs I4 try { <>c__DisplayClass26_0 CS$<>8__locals0 = new <>c__DisplayClass26_0(); CS$<>8__locals0.users = NetworkUser.readOnlyInstancesList.Select((NetworkUser el) => el.id).ToList(); if ((Object)(object)exceptUser != (Object)null) { CS$<>8__locals0.users.Remove(exceptUser.id); } if (CS$<>8__locals0.users.Count == 0) { return null; } object obj = CS$<>8__locals0; int num; if (Object.op_Implicit((Object)(object)PreGameController.instance)) { obj = PreGameController.instance.gameModeIndex; num = (int)obj; } else if (Object.op_Implicit((Object)(object)Run.instance)) { obj = Run.instance.gameModeIndex; num = (int)obj; } else { num = -1; obj = num; num = (int)obj; } ((<>c__DisplayClass26_0)num).gameMode = (GameModeIndex)obj; if ((int)CS$<>8__locals0.gameMode == -1) { return null; } if (CS$<>8__locals0.users.Count == 1) { <>c__DisplayClass26_0 <>c__DisplayClass26_ = CS$<>8__locals0; string profile = Path.GetFileNameWithoutExtension(LocalUserManager.readOnlyLocalUsersList[0].userProfile.fileName); return SavesMetadata.FirstOrDefault((SaveFileMetadata el) => el.Header.UserProfileId == profile && el.Header.Users.Length == 1 && el.Header.GameMode == <>c__DisplayClass26_.gameMode); } return SavesMetadata.FirstOrDefault((SaveFileMetadata el) => el.Header.Users.Length == CS$<>8__locals0.users.Count && el.Header.GameMode == CS$<>8__locals0.gameMode && CS$<>8__locals0.users.DifferenceCount(el.Header.Users.Select((HeaderUserData e) => (e?.UserId?.Load()).GetValueOrDefault())) == 0); } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogWarning((object)"Couldn't get save metadata for current lobby"); ProperSavePlugin.InstanceLogger.LogError((object)ex.ToString()); return null; } } internal static void PopulateSavesMetadata() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (!ProperSavePlugin.SavesFileSystem.DirectoryExists(ProperSavePlugin.SavesPath)) { ProperSavePlugin.SavesFileSystem.CreateDirectory(ProperSavePlugin.SavesPath); return; } SavesMetadata.Clear(); List<SaveFileMetadata> list = new List<SaveFileMetadata>(); foreach (UPath item in FileSystemExtensions.EnumerateFiles((IFileSystem)(object)ProperSavePlugin.SavesFileSystem, ProperSavePlugin.SavesPath, "*.bin")) { try { SaveFileMetadata saveFileMetadata = new SaveFileMetadata(); saveFileMetadata.FileName = UPathExtensions.GetNameWithoutExtension(item); saveFileMetadata.ReadHeader(); list.Add(saveFileMetadata); } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Failed to load save file \"" + UPathExtensions.GetName(item) + "\"")); ProperSavePlugin.InstanceLogger.LogError((object)ex); } } SavesMetadata.AddRange(from el in list orderby el.Header.ContentHash == ProperSavePlugin.ContentHash descending, el.Header.SaveDate descending select el); } internal static void Replace(SaveFileMetadata metadata, SaveFileMetadata oldMetadata) { if (oldMetadata != null) { SavesMetadata.Remove(oldMetadata); } SavesMetadata.Insert(0, metadata); } internal void Write(bool resilient) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(memoryStream); Header.Write(writer, resilient); Body.Write(writer, resilient); using Stream destination = ProperSavePlugin.SavesFileSystem.OpenFile(FilePath.Value, FileMode.Create, FileAccess.Write, FileShare.None); memoryStream.Seek(0L, SeekOrigin.Begin); memoryStream.CopyTo(destination); } internal void ReadBody() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (Body != null) { return; } using Stream stream = ProperSavePlugin.SavesFileSystem.OpenFile(FilePath.Value, FileMode.Open, FileAccess.Read, FileShare.None); stream.Seek(BodyOffset, SeekOrigin.Begin); using BinaryReader reader = new BinaryReader(stream); Body = SaveFile.Read(reader); } internal void ReadHeader() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (Header != null) { return; } using Stream stream = ProperSavePlugin.SavesFileSystem.OpenFile(FilePath.Value, FileMode.Open, FileAccess.Read, FileShare.None); using BinaryReader reader = new BinaryReader(stream); Header = SaveFileHeader.Read(reader); BodyOffset = stream.Position; ForceLoad = false; } internal void ReadForce(string path) { using FileStream fileStream = File.Open(path, FileMode.Open, FileAccess.Read); using BinaryReader reader = new BinaryReader(fileStream); Header = SaveFileHeader.Read(reader); BodyOffset = fileStream.Position; Body = SaveFile.Read(reader); ForceLoad = true; } } internal static class Saving { internal static ProperSave.SaveData.RunRngData PreStageRng { get; private set; } internal static ProperSave.Data.RngData PreStageInfiniteTowerSafeWardRng { get; private set; } internal static string PreStageSceneName { get; private set; } internal static void RegisterHooks() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown Run.BeginStage += new hook_BeginStage(StageOnStageStartGlobal); Run.onServerGameOver += RunOnServerGameOver; Run.AdvanceStage += new hook_AdvanceStage(RunAdvanceStage); QuitConfirmationHelper.IssueQuitCommand_Action += new Manipulator(IssueQuitCommandIL); } internal static void UnregisterHooks() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Expected O, but got Unknown Run.BeginStage -= new hook_BeginStage(StageOnStageStartGlobal); Run.onServerGameOver -= RunOnServerGameOver; Run.AdvanceStage -= new hook_AdvanceStage(RunAdvanceStage); QuitConfirmationHelper.IssueQuitCommand_Action -= new Manipulator(IssueQuitCommandIL); } private static void RunAdvanceStage(orig_AdvanceStage orig, Run self, SceneDef sceneDef) { PreStageSceneName = SceneCatalog.GetSceneDefForCurrentScene().cachedName; PreStageRng = ProperSave.SaveData.RunRngData.Create(Run.instance); InfiniteTowerRun val = (InfiniteTowerRun)(object)((self is InfiniteTowerRun) ? self : null); if (val != null) { PreStageInfiniteTowerSafeWardRng = ProperSave.Data.RngData.Create(val.safeWardRng); } orig.Invoke(self, sceneDef); } private static void RunOnServerGameOver(Run run, GameEndingDef ending) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) try { SaveFileMetadata currentSave = ProperSavePlugin.CurrentSave; if (currentSave != null && currentSave.FilePath.HasValue) { if (ProperSavePlugin.SavesFileSystem.FileExists(currentSave.FilePath.Value)) { ProperSavePlugin.SavesFileSystem.DeleteFile(currentSave.FilePath.Value); } ProperSavePlugin.CurrentSave = null; SaveFileMetadata.SavesMetadata.Remove(currentSave); } } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogWarning((object)"Failed to delete save file"); ProperSavePlugin.InstanceLogger.LogError((object)ex); } } private static void StageOnStageStartGlobal(orig_BeginStage orig, Run self) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 try { if (!NetworkServer.active) { return; } if (Loading.FirstRunStage) { Loading.FirstRunStage = false; return; } SceneDef sceneDefForCurrentScene = SceneCatalog.GetSceneDefForCurrentScene(); if ((int)sceneDefForCurrentScene.sceneType != 0 && (int)sceneDefForCurrentScene.sceneType != 3) { SaveFileMetadata currentSave = ProperSavePlugin.CurrentSave; if (currentSave == null || !currentSave.ForceLoad) { SaveGame(); } } } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogError((object)ex); } finally { orig.Invoke(self); } } private static void SaveGame() { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown SaveFileMetadata currentLobbySaveMetadata = SaveFileMetadata.GetCurrentLobbySaveMetadata(); SaveFileMetadata saveFileMetadata = new SaveFileMetadata { FileName = currentLobbySaveMetadata?.FileName }; saveFileMetadata.FillMetadataForCurrentLobby(); try { saveFileMetadata.Write(ProperSavePlugin.Resilient.Value); ProperSavePlugin.CurrentSave = saveFileMetadata; SaveFileMetadata.Replace(saveFileMetadata, currentLobbySaveMetadata); Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage { baseToken = string.Format(Language.GetString(LanguageConsts.PROPER_SAVE_CHAT_SAVE), Language.GetString(SceneCatalog.currentSceneDef.nameToken)) }); } catch (Exception ex) { Chat.SendBroadcastChat((ChatMessageBase)new SimpleChatMessage { baseToken = string.Format(Language.GetString(LanguageConsts.PROPER_SAVE_CHAT_SAVE_FAILED), Language.GetString(SceneCatalog.currentSceneDef.nameToken)) }); ProperSavePlugin.InstanceLogger.LogWarning((object)"Failed to save the game"); ProperSavePlugin.InstanceLogger.LogError((object)ex); } } private static void IssueQuitCommandIL(ILContext il) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) ILCursor val = new ILCursor(il); MethodReference val3 = default(MethodReference); MethodReference val2 = default(MethodReference); string text = default(string); val.GotoNext(new Func<Instruction, bool>[4] { (Instruction x) => ILPatternMatchingExt.MatchLdarg(x, 0), (Instruction x) => ILPatternMatchingExt.MatchLdftn(x, ref val3), (Instruction x) => ILPatternMatchingExt.MatchNewobj(x, ref val2), (Instruction x) => ILPatternMatchingExt.MatchLdstr(x, ref text) }); val.Emit(OpCodes.Dup); val.EmitDelegate<Action<SimpleDialogBox>>((Action<SimpleDialogBox>)AddQuitText); } private static void AddQuitText(SimpleDialogBox simpleDialogBox) { if (NetworkServer.active || NetworkUser.readOnlyInstancesList.Count == NetworkUser.readOnlyLocalPlayersList.Count) { if (ProperSavePlugin.CurrentSave == null) { TextMeshProUGUI descriptionLabel = simpleDialogBox.descriptionLabel; ((TMP_Text)descriptionLabel).text = ((TMP_Text)descriptionLabel).text + Language.GetString(LanguageConsts.PROPER_SAVE_QUIT_DIALOG_NOT_SAVED); return; } if (ProperSavePlugin.CurrentSave.Header.StageClearCount == Run.instance.stageClearCount) { TextMeshProUGUI descriptionLabel2 = simpleDialogBox.descriptionLabel; ((TMP_Text)descriptionLabel2).text = ((TMP_Text)descriptionLabel2).text + Language.GetString(LanguageConsts.PROPER_SAVE_QUIT_DIALOG_SAVED); return; } TextMeshProUGUI descriptionLabel3 = simpleDialogBox.descriptionLabel; string text = ((TMP_Text)descriptionLabel3).text; string pROPER_SAVE_QUIT_DIALOG_SAVED_BEFORE = LanguageConsts.PROPER_SAVE_QUIT_DIALOG_SAVED_BEFORE; object[] array = new string[1] { (Run.instance.stageClearCount - ProperSavePlugin.CurrentSave.Header.StageClearCount).ToString() }; ((TMP_Text)descriptionLabel3).text = text + Language.GetStringFormatted(pROPER_SAVE_QUIT_DIALOG_SAVED_BEFORE, array); } } } } namespace ProperSave.Utils { public static class CatalogHelpers { private static Dictionary<string, DroneIndex> DroneNameToIndex { get; set; } public static DifficultyIndex FindDifficultyIndex(string nameToken) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) try { if (ModCompat.IsR2APIDifficultyLoaded) { return ModCompat.FindR2APIDifficultyIndex(nameToken); } } catch (Exception ex) { ProperSavePlugin.InstanceLogger.LogError((object)ex); } for (int i = 0; i < DifficultyCatalog.difficultyDefs.Length; i++) { if (DifficultyCatalog.difficultyDefs[i].nameToken == nameToken) { return (DifficultyIndex)i; } } return (DifficultyIndex)(-1); } public static DroneIndex FindDroneIndex(string name) { //IL_0065: Unknown result type (might be due to invalid IL or missing references) if (DroneNameToIndex == null) { DroneNameToIndex = DroneCatalog.droneDefs.ToDictionary((DroneDef d) => ((Object)d).name, (DroneDef d) => d.droneIndex); } if (DroneNameToIndex.TryGetValue(name, out var value)) { return value; } return (DroneIndex)(-1); } } public class ReaderContext { public BinaryReader Reader { get; set; } public string[] SharedStrings { get; set; } public bool Resilient { get; set; } public int Version { get; set; } } public static class SharedIndexHelpers { public static int FromDifficulty(DifficultyIndex difficultyIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if ((int)difficultyIndex == -1) { return -1; } if (!context.Resilient) { return (int)difficultyIndex; } DifficultyDef difficultyDef = DifficultyCatalog.GetDifficultyDef(difficultyIndex); return context.SharedStrings.AddOrIndexOf(difficultyDef.nameToken); } public static DifficultyIndex ResolveDifficulty(int sharedIndex, ReaderContext context) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (sharedIndex == -1) { return (DifficultyIndex)(-1); } if (!context.Resilient) { return (DifficultyIndex)sharedIndex; } string text = context.SharedStrings[sharedIndex]; DifficultyIndex val = CatalogHelpers.FindDifficultyIndex(text); if ((int)val == -1) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find difficulty \"" + text + "\" in DifficultyCatalog")); return (DifficultyIndex)(-1); } return val; } public static int FromRule(int ruleIndex, WriterContext context) { if (!context.Resilient || ruleIndex == -1) { return ruleIndex; } RuleDef ruleDef = RuleCatalog.GetRuleDef(ruleIndex); return context.SharedStrings.AddOrIndexOf(ruleDef.globalName); } public static int ResolveRule(int sharedIndex, ReaderContext context) { if (sharedIndex == -1) { return -1; } if (!context.Resilient) { return sharedIndex; } string text = context.SharedStrings[sharedIndex]; RuleDef val = RuleCatalog.FindRuleDef(text); if (val == null) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find rule \"" + text + "\" in RuleCatalog")); return -1; } return val.globalIndex; } public static int FromArtifact(ArtifactIndex artifactIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if ((int)artifactIndex == -1) { return -1; } if (!context.Resilient) { return (int)artifactIndex; } ArtifactDef artifactDef = ArtifactCatalog.GetArtifactDef(artifactIndex); return context.SharedStrings.AddOrIndexOf(artifactDef.cachedName); } public static ArtifactIndex ResolveArtifact(int sharedIndex, ReaderContext context) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (sharedIndex == -1) { return (ArtifactIndex)(-1); } if (!context.Resilient) { return (ArtifactIndex)sharedIndex; } string text = context.SharedStrings[sharedIndex]; ArtifactDef val = ArtifactCatalog.FindArtifactDef(text); if ((Object)(object)val == (Object)null) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find artifact \"" + text + "\" in ArtifactCatalog")); return (ArtifactIndex)(-1); } return val.artifactIndex; } public static int FromBody(BodyIndex bodyIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if ((int)bodyIndex == -1) { return -1; } if (!context.Resilient) { return (int)bodyIndex; } string bodyName = BodyCatalog.GetBodyName(bodyIndex); return context.SharedStrings.AddOrIndexOf(bodyName); } public static BodyIndex ResolveBody(int sharedIndex, ReaderContext context) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (sharedIndex == -1) { return (BodyIndex)(-1); } if (!context.Resilient) { return (BodyIndex)sharedIndex; } string text = context.SharedStrings[sharedIndex]; BodyIndex val = BodyCatalog.FindBodyIndex(text); if ((int)val == -1) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find body \"" + text + "\" in BodyCatalog")); return (BodyIndex)(-1); } return val; } public static int FromStatField(int statIndex, WriterContext context) { if (!context.Resilient || statIndex == -1) { return statIndex; } StatDef val = StatDef.allStatDefs[statIndex]; return context.SharedStrings.AddOrIndexOf(val.name); } public static int ResolveStatField(int sharedIndex, ReaderContext context) { if (sharedIndex == -1) { return -1; } if (!context.Resilient) { return sharedIndex; } string text = context.SharedStrings[sharedIndex]; StatDef val = StatDef.Find(text); if (val == null) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find stat \"" + text + "\" in StatDef")); return -1; } return val.index; } public static int FromUnlockable(UnlockableIndex unlockableIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if ((int)unlockableIndex == -1) { return -1; } if (!context.Resilient) { return (int)unlockableIndex; } UnlockableDef unlockableDef = UnlockableCatalog.GetUnlockableDef(unlockableIndex); return context.SharedStrings.AddOrIndexOf(unlockableDef.cachedName); } public static UnlockableIndex ResolveUnlockable(int sharedIndex, ReaderContext context) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) if (sharedIndex == -1) { return (UnlockableIndex)(-1); } if (!context.Resilient) { return (UnlockableIndex)sharedIndex; } string text = context.SharedStrings[sharedIndex]; UnlockableDef unlockableDef = UnlockableCatalog.GetUnlockableDef(text); if ((Object)(object)unlockableDef == (Object)null) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find unlockable \"" + text + "\" in UnlockableCatalog")); return (UnlockableIndex)(-1); } return unlockableDef.index; } public static int FromMaster(MasterIndex masterIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (masterIndex == MasterIndex.none) { return -1; } if (!context.Resilient) { return masterIndex.i; } string masterName = MasterCatalog.GetMasterName(masterIndex); return context.SharedStrings.AddOrIndexOf(masterName); } public static MasterIndex ResolveMaster(int sharedIndex, ReaderContext context) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) if (sharedIndex == -1) { return MasterIndex.none; } if (!context.Resilient) { return new MasterIndex(sharedIndex); } string text = context.SharedStrings[sharedIndex]; MasterIndex val = MasterCatalog.FindMasterIndex(text); if (val == MasterIndex.none) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find master \"" + text + "\" in MasterCatalog")); return MasterIndex.none; } return val; } public static int FromItem(ItemIndex itemIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if ((int)itemIndex == -1) { return -1; } if (!context.Resilient) { return (int)itemIndex; } ItemDef itemDef = ItemCatalog.GetItemDef(itemIndex); return context.SharedStrings.AddOrIndexOf(((Object)itemDef).name); } public static ItemIndex ResolveItem(int sharedIndex, ReaderContext context) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (sharedIndex == -1) { return (ItemIndex)(-1); } if (!context.Resilient) { return (ItemIndex)sharedIndex; } string text = context.SharedStrings[sharedIndex]; ItemIndex val = ItemCatalog.FindItemIndex(text); if ((int)val == -1) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find item \"" + text + "\" in ItemCatalog")); return (ItemIndex)(-1); } return val; } public static int FromEquipment(EquipmentIndex equipmentIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if ((int)equipmentIndex == -1) { return -1; } if (!context.Resilient) { return (int)equipmentIndex; } EquipmentDef equipmentDef = EquipmentCatalog.GetEquipmentDef(equipmentIndex); return context.SharedStrings.AddOrIndexOf(((Object)equipmentDef).name); } public static EquipmentIndex ResolveEquipment(int sharedIndex, ReaderContext context) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) if (sharedIndex == -1) { return (EquipmentIndex)(-1); } if (!context.Resilient) { return (EquipmentIndex)sharedIndex; } string text = context.SharedStrings[sharedIndex]; EquipmentIndex val = EquipmentCatalog.FindEquipmentIndex(text); if ((int)val == -1) { ProperSavePlugin.InstanceLogger.LogWarning((object)("Couldn't find equipment \"" + text + "\" in EquipmentCatalog")); return (EquipmentIndex)(-1); } return val; } public static int FromDrone(DroneIndex droneIndex, WriterContext context) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown if ((int