mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Basic support for 1.19.2
This commit is contained in:
parent
af1485c753
commit
c34dd46067
14 changed files with 703 additions and 129 deletions
|
|
@ -14,6 +14,7 @@ using MinecraftClient.Mapping;
|
||||||
using MinecraftClient.Inventory;
|
using MinecraftClient.Inventory;
|
||||||
using MinecraftClient.Logger;
|
using MinecraftClient.Logger;
|
||||||
using MinecraftClient.Protocol.Keys;
|
using MinecraftClient.Protocol.Keys;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient
|
namespace MinecraftClient
|
||||||
{
|
{
|
||||||
|
|
@ -68,7 +69,8 @@ namespace MinecraftClient
|
||||||
private int port;
|
private int port;
|
||||||
private int protocolversion;
|
private int protocolversion;
|
||||||
private string username;
|
private string username;
|
||||||
private string uuid;
|
private Guid uuid;
|
||||||
|
private string uuidStr;
|
||||||
private string sessionid;
|
private string sessionid;
|
||||||
private PlayerKeyPair? playerKeyPair;
|
private PlayerKeyPair? playerKeyPair;
|
||||||
private DateTime lastKeepAlive;
|
private DateTime lastKeepAlive;
|
||||||
|
|
@ -104,7 +106,8 @@ namespace MinecraftClient
|
||||||
public int GetServerPort() { return port; }
|
public int GetServerPort() { return port; }
|
||||||
public string GetServerHost() { return host; }
|
public string GetServerHost() { return host; }
|
||||||
public string GetUsername() { return username; }
|
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 string GetSessionID() { return sessionid; }
|
||||||
public Location GetCurrentLocation() { return location; }
|
public Location GetCurrentLocation() { return location; }
|
||||||
public float GetYaw() { return playerYaw; }
|
public float GetYaw() { return playerYaw; }
|
||||||
|
|
@ -180,7 +183,8 @@ namespace MinecraftClient
|
||||||
|
|
||||||
bool retry = false;
|
bool retry = false;
|
||||||
this.sessionid = sessionID;
|
this.sessionid = sessionID;
|
||||||
this.uuid = uuid;
|
this.uuid = new Guid(uuid);
|
||||||
|
this.uuidStr = uuid;
|
||||||
this.username = user;
|
this.username = user;
|
||||||
this.host = server_ip;
|
this.host = server_ip;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
|
|
@ -2093,20 +2097,18 @@ namespace MinecraftClient
|
||||||
List<string> links = new();
|
List<string> links = new();
|
||||||
string messageText;
|
string messageText;
|
||||||
|
|
||||||
if (message.isJson)
|
if (message.isSignedChat)
|
||||||
{
|
{
|
||||||
if (message.isSignedChat)
|
if (!Settings.ShowIllegalSignedChat && !message.isSystemChat && !(bool)message.isSignatureLegal!)
|
||||||
{
|
return;
|
||||||
if (!Settings.ShowIllegalSignedChat && !message.isSystemChat && !(bool)message.isSignatureLegal!)
|
messageText = ChatParser.ParseSignedChat(message, links);
|
||||||
return;
|
|
||||||
messageText = ChatParser.ParseSignedChat(message, links);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
messageText = ChatParser.ParseText(message.content, links);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
messageText = message.content;
|
if (message.isJson)
|
||||||
|
messageText = ChatParser.ParseText(message.content, links);
|
||||||
|
else
|
||||||
|
messageText = message.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.Chat(messageText);
|
Log.Chat(messageText);
|
||||||
|
|
@ -2243,15 +2245,16 @@ namespace MinecraftClient
|
||||||
if (player.Name == username)
|
if (player.Name == username)
|
||||||
{
|
{
|
||||||
// 1.19+ offline server is possible to return different uuid
|
// 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)
|
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>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -1103,9 +1103,51 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="bytes">Byte array</param>
|
/// <param name="bytes">Byte array</param>
|
||||||
/// <returns>String representation</returns>
|
/// <returns>String representation</returns>
|
||||||
public string ByteArrayToString(byte[] bytes)
|
public string ByteArrayToString(byte[]? bytes)
|
||||||
{
|
{
|
||||||
return BitConverter.ToString(bytes).Replace("-", " ");
|
if (bytes == null)
|
||||||
|
return "null";
|
||||||
|
else
|
||||||
|
return BitConverter.ToString(bytes).Replace("-", " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write LastSeenMessageList
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msgList">Message.LastSeenMessageList</param>
|
||||||
|
/// <returns>Message.LastSeenMessageList Packet Data</returns>
|
||||||
|
public byte[] GetLastSeenMessageList(Message.LastSeenMessageList msgList)
|
||||||
|
{
|
||||||
|
List<byte> fields = new List<byte>();
|
||||||
|
fields.AddRange(GetVarInt(msgList.entries.Length));
|
||||||
|
foreach (Message.LastSeenMessageList.Entry entry in msgList.entries)
|
||||||
|
{
|
||||||
|
fields.AddRange(entry.profileId.ToBigEndianBytes());
|
||||||
|
fields.AddRange(GetVarInt(entry.lastSignature.Length));
|
||||||
|
fields.AddRange(entry.lastSignature);
|
||||||
|
}
|
||||||
|
return fields.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Write LastSeenMessageList.Acknowledgment
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ack">Acknowledgment</param>
|
||||||
|
/// <returns>Acknowledgment Packet Data</returns>
|
||||||
|
public byte[] GetAcknowledgment(Message.LastSeenMessageList.Acknowledgment ack)
|
||||||
|
{
|
||||||
|
List<byte> fields = new List<byte>();
|
||||||
|
fields.AddRange(GetLastSeenMessageList(ack.lastSeen));
|
||||||
|
if (ack.lastReceived == null)
|
||||||
|
fields.AddRange(GetBool(false));
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fields.AddRange(GetBool(true));
|
||||||
|
fields.AddRange(ack.lastReceived.profileId.ToBigEndianBytes());
|
||||||
|
fields.AddRange(GetVarInt(ack.lastReceived.lastSignature.Length));
|
||||||
|
fields.AddRange(ack.lastReceived.lastSignature);
|
||||||
|
}
|
||||||
|
return fields.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
PacketTypePalette p;
|
PacketTypePalette p;
|
||||||
if (protocol > Protocol18Handler.MC_1_19_2_Version)
|
if (protocol > Protocol18Handler.MC_1_19_2_Version)
|
||||||
throw new NotImplementedException(Translations.Get("exception.palette.packet"));
|
throw new NotImplementedException(Translations.Get("exception.palette.packet"));
|
||||||
|
|
||||||
if (protocol <= Protocol18Handler.MC_1_8_Version)
|
if (protocol <= Protocol18Handler.MC_1_8_Version)
|
||||||
p = new PacketPalette17();
|
p = new PacketPalette17();
|
||||||
else if (protocol <= Protocol18Handler.MC_1_11_2_Version)
|
else if (protocol <= Protocol18Handler.MC_1_11_2_Version)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ using System.Security.Cryptography;
|
||||||
using MinecraftClient.Mapping;
|
using MinecraftClient.Mapping;
|
||||||
using MinecraftClient.Inventory;
|
using MinecraftClient.Inventory;
|
||||||
using MinecraftClient.Protocol.Keys;
|
using MinecraftClient.Protocol.Keys;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol.Handlers
|
namespace MinecraftClient.Protocol.Handlers
|
||||||
{
|
{
|
||||||
|
|
@ -559,7 +560,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
|
|
||||||
public bool Login(PlayerKeyPair playerKeyPair)
|
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 });
|
Send(new byte[] { 0xCD, 0 });
|
||||||
try
|
try
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ using MinecraftClient.Logger;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MinecraftClient.Protocol.Keys;
|
using MinecraftClient.Protocol.Keys;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol.Handlers
|
namespace MinecraftClient.Protocol.Handlers
|
||||||
{
|
{
|
||||||
|
|
@ -66,6 +67,10 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
private int protocolversion;
|
private int protocolversion;
|
||||||
private int currentDimension;
|
private int currentDimension;
|
||||||
|
|
||||||
|
private int pendingAcknowledgments = 0;
|
||||||
|
private LastSeenMessagesCollector lastSeenMessagesCollector = new(5);
|
||||||
|
private LastSeenMessageList.Entry? lastReceivedMessage = null;
|
||||||
|
|
||||||
Protocol18Forge pForge;
|
Protocol18Forge pForge;
|
||||||
Protocol18Terrain pTerrain;
|
Protocol18Terrain pTerrain;
|
||||||
IMinecraftComHandler handler;
|
IMinecraftComHandler handler;
|
||||||
|
|
@ -163,6 +168,36 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
else itemPalette = new ItemPalette1161();
|
else itemPalette = new ItemPalette1161();
|
||||||
}
|
}
|
||||||
else itemPalette = new ItemPalette115();
|
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 (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 (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>
|
/// <summary>
|
||||||
|
|
@ -439,14 +474,104 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
|
|
||||||
byte[] messageSignature = dataTypes.ReadNextByteArray(packetData);
|
byte[] messageSignature = dataTypes.ReadNextByteArray(packetData);
|
||||||
|
|
||||||
PlayerInfo? player = handler.GetPlayerInfo(senderUUID);
|
bool verifyResult;
|
||||||
bool verifyResult = player == null ? false : player.VerifyMessage(signedChat, senderUUID, timestamp, salt, ref messageSignature);
|
if (senderUUID.ToString().Replace("-", String.Empty) == handler.GetUserUuidStr())
|
||||||
|
verifyResult = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlayerInfo? player = handler.GetPlayerInfo(senderUUID);
|
||||||
|
verifyResult = player == null ? false : player.VerifyMessage(signedChat, timestamp, salt, ref messageSignature);
|
||||||
|
}
|
||||||
|
|
||||||
handler.OnTextReceived(new(signedChat, true, messageType, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, verifyResult));
|
handler.OnTextReceived(new(signedChat, true, messageType, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, messageSignature, verifyResult));
|
||||||
}
|
}
|
||||||
else // 1.19.1 +
|
else // 1.19.1 +
|
||||||
{
|
{
|
||||||
// Todo
|
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;
|
||||||
|
|
||||||
|
bool verifyResult;
|
||||||
|
if (senderUUID.ToString().Replace("-", String.Empty) == handler.GetUserUuidStr())
|
||||||
|
verifyResult = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlayerInfo? player = handler.GetPlayerInfo(senderUUID);
|
||||||
|
if (player == null)
|
||||||
|
verifyResult = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool lastVerifyResult = player.IsMessageChainLegal();
|
||||||
|
verifyResult = player.VerifyMessage(signedChat, timestamp, salt, ref headerSignature, ref precedingSignature, lastSeenMessages);
|
||||||
|
if (lastVerifyResult && !verifyResult)
|
||||||
|
log.Warn("Player " + player.DisplayName + " message chains broken!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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![chatTypeId];
|
||||||
|
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;
|
||||||
|
|
||||||
|
ChatMessage chat = new(signedChat, false, chatTypeId, senderUUID, unsignedChatContent, senderDisplayName, senderTeamName, timestamp, headerSignature, verifyResult);
|
||||||
|
if (!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 (senderUUID.ToString().Replace("-", String.Empty) == handler.GetUserUuidStr())
|
||||||
|
verifyResult = true;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PlayerInfo? player = handler.GetPlayerInfo(senderUUID);
|
||||||
|
if (player == null)
|
||||||
|
verifyResult = false;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool lastVerifyResult = player.IsMessageChainLegal();
|
||||||
|
verifyResult = player.VerifyMessageHead(ref precedingSignature, ref headerSignature, ref bodyDigest);
|
||||||
|
if (lastVerifyResult && !verifyResult)
|
||||||
|
log.Warn("Player " + player.DisplayName + " message chains broken!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case PacketTypesIn.Respawn:
|
case PacketTypesIn.Respawn:
|
||||||
|
|
@ -826,6 +951,8 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
//handler.OnTextReceived(message, true);
|
//handler.OnTextReceived(message, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case PacketTypesIn.ChatSuggestions:
|
||||||
break;
|
break;
|
||||||
case PacketTypesIn.MapChunkBulk:
|
case PacketTypesIn.MapChunkBulk:
|
||||||
if (protocolversion < MC_1_9_Version && handler.GetTerrainEnabled())
|
if (protocolversion < MC_1_9_Version && handler.GetTerrainEnabled())
|
||||||
|
|
@ -1546,7 +1673,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
}
|
}
|
||||||
if (protocolversion >= MC_1_19_2_Version)
|
if (protocolversion >= MC_1_19_2_Version)
|
||||||
{
|
{
|
||||||
string uuid = handler.GetUserUUID();
|
string uuid = handler.GetUserUuidStr();
|
||||||
if (uuid == "0")
|
if (uuid == "0")
|
||||||
fullLoginPacket.AddRange(dataTypes.GetBool(false)); // Has UUID
|
fullLoginPacket.AddRange(dataTypes.GetBool(false)); // Has UUID
|
||||||
else
|
else
|
||||||
|
|
@ -1572,7 +1699,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
string serverID = dataTypes.ReadNextString(packetData);
|
string serverID = dataTypes.ReadNextString(packetData);
|
||||||
byte[] serverPublicKey = dataTypes.ReadNextByteArray(packetData);
|
byte[] serverPublicKey = dataTypes.ReadNextByteArray(packetData);
|
||||||
byte[] token = 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
|
else if (packetID == 0x02) //Login successful
|
||||||
{
|
{
|
||||||
|
|
@ -1854,15 +1981,57 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
return protocolversion;
|
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);
|
||||||
|
|
||||||
|
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>
|
/// <summary>
|
||||||
/// The signable argument names and their values from command
|
/// The signable argument names and their values from command
|
||||||
/// Signature will used in Vanilla's say, me, msg, teammsg, ban, banip, and kick commands.
|
/// Signature will used in Vanilla's say, me, msg, teammsg, ban, banip, and kick commands.
|
||||||
/// https://gist.github.com/kennytv/ed783dd244ca0321bbd882c347892874#signed-command-arguments
|
/// 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>
|
/// </summary>
|
||||||
/// <param name="command">Command</param>
|
/// <param name="command">Command</param>
|
||||||
/// <returns> List< Argument Name, Argument Value > </returns>
|
/// <returns> List< Argument Name, Argument Value > </returns>
|
||||||
private List<Tuple<string, string>> collectCommandArguments(string command)
|
private static List<Tuple<string, string>> CollectCommandArguments(string command)
|
||||||
{
|
{
|
||||||
List<Tuple<string, string>> needSigned = new();
|
List<Tuple<string, string>> needSigned = new();
|
||||||
|
|
||||||
|
|
@ -1874,21 +2043,25 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
{
|
{
|
||||||
/* /me <action>
|
/* /me <action>
|
||||||
/say <message>
|
/say <message>
|
||||||
/teammsg <message> */
|
/teammsg <message>
|
||||||
|
/tm <message> */
|
||||||
if (argStage1[0] == "me")
|
if (argStage1[0] == "me")
|
||||||
needSigned.Add(new("action", argStage1[1]));
|
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]));
|
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>
|
/* /msg <targets> <message>
|
||||||
|
/tell <targets> <message>
|
||||||
|
/w <targets> <message>
|
||||||
/ban <target> [<reason>]
|
/ban <target> [<reason>]
|
||||||
/ban-ip <target> [<reason>]
|
/ban-ip <target> [<reason>]
|
||||||
/kick <target> [<reason>] */
|
/kick <target> [<reason>] */
|
||||||
string[] argStage2 = argStage1[1].Split(' ', 2, StringSplitOptions.None);
|
string[] argStage2 = argStage1[1].Split(' ', 2, StringSplitOptions.None);
|
||||||
if (argStage2.Length == 2)
|
if (argStage2.Length == 2)
|
||||||
{
|
{
|
||||||
if (argStage1[0] == "msg")
|
if (argStage1[0] == "msg" || argStage1[0] == "tell" || argStage1[0] == "w")
|
||||||
needSigned.Add(new("message", argStage2[1]));
|
needSigned.Add(new("message", argStage2[1]));
|
||||||
else if (argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick")
|
else if (argStage1[0] == "ban" || argStage1[0] == "ban-ip" || argStage1[0] == "kick")
|
||||||
needSigned.Add(new("reason", argStage2[1]));
|
needSigned.Add(new("reason", argStage2[1]));
|
||||||
|
|
@ -1917,6 +2090,8 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
LastSeenMessageList.Acknowledgment? acknowledgment = (protocolversion >= MC_1_19_2_Version) ? this.consumeAcknowledgment() : null;
|
||||||
|
|
||||||
List<byte> fields = new();
|
List<byte> fields = new();
|
||||||
|
|
||||||
// Command: String
|
// Command: String
|
||||||
|
|
@ -1926,9 +2101,7 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
DateTimeOffset timeNow = DateTimeOffset.UtcNow;
|
DateTimeOffset timeNow = DateTimeOffset.UtcNow;
|
||||||
fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds()));
|
fields.AddRange(dataTypes.GetLong(timeNow.ToUnixTimeMilliseconds()));
|
||||||
|
|
||||||
List<Tuple<string, string>> needSigned = collectCommandArguments(command); // List< Argument Name, Argument Value >
|
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)
|
if (needSigned.Count == 0 || playerKeyPair == null || !Settings.SignMessageInCommand)
|
||||||
{
|
{
|
||||||
fields.AddRange(dataTypes.GetLong(0)); // Salt: Long
|
fields.AddRange(dataTypes.GetLong(0)); // Salt: Long
|
||||||
|
|
@ -1936,14 +2109,16 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string uuid = handler.GetUserUUID()!;
|
Guid uuid = handler.GetUserUuid();
|
||||||
byte[] salt = GenerateSalt();
|
byte[] salt = GenerateSalt();
|
||||||
fields.AddRange(salt); // Salt: Long
|
fields.AddRange(salt); // Salt: Long
|
||||||
fields.AddRange(dataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt
|
fields.AddRange(dataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt
|
||||||
foreach (var argument in needSigned)
|
foreach (var argument in needSigned)
|
||||||
{
|
{
|
||||||
fields.AddRange(dataTypes.GetString(argument.Item1)); // Argument name: String
|
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(dataTypes.GetVarInt(sign.Length)); // Signature length: VarInt
|
||||||
fields.AddRange(sign); // Signature: Byte Array
|
fields.AddRange(sign); // Signature: Byte Array
|
||||||
}
|
}
|
||||||
|
|
@ -1952,6 +2127,12 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
// Signed Preview: Boolean
|
// Signed Preview: Boolean
|
||||||
fields.AddRange(dataTypes.GetBool(false));
|
fields.AddRange(dataTypes.GetBool(false));
|
||||||
|
|
||||||
|
if (protocolversion >= MC_1_19_2_Version)
|
||||||
|
{
|
||||||
|
// Message Acknowledgment
|
||||||
|
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment!));
|
||||||
|
}
|
||||||
|
|
||||||
SendPacket(PacketTypesOut.ChatCommand, fields);
|
SendPacket(PacketTypesOut.ChatCommand, fields);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -1971,19 +2152,14 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
if (String.IsNullOrEmpty(message))
|
if (String.IsNullOrEmpty(message))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (protocolversion >= MC_1_19_2_Version)
|
|
||||||
{
|
|
||||||
// Todo
|
|
||||||
log.Warn("Not implement");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process Chat Command - 1.19 and above
|
// Process Chat Command - 1.19 and above
|
||||||
if (protocolversion >= MC_1_19_Version && message.StartsWith('/'))
|
if (protocolversion >= MC_1_19_Version && message.StartsWith('/'))
|
||||||
return SendChatCommand(message[1..], playerKeyPair);
|
return SendChatCommand(message[1..], playerKeyPair);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
LastSeenMessageList.Acknowledgment? acknowledgment = (protocolversion >= MC_1_19_2_Version) ? this.consumeAcknowledgment() : null;
|
||||||
|
|
||||||
List<byte> fields = new();
|
List<byte> fields = new();
|
||||||
|
|
||||||
// Message: String (up to 256 chars)
|
// Message: String (up to 256 chars)
|
||||||
|
|
@ -2007,8 +2183,10 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
fields.AddRange(salt);
|
fields.AddRange(salt);
|
||||||
|
|
||||||
// Signature Length & Signature: (VarInt) and Byte Array
|
// Signature Length & Signature: (VarInt) and Byte Array
|
||||||
string uuid = handler.GetUserUUID()!;
|
Guid uuid = handler.GetUserUuid();
|
||||||
byte[] sign = playerKeyPair.PrivateKey.SignMessage(message, uuid, timeNow, ref salt);
|
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(dataTypes.GetVarInt(sign.Length));
|
||||||
fields.AddRange(sign);
|
fields.AddRange(sign);
|
||||||
}
|
}
|
||||||
|
|
@ -2016,8 +2194,11 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
// Signed Preview: Boolean
|
// Signed Preview: Boolean
|
||||||
fields.AddRange(dataTypes.GetBool(false));
|
fields.AddRange(dataTypes.GetBool(false));
|
||||||
|
|
||||||
fields.AddRange(dataTypes.GetVarInt(0));
|
if (protocolversion >= MC_1_19_2_Version)
|
||||||
fields.AddRange(dataTypes.GetBool(false));
|
{
|
||||||
|
// Message Acknowledgment
|
||||||
|
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment!));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SendPacket(PacketTypesOut.ChatMessage, fields);
|
SendPacket(PacketTypesOut.ChatMessage, fields);
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -2098,9 +2279,12 @@ namespace MinecraftClient.Protocol.Handlers
|
||||||
List<byte> fields = new List<byte>();
|
List<byte> fields = new List<byte>();
|
||||||
fields.AddRange(dataTypes.GetString(language));
|
fields.AddRange(dataTypes.GetString(language));
|
||||||
fields.Add(viewDistance);
|
fields.Add(viewDistance);
|
||||||
fields.AddRange(protocolversion >= MC_1_9_Version
|
|
||||||
? dataTypes.GetVarInt(chatMode)
|
if (protocolversion >= MC_1_9_Version)
|
||||||
: new byte[] { chatMode });
|
fields.AddRange(dataTypes.GetVarInt(chatMode));
|
||||||
|
else
|
||||||
|
fields.AddRange(new byte[] { chatMode });
|
||||||
|
|
||||||
fields.Add(chatColors ? (byte)1 : (byte)0);
|
fields.Add(chatColors ? (byte)1 : (byte)0);
|
||||||
if (protocolversion < MC_1_8_Version)
|
if (protocolversion < MC_1_8_Version)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Text;
|
||||||
using MinecraftClient.Mapping;
|
using MinecraftClient.Mapping;
|
||||||
using MinecraftClient.Inventory;
|
using MinecraftClient.Inventory;
|
||||||
using MinecraftClient.Logger;
|
using MinecraftClient.Logger;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol
|
namespace MinecraftClient.Protocol
|
||||||
{
|
{
|
||||||
|
|
@ -22,7 +23,8 @@ namespace MinecraftClient.Protocol
|
||||||
int GetServerPort();
|
int GetServerPort();
|
||||||
string GetServerHost();
|
string GetServerHost();
|
||||||
string GetUsername();
|
string GetUsername();
|
||||||
string GetUserUUID();
|
Guid GetUserUuid();
|
||||||
|
string GetUserUuidStr();
|
||||||
string GetSessionID();
|
string GetSessionID();
|
||||||
string[] GetOnlinePlayers();
|
string[] GetOnlinePlayers();
|
||||||
Dictionary<string, string> GetOnlinePlayersWithUUID();
|
Dictionary<string, string> GetOnlinePlayersWithUUID();
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol
|
namespace MinecraftClient.Protocol.Message
|
||||||
{
|
{
|
||||||
public class ChatMessage
|
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,
|
// 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
|
// 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;
|
public readonly Guid senderUUID;
|
||||||
|
|
||||||
|
|
@ -31,31 +31,44 @@ namespace MinecraftClient.Protocol
|
||||||
|
|
||||||
public readonly DateTime? timestamp;
|
public readonly DateTime? timestamp;
|
||||||
|
|
||||||
|
public readonly byte[]? signature;
|
||||||
|
|
||||||
public readonly bool? isSignatureLegal;
|
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;
|
isSignedChat = true;
|
||||||
this.isSystemChat = false;
|
isSystemChat = false;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.isJson = isJson;
|
this.isJson = isJson;
|
||||||
this.chatType = chatType;
|
this.chatTypeId = chatType;
|
||||||
this.senderUUID = senderUUID;
|
this.senderUUID = senderUUID;
|
||||||
this.unsignedContent = unsignedContent;
|
this.unsignedContent = unsignedContent;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.teamName = teamName;
|
this.teamName = teamName;
|
||||||
this.timestamp = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
|
this.timestamp = DateTimeOffset.FromUnixTimeMilliseconds(timestamp).DateTime;
|
||||||
|
this.signature = signature;
|
||||||
this.isSignatureLegal = isSignatureLegal;
|
this.isSignatureLegal = isSignatureLegal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, bool isSystemChat = false)
|
public ChatMessage(string content, bool isJson, int chatType, Guid senderUUID, bool isSystemChat = false)
|
||||||
{
|
{
|
||||||
this.isSignedChat = isSystemChat;
|
isSignedChat = isSystemChat;
|
||||||
this.isSystemChat = isSystemChat;
|
this.isSystemChat = isSystemChat;
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.isJson = isJson;
|
this.isJson = isJson;
|
||||||
this.chatType = chatType;
|
this.chatTypeId = chatType;
|
||||||
this.senderUUID = senderUUID;
|
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.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol
|
namespace MinecraftClient.Protocol
|
||||||
{
|
{
|
||||||
|
|
@ -12,6 +13,20 @@ namespace MinecraftClient.Protocol
|
||||||
|
|
||||||
static class ChatParser
|
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>
|
/// <summary>
|
||||||
/// The main function to convert text from MC 1.6+ JSON to MC 1.5.2 formatted text
|
/// The main function to convert text from MC 1.6+ JSON to MC 1.5.2 formatted text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -31,56 +46,64 @@ namespace MinecraftClient.Protocol
|
||||||
/// <returns>Returns the translated text</returns>
|
/// <returns>Returns the translated text</returns>
|
||||||
public static string ParseSignedChat(ChatMessage message, List<string>? links = null)
|
public static string ParseSignedChat(ChatMessage message, List<string>? links = null)
|
||||||
{
|
{
|
||||||
string content;
|
string chatContent = Settings.ShowModifiedChat && message.unsignedContent != null ? message.unsignedContent : message.content;
|
||||||
if (Settings.ShowModifiedChat && message.unsignedContent != null)
|
string content = message.isJson ? ParseText(chatContent, links) : chatContent;
|
||||||
content = ChatParser.ParseText(message.unsignedContent, links);
|
|
||||||
else
|
|
||||||
content = ChatParser.ParseText(message.content, links);
|
|
||||||
string sender = message.displayName!;
|
string sender = message.displayName!;
|
||||||
|
|
||||||
string text;
|
string text;
|
||||||
List<string> usingData = new();
|
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(sender);
|
||||||
usingData.Add(content);
|
usingData.Add(content);
|
||||||
text = TranslateString("chat.type.text", usingData);
|
text = TranslateString("chat.type.text", usingData);
|
||||||
break;
|
break;
|
||||||
case 1: // system message (chat box)
|
case MessageType.SAY_COMMAND:
|
||||||
text = content;
|
|
||||||
break;
|
|
||||||
case 2: // game info (above hotbar)
|
|
||||||
text = content;
|
|
||||||
break;
|
|
||||||
case 3: // say command
|
|
||||||
usingData.Add(sender);
|
usingData.Add(sender);
|
||||||
usingData.Add(content);
|
usingData.Add(content);
|
||||||
text = TranslateString("chat.type.announcement", usingData);
|
text = TranslateString("chat.type.announcement", usingData);
|
||||||
break;
|
break;
|
||||||
case 4: // msg command
|
case MessageType.MSG_COMMAND_INCOMING:
|
||||||
usingData.Add(sender);
|
usingData.Add(sender);
|
||||||
usingData.Add(content);
|
usingData.Add(content);
|
||||||
text = TranslateString("commands.message.display.incoming", usingData);
|
text = TranslateString("commands.message.display.incoming", usingData);
|
||||||
break;
|
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(message.teamName!);
|
||||||
usingData.Add(sender);
|
usingData.Add(sender);
|
||||||
usingData.Add(content);
|
usingData.Add(content);
|
||||||
text = TranslateString("chat.type.team.text", usingData);
|
text = TranslateString("chat.type.team.text", usingData);
|
||||||
break;
|
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(sender);
|
||||||
usingData.Add(content);
|
usingData.Add(content);
|
||||||
text = TranslateString("chat.type.emote", usingData);
|
text = TranslateString("chat.type.emote", usingData);
|
||||||
break;
|
break;
|
||||||
case 7: // tellraw command
|
case MessageType.RAW_MSG:
|
||||||
text = content;
|
text = content;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
text = $"{sender}: {content}";
|
goto case MessageType.CHAT;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
string color = String.Empty;
|
string color = string.Empty;
|
||||||
if (message.isSystemChat)
|
if (message.isSystemChat)
|
||||||
{
|
{
|
||||||
if (Settings.MarkSystemMessage)
|
if (Settings.MarkSystemMessage)
|
||||||
|
|
@ -121,21 +144,21 @@ namespace MinecraftClient.Protocol
|
||||||
{
|
{
|
||||||
/* MC 1.7+ Name MC 1.6 Name Classic tag */
|
/* MC 1.7+ Name MC 1.6 Name Classic tag */
|
||||||
case "black": /* Blank if same */ return "§0";
|
case "black": /* Blank if same */ return "§0";
|
||||||
case "dark_blue": return "§1";
|
case "dark_blue": return "§1";
|
||||||
case "dark_green": return "§2";
|
case "dark_green": return "§2";
|
||||||
case "dark_aqua": case "dark_cyan": return "§3";
|
case "dark_aqua": case "dark_cyan": return "§3";
|
||||||
case "dark_red": return "§4";
|
case "dark_red": return "§4";
|
||||||
case "dark_purple": case "dark_magenta": return "§5";
|
case "dark_purple": case "dark_magenta": return "§5";
|
||||||
case "gold": case "dark_yellow": return "§6";
|
case "gold": case "dark_yellow": return "§6";
|
||||||
case "gray": return "§7";
|
case "gray": return "§7";
|
||||||
case "dark_gray": return "§8";
|
case "dark_gray": return "§8";
|
||||||
case "blue": return "§9";
|
case "blue": return "§9";
|
||||||
case "green": return "§a";
|
case "green": return "§a";
|
||||||
case "aqua": case "cyan": return "§b";
|
case "aqua": case "cyan": return "§b";
|
||||||
case "red": return "§c";
|
case "red": return "§c";
|
||||||
case "light_purple": case "magenta": return "§d";
|
case "light_purple": case "magenta": return "§d";
|
||||||
case "yellow": return "§e";
|
case "yellow": return "§e";
|
||||||
case "white": return "§f";
|
case "white": return "§f";
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -172,13 +195,13 @@ namespace MinecraftClient.Protocol
|
||||||
TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s";
|
TranslationRules["commands.message.display.outgoing"] = "§7You whisper to %s: %s";
|
||||||
|
|
||||||
//Language file in a subfolder, depending on the language setting
|
//Language file in a subfolder, depending on the language setting
|
||||||
if (!System.IO.Directory.Exists("lang"))
|
if (!Directory.Exists("lang"))
|
||||||
System.IO.Directory.CreateDirectory("lang");
|
Directory.CreateDirectory("lang");
|
||||||
|
|
||||||
string Language_File = "lang" + Path.DirectorySeparatorChar + Settings.Language + ".lang";
|
string Language_File = "lang" + Path.DirectorySeparatorChar + Settings.Language + ".lang";
|
||||||
|
|
||||||
//File not found? Try downloading language file from Mojang's servers?
|
//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));
|
ConsoleIO.WriteLineFormatted(Translations.Get("chat.download", Settings.Language));
|
||||||
try
|
try
|
||||||
|
|
@ -197,7 +220,7 @@ namespace MinecraftClient.Protocol
|
||||||
stringBuilder.Append(entry.Key + "=" + entry.Value.StringValue + Environment.NewLine);
|
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));
|
ConsoleIO.WriteLineFormatted(Translations.Get("chat.done", Language_File));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
|
@ -207,17 +230,17 @@ namespace MinecraftClient.Protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
//Download Failed? Defaulting to en_GB.lang if the game is installed
|
//Download Failed? Defaulting to en_GB.lang if the game is installed
|
||||||
if (!System.IO.File.Exists(Language_File) //Try en_GB.lang
|
if (!File.Exists(Language_File) //Try en_GB.lang
|
||||||
&& System.IO.File.Exists(Settings.TranslationsFile_FromMCDir))
|
&& File.Exists(Settings.TranslationsFile_FromMCDir))
|
||||||
{
|
{
|
||||||
Language_File = Settings.TranslationsFile_FromMCDir;
|
Language_File = Settings.TranslationsFile_FromMCDir;
|
||||||
Translations.WriteLineFormatted("chat.from_dir");
|
Translations.WriteLineFormatted("chat.from_dir");
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load the external dictionnary of translation rules or display an error message
|
//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)
|
foreach (string line in translations)
|
||||||
{
|
{
|
||||||
if (line.Length > 0)
|
if (line.Length > 0)
|
||||||
|
|
@ -289,7 +312,7 @@ namespace MinecraftClient.Protocol
|
||||||
}
|
}
|
||||||
return result.ToString();
|
return result.ToString();
|
||||||
}
|
}
|
||||||
else return "[" + rulename + "] " + String.Join(" ", using_data);
|
else return "[" + rulename + "] " + string.Join(" ", using_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -315,11 +338,11 @@ namespace MinecraftClient.Protocol
|
||||||
if (clickEvent.Properties.ContainsKey("action")
|
if (clickEvent.Properties.ContainsKey("action")
|
||||||
&& clickEvent.Properties.ContainsKey("value")
|
&& clickEvent.Properties.ContainsKey("value")
|
||||||
&& clickEvent.Properties["action"].StringValue == "open_url"
|
&& clickEvent.Properties["action"].StringValue == "open_url"
|
||||||
&& !String.IsNullOrEmpty(clickEvent.Properties["value"].StringValue))
|
&& !string.IsNullOrEmpty(clickEvent.Properties["value"].StringValue))
|
||||||
{
|
{
|
||||||
links.Add(clickEvent.Properties["value"].StringValue);
|
links.Add(clickEvent.Properties["value"].StringValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data.Properties.ContainsKey("extra"))
|
if (data.Properties.ContainsKey("extra"))
|
||||||
{
|
{
|
||||||
Json.JSONData[] extras = data.Properties["extra"].DataArray.ToArray();
|
Json.JSONData[] extras = data.Properties["extra"].DataArray.ToArray();
|
||||||
|
|
@ -372,7 +395,7 @@ namespace MinecraftClient.Protocol
|
||||||
System.Net.HttpWebRequest myRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
|
System.Net.HttpWebRequest myRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url);
|
||||||
myRequest.Method = "GET";
|
myRequest.Method = "GET";
|
||||||
System.Net.WebResponse myResponse = myRequest.GetResponse();
|
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();
|
string result = sr.ReadToEnd();
|
||||||
sr.Close();
|
sr.Close();
|
||||||
myResponse.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,12 +4,13 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MinecraftClient.Protocol.Keys;
|
using MinecraftClient.Protocol.Keys;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol
|
namespace MinecraftClient.Protocol
|
||||||
{
|
{
|
||||||
public class PlayerInfo
|
public class PlayerInfo
|
||||||
{
|
{
|
||||||
public readonly Guid UUID;
|
public readonly Guid Uuid;
|
||||||
|
|
||||||
public readonly string Name;
|
public readonly string Name;
|
||||||
|
|
||||||
|
|
@ -26,9 +27,13 @@ namespace MinecraftClient.Protocol
|
||||||
|
|
||||||
private readonly DateTime? KeyExpiresAt;
|
private readonly DateTime? KeyExpiresAt;
|
||||||
|
|
||||||
|
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)
|
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;
|
Name = name;
|
||||||
if (property != null)
|
if (property != null)
|
||||||
Property = property;
|
Property = property;
|
||||||
|
|
@ -48,36 +53,107 @@ namespace MinecraftClient.Protocol
|
||||||
PublicKey = null;
|
PublicKey = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lastMessageVerified = true;
|
||||||
|
precedingSignature = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerInfo(string name, Guid uuid)
|
public PlayerInfo(string name, Guid uuid)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
UUID = uuid;
|
Uuid = uuid;
|
||||||
Gamemode = -1;
|
Gamemode = -1;
|
||||||
Ping = 0;
|
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;
|
return false;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string uuidString = uuid.ToString().Replace("-", string.Empty);
|
|
||||||
|
|
||||||
DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
|
DateTimeOffset timeOffset = DateTimeOffset.FromUnixTimeMilliseconds(timestamp);
|
||||||
|
|
||||||
byte[] saltByte = BitConverter.GetBytes(salt);
|
byte[] saltByte = BitConverter.GetBytes(salt);
|
||||||
Array.Reverse(saltByte);
|
Array.Reverse(saltByte);
|
||||||
|
|
||||||
return PublicKey.VerifyMessage(message, uuidString, timeOffset, ref saltByte, ref signature);
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MinecraftClient.Protocol.Handlers;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol.Keys
|
namespace MinecraftClient.Protocol.Keys
|
||||||
{
|
{
|
||||||
static class KeyUtils
|
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)
|
public static PlayerKeyPair? GetKeys(string accessToken)
|
||||||
{
|
{
|
||||||
|
|
@ -67,19 +72,18 @@ namespace MinecraftClient.Protocol.Keys
|
||||||
return Convert.FromBase64String(key);
|
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();
|
List<byte> data = new();
|
||||||
|
|
||||||
data.AddRange(salt);
|
data.AddRange(salt);
|
||||||
|
|
||||||
byte[] UUIDLeastSignificantBits = BitConverter.GetBytes(Convert.ToInt64(uuid[..16], 16));
|
data.AddRange(uuid.ToBigEndianBytes());
|
||||||
Array.Reverse(UUIDLeastSignificantBits);
|
|
||||||
data.AddRange(UUIDLeastSignificantBits);
|
|
||||||
|
|
||||||
byte[] UUIDMostSignificantBits = BitConverter.GetBytes(Convert.ToInt64(uuid.Substring(16, 16), 16));
|
|
||||||
Array.Reverse(UUIDMostSignificantBits);
|
|
||||||
data.AddRange(UUIDMostSignificantBits);
|
|
||||||
|
|
||||||
byte[] timestampByte = BitConverter.GetBytes(timestamp.ToUnixTimeSeconds());
|
byte[] timestampByte = BitConverter.GetBytes(timestamp.ToUnixTimeSeconds());
|
||||||
Array.Reverse(timestampByte);
|
Array.Reverse(timestampByte);
|
||||||
|
|
@ -90,6 +94,39 @@ namespace MinecraftClient.Protocol.Keys
|
||||||
return data.ToArray();
|
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
|
// https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs
|
||||||
public static string EscapeString(string src)
|
public static string EscapeString(string src)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol.Keys
|
namespace MinecraftClient.Protocol.Keys
|
||||||
{
|
{
|
||||||
|
|
@ -13,6 +14,8 @@ namespace MinecraftClient.Protocol.Keys
|
||||||
|
|
||||||
private readonly RSA rsa;
|
private readonly RSA rsa;
|
||||||
|
|
||||||
|
private byte[]? precedingSignature = null;
|
||||||
|
|
||||||
public PrivateKey(string pemKey)
|
public PrivateKey(string pemKey)
|
||||||
{
|
{
|
||||||
this.Key = KeyUtils.DecodePemKey(pemKey, "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----");
|
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);
|
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) + "\"}";
|
string messageJson = "{\"text\":\"" + KeyUtils.EscapeString(message) + "\"}";
|
||||||
|
|
||||||
|
|
@ -35,5 +46,27 @@ namespace MinecraftClient.Protocol.Keys
|
||||||
return SignData(data);
|
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,6 +4,7 @@ using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MinecraftClient.Protocol.Message;
|
||||||
|
|
||||||
namespace MinecraftClient.Protocol.Keys
|
namespace MinecraftClient.Protocol.Keys
|
||||||
{
|
{
|
||||||
|
|
@ -44,12 +45,53 @@ namespace MinecraftClient.Protocol.Keys
|
||||||
return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
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);
|
byte[] data = KeyUtils.GetSignatureData(message, uuid, timestamp, ref salt);
|
||||||
|
|
||||||
return VerifyData(data, signature);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1110,7 +1110,7 @@ namespace MinecraftClient
|
||||||
/// <returns>UserUUID of the current account</returns>
|
/// <returns>UserUUID of the current account</returns>
|
||||||
protected string GetUserUUID()
|
protected string GetUserUUID()
|
||||||
{
|
{
|
||||||
return Handler.GetUserUUID();
|
return Handler.GetUserUuidStr();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue