Refactoring to asynchronous. (partially completed)

This commit is contained in:
BruceChen 2022-12-20 22:41:14 +08:00
parent 7ee08092d4
commit 096ea0c70c
72 changed files with 6033 additions and 5080 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace MinecraftClient.Protocol.Handlers.Forge
{
@ -11,16 +12,22 @@ namespace MinecraftClient.Protocol.Handlers.Forge
/// <summary>
/// Represents an individual forge mod.
/// </summary>
public class ForgeMod
public record ForgeMod
{
public ForgeMod(String ModID, String Version)
public ForgeMod(string? modID, string? version)
{
this.ModID = ModID;
this.Version = Version;
ModID = modID;
Version = ModMarker = version;
}
public readonly String ModID;
public readonly String Version;
[JsonPropertyName("modId")]
public string? ModID { init; get; }
[JsonPropertyName("version")]
public string? Version { init; get; }
[JsonPropertyName("modmarker")]
public string? ModMarker { init; get; }
public override string ToString()
{
@ -138,5 +145,16 @@ namespace MinecraftClient.Protocol.Handlers.Forge
throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!");
}
}
/// <summary>
/// Create a new ForgeInfo from the given data.
/// </summary>
/// <param name="data">The modinfo JSON tag.</param>
/// <param name="fmlVersion">Forge protocol version</param>
internal ForgeInfo(ForgeMod[] mods, FMLVersion fmlVersion)
{
Mods = new(mods);
Version = fmlVersion;
}
}
}

View file

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MinecraftClient.Protocol.PacketPipeline;
namespace MinecraftClient.Protocol.Handlers.packet.s2c
{
@ -8,7 +10,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
private static int RootIdx;
private static CommandNode[] Nodes = Array.Empty<CommandNode>();
public static void Read(DataTypes dataTypes, Queue<byte> packetData)
public static async Task Read(DataTypes dataTypes, PacketStream packetData)
{
int count = dataTypes.ReadNextVarInt(packetData);
Nodes = new CommandNode[count];
@ -23,7 +25,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
int redirectNode = ((flags & 0x08) > 0) ? dataTypes.ReadNextVarInt(packetData) : -1;
string? name = ((flags & 0x03) == 1 || (flags & 0x03) == 2) ? dataTypes.ReadNextString(packetData) : null;
string? name = ((flags & 0x03) == 1 || (flags & 0x03) == 2) ? (await dataTypes.ReadNextStringAsync(packetData)) : null;
int paserId = ((flags & 0x03) == 2) ? dataTypes.ReadNextVarInt(packetData) : -1;
Paser? paser = null;
@ -50,7 +52,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
};
}
string? suggestionsType = ((flags & 0x10) > 0) ? dataTypes.ReadNextString(packetData) : null;
string? suggestionsType = ((flags & 0x10) > 0) ? (await dataTypes.ReadNextStringAsync(packetData)) : null;
Nodes[i] = new(flags, childs, redirectNode, name, paser, suggestionsType);
}
@ -158,7 +160,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
internal class PaserEmpty : Paser
{
public PaserEmpty(DataTypes dataTypes, Queue<byte> packetData) { }
public PaserEmpty(DataTypes dataTypes, PacketStream packetData) { }
public override bool Check(string text)
{
@ -181,7 +183,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
private byte Flags;
private float Min = float.MinValue, Max = float.MaxValue;
public PaserFloat(DataTypes dataTypes, Queue<byte> packetData)
public PaserFloat(DataTypes dataTypes, PacketStream packetData)
{
Flags = dataTypes.ReadNextByte(packetData);
if ((Flags & 0x01) > 0)
@ -211,7 +213,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
private byte Flags;
private double Min = double.MinValue, Max = double.MaxValue;
public PaserDouble(DataTypes dataTypes, Queue<byte> packetData)
public PaserDouble(DataTypes dataTypes, PacketStream packetData)
{
Flags = dataTypes.ReadNextByte(packetData);
if ((Flags & 0x01) > 0)
@ -241,7 +243,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
private byte Flags;
private int Min = int.MinValue, Max = int.MaxValue;
public PaserInteger(DataTypes dataTypes, Queue<byte> packetData)
public PaserInteger(DataTypes dataTypes, PacketStream packetData)
{
Flags = dataTypes.ReadNextByte(packetData);
if ((Flags & 0x01) > 0)
@ -271,7 +273,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
private byte Flags;
private long Min = long.MinValue, Max = long.MaxValue;
public PaserLong(DataTypes dataTypes, Queue<byte> packetData)
public PaserLong(DataTypes dataTypes, PacketStream packetData)
{
Flags = dataTypes.ReadNextByte(packetData);
if ((Flags & 0x01) > 0)
@ -302,7 +304,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
private enum StringType { SINGLE_WORD, QUOTABLE_PHRASE, GREEDY_PHRASE };
public PaserString(DataTypes dataTypes, Queue<byte> packetData)
public PaserString(DataTypes dataTypes, PacketStream packetData)
{
Type = (StringType)dataTypes.ReadNextVarInt(packetData);
}
@ -327,7 +329,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
{
private byte Flags;
public PaserEntity(DataTypes dataTypes, Queue<byte> packetData)
public PaserEntity(DataTypes dataTypes, PacketStream packetData)
{
Flags = dataTypes.ReadNextByte(packetData);
}
@ -351,7 +353,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
internal class PaserBlockPos : Paser
{
public PaserBlockPos(DataTypes dataTypes, Queue<byte> packetData) { }
public PaserBlockPos(DataTypes dataTypes, PacketStream packetData) { }
public override bool Check(string text)
{
@ -372,7 +374,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
internal class PaserColumnPos : Paser
{
public PaserColumnPos(DataTypes dataTypes, Queue<byte> packetData) { }
public PaserColumnPos(DataTypes dataTypes, PacketStream packetData) { }
public override bool Check(string text)
{
@ -393,7 +395,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
internal class PaserVec3 : Paser
{
public PaserVec3(DataTypes dataTypes, Queue<byte> packetData) { }
public PaserVec3(DataTypes dataTypes, PacketStream packetData) { }
public override bool Check(string text)
{
@ -414,7 +416,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
internal class PaserVec2 : Paser
{
public PaserVec2(DataTypes dataTypes, Queue<byte> packetData) { }
public PaserVec2(DataTypes dataTypes, PacketStream packetData) { }
public override bool Check(string text)
{
@ -435,7 +437,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
internal class PaserRotation : Paser
{
public PaserRotation(DataTypes dataTypes, Queue<byte> packetData) { }
public PaserRotation(DataTypes dataTypes, PacketStream packetData) { }
public override bool Check(string text)
{
@ -455,7 +457,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
internal class PaserMessage : Paser
{
public PaserMessage(DataTypes dataTypes, Queue<byte> packetData) { }
public PaserMessage(DataTypes dataTypes, PacketStream packetData) { }
public override bool Check(string text)
{
@ -477,7 +479,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
{
private byte Flags;
public PaserScoreHolder(DataTypes dataTypes, Queue<byte> packetData)
public PaserScoreHolder(DataTypes dataTypes, PacketStream packetData)
{
Flags = dataTypes.ReadNextByte(packetData);
}
@ -502,7 +504,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
{
private bool Decimals;
public PaserRange(DataTypes dataTypes, Queue<byte> packetData)
public PaserRange(DataTypes dataTypes, PacketStream packetData)
{
Decimals = dataTypes.ReadNextBool(packetData);
}
@ -527,9 +529,11 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
{
private string Registry;
public PaserResourceOrTag(DataTypes dataTypes, Queue<byte> packetData)
public PaserResourceOrTag(DataTypes dataTypes, PacketStream packetData)
{
Registry = dataTypes.ReadNextString(packetData);
var task = dataTypes.ReadNextStringAsync(packetData);
task.Wait();
Registry = task.Result;
}
public override bool Check(string text)
@ -552,9 +556,11 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
{
private string Registry;
public PaserResource(DataTypes dataTypes, Queue<byte> packetData)
public PaserResource(DataTypes dataTypes, PacketStream packetData)
{
Registry = dataTypes.ReadNextString(packetData);
var task = dataTypes.ReadNextStringAsync(packetData);
task.Wait();
Registry = task.Result;
}
public override bool Check(string text)

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Protocol.PacketPipeline;
using MinecraftClient.Scripting;
using static MinecraftClient.Protocol.Handlers.Protocol18Handler;
namespace MinecraftClient.Protocol.Handlers
{
@ -54,23 +57,23 @@ namespace MinecraftClient.Protocol.Handlers
/// Completes the Minecraft Forge handshake (Forge Protocol version 1: FML)
/// </summary>
/// <returns>Whether the handshake was successful.</returns>
public bool CompleteForgeHandshake()
public async Task<bool> CompleteForgeHandshake(SocketWrapper socketWrapper)
{
if (ForgeEnabled() && forgeInfo!.Version == FMLVersion.FML)
{
while (fmlHandshakeState != FMLHandshakeClientState.DONE)
{
(int packetID, Queue<byte> packetData) = protocol18.ReadNextPacket();
(int packetID, PacketStream packetStream) = await socketWrapper.GetNextPacket(handleCompress: true);
if (packetID == 0x40) // Disconnect
{
mcHandler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
mcHandler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(await dataTypes.ReadNextStringAsync(packetStream)));
return false;
}
else
{
// Send back regular packet to the vanilla protocol handler
protocol18.HandlePacket(packetID, packetData);
await protocol18.HandlePacket(packetID, packetStream);
}
}
}
@ -82,7 +85,7 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
/// <param name="packetData">Packet data to read from</param>
/// <returns>Length from packet data</returns>
public int ReadNextVarShort(Queue<byte> packetData)
public int ReadNextVarShort(PacketStream packetData)
{
if (ForgeEnabled())
{
@ -103,10 +106,11 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="packetData">Plugin message data</param>
/// <param name="currentDimension">Current world dimension</param>
/// <returns>TRUE if the plugin message was recognized and handled</returns>
public bool HandlePluginMessage(string channel, Queue<byte> packetData, ref int currentDimension)
public async Task<Tuple<bool, int>> HandlePluginMessage(string channel, byte[] packetDataArr, int currentDimension)
{
if (ForgeEnabled() && forgeInfo!.Version == FMLVersion.FML && fmlHandshakeState != FMLHandshakeClientState.DONE)
{
Queue<byte> packetData = new(packetDataArr);
if (channel == "FML|HS")
{
FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)dataTypes.ReadNextByte(packetData);
@ -114,21 +118,21 @@ namespace MinecraftClient.Protocol.Handlers
if (discriminator == FMLHandshakeDiscriminator.HandshakeReset)
{
fmlHandshakeState = FMLHandshakeClientState.START;
return true;
return new(true, currentDimension);
}
switch (fmlHandshakeState)
{
case FMLHandshakeClientState.START:
if (discriminator != FMLHandshakeDiscriminator.ServerHello)
return false;
return new(false, currentDimension);
// 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" };
protocol18.SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels)));
await protocol18.SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels)));
byte fmlProtocolVersion = dataTypes.ReadNextByte(packetData);
@ -139,7 +143,7 @@ namespace MinecraftClient.Protocol.Handlers
currentDimension = dataTypes.ReadNextInt(packetData);
// Tell the server we're running the same version.
SendForgeHandshakePacket(FMLHandshakeDiscriminator.ClientHello, new byte[] { fmlProtocolVersion });
await SendForgeHandshakePacket(FMLHandshakeDiscriminator.ClientHello, new byte[] { fmlProtocolVersion });
// Then tell the server that we're running the same mods.
if (Settings.Config.Logging.DebugMessages)
@ -148,17 +152,17 @@ namespace MinecraftClient.Protocol.Handlers
for (int i = 0; i < forgeInfo.Mods.Count; i++)
{
ForgeInfo.ForgeMod mod = forgeInfo.Mods[i];
mods[i] = dataTypes.ConcatBytes(dataTypes.GetString(mod.ModID), dataTypes.GetString(mod.Version));
mods[i] = dataTypes.ConcatBytes(dataTypes.GetString(mod.ModID!), dataTypes.GetString(mod.Version ?? mod.ModMarker ?? string.Empty));
}
SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList,
await SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList,
dataTypes.ConcatBytes(dataTypes.GetVarInt(forgeInfo.Mods.Count), dataTypes.ConcatBytes(mods)));
fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA;
return true;
return new(true, currentDimension);
case FMLHandshakeClientState.WAITINGSERVERDATA:
if (discriminator != FMLHandshakeDiscriminator.ModList)
return false;
return new(false, currentDimension);
Thread.Sleep(2000);
@ -167,16 +171,16 @@ namespace MinecraftClient.Protocol.Handlers
// Tell the server that yes, we are OK with the mods it has
// even though we don't actually care what mods it has.
SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
await SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
new byte[] { (byte)FMLHandshakeClientState.WAITINGSERVERDATA });
fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERCOMPLETE;
return false;
return new(false, currentDimension);
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;
return new(false, currentDimension);
if (protocolversion < Protocol18Handler.MC_1_8_Version)
{
@ -202,34 +206,34 @@ namespace MinecraftClient.Protocol.Handlers
fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
}
return false;
return new(false, currentDimension);
case FMLHandshakeClientState.PENDINGCOMPLETE:
// The server will ask us to accept the registries.
// Just say yes.
if (discriminator != FMLHandshakeDiscriminator.HandshakeAck)
return false;
return new(false, currentDimension);
if (Settings.Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted("§8" + Translations.forge_accept_registry, acceptnewlines: true);
SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
await SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
new byte[] { (byte)FMLHandshakeClientState.PENDINGCOMPLETE });
fmlHandshakeState = FMLHandshakeClientState.COMPLETE;
return true;
return new(true, currentDimension);
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,
await SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
new byte[] { (byte)FMLHandshakeClientState.COMPLETE });
if (Settings.Config.Logging.DebugMessages)
ConsoleIO.WriteLine(Translations.forge_complete);
fmlHandshakeState = FMLHandshakeClientState.DONE;
return true;
return new(true, currentDimension);
}
}
}
return false;
return new(false, currentDimension);
}
/// <summary>
@ -239,8 +243,9 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="packetData">Plugin message data</param>
/// <param name="responseData">Response data to return to server</param>
/// <returns>TRUE/FALSE depending on whether the packet was understood or not</returns>
public bool HandleLoginPluginRequest(string channel, Queue<byte> packetData, ref List<byte> responseData)
public async Task<Tuple<bool, List<byte>>> HandleLoginPluginRequest(string channel, PacketStream packetData)
{
List<byte> responseData = new();
if (ForgeEnabled() && forgeInfo!.Version == FMLVersion.FML2 && channel == "fml:loginwrapper")
{
// Forge Handshake handler source code used to implement the FML2 packets:
@ -278,8 +283,8 @@ namespace MinecraftClient.Protocol.Handlers
// The content of each message is mapped into a class inside FMLHandshakeMessages.java
// FMLHandshakeHandler will then process the packet, e.g. handleServerModListOnClient() for Server Mod List.
string fmlChannel = dataTypes.ReadNextString(packetData);
dataTypes.ReadNextVarInt(packetData); // Packet length
string fmlChannel = await dataTypes.ReadNextStringAsync(packetData);
dataTypes.SkipNextVarInt(packetData); // Packet length
int packetID = dataTypes.ReadNextVarInt(packetData);
if (fmlChannel == "fml:handshake")
@ -308,17 +313,17 @@ namespace MinecraftClient.Protocol.Handlers
List<string> mods = new();
int modCount = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < modCount; i++)
mods.Add(dataTypes.ReadNextString(packetData));
mods.Add(await dataTypes.ReadNextStringAsync(packetData));
Dictionary<string, string> channels = new();
int channelCount = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < channelCount; i++)
channels.Add(dataTypes.ReadNextString(packetData), dataTypes.ReadNextString(packetData));
channels.Add(await dataTypes.ReadNextStringAsync(packetData), await dataTypes.ReadNextStringAsync(packetData));
List<string> registries = new();
int registryCount = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < registryCount; i++)
registries.Add(dataTypes.ReadNextString(packetData));
registries.Add(await dataTypes.ReadNextStringAsync(packetData));
// Server Mod List Reply: FMLHandshakeMessages.java > C2SModListReply > encode()
//
@ -372,7 +377,7 @@ namespace MinecraftClient.Protocol.Handlers
if (Settings.Config.Logging.DebugMessages)
{
string registryName = dataTypes.ReadNextString(packetData);
string registryName = await dataTypes.ReadNextStringAsync(packetData);
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_registry, registryName));
}
@ -391,7 +396,7 @@ namespace MinecraftClient.Protocol.Handlers
if (Settings.Config.Logging.DebugMessages)
{
string configName = dataTypes.ReadNextString(packetData);
string configName = await dataTypes.ReadNextStringAsync(packetData);
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_config, configName));
}
@ -408,11 +413,10 @@ namespace MinecraftClient.Protocol.Handlers
if (fmlResponseReady)
{
// Wrap our FML packet into a LoginPluginResponse payload
responseData.Clear();
responseData.AddRange(dataTypes.GetString(fmlChannel));
responseData.AddRange(dataTypes.GetVarInt(fmlResponsePacket.Count));
responseData.AddRange(fmlResponsePacket);
return true;
return new(true, responseData);
}
}
else if (Settings.Config.Logging.DebugMessages)
@ -420,7 +424,7 @@ namespace MinecraftClient.Protocol.Handlers
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_fml2_unknown_channel, fmlChannel));
}
}
return false;
return new(false, responseData);
}
/// <summary>
@ -428,9 +432,9 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
/// <param name="discriminator">Discriminator to use.</param>
/// <param name="data">packet Data</param>
private void SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, byte[] data)
private async Task SendForgeHandshakePacket(FMLHandshakeDiscriminator discriminator, byte[] data)
{
protocol18.SendPluginChannelPacket("FML|HS", dataTypes.ConcatBytes(new byte[] { (byte)discriminator }, data));
await protocol18.SendPluginChannelPacket("FML|HS", dataTypes.ConcatBytes(new byte[] { (byte)discriminator }, data));
}
/// <summary>
@ -439,10 +443,10 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="jsonData">JSON data returned by the server</param>
/// <param name="forgeInfo">ForgeInfo to populate</param>
/// <returns>True if the server is running Forge</returns>
public static bool ServerInfoCheckForge(Json.JSONData jsonData, ref ForgeInfo? forgeInfo)
public static bool ServerInfoCheckForge(PingResult jsonData, ref ForgeInfo? forgeInfo)
{
return ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML) // MC 1.12 and lower
|| ServerInfoCheckForgeSub(jsonData, ref forgeInfo, FMLVersion.FML2); // MC 1.13 and greater
return ServerInfoCheckForgeSubFML1(jsonData, ref forgeInfo) // MC 1.12 and lower
|| ServerInfoCheckForgeSubFML2(jsonData, ref forgeInfo); // MC 1.13 and greater
}
/// <summary>
@ -474,38 +478,21 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
/// <param name="jsonData">JSON data returned by the server</param>
/// <param name="forgeInfo">ForgeInfo to populate</param>
/// <param name="fmlVersion">Forge protocol version</param>
/// <returns>True if the server is running Forge</returns>
private static bool ServerInfoCheckForgeSub(Json.JSONData jsonData, ref ForgeInfo? forgeInfo, FMLVersion fmlVersion)
private static bool ServerInfoCheckForgeSubFML1(PingResult jsonData, ref ForgeInfo? forgeInfo)
{
string forgeDataTag;
string versionField;
string versionString;
switch (fmlVersion)
if (jsonData.modinfo != null)
{
case FMLVersion.FML:
forgeDataTag = "modinfo";
versionField = "type";
versionString = "FML";
break;
case FMLVersion.FML2:
forgeDataTag = "forgeData";
versionField = "fmlNetworkVersion";
versionString = "2";
break;
default:
throw new NotImplementedException("FMLVersion '" + fmlVersion + "' not implemented!");
}
if (jsonData.Properties.ContainsKey(forgeDataTag) && jsonData.Properties[forgeDataTag].Type == Json.JSONData.DataType.Object)
{
Json.JSONData modData = jsonData.Properties[forgeDataTag];
if (modData.Properties.ContainsKey(versionField) && modData.Properties[versionField].StringValue == versionString)
if (jsonData.modinfo.type == "FML")
{
forgeInfo = new ForgeInfo(modData, fmlVersion);
if (forgeInfo.Mods.Any())
if (jsonData.modinfo.modList == null || jsonData.modinfo.modList.Length == 0)
{
forgeInfo = null;
ConsoleIO.WriteLineFormatted("§8" + Translations.forge_no_mod, acceptnewlines: true);
}
else
{
forgeInfo = new ForgeInfo(jsonData.modinfo.modList, FMLVersion.FML);
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_with_mod, forgeInfo.Mods.Count));
if (Settings.Config.Logging.DebugMessages)
{
@ -515,10 +502,39 @@ namespace MinecraftClient.Protocol.Handlers
}
return true;
}
}
}
return false;
}
/// <summary>
/// Server Info: Check for For Forge on a Minecraft server Ping result (Handles FML and FML2
/// </summary>
/// <param name="jsonData">JSON data returned by the server</param>
/// <param name="forgeInfo">ForgeInfo to populate</param>
/// <returns>True if the server is running Forge</returns>
private static bool ServerInfoCheckForgeSubFML2(PingResult jsonData, ref ForgeInfo? forgeInfo)
{
if (jsonData.forgeData != null)
{
if (jsonData.forgeData.fmlNetworkVersion == "2")
{
if (jsonData.forgeData.mods == null || jsonData.forgeData.mods.Length == 0)
{
forgeInfo = null;
ConsoleIO.WriteLineFormatted("§8" + Translations.forge_no_mod, acceptnewlines: true);
}
else
{
ConsoleIO.WriteLineFormatted("§8" + Translations.forge_no_mod, acceptnewlines: true);
forgeInfo = null;
forgeInfo = new ForgeInfo(jsonData.forgeData.mods, FMLVersion.FML2);
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.forge_with_mod, forgeInfo.Mods.Count));
if (Settings.Config.Logging.DebugMessages)
{
ConsoleIO.WriteLineFormatted("§8" + Translations.forge_mod_list, acceptnewlines: true);
foreach (ForgeInfo.ForgeMod mod in forgeInfo.Mods)
ConsoleIO.WriteLineFormatted("§8 " + mod.ToString());
}
return true;
}
}
}

View file

@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
//using System.Linq;
//using System.Text;
using MinecraftClient.Mapping;
using MinecraftClient.Protocol.PacketPipeline;
namespace MinecraftClient.Protocol.Handlers
{
@ -33,21 +35,21 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Reading the "Block states" field: consists of 4096 entries, representing all the blocks in the chunk section.
/// </summary>
/// <param name="cache">Cache for reading data</param>
/// <param name="stream">Cache for reading data</param>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private Chunk? ReadBlockStatesField(Queue<byte> cache)
private async Task<Chunk?> ReadBlockStatesFieldAsync(PacketStream stream)
{
// read Block states (Type: Paletted Container)
byte bitsPerEntry = dataTypes.ReadNextByte(cache);
byte bitsPerEntry = await dataTypes.ReadNextByteAsync(stream);
// 1.18(1.18.1) add a pattle named "Single valued" to replace the vertical strip bitmask in the old
if (bitsPerEntry == 0 && protocolversion >= Protocol18Handler.MC_1_18_1_Version)
{
// Palettes: Single valued - 1.18(1.18.1) and above
ushort blockId = (ushort)dataTypes.ReadNextVarInt(cache);
ushort blockId = (ushort)(await dataTypes.ReadNextVarIntAsync(stream));
Block block = new(blockId);
dataTypes.SkipNextVarInt(cache); // Data Array Length will be zero
dataTypes.SkipNextVarInt(stream); // Data Array Length will be zero
// Empty chunks will not be stored
if (block.Type == Material.Air)
@ -73,16 +75,16 @@ namespace MinecraftClient.Protocol.Handlers
// EG, if bitsPerEntry = 5, valueMask = 00011111 in binary
uint valueMask = (uint)((1 << bitsPerEntry) - 1);
int paletteLength = usePalette ? dataTypes.ReadNextVarInt(cache) : 0; // Assume zero when length is absent
int paletteLength = usePalette ? await dataTypes.ReadNextVarIntAsync(stream) : 0; // Assume zero when length is absent
Span<uint> palette = paletteLength < 256 ? stackalloc uint[paletteLength] : new uint[paletteLength];
uint[] palette = new uint[paletteLength];
for (int i = 0; i < paletteLength; i++)
palette[i] = (uint)dataTypes.ReadNextVarInt(cache);
palette[i] = (uint)(await dataTypes.ReadNextVarIntAsync(stream));
//// Block IDs are packed in the array of 64-bits integers
dataTypes.SkipNextVarInt(cache); // Entry length
Span<byte> entryDataByte = stackalloc byte[8];
Span<long> entryDataLong = MemoryMarshal.Cast<byte, long>(entryDataByte); // Faster than MemoryMarshal.Read<long>
dataTypes.SkipNextVarInt(stream); // Entry length
long entryData = 0;
Chunk chunk = new();
int startOffset = 64; // Read the first data immediately
@ -101,10 +103,10 @@ namespace MinecraftClient.Protocol.Handlers
// When overlapping, move forward to the beginning of the next Long
startOffset = 0;
dataTypes.ReadDataReverse(cache, entryDataByte); // read long
entryData = await dataTypes.ReadNextLongAsync(stream);
}
uint blockId = (uint)(entryDataLong[0] >> startOffset) & valueMask;
uint blockId = (uint)(entryData >> startOffset) & valueMask;
// Map small IDs to actual larger block IDs
if (usePalette)
@ -141,10 +143,10 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="chunkX">Chunk X location</param>
/// <param name="chunkZ">Chunk Z location</param>
/// <param name="verticalStripBitmask">Chunk mask for reading data, store in bitset, used in 1.17 and 1.17.1</param>
/// <param name="cache">Cache for reading chunk data</param>
/// <param name="stream">Cache for reading chunk data</param>
/// <param name="cancellationToken">token to cancel the task</param>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[]? verticalStripBitmask, Queue<byte> cache)
public async Task ProcessChunkColumnData(int chunkX, int chunkZ, ulong[]? verticalStripBitmask, PacketStream stream)
{
World world = handler.GetWorld();
@ -181,10 +183,10 @@ namespace MinecraftClient.Protocol.Handlers
((verticalStripBitmask![chunkY / 64] & (1UL << (chunkY % 64))) != 0))
{
// Non-air block count inside chunk section, for lighting purposes
int blockCnt = dataTypes.ReadNextShort(cache);
int blockCnt = await dataTypes.ReadNextShortAsync(stream);
// Read Block states (Type: Paletted Container)
Chunk? chunk = ReadBlockStatesField(cache);
Chunk? chunk = await ReadBlockStatesFieldAsync(stream);
//We have our chunk, save the chunk into the world
world.StoreChunk(chunkX, chunkY, chunkZ, chunkColumnSize, chunk, chunkY == lastChunkY);
@ -192,23 +194,23 @@ namespace MinecraftClient.Protocol.Handlers
// Skip Read Biomes (Type: Paletted Container) - 1.18(1.18.1) and above
if (protocolversion >= Protocol18Handler.MC_1_18_1_Version)
{
byte bitsPerEntryBiome = dataTypes.ReadNextByte(cache); // Bits Per Entry
byte bitsPerEntryBiome = await dataTypes.ReadNextByteAsync(stream); // Bits Per Entry
if (bitsPerEntryBiome == 0)
{
dataTypes.SkipNextVarInt(cache); // Value
dataTypes.SkipNextVarInt(cache); // Data Array Length
dataTypes.SkipNextVarInt(stream); // Value
dataTypes.SkipNextVarInt(stream); // Data Array Length
// Data Array must be empty
}
else
{
if (bitsPerEntryBiome <= 3)
{
int paletteLength = dataTypes.ReadNextVarInt(cache); // Palette Length
int paletteLength = await dataTypes.ReadNextVarIntAsync(stream); // Palette Length
for (int i = 0; i < paletteLength; i++)
dataTypes.SkipNextVarInt(cache); // Palette
dataTypes.SkipNextVarInt(stream); // Palette
}
int dataArrayLength = dataTypes.ReadNextVarInt(cache); // Data Array Length
dataTypes.DropData(dataArrayLength * 8, cache); // Data Array
int dataArrayLength = await dataTypes.ReadNextVarIntAsync(stream); // Data Array Length
await dataTypes.DropDataAsync(dataArrayLength * 8, stream); // Data Array
}
}
}
@ -228,10 +230,10 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="hasSkyLight">Contains skylight info</param>
/// <param name="chunksContinuous">Are the chunk continuous</param>
/// <param name="currentDimension">Current dimension type (0 = overworld)</param>
/// <param name="cache">Cache for reading chunk data</param>
/// <param name="stream">Cache for reading chunk data</param>
/// <param name="cancellationToken">token to cancel the task</param>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, ushort chunkMask2, bool hasSkyLight, bool chunksContinuous, int currentDimension, Queue<byte> cache)
public async Task ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, ushort chunkMask2, bool hasSkyLight, bool chunksContinuous, int currentDimension, PacketStream stream)
{
World world = handler.GetWorld();
@ -247,9 +249,9 @@ namespace MinecraftClient.Protocol.Handlers
{
// 1.14 and above Non-air block count inside chunk section, for lighting purposes
if (protocolversion >= Protocol18Handler.MC_1_14_Version)
dataTypes.ReadNextShort(cache);
await dataTypes.SkipNextShortAsync(stream);
byte bitsPerBlock = dataTypes.ReadNextByte(cache);
byte bitsPerBlock = await dataTypes.ReadNextByteAsync(stream);
bool usePalette = (bitsPerBlock <= 8);
// Vanilla Minecraft will use at least 4 bits per block
@ -260,12 +262,12 @@ namespace MinecraftClient.Protocol.Handlers
// is not used, MC 1.13+ does not send the field at all in this case
int paletteLength = 0; // Assume zero when length is absent
if (usePalette || protocolversion < Protocol18Handler.MC_1_13_Version)
paletteLength = dataTypes.ReadNextVarInt(cache);
paletteLength = await dataTypes.ReadNextVarIntAsync(stream);
int[] palette = new int[paletteLength];
for (int i = 0; i < paletteLength; i++)
{
palette[i] = dataTypes.ReadNextVarInt(cache);
palette[i] = await dataTypes.ReadNextVarIntAsync(stream);
}
// Bit mask covering bitsPerBlock bits
@ -273,7 +275,7 @@ namespace MinecraftClient.Protocol.Handlers
uint valueMask = (uint)((1 << bitsPerBlock) - 1);
// Block IDs are packed in the array of 64-bits integers
ulong[] dataArray = dataTypes.ReadNextULongArray(cache);
ulong[] dataArray = await dataTypes.ReadNextULongArrayAsync(stream);
Chunk chunk = new();
@ -358,19 +360,19 @@ namespace MinecraftClient.Protocol.Handlers
}
}
//We have our chunk, save the chunk into the world
// We have our chunk, save the chunk into the world
world.StoreChunk(chunkX, chunkY, chunkZ, chunkColumnSize, chunk, chunkY == maxChunkY);
//Pre-1.14 Lighting data
// Pre-1.14 Lighting data
if (protocolversion < Protocol18Handler.MC_1_14_Version)
{
//Skip block light
dataTypes.DropData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache);
// Skip block light
await dataTypes.DropDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, stream);
//Skip sky light
// Skip sky light
if (currentDimension == 0)
// Sky light is not sent in the nether or the end
dataTypes.DropData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache);
await dataTypes.DropDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, stream);
}
}
}
@ -383,15 +385,12 @@ namespace MinecraftClient.Protocol.Handlers
// 1.8 chunk format
if (chunksContinuous && chunkMask == 0)
{
//Unload the entire chunk column
handler.InvokeOnMainThread(() =>
{
world[chunkX, chunkZ] = null;
});
// Unload the entire chunk column
world[chunkX, chunkZ] = null;
}
else
{
//Load chunk data from the server
// Load chunk data from the server
int maxChunkY = sizeof(int) * 8 - 1 - BitOperations.LeadingZeroCount(chunkMask);
for (int chunkY = 0; chunkY <= maxChunkY; chunkY++)
{
@ -399,35 +398,34 @@ namespace MinecraftClient.Protocol.Handlers
{
Chunk chunk = new();
//Read chunk data, all at once for performance reasons, and build the chunk object
Queue<ushort> queue = new(dataTypes.ReadNextUShortsLittleEndian(Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ, cache));
// Read chunk data, all at once for performance reasons, and build the chunk object
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.SetWithoutCheck(blockX, blockY, blockZ, new Block(queue.Dequeue()));
chunk.SetWithoutCheck(blockX, blockY, blockZ, new Block(await dataTypes.ReadNextUShortAsync(stream)));
//We have our chunk, save the chunk into the world
// We have our chunk, save the chunk into the world
world.StoreChunk(chunkX, chunkY, chunkZ, chunkColumnSize, chunk, chunkY == maxChunkY);
}
}
//Skip light information
// Skip light information
for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++)
{
if ((chunkMask & (1 << chunkY)) != 0)
{
//Skip block light
dataTypes.DropData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache);
// Skip block light
await dataTypes.DropDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, stream);
//Skip sky light
// Skip sky light
if (hasSkyLight)
dataTypes.DropData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache);
await dataTypes.DropDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, stream);
}
}
//Skip biome metadata
// Skip biome metadata
if (chunksContinuous)
dataTypes.DropData(Chunk.SizeX * Chunk.SizeZ, cache);
await dataTypes.DropDataAsync(Chunk.SizeX * Chunk.SizeZ, stream);
}
}
else
@ -435,15 +433,12 @@ namespace MinecraftClient.Protocol.Handlers
// 1.7 chunk format
if (chunksContinuous && chunkMask == 0)
{
//Unload the entire chunk column
handler.InvokeOnMainThread(() =>
{
world[chunkX, chunkZ] = null;
});
// Unload the entire chunk column
world[chunkX, chunkZ] = null;
}
else
{
//Count chunk sections
// Count chunk sections
int sectionCount = 0;
int addDataSectionCount = 0;
for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++)
@ -454,10 +449,10 @@ namespace MinecraftClient.Protocol.Handlers
addDataSectionCount++;
}
//Read chunk data, unpacking 4-bit values into 8-bit values for block metadata
Queue<byte> blockTypes = new(dataTypes.ReadData(Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount, cache));
// Read chunk data, unpacking 4-bit values into 8-bit values for block metadata
Queue<byte> blockTypes = new(await dataTypes.ReadDataAsync(Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount, stream));
Queue<byte> blockMeta = new();
foreach (byte packed in dataTypes.ReadData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, cache))
foreach (byte packed in await dataTypes.ReadDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, stream))
{
byte hig = (byte)(packed >> 4);
byte low = (byte)(packed & (byte)0x0F);
@ -465,15 +460,15 @@ namespace MinecraftClient.Protocol.Handlers
blockMeta.Enqueue(low);
}
//Skip data we don't need
dataTypes.DropData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, cache); //Block light
// Skip data we don't need
await dataTypes.DropDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, stream); //Block light
if (hasSkyLight)
dataTypes.DropData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, cache); //Sky light
dataTypes.DropData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * addDataSectionCount) / 2, cache); //BlockAdd
await dataTypes.DropDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * sectionCount) / 2, stream); //Sky light
await dataTypes.DropDataAsync((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ * addDataSectionCount) / 2, stream); //BlockAdd
if (chunksContinuous)
dataTypes.DropData(Chunk.SizeX * Chunk.SizeZ, cache); //Biomes
await dataTypes.DropDataAsync(Chunk.SizeX * Chunk.SizeZ, stream); //Biomes
//Load chunk data
// Load chunk data
int maxChunkY = sizeof(int) * 8 - 1 - BitOperations.LeadingZeroCount(chunkMask);
for (int chunkY = 0; chunkY <= maxChunkY; chunkY++)
{

View file

@ -1,114 +0,0 @@
using System;
using System.Net.Sockets;
using MinecraftClient.Crypto;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Wrapper for handling unencrypted & encrypted socket
/// </summary>
class SocketWrapper
{
readonly TcpClient c;
AesCfb8Stream? s;
bool encrypted = false;
/// <summary>
/// Initialize a new SocketWrapper
/// </summary>
/// <param name="client">TcpClient connected to the server</param>
public SocketWrapper(TcpClient client)
{
c = client;
}
/// <summary>
/// Check if the socket is still connected
/// </summary>
/// <returns>TRUE if still connected</returns>
/// <remarks>Silently dropped connection can only be detected by attempting to read/write data</remarks>
public bool IsConnected()
{
return c.Client != null && c.Connected;
}
/// <summary>
/// Check if the socket has data available to read
/// </summary>
/// <returns>TRUE if data is available to read</returns>
public bool HasDataAvailable()
{
return c.Client.Available > 0;
}
/// <summary>
/// Switch network reading/writing to an encrypted stream
/// </summary>
/// <param name="secretKey">AES secret key</param>
public void SwitchToEncrypted(byte[] secretKey)
{
if (encrypted)
throw new InvalidOperationException("Stream is already encrypted!?");
s = new AesCfb8Stream(c.GetStream(), secretKey);
encrypted = true;
}
/// <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);
}
}
/// <summary>
/// Read some data from the server.
/// </summary>
/// <param name="length">Amount of bytes to read</param>
/// <returns>The data read from the network as an array</returns>
public byte[] ReadDataRAW(int length)
{
if (length > 0)
{
byte[] cache = new byte[length];
Receive(cache, 0, length, SocketFlags.None);
return cache;
}
return Array.Empty<byte>();
}
/// <summary>
/// Send raw data to the server.
/// </summary>
/// <param name="buffer">data to send</param>
public void SendDataRAW(byte[] buffer)
{
if (encrypted)
s!.Write(buffer, 0, buffer.Length);
else
c.Client.Send(buffer);
}
/// <summary>
/// Disconnect from the server
/// </summary>
public void Disconnect()
{
try
{
c.Close();
}
catch (SocketException) { }
catch (System.IO.IOException) { }
catch (NullReferenceException) { }
catch (ObjectDisposedException) { }
}
}
}

View file

@ -1,63 +0,0 @@
using Ionic.Zlib;
namespace MinecraftClient.Protocol.Handlers
{
/// <summary>
/// Quick Zlib compression handling for network packet compression.
/// Note: Underlying compression handling is taken from the DotNetZip Library.
/// This library is open source and provided under the Microsoft Public License.
/// More info about DotNetZip at dotnetzip.codeplex.com.
/// </summary>
public static class ZlibUtils
{
/// <summary>
/// Compress a byte array into another bytes array using Zlib compression
/// </summary>
/// <param name="to_compress">Data to compress</param>
/// <returns>Compressed data as a byte array</returns>
public static byte[] Compress(byte[] to_compress)
{
byte[] data;
using (System.IO.MemoryStream memstream = new())
{
using (ZlibStream stream = new(memstream, CompressionMode.Compress))
{
stream.Write(to_compress, 0, to_compress.Length);
}
data = memstream.ToArray();
}
return data;
}
/// <summary>
/// Decompress a byte array into another byte array of the specified size
/// </summary>
/// <param name="to_decompress">Data to decompress</param>
/// <param name="size_uncompressed">Size of the data once decompressed</param>
/// <returns>Decompressed data as a byte array</returns>
public static byte[] Decompress(byte[] to_decompress, int size_uncompressed)
{
ZlibStream stream = new(new System.IO.MemoryStream(to_decompress, false), CompressionMode.Decompress);
byte[] packetData_decompressed = new byte[size_uncompressed];
stream.Read(packetData_decompressed, 0, size_uncompressed);
stream.Close();
return packetData_decompressed;
}
/// <summary>
/// Decompress a byte array into another byte array of a potentially unlimited size (!)
/// </summary>
/// <param name="to_decompress">Data to decompress</param>
/// <returns>Decompressed data as byte array</returns>
public static byte[] Decompress(byte[] to_decompress)
{
ZlibStream stream = new(new System.IO.MemoryStream(to_decompress, false), CompressionMode.Decompress);
byte[] buffer = new byte[16 * 1024];
using System.IO.MemoryStream decompressedBuffer = new();
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
decompressedBuffer.Write(buffer, 0, read);
return decompressedBuffer.ToArray();
}
}
}