using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using DealerCustomerPreferences; using DealerCustomerPreferences.Config; using DealerCustomerPreferences.Integrations.Interop; using DealerCustomerPreferences.Integrations.Tooltips; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using S1API.Logging; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Core), "Dealer Customer Preferences", "1.0.0", "Riccaforte", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonAuthorColor(1, 68, 2, 152)] [assembly: MelonColor(1, 68, 2, 152)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("DealerCustomerPreferences")] [assembly: AssemblyConfiguration("CrossCompat")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+c009ad7123b5c0e7913ea8348486801dd78b1656")] [assembly: AssemblyProduct("DealerCustomerPreferences")] [assembly: AssemblyTitle("DealerCustomerPreferences")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace DealerCustomerPreferences { public class Core : MelonMod { private static ModPreferences? _preferences; private PreferencesFileWatcher? _preferencesWatcher; public readonly Log _logger = new Log("DealerCustomerPreferences"); public static Core? Instance { get; private set; } public static bool IsModEnabled => _preferences?.IsEnabled ?? true; public static int TooltipFontSize => _preferences?.TooltipFontSize ?? 14; public override void OnInitializeMelon() { Instance = this; _preferences = new ModPreferences(); _preferencesWatcher = new PreferencesFileWatcher(_logger); _logger.Msg("Mod initialized and config loaded."); } public override void OnUpdate() { PreferencesFileWatcher? preferencesWatcher = _preferencesWatcher; if (preferencesWatcher != null && preferencesWatcher.TryConsumeScheduledReload()) { ReloadPreferencesFromDisk(); } } public override void OnApplicationQuit() { _preferencesWatcher?.Dispose(); _preferencesWatcher = null; _preferences = null; Instance = null; } private void ReloadPreferencesFromDisk() { string text = _preferencesWatcher?.FilePath; if (string.IsNullOrWhiteSpace(text)) { return; } if (!PreferencesFileParser.TryReadSnapshot(text, out PreferencesSnapshot snapshot)) { _preferencesWatcher?.ScheduleReload(); return; } ModPreferences? preferences = _preferences; if (preferences != null && preferences.ApplySnapshot(snapshot)) { _logger.Msg("Reloaded preferences from MelonPreferences.cfg."); } } } } namespace DealerCustomerPreferences.Utils { public static class Constants { public static class Game { public const string GAME_STUDIO = "TVGS"; public const string GAME_NAME = "Schedule I"; } public const string MOD_NAME = "Dealer Customer Preferences"; public const string MOD_VERSION = "1.0.0"; public const string MOD_AUTHOR = "Riccaforte"; public const string PREFERENCES_CATEGORY = "Dealer Customer Preferences"; } } namespace DealerCustomerPreferences.Integrations { public static class HarmonyPatches { [HarmonyPatch] private static class DealerManagementAppSetDisplayedDealerPatch { [HarmonyPrepare] private static bool Prepare() { return CanPatch(GameTypeNames.DealerManagementApp, "SetDisplayedDealer"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return GetRequiredPatchMethod(GameTypeNames.DealerManagementApp, "SetDisplayedDealer"); } [HarmonyPostfix] private static void Postfix(object __instance, object dealer) { if (!Core.IsModEnabled) { return; } try { CustomerTooltips.ApplyDealerTooltips(__instance, dealer); } catch (Exception arg) { MelonLogger.Error($"Failed to update dealer customer preference tooltips: {arg}"); } } } [HarmonyPatch] private static class TooltipManagerCheckForTooltipHoverPatch { [HarmonyPrepare] private static bool Prepare() { return CanPatch(GameTypeNames.TooltipManager, "CheckForTooltipHover"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return GetRequiredPatchMethod(GameTypeNames.TooltipManager, "CheckForTooltipHover"); } [HarmonyPostfix] private static void Postfix(object __instance) { if (!Core.IsModEnabled) { return; } try { CustomerTooltips.ShowCustomerNameTooltip(__instance); } catch (Exception arg) { MelonLogger.Error($"Failed to show dealer customer name tooltip: {arg}"); } } } [HarmonyPatch] private static class CustomerSelectorCreateEntryPatch { [HarmonyPrepare] private static bool Prepare() { return CanPatch(GameTypeNames.CustomerSelector, "CreateEntry"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return GetRequiredPatchMethod(GameTypeNames.CustomerSelector, "CreateEntry"); } [HarmonyPrefix] private static void Prefix(object __instance, out int __state) { __state = 0; if (Core.IsModEnabled) { __state = CustomerTooltips.GetCustomerSelectorEntryCount(__instance); } } [HarmonyPostfix] private static void Postfix(object __instance, object __0, int __state) { if (!Core.IsModEnabled) { return; } try { CustomerTooltips.CaptureCustomerSelectorRoot(__instance); CustomerTooltips.BindCreatedCustomerSelectorEntry(__instance, __0, __state); } catch (Exception arg) { MelonLogger.Error($"Failed to update customer selector preference tooltips: {arg}"); } } } [HarmonyPatch] private static class CustomerSelectorOpenPatch { [HarmonyPrepare] private static bool Prepare() { return CanPatch(GameTypeNames.CustomerSelector, "Open"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return GetRequiredPatchMethod(GameTypeNames.CustomerSelector, "Open"); } [HarmonyPostfix] private static void Postfix(object __instance) { if (!Core.IsModEnabled) { return; } try { CustomerTooltips.CaptureCustomerSelectorRoot(__instance); CustomerTooltips.RefreshCustomerSelectorVisibility(__instance); } catch (Exception arg) { MelonLogger.Error($"Failed to refresh customer selector preference tooltips: {arg}"); } } } [HarmonyPatch] private static class CustomerSelectorAwakePatch { [HarmonyPrepare] private static bool Prepare() { return CanPatch(GameTypeNames.CustomerSelector, "Awake"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return GetRequiredPatchMethod(GameTypeNames.CustomerSelector, "Awake"); } [HarmonyPostfix] private static void Postfix(object __instance) { CustomerTooltips.MarkCustomerSelectorInitialized(__instance); } } [HarmonyPatch] private static class CustomerSelectorClosePatch { [HarmonyPrepare] private static bool Prepare() { return CanPatch(GameTypeNames.CustomerSelector, "Close"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return GetRequiredPatchMethod(GameTypeNames.CustomerSelector, "Close"); } [HarmonyPrefix] private static bool Prefix() { if (!Core.IsModEnabled) { return true; } return CustomerTooltips.ShouldAllowCustomerSelectorClose(); } } [HarmonyPatch] private static class DealerManagementAppAssignCustomerPatch { [HarmonyPrepare] private static bool Prepare() { return CanPatch(GameTypeNames.DealerManagementApp, "AssignCustomer"); } [HarmonyTargetMethod] private static MethodBase TargetMethod() { return GetRequiredPatchMethod(GameTypeNames.DealerManagementApp, "AssignCustomer"); } [HarmonyPrefix] private static void Prefix(object __instance) { if (!Core.IsModEnabled) { return; } try { CustomerTooltips.PrepareCustomerSelectorFirstOpen(__instance); } catch (Exception arg) { MelonLogger.Error($"Failed to prepare customer selector first-open state: {arg}"); } } [HarmonyPostfix] private static void Postfix(object __instance) { if (!Core.IsModEnabled) { return; } try { CustomerTooltips.RecoverCustomerSelectorFirstOpen(__instance); } catch (Exception arg) { MelonLogger.Error($"Failed to recover customer selector first open state: {arg}"); } } } private static readonly CustomerTooltipController CustomerTooltips = new CustomerTooltipController(); private static bool CanPatch(string[] typeNames, string methodName) { Type type = RuntimeInterop.ResolveGameType(typeNames); return type != null && AccessTools.Method(type, methodName, (Type[])null, (Type[])null) != null; } private static MethodBase GetRequiredPatchMethod(string[] typeNames, string methodName) { Type type = RuntimeInterop.ResolveGameType(typeNames); MethodBase methodBase = ((type == null) ? null : AccessTools.Method(type, methodName, (Type[])null, (Type[])null)); if (methodBase != null) { return methodBase; } throw new MissingMethodException("Could not resolve Harmony patch target '" + methodName + "'."); } } } namespace DealerCustomerPreferences.Integrations.Tooltips { internal sealed class CustomerTooltipController { private enum TooltipBindingContext { DealerList, CustomerSelector } private sealed class CustomerNameTooltipBinding { public RectTransform LabelOriginRect { get; } public string Text { get; } public Vector2 LabelOffset { get; } public TooltipBindingContext Context { get; } public CustomerNameTooltipBinding(RectTransform labelOriginRect, string text, Vector2 labelOffset, TooltipBindingContext context) { //IL_0017: 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) LabelOriginRect = labelOriginRect; Text = text; LabelOffset = labelOffset; Context = context; } } private readonly Dictionary _customerNameTooltips = new Dictionary(); private GameObject? _customerSelectorRootObject; private bool _customerSelectorInitialized; private bool _skipNextCustomerSelectorClose; private bool _recoverNextCustomerSelectorOpen; public void ApplyDealerTooltips(object appInstance, object dealer) { //IL_0110: Unknown result type (might be due to invalid IL or missing references) IReadOnlyList readOnlyList = RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(appInstance, "CustomerEntries")); IReadOnlyList readOnlyList2 = RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(dealer, "AssignedCustomers")); for (int i = 0; i < readOnlyList.Count; i++) { object obj = readOnlyList[i]; RectTransform val = (RectTransform)((obj is RectTransform) ? obj : null); if (val == null) { continue; } Transform val2 = ((Transform)val).Find("Name"); if (val2 == null) { continue; } Graphic component = ((Component)val2).GetComponent(); if ((Object)(object)component != (Object)null) { component.raycastTarget = true; } int instanceID = ((Object)((Component)val2).gameObject).GetInstanceID(); if (i >= readOnlyList2.Count) { _customerNameTooltips.Remove(instanceID); continue; } RectTransform component2 = ((Component)val2).GetComponent(); if ((Object)(object)component2 == (Object)null) { _customerNameTooltips.Remove(instanceID); continue; } object obj2 = readOnlyList2[i]; if (obj2 == null) { _customerNameTooltips.Remove(instanceID); } else { SetTooltipBinding(val2, component2, obj2, GetTooltipOffset(), TooltipBindingContext.DealerList); } } } public void CaptureCustomerSelectorRoot(object customerSelector) { object? memberValue = RuntimeInterop.GetMemberValue(customerSelector, "gameObject"); GameObject val = (GameObject)((memberValue is GameObject) ? memberValue : null); if (val != null) { _customerSelectorRootObject = val; } } public void MarkCustomerSelectorInitialized(object customerSelector) { CaptureCustomerSelectorRoot(customerSelector); _customerSelectorInitialized = true; } public void RefreshCustomerSelectorVisibility(object customerSelector) { IReadOnlyList readOnlyList = RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(customerSelector, "customerEntries")); if (readOnlyList.Count == 0) { return; } object memberValue = RuntimeInterop.GetMemberValue(customerSelector, "entryToCustomer"); if (memberValue == null) { return; } Type type = RuntimeInterop.ResolveGameType(GameTypeNames.Dealer); if (type == null) { return; } HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (object item in RuntimeInterop.MaterializeObjects(RuntimeInterop.GetStaticMemberValue(type, "AllPlayerDealers"))) { if (item == null) { continue; } foreach (object item2 in RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(item, "AssignedCustomers"))) { string text = RuntimeInterop.GetMemberValue(RuntimeInterop.GetMemberValue(item2, "NPC"), "ID")?.ToString(); if (!string.IsNullOrWhiteSpace(text)) { hashSet.Add(text); } } } foreach (object item3 in readOnlyList) { RectTransform val = (RectTransform)((item3 is RectTransform) ? item3 : null); if (val != null) { object instance = RuntimeInterop.TryGetMappedValue(memberValue, val); string text2 = RuntimeInterop.GetMemberValue(RuntimeInterop.GetMemberValue(instance, "NPC"), "ID")?.ToString(); if (!string.IsNullOrWhiteSpace(text2)) { ((Component)val).gameObject.SetActive(!hashSet.Contains(text2)); } } } } public int GetCustomerSelectorEntryCount(object customerSelector) { return RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(customerSelector, "customerEntries")).Count; } public void BindCreatedCustomerSelectorEntry(object customerSelector, object? customer, int previousEntryCount) { if (customer == null) { return; } IReadOnlyList readOnlyList = RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(customerSelector, "customerEntries")); if (readOnlyList.Count > previousEntryCount) { object obj = readOnlyList[readOnlyList.Count - 1]; RectTransform val = (RectTransform)((obj is RectTransform) ? obj : null); if (val != null) { BindCustomerSelectorEntry(val, customer); } } } public bool ShouldAllowCustomerSelectorClose() { if (!RuntimeInterop.IsIl2CppRuntime()) { return true; } if (_skipNextCustomerSelectorClose) { _skipNextCustomerSelectorClose = false; return false; } return true; } public void PrepareCustomerSelectorFirstOpen(object appInstance) { if (!RuntimeInterop.IsIl2CppRuntime()) { return; } object memberValue = RuntimeInterop.GetMemberValue(appInstance, "CustomerSelector"); if (memberValue != null) { CaptureCustomerSelectorRoot(memberValue); if (!_customerSelectorInitialized) { _skipNextCustomerSelectorClose = true; _recoverNextCustomerSelectorOpen = true; } } } public void RecoverCustomerSelectorFirstOpen(object appInstance) { if (!RuntimeInterop.IsIl2CppRuntime() || !_recoverNextCustomerSelectorOpen) { return; } try { object memberValue = RuntimeInterop.GetMemberValue(appInstance, "CustomerSelector"); if (memberValue != null) { CaptureCustomerSelectorRoot(memberValue); if ((Object)(object)_customerSelectorRootObject != (Object)null && !_customerSelectorRootObject.activeSelf) { AccessTools.Method(memberValue.GetType(), "Open", (Type[])null, (Type[])null)?.Invoke(memberValue, Array.Empty()); } } } finally { _recoverNextCustomerSelectorOpen = false; } } public void ShowCustomerNameTooltip(object tooltipManager) { if (RuntimeInterop.GetMemberValue(tooltipManager, "tooltipShownThisFrame") is int num && num != 0) { return; } IReadOnlyList readOnlyList = RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(tooltipManager, "rayResults")); if (readOnlyList.Count == 0) { return; } if (IsCustomerSelectorVisible()) { CustomerNameTooltipBinding customerNameTooltipBinding = FindTooltipBinding(readOnlyList, TooltipBindingContext.CustomerSelector); if (customerNameTooltipBinding != null) { ShowTooltip(tooltipManager, customerNameTooltipBinding); } } else { CustomerNameTooltipBinding customerNameTooltipBinding2 = FindTooltipBinding(readOnlyList); if (customerNameTooltipBinding2 != null) { ShowTooltip(tooltipManager, customerNameTooltipBinding2); } } } private void BindCustomerSelectorEntry(RectTransform entryTransform, object customer) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) SetRaycastTargets((Transform)(object)entryTransform, raycastTarget: true); Transform val = ((Transform)entryTransform).Find("Name"); RectTransform labelOriginRect = ((val != null) ? ((Component)val).GetComponent() : null) ?? entryTransform; Graphic val2 = ((val != null) ? ((Component)val).GetComponent() : null); if ((Object)(object)val2 != (Object)null) { val2.raycastTarget = true; } if ((Object)(object)val != (Object)null) { SetTooltipBinding(val, labelOriginRect, customer, GetTooltipOffset(), TooltipBindingContext.CustomerSelector); } SetTooltipBinding((Transform)(object)entryTransform, labelOriginRect, customer, GetTooltipOffset(), TooltipBindingContext.CustomerSelector); } private void ShowTooltip(object tooltipManager, CustomerNameTooltipBinding binding) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) MethodInfo methodInfo = AccessTools.Method(tooltipManager.GetType(), "ShowTooltip", new Type[3] { typeof(string), typeof(Vector2), typeof(bool) }, (Type[])null); if (!(methodInfo == null)) { EnsureTooltipStyle(tooltipManager); Vector2 val = GetMousePosition() + binding.LabelOffset; methodInfo.Invoke(tooltipManager, new object[3] { binding.Text, val, false }); } } private bool IsCustomerSelectorVisible() { return (Object)(object)_customerSelectorRootObject != (Object)null && _customerSelectorRootObject.activeInHierarchy; } private CustomerNameTooltipBinding? FindTooltipBinding(IReadOnlyList rayResults, TooltipBindingContext? requiredContext = null) { for (int i = 0; i < rayResults.Count; i++) { object? memberValue = RuntimeInterop.GetMemberValue(rayResults[i], "gameObject"); GameObject val = (GameObject)((memberValue is GameObject) ? memberValue : null); if (val != null) { CustomerNameTooltipBinding customerNameTooltipBinding = FindTooltipBinding(val.transform); if (customerNameTooltipBinding != null && ((Component)customerNameTooltipBinding.LabelOriginRect).gameObject.activeInHierarchy && (!requiredContext.HasValue || customerNameTooltipBinding.Context == requiredContext.Value)) { return customerNameTooltipBinding; } } } return null; } private Vector2 GetMousePosition() { //IL_0077: 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_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) Type type = RuntimeInterop.ResolveGameType(GameTypeNames.GameInput); if (type != null) { object obj = AccessTools.Property(type, "MousePosition")?.GetValue(null); object obj2 = obj; object obj3 = obj2; if (obj3 is Vector2 result) { return result; } if (obj3 is Vector3 val) { return Vector2.op_Implicit(val); } } return Vector2.op_Implicit(Input.mousePosition); } private void EnsureTooltipStyle(object tooltipManager) { object? memberValue = RuntimeInterop.GetMemberValue(tooltipManager, "anchor"); RectTransform val = (RectTransform)((memberValue is RectTransform) ? memberValue : null); if (val == null) { return; } object memberValue2 = RuntimeInterop.GetMemberValue(tooltipManager, "tooltipLabel"); if (memberValue2 != null) { float num = Core.TooltipFontSize; float num2 = RuntimeInterop.ConvertToSingle(RuntimeInterop.GetMemberValue(memberValue2, "fontSize")); if (num2 > 0f && Math.Abs(num2 - num) > 0.01f) { RuntimeInterop.SetMemberValue(memberValue2, "fontSize", num); } float num3 = RuntimeInterop.ConvertToSingle(RuntimeInterop.GetMemberValue(memberValue2, "fontSizeMax")); if (num3 > 0f && Math.Abs(num3 - num) > 0.01f) { RuntimeInterop.SetMemberValue(memberValue2, "fontSizeMax", num); } } SetOpaqueGraphicAlpha((Transform)(object)val); } private static void SetOpaqueGraphicAlpha(Transform transform) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) Graphic component = ((Component)transform).GetComponent(); if ((Object)(object)component != (Object)null) { Color color = component.color; if (color.a < 1f) { color.a = 1f; component.color = color; } } CanvasGroup component2 = ((Component)transform).GetComponent(); if ((Object)(object)component2 != (Object)null && component2.alpha < 1f) { component2.alpha = 1f; } for (int i = 0; i < transform.childCount; i++) { SetOpaqueGraphicAlpha(transform.GetChild(i)); } } private static void SetRaycastTargets(Transform transform, bool raycastTarget) { Graphic component = ((Component)transform).GetComponent(); if ((Object)(object)component != (Object)null) { component.raycastTarget = raycastTarget; } for (int i = 0; i < transform.childCount; i++) { SetRaycastTargets(transform.GetChild(i), raycastTarget); } } private void SetTooltipBinding(Transform trackedTransform, RectTransform labelOriginRect, object customer, Vector2 labelOffset, TooltipBindingContext context) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) _customerNameTooltips[((Object)((Component)trackedTransform).gameObject).GetInstanceID()] = new CustomerNameTooltipBinding(labelOriginRect, PreferenceTooltipFormatter.BuildPreferenceTooltip(customer), labelOffset, context); } private CustomerNameTooltipBinding? FindTooltipBinding(Transform? transform) { while ((Object)(object)transform != (Object)null) { if (_customerNameTooltips.TryGetValue(((Object)((Component)transform).gameObject).GetInstanceID(), out CustomerNameTooltipBinding value)) { return value; } transform = transform.parent; } return null; } private static Vector2 GetTooltipOffset() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) return new Vector2(30f, -26f); } } internal static class PreferenceTooltipFormatter { private static readonly Dictionary DrugTypeColors = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Weed"] = "6CBF59", ["Marijuana"] = "6CBF59", ["Meth"] = "E5D85C", ["Methamphetamine"] = "E5D85C", ["Cocaine"] = "F4F4F4", ["Coke"] = "F4F4F4", ["Mushrooms"] = "D2B48C", ["Mushroom"] = "D2B48C", ["Shrooms"] = "D2B48C", ["Shroom"] = "D2B48C" }; public static string BuildPreferenceTooltip(object? customer) { object memberValue = RuntimeInterop.GetMemberValue(customer, "CustomerData"); StringBuilder stringBuilder = new StringBuilder(); string value = BuildCustomerHeader(customer); if (!string.IsNullOrWhiteSpace(value)) { stringBuilder.Append(value); } if (memberValue == null) { AppendSection(stringBuilder, "No preference data available.", Array.Empty()); return stringBuilder.ToString(); } List preferredProperties = GetPreferredProperties(memberValue); List drugAffinities = GetDrugAffinities(memberValue, positive: true); List drugAffinities2 = GetDrugAffinities(memberValue, positive: false); if (preferredProperties.Count > 0) { AppendSection(stringBuilder, "Favourite Effects:", preferredProperties); } if (drugAffinities.Count > 0) { AppendSection(stringBuilder, "Preferred Drug Types:", drugAffinities); } if (drugAffinities2.Count > 0) { AppendSection(stringBuilder, "Avoided Drug Types:", drugAffinities2); } if (stringBuilder.Length == 0) { stringBuilder.Append("No preference data available."); } return stringBuilder.ToString(); } private static void AppendSection(StringBuilder builder, string label, IReadOnlyList values) { if (builder.Length > 0) { builder.Append('\n'); } builder.Append(label); if (values.Count != 0) { builder.Append('\n'); builder.Append(" "); builder.Append(string.Join(", ", values)); } } private static string BuildCustomerHeader(object? customer) { object memberValue = RuntimeInterop.GetMemberValue(customer, "NPC"); string value = RuntimeInterop.GetMemberValue(memberValue, "fullName")?.ToString() ?? RuntimeInterop.GetMemberValue(memberValue, "FullName")?.ToString() ?? string.Empty; string value2 = RuntimeInterop.GetMemberValue(memberValue, "Region")?.ToString() ?? string.Empty; value2 = RuntimeInterop.HumanizeIdentifier(value2); if (string.IsNullOrWhiteSpace(value) && string.IsNullOrWhiteSpace(value2)) { return string.Empty; } string text = EscapeRichText(value); string text2 = EscapeRichText(value2); if (string.IsNullOrWhiteSpace(value2)) { return "" + text + ""; } if (string.IsNullOrWhiteSpace(value)) { return "" + text2 + ""; } return "" + text + " (" + text2 + ")"; } private static List GetPreferredProperties(object customerData) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); List list = new List(); foreach (object item in RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(customerData, "PreferredProperties"))) { if (item != null) { string text = RuntimeInterop.GetMemberValue(item, "Name")?.ToString() ?? RuntimeInterop.HumanizeIdentifier(item.GetType().Name); if (!string.IsNullOrWhiteSpace(text) && hashSet.Add(text)) { list.Add(FormatEffectLabel(item, text)); } } } return list; } private static List GetDrugAffinities(object customerData, bool positive) { List<(string, float)> list = new List<(string, float)>(); object memberValue = RuntimeInterop.GetMemberValue(customerData, "DefaultAffinityData"); foreach (object item in RuntimeInterop.MaterializeObjects(RuntimeInterop.GetMemberValue(memberValue, "ProductAffinities"))) { if (item == null) { continue; } float num = RuntimeInterop.ConvertToSingle(RuntimeInterop.GetMemberValue(item, "Affinity")); if ((!positive || !(num <= 0.15f)) && (positive || !(num >= -0.15f))) { string text = RuntimeInterop.HumanizeIdentifier(RuntimeInterop.GetMemberValue(item, "DrugType")?.ToString() ?? string.Empty); if (!string.IsNullOrWhiteSpace(text)) { list.Add((text, num)); } } } list.Sort(((string Name, float Affinity) left, (string Name, float Affinity) right) => positive ? right.Affinity.CompareTo(left.Affinity) : left.Affinity.CompareTo(right.Affinity)); List list2 = new List(); int val = (positive ? 3 : 2); for (int i = 0; i < Math.Min(list.Count, val); i++) { var (label, affinity) = list[i]; list2.Add(FormatDrugTypeLabel(label) + " (" + DescribeAffinity(affinity) + ")"); } return list2; } private static string FormatEffectLabel(object effect, string label) { string text = EscapeRichText(label); string effectColorHex = GetEffectColorHex(effect); if (string.IsNullOrEmpty(effectColorHex)) { return "" + text + ""; } return "" + text + ""; } private static string FormatDrugTypeLabel(string label) { string text = EscapeRichText(label); if (!DrugTypeColors.TryGetValue(label, out string value)) { return "" + text + ""; } return "" + text + ""; } private static string? GetEffectColorHex(object effect) { object memberValue = RuntimeInterop.GetMemberValue(effect, "LabelColor"); if (TryGetColorHex(memberValue, out string colorHex)) { return colorHex; } object memberValue2 = RuntimeInterop.GetMemberValue(effect, "TintColor"); if (TryGetColorHex(memberValue2, out colorHex)) { return colorHex; } return null; } private static bool TryGetColorHex(object? colorValue, out string colorHex) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (!(colorValue is Color val)) { if (colorValue is Color32 val2) { colorHex = ColorUtility.ToHtmlStringRGB(Color32.op_Implicit(val2)); return true; } colorHex = string.Empty; return false; } colorHex = ColorUtility.ToHtmlStringRGB(val); return true; } private static string EscapeRichText(string value) { return value.Replace("&", "&", StringComparison.Ordinal).Replace("<", "<", StringComparison.Ordinal).Replace(">", ">", StringComparison.Ordinal); } private static string DescribeAffinity(float affinity) { if (affinity >= 0.75f) { return "loves"; } if (affinity >= 0.35f) { return "likes"; } if (affinity > 0.15f) { return "prefers"; } if (affinity <= -0.75f) { return "hates"; } if (affinity <= -0.35f) { return "dislikes"; } return "avoids"; } } } namespace DealerCustomerPreferences.Integrations.Interop { internal static class GameTypeNames { public static readonly string[] DealerManagementApp = new string[2] { "ScheduleOne.UI.Phone.Messages.DealerManagementApp", "Il2CppScheduleOne.UI.Phone.Messages.DealerManagementApp" }; public static readonly string[] CustomerSelector = new string[2] { "ScheduleOne.UI.Phone.CustomerSelector", "Il2CppScheduleOne.UI.Phone.CustomerSelector" }; public static readonly string[] Dealer = new string[2] { "ScheduleOne.Economy.Dealer", "Il2CppScheduleOne.Economy.Dealer" }; public static readonly string[] TooltipManager = new string[2] { "ScheduleOne.UI.Tooltips.TooltipManager", "Il2CppScheduleOne.UI.Tooltips.TooltipManager" }; public static readonly string[] GameInput = new string[2] { "ScheduleOne.GameInput", "Il2CppScheduleOne.GameInput" }; } internal static class RuntimeInterop { private const BindingFlags MemberLookupFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private static readonly ConcurrentDictionary<(Type Type, string MemberName), MemberInfo?> MemberCache = new ConcurrentDictionary<(Type, string), MemberInfo>(); private static readonly ConcurrentDictionary TypeCache = new ConcurrentDictionary(StringComparer.Ordinal); public static float ConvertToSingle(object? value) { if (value == null) { return 0f; } if (1 == 0) { } float result = ((value is float num) ? num : ((!(value is double num2)) ? Convert.ToSingle(value) : ((float)num2))); if (1 == 0) { } return result; } public static IReadOnlyList MaterializeObjects(object? value) { if (value == null) { return Array.Empty(); } if (value is IEnumerable enumerable) { List list = new List(); foreach (object item in enumerable) { list.Add(item); } return list; } object memberValue = GetMemberValue(value, "Count"); if (memberValue == null) { return Array.Empty(); } int num = Convert.ToInt32(memberValue); PropertyInfo property = value.GetType().GetProperty("Item", new Type[1] { typeof(int) }); if (property == null) { return Array.Empty(); } List list2 = new List(num); for (int i = 0; i < num; i++) { list2.Add(property.GetValue(value, new object[1] { i })); } return list2; } public static object? TryGetMappedValue(object? map, object key) { foreach (object item in MaterializeObjects(map)) { if (item != null) { object memberValue = GetMemberValue(item, "Key"); if (memberValue == key || object.Equals(memberValue, key)) { return GetMemberValue(item, "Value"); } } } return null; } public static object? GetMemberValue(object? instance, string memberName) { if (instance == null) { return null; } MemberInfo memberInfo = ResolveMember(instance.GetType(), memberName); if (memberInfo is PropertyInfo propertyInfo) { return propertyInfo.GetValue(instance); } return (memberInfo is FieldInfo fieldInfo) ? fieldInfo.GetValue(instance) : null; } public static object? GetStaticMemberValue(Type type, string memberName) { MemberInfo memberInfo = ResolveMember(type, memberName); if (memberInfo is PropertyInfo propertyInfo) { return propertyInfo.GetValue(null); } return (memberInfo is FieldInfo fieldInfo) ? fieldInfo.GetValue(null) : null; } public static void SetMemberValue(object instance, string memberName, object? value) { MemberInfo memberInfo = ResolveMember(instance.GetType(), memberName); if (memberInfo is PropertyInfo propertyInfo && propertyInfo.CanWrite) { propertyInfo.SetValue(instance, value); } else if (memberInfo is FieldInfo fieldInfo) { fieldInfo.SetValue(instance, value); } } public static Type? ResolveGameType(IEnumerable typeNames) { foreach (string typeName in typeNames) { Type type = ResolveLoadedType(typeName); if (type != null) { return type; } } return null; } public static string HumanizeIdentifier(string value) { if (string.IsNullOrWhiteSpace(value)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(value.Length + 4); for (int i = 0; i < value.Length; i++) { char c = value[i]; if (c == '_') { if (stringBuilder.Length > 0) { if (stringBuilder[stringBuilder.Length - 1] != ' ') { stringBuilder.Append(' '); } } continue; } if (i > 0 && char.IsUpper(c) && !char.IsUpper(value[i - 1])) { if (stringBuilder[stringBuilder.Length - 1] != ' ') { stringBuilder.Append(' '); } } stringBuilder.Append(c); } return stringBuilder.ToString().Trim(); } private static MemberInfo? ResolveMember(Type type, string memberName) { return MemberCache.GetOrAdd((type, memberName), ((Type Type, string MemberName) key) => FindMember(key.Type, key.MemberName)); } private static MemberInfo? FindMember(Type type, string memberName) { Type type2 = type; while (type2 != null) { PropertyInfo property = type2.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property; } FieldInfo field = type2.GetField(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } type2 = type2.BaseType; } return null; } public static bool IsIl2CppRuntime() { return ResolveLoadedType("Il2CppScheduleOne.UI.Phone.CustomerSelector") != null; } private static Type? ResolveLoadedType(string typeName) { return TypeCache.GetOrAdd(typeName, (string name) => FindLoadedType(name)); } private static Type? FindLoadedType(string typeName) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType(typeName, throwOnError: false, ignoreCase: false); if (type != null) { return type; } } return null; } } } namespace DealerCustomerPreferences.Config { internal sealed class ModPreferences { public const int DefaultTooltipFontSize = 14; private readonly MelonPreferences_Entry _enabledPreference; private readonly MelonPreferences_Entry _fontSizePreference; public bool IsEnabled => _enabledPreference.Value; public int TooltipFontSize => Math.Max(1, _fontSizePreference.Value); public ModPreferences() { MelonPreferences_Category val = MelonPreferences.CreateCategory("Dealer Customer Preferences"); _enabledPreference = val.CreateEntry("enabled", true, "Whether the mod is enabled.", (string)null, false, false, (ValueValidator)null, (string)null); _fontSizePreference = val.CreateEntry("fontSize", 14, "Font size used for the tooltip.", (string)null, false, false, (ValueValidator)null, (string)null); } public bool ApplySnapshot(PreferencesSnapshot snapshot) { bool result = false; bool? isEnabled = snapshot.IsEnabled; if (isEnabled.HasValue) { bool valueOrDefault = isEnabled.GetValueOrDefault(); if (_enabledPreference.Value != valueOrDefault) { _enabledPreference.Value = valueOrDefault; result = true; } } int? fontSize = snapshot.FontSize; if (fontSize.HasValue) { int valueOrDefault2 = fontSize.GetValueOrDefault(); if (_fontSizePreference.Value != valueOrDefault2) { _fontSizePreference.Value = valueOrDefault2; result = true; } } return result; } } internal static class PreferencesFileParser { public static bool TryReadSnapshot(string preferencesFilePath, out PreferencesSnapshot snapshot) { snapshot = new PreferencesSnapshot(); if (!File.Exists(preferencesFilePath)) { return false; } try { string[] array = File.ReadAllLines(preferencesFilePath); bool flag = false; bool? isEnabled = null; int? fontSize = null; for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length == 0) { continue; } if (text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) { string a = text.Substring(1, text.Length - 2); flag = string.Equals(a, "Dealer Customer Preferences", StringComparison.Ordinal); } else { if (!flag) { continue; } int num = text.IndexOf('='); if (num > 0) { string a2 = text.Substring(0, num).Trim(); string text2 = text.Substring(num + 1).Trim(); int result2; if (string.Equals(a2, "enabled", StringComparison.Ordinal) && bool.TryParse(text2, out var result)) { isEnabled = result; } else if (string.Equals(a2, "fontSize", StringComparison.Ordinal) && int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2)) { fontSize = result2; } } } } snapshot = new PreferencesSnapshot(isEnabled, fontSize); return true; } catch (IOException) { return false; } catch (UnauthorizedAccessException) { return false; } } } internal sealed class PreferencesFileWatcher : IDisposable { private const string PreferencesFileName = "MelonPreferences.cfg"; private static readonly TimeSpan PreferencesReloadDebounce = TimeSpan.FromMilliseconds(150.0); private readonly object _preferencesReloadLock = new object(); private FileSystemWatcher? _preferencesWatcher; private DateTime? _preferencesReloadDueUtc; public string? FilePath { get; } public PreferencesFileWatcher(Log logger) { FilePath = ResolvePreferencesFilePath(); Initialize(logger); } public void ScheduleReload() { lock (_preferencesReloadLock) { _preferencesReloadDueUtc = DateTime.UtcNow + PreferencesReloadDebounce; } } public bool TryConsumeScheduledReload() { lock (_preferencesReloadLock) { DateTime? preferencesReloadDueUtc = _preferencesReloadDueUtc; if (!preferencesReloadDueUtc.HasValue || DateTime.UtcNow < _preferencesReloadDueUtc.Value) { return false; } _preferencesReloadDueUtc = null; return true; } } public void Dispose() { if (_preferencesWatcher != null) { _preferencesWatcher.EnableRaisingEvents = false; _preferencesWatcher.Changed -= OnPreferencesFileChanged; _preferencesWatcher.Created -= OnPreferencesFileChanged; _preferencesWatcher.Renamed -= OnPreferencesFileRenamed; _preferencesWatcher.Dispose(); _preferencesWatcher = null; } } private void Initialize(Log logger) { if (string.IsNullOrWhiteSpace(FilePath)) { logger.Warning("Could not resolve the MelonPreferences path; live config reload is disabled."); return; } string directoryName = Path.GetDirectoryName(FilePath); if (string.IsNullOrWhiteSpace(directoryName) || !Directory.Exists(directoryName)) { logger.Warning("MelonPreferences directory was not found: " + directoryName); return; } _preferencesWatcher = new FileSystemWatcher(directoryName, "MelonPreferences.cfg") { NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime), IncludeSubdirectories = false, EnableRaisingEvents = true }; _preferencesWatcher.Changed += OnPreferencesFileChanged; _preferencesWatcher.Created += OnPreferencesFileChanged; _preferencesWatcher.Renamed += OnPreferencesFileRenamed; } private void OnPreferencesFileChanged(object sender, FileSystemEventArgs e) { ScheduleReload(); } private void OnPreferencesFileRenamed(object sender, RenamedEventArgs e) { ScheduleReload(); } private static string? ResolvePreferencesFilePath() { string userDataDirectory = MelonEnvironment.UserDataDirectory; if (string.IsNullOrWhiteSpace(userDataDirectory)) { return null; } return Path.Combine(userDataDirectory, "MelonPreferences.cfg"); } } internal sealed class PreferencesSnapshot { public bool? IsEnabled { get; } public int? FontSize { get; } public PreferencesSnapshot(bool? isEnabled = null, int? fontSize = null) { IsEnabled = isEnabled; FontSize = fontSize; } } }