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,73 +1,73 @@
using System.Net;
using System.Net.Sockets;
using GServer.Common;
using GServer.Common.Networking.Enums;
using GServer.Common.Networking.Messages.Client;
using GServer.Common.Networking.Messages.Server;
internal class Program
{
private const int SERVER_PORT = 11000;
private static void Main(string[] args)
{
IPEndPoint serverEP = new(IPAddress.Any, SERVER_PORT);
TcpClient tcpClient = new();
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
tcpClient.Connect(serverEP);
Console.WriteLine("Username...");
string username = Console.ReadLine()!;
Console.WriteLine("Password...");
string password = Console.ReadLine()!;
AuthMessage authMessage = new(username, password);
_ = tcpClient.Client.Send(authMessage.Serialize());
try
{
while (true)
{
byte[] bytes = new byte[tcpClient.Client.ReceiveBufferSize];
_ = tcpClient.Client.Receive(bytes);
MessageMemoryStream stream = new(bytes);
ClientPacketIn packetIn = (ClientPacketIn)stream.ReadByte();
switch (packetIn)
{
case ClientPacketIn.AUTH_RESPONSE:
AuthResponseMessage authResultMessage = new(stream);
Console.WriteLine("Success = " + authResultMessage.IsSuccessful);
Console.WriteLine("SessionToken = " + authResultMessage.SessionToken ?? "null");
Console.WriteLine("FailureReason = " + authResultMessage.FailureReason ?? "null");
break;
case ClientPacketIn.LIST_SERVERS_RESPONSE:
break;
case ClientPacketIn.UNKNOWN:
break;
default:
Console.WriteLine($"Received unsupported packet.");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
tcpClient.Close();
}
}
using System.Net;
using System.Net.Sockets;
using GServer.Common.Networking.Core;
using GServer.Common.Networking.Enums;
using GServer.Common.Networking.Messages.Client;
using GServer.Common.Networking.Messages.Server;
namespace GServer.Client;
public class Program
{
private const int ServerPort = 11000;
private static void Main(string[] args)
{
IPEndPoint serverEp = new(IPAddress.Any, ServerPort);
TcpClient tcpClient = new();
tcpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
tcpClient.Connect(serverEp);
Console.WriteLine("Username...");
string username = Console.ReadLine()!;
Console.WriteLine("Password...");
string password = Console.ReadLine()!;
AuthMessage authMessage = new(username, password);
_ = tcpClient.Client.Send(authMessage.Serialize());
try
{
while (true)
{
byte[] bytes = new byte[tcpClient.Client.ReceiveBufferSize];
_ = tcpClient.Client.Receive(bytes);
MessageMemoryStream stream = new(bytes);
ClientPacketIn packetIn = (ClientPacketIn)stream.ReadByte();
switch (packetIn)
{
case ClientPacketIn.AuthResponse:
AuthResponseMessage authResultMessage = new(stream);
Console.WriteLine("Success = " + authResultMessage.IsSuccessful);
Console.WriteLine("SessionToken = " + authResultMessage.SessionToken);
Console.WriteLine("FailureReason = " + authResultMessage.FailureReason);
break;
case ClientPacketIn.ListServersResponse:
break;
case ClientPacketIn.Unknown:
break;
default:
Console.WriteLine("Received unsupported packet.");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
tcpClient.Close();
}
}
}

View file

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

View file

@ -5,7 +5,7 @@ public record ServerListing
public required string Name { get; set; }
public required string Description { 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 ServerTier ServerTier { get; set; }
}

View file

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

View file

@ -5,12 +5,12 @@ public enum ClientPacketIn : byte
/// <summary>
/// Represents an auth result from the server.
/// </summary>
AUTH_RESPONSE = 1,
AuthResponse = 1,
/// <summary>
/// Contains a list of server listings.
/// </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
{
AUTH = 1,
LIST_SERVERS = 2
Auth = 1,
ListServers = 2
}

View file

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

View file

@ -1,68 +1,69 @@
using System.Text;
using GServer.Common.Networking.Enums;
namespace GServer.Common.Networking.Messages.Client;
public enum AuthResponseFailure : byte
{
IncorrectLoginOrPassword,
Unknown
}
public class AuthResponseMessage : BaseMessage, IMessage<AuthResponseMessage>
{
public bool IsSuccessful { get; private set; }
/// <summary>
/// Used to authenticate the user. Only set if IsSuccessful is true.
/// </summary>
public string? SessionToken { get; private set; }
/// <summary>
/// Reason for auth failure. Only set is IsSuccessful is false.
/// </summary>
public AuthResponseFailure? FailureReason { get; private set; }
public AuthResponseMessage(bool isSuccessful, string? sessionToken = null, AuthResponseFailure? failureReason = null) : base((byte)ClientPacketIn.AUTH_RESPONSE)
{
IsSuccessful = isSuccessful;
SessionToken = sessionToken;
FailureReason = failureReason;
}
public AuthResponseMessage(MessageMemoryStream stream) : base((byte)ClientPacketIn.AUTH_RESPONSE)
{
IsSuccessful = stream.ReadBoolean();
if (IsSuccessful)
{
ushort sessionTokenLen = stream.ReadUInt16();
SessionToken = stream.ReadUTF8String(sessionTokenLen);
}
else
{
FailureReason = (AuthResponseFailure)stream.ReadByte();
}
}
public byte[] Serialize()
{
using MessageMemoryStream stream = new();
stream.WriteByte(PacketId);
stream.WriteBoolean(IsSuccessful);
if (IsSuccessful)
{
short sessionTokenByteLen = (short)Encoding.UTF8.GetByteCount(SessionToken!);
stream.WriteUInt16(sessionTokenByteLen);
stream.WriteUTF8String(SessionToken!);
}
else
{
stream.WriteByte((byte)FailureReason!);
}
return stream.ToArray();
}
}
using System.Text;
using GServer.Common.Networking.Core;
using GServer.Common.Networking.Enums;
namespace GServer.Common.Networking.Messages.Client;
public enum AuthResponseFailure : byte
{
IncorrectLoginOrPassword,
Unknown
}
public class AuthResponseMessage : BaseMessage, IMessage<AuthResponseMessage>
{
public bool IsSuccessful { get; }
/// <summary>
/// Used to authenticate the user. Only set if IsSuccessful is true.
/// </summary>
public string? SessionToken { get; }
/// <summary>
/// Reason for auth failure. Only set is IsSuccessful is false.
/// </summary>
public AuthResponseFailure? FailureReason { get; }
public AuthResponseMessage(bool isSuccessful, string? sessionToken = null, AuthResponseFailure? failureReason = null) : base((byte)ClientPacketIn.AuthResponse)
{
IsSuccessful = isSuccessful;
SessionToken = sessionToken;
FailureReason = failureReason;
}
public AuthResponseMessage(MessageMemoryStream stream) : base((byte)ClientPacketIn.AuthResponse)
{
IsSuccessful = stream.ReadBoolean();
if (IsSuccessful)
{
ushort sessionTokenLen = stream.ReadUInt16();
SessionToken = stream.ReadUtf8String(sessionTokenLen);
}
else
{
FailureReason = (AuthResponseFailure)stream.ReadByte();
}
}
public byte[] Serialize()
{
using MessageMemoryStream stream = new();
stream.WriteByte(PacketId);
stream.WriteBoolean(IsSuccessful);
if (IsSuccessful)
{
short sessionTokenByteLen = (short)Encoding.UTF8.GetByteCount(SessionToken!);
stream.WriteUInt16(sessionTokenByteLen);
stream.WriteUtf8String(SessionToken!);
}
else
{
stream.WriteByte((byte)FailureReason!);
}
return stream.ToArray();
}
}

View file

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

View file

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

View file

@ -4,7 +4,7 @@ namespace GServer.Common.Networking.Messages.Server;
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

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\GServer.Common\GServer.Common.csproj" />
<ItemGroup>
<ProjectReference Include="..\GServer.Common\GServer.Common.csproj" />
</ItemGroup>
<PropertyGroup>

View file

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

View file

@ -1,15 +1,23 @@
using System.Net;
using GServer.Server;
namespace GServer.Server;
// ReSharper disable once ClassNeverInstantiated.Global
internal sealed class Program
{
private const int LISTEN_PORT = 11000;
private const int ListenPort = 11000;
private static void Main(string[] args)
{
TCPGameServer server = new(new IPEndPoint(IPAddress.Any, LISTEN_PORT), new GameServerOptions());
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 GServer.Common;
using GServer.Common.Networking.Core;
using GServer.Common.Networking.Enums;
using GServer.Common.Networking.Messages;
using GServer.Common.Networking.Messages.Client;
@ -7,12 +7,8 @@ using GServer.Common.Networking.Messages.Server;
namespace GServer.Server;
public class TCPMessageHandler : IMessageHandler
public class TcpMessageHandler : IMessageHandler
{
public TCPMessageHandler()
{
}
public async Task HandleMessageAsync(Socket clientSocket, MessageMemoryStream messageStream)
{
ServerPacketIn serverPacketIn = (ServerPacketIn)messageStream.ReadByte();
@ -21,11 +17,11 @@ public class TCPMessageHandler : IMessageHandler
switch (serverPacketIn)
{
case ServerPacketIn.AUTH:
case ServerPacketIn.Auth:
AuthMessage msg = new(messageStream);
AuthResponseMessage resp = msg.Username == "aaronyarbz" && msg.Password == "password123"
? new(true, Guid.NewGuid().ToString(), null)
AuthResponseMessage resp = msg is { Username: "aaronyarbz", Password: "password123" }
? new(true, Guid.NewGuid().ToString(), failureReason: null)
: new(false, null, AuthResponseFailure.IncorrectLoginOrPassword);
byte[] buffer = resp.Serialize();
@ -33,7 +29,7 @@ public class TCPMessageHandler : IMessageHandler
break;
case ServerPacketIn.LIST_SERVERS:
case ServerPacketIn.ListServers:
throw new NotImplementedException();
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
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GServer.Client", "GServer.Client\GServer.Client.csproj", "{C105363D-E719-4296-94A2-01170E603889}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GServer.NCSServer", "GServer.NCSServer\GServer.NCSServer.csproj", "{29CBF617-0334-4DC0-A61E-6F03796AF66F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE