using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using SilksongTweaks.Modules; using SilksongTweaks.Rules; using SilksongTweaks.Ui; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SilksongTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+58ec265190fc9f177178d92ee57f7273a21c6c75")] [assembly: AssemblyProduct("SilksongTweaks")] [assembly: AssemblyTitle("SilksongTweaks")] [assembly: AssemblyVersion("1.0.0.0")] namespace SilksongTweaks { public enum HookKind { Method, PropertyGetter, Field } public sealed class HookTarget { public string DeclaringType { get; } public string Member { get; } public HookKind Kind { get; } public string UsedBy { get; } public HookTarget(string declaringType, string member, HookKind kind, string usedBy) { DeclaringType = declaringType; Member = member; Kind = kind; UsedBy = usedBy; } public override string ToString() { return $"{DeclaringType}.{Member} ({Kind}, for {UsedBy})"; } } public static class HookTargets { public static readonly HookTarget HeroRespawned = new HookTarget("HeroController", "HeroRespawned", HookKind.Method, "ReturnToDeath"); public static readonly HookTarget Die = new HookTarget("HeroController", "Die", HookKind.Method, "ReturnToDeath, KeepRosaries"); public static readonly HookTarget BeginSceneTransition = new HookTarget("GameManager", "BeginSceneTransition", HookKind.Method, "ReturnToDeath"); public static readonly HookTarget SetHazardRespawn = new HookTarget("HeroController", "SetHazardRespawn", HookKind.Method, "ReturnToDeath"); public static readonly HookTarget CocoonBroken = new HookTarget("HeroController", "CocoonBroken", HookKind.Method, "KeepRosaries"); public static readonly HookTarget CorpseMoneyPool = new HookTarget("PlayerData", "HeroCorpseMoneyPool", HookKind.Field, "KeepRosaries"); public static readonly HookTarget CurrentMaxHealth = new HookTarget("PlayerData", "CurrentMaxHealth", HookKind.PropertyGetter, "MaxHealth"); public static readonly HookTarget GetInt = new HookTarget("PlayerData", "GetInt", HookKind.Method, "MaxHealth"); public static readonly HookTarget MaxHealthField = new HookTarget("PlayerData", "maxHealth", HookKind.Field, "MaxHealth"); public static readonly HookTarget PlayerDataAddHealth = new HookTarget("PlayerData", "AddHealth", HookKind.Method, "MaxHealth"); public static readonly HookTarget PlayerDataTakeHealth = new HookTarget("PlayerData", "TakeHealth", HookKind.Method, "MaxHealth"); public static readonly HookTarget PlayerDataMaxHealth = new HookTarget("PlayerData", "MaxHealth", HookKind.Method, "MaxHealth"); public static readonly HookTarget TakeDamage = new HookTarget("HeroController", "TakeDamage", HookKind.Method, "DamageTaken"); public static IReadOnlyList All { get; } = new List { HeroRespawned, Die, BeginSceneTransition, SetHazardRespawn, CocoonBroken, CorpseMoneyPool, CurrentMaxHealth, GetInt, MaxHealthField, PlayerDataAddHealth, PlayerDataTakeHealth, PlayerDataMaxHealth, TakeDamage }; } public interface ITweakModule { string Id { get; } string DisplayName { get; } string Description { get; } TweakStatus Status { get; } long LastFiredUtcTicks { get; } IReadOnlyList Settings { get; } void BindConfig(ConfigFile config); TweakStatus TryApply(Harmony harmony); } public sealed class ModuleRegistry { private readonly List _modules = new List(); private readonly ManualLogSource _log; public IReadOnlyList Modules => _modules; public int ActiveCount { get; private set; } public ModuleRegistry(ManualLogSource log) { _log = log; } public void Add(ITweakModule module) { _modules.Add(module); } public void ApplyAll(ConfigFile config, Harmony harmony) { ActiveCount = 0; foreach (ITweakModule module in _modules) { try { module.BindConfig(config); } catch (Exception arg) { _log.LogError((object)$"[{module.Id}] config binding failed: {arg}"); continue; } TweakStatus tweakStatus = module.TryApply(harmony); if (tweakStatus.State == TweakState.Unavailable) { _log.LogWarning((object)("[" + module.Id + "] UNAVAILABLE: " + tweakStatus.Reason)); continue; } ActiveCount++; _log.LogInfo((object)("[" + module.Id + "] hooked OK")); } _log.LogInfo((object)$"{ActiveCount}/{_modules.Count} tweak(s) hooked successfully"); } } [BepInPlugin("com.dloizides.silksongtweaks", "Silksong Tweaks", "1.0.0")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "com.dloizides.silksongtweaks"; public const string PluginName = "Silksong Tweaks"; public const string PluginVersion = "1.0.0"; private ModuleRegistry _registry; private TweakWindow _window; private ConfigEntry _toggleKey; private ConfigEntry _gamepadToggleButton; private ConfigEntry _freezeWhileOpen; private bool _uiBroken; private bool _wasVisible; private float _timeScaleBeforeOpen = 1f; public static Plugin Instance { get; private set; } public static ManualLogSource Log { get; private set; } private void Awake() { //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown //IL_0112: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; try { _toggleKey = ((BaseUnityPlugin)this).Config.Bind("General", "ToggleKey", (KeyCode)289, "Opens and closes the Silksong Tweaks panel."); _gamepadToggleButton = ((BaseUnityPlugin)this).Config.Bind("General", "GamepadToggleButton", (KeyCode)336, "Gamepad button that opens the panel. JoystickButton6 is Back/View/Share."); _freezeWhileOpen = ((BaseUnityPlugin)this).Config.Bind("General", "FreezeGameWhileOpen", true, "Pause the game while the panel is open, so navigating it does not also move Hornet. Turn off if it conflicts with anything."); _registry = new ModuleRegistry(Log); _registry.Add(new ReturnToDeathModule()); _registry.Add(new KeepRosariesModule()); _registry.Add(new MaxHealthModule()); _registry.Add(new DamageTakenModule()); _registry.ApplyAll(((BaseUnityPlugin)this).Config, new Harmony("com.dloizides.silksongtweaks")); _window = new TweakWindow(_registry); Log.LogInfo((object)string.Format("{0} {1} ready. Press {2} for the panel.", "Silksong Tweaks", "1.0.0", _toggleKey.Value)); } catch (Exception arg) { Log.LogError((object)$"startup failed: {arg}"); } } public Coroutine Run(IEnumerator routine) { return ((MonoBehaviour)this).StartCoroutine(routine); } private void Update() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (_window != null && _toggleKey != null) { if (Input.GetKeyDown(_toggleKey.Value) || (_gamepadToggleButton != null && Input.GetKeyDown(_gamepadToggleButton.Value))) { _window.Visible = !_window.Visible; } _window.HandleInput(Time.unscaledDeltaTime); ApplyFreeze(); } } private void ApplyFreeze() { if (_freezeWhileOpen == null || !_freezeWhileOpen.Value) { _wasVisible = _window.Visible; } else if (_window.Visible != _wasVisible) { _wasVisible = _window.Visible; if (_window.Visible) { _timeScaleBeforeOpen = Time.timeScale; Time.timeScale = 0f; } else { Time.timeScale = ((_timeScaleBeforeOpen <= 0f) ? 1f : _timeScaleBeforeOpen); } } } private void OnDestroy() { if (_wasVisible && Time.timeScale == 0f) { Time.timeScale = 1f; } } private void OnGUI() { if (_window == null || _uiBroken) { return; } try { _window.Draw(); } catch (Exception arg) { _uiBroken = true; Log.LogError((object)$"UI disabled after a draw error: {arg}"); } } } public interface ISettingRow { string Label { get; } string Tooltip { get; } } public sealed class BoolRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry Entry { get; } public BoolRow(string label, string tooltip, ConfigEntry entry) { Label = label; Tooltip = tooltip; Entry = entry; } } public sealed class IntRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry Entry { get; } public int Min { get; } public int Max { get; } public IntRow(string label, string tooltip, ConfigEntry entry, int min, int max) { Label = label; Tooltip = tooltip; Entry = entry; Min = min; Max = max; } } public sealed class FloatRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry Entry { get; } public float Min { get; } public float Max { get; } public string Format { get; } public FloatRow(string label, string tooltip, ConfigEntry entry, float min, float max, string format) { Label = label; Tooltip = tooltip; Entry = entry; Min = min; Max = max; Format = format; } } public sealed class KeyRow : ISettingRow { public string Label { get; } public string Tooltip { get; } public ConfigEntry Entry { get; } public KeyRow(string label, string tooltip, ConfigEntry entry) { Label = label; Tooltip = tooltip; Entry = entry; } } public enum TweakState { Active, Disabled, Unavailable } public sealed class TweakStatus { public TweakState State { get; } public string Reason { get; } public static TweakStatus Active { get; } = new TweakStatus(TweakState.Active, string.Empty); public static TweakStatus Disabled { get; } = new TweakStatus(TweakState.Disabled, string.Empty); private TweakStatus(TweakState state, string reason) { State = state; Reason = reason; } public static TweakStatus Unavailable(string reason) { return new TweakStatus(TweakState.Unavailable, reason ?? "unknown"); } } } namespace SilksongTweaks.Ui { public static class Conflicts { private static readonly string[] Known = new string[3] { "ReBack.dll", "com.blueraja.rosaries_never_permanently_lost.dll", "CustomDifficulty" }; public static IReadOnlyList Detect() { List list = new List(); try { string pluginPath = Paths.PluginPath; if (string.IsNullOrEmpty(pluginPath) || !Directory.Exists(pluginPath)) { return list; } string[] known = Known; foreach (string text in known) { string path = Path.Combine(pluginPath, text); if (File.Exists(path) || Directory.Exists(path)) { list.Add(text); continue; } string[] files = Directory.GetFiles(pluginPath, text, SearchOption.AllDirectories); int num = 0; if (num < files.Length) { string path2 = files[num]; list.Add(Path.GetFileName(path2)); } } } catch { } return list; } } public sealed class PanelInput { private const float DeadZone = 0.5f; private const float FirstRepeatDelay = 0.35f; private const float RepeatInterval = 0.09f; private static bool _axesUnavailable; private float _verticalHeldFor; private float _horizontalHeldFor; private int _lastVertical; private int _lastHorizontal; public int Vertical { get; private set; } public int Horizontal { get; private set; } public bool Activate { get; private set; } public bool Cancel { get; private set; } public void Sample(float unscaledDelta) { Vertical = Step(RawVertical(), ref _lastVertical, ref _verticalHeldFor, unscaledDelta); Horizontal = Step(RawHorizontal(), ref _lastHorizontal, ref _horizontalHeldFor, unscaledDelta); Activate = Input.GetKeyDown((KeyCode)330) || Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)32); Cancel = Input.GetKeyDown((KeyCode)331) || Input.GetKeyDown((KeyCode)27); } private static int RawVertical() { if (Input.GetKey((KeyCode)273)) { return -1; } if (Input.GetKey((KeyCode)274)) { return 1; } float num = SafeAxis("Vertical"); if (num > 0.5f) { return -1; } if (num < -0.5f) { return 1; } return 0; } private static int RawHorizontal() { if (Input.GetKey((KeyCode)276)) { return -1; } if (Input.GetKey((KeyCode)275)) { return 1; } float num = SafeAxis("Horizontal"); if (num < -0.5f) { return -1; } if (num > 0.5f) { return 1; } return 0; } private static float SafeAxis(string name) { if (_axesUnavailable) { return 0f; } try { return Input.GetAxisRaw(name); } catch { _axesUnavailable = true; Plugin.Log.LogWarning((object)("Input axis '" + name + "' is not defined by the game; gamepad sticks disabled. Keyboard navigation still works.")); return 0f; } } private static int Step(int raw, ref int last, ref float heldFor, float delta) { if (raw == 0) { last = 0; heldFor = 0f; return 0; } if (raw != last) { last = raw; heldFor = 0f; return raw; } heldFor += delta; if (heldFor < 0.35f) { return 0; } heldFor -= 0.09f; return raw; } } public sealed class Theme { public static readonly Color Silk = new Color(0.93f, 0.91f, 0.86f); public static readonly Color Muted = new Color(0.62f, 0.6f, 0.58f); public static readonly Color Accent = new Color(0.85f, 0.44f, 0.52f); public static readonly Color Good = new Color(0.55f, 0.8f, 0.55f); public static readonly Color Warn = new Color(0.92f, 0.74f, 0.35f); public static readonly Color Bad = new Color(0.88f, 0.45f, 0.45f); public static readonly Color Panel = new Color(0.09f, 0.09f, 0.12f, 0.96f); private bool _built; public GUIStyle Window { get; private set; } public GUIStyle SectionTitle { get; private set; } public GUIStyle SectionDesc { get; private set; } public GUIStyle RowLabel { get; private set; } public GUIStyle Value { get; private set; } public GUIStyle Badge { get; private set; } public GUIStyle Footer { get; private set; } public GUIStyle Toast { get; private set; } public GUIStyle SelectedRow { get; private set; } public void EnsureBuilt() { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0038: Expected O, but got Unknown //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Expected O, but got Unknown //IL_00dc: Expected O, but got Unknown //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Expected O, but got Unknown //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Expected O, but got Unknown //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Expected O, but got Unknown //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Expected O, but got Unknown //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0210: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Expected O, but got Unknown //IL_023e: Expected O, but got Unknown //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Expected O, but got Unknown //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Expected O, but got Unknown //IL_02ac: Expected O, but got Unknown //IL_02da: Unknown result type (might be due to invalid IL or missing references) if (!_built) { _built = true; Window = new GUIStyle(GUI.skin.window) { padding = new RectOffset(14, 14, 24, 12) }; Window.normal.background = SolidTexture(Panel); Window.onNormal.background = Window.normal.background; Window.normal.textColor = Silk; Window.onNormal.textColor = Silk; Window.fontStyle = (FontStyle)1; SectionTitle = new GUIStyle(GUI.skin.label) { fontStyle = (FontStyle)1, fontSize = 14, margin = new RectOffset(0, 0, 10, 2) }; SectionTitle.normal.textColor = Accent; SectionDesc = new GUIStyle(GUI.skin.label) { fontSize = 11, wordWrap = true }; SectionDesc.normal.textColor = Muted; RowLabel = new GUIStyle(GUI.skin.label) { fontSize = 12 }; RowLabel.normal.textColor = Silk; Value = new GUIStyle(GUI.skin.label) { fontSize = 12, alignment = (TextAnchor)5, fontStyle = (FontStyle)1 }; Value.normal.textColor = Silk; Badge = new GUIStyle(GUI.skin.label) { fontSize = 10, alignment = (TextAnchor)5, fontStyle = (FontStyle)1 }; Footer = new GUIStyle(GUI.skin.label) { fontSize = 10, wordWrap = true }; Footer.normal.textColor = Muted; Toast = new GUIStyle(GUI.skin.box) { fontSize = 15, fontStyle = (FontStyle)1, alignment = (TextAnchor)4, padding = new RectOffset(18, 18, 12, 12) }; Toast.normal.background = SolidTexture(new Color(0.06f, 0.06f, 0.08f, 0.88f)); Toast.normal.textColor = Silk; SelectedRow = new GUIStyle { padding = new RectOffset(6, 6, 1, 1), margin = new RectOffset(-6, -6, 0, 0) }; SelectedRow.normal.background = SolidTexture(new Color(Accent.r, Accent.g, Accent.b, 0.22f)); } } private static Texture2D SolidTexture(Color color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, color); val.Apply(); ((Object)val).hideFlags = (HideFlags)61; return val; } } public sealed class TweakWindow { private struct NavRow { public ITweakModule Module; public ISettingRow Row; } private const int WindowId = 5327699; private static readonly Vector2 Size = new Vector2(430f, 560f); private const int FloatSliderSteps = 20; private readonly ModuleRegistry _registry; private readonly Theme _theme = new Theme(); private readonly PanelInput _input = new PanelInput(); private readonly Dictionary _listening = new Dictionary(); private readonly List _nav = new List(); private IReadOnlyList _conflicts; private Rect _rect = new Rect(60f, 60f, Size.x, Size.y); private Vector2 _scroll; private bool _showCredits; private int _focus; private int _drawIndex; public bool Visible { get; set; } public TweakWindow(ModuleRegistry registry) { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) _registry = registry; } public void HandleInput(float unscaledDelta) { if (!Visible) { return; } RebuildNav(); if (_nav.Count == 0) { return; } _input.Sample(unscaledDelta); if (_input.Cancel) { Visible = false; return; } if (_input.Vertical != 0) { _focus = (_focus + _input.Vertical + _nav.Count) % _nav.Count; } if (_input.Horizontal != 0) { Adjust(_nav[_focus], _input.Horizontal); } if (_input.Activate) { Activate(_nav[_focus]); } } private void RebuildNav() { _nav.Clear(); foreach (ITweakModule module in _registry.Modules) { if (module.Status.State == TweakState.Unavailable) { continue; } foreach (ISettingRow setting in module.Settings) { _nav.Add(new NavRow { Module = module, Row = setting }); } } if (_focus >= _nav.Count) { _focus = 0; } } private void Adjust(NavRow nav, int direction) { if (nav.Row is BoolRow boolRow) { boolRow.Entry.Value = direction > 0; } else if (nav.Row is IntRow intRow) { intRow.Entry.Value = Mathf.Clamp(intRow.Entry.Value + direction, intRow.Min, intRow.Max); } else if (nav.Row is FloatRow floatRow) { float num = (floatRow.Max - floatRow.Min) / 20f; floatRow.Entry.Value = Mathf.Clamp(floatRow.Entry.Value + (float)direction * num, floatRow.Min, floatRow.Max); } } private void Activate(NavRow nav) { if (nav.Row is BoolRow boolRow) { boolRow.Entry.Value = !boolRow.Entry.Value; } else if (nav.Row is KeyRow keyRow) { string key = nav.Module.Id + ":" + keyRow.Label; _listening[key] = !Listening(key); } } private bool Listening(string key) { bool value; return _listening.TryGetValue(key, out value) && value; } public void Draw() { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) _theme.EnsureBuilt(); DrawCountdownToast(); if (Visible) { if (_conflicts == null) { _conflicts = Conflicts.Detect(); } _rect = GUILayout.Window(5327699, _rect, new WindowFunction(DrawBody), $" SILKSONG TWEAKS · {_registry.ActiveCount}/{_registry.Modules.Count} active", _theme.Window, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(Size.x), GUILayout.Height(Size.y) }); } } private void DrawCountdownToast() { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) ReturnToDeathModule returnToDeathModule = null; foreach (ITweakModule module in _registry.Modules) { returnToDeathModule = module as ReturnToDeathModule; if (returnToDeathModule != null) { break; } } if (returnToDeathModule != null && !(returnToDeathModule.CountdownRemaining <= 0f)) { string text = $"Returning to where you died in {returnToDeathModule.CountdownRemaining:0.0}s" + $"\nPress {returnToDeathModule.CancelKey} to stay here"; Vector2 val = _theme.Toast.CalcSize(new GUIContent(text)); GUI.Label(new Rect(((float)Screen.width - val.x) * 0.5f, (float)Screen.height * 0.14f, val.x, val.y), text, _theme.Toast); } } private void DrawBody(int id) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) if (_conflicts != null && _conflicts.Count > 0) { DrawConflictWarning(); } _scroll = GUILayout.BeginScrollView(_scroll, Array.Empty()); _drawIndex = 0; foreach (ITweakModule module in _registry.Modules) { DrawModule(module); } GUILayout.Space(6f); GUILayout.Label("Move: D-pad / left stick / arrows Change: left-right Toggle: A / Enter\nClose: B / Esc / the open button", _theme.Footer, Array.Empty()); GUILayout.Space(6f); DrawCredits(); GUILayout.EndScrollView(); GUI.DragWindow(new Rect(0f, 0f, Size.x, 22f)); } private void DrawConflictWarning() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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_0079: Unknown result type (might be due to invalid IL or missing references) Color textColor = _theme.SectionDesc.normal.textColor; _theme.SectionDesc.normal.textColor = Theme.Bad; GUILayout.Label("CONFLICT: these mods do the same job and will fight this one.\n · " + string.Join("\n · ", ToArray(_conflicts)) + "\nRemove them from BepInEx/plugins.", _theme.SectionDesc, Array.Empty()); _theme.SectionDesc.normal.textColor = textColor; GUILayout.Space(6f); } private void DrawModule(ITweakModule module) { GUILayout.Label(module.DisplayName, _theme.SectionTitle, Array.Empty()); Widgets.StatusBadge(_theme, module.Status, module.LastFiredUtcTicks); if (module.Status.State == TweakState.Unavailable) { GUILayout.Label(module.Description, _theme.SectionDesc, Array.Empty()); GUILayout.Space(6f); return; } foreach (ISettingRow setting in module.Settings) { bool selected = _drawIndex == _focus; _drawIndex++; DrawRow(module, setting, selected); } GUILayout.Space(6f); } private void DrawRow(ITweakModule module, ISettingRow row, bool selected) { //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) if (selected) { GUILayout.BeginHorizontal(_theme.SelectedRow, Array.Empty()); } if (row is BoolRow boolRow) { bool value = boolRow.Entry.Value; Widgets.Toggle(_theme, boolRow.Label, boolRow.Tooltip, ref value); if (value != boolRow.Entry.Value) { boolRow.Entry.Value = value; } } else if (row is IntRow intRow) { int value2 = intRow.Entry.Value; Widgets.IntSlider(_theme, intRow.Label, intRow.Tooltip, ref value2, intRow.Min, intRow.Max); if (value2 != intRow.Entry.Value) { intRow.Entry.Value = value2; } } else if (row is FloatRow floatRow) { float value3 = floatRow.Entry.Value; Widgets.FloatSlider(_theme, floatRow.Label, floatRow.Tooltip, ref value3, new FloatRange(floatRow.Min, floatRow.Max, floatRow.Format)); if (!Mathf.Approximately(value3, floatRow.Entry.Value)) { floatRow.Entry.Value = value3; } } else if (row is KeyRow keyRow) { string key = module.Id + ":" + keyRow.Label; bool listening = Listening(key); KeyCode value4 = keyRow.Entry.Value; Widgets.KeyBinder(_theme, keyRow.Label, keyRow.Tooltip, ref value4, ref listening); _listening[key] = listening; if (value4 != keyRow.Entry.Value) { keyRow.Entry.Value = value4; } } if (selected) { GUILayout.EndHorizontal(); } } private void DrawCredits() { _showCredits = GUILayout.Toggle(_showCredits, _showCredits ? "Credits" : "Credits ...", Array.Empty()); if (_showCredits) { GUILayout.Label("Silksong Tweaks is an independent implementation, built against the game's own API. It replaces three mods whose ideas it owes a debt to — with thanks to:\n - Xiaohai (XiaohaiMod) - ReBack, for returning to the death location\n - BlueRaja - rosaries never permanently lost, for cocoon merging\n - Ericky1694 - CustomDifficulty, for health and damage tuning\n\nMIT licensed. Built on BepInEx and HarmonyX.", _theme.Footer, Array.Empty()); } } private static string[] ToArray(IReadOnlyList items) { string[] array = new string[items.Count]; for (int i = 0; i < items.Count; i++) { array[i] = items[i]; } return array; } } public static class Widgets { private const float LabelWidth = 190f; private const float ValueWidth = 60f; private const float RowHeight = 22f; public static void Toggle(Theme theme, string label, string tooltip, ref bool value) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); GUILayout.FlexibleSpace(); value = GUILayout.Toggle(value, value ? " ON" : " OFF", (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); GUILayout.EndHorizontal(); } public static void IntSlider(Theme theme, string label, string tooltip, ref int value, int min, int max) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); value = Mathf.RoundToInt(GUILayout.HorizontalSlider((float)value, (float)min, (float)max, Array.Empty())); GUILayout.Label(value.ToString(), theme.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); GUILayout.EndHorizontal(); } public static void FloatSlider(Theme theme, string label, string tooltip, ref float value, FloatRange range) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); value = GUILayout.HorizontalSlider(value, range.Min, range.Max, Array.Empty()); GUILayout.Label(Format(value, range.Format), theme.Value, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(60f) }); GUILayout.EndHorizontal(); } public static void KeyBinder(Theme theme, string label, string tooltip, ref KeyCode value, ref bool listening) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected I4, but got Unknown GUILayout.BeginHorizontal((GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f) }); GUILayout.Label(new GUIContent(label, tooltip), theme.RowLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(190f) }); GUILayout.FlexibleSpace(); if (GUILayout.Button(listening ? "press a key..." : ((object)Unsafe.As(ref value)/*cast due to .constrained prefix*/).ToString(), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(110f) })) { listening = !listening; } if (listening && Event.current != null && Event.current.isKey && (int)Event.current.keyCode != 0) { value = (KeyCode)(int)Event.current.keyCode; listening = false; Event.current.Use(); } GUILayout.EndHorizontal(); } public static void StatusBadge(Theme theme, TweakStatus status, long lastFiredTicks) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) string text; Color textColor; switch (status.State) { case TweakState.Active: text = ((lastFiredTicks > 0) ? ("ACTIVE · fired " + Ago(lastFiredTicks)) : "ACTIVE · not yet fired"); textColor = ((lastFiredTicks > 0) ? Theme.Good : Theme.Warn); break; case TweakState.Disabled: text = "OFF"; textColor = Theme.Muted; break; default: text = "UNAVAILABLE · " + status.Reason; textColor = Theme.Bad; break; } Color textColor2 = theme.Badge.normal.textColor; theme.Badge.normal.textColor = textColor; GUILayout.Label(text, theme.Badge, Array.Empty()); theme.Badge.normal.textColor = textColor2; } private static string Format(float value, string format) { if (format == "0%") { return Mathf.RoundToInt(value * 100f) + "%"; } if (format == "0.0s") { return value.ToString("0.0") + "s"; } return value.ToString("0.00") + "x"; } private static string Ago(long utcTicks) { double totalSeconds = (DateTime.UtcNow - new DateTime(utcTicks, DateTimeKind.Utc)).TotalSeconds; if (totalSeconds < 2.0) { return "just now"; } if (totalSeconds < 60.0) { return (int)totalSeconds + "s ago"; } if (totalSeconds < 3600.0) { return (int)(totalSeconds / 60.0) + "m ago"; } return (int)(totalSeconds / 3600.0) + "h ago"; } } public struct FloatRange { public float Min { get; } public float Max { get; } public string Format { get; } public FloatRange(float min, float max, string format) { Min = min; Max = max; Format = format; } } } namespace SilksongTweaks.Rules { public static class DamageRules { public static int Scale(int incoming, float multiplier) { if (incoming <= 0) { return incoming; } if (multiplier <= 0f) { return 0; } int num = (int)Math.Round((double)incoming * (double)multiplier); if (num < 1) { num = 1; } return num; } } public static class HealthRules { public const int MinMasks = 1; public const int MaxMasks = 20; public static int Resolve(int vanillaMax, int configuredMasks, bool enabled) { if (!enabled) { return vanillaMax; } int num = Clamp(configuredMasks, 1, 20); if (vanillaMax <= num) { return num; } return vanillaMax; } private static int Clamp(int value, int min, int max) { if (value < min) { return min; } if (value > max) { return max; } return value; } } public static class CocoonRules { public static int Merge(int previousPool, int carriedThisDeath, float keepFraction) { if (previousPool < 0) { previousPool = 0; } if (carriedThisDeath < 0) { carriedThisDeath = 0; } if (keepFraction <= 0f) { return carriedThisDeath; } if (keepFraction > 1f) { keepFraction = 1f; } long num = (long)(int)((double)previousPool * (double)keepFraction) + (long)carriedThisDeath; if (num <= int.MaxValue) { return (int)num; } return int.MaxValue; } } } namespace SilksongTweaks.Modules { public sealed class DamageTakenModule : ModuleBase { private const float MinMultiplier = 0f; private const float MaxMultiplier = 3f; private static DamageTakenModule _instance; private ConfigEntry _multiplier; public override string Id => "DamageTaken"; public override string DisplayName => "Damage taken"; public override string Description => "Scale how much damage Hornet takes."; protected override void BindSettings(ConfigFile config) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown _multiplier = config.Bind(Id, "Multiplier", 1f, new ConfigDescription("1 = vanilla. 0.5 = half damage. 0 = invulnerable. Above 1 makes the game harder.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); AddRow(new FloatRow("Multiplier", "1 = vanilla, 0 = invulnerable.", _multiplier, 0f, 3f, "0.00x")); } protected override TweakStatus Apply(Harmony harmony) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.TakeDamage, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, new HarmonyMethod(typeof(DamageTakenModule), "TakeDamagePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return TweakStatus.Active; } private static void TakeDamagePrefix(ref int damageAmount) { DamageTakenModule instance = _instance; if (instance != null && instance.IsOn && damageAmount > 0) { int num = DamageRules.Scale(damageAmount, instance._multiplier.Value); if (num != damageAmount) { damageAmount = num; instance.MarkFired(); } } } } public sealed class KeepRosariesModule : ModuleBase { private static KeepRosariesModule _instance; private ConfigEntry _keepFraction; private int _poolBeforeDeath; private bool _armed; public override string Id => "KeepRosaries"; public override string DisplayName => "Keep rosaries"; public override string Description => "Dying twice no longer destroys your first cocoon."; protected override void BindSettings(ConfigFile config) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown _keepFraction = config.Bind(Id, "KeepFraction", 1f, new ConfigDescription("Fraction of the previous cocoon carried into the new one. 1 = lose nothing, 0 = vanilla.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); AddRow(new FloatRow("Keep from old cocoon", "1 = lose nothing, 0 = vanilla behaviour.", _keepFraction, 0f, 1f, "0%")); } protected override TweakStatus Apply(Harmony harmony) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.Die, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } string error2; MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.HeroRespawned, out error2); if (methodBase2 == null) { return TweakStatus.Unavailable(error2); } harmony.Patch(methodBase, new HarmonyMethod(typeof(KeepRosariesModule), "DiePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(KeepRosariesModule), "HeroRespawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return TweakStatus.Active; } private static void DiePrefix() { KeepRosariesModule instance = _instance; if (instance != null && instance.IsOn) { PlayerData instance2 = PlayerData.instance; if (instance2 != null) { instance._poolBeforeDeath = instance2.HeroCorpseMoneyPool; instance._armed = true; } } } private static void HeroRespawnedPostfix() { KeepRosariesModule instance = _instance; if (instance == null || !instance.IsOn || !instance._armed) { return; } instance._armed = false; PlayerData instance2 = PlayerData.instance; if (instance2 != null) { int num = CocoonRules.Merge(instance._poolBeforeDeath, instance2.HeroCorpseMoneyPool, instance._keepFraction.Value); if (num != instance2.HeroCorpseMoneyPool) { Plugin.Log.LogInfo((object)($"[KeepRosaries] cocoon {instance2.HeroCorpseMoneyPool} -> {num} " + $"(rescued {num - instance2.HeroCorpseMoneyPool} from the previous cocoon)")); instance2.HeroCorpseMoneyPool = num; instance.MarkFired(); } } } } public sealed class MaxHealthModule : ModuleBase { private const string MaxHealthKey = "maxHealth"; private const string MaxHealthField = "maxHealth"; private static MaxHealthModule _instance; private static int _rewritten; private ConfigEntry _masks; public override string Id => "MaxHealth"; public override string DisplayName => "Max masks"; public override string Description => "Raise Hornet's maximum health."; protected override void BindSettings(ConfigFile config) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown _masks = config.Bind(Id, "Masks", 8, new ConfigDescription("Maximum masks. Never lowers you below what you have earned in-game.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 20), Array.Empty())); AddRow(new IntRow("Masks", "How many masks Hornet has at full health.", _masks, 1, 20)); _masks.SettingChanged += delegate { RefreshMaskDisplay(); }; base.EnabledEntry.SettingChanged += delegate { RefreshMaskDisplay(); }; } private static void RefreshMaskDisplay() { try { HeroController instance = HeroController.instance; if (!((Object)(object)instance == (Object)null)) { MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "MaxHealth", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogWarning((object)"[MaxHealth] HeroController.MaxHealth() not found; HUD will refresh on its own next bench."); } else { methodInfo.Invoke(instance, null); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("[MaxHealth] HUD refresh failed: " + ex.Message)); } } protected override TweakStatus Apply(Harmony harmony) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.CurrentMaxHealth, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.GetInt, out error); if (methodBase2 == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, (HarmonyMethod)null, new HarmonyMethod(typeof(MaxHealthModule), "CurrentMaxHealthPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(MaxHealthModule), "GetIntPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); TweakStatus tweakStatus = RestoreInvariantIn(harmony); if (tweakStatus != null) { return tweakStatus; } if (_rewritten == 0) { return TweakStatus.Unavailable("no maxHealth field reads found to rewrite — the game's health code changed"); } base.Log.LogInfo((object)$"[MaxHealth] restored max-health invariant at {_rewritten} site(s)"); return TweakStatus.Active; } private TweakStatus RestoreInvariantIn(Harmony harmony) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown HookTarget[] obj = new HookTarget[3] { HookTargets.PlayerDataAddHealth, HookTargets.PlayerDataTakeHealth, HookTargets.PlayerDataMaxHealth }; HarmonyMethod val = new HarmonyMethod(typeof(MaxHealthModule), "MaxHealthTranspiler", (Type[])null); HookTarget[] array = obj; for (int i = 0; i < array.Length; i++) { string error; MethodBase methodBase = ModuleBase.Resolve(array[i], out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, (HarmonyMethod)null, (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null); } return null; } private static IEnumerable MaxHealthTranspiler(IEnumerable instructions) { MethodInfo getter = AccessTools.PropertyGetter(typeof(PlayerData), "CurrentMaxHealth"); foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldfld && instruction.operand is FieldInfo { Name: "maxHealth" } fieldInfo && fieldInfo.DeclaringType == typeof(PlayerData)) { instruction.opcode = OpCodes.Call; instruction.operand = getter; _rewritten++; } yield return instruction; } } private static void CurrentMaxHealthPostfix(ref int __result) { MaxHealthModule instance = _instance; if (instance != null && instance.IsOn) { int num = HealthRules.Resolve(__result, instance._masks.Value, enabled: true); if (num != __result) { __result = num; instance.MarkFired(); } } } private static void GetIntPostfix(string intName, ref int __result) { MaxHealthModule instance = _instance; if (instance != null && instance.IsOn && !(intName != "maxHealth")) { __result = HealthRules.Resolve(__result, instance._masks.Value, enabled: true); } } } public abstract class ModuleBase : ITweakModule { private readonly List _rows = new List(); private TweakStatus _hookStatus = TweakStatus.Unavailable("not applied yet"); protected ConfigEntry EnabledEntry { get; private set; } protected ManualLogSource Log => Plugin.Log; public abstract string Id { get; } public abstract string DisplayName { get; } public abstract string Description { get; } public TweakStatus Status { get { if (_hookStatus.State == TweakState.Unavailable) { return _hookStatus; } if (EnabledEntry == null || EnabledEntry.Value) { return TweakStatus.Active; } return TweakStatus.Disabled; } protected set { _hookStatus = value; } } public long LastFiredUtcTicks { get; private set; } public IReadOnlyList Settings => _rows; public bool IsOn { get { if (Status.State != TweakState.Unavailable && EnabledEntry != null) { return EnabledEntry.Value; } return false; } } public void BindConfig(ConfigFile config) { EnabledEntry = config.Bind(Id, "Enabled", true, Description); AddRow(new BoolRow("Enabled", Description, EnabledEntry)); BindSettings(config); } protected abstract void BindSettings(ConfigFile config); protected void AddRow(ISettingRow row) { _rows.Add(row); } public TweakStatus TryApply(Harmony harmony) { try { Status = Apply(harmony); } catch (Exception ex) { Status = TweakStatus.Unavailable(ex.Message); Log.LogError((object)$"[{Id}] failed to apply: {ex}"); } return Status; } protected abstract TweakStatus Apply(Harmony harmony); public void MarkFired() { LastFiredUtcTicks = DateTime.UtcNow.Ticks; } protected static MethodBase Resolve(HookTarget target, out string error) { Type type = AccessTools.TypeByName(target.DeclaringType); if (type == null) { error = "type '" + target.DeclaringType + "' not found"; return null; } MethodBase methodBase = ((target.Kind == HookKind.PropertyGetter) ? AccessTools.PropertyGetter(type, target.Member) : AccessTools.Method(type, target.Member, (Type[])null, (Type[])null)); if (methodBase == null) { error = target.DeclaringType + "." + target.Member + " not found"; return null; } error = null; return methodBase; } } public sealed class ReturnToDeathModule : ModuleBase { private const float MinCountdown = 0f; private const float MaxCountdown = 15f; private const float ArrivalTimeout = 15f; private const float SettleSeconds = 0.35f; private static ReturnToDeathModule _instance; private ConfigEntry _countdown; private ConfigEntry _cancelKey; private string _deathScene; private string _deathGate; private Vector3 _deathPosition; private bool _hasRecord; private bool _returning; public override string Id => "ReturnToDeath"; public override string DisplayName => "Return to death spot"; public override string Description => "Travel back to where you died after respawning."; public float CountdownRemaining { get; private set; } public KeyCode CancelKey { get { //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (_cancelKey == null) { return (KeyCode)110; } return _cancelKey.Value; } } protected override void BindSettings(ConfigFile config) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown _countdown = config.Bind(Id, "CountdownSeconds", 5f, new ConfigDescription("Seconds before the return happens, giving you time to cancel.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 15f), Array.Empty())); _cancelKey = config.Bind(Id, "CancelKey", (KeyCode)110, "Press during the countdown to stay at the bench."); AddRow(new FloatRow("Countdown", "Seconds before returning.", _countdown, 0f, 15f, "0.0s")); AddRow(new KeyRow("Cancel key", "Press during the countdown to stay put.", _cancelKey)); } protected override TweakStatus Apply(Harmony harmony) { //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown _instance = this; string error; MethodBase methodBase = ModuleBase.Resolve(HookTargets.Die, out error); if (methodBase == null) { return TweakStatus.Unavailable(error); } MethodBase methodBase2 = ModuleBase.Resolve(HookTargets.HeroRespawned, out error); if (methodBase2 == null) { return TweakStatus.Unavailable(error); } if (ModuleBase.Resolve(HookTargets.BeginSceneTransition, out error) == null) { return TweakStatus.Unavailable(error); } harmony.Patch(methodBase, new HarmonyMethod(typeof(ReturnToDeathModule), "DiePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch(methodBase2, (HarmonyMethod)null, new HarmonyMethod(typeof(ReturnToDeathModule), "HeroRespawnedPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return TweakStatus.Active; } private static void DiePrefix() { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) ReturnToDeathModule instance = _instance; if (instance == null || !instance.IsOn) { return; } HeroController instance2 = HeroController.instance; GameManager instance3 = GameManager.instance; if (!((Object)(object)instance2 == (Object)null) && !((Object)(object)instance3 == (Object)null)) { instance._deathScene = instance3.GetSceneNameString(); instance._deathPosition = ((Component)instance2).transform.position; instance._deathGate = FindGateInCurrentScene(); instance._hasRecord = !string.IsNullOrEmpty(instance._deathGate); if (!instance._hasRecord) { Plugin.Log.LogWarning((object)("[ReturnToDeath] no transition point found in '" + instance._deathScene + "'. Staying at the bench rather than risking a transition with no entry gate.")); } else { Plugin.Log.LogInfo((object)($"[ReturnToDeath] recorded death in '{instance._deathScene}' at {instance._deathPosition} " + "via gate '" + instance._deathGate + "'")); } } } private static string FindGateInCurrentScene() { List transitionPoints = TransitionPoint.TransitionPoints; if (transitionPoints == null || transitionPoints.Count == 0) { return null; } string text = null; foreach (TransitionPoint item in transitionPoints) { if (!((Object)(object)item == (Object)null) && !string.IsNullOrEmpty(((Object)item).name)) { if (!item.isADoor) { return ((Object)item).name; } if (text == null) { text = ((Object)item).name; } } } return text; } private static void HeroRespawnedPostfix() { ReturnToDeathModule instance = _instance; if (instance != null && instance.IsOn && instance._hasRecord && !instance._returning) { Plugin.Instance.Run(instance.ReturnRoutine()); } } private IEnumerator ReturnRoutine() { _returning = true; CountdownRemaining = _countdown.Value; while (CountdownRemaining > 0f) { if (Input.GetKeyDown(CancelKey)) { Plugin.Log.LogInfo((object)"[ReturnToDeath] cancelled by player"); Finish(); yield break; } CountdownRemaining -= Time.unscaledDeltaTime; yield return null; } CountdownRemaining = 0f; yield return Travel(); Finish(); } private IEnumerator Travel() { GameManager gm = GameManager.instance; if ((Object)(object)gm == (Object)null || string.IsNullOrEmpty(_deathScene) || string.IsNullOrEmpty(_deathGate)) { yield break; } yield return WaitUntilRespawnFinished(); if (gm.GetSceneNameString() != _deathScene) { Plugin.Log.LogInfo((object)("[ReturnToDeath] entering '" + _deathScene + "' via gate '" + _deathGate + "'")); gm.BeginSceneTransition(new SceneLoadInfo { SceneName = _deathScene, EntryGateName = _deathGate }); float waited = 0f; while (waited < 15f) { GameManager instance = GameManager.instance; if ((Object)(object)instance != (Object)null && instance.GetSceneNameString() == _deathScene && (Object)(object)HeroController.instance != (Object)null) { break; } waited += Time.unscaledDeltaTime; yield return null; } if (waited >= 15f) { Plugin.Log.LogWarning((object)"[ReturnToDeath] timed out waiting for the scene; leaving you where you are"); yield break; } yield return (object)new WaitForSeconds(0.35f); } PlaceHeroAtDeathSpot(); MarkFired(); } private static IEnumerator WaitUntilRespawnFinished() { float waited = 0f; while (waited < 15f) { GameManager instance = GameManager.instance; HeroController instance2 = HeroController.instance; if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance2 == (Object)null) && !instance.RespawningHero) { break; } waited += Time.unscaledDeltaTime; yield return null; } yield return (object)new WaitForSeconds(0.35f); } private void PlaceHeroAtDeathSpot() { //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_003d: 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_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_008e: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: 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) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) HeroController instance = HeroController.instance; if ((Object)(object)instance == (Object)null) { return; } MethodInfo methodInfo = AccessTools.Method(typeof(HeroController), "TryFindGroundPoint", (Type[])null, (Type[])null); if (methodInfo != null) { object[] array = new object[3] { (object)default(Vector2), Vector2.op_Implicit(_deathPosition), true }; object obj = methodInfo.Invoke(instance, array); bool flag = default(bool); int num; if (obj is bool) { flag = (bool)obj; num = 1; } else { num = 0; } if (((uint)num & (flag ? 1u : 0u)) != 0) { Vector2 val = (Vector2)array[0]; ((Component)instance).transform.position = new Vector3(val.x, val.y, ((Component)instance).transform.position.z); Plugin.Log.LogInfo((object)$"[ReturnToDeath] placed at ground point {val}"); return; } } ((Component)instance).transform.position = _deathPosition; Plugin.Log.LogInfo((object)$"[ReturnToDeath] placed at recorded position {_deathPosition}"); } private void Finish() { CountdownRemaining = 0f; _returning = false; _hasRecord = false; } } }