Client telemetry
This commit is contained in:
parent
f4ec57a44d
commit
a0f40cf2cd
5 changed files with 466 additions and 8 deletions
35
MosswartMassacre/ClientTelemetry.cs
Normal file
35
MosswartMassacre/ClientTelemetry.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
public class ClientTelemetry
|
||||||
|
{
|
||||||
|
private readonly Process _proc;
|
||||||
|
|
||||||
|
public ClientTelemetry()
|
||||||
|
{
|
||||||
|
_proc = Process.GetCurrentProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Working-set memory in bytes.</summary>
|
||||||
|
public long MemoryBytes => _proc.WorkingSet64;
|
||||||
|
|
||||||
|
/// <summary>Total open handles.</summary>
|
||||||
|
public int HandleCount => _proc.HandleCount;
|
||||||
|
|
||||||
|
/// <summary>CPU utilisation (%) averaged over <paramref name="sampleMs"/>.</summary>
|
||||||
|
public float GetCpuUsage(int sampleMs = 500)
|
||||||
|
{
|
||||||
|
// you can keep your PerformanceCounter variant, but here’s a simpler PID-based way:
|
||||||
|
var startCpu = _proc.TotalProcessorTime;
|
||||||
|
var start = DateTime.UtcNow;
|
||||||
|
Thread.Sleep(sampleMs);
|
||||||
|
var endCpu = _proc.TotalProcessorTime;
|
||||||
|
var end = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// CPU‐time used across all cores:
|
||||||
|
var cpuMs = (endCpu - startCpu).TotalMilliseconds;
|
||||||
|
var elapsedMs = (end - start).TotalMilliseconds * Environment.ProcessorCount;
|
||||||
|
return (float)(cpuMs / elapsedMs * 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
392
MosswartMassacre/ManyHook.cs
Normal file
392
MosswartMassacre/ManyHook.cs
Normal file
|
|
@ -0,0 +1,392 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Decal.Adapter;
|
||||||
|
|
||||||
|
namespace MosswartMassacre
|
||||||
|
{
|
||||||
|
public class MultiHook
|
||||||
|
{
|
||||||
|
internal IntPtr Entrypoint;
|
||||||
|
internal Delegate Del;
|
||||||
|
|
||||||
|
internal List<int> CallLocations = new List<int>();
|
||||||
|
internal List<Hook> Hooks = new List<Hook>();
|
||||||
|
|
||||||
|
public MultiHook(int entrypoint, params int[] callLocations)
|
||||||
|
{
|
||||||
|
Entrypoint = (IntPtr)entrypoint;
|
||||||
|
CallLocations.AddRange(callLocations);
|
||||||
|
Hooks.AddRange(callLocations.Select(c => new Hook((int)Entrypoint, c)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Setup(Delegate del)
|
||||||
|
{
|
||||||
|
Del = del;
|
||||||
|
return !Hooks.Any(h => !h.Setup(del));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove()
|
||||||
|
{
|
||||||
|
return !Hooks.Any(h => !h.Remove());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// New improved Hooker
|
||||||
|
/// </summary>
|
||||||
|
public class Hook
|
||||||
|
{
|
||||||
|
internal IntPtr Entrypoint;
|
||||||
|
internal Delegate Del;
|
||||||
|
internal int call;
|
||||||
|
|
||||||
|
public Hook(int entrypoint, int call_location)
|
||||||
|
{
|
||||||
|
Entrypoint = (IntPtr)entrypoint;
|
||||||
|
call = call_location;
|
||||||
|
}
|
||||||
|
public bool Setup(Delegate del)
|
||||||
|
{
|
||||||
|
if (!hookers.Contains(this))
|
||||||
|
{
|
||||||
|
Del = del;
|
||||||
|
if (ReadCall(call) != (int)Entrypoint)
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat(
|
||||||
|
$"[Hook] Failed to detour 0x{call:X8}. " +
|
||||||
|
$"Expected 0x{((int)Entrypoint):X8}, " +
|
||||||
|
$"got 0x{ReadCall(call):X8}"
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!PatchCall(call, Marshal.GetFunctionPointerForDelegate(Del)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hookers.Add(this);
|
||||||
|
PluginCore.WriteToChat($"[Hook] Hooked 0x{(int)Entrypoint:X8}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public bool Remove()
|
||||||
|
{
|
||||||
|
if (hookers.Contains(this))
|
||||||
|
{
|
||||||
|
hookers.Remove(this);
|
||||||
|
if (PatchCall(call, Entrypoint))
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[Hook] Un-hooked 0x{(int)Entrypoint:X8}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static half
|
||||||
|
internal static List<Hook> hookers = new List<Hook>();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
internal static extern bool VirtualProtectEx(
|
||||||
|
IntPtr hProcess, IntPtr lpAddress,
|
||||||
|
UIntPtr dwSize, int flNewProtect,
|
||||||
|
out int lpflOldProtect
|
||||||
|
);
|
||||||
|
|
||||||
|
internal static void Write(IntPtr address, int newValue)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
VirtualProtectEx(
|
||||||
|
Process.GetCurrentProcess().Handle,
|
||||||
|
address, (UIntPtr)4, 0x40, out int old
|
||||||
|
);
|
||||||
|
*(int*)address = newValue;
|
||||||
|
VirtualProtectEx(
|
||||||
|
Process.GetCurrentProcess().Handle,
|
||||||
|
address, (UIntPtr)4, old, out old
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool PatchCall(int callLocation, IntPtr newPointer)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
byte* p = (byte*)callLocation;
|
||||||
|
if ((p[0] & 0xFE) != 0xE8) return false;
|
||||||
|
int newOffset = (int)newPointer - (callLocation + 5);
|
||||||
|
Write((IntPtr)(callLocation + 1), newOffset);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static int ReadCall(int callLocation)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
byte* p = (byte*)callLocation;
|
||||||
|
if ((p[0] & 0xFE) != 0xE8) return 0;
|
||||||
|
int prevOffset = *(int*)(callLocation + 1);
|
||||||
|
return prevOffset + (callLocation + 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void Cleanup()
|
||||||
|
{
|
||||||
|
for (int i = hookers.Count - 1; i >= 0; i--)
|
||||||
|
hookers[i].Remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// New improved Hooker (Virtual‐table edition)
|
||||||
|
/// </summary>
|
||||||
|
public class VHook
|
||||||
|
{
|
||||||
|
internal int Entrypoint;
|
||||||
|
internal Delegate Del;
|
||||||
|
internal int call;
|
||||||
|
|
||||||
|
public VHook(int entrypoint, int vtbl_address)
|
||||||
|
{
|
||||||
|
Entrypoint = entrypoint;
|
||||||
|
call = vtbl_address;
|
||||||
|
}
|
||||||
|
public bool Setup(Delegate del)
|
||||||
|
{
|
||||||
|
if (!hookers.Contains(this))
|
||||||
|
{
|
||||||
|
Del = del;
|
||||||
|
if (ReadVCall(call) != Entrypoint) return false;
|
||||||
|
if (PatchVCall(call, (int)Marshal.GetFunctionPointerForDelegate(Del)))
|
||||||
|
{
|
||||||
|
hookers.Add(this);
|
||||||
|
PluginCore.WriteToChat($"[VHook] Hooked vtbl slot 0x{call:X8}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
public bool Remove()
|
||||||
|
{
|
||||||
|
if (hookers.Contains(this))
|
||||||
|
{
|
||||||
|
hookers.Remove(this);
|
||||||
|
if (PatchVCall(call, Entrypoint))
|
||||||
|
{
|
||||||
|
PluginCore.WriteToChat($"[VHook] Un-hooked vtbl slot 0x{call:X8}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static half
|
||||||
|
internal static List<VHook> hookers = new List<VHook>();
|
||||||
|
|
||||||
|
internal static bool PatchVCall(int callLocation, int newPointer)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
Hook.Write((IntPtr)callLocation, newPointer);
|
||||||
|
return *(int*)callLocation == newPointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
internal unsafe static int ReadVCall(int callLocation) => *(int*)callLocation;
|
||||||
|
|
||||||
|
internal static void Cleanup()
|
||||||
|
{
|
||||||
|
for (int i = hookers.Count - 1; i >= 0; i--)
|
||||||
|
hookers[i].Remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ChatHooks
|
||||||
|
{
|
||||||
|
// For loc_463D60 – UIElement, probably SetText(this, string)
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate void SetTextDelegate(IntPtr @this, IntPtr strPtr);
|
||||||
|
|
||||||
|
// For loc_469440 – Same signature; adjust if you find it’s different in IDA!
|
||||||
|
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||||
|
private delegate void SetTextDelegate2(IntPtr @this, IntPtr strPtr);
|
||||||
|
|
||||||
|
private static MultiHook _hook1, _hook2;
|
||||||
|
private static SetTextDelegate _orig1;
|
||||||
|
private static SetTextDelegate2 _orig2;
|
||||||
|
|
||||||
|
// All relevant direct code addresses (calls/jmps) to loc_463D60:
|
||||||
|
private static readonly int[] Sites1 = {
|
||||||
|
//0x00464A22,
|
||||||
|
//0x0046B8FF,
|
||||||
|
//0x0047037F,
|
||||||
|
//0x00474AA1,
|
||||||
|
0x004D22A3,
|
||||||
|
0x004E5F20,
|
||||||
|
0x004F4663,
|
||||||
|
// (skip 0x004BD370, it's a jmp)
|
||||||
|
};
|
||||||
|
|
||||||
|
// All relevant direct code addresses (calls/jmps) to loc_469440:
|
||||||
|
private static readonly int[] Sites2 = {
|
||||||
|
//0x00468EFA,
|
||||||
|
//0x00469F23,
|
||||||
|
//0x0046A4C5,
|
||||||
|
//0x004B80EC,
|
||||||
|
0x004CDD49,
|
||||||
|
0x004F4679,
|
||||||
|
//0x004F5392,
|
||||||
|
//0x004F623B,
|
||||||
|
//0x004F6253,
|
||||||
|
// (skip 0x004F46E6, it's a jmp)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private static bool IsSanePtr(IntPtr ptr)
|
||||||
|
{
|
||||||
|
long v = ptr.ToInt64();
|
||||||
|
// Only allow user-mode addresses (protects from 0, 0x10, or other garbage)
|
||||||
|
return v > 0x10000 && v < 0x00007FFFFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MyHook1(IntPtr @this, IntPtr strPtr)
|
||||||
|
{
|
||||||
|
string msg;
|
||||||
|
long val = strPtr.ToInt64();
|
||||||
|
|
||||||
|
// Always log, even if pointer looks bad!
|
||||||
|
if (val == 0 || val == 0x10 || val < 0x10000)
|
||||||
|
{
|
||||||
|
msg = $"[SetText1] strPtr=0x{val:X} [skipped-invalid]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try direct pointer to string (UNICODE)
|
||||||
|
msg = Marshal.PtrToStringUni(strPtr);
|
||||||
|
if (string.IsNullOrEmpty(msg))
|
||||||
|
{
|
||||||
|
msg = $"[SetText1] strPtr=0x{val:X} [direct:empty]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
msg = $"[SetText1] strPtr=0x{val:X} \"{msg}\"";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
msg = $"[SetText1] strPtr=0x{val:X} [EX:{ex.GetType().Name}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually write to log
|
||||||
|
LogToFile("SetText1", msg);
|
||||||
|
|
||||||
|
// Always call the original
|
||||||
|
_orig1(@this, strPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Repeat for MyHook2, just change the tag/log if you want to differentiate
|
||||||
|
private static void MyHook2(IntPtr @this, IntPtr strPtr)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string msg;
|
||||||
|
long val = strPtr.ToInt64();
|
||||||
|
if (val == 0 || val == 0x10 || val < 0x10000)
|
||||||
|
{
|
||||||
|
msg = $"[SetText2] strPtr=0x{val:X} [skipped-invalid]";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
msg = Marshal.PtrToStringUni(strPtr);
|
||||||
|
if (string.IsNullOrEmpty(msg))
|
||||||
|
msg = $"[SetText2] strPtr=0x{val:X} [direct:empty]";
|
||||||
|
else
|
||||||
|
msg = $"[SetText2] \"{msg}\"";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
msg = $"[SetText2] strPtr=0x{val:X} [EX:{ex.GetType().Name}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogToFile("SetText2", msg);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_orig2(@this, strPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
foreach (int addr in Sites2)
|
||||||
|
{
|
||||||
|
byte* p = (byte*)addr;
|
||||||
|
int callTarget = Hook.ReadCall(addr);
|
||||||
|
// Print the bytes and resolved call target
|
||||||
|
PluginCore.WriteToChat(
|
||||||
|
$"[DEBUG] Addr: 0x{addr:X8} Bytes: {p[0]:X2} {p[1]:X2} {p[2]:X2} {p[3]:X2} {p[4]:X2} -> 0x{callTarget:X8}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Patch for loc_463D60 (ENTRY1)
|
||||||
|
_hook1 = new MultiHook(0x00463D60, Sites1);
|
||||||
|
_orig1 = Marshal.GetDelegateForFunctionPointer<SetTextDelegate>(_hook1.Entrypoint);
|
||||||
|
if (_hook1.Setup((SetTextDelegate)MyHook1))
|
||||||
|
PluginCore.WriteToChat("[ChatHooks] SetText1 hook installed.");
|
||||||
|
else
|
||||||
|
PluginCore.WriteToChat("[ChatHooks] SetText1 hook FAILED.");
|
||||||
|
|
||||||
|
// Patch for loc_469440 (ENTRY2)
|
||||||
|
_hook2 = new MultiHook(0x00469440, Sites2);
|
||||||
|
_orig2 = Marshal.GetDelegateForFunctionPointer<SetTextDelegate2>(_hook2.Entrypoint);
|
||||||
|
if (_hook2.Setup((SetTextDelegate2)MyHook2))
|
||||||
|
PluginCore.WriteToChat("[ChatHooks] SetText2 hook installed.");
|
||||||
|
else
|
||||||
|
PluginCore.WriteToChat("[ChatHooks] SetText2 hook FAILED.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Dispose()
|
||||||
|
{
|
||||||
|
_hook1?.Remove();
|
||||||
|
_hook2?.Remove();
|
||||||
|
_hook1 = null;
|
||||||
|
_hook2 = null;
|
||||||
|
Hook.Cleanup();
|
||||||
|
VHook.Cleanup();
|
||||||
|
PluginCore.WriteToChat("[ChatHooks] All hooks removed.");
|
||||||
|
}
|
||||||
|
private static void LogToFile(string prefix, string msg)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string characterName = CoreManager.Current.CharacterFilter.Name;
|
||||||
|
string pluginFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
|
||||||
|
string characterFolder = Path.Combine(pluginFolder, characterName);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(characterFolder);
|
||||||
|
|
||||||
|
string logFile = Path.Combine(characterFolder, "hooklog.txt");
|
||||||
|
File.AppendAllText(logFile, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{prefix}] {msg}\r\n");
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -148,6 +148,8 @@
|
||||||
<Compile Include="..\Shared\VCS_Connector.cs">
|
<Compile Include="..\Shared\VCS_Connector.cs">
|
||||||
<Link>Shared\VCS_Connector.cs</Link>
|
<Link>Shared\VCS_Connector.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="ClientTelemetry.cs" />
|
||||||
|
<Compile Include="ManyHook.cs" />
|
||||||
<Compile Include="MossyInventory.cs" />
|
<Compile Include="MossyInventory.cs" />
|
||||||
<Compile Include="vTank.cs" />
|
<Compile Include="vTank.cs" />
|
||||||
<Compile Include="VtankControl.cs" />
|
<Compile Include="VtankControl.cs" />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
@ -73,6 +74,7 @@ namespace MosswartMassacre
|
||||||
_inventoryLogger = new MossyInventory();
|
_inventoryLogger = new MossyInventory();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +142,7 @@ namespace MosswartMassacre
|
||||||
Telemetry.Start();
|
Telemetry.Start();
|
||||||
if (WebSocketEnabled)
|
if (WebSocketEnabled)
|
||||||
WebSocket.Start();
|
WebSocket.Start();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,8 +237,10 @@ namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
Decal_DispatchOnChatCommand("/vt setmetastate loot_rare");
|
Decal_DispatchOnChatCommand("/vt setmetastate loot_rare");
|
||||||
}
|
}
|
||||||
|
|
||||||
DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000);
|
DelayedCommandManager.AddDelayedCommand($"/a {rareText}", 3000);
|
||||||
|
// Fire and forget: we don't await, since sending is not critical and we don't want to block.
|
||||||
|
_ = WebSocket.SendRareAsync(rareText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Color == 18 && e.Text.EndsWith("!report\""))
|
if (e.Color == 18 && e.Text.EndsWith("!report\""))
|
||||||
|
|
@ -521,7 +525,8 @@ namespace MosswartMassacre
|
||||||
WriteToChat("/mm http - Local http-command server enable|disable");
|
WriteToChat("/mm http - Local http-command server enable|disable");
|
||||||
WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable");
|
WriteToChat("/mm remotecommand - Listen to allegiance !do/!dot enable|disable");
|
||||||
WriteToChat("/mm getmetastate - Gets the current metastate");
|
WriteToChat("/mm getmetastate - Gets the current metastate");
|
||||||
|
WriteToChat("/TESTAR");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "report":
|
case "report":
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Decal.Adapter;
|
using Decal.Adapter;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using uTank2;
|
||||||
|
|
||||||
namespace MosswartMassacre
|
namespace MosswartMassacre
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +23,7 @@ namespace MosswartMassacre
|
||||||
public static class WebSocket
|
public static class WebSocket
|
||||||
{
|
{
|
||||||
// ─── configuration ──────────────────────────
|
// ─── configuration ──────────────────────────
|
||||||
private static readonly Uri WsEndpoint = new Uri("wss://mosswart.snakedesert.se/websocket/");
|
private static readonly Uri WsEndpoint = new Uri("wss://overlord.snakedesert.se/websocket/");
|
||||||
private const string SharedSecret = "your_shared_secret";
|
private const string SharedSecret = "your_shared_secret";
|
||||||
private const int IntervalSec = 5;
|
private const int IntervalSec = 5;
|
||||||
private static string SessionId = "";
|
private static string SessionId = "";
|
||||||
|
|
@ -185,6 +186,7 @@ namespace MosswartMassacre
|
||||||
var envelope = new
|
var envelope = new
|
||||||
{
|
{
|
||||||
type = "chat",
|
type = "chat",
|
||||||
|
timestamp = DateTime.UtcNow.ToString("o"),
|
||||||
character_name = CoreManager.Current.CharacterFilter.Name,
|
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||||
text = chatText,
|
text = chatText,
|
||||||
color = colorIndex
|
color = colorIndex
|
||||||
|
|
@ -198,6 +200,8 @@ namespace MosswartMassacre
|
||||||
var envelope = new
|
var envelope = new
|
||||||
{
|
{
|
||||||
type = "spawn",
|
type = "spawn",
|
||||||
|
timestamp = DateTime.UtcNow.ToString("o"),
|
||||||
|
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||||
mob = monster,
|
mob = monster,
|
||||||
ns = nsCoord,
|
ns = nsCoord,
|
||||||
ew = ewCoord
|
ew = ewCoord
|
||||||
|
|
@ -206,6 +210,23 @@ namespace MosswartMassacre
|
||||||
var json = JsonConvert.SerializeObject(envelope);
|
var json = JsonConvert.SerializeObject(envelope);
|
||||||
await SendEncodedAsync(json, CancellationToken.None);
|
await SendEncodedAsync(json, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
public static async Task SendRareAsync(string rare)
|
||||||
|
{
|
||||||
|
var coords = Coordinates.Me;
|
||||||
|
var envelope = new
|
||||||
|
{
|
||||||
|
type = "rare",
|
||||||
|
timestamp = DateTime.UtcNow.ToString("o"),
|
||||||
|
character_name = CoreManager.Current.CharacterFilter.Name,
|
||||||
|
name = rare,
|
||||||
|
ew = coords.EW,
|
||||||
|
ns = coords.NS,
|
||||||
|
z = coords.Z
|
||||||
|
|
||||||
|
};
|
||||||
|
var json = JsonConvert.SerializeObject(envelope);
|
||||||
|
await SendEncodedAsync(json, CancellationToken.None);
|
||||||
|
}
|
||||||
|
|
||||||
// ─── shared send helper with locking ───────────────
|
// ─── shared send helper with locking ───────────────
|
||||||
|
|
||||||
|
|
@ -242,6 +263,7 @@ namespace MosswartMassacre
|
||||||
|
|
||||||
private static string BuildPayloadJson()
|
private static string BuildPayloadJson()
|
||||||
{
|
{
|
||||||
|
var tele = new ClientTelemetry();
|
||||||
var coords = Coordinates.Me;
|
var coords = Coordinates.Me;
|
||||||
var payload = new
|
var payload = new
|
||||||
{
|
{
|
||||||
|
|
@ -254,13 +276,15 @@ namespace MosswartMassacre
|
||||||
ns = coords.NS,
|
ns = coords.NS,
|
||||||
z = coords.Z,
|
z = coords.Z,
|
||||||
kills = PluginCore.totalKills,
|
kills = PluginCore.totalKills,
|
||||||
onlinetime = (DateTime.Now - PluginCore.statsStartTime)
|
|
||||||
.ToString(@"dd\.hh\:mm\:ss"),
|
|
||||||
kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
|
kills_per_hour = PluginCore.killsPerHour.ToString("F0"),
|
||||||
|
onlinetime = (DateTime.Now - PluginCore.statsStartTime).ToString(@"dd\.hh\:mm\:ss"),
|
||||||
deaths = 0,
|
deaths = 0,
|
||||||
rares_found = PluginCore.rareCount,
|
|
||||||
prismatic_taper_count = 0,
|
prismatic_taper_count = 0,
|
||||||
vt_state = VtankControl.VtGetMetaState()
|
vt_state = VtankControl.VtGetMetaState(),
|
||||||
|
mem_mb = tele.MemoryBytes,
|
||||||
|
cpu_pct = tele.GetCpuUsage(),
|
||||||
|
mem_handles = tele.HandleCount
|
||||||
|
|
||||||
};
|
};
|
||||||
return JsonConvert.SerializeObject(payload);
|
return JsonConvert.SerializeObject(payload);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue