Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of SaveUtility v1.0.5
ScheduleOneSaveUtility.dll
Decompiled a month agousing System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using HarmonyLib; using MelonLoader; using ScheduleOne.DevUtilities; using ScheduleOne.Persistence; using ScheduleOne.Persistence.Datas; using ScheduleOne.UI.MainMenu; using ScheduleOneSaveUtility; using ScheduleOneSaveUtility.UI; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "SaveUtility", "1.0.0", "MaxtorCoder", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = "")] [assembly: AssemblyCompany("ScheduleOneSaveUtility")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.5")] [assembly: AssemblyProduct("ScheduleOneSaveUtility")] [assembly: AssemblyTitle("ScheduleOneSaveUtility")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace ScheduleOneSaveUtility { public class Core : MelonMod { public override void OnInitializeMelon() { ((MelonBase)this).LoggerInstance.Msg("Initialized."); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (sceneName == "Menu") { UIManager.FindContinueButton(); } } } } namespace ScheduleOneSaveUtility.UI { public static class UIManager { private static GameObject _continueButtonPrefab; public static void FindContinueButton() { _continueButtonPrefab = GameObject.Find("MainMenu/Home/Bank/Continue"); } public static void AddDuplicateSaveButtons() { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Expected O, but got Unknown //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) if (!Object.op_Implicit((Object)(object)_continueButtonPrefab)) { Melon<Core>.Logger.Error("Failed to find continue button in main menu..."); return; } GameObject val = GameObject.Find("MainMenu/Continue/Container"); for (int i = 0; i < val.transform.childCount; i++) { Transform child = val.transform.GetChild(i); if (!Object.op_Implicit((Object)(object)child)) { Melon<Core>.Logger.Error($"Slot for index: {i} is null."); continue; } Transform val2 = child.Find("Container"); if (!Object.op_Implicit((Object)(object)val2)) { Melon<Core>.Logger.Error($"Container {i} could not be found or is not active."); continue; } int saveIndex = i; GameObject obj = UIUtils.CreateButtonFromPrefab("Duplicate", 16f, _continueButtonPrefab, null, (UnityAction)delegate { DuplicateSaveButtonEvent(saveIndex); }); obj.transform.SetParent(((Component)val2).transform, false); obj.transform.position = ((Component)val2).transform.position; obj.transform.localPosition = new Vector3(451f, 15f, 0f); GameObject obj2 = UIUtils.CreateButtonFromPrefab("Delete", 16f, _continueButtonPrefab, (Color?)new Color(1f, 0.28f, 0.14f, 1f), (UnityAction)delegate { DeleteSaveButtonEvent(saveIndex); }); obj2.transform.SetParent(((Component)val2).transform, false); obj2.transform.position = ((Component)val2).transform.position; obj2.transform.localPosition = new Vector3(575f, 15f, 0f); } } private static void DuplicateSaveButtonEvent(int currentSaveIndex) { SaveInfo val = LoadManager.SaveGames[currentSaveIndex]; if (val == null) { return; } int num = -1; for (int i = 0; i < LoadManager.SaveGames.Length; i++) { if (LoadManager.SaveGames[i] == null) { num = i; break; } } if (num != -1) { string text = Path.Combine(Singleton<SaveManager>.Instance.IndividualSavesContainerPath, $"SaveGame_{num + 1}"); if (!Directory.Exists(text)) { val.SaveSlotNumber = num + 1; val.DateCreated = DateTime.Now; val.OrganisationName = (val.OrganisationName.Contains("Duplicate") ? val.OrganisationName.Replace($"Duplicate {num - 1}", $"Duplicate {num}") : $"{val.OrganisationName} (Duplicate {num})"); Melon<Core>.Logger.Msg($"Saving game {currentSaveIndex} -> {num} ({val.OrganisationName})"); CopyDirectory(Path.Combine(Singleton<SaveManager>.Instance.IndividualSavesContainerPath, $"SaveGame_{currentSaveIndex + 1}"), text, recursive: true); GameData val2 = JsonUtility.FromJson<GameData>(File.ReadAllText(Path.Combine(text, "Game.json"))); val2.OrganisationName = val.OrganisationName; File.WriteAllText(Path.Combine(text, "Game.json"), JsonUtility.ToJson((object)val2)); Singleton<LoadManager>.Instance.RefreshSaveInfo(); } } } private static void DeleteSaveButtonEvent(int currentSaveIndex) { if (LoadManager.SaveGames[currentSaveIndex] != null) { Directory.Delete(Path.Combine(Singleton<SaveManager>.Instance.IndividualSavesContainerPath, $"SaveGame_{currentSaveIndex + 1}"), recursive: true); Singleton<LoadManager>.Instance.RefreshSaveInfo(); } } private static void CopyDirectory(string sourceDir, string destinationDir, bool recursive) { DirectoryInfo directoryInfo = new DirectoryInfo(sourceDir); if (!directoryInfo.Exists) { throw new DirectoryNotFoundException("Source directory not found: " + directoryInfo.FullName); } DirectoryInfo[] directories = directoryInfo.GetDirectories(); Directory.CreateDirectory(destinationDir); FileInfo[] files = directoryInfo.GetFiles(); foreach (FileInfo fileInfo in files) { string destFileName = Path.Combine(destinationDir, fileInfo.Name); fileInfo.CopyTo(destFileName); } if (recursive) { DirectoryInfo[] array = directories; foreach (DirectoryInfo directoryInfo2 in array) { string destinationDir2 = Path.Combine(destinationDir, directoryInfo2.Name); CopyDirectory(directoryInfo2.FullName, destinationDir2, recursive: true); } } } } public static class UIUtils { public static GameObject CreateButtonFromPrefab(string text, float fontSize, GameObject prefab, Color? fontColor = null, UnityAction onClick = null) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate<GameObject>(prefab); ((Object)val).name = text + "Button"; TextMeshProUGUI componentInChildren = val.GetComponentInChildren<TextMeshProUGUI>(); ((TMP_Text)componentInChildren).text = text; ((TMP_Text)componentInChildren).fontSize = fontSize; if (fontColor.HasValue) { ((Graphic)componentInChildren).color = fontColor.Value; } if (onClick != null) { Button component = val.GetComponent<Button>(); ((UnityEventBase)component.onClick).RemoveAllListeners(); ((UnityEvent)component.onClick).AddListener(onClick); } return val; } } } namespace ScheduleOneSaveUtility.Patches.UI { [HarmonyPatch(typeof(ContinueScreen))] public class ContinueScreenPatches { [HarmonyPatch("Update")] [HarmonyPostfix] public static void UpdatePostfix(ContinueScreen __instance) { if (!Object.op_Implicit((Object)(object)((Component)__instance).gameObject.GetComponent<ContinueScreenComponent>())) { ((Component)__instance).gameObject.AddComponent<ContinueScreenComponent>(); } } } public class ContinueScreenComponent : MonoBehaviour { private void Awake() { Debug.Log((object)"Adding \"Duplicate Save\" buttons..."); UIManager.AddDuplicateSaveButtons(); } } }