public void PlayDemo(string filename) { if (File.Exists(filename)) { this.connState = ConnectionState.Connected; this.demoPlaying = true; this.demoFileStream = File.OpenRead(filename); this.demoStream = new Q3DemoStream(this.demoFileStream, this); this.q3HuffDemoReadStream = new Q3HuffmanStream(this.demoStream, CompressionMode.Decompress); q3HuffDemoReadStream.InitWithQ3Data(); q3HuffDemoReadStream.TreeIsFrozen = true; try { while (this.connState >= ConnectionState.Connected && this.connState < ConnectionState.Primed) { ReadPacket(this.q3HuffDemoReadStream); } this.firstDemoFrameSkipped = false; while (true) { ReadPacket(this.q3HuffDemoReadStream); } } catch (Exception ex) { // Demo end } } else { throw new FileNotFoundException("Demo file not found", filename); } }
private void ReadPacket(Q3HuffmanStream stream) { List <string> cmdLog = new List <string> (); PacketKind pktKind = stream.BeginRead(); this.reliableAcknowledge = stream.ReadInt32(); ServerCommandType cmd; while (ServerCommandType.EOF != (cmd = ( ServerCommandType )stream.ReadByte())) { switch (cmd) { case ServerCommandType.Nop: cmdLog.Add("Nop"); break; case ServerCommandType.ServerCommand: this.ParseCommandString(stream); cmdLog.Add("ServerCommand"); break; case ServerCommandType.GameState: this.ParseGamestate(stream); cmdLog.Add("GameState"); break; case ServerCommandType.Snapshot: this.ParseSnapshot(stream); cmdLog.Add("Snapshot"); break; case ServerCommandType.Download: // We never download ;) return; default: // Unknown command return; } } stream.EndRead(); }
public void Connect(IPAddress ip, int port) { Random rnd = new Random(); qport = ( ushort )rnd.Next(65536); // Generate port from 0 to 65535 (inclusively) // STATIC_DEBUG //qport = 0x2233; // ( short ) rnd.Next ( 65536 ); // Generate port from 0 to 65535 (inclusively) incomingSequence = 0; outgoingSequence = 1; IPEndPoint ep = new IPEndPoint(ip, port); sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); try { sock.Connect(ep); } catch { throw; } q3NetStream = new Q3NetworkStream(sock, this, FileAccess.ReadWrite); q3CryptStream = new Q3CryptStream(q3NetStream, this, FileAccess.ReadWrite); q3HuffCStream = new Q3HuffmanStream(q3CryptStream, CompressionMode.Compress); q3HuffCStream.InitWithQ3Data(); q3HuffCStream.TreeIsFrozen = true; q3HuffDStream = new Q3HuffmanStream(q3CryptStream, CompressionMode.Decompress); q3HuffDStream.InitWithQ3Data(); q3HuffDStream.TreeIsFrozen = true; if (DoConnectionlessHandshake()) { // enter main loop SendUserInfo(); // FIXIT: move this out of here while (true) { ReadPacket(q3HuffDStream); SendCmd(); System.Threading.Thread.Sleep(10); } } else { throw new Exception(string.Format("Could not connect to {0}", ep)); } }
private bool DoConnectionlessHandshake() { string command; byte [] data; this.connState = ConnectionState.Connecting; WriteConnectionlessPacket("getchallenge", null, 0, 0); if (ReadConnectionlessPacket(out command, out data)) { if (command == "challengeResponse") { this.challenge = Convert.ToInt32(Encoding.Default.GetString(data)); MemoryStream ms = new MemoryStream(); ms.Position = 2; Q3HuffmanStream huff = new Q3HuffmanStream(ms, System.IO.Compression.CompressionMode.Compress); string connStr = string.Format(@"\challenge\{0}\qport\{1}\protocol\{2}{3}", this.challenge, this.qport, PROTOCOL_VERSION, userInfo); huff.WriteString(connStr); huff.Flush(); ms.Position = 0; ms.Write(ExBitConverter.GetBytes(( short )connStr.Length, false), 0, 2); this.connState = ConnectionState.Challenging; WriteConnectionlessPacket("connect ", ms.GetBuffer(), 0, ( int )ms.Length); if (ReadConnectionlessPacket(out command, out data)) { if (command == "connectResponse") { this.connState = ConnectionState.Connected; return(true); } } } } return(false); }
private void ParseGamestate(Q3HuffmanStream stream) { this.gameState.Clear(); this.incomingCommandSequence = stream.ReadInt32(); EntityState nullstate = new EntityState(); ServerCommandType cmd; entityBaselinesEvent.WaitOne(); while (ServerCommandType.EOF != (cmd = ( ServerCommandType )stream.ReadByte())) { switch (cmd) { case ServerCommandType.ConfigString: int i = stream.ReadInt16(); gameState [i] = stream.ReadString(); break; case ServerCommandType.BaseLine: int newnum = stream.ReadBits(10); ReadDeltaEntity(stream, nullstate, ref entityBaselines [newnum], newnum); break; default: // Unknown command break; } } entityBaselinesEvent.Set(); this.clientNum = stream.ReadInt32(); this.checksumFeed = stream.ReadInt32(); SystemInfoChanged(); this.connState = ConnectionState.Primed; //SendPureChecksums (); }
private void ParseCommandString(Q3HuffmanStream stream) { this.incomingCommandSequence = stream.ReadInt32(); string cmdStr = stream.ReadString(); int index = this.incomingCommandSequence & (MAX_RELIABLE_COMMANDS - 1); this.incomingReliableCommands [index] = cmdStr; if (cmdStr.StartsWith("cs ")) { string [] parts = cmdStr.Split(' '); int csId; if (parts.Length >= 2) { try { csId = Convert.ToInt32(parts [1]); } catch { csId = 0; } this.gameState [csId] = string.Join(" ", parts, 2, parts.Length - 2); if (csId == 1) { SystemInfoChanged(); } } } if (this.ServerCommandReceived != null) { this.ServerCommandReceived(this, cmdStr); } }
private void DeltaEntity(Q3HuffmanStream stream, Snapshot frame, int newNum, EntityState old, bool unchanged) { EntityState state; // save the parsed entity state into the big circular buffer so // it can be used as the source for a later delta state = this.parseEntities [this.parseEntitiesNum & (MAX_PARSE_ENTITIES - 1)]; if (unchanged) { old.CopyTo(state); } else { this.ReadDeltaEntity(stream, old, ref state, newNum); } if (state.number == (MAX_GENTITIES - 1)) { return; // entity was delta removed } this.parseEntitiesNum++; frame.numEntities++; }
private void ReadDeltaPlayerstate(Q3HuffmanStream stream, PlayerState from, ref PlayerState to) { int i; if (from == null) { from = new PlayerState(); } from.CopyTo(to); int lc = stream.ReadByte(); NetField field; int trunc; for (i = 0, field = PlayerState.fields [i]; i < lc; i++) { field = PlayerState.fields [i]; if (stream.ReadBits(1) == 0) { // no change KeyValueCoder.TrySetFieldValue(to, field.name, KeyValueCoder.TryGetFieldValue(from, field.name)); } else { if (field.bits == 0) { // float if (stream.ReadBits(1) == 0) { // integral float trunc = stream.ReadBits(NetField.FLOAT_INT_BITS); // bias to allow equal parts positive and negative trunc -= NetField.FLOAT_INT_BIAS; KeyValueCoder.TrySetFieldValue(to, field.name, trunc); } else { // full floating point value // FIXIT: wrong conversion from 32 bits to floating point value KeyValueCoder.TrySetFieldValue(to, field.name, stream.ReadInt32()); } } else { // integer KeyValueCoder.TrySetFieldValue(to, field.name, stream.ReadBits(( int )field.bits)); } } } for (i = lc, field = PlayerState.fields [lc]; i < PlayerState.fields.Length; i++) { field = PlayerState.fields [i]; // no change KeyValueCoder.TrySetFieldValue(to, field.name, KeyValueCoder.TryGetFieldValue(from, field.name)); } short bits; // read the arrays if (0 != stream.ReadBits(1)) { // parse stats if (0 != stream.ReadBits(1)) { bits = stream.ReadInt16(); for (i = 0; i < 16; i++) { if (0 != (bits & (1 << i))) { to.stats [i] = stream.ReadInt16(); } } } // parse persistant stats if (0 != stream.ReadBits(1)) { bits = stream.ReadInt16(); for (i = 0; i < 16; i++) { if (0 != (bits & (1 << i))) { to.persistant [i] = stream.ReadInt16(); } } } // parse ammo if (0 != stream.ReadBits(1)) { bits = stream.ReadInt16(); for (i = 0; i < 16; i++) { if (0 != (bits & (1 << i))) { to.ammo [i] = stream.ReadInt16(); } } } // parse powerups if (0 != stream.ReadBits(1)) { bits = stream.ReadInt16(); for (i = 0; i < 16; i++) { if (0 != (bits & (1 << i))) { to.powerups [i] = stream.ReadInt32(); } } } } }
private void ParseSnapshot(Q3HuffmanStream stream) { Snapshot old; Snapshot newSnap = new Snapshot(); int deltaNum; newSnap.serverCommandNum = this.incomingCommandSequence; newSnap.serverTime = stream.ReadInt32(); newSnap.messageNum = this.incomingSequence; if (0 == (deltaNum = stream.ReadByte())) { newSnap.deltaNum = -1; } else { newSnap.deltaNum = newSnap.messageNum - deltaNum; } newSnap.snapFlags = stream.ReadByte(); // If the frame is delta compressed from data that we // no longer have available, we must suck up the rest of // the frame, but not use it, then ask for a non-compressed // message if (newSnap.deltaNum <= 0) { newSnap.valid = true; // uncompressed frame old = null; //clc.demowaiting = false; // we can start recording now } else { old = this.snapshots [newSnap.deltaNum & PACKET_MASK]; if (!old.valid) { // should never happen // "Delta from invalid frame (not supposed to happen!)" } else if (old.messageNum != newSnap.deltaNum) { // The frame that the server did the delta from // is too old, so we can't reconstruct it properly. // "Delta frame too old." } else if (parseEntitiesNum - old.parseEntitiesNum > MAX_PARSE_ENTITIES - 128) { // "Delta parseEntitiesNum too old." } else { newSnap.valid = true; // valid delta parse } } // read areamask int len = stream.ReadByte(); stream.Read(newSnap.areamask, 0, len); // read playerinfo if (old != null) { ReadDeltaPlayerstate(stream, old.ps, ref newSnap.ps); } else { ReadDeltaPlayerstate(stream, null, ref newSnap.ps); } // read packet entities ParsePacketEntities(stream, old, newSnap); // if not valid, dump the entire thing now that it has // been properly read if (!newSnap.valid) { return; } // clear the valid flags of any snapshots between the last // received and this one, so if there was a dropped packet // it won't look like something valid to delta from next // time we wrap around in the buffer int oldMessageNum = this.snap.messageNum + 1; if (newSnap.messageNum - oldMessageNum >= PACKET_BACKUP) { oldMessageNum = newSnap.messageNum - (PACKET_BACKUP - 1); } for ( ; oldMessageNum < newSnap.messageNum; oldMessageNum++) { this.snapshots [oldMessageNum & PACKET_MASK].valid = false; } // copy to the current good spot this.snap = newSnap; this.snap.ping = 999; // calculate ping time for (int i = 0; i < PACKET_BACKUP; i++) { int packetNum = (this.outgoingSequence - 1 - i) & PACKET_MASK; if (this.snap.ps.commandTime >= this.outPackets [packetNum].serverTime) { this.snap.ping = ( int )(DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond) - this.outPackets[packetNum].realtime; break; } } // save the frame off in the backup array for later delta comparisons this.snapshots [this.snap.messageNum & PACKET_MASK] = this.snap; this.newSnapshots = true; }
private void ReadDeltaEntity(Q3HuffmanStream stream, EntityState from, ref EntityState to, int number) { // check for a remove if (stream.ReadBits(1) == 1) { new EntityState().CopyTo(to); to.number = MAX_GENTITIES - 1; return; } // check for no delta if (stream.ReadBits(1) == 0) { from.CopyTo(to); to.number = number; return; } int lc = stream.ReadByte(); to.number = number; int i; NetField field; int trunc; for (i = 0, field = EntityState.fields [i]; i < lc && i < EntityState.fields.Length; i++) { field = EntityState.fields [i]; if (stream.ReadBits(1) == 0) { // no change KeyValueCoder.TrySetFieldValue(to, field.name, KeyValueCoder.TryGetFieldValue(from, field.name)); } else { if (field.bits == 0) { // float if (stream.ReadBits(1) == 0) { KeyValueCoder.TrySetFieldValue(to, field.name, 0); } else { if (stream.ReadBits(1) == 0) { // integral float trunc = stream.ReadBits(NetField.FLOAT_INT_BITS); // bias to allow equal parts positive and negative trunc -= NetField.FLOAT_INT_BIAS; KeyValueCoder.TrySetFieldValue(to, field.name, trunc); } else { // full floating point value // FIXIT: wrong conversion from 32 bits to floating point value KeyValueCoder.TrySetFieldValue(to, field.name, stream.ReadInt32()); } } } else { if (stream.ReadBits(1) == 0) { KeyValueCoder.TrySetFieldValue(to, field.name, 0); } else { // integer KeyValueCoder.TrySetFieldValue(to, field.name, stream.ReadBits(( int )field.bits)); } } } } for (i = lc; i < EntityState.fields.Length; i++) { field = EntityState.fields [i]; // no change KeyValueCoder.TrySetFieldValue(to, field.name, KeyValueCoder.TryGetFieldValue(from, field.name)); } }
private void ParsePacketEntities(Q3HuffmanStream stream, Snapshot oldFrame, Snapshot newFrame) { EntityState oldState = null; int oldIndex = 0; int oldNum; parseEntitiesEvent.WaitOne(); entityBaselinesEvent.WaitOne(); newFrame.parseEntitiesNum = this.parseEntitiesNum; newFrame.numEntities = 0; if (null == oldFrame) { oldNum = 99999; } else { if (oldIndex >= oldFrame.numEntities) { oldNum = 99999; } else { oldState = this.parseEntities [(oldFrame.parseEntitiesNum + oldIndex) & (MAX_PARSE_ENTITIES - 1)]; oldNum = oldState.number; } } int newNum; while ((MAX_GENTITIES - 1) != (newNum = stream.ReadBits(GENTITYNUM_BITS))) { while (oldNum < newNum) { // one or more entities from the old packet are unchanged DeltaEntity(stream, newFrame, oldNum, oldState, true); oldIndex++; if (oldIndex >= oldFrame.numEntities) { oldNum = 99999; } else { oldState = this.parseEntities[(oldFrame.parseEntitiesNum + oldIndex) & (MAX_PARSE_ENTITIES - 1)]; oldNum = oldState.number; } } if (oldNum == newNum) { // delta from previous DeltaEntity(stream, newFrame, newNum, oldState, false); oldIndex++; if (oldIndex >= oldFrame.numEntities) { oldNum = 99999; } else { oldState = this.parseEntities[(oldFrame.parseEntitiesNum + oldIndex) & (MAX_PARSE_ENTITIES - 1)]; oldNum = oldState.number; } continue; } if (oldNum > newNum) { // delta from baseline DeltaEntity(stream, newFrame, newNum, this.entityBaselines [newNum], false); continue; } } // any remaining entities in the old frame are copied over while (oldNum != 99999) { // one or more entities from the old packet are unchanged DeltaEntity(stream, newFrame, oldNum, oldState, true); oldIndex++; if (oldIndex >= oldFrame.numEntities) { oldNum = 99999; } else { oldState = this.parseEntities[(oldFrame.parseEntitiesNum + oldIndex) & (MAX_PARSE_ENTITIES - 1)]; oldNum = oldState.number; } } entityBaselinesEvent.Set(); parseEntitiesEvent.Set(); }
// // Summary: // Writes encoded bytes to the underlying stream from the specified byte // array. // // Parameters: // array: // The array used to store encoded bytes. // // offset: // The location in the array to begin reading. // // count: // The number of bytes encoded. // // Exceptions: // System.ArgumentNullException: // array is null. // // System.InvalidOperationException: // The Q3Cypher.CypherMode value was Decode when the object // was created. - or - The underlying stream does not support writing. // // System.ArgumentOutOfRangeException: // offset or count is less than zero. -or- array length minus the index starting // point is less than count. // // System.ObjectDisposedException: // The stream is closed. public override void Write(byte [] array, int offset, int count) { #region Exception checks if (array == null) { throw new ArgumentNullException("array"); } if (baseStream == null) { throw new ObjectDisposedException("baseStream"); } if (mode == CypherMode.Decode) { throw new InvalidOperationException("Stream cannot be written while its mode set to Q3Cypher.CypherMode.Decode"); } if (!baseStream.CanWrite) { throw new InvalidOperationException("Underlying stream cannot be written"); } if (offset < 0) { throw new ArgumentOutOfRangeException("offset"); } if (count < 0) { throw new ArgumentOutOfRangeException("count"); } if (array.Length < offset) { throw new ArgumentOutOfRangeException("offset"); } if (array.Length < offset + count) { throw new ArgumentOutOfRangeException("count"); } #endregion Exception checks (baseStream as Q3DatagramStream).BeginWritePacket(PacketKind.ConnectionOriented); Q3HuffmanStream huff = connection.Q3HuffDStream; int bloc = 0; int serverId = huff.DecompressBufferToInt32(array, 0, ref bloc); int messageAcknowledge = huff.DecompressBufferToInt32(array, 0, ref bloc); int reliableAcknowledge = huff.DecompressBufferToInt32(array, 0, ref bloc); byte [] buffer = new byte [count]; Array.Copy(array, buffer, CL_ENCODE_START < count ? CL_ENCODE_START : count); string pattern = connection.IncomingReliableCommands [reliableAcknowledge & (Q3Connection.MAX_RELIABLE_COMMANDS - 1)]; int index = 0; byte key = ( byte )(connection.Challenge ^ serverId ^ messageAcknowledge); for (int i = CL_ENCODE_START; i < count; i++) { // modify the key with the last received now acknowledged server command if (pattern == null || index >= pattern.Length) { index = 0; } if (pattern != null && (pattern [index] > 127 || pattern [index] == '%')) { key ^= ( byte )('.' << (i & 1)); } else { key ^= ( byte )((pattern != null ? pattern [index] : 0) << (i & 1)); } index++; // encode the data with this key buffer [i] = ( byte )(array [i + offset] ^ key); } baseStream.Write(buffer, 0, count); }
// // Summary: // Reads a number of decoded bytes into the specified byte array. // // Parameters: // array: // The array used to store decoded bytes. // // offset: // The byte offset in array at which to begin writing data read from the stream. // // count: // The number of bytes to decode. // // Returns: // The number of bytes that were decoded into the byte array. If the end // of the stream has been reached, zero or the number of bytes read is returned. // // Exceptions: // System.ArgumentNullException: // array is null. // // System.InvalidOperationException: // The Q3Cypher.CypherMode value was Encode when the object // was created. - or - The underlying stream does not support reading. // // System.ArgumentOutOfRangeException: // offset or count is less than zero. -or- array length minus the index starting // point is less than count. // // System.ObjectDisposedException: // The stream is closed. public override int Read(byte [] array, int offset, int count) { #region Exception checks if (array == null) { throw new ArgumentNullException("array"); } if (baseStream == null) { throw new ObjectDisposedException("baseStream"); } if (mode == CypherMode.Encode) { throw new InvalidOperationException("Stream cannot be read while its mode set to CypherMode.Encode"); } if (!baseStream.CanRead) { throw new InvalidOperationException("Underlying stream cannot be read"); } if (offset < 0) { throw new ArgumentOutOfRangeException("offset"); } if (count < 0) { throw new ArgumentOutOfRangeException("count"); } if (array.Length < offset) { throw new ArgumentOutOfRangeException("offset"); } if (array.Length < offset + count) { throw new ArgumentOutOfRangeException("count"); } #endregion Exception checks if (this.readPosition == 0) { (( Q3DatagramStream )baseStream).BeginReadPacket(); } int bytesRead = baseStream.Read(array, offset, count); int encStart = 0, encEnd = 0; if (this.readPosition < CL_DECODE_START) { Array.Copy(array, offset, this.readBuffer, this.readPosition, bytesRead + this.readPosition > CL_DECODE_START ? CL_DECODE_START - this.readPosition : bytesRead); if (this.readPosition + bytesRead >= CL_DECODE_START) { Q3HuffmanStream huff = connection.Q3HuffDStream; int bloc = 0; this.readReliableAcknowledge = huff.DecompressBufferToInt32(this.readBuffer, 0, ref bloc); encStart = CL_DECODE_START - this.readPosition + offset; encEnd = bytesRead + offset; this.readPattern = connection.OutgoingReliableCommands [this.readReliableAcknowledge & (Q3Connection.MAX_RELIABLE_COMMANDS - 1)]; this.readKey = ( byte )(connection.Challenge ^ connection.IncomingSequence); this.readPatternIndex = 0; } } else { encStart = offset; encEnd = bytesRead; } for (int i = encStart; i < encEnd; i++) { // modify the key with the last received now acknowledged server command if (this.readPattern == null || this.readPatternIndex >= this.readPattern.Length) { this.readPatternIndex = 0; } if (this.readPattern != null && (this.readPattern [this.readPatternIndex] > 127 || this.readPattern [this.readPatternIndex] == '%')) { this.readKey ^= ( byte )('.' << (i & 1)); } else { this.readKey ^= ( byte )((this.readPattern != null ? this.readPattern [this.readPatternIndex] : 0) << (i & 1)); } this.readPatternIndex++; array [i] = ( byte )(array [i] ^ this.readKey); } this.readPosition += bytesRead; //string dump = BufferInfo.DumpBuffer ( "Decrypted buffer", array, offset, bytesRead, true, true, true ); //Console.Write ( dump ); return(bytesRead); }