Decompiled source of ForgeKit v0.1.0

ForgeKit/ForgeKit.dll

Decompiled 3 days ago
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."));
			}
		});
	}
}