Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Persistent Nutrients v1.0.1
PersistentNutrients.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using HarmonyLib; using Il2CppFishNet.Object; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.Employees; using Il2CppScheduleOne.EntityFramework; using Il2CppScheduleOne.Growing; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.ObjectScripts; using Il2CppScheduleOne.Persistence; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using MelonLoader; using MelonLoader.Utils; using Newtonsoft.Json; using PersistentNutrients; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(global::PersistentNutrients.PersistentNutrients), "PersistentNutrients Mod", "1.0.1", "Shootex20", null)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyVersion("0.0.0.0")] namespace PersistentNutrients; public class PersistentNutrients : MelonMod { [HarmonyPatch(typeof(Pot), "OnPlantFullyHarvested")] public static class OnPlantFullyHarvested_Patch { [HarmonyPrefix] public static void Prefix(Pot __instance) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } _ = ((BuildableItem)__instance).GUID; if (0 == 0) { Guid gUID = ((BuildableItem)__instance).GUID; Guid guid = new Guid(Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)((Guid)(ref gUID)).ToByteArray())); if (((GrowContainer)__instance)._remainingSoilUses > 0 && ((GrowContainer)__instance).AppliedAdditives != null && (Object)(object)__instance.Plant != (Object)null) { SaveFertilizers(__instance, guid); } else if (((GrowContainer)__instance)._remainingSoilUses <= 0 && persistentFertilizers.ContainsKey(guid)) { persistentFertilizers.Remove(guid); MelonLogger.Msg($"[PersistentNutrientsMod] Soil depleted, cleared fertilizer data for pot {guid}"); } } } } [HarmonyPatch(typeof(Pot), "PlantSeed_Server")] public static class PlantSeed_Patch { [CompilerGenerated] private sealed class <WaitAndRestore>d__1 : IEnumerator<object>, IDisposable, IEnumerator { private int <>1__state; private object <>2__current; public Pot pot; public SavedPotFertilizerData fertData; public Guid potGuid; private int <attempts>5__1; private int <i>5__2; object IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <WaitAndRestore>d__1(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <>1__state = -2; } private bool MoveNext() { switch (<>1__state) { default: return false; case 0: <>1__state = -1; <attempts>5__1 = 0; goto IL_0060; case 1: <>1__state = -1; <attempts>5__1++; goto IL_0060; case 2: { <>1__state = -1; <i>5__2++; goto IL_00ea; } IL_0060: if (<attempts>5__1 < 100 && ((Object)(object)pot == (Object)null || (Object)(object)pot.Plant == (Object)null)) { <>2__current = null; <>1__state = 1; return true; } if ((Object)(object)pot != (Object)null && (Object)(object)pot.Plant != (Object)null) { <i>5__2 = 0; goto IL_00ea; } MelonLogger.Warning($"[PersistentNutrients Mod] Failed to restore fertilizers for pot {potGuid} - Plant not created after {<attempts>5__1} frames"); break; IL_00ea: if (<i>5__2 < 10) { <>2__current = null; <>1__state = 2; return true; } RestoreFertilizers(pot, fertData); break; } 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(); } } [HarmonyPostfix] public static void Postfix(Pot __instance, string seedID, float normalizedSeedProgress) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } _ = ((BuildableItem)__instance).GUID; if (((NetworkBehaviour)__instance).IsServer) { Guid gUID = ((BuildableItem)__instance).GUID; Guid guid = new Guid(Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)((Guid)(ref gUID)).ToByteArray())); if (persistentFertilizers.TryGetValue(guid, out var value)) { MelonLogger.Msg($"[PersistentNutrients Mod] Found saved fertilizer data for pot {guid}, scheduling restoration..."); MelonCoroutines.Start(WaitAndRestore(__instance, value, guid)); } } } [IteratorStateMachine(typeof(<WaitAndRestore>d__1))] private static IEnumerator WaitAndRestore(Pot pot, SavedPotFertilizerData fertData, Guid potGuid) { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <WaitAndRestore>d__1(0) { pot = pot, fertData = fertData, potGuid = potGuid }; } } [HarmonyPatch(typeof(Pot), "CanApplyAdditive")] public static class CanApplyAdditive_Patch { [HarmonyPrefix] public static bool Prefix(Pot __instance, AdditiveDefinition additiveDef, ref string invalidReason, ref bool __result) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance == (Object)null)) { _ = ((BuildableItem)__instance).GUID; if (!((Object)(object)additiveDef == (Object)null)) { if (!((ItemDefinition)additiveDef).Name.ToLower().Contains("fertilizer")) { return true; } Guid gUID = ((BuildableItem)__instance).GUID; Guid key = new Guid(Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)((Guid)(ref gUID)).ToByteArray())); if (persistentFertilizers.ContainsKey(key)) { invalidReason = "This soil is already fertilized!"; __result = false; return false; } return true; } } return true; } } [HarmonyPatch(typeof(Botanist), "GetGrowContainersForAdditives")] public static class BotanistGetGrowContainersForAdditives_Patch { [HarmonyPostfix] public static void Postfix(ref List<GrowContainer> __result) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) if (__result == null || __result.Count == 0) { return; } List<GrowContainer> list = new List<GrowContainer>(); Enumerator<GrowContainer> enumerator = __result.GetEnumerator(); while (enumerator.MoveNext()) { GrowContainer current = enumerator.Current; if ((Object)(object)current == (Object)null) { continue; } _ = ((BuildableItem)current).GUID; if (false) { continue; } Pot val = ((Il2CppObjectBase)current).TryCast<Pot>(); if ((Object)(object)val != (Object)null) { Guid gUID = ((BuildableItem)val).GUID; Guid guid = new Guid(Il2CppArrayBase<byte>.op_Implicit((Il2CppArrayBase<byte>)(object)((Guid)(ref gUID)).ToByteArray())); if (persistentFertilizers.ContainsKey(guid)) { list.Add(current); MelonLogger.Msg($"[PersistentNutrients Mod] Prevented botanist from fertilizing pot {guid} - soil already fertilized"); } } } foreach (GrowContainer item in list) { __result.Remove(item); } } } [HarmonyPatch(typeof(SaveManager), "Save", new Type[] { typeof(string) })] public static class SaveManager_SaveWithPath_Patch { [HarmonyPrefix] public static void Prefix(string saveFolderPath) { MelonLogger.Msg("[PersistentNutrients Mod] Game saving, persisting fertilizer data..."); SaveFertilizerData(); } } [HarmonyPatch(typeof(SaveManager), "Save", new Type[] { })] public static class SaveManager_SaveNoArgs_Patch { [HarmonyPrefix] public static void Prefix() { MelonLogger.Msg("[PersistentNutrients Mod] Game saving, persisting fertilizer data..."); SaveFertilizerData(); } } [HarmonyPatch(typeof(LoadManager), "StartGame")] public static class LoadManager_StartGame_Patch { [HarmonyPostfix] public static void Postfix(SaveInfo info, bool allowLoadStacking) { if (DebugMode) { MelonLogger.Msg("[PersistentNutrients Mod] LoadManager.StartGame Postfix triggered."); } if (info != null) { string text = $"SaveGame_{info.SaveSlotNumber}"; if (DebugMode) { MelonLogger.Msg("[PersistentNutrients Mod] Using derived slotIdentifier: '" + text + "'"); } currentSaveSlot = text; LoadFertilizerData(); } else { if (DebugMode) { MelonLogger.Msg("[PersistentNutrients Mod] LoadManager.StartGame: SaveInfo was null. Using 'NewGame_Initial'."); } currentSaveSlot = "NewGame_Initial"; persistentFertilizers.Clear(); } } } private static readonly Dictionary<Guid, SavedPotFertilizerData> persistentFertilizers = new Dictionary<Guid, SavedPotFertilizerData>(); private static string currentSaveSlot = "default_slot"; public static bool DebugMode = false; private static string DataFilePath { get { string text = currentSaveSlot; char[] invalidFileNameChars = Path.GetInvalidFileNameChars(); foreach (char oldChar in invalidFileNameChars) { text = text.Replace(oldChar, '_'); } return Path.Combine(MelonEnvironment.UserDataDirectory, "PersistentNutrients_Fertilizers_" + text + ".json"); } } public override void OnInitializeMelon() { ((MelonBase)this).LoggerInstance.Msg("PersistentNutrients Mod initialized - Fertilizer will now persist with soil!"); } public override void OnApplicationQuit() { SaveFertilizerData(); } private static void SaveFertilizers(Pot pot, Guid potGuid) { if (((GrowContainer)pot).AppliedAdditives == null || ((GrowContainer)pot).AppliedAdditives.Count == 0 || (Object)(object)pot.Plant == (Object)null) { return; } List<AdditiveData> list = new List<AdditiveData>(); bool flag = false; Enumerator<AdditiveDefinition> enumerator = ((GrowContainer)pot).AppliedAdditives.GetEnumerator(); while (enumerator.MoveNext()) { AdditiveDefinition current = enumerator.Current; if ((Object)(object)current != (Object)null) { if (((ItemDefinition)current).Name.ToLower().Contains("fertilizer")) { list.Add(new AdditiveData { Name = ((ItemDefinition)current).Name, YieldChange = current.YieldMultiplier, QualityChange = current.QualityChange, InstantGrowth = current.InstantGrowth }); } if (current.InstantGrowth > 0f) { flag = true; } } } if (list.Count > 0) { persistentFertilizers[potGuid] = new SavedPotFertilizerData { Fertilizers = list, YieldLevel = pot.Plant.YieldMultiplier, QualityLevel = pot.Plant.QualityLevel, HadSpeedGrow = flag }; MelonLogger.Msg($"[PersistentNutrients Mod] Saved {list.Count} fertilizer(s) for pot {potGuid} (Yield: {pot.Plant.YieldMultiplier:F2}, Quality: {pot.Plant.QualityLevel:F2}, SpeedGrow: {flag})"); } } private static void RestoreFertilizers(Pot pot, SavedPotFertilizerData fertData) { if (!((Object)(object)pot.Plant == (Object)null) && fertData?.Fertilizers != null) { pot.Plant.YieldMultiplier = fertData.YieldLevel; pot.Plant.QualityLevel = fertData.QualityLevel; } } private static void SaveFertilizerData() { try { FertilizerDataCollection fertilizerDataCollection = new FertilizerDataCollection(); foreach (KeyValuePair<Guid, SavedPotFertilizerData> persistentFertilizer in persistentFertilizers) { fertilizerDataCollection.Entries.Add(new FertilizerEntry { PotGUID = persistentFertilizer.Key.ToString(), Data = persistentFertilizer.Value }); } string contents = JsonConvert.SerializeObject((object)fertilizerDataCollection, (Formatting)1); File.WriteAllText(DataFilePath, contents); MelonLogger.Msg($"[PersistentNutrients Mod] Saved {fertilizerDataCollection.Entries.Count} pot(s) with persistent fertilizers to {DataFilePath}"); } catch (Exception ex) { MelonLogger.Error("[PersistentNutrients Mod] Error saving fertilizer data: " + ex.Message); } } private static void LoadFertilizerData() { try { if (!File.Exists(DataFilePath)) { MelonLogger.Msg("[PersistentNutrients Mod] No save file found at " + DataFilePath + ", starting fresh."); persistentFertilizers.Clear(); return; } string text = File.ReadAllText(DataFilePath); FertilizerDataCollection fertilizerDataCollection = JsonConvert.DeserializeObject(text, typeof(FertilizerDataCollection)) as FertilizerDataCollection; persistentFertilizers.Clear(); if (fertilizerDataCollection?.Entries != null) { foreach (FertilizerEntry entry in fertilizerDataCollection.Entries) { if (Guid.TryParse(entry.PotGUID, out var result)) { persistentFertilizers[result] = entry.Data; } } } MelonLogger.Msg($"[PersistentNutrients Mod] Loaded {persistentFertilizers.Count} pot(s) with persistent fertilizers from {DataFilePath}"); } catch (Exception ex) { MelonLogger.Error("[PersistentNutrients Mod] Error loading fertilizer data: " + ex.Message); persistentFertilizers.Clear(); } } } [Serializable] public class AdditiveData { public string Name { get; set; } public float YieldChange { get; set; } public float QualityChange { get; set; } public float InstantGrowth { get; set; } } [Serializable] public class SavedPotFertilizerData { public List<AdditiveData> Fertilizers { get; set; } public float YieldLevel { get; set; } public float QualityLevel { get; set; } public bool HadSpeedGrow { get; set; } } [Serializable] public class FertilizerEntry { public string PotGUID { get; set; } public SavedPotFertilizerData Data { get; set; } } [Serializable] public class FertilizerDataCollection { public List<FertilizerEntry> Entries { get; set; } = new List<FertilizerEntry>(); }