Initial implementation of improved GC message handling.

--HG--
branch : gc
This commit is contained in:
Ryan Stecker
2012-05-04 19:00:57 -05:00
parent 93f44366ef
commit afc4dc610e
7 changed files with 890 additions and 8 deletions

View File

@@ -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
{
/// <summary>
/// Represents a protobuf backed client message.
/// </summary>
/// <typeparam name="BodyType">The body type of this message.</typeparam>
public sealed class ClientGCMsgProtobuf<BodyType> : GCMsgBase<MsgGCHdrProtoBuf>
where BodyType : IExtensible, new()
{
/// <summary>
/// Gets a value indicating whether this client message is protobuf backed.
/// Client messages of this type are always protobuf backed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is protobuf backed; otherwise, <c>false</c>.
/// </value>
public override bool IsProto { get { return true; } }
/// <summary>
/// Gets the network message type of this client message.
/// </summary>
/// <value>
/// The network message type.
/// </value>
public override uint MsgType { get { return Header.Msg; } }
/// <summary>
/// Gets or sets the session id for this client message.
/// </summary>
/// <value>
/// The session id.
/// </value>
public override int SessionID
{
get { return ProtoHeader.client_session_id; }
set { ProtoHeader.client_session_id = value; }
}
/// <summary>
/// Gets or sets the <see cref="SteamID"/> for this client message.
/// </summary>
/// <value>
/// The <see cref="SteamID"/>.
/// </value>
public override SteamID SteamID
{
get { return ProtoHeader.client_steam_id; }
set { ProtoHeader.client_steam_id = value; }
}
/// <summary>
/// Gets or sets the target job id for this client message.
/// </summary>
/// <value>
/// The target job id.
/// </value>
public override ulong TargetJobID
{
get { return ProtoHeader.job_id_target; }
set { ProtoHeader.job_id_target = value; }
}
/// <summary>
/// Gets or sets the source job id for this client message.
/// </summary>
/// <value>
/// The source job id.
/// </value>
public override ulong SourceJobID
{
get { return ProtoHeader.job_id_source; }
set { ProtoHeader.job_id_source = value; }
}
/// <summary>
/// Shorthand accessor for the protobuf header.
/// </summary>
public CMsgProtoBufHeader ProtoHeader { get { return Header.Proto; } }
/// <summary>
/// Gets the body structure of this message.
/// </summary>
public BodyType Body { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="ClientMsgProtobuf&lt;BodyType&gt;"/> class.
/// This is a client send constructor.
/// </summary>
/// <param name="eMsg">The network message type this client message represents.</param>
/// <param name="payloadReserve">The number of bytes to initialize the payload capacity to.</param>
public ClientGCMsgProtobuf( uint eMsg, int payloadReserve = 64 )
: base( payloadReserve )
{
Body = new BodyType();
// set our emsg
Header.Msg = eMsg;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClientMsgProtobuf&lt;BodyType&gt;"/> class.
/// This a reply constructor.
/// </summary>
/// <param name="eMsg">The network message type this client message represents.</param>
/// <param name="msg">The message that this instance is a reply for.</param>
/// <param name="payloadReserve">The number of bytes to initialize the payload capacity to.</param>
public ClientGCMsgProtobuf( uint eMsg, GCMsgBase<MsgGCHdrProtoBuf> 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;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClientMsgProtobuf&lt;BodyType&gt;"/> class.
/// This is a recieve constructor.
/// </summary>
/// <param name="msg">The packet message to build this client message from.</param>
public ClientGCMsgProtobuf( IPacketGCMsg msg )
: this( msg.MsgType )
{
Deserialize( msg.GetData() );
}
/// <summary>
/// Serializes this client message instance to a byte array.
/// </summary>
/// <returns>
/// Data representing a client message.
/// </returns>
public override byte[] Serialize()
{
using ( MemoryStream ms = new MemoryStream() )
{
Header.Serialize( ms );
Serializer.Serialize( ms, Body );
Payload.WriteTo( ms );
return ms.ToArray();
}
}
/// <summary>
/// Initializes this client message by deserializing the specified data.
/// </summary>
/// <param name="data">The data representing a client message.</param>
public override void Deserialize( byte[] data )
{
using ( MemoryStream ms = new MemoryStream( data ) )
{
Header.Deserialize( ms );
Body = Serializer.Deserialize<BodyType>( 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 );
}
}
}
}

View File

@@ -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
{
/// <summary>
/// Represents a unified interface into client messages.
/// </summary>
public interface IClientGCMsg
{
/// <summary>
/// Gets a value indicating whether this client message is protobuf backed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is protobuf backed; otherwise, <c>false</c>.
/// </value>
bool IsProto { get; }
/// <summary>
/// Gets the network message type of this client message.
/// </summary>
/// <value>
/// The message type.
/// </value>
uint MsgType { get; }
/// <summary>
/// Gets or sets the session id for this client message.
/// </summary>
/// <value>
/// The session id.
/// </value>
int SessionID { get; set; }
/// <summary>
/// Gets or sets the <see cref="SteamID"/> for this client message.
/// </summary>
/// <value>
/// The <see cref="SteamID"/>.
/// </value>
SteamID SteamID { get; set; }
/// <summary>
/// Gets or sets the target job id for this client message.
/// </summary>
/// <value>
/// The target job id.
/// </value>
ulong TargetJobID { get; set; }
/// <summary>
/// Gets or sets the source job id for this client message.
/// </summary>
/// <value>
/// The source job id.
/// </value>
ulong SourceJobID { get; set; }
/// <summary>
/// Serializes this client message instance to a byte array.
/// </summary>
/// <returns>Data representing a client message.</returns>
byte[] Serialize();
/// <summary>
/// Initializes this client message by deserializing the specified data.
/// </summary>
/// <param name="data">The data representing a client message.</param>
void Deserialize( byte[] data );
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="HdrType">The header type for this client message.</typeparam>
public abstract class GCMsgBase<HdrType> : IClientGCMsg
where HdrType : IGCSerializableHeader, new()
{
/// <summary>
/// Gets a value indicating whether this client message is protobuf backed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is protobuf backed; otherwise, <c>false</c>.
/// </value>
public abstract bool IsProto { get; }
/// <summary>
/// Gets the network message type of this client message.
/// </summary>
/// <value>
/// The network message type.
/// </value>
public abstract uint MsgType { get; }
/// <summary>
/// Gets or sets the session id for this client message.
/// </summary>
/// <value>
/// The session id.
/// </value>
public abstract int SessionID { get; set; }
/// <summary>
/// Gets or sets the <see cref="SteamID"/> for this client message.
/// </summary>
/// <value>
/// The <see cref="SteamID"/>.
/// </value>
public abstract SteamID SteamID { get; set; }
/// <summary>
/// Gets or sets the target job id for this client message.
/// </summary>
/// <value>
/// The target job id.
/// </value>
public abstract ulong TargetJobID { get; set; }
/// <summary>
/// Gets or sets the source job id for this client message.
/// </summary>
/// <value>
/// The source job id.
/// </value>
public abstract ulong SourceJobID { get; set; }
/// <summary>
/// Gets the header for this message type.
/// </summary>
public HdrType Header { get; private set; }
/// <summary>
/// Returns a <see cref="System.IO.MemoryStream"/> which is the backing stream for client message payload data.
/// </summary>
public MemoryStream Payload { get; private set; }
BinaryReader reader;
BinaryWriter writer;
/// <summary>
/// Initializes a new instance of the <see cref="MsgBase&lt;HdrType&gt;"/> class.
/// </summary>
/// <param name="payloadReserve">The number of bytes to initialize the payload capacity to.</param>
public GCMsgBase( int payloadReserve = 0 )
{
Header = new HdrType();
Payload = new MemoryStream( payloadReserve );
reader = new BinaryReader( Payload );
writer = new BinaryWriter( Payload );
}
/// <summary>
/// Serializes this client message instance to a byte array.
/// </summary>
/// <returns>
/// Data representing a client message.
/// </returns>
public abstract byte[] Serialize();
/// <summary>
/// Initializes this client message by deserializing the specified data.
/// </summary>
/// <param name="data">The data representing a client message.</param>
public abstract void Deserialize( byte[] data );
/// <summary>
/// Seeks within the payload to the specified offset.
/// </summary>
/// <param name="offset">The offset in the payload to seek to.</param>
/// <param name="loc">The origin to seek from.</param>
/// <returns>The new position within the stream, calculated by combining the initial reference point and the offset.</returns>
public long Seek( long offset, SeekOrigin loc )
{
return Payload.Seek( offset, loc );
}
/// <summary>
/// Writes a single unsigned byte to the message payload.
/// </summary>
/// <param name="data">The unsigned byte.</param>
public void Write( byte data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single signed byte to the message payload.
/// </summary>
/// <param name="data">The signed byte.</param>
public void Write( sbyte data )
{
writer.Write( data );
}
/// <summary>
/// Writes the specified byte array to the message payload.
/// </summary>
/// <param name="data">The byte array.</param>
public void Write( byte[] data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single 16bit short to the message payload.
/// </summary>
/// <param name="data">The short.</param>
public void Write( short data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single unsigned 16bit short to the message payload.
/// </summary>
/// <param name="data">The unsigned short.</param>
public void Write( ushort data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single 32bit integer to the message payload.
/// </summary>
/// <param name="data">The integer.</param>
public void Write( int data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single unsigned 32bit integer to the message payload.
/// </summary>
/// <param name="data">The unsigned integer.</param>
public void Write( uint data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single 64bit long to the message payload.
/// </summary>
/// <param name="data">The long.</param>
public void Write( long data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single unsigned 64bit long to the message payload.
/// </summary>
/// <param name="data">The unsigned long.</param>
public void Write( ulong data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single 32bit float to the message payload.
/// </summary>
/// <param name="data">The float.</param>
public void Write( float data )
{
writer.Write( data );
}
/// <summary>
/// Writes a single 64bit double to the message payload.
/// </summary>
/// <param name="data">The double.</param>
public void Write( double data )
{
writer.Write( data );
}
/// <summary>
/// Writes the specified string to the message payload using default encoding.
/// This function does not write a terminating null character.
/// </summary>
/// <param name="data">The string to write.</param>
public void Write( string data )
{
Write( data, Encoding.Default );
}
/// <summary>
/// Writes the specified string to the message payload using the specified encoding.
/// This function does not write a terminating null character.
/// </summary>
/// <param name="data">The string to write.</param>
/// <param name="encoding">The encoding to use.</param>
public void Write( string data, Encoding encoding )
{
if ( data == null )
return;
Write( encoding.GetBytes( data ) );
}
/// <summary>
/// Writes the secified string and a null terminator to the message payload using default encoding.
/// </summary>
/// <param name="data">The string to write.</param>
public void WriteNullTermString( string data )
{
WriteNullTermString( data, Encoding.Default );
}
/// <summary>
/// Writes the specified string and a null terminator to the message payload using the specified encoding.
/// </summary>
/// <param name="data">The string to write.</param>
/// <param name="encoding">The encoding to use.</param>
public void WriteNullTermString( string data, Encoding encoding )
{
Write( data, encoding );
Write( encoding.GetBytes( "\0" ) );
}
/// <summary>
/// Reads a single signed byte from the message payload.
/// </summary>
/// <returns>The signed byte.</returns>
public sbyte ReadInt8()
{
return reader.ReadSByte();
}
/// <summary>
/// Reads a single signed byte from the message payload.
/// </summary>
/// <returns>The signed byte.</returns>
public sbyte ReadSByte()
{
return reader.ReadSByte();
}
/// <summary>
/// Reads a single unsigned byte from the message payload.
/// </summary>
/// <returns>The unsigned byte.</returns>
public byte ReadUInt8()
{
return reader.ReadByte();
}
/// <summary>
/// Reads a single unsigned byte from the message payload.
/// </summary>
/// <returns>The unsigned byte.</returns>
public byte ReadByte()
{
return reader.ReadByte();
}
/// <summary>
/// Reads a number of bytes from the message payload.
/// </summary>
/// <param name="numBytes">The number of bytes to read.</param>
/// <returns>The data.</returns>
public byte[] ReadBytes( int numBytes )
{
return reader.ReadBytes( numBytes );
}
/// <summary>
/// Reads a single 16bit short from the message payload.
/// </summary>
/// <returns>The short.</returns>
public short ReadInt16()
{
return reader.ReadInt16();
}
/// <summary>
/// Reads a single 16bit short from the message payload.
/// </summary>
/// <returns>The short.</returns>
public short ReadShort()
{
return reader.ReadInt16();
}
/// <summary>
/// Reads a single unsigned 16bit short from the message payload.
/// </summary>
/// <returns>The unsigned short.</returns>
public ushort ReadUInt16()
{
return reader.ReadUInt16();
}
/// <summary>
/// Reads a single unsigned 16bit short from the message payload.
/// </summary>
/// <returns>The unsigned short.</returns>
public ushort ReadUShort()
{
return reader.ReadUInt16();
}
/// <summary>
/// Reads a single 32bit integer from the message payload.
/// </summary>
/// <returns>The integer.</returns>
public int ReadInt32()
{
return reader.ReadInt32();
}
/// <summary>
/// Reads a single 32bit integer from the message payload.
/// </summary>
/// <returns>The integer.</returns>
public int ReadInt()
{
return reader.ReadInt32();
}
/// <summary>
/// Reads a single unsigned 32bit integer from the message payload.
/// </summary>
/// <returns>The unsigned integer.</returns>
public uint ReadUInt32()
{
return reader.ReadUInt32();
}
/// <summary>
/// Reads a single unsigned 32bit integer from the message payload.
/// </summary>
/// <returns>The unsigned integer.</returns>
public uint ReadUInt()
{
return reader.ReadUInt32();
}
/// <summary>
/// Reads a single 64bit long from the message payload.
/// </summary>
/// <returns>The long.</returns>
public long ReadInt64()
{
return reader.ReadInt64();
}
/// <summary>
/// Reads a single 64bit long from the message payload.
/// </summary>
/// <returns>The long.</returns>
public long ReadLong()
{
return reader.ReadInt64();
}
/// <summary>
/// Reads a single unsigned 64bit long from the message payload.
/// </summary>
/// <returns>The unsigned long.</returns>
public ulong ReadUInt64()
{
return reader.ReadUInt64();
}
/// <summary>
/// Reads a single unsigned 64bit long from the message payload.
/// </summary>
/// <returns>The unsigned long.</returns>
public ulong ReadULong()
{
return reader.ReadUInt64();
}
/// <summary>
/// Reads a single 32bit float from the message payload.
/// </summary>
/// <returns>The float.</returns>
public float ReadSingle()
{
return reader.ReadSingle();
}
/// <summary>
/// Reads a single 32bit float from the message payload.
/// </summary>
/// <returns>The float.</returns>
public float ReadFloat()
{
return reader.ReadSingle();
}
/// <summary>
/// Reads a single 64bit double from the message payload.
/// </summary>
/// <returns>The double.</returns>
public double ReadDouble()
{
return reader.ReadDouble();
}
}
}

View File

@@ -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
{
/// <summary>
/// Represents a simple unified interface into client messages recieved from the network.
/// This is contrasted with <see cref="IClientMsg"/> 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.
/// </summary>
public interface IPacketGCMsg
{
/// <summary>
/// Gets a value indicating whether this packet message is protobuf backed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is protobuf backed; otherwise, <c>false</c>.
/// </value>
bool IsProto { get; }
/// <summary>
/// Gets the network message type of this packet message.
/// </summary>
/// <value>
/// The message type.
/// </value>
uint MsgType { get; }
/// <summary>
/// Gets the target job id for this packet message.
/// </summary>
/// <value>
/// The target job id.
/// </value>
ulong TargetJobID { get; }
/// <summary>
/// Gets the source job id for this packet message.
/// </summary>
/// <value>
/// The source job id.
/// </value>
ulong SourceJobID { get; }
/// <summary>
/// Gets the underlying data that represents this client message.
/// </summary>
/// <returns>The data.</returns>
byte[] GetData();
}
/// <summary>
/// Represents a protobuf backed packet message.
/// </summary>
public sealed class PacketClientGCMsgProtobuf : IPacketGCMsg
{
/// <summary>
/// Gets a value indicating whether this packet message is protobuf backed.
/// This type of message is always protobuf backed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is protobuf backed; otherwise, <c>false</c>.
/// </value>
public bool IsProto { get { return true; } }
/// <summary>
/// Gets the network message type of this packet message.
/// </summary>
/// <value>
/// The message type.
/// </value>
public uint MsgType { get; private set; }
/// <summary>
/// Gets the target job id for this packet message.
/// </summary>
/// <value>
/// The target job id.
/// </value>
public ulong TargetJobID { get; private set; }
/// <summary>
/// Gets the source job id for this packet message.
/// </summary>
/// <value>
/// The source job id.
/// </value>
public ulong SourceJobID { get; private set; }
byte[] payload;
/// <summary>
/// Initializes a new instance of the <see cref="PacketClientMsgProtobuf"/> class.
/// </summary>
/// <param name="eMsg">The network message type for this packet message.</param>
/// <param name="data">The data.</param>
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;
}
/// <summary>
/// Gets the underlying data that represents this client message.
/// </summary>
/// <returns>The data.</returns>
public byte[] GetData()
{
return payload;
}
}
/// <summary>
/// Represents a packet message with extended header information.
/// </summary>
public sealed class PacketClientGCMsg : IPacketGCMsg
{
/// <summary>
/// Gets a value indicating whether this packet message is protobuf backed.
/// This type of message is never protobuf backed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is protobuf backed; otherwise, <c>false</c>.
/// </value>
public bool IsProto { get { return false; } }
/// <summary>
/// Gets the network message type of this packet message.
/// </summary>
/// <value>
/// The message type.
/// </value>
public uint MsgType { get; private set; }
/// <summary>
/// Gets the target job id for this packet message.
/// </summary>
/// <value>
/// The target job id.
/// </value>
public ulong TargetJobID { get; private set; }
/// <summary>
/// Gets the source job id for this packet message.
/// </summary>
/// <value>
/// The source job id.
/// </value>
public ulong SourceJobID { get; private set; }
byte[] payload;
/// <summary>
/// Initializes a new instance of the <see cref="PacketClientMsg"/> class.
/// </summary>
/// <param name="eMsg">The network message type for this packet message.</param>
/// <param name="data">The data.</param>
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;
}
/// <summary>
/// Gets the underlying data that represents this client message.
/// </summary>
/// <returns>The data.</returns>
public byte[] GetData()
{
return payload;
}
}
}

View File

@@ -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
/// </summary>
public class MessageCallback : CallbackMsg
{
// raw emsg (with protobuf flag, if present)
uint eMsg;
/// <summary>
/// Gets the game coordinator message type.
/// </summary>
@@ -33,7 +36,7 @@ namespace SteamKit2
/// <summary>
/// Gets the actual message.
/// </summary>
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 );
}
}
}
}

View File

@@ -13,20 +13,19 @@ namespace SteamKit2
public sealed partial class SteamGameCoordinator : ClientMsgHandler
{
/// <summary>
/// Sends a game coordinator message for a specific appid.
/// </summary>
/// <param name="data">The data to send. This should be a serialized GC message.</param>
/// <param name="msg">The GC message to send.</param>
/// <param name="appId">The app id of the game coordinator to send to.</param>
public void Send( byte[] data, uint appId )
public void Send( IClientGCMsg msg, uint appId )
{
var clientMsg = new ClientMsgProtobuf<CMsgGCClient>( 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 );
}

View File

@@ -90,7 +90,7 @@ namespace SteamKit2
if ( mgr != null )
mgr.Unregister( this );
GC.SuppressFinalize( this );
System.GC.SuppressFinalize( this );
}

View File

@@ -71,6 +71,9 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Base\ClientMsg.cs" />
<Compile Include="Base\GC\MsgBaseGC.cs" />
<Compile Include="Base\GC\ClientMsgGC.cs" />
<Compile Include="Base\GC\PacketBaseGC.cs" />
<Compile Include="Base\Generated\ContentManifest.cs" />
<Compile Include="Base\Generated\GC\SteamMsgBase.cs" />
<Compile Include="Base\Generated\GC\SteamMsgGC.cs" />