Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of ExpandRecipe v0.2.0
ExpandRecipe.dll
Decompiled 3 weeks agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using ExpandRecipe; using HarmonyLib; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.StationFramework; using Il2CppScheduleOne.UI.Phone.ProductManagerApp; using Il2CppScheduleOne.UI.Tooltips; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using MelonLoader; using Microsoft.CodeAnalysis; 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(Main), "ExpandRecipe", "0.2.0", "Robb Manes", "https://github.com/robbmanes/Schedule1_ExpandRecipe")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ExpandRecipe")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+a5df1431d5ba73febb258438632c9750b62963c2")] [assembly: AssemblyProduct("ExpandRecipe")] [assembly: AssemblyTitle("ExpandRecipe")] [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 ExpandRecipe { public class Main : MelonMod { public string testedVersion = "0.3.4f4"; public static ProductManager productManager; public static ProductManagerApp productManagerApp; public override void OnInitializeMelon() { MelonLogger.Msg("Tested on Schedule I version \"" + testedVersion + "\""); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Main") { try { productManager = Object.FindObjectsOfType<ProductManager>()[0]; } catch (Exception value) { MelonLogger.Error($"Failed to find base Product Manager: {value}"); } } ((MelonMod)this).OnSceneWasLoaded(buildIndex, sceneName); } public static List<List<IngredientQuantity>> GetExpandedRecipes(ProductEntry productEntry) { List<StationRecipe> recipes = productEntry.Definition.Recipes; List<List<IngredientQuantity>> list = new List<List<IngredientQuantity>>(); return GetExpandedRecipesInternal(recipes); } private static List<List<IngredientQuantity>> GetExpandedRecipesInternal(List<StationRecipe> baseRecipes) { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Invalid comparison between Unknown and I4 //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Expected O, but got Unknown List<List<IngredientQuantity>> list = new List<List<IngredientQuantity>>(); if (baseRecipes.Count > 0) { Enumerator<StationRecipe> enumerator = baseRecipes.GetEnumerator(); while (enumerator.MoveNext()) { StationRecipe current = enumerator.Current; List<IngredientQuantity> list2 = new List<IngredientQuantity>(); Enumerator<IngredientQuantity> enumerator2 = current.Ingredients.GetEnumerator(); while (enumerator2.MoveNext()) { IngredientQuantity current2 = enumerator2.Current; list2 = (((int)current2.Item.Category != 0) ? list2.Prepend(current2).ToList() : list2.Append(current2).ToList()); } foreach (IngredientQuantity ingredient in list2) { bool flag = false; if ((int)ingredient.Item.Category == 0) { Func<ProductDefinition, bool> func = (ProductDefinition x) => ((ItemDefinition)x).ID == ingredient.Item.ID; ProductDefinition val = productManager.AllProducts.Find(Predicate<ProductDefinition>.op_Implicit(func)) ?? throw new Exception("Could not find base product for \"'" + ingredient.Item.Name + "'\""); if (val.Recipes.Count <= 0) { if (list.Count > 0 && list[list.Count - 1].Count > 0) { list[list.Count - 1] = list[list.Count - 1].Prepend(ingredient).ToList(); } flag = true; break; } for (int i = 0; i < val.Recipes.Count; i++) { StationRecipe val2 = val.Recipes[i]; Enumerator<IngredientQuantity> enumerator4 = val2.Ingredients.GetEnumerator(); while (enumerator4.MoveNext()) { IngredientQuantity current3 = enumerator4.Current; IngredientQuantity val3 = new IngredientQuantity(); if (val2.Ingredients.Contains(ingredient)) { val3 = current3; if (list.Count > 0 && list[list.Count - 1].Count > 0) { list[list.Count - 1] = list[list.Count - 1].Prepend(ingredient).ToList(); } flag = true; break; } } } if (flag) { break; } List<List<IngredientQuantity>> expandedRecipesInternal = GetExpandedRecipesInternal(val.Recipes); foreach (List<IngredientQuantity> item in expandedRecipesInternal) { item.AddRange(list2); item.Remove(ingredient); list.Add(item); } } else { List<IngredientQuantity> list3 = new List<IngredientQuantity>(); list3.Add(ingredient); list.Add(list3); } } } } foreach (List<IngredientQuantity> item2 in list.ToList()) { foreach (List<IngredientQuantity> item3 in list.ToList()) { if (item2 != item3 && !item3.Except(item2).Any() && item3.Except(item2).ToList().Count <= 0) { list.Remove(item3); } } } return list; } public static GameObject CreateExpandedRecipesTextUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { Transform val = parentGameObject.transform.Find("ExpandedRecipesText"); GameObject gameObject; if ((Object)(object)val == (Object)null) { gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; ((Object)gameObject).name = "ExpandedRecipesText"; gameObject.GetComponent<Text>().text = "Expanded Recipe(s)"; } else { gameObject = ((Component)val).gameObject; } gameObject.gameObject.SetActive(true); return gameObject; } public static GameObject GetOrCreateExpandedRecipesUIGameObject(GameObject parentGameObject) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown Transform val = parentGameObject.transform.Find("ExpandedRecipes"); GameObject gameObject; if ((Object)(object)val == (Object)null) { gameObject = Object.Instantiate<GameObject>(new GameObject(), parentGameObject.transform).gameObject; ((Object)gameObject).name = "ExpandedRecipes"; VerticalLayoutGroup val2 = gameObject.gameObject.AddComponent<VerticalLayoutGroup>(); ((HorizontalOrVerticalLayoutGroup)val2).spacing = 8f; ((HorizontalOrVerticalLayoutGroup)val2).childScaleHeight = false; ((HorizontalOrVerticalLayoutGroup)val2).childScaleWidth = false; ((HorizontalOrVerticalLayoutGroup)val2).childControlHeight = false; ((HorizontalOrVerticalLayoutGroup)val2).childControlWidth = false; ((HorizontalOrVerticalLayoutGroup)val2).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)val2).childForceExpandWidth = false; } else { gameObject = ((Component)val).gameObject; gameObject.DestroyChildren(); } gameObject.gameObject.SetActive(true); return gameObject; } public static GameObject CreateExpandedRecipeUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.gameObject.SetActive(true); HorizontalLayoutGroup val = gameObject.gameObject.AddComponent<HorizontalLayoutGroup>(); ((LayoutGroup)val).childAlignment = (TextAnchor)4; ((HorizontalOrVerticalLayoutGroup)val).childScaleHeight = false; ((HorizontalOrVerticalLayoutGroup)val).childScaleWidth = false; ((HorizontalOrVerticalLayoutGroup)val).childControlHeight = false; ((HorizontalOrVerticalLayoutGroup)val).childControlWidth = true; ((HorizontalOrVerticalLayoutGroup)val).childForceExpandHeight = false; ((HorizontalOrVerticalLayoutGroup)val).childForceExpandWidth = false; gameObject.DestroyChildren(); return gameObject; } public static GameObject CreateBaseProductUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject, List<IngredientQuantity> expandedRecipe) { IngredientQuantity val = expandedRecipe.First(); GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().sprite = val.Item.Icon; gameObject.GetComponent<Image>().preserveAspect = true; gameObject.GetComponent<Tooltip>().text = ((Object)val.Item).name; return gameObject; } public static GameObject CreatePlusUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().preserveAspect = true; return gameObject; } public static GameObject CreateMixUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject, IngredientQuantity ingredient) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().sprite = ingredient.Item.Icon; gameObject.GetComponent<Image>().preserveAspect = true; gameObject.GetComponent<Tooltip>().text = ((Object)ingredient.Item).name; return gameObject; } public static GameObject CreateArrowUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject) { return Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; } public static GameObject CreateOutputUIGameObject(GameObject gameObjectToClone, GameObject parentGameObject, ItemQuantity finalProduct) { GameObject gameObject = Object.Instantiate<GameObject>(gameObjectToClone, parentGameObject.transform).gameObject; gameObject.GetComponent<Image>().sprite = finalProduct.Item.Icon; gameObject.GetComponent<Image>().preserveAspect = true; gameObject.GetComponent<Tooltip>().text = ((Object)finalProduct.Item).name; return gameObject; } public static void BuildUIWithRecipe(ProductEntry productEntry, List<List<IngredientQuantity>> expandedRecipes, GameObject recipeTextUI, GameObject recipesContainerUI) { ItemQuantity product = productEntry.Definition.Recipes[0].Product; GameObject val = ((Component)recipesContainerUI.transform.Find("Recipe")).gameObject ?? throw new Exception("Unable to find recipeUI GameObject"); GameObject gameObjectToClone = ((Component)val.transform.Find("Product")).gameObject ?? throw new Exception("Unable to find productUI GameObject"); GameObject gameObjectToClone2 = ((Component)val.transform.Find("Plus")).gameObject ?? throw new Exception("Unable to find plusUI GameObject"); GameObject gameObjectToClone3 = ((Component)val.transform.Find("Mixer")).gameObject ?? throw new Exception("Unable to find mixerUI GameObject"); GameObject gameObjectToClone4 = ((Component)val.transform.Find("Arrow")).gameObject ?? throw new Exception("Unable to find arrowUI GameObject"); GameObject gameObjectToClone5 = ((Component)val.transform.Find("Output")).gameObject ?? throw new Exception("Unable to find outputUI GameObject"); CreateExpandedRecipesTextUIGameObject(recipeTextUI, recipesContainerUI); GameObject orCreateExpandedRecipesUIGameObject = GetOrCreateExpandedRecipesUIGameObject(recipesContainerUI); foreach (List<IngredientQuantity> expandedRecipe in expandedRecipes) { GameObject parentGameObject = CreateExpandedRecipeUIGameObject(val, orCreateExpandedRecipesUIGameObject); CreateBaseProductUIGameObject(gameObjectToClone, parentGameObject, expandedRecipe); expandedRecipe.Remove(expandedRecipe[0]); int num = expandedRecipe.Count; foreach (IngredientQuantity item in expandedRecipe) { if (num >= 0) { CreatePlusUIGameObject(gameObjectToClone2, parentGameObject); } CreateMixUIGameObject(gameObjectToClone3, parentGameObject, item); num--; } CreateArrowUIGameObject(gameObjectToClone4, parentGameObject); CreateOutputUIGameObject(gameObjectToClone5, parentGameObject, product); } } } public static class GameObjectExtensions { public static void DestroyChildren(this GameObject thisGameObject) { int childCount = thisGameObject.transform.GetChildCount(); for (int num = childCount - 1; num >= 0; num--) { Transform child = thisGameObject.transform.GetChild(num); Object.Destroy((Object)(object)((Component)child).gameObject); } } } [HarmonyPatch(typeof(ProductManagerApp), "SelectProduct")] public static class ProductManager_SelectProduct_Patch { public static void Prefix(ProductManagerApp __instance, ProductEntry entry) { Main.productManagerApp = __instance; Transform val; Transform val2; try { ProductAppDetailPanel detailPanel = __instance.DetailPanel; val = ((Component)detailPanel).transform.Find("Scroll View/Viewport/Content/RecipesContainer"); if ((Object)(object)val == (Object)null) { MelonLogger.Error("Can't find RecipesContainer object in current scene"); return; } val2 = ((Component)detailPanel).transform.Find("Scroll View/Viewport/Content/Recipes"); if ((Object)(object)val2 == (Object)null) { MelonLogger.Error("Can't find Recipes object in current scene"); return; } Transform val3 = val.Find("ExpandedRecipes"); if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(false); } } catch (Exception value) { MelonLogger.Error($"Failed to find Phone UI components: {value}"); return; } List<List<IngredientQuantity>> expandedRecipes; try { expandedRecipes = Main.GetExpandedRecipes(entry); foreach (List<IngredientQuantity> item in expandedRecipes) { string name = ((ItemDefinition)entry.Definition).Name; string text = ""; foreach (IngredientQuantity item2 in item) { if (text.Length > 0) { text += " + "; } text += ((Object)item2.Item).name; } MelonLogger.Msg("Expanded Recipe for \"" + name + "\": " + text); } } catch (Exception value2) { MelonLogger.Error($"Failed to get expanded recipe list: {value2}"); return; } try { Main.BuildUIWithRecipe(entry, expandedRecipes, ((Component)val2).gameObject, ((Component)val).gameObject); } catch (Exception value3) { MelonLogger.Error($"Exception raised building UI component: {value3}"); } } } }