Packets refactoring to async

This commit is contained in:
Anon 2026-04-04 22:28:05 +02:00
parent 4d800a31fc
commit 5d03cd4fd1
10 changed files with 611 additions and 312 deletions

View file

@ -2289,34 +2289,6 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
return client.GetInventories().Keys.Where(id => id > 0).DefaultIfEmpty(0).Max();
}
private static bool WaitForContainerOpen(McClient client, ISet<int> beforeIds, int waitMs, out int inventoryId, out Container? inventory)
{
inventoryId = 0;
inventory = null;
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
(int activeId, Container? activeInventory) state = client.InvokeOnMainThread(() =>
{
int activeId = GetActiveContainerId(client);
Container? activeInventory = activeId > 0 ? client.GetInventory(activeId) : null;
return (activeId, activeInventory);
});
if (state.activeId > 0 && (!beforeIds.Contains(state.activeId) || beforeIds.Count == 0) && state.activeInventory is not null)
{
inventoryId = state.activeId;
inventory = state.activeInventory;
return true;
}
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static async Task<bool> WaitForContainerOpenAsync(McClient client, ISet<int> beforeIds, int waitMs, Action<ContainerOpenState> onOpened)
{
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
@ -2342,22 +2314,6 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
}
}
private static bool WaitForContainerClose(McClient client, int inventoryId, int waitMs)
{
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
bool stillOpen = client.InvokeOnMainThread(() => client.GetInventories().ContainsKey(inventoryId));
if (!stillOpen)
return true;
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static async Task<bool> WaitForContainerCloseAsync(McClient client, int inventoryId, int waitMs)
{
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
@ -2453,14 +2409,6 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
: 0;
}
private static bool TryDropInventorySlotItems(McClient client, int inventoryId, Container inventory, int slotId, ItemType itemType, int dropCount, out int droppedCount)
{
(bool success, int actualDroppedCount) =
TryDropInventorySlotItemsAsync(client, inventoryId, inventory, slotId, itemType, dropCount).GetAwaiter().GetResult();
droppedCount = actualDroppedCount;
return success;
}
private static int CountItemInRange(Container inventory, ItemType itemType, int startSlot, int endSlot)
{
return inventory.Items
@ -2827,115 +2775,6 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
}
}
private static bool WaitForCursorItem(McClient client, ItemType itemType, int waitMs, out Item? cursorItem)
{
cursorItem = null;
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
if (TryGetCursorItem(client, out cursorItem) && cursorItem is not null && cursorItem.Type == itemType)
return true;
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static bool WaitForCursorClear(McClient client, int waitMs)
{
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
if (!TryGetCursorItem(client, out _))
return true;
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static bool WaitForPlacement(McClient client, int inventoryId, int targetSlot, ItemType itemType, int beforeTargetCount, int beforeCursorCount, int placedCount)
{
DateTime deadline = DateTime.UtcNow.AddMilliseconds(DefaultInventoryActionWaitMs);
while (true)
{
bool targetUpdated = false;
bool cursorUpdated = false;
Container? inventory = client.InvokeOnMainThread(() => client.GetInventory(inventoryId));
if (inventory is not null)
{
int currentTargetCount = GetSlotItemCount(inventory, targetSlot, itemType);
targetUpdated = currentTargetCount >= beforeTargetCount + placedCount;
}
if (placedCount >= beforeCursorCount)
{
cursorUpdated = !TryGetCursorItem(client, out _);
}
else if (TryGetCursorItem(client, out Item? cursorItem) && cursorItem is not null && cursorItem.Type == itemType)
{
cursorUpdated = cursorItem.Count <= beforeCursorCount - placedCount;
}
if (targetUpdated && cursorUpdated)
return true;
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static bool WaitForSlotItemCount(McClient client, int inventoryId, int slotId, ItemType itemType, Func<int, bool> predicate, int waitMs, out Container? inventory, out int itemCount)
{
inventory = null;
itemCount = 0;
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
inventory = client.GetInventory(inventoryId);
if (inventory is not null)
{
itemCount = GetSlotItemCount(inventory, slotId, itemType);
if (predicate(itemCount))
return true;
}
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static bool WaitForRangeCount(McClient client, int inventoryId, ItemType itemType, int startSlot, int endSlot, Func<int, bool> predicate, int waitMs, out Container? inventory, out int itemCount)
{
inventory = null;
itemCount = 0;
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
inventory = client.InvokeOnMainThread(() => client.GetInventory(inventoryId));
if (inventory is not null)
{
itemCount = CountItemInRange(inventory, itemType, startSlot, endSlot);
if (predicate(itemCount))
return true;
}
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static List<NearbyPlayerSnapshot> BuildTrackedPlayerSnapshots(McClient client, bool includeSelf)
{
Location playerLocation = client.GetCurrentLocation();
@ -3121,25 +2960,6 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
return new string(buffer);
}
private static bool WaitForArrival(McClient client, Location goal, int waitMs, double tolerance, out Location? finalLocation)
{
finalLocation = null;
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
Location location = client.InvokeOnMainThread(client.GetCurrentLocation);
finalLocation = location;
double distance = GetDistance(location, goal);
if (distance <= tolerance)
return true;
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static async Task<(bool Arrived, Location FinalLocation)> WaitForArrivalAsync(McClient client, Location goal, int waitMs, double tolerance)
{
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
@ -3186,24 +3006,6 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
return Math.Clamp(timeoutMs, MinPathQueryTimeoutMs, MaxPathQueryTimeoutMs);
}
private static bool WaitForBlockChange(McClient client, Location target, Block beforeBlock, int waitMs, out Block afterBlock)
{
afterBlock = beforeBlock;
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
Block current = client.InvokeOnMainThread(() => client.GetWorld().GetBlock(target));
afterBlock = current;
if (!AreEquivalentBlocks(current, beforeBlock))
return true;
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static async Task<(bool Changed, Block AfterBlock)> WaitForBlockChangeAsync(McClient client, Location target, Block beforeBlock, int waitMs)
{
Block afterBlock = beforeBlock;
@ -3337,22 +3139,6 @@ public sealed class MccMcpCapabilities : IMccMcpCapabilities
.ToArray();
}
private static bool WaitForEntityRemoval(McClient client, int entityId, int waitMs)
{
DateTime deadline = DateTime.UtcNow.AddMilliseconds(waitMs);
while (true)
{
bool exists = client.InvokeOnMainThread(() => client.GetEntities().ContainsKey(entityId));
if (!exists)
return true;
if (DateTime.UtcNow >= deadline)
return false;
Thread.Sleep(ArrivalPollIntervalMs);
}
}
private static int GetInventoryItemCount(McClient client, ItemType itemType)
{
Container? inventory = client.GetInventory(0);

View file

@ -8,6 +8,7 @@ using MinecraftClient.Inventory;
using MinecraftClient.Inventory.ItemPalettes;
using MinecraftClient.Mapping;
using MinecraftClient.Mapping.EntityPalettes;
using MinecraftClient.Protocol.PacketPipeline;
using MinecraftClient.Protocol.Handlers.StructuredComponents;
using MinecraftClient.Protocol.Handlers.StructuredComponents.Core;
using MinecraftClient.Protocol.Message;
@ -44,6 +45,12 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public byte[] ReadData(int offset, PacketReader reader)
{
return reader.ReadData(offset);
}
/// <summary>
/// Read some data from a cache of bytes and remove it from the cache
/// </summary>
@ -56,6 +63,12 @@ namespace MinecraftClient.Protocol.Handlers
dest[i] = cache.Dequeue();
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void ReadDataReverse(PacketReader reader, Span<byte> dest)
{
reader.ReadDataReverse(dest);
}
/// <summary>
/// Remove some data from the cache
/// </summary>
@ -68,6 +81,12 @@ namespace MinecraftClient.Protocol.Handlers
cache.Dequeue();
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void DropData(int offset, PacketReader reader)
{
reader.Skip(offset);
}
/// <summary>
/// Read a string from a cache of bytes and remove it from the cache
/// </summary>
@ -84,6 +103,13 @@ namespace MinecraftClient.Protocol.Handlers
else return "";
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public string ReadNextString(PacketReader reader)
{
int length = ReadNextVarInt(reader);
return length > 0 ? Encoding.UTF8.GetString(ReadData(length, reader)) : "";
}
/// <summary>
/// Skip a string from a cache of bytes and remove it from the cache
/// </summary>
@ -94,6 +120,12 @@ namespace MinecraftClient.Protocol.Handlers
DropData(length, cache);
}
public void SkipNextString(PacketReader reader)
{
int length = ReadNextVarInt(reader);
DropData(length, reader);
}
/// <summary>
/// Read a boolean from a cache of bytes and remove it from the cache
/// </summary>
@ -104,6 +136,12 @@ namespace MinecraftClient.Protocol.Handlers
return ReadNextByte(cache) != 0x00;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public bool ReadNextBool(PacketReader reader)
{
return ReadNextByte(reader) != 0x00;
}
/// <summary>
/// Read a short integer from a cache of bytes and remove it from the cache
/// </summary>
@ -117,6 +155,12 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToInt16(rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public short ReadNextShort(PacketReader reader)
{
return reader.ReadInt16BigEndian();
}
/// <summary>
/// Read an integer from a cache of bytes and remove it from the cache
/// </summary>
@ -130,6 +174,12 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToInt32(rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int ReadNextInt(PacketReader reader)
{
return reader.ReadInt32BigEndian();
}
/// <summary>
/// Read a long integer from a cache of bytes and remove it from the cache
/// </summary>
@ -143,6 +193,12 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToInt64(rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public long ReadNextLong(PacketReader reader)
{
return reader.ReadInt64BigEndian();
}
/// <summary>
/// Read an unsigned short integer from a cache of bytes and remove it from the cache
/// </summary>
@ -156,6 +212,12 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToUInt16(rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public ushort ReadNextUShort(PacketReader reader)
{
return reader.ReadUInt16BigEndian();
}
/// <summary>
/// Read an unsigned long integer from a cache of bytes and remove it from the cache
/// </summary>
@ -169,6 +231,12 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToUInt64(rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public ulong ReadNextULong(PacketReader reader)
{
return unchecked((ulong)reader.ReadInt64BigEndian());
}
/// <summary>
/// Read a Location encoded as an ulong field and remove it from the cache
/// </summary>
@ -199,6 +267,32 @@ namespace MinecraftClient.Protocol.Handlers
return new Location(x, y, z);
}
public Location ReadNextLocation(PacketReader reader)
{
ulong locEncoded = ReadNextULong(reader);
int x, y, z;
if (protocolversion >= Protocol18Handler.MC_1_14_Version)
{
x = (int)(locEncoded >> 38);
y = (int)(locEncoded & 0xFFF);
z = (int)(locEncoded << 26 >> 38);
}
else
{
x = (int)(locEncoded >> 38);
y = (int)((locEncoded >> 26) & 0xFFF);
z = (int)(locEncoded << 38 >> 38);
}
if (x >= 0x02000000)
x -= 0x04000000;
if (y >= 0x00000800)
y -= 0x00001000;
if (z >= 0x02000000)
z -= 0x04000000;
return new Location(x, y, z);
}
/// <summary>
/// Read several little endian unsigned short integers at once from a cache of bytes and remove them from the cache
/// </summary>
@ -212,6 +306,15 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
public ushort[] ReadNextUShortsLittleEndian(int amount, PacketReader reader)
{
byte[] rawValues = ReadData(2 * amount, reader);
ushort[] result = new ushort[amount];
for (int i = 0; i < amount; i++)
result[i] = BitConverter.ToUInt16(rawValues, i * 2);
return result;
}
/// <summary>
/// Read a uuid from a cache of bytes and remove it from the cache
/// </summary>
@ -228,6 +331,16 @@ namespace MinecraftClient.Protocol.Handlers
return guid;
}
public Guid ReadNextUUID(PacketReader reader)
{
Span<byte> javaUUID = stackalloc byte[16];
reader.ReadData(javaUUID);
Guid guid = new(javaUUID);
if (BitConverter.IsLittleEndian)
guid = guid.ToLittleEndian();
return guid;
}
/// <summary>
/// Read a byte array from a cache of bytes and remove it from the cache
/// </summary>
@ -242,6 +355,15 @@ namespace MinecraftClient.Protocol.Handlers
return ReadData(len, cache);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public byte[] ReadNextByteArray(PacketReader reader)
{
int len = protocolversion >= Protocol18Handler.MC_1_8_Version
? ReadNextVarInt(reader)
: ReadNextShort(reader);
return ReadData(len, reader);
}
/// <summary>
/// Read a byte array with given length from a cache of bytes and remove it from the cache
/// </summary>
@ -254,6 +376,12 @@ namespace MinecraftClient.Protocol.Handlers
return ReadData(length, cache);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public byte[] ReadNextByteArray(PacketReader reader, int length)
{
return ReadData(length, reader);
}
/// <summary>
/// Reads a length-prefixed array of unsigned long integers and removes it from the cache
/// </summary>
@ -268,6 +396,15 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
public ulong[] ReadNextULongArray(PacketReader reader)
{
int len = ReadNextVarInt(reader);
ulong[] result = new ulong[len];
for (int i = 0; i < len; i++)
result[i] = ReadNextULong(reader);
return result;
}
/// <summary>
/// Read a double from a cache of bytes and remove it from the cache
/// </summary>
@ -281,6 +418,12 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToDouble(rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public double ReadNextDouble(PacketReader reader)
{
return BitConverter.Int64BitsToDouble(ReadNextLong(reader));
}
/// <summary>
/// Read a float from a cache of bytes and remove it from the cache
/// </summary>
@ -294,6 +437,12 @@ namespace MinecraftClient.Protocol.Handlers
return BitConverter.ToSingle(rawValue);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public float ReadNextFloat(PacketReader reader)
{
return BitConverter.Int32BitsToSingle(ReadNextInt(reader));
}
/// <summary>
/// Read an integer from the network
/// </summary>
@ -357,6 +506,22 @@ namespace MinecraftClient.Protocol.Handlers
return i;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int ReadNextVarInt(PacketReader reader)
{
int i = 0;
int j = 0;
byte b;
do
{
b = reader.ReadByte();
i |= (b & 0x7F) << j++ * 7;
if (j > 5) throw new OverflowException("VarInt too big");
} while ((b & 0x80) == 128);
return i;
}
/// <summary>
/// Skip a VarInt from a cache of bytes with better performance
/// </summary>
@ -369,6 +534,14 @@ namespace MinecraftClient.Protocol.Handlers
break;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void SkipNextVarInt(PacketReader reader)
{
while (true)
if ((ReadNextByte(reader) & 0x80) != 128)
break;
}
/// <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
@ -389,6 +562,19 @@ namespace MinecraftClient.Protocol.Handlers
return ((high & 0xFF) << 15) | low;
}
public int ReadNextVarShort(PacketReader reader)
{
ushort low = ReadNextUShort(reader);
byte high = 0;
if ((low & 0x8000) != 0)
{
low &= 0x7FFF;
high = ReadNextByte(reader);
}
return ((high & 0xFF) << 15) | low;
}
/// <summary>
/// Read a long from a cache of bytes and remove it from the cache
/// </summary>
@ -415,6 +601,27 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
public long ReadNextVarLong(PacketReader reader)
{
int numRead = 0;
long result = 0;
byte read;
do
{
read = ReadNextByte(reader);
long value = (read & 0x7F);
result |= (value << (7 * numRead));
numRead++;
if (numRead > 10)
{
throw new OverflowException("VarLong is too big");
}
} while ((read & 0x80) != 0);
return result;
}
/// <summary>
/// Read a single byte from a cache of bytes and remove it from the cache
/// </summary>
@ -426,6 +633,12 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public byte ReadNextByte(PacketReader reader)
{
return reader.ReadByte();
}
/// <summary>
/// Read an uncompressed Named Binary Tag blob and remove it from the cache
/// </summary>
@ -434,6 +647,11 @@ namespace MinecraftClient.Protocol.Handlers
return ReadNextNbt(cache, true);
}
public Dictionary<string, object> ReadNextNbt(PacketReader reader)
{
return ReadWithQueueFallback(reader, cache => ReadNextNbt(cache, true));
}
/// <summary>
/// Read an ItemStackTemplate (26.1+) from a cache of bytes.
/// Unlike ItemStack, this uses item-first encoding: item_id, count, DataComponentPatch.
@ -465,6 +683,11 @@ namespace MinecraftClient.Protocol.Handlers
return item;
}
public Item ReadNextItemStackTemplate(PacketReader reader, ItemPalette itemPalette)
{
return ReadWithQueueFallback(reader, cache => ReadNextItemStackTemplate(cache, itemPalette));
}
/// <summary>
/// Read a single item slot from a cache of bytes and remove it from the cache
/// </summary>
@ -568,6 +791,11 @@ namespace MinecraftClient.Protocol.Handlers
}
}
public Item? ReadNextItemSlot(PacketReader reader, ItemPalette itemPalette)
{
return ReadWithQueueFallback(reader, cache => ReadNextItemSlot(cache, itemPalette));
}
private void ReadNextDetail(Queue<byte> cache)
{
var potionEffectId = ReadNextVarInt(cache);
@ -724,6 +952,11 @@ namespace MinecraftClient.Protocol.Handlers
return entity;
}
public Entity ReadNextEntity(PacketReader reader, EntityPalette entityPalette, bool living)
{
return ReadWithQueueFallback(reader, cache => ReadNextEntity(cache, entityPalette, living));
}
/// <summary>
/// Read an uncompressed Named Binary Tag blob and remove it from the cache (internal)
/// </summary>
@ -1077,6 +1310,12 @@ namespace MinecraftClient.Protocol.Handlers
}
}
public Dictionary<int, object?> ReadNextMetadata(PacketReader reader, ItemPalette itemPalette,
EntityMetadataPalette metadataPalette)
{
return ReadWithQueueFallback(reader, cache => ReadNextMetadata(cache, itemPalette, metadataPalette));
}
private static bool HasLpVec3Continuation(int firstByte) => (firstByte & 4) == 4;
private static double UnpackLpVec3(long packedAxis)
@ -1109,6 +1348,27 @@ namespace MinecraftClient.Protocol.Handlers
);
}
public (double X, double Y, double Z) ReadNextLpVec3Values(PacketReader reader)
{
int first = ReadNextByte(reader);
if (first == 0)
return (0.0, 0.0, 0.0);
int second = ReadNextByte(reader);
uint high = (uint)ReadNextInt(reader);
long packed = ((long)high << 16) | (long)(second << 8) | (uint)first;
long scale = first & 3;
if (HasLpVec3Continuation(first))
scale |= ((long)ReadNextVarInt(reader) & 0xFFFFFFFFL) << 2;
return (
UnpackLpVec3(packed >> 3) * scale,
UnpackLpVec3(packed >> 18) * scale,
UnpackLpVec3(packed >> 33) * scale
);
}
/// <summary>
/// Read an LpVec3 (low-precision vec3) from the cache (1.21.9+) and discard it.
/// </summary>
@ -1117,6 +1377,11 @@ namespace MinecraftClient.Protocol.Handlers
ReadNextLpVec3Values(cache);
}
public void ReadNextLpVec3(PacketReader reader)
{
ReadNextLpVec3Values(reader);
}
/// <summary>
/// Consume bytes for a ResolvableProfile (1.21.9+).
/// Wire: Either(GameProfile, Partial) + PlayerSkin.Patch
@ -1379,6 +1644,11 @@ namespace MinecraftClient.Protocol.Handlers
}
}
public void ReadParticleData(PacketReader reader, ItemPalette itemPalette)
{
ReadWithQueueFallback(reader, cache => ReadParticleData(cache, itemPalette));
}
private void ReadDustParticle(Queue<byte> cache)
{
ReadNextFloat(cache); // Red
@ -1437,6 +1707,11 @@ namespace MinecraftClient.Protocol.Handlers
maximumNumberOfTradeUses, xp, specialPrice, priceMultiplier, demand);
}
public VillagerTrade ReadNextTrade(PacketReader reader, ItemPalette itemPalette)
{
return ReadWithQueueFallback(reader, cache => ReadNextTrade(cache, itemPalette));
}
public string ReadNextChat(Queue<byte> cache)
{
if (protocolversion >= Protocol18Handler.MC_1_20_4_Version)
@ -1454,6 +1729,11 @@ namespace MinecraftClient.Protocol.Handlers
}
}
public string ReadNextChat(PacketReader reader)
{
return ReadWithQueueFallback(reader, ReadNextChat);
}
/// <summary>
/// Build an uncompressed Named Binary Tag blob for sending over the network
/// </summary>
@ -2035,5 +2315,22 @@ namespace MinecraftClient.Protocol.Handlers
return fields.ToArray();
}
private static T ReadWithQueueFallback<T>(PacketReader reader, Func<Queue<byte>, T> read)
{
byte[] remaining = reader.CopyRemaining();
Queue<byte> cache = new(remaining);
T result = read(cache);
reader.Skip(remaining.Length - cache.Count);
return result;
}
private static void ReadWithQueueFallback(PacketReader reader, Action<Queue<byte>> read)
{
byte[] remaining = reader.CopyRemaining();
Queue<byte> cache = new(remaining);
read(cache);
reader.Skip(remaining.Length - cache.Count);
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using MinecraftClient.Protocol.PacketPipeline;
namespace MinecraftClient.Protocol.Handlers.packet.s2c
{
@ -59,7 +60,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
public static bool IsCommandTreeAvailable => HasValidCommandTree();
public static void Read(DataTypes dataTypes, Queue<byte> packetData, int protocolVersion)
public static void Read(DataTypes dataTypes, PacketReader packetData, int protocolVersion)
{
Reset();
ConsoleIO.OnDeclareMinecraftCommand(Array.Empty<string>());
@ -88,7 +89,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
: [];
}
private static void ReadCommandTree(DataTypes dataTypes, Queue<byte> packetData, int protocolVersion)
private static void ReadCommandTree(DataTypes dataTypes, PacketReader packetData, int protocolVersion)
{
int count = dataTypes.ReadNextVarInt(packetData);
Nodes = new CommandNode[count];
@ -117,7 +118,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
private static CommandNode ReadArgumentNode(
DataTypes dataTypes,
Queue<byte> packetData,
PacketReader packetData,
int protocolVersion,
byte flags,
int[] children,
@ -135,7 +136,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
return new(flags, children, redirectNode, name, descriptor, suggestionsType, parserId);
}
private static int[] ReadChildIndices(DataTypes dataTypes, Queue<byte> packetData)
private static int[] ReadChildIndices(DataTypes dataTypes, PacketReader packetData)
{
int childCount = dataTypes.ReadNextVarInt(packetData);
int[] children = new int[childCount];
@ -146,7 +147,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
return children;
}
private static CommandArgumentDescriptor ReadArgumentDescriptor(DataTypes dataTypes, Queue<byte> packetData, ArgumentTypeLayout layout)
private static CommandArgumentDescriptor ReadArgumentDescriptor(DataTypes dataTypes, PacketReader packetData, ArgumentTypeLayout layout)
{
switch (layout.PayloadKind)
{
@ -189,7 +190,7 @@ namespace MinecraftClient.Protocol.Handlers.packet.s2c
}
}
private static void ReadNumberBounds<TValue>(DataTypes dataTypes, Queue<byte> packetData, Func<DataTypes, Queue<byte>, TValue> readValue)
private static void ReadNumberBounds<TValue>(DataTypes dataTypes, PacketReader packetData, Func<DataTypes, PacketReader, TValue> readValue)
{
byte flags = dataTypes.ReadNextByte(packetData);
if ((flags & 0x01) != 0)

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Security.Cryptography;
@ -476,6 +477,15 @@ namespace MinecraftClient.Protocol.Handlers
}
}
private async Task ReceiveAsync(byte[] buffer, int start, int offset, CancellationToken cancellationToken = default)
{
if (offset <= 0)
return;
Stream stream = encrypted ? s! : c.GetStream();
await stream.ReadExactlyAsync(buffer.AsMemory(start, offset), cancellationToken);
}
private void Send(byte[] buffer)
{
if (encrypted)
@ -484,7 +494,61 @@ namespace MinecraftClient.Protocol.Handlers
c.Client.Send(buffer);
}
private bool Handshake(string uuid, string username, string sessionID, string host, int port, SessionToken session)
private async Task SendAsync(byte[] buffer, CancellationToken cancellationToken = default)
{
if (buffer.Length == 0)
return;
Stream stream = encrypted ? s! : c.GetStream();
await stream.WriteAsync(buffer.AsMemory(0, buffer.Length), cancellationToken);
await stream.FlushAsync(cancellationToken);
}
private async Task<byte[]> ReadDataAsync(int offset, CancellationToken cancellationToken = default)
{
if (offset <= 0)
return [];
byte[] cache = new byte[offset];
await ReceiveAsync(cache, 0, offset, cancellationToken);
return cache;
}
private async Task<string> ReadNextStringAsync(CancellationToken cancellationToken = default)
{
ushort length = (ushort)await ReadNextShortAsync(cancellationToken);
if (length <= 0)
return "";
byte[] cache = new byte[length * 2];
await ReceiveAsync(cache, 0, length * 2, cancellationToken);
return Encoding.BigEndianUnicode.GetString(cache);
}
private async Task<byte[]> ReadNextByteArrayAsync(CancellationToken cancellationToken = default)
{
short len = await ReadNextShortAsync(cancellationToken);
byte[] data = new byte[len];
await ReceiveAsync(data, 0, len, cancellationToken);
return data;
}
private async Task<short> ReadNextShortAsync(CancellationToken cancellationToken = default)
{
byte[] tmp = new byte[2];
await ReceiveAsync(tmp, 0, 2, cancellationToken);
Array.Reverse(tmp);
return BitConverter.ToInt16(tmp, 0);
}
private async Task<byte> ReadNextByteAsync(CancellationToken cancellationToken = default)
{
byte[] result = new byte[1];
await ReceiveAsync(result, 0, 1, cancellationToken);
return result[0];
}
private async Task<bool> HandshakeAsync(string uuid, string username, string sessionID, string host, int port, SessionToken session, CancellationToken cancellationToken = default)
{
//array
byte[] data = new byte[10 + (username.Length + host.Length) * 2];
@ -518,28 +582,28 @@ namespace MinecraftClient.Protocol.Handlers
Array.Reverse(sh);
sh.CopyTo(data, 6 + (username.Length * 2) + (host.Length * 2));
Send(data);
await SendAsync(data, cancellationToken);
byte[] pid = new byte[1];
Receive(pid, 0, 1, SocketFlags.None);
await ReceiveAsync(pid, 0, 1, cancellationToken);
while (pid[0] == 0xFA) //Skip some early plugin messages
{
using (MainThreadExecutionScope.Enter(handler))
ProcessPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
await ReceiveAsync(pid, 0, 1, cancellationToken);
}
if (pid[0] == 0xFD)
{
string serverID = ReadNextString();
byte[] PublicServerkey = ReadNextByteArray();
byte[] token = ReadNextByteArray();
string serverID = await ReadNextStringAsync(cancellationToken);
byte[] PublicServerkey = await ReadNextByteArrayAsync(cancellationToken);
byte[] token = await ReadNextByteArrayAsync(cancellationToken);
if (serverID == "-")
ConsoleIO.WriteLineFormatted("§8" + Translations.mcc_server_offline, acceptnewlines: true);
else if (Settings.Config.Logging.DebugMessages)
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.mcc_handshake, serverID));
return StartEncryption(uuid, username, sessionID, Config.Main.General.AccountType, token, serverID, PublicServerkey, session);
return await StartEncryptionAsync(uuid, username, sessionID, Config.Main.General.AccountType, token, serverID, PublicServerkey, session, cancellationToken);
}
else
{
@ -548,7 +612,7 @@ namespace MinecraftClient.Protocol.Handlers
}
}
private bool StartEncryption(string uuid, string username, string sessionID, LoginType type, byte[] token, string serverIDhash, byte[] serverPublicKey, SessionToken session)
private async Task<bool> StartEncryptionAsync(string uuid, string username, string sessionID, LoginType type, byte[] token, string serverIDhash, byte[] serverPublicKey, SessionToken session, CancellationToken cancellationToken = default)
{
RSACryptoServiceProvider RSAService = CryptoHandler.DecodeRSAPublicKey(serverPublicKey)!;
byte[] secretKey = CryptoHandler.ClientAESPrivateKey ?? CryptoHandler.GenerateAESPrivateKey();
@ -571,7 +635,7 @@ namespace MinecraftClient.Protocol.Handlers
if (needCheckSession)
{
if (ProtocolHandler.SessionCheck(uuid, sessionID, serverHash, type))
if (await ProtocolHandler.SessionCheckAsync(uuid, sessionID, serverHash, type))
{
session.ServerIDhash = serverIDhash;
session.ServerPublicKey = serverPublicKey;
@ -604,14 +668,14 @@ namespace MinecraftClient.Protocol.Handlers
token_enc.CopyTo(data, 5 + (short)key_enc.Length);
//Send it back
Send(data);
await SendAsync(data, cancellationToken);
//Getting the next packet
byte[] pid = new byte[1];
Receive(pid, 0, 1, SocketFlags.None);
await ReceiveAsync(pid, 0, 1, cancellationToken);
if (pid[0] == 0xFC)
{
ReadData(4);
await ReadDataAsync(4, cancellationToken);
s = new AesCfb8Stream(c.GetStream(), secretKey);
encrypted = true;
return true;
@ -625,9 +689,14 @@ namespace MinecraftClient.Protocol.Handlers
public bool Login(PlayerKeyPair? playerKeyPair, SessionToken session, bool isTransfer = false)
{
if (Handshake(handler.GetUserUuidStr(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort(), session))
return LoginAsync(playerKeyPair, session, isTransfer).GetAwaiter().GetResult();
}
private async Task<bool> LoginAsync(PlayerKeyPair? playerKeyPair, SessionToken session, bool isTransfer = false)
{
if (await HandshakeAsync(handler.GetUserUuidStr(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort(), session))
{
Send(new byte[] { 0xCD, 0 });
await SendAsync([0xCD, 0]);
try
{
byte[] pid = new byte[1];
@ -635,22 +704,24 @@ namespace MinecraftClient.Protocol.Handlers
{
if (c.Connected)
{
Receive(pid, 0, 1, SocketFlags.None);
await ReceiveAsync(pid, 0, 1);
while (pid[0] >= 0xC0 && pid[0] != 0xFF) //Skip some early packets or plugin messages
{
using (MainThreadExecutionScope.Enter(handler))
ProcessPacket(pid[0]);
Receive(pid, 0, 1, SocketFlags.None);
await ReceiveAsync(pid, 0, 1);
}
if (pid[0] == (byte)1)
{
ReadData(4); ReadNextString(); ReadData(5);
await ReadDataAsync(4);
_ = await ReadNextStringAsync();
await ReadDataAsync(5);
StartUpdating();
return true; //The Server accepted the request
}
else if (pid[0] == (byte)0xFF)
{
string reason = ReadNextString();
string reason = await ReadNextStringAsync();
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, reason);
return false;
}

View file

@ -21,6 +21,7 @@ using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.Handlers.packet.s2c;
using MinecraftClient.Protocol.Handlers.PacketPalettes;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Protocol.PacketPipeline;
using MinecraftClient.Protocol.ProfileKey;
using MinecraftClient.Protocol.Session;
using MinecraftClient.Proxy;
@ -91,7 +92,7 @@ namespace MinecraftClient.Protocol.Handlers
private readonly int rawProtocolVersion;
private int currentDimension;
private bool isOnlineMode = false;
private readonly BlockingCollection<Tuple<int, Queue<byte>>> packetQueue = new();
private readonly BlockingCollection<IncomingPacket> packetQueue = new();
private readonly Dictionary<string, bool> legacyAchievementProgress = new(StringComparer.Ordinal);
private float LastYaw, LastPitch;
private double lastSentX, lastSentY, lastSentZ;
@ -309,8 +310,7 @@ namespace MinecraftClient.Protocol.Handlers
if (packetQueue.TryTake(out var packetInfo, 1))
{
var (packetId, packetData) = packetInfo;
HandlePacket(packetId, packetData);
HandlePacket(packetInfo.PacketId, packetInfo.CreateReader());
continue;
}
@ -393,27 +393,27 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
/// <param name="packetId">will contain packet ID</param>
/// <param name="packetData">will contain raw packet Data</param>
internal Tuple<int, Queue<byte>> ReadNextPacket()
internal IncomingPacket ReadNextPacket()
{
var (packetId, packetData) = socketWrapper.GetNextPacket(
IncomingPacket packet = socketWrapper.GetNextPacket(
protocolVersion >= MC_1_8_Version ? compression_treshold : -1,
dataTypes);
if (handler.GetNetworkPacketCaptureEnabled())
handler.OnNetworkPacket(packetId, packetData.ToList(), currentState == CurrentState.Login, true);
handler.OnNetworkPacket(packet.PacketId, packet.Payload.ToList(), currentState == CurrentState.Login, true);
return new(packetId, packetData);
return packet;
}
internal async Task<Tuple<int, Queue<byte>>> ReadNextPacketAsync(CancellationToken cancellationToken)
internal async Task<IncomingPacket> ReadNextPacketAsync(CancellationToken cancellationToken)
{
var (packetId, packetData) = await socketWrapper.GetNextPacketAsync(
IncomingPacket packet = await socketWrapper.GetNextPacketAsync(
protocolVersion >= MC_1_8_Version ? compression_treshold : -1,
dataTypes,
cancellationToken);
if (handler.GetNetworkPacketCaptureEnabled())
handler.OnNetworkPacket(packetId, packetData.ToList(), currentState == CurrentState.Login, true);
handler.OnNetworkPacket(packet.PacketId, packet.Payload.ToList(), currentState == CurrentState.Login, true);
return new(packetId, packetData);
return packet;
}
/// <summary>
@ -422,11 +422,9 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="packetId">Packet ID</param>
/// <param name="packetData">Packet contents</param>
/// <returns>TRUE if the packet was processed, FALSE if ignored or unknown</returns>
internal bool HandlePacket(int packetId, Queue<byte> packetData)
internal bool HandlePacket(int packetId, PacketReader packetData)
{
// This copy is necessary because by the time we get to the catch block,
// the packetData queue will have been processed and the data will be lost
var _copy = packetData.ToArray();
byte[] _copy = packetData.GetRawData();
try
{
@ -488,11 +486,11 @@ namespace MinecraftClient.Protocol.Handlers
break;
case ConfigurationPacketTypesIn.KeepAlive:
SendPacket(ConfigurationPacketTypesOut.KeepAlive, packetData);
SendPacket(ConfigurationPacketTypesOut.KeepAlive, packetData.CopyRemaining());
break;
case ConfigurationPacketTypesIn.Ping:
SendPacket(ConfigurationPacketTypesOut.Pong, packetData);
SendPacket(ConfigurationPacketTypesOut.Pong, packetData.CopyRemaining());
break;
case ConfigurationPacketTypesIn.RegistryData:
@ -674,7 +672,7 @@ namespace MinecraftClient.Protocol.Handlers
return true;
}
public void HandleResourcePackPacket(Queue<byte> packetData)
public void HandleResourcePackPacket(PacketReader packetData)
{
var uuid = Guid.Empty;
@ -722,17 +720,17 @@ namespace MinecraftClient.Protocol.Handlers
}
}
private bool HandlePlayPackets(int packetId, Queue<byte> packetData)
private bool HandlePlayPackets(int packetId, PacketReader packetData)
{
switch (packetPalette.GetIncomingTypeById(packetId))
{
case PacketTypesIn.KeepAlive: // Keep Alive (Play)
SendPacket(PacketTypesOut.KeepAlive, packetData);
SendPacket(PacketTypesOut.KeepAlive, packetData.CopyRemaining());
handler.OnServerKeepAlive();
break;
case PacketTypesIn.Ping:
SendPacket(PacketTypesOut.Pong, packetData);
SendPacket(PacketTypesOut.Pong, packetData.CopyRemaining());
break;
case PacketTypesIn.JoinGame:
@ -1602,7 +1600,7 @@ namespace MinecraftClient.Protocol.Handlers
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap,
currentDimension == 0, chunksContinuous, currentDimension,
new Queue<byte>(decompressed));
new PacketReader(decompressed));
Interlocked.Decrement(ref handler.GetWorld().chunkLoadNotCompleted);
}
else
@ -1984,7 +1982,7 @@ namespace MinecraftClient.Protocol.Handlers
hasSkyLight = dataTypes.ReadNextBool(packetData);
var compressed = dataTypes.ReadData(compressedDataSize, packetData);
var decompressed = ZlibUtils.Decompress(compressed);
chunkData = new Queue<byte>(decompressed);
chunkData = new PacketReader(decompressed);
}
else
{
@ -2297,7 +2295,7 @@ namespace MinecraftClient.Protocol.Handlers
// Length is unneeded as the whole remaining packetData is the entire payload of the packet.
if (protocolVersion < MC_1_8_Version)
pForge.ReadNextVarShort(packetData);
handler.OnPluginChannelMessage(channel, packetData.ToArray());
handler.OnPluginChannelMessage(channel, packetData.CopyRemaining());
return pForge.HandlePluginMessage(channel, packetData, ref currentDimension);
case PacketTypesIn.Disconnect:
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick,
@ -3323,7 +3321,7 @@ namespace MinecraftClient.Protocol.Handlers
/// Read a Holder&lt;SoundEvent&gt; from packet data and return its key when inline.
/// Returns null when the holder is a registry reference.
/// </summary>
private string? ReadSoundEventHolderName(Queue<byte> packetData)
private string? ReadSoundEventHolderName(PacketReader packetData)
{
int soundHolderId = dataTypes.ReadNextVarInt(packetData);
if (soundHolderId != 0)
@ -3339,7 +3337,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Handle the Statistics packet for pre-1.12 legacy achievements.
/// </summary>
private void HandleLegacyStatistics(Queue<byte> packetData)
private void HandleLegacyStatistics(PacketReader packetData)
{
int statCount = dataTypes.ReadNextVarInt(packetData);
@ -3370,7 +3368,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Handle the Advancements packet (1.12+).
/// </summary>
private void HandleAdvancements(Queue<byte> packetData)
private void HandleAdvancements(PacketReader packetData)
{
bool reset = dataTypes.ReadNextBool(packetData);
@ -3541,14 +3539,14 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Handle the SelectAdvancementTab packet.
/// </summary>
private void HandleSelectAdvancementTab(Queue<byte> packetData)
private void HandleSelectAdvancementTab(PacketReader packetData)
{
bool hasTab = dataTypes.ReadNextBool(packetData);
string? tabId = hasTab ? dataTypes.ReadNextString(packetData) : null;
handler.OnSelectAdvancementTab(tabId);
}
private void HandleUnlockRecipes(Queue<byte> packetData)
private void HandleUnlockRecipes(PacketReader packetData)
{
int action = dataTypes.ReadNextVarInt(packetData);
if (!SkipRecipeBookSettings(packetData))
@ -3576,7 +3574,7 @@ namespace MinecraftClient.Protocol.Handlers
}
}
private void HandleRecipeBookAdd(Queue<byte> packetData)
private void HandleRecipeBookAdd(PacketReader packetData)
{
int entryCount = dataTypes.ReadNextVarInt(packetData);
RecipeBookRecipeEntry[] recipeEntries = new RecipeBookRecipeEntry[entryCount];
@ -3593,7 +3591,7 @@ namespace MinecraftClient.Protocol.Handlers
handler.OnRecipeBookAdd(recipeEntries, replace);
}
private string[] ReadRecipeBookRecipeIds(Queue<byte> packetData)
private string[] ReadRecipeBookRecipeIds(PacketReader packetData)
{
int recipeCount = dataTypes.ReadNextVarInt(packetData);
string[] recipeIds = new string[recipeCount];
@ -3604,7 +3602,7 @@ namespace MinecraftClient.Protocol.Handlers
return recipeIds;
}
private string[] ReadRecipeBookDisplayIds(Queue<byte> packetData)
private string[] ReadRecipeBookDisplayIds(PacketReader packetData)
{
int recipeCount = dataTypes.ReadNextVarInt(packetData);
string[] recipeIds = new string[recipeCount];
@ -3615,7 +3613,7 @@ namespace MinecraftClient.Protocol.Handlers
return recipeIds;
}
private RecipeBookRecipeEntry ReadRecipeBookDisplayEntry(Queue<byte> packetData)
private RecipeBookRecipeEntry ReadRecipeBookDisplayEntry(PacketReader packetData)
{
int displayId = dataTypes.ReadNextVarInt(packetData);
string resultLabel = ReadRecipeDisplayResultLabel(packetData);
@ -3629,7 +3627,7 @@ namespace MinecraftClient.Protocol.Handlers
return new RecipeBookRecipeEntry(commandId, displayText);
}
private string ReadRecipeDisplayResultLabel(Queue<byte> packetData)
private string ReadRecipeDisplayResultLabel(PacketReader packetData)
{
int displayType = dataTypes.ReadNextVarInt(packetData);
return displayType switch
@ -3643,7 +3641,7 @@ namespace MinecraftClient.Protocol.Handlers
};
}
private string ReadShapelessRecipeDisplayResultLabel(Queue<byte> packetData)
private string ReadShapelessRecipeDisplayResultLabel(PacketReader packetData)
{
int ingredientCount = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < ingredientCount; i++)
@ -3654,7 +3652,7 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
private string ReadShapedRecipeDisplayResultLabel(Queue<byte> packetData)
private string ReadShapedRecipeDisplayResultLabel(PacketReader packetData)
{
_ = dataTypes.ReadNextVarInt(packetData); // width
_ = dataTypes.ReadNextVarInt(packetData); // height
@ -3667,7 +3665,7 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
private string ReadFurnaceRecipeDisplayResultLabel(Queue<byte> packetData)
private string ReadFurnaceRecipeDisplayResultLabel(PacketReader packetData)
{
_ = ReadSlotDisplayLabel(packetData); // ingredient
_ = ReadSlotDisplayLabel(packetData); // fuel
@ -3678,7 +3676,7 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
private string ReadStonecutterRecipeDisplayResultLabel(Queue<byte> packetData)
private string ReadStonecutterRecipeDisplayResultLabel(PacketReader packetData)
{
_ = ReadSlotDisplayLabel(packetData); // input
string result = ReadSlotDisplayLabel(packetData);
@ -3686,7 +3684,7 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
private string ReadSmithingRecipeDisplayResultLabel(Queue<byte> packetData)
private string ReadSmithingRecipeDisplayResultLabel(PacketReader packetData)
{
_ = ReadSlotDisplayLabel(packetData); // template
_ = ReadSlotDisplayLabel(packetData); // base
@ -3696,7 +3694,7 @@ namespace MinecraftClient.Protocol.Handlers
return result;
}
private string ReadSlotDisplayLabel(Queue<byte> packetData)
private string ReadSlotDisplayLabel(PacketReader packetData)
{
int slotDisplayType = dataTypes.ReadNextVarInt(packetData);
@ -3739,7 +3737,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Reads a with_any_potion slot display (26.1+): contains a nested SlotDisplay.
/// </summary>
private string ReadWithAnyPotionSlotDisplayLabel(Queue<byte> packetData)
private string ReadWithAnyPotionSlotDisplayLabel(PacketReader packetData)
{
return ReadSlotDisplayLabel(packetData);
}
@ -3747,7 +3745,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Reads an only_with_component slot display (26.1+): contains a nested SlotDisplay and a DataComponentType VarInt ID.
/// </summary>
private string ReadOnlyWithComponentSlotDisplayLabel(Queue<byte> packetData)
private string ReadOnlyWithComponentSlotDisplayLabel(PacketReader packetData)
{
string sourceLabel = ReadSlotDisplayLabel(packetData);
_ = dataTypes.ReadNextVarInt(packetData); // DataComponentType registry id
@ -3757,14 +3755,14 @@ namespace MinecraftClient.Protocol.Handlers
/// <summary>
/// Reads a dyed slot display (26.1+): contains two nested SlotDisplays (dye + target).
/// </summary>
private string ReadDyedSlotDisplayLabel(Queue<byte> packetData)
private string ReadDyedSlotDisplayLabel(PacketReader packetData)
{
_ = ReadSlotDisplayLabel(packetData); // dye
string targetLabel = ReadSlotDisplayLabel(packetData); // target
return targetLabel;
}
private string ReadSmithingTrimSlotDisplayLabel(Queue<byte> packetData)
private string ReadSmithingTrimSlotDisplayLabel(PacketReader packetData)
{
string baseLabel = ReadSlotDisplayLabel(packetData);
_ = ReadSlotDisplayLabel(packetData); // material
@ -3772,14 +3770,14 @@ namespace MinecraftClient.Protocol.Handlers
return baseLabel;
}
private string ReadWithRemainderSlotDisplayLabel(Queue<byte> packetData)
private string ReadWithRemainderSlotDisplayLabel(PacketReader packetData)
{
string inputLabel = ReadSlotDisplayLabel(packetData);
_ = ReadSlotDisplayLabel(packetData); // remainder
return inputLabel;
}
private string ReadCompositeSlotDisplayLabel(Queue<byte> packetData)
private string ReadCompositeSlotDisplayLabel(PacketReader packetData)
{
int optionCount = dataTypes.ReadNextVarInt(packetData);
string label = "Composite";
@ -3798,12 +3796,12 @@ namespace MinecraftClient.Protocol.Handlers
/// Read an ItemStackTemplate (26.1+) which encodes fields in a different order
/// than ItemStack: item_id (VarInt), count (VarInt), DataComponentPatch.
/// </summary>
private string ReadItemStackTemplateLabel(Queue<byte> packetData)
private string ReadItemStackTemplateLabel(PacketReader packetData)
{
return dataTypes.ReadNextItemStackTemplate(packetData, itemPalette).GetTypeString();
}
private void SkipOptionalCraftingRequirements(Queue<byte> packetData)
private void SkipOptionalCraftingRequirements(PacketReader packetData)
{
if (!dataTypes.ReadNextBool(packetData))
return;
@ -3813,7 +3811,7 @@ namespace MinecraftClient.Protocol.Handlers
SkipItemHolderSet(packetData);
}
private void SkipItemHolderSet(Queue<byte> packetData)
private void SkipItemHolderSet(PacketReader packetData)
{
int entryCount = dataTypes.ReadNextVarInt(packetData) - 1;
if (entryCount == -1)
@ -3826,12 +3824,12 @@ namespace MinecraftClient.Protocol.Handlers
_ = dataTypes.ReadNextVarInt(packetData);
}
private bool SkipRecipeBookSettings(Queue<byte> packetData)
private bool SkipRecipeBookSettings(PacketReader packetData)
{
// MC 1.13 uses 4 booleans for the crafting/smelting recipe book states.
// MC 1.14+ expands this to 8 booleans by adding blast furnace and smoker states.
int boolCount = protocolVersion >= MC_1_14_Version ? 8 : 4;
if (packetData.Count < boolCount)
if (packetData.RemainingLength < boolCount)
return false;
for (int i = 0; i < boolCount; i++)
@ -3896,9 +3894,9 @@ namespace MinecraftClient.Protocol.Handlers
SendPacket(packetPalette.GetOutgoingIdByType(packet), packetData);
}
private void ProcessChunkBlockEntityData(int chunkX, int chunkZ, Queue<byte> packetData)
private void ProcessChunkBlockEntityData(int chunkX, int chunkZ, PacketReader packetData)
{
if (protocolVersion < MC_1_17_Version || packetData.Count == 0)
if (protocolVersion < MC_1_17_Version || packetData.RemainingLength == 0)
return;
int blockEntityCount = dataTypes.ReadNextVarInt(packetData);
@ -4057,7 +4055,9 @@ namespace MinecraftClient.Protocol.Handlers
// 3. Encryption Request - 9. Login Acknowledged
while (true)
{
var (packetId, packetData) = await ReadNextPacketAsync(CancellationToken.None);
IncomingPacket packet = await ReadNextPacketAsync(CancellationToken.None);
int packetId = packet.PacketId;
PacketReader packetData = packet.CreateReader();
switch (packetId)
{
@ -4195,7 +4195,9 @@ namespace MinecraftClient.Protocol.Handlers
int loopPrevention = ushort.MaxValue;
while (true)
{
var (packetId, packetData) = await ReadNextPacketAsync(CancellationToken.None);
IncomingPacket packet = await ReadNextPacketAsync(CancellationToken.None);
int packetId = packet.PacketId;
PacketReader packetData = packet.CreateReader();
if (packetId < 0 || loopPrevention-- < 0) // Failed to read packet or too many iterations (issue #1150)
{
handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost,
@ -4356,11 +4358,12 @@ namespace MinecraftClient.Protocol.Handlers
var statusRequest = DataTypes.GetVarInt(0);
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(statusRequest.Length), statusRequest));
var (statusPacketId, packetData) = socketWrapper.GetNextPacket(-1, dataTypes);
if (statusPacketId != 0x00)
IncomingPacket statusPacket = socketWrapper.GetNextPacket(-1, dataTypes);
if (statusPacket.PacketId != 0x00)
return false;
// Get the Json data
var packetData = statusPacket.CreateReader();
var result = dataTypes.ReadNextString(packetData);
if (Config.Logging.DebugMessages)
@ -4436,9 +4439,10 @@ namespace MinecraftClient.Protocol.Handlers
var pingRequest = dataTypes.ConcatBytes(DataTypes.GetVarInt(0x01), DataTypes.GetLong(pingPayload));
socketWrapper.SendDataRAW(dataTypes.ConcatBytes(DataTypes.GetVarInt(pingRequest.Length), pingRequest));
var (pongPacketId, pongPacketData) = socketWrapper.GetNextPacket(-1, dataTypes);
if (pongPacketId == 0x01)
IncomingPacket pongPacket = socketWrapper.GetNextPacket(-1, dataTypes);
if (pongPacket.PacketId == 0x01)
{
var pongPacketData = pongPacket.CreateReader();
long pongPayload = dataTypes.ReadNextLong(pongPacketData);
pingMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - pingPayload;
}

View file

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using MinecraftClient.Protocol.Handlers.Forge;
using MinecraftClient.Protocol.Message;
using MinecraftClient.Protocol.PacketPipeline;
using MinecraftClient.Scripting;
namespace MinecraftClient.Protocol.Handlers
@ -52,7 +53,9 @@ namespace MinecraftClient.Protocol.Handlers
{
while (fmlHandshakeState != FMLHandshakeClientState.DONE)
{
(int packetID, Queue<byte> packetData) = await protocol18.ReadNextPacketAsync(cancellationToken);
IncomingPacket packet = await protocol18.ReadNextPacketAsync(cancellationToken);
int packetID = packet.PacketId;
PacketReader packetData = packet.CreateReader();
if (packetID == 0x40) // Disconnect
{
@ -93,7 +96,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(PacketReader packetData)
{
if (ForgeEnabled())
{
@ -114,7 +117,7 @@ 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 bool HandlePluginMessage(string channel, PacketReader packetData, ref int currentDimension)
{
if (ForgeEnabled() && forgeInfo!.Version == FMLVersion.FML && fmlHandshakeState != FMLHandshakeClientState.DONE)
{
@ -243,7 +246,7 @@ 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 bool HandleLoginPluginRequest(string channel, PacketReader packetData, ref List<byte> responseData)
{
if (ForgeEnabled() && (forgeInfo!.Version == FMLVersion.FML2 || forgeInfo!.Version == FMLVersion.FML3) && channel == "fml:loginwrapper")
{
@ -332,7 +335,7 @@ namespace MinecraftClient.Protocol.Handlers
// FML3 specific,
List<string> dataPackRegistries = new();
if (forgeInfo!.Version == FMLVersion.FML3 && packetData.Count != 0)
if (forgeInfo!.Version == FMLVersion.FML3 && packetData.RemainingLength != 0)
{
int dataPackRegistryCount = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < dataPackRegistryCount; i++)

View file

@ -6,6 +6,7 @@ using System.Runtime.InteropServices;
//using System.Linq;
//using System.Text;
using MinecraftClient.Mapping;
using MinecraftClient.Protocol.PacketPipeline;
namespace MinecraftClient.Protocol.Handlers
{
@ -23,7 +24,7 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
/// <param name="cache">Cache for reading data</param>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private Chunk? ReadBlockStatesField(Queue<byte> cache)
private Chunk? ReadBlockStatesField(PacketReader cache)
{
// read Block states (Type: Paletted Container)
byte bitsPerEntry = dataTypes.ReadNextByte(cache);
@ -134,7 +135,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="cache">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 void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[]? verticalStripBitmask, PacketReader cache)
{
World world = handler.GetWorld();
@ -236,7 +237,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="cache">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 void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, ushort chunkMask2, bool hasSkyLight, bool chunksContinuous, int currentDimension, PacketReader cache)
{
World world = handler.GetWorld();

View file

@ -107,24 +107,24 @@ namespace MinecraftClient.Protocol.Handlers
return Array.Empty<byte>();
}
internal Tuple<int, Queue<byte>> GetNextPacket(int compressionThreshold, DataTypes dataTypes)
internal IncomingPacket GetNextPacket(int compressionThreshold, DataTypes dataTypes)
{
int packetLength = ReadNextVarIntRaw();
using PacketReadStream packetStream = new(readStream, packetLength);
byte[] payload = ReadPacketPayload(packetStream, compressionThreshold);
Queue<byte> packetData = new(payload);
var packetData = new PacketReader(payload);
int packetId = dataTypes.ReadNextVarInt(packetData);
return new(packetId, packetData);
return new(packetId, packetData.CopyRemaining());
}
internal async Task<Tuple<int, Queue<byte>>> GetNextPacketAsync(int compressionThreshold, DataTypes dataTypes, CancellationToken cancellationToken)
internal async Task<IncomingPacket> GetNextPacketAsync(int compressionThreshold, DataTypes dataTypes, CancellationToken cancellationToken)
{
int packetLength = await ReadNextVarIntRawAsync(cancellationToken);
await using PacketReadStream packetStream = new(readStream, packetLength);
byte[] payload = await ReadPacketPayloadAsync(packetStream, compressionThreshold, cancellationToken);
Queue<byte> packetData = new(payload);
var packetData = new PacketReader(payload);
int packetId = dataTypes.ReadNextVarInt(packetData);
return new(packetId, packetData);
return new(packetId, packetData.CopyRemaining());
}
/// <summary>

View file

@ -0,0 +1,13 @@
using System;
namespace MinecraftClient.Protocol.PacketPipeline;
internal readonly record struct IncomingPacket(int PacketId, byte[] Payload)
{
public PacketReader CreateReader()
{
return new PacketReader(Payload);
}
public ReadOnlySpan<byte> PayloadSpan => Payload;
}

View file

@ -0,0 +1,123 @@
using System;
using System.Buffers.Binary;
namespace MinecraftClient.Protocol.PacketPipeline;
public sealed class PacketReader
{
private readonly byte[] buffer;
private int position;
public PacketReader(byte[] buffer)
{
ArgumentNullException.ThrowIfNull(buffer);
this.buffer = buffer;
}
public int Position => position;
public int RemainingLength => buffer.Length - position;
public ReadOnlySpan<byte> RemainingSpan => buffer.AsSpan(position);
public ReadOnlySpan<byte> FullSpan => buffer;
public byte[] GetRawData() => buffer;
public byte ReadByte()
{
EnsureAvailable(1);
return buffer[position++];
}
public byte[] ReadData(int length)
{
if (length == 0)
return [];
EnsureAvailable(length);
byte[] data = GC.AllocateUninitializedArray<byte>(length);
Buffer.BlockCopy(buffer, position, data, 0, length);
position += length;
return data;
}
public void ReadData(Span<byte> destination)
{
if (destination.Length == 0)
return;
EnsureAvailable(destination.Length);
buffer.AsSpan(position, destination.Length).CopyTo(destination);
position += destination.Length;
}
public void ReadDataReverse(Span<byte> destination)
{
if (destination.Length == 0)
return;
EnsureAvailable(destination.Length);
for (int i = destination.Length - 1; i >= 0; --i)
destination[i] = buffer[position++];
}
public void Skip(int length)
{
if (length < 0)
throw new ArgumentOutOfRangeException(nameof(length));
EnsureAvailable(length);
position += length;
}
public ushort ReadUInt16BigEndian()
{
EnsureAvailable(sizeof(ushort));
ushort value = BinaryPrimitives.ReadUInt16BigEndian(buffer.AsSpan(position, sizeof(ushort)));
position += sizeof(ushort);
return value;
}
public short ReadInt16BigEndian()
{
EnsureAvailable(sizeof(short));
short value = BinaryPrimitives.ReadInt16BigEndian(buffer.AsSpan(position, sizeof(short)));
position += sizeof(short);
return value;
}
public int ReadInt32BigEndian()
{
EnsureAvailable(sizeof(int));
int value = BinaryPrimitives.ReadInt32BigEndian(buffer.AsSpan(position, sizeof(int)));
position += sizeof(int);
return value;
}
public long ReadInt64BigEndian()
{
EnsureAvailable(sizeof(long));
long value = BinaryPrimitives.ReadInt64BigEndian(buffer.AsSpan(position, sizeof(long)));
position += sizeof(long);
return value;
}
public byte[] CopyRemaining()
{
return ReadOnlySpanToArray(RemainingSpan);
}
private void EnsureAvailable(int length)
{
if (RemainingLength < length)
throw new OverflowException("Reached the end of the packet.");
}
private static byte[] ReadOnlySpanToArray(ReadOnlySpan<byte> span)
{
if (span.IsEmpty)
return [];
byte[] copy = GC.AllocateUninitializedArray<byte>(span.Length);
span.CopyTo(copy);
return copy;
}
}