2014-09-04 13:58:49 +02:00
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 ;
2015-07-30 16:47:55 +02:00
using System.Security.Cryptography ;
2015-10-23 16:54:36 -07:00
using MinecraftClient.Protocol.Handlers.Forge ;
2015-11-27 17:16:33 +01:00
using MinecraftClient.Mapping ;
2014-09-04 13:58:49 +02:00
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
2016-06-09 21:10:45 +02:00
/// Implementation for Minecraft 1.7.X, 1.8.X, 1.9.X, 1.10.X Protocols
2014-09-04 13:58:49 +02:00
/// </summary>
class Protocol18Handler : IMinecraftCom
{
2015-07-30 16:47:55 +02:00
private const int MC18Version = 47 ;
2016-03-05 19:12:43 +01:00
private const int MC19Version = 107 ;
2016-06-10 16:59:53 -07:00
private const int MC191Version = 108 ;
2016-06-09 21:10:45 +02:00
private const int MC110Version = 210 ;
2016-11-19 16:06:08 +01:00
private const int MC111Version = 315 ;
2017-06-10 09:05:06 -07:00
private const int MC17w13aVersion = 318 ;
private const int MC112pre5Version = 332 ;
2017-08-01 10:34:16 -07:00
private const int MC17w31aVersion = 336 ;
2018-09-25 07:38:28 -04:00
private const int MC17w45aVersion = 343 ;
private const int MC17w46aVersion = 345 ;
private const int MC17w47aVersion = 346 ;
private const int MC18w01aVersion = 352 ;
private const int MC18w06aVersion = 357 ;
private const int MC113pre4Version = 386 ;
private const int MC113pre7Version = 389 ;
private const int MC113Version = 393 ;
2015-10-24 13:31:43 -07:00
2014-09-04 13:58:49 +02:00
private int compression_treshold = 0 ;
private bool autocomplete_received = false ;
2018-09-25 07:38:28 -04:00
private int autocomplete_transaction_id = 0 ;
2016-05-14 11:51:02 +02:00
private readonly List < string > autocomplete_result = new List < string > ( ) ;
2014-09-04 13:58:49 +02:00
private bool login_phase = true ;
private bool encrypted = false ;
private int protocolversion ;
2015-07-30 16:47:55 +02:00
2015-10-23 16:54:36 -07:00
// Server forge info -- may be null.
private ForgeInfo forgeInfo ;
2015-10-24 13:31:43 -07:00
private FMLHandshakeClientState fmlHandshakeState = FMLHandshakeClientState . START ;
2015-10-23 16:54:36 -07:00
2015-07-30 16:47:55 +02:00
IMinecraftComHandler handler ;
Thread netRead ;
IAesStream s ;
2014-09-04 13:58:49 +02:00
TcpClient c ;
2016-06-10 16:59:53 -07:00
int currentDimension ;
2015-10-23 16:54:36 -07:00
public Protocol18Handler ( TcpClient Client , int ProtocolVersion , IMinecraftComHandler Handler , ForgeInfo ForgeInfo )
2014-09-04 13:58:49 +02:00
{
ConsoleIO . SetAutoCompleteEngine ( this ) ;
ChatParser . InitTranslations ( ) ;
this . c = Client ;
this . protocolversion = ProtocolVersion ;
this . handler = Handler ;
2015-10-23 16:54:36 -07:00
this . forgeInfo = ForgeInfo ;
2014-09-04 13:58:49 +02:00
}
private Protocol18Handler ( TcpClient Client )
{
this . c = Client ;
}
/// <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>
2016-08-21 15:44:15 +02:00
/// <returns>FALSE if an error occured, TRUE otherwise.</returns>
2014-09-04 13:58:49 +02:00
private bool Update ( )
{
handler . OnUpdate ( ) ;
if ( c . Client = = null | | ! c . Connected ) { return false ; }
try
{
while ( c . Client . Available > 0 )
{
int packetID = 0 ;
2015-11-30 15:30:49 +01:00
List < byte > packetData = new List < byte > ( ) ;
readNextPacket ( ref packetID , packetData ) ;
handlePacket ( packetID , new List < byte > ( packetData ) ) ;
2014-09-04 13:58:49 +02:00
}
}
catch ( SocketException ) { return false ; }
2015-10-20 22:54:58 +02:00
catch ( NullReferenceException ) { return false ; }
2014-09-04 13:58:49 +02:00
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>
2015-11-30 15:30:49 +01:00
private void readNextPacket ( ref int packetID , List < byte > packetData )
2014-09-04 13:58:49 +02:00
{
2016-02-26 17:57:05 -08:00
packetData . Clear ( ) ;
2014-09-04 13:58:49 +02:00
int size = readNextVarIntRAW ( ) ; //Packet size
2015-11-30 15:30:49 +01:00
packetData . AddRange ( readDataRAW ( size ) ) ; //Packet contents
2014-09-04 13:58:49 +02:00
2015-07-30 16:47:55 +02:00
//Handle packet decompression
if ( protocolversion > = MC18Version
& & compression_treshold > 0 )
2014-09-04 13:58:49 +02:00
{
2015-11-30 15:30:49 +01:00
int sizeUncompressed = 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 ) ;
}
2014-09-04 13:58:49 +02:00
}
2015-11-30 15:30:49 +01:00
packetID = readNextVarInt ( packetData ) ; //Packet ID
2014-09-04 13:58:49 +02:00
}
2016-03-05 19:12:43 +01:00
/// <summary>
/// Abstract incoming packet numbering
/// </summary>
private enum PacketIncomingType
{
KeepAlive ,
JoinGame ,
ChatMessage ,
2016-06-10 16:59:53 -07:00
Respawn ,
2016-03-05 19:12:43 +01:00
PlayerPositionAndLook ,
ChunkData ,
MultiBlockChange ,
BlockChange ,
MapChunkBulk ,
UnloadChunk ,
PlayerListUpdate ,
TabCompleteResult ,
PluginMessage ,
KickPacket ,
NetworkCompressionTreshold ,
ResourcePackSend ,
UnknownPacket
}
/// <summary>
2017-06-10 09:05:06 -07:00
/// Get abstract numbering of the specified packet ID
2016-03-05 19:12:43 +01:00
/// </summary>
/// <param name="packetID">Packet ID</param>
/// <param name="protocol">Protocol version</param>
/// <returns>Abstract numbering</returns>
private PacketIncomingType getPacketIncomingType ( int packetID , int protocol )
{
if ( protocol < MC19Version )
{
switch ( packetID )
{
case 0x00 : return PacketIncomingType . KeepAlive ;
case 0x01 : return PacketIncomingType . JoinGame ;
case 0x02 : return PacketIncomingType . ChatMessage ;
2016-06-10 16:59:53 -07:00
case 0x07 : return PacketIncomingType . Respawn ;
2016-03-05 19:12:43 +01:00
case 0x08 : return PacketIncomingType . PlayerPositionAndLook ;
case 0x21 : return PacketIncomingType . ChunkData ;
case 0x22 : return PacketIncomingType . MultiBlockChange ;
case 0x23 : return PacketIncomingType . BlockChange ;
case 0x26 : return PacketIncomingType . MapChunkBulk ;
//UnloadChunk does not exists prior to 1.9
case 0x38 : return PacketIncomingType . PlayerListUpdate ;
case 0x3A : return PacketIncomingType . TabCompleteResult ;
case 0x3F : return PacketIncomingType . PluginMessage ;
case 0x40 : return PacketIncomingType . KickPacket ;
case 0x46 : return PacketIncomingType . NetworkCompressionTreshold ;
case 0x48 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
2017-06-10 09:05:06 -07:00
else if ( protocol < MC17w13aVersion )
2016-03-05 19:12:43 +01:00
{
switch ( packetID )
{
case 0x1F : return PacketIncomingType . KeepAlive ;
case 0x23 : return PacketIncomingType . JoinGame ;
case 0x0F : return PacketIncomingType . ChatMessage ;
2016-06-10 16:59:53 -07:00
case 0x33 : return PacketIncomingType . Respawn ;
2016-03-05 19:12:43 +01:00
case 0x2E : return PacketIncomingType . PlayerPositionAndLook ;
case 0x20 : return PacketIncomingType . ChunkData ;
case 0x10 : return PacketIncomingType . MultiBlockChange ;
case 0x0B : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1D : return PacketIncomingType . UnloadChunk ;
case 0x2D : return PacketIncomingType . PlayerListUpdate ;
case 0x0E : return PacketIncomingType . TabCompleteResult ;
case 0x18 : return PacketIncomingType . PluginMessage ;
case 0x1A : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x32 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
2017-06-10 09:05:06 -07:00
else if ( protocolversion < MC112pre5Version )
{
switch ( packetID )
{
case 0x20 : return PacketIncomingType . KeepAlive ;
case 0x24 : return PacketIncomingType . JoinGame ;
case 0x10 : return PacketIncomingType . ChatMessage ;
case 0x35 : return PacketIncomingType . Respawn ;
case 0x2F : return PacketIncomingType . PlayerPositionAndLook ;
case 0x21 : return PacketIncomingType . ChunkData ;
case 0x11 : return PacketIncomingType . MultiBlockChange ;
case 0x0C : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1E : return PacketIncomingType . UnloadChunk ;
case 0x2E : return PacketIncomingType . PlayerListUpdate ;
case 0x0F : return PacketIncomingType . TabCompleteResult ;
case 0x19 : return PacketIncomingType . PluginMessage ;
case 0x1B : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x34 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
2017-08-01 10:34:16 -07:00
else if ( protocol < MC17w31aVersion )
2017-06-10 09:05:06 -07:00
{
switch ( packetID )
{
case 0x1F : return PacketIncomingType . KeepAlive ;
case 0x23 : return PacketIncomingType . JoinGame ;
case 0x0F : return PacketIncomingType . ChatMessage ;
case 0x34 : return PacketIncomingType . Respawn ;
case 0x2E : return PacketIncomingType . PlayerPositionAndLook ;
case 0x20 : return PacketIncomingType . ChunkData ;
case 0x10 : return PacketIncomingType . MultiBlockChange ;
case 0x0B : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1D : return PacketIncomingType . UnloadChunk ;
case 0x2D : return PacketIncomingType . PlayerListUpdate ;
case 0x0E : return PacketIncomingType . TabCompleteResult ;
case 0x18 : return PacketIncomingType . PluginMessage ;
case 0x1A : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x33 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
2018-09-25 07:38:28 -04:00
else if ( protocol < MC17w45aVersion )
2017-08-01 10:34:16 -07:00
{
switch ( packetID )
{
case 0x1F : return PacketIncomingType . KeepAlive ;
case 0x23 : return PacketIncomingType . JoinGame ;
case 0x0F : return PacketIncomingType . ChatMessage ;
case 0x35 : return PacketIncomingType . Respawn ;
case 0x2F : return PacketIncomingType . PlayerPositionAndLook ;
case 0x20 : return PacketIncomingType . ChunkData ;
case 0x10 : return PacketIncomingType . MultiBlockChange ;
case 0x0B : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1D : return PacketIncomingType . UnloadChunk ;
case 0x2E : return PacketIncomingType . PlayerListUpdate ;
case 0x0E : return PacketIncomingType . TabCompleteResult ;
case 0x18 : return PacketIncomingType . PluginMessage ;
case 0x1A : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x34 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
2018-09-25 07:38:28 -04:00
else if ( protocol < MC17w46aVersion )
{
switch ( packetID )
{
case 0x1F : return PacketIncomingType . KeepAlive ;
case 0x23 : return PacketIncomingType . JoinGame ;
case 0x0E : return PacketIncomingType . ChatMessage ;
case 0x35 : return PacketIncomingType . Respawn ;
case 0x2F : return PacketIncomingType . PlayerPositionAndLook ;
case 0x21 : return PacketIncomingType . ChunkData ;
case 0x0F : return PacketIncomingType . MultiBlockChange ;
case 0x0B : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1D : return PacketIncomingType . UnloadChunk ;
case 0x2E : return PacketIncomingType . PlayerListUpdate ;
//TabCompleteResult accidentely removed
case 0x18 : return PacketIncomingType . PluginMessage ;
case 0x1A : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x34 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
else if ( protocol < MC18w01aVersion )
{
switch ( packetID )
{
case 0x20 : return PacketIncomingType . KeepAlive ;
case 0x24 : return PacketIncomingType . JoinGame ;
case 0x0E : return PacketIncomingType . ChatMessage ;
case 0x36 : return PacketIncomingType . Respawn ;
case 0x30 : return PacketIncomingType . PlayerPositionAndLook ;
case 0x21 : return PacketIncomingType . ChunkData ;
case 0x0F : return PacketIncomingType . MultiBlockChange ;
case 0x0B : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1E : return PacketIncomingType . UnloadChunk ;
case 0x2F : return PacketIncomingType . PlayerListUpdate ;
case 0x10 : return PacketIncomingType . TabCompleteResult ;
case 0x19 : return PacketIncomingType . PluginMessage ;
case 0x1B : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x35 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
else if ( protocol < MC113pre7Version )
{
switch ( packetID )
{
case 0x20 : return PacketIncomingType . KeepAlive ;
case 0x24 : return PacketIncomingType . JoinGame ;
case 0x0E : return PacketIncomingType . ChatMessage ;
case 0x37 : return PacketIncomingType . Respawn ;
case 0x31 : return PacketIncomingType . PlayerPositionAndLook ;
case 0x21 : return PacketIncomingType . ChunkData ;
case 0x0F : return PacketIncomingType . MultiBlockChange ;
case 0x0B : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1E : return PacketIncomingType . UnloadChunk ;
case 0x2F : return PacketIncomingType . PlayerListUpdate ;
case 0x10 : return PacketIncomingType . TabCompleteResult ;
case 0x19 : return PacketIncomingType . PluginMessage ;
case 0x1B : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x36 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
else
{
switch ( packetID )
{
case 0x21 : return PacketIncomingType . KeepAlive ;
case 0x25 : return PacketIncomingType . JoinGame ;
case 0x0E : return PacketIncomingType . ChatMessage ;
case 0x38 : return PacketIncomingType . Respawn ;
case 0x32 : return PacketIncomingType . PlayerPositionAndLook ;
case 0x22 : return PacketIncomingType . ChunkData ;
case 0x0F : return PacketIncomingType . MultiBlockChange ;
case 0x0B : return PacketIncomingType . BlockChange ;
//MapChunkBulk removed in 1.9
case 0x1F : return PacketIncomingType . UnloadChunk ;
case 0x30 : return PacketIncomingType . PlayerListUpdate ;
case 0x10 : return PacketIncomingType . TabCompleteResult ;
case 0x19 : return PacketIncomingType . PluginMessage ;
case 0x1B : return PacketIncomingType . KickPacket ;
//NetworkCompressionTreshold removed in 1.9
case 0x37 : return PacketIncomingType . ResourcePackSend ;
default : return PacketIncomingType . UnknownPacket ;
}
}
2017-06-10 09:05:06 -07:00
}
/// <summary>
/// Abstract outgoing packet numbering
/// </summary>
private enum PacketOutgoingType
{
KeepAlive ,
ResourcePackStatus ,
ChatMessage ,
ClientStatus ,
ClientSettings ,
PluginMessage ,
TabComplete ,
PlayerPosition ,
2017-09-08 15:08:45 -07:00
PlayerPositionAndLook ,
TeleportConfirm
2017-06-10 09:05:06 -07:00
}
/// <summary>
/// Get packet ID of the specified outgoing packet
/// </summary>
/// <param name="packet">Abstract packet numbering</param>
/// <param name="protocol">Protocol version</param>
/// <returns>Packet ID</returns>
private int getPacketOutgoingID ( PacketOutgoingType packet , int protocol )
{
if ( protocol < MC19Version )
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x00 ;
case PacketOutgoingType . ResourcePackStatus : return 0x19 ;
case PacketOutgoingType . ChatMessage : return 0x01 ;
case PacketOutgoingType . ClientStatus : return 0x16 ;
case PacketOutgoingType . ClientSettings : return 0x15 ;
case PacketOutgoingType . PluginMessage : return 0x17 ;
case PacketOutgoingType . TabComplete : return 0x14 ;
case PacketOutgoingType . PlayerPosition : return 0x04 ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x06 ;
2017-09-08 15:08:45 -07:00
case PacketOutgoingType . TeleportConfirm : throw new InvalidOperationException ( "Teleport confirm is not supported in protocol " + protocol ) ;
2017-06-10 09:05:06 -07:00
}
}
else if ( protocol < MC17w13aVersion )
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0B ;
case PacketOutgoingType . ResourcePackStatus : return 0x16 ;
case PacketOutgoingType . ChatMessage : return 0x02 ;
case PacketOutgoingType . ClientStatus : return 0x03 ;
case PacketOutgoingType . ClientSettings : return 0x04 ;
case PacketOutgoingType . PluginMessage : return 0x09 ;
case PacketOutgoingType . TabComplete : return 0x01 ;
case PacketOutgoingType . PlayerPosition : return 0x0C ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x0D ;
2017-09-08 15:08:45 -07:00
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
2017-06-10 09:05:06 -07:00
}
}
else if ( protocolversion < MC112pre5Version )
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0C ;
2017-10-19 10:28:15 -07:00
case PacketOutgoingType . ResourcePackStatus : return 0x18 ;
2017-06-10 09:05:06 -07:00
case PacketOutgoingType . ChatMessage : return 0x03 ;
case PacketOutgoingType . ClientStatus : return 0x04 ;
case PacketOutgoingType . ClientSettings : return 0x05 ;
case PacketOutgoingType . PluginMessage : return 0x0A ;
case PacketOutgoingType . TabComplete : return 0x02 ;
case PacketOutgoingType . PlayerPosition : return 0x0D ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x0E ;
2017-09-08 15:08:45 -07:00
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
2017-06-10 09:05:06 -07:00
}
}
2017-08-01 10:34:16 -07:00
else if ( protocol < MC17w31aVersion )
2017-06-10 09:05:06 -07:00
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0C ;
2017-10-19 10:28:15 -07:00
case PacketOutgoingType . ResourcePackStatus : return 0x18 ;
2017-06-10 09:05:06 -07:00
case PacketOutgoingType . ChatMessage : return 0x03 ;
case PacketOutgoingType . ClientStatus : return 0x04 ;
case PacketOutgoingType . ClientSettings : return 0x05 ;
case PacketOutgoingType . PluginMessage : return 0x0A ;
case PacketOutgoingType . TabComplete : return 0x02 ;
case PacketOutgoingType . PlayerPosition : return 0x0E ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x0F ;
2017-09-08 15:08:45 -07:00
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
2017-06-10 09:05:06 -07:00
}
}
2018-09-25 07:38:28 -04:00
else if ( protocol < MC17w45aVersion )
2017-08-01 10:34:16 -07:00
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0B ;
2017-10-19 10:28:15 -07:00
case PacketOutgoingType . ResourcePackStatus : return 0x18 ;
2017-08-01 10:34:16 -07:00
case PacketOutgoingType . ChatMessage : return 0x02 ;
case PacketOutgoingType . ClientStatus : return 0x03 ;
case PacketOutgoingType . ClientSettings : return 0x04 ;
case PacketOutgoingType . PluginMessage : return 0x09 ;
case PacketOutgoingType . TabComplete : return 0x01 ;
case PacketOutgoingType . PlayerPosition : return 0x0D ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x0E ;
2017-09-08 15:08:45 -07:00
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
2017-08-01 10:34:16 -07:00
}
}
2018-09-25 07:38:28 -04:00
else if ( protocol < MC17w46aVersion )
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0A ;
case PacketOutgoingType . ResourcePackStatus : return 0x17 ;
case PacketOutgoingType . ChatMessage : return 0x01 ;
case PacketOutgoingType . ClientStatus : return 0x02 ;
case PacketOutgoingType . ClientSettings : return 0x03 ;
case PacketOutgoingType . PluginMessage : return 0x08 ;
case PacketOutgoingType . TabComplete : throw new InvalidOperationException ( "TabComplete was accidentely removed in protocol " + protocol + ". Please use a more recent version." ) ;
case PacketOutgoingType . PlayerPosition : return 0x0C ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x0D ;
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
}
}
else if ( protocol < MC113pre4Version )
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0B ;
case PacketOutgoingType . ResourcePackStatus : return 0x18 ;
case PacketOutgoingType . ChatMessage : return 0x01 ;
case PacketOutgoingType . ClientStatus : return 0x02 ;
case PacketOutgoingType . ClientSettings : return 0x03 ;
case PacketOutgoingType . PluginMessage : return 0x09 ;
case PacketOutgoingType . TabComplete : return 0x04 ;
case PacketOutgoingType . PlayerPosition : return 0x0D ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x0E ;
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
}
}
else if ( protocol < MC113pre7Version )
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0C ;
case PacketOutgoingType . ResourcePackStatus : return 0x1B ;
case PacketOutgoingType . ChatMessage : return 0x01 ;
case PacketOutgoingType . ClientStatus : return 0x02 ;
case PacketOutgoingType . ClientSettings : return 0x03 ;
case PacketOutgoingType . PluginMessage : return 0x09 ;
case PacketOutgoingType . TabComplete : return 0x04 ;
case PacketOutgoingType . PlayerPosition : return 0x0E ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x0F ;
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
}
}
else
{
switch ( packet )
{
case PacketOutgoingType . KeepAlive : return 0x0E ;
case PacketOutgoingType . ResourcePackStatus : return 0x1D ;
case PacketOutgoingType . ChatMessage : return 0x02 ;
case PacketOutgoingType . ClientStatus : return 0x03 ;
case PacketOutgoingType . ClientSettings : return 0x04 ;
case PacketOutgoingType . PluginMessage : return 0x0A ;
case PacketOutgoingType . TabComplete : return 0x05 ;
case PacketOutgoingType . PlayerPosition : return 0x10 ;
case PacketOutgoingType . PlayerPositionAndLook : return 0x11 ;
case PacketOutgoingType . TeleportConfirm : return 0x00 ;
}
}
2017-06-10 09:05:06 -07:00
throw new System . ComponentModel . InvalidEnumArgumentException ( "Unknown PacketOutgoingType (protocol=" + protocol + ")" , ( int ) packet , typeof ( PacketOutgoingType ) ) ;
2016-03-05 19:12:43 +01:00
}
2014-09-04 13:58:49 +02:00
/// <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>
2015-11-30 15:30:49 +01:00
private bool handlePacket ( int packetID , List < byte > packetData )
2014-09-04 13:58:49 +02:00
{
if ( login_phase )
{
switch ( packetID ) //Packet IDs are different while logging in
{
case 0x03 :
2015-07-30 16:47:55 +02:00
if ( protocolversion > = MC18Version )
2015-11-30 15:30:49 +01:00
compression_treshold = readNextVarInt ( packetData ) ;
2014-09-04 13:58:49 +02:00
break ;
default :
return false ; //Ignored packet
}
}
2015-10-24 13:31:43 -07:00
// Regular in-game packets
2016-03-05 19:12:43 +01:00
switch ( getPacketIncomingType ( packetID , protocolversion ) )
2015-10-24 13:31:43 -07:00
{
2016-03-05 19:12:43 +01:00
case PacketIncomingType . KeepAlive :
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . KeepAlive , packetData ) ;
2015-10-24 13:31:43 -07:00
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . JoinGame :
2015-10-24 13:31:43 -07:00
handler . OnGameJoined ( ) ;
2016-06-10 16:59:53 -07:00
readNextInt ( packetData ) ;
readNextByte ( packetData ) ;
if ( protocolversion > = MC191Version )
this . currentDimension = readNextInt ( packetData ) ;
else
this . currentDimension = ( sbyte ) readNextByte ( packetData ) ;
readNextByte ( packetData ) ;
readNextByte ( packetData ) ;
readNextString ( packetData ) ;
2016-06-20 15:18:27 -07:00
if ( protocolversion > = MC18Version )
readNextBool ( packetData ) ; // Reduced debug info - 1.8 and above
2015-10-24 13:31:43 -07:00
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . ChatMessage :
2015-11-30 15:30:49 +01:00
string message = readNextString ( packetData ) ;
2015-10-24 13:31:43 -07:00
try
{
//Hide system messages or xp bar messages?
2015-11-30 15:30:49 +01:00
byte messageType = readNextByte ( packetData ) ;
2015-10-24 13:31:43 -07:00
if ( ( messageType = = 1 & & ! Settings . DisplaySystemMessages )
| | ( messageType = = 2 & & ! Settings . DisplayXPBarMessages ) )
break ;
}
2016-02-26 17:57:05 -08:00
catch ( ArgumentOutOfRangeException ) { /* No message type */ }
2017-05-31 20:54:16 +02:00
handler . OnTextReceived ( message , true ) ;
2015-10-24 13:31:43 -07:00
break ;
2016-06-10 16:59:53 -07:00
case PacketIncomingType . Respawn :
this . currentDimension = readNextInt ( packetData ) ;
readNextByte ( packetData ) ;
readNextByte ( packetData ) ;
readNextString ( packetData ) ;
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . PlayerPositionAndLook :
2015-11-27 17:16:33 +01:00
if ( Settings . TerrainAndMovements )
{
2015-11-30 15:30:49 +01:00
double x = readNextDouble ( packetData ) ;
double y = readNextDouble ( packetData ) ;
double z = readNextDouble ( packetData ) ;
2019-04-09 18:01:00 -07:00
float yaw = readNextFloat ( packetData ) ;
float pitch = readNextFloat ( packetData ) ;
2015-11-30 15:30:49 +01:00
byte locMask = readNextByte ( packetData ) ;
2015-11-27 17:16:33 +01:00
2016-08-21 15:44:15 +02:00
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 ;
2019-04-09 18:01:00 -07:00
handler . UpdateLocation ( location , yaw , pitch ) ;
2016-08-21 15:44:15 +02:00
}
2019-04-09 18:01:00 -07:00
else handler . UpdateLocation ( new Location ( x , y , z ) , yaw , pitch ) ;
2017-09-08 15:08:45 -07:00
}
2016-03-05 19:12:43 +01:00
2017-09-08 15:08:45 -07:00
if ( protocolversion > = MC19Version )
{
int teleportID = readNextVarInt ( packetData ) ;
// Teleport confirm packet
SendPacket ( PacketOutgoingType . TeleportConfirm , getVarInt ( teleportID ) ) ;
2015-11-27 17:16:33 +01:00
}
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . ChunkData :
2015-11-30 15:30:49 +01:00
if ( Settings . TerrainAndMovements )
{
int chunkX = readNextInt ( packetData ) ;
int chunkZ = readNextInt ( packetData ) ;
bool chunksContinuous = readNextBool ( packetData ) ;
2016-08-21 15:44:15 +02:00
ushort chunkMask = protocolversion > = MC19Version
? ( ushort ) readNextVarInt ( packetData )
: readNextUShort ( packetData ) ;
if ( protocolversion < MC18Version )
{
ushort addBitmap = readNextUShort ( packetData ) ;
int compressedDataSize = readNextInt ( packetData ) ;
byte [ ] compressed = readData ( compressedDataSize , packetData ) ;
byte [ ] decompressed = ZlibUtils . Decompress ( compressed ) ;
2016-11-19 16:23:35 +01:00
ProcessChunkColumnData ( chunkX , chunkZ , chunkMask , addBitmap , currentDimension = = 0 , chunksContinuous , new List < byte > ( decompressed ) ) ;
2016-08-21 15:44:15 +02:00
}
else
{
int dataSize = readNextVarInt ( packetData ) ;
ProcessChunkColumnData ( chunkX , chunkZ , chunkMask , 0 , false , chunksContinuous , packetData ) ;
}
2015-11-30 15:30:49 +01:00
}
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . MultiBlockChange :
2015-11-30 15:30:49 +01:00
if ( Settings . TerrainAndMovements )
{
int chunkX = readNextInt ( packetData ) ;
int chunkZ = readNextInt ( packetData ) ;
2016-08-21 15:44:15 +02:00
int recordCount = protocolversion < MC18Version
? ( int ) readNextShort ( packetData )
: readNextVarInt ( packetData ) ;
2015-11-30 15:30:49 +01:00
for ( int i = 0 ; i < recordCount ; i + + )
{
2016-08-21 15:44:15 +02:00
byte locationXZ ;
ushort blockIdMeta ;
int blockY ;
if ( protocolversion < MC18Version )
{
blockIdMeta = readNextUShort ( packetData ) ;
blockY = ( ushort ) readNextByte ( packetData ) ;
locationXZ = readNextByte ( packetData ) ;
}
else
{
locationXZ = readNextByte ( packetData ) ;
blockY = ( ushort ) readNextByte ( packetData ) ;
blockIdMeta = ( ushort ) readNextVarInt ( packetData ) ;
}
2015-11-30 15:30:49 +01:00
int blockX = locationXZ > > 4 ;
int blockZ = locationXZ & 0x0F ;
2016-08-21 15:44:15 +02:00
Block block = new Block ( blockIdMeta ) ;
2015-12-08 00:34:40 +01:00
handler . GetWorld ( ) . SetBlock ( new Location ( chunkX , chunkZ , blockX , blockY , blockZ ) , block ) ;
2015-11-30 15:30:49 +01:00
}
}
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . BlockChange :
2015-11-30 15:30:49 +01:00
if ( Settings . TerrainAndMovements )
2016-08-21 15:44:15 +02:00
if ( protocolversion < MC18Version )
{
int blockX = readNextInt ( packetData ) ;
int blockY = readNextByte ( packetData ) ;
int blockZ = readNextInt ( packetData ) ;
short blockId = ( short ) readNextVarInt ( packetData ) ;
byte blockMeta = readNextByte ( packetData ) ;
handler . GetWorld ( ) . SetBlock ( new Location ( blockX , blockY , blockZ ) , new Block ( blockId , blockMeta ) ) ;
}
else handler . GetWorld ( ) . SetBlock ( Location . FromLong ( readNextULong ( packetData ) ) , new Block ( ( ushort ) readNextVarInt ( packetData ) ) ) ;
2015-11-30 15:30:49 +01:00
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . MapChunkBulk :
if ( protocolversion < MC19Version & & Settings . TerrainAndMovements )
2015-11-30 15:30:49 +01:00
{
2016-08-21 15:44:15 +02:00
int chunkCount ;
bool hasSkyLight ;
List < byte > chunkData = packetData ;
//Read global fields
if ( protocolversion < MC18Version )
{
chunkCount = readNextShort ( packetData ) ;
int compressedDataSize = readNextInt ( packetData ) ;
hasSkyLight = readNextBool ( packetData ) ;
byte [ ] compressed = readData ( compressedDataSize , packetData ) ;
byte [ ] decompressed = ZlibUtils . Decompress ( compressed ) ;
chunkData = new List < byte > ( decompressed ) ;
}
else
{
hasSkyLight = readNextBool ( packetData ) ;
chunkCount = readNextVarInt ( packetData ) ;
}
2015-11-30 15:30:49 +01:00
//Read chunk records
int [ ] chunkXs = new int [ chunkCount ] ;
int [ ] chunkZs = new int [ chunkCount ] ;
ushort [ ] chunkMasks = new ushort [ chunkCount ] ;
2016-08-21 15:44:15 +02:00
ushort [ ] addBitmaps = new ushort [ chunkCount ] ;
2015-11-30 15:30:49 +01:00
for ( int chunkColumnNo = 0 ; chunkColumnNo < chunkCount ; chunkColumnNo + + )
{
chunkXs [ chunkColumnNo ] = readNextInt ( packetData ) ;
chunkZs [ chunkColumnNo ] = readNextInt ( packetData ) ;
chunkMasks [ chunkColumnNo ] = readNextUShort ( packetData ) ;
2016-08-21 15:44:15 +02:00
addBitmaps [ chunkColumnNo ] = protocolversion < MC18Version
? readNextUShort ( packetData )
: ( ushort ) 0 ;
2015-11-30 15:30:49 +01:00
}
//Process chunk records
for ( int chunkColumnNo = 0 ; chunkColumnNo < chunkCount ; chunkColumnNo + + )
2016-08-21 15:44:15 +02:00
ProcessChunkColumnData ( chunkXs [ chunkColumnNo ] , chunkZs [ chunkColumnNo ] , chunkMasks [ chunkColumnNo ] , addBitmaps [ chunkColumnNo ] , hasSkyLight , true , chunkData ) ;
2015-11-30 15:30:49 +01:00
}
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . UnloadChunk :
if ( protocolversion > = MC19Version & & Settings . TerrainAndMovements )
{
int chunkX = readNextInt ( packetData ) ;
int chunkZ = readNextInt ( packetData ) ;
handler . GetWorld ( ) [ chunkX , chunkZ ] = null ;
}
break ;
case PacketIncomingType . PlayerListUpdate :
2015-10-24 13:31:43 -07:00
if ( protocolversion > = MC18Version )
{
2015-11-30 15:30:49 +01:00
int action = readNextVarInt ( packetData ) ;
int numActions = readNextVarInt ( packetData ) ;
2015-10-24 13:31:43 -07:00
for ( int i = 0 ; i < numActions ; i + + )
2014-11-10 20:43:00 +01:00
{
2015-11-30 15:30:49 +01:00
Guid uuid = readNextUUID ( packetData ) ;
2015-10-24 13:31:43 -07:00
switch ( action )
2015-06-03 12:00:25 +02:00
{
2015-10-24 13:31:43 -07:00
case 0x00 : //Player Join
2015-11-30 15:30:49 +01:00
string name = readNextString ( packetData ) ;
2016-03-08 18:03:02 +01:00
int propNum = readNextVarInt ( packetData ) ;
for ( int p = 0 ; p < propNum ; p + + )
{
2016-08-22 23:15:16 +02:00
string key = readNextString ( packetData ) ;
string val = readNextString ( packetData ) ;
2016-03-08 18:03:02 +01:00
if ( readNextBool ( packetData ) )
readNextString ( packetData ) ;
}
readNextVarInt ( packetData ) ;
readNextVarInt ( packetData ) ;
if ( readNextBool ( packetData ) )
2016-08-27 15:46:34 +02:00
readNextString ( packetData ) ;
handler . OnPlayerJoin ( uuid , name ) ;
2015-10-24 13:31:43 -07:00
break ;
2016-03-08 18:03:02 +01:00
case 0x01 : //Update gamemode
case 0x02 : //Update latency
readNextVarInt ( packetData ) ;
break ;
case 0x03 : //Update display name
2016-08-27 15:46:34 +02:00
if ( readNextBool ( packetData ) )
readNextString ( packetData ) ;
2016-03-08 18:03:02 +01:00
break ;
2015-10-24 13:31:43 -07:00
case 0x04 : //Player Leave
handler . OnPlayerLeave ( uuid ) ;
break ;
default :
//Unknown player list item type
break ;
2015-06-03 12:00:25 +02:00
}
2014-11-11 00:55:42 +11:00
}
2015-10-24 13:31:43 -07:00
}
else //MC 1.7.X does not provide UUID in tab-list updates
{
2015-11-30 15:30:49 +01:00
string name = readNextString ( packetData ) ;
bool online = readNextBool ( packetData ) ;
short ping = readNextShort ( packetData ) ;
2015-10-24 13:31:43 -07:00
Guid FakeUUID = new Guid ( MD5 . Create ( ) . ComputeHash ( Encoding . UTF8 . GetBytes ( name ) ) . Take ( 16 ) . ToArray ( ) ) ;
if ( online )
2016-08-27 15:46:34 +02:00
handler . OnPlayerJoin ( FakeUUID , name ) ;
2015-10-24 13:31:43 -07:00
else handler . OnPlayerLeave ( FakeUUID ) ;
}
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . TabCompleteResult :
2018-09-25 07:38:28 -04:00
if ( protocolversion > = MC17w46aVersion )
{
autocomplete_transaction_id = readNextVarInt ( packetData ) ;
}
if ( protocolversion > = MC17w47aVersion )
{
// Start of the text to replace - currently unused
readNextVarInt ( packetData ) ;
}
if ( protocolversion > = MC18w06aVersion )
{
// Length of the text to replace - currently unused
readNextVarInt ( packetData ) ;
}
2015-11-30 15:30:49 +01:00
int autocomplete_count = readNextVarInt ( packetData ) ;
2016-05-14 11:51:02 +02:00
autocomplete_result . Clear ( ) ;
2015-10-24 13:31:43 -07:00
for ( int i = 0 ; i < autocomplete_count ; i + + )
2016-05-14 11:51:02 +02:00
autocomplete_result . Add ( readNextString ( packetData ) ) ;
2015-10-24 13:31:43 -07:00
autocomplete_received = true ;
2018-09-25 07:38:28 -04:00
// In protocolversion >= MC18w06aVersion there is additional data if the match is a tooltip
// Don't worry about skipping remaining data since there is no useful for us (yet)
2015-10-24 13:31:43 -07:00
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . PluginMessage :
2015-11-30 15:30:49 +01:00
String channel = readNextString ( packetData ) ;
2015-10-24 21:17:28 -07:00
if ( protocolversion < MC18Version )
{
2015-10-25 11:51:53 -07:00
if ( forgeInfo = = null )
{
// 1.7 and lower prefix plugin channel packets with the length.
// We can skip it, though.
2015-11-30 15:30:49 +01:00
readNextShort ( packetData ) ;
2015-10-25 11:51:53 -07:00
}
else
{
// Forge does something even weirder with the length.
2015-11-30 15:30:49 +01:00
readNextVarShort ( packetData ) ;
2015-10-25 11:51:53 -07:00
}
2015-10-24 21:17:28 -07:00
}
2016-02-07 14:24:01 -08:00
// The remaining data in the array is the entire payload of the packet.
handler . OnPluginChannelMessage ( channel , packetData . ToArray ( ) ) ;
2016-03-05 19:12:43 +01:00
#region Forge Login
2016-02-27 18:01:52 +01:00
if ( forgeInfo ! = null & & fmlHandshakeState ! = FMLHandshakeClientState . DONE )
2015-10-24 13:31:43 -07:00
{
2015-10-24 13:38:44 -07:00
if ( channel = = "FML|HS" )
2015-07-30 16:47:55 +02:00
{
2015-11-30 15:30:49 +01:00
FMLHandshakeDiscriminator discriminator = ( FMLHandshakeDiscriminator ) readNextByte ( packetData ) ;
2015-10-24 22:56:35 -07:00
2015-10-24 13:38:44 -07:00
if ( discriminator = = FMLHandshakeDiscriminator . HandshakeReset )
{
fmlHandshakeState = FMLHandshakeClientState . START ;
return true ;
}
switch ( fmlHandshakeState )
{
case FMLHandshakeClientState . START :
if ( discriminator ! = FMLHandshakeDiscriminator . ServerHello )
return false ;
// Send the plugin channel registration.
// REGISTER is somewhat special in that it doesn't actually include length information,
// and is also \0-separated.
// Also, yes, "FML" is there twice. Don't ask me why, but that's the way forge does it.
string [ ] channels = { "FML|HS" , "FML" , "FML|MP" , "FML" , "FORGE" } ;
SendPluginChannelPacket ( "REGISTER" , Encoding . UTF8 . GetBytes ( string . Join ( "\0" , channels ) ) ) ;
2015-10-24 13:31:43 -07:00
2015-11-30 15:30:49 +01:00
byte fmlProtocolVersion = readNextByte ( packetData ) ;
2015-10-24 13:38:44 -07:00
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLineFormatted ( "§8Forge protocol version : " + fmlProtocolVersion ) ;
2015-10-24 13:38:44 -07:00
2016-06-10 16:59:53 -07:00
if ( fmlProtocolVersion > = 1 )
this . currentDimension = readNextInt ( packetData ) ;
2015-10-24 13:38:44 -07:00
// Tell the server we're running the same version.
SendForgeHandshakePacket ( FMLHandshakeDiscriminator . ClientHello , new byte [ ] { fmlProtocolVersion } ) ;
// Then tell the server that we're running the same mods.
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLineFormatted ( "§8Sending falsified mod list to server..." ) ;
2015-10-24 13:38:44 -07:00
byte [ ] [ ] mods = new byte [ forgeInfo . Mods . Count ] [ ] ;
for ( int i = 0 ; i < forgeInfo . Mods . Count ; i + + )
{
ForgeInfo . ForgeMod mod = forgeInfo . Mods [ i ] ;
mods [ i ] = concatBytes ( getString ( mod . ModID ) , getString ( mod . Version ) ) ;
}
2015-10-24 22:56:35 -07:00
SendForgeHandshakePacket ( FMLHandshakeDiscriminator . ModList ,
2015-10-24 13:38:44 -07:00
concatBytes ( getVarInt ( forgeInfo . Mods . Count ) , concatBytes ( mods ) ) ) ;
fmlHandshakeState = FMLHandshakeClientState . WAITINGSERVERDATA ;
2015-10-24 15:01:46 -07:00
return true ;
case FMLHandshakeClientState . WAITINGSERVERDATA :
if ( discriminator ! = FMLHandshakeDiscriminator . ModList )
return false ;
2015-10-25 11:51:53 -07:00
Thread . Sleep ( 2000 ) ;
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLineFormatted ( "§8Accepting server mod list..." ) ;
2015-10-24 15:01:46 -07:00
// Tell the server that yes, we are OK with the mods it has
// even though we don't actually care what mods it has.
2015-10-25 11:51:53 -07:00
2015-10-24 15:01:46 -07:00
SendForgeHandshakePacket ( FMLHandshakeDiscriminator . HandshakeAck ,
new byte [ ] { ( byte ) FMLHandshakeClientState . WAITINGSERVERDATA } ) ;
fmlHandshakeState = FMLHandshakeClientState . WAITINGSERVERCOMPLETE ;
return false ;
case FMLHandshakeClientState . WAITINGSERVERCOMPLETE :
// The server now will tell us a bunch of registry information.
// We need to read it all, though, until it says that there is no more.
if ( discriminator ! = FMLHandshakeDiscriminator . RegistryData )
return false ;
2015-10-24 21:17:28 -07:00
if ( protocolversion < MC18Version )
{
// 1.7.10 and below have one registry
// with blocks and items.
2015-11-30 15:30:49 +01:00
int registrySize = readNextVarInt ( packetData ) ;
2015-10-24 15:01:46 -07:00
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLineFormatted ( "§8Received registry with " + registrySize + " entries" ) ;
2015-10-24 15:01:46 -07:00
fmlHandshakeState = FMLHandshakeClientState . PENDINGCOMPLETE ;
}
2015-10-24 21:17:28 -07:00
else
{
// 1.8+ has more than one registry.
2015-11-30 15:30:49 +01:00
bool hasNextRegistry = readNextBool ( packetData ) ;
string registryName = readNextString ( packetData ) ;
int registrySize = readNextVarInt ( packetData ) ;
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLineFormatted ( "§8Received registry " + registryName + " with " + registrySize + " entries" ) ;
2015-10-24 21:17:28 -07:00
if ( ! hasNextRegistry )
fmlHandshakeState = FMLHandshakeClientState . PENDINGCOMPLETE ;
}
2015-10-24 15:01:46 -07:00
return false ;
case FMLHandshakeClientState . PENDINGCOMPLETE :
// The server will ask us to accept the registries.
// Just say yes.
if ( discriminator ! = FMLHandshakeDiscriminator . HandshakeAck )
return false ;
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLineFormatted ( "§8Accepting server registries..." ) ;
2015-10-24 15:01:46 -07:00
SendForgeHandshakePacket ( FMLHandshakeDiscriminator . HandshakeAck ,
new byte [ ] { ( byte ) FMLHandshakeClientState . PENDINGCOMPLETE } ) ;
fmlHandshakeState = FMLHandshakeClientState . COMPLETE ;
return true ;
case FMLHandshakeClientState . COMPLETE :
// One final "OK". On the actual forge source, a packet is sent from
// the client to the client saying that the connection was complete, but
// we don't need to do that.
SendForgeHandshakePacket ( FMLHandshakeDiscriminator . HandshakeAck ,
new byte [ ] { ( byte ) FMLHandshakeClientState . COMPLETE } ) ;
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLine ( "Forge server connection complete!" ) ;
2015-10-24 15:01:46 -07:00
fmlHandshakeState = FMLHandshakeClientState . DONE ;
2015-10-24 13:38:44 -07:00
return true ;
}
2014-09-04 13:58:49 +02:00
}
2015-10-24 13:31:43 -07:00
}
2016-03-05 19:12:43 +01:00
#endregion
2015-10-24 13:38:44 -07:00
return false ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . KickPacket :
2015-11-30 15:30:49 +01:00
handler . OnConnectionLost ( ChatBot . DisconnectReason . InGameKick , ChatParser . ParseText ( readNextString ( packetData ) ) ) ;
2015-10-24 13:31:43 -07:00
return false ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . NetworkCompressionTreshold :
if ( protocolversion > = MC18Version & & protocolversion < MC19Version )
2015-11-30 15:30:49 +01:00
compression_treshold = readNextVarInt ( packetData ) ;
2015-10-24 13:31:43 -07:00
break ;
2016-03-05 19:12:43 +01:00
case PacketIncomingType . ResourcePackSend :
2015-11-30 15:30:49 +01:00
string url = readNextString ( packetData ) ;
string hash = readNextString ( packetData ) ;
2015-10-24 13:31:43 -07:00
//Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory
2016-06-09 21:10:45 +02:00
byte [ ] responseHeader = new byte [ 0 ] ;
if ( protocolversion < MC110Version ) //MC 1.10 does not include resource pack hash in responses
responseHeader = concatBytes ( getVarInt ( hash . Length ) , Encoding . UTF8 . GetBytes ( hash ) ) ;
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . ResourcePackStatus , concatBytes ( responseHeader , getVarInt ( 3 ) ) ) ; //Accepted pack
SendPacket ( PacketOutgoingType . ResourcePackStatus , concatBytes ( responseHeader , getVarInt ( 0 ) ) ) ; //Successfully loaded
2015-10-24 13:31:43 -07:00
break ;
default :
return false ; //Ignored packet
2014-09-04 13:58:49 +02:00
}
return true ; //Packet processed
}
2015-11-30 15:30:49 +01:00
/// <summary>
/// Process chunk column data from the server and (un)load the chunk from the Minecraft world
/// </summary>
/// <param name="chunkX">Chunk X location</param>
/// <param name="chunkZ">Chunk Z location</param>
/// <param name="chunkMask">Chunk mask for reading data</param>
2016-08-21 15:44:15 +02:00
/// <param name="chunkMask2">Chunk mask for some additional 1.7 metadata</param>
2015-11-30 15:30:49 +01:00
/// <param name="hasSkyLight">Contains skylight info</param>
/// <param name="chunksContinuous">Are the chunk continuous</param>
/// <param name="cache">Cache for reading chunk data</param>
2016-08-21 15:44:15 +02:00
private void ProcessChunkColumnData ( int chunkX , int chunkZ , ushort chunkMask , ushort chunkMask2 , bool hasSkyLight , bool chunksContinuous , List < byte > cache )
2015-11-30 15:30:49 +01:00
{
2016-06-09 17:06:23 -07:00
if ( protocolversion > = MC19Version )
2015-11-30 15:30:49 +01:00
{
2016-06-09 17:06:23 -07:00
// 1.9 and above chunk format
// Unloading chunks is handled by a separate packet
2015-11-30 15:30:49 +01:00
for ( int chunkY = 0 ; chunkY < ChunkColumn . ColumnSize ; chunkY + + )
{
if ( ( chunkMask & ( 1 < < chunkY ) ) ! = 0 )
{
2016-06-09 17:06:23 -07:00
byte bitsPerBlock = readNextByte ( cache ) ;
bool usePalette = ( bitsPerBlock < = 8 ) ;
int paletteLength = readNextVarInt ( cache ) ;
int [ ] palette = new int [ paletteLength ] ;
for ( int i = 0 ; i < paletteLength ; i + + )
{
palette [ i ] = readNextVarInt ( cache ) ;
}
// Bit mask covering bitsPerBlock bits
// EG, if bitsPerBlock = 5, valueMask = 00011111 in binary
uint valueMask = ( uint ) ( ( 1 < < bitsPerBlock ) - 1 ) ;
ulong [ ] dataArray = readNextULongArray ( cache ) ;
2015-11-30 15:30:49 +01:00
Chunk chunk = new Chunk ( ) ;
2016-07-22 23:48:14 +02:00
if ( dataArray . Length > 0 )
2016-06-09 17:06:23 -07:00
{
2016-07-22 23:48:14 +02:00
for ( int blockY = 0 ; blockY < Chunk . SizeY ; blockY + + )
2016-06-09 17:06:23 -07:00
{
2016-07-22 23:48:14 +02:00
for ( int blockZ = 0 ; blockZ < Chunk . SizeZ ; blockZ + + )
2016-06-09 17:06:23 -07:00
{
2016-07-22 23:48:14 +02:00
for ( int blockX = 0 ; blockX < Chunk . SizeX ; blockX + + )
2016-06-09 17:06:23 -07:00
{
2016-07-22 23:48:14 +02:00
int blockNumber = ( blockY * Chunk . SizeZ + blockZ ) * Chunk . SizeX + blockX ;
int startLong = ( blockNumber * bitsPerBlock ) / 64 ;
int startOffset = ( blockNumber * bitsPerBlock ) % 64 ;
int endLong = ( ( blockNumber + 1 ) * bitsPerBlock - 1 ) / 64 ;
// TODO: In the future a single ushort may not store the entire block id;
// the Block code may need to change.
ushort blockId ;
if ( startLong = = endLong )
{
blockId = ( ushort ) ( ( dataArray [ startLong ] > > startOffset ) & valueMask ) ;
}
else
{
int endOffset = 64 - startOffset ;
blockId = ( ushort ) ( ( dataArray [ startLong ] > > startOffset | dataArray [ endLong ] < < endOffset ) & valueMask ) ;
}
if ( usePalette )
{
// Get the real block ID out of the palette
blockId = ( ushort ) palette [ blockId ] ;
}
chunk [ blockX , blockY , blockZ ] = new Block ( blockId ) ;
2016-06-09 17:06:23 -07:00
}
}
}
}
2015-11-30 15:30:49 +01:00
//We have our chunk, save the chunk into the world
if ( handler . GetWorld ( ) [ chunkX , chunkZ ] = = null )
handler . GetWorld ( ) [ chunkX , chunkZ ] = new ChunkColumn ( ) ;
handler . GetWorld ( ) [ chunkX , chunkZ ] [ chunkY ] = chunk ;
//Skip block light
readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ ) / 2 , cache ) ;
//Skip sky light
2016-07-22 23:48:14 +02:00
if ( this . currentDimension = = 0 )
// Sky light is not sent in the nether or the end
2016-06-10 16:59:53 -07:00
readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ ) / 2 , cache ) ;
2015-11-30 15:30:49 +01:00
}
}
2016-06-09 17:06:23 -07:00
// Don't worry about skipping remaining data since there is no useful data afterwards in 1.9
// (plus, it would require parsing the tile entity lists' NBT)
}
2016-08-21 15:44:15 +02:00
else if ( protocolversion > = MC18Version )
2016-06-09 17:06:23 -07:00
{
2016-08-21 15:44:15 +02:00
// 1.8 chunk format
if ( chunksContinuous & & chunkMask = = 0 )
{
2016-06-09 17:06:23 -07:00
//Unload the entire chunk column
handler . GetWorld ( ) [ chunkX , chunkZ ] = null ;
2016-08-21 15:44:15 +02:00
}
else
{
2016-06-09 17:06:23 -07:00
//Load chunk data from the server
for ( int chunkY = 0 ; chunkY < ChunkColumn . ColumnSize ; chunkY + + )
{
if ( ( chunkMask & ( 1 < < chunkY ) ) ! = 0 )
{
Chunk chunk = new Chunk ( ) ;
//Read chunk data, all at once for performance reasons, and build the chunk object
Queue < ushort > queue = new Queue < ushort > ( readNextUShortsLittleEndian ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ , cache ) ) ;
for ( int blockY = 0 ; blockY < Chunk . SizeY ; blockY + + )
for ( int blockZ = 0 ; blockZ < Chunk . SizeZ ; blockZ + + )
for ( int blockX = 0 ; blockX < Chunk . SizeX ; blockX + + )
chunk [ blockX , blockY , blockZ ] = new Block ( queue . Dequeue ( ) ) ;
//We have our chunk, save the chunk into the world
if ( handler . GetWorld ( ) [ chunkX , chunkZ ] = = null )
handler . GetWorld ( ) [ chunkX , chunkZ ] = new ChunkColumn ( ) ;
handler . GetWorld ( ) [ chunkX , chunkZ ] [ chunkY ] = chunk ;
}
}
//Skip light information
for ( int chunkY = 0 ; chunkY < ChunkColumn . ColumnSize ; chunkY + + )
{
if ( ( chunkMask & ( 1 < < chunkY ) ) ! = 0 )
{
//Skip block light
readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ ) / 2 , cache ) ;
//Skip sky light
if ( hasSkyLight )
readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ ) / 2 , cache ) ;
}
}
//Skip biome metadata
if ( chunksContinuous )
readData ( Chunk . SizeX * Chunk . SizeZ , cache ) ;
}
2015-11-30 15:30:49 +01:00
}
2016-08-21 15:44:15 +02:00
else
{
// 1.7 chunk format
if ( chunksContinuous & & chunkMask = = 0 )
{
//Unload the entire chunk column
handler . GetWorld ( ) [ chunkX , chunkZ ] = null ;
}
else
{
//Count chunk sections
int sectionCount = 0 ;
int addDataSectionCount = 0 ;
for ( int chunkY = 0 ; chunkY < ChunkColumn . ColumnSize ; chunkY + + )
{
if ( ( chunkMask & ( 1 < < chunkY ) ) ! = 0 )
sectionCount + + ;
if ( ( chunkMask2 & ( 1 < < chunkY ) ) ! = 0 )
addDataSectionCount + + ;
}
//Read chunk data, unpacking 4-bit values into 8-bit values for block metadata
Queue < byte > blockTypes = new Queue < byte > ( readData ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ * sectionCount , cache ) ) ;
Queue < byte > blockMeta = new Queue < byte > ( ) ;
foreach ( byte packed in readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ * sectionCount ) / 2 , cache ) )
{
byte hig = ( byte ) ( packed > > 4 ) ;
byte low = ( byte ) ( packed & ( byte ) 0x0F ) ;
blockMeta . Enqueue ( hig ) ;
blockMeta . Enqueue ( low ) ;
}
//Skip data we don't need
readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ * sectionCount ) / 2 , cache ) ; //Block light
if ( hasSkyLight )
readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ * sectionCount ) / 2 , cache ) ; //Sky light
readData ( ( Chunk . SizeX * Chunk . SizeY * Chunk . SizeZ * addDataSectionCount ) / 2 , cache ) ; //BlockAdd
if ( chunksContinuous )
readData ( Chunk . SizeX * Chunk . SizeZ , cache ) ; //Biomes
//Load chunk data
for ( int chunkY = 0 ; chunkY < ChunkColumn . ColumnSize ; chunkY + + )
{
if ( ( chunkMask & ( 1 < < chunkY ) ) ! = 0 )
{
Chunk chunk = new Chunk ( ) ;
for ( int blockY = 0 ; blockY < Chunk . SizeY ; blockY + + )
for ( int blockZ = 0 ; blockZ < Chunk . SizeZ ; blockZ + + )
for ( int blockX = 0 ; blockX < Chunk . SizeX ; blockX + + )
chunk [ blockX , blockY , blockZ ] = new Block ( blockTypes . Dequeue ( ) , blockMeta . Dequeue ( ) ) ;
if ( handler . GetWorld ( ) [ chunkX , chunkZ ] = = null )
handler . GetWorld ( ) [ chunkX , chunkZ ] = new ChunkColumn ( ) ;
handler . GetWorld ( ) [ chunkX , chunkZ ] [ chunkY ] = chunk ;
}
}
}
}
2015-11-30 15:30:49 +01:00
}
2014-09-04 13:58:49 +02:00
/// <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 ( ) ;
c . Close ( ) ;
}
}
catch { }
}
/// <summary>
/// Read some data directly from the network
/// </summary>
/// <param name="offset">Amount of bytes to read</param>
/// <returns>The data read from the network as an array</returns>
private byte [ ] readDataRAW ( int offset )
{
if ( offset > 0 )
{
try
{
byte [ ] cache = new byte [ offset ] ;
Receive ( cache , 0 , offset , SocketFlags . None ) ;
return cache ;
}
catch ( OutOfMemoryException ) { }
}
return new byte [ ] { } ;
}
2015-10-24 13:31:43 -07:00
2014-09-04 13:58:49 +02:00
/// <summary>
/// Read some data from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="offset">Amount of bytes to read</param>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The data read from the cache as an array</returns>
2015-11-30 15:30:49 +01:00
private static byte [ ] readData ( int offset , List < byte > cache )
2014-09-04 13:58:49 +02:00
{
2015-07-30 16:47:55 +02:00
byte [ ] result = cache . Take ( offset ) . ToArray ( ) ;
2015-11-30 15:30:49 +01:00
cache . RemoveRange ( 0 , offset ) ;
2015-07-30 16:47:55 +02:00
return result ;
2014-09-04 13:58:49 +02:00
}
/// <summary>
/// Read a string from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The string</returns>
2015-11-30 15:30:49 +01:00
private static string readNextString ( List < byte > cache )
2014-09-04 13:58:49 +02:00
{
2015-11-30 15:30:49 +01:00
int length = readNextVarInt ( cache ) ;
2014-09-04 13:58:49 +02:00
if ( length > 0 )
{
2015-11-30 15:30:49 +01:00
return Encoding . UTF8 . GetString ( readData ( length , cache ) ) ;
2014-09-04 13:58:49 +02:00
}
else return "" ;
}
2015-07-30 16:47:55 +02:00
/// <summary>
/// Read a boolean from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The boolean value</returns>
2015-11-30 15:30:49 +01:00
private static bool readNextBool ( List < byte > cache )
2015-07-30 16:47:55 +02:00
{
2015-11-30 15:30:49 +01:00
return readNextByte ( cache ) ! = 0x00 ;
2015-07-30 16:47:55 +02:00
}
/// <summary>
/// Read a short integer from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The short integer value</returns>
2015-11-30 15:30:49 +01:00
private static short readNextShort ( List < byte > cache )
2015-07-30 16:47:55 +02:00
{
2015-11-30 15:30:49 +01:00
byte [ ] rawValue = readData ( 2 , cache ) ;
2015-07-30 16:47:55 +02:00
Array . Reverse ( rawValue ) ; //Endianness
return BitConverter . ToInt16 ( rawValue , 0 ) ;
}
2015-11-30 15:30:49 +01:00
/// <summary>
/// Read an integer from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The integer value</returns>
private static int readNextInt ( List < byte > cache )
{
byte [ ] rawValue = readData ( 4 , cache ) ;
Array . Reverse ( rawValue ) ; //Endianness
return BitConverter . ToInt32 ( rawValue , 0 ) ;
}
2015-10-25 11:51:53 -07:00
/// <summary>
/// Read an unsigned short integer from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The unsigned short integer value</returns>
2015-11-30 15:30:49 +01:00
private static ushort readNextUShort ( List < byte > cache )
2015-10-25 11:51:53 -07:00
{
2015-11-30 15:30:49 +01:00
byte [ ] rawValue = readData ( 2 , cache ) ;
2015-10-25 11:51:53 -07:00
Array . Reverse ( rawValue ) ; //Endianness
return BitConverter . ToUInt16 ( rawValue , 0 ) ;
}
2015-11-30 15:30:49 +01:00
/// <summary>
2016-06-09 17:06:23 -07:00
/// Read an unsigned long integer from a cache of bytes and remove it from the cache
2015-11-30 15:30:49 +01:00
/// </summary>
2016-06-09 17:06:23 -07:00
/// <returns>The unsigned long integer value</returns>
2015-11-30 15:30:49 +01:00
private static ulong readNextULong ( List < byte > cache )
{
byte [ ] rawValue = readData ( 8 , cache ) ;
Array . Reverse ( rawValue ) ; //Endianness
return BitConverter . ToUInt64 ( rawValue , 0 ) ;
}
/// <summary>
/// Read several little endian unsigned short integers at once from a cache of bytes and remove them from the cache
/// </summary>
/// <returns>The unsigned short integer value</returns>
private static ushort [ ] readNextUShortsLittleEndian ( int amount , List < byte > cache )
{
byte [ ] rawValues = readData ( 2 * amount , cache ) ;
ushort [ ] result = new ushort [ amount ] ;
for ( int i = 0 ; i < amount ; i + + )
2015-12-08 00:34:40 +01:00
result [ i ] = BitConverter . ToUInt16 ( rawValues , i * 2 ) ;
2015-11-30 15:30:49 +01:00
return result ;
}
2014-11-11 00:55:42 +11:00
/// <summary>
/// Read a uuid from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
2014-11-10 20:43:00 +01:00
/// <returns>The uuid</returns>
2015-11-30 15:30:49 +01:00
private static Guid readNextUUID ( List < byte > cache )
2014-11-11 00:55:42 +11:00
{
2015-11-30 15:30:49 +01:00
return new Guid ( readData ( 16 , cache ) ) ;
2014-11-11 00:55:42 +11:00
}
2014-11-11 00:32:32 +11:00
2014-09-04 13:58:49 +02:00
/// <summary>
/// Read a byte array from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The byte array</returns>
2015-11-30 15:30:49 +01:00
private byte [ ] readNextByteArray ( List < byte > cache )
2014-09-04 13:58:49 +02:00
{
2015-07-30 16:47:55 +02:00
int len = protocolversion > = MC18Version
2015-11-30 15:30:49 +01:00
? readNextVarInt ( cache )
: readNextShort ( cache ) ;
return readData ( len , cache ) ;
2014-09-04 13:58:49 +02:00
}
2016-06-09 17:06:23 -07:00
/// <summary>
/// Reads a length-prefixed array of unsigned long integers and removes it from the cache
/// </summary>
/// <returns>The unsigned long integer values</returns>
private static ulong [ ] readNextULongArray ( List < byte > cache )
{
int len = readNextVarInt ( cache ) ;
ulong [ ] result = new ulong [ len ] ;
for ( int i = 0 ; i < len ; i + + )
result [ i ] = readNextULong ( cache ) ;
return result ;
}
2015-11-27 17:16:33 +01:00
/// <summary>
/// Read a double from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The double value</returns>
2015-11-30 15:30:49 +01:00
private static double readNextDouble ( List < byte > cache )
2015-11-27 17:16:33 +01:00
{
2015-11-30 15:30:49 +01:00
byte [ ] rawValue = readData ( 8 , cache ) ;
2015-11-27 17:16:33 +01:00
Array . Reverse ( rawValue ) ; //Endianness
return BitConverter . ToDouble ( rawValue , 0 ) ;
}
2019-04-09 18:01:00 -07:00
/// <summary>
/// Read a float from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The float value</returns>
private static float readNextFloat ( List < byte > cache )
{
byte [ ] rawValue = readData ( 4 , cache ) ;
Array . Reverse ( rawValue ) ; //Endianness
return BitConverter . ToSingle ( rawValue , 0 ) ;
}
2014-09-04 13:58:49 +02:00
/// <summary>
/// Read an integer from the network
/// </summary>
/// <returns>The integer</returns>
private int readNextVarIntRAW ( )
{
int i = 0 ;
int j = 0 ;
int k = 0 ;
byte [ ] tmp = new byte [ 1 ] ;
while ( true )
{
Receive ( tmp , 0 , 1 , SocketFlags . None ) ;
k = tmp [ 0 ] ;
i | = ( k & 0x7F ) < < j + + * 7 ;
if ( j > 5 ) throw new OverflowException ( "VarInt too big" ) ;
if ( ( k & 0x80 ) ! = 128 ) break ;
}
return i ;
}
2015-10-24 13:31:43 -07:00
2014-09-04 13:58:49 +02:00
/// <summary>
/// Read an integer from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The integer</returns>
2015-11-30 15:30:49 +01:00
private static int readNextVarInt ( List < byte > cache )
2014-09-04 13:58:49 +02:00
{
int i = 0 ;
int j = 0 ;
int k = 0 ;
while ( true )
{
2015-11-30 15:30:49 +01:00
k = readNextByte ( cache ) ;
2014-09-04 13:58:49 +02:00
i | = ( k & 0x7F ) < < j + + * 7 ;
if ( j > 5 ) throw new OverflowException ( "VarInt too big" ) ;
if ( ( k & 0x80 ) ! = 128 ) break ;
}
return i ;
}
2015-10-25 11:51:53 -07:00
/// <summary>
/// Read an "extended short", which is actually an int of some kind, from the cache of bytes.
/// This is only done with forge. It looks like it's a normal short, except that if the high
/// bit is set, it has an extra byte.
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The int</returns>
2015-11-30 15:30:49 +01:00
private static int readNextVarShort ( List < byte > cache )
2015-10-25 11:51:53 -07:00
{
2015-11-30 15:30:49 +01:00
ushort low = readNextUShort ( cache ) ;
2015-10-25 11:51:53 -07:00
byte high = 0 ;
if ( ( low & 0x8000 ) ! = 0 )
{
low & = 0x7FFF ;
2015-11-30 15:30:49 +01:00
high = readNextByte ( cache ) ;
2015-10-25 11:51:53 -07:00
}
return ( ( high & 0xFF ) < < 15 ) | low ;
}
2015-10-24 13:31:43 -07:00
/// <summary>
/// Read a single byte from a cache of bytes and remove it from the cache
/// </summary>
/// <returns>The byte that was read</returns>
2015-11-30 15:30:49 +01:00
private static byte readNextByte ( List < byte > cache )
2015-10-24 13:31:43 -07:00
{
2015-11-30 15:30:49 +01:00
byte result = cache [ 0 ] ;
cache . RemoveAt ( 0 ) ;
return result ;
2015-10-24 13:31:43 -07:00
}
2014-09-04 13:58:49 +02:00
/// <summary>
/// Build an integer for sending over the network
/// </summary>
/// <param name="paramInt">Integer to encode</param>
/// <returns>Byte array for this integer</returns>
private static byte [ ] getVarInt ( int paramInt )
{
List < byte > bytes = new List < byte > ( ) ;
while ( ( paramInt & - 128 ) ! = 0 )
{
bytes . Add ( ( byte ) ( paramInt & 127 | 128 ) ) ;
paramInt = ( int ) ( ( ( uint ) paramInt ) > > 7 ) ;
}
bytes . Add ( ( byte ) paramInt ) ;
return bytes . ToArray ( ) ;
}
2015-11-27 17:16:33 +01:00
/// <summary>
/// Get byte array representing a double
/// </summary>
2018-09-25 07:38:28 -04:00
/// <param name="number">Double to process</param>
2015-11-27 17:16:33 +01:00
/// <returns>Array ready to send</returns>
private byte [ ] getDouble ( double number )
{
byte [ ] theDouble = BitConverter . GetBytes ( number ) ;
Array . Reverse ( theDouble ) ; //Endianness
return theDouble ;
}
2019-04-09 18:01:00 -07:00
/// <summary>
/// Get byte array representing a float
/// </summary>
/// <param name="number">Floalt to process</param>
/// <returns>Array ready to send</returns>
private byte [ ] getFloat ( float number )
{
byte [ ] theFloat = BitConverter . GetBytes ( number ) ;
Array . Reverse ( theFloat ) ; //Endianness
return theFloat ;
}
2015-07-30 16:47:55 +02:00
/// <summary>
/// Get byte array with length information prepended to it
/// </summary>
/// <param name="array">Array to process</param>
/// <returns>Array ready to send</returns>
private byte [ ] getArray ( byte [ ] array )
{
if ( protocolversion < MC18Version )
{
byte [ ] length = BitConverter . GetBytes ( ( short ) array . Length ) ;
Array . Reverse ( length ) ;
return concatBytes ( length , array ) ;
}
else return concatBytes ( getVarInt ( array . Length ) , array ) ;
}
2015-10-24 13:31:43 -07:00
/// <summary>
/// Get a byte array from the given string for sending over the network, with length information prepended.
/// </summary>
2018-09-25 07:38:28 -04:00
/// <param name="text">String to process</param>
2015-10-24 13:31:43 -07:00
/// <returns>Array ready to send</returns>
2018-09-25 07:38:28 -04:00
private static byte [ ] getString ( string text )
2015-10-24 13:31:43 -07:00
{
byte [ ] bytes = Encoding . UTF8 . GetBytes ( text ) ;
return concatBytes ( getVarInt ( bytes . Length ) , bytes ) ;
}
2014-09-04 13:58:49 +02:00
/// <summary>
/// Easily append several byte arrays
/// </summary>
/// <param name="bytes">Bytes to append</param>
/// <returns>Array containing all the data</returns>
private static byte [ ] concatBytes ( params byte [ ] [ ] bytes )
{
List < byte > result = new List < byte > ( ) ;
foreach ( byte [ ] array in bytes )
result . AddRange ( array ) ;
return result . ToArray ( ) ;
}
/// <summary>
/// C-like atoi function for parsing an int from string
/// </summary>
/// <param name="str">String to parse</param>
/// <returns>Int parsed</returns>
private static int atoi ( string str )
{
return int . Parse ( new string ( str . Trim ( ) . TakeWhile ( char . IsDigit ) . ToArray ( ) ) ) ;
}
/// <summary>
/// Network reading method. Read bytes from the socket or encrypted socket.
/// </summary>
private void Receive ( byte [ ] buffer , int start , int offset , SocketFlags f )
{
int read = 0 ;
while ( read < offset )
{
if ( encrypted )
{
read + = s . Read ( buffer , start + read , offset - read ) ;
}
else read + = c . Client . Receive ( buffer , start + read , offset - read , f ) ;
}
}
2015-10-24 13:31:43 -07:00
/// <summary>
/// Send a forge plugin channel packet ("FML|HS"). Compression and encryption will be handled automatically
/// </summary>
/// <param name="discriminator">Discriminator to use.</param>
/// <param name="data">packet Data</param>
private void SendForgeHandshakePacket ( FMLHandshakeDiscriminator discriminator , byte [ ] data )
{
SendPluginChannelPacket ( "FML|HS" , concatBytes ( new byte [ ] { ( byte ) discriminator } , data ) ) ;
}
2014-09-04 13:58:49 +02:00
/// <summary>
2017-06-10 09:05:06 -07:00
/// 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 ( getPacketOutgoingID ( packet , protocolversion ) , packetData ) ;
}
/// <summary>
/// Send a packet to the server. Compression and encryption will be handled automatically.
2014-09-04 13:58:49 +02:00
/// </summary>
/// <param name="packetID">packet ID</param>
/// <param name="packetData">packet Data</param>
2015-11-30 15:30:49 +01:00
private void SendPacket ( int packetID , IEnumerable < byte > packetData )
2014-09-04 13:58:49 +02:00
{
//The inner packet
2015-11-30 15:30:49 +01:00
byte [ ] the_packet = concatBytes ( getVarInt ( packetID ) , packetData . ToArray ( ) ) ;
2014-09-04 13:58:49 +02:00
if ( compression_treshold > 0 ) //Compression enabled?
{
2017-09-08 17:36:22 -07:00
if ( the_packet . Length > = compression_treshold ) //Packet long enough for compressing?
2014-09-04 13:58:49 +02:00
{
byte [ ] compressed_packet = ZlibUtils . Compress ( the_packet ) ;
2017-09-08 17:36:22 -07:00
the_packet = concatBytes ( getVarInt ( the_packet . Length ) , compressed_packet ) ;
2014-09-04 13:58:49 +02:00
}
else
{
byte [ ] uncompressed_length = getVarInt ( 0 ) ; //Not compressed (short packet)
the_packet = concatBytes ( uncompressed_length , the_packet ) ;
}
}
2015-10-24 13:31:43 -07:00
SendRAW ( concatBytes ( getVarInt ( the_packet . Length ) , the_packet ) ) ;
2014-09-04 13:58:49 +02:00
}
/// <summary>
/// Send raw data to the server. Encryption will be handled automatically.
/// </summary>
/// <param name="buffer">data to send</param>
private void SendRAW ( byte [ ] buffer )
{
if ( encrypted )
{
s . Write ( buffer , 0 , buffer . Length ) ;
}
else c . Client . Send ( buffer ) ;
}
/// <summary>
/// Do the Minecraft login.
/// </summary>
/// <returns>True if login successful</returns>
public bool Login ( )
{
byte [ ] protocol_version = getVarInt ( protocolversion ) ;
2018-09-25 07:38:28 -04:00
string server_address = handler . GetServerHost ( ) + ( forgeInfo ! = null ? "\0FML\0" : "" ) ;
2015-06-20 22:58:18 +02:00
byte [ ] server_port = BitConverter . GetBytes ( ( ushort ) handler . GetServerPort ( ) ) ; Array . Reverse ( server_port ) ;
2014-09-04 13:58:49 +02:00
byte [ ] next_state = getVarInt ( 2 ) ;
2018-09-25 07:38:28 -04:00
byte [ ] handshake_packet = concatBytes ( protocol_version , getString ( server_address ) , server_port , next_state ) ;
2014-09-04 13:58:49 +02:00
SendPacket ( 0x00 , handshake_packet ) ;
2018-09-25 07:38:28 -04:00
byte [ ] login_packet = getString ( handler . GetUsername ( ) ) ;
2014-09-04 13:58:49 +02:00
SendPacket ( 0x00 , login_packet ) ;
int packetID = - 1 ;
2015-11-30 15:30:49 +01:00
List < byte > packetData = new List < byte > ( ) ;
2014-09-04 13:58:49 +02:00
while ( true )
{
2015-11-30 15:30:49 +01:00
readNextPacket ( ref packetID , packetData ) ;
2014-09-04 13:58:49 +02:00
if ( packetID = = 0x00 ) //Login rejected
{
2015-11-30 15:30:49 +01:00
handler . OnConnectionLost ( ChatBot . DisconnectReason . LoginRejected , ChatParser . ParseText ( readNextString ( packetData ) ) ) ;
2014-09-04 13:58:49 +02:00
return false ;
}
else if ( packetID = = 0x01 ) //Encryption request
{
2015-11-30 15:30:49 +01:00
string serverID = readNextString ( packetData ) ;
byte [ ] Serverkey = readNextByteArray ( packetData ) ;
byte [ ] token = readNextByteArray ( packetData ) ;
2015-06-20 22:58:18 +02:00
return StartEncryption ( handler . GetUserUUID ( ) , handler . GetSessionID ( ) , token , serverID , Serverkey ) ;
2014-09-04 13:58:49 +02:00
}
else if ( packetID = = 0x02 ) //Login successful
{
ConsoleIO . WriteLineFormatted ( "§8Server is in offline mode." ) ;
login_phase = false ;
2015-10-25 12:20:38 -07:00
2016-03-05 19:12:43 +01:00
if ( forgeInfo ! = null )
{
2015-10-25 12:20:38 -07:00
// Do the forge handshake.
if ( ! CompleteForgeHandshake ( ) )
{
return false ;
}
}
2014-09-04 13:58:49 +02:00
StartUpdating ( ) ;
return true ; //No need to check session or start encryption
}
else handlePacket ( packetID , packetData ) ;
}
}
2015-10-25 12:20:38 -07:00
/// <summary>
/// Completes the Minecraft Forge handshake.
/// </summary>
/// <returns>Whether the handshake was successful.</returns>
private bool CompleteForgeHandshake ( )
{
int packetID = - 1 ;
2015-11-30 15:30:49 +01:00
List < byte > packetData = new List < byte > ( ) ;
2015-10-25 12:20:38 -07:00
while ( fmlHandshakeState ! = FMLHandshakeClientState . DONE )
{
2015-11-30 15:30:49 +01:00
readNextPacket ( ref packetID , packetData ) ;
2015-10-25 12:20:38 -07:00
2016-03-10 13:29:05 +01:00
if ( packetID = = 0x40 ) // Disconnect
2015-10-25 12:20:38 -07:00
{
2015-11-30 15:30:49 +01:00
handler . OnConnectionLost ( ChatBot . DisconnectReason . LoginRejected , ChatParser . ParseText ( readNextString ( packetData ) ) ) ;
2015-10-25 12:20:38 -07:00
return false ;
}
else
{
handlePacket ( packetID , packetData ) ;
}
}
return true ;
}
2014-09-04 13:58:49 +02:00
/// <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 ( ) ;
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
ConsoleIO . WriteLineFormatted ( "§8Crypto keys & hash generated." ) ;
2014-09-04 13:58:49 +02:00
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
2015-07-30 16:47:55 +02:00
byte [ ] key_enc = getArray ( RSAService . Encrypt ( secretKey , false ) ) ;
byte [ ] token_enc = getArray ( RSAService . Encrypt ( token , false ) ) ;
2014-09-04 13:58:49 +02:00
//Encryption Response packet
2015-07-30 16:47:55 +02:00
SendPacket ( 0x01 , concatBytes ( key_enc , token_enc ) ) ;
2014-09-04 13:58:49 +02:00
//Start client-side encryption
2015-06-19 19:40:18 +02:00
s = CryptoHandler . getAesStream ( c . GetStream ( ) , secretKey ) ;
2014-09-04 13:58:49 +02:00
encrypted = true ;
//Process the next packet
int packetID = - 1 ;
2015-11-30 15:30:49 +01:00
List < byte > packetData = new List < byte > ( ) ;
2014-09-04 13:58:49 +02:00
while ( true )
{
2015-11-30 15:30:49 +01:00
readNextPacket ( ref packetID , packetData ) ;
2014-09-04 13:58:49 +02:00
if ( packetID = = 0x00 ) //Login rejected
{
2015-11-30 15:30:49 +01:00
handler . OnConnectionLost ( ChatBot . DisconnectReason . LoginRejected , ChatParser . ParseText ( readNextString ( packetData ) ) ) ;
2014-09-04 13:58:49 +02:00
return false ;
}
else if ( packetID = = 0x02 ) //Login successful
{
login_phase = false ;
2015-10-25 12:20:38 -07:00
if ( forgeInfo ! = null )
{
// Do the forge handshake.
if ( ! CompleteForgeHandshake ( ) )
{
return false ;
}
}
2014-09-04 13:58:49 +02:00
StartUpdating ( ) ;
return true ;
}
else handlePacket ( packetID , packetData ) ;
}
}
2016-11-19 16:06:08 +01:00
/// <summary>
/// Get max length for chat messages
/// </summary>
/// <returns>Max length, in characters</returns>
public int GetMaxChatMessageLength ( )
{
return protocolversion > = MC111Version
? 256
: 100 ;
}
2014-09-04 13:58:49 +02:00
/// <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
{
2018-09-25 07:38:28 -04:00
byte [ ] message_packet = getString ( message ) ;
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . ChatMessage , message_packet ) ;
2014-09-04 13:58:49 +02:00
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
{
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . ClientStatus , new byte [ ] { 0 } ) ;
2014-09-04 13:58:49 +02:00
return true ;
}
catch ( SocketException ) { return false ; }
}
2015-09-29 14:00:44 +02:00
/// <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 ;
2018-09-25 07:38:28 -04:00
// 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" , getString ( brandInfo ) ) ;
}
else
{
return SendPluginChannelPacket ( "MC|Brand" , getString ( brandInfo ) ) ;
}
2015-10-24 22:26:45 -07:00
}
2016-08-26 12:19:25 +02:00
/// <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 ( getString ( language ) ) ;
fields . Add ( viewDistance ) ;
fields . AddRange ( protocolversion > = MC19Version
? 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 ( getVarInt ( mainHand ) ) ;
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . ClientSettings , fields ) ;
2016-08-26 12:19:25 +02:00
}
catch ( SocketException ) { }
return false ;
}
2015-11-27 17:16:33 +01:00
/// <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>
2019-04-09 22:41:39 -07:00
/// <param name="yaw">The new yaw of the player</param>
/// <param name="pitch">The new pitch of the player</param>
2015-11-27 17:16:33 +01:00
/// <returns>True if the location update was successfully sent</returns>
2019-04-09 18:01:00 -07:00
public bool SendLocationUpdate ( Location location , bool onGround , float? yaw = null , float? pitch = null )
2015-11-27 17:16:33 +01:00
{
if ( Settings . TerrainAndMovements )
{
2017-06-10 09:05:06 -07:00
PacketOutgoingType packetType ;
2019-04-09 18:01:00 -07:00
byte [ ] yawpitch = new byte [ 0 ] ;
if ( yaw ! = null & & pitch ! = null )
2017-03-10 23:40:02 +01:00
{
2019-04-09 22:41:39 -07:00
yawpitch = concatBytes ( getFloat ( ( float ) yaw ) , getFloat ( ( float ) pitch ) ) ;
2017-06-10 09:05:06 -07:00
packetType = PacketOutgoingType . PlayerPositionAndLook ;
2017-03-10 23:40:02 +01:00
}
else
{
2017-06-10 09:05:06 -07:00
packetType = PacketOutgoingType . PlayerPosition ;
2017-03-10 23:40:02 +01:00
}
2015-11-27 17:16:33 +01:00
try
{
2017-06-10 09:05:06 -07:00
SendPacket ( packetType , concatBytes (
2016-08-21 15:44:15 +02:00
getDouble ( location . X ) ,
getDouble ( location . Y ) ,
protocolversion < MC18Version
? getDouble ( location . Y + 1.62 )
: new byte [ 0 ] ,
getDouble ( location . Z ) ,
2017-03-10 23:40:02 +01:00
yawpitch ,
2015-11-27 17:16:33 +01:00
new byte [ ] { onGround ? ( byte ) 1 : ( byte ) 0 } ) ) ;
return true ;
}
catch ( SocketException ) { return false ; }
}
else return false ;
}
2015-10-24 22:26:45 -07:00
/// <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 )
{
2015-09-29 14:00:44 +02:00
try
{
2015-10-24 22:26:45 -07:00
// 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 ) ;
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . PluginMessage , concatBytes ( getString ( channel ) , length , data ) ) ;
2015-10-24 22:26:45 -07:00
}
else
{
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . PluginMessage , concatBytes ( getString ( channel ) , data ) ) ;
2015-10-24 22:26:45 -07:00
}
2015-09-29 14:00:44 +02:00
return true ;
}
catch ( SocketException ) { return false ; }
catch ( System . IO . IOException ) { return false ; }
}
2014-09-04 13:58:49 +02:00
/// <summary>
/// Disconnect from the server
/// </summary>
public void Disconnect ( )
{
try
{
2015-06-14 21:43:24 +02:00
c . Close ( ) ;
2014-09-04 13:58:49 +02:00
}
catch ( SocketException ) { }
catch ( System . IO . IOException ) { }
catch ( NullReferenceException ) { }
catch ( ObjectDisposedException ) { }
}
/// <summary>
/// Autocomplete text while typing username or command
/// </summary>
/// <param name="BehindCursor">Text behind cursor</param>
/// <returns>Completed text</returns>
2016-05-14 11:51:02 +02:00
IEnumerable < string > IAutoComplete . AutoComplete ( string BehindCursor )
2014-09-04 13:58:49 +02:00
{
if ( String . IsNullOrEmpty ( BehindCursor ) )
2016-05-14 11:51:02 +02:00
return new string [ ] { } ;
2014-09-04 13:58:49 +02:00
2018-09-25 07:38:28 -04:00
byte [ ] transaction_id = getVarInt ( autocomplete_transaction_id ) ;
2016-03-05 19:12:43 +01:00
byte [ ] assume_command = new byte [ ] { 0x00 } ;
2015-07-30 16:47:55 +02:00
byte [ ] has_position = new byte [ ] { 0x00 } ;
2018-09-25 07:38:28 -04:00
byte [ ] tabcomplete_packet = new byte [ ] { } ;
if ( protocolversion > = MC18Version )
{
if ( protocolversion > = MC17w46aVersion )
{
tabcomplete_packet = concatBytes ( tabcomplete_packet , transaction_id ) ;
tabcomplete_packet = concatBytes ( tabcomplete_packet , getString ( BehindCursor ) ) ;
}
else
{
tabcomplete_packet = concatBytes ( tabcomplete_packet , getString ( BehindCursor ) ) ;
if ( protocolversion > = MC19Version )
{
tabcomplete_packet = concatBytes ( tabcomplete_packet , assume_command ) ;
}
tabcomplete_packet = concatBytes ( tabcomplete_packet , has_position ) ;
}
}
else
{
tabcomplete_packet = concatBytes ( getString ( BehindCursor ) ) ;
}
2014-09-04 13:58:49 +02:00
autocomplete_received = false ;
2016-05-14 11:51:02 +02:00
autocomplete_result . Clear ( ) ;
autocomplete_result . Add ( BehindCursor ) ;
2017-06-10 09:05:06 -07:00
SendPacket ( PacketOutgoingType . TabComplete , tabcomplete_packet ) ;
2014-09-04 13:58:49 +02:00
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 - - ; }
2016-05-14 11:51:02 +02:00
if ( autocomplete_result . Count > 0 )
ConsoleIO . WriteLineFormatted ( "§8" + String . Join ( " " , autocomplete_result ) , false ) ;
2014-09-04 13:58:49 +02:00
return autocomplete_result ;
}
2015-07-30 16:47:55 +02:00
/// <summary>
/// Ping a Minecraft server to get information about the server
/// </summary>
/// <returns>True if ping was successful</returns>
2015-10-23 16:54:36 -07:00
public static bool doPing ( string host , int port , ref int protocolversion , ref ForgeInfo forgeInfo )
2015-07-30 16:47:55 +02:00
{
string version = "" ;
TcpClient tcp = ProxyHandler . newTcpClient ( host , port ) ;
tcp . ReceiveBufferSize = 1024 * 1024 ;
byte [ ] packet_id = getVarInt ( 0 ) ;
2017-03-14 23:33:33 +01:00
byte [ ] protocol_version = getVarInt ( - 1 ) ;
2015-07-30 16:47:55 +02:00
byte [ ] server_port = BitConverter . GetBytes ( ( ushort ) port ) ; Array . Reverse ( server_port ) ;
byte [ ] next_state = getVarInt ( 1 ) ;
2018-09-25 07:38:28 -04:00
byte [ ] packet = concatBytes ( packet_id , protocol_version , getString ( host ) , server_port , next_state ) ;
2015-07-30 16:47:55 +02:00
byte [ ] tosend = concatBytes ( getVarInt ( packet . Length ) , packet ) ;
tcp . Client . Send ( tosend , SocketFlags . None ) ;
byte [ ] status_request = getVarInt ( 0 ) ;
byte [ ] request_packet = concatBytes ( getVarInt ( status_request . Length ) , status_request ) ;
tcp . Client . Send ( request_packet , SocketFlags . None ) ;
Protocol18Handler ComTmp = new Protocol18Handler ( tcp ) ;
int packetLength = ComTmp . readNextVarIntRAW ( ) ;
if ( packetLength > 0 ) //Read Response length
{
2015-11-30 15:30:49 +01:00
List < byte > packetData = new List < byte > ( ComTmp . readDataRAW ( packetLength ) ) ;
if ( readNextVarInt ( packetData ) = = 0x00 ) //Read Packet ID
2015-07-30 16:47:55 +02:00
{
2015-11-30 15:30:49 +01:00
string result = readNextString ( packetData ) ; //Get the Json data
2015-10-21 22:40:50 -07:00
2015-07-30 16:47:55 +02:00
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" ) )
{
2015-10-23 16:54:36 -07:00
Json . JSONData versionData = jsonData . Properties [ "version" ] ;
2015-07-30 16:47:55 +02:00
//Retrieve display name of the Minecraft version
2015-10-23 16:54:36 -07:00
if ( versionData . Properties . ContainsKey ( "name" ) )
version = versionData . Properties [ "name" ] . StringValue ;
2015-07-30 16:47:55 +02:00
//Retrieve protocol version number for handling this server
2015-10-23 16:54:36 -07:00
if ( versionData . Properties . ContainsKey ( "protocol" ) )
protocolversion = atoi ( versionData . Properties [ "protocol" ] . StringValue ) ;
2015-07-30 16:47:55 +02:00
//Automatic fix for BungeeCord 1.8 reporting itself as 1.7...
2015-08-21 17:16:27 +02:00
if ( protocolversion < 47 & & version . Split ( ' ' , '/' ) . Contains ( "1.8" ) )
2015-07-30 16:47:55 +02:00
protocolversion = ProtocolHandler . MCVer2ProtocolVersion ( "1.8.0" ) ;
2015-10-23 16:54:36 -07:00
// Check for forge on the server.
if ( jsonData . Properties . ContainsKey ( "modinfo" ) & & jsonData . Properties [ "modinfo" ] . Type = = Json . JSONData . DataType . Object )
{
Json . JSONData modData = jsonData . Properties [ "modinfo" ] ;
if ( modData . Properties . ContainsKey ( "type" ) & & modData . Properties [ "type" ] . StringValue = = "FML" )
{
forgeInfo = new ForgeInfo ( modData ) ;
2016-03-10 13:29:05 +01:00
if ( forgeInfo . Mods . Any ( ) )
2015-10-23 16:54:36 -07:00
{
2016-03-10 13:29:05 +01:00
if ( Settings . DebugMessages )
{
ConsoleIO . WriteLineFormatted ( "§8Server is running Forge. Mod list:" ) ;
foreach ( ForgeInfo . ForgeMod mod in forgeInfo . Mods )
{
ConsoleIO . WriteLineFormatted ( "§8 " + mod . ToString ( ) ) ;
}
}
else ConsoleIO . WriteLineFormatted ( "§8Server is running Forge." ) ;
2015-10-23 16:54:36 -07:00
}
2016-03-10 13:29:05 +01:00
else forgeInfo = null ;
2015-10-23 16:54:36 -07:00
}
}
2016-03-10 13:29:05 +01:00
ConsoleIO . WriteLineFormatted ( "§8Server version : " + version + " (protocol v" + protocolversion + ( forgeInfo ! = null ? ", with Forge)." : ")." ) ) ;
2015-07-30 16:47:55 +02:00
return true ;
}
}
}
}
return false ;
}
2014-09-04 13:58:49 +02:00
}
}