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); + } +}