Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of BetterChemStationsIl2cpp v1.2.2
BetterChemStationsIL2.dll
Decompiled a month 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 BetterChemStationsIL2; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Management; using Il2CppScheduleOne.ObjectScripts; using Il2CppScheduleOne.StationFramework; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Events; [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), "BetterChemStationsMain", "1.2.2", "trpip", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("BetterChemStationsIL2")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+2b17f22906a4130797751944bce7c814ad932b7b")] [assembly: AssemblyProduct("BetterChemStationsIL2")] [assembly: AssemblyTitle("BetterChemStationsIL2")] [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 BetterChemStationsIL2 { public class Core : MelonMod { private static readonly string CATEGORY = "BetterChemStations"; private static MelonPreferences_Entry<bool> _debugModeEntry; public static bool DebugMode => _debugModeEntry?.Value ?? false; public override void OnInitializeMelon() { MelonPreferences_Category val = MelonPreferences.CreateCategory(CATEGORY); _debugModeEntry = val.CreateEntry<bool>("DebugMode", false, "Debug Logging", "When enabled, outputs detailed logs to help with troubleshooting.", false, false, (ValueValidator)null, (string)null); Harmony.CreateAndPatchAll(typeof(RecipeChangedPatch), (string)null); Harmony.CreateAndPatchAll(typeof(ChemistryStationAwakePatch), (string)null); ((MelonBase)this).LoggerInstance.Msg("Chemistry Station Filter Mod loaded!"); DebugLog("Debug mode is enabled"); } public static void DebugLog(string message) { if (DebugMode) { MelonLogger.Msg("[DEBUG] " + message); } } } public static class FilterHelper { [CompilerGenerated] private sealed class <DelayedFilterApplication>d__5 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public ChemistryStation station; public float delay; private ChemistryStationConfiguration <config>5__1; private StationRecipe <recipe>5__2; private Exception <ex>5__3; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <DelayedFilterApplication>d__5(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <config>5__1 = null; <recipe>5__2 = null; <ex>5__3 = null; <>1__state = -2; } private bool MoveNext() { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Expected O, but got Unknown switch (<>1__state) { default: return false; case 0: <>1__state = -1; Core.DebugLog($"Starting delayed filter application for {((Object)station).name} with delay {delay}s"); <>2__current = (object)new WaitForSeconds(delay); <>1__state = 1; return true; case 1: <>1__state = -1; Core.DebugLog($"Applying delayed filters for {((Object)station).name} after {delay}s"); try { <config>5__1 = GetStationConfiguration(station); if (<config>5__1 == null) { Core.DebugLog("Config is still null after delay - recipe may not be set"); return false; } StationRecipeField recipe = <config>5__1.Recipe; <recipe>5__2 = ((recipe != null) ? recipe.SelectedRecipe : null); if ((Object)(object)<recipe>5__2 != (Object)null && !IsGhost(station)) { Core.DebugLog("Found recipe after delay: " + <recipe>5__2.RecipeTitle); ApplyRecipeFilters(station, <recipe>5__2); } else { Core.DebugLog("No recipe found after delay: recipe is " + (((Object)(object)<recipe>5__2 == (Object)null) ? "null" : "not null")); } <config>5__1 = null; <recipe>5__2 = null; } catch (Exception ex) { <ex>5__3 = ex; MelonLogger.Error("Error in delayed filter application: " + <ex>5__3.Message); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } public static void ApplyRecipeFilters(ChemistryStation station, StationRecipe recipe) { //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Expected O, but got Unknown if ((Object)(object)station == (Object)null || ((recipe != null) ? recipe.Ingredients : null) == null) { MelonLogger.Warning("Cannot apply filters: station or recipe is null"); return; } Core.DebugLog($"Applying filters for recipe: {recipe.RecipeTitle} with {recipe.Ingredients.Count} ingredients"); try { for (int i = 0; i < ((Il2CppArrayBase<ItemSlot>)(object)station.IngredientSlots).Length; i++) { ItemSlot val = ((Il2CppArrayBase<ItemSlot>)(object)station.IngredientSlots)[i]; if (((object)val).GetType().GetField("Filters", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(val) is List<ItemFilter> list) { list.Clear(); } } for (int j = 0; j < recipe.Ingredients.Count && j < ((Il2CppArrayBase<ItemSlot>)(object)station.IngredientSlots).Length; j++) { IngredientQuantity val2 = recipe.Ingredients[j]; ItemSlot val3 = ((Il2CppArrayBase<ItemSlot>)(object)station.IngredientSlots)[j]; List<string> val4 = new List<string>(); Enumerator<ItemDefinition> enumerator = val2.Items.GetEnumerator(); while (enumerator.MoveNext()) { ItemDefinition current = enumerator.Current; val4.Add(current.ID); } val3.AddFilter((ItemFilter)new ItemFilter_ID(val4) { IsWhitelist = true }); Core.DebugLog($"Added filter for ingredient {val2.Item.Name} to slot {j}"); } } catch (Exception ex) { MelonLogger.Error("Error applying filters: " + ex.Message); } } public static ChemistryStationConfiguration GetStationConfiguration(ChemistryStation station) { try { PropertyInfo propertyInfo = AccessTools.Property(typeof(ChemistryStation), "stationConfiguration"); if (propertyInfo != null) { object? value = propertyInfo.GetValue(station); return (ChemistryStationConfiguration)((value is ChemistryStationConfiguration) ? value : null); } MelonLogger.Warning("Could not find stationConfiguration field"); return null; } catch (Exception ex) { MelonLogger.Error("Error accessing station configuration: " + ex.Message); return null; } } public static StationRecipe GetRecipeFromConfig(ChemistryStationConfiguration config) { if (config == null) { return null; } try { StationRecipeField recipe = config.Recipe; return (recipe != null) ? recipe.SelectedRecipe : null; } catch (Exception ex) { MelonLogger.Error("Error getting recipe: " + ex.Message); return null; } } public static bool IsGhost(ChemistryStation station) { try { FieldInfo fieldInfo = AccessTools.Field(typeof(ChemistryStation), "isGhost"); if (fieldInfo != null) { return (bool)fieldInfo.GetValue(station); } } catch (Exception ex) { MelonLogger.Error("Error checking isGhost: " + ex.Message); } return false; } public static void StartDelayedFilterApplication(ChemistryStation station, float delay) { MelonCoroutines.Start(DelayedFilterApplication(station, delay)); } [IteratorStateMachine(typeof(<DelayedFilterApplication>d__5))] private static IEnumerator DelayedFilterApplication(ChemistryStation station, float delay) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <DelayedFilterApplication>d__5(0) { station = station, delay = delay }; } } [HarmonyPatch(/*Could not decode attribute arguments.*/)] public class RecipeChangedPatch { private static void Postfix(ChemistryStationConfiguration __instance) { try { UnityAction<StationRecipe> val = UnityAction<StationRecipe>.op_Implicit((Action<StationRecipe>)delegate(StationRecipe recipe) { Core.DebugLog("Recipe changed to: " + (((recipe != null) ? recipe.RecipeTitle : null) ?? "null")); FilterHelper.ApplyRecipeFilters(__instance.Station, recipe); }); __instance.Recipe.onRecipeChanged.AddListener(val); StationRecipeField recipe2 = __instance.Recipe; StationRecipe val2 = ((recipe2 != null) ? recipe2.SelectedRecipe : null); Core.DebugLog("Current recipe: " + (((val2 != null) ? val2.RecipeTitle : null) ?? "null")); if ((Object)(object)val2 != (Object)null && !FilterHelper.IsGhost(__instance.Station)) { Core.DebugLog("Applying filters for initial recipe: " + val2.RecipeTitle); FilterHelper.ApplyRecipeFilters(__instance.Station, val2); } } catch (Exception ex) { MelonLogger.Error("Error in RecipeChangedPatch: " + ex.Message); } } } [HarmonyPatch(typeof(ChemistryStation), "Awake")] public class ChemistryStationAwakePatch { private static void Postfix(ChemistryStation __instance) { try { if (FilterHelper.IsGhost(__instance)) { Core.DebugLog("Skipping Awake hook for ghost ChemistryStation " + ((Object)__instance).name); return; } Core.DebugLog("Awake called for ChemistryStation " + ((Object)__instance).name); ChemistryStationConfiguration stationConfiguration = FilterHelper.GetStationConfiguration(__instance); if (stationConfiguration != null) { Core.DebugLog("Found config in Awake, checking for recipe"); StationRecipeField recipe = stationConfiguration.Recipe; StationRecipe val = ((recipe != null) ? recipe.SelectedRecipe : null); if ((Object)(object)val != (Object)null) { Core.DebugLog("Found recipe in Awake: " + val.RecipeTitle); FilterHelper.ApplyRecipeFilters(__instance, val); } else { Core.DebugLog("No recipe found in Awake, setting up delayed attempt"); FilterHelper.StartDelayedFilterApplication(__instance, 2f); } } else { Core.DebugLog("Station configuration is null in Awake"); FilterHelper.StartDelayedFilterApplication(__instance, 3f); } } catch (Exception ex) { MelonLogger.Error("Error in ChemistryStationAwakePatch: " + ex.Message); } } } }