summaryrefslogblamecommitdiffstats
path: root/ProtoProxy/Connection.cpp
blob: b0f63493bfda8d8b9f981712238c7dcadae1984c (plain) (tree)
1
2
3
4
5
6
7
8
9







                                                                                       
                    




 




                                                     
                                        







                                                     
                                        







                                                                                                           































                                                                               


                                                                                                                                              














                                                      



                         
 







                            















                                                 
                                                 






                                                 
                                                 





                                                 
                                                 


                                                 
                                                 


                                                 
                                                 
                                                 

                                                 
                                                 
                                                 





                                                 
                                                 







                                                 




    



















                                      



                                                                                                                        




                                                                      


                                      

                                  
                   





































                                                                                           
                               




                                                        
                                       





                                                        
                                       


                          


                                                















                                                                         
                        

                                           

                          
                              














                                                                                       
                                                                                                                             
         
                        

                                           
         
           
                          

                              





















                                                                                     
                                    








                                        
                             






                                                                                                      
                                                                        
         
                               
          
















                                                                                            










                                        
                             






                                                                                                      
                                                                        
         
                               
          
















                                                                                            

















                                                                 































                                                                                                                                    
                                                                              


                                                  
                                     






































                                                                                                                         
                                                                                                                          



                                                     





                                                                                                                               
                                                                                                                    



                                                                                                                     
                                                                                                                       








                                                                                                                          



                                                                            
                                                                                                                                                                     



                                                                                                                                             















































                                                                                                                                         
                                                                                                                                                           

                                    
                                                                                                                   


                                                                                                                       
                                                                                                                     
                                                                                                               
                                                                                                                       










                                                                                                                              
                                                                                                                      





                                                                                                                        
                                                                                                                        




                                                                                                                          
                                                                                                                   



                                                                                                                          


                                                                                                                    

                                                                                                                        
                                                                                                                      
                                                                                                                  



                                                                            
                                                                                                                                                                         



                                                                                                                                             



























                                                                                                                                         









































                                                                   












                                                                          













                                                                   


















                                                                             
                                                          




                                                                     
                             




                                                                       
                             



                                                                            
                             

                                                 
                     





  














                                                                 
                                              







                                                                                  
                                                                  






                                                                  






                     











                                                            








                                                                               





  
                                         
  
                                                       

                                    






                     















                                                                                        




































                                                                     
                          






                     












                                                                       

































                                                                                                                     






                     























































                                                                        



















                                                                               















                                                                  














                                                                     



                                                                          
                                                    







                          














                                                                  








                                                               





  
















                                                                  
                                                         






                                                                                  
                              




                                                                              
                              



















                                                                                                                                                






                     














































                                                                    














                                                                    





  


























































































                                                                             
















                                                                           













                                                              




                                                            
                             
          
                                                      
                                               
                     





  
                                         



                                                                         















































                                                                             
                                                                              
         









                                                                         



























                                                                              





















                                                                             




















                                                                                                    





















































                                                                             
















                                                                        




















                                                                 


















                                                                 

































                                                                                                       















                                                                                                                                         













                                                                                                                                                     

                                                                                                    


















                                                                                                               
                   


                                                                        
                                        




























































                                                                                                        











                                                              













                                                                   



















                                                                                                                     























                                                                              












                                                                



























                                                                   


















                                                                             
































                                                                           





  




















































                                                                              












                                                                                                        


                                                          




























                                                                                                                                                    


                                                          































                                                                                                                                                    
                                                   








                                               

// Connection.cpp

// Interfaces to the cConnection class representing a single pair of connected sockets

#include "Globals.h"
#include "Connection.h"
#include "Server.h"
#include <iostream>





#define HANDLE_CLIENT_PACKET_READ(Proc, Type, Var) \
	Type Var; \
	{ \
		if (!m_ClientBuffer.Proc(Var)) \
		{ \
			return false; \
		} \
	}

#define HANDLE_SERVER_PACKET_READ(Proc, Type, Var) \
	Type Var; \
	{ \
		if (!m_ServerBuffer.Proc(Var)) \
		{ \
			return false; \
		} \
	}

#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 COPY_TO_SERVER() \
	{ \
		AString ToServer; \
		m_ClientBuffer.ReadAgain(ToServer); \
		if (m_ServerState == csUnencrypted) \
		{ \
			SERVERSEND(ToServer.data(), ToServer.size()); \
		} \
		else \
		{ \
			SERVERENCRYPTSEND(ToServer.data(), ToServer.size()); \
		} \
	}

#define COPY_TO_CLIENT() \
	{ \
		AString ToClient; \
		m_ServerBuffer.ReadAgain(ToClient); \
		if (m_ClientState == csUnencrypted) \
		{ \
			CLIENTSEND(ToClient.data(), ToClient.size()); \
		} \
		else \
		{ \
			CLIENTENCRYPTSEND(ToClient.data(), ToClient.size()); \
		} \
	}

#define HANDLE_CLIENT_READ(Proc) \
	{ \
		if (!Proc()) \
		{ \
			AString Leftover; \
			m_ClientBuffer.ReadAgain(Leftover); \
			DataLog(Leftover.data(), Leftover.size(), "Leftover data after client packet parsing, %d bytes:", Leftover.size()); \
			m_ClientBuffer.ResetRead(); \
			return true; \
		} \
	}
	
#define HANDLE_SERVER_READ(Proc) \
	{ \
		if (!Proc()) \
		{ \
			m_ServerBuffer.ResetRead(); \
			return true; \
		} \
	}
	
	
#define MAX_ENC_LEN 1024




typedef unsigned char Byte;





enum
{
	PACKET_KEEPALIVE                 = 0x00,
	PACKET_LOGIN                     = 0x01,
	PACKET_HANDSHAKE                 = 0x02,
	PACKET_CHAT_MESSAGE              = 0x03,
	PACKET_TIME_UPDATE               = 0x04,
	PACKET_ENTITY_EQUIPMENT          = 0x05,
	PACKET_COMPASS                   = 0x06,
	PACKET_USE_ENTITY                = 0x07,
	PACKET_UPDATE_HEALTH             = 0x08,
	PACKET_PLAYER_ON_GROUND          = 0x0a,
	PACKET_PLAYER_POSITION           = 0x0b,
	PACKET_PLAYER_LOOK               = 0x0c,
	PACKET_PLAYER_POSITION_LOOK      = 0x0d,
	PACKET_BLOCK_PLACE               = 0x0f,
	PACKET_SLOT_SELECT               = 0x10,
	PACKET_ANIMATION                 = 0x12,
	PACKET_ENTITY_ACTION             = 0x13,
	PACKET_SPAWN_PICKUP              = 0x15,
	PACKET_COLLECT_PICKUP            = 0x16,
	PACKET_SPAWN_OBJECT_VEHICLE      = 0x17,
	PACKET_SPAWN_MOB                 = 0x18,
	PACKET_SPAWN_PAINTING            = 0x19,
	PACKET_SPAWN_EXPERIENCE_ORB      = 0x1a,
	PACKET_ENTITY_VELOCITY           = 0x1c,
	PACKET_DESTROY_ENTITIES          = 0x1d,
	PACKET_ENTITY                    = 0x1e,
	PACKET_ENTITY_RELATIVE_MOVE      = 0x1f,
	PACKET_ENTITY_LOOK               = 0x20,
	PACKET_ENTITY_RELATIVE_MOVE_LOOK = 0x21,
	PACKET_ENTITY_TELEPORT           = 0x22,
	PACKET_ENTITY_HEAD_LOOK          = 0x23,
	PACKET_ENTITY_STATUS             = 0x26,
	PACKET_ENTITY_METADATA           = 0x28,
	PACKET_ENTITY_EFFECT             = 0x29,
	PACKET_ENTITY_EFFECT_REMOVE      = 0x2a,
	PACKET_SET_EXPERIENCE            = 0x2b,
	PACKET_MAP_CHUNK                 = 0x33,
	PACKET_MULTI_BLOCK_CHANGE        = 0x34,
	PACKET_BLOCK_CHANGE              = 0x35,
	PACKET_BLOCK_ACTION              = 0x36,
	PACKET_MAP_CHUNK_BULK            = 0x38,
	PACKET_SOUND_EFFECT              = 0x3d,
	PACKET_NAMED_SOUND_EFFECT        = 0x3e,
	PACKET_CHANGE_GAME_STATE         = 0x46,
	PACKET_WINDOW_OPEN               = 0x64,
	PACKET_WINDOW_CLOSE              = 0x65,
	PACKET_WINDOW_CLICK              = 0x66,
	PACKET_SET_SLOT                  = 0x67,
	PACKET_WINDOW_CONTENTS           = 0x68,
	PACKET_CREATIVE_INVENTORY_ACTION = 0x6b,
	PACKET_UPDATE_SIGN               = 0x82,
	PACKET_UPDATE_TILE_ENTITY        = 0x84,
	PACKET_PLAYER_LIST_ITEM          = 0xc9,
	PACKET_PLAYER_ABILITIES          = 0xca,
	PACKET_LOCALE_AND_VIEW           = 0xcc,
	PACKET_CLIENT_STATUSES           = 0xcd,
	PACKET_ENCRYPTION_KEY_RESPONSE   = 0xfc,
	PACKET_ENCRYPTION_KEY_REQUEST    = 0xfd,
	PACKET_PING                      = 0xfe,
	PACKET_KICK                      = 0xff,
} ;




enum
{
	OBJECT_BOAT             = 1,
	OBJECT_MINECART         = 10,
	OBJECT_MINECART_STORAGE = 11,
	OBJECT_MINECART_POWERED = 12,
	OBJECT_TNT              = 50,
	OBJECT_ENDERCRYSTAL     = 51,
	OBJECT_ARROW            = 60,
	OBJECT_SNOWBALL         = 61,
	OBJECT_EGG              = 62,
	OBJECT_FALLING_BLOCK    = 70,
	OBJECT_EYE_OF_ENDER     = 72,
	OBJECT_DRAGON_EGG       = 74,
	OBJECT_FISHING_FLOAT    = 90,
} ;





///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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_ClientState(csUnencrypted),
	m_ServerState(csUnencrypted),
	m_ClientBuffer(1024 KiB),
	m_ServerBuffer(1024 KiB),
	m_Nonce(0)
{
	AString fnam;
	Printf(fnam, "Log_%d.log", (int)time(NULL));
	m_LogFile = fopen(fnam.c_str(), "w");
	Log("Log file created");
}





cConnection::~cConnection()
{
	fclose(m_LogFile);
}





void cConnection::Run(void)
{
	if (!ConnectToServer())
	{
		Log("Cannot connect to server; aborting");
		return;
	}
	
	while (true)
	{
		fd_set ReadFDs;
		FD_ZERO(&ReadFDs);
		FD_SET(m_ServerSocket, &ReadFDs);
		FD_SET(m_ClientSocket, &ReadFDs);
		int res = select(2, &ReadFDs, NULL, NULL, NULL);
		if (res <= 0)
		{
			printf("select() failed: %d; aborting client", WSAGetLastError());
			break;
		}
		if (FD_ISSET(m_ServerSocket, &ReadFDs))
		{
			if (!RelayFromServer())
			{
				break;
			}
		}
		if (FD_ISSET(m_ClientSocket, &ReadFDs))
		{
			if (!RelayFromClient())
			{
				break;
			}
		}
	}
	Log("Relaying ended, closing sockets");
	closesocket(m_ServerSocket);
	closesocket(m_ClientSocket);
}





void cConnection::Log(const char * a_Format, ...)
{
	va_list args;
	va_start(args, a_Format);
	AString msg;
	AppendVPrintf(msg, a_Format, args);
	va_end(args);
	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:
	std::cout << FullMsg;
}





void cConnection::DataLog(const void * a_Data, int a_Size, const char * a_Format, ...)
{
	va_list args;
	va_start(args, a_Format);
	AString msg;
	AppendVPrintf(msg, a_Format, args);
	va_end(args);
	AString FullMsg;
	AString Hex;
	Printf(FullMsg, "[%5.3f] %s\n%s\n", 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:
	std::cout << FullMsg;
	//*/
}





bool cConnection::ConnectToServer(void)
{
	m_ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_ServerSocket == INVALID_SOCKET)
	{
		return false;
	}
	sockaddr_in localhost;
	localhost.sin_family = AF_INET;
	localhost.sin_port = htons(m_Server.GetConnectPort());
	localhost.sin_addr.s_addr = htonl(0x7f000001);  // localhost
	if (connect(m_ServerSocket, (sockaddr *)&localhost, sizeof(localhost)) != 0)
	{
		printf("connection to server failed: %d\n", WSAGetLastError());
		return false;
	}
	Log("Connected to SERVER");
	return true;
}





bool cConnection::RelayFromServer(void)
{
	char Buffer[64 KiB];
	int res = recv(m_ServerSocket, Buffer, sizeof(Buffer), 0);
	if (res <= 0)
	{
		Log("Server closed the socket: %d; %d; aborting connection", res, WSAGetLastError());
		return false;
	}
	
	DataLog(Buffer, res, "Received %d bytes from the SERVER", res);
	
	switch (m_ServerState)
	{
		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;
}





bool cConnection::RelayFromClient(void)
{
	char Buffer[64 KiB];
	int res = recv(m_ClientSocket, Buffer, sizeof(Buffer), 0);
	if (res <= 0)
	{
		Log("Client closed the socket: %d; %d; aborting connection", res, WSAGetLastError());
		return false;
	}
	
	DataLog(Buffer, res, "Received %d bytes from the CLIENT", res);
	
	switch (m_ClientState)
	{
		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;
}





double cConnection::GetRelativeTime(void)
{
	return (double)(clock() - m_BeginTick) / CLOCKS_PER_SEC;
	
}





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, "Encrypting %d bytes to %s", a_Size, a_Peer);
	const byte * Data = (const byte *)a_Data;
	while (a_Size > 0)
	{
		byte Buffer[64 KiB];
		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))
	{
		Log("Decoding client's packets, there are now %d bytes in the queue", m_ClientBuffer.GetReadableSpace());
		unsigned char PacketType;
		m_ClientBuffer.ReadByte(PacketType);
		switch (PacketType)
		{
			case PACKET_ANIMATION:                 HANDLE_CLIENT_READ(HandleClientAnimation); break;
			case PACKET_BLOCK_PLACE:               HANDLE_CLIENT_READ(HandleClientBlockPlace); break;
			case PACKET_CHAT_MESSAGE:              HANDLE_CLIENT_READ(HandleClientChatMessage); break;
			case PACKET_CLIENT_STATUSES:           HANDLE_CLIENT_READ(HandleClientClientStatuses); break;
			case PACKET_CREATIVE_INVENTORY_ACTION: HANDLE_CLIENT_READ(HandleClientCreativeInventoryAction); break;
			case PACKET_ENCRYPTION_KEY_RESPONSE:   HANDLE_CLIENT_READ(HandleClientEncryptionKeyResponse); break;
			case PACKET_ENTITY_ACTION:             HANDLE_CLIENT_READ(HandleClientEntityAction); break;
			case PACKET_HANDSHAKE:                 HANDLE_CLIENT_READ(HandleClientHandshake); break;
			case PACKET_KEEPALIVE:                 HANDLE_CLIENT_READ(HandleClientKeepAlive); break;
			case PACKET_LOCALE_AND_VIEW:           HANDLE_CLIENT_READ(HandleClientLocaleAndView); break;
			case PACKET_PING:                      HANDLE_CLIENT_READ(HandleClientPing); break;
			case PACKET_PLAYER_ABILITIES:          HANDLE_CLIENT_READ(HandleClientPlayerAbilities); break;
			case PACKET_PLAYER_LOOK:               HANDLE_CLIENT_READ(HandleClientPlayerLook); break;
			case PACKET_PLAYER_ON_GROUND:          HANDLE_CLIENT_READ(HandleClientPlayerOnGround); break;
			case PACKET_PLAYER_POSITION:           HANDLE_CLIENT_READ(HandleClientPlayerPosition); break;
			case PACKET_PLAYER_POSITION_LOOK:      HANDLE_CLIENT_READ(HandleClientPlayerPositionLook); break;
			case PACKET_SLOT_SELECT:               HANDLE_CLIENT_READ(HandleClientSlotSelect); break;
			case PACKET_UPDATE_SIGN:               HANDLE_CLIENT_READ(HandleClientUpdateSign); break;
			case PACKET_USE_ENTITY:                HANDLE_CLIENT_READ(HandleClientUseEntity); break;
			case PACKET_WINDOW_CLICK:              HANDLE_CLIENT_READ(HandleClientWindowClick); break;
			case PACKET_WINDOW_CLOSE:              HANDLE_CLIENT_READ(HandleClientWindowClose); break;
			default:
			{
				if (m_ClientState == csEncryptedUnderstood)
				{
					Log("****************** Unknown packet 0x%02x from the client while encrypted; continuing to relay blind only", PacketType);
					AString Data;
					m_ClientBuffer.ResetRead();
					m_ClientBuffer.ReadAll(Data);
					DataLog(Data.data(), Data.size(), "Current data in the client packet queue: %d bytes", Data.size());
					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);
		Log("Decoding server's packets, there are now %d bytes in the queue; next packet is 0x%x", m_ServerBuffer.GetReadableSpace(), PacketType);
		switch (PacketType)
		{
			case PACKET_BLOCK_ACTION:              HANDLE_SERVER_READ(HandleServerBlockAction); break;
			case PACKET_BLOCK_CHANGE:              HANDLE_SERVER_READ(HandleServerBlockChange); break;
			case PACKET_CHANGE_GAME_STATE:         HANDLE_SERVER_READ(HandleServerChangeGameState); break;
			case PACKET_CHAT_MESSAGE:              HANDLE_SERVER_READ(HandleServerChatMessage); break;
			case PACKET_COLLECT_PICKUP:            HANDLE_SERVER_READ(HandleServerCollectPickup); break;
			case PACKET_COMPASS:                   HANDLE_SERVER_READ(HandleServerCompass); break;
			case PACKET_DESTROY_ENTITIES:          HANDLE_SERVER_READ(HandleServerDestroyEntities); break;
			case PACKET_ENCRYPTION_KEY_REQUEST:    HANDLE_SERVER_READ(HandleServerEncryptionKeyRequest); break;
			case PACKET_ENCRYPTION_KEY_RESPONSE:   HANDLE_SERVER_READ(HandleServerEncryptionKeyResponse); break;
			case PACKET_ENTITY:                    HANDLE_SERVER_READ(HandleServerEntity); break;
			case PACKET_ENTITY_EQUIPMENT:          HANDLE_SERVER_READ(HandleServerEntityEquipment); break;
			case PACKET_ENTITY_HEAD_LOOK:          HANDLE_SERVER_READ(HandleServerEntityHeadLook); break;
			case PACKET_ENTITY_LOOK:               HANDLE_SERVER_READ(HandleServerEntityLook); break;
			case PACKET_ENTITY_METADATA:           HANDLE_SERVER_READ(HandleServerEntityMetadata); break;
			case PACKET_ENTITY_RELATIVE_MOVE:      HANDLE_SERVER_READ(HandleServerEntityRelativeMove); break;
			case PACKET_ENTITY_RELATIVE_MOVE_LOOK: HANDLE_SERVER_READ(HandleServerEntityRelativeMoveLook); break;
			case PACKET_ENTITY_STATUS:             HANDLE_SERVER_READ(HandleServerEntityStatus); break;
			case PACKET_ENTITY_TELEPORT:           HANDLE_SERVER_READ(HandleServerEntityTeleport); break;
			case PACKET_ENTITY_VELOCITY:           HANDLE_SERVER_READ(HandleServerEntityVelocity); break;
			case PACKET_KEEPALIVE:                 HANDLE_SERVER_READ(HandleServerKeepAlive); break;
			case PACKET_KICK:                      HANDLE_SERVER_READ(HandleServerKick); break;
			case PACKET_LOGIN:                     HANDLE_SERVER_READ(HandleServerLogin); break;
			case PACKET_MAP_CHUNK:                 HANDLE_SERVER_READ(HandleServerMapChunk); break;
			case PACKET_MAP_CHUNK_BULK:            HANDLE_SERVER_READ(HandleServerMapChunkBulk); break;
			case PACKET_MULTI_BLOCK_CHANGE:        HANDLE_SERVER_READ(HandleServerMultiBlockChange); break;
			case PACKET_NAMED_SOUND_EFFECT:        HANDLE_SERVER_READ(HandleServerNamedSoundEffect); break;
			case PACKET_PLAYER_ABILITIES:          HANDLE_SERVER_READ(HandleServerPlayerAbilities); break;
			case PACKET_PLAYER_LIST_ITEM:          HANDLE_SERVER_READ(HandleServerPlayerListItem); break;
			case PACKET_PLAYER_POSITION_LOOK:      HANDLE_SERVER_READ(HandleServerPlayerPositionLook); break;
			case PACKET_SET_EXPERIENCE:            HANDLE_SERVER_READ(HandleServerSetExperience); break;
			case PACKET_SET_SLOT:                  HANDLE_SERVER_READ(HandleServerSetSlot); break;
			case PACKET_SOUND_EFFECT:              HANDLE_SERVER_READ(HandleServerSoundEffect); break;
			case PACKET_SPAWN_MOB:                 HANDLE_SERVER_READ(HandleServerSpawnMob); break;
			case PACKET_SPAWN_OBJECT_VEHICLE:      HANDLE_SERVER_READ(HandleServerSpawnObjectVehicle); break;
			case PACKET_SPAWN_PAINTING:            HANDLE_SERVER_READ(HandleServerSpawnPainting); break;
			case PACKET_SPAWN_PICKUP:              HANDLE_SERVER_READ(HandleServerSpawnPickup); break;
			case PACKET_TIME_UPDATE:               HANDLE_SERVER_READ(HandleServerTimeUpdate); break;
			case PACKET_UPDATE_HEALTH:             HANDLE_SERVER_READ(HandleServerUpdateHealth); break;
			case PACKET_UPDATE_SIGN:               HANDLE_SERVER_READ(HandleServerUpdateSign); break;
			case PACKET_UPDATE_TILE_ENTITY:        HANDLE_SERVER_READ(HandleServerUpdateTileEntity); break;
			case PACKET_WINDOW_CLOSE:              HANDLE_SERVER_READ(HandleServerWindowClose); break;
			case PACKET_WINDOW_CONTENTS:           HANDLE_SERVER_READ(HandleServerWindowContents); break;
			case PACKET_WINDOW_OPEN:               HANDLE_SERVER_READ(HandleServerWindowOpen); break;
			default:
			{
				if (m_ServerState == csEncryptedUnderstood)
				{
					Log("********************** Unknown packet 0x%02x from the server while encrypted; continuing to relay blind only", PacketType);
					AString Data;
					m_ServerBuffer.ResetRead();
					m_ServerBuffer.ReadAll(Data);
					DataLog(Data.data(), Data.size(), "Current data in the server packet queue: %d bytes", Data.size());
					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;
}





bool cConnection::HandleClientAnimation(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEInt, int,  EntityID);
	HANDLE_CLIENT_PACKET_READ(ReadChar,  char, Animation);
	Log("Received a PACKET_ANIMATION from the client:");
	Log("  EntityID: %d", EntityID);
	Log("  Animation: %d", Animation);
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientBlockPlace(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEInt, int,  BlockX);
	HANDLE_CLIENT_PACKET_READ(ReadByte,  Byte, BlockY);
	HANDLE_CLIENT_PACKET_READ(ReadBEInt, int,  BlockZ);
	HANDLE_CLIENT_PACKET_READ(ReadChar,  char, Face);
	AString Desc;
	if (!ParseSlot(m_ClientBuffer, Desc))
	{
		return false;
	}
	HANDLE_CLIENT_PACKET_READ(ReadChar,  char, CursorX);
	HANDLE_CLIENT_PACKET_READ(ReadChar,  char, CursorY);
	HANDLE_CLIENT_PACKET_READ(ReadChar,  char, CursorZ);
	Log("Received a PACKET_BLOCK_PLACE from the client:");
	Log("  Block = {%d, %d, %d}", BlockX, BlockY, BlockZ);
	Log("  Face = %d", Face);
	Log("  Item = %s", Desc.c_str());
	Log("  Cursor = <%d, %d, %d>", CursorX, CursorY, CursorZ);
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientChatMessage(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Message);
	Log("Received a PACKET_CHAT_MESSAGE from the client:");
	Log("  Message = \"%s\"", Message.c_str());
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientClientStatuses(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadChar, char, Statuses);
	Log("Received a PACKET_CLIENT_STATUSES from the CLIENT:");
	Log("  Statuses = %d", Statuses);
	
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientCreativeInventoryAction(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, SlotNum);
	AString Item;
	if (!ParseSlot(m_ClientBuffer, Item))
	{
		return false;
	}
	Log("Received a PACKET_CREATIVE_INVENTORY_ACTION from the client:");
	Log("  SlotNum = %d", SlotNum);
	Log("  Item = %s", Item.c_str());
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientEncryptionKeyResponse(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, EncKeyLength);
	AString EncKey;
	if (!m_ClientBuffer.ReadString(EncKey, EncKeyLength))
	{
		return true;
	}
	HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, EncNonceLength);
	AString EncNonce;
	if (!m_ClientBuffer.ReadString(EncNonce, EncNonceLength))
	{
		return true;
	}
	if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN))
	{
		Log("Client: Too long encryption params");
		return true;
	}
	StartClientEncryption(EncKey, EncNonce);
	return true;
}





bool cConnection::HandleClientEntityAction(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEInt, int,  PlayerID);
	HANDLE_CLIENT_PACKET_READ(ReadByte,  Byte, ActionType);
	Log("Received a PACKET_ENTITY_ACTION from the client:");
	Log("  PlayerID = %d", PlayerID);
	Log("  ActionType = %d", ActionType);
	COPY_TO_SERVER();
	return true;
}





bool 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, but with our port:
	cByteBuffer ToServer(512);
	ToServer.WriteByte           (PACKET_HANDSHAKE);
	ToServer.WriteByte           (ProtocolVersion);
	ToServer.WriteBEUTF16String16(Username);
	ToServer.WriteBEUTF16String16(ServerHost);
	ToServer.WriteBEInt          (m_Server.GetConnectPort());
	SERVERSEND(ToServer);
	return true;
}





bool cConnection::HandleClientKeepAlive(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEInt, int, ID);
	Log("Received a PACKET_KEEPALIVE from the client");
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientLocaleAndView(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Locale);
	HANDLE_CLIENT_PACKET_READ(ReadChar,            char,    ViewDistance);
	HANDLE_CLIENT_PACKET_READ(ReadChar,            char,    ChatFlags);
	HANDLE_CLIENT_PACKET_READ(ReadChar,            char,    Difficulty);
	Log("Received a PACKET_LOCALE_AND_VIEW from the client");
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientPing(void)
{
	Log("Received a PACKET_PING from the client");
	m_ClientBuffer.ResetRead();
	SERVERSEND(m_ClientBuffer);
	return true;
}





bool cConnection::HandleClientPlayerAbilities(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadChar, char, IsInvulnerable);
	HANDLE_CLIENT_PACKET_READ(ReadChar, char, IsFlying);
	HANDLE_CLIENT_PACKET_READ(ReadChar, char, CanFly);
	HANDLE_CLIENT_PACKET_READ(ReadChar, char, IsInstaMine);
	Log("Receives a PACKET_PLAYER_ABILITIES from the client:");
	Log("  Flags = %d, %d, %d, %d", IsInvulnerable, IsFlying, CanFly, IsInstaMine);
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientPlayerLook(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEFloat, float, Yaw);
	HANDLE_CLIENT_PACKET_READ(ReadBEFloat, float, Pitch);
	HANDLE_CLIENT_PACKET_READ(ReadChar,    char,  OnGround);
	Log("Received a PACKET_PLAYER_LOOK from the client");
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientPlayerOnGround(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadChar,    char,  OnGround);
	Log("Received a PACKET_PLAYER_ON_GROUND from the client");
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientPlayerPosition(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, PosX);
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, Stance);
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, PosY);
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, PosZ);
	HANDLE_CLIENT_PACKET_READ(ReadChar,     char,   IsOnGround);
	Log("Received a PACKET_PLAYER_POSITION from the client");

	// TODO: list packet contents
	
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientPlayerPositionLook(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, PosX);
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, Stance);
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, PosY);
	HANDLE_CLIENT_PACKET_READ(ReadBEDouble, double, PosZ);
	HANDLE_CLIENT_PACKET_READ(ReadBEFloat,  float,  Yaw);
	HANDLE_CLIENT_PACKET_READ(ReadBEFloat,  float,  Pitch);
	HANDLE_CLIENT_PACKET_READ(ReadChar,     char,   IsOnGround);
	Log("Received a PACKET_PLAYER_POSITION_LOOK from the client");

	// TODO: list packet contents
	
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientSlotSelect(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, SlotNum);
	Log("Received a PACKET_SLOT_SELECT from the client");
	Log("  SlotNum = %d", SlotNum);
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientUpdateSign(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEInt,           int,   BlockX);
	HANDLE_CLIENT_PACKET_READ(ReadBEShort,         short, BlockY);
	HANDLE_CLIENT_PACKET_READ(ReadBEInt,           int,   BlockZ);
	HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Line1);
	HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Line2);
	HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Line3);
	HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Line4);
	Log("Received a PACKET_UPDATE_SIGN from the client:");
	Log("  Block = {%d, %d, %d}", BlockX, BlockY, BlockZ);
	Log("  Lines = \"%s\", \"%s\", \"%s\", \"%s\"", Line1.c_str(), Line2.c_str(), Line3.c_str(), Line4.c_str());
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientUseEntity(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadBEInt, int,  PlayerID);
	HANDLE_CLIENT_PACKET_READ(ReadBEInt, int,  EntityID);
	HANDLE_CLIENT_PACKET_READ(ReadChar,  char, MouseButton);
	Log("Received a PACKET_USE_ENTITY from the client:");
	Log("  PlayerID = %d", PlayerID);
	Log("  EntityID = %d", EntityID);
	Log("  MouseButton = %d", MouseButton);
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientWindowClick(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadChar,    char,  WindowID);
	HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, SlotNum);
	HANDLE_CLIENT_PACKET_READ(ReadChar,    char,  IsRightClick);
	HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, TransactionID);
	HANDLE_CLIENT_PACKET_READ(ReadChar,    char,  IsShiftClick);
	AString Item;
	if (!ParseSlot(m_ClientBuffer, Item))
	{
		return false;
	}
	Log("Received a PACKET_WINDOW_CLICK from the client");
	Log("  WindowID = %d", WindowID);
	Log("  SlotNum = %d", SlotNum);
	Log("  IsRclk = %d, IsShift = %d", IsRightClick, IsShiftClick);
	Log("  TransactionID = 0x%x", TransactionID);
	Log("  ClickedItem = %s", Item.c_str());
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleClientWindowClose(void)
{
	HANDLE_CLIENT_PACKET_READ(ReadChar, char, WindowID);
	Log("Received a PACKET_WINDOW_CLOSE from the client:");
	Log("  WindowID = %d", WindowID);
	COPY_TO_SERVER();
	return true;
}





bool cConnection::HandleServerBlockAction(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   BlockX);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, BlockY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   BlockZ);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Byte1);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Byte2);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, BlockID);
	Log("Received a PACKET_BLOCK_ACTION from the server:");
	Log("  Pos = {%d, %d, %d}", BlockX, BlockY, BlockZ);
	Log("  Bytes = (%d, %d) == (0x%x, 0x%x)", Byte1, Byte2, Byte1, Byte2);
	Log("  BlockID = %d", BlockID);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerBlockChange(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   BlockX);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  BlockY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   BlockZ);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, BlockType);
	HANDLE_SERVER_PACKET_READ(ReadChar,    char,  BlockMeta);
	Log("Received a PACKET_BLOCK_CHANGE from the server");
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerChangeGameState(void)
{
	HANDLE_SERVER_PACKET_READ(ReadChar, char,  Reason);
	HANDLE_SERVER_PACKET_READ(ReadChar, char,  Data);
	Log("Received a PACKET_CHANGE_GAME_STATE from the server:");
	Log("  Reason = %d", Reason);
	Log("  Data = %d", Data);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerChatMessage(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Message);
	Log("Received a PACKET_CHAT_MESSAGE from the server:");
	Log("  Message = \"%s\"", Message.c_str());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerCollectPickup(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, CollectedID);
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, CollectorID);
	Log("Received a PACKET_COLLECT_PICKUP from the server:");
	Log("  CollectedID = %d", CollectedID);
	Log("  CollectorID = %d", CollectorID);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerCompass(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, SpawnX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, SpawnY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, SpawnZ);
	Log("Received PACKET_COMPASS from the server:");
	Log("  Spawn = {%d, %d, %d}", SpawnX, SpawnY, SpawnZ);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerDestroyEntities(void)
{
	HANDLE_SERVER_PACKET_READ(ReadByte, Byte, NumEntities);
	if (!m_ServerBuffer.SkipRead((int)NumEntities * 4))
	{
		return false;
	}
	Log("Received PACKET_DESTROY_ENTITIES from the server:");
	Log("  NumEntities = %d", NumEntities);
	COPY_TO_CLIENT();
	return true;
}





bool 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 false;
	}
	HANDLE_SERVER_PACKET_READ(ReadBEShort,         short,   NonceLength);
	AString Nonce;
	if (!m_ServerBuffer.ReadString(Nonce, NonceLength))
	{
		return false;
	}
	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);
	return true;
}





bool cConnection::HandleServerEntity(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, EntityID);
	Log("Received a PACKET_ENTITY from the server:");
	Log("  EntityID = %d", EntityID);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityHeadLook(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int,  EntityID);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, HeadYaw);
	Log("Received a PACKET_ENTITY_HEAD_LOOK from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  HeadYaw = %d", HeadYaw);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityMetadata(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, EntityID);
	AString Metadata;
	if (!ParseMetadata(m_ServerBuffer, Metadata))
	{
		return false;
	}
	Log("Received a PACKET_ENTITY_METADATA from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  Metadata length = %d", Metadata.length());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityEquipment(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   EntityID);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, SlotNum);
	AString Item;
	if (!ParseSlot(m_ServerBuffer, Item))
	{
		return false;
	}
	Log("Received a PACKET_ENTITY_EQUIPMENT from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  SlotNum = %d", SlotNum);
	Log("  Item = %s", Item.c_str());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityLook(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int,  EntityID);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, Yaw);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, Pitch);
	Log("Received a PACKET_ENTITY_LOOK from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  Yaw = %d", Yaw);
	Log("  Pitch = %d", Pitch);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityRelativeMove(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int,  EntityID);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, dx);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, dy);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, dz);
	Log("Received a PACKET_ENTITY_RELATIVE_MOVE from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  RelMove = <%d, %d, %d>", dx, dy, dz);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityRelativeMoveLook(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, EntityID);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, dx);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, dy);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, dz);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, Yaw);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, Pitch);
	Log("Received a PACKET_ENTITY_RELATIVE_MOVE_LOOK from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  RelMove = <%d, %d, %d>", dx, dy, dz);
	Log("  Yaw = %d", Yaw);
	Log("  Pitch = %d", Pitch);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityStatus(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int,  EntityID);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, Status);
	Log("Received a PACKET_ENTITY_STATUS from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  Status = %d", Status);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityTeleport(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, EntityID);
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int,  BlockX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int,  BlockY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int,  BlockZ);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, Yaw);
	HANDLE_SERVER_PACKET_READ(ReadByte,  Byte, Pitch);
	Log("Received a PACKET_ENTITY_TELEPORT from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  Pos = {%d, %d, %d}", BlockX, BlockY, BlockZ);
	Log("  Yaw = %d", Yaw);
	Log("  Pitch = %d", Pitch);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerEntityVelocity(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   EntityID);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, VelocityX);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, VelocityY);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, VelocityZ);
	Log("Received a PACKET_SPAWN_MOB from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  Velocity = <%d, %d, %d>", VelocityX, VelocityY, VelocityZ);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerKeepAlive(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, PingID);
	Log("Received a PACKET_KEEP_ALIVE from the server:");
	Log("  ID = %d", PingID);
	COPY_TO_CLIENT()
	return true;
}





bool cConnection::HandleServerEncryptionKeyResponse(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt, int, Lengths);
	if (Lengths != 0)
	{
		Log("Lengths are not zero!");
		return true;
	}
	Log("Server communication is now encrypted");
	m_ServerState = csEncryptedUnderstood;
	return true;
}





bool cConnection::HandleServerKick(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Reason);
	Log("Received PACKET_KICK from the SERVER:");
	Log("  Reason = \"%s\"", Reason.c_str());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerLogin(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     EntityID);
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, LevelType);
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    GameMode);
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    Dimension);
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    Difficulty);
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    Unused);
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    MaxPlayers);
	Log("Received a PACKET_LOGIN from the server:");
	Log("  EntityID = %d",      EntityID);
	Log("  LevelType = \"%s\"", LevelType.c_str());
	Log("  GameMode = %d",      GameMode);
	Log("  Dimension = %d",     Dimension);
	Log("  Difficulty = %d",    Difficulty);
	Log("  Unused = %d",        Unused);
	Log("  MaxPlayers = %d",    MaxPlayers);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerMapChunk(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   ChunkX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   ChunkZ);
	HANDLE_SERVER_PACKET_READ(ReadChar,    char,  IsContiguous);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, PrimaryBitmap);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, AdditionalBitmap);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   CompressedSize);
	AString CompressedData;
	if (!m_ServerBuffer.ReadString(CompressedData, CompressedSize))
	{
		return false;
	}
	Log("Received a PACKET_MAP_CHUNK from the server:");
	Log("  ChunkPos = [%d, %d]", ChunkX, ChunkZ);
	Log("  Compressed size = %d (0x%x)", CompressedSize, CompressedSize);
	
	// TODO: Save the compressed data into a file for later analysis
	
	COPY_TO_CLIENT()
	return true;
}





bool cConnection::HandleServerMapChunkBulk(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, ChunkCount);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   CompressedSize);
	AString CompressedData;
	if (!m_ServerBuffer.ReadString(CompressedData, CompressedSize))
	{
		return false;
	}
	AString Meta;
	if (!m_ServerBuffer.ReadString(Meta, ChunkCount * 12))
	{
		return false;
	}
	Log("Received a PACKET_MAP_CHUNK_BULK from the server:");
	Log("  ChunkCount = %d", ChunkCount);
	Log("  Compressed size = %d (0x%x)", CompressedSize, CompressedSize);
	
	// TODO: Save the compressed data into a file for later analysis
	
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerMultiBlockChange(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   ChunkX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   ChunkZ);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, NumBlocks);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   DataSize);
	AString BlockChangeData;
	if (!m_ServerBuffer.ReadString(BlockChangeData, DataSize))
	{
		return false;
	}
	Log("Received a PACKET_MULTI_BLOCK_CHANGE packet from the server:");
	Log("  Chunk = [%d, %d]", ChunkX, ChunkZ);
	Log("  NumBlocks = %d", NumBlocks);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerNamedSoundEffect(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, SoundName);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     PosX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     PosY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     PosZ);
	HANDLE_SERVER_PACKET_READ(ReadBEFloat,         float,   Volume);
	HANDLE_SERVER_PACKET_READ(ReadByte,            Byte,    Pitch);
	Log("Received a PACKET_NAMED_SOUND_EFFECT from the server:");
	Log("  SoundName = \"%s\"", SoundName.c_str());
	Log("  Pos = (%d, %d, %d) ~ {%d, %d, %d}", PosX, PosY, PosZ, PosX / 8, PosY / 8, PosZ / 8);
	Log("  Volume = %f", Volume);
	Log("  Pitch = %d", Pitch);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerPlayerAbilities(void)
{
	HANDLE_SERVER_PACKET_READ(ReadChar, char, Flags);
	HANDLE_SERVER_PACKET_READ(ReadChar, char, FlyingSpeed);
	HANDLE_SERVER_PACKET_READ(ReadChar, char, WalkingSpeed);
	Log("Received a PACKET_PLAYER_ABILITIES from the server:");
	Log("  Flags = 0x%x", Flags);
	Log("  FlyingSpeed = %d", FlyingSpeed);
	Log("  WalkingSpeed = %d", WalkingSpeed);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerPlayerListItem(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, PlayerName);
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    IsOnline);
	HANDLE_SERVER_PACKET_READ(ReadBEShort,         short,   Ping);
	Log("Received a PACKET_PLAYERLIST_ITEM from the server:");
	Log("  PlayerName = \"%s\"", PlayerName.c_str());
	Log("  Ping = %d", Ping);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerPlayerPositionLook(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEDouble, double, PosX);
	HANDLE_SERVER_PACKET_READ(ReadBEDouble, double, Stance);
	HANDLE_SERVER_PACKET_READ(ReadBEDouble, double, PosY);
	HANDLE_SERVER_PACKET_READ(ReadBEDouble, double, PosZ);
	HANDLE_SERVER_PACKET_READ(ReadBEFloat,  float,  Yaw);
	HANDLE_SERVER_PACKET_READ(ReadBEFloat,  float,  Pitch);
	HANDLE_SERVER_PACKET_READ(ReadChar,     char,   IsOnGround);
	Log("Received a PACKET_PLAYER_POSITION_LOOK from the server");

	// TODO: list packet contents
	
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerSetExperience(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEFloat, float, ExperienceBar);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, Level);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, TotalExperience);
	Log("Received a PACKET_SET_EXPERIENCE from the server:");
	Log("  ExperienceBar = %.05f", ExperienceBar);
	Log("  Level = %d", Level);
	Log("  TotalExperience = %d", TotalExperience);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerSetSlot(void)
{
	HANDLE_SERVER_PACKET_READ(ReadChar,    char,  WindowID);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, SlotNum);
	AString Item;
	if (!ParseSlot(m_ServerBuffer, Item))
	{
		return false;
	}
	Log("Received a PACKET_SET_SLOT from the server:");
	Log("  WindowID = %d", WindowID);
	Log("  SlotNum = %d", SlotNum);
	Log("  Item = %s", Item.c_str());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerSoundEffect(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   EffectID);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosX);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  PosY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosZ);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   Data);
	Log("Received a PACKET_SOUND_EFFECT from the server:");
	Log("  EffectID = %d", EffectID);
	Log("  Pos = {%d, %d, %d}", PosX, PosY, PosZ);
	Log("  Data = %d", Data);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerSpawnMob(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   EntityID);
	HANDLE_SERVER_PACKET_READ(ReadChar,    char,  MobType);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosZ);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Yaw);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Pitch);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  HeadYaw);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, VelocityX);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, VelocityY);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, VelocityZ);
	AString Metadata;
	if (!ParseMetadata(m_ServerBuffer, Metadata))
	{
		return false;
	}
	Log("Received a PACKET_SPAWN_MOB from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  Pos = <%d, %d, %d> ~ {%d, %d, %d}", PosX, PosY, PosZ, PosX / 32, PosY / 32, PosZ / 32);
	Log("  Angles = [%d, %d, %d]", Yaw, Pitch, HeadYaw);
	Log("  Velocity = <%d, %d, %d>", VelocityX, VelocityY, VelocityZ);
	Log("  Metadata size = %d", Metadata.size());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerSpawnObjectVehicle(void)
{
	#ifdef _DEBUG
	// DEBUG:
	// This packet is still troublesome when DataIndicator != 0
	AString Buffer;
	m_ServerBuffer.ResetRead();
	m_ServerBuffer.ReadAll(Buffer);
	m_ServerBuffer.ResetRead();
	m_ServerBuffer.SkipRead(1);
	if (Buffer.size() > 128)
	{
		// Only log up to 128 bytes
		Buffer.erase(128, AString::npos);
	}
	DataLog(Buffer.data(), Buffer.size(), "Buffer while parsing the PACKET_SPAWN_OBJECT_VEHICLE packet (%d bytes):", Buffer.size());
	#endif  // _DEBUG
	
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   EntityID);
	HANDLE_SERVER_PACKET_READ(ReadChar,    char,  ObjType);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosZ);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   DataIndicator);
	AString ExtraData;
	short VelocityX, VelocityY, VelocityZ;
	if (DataIndicator != 0)
	{
		HANDLE_SERVER_PACKET_READ(ReadBEShort, short, SpeedX);
		HANDLE_SERVER_PACKET_READ(ReadBEShort, short, SpeedY);
		HANDLE_SERVER_PACKET_READ(ReadBEShort, short, SpeedZ);
		VelocityX = SpeedX; VelocityY = SpeedY; VelocityZ = SpeedZ;  // Speed vars are local to this scope, but we need them available later
		/*
		// This doesn't seem to work - for a falling block I'm getting no extra data at all
		int ExtraLen = 0;
		switch (ObjType)
		{
			case OBJECT_FALLING_BLOCK: ExtraLen = 4; break;  // int: BlockType | (BlockMeta << 12)
			case OBJECT_ARROW:
			case OBJECT_SNOWBALL:
			case OBJECT_EGG:
			case OBJECT_EYE_OF_ENDER:
			case OBJECT_DRAGON_EGG:
			case OBJECT_FISHING_FLOAT:
			{
				ExtraLen = 4; break;  // int: EntityID of the thrower
			}
			// TODO: Splash potions
		}
		if ((ExtraLen > 0) && !m_ServerBuffer.ReadString(ExtraData, ExtraLen))
		{
			return false;
		}
		*/
	}
	Log("Received a PACKET_SPAWN_OBJECT_VEHICLE from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  ObjType = %d", ObjType);
	Log("  Pos = <%d, %d, %d> ~ {%d, %d, %d}", PosX, PosY, PosZ, PosX / 32, PosY / 32, PosZ / 32);
	Log("  DataIndicator = %d", DataIndicator);
	if (DataIndicator != 0)
	{
		Log("  Velocity = <%d, %d, %d>", VelocityX, VelocityY, VelocityZ);
		DataLog(ExtraData.data(), ExtraData.size(), "  ExtraData size = %d", ExtraData.size());
	}
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerSpawnPainting(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     EntityID);
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, ImageName);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     PosX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     PosY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     PosZ);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,     Direction);
	Log("Received a PACKET_SPAWN_PAINTING from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  ImageName = \"%s\"", ImageName.c_str());
	Log("  Pos = <%d, %d, %d> ~ {%d, %d, %d}", PosX, PosY, PosZ, PosX / 32, PosY / 32, PosZ / 32);
	Log("  Direction = %d", Direction);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerSpawnPickup(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   EntityID);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, ItemType);
	HANDLE_SERVER_PACKET_READ(ReadChar,    char,  ItemCount);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, ItemDamage);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosX);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   PosZ);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Rotation);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Pitch);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Roll);
	Log("Received a PACKET_SPAWN_PICKUP from the server:");
	Log("  EntityID = %d", EntityID);
	Log("  Item = %d:%d * %d", ItemType, ItemDamage, ItemCount);
	Log("  Pos = <%d, %d, %d> ~ {%d, %d, %d}", PosX, PosY, PosZ, PosX / 32, PosY / 32, PosZ / 32);
	Log("  Angles = [%d, %d, %d]", Rotation, Pitch, Roll);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerTimeUpdate(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt64, Int64, Time);
	Log("Received a PACKET_TIME_UPDATE from the server");
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerUpdateHealth(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, Health);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, Food);
	HANDLE_SERVER_PACKET_READ(ReadBEFloat, float, Saturation);
	Log("Received a PACKET_UPDATE_HEALTH from the server");
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerUpdateSign(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,   BlockX);
	HANDLE_SERVER_PACKET_READ(ReadBEShort,         short, BlockY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,           int,   BlockZ);
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Line1);
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Line2);
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Line3);
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Line4);
	Log("Received a PACKET_UPDATE_SIGN from the server:");
	Log("  Block = {%d, %d, %d}", BlockX, BlockY, BlockZ);
	Log("  Lines = \"%s\", \"%s\", \"%s\", \"%s\"", Line1.c_str(), Line2.c_str(), Line3.c_str(), Line4.c_str());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerUpdateTileEntity(void)
{
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   BlockX);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, BlockY);
	HANDLE_SERVER_PACKET_READ(ReadBEInt,   int,   BlockZ);
	HANDLE_SERVER_PACKET_READ(ReadByte,    Byte,  Action);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, DataLength);
	AString Data;
	if ((DataLength > 0) && !m_ServerBuffer.ReadString(Data, DataLength))
	{
		return false;
	}
	Log("Received a PACKET_UPDATE_TILE_ENTITY from the server:");
	Log("  Block = {%d, %d, %d}", BlockX, BlockY, BlockZ);
	Log("  Action = %d", Action);
	DataLog(Data.data(), Data.size(), "  Data (%d bytes)", Data.size());
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerWindowClose(void)
{
	HANDLE_SERVER_PACKET_READ(ReadChar, char, WindowID);
	Log("Received a PACKET_WINDOW_CLOSE from the server:");
	Log("  WindowID = %d", WindowID);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerWindowContents(void)
{
	HANDLE_SERVER_PACKET_READ(ReadChar, char, WindowID);
	HANDLE_SERVER_PACKET_READ(ReadBEShort, short, NumSlots);
	AStringVector Items;
	for (short i = 0; i < NumSlots; i++)
	{
		AString Item;
		if (!ParseSlot(m_ServerBuffer, Item))
		{
			return false;
		}
		Items.push_back(Item);
	}
	Log("Received a PACKET_WINDOW_CONTENTS from the server:");
	Log("  WindowID = %d", WindowID);
	Log("  NumSlots = %d", NumSlots);

	// TODO: list items
	
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::HandleServerWindowOpen(void)
{
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    WindowID);
	HANDLE_SERVER_PACKET_READ(ReadChar,            char,    WindowType);
	HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Title);
	HANDLE_SERVER_PACKET_READ(ReadByte,            Byte,    NumSlots);
	Log("Received a PACKET_WINDOW_OPEN from the server:");
	Log("  WindowID = %d", WindowID);
	Log("  WindowType = %d", WindowType);
	Log("  Title = \"%s\"", Title.c_str());
	Log("  NumSlots = %d", NumSlots);
	COPY_TO_CLIENT();
	return true;
}





bool cConnection::ParseSlot(cByteBuffer & a_Buffer, AString & a_ItemDesc)
{
	short ItemType;
	if (!a_Buffer.ReadBEShort(ItemType))
	{
		return false;
	}
	if (ItemType <= 0)
	{
		a_ItemDesc = "<empty>";
		return true;
	}
	if (!a_Buffer.CanReadBytes(5))
	{
		return false;
	}
	char ItemCount;
	short ItemDamage;
	short MetadataLength;
	a_Buffer.ReadChar(ItemCount);
	a_Buffer.ReadBEShort(ItemDamage);
	a_Buffer.ReadBEShort(MetadataLength);
	Printf(a_ItemDesc, "%d:%d * %d", ItemType, ItemDamage, ItemCount);
	if (MetadataLength <= 0)
	{
		return true;
	}
	AppendPrintf(a_ItemDesc, " (%d bytes of meta)", MetadataLength);
	if (!a_Buffer.SkipRead(MetadataLength))
	{
		return false;
	}
	return true;
}





bool cConnection::ParseMetadata(cByteBuffer & a_Buffer, AString & a_Metadata)
{
	char x;
	if (!a_Buffer.ReadChar(x))
	{
		return false;
	}
	a_Metadata.push_back(x);
	while (x != 0x7f)
	{
		int Index = x & 0x1f;  // Lower 5 bits = index
		int Type  = x >> 5;    // Upper 3 bits = type
		int Length = 0;
		switch (Type)
		{
			case 0: Length = 1; break;  // byte
			case 1: Length = 2; break;  // short
			case 2: Length = 4; break;  // int
			case 3: Length = 4; break;  // float
			case 4:  // string16
			{
				short Len = 0;
				if (!a_Buffer.ReadBEShort(Len))
				{
					return false;
				}
				short NetLen = htons(Len);
				a_Metadata.append((char *)&NetLen, 2);
				Length = Len;
				break;
			}
			case 5: Length = 5;  break;  // short, byte, short
			case 6: Length = 12; break;  // 3 * int
		}  // switch (Type)
		AString data;
		if (!a_Buffer.ReadString(data, Length))
		{
			return false;
		}
		a_Metadata.append(data);
		if (!a_Buffer.ReadChar(x))
		{
			return false;
		}
		a_Metadata.push_back(x);
	}  // while (x != 0x7f)
	return true;
}





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<PKCS1v15>::Encryptor rsaEncryptor(pk);
	RandomPool rng;
	time_t CurTime = time(NULL);
	rng.Put((const byte *)&CurTime, sizeof(CurTime));
	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<PKCS1v15>::Decryptor rsaDecryptor(m_Server.GetPrivateKey());
	time_t CurTime = time(NULL);
	RandomPool rng;
	rng.Put((const byte *)&CurTime, sizeof(CurTime));
	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));
	Log("Client connection is now encrypted");
	m_ClientState = csEncryptedUnderstood;
	
	// Handle all postponed server data
	DecodeServersPackets(NULL, 0);
}