public bool ParseStats(byte[] stat) { try { if (stat != null) { GameStatsRaw = stat; SerializationContext context = new SerializationContext(); //Convert replay end of game stats to parsable object context.Register(typeof(EndOfGameStats)); context.Register(typeof(PlayerParticipantStatsSummary)); context.Register(typeof(RawStatDTO)); AmfReader statsReader = new AmfReader(new MemoryStream(stat), context); GameStats = (EndOfGameStats)statsReader.ReadAmf3Item(); } else { GameStats = null; } return(true); } catch { return(false); } }
static RtmpEvent ReadCommandOrData(AmfReader r, Command command, RtmpHeader header = null) { var methodName = (string)r.ReadAmf0Item(); object temp = r.ReadAmf0Item(); if (header != null && methodName == "@setDataFrame") { command.ConnectionParameters = temp; } else { command.InvokeId = Convert.ToInt32(temp); command.ConnectionParameters = r.ReadAmf0Item(); } var parameters = new List <object>(); while (r.DataAvailable) { parameters.Add(r.ReadAmf0Item()); } command.MethodCall = new Method(methodName, parameters.ToArray()); return(command); }
static RtmpEvent ReadCommandOrData(AmfReader r, Command command) { var methodName = (string)r.ReadAmf0Item(); var tmp = r.ReadAmf0Item(); if (tmp is AsObject) { command.ConnectionParameters = tmp; // tmp = r.ReadAmf0Item(); } else { command.InvokeId = Convert.ToInt32(tmp); command.ConnectionParameters = r.ReadAmf0Item(); } var parameters = new List <object>(); while (r.DataAvailable) { parameters.Add(r.ReadAmf0Item()); } command.MethodCall = new Method(methodName, parameters.ToArray()); return(command); }
private RtmpHeader ReadHeader() { int num1 = (int)this._reader.ReadByte(); AmfReader reader = this._reader; int chunkStreamId = RtmpPacketReader.GetChunkStreamId((byte)num1, reader); int num2 = 6; ChunkMessageHeaderType messageHeaderType = (ChunkMessageHeaderType)(num1 >> num2); RtmpHeader rtmpHeader1 = new RtmpHeader(); rtmpHeader1.StreamId = chunkStreamId; int num3 = (uint)messageHeaderType > 0U ? 1 : 0; rtmpHeader1.IsTimerRelative = num3 != 0; RtmpHeader rtmpHeader2 = rtmpHeader1; RtmpHeader rtmpHeader3; if (!this._rtmpHeaders.TryGetValue(chunkStreamId, out rtmpHeader3) && messageHeaderType != ChunkMessageHeaderType.New) { rtmpHeader3 = rtmpHeader2.Clone(); } switch (messageHeaderType) { case ChunkMessageHeaderType.New: rtmpHeader2.Timestamp = this._reader.ReadUInt24(); rtmpHeader2.PacketLength = this._reader.ReadUInt24(); rtmpHeader2.MessageType = (MessageType)this._reader.ReadByte(); rtmpHeader2.MessageStreamId = this._reader.ReadReverseInt(); break; case ChunkMessageHeaderType.SameSource: rtmpHeader2.Timestamp = this._reader.ReadUInt24(); rtmpHeader2.PacketLength = this._reader.ReadUInt24(); rtmpHeader2.MessageType = (MessageType)this._reader.ReadByte(); rtmpHeader2.MessageStreamId = rtmpHeader3.MessageStreamId; break; case ChunkMessageHeaderType.TimestampAdjustment: rtmpHeader2.Timestamp = this._reader.ReadUInt24(); rtmpHeader2.PacketLength = rtmpHeader3.PacketLength; rtmpHeader2.MessageType = rtmpHeader3.MessageType; rtmpHeader2.MessageStreamId = rtmpHeader3.MessageStreamId; break; case ChunkMessageHeaderType.Continuation: rtmpHeader2.Timestamp = rtmpHeader3.Timestamp; rtmpHeader2.PacketLength = rtmpHeader3.PacketLength; rtmpHeader2.MessageType = rtmpHeader3.MessageType; rtmpHeader2.MessageStreamId = rtmpHeader3.MessageStreamId; rtmpHeader2.IsTimerRelative = rtmpHeader3.IsTimerRelative; break; default: throw new SerializationException("Unexpected header type: " + (object)(int)messageHeaderType); } if (rtmpHeader2.Timestamp == 16777215) { rtmpHeader2.Timestamp = this._reader.ReadInt32(); } return(rtmpHeader2); }
public RtmpPacketReader(AmfReader reader) { this._reader = reader; this._rtmpHeaders = new Dictionary <int, RtmpHeader>(); this._rtmpPackets = new Dictionary <int, RtmpPacket>(); this.Continue = true; }
// part 1: read from the chunk stream, returning true if enough data is here. you must take the value // returned at `chunkStreamId`, find the chunk stream snapshot associated with that chunk stream and // pass it to the second stage (part2) along with the opaque value. public static bool ReadFrom1(AmfReader reader, out int chunkStreamId, out MessageHeader.Type opaque) { if (BasicHeader.ReadFrom(reader, out var format, out var streamId)) { opaque = (MessageHeader.Type)format; chunkStreamId = streamId; return(true); }
static async Task <(uint echoTime, Space <byte> echoRandom)> ReadS2Async(Stream stream) { var buffer = await stream.ReadBytesAsync(FrameLength); var reader = new AmfReader(buffer, EmptyContext); var time = reader.ReadUInt32(); // "time": a copy of c1 time var ____ = reader.ReadUInt32(); // "time2": current local time var echo = reader.ReadSpan(RandomLength); // "random echo": a copy of c1 random return(time, echo); }
RtmpEvent ParsePacket(RtmpPacket packet, Func <AmfReader, RtmpEvent> handler) { var memoryStream = new MemoryStream(packet.Buffer, false); var packetReader = new AmfReader(memoryStream, reader.SerializationContext); var header = packet.Header; var message = handler(packetReader); message.Header = header; message.Timestamp = header.Timestamp; return(message); }
static async Task <(uint three, uint time, uint zero, Space <byte> random)> ReadS1Async(Stream stream) { var buffer = await stream.ReadBytesAsync(C1Length); var reader = new AmfReader(buffer, EmptyContext); var three = reader.ReadByte(); // rtmp version (constant 3) [s0] var time = reader.ReadUInt32(); // time [s1] var zero = reader.ReadUInt32(); // zero [s1] var random = reader.ReadSpan(RandomLength); // random bytes [s1] return(three, time, zero, random); }
private RtmpEvent ParsePacket(RtmpPacket packet, Func <AmfReader, RtmpEvent> handler) { AmfReader amfReader = new AmfReader((Stream) new MemoryStream(packet.Buffer, false), this._reader.SerializationContext); RtmpHeader header = packet.Header; RtmpEvent rtmpEvent = handler(amfReader); RtmpHeader rtmpHeader = header; rtmpEvent.Header = rtmpHeader; int timestamp = header.Timestamp; rtmpEvent.Timestamp = timestamp; return(rtmpEvent); }
public static async Task <RtmpHandshake> ReadAsync(Stream stream, bool readVersion) { RtmpHandshake rtmpHandshake; using (AmfReader amfReader = new AmfReader((Stream) new MemoryStream(await StreamHelper.ReadBytesAsync(stream, 1536 + (readVersion ? 1 : 0))), (SerializationContext)null)) rtmpHandshake = new RtmpHandshake() { Version = readVersion ? amfReader.ReadByte() : (byte)0, Time = amfReader.ReadUInt32(), Time2 = amfReader.ReadUInt32(), Random = amfReader.ReadBytes(1528) }; return(rtmpHandshake); }
static void Main(string[] args) { var ConstansUrl = "http://hz-static-2.akamaized.net/assets/data/constants.data?cfeb208c39166420a536c2dcc146a277146"; var zlibstream = new System.Net.Http.HttpClient().GetStreamAsync(ConstansUrl).GetAwaiter().GetResult(); AmfReader amfreader; using (var amfstream = new ZlibStream(zlibstream, System.IO.Compression.CompressionMode.Decompress)) { amfreader = new AmfReader(ReadFully(amfstream), new SerializationContext()); } var output = JsonConvert.SerializeObject(amfreader.ReadAmf3Object()); File.WriteAllText("constantsReadable.json", output); }
public static RtmpHandshake Read(Stream stream, bool readVersion) { int count = 1536 + (readVersion ? 1 : 0); using (AmfReader amfReader = new AmfReader((Stream) new MemoryStream(StreamHelper.ReadBytes(stream, count)), (SerializationContext)null)) { RtmpHandshake rtmpHandshake = new RtmpHandshake(); rtmpHandshake.Version = readVersion ? amfReader.ReadByte() : (byte)0; rtmpHandshake.Time = amfReader.ReadUInt32(); rtmpHandshake.Time2 = amfReader.ReadUInt32(); rtmpHandshake.Random = amfReader.ReadBytes(1528); rtmpHandshake = rtmpHandshake; return(rtmpHandshake); } }
public static RtmpHandshake Read(Stream stream, bool readVersion) { int count = 1536 + (readVersion ? 1 : 0); using (AmfReader amfReader = new AmfReader(new MemoryStream(StreamHelper.ReadBytes(stream, count)), null)) { return(new RtmpHandshake() { Version = readVersion ? amfReader.ReadByte() : (byte)0, Time = amfReader.ReadUInt32(), Time2 = amfReader.ReadUInt32(), Random = amfReader.ReadBytes(1528) }); } }
private static RtmpEvent ReadCommandOrData(AmfReader r, Command command) { string methodName = (string)r.ReadAmf0Item(); command.InvokeId = Convert.ToInt32(r.ReadAmf0Item()); command.ConnectionParameters = r.ReadAmf0Item(); List <object> objectList = new List <object>(); while (r.DataAvailable) { objectList.Add(r.ReadAmf0Item()); } command.MethodCall = new Method(methodName, objectList.ToArray(), true, CallStatus.Request); return((RtmpEvent)command); }
public static RtmpHandshake Read(Stream stream, bool readVersion) { var size = HandshakeSize + (readVersion ? 1 : 0); var buffer = StreamHelper.ReadBytes(stream, size); using (var reader = new AmfReader(new MemoryStream(buffer), null)) { return(new RtmpHandshake { Version = readVersion ? reader.ReadByte() : default(byte), Time = reader.ReadUInt32(), Time2 = reader.ReadUInt32(), Random = reader.ReadBytes(HandshakeRandomSize) }); } }
private static int GetChunkStreamId(byte chunkBasicHeaderByte, AmfReader reader) { int num = (int)chunkBasicHeaderByte & 63; switch (num) { case 0: return((int)reader.ReadByte() + 64); case 1: return((int)reader.ReadByte() + (int)reader.ReadByte() * 256 + 64); default: return(num); } }
public static async Task <Handshake> ReadAsync(Stream stream, bool readVersion) { var size = HandshakeSize + (readVersion ? 1 : 0); var buffer = await StreamHelper.ReadBytesAsync(stream, size); using (var reader = new AmfReader(new MemoryStream(buffer), null)) { return(new Handshake() { Version = readVersion ? reader.ReadByte() : default(byte), Time = reader.ReadUInt32(), Time2 = reader.ReadUInt32(), Random = reader.ReadBytes(HandshakeRandomSize) }); } }
public static bool ReadFrom(AmfReader reader, out int format, out int chunkStreamId) { format = 0; chunkStreamId = 0; if (!reader.HasLength(1)) { return(false); } var b0 = reader.ReadByte(); var v = b0 & 0x3f; var fmt = b0 >> 6; switch (v) { // 2 byte variant case 0: if (!reader.HasLength(1)) { return(false); } format = fmt; chunkStreamId = reader.ReadByte() + 64; return(true); // 3 byte variant case 1: if (!reader.HasLength(2)) { return(false); } format = fmt; chunkStreamId = reader.ReadByte() + reader.ReadByte() * 256 + 64; return(true); // 1 byte variant default: format = fmt; chunkStreamId = v; return(true); } }
static int GetChunkStreamId(byte chunkBasicHeaderByte, AmfReader reader) { var chunkStreamId = chunkBasicHeaderByte & 0x3F; // 2 bytes if (chunkStreamId == 0) { return(reader.ReadByte() + 64); } // 3 bytes if (chunkStreamId == 1) { return(reader.ReadByte() + reader.ReadByte() * 256 + 64); } return(chunkStreamId); }
public Reader(RtmpClient owner, Stream stream, SerializationContext context, CancellationToken cancellationToken) { this.owner = owner; this.stream = stream; this.context = context; this.token = cancellationToken; this.reset = new AsyncAutoResetEvent(); this.queue = new ConcurrentQueue <Builder>(); this.streams = new KeyDictionary <int, ChunkStream.Snapshot>(); this.messages = new KeyDictionary <(int, uint), Builder>(); this.buffer = new byte[DefaultBufferLength]; this.available = 0; this.__readSingleFrameReader = new AmfReader(context); this.__readFramesFromBufferReader = new AmfReader(context); }
static int GetChunkStreamId(byte chunkBasicHeaderByte, AmfReader reader) { var chunkStreamId = chunkBasicHeaderByte & 0x3F; switch (chunkStreamId) { // 2 bytes case 0: return(reader.ReadByte() + 64); // 3 bytes case 1: return(reader.ReadByte() + reader.ReadByte() * 256 + 64); // 1 byte default: return(chunkStreamId); } }
static async Task <int> GetChunkStreamIdAsync(byte chunkBasicHeaderByte, AmfReader reader) { var chunkStreamId = chunkBasicHeaderByte & 0x3F; switch (chunkStreamId) { // 2 bytes case 0: return(await reader.ReadByteAsync() + 64); // 3 bytes case 1: return(await reader.ReadByteAsync() + await reader.ReadByteAsync() * 256 + 64); // 1 byte default: return(chunkStreamId); } }
public DataInput(AmfReader reader) { this.reader = reader; this.objectEncoding = ObjectEncoding.Amf3; }
private void UpdateReplays() { GamePanel.Children.Clear(); var dir = new DirectoryInfo(Path.Combine(Client.ExecutingDirectory, "cabinet")); IOrderedEnumerable <DirectoryInfo> directories = dir.EnumerateDirectories() .OrderBy(d => d.CreationTime); string[] Replays = Directory.GetDirectories(Path.Combine(Client.ExecutingDirectory, "cabinet")); foreach (DirectoryInfo di in directories) { string d = di.Name; if (!File.Exists(Path.Combine(Client.ExecutingDirectory, "cabinet", d, "token")) || !File.Exists(Path.Combine(Client.ExecutingDirectory, "cabinet", d, "key")) || !File.Exists(Path.Combine(Client.ExecutingDirectory, "cabinet", d, "endOfGameStats"))) { continue; } byte[] base64Stats = Convert.FromBase64String( File.ReadAllText(Path.Combine(Client.ExecutingDirectory, "cabinet", d, "endOfGameStats"))); var statsReader = new AmfReader(new MemoryStream(base64Stats), context); var stats = (EndOfGameStats)statsReader.ReadAmf3Item(); var item = new ReplayItem(); //Use unoccupied variable stats.Difficulty = d; item.Tag = stats; item.GameId.Text = File.Exists(Path.Combine(Client.ExecutingDirectory, "cabinet", d, "name")) ? File.ReadAllText(Path.Combine(Client.ExecutingDirectory, "cabinet", d, "name")) : d; item.GameId.Tag = d; item.GameType.Content = stats.GameMode.ToLower(); item.GameDate.Content = di.CreationTime.ToShortTimeString() + " " + di.CreationTime.ToShortDateString(); double seconds = stats.GameLength % 60; double minutes = stats.GameLength / 60; item.GameTime.Content = string.Format("{0:0}:{1:00}", minutes, seconds); item.Margin = new Thickness(0, 5, 0, 0); foreach ( SmallChampionItem image in stats.TeamPlayerParticipantStats.Select(summary => new SmallChampionItem { Width = 38, Height = 38, ChampionImage = { Source = Client.GetImage(Path.Combine(Client.ExecutingDirectory, "Assets", "champion", summary.SkinName + ".png")) } })) { item.TeamOnePanel.Children.Add(image); } foreach ( SmallChampionItem image in stats.OtherTeamPlayerParticipantStats.Select(summary => new SmallChampionItem { Width = 38, Height = 38, ChampionImage = { Source = Client.GetImage(Path.Combine(Client.ExecutingDirectory, "Assets", "champion", summary.SkinName + ".png")) } })) { item.TeamTwoPanel.Children.Add(image); } item.MouseDown += item_MouseDown; item.GameId.MouseDoubleClick += GameId_MouseDoubleClick; item.GameId.MouseLeave += GameId_MouseLeave; item.KeyDown += item_KeyDown; //Insert on top GamePanel.Children.Insert(0, item); } }
void UpdateReplays() { GamePanel.Children.Clear(); var dir = new DirectoryInfo("cabinet"); var directories = dir.EnumerateDirectories() .OrderBy(d => d.CreationTime); string[] Replays = Directory.GetDirectories("cabinet"); foreach (DirectoryInfo di in directories) { string d = di.Name; if (!File.Exists(Path.Combine("cabinet", d, "token")) || !File.Exists(Path.Combine("cabinet", d, "key")) || !File.Exists(Path.Combine("cabinet", d, "endOfGameStats"))) continue; byte[] Base64Stats = Convert.FromBase64String(File.ReadAllText(Path.Combine("cabinet", d, "endOfGameStats"))); AmfReader statsReader = new AmfReader(new MemoryStream(Base64Stats), context); EndOfGameStats stats = (EndOfGameStats)statsReader.ReadAmf3Item(); ReplayItem item = new ReplayItem(); //Use unoccupied variable stats.Difficulty = d; item.Tag = stats; item.GameId.Content = d; item.GameType.Content = stats.GameMode.ToLower(); item.GameDate.Content = di.CreationTime.ToShortTimeString() + " " + di.CreationTime.ToShortDateString(); double seconds = stats.GameLength % 60; double minutes = stats.GameLength / 60; item.GameTime.Content = string.Format("{0:0}:{1:00}", minutes, seconds); item.Margin = new Thickness(0, 5, 0, 0); foreach (PlayerParticipantStatsSummary summary in stats.TeamPlayerParticipantStats) { SmallChampionItem image = new SmallChampionItem(); image.Width = 38; image.Height = 38; Uri UriSource = new Uri("/LegendaryReplays;component/champion/" + summary.SkinName + ".png", UriKind.RelativeOrAbsolute); image.ChampionImage.Source = new BitmapImage(UriSource); item.TeamOnePanel.Children.Add(image); } foreach (PlayerParticipantStatsSummary summary in stats.OtherTeamPlayerParticipantStats) { SmallChampionItem image = new SmallChampionItem(); image.Width = 38; image.Height = 38; Uri UriSource = new Uri("/LegendaryReplays;component/champion/" + summary.SkinName + ".png", UriKind.RelativeOrAbsolute); image.ChampionImage.Source = new BitmapImage(UriSource); item.TeamTwoPanel.Children.Add(image); } item.MouseDown += item_MouseDown; //Insert on top GamePanel.Children.Insert(0, item); } }
bool ReadSingleFrame(AmfReader reader) { if (!ChunkStream.ReadFrom1(reader, out var streamId, out var opaque)) { return(false); } if (!streams.TryGetValue(streamId, out var previous)) { previous = new ChunkStream.Snapshot() { ChunkStreamId = streamId } } ; if (!ChunkStream.ReadFrom2(reader, previous, opaque, out var next)) { return(false); } streams[streamId] = next; context.RequestReadAllocation(next.MessageLength); var key = (next.ChunkStreamId, next.MessageStreamId); var builder = messages.TryGetValue(key, out var packet) ? packet : messages[key] = new Builder(next.MessageLength); var length = Math.Min(chunkLength, builder.Remaining); if (!reader.HasLength(length)) { return(false); } builder.AddData( reader.ReadSpan(length)); if (builder.Current == builder.Length) { messages.Remove(key); using (builder) { var dereader = __readSingleFrameReader; dereader.Rebind(builder.Span); var message = Deserialize(next.ContentType, dereader); DispatchMessage(message); } } return(true); } void DispatchMessage(RtmpMessage message) { switch (message) { case ChunkLength chunk: Kon.Trace("received: chunk-length", new { length = chunk.Length }); if (chunk.Length < 0) { throw new ArgumentException("invalid chunk length"); } context.RequestReadAllocation(chunk.Length); chunkLength = chunk.Length; break; case WindowAcknowledgementSize acknowledgement: if (acknowledgement.Count < 0) { throw new ArgumentException("invalid acknowledgement window length"); } acknowledgementLength = acknowledgement.Count; break; case Abort abort: Kon.Trace("received: abort", new { chunk = abort.ChunkStreamId }); // delete the chunk stream streams.Remove(abort.ChunkStreamId); // then, delete all message streams associated with that chunk stream foreach (var(key, builder) in messages.FilterArray(x => x.Key.chunkStreamId == abort.ChunkStreamId)) { messages.Remove(key); builder.Dispose(); } break; default: owner.InternalReceiveEvent(message); break; } } // todo: refactor RtmpMessage Deserialize(PacketContentType contentType, AmfReader r) { // (this comment must be kept in sync at rtmpclient.reader.cs and rtmpclient.writer.cs) // // unsupported type summary: // // - aggregate: we have never encountered this packet in the wild // - shared objects: we have not found a use case for this // - data commands: we have not found a use case for this, though it should be extremely easy to // support. it's just a one-way equivalent of command (invoke). that is, we don't // generate an invoke id for it, and it does not contain headers. other than that, // they're identical. we can use existing logic and add if statements to surround // writing the invokeid + headers if needed. switch (contentType) { case PacketContentType.SetChunkSize: return(new ChunkLength( length: r.ReadInt32())); case PacketContentType.AbortMessage: return(new Abort( chunkStreamId: r.ReadInt32())); case PacketContentType.Acknowledgement: return(new Acknowledgement( read: r.ReadUInt32())); case PacketContentType.UserControlMessage: var type = r.ReadUInt16(); var values = EnumerableEx.Range(r.Remaining / 4, r.ReadUInt32); return(new UserControlMessage( type: (UserControlMessage.Type)type, values: values)); case PacketContentType.WindowAcknowledgementSize: return(new WindowAcknowledgementSize( count: r.ReadInt32())); case PacketContentType.SetPeerBandwith: return(new PeerBandwidth( acknowledgementWindowSize: r.ReadInt32(), type: r.ReadByte())); case PacketContentType.Audio: return(new AudioData( r.ReadBytes(r.Remaining))); case PacketContentType.Video: return(new VideoData( r.ReadBytes(r.Remaining))); case PacketContentType.DataAmf0: throw NotSupportedException("data-amf0"); case PacketContentType.SharedObjectAmf0: throw NotSupportedException("sharedobject-amf0"); case PacketContentType.CommandAmf0: return(ReadCommand(ObjectEncoding.Amf0, contentType, r)); case PacketContentType.DataAmf3: throw NotSupportedException("data-amf3"); case PacketContentType.SharedObjectAmf3: throw NotSupportedException("sharedobject-amf0"); case PacketContentType.CommandAmf3: var encoding = (ObjectEncoding)r.ReadByte(); return(ReadCommand(encoding, contentType, r)); case PacketContentType.Aggregate: throw NotSupportedException("aggregate"); default: throw NotSupportedException($"unknown ({contentType})"); } }
static RtmpMessage ReadCommand(ObjectEncoding encoding, PacketContentType type, AmfReader r) { var name = (string)r.ReadAmfObject(encoding); var invokeId = Convert.ToUInt32(r.ReadAmfObject(encoding)); var headers = r.ReadAmfObject(encoding); var args = new List <object>(); while (r.Remaining > 0) { args.Add(r.ReadAmfObject(encoding)); } return(new Invoke(type) { MethodName = name, Arguments = args.ToArray(), InvokeId = invokeId, Headers = headers }); }
public void Init() { foreach (BootstrapInfo b in BootstrapInfos) { MemoryStream ms=new MemoryStream(Convert.FromBase64String(b.Text)); BoxReader reader=new BoxReader(ms); b.Info=new BootStrap(); string name; reader.ReadHeader(out name); reader.ReadBootStrap(b.Info); reader.Dispose(); } foreach (Media m in Medias) { MemoryStream ms=new MemoryStream(Convert.FromBase64String(m.Metadata)); AmfReader reader=new AmfReader(ms); m.MetadataInfo = reader.ReadObject<Metadata>(); reader.Dispose(); m.Info = BootstrapInfos.First(a => a.Id == m.BootstrapInfoId); } }
public static bool ReadFrom(AmfReader reader, Type type, ChunkStream.Snapshot previous, out ChunkStream.Snapshot next) { next = default(ChunkStream.Snapshot); next.Ready = true; next.ChunkStreamId = previous.ChunkStreamId; if (!reader.HasLength(TypeByteLengths[(byte)type])) { return(false); } switch (type) { case Type.Type0: next.Timestamp = reader.ReadUInt24(); next.MessageLength = (int)reader.ReadUInt24(); next.ContentType = (PacketContentType)reader.ReadByte(); next.MessageStreamId = (uint)reader.ReadLittleEndianInt(); return(MaybeReadExtraTimestamp(ref next.Timestamp)); case Type.Type1: next.Timestamp = reader.ReadUInt24(); next.MessageLength = (int)reader.ReadUInt24(); next.ContentType = (PacketContentType)reader.ReadByte(); next.MessageStreamId = previous.MessageStreamId; return(MaybeReadExtraTimestamp(ref next.Timestamp)); case Type.Type2: next.Timestamp = reader.ReadUInt24(); next.MessageLength = previous.MessageLength; next.ContentType = previous.ContentType; next.MessageStreamId = previous.MessageStreamId; return(MaybeReadExtraTimestamp(ref next.Timestamp)); case Type.Type3: next.Timestamp = previous.Timestamp; next.MessageLength = previous.MessageLength; next.ContentType = previous.ContentType; next.MessageStreamId = previous.MessageStreamId; return(true); default: throw new ArgumentOutOfRangeException(nameof(type), "unknown type"); } bool MaybeReadExtraTimestamp(ref uint timestamp) { if (timestamp != ExtendedTimestampSentinel) { return(true); } if (!reader.HasLength(4)) { return(false); } timestamp = (uint)reader.ReadInt32(); return(true); } }