Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of HonestMainMenu v1.1.0
HonestMainMenu.Il2Cpp.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using HarmonyLib; using HonestMainMenu; using HonestMainMenu.Models; using HonestMainMenu.Patches; using HonestMainMenu.Services; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.AvatarFramework; using Il2CppScheduleOne.AvatarFramework.Customization; using Il2CppScheduleOne.Clothing; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Networking; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Tools; using Il2CppScheduleOne.UI.Input; using Il2CppScheduleOne.UI.MainMenu; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Utils; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(true)] [assembly: Guid("F2F8CCDD-DA07-47DF-9247-39B991BE39E3")] [assembly: MelonInfo(typeof(Main), "Honest Main Menu", "1.1.0", "Roach_ (Adrian Nicolae)", "https://github.com/RoachxD/ScheduleOne.HonestMainMenu/releases/latest")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonColor(255, 97, 100, 62)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("Roach_")] [assembly: AssemblyConfiguration("Release_Il2Cpp")] [assembly: AssemblyCopyright("Copyright © 2025 Roach_ (Adrian Nicolae)")] [assembly: AssemblyDescription("A MelonLoader mod for Schedule I that adds a true continue button to the main menu.")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("HonestMainMenu.Il2Cpp")] [assembly: AssemblyTitle("HonestMainMenu.Il2Cpp")] [assembly: NeutralResourcesLanguage("en-GB")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] namespace HonestMainMenu { public class Main : MelonMod { public override void OnInitializeMelon() { string text = "IL2CPP"; Melon<Main>.Logger.Msg("Honest Main Menu (" + text + ") initializing.."); try { ((MelonBase)this).HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); IEnumerable<string> values = from p in ((MelonBase)this).HarmonyInstance.GetPatchedMethods() select p.DeclaringType.FullName + "." + p.Name; string text2 = string.Join(", ", values); Melon<Main>.Logger.Msg("Honest Main Menu initialized successfully!"); Melon<Main>.Logger.Msg("Harmony patches successfully applied: " + text2 + "."); } catch (Exception ex) { Melon<Main>.Logger.Error("Failed to apply Harmony patches: " + ex.Message); Melon<Main>.Logger.Error((object)ex); } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (!sceneName.Equals("Menu", StringComparison.OrdinalIgnoreCase)) { HandleNonMenuScene(sceneName); return; } MainMenuRigLoadStuffPatch.ResetLoadedRigs(); HandleMenuScene(); } private static void HandleNonMenuScene(string sceneName) { Melon<Main>.Logger.Msg("Scene '" + sceneName + "' loaded outside the main menu. Stopping menu reactivity and snapshotting live clothing colors."); MenuReactivity.Stop(); MainMenuRigLoadStuffPatch.ResetLoadedRigs(); ClothingUtility instance = Singleton<ClothingUtility>.Instance; if (((instance != null) ? instance.ColorDataList : null) != null) { ClothingDataService.ExtractAndSaveColorData(instance.ColorDataList); Melon<Main>.Logger.Msg($"Captured {instance.ColorDataList.Count} clothing colors before leaving the menu scene."); } else { Melon<Main>.Logger.Warning("[Main.OnSceneWasLoaded] ClothingUtility or its ColorDataList is null, skipping color data extraction."); } } private static void HandleMenuScene() { Melon<Main>.Logger.Msg("Main menu scene ('Menu') loaded. Attempting UI modifications.."); GameObject val = GameObject.Find("MainMenu"); if ((Object)(object)val == (Object)null) { Melon<Main>.Logger.Error("Could not find main menu root object 'MainMenu'. Aborting UI modifications."); return; } Transform transform = val.transform; BackButtonPromptSetup.Apply(transform); MenuButtons menuButtons = MenuSetup.Build(transform); if (menuButtons != null) { MelonCoroutines.Start(MenuReactivity.Run(menuButtons)); ContinueScreenSetup.Apply(transform); Melon<Main>.Logger.Msg("Honest Main Menu UI modifications applied successfully."); } } } public static class UIConstants { public const string MenuSceneName = "Menu"; public const string MainMenuObjectName = "MainMenu"; public const string MenuButtonsParentPath = "Home/Bank"; public const string ContinueObjectNameAndLabel = "Continue"; public const string LoadGameObjectName = "LoadGame"; public const string InputPromptObjectName = "InputPrompt (Back)"; public const string LoadGameLabel = "Load Game"; public const string RmbPromptBindingKey = "rightButton"; public const string TitleObjectName = "Title"; } public static class UIHelper { public static void SetText(this GameObject gameObject, string text) { if (!((Object)(object)gameObject == (Object)null)) { TextMeshProUGUI componentInChildren = gameObject.GetComponentInChildren<TextMeshProUGUI>(true); if (!((Object)(object)componentInChildren == (Object)null)) { ((TMP_Text)componentInChildren).text = text; } } } public static bool TryFindChild(Transform parent, string childPath, out Transform child, string failureMessage, bool logWarning = false) { child = ((parent != null) ? parent.Find(childPath) : null); if ((Object)(object)child != (Object)null) { return true; } if (logWarning) { Melon<Main>.Logger.Warning(failureMessage); } else { Melon<Main>.Logger.Error(failureMessage); } return false; } } } namespace HonestMainMenu.Services { internal static class BackButtonPromptSetup { public static void Apply(Transform menuRoot) { if (TryGetInputPrompt(menuRoot, out var promptComponent, out var ownerObject) && RemoveActionsMatchingBinding(promptComponent, "rightButton")) { RefreshPrompt(promptComponent, ownerObject); } } private static bool TryGetInputPrompt(Transform mainMenuRoot, out InputPrompt promptComponent, out GameObject ownerObject) { promptComponent = null; Transform obj = mainMenuRoot.Find("InputPrompt (Back)"); ownerObject = ((obj != null) ? ((Component)obj).gameObject : null); if ((Object)(object)ownerObject == (Object)null) { Melon<Main>.Logger.Warning("Could not find InputPrompt owner GameObject at 'InputPrompt (Back)'."); return false; } promptComponent = ownerObject.GetComponent<InputPrompt>(); if ((Object)(object)promptComponent == (Object)null) { Melon<Main>.Logger.Warning("Could not find 'ScheduleOne.UI.Input.InputPrompt' component on 'InputPrompt (Back)'. Make sure the type name is correct."); return false; } return true; } private static bool RemoveActionsMatchingBinding(InputPrompt promptComponent, string bindingKeyToRemove) { List<InputActionReference> actions = promptComponent.Actions; bool result = false; for (int num = actions.Count - 1; num >= 0; num--) { if (ShouldRemoveAction(actions[num], bindingKeyToRemove)) { actions.RemoveAt(num); result = true; } } return result; } private static bool ShouldRemoveAction(InputActionReference actionRef, string bindingKeyToRemove) { if (((actionRef != null) ? actionRef.action : null) == null) { return false; } string text = default(string); string value = default(string); InputActionRebindingExtensions.GetBindingDisplayString(actionRef.action, 0, ref text, ref value, (DisplayStringOptions)0); return bindingKeyToRemove.Equals(value, StringComparison.OrdinalIgnoreCase); } private static void RefreshPrompt(InputPrompt promptComponent, GameObject promptOwnerObject) { if (promptOwnerObject.activeInHierarchy) { promptOwnerObject.SetActive(false); promptOwnerObject.SetActive(true); } } } internal interface IClothingApplicator { IEnumerator Apply(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings); } internal static class ClothingApplicatorFactory { internal static IClothingApplicator Create() { return new Il2CppClothingApplicator(); } } internal abstract class ClothingApplicatorBase : IClothingApplicator { protected static readonly Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); protected static readonly DataContractJsonSerializerSettings SerializerSettings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }; protected static readonly DataContractJsonSerializer ClothingFileSerializer = new DataContractJsonSerializer(typeof(SerializableClothingFile), SerializerSettings); protected static readonly DataContractJsonSerializer ClothingItemSerializer = new DataContractJsonSerializer(typeof(SerializableClothingSaveData), SerializerSettings); public abstract IEnumerator Apply(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings); protected static SerializableClothingFile LoadClothingFile(string playerSavePath) { string text = Path.Combine(playerSavePath, "Clothing.json"); if (!File.Exists(text)) { Melon<Main>.Logger.Warning("[ClothingApplicator] Clothing file not found at " + text + ". Skipping."); return null; } try { using FileStream stream = File.OpenRead(text); return ClothingFileSerializer.ReadObject((Stream)stream) as SerializableClothingFile; } catch (Exception value) { Melon<Main>.Logger.Error($"[ClothingApplicator] Failed to read clothing file at {text}: {value}"); return null; } } protected static SerializableClothingSaveData DeserializeClothingItem(string json, string sourcePath) { if (string.IsNullOrWhiteSpace(json)) { Melon<Main>.Logger.Warning("[ClothingApplicator] Empty clothing item JSON found in '" + sourcePath + "', skipping."); return null; } try { using MemoryStream stream = new MemoryStream(Utf8.GetBytes(json)); return ClothingItemSerializer.ReadObject((Stream)stream) as SerializableClothingSaveData; } catch (Exception value) { Melon<Main>.Logger.Error($"[ClothingApplicator] Failed to deserialize clothing item ({sourcePath}): {value}"); return null; } } } internal sealed class Il2CppClothingApplicator : ClothingApplicatorBase { public override IEnumerator Apply(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings) { yield return null; if (!File.Exists(Path.Combine(playerSavePath, "Appearance.json"))) { Melon<Main>.Logger.Warning("[ClothingApplicator] Appearance.json not found; skipping clothing application."); yield break; } SerializableClothingFile serializableClothingFile = ClothingApplicatorBase.LoadClothingFile(playerSavePath); if (serializableClothingFile?.Items == null || serializableClothingFile.Items.Count == 0) { yield break; } PlayerClothing val = ((Component)avatar).GetComponent<PlayerClothing>(); if (val == null) { val = ((Component)avatar).gameObject.AddComponent<PlayerClothing>(); } AvatarSettings val2 = Object.Instantiate<AvatarSettings>(basicSettings.GetAvatarSettings()); int num = 0; foreach (string item in serializableClothingFile.Items) { if (TryBuildInstance(item, playerSavePath, out var instance)) { try { val.ApplyClothing(val2, instance); num++; } catch (Exception value) { Melon<Main>.Logger.Warning($"[ClothingApplicator] Failed to apply clothing instance: {value}"); } } } if (num > 0) { avatar.LoadAvatarSettings(val2); Melon<Main>.Logger.Msg($"[ClothingApplicator] Applied {num} clothing items to the main menu Avatar."); } else { Melon<Main>.Logger.Warning("[ClothingApplicator] No valid clothing items were applied to the main menu Avatar."); } } private static bool TryBuildInstance(string itemJson, string sourcePath, out ClothingInstance instance) { //IL_0095: 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_009c: Expected O, but got Unknown instance = null; SerializableClothingSaveData serializableClothingSaveData = ClothingApplicatorBase.DeserializeClothingItem(itemJson, sourcePath); if (serializableClothingSaveData == null) { return false; } if (!string.Equals(serializableClothingSaveData.DataType, "ClothingData", StringComparison.OrdinalIgnoreCase) || !serializableClothingSaveData.TryGetValidColor(out var _)) { return false; } if (!ClothingDefinitionResolver.TryResolve(serializableClothingSaveData, out var definition, out var resolvedColor)) { Melon<Main>.Logger.Warning($"[ClothingApplicator] Clothing item not found in registry: {serializableClothingSaveData.Id} (source: {sourcePath})"); return false; } try { instance = new ClothingInstance((ItemDefinition)(object)definition, 1, resolvedColor); return true; } catch (Exception value) { Melon<Main>.Logger.Warning($"[ClothingApplicator] Failed to create ClothingInstance for '{serializableClothingSaveData.Id}': {value}"); return false; } } } public static class ClothingDataService { public static IEnumerator ApplyClothingCoroutine(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings) { if ((Object)(object)avatar == (Object)null || (Object)(object)basicSettings == (Object)null || string.IsNullOrEmpty(playerSavePath)) { Melon<Main>.Logger.Warning("[ClothingDataService] Missing data needed for clothing application."); yield break; } ClothingUtilitySeeder.EnsureWithColors(ColorDataRepository.Load()); IClothingApplicator clothingApplicator = ClothingApplicatorFactory.Create(); yield return clothingApplicator.Apply(avatar, playerSavePath, basicSettings); } public static void ExtractAndSaveColorData(List<ColorData> liveDataList) { ColorDataRepository.SaveLiveColors(liveDataList); } public static List<SerializableColorData> LoadColorData() { return ColorDataRepository.Load(); } } internal static class ClothingDefinitionResolver { internal unsafe static bool TryResolve(SerializableClothingSaveData clothingData, out ClothingDefinition definition, out EClothingColor resolvedColor) { //IL_001c: 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_0039: Expected I4, but got Unknown //IL_0021->IL0021: Incompatible stack types: Ref vs I4 //IL_001c->IL0021: Incompatible stack types: I4 vs Ref //IL_001c->IL0021: Incompatible stack types: Ref vs I4 ItemDefinition item = Registry.GetItem(clothingData.Id); definition = (ClothingDefinition)(object)((item is ClothingDefinition) ? item : null); ref EClothingColor reference = ref resolvedColor; ClothingDefinition obj = definition; int num; if (obj != null) { reference = ref *(EClothingColor*)obj.DefaultColor; num = (int)(ref reference); } else { num = 0; reference = ref *(EClothingColor*)num; num = (int)(ref reference); } *(int*)num = (int)Unsafe.AsPointer(ref reference); if ((Object)(object)definition != (Object)null) { if (clothingData.TryGetValidColor(out var clothingColor)) { resolvedColor = (EClothingColor)(int)clothingColor; } return true; } return TryResolveFromRegister(clothingData, out definition, out resolvedColor); } private unsafe static bool TryResolveFromRegister(SerializableClothingSaveData clothingData, out ClothingDefinition definition, out EClothingColor resolvedColor) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0012->IL0012: Incompatible stack types: Ref vs I4 //IL_0011->IL0012: Incompatible stack types: I4 vs Ref //IL_0011->IL0012: Incompatible stack types: Ref vs I4 definition = null; ref EClothingColor reference = ref resolvedColor; int num; if (clothingData.TryGetValidColor(out var clothingColor)) { reference = ref *(EClothingColor*)clothingColor; num = (int)(ref reference); } else { num = 0; reference = ref *(EClothingColor*)num; num = (int)(ref reference); } *(int*)num = (int)Unsafe.AsPointer(ref reference); ItemRegister val = FindRegister(clothingData.Id); if (val == null) { return false; } string text = NormalizeRegistryAssetPath(val.AssetPath); if (string.IsNullOrEmpty(text)) { return false; } definition = Resources.Load<ClothingDefinition>(text); if ((Object)(object)definition != (Object)null) { return true; } if (TryMapItemDefinition(text, out var avatarPath, out var applicationType) && ResourceExists(avatarPath)) { definition = ScriptableObject.CreateInstance<ClothingDefinition>(); definition.ClothingAssetPath = avatarPath; definition.ApplicationType = applicationType; definition.Colorable = true; return true; } return false; } private static ItemRegister FindRegister(string id) { Registry instance = Singleton<Registry>.Instance; if (((instance != null) ? instance.ItemDictionary : null) == null || string.IsNullOrEmpty(id)) { return null; } Enumerator<int, ItemRegister> enumerator = instance.ItemDictionary.GetEnumerator(); while (enumerator.MoveNext()) { ItemRegister val = enumerator.Current?.Value; if (val != null && val.ID != null && val.ID.Equals(id, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } private static string NormalizeRegistryAssetPath(string assetPath) { if (string.IsNullOrWhiteSpace(assetPath)) { return null; } string text = assetPath.Replace("\\", "/"); if (text.StartsWith("Assets/Resources/", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("Assets/Resources/".Length); } if (text.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - ".asset".Length); } return text.Trim('/'); } private static bool TryMapItemDefinition(string normalizedPath, out string avatarPath, out EClothingApplicationType applicationType) { avatarPath = null; applicationType = (EClothingApplicationType)2; if (string.IsNullOrWhiteSpace(normalizedPath)) { return false; } string[] array = normalizedPath.Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 3 || !array[0].Equals("Clothing", StringComparison.OrdinalIgnoreCase)) { return false; } if (array.Length >= 4 && array[1].Equals("Accessories", StringComparison.OrdinalIgnoreCase)) { string value = array[2]; string value2 = array[3]; avatarPath = $"Avatar/Accessories/{value}/{value2}/{value2}"; applicationType = (EClothingApplicationType)2; return true; } string text = array[1]; string text2 = array[2]; if (IsAccessoryCategory(text)) { avatarPath = $"Avatar/Accessories/{text}/{text2}/{text2}"; applicationType = (EClothingApplicationType)2; return true; } applicationType = (EClothingApplicationType)0; avatarPath = "Avatar/Layers/" + text + "/" + text2; return true; } private static bool IsAccessoryCategory(string category) { if (!category.Equals("Head", StringComparison.OrdinalIgnoreCase) && !category.Equals("Face", StringComparison.OrdinalIgnoreCase) && !category.Equals("Feet", StringComparison.OrdinalIgnoreCase) && !category.Equals("Hands", StringComparison.OrdinalIgnoreCase) && !category.Equals("Neck", StringComparison.OrdinalIgnoreCase) && !category.Equals("Back", StringComparison.OrdinalIgnoreCase)) { return category.Equals("Accessory", StringComparison.OrdinalIgnoreCase); } return true; } private static bool ResourceExists(string resourcePath) { if (string.IsNullOrEmpty(resourcePath)) { return false; } return Resources.Load<Object>(resourcePath) != (Object)null; } } internal static class ClothingUtilitySeeder { private static int? _lastHash; internal static ClothingUtility EnsureWithColors(IReadOnlyList<SerializableColorData> colors) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) ClothingUtility val = Singleton<ClothingUtility>.Instance; if ((Object)(object)val == (Object)null) { Melon<Main>.Logger.Msg("[ClothingUtilitySeeder] ClothingUtility instance not found, creating a new one."); val = new GameObject("@Clothing").AddComponent<ClothingUtility>(); } Populate(val, colors); return val; } internal static void Populate(ClothingUtility clothingUtility, IReadOnlyList<SerializableColorData> colors) { if ((Object)(object)clothingUtility == (Object)null) { Melon<Main>.Logger.Error("[ClothingUtilitySeeder] Cannot populate colors because ClothingUtility is null."); } else if (colors == null || colors.Count == 0) { Melon<Main>.Logger.Error("[ClothingUtilitySeeder] No color data provided; ClothingUtility will remain unchanged."); } else if (!IsAlreadySeeded(clothingUtility, colors)) { PopulateColorList(clothingUtility, colors); _lastHash = ComputeHash(colors); } } private static void PopulateColorList(ClothingUtility clothingUtility, IReadOnlyList<SerializableColorData> colors) { //IL_003b: 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) //IL_0043: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0055: 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_0062: Expected O, but got Unknown if (clothingUtility.ColorDataList == null) { List<ColorData> val2 = (clothingUtility.ColorDataList = new List<ColorData>()); } List<ColorData> colorDataList = clothingUtility.ColorDataList; colorDataList.Clear(); foreach (SerializableColorData color in colors) { if (color != null) { Color actualColor = color.ToUnityColor(); colorDataList.Add(new ColorData { ColorType = color.GetEnumColorType(), ActualColor = actualColor }); } } } private static bool IsAlreadySeeded(ClothingUtility clothingUtility, IReadOnlyList<SerializableColorData> colors) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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) if (((clothingUtility != null) ? clothingUtility.ColorDataList : null) == null || colors == null) { return false; } if (colors.Count == 0) { return false; } int num = ComputeHash(colors); if (_lastHash.HasValue && _lastHash.Value == num) { return true; } if (clothingUtility.ColorDataList.Count != colors.Count) { return false; } for (int i = 0; i < colors.Count; i++) { ColorData val = clothingUtility.ColorDataList[i]; SerializableColorData serializableColorData = colors[i]; if (val == null || serializableColorData == null) { return false; } if (val.ColorType != serializableColorData.GetEnumColorType()) { return false; } if (val.ActualColor != serializableColorData.ToUnityColor()) { return false; } } _lastHash = num; return true; } private static int ComputeHash(IReadOnlyList<SerializableColorData> colors) { //IL_0038: 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_003d: Unknown result type (might be due to invalid IL or missing references) int num = 17; for (int i = 0; i < colors.Count; i++) { SerializableColorData serializableColorData = colors[i]; num = num * 31 + (serializableColorData?.ColorType.GetHashCode() ?? 0); Color val = serializableColorData?.ToUnityColor() ?? Color.white; num = num * 31 + val.r.GetHashCode(); num = num * 31 + val.g.GetHashCode(); num = num * 31 + val.b.GetHashCode(); num = num * 31 + val.a.GetHashCode(); } return num; } } internal static class ColorDataRepository { private const string ResourceName = "HonestMainMenu.Resources.ColorData.json"; private static readonly Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static readonly DataContractJsonSerializerSettings SerializerSettings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }; private static readonly DataContractJsonSerializer ColorListSerializer = new DataContractJsonSerializer(typeof(List<SerializableColorData>), SerializerSettings); internal static string UserDataPath => Path.Combine(MelonEnvironment.MelonBaseDirectory, "Mods", "HonestMainMenu"); internal static string ColorDataFilePath => Path.Combine(UserDataPath, "ColorData.json"); internal static List<SerializableColorData> Load() { if (File.Exists(ColorDataFilePath)) { try { using FileStream stream = File.OpenRead(ColorDataFilePath); return ReadColorList(stream, ColorDataFilePath); } catch (Exception value) { Melon<Main>.Logger.Error($"[ColorDataRepository] Failed to read custom color data '{ColorDataFilePath}': {value}"); } } Melon<Main>.Logger.Msg("[ColorDataRepository] Custom color data not found or unreadable. Loading embedded defaults."); return LoadEmbeddedDefaults(); } internal static void SaveLiveColors(List<ColorData> liveDataList) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (liveDataList == null) { Melon<Main>.Logger.Warning("[ColorDataRepository] No live color data provided; skipping save."); return; } try { List<SerializableColorData> list = new List<SerializableColorData>(liveDataList.Count); Enumerator<ColorData> enumerator = liveDataList.GetEnumerator(); while (enumerator.MoveNext()) { ColorData current = enumerator.Current; if (current != null) { list.Add(new SerializableColorData(current.ColorType, current.ActualColor)); } } List<SerializableColorData> second = LoadEmbeddedDefaults(cloneList: false); if (!list.SequenceEqual(second)) { Melon<Main>.Logger.Warning("[ColorDataRepository] Live color data differs from embedded defaults. Consider updating Resources/ColorData.json."); } else { Melon<Main>.Logger.Msg("[ColorDataRepository] Live color data matches embedded defaults."); } EnsureUserDataDirectory(); string contents = SerializeColors(list); File.WriteAllText(ColorDataFilePath, contents, Utf8); Melon<Main>.Logger.Msg($"[ColorDataRepository] Saved {list.Count} colors to '{ColorDataFilePath}'."); } catch (Exception value) { Melon<Main>.Logger.Error($"[ColorDataRepository] Failed to save live color data: {value}"); } } internal static List<SerializableColorData> LoadEmbeddedDefaults(bool cloneList = true) { try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("HonestMainMenu.Resources.ColorData.json"); if (stream == null) { Melon<Main>.Logger.Error("[ColorDataRepository] Embedded resource 'HonestMainMenu.Resources.ColorData.json' not found."); return new List<SerializableColorData>(); } List<SerializableColorData> list = ReadColorList(stream, "HonestMainMenu.Resources.ColorData.json"); return cloneList ? new List<SerializableColorData>(list) : list; } catch (Exception value) { Melon<Main>.Logger.Error($"[ColorDataRepository] Failed to read embedded color data '{"HonestMainMenu.Resources.ColorData.json"}': {value}"); return new List<SerializableColorData>(); } } private static List<SerializableColorData> ReadColorList(Stream stream, string source) { try { return Normalize(ColorListSerializer.ReadObject(stream) as List<SerializableColorData>); } catch (Exception value) { Melon<Main>.Logger.Error($"[ColorDataRepository] Failed to deserialize color data ({source}): {value}"); return new List<SerializableColorData>(); } } private static List<SerializableColorData> Normalize(List<SerializableColorData> rawList) { return rawList?.Where((SerializableColorData entry) => entry != null && entry.ActualColor != null).ToList() ?? new List<SerializableColorData>(); } private static string SerializeColors(IReadOnlyList<SerializableColorData> colors) { using MemoryStream memoryStream = new MemoryStream(); ColorListSerializer.WriteObject((Stream)memoryStream, (object?)colors); return Utf8.GetString(memoryStream.ToArray()); } private static void EnsureUserDataDirectory() { if (!Directory.Exists(UserDataPath)) { Directory.CreateDirectory(UserDataPath); } } } internal static class ContinueScreenSetup { public static void Apply(Transform mainMenuRoot) { if (UIHelper.TryFindChild(mainMenuRoot, "Continue", out var child, "Could not find 'MainMenu/Continue' screen object. Aborting screen configuration.")) { ((Object)((Component)child).gameObject).name = "LoadGame"; if (UIHelper.TryFindChild(child, "Title", out var child2, $"Could not find '{"Title"}' child GameObject under '{((Object)child).name}'. The screen title text will not be updated.")) { ((Component)child2).gameObject.SetText("Load Game"); } } } } internal static class MenuReactivity { private static UnityAction _onSaveInfoLoaded; private static bool _continueActionHooked; private const string ContinueErrorTitle = "Error"; private const string ContinueErrorMessage = "An error occurred while trying to continue the last played game. Please try loading it manually from the Load Game menu."; public static IEnumerator Run(MenuButtons menuButtons) { yield return (object)new WaitUntil(Func<bool>.op_Implicit((Func<bool>)(() => (Object)(object)Singleton<LoadManager>.Instance != (Object)null))); if (LoadManager.LastPlayedGame != null) { UpdateContinueButtonInteractableState(menuButtons); } else if ((Delegate)(object)_onSaveInfoLoaded == (Delegate)null) { _onSaveInfoLoaded = UnityAction.op_Implicit((Action)delegate { Melon<Main>.Logger.Msg("LoadManager.OnSaveInfoLoaded event triggered, updating buttons interactable states."); UpdateContinueButtonInteractableState(menuButtons); DetachSaveInfoListener(); }); Singleton<LoadManager>.Instance.onSaveInfoLoaded.AddListener(_onSaveInfoLoaded); } } public static void Stop() { DetachSaveInfoListener(); _continueActionHooked = false; } private static void UpdateContinueButtonInteractableState(MenuButtons menuButtons) { if (!_continueActionHooked) { ((UnityEvent)menuButtons.ContinueButton.onClick).AddListener(UnityAction.op_Implicit((Action)PerformNewContinueAction)); _continueActionHooked = true; } ((Selectable)menuButtons.ContinueButton).interactable = true; ((Selectable)menuButtons.LoadGameButton).interactable = true; } private static void PerformNewContinueAction() { if (!Singleton<Lobby>.Instance.IsHost) { Singleton<MainMenuPopup>.Instance.Open("Cannot Continue", "You must be the host in order to be able to continue a game.", true); return; } try { Singleton<LoadManager>.Instance.StartGame(LoadManager.LastPlayedGame, false, true); } catch (MissingMethodException ex) when (ex.Message.Contains("StartGame") || (ex.StackTrace?.Contains("LoadManager.StartGame") ?? false)) { try { Singleton<LoadManager>.Instance.StartGame(LoadManager.LastPlayedGame, false, true); Melon<Main>.Logger.Warning($"Detected missing method exception while trying to continue the last played game: {ex}. " + "This is likely due to an outdated game build. Please update to the latest version. Continuing with the older method signature."); } catch (Exception exception) { ShowContinueFailureDialog("Fallback StartGame call also failed after MissingMethodException", exception); } } catch (Exception exception2) { ShowContinueFailureDialog("An error occurred while trying to continue the last played game", exception2); } } private static void ShowContinueFailureDialog(string context, Exception exception) { Melon<Main>.Logger.Error($"{context}: {exception}"); Singleton<MainMenuPopup>.Instance.Open("Error", "An error occurred while trying to continue the last played game. Please try loading it manually from the Load Game menu.", true); } private static void DetachSaveInfoListener() { if (!((Delegate)(object)_onSaveInfoLoaded == (Delegate)null) && !((Object)(object)Singleton<LoadManager>.Instance == (Object)null)) { Singleton<LoadManager>.Instance.onSaveInfoLoaded.RemoveListener(_onSaveInfoLoaded); _onSaveInfoLoaded = null; } } } internal static class MenuSetup { public static MenuButtons Build(Transform menuRoot) { if (!UIHelper.TryFindChild(menuRoot, "Home/Bank", out var child, "Could not find menu buttons parent object at 'MainMenu/Home/Bank', aborting UI modifications!")) { return null; } if (!UIHelper.TryFindChild(child, "Continue", out var child2, "Could not find the original 'Continue' button object, aborting UI modifications!")) { return null; } MenuButtons menuButtons = new MenuButtons { ContinueButton = CreateAndConfigureNewContinueButton(((Component)child2).gameObject), LoadGameButton = null }; if ((Object)(object)menuButtons.ContinueButton == (Object)null) { Melon<Main>.Logger.Error("Failed to create and configure the new 'Continue' button, aborting UI modifications!"); return null; } menuButtons.LoadGameButton = ConfigureLoadGameButton(((Component)child2).gameObject); if ((Object)(object)menuButtons.LoadGameButton == (Object)null) { Melon<Main>.Logger.Error("Failed to configure the 'LoadGame' button, aborting UI modifications!"); return null; } ((Selectable)menuButtons.ContinueButton).interactable = false; ((Selectable)menuButtons.LoadGameButton).interactable = false; PositionButtonsAndOffsetSiblings(menuButtons); return menuButtons; } private static Button ConfigureLoadGameButton(GameObject buttonObject) { Button component = buttonObject.GetComponent<Button>(); if ((Object)(object)component == (Object)null) { return null; } ((Object)buttonObject).name = "LoadGame"; buttonObject.SetText("Load Game"); return component; } private static Button CreateAndConfigureNewContinueButton(GameObject buttonObject) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown GameObject val = Object.Instantiate<GameObject>(buttonObject, buttonObject.transform.parent); ((Object)val).name = "Continue"; val.SetText("Continue"); Button component = val.GetComponent<Button>(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val); return null; } component.onClick = new ButtonClickedEvent(); return component; } private static void PositionButtonsAndOffsetSiblings(MenuButtons menuButtons) { //IL_0022: 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_0033: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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) if (!TryGetRects(menuButtons, out var loadGameRect, out var continueRect, out var parentTransform, out var originalIndex)) { Object.Destroy((Object)(object)((Component)menuButtons.ContinueButton).gameObject); return; } Rect rect = continueRect.rect; float height = ((Rect)(ref rect)).height; Vector2 anchoredPosition = loadGameRect.anchoredPosition; PlaceButton(((Component)menuButtons.ContinueButton).gameObject, continueRect, anchoredPosition, originalIndex); PlaceButton(((Component)menuButtons.LoadGameButton).gameObject, loadGameRect, anchoredPosition + new Vector2(0f, 0f - height), originalIndex + 1); OffsetRemainingSiblings(parentTransform, originalIndex + 2, height); Melon<Main>.Logger.Msg("Positioned new 'Continue' at original spot, shifted 'LoadGame' (Load Game) below it, and offset subsequent buttons."); } private static bool TryGetRects(MenuButtons menuButtons, out RectTransform loadGameRect, out RectTransform continueRect, out Transform parentTransform, out int originalIndex) { loadGameRect = ((Component)menuButtons.LoadGameButton).gameObject.GetComponent<RectTransform>(); continueRect = ((Component)menuButtons.ContinueButton).gameObject.GetComponent<RectTransform>(); parentTransform = ((Component)menuButtons.LoadGameButton).gameObject.transform.parent; originalIndex = ((Component)menuButtons.LoadGameButton).gameObject.transform.GetSiblingIndex(); if ((Object)(object)loadGameRect != (Object)null && (Object)(object)continueRect != (Object)null && (Object)(object)parentTransform != (Object)null) { return true; } Melon<Main>.Logger.Error("Could not get RectTransforms or parent for positioning. Destroying new button."); return false; } private static void PlaceButton(GameObject buttonObject, RectTransform rectTransform, Vector2 anchoredPosition, int siblingIndex) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) rectTransform.anchoredPosition = anchoredPosition; buttonObject.transform.SetSiblingIndex(siblingIndex); } private static void OffsetRemainingSiblings(Transform parentTransform, int startIndex, float offset) { //IL_0016: 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_002d: Unknown result type (might be due to invalid IL or missing references) for (int i = startIndex; i < parentTransform.childCount; i++) { RectTransform component = ((Component)parentTransform.GetChild(i)).GetComponent<RectTransform>(); if (component != null) { component.anchoredPosition = new Vector2(component.anchoredPosition.x, component.anchoredPosition.y - offset); } } } } } namespace HonestMainMenu.Patches { [HarmonyPatch(typeof(MainMenuRig), "LoadStuff")] public static class MainMenuRigLoadStuffPatch { private static readonly HashSet<int> LoadedRigs = new HashSet<int>(); private static readonly object LoadedRigsLock = new object(); public static bool Prefix(MainMenuRig __instance) { try { if (!TryBeginRigLoad(__instance)) { return false; } bool flag = false; if (LoadManager.LastPlayedGame != null) { string text = Path.Combine(LoadManager.LastPlayedGame.SavePath, "Players", "Player_0"); string path = Path.Combine(text, "Appearance.json"); BasicAvatarSettings val = ScriptableObject.CreateInstance<BasicAvatarSettings>(); if (File.Exists(path)) { JsonUtility.FromJsonOverwrite(File.ReadAllText(path), (Object)(object)val); __instance.Avatar.LoadAvatarSettings(val.GetAvatarSettings()); MelonCoroutines.Start(ClothingDataService.ApplyClothingCoroutine(__instance.Avatar, text, val)); flag = true; } else { Melon<Main>.Logger.Warning("[MainMenuRigLoadStuffPatch] Appearance.json not found, skipping avatar appearance load."); } float num = LoadManager.LastPlayedGame.Networth; for (int i = 0; i < ((Il2CppArrayBase<CashPile>)(object)__instance.CashPiles).Length; i++) { float displayedAmount = Mathf.Clamp(num, 0f, 100000f); ((Il2CppArrayBase<CashPile>)(object)__instance.CashPiles)[i].SetDisplayedAmount(displayedAmount); num -= 100000f; if (num <= 0f) { break; } } Melon<Main>.Logger.Msg("[MainMenuRigLoadStuffPatch] Completed loading Main Menu Rig and started Avatar appearance application.."); } if (!flag) { ((Component)__instance.Avatar).gameObject.SetActive(false); } } catch (Exception value) { Melon<Main>.Logger.Error($"[MainMenuRigLoadStuffPatch] An error occurred in the Prefix: {value}"); return true; } return false; } private static bool TryBeginRigLoad(MainMenuRig rig) { if ((Object)(object)rig == (Object)null) { return false; } int instanceID = ((Object)rig).GetInstanceID(); lock (LoadedRigsLock) { if (LoadedRigs.Contains(instanceID)) { return false; } LoadedRigs.Add(instanceID); return true; } } public static void ResetLoadedRigs() { lock (LoadedRigsLock) { LoadedRigs.Clear(); } } } [HarmonyPatch(typeof(ClothingUtility), "Awake")] public static class ClothingUtilityAwakePatch { [HarmonyPrefix] public static void Prefix(ClothingUtility __instance) { try { List<SerializableColorData> list = ClothingDataService.LoadColorData(); if (list != null && list.Count != 0) { ClothingUtilitySeeder.Populate(__instance, list); } } catch (Exception value) { Melon<Main>.Logger.Warning($"[ClothingService] Failed to seed ClothingUtility colors before Awake: {value}"); } } } [HarmonyPatch(typeof(SceneManager), "LoadScene", new Type[] { typeof(string) })] public static class SceneManagerLoadScenePatch { private static string _lastSceneName = string.Empty; private static bool Prefix(string sceneName) { if (!string.IsNullOrEmpty(_lastSceneName) && (_lastSceneName.Equals(sceneName, StringComparison.OrdinalIgnoreCase) || _lastSceneName.Equals("Assets/Scenes/" + sceneName + ".unity", StringComparison.OrdinalIgnoreCase))) { return false; } _lastSceneName = sceneName; return true; } } } namespace HonestMainMenu.Models { [Serializable] [DataContract] public sealed class SerializableClothingFile { [DataMember(Name = "Items")] public List<string> Items { get; private set; } = new List<string>(); [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (Items == null) { List<string> list2 = (Items = new List<string>()); } } } [Serializable] [DataContract] public sealed class SerializableClothingSaveData { private const string ClothingDataType = "ClothingData"; private static readonly Type ColorEnumType = typeof(EClothingColor); private static readonly Array EnumValues = Enum.GetValues(ColorEnumType); private static readonly EClothingColor DefaultColor = (EClothingColor)((EnumValues.Length > 0) ? ((int)(EClothingColor)EnumValues.GetValue(0)) : 0); private static readonly HashSet<int> ValidColorValues = BuildValidColorSet(); [DataMember(Name = "DataType")] public string DataType { get; set; } = "ClothingData"; [DataMember(Name = "ID")] public string Id { get; set; } [DataMember(Name = "Color")] public int Color { get; set; } public bool IsValid { get { EClothingColor clothingColor; return TryGetValidColor(out clothingColor); } } [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (DataType == null) { string text2 = (DataType = "ClothingData"); } } public bool TryGetValidColor(out EClothingColor clothingColor) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected I4, but got Unknown if (!string.Equals(DataType, "ClothingData", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(Id)) { clothingColor = (EClothingColor)(int)DefaultColor; return false; } return TryGetColor(out clothingColor); } public bool TryGetColor(out EClothingColor clothingColor) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected I4, but got Unknown if (ValidColorValues.Contains(Color)) { clothingColor = (EClothingColor)Color; return true; } clothingColor = (EClothingColor)(int)DefaultColor; return false; } private static HashSet<int> BuildValidColorSet() { HashSet<int> hashSet = new HashSet<int>(); foreach (object enumValue in EnumValues) { hashSet.Add((int)enumValue); } return hashSet; } } [Serializable] [DataContract] public sealed class SerializableColor : IEquatable<SerializableColor> { private float _alpha = 1f; private bool _alphaExplicitlySet; [DataMember(Name = "r")] public float R { get; private set; } [DataMember(Name = "g")] public float G { get; private set; } [DataMember(Name = "b")] public float B { get; private set; } [DataMember(Name = "a")] public float A { get { return _alpha; } private set { _alpha = value; _alphaExplicitlySet = true; } } public SerializableColor() { } public SerializableColor(float r, float g, float b, float a = 1f) { R = r; G = g; B = b; A = a; } public SerializableColor(Color color) { //IL_0012: 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_002a: 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) R = color.r; G = color.g; B = color.b; A = color.a; } public Color ToUnityColor() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) return new Color(R, G, B, A); } [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (!_alphaExplicitlySet) { _alpha = 1f; } } public bool Equals(SerializableColor other) { if (other == null) { return false; } if (this == other) { return true; } if (R.Equals(other.R) && G.Equals(other.G) && B.Equals(other.B)) { return A.Equals(other.A); } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((SerializableColor)obj); } public override int GetHashCode() { return (((17 * 23 + R.GetHashCode()) * 23 + G.GetHashCode()) * 23 + B.GetHashCode()) * 23 + A.GetHashCode(); } } [Serializable] [DataContract] public sealed class SerializableColorData : IEquatable<SerializableColorData> { [NonSerialized] private Color? _cachedUnityColor; [DataMember(Name = "ColorType")] public int ColorType { get; private set; } [DataMember(Name = "ActualColor")] public SerializableColor ActualColor { get; private set; } = new SerializableColor(); public SerializableColorData() { } public SerializableColorData(int colorType, SerializableColor actualColor) { ColorType = colorType; ActualColor = actualColor ?? new SerializableColor(); } public SerializableColorData(int colorType, Color actualColor) : this(colorType, new SerializableColor(actualColor)) { }//IL_0002: Unknown result type (might be due to invalid IL or missing references) public SerializableColorData(EClothingColor colorType, Color actualColor) : this((int)colorType, actualColor) { }//IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (ActualColor == null) { SerializableColor serializableColor2 = (ActualColor = new SerializableColor()); } _cachedUnityColor = null; } public EClothingColor GetEnumColorType() { return (EClothingColor)ColorType; } public Color ToUnityColor() { //IL_0034: 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_0018: Unknown result type (might be due to invalid IL or missing references) if (!_cachedUnityColor.HasValue) { _cachedUnityColor = ActualColor?.ToUnityColor() ?? Color.white; } return _cachedUnityColor.Value; } public bool Equals(SerializableColorData other) { if (other == null) { return false; } if (this == other) { return true; } if (ColorType == other.ColorType) { return object.Equals(ActualColor, other.ActualColor); } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((SerializableColorData)obj); } public override int GetHashCode() { return (17 * 23 + ColorType.GetHashCode()) * 23 + (ActualColor?.GetHashCode() ?? 0); } } internal class MenuButtons { public Button ContinueButton { get; set; } public Button LoadGameButton { get; set; } } }
HonestMainMenu.Mono.dll
Decompiled a week agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using HarmonyLib; using HonestMainMenu; using HonestMainMenu.Models; using HonestMainMenu.Patches; using HonestMainMenu.Services; using MelonLoader; using MelonLoader.Utils; using ScheduleOne.AvatarFramework; using ScheduleOne.AvatarFramework.Customization; using ScheduleOne.Clothing; using ScheduleOne.DevUtilities; using ScheduleOne.Networking; using ScheduleOne.Persistence; using ScheduleOne.UI.Input; using ScheduleOne.UI.MainMenu; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(true)] [assembly: Guid("F2F8CCDD-DA07-47DF-9247-39B991BE39E3")] [assembly: MelonInfo(typeof(Main), "Honest Main Menu", "1.1.0", "Roach_ (Adrian Nicolae)", "https://github.com/RoachxD/ScheduleOne.HonestMainMenu/releases/latest")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonColor(255, 97, 100, 62)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Roach_")] [assembly: AssemblyConfiguration("Release_Mono")] [assembly: AssemblyCopyright("Copyright © 2025 Roach_ (Adrian Nicolae)")] [assembly: AssemblyDescription("A MelonLoader mod for Schedule I that adds a true continue button to the main menu.")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("HonestMainMenu.Mono")] [assembly: AssemblyTitle("HonestMainMenu.Mono")] [assembly: NeutralResourcesLanguage("en-GB")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] namespace HonestMainMenu { public class Main : MelonMod { public override void OnInitializeMelon() { string text = "Mono"; Melon<Main>.Logger.Msg("Honest Main Menu (" + text + ") initializing.."); try { ((MelonBase)this).HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); IEnumerable<string> values = from p in ((MelonBase)this).HarmonyInstance.GetPatchedMethods() select p.DeclaringType.FullName + "." + p.Name; string text2 = string.Join(", ", values); Melon<Main>.Logger.Msg("Honest Main Menu initialized successfully!"); Melon<Main>.Logger.Msg("Harmony patches successfully applied: " + text2 + "."); } catch (Exception ex) { Melon<Main>.Logger.Error("Failed to apply Harmony patches: " + ex.Message); Melon<Main>.Logger.Error((object)ex); } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (!sceneName.Equals("Menu", StringComparison.OrdinalIgnoreCase)) { HandleNonMenuScene(sceneName); return; } MainMenuRigLoadStuffPatch.ResetLoadedRigs(); HandleMenuScene(); } private static void HandleNonMenuScene(string sceneName) { Melon<Main>.Logger.Msg("Scene '" + sceneName + "' loaded outside the main menu. Stopping menu reactivity and snapshotting live clothing colors."); MenuReactivity.Stop(); MainMenuRigLoadStuffPatch.ResetLoadedRigs(); ClothingUtility instance = Singleton<ClothingUtility>.Instance; if (instance?.ColorDataList != null) { ClothingDataService.ExtractAndSaveColorData(instance.ColorDataList); Melon<Main>.Logger.Msg($"Captured {instance.ColorDataList.Count} clothing colors before leaving the menu scene."); } else { Melon<Main>.Logger.Warning("[Main.OnSceneWasLoaded] ClothingUtility or its ColorDataList is null, skipping color data extraction."); } } private static void HandleMenuScene() { Melon<Main>.Logger.Msg("Main menu scene ('Menu') loaded. Attempting UI modifications.."); GameObject val = GameObject.Find("MainMenu"); if ((Object)(object)val == (Object)null) { Melon<Main>.Logger.Error("Could not find main menu root object 'MainMenu'. Aborting UI modifications."); return; } Transform transform = val.transform; BackButtonPromptSetup.Apply(transform); MenuButtons menuButtons = MenuSetup.Build(transform); if (menuButtons != null) { MelonCoroutines.Start(MenuReactivity.Run(menuButtons)); ContinueScreenSetup.Apply(transform); Melon<Main>.Logger.Msg("Honest Main Menu UI modifications applied successfully."); } } } public static class UIConstants { public const string MenuSceneName = "Menu"; public const string MainMenuObjectName = "MainMenu"; public const string MenuButtonsParentPath = "Home/Bank"; public const string ContinueObjectNameAndLabel = "Continue"; public const string LoadGameObjectName = "LoadGame"; public const string InputPromptObjectName = "InputPrompt (Back)"; public const string LoadGameLabel = "Load Game"; public const string RmbPromptBindingKey = "rightButton"; public const string TitleObjectName = "Title"; } public static class UIHelper { public static void SetText(this GameObject gameObject, string text) { if (!((Object)(object)gameObject == (Object)null)) { TextMeshProUGUI componentInChildren = gameObject.GetComponentInChildren<TextMeshProUGUI>(true); if (!((Object)(object)componentInChildren == (Object)null)) { ((TMP_Text)componentInChildren).text = text; } } } public static bool TryFindChild(Transform parent, string childPath, out Transform child, string failureMessage, bool logWarning = false) { child = ((parent != null) ? parent.Find(childPath) : null); if ((Object)(object)child != (Object)null) { return true; } if (logWarning) { Melon<Main>.Logger.Warning(failureMessage); } else { Melon<Main>.Logger.Error(failureMessage); } return false; } } } namespace HonestMainMenu.Services { internal static class BackButtonPromptSetup { public static void Apply(Transform menuRoot) { if (TryGetInputPrompt(menuRoot, out var promptComponent, out var ownerObject) && RemoveActionsMatchingBinding(promptComponent, "rightButton")) { RefreshPrompt(promptComponent, ownerObject); } } private static bool TryGetInputPrompt(Transform mainMenuRoot, out InputPrompt promptComponent, out GameObject ownerObject) { promptComponent = null; Transform obj = mainMenuRoot.Find("InputPrompt (Back)"); ownerObject = ((obj != null) ? ((Component)obj).gameObject : null); if ((Object)(object)ownerObject == (Object)null) { Melon<Main>.Logger.Warning("Could not find InputPrompt owner GameObject at 'InputPrompt (Back)'."); return false; } promptComponent = ownerObject.GetComponent<InputPrompt>(); if ((Object)(object)promptComponent == (Object)null) { Melon<Main>.Logger.Warning("Could not find 'ScheduleOne.UI.Input.InputPrompt' component on 'InputPrompt (Back)'. Make sure the type name is correct."); return false; } return true; } private static bool RemoveActionsMatchingBinding(InputPrompt promptComponent, string bindingKeyToRemove) { return promptComponent.Actions.RemoveAll((InputActionReference actionRef) => ShouldRemoveAction(actionRef, bindingKeyToRemove)) > 0; } private static bool ShouldRemoveAction(InputActionReference actionRef, string bindingKeyToRemove) { if (((actionRef != null) ? actionRef.action : null) == null) { return false; } string text = default(string); string value = default(string); InputActionRebindingExtensions.GetBindingDisplayString(actionRef.action, 0, ref text, ref value, (DisplayStringOptions)0); return bindingKeyToRemove.Equals(value, StringComparison.OrdinalIgnoreCase); } private static void RefreshPrompt(InputPrompt promptComponent, GameObject promptOwnerObject) { if (promptOwnerObject.activeInHierarchy) { promptOwnerObject.SetActive(false); promptOwnerObject.SetActive(true); } } } internal interface IClothingApplicator { IEnumerator Apply(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings); } internal static class ClothingApplicatorFactory { internal static IClothingApplicator Create() { return new MonoClothingApplicator(); } } internal abstract class ClothingApplicatorBase : IClothingApplicator { protected static readonly Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); protected static readonly DataContractJsonSerializerSettings SerializerSettings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }; protected static readonly DataContractJsonSerializer ClothingFileSerializer = new DataContractJsonSerializer(typeof(SerializableClothingFile), SerializerSettings); protected static readonly DataContractJsonSerializer ClothingItemSerializer = new DataContractJsonSerializer(typeof(SerializableClothingSaveData), SerializerSettings); public abstract IEnumerator Apply(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings); protected static SerializableClothingFile LoadClothingFile(string playerSavePath) { string text = Path.Combine(playerSavePath, "Clothing.json"); if (!File.Exists(text)) { Melon<Main>.Logger.Warning("[ClothingApplicator] Clothing file not found at " + text + ". Skipping."); return null; } try { using FileStream fileStream = File.OpenRead(text); return ((XmlObjectSerializer)ClothingFileSerializer).ReadObject((Stream)fileStream) as SerializableClothingFile; } catch (Exception arg) { Melon<Main>.Logger.Error($"[ClothingApplicator] Failed to read clothing file at {text}: {arg}"); return null; } } protected static SerializableClothingSaveData DeserializeClothingItem(string json, string sourcePath) { if (string.IsNullOrWhiteSpace(json)) { Melon<Main>.Logger.Warning("[ClothingApplicator] Empty clothing item JSON found in '" + sourcePath + "', skipping."); return null; } try { using MemoryStream memoryStream = new MemoryStream(Utf8.GetBytes(json)); return ((XmlObjectSerializer)ClothingItemSerializer).ReadObject((Stream)memoryStream) as SerializableClothingSaveData; } catch (Exception arg) { Melon<Main>.Logger.Error($"[ClothingApplicator] Failed to deserialize clothing item ({sourcePath}): {arg}"); return null; } } } internal sealed class MonoClothingApplicator : ClothingApplicatorBase { public override IEnumerator Apply(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings) { SerializableClothingFile serializableClothingFile = ClothingApplicatorBase.LoadClothingFile(playerSavePath); if (serializableClothingFile?.Items == null || serializableClothingFile.Items.Count == 0) { Melon<Main>.Logger.Warning("[ClothingApplicator] Clothing file at " + playerSavePath + " was empty or missing."); yield break; } AvatarSettings val = Object.Instantiate<AvatarSettings>(basicSettings.GetAvatarSettings()); val.BodyLayerSettings.Clear(); val.AccessorySettings.Clear(); int num = 0; foreach (string item in serializableClothingFile.Items) { if (TryApplyItem(val, item, playerSavePath)) { num++; } } avatar.LoadAvatarSettings(val); if (num > 0) { Melon<Main>.Logger.Msg($"[ClothingApplicator] Applied {num} clothing items to the main menu Avatar."); } else { Melon<Main>.Logger.Warning("[ClothingApplicator] No valid clothing items were applied to the main menu Avatar."); } } private static bool TryApplyItem(AvatarSettings avatarSettings, string itemJson, string sourcePath) { //IL_0074: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Expected O, but got Unknown //IL_008c: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) SerializableClothingSaveData serializableClothingSaveData = ClothingApplicatorBase.DeserializeClothingItem(itemJson, sourcePath); if (serializableClothingSaveData == null) { return false; } if (!string.Equals(serializableClothingSaveData.DataType, "ClothingData", StringComparison.OrdinalIgnoreCase) || !serializableClothingSaveData.TryGetValidColor(out var clothingColor)) { return false; } if (!ClothingDefinitionResolver.TryResolve(serializableClothingSaveData, out var definition, out var _)) { Melon<Main>.Logger.Warning("[ClothingApplicator] Clothing item not found in registry: " + serializableClothingSaveData.Id + " (source: " + sourcePath + ")"); return false; } Color actualColor = ClothingColorExtensions.GetActualColor(clothingColor); if ((int)definition.ApplicationType == 0) { avatarSettings.BodyLayerSettings.Add(new LayerSetting { layerPath = definition.ClothingAssetPath, layerTint = actualColor }); } else { avatarSettings.AccessorySettings.Add(new AccessorySetting { path = definition.ClothingAssetPath, color = actualColor }); } return true; } } public static class ClothingDataService { public static IEnumerator ApplyClothingCoroutine(Avatar avatar, string playerSavePath, BasicAvatarSettings basicSettings) { if ((Object)(object)avatar == (Object)null || (Object)(object)basicSettings == (Object)null || string.IsNullOrEmpty(playerSavePath)) { Melon<Main>.Logger.Warning("[ClothingDataService] Missing data needed for clothing application."); yield break; } ClothingUtilitySeeder.EnsureWithColors(ColorDataRepository.Load()); IClothingApplicator clothingApplicator = ClothingApplicatorFactory.Create(); yield return clothingApplicator.Apply(avatar, playerSavePath, basicSettings); } public static void ExtractAndSaveColorData(List<ColorData> liveDataList) { ColorDataRepository.SaveLiveColors(liveDataList); } public static List<SerializableColorData> LoadColorData() { return ColorDataRepository.Load(); } } internal static class ClothingDefinitionResolver { private sealed class MonoRegistryAdapter { private static MonoRegistryAdapter _cached; private readonly Dictionary<string, object> _registers; private readonly Dictionary<string, string> _aliases; private MonoRegistryAdapter(Dictionary<string, object> registers, Dictionary<string, string> aliases) { _registers = registers; _aliases = aliases; } internal static MonoRegistryAdapter GetOrCreate() { if (_cached != null) { return _cached; } Type type = Type.GetType("ScheduleOne.Registry, Assembly-CSharp"); if (type == null) { return null; } object obj = (type.BaseType?.BaseType?.GetField("instance", BindingFlags.Static | BindingFlags.NonPublic))?.GetValue(null); if (obj == null) { return null; } Dictionary<string, object> registers = BuildRegisterSnapshot(obj, type); Dictionary<string, string> aliases = BuildAliasSnapshot(obj, type); _cached = new MonoRegistryAdapter(registers, aliases); return _cached; } private static Dictionary<string, object> BuildRegisterSnapshot(object registryInstance, Type registryType) { Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); if (registryType.GetField("ItemDictionary", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(registryInstance) is IDictionary dictionary2) { foreach (DictionaryEntry item in dictionary2) { object value = item.Value; string fieldValue = GetFieldValue<string>(value, "ID", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (!string.IsNullOrEmpty(fieldValue) && !dictionary.ContainsKey(fieldValue)) { dictionary[fieldValue] = value; } } } return dictionary; } private static Dictionary<string, string> BuildAliasSnapshot(object registryInstance, Type registryType) { if (!(registryType.GetField("itemIDAliases", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(registryInstance) is IDictionary dictionary)) { return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (DictionaryEntry item in dictionary) { if (item.Key is string key && item.Value is string value) { dictionary2[key] = value; } } return dictionary2; } internal ClothingDefinition TryGetDefinition(string id) { string id2 = ResolveAlias(id); object obj = FindRegister(id2); if (obj == null) { return null; } return GetFieldValue<ClothingDefinition>(obj, "Definition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); } internal string ResolveAlias(string id) { if (string.IsNullOrEmpty(id)) { return id; } if (!_aliases.TryGetValue(id, out var value)) { return id; } return value; } internal object FindRegister(string id) { if (string.IsNullOrEmpty(id)) { return null; } if (!_registers.TryGetValue(id, out var value)) { return null; } return value; } internal static T GetFieldValue<T>(object instance, string fieldName, BindingFlags flags) { if (instance == null || string.IsNullOrEmpty(fieldName)) { return default(T); } FieldInfo field = instance.GetType().GetField(fieldName, flags); if (field == null) { return default(T); } object value = field.GetValue(instance); if (value is T) { return (T)value; } return default(T); } } internal unsafe static bool TryResolve(SerializableClothingSaveData clothingData, out ClothingDefinition definition, out EClothingColor resolvedColor) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected I4, but got Unknown //IL_001c->IL001c: Incompatible stack types: Ref vs I4 //IL_0017->IL001c: Incompatible stack types: I4 vs Ref //IL_0017->IL001c: Incompatible stack types: Ref vs I4 definition = TryGetDefinitionFromMonoRegistry(clothingData.Id); ref EClothingColor reference = ref resolvedColor; ClothingDefinition obj = definition; int num; if (obj != null) { reference = ref *(EClothingColor*)obj.DefaultColor; num = (int)(ref reference); } else { num = 0; reference = ref *(EClothingColor*)num; num = (int)(ref reference); } *(int*)num = (int)System.Runtime.CompilerServices.Unsafe.AsPointer(ref reference); if ((Object)(object)definition != (Object)null) { if (clothingData.TryGetValidColor(out var clothingColor)) { resolvedColor = (EClothingColor)(int)clothingColor; } return true; } return TryResolveFromMonoRegister(clothingData, out definition, out resolvedColor); } private static ClothingDefinition TryGetDefinitionFromMonoRegistry(string id) { try { return MonoRegistryAdapter.GetOrCreate()?.TryGetDefinition(id); } catch (Exception arg) { Melon<Main>.Logger.Warning($"[ClothingDefinitionResolver] Failed to resolve clothing definition via reflection: {arg}"); return null; } } private unsafe static bool TryResolveFromMonoRegister(SerializableClothingSaveData clothingData, out ClothingDefinition definition, out EClothingColor resolvedColor) { //IL_0011: 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_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_0012->IL0012: Incompatible stack types: Ref vs I4 //IL_0011->IL0012: Incompatible stack types: I4 vs Ref //IL_0011->IL0012: Incompatible stack types: Ref vs I4 definition = null; ref EClothingColor reference = ref resolvedColor; int num; if (clothingData.TryGetValidColor(out var clothingColor)) { reference = ref *(EClothingColor*)clothingColor; num = (int)(ref reference); } else { num = 0; reference = ref *(EClothingColor*)num; num = (int)(ref reference); } *(int*)num = (int)System.Runtime.CompilerServices.Unsafe.AsPointer(ref reference); MonoRegistryAdapter orCreate = MonoRegistryAdapter.GetOrCreate(); if (orCreate == null) { return false; } string id = orCreate.ResolveAlias(clothingData.Id); object obj = orCreate.FindRegister(id); if (obj == null) { return false; } ClothingDefinition fieldValue = MonoRegistryAdapter.GetFieldValue<ClothingDefinition>(obj, "Definition", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((Object)(object)fieldValue != (Object)null) { definition = fieldValue; return true; } string text = NormalizeRegistryAssetPath(MonoRegistryAdapter.GetFieldValue<string>(obj, "AssetPath", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)); if (string.IsNullOrEmpty(text)) { return false; } definition = Resources.Load<ClothingDefinition>(text); if ((Object)(object)definition != (Object)null) { return true; } if (TryMapItemDefinition(text, out var avatarPath, out var applicationType) && ResourceExists(avatarPath)) { definition = ScriptableObject.CreateInstance<ClothingDefinition>(); definition.ClothingAssetPath = avatarPath; definition.ApplicationType = applicationType; definition.Colorable = true; return true; } return false; } private static string NormalizeRegistryAssetPath(string assetPath) { if (string.IsNullOrWhiteSpace(assetPath)) { return null; } string text = assetPath.Replace("\\", "/"); if (text.StartsWith("Assets/Resources/", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("Assets/Resources/".Length); } if (text.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - ".asset".Length); } return text.Trim(new char[1] { '/' }); } private static bool TryMapItemDefinition(string normalizedPath, out string avatarPath, out EClothingApplicationType applicationType) { avatarPath = null; applicationType = (EClothingApplicationType)2; if (string.IsNullOrWhiteSpace(normalizedPath)) { return false; } string[] array = normalizedPath.Split(new char[1] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 3 || !array[0].Equals("Clothing", StringComparison.OrdinalIgnoreCase)) { return false; } if (array.Length >= 4 && array[1].Equals("Accessories", StringComparison.OrdinalIgnoreCase)) { string text = array[2]; string text2 = array[3]; avatarPath = "Avatar/Accessories/" + text + "/" + text2 + "/" + text2; applicationType = (EClothingApplicationType)2; return true; } string text3 = array[1]; string text4 = array[2]; if (IsAccessoryCategory(text3)) { avatarPath = "Avatar/Accessories/" + text3 + "/" + text4 + "/" + text4; applicationType = (EClothingApplicationType)2; return true; } applicationType = (EClothingApplicationType)0; avatarPath = "Avatar/Layers/" + text3 + "/" + text4; return true; } private static bool IsAccessoryCategory(string category) { if (!category.Equals("Head", StringComparison.OrdinalIgnoreCase) && !category.Equals("Face", StringComparison.OrdinalIgnoreCase) && !category.Equals("Feet", StringComparison.OrdinalIgnoreCase) && !category.Equals("Hands", StringComparison.OrdinalIgnoreCase) && !category.Equals("Neck", StringComparison.OrdinalIgnoreCase) && !category.Equals("Back", StringComparison.OrdinalIgnoreCase)) { return category.Equals("Accessory", StringComparison.OrdinalIgnoreCase); } return true; } private static bool ResourceExists(string resourcePath) { if (string.IsNullOrEmpty(resourcePath)) { return false; } return Resources.Load<Object>(resourcePath) != (Object)null; } } internal static class ClothingUtilitySeeder { private static int? _lastHash; internal static ClothingUtility EnsureWithColors(IReadOnlyList<SerializableColorData> colors) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) ClothingUtility val = Singleton<ClothingUtility>.Instance; if ((Object)(object)val == (Object)null) { Melon<Main>.Logger.Msg("[ClothingUtilitySeeder] ClothingUtility instance not found, creating a new one."); val = new GameObject("@Clothing").AddComponent<ClothingUtility>(); } Populate(val, colors); return val; } internal static void Populate(ClothingUtility clothingUtility, IReadOnlyList<SerializableColorData> colors) { if ((Object)(object)clothingUtility == (Object)null) { Melon<Main>.Logger.Error("[ClothingUtilitySeeder] Cannot populate colors because ClothingUtility is null."); } else if (colors == null || colors.Count == 0) { Melon<Main>.Logger.Error("[ClothingUtilitySeeder] No color data provided; ClothingUtility will remain unchanged."); } else if (!IsAlreadySeeded(clothingUtility, colors)) { PopulateColorList(clothingUtility, colors); _lastHash = ComputeHash(colors); } } private static void PopulateColorList(ClothingUtility clothingUtility, IReadOnlyList<SerializableColorData> colors) { //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) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0045: 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) //IL_004f: 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) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown if (clothingUtility.ColorDataList == null) { clothingUtility.ColorDataList = new List<ColorData>(); } List<ColorData> colorDataList = clothingUtility.ColorDataList; colorDataList.Clear(); foreach (SerializableColorData color in colors) { if (color != null) { Color actualColor = color.ToUnityColor(); colorDataList.Add(new ColorData { ColorType = color.GetEnumColorType(), ActualColor = actualColor }); } } } private static bool IsAlreadySeeded(ClothingUtility clothingUtility, IReadOnlyList<SerializableColorData> colors) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: 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) if (clothingUtility?.ColorDataList == null || colors == null) { return false; } if (colors.Count == 0) { return false; } int num = ComputeHash(colors); if (_lastHash.HasValue && _lastHash.Value == num) { return true; } if (clothingUtility.ColorDataList.Count != colors.Count) { return false; } for (int i = 0; i < colors.Count; i++) { ColorData val = clothingUtility.ColorDataList[i]; SerializableColorData serializableColorData = colors[i]; if (val == null || serializableColorData == null) { return false; } if (val.ColorType != serializableColorData.GetEnumColorType()) { return false; } if (val.ActualColor != serializableColorData.ToUnityColor()) { return false; } } _lastHash = num; return true; } private static int ComputeHash(IReadOnlyList<SerializableColorData> colors) { //IL_0038: 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_003d: Unknown result type (might be due to invalid IL or missing references) int num = 17; for (int i = 0; i < colors.Count; i++) { SerializableColorData serializableColorData = colors[i]; num = num * 31 + (serializableColorData?.ColorType.GetHashCode() ?? 0); Color val = serializableColorData?.ToUnityColor() ?? Color.white; num = num * 31 + val.r.GetHashCode(); num = num * 31 + val.g.GetHashCode(); num = num * 31 + val.b.GetHashCode(); num = num * 31 + val.a.GetHashCode(); } return num; } } internal static class ColorDataRepository { private const string ResourceName = "HonestMainMenu.Resources.ColorData.json"; private static readonly Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static readonly DataContractJsonSerializerSettings SerializerSettings = new DataContractJsonSerializerSettings { UseSimpleDictionaryFormat = true }; private static readonly DataContractJsonSerializer ColorListSerializer = new DataContractJsonSerializer(typeof(List<SerializableColorData>), SerializerSettings); internal static string UserDataPath => Path.Combine(MelonEnvironment.MelonBaseDirectory, "Mods", "HonestMainMenu"); internal static string ColorDataFilePath => Path.Combine(UserDataPath, "ColorData.json"); internal static List<SerializableColorData> Load() { if (File.Exists(ColorDataFilePath)) { try { using FileStream stream = File.OpenRead(ColorDataFilePath); return ReadColorList(stream, ColorDataFilePath); } catch (Exception arg) { Melon<Main>.Logger.Error($"[ColorDataRepository] Failed to read custom color data '{ColorDataFilePath}': {arg}"); } } Melon<Main>.Logger.Msg("[ColorDataRepository] Custom color data not found or unreadable. Loading embedded defaults."); return LoadEmbeddedDefaults(); } internal static void SaveLiveColors(List<ColorData> liveDataList) { //IL_0039: 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) if (liveDataList == null) { Melon<Main>.Logger.Warning("[ColorDataRepository] No live color data provided; skipping save."); return; } try { List<SerializableColorData> list = new List<SerializableColorData>(liveDataList.Count); foreach (ColorData liveData in liveDataList) { if (liveData != null) { list.Add(new SerializableColorData(liveData.ColorType, liveData.ActualColor)); } } List<SerializableColorData> second = LoadEmbeddedDefaults(cloneList: false); if (!list.SequenceEqual(second)) { Melon<Main>.Logger.Warning("[ColorDataRepository] Live color data differs from embedded defaults. Consider updating Resources/ColorData.json."); } else { Melon<Main>.Logger.Msg("[ColorDataRepository] Live color data matches embedded defaults."); } EnsureUserDataDirectory(); string contents = SerializeColors(list); File.WriteAllText(ColorDataFilePath, contents, Utf8); Melon<Main>.Logger.Msg($"[ColorDataRepository] Saved {list.Count} colors to '{ColorDataFilePath}'."); } catch (Exception arg) { Melon<Main>.Logger.Error($"[ColorDataRepository] Failed to save live color data: {arg}"); } } internal static List<SerializableColorData> LoadEmbeddedDefaults(bool cloneList = true) { try { using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("HonestMainMenu.Resources.ColorData.json"); if (stream == null) { Melon<Main>.Logger.Error("[ColorDataRepository] Embedded resource 'HonestMainMenu.Resources.ColorData.json' not found."); return new List<SerializableColorData>(); } List<SerializableColorData> list = ReadColorList(stream, "HonestMainMenu.Resources.ColorData.json"); return cloneList ? new List<SerializableColorData>(list) : list; } catch (Exception arg) { Melon<Main>.Logger.Error(string.Format("[ColorDataRepository] Failed to read embedded color data '{0}': {1}", "HonestMainMenu.Resources.ColorData.json", arg)); return new List<SerializableColorData>(); } } private static List<SerializableColorData> ReadColorList(Stream stream, string source) { try { return Normalize(((XmlObjectSerializer)ColorListSerializer).ReadObject(stream) as List<SerializableColorData>); } catch (Exception arg) { Melon<Main>.Logger.Error($"[ColorDataRepository] Failed to deserialize color data ({source}): {arg}"); return new List<SerializableColorData>(); } } private static List<SerializableColorData> Normalize(List<SerializableColorData> rawList) { return rawList?.Where((SerializableColorData entry) => entry != null && entry.ActualColor != null).ToList() ?? new List<SerializableColorData>(); } private static string SerializeColors(IReadOnlyList<SerializableColorData> colors) { using MemoryStream memoryStream = new MemoryStream(); ((XmlObjectSerializer)ColorListSerializer).WriteObject((Stream)memoryStream, (object)colors); return Utf8.GetString(memoryStream.ToArray()); } private static void EnsureUserDataDirectory() { if (!Directory.Exists(UserDataPath)) { Directory.CreateDirectory(UserDataPath); } } } internal static class ContinueScreenSetup { public static void Apply(Transform mainMenuRoot) { if (UIHelper.TryFindChild(mainMenuRoot, "Continue", out var child, "Could not find 'MainMenu/Continue' screen object. Aborting screen configuration.")) { ((Object)((Component)child).gameObject).name = "LoadGame"; if (UIHelper.TryFindChild(child, "Title", out var child2, "Could not find 'Title' child GameObject under '" + ((Object)child).name + "'. The screen title text will not be updated.")) { ((Component)child2).gameObject.SetText("Load Game"); } } } } internal static class MenuReactivity { private static UnityAction _onSaveInfoLoaded; private static bool _continueActionHooked; private const string ContinueErrorTitle = "Error"; private const string ContinueErrorMessage = "An error occurred while trying to continue the last played game. Please try loading it manually from the Load Game menu."; public static IEnumerator Run(MenuButtons menuButtons) { yield return (object)new WaitUntil((Func<bool>)(() => (Object)(object)Singleton<LoadManager>.Instance != (Object)null)); if (LoadManager.LastPlayedGame != null) { UpdateContinueButtonInteractableState(menuButtons); } else if (_onSaveInfoLoaded == null) { _onSaveInfoLoaded = (UnityAction)delegate { Melon<Main>.Logger.Msg("LoadManager.OnSaveInfoLoaded event triggered, updating buttons interactable states."); UpdateContinueButtonInteractableState(menuButtons); DetachSaveInfoListener(); }; Singleton<LoadManager>.Instance.onSaveInfoLoaded.AddListener(_onSaveInfoLoaded); } } public static void Stop() { DetachSaveInfoListener(); _continueActionHooked = false; } private static void UpdateContinueButtonInteractableState(MenuButtons menuButtons) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown if (!_continueActionHooked) { ((UnityEvent)menuButtons.ContinueButton.onClick).AddListener(new UnityAction(PerformNewContinueAction)); _continueActionHooked = true; } ((Selectable)menuButtons.ContinueButton).interactable = true; ((Selectable)menuButtons.LoadGameButton).interactable = true; } private static void PerformNewContinueAction() { if (!Singleton<Lobby>.Instance.IsHost) { Singleton<MainMenuPopup>.Instance.Open("Cannot Continue", "You must be the host in order to be able to continue a game.", true); return; } try { Singleton<LoadManager>.Instance.StartGame(LoadManager.LastPlayedGame, false, true); } catch (MissingMethodException ex) when (ex.Message.Contains("StartGame") || (ex.StackTrace?.Contains("LoadManager.StartGame") ?? false)) { try { Singleton<LoadManager>.Instance.StartGame(LoadManager.LastPlayedGame, false, true); Melon<Main>.Logger.Warning($"Detected missing method exception while trying to continue the last played game: {ex}. " + "This is likely due to an outdated game build. Please update to the latest version. Continuing with the older method signature."); } catch (Exception exception) { ShowContinueFailureDialog("Fallback StartGame call also failed after MissingMethodException", exception); } } catch (Exception exception2) { ShowContinueFailureDialog("An error occurred while trying to continue the last played game", exception2); } } private static void ShowContinueFailureDialog(string context, Exception exception) { Melon<Main>.Logger.Error($"{context}: {exception}"); Singleton<MainMenuPopup>.Instance.Open("Error", "An error occurred while trying to continue the last played game. Please try loading it manually from the Load Game menu.", true); } private static void DetachSaveInfoListener() { if (_onSaveInfoLoaded != null && !((Object)(object)Singleton<LoadManager>.Instance == (Object)null)) { Singleton<LoadManager>.Instance.onSaveInfoLoaded.RemoveListener(_onSaveInfoLoaded); _onSaveInfoLoaded = null; } } } internal static class MenuSetup { public static MenuButtons Build(Transform menuRoot) { if (!UIHelper.TryFindChild(menuRoot, "Home/Bank", out var child, "Could not find menu buttons parent object at 'MainMenu/Home/Bank', aborting UI modifications!")) { return null; } if (!UIHelper.TryFindChild(child, "Continue", out var child2, "Could not find the original 'Continue' button object, aborting UI modifications!")) { return null; } MenuButtons menuButtons = new MenuButtons { ContinueButton = CreateAndConfigureNewContinueButton(((Component)child2).gameObject), LoadGameButton = null }; if ((Object)(object)menuButtons.ContinueButton == (Object)null) { Melon<Main>.Logger.Error("Failed to create and configure the new 'Continue' button, aborting UI modifications!"); return null; } menuButtons.LoadGameButton = ConfigureLoadGameButton(((Component)child2).gameObject); if ((Object)(object)menuButtons.LoadGameButton == (Object)null) { Melon<Main>.Logger.Error("Failed to configure the 'LoadGame' button, aborting UI modifications!"); return null; } ((Selectable)menuButtons.ContinueButton).interactable = false; ((Selectable)menuButtons.LoadGameButton).interactable = false; PositionButtonsAndOffsetSiblings(menuButtons); return menuButtons; } private static Button ConfigureLoadGameButton(GameObject buttonObject) { Button component = buttonObject.GetComponent<Button>(); if ((Object)(object)component == (Object)null) { return null; } ((Object)buttonObject).name = "LoadGame"; buttonObject.SetText("Load Game"); return component; } private static Button CreateAndConfigureNewContinueButton(GameObject buttonObject) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown GameObject val = Object.Instantiate<GameObject>(buttonObject, buttonObject.transform.parent); ((Object)val).name = "Continue"; val.SetText("Continue"); Button component = val.GetComponent<Button>(); if ((Object)(object)component == (Object)null) { Object.Destroy((Object)(object)val); return null; } component.onClick = new ButtonClickedEvent(); return component; } private static void PositionButtonsAndOffsetSiblings(MenuButtons menuButtons) { //IL_0022: 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_0033: 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_0046: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: 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) if (!TryGetRects(menuButtons, out var loadGameRect, out var continueRect, out var parentTransform, out var originalIndex)) { Object.Destroy((Object)(object)((Component)menuButtons.ContinueButton).gameObject); return; } Rect rect = continueRect.rect; float height = ((Rect)(ref rect)).height; Vector2 anchoredPosition = loadGameRect.anchoredPosition; PlaceButton(((Component)menuButtons.ContinueButton).gameObject, continueRect, anchoredPosition, originalIndex); PlaceButton(((Component)menuButtons.LoadGameButton).gameObject, loadGameRect, anchoredPosition + new Vector2(0f, 0f - height), originalIndex + 1); OffsetRemainingSiblings(parentTransform, originalIndex + 2, height); Melon<Main>.Logger.Msg("Positioned new 'Continue' at original spot, shifted 'LoadGame' (Load Game) below it, and offset subsequent buttons."); } private static bool TryGetRects(MenuButtons menuButtons, out RectTransform loadGameRect, out RectTransform continueRect, out Transform parentTransform, out int originalIndex) { loadGameRect = ((Component)menuButtons.LoadGameButton).gameObject.GetComponent<RectTransform>(); continueRect = ((Component)menuButtons.ContinueButton).gameObject.GetComponent<RectTransform>(); parentTransform = ((Component)menuButtons.LoadGameButton).gameObject.transform.parent; originalIndex = ((Component)menuButtons.LoadGameButton).gameObject.transform.GetSiblingIndex(); if ((Object)(object)loadGameRect != (Object)null && (Object)(object)continueRect != (Object)null && (Object)(object)parentTransform != (Object)null) { return true; } Melon<Main>.Logger.Error("Could not get RectTransforms or parent for positioning. Destroying new button."); return false; } private static void PlaceButton(GameObject buttonObject, RectTransform rectTransform, Vector2 anchoredPosition, int siblingIndex) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) rectTransform.anchoredPosition = anchoredPosition; buttonObject.transform.SetSiblingIndex(siblingIndex); } private static void OffsetRemainingSiblings(Transform parentTransform, int startIndex, float offset) { //IL_0016: 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_002d: Unknown result type (might be due to invalid IL or missing references) for (int i = startIndex; i < parentTransform.childCount; i++) { RectTransform component = ((Component)parentTransform.GetChild(i)).GetComponent<RectTransform>(); if (component != null) { component.anchoredPosition = new Vector2(component.anchoredPosition.x, component.anchoredPosition.y - offset); } } } } } namespace HonestMainMenu.Patches { [HarmonyPatch(typeof(MainMenuRig), "LoadStuff")] public static class MainMenuRigLoadStuffPatch { private static readonly HashSet<int> LoadedRigs = new HashSet<int>(); private static readonly object LoadedRigsLock = new object(); public static bool Prefix(MainMenuRig __instance) { try { if (!TryBeginRigLoad(__instance)) { return false; } bool flag = false; if (LoadManager.LastPlayedGame != null) { string text = Path.Combine(LoadManager.LastPlayedGame.SavePath, "Players", "Player_0"); string path = Path.Combine(text, "Appearance.json"); BasicAvatarSettings val = ScriptableObject.CreateInstance<BasicAvatarSettings>(); if (File.Exists(path)) { JsonUtility.FromJsonOverwrite(File.ReadAllText(path), (object)val); __instance.Avatar.LoadAvatarSettings(val.GetAvatarSettings()); MelonCoroutines.Start(ClothingDataService.ApplyClothingCoroutine(__instance.Avatar, text, val)); flag = true; } else { Melon<Main>.Logger.Warning("[MainMenuRigLoadStuffPatch] Appearance.json not found, skipping avatar appearance load."); } float num = LoadManager.LastPlayedGame.Networth; for (int i = 0; i < __instance.CashPiles.Length; i++) { float displayedAmount = Mathf.Clamp(num, 0f, 100000f); __instance.CashPiles[i].SetDisplayedAmount(displayedAmount); num -= 100000f; if (num <= 0f) { break; } } Melon<Main>.Logger.Msg("[MainMenuRigLoadStuffPatch] Completed loading Main Menu Rig and started Avatar appearance application.."); } if (!flag) { ((Component)__instance.Avatar).gameObject.SetActive(false); } } catch (Exception arg) { Melon<Main>.Logger.Error($"[MainMenuRigLoadStuffPatch] An error occurred in the Prefix: {arg}"); return true; } return false; } private static bool TryBeginRigLoad(MainMenuRig rig) { if ((Object)(object)rig == (Object)null) { return false; } int instanceID = ((Object)rig).GetInstanceID(); lock (LoadedRigsLock) { if (LoadedRigs.Contains(instanceID)) { return false; } LoadedRigs.Add(instanceID); return true; } } public static void ResetLoadedRigs() { lock (LoadedRigsLock) { LoadedRigs.Clear(); } } } [HarmonyPatch(typeof(SceneManager), "LoadScene", new Type[] { typeof(string) })] public static class SceneManagerLoadScenePatch { private static string _lastSceneName = string.Empty; private static bool Prefix(string sceneName) { if (!string.IsNullOrEmpty(_lastSceneName) && (_lastSceneName.Equals(sceneName, StringComparison.OrdinalIgnoreCase) || _lastSceneName.Equals("Assets/Scenes/" + sceneName + ".unity", StringComparison.OrdinalIgnoreCase))) { return false; } _lastSceneName = sceneName; return true; } } } namespace HonestMainMenu.Models { [Serializable] [DataContract] public sealed class SerializableClothingFile { [DataMember(Name = "Items")] public List<string> Items { get; private set; } = new List<string>(); [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (Items == null) { List<string> list2 = (Items = new List<string>()); } } } [Serializable] [DataContract] public sealed class SerializableClothingSaveData { private const string ClothingDataType = "ClothingData"; private static readonly Type ColorEnumType = typeof(EClothingColor); private static readonly Array EnumValues = Enum.GetValues(ColorEnumType); private static readonly EClothingColor DefaultColor = (EClothingColor)((EnumValues.Length > 0) ? ((int)(EClothingColor)EnumValues.GetValue(0)) : 0); private static readonly HashSet<int> ValidColorValues = BuildValidColorSet(); [DataMember(Name = "DataType")] public string DataType { get; set; } = "ClothingData"; [DataMember(Name = "ID")] public string Id { get; set; } [DataMember(Name = "Color")] public int Color { get; set; } public bool IsValid { get { EClothingColor clothingColor; return TryGetValidColor(out clothingColor); } } [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (DataType == null) { string text2 = (DataType = "ClothingData"); } } public bool TryGetValidColor(out EClothingColor clothingColor) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected I4, but got Unknown if (!string.Equals(DataType, "ClothingData", StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(Id)) { clothingColor = (EClothingColor)(int)DefaultColor; return false; } return TryGetColor(out clothingColor); } public bool TryGetColor(out EClothingColor clothingColor) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected I4, but got Unknown if (ValidColorValues.Contains(Color)) { clothingColor = (EClothingColor)Color; return true; } clothingColor = (EClothingColor)(int)DefaultColor; return false; } private static HashSet<int> BuildValidColorSet() { HashSet<int> hashSet = new HashSet<int>(); foreach (object enumValue in EnumValues) { hashSet.Add((int)enumValue); } return hashSet; } } [Serializable] [DataContract] public sealed class SerializableColor : IEquatable<SerializableColor> { private float _alpha = 1f; private bool _alphaExplicitlySet; [DataMember(Name = "r")] public float R { get; private set; } [DataMember(Name = "g")] public float G { get; private set; } [DataMember(Name = "b")] public float B { get; private set; } [DataMember(Name = "a")] public float A { get { return _alpha; } private set { _alpha = value; _alphaExplicitlySet = true; } } public SerializableColor() { } public SerializableColor(float r, float g, float b, float a = 1f) { R = r; G = g; B = b; A = a; } public SerializableColor(Color color) { //IL_0012: 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_002a: 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) R = color.r; G = color.g; B = color.b; A = color.a; } public Color ToUnityColor() { //IL_0018: Unknown result type (might be due to invalid IL or missing references) return new Color(R, G, B, A); } [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (!_alphaExplicitlySet) { _alpha = 1f; } } public bool Equals(SerializableColor other) { if (other == null) { return false; } if (this == other) { return true; } if (R.Equals(other.R) && G.Equals(other.G) && B.Equals(other.B)) { return A.Equals(other.A); } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((SerializableColor)obj); } public override int GetHashCode() { return (((17 * 23 + R.GetHashCode()) * 23 + G.GetHashCode()) * 23 + B.GetHashCode()) * 23 + A.GetHashCode(); } } [Serializable] [DataContract] public sealed class SerializableColorData : IEquatable<SerializableColorData> { [NonSerialized] private Color? _cachedUnityColor; [DataMember(Name = "ColorType")] public int ColorType { get; private set; } [DataMember(Name = "ActualColor")] public SerializableColor ActualColor { get; private set; } = new SerializableColor(); public SerializableColorData() { } public SerializableColorData(int colorType, SerializableColor actualColor) { ColorType = colorType; ActualColor = actualColor ?? new SerializableColor(); } public SerializableColorData(int colorType, Color actualColor) : this(colorType, new SerializableColor(actualColor)) { }//IL_0002: Unknown result type (might be due to invalid IL or missing references) public SerializableColorData(EClothingColor colorType, Color actualColor) : this((int)colorType, actualColor) { }//IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Expected I4, but got Unknown [OnDeserialized] private void OnDeserialized(StreamingContext context) { if (ActualColor == null) { SerializableColor serializableColor2 = (ActualColor = new SerializableColor()); } _cachedUnityColor = null; } public EClothingColor GetEnumColorType() { return (EClothingColor)ColorType; } public Color ToUnityColor() { //IL_0034: 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_0018: Unknown result type (might be due to invalid IL or missing references) if (!_cachedUnityColor.HasValue) { _cachedUnityColor = ActualColor?.ToUnityColor() ?? Color.white; } return _cachedUnityColor.Value; } public bool Equals(SerializableColorData other) { if (other == null) { return false; } if (this == other) { return true; } if (ColorType == other.ColorType) { return object.Equals(ActualColor, other.ActualColor); } return false; } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } if (obj.GetType() != GetType()) { return false; } return Equals((SerializableColorData)obj); } public override int GetHashCode() { return (17 * 23 + ColorType.GetHashCode()) * 23 + (ActualColor?.GetHashCode() ?? 0); } } internal class MenuButtons { public Button ContinueButton { get; set; } public Button LoadGameButton { get; set; } } }