Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of ModSettings v0.1.1
Mods/ModSettings.dll
Decompiled 2 days agousing System; using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.UI.MainMenu; using Il2CppScheduleOne.UI.Settings; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using Microsoft.CodeAnalysis; using ModSettings; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Main), "Mod Settings", "0.1.1", "holyfurries", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("ModSettings")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.1.0")] [assembly: AssemblyInformationalVersion("0.1.1+4bec1ce0c75c50eeaf77566fdae84ecd9678f64f")] [assembly: AssemblyProduct("ModSettings")] [assembly: AssemblyTitle("ModSettings")] [assembly: AssemblyVersion("0.1.1.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ModSettings { public sealed class Main : MelonMod { private const float save_delay_seconds = 1f; public override void OnInitializeMelon() { Tab.install(((MelonBase)this).HarmonyInstance); } public override void OnUpdate() { float unsaved_since_seconds = Settings.unsaved_since_seconds; if (float.IsNaN(unsaved_since_seconds) || Time.unscaledTime - unsaved_since_seconds < 1f) { return; } Settings.unsaved_since_seconds = float.NaN; try { MelonPreferences.Save(); } catch (Exception ex) { ((MelonBase)this).LoggerInstance.Warning("Could not save preferences: " + ex.Message); } } } internal enum ControlKind { Toggle, Slider, Dropdown } internal sealed class Setting { public readonly string mod; public readonly string label; public readonly ControlKind kind; public readonly float minimum; public readonly float maximum; public readonly bool whole_numbers; public readonly string[] options; public readonly Func<float> read; public readonly Action<float> write; public Action<float>? show; public Setting(string mod, string label, ControlKind kind, float minimum, float maximum, bool whole_numbers, string[] options, Func<float> read, Action<float> write) { if (string.IsNullOrWhiteSpace(mod) || mod.Length > 32) { throw new ArgumentOutOfRangeException("mod"); } if (string.IsNullOrWhiteSpace(label) || label.Length > 48) { throw new ArgumentOutOfRangeException("label"); } if (!float.IsFinite(minimum) || !float.IsFinite(maximum) || maximum <= minimum) { throw new ArgumentOutOfRangeException("maximum"); } if (kind == ControlKind.Dropdown && (options.Length < 2 || options.Length > 32)) { throw new ArgumentOutOfRangeException("options"); } this.mod = mod; this.label = label; this.kind = kind; this.minimum = minimum; this.maximum = maximum; this.whole_numbers = whole_numbers; this.options = options; this.read = read; this.write = write; } public static string spaced(string name) { if (string.IsNullOrEmpty(name) || name.Length > 48) { throw new ArgumentOutOfRangeException("name"); } StringBuilder stringBuilder = new StringBuilder(name.Length + 8); for (int i = 0; i < name.Length; i++) { if ((i > 0 && char.IsUpper(name[i]) && (char.IsLower(name[i - 1]) || (i + 1 < name.Length && char.IsLower(name[i + 1])))) || name[i] == '_') { stringBuilder.Append(' '); } if (name[i] != '_') { stringBuilder.Append(name[i]); } } return stringBuilder.ToString(); } public float clamp(float value) { if (!float.IsFinite(value)) { return minimum; } float num = Math.Clamp(value, minimum, maximum); if (!whole_numbers && kind == ControlKind.Slider) { return num; } return MathF.Round(num); } public string format(float value) { float num = clamp(value); if (whole_numbers) { return num.ToString("0", CultureInfo.InvariantCulture); } return num.ToString((maximum - minimum >= 20f) ? "0" : "0.00", CultureInfo.InvariantCulture); } } internal sealed class Registry { public const int setting_count_max = 128; private readonly Setting[] settings = new Setting[128]; public int count { get; private set; } public void add(Setting setting) { if (count == 128) { throw new InvalidOperationException("Too many mod settings registered."); } for (int i = 0; i < count; i++) { if (settings[i].mod == setting.mod && settings[i].label == setting.label) { throw new InvalidOperationException(setting.mod + " already registered \"" + setting.label + "\"."); } } int num = count; for (int j = 0; j < count; j++) { if (settings[j].mod == setting.mod) { num = j + 1; } } Array.Copy(settings, num, settings, num + 1, count - num); settings[num] = setting; count++; } public Setting at(int index) { if (index < 0 || index >= count) { throw new ArgumentOutOfRangeException("index"); } return settings[index]; } public bool starts_group(int index) { if (index != 0) { return at(index).mod != at(index - 1).mod; } return true; } } public static class Settings { internal static readonly Registry registry = new Registry(); internal static float unsaved_since_seconds = float.NaN; public static void toggle(string mod, string label, MelonPreferences_Entry<bool> entry) { if (entry == null) { throw new ArgumentNullException("entry"); } register(new Setting(mod, label, ControlKind.Toggle, 0f, 1f, whole_numbers: true, Array.Empty<string>(), () => (!entry.Value) ? 0f : 1f, delegate(float value) { entry.Value = value >= 0.5f; })); ((MelonEventBase<LemonAction<bool, bool>>)(object)entry.OnEntryValueChanged).Subscribe((LemonAction<bool, bool>)delegate(bool _, bool value) { registry_show(mod, label, value ? 1f : 0f); }, 0, false); } public static void slider(string mod, string label, MelonPreferences_Entry<float> entry, float minimum, float maximum, bool whole_numbers = false) { if (entry == null) { throw new ArgumentNullException("entry"); } register(new Setting(mod, label, ControlKind.Slider, minimum, maximum, whole_numbers, Array.Empty<string>(), () => entry.Value, delegate(float value) { entry.Value = value; })); ((MelonEventBase<LemonAction<float, float>>)(object)entry.OnEntryValueChanged).Subscribe((LemonAction<float, float>)delegate(float _, float value) { registry_show(mod, label, value); }, 0, false); } public static void dropdown<T>(string mod, string label, MelonPreferences_Entry<T> entry) where T : struct, Enum { if (entry == null) { throw new ArgumentNullException("entry"); } T[] values = Enum.GetValues<T>(); string[] options = Array.ConvertAll(Enum.GetNames<T>(), Setting.spaced); register(new Setting(mod, label, ControlKind.Dropdown, 0f, Math.Max(1, values.Length - 1), whole_numbers: true, options, () => Math.Max(0, Array.IndexOf(values, entry.Value)), delegate(float value) { entry.Value = values[(int)value]; })); ((MelonEventBase<LemonAction<T, T>>)(object)entry.OnEntryValueChanged).Subscribe((LemonAction<T, T>)delegate(T _, T value) { registry_show(mod, label, Math.Max(0, Array.IndexOf(values, value))); }, 0, false); } internal static void change(Setting setting, float value, float now_seconds) { float num = setting.clamp(value); if (num != setting.clamp(setting.read())) { setting.write(num); if (float.IsNaN(unsaved_since_seconds)) { unsaved_since_seconds = now_seconds; } } } private static void register(Setting setting) { registry.add(setting); } private static void registry_show(string mod, string label, float value) { for (int i = 0; i < registry.count; i++) { Setting setting = registry.at(i); if (setting.mod == mod && setting.label == label) { setting.show?.Invoke(setting.clamp(value)); } } } } internal static class Tab { private readonly record struct Templates(GameObject panel, SettingsToggle? toggle, SettingsSlider? slider, SettingsDropdown? dropdown); private const string tab_name = "ModSettingsTab"; private const int category_count_max = 16; private const float header_height = 40f; public static void install(Harmony harmony) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(SettingsScreen), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Tab), "add_tab", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void add_tab(SettingsScreen __instance) { try { build(__instance); } catch (Exception value) { MelonLogger.Warning($"Mod Settings: Tab unavailable, edit UserData/MelonPreferences.cfg instead: {value}"); } } private static void build(SettingsScreen screen) { //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02b0: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: Unknown result type (might be due to invalid IL or missing references) //IL_02c5: Expected O, but got Unknown Registry registry = Settings.registry; if (registry.count == 0) { return; } Il2CppReferenceArray<SettingsCategory> categories = screen.Categories; if (categories == null || ((Il2CppArrayBase<SettingsCategory>)(object)categories).Length == 0 || ((Il2CppArrayBase<SettingsCategory>)(object)categories).Length >= 16) { throw new InvalidOperationException("Unexpected settings categories."); } SettingsCategory val = ((Il2CppArrayBase<SettingsCategory>)(object)categories)[((Il2CppArrayBase<SettingsCategory>)(object)categories).Length - 1]; if ((Object)(object)val.Toggle == (Object)null || (Object)(object)val.Panel == (Object)null) { throw new InvalidOperationException("Settings category is incomplete."); } if ((Object)(object)((Component)val.Toggle).transform.parent.Find("ModSettingsTab") != (Object)null) { return; } Templates templates = find_templates(categories); GameObject val2 = Object.Instantiate<GameObject>(templates.panel, templates.panel.transform.parent); ((Object)val2).name = "ModSettings"; val2.SetActive(false); Transform transform = ((Component)val2.GetComponentInChildren<VerticalLayoutGroup>(true)).transform; for (int i = 0; i < transform.childCount; i++) { ((Component)transform.GetChild(i)).gameObject.SetActive(false); } float row_width = ((transform.childCount > 0) ? ((Component)transform.GetChild(0)).GetComponent<RectTransform>().sizeDelta.x : 450f); TextMeshProUGUI componentInChildren = templates.panel.GetComponentInChildren<TextMeshProUGUI>(true); for (int j = 0; j < registry.count; j++) { Setting setting = registry.at(j); if (registry.starts_group(j)) { add_header(transform, componentInChildren, setting.mod, row_width); } switch (setting.kind) { case ControlKind.Toggle: add_toggle(transform, templates.toggle, setting); break; case ControlKind.Slider: add_slider(transform, templates.slider, setting); break; case ControlKind.Dropdown: add_dropdown(transform, templates.dropdown, setting); break; default: throw new InvalidOperationException("Unknown control kind."); } } Toggle component = Object.Instantiate<GameObject>(((Component)val.Toggle).gameObject, ((Component)val.Toggle).transform.parent).GetComponent<Toggle>(); ((Object)component).name = "ModSettingsTab"; component.SetIsOnWithoutNotify(false); TextMeshProUGUI componentInChildren2 = ((Component)component).GetComponentInChildren<TextMeshProUGUI>(true); if ((Object)(object)componentInChildren2 != (Object)null) { ((TMP_Text)componentInChildren2).text = "Mods"; } int index = ((Il2CppArrayBase<SettingsCategory>)(object)categories).Length; Il2CppReferenceArray<SettingsCategory> val3 = new Il2CppReferenceArray<SettingsCategory>((long)(index + 1)); for (int k = 0; k < index; k++) { ((Il2CppArrayBase<SettingsCategory>)(object)val3)[k] = ((Il2CppArrayBase<SettingsCategory>)(object)categories)[k]; } ((Il2CppArrayBase<SettingsCategory>)(object)val3)[index] = new SettingsCategory { Toggle = component, Panel = val2 }; screen.Categories = val3; ((UnityEvent<bool>)(object)component.onValueChanged).AddListener(UnityAction<bool>.op_Implicit((Action<bool>)delegate(bool selected) { if (selected) { screen.ShowCategory(index); for (int l = 0; l < registry.count; l++) { registry.at(l).show?.Invoke(registry.at(l).clamp(registry.at(l).read())); } } })); fit_tabs(((Component)screen).GetComponent<RectTransform>(), ((Component)component).transform.parent); } private static Templates find_templates(Il2CppReferenceArray<SettingsCategory> categories) { GameObject val = null; int num = -1; SettingsToggle val2 = null; SettingsSlider val3 = null; SettingsDropdown val4 = null; for (int i = 0; i < ((Il2CppArrayBase<SettingsCategory>)(object)categories).Length; i++) { GameObject panel = ((Il2CppArrayBase<SettingsCategory>)(object)categories)[i].Panel; if ((Object)(object)panel == (Object)null) { continue; } SettingsToggle componentInChildren = panel.GetComponentInChildren<SettingsToggle>(true); SettingsSlider componentInChildren2 = panel.GetComponentInChildren<SettingsSlider>(true); SettingsDropdown componentInChildren3 = panel.GetComponentInChildren<SettingsDropdown>(true); if (val2 == null) { val2 = componentInChildren; } if (val3 == null) { val3 = componentInChildren2; } if (val4 == null) { val4 = componentInChildren3; } if (!((Object)(object)panel.GetComponentInChildren<VerticalLayoutGroup>(true) == (Object)null)) { int num2 = (((Object)(object)componentInChildren != (Object)null) ? 1 : 0) + (((Object)(object)componentInChildren2 != (Object)null) ? 1 : 0) + (((Object)(object)componentInChildren3 != (Object)null) ? 1 : 0); if (num2 > num) { val = panel; num = num2; val2 = componentInChildren ?? val2; val3 = componentInChildren2 ?? val3; val4 = componentInChildren3 ?? val4; } } } if ((Object)(object)val == (Object)null) { throw new InvalidOperationException("No scrolling settings panel to copy."); } return new Templates(val, val2, val3, val4); } private static void add_header(Transform rows, TextMeshProUGUI? label_template, string mod, float row_width) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Header"); val.transform.SetParent(rows, false); val.AddComponent<RectTransform>().sizeDelta = new Vector2(row_width, 40f); TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>(); if ((Object)(object)label_template != (Object)null) { ((TMP_Text)val2).font = ((TMP_Text)label_template).font; ((TMP_Text)val2).fontSize = ((TMP_Text)label_template).fontSize + 2f; } ((TMP_Text)val2).fontStyle = (FontStyles)1; ((TMP_Text)val2).alignment = (TextAlignmentOptions)1025; ((TMP_Text)val2).margin = new Vector4(10f, 0f, 0f, 4f); ((Graphic)val2).raycastTarget = false; ((TMP_Text)val2).text = mod; } private static void add_toggle(Transform rows, SettingsToggle? template, Setting setting) { if ((Object)(object)template == (Object)null) { throw new InvalidOperationException("No switch row to copy."); } GameObject val = Object.Instantiate<GameObject>(((Component)template).gameObject, rows); Object.DestroyImmediate((Object)(object)val.GetComponent<SettingsToggle>()); UIToggle toggle = val.GetComponent<UIToggle>() ?? throw new InvalidOperationException("Switch row has no UIToggle."); ((UIOption)toggle).optionName = setting.label; name_row(val, setting.label); toggle.OnChanged.AddListener(UnityAction<bool>.op_Implicit((Action<bool>)delegate(bool value) { Settings.change(setting, value ? 1f : 0f, Time.unscaledTime); })); setting.show = delegate(float value) { if ((Object)(object)toggle != (Object)null) { toggle.SetStateWithoutNotify(value >= 0.5f); } }; val.SetActive(true); } private static void add_slider(Transform rows, SettingsSlider? template, Setting setting) { if ((Object)(object)template == (Object)null) { throw new InvalidOperationException("No slider row to copy."); } GameObject val = Object.Instantiate<GameObject>(((Component)(((Object)(object)((Component)((Component)template).transform.parent).GetComponent<UISlider>() != (Object)null) ? ((Component)template).transform.parent : ((Component)template).transform)).gameObject, rows); Object.DestroyImmediate((Object)(object)val.GetComponentInChildren<SettingsSlider>(true)); Slider slider = val.GetComponentInChildren<Slider>(true) ?? throw new InvalidOperationException("Slider row has no Slider."); UISlider componentInChildren = val.GetComponentInChildren<UISlider>(true); TextMeshProUGUI value_text = ((componentInChildren != null) ? componentInChildren.valueText : null); if ((Object)(object)componentInChildren != (Object)null) { ((UIOption)componentInChildren).optionName = setting.label; componentInChildren.canUpdateValueText = false; componentInChildren.stepSize = (setting.whole_numbers ? 1f : ((setting.maximum - setting.minimum) / 20f)); } name_row(val, setting.label); slider.minValue = setting.minimum; slider.maxValue = setting.maximum; slider.wholeNumbers = setting.whole_numbers; if ((Object)(object)value_text != (Object)null) { ((Component)value_text).gameObject.SetActive(true); ((TMP_Text)value_text).alpha = 1f; } ((UnityEvent<float>)(object)slider.onValueChanged).AddListener(UnityAction<float>.op_Implicit((Action<float>)delegate(float value) { Settings.change(setting, value, Time.unscaledTime); if ((Object)(object)value_text != (Object)null) { ((TMP_Text)value_text).text = setting.format(value); } })); setting.show = delegate(float value) { if (!((Object)(object)slider == (Object)null)) { slider.SetValueWithoutNotify(value); if ((Object)(object)value_text != (Object)null) { ((TMP_Text)value_text).text = setting.format(value); } } }; val.SetActive(true); } private static void add_dropdown(Transform rows, SettingsDropdown? template, Setting setting) { if ((Object)(object)template == (Object)null) { throw new InvalidOperationException("No dropdown row to copy."); } GameObject val = Object.Instantiate<GameObject>(((Component)((Component)template).transform.parent).gameObject, rows); Object.DestroyImmediate((Object)(object)val.GetComponentInChildren<SettingsDropdown>(true)); TMP_Dropdown dropdown = val.GetComponentInChildren<TMP_Dropdown>(true) ?? throw new InvalidOperationException("Dropdown row has no TMP_Dropdown."); name_row(val, setting.label); List<string> val2 = new List<string>(); string[] options = setting.options; foreach (string text in options) { val2.Add(text); } dropdown.ClearOptions(); dropdown.AddOptions(val2); ((UnityEvent<int>)(object)dropdown.onValueChanged).AddListener(UnityAction<int>.op_Implicit((Action<int>)delegate(int value) { Settings.change(setting, value, Time.unscaledTime); })); setting.show = delegate(float value) { if ((Object)(object)dropdown != (Object)null) { dropdown.SetValueWithoutNotify((int)value); } }; val.SetActive(true); } private static void name_row(GameObject row, string label) { ((Object)row).name = label; foreach (TextMeshProUGUI componentsInChild in row.GetComponentsInChildren<TextMeshProUGUI>(true)) { if (!((Object)(object)((TMP_Text)componentsInChild).transform.parent != (Object)(object)row.transform)) { if (((Object)componentsInChild).name.StartsWith("Option Name", StringComparison.Ordinal) || ((Object)componentsInChild).name == "Label") { ((TMP_Text)componentsInChild).text = label; } else if (((Object)componentsInChild).name.StartsWith("Label (", StringComparison.Ordinal)) { ((Component)componentsInChild).gameObject.SetActive(false); } } } } private static void fit_tabs(RectTransform screen, Transform tabs) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) int num = 0; for (int i = 0; i < tabs.childCount; i++) { if (((Component)tabs.GetChild(i)).gameObject.activeSelf) { num++; } } if (num == 0 || num > 16) { throw new InvalidOperationException("Unexpected settings tab count."); } Rect rect = screen.rect; float num2 = (((Rect)(ref rect)).width - 42f - 5f * (float)(num - 1)) / (float)num; if (!float.IsFinite(num2) || num2 < 30f) { throw new InvalidOperationException("Settings tabs do not fit."); } for (int j = 0; j < tabs.childCount; j++) { RectTransform component = ((Component)tabs.GetChild(j)).GetComponent<RectTransform>(); component.sizeDelta = new Vector2(Math.Min(component.sizeDelta.x, num2), component.sizeDelta.y); TextMeshProUGUI componentInChildren = ((Component)component).GetComponentInChildren<TextMeshProUGUI>(true); if (!((Object)(object)componentInChildren == (Object)null) && !((TMP_Text)componentInChildren).enableAutoSizing) { ((TMP_Text)componentInChildren).fontSizeMax = ((TMP_Text)componentInChildren).fontSize; ((TMP_Text)componentInChildren).fontSizeMin = 6f; ((TMP_Text)componentInChildren).enableAutoSizing = true; } } } } }