From afc4dc610ec3dcc7107505c1bd186e2ba16632dc Mon Sep 17 00:00:00 2001 From: Ryan Stecker Date: Fri, 4 May 2012 19:00:57 -0500 Subject: [PATCH] Initial implementation of improved GC message handling. --HG-- branch : gc --- SteamKit2/SteamKit2/Base/GC/ClientMsgGC.cs | 175 +++++++ SteamKit2/SteamKit2/Base/GC/MsgBaseGC.cs | 480 ++++++++++++++++++ SteamKit2/SteamKit2/Base/GC/PacketBaseGC.cs | 206 ++++++++ .../SteamGameCoordinator/Callbacks.cs | 23 +- .../SteamGameCoordinator.cs | 9 +- .../SteamClient/CallbackMgr/CallbackMgr.cs | 2 +- SteamKit2/SteamKit2/SteamKit2.csproj | 3 + 7 files changed, 890 insertions(+), 8 deletions(-) create mode 100644 SteamKit2/SteamKit2/Base/GC/ClientMsgGC.cs create mode 100644 SteamKit2/SteamKit2/Base/GC/MsgBaseGC.cs create mode 100644 SteamKit2/SteamKit2/Base/GC/PacketBaseGC.cs diff --git a/SteamKit2/SteamKit2/Base/GC/ClientMsgGC.cs b/SteamKit2/SteamKit2/Base/GC/ClientMsgGC.cs new file mode 100644 index 00000000..f491a60d --- /dev/null +++ b/SteamKit2/SteamKit2/Base/GC/ClientMsgGC.cs @@ -0,0 +1,175 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'license.txt', which is part of this source code package. + */ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using ProtoBuf; +using SteamKit2.Internal.GC; +using MsgGCHdrProtoBuf = SteamKit2.Internal.MsgGCHdrProtoBuf; + +namespace SteamKit2.GC +{ + /// + /// Represents a protobuf backed client message. + /// + /// The body type of this message. + public sealed class ClientGCMsgProtobuf : GCMsgBase + where BodyType : IExtensible, new() + { + /// + /// Gets a value indicating whether this client message is protobuf backed. + /// Client messages of this type are always protobuf backed. + /// + /// + /// true if this instance is protobuf backed; otherwise, false. + /// + public override bool IsProto { get { return true; } } + /// + /// Gets the network message type of this client message. + /// + /// + /// The network message type. + /// + public override uint MsgType { get { return Header.Msg; } } + + /// + /// Gets or sets the session id for this client message. + /// + /// + /// The session id. + /// + public override int SessionID + { + get { return ProtoHeader.client_session_id; } + set { ProtoHeader.client_session_id = value; } + } + /// + /// Gets or sets the for this client message. + /// + /// + /// The . + /// + public override SteamID SteamID + { + get { return ProtoHeader.client_steam_id; } + set { ProtoHeader.client_steam_id = value; } + } + + /// + /// Gets or sets the target job id for this client message. + /// + /// + /// The target job id. + /// + public override ulong TargetJobID + { + get { return ProtoHeader.job_id_target; } + set { ProtoHeader.job_id_target = value; } + } + /// + /// Gets or sets the source job id for this client message. + /// + /// + /// The source job id. + /// + public override ulong SourceJobID + { + get { return ProtoHeader.job_id_source; } + set { ProtoHeader.job_id_source = value; } + } + + + /// + /// Shorthand accessor for the protobuf header. + /// + public CMsgProtoBufHeader ProtoHeader { get { return Header.Proto; } } + + /// + /// Gets the body structure of this message. + /// + public BodyType Body { get; private set; } + + + /// + /// Initializes a new instance of the class. + /// This is a client send constructor. + /// + /// The network message type this client message represents. + /// The number of bytes to initialize the payload capacity to. + public ClientGCMsgProtobuf( uint eMsg, int payloadReserve = 64 ) + : base( payloadReserve ) + { + Body = new BodyType(); + + // set our emsg + Header.Msg = eMsg; + } + + /// + /// Initializes a new instance of the class. + /// This a reply constructor. + /// + /// The network message type this client message represents. + /// The message that this instance is a reply for. + /// The number of bytes to initialize the payload capacity to. + public ClientGCMsgProtobuf( uint eMsg, GCMsgBase msg, int payloadReserve = 64 ) + : this( eMsg, payloadReserve ) + { + // our target is where the message came from + Header.Proto.job_id_target = msg.Header.Proto.job_id_source; + } + + /// + /// Initializes a new instance of the class. + /// This is a recieve constructor. + /// + /// The packet message to build this client message from. + public ClientGCMsgProtobuf( IPacketGCMsg msg ) + : this( msg.MsgType ) + { + Deserialize( msg.GetData() ); + } + + /// + /// Serializes this client message instance to a byte array. + /// + /// + /// Data representing a client message. + /// + public override byte[] Serialize() + { + using ( MemoryStream ms = new MemoryStream() ) + { + Header.Serialize( ms ); + Serializer.Serialize( ms, Body ); + Payload.WriteTo( ms ); + + return ms.ToArray(); + } + } + /// + /// Initializes this client message by deserializing the specified data. + /// + /// The data representing a client message. + public override void Deserialize( byte[] data ) + { + using ( MemoryStream ms = new MemoryStream( data ) ) + { + Header.Deserialize( ms ); + Body = Serializer.Deserialize( ms ); + + // the rest of the data is the payload + int payloadOffset = ( int )ms.Position; + int payloadLen = ( int )( ms.Length - ms.Position ); + + Payload.Write( data, payloadOffset, payloadLen ); + } + } + } +} diff --git a/SteamKit2/SteamKit2/Base/GC/MsgBaseGC.cs b/SteamKit2/SteamKit2/Base/GC/MsgBaseGC.cs new file mode 100644 index 00000000..7262bd26 --- /dev/null +++ b/SteamKit2/SteamKit2/Base/GC/MsgBaseGC.cs @@ -0,0 +1,480 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'license.txt', which is part of this source code package. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Runtime.InteropServices; +using SteamKit2.Internal; + +namespace SteamKit2.GC +{ + /// + /// Represents a unified interface into client messages. + /// + public interface IClientGCMsg + { + /// + /// Gets a value indicating whether this client message is protobuf backed. + /// + /// + /// true if this instance is protobuf backed; otherwise, false. + /// + bool IsProto { get; } + /// + /// Gets the network message type of this client message. + /// + /// + /// The message type. + /// + uint MsgType { get; } + + /// + /// Gets or sets the session id for this client message. + /// + /// + /// The session id. + /// + int SessionID { get; set; } + /// + /// Gets or sets the for this client message. + /// + /// + /// The . + /// + SteamID SteamID { get; set; } + + /// + /// Gets or sets the target job id for this client message. + /// + /// + /// The target job id. + /// + ulong TargetJobID { get; set; } + /// + /// Gets or sets the source job id for this client message. + /// + /// + /// The source job id. + /// + ulong SourceJobID { get; set; } + + /// + /// Serializes this client message instance to a byte array. + /// + /// Data representing a client message. + byte[] Serialize(); + /// + /// Initializes this client message by deserializing the specified data. + /// + /// The data representing a client message. + void Deserialize( byte[] data ); + } + + /// + /// This is the abstract base class for all available client messages. + /// It's used to maintain packet payloads and provide a header for all client messages. + /// + /// The header type for this client message. + public abstract class GCMsgBase : IClientGCMsg + where HdrType : IGCSerializableHeader, new() + { + /// + /// Gets a value indicating whether this client message is protobuf backed. + /// + /// + /// true if this instance is protobuf backed; otherwise, false. + /// + public abstract bool IsProto { get; } + /// + /// Gets the network message type of this client message. + /// + /// + /// The network message type. + /// + public abstract uint MsgType { get; } + + /// + /// Gets or sets the session id for this client message. + /// + /// + /// The session id. + /// + public abstract int SessionID { get; set; } + /// + /// Gets or sets the for this client message. + /// + /// + /// The . + /// + public abstract SteamID SteamID { get; set; } + + /// + /// Gets or sets the target job id for this client message. + /// + /// + /// The target job id. + /// + public abstract ulong TargetJobID { get; set; } + /// + /// Gets or sets the source job id for this client message. + /// + /// + /// The source job id. + /// + public abstract ulong SourceJobID { get; set; } + + + /// + /// Gets the header for this message type. + /// + public HdrType Header { get; private set; } + + /// + /// Returns a which is the backing stream for client message payload data. + /// + public MemoryStream Payload { get; private set; } + + + BinaryReader reader; + BinaryWriter writer; + + /// + /// Initializes a new instance of the class. + /// + /// The number of bytes to initialize the payload capacity to. + public GCMsgBase( int payloadReserve = 0 ) + { + Header = new HdrType(); + + Payload = new MemoryStream( payloadReserve ); + reader = new BinaryReader( Payload ); + writer = new BinaryWriter( Payload ); + } + + + /// + /// Serializes this client message instance to a byte array. + /// + /// + /// Data representing a client message. + /// + public abstract byte[] Serialize(); + /// + /// Initializes this client message by deserializing the specified data. + /// + /// The data representing a client message. + public abstract void Deserialize( byte[] data ); + + + + /// + /// Seeks within the payload to the specified offset. + /// + /// The offset in the payload to seek to. + /// The origin to seek from. + /// The new position within the stream, calculated by combining the initial reference point and the offset. + public long Seek( long offset, SeekOrigin loc ) + { + return Payload.Seek( offset, loc ); + } + + /// + /// Writes a single unsigned byte to the message payload. + /// + /// The unsigned byte. + public void Write( byte data ) + { + writer.Write( data ); + } + /// + /// Writes a single signed byte to the message payload. + /// + /// The signed byte. + public void Write( sbyte data ) + { + writer.Write( data ); + } + /// + /// Writes the specified byte array to the message payload. + /// + /// The byte array. + public void Write( byte[] data ) + { + writer.Write( data ); + } + /// + /// Writes a single 16bit short to the message payload. + /// + /// The short. + public void Write( short data ) + { + writer.Write( data ); + } + /// + /// Writes a single unsigned 16bit short to the message payload. + /// + /// The unsigned short. + public void Write( ushort data ) + { + writer.Write( data ); + } + /// + /// Writes a single 32bit integer to the message payload. + /// + /// The integer. + public void Write( int data ) + { + writer.Write( data ); + } + /// + /// Writes a single unsigned 32bit integer to the message payload. + /// + /// The unsigned integer. + public void Write( uint data ) + { + writer.Write( data ); + } + /// + /// Writes a single 64bit long to the message payload. + /// + /// The long. + public void Write( long data ) + { + writer.Write( data ); + } + /// + /// Writes a single unsigned 64bit long to the message payload. + /// + /// The unsigned long. + public void Write( ulong data ) + { + writer.Write( data ); + } + /// + /// Writes a single 32bit float to the message payload. + /// + /// The float. + public void Write( float data ) + { + writer.Write( data ); + } + /// + /// Writes a single 64bit double to the message payload. + /// + /// The double. + public void Write( double data ) + { + writer.Write( data ); + } + + /// + /// Writes the specified string to the message payload using default encoding. + /// This function does not write a terminating null character. + /// + /// The string to write. + public void Write( string data ) + { + Write( data, Encoding.Default ); + } + /// + /// Writes the specified string to the message payload using the specified encoding. + /// This function does not write a terminating null character. + /// + /// The string to write. + /// The encoding to use. + public void Write( string data, Encoding encoding ) + { + if ( data == null ) + return; + + Write( encoding.GetBytes( data ) ); + } + + /// + /// Writes the secified string and a null terminator to the message payload using default encoding. + /// + /// The string to write. + public void WriteNullTermString( string data ) + { + WriteNullTermString( data, Encoding.Default ); + } + /// + /// Writes the specified string and a null terminator to the message payload using the specified encoding. + /// + /// The string to write. + /// The encoding to use. + public void WriteNullTermString( string data, Encoding encoding ) + { + Write( data, encoding ); + Write( encoding.GetBytes( "\0" ) ); + } + + /// + /// Reads a single signed byte from the message payload. + /// + /// The signed byte. + public sbyte ReadInt8() + { + return reader.ReadSByte(); + } + /// + /// Reads a single signed byte from the message payload. + /// + /// The signed byte. + public sbyte ReadSByte() + { + return reader.ReadSByte(); + } + /// + /// Reads a single unsigned byte from the message payload. + /// + /// The unsigned byte. + public byte ReadUInt8() + { + return reader.ReadByte(); + } + /// + /// Reads a single unsigned byte from the message payload. + /// + /// The unsigned byte. + public byte ReadByte() + { + return reader.ReadByte(); + } + /// + /// Reads a number of bytes from the message payload. + /// + /// The number of bytes to read. + /// The data. + public byte[] ReadBytes( int numBytes ) + { + return reader.ReadBytes( numBytes ); + } + /// + /// Reads a single 16bit short from the message payload. + /// + /// The short. + public short ReadInt16() + { + return reader.ReadInt16(); + } + /// + /// Reads a single 16bit short from the message payload. + /// + /// The short. + public short ReadShort() + { + return reader.ReadInt16(); + } + /// + /// Reads a single unsigned 16bit short from the message payload. + /// + /// The unsigned short. + public ushort ReadUInt16() + { + return reader.ReadUInt16(); + } + /// + /// Reads a single unsigned 16bit short from the message payload. + /// + /// The unsigned short. + public ushort ReadUShort() + { + return reader.ReadUInt16(); + } + /// + /// Reads a single 32bit integer from the message payload. + /// + /// The integer. + public int ReadInt32() + { + return reader.ReadInt32(); + } + /// + /// Reads a single 32bit integer from the message payload. + /// + /// The integer. + public int ReadInt() + { + return reader.ReadInt32(); + } + /// + /// Reads a single unsigned 32bit integer from the message payload. + /// + /// The unsigned integer. + public uint ReadUInt32() + { + return reader.ReadUInt32(); + } + /// + /// Reads a single unsigned 32bit integer from the message payload. + /// + /// The unsigned integer. + public uint ReadUInt() + { + return reader.ReadUInt32(); + } + /// + /// Reads a single 64bit long from the message payload. + /// + /// The long. + public long ReadInt64() + { + return reader.ReadInt64(); + } + /// + /// Reads a single 64bit long from the message payload. + /// + /// The long. + public long ReadLong() + { + return reader.ReadInt64(); + } + /// + /// Reads a single unsigned 64bit long from the message payload. + /// + /// The unsigned long. + public ulong ReadUInt64() + { + return reader.ReadUInt64(); + } + /// + /// Reads a single unsigned 64bit long from the message payload. + /// + /// The unsigned long. + public ulong ReadULong() + { + return reader.ReadUInt64(); + } + /// + /// Reads a single 32bit float from the message payload. + /// + /// The float. + public float ReadSingle() + { + return reader.ReadSingle(); + } + /// + /// Reads a single 32bit float from the message payload. + /// + /// The float. + public float ReadFloat() + { + return reader.ReadSingle(); + } + /// + /// Reads a single 64bit double from the message payload. + /// + /// The double. + public double ReadDouble() + { + return reader.ReadDouble(); + } + + + } +} diff --git a/SteamKit2/SteamKit2/Base/GC/PacketBaseGC.cs b/SteamKit2/SteamKit2/Base/GC/PacketBaseGC.cs new file mode 100644 index 00000000..e599cf7d --- /dev/null +++ b/SteamKit2/SteamKit2/Base/GC/PacketBaseGC.cs @@ -0,0 +1,206 @@ +/* + * This file is subject to the terms and conditions defined in + * file 'license.txt', which is part of this source code package. + */ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.IO; +using SteamKit2.Internal; + +namespace SteamKit2.GC +{ + /// + /// Represents a simple unified interface into client messages recieved from the network. + /// This is contrasted with in that this interface is packet body agnostic + /// and only allows simple access into the header. This interface is also immutable, and the underlying + /// data cannot be modified. + /// + public interface IPacketGCMsg + { + /// + /// Gets a value indicating whether this packet message is protobuf backed. + /// + /// + /// true if this instance is protobuf backed; otherwise, false. + /// + bool IsProto { get; } + /// + /// Gets the network message type of this packet message. + /// + /// + /// The message type. + /// + uint MsgType { get; } + + /// + /// Gets the target job id for this packet message. + /// + /// + /// The target job id. + /// + ulong TargetJobID { get; } + /// + /// Gets the source job id for this packet message. + /// + /// + /// The source job id. + /// + ulong SourceJobID { get; } + + /// + /// Gets the underlying data that represents this client message. + /// + /// The data. + byte[] GetData(); + } + + + /// + /// Represents a protobuf backed packet message. + /// + public sealed class PacketClientGCMsgProtobuf : IPacketGCMsg + { + /// + /// Gets a value indicating whether this packet message is protobuf backed. + /// This type of message is always protobuf backed. + /// + /// + /// true if this instance is protobuf backed; otherwise, false. + /// + public bool IsProto { get { return true; } } + /// + /// Gets the network message type of this packet message. + /// + /// + /// The message type. + /// + public uint MsgType { get; private set; } + + /// + /// Gets the target job id for this packet message. + /// + /// + /// The target job id. + /// + public ulong TargetJobID { get; private set; } + /// + /// Gets the source job id for this packet message. + /// + /// + /// The source job id. + /// + public ulong SourceJobID { get; private set; } + + + byte[] payload; + + + /// + /// Initializes a new instance of the class. + /// + /// The network message type for this packet message. + /// The data. + public PacketClientGCMsgProtobuf( uint eMsg, byte[] data ) + { + MsgType = eMsg; + payload = data; + + MsgGCHdrProtoBuf protobufHeader = new MsgGCHdrProtoBuf(); + + // we need to pull out the job ids, so we deserialize the protobuf header + using ( MemoryStream ms = new MemoryStream( data ) ) + { + protobufHeader.Deserialize( ms ); + } + + TargetJobID = protobufHeader.Proto.job_id_target; + SourceJobID = protobufHeader.Proto.job_id_source; + } + + + /// + /// Gets the underlying data that represents this client message. + /// + /// The data. + public byte[] GetData() + { + return payload; + } + } + + /// + /// Represents a packet message with extended header information. + /// + public sealed class PacketClientGCMsg : IPacketGCMsg + { + /// + /// Gets a value indicating whether this packet message is protobuf backed. + /// This type of message is never protobuf backed. + /// + /// + /// true if this instance is protobuf backed; otherwise, false. + /// + public bool IsProto { get { return false; } } + /// + /// Gets the network message type of this packet message. + /// + /// + /// The message type. + /// + public uint MsgType { get; private set; } + + /// + /// Gets the target job id for this packet message. + /// + /// + /// The target job id. + /// + public ulong TargetJobID { get; private set; } + /// + /// Gets the source job id for this packet message. + /// + /// + /// The source job id. + /// + public ulong SourceJobID { get; private set; } + + byte[] payload; + + + /// + /// Initializes a new instance of the class. + /// + /// The network message type for this packet message. + /// The data. + public PacketClientGCMsg( uint eMsg, byte[] data ) + { + MsgType = eMsg; + payload = data; + + MsgGCHdr gcHdr = new MsgGCHdr(); + + // deserialize the gc header to get our hands on the job ids + using ( MemoryStream ms = new MemoryStream( data ) ) + { + gcHdr.Deserialize( ms ); + } + + TargetJobID = gcHdr.TargetJobID; + SourceJobID = gcHdr.SourceJobID; + } + + + /// + /// Gets the underlying data that represents this client message. + /// + /// The data. + public byte[] GetData() + { + return payload; + } + } +} diff --git a/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/Callbacks.cs b/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/Callbacks.cs index 82ff67a9..337fcb95 100644 --- a/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/Callbacks.cs +++ b/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/Callbacks.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using SteamKit2.Internal; +using SteamKit2.GC; namespace SteamKit2 { @@ -13,7 +14,9 @@ namespace SteamKit2 /// public class MessageCallback : CallbackMsg { + // raw emsg (with protobuf flag, if present) uint eMsg; + /// /// Gets the game coordinator message type. /// @@ -33,7 +36,7 @@ namespace SteamKit2 /// /// Gets the actual message. /// - public byte[] Payload { get; private set; } + public IPacketGCMsg Message { get; private set; } #if STATIC_CALLBACKS @@ -46,7 +49,23 @@ namespace SteamKit2 this.eMsg = gcMsg.msgtype; this.AppID = gcMsg.appid; - this.Payload = gcMsg.payload; + this.Message = GetPacketGCMsg( gcMsg.msgtype, gcMsg.payload ); + } + + + static IPacketGCMsg GetPacketGCMsg( uint eMsg, byte[] data ) + { + // strip off the protobuf flag + uint realEMsg = MsgUtil.GetGCMsg( eMsg ); + + if ( MsgUtil.IsProtoBuf( eMsg ) ) + { + return new PacketClientGCMsgProtobuf( realEMsg, data ); + } + else + { + return new PacketClientGCMsg( realEMsg, data ); + } } } } diff --git a/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/SteamGameCoordinator.cs b/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/SteamGameCoordinator.cs index 24402a49..b557e520 100644 --- a/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/SteamGameCoordinator.cs +++ b/SteamKit2/SteamKit2/Steam3/Handlers/SteamGameCoordinator/SteamGameCoordinator.cs @@ -13,20 +13,19 @@ namespace SteamKit2 public sealed partial class SteamGameCoordinator : ClientMsgHandler { - /// /// Sends a game coordinator message for a specific appid. /// - /// The data to send. This should be a serialized GC message. + /// The GC message to send. /// The app id of the game coordinator to send to. - public void Send( byte[] data, uint appId ) + public void Send( IClientGCMsg msg, uint appId ) { var clientMsg = new ClientMsgProtobuf( EMsg.ClientToGC ); - clientMsg.Body.msgtype = BitConverter.ToUInt32( data, 0 ); + clientMsg.Body.msgtype = msg.MsgType; clientMsg.Body.appid = appId; - clientMsg.Body.payload = data; + clientMsg.Body.payload = msg.Serialize(); this.Client.Send( clientMsg ); } diff --git a/SteamKit2/SteamKit2/Steam3/SteamClient/CallbackMgr/CallbackMgr.cs b/SteamKit2/SteamKit2/Steam3/SteamClient/CallbackMgr/CallbackMgr.cs index c33968d6..f29cded4 100644 --- a/SteamKit2/SteamKit2/Steam3/SteamClient/CallbackMgr/CallbackMgr.cs +++ b/SteamKit2/SteamKit2/Steam3/SteamClient/CallbackMgr/CallbackMgr.cs @@ -90,7 +90,7 @@ namespace SteamKit2 if ( mgr != null ) mgr.Unregister( this ); - GC.SuppressFinalize( this ); + System.GC.SuppressFinalize( this ); } diff --git a/SteamKit2/SteamKit2/SteamKit2.csproj b/SteamKit2/SteamKit2/SteamKit2.csproj index 3b1579d5..09f5830a 100644 --- a/SteamKit2/SteamKit2/SteamKit2.csproj +++ b/SteamKit2/SteamKit2/SteamKit2.csproj @@ -71,6 +71,9 @@ + + +