private BinaryWriter CreateOutputWriter() { try { string filePath = GenerateNewOutputFile(); if (string.IsNullOrEmpty(filePath)) { Console.WriteLine($"Unable to generate a numbered file name using the prefix: {OutputPrefix}. Try cleaning up the output directory."); return(null); } Console.WriteLine($"Recording to: {filePath}"); Stream stream = null; FileStream fileStream = new FileStream(filePath, FileMode.Create); stream = fileStream; // Write the recording header uncompressed to the file. // We'll rewind here later and update the frame count. // Write to a memory stream to prevent corruption of the file stream when we wrap it // in a GZipStream. MemoryStream headerStream = new MemoryStream(512); BinaryWriter writer = new NetworkWriter(headerStream); WriteRecordingHeader(writer); writer.Flush(); byte[] headerBytes = headerStream.ToArray(); // Copy header to the file. fileStream.Write(headerBytes, 0, headerBytes.Length); // Dispose of the temporary objects. headerBytes = null; writer = null; headerStream = null; switch (DecodeMode) { case Mode.CollateAndCompress: stream = new CollationStream(fileStream, true); break; case Mode.CollateOnly: stream = new CollationStream(fileStream, false); break; case Mode.FileCompression: stream = new GZipStream(fileStream, CompressionLevel.Fastest); break; case Mode.Uncompressed: stream = fileStream; break; } return(new NetworkWriter(stream)); } catch (Exception e) { Console.Error.WriteLine(e); } return(null); }
private void FinaliseOutput(BinaryWriter writer, uint frameCount) { // Rewind the stream to the beginning and find the first RoutingID.ServerInfo message // and RoutingID.Control message with a ControlMessageID.FrameCount ID. These should be // the first and second messages in the stream. // We'll limit searching to the first 5 messages. long serverInfoMessageStart = -1; long frameCountMessageStart = -1; writer.Flush(); // Extract the stream from the writer. The first stream may be a GZip stream, in which case we must // extract the stream that is writing to instead as we can't rewind compression streams // and we wrote the header raw. writer.BaseStream.Flush(); Stream outStream = null; CollationStream zipStream = writer.BaseStream as CollationStream; if (zipStream != null) { outStream = zipStream.BaseStream; zipStream.Flush(); } else { outStream = writer.BaseStream; } // Check we are allowed to seek the stream. if (!outStream.CanSeek) { // Stream does not support seeking. The frame count will not be fixed. return; } // Record the initial stream position to restore later. long restorePos = outStream.Position; outStream.Seek(0, SeekOrigin.Begin); long streamPos = 0; byte[] headerBuffer = new byte[PacketHeader.Size]; PacketHeader header = new PacketHeader(); byte[] markerValidationBytes = BitConverter.GetBytes(Tes.IO.Endian.ToNetwork(PacketHeader.PacketMarker)); byte[] markerBytes = new byte[markerValidationBytes.Length]; bool markerValid = false; int attemptsRemaining = 5; int byteReadLimit = 0; markerBytes[0] = 0; while ((frameCountMessageStart < 0 || serverInfoMessageStart < 0) && attemptsRemaining > 0 && outStream.CanRead) { --attemptsRemaining; markerValid = false; // Limit the number of bytes we try read in each attempt. byteReadLimit = 1024; while (byteReadLimit > 0) { --byteReadLimit; outStream.Read(markerBytes, 0, 1); if (markerBytes[0] == markerValidationBytes[0]) { markerValid = true; int i = 1; for (i = 1; markerValid && outStream.CanRead && i < markerValidationBytes.Length; ++i) { outStream.Read(markerBytes, i, 1); markerValid = markerValid && markerBytes[i] == markerValidationBytes[i]; } if (markerValid) { break; } else { // We've failed to fully validate the maker. However, we did read and validate // one byte in the marker, then continued reading until the failure. It's possible // that the last byte read, the failed byte, may be the start of the actual marker. // We check this below, and if so, we rewind the stream one byte in order to // start validation from there on the next iteration. We can ignore the byte if // it is does not match the first validation byte. We are unlikely to ever make this // match though. --i; // Go back to the last read byte. if (markerBytes[i] == markerValidationBytes[0]) { // Potentially the start of a new marker. Rewind the stream to attempt to validate it. outStream.Seek(-1, SeekOrigin.Current); } } } } if (markerValid && outStream.CanRead) { // Potential packet target. Record the stream position at the start of the marker. streamPos = outStream.Position - markerBytes.Length; outStream.Seek(streamPos, SeekOrigin.Begin); // Test the packet. int bytesRead = outStream.Read(headerBuffer, 0, headerBuffer.Length); if (bytesRead == headerBuffer.Length) { // Create a packet. if (header.Read(new NetworkReader(new MemoryStream(headerBuffer, false)))) { // Header is OK. Looking for RoutingID.Control if (header.RoutingID == (ushort)RoutingID.ServerInfo) { serverInfoMessageStart = streamPos; } else if (header.RoutingID == (ushort)RoutingID.Control) { // It's control message. Complete and validate the packet. // Read the header. Determine the expected size and read that much more data. PacketBuffer packet = new PacketBuffer(header.PacketSize + Crc16.CrcSize); packet.Emplace(headerBuffer, bytesRead); packet.Emplace(outStream, header.PacketSize + Crc16.CrcSize - bytesRead); if (packet.Status == PacketBufferStatus.Complete) { // Packet complete. Extract the control message. NetworkReader packetReader = new NetworkReader(packet.CreateReadStream(true)); ControlMessage message = new ControlMessage(); if (message.Read(packetReader) && header.MessageID == (ushort)ControlMessageID.FrameCount) { // Found the message location. frameCountMessageStart = streamPos; } } } else { // At this point, we've failed to find the right kind of header. We could use the payload size to // skip ahead in the stream which should align exactly to the next message. // Not done for initial testing. } } } } } if (serverInfoMessageStart >= 0) { // Found the correct location. Seek the stream to here and write a new FrameCount control message. outStream.Seek(serverInfoMessageStart, SeekOrigin.Begin); PacketBuffer packet = new PacketBuffer(); header = PacketHeader.Create((ushort)RoutingID.ServerInfo, 0); packet.WriteHeader(header); _serverInfo.Write(packet); packet.FinalisePacket(); BinaryWriter patchWriter = new Tes.IO.NetworkWriter(outStream); packet.ExportTo(patchWriter); patchWriter.Flush(); } if (frameCountMessageStart >= 0) { // Found the correct location. Seek the stream to here and write a new FrameCount control message. outStream.Seek(frameCountMessageStart, SeekOrigin.Begin); PacketBuffer packet = new PacketBuffer(); ControlMessage frameCountMsg = new ControlMessage(); header = PacketHeader.Create((ushort)RoutingID.Control, (ushort)ControlMessageID.FrameCount); frameCountMsg.ControlFlags = 0; frameCountMsg.Value32 = frameCount; // Placeholder. Frame count is currently unknown. frameCountMsg.Value64 = 0; packet.WriteHeader(header); frameCountMsg.Write(packet); packet.FinalisePacket(); BinaryWriter patchWriter = new Tes.IO.NetworkWriter(outStream); packet.ExportTo(patchWriter); patchWriter.Flush(); } if (outStream.Position != restorePos) { outStream.Seek(restorePos, SeekOrigin.Begin); } }