Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of BetterChemStations v1.2.2
BetterChemStations.dll
Decompiled 10 months 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 BetterChemStations; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using ScheduleOne.ItemFramework; using ScheduleOne.Management; using ScheduleOne.ObjectScripts; using ScheduleOne.StationFramework; 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), "BetterChemStations", "1.2.2", "trpipher", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("BetterChemStations")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+2b17f22906a4130797751944bce7c814ad932b7b")] [assembly: AssemblyProduct("BetterChemStations")] [assembly: AssemblyTitle("BetterChemStations")] [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 BetterChemStations { 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0057: 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_0127: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Expected O, but got Unknown if ((Object)(object)station == (Object)null || recipe?.Ingredients == 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 < station.IngredientSlots.Length; i++) { ItemSlot val = 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 < station.IngredientSlots.Length; j++) { IngredientQuantity val2 = recipe.Ingredients[j]; ItemSlot val3 = station.IngredientSlots[j]; List<string> list2 = new List<string>(); foreach (ItemDefinition item in val2.Items) { list2.Add(item.ID); } val3.AddFilter((ItemFilter)new ItemFilter_ID(list2) { 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 { __instance.Recipe.onRecipeChanged.AddListener((UnityAction<StationRecipe>)delegate(StationRecipe recipe) { Core.DebugLog("Recipe changed to: " + (recipe?.RecipeTitle ?? "null")); FilterHelper.ApplyRecipeFilters(__instance.Station, recipe); }); StationRecipeField recipe2 = __instance.Recipe; StationRecipe val = ((recipe2 != null) ? recipe2.SelectedRecipe : null); Core.DebugLog("Current recipe: " + (val?.RecipeTitle ?? "null")); if ((Object)(object)val != (Object)null && !FilterHelper.IsGhost(__instance.Station)) { Core.DebugLog("Applying filters for initial recipe: " + val.RecipeTitle); FilterHelper.ApplyRecipeFilters(__instance.Station, val); } } 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); } } } }