summaryrefslogblamecommitdiffstats
path: root/source/Protocol132.cpp
blob: e291c0c697117db32651fa5fa2e0d4b9defc14a1 (plain) (tree)
1
2
3
4
5
6
7
8
9
10






                                                                                   


                           
                               



                                 






















                                                   











                                                                                                        



                                        
                                        



                                        
                                        






                                        










































































                                                                                   



                                                                                                                        

                             






  











                                                                                                    

                                                                 





















                                                                                                  






































                                                                                                                                
                                





                                                                                          






























                                                               
                                                                               










                                                                        

                              





























































                                                                                                                            












                                                                                          




                                                                                                                            
                                                             


                                                                





  

























                                                                           




                                                                           
                                                                      
                               
 




                                                                          
                                                                           
                                                                                                                                            
                                                              
                                    
 






                         


                                                    
                                               





                         





























                                                                            







































                                                                            

                                                             
                                             







                               

                                                                                                




                                        

                                                   

                           
                                                                                                 












                                                                                                  
                              


























                                                                               





  




















































                                                                 
                                                  


                                  
                                                               

                                                                      
                       
                                                                                                                                       






                 



                                                                                                     


                                                          







                                                                                                                            
                                                                          















                                                                                                       







                                                 













                                                                                                                                









                                                                                              




  

// Protocol132.cpp

// Implements the cProtocol132 class representing the release 1.3.2 protocol (#39)

#include "Globals.h"
#include "Protocol132.h"
#include "cRoot.h"
#include "cServer.h"
#include "cClientHandle.h"
#include "CryptoPP/randpool.h"
#include "cItem.h"
#include "ChunkDataSerializer.h"
#include "cPlayer.h"
#include "cMonster.h"





#define HANDLE_PACKET_READ(Proc, Type, Var) \
	Type Var; \
	{ \
		if (!m_ReceivedData.Proc(Var)) \
		{ \
			return PARSE_INCOMPLETE; \
		} \
	}




typedef unsigned char Byte;





using namespace CryptoPP;





const int MAX_ENC_LEN = 512;  // Maximum size of the encrypted message; should be 128, but who knows...





enum
{
	PACKET_KEEP_ALIVE       = 0x00,
	PACKET_LOGIN            = 0x01,
	PACKET_COMPASS          = 0x06,
	PACKET_PLAYER_SPAWN     = 0x14,
	PACKET_SPAWN_MOB        = 0x18,
	PACKET_DESTROY_ENTITIES = 0x1d,
	PACKET_CHUNK_DATA       = 0x33,
	PACKET_BLOCK_CHANGE     = 0x35,
	PACKET_BLOCK_ACTION     = 0x36,
} ;





// Converts a raw 160-bit SHA1 digest into a Java Hex representation
// According to http://wiki.vg/wiki/index.php?title=Protocol_Encryption&oldid=2802
static void DigestToJava(byte a_Digest[20], AString & a_Out)
{
	bool IsNegative = (a_Digest[0] >= 0x80);
	if (IsNegative)
	{
		// Two's complement:
		bool carry = true;  // Add one to the whole number
		for (int i = 19; i >= 0; i--)
		{
			a_Digest[i] = ~a_Digest[i];
			if (carry)
			{
				carry = (a_Digest[i] == 0xff);
				a_Digest[i]++;
			}
		}
	}
	a_Out.clear();
	a_Out.reserve(40);
	for (int i = 0; i < 20; i++)
	{
		AppendPrintf(a_Out, "%02x", a_Digest[i]);
	}
	while ((a_Out.length() > 0) && (a_Out[0] == '0'))
	{
		a_Out.erase(0, 1);
	}
	if (IsNegative)
	{
		a_Out.insert(0, "-");
	}
}





/*
// Self-test the hash formatting for known values:
// sha1(Notch) : 4ed1f46bbe04bc756bcb17c0c7ce3e4632f06a48
// sha1(jeb_)  : -7c9d5b0044c130109a5d7b5fb5c317c02b4e28c1
// sha1(simon) : 88e16a1019277b15d58faf0541e11910eb756f6

class Test
{
public:
	Test(void)
	{
		AString DigestNotch, DigestJeb, DigestSimon;
		byte Digest[20];
		CryptoPP::SHA1 Checksum;
		Checksum.Update((const byte *)"Notch", 5);
		Checksum.Final(Digest);
		DigestToJava(Digest, DigestNotch);
		Checksum.Restart();
		Checksum.Update((const byte *)"jeb_", 4);
		Checksum.Final(Digest);
		DigestToJava(Digest, DigestJeb);
		Checksum.Restart();
		Checksum.Update((const byte *)"simon", 5);
		Checksum.Final(Digest);
		DigestToJava(Digest, DigestSimon);
		printf("Notch: \"%s\"", DigestNotch.c_str());
		printf("jeb_: \"%s\"",  DigestJeb.c_str());
		printf("simon: \"%s\"", DigestSimon.c_str());
	}
} test;
*/





///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// cProtocol132:

cProtocol132::cProtocol132(cClientHandle * a_Client) :
	super(a_Client),
	m_IsEncrypted(false)
{
}





cProtocol132::~cProtocol132()
{
	if (!m_DataToSend.empty())
	{
		LOGD("There are %d unsent bytes while deleting cProtocol132", m_DataToSend.size());
	}
}





void cProtocol132::DataReceived(const char * a_Data, int a_Size)
{
	if (m_IsEncrypted)
	{
		byte Decrypted[512];
		while (a_Size > 0)
		{
			int NumBytes = (a_Size > sizeof(Decrypted)) ? sizeof(Decrypted) : a_Size;
			m_Decryptor.ProcessData(Decrypted, (byte *)a_Data, NumBytes);
			super::DataReceived((const char *)Decrypted, NumBytes);
			a_Size -= NumBytes;
			a_Data += NumBytes;
		}
	}
	else
	{
		super::DataReceived(a_Data, a_Size);
	}
}





void cProtocol132::SendBlockAction(int a_BlockX, int a_BlockY, int a_BlockZ, char a_Byte1, char a_Byte2, BLOCKTYPE a_BlockType)
{
	cCSLock Lock(m_CSPacket);
	WriteByte (PACKET_BLOCK_ACTION);
	WriteInt  (a_BlockX);
	WriteShort((short)a_BlockY);
	WriteInt  (a_BlockZ);
	WriteByte (a_Byte1);
	WriteByte (a_Byte2);
	WriteShort(a_BlockType);
	Flush();
}





void cProtocol132::SendBlockChange(int a_BlockX, int a_BlockY, int a_BlockZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta)
{
	cCSLock Lock(m_CSPacket);
	WriteByte (PACKET_BLOCK_CHANGE);
	WriteInt  (a_BlockX);
	WriteByte ((unsigned char)a_BlockY);
	WriteInt  (a_BlockZ);
	WriteShort(a_BlockType);
	WriteByte (a_BlockMeta);
	Flush();
}





void cProtocol132::SendChunkData(int a_ChunkX, int a_ChunkZ, cChunkDataSerializer & a_Serializer)
{
	cCSLock Lock(m_CSPacket);
	
	// Pre-chunk not used in 1.3.2. Finally.

	// Send the chunk data:
	AString Serialized = a_Serializer.Serialize(cChunkDataSerializer::RELEASE_1_3_2);
	WriteByte(PACKET_CHUNK_DATA);
	WriteInt (a_ChunkX);
	WriteInt (a_ChunkZ);
	SendData(Serialized.data(), Serialized.size());
	Flush();
}





void cProtocol132::SendDestroyEntity(const cEntity & a_Entity)
{
	cCSLock Lock(m_CSPacket);
	WriteByte(PACKET_DESTROY_ENTITIES);
	WriteByte(1);  // entity count
	WriteInt (a_Entity.GetUniqueID());
	Flush();
}





void cProtocol132::SendKeepAlive(int a_PingID)
{
	cCSLock Lock(m_CSPacket);
	WriteByte(PACKET_KEEP_ALIVE);
	WriteInt (a_PingID);
	Flush();
}





void cProtocol132::SendLogin(const cPlayer & a_Player, const cWorld & a_World)
{
	cCSLock Lock(m_CSPacket);
	WriteByte  (PACKET_LOGIN);
	WriteInt   (a_Player.GetUniqueID());  // EntityID of the player
	WriteString("default");  // Level type
	WriteByte  ((int)a_Player.GetGameMode());
	WriteByte  (0);  // TODO: Dimension (Nether / Overworld / End)
	WriteByte  (2);  // TODO: Difficulty
	WriteByte  (0);  // Unused, used to be world height
	WriteByte  (8);  // Client list width or something
	Flush();
	
	SendCompass(a_World);
}





void cProtocol132::SendPlayerSpawn(const cPlayer & a_Player)
{
	const cItem & HeldItem = a_Player.GetEquippedItem();
	cCSLock Lock(m_CSPacket);
	WriteByte	 (PACKET_PLAYER_SPAWN);
	WriteInt   (a_Player.GetUniqueID());
	WriteString(a_Player.GetName());
	WriteInt   ((int)(a_Player.GetPosX() * 32));
	WriteInt   ((int)(a_Player.GetPosY() * 32));
	WriteInt   ((int)(a_Player.GetPosZ() * 32));
	WriteByte  ((char)((a_Player.GetRot().x / 360.f) * 256));
	WriteByte  ((char)((a_Player.GetRot().y / 360.f) * 256));
	WriteShort (HeldItem.IsEmpty() ? 0 : HeldItem.m_ItemType);
	// Player metadata: just use a default metadata value, since the client doesn't like starting without any metadata:
	WriteByte  (0);  // Index 0, byte (flags)
	WriteByte  (0);  // Flags, empty
	WriteByte  (0x7f);  // End of metadata
	Flush();
}





void cProtocol132::SendSpawnMob(const cMonster & a_Mob)
{
	cCSLock Lock(m_CSPacket);
	WriteByte   (PACKET_SPAWN_MOB);
	WriteInt    (a_Mob.GetUniqueID());
	WriteByte   (a_Mob.GetMobType());
	WriteVectorI((Vector3i)(a_Mob.GetPosition() * 32));
	WriteByte   (0);  // yaw
	WriteByte   (0);  // pitch
	WriteByte   (0);  // head yaw
	WriteShort  (0);  // Velocity Z
	WriteShort  (0);  // Velocity X
	WriteShort  (0);  // Velocity Y
	AString MetaData = GetEntityMetaData(a_Mob);
	SendData (MetaData.data(), MetaData.size());
	Flush();
}





void cProtocol132::SendUnloadChunk(int a_ChunkX, int a_ChunkZ)
{
	// Not used in 1.3.2
	// Does it unload chunks on its own?
}





AString cProtocol132::GetAuthServerID(void)
{
	// http://wiki.vg/wiki/index.php?title=Session&oldid=2615
	// Server uses SHA1 to mix ServerID, Client secret and server public key together
	// The mixing is done in StartEncryption, the result is in m_AuthServerID
	
	return m_AuthServerID;
}





int cProtocol132::ParsePacket(unsigned char a_PacketType)
{
	switch (a_PacketType)
	{
		default:   return super::ParsePacket(a_PacketType);  // off-load previously known packets into cProtocol125
		case 0xcc: return ParseLocaleViewDistance();
		case 0xcd: return ParseClientStatuses();
		case 0xfc: return ParseEncryptionKeyResponse();
	}
}





int cProtocol132::ParseBlockPlace(void)
{
	HANDLE_PACKET_READ(ReadBEInt, int,  PosX);
	HANDLE_PACKET_READ(ReadByte,  Byte, PosY);
	HANDLE_PACKET_READ(ReadBEInt, int,  PosZ);
	HANDLE_PACKET_READ(ReadChar,  char, Direction);

	cItem HeldItem;
	int res = ParseItem(HeldItem);
	if (res < 0)
	{
		return res;
	}
	
	HANDLE_PACKET_READ(ReadChar, char, CursorX);
	HANDLE_PACKET_READ(ReadChar, char, CursorY);
	HANDLE_PACKET_READ(ReadChar, char, CursorZ);

	m_Client->HandleBlockPlace(PosX, PosY, PosZ, Direction, HeldItem);
	return PARSE_OK;
}





int cProtocol132::ParseHandshake(void)
{
	HANDLE_PACKET_READ(ReadByte,            Byte,    ProtocolVersion);
	HANDLE_PACKET_READ(ReadBEUTF16String16, AString, Username);
	HANDLE_PACKET_READ(ReadBEUTF16String16, AString, ServerHost);
	HANDLE_PACKET_READ(ReadBEInt,           int,     ServerPort);
	m_Username = Username;

	if (!m_Client->HandleHandshake( m_Username ))
	{
		return PARSE_OK; // Player is not allowed into the server
	}

	// Send a 0xFD Encryption Key Request http://wiki.vg/Protocol#0xFD
	CryptoPP::StringSink sink(m_ServerPublicKey);  // GCC won't allow inline instantiation in the following line, damned temporary refs
	cRoot::Get()->GetServer()->GetPublicKey().Save(sink);
	SendEncryptionKeyRequest();

	return PARSE_OK;
}





int cProtocol132::ParseClientStatuses(void)
{
	HANDLE_PACKET_READ(ReadByte, byte, Status);
	m_Client->HandleLogin(39, m_Username);
	return PARSE_OK;
}





int cProtocol132::ParseEncryptionKeyResponse(void)
{
	HANDLE_PACKET_READ(ReadBEShort, short, EncKeyLength);
	AString EncKey;
	if (!m_ReceivedData.ReadString(EncKey, EncKeyLength))
	{
		return PARSE_INCOMPLETE;
	}
	HANDLE_PACKET_READ(ReadBEShort, short, EncNonceLength);
	AString EncNonce;
	if (!m_ReceivedData.ReadString(EncNonce, EncNonceLength))
	{
		return PARSE_INCOMPLETE;
	}
	if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN))
	{
		LOGD("Too long encryption");
		m_Client->Kick("Hacked client");
		return PARSE_OK;
	}
	
	HandleEncryptionKeyResponse(EncKey, EncNonce);
	return PARSE_OK;
}





int cProtocol132::ParseLocaleViewDistance(void)
{
	HANDLE_PACKET_READ(ReadBEUTF16String16, AString, Locale);
	HANDLE_PACKET_READ(ReadChar,            char,    ViewDistance);
	HANDLE_PACKET_READ(ReadChar,            char,    ChatFlags);
	HANDLE_PACKET_READ(ReadChar,            char,    ClientDifficulty);
	// TODO: m_Client->HandleLocale(Locale);
	// TODO: m_Client->HandleViewDistance(ViewDistance);
	// TODO: m_Client->HandleChatFlags(ChatFlags);
	// Ignoring client difficulty
	return PARSE_OK;
}





int cProtocol132::ParseLogin(void)
{
	// Login packet not used in 1.3.2
	return PARSE_ERROR;
}





int cProtocol132::ParsePlayerAbilities(void)
{
	HANDLE_PACKET_READ(ReadBool, bool, Flags);
	HANDLE_PACKET_READ(ReadChar, char, FlyingSpeed);
	HANDLE_PACKET_READ(ReadChar, char, WalkingSpeed);
	// TODO: m_Client->HandlePlayerAbilities(...);
	return PARSE_OK;
}





void cProtocol132::SendData(const char * a_Data, int a_Size)
{
	m_DataToSend.append(a_Data, a_Size);
}





void cProtocol132::Flush(void)
{
	ASSERT(m_CSPacket.IsLockedByCurrentThread());  // Did all packets lock the CS properly?
	
	if (m_DataToSend.empty())
	{
		LOGD("Flushing empty");
		return;
	}
	const char * a_Data = m_DataToSend.data();
	int a_Size = m_DataToSend.size();
	if (m_IsEncrypted)
	{
		byte Encrypted[8192];  // Larger buffer, we may be sending lots of data (chunks)
		while (a_Size > 0)
		{
			int NumBytes = (a_Size > sizeof(Encrypted)) ? sizeof(Encrypted) : a_Size;
			m_Encryptor.ProcessData(Encrypted, (byte *)a_Data, NumBytes);
			super::SendData((const char *)Encrypted, NumBytes);
			a_Size -= NumBytes;
			a_Data += NumBytes;
		}
	}
	else
	{
		super::SendData(a_Data, a_Size);
	}
	m_DataToSend.clear();
}





void cProtocol132::WriteItem(const cItem & a_Item)
{
	short ItemType = a_Item.m_ItemType;
	ASSERT(ItemType >= -1);  // Check validity of packets in debug runtime
	if (ItemType <= 0)
	{
		// Fix, to make sure no invalid values are sent.
		ItemType = -1;
	}
	
	WriteShort(ItemType);
	if (a_Item.IsEmpty())
	{
		return;
	}
	
	WriteByte (a_Item.m_ItemCount);
	WriteShort(a_Item.m_ItemDamage);
	
	// TODO: Implement enchantments
	WriteShort(-1);
}





int cProtocol132::ParseItem(cItem & a_Item)
{
	HANDLE_PACKET_READ(ReadBEShort, short, ItemType);

	if (ItemType <= -1)
	{
		a_Item.Empty();
		return PARSE_OK;
	}
	a_Item.m_ItemType = ItemType;

	HANDLE_PACKET_READ(ReadChar,    char,  ItemCount);
	HANDLE_PACKET_READ(ReadBEShort, short, ItemDamage);
	a_Item.m_ItemCount  = ItemCount;
	// a_Item.m_ItemDamage = ItemDamage;
	if (ItemCount <= 0)
	{
		a_Item.Empty();
	}

	HANDLE_PACKET_READ(ReadBEShort, short, EnchantNumBytes);
	if (EnchantNumBytes <= 0)
	{
		return PARSE_OK;
	}
		
	// TODO: Enchantment not implemented yet!
	if (!m_ReceivedData.SkipRead(EnchantNumBytes))
	{
		return PARSE_INCOMPLETE;
	}
	
	return PARSE_OK;
}





void cProtocol132::SendCompass(const cWorld & a_World)
{
	cCSLock Lock(m_CSPacket);
	WriteByte(PACKET_COMPASS);
	WriteInt((int)(a_World.GetSpawnX()));
	WriteInt((int)(a_World.GetSpawnY()));
	WriteInt((int)(a_World.GetSpawnZ()));
	Flush();
}





void cProtocol132::SendEncryptionKeyRequest(void)
{
	cCSLock Lock(m_CSPacket);
	WriteByte((char)0xfd);
	WriteString(cRoot::Get()->GetServer()->GetServerID());
	WriteShort((short)m_ServerPublicKey.size());
	SendData(m_ServerPublicKey.data(), m_ServerPublicKey.size());
	WriteShort(4);
	WriteInt((int)(intptr_t)this);  // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :)
	Flush();
}





void cProtocol132::HandleEncryptionKeyResponse(const AString & a_EncKey, const AString & a_EncNonce)
{
	// Decrypt EncNonce using privkey
	RSAES<PKCS1v15>::Decryptor rsaDecryptor(cRoot::Get()->GetServer()->GetPrivateKey());
	time_t CurTime = time(NULL);
	CryptoPP::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))
	{
		LOGD("Bad nonce length");
		m_Client->Kick("Hacked client");
		return;
	}
	if (ntohl(*((int *)DecryptedNonce)) != (unsigned)(uintptr_t)this)
	{
		LOGD("Bad nonce value");
		m_Client->Kick("Hacked client");
		return;
	}
	
	// Decrypt the symmetric encryption key using privkey:
	byte DecryptedKey[MAX_ENC_LEN];
	res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncKey.data(), a_EncKey.size(), DecryptedKey);
	if (!res.isValidCoding || (res.messageLength != 16))
	{
		LOGD("Bad key length");
		m_Client->Kick("Hacked client");
		return;
	}
	
	{
		// Send encryption key response:
		cCSLock Lock(m_CSPacket);
		WriteByte((char)0xfc);
		WriteShort(0);
		WriteShort(0);
		Flush();
	}
	
	StartEncryption(DecryptedKey);
	return;
}





void cProtocol132::StartEncryption(const byte * a_Key)
{
	m_Encryptor.SetKey(a_Key, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(a_Key, 16))(Name::FeedbackSize(), 1));
	m_Decryptor.SetKey(a_Key, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(a_Key, 16))(Name::FeedbackSize(), 1));
	m_IsEncrypted = true;
	
	// Prepare the m_AuthServerID:
	CryptoPP::SHA1 Checksum;
	AString ServerID = cRoot::Get()->GetServer()->GetServerID();
	Checksum.Update((const byte *)ServerID.c_str(), ServerID.length());
	Checksum.Update(a_Key, 16);
	Checksum.Update((const byte *)m_ServerPublicKey.c_str(), m_ServerPublicKey.length());
	byte Digest[20];
	Checksum.Final(Digest);
	DigestToJava(Digest, m_AuthServerID);
}