diff --git a/src/Syroot.OnlineWorms.Server/Client.cs b/src/Syroot.OnlineWorms.Server/Client.cs new file mode 100644 index 0000000..8eef7d7 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Client.cs @@ -0,0 +1,55 @@ +using System.Net.Sockets; +using Syroot.Worms.OnlineWorms.Server.Net; + +namespace Syroot.Worms.OnlineWorms.Server +{ + /// + /// Represents a connection with an Online Worms client which replies to received packets appropriately. + /// + internal class Client : PacketHandler + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private readonly Server _server; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class accepted by the given + /// through the provided . + /// + /// The representing the connection to the client. + /// The instance with which this client communicates. + public Client(TcpClient tcpClient, Server server) + : base(tcpClient) + { + _server = server; + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private Packet HandleConnect(ConnectQueryPacket connectPacket) + { + return new ConnectReplyPacket + { + Unknown = "Online Worms Private Server", + Version = "114" + }; + } + + //private Packet HandleLogin(LoginQueryPacket loginPacket) + //{ + // return new LoginReplyPacket + // { + // LoginResult = LoginResult.Success + // }; + //} + +#if DEBUG + private Packet HandleRaw(RawQueryPacket rawPacket) + { + return null; + } +#endif + } +} \ No newline at end of file diff --git a/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs b/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs new file mode 100644 index 0000000..72ac934 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Core/MathFuncs.cs @@ -0,0 +1,26 @@ +namespace Syroot.Worms.OnlineWorms.Server.Core +{ + /// + /// Represents common mathematical operations. + /// + internal static class MathFuncs + { + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + /// + /// Returns the nearest power of two bigger than the given . + /// + /// The value to get the nearest, bigger power of two for. + /// The nearest, bigger power of two. + internal static int NextPowerOfTwo(int value) + { + value--; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + return ++value; + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs new file mode 100644 index 0000000..414841a --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs @@ -0,0 +1,128 @@ +using System; +using System.IO; +using System.Net.Sockets; +using System.Text; +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents a stream of instances send between a game client and server. + /// + internal class GameConnection + { + // A complete packet consists of the following: + // - ushort id + // - ushort dataSize + // - byte[dataSize] data + + // ---- CONSTANTS ---------------------------------------------------------------------------------------------- + + private const int _maxDataSize = 2048; + + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private readonly Stream _tcpStream; + private readonly byte[] _receiveBuffer = new byte[_maxDataSize]; + private readonly byte[] _sendDataBuffer = new byte[_maxDataSize]; + private readonly BinaryStream _writeStream; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + /// + /// Initializes a new instance of the class, receiving and sending packets from and + /// to the given . + /// + /// The to communicate with. + internal GameConnection(TcpClient tcpClient) + { + _tcpStream = tcpClient.GetStream(); + _writeStream = new BinaryStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true), + encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount); + } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + /// + /// Returns a read from the stream. If the packet could not be read, + /// is returned. + /// + /// The read instance or if the packet could not be read. + /// + internal Packet ReceivePacket() + { + // Receive the raw packet data. + ushort id; + ushort dataSize; + try + { + id = _tcpStream.ReadUInt16(); + dataSize = _tcpStream.ReadUInt16(); + ReadComplete(_receiveBuffer, 0, dataSize); + } + catch (IOException) { return null; } // The underlying socket closed. + catch (ObjectDisposedException) { return null; } // The underlying stream closed. + + // Deserialize and return the packet. + Packet packet = PacketFactory.Create(id); + using (BinaryStream readStream = new BinaryStream(new MemoryStream(_receiveBuffer, 0, dataSize, false), + encoding: Encoding.GetEncoding(949), stringCoding: StringCoding.Int16CharCount)) + { + packet.Deserialize(readStream); + } + return packet; + } + + /// + /// Sends the given and returns if the packet was sent + /// successfully. + /// + /// The to send. + /// when the packet was sent. + internal bool SendPacket(Packet packet) + { + // Serialize the raw packet data. + _writeStream.Position = 0; + packet.Serialize(_writeStream); + ushort dataSize = (ushort)_writeStream.Position; + + // Send the data and return success. + try + { + _tcpStream.WriteUInt16(PacketFactory.GetID(packet)); + _tcpStream.WriteUInt16(dataSize); + _tcpStream.Write(_sendDataBuffer, 0, dataSize); + return true; + } + catch (IOException) { return false; } // A network error appeared, and communication should end. + catch (ObjectDisposedException) { return false; } // The underlying stream was most apparently closed. + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + /// + /// Reads number of bytes to the given , starting at the + /// specified , and returns the number of bytes read. Since this method ensures that + /// the requested number of bytes is always read, it always returns , and otherwise + /// throws an if the required bytes could not be read. + /// + /// The byte array to write the read data to. + /// The offset into at which to start writing data. + /// The number of bytes to read into . + /// The cannot store the requested number of + /// bytes. + /// The requested number of bytes could not be read. + private void ReadComplete(byte[] buffer, int offset, int count) + { + int totalRead = 0; + while (totalRead < count) + { + // Read returns 0 only when the underlying socket is closed, otherwise it blocks. + int read = _tcpStream.Read(buffer, offset + totalRead, count - totalRead); + if (read == 0) + throw new IOException("The underlying stream has closed, 0 bytes were retrieved."); + totalRead += read; + } + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs b/src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs new file mode 100644 index 0000000..04c423c --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal class PacketAttribute : Attribute + { + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + public PacketAttribute(ushort id) + { + ID = id; + } + + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + public ushort ID { get; } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs b/src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs new file mode 100644 index 0000000..9afd7e1 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents a factory creating instances by mapping their ID to types to instantiate. + /// + internal static class PacketFactory + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private static readonly Dictionary _packetTypeMap = new Dictionary(); + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + static PacketFactory() + { + // Cache the ID-to-packet-class map. + foreach (Type type in Assembly.GetExecutingAssembly().GetTypes()) + { + foreach (PacketAttribute packetAttribute in type.GetCustomAttributes()) + { + if (_packetTypeMap.ContainsKey(packetAttribute.ID)) + throw new InvalidOperationException($"Packet {packetAttribute.ID} mapped to multiple classes."); + _packetTypeMap.Add(packetAttribute.ID, type); + } + } + } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + /// + /// Creates a new instance mapped to the given . + /// + /// The ID of the packet class to instantiate. + /// The created instance. + /// No class was mapped to the given packet ID. + internal static Packet Create(ushort id) + { +#if DEBUG + return _packetTypeMap.TryGetValue(id, out Type type) + ? (Packet)Activator.CreateInstance(type, true) + : new RawQueryPacket(id); +#else + return (Packet)Activator.CreateInstance(_packetTypeMap[id], true); +#endif + } + + /// + /// Gets the ID for the class of the given . + /// + /// The type of the . + /// The packet, whose class ID will be returned. + /// The ID of the class. + internal static ushort GetID(T packet) where T : Packet + { +#if DEBUG + if (packet is RawQueryPacket rawPacket) + return rawPacket.ID; +#endif + return _packetTypeMap.Where(x => x.Value == packet.GetType()).First().Key; + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs b/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs new file mode 100644 index 0000000..c85c75f --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/PacketHandler.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Reflection; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents a class capable of dispatching received instances to a corresponding method. + /// + internal class PacketHandler : IDisposable + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private static readonly Dictionary> _packetHandlers + = new Dictionary>(); + + private readonly TcpClient _tcpClient; + private readonly Dictionary _handlerMethods; + private readonly GameConnection _connection; + private bool _disposed; + + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + internal PacketHandler(TcpClient tcpClient) + { + Console.WriteLine($"{tcpClient.Client.RemoteEndPoint} connected"); + _tcpClient = tcpClient; + _handlerMethods = GetHandlerMethods(); + _connection = new GameConnection(_tcpClient); + } + + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing). + Dispose(true); + } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + /// + /// Starts handling packets incoming from this client and calls corresponding methods. + /// + internal void Accept() + { + Packet packet; + while ((packet = _connection.ReceivePacket()) != null) + HandlePacket(packet); + } + + // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + _tcpClient.Dispose(); + + _disposed = true; + } + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private Dictionary GetHandlerMethods() + { + Type classType = GetType(); + if (!_packetHandlers.TryGetValue(classType, out Dictionary handlerMethods)) + { + handlerMethods = new Dictionary(); + // Find all packet handling methods which are methods accepting and returning a specific packet. + foreach (MethodInfo method in classType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)) + { + Type inPacket = method.GetParameters().FirstOrDefault()?.ParameterType; + if (typeof(Packet).IsAssignableFrom(inPacket) && typeof(Packet).IsAssignableFrom(method.ReturnType)) + handlerMethods.Add(inPacket, method); + } + _packetHandlers.Add(classType, handlerMethods); + } + return handlerMethods; + } + + private void HandlePacket(Packet inPacket) + { + Type packetType = inPacket.GetType(); + Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}"); + + // Invoke the handler and send back any packet resulting from it. + if (_handlerMethods[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket) + { + Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}"); + _connection.SendPacket(outPacket); + } + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs new file mode 100644 index 0000000..71edf94 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectQueryPacket.cs @@ -0,0 +1,18 @@ +using System; +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents the client request for a . + /// + [Packet(0x800E)] + internal class ConnectQueryPacket : Packet + { + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal override void Deserialize(BinaryStream stream) { } + + internal override void Serialize(BinaryStream stream) => throw new NotImplementedException(); + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs new file mode 100644 index 0000000..9224c21 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs @@ -0,0 +1,28 @@ +using System; +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents the server response to a . + /// + [Packet(0x800F)] + internal class ConnectReplyPacket : Packet + { + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + public string Unknown { get; set; } + + public string Version { get; set; } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException(); + + internal override void Serialize(BinaryStream stream) + { + stream.WriteString(Unknown); + stream.WriteString(Version); + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs new file mode 100644 index 0000000..02f33a8 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/LoginReplyPacket.cs @@ -0,0 +1,40 @@ +using System; +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents the server response to a . + /// + [Packet(0x8001)] + internal class LoginReplyPacket : Packet + { + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + public LoginResult LoginResult { get; set; } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal override void Deserialize(BinaryStream stream) => throw new NotImplementedException(); + + internal override void Serialize(BinaryStream stream) + { + stream.WriteBoolean(LoginResult == LoginResult.Success); + stream.WriteEnum(LoginResult); + } + } + + internal enum LoginResult : byte + { + Success, + IDAlreadyInUse = 3, + UnqualifiedID = 4, + IncorrectID = 6, + IncorrectPassword = 7, + DuplicateIDs = 9, + IncorrectVersion = 10, + BannedID = 11, + PublicAccessBanned = 12, + TemporarilyBannedID = 13 + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs new file mode 100644 index 0000000..f9cef3a --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/Packet.cs @@ -0,0 +1,17 @@ +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes, + /// decorate it with the . + /// + internal abstract class Packet + { + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal abstract void Deserialize(BinaryStream stream); + + internal abstract void Serialize(BinaryStream stream); + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs new file mode 100644 index 0000000..676e386 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs @@ -0,0 +1,38 @@ +#if DEBUG +using Syroot.BinaryData; + +namespace Syroot.Worms.OnlineWorms.Server.Net +{ + /// + /// Represents a special fallback packet which simply stores any ID and the raw client packet data. + /// + internal class RawQueryPacket : Packet + { + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + internal RawQueryPacket(ushort id) + { + ID = id; + } + + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + internal ushort ID { get; } + + internal byte[] Data { get; set; } + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal override void Deserialize(BinaryStream stream) + { + Data = new byte[stream.Length]; + stream.Read(Data, 0, Data.Length); + } + + internal override void Serialize(BinaryStream stream) + { + stream.Write(Data, 0, Data.Length); + } + } +} +#endif diff --git a/src/Syroot.OnlineWorms.Server/Program.cs b/src/Syroot.OnlineWorms.Server/Program.cs new file mode 100644 index 0000000..f1e1c73 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Program.cs @@ -0,0 +1,32 @@ +using System; +using System.Text; + +namespace Syroot.Worms.OnlineWorms.Server +{ + /// + /// Represents the main class of the application containing the entry point. + /// + internal class Program + { + // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + + static Program() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private static void Main(string[] args) + { + try + { + new Server().Listen(17022); + } + catch (Exception ex) + { + Console.WriteLine($"Unhandled exception: {ex}"); + } + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Server.cs b/src/Syroot.OnlineWorms.Server/Server.cs new file mode 100644 index 0000000..c97351d --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Server.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace Syroot.Worms.OnlineWorms.Server +{ + /// + /// Represents a server listening for incoming client connections and dispatching them into + /// instances. + /// + public class Server + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private readonly List _clients = new List(); + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + /// + /// Starts the server by accepting new client connections under the given and + /// dispatching them into asynchronous handling threads. This call is blocking. + /// + /// The port on which to listen for new client connections. + internal void Listen(int port) + { + TcpListener tcpListener = new TcpListener(IPAddress.Any, port); + tcpListener.Start(); + Console.WriteLine($"Listening on port {port}..."); + + while (true) + { + Client client = new Client(tcpListener.AcceptTcpClient(), this); + _clients.Add(client); + + Task.Run(client.Accept).ContinueWith(_ => + { + _clients.Remove(client); + client.Dispose(); + }); + } + } + } +} diff --git a/src/Syroot.OnlineWorms.Server/Syroot.Worms.OnlineWorms.Server.csproj b/src/Syroot.OnlineWorms.Server/Syroot.Worms.OnlineWorms.Server.csproj new file mode 100644 index 0000000..3129073 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Syroot.Worms.OnlineWorms.Server.csproj @@ -0,0 +1,14 @@ + + + Exe + netcoreapp2.1 + latest + + + DEBUG;TRACE + + + + + + diff --git a/src/Syroot.Worms.sln b/src/Syroot.Worms.sln index 1725a38..a6888a2 100644 --- a/src/Syroot.Worms.sln +++ b/src/Syroot.Worms.sln @@ -11,28 +11,45 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.Scratchpad", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.OnlineWorms.Launcher", "Syroot.Worms.OnlineWorms.Launcher\Syroot.Worms.OnlineWorms.Launcher.csproj", "{510EE83E-9C52-40FD-AC7E-C4981EBF4182}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Syroot.Worms.OnlineWorms.Server", "Syroot.OnlineWorms.Server\Syroot.Worms.OnlineWorms.Server.csproj", "{2A06124C-EA75-4946-9959-4AD3DC754B90}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + ReleaseSigned|Any CPU = ReleaseSigned|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Debug|Any CPU.Build.0 = Debug|Any CPU {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.ActiveCfg = Release|Any CPU {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.Release|Any CPU.Build.0 = Release|Any CPU + {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU + {DD76B6AA-5A5A-4FCD-95AA-9552977525A1}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU {20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Debug|Any CPU.Build.0 = Debug|Any CPU {20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Release|Any CPU.ActiveCfg = Release|Any CPU {20ACA971-A9D0-4424-9958-9CEE24F43C9C}.Release|Any CPU.Build.0 = Release|Any CPU + {20ACA971-A9D0-4424-9958-9CEE24F43C9C}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU + {20ACA971-A9D0-4424-9958-9CEE24F43C9C}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU {0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU {0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.Release|Any CPU.Build.0 = Release|Any CPU + {0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU + {0D7F9DC3-7268-494E-BA1E-6C01525EB9AB}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU {510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Debug|Any CPU.Build.0 = Debug|Any CPU {510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.ActiveCfg = Release|Any CPU {510EE83E-9C52-40FD-AC7E-C4981EBF4182}.Release|Any CPU.Build.0 = Release|Any CPU + {510EE83E-9C52-40FD-AC7E-C4981EBF4182}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU + {510EE83E-9C52-40FD-AC7E-C4981EBF4182}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU + {2A06124C-EA75-4946-9959-4AD3DC754B90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A06124C-EA75-4946-9959-4AD3DC754B90}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A06124C-EA75-4946-9959-4AD3DC754B90}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A06124C-EA75-4946-9959-4AD3DC754B90}.Release|Any CPU.Build.0 = Release|Any CPU + {2A06124C-EA75-4946-9959-4AD3DC754B90}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU + {2A06124C-EA75-4946-9959-4AD3DC754B90}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE