Minecraft-Console-Client/MinecraftClient/Protocol/Handlers/Protocol18.cs
ORelio 877e50579d Add connection timeout using server keepalives
Vanilla client will consider that connection has been lost
when no server keepalive was received during the last 30 seconds.
This commit implements a similar mechanism in MCC. See #802
2019-09-15 17:01:53 +02:00

1008 lines
49 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
using System.Security.Cryptography;
using MinecraftClient.Mapping;
using MinecraftClient.Mapping.BlockPalettes;
using MinecraftClient.Protocol.Handlers.Forge;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Implementation for Minecraft 1.7.X+ Protocols
/// </summary>
/// <remarks>
/// Typical update steps for implementing protocol changes for a new Minecraft version:
/// - Perform a diff between latest supported version in MCC and new stable version to support on https://wiki.vg/Protocol
/// - If there are any changes in packets implemented by MCC, add MCXXXVersion field below and implement new packet layouts
/// - If packet IDs were changed, also update getPacketIncomingType() and getPacketOutgoingID() inside Protocol18PacketTypes.cs
/// </remarks>
class Protocol18Handler : IMinecraftCom
{
internal const int MC18Version = 47;
internal const int MC19Version = 107;
internal const int MC191Version = 108;
internal const int MC110Version = 210;
internal const int MC1112Version = 316;
internal const int MC112Version = 335;
internal const int MC1121Version = 338;
internal const int MC1122Version = 340;
internal const int MC113Version = 393;
internal const int MC114Version = 477;
internal const int MC1142Version = 485;
private int compression_treshold = 0;
private bool autocomplete_received = false;
private int autocomplete_transaction_id = 0;
private readonly List<string> autocomplete_result = new List<string>();
private bool login_phase = true;
private int protocolversion;
private int currentDimension;
Protocol18Forge pForge;
Protocol18Terrain pTerrain;
IMinecraftComHandler handler;
SocketWrapper socketWrapper;
DataTypes dataTypes;
Thread netRead;
public Protocol18Handler(TcpClient Client, int protocolVersion, IMinecraftComHandler handler, ForgeInfo forgeInfo)
{
ConsoleIO.SetAutoCompleteEngine(this);
ChatParser.InitTranslations();
this.socketWrapper = new SocketWrapper(Client);
this.dataTypes = new DataTypes(protocolVersion);
this.protocolversion = protocolVersion;
this.handler = handler;
this.pForge = new Protocol18Forge(forgeInfo, protocolVersion, dataTypes, this, handler);
this.pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler);
if (handler.GetTerrainEnabled() && protocolversion > MC1142Version)
{
ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version.");
handler.SetTerrainEnabled(false);
}
if (handler.GetInventoryEnabled() && protocolversion > MC114Version)
{
ConsoleIO.WriteLineFormatted("§8Inventories are currently not handled for that MC version.");
handler.SetInventoryEnabled(false);
}
if (protocolversion >= MC113Version)
{
if (protocolVersion > MC1142Version && handler.GetTerrainEnabled())
throw new NotImplementedException("Please update block types handling for this Minecraft version. See Material.cs");
if (protocolVersion >= MC114Version)
Block.Palette = new Palette114();
else Block.Palette = new Palette113();
}
else Block.Palette = new Palette112();
}
/// <summary>
/// Separate thread. Network reading loop.
/// </summary>
private void Updater()
{
try
{
do
{
Thread.Sleep(100);
}
while (Update());
}
catch (System.IO.IOException) { }
catch (SocketException) { }
catch (ObjectDisposedException) { }
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "");
}
/// <summary>
/// Read data from the network. Should be called on a separate thread.
/// </summary>
/// <returns>FALSE if an error occured, TRUE otherwise.</returns>
private bool Update()
{
handler.OnUpdate();
if (!socketWrapper.IsConnected())
return false;
try
{
while (socketWrapper.HasDataAvailable())
{
int packetID = 0;
List<byte> packetData = new List<byte>();
ReadNextPacket(ref packetID, packetData);
HandlePacket(packetID, new List<byte>(packetData));
}
}
catch (System.IO.IOException) { return false; }
catch (SocketException) { return false; }
catch (NullReferenceException) { return false; }
return true;
}
/// <summary>
/// Read the next packet from the network
/// </summary>
/// <param name="packetID">will contain packet ID</param>
/// <param name="packetData">will contain raw packet Data</param>
internal void ReadNextPacket(ref int packetID, List<byte> packetData)
{
packetData.Clear();
int size = dataTypes.ReadNextVarIntRAW(socketWrapper); //Packet size
packetData.AddRange(socketWrapper.ReadDataRAW(size)); //Packet contents
//Handle packet decompression
if (protocolversion >= MC18Version
&& compression_treshold > 0)
{
int sizeUncompressed = dataTypes.ReadNextVarInt(packetData);
if (sizeUncompressed != 0) // != 0 means compressed, let's decompress
{
byte[] toDecompress = packetData.ToArray();
byte[] uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed);
packetData.Clear();
packetData.AddRange(uncompressed);
}
}
packetID = dataTypes.ReadNextVarInt(packetData); //Packet ID
}
/// <summary>
/// Handle the given packet
/// </summary>
/// <param name="packetID">Packet ID</param>
/// <param name="packetData">Packet contents</param>
/// <returns>TRUE if the packet was processed, FALSE if ignored or unknown</returns>
internal bool HandlePacket(int packetID, List<byte> packetData)
{
try
{
if (login_phase)
{
switch (packetID) //Packet IDs are different while logging in
{
case 0x03:
if (protocolversion >= MC18Version)
compression_treshold = dataTypes.ReadNextVarInt(packetData);
break;
default:
return false; //Ignored packet
}
}
// Regular in-game packets
switch (Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion))
{
case PacketIncomingType.KeepAlive:
SendPacket(PacketOutgoingType.KeepAlive, packetData);
handler.OnServerKeepAlive();
break;
case PacketIncomingType.JoinGame:
handler.OnGameJoined();
dataTypes.ReadNextInt(packetData);
dataTypes.ReadNextByte(packetData);
if (protocolversion >= MC191Version)
this.currentDimension = dataTypes.ReadNextInt(packetData);
else
this.currentDimension = (sbyte)dataTypes.ReadNextByte(packetData);
if (protocolversion < MC114Version)
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
dataTypes.ReadNextByte(packetData);
dataTypes.ReadNextString(packetData);
if (protocolversion >= MC114Version)
dataTypes.ReadNextVarInt(packetData); // View distance - 1.14 and above
if (protocolversion >= MC18Version)
dataTypes.ReadNextBool(packetData); // Reduced debug info - 1.8 and above
break;
case PacketIncomingType.ChatMessage:
string message = dataTypes.ReadNextString(packetData);
try
{
//Hide system messages or xp bar messages?
byte messageType = dataTypes.ReadNextByte(packetData);
if ((messageType == 1 && !Settings.DisplaySystemMessages)
|| (messageType == 2 && !Settings.DisplayXPBarMessages))
break;
}
catch (ArgumentOutOfRangeException) { /* No message type */ }
handler.OnTextReceived(message, true);
break;
case PacketIncomingType.Respawn:
this.currentDimension = dataTypes.ReadNextInt(packetData);
if (protocolversion < MC114Version)
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
dataTypes.ReadNextByte(packetData);
dataTypes.ReadNextString(packetData);
handler.OnRespawn();
break;
case PacketIncomingType.PlayerPositionAndLook:
if (handler.GetTerrainEnabled())
{
double x = dataTypes.ReadNextDouble(packetData);
double y = dataTypes.ReadNextDouble(packetData);
double z = dataTypes.ReadNextDouble(packetData);
float yaw = dataTypes.ReadNextFloat(packetData);
float pitch = dataTypes.ReadNextFloat(packetData);
byte locMask = dataTypes.ReadNextByte(packetData);
if (protocolversion >= MC18Version)
{
Location location = handler.GetCurrentLocation();
location.X = (locMask & 1 << 0) != 0 ? location.X + x : x;
location.Y = (locMask & 1 << 1) != 0 ? location.Y + y : y;
location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z;
handler.UpdateLocation(location, yaw, pitch);
}
else handler.UpdateLocation(new Location(x, y, z), yaw, pitch);
}
if (protocolversion >= MC19Version)
{
int teleportID = dataTypes.ReadNextVarInt(packetData);
// Teleport confirm packet
SendPacket(PacketOutgoingType.TeleportConfirm, dataTypes.GetVarInt(teleportID));
}
break;
case PacketIncomingType.ChunkData:
if (handler.GetTerrainEnabled())
{
int chunkX = dataTypes.ReadNextInt(packetData);
int chunkZ = dataTypes.ReadNextInt(packetData);
bool chunksContinuous = dataTypes.ReadNextBool(packetData);
ushort chunkMask = protocolversion >= MC19Version
? (ushort)dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextUShort(packetData);
if (protocolversion < MC18Version)
{
ushort addBitmap = dataTypes.ReadNextUShort(packetData);
int compressedDataSize = dataTypes.ReadNextInt(packetData);
byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData);
byte[] decompressed = ZlibUtils.Decompress(compressed);
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new List<byte>(decompressed));
}
else
{
if (protocolversion >= MC114Version)
dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above
int dataSize = dataTypes.ReadNextVarInt(packetData);
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData);
}
}
break;
case PacketIncomingType.MultiBlockChange:
if (handler.GetTerrainEnabled())
{
int chunkX = dataTypes.ReadNextInt(packetData);
int chunkZ = dataTypes.ReadNextInt(packetData);
int recordCount = protocolversion < MC18Version
? (int)dataTypes.ReadNextShort(packetData)
: dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < recordCount; i++)
{
byte locationXZ;
ushort blockIdMeta;
int blockY;
if (protocolversion < MC18Version)
{
blockIdMeta = dataTypes.ReadNextUShort(packetData);
blockY = (ushort)dataTypes.ReadNextByte(packetData);
locationXZ = dataTypes.ReadNextByte(packetData);
}
else
{
locationXZ = dataTypes.ReadNextByte(packetData);
blockY = (ushort)dataTypes.ReadNextByte(packetData);
blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData);
}
int blockX = locationXZ >> 4;
int blockZ = locationXZ & 0x0F;
Block block = new Block(blockIdMeta);
handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block);
}
}
break;
case PacketIncomingType.BlockChange:
if (handler.GetTerrainEnabled())
{
if (protocolversion < MC18Version)
{
int blockX = dataTypes.ReadNextInt(packetData);
int blockY = dataTypes.ReadNextByte(packetData);
int blockZ = dataTypes.ReadNextInt(packetData);
short blockId = (short)dataTypes.ReadNextVarInt(packetData);
byte blockMeta = dataTypes.ReadNextByte(packetData);
handler.GetWorld().SetBlock(new Location(blockX, blockY, blockZ), new Block(blockId, blockMeta));
}
else handler.GetWorld().SetBlock(dataTypes.ReadNextLocation(packetData), new Block((ushort)dataTypes.ReadNextVarInt(packetData)));
}
break;
case PacketIncomingType.MapChunkBulk:
if (protocolversion < MC19Version && handler.GetTerrainEnabled())
{
int chunkCount;
bool hasSkyLight;
List<byte> chunkData = packetData;
//Read global fields
if (protocolversion < MC18Version)
{
chunkCount = dataTypes.ReadNextShort(packetData);
int compressedDataSize = dataTypes.ReadNextInt(packetData);
hasSkyLight = dataTypes.ReadNextBool(packetData);
byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData);
byte[] decompressed = ZlibUtils.Decompress(compressed);
chunkData = new List<byte>(decompressed);
}
else
{
hasSkyLight = dataTypes.ReadNextBool(packetData);
chunkCount = dataTypes.ReadNextVarInt(packetData);
}
//Read chunk records
int[] chunkXs = new int[chunkCount];
int[] chunkZs = new int[chunkCount];
ushort[] chunkMasks = new ushort[chunkCount];
ushort[] addBitmaps = new ushort[chunkCount];
for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++)
{
chunkXs[chunkColumnNo] = dataTypes.ReadNextInt(packetData);
chunkZs[chunkColumnNo] = dataTypes.ReadNextInt(packetData);
chunkMasks[chunkColumnNo] = dataTypes.ReadNextUShort(packetData);
addBitmaps[chunkColumnNo] = protocolversion < MC18Version
? dataTypes.ReadNextUShort(packetData)
: (ushort)0;
}
//Process chunk records
for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++)
pTerrain.ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], addBitmaps[chunkColumnNo], hasSkyLight, true, currentDimension, chunkData);
}
break;
case PacketIncomingType.UnloadChunk:
if (protocolversion >= MC19Version && handler.GetTerrainEnabled())
{
int chunkX = dataTypes.ReadNextInt(packetData);
int chunkZ = dataTypes.ReadNextInt(packetData);
handler.GetWorld()[chunkX, chunkZ] = null;
}
break;
case PacketIncomingType.PlayerListUpdate:
if (protocolversion >= MC18Version)
{
int action = dataTypes.ReadNextVarInt(packetData);
int numActions = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < numActions; i++)
{
Guid uuid = dataTypes.ReadNextUUID(packetData);
switch (action)
{
case 0x00: //Player Join
string name = dataTypes.ReadNextString(packetData);
int propNum = dataTypes.ReadNextVarInt(packetData);
for (int p = 0; p < propNum; p++)
{
string key = dataTypes.ReadNextString(packetData);
string val = dataTypes.ReadNextString(packetData);
if (dataTypes.ReadNextBool(packetData))
dataTypes.ReadNextString(packetData);
}
dataTypes.ReadNextVarInt(packetData);
dataTypes.ReadNextVarInt(packetData);
if (dataTypes.ReadNextBool(packetData))
dataTypes.ReadNextString(packetData);
handler.OnPlayerJoin(uuid, name);
break;
case 0x01: //Update gamemode
case 0x02: //Update latency
dataTypes.ReadNextVarInt(packetData);
break;
case 0x03: //Update display name
if (dataTypes.ReadNextBool(packetData))
dataTypes.ReadNextString(packetData);
break;
case 0x04: //Player Leave
handler.OnPlayerLeave(uuid);
break;
default:
//Unknown player list item type
break;
}
}
}
else //MC 1.7.X does not provide UUID in tab-list updates
{
string name = dataTypes.ReadNextString(packetData);
bool online = dataTypes.ReadNextBool(packetData);
short ping = dataTypes.ReadNextShort(packetData);
Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray());
if (online)
handler.OnPlayerJoin(FakeUUID, name);
else handler.OnPlayerLeave(FakeUUID);
}
break;
case PacketIncomingType.TabCompleteResult:
if (protocolversion >= MC113Version)
{
autocomplete_transaction_id = dataTypes.ReadNextVarInt(packetData);
dataTypes.ReadNextVarInt(packetData); // Start of text to replace
dataTypes.ReadNextVarInt(packetData); // Length of text to replace
}
int autocomplete_count = dataTypes.ReadNextVarInt(packetData);
autocomplete_result.Clear();
for (int i = 0; i < autocomplete_count; i++)
{
autocomplete_result.Add(dataTypes.ReadNextString(packetData));
if (protocolversion >= MC113Version)
{
// Skip optional tooltip for each tab-complete result
if (dataTypes.ReadNextBool(packetData))
dataTypes.ReadNextString(packetData);
}
}
autocomplete_received = true;
break;
case PacketIncomingType.PluginMessage:
String channel = dataTypes.ReadNextString(packetData);
// Length is unneeded as the whole remaining packetData is the entire payload of the packet.
if (protocolversion < MC18Version)
pForge.ReadNextVarShort(packetData);
handler.OnPluginChannelMessage(channel, packetData.ToArray());
return pForge.HandlePluginMessage(channel, packetData, ref currentDimension);
case PacketIncomingType.KickPacket:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
return false;
case PacketIncomingType.NetworkCompressionTreshold:
if (protocolversion >= MC18Version && protocolversion < MC19Version)
compression_treshold = dataTypes.ReadNextVarInt(packetData);
break;
case PacketIncomingType.OpenWindow:
if (handler.GetInventoryEnabled())
{
byte windowID = dataTypes.ReadNextByte(packetData);
string type = dataTypes.ReadNextString(packetData).Replace("minecraft:", "").ToUpper();
InventoryType inventoryType = (InventoryType)Enum.Parse(typeof(InventoryType), type);
string title = dataTypes.ReadNextString(packetData);
byte slots = dataTypes.ReadNextByte(packetData);
Inventory inventory = new Inventory(windowID, inventoryType, title, slots);
handler.OnInventoryOpen(inventory);
}
break;
case PacketIncomingType.CloseWindow:
if (handler.GetInventoryEnabled())
{
byte windowID = dataTypes.ReadNextByte(packetData);
handler.OnInventoryClose(windowID);
}
break;
case PacketIncomingType.WindowItems:
if (handler.GetInventoryEnabled())
{
byte id = dataTypes.ReadNextByte(packetData);
short elements = dataTypes.ReadNextShort(packetData);
for (int i = 0; i < elements; i++)
{
short itemID = dataTypes.ReadNextShort(packetData);
if (itemID == -1) continue;
byte itemCount = dataTypes.ReadNextByte(packetData);
short itemDamage = dataTypes.ReadNextShort(packetData);
Item item = new Item(itemID, itemCount, itemDamage, 0);
//TODO: Add to the dictionary for the inventory its in using the id
if (packetData.ToArray().Count() > 0)
{
dataTypes.ReadNextNbt(packetData);
}
}
}
break;
case PacketIncomingType.ResourcePackSend:
string url = dataTypes.ReadNextString(packetData);
string hash = dataTypes.ReadNextString(packetData);
//Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory
byte[] responseHeader = new byte[0];
if (protocolversion < MC110Version) //MC 1.10 does not include resource pack hash in responses
responseHeader = dataTypes.ConcatBytes(dataTypes.GetVarInt(hash.Length), Encoding.UTF8.GetBytes(hash));
SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(3))); //Accepted pack
SendPacket(PacketOutgoingType.ResourcePackStatus, dataTypes.ConcatBytes(responseHeader, dataTypes.GetVarInt(0))); //Successfully loaded
break;
default:
return false; //Ignored packet
}
return true; //Packet processed
}
catch (Exception innerException)
{
if (innerException is SocketException || innerException.InnerException is SocketException)
throw; //Connection lost rather than invalid data
throw new System.IO.InvalidDataException(
String.Format("Failed to process incoming packet of type {0}. (PacketID: {1}, Protocol: {2}, LoginPhase: {3}, InnerException: {4}).",
Protocol18PacketTypes.GetPacketIncomingType(packetID, protocolversion),
packetID,
protocolversion,
login_phase,
innerException.GetType()),
innerException);
}
}
/// <summary>
/// Start the updating thread. Should be called after login success.
/// </summary>
private void StartUpdating()
{
netRead = new Thread(new ThreadStart(Updater));
netRead.Name = "ProtocolPacketHandler";
netRead.Start();
}
/// <summary>
/// Disconnect from the server, cancel network reading.
/// </summary>
public void Dispose()
{
try
{
if (netRead != null)
{
netRead.Abort();
socketWrapper.Disconnect();
}
}
catch { }
}
/// <summary>
/// Send a packet to the server. Packet ID, compression, and encryption will be handled automatically.
/// </summary>
/// <param name="packet">packet type</param>
/// <param name="packetData">packet Data</param>
private void SendPacket(PacketOutgoingType packet, IEnumerable<byte> packetData)
{
SendPacket(Protocol18PacketTypes.GetPacketOutgoingID(packet, protocolversion), packetData);
}
/// <summary>
/// Send a packet to the server. Compression and encryption will be handled automatically.
/// </summary>
/// <param name="packetID">packet ID</param>
/// <param name="packetData">packet Data</param>
private void SendPacket(int packetID, IEnumerable<byte> packetData)
{
//The inner packet
byte[] the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(packetID), packetData.ToArray());
if (compression_treshold > 0) //Compression enabled?
{
if (the_packet.Length >= compression_treshold) //Packet long enough for compressing?
{
byte[] compressed_packet = ZlibUtils.Compress(the_packet);
the_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), compressed_packet);
}
else
{
byte[] uncompressed_length = dataTypes.GetVarInt(0); //Not compressed (short packet)
the_packet = dataTypes.ConcatBytes(uncompressed_length, the_packet);
}
}
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(dataTypes.GetVarInt(the_packet.Length), the_packet));
}
/// <summary>
/// Do the Minecraft login.
/// </summary>
/// <returns>True if login successful</returns>
public bool Login()
{
byte[] protocol_version = dataTypes.GetVarInt(protocolversion);
string server_address = pForge.GetServerAddress(handler.GetServerHost());
byte[] server_port = BitConverter.GetBytes((ushort)handler.GetServerPort()); Array.Reverse(server_port);
byte[] next_state = dataTypes.GetVarInt(2);
byte[] handshake_packet = dataTypes.ConcatBytes(protocol_version, dataTypes.GetString(server_address), server_port, next_state);
SendPacket(0x00, handshake_packet);
byte[] login_packet = dataTypes.GetString(handler.GetUsername());
SendPacket(0x00, login_packet);
int packetID = -1;
List<byte> packetData = new List<byte>();
while (true)
{
ReadNextPacket(ref packetID, packetData);
if (packetID == 0x00) //Login rejected
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
return false;
}
else if (packetID == 0x01) //Encryption request
{
string serverID = dataTypes.ReadNextString(packetData);
byte[] Serverkey = dataTypes.ReadNextByteArray(packetData);
byte[] token = dataTypes.ReadNextByteArray(packetData);
return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey);
}
else if (packetID == 0x02) //Login successful
{
ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
login_phase = false;
if (!pForge.CompleteForgeHandshake())
return false;
StartUpdating();
return true; //No need to check session or start encryption
}
else HandlePacket(packetID, packetData);
}
}
/// <summary>
/// Start network encryption. Automatically called by Login() if the server requests encryption.
/// </summary>
/// <returns>True if encryption was successful</returns>
private bool StartEncryption(string uuid, string sessionID, byte[] token, string serverIDhash, byte[] serverKey)
{
System.Security.Cryptography.RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverKey);
byte[] secretKey = CryptoHandler.GenerateAESPrivateKey();
if (Settings.DebugMessages)
ConsoleIO.WriteLineFormatted("§8Crypto keys & hash generated.");
if (serverIDhash != "-")
{
Console.WriteLine("Checking Session...");
if (!ProtocolHandler.SessionCheck(uuid, sessionID, CryptoHandler.getServerHash(serverIDhash, serverKey, secretKey)))
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Failed to check session.");
return false;
}
}
//Encrypt the data
byte[] key_enc = dataTypes.GetArray(RSAService.Encrypt(secretKey, false));
byte[] token_enc = dataTypes.GetArray(RSAService.Encrypt(token, false));
//Encryption Response packet
SendPacket(0x01, dataTypes.ConcatBytes(key_enc, token_enc));
//Start client-side encryption
socketWrapper.SwitchToEncrypted(secretKey);
//Process the next packet
int packetID = -1;
List<byte> packetData = new List<byte>();
while (true)
{
ReadNextPacket(ref packetID, packetData);
if (packetID == 0x00) //Login rejected
{
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
return false;
}
else if (packetID == 0x02) //Login successful
{
login_phase = false;
if (!pForge.CompleteForgeHandshake())
return false;
StartUpdating();
return true;
}
else HandlePacket(packetID, packetData);
}
}
/// <summary>
/// Get max length for chat messages
/// </summary>
/// <returns>Max length, in characters</returns>
public int GetMaxChatMessageLength()
{
return protocolversion > MC110Version
? 256
: 100;
}
/// <summary>
/// Send a chat message to the server
/// </summary>
/// <param name="message">Message</param>
/// <returns>True if properly sent</returns>
public bool SendChatMessage(string message)
{
if (String.IsNullOrEmpty(message))
return true;
try
{
byte[] message_packet = dataTypes.GetString(message);
SendPacket(PacketOutgoingType.ChatMessage, message_packet);
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
}
/// <summary>
/// Send a respawn packet to the server
/// </summary>
/// <returns>True if properly sent</returns>
public bool SendRespawnPacket()
{
try
{
SendPacket(PacketOutgoingType.ClientStatus, new byte[] { 0 });
return true;
}
catch (SocketException) { return false; }
}
/// <summary>
/// Tell the server what client is being used to connect to the server
/// </summary>
/// <param name="brandInfo">Client string describing the client</param>
/// <returns>True if brand info was successfully sent</returns>
public bool SendBrandInfo(string brandInfo)
{
if (String.IsNullOrEmpty(brandInfo))
return false;
// Plugin channels were significantly changed between Minecraft 1.12 and 1.13
// https://wiki.vg/index.php?title=Pre-release_protocol&oldid=14132#Plugin_Channels
if (protocolversion >= MC113Version)
{
return SendPluginChannelPacket("minecraft:brand", dataTypes.GetString(brandInfo));
}
else
{
return SendPluginChannelPacket("MC|Brand", dataTypes.GetString(brandInfo));
}
}
/// <summary>
/// Inform the server of the client's Minecraft settings
/// </summary>
/// <param name="language">Client language eg en_US</param>
/// <param name="viewDistance">View distance, in chunks</param>
/// <param name="difficulty">Game difficulty (client-side...)</param>
/// <param name="chatMode">Chat mode (allows muting yourself)</param>
/// <param name="chatColors">Show chat colors</param>
/// <param name="skinParts">Show skin layers</param>
/// <param name="mainHand">1.9+ main hand</param>
/// <returns>True if client settings were successfully sent</returns>
public bool SendClientSettings(string language, byte viewDistance, byte difficulty, byte chatMode, bool chatColors, byte skinParts, byte mainHand)
{
try
{
List<byte> fields = new List<byte>();
fields.AddRange(dataTypes.GetString(language));
fields.Add(viewDistance);
fields.AddRange(protocolversion >= MC19Version
? dataTypes.GetVarInt(chatMode)
: new byte[] { chatMode });
fields.Add(chatColors ? (byte)1 : (byte)0);
if (protocolversion < MC18Version)
{
fields.Add(difficulty);
fields.Add((byte)(skinParts & 0x1)); //show cape
}
else fields.Add(skinParts);
if (protocolversion >= MC19Version)
fields.AddRange(dataTypes.GetVarInt(mainHand));
SendPacket(PacketOutgoingType.ClientSettings, fields);
}
catch (SocketException) { }
return false;
}
/// <summary>
/// Send a location update to the server
/// </summary>
/// <param name="location">The new location of the player</param>
/// <param name="onGround">True if the player is on the ground</param>
/// <param name="yaw">Optional new yaw for updating player look</param>
/// <param name="pitch">Optional new pitch for updating player look</param>
/// <returns>True if the location update was successfully sent</returns>
public bool SendLocationUpdate(Location location, bool onGround, float? yaw = null, float? pitch = null)
{
if (handler.GetTerrainEnabled())
{
byte[] yawpitch = new byte[0];
PacketOutgoingType packetType = PacketOutgoingType.PlayerPosition;
if (yaw.HasValue && pitch.HasValue)
{
yawpitch = dataTypes.ConcatBytes(dataTypes.GetFloat(yaw.Value), dataTypes.GetFloat(pitch.Value));
packetType = PacketOutgoingType.PlayerPositionAndLook;
}
try
{
SendPacket(packetType, dataTypes.ConcatBytes(
dataTypes.GetDouble(location.X),
dataTypes.GetDouble(location.Y),
protocolversion < MC18Version
? dataTypes.GetDouble(location.Y + 1.62)
: new byte[0],
dataTypes.GetDouble(location.Z),
yawpitch,
new byte[] { onGround ? (byte)1 : (byte)0 }));
return true;
}
catch (SocketException) { return false; }
}
else return false;
}
/// <summary>
/// Send a plugin channel packet (0x17) to the server, compression and encryption will be handled automatically
/// </summary>
/// <param name="channel">Channel to send packet on</param>
/// <param name="data">packet Data</param>
public bool SendPluginChannelPacket(string channel, byte[] data)
{
try
{
// In 1.7, length needs to be included.
// In 1.8, it must not be.
if (protocolversion < MC18Version)
{
byte[] length = BitConverter.GetBytes((short)data.Length);
Array.Reverse(length);
SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), length, data));
}
else
{
SendPacket(PacketOutgoingType.PluginMessage, dataTypes.ConcatBytes(dataTypes.GetString(channel), data));
}
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
}
/// <summary>
/// Disconnect from the server
/// </summary>
public void Disconnect()
{
socketWrapper.Disconnect();
}
/// <summary>
/// Autocomplete text while typing username or command
/// </summary>
/// <param name="BehindCursor">Text behind cursor</param>
/// <returns>Completed text</returns>
IEnumerable<string> IAutoComplete.AutoComplete(string BehindCursor)
{
if (String.IsNullOrEmpty(BehindCursor))
return new string[] { };
byte[] transaction_id = dataTypes.GetVarInt(autocomplete_transaction_id);
byte[] assume_command = new byte[] { 0x00 };
byte[] has_position = new byte[] { 0x00 };
byte[] tabcomplete_packet = new byte[] { };
if (protocolversion >= MC18Version)
{
if (protocolversion >= MC113Version)
{
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, transaction_id);
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor));
}
else
{
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, dataTypes.GetString(BehindCursor));
if (protocolversion >= MC19Version)
{
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, assume_command);
}
tabcomplete_packet = dataTypes.ConcatBytes(tabcomplete_packet, has_position);
}
}
else
{
tabcomplete_packet = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor));
}
autocomplete_received = false;
autocomplete_result.Clear();
autocomplete_result.Add(BehindCursor);
SendPacket(PacketOutgoingType.TabComplete, tabcomplete_packet);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms)
while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }
if (autocomplete_result.Count > 0)
ConsoleIO.WriteLineFormatted("§8" + String.Join(" ", autocomplete_result), false);
return autocomplete_result;
}
/// <summary>
/// Ping a Minecraft server to get information about the server
/// </summary>
/// <returns>True if ping was successful</returns>
public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo forgeInfo)
{
string version = "";
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
tcp.ReceiveBufferSize = 1024 * 1024;
SocketWrapper socketWrapper = new SocketWrapper(tcp);
DataTypes dataTypes = new DataTypes(MC18Version);
byte[] packet_id = dataTypes.GetVarInt(0);
byte[] protocol_version = dataTypes.GetVarInt(-1);
byte[] server_port = BitConverter.GetBytes((ushort)port); Array.Reverse(server_port);
byte[] next_state = dataTypes.GetVarInt(1);
byte[] packet = dataTypes.ConcatBytes(packet_id, protocol_version, dataTypes.GetString(host), server_port, next_state);
byte[] tosend = dataTypes.ConcatBytes(dataTypes.GetVarInt(packet.Length), packet);
socketWrapper.SendDataRAW(tosend);
byte[] status_request = dataTypes.GetVarInt(0);
byte[] request_packet = dataTypes.ConcatBytes(dataTypes.GetVarInt(status_request.Length), status_request);
socketWrapper.SendDataRAW(request_packet);
int packetLength = dataTypes.ReadNextVarIntRAW(socketWrapper);
if (packetLength > 0) //Read Response length
{
List<byte> packetData = new List<byte>(socketWrapper.ReadDataRAW(packetLength));
if (dataTypes.ReadNextVarInt(packetData) == 0x00) //Read Packet ID
{
string result = dataTypes.ReadNextString(packetData); //Get the Json data
if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}"))
{
Json.JSONData jsonData = Json.ParseJson(result);
if (jsonData.Type == Json.JSONData.DataType.Object && jsonData.Properties.ContainsKey("version"))
{
Json.JSONData versionData = jsonData.Properties["version"];
//Retrieve display name of the Minecraft version
if (versionData.Properties.ContainsKey("name"))
version = versionData.Properties["name"].StringValue;
//Retrieve protocol version number for handling this server
if (versionData.Properties.ContainsKey("protocol"))
protocolversion = dataTypes.Atoi(versionData.Properties["protocol"].StringValue);
// Check for forge on the server.
Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo);
ConsoleIO.WriteLineFormatted("§8Server version : " + version + " (protocol v" + protocolversion + (forgeInfo != null ? ", with Forge)." : ")."));
return true;
}
}
}
}
return false;
}
}
}