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 10 months 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(); } } }