From 532ed91a72b797e11c56f6a7032e8e8f6d582617 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Sun, 2 Sep 2012 21:38:13 +0000 Subject: Protocol proxy now decrypts the data (but doesn't understand the packets yet) git-svn-id: http://mc-server.googlecode.com/svn/trunk@826 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- ProtoProxy/Connection.cpp | 515 +++++++++++++++++++++++++++++++++++++++++-- ProtoProxy/Connection.h | 59 +++++ ProtoProxy/Globals.h | 1 + ProtoProxy/ProtoProxy.vcproj | 12 +- 4 files changed, 569 insertions(+), 18 deletions(-) diff --git a/ProtoProxy/Connection.cpp b/ProtoProxy/Connection.cpp index fe37d5003..5cc603641 100644 --- a/ProtoProxy/Connection.cpp +++ b/ProtoProxy/Connection.cpp @@ -11,12 +11,66 @@ +#define HANDLE_CLIENT_PACKET_READ(Proc, Type, Var) \ + Type Var; \ + { \ + if (!m_ClientBuffer.Proc(Var)) \ + { \ + return; \ + } \ + } + +#define HANDLE_SERVER_PACKET_READ(Proc, Type, Var) \ + Type Var; \ + { \ + if (!m_ServerBuffer.Proc(Var)) \ + { \ + return; \ + } \ + } + +#define CLIENTSEND(...) SendData(m_ClientSocket, __VA_ARGS__, "Client") +#define SERVERSEND(...) SendData(m_ServerSocket, __VA_ARGS__, "Server") +#define CLIENTENCRYPTSEND(...) SendEncryptedData(m_ClientSocket, m_ClientEncryptor, __VA_ARGS__, "Client") +#define SERVERENCRYPTSEND(...) SendEncryptedData(m_ServerSocket, m_ServerEncryptor, __VA_ARGS__, "Server") + +#define MAX_ENC_LEN 1024 + + + +typedef unsigned char Byte; + + + + + +enum +{ + PACKET_HANDSHAKE = 0x02, + PACKET_ENCRYPTION_KEY_RESPONSE = 0xfc, + PACKET_ENCRYPTION_KEY_REQUEST = 0xfd, + PACKET_PING = 0xfe, + PACKET_KICK = 0xff, +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cConnection: + cConnection::cConnection(SOCKET a_ClientSocket, cServer & a_Server) : m_Server(a_Server), m_LogFile(NULL), m_ClientSocket(a_ClientSocket), m_ServerSocket(-1), - m_BeginTick(clock()) + m_BeginTick(clock()), + m_ClientState(csUnencrypted), + m_ServerState(csUnencrypted), + m_ClientBuffer(64 KiB), + m_ServerBuffer(64 KiB), + m_Nonce(0) { AString fnam; Printf(fnam, "Log_%d.log", (int)time(NULL)); @@ -55,23 +109,26 @@ void cConnection::Run(void) if (res <= 0) { printf("select() failed: %d; aborting client", WSAGetLastError()); - return; + break; } if (FD_ISSET(m_ServerSocket, &ReadFDs)) { if (!RelayFromServer()) { - return; + break; } } if (FD_ISSET(m_ClientSocket, &ReadFDs)) { if (!RelayFromClient()) { - return; + break; } } } + Log("Relaying ended, closing sockets"); + closesocket(m_ServerSocket); + closesocket(m_ClientSocket); } @@ -88,8 +145,12 @@ void cConnection::Log(const char * a_Format, ...) AString FullMsg; Printf(FullMsg, "[%5.3f] %s\n", GetRelativeTime(), msg.c_str()); + // Log to file: cCSLock Lock(m_CSLog); fputs(FullMsg.c_str(), m_LogFile); + + // Log to screen: + puts(FullMsg.c_str()); } @@ -107,8 +168,12 @@ void cConnection::DataLog(const void * a_Data, int a_Size, const char * a_Format AString Hex; Printf(FullMsg, "[%5.3f] %s\n%s", GetRelativeTime(), msg.c_str(), CreateHexDump(Hex, a_Data, a_Size, 16).c_str()); + // Log to file: cCSLock Lock(m_CSLog); fputs(FullMsg.c_str(), m_LogFile); + + // Log to screen: + puts(FullMsg.c_str()); } @@ -131,6 +196,7 @@ bool cConnection::ConnectToServer(void) printf("connection to server failed: %d\n", WSAGetLastError()); return false; } + Log("Connected to SERVER"); return true; } @@ -148,14 +214,27 @@ bool cConnection::RelayFromServer(void) return false; } - DataLog(Buffer, res, "Received %d bytes from the server", res); - // TODO: Process the data + DataLog(Buffer, res, "Received %d bytes from the SERVER", res); - res = send(m_ClientSocket, Buffer, res, 0); - if (res <= 0) + switch (m_ServerState) { - Log("Client closed the socket: %d, %d; aborting connection", res, WSAGetLastError()); - return false; + case csUnencrypted: + { + return DecodeServersPackets(Buffer, res); + } + case csEncryptedUnderstood: + { + m_ServerDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res); + DataLog(Buffer, res, "Decrypted %d bytes from the SERVER", res); + return DecodeServersPackets(Buffer, res); + } + case csEncryptedUnknown: + { + m_ServerDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res); + DataLog(Buffer, res, "Decrypted %d bytes from the SERVER", res); + m_ClientEncryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res); + return CLIENTSEND(Buffer, res); + } } return true; @@ -175,14 +254,27 @@ bool cConnection::RelayFromClient(void) return false; } - DataLog(Buffer, res, "Received %d bytes from the client", res); - // TODO: Process the data + DataLog(Buffer, res, "Received %d bytes from the CLIENT", res); - res = send(m_ServerSocket, Buffer, res, 0); - if (res <= 0) + switch (m_ClientState) { - Log("Server closed the socket: %d, %d; aborting connection", res, WSAGetLastError()); - return false; + case csUnencrypted: + { + return DecodeClientsPackets(Buffer, res); + } + case csEncryptedUnderstood: + { + m_ClientDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res); + DataLog(Buffer, res, "Decrypted %d bytes from the CLIENT", res); + return DecodeClientsPackets(Buffer, res); + } + case csEncryptedUnknown: + { + m_ClientDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res); + DataLog(Buffer, res, "Decrypted %d bytes from the CLIENT", res); + m_ServerEncryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res); + return SERVERSEND(Buffer, res); + } } return true; @@ -201,3 +293,394 @@ double cConnection::GetRelativeTime(void) + +bool cConnection::SendData(SOCKET a_Socket, const char * a_Data, int a_Size, const char * a_Peer) +{ + DataLog(a_Data, a_Size, "Sending data to %s", a_Peer); + + int res = send(a_Socket, a_Data, a_Size, 0); + if (res <= 0) + { + Log("%s closed the socket: %d, %d; aborting connection", a_Peer, res, WSAGetLastError()); + return false; + } + return true; +} + + + + + +bool cConnection::SendData(SOCKET a_Socket, cByteBuffer & a_Data, const char * a_Peer) +{ + AString All; + a_Data.ReadAll(All); + a_Data.CommitRead(); + return SendData(a_Socket, All.data(), All.size(), a_Peer); +} + + + + + +bool cConnection::SendEncryptedData(SOCKET a_Socket, Encryptor & a_Encryptor, const char * a_Data, int a_Size, const char * a_Peer) +{ + DataLog(a_Data, a_Size, "Sending encrypted %d bytes to %s", a_Size, a_Peer); + const byte * Data = (const byte *)a_Data; + while (a_Size > 0) + { + byte Buffer[512]; + int NumBytes = (a_Size > sizeof(Buffer)) ? sizeof(Buffer) : a_Size; + a_Encryptor.ProcessData(Buffer, Data, NumBytes); + bool res = SendData(a_Socket, (const char *)Buffer, NumBytes, a_Peer); + if (!res) + { + return false; + } + Data += NumBytes; + a_Size -= NumBytes; + } + return true; +} + + + + + +bool cConnection::SendEncryptedData(SOCKET a_Socket, Encryptor & a_Encryptor, cByteBuffer & a_Data, const char * a_Peer) +{ + AString All; + a_Data.ReadAll(All); + a_Data.CommitRead(); + return SendEncryptedData(a_Socket, a_Encryptor, All.data(), All.size(), a_Peer); +} + + + + + +bool cConnection::DecodeClientsPackets(const char * a_Data, int a_Size) +{ + if (!m_ClientBuffer.Write(a_Data, a_Size)) + { + Log("Too much queued data for the server, aborting connection"); + return false; + } + + while (m_ClientBuffer.CanReadBytes(1)) + { + unsigned char PacketType; + m_ClientBuffer.ReadByte(PacketType); + switch (PacketType) + { + case PACKET_ENCRYPTION_KEY_RESPONSE: HandleClientEncryptionKeyResponse(); break; + case PACKET_HANDSHAKE: HandleClientHandshake(); break; + case PACKET_PING: HandleClientPing(); break; + default: + { + if (m_ClientState == csEncryptedUnderstood) + { + Log("Unknown packet 0x%02x from the client while encrypted; continuing to relay blind only", PacketType); + m_ClientState = csEncryptedUnknown; + m_ClientBuffer.ResetRead(); + if (m_ServerState == csUnencrypted) + { + SERVERSEND(m_ClientBuffer); + } + else + { + SERVERENCRYPTSEND(m_ClientBuffer); + } + return true; + } + else + { + Log("Unknown packet 0x%02x from the client while unencrypted; aborting connection", PacketType); + return false; + } + } + } // switch (PacketType) + m_ClientBuffer.CommitRead(); + } // while (CanReadBytes(1)) + return true; +} + + + + + +bool cConnection::DecodeServersPackets(const char * a_Data, int a_Size) +{ + if (!m_ServerBuffer.Write(a_Data, a_Size)) + { + Log("Too much queued data for the client, aborting connection"); + return false; + } + + if ( + (m_ServerState == csEncryptedUnderstood) && + (m_ClientState == csUnencrypted) + ) + { + // Client hasn't finished encryption handshake yet, don't send them any data yet + } + + while (m_ServerBuffer.CanReadBytes(1)) + { + unsigned char PacketType; + m_ServerBuffer.ReadByte(PacketType); + switch (PacketType) + { + case PACKET_ENCRYPTION_KEY_REQUEST: HandleServerEncryptionKeyRequest(); break; + case PACKET_ENCRYPTION_KEY_RESPONSE: HandleServerEncryptionKeyResponse(); break; + case PACKET_KICK: HandleServerKick(); break; + default: + { + if (m_ServerState == csEncryptedUnderstood) + { + Log("Unknown packet 0x%02x from the server while encrypted; continuing to relay blind only", PacketType); + m_ServerState = csEncryptedUnknown; + m_ServerBuffer.ResetRead(); + if (m_ClientState == csUnencrypted) + { + CLIENTSEND(m_ServerBuffer); + } + else + { + CLIENTENCRYPTSEND(m_ServerBuffer); + } + return true; + } + else + { + Log("Unknown packet 0x%02x from the server while unencrypted; aborting connection", PacketType); + return false; + } + } + } // switch (PacketType) + m_ServerBuffer.CommitRead(); + } // while (CanReadBytes(1)) + return true; +} + + + + + +void cConnection::HandleClientEncryptionKeyResponse(void) +{ + HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, EncKeyLength); + AString EncKey; + if (!m_ClientBuffer.ReadString(EncKey, EncKeyLength)) + { + return; + } + HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, EncNonceLength); + AString EncNonce; + if (!m_ClientBuffer.ReadString(EncNonce, EncNonceLength)) + { + return; + } + if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN)) + { + Log("Client: Too long encryption params"); + return; + } + StartClientEncryption(EncKey, EncNonce); +} + + + + + +void cConnection::HandleClientHandshake(void) +{ + // Read the packet from the client: + HANDLE_CLIENT_PACKET_READ(ReadByte, Byte, ProtocolVersion); + HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Username); + HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, ServerHost); + HANDLE_CLIENT_PACKET_READ(ReadBEInt, int, ServerPort); + m_ClientBuffer.CommitRead(); + + // Send the same packet to the server: + cByteBuffer ToServer(512); + ToServer.WriteByte (PACKET_HANDSHAKE); + ToServer.WriteByte (ProtocolVersion); + ToServer.WriteBEUTF16String16(Username); + ToServer.WriteBEUTF16String16(ServerHost); + ToServer.WriteBEInt (m_Server.GetConnectPort()); + SERVERSEND(ToServer); +} + + + + + +void cConnection::HandleClientPing(void) +{ + Log("Received a PACKET_PING from the CLIENT"); + m_ClientBuffer.ResetRead(); + SERVERSEND(m_ClientBuffer); +} + + + + + +void cConnection::HandleServerEncryptionKeyRequest(void) +{ + // Read the packet from the server: + HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, ServerID); + HANDLE_SERVER_PACKET_READ(ReadBEShort, short, PublicKeyLength); + AString PublicKey; + if (!m_ServerBuffer.ReadString(PublicKey, PublicKeyLength)) + { + return; + } + HANDLE_SERVER_PACKET_READ(ReadBEShort, short, NonceLength); + AString Nonce; + if (!m_ServerBuffer.ReadString(Nonce, NonceLength)) + { + return; + } + Log("Got PACKET_ENCRYPTION_KEY_REQUEST from the SERVER:"); + Log(" ServerID = %s", ServerID.c_str()); + + // Reply to the server: + SendEncryptionKeyResponse(PublicKey, Nonce); + + // Send a 0xFD Encryption Key Request http://wiki.vg/Protocol#0xFD to the client, using our own key: + Log("Sending PACKET_ENCRYPTION_KEY_REQUEST to the CLIENT"); + AString key; + StringSink sink(key); // GCC won't allow inline instantiation in the following line, damned temporary refs + m_Server.GetPublicKey().Save(sink); + cByteBuffer ToClient(512); + ToClient.WriteByte (PACKET_ENCRYPTION_KEY_REQUEST); + ToClient.WriteBEUTF16String16(ServerID); + ToClient.WriteBEShort ((short)key.size()); + ToClient.WriteBuf (key.data(), key.size()); + ToClient.WriteBEShort (4); + ToClient.WriteBEInt (m_Nonce); // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :) + CLIENTSEND(ToClient); +} + + + + + +void cConnection::HandleServerEncryptionKeyResponse(void) +{ + HANDLE_SERVER_PACKET_READ(ReadBEInt, int, Lengths); + if (Lengths != 0) + { + Log("Lengths are not zero!"); + return; + } + m_ServerState = csEncryptedUnderstood; +} + + + + + +void cConnection::HandleServerKick(void) +{ + HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Reason); + Log("Received PACKET_KICK from the SERVER:"); + Log(" Reason = \"%s\"", Reason.c_str()); + + cByteBuffer ToClient(Reason.size() * 2 + 4); + ToClient.WriteByte(PACKET_KICK); + ToClient.WriteBEUTF16String16(Reason); + CLIENTSEND(ToClient); +} + + + + + +void cConnection::SendEncryptionKeyResponse(const AString & a_ServerPublicKey, const AString & a_Nonce) +{ + // Generate the shared secret and encrypt using the server's public key + byte SharedSecret[16]; + byte EncryptedSecret[128]; + memset(SharedSecret, 0, sizeof(SharedSecret)); // Use all zeroes for the initial secret + RSA::PublicKey pk; + CryptoPP::StringSource src(a_ServerPublicKey, true); + ByteQueue bq; + src.TransferTo(bq); + bq.MessageEnd(); + pk.Load(bq); + RSAES::Encryptor rsaEncryptor(pk); + AutoSeededRandomPool rng; + int EncryptedLength = rsaEncryptor.FixedCiphertextLength(); + ASSERT(EncryptedLength <= sizeof(EncryptedSecret)); + rsaEncryptor.Encrypt(rng, SharedSecret, sizeof(SharedSecret), EncryptedSecret); + m_ServerEncryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1)); + m_ServerDecryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1)); + + // Encrypt the nonce: + byte EncryptedNonce[128]; + rsaEncryptor.Encrypt(rng, (const byte *)(a_Nonce.data()), a_Nonce.size(), EncryptedNonce); + + // Send the packet to the server: + Log("Sending PACKET_ENCRYPTION_KEY_RESPONSE to the SERVER"); + cByteBuffer ToServer(1024); + ToServer.WriteByte(PACKET_ENCRYPTION_KEY_RESPONSE); + ToServer.WriteBEShort(EncryptedLength); + ToServer.WriteBuf(EncryptedSecret, EncryptedLength); + ToServer.WriteBEShort(EncryptedLength); + ToServer.WriteBuf(EncryptedNonce, EncryptedLength); + SERVERSEND(ToServer); +} + + + + + +void cConnection::StartClientEncryption(const AString & a_EncKey, const AString & a_EncNonce) +{ + // Decrypt EncNonce using privkey + RSAES::Decryptor rsaDecryptor(m_Server.GetPrivateKey()); + AutoSeededRandomPool rng; + byte DecryptedNonce[MAX_ENC_LEN]; + DecodingResult res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncNonce.data(), a_EncNonce.size(), DecryptedNonce); + if (!res.isValidCoding || (res.messageLength != 4)) + { + Log("Client: Bad nonce length"); + return; + } + if (ntohl(*((int *)DecryptedNonce)) != m_Nonce) + { + Log("Bad nonce value"); + return; + } + + // Decrypt the symmetric encryption key using privkey: + byte SharedSecret[MAX_ENC_LEN]; + res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncKey.data(), a_EncKey.size(), SharedSecret); + if (!res.isValidCoding || (res.messageLength != 16)) + { + Log("Bad key length"); + return; + } + + // Send encryption key response: + cByteBuffer ToClient(6); + ToClient.WriteByte((char)0xfc); + ToClient.WriteBEShort(0); + ToClient.WriteBEShort(0); + CLIENTSEND(ToClient); + + // Start the encryption: + m_ClientEncryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1)); + m_ClientDecryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1)); + m_ClientState = csEncryptedUnderstood; + + // Handle all postponed server data + DecodeServersPackets(NULL, 0); +} + + + + diff --git a/ProtoProxy/Connection.h b/ProtoProxy/Connection.h index decf42435..8022a293f 100644 --- a/ProtoProxy/Connection.h +++ b/ProtoProxy/Connection.h @@ -10,6 +10,7 @@ #pragma once #include +#include "ByteBuffer.h" @@ -32,6 +33,18 @@ class cConnection clock_t m_BeginTick; // Tick when the relative time was first retrieved (used for GetRelativeTime()) + enum eConnectionState + { + csUnencrypted, // The connection is not encrypted. Packets must be decoded in order to be able to start decryption. + csEncryptedUnderstood, // The communication is encrypted and so far all packets have been understood, so they can be still decoded + csEncryptedUnknown, // The communication is encrypted, but an unknown packet has been received, so packets cannot be decoded anymore + }; + + eConnectionState m_ClientState; + eConnectionState m_ServerState; + + int m_Nonce; + public: cConnection(SOCKET a_ClientSocket, cServer & a_Server); ~cConnection(); @@ -42,6 +55,18 @@ public: void DataLog(const void * a_Data, int a_Size, const char * a_Format, ...); protected: + typedef CFB_Mode::Encryption Encryptor; + typedef CFB_Mode::Decryption Decryptor; + + cByteBuffer m_ClientBuffer; + cByteBuffer m_ServerBuffer; + + Decryptor m_ServerDecryptor; + Encryptor m_ServerEncryptor; + + Decryptor m_ClientDecryptor; + Encryptor m_ClientEncryptor; + bool ConnectToServer(void); /// Relays data from server to client; returns false if connection aborted @@ -52,6 +77,40 @@ protected: /// Returns the time relative to the first call of this function, in the fractional seconds elapsed double GetRelativeTime(void); + + /// Sends data to the specified socket. If sending fails, prints a fail message using a_Peer and returns false. + bool SendData(SOCKET a_Socket, const char * a_Data, int a_Size, const char * a_Peer); + + /// Sends data to the specified socket. If sending fails, prints a fail message using a_Peer and returns false. + bool SendData(SOCKET a_Socket, cByteBuffer & a_Data, const char * a_Peer); + + /// Sends data to the specfied socket, after encrypting it using a_Encryptor. If sending fails, prints a fail message using a_Peer and returns false + bool SendEncryptedData(SOCKET a_Socket, Encryptor & a_Encryptor, const char * a_Data, int a_Size, const char * a_Peer); + + /// Sends data to the specfied socket, after encrypting it using a_Encryptor. If sending fails, prints a fail message using a_Peer and returns false + bool SendEncryptedData(SOCKET a_Socket, Encryptor & a_Encryptor, cByteBuffer & a_Data, const char * a_Peer); + + /// Decodes packets coming from the client, sends appropriate counterparts to the server; returns false if the connection is to be dropped + bool DecodeClientsPackets(const char * a_Data, int a_Size); + + /// Decodes packets coming from the server, sends appropriate counterparts to the client; returns false if the connection is to be dropped + bool DecodeServersPackets(const char * a_Data, int a_Size); + + // Packet handling, client-side: + void HandleClientEncryptionKeyResponse(void); + void HandleClientHandshake(void); + void HandleClientPing(void); + + // Packet handling, server-side: + void HandleServerEncryptionKeyRequest(void); + void HandleServerEncryptionKeyResponse(void); + void HandleServerKick(void); + + /// Send EKResp to the server: + void SendEncryptionKeyResponse(const AString & a_ServerPublicKey, const AString & a_Nonce); + + /// Starts client encryption based on the parameters received + void StartClientEncryption(const AString & a_EncryptedSecret, const AString & a_EncryptedNonce); } ; diff --git a/ProtoProxy/Globals.h b/ProtoProxy/Globals.h index e97d2e561..e962d9a6f 100644 --- a/ProtoProxy/Globals.h +++ b/ProtoProxy/Globals.h @@ -208,6 +208,7 @@ public: #include "CryptoPP/osrng.h" #include "CryptoPP/rsa.h" +#include "CryptoPP/modes.h" using namespace CryptoPP; diff --git a/ProtoProxy/ProtoProxy.vcproj b/ProtoProxy/ProtoProxy.vcproj index 2d980bed7..aa1d138c3 100644 --- a/ProtoProxy/ProtoProxy.vcproj +++ b/ProtoProxy/ProtoProxy.vcproj @@ -42,7 +42,7 @@ Name="VCCLCompilerTool" Optimization="0" AdditionalIncludeDirectories="..;../source" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS" MinimalRebuild="true" BasicRuntimeChecks="3" RuntimeLibrary="1" @@ -118,7 +118,7 @@ Optimization="2" EnableIntrinsicFunctions="true" AdditionalIncludeDirectories="..;../source" - PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE" + PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS" RuntimeLibrary="0" EnableFunctionLevelLinking="true" UsePrecompiledHeader="2" @@ -176,6 +176,14 @@ Filter="cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;h;hpp" UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" > + + + + -- cgit v1.2.3