Add support for Minecraft 1.9

Currently no terrain handling but anything else should work.

Related: #125
This commit is contained in:
ORelio 2016-03-05 19:12:43 +01:00
parent 578a6170ef
commit a82c6823af
3 changed files with 137 additions and 33 deletions

View file

@ -10,7 +10,7 @@ using MinecraftClient.Protocol.Handlers.Forge;
namespace MinecraftClient namespace MinecraftClient
{ {
/// <summary> /// <summary>
/// Minecraft Console Client by ORelio (c) 2012-2014. /// Minecraft Console Client by ORelio & Contributors (c) 2012-2016.
/// Allows to connect to any Minecraft server, send and receive text, automated scripts. /// Allows to connect to any Minecraft server, send and receive text, automated scripts.
/// This source code is released under the CDDL 1.0 License. /// This source code is released under the CDDL 1.0 License.
/// </summary> /// </summary>
@ -20,9 +20,9 @@ namespace MinecraftClient
private static McTcpClient Client; private static McTcpClient Client;
public static string[] startupargs; public static string[] startupargs;
public const string Version = "1.8.2"; public const string Version = "1.9.0 BETA";
public const string MCLowestVersion = "1.4.6"; public const string MCLowestVersion = "1.4.6";
public const string MCHighestVersion = "1.8.8"; public const string MCHighestVersion = "1.9.0";
private static Thread offlinePrompt = null; private static Thread offlinePrompt = null;
private static bool useMcVersionOnce = false; private static bool useMcVersionOnce = false;

View file

@ -34,6 +34,9 @@ namespace MinecraftClient.Protocol.Handlers
this.c = Client; this.c = Client;
this.protocolversion = ProtocolVersion; this.protocolversion = ProtocolVersion;
this.handler = Handler; this.handler = Handler;
if (Settings.TerrainAndMovements)
ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version.");
} }
private Protocol16Handler(TcpClient Client) private Protocol16Handler(TcpClient Client)

View file

@ -13,12 +13,13 @@ using MinecraftClient.Mapping;
namespace MinecraftClient.Protocol.Handlers namespace MinecraftClient.Protocol.Handlers
{ {
/// <summary> /// <summary>
/// Implementation for Minecraft 1.7.X and 1.8.X Protocols /// Implementation for Minecraft 1.7.X, 1.8.X, 1.9.X Protocols
/// </summary> /// </summary>
class Protocol18Handler : IMinecraftCom class Protocol18Handler : IMinecraftCom
{ {
private const int MC18Version = 47; private const int MC18Version = 47;
private const int MC19Version = 107;
private int compression_treshold = 0; private int compression_treshold = 0;
private bool autocomplete_received = false; private bool autocomplete_received = false;
@ -44,6 +45,9 @@ namespace MinecraftClient.Protocol.Handlers
this.protocolversion = ProtocolVersion; this.protocolversion = ProtocolVersion;
this.handler = Handler; this.handler = Handler;
this.forgeInfo = ForgeInfo; this.forgeInfo = ForgeInfo;
if (Settings.TerrainAndMovements && protocolversion > MC18Version)
ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version.");
} }
private Protocol18Handler(TcpClient Client) private Protocol18Handler(TcpClient Client)
@ -125,6 +129,85 @@ namespace MinecraftClient.Protocol.Handlers
packetID = readNextVarInt(packetData); //Packet ID packetID = readNextVarInt(packetData); //Packet ID
} }
/// <summary>
/// Abstract incoming packet numbering
/// </summary>
private enum PacketIncomingType
{
KeepAlive,
JoinGame,
ChatMessage,
PlayerPositionAndLook,
ChunkData,
MultiBlockChange,
BlockChange,
MapChunkBulk,
UnloadChunk,
PlayerListUpdate,
TabCompleteResult,
PluginMessage,
KickPacket,
NetworkCompressionTreshold,
ResourcePackSend,
UnknownPacket
}
/// <summary>
/// Get abstract numbering ov the specified packet ID
/// </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;
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;
}
}
else
{
switch (packetID)
{
case 0x1F: return PacketIncomingType.KeepAlive;
case 0x23: return PacketIncomingType.JoinGame;
case 0x0F: return PacketIncomingType.ChatMessage;
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;
}
}
}
/// <summary> /// <summary>
/// Handle the given packet /// Handle the given packet
/// </summary> /// </summary>
@ -148,15 +231,15 @@ namespace MinecraftClient.Protocol.Handlers
} }
// Regular in-game packets // Regular in-game packets
switch (packetID) switch (getPacketIncomingType(packetID, protocolversion))
{ {
case 0x00: //Keep-Alive case PacketIncomingType.KeepAlive:
SendPacket(0x00, packetData); SendPacket(protocolversion >= MC19Version ? 0x0B : 0x00, packetData);
break; break;
case 0x01: //Join game case PacketIncomingType.JoinGame:
handler.OnGameJoined(); handler.OnGameJoined();
break; break;
case 0x02: //Chat message case PacketIncomingType.ChatMessage:
string message = readNextString(packetData); string message = readNextString(packetData);
try try
{ {
@ -169,7 +252,7 @@ namespace MinecraftClient.Protocol.Handlers
catch (ArgumentOutOfRangeException) { /* No message type */ } catch (ArgumentOutOfRangeException) { /* No message type */ }
handler.OnTextReceived(ChatParser.ParseText(message)); handler.OnTextReceived(ChatParser.ParseText(message));
break; break;
case 0x08: //Player Position and Look case PacketIncomingType.PlayerPositionAndLook:
if (Settings.TerrainAndMovements) if (Settings.TerrainAndMovements)
{ {
double x = readNextDouble(packetData); double x = readNextDouble(packetData);
@ -184,20 +267,23 @@ namespace MinecraftClient.Protocol.Handlers
location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z; location.Z = (locMask & 1 << 2) != 0 ? location.Z + z : z;
handler.UpdateLocation(location); handler.UpdateLocation(location);
if (protocolversion >= MC19Version)
readNextVarInt(packetData);
} }
break; break;
case 0x21: //Chunk Data case PacketIncomingType.ChunkData:
if (Settings.TerrainAndMovements) if (Settings.TerrainAndMovements)
{ {
int chunkX = readNextInt(packetData); int chunkX = readNextInt(packetData);
int chunkZ = readNextInt(packetData); int chunkZ = readNextInt(packetData);
bool chunksContinuous = readNextBool(packetData); bool chunksContinuous = readNextBool(packetData);
ushort chunkMask = readNextUShort(packetData); ushort chunkMask = protocolversion >= MC19Version ? (ushort)readNextVarInt(packetData) : readNextUShort(packetData);
int dataSize = readNextVarInt(packetData); int dataSize = readNextVarInt(packetData);
ProcessChunkColumnData(chunkX, chunkZ, chunkMask, false, chunksContinuous, packetData); ProcessChunkColumnData(chunkX, chunkZ, chunkMask, false, chunksContinuous, packetData);
} }
break; break;
case 0x22: //Multi Block Change case PacketIncomingType.MultiBlockChange:
if (Settings.TerrainAndMovements) if (Settings.TerrainAndMovements)
{ {
int chunkX = readNextInt(packetData); int chunkX = readNextInt(packetData);
@ -214,12 +300,12 @@ namespace MinecraftClient.Protocol.Handlers
} }
} }
break; break;
case 0x23: //Block Change case PacketIncomingType.BlockChange:
if (Settings.TerrainAndMovements) if (Settings.TerrainAndMovements)
handler.GetWorld().SetBlock(Location.FromLong(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); handler.GetWorld().SetBlock(Location.FromLong(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData)));
break; break;
case 0x26: //Map Chunk Bulk case PacketIncomingType.MapChunkBulk:
if (Settings.TerrainAndMovements) if (protocolversion < MC19Version && Settings.TerrainAndMovements)
{ {
bool hasSkyLight = readNextBool(packetData); bool hasSkyLight = readNextBool(packetData);
int chunkCount = readNextVarInt(packetData); int chunkCount = readNextVarInt(packetData);
@ -240,7 +326,15 @@ namespace MinecraftClient.Protocol.Handlers
ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], hasSkyLight, true, packetData); ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], hasSkyLight, true, packetData);
} }
break; break;
case 0x38: //Player List update 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:
if (protocolversion >= MC18Version) if (protocolversion >= MC18Version)
{ {
int action = readNextVarInt(packetData); int action = readNextVarInt(packetData);
@ -274,7 +368,7 @@ namespace MinecraftClient.Protocol.Handlers
else handler.OnPlayerLeave(FakeUUID); else handler.OnPlayerLeave(FakeUUID);
} }
break; break;
case 0x3A: //Tab-Complete Result case PacketIncomingType.TabCompleteResult:
int autocomplete_count = readNextVarInt(packetData); int autocomplete_count = readNextVarInt(packetData);
string tab_list = ""; string tab_list = "";
for (int i = 0; i < autocomplete_count; i++) for (int i = 0; i < autocomplete_count; i++)
@ -288,7 +382,7 @@ namespace MinecraftClient.Protocol.Handlers
if (tab_list.Length > 0) if (tab_list.Length > 0)
ConsoleIO.WriteLineFormatted("§8" + tab_list, false); ConsoleIO.WriteLineFormatted("§8" + tab_list, false);
break; break;
case 0x3F: //Plugin message. case PacketIncomingType.PluginMessage:
String channel = readNextString(packetData); String channel = readNextString(packetData);
if (protocolversion < MC18Version) if (protocolversion < MC18Version)
{ {
@ -308,6 +402,7 @@ namespace MinecraftClient.Protocol.Handlers
// The remaining data in the array is the entire payload of the packet. // The remaining data in the array is the entire payload of the packet.
handler.OnPluginChannelMessage(channel, packetData.ToArray()); handler.OnPluginChannelMessage(channel, packetData.ToArray());
#region Forge Login
if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE) if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE)
{ {
if (channel == "FML|HS") if (channel == "FML|HS")
@ -432,20 +527,22 @@ namespace MinecraftClient.Protocol.Handlers
} }
} }
} }
#endregion
return false; return false;
case 0x40: //Kick Packet case PacketIncomingType.KickPacket:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(packetData))); handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(packetData)));
return false; return false;
case 0x46: //Network Compression Treshold Info case PacketIncomingType.NetworkCompressionTreshold:
if (protocolversion >= MC18Version) if (protocolversion >= MC18Version && protocolversion < MC19Version)
compression_treshold = readNextVarInt(packetData); compression_treshold = readNextVarInt(packetData);
break; break;
case 0x48: //Resource Pack Send case PacketIncomingType.ResourcePackSend:
string url = readNextString(packetData); string url = readNextString(packetData);
string hash = readNextString(packetData); string hash = readNextString(packetData);
//Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory
SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); SendPacket(protocolversion >= MC19Version ? 0x16 : 0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3)));
SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); SendPacket(protocolversion >= MC19Version ? 0x16 : 0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0)));
break; break;
default: default:
return false; //Ignored packet return false; //Ignored packet
@ -465,7 +562,7 @@ namespace MinecraftClient.Protocol.Handlers
private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List<byte> cache) private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List<byte> cache)
{ {
if (chunksContinuous && chunkMask == 0) if (protocolversion >= MC19Version && chunksContinuous && chunkMask == 0)
{ {
//Unload the entire chunk column //Unload the entire chunk column
handler.GetWorld()[chunkX, chunkZ] = null; handler.GetWorld()[chunkX, chunkZ] = null;
@ -978,7 +1075,8 @@ namespace MinecraftClient.Protocol.Handlers
ConsoleIO.WriteLineFormatted("§8Server is in offline mode."); ConsoleIO.WriteLineFormatted("§8Server is in offline mode.");
login_phase = false; login_phase = false;
if (forgeInfo != null) { if (forgeInfo != null)
{
// Do the forge handshake. // Do the forge handshake.
if (!CompleteForgeHandshake()) if (!CompleteForgeHandshake())
{ {
@ -1099,7 +1197,7 @@ namespace MinecraftClient.Protocol.Handlers
byte[] message_val = Encoding.UTF8.GetBytes(message); byte[] message_val = Encoding.UTF8.GetBytes(message);
byte[] message_len = getVarInt(message_val.Length); byte[] message_len = getVarInt(message_val.Length);
byte[] message_packet = concatBytes(message_len, message_val); byte[] message_packet = concatBytes(message_len, message_val);
SendPacket(0x01, message_packet); SendPacket(protocolversion >= MC19Version ? 0x02 : 0x01, message_packet);
return true; return true;
} }
catch (SocketException) { return false; } catch (SocketException) { return false; }
@ -1116,7 +1214,7 @@ namespace MinecraftClient.Protocol.Handlers
{ {
try try
{ {
SendPacket(0x16, new byte[] { 0 }); SendPacket(protocolversion >= MC19Version ? 0x03 : 0x16, new byte[] { 0 });
return true; return true;
} }
catch (SocketException) { return false; } catch (SocketException) { return false; }
@ -1149,7 +1247,7 @@ namespace MinecraftClient.Protocol.Handlers
{ {
try try
{ {
SendPacket(0x04, concatBytes( SendPacket(protocolversion >= MC19Version ? 0x0C : 0x04, concatBytes(
getDouble(location.X), getDouble(location.Y), getDouble(location.Z), getDouble(location.X), getDouble(location.Y), getDouble(location.Z),
new byte[] { onGround ? (byte)1 : (byte)0 })); new byte[] { onGround ? (byte)1 : (byte)0 }));
return true; return true;
@ -1180,7 +1278,7 @@ namespace MinecraftClient.Protocol.Handlers
} }
else else
{ {
SendPacket(0x17, concatBytes(getString(channel), data)); SendPacket(protocolversion >= MC19Version ? 0x09 : 0x17, concatBytes(getString(channel), data));
} }
return true; return true;
@ -1218,14 +1316,17 @@ namespace MinecraftClient.Protocol.Handlers
byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor); byte[] tocomplete_val = Encoding.UTF8.GetBytes(BehindCursor);
byte[] tocomplete_len = getVarInt(tocomplete_val.Length); byte[] tocomplete_len = getVarInt(tocomplete_val.Length);
byte[] assume_command = new byte[] { 0x00 };
byte[] has_position = new byte[] { 0x00 }; byte[] has_position = new byte[] { 0x00 };
byte[] tabcomplete_packet = protocolversion >= MC18Version byte[] tabcomplete_packet = protocolversion >= MC18Version
? concatBytes(tocomplete_len, tocomplete_val, has_position) ? protocolversion >= MC19Version
? concatBytes(tocomplete_len, tocomplete_val, assume_command, has_position)
: concatBytes(tocomplete_len, tocomplete_val, has_position)
: concatBytes(tocomplete_len, tocomplete_val); : concatBytes(tocomplete_len, tocomplete_val);
autocomplete_received = false; autocomplete_received = false;
autocomplete_result = BehindCursor; autocomplete_result = BehindCursor;
SendPacket(0x14, tabcomplete_packet); SendPacket(protocolversion >= MC19Version ? 0x01 : 0x14, tabcomplete_packet);
int wait_left = 50; //do not wait more than 5 seconds (50 * 100 ms) 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--; } while (wait_left > 0 && !autocomplete_received) { System.Threading.Thread.Sleep(100); wait_left--; }