using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("ForgeKit")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+294094e110e9aca56665cc10cce0047db95418d1")] [assembly: AssemblyProduct("ForgeKit")] [assembly: AssemblyTitle("ForgeKit")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] namespace ForgeKit; public sealed class CommandChannel { private readonly string _path; private readonly ManualLogSource _log; private readonly CommandRegistry _registry; private readonly float _pollSeconds; private readonly bool _allLines; private float _check; private long _stamp; public string Path => _path; public CommandChannel(string fileName, ManualLogSource log, CommandRegistry registry, float pollSeconds = 0.5f, bool allLines = true, bool primeStamp = false) { _path = System.IO.Path.Combine(Paths.ConfigPath, fileName); _log = log; _registry = registry; _pollSeconds = pollSeconds; _allLines = allLines; if (primeStamp) { _stamp = (File.Exists(_path) ? File.GetLastWriteTimeUtc(_path).Ticks : 0); } } public void Tick() { if (!(Time.unscaledTime - _check <= _pollSeconds)) { _check = Time.unscaledTime; Poll(); } } private void Poll() { try { if (!File.Exists(_path)) { return; } long ticks = File.GetLastWriteTimeUtc(_path).Ticks; if (ticks == _stamp) { return; } _stamp = ticks; if (_allLines) { string[] array = File.ReadAllLines(_path); foreach (string text in array) { string text2 = text.Trim(); if (text2.Length != 0 && !text2.StartsWith("#")) { Run(text2); } } return; } string text3 = null; string[] array2 = File.ReadAllLines(_path); foreach (string text4 in array2) { if (!string.IsNullOrWhiteSpace(text4)) { text3 = text4; break; } } if (!string.IsNullOrWhiteSpace(text3)) { Run(text3.Trim()); } } catch (Exception ex) { _log.LogWarning((object)("[CMD] poll error: " + ex.Message)); } } public void Run(string cmd) { _log.LogMessage((object)("[CMD] run: " + cmd)); string[] array = cmd.Split(new char[1] { ' ' }, StringSplitOptions.RemoveEmptyEntries); string verb = ((array.Length != 0) ? array[0] : ""); if (_registry.TryGet(verb, out var run)) { run(array); } else { _registry.PrintHelp(verb); } } } public sealed class CommandRegistry { private readonly ManualLogSource _log; private readonly Dictionary> _run = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly List> _help = new List>(); public CommandRegistry(ManualLogSource log) { _log = log; } public void Register(string verb, string help, Action run) { Register(new string[1] { verb }, help, run); } public void Register(string[] verbs, string help, Action run) { foreach (string v in verbs) { _run[v] = delegate(string[] args) { try { run(args); } catch (Exception arg) { _log.LogError((object)$"[CMD] '{v}' failed: {arg}"); } }; } _help.Add(new KeyValuePair(string.Join("/", verbs), help)); } public bool TryGet(string verb, out Action run) { return _run.TryGetValue(verb, out run); } public void PrintHelp(string verb) { if (string.IsNullOrEmpty(verb) || string.Equals(verb, "help", StringComparison.OrdinalIgnoreCase)) { _log.LogMessage((object)$"[CMD] {_help.Count} registered verbs:"); } else { _log.LogWarning((object)$"[CMD] unknown verb '{verb}' — {_help.Count} registered verbs:"); } foreach (KeyValuePair item in _help) { _log.LogMessage((object)("[CMD] " + item.Key + " — " + item.Value)); } } } public static class EmbeddedRes { public static string Text(Assembly asm, string suffix, string logTag, ManualLogSource log = null) { string text = Array.Find(asm.GetManifestResourceNames(), (string n) => n.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); if (text == null) { ManualLogSource obj = log ?? Plugin.Log; if (obj != null) { obj.LogWarning((object)(logTag + " embedded " + suffix + " not found.")); } return ""; } using Stream stream = asm.GetManifestResourceStream(text); using StreamReader streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } public static Texture2D Texture(Assembly asm, string suffix, string logTag, ManualLogSource log = null) { //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Expected O, but got Unknown ManualLogSource val = log ?? Plugin.Log; try { string text = Array.Find(asm.GetManifestResourceNames(), (string n) => n.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); if (text == null) { if (val != null) { val.LogWarning((object)(logTag + " embedded icon '" + suffix + "' missing from the DLL — keeping the donor icon.")); } return null; } byte[] array; using (Stream stream = asm.GetManifestResourceStream(text)) { using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); array = memoryStream.ToArray(); } Texture2D val2 = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val2, array)) { if (val != null) { val.LogWarning((object)(logTag + " icon '" + suffix + "' failed to decode.")); } return null; } ((Object)val2).hideFlags = (HideFlags)61; return val2; } catch (Exception ex) { if (val != null) { val.LogWarning((object)(logTag + " icon '" + suffix + "' load failed: " + ex.Message)); } return null; } } } public static class Keybinds { private struct Bound { public string Mod; public string Action; public string ConfigHint; public KeyboardShortcut Combo; } private static readonly Dictionary _claims = new Dictionary(); private static ManualLogSource L => Notify.Log ?? Plugin.Log; private static bool SameCombo(KeyboardShortcut a, KeyboardShortcut b) { //IL_0002: 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_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_004e: Unknown result type (might be due to invalid IL or missing references) if (((KeyboardShortcut)(ref a)).MainKey != ((KeyboardShortcut)(ref b)).MainKey) { return false; } List list = new List(((KeyboardShortcut)(ref a)).Modifiers); List list2 = new List(((KeyboardShortcut)(ref b)).Modifiers); if (list.Count != list2.Count) { return false; } foreach (KeyCode item in list) { if (!list2.Contains(item)) { return false; } } return true; } private static string ComboText(KeyboardShortcut c) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); foreach (KeyCode modifier in ((KeyboardShortcut)(ref c)).Modifiers) { stringBuilder.Append(modifier).Append('+'); } return stringBuilder.Append(((KeyboardShortcut)(ref c)).MainKey).ToString(); } public static void Claim(string mod, string action, ConfigEntry entry) { //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (entry != null) { string hint = "[" + ((ConfigEntryBase)entry).Definition.Section + "] " + ((ConfigEntryBase)entry).Definition.Key; Claim(mod, action, entry.Value, hint); entry.SettingChanged += delegate { //IL_0012: Unknown result type (might be due to invalid IL or missing references) Claim(mod, action, entry.Value, hint); }; } } public static void Claim(string mod, string action, KeyCode key, string configHint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Keybinds.Claim(mod, action, new KeyboardShortcut(key, Array.Empty()), configHint); } public static void Claim(string mod, string action, KeyboardShortcut combo, string configHint) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) if ((int)((KeyboardShortcut)(ref combo)).MainKey == 0) { return; } string text = mod + "|" + action; _claims[text] = new Bound { Mod = mod, Action = action, Combo = combo, ConfigHint = configHint }; List list = Others(combo, text); if (list.Count == 0) { return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[KEYBIND] CONFLICT on " + ComboText(combo) + ": "); stringBuilder.Append(Describe(_claims[text])); foreach (Bound item in list) { stringBuilder.Append(" AND ").Append(Describe(item)); } stringBuilder.Append(". One keypress fires ALL of them — that is a real gameplay bug, not just noise "); stringBuilder.Append("(Bug 26: opening the spawn menu silently cast Hunt as One). Rebind one of them: "); stringBuilder.Append(RebindAdvice(combo, text, list)); L.LogWarning((object)stringBuilder.ToString()); } public static string Report() { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (_claims.Count == 0) { return "[KEYBIND] no keys claimed."; } SortedDictionary> sortedDictionary = new SortedDictionary>(StringComparer.Ordinal); foreach (Bound value2 in _claims.Values) { string key = ComboText(value2.Combo); if (!sortedDictionary.TryGetValue(key, out var value)) { value = (sortedDictionary[key] = new List()); } value.Add(value2); } StringBuilder stringBuilder = new StringBuilder("[KEYBIND] claimed keys:"); int num = 0; foreach (KeyValuePair> item in sortedDictionary) { bool flag = item.Value.Count > 1; if (flag) { num++; } stringBuilder.Append(string.Format("\n {0,-12}{1}", item.Key, flag ? "*** CONFLICT *** " : "")); for (int i = 0; i < item.Value.Count; i++) { stringBuilder.Append((i == 0) ? "" : " AND ").Append(Describe(item.Value[i])); } } stringBuilder.Append($"\n {num} conflict(s) across {sortedDictionary.Count} key(s)."); return stringBuilder.ToString(); } public static bool HasConflicts() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) foreach (KeyValuePair claim in _claims) { if (Others(claim.Value.Combo, claim.Key).Count > 0) { return true; } } return false; } private static List Others(KeyboardShortcut combo, string selfId) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (KeyValuePair claim in _claims) { if (claim.Key != selfId && SameCombo(claim.Value.Combo, combo)) { list.Add(claim.Value); } } return list; } private static string Describe(Bound c) { return c.Mod + " '" + c.Action + "'" + ((c.ConfigHint == null) ? " (HARDCODED — cannot be rebound)" : (" (" + c.ConfigHint + ")")); } private static string RebindAdvice(KeyboardShortcut combo, string selfId, List others) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) List list = new List(); Bound item = _claims[selfId]; if (item.ConfigHint != null) { list.Add(item); } foreach (Bound other in others) { if (other.ConfigHint != null) { list.Add(other); } } if (list.Count == 0) { return "BOTH are hardcoded — this needs a code fix, there is nothing the player can do about " + ComboText(combo) + "."; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < list.Count; i++) { stringBuilder.Append((i == 0) ? "" : " or ").Append(list[i].Mod + "'s " + list[i].ConfigHint); } return stringBuilder.ToString() + "."; } } public static class Lifecycle { private static readonly Dictionary _generations = new Dictionary(); public static bool IsSanePosition(Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) if (p.y > -3000f) { return ((Vector3)(ref p)).sqrMagnitude < 100000000f; } return false; } public static int InvalidateWaits(object waitKey) { if (waitKey == null) { return 0; } _generations.TryGetValue(waitKey, out var value); value++; _generations[waitKey] = value; return value; } private static bool IsStale(object waitKey, int myGen) { if (waitKey != null && _generations.TryGetValue(waitKey, out var value)) { return value != myGen; } return false; } public static IEnumerator WhenPlayerReady(Func getPlayer, Action onReady, Action onTimeout = null, float timeoutSeconds = 30f, object waitKey = null) { int myGen = ((waitKey != null) ? InvalidateWaits(waitKey) : 0); float t0 = Time.unscaledTime; Character player = null; while (Time.unscaledTime - t0 < timeoutSeconds) { if (IsStale(waitKey, myGen)) { LogSuperseded(waitKey); yield break; } player = getPlayer(); if ((Object)(object)player != (Object)null && IsSanePosition(((Component)player).transform.position)) { break; } player = null; yield return (object)new WaitForSeconds(0.5f); } if (IsStale(waitKey, myGen)) { LogSuperseded(waitKey); } else if ((Object)(object)player == (Object)null) { onTimeout?.Invoke($"player not ready within {timeoutSeconds:F0}s"); } else { onReady(player); } } private static void LogSuperseded(object waitKey) { ManualLogSource obj = Notify.Log ?? Plugin.Log; if (obj != null) { obj.LogMessage((object)$"[LIFECYCLE] superseded a stale WhenPlayerReady wait (key={waitKey})."); } } } public static class Notify { public static ManualLogSource Log; private static ManualLogSource L => Log ?? Plugin.Log; public static void Player(Character character, string message) { L.LogMessage((object)("[NOTIFY] " + message)); if ((Object)(object)character != (Object)null && (Object)(object)character.CharacterUI != (Object)null) { character.CharacterUI.ShowInfoNotification(message); } else { L.LogWarning((object)"[NOTIFY] no CharacterUI to show the toast on (logged only)."); } } } [BepInPlugin("cobalt.forgekit", "ForgeKit", "0.1.0")] public class Plugin : BaseUnityPlugin { public const string GUID = "cobalt.forgekit"; public const string NAME = "ForgeKit"; public const string VERSION = "0.1.0"; internal static ManualLogSource Log; internal void Awake() { Log = ((BaseUnityPlugin)this).Logger; Log.LogMessage((object)"ForgeKit 0.1.0 loaded."); } } public sealed class SelfTestHarness { private readonly ManualLogSource _log; private int _pass; private int _fail; public SelfTestHarness(ManualLogSource log) { _log = log; } public void Begin(string tag) { _pass = (_fail = 0); _log.LogMessage((object)("[SELFTEST] BEGIN (" + tag + ")")); } public void Check(string name, bool ok) { if (ok) { _pass++; _log.LogMessage((object)("[SELFTEST] PASS: " + name)); } else { _fail++; _log.LogError((object)("[SELFTEST] FAIL: " + name)); } } public void Exception(Exception e) { _fail++; _log.LogError((object)("[SELFTEST] EXCEPTION: " + e)); } public void Done() { _log.LogMessage((object)$"[SELFTEST] DONE pass={_pass} fail={_fail}"); } } public class TableLoader { private readonly Assembly _embeddedSource; private readonly ManualLogSource _log; private readonly string _fileName; private readonly string _logTag; private readonly string _singular; private readonly string _plural; private readonly string _mergeNote; private readonly string _reloadSuffix; private readonly Func, Dictionary> _parse; private readonly Func, Dictionary, Dictionary> _merge; private readonly Func, Action, Dictionary> _postLoad; private readonly Func, string> _builtInSuffix; private Dictionary _table; private ManualLogSource Log => _log ?? Plugin.Log; public Dictionary Table { get { if (_table == null) { _table = Load(); } return _table; } } public TableLoader(Assembly embeddedSource, ManualLogSource log, string fileName, string logTag, string singularNoun, string pluralNoun, Func, Dictionary> parse, Func, Dictionary, Dictionary> merge, string mergeNote = "replaces per-species", Func, Action, Dictionary> postLoad = null, Func, string> builtInSuffix = null, string reloadSuffix = "") { _embeddedSource = embeddedSource; _log = log; _fileName = fileName; _logTag = logTag; _singular = singularNoun; _plural = pluralNoun; _parse = parse; _merge = merge; _mergeNote = mergeNote; _postLoad = postLoad; _builtInSuffix = builtInSuffix; _reloadSuffix = reloadSuffix; } public Dictionary Reload() { _table = Load(); Log.LogMessage((object)$"{_logTag} reloaded {_fileName} ({_table.Count} {Noun(_table.Count)}).{_reloadSuffix}"); return _table; } private string Noun(int n) { if (n != 1) { return _plural; } return _singular; } private Dictionary Load() { Action arg = delegate(string m) { Log.LogWarning((object)(_logTag + " " + _fileName + ": " + m)); }; Dictionary dictionary = _parse(EmbeddedRes.Text(_embeddedSource, _fileName, _logTag, _log), arg); Log.LogMessage((object)string.Format("{0} loaded {1} built-in {2}{3}.", _logTag, dictionary.Count, Noun(dictionary.Count), _builtInSuffix?.Invoke(dictionary) ?? "")); try { string path = Path.Combine(Paths.ConfigPath, _fileName); if (File.Exists(path)) { Dictionary dictionary2 = _parse(File.ReadAllText(path), arg); dictionary = _merge(dictionary, dictionary2); Log.LogMessage((object)$"{_logTag} merged {dictionary2.Count} {Noun(dictionary2.Count)} from config override ({_mergeNote})."); } } catch (Exception ex) { Log.LogWarning((object)(_logTag + " " + _fileName + " override load failed: " + ex.Message)); } if (_postLoad != null) { dictionary = _postLoad(dictionary, arg); } return dictionary; } } public sealed class VerbContext { public Character Player; public string[] Parts; public string Verb { get { if (Parts == null || Parts.Length == 0) { return ""; } return Parts[0]; } } public string Arg(int i) { if (Parts == null || i < 0 || i >= Parts.Length) { return null; } return Parts[i]; } public string Tail(int from = 1) { if (Parts == null || Parts.Length <= from) { return null; } return string.Join(" ", Parts, from, Parts.Length - from); } } public sealed class VerbHost { private readonly CommandRegistry _commands; private readonly ManualLogSource _log; private readonly Func _player; public VerbHost(CommandRegistry commands, ManualLogSource log, Func player) { _commands = commands; _log = log; _player = player; } public void Register(string verb, string help, Action body, string tag = "[CMD]", bool needsPlayer = true, bool warnOnNoPlayer = true, bool masterOnly = false, string noPlayerMsg = null) { Register(new string[1] { verb }, help, body, tag, needsPlayer, warnOnNoPlayer, masterOnly, noPlayerMsg); } public void Register(string[] verbs, string help, Action body, string tag = "[CMD]", bool needsPlayer = true, bool warnOnNoPlayer = true, bool masterOnly = false, string noPlayerMsg = null) { _commands.Register(verbs, help, delegate(string[] parts) { Character val = _player(); if ((Object)(object)val == (Object)null && needsPlayer) { string text = noPlayerMsg ?? (tag + " no local player."); if (warnOnNoPlayer) { _log.LogWarning((object)text); } else { _log.LogMessage((object)text); } } else { if (!masterOnly || !PhotonNetwork.isNonMasterClientInRoom) { VerbContext verbContext = new VerbContext { Player = val, Parts = parts }; try { body(verbContext); return; } catch (Exception arg) { _log.LogError((object)$"{tag} '{verbContext.Verb}' failed: {arg}"); return; } } _log.LogWarning((object)(tag + " non-master client in room — master-only; skipped.")); } }); } }