Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of ProductAppFix v1.0.1
ProductAppFix.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text.RegularExpressions; using HarmonyLib; using MelonLoader; using Microsoft.CodeAnalysis; using ProductAppFix; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "ProductAppFix", "1.0.1", "HazDS", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("ProductAppFix")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+1f3fdc661951e620786a1435a8a4afacaf3558ce")] [assembly: AssemblyProduct("ProductAppFix")] [assembly: AssemblyTitle("ProductAppFix")] [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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace ProductAppFix { public class Core : MelonMod { private const float MAX_PRICE = 99999f; private static readonly Dictionary<string, float> _prices = new Dictionary<string, float>(); private static readonly Dictionary<string, object> _productObjects = new Dictionary<string, object>(); private static FieldInfo _productPricesField; [ThreadStatic] private static bool _settingPrice; [ThreadStatic] private static Dictionary<object, float> _savedOriginalPrices; public override void OnInitializeMelon() { //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Expected O, but got Unknown //IL_0118: Expected O, but got Unknown //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Expected O, but got Unknown //IL_016c: Expected O, but got Unknown Type type = FindType("ScheduleOne.UI.Phone.ProductManagerApp.ProductAppDetailPanel"); Type type2 = FindType("ScheduleOne.Product.ProductManager"); if (type == null || type2 == null) { ((MelonBase)this).LoggerInstance.Error("Failed to find required types."); return; } _productPricesField = type2.GetField("ProductPrices", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); MethodInfo methodInfo = AccessTools.Method(type, "Awake", (Type[])null, (Type[])null); if (methodInfo != null) { ((MelonBase)this).HarmonyInstance.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Core), "Awake_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo2 = AccessTools.Method(type2, "GetPrice", (Type[])null, (Type[])null); if (methodInfo2 != null) { ((MelonBase)this).HarmonyInstance.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(Core), "GetPrice_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo3 = AccessTools.Method(type2, "RpcLogic___SetPrice_4077118173", (Type[])null, (Type[])null); if (methodInfo3 != null) { ((MelonBase)this).HarmonyInstance.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(Core), "SetPriceRpc_Prefix", (Type[])null), new HarmonyMethod(typeof(Core), "SetPriceRpc_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } MethodInfo methodInfo4 = AccessTools.Method(type2, "GetSaveString", (Type[])null, (Type[])null); if (methodInfo4 != null) { ((MelonBase)this).HarmonyInstance.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(Core), "GetSaveString_Prefix", (Type[])null), new HarmonyMethod(typeof(Core), "GetSaveString_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } ((MelonBase)this).LoggerInstance.Msg("ProductAppFix loaded."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Menu") { _prices.Clear(); _productObjects.Clear(); } } private static void Awake_Postfix(object __instance) { object memberValue = GetMemberValue(__instance, "ValueLabel"); if (memberValue != null) { PropertyInfo property = memberValue.GetType().GetProperty("characterLimit", BindingFlags.Instance | BindingFlags.Public); if (property != null) { property.SetValue(memberValue, 5); } } } private static bool GetPrice_Prefix(object __instance, object product, ref float __result) { if (product == null) { __result = 1f; return false; } string productId = GetProductId(product); if (productId != null) { _productObjects[productId] = product; if (_prices.TryGetValue(productId, out var value)) { __result = value; return false; } } return true; } private static bool SetPriceRpc_Prefix(object __instance, string productID, float value) { if (_settingPrice) { return true; } float value2 = Mathf.RoundToInt(Mathf.Clamp(value, 1f, 99999f)); _prices[productID] = value2; return true; } private static void SetPriceRpc_Postfix(object __instance, string productID, float value) { if (_settingPrice || _productPricesField == null || !_productObjects.TryGetValue(productID, out var value2) || !_prices.TryGetValue(productID, out var value3)) { return; } object value4 = _productPricesField.GetValue(__instance); if (value4 == null) { return; } _settingPrice = true; try { DictSet(value4, value2, value3); } catch { } finally { _settingPrice = false; } } private static void GetSaveString_Prefix(object __instance) { if (_productPricesField == null || _prices.Count == 0) { return; } try { object value = _productPricesField.GetValue(__instance); if (value == null) { return; } _savedOriginalPrices = new Dictionary<object, float>(); foreach (KeyValuePair<string, float> price in _prices) { if (_productObjects.TryGetValue(price.Key, out var value2)) { try { float value3 = DictGet(value, value2); _savedOriginalPrices[value2] = value3; DictSet(value, value2, price.Value); } catch (Exception ex) { Melon<Core>.Logger.Warning("Dict access failed for " + price.Key + ": " + ex.Message); } } } } catch (Exception ex2) { Melon<Core>.Logger.Error("GetSaveString_Prefix failed: " + ex2.Message); Melon<Core>.Logger.Error("Stack trace: " + ex2.StackTrace); } } private static void GetSaveString_Postfix(object __instance, ref string __result) { try { if (_savedOriginalPrices != null && _savedOriginalPrices.Count > 0 && _productPricesField != null) { object value = _productPricesField.GetValue(__instance); if (value != null) { foreach (KeyValuePair<object, float> savedOriginalPrice in _savedOriginalPrices) { try { DictSet(value, savedOriginalPrice.Key, savedOriginalPrice.Value); } catch { } } } } if (__result == null || _prices.Count <= 0) { return; } foreach (KeyValuePair<string, float> price in _prices) { string text = Regex.Escape(price.Key); string text2 = ((int)price.Value).ToString(); string pattern = "(\"String\"\\s*:\\s*\"" + text + "\"[^}]*?\"Int\"\\s*:\\s*)(\\d+)"; string text3 = Regex.Replace(__result, pattern, "${1}" + text2); if (text3 != __result) { __result = text3; } } } catch (Exception ex) { Melon<Core>.Logger.Error("GetSaveString_Postfix failed: " + ex.Message); Melon<Core>.Logger.Error("Stack trace: " + ex.StackTrace); } finally { _savedOriginalPrices = null; } } private static float DictGet(object dict, object key) { if (dict is IDictionary dictionary) { return Convert.ToSingle(dictionary[key]); } Type type = dict.GetType(); MethodInfo? obj = type.GetMethod("get_Item", new Type[1] { key.GetType() }) ?? type.GetMethod("get_Item"); if (obj == null) { Melon<Core>.Logger.Warning("get_Item not found on " + type.FullName); throw new MissingMethodException("get_Item"); } return Convert.ToSingle(obj.Invoke(dict, new object[1] { key })); } private static void DictSet(object dict, object key, float value) { if (dict is IDictionary dictionary) { dictionary[key] = value; return; } Type type = dict.GetType(); MethodInfo? obj = type.GetMethod("set_Item", new Type[2] { key.GetType(), typeof(float) }) ?? type.GetMethod("set_Item"); if (obj == null) { Melon<Core>.Logger.Warning("set_Item not found on " + type.FullName); throw new MissingMethodException("set_Item"); } obj.Invoke(dict, new object[2] { key, value }); } private static object GetMemberValue(object obj, string name) { Type type = obj.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(obj); } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(obj); } return null; } private static string GetProductId(object product) { return GetMemberValue(product, "ID") as string; } private static Type FindType(string fullName) { string name = (fullName.StartsWith("ScheduleOne") ? ("Il2Cpp" + fullName) : fullName); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { Type type = assembly.GetType(fullName) ?? assembly.GetType(name); if (type != null) { return type; } } return null; } } }