Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of ValAgentBridge v1.5.5
plugins/ValAgentBridge.dll
Decompiled 7 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Lib.GAB; using Lib.GAB.Events; using Lib.GAB.Server; using Lib.GAB.Tools; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using TMPro; using Unity.Collections; using UnityEngine; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.LowLevel; using UnityEngine.SceneManagement; using UnityEngine.UI; using ValAgentBridge.Tools; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ValAgentBridge")] [assembly: AssemblyDescription("Lets an AI agent automate and monitor mod testing in a running Valheim")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("BarryWhite")] [assembly: AssemblyProduct("ValAgentBridge")] [assembly: AssemblyCopyright("Copyright (c) 2026 BarryWhite")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("7c1e3b52-9a4d-4f0e-8b6a-2d5f1c9e4a37")] [assembly: AssemblyFileVersion("1.5.5")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.5.5.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace ValAgentBridge { public static class BackpackCompat { private sealed class Source { public string mod = ""; public MethodInfo method; public bool takesPlayer; } private static List<Source>? _sources; public static List<(string mod, Inventory inventory)> EquippedBackpacks(Player player) { List<(string, Inventory)> list = new List<(string, Inventory)>(); if ((Object)(object)player == (Object)null) { return list; } foreach (Source item in Sources()) { try { object? obj = item.method.Invoke(null, (!item.takesPlayer) ? Array.Empty<object>() : new object[1] { player }); Inventory val = (Inventory)((obj is Inventory) ? obj : null); if (val != null) { list.Add((item.mod, val)); } } catch (Exception) { } } return list; } private static List<Source> Sources() { if (_sources != null) { return _sources; } _sources = new List<Source>(); Add("Adventure Backpacks", "AdventureBackpacks.API.ABAPI", new Type[1] { typeof(Player) }); Add("Backpacks", "Backpacks.API", Type.EmptyTypes); return _sources; } private static void Add(string mod, string typeName, Type[] args) { Type type = AccessTools.TypeByName(typeName); if (!(type == null)) { MethodInfo methodInfo = AccessTools.Method(type, "GetEquippedBackpackInventory", args, (Type[])null); if (methodInfo == null || !methodInfo.IsStatic || methodInfo.ReturnType != typeof(Inventory)) { ValAgentBridgePlugin.ModLogger.LogWarning((object)(mod + " API changed (GetEquippedBackpackInventory); its backpack contents won't be shown.")); return; } _sources.Add(new Source { mod = mod, method = methodInfo, takesPlayer = (args.Length == 1) }); } } } public static class BlockHold { private static Player? _held; private static bool _patched; private static FieldRef<Character, bool>? _blocking; public static string? Set(Player player, bool active) { //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown if (!active) { if ((Object)(object)_held != (Object)null && (Object)(object)_held == (Object)(object)player && _blocking != null) { _blocking.Invoke((Character)(object)player) = false; } _held = null; return null; } if (!_patched) { try { _blocking = AccessTools.FieldRefAccess<Character, bool>("m_blocking"); MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(Player), "SetControls", (Type[])null, (Type[])null); if (methodInfo == null) { return "Player.SetControls not found; blocking can't be held."; } new Harmony("BarryWhite.ValAgentBridge.block").Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(BlockHold), "SetControlsPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patched = true; } catch (Exception ex) { return "Blocking can't be held: " + ex.GetBaseException().Message; } } _held = player; _blocking.Invoke((Character)(object)player) = true; return null; } private static void SetControlsPostfix(Player __instance) { if (!((Object)(object)_held == (Object)null)) { if ((Object)(object)_held != (Object)(object)Player.m_localPlayer) { _held = null; } else if ((Object)(object)__instance == (Object)(object)_held) { _blocking.Invoke((Character)(object)__instance) = true; } } } } public static class Inspector { public sealed class PathEnd { public Type OwnerType = typeof(object); public object? Owner; public string? Member; public int Index = -1; public bool IsTypeOnly { get { if (Member == null) { return Index < 0; } return false; } } } public const BindingFlags All = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; private const int MaxItems = 50; private static readonly object IndexLock = new object(); private static int _indexedAssemblies = -1; private static List<Type> _allTypes = new List<Type>(); private static Dictionary<string, Type> _byFullName = new Dictionary<string, Type>(); private static Dictionary<string, List<Type>> _byShortName = new Dictionary<string, List<Type>>(); public static object? Describe(object? value, int depth, int nodeBudget = 3000) { int budget = nodeBudget; return Walk(value, depth, ref budget); } private static object? Walk(object? value, int depth, ref int budget) { if (--budget < 0) { return "(output limit reached)"; } if (value == null) { return null; } Object val = (Object)((value is Object) ? value : null); if (val != null && val == (Object)null) { return "(destroyed)"; } Type type = value.GetType(); if (type.IsPrimitive || value is string || value is decimal) { return value; } if (type.IsEnum) { return value.ToString(); } if (value is Type type2) { return type2.FullName; } if (value is Delegate obj) { return "(delegate " + obj.Method.DeclaringType?.Name + "." + obj.Method.Name + ")"; } if (value is Vector2 || value is Vector3 || value is Vector4 || value is Quaternion || value is Color || value is Vector2Int || value is Vector3Int) { return value.ToString(); } Object val2 = (Object)((value is Object) ? value : null); if (val2 != null && depth <= 0) { return val2.name + " (" + type.Name + ")"; } if (depth <= 0) { return "(" + TypeName(type) + ")"; } if (value is IDictionary dictionary) { Dictionary<string, object> dictionary2 = new Dictionary<string, object> { ["$count"] = dictionary.Count }; int num = 0; foreach (DictionaryEntry item in dictionary) { if (num++ >= 50) { dictionary2["$more"] = dictionary.Count - 50; break; } dictionary2[item.Key?.ToString() ?? "null"] = Walk(item.Value, depth - 1, ref budget); } return dictionary2; } if (value is IEnumerable enumerable) { List<object> list = new List<object>(); int num2 = 0; foreach (object item2 in enumerable) { if (num2++ >= 50) { list.Add("(more items not shown)"); break; } list.Add(Walk(item2, depth - 1, ref budget)); } return list; } Dictionary<string, object> dictionary3 = new Dictionary<string, object> { ["$type"] = TypeName(type) }; Object val3 = (Object)((value is Object) ? value : null); if (val3 != null) { dictionary3["$name"] = val3.name; } List<FieldInfo> list2; try { list2 = InstanceFields(type).ToList(); } catch (Exception ex) { dictionary3["$fields"] = "(unreadable: " + ex.GetBaseException().GetType().Name + ": " + ex.GetBaseException().Message + ")"; return dictionary3; } foreach (FieldInfo item3 in list2) { object value2; try { value2 = item3.GetValue(value); } catch (Exception ex2) { value2 = "(error: " + ex2.GetType().Name + ")"; } dictionary3[CleanName(item3.Name)] = Walk(value2, depth - 1, ref budget); } return dictionary3; } public static IEnumerable<FieldInfo> InstanceFields(Type type) { Type t = type; while (t != null && t != typeof(object) && !IsUnityType(t)) { FieldInfo[] fields = t.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < fields.Length; i++) { yield return fields[i]; } t = t.BaseType; } } public static bool IsUnityType(Type t) { if (!(t.Namespace ?? "").StartsWith("UnityEngine")) { return t.Assembly.GetName().Name.StartsWith("UnityEngine"); } return true; } public static string CleanName(string name) { if (!name.StartsWith("<") || !name.Contains(">k__BackingField")) { return name; } return name.Substring(1, name.IndexOf('>') - 1); } public static string TypeName(Type t) { if (!t.IsGenericType) { return t.Name; } return t.Name.Split(new char[1] { '`' })[0] + "<" + string.Join(",", t.GetGenericArguments().Select(TypeName)) + ">"; } public static IReadOnlyList<Type> AllTypes() { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); lock (IndexLock) { if (assemblies.Length == _indexedAssemblies) { return _allTypes; } List<Type> list = new List<Type>(); Dictionary<string, Type> dictionary = new Dictionary<string, Type>(); Dictionary<string, List<Type>> dictionary2 = new Dictionary<string, List<Type>>(); Assembly[] array = assemblies; foreach (Assembly assembly in array) { if (assembly.IsDynamic) { continue; } Type[] types; try { types = assembly.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } catch { continue; } Type[] array2 = types; foreach (Type type in array2) { if (type == null) { continue; } list.Add(type); string key = (type.FullName ?? type.Name).Replace('+', '.'); if (!dictionary.ContainsKey(key)) { dictionary[key] = type; } if (!type.IsNested) { if (!dictionary2.TryGetValue(type.Name, out var value)) { value = (dictionary2[type.Name] = new List<Type>()); } value.Add(type); } } } _allTypes = list; _byFullName = dictionary; _byShortName = dictionary2; _indexedAssemblies = assemblies.Length; return _allTypes; } } public static Type? ResolveType(string name, out string? ambiguous) { ambiguous = null; AllTypes(); lock (IndexLock) { if (_byFullName.TryGetValue(name.Replace('+', '.'), out Type value)) { return value; } } if (Enumerable.Contains(name, '.')) { return null; } List<Type> list; lock (IndexLock) { list = (_byShortName.TryGetValue(name, out List<Type> value2) ? value2 : new List<Type>()); } if (list.Count == 1) { return list[0]; } if (list.Count > 1) { List<Type> list2 = list.Where((Type t) => t.Assembly.GetName().Name == "assembly_valheim").ToList(); if (list2.Count == 1) { return list2[0]; } ambiguous = string.Join(", ", list.Select((Type t) => t.FullName).Take(6)); } return null; } public static PathEnd? ResolvePath(string path, out string? error) { error = null; string[] array = path.Split(new char[1] { '.' }); Type type = null; int num = 0; int num2 = array.Length; while (num2 >= 1 && type == null) { string text = string.Join(".", array.Take(num2)); type = ResolveType(text, out string ambiguous); if (ambiguous != null) { error = "'" + text + "' matches several types: " + ambiguous + ". Use the full name."; return null; } if (type != null) { num = num2; } num2--; } if (type == null) { error = "No type found at the start of '" + path + "'. Try debug/find_types."; return null; } if (num == array.Length) { return new PathEnd { OwnerType = type }; } object next = null; Type type2 = type; int num3 = num; while (num3 < array.Length - 1) { if (!Step(type2, next, array[num3], out next, out error)) { return null; } if (next != null) { Object val = (Object)((next is Object) ? next : null); if (val == null || !(val == (Object)null)) { type2 = next.GetType(); num3++; continue; } } error = "'" + string.Join(".", array.Take(num3 + 1)) + "' is null"; return null; } string text2 = array[^1]; if (int.TryParse(text2, out var result) && next is IList) { return new PathEnd { OwnerType = type2, Owner = next, Index = result }; } return new PathEnd { OwnerType = type2, Owner = next, Member = text2 }; } public static bool Read(PathEnd end, out object? value, out string? error) { value = null; error = null; if (end.Index >= 0) { IList list = (IList)end.Owner; if (end.Index >= list.Count) { error = $"Index {end.Index} is outside 0..{list.Count - 1}"; return false; } value = list[end.Index]; return true; } try { return TryGetMember(end.OwnerType, end.Owner, end.Member, out value, out error); } catch (Exception ex) { error = "Reading '" + end.Member + "' threw " + ex.GetBaseException().GetType().Name + ": " + ex.GetBaseException().Message; return false; } } public static bool Write(PathEnd end, string text, out object? before, out object? after, out string? error) { before = (after = null); error = null; if (end.IsTypeOnly) { error = "The path names a type, not a value."; return false; } if (end.Owner != null && end.OwnerType.IsValueType) { error = "'" + end.Member + "' belongs to a struct copy (" + end.OwnerType.Name + "); writing it would change nothing. Set the whole struct one level up."; return false; } try { if (end.Index >= 0) { IList list = (IList)end.Owner; Type type = (end.OwnerType.IsArray ? end.OwnerType.GetElementType() : (end.OwnerType.IsGenericType ? end.OwnerType.GetGenericArguments()[0] : typeof(object))); before = list[end.Index]; list[end.Index] = ConvertText(text, type); after = list[end.Index]; return true; } BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; Type type2 = end.OwnerType; while (type2 != null) { FieldInfo fieldInfo = type2.GetField(end.Member, bindingFlags | BindingFlags.DeclaredOnly) ?? type2.GetField("<" + end.Member + ">k__BackingField", bindingFlags | BindingFlags.DeclaredOnly); if (fieldInfo != null) { if (fieldInfo.IsLiteral) { error = "'" + end.Member + "' is a constant"; return false; } if (!fieldInfo.IsStatic && end.Owner == null) { error = "'" + end.Member + "' needs an instance"; return false; } object obj = (fieldInfo.IsStatic ? null : end.Owner); before = fieldInfo.GetValue(obj); fieldInfo.SetValue(obj, ConvertText(text, fieldInfo.FieldType)); after = fieldInfo.GetValue(obj); return true; } PropertyInfo property = type2.GetProperty(end.Member, bindingFlags | BindingFlags.DeclaredOnly); if (property != null && property.GetIndexParameters().Length == 0) { MethodInfo setMethod = property.GetSetMethod(nonPublic: true); if (setMethod == null) { error = "'" + end.Member + "' is read-only"; return false; } if (!setMethod.IsStatic && end.Owner == null) { error = "'" + end.Member + "' needs an instance"; return false; } object obj2 = (setMethod.IsStatic ? null : end.Owner); before = property.GetGetMethod(nonPublic: true)?.Invoke(obj2, null); setMethod.Invoke(obj2, new object[1] { ConvertText(text, property.PropertyType) }); after = property.GetGetMethod(nonPublic: true)?.Invoke(obj2, null); return true; } type2 = type2.BaseType; } error = end.OwnerType.Name + " has no field or property '" + end.Member + "'"; return false; } catch (Exception ex) { error = "Could not set '" + end.Member + "': " + ex.GetBaseException().Message; return false; } } public static object? ConvertText(string text, Type type) { //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) if (type == typeof(string)) { return text; } if (text == "null" && !type.IsValueType) { return null; } Type type2 = Nullable.GetUnderlyingType(type) ?? type; if (type2.IsEnum) { return Enum.Parse(type2, text, ignoreCase: true); } if (type2.IsPrimitive || type2 == typeof(decimal)) { return Convert.ChangeType(text, type2, CultureInfo.InvariantCulture); } if (type2 == typeof(Vector3) || type2 == typeof(Vector2)) { float[] array = (from s in text.Trim('(', ')', '[', ']', ' ').Split(new char[1] { ',' }) select float.Parse(s.Trim(), CultureInfo.InvariantCulture)).ToArray(); if (!(type2 == typeof(Vector2))) { return (object)new Vector3(array[0], (array.Length > 1) ? array[1] : 0f, (array.Length > 2) ? array[2] : 0f); } return (object)new Vector2(array[0], array[1]); } return JsonConvert.DeserializeObject(text, type2); } private static bool Step(Type type, object? instance, string seg, out object? next, out string? error) { next = null; error = null; if (int.TryParse(seg, out var result) && instance is IList list) { if (result < 0 || result >= list.Count) { error = $"Index {result} is outside 0..{list.Count - 1}"; return false; } next = list[result]; return true; } try { return TryGetMember(type, instance, seg, out next, out error); } catch (Exception ex) { error = "Reading '" + seg + "' threw " + ex.GetBaseException().GetType().Name + ": " + ex.GetBaseException().Message; return false; } } public static bool TryGetMember(Type type, object? instance, string name, out object? value, out string? error) { value = null; error = null; BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy; Type type2 = type; while (type2 != null) { FieldInfo fieldInfo = type2.GetField(name, bindingFlags | BindingFlags.DeclaredOnly) ?? type2.GetField("<" + name + ">k__BackingField", bindingFlags | BindingFlags.DeclaredOnly); if (fieldInfo != null) { if (!fieldInfo.IsStatic && instance == null) { error = "'" + name + "' needs an instance"; return false; } value = fieldInfo.GetValue(fieldInfo.IsStatic ? null : instance); return true; } PropertyInfo property = type2.GetProperty(name, bindingFlags | BindingFlags.DeclaredOnly); if (property != null && property.GetIndexParameters().Length == 0 && property.GetGetMethod(nonPublic: true) != null) { MethodInfo getMethod = property.GetGetMethod(nonPublic: true); if (!getMethod.IsStatic && instance == null) { error = "'" + name + "' needs an instance"; return false; } value = getMethod.Invoke(getMethod.IsStatic ? null : instance, null); return true; } type2 = type2.BaseType; } error = type.Name + " has no field or property '" + name + "'"; return false; } } public class MainThreadDispatcher : MonoBehaviour { private static MainThreadDispatcher? _instance; private static readonly ConcurrentQueue<Action> ExecutionQueue = new ConcurrentQueue<Action>(); private static int _mainThreadId = -1; private static readonly Stopwatch Clock = Stopwatch.StartNew(); private static long _lastFrameMs = -1L; private static float _smoothedFrameMs; public static MainThreadDispatcher Instance { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("ValAgentBridge_MainThreadDispatcher"); _instance = val.AddComponent<MainThreadDispatcher>(); Object.DontDestroyOnLoad((Object)val); } return _instance; } } public static long FrameAgeMs { get { if (Interlocked.Read(in _lastFrameMs) >= 0) { return Clock.ElapsedMilliseconds - Interlocked.Read(in _lastFrameMs); } return -1L; } } public static float FrameMs => _smoothedFrameMs; public static void Init() { _mainThreadId = Thread.CurrentThread.ManagedThreadId; _ = Instance; } public void Enqueue(Action action) { ExecutionQueue.Enqueue(action); } public static T Run<T>(Func<T> work, int timeoutMs = 10000) { if (Thread.CurrentThread.ManagedThreadId == _mainThreadId) { return work(); } TaskCompletionSource<T> tcs = new TaskCompletionSource<T>(); Instance.Enqueue(delegate { try { tcs.SetResult(work()); } catch (Exception exception) { tcs.SetException(exception); } }); if (!tcs.Task.Wait(timeoutMs)) { throw new TimeoutException("The game's main thread did not answer in time (loading screen or paused?)"); } return tcs.Task.Result; } public static object RunTool(Func<object> work, int timeoutMs = 15000) { try { return Run(work, timeoutMs); } catch (TimeoutException) { return new { success = false, error = "The game's main thread did not answer in time (loading screen or paused?). The action may still happen when the game catches up." }; } catch (Exception ex2) { Exception baseException = ex2.GetBaseException(); return new { success = false, error = baseException.GetType().Name + ": " + baseException.Message }; } } private void Update() { Interlocked.Exchange(ref _lastFrameMs, Clock.ElapsedMilliseconds); float num = Time.unscaledDeltaTime * 1000f; _smoothedFrameMs = ((_smoothedFrameMs <= 0f) ? num : (_smoothedFrameMs * 0.9f + num * 0.1f)); Action result; while (ExecutionQueue.TryDequeue(out result)) { try { result(); } catch (Exception arg) { ValAgentBridgePlugin.ModLogger.LogError((object)$"Queued bridge work failed: {arg}"); } } } } public static class ModCommands { public sealed class Found { public string name = ""; public string mod = ""; public string method = ""; public string? description; public bool registered; public string? problem; } public static readonly List<Found> All = new List<Found>(); public static void RegisterAll(IToolRegistry registry) { foreach (var item3 in from p in Chainloader.PluginInfos.Values where (Object)(object)p.Instance != (Object)null select (mod: p.Metadata.Name, asm: ((object)p.Instance).GetType().Assembly) into x where x.asm != typeof(ModCommands).Assembly group x by x.asm into g select g.First()) { string item = item3.mod; Assembly item2 = item3.asm; int num = 0; Type[] types; try { types = item2.GetTypes(); } catch (ReflectionTypeLoadException ex) { types = ex.Types; } catch { continue; } foreach (Type item4 in types.Where((Type t) => t != null)) { MethodInfo[] methods; try { methods = item4.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); } catch { num++; continue; } MethodInfo[] array = methods; foreach (MethodInfo methodInfo in array) { try { object obj3 = methodInfo.GetCustomAttributes(inherit: false).FirstOrDefault((object a) => a.GetType().Name == "BridgeToolAttribute"); if (obj3 != null) { Register(registry, item, methodInfo, obj3); } } catch (Exception) { num++; } } } if (num > 0) { ValAgentBridgePlugin.ModLogger.LogInfo((object)$"Mod commands: skipped {num} part(s) of {item} that need a mod which isn't installed."); } } if (All.Count > 0) { ValAgentBridgePlugin.ModLogger.LogInfo((object)$"Mod commands: {All.Count((Found f) => f.registered)} registered, {All.Count((Found f) => !f.registered)} skipped"); } } private static void Register(IToolRegistry registry, string mod, MethodInfo method, object attr) { //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_014b: 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_01a1: Expected O, but got Unknown Found found = new Found { mod = mod, method = method.DeclaringType?.FullName + "." + method.Name, name = (Read(attr, "Name") ?? ""), description = Read(attr, "Description") }; All.Add(found); if (!ToolNameValidator.IsValid(found.name)) { found.problem = "name '" + found.name + "' must look like 'mymod/do_thing' (lowercase, at least one '/')"; ValAgentBridgePlugin.ModLogger.LogWarning((object)("Mod command from " + mod + " skipped: " + found.problem)); return; } if (registry.HasTool(found.name)) { found.problem = "a command with this name already exists"; ValAgentBridgePlugin.ModLogger.LogWarning((object)("Mod command " + found.name + " from " + mod + " skipped: " + found.problem)); return; } ParameterInfo[] parameters = method.GetParameters(); ToolInfo info = new ToolInfo { Name = found.name, Description = "[" + mod + "] " + found.description, Parameters = ((IEnumerable<ParameterInfo>)parameters).Select((Func<ParameterInfo, ToolParameterInfo>)((ParameterInfo p) => new ToolParameterInfo { Name = (p.Name ?? ""), Type = p.ParameterType, Required = !p.HasDefaultValue, DefaultValue = (p.HasDefaultValue ? p.DefaultValue : null) })).ToList() }; registry.RegisterTool(found.name, (Func<object, Task<object>>)delegate(object input) { string text = ToolGuard.CheckArgs(info, input); return Task.FromResult((text != null) ? new { success = false, error = text } : Invoke(method, parameters, input)); }, info); found.registered = true; } private static object Invoke(MethodInfo method, ParameterInfo[] parameters, object input) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown JObject args; try { args = (JObject)((input != null) ? ((object)((input is string text) ? JObject.Parse(text) : JObject.FromObject(input))) : ((object)new JObject())); } catch (Exception ex) { return new { success = false, error = "Bad arguments: " + ex.Message }; } object?[] values; try { values = parameters.Select(delegate(ParameterInfo p) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 JProperty? obj = args.Properties().FirstOrDefault((Func<JProperty, bool>)((JProperty x) => string.Equals(x.Name, p.Name, StringComparison.OrdinalIgnoreCase))); JToken val = ((obj != null) ? obj.Value : null); return (val == null || (int)val.Type == 10) ? ((!p.HasDefaultValue) ? ((!p.ParameterType.IsValueType) ? null : Activator.CreateInstance(p.ParameterType)) : p.DefaultValue) : val.ToObject(p.ParameterType); }).ToArray(); } catch (Exception ex2) { return new { success = false, error = "Bad arguments: " + ex2.Message }; } return MainThreadDispatcher.RunTool(() => method.Invoke(null, values) ?? new { success = true }, 30000); } private static string? Read(object attr, string property) { PropertyInfo property2 = attr.GetType().GetProperty(property); if (property2 != null) { return property2.GetValue(attr) as string; } return attr.GetType().GetField(property)?.GetValue(attr) as string; } } public class MovementManager : MonoBehaviour { private static MovementManager? _instance; private bool _isMoving; private TaskCompletionSource<object>? _tcs; private float _startTime; private float _duration; private Vector3 _moveDir; private bool _run; public static MovementManager Instance { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("ValAgentBridge_MovementManager"); _instance = val.AddComponent<MovementManager>(); Object.DontDestroyOnLoad((Object)val); } return _instance; } } public Task<object> StartMoving(Vector3 direction, bool run, float duration) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) StopMoving(); _moveDir = ((Vector3)(ref direction)).normalized; _run = run; _duration = duration; _startTime = Time.time; _isMoving = true; _tcs = new TaskCompletionSource<object>(); return _tcs.Task; } public void StopMoving() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) if (_isMoving) { _isMoving = false; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).SetMoveDir(Vector3.zero); ((Character)localPlayer).SetRun(false); } } } private void FixedUpdate() { if (!_isMoving || _tcs == null) { return; } try { Step(); } catch (Exception ex) { StopMoving(); _tcs?.TrySetResult(new { success = false, message = "Moving failed: " + ex.GetBaseException().Message }); } } private void Step() { //IL_003d: 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) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Finish("Player lost"); return; } if (Time.time - _startTime >= _duration) { Finish("Completed"); return; } ((Character)localPlayer).SetMoveDir(_moveDir); ((Character)localPlayer).SetLookDir(_moveDir, 0f); ((Character)localPlayer).SetRun(_run); } private void Finish(string message) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; Vector3 val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).transform.position : Vector3.zero); StopMoving(); _tcs?.TrySetResult(new { success = true, message = message, position = new { val.x, val.y, val.z } }); } } public class NavigationManager : MonoBehaviour { private static NavigationManager? _instance; private Vector3 _targetPos; private readonly List<Vector3> _navPath = new List<Vector3>(); private bool _isNavigating; private TaskCompletionSource<object>? _tcs; private float _startTime; private float _timeout; private float _lastPathCalcTime; private const float ArrivalDistXZ = 1.5f; private const float WaypointAdvanceDist = 1.5f; private const float PathRecalcInterval = 1f; private const float RunDistanceThreshold = 5f; public static NavigationManager Instance { get { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if ((Object)(object)_instance == (Object)null) { GameObject val = new GameObject("ValAgentBridge_NavigationManager"); _instance = val.AddComponent<NavigationManager>(); Object.DontDestroyOnLoad((Object)val); } return _instance; } } public Task<object> StartNavigation(Vector3 target, float timeout) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) StopNavigation("Cancelled by new navigation request"); _targetPos = target; _timeout = timeout; _startTime = Time.time; _lastPathCalcTime = 0f; _navPath.Clear(); _isNavigating = true; _tcs = new TaskCompletionSource<object>(); ValAgentBridgePlugin.ModLogger.LogInfo((object)$"Navigation started to ({target.x:F1}, {target.z:F1}), timeout={timeout}s"); return _tcs.Task; } public void StopNavigation(string? reason = null) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (_isNavigating) { _isNavigating = false; _navPath.Clear(); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { ((Character)localPlayer).SetMoveDir(Vector3.zero); ((Character)localPlayer).SetRun(false); } if (reason != null) { ValAgentBridgePlugin.ModLogger.LogInfo((object)("Navigation stopped: " + reason)); } } } private void FixedUpdate() { if (!_isNavigating || _tcs == null) { return; } try { Step(); } catch (Exception ex) { Finish(success: false, "Navigation failed: " + ex.GetBaseException().Message); } } private void Step() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { Finish(success: false, "Player lost"); return; } Vector3 position = ((Component)localPlayer).transform.position; if (Time.time - _startTime > _timeout) { float num = Utils.DistanceXZ(position, _targetPos); Finish(success: false, $"Timeout after {_timeout}s (distance remaining: {num:F1}m)"); return; } float num2 = Utils.DistanceXZ(position, _targetPos); if (num2 < 1.5f) { Finish(success: true, $"Arrived (distance: {num2:F2}m)"); } else { if (!((Character)localPlayer).CanMove()) { return; } if (Time.time - _lastPathCalcTime > 1f) { _lastPathCalcTime = Time.time; if ((Object)(object)Pathfinding.instance == (Object)null) { Finish(success: false, "Pathfinding isn't available (still loading?)"); return; } if (!Pathfinding.instance.GetPath(position, _targetPos, _navPath, (AgentType)1, false, true, false) || _navPath.Count == 0) { Finish(success: false, "No path found"); return; } } if (_navPath.Count != 0) { while (_navPath.Count > 1 && Utils.DistanceXZ(position, _navPath[0]) < 1.5f) { _navPath.RemoveAt(0); } Vector3 val = _navPath[0] - position; val.y = 0f; if (((Vector3)(ref val)).magnitude > 0.1f) { ((Vector3)(ref val)).Normalize(); } ((Character)localPlayer).SetMoveDir(val); ((Character)localPlayer).SetLookDir(val, 0f); ((Character)localPlayer).SetRun(num2 > 5f); } } } private void Finish(bool success, string message) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: 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_0034: 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_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; Vector3 val = (((Object)(object)localPlayer != (Object)null) ? ((Component)localPlayer).transform.position : Vector3.zero); float num = (((Object)(object)localPlayer != (Object)null) ? Utils.DistanceXZ(val, _targetPos) : (-1f)); StopNavigation(message); _tcs?.TrySetResult(new { success = success, message = message, finalPosition = new { val.x, val.y, val.z }, distanceToTarget = (float)Math.Round(num, 2) }); } } public static class PlayerEvents { private static IEventManager? _events; private static float _lastHealth = -1f; private static float _nextSample; public static void Install(Harmony harmony, IEventManager events) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown _events = events; MethodInfo methodInfo = AccessTools.Method(typeof(Player), "OnDeath", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(PlayerEvents), "OnDeathPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { ValAgentBridgePlugin.ModLogger.LogWarning((object)"Player.OnDeath not found; player/death events won't be sent."); } } public static void Tick() { if (Time.unscaledTime < _nextSample) { return; } _nextSample = Time.unscaledTime + 0.5f; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { _lastHealth = -1f; return; } float health = ((Character)localPlayer).GetHealth(); if (_lastHealth < 0f) { _lastHealth = health; return; } float num = health - _lastHealth; if (!(Mathf.Abs(num) < 1f)) { _lastHealth = health; Send("player/health_changed", new { health = (float)Math.Round(health, 1), maxHealth = (float)Math.Round(((Character)localPlayer).GetMaxHealth(), 1), change = (float)Math.Round(num, 1) }); } } private static void OnDeathPostfix(Player __instance) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001f: 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_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { Vector3 position = ((Component)__instance).transform.position; Send("player/death", new { position = new { x = (float)Math.Round(position.x, 1), y = (float)Math.Round(position.y, 1), z = (float)Math.Round(position.z, 1) }, biome = ((object)__instance.GetCurrentBiome()/*cast due to .constrained prefix*/).ToString() }); _lastHealth = -1f; } } private static void Send(string channel, object payload) { Recorder.AddEvent(channel, payload); try { IEventManager? events = _events; if (events != null) { events.EmitEventAsync(channel, payload, (DateTimeOffset?)null).ContinueWith(delegate(Task t) { _ = t.Exception; }); } } catch (Exception) { } } } public static class PlayerLook { private static readonly FieldRef<Character, Quaternion> LookYaw = AccessTools.FieldRefAccess<Character, Quaternion>("m_lookYaw"); private static readonly FieldRef<Player, float> LookPitch = AccessTools.FieldRefAccess<Player, float>("m_lookPitch"); public static void Set(Player player, float heading, float tilt) { //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_0049: 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) tilt = Mathf.Clamp(tilt, -80f, 80f); LookYaw.Invoke((Character)(object)player) = Quaternion.Euler(0f, heading, 0f); LookPitch.Invoke(player) = 0f - tilt; ((Character)player).SetLookDir(Quaternion.Euler(0f - tilt, heading, 0f) * Vector3.forward, 0f); player.FaceLookDirection(); } public static bool Face(Player player, Vector3 direction) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) direction.y = 0f; if (((Vector3)(ref direction)).magnitude < 0.01f) { return false; } Set(player, Mathf.Atan2(direction.x, direction.z) * 57.29578f, 0f); return true; } } public class BridgeConfig { [JsonProperty("port")] public int Port { get; set; } [JsonProperty("token")] public string Token { get; set; } = ""; [JsonProperty("gameId")] public string GameId { get; set; } = "valheim"; } [BepInPlugin("BarryWhite.ValAgentBridge", "ValAgentBridge", "1.5.5")] public class ValAgentBridgePlugin : BaseUnityPlugin { private const string ModName = "ValAgentBridge"; private const string ModVersion = "1.5.5"; private const string Author = "BarryWhite"; private const string ModGUID = "BarryWhite.ValAgentBridge"; public static readonly ManualLogSource ModLogger = Logger.CreateLogSource("ValAgentBridge"); private GabpServer? _server; private Harmony? _harmony; private bool _bridgeUp; public static IToolRegistry? Tools { get; private set; } public void Start() { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown ModLogger.LogInfo((object)"ValAgentBridge v1.5.5 is loading..."); if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GABP_SERVER_PORT")) && string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GABS_BRIDGE_PATH"))) { ModLogger.LogInfo((object)"Not started by GABS, so the bridge stays off."); return; } try { _harmony = new Harmony("BarryWhite.ValAgentBridge"); Harmony recorderHarmony = _harmony; Optional("Log and message recording", delegate { Recorder.Install(recorderHarmony); }); BridgeConfig bridgeConfig = ReadBridgeConfig(); ModLogger.LogInfo((object)$"GABP config loaded - GameId: {bridgeConfig.GameId}, Port: {bridgeConfig.Port}"); _server = Gabp.CreateServerWithExternalConfig("Valheim", "1.5.5", bridgeConfig.Port, bridgeConfig.Token, bridgeConfig.GameId); ToolGuard.RegisterChecked(_server.Tools, new PlayerTools()); ToolGuard.RegisterChecked(_server.Tools, new ZNetSceneTools()); ToolGuard.RegisterChecked(_server.Tools, new TerminalTools()); ToolGuard.RegisterChecked(_server.Tools, new WorldTools()); ToolGuard.RegisterChecked(_server.Tools, new ModTools()); ToolGuard.RegisterChecked(_server.Tools, new ScreenTools()); ToolGuard.RegisterChecked(_server.Tools, new LogTools()); ToolGuard.RegisterChecked(_server.Tools, new UiTools()); ToolGuard.RegisterChecked(_server.Tools, new TimeTools()); ToolGuard.RegisterChecked(_server.Tools, new EditTools()); ToolGuard.RegisterChecked(_server.Tools, new WatchTools()); ToolGuard.RegisterChecked(_server.Tools, new TestTools()); ToolGuard.RegisterChecked(_server.Tools, new ItemTools()); ToolGuard.RegisterChecked(_server.Tools, new ControlTools()); ToolGuard.RegisterChecked(_server.Tools, new InputTools()); Optional("Commands from other mods", delegate { ModCommands.RegisterAll(_server.Tools); }); Tools = _server.Tools; _server.Events.RegisterChannel("player/death", "Player death events"); _server.Events.RegisterChannel("player/health_changed", "Player health change events"); Harmony harmony = _harmony; IEventManager events = _server.Events; Optional("Player events", delegate { PlayerEvents.Install(harmony, events); }); _bridgeUp = true; MainThreadDispatcher.Init(); _ = NavigationManager.Instance; _ = MovementManager.Instance; _server.StartAsync(default(CancellationToken)).ContinueWith(delegate(Task task) { if (task.IsFaulted) { ModLogger.LogError((object)("GABP server failed to start: " + task.Exception?.InnerException?.Message)); } else { ModLogger.LogInfo((object)$"GABP server listening on 127.0.0.1:{_server.Port}"); ModLogger.LogInfo((object)"ValAgentBridge loaded successfully!"); } }); } catch (Exception ex) { ModLogger.LogError((object)("Failed to initialize GABP server: " + ex.Message)); ModLogger.LogError((object)("Stack trace: " + ex.StackTrace)); } } private static void Optional(string what, Action step) { try { step(); } catch (Exception ex) { Exception baseException = ex.GetBaseException(); ModLogger.LogWarning((object)(what + " unavailable (" + baseException.GetType().Name + ": " + baseException.Message + "); the bridge runs without it.")); } } private BridgeConfig ReadBridgeConfig() { string environmentVariable = Environment.GetEnvironmentVariable("GABS_GAME_ID"); string environmentVariable2 = Environment.GetEnvironmentVariable("GABP_SERVER_PORT"); string environmentVariable3 = Environment.GetEnvironmentVariable("GABP_TOKEN"); if (!string.IsNullOrEmpty(environmentVariable2) && !string.IsNullOrEmpty(environmentVariable3) && int.TryParse(environmentVariable2, out var result)) { ModLogger.LogInfo((object)"Config loaded from environment variables"); return new BridgeConfig { Port = result, Token = environmentVariable3, GameId = (environmentVariable ?? "valheim") }; } string environmentVariable4 = Environment.GetEnvironmentVariable("GABS_BRIDGE_PATH"); if (!string.IsNullOrEmpty(environmentVariable4) && File.Exists(environmentVariable4)) { ModLogger.LogInfo((object)("Config loaded from bridge file: " + environmentVariable4)); return JsonConvert.DeserializeObject<BridgeConfig>(File.ReadAllText(environmentVariable4)) ?? throw new Exception("Failed to parse bridge.json"); } throw new Exception("GABP server config not found. Start the game through GABS. Expected: GABP_SERVER_PORT and GABP_TOKEN environment variables, or GABS_BRIDGE_PATH."); } private void Update() { if (_bridgeUp) { PlayerEvents.Tick(); } } private void OnDestroy() { ModLogger.LogInfo((object)"ValAgentBridge is unloading..."); if (_server == null) { return; } try { _server.StopAsync(default(CancellationToken)).ContinueWith(delegate(Task task) { if (task.IsFaulted) { ModLogger.LogError((object)("Error stopping GABP server: " + task.Exception?.InnerException?.Message)); } else { ModLogger.LogInfo((object)"GABP server stopped"); } }); _server.Dispose(); } catch (Exception ex) { ModLogger.LogError((object)("Error disposing GABP server: " + ex.Message)); } } } public static class Recorder { public sealed class LogEntry { public long seq; public string time = ""; public string level = ""; public string source = ""; public string text = ""; } public sealed class MessageEntry { public long seq; public string time = ""; public string type = ""; public string text = ""; } public sealed class EventEntry { public long seq; public string time = ""; public string channel = ""; public object? data; } private sealed class Listener : ILogListener, IDisposable { public void LogEvent(object sender, LogEventArgs e) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { string text = ((object)e.Level/*cast due to .constrained prefix*/).ToString(); string text2; try { text2 = e.Data?.ToString() ?? ""; } catch (Exception ex) { text2 = "(log line could not be read: " + ex.GetType().Name + ")"; } LogEntry logEntry = new LogEntry(); logEntry.time = DateTime.Now.ToString("HH:mm:ss.fff"); logEntry.level = (text.Contains(",") ? text.Split(new char[1] { ',' })[0].Trim() : text); ILogSource source = e.Source; logEntry.source = ((source != null) ? source.SourceName : null) ?? ""; logEntry.text = text2; LogEntry logEntry2 = logEntry; lock (Gate) { logEntry2.seq = ++_logSeq; Log.AddLast(logEntry2); while (Log.Count > 3000) { Log.RemoveFirst(); } } } catch { } } public void Dispose() { } } private static readonly LinkedList<EventEntry> Events = new LinkedList<EventEntry>(); private static long _eventSeq; private const int MaxLog = 3000; private const int MaxMessages = 200; private static readonly object Gate = new object(); private static readonly LinkedList<LogEntry> Log = new LinkedList<LogEntry>(); private static readonly LinkedList<MessageEntry> Messages = new LinkedList<MessageEntry>(); private static long _logSeq; private static long _msgSeq; private static List<string>? _consoleCapture; public static long LastEventSeq { get { lock (Gate) { return _eventSeq; } } } public static long LastLogSeq { get { lock (Gate) { return _logSeq; } } } public static long LastMessageSeq { get { lock (Gate) { return _msgSeq; } } } public static void AddEvent(string channel, object data) { lock (Gate) { Events.AddLast(new EventEntry { seq = ++_eventSeq, time = DateTime.Now.ToString("HH:mm:ss.fff"), channel = channel, data = data }); while (Events.Count > 500) { Events.RemoveFirst(); } } } public static List<EventEntry> EventsSince(long since, string channel, int limit) { lock (Gate) { return Events.Where((EventEntry e) => e.seq > since && (channel.Length == 0 || e.channel == channel)).Reverse().Take(limit) .Reverse() .ToList(); } } public static void Install(Harmony harmony) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Expected O, but got Unknown Logger.Listeners.Add((ILogListener)(object)new Listener()); MethodInfo methodInfo = AccessTools.Method(typeof(MessageHud), "ShowMessage", (Type[])null, (Type[])null); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(Recorder), "ShowMessagePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { ValAgentBridgePlugin.ModLogger.LogWarning((object)"MessageHud.ShowMessage not found; ui/messages will stay empty."); } MethodInfo methodInfo2 = AccessTools.Method(typeof(Terminal), "AddString", new Type[1] { typeof(string) }, (Type[])null); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(Recorder), "AddStringPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } public static List<string> CaptureConsole(Action run) { List<string> result = (_consoleCapture = new List<string>()); try { run(); return result; } finally { _consoleCapture = null; } } private static void AddStringPostfix(string text) { _consoleCapture?.Add(text); } public static List<LogEntry> LogSince(long since, string minLevel, string filter, int limit) { HashSet<string> allowed = AllowedLevels(minLevel); lock (Gate) { return Log.Where((LogEntry e) => e.seq > since && allowed.Contains(e.level) && (filter.Length == 0 || e.text.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0 || e.source.IndexOf(filter, StringComparison.OrdinalIgnoreCase) >= 0)).Reverse().Take(limit) .Reverse() .ToList(); } } public static List<MessageEntry> MessagesSince(long since, int limit) { lock (Gate) { return Messages.Where((MessageEntry m) => m.seq > since).Reverse().Take(limit) .Reverse() .ToList(); } } private static HashSet<string> AllowedLevels(string minLevel) { string[] source = new string[6] { "Fatal", "Error", "Warning", "Message", "Info", "Debug" }; return new HashSet<string>(source.Take((minLevel ?? "").ToLowerInvariant() switch { "error" => 1, "warning" => 2, "all" => 5, "debug" => 5, _ => 4, } + 1)); } private unsafe static void ShowMessagePostfix(MessageType type, string text) { try { string text2 = ((Localization.instance != null) ? Localization.instance.Localize(text) : text); lock (Gate) { Messages.AddLast(new MessageEntry { seq = ++_msgSeq, time = DateTime.Now.ToString("HH:mm:ss.fff"), type = ((object)(*(MessageType*)(&type))/*cast due to .constrained prefix*/).ToString(), text = text2 }); while (Messages.Count > 200) { Messages.RemoveFirst(); } } } catch { } } } public static class ToolGuard { public static void RegisterChecked(IToolRegistry target, object instance) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown ToolRegistry inner = new ToolRegistry(); inner.RegisterToolsFromInstance(instance); foreach (ToolInfo info in inner.GetTools()) { string name = info.Name; target.RegisterTool(name, (Func<object, Task<object>>)delegate(object input) { string text = CheckArgs(info, input); return (text == null) ? inner.CallToolAsync(name, input) : Task.FromResult((object)new { success = false, error = text }); }, info); } } public static string? CheckArgs(ToolInfo info, object? input) { JObject val; try { val = (JObject)((input == null) ? null : (((input is JObject) ? input : null) ?? ((input is string text) ? JObject.Parse(text) : JObject.FromObject(input)))); } catch { return null; } List<ToolParameterInfo> known = info.Parameters; List<string> given = ((val != null) ? (from p in val.Properties() select p.Name).ToList() : null) ?? new List<string>(); List<string> list = given.Where((string g) => !known.Any((ToolParameterInfo p) => string.Equals(p.Name, g, StringComparison.OrdinalIgnoreCase))).ToList(); List<string> list2 = (from p in known where p.Required && !given.Any((string g) => string.Equals(p.Name, g, StringComparison.OrdinalIgnoreCase)) select p.Name).ToList(); if (list.Count == 0 && list2.Count == 0) { return null; } List<string> list3 = new List<string>(); if (list.Count > 0) { list3.Add("unknown argument" + ((list.Count > 1) ? "s" : "") + " " + string.Join(", ", list.Select((string u) => "'" + u + "'"))); } if (list2.Count > 0) { list3.Add("missing " + string.Join(", ", list2.Select((string m) => "'" + m + "'"))); } return info.Name + ": " + string.Join("; ", list3) + ". It takes: " + Describe(known) + "."; } private static string Describe(List<ToolParameterInfo> parameters) { if (parameters.Count == 0) { return "no arguments"; } return string.Join(", ", parameters.Select((ToolParameterInfo p) => (!p.Required) ? p.Name : (p.Name + " (required)"))); } public static List<string> Suggest(IToolRegistry registry, string wanted, int max = 3) { wanted = wanted.Replace('.', '/').ToLowerInvariant(); string area = (Enumerable.Contains(wanted, '/') ? wanted.Substring(0, wanted.IndexOf('/')) : ""); string verb = wanted.Substring(wanted.IndexOf('/') + 1); return (from x in (from x in (from t in registry.GetTools() select t.Name).Select(delegate(string n) { string text = n.ToLowerInvariant(); string text2 = text.Substring(0, Math.Max(0, text.IndexOf('/'))); string nVerb = text.Substring(text.IndexOf('/') + 1); int num = Distance(verb, nVerb) + ((!(text2 == area)) ? 6 : 0); if (verb.Split(new char[1] { '_' }).Any((string w) => w.Length > 2 && nVerb.Contains(w))) { num -= 3; } return (n: n, score: num); }) orderby x.score, x.n select x).Take(max) select x.n).ToList(); } private static int Distance(string a, string b) { int[,] array = new int[a.Length + 1, b.Length + 1]; for (int i = 0; i <= a.Length; i++) { array[i, 0] = i; } for (int j = 0; j <= b.Length; j++) { array[0, j] = j; } for (int k = 1; k <= a.Length; k++) { for (int l = 1; l <= b.Length; l++) { array[k, l] = Math.Min(Math.Min(array[k - 1, l] + 1, array[k, l - 1] + 1), array[k - 1, l - 1] + ((a[k - 1] != b[l - 1]) ? 1 : 0)); } } return array[a.Length, b.Length]; } } } namespace ValAgentBridge.Tools { public class ControlTools { private const float HighUp = 800f; private const float RayStart = 1000f; private static readonly int SolidMask = LayerMask.GetMask(new string[5] { "Default", "static_solid", "Default_small", "piece", "terrain" }); private static FieldRef<Character, float>? _maxAirAltitude; private static readonly object KeepClearGate = new object(); private static Timer? _keepClear; private static float _keepClearRadius; private static double _keepClearEvery; private static bool _keepClearBosses; private static int _keepClearRemoved; private static int _keepClearBusy; public static bool KeepClearOn { get; private set; } public static bool KeepClearChosen { get; private set; } private static void Log(string text) { ValAgentBridgePlugin.ModLogger.LogInfo((object)text); } private static bool TryLandingHeight(float x, float z, out float y) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) y = 0f; ZoneSystem instance = ZoneSystem.instance; if ((Object)(object)instance == (Object)null) { return false; } foreach (RaycastHit item in from h in Physics.RaycastAll(new Vector3(x, 1000f, z), Vector3.down, 1100f, SolidMask, (QueryTriggerInteraction)1) orderby ((RaycastHit)(ref h)).distance select h) { RaycastHit current = item; if (!((Object)(object)((RaycastHit)(ref current)).collider.attachedRigidbody != (Object)null) && !((Object)(object)((Component)((RaycastHit)(ref current)).collider).GetComponentInParent<TreeBase>() != (Object)null)) { y = Mathf.Max(((RaycastHit)(ref current)).point.y, instance.m_waterLevel) + 0.5f; return true; } } return false; } [Tool("player/teleport", Description = "(cheat) Teleport the player to a world position. Without a height the player lands on the highest surface there: ground, a rock, a roof (not tree tops), or the water surface; give the height to land inside a building. Waits until the teleport has finished (far ones load the area first) and returns where the player ended up.")] public object Teleport([ToolParameter(Description = "X coordinate")] float x, [ToolParameter(Description = "Z coordinate")] float z, [ToolParameter(Description = "Height; leave out to land on top of whatever is there")] float y = float.NaN) { bool landLater = false; int abandoned = 0; string text; try { text = MainThreadDispatcher.Run(delegate { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0088: 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_0099: 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_00c6: 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_00ee: 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_010a: Unknown result type (might be due to invalid IL or missing references) if (Interlocked.CompareExchange(ref abandoned, 0, 0) != 0) { return "abandoned"; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return "No local player (not in a world)."; } Vector3 position = ((Component)localPlayer).transform.position; float y2 = y; if (float.IsNaN(y) && !TryLandingHeight(x, z, out y2)) { y2 = 800f; landLater = true; } Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(x, y2, z); bool flag = landLater || Vector3.Distance(new Vector3(position.x, 0f, position.z), new Vector3(x, 0f, z)) > 64f; if (!((Character)localPlayer).TeleportTo(val, ((Component)localPlayer).transform.rotation, flag)) { return "The game refused (already teleporting, or within 2 s of the last teleport). Try again in a moment."; } Log(string.Format("player/teleport: ({0:F0}, {1:F0}, {2:F0}) -> ({3:F0}, {4}, {5:F0})", position.x, position.y, position.z, x, landLater ? "ground" : y2.ToString("F0"), z)); return (string)null; }, 15000); } catch (TimeoutException) { Interlocked.Exchange(ref abandoned, 1); text = "The game's main thread did not answer in time (loading screen or paused?); the teleport was cancelled."; } catch (Exception ex2) { text = ex2.GetBaseException().Message; } if (text != null) { return new { success = false, error = text }; } Stopwatch stopwatch = Stopwatch.StartNew(); int landings = 0; while (stopwatch.Elapsed.TotalSeconds < 40.0) { Thread.Sleep(500); object obj; try { obj = MainThreadDispatcher.Run(delegate { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsTeleporting()) { return (object)null; } if ((Object)(object)localPlayer != (Object)null && landLater && landings < 3 && TryLandingHeight(x, z, out var y2) && ((Component)localPlayer).transform.position.y - y2 > 2f) { if (_maxAirAltitude == null) { _maxAirAltitude = AccessTools.FieldRefAccess<Character, float>("m_maxAirAltitude"); } Vector3 position = default(Vector3); ((Vector3)(ref position))..ctor(x, y2, z); ((Component)localPlayer).transform.position = position; Rigidbody component = ((Component)localPlayer).GetComponent<Rigidbody>(); if ((Object)(object)component != (Object)null) { component.position = position; component.linearVelocity = Vector3.zero; } _maxAirAltitude.Invoke((Character)(object)localPlayer) = y2; landings++; return (object)null; } return Pos(localPlayer) ?? "no player"; }, 5000); } catch (TimeoutException) { continue; } catch (Exception ex4) { return new { success = false, error = "Teleported, but landing failed: " + ex4.GetBaseException().Message, position = (object)null }; } if (obj != null) { return new { success = true, position = obj, seconds = Math.Round(stopwatch.Elapsed.TotalSeconds, 1) }; } } return new { success = false, error = "Still teleporting after 40 s (area slow to load). Check world/status." }; } [Tool("world/clear_hostiles", Description = "(cheat) Remove creatures hostile to the player within a radius, so a test isn't interrupted by attacks. Leaves the player untouched (no god mode). Never removes players or tamed animals; bosses only with includeBosses. Returns what was removed.")] public object ClearHostiles([ToolParameter(Description = "Radius in metres around the player (default 50, max 200)")] float radius = 50f, [ToolParameter(Description = "Also remove bosses (default false)")] bool includeBosses = false) { return ClearNow(radius, includeBosses); } public static object KeepClearState() { lock (KeepClearGate) { return KeepClearOn ? new { on = true, radius = _keepClearRadius, everySeconds = _keepClearEvery, includeBosses = _keepClearBosses, removedSoFar = _keepClearRemoved } : new { on = false, radius = 0f, everySeconds = 0.0, includeBosses = false, removedSoFar = _keepClearRemoved }; } } [Tool("world/keep_clear", Description = "(cheat) Removes creatures hostile to the player every few seconds, between calls and test scripts, so one can't kill the character mid-test. Off by default. At the start of testing, offer it to the owner and turn it on only if they want it; leave it off when the test is about hostile creatures (spawning, combat, AI, raids) or measures performance. Same rules as world/clear_hostiles. Returns the state.")] public object KeepClear([ToolParameter(Description = "true = keep clearing, false = stop")] bool on, [ToolParameter(Description = "Radius in metres around the player (default 50, max 200)")] float radius = 50f, [ToolParameter(Description = "Seconds between clearings (default 5, 2 to 600)")] double every = 5.0, [ToolParameter(Description = "Also remove bosses (default false)")] bool includeBosses = false) { lock (KeepClearGate) { KeepClearChosen = true; Switch(on, radius, every, includeBosses); } return new { success = true, keepClear = KeepClearState() }; } private static void Switch(bool on, float radius, double every, bool includeBosses) { _keepClear?.Dispose(); _keepClear = null; KeepClearOn = on; if (on) { _keepClearRadius = Mathf.Clamp(radius, 1f, 200f); _keepClearEvery = Math.Max(2.0, Math.Min(600.0, every)); _keepClearBosses = includeBosses; TimeSpan period = TimeSpan.FromSeconds(_keepClearEvery); _keepClear = new Timer(delegate { KeepClearTick(); }, null, TimeSpan.Zero, period); } Log(on ? $"world/keep_clear: on, hostile creatures within {_keepClearRadius:F0} m are removed every {_keepClearEvery:F0} s" : "world/keep_clear: off"); } private static void KeepClearTick() { if (Interlocked.Exchange(ref _keepClearBusy, 1) == 1) { return; } try { KeepClearOnce(); } finally { Interlocked.Exchange(ref _keepClearBusy, 0); } } private static void KeepClearOnce() { float radius; bool bosses; lock (KeepClearGate) { if (!KeepClearOn) { return; } radius = _keepClearRadius; bosses = _keepClearBosses; } try { int num = MainThreadDispatcher.Run(() => (!((Object)(object)Player.m_localPlayer == (Object)null)) ? ClearOnMainThread(radius, bosses).total : 0, 5000); if (num > 0) { lock (KeepClearGate) { _keepClearRemoved += num; return; } } } catch (Exception) { } } private static object ClearNow(float radius, bool includeBosses) { return MainThreadDispatcher.RunTool(delegate { if ((Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return new { success = false, error = "Not in a world." }; } var (removed, kinds, bossesLeft) = ClearOnMainThread(radius, includeBosses); return new { success = true, removed = removed, kinds = kinds, bossesLeft = bossesLeft }; }); } private static (int total, Dictionary<string, int> kinds, int bossesLeft) ClearOnMainThread(float radius, bool includeBosses) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) Dictionary<string, int> dictionary = new Dictionary<string, int>(); int num = 0; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return (total: 0, kinds: dictionary, bossesLeft: 0); } List<Character> list = new List<Character>(); Character.GetCharactersInRange(((Component)localPlayer).transform.position, Mathf.Clamp(radius, 1f, 200f), list); foreach (Character item in list) { if (!((Object)(object)item == (Object)null) && !item.IsPlayer() && !item.IsTamed() && BaseAI.IsEnemy((Character)(object)localPlayer, item)) { if (item.IsBoss() && !includeBosses) { num++; continue; } string prefabName = Utils.GetPrefabName(((Component)item).gameObject); dictionary[prefabName] = ((!dictionary.TryGetValue(prefabName, out var value)) ? 1 : (value + 1)); ZNetScene.instance.Destroy(((Component)item).gameObject); } } int num2 = dictionary.Values.Sum(); if (num2 > 0) { Log(string.Format("world/clear_hostiles: removed {0} within {1:F0} m ({2})", num2, radius, string.Join(", ", dictionary.Select((KeyValuePair<string, int> kv) => $"{kv.Value} {kv.Key}")))); } return (total: num2, kinds: dictionary, bossesLeft: num); } [Tool("player/set_protection", Description = "Keep the player safe while a test runs: god = takes no damage, ghost = creatures ignore the player. Switched directly, not through the console, so the character is not marked as having used cheats. Leave an argument out to keep it as it is; returns the state before and after. Not saved: both are off again after a restart.")] public object SetProtection([ToolParameter(Description = "No damage (true/false; leave out to keep)")] string god = "", [ToolParameter(Description = "Creatures ignore the player (true/false; leave out to keep)")] string ghost = "") { bool? wantGod = Flag(god); bool? wantGhost = Flag(ghost); if ((god.Length > 0 && !wantGod.HasValue) || (ghost.Length > 0 && !wantGhost.HasValue)) { return new { success = false, error = "god and ghost take true or false." }; } return MainThreadDispatcher.RunTool(delegate { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return new { success = false, error = "Not in a world." }; } var before = new { god = ((Character)localPlayer).InGodMode(), ghost = ((Character)localPlayer).InGhostMode() }; if (wantGod.HasValue) { localPlayer.SetGodMode(wantGod.Value); } if (wantGhost.HasValue) { localPlayer.SetGhostMode(wantGhost.Value); } var anon = new { god = ((Character)localPlayer).InGodMode(), ghost = ((Character)localPlayer).InGhostMode() }; if (wantGod.HasValue || wantGhost.HasValue) { Log($"player/set_protection: god {anon.god}, ghost {anon.ghost}"); } return new { success = true, before = before, after = anon }; }); } private static bool? Flag(string text) { switch (text.Trim().ToLowerInvariant()) { case "on": case "1": case "yes": case "true": return true; case "no": case "0": case "off": case "false": return false; default: return null; } } [Tool("world/find_locations", Description = "Find generated places (trader, boss altars, dungeons, villages, ...) nearest the player. With no name: lists the place types in this world and how many of each. Names are prefab names, e.g. Vendor_BlackForest, Eikthyrnir, Crypt2, StartTemple; a part of the name matches.")] public object FindLocations([ToolParameter(Description = "Part of the place name (empty = list the types)")] string name = "", [ToolParameter(Description = "How many to return (default 5)")] int max = 5) { return MainThreadDispatcher.RunTool(delegate { //IL_00c9: 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_00ce: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZoneSystem.instance == (Object)null) { return new { success = false, error = "Not in a world." }; } List<LocationInstance> list = ZoneSystem.instance.GetLocationList().ToList(); if (string.IsNullOrWhiteSpace(name)) { var types = (from g in list.GroupBy(NameOf) orderby g.Key select new { name = g.Key, count = g.Count() }).ToList(); return new { success = true, total = list.Count, types = types }; } Vector3 from = (((Object)(object)Player.m_localPlayer != (Object)null) ? ((Component)Player.m_localPlayer).transform.position : Vector3.zero); var list2 = (from l in list where NameOf(l).IndexOf(name, StringComparison.OrdinalIgnoreCase) >= 0 select new { l = l, dist = Vector2.Distance(new Vector2(l.m_position.x, l.m_position.z), new Vector2(@from.x, @from.z)) } into m orderby m.dist select m).ToList(); return (list2.Count == 0) ? ((object)new { success = false, error = "No place matching '" + name + "'. Call with no name to list the types." }) : ((object)new { success = true, found = list2.Count, nearest = (from m in list2.Take(Math.Max(1, max)) select new { name = NameOf(m.l), position = Vec(m.l.m_position), distance = Math.Round(m.dist), biome = ((object)Unsafe.As<Biome, Biome>(ref m.l.m_location?.m_biome)/*cast due to .constrained prefix*/).ToString(), placed = m.l.m_placed }).ToList() }); }); static string NameOf(LocationInstance l) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) return l.m_location?.m_prefabName ?? l.m_location?.m_name ?? "?"; } } [Tool("world/spawn_creature", Description = "(cheat) Spawn creatures (prefab name, e.g. Greyling, Troll, Boar) in front of the player. Returns their instanceIds; remove them again with world/remove_objects.")] public object SpawnCreature([ToolParameter(Description = "Creature prefab name (znetscene/search_prefabs to look one up)")] string name, [ToolParameter(Description = "How many (default 1, max 20)")] int amount = 1, [ToolParameter(Description = "Star level: 1 = normal, 2 = one star, 3 = two stars")] int level = 1, [ToolParameter(Description = "Distance in front of the player in metres (default 5)")] float distance = 5f, [ToolParameter(Description = "Spawn tamed (default false)")] bool tamed = false) { return MainThreadDispatcher.RunTool(delegate { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: 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_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: 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: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || (Object)(object)ZNetScene.instance == (Object)null) { return new { success = false, error = "Not in a world." }; } GameObject prefab = ZNetScene.instance.GetPrefab(name); if ((Object)(object)prefab == (Object)null) { return new { success = false, error = "No prefab '" + name + "'. Use znetscene/search_prefabs." }; } if ((Object)(object)prefab.GetComponent<Character>() == (Object)null) { return new { success = false, error = "'" + name + "' is not a creature. For items use player/give_item." }; } amount = Mathf.Clamp(amount, 1, 20); Vector3 val = ((Component)localPlayer).transform.position + ((Component)localPlayer).transform.forward * distance; List<object> list = new List<object>(); float num = default(float); for (int i = 0; i < amount; i++) { Vector3 val2 = ((amount == 1) ? Vector3.zero : (Quaternion.Euler(0f, 360f * (float)i / (float)amount, 0f) * Vector3.forward * 1.5f)); Vector3 val3 = val + val2; if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetSolidHeight(val3, ref num, 1000)) { val3.y = num + 0.2f; } GameObject val4 = Object.Instantiate<GameObject>(prefab, val3, Quaternion.LookRotation(-((Component)localPlayer).transform.forward)); Character component = val4.GetComponent<Character>(); if (level > 1) { component.SetLevel(Mathf.Clamp(level, 1, 10)); } if (tamed) { component.SetTamed(true); } list.Add(new { instanceId = ((Object)val4).GetInstanceID(), position = Vec(val3) }); } Log(string.Format("world/spawn_creature: {0} x {1} (level {2}{3})", amount, name, level, tamed ? ", tamed" : "")); return new { success = true, name = name, level = level, spawned = list }; }); } [Tool("world/remove_objects", Description = "(cheat) Remove objects from the world by instanceId (creatures spawned for a test, dropped items, ...). Never removes the player.")] public object RemoveObjects([ToolParameter(Description = "instanceIds as a JSON array, e.g. [123, 456]")] string ids) { return MainThreadDispatcher.RunTool(delegate { List<int> list; try { list = JsonConvert.DeserializeObject<List<int>>(ids) ?? new List<int>(); } catch (Exception ex) { return new { success = false, error = "ids is not a JSON array of numbers: " + ex.Message }; } HashSet<int> hashSet = new HashSet<int>(list); int num = 0; ZNetView[] array = Object.FindObjectsByType<ZNetView>((FindObjectsSortMode)0); foreach (ZNetView val in array) { if (!((Object)(object)val == (Object)null) && hashSet.Contains(((Object)((Component)val).gameObject).GetInstanceID()) && !((Object)(object)((Component)val).GetComponent<Player>() != (Object)null)) { ZNetScene.instance.Destroy(((Component)val).gameObject); num++; } } Log($"world/remove_objects: removed {num} of {list.Count}"); return new { success = true, removed = num, notFound = list.Count - num }; }); } [Tool("env/status", Description = "Time of day (hour 0-24, day number), current weather, whether time or weather are pinned, and the weather names that can be set.")] public object EnvStatus() { return MainThreadDispatcher.RunTool(delegate { EnvMan instance = EnvMan.instance; if ((Object)(object)instance == (Object)null) { return new { success = false, error = "Not in a world." }; } EnvSetup currentEnvironment = instance.GetCurrentEnvironment(); return new { success = true, hour = Math.Round(instance.GetDayFraction() * 24f, 2), day = instance.GetDay(), night = EnvMan.IsNight(), weather = currentEnvironment?.m_name, timePinned = instance.m_debugTimeOfDay, weatherPinned = (string.IsNullOrEmpty(instance.m_debugEnv) ? null : instance.m_debugEnv), weathers = (from n in instance.m_environments.Select((EnvSetup e) => e.m_name).Distinct() orderby n select n).ToList() }; }); } [Tool("env/set_time", Description = "(cheat) Set the time of day. By default the clock jumps forward to that hour and keeps running (like sleeping). pin=true holds it there for repeatable tests (shown time only; pin=false with hour -1 releases it).")] public object SetTime([ToolParameter(Description = "Hour 0-24 (6 = sunrise, 12 = noon, 22 = night); -1 with pin=false just releases a pinned time")] float hour, [ToolParameter(Description = "Hold the time there (default false)")] bool pin = false) { return MainThreadDispatcher.RunTool(delegate { EnvMan instance = EnvMan.instance; if ((Object)(object)instance == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return new { success = false, error = "Not in a world." }; } if (hour < 0f) { instance.m_debugTimeOfDay = false; Log("env/set_time: time released"); return new { success = true, pinned = false, hour = Math.Round(instance.GetDayFraction() * 24f, 2) }; } float num = Mathf.Repeat(hour, 24f) / 24f; if (pin) { instance.m_debugTimeOfDay = true; instance.m_debugTime = num; } else { instance.m_debugTimeOfDay = false; double num2 = instance.m_dayLengthSec; double timeSeconds = ZNet.instance.GetTimeSeconds(); double num3 = (double)instance.GetDay(timeSeconds) * num2 + RawFraction(num) * num2; if (num3 <= timeSeconds) { num3 += num2; } ZNet.instance.SetNetTime(num3); } Log(string.Format("env/set_time: {0:F1}h{1}", hour, pin ? " (pinned)" : "")); return new { success = true, pinned = pin, hour = Math.Round(num * 24f, 2) }; }); } private static double RawFraction(float shown) { if (shown >= 0.25f && shown <= 0.75f) { return 0.15 + ((double)shown - 0.25) / 0.5 * 0.7; } if (shown < 0.25f) { return (double)shown / 0.25 * 0.15; } return 0.85 + ((double)shown - 0.75) / 0.25 * 0.15; } [Tool("env/set_weather", Description = "(cheat) Force a weather (name from env/status, e.g. Clear, Rain, ThunderStorm, Snow, Misty). Empty name goes back to normal weather.")] public object SetWeather([ToolParameter(Description = "Weather name; empty = normal weather again")] string name = "") { return MainThreadDispatcher.RunTool(delegate { EnvMan instance = EnvMan.instance; if ((Object)(object)instance == (Object)null) { return new { success = false, error = "Not in a world." }; } if (name.Length > 0) { EnvSetup val = ((IEnumerable<EnvSetup>)instance.m_environments).FirstOrDefault((Func<EnvSetup, bool>)((EnvSetup e) => string.Equals(e.m_name, name, StringComparison.OrdinalIgnoreCase))); if (val == null) { return new { success = false, error = "No weather '" + name + "'. Have: " + string.Join(", ", instance.m_environments.Select((EnvSetup e) => e.m_name).Distinct()) }; } name = val.m_name; } instance.m_debugEnv = name; Log("env/set_weather: " + ((name.Length == 0) ? "normal" : name)); return new { success = true, weather = ((name.Length == 0) ? "normal" : name) }; }); } [Tool("world/save", Description = "Save the world and characters now (like the 'save' console command). Returns once the save has been started; the game shows 'World saved' when done.")] public object Save() { return MainThreadDispatcher.RunTool(delegate { if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)Player.m_localPlayer == (Object)null) { return new { success = false, error = "Not in a world." }; } ZNet.instance.SaveWorldAndPlayerProfiles(); Log("world/save"); return new { success = true }; }); } [Tool("player/look_direction", Description = "Turn the player and camera to a compass heading and tilt, e.g. to aim a screenshot. Heading: 0 = north (+Z), 90 = east (+X). Tilt: 0 = level, positive = up, negative = down.")] public object LookDirection([ToolParameter(Description = "Heading in degrees (0 = north, 90 = east)")] float heading, [ToolParameter(Description = "Tilt in degrees (-80 to 80, default 0)")] float tilt = 0f) { return MainThreadDispatcher.RunTool(delegate { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return new { success = false, error = "Not in a world." }; } PlayerLook.Set(localPlayer, heading, tilt); return new { success = true, heading = heading, tilt = Mathf.Clamp(tilt, -80f, 80f) }; }); } private static object? Pos(Player? player) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null)) { return Vec(((Component)player).transform.position); } return null; } private static object Vec(Vector3 v) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) return new { x = Math.Round(v.x, 1), y = Math.Round(v.y, 1), z = Math.Round(v.z, 1) }; } } public class EditTools { [Tool("debug/set_value", Description = "Change a live field or property by path (same paths as debug/inspect), e.g. 'Player.m_localPlayer.m_maxCarryWeight' = 600. Lasts until the game or mod changes it back or the game restarts.")] public object SetValue([ToolParameter(Description = "Dotted path to the field or property")] string path, [ToolParameter(Description = "New value as text: number, true/false, enum name, 'x,y,z' for a Vector3, JSON for anything else")] string value) { return MainThreadDispatcher.RunTool(delegate { string error; Inspector.PathEnd pathEnd = Inspector.ResolvePath(path, out error); if (pathEnd == null) { return new { success = false, error = error }; } if (!Inspector.Write(pathEnd, value, out object before, out object after, out error)) { return new { success = false, error = error }; } ValAgentBridgePlugin.ModLogger.LogInfo((object)$"debug/set_value: {path}: {before} -> {after}"); return new { success = true, path = path, before = Inspector.Describe(before, 1), after = Inspector.Describe(after, 1) }; }); } [Tool("debug/call_method", Description = "Run a method on a type (static) or on the object at a path, e.g. target 'Player.m_localPlayer', method 'GetHealth', args '[]'. A Vector3 argument can be [x, y, z] or \"x,y,z\". Returns what it returned.")] public object CallMethod([ToolParameter(Description = "A type (for static methods) or a path to an object")] string target, [ToolParameter(Description = "Method name")] string method, [ToolParameter(Description = "Arguments as a JSON array, e.g. '[5, \"Wood\"]' (default none)")] string args = "[]", [ToolParameter(Description = "Levels of fields to show in the result (default 1)")] int depth = 1) { return MainThreadDispatcher.RunTool(delegate { JArray argList; try { argList = JArray.Parse(string.IsNullOrWhiteSpace(args) ? "[]" : args); } catch (Exception ex) { return new { success = false, error = "args is not a JSON array: " + ex.Message }; } string error; Inspector.PathEnd pathEnd = Inspector.ResolvePath(target, out error); if (pathEnd == null) { return new { success = false, error = error }; } object instance = null; Type type = pathEnd.OwnerType; if (!pathEnd.IsTypeOnly) { if (!Inspector.Read(pathEnd, out instance, out error)) { return new { success = false, error = error }; } if (instance != null) { object obj = instance; Object val = (Object)((obj is Object) ? obj : null); if (val == null || !(val == (Object)null)) { type = instance.GetType(); goto IL_00e6; } } return new { success = false, error = "'" + target + "' is null" }; } goto IL_00e6; IL_00e6: List<MethodInfo> list = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) where m.Name == method && m.GetParameters().Length == ((JContainer)argList).Count && !m.ContainsGenericParameters where instance != null || m.IsStatic select m).ToList(); if (list.Count == 0) { List<string> list2 = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy) where m.Name == method select m.Name + "(" + string.Join(", ", from p in m.GetParameters() select p.ParameterType.Name + " " + p.Name) + ")").Distinct().ToList(); return new { success = false, error = ((list2.Count == 0) ? (type.Name + " has no method '" + method + "'") : string.Format("No overload takes {0} argument(s). Have: {1}", ((JContainer)argList).Count, string.Join("; ", list2))) }; } foreach (MethodInfo item in list) { object[] parameters; try { parameters = item.GetParameters().Select((ParameterInfo p, int i) => ConvertArg(argList[i], p.ParameterType)).ToArray(); } catch { continue; } try { object value = item.Invoke(item.IsStatic ? null : instance, parameters); ValAgentBridgePlugin.ModLogger.LogInfo((object)("debug/call_method: " + type.Name + "." + item.Name + "(" + args + ")")); return new { success = true, method = type.Name + "." + item.Name, returned = ((item.ReturnType == typeof(void)) ? null : Inspector.Describe(value, Mathf.Clamp(depth, 0, 4))) }; } catch (Exception ex2) { Exception baseException = ex2.GetBaseException(); return new { success = false, error = type.Name + "." + item.Name + " threw " + baseException.GetType().Name + ": " + baseException.Message }; } } return new { success = false, error = "The arguments don't fit any overload's parameter types." }; }); } private static object? ConvertArg(JToken token, Type type) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 if ((int)token.Type == 10) { return null; } if ((int)token.Type == 8 && type != typeof(string)) { return Inspector.ConvertText(Extensions.Value<string>((IEnumerable<JToken>)token), type); } JArray val = (JArray)(object)((token is JArray) ? token : null); if (val != null && (type == typeof(Vector3) || type == typeof(Vector2))) { return Inspector.ConvertText(string.Join(",", ((IEnumerable<JToken>)val).Select((JToken n) => ((object)n).ToString())), type); } return token.ToObject(type); } } public class InputTools { private sealed class Simulated { public int down; public int up; } private static readonly Dictionary<KeyCode, Simulated> Keys = new Dictionary<KeyCode, Simulated>(); private static bool _patched; private static string? _patchProblem; [Tool("input/press_key", Description = "Press a key (with optional modifiers) for a moment, as if typed: F11, Escape, Tab, Alpha1 / 1, LeftShift, ... Reaches the game's controls and mod hotkeys set up through BepInEx (most mods); not mods that read UnityEngine.Input directly. Returns once the key is released.")] public object PressKey([ToolParameter(Description = "Key name (Unity KeyCode, e.g. F11, Escape, Return, A, Alpha1 or 1)")] string key, [ToolParameter(Description = "Modifiers held with it, comma separated: ctrl, shift, alt (or KeyCode names)")] string modifiers = "", [ToolParameter(Description = "Frames to hold the key (default 2)")] int holdFrames = 2) { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected I4, but got Unknown //IL_0088: Unknown result type (might be due to invalid IL or missing references) if (!TryKey(key, out var key2)) { return new { success = false, error = "Unknown key '" + key + "'. Use Unity KeyCode names, e.g. F11, Escape, Return, A, Alpha1." }; } List<KeyCode> mods = new List<KeyCode>(); string[] array = modifiers.Split(new char[3] { ',', '+', ' ' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { if (!TryKey(text, out var key3)) { return new { success = false, error = "Unknown modifier '" + text + "'. Use ctrl, shift, alt or KeyCode names." }; } mods.Add(key3); } List<KeyCode> all = mods.Concat((IEnumerable<KeyCode>)(object)new KeyCode[1] { (KeyCode)(int)key2 }).ToList(); holdFrames = Mathf.Clamp(holdFrames, 1, 600); int num; bool gameControls; try { (num, gameControls) = MainThreadDispatcher.Run(delegate { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: 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_0040: Unknown result type (might be due to invalid IL or missing references) EnsurePatched(); int num2 = Time.frameCount + 1; int num3 = num2 + holdFrames; foreach (KeyCode item in all) { Keys[item] = new Simulated { down = (mods.Contains(item) ? (num2 - 1) : num2), up = num3 }; } bool flag = Keyboard.current != null; if (flag) { ((MonoBehaviour)MainThreadDispatcher.Instance).StartCoroutine(PressInputSystem(all, num3)); } return (up: num3, kb: flag); }, 5000); } catch (Exception ex) { return new { success = false, error = ex.GetBaseException().Message }; } DateTime dateTime = DateTime.UtcNow.AddSeconds(30.0); while (DateTime.UtcNow < dateTime) { Thread.Sleep(20); try { if (MainThreadDispatcher.Run(() => Time.frameCount, 2000) > num) { break; } } catch (TimeoutException) { } } try { MainThreadDispatcher.Run(delegate { //IL_0010: Unknown result type (might be due
plugins/Lib.GAB.dll
Decompiled 7 hours ago#define TRACE using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Gabp.Runtime; using Lib.GAB.Attention; using Lib.GAB.Events; using Lib.GAB.Protocol; using Lib.GAB.Server; using Lib.GAB.Tools; using Lib.GAB.Transport; using Newtonsoft.Json; using Newtonsoft.Json.Linq; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("Lib.GAB.Tests")] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("GABP Contributors")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Host-side GABP server library for teams embedding tool, event, and attention surfaces into game mods, automation bridges, and product-specific runtimes.")] [assembly: AssemblyFileVersion("1.0.5.0")] [assembly: AssemblyInformationalVersion("1.0.5+6b3e5e82a10ea12f595ee1a22f9a8489b2f76510")] [assembly: AssemblyProduct("Lib.GAB")] [assembly: AssemblyTitle("Lib.GAB")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/pardeike/Lib.GAB")] [assembly: AssemblyVersion("1.0.5.0")] namespace Lib.GAB { public class GabpServerBuilder { private class GabsEnvironmentConfig { public int Port { get; set; } public string Token { get; set; } public string GameId { get; set; } } private readonly GabpServerConfig _config = new GabpServerConfig(); public GabpServerBuilder UsePort(int port) { _config.Port = port; return this; } public GabpServerBuilder UsePortIfNotSet(int port) { if (_config.Port == 0) { _config.Port = port; } return this; } public GabpServerBuilder UseToken(string token) { _config.Token = token; return this; } public GabpServerBuilder UseAgentId(string agentId) { _config.AgentId = agentId; return this; } public GabpServerBuilder UseAppInfo(string name, string version) { _config.AppInfo = new AppInfo { Name = name, Version = version }; return this; } public GabpServerBuilder EnableAttentionSupport() { _config.EnableAttentionSupport = true; return this; } public GabpServerBuilder UseExternalConfig(int port, string token) { _config.Port = port; _config.Token = token; return this; } public GabpServerBuilder UseExternalConfig(int port, string token, string gameId) { _config.Port = port; _config.Token = token; _config.AgentId = gameId; return this; } public GabpServerBuilder UseGabsEnvironmentIfAvailable() { GabsEnvironmentConfig gabsEnvironmentConfig = TryReadGabsEnvironment(); if (gabsEnvironmentConfig != null) { _config.Port = gabsEnvironmentConfig.Port; _config.Token = gabsEnvironmentConfig.Token; if (!string.IsNullOrEmpty(gabsEnvironmentConfig.GameId)) { _config.AgentId = gabsEnvironmentConfig.GameId; } } return this; } private static GabsEnvironmentConfig TryReadGabsEnvironment() { string environmentVariable = Environment.GetEnvironmentVariable("GABS_GAME_ID"); string environmentVariable2 = Environment.GetEnvironmentVariable("GABP_SERVER_PORT"); string environmentVariable3 = Environment.GetEnvironmentVariable("GABP_TOKEN"); if (!string.IsNullOrEmpty(environmentVariable2) && !string.IsNullOrEmpty(environmentVariable3) && int.TryParse(environmentVariable2, out var result) && result > 0 && result <= 65535) { return new GabsEnvironmentConfig { Port = result, Token = environmentVariable3, GameId = environmentVariable }; } return null; } internal static bool HasGabsConfiguration() { return TryReadGabsEnvironment() != null; } public GabpServer Build() { return new GabpServer(_config); } } public static class Gabp { public static GabpServerBuilder CreateServer() { return new GabpServerBuilder(); } public static GabpServer CreateSimpleServer(string appName, string appVersion, int port = 0) { return CreateServer().UseAppInfo(appName, appVersion).UsePort(port).Build(); } public static GabpServer CreateServerWithAssembly(string appName, string AppVersion, Assembly assembly, int port = 0) { GabpServer gabpServer = CreateSimpleServer(appName, AppVersion, port); gabpServer.Tools.RegisterToolsFromAssembly(assembly); return gabpServer; } public static GabpServer CreateServerWithInstance(string appName, string appVersion, object instance, int port = 0) { GabpServer gabpServer = CreateSimpleServer(appName, appVersion, port); gabpServer.Tools.RegisterToolsFromInstance(instance); return gabpServer; } public static GabpServer CreateServerWithInstanceAndExternalConfig(string appName, string appVersion, object instance, int port, string token) { GabpServer gabpServer = CreateServerWithExternalConfig(appName, appVersion, port, token); gabpServer.Tools.RegisterToolsFromInstance(instance); return gabpServer; } public static GabpServer CreateServerWithInstanceAndExternalConfig(string appName, string appVersion, object instance, int port, string token, string gameId) { GabpServer gabpServer = CreateServerWithExternalConfig(appName, appVersion, port, token, gameId); gabpServer.Tools.RegisterToolsFromInstance(instance); return gabpServer; } public static GabpServer CreateServerWithExternalConfig(string appName, string appVersion, int port, string token) { return CreateServer().UseAppInfo(appName, appVersion).UseExternalConfig(port, token).Build(); } public static GabpServer CreateServerWithExternalConfig(string appName, string appVersion, int port, string token, string gameId) { return CreateServer().UseAppInfo(appName, appVersion).UseExternalConfig(port, token, gameId).Build(); } public static GabpServer CreateGabsAwareServer(string appName, string appVersion, int fallbackPort = 0) { return CreateServer().UseAppInfo(appName, appVersion).UseGabsEnvironmentIfAvailable().UsePortIfNotSet(fallbackPort) .Build(); } public static GabpServer CreateGabsAwareServerWithInstance(string appName, string appVersion, object instance, int fallbackPort = 0) { GabpServer gabpServer = CreateGabsAwareServer(appName, appVersion, fallbackPort); gabpServer.Tools.RegisterToolsFromInstance(instance); return gabpServer; } public static bool IsRunningUnderGabs() { return GabpServerBuilder.HasGabsConfiguration(); } } } namespace Lib.GAB.Transport { public interface IConnection : IDisposable { string Id { get; } bool IsConnected { get; } event EventHandler Disconnected; Task SendMessageAsync(GabpMessage message, CancellationToken cancellationToken = default(CancellationToken)); } public interface ITransport : IDisposable { event EventHandler<ConnectionEstablishedEventArgs> ConnectionEstablished; event EventHandler<MessageReceivedEventArgs> MessageReceived; Task StartAsync(CancellationToken cancellationToken = default(CancellationToken)); Task StopAsync(CancellationToken cancellationToken = default(CancellationToken)); } public class ConnectionEstablishedEventArgs : EventArgs { public IConnection Connection { get; } public ConnectionEstablishedEventArgs(IConnection connection) { Connection = connection; } } public class MessageReceivedEventArgs : EventArgs { public IConnection Connection { get; } public GabpMessage Message { get; } public MessageReceivedEventArgs(IConnection connection, GabpMessage message) { Connection = connection; Message = message; } } public class TcpConnection : IConnection, IDisposable { internal readonly TcpClient _client; private readonly Stream _stream; private readonly string _id; private readonly CancellationTokenSource _cancellationTokenSource; private readonly SemaphoreSlim _sendLock; private bool _disposed; public string Id => _id; public bool IsConnected { get { if (_client.Connected) { return !_disposed; } return false; } } public event EventHandler Disconnected; public TcpConnection(TcpClient client) : this(client, client?.GetStream() ?? throw new ArgumentNullException("client")) { } internal TcpConnection(TcpClient client, Stream stream) { _client = client ?? throw new ArgumentNullException("client"); _stream = stream ?? throw new ArgumentNullException("stream"); _id = Guid.NewGuid().ToString(); _cancellationTokenSource = new CancellationTokenSource(); _sendLock = new SemaphoreSlim(1, 1); } public async Task SendMessageAsync(GabpMessage message, CancellationToken cancellationToken = default(CancellationToken)) { if (_disposed || !IsConnected) { throw new InvalidOperationException("Connection is not active"); } string s = JsonConvert.SerializeObject((object)message); byte[] bytes = Encoding.UTF8.GetBytes(s); string s2 = $"Content-Length: {bytes.Length}\r\nContent-Type: application/json\r\n\r\n"; byte[] bytes2 = Encoding.UTF8.GetBytes(s2); byte[] frameBytes = new byte[bytes2.Length + bytes.Length]; Buffer.BlockCopy(bytes2, 0, frameBytes, 0, bytes2.Length); Buffer.BlockCopy(bytes, 0, frameBytes, bytes2.Length, bytes.Length); using CancellationTokenSource linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _cancellationTokenSource.Token); CancellationToken combinedToken = linkedTokenSource.Token; await _sendLock.WaitAsync(combinedToken).ConfigureAwait(continueOnCapturedContext: false); try { if (_disposed || !IsConnected) { throw new InvalidOperationException("Connection is not active"); } await _stream.WriteAsync(frameBytes, 0, frameBytes.Length, combinedToken).ConfigureAwait(continueOnCapturedContext: false); await _stream.FlushAsync(combinedToken).ConfigureAwait(continueOnCapturedContext: false); } finally { _sendLock.Release(); } } public void Dispose() { if (!_disposed) { _disposed = true; _cancellationTokenSource.Cancel(); try { this.Disconnected?.Invoke(this, EventArgs.Empty); } catch { } _stream?.Dispose(); _client?.Close(); _cancellationTokenSource?.Dispose(); } } } public class TcpTransport : ITransport, IDisposable { private readonly int _port; private TcpListener _listener; private bool _disposed; private bool _running; public int Port { get; private set; } public event EventHandler<ConnectionEstablishedEventArgs> ConnectionEstablished; public event EventHandler<MessageReceivedEventArgs> MessageReceived; public TcpTransport(int port = 0) { _port = port; } public async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (_running) { throw new InvalidOperationException("Transport is already running"); } _listener = new TcpListener(IPAddress.Loopback, _port); _listener.Start(); Port = ((IPEndPoint)_listener.LocalEndpoint).Port; _running = true; Task.Run(async delegate { await AcceptConnectionsAsync(cancellationToken); }, cancellationToken); } public Task StopAsync(CancellationToken cancellationToken = default(CancellationToken)) { if (!_running) { return Task.FromResult(0); } _running = false; _listener?.Stop(); return Task.FromResult(0); } private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) { while (_running && !cancellationToken.IsCancellationRequested) { try { TcpConnection connection = new TcpConnection(await _listener.AcceptTcpClientAsync()); this.ConnectionEstablished?.Invoke(this, new ConnectionEstablishedEventArgs(connection)); Task.Run(async delegate { await ReadMessagesAsync(connection, cancellationToken); }, cancellationToken); } catch (ObjectDisposedException) { break; } catch (Exception) { if (_running) { await Task.Delay(1000, cancellationToken); } } } } private async Task ReadMessagesAsync(TcpConnection connection, CancellationToken cancellationToken) { NetworkStream stream = connection._client.GetStream(); byte[] buffer = new byte[8192]; StringBuilder messageBuffer = new StringBuilder(); try { while (connection.IsConnected && !cancellationToken.IsCancellationRequested) { int num = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken); if (num != 0) { string value = Encoding.UTF8.GetString(buffer, 0, num); messageBuffer.Append(value); await ProcessMessagesAsync(connection, messageBuffer, cancellationToken); continue; } break; } } catch (Exception) { } finally { connection.Dispose(); } } private Task ProcessMessagesAsync(TcpConnection connection, StringBuilder buffer, CancellationToken cancellationToken) { while (true) { string text = buffer.ToString(); int num = text.IndexOf("\r\n\r\n", StringComparison.Ordinal); if (num == -1) { break; } string text2 = text.Substring(0, num); int num2 = text2.IndexOf("Content-Length:", StringComparison.OrdinalIgnoreCase); if (num2 == -1) { break; } int num3 = num2 + "Content-Length:".Length; int num4 = text2.IndexOf('\r', num3); if (num4 == -1) { num4 = text2.IndexOf('\n', num3); } if (num4 == -1) { num4 = text2.Length; } if (!int.TryParse(text2.Substring(num3, num4 - num3).Trim(), out var result)) { break; } int num5 = num + 4; if (text.Length < num5 + result) { break; } string json = text.Substring(num5, result); try { GabpMessage gabpMessage = ParseMessage(json); if (gabpMessage != null) { this.MessageReceived?.Invoke(this, new MessageReceivedEventArgs(connection, gabpMessage)); } } catch (Exception) { } buffer.Remove(0, num5 + result); } return Task.FromResult(0); } private static GabpMessage ParseMessage(string json) { Dictionary<string, object> dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(json); if (dictionary == null || !dictionary.ContainsKey("type")) { return null; } return dictionary["type"].ToString() switch { "request" => JsonConvert.DeserializeObject<GabpRequest>(json), "response" => JsonConvert.DeserializeObject<GabpResponse>(json), "event" => JsonConvert.DeserializeObject<GabpEvent>(json), _ => null, }; } public void Dispose() { if (!_disposed) { _disposed = true; if (_running) { _running = false; _listener?.Stop(); } } } } } namespace Lib.GAB.Tools { public class ToolInfo { public string Name { get; set; } = string.Empty; public string Description { get; set; } public string Title { get; set; } public string ResultDescription { get; set; } public List<string> Tags { get; set; } = new List<string>(); public List<ToolParameterInfo> Parameters { get; set; } = new List<ToolParameterInfo>(); public List<ToolResponseFieldInfo> ResponseFields { get; set; } = new List<ToolResponseFieldInfo>(); public bool RequiresAuth { get; set; } = true; } public class ToolResponseFieldInfo { public string Name { get; set; } = string.Empty; public string Type { get; set; } = "string"; public string Description { get; set; } public bool Always { get; set; } = true; public bool Nullable { get; set; } } public class ToolParameterInfo { public string Name { get; set; } = string.Empty; public Type Type { get; set; } = typeof(object); public string Description { get; set; } public bool Required { get; set; } = true; public object DefaultValue { get; set; } } public interface IToolRegistry { void RegisterTool(string name, Func<object, Task<object>> handler, ToolInfo info = null); void RegisterToolsFromAssembly(Assembly assembly); void RegisterToolsFromInstance(object instance); bool UnregisterTool(string name); IList<ToolInfo> GetTools(); bool HasTool(string name); Task<object> CallToolAsync(string name, object parameters = null); } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class ToolAttribute : Attribute { public string Name { get; } public string Description { get; set; } public string Title { get; set; } public string ResultDescription { get; set; } public string[] Tags { get; set; } = Array.Empty<string>(); public bool RequiresAuth { get; set; } = true; public ToolAttribute(string name) { ToolNameValidator.EnsureValid(name); Name = name; } } [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] public class ToolParameterAttribute : Attribute { public string Description { get; set; } public bool Required { get; set; } = true; public object DefaultValue { get; set; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class ToolResponseAttribute : Attribute { public string Name { get; } public string Type { get; set; } = "string"; public string Description { get; set; } public bool Always { get; set; } = true; public bool Nullable { get; set; } public ToolResponseAttribute(string name) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Response field name cannot be null or empty", "name"); } Name = name; } } public static class ToolNameValidator { public const string CanonicalPattern = "^[a-z][a-z0-9_-]*(/[a-z][a-z0-9_-]*)+$"; private static readonly Regex CanonicalToolNameRegex = new Regex("^[a-z][a-z0-9_-]*(/[a-z][a-z0-9_-]*)+$", RegexOptions.CultureInvariant); public static bool IsValid(string name) { if (!string.IsNullOrWhiteSpace(name)) { return CanonicalToolNameRegex.IsMatch(name); } return false; } public static void EnsureValid(string name, string parameterName = "name") { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("Tool name cannot be null or empty", parameterName); } if (!IsValid(name)) { throw new ArgumentException("Tool name '" + name + "' must be a canonical GABP tool name matching ^[a-z][a-z0-9_-]*(/[a-z][a-z0-9_-]*)+$ (for example 'inventory/get'). Dotted MCP adapter names such as 'inventory.get' are not valid GABP tool names.", parameterName); } } } internal sealed class ToolParameterBindingException : Exception { public ToolParameterBindingException(string message, Exception innerException) : base(message, innerException) { } } public class ToolRegistry : IToolRegistry { private class RegisteredTool { public ToolInfo Info { get; set; } = new ToolInfo(); public Func<object, Task<object>> Handler { get; set; } = (object _) => Task.FromResult<object>(null); } private readonly ConcurrentDictionary<string, RegisteredTool> _tools = new ConcurrentDictionary<string, RegisteredTool>(); public void RegisterTool(string name, Func<object, Task<object>> handler, ToolInfo info = null) { ToolNameValidator.EnsureValid(name); if (handler == null) { throw new ArgumentNullException("handler"); } ToolInfo toolInfo = info ?? new ToolInfo { Name = name }; toolInfo.Name = name; _tools[name] = new RegisteredTool { Info = toolInfo, Handler = handler }; } public void RegisterToolsFromAssembly(Assembly assembly) { if (assembly == null) { throw new ArgumentNullException("assembly"); } Type[] types = assembly.GetTypes(); foreach (Type type in types) { RegisterToolsFromType(type, null); } } public void RegisterToolsFromInstance(object instance) { if (instance == null) { throw new ArgumentNullException("instance"); } RegisterToolsFromType(instance.GetType(), instance); } private void RegisterToolsFromType(Type type, object instance) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); foreach (MethodInfo methodInfo in methods) { ToolAttribute customAttribute = methodInfo.GetCustomAttribute<ToolAttribute>(); if (customAttribute != null && (instance != null || methodInfo.IsStatic) && (instance == null || !methodInfo.IsStatic)) { ToolInfo info = new ToolInfo { Name = customAttribute.Name, Title = customAttribute.Title, Description = customAttribute.Description, ResultDescription = customAttribute.ResultDescription, Tags = NormalizeTags(customAttribute.Tags), RequiresAuth = customAttribute.RequiresAuth, Parameters = GetParameterInfo(methodInfo), ResponseFields = GetResponseFieldInfo(methodInfo) }; RegisterTool(customAttribute.Name, CreateHandler(methodInfo, instance, customAttribute.Name), info); } } } private static List<string> NormalizeTags(IEnumerable<string> tags) { if (tags == null) { return new List<string>(); } return (from tag in tags where !string.IsNullOrWhiteSpace(tag) select tag.Trim()).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); } private List<ToolParameterInfo> GetParameterInfo(MethodInfo method) { List<ToolParameterInfo> list = new List<ToolParameterInfo>(); ParameterInfo[] parameters = method.GetParameters(); foreach (ParameterInfo parameterInfo in parameters) { ToolParameterAttribute customAttribute = parameterInfo.GetCustomAttribute<ToolParameterAttribute>(); object defaultValue = GetDefaultValue(parameterInfo, customAttribute); list.Add(new ToolParameterInfo { Name = parameterInfo.Name, Type = parameterInfo.ParameterType, Description = customAttribute?.Description, Required = IsParameterRequired(parameterInfo, customAttribute), DefaultValue = defaultValue }); } return list; } private static bool IsParameterRequired(ParameterInfo parameter, ToolParameterAttribute parameterAttribute) { if (parameter.HasDefaultValue) { return false; } if (parameterAttribute != null && parameterAttribute.DefaultValue != null) { return false; } return parameterAttribute?.Required ?? true; } private static object GetDefaultValue(ParameterInfo parameter, ToolParameterAttribute parameterAttribute) { if (parameter.HasDefaultValue) { return parameter.DefaultValue; } return parameterAttribute?.DefaultValue; } private List<ToolResponseFieldInfo> GetResponseFieldInfo(MethodInfo method) { List<ToolResponseFieldInfo> list = new List<ToolResponseFieldInfo>(); foreach (ToolResponseAttribute customAttribute in method.GetCustomAttributes<ToolResponseAttribute>()) { list.Add(new ToolResponseFieldInfo { Name = customAttribute.Name, Type = customAttribute.Type, Description = customAttribute.Description, Always = customAttribute.Always, Nullable = customAttribute.Nullable }); } return list; } private Func<object, Task<object>> CreateHandler(MethodInfo method, object instance, string toolName) { return async delegate(object parameters) { try { object[] parameters2 = ConvertParameters(method, parameters, toolName); object obj = method.Invoke(instance, parameters2); if (obj is Task task) { await task; if (task.GetType().IsGenericType) { return task.GetType().GetProperty("Result")?.GetValue(task); } return (object)null; } return obj; } catch (ToolParameterBindingException) { throw; } catch (Exception ex2) { throw new InvalidOperationException("Error calling tool '" + method.Name + "': " + ex2.Message, ex2); } }; } private object[] ConvertParameters(MethodInfo method, object parameters, string toolName) { ParameterInfo[] parameters2 = method.GetParameters(); object[] array = new object[parameters2.Length]; if (parameters == null) { for (int i = 0; i < parameters2.Length; i++) { array[i] = GetDefaultValue(parameters2[i], parameters2[i].GetCustomAttribute<ToolParameterAttribute>()); } return array; } Dictionary<string, object> dictionary = new Dictionary<string, object>(); dictionary = ((!(parameters is string text)) ? (JsonConvert.DeserializeObject<Dictionary<string, object>>(JsonConvert.SerializeObject(parameters)) ?? new Dictionary<string, object>()) : (JsonConvert.DeserializeObject<Dictionary<string, object>>(text) ?? new Dictionary<string, object>())); string[] array2 = parameters2.Select((ParameterInfo p) => p.Name).ToArray(); HashSet<string> hashSet = new HashSet<string>(array2, StringComparer.Ordinal); foreach (string key in dictionary.Keys) { if (!hashSet.Contains(key) && array2.FirstOrDefault((string n) => n.Equals(key, StringComparison.OrdinalIgnoreCase)) == null) { Trace.TraceWarning("[ToolRegistry] Tool '" + method.Name + "': unrecognized parameter '" + key + "'. Known parameters: [" + string.Join(", ", array2) + "]"); } } for (int num = 0; num < parameters2.Length; num++) { ParameterInfo param = parameters2[num]; if (dictionary.ContainsKey(param.Name)) { try { object value = dictionary[param.Name]; array[num] = ConvertValue(value, param.ParameterType); } catch (Exception innerException) { throw CreateParameterBindingException(toolName, param, innerException); } continue; } string text2 = dictionary.Keys.FirstOrDefault((string k) => k.Equals(param.Name, StringComparison.OrdinalIgnoreCase)); if (text2 != null) { Trace.TraceWarning("[ToolRegistry] Tool '" + method.Name + "': parameter '" + text2 + "' matched '" + param.Name + "' via case-insensitive fallback."); try { object value2 = dictionary[text2]; array[num] = ConvertValue(value2, param.ParameterType); } catch (Exception innerException2) { throw CreateParameterBindingException(toolName, param, innerException2); } } else { array[num] = GetDefaultValue(param, param.GetCustomAttribute<ToolParameterAttribute>()); } } return array; } private static ToolParameterBindingException CreateParameterBindingException(string toolName, ParameterInfo parameter, Exception innerException) { return new ToolParameterBindingException("Invalid value for parameter '" + parameter.Name + "' in tool '" + toolName + "'; expected " + parameter.ParameterType.Name + ".", innerException); } private object ConvertValue(object value, Type targetType) { if (value == null) { return null; } if (targetType.IsAssignableFrom(value.GetType())) { return value; } try { return Convert.ChangeType(value, targetType); } catch { return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(value), targetType); } } public bool UnregisterTool(string name) { RegisteredTool value; return _tools.TryRemove(name, out value); } public IList<ToolInfo> GetTools() { return _tools.Values.Select((RegisteredTool t) => t.Info).ToList(); } public bool HasTool(string name) { return _tools.ContainsKey(name); } public async Task<object> CallToolAsync(string name, object parameters = null) { if (!_tools.TryGetValue(name, out var value)) { throw new ArgumentException("Tool '" + name + "' not found", "name"); } return await value.Handler(parameters); } } } namespace Lib.GAB.Server { public class GabpServerConfig { public int Port { get; set; } public string Token { get; set; } = Guid.NewGuid().ToString("N"); public string AgentId { get; set; } = "gabp-server"; public AppInfo AppInfo { get; set; } = new AppInfo { Name = "GABP Server", Version = "1.0.0" }; public bool EnableAttentionSupport { get; set; } } public class GabpServer : IDisposable { private class SessionInfo { public IConnection Connection { get; set; } public bool IsAuthenticated { get; set; } public string BridgeVersion { get; set; } public string Platform { get; set; } public string LaunchId { get; set; } } private readonly GabpServerConfig _config; private readonly ITransport _transport; private readonly IToolRegistry _toolRegistry; private readonly IEventManager _eventManager; private readonly IAttentionManager _attentionManager; private readonly ConcurrentDictionary<string, SessionInfo> _sessions = new ConcurrentDictionary<string, SessionInfo>(); private bool _disposed; private static readonly IReadOnlyList<string> BaseProtocolMethods = new string[5] { "session/hello", "tools/list", "tools/call", "events/subscribe", "events/unsubscribe" }; public int Port { get { if (!(_transport is TcpTransport tcpTransport)) { return 0; } return tcpTransport.Port; } } public string Token => _config.Token; public IToolRegistry Tools => _toolRegistry; public IEventManager Events => _eventManager; public IAttentionManager Attention => _attentionManager; public GabpServer(GabpServerConfig config = null) { _config = config ?? new GabpServerConfig(); _transport = new TcpTransport(_config.Port); _toolRegistry = new ToolRegistry(); _eventManager = new EventManager(); _attentionManager = new AttentionManager(_eventManager, _config.EnableAttentionSupport); SetupTransportEvents(); RegisterCoreTools(); RegisterCoreEventChannels(); } public async Task StartAsync(CancellationToken cancellationToken = default(CancellationToken)) { await _transport.StartAsync(cancellationToken); } public async Task StopAsync(CancellationToken cancellationToken = default(CancellationToken)) { await _transport.StopAsync(cancellationToken); DisposeActiveSessions(); } private void SetupTransportEvents() { _transport.ConnectionEstablished += OnConnectionEstablished; _transport.MessageReceived += OnMessageReceived; } private void OnConnectionEstablished(object sender, ConnectionEstablishedEventArgs e) { _sessions[e.Connection.Id] = new SessionInfo { Connection = e.Connection, IsAuthenticated = false }; e.Connection.Disconnected += delegate { _sessions.TryRemove(e.Connection.Id, out var _); }; } private async void OnMessageReceived(object sender, MessageReceivedEventArgs e) { try { await HandleMessageAsync(e.Connection, e.Message); } catch (Exception ex) { await SendErrorResponseAsync(e.Connection, e.Message.Id, -32603, "Internal server error: " + ex.Message); } } private async Task HandleMessageAsync(IConnection connection, GabpMessage message) { if (!_sessions.TryGetValue(connection.Id, out var value)) { return; } if (!(message is GabpRequest request)) { if (!(message is GabpResponse) && message is GabpEvent) { } } else { await HandleRequestAsync(connection, value, request); } } private async Task HandleRequestAsync(IConnection connection, SessionInfo session, GabpRequest request) { if (request.Method == "session/hello") { await HandleSessionHelloAsync(connection, session, request); return; } if (!session.IsAuthenticated) { await SendErrorResponseAsync(connection, request.Id, -31001, "Session not established. Send session/hello first."); return; } switch (request.Method) { case "tools/list": await HandleToolsListAsync(connection, request); return; case "tools/call": await HandleToolsCallAsync(connection, request); return; case "events/subscribe": await HandleEventsSubscribeAsync(connection, request); return; case "events/unsubscribe": await HandleEventsUnsubscribeAsync(connection, request); return; case "attention/current": if (_attentionManager.IsEnabled) { await HandleAttentionCurrentAsync(connection, request); return; } break; case "attention/ack": if (_attentionManager.IsEnabled) { await HandleAttentionAckAsync(connection, request); return; } break; } await SendErrorResponseAsync(connection, request.Id, -32601, "Method '" + request.Method + "' not found"); } private async Task HandleSessionHelloAsync(IConnection connection, SessionInfo session, GabpRequest request) { try { SessionHelloParams val = GabpJson.Deserialize<SessionHelloParams>(JsonConvert.SerializeObject(request.Params)); if (val == null || val.Token != _config.Token) { await SendErrorResponseAsync(connection, request.Id, -31000, "Invalid authentication token"); return; } session.IsAuthenticated = true; session.BridgeVersion = val.BridgeVersion; session.Platform = val.Platform; session.LaunchId = val.LaunchId; SessionWelcomeResult result = new SessionWelcomeResult { AgentId = _config.AgentId, App = new GabpAppInfo { Name = _config.AppInfo.Name, Version = _config.AppInfo.Version }, Capabilities = new GabpCapabilities { Methods = GetSupportedProtocolMethods(), Events = _eventManager.GetAvailableChannels(), Resources = new List<string>() }, SchemaVersion = "1.0" }; await SendResponseAsync(connection, request.Id, result); } catch (Exception ex) { await SendErrorResponseAsync(connection, request.Id, -32602, "Invalid session/hello parameters: " + ex.Message); } } private async Task HandleToolsListAsync(IConnection connection, GabpRequest request) { List<Dictionary<string, object>> tools = _toolRegistry.GetTools().Select(BuildToolDescriptor).ToList(); await SendResponseAsync(connection, request.Id, new { tools }); } private static Dictionary<string, object> BuildToolDescriptor(ToolInfo tool) { Dictionary<string, object> dictionary = new Dictionary<string, object> { ["name"] = tool.Name, ["title"] = (string.IsNullOrWhiteSpace(tool.Title) ? BuildToolTitle(tool.Name) : tool.Title), ["description"] = tool.Description, ["inputSchema"] = BuildInputSchema(tool.Parameters), ["parameters"] = tool.Parameters.Select((ToolParameterInfo parameter) => new { name = parameter.Name, type = parameter.Type.Name, description = parameter.Description, required = parameter.Required, defaultValue = parameter.DefaultValue }).ToList(), ["outputSchema"] = BuildOutputSchema(tool.ResultDescription, tool.ResponseFields), ["requiresAuth"] = tool.RequiresAuth }; if (tool.Tags != null && tool.Tags.Count > 0) { dictionary["tags"] = tool.Tags; } return dictionary; } private static Dictionary<string, object> BuildInputSchema(List<ToolParameterInfo> parameters) { Dictionary<string, object> dictionary = new Dictionary<string, object>(); List<string> list = new List<string>(); foreach (ToolParameterInfo parameter in parameters) { Dictionary<string, object> dictionary2 = BuildParameterSchema(parameter.Type); if (!string.IsNullOrWhiteSpace(parameter.Description)) { dictionary2["description"] = parameter.Description; } if (parameter.DefaultValue != null) { dictionary2["default"] = parameter.DefaultValue; } dictionary[parameter.Name] = dictionary2; if (parameter.Required) { list.Add(parameter.Name); } } Dictionary<string, object> dictionary3 = new Dictionary<string, object> { ["type"] = "object", ["properties"] = dictionary, ["additionalProperties"] = false }; if (list.Count > 0) { dictionary3["required"] = list; } return dictionary3; } private static Dictionary<string, object> BuildParameterSchema(Type type) { Dictionary<string, object> dictionary = new Dictionary<string, object> { ["type"] = MapTypeToJsonSchemaType(type) }; if (TryGetDictionaryValueType(type, out var valueType)) { dictionary["additionalProperties"] = ((valueType == typeof(object)) ? new Dictionary<string, object>() : new Dictionary<string, object> { ["type"] = MapTypeToJsonSchemaType(valueType) }); } return dictionary; } private static Dictionary<string, object> BuildOutputSchema(string resultDescription, List<ToolResponseFieldInfo> fields) { if (fields.Count == 0 && string.IsNullOrWhiteSpace(resultDescription)) { return new Dictionary<string, object>(); } Dictionary<string, object> dictionary = new Dictionary<string, object>(); if (!string.IsNullOrWhiteSpace(resultDescription)) { dictionary["description"] = resultDescription; } if (fields.Count == 0) { return dictionary; } Dictionary<string, object> dictionary2 = new Dictionary<string, object>(); List<string> list = new List<string>(); foreach (ToolResponseFieldInfo field in fields) { Dictionary<string, object> dictionary3 = new Dictionary<string, object> { ["type"] = field.Type }; if (field.Nullable) { dictionary3["nullable"] = true; } if (!string.IsNullOrEmpty(field.Description)) { dictionary3["description"] = field.Description; } dictionary2[field.Name] = dictionary3; if (field.Always) { list.Add(field.Name); } } dictionary["type"] = "object"; dictionary["properties"] = dictionary2; if (list.Count > 0) { dictionary["required"] = list; } return dictionary; } private static string BuildToolTitle(string toolName) { List<string> list = (from part in toolName.Split(new char[3] { '/', '_', '-' }, StringSplitOptions.RemoveEmptyEntries) select CultureInfo.InvariantCulture.TextInfo.ToTitleCase(part)).ToList(); if (list.Count != 0) { return string.Join(" ", list); } return toolName; } private static string MapTypeToJsonSchemaType(Type type) { Type type2 = Nullable.GetUnderlyingType(type) ?? type; if (type2 == typeof(string) || type2 == typeof(char) || type2 == typeof(Guid)) { return "string"; } if (type2 == typeof(bool)) { return "boolean"; } if (type2 == typeof(byte) || type2 == typeof(sbyte) || type2 == typeof(short) || type2 == typeof(ushort) || type2 == typeof(int) || type2 == typeof(uint) || type2 == typeof(long) || type2 == typeof(ulong)) { return "integer"; } if (type2 == typeof(float) || type2 == typeof(double) || type2 == typeof(decimal)) { return "number"; } if (TryGetDictionaryValueType(type2, out var _)) { return "object"; } if (type2 != typeof(string) && typeof(IEnumerable).IsAssignableFrom(type2)) { return "array"; } return "object"; } private static bool TryGetDictionaryValueType(Type type, out Type valueType) { Type type2 = Nullable.GetUnderlyingType(type) ?? type; Type type3 = new Type[1] { type2 }.Concat(type2.GetInterfaces()).FirstOrDefault((Type candidate) => candidate.IsGenericType && (candidate.GetGenericTypeDefinition() == typeof(IDictionary<, >) || candidate.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<, >))); if (type3 != null) { valueType = type3.GetGenericArguments()[1]; return true; } if (typeof(IDictionary).IsAssignableFrom(type2)) { valueType = typeof(object); return true; } valueType = null; return false; } private async Task HandleToolsCallAsync(IConnection connection, GabpRequest request) { try { string text = JsonConvert.SerializeObject(request.Params); ToolsCallParams val = GabpJson.Deserialize<ToolsCallParams>(text); if (val == null || string.IsNullOrWhiteSpace(val.Name)) { await SendErrorResponseAsync(connection, request.Id, -32602, "Missing 'name' parameter"); return; } string name = val.Name; if (string.IsNullOrEmpty(name)) { await SendErrorResponseAsync(connection, request.Id, -32602, "Tool name cannot be empty"); return; } if (!_toolRegistry.HasTool(name)) { await SendErrorResponseAsync(connection, request.Id, -31002, "Tool '" + name + "' not found"); return; } object parameters = null; JToken val2 = default(JToken); if (JObject.Parse(text).TryGetValue("parameters", ref val2) && (int)val2.Type != 10) { parameters = val2; } else if (val.Arguments != null) { parameters = val.Arguments; } object result = await _toolRegistry.CallToolAsync(name, parameters); await SendResponseAsync(connection, request.Id, result); } catch (ToolParameterBindingException ex) { await SendErrorResponseAsync(connection, request.Id, -32602, ex.Message); } catch (Exception ex2) { await SendErrorResponseAsync(connection, request.Id, -32603, "Tool execution failed: " + ex2.Message); } } private async Task HandleEventsSubscribeAsync(IConnection connection, GabpRequest request) { try { Dictionary<string, object> dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(JsonConvert.SerializeObject(request.Params)); if (dictionary == null || !dictionary.ContainsKey("channels")) { await SendErrorResponseAsync(connection, request.Id, -32602, "Missing 'channels' parameter"); return; } List<string> channels = JsonConvert.DeserializeObject<List<string>>(JsonConvert.SerializeObject(dictionary["channels"])) ?? new List<string>(); List<string> subscribed = await _eventManager.SubscribeAsync(connection, channels); await SendResponseAsync(connection, request.Id, new { subscribed }); } catch (Exception ex) { await SendErrorResponseAsync(connection, request.Id, -32603, "Subscription failed: " + ex.Message); } } private async Task HandleEventsUnsubscribeAsync(IConnection connection, GabpRequest request) { try { Dictionary<string, object> dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(JsonConvert.SerializeObject(request.Params)); if (dictionary == null || !dictionary.ContainsKey("channels")) { await SendErrorResponseAsync(connection, request.Id, -32602, "Missing 'channels' parameter"); return; } List<string> channels = JsonConvert.DeserializeObject<List<string>>(JsonConvert.SerializeObject(dictionary["channels"])) ?? new List<string>(); List<string> unsubscribed = await _eventManager.UnsubscribeAsync(connection, channels); await SendResponseAsync(connection, request.Id, new { unsubscribed }); } catch (Exception ex) { await SendErrorResponseAsync(connection, request.Id, -32603, "Unsubscription failed: " + ex.Message); } } private async Task HandleAttentionCurrentAsync(IConnection connection, GabpRequest request) { AttentionItem current = _attentionManager.GetCurrent(); await SendResponseAsync(connection, request.Id, new AttentionCurrentResult { Attention = current }); } private async Task HandleAttentionAckAsync(IConnection connection, GabpRequest request) { try { AttentionAckParams attentionAckParams = JsonConvert.DeserializeObject<AttentionAckParams>(JsonConvert.SerializeObject(request.Params)); if (attentionAckParams == null || string.IsNullOrWhiteSpace(attentionAckParams.AttentionId)) { await SendErrorResponseAsync(connection, request.Id, -32602, "Missing 'attentionId' parameter"); return; } AttentionAckResult result = await _attentionManager.AcknowledgeAsync(attentionAckParams.AttentionId); await SendResponseAsync(connection, request.Id, result); } catch (Exception ex) { await SendErrorResponseAsync(connection, request.Id, -32603, "Attention acknowledgement failed: " + ex.Message); } } private async Task SendResponseAsync(IConnection connection, string requestId, object result) { GabpResponse message = new GabpResponse { Id = requestId, Result = result }; await connection.SendMessageAsync(message); } private async Task SendErrorResponseAsync(IConnection connection, string requestId, int errorCode, string message, object data = null) { GabpResponse message2 = new GabpResponse { Id = requestId, Error = new GabpError { Code = errorCode, Message = message, Data = data } }; await connection.SendMessageAsync(message2); } private void RegisterCoreTools() { } private void RegisterCoreEventChannels() { _eventManager.RegisterChannel("system/status", "System status events"); _eventManager.RegisterChannel("system/log", "System log events"); if (_attentionManager.IsEnabled) { _eventManager.RegisterChannel("attention/opened", "Attention lifecycle events when a blocking or advisory item opens"); _eventManager.RegisterChannel("attention/updated", "Attention lifecycle events when the current item is updated"); _eventManager.RegisterChannel("attention/cleared", "Attention lifecycle events when the current item is cleared"); } } private List<string> GetSupportedProtocolMethods() { List<string> list = new List<string>(BaseProtocolMethods); if (_attentionManager.IsEnabled) { list.Add("attention/current"); list.Add("attention/ack"); } return list; } private void DisposeActiveSessions() { foreach (SessionInfo item in _sessions.Values.ToList()) { try { item.Connection?.Dispose(); } catch { } } _sessions.Clear(); } public void Dispose() { if (!_disposed) { _disposed = true; _transport.ConnectionEstablished -= OnConnectionEstablished; _transport.MessageReceived -= OnMessageReceived; try { _transport.StopAsync().GetAwaiter().GetResult(); } catch { } DisposeActiveSessions(); _transport?.Dispose(); } } } } namespace Lib.GAB.Protocol { public static class GabpErrorCodes { public const int InvalidRequest = -32600; public const int MethodNotFound = -32601; public const int InvalidParams = -32602; public const int InternalError = -32603; public const int ServerError = -32000; public const int AuthenticationFailed = -31000; public const int SessionNotEstablished = -31001; public const int ToolNotFound = -31002; public const int EventChannelNotFound = -31003; public const int ResourceNotFound = -31004; public const int MethodNotAllowed = -31005; } public abstract class GabpMessage { [JsonProperty("v")] public string Version { get; set; } = "gabp/1"; [JsonProperty("id")] public string Id { get; set; } = Guid.NewGuid().ToString(); [JsonProperty("type")] public abstract string Type { get; } } public class GabpRequest : GabpMessage { public override string Type => "request"; [JsonProperty("method")] public string Method { get; set; } = string.Empty; [JsonProperty("params")] public object Params { get; set; } } public class GabpResponse : GabpMessage { public override string Type => "response"; [JsonProperty("result")] public object Result { get; set; } [JsonProperty("error")] public GabpError Error { get; set; } } public class GabpEvent : GabpMessage { public override string Type => "event"; [JsonProperty("channel")] public string Channel { get; set; } = string.Empty; [JsonProperty("seq")] public int Sequence { get; set; } [JsonProperty("payload")] public object Payload { get; set; } [JsonProperty("timestamp")] public DateTimeOffset? Timestamp { get; set; } } public class GabpError { [JsonProperty("code")] public int Code { get; set; } [JsonProperty("message")] public string Message { get; set; } = string.Empty; [JsonProperty("data")] public object Data { get; set; } } public class SessionHelloParams { [JsonProperty("token")] public string Token { get; set; } = string.Empty; [JsonProperty("bridgeVersion")] public string BridgeVersion { get; set; } = string.Empty; [JsonProperty("platform")] public string Platform { get; set; } = string.Empty; [JsonProperty("launchId")] public string LaunchId { get; set; } = string.Empty; } public class SessionWelcomeResult { [JsonProperty("agentId")] public string AgentId { get; set; } = string.Empty; [JsonProperty("app")] public AppInfo App { get; set; } = new AppInfo(); [JsonProperty("capabilities")] public Capabilities Capabilities { get; set; } = new Capabilities(); [JsonProperty("schemaVersion")] public string SchemaVersion { get; set; } = "1.0"; } public class AppInfo { [JsonProperty("name")] public string Name { get; set; } = string.Empty; [JsonProperty("version")] public string Version { get; set; } = string.Empty; } public class Capabilities { [JsonProperty("methods")] public List<string> Methods { get; set; } = new List<string>(); [JsonIgnore] public List<string> Tools { get { return Methods; } set { Methods = value ?? new List<string>(); } } [JsonProperty("events")] public List<string> Events { get; set; } = new List<string>(); [JsonProperty("resources")] public List<string> Resources { get; set; } = new List<string>(); } } namespace Lib.GAB.Events { public class EventManager : IEventManager { private class EventChannelState { public int SequenceNumber; public string Name { get; set; } = string.Empty; public string Description { get; set; } public HashSet<IConnection> Subscribers { get; set; } = new HashSet<IConnection>(); } private readonly ConcurrentDictionary<string, EventChannelState> _channels = new ConcurrentDictionary<string, EventChannelState>(); private readonly ConcurrentDictionary<string, HashSet<IConnection>> _subscriptions = new ConcurrentDictionary<string, HashSet<IConnection>>(); private readonly object _lockObject = new object(); public async Task<List<string>> SubscribeAsync(IConnection connection, List<string> channels) { List<string> list = new List<string>(); lock (_lockObject) { foreach (string channel in channels) { if (_channels.ContainsKey(channel)) { _channels[channel].Subscribers.Add(connection); if (!_subscriptions.ContainsKey(connection.Id)) { _subscriptions[connection.Id] = new HashSet<IConnection>(); } _subscriptions[connection.Id].Add(connection); list.Add(channel); } } connection.Disconnected += delegate { CleanupConnection(connection); }; } return await Task.FromResult(list); } public async Task<List<string>> UnsubscribeAsync(IConnection connection, List<string> channels) { List<string> list = new List<string>(); lock (_lockObject) { foreach (string channel in channels) { if (_channels.TryGetValue(channel, out var value) && value.Subscribers.Remove(connection)) { list.Add(channel); } } } return await Task.FromResult(list); } public List<string> GetAvailableChannels() { return _channels.Keys.ToList(); } public void RegisterChannel(string channel, string description = null) { if (string.IsNullOrWhiteSpace(channel)) { throw new ArgumentException("Channel name cannot be null or empty", "channel"); } _channels[channel] = new EventChannelState { Name = channel, Description = description, SequenceNumber = 0, Subscribers = new HashSet<IConnection>() }; } public bool UnregisterChannel(string channel) { lock (_lockObject) { if (_channels.TryRemove(channel, out var value)) { foreach (IConnection item in value.Subscribers.ToList()) { if (_subscriptions.TryGetValue(item.Id, out var value2)) { value2.Remove(item); if (value2.Count == 0) { _subscriptions.TryRemove(item.Id, out var _); } } } return true; } return false; } } public async Task EmitEventAsync(string channel, object payload, DateTimeOffset? timestamp = null) { if (!_channels.TryGetValue(channel, out var value)) { return; } GabpEvent eventMessage = new GabpEvent { Channel = channel, Sequence = Interlocked.Increment(ref value.SequenceNumber), Payload = payload, Timestamp = (timestamp ?? DateTimeOffset.UtcNow) }; await Task.WhenAll(((IEnumerable<IConnection>)value.Subscribers.ToList()).Select((Func<IConnection, Task>)async delegate(IConnection connection) { try { if (connection.IsConnected) { await connection.SendMessageAsync(eventMessage); } else { CleanupConnection(connection); } } catch { CleanupConnection(connection); } })); } public int GetSubscriberCount(string channel) { if (!_channels.TryGetValue(channel, out var value)) { return 0; } return value.Subscribers.Count; } private void CleanupConnection(IConnection connection) { lock (_lockObject) { foreach (EventChannelState value2 in _channels.Values) { value2.Subscribers.Remove(connection); } _subscriptions.TryRemove(connection.Id, out var _); } } } public interface IEventManager { Task<List<string>> SubscribeAsync(IConnection connection, List<string> channels); Task<List<string>> UnsubscribeAsync(IConnection connection, List<string> channels); List<string> GetAvailableChannels(); void RegisterChannel(string channel, string description = null); bool UnregisterChannel(string channel); Task EmitEventAsync(string channel, object payload, DateTimeOffset? timestamp = null); int GetSubscriberCount(string channel); } public class EventChannelInfo { public string Name { get; set; } = string.Empty; public string Description { get; set; } public int SubscriberCount { get; set; } } } namespace Lib.GAB.Attention { public static class AttentionProtocol { public const string CurrentMethod = "attention/current"; public const string AckMethod = "attention/ack"; public const string OpenedChannel = "attention/opened"; public const string UpdatedChannel = "attention/updated"; public const string ClearedChannel = "attention/cleared"; } public class AttentionSample { [JsonProperty("level")] public string Level { get; set; } = string.Empty; [JsonProperty("message")] public string Message { get; set; } = string.Empty; [JsonProperty("repeatCount")] public int RepeatCount { get; set; } [JsonProperty("latestSequence")] public long LatestSequence { get; set; } public AttentionSample Clone() { return new AttentionSample { Level = Level, Message = Message, RepeatCount = RepeatCount, LatestSequence = LatestSequence }; } } public class AttentionItem { [JsonProperty("attentionId")] public string AttentionId { get; set; } = string.Empty; [JsonProperty("state")] public string State { get; set; } = "open"; [JsonProperty("severity")] public string Severity { get; set; } = "error"; [JsonProperty("blocking")] public bool Blocking { get; set; } [JsonProperty("stateInvalidated")] public bool StateInvalidated { get; set; } [JsonProperty("summary")] public string Summary { get; set; } = string.Empty; [JsonProperty(/*Could not decode attribute arguments.*/)] public string CausalOperationId { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string CausalMethod { get; set; } [JsonProperty("openedAtSequence")] public long OpenedAtSequence { get; set; } [JsonProperty("latestSequence")] public long LatestSequence { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public long? DiagnosticsCursor { get; set; } [JsonProperty("totalUrgentEntries")] public int TotalUrgentEntries { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public List<AttentionSample> Sample { get; set; } = new List<AttentionSample>(); public AttentionItem Clone() { return new AttentionItem { AttentionId = AttentionId, State = State, Severity = Severity, Blocking = Blocking, StateInvalidated = StateInvalidated, Summary = Summary, CausalOperationId = CausalOperationId, CausalMethod = CausalMethod, OpenedAtSequence = OpenedAtSequence, LatestSequence = LatestSequence, DiagnosticsCursor = DiagnosticsCursor, TotalUrgentEntries = TotalUrgentEntries, Sample = ((Sample == null) ? new List<AttentionSample>() : Sample.Select((AttentionSample entry) => entry.Clone()).ToList()) }; } } public class AttentionCurrentResult { [JsonProperty("attention")] public AttentionItem Attention { get; set; } } public class AttentionAckParams { [JsonProperty("attentionId")] public string AttentionId { get; set; } = string.Empty; } public class AttentionAckResult { [JsonProperty("acknowledged")] public bool Acknowledged { get; set; } [JsonProperty("attentionId")] public string AttentionId { get; set; } [JsonProperty("currentAttention")] public AttentionItem CurrentAttention { get; set; } } public class AttentionManager : IAttentionManager { private readonly IEventManager _eventManager; private readonly bool _isEnabled; private readonly object _lockObject = new object(); private AttentionItem _currentAttention; public bool IsEnabled => _isEnabled; public AttentionManager(IEventManager eventManager, bool isEnabled) { _eventManager = eventManager ?? throw new ArgumentNullException("eventManager"); _isEnabled = isEnabled; } public AttentionItem GetCurrent() { lock (_lockObject) { return _currentAttention?.Clone(); } } public async Task<AttentionItem> PublishAsync(AttentionItem attention, DateTimeOffset? timestamp = null) { EnsureEnabled(); if (attention == null) { throw new ArgumentNullException("attention"); } if (string.IsNullOrWhiteSpace(attention.AttentionId)) { throw new ArgumentException("Attention item must have a non-empty attentionId.", "attention"); } AttentionItem normalized; string channel; lock (_lockObject) { AttentionItem currentAttention = _currentAttention; normalized = NormalizePublishedAttention(attention, currentAttention); channel = ((currentAttention != null && string.Equals(currentAttention.AttentionId, normalized.AttentionId, StringComparison.Ordinal)) ? "attention/updated" : "attention/opened"); _currentAttention = normalized.Clone(); } await _eventManager.EmitEventAsync(channel, normalized, timestamp); return normalized.Clone(); } public async Task<AttentionItem> ClearAsync(AttentionItem clearedAttention = null, DateTimeOffset? timestamp = null) { EnsureEnabled(); AttentionItem cleared; lock (_lockObject) { if (_currentAttention == null) { return null; } cleared = NormalizeClearedAttention(clearedAttention, _currentAttention); _currentAttention = null; } await _eventManager.EmitEventAsync("attention/cleared", cleared, timestamp); return cleared.Clone(); } public async Task<AttentionAckResult> AcknowledgeAsync(string attentionId, DateTimeOffset? timestamp = null) { EnsureEnabled(); if (string.IsNullOrWhiteSpace(attentionId)) { throw new ArgumentException("Attention acknowledgement requires a non-empty attentionId.", "attentionId"); } AttentionItem attentionItem = null; AttentionItem currentAttention = null; bool acknowledged = false; lock (_lockObject) { if (_currentAttention != null && string.Equals(_currentAttention.AttentionId, attentionId, StringComparison.Ordinal)) { attentionItem = NormalizeClearedAttention(null, _currentAttention); _currentAttention = null; acknowledged = true; } else if (_currentAttention != null) { currentAttention = _currentAttention.Clone(); } } if (attentionItem != null) { await _eventManager.EmitEventAsync("attention/cleared", attentionItem, timestamp); } return new AttentionAckResult { Acknowledged = acknowledged, AttentionId = attentionId, CurrentAttention = currentAttention?.Clone() }; } private void EnsureEnabled() { if (!_isEnabled) { throw new InvalidOperationException("Attention support is not enabled for this server."); } } private static AttentionItem NormalizePublishedAttention(AttentionItem incoming, AttentionItem current) { AttentionItem attentionItem = incoming.Clone(); attentionItem.State = "open"; if (current != null && string.Equals(current.AttentionId, attentionItem.AttentionId, StringComparison.Ordinal)) { if (attentionItem.OpenedAtSequence == 0L) { attentionItem.OpenedAtSequence = current.OpenedAtSequence; } if (attentionItem.LatestSequence == 0L) { attentionItem.LatestSequence = current.LatestSequence; } if (attentionItem.TotalUrgentEntries == 0) { attentionItem.TotalUrgentEntries = current.TotalUrgentEntries; } } else if (attentionItem.LatestSequence == 0L) { attentionItem.LatestSequence = attentionItem.OpenedAtSequence; } return attentionItem; } private static AttentionItem NormalizeClearedAttention(AttentionItem clearedAttention, AttentionItem current) { AttentionItem attentionItem = (clearedAttention ?? current).Clone(); attentionItem.AttentionId = (string.IsNullOrWhiteSpace(attentionItem.AttentionId) ? current.AttentionId : attentionItem.AttentionId); attentionItem.State = "cleared"; attentionItem.Blocking = false; if (attentionItem.OpenedAtSequence == 0L) { attentionItem.OpenedAtSequence = current.OpenedAtSequence; } if (attentionItem.LatestSequence == 0L) { attentionItem.LatestSequence = current.LatestSequence; } if (attentionItem.TotalUrgentEntries == 0) { attentionItem.TotalUrgentEntries = current.TotalUrgentEntries; } if (string.IsNullOrWhiteSpace(attentionItem.Severity)) { attentionItem.Severity = current.Severity; } if (string.IsNullOrWhiteSpace(attentionItem.Summary)) { attentionItem.Summary = current.Summary; } if (string.IsNullOrWhiteSpace(attentionItem.CausalOperationId)) { attentionItem.CausalOperationId = current.CausalOperationId; } if (string.IsNullOrWhiteSpace(attentionItem.CausalMethod)) { attentionItem.CausalMethod = current.CausalMethod; } if (!attentionItem.DiagnosticsCursor.HasValue) { attentionItem.DiagnosticsCursor = current.DiagnosticsCursor; } if ((attentionItem.Sample == null || attentionItem.Sample.Count == 0) && current.Sample != null) { attentionItem.Sample = current.Sample; } return attentionItem; } } public interface IAttentionManager { bool IsEnabled { get; } AttentionItem GetCurrent(); Task<AttentionItem> PublishAsync(AttentionItem attention, DateTimeOffset? timestamp = null); Task<AttentionItem> ClearAsync(AttentionItem clearedAttention = null, DateTimeOffset? timestamp = null); Task<AttentionAckResult> AcknowledgeAsync(string attentionId, DateTimeOffset? timestamp = null); } }
plugins/Gabp.Runtime.dll
Decompiled 7 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")] [assembly: AssemblyCompany("GABP Contributors")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Shared runtime primitives for the Game Agent Bridge Protocol (GABP).")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+ce258707e15bc45e4303e9785ce49bc1fe6506cf")] [assembly: AssemblyProduct("Gabp.Runtime")] [assembly: AssemblyTitle("Gabp.Runtime")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/pardeike/gabp-runtime")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Gabp.Runtime { public class GabpAttentionSample { [JsonProperty("level")] public string Level { get; set; } = string.Empty; [JsonProperty("message")] public string Message { get; set; } = string.Empty; [JsonProperty("repeatCount")] public int RepeatCount { get; set; } [JsonProperty("latestSequence")] public int LatestSequence { get; set; } } public class GabpAttention { [JsonProperty("attentionId")] public string AttentionId { get; set; } = string.Empty; [JsonProperty("state")] public string State { get; set; } = string.Empty; [JsonProperty("severity")] public string Severity { get; set; } = string.Empty; [JsonProperty("blocking")] public bool Blocking { get; set; } [JsonProperty("stateInvalidated")] public bool StateInvalidated { get; set; } [JsonProperty("summary")] public string Summary { get; set; } = string.Empty; [JsonProperty(/*Could not decode attribute arguments.*/)] public string? CausalOperationId { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string? CausalMethod { get; set; } [JsonProperty("openedAtSequence")] public int OpenedAtSequence { get; set; } [JsonProperty("latestSequence")] public int LatestSequence { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public int? DiagnosticsCursor { get; set; } [JsonProperty("totalUrgentEntries")] public int TotalUrgentEntries { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public List<GabpAttentionSample>? Sample { get; set; } } public class AttentionCurrentParams { } public class AttentionCurrentRequest : GabpRequestEnvelope<AttentionCurrentParams> { public AttentionCurrentRequest() { base.Method = "attention/current"; base.Params = new AttentionCurrentParams(); } } public class AttentionCurrentResult { [JsonProperty("attention")] public GabpAttention? Attention { get; set; } } public class AttentionCurrentResponse : GabpResponseEnvelope<AttentionCurrentResult> { public AttentionCurrentResponse() { base.Result = new AttentionCurrentResult(); } } public class AttentionAckParams { [JsonProperty("attentionId")] public string AttentionId { get; set; } = string.Empty; } public class AttentionAckRequest : GabpRequestEnvelope<AttentionAckParams> { public AttentionAckRequest() { base.Method = "attention/ack"; base.Params = new AttentionAckParams(); } } public class AttentionAckResult { [JsonProperty("acknowledged")] public bool Acknowledged { get; set; } [JsonProperty("attentionId")] public string AttentionId { get; set; } = string.Empty; [JsonProperty("currentAttention")] public GabpAttention? CurrentAttention { get; set; } } public class AttentionAckResponse : GabpResponseEnvelope<AttentionAckResult> { public AttentionAckResponse() { base.Result = new AttentionAckResult(); } } public class GabpCapabilities { [JsonProperty("methods")] public List<string> Methods { get; set; } = new List<string>(); [JsonProperty("events")] public List<string> Events { get; set; } = new List<string>(); [JsonProperty("resources")] public List<string> Resources { get; set; } = new List<string>(); [JsonProperty(/*Could not decode attribute arguments.*/)] public Dictionary<string, object>? Extensions { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public GabpLimits? Limits { get; set; } } public class GabpLimits { [JsonProperty(/*Could not decode attribute arguments.*/)] public int? MaxMessageSize { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public int? MaxConcurrentRequests { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public int? RequestTimeout { get; set; } } public class GabpError { [JsonProperty("code")] public int Code { get; set; } [JsonProperty("message")] public string Message { get; set; } = string.Empty; [JsonProperty(/*Could not decode attribute arguments.*/)] public JToken? Data { get; set; } } public static class GabpJson { private static readonly JsonSerializerSettings DefaultSettings = CreateDefaultSettings(); public static string Serialize<T>(T value) { return JsonConvert.SerializeObject((object)value, DefaultSettings); } public static byte[] SerializeToUtf8Bytes<T>(T value) { return Encoding.UTF8.GetBytes(Serialize(value)); } public static T Deserialize<T>(string json) { return JsonConvert.DeserializeObject<T>(json, DefaultSettings); } public static T Deserialize<T>(byte[] json) { return Deserialize<T>(Encoding.UTF8.GetString(json)); } private static JsonSerializerSettings CreateDefaultSettings() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown return new JsonSerializerSettings { NullValueHandling = (NullValueHandling)1 }; } } public static class GabpProtocol { public const string EnvelopeVersion = "gabp/1"; public const string RequestType = "request"; public const string ResponseType = "response"; public const string EventType = "event"; public const string SessionHelloMethod = "session/hello"; public const string ToolsCallMethod = "tools/call"; public const string ToolsListMethod = "tools/list"; public const string AttentionCurrentMethod = "attention/current"; public const string AttentionAckMethod = "attention/ack"; public const string AttentionOpenedChannel = "attention/opened"; public const string AttentionUpdatedChannel = "attention/updated"; public const string AttentionClearedChannel = "attention/cleared"; } public class GabpRequestEnvelope<TParams> { [JsonProperty("v")] public string Version { get; set; } = "gabp/1"; [JsonProperty("id")] public string Id { get; set; } = string.Empty; [JsonProperty("type")] public string Type { get; set; } = "request"; [JsonProperty("method")] public string Method { get; set; } = string.Empty; [JsonProperty("params")] public TParams Params { get; set; } } public class GabpResponseEnvelope<TResult> { [JsonProperty("v")] public string Version { get; set; } = "gabp/1"; [JsonProperty("id")] public string Id { get; set; } = string.Empty; [JsonProperty("type")] public string Type { get; set; } = "response"; [JsonProperty(/*Could not decode attribute arguments.*/)] public TResult? Result { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public GabpError? Error { get; set; } } public class GabpEventEnvelope<TPayload> { [JsonProperty("v")] public string Version { get; set; } = "gabp/1"; [JsonProperty("id")] public string Id { get; set; } = string.Empty; [JsonProperty("type")] public string Type { get; set; } = "event"; [JsonProperty("channel")] public string Channel { get; set; } = string.Empty; [JsonProperty("seq")] public int Sequence { get; set; } [JsonProperty("payload")] public TPayload Payload { get; set; } } public static class RuntimeMetadata { public const string PackageId = "Gabp.Runtime"; public const string Version = "1.0.0"; public const string DevelopmentVersion = "1.0.0"; public const string TargetGabpSchemaVersion = "1.0"; } public class GabpClientInfo { [JsonProperty(/*Could not decode attribute arguments.*/)] public string? Name { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string? Version { get; set; } } public class GabpAppInfo { [JsonProperty("name")] public string Name { get; set; } = string.Empty; [JsonProperty("version")] public string Version { get; set; } = string.Empty; } public class GabpServerInfo { [JsonProperty(/*Could not decode attribute arguments.*/)] public string? Name { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string? Version { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string? Author { get; set; } } public class SessionHelloParams { [JsonProperty("token")] public string Token { get; set; } = string.Empty; [JsonProperty("bridgeVersion")] public string BridgeVersion { get; set; } = string.Empty; [JsonProperty("platform")] public string Platform { get; set; } = string.Empty; [JsonProperty("launchId")] public string LaunchId { get; set; } = string.Empty; [JsonProperty(/*Could not decode attribute arguments.*/)] public GabpClientInfo? ClientInfo { get; set; } } public class SessionHelloRequest : GabpRequestEnvelope<SessionHelloParams> { public SessionHelloRequest() { base.Method = "session/hello"; base.Params = new SessionHelloParams(); } } public class SessionWelcomeResult { [JsonProperty("agentId")] public string AgentId { get; set; } = string.Empty; [JsonProperty("app")] public GabpAppInfo App { get; set; } = new GabpAppInfo(); [JsonProperty("capabilities")] public GabpCapabilities Capabilities { get; set; } = new GabpCapabilities(); [JsonProperty("schemaVersion")] public string SchemaVersion { get; set; } = "1.0"; [JsonProperty(/*Could not decode attribute arguments.*/)] public GabpServerInfo? ServerInfo { get; set; } } public class SessionWelcomeResponse : GabpResponseEnvelope<SessionWelcomeResult> { public SessionWelcomeResponse() { base.Result = new SessionWelcomeResult(); } } public class GabpToolDescriptor { [JsonProperty("name")] public string Name { get; set; } = string.Empty; [JsonProperty("title")] public string Title { get; set; } = string.Empty; [JsonProperty("description")] public string Description { get; set; } = string.Empty; [JsonProperty("inputSchema")] public JToken InputSchema { get; set; } = (JToken)new JObject(); [JsonProperty("outputSchema")] public JToken OutputSchema { get; set; } = (JToken)new JObject(); [JsonProperty(/*Could not decode attribute arguments.*/)] public List<string>? Tags { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public bool? Deprecated { get; set; } [JsonProperty(/*Could not decode attribute arguments.*/)] public string? Version { get; set; } } public class ToolsCallParams { [JsonProperty("name")] public string Name { get; set; } = string.Empty; [JsonProperty(/*Could not decode attribute arguments.*/)] public JToken? Arguments { get; set; } } public class ToolsCallRequest : GabpRequestEnvelope<ToolsCallParams> { public ToolsCallRequest() { base.Method = "tools/call"; base.Params = new ToolsCallParams(); } } public class ToolsListResult { [JsonProperty("tools")] public List<GabpToolDescriptor> Tools { get; set; } = new List<GabpToolDescriptor>(); } public class ToolsListResponse : GabpResponseEnvelope<ToolsListResult> { public ToolsListResponse() { base.Result = new ToolsListResult(); } } }