using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Security; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using FistVR; using Fleck2; using Fleck2.Handlers; using Fleck2.Interfaces; using H3Status.Model; using H3Status.Patches; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using Valve.Newtonsoft.Json; using Valve.Newtonsoft.Json.Converters; using Valve.Newtonsoft.Json.Serialization; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("Assembly-CSharp")] [assembly: AssemblyCompany("H3Status")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.1.0")] [assembly: AssemblyInformationalVersion("0.5.1+6a5e0dc5f46c295da8d6b5979e4a299739236a95")] [assembly: AssemblyProduct("H3Status")] [assembly: AssemblyTitle("H3Status")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.5.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsReadOnlyAttribute : Attribute { } [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 xyz.bacur.plugins { public static class MyPluginInfo { public const string PLUGIN_GUID = "H3Status"; public const string PLUGIN_NAME = "H3Status"; public const string PLUGIN_VERSION = "0.5.1"; } } namespace Fleck2 { public static class Fleck2Extensions { public delegate void Action(); public delegate void Action(T t); public delegate void Action(T t, TU u); public delegate void Action(T t, TU u, TV v); public delegate TResult Func(); public delegate TResult Func(T t); public delegate TResult Func(T t, TU u); public delegate TResult Func(T t, TU u, TV v); public static T[] ToArray(this IEnumerable enumerable) { return enumerable.ToList().ToArray(); } public static List ToList(this IEnumerable enumerable) { List list = new List(); foreach (T item in enumerable) { list.Add(item); } return list; } public static IEnumerable Skip(this IEnumerable enumerable, int count) { foreach (T item in enumerable) { if (count-- <= 0) { yield return item; } } } public static byte[] Skip(this byte[] array, int count) { byte[] array2 = new byte[array.Length - count]; int num = 0; for (int i = 0; i < array.Length; i++) { if (count-- <= 0) { array2[num] = array[i]; num++; } } return array2; } public static IEnumerable Take(this IEnumerable enumerable, int count) { foreach (T item in enumerable) { if (count-- > 0) { yield return item; continue; } yield break; } } public static IEnumerable Skip(this T[] enumerable, int count) { foreach (T val in enumerable) { if (count-- > 0) { yield return val; continue; } break; } } public static IEnumerable Take(this T[] enumerable, int count) { foreach (T val in enumerable) { if (count-- > 0) { yield return val; continue; } break; } } public static IEnumerable Select(this IEnumerable enumerable, Converter selector) { foreach (TResult item in enumerable.ToList().ConvertAll(selector)) { yield return item; } } } public enum LogLevel { None, Debug, Info, Warn, Error } public class FleckLog { public static LogLevel Level = LogLevel.Info; public static Fleck2Extensions.Action LogAction = delegate(LogLevel level, string message, Exception ex) { if (Level != LogLevel.None && level >= Level) { Console.WriteLine("{0} [{1}] {2} {3}", DateTime.Now, level, message, ex); } }; public static void Warn(string message, Exception ex = null) { LogAction(LogLevel.Warn, message, ex); } public static void Error(string message, Exception ex = null) { LogAction(LogLevel.Error, message, ex); } public static void Debug(string message, Exception ex = null) { LogAction(LogLevel.Debug, message, ex); } public static void Info(string message, Exception ex = null) { LogAction(LogLevel.Info, message, ex); } } public enum FrameType : byte { Continuation = 0, Text = 1, Binary = 2, Close = 8, Ping = 9, Pong = 10 } public class HandlerFactory { public static IHandler BuildHandler(WebSocketHttpRequest request, Action onMessage, Fleck2Extensions.Action onClose, Action onBinary) { switch (GetVersion(request)) { case "76": return Draft76Handler.Create(request, onMessage); case "7": case "8": case "13": return Hybi13Handler.Create(request, onMessage, onClose, onBinary); default: throw new WebSocketException(1003); } } public static string GetVersion(WebSocketHttpRequest request) { if (request.Headers.TryGetValue("Sec-WebSocket-Version", out var value)) { return value; } if (request.Headers.TryGetValue("Sec-WebSocket-Draft", out value)) { return value; } if (request.Headers.ContainsKey("Sec-WebSocket-Key1")) { return "76"; } return "75"; } } public class HandshakeException : Exception { public HandshakeException() { } public HandshakeException(string message) : base(message) { } public HandshakeException(string message, Exception innerException) : base(message, innerException) { } } public static class IntExtensions { public static byte[] ToBigEndianBytes(this int source) { Type typeFromHandle = typeof(T); byte[] bytes; if ((object)typeFromHandle == typeof(ushort)) { bytes = BitConverter.GetBytes((ushort)source); } else if ((object)typeFromHandle == typeof(ulong)) { bytes = BitConverter.GetBytes((ulong)source); } else { if ((object)typeFromHandle != typeof(int)) { throw new InvalidCastException("Cannot be cast to T"); } bytes = BitConverter.GetBytes(source); } if (BitConverter.IsLittleEndian) { Array.Reverse((Array)bytes); } return bytes; } public static int ToLittleEndianInt(this byte[] source) { if (BitConverter.IsLittleEndian) { Array.Reverse((Array)source); } if (source.Length == 2) { return BitConverter.ToUInt16(source, 0); } if (source.Length == 8) { return (int)BitConverter.ToUInt64(source, 0); } throw new ArgumentException("Unsupported Size"); } } public class ReadState { public List Data { get; private set; } public FrameType? FrameType { get; set; } public ReadState() { Data = new List(); } public void Clear() { Data.Clear(); FrameType = null; } } public class RequestParser { private const string Pattern = "^(?[^\\s]+)\\s(?[^\\s]+)\\sHTTP\\/1\\.1\\r\\n((?[^:\\r\\n]+):\\s(?[^\\r\\n]+)\\r\\n)+\\r\\n(?.+)?"; private static readonly Regex Regex = new Regex("^(?[^\\s]+)\\s(?[^\\s]+)\\sHTTP\\/1\\.1\\r\\n((?[^:\\r\\n]+):\\s(?[^\\r\\n]+)\\r\\n)+\\r\\n(?.+)?", RegexOptions.IgnoreCase); public static WebSocketHttpRequest Parse(byte[] bytes) { return Parse(bytes, "ws"); } public static WebSocketHttpRequest Parse(byte[] bytes, string scheme) { string input = Encoding.UTF8.GetString(bytes); Match match = Regex.Match(input); if (!match.Success) { return null; } WebSocketHttpRequest webSocketHttpRequest = new WebSocketHttpRequest { Method = match.Groups["method"].Value, Path = match.Groups["path"].Value, Body = match.Groups["body"].Value, Bytes = bytes, Scheme = scheme }; CaptureCollection captures = match.Groups["field_name"].Captures; CaptureCollection captures2 = match.Groups["field_value"].Captures; for (int i = 0; i < captures.Count; i++) { string key = captures[i].ToString(); string value = captures2[i].ToString(); webSocketHttpRequest.Headers[key] = value; } return webSocketHttpRequest; } } public class SocketCancellationToken : ICancellationToken { private readonly object _syncLock = new object(); public readonly Guid Token; private bool _isCancellationRequested; public bool IsCancellationRequested { get { Monitor.Enter(_syncLock); try { return _isCancellationRequested; } finally { Monitor.Exit(_syncLock); } } private set { Monitor.Enter(_syncLock); try { _isCancellationRequested = value; } finally { Monitor.Exit(_syncLock); } } } public void ThrowIfCancellationRequested() { if (IsCancellationRequested) { throw new SocketCancellationTokenException(this); } } public void Cancel() { IsCancellationRequested = true; } } public class SocketCancellationTokenException : Exception { public SocketCancellationToken Token { get; private set; } public SocketCancellationTokenException(SocketCancellationToken token) { Token = token; } } public class SocketFactory { public SocketCancellationToken Token { get; private set; } public SocketFactory(SocketCancellationToken token) { Token = token; } public void HandleAsync(Fleck2Extensions.Func beginMethod, Fleck2Extensions.Func endMethod, Action resultCallback) { DoAsyncTask(delegate { beginMethod(delegate(IAsyncResult result) { DoAsyncTask(delegate { resultCallback(new SocketResult(endMethod(result))); }, resultCallback); }, null); }, resultCallback); } public void HandleAsync(Fleck2Extensions.Func beginMethod, Fleck2Extensions.Func endMethod, Action resultCallback) { DoAsyncTask(delegate { beginMethod(delegate(IAsyncResult result) { DoAsyncTask(delegate { resultCallback(new SocketResult(endMethod(result, Token))); }, resultCallback); }, null); }, resultCallback); } public void HandleAsyncVoid(Fleck2Extensions.Func beginMethod, Fleck2Extensions.Action endMethod, Action resultCallback) { DoAsyncTask(delegate { beginMethod(delegate(IAsyncResult result) { DoAsyncTask(delegate { endMethod((T)result); resultCallback(new SocketResult(true)); }, resultCallback); }, null); }, resultCallback); } private static void DoAsyncTask(Fleck2Extensions.Action unitOfWork, Action resultCallback) { ThreadPool.QueueUserWorkItem(delegate { try { unitOfWork(); } catch (SocketCancellationTokenException) { } catch (Exception result) { resultCallback(new SocketResult(result)); } }); } } public class SocketResult { private readonly object _result; public SocketResult(object result) { _result = result; } public SocketResult Success(Fleck2Extensions.Action callback) { if (!(_result is Exception)) { callback((TResult)_result); } return this; } public SocketResult Error(Fleck2Extensions.Action callback) { if (_result is Exception) { callback((TResult)_result); } return this; } public SocketResult Success(Fleck2Extensions.Action callback) { if (!(_result is Exception)) { callback(); } return this; } public TResult AsValue() { return (TResult)_result; } } public class SocketWrapper : ISocket { private readonly Socket _socket; private Stream _stream; private readonly SocketCancellationToken _socketCancellationToken; private readonly SocketFactory _socketFactory; public string RemoteIpAddress { get { if (!(_socket.RemoteEndPoint is IPEndPoint iPEndPoint)) { return null; } return iPEndPoint.Address.ToString(); } } public int RemotePort { get { if (!(_socket.RemoteEndPoint is IPEndPoint iPEndPoint)) { return -1; } return iPEndPoint.Port; } } public bool Connected => _socket.Connected; public Stream Stream => _stream; public bool NoDelay { get { return _socket.NoDelay; } set { _socket.NoDelay = value; } } public SocketWrapper(Socket socket) { _socketCancellationToken = new SocketCancellationToken(); _socketFactory = new SocketFactory(_socketCancellationToken); _socket = socket; if (_socket.Connected) { _stream = new NetworkStream(_socket); } } public void Authenticate(X509Certificate2 certificate, Fleck2Extensions.Action callback, Fleck2Extensions.Action error) { SslStream ssl = new SslStream(_stream, leaveInnerStreamOpen: false); _stream = ssl; Fleck2Extensions.Func beginMethod = (AsyncCallback cb, object s) => ssl.BeginAuthenticateAsServer(certificate, clientCertificateRequired: false, SslProtocols.Tls, checkCertificateRevocation: false, cb, s); _socketFactory.HandleAsyncVoid(beginMethod, ssl.EndAuthenticateAsServer, delegate(SocketResult result) { result.Success(callback); result.Error(error); }); } public void Listen(int backlog) { _socket.Listen(backlog); } public void Bind(EndPoint endPoint) { _socket.Bind(endPoint); } public void Receive(byte[] buffer, Fleck2Extensions.Action callback, Fleck2Extensions.Action error, int offset = 0) { Fleck2Extensions.Func beginMethod = (AsyncCallback cb, object data) => _stream.BeginRead(buffer, offset, buffer.Length, cb, data); _socketFactory.HandleAsync(beginMethod, _stream.EndRead, delegate(SocketResult result) { result.Success(callback); result.Error(error); }); } public void Accept(Fleck2Extensions.Action callback, Fleck2Extensions.Action error) { Fleck2Extensions.Func endMethod = delegate(IAsyncResult result, ICancellationToken token) { token.ThrowIfCancellationRequested(); return new SocketWrapper(_socket.EndAccept(result)); }; _socketFactory.HandleAsync(_socket.BeginAccept, endMethod, delegate(SocketResult result) { result.Success(callback); result.Error(error); }); } public void Dispose() { _socketCancellationToken.Cancel(); if (_stream != null) { _stream.Dispose(); } if (_socket != null) { _socket.Close(); } } public void Close() { _socketCancellationToken.Cancel(); if (_stream != null) { _stream.Close(); } if (_socket != null) { _socket.Close(); } } public int EndSend(IAsyncResult asyncResult) { _stream.EndWrite(asyncResult); return 0; } public void Send(byte[] buffer, Fleck2Extensions.Action callback, Fleck2Extensions.Action error) { Fleck2Extensions.Func beginMethod = (AsyncCallback cb, object s) => _stream.BeginWrite(buffer, 0, buffer.Length, cb, s); _socketFactory.HandleAsyncVoid(beginMethod, _stream.EndWrite, delegate(SocketResult result) { result.Success(callback); result.Error(error); }); } } public class WebSocketConnection : IWebSocketConnection { private readonly Action _initialize; private readonly Fleck2Extensions.Func _handlerFactory; private readonly Fleck2Extensions.Func _parseRequest; private bool _closed; private const int ReadSize = 4096; public ISocket Socket { get; set; } public IHandler Handler { get; set; } public Fleck2Extensions.Action OnOpen { get; set; } public Fleck2Extensions.Action OnClose { get; set; } public Action OnMessage { get; set; } public Action OnBinary { get; set; } public Action OnError { get; set; } public IWebSocketConnectionInfo ConnectionInfo { get; private set; } public WebSocketConnection(ISocket socket, Action initialize, Fleck2Extensions.Func parseRequest, Fleck2Extensions.Func handlerFactory) { Socket = socket; OnOpen = delegate { }; OnClose = delegate { }; OnMessage = delegate { }; OnBinary = delegate { }; OnError = delegate { }; _initialize = initialize; _handlerFactory = handlerFactory; _parseRequest = parseRequest; } public void Send(string message) { if (Handler == null) { throw new InvalidOperationException("Cannot send before handshake"); } if (_closed || !Socket.Connected) { FleckLog.Warn("Data sent after close. Ignoring."); return; } byte[] bytes = Handler.FrameText(message); SendBytes(bytes); } public void Send(byte[] message) { if (Handler == null) { throw new InvalidOperationException("Cannot send before handshake"); } if (_closed || !Socket.Connected) { FleckLog.Warn("Data sent after close. Ignoring."); return; } byte[] bytes = Handler.FrameBinary(message); SendBytes(bytes); } public void StartReceiving() { List data = new List(4096); byte[] buffer = new byte[4096]; Read(data, buffer); } public void Close() { Close(1000); } public void Close(int code) { if (Handler == null) { CloseSocket(); return; } byte[] array = Handler.FrameClose(code); if (array.Length == 0) { CloseSocket(); } else { SendBytes(array, CloseSocket); } } public void CreateHandler(IEnumerable data) { WebSocketHttpRequest webSocketHttpRequest = _parseRequest(data.ToArray()); if (webSocketHttpRequest != null) { Handler = _handlerFactory(webSocketHttpRequest); if (Handler != null) { ConnectionInfo = WebSocketConnectionInfo.Create(webSocketHttpRequest, Socket.RemoteIpAddress, Socket.RemotePort); _initialize(this); byte[] bytes = Handler.CreateHandshake(); SendBytes(bytes, OnOpen); } } } private void Read(List data, byte[] buffer) { if (_closed || !Socket.Connected) { return; } Socket.Receive(buffer, delegate(int r) { if (r <= 0) { FleckLog.Debug("0 bytes read. Closing."); CloseSocket(); } else { FleckLog.Debug(r + " bytes read"); byte[] array = buffer.Take(r).ToArray(); if (Handler != null) { Handler.Receive(array); } else { data.AddRange(array); CreateHandler(data); } Read(data, buffer); } }, HandleReadError); } private void HandleReadError(Exception e) { if (e is ObjectDisposedException) { FleckLog.Debug("Swallowing ObjectDisposedException", e); return; } OnError(e); if (e is HandshakeException) { FleckLog.Debug("Error while reading", e); } else if (e is WebSocketException) { FleckLog.Debug("Error while reading", e); Close(((WebSocketException)e).StatusCode); } else if (e is IOException) { FleckLog.Debug("Error while reading", e); Close(1006); } else { FleckLog.Error("Application Error", e); Close(1011); } } private void SendBytes(byte[] bytes, Fleck2Extensions.Action callback = null) { Socket.Send(bytes, delegate { FleckLog.Debug("Sent " + bytes.Length + " bytes"); if (callback != null) { callback(); } }, delegate(Exception e) { if (e is IOException) { FleckLog.Debug("Failed to send. Disconnecting.", e); } else { FleckLog.Info("Failed to send. Disconnecting.", e); } CloseSocket(); }); } private void CloseSocket() { OnClose(); _closed = true; Socket.Close(); Socket.Dispose(); } } public class WebSocketConnectionInfo : IWebSocketConnectionInfo { private const string CookiePattern = "((;\\s)*(?[^=]+)=(?[^\\;]+))+"; private static readonly Regex CookieRegex = new Regex("((;\\s)*(?[^=]+)=(?[^\\;]+))+"); public string SubProtocol { get; private set; } public string Origin { get; private set; } public string Host { get; private set; } public string Path { get; private set; } public string ClientIpAddress { get; set; } public int ClientPort { get; set; } public Guid Id { get; set; } public IDictionary Cookies { get; private set; } public static WebSocketConnectionInfo Create(WebSocketHttpRequest request, string clientIp, int clientPort) { WebSocketConnectionInfo webSocketConnectionInfo = new WebSocketConnectionInfo { Origin = (request["Origin"] ?? request["Sec-WebSocket-Origin"]), Host = request["Host"], SubProtocol = request["Sec-WebSocket-Protocol"], Path = request.Path, ClientIpAddress = clientIp, ClientPort = clientPort }; string text = request["Cookie"]; if (text != null) { Match match = CookieRegex.Match(text); CaptureCollection captures = match.Groups["cookie_name"].Captures; CaptureCollection captures2 = match.Groups["cookie_value"].Captures; for (int i = 0; i < captures.Count; i++) { string key = captures[i].ToString(); string value = captures2[i].ToString(); webSocketConnectionInfo.Cookies[key] = value; } } return webSocketConnectionInfo; } private WebSocketConnectionInfo() { Cookies = new Dictionary(); Id = Guid.NewGuid(); } } public class WebSocketException : Exception { public ushort StatusCode { get; private set; } public WebSocketException(ushort statusCode) { StatusCode = statusCode; } public WebSocketException(ushort statusCode, string message) : base(message) { StatusCode = statusCode; } public WebSocketException(ushort statusCode, string message, Exception innerException) : base(message, innerException) { StatusCode = statusCode; } } public class WebSocketHttpRequest { private readonly IDictionary _headers = new Dictionary(); public string Method { get; set; } public string Path { get; set; } public string Body { get; set; } public string Scheme { get; set; } public byte[] Bytes { get; set; } public string this[string name] { get { if (!_headers.TryGetValue(name, out var value)) { return null; } return value; } } public IDictionary Headers => _headers; } public class WebSocketServer : IWebSocketServer, IDisposable { private readonly string _scheme; private Action _config; public ISocket ListenerSocket { get; set; } public string Location { get; private set; } public int Port { get; private set; } public X509Certificate2 Certificate { get; set; } public bool IsSecure { get { if (_scheme == "wss") { return Certificate != null; } return false; } } public WebSocketServer(string location) : this(8181, location) { } public WebSocketServer(int port, string location) { Uri uri = new Uri(location); Port = ((uri.Port > 0) ? uri.Port : port); Location = location; _scheme = uri.Scheme; Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); ListenerSocket = new SocketWrapper(socket); } public void Dispose() { ListenerSocket.Dispose(); } public void Start(Action config) { IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, Port); ListenerSocket.Bind(ipLocal); ListenerSocket.Listen(100); FleckLog.Info("Server started at " + Location); if (_scheme == "wss" && Certificate == null) { FleckLog.Error("Scheme cannot be 'wss' without a Certificate"); return; } ListenForClients(); _config = config; } private void ListenForClients() { ListenerSocket.Accept(OnClientConnect, delegate(Exception e) { FleckLog.Error("Listener socket is closed", e); }); } private void OnClientConnect(ISocket clientSocket) { FleckLog.Debug($"Client connected from {clientSocket.RemoteIpAddress}:{clientSocket.RemotePort.ToString(CultureInfo.InvariantCulture)}"); ListenForClients(); WebSocketConnection connection = null; connection = new WebSocketConnection(clientSocket, _config, (byte[] bytes) => RequestParser.Parse(bytes, _scheme), (WebSocketHttpRequest r) => HandlerFactory.BuildHandler(r, delegate(string s) { connection.OnMessage(s); }, connection.Close, delegate(byte[] b) { connection.OnBinary(b); })); if (IsSecure) { FleckLog.Debug("Authenticating Secure Connection"); clientSocket.Authenticate(Certificate, connection.StartReceiving, delegate(Exception e) { FleckLog.Warn("Failed to Authenticate", e); }); } else { connection.StartReceiving(); } } } public static class WebSocketStatusCodes { public const ushort NormalClosure = 1000; public const ushort GoingAway = 1001; public const ushort ProtocolError = 1002; public const ushort UnsupportedDataType = 1003; public const ushort NoStatusReceived = 1005; public const ushort AbnormalClosure = 1006; public const ushort InvalidFramePayloadData = 1007; public const ushort PolicyViolation = 1008; public const ushort MessageTooBig = 1009; public const ushort MandatoryExt = 1010; public const ushort InternalServerError = 1011; public const ushort TlsHandshake = 1015; public const ushort ApplicationError = 3000; public static ushort[] ValidCloseCodes = new ushort[9] { 1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011 }; public static bool Contains(ushort code) { for (int i = 0; i < ValidCloseCodes.Length; i++) { if (ValidCloseCodes[i] == code) { return true; } } return false; } } } namespace Fleck2.Interfaces { public interface ICancellationToken { void ThrowIfCancellationRequested(); } public interface IHandler { byte[] CreateHandshake(); void Receive(IEnumerable data); byte[] FrameText(string text); byte[] FrameBinary(byte[] bytes); byte[] FrameClose(int code); } public interface ISocket { bool Connected { get; } string RemoteIpAddress { get; } int RemotePort { get; } Stream Stream { get; } bool NoDelay { get; set; } void Accept(Fleck2Extensions.Action callback, Fleck2Extensions.Action error); void Send(byte[] buffer, Fleck2Extensions.Action callback, Fleck2Extensions.Action error); void Receive(byte[] buffer, Fleck2Extensions.Action callback, Fleck2Extensions.Action error, int offset = 0); void Authenticate(X509Certificate2 certificate, Fleck2Extensions.Action callback, Fleck2Extensions.Action error); void Dispose(); void Close(); void Bind(EndPoint ipLocal); void Listen(int backlog); } public interface IWebSocketConnection { Fleck2Extensions.Action OnOpen { get; set; } Fleck2Extensions.Action OnClose { get; set; } Action OnMessage { get; set; } Action OnBinary { get; set; } Action OnError { get; set; } IWebSocketConnectionInfo ConnectionInfo { get; } void Send(string message); void Send(byte[] message); void Close(); } public interface IWebSocketConnectionInfo { string SubProtocol { get; } string Origin { get; } string Host { get; } string Path { get; } string ClientIpAddress { get; } int ClientPort { get; } IDictionary Cookies { get; } Guid Id { get; } } public interface IWebSocketServer : IDisposable { void Start(Action config); } } namespace Fleck2.Handlers { public class ComposableHandler : IHandler { public Fleck2Extensions.Func Handshake = () => new byte[0]; public Fleck2Extensions.Func TextFrame = (string x) => new byte[0]; public Fleck2Extensions.Func BinaryFrame = (byte[] x) => new byte[0]; public Action> ReceiveData = delegate { }; public Fleck2Extensions.Func CloseFrame = (int i) => new byte[0]; private readonly List _data = new List(); public byte[] CreateHandshake() { return Handshake(); } public void Receive(IEnumerable data) { _data.AddRange(data); ReceiveData(_data); } public byte[] FrameText(string text) { return TextFrame(text); } public byte[] FrameBinary(byte[] bytes) { return BinaryFrame(bytes); } public byte[] FrameClose(int code) { return CloseFrame(code); } } public static class Draft76Handler { private const byte End = byte.MaxValue; private const byte Start = 0; private const int MaxSize = 5242880; public static IHandler Create(WebSocketHttpRequest request, Action onMessage) { return new ComposableHandler { TextFrame = FrameText, Handshake = () => Handshake(request), ReceiveData = delegate(List data) { ReceiveData(onMessage, data); } }; } public static void ReceiveData(Action onMessage, List data) { while (data.Count > 0) { if (data[0] != 0) { throw new WebSocketException(1007); } int num = data.IndexOf(byte.MaxValue); if (num < 0) { break; } if (num > 5242880) { throw new WebSocketException(1009); } byte[] bytes = data.Skip(1).Take(num - 1).ToArray(); data.RemoveRange(0, num + 1); string obj = Encoding.UTF8.GetString(bytes); onMessage(obj); } } public static byte[] FrameText(string data) { byte[] bytes = Encoding.UTF8.GetBytes(data); byte[] array = new byte[bytes.Length + 2]; array[0] = 0; array[^1] = byte.MaxValue; Array.Copy(bytes, 0, array, 1, bytes.Length); return array; } public static byte[] Handshake(WebSocketHttpRequest request) { FleckLog.Debug("Building Draft76 Response"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("HTTP/1.1 101 WebSocket Protocol Handshake\r\n"); stringBuilder.Append("Upgrade: WebSocket\r\n"); stringBuilder.Append("Connection: Upgrade\r\n"); stringBuilder.AppendFormat("Sec-WebSocket-Origin: {0}\r\n", request["Origin"]); stringBuilder.AppendFormat("Sec-WebSocket-Location: {0}://{1}{2}\r\n", request.Scheme, request["Host"], request.Path); if (request.Headers.ContainsKey("Sec-WebSocket-Protocol")) { stringBuilder.AppendFormat("Sec-WebSocket-Protocol: {0}\r\n", request["Sec-WebSocket-Protocol"]); } stringBuilder.Append("\r\n"); string key = request["Sec-WebSocket-Key1"]; string key2 = request["Sec-WebSocket-Key2"]; ArraySegment challenge = new ArraySegment(request.Bytes, request.Bytes.Length - 8, 8); byte[] array = CalculateAnswerBytes(key, key2, challenge); byte[] array2 = Encoding.ASCII.GetBytes(stringBuilder.ToString()); int num = array2.Length; Array.Resize(ref array2, num + array.Length); Array.Copy(array, 0, array2, num, array.Length); return array2; } public static byte[] CalculateAnswerBytes(string key1, string key2, ArraySegment challenge) { byte[] sourceArray = ParseKey(key1); byte[] sourceArray2 = ParseKey(key2); byte[] array = new byte[16]; Array.Copy(sourceArray, 0, array, 0, 4); Array.Copy(sourceArray2, 0, array, 4, 4); Array.Copy(challenge.Array, challenge.Offset, array, 8, 8); return MD5.Create().ComputeHash(array); } private static byte[] ParseKey(string key) { int num = 0; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < key.Length; i++) { if (char.IsWhiteSpace(key[i])) { num++; } if (char.IsDigit(key[i])) { stringBuilder.Append(key[i]); } } byte[] bytes = BitConverter.GetBytes((int)(long.Parse(stringBuilder.ToString()) / num)); if (BitConverter.IsLittleEndian) { Array.Reverse((Array)bytes); } return bytes; } } public static class Hybi13Handler { private const string WebSocketResponseGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static IHandler Create(WebSocketHttpRequest request, Action onMessage, Fleck2Extensions.Action onClose, Action onBinary) { ReadState readState = new ReadState(); return new ComposableHandler { Handshake = () => BuildHandshake(request), TextFrame = (string data) => FrameData(Encoding.UTF8.GetBytes(data), FrameType.Text), BinaryFrame = (byte[] data) => FrameData(data, FrameType.Binary), CloseFrame = (int i) => FrameData(i.ToBigEndianBytes(), FrameType.Close), ReceiveData = delegate(List bytes) { ReceiveData(bytes, readState, delegate(FrameType op, byte[] data) { ProcessFrame(op, data, onMessage, onClose, onBinary); }); } }; } public static byte[] FrameData(byte[] payload, FrameType frameType) { MemoryStream memoryStream = new MemoryStream(); byte value = (byte)(frameType + 128); memoryStream.WriteByte(value); if (payload.Length > 65535) { memoryStream.WriteByte(127); byte[] array = payload.Length.ToBigEndianBytes(); memoryStream.Write(array, 0, array.Length); } else if (payload.Length > 125) { memoryStream.WriteByte(126); byte[] array2 = payload.Length.ToBigEndianBytes(); memoryStream.Write(array2, 0, array2.Length); } else { memoryStream.WriteByte((byte)payload.Length); } memoryStream.Write(payload, 0, payload.Length); return memoryStream.ToArray(); } public static void ReceiveData(List data, ReadState readState, Fleck2Extensions.Action processFrame) { while (data.Count >= 2) { bool flag = (data[0] & 0x80) != 0; int num = data[0] & 0x70; FrameType frameType = (FrameType)(data[0] & 0xF); bool num2 = (data[1] & 0x80) != 0; int num3 = data[1] & 0x7F; if (!num2 || !Enum.IsDefined(typeof(FrameType), frameType) || num != 0 || (frameType == FrameType.Continuation && !readState.FrameType.HasValue)) { throw new WebSocketException(1002); } int num4 = 2; int num5; switch (num3) { case 127: if (data.Count < num4 + 8) { return; } num5 = data.Skip(num4).Take(8).ToArray() .ToLittleEndianInt(); num4 += 8; break; case 126: if (data.Count < num4 + 2) { return; } num5 = data.Skip(num4).Take(2).ToArray() .ToLittleEndianInt(); num4 += 2; break; default: num5 = num3; break; } if (data.Count < num4 + 4) { break; } List maskBytes = data.Skip(num4).Take(4).ToList(); num4 += 4; if (data.Count < num4 + num5) { break; } int i = 0; IEnumerable collection = from value in data.Skip(num4).Take(num5) select (byte)(value ^ maskBytes[i++ % 4]); readState.Data.AddRange(collection); data.RemoveRange(0, num4 + num5); if (frameType != FrameType.Continuation) { readState.FrameType = frameType; } if (flag && readState.FrameType.HasValue) { byte[] u = readState.Data.ToArray(); FrameType? frameType2 = readState.FrameType; readState.Clear(); processFrame(frameType2.Value, u); } } } public static void ProcessFrame(FrameType frameType, byte[] data, Action onMessage, Fleck2Extensions.Action onClose, Action onBinary) { switch (frameType) { case FrameType.Close: if (data.Length == 1 || data.Length > 125) { throw new WebSocketException(1002); } if (data.Length >= 2) { ushort num = (ushort)data.Take(2).ToArray().ToLittleEndianInt(); if (!WebSocketStatusCodes.Contains(num) && (num < 3000 || num > 4999)) { throw new WebSocketException(1002); } } if (data.Length > 2) { ReadUtf8PayloadData(data.Skip(2)); } onClose(); break; case FrameType.Binary: onBinary(data); break; case FrameType.Text: onMessage(ReadUtf8PayloadData(data)); break; default: FleckLog.Debug("Received unhandled " + frameType); break; } } public static byte[] BuildHandshake(WebSocketHttpRequest request) { FleckLog.Debug("Building Hybi-14 Response"); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("HTTP/1.1 101 Switching Protocols\r\n"); stringBuilder.Append("Upgrade: websocket\r\n"); stringBuilder.Append("Connection: Upgrade\r\n"); string arg = CreateResponseKey(request["Sec-WebSocket-Key"]); stringBuilder.AppendFormat("Sec-WebSocket-Accept: {0}\r\n", arg); stringBuilder.Append("\r\n"); return Encoding.ASCII.GetBytes(stringBuilder.ToString()); } public static string CreateResponseKey(string requestKey) { string s = requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(s))); } private static string ReadUtf8PayloadData(byte[] bytes) { UTF8Encoding uTF8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); try { return uTF8Encoding.GetString(bytes); } catch (ArgumentException) { throw new WebSocketException(1007); } } } } namespace H3Status { internal static class Config { public static ConfigEntry ServerEnabled; public static ConfigEntry ServerPort; public static ConfigEntry LogScoreEvents; public static ConfigEntry SceneEvent; public static ConfigEntry AmmoEvent; public static ConfigEntry HealthEvent; public static ConfigEntry BuffEvent; public static ConfigEntry TNHLevelEvent; public static ConfigEntry TNHPhaseEvent; public static ConfigEntry TNHHoldPhaseEvent; public static ConfigEntry TNHScoreEvent; public static ConfigEntry TNHEncryptionDestroyed; public static ConfigEntry TNHTokenEvent; private static ConfigFile _configFile; private static ConfigEntry Bind(this ConfigFile config, string section, string key, T defaultValue, Action callback) { ConfigEntry entry = config.Bind(section, key, defaultValue, (ConfigDescription)null); entry.SettingChanged += delegate { callback(entry.Value); }; return entry; } public static void Init(ConfigFile config) { _configFile = config; string text = "1. General"; string text2 = "2. Event Types"; ServerEnabled = config.Bind(text, "Server Enabled", defaultValue: true, OnServerEnabledChanged); ServerPort = config.Bind(text, "Server Port", 9504, OnServerPortChanged); LogScoreEvents = config.Bind(text, "Log Score Events", false, (ConfigDescription)null); SceneEvent = config.Bind(text2, "Scene Changed", true, (ConfigDescription)null); AmmoEvent = config.Bind(text2, "Score Changed", true, (ConfigDescription)null); HealthEvent = config.Bind(text2, "Health Changed", true, (ConfigDescription)null); BuffEvent = config.Bind(text2, "Powerup Activated", true, (ConfigDescription)null); TNHLevelEvent = config.Bind(text2, "T&H Level Started", true, (ConfigDescription)null); TNHPhaseEvent = config.Bind(text2, "T&H Phase Changed", true, (ConfigDescription)null); TNHHoldPhaseEvent = config.Bind(text2, "T&H Hold Phase Changed", true, (ConfigDescription)null); TNHScoreEvent = config.Bind(text2, "T&H Score Changed", true, (ConfigDescription)null); TNHEncryptionDestroyed = config.Bind(text2, "T&H Encryption Destroyed", true, (ConfigDescription)null); TNHTokenEvent = config.Bind(text2, "T&H Tokens Changed", true, (ConfigDescription)null); } private static void OnServerEnabledChanged(bool enabled) { if (enabled) { Plugin.Start(); } else { Plugin.Stop(); } } private static void OnServerPortChanged(int port) { Server.Stop(); Server.Start(port); } } [BepInProcess("h3vr.exe")] [BepInPlugin("xyz.bacur.plugins.h3status", "H3Status", "0.5.1")] public class Plugin : BaseUnityPlugin { public const string Guid = "xyz.bacur.plugins.h3status"; public const string Name = "H3Status"; public const string Version = "0.5.1"; internal static ManualLogSource Logger; internal static Harmony Patcher; protected void Awake() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown Logger = ((BaseUnityPlugin)this).Logger; Patcher = new Harmony("xyz.bacur.plugins.h3status"); Config.Init(((BaseUnityPlugin)this).Config); if (!Config.ServerEnabled.Value) { Logger.LogWarning((object)"Server disabled in config"); } else { Start(); } } protected void OnDestroy() { Stop(); } internal static void Start() { Server.Start(Config.ServerPort.Value); Harmony patcher = Patcher; if (patcher != null) { patcher.PatchAll(typeof(SceneHandler)); } Harmony patcher2 = Patcher; if (patcher2 != null) { patcher2.PatchAll(typeof(TNHScoreHandler)); } Harmony patcher3 = Patcher; if (patcher3 != null) { patcher3.PatchAll(typeof(TNHPhaseHandler)); } Harmony patcher4 = Patcher; if (patcher4 != null) { patcher4.PatchAll(typeof(PlayerHealthHandler)); } Harmony patcher5 = Patcher; if (patcher5 != null) { patcher5.PatchAll(typeof(WeaponAmmoHandler)); } } internal static void Stop() { Server.Stop(); Harmony patcher = Patcher; if (patcher != null) { patcher.UnpatchSelf(); } } } internal static class Server { private static WebSocketServer _server; private static readonly List _instances = new List(); private static readonly JsonSerializerSettings _jsonSettings = new JsonSerializerSettings { ContractResolver = (IContractResolver)new CamelCasePropertyNamesContractResolver(), Converters = new List(1) { (JsonConverter)new StringEnumConverter(false) } }; public static void Start(int port) { if (_server != null) { return; } FleckLog.Level = LogLevel.Warn; _server = new WebSocketServer($"ws://0.0.0.0:{port}"); _server.Start(delegate(IWebSocketConnection instance) { instance.OnOpen = delegate { _instances.Add(instance); instance.Send(JsonConvert.SerializeObject((object)new Event { Type = EventType.Hello, Status = VersionHandler.GetVersionInfo() }, _jsonSettings)); }; instance.OnClose = delegate { _instances.Remove(instance); }; }); Plugin.Logger.LogInfo((object)$"Server started on port {port}"); } public static void Stop() { if (_server == null) { return; } Plugin.Logger.LogInfo((object)"Server shutting down"); foreach (IWebSocketConnection instance in _instances) { instance.Close(); } _server.Dispose(); _server = null; } public static void SendMessage(Event evt) { string message = JsonConvert.SerializeObject((object)evt, _jsonSettings); foreach (IWebSocketConnection instance in _instances) { instance.Send(message); } } } } namespace H3Status.Utils { internal static class AmmoReader { public static void GetAmmo(string path) { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogInfo((object)"WRITING FILE"); StreamWriter streamWriter = new StreamWriter(path, append: false); try { ManagerSingleton.Instance.GenerateFireArmRoundDictionaries(); } catch { Plugin.Logger.LogInfo((object)"TypeDict already generated"); } foreach (KeyValuePair> item in ManagerSingleton.Instance.TypeDic) { foreach (KeyValuePair item2 in item.Value) { string text = ((object)item.Key/*cast due to .constrained prefix*/).ToString() + "," + ((object)item2.Key/*cast due to .constrained prefix*/).ToString() + "," + ((Object)item2.Value.Mesh).name; Plugin.Logger.LogInfo((object)text); streamWriter.WriteLine(text); } } streamWriter.Flush(); streamWriter.Close(); Plugin.Logger.LogInfo((object)"DONE"); } public unsafe static void GetShells(string path) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) Plugin.Logger.LogInfo((object)"WRITING FILE"); StreamWriter streamWriter = new StreamWriter(path, append: false); foreach (FireArmRoundType type in ManagerSingleton.Instance.TypeList) { FVRFireArmRound component = ((AnvilAsset)AM.GetRoundSelfPrefab(type, AM.GetDefaultRoundClass(type))).GetGameObject().GetComponent(); if ((Object)(object)component != (Object)null) { component.Fire(); if ((Object)(object)component != (Object)null && (Object)(object)component.FiredRenderer != (Object)null) { string text = ((object)(*(FireArmRoundType*)(&type))/*cast due to .constrained prefix*/).ToString() + "," + ((Object)((Component)component.FiredRenderer).gameObject.GetComponent().sharedMesh).name; Plugin.Logger.LogInfo((object)text); streamWriter.WriteLine(text); } } } streamWriter.Flush(); streamWriter.Close(); Plugin.Logger.LogInfo((object)"DONE"); } } internal static class WeaponReader { public static void GetWeapons(string path, ItemSpawnerV2 spawner) { //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_024f: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_027b: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Unknown result type (might be due to invalid IL or missing references) //IL_0330: Unknown result type (might be due to invalid IL or missing references) List list = ManagerSingleton.Instance.odicTagCategory[(ObjectCategory)1]; int count = list.Count; for (int i = 0; i < list.Count; i++) { bool flag = false; FVRObject val = list[i]; if (val.OSple && val.SpawnedFromId != null && !(val.SpawnedFromId == string.Empty) && IM.HasSpawnedID(val.SpawnedFromId) && IM.GetSpawnerID(val.SpawnedFromId).IsDisplayedInMainEntry) { flag = true; } if (!flag) { list.RemoveAt(i--); } } Plugin.Logger.LogInfo((object)$"Found {list.Count} guns ({count - list.Count} excluded)"); Plugin.Logger.LogInfo((object)"WRITING FILES"); List list2 = new List { "Cube", "Sphere", "Capsule", "Quad" }; foreach (FVRObject item in list) { string itemID = IM.GetSpawnerID(item.SpawnedFromId).ItemID; Plugin.Logger.LogInfo((object)itemID); GameObject val2 = Object.Instantiate(((AnvilAsset)item).GetGameObject(), Vector3.zero, Quaternion.identity); val2.SetActive(false); StreamWriter streamWriter = new StreamWriter(path + "/" + itemID + ".csv", append: false); MeshFilter[] componentsInChildren = val2.GetComponentsInChildren(false); foreach (MeshFilter val3 in componentsInChildren) { if ((Object)(object)val3.sharedMesh != (Object)null) { string name = ((Object)val3.sharedMesh).name; if (!list2.Contains(name)) { string value = ((Object)val3.sharedMesh).name + "," + ((Component)val3).transform.position.x.ToString("F6") + "," + ((Component)val3).transform.position.y.ToString("F6") + "," + ((Component)val3).transform.position.z.ToString("F6") + "," + ((Component)val3).transform.rotation.x.ToString("F6") + "," + ((Component)val3).transform.rotation.y.ToString("F6") + "," + ((Component)val3).transform.rotation.z.ToString("F6") + "," + ((Component)val3).transform.localScale.x.ToString("F6") + "," + ((Component)val3).transform.localScale.y.ToString("F6") + "," + ((Component)val3).transform.localScale.z.ToString("F6"); streamWriter.WriteLine(value); } } } Object.Destroy((Object)(object)val2); streamWriter.Flush(); streamWriter.Close(); } Plugin.Logger.LogInfo((object)"DONE"); } } } namespace H3Status.Patches { internal static class VersionHandler { private static VersionStatus versionStatus; public static VersionStatus GetVersionInfo() { versionStatus.Version = "0.5.1"; versionStatus.GameVersion = $"{GM.Version_UpdateNumber}.{GM.Version_AlphaNumber}.{GM.Version_PatchNumber}"; return versionStatus; } } [HarmonyPatch] internal static class SceneHandler { public static string activeScene = string.Empty; private static SceneStatus sceneStatus = default(SceneStatus); [HarmonyPostfix] [HarmonyPatch(typeof(SteamVR_LoadLevel), "Begin")] private static void SceneEvent(string levelName) { if (Config.SceneEvent.Value) { activeScene = levelName; sceneStatus = new SceneStatus { Name = levelName }; Server.SendMessage(new Event { Type = EventType.SceneEvent, Status = sceneStatus }); } } } [HarmonyPatch] internal static class TNHPhaseHandler { private static readonly string[] holdNamesInstitution = new string[20] { "HUB", "LIBRARY", "GARDEN", "ATRIUM", "LOBBY", "HEDRONS", "TURBINE", "HYDRO", "SPILLWAY", "RODS", "STORAGE", "APRROACH", "CROSSOVER", "PIPEWORKS", "VOID", "CONCOURSE", "BUNKER", "INCLINATOR", "ABYSS", "SUBSTATION" }; private static readonly string[] supplyNamesInstitution = new string[16] { "ARRAY", "STUDIO", "SUITE", "LOFT", "PENTHOUSE", "GREENWALL", "FENESTRA", "JUDGEMENT", "PRESIDIO", "DISSONANCE", "CLERESTORY", "HELIX", "FACILITY", "STACKS", "ALTAR", "PUMP" }; private static bool isInitialized = false; private static TNHLevelStatus levelStatus = default(TNHLevelStatus); private static TNHPhaseStatus phaseStatus = default(TNHPhaseStatus); private static TNHHoldPhaseStatus holdPhaseStatus = default(TNHHoldPhaseStatus); [HarmonyPrefix] [HarmonyPatch(typeof(TNH_Manager), "DelayedInit")] private static void TNHLevelEventPre(TNH_Manager __instance) { isInitialized = __instance.m_hasInit; } [HarmonyPostfix] [HarmonyPatch(typeof(TNH_Manager), "DelayedInit")] private static void TNHLevelEventPost(TNH_Manager __instance) { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: 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) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) if (Config.TNHLevelEvent.Value && !isInitialized && __instance.m_hasInit) { levelStatus = new TNHLevelStatus { Seed = __instance.HoldSequenceSeed, EquipmentSeed = __instance.equipmentSeed, LevelName = __instance.LevelName, CharacterName = __instance.C.DisplayName, ScoreMultiplier = TNHScoreHandler.GetMultiplier(), AiDifficulty = __instance.AI_Difficulty, RadarMode = __instance.RadarMode, TargetMode = __instance.TargetMode, HealthMode = __instance.HealthMode, EquipmentMode = __instance.EquipmentMode }; Server.SendMessage(new Event { Type = EventType.TNHLevelEvent, Status = levelStatus }); } } [HarmonyPostfix] [HarmonyPatch(typeof(TNH_Manager), "SetPhase")] private static void TNHPhaseEvent(TNH_Phase p, TNH_Manager __instance) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) if (!Config.TNHPhaseEvent.Value) { return; } phaseStatus = new TNHPhaseStatus { Phase = p, Level = __instance.m_level, Count = __instance.m_maxLevels, Seed = __instance.m_holdSequenceSeed, Hold = __instance.m_curHoldIndex, Supply = new List(), HoldName = null, SupplyNames = null }; foreach (int activeSupplyPointIndicy in __instance.m_activeSupplyPointIndicies) { phaseStatus.Supply.Add(activeSupplyPointIndicy); } if (SceneHandler.activeScene == "Institution") { phaseStatus.HoldName = holdNamesInstitution[__instance.m_curHoldIndex]; phaseStatus.SupplyNames = new List(); foreach (int activeSupplyPointIndicy2 in __instance.m_activeSupplyPointIndicies) { phaseStatus.SupplyNames.Add(supplyNamesInstitution[activeSupplyPointIndicy2]); } } Server.SendMessage(new Event { Type = EventType.TNHPhaseEvent, Status = phaseStatus }); } [HarmonyPostfix] [HarmonyPatch(typeof(TNH_HoldPoint), "BeginAnalyzing")] [HarmonyPatch(typeof(TNH_HoldPoint), "IdentifyEncryption")] [HarmonyPatch(typeof(TNH_HoldPoint), "CompletePhase")] private static void TNHHoldPhaseEvent(TNH_HoldPoint __instance) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) if (Config.TNHHoldPhaseEvent.Value) { holdPhaseStatus = new TNHHoldPhaseStatus { Phase = __instance.m_state, Level = __instance.m_phaseIndex, Count = __instance.H.Phases.Count, EncryptionType = __instance.m_curPhase.Encryption, EncryptionCount = __instance.m_numTargsToSpawn, EncryptionTime = 120f }; Server.SendMessage(new Event { Type = EventType.TNHHoldPhaseEvent, Status = holdPhaseStatus }); } } } [HarmonyPatch] internal static class TNHScoreHandler { private static TNHScoreStatus scoreStatus = default(TNHScoreStatus); private static TNHTokenStatus tokenStatus = default(TNHTokenStatus); private static readonly int[] eventMultiplier = new int[15] { 12000, 12, 1, 12, 300, 100, 100, 100, 100, 1, 250, 12, 1, 1, 250 }; private static readonly bool[] eventIsCounted = new bool[15] { true, true, false, true, true, true, true, false, false, false, false, true, false, false, false }; private static int GetEventScore(ScoringEvent ev, int num) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) return num * eventMultiplier[ev]; } private static int GetTotalScore() { int num = 0; int multiplier = GetMultiplier(); for (int i = 0; i <= 14; i++) { if (eventIsCounted[i]) { num += GetEventScore((ScoringEvent)i, GM.TNH_Manager.Nums[i]); } } return num * multiplier; } internal static int GetMultiplier() { //IL_0007: 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: Invalid comparison between Unknown and I4 //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Invalid comparison between Unknown and I4 int num = 1; if ((int)GM.TNHOptions.TargetModeSetting == 0) { num += 3; } else if ((int)GM.TNHOptions.TargetModeSetting == 1) { num += 2; } if ((int)GM.TNHOptions.AIDifficultyModifier == 0) { num += 3; } if ((int)GM.TNHOptions.RadarModeModifier == 0) { num += 2; } else if ((int)GM.TNHOptions.RadarModeModifier != 1) { num += 3; } return num; } [HarmonyPostfix] [HarmonyPatch(typeof(TNH_Manager), "IncrementScoringStat")] private static void TNHScoreEvent(ScoringEvent ev, int num, TNH_Manager __instance) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) if (eventIsCounted[ev]) { if (Config.LogScoreEvents.Value) { Plugin.Logger.LogMessage((object)$"{ev}: {GetEventScore(ev, num) * GetMultiplier()} ({GetEventScore(ev, num)}x{GetMultiplier()})"); } if (Config.TNHScoreEvent.Value) { scoreStatus = new TNHScoreStatus { Type = ev, Value = GetEventScore(ev, num), Mult = GetMultiplier(), Score = GetTotalScore() }; Server.SendMessage(new Event { Type = EventType.TNHScoreEvent, Status = scoreStatus }); } } } [HarmonyPostfix] [HarmonyPatch(typeof(TNH_HoldPoint), "TargetDestroyed")] private static void TNHEncryptionDestroyed() { if (Config.TNHEncryptionDestroyed.Value) { Server.SendMessage(new Event { Type = EventType.TNHEncryptionDestroyed }); } } [HarmonyPostfix] [HarmonyPatch(typeof(TNH_Manager), "AddTokens")] private static void TNHTokenEventAdd(int i, TNH_Manager __instance) { if (Config.TNHTokenEvent.Value) { tokenStatus = new TNHTokenStatus { Change = i, Tokens = __instance.m_numTokens }; Server.SendMessage(new Event { Type = EventType.TNHTokenEvent, Status = tokenStatus }); } } [HarmonyPostfix] [HarmonyPatch(typeof(TNH_Manager), "SubtractTokens")] private static void TNHTokenEventSubtract(int i, TNH_Manager __instance) { if (Config.TNHTokenEvent.Value) { tokenStatus = new TNHTokenStatus { Change = -i, Tokens = __instance.m_numTokens }; Server.SendMessage(new Event { Type = EventType.TNHTokenEvent, Status = tokenStatus }); } } } [HarmonyPatch] internal static class PlayerHealthHandler { private static HealthStatus healthStatus; private static BuffStatus buffStatus; [HarmonyPostfix] [HarmonyPatch(typeof(FVRPlayerBody), "RegisterPlayerHit")] private static void HealthEventHit(float DamagePoints, bool FromSelf, int iff, FVRPlayerBody __instance) { if (Config.HealthEvent.Value) { healthStatus = new HealthStatus { Change = -(int)DamagePoints, Health = (int)__instance.Health, MaxHealth = (int)__instance.m_startingHealth }; Server.SendMessage(new Event { Type = EventType.HealthEvent, Status = healthStatus }); } } [HarmonyPostfix] [HarmonyPatch(typeof(FVRPlayerBody), "HarmPercent")] private static void HealthEventHarm(float f, FVRPlayerBody __instance) { if (Config.HealthEvent.Value) { healthStatus = new HealthStatus { Change = -(int)(__instance.m_startingHealth * f), Health = (int)__instance.Health, MaxHealth = (int)__instance.m_startingHealth }; Server.SendMessage(new Event { Type = EventType.HealthEvent, Status = healthStatus }); } } [HarmonyPostfix] [HarmonyPatch(typeof(FVRPlayerBody), "Init")] private static void HealthEventInit(FVRPlayerBody __instance) { if (Config.HealthEvent.Value) { healthStatus = new HealthStatus { Change = 0f, Health = (int)__instance.Health, MaxHealth = (int)__instance.m_startingHealth }; Server.SendMessage(new Event { Type = EventType.HealthEvent, Status = healthStatus }); } } [HarmonyPrefix] [HarmonyPatch(typeof(FVRPlayerBody), "SetHealthThreshold")] private static void HealthEventUpdate(float h, FVRPlayerBody __instance) { if (Config.HealthEvent.Value) { healthStatus = new HealthStatus { Change = (int)(h - __instance.Health), Health = (int)h, MaxHealth = (int)h }; Server.SendMessage(new Event { Type = EventType.HealthEvent, Status = healthStatus }); } } [HarmonyPostfix] [HarmonyPatch(typeof(FVRPlayerBody), "HealPercent")] private static void HealthEventHeal(float f, FVRPlayerBody __instance) { if (Config.HealthEvent.Value) { healthStatus = new HealthStatus { Change = (int)(__instance.m_startingHealth * f), Health = (int)__instance.Health, MaxHealth = (int)__instance.m_startingHealth }; Server.SendMessage(new Event { Type = EventType.HealthEvent, Status = healthStatus }); } } [HarmonyPostfix] [HarmonyPatch(typeof(FVRPlayerBody), "ActivatePower")] private static void BuffEvent(PowerupType type, PowerUpIntensity intensity, PowerUpDuration d, bool isPuke, bool isInverted, float DurationOverride = -1f) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected I4, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) if (Config.BuffEvent.Value) { float duration = 1f; switch ((int)d) { case 0: duration = 30f; break; case 1: duration = 20f; break; case 2: duration = 10f; break; case 3: duration = 2f; break; case 4: duration = 40f; break; } if (DurationOverride > 0f) { duration = DurationOverride; } buffStatus = new BuffStatus { Type = type, Duration = duration, Inverted = isInverted }; Server.SendMessage(new Event { Type = EventType.BuffEvent, Status = buffStatus }); } } } [HarmonyPatch] internal static class WeaponAmmoHandler { private static bool isUpdatePending; private static AmmoStatus ammoStatus; [HarmonyPrefix] [HarmonyPatch(typeof(FVRFireArm), "FVRFixedUpdate")] private static void HandlePendingEvent() { if (Config.AmmoEvent.Value && isUpdatePending) { isUpdatePending = false; Server.SendMessage(new Event { Type = EventType.AmmoEvent, Status = ammoStatus }); } } private static void UpdateAmmoCount(FVRFireArm fireArm) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0184: 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) if ((Object)(object)fireArm == (Object)null || (Object)(object)((FVRInteractiveObject)fireArm).m_hand == (Object)null) { return; } string weapon = string.Empty; int hand = (((FVRInteractiveObject)fireArm).m_hand.IsThisTheRightHand ? 1 : 0); FireArmRoundType roundType = fireArm.RoundType; FireArmRoundClass roundClass = (FireArmRoundClass)0; int num = 0; int num2 = 0; int num3 = 0; try { roundClass = AM.GetDefaultRoundClass(fireArm.RoundType); } catch { } if ((Object)(object)((FVRPhysicalObject)fireArm).ObjectWrapper != (Object)null) { weapon = ((!IM.HasSpawnedID(((FVRPhysicalObject)fireArm).ObjectWrapper.ItemID)) ? ((FVRPhysicalObject)fireArm).ObjectWrapper.DisplayName : IM.GetSpawnerID(((FVRPhysicalObject)fireArm).ObjectWrapper.ItemID).DisplayName); } if ((Object)(object)fireArm.Magazine != (Object)null) { num3 += fireArm.Magazine.m_capacity; num += fireArm.Magazine.m_numRounds; if (fireArm.Magazine.LoadedRounds != null) { for (int i = 0; i < fireArm.Magazine.LoadedRounds.Length; i++) { if (fireArm.Magazine.LoadedRounds[i] != null) { roundClass = fireArm.Magazine.LoadedRounds[i].LR_Class; } } } } if ((Object)(object)fireArm.BeltDD != (Object)null) { num += fireArm.BeltDD.m_roundsOnBelt; } if (fireArm.FChambers != null) { num3 += fireArm.FChambers.Count; foreach (FVRFireArmChamber fChamber in fireArm.FChambers) { if (!((Object)(object)fChamber.m_round == (Object)null)) { if (fChamber.IsSpent) { num2++; continue; } roundClass = fChamber.m_round.RoundClass; num++; } } } ammoStatus = new AmmoStatus { Weapon = weapon, Hand = hand, RoundType = roundType, RoundClass = roundClass, Current = num, Spent = num2, Capacity = num3 }; isUpdatePending = true; } [HarmonyPostfix] [HarmonyPatch(typeof(FVRFireArmMagazine), "AddRound", new Type[] { typeof(FireArmRoundClass), typeof(bool), typeof(bool) })] [HarmonyPatch(typeof(FVRFireArmMagazine), "AddRound", new Type[] { typeof(FVRFireArmRound), typeof(bool), typeof(bool), typeof(bool) })] private static void AmmoEventMagazine(FVRFireArmMagazine __instance) { if (Config.AmmoEvent.Value) { UpdateAmmoCount(__instance.FireArm); } } [HarmonyPostfix] [HarmonyPatch(typeof(FVRFireArm), "LoadMag")] [HarmonyPatch(typeof(FVRFireArm), "EjectMag")] private static void AmmoEventFireArm(FVRFireArm __instance) { if (Config.AmmoEvent.Value) { UpdateAmmoCount(__instance); } } [HarmonyPostfix] [HarmonyPatch(typeof(FVRFireArmChamber), "UpdateProxyDisplay")] private static void AmmoEventChamber(FVRFireArmChamber __instance) { if (!Config.AmmoEvent.Value) { return; } if ((Object)(object)__instance.Firearm != (Object)null) { UpdateAmmoCount(__instance.Firearm); return; } Transform parent = ((Component)__instance).transform.parent; FVRFireArm val = ((parent != null) ? ((Component)parent).gameObject.GetComponent() : null); if ((Object)(object)val != (Object)null) { UpdateAmmoCount(val); } } } } namespace H3Status.Model { internal struct Event { public EventType Type { get; set; } public object? Status { get; set; } } [JsonConverter(typeof(StringEnumConverter), new object[] { true })] internal enum EventType { Hello, SceneEvent, AmmoEvent, HealthEvent, BuffEvent, TNHLevelEvent, TNHPhaseEvent, TNHHoldPhaseEvent, TNHScoreEvent, TNHEncryptionDestroyed, TNHTokenEvent } internal struct VersionStatus { public string Version { get; set; } public string GameVersion { get; set; } } internal struct SceneStatus { public string Name { get; set; } } internal struct AmmoStatus { public string Weapon { get; set; } public FireArmRoundType RoundType { get; set; } public FireArmRoundClass RoundClass { get; set; } public int Hand { get; set; } public int Current { get; set; } public int Spent { get; set; } public int Capacity { get; set; } } internal struct HealthStatus { public float Change { get; set; } public float Health { get; set; } public float MaxHealth { get; set; } } internal struct BuffStatus { public PowerupType Type { get; set; } public float Duration { get; set; } public bool Inverted { get; set; } } internal struct TNHLevelStatus { public int Seed { get; set; } public int EquipmentSeed { get; set; } public string LevelName { get; set; } public string CharacterName { get; set; } public int ScoreMultiplier { get; set; } public TNHModifier_AIDifficulty AiDifficulty { get; set; } public TNHModifier_RadarMode RadarMode { get; set; } public TNHSetting_TargetMode TargetMode { get; set; } public TNHSetting_HealthMode HealthMode { get; set; } public TNHSetting_EquipmentMode EquipmentMode { get; set; } } internal struct TNHPhaseStatus { public TNH_Phase Phase { get; set; } public int Level { get; set; } public int Count { get; set; } public int Seed { get; set; } public int Hold { get; set; } public List Supply { get; set; } public string? HoldName { get; set; } public List? SupplyNames { get; set; } } internal struct TNHHoldPhaseStatus { public HoldState Phase { get; set; } public int Level { get; set; } public int Count { get; set; } public TNH_EncryptionType EncryptionType { get; set; } public int EncryptionCount { get; set; } public float EncryptionTime { get; set; } } internal struct TNHScoreStatus { public ScoringEvent Type { get; set; } public int Value { get; set; } public int Mult { get; set; } public int Score { get; set; } } internal struct TNHTokenStatus { public int Change { get; set; } public int Tokens { get; set; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, Inherited = false, AllowMultiple = false)] public class ExtensionAttribute : Attribute { } }