This commit is contained in:
Aaron Yarborough 2024-09-11 21:38:32 +01:00
parent c640988354
commit 6d80785dfd
26 changed files with 389 additions and 371 deletions

View file

@ -1,21 +1,23 @@
using System.Net; using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using GServer.Common; using GServer.Common.Networking.Core;
using GServer.Common.Networking.Enums; using GServer.Common.Networking.Enums;
using GServer.Common.Networking.Messages.Client; using GServer.Common.Networking.Messages.Client;
using GServer.Common.Networking.Messages.Server; using GServer.Common.Networking.Messages.Server;
internal class Program namespace GServer.Client;
public class Program
{ {
private const int SERVER_PORT = 11000; private const int ServerPort = 11000;
private static void Main(string[] args) private static void Main(string[] args)
{ {
IPEndPoint serverEP = new(IPAddress.Any, SERVER_PORT); IPEndPoint serverEp = new(IPAddress.Any, ServerPort);
TcpClient tcpClient = new(); TcpClient tcpClient = new();
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
tcpClient.Connect(serverEP); tcpClient.Connect(serverEp);
Console.WriteLine("Username..."); Console.WriteLine("Username...");
string username = Console.ReadLine()!; string username = Console.ReadLine()!;
@ -38,23 +40,23 @@ internal class Program
ClientPacketIn packetIn = (ClientPacketIn)stream.ReadByte(); ClientPacketIn packetIn = (ClientPacketIn)stream.ReadByte();
switch (packetIn) switch (packetIn)
{ {
case ClientPacketIn.AUTH_RESPONSE: case ClientPacketIn.AuthResponse:
AuthResponseMessage authResultMessage = new(stream); AuthResponseMessage authResultMessage = new(stream);
Console.WriteLine("Success = " + authResultMessage.IsSuccessful); Console.WriteLine("Success = " + authResultMessage.IsSuccessful);
Console.WriteLine("SessionToken = " + authResultMessage.SessionToken ?? "null"); Console.WriteLine("SessionToken = " + authResultMessage.SessionToken);
Console.WriteLine("FailureReason = " + authResultMessage.FailureReason ?? "null"); Console.WriteLine("FailureReason = " + authResultMessage.FailureReason);
break; break;
case ClientPacketIn.LIST_SERVERS_RESPONSE: case ClientPacketIn.ListServersResponse:
break; break;
case ClientPacketIn.UNKNOWN: case ClientPacketIn.Unknown:
break; break;
default: default:
Console.WriteLine($"Received unsupported packet."); Console.WriteLine("Received unsupported packet.");
break; break;
} }
} }
@ -67,7 +69,5 @@ internal class Program
{ {
tcpClient.Close(); tcpClient.Close();
} }
} }
} }

View file

@ -1,11 +1,11 @@
using System.Text; using System.Text;
namespace GServer.Common; namespace GServer.Common.Extensions;
public static class StringExtensions public static class StringExtensions
{ {
public static byte[] GetASCIIBytes(this string value) public static byte[] GetAsciiBytes(this string value)
{ {
return Encoding.ASCII.GetBytes(value); return Encoding.ASCII.GetBytes(value);
} }
} }

View file

@ -5,7 +5,7 @@ public record ServerListing
public required string Name { get; set; } public required string Name { get; set; }
public required string Description { get; set; } public required string Description { get; set; }
public ushort Playercount { get; set; } public ushort Playercount { get; set; }
public required string IPAddress { get; set; } public required string IpAddress { get; set; }
public ushort Port { get; set; } public ushort Port { get; set; }
public ServerTier ServerTier { get; set; } public ServerTier ServerTier { get; set; }
} }

View file

@ -1,6 +1,6 @@
using System.Text; using System.Text;
namespace GServer.Common; namespace GServer.Common.Networking.Core;
public class MessageMemoryStream : MemoryStream public class MessageMemoryStream : MemoryStream
{ {
@ -31,7 +31,7 @@ public class MessageMemoryStream : MemoryStream
return BitConverter.ToInt16(buffer); return BitConverter.ToInt16(buffer);
} }
public string ReadUTF8String(int length) public string ReadUtf8String(int length)
{ {
byte[] bytes = new byte[length]; byte[] bytes = new byte[length];
_ = Read(bytes, 0, length); _ = Read(bytes, 0, length);
@ -49,7 +49,7 @@ public class MessageMemoryStream : MemoryStream
Write(bytes, 0, 2); Write(bytes, 0, 2);
} }
public void WriteUTF8String(string value) public void WriteUtf8String(string value)
{ {
byte[] bytes = Encoding.UTF8.GetBytes(value); byte[] bytes = Encoding.UTF8.GetBytes(value);
Write(bytes, 0, bytes.Length); Write(bytes, 0, bytes.Length);

View file

@ -5,12 +5,12 @@ public enum ClientPacketIn : byte
/// <summary> /// <summary>
/// Represents an auth result from the server. /// Represents an auth result from the server.
/// </summary> /// </summary>
AUTH_RESPONSE = 1, AuthResponse = 1,
/// <summary> /// <summary>
/// Contains a list of server listings. /// Contains a list of server listings.
/// </summary> /// </summary>
LIST_SERVERS_RESPONSE = 2, ListServersResponse = 2,
UNKNOWN = 255 Unknown = 255
} }

View file

@ -2,6 +2,6 @@ namespace GServer.Common.Networking.Enums;
public enum ServerPacketIn : byte public enum ServerPacketIn : byte
{ {
AUTH = 1, Auth = 1,
LIST_SERVERS = 2 ListServers = 2
} }

View file

@ -4,7 +4,7 @@ public abstract class BaseMessage
{ {
protected readonly byte PacketId; protected readonly byte PacketId;
public BaseMessage(byte packetId) protected BaseMessage(byte packetId)
{ {
PacketId = packetId; PacketId = packetId;
} }

View file

@ -1,4 +1,5 @@
using System.Text; using System.Text;
using GServer.Common.Networking.Core;
using GServer.Common.Networking.Enums; using GServer.Common.Networking.Enums;
namespace GServer.Common.Networking.Messages.Client; namespace GServer.Common.Networking.Messages.Client;
@ -11,33 +12,33 @@ public enum AuthResponseFailure : byte
public class AuthResponseMessage : BaseMessage, IMessage<AuthResponseMessage> public class AuthResponseMessage : BaseMessage, IMessage<AuthResponseMessage>
{ {
public bool IsSuccessful { get; private set; } public bool IsSuccessful { get; }
/// <summary> /// <summary>
/// Used to authenticate the user. Only set if IsSuccessful is true. /// Used to authenticate the user. Only set if IsSuccessful is true.
/// </summary> /// </summary>
public string? SessionToken { get; private set; } public string? SessionToken { get; }
/// <summary> /// <summary>
/// Reason for auth failure. Only set is IsSuccessful is false. /// Reason for auth failure. Only set is IsSuccessful is false.
/// </summary> /// </summary>
public AuthResponseFailure? FailureReason { get; private set; } public AuthResponseFailure? FailureReason { get; }
public AuthResponseMessage(bool isSuccessful, string? sessionToken = null, AuthResponseFailure? failureReason = null) : base((byte)ClientPacketIn.AUTH_RESPONSE) public AuthResponseMessage(bool isSuccessful, string? sessionToken = null, AuthResponseFailure? failureReason = null) : base((byte)ClientPacketIn.AuthResponse)
{ {
IsSuccessful = isSuccessful; IsSuccessful = isSuccessful;
SessionToken = sessionToken; SessionToken = sessionToken;
FailureReason = failureReason; FailureReason = failureReason;
} }
public AuthResponseMessage(MessageMemoryStream stream) : base((byte)ClientPacketIn.AUTH_RESPONSE) public AuthResponseMessage(MessageMemoryStream stream) : base((byte)ClientPacketIn.AuthResponse)
{ {
IsSuccessful = stream.ReadBoolean(); IsSuccessful = stream.ReadBoolean();
if (IsSuccessful) if (IsSuccessful)
{ {
ushort sessionTokenLen = stream.ReadUInt16(); ushort sessionTokenLen = stream.ReadUInt16();
SessionToken = stream.ReadUTF8String(sessionTokenLen); SessionToken = stream.ReadUtf8String(sessionTokenLen);
} }
else else
{ {
@ -56,7 +57,7 @@ public class AuthResponseMessage : BaseMessage, IMessage<AuthResponseMessage>
{ {
short sessionTokenByteLen = (short)Encoding.UTF8.GetByteCount(SessionToken!); short sessionTokenByteLen = (short)Encoding.UTF8.GetByteCount(SessionToken!);
stream.WriteUInt16(sessionTokenByteLen); stream.WriteUInt16(sessionTokenByteLen);
stream.WriteUTF8String(SessionToken!); stream.WriteUtf8String(SessionToken!);
} }
else else
{ {

View file

@ -1,4 +1,5 @@
using System.Net.Sockets; using System.Net.Sockets;
using GServer.Common.Networking.Core;
namespace GServer.Common.Networking.Messages; namespace GServer.Common.Networking.Messages;

View file

@ -1,26 +1,27 @@
using System.Text; using System.Text;
using GServer.Common.Networking.Core;
using GServer.Common.Networking.Enums; using GServer.Common.Networking.Enums;
namespace GServer.Common.Networking.Messages.Server; namespace GServer.Common.Networking.Messages.Server;
public class AuthMessage : BaseMessage, IMessage<AuthMessage> public class AuthMessage : BaseMessage, IMessage<AuthMessage>
{ {
public string Username { get; private set; } public string Username { get; }
public string Password { get; private set; } public string Password { get; }
public AuthMessage(string username, string password) : base((byte)ServerPacketIn.AUTH) public AuthMessage(string username, string password) : base((byte)ServerPacketIn.Auth)
{ {
Username = username; Username = username;
Password = password; Password = password;
} }
public AuthMessage(MessageMemoryStream stream) : base((byte)ServerPacketIn.AUTH) public AuthMessage(MessageMemoryStream stream) : base((byte)ServerPacketIn.Auth)
{ {
byte usernameLen = (byte)stream.ReadByte(); byte usernameLen = (byte)stream.ReadByte();
string username = stream.ReadUTF8String(usernameLen); string username = stream.ReadUtf8String(usernameLen);
byte passwordLen = (byte)stream.ReadByte(); byte passwordLen = (byte)stream.ReadByte();
string password = stream.ReadUTF8String(passwordLen); string password = stream.ReadUtf8String(passwordLen);
Username = username; Username = username;
Password = password; Password = password;

View file

@ -4,7 +4,7 @@ namespace GServer.Common.Networking.Messages.Server;
public class ListServersMessage : BaseMessage, IMessage<ListServersMessage> public class ListServersMessage : BaseMessage, IMessage<ListServersMessage>
{ {
public ListServersMessage() : base((byte)ServerPacketIn.LIST_SERVERS) public ListServersMessage() : base((byte)ServerPacketIn.ListServers)
{ {
} }

View file

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NetCoreServer" Version="8.0.7" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,51 @@
using System.Net.Sockets;
using NetCoreServer;
namespace GServer.NCSServer;
public class GameSession : TcpSession
{
public GameSession(TcpServer server) : base(server)
{
}
protected override void OnConnected()
{
Console.WriteLine($"Chat TCP session with Id {Id} connected!");
}
protected override void OnConnecting()
{
base.OnConnecting();
}
protected override void OnDisconnecting()
{
base.OnDisconnecting();
}
protected override void OnDisconnected()
{
Console.WriteLine($"Chat TCP session with Id {Id} disconnected!");
}
protected override void OnReceived(byte[] buffer, long offset, long size)
{
base.OnReceived(buffer, offset, size);
}
protected override void OnSent(long sent, long pending)
{
base.OnSent(sent, pending);
}
protected override void OnEmpty()
{
base.OnEmpty();
}
protected override void OnError(SocketError error)
{
base.OnError(error);
}
}

View file

@ -0,0 +1,4 @@
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.Run();

View file

@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:58673",
"sslPort": 44396
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7192;http://localhost:5051",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View file

@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View file

@ -2,5 +2,4 @@
public class GameServerOptions public class GameServerOptions
{ {
public int PacketLength { get; set; }
} }

View file

@ -1,15 +1,23 @@
using System.Net; using System.Net;
using GServer.Server;
namespace GServer.Server;
// ReSharper disable once ClassNeverInstantiated.Global
internal sealed class Program internal sealed class Program
{ {
private const int LISTEN_PORT = 11000; private const int ListenPort = 11000;
private static void Main(string[] args) private static void Main(string[] args)
{ {
TCPGameServer server = new(new IPEndPoint(IPAddress.Any, LISTEN_PORT), new GameServerOptions());
CancellationTokenSource cancellationTokenSource = new(); CancellationTokenSource cancellationTokenSource = new();
server.Start(cancellationTokenSource.Token).Wait(); Thread serverWorker = new(delegate()
{
TcpGameServer server = new(new IPEndPoint(IPAddress.Any, ListenPort), new GameServerOptions());
server.Start();
});
serverWorker.Start();
} }
} }

View file

@ -1,24 +0,0 @@
using GServer.Common.Game.Entities;
namespace GServer.Server.Services;
public class ServerListService
{
public ServerListService()
{
}
public IEnumerable<ServerListing> List()
{
return [
new ServerListing
{
Name = "Smallville",
Description = "A tiny development server!",
IPAddress = "localhost",
Port = 11001,
Playercount = 1,
}
];
}
}

View file

@ -1,94 +0,0 @@
using System.Net;
using System.Net.Sockets;
using GServer.Common;
using GServer.Common.Networking.Messages;
namespace GServer.Server;
public class TCPGameServer : IDisposable
{
private readonly TcpListener _tcpListener;
private readonly IPEndPoint _endPoint;
private readonly GameServerOptions _options;
private readonly IMessageHandler _messageHandler;
public TCPGameServer(IPEndPoint endPoint, GameServerOptions options)
{
_endPoint = endPoint;
_options = options;
_tcpListener = new TcpListener(endPoint);
_messageHandler = new TCPMessageHandler();
}
/// <summary>
/// Bind the server to the given endpoint.
/// </summary>
public async Task Start(CancellationToken cancellationToken)
{
Console.WriteLine("Starting TCPGameServer listener...");
_tcpListener.Start();
_ = cancellationToken.Register(_tcpListener.Stop);
Console.WriteLine($"TCPGameServer listening on {_endPoint}");
while (!cancellationToken.IsCancellationRequested)
{
try
{
using TcpClient tcpClient = await _tcpListener.AcceptTcpClientAsync(cancellationToken);
await ProcessAsync(tcpClient);
}
catch (SocketException) when (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("TcpListener stopped listening because cancellation was requested.");
}
catch
{
throw;
}
finally
{
_tcpListener.Stop();
}
}
}
/// <summary>
/// Begin processing messages
/// </summary>
/// <returns></returns>
public async Task ProcessAsync(TcpClient tcpClient)
{
try
{
using NetworkStream stream = tcpClient.GetStream();
byte[] data = new byte[tcpClient.ReceiveBufferSize];
int bytesRead = 0;
int chunkSize = 1;
// Read everything into the data buffer
while (bytesRead < data.Length && chunkSize > 0)
{
bytesRead +=
chunkSize =
await stream.ReadAsync(data.AsMemory(bytesRead, data.Length - bytesRead));
}
// Use the in-memory buffer to process the message
await _messageHandler.HandleMessageAsync(stream.Socket, new MessageMemoryStream(data));
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
}
}

View file

@ -0,0 +1,75 @@
using System.Net;
using System.Net.Sockets;
using GServer.Common.Networking.Core;
using GServer.Common.Networking.Messages;
namespace GServer.Server;
public class TcpGameServer(IPEndPoint endPoint, GameServerOptions options) : IDisposable
{
private readonly GameServerOptions _options = options;
private readonly TcpListener _tcpListener = new(endPoint);
private readonly IMessageHandler _messageHandler = new TcpMessageHandler();
/// <summary>
/// Bind the server to the given endpoint.
/// </summary>
public void Start()
{
Console.WriteLine($"Starting ${nameof(TcpGameServer)} listener...");
_tcpListener.Start();
Console.WriteLine($"{nameof(TcpGameServer)} listening on {endPoint}");
while (true)
{
try
{
Console.WriteLine("Waiting for a connection...");
_tcpListener.AcceptTcpClient();
Console.WriteLine("Client accepted!");
Thread worker = new Thread(new ParameterizedThreadStart(HandleClient!));
worker.Start();
}
finally
{
_tcpListener.Stop();
}
}
}
private void HandleClient(object clientObj)
{
TcpClient tcpClient = (TcpClient)clientObj;
try
{
using NetworkStream stream = tcpClient.GetStream();
byte[] data = new byte[tcpClient.ReceiveBufferSize];
while (stream.Read(data, 0, data.Length) != 0)
{
// Use the in-memory buffer to process the message
_messageHandler.HandleMessageAsync(stream.Socket, new MessageMemoryStream(data)).Wait();
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private async void Stop()
{
Console.WriteLine($"Stopping ${nameof(TcpGameServer)} listener...");
_tcpListener.Stop();
Console.WriteLine($"Stopped ${nameof(TcpGameServer)} listener.");
}
public void Dispose()
{
Stop();
GC.SuppressFinalize(this);
}
}

View file

@ -1,5 +1,5 @@
using System.Net.Sockets; using System.Net.Sockets;
using GServer.Common; using GServer.Common.Networking.Core;
using GServer.Common.Networking.Enums; using GServer.Common.Networking.Enums;
using GServer.Common.Networking.Messages; using GServer.Common.Networking.Messages;
using GServer.Common.Networking.Messages.Client; using GServer.Common.Networking.Messages.Client;
@ -7,12 +7,8 @@ using GServer.Common.Networking.Messages.Server;
namespace GServer.Server; namespace GServer.Server;
public class TCPMessageHandler : IMessageHandler public class TcpMessageHandler : IMessageHandler
{ {
public TCPMessageHandler()
{
}
public async Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream) public async Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream)
{ {
ServerPacketIn serverPacketIn = (ServerPacketIn)messageStream.ReadByte(); ServerPacketIn serverPacketIn = (ServerPacketIn)messageStream.ReadByte();
@ -21,11 +17,11 @@ public class TCPMessageHandler : IMessageHandler
switch (serverPacketIn) switch (serverPacketIn)
{ {
case ServerPacketIn.AUTH: case ServerPacketIn.Auth:
AuthMessage msg = new(messageStream); AuthMessage msg = new(messageStream);
AuthResponseMessage resp = msg.Username == "aaronyarbz" && msg.Password == "password123" AuthResponseMessage resp = msg is { Username: "aaronyarbz", Password: "password123" }
? new(true, Guid.NewGuid().ToString(), null) ? new(true, Guid.NewGuid().ToString(), failureReason: null)
: new(false, null, AuthResponseFailure.IncorrectLoginOrPassword); : new(false, null, AuthResponseFailure.IncorrectLoginOrPassword);
byte[] buffer = resp.Serialize(); byte[] buffer = resp.Serialize();
@ -33,7 +29,7 @@ public class TCPMessageHandler : IMessageHandler
break; break;
case ServerPacketIn.LIST_SERVERS: case ServerPacketIn.ListServers:
throw new NotImplementedException(); throw new NotImplementedException();
default: default:

View file

@ -1,74 +0,0 @@
using System.Net;
using System.Net.Sockets;
using GServer.Common;
using GServer.Common.Networking.Enums;
namespace GServer.Server;
public class UDPGameServer : IDisposable
{
public readonly UdpClient UdpClient;
private readonly IPEndPoint _endPoint;
public UDPGameServer(IPEndPoint endPoint)
{
_endPoint = endPoint;
UdpClient = new UdpClient();
UdpClient.Client.SetSocketOption(
SocketOptionLevel.Socket,
SocketOptionName.ReuseAddress,
true);
}
/// <summary>
/// Bind the server to the given endpoint.
/// </summary>
public void Start()
{
UdpClient.Client.Bind(_endPoint);
Console.WriteLine($"UDPGameServer listening on {_endPoint}");
}
/// <summary>
/// Begin processing messages
/// </summary>
/// <returns></returns>
public async Task ProcessAsync()
{
try
{
UdpReceiveResult res = await UdpClient.ReceiveAsync();
byte[] bytes = res.Buffer;
MessageMemoryStream stream = new(bytes);
await HandleMessageAsync(stream, res.RemoteEndPoint);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private async Task HandleMessageAsync(MessageMemoryStream stream, IPEndPoint remoteEndPoint)
{
byte serverPacketInByte = (byte)stream.ReadByte();
ServerPacketIn serverPacketIn = (ServerPacketIn)serverPacketInByte;
Console.WriteLine($"Handling UDP message {serverPacketInByte} from {remoteEndPoint}...");
throw serverPacketIn switch
{
ServerPacketIn.AUTH => new NotImplementedException(),
ServerPacketIn.LIST_SERVERS => new NotImplementedException(),
_ => new NotImplementedException($"Received unsupported packet {serverPacketInByte}"),
};
}
public void Dispose()
{
UdpClient.Close();
UdpClient.Dispose();
GC.SuppressFinalize(this);
}
}

View file

@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GServer.Server", "GServer.S
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GServer.Client", "GServer.Client\GServer.Client.csproj", "{C105363D-E719-4296-94A2-01170E603889}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GServer.Client", "GServer.Client\GServer.Client.csproj", "{C105363D-E719-4296-94A2-01170E603889}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GServer.NCSServer", "GServer.NCSServer\GServer.NCSServer.csproj", "{29CBF617-0334-4DC0-A61E-6F03796AF66F}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,10 @@ Global
{C105363D-E719-4296-94A2-01170E603889}.Debug|Any CPU.Build.0 = Debug|Any CPU {C105363D-E719-4296-94A2-01170E603889}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C105363D-E719-4296-94A2-01170E603889}.Release|Any CPU.ActiveCfg = Release|Any CPU {C105363D-E719-4296-94A2-01170E603889}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C105363D-E719-4296-94A2-01170E603889}.Release|Any CPU.Build.0 = Release|Any CPU {C105363D-E719-4296-94A2-01170E603889}.Release|Any CPU.Build.0 = Release|Any CPU
{29CBF617-0334-4DC0-A61E-6F03796AF66F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29CBF617-0334-4DC0-A61E-6F03796AF66F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29CBF617-0334-4DC0-A61E-6F03796AF66F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29CBF617-0334-4DC0-A61E-6F03796AF66F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE