public static IEnumerable <int> GetPauseTicks(SourceDemo demo) { return (from packet in demo.FilterForPacket <Packet>() from messageTup in packet.MessageStream where messageTup.message?.GetType() == typeof(SvcSetPause) where ((SvcSetPause)messageTup.message).IsPaused select packet.Tick); }
public static void ChangeDemoDir(SourceDemo demo, Stream s, string newDir) { void Write(byte[] buf) => s.Write(buf, 0, buf.Length); string old = demo.Header.GameDirectory; if (old == newDir) { // no difference Write(demo.Reader.Data); return; } int lenDiff = newDir.Length - old.Length; BitStreamReader bsr = demo.Reader; byte[] dirBytes = Encoding.ASCII.GetBytes(newDir); Write(bsr.ReadBytes(796)); Write(dirBytes); // header doesn't matter but I change it anyway Write(new byte[260 - newDir.Length]); bsr.SkipBytes(260); Write(bsr.ReadBytes(12)); byte[] tmp = BitConverter.GetBytes((uint)(bsr.ReadUInt() + lenDiff)); if (!BitConverter.IsLittleEndian) { tmp = tmp.Reverse().ToArray(); } Write(tmp); foreach (SignOn signOn in demo.FilterForPacket <SignOn>().Where(signOn => signOn.FilterForMessage <SvcServerInfo>().Any())) { // catch up to signOn packet int byteCount = (signOn.Reader.AbsoluteBitIndex - bsr.AbsoluteBitIndex) / 8; Write(bsr.ReadBytes(byteCount)); bsr.SkipBits(signOn.Reader.BitLength); BitStreamWriter bsw = new BitStreamWriter(); BitStreamReader signOnReader = signOn.Reader; bsw.WriteBits(signOnReader.ReadRemainingBits()); signOnReader = signOnReader.FromBeginning(); int bytesToMessageStreamSize = demo.DemoInfo.MaxSplitscreenPlayers * 76 + 8; signOnReader.SkipBytes(bytesToMessageStreamSize); // edit the message stream length - read uint, and edit at index before the reading of said uint bsw.EditIntAtIndex((int)(signOnReader.ReadUInt() + lenDiff), signOnReader.CurrentBitIndex - 32, 32); // actually change the game dir SvcServerInfo serverInfo = signOn.FilterForMessage <SvcServerInfo>().Single(); int editIndex = serverInfo.GameDirBitIndex - signOn.Reader.AbsoluteBitIndex; bsw.RemoveBitsAtIndex(editIndex, old.Length * 8); bsw.InsertBitsAtIndex(dirBytes, editIndex, newDir.Length * 8); Write(bsw.AsArray); } Write(bsr.ReadRemainingBits().bytes); }
// writes a edited version of the given demo to the stream // this is all manually hacked together and it shall stay this way for now public static void RemoveCaptions(SourceDemo demo, Stream s) { void Write(byte[] buf) => s.Write(buf, 0, buf.Length); Packet[] closeCaptionPackets = demo.FilterForPacket <Packet>() .Where(packet => packet.FilterForMessage <SvcUserMessage>() .Any(frame => frame.MessageType == UserMessageType.CloseCaption)).ToArray(); if (closeCaptionPackets.Length == 0) { Write(demo.Reader.Data); return; } int changedPackets = 0; Write(demo.Header.Reader.ReadRemainingBits().bytes); foreach (PacketFrame frame in demo.Frames) { if (frame.Packet != closeCaptionPackets[changedPackets]) { Write(frame.Reader.ReadRemainingBits().bytes); // write frames that aren't changed } else { Packet p = (Packet)frame.Packet; BitStreamWriter bsw = new BitStreamWriter(frame.Reader.BitLength / 8); var last = p.MessageStream.Last().message; int len = last.Reader.AbsoluteStart - frame.Reader.AbsoluteStart + last.Reader.BitLength; bsw.WriteBits(frame.Reader.ReadBits(len), len); int msgSizeOffset = p.MessageStream.Reader.AbsoluteStart - frame.Reader.AbsoluteStart; int typeInfoLen = demo.DemoInfo.NetMsgTypeBits + demo.DemoInfo.UserMessageLengthBits + 8; bsw.RemoveBitsAtIndices(p.FilterForUserMessage <CloseCaption>() .Select(caption => (caption.Reader.AbsoluteStart - frame.Reader.AbsoluteStart - typeInfoLen, caption.Reader.BitLength + typeInfoLen))); bsw.WriteUntilByteBoundary(); bsw.EditIntAtIndex((bsw.BitLength - msgSizeOffset - 32) >> 3, msgSizeOffset, 32); Write(bsw.AsArray); // if we've edited all the packets, write the rest of the data in the demo if (++changedPackets == closeCaptionPackets.Length) { BitStreamReader tmp = demo.Reader; tmp.SkipBits(frame.Reader.AbsoluteStart + frame.Reader.BitLength); Write(tmp.ReadRemainingBits().bytes); break; } } } }
public static void WriteDataTableDump(StreamWriter sw, SourceDemo demo, DataTableDumpMode mode) { PrettyStreamWriter pw = new PrettyStreamWriter(sw.BaseStream); int datatablesCount = 0; // spliced demos have more than one datatables packet foreach (DataTables dataTables in demo.FilterForPacket <DataTables>()) { if (datatablesCount++ > 0) { pw.Append("\n\n\n"); } if (mode != DataTableDumpMode.TreeOnly) { switch (mode) { case DataTableDumpMode.PacketAndTree: dataTables.PrettyWrite(pw); break; case DataTableDumpMode.Flattened: var tableParser = new DataTableParser(demo, dataTables); tableParser.FlattenClasses(false); foreach ((ServerClass sClass, List <FlattenedProp> fProps) in tableParser.FlattenedProps !) { pw.AppendLine($"{sClass.ClassName} ({sClass.DataTableName}) ({fProps.Count} props):"); for (var i = 0; i < fProps.Count; i++) { FlattenedProp fProp = fProps[i]; pw.Append($"\t({i}): "); pw.Append(fProp.TypeString().PadRight(12)); pw.AppendLine(fProp.ArrayElementPropInfo == null ? fProp.PropInfo.ToStringNoType() : fProp.ArrayElementPropInfo.ToStringNoType()); } } break; default: throw new ArgProcessProgrammerException($"invalid data table dump mode: \"{mode}\""); } } if (mode != DataTableDumpMode.TreeOnly) { pw.Append("\n\n\n"); } pw.Append("Datatable hierarchy:\n\n\n"); new DataTableTree(dataTables, true).PrettyWrite(pw); } // see note at PrettyStreamWriter pw.Flush(); }
public static void WriteStringTableDump(StreamWriter sw, SourceDemo demo) { PrettyStreamWriter pw = new PrettyStreamWriter(sw.BaseStream); int stringTablesCount = 0; // spliced demos surely have more than one stringtables packet (unsure about regular demos) foreach (StringTables stringTablesPacket in demo.FilterForPacket <StringTables>()) { if (stringTablesCount++ > 0) { pw.Append("\n\n\n"); } stringTablesPacket.PrettyWrite(pw); } // see note at PrettyStreamWriter pw.Flush(); }
public static IEnumerable <(int tick, string repr)> GetUserInputs(SourceDemo demo, InputDisplayMode mode) { Buttons?prevButtons = null; int prevTick = int.MinValue; foreach (UserCmd u in demo.FilterForPacket <UserCmd>()) { Buttons?b = u.Buttons; // don't want pauses taking up all the space if (prevButtons == b && prevTick == u.Tick) { continue; } prevButtons = b; prevTick = u.Tick; yield return(mode switch { InputDisplayMode.Text => (u.Tick, b?.ToString() ?? "none"), InputDisplayMode.Int => (u.Tick, b.HasValue ? ((uint)b.Value).ToString() : "0"), InputDisplayMode.Flags => (u.Tick, Convert.ToString(b.HasValue ? (uint)b.Value : 0, 2).PadLeft(32, '0')), _ => throw new ArgProcessProgrammerException($"invalid input display mode: \"{mode}\"") });