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 LethalLEDSign v1.0.1
Fleck.dll
Decompiled 2 years agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Fleck.Handlers; using Fleck.Helpers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.5", FrameworkDisplayName = ".NET Framework 4.5")] [assembly: AssemblyCompany("statenjason")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright Jason Staten 2010-2018. All rights reserved.")] [assembly: AssemblyDescription("C# WebSocket Implementation")] [assembly: AssemblyFileVersion("1.2.0.0")] [assembly: AssemblyInformationalVersion("1.2.0")] [assembly: AssemblyProduct("Fleck")] [assembly: AssemblyTitle("Fleck")] [assembly: AssemblyVersion("1.2.0.0")] namespace Fleck { public class ConnectionNotAvailableException : Exception { public ConnectionNotAvailableException() { } public ConnectionNotAvailableException(string message) : base(message) { } public ConnectionNotAvailableException(string message, Exception innerException) : base(message, innerException) { } } public enum LogLevel { Debug, Info, Warn, Error } public class FleckLog { public static LogLevel Level = LogLevel.Info; public static Action<LogLevel, string, Exception> LogAction = delegate(LogLevel level, string message, Exception ex) { if (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<string> onMessage, Action onClose, Action<byte[]> onBinary, Action<byte[]> onPing, Action<byte[]> onPong) { switch (GetVersion(request)) { case "76": return Draft76Handler.Create(request, onMessage); case "7": case "8": case "13": return Hybi13Handler.Create(request, onMessage, onClose, onBinary, onPing, onPong); case "policy-file-request": return FlashSocketPolicyRequestHandler.Create(request); 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"; } if (request.Body != null && request.Body.ToLower().Contains("policy-file-request")) { return "policy-file-request"; } return "75"; } } public interface IHandler { byte[] CreateHandshake(string subProtocol = null); void Receive(IEnumerable<byte> data); byte[] FrameText(string text); byte[] FrameBinary(byte[] bytes); byte[] FramePing(byte[] bytes); byte[] FramePong(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; } EndPoint LocalEndPoint { get; } Task<ISocket> Accept(Action<ISocket> callback, Action<Exception> error); Task Send(byte[] buffer, Action callback, Action<Exception> error); Task<int> Receive(byte[] buffer, Action<int> callback, Action<Exception> error, int offset = 0); Task Authenticate(X509Certificate2 certificate, SslProtocols enabledSslProtocols, Action callback, Action<Exception> error); void Dispose(); void Close(); void Bind(EndPoint ipLocal); void Listen(int backlog); } public interface IWebSocketConnection { Action OnOpen { get; set; } Action OnClose { get; set; } Action<string> OnMessage { get; set; } Action<byte[]> OnBinary { get; set; } Action<byte[]> OnPing { get; set; } Action<byte[]> OnPong { get; set; } Action<Exception> OnError { get; set; } IWebSocketConnectionInfo ConnectionInfo { get; } bool IsAvailable { get; } Task Send(string message); Task Send(byte[] message); Task SendPing(byte[] message); Task SendPong(byte[] message); void Close(); void Close(int code); } public interface IWebSocketConnectionInfo { string SubProtocol { get; } string Origin { get; } string Host { get; } string Path { get; } string ClientIpAddress { get; } int ClientPort { get; } IDictionary<string, string> Cookies { get; } IDictionary<string, string> Headers { get; } Guid Id { get; } string NegotiatedSubProtocol { get; } } public interface IWebSocketServer : IDisposable { void Start(Action<IWebSocketConnection> config); } public static class IntExtensions { public static byte[] ToBigEndianBytes<T>(this int source) { Type typeFromHandle = typeof(T); byte[] bytes; if (typeFromHandle == typeof(ushort)) { bytes = BitConverter.GetBytes((ushort)source); } else if (typeFromHandle == typeof(ulong)) { bytes = BitConverter.GetBytes((ulong)source); } else { if (!(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 QueuedStream : Stream { private class WriteData { public readonly byte[] Buffer; public readonly int Offset; public readonly int Count; public readonly AsyncCallback Callback; public readonly object State; public readonly QueuedWriteResult AsyncResult; public WriteData(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { Buffer = buffer; Offset = offset; Count = count; Callback = callback; State = state; AsyncResult = new QueuedWriteResult(state); } } private class QueuedWriteResult : IAsyncResult { private readonly object _state; public Exception Exception { get; set; } public IAsyncResult ActualResult { get; set; } public object AsyncState => _state; public WaitHandle AsyncWaitHandle { get { throw new NotSupportedException("Queued write operations do not support wait handle."); } } public bool CompletedSynchronously => false; public bool IsCompleted { get { if (ActualResult != null) { return ActualResult.IsCompleted; } return false; } } public QueuedWriteResult(object state) { _state = state; } } private readonly Stream _stream; private readonly Queue<WriteData> _queue = new Queue<WriteData>(); private int _pendingWrite; private bool _disposed; public override bool CanRead => _stream.CanRead; public override bool CanSeek => _stream.CanSeek; public override bool CanWrite => _stream.CanWrite; public override long Length => _stream.Length; public override long Position { get { return _stream.Position; } set { _stream.Position = value; } } public QueuedStream(Stream stream) { _stream = stream; } public override int Read(byte[] buffer, int offset, int count) { return _stream.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { return _stream.Seek(offset, origin); } public override void SetLength(long value) { _stream.SetLength(value); } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException("QueuedStream does not support synchronous write operations yet."); } public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { return _stream.BeginRead(buffer, offset, count, callback, state); } public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { lock (_queue) { WriteData writeData = new WriteData(buffer, offset, count, callback, state); if (_pendingWrite > 0) { _queue.Enqueue(writeData); return writeData.AsyncResult; } return BeginWriteInternal(buffer, offset, count, callback, state, writeData); } } public override int EndRead(IAsyncResult asyncResult) { return _stream.EndRead(asyncResult); } public override void EndWrite(IAsyncResult asyncResult) { if (asyncResult is QueuedWriteResult) { QueuedWriteResult queuedWriteResult = asyncResult as QueuedWriteResult; if (queuedWriteResult.Exception != null) { throw queuedWriteResult.Exception; } if (queuedWriteResult.ActualResult == null) { throw new NotSupportedException("QueuedStream does not support synchronous write operations. Please wait for callback to be invoked before calling EndWrite."); } return; } throw new ArgumentException(); } public override void Flush() { _stream.Flush(); } public override void Close() { _stream.Close(); } protected override void Dispose(bool disposing) { if (!_disposed) { if (disposing) { _stream.Dispose(); } _disposed = true; } base.Dispose(disposing); } private IAsyncResult BeginWriteInternal(byte[] buffer, int offset, int count, AsyncCallback callback, object state, WriteData queued) { _pendingWrite++; IAsyncResult actualResult = _stream.BeginWrite(buffer, offset, count, delegate(IAsyncResult ar) { queued.AsyncResult.ActualResult = ar; try { _stream.EndWrite(ar); } catch (Exception exception) { queued.AsyncResult.Exception = exception; } lock (_queue) { _pendingWrite--; while (_queue.Count > 0) { WriteData writeData = _queue.Dequeue(); try { writeData.AsyncResult.ActualResult = BeginWriteInternal(writeData.Buffer, writeData.Offset, writeData.Count, writeData.Callback, writeData.State, writeData); } catch (Exception exception2) { _pendingWrite--; writeData.AsyncResult.Exception = exception2; writeData.Callback(writeData.AsyncResult); continue; } break; } callback(queued.AsyncResult); } }, state); queued.AsyncResult.ActualResult = actualResult; return queued.AsyncResult; } } public class ReadState { public List<byte> Data { get; private set; } public FrameType? FrameType { get; set; } public ReadState() { Data = new List<byte>(); } public void Clear() { Data.Clear(); FrameType = null; } } public class RequestParser { private const string pattern = "^(?<method>[^\\s]+)\\s(?<path>[^\\s]+)\\sHTTP\\/1\\.1\\r\\n((?<field_name>[^:\\r\\n]+):(?([^\\r\\n])\\s)*(?<field_value>[^\\r\\n]*)\\r\\n)+\\r\\n(?<body>.+)?"; private const string FlashSocketPolicyRequestPattern = "^[<]policy-file-request\\s*[/][>]"; private static readonly Regex _regex = new Regex("^(?<method>[^\\s]+)\\s(?<path>[^\\s]+)\\sHTTP\\/1\\.1\\r\\n((?<field_name>[^:\\r\\n]+):(?([^\\r\\n])\\s)*(?<field_value>[^\\r\\n]*)\\r\\n)+\\r\\n(?<body>.+)?", RegexOptions.IgnoreCase | RegexOptions.Compiled); private static readonly Regex _FlashSocketPolicyRequestRegex = new Regex("^[<]policy-file-request\\s*[/][>]", RegexOptions.IgnoreCase | RegexOptions.Compiled); public static WebSocketHttpRequest Parse(byte[] bytes) { return Parse(bytes, "ws"); } public static WebSocketHttpRequest Parse(byte[] bytes, string scheme) { string @string = Encoding.UTF8.GetString(bytes); Match match = _regex.Match(@string); if (!match.Success) { match = _FlashSocketPolicyRequestRegex.Match(@string); if (match.Success) { return new WebSocketHttpRequest { Body = @string, Bytes = bytes }; } 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 SocketWrapper : ISocket { public const uint KeepAliveInterval = 60000u; public const uint RetryInterval = 10000u; private readonly Socket _socket; private Stream _stream; private CancellationTokenSource _tokenSource; private TaskFactory _taskFactory; 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 EndPoint LocalEndPoint => _socket.LocalEndPoint; public void SetKeepAlive(Socket socket, uint keepAliveInterval, uint retryInterval) { int num = 4; byte[] array = new byte[num * 3]; Array.Copy(BitConverter.GetBytes(1u), 0, array, 0, num); Array.Copy(BitConverter.GetBytes(keepAliveInterval), 0, array, num, num); Array.Copy(BitConverter.GetBytes(retryInterval), 0, array, num * 2, num); socket.IOControl(IOControlCode.KeepAliveValues, array, null); } public SocketWrapper(Socket socket) { _tokenSource = new CancellationTokenSource(); _taskFactory = new TaskFactory(_tokenSource.Token); _socket = socket; if (_socket.Connected) { _stream = new NetworkStream(_socket); } if (FleckRuntime.IsRunningOnWindows()) { SetKeepAlive(socket, 60000u, 10000u); } } public Task Authenticate(X509Certificate2 certificate, SslProtocols enabledSslProtocols, Action callback, Action<Exception> error) { SslStream ssl = new SslStream(_stream, leaveInnerStreamOpen: false); _stream = new QueuedStream(ssl); Func<AsyncCallback, object, IAsyncResult> beginMethod = (AsyncCallback cb, object s) => ssl.BeginAuthenticateAsServer(certificate, clientCertificateRequired: false, enabledSslProtocols, checkCertificateRevocation: false, cb, s); Task task = Task.Factory.FromAsync(beginMethod, ssl.EndAuthenticateAsServer, null); task.ContinueWith(delegate { callback(); }, TaskContinuationOptions.NotOnFaulted).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } public void Listen(int backlog) { _socket.Listen(backlog); } public void Bind(EndPoint endPoint) { _socket.Bind(endPoint); } public Task<int> Receive(byte[] buffer, Action<int> callback, Action<Exception> error, int offset) { try { Func<AsyncCallback, object, IAsyncResult> beginMethod = (AsyncCallback cb, object s) => _stream.BeginRead(buffer, offset, buffer.Length, cb, s); Task<int> task = Task.Factory.FromAsync(beginMethod, (Func<IAsyncResult, int>)_stream.EndRead, (object?)null); task.ContinueWith(delegate(Task<int> t) { callback(t.Result); }, TaskContinuationOptions.NotOnFaulted).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task<int> t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } catch (Exception obj) { error(obj); return null; } } public Task<ISocket> Accept(Action<ISocket> callback, Action<Exception> error) { Func<IAsyncResult, ISocket> endMethod = (IAsyncResult r) => (!_tokenSource.Token.IsCancellationRequested) ? new SocketWrapper(_socket.EndAccept(r)) : null; Task<ISocket> task = _taskFactory.FromAsync(_socket.BeginAccept, endMethod, null); task.ContinueWith(delegate(Task<ISocket> t) { callback(t.Result); }, TaskContinuationOptions.OnlyOnRanToCompletion).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task<ISocket> t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } public void Dispose() { _tokenSource.Cancel(); if (_stream != null) { _stream.Dispose(); } if (_socket != null) { _socket.Dispose(); } } public void Close() { _tokenSource.Cancel(); if (_stream != null) { _stream.Close(); } if (_socket != null) { _socket.Close(); } } public int EndSend(IAsyncResult asyncResult) { _stream.EndWrite(asyncResult); return 0; } public Task Send(byte[] buffer, Action callback, Action<Exception> error) { if (_tokenSource.IsCancellationRequested) { return null; } try { Func<AsyncCallback, object, IAsyncResult> beginMethod = (AsyncCallback cb, object s) => _stream.BeginWrite(buffer, 0, buffer.Length, cb, s); Task task = Task.Factory.FromAsync(beginMethod, _stream.EndWrite, null); task.ContinueWith(delegate { callback(); }, TaskContinuationOptions.NotOnFaulted).ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); task.ContinueWith(delegate(Task t) { error(t.Exception); }, TaskContinuationOptions.OnlyOnFaulted); return task; } catch (Exception obj) { error(obj); return null; } } } public class SubProtocolNegotiationFailureException : Exception { public SubProtocolNegotiationFailureException() { } public SubProtocolNegotiationFailureException(string message) : base(message) { } public SubProtocolNegotiationFailureException(string message, Exception innerException) : base(message, innerException) { } } public static class SubProtocolNegotiator { public static string Negotiate(IEnumerable<string> server, IEnumerable<string> client) { if (!server.Any() || !client.Any()) { return null; } IEnumerable<string> source = client.Intersect(server); if (!source.Any()) { throw new SubProtocolNegotiationFailureException("Unable to negotiate a subprotocol"); } return source.First(); } } public class WebSocketConnection : IWebSocketConnection { private readonly Action<IWebSocketConnection> _initialize; private readonly Func<WebSocketHttpRequest, IHandler> _handlerFactory; private readonly Func<IEnumerable<string>, string> _negotiateSubProtocol; private readonly Func<byte[], WebSocketHttpRequest> _parseRequest; private bool _closing; private bool _closed; private const int ReadSize = 4096; public ISocket Socket { get; set; } public IHandler Handler { get; set; } public Action OnOpen { get; set; } public Action OnClose { get; set; } public Action<string> OnMessage { get; set; } public Action<byte[]> OnBinary { get; set; } public Action<byte[]> OnPing { get; set; } public Action<byte[]> OnPong { get; set; } public Action<Exception> OnError { get; set; } public IWebSocketConnectionInfo ConnectionInfo { get; private set; } public bool IsAvailable { get { if (!_closing && !_closed) { return Socket.Connected; } return false; } } public WebSocketConnection(ISocket socket, Action<IWebSocketConnection> initialize, Func<byte[], WebSocketHttpRequest> parseRequest, Func<WebSocketHttpRequest, IHandler> handlerFactory, Func<IEnumerable<string>, string> negotiateSubProtocol) { Socket = socket; OnOpen = delegate { }; OnClose = delegate { }; OnMessage = delegate { }; OnBinary = delegate { }; OnPing = delegate(byte[] x) { SendPong(x); }; OnPong = delegate { }; OnError = delegate { }; _initialize = initialize; _handlerFactory = handlerFactory; _parseRequest = parseRequest; _negotiateSubProtocol = negotiateSubProtocol; } public Task Send(string message) { return Send(message, Handler.FrameText); } public Task Send(byte[] message) { return Send(message, Handler.FrameBinary); } public Task SendPing(byte[] message) { return Send(message, Handler.FramePing); } public Task SendPong(byte[] message) { return Send(message, Handler.FramePong); } private Task Send<T>(T message, Func<T, byte[]> createFrame) { if (Handler == null) { throw new InvalidOperationException("Cannot send before handshake"); } if (!IsAvailable) { FleckLog.Warn("Data sent while closing or after close. Ignoring."); TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>(); taskCompletionSource.SetException(new ConnectionNotAvailableException("Data sent while closing or after close. Ignoring.")); return taskCompletionSource.Task; } byte[] bytes = createFrame(message); return SendBytes(bytes); } public void StartReceiving() { List<byte> data = new List<byte>(4096); byte[] buffer = new byte[4096]; Read(data, buffer); } public void Close() { Close(1000); } public void Close(int code) { if (!IsAvailable) { return; } _closing = true; if (Handler == null) { CloseSocket(); return; } byte[] array = Handler.FrameClose(code); if (array.Length == 0) { CloseSocket(); } else { SendBytes(array, CloseSocket); } } public void CreateHandler(IEnumerable<byte> data) { WebSocketHttpRequest webSocketHttpRequest = _parseRequest(data.ToArray()); if (webSocketHttpRequest != null) { Handler = _handlerFactory(webSocketHttpRequest); if (Handler != null) { string text = _negotiateSubProtocol(webSocketHttpRequest.SubProtocols); ConnectionInfo = WebSocketConnectionInfo.Create(webSocketHttpRequest, Socket.RemoteIpAddress, Socket.RemotePort, text); _initialize(this); byte[] bytes = Handler.CreateHandshake(text); SendBytes(bytes, OnOpen); } } } private void Read(List<byte> data, byte[] buffer) { if (!IsAvailable) { return; } Socket.Receive(buffer, delegate(int r) { if (r <= 0) { FleckLog.Debug("0 bytes read. Closing."); CloseSocket(); } else { FleckLog.Debug(r + " bytes read"); IEnumerable<byte> enumerable = buffer.Take(r); if (Handler != null) { Handler.Receive(enumerable); } else { data.AddRange(enumerable); CreateHandler(data); } Read(data, buffer); } }, HandleReadError); } private void HandleReadError(Exception e) { if (e is AggregateException) { AggregateException ex = e as AggregateException; HandleReadError(ex.InnerException); return; } if (e is ObjectDisposedException) { FleckLog.Debug("Swallowing ObjectDisposedException", e); return; } OnError(e); if (e is WebSocketException) { FleckLog.Debug("Error while reading", e); Close(((WebSocketException)e).StatusCode); } else if (e is SubProtocolNegotiationFailureException) { FleckLog.Debug(e.Message); Close(1002); } else if (e is IOException) { FleckLog.Debug("Error while reading", e); Close(1006); } else { FleckLog.Error("Application Error", e); Close(1011); } } private Task SendBytes(byte[] bytes, Action callback = null) { return 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() { _closing = true; OnClose(); _closed = true; Socket.Close(); Socket.Dispose(); _closing = false; } } public class WebSocketConnectionInfo : IWebSocketConnectionInfo { private const string CookiePattern = "((;)*(\\s)*(?<cookie_name>[^=]+)=(?<cookie_value>[^\\;]+))+"; private static readonly Regex CookieRegex = new Regex("((;)*(\\s)*(?<cookie_name>[^=]+)=(?<cookie_value>[^\\;]+))+", RegexOptions.Compiled); public string NegotiatedSubProtocol { get; private set; } 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<string, string> Cookies { get; private set; } public IDictionary<string, string> Headers { get; private set; } public static WebSocketConnectionInfo Create(WebSocketHttpRequest request, string clientIp, int clientPort, string negotiatedSubprotocol) { 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, NegotiatedSubProtocol = negotiatedSubprotocol, Headers = new Dictionary<string, string>(request.Headers, StringComparer.InvariantCultureIgnoreCase) }; 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<string, string>(); 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<string, string> _headers = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); 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<string, string> Headers => _headers; public string[] SubProtocols { get { if (!_headers.TryGetValue("Sec-WebSocket-Protocol", out var value)) { return new string[0]; } return value.Split(new char[2] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries); } } } public class WebSocketServer : IWebSocketServer, IDisposable { private readonly string _scheme; private readonly IPAddress _locationIP; private Action<IWebSocketConnection> _config; public ISocket ListenerSocket { get; set; } public string Location { get; private set; } public bool SupportDualStack { get; } public int Port { get; private set; } public X509Certificate2 Certificate { get; set; } public SslProtocols EnabledSslProtocols { get; set; } public IEnumerable<string> SupportedSubProtocols { get; set; } public bool RestartAfterListenError { get; set; } public bool IsSecure { get { if (_scheme == "wss") { return Certificate != null; } return false; } } public WebSocketServer(string location, bool supportDualStack = true) { Uri uri = new Uri(location); Port = uri.Port; Location = location; SupportDualStack = supportDualStack; _locationIP = ParseIPAddress(uri); _scheme = uri.Scheme; Socket socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); if (SupportDualStack && !FleckRuntime.IsRunningOnMono() && FleckRuntime.IsRunningOnWindows()) { socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, optionValue: false); } ListenerSocket = new SocketWrapper(socket); SupportedSubProtocols = new string[0]; } public void Dispose() { ListenerSocket.Dispose(); } private IPAddress ParseIPAddress(Uri uri) { string host = uri.Host; if (host == "0.0.0.0") { return IPAddress.Any; } if (host == "[0000:0000:0000:0000:0000:0000:0000:0000]") { return IPAddress.IPv6Any; } try { return IPAddress.Parse(host); } catch (Exception innerException) { throw new FormatException("Failed to parse the IP address part of the location. Please make sure you specify a valid IP address. Use 0.0.0.0 or [::] to listen on all interfaces.", innerException); } } public void Start(Action<IWebSocketConnection> config) { IPEndPoint ipLocal = new IPEndPoint(_locationIP, Port); ListenerSocket.Bind(ipLocal); ListenerSocket.Listen(100); Port = ((IPEndPoint)ListenerSocket.LocalEndPoint).Port; FleckLog.Info($"Server started at {Location} (actual port {Port})"); if (_scheme == "wss") { if (Certificate == null) { FleckLog.Error("Scheme cannot be 'wss' without a Certificate"); return; } if (EnabledSslProtocols == SslProtocols.None) { EnabledSslProtocols = SslProtocols.Tls; FleckLog.Debug("Using default TLS 1.0 security protocol."); } } ListenForClients(); _config = config; } private void ListenForClients() { ListenerSocket.Accept(OnClientConnect, delegate(Exception e) { FleckLog.Error("Listener socket is closed", e); if (RestartAfterListenError) { FleckLog.Info("Listener socket restarting"); try { ListenerSocket.Dispose(); Socket socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP); ListenerSocket = new SocketWrapper(socket); Start(_config); FleckLog.Info("Listener socket restarted"); } catch (Exception ex) { FleckLog.Error("Listener could not be restarted", ex); } } }); } private void OnClientConnect(ISocket clientSocket) { if (clientSocket == null) { return; } FleckLog.Debug($"Client connected from {clientSocket.RemoteIpAddress}:{clientSocket.RemotePort.ToString()}"); 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); }, delegate(byte[] b) { connection.OnPing(b); }, delegate(byte[] b) { connection.OnPong(b); }), (IEnumerable<string> s) => SubProtocolNegotiator.Negotiate(SupportedSubProtocols, s)); if (IsSecure) { FleckLog.Debug("Authenticating Secure Connection"); clientSocket.Authenticate(Certificate, EnabledSslProtocols, 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 }; } } namespace Fleck.Helpers { internal static class FleckRuntime { public static bool IsRunningOnMono() { return Type.GetType("Mono.Runtime") != null; } public static bool IsRunningOnWindows() { return true; } } } namespace Fleck.Handlers { public class ComposableHandler : IHandler { public Func<string, byte[]> Handshake = (string s) => new byte[0]; public Func<string, byte[]> TextFrame = (string x) => new byte[0]; public Func<byte[], byte[]> BinaryFrame = (byte[] x) => new byte[0]; public Action<List<byte>> ReceiveData = delegate { }; public Func<byte[], byte[]> PingFrame = (byte[] i) => new byte[0]; public Func<byte[], byte[]> PongFrame = (byte[] i) => new byte[0]; public Func<int, byte[]> CloseFrame = (int i) => new byte[0]; private readonly List<byte> _data = new List<byte>(); public byte[] CreateHandshake(string subProtocol = null) { return Handshake(subProtocol); } public void Receive(IEnumerable<byte> data) { _data.AddRange(data); ReceiveData(_data); } public byte[] FrameText(string text) { return TextFrame(text); } public byte[] FrameBinary(byte[] bytes) { return BinaryFrame(bytes); } public byte[] FramePing(byte[] bytes) { return PingFrame(bytes); } public byte[] FramePong(byte[] bytes) { return PongFrame(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<string> onMessage) { return new ComposableHandler { TextFrame = FrameText, Handshake = (string sub) => Handshake(request, sub), ReceiveData = delegate(List<byte> data) { ReceiveData(onMessage, data); } }; } public static void ReceiveData(Action<string> onMessage, List<byte> 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 @string = Encoding.UTF8.GetString(bytes); onMessage(@string); } } 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, string subProtocol) { 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 (subProtocol != null) { stringBuilder.AppendFormat("Sec-WebSocket-Protocol: {0}\r\n", subProtocol); } stringBuilder.Append("\r\n"); string key = request["Sec-WebSocket-Key1"]; string key2 = request["Sec-WebSocket-Key2"]; ArraySegment<byte> challenge = new ArraySegment<byte>(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<byte> 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 = key.Count((char x) => x == ' '); byte[] bytes = BitConverter.GetBytes((int)(long.Parse(new string(key.Where(char.IsDigit).ToArray())) / num)); if (BitConverter.IsLittleEndian) { Array.Reverse((Array)bytes); } return bytes; } } public class FlashSocketPolicyRequestHandler { public static string PolicyResponse = "<?xml version=\"1.0\"?>\n<cross-domain-policy>\n <allow-access-from domain=\"*\" to-ports=\"*\"/>\n <site-control permitted-cross-domain-policies=\"all\"/>\n</cross-domain-policy>\n\0"; public static IHandler Create(WebSocketHttpRequest request) { return new ComposableHandler { Handshake = (string sub) => Handshake(request, sub) }; } public static byte[] Handshake(WebSocketHttpRequest request, string subProtocol) { FleckLog.Debug("Building Flash Socket Policy Response"); return Encoding.UTF8.GetBytes(PolicyResponse); } } public static class Hybi13Handler { private const string WebSocketResponseGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; public static IHandler Create(WebSocketHttpRequest request, Action<string> onMessage, Action onClose, Action<byte[]> onBinary, Action<byte[]> onPing, Action<byte[]> onPong) { ReadState readState = new ReadState(); return new ComposableHandler { Handshake = (string sub) => BuildHandshake(request, sub), TextFrame = (string s) => FrameData(Encoding.UTF8.GetBytes(s), FrameType.Text), BinaryFrame = (byte[] s) => FrameData(s, FrameType.Binary), PingFrame = (byte[] s) => FrameData(s, FrameType.Ping), PongFrame = (byte[] s) => FrameData(s, FrameType.Pong), CloseFrame = (int i) => FrameData(i.ToBigEndianBytes<ushort>(), FrameType.Close), ReceiveData = delegate(List<byte> d) { ReceiveData(d, readState, delegate(FrameType op, byte[] data) { ProcessFrame(op, data, onMessage, onClose, onBinary, onPing, onPong); }); } }; } 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<ulong>(); memoryStream.Write(array, 0, array.Length); } else if (payload.Length > 125) { memoryStream.WriteByte(126); byte[] array2 = payload.Length.ToBigEndianBytes<ushort>(); 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<byte> data, ReadState readState, Action<FrameType, byte[]> processFrame) { while (data.Count >= 2) { bool flag = (data[0] & 0x80) != 0; int num = data[0] & 0x70; FrameType frameType = (FrameType)(data[0] & 0xFu); 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; } byte[] array = data.Skip(num4).Take(4).ToArray(); num4 += 4; if (data.Count < num4 + num5) { break; } byte[] array2 = new byte[num5]; for (int i = 0; i < num5; i++) { array2[i] = (byte)(data[num4 + i] ^ array[i % 4]); } readState.Data.AddRange(array2); data.RemoveRange(0, num4 + num5); if (frameType != 0) { readState.FrameType = frameType; } if (flag && readState.FrameType.HasValue) { byte[] arg = readState.Data.ToArray(); FrameType? frameType2 = readState.FrameType; readState.Clear(); processFrame(frameType2.Value, arg); } } } public static void ProcessFrame(FrameType frameType, byte[] data, Action<string> onMessage, Action onClose, Action<byte[]> onBinary, Action<byte[]> onPing, Action<byte[]> onPong) { 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.ValidCloseCodes.Contains(num) && (num < 3000 || num > 4999)) { throw new WebSocketException(1002); } } if (data.Length > 2) { ReadUTF8PayloadData(data.Skip(2).ToArray()); } onClose(); break; case FrameType.Binary: onBinary(data); break; case FrameType.Ping: onPing(data); break; case FrameType.Pong: onPong(data); break; case FrameType.Text: onMessage(ReadUTF8PayloadData(data)); break; default: FleckLog.Debug("Received unhandled " + frameType); break; } } public static byte[] BuildHandshake(WebSocketHttpRequest request, string subProtocol) { 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"); if (subProtocol != null) { stringBuilder.AppendFormat("Sec-WebSocket-Protocol: {0}\r\n", subProtocol); } 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); } } } }
ShipLootPlusWeb.dll
Decompiled 2 years agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Threading.Tasks; using System.Timers; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using Figgle; using Fleck; using HarmonyLib; using LethalConfig; using LethalConfig.ConfigItems; using Newtonsoft.Json; using ShipLootPlus.Utils; using ShipLootPlusWeb.Utils; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("ShipLootPlusWeb")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("ShipLootPlusWeb")] [assembly: AssemblyCopyright("Copyright © 2024")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("f468583f-79e8-44ef-8038-ef41fd745bef")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace ShipLootPlusWeb { public class PluginMetadata { public const string Author = "ShakePrint"; public const string Name = "Lethal LED Sign"; public const string Id = "ShakePrint.LethalCompanyLEDSign"; public const string Version = "1.0.0"; public string FullName => string.Format("{0} v{1}", "Lethal LED Sign", "1.0.0"); } [BepInPlugin("ShakePrint.LethalCompanyLEDSign", "Lethal LED Sign", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class ShipLootPlusWeb : BaseUnityPlugin { public static PluginMetadata pluginMetadata = new PluginMetadata(); public static ManualLogSource Log = new ManualLogSource("ShakePrint.LethalCompanyLEDSign"); public static ShipLootPlusWeb Instance; public static Harmony harmony = new Harmony("ShakePrint.LethalCompanyLEDSign"); private void Awake() { if ((Object)(object)Instance == (Object)null) { Instance = this; } Log = ((BaseUnityPlugin)this).Logger; Log.LogInfo((object)string.Format("Initializing plugin: {0} by {1}", pluginMetadata.FullName, "ShakePrint")); ConfigSettings.Initialize(((BaseUnityPlugin)this).Config, "Boadcasts ShipLootPlus data to a websocket."); harmony.PatchAll(); Log.LogInfo((object)$"Enable Websocket? {ConfigSettings.EnableWebsocket.Value}"); if (ConfigSettings.EnableWebsocket.Value) { Task.Run(async delegate { await WebsocketHelper.InitializeWebSocketServer(ConfigSettings.PortNumber.Value); }); } Log.LogInfo((object)$"Loaded!\n{FiggleFonts.Doom.Render(pluginMetadata.FullName, (int?)null)}"); } public static bool AssemblyExists(string Name) { try { Assembly arg = AppDomain.CurrentDomain.Load(Name); Log.LogInfo((object)$"Found {Name}: {arg}"); return true; } catch (FileNotFoundException) { return false; } } } } namespace ShipLootPlusWeb.Utils { public class ConfigSettings { public static ConfigEntry<bool> EnableWebsocket; public static ConfigEntry<int> PortNumber; public static void Initialize(ConfigFile config, string description) { string text = "Websocket"; EnableWebsocket = config.Bind<bool>(text, "Enable", true, "Enable websocket broadcasting."); PortNumber = config.Bind<int>(text, "Port Number", 3636, "Port number to send the serialized data to."); try { if (ShipLootPlusWeb.AssemblyExists("LethalConfig")) { SetupLethalConfig(description); } } catch { ShipLootPlusWeb.Log.LogWarning((object)"LethalSettings was not found - Skipping its initialization..."); } } private static void SetupLethalConfig(string description) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown LethalConfigManager.SetModDescription(description); LethalConfigManager.AddConfigItem((BaseConfigItem)new BoolCheckBoxConfigItem(EnableWebsocket, true)); LethalConfigManager.AddConfigItem((BaseConfigItem)new IntInputFieldConfigItem(PortNumber, true)); } } public class WebsocketHelper { public static class GlobalVars { public static string PreviousShipLootValue = "0"; public static string PreviousShipLootCount = "0"; public static string PreviousQuotaValue = "0"; public static string PreviousFulfilledValue = "0"; public static string PreviousDeadline = "0"; public static string PreviousDayNumberHuman = "0"; } private static List<IWebSocketConnection> _sockets = new List<IWebSocketConnection>(); public static async Task InitializeWebSocketServer(int port) { ShipLootPlusWeb.Log.LogInfo((object)"Attempting to initialize websocket..."); try { string text = Dns.GetHostEntry(Dns.GetHostName()).AddressList.FirstOrDefault((IPAddress ip) => ip.AddressFamily == AddressFamily.InterNetwork)?.ToString(); if (string.IsNullOrEmpty(text)) { ShipLootPlusWeb.Log.LogError((object)"No IPv4 address found. Exiting..."); return; } ShipLootPlusWeb.Log.LogInfo((object)("LOADING WEBSOCKET on " + text)); new WebSocketServer($"ws://{text}:{port}", true).Start((Action<IWebSocketConnection>)delegate(IWebSocketConnection socket) { Timer timer = null; socket.OnOpen = delegate { Console.WriteLine($"SERVER Open! Client connected: {socket.ConnectionInfo.Id}"); _sockets.Add(socket); }; socket.OnClose = delegate { Console.WriteLine($"Client disconnected: {socket.ConnectionInfo.Id}"); _sockets.Remove(socket); timer?.Stop(); timer?.Dispose(); }; socket.OnMessage = delegate(string message) { Console.WriteLine($"Received message: {message} from client {socket.ConnectionInfo.Id}"); socket.Send(message); }; }); } catch (Exception ex) { Console.WriteLine("WebSocket server initialization error: " + ex.Message); } } public static void BroadcastMessage(string message) { foreach (IWebSocketConnection socket in _sockets) { if (socket.IsAvailable) { socket.Send(message); } } } } } namespace ShipLootPlusWeb.Patches { [HarmonyPatch] internal class UiHelperPatcher { public static IEnumerable<MethodBase> TargetMethods() { return from MethodBase m in AccessTools.GetDeclaredMethods(typeof(UiHelper)) where m.Name == "RefreshElementValues" select m; } private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, MethodBase original) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown ShipLootPlusWeb.Log.LogInfo((object)("Patching: ShipLootPlus.Utils.UiHelper:" + original.Name)); List<CodeInstruction> list = new List<CodeInstruction>(instructions); list.Insert(list.Count - 2, new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(UiHelperPatcher), "RefreshWebValues", (Type[])null, (Type[])null))); ShipLootPlusWeb.Log.LogInfo((object)("Patched: ShipLootPlus.Utils.UiHelper:" + original.Name)); return list; } public static void RefreshWebValues() { if (!ConfigSettings.EnableWebsocket.Value) { return; } ShipLootPlusWeb.Log.LogInfo((object)"BROADCAST!"); HashSet<string> requiredDataPoints = new HashSet<string> { "ShipLootValue", "ShipLootCount", "QuotaValue", "FulfilledValue", "Deadline", "DayNumberHuman", "DayNumber" }; foreach (var item in (from dp in UiHelper.DataPoints where requiredDataPoints.Contains(dp.Name) select new { dp.Name, dp.Value }).ToList()) { WebsocketHelper.BroadcastMessage(JsonConvert.SerializeObject((object)item)); } } } }