diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..a7155c5
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,30 @@
+
+name: CI
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+
+jobs:
+ build:
+ name: Build
+ runs-on: windows-2022
+
+ steps:
+ - name: Checkout files
+ uses: actions/checkout@v2
+
+ - name: Setup .NET Core SDK 6
+ uses: actions/setup-dotnet@v1.7.2
+ with:
+ dotnet-version: 6.0.x
+
+ - name: Install dependencies
+ run: dotnet restore
+
+ - name: Build
+ run: dotnet build --configuration Release --no-restore
\ No newline at end of file
diff --git a/Constants.cs b/Constants.cs
new file mode 100644
index 0000000..3dea4dd
--- /dev/null
+++ b/Constants.cs
@@ -0,0 +1,199 @@
+namespace ParaTracyReplay {
+ ///
+ /// Contains a bunch of constants Tracy needs.
+ ///
+ internal class Constants {
+ // File protocol stuff
+
+ ///
+ /// The expected file signature that this program is designed to decode.
+ ///
+ public const ulong FileSignature = 0x6D64796361727475;
+
+ ///
+ /// The expected file version that this program is designed to decode.
+ ///
+ public const uint FileVersion = 2;
+
+ ///
+ /// The event ID for a zone begin in the file.
+ ///
+ public const byte FileEventZoneBegin = 15;
+
+ ///
+ /// The event ID for a zone end in the file.
+ ///
+ public const byte FileEventZoneEnd = 17;
+
+ ///
+ /// The event ID for a zone colour in the file.
+ ///
+ public const byte FileEventZoneColour = 62;
+
+ ///
+ /// The event ID for a marked frame in the file.
+ ///
+ public const byte FileEventFrameMark = 64;
+
+ // Network protocol stuff
+ ///
+ /// The maximum size of a network frame that the Tracy client can capture.
+ ///
+ public const int NetworkMaxFrameSize = 256 * 1024;
+
+ ///
+ /// Packet ID for a welcome handshake.
+ ///
+ public const byte NetworkHandshakeWelcome = 0x01;
+
+ ///
+ /// Packet ID for a protocol mismatch.
+ ///
+ public const byte NetworkHandshakeProtocolMismatch = 0x02;
+
+ ///
+ /// Packet ID for a zone begin packet.
+ ///
+ public const byte NetworkEventZoneBegin = 15;
+
+ ///
+ /// Packet ID for a zone end packet.
+ ///
+ public const byte NetworkEventZoneEnd = 17;
+
+ ///
+ /// Packet ID for a termination packet.
+ ///
+ public const byte NetworkEventTerminate = 55;
+
+ ///
+ /// Packet ID for a thread context packet.
+ ///
+ public const byte NetworkEventThreadContext = 57;
+
+ ///
+ /// Packet ID for a zone colour packet.
+ ///
+ public const byte NetworkEventZoneColour = 62;
+
+ ///
+ /// Packet ID for a frame mark packet.
+ ///
+ public const byte NetworkEventFrameMark = 64;
+
+ ///
+ /// Packet ID for a source location packet.
+ ///
+ public const byte NetworkEventSrcloc = 67;
+
+ ///
+ /// Packet ID for a response to a no-op query.
+ ///
+ public const byte NetworkResponseServerQueryNoop = 87;
+
+ ///
+ /// Packet ID for a response to a source code request to say it isnt available.
+ ///
+ public const byte NetworkResponseSourceCodeNotAvailable = 88;
+
+ ///
+ /// Packet ID for a response to a source code request to say it isnt available.
+ ///
+ public const byte NetworkResponseSymbolCodeNotAvailable = 89;
+
+ ///
+ /// Packet ID for a response containing string data.
+ ///
+ public const byte NetworkResponseStringData = 94;
+
+ ///
+ /// Packet ID for a response containing a thread name.
+ ///
+ public const byte NetworkResponseThreadName = 95;
+
+ ///
+ /// Packet ID for a request about a termination.
+ ///
+ public const byte NetworkQueryTerminate = 0;
+
+ ///
+ /// Packet ID for a request about a string.
+ ///
+ public const byte NetworkQueryString = 1;
+
+ ///
+ /// Packet ID for a request about a thread string.
+ ///
+ public const byte NetworkQueryThreadString = 2;
+
+ ///
+ /// Packet ID for a request about a source location.
+ ///
+ public const byte NetworkQuerySrcloc = 3;
+
+ ///
+ /// Packet ID for a request about a plot name.
+ ///
+ public const byte NetworkQueryPlotName = 4;
+
+ ///
+ /// Packet ID for a request about a frame name.
+ ///
+ public const byte NetworkQueryFrameName = 5;
+
+ ///
+ /// Packet ID for a request about a query parameter.
+ ///
+ public const byte NetworkQueryParameter = 6;
+
+ ///
+ /// Packet ID for a request about a fiber name.
+ ///
+ public const byte NetworkQueryFiberName = 7;
+
+ ///
+ /// Packet ID for a disconnect.
+ ///
+ public const byte NetworkQueryDisconnect = 8;
+
+ ///
+ /// Packet ID for a request about a callstack frame.
+ ///
+ public const byte NetworkQueryCallstackFrame = 9;
+
+ ///
+ /// Packet ID for a request about an external name.
+ ///
+ public const byte NetworkQueryExternalName = 10;
+
+ ///
+ /// Packet ID for a request about a symbol.
+ ///
+ public const byte NetworkQuerySymbol = 11;
+
+ ///
+ /// Packet ID for a request about symbol code.
+ ///
+ public const byte NetworkQuerySymbolCode = 12;
+
+ ///
+ /// Packet ID for a request about a code location.
+ ///
+ public const byte NetworkQueryCodeLocation = 13;
+
+ ///
+ /// Packet ID for a request about source code.
+ ///
+ public const byte NetworkQuerySourceCode = 14;
+
+ ///
+ /// Packet ID for a request about data transfer.
+ ///
+ public const byte NetworkQueryDataTransfer = 15;
+
+ ///
+ /// Packet ID for a request about partial data transfer.
+ ///
+ public const byte NetworkQueryDataTransferPart = 16;
+ }
+}
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..8a62537
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,25 @@
+The MIT License (MIT)
+=====================
+
+Copyright © `2023` `AffectedArc07`
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the “Software”), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/LICENSE.txt b/LICENSE.txt
deleted file mode 100644
index 8aa2645..0000000
--- a/LICENSE.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) [year] [fullname]
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/Loader.cs b/Loader.cs
new file mode 100644
index 0000000..aafea51
--- /dev/null
+++ b/Loader.cs
@@ -0,0 +1,467 @@
+using Serilog;
+using ParaTracyReplay.Structures;
+using System.Text;
+using System.Net;
+using System.Net.Sockets;
+using ParaTracyReplay.Structures.Network;
+using K4os.Compression.LZ4;
+using ParaTracyReplay.Structures.File;
+using System.Buffers;
+using System.Buffers.Binary;
+
+namespace ParaTracyReplay {
+ ///
+ /// The actual loader implementation.
+ /// This loads the file up and sends it over the network to a Tracy capture client.
+ ///
+ internal class Loader {
+ ///
+ /// Current timestamp we are working with.
+ /// This is class-scoped as its uesd by and by .
+ ///
+ private long Timestamp;
+
+ ///
+ /// Current thread ID we are working with.
+ /// This is class-scoped as its uesd by and by .
+ ///
+ private uint ThreadId;
+
+ ///
+ /// Holding array to hold data from before sending as one bunched message in .
+ ///
+ private byte[] WriteBuffer;
+
+ ///
+ /// The current offset for the array.
+ ///
+ private int Offset;
+
+ ///
+ /// The representing the connection from the client.
+ ///
+ private Socket ClientSocket;
+
+ ///
+ /// Loads the Tracy file from disk and sends it over the network.
+ ///
+ /// The path to the file we want to load.
+ public void Load(string file_to_load) {
+ // Make sure it exists first
+ if (!File.Exists(file_to_load)) {
+ Log.Logger.Fatal($"File \"{file_to_load}\" not found!");
+ Environment.Exit(1);
+ }
+
+ Log.Logger.Information($"Loading \"{file_to_load}\"...");
+
+ // Read it as a file stream not just as the full thing at once
+ using (FileStream fs = File.OpenRead(file_to_load)) {
+ using (BinaryReader br = new BinaryReader(fs)) {
+ // Get the file header first
+ FileHeader file_header = new FileHeader();
+ file_header.Read(br);
+
+ // So we can validate its signature
+ if (file_header.Signature == Constants.FileSignature) {
+ Log.Logger.Information("File signature matches");
+ } else {
+ Log.Logger.Fatal($"File signature mismatch! Expected \"{Constants.FileSignature}\", got \"{file_header.Signature}\"");
+ Environment.Exit(1);
+ }
+
+ // And version
+ if (file_header.Version == Constants.FileVersion) {
+ Log.Logger.Information("File version matches");
+ } else {
+ Log.Logger.Fatal($"File version mismatch! Expected \"{Constants.FileVersion}\", got \"{file_header.Version}\"");
+ Environment.Exit(1);
+ }
+
+ // Inform of the process name
+ Log.Logger.Information($"Captured process: {string.Join("", file_header.ProgramName)}");
+
+ // Get how many source locations we have
+ uint source_locations_count = br.ReadUInt32();
+ Log.Logger.Information($"Found {source_locations_count} source locations");
+
+ // Save our cache of them
+ Dictionary strings = new Dictionary();
+ Dictionary source_locations = new Dictionary();
+
+ // Read them all in
+ for (ulong i = 0; i < source_locations_count; i++) {
+ // Track the location name
+ uint loc_name_length = br.ReadUInt32();
+ string loc_name = string.Join("", br.ReadChars((int)loc_name_length));
+
+ // And the function name
+ uint function_name_len = br.ReadUInt32();
+ string function_name = string.Join("", br.ReadChars((int)function_name_len));
+
+ // And the file name
+ uint file_name_len = br.ReadUInt32();
+ string file_name = string.Join("", br.ReadChars((int)file_name_len));
+
+ // And the file name
+ uint line = br.ReadUInt32();
+ uint colour = br.ReadUInt32();
+
+ // Get the int of the name
+ long name_int = 0;
+ if (!string.IsNullOrWhiteSpace(loc_name)) {
+ name_int = loc_name.GetHashCode();
+ strings[name_int] = loc_name;
+ }
+
+ // And of the function
+ long function_int = 0;
+ if (!string.IsNullOrWhiteSpace(function_name)) {
+ function_int = function_name.GetHashCode();
+ strings[function_int] = function_name;
+ }
+
+ // And of the file
+ long file_int = 0;
+ if (!string.IsNullOrWhiteSpace(file_name)) {
+ file_int = file_name.GetHashCode();
+ strings[file_int] = file_name;
+ }
+
+ // And pack it all into a nice object
+ source_locations[i] = new SourceLocation() {
+ Name = name_int,
+ Function = function_int,
+ File = file_int,
+ Line = line,
+ Colour = colour,
+ };
+ }
+
+ // Inform of load
+ Log.Logger.Information($"Successfully loaded {source_locations.Count} source locations");
+
+ // Create our server endpoint
+ IPEndPoint socket_endpoint = new IPEndPoint(IPAddress.Any, 8086);
+ Socket server = new Socket(SocketType.Stream, ProtocolType.Tcp);
+ server.Bind(socket_endpoint);
+ // Allow 1 client max
+ server.Listen(1);
+
+ // Wait here
+ Log.Logger.Information($"Waiting for connection on {socket_endpoint.Address}:{socket_endpoint.Port}...");
+
+ // We got a connection
+ ClientSocket = server.Accept();
+ Log.Logger.Information($"Connection established");
+
+ // Read 8 bytes for the client name
+ byte[] clientname_buffer = new byte[8];
+ ClientSocket.Receive(clientname_buffer);
+ string client_name = Encoding.ASCII.GetString(clientname_buffer);
+
+ // Validate its a Tracy client
+ if (client_name != "TracyPrf") {
+ Log.Logger.Fatal($"Invalid client (Expected \"TracyPrf\", got \"{client_name}\")");
+ Environment.Exit(1);
+ }
+
+ // Read 4 bytes for protocol number
+ byte[] protocol_buffer = new byte[4];
+ ClientSocket.Receive(protocol_buffer);
+ uint protocol_version = BitConverter.ToUInt32(protocol_buffer);
+
+ // We only support these protocol versions
+ uint[] valid_versions = new uint[] { 56, 57 };
+
+ // Validate protocol version
+ if (!valid_versions.Contains(protocol_version)) {
+ Log.Logger.Fatal($"Invalid protocol version (Got {protocol_version}, valid versions are {string.Join(", ", valid_versions)})");
+ ClientSocket.Send(new byte[] { Constants.NetworkHandshakeProtocolMismatch });
+ Environment.Exit(1);
+ }
+
+ // If we got here its a valid version, send our welcome
+ ClientSocket.Send(new byte[] { Constants.NetworkHandshakeWelcome });
+
+ // Make our network header from the file header
+ NetworkHeader net_header = new NetworkHeader();
+ net_header.Multiplier = file_header.Multiplier;
+ net_header.InitBegin = file_header.InitBegin;
+ net_header.InitEnd = file_header.InitEnd;
+ net_header.Delay = file_header.Delay;
+ net_header.Resolution = file_header.Resolution;
+ net_header.Epoch = file_header.Epoch;
+ net_header.ExecTime = file_header.ExecTime;
+ net_header.ProcessId = file_header.ProcessId;
+ net_header.SamplingPeriod = file_header.SamplingPeriod;
+ net_header.Flags = (sbyte)file_header.Flags;
+ net_header.CpuArch = (sbyte)file_header.CpuArch;
+ Array.Copy(file_header.CpuManufacturer, net_header.CpuManufacturer, 12);
+ net_header.CpuId = file_header.CpuId;
+ Array.Copy(file_header.ProgramName, net_header.ProgramName, 64);
+ Array.Copy(file_header.HostInfo, net_header.HostInfo, 64);
+
+ // Send the network header
+ ClientSocket.Send(net_header.Write());
+
+ // Setup some vars for the network handling
+ int event_array_size = 24;
+ byte[] event_array = new byte[event_array_size];
+ Timestamp = 0;
+ ThreadId = 0;
+ WriteBuffer = new byte[(int)Math.Floor((decimal)(Constants.NetworkMaxFrameSize / event_array_size))];
+ Offset = 0;
+
+ Log.Logger.Information("Sending proc events to client...");
+
+ // Now read file events and send them off
+ // A FileEvent is 24 bytes (maximum), so read into an array of that size
+ while (br.Read(event_array) == event_array_size) {
+ // Make a new event and read the binary in from the file
+ FileEvent file_event = new FileEvent();
+ file_event.Read(new BinaryReader(new MemoryStream(event_array)));
+
+ // Figure out what event we have
+ switch (file_event.Type) {
+ // Handle ZoneBegin event
+ case Constants.FileEventZoneBegin:
+ FileZoneBegin file_zonebegin = (FileZoneBegin)file_event.Event;
+ WriteThreadContext(file_zonebegin.ThreadId);
+
+ NetworkZoneBegin network_startevent = new NetworkZoneBegin() {
+ Type = Constants.NetworkEventZoneBegin,
+ Timestamp = file_zonebegin.Timestamp - Timestamp,
+ SourceLocation = file_zonebegin.SourceLocation
+ };
+
+ WriteMessage(network_startevent.Write());
+ Timestamp = file_zonebegin.Timestamp;
+ break;
+
+
+ // Handle ZoneEnd event
+ case Constants.FileEventZoneEnd:
+ FileZoneEnd file_zoneend = (FileZoneEnd)file_event.Event;
+ WriteThreadContext(file_zoneend.ThreadId);
+
+ NetworkZoneEnd network_endevent = new NetworkZoneEnd() {
+ Type = Constants.NetworkEventZoneEnd,
+ Timestamp = file_zoneend.Timestamp - Timestamp
+ };
+
+ WriteMessage(network_endevent.Write());
+ Timestamp = file_zoneend.Timestamp;
+ break;
+
+
+ // Handle ZoneColour event
+ case Constants.FileEventZoneColour:
+ FileZoneColour file_zonecolour = (FileZoneColour)file_event.Event;
+ WriteThreadContext(file_zonecolour.ThreadId);
+
+ NetworkZoneColour network_colourevent = new NetworkZoneColour() {
+ Type = Constants.NetworkEventZoneColour,
+ ColourR = (byte)((file_zonecolour.Colour >> 0x00) & 0xFF),
+ ColourG = (byte)((file_zonecolour.Colour >> 0x08) & 0xFF),
+ ColourB = (byte)((file_zonecolour.Colour >> 0x10) & 0xFF)
+ };
+
+ WriteMessage(network_colourevent.Write());
+ break;
+
+
+ // Handle FrameMark event
+ case Constants.FileEventFrameMark:
+ FileFrameMark file_framemarkevent = (FileFrameMark)file_event.Event;
+
+ NetworkFrameMark network_framemarkevent = new NetworkFrameMark() {
+ Type = Constants.NetworkEventFrameMark,
+ Name = 0,
+ Timestamp = file_framemarkevent.Timestamp
+ };
+
+ WriteMessage(network_framemarkevent.Write());
+ break;
+ }
+ }
+
+ // We have read all the proc events, dump to the network and start the next
+ Commit();
+ Log.Logger.Information("Proc events sent. Negotiating string info.");
+
+ // Network requests are 13 bytes
+ int network_recv_size = 13;
+ byte[] network_recv_buffer = new byte[network_recv_size];
+
+ // Set this timeout to account for the fact we dont handle the data done packet if that even exists
+ ClientSocket.ReceiveTimeout = 1;
+ try {
+ while (ClientSocket.Receive(network_recv_buffer) == network_recv_size) {
+ // Decode the request
+ NetworkRequest nr = new NetworkRequest();
+ nr.Read(new BinaryReader(new MemoryStream(network_recv_buffer)));
+ // Wipe this, we will need it later
+ Array.Clear(network_recv_buffer);
+
+ // Figure out what we have
+ switch (nr.Type) {
+ // Source location query, handle it
+ case Constants.NetworkQuerySrcloc:
+ SourceLocation sourceloc = source_locations[BitConverter.ToUInt64(BitConverter.GetBytes(nr.Pointer))];
+ NetworkSourceLocation sourceloc_packet = new NetworkSourceLocation() {
+ Type = Constants.NetworkEventSrcloc,
+ Name = sourceloc.Name,
+ Function = sourceloc.Function,
+ File = sourceloc.File,
+ Line = sourceloc.Line,
+ ColourR = (byte)((sourceloc.Colour >> 0x00) & 0xFF),
+ ColourG = (byte)((sourceloc.Colour >> 0x08) & 0xFF),
+ ColourB = (byte)((sourceloc.Colour >> 0x10) & 0xFF)
+ };
+ WriteMessage(sourceloc_packet.Write());
+ break;
+
+ // String query, handle it
+ case Constants.NetworkQueryString:
+ WriteStringResponse(strings[nr.Pointer].ToCharArray(), BitConverter.ToUInt64(BitConverter.GetBytes(nr.Pointer)), Constants.NetworkResponseStringData);
+ break;
+
+ // No symbol code handling
+ case Constants.NetworkQuerySymbolCode:
+ WriteMessage(new byte[] { Constants.NetworkResponseSymbolCodeNotAvailable });
+ break;
+
+ // No source code handling
+ case Constants.NetworkQuerySourceCode:
+ WriteMessage(new byte[] { Constants.NetworkResponseSourceCodeNotAvailable });
+ break;
+
+ // No data transfer handling
+ case Constants.NetworkQueryDataTransfer:
+ WriteMessage(new byte[] { Constants.NetworkResponseServerQueryNoop });
+ break;
+
+ // No partial data transfer handling
+ case Constants.NetworkQueryDataTransferPart:
+ WriteMessage(new byte[] { Constants.NetworkResponseServerQueryNoop });
+ break;
+
+ // Handle thread names. Only one.
+ case Constants.NetworkQueryThreadString:
+ WriteStringResponse("main".ToCharArray(), BitConverter.ToUInt64(BitConverter.GetBytes(nr.Pointer)), Constants.NetworkResponseThreadName);
+ break;
+
+ // Infom on packets we dont recognise
+ default:
+ Log.Logger.Warning($"Unknown request from Tracy (Packet type: {nr.Type})");
+ break;
+ }
+ }
+ } catch (SocketException exc) {
+ // Timeouts are expected since we dont parse the "done" message
+ if (exc.SocketErrorCode != SocketError.TimedOut) {
+ // If its not a timeout, kick up a fuss
+ throw exc;
+ }
+ }
+
+ // Dump it all
+ Commit();
+ Log.Logger.Information("Data transfer complete");
+
+ // And clean up
+ ClientSocket.Close();
+ server.Close();
+ Log.Logger.Information("Done!");
+ }
+ }
+ }
+
+ ///
+ /// Sends the current down the , after encoding it with LZ4.
+ ///
+ private void Commit() {
+ if (Offset > 0) {
+ // Make our buffers
+ ArrayBufferWriter buffer = new ArrayBufferWriter();
+ ArrayBufferWriter encoded = new ArrayBufferWriter();
+
+ // Encode it
+ encoded.Advance(LZ4Codec.Encode(WriteBuffer.AsSpan()[..Offset], encoded.GetSpan(LZ4Codec.MaximumOutputSize(Offset))));
+
+ // Get the amount written
+ BinaryPrimitives.WriteUInt32LittleEndian(buffer.GetSpan(sizeof(uint)), (uint)encoded.WrittenCount);
+ // Advance once
+ buffer.Advance(sizeof(uint));
+ // Write the encoded data
+ buffer.Write(encoded.WrittenSpan);
+
+ // Fire it at Tracy
+ ClientSocket.Send(buffer.WrittenSpan);
+
+ // Cleanup
+ Array.Clear(WriteBuffer);
+ Offset = 0;
+ }
+ }
+
+ ///
+ /// Adds the message param to the , running if required.
+ ///
+ /// The array to write.
+ private void WriteMessage(byte[] message) {
+ // Flush buffer if required
+ if ((Offset + message.Length) > WriteBuffer.Length) {
+ Commit();
+ }
+
+ // Clone the message to our write buffer and offset it
+ Array.Copy(message, 0, WriteBuffer, Offset, message.Length);
+ Offset += message.Length;
+ }
+
+ ///
+ /// Writes a thread ID to the .
+ ///
+ /// The thread ID to write
+ private void WriteThreadContext(uint passed_threadid) {
+ // If its different, send a new thread and reset the timestamp
+ if (ThreadId != passed_threadid) {
+ ThreadId = passed_threadid;
+ Timestamp = 0;
+
+ // Make the event
+ NetworkThreadContext netthread_event = new NetworkThreadContext() {
+ ThreadId = passed_threadid,
+ Type = Constants.NetworkEventThreadContext
+ };
+
+ // And fire
+ WriteMessage(netthread_event.Write());
+ }
+ }
+
+ ///
+ /// Writes a response to a string query to the .
+ ///
+ /// The string as a array.
+ /// The pointer expressed as a .
+ /// The string type expressed as a .
+ private void WriteStringResponse(char[] str, ulong pointer, byte stringtype) {
+ // Get the length of the array
+ ushort string_length = (ushort)str.Length;
+
+ // Make the event
+ NetworkStringData string_packet = new NetworkStringData() {
+ Type = stringtype,
+ Pointer = pointer,
+ StringLength = string_length,
+ String = str
+ };
+
+ // And fire
+ WriteMessage(string_packet.Write());
+ }
+ }
+}
diff --git a/ParaTracyReplay.csproj b/ParaTracyReplay.csproj
new file mode 100644
index 0000000..0a0f40a
--- /dev/null
+++ b/ParaTracyReplay.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/ParaTracyReplay.sln b/ParaTracyReplay.sln
new file mode 100644
index 0000000..05fc234
--- /dev/null
+++ b/ParaTracyReplay.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32630.192
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParaTracyReplay", "ParaTracyReplay.csproj", "{D4A6D234-F859-4EDE-A0C8-C2AB5339023C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {D4A6D234-F859-4EDE-A0C8-C2AB5339023C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D4A6D234-F859-4EDE-A0C8-C2AB5339023C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D4A6D234-F859-4EDE-A0C8-C2AB5339023C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D4A6D234-F859-4EDE-A0C8-C2AB5339023C}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {07C81814-0D4F-46DE-857D-4245F58915E3}
+ EndGlobalSection
+EndGlobal
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..72ecf31
--- /dev/null
+++ b/Program.cs
@@ -0,0 +1,36 @@
+using Serilog;
+
+namespace ParaTracyReplay {
+ ///
+ /// Main program class
+ ///
+ internal class Program {
+ ///
+ /// Program entrypoint.
+ /// This sets up serilog, validates the args then invokes the .
+ ///
+ /// The file to load in position 0
+ static void Main(string[] args) {
+ // Setup serilog
+ Log.Logger = new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .WriteTo.Logger(lc => {
+ lc.WriteTo.Console(
+ outputTemplate:
+ "[{Timestamp:yyyy-MM-dd HH:mm:ss}] [{Level:u3}] {Message:lj}{NewLine}{Exception}");
+ })
+ .CreateLogger();
+
+ // Validate args
+ if (args.Length == 0) {
+ Log.Logger.Fatal("Error, not enough arguments");
+ Log.Logger.Fatal("Usage: ParaTracyReplay.exe yourfile.utracy");
+ Environment.Exit(1);
+ }
+
+ // Create and invoke the loader
+ Loader loader = new Loader();
+ loader.Load(args[0]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 7726453..f0cad98 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,28 @@
-# ParaTracyReplay
\ No newline at end of file
+# ParaTracyReplay
+
+Allows loading of `.utracy` files generated by [https://github.com/ParadiseSS13/byond-tracy](https://github.com/ParadiseSS13/byond-tracy) to then be captured by Tracy.
+
+Works with Tracy 0.8.2 and Paradise's fork of byond-tracy as of [6168fac975061bb20faf26c0d5c0f41ca4be6e75](https://github.com/ParadiseSS13/byond-tracy/commit/6168fac975061bb20faf26c0d5c0f41ca4be6e75). This is latest as of time of writing (2023-05-02), with subsequent commits being documentation changes.
+
+## Usage
+
+1. Build with Visual Studio or grab the latest release.
+2. `ParaTracyReplay.exe your_file.utracy`.
+3. Connect `capture.exe` or `tracy.exe` to `127.0.0.1:8086`.
+4. Wait for the data to finish streaming.
+
+## Why
+
+This is approximately 5x faster than the [Python script it was based on](https://github.com/ParadiseSS13/byond-tracy/blob/6168fac975061bb20faf26c0d5c0f41ca4be6e75/replay.py), and has proper type safety. The former may not sound like an issue with small traces, but when you have a profile spanning an entire 2 hour round (40GB), the speed **REALLY** starts to matter.
+
+## Contributing
+
+Just make PRs and test them, the contribution rules on this repo are pretty loose, just don't take a mile.
+
+## Thanks
+
+Special thanks to @mafemergency for making `byond-tracy` and the original python script, and to @FiniteReality for helping with some final implementation issues.
+
+## License
+
+This project is licensed under the MIT license. You can find a copy in `LICENSE.MD`.
\ No newline at end of file
diff --git a/Structures/File/FileEvent.cs b/Structures/File/FileEvent.cs
new file mode 100644
index 0000000..3e2eb72
--- /dev/null
+++ b/Structures/File/FileEvent.cs
@@ -0,0 +1,61 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures.File {
+ ///
+ /// Represents an event inside of the data file.
+ ///
+ internal class FileEvent : StructureBase {
+ ///
+ /// The contained event in the file.
+ ///
+ public StructureBase Event { get; set; }
+
+ ///
+ /// The type of event in the file, expressed as a .
+ ///
+ public byte Type { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Event.Write());
+ // Write out some padding
+ writer.Write(new byte[7]);
+ writer.Write(Type);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ // Get our type
+ Type = reader.ReadByte();
+
+ // Skip the padding
+ reader.ReadBytes(7);
+
+ // Parse the event type
+ switch (Type) {
+ // Cast it based on the event type
+ case Constants.FileEventZoneBegin:
+ Event = new FileZoneBegin();
+ break;
+ case Constants.FileEventZoneEnd:
+ Event = new FileZoneEnd();
+ break;
+ case Constants.FileEventZoneColour:
+ Event = new FileZoneColour();
+ break;
+ case Constants.FileEventFrameMark:
+ Event = new FileFrameMark();
+ break;
+ }
+
+ // And read the data in
+ Event.Read(reader);
+ }
+ }
+}
diff --git a/Structures/File/FileFrameMark.cs b/Structures/File/FileFrameMark.cs
new file mode 100644
index 0000000..59d0c69
--- /dev/null
+++ b/Structures/File/FileFrameMark.cs
@@ -0,0 +1,40 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures.File {
+ ///
+ /// Represents a frame mark event inside of the data file.
+ ///
+ internal class FileFrameMark : StructureBase {
+ ///
+ /// The name of the marker, expressed as a pointer.
+ ///
+ public uint Name { get; set; }
+
+ ///
+ /// The timestamp of the marker, expressed as a .
+ ///
+ public long Timestamp { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Name);
+ // Write out some padding
+ writer.Write(new byte[4]);
+ writer.Write(Timestamp);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Name = reader.ReadUInt32();
+ // Skip padding bytes
+ reader.ReadBytes(4);
+ Timestamp = reader.ReadInt64();
+ }
+ }
+}
diff --git a/Structures/File/FileHeader.cs b/Structures/File/FileHeader.cs
new file mode 100644
index 0000000..548505c
--- /dev/null
+++ b/Structures/File/FileHeader.cs
@@ -0,0 +1,155 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures {
+ ///
+ /// Represents the header inside of the data file.
+ ///
+ internal class FileHeader : StructureBase {
+ ///
+ /// The signature of the file expressed as a .
+ ///
+ public ulong Signature { get; set; }
+
+ ///
+ /// The version of the file, expressed as a .
+ ///
+ public uint Version { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public double Multiplier { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public long InitBegin { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public long InitEnd { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public long Delay { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public long Resolution { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public long Epoch { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public long ExecTime { get; set; }
+
+ ///
+ /// The PID of the profiled process, expressed as a .
+ ///
+ public long ProcessId { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public long SamplingPeriod { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public byte Flags { get; set; }
+
+ ///
+ /// The architecture of the CPU the process ran on, expressed as a .
+ ///
+ public byte CpuArch { get; set; }
+
+ ///
+ /// The manufacturer of the CPU the process ran on, expressed as an ASCII array.
+ ///
+ public char[] CpuManufacturer { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as a .
+ ///
+ public uint CpuId { get; set; }
+
+ ///
+ /// The name of the proccess profiled, expressed as an ASCII array.
+ ///
+ public char[] ProgramName { get; set; }
+
+ ///
+ /// I dont actually know what this means, but its expressed as an ASCII array.
+ ///
+ public char[] HostInfo { get; set; }
+
+ ///
+ /// Creates a new .
+ ///
+ public FileHeader() {
+ // Setup the byte arrays
+ CpuManufacturer = new char[12];
+ ProgramName = new char[64];
+ HostInfo = new char[1024];
+ }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Signature);
+ writer.Write(Version);
+ writer.Write(Multiplier);
+ writer.Write(InitBegin);
+ writer.Write(InitEnd);
+ writer.Write(Delay);
+ writer.Write(Resolution);
+ writer.Write(Epoch);
+ writer.Write(ExecTime);
+ writer.Write(ProcessId);
+ writer.Write(SamplingPeriod);
+ writer.Write(Flags);
+ writer.Write(CpuArch);
+ writer.Write(CpuManufacturer);
+ writer.Write(CpuId);
+ writer.Write(ProgramName);
+ writer.Write(HostInfo);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ // POS: 0
+ Signature = reader.ReadUInt64(); // ("signature", ctypes.c_ulonglong), Start at 0, read 8 bytes POS: 8
+ Version = reader.ReadUInt32(); // ("version", ctypes.c_uint), Start at 8, read 4 bytes POS: 12
+ reader.ReadBytes(4); // Skip 4 bytes due to alignment rules POS: 16
+ Multiplier = reader.ReadDouble(); // ("multiplier", ctypes.c_double), Start at 16, read 8 bytes POS: 24
+ InitBegin = reader.ReadInt64(); // ("init_begin", ctypes.c_longlong), Start at 24, read 8 bytes POS: 32
+ InitEnd = reader.ReadInt64(); // ("init_end", ctypes.c_longlong), Start at 32, read 8 bytes POS: 40
+ Delay = reader.ReadInt64(); // ("delay", ctypes.c_longlong), Start at 40, read 8 bytes POS: 48
+ Resolution = reader.ReadInt64(); // ("resolution", ctypes.c_longlong), Start at 48, read 8 bytes POS: 56
+ Epoch = reader.ReadInt64(); // ("epoch", ctypes.c_longlong), Start at 56, read 8 bytes POS: 64
+ ExecTime = reader.ReadInt64(); // ("exec_time", ctypes.c_longlong), Start at 64, read 8 bytes POS: 72
+ ProcessId = reader.ReadInt64(); // ("pid", ctypes.c_longlong), Start at 72, read 8 bytes POS: 80
+ SamplingPeriod = reader.ReadInt64(); // ("sampling_period", ctypes.c_longlong), Start at 80, read 8 bytes POS: 88
+ Flags = reader.ReadByte(); // ("flags", ctypes.c_byte), Start at 88, read 1 byte POS: 89
+ CpuArch = reader.ReadByte(); // ("cpu_arch", ctypes.c_byte), Start at 89, read 1 byte POS: 90
+ CpuManufacturer = reader.ReadChars(12); // ("cpu_manufacturer", ctypes.c_char * 12), Start at 90, read 12 bytes POS: 102
+ reader.ReadBytes(2); // Skip 2 bytes due to alignment rules POS: 104
+ CpuId = reader.ReadUInt32(); // ("cpu_id", ctypes.c_uint), Start at 104, read 4 bytes POS: 108
+ ProgramName = reader.ReadChars(64); // ("program_name", ctypes.c_char * 64), Start at 108, read 64 bytes POS: 172
+ HostInfo = reader.ReadChars(1024); // ("host_info", ctypes.c_char * 1024) Start at 172, read 1024 bytes POS: 1196
+ reader.ReadBytes(4); // Skip 4 bytes due to alignment rules POS: 1200
+ }
+ }
+}
diff --git a/Structures/File/FileZoneBegin.cs b/Structures/File/FileZoneBegin.cs
new file mode 100644
index 0000000..59e0c28
--- /dev/null
+++ b/Structures/File/FileZoneBegin.cs
@@ -0,0 +1,43 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures {
+ ///
+ /// Represents a zone begin event inside of the data file.
+ ///
+ internal class FileZoneBegin : StructureBase {
+ ///
+ /// The ID of the thread we are currently on.
+ ///
+ public uint ThreadId { get; set; }
+
+ ///
+ /// The source location pointer.
+ ///
+ public uint SourceLocation { get; set; }
+
+ ///
+ /// The timestamp this zone begins at.
+ ///
+ public long Timestamp { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(ThreadId);
+ writer.Write(SourceLocation);
+ writer.Write(Timestamp);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ ThreadId = reader.ReadUInt32();
+ SourceLocation = reader.ReadUInt32();
+ Timestamp = reader.ReadInt64();
+ }
+ }
+}
diff --git a/Structures/File/FileZoneColour.cs b/Structures/File/FileZoneColour.cs
new file mode 100644
index 0000000..15efdf4
--- /dev/null
+++ b/Structures/File/FileZoneColour.cs
@@ -0,0 +1,37 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures.File {
+ ///
+ /// Represents a zone colour event inside of the data file.
+ ///
+ internal class FileZoneColour : StructureBase {
+ ///
+ /// The ID of the thread we are applying this zone event to.
+ /// I feel like this might actually be a different ID but oh well.
+ ///
+ public uint ThreadId { get; set; }
+
+ ///
+ /// The colour of the zone, expressed as a .
+ ///
+ public uint Colour { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(ThreadId);
+ writer.Write(Colour);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ ThreadId = reader.ReadUInt32();
+ Colour = reader.ReadUInt32();
+ }
+ }
+}
diff --git a/Structures/File/FileZoneEnd.cs b/Structures/File/FileZoneEnd.cs
new file mode 100644
index 0000000..58d4ba7
--- /dev/null
+++ b/Structures/File/FileZoneEnd.cs
@@ -0,0 +1,41 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures {
+ ///
+ /// Represents a zone end event inside of the data file.
+ ///
+ internal class FileZoneEnd : StructureBase {
+ ///
+ /// The ID of the thread we are applying this zone event to.
+ /// I feel like this might actually be a different ID but oh well.
+ ///
+ public uint ThreadId { get; set; }
+
+ ///
+ /// The timestamp this zone ends.
+ ///
+ public long Timestamp { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(ThreadId);
+ // Write 4 padding bytes
+ writer.Write(new byte[4]);
+ writer.Write(Timestamp);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ ThreadId = reader.ReadUInt32();
+ // Skip over the 4 padding bytes
+ reader.ReadBytes(4);
+ Timestamp = reader.ReadInt64();
+ }
+ }
+}
diff --git a/Structures/Network/NetworkFrameMark.cs b/Structures/Network/NetworkFrameMark.cs
new file mode 100644
index 0000000..511c094
--- /dev/null
+++ b/Structures/Network/NetworkFrameMark.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkFrameMark : StructureBase {
+ public byte Type { get; set; }
+ public long Timestamp { get; set; }
+ public ulong Name { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(Timestamp);
+ writer.Write(Name);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ Timestamp = reader.ReadInt64();
+ Name = reader.ReadUInt64();
+ }
+ }
+}
diff --git a/Structures/Network/NetworkHeader.cs b/Structures/Network/NetworkHeader.cs
new file mode 100644
index 0000000..508762e
--- /dev/null
+++ b/Structures/Network/NetworkHeader.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkHeader : StructureBase {
+ public double Multiplier { get; set; }
+ public long InitBegin { get; set; }
+ public long InitEnd { get; set; }
+ public long Delay { get; set; }
+ public long Resolution { get; set; }
+ public long Epoch { get; set; }
+ public long ExecTime { get; set; }
+ public long ProcessId { get; set; }
+ public long SamplingPeriod { get; set; }
+ public sbyte Flags { get; set; }
+ public sbyte CpuArch { get; set; }
+ public char[] CpuManufacturer { get; set; }
+ public uint CpuId { get; set; }
+ public char[] ProgramName { get; set; }
+ public char[] HostInfo { get; set; }
+
+ public NetworkHeader() {
+ CpuManufacturer = new char[12];
+ ProgramName = new char[64];
+ HostInfo = new char[1024];
+ }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Multiplier);
+ writer.Write(InitBegin);
+ writer.Write(InitEnd);
+ writer.Write(Delay);
+ writer.Write(Resolution);
+ writer.Write(Epoch);
+ writer.Write(ExecTime);
+ writer.Write(ProcessId);
+ writer.Write(SamplingPeriod);
+ writer.Write(Flags);
+ writer.Write(CpuArch);
+ writer.Write(CpuManufacturer);
+ writer.Write(CpuId);
+ writer.Write(ProgramName);
+ writer.Write(HostInfo);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Multiplier = reader.ReadDouble();
+ InitBegin = reader.ReadInt64();
+ InitEnd = reader.ReadInt64();
+ Delay = reader.ReadInt64();
+ Resolution = reader.ReadInt64();
+ Epoch = reader.ReadInt64();
+ ExecTime = reader.ReadInt64();
+ ProcessId = reader.ReadInt64();
+ SamplingPeriod = reader.ReadInt64();
+ Flags = reader.ReadSByte();
+ CpuArch = reader.ReadSByte();
+ CpuManufacturer = reader.ReadChars(12);
+ CpuId = reader.ReadUInt32();
+ ProgramName = reader.ReadChars(64);
+ HostInfo = reader.ReadChars(1024);
+ }
+ }
+}
diff --git a/Structures/Network/NetworkRequest.cs b/Structures/Network/NetworkRequest.cs
new file mode 100644
index 0000000..679a3a6
--- /dev/null
+++ b/Structures/Network/NetworkRequest.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkRequest : StructureBase {
+ public byte Type { get; set; }
+ public long Pointer { get; set; }
+ public uint Extra { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(Pointer);
+ writer.Write(Extra);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ Pointer = reader.ReadInt64();
+ Extra = reader.ReadUInt32();
+ }
+ }
+}
diff --git a/Structures/Network/NetworkSourceLocation.cs b/Structures/Network/NetworkSourceLocation.cs
new file mode 100644
index 0000000..da7ab0b
--- /dev/null
+++ b/Structures/Network/NetworkSourceLocation.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkSourceLocation : StructureBase {
+ public byte Type { get; set; }
+ public long Name { get; set; }
+ public long Function { get; set; }
+ public long File { get; set; }
+ public uint Line { get; set; }
+ public byte ColourR { get; set; }
+ public byte ColourG { get; set; }
+ public byte ColourB { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(Name);
+ writer.Write(Function);
+ writer.Write(File);
+ writer.Write(Line);
+ writer.Write(ColourR);
+ writer.Write(ColourG);
+ writer.Write(ColourB);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ Name = reader.ReadInt64();
+ Function = reader.ReadInt64();
+ File = reader.ReadInt64();
+ Line = reader.ReadUInt32();
+ ColourR = reader.ReadByte();
+ ColourG = reader.ReadByte();
+ ColourB = reader.ReadByte();
+ }
+ }
+}
diff --git a/Structures/Network/NetworkStringData.cs b/Structures/Network/NetworkStringData.cs
new file mode 100644
index 0000000..a210847
--- /dev/null
+++ b/Structures/Network/NetworkStringData.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkStringData : StructureBase {
+ public byte Type { get; set; }
+ public ulong Pointer { get; set; }
+ public ushort StringLength { get; set; }
+ public char[] String { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(Pointer);
+ writer.Write(StringLength);
+ writer.Write(String);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ Pointer = reader.ReadUInt64();
+ StringLength = reader.ReadUInt16();
+ String = reader.ReadChars(StringLength);
+ }
+ }
+}
diff --git a/Structures/Network/NetworkThreadContext.cs b/Structures/Network/NetworkThreadContext.cs
new file mode 100644
index 0000000..a457670
--- /dev/null
+++ b/Structures/Network/NetworkThreadContext.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkThreadContext : StructureBase {
+ public byte Type { get; set; }
+ public uint ThreadId { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(ThreadId);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ ThreadId = reader.ReadUInt32();
+ }
+ }
+}
diff --git a/Structures/Network/NetworkZoneBegin.cs b/Structures/Network/NetworkZoneBegin.cs
new file mode 100644
index 0000000..89f662f
--- /dev/null
+++ b/Structures/Network/NetworkZoneBegin.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkZoneBegin : StructureBase {
+ public byte Type { get; set; }
+ public long Timestamp { get; set; }
+ public ulong SourceLocation { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(Timestamp);
+ writer.Write(SourceLocation);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ Timestamp = reader.ReadInt64();
+ SourceLocation = reader.ReadUInt64();
+ }
+ }
+}
diff --git a/Structures/Network/NetworkZoneColour.cs b/Structures/Network/NetworkZoneColour.cs
new file mode 100644
index 0000000..6063542
--- /dev/null
+++ b/Structures/Network/NetworkZoneColour.cs
@@ -0,0 +1,32 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkZoneColour : StructureBase {
+ public byte Type { get; set; }
+ public byte ColourR { get; set; }
+ public byte ColourG { get; set; }
+ public byte ColourB { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(ColourR);
+ writer.Write(ColourG);
+ writer.Write(ColourB);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ ColourR = reader.ReadByte();
+ ColourG = reader.ReadByte();
+ ColourB = reader.ReadByte();
+ }
+ }
+}
diff --git a/Structures/Network/NetworkZoneEnd.cs b/Structures/Network/NetworkZoneEnd.cs
new file mode 100644
index 0000000..fead0e2
--- /dev/null
+++ b/Structures/Network/NetworkZoneEnd.cs
@@ -0,0 +1,26 @@
+using System.Text;
+
+namespace ParaTracyReplay.Structures.Network {
+ internal class NetworkZoneEnd : StructureBase {
+ public byte Type { get; set; }
+ public long Timestamp { get; set; }
+
+ ///
+ public override byte[] Write() {
+ MemoryStream stream = new MemoryStream();
+
+ using (BinaryWriter writer = new BinaryWriter(stream, Encoding.ASCII)) {
+ writer.Write(Type);
+ writer.Write(Timestamp);
+ }
+
+ return stream.ToArray();
+ }
+
+ ///
+ public override void Read(BinaryReader reader) {
+ Type = reader.ReadByte();
+ Timestamp = reader.ReadInt64();
+ }
+ }
+}
diff --git a/Structures/SourceLocation.cs b/Structures/SourceLocation.cs
new file mode 100644
index 0000000..c37e4b8
--- /dev/null
+++ b/Structures/SourceLocation.cs
@@ -0,0 +1,31 @@
+namespace ParaTracyReplay.Structures {
+ ///
+ /// Represents a source location to be sent to Tracy.
+ ///
+ internal class SourceLocation {
+ ///
+ /// The zone name, expressed as a pointer.
+ ///
+ public long Name { get; set; }
+
+ ///
+ /// The function name, expressed as a pointer.
+ ///
+ public long Function { get; set; }
+
+ ///
+ /// The file name, expressed as a pointer.
+ ///
+ public long File { get; set; }
+
+ ///
+ /// The line of the file, expressed as a .
+ ///
+ public uint Line { get; set; }
+
+ ///
+ /// The colour of the zone, expressed as a .
+ ///
+ public uint Colour { get; set; }
+ }
+}
diff --git a/Structures/StructureBase.cs b/Structures/StructureBase.cs
new file mode 100644
index 0000000..20e208f
--- /dev/null
+++ b/Structures/StructureBase.cs
@@ -0,0 +1,19 @@
+namespace ParaTracyReplay.Structures {
+ ///
+ /// Base class for all structures in the program.
+ /// This allows for designated read + write methods to interact with byte arrays from C# objects.
+ ///
+ internal abstract class StructureBase {
+ ///
+ /// Prepares the data to be written to another stream as a array.
+ ///
+ /// A array containing all the variables from this structure.
+ public abstract byte[] Write();
+
+ ///
+ /// Reads the data from the supplied into the structure variables.
+ ///
+ /// The to read from.
+ public abstract void Read(BinaryReader reader);
+ }
+}