mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Added 1.19.1/2 + Terrain Handling
Added 1.19.1/2 + Terrain Handling
This commit is contained in:
commit
a383ead27f
26 changed files with 1433 additions and 576 deletions
|
|
@ -22,6 +22,13 @@ namespace MinecraftClient.Mapping
|
|||
/// </summary>
|
||||
public readonly bool piglinSafe = false;
|
||||
|
||||
/// <summary>
|
||||
/// Possibly the light level(s) at which monsters can spawn.
|
||||
/// </summary>
|
||||
public readonly int monsterSpawnMinLightLevel = 0;
|
||||
public readonly int monsterSpawnMaxLightLevel = 7;
|
||||
public readonly int monsterSpawnBlockLightLimit = 0;
|
||||
|
||||
/// <summary>
|
||||
/// When false, compasses spin randomly. When true, nether portals can spawn zombified piglins.
|
||||
/// </summary>
|
||||
|
|
@ -132,6 +139,25 @@ namespace MinecraftClient.Mapping
|
|||
|
||||
if (nbt.ContainsKey("piglin_safe"))
|
||||
this.piglinSafe = 1 == (byte)nbt["piglin_safe"];
|
||||
if (nbt.ContainsKey("monster_spawn_light_level"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var monsterSpawnLightLevelObj = nbt["monster_spawn_light_level"];
|
||||
if (monsterSpawnLightLevelObj.GetType() == typeof(int))
|
||||
this.monsterSpawnMinLightLevel = this.monsterSpawnMaxLightLevel = (int)monsterSpawnLightLevelObj;
|
||||
else
|
||||
{
|
||||
var inclusive = (Dictionary<string, object>)(((Dictionary<string, object>)monsterSpawnLightLevelObj)["value"]);
|
||||
this.monsterSpawnMinLightLevel = (int)inclusive["min_inclusive"];
|
||||
this.monsterSpawnMaxLightLevel = (int)inclusive["max_inclusive"];
|
||||
}
|
||||
|
||||
}
|
||||
catch (KeyNotFoundException) { }
|
||||
}
|
||||
if (nbt.ContainsKey("monster_spawn_block_light_limit"))
|
||||
this.monsterSpawnBlockLightLimit = (int)nbt["monster_spawn_block_light_limit"];
|
||||
if (nbt.ContainsKey("natural"))
|
||||
this.natural = 1 == (byte)nbt["natural"];
|
||||
if (nbt.ContainsKey("ambient_light"))
|
||||
|
|
|
|||
|
|
@ -65,6 +65,9 @@ namespace MinecraftClient.Mapping
|
|||
case Material.BlackShulkerBox:
|
||||
case Material.BlackStainedGlass:
|
||||
case Material.BlackStainedGlassPane:
|
||||
case Material.Blackstone:
|
||||
case Material.BlackstoneSlab:
|
||||
case Material.BlackstoneStairs:
|
||||
case Material.BlackTerracotta:
|
||||
case Material.BlackWool:
|
||||
case Material.BlastFurnace:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ namespace MinecraftClient.Mapping
|
|||
/// <summary>
|
||||
/// The dimension info of the world
|
||||
/// </summary>
|
||||
private static Dimension dimension = new Dimension();
|
||||
private static Dimension curDimension = new Dimension();
|
||||
|
||||
private static Dictionary<string, Dimension>? dimensionList = null;
|
||||
|
||||
/// <summary>
|
||||
/// Lock for thread safety
|
||||
|
|
@ -89,24 +91,42 @@ namespace MinecraftClient.Mapping
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set dimension type
|
||||
/// Storage of all dimensional data - 1.19.1 and above
|
||||
/// </summary>
|
||||
/// <param name="registryCodec">Registry Codec nbt data</param>
|
||||
public static void StoreDimensionList(Dictionary<string, object> registryCodec)
|
||||
{
|
||||
dimensionList = new();
|
||||
var dimensionListNbt = (object[])(((Dictionary<string, object>)registryCodec["minecraft:dimension_type"])["value"]);
|
||||
foreach (Dictionary<string, object> dimensionNbt in dimensionListNbt)
|
||||
{
|
||||
string dimensionName = (string)dimensionNbt["name"];
|
||||
Dictionary<string, object> element = (Dictionary<string, object>)dimensionNbt["element"];
|
||||
dimensionList.Add(dimensionName, new Dimension(dimensionName, element));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set current dimension - 1.16 and above
|
||||
/// </summary>
|
||||
/// <param name="name"> The name of the dimension type</param>
|
||||
/// <param name="nbt">The dimension type (NBT Tag Compound)</param>
|
||||
public static void SetDimension(string name, Dictionary<string, object> nbt)
|
||||
public static void SetDimension(string name)
|
||||
{
|
||||
// will change in 1.19 and above
|
||||
dimension = new Dimension(name, nbt);
|
||||
curDimension = dimensionList![name]; // Should not fail
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get current dimension
|
||||
/// </summary>
|
||||
/// <returns>Current dimension</returns>
|
||||
public static Dimension GetDimension()
|
||||
{
|
||||
return dimension;
|
||||
return curDimension;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ using MinecraftClient.Mapping;
|
|||
using MinecraftClient.Inventory;
|
||||
using MinecraftClient.Logger;
|
||||
using MinecraftClient.Protocol.Keys;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient
|
||||
{
|
||||
|
|
@ -68,9 +69,10 @@ namespace MinecraftClient
|
|||
private int port;
|
||||
private int protocolversion;
|
||||
private string username;
|
||||
private string uuid;
|
||||
private Guid uuid;
|
||||
private string uuidStr;
|
||||
private string sessionid;
|
||||
private PlayerKeyPair playerKeyPair;
|
||||
private PlayerKeyPair? playerKeyPair;
|
||||
private DateTime lastKeepAlive;
|
||||
private object lastKeepAliveLock = new();
|
||||
private int respawnTicks = 0;
|
||||
|
|
@ -104,7 +106,8 @@ namespace MinecraftClient
|
|||
public int GetServerPort() { return port; }
|
||||
public string GetServerHost() { return host; }
|
||||
public string GetUsername() { return username; }
|
||||
public string GetUserUUID() { return uuid; }
|
||||
public Guid GetUserUuid() { return uuid; }
|
||||
public string GetUserUuidStr() { return uuidStr; }
|
||||
public string GetSessionID() { return sessionid; }
|
||||
public Location GetCurrentLocation() { return location; }
|
||||
public float GetYaw() { return playerYaw; }
|
||||
|
|
@ -141,7 +144,7 @@ namespace MinecraftClient
|
|||
/// <param name="server_ip">The server IP</param>
|
||||
/// <param name="port">The server port to use</param>
|
||||
/// <param name="protocolversion">Minecraft protocol version to use</param>
|
||||
public McClient(string username, string uuid, string sessionID, PlayerKeyPair playerKeyPair, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port)
|
||||
public McClient(string username, string uuid, string sessionID, PlayerKeyPair? playerKeyPair, int protocolversion, ForgeInfo forgeInfo, string server_ip, ushort port)
|
||||
{
|
||||
StartClient(username, uuid, sessionID, playerKeyPair, server_ip, port, protocolversion, forgeInfo, false, "");
|
||||
}
|
||||
|
|
@ -156,7 +159,7 @@ namespace MinecraftClient
|
|||
/// <param name="port">The server port to use</param>
|
||||
/// <param name="protocolversion">Minecraft protocol version to use</param>
|
||||
/// <param name="command">The text or command to send.</param>
|
||||
public McClient(string username, string uuid, string sessionID, PlayerKeyPair playerKeyPair, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command)
|
||||
public McClient(string username, string uuid, string sessionID, PlayerKeyPair? playerKeyPair, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, string command)
|
||||
{
|
||||
StartClient(username, uuid, sessionID, playerKeyPair, server_ip, port, protocolversion, forgeInfo, true, command);
|
||||
}
|
||||
|
|
@ -172,7 +175,7 @@ namespace MinecraftClient
|
|||
/// <param name="uuid">The player's UUID for online-mode authentication</param>
|
||||
/// <param name="singlecommand">If set to true, the client will send a single command and then disconnect from the server</param>
|
||||
/// <param name="command">The text or command to send. Will only be sent if singlecommand is set to true.</param>
|
||||
private void StartClient(string user, string uuid, string sessionID, PlayerKeyPair playerKeyPair, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command)
|
||||
private void StartClient(string user, string uuid, string sessionID, PlayerKeyPair? playerKeyPair, string server_ip, ushort port, int protocolversion, ForgeInfo forgeInfo, bool singlecommand, string command)
|
||||
{
|
||||
terrainAndMovementsEnabled = Settings.TerrainAndMovements;
|
||||
inventoryHandlingEnabled = Settings.InventoryHandling;
|
||||
|
|
@ -180,7 +183,9 @@ namespace MinecraftClient
|
|||
|
||||
bool retry = false;
|
||||
this.sessionid = sessionID;
|
||||
this.uuid = uuid;
|
||||
if (!Guid.TryParse(uuid, out this.uuid))
|
||||
this.uuid = Guid.Empty;
|
||||
this.uuidStr = uuid;
|
||||
this.username = user;
|
||||
this.host = server_ip;
|
||||
this.port = port;
|
||||
|
|
@ -2093,19 +2098,17 @@ namespace MinecraftClient
|
|||
List<string> links = new();
|
||||
string messageText;
|
||||
|
||||
if (message.isJson)
|
||||
{
|
||||
if (message.isSignedChat)
|
||||
{
|
||||
if (!Settings.ShowIllegalSignedChat && !message.isSystemChat && !(bool)message.isSignatureLegal!)
|
||||
return;
|
||||
messageText = ChatParser.ParseSignedChat(message, links);
|
||||
}
|
||||
else
|
||||
messageText = ChatParser.ParseText(message.content, links);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (message.isJson)
|
||||
messageText = ChatParser.ParseText(message.content, links);
|
||||
else
|
||||
messageText = message.content;
|
||||
}
|
||||
|
||||
|
|
@ -2243,15 +2246,16 @@ namespace MinecraftClient
|
|||
if (player.Name == username)
|
||||
{
|
||||
// 1.19+ offline server is possible to return different uuid
|
||||
this.uuid = player.UUID.ToString().Replace("-", string.Empty);
|
||||
this.uuid = player.Uuid;
|
||||
this.uuidStr = player.Uuid.ToString().Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
lock (onlinePlayers)
|
||||
{
|
||||
onlinePlayers[player.UUID] = player;
|
||||
onlinePlayers[player.Uuid] = player;
|
||||
}
|
||||
|
||||
DispatchBotEvent(bot => bot.OnPlayerJoin(player.UUID, player.Name));
|
||||
DispatchBotEvent(bot => bot.OnPlayerJoin(player.Uuid, player.Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ namespace MinecraftClient
|
|||
|
||||
public const string Version = MCHighestVersion;
|
||||
public const string MCLowestVersion = "1.4.6";
|
||||
public const string MCHighestVersion = "1.19";
|
||||
public const string MCHighestVersion = "1.19.2";
|
||||
public static readonly string BuildInfo = null;
|
||||
|
||||
private static Tuple<Thread, CancellationTokenSource>? offlinePrompt = null;
|
||||
|
|
|
|||
|
|
@ -1105,9 +1105,58 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// </summary>
|
||||
/// <param name="bytes">Byte array</param>
|
||||
/// <returns>String representation</returns>
|
||||
public string ByteArrayToString(byte[] bytes)
|
||||
public string ByteArrayToString(byte[]? bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
return "null";
|
||||
else
|
||||
return BitConverter.ToString(bytes).Replace("-", " ");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write LastSeenMessageList
|
||||
/// </summary>
|
||||
/// <param name="msgList">Message.LastSeenMessageList</param>
|
||||
/// <param name="isOnlineMode">Whether the server is in online mode</param>
|
||||
/// <returns>Message.LastSeenMessageList Packet Data</returns>
|
||||
public byte[] GetLastSeenMessageList(Message.LastSeenMessageList msgList, bool isOnlineMode)
|
||||
{
|
||||
if (!isOnlineMode)
|
||||
return GetVarInt(0); // Message list size
|
||||
else
|
||||
{
|
||||
List<byte> fields = new();
|
||||
fields.AddRange(GetVarInt(msgList.entries.Length)); // Message list size
|
||||
foreach (Message.LastSeenMessageList.Entry entry in msgList.entries)
|
||||
{
|
||||
fields.AddRange(entry.profileId.ToBigEndianBytes()); // UUID
|
||||
fields.AddRange(GetVarInt(entry.lastSignature.Length)); // Signature length
|
||||
fields.AddRange(entry.lastSignature); // Signature data
|
||||
}
|
||||
return fields.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write LastSeenMessageList.Acknowledgment
|
||||
/// </summary>
|
||||
/// <param name="ack">Acknowledgment</param>
|
||||
/// <param name="isOnlineMode">Whether the server is in online mode</param>
|
||||
/// <returns>Acknowledgment Packet Data</returns>
|
||||
public byte[] GetAcknowledgment(Message.LastSeenMessageList.Acknowledgment ack, bool isOnlineMode)
|
||||
{
|
||||
List<byte> fields = new();
|
||||
fields.AddRange(GetLastSeenMessageList(ack.lastSeen, isOnlineMode));
|
||||
if (!isOnlineMode || ack.lastReceived == null)
|
||||
fields.AddRange(GetBool(false)); // Has last received message
|
||||
else
|
||||
{
|
||||
fields.AddRange(GetBool(true));
|
||||
fields.AddRange(ack.lastReceived.profileId.ToBigEndianBytes()); // Has last received message
|
||||
fields.AddRange(GetVarInt(ack.lastReceived.lastSignature.Length)); // Last received message signature length
|
||||
fields.AddRange(ack.lastReceived.lastSignature); // Last received message signature data
|
||||
}
|
||||
return fields.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,37 +7,37 @@ namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
|||
private Dictionary<int, PacketTypesIn> typeIn = new Dictionary<int, PacketTypesIn>()
|
||||
{
|
||||
{ 0x00, PacketTypesIn.SpawnEntity }, // Changed in 1.19 (Wiki name: Spawn Entity) - DONE
|
||||
{ 0x01, PacketTypesIn.SpawnExperienceOrb }, //(Wiki name: Spawn Exeprience Orb)
|
||||
{ 0x02, PacketTypesIn.SpawnPlayer },
|
||||
{ 0x03, PacketTypesIn.EntityAnimation }, //(Wiki name: Entity Animation (clientbound))
|
||||
{ 0x04, PacketTypesIn.Statistics }, //(Wiki name: Award Statistics)
|
||||
{ 0x05, PacketTypesIn.BlockChangedAck }, //Added 1.19 (Wiki name: Acknowledge Block Change) - DONE
|
||||
{ 0x06, PacketTypesIn.BlockBreakAnimation }, //(Wiki name: Set Block Destroy Stage)
|
||||
{ 0x07, PacketTypesIn.BlockEntityData },
|
||||
{ 0x08, PacketTypesIn.BlockAction },
|
||||
{ 0x09, PacketTypesIn.BlockChange }, //(Wiki name: Block Update)
|
||||
{ 0x0A, PacketTypesIn.BossBar },
|
||||
{ 0x01, PacketTypesIn.SpawnExperienceOrb }, // (Wiki name: Spawn Exeprience Orb)
|
||||
{ 0x02, PacketTypesIn.SpawnPlayer }, //
|
||||
{ 0x03, PacketTypesIn.EntityAnimation }, // (Wiki name: Entity Animation (clientbound))
|
||||
{ 0x04, PacketTypesIn.Statistics }, // (Wiki name: Award Statistics)
|
||||
{ 0x05, PacketTypesIn.BlockChangedAck }, // Added 1.19 (Wiki name: Acknowledge Block Change) - DONE
|
||||
{ 0x06, PacketTypesIn.BlockBreakAnimation }, // (Wiki name: Set Block Destroy Stage)
|
||||
{ 0x07, PacketTypesIn.BlockEntityData }, //
|
||||
{ 0x08, PacketTypesIn.BlockAction }, //
|
||||
{ 0x09, PacketTypesIn.BlockChange }, // (Wiki name: Block Update)
|
||||
{ 0x0A, PacketTypesIn.BossBar }, //
|
||||
{ 0x0B, PacketTypesIn.ServerDifficulty }, // (Wiki name: Change Difficulty)
|
||||
{ 0x0C, PacketTypesIn.ChatPreview }, // Added 1.19
|
||||
{ 0x0D, PacketTypesIn.ClearTiles },
|
||||
{ 0x0D, PacketTypesIn.ClearTiles }, //
|
||||
{ 0x0E, PacketTypesIn.TabComplete }, // (Wiki name: Command Suggestions Response)
|
||||
{ 0x0F, PacketTypesIn.DeclareCommands }, // (Wiki name: Commands)
|
||||
{ 0x10, PacketTypesIn.CloseWindow }, // (Wiki name: Close Container (clientbound))
|
||||
{ 0x11, PacketTypesIn.WindowItems }, // (Wiki name: Set Container Content)
|
||||
{ 0x12, PacketTypesIn.WindowProperty }, // (Wiki name: Set Container Property)
|
||||
{ 0x13, PacketTypesIn.SetSlot }, // (Wiki name: Set Container Slot)
|
||||
{ 0x14, PacketTypesIn.SetCooldown },
|
||||
{ 0x14, PacketTypesIn.SetCooldown }, //
|
||||
{ 0x15, PacketTypesIn.PluginMessage }, // (Wiki name: Plugin Message (clientbound))
|
||||
{ 0x16, PacketTypesIn.NamedSoundEffect }, // Changed in 1.19 (Added "Speed" field) (Wiki name: Custom Sound Effect) - DONE (No need to be implemented)
|
||||
{ 0x17, PacketTypesIn.Disconnect },
|
||||
{ 0x17, PacketTypesIn.Disconnect }, //
|
||||
{ 0x18, PacketTypesIn.EntityStatus }, // (Wiki name: Entity Event)
|
||||
{ 0x19, PacketTypesIn.Explosion }, // Changed in 1.19 (Location fields are now Double instead of Float) (Wiki name: Explosion) - DONE
|
||||
{ 0x1A, PacketTypesIn.UnloadChunk }, // (Wiki name: Forget Chunk)
|
||||
{ 0x1B, PacketTypesIn.ChangeGameState }, // (Wiki name: Game Event)
|
||||
{ 0x1C, PacketTypesIn.OpenHorseWindow }, // (Wiki name: Horse Screen Open)
|
||||
{ 0x1D, PacketTypesIn.InitializeWorldBorder },
|
||||
{ 0x1E, PacketTypesIn.KeepAlive },
|
||||
{ 0x1F, PacketTypesIn.ChunkData },
|
||||
{ 0x1D, PacketTypesIn.InitializeWorldBorder }, //
|
||||
{ 0x1E, PacketTypesIn.KeepAlive }, //
|
||||
{ 0x1F, PacketTypesIn.ChunkData }, //
|
||||
{ 0x20, PacketTypesIn.Effect }, // (Wiki name: Level Event)
|
||||
{ 0x21, PacketTypesIn.Particle }, // Changed in 1.19 ("Particle Data" field is now "Max Speed", it's the same Float data type) (Wiki name: Level Particle) - DONE (No need to be implemented)
|
||||
{ 0x22, PacketTypesIn.UpdateLight }, // (Wiki name: Light Update)
|
||||
|
|
@ -48,12 +48,12 @@ namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
|||
{ 0x27, PacketTypesIn.EntityPositionAndRotation }, // (Wiki name: Move Entity Position and Rotation)
|
||||
{ 0x28, PacketTypesIn.EntityRotation }, // (Wiki name: Move Entity Rotation)
|
||||
{ 0x29, PacketTypesIn.VehicleMove }, // (Wiki name: Move Vehicle)
|
||||
{ 0x2A, PacketTypesIn.OpenBook },
|
||||
{ 0x2A, PacketTypesIn.OpenBook }, //
|
||||
{ 0x2B, PacketTypesIn.OpenWindow }, // (Wiki name: Open Screen)
|
||||
{ 0x2C, PacketTypesIn.OpenSignEditor },
|
||||
{ 0x2C, PacketTypesIn.OpenSignEditor }, //
|
||||
{ 0x2D, PacketTypesIn.Ping }, // (Wiki name: Ping (play))
|
||||
{ 0x2E, PacketTypesIn.CraftRecipeResponse }, // (Wiki name: Place Ghost Recipe)
|
||||
{ 0x2F, PacketTypesIn.PlayerAbilities },
|
||||
{ 0x2F, PacketTypesIn.PlayerAbilities }, //
|
||||
{ 0x30, PacketTypesIn.ChatMessage }, // Changed in 1.19 (Completely changed) (Wiki name: Player Chat Message)
|
||||
{ 0x31, PacketTypesIn.EndCombatEvent }, // (Wiki name: Player Combat End)
|
||||
{ 0x32, PacketTypesIn.EnterCombatEvent }, // (Wiki name: Player Combat Enter)
|
||||
|
|
@ -63,16 +63,16 @@ namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
|||
{ 0x36, PacketTypesIn.PlayerPositionAndLook }, // (Wiki name: Player Position)
|
||||
{ 0x37, PacketTypesIn.UnlockRecipes }, // (Wiki name: Recipe)
|
||||
{ 0x38, PacketTypesIn.DestroyEntities }, // (Wiki name: Remove Entites)
|
||||
{ 0x39, PacketTypesIn.RemoveEntityEffect },
|
||||
{ 0x39, PacketTypesIn.RemoveEntityEffect }, //
|
||||
{ 0x3A, PacketTypesIn.ResourcePackSend }, // (Wiki name: Resource Pack)
|
||||
{ 0x3B, PacketTypesIn.Respawn }, // Changed in 1.19 (Heavy changes) - DONE
|
||||
{ 0x3C, PacketTypesIn.EntityHeadLook }, // (Wiki name: Rotate Head)
|
||||
{ 0x3D, PacketTypesIn.MultiBlockChange }, // (Wiki name: Sections Block Update)
|
||||
{ 0x3E, PacketTypesIn.SelectAdvancementTab },
|
||||
{ 0x3E, PacketTypesIn.SelectAdvancementTab }, //
|
||||
{ 0x3F, PacketTypesIn.ServerData }, // Added in 1.19
|
||||
{ 0x40, PacketTypesIn.ActionBar }, // (Wiki name: Set Action Bar Text)
|
||||
{ 0x41, PacketTypesIn.WorldBorderCenter }, // (Wiki name: Set Border Center)
|
||||
{ 0x42, PacketTypesIn.WorldBorderLerpSize },
|
||||
{ 0x42, PacketTypesIn.WorldBorderLerpSize }, //
|
||||
{ 0x43, PacketTypesIn.WorldBorderSize }, // (Wiki name: Set World Border Size)
|
||||
{ 0x44, PacketTypesIn.WorldBorderWarningDelay }, // (Wiki name: Set World Border Warning Delay)
|
||||
{ 0x45, PacketTypesIn.WorldBorderWarningReach }, // (Wiki name: Set Border Warning Distance)
|
||||
|
|
@ -87,10 +87,10 @@ namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
|||
{ 0x4E, PacketTypesIn.AttachEntity }, // (Wiki name: Set Entity Link)
|
||||
{ 0x4F, PacketTypesIn.EntityVelocity }, // (Wiki name: Set Entity Motion)
|
||||
{ 0x50, PacketTypesIn.EntityEquipment }, // (Wiki name: Set Equipment)
|
||||
{ 0x51, PacketTypesIn.SetExperience },
|
||||
{ 0x51, PacketTypesIn.SetExperience }, //
|
||||
{ 0x52, PacketTypesIn.UpdateHealth }, // (Wiki name: Set Health)
|
||||
{ 0x53, PacketTypesIn.ScoreboardObjective }, // (Wiki name: Set Objective)
|
||||
{ 0x54, PacketTypesIn.SetPassengers },
|
||||
{ 0x54, PacketTypesIn.SetPassengers }, //
|
||||
{ 0x55, PacketTypesIn.Teams }, // (Wiki name: Set Player Team)
|
||||
{ 0x56, PacketTypesIn.UpdateScore }, // (Wiki name: Set Score)
|
||||
{ 0x57, PacketTypesIn.UpdateSimulationDistance }, // (Wiki name: Set Simulation Distance)
|
||||
|
|
@ -100,7 +100,7 @@ namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
|||
{ 0x5B, PacketTypesIn.SetTitleTime }, // (Wiki name: Set Titles Animation)
|
||||
{ 0x5C, PacketTypesIn.EntitySoundEffect }, // (Wiki name: Sound Entity)
|
||||
{ 0x5D, PacketTypesIn.SoundEffect }, // Changed in 1.19 (Added "Seed" field) (Wiki name: Sound Effect) - DONE (No need to be implemented)
|
||||
{ 0x5E, PacketTypesIn.StopSound },
|
||||
{ 0x5E, PacketTypesIn.StopSound }, //
|
||||
{ 0x5F, PacketTypesIn.SystemChat }, // Added in 1.19 (Wiki name: System Chat Message)
|
||||
{ 0x60, PacketTypesIn.PlayerListHeaderAndFooter }, // (Wiki name: Tab List)
|
||||
{ 0x61, PacketTypesIn.NBTQueryResponse }, // (Wiki name: Tab Query)
|
||||
|
|
@ -128,21 +128,21 @@ namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
|||
{ 0x0A, PacketTypesOut.ClickWindow }, // (Wiki name: Click Container)
|
||||
{ 0x0B, PacketTypesOut.CloseWindow }, // (Wiki name: Close Container (serverbound))
|
||||
{ 0x0C, PacketTypesOut.PluginMessage }, // (Wiki name: Plugin Message (serverbound))
|
||||
{ 0x0D, PacketTypesOut.EditBook },
|
||||
{ 0x0D, PacketTypesOut.EditBook }, //
|
||||
{ 0x0E, PacketTypesOut.EntityNBTRequest }, // (Wiki name: Query Entity Tag)
|
||||
{ 0x0F, PacketTypesOut.InteractEntity }, // (Wiki name: Interact)
|
||||
{ 0x10, PacketTypesOut.GenerateStructure }, // (Wiki name: Jigsaw Generate)
|
||||
{ 0x11, PacketTypesOut.KeepAlive },
|
||||
{ 0x12, PacketTypesOut.LockDifficulty },
|
||||
{ 0x11, PacketTypesOut.KeepAlive }, //
|
||||
{ 0x12, PacketTypesOut.LockDifficulty }, //
|
||||
{ 0x13, PacketTypesOut.PlayerPosition }, // (Wiki name: Move Player Position)
|
||||
{ 0x14, PacketTypesOut.PlayerPositionAndRotation }, // (Wiki name: Set Player Position and Rotation)
|
||||
{ 0x15, PacketTypesOut.PlayerRotation }, // (Wiki name: Set Player Rotation)
|
||||
{ 0x16, PacketTypesOut.PlayerMovement }, // (Wiki name: Set Player On Ground)
|
||||
{ 0x17, PacketTypesOut.VehicleMove }, // (Wiki name: Move Vehicle (serverbound))
|
||||
{ 0x18, PacketTypesOut.SteerBoat }, // (Wiki name: Paddle Boat)
|
||||
{ 0x19, PacketTypesOut.PickItem },
|
||||
{ 0x19, PacketTypesOut.PickItem }, //
|
||||
{ 0x1A, PacketTypesOut.CraftRecipeRequest }, // (Wiki name: Place recipe)
|
||||
{ 0x1B, PacketTypesOut.PlayerAbilities },
|
||||
{ 0x1B, PacketTypesOut.PlayerAbilities }, //
|
||||
{ 0x1C, PacketTypesOut.PlayerDigging }, // Changed in 1.19 (Added a "Sequence" field) (Wiki name: Player Action) - DONE
|
||||
{ 0x1D, PacketTypesOut.EntityAction }, // (Wiki name: Player Command)
|
||||
{ 0x1E, PacketTypesOut.SteerVehicle }, // (Wiki name: Player Input)
|
||||
|
|
@ -152,11 +152,11 @@ namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
|||
{ 0x22, PacketTypesOut.NameItem }, // (Wiki name: Rename Item)
|
||||
{ 0x23, PacketTypesOut.ResourcePackStatus }, // (Wiki name: Resource Pack (serverbound))
|
||||
{ 0x24, PacketTypesOut.AdvancementTab }, // (Wiki name: Seen Advancements)
|
||||
{ 0x25, PacketTypesOut.SelectTrade },
|
||||
{ 0x25, PacketTypesOut.SelectTrade }, //
|
||||
{ 0x26, PacketTypesOut.SetBeaconEffect }, // Changed in 1.19 (Added a "Secondary Effect Present" and "Secondary Effect" fields) (Wiki name: Set Beacon) - DONE - (No need to be implemented)
|
||||
{ 0x27, PacketTypesOut.HeldItemChange }, // (Wiki name: Set Carried Item (serverbound))
|
||||
{ 0x28, PacketTypesOut.UpdateCommandBlock }, // (Wiki name: Set Command Block)
|
||||
{ 0x29, PacketTypesOut.UpdateCommandBlockMinecart },
|
||||
{ 0x29, PacketTypesOut.UpdateCommandBlockMinecart }, //
|
||||
{ 0x2A, PacketTypesOut.CreativeInventoryAction }, // (Wiki name: Set Creative Mode Slot)
|
||||
{ 0x2B, PacketTypesOut.UpdateJigsawBlock }, // (Wiki name: Set Jigsaw Block)
|
||||
{ 0x2C, PacketTypesOut.UpdateStructureBlock }, // (Wiki name: Set Structure Block)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,184 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers.PacketPalettes
|
||||
{
|
||||
public class PacketPalette1192 : PacketTypePalette
|
||||
{
|
||||
private Dictionary<int, PacketTypesIn> typeIn = new Dictionary<int, PacketTypesIn>()
|
||||
{
|
||||
{ 0x00, PacketTypesIn.SpawnEntity }, // Changed in 1.19 (Wiki name: Spawn Entity) - DONE
|
||||
{ 0x01, PacketTypesIn.SpawnExperienceOrb }, // (Wiki name: Spawn Exeprience Orb)
|
||||
{ 0x02, PacketTypesIn.SpawnPlayer }, //
|
||||
{ 0x03, PacketTypesIn.EntityAnimation }, // (Wiki name: Entity Animation (clientbound))
|
||||
{ 0x04, PacketTypesIn.Statistics }, // (Wiki name: Award Statistics)
|
||||
{ 0x05, PacketTypesIn.BlockChangedAck }, // Added 1.19 (Wiki name: Acknowledge Block Change) - DONE
|
||||
{ 0x06, PacketTypesIn.BlockBreakAnimation }, // (Wiki name: Set Block Destroy Stage)
|
||||
{ 0x07, PacketTypesIn.BlockEntityData }, //
|
||||
{ 0x08, PacketTypesIn.BlockAction }, //
|
||||
{ 0x09, PacketTypesIn.BlockChange }, // (Wiki name: Block Update)
|
||||
{ 0x0A, PacketTypesIn.BossBar }, //
|
||||
{ 0x0B, PacketTypesIn.ServerDifficulty }, // (Wiki name: Change Difficulty)
|
||||
{ 0x0C, PacketTypesIn.ChatPreview }, // Added 1.19
|
||||
{ 0x0D, PacketTypesIn.ClearTiles }, //
|
||||
{ 0x0E, PacketTypesIn.TabComplete }, // (Wiki name: Command Suggestions Response)
|
||||
{ 0x0F, PacketTypesIn.DeclareCommands }, // (Wiki name: Commands)
|
||||
{ 0x10, PacketTypesIn.CloseWindow }, // (Wiki name: Close Container (clientbound))
|
||||
{ 0x11, PacketTypesIn.WindowItems }, // (Wiki name: Set Container Content)
|
||||
{ 0x12, PacketTypesIn.WindowProperty }, // (Wiki name: Set Container Property)
|
||||
{ 0x13, PacketTypesIn.SetSlot }, // (Wiki name: Set Container Slot)
|
||||
{ 0x14, PacketTypesIn.SetCooldown }, //
|
||||
{ 0x15, PacketTypesIn.ChatSuggestions }, // Added 1.19.1
|
||||
{ 0x16, PacketTypesIn.PluginMessage }, // (Wiki name: Plugin Message (clientbound))
|
||||
{ 0x17, PacketTypesIn.NamedSoundEffect }, // Changed in 1.19 (Added "Speed" field) (Wiki name: Custom Sound Effect) - DONE (No need to be implemented)
|
||||
{ 0x18, PacketTypesIn.HideMessage }, // Added 1.19.1
|
||||
{ 0x19, PacketTypesIn.Disconnect }, //
|
||||
{ 0x1A, PacketTypesIn.EntityStatus }, // (Wiki name: Entity Event)
|
||||
{ 0x1B, PacketTypesIn.Explosion }, // Changed in 1.19 (Location fields are now Double instead of Float) (Wiki name: Explosion) - DONE
|
||||
{ 0x1C, PacketTypesIn.UnloadChunk }, // (Wiki name: Forget Chunk)
|
||||
{ 0x1D, PacketTypesIn.ChangeGameState }, // (Wiki name: Game Event)
|
||||
{ 0x1E, PacketTypesIn.OpenHorseWindow }, // (Wiki name: Horse Screen Open)
|
||||
{ 0x1F, PacketTypesIn.InitializeWorldBorder }, //
|
||||
{ 0x20, PacketTypesIn.KeepAlive }, //
|
||||
{ 0x21, PacketTypesIn.ChunkData }, //
|
||||
{ 0x22, PacketTypesIn.Effect }, // (Wiki name: Level Event)
|
||||
{ 0x23, PacketTypesIn.Particle }, // Changed in 1.19 ("Particle Data" field is now "Max Speed", it's the same Float data type) (Wiki name: Level Particle) - DONE (No need to be implemented)
|
||||
{ 0x24, PacketTypesIn.UpdateLight }, // (Wiki name: Light Update)
|
||||
{ 0x25, PacketTypesIn.JoinGame }, // Changed in 1.19 (lot's of changes) (Wiki name: Login (play)) - DONE
|
||||
{ 0x26, PacketTypesIn.MapData }, // (Wiki name: Map Item Data)
|
||||
{ 0x27, PacketTypesIn.TradeList }, // (Wiki name: Merchant Offers)
|
||||
{ 0x28, PacketTypesIn.EntityPosition }, // (Wiki name: Move Entity Position)
|
||||
{ 0x29, PacketTypesIn.EntityPositionAndRotation }, // (Wiki name: Move Entity Position and Rotation)
|
||||
{ 0x2A, PacketTypesIn.EntityRotation }, // (Wiki name: Move Entity Rotation)
|
||||
{ 0x2B, PacketTypesIn.VehicleMove }, // (Wiki name: Move Vehicle)
|
||||
{ 0x2C, PacketTypesIn.OpenBook }, //
|
||||
{ 0x2D, PacketTypesIn.OpenWindow }, // (Wiki name: Open Screen)
|
||||
{ 0x2E, PacketTypesIn.OpenSignEditor }, //
|
||||
{ 0x2F, PacketTypesIn.Ping }, // (Wiki name: Ping (play))
|
||||
{ 0x30, PacketTypesIn.CraftRecipeResponse }, // (Wiki name: Place Ghost Recipe)
|
||||
{ 0x31, PacketTypesIn.PlayerAbilities }, //
|
||||
{ 0x32, PacketTypesIn.MessageHeader }, // Added 1.19.1
|
||||
{ 0x33, PacketTypesIn.ChatMessage }, // Changed in 1.19 (Completely changed) (Wiki name: Player Chat Message)
|
||||
{ 0x34, PacketTypesIn.EndCombatEvent }, // (Wiki name: Player Combat End)
|
||||
{ 0x35, PacketTypesIn.EnterCombatEvent }, // (Wiki name: Player Combat Enter)
|
||||
{ 0x36, PacketTypesIn.DeathCombatEvent }, // (Wiki name: Player Combat Kill)
|
||||
{ 0x37, PacketTypesIn.PlayerInfo }, // Changed in 1.19 (Heavy changes) - DONE
|
||||
{ 0x38, PacketTypesIn.FacePlayer }, // (Wiki name: Player Look At)
|
||||
{ 0x39, PacketTypesIn.PlayerPositionAndLook }, // (Wiki name: Player Position)
|
||||
{ 0x3A, PacketTypesIn.UnlockRecipes }, // (Wiki name: Recipe)
|
||||
{ 0x3B, PacketTypesIn.DestroyEntities }, // (Wiki name: Remove Entites)
|
||||
{ 0x3C, PacketTypesIn.RemoveEntityEffect }, //
|
||||
{ 0x3D, PacketTypesIn.ResourcePackSend }, // (Wiki name: Resource Pack)
|
||||
{ 0x3E, PacketTypesIn.Respawn }, // Changed in 1.19 (Heavy changes) - DONE
|
||||
{ 0x3F, PacketTypesIn.EntityHeadLook }, // (Wiki name: Rotate Head)
|
||||
{ 0x40, PacketTypesIn.MultiBlockChange }, // (Wiki name: Sections Block Update)
|
||||
{ 0x41, PacketTypesIn.SelectAdvancementTab }, //
|
||||
{ 0x42, PacketTypesIn.ServerData }, // Added in 1.19
|
||||
{ 0x43, PacketTypesIn.ActionBar }, // (Wiki name: Set Action Bar Text)
|
||||
{ 0x44, PacketTypesIn.WorldBorderCenter }, // (Wiki name: Set Border Center)
|
||||
{ 0x45, PacketTypesIn.WorldBorderLerpSize }, //
|
||||
{ 0x46, PacketTypesIn.WorldBorderSize }, // (Wiki name: Set World Border Size)
|
||||
{ 0x47, PacketTypesIn.WorldBorderWarningDelay }, // (Wiki name: Set World Border Warning Delay)
|
||||
{ 0x48, PacketTypesIn.WorldBorderWarningReach }, // (Wiki name: Set Border Warning Distance)
|
||||
{ 0x49, PacketTypesIn.Camera }, // (Wiki name: Set Camera)
|
||||
{ 0x4A, PacketTypesIn.HeldItemChange }, // (Wiki name: Set Carried Item (clientbound))
|
||||
{ 0x4B, PacketTypesIn.UpdateViewPosition }, // (Wiki name: Set Chunk Cache Center)
|
||||
{ 0x4C, PacketTypesIn.UpdateViewDistance }, // (Wiki name: Set Chunk Cache Radius)
|
||||
{ 0x4D, PacketTypesIn.SpawnPosition }, // (Wiki name: Set Default Spawn Position)
|
||||
{ 0x4E, PacketTypesIn.SetDisplayChatPreview }, // Added in 1.19 (Wiki name: Set Display Chat Preview)
|
||||
{ 0x4F, PacketTypesIn.DisplayScoreboard }, // (Wiki name: Set Display Objective)
|
||||
{ 0x50, PacketTypesIn.EntityMetadata }, // (Wiki name: Set Entity Metadata)
|
||||
{ 0x51, PacketTypesIn.AttachEntity }, // (Wiki name: Set Entity Link)
|
||||
{ 0x52, PacketTypesIn.EntityVelocity }, // (Wiki name: Set Entity Motion)
|
||||
{ 0x53, PacketTypesIn.EntityEquipment }, // (Wiki name: Set Equipment)
|
||||
{ 0x54, PacketTypesIn.SetExperience }, //
|
||||
{ 0x55, PacketTypesIn.UpdateHealth }, // (Wiki name: Set Health)
|
||||
{ 0x56, PacketTypesIn.ScoreboardObjective }, // (Wiki name: Set Objective)
|
||||
{ 0x57, PacketTypesIn.SetPassengers }, //
|
||||
{ 0x58, PacketTypesIn.Teams }, // (Wiki name: Set Player Team)
|
||||
{ 0x59, PacketTypesIn.UpdateScore }, // (Wiki name: Set Score)
|
||||
{ 0x5A, PacketTypesIn.UpdateSimulationDistance }, // (Wiki name: Set Simulation Distance)
|
||||
{ 0x5B, PacketTypesIn.SetTitleSubTitle }, // (Wiki name: Set Subtitle Test)
|
||||
{ 0x5C, PacketTypesIn.TimeUpdate }, // (Wiki name: Set Time)
|
||||
{ 0x5D, PacketTypesIn.SetTitleText }, // (Wiki name: Set Title)
|
||||
{ 0x5E, PacketTypesIn.SetTitleTime }, // (Wiki name: Set Titles Animation)
|
||||
{ 0x5F, PacketTypesIn.EntitySoundEffect }, // (Wiki name: Sound Entity)
|
||||
{ 0x60, PacketTypesIn.SoundEffect }, // Changed in 1.19 (Added "Seed" field) (Wiki name: Sound Effect) - DONE (No need to be implemented)
|
||||
{ 0x61, PacketTypesIn.StopSound }, //
|
||||
{ 0x62, PacketTypesIn.SystemChat }, // Added in 1.19 (Wiki name: System Chat Message)
|
||||
{ 0x63, PacketTypesIn.PlayerListHeaderAndFooter }, // (Wiki name: Tab List)
|
||||
{ 0x64, PacketTypesIn.NBTQueryResponse }, // (Wiki name: Tab Query)
|
||||
{ 0x65, PacketTypesIn.CollectItem }, // (Wiki name: Take Item Entity)
|
||||
{ 0x66, PacketTypesIn.EntityTeleport }, // (Wiki name: Teleport Entity)
|
||||
{ 0x67, PacketTypesIn.Advancements }, // (Wiki name: Update Advancements)
|
||||
{ 0x68, PacketTypesIn.EntityProperties }, // (Wiki name: Update Attributes)
|
||||
{ 0x69, PacketTypesIn.EntityEffect }, // Changed in 1.19 (Added "Has Factor Data" and "Factor Codec" fields) (Wiki name: Entity Effect) - DONE
|
||||
{ 0x6A, PacketTypesIn.DeclareRecipes }, // (Wiki name: Update Recipes)
|
||||
{ 0x6B, PacketTypesIn.Tags }, // (Wiki name: Update Tags)
|
||||
};
|
||||
|
||||
private Dictionary<int, PacketTypesOut> typeOut = new Dictionary<int, PacketTypesOut>()
|
||||
{
|
||||
{ 0x00, PacketTypesOut.TeleportConfirm }, // (Wiki name: Confirm Teleportation)
|
||||
{ 0x01, PacketTypesOut.QueryBlockNBT }, // (Wiki name: Query Block Entity Tag)
|
||||
{ 0x02, PacketTypesOut.SetDifficulty }, // (Wiki name: Change Difficutly)
|
||||
{ 0x03, PacketTypesOut.MessageAcknowledgment }, // Added in 1.19.1
|
||||
{ 0x04, PacketTypesOut.ChatCommand }, // Added in 1.19
|
||||
{ 0x05, PacketTypesOut.ChatMessage }, // Changed in 1.19 (Completely changed) (Wiki name: Chat)
|
||||
{ 0x06, PacketTypesOut.ChatPreview }, // Added in 1.19 (Wiki name: Chat Preview (serverbound))
|
||||
{ 0x07, PacketTypesOut.ClientStatus }, // (Wiki name: Client Command)
|
||||
{ 0x08, PacketTypesOut.ClientSettings }, // (Wiki name: Client Information)
|
||||
{ 0x09, PacketTypesOut.TabComplete }, // (Wiki name: Command Suggestions Request)
|
||||
{ 0x0A, PacketTypesOut.ClickWindowButton }, // (Wiki name: Click Container Button)
|
||||
{ 0x0B, PacketTypesOut.ClickWindow }, // (Wiki name: Click Container)
|
||||
{ 0x0C, PacketTypesOut.CloseWindow }, // (Wiki name: Close Container (serverbound))
|
||||
{ 0x0D, PacketTypesOut.PluginMessage }, // (Wiki name: Plugin Message (serverbound))
|
||||
{ 0x0E, PacketTypesOut.EditBook }, //
|
||||
{ 0x0F, PacketTypesOut.EntityNBTRequest }, // (Wiki name: Query Entity Tag)
|
||||
{ 0x10, PacketTypesOut.InteractEntity }, // (Wiki name: Interact)
|
||||
{ 0x11, PacketTypesOut.GenerateStructure }, // (Wiki name: Jigsaw Generate)
|
||||
{ 0x12, PacketTypesOut.KeepAlive }, //
|
||||
{ 0x13, PacketTypesOut.LockDifficulty }, //
|
||||
{ 0x14, PacketTypesOut.PlayerPosition }, // (Wiki name: Move Player Position)
|
||||
{ 0x15, PacketTypesOut.PlayerPositionAndRotation }, // (Wiki name: Set Player Position and Rotation)
|
||||
{ 0x16, PacketTypesOut.PlayerRotation }, // (Wiki name: Set Player Rotation)
|
||||
{ 0x17, PacketTypesOut.PlayerMovement }, // (Wiki name: Set Player On Ground)
|
||||
{ 0x18, PacketTypesOut.VehicleMove }, // (Wiki name: Move Vehicle (serverbound))
|
||||
{ 0x19, PacketTypesOut.SteerBoat }, // (Wiki name: Paddle Boat)
|
||||
{ 0x1A, PacketTypesOut.PickItem }, //
|
||||
{ 0x1B, PacketTypesOut.CraftRecipeRequest }, // (Wiki name: Place recipe)
|
||||
{ 0x1C, PacketTypesOut.PlayerAbilities }, //
|
||||
{ 0x1D, PacketTypesOut.PlayerDigging }, // Changed in 1.19 (Added a "Sequence" field) (Wiki name: Player Action) - DONE
|
||||
{ 0x1E, PacketTypesOut.EntityAction }, // (Wiki name: Player Command)
|
||||
{ 0x1F, PacketTypesOut.SteerVehicle }, // (Wiki name: Player Input)
|
||||
{ 0x20, PacketTypesOut.Pong }, // (Wiki name: Pong (play))
|
||||
{ 0x21, PacketTypesOut.SetDisplayedRecipe }, // (Wiki name: Recipe Book Change Settings)
|
||||
{ 0x22, PacketTypesOut.SetRecipeBookState }, // (Wiki name: Recipe Book Seen Recipe)
|
||||
{ 0x23, PacketTypesOut.NameItem }, // (Wiki name: Rename Item)
|
||||
{ 0x24, PacketTypesOut.ResourcePackStatus }, // (Wiki name: Resource Pack (serverbound))
|
||||
{ 0x25, PacketTypesOut.AdvancementTab }, // (Wiki name: Seen Advancements)
|
||||
{ 0x26, PacketTypesOut.SelectTrade }, //
|
||||
{ 0x27, PacketTypesOut.SetBeaconEffect }, // Changed in 1.19 (Added a "Secondary Effect Present" and "Secondary Effect" fields) (Wiki name: Set Beacon) - DONE - (No need to be implemented)
|
||||
{ 0x28, PacketTypesOut.HeldItemChange }, // (Wiki name: Set Carried Item (serverbound))
|
||||
{ 0x29, PacketTypesOut.UpdateCommandBlock }, // (Wiki name: Set Command Block)
|
||||
{ 0x2A, PacketTypesOut.UpdateCommandBlockMinecart }, //
|
||||
{ 0x2B, PacketTypesOut.CreativeInventoryAction }, // (Wiki name: Set Creative Mode Slot)
|
||||
{ 0x2C, PacketTypesOut.UpdateJigsawBlock }, // (Wiki name: Set Jigsaw Block)
|
||||
{ 0x2D, PacketTypesOut.UpdateStructureBlock }, // (Wiki name: Set Structure Block)
|
||||
{ 0x2E, PacketTypesOut.UpdateSign }, // (Wiki name: Sign Update)
|
||||
{ 0x2F, PacketTypesOut.Animation }, // (Wiki name: Swing)
|
||||
{ 0x30, PacketTypesOut.Spectate }, // (Wiki name: Teleport To Entity)
|
||||
{ 0x31, PacketTypesOut.PlayerBlockPlacement }, // Changed in 1.19 (Added a "Sequence" field) (Wiki name: Use Item On) - DONE
|
||||
{ 0x32, PacketTypesOut.UseItem }, // Changed in 1.19 (Added a "Sequence" field) (Wiki name: Use Item) - DONE
|
||||
};
|
||||
|
||||
protected override Dictionary<int, PacketTypesIn> GetListIn()
|
||||
{
|
||||
return typeIn;
|
||||
}
|
||||
|
||||
protected override Dictionary<int, PacketTypesOut> GetListOut()
|
||||
{
|
||||
return typeOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -51,8 +51,9 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
public PacketTypePalette GetTypeHandler(int protocol)
|
||||
{
|
||||
PacketTypePalette p;
|
||||
if (protocol > Protocol18Handler.MC_1_19_Version)
|
||||
if (protocol > Protocol18Handler.MC_1_19_2_Version)
|
||||
throw new NotImplementedException(Translations.Get("exception.palette.packet"));
|
||||
|
||||
if (protocol <= Protocol18Handler.MC_1_8_Version)
|
||||
p = new PacketPalette17();
|
||||
else if (protocol <= Protocol18Handler.MC_1_11_2_Version)
|
||||
|
|
@ -75,8 +76,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
p = new PacketPalette117();
|
||||
else if (protocol <= Protocol18Handler.MC_1_18_2_Version)
|
||||
p = new PacketPalette118();
|
||||
else
|
||||
else if (protocol <= Protocol18Handler.MC_1_19_Version)
|
||||
p = new PacketPalette119();
|
||||
else
|
||||
p = new PacketPalette1192();
|
||||
|
||||
p.SetForgeEnabled(this.forgeEnabled);
|
||||
return p;
|
||||
|
|
|
|||
|
|
@ -10,128 +10,129 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// </summary>
|
||||
public enum PacketTypesIn
|
||||
{
|
||||
SpawnEntity,
|
||||
SpawnExperienceOrb,
|
||||
SpawnWeatherEntity,
|
||||
SpawnLivingEntity,
|
||||
SpawnPainting,
|
||||
SpawnPlayer,
|
||||
EntityAnimation,
|
||||
Statistics,
|
||||
AcknowledgePlayerDigging,
|
||||
BlockBreakAnimation,
|
||||
BlockEntityData,
|
||||
BlockAction,
|
||||
BlockChange,
|
||||
BossBar,
|
||||
ServerDifficulty,
|
||||
ChatMessage,
|
||||
MultiBlockChange,
|
||||
TabComplete,
|
||||
DeclareCommands,
|
||||
WindowConfirmation,
|
||||
CloseWindow,
|
||||
WindowItems,
|
||||
WindowProperty,
|
||||
SetSlot,
|
||||
SetCooldown,
|
||||
PluginMessage,
|
||||
NamedSoundEffect,
|
||||
Disconnect,
|
||||
EntityStatus,
|
||||
Explosion,
|
||||
UnloadChunk,
|
||||
ChangeGameState,
|
||||
OpenHorseWindow,
|
||||
KeepAlive,
|
||||
ChunkData,
|
||||
Effect,
|
||||
Particle,
|
||||
UpdateLight,
|
||||
JoinGame,
|
||||
MapData,
|
||||
TradeList,
|
||||
EntityPosition,
|
||||
EntityPositionAndRotation,
|
||||
EntityRotation,
|
||||
EntityMovement,
|
||||
VehicleMove,
|
||||
OpenBook,
|
||||
OpenWindow,
|
||||
OpenSignEditor,
|
||||
CraftRecipeResponse,
|
||||
PlayerAbilities,
|
||||
CombatEvent,
|
||||
PlayerInfo,
|
||||
FacePlayer,
|
||||
PlayerPositionAndLook,
|
||||
UnlockRecipes,
|
||||
DestroyEntities,
|
||||
RemoveEntityEffect,
|
||||
ResourcePackSend,
|
||||
Respawn,
|
||||
EntityHeadLook,
|
||||
SelectAdvancementTab,
|
||||
WorldBorder,
|
||||
Camera,
|
||||
HeldItemChange,
|
||||
UpdateViewPosition,
|
||||
UpdateViewDistance,
|
||||
DisplayScoreboard,
|
||||
EntityMetadata,
|
||||
AttachEntity,
|
||||
EntityVelocity,
|
||||
EntityEquipment,
|
||||
SetExperience,
|
||||
UpdateHealth,
|
||||
ScoreboardObjective,
|
||||
SetPassengers,
|
||||
Teams,
|
||||
UpdateScore,
|
||||
SpawnPosition,
|
||||
TimeUpdate,
|
||||
Title,
|
||||
EntitySoundEffect,
|
||||
SoundEffect,
|
||||
StopSound,
|
||||
PlayerListHeaderAndFooter,
|
||||
NBTQueryResponse,
|
||||
CollectItem,
|
||||
EntityTeleport,
|
||||
Advancements,
|
||||
EntityProperties,
|
||||
EntityEffect,
|
||||
DeclareRecipes,
|
||||
SetTitleTime,
|
||||
SetTitleText,
|
||||
SetTitleSubTitle,
|
||||
WorldBorderWarningReach,
|
||||
WorldBorderWarningDelay,
|
||||
WorldBorderSize,
|
||||
WorldBorderLerpSize,
|
||||
WorldBorderCenter,
|
||||
ActionBar,
|
||||
Tags,
|
||||
DeathCombatEvent,
|
||||
EnterCombatEvent,
|
||||
EndCombatEvent,
|
||||
Ping,
|
||||
InitializeWorldBorder,
|
||||
SkulkVibrationSignal,
|
||||
ClearTiles,
|
||||
UseBed, // For 1.13.2 or below
|
||||
AcknowledgePlayerDigging, //
|
||||
ActionBar, //
|
||||
Advancements, //
|
||||
AttachEntity, //
|
||||
BlockAction, //
|
||||
BlockBreakAnimation, //
|
||||
BlockChange, //
|
||||
BlockChangedAck, // Added in 1.19
|
||||
BlockEntityData, //
|
||||
BossBar, //
|
||||
Camera, //
|
||||
ChangeGameState, //
|
||||
ChatMessage, //
|
||||
ChatPreview, // Added in 1.19
|
||||
ChatSuggestions, // Added in 1.19.1 (1.19.2)
|
||||
ChunkData, //
|
||||
ClearTiles, //
|
||||
CloseWindow, //
|
||||
CollectItem, //
|
||||
CombatEvent, //
|
||||
CraftRecipeResponse, //
|
||||
DeathCombatEvent, //
|
||||
DeclareCommands, //
|
||||
DeclareRecipes, //
|
||||
DestroyEntities, //
|
||||
Disconnect, //
|
||||
DisplayScoreboard, //
|
||||
Effect, //
|
||||
EndCombatEvent, //
|
||||
EnterCombatEvent, //
|
||||
EntityAnimation, //
|
||||
EntityEffect, //
|
||||
EntityEquipment, //
|
||||
EntityHeadLook, //
|
||||
EntityMetadata, //
|
||||
EntityMovement, //
|
||||
EntityPosition, //
|
||||
EntityPositionAndRotation, //
|
||||
EntityProperties, //
|
||||
EntityRotation, //
|
||||
EntitySoundEffect, //
|
||||
EntityStatus, //
|
||||
EntityTeleport, //
|
||||
EntityVelocity, //
|
||||
Explosion, //
|
||||
FacePlayer, //
|
||||
HeldItemChange, //
|
||||
HideMessage, // Added in 1.19.1 (1.19.2)
|
||||
InitializeWorldBorder, //
|
||||
JoinGame, //
|
||||
KeepAlive, //
|
||||
MapChunkBulk, // For 1.8 or below
|
||||
MapData, //
|
||||
MessageHeader, // Added in 1.19.1 (1.19.2)
|
||||
MultiBlockChange, //
|
||||
NamedSoundEffect, //
|
||||
NBTQueryResponse, //
|
||||
OpenBook, //
|
||||
OpenHorseWindow, //
|
||||
OpenSignEditor, //
|
||||
OpenWindow, //
|
||||
Particle, //
|
||||
Ping, //
|
||||
PlayerAbilities, //
|
||||
PlayerInfo, //
|
||||
PlayerListHeaderAndFooter, //
|
||||
PlayerPositionAndLook, //
|
||||
PluginMessage, //
|
||||
RemoveEntityEffect, //
|
||||
ResourcePackSend, //
|
||||
Respawn, //
|
||||
ScoreboardObjective, //
|
||||
SelectAdvancementTab, //
|
||||
ServerData, // Added in 1.19
|
||||
ServerDifficulty, //
|
||||
SetCompression, // For 1.8 or below
|
||||
UpdateSign, // For 1.8 or below
|
||||
UpdateEntityNBT, // For 1.8 or below
|
||||
SetCooldown, //
|
||||
SetDisplayChatPreview, // Added in 1.19
|
||||
SetExperience, //
|
||||
SetPassengers, //
|
||||
SetSlot, //
|
||||
SetTitleSubTitle, //
|
||||
SetTitleText, //
|
||||
SetTitleTime, //
|
||||
SkulkVibrationSignal, //
|
||||
SoundEffect, //
|
||||
SpawnEntity, //
|
||||
SpawnExperienceOrb, //
|
||||
SpawnLivingEntity, //
|
||||
SpawnPainting, //
|
||||
SpawnPlayer, //
|
||||
SpawnPosition, //
|
||||
SpawnWeatherEntity, //
|
||||
Statistics, //
|
||||
StopSound, //
|
||||
SystemChat, // Added in 1.19
|
||||
TabComplete, //
|
||||
Tags, //
|
||||
Teams, //
|
||||
TimeUpdate, //
|
||||
Title, //
|
||||
TradeList, //
|
||||
Unknown, // For old version packet that have been removed and not used by mcc
|
||||
UpdateSimulationDistance,
|
||||
|
||||
// 1.19 Additions
|
||||
BlockChangedAck,
|
||||
ChatPreview,
|
||||
ServerData,
|
||||
SetDisplayChatPreview,
|
||||
SystemChat
|
||||
UnloadChunk, //
|
||||
UnlockRecipes, //
|
||||
UpdateEntityNBT, // For 1.8 or below
|
||||
UpdateHealth, //
|
||||
UpdateLight, //
|
||||
UpdateScore, //
|
||||
UpdateSign, // For 1.8 or below
|
||||
UpdateSimulationDistance, //
|
||||
UpdateViewDistance, //
|
||||
UpdateViewPosition, //
|
||||
UseBed, // For 1.13.2 or below
|
||||
VehicleMove, //
|
||||
WindowConfirmation, //
|
||||
WindowItems, //
|
||||
WindowProperty, //
|
||||
WorldBorder, //
|
||||
WorldBorderCenter, //
|
||||
WorldBorderLerpSize, //
|
||||
WorldBorderSize, //
|
||||
WorldBorderWarningDelay, //
|
||||
WorldBorderWarningReach, //
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,62 +10,61 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// </summary>
|
||||
public enum PacketTypesOut
|
||||
{
|
||||
TeleportConfirm,
|
||||
QueryBlockNBT,
|
||||
SetDifficulty,
|
||||
ChatMessage,
|
||||
ClientStatus,
|
||||
ClientSettings,
|
||||
TabComplete,
|
||||
WindowConfirmation,
|
||||
ClickWindowButton,
|
||||
ClickWindow,
|
||||
CloseWindow,
|
||||
PluginMessage,
|
||||
EditBook,
|
||||
EntityNBTRequest,
|
||||
InteractEntity,
|
||||
KeepAlive,
|
||||
LockDifficulty,
|
||||
PlayerPosition,
|
||||
PlayerPositionAndRotation,
|
||||
PlayerRotation,
|
||||
PlayerMovement,
|
||||
VehicleMove,
|
||||
SteerBoat,
|
||||
PickItem,
|
||||
CraftRecipeRequest,
|
||||
PlayerAbilities,
|
||||
PlayerDigging,
|
||||
EntityAction,
|
||||
SteerVehicle,
|
||||
RecipeBookData,
|
||||
NameItem,
|
||||
ResourcePackStatus,
|
||||
AdvancementTab,
|
||||
SelectTrade,
|
||||
SetBeaconEffect,
|
||||
HeldItemChange,
|
||||
UpdateCommandBlock,
|
||||
UpdateCommandBlockMinecart,
|
||||
CreativeInventoryAction,
|
||||
UpdateJigsawBlock,
|
||||
UpdateStructureBlock,
|
||||
UpdateSign,
|
||||
Animation,
|
||||
Spectate,
|
||||
PlayerBlockPlacement,
|
||||
UseItem,
|
||||
Pong,
|
||||
PrepareCraftingGrid, // For 1.12 - 1.12.1 only
|
||||
AdvancementTab, //
|
||||
Animation, //
|
||||
ChatCommand, // Added in 1.19
|
||||
ChatMessage, //
|
||||
ChatPreview, // Added in 1.19
|
||||
ClickWindow, //
|
||||
ClickWindowButton, //
|
||||
ClientSettings, //
|
||||
ClientStatus, //
|
||||
CloseWindow, //
|
||||
CraftRecipeRequest, //
|
||||
CreativeInventoryAction, //
|
||||
EditBook, //
|
||||
EnchantItem, // For 1.13.2 or below
|
||||
EntityAction, //
|
||||
EntityNBTRequest, //
|
||||
GenerateStructure, // Added in 1.16
|
||||
HeldItemChange, //
|
||||
InteractEntity, //
|
||||
KeepAlive, //
|
||||
LockDifficulty, //
|
||||
MessageAcknowledgment, // Added in 1.19.1 (1.19.2)
|
||||
NameItem, //
|
||||
PickItem, //
|
||||
PlayerAbilities, //
|
||||
PlayerBlockPlacement, //
|
||||
PlayerDigging, //
|
||||
PlayerMovement, //
|
||||
PlayerPosition, //
|
||||
PlayerPositionAndRotation, //
|
||||
PlayerRotation, //
|
||||
PluginMessage, //
|
||||
Pong, //
|
||||
PrepareCraftingGrid, // For 1.12 - 1.12.1 only
|
||||
QueryBlockNBT, //
|
||||
RecipeBookData, //
|
||||
ResourcePackStatus, //
|
||||
SelectTrade, //
|
||||
SetBeaconEffect, //
|
||||
SetDifficulty, //
|
||||
SetDisplayedRecipe, // Added in 1.16.2
|
||||
SetRecipeBookState, // Added in 1.16.2
|
||||
Spectate, //
|
||||
SteerBoat, //
|
||||
SteerVehicle, //
|
||||
TabComplete, //
|
||||
TeleportConfirm, //
|
||||
Unknown, // For old version packet that have been removed and not used by mcc
|
||||
|
||||
// Added in 1.19
|
||||
ChatCommand,
|
||||
ChatPreview,
|
||||
UpdateCommandBlock, //
|
||||
UpdateCommandBlockMinecart, //
|
||||
UpdateJigsawBlock, //
|
||||
UpdateSign, //
|
||||
UpdateStructureBlock, //
|
||||
UseItem, //
|
||||
VehicleMove, //
|
||||
WindowConfirmation, //
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using System.Security.Cryptography;
|
|||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Inventory;
|
||||
using MinecraftClient.Protocol.Keys;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers
|
||||
{
|
||||
|
|
@ -559,7 +560,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
public bool Login(PlayerKeyPair playerKeyPair)
|
||||
{
|
||||
if (Handshake(handler.GetUserUUID(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort()))
|
||||
if (Handshake(handler.GetUserUuidStr(), handler.GetUsername(), handler.GetSessionID(), handler.GetServerHost(), handler.GetServerPort()))
|
||||
{
|
||||
Send(new byte[] { 0xCD, 0 });
|
||||
try
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using MinecraftClient.Logger;
|
|||
using System.Threading.Tasks;
|
||||
using MinecraftClient.Protocol.Keys;
|
||||
using System.Text.RegularExpressions;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol.Handlers
|
||||
{
|
||||
|
|
@ -55,6 +56,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
internal const int MC_1_18_1_Version = 757;
|
||||
internal const int MC_1_18_2_Version = 758;
|
||||
internal const int MC_1_19_Version = 759;
|
||||
internal const int MC_1_19_2_Version = 760;
|
||||
|
||||
private int compression_treshold = 0;
|
||||
private bool autocomplete_received = false;
|
||||
|
|
@ -64,6 +66,11 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
private bool login_phase = true;
|
||||
private int protocolVersion;
|
||||
private int currentDimension;
|
||||
private bool isOnlineMode = false;
|
||||
|
||||
private int pendingAcknowledgments = 0;
|
||||
private LastSeenMessagesCollector lastSeenMessagesCollector = new(5);
|
||||
private LastSeenMessageList.Entry? lastReceivedMessage = null;
|
||||
|
||||
Protocol18Forge pForge;
|
||||
Protocol18Terrain pTerrain;
|
||||
|
|
@ -91,31 +98,28 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
this.log = handler.GetLogger();
|
||||
this.randomGen = RandomNumberGenerator.Create();
|
||||
|
||||
if (handler.GetTerrainEnabled() && protocolVersion > MC_1_18_2_Version)
|
||||
if (handler.GetTerrainEnabled() && protocolVersion > MC_1_19_2_Version)
|
||||
{
|
||||
log.Error(Translations.Get("extra.terrainandmovement_disabled"));
|
||||
handler.SetTerrainEnabled(false);
|
||||
}
|
||||
|
||||
if (handler.GetInventoryEnabled() && (protocolVersion < MC_1_10_Version || protocolVersion > MC_1_19_Version))
|
||||
if (handler.GetInventoryEnabled() && (protocolVersion < MC_1_10_Version || protocolVersion > MC_1_19_2_Version))
|
||||
{
|
||||
log.Error(Translations.Get("extra.inventory_disabled"));
|
||||
handler.SetInventoryEnabled(false);
|
||||
}
|
||||
|
||||
if (handler.GetEntityHandlingEnabled() && (protocolVersion < MC_1_10_Version || protocolVersion > MC_1_19_Version))
|
||||
if (handler.GetEntityHandlingEnabled() && (protocolVersion < MC_1_10_Version || protocolVersion > MC_1_19_2_Version))
|
||||
{
|
||||
log.Error(Translations.Get("extra.entity_disabled"));
|
||||
handler.SetEntityHandlingEnabled(false);
|
||||
}
|
||||
|
||||
// Block palette
|
||||
if (protocolVersion >= MC_1_13_Version)
|
||||
{
|
||||
if (protocolVersion > MC_1_19_Version && handler.GetTerrainEnabled())
|
||||
if (protocolVersion > MC_1_19_2_Version && handler.GetTerrainEnabled())
|
||||
throw new NotImplementedException(Translations.Get("exception.palette.block"));
|
||||
|
||||
if (protocolVersion == MC_1_19_Version)
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
Block.Palette = new Palette119();
|
||||
else if (protocolVersion >= MC_1_17_Version)
|
||||
Block.Palette = new Palette117();
|
||||
|
|
@ -125,18 +129,16 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
Block.Palette = new Palette115();
|
||||
else if (protocolVersion >= MC_1_14_Version)
|
||||
Block.Palette = new Palette114();
|
||||
else Block.Palette = new Palette113();
|
||||
|
||||
}
|
||||
else Block.Palette = new Palette112();
|
||||
else if (protocolVersion >= MC_1_13_Version)
|
||||
Block.Palette = new Palette113();
|
||||
else
|
||||
Block.Palette = new Palette112();
|
||||
|
||||
// Entity palette
|
||||
if (protocolVersion >= MC_1_13_Version)
|
||||
{
|
||||
if (protocolVersion > MC_1_19_Version && handler.GetEntityHandlingEnabled())
|
||||
if (protocolVersion > MC_1_19_2_Version && handler.GetEntityHandlingEnabled())
|
||||
throw new NotImplementedException(Translations.Get("exception.palette.entity"));
|
||||
|
||||
if (protocolVersion == MC_1_19_Version)
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
entityPalette = new EntityPalette119();
|
||||
else if (protocolVersion >= MC_1_17_Version)
|
||||
entityPalette = new EntityPalette117();
|
||||
|
|
@ -148,28 +150,53 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
entityPalette = new EntityPalette115();
|
||||
else if (protocolVersion >= MC_1_14_Version)
|
||||
entityPalette = new EntityPalette114();
|
||||
else entityPalette = new EntityPalette113();
|
||||
}
|
||||
else entityPalette = new EntityPalette112();
|
||||
else if (protocolVersion >= MC_1_13_Version)
|
||||
entityPalette = new EntityPalette113();
|
||||
else
|
||||
entityPalette = new EntityPalette112();
|
||||
|
||||
// Item palette
|
||||
if (protocolVersion >= MC_1_16_2_Version)
|
||||
{
|
||||
if (protocolVersion > MC_1_19_Version && handler.GetInventoryEnabled())
|
||||
if (protocolVersion > MC_1_19_2_Version && handler.GetInventoryEnabled())
|
||||
throw new NotImplementedException(Translations.Get("exception.palette.item"));
|
||||
|
||||
if (protocolVersion == MC_1_19_Version)
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
itemPalette = new ItemPalette119();
|
||||
else if (protocolVersion >= MC_1_18_1_Version)
|
||||
itemPalette = new ItemPalette118();
|
||||
else if (protocolVersion >= MC_1_17_Version)
|
||||
itemPalette = new ItemPalette117();
|
||||
else if (protocolVersion >= MC_1_16_2_Version)
|
||||
if (protocolVersion >= MC_1_16_2_Version)
|
||||
itemPalette = new ItemPalette1162();
|
||||
else itemPalette = new ItemPalette1161();
|
||||
}
|
||||
else itemPalette = new ItemPalette115();
|
||||
else if (protocolVersion >= MC_1_16_1_Version)
|
||||
itemPalette = new ItemPalette1161();
|
||||
else
|
||||
itemPalette = new ItemPalette115();
|
||||
|
||||
// MessageType
|
||||
// You can find it in https://wiki.vg/Protocol#Player_Chat_Message or /net/minecraft/network/message/MessageType.java
|
||||
if (this.protocolVersion >= MC_1_19_2_Version)
|
||||
ChatParser.ChatId2Type = new()
|
||||
{
|
||||
{ 0, ChatParser.MessageType.CHAT },
|
||||
{ 1, ChatParser.MessageType.SAY_COMMAND },
|
||||
{ 2, ChatParser.MessageType.MSG_COMMAND_INCOMING },
|
||||
{ 3, ChatParser.MessageType.MSG_COMMAND_OUTGOING },
|
||||
{ 4, ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING },
|
||||
{ 5, ChatParser.MessageType.TEAM_MSG_COMMAND_OUTGOING },
|
||||
{ 6, ChatParser.MessageType.EMOTE_COMMAND },
|
||||
};
|
||||
else if (this.protocolVersion == MC_1_19_Version)
|
||||
ChatParser.ChatId2Type = new()
|
||||
{
|
||||
{ 0, ChatParser.MessageType.CHAT },
|
||||
{ 1, ChatParser.MessageType.RAW_MSG },
|
||||
{ 2, ChatParser.MessageType.RAW_MSG },
|
||||
{ 3, ChatParser.MessageType.SAY_COMMAND },
|
||||
{ 4, ChatParser.MessageType.MSG_COMMAND_INCOMING },
|
||||
{ 5, ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING },
|
||||
{ 6, ChatParser.MessageType.EMOTE_COMMAND },
|
||||
{ 7, ChatParser.MessageType.RAW_MSG },
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -328,26 +355,22 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
int worldCount = dataTypes.ReadNextVarInt(packetData); // Dimension Count (World Count) - 1.16 and above
|
||||
for (int i = 0; i < worldCount; i++)
|
||||
dataTypes.ReadNextString(packetData); // Dimension Names (World Names) - 1.16 and above
|
||||
dataTypes.ReadNextNbt(packetData); // Registry Codec (Dimension Codec) - 1.16 and above
|
||||
var registryCodec = dataTypes.ReadNextNbt(packetData); // Registry Codec (Dimension Codec) - 1.16 and above
|
||||
World.StoreDimensionList(registryCodec);
|
||||
}
|
||||
|
||||
string? currentDimensionName = null;
|
||||
Dictionary<string, object>? currentDimensionType = null;
|
||||
|
||||
// Current dimension
|
||||
// NBT Tag Compound: 1.16.2 and above
|
||||
// String: 1.19 and above
|
||||
// NBT Tag Compound: [1.16.2 to 1.18.2]
|
||||
// String identifier: 1.16 and 1.16.1
|
||||
// varInt: [1.9.1 to 1.15.2]
|
||||
// byte: below 1.9.1
|
||||
if (protocolVersion >= MC_1_16_Version)
|
||||
{
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
{
|
||||
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
|
||||
currentDimensionType = new Dictionary<string, object>();
|
||||
}
|
||||
else if (protocolVersion >= MC_1_16_2_Version)
|
||||
currentDimensionType = dataTypes.ReadNextNbt(packetData); // Dimension Type: NBT Tag Compound
|
||||
dataTypes.ReadNextNbt(packetData); // Dimension Type: NBT Tag Compound
|
||||
else
|
||||
dataTypes.ReadNextString(packetData);
|
||||
this.currentDimension = 0;
|
||||
|
|
@ -361,10 +384,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
|
||||
|
||||
if (protocolVersion >= MC_1_16_Version)
|
||||
currentDimensionName = dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
|
||||
if (protocolVersion >= MC_1_16_2_Version)
|
||||
World.SetDimension(currentDimensionName, currentDimensionType);
|
||||
{
|
||||
string dimensionName = dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
World.SetDimension(dimensionName);
|
||||
}
|
||||
|
||||
if (protocolVersion >= MC_1_15_Version)
|
||||
dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above
|
||||
|
|
@ -422,7 +445,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
handler.OnTextReceived(new(message, true, messageType, senderUUID));
|
||||
}
|
||||
else // 1.19+
|
||||
else if (protocolVersion == MC_1_19_Version) // 1.19
|
||||
{
|
||||
string signedChat = dataTypes.ReadNextString(packetData);
|
||||
|
||||
|
|
@ -446,38 +469,134 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
byte[] messageSignature = dataTypes.ReadNextByteArray(packetData);
|
||||
|
||||
bool verifyResult;
|
||||
if (!isOnlineMode)
|
||||
verifyResult = false;
|
||||
else if (senderUUID == handler.GetUserUuid())
|
||||
verifyResult = true;
|
||||
else
|
||||
{
|
||||
PlayerInfo? player = handler.GetPlayerInfo(senderUUID);
|
||||
bool verifyResult = player == null ? false : player.VerifyMessage(signedChat, senderUUID, timestamp, salt, ref messageSignature);
|
||||
verifyResult = player == null ? false : player.VerifyMessage(signedChat, timestamp, salt, ref messageSignature);
|
||||
}
|
||||
|
||||
handler.OnTextReceived(new(signedChat, true, messageType, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, verifyResult));
|
||||
ChatMessage chat = new(signedChat, true, messageType, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult);
|
||||
handler.OnTextReceived(chat);
|
||||
}
|
||||
else // 1.19.1 +
|
||||
{
|
||||
byte[]? precedingSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData) : null;
|
||||
Guid senderUUID = dataTypes.ReadNextUUID(packetData);
|
||||
byte[] headerSignature = dataTypes.ReadNextByteArray(packetData);
|
||||
|
||||
string signedChat = dataTypes.ReadNextString(packetData);
|
||||
string? decorated = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null;
|
||||
|
||||
long timestamp = dataTypes.ReadNextLong(packetData);
|
||||
long salt = dataTypes.ReadNextLong(packetData);
|
||||
|
||||
int lastSeenMessageListLen = dataTypes.ReadNextVarInt(packetData);
|
||||
LastSeenMessageList.Entry[] lastSeenMessageList = new LastSeenMessageList.Entry[lastSeenMessageListLen];
|
||||
for (int i = 0; i < lastSeenMessageListLen; ++i)
|
||||
{
|
||||
Guid user = dataTypes.ReadNextUUID(packetData);
|
||||
byte[] lastSignature = dataTypes.ReadNextByteArray(packetData);
|
||||
lastSeenMessageList[i] = new(user, lastSignature);
|
||||
}
|
||||
LastSeenMessageList lastSeenMessages = new(lastSeenMessageList);
|
||||
|
||||
string? unsignedChatContent = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null;
|
||||
|
||||
int filterEnum = dataTypes.ReadNextVarInt(packetData);
|
||||
if (filterEnum == 2) // PARTIALLY_FILTERED
|
||||
dataTypes.ReadNextULongArray(packetData);
|
||||
|
||||
int chatTypeId = dataTypes.ReadNextVarInt(packetData);
|
||||
string chatName = dataTypes.ReadNextString(packetData);
|
||||
string? targetName = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextString(packetData) : null;
|
||||
|
||||
Dictionary<string, Json.JSONData> chatInfo = Json.ParseJson(chatName).Properties;
|
||||
string senderDisplayName = (chatInfo.ContainsKey("insertion") ? chatInfo["insertion"] : chatInfo["text"]).StringValue;
|
||||
string? senderTeamName = null;
|
||||
ChatParser.MessageType messageTypeEnum = ChatParser.ChatId2Type!.GetValueOrDefault(chatTypeId, ChatParser.MessageType.CHAT);
|
||||
if (targetName != null &&
|
||||
(messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_INCOMING || messageTypeEnum == ChatParser.MessageType.TEAM_MSG_COMMAND_OUTGOING))
|
||||
senderTeamName = Json.ParseJson(targetName).Properties["with"].DataArray[0].Properties["text"].StringValue;
|
||||
|
||||
bool verifyResult;
|
||||
if (!isOnlineMode)
|
||||
verifyResult = false;
|
||||
else if (senderUUID == handler.GetUserUuid())
|
||||
verifyResult = true;
|
||||
else
|
||||
{
|
||||
PlayerInfo? player = handler.GetPlayerInfo(senderUUID);
|
||||
if (player == null || !player.IsMessageChainLegal())
|
||||
verifyResult = false;
|
||||
else
|
||||
{
|
||||
bool lastVerifyResult = player.IsMessageChainLegal();
|
||||
verifyResult = player.VerifyMessage(signedChat, timestamp, salt, ref headerSignature, ref precedingSignature, lastSeenMessages);
|
||||
if (lastVerifyResult && !verifyResult)
|
||||
log.Warn(Translations.Get("chat.message_chain_broken", senderDisplayName));
|
||||
}
|
||||
}
|
||||
|
||||
ChatMessage chat = new(signedChat, false, chatTypeId, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, headerSignature, verifyResult);
|
||||
if (isOnlineMode && !chat.lacksSender())
|
||||
this.acknowledge(chat);
|
||||
handler.OnTextReceived(chat);
|
||||
}
|
||||
break;
|
||||
case PacketTypesIn.MessageHeader:
|
||||
if (protocolVersion >= MC_1_19_2_Version)
|
||||
{
|
||||
byte[]? precedingSignature = dataTypes.ReadNextBool(packetData) ? dataTypes.ReadNextByteArray(packetData) : null;
|
||||
Guid senderUUID = dataTypes.ReadNextUUID(packetData);
|
||||
byte[] headerSignature = dataTypes.ReadNextByteArray(packetData);
|
||||
byte[] bodyDigest = dataTypes.ReadNextByteArray(packetData);
|
||||
|
||||
bool verifyResult;
|
||||
if (!isOnlineMode)
|
||||
verifyResult = false;
|
||||
else if (senderUUID == handler.GetUserUuid())
|
||||
verifyResult = true;
|
||||
else
|
||||
{
|
||||
PlayerInfo? player = handler.GetPlayerInfo(senderUUID);
|
||||
if (player == null || !player.IsMessageChainLegal())
|
||||
verifyResult = false;
|
||||
else
|
||||
{
|
||||
bool lastVerifyResult = player.IsMessageChainLegal();
|
||||
verifyResult = player.VerifyMessageHead(ref precedingSignature, ref headerSignature, ref bodyDigest);
|
||||
if (lastVerifyResult && !verifyResult)
|
||||
log.Warn("Player " + player.DisplayName + "'s message chain is broken!");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case PacketTypesIn.Respawn:
|
||||
string? dimensionNameInRespawn = null;
|
||||
Dictionary<string, object> dimensionTypeInRespawn = null;
|
||||
if (protocolVersion >= MC_1_16_Version)
|
||||
{
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
{
|
||||
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
|
||||
dimensionTypeInRespawn = new Dictionary<string, object>();
|
||||
}
|
||||
else if (protocolVersion >= MC_1_16_2_Version)
|
||||
dimensionTypeInRespawn = dataTypes.ReadNextNbt(packetData); // Dimension Type: NBT Tag Compound
|
||||
dataTypes.ReadNextNbt(packetData); // Dimension Type: NBT Tag Compound
|
||||
else
|
||||
dataTypes.ReadNextString(packetData);
|
||||
this.currentDimension = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 1.15 and below
|
||||
{ // 1.15 and below
|
||||
this.currentDimension = dataTypes.ReadNextInt(packetData);
|
||||
}
|
||||
if (protocolVersion >= MC_1_16_Version)
|
||||
dimensionNameInRespawn = dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
|
||||
if (protocolVersion >= MC_1_16_2_Version)
|
||||
World.SetDimension(dimensionNameInRespawn, dimensionTypeInRespawn);
|
||||
if (protocolVersion >= MC_1_16_Version)
|
||||
{
|
||||
string dimensionName = dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
World.SetDimension(dimensionName);
|
||||
}
|
||||
|
||||
if (protocolVersion < MC_1_14_Version)
|
||||
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
|
||||
|
|
@ -829,6 +948,8 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
//handler.OnTextReceived(message, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case PacketTypesIn.ChatSuggestions:
|
||||
break;
|
||||
case PacketTypesIn.MapChunkBulk:
|
||||
if (protocolVersion < MC_1_9_Version && handler.GetTerrainEnabled())
|
||||
|
|
@ -904,14 +1025,20 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
string name = dataTypes.ReadNextString(packetData); // Player name
|
||||
int propNum = dataTypes.ReadNextVarInt(packetData); // Number of properties in the following array
|
||||
|
||||
Tuple<string, string, string>[]? property = null; // Property: Tuple<Name, Value, Signature(empty if there is no signature)
|
||||
// Property: Tuple<Name, Value, Signature(empty if there is no signature)
|
||||
// The Property field looks as in the response of https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape
|
||||
const bool useProperty = false;
|
||||
Tuple<string, string, string?>[]? properties = useProperty ?
|
||||
new Tuple<string, string, string?>[propNum] : null;
|
||||
for (int p = 0; p < propNum; p++)
|
||||
{
|
||||
string key = dataTypes.ReadNextString(packetData); // Name
|
||||
string val = dataTypes.ReadNextString(packetData); // Value
|
||||
|
||||
string propertyName = dataTypes.ReadNextString(packetData); // Name: String (32767)
|
||||
string val = dataTypes.ReadNextString(packetData); // Value: String (32767)
|
||||
string? propertySignature = null;
|
||||
if (dataTypes.ReadNextBool(packetData)) // Is Signed
|
||||
dataTypes.ReadNextString(packetData); // Signature
|
||||
propertySignature = dataTypes.ReadNextString(packetData); // Signature: String (32767)
|
||||
if (useProperty)
|
||||
properties![p] = new(propertyName, val, propertySignature);
|
||||
}
|
||||
|
||||
int gameMode = dataTypes.ReadNextVarInt(packetData); // Gamemode
|
||||
|
|
@ -942,7 +1069,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
}
|
||||
|
||||
handler.OnPlayerJoin(new PlayerInfo(uuid, name, property, gameMode, ping, displayName, keyExpiration, publicKey, signature));
|
||||
handler.OnPlayerJoin(new PlayerInfo(uuid, name, properties, gameMode, ping, displayName, keyExpiration, publicKey, signature));
|
||||
break;
|
||||
case 0x01: //Update gamemode
|
||||
handler.OnGamemodeUpdate(uuid, dataTypes.ReadNextVarInt(packetData));
|
||||
|
|
@ -1291,13 +1418,16 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
int EntityID = dataTypes.ReadNextVarInt(packetData);
|
||||
Dictionary<int, object> metadata = dataTypes.ReadNextMetadata(packetData, itemPalette);
|
||||
|
||||
// See https://wiki.vg/Entity_metadata#Living_Entity
|
||||
int healthField = 7; // From 1.10 to 1.13.2
|
||||
if (protocolVersion >= MC_1_14_Version)
|
||||
healthField = 8; // 1.14 and above
|
||||
if (protocolVersion >= MC_1_17_Version)
|
||||
healthField = 9; // 1.17 and above
|
||||
if (protocolVersion > MC_1_19_Version)
|
||||
int healthField; // See https://wiki.vg/Entity_metadata#Living_Entity
|
||||
if (protocolVersion > MC_1_19_2_Version)
|
||||
throw new NotImplementedException(Translations.Get("exception.palette.healthfield"));
|
||||
else if (protocolVersion >= MC_1_17_Version) // 1.17 and above
|
||||
healthField = 9;
|
||||
else if (protocolVersion >= MC_1_14_Version) // 1.14 and above
|
||||
healthField = 8;
|
||||
else if (protocolVersion >= MC_1_10_Version) // 1.10 and above
|
||||
healthField = 7;
|
||||
else
|
||||
throw new NotImplementedException(Translations.Get("exception.palette.healthfield"));
|
||||
|
||||
if (metadata.ContainsKey(healthField) && metadata[healthField] != null && metadata[healthField].GetType() == typeof(float))
|
||||
|
|
@ -1541,13 +1671,27 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
fullLoginPacket.AddRange(dataTypes.GetBool(true)); // Has Sig Data
|
||||
fullLoginPacket.AddRange(dataTypes.GetLong(playerKeyPair.GetExpirationMilliseconds())); // Expiration time
|
||||
fullLoginPacket.AddRange(dataTypes.GetArray(playerKeyPair.PublicKey.Key)); // Public key received from Microsoft API
|
||||
fullLoginPacket.AddRange(dataTypes.GetArray(playerKeyPair.PublicKey.Signature)); // Public key signature received from Microsoft API
|
||||
if (protocolVersion >= MC_1_19_2_Version)
|
||||
fullLoginPacket.AddRange(dataTypes.GetArray(playerKeyPair.PublicKey.SignatureV2!)); // Public key signature received from Microsoft API
|
||||
else
|
||||
fullLoginPacket.AddRange(dataTypes.GetArray(playerKeyPair.PublicKey.Signature!)); // Public key signature received from Microsoft API
|
||||
}
|
||||
}
|
||||
if (protocolVersion >= MC_1_19_2_Version)
|
||||
{
|
||||
Guid uuid = handler.GetUserUuid();
|
||||
if (uuid == Guid.Empty)
|
||||
fullLoginPacket.AddRange(dataTypes.GetBool(false)); // Has UUID
|
||||
else
|
||||
{
|
||||
fullLoginPacket.AddRange(dataTypes.GetBool(true)); // Has UUID
|
||||
fullLoginPacket.AddRange(dataTypes.GetUUID(uuid)); // UUID
|
||||
}
|
||||
}
|
||||
SendPacket(0x00, fullLoginPacket);
|
||||
|
||||
int packetID = -1;
|
||||
Queue<byte> packetData = new Queue<byte>();
|
||||
Queue<byte> packetData = new();
|
||||
while (true)
|
||||
{
|
||||
ReadNextPacket(ref packetID, packetData);
|
||||
|
|
@ -1558,10 +1702,11 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
else if (packetID == 0x01) //Encryption request
|
||||
{
|
||||
this.isOnlineMode = true;
|
||||
string serverID = dataTypes.ReadNextString(packetData);
|
||||
byte[] serverPublicKey = dataTypes.ReadNextByteArray(packetData);
|
||||
byte[] token = dataTypes.ReadNextByteArray(packetData);
|
||||
return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, serverPublicKey, playerKeyPair);
|
||||
return StartEncryption(handler.GetUserUuidStr(), handler.GetSessionID(), token, serverID, serverPublicKey, playerKeyPair);
|
||||
}
|
||||
else if (packetID == 0x02) //Login successful
|
||||
{
|
||||
|
|
@ -1753,7 +1898,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
/// Ping a Minecraft server to get information about the server
|
||||
/// </summary>
|
||||
/// <returns>True if ping was successful</returns>
|
||||
public static bool doPing(string host, int port, ref int protocolVersion, ref ForgeInfo? forgeInfo)
|
||||
public static bool doPing(string host, int port, ref int protocolversion, ref ForgeInfo? forgeInfo)
|
||||
{
|
||||
string version = "";
|
||||
TcpClient tcp = ProxyHandler.newTcpClient(host, port);
|
||||
|
|
@ -1805,12 +1950,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
//Retrieve protocol version number for handling this server
|
||||
if (versionData.Properties.ContainsKey("protocol"))
|
||||
protocolVersion = int.Parse(versionData.Properties["protocol"].StringValue);
|
||||
protocolversion = int.Parse(versionData.Properties["protocol"].StringValue);
|
||||
|
||||
// Check for forge on the server.
|
||||
Protocol18Forge.ServerInfoCheckForge(jsonData, ref forgeInfo);
|
||||
|
||||
ConsoleIO.WriteLineFormatted(Translations.Get("mcc.server_protocol", version, protocolVersion + (forgeInfo != null ? Translations.Get("mcc.with_forge") : "")));
|
||||
ConsoleIO.WriteLineFormatted(Translations.Get("mcc.server_protocol", version, protocolversion + (forgeInfo != null ? Translations.Get("mcc.with_forge") : "")));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1843,41 +1988,87 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
return protocolVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send MessageAcknowledgment packet
|
||||
/// </summary>
|
||||
/// <param name="acknowledgment">Message acknowledgment</param>
|
||||
/// <returns>True if properly sent</returns>
|
||||
public bool SendMessageAcknowledgment(LastSeenMessageList.Acknowledgment acknowledgment)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] fields = dataTypes.GetAcknowledgment(acknowledgment, isOnlineMode);
|
||||
|
||||
SendPacket(PacketTypesOut.MessageAcknowledgment, fields);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (SocketException) { return false; }
|
||||
catch (System.IO.IOException) { return false; }
|
||||
catch (ObjectDisposedException) { return false; }
|
||||
}
|
||||
|
||||
public LastSeenMessageList.Acknowledgment consumeAcknowledgment()
|
||||
{
|
||||
this.pendingAcknowledgments = 0;
|
||||
return new LastSeenMessageList.Acknowledgment(this.lastSeenMessagesCollector.GetLastSeenMessages(), this.lastReceivedMessage);
|
||||
}
|
||||
|
||||
public void acknowledge(ChatMessage message)
|
||||
{
|
||||
LastSeenMessageList.Entry? entry = message.toLastSeenMessageEntry();
|
||||
|
||||
if (entry != null)
|
||||
{
|
||||
lastSeenMessagesCollector.Add(entry);
|
||||
lastReceivedMessage = null;
|
||||
|
||||
if (pendingAcknowledgments++ > 64)
|
||||
SendMessageAcknowledgment(this.consumeAcknowledgment());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The signable argument names and their values from command
|
||||
/// Signature will used in Vanilla's say, me, msg, teammsg, ban, banip, and kick commands.
|
||||
/// https://gist.github.com/kennytv/ed783dd244ca0321bbd882c347892874#signed-command-arguments
|
||||
/// You can find all the commands that need to be signed by searching for "MessageArgumentType.getSignedMessage" in the source code.
|
||||
/// Don't forget to handle the redirected commands, e.g. /tm, /w
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="command">Command</param>
|
||||
/// <returns> List< Argument Name, Argument Value > </returns>
|
||||
private List<Tuple<string, string>> collectCommandArguments(string command)
|
||||
private List<Tuple<string, string>>? CollectCommandArguments(string command)
|
||||
{
|
||||
List<Tuple<string, string>> needSigned = new();
|
||||
if (!isOnlineMode || !Settings.SignMessageInCommand)
|
||||
return null;
|
||||
|
||||
if (!Settings.SignMessageInCommand)
|
||||
return needSigned;
|
||||
List<Tuple<string, string>> needSigned = new();
|
||||
|
||||
string[] argStage1 = command.Split(' ', 2, StringSplitOptions.None);
|
||||
if (argStage1.Length == 2)
|
||||
{
|
||||
/* /me <action>
|
||||
/say <message>
|
||||
/teammsg <message> */
|
||||
/teammsg <message>
|
||||
/tm <message> */
|
||||
if (argStage1[0] == "me")
|
||||
needSigned.Add(new("action", argStage1[1]));
|
||||
else if (argStage1[0] == "say" || argStage1[0] == "teammsg")
|
||||
else if (argStage1[0] == "say" || argStage1[0] == "teammsg" || argStage1[0] == "tm")
|
||||
needSigned.Add(new("message", argStage1[1]));
|
||||
else if (argStage1[0] == "msg" || argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick")
|
||||
else if (argStage1[0] == "msg" || argStage1[0] == "tell" || argStage1[0] == "w" ||
|
||||
argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick")
|
||||
{
|
||||
/* /msg <targets> <message>
|
||||
/tell <targets> <message>
|
||||
/w <targets> <message>
|
||||
/ban <target> [<reason>]
|
||||
/ban-ip <target> [<reason>]
|
||||
/kick <target> [<reason>] */
|
||||
string[] argStage2 = argStage1[1].Split(' ', 2, StringSplitOptions.None);
|
||||
if (argStage2.Length == 2)
|
||||
{
|
||||
if (argStage1[0] == "msg")
|
||||
if (argStage1[0] == "msg" || argStage1[0] == "tell" || argStage1[0] == "w")
|
||||
needSigned.Add(new("message", argStage2[1]));
|
||||
else if (argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick")
|
||||
needSigned.Add(new("reason", argStage2[1]));
|
||||
|
|
@ -1889,7 +2080,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat command to the server
|
||||
/// Send a chat command to the server - 1.19 and above
|
||||
/// </summary>
|
||||
/// <param name="command">Command</param>
|
||||
/// <param name="playerKeyPair">PlayerKeyPair</param>
|
||||
|
|
@ -1906,6 +2097,9 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
try
|
||||
{
|
||||
LastSeenMessageList.Acknowledgment? acknowledgment =
|
||||
(protocolVersion >= MC_1_19_2_Version) ? this.consumeAcknowledgment() : null;
|
||||
|
||||
List<byte> fields = new();
|
||||
|
||||
// Command: String
|
||||
|
|
@ -1915,24 +2109,25 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
DateTimeOffset timeNow = DateTimeOffset.UtcNow;
|
||||
fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds()));
|
||||
|
||||
List<Tuple<string, string>> needSigned = collectCommandArguments(command); // List< Argument Name, Argument Value >
|
||||
// foreach (var msg in needSigned)
|
||||
// log.Info("<" + msg.Item1 + ">: " + msg.Item2);
|
||||
if (needSigned.Count == 0 || playerKeyPair == null || !Settings.SignMessageInCommand)
|
||||
List<Tuple<string, string>>? needSigned =
|
||||
playerKeyPair != null ? CollectCommandArguments(command) : null; // List< Argument Name, Argument Value >
|
||||
if (needSigned == null || needSigned!.Count == 0)
|
||||
{
|
||||
fields.AddRange(dataTypes.GetLong(0)); // Salt: Long
|
||||
fields.AddRange(dataTypes.GetVarInt(0)); // Signature Length: VarInt
|
||||
}
|
||||
else
|
||||
{
|
||||
string uuid = handler.GetUserUUID()!;
|
||||
Guid uuid = handler.GetUserUuid();
|
||||
byte[] salt = GenerateSalt();
|
||||
fields.AddRange(salt); // Salt: Long
|
||||
fields.AddRange(dataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt
|
||||
foreach (var argument in needSigned)
|
||||
{
|
||||
fields.AddRange(dataTypes.GetString(argument.Item1)); // Argument name: String
|
||||
byte[] sign = playerKeyPair.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt);
|
||||
byte[] sign = (protocolVersion >= MC_1_19_2_Version) ?
|
||||
playerKeyPair!.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt, acknowledgment!.lastSeen) :
|
||||
playerKeyPair!.PrivateKey.SignMessage(argument.Item2, uuid, timeNow, ref salt);
|
||||
fields.AddRange(dataTypes.GetVarInt(sign.Length)); // Signature length: VarInt
|
||||
fields.AddRange(sign); // Signature: Byte Array
|
||||
}
|
||||
|
|
@ -1941,6 +2136,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
// Signed Preview: Boolean
|
||||
fields.AddRange(dataTypes.GetBool(false));
|
||||
|
||||
if (protocolVersion >= MC_1_19_2_Version)
|
||||
{
|
||||
// Message Acknowledgment
|
||||
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment!, isOnlineMode));
|
||||
}
|
||||
|
||||
SendPacket(PacketTypesOut.ChatCommand, fields);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1973,11 +2174,14 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
{
|
||||
LastSeenMessageList.Acknowledgment? acknowledgment =
|
||||
(protocolVersion >= MC_1_19_2_Version) ? this.consumeAcknowledgment() : null;
|
||||
|
||||
// Timestamp: Instant(Long)
|
||||
DateTimeOffset timeNow = DateTimeOffset.UtcNow;
|
||||
fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds()));
|
||||
|
||||
if (playerKeyPair == null || !Settings.SignChat)
|
||||
if (!isOnlineMode || playerKeyPair == null || !Settings.SignChat)
|
||||
{
|
||||
fields.AddRange(dataTypes.GetLong(0)); // Salt: Long
|
||||
fields.AddRange(dataTypes.GetVarInt(0)); // Signature Length: VarInt
|
||||
|
|
@ -1989,14 +2193,22 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
fields.AddRange(salt);
|
||||
|
||||
// Signature Length & Signature: (VarInt) and Byte Array
|
||||
string uuid = handler.GetUserUUID()!;
|
||||
byte[] sign = playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt);
|
||||
Guid uuid = handler.GetUserUuid();
|
||||
byte[] sign = (protocolVersion >= MC_1_19_2_Version) ?
|
||||
playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt, acknowledgment!.lastSeen) :
|
||||
playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt);
|
||||
fields.AddRange(dataTypes.GetVarInt(sign.Length));
|
||||
fields.AddRange(sign);
|
||||
}
|
||||
|
||||
// Signed Preview: Boolean
|
||||
fields.AddRange(dataTypes.GetBool(false));
|
||||
|
||||
if (protocolVersion >= MC_1_19_2_Version)
|
||||
{
|
||||
// Message Acknowledgment
|
||||
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment!, isOnlineMode));
|
||||
}
|
||||
}
|
||||
SendPacket(PacketTypesOut.ChatMessage, fields);
|
||||
return true;
|
||||
|
|
@ -2077,9 +2289,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
List<byte> fields = new List<byte>();
|
||||
fields.AddRange(dataTypes.GetString(language));
|
||||
fields.Add(viewDistance);
|
||||
fields.AddRange(protocolVersion >= MC_1_9_Version
|
||||
? dataTypes.GetVarInt(chatMode)
|
||||
: new byte[] { chatMode });
|
||||
|
||||
if (protocolVersion >= MC_1_9_Version)
|
||||
fields.AddRange(dataTypes.GetVarInt(chatMode));
|
||||
else
|
||||
fields.AddRange(new byte[] { chatMode });
|
||||
|
||||
fields.Add(chatColors ? (byte)1 : (byte)0);
|
||||
if (protocolVersion < MC_1_8_Version)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace MinecraftClient.Protocol
|
|||
/// Start the login procedure once connected to the server
|
||||
/// </summary>
|
||||
/// <returns>True if login was successful</returns>
|
||||
bool Login(PlayerKeyPair playerKeyPair);
|
||||
bool Login(PlayerKeyPair? playerKeyPair);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect from the server
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using System.Text;
|
|||
using MinecraftClient.Mapping;
|
||||
using MinecraftClient.Inventory;
|
||||
using MinecraftClient.Logger;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
{
|
||||
|
|
@ -22,7 +23,8 @@ namespace MinecraftClient.Protocol
|
|||
int GetServerPort();
|
||||
string GetServerHost();
|
||||
string GetUsername();
|
||||
string GetUserUUID();
|
||||
Guid GetUserUuid();
|
||||
string GetUserUuidStr();
|
||||
string GetSessionID();
|
||||
string[] GetOnlinePlayers();
|
||||
Dictionary<string, string> GetOnlinePlayersWithUUID();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
namespace MinecraftClient.Protocol.Message
|
||||
{
|
||||
public class ChatMessage
|
||||
{
|
||||
|
|
@ -17,7 +17,7 @@ namespace MinecraftClient.Protocol
|
|||
|
||||
// 0: chat (chat box), 1: system message (chat box), 2: game info (above hotbar), 3: say command,
|
||||
// 4: msg command, 5: team msg command, 6: emote command, 7: tellraw command
|
||||
public readonly int chatType;
|
||||
public readonly int chatTypeId;
|
||||
|
||||
public readonly Guid senderUUID;
|
||||
|
||||
|
|
@ -31,31 +31,44 @@ namespace MinecraftClient.Protocol
|
|||
|
||||
public readonly DateTime? timestamp;
|
||||
|
||||
public readonly byte[]? signature;
|
||||
|
||||
public readonly bool? isSignatureLegal;
|
||||
|
||||
public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, string? unsignedContent, string displayName, string? teamName, long timestamp, bool isSignatureLegal)
|
||||
public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, string? unsignedContent, string displayName, string? teamName, long timestamp, byte[] signature, bool isSignatureLegal)
|
||||
{
|
||||
this.isSignedChat = true;
|
||||
this.isSystemChat = false;
|
||||
isSignedChat = true;
|
||||
isSystemChat = false;
|
||||
this.content = content;
|
||||
this.isJson = isJson;
|
||||
this.chatType = chatType;
|
||||
this.chatTypeId = chatType;
|
||||
this.senderUUID = senderUUID;
|
||||
this.unsignedContent = unsignedContent;
|
||||
this.displayName = displayName;
|
||||
this.teamName = teamName;
|
||||
this.timestamp = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
|
||||
this.signature = signature;
|
||||
this.isSignatureLegal = isSignatureLegal;
|
||||
}
|
||||
|
||||
public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, bool isSystemChat = false)
|
||||
{
|
||||
this.isSignedChat = isSystemChat;
|
||||
isSignedChat = isSystemChat;
|
||||
this.isSystemChat = isSystemChat;
|
||||
this.content = content;
|
||||
this.isJson = isJson;
|
||||
this.chatType = chatType;
|
||||
this.chatTypeId = chatType;
|
||||
this.senderUUID = senderUUID;
|
||||
}
|
||||
|
||||
public LastSeenMessageList.Entry? toLastSeenMessageEntry()
|
||||
{
|
||||
return signature != null ? new LastSeenMessageList.Entry(senderUUID, signature) : null;
|
||||
}
|
||||
|
||||
public bool lacksSender()
|
||||
{
|
||||
return this.senderUUID == Guid.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
{
|
||||
|
|
@ -12,6 +13,20 @@ namespace MinecraftClient.Protocol
|
|||
|
||||
static class ChatParser
|
||||
{
|
||||
public enum MessageType
|
||||
{
|
||||
CHAT,
|
||||
SAY_COMMAND,
|
||||
MSG_COMMAND_INCOMING,
|
||||
MSG_COMMAND_OUTGOING,
|
||||
TEAM_MSG_COMMAND_INCOMING,
|
||||
TEAM_MSG_COMMAND_OUTGOING,
|
||||
EMOTE_COMMAND,
|
||||
RAW_MSG
|
||||
};
|
||||
|
||||
public static Dictionary<int, MessageType>? ChatId2Type;
|
||||
|
||||
/// <summary>
|
||||
/// The main function to convert text from MC 1.6+ JSON to MC 1.5.2 formatted text
|
||||
/// </summary>
|
||||
|
|
@ -31,60 +46,68 @@ namespace MinecraftClient.Protocol
|
|||
/// <returns>Returns the translated text</returns>
|
||||
public static string ParseSignedChat(ChatMessage message, List<string>? links = null)
|
||||
{
|
||||
string content;
|
||||
if (Settings.ShowModifiedChat && message.unsignedContent != null)
|
||||
content = ChatParser.ParseText(message.unsignedContent, links);
|
||||
else
|
||||
content = ChatParser.ParseText(message.content, links);
|
||||
string chatContent = Settings.ShowModifiedChat && message.unsignedContent != null ? message.unsignedContent : message.content;
|
||||
string content = message.isJson ? ParseText(chatContent, links) : chatContent;
|
||||
string sender = message.displayName!;
|
||||
|
||||
string text;
|
||||
List<string> usingData = new();
|
||||
switch (message.chatType)
|
||||
|
||||
MessageType chatType;
|
||||
if (message.isSystemChat)
|
||||
chatType = MessageType.RAW_MSG;
|
||||
else if (!ChatId2Type!.TryGetValue(message.chatTypeId, out chatType))
|
||||
chatType = MessageType.CHAT;
|
||||
switch (chatType)
|
||||
{
|
||||
case 0: // chat (chat box)
|
||||
case MessageType.CHAT:
|
||||
usingData.Add(sender);
|
||||
usingData.Add(content);
|
||||
text = TranslateString("chat.type.text", usingData);
|
||||
break;
|
||||
case 1: // system message (chat box)
|
||||
text = content;
|
||||
break;
|
||||
case 2: // game info (above hotbar)
|
||||
text = content;
|
||||
break;
|
||||
case 3: // say command
|
||||
case MessageType.SAY_COMMAND:
|
||||
usingData.Add(sender);
|
||||
usingData.Add(content);
|
||||
text = TranslateString("chat.type.announcement", usingData);
|
||||
break;
|
||||
case 4: // msg command
|
||||
case MessageType.MSG_COMMAND_INCOMING:
|
||||
usingData.Add(sender);
|
||||
usingData.Add(content);
|
||||
text = TranslateString("commands.message.display.incoming", usingData);
|
||||
break;
|
||||
case 5: // team msg command (/teammsg)
|
||||
case MessageType.MSG_COMMAND_OUTGOING:
|
||||
usingData.Add(sender);
|
||||
usingData.Add(content);
|
||||
text = TranslateString("commands.message.display.outgoing", usingData);
|
||||
break;
|
||||
case MessageType.TEAM_MSG_COMMAND_INCOMING:
|
||||
usingData.Add(message.teamName!);
|
||||
usingData.Add(sender);
|
||||
usingData.Add(content);
|
||||
text = TranslateString("chat.type.team.text", usingData);
|
||||
break;
|
||||
case 6: // emote command (/me)
|
||||
case MessageType.TEAM_MSG_COMMAND_OUTGOING:
|
||||
usingData.Add(message.teamName!);
|
||||
usingData.Add(sender);
|
||||
usingData.Add(content);
|
||||
text = TranslateString("chat.type.team.sent", usingData);
|
||||
break;
|
||||
case MessageType.EMOTE_COMMAND:
|
||||
usingData.Add(sender);
|
||||
usingData.Add(content);
|
||||
text = TranslateString("chat.type.emote", usingData);
|
||||
break;
|
||||
case 7: // tellraw command
|
||||
case MessageType.RAW_MSG:
|
||||
text = content;
|
||||
break;
|
||||
default:
|
||||
text = $"{sender}: {content}";
|
||||
break;
|
||||
goto case MessageType.CHAT;
|
||||
}
|
||||
string color = String.Empty;
|
||||
string color = string.Empty;
|
||||
if (message.isSystemChat)
|
||||
{
|
||||
if (Settings.MarkSystemMessage)
|
||||
color = "§z §r "; // Custom: Background Gray
|
||||
color = "§z §r "; // Custom color code §z : Background Gray
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -93,18 +116,18 @@ namespace MinecraftClient.Protocol
|
|||
if (Settings.ShowModifiedChat && message.unsignedContent != null)
|
||||
{
|
||||
if (Settings.MarkModifiedMsg)
|
||||
color = "§x §r "; // Custom: Background Yellow
|
||||
color = "§x §r "; // Custom color code §x : Background Yellow
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.MarkLegallySignedMsg)
|
||||
color = "§y §r "; // Custom: Background Green
|
||||
color = "§y §r "; // Custom color code §y : Background Green
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.MarkIllegallySignedMsg)
|
||||
color = "§w §r "; // Custom: Background Red
|
||||
color = "§w §r "; // Custom color code §w : Background Red
|
||||
}
|
||||
}
|
||||
return color + text;
|
||||
|
|
@ -172,13 +195,13 @@ namespace MinecraftClient.Protocol
|
|||
TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s";
|
||||
|
||||
//Language file in a subfolder, depending on the language setting
|
||||
if (!System.IO.Directory.Exists("lang"))
|
||||
System.IO.Directory.CreateDirectory("lang");
|
||||
if (!Directory.Exists("lang"))
|
||||
Directory.CreateDirectory("lang");
|
||||
|
||||
string Language_File = "lang" + Path.DirectorySeparatorChar + Settings.Language + ".lang";
|
||||
|
||||
//File not found? Try downloading language file from Mojang's servers?
|
||||
if (!System.IO.File.Exists(Language_File))
|
||||
if (!File.Exists(Language_File))
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted(Translations.Get("chat.download", Settings.Language));
|
||||
try
|
||||
|
|
@ -197,7 +220,7 @@ namespace MinecraftClient.Protocol
|
|||
stringBuilder.Append(entry.Key + "=" + entry.Value.StringValue + Environment.NewLine);
|
||||
}
|
||||
|
||||
System.IO.File.WriteAllText(Language_File, stringBuilder.ToString());
|
||||
File.WriteAllText(Language_File, stringBuilder.ToString());
|
||||
ConsoleIO.WriteLineFormatted(Translations.Get("chat.done", Language_File));
|
||||
}
|
||||
catch
|
||||
|
|
@ -207,17 +230,17 @@ namespace MinecraftClient.Protocol
|
|||
}
|
||||
|
||||
//Download Failed? Defaulting to en_GB.lang if the game is installed
|
||||
if (!System.IO.File.Exists(Language_File) //Try en_GB.lang
|
||||
&& System.IO.File.Exists(Settings.TranslationsFile_FromMCDir))
|
||||
if (!File.Exists(Language_File) //Try en_GB.lang
|
||||
&& File.Exists(Settings.TranslationsFile_FromMCDir))
|
||||
{
|
||||
Language_File = Settings.TranslationsFile_FromMCDir;
|
||||
Translations.WriteLineFormatted("chat.from_dir");
|
||||
}
|
||||
|
||||
//Load the external dictionnary of translation rules or display an error message
|
||||
if (System.IO.File.Exists(Language_File))
|
||||
if (File.Exists(Language_File))
|
||||
{
|
||||
string[] translations = System.IO.File.ReadAllLines(Language_File);
|
||||
string[] translations = File.ReadAllLines(Language_File);
|
||||
foreach (string line in translations)
|
||||
{
|
||||
if (line.Length > 0)
|
||||
|
|
@ -289,7 +312,7 @@ namespace MinecraftClient.Protocol
|
|||
}
|
||||
return result.ToString();
|
||||
}
|
||||
else return "[" + rulename + "] " + String.Join(" ", using_data);
|
||||
else return "[" + rulename + "] " + string.Join(" ", using_data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -315,7 +338,7 @@ namespace MinecraftClient.Protocol
|
|||
if (clickEvent.Properties.ContainsKey("action")
|
||||
&& clickEvent.Properties.ContainsKey("value")
|
||||
&& clickEvent.Properties["action"].StringValue == "open_url"
|
||||
&& !String.IsNullOrEmpty(clickEvent.Properties["value"].StringValue))
|
||||
&& !string.IsNullOrEmpty(clickEvent.Properties["value"].StringValue))
|
||||
{
|
||||
links.Add(clickEvent.Properties["value"].StringValue);
|
||||
}
|
||||
|
|
@ -372,7 +395,7 @@ namespace MinecraftClient.Protocol
|
|||
System.Net.HttpWebRequest myRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
|
||||
myRequest.Method = "GET";
|
||||
System.Net.WebResponse myResponse = myRequest.GetResponse();
|
||||
System.IO.StreamReader sr = new System.IO.StreamReader(myResponse.GetResponseStream(), System.Text.Encoding.UTF8);
|
||||
StreamReader sr = new StreamReader(myResponse.GetResponseStream(), Encoding.UTF8);
|
||||
string result = sr.ReadToEnd();
|
||||
sr.Close();
|
||||
myResponse.Close();
|
||||
117
MinecraftClient/Protocol/Message/LastSeenMessageList.cs
Normal file
117
MinecraftClient/Protocol/Message/LastSeenMessageList.cs
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MinecraftClient.Protocol.Message
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of messages a client has seen.
|
||||
/// </summary>
|
||||
public class LastSeenMessageList
|
||||
{
|
||||
public static readonly LastSeenMessageList EMPTY = new(new Entry[0]);
|
||||
public static readonly int MAX_ENTRIES = 5;
|
||||
|
||||
public Entry[] entries;
|
||||
|
||||
public LastSeenMessageList(Entry[] list)
|
||||
{
|
||||
entries = list;
|
||||
}
|
||||
|
||||
public void WriteForSign(List<byte> data)
|
||||
{
|
||||
foreach (Entry entry in entries)
|
||||
{
|
||||
data.Add(70);
|
||||
data.AddRange(entry.profileId.ToBigEndianBytes());
|
||||
data.AddRange(entry.lastSignature);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A pair of a player's UUID and the signature of the last message they saw, used as an entry of LastSeenMessageList.
|
||||
/// </summary>
|
||||
public class Entry
|
||||
{
|
||||
public Guid profileId;
|
||||
public byte[] lastSignature;
|
||||
|
||||
public Entry(Guid profileId, byte[] lastSignature)
|
||||
{
|
||||
this.profileId = profileId;
|
||||
this.lastSignature = lastSignature;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A record of messages acknowledged by a client.
|
||||
/// This holds the messages the client has recently seen, as well as the last message they received, if any.
|
||||
/// </summary>
|
||||
public class Acknowledgment
|
||||
{
|
||||
public LastSeenMessageList lastSeen;
|
||||
public Entry? lastReceived;
|
||||
|
||||
public Acknowledgment(LastSeenMessageList lastSeenMessageList, Entry? lastReceivedMessage)
|
||||
{
|
||||
lastSeen = lastSeenMessageList;
|
||||
lastReceived = lastReceivedMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Collects the message that are last seen by a client.
|
||||
/// The message, along with the "last received" message, forms an "acknowledgment" of received messages.
|
||||
/// They are sent to the server when the client has enough messages received or when they send a message.
|
||||
/// The maximum amount of message entries are specified in the constructor.The vanilla clients collect 5 entries.
|
||||
/// Calling add adds the message to the beginning of the entries list, and evicts the oldest message.
|
||||
/// If there are entries with the same sender profile ID, the older entry will be replaced with null instead of filling the hole.
|
||||
/// </summary>
|
||||
public class LastSeenMessagesCollector
|
||||
{
|
||||
private readonly LastSeenMessageList.Entry[] entries;
|
||||
private int size = 0;
|
||||
private LastSeenMessageList lastSeenMessages;
|
||||
|
||||
public LastSeenMessagesCollector(int size)
|
||||
{
|
||||
lastSeenMessages = LastSeenMessageList.EMPTY;
|
||||
entries = new LastSeenMessageList.Entry[size];
|
||||
}
|
||||
|
||||
public void Add(LastSeenMessageList.Entry entry)
|
||||
{
|
||||
LastSeenMessageList.Entry? lastEntry = entry;
|
||||
|
||||
for (int i = 0; i < size; ++i)
|
||||
{
|
||||
LastSeenMessageList.Entry curEntry = entries[i];
|
||||
entries[i] = lastEntry;
|
||||
lastEntry = curEntry;
|
||||
if (curEntry.profileId == entry.profileId)
|
||||
{
|
||||
lastEntry = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastEntry != null && size < entries.Length)
|
||||
entries[size++] = lastEntry;
|
||||
|
||||
LastSeenMessageList.Entry[] msgList = new LastSeenMessageList.Entry[size];
|
||||
for (int i = 0; i < size; ++i)
|
||||
msgList[i] = entries[i];
|
||||
lastSeenMessages = new LastSeenMessageList(msgList);
|
||||
}
|
||||
|
||||
public LastSeenMessageList GetLastSeenMessages()
|
||||
{
|
||||
return lastSeenMessages;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -4,17 +4,18 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MinecraftClient.Protocol.Keys;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
{
|
||||
public class PlayerInfo
|
||||
{
|
||||
public readonly Guid UUID;
|
||||
public readonly Guid Uuid;
|
||||
|
||||
public readonly string Name;
|
||||
|
||||
// Tuple<Name, Value, Signature(empty if there is no signature)
|
||||
public readonly Tuple<string, string, string>[]? Property;
|
||||
public readonly Tuple<string, string, string?>[]? Property;
|
||||
|
||||
public int Gamemode;
|
||||
|
||||
|
|
@ -26,9 +27,13 @@ namespace MinecraftClient.Protocol
|
|||
|
||||
private readonly DateTime? KeyExpiresAt;
|
||||
|
||||
public PlayerInfo(Guid uuid, string name, Tuple<string, string, string>[]? property, int gamemode, int ping, string? displayName, long? timeStamp, byte[]? publicKey, byte[]? signature)
|
||||
private bool lastMessageVerified;
|
||||
|
||||
private byte[]? precedingSignature;
|
||||
|
||||
public PlayerInfo(Guid uuid, string name, Tuple<string, string, string?>[]? property, int gamemode, int ping, string? displayName, long? timeStamp, byte[]? publicKey, byte[]? signature)
|
||||
{
|
||||
UUID = uuid;
|
||||
Uuid = uuid;
|
||||
Name = name;
|
||||
if (property != null)
|
||||
Property = property;
|
||||
|
|
@ -48,36 +53,107 @@ namespace MinecraftClient.Protocol
|
|||
PublicKey = null;
|
||||
}
|
||||
}
|
||||
lastMessageVerified = true;
|
||||
precedingSignature = null;
|
||||
}
|
||||
|
||||
public PlayerInfo(string name, Guid uuid)
|
||||
{
|
||||
Name = name;
|
||||
UUID = uuid;
|
||||
Uuid = uuid;
|
||||
Gamemode = -1;
|
||||
Ping = 0;
|
||||
lastMessageVerified = true;
|
||||
precedingSignature = null;
|
||||
}
|
||||
|
||||
public bool IsKeyVaild()
|
||||
public bool IsMessageChainLegal()
|
||||
{
|
||||
return PublicKey != null && DateTime.Now.ToUniversalTime() > this.KeyExpiresAt;
|
||||
return this.lastMessageVerified;
|
||||
}
|
||||
|
||||
public bool VerifyMessage(string message, Guid uuid, long timestamp, long salt, ref byte[] signature)
|
||||
public bool IsKeyExpired()
|
||||
{
|
||||
if (PublicKey == null)
|
||||
return DateTime.Now.ToUniversalTime() > this.KeyExpiresAt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify message - 1.19
|
||||
/// </summary>
|
||||
/// <param name="message">Message content</param>
|
||||
/// <param name="timestamp">Timestamp</param>
|
||||
/// <param name="salt">Salt</param>
|
||||
/// <param name="signature">Message signature</param>
|
||||
/// <returns>Is this message vaild</returns>
|
||||
public bool VerifyMessage(string message, long timestamp, long salt, ref byte[] signature)
|
||||
{
|
||||
if (PublicKey == null || IsKeyExpired())
|
||||
return false;
|
||||
else
|
||||
{
|
||||
string uuidString = uuid.ToString().Replace("-", string.Empty);
|
||||
DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
|
||||
|
||||
byte[] saltByte = BitConverter.GetBytes(salt);
|
||||
Array.Reverse(saltByte);
|
||||
|
||||
return PublicKey.VerifyMessage(message, Uuid, timeOffset, ref saltByte, ref signature);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify message - 1.19.1 and above
|
||||
/// </summary>
|
||||
/// <param name="message">Message content</param>
|
||||
/// <param name="timestamp">Timestamp</param>
|
||||
/// <param name="salt">Salt</param>
|
||||
/// <param name="signature">Message signature</param>
|
||||
/// <param name="precedingSignature">Preceding message signature</param>
|
||||
/// <param name="lastSeenMessages">LastSeenMessages</param>
|
||||
/// <returns>Is this message chain vaild</returns>
|
||||
public bool VerifyMessage(string message, long timestamp, long salt, ref byte[] signature, ref byte[]? precedingSignature, LastSeenMessageList lastSeenMessages)
|
||||
{
|
||||
if (this.lastMessageVerified == false)
|
||||
return false;
|
||||
if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null))
|
||||
return false;
|
||||
if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!))
|
||||
return false;
|
||||
|
||||
DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
|
||||
|
||||
byte[] saltByte = BitConverter.GetBytes(salt);
|
||||
Array.Reverse(saltByte);
|
||||
|
||||
return PublicKey.VerifyMessage(message, uuidString, timeOffset, ref saltByte, ref signature);
|
||||
bool res = PublicKey.VerifyMessage(message, Uuid, timeOffset, ref saltByte, ref signature, ref precedingSignature, lastSeenMessages);
|
||||
|
||||
this.lastMessageVerified = res;
|
||||
this.precedingSignature = signature;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify message head - 1.19.1 and above
|
||||
/// </summary>
|
||||
/// <param name="precedingSignature">Preceding message signature</param>
|
||||
/// <param name="headerSignature">Message signature</param>
|
||||
/// <param name="bodyDigest">Message body hash</param>
|
||||
/// <returns>Is this message chain vaild</returns>
|
||||
public bool VerifyMessageHead(ref byte[]? precedingSignature, ref byte[] headerSignature, ref byte[] bodyDigest)
|
||||
{
|
||||
if (this.lastMessageVerified == false)
|
||||
return false;
|
||||
if (PublicKey == null || IsKeyExpired() || (this.precedingSignature != null && precedingSignature == null))
|
||||
return false;
|
||||
if (this.precedingSignature != null && !this.precedingSignature.SequenceEqual(precedingSignature!))
|
||||
return false;
|
||||
|
||||
bool res = PublicKey.VerifyHeader(ref bodyDigest, ref headerSignature);
|
||||
|
||||
this.lastMessageVerified = res;
|
||||
this.precedingSignature = headerSignature;
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MinecraftClient.Protocol.Handlers;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol.Keys
|
||||
{
|
||||
static class KeyUtils
|
||||
{
|
||||
private static string certificates = "https://api.minecraftservices.com/player/certificates";
|
||||
private static readonly SHA256 sha256Hash = SHA256.Create();
|
||||
|
||||
private static readonly string certificates = "https://api.minecraftservices.com/player/certificates";
|
||||
|
||||
public static PlayerKeyPair? GetKeys(string accessToken)
|
||||
{
|
||||
|
|
@ -67,19 +72,18 @@ namespace MinecraftClient.Protocol.Keys
|
|||
return Convert.FromBase64String(key);
|
||||
}
|
||||
|
||||
public static byte[] GetSignatureData(string message, string uuid, DateTimeOffset timestamp, ref byte[] salt)
|
||||
public static byte[] ComputeHash(byte[] data)
|
||||
{
|
||||
return sha256Hash.ComputeHash(data);
|
||||
}
|
||||
|
||||
public static byte[] GetSignatureData(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt)
|
||||
{
|
||||
List<byte> data = new();
|
||||
|
||||
data.AddRange(salt);
|
||||
|
||||
byte[] UUIDLeastSignificantBits = BitConverter.GetBytes(Convert.ToInt64(uuid[..16], 16));
|
||||
Array.Reverse(UUIDLeastSignificantBits);
|
||||
data.AddRange(UUIDLeastSignificantBits);
|
||||
|
||||
byte[] UUIDMostSignificantBits = BitConverter.GetBytes(Convert.ToInt64(uuid.Substring(16, 16), 16));
|
||||
Array.Reverse(UUIDMostSignificantBits);
|
||||
data.AddRange(UUIDMostSignificantBits);
|
||||
data.AddRange(uuid.ToBigEndianBytes());
|
||||
|
||||
byte[] timestampByte = BitConverter.GetBytes(timestamp.ToUnixTimeSeconds());
|
||||
Array.Reverse(timestampByte);
|
||||
|
|
@ -90,6 +94,39 @@ namespace MinecraftClient.Protocol.Keys
|
|||
return data.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] GetSignatureData(string message, DateTimeOffset timestamp, ref byte[] salt, LastSeenMessageList lastSeenMessages)
|
||||
{
|
||||
List<byte> data = new();
|
||||
|
||||
data.AddRange(salt);
|
||||
|
||||
byte[] timestampByte = BitConverter.GetBytes(timestamp.ToUnixTimeSeconds());
|
||||
Array.Reverse(timestampByte);
|
||||
data.AddRange(timestampByte);
|
||||
|
||||
data.AddRange(Encoding.UTF8.GetBytes(message));
|
||||
|
||||
data.Add(70);
|
||||
|
||||
lastSeenMessages.WriteForSign(data);
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
|
||||
public static byte[] GetSignatureData(byte[]? precedingSignature, Guid sender, byte[] bodySign)
|
||||
{
|
||||
List<byte> data = new();
|
||||
|
||||
if (precedingSignature != null)
|
||||
data.AddRange(precedingSignature);
|
||||
|
||||
data.AddRange(sender.ToBigEndianBytes());
|
||||
|
||||
data.AddRange(bodySign);
|
||||
|
||||
return data.ToArray();
|
||||
}
|
||||
|
||||
// https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs
|
||||
public static string EscapeString(string src)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -65,6 +65,9 @@ namespace MinecraftClient.Protocol.Keys
|
|||
{
|
||||
List<string> datas = new List<string>();
|
||||
datas.Add(Convert.ToBase64String(PublicKey.Key));
|
||||
if (PublicKey.Signature == null)
|
||||
datas.Add(String.Empty);
|
||||
else
|
||||
datas.Add(Convert.ToBase64String(PublicKey.Signature));
|
||||
if (PublicKey.SignatureV2 == null)
|
||||
datas.Add(String.Empty);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol.Keys
|
||||
{
|
||||
|
|
@ -13,6 +14,8 @@ namespace MinecraftClient.Protocol.Keys
|
|||
|
||||
private readonly RSA rsa;
|
||||
|
||||
private byte[]? precedingSignature = null;
|
||||
|
||||
public PrivateKey(string pemKey)
|
||||
{
|
||||
this.Key = KeyUtils.DecodePemKey(pemKey, "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----");
|
||||
|
|
@ -26,7 +29,15 @@ namespace MinecraftClient.Protocol.Keys
|
|||
return rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
|
||||
public byte[] SignMessage(string message, string uuid, DateTimeOffset timestamp, ref byte[] salt)
|
||||
/// <summary>
|
||||
/// Sign message - 1.19
|
||||
/// </summary>
|
||||
/// <param name="message">Message content</param>
|
||||
/// <param name="uuid">Sender uuid</param>
|
||||
/// <param name="timestamp">Timestamp</param>
|
||||
/// <param name="salt">Salt</param>
|
||||
/// <returns>Signature data</returns>
|
||||
public byte[] SignMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt)
|
||||
{
|
||||
string messageJson = "{\"text\":\"" + KeyUtils.EscapeString(message) + "\"}";
|
||||
|
||||
|
|
@ -35,5 +46,27 @@ namespace MinecraftClient.Protocol.Keys
|
|||
return SignData(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sign message - 1.19.1 and above
|
||||
/// </summary>
|
||||
/// <param name="message">Message content</param>
|
||||
/// <param name="uuid">Sender uuid</param>
|
||||
/// <param name="timestamp">Timestamp</param>
|
||||
/// <param name="salt">Salt</param>
|
||||
/// <param name="lastSeenMessages">LastSeenMessageList</param>
|
||||
/// <returns>Signature data</returns>
|
||||
public byte[] SignMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt, LastSeenMessageList lastSeenMessages)
|
||||
{
|
||||
byte[] bodySignData = KeyUtils.GetSignatureData(message, timestamp, ref salt, lastSeenMessages);
|
||||
byte[] bodyDigest = KeyUtils.ComputeHash(bodySignData);
|
||||
|
||||
byte[] msgSignData = KeyUtils.GetSignatureData(precedingSignature, uuid, bodyDigest);
|
||||
byte[] msgSign = SignData(msgSignData);
|
||||
|
||||
this.precedingSignature = msgSign;
|
||||
|
||||
return msgSign;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,24 +4,26 @@ using System.Linq;
|
|||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol.Keys
|
||||
{
|
||||
public class PublicKey
|
||||
{
|
||||
public byte[] Key { get; set; }
|
||||
public byte[] Signature { get; set; }
|
||||
public byte[]? Signature { get; set; }
|
||||
public byte[]? SignatureV2 { get; set; }
|
||||
|
||||
private readonly RSA rsa;
|
||||
|
||||
public PublicKey(string pemKey, string sig, string? sigV2 = null)
|
||||
public PublicKey(string pemKey, string? sig = null, string? sigV2 = null)
|
||||
{
|
||||
this.Key = KeyUtils.DecodePemKey(pemKey, "-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----");
|
||||
|
||||
this.rsa = RSA.Create();
|
||||
rsa.ImportSubjectPublicKeyInfo(this.Key, out _);
|
||||
|
||||
if (!string.IsNullOrEmpty(sig))
|
||||
this.Signature = Convert.FromBase64String(sig);
|
||||
|
||||
if (!string.IsNullOrEmpty(sigV2))
|
||||
|
|
@ -43,12 +45,53 @@ namespace MinecraftClient.Protocol.Keys
|
|||
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
}
|
||||
|
||||
public bool VerifyMessage(string message, string uuid, DateTimeOffset timestamp, ref byte[] salt, ref byte[] signature)
|
||||
/// <summary>
|
||||
/// Verify message - 1.19
|
||||
/// </summary>
|
||||
/// <param name="message">Message content</param>
|
||||
/// <param name="uuid">Sender uuid</param>
|
||||
/// <param name="timestamp">Timestamp</param>
|
||||
/// <param name="salt">Salt</param>
|
||||
/// <param name="signature">Message signature</param>
|
||||
/// <returns>Is this message vaild</returns>
|
||||
public bool VerifyMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt, ref byte[] signature)
|
||||
{
|
||||
byte[] data = KeyUtils.GetSignatureData(message, uuid, timestamp, ref salt);
|
||||
|
||||
return VerifyData(data, signature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify message - 1.19.1 and above
|
||||
/// </summary>
|
||||
/// <param name="message">Message content</param>
|
||||
/// <param name="uuid">Sender uuid</param>
|
||||
/// <param name="timestamp">Timestamp</param>
|
||||
/// <param name="salt">Salt</param>
|
||||
/// <param name="signature">Message signature</param>
|
||||
/// <param name="precedingSignature">Preceding message signature</param>
|
||||
/// <param name="lastSeenMessages">LastSeenMessages</param>
|
||||
/// <returns>Is this message vaild</returns>
|
||||
public bool VerifyMessage(string message, Guid uuid, DateTimeOffset timestamp, ref byte[] salt, ref byte[] signature, ref byte[]? precedingSignature, LastSeenMessageList lastSeenMessages)
|
||||
{
|
||||
byte[] bodySignData = KeyUtils.GetSignatureData(message, timestamp, ref salt, lastSeenMessages);
|
||||
byte[] bodyDigest = KeyUtils.ComputeHash(bodySignData);
|
||||
|
||||
byte[] msgSignData = KeyUtils.GetSignatureData(precedingSignature, uuid, bodyDigest);
|
||||
|
||||
return VerifyData(msgSignData, signature);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify message head - 1.19.1 and above
|
||||
/// </summary>
|
||||
/// <param name="bodyDigest">Message body hash</param>
|
||||
/// <param name="signature">Message signature</param>
|
||||
/// <returns>Is this message header vaild</returns>
|
||||
public bool VerifyHeader(ref byte[] bodyDigest, ref byte[] signature)
|
||||
{
|
||||
return VerifyData(bodyDigest, signature);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ namespace MinecraftClient.Protocol
|
|||
if (Array.IndexOf(supportedVersions_Protocol16, ProtocolVersion) > -1)
|
||||
return new Protocol16Handler(Client, ProtocolVersion, Handler);
|
||||
|
||||
int[] supportedVersions_Protocol18 = { 4, 5, 47, 107, 108, 109, 110, 210, 315, 316, 335, 338, 340, 393, 401, 404, 477, 480, 485, 490, 498, 573, 575, 578, 735, 736, 751, 753, 754, 755, 756, 757, 758, 759 };
|
||||
int[] supportedVersions_Protocol18 = { 4, 5, 47, 107, 108, 109, 110, 210, 315, 316, 335, 338, 340, 393, 401, 404, 477, 480, 485, 490, 498, 573, 575, 578, 735, 736, 751, 753, 754, 755, 756, 757, 758, 759, 760 };
|
||||
|
||||
if (Array.IndexOf(supportedVersions_Protocol18, ProtocolVersion) > -1)
|
||||
return new Protocol18Handler(Client, ProtocolVersion, Handler, forgeInfo);
|
||||
|
|
@ -263,6 +263,9 @@ namespace MinecraftClient.Protocol
|
|||
return 758;
|
||||
case "1.19":
|
||||
return 759;
|
||||
case "1.19.1":
|
||||
case "1.19.2":
|
||||
return 760;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -329,6 +332,7 @@ namespace MinecraftClient.Protocol
|
|||
case 757: return "1.18.1";
|
||||
case 758: return "1.18.2";
|
||||
case 759: return "1.19";
|
||||
case 760: return "1.19.2";
|
||||
default: return "0.0";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ chat.fail=§8Failed to download the file.
|
|||
chat.from_dir=§8Defaulting to en_GB.lang from your Minecraft directory.
|
||||
chat.loaded=§8Translations file loaded.
|
||||
chat.not_found=§8Translations file not found: '{0}'\nSome messages won't be properly printed without this file.
|
||||
chat.message_chain_broken=Player {0}'s message chain is broken!
|
||||
|
||||
[general]
|
||||
# General message/information (i.e. Done)
|
||||
|
|
|
|||
|
|
@ -1110,7 +1110,7 @@ namespace MinecraftClient
|
|||
/// <returns>UserUUID of the current account</returns>
|
||||
protected string GetUserUUID()
|
||||
{
|
||||
return Handler.GetUserUUID();
|
||||
return Handler.GetUserUuidStr();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue