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<string, Action<string[]>> _run = new Dictionary<string, Action<string[]>>(StringComparer.OrdinalIgnoreCase);
private readonly List<KeyValuePair<string, string>> _help = new List<KeyValuePair<string, string>>();
public CommandRegistry(ManualLogSource log)
{
_log = log;
}
public void Register(string verb, string help, Action<string[]> run)
{
Register(new string[1] { verb }, help, run);
}
public void Register(string[] verbs, string help, Action<string[]> 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, string>(string.Join("/", verbs), help));
}
public bool TryGet(string verb, out Action<string[]> 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<string, string> 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<string, Bound> _claims = new Dictionary<string, Bound>();
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<KeyCode> list = new List<KeyCode>(((KeyboardShortcut)(ref a)).Modifiers);
List<KeyCode> list2 = new List<KeyCode>(((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<KeyboardShortcut> 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<KeyCode>()), 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<Bound> 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<string, List<Bound>> sortedDictionary = new SortedDictionary<string, List<Bound>>(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<Bound>());
}
value.Add(value2);
}
StringBuilder stringBuilder = new StringBuilder("[KEYBIND] claimed keys:");
int num = 0;
foreach (KeyValuePair<string, List<Bound>> 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<string, Bound> claim in _claims)
{
if (Others(claim.Value.Combo, claim.Key).Count > 0)
{
return true;
}
}
return false;
}
private static List<Bound> 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<Bound> list = new List<Bound>();
foreach (KeyValuePair<string, Bound> 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<Bound> others)
{
//IL_006a: Unknown result type (might be due to invalid IL or missing references)
List<Bound> list = new List<Bound>();
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<object, int> _generations = new Dictionary<object, int>();
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<Character> getPlayer, Action<Character> onReady, Action<string> 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<T>
{
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<string, Action<string>, Dictionary<string, T>> _parse;
private readonly Func<Dictionary<string, T>, Dictionary<string, T>, Dictionary<string, T>> _merge;
private readonly Func<Dictionary<string, T>, Action<string>, Dictionary<string, T>> _postLoad;
private readonly Func<Dictionary<string, T>, string> _builtInSuffix;
private Dictionary<string, T> _table;
private ManualLogSource Log => _log ?? Plugin.Log;
public Dictionary<string, T> Table
{
get
{
if (_table == null)
{
_table = Load();
}
return _table;
}
}
public TableLoader(Assembly embeddedSource, ManualLogSource log, string fileName, string logTag, string singularNoun, string pluralNoun, Func<string, Action<string>, Dictionary<string, T>> parse, Func<Dictionary<string, T>, Dictionary<string, T>, Dictionary<string, T>> merge, string mergeNote = "replaces per-species", Func<Dictionary<string, T>, Action<string>, Dictionary<string, T>> postLoad = null, Func<Dictionary<string, T>, 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<string, T> 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<string, T> Load()
{
Action<string> arg = delegate(string m)
{
Log.LogWarning((object)(_logTag + " " + _fileName + ": " + m));
};
Dictionary<string, T> 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<string, T> 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<Character> _player;
public VerbHost(CommandRegistry commands, ManualLogSource log, Func<Character> player)
{
_commands = commands;
_log = log;
_player = player;
}
public void Register(string verb, string help, Action<VerbContext> 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<VerbContext> 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."));
}
});
}
}