From 7a481d4367691a33fb9b5b3a11313c0b4f3f89d6 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Wed, 24 Jul 2024 15:59:28 -0700 Subject: [PATCH 1/3] Added improved WebSocket support with initial debug logging; added eventing framework --- .../Logic/Proxy/BaseWebSocketMessage.cs | 49 ++++++++ .../Logic/Proxy/IWebServiceProxy.cs | 2 +- .../Logic/Proxy/IWebSocketProxy.cs | 24 ++++ .../Logic/Proxy/WebServiceProxy.cs | 87 ++----------- .../Logic/Proxy/WebSocketProxy.cs | 114 +++++++++++++++--- .../Logic/Proxy/WebSocketProxyEvents.cs | 29 +++++ 6 files changed, 210 insertions(+), 95 deletions(-) create mode 100644 XMAT/Engines/WebServiceProxy/Logic/Proxy/BaseWebSocketMessage.cs create mode 100644 XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs create mode 100644 XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxyEvents.cs diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/BaseWebSocketMessage.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/BaseWebSocketMessage.cs new file mode 100644 index 0000000..75cddd8 --- /dev/null +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/BaseWebSocketMessage.cs @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Reflection.PortableExecutable; +using System.Text; + +namespace XMAT.WebServiceCapture.Proxy +{ + public abstract class BaseWebSocketMessage + { + internal StringBuilder _message = new(); + + [Description("WEB_SVC_SCRIPT_PROP_DESC_WS_REQNUM")] + public int RequestNumber; + [Description("WEB_SVC_SCRIPT_PROP_DESC_WS_BODY")] + public byte[] BodyBytes { get; set; } + + public BaseWebSocketMessage() + { + BodyBytes = Array.Empty(); + } + public override string ToString() + { + string final = ""; + + if (BodyBytes != null && BodyBytes.Length > 0) + { + final = Encoding.ASCII.GetString(BodyBytes); + } + + return final; + } + public byte[] ToByteArray() + { + byte[] final = new byte[BodyBytes.Length]; + Array.Copy(BodyBytes, 0, final, 0, BodyBytes.Length); + return final; + } + } + + public class WebSocketMessage : BaseWebSocketMessage + { + [Description("WEB_SVC_SCRIPT_PROP_DESC_WS_FROM_HOST")] + public bool FromHost { get; set; } + } +} diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebServiceProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebServiceProxy.cs index 29f10a1..9f43efd 100644 --- a/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebServiceProxy.cs +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebServiceProxy.cs @@ -6,7 +6,7 @@ namespace XMAT.WebServiceCapture.Proxy { public interface IWebServiceProxy - { + { event EventHandler ReceivedInitialConnection; event EventHandler ReceivedSslConnectionRequest; event EventHandler CompletedSslConnectionRequest; diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs new file mode 100644 index 0000000..fb29e10 --- /dev/null +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using Windows.Networking.Sockets; + +namespace XMAT.WebServiceCapture.Proxy +{ + public interface IWebSocketProxy + { + event EventHandler WebSocketOpened; + event EventHandler WebSocketMessage; + event EventHandler WebSocketClosed; + event EventHandler ProxyStopped; + +// bool IsProxyEnabled { get; } + +// void StartProxy(WebServiceProxyOptions options); + +// void StopProxy(); + +// void Reset(); + } +} diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs index 79b2e89..c5fe61b 100644 --- a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs @@ -397,14 +397,11 @@ private async Task ForwardRequestAsync(ClientState clientState, ClientRequ private async Task HandleWebSocketRequest(Uri uri, ClientState clientState, ClientRequest clientRequest) { - // TODO: this needs to be fleshed out into something bigger, - // but since it only partially works and needs a native implementation anyway, - // I'm leaving it as-is for now. - - // this will spawn read/write threads that will run until the websocket disconnects, + // This will spawn read/write threads that will run until the websocket disconnects, // after which the threads will terminate and all will be cleaned up + var wspc = new WebSocketProxy(); - await wspc.StartWebSocketProxy(uri, clientState, clientRequest); + await wspc.StartWebSocketProxy(uri, clientState, clientRequest, _logger); } private async Task HandleWebRequest(Uri uri, ClientState clientState, ClientRequest clientRequest) @@ -508,39 +505,6 @@ private ClientRequest ParseRequestAndHeaders(int clientId, List lines) return clientRequest; } - private int PrefetchContentLengthFromHeaders(int clientId, List lines) - { - // Return 0 if not found or malformed - int contentLength = 0; - - if (lines == null || lines.Count == 0) - { - _logger.Log(clientId, LogLevel.ERROR, "Connect request has no data."); - return contentLength; - } - - // Isolate the content-length header only - for (int i = 1; i < lines.Count; i++) - { - string line = lines[i]; - string[] header = line.Split(':', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - if (header.Length < 2) - { - _logger.Log(clientId, LogLevel.ERROR, $"Malformed header: {line}"); - } - else - { - if (header[0].Trim().ToLower() == "content-length") - { - contentLength = int.Parse(header[1].Trim()); - _logger.Log(clientId, LogLevel.DEBUG, $"Prefetch of Content-Length: {contentLength}"); - } - } - } - - return contentLength; - } - private async Task ReturnResponseAsync(ClientRequest clientRequest, ClientState clientState, HttpResponseMessage responseMessage) { ServerResponse serverResponse = await ParseServerResponseAsync(clientState.ID, responseMessage).ConfigureAwait(false); @@ -628,6 +592,8 @@ private async Task, byte[]>> ReadRequestAndHeadersAsync(Clien { do { + + readThisFrame = await stream.ReadAsync(buffer, readTotal, buffer.Length - readTotal); readTotal += readThisFrame; @@ -642,9 +608,9 @@ private async Task, byte[]>> ReadRequestAndHeadersAsync(Clien } while (clientState.TcpClient.Available > 0); } - catch (Exception ex) + catch { - _logger.Log(clientState.ID, LogLevel.ERROR, $"Failed reading client request: {ex}"); + } //File.WriteAllBytes($"XMAT_{clientState.ID}.dat", buffer); @@ -685,38 +651,13 @@ private async Task, byte[]>> ReadRequestAndHeadersAsync(Clien } } - // Get the actual body length from the content-length header - int expectedBodyLength = 0; - expectedBodyLength = PrefetchContentLengthFromHeaders(clientState.ID, lines); - - // Read the rest of the delayed body if needed - if (expectedBodyLength > readTotal - startIndex) - { - try - { - do - { - _logger.Log(clientState.ID, LogLevel.DEBUG, $"Delayed body bytes, reading {expectedBodyLength - (readTotal - startIndex)} at {readTotal - startIndex}"); - readThisFrame = await stream.ReadAsync(buffer, expectedBodyLength - (readTotal - startIndex), buffer.Length - readTotal); - readTotal += readThisFrame; - - // Do we need to resize? - if (readTotal >= buffer.Length) - { - _logger.Log(clientState.ID, LogLevel.DEBUG, $"Resizing buffer from {buffer.Length} bytes, to {buffer.Length * 2} bytes"); - Array.Resize(ref buffer, buffer.Length * 2); - } - } - while (expectedBodyLength > readTotal - startIndex); - } - catch (Exception ex) - { - _logger.Log(clientState.ID, LogLevel.ERROR, $"Failed reading client request for delayed body: {ex}"); - } - } - - _logger.Log(clientState.ID, LogLevel.DEBUG, $"Done with reading, read {readTotal} bytes total, and processed {startIndex} as headers, {readTotal-startIndex} as body"); + //string strHeadersTotal = strAllData.Substring(0, startIndex); + //string strBody = strAllData.Substring(startIndex); + //_logger.Log(clientState.ID, LogLevel.DEBUG, $"Whole buffer was {strAllData}"); + //_logger.Log(clientState.ID, LogLevel.DEBUG, $"Headers buffer was {strHeadersTotal}"); + //_logger.Log(clientState.ID, LogLevel.DEBUG, $"Body buffer was {strBody}"); + //_logger.Log(clientState.ID, LogLevel.DEBUG, $"Done with read, read {readTotal} bytes total, and processed {startIndex} as headers, body remainder is {readTotal-startIndex}"); // resize the buffer, otherwise we're taking up unnecessary memory Array.Resize(ref buffer, readTotal); @@ -750,8 +691,6 @@ private void CloseClientState(ClientState clientState) Timestamp = DateTime.Now } ); - - _logger.Log(clientState.ID, LogLevel.DEBUG, $"ConnectionClosed Event: {clientState.ID} | {DateTime.Now.ToString()}"); } private bool RaiseReceivedInitialConnection(int connectionID, TcpClient client) diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs index 6e28daa..6f239b8 100644 --- a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs @@ -2,18 +2,25 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; +using System.Linq; using System.Net.Security; using System.Net.WebSockets; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; +using Windows.Networking.Sockets; namespace XMAT.WebServiceCapture.Proxy { - internal class WebSocketProxy - + internal class WebSocketProxy : IWebSocketProxy { + public event EventHandler WebSocketOpened; + public event EventHandler WebSocketMessage; + public event EventHandler WebSocketClosed; + public event EventHandler ProxyStopped; + private ClientWebSocket _serverWebSocket; private WebSocket _clientWebSocket; @@ -23,8 +30,11 @@ internal class WebSocketProxy private WebSocketCloseStatus _closeStatus; private string _closeDescription; - public async Task StartWebSocketProxy(Uri uri, ClientState clientState, ClientRequest clientRequest) + private Logger _logger; + + public async Task StartWebSocketProxy(Uri uri, ClientState clientState, ClientRequest clientRequest, Logger logger) { + _logger = logger; await SetupProxy(uri, clientState, clientRequest); _tokenSource = new CancellationTokenSource(); @@ -47,16 +57,19 @@ private async Task SetupProxy(Uri uri, ClientState clientState, ClientRequest cl { _serverWebSocket = new ClientWebSocket(); - // TODO: we need to add all headers that aren't websocket-related - var auth = clientRequest.Headers.GetHeaderValuesAsString("Authorization"); - if (!string.IsNullOrEmpty(auth)) + // Get all non-Websocket headers to pass on + HeaderCollection clientHeaders = GetNonWebSocketClientHeaders(clientRequest.Headers); + + // Forward all headers that are not websocket-related + for (int i = 0; i < clientHeaders.Count(); i++) { - _serverWebSocket.Options.SetRequestHeader("Authorization", auth); + _serverWebSocket.Options.SetRequestHeader(clientHeaders.ElementAt(i).Key, clientHeaders[clientHeaders.ElementAt(i).Key]); } // accept all connections, don't use the proxy _serverWebSocket.Options.RemoteCertificateValidationCallback += new RemoteCertificateValidationCallback((sender, certificate, chain, policyErrors) => { return true; }); _serverWebSocket.Options.Proxy = null; + _serverWebSocket.Options.CollectHttpResponseDetails = true; // WebSocket class requires ws/wss scheme, so try to build a URI replacing just the scheme var wssUri = new UriBuilder("wss", uri.Host, -1, uri.AbsolutePath, uri.Query); @@ -73,16 +86,26 @@ private async Task SetupProxy(Uri uri, ClientState clientState, ClientRequest cl string respKey = CreateSecWebSocketAcceptKey(key); - // TODO: add headers from the real server connection to the headers here?? - string response = -$@"HTTP/1.1 101 Switching Protocols -Upgrade: websocket -Connection: Upgrade -Sec-WebSocket-Accept: {respKey} -Date: {DateTime.Now:R} + // get headers from server to pass on + HeaderCollection fullServerHeaders = new HeaderCollection(); + + HeaderCollection serverHeaders = GetNonWebSocketServerHeaders(_serverWebSocket.HttpResponseHeaders); + + StringBuilder response = new StringBuilder(); + response.AppendLine("HTTP/1.1 101 Switching Protocols"); + response.AppendLine("Upgrade: websocket"); + response.AppendLine("Connection: Upgrade"); + response.AppendLine($"Sec-WebSocket-Accept: {respKey}"); + + for (int i = 0; i < serverHeaders.Count(); i++) + { + response.AppendLine(serverHeaders.ElementAt(i).Key + ": " + serverHeaders[serverHeaders.ElementAt(i).Key]); + } + + response.AppendLine($"Date: {DateTime.Now:R}"); + response.AppendLine(""); -"; - await clientState.GetStream().WriteAsync(Encoding.UTF8.GetBytes(response)); + await clientState.GetStream().WriteAsync(Encoding.UTF8.GetBytes(response.ToString())); // now, create a WebSocket around the original incoming HTTP(S) stream and act as the server _clientWebSocket = WebSocket.CreateFromStream(clientState.GetStream(), true, null, TimeSpan.FromSeconds(1)); @@ -135,16 +158,20 @@ private async void ServerThread(object obj) } else { + string mes = new ArraySegment(buffer, 0, result.Count).ConvertToString(); + + _logger.Log(0, LogLevel.DEBUG, $"Websocket server message: {mes}"); + await _clientWebSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, _tokenSource.Token); } } catch (OperationCanceledException) { - PublicUtilities.AppLog(LogLevel.DEBUG, "Server websocket thread exited due to token cancelation."); + _logger.Log(0, LogLevel.DEBUG, "Server websocket thread exited due to token cancelation."); } catch (WebSocketException wse) { - PublicUtilities.AppLog(LogLevel.ERROR, $"Unexpected server websocket exception: {wse}"); + _logger.Log(0, LogLevel.ERROR, $"Unexpected server websocket exception: {wse}"); _tokenSource.Cancel(); } } @@ -167,19 +194,66 @@ private async void ClientThread(object obj) } else { + string mes = new ArraySegment(buffer, 0, result.Count).ConvertToString(); + + _logger.Log(0, LogLevel.DEBUG, $"Websocket server message: {mes}"); + await _serverWebSocket.SendAsync(new ArraySegment(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, _tokenSource.Token); } } catch (OperationCanceledException) { - PublicUtilities.AppLog(LogLevel.DEBUG, "Client websocket thread exited due to token cancelation."); + _logger.Log(0, LogLevel.DEBUG, "Client websocket thread exited due to token cancelation."); } catch (WebSocketException wse) { - PublicUtilities.AppLog(LogLevel.ERROR, $"Unexpected client websocket exception: {wse}"); + _logger.Log(0, LogLevel.ERROR, $"Unexpected client websocket exception: {wse}"); _tokenSource.Cancel(); } } } + private HeaderCollection GetNonWebSocketClientHeaders(HeaderCollection allHeaders) + { + HeaderCollection _headers = new HeaderCollection(); + + // Get all non-Websocket related headers + for (int i = 0; i < allHeaders.Count(); i++) + { + if (allHeaders.ElementAt(i).Key.ToLower() != "host" && + allHeaders.ElementAt(i).Key.ToLower() != "upgrade" && + allHeaders.ElementAt(i).Key.ToLower() != "connection" && + allHeaders.ElementAt(i).Key.ToLower() != "sec-websocket-key" && + allHeaders.ElementAt(i).Key.ToLower() != "sec-websocket-version" && + allHeaders.ElementAt(i).Key.ToLower() != "origin" && + allHeaders.ElementAt(i).Key.ToLower() != "sec-websocket-protocol" && + allHeaders.ElementAt(i).Key.ToLower() != "sec-websocket-extensions") + { + _headers[allHeaders.ElementAt(i).Key] = allHeaders[allHeaders.ElementAt(i).Key]; + } + } + + _logger.Log(0, LogLevel.DEBUG, $"Client WebSocket Headers to pass on: \n{_headers.ToString()}"); + + return _headers; + } + private HeaderCollection GetNonWebSocketServerHeaders(IReadOnlyDictionary> allHeaders) + { + HeaderCollection _headers = new HeaderCollection(); + + // Get all non-Websocket related headers + for (int i = 0; i < allHeaders.Count(); i++) + { + if (allHeaders.ElementAt(i).Key.ToLower() != "upgrade" && + allHeaders.ElementAt(i).Key.ToLower() != "connection" && + allHeaders.ElementAt(i).Key.ToLower() != "sec-websocket-accept") + { + _headers[allHeaders.ElementAt(i).Key] = allHeaders[allHeaders.ElementAt(i).Key].First(); + } + } + + _logger.Log(0, LogLevel.DEBUG, $"Server WebSocket Headers to pass on: \n{_headers.ToString()}"); + + return _headers; + } } } diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxyEvents.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxyEvents.cs new file mode 100644 index 0000000..c0e25bc --- /dev/null +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxyEvents.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Net.Sockets; + +namespace XMAT.WebServiceCapture.Proxy +{ + public class WebSocketOpenedEventArgs : EventArgs + { + public DateTime Timestamp { get; set; } + public int ConnectionID { get; set; } + public TcpClient TcpClient { get; set; } + public bool AcceptConnection { get; set; } + } + + public class WebSocketMessageEventArgs : EventArgs + { + public DateTime Timestamp { get; set; } + public int ConnectionID { get; set; } + public byte[] Message { get; set; } + } + + public class WebSocketClosedEventArgs : EventArgs + { + public DateTime Timestamp { get; set; } + public int ConnectionID { get; set; } + } +} From 23b582e464f9b4dc48c0bcd1d30775dbfa063a36 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Wed, 24 Jul 2024 16:55:29 -0700 Subject: [PATCH 2/3] Updated interface events --- .../WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs | 9 --------- .../WebServiceProxy/Logic/Proxy/WebServiceProxy.cs | 6 ++++++ .../WebServiceProxy/Logic/Proxy/WebSocketProxy.cs | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs index fb29e10..de615c5 100644 --- a/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/IWebSocketProxy.cs @@ -11,14 +11,5 @@ public interface IWebSocketProxy event EventHandler WebSocketOpened; event EventHandler WebSocketMessage; event EventHandler WebSocketClosed; - event EventHandler ProxyStopped; - -// bool IsProxyEnabled { get; } - -// void StartProxy(WebServiceProxyOptions options); - -// void StopProxy(); - -// void Reset(); } } diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs index c5fe61b..e341922 100644 --- a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebServiceProxy.cs @@ -401,6 +401,12 @@ private async Task HandleWebSocketRequest(Uri uri, ClientState clientState, Clie // after which the threads will terminate and all will be cleaned up var wspc = new WebSocketProxy(); + + // TODO: Add events for driving UI + wspc.WebSocketOpened += null; + wspc.WebSocketClosed += null; + wspc.WebSocketMessage += null; + await wspc.StartWebSocketProxy(uri, clientState, clientRequest, _logger); } diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs index 6f239b8..7838e90 100644 --- a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs @@ -16,10 +16,10 @@ namespace XMAT.WebServiceCapture.Proxy { internal class WebSocketProxy : IWebSocketProxy { + // TODO: use eventing for driving UI public event EventHandler WebSocketOpened; public event EventHandler WebSocketMessage; public event EventHandler WebSocketClosed; - public event EventHandler ProxyStopped; private ClientWebSocket _serverWebSocket; private WebSocket _clientWebSocket; From 4ff0d4a7ab6ba181bf764b80da3b4abb4f3058c6 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Wed, 24 Jul 2024 17:14:03 -0700 Subject: [PATCH 3/3] Updated unused events for clean warnings; updated project file to raise warnings for debug build at same level --- .../Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs | 8 +++++--- XMAT/XboxMultiplayerAnalysisTool.csproj | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs index 7838e90..760136a 100644 --- a/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs +++ b/XMAT/Engines/WebServiceProxy/Logic/Proxy/WebSocketProxy.cs @@ -17,9 +17,9 @@ namespace XMAT.WebServiceCapture.Proxy internal class WebSocketProxy : IWebSocketProxy { // TODO: use eventing for driving UI - public event EventHandler WebSocketOpened; - public event EventHandler WebSocketMessage; - public event EventHandler WebSocketClosed; + public event EventHandler WebSocketOpened { add { } remove { } } + public event EventHandler WebSocketMessage { add { } remove { } } + public event EventHandler WebSocketClosed { add { } remove { } } private ClientWebSocket _serverWebSocket; private WebSocket _clientWebSocket; @@ -32,6 +32,8 @@ internal class WebSocketProxy : IWebSocketProxy private Logger _logger; + + public async Task StartWebSocketProxy(Uri uri, ClientState clientState, ClientRequest clientRequest, Logger logger) { _logger = logger; diff --git a/XMAT/XboxMultiplayerAnalysisTool.csproj b/XMAT/XboxMultiplayerAnalysisTool.csproj index ed6cc6c..4b948e4 100644 --- a/XMAT/XboxMultiplayerAnalysisTool.csproj +++ b/XMAT/XboxMultiplayerAnalysisTool.csproj @@ -24,7 +24,7 @@ TRACE;DEBUG true - 1 + 3 True