First working version, not fully tested

This commit is contained in:
Anon 2024-06-30 11:26:41 +02:00
parent 08c5c15557
commit 67e36a92d2
4 changed files with 294 additions and 72 deletions

View file

@ -69,6 +69,149 @@ namespace MinecraftClient.Mapping
}
}
public static void LoadDefaultDimensions1206Plus()
{
// TODO: Move this to a JSON file.
var defaultRegistryCodec = new Dictionary<string, object>
{
{ "minecraft:dimension_type", new Dictionary<string, object>
{
{ "value", new object[]
{
new Dictionary<string, object>
{
{ "name", "minecraft:overworld" },
{ "id", 0 },
{ "element", new Dictionary<string, object>
{
{ "piglin_safe", (byte)0 },
{ "natural", 1 },
{ "ambient_light", 0.0 },
{ "monster_spawn_block_light_limit", 0 },
{ "infiniburn", "#minecraft:infiniburn_overworld" },
{ "respawn_anchor_works", 0 },
{ "has_skylight", 1 },
{ "bed_works", 1 },
{ "effects", "minecraft:overworld" },
{ "has_raids", 1 },
{ "logical_height", 384 },
{ "coordinate_scale", 1.0 },
{ "monster_spawn_light_level", new Dictionary<string, object>
{
{ "min_inclusive", 0 },
{ "max_inclusive", 7 },
{ "type", "minecraft:uniform" }
}
},
{ "min_y", -64 },
{ "ultrawarm", 0 },
{ "has_ceiling", 0 },
{ "height", 384 }
}
}
},
new Dictionary<string, object>
{
{ "name", "minecraft:overworld_caves" },
{ "id", 1 },
{ "element", new Dictionary<string, object>
{
{ "piglin_safe", (byte)0 },
{ "natural", 1 },
{ "ambient_light", 0.0 },
{ "monster_spawn_block_light_limit", 0 },
{ "infiniburn", "#minecraft:infiniburn_overworld" },
{ "respawn_anchor_works", 0 },
{ "has_skylight", 1 },
{ "bed_works", 1 },
{ "effects", "minecraft:overworld" },
{ "has_raids", 1 },
{ "logical_height", 384 },
{ "coordinate_scale", 1.0 },
{ "monster_spawn_light_level", new Dictionary<string, object>
{
{ "min_inclusive", 0 },
{ "max_inclusive", 7 },
{ "type", "minecraft:uniform" }
}
},
{ "min_y", -64 },
{ "ultrawarm", 0 },
{ "has_ceiling", 1 },
{ "height", 384 }
}
}
},
new Dictionary<string, object>
{
{ "name", "minecraft:the_end" },
{ "id", 2 },
{ "element", new Dictionary<string, object>
{
{ "piglin_safe", (byte)0 },
{ "natural", 0 },
{ "ambient_light", 0.0 },
{ "monster_spawn_block_light_limit", 0 },
{ "infiniburn", "#minecraft:infiniburn_end" },
{ "respawn_anchor_works", 0 },
{ "has_skylight", 0 },
{ "bed_works", 0 },
{ "effects", "minecraft:the_end" },
{ "fixed_time", 6000 },
{ "has_raids", 1 },
{ "logical_height", 256 },
{ "coordinate_scale", 1.0 },
{ "monster_spawn_light_level", new Dictionary<string, object>
{
{ "min_inclusive", 0 },
{ "max_inclusive", 7 },
{ "type", "minecraft:uniform" }
}
},
{ "min_y", 0 },
{ "ultrawarm", 0 },
{ "has_ceiling", 0 },
{ "height", 256 }
}
}
},
new Dictionary<string, object>
{
{ "name", "minecraft:the_nether" },
{ "id", 3 },
{ "element", new Dictionary<string, object>
{
{ "piglin_safe", (byte)1 },
{ "natural", 0 },
{ "ambient_light", 0.1 },
{ "monster_spawn_block_light_limit", 15 },
{ "infiniburn", "#minecraft:infiniburn_nether" },
{ "respawn_anchor_works", 1 },
{ "has_skylight", 0 },
{ "bed_works", 0 },
{ "effects", "minecraft:the_nether" },
{ "fixed_time", 18000 },
{ "has_raids", 0 },
{ "logical_height", 128 },
{ "coordinate_scale", 8.0 },
{ "monster_spawn_light_level", 7 },
{ "min_y", 0 },
{ "ultrawarm", 1 },
{ "has_ceiling", 1 },
{ "height", 256 }
}
}
}
}
}
}
}
};
StoreDimensionList(defaultRegistryCodec);
}
/// <summary>
/// Store one dimension - Directly used in 1.16.2 to 1.18.2
/// </summary>
@ -92,7 +235,6 @@ namespace MinecraftClient.Mapping
curDimension = dimensionList[name]; // Should not fail
}
/// <summary>
/// Get current dimension
/// </summary>

View file

@ -148,9 +148,11 @@ namespace MinecraftClient
public List<ChatBot> GetLoadedChatBots() { return new List<ChatBot>(bots); }
public void GetCookie(string key, out byte[]? data) => Cookies.TryGetValue(key, out data);
public void SetCookie(string key, byte[] data) => Cookies[key] = data;
public void DeleteCookie(string key) => Cookies.Remove(key, out var data);
readonly TcpClient client;
readonly IMinecraftCom handler;
TcpClient client;
IMinecraftCom handler;
SessionToken _sessionToken;
CancellationTokenSource? cmdprompt = null;
Tuple<Thread, CancellationTokenSource>? timeoutdetector = null;
@ -186,6 +188,7 @@ namespace MinecraftClient
this.port = port;
this.protocolversion = protocolversion;
this.playerKeyPair = playerKeyPair;
_sessionToken = session;
Log = Settings.Config.Logging.LogToFile
? new FileLogLogger(Config.AppVar.ExpandVars(Settings.Config.Logging.LogFile), Settings.Config.Logging.PrependTimestamp)
@ -294,6 +297,77 @@ namespace MinecraftClient
}
}
}
public void Transfer(string newHost, int newPort)
{
try
{
Log.Info($"Initiating a transfer to: {host}:{port}");
// Unload bots
UnloadAllBots();
bots.Clear();
// Close existing connection
client.Close();
// Establish new connection
client = ProxyHandler.NewTcpClient(newHost, newPort);
client.ReceiveBufferSize = 1024 * 1024;
client.ReceiveTimeout = Config.Main.Advanced.TcpTimeout * 1000;
// Reinitialize the protocol handler
handler = Protocol.ProtocolHandler.GetProtocolHandler(client, protocolversion, null, this);
Log.Info($"Connected to {host}:{port}");
// Retry login process
if (handler.Login(playerKeyPair, _sessionToken))
{
foreach (var bot in botsOnHold)
BotLoad(bot, false);
botsOnHold.Clear();
Log.Info("Successfully transferred connection and logged in.");
cmdprompt = new CancellationTokenSource();
ConsoleInteractive.ConsoleReader.BeginReadThread();
ConsoleInteractive.ConsoleReader.MessageReceived += ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange += ConsoleIO.AutocompleteHandler;
}
else
{
Log.Error("Failed to login to the new host.");
throw new Exception("Login failed after transfer.");
}
}
catch (Exception ex)
{
Log.Error($"Transfer to {newHost}:{newPort} failed: {ex.Message}");
// Handle reconnection attempts
if (timeoutdetector != null)
{
timeoutdetector.Item2.Cancel();
timeoutdetector = null;
}
if (ReconnectionAttemptsLeft > 0)
{
Log.Info($"Reconnecting... Attempts left: {ReconnectionAttemptsLeft}");
Thread.Sleep(5000);
ReconnectionAttemptsLeft--;
Program.Restart();
}
else if (InternalConfig.InteractiveMode)
{
ConsoleInteractive.ConsoleReader.StopReadThread();
ConsoleInteractive.ConsoleReader.MessageReceived -= ConsoleReaderOnMessageReceived;
ConsoleInteractive.ConsoleReader.OnInputChange -= ConsoleIO.AutocompleteHandler;
Program.HandleFailure();
}
throw new Exception("Transfer failed and reconnection attempts exhausted.");
}
}
/// <summary>
/// Register bots

View file

@ -449,6 +449,9 @@ namespace MinecraftClient.Protocol.Handlers
}
else
{
// TODO: Implement proper parsing for 1.20.6 when there is a custom data pack on the server
// THis is a temporary workaround to get the client to be useable asap
var registryId = dataTypes.ReadNextString(packetData);
var entryCount = dataTypes.ReadNextVarInt(packetData);
@ -466,7 +469,8 @@ namespace MinecraftClient.Protocol.Handlers
if (hasData)
{
dataTypes.ReadNextNbt(packetData); // Never seem to be sent because hasData is always false
// TODO: Parse in case when the server data packs differ from the client
dataTypes.ReadNextNbt(packetData);
}
if (registryId == "minecraft:chat_type")
@ -476,9 +480,9 @@ namespace MinecraftClient.Protocol.Handlers
if (registryId == "minecraft:chat_type")
ChatParser.ReadChatType(avaliableChats);
else
else
{
// TODO: 1.20.6 Somehow store this data from dimensionType in the World class
World.LoadDefaultDimensions1206Plus();
}
}
@ -502,9 +506,8 @@ namespace MinecraftClient.Protocol.Handlers
case ConfigurationPacketTypesIn.Transfer:
var host = dataTypes.ReadNextString(packetData);
var port = dataTypes.ReadNextVarInt(packetData);
// TODO: 1.20.6 Implement Host Chaging in the McClient class
// McClient.Instance?.Transfer(host, port);
McClient.Instance?.Transfer(host, port);
break;
case ConfigurationPacketTypesIn.KnownDataPacks:
@ -673,7 +676,6 @@ namespace MinecraftClient.Protocol.Handlers
// varInt: [1.9.1 to 1.15.2]
// byte: below 1.9.1
string? dimensionTypeName = null;
int? dimensionTypeInt2 = null;
Dictionary<string, object>? dimensionType = null;
switch (protocolVersion)
{
@ -681,9 +683,6 @@ namespace MinecraftClient.Protocol.Handlers
{
switch (protocolVersion)
{
case >= MC_1_20_6_Version:
dimensionTypeInt2 = dataTypes.ReadNextVarInt(packetData);
break;
case >= MC_1_19_Version:
dimensionTypeName =
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
@ -731,9 +730,6 @@ namespace MinecraftClient.Protocol.Handlers
case < MC_1_20_6_Version:
World.SetDimension(dimensionTypeName!);
break;
case >= MC_1_20_6_Version:
// TODO: 1.20.6 Set the dimension (use dimensionTypeInt)
break;
}
}
@ -782,8 +778,19 @@ namespace MinecraftClient.Protocol.Handlers
else
{
dataTypes.ReadNextBool(packetData); // Do limited crafting
var dimensionTypeName =
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
// Dimension Type (string bellow 1.20.6, VarInt for 1.20.6+)
var dimensionTypeName = protocolVersion < MC_1_20_6_Version
? dataTypes.ReadNextString(packetData) // < 1.20.6
: (dataTypes.ReadNextVarInt(packetData) switch // 1.20.6+ // TODO: Use values from the registry
{
0 => "minecraft:overworld",
1 => "minecraft:overworld_caves",
2 => "minecraft:the_end",
3 => "minecraft:the_nether",
_ => null
} ?? "minecraft:overworld");
dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above
if (handler.GetTerrainEnabled())
@ -1240,14 +1247,20 @@ namespace MinecraftClient.Protocol.Handlers
case PacketTypesIn.Respawn:
string? dimensionTypeNameRespawn = null;
Dictionary<string, object>? dimensionTypeRespawn = null;
int? dimensionTypeInt = null;
if (protocolVersion >= MC_1_16_Version)
{
switch (protocolVersion)
{
case >= MC_1_20_6_Version:
dimensionTypeInt = dataTypes.ReadNextVarInt(packetData);
dimensionTypeNameRespawn = dataTypes.ReadNextVarInt(packetData) switch // 1.20.6+ // TODO: Use values from the registry
{
0 => "minecraft:overworld",
1 => "minecraft:overworld_caves",
2 => "minecraft:the_end",
3 => "minecraft:the_nether",
_ => null
} ?? "minecraft:overworld";
break;
case >= MC_1_19_Version:
dimensionTypeNameRespawn =
@ -1286,12 +1299,9 @@ namespace MinecraftClient.Protocol.Handlers
World.StoreOneDimension(dimensionName, dimensionTypeRespawn!);
World.SetDimension(dimensionName);
break;
case < MC_1_20_6_Version:
case <= MC_1_20_6_Version:
World.SetDimension(dimensionTypeNameRespawn!);
break;
case >= MC_1_20_6_Version:
// TODO: 1.20.6 Set the dimension (use dimensionTypeInt)
break;
}
}
@ -2759,8 +2769,7 @@ namespace MinecraftClient.Protocol.Handlers
var host = dataTypes.ReadNextString(packetData);
var port = dataTypes.ReadNextVarInt(packetData);
// TODO: 1.20.6 Implement Host Chaging in the McClient class
// McClient.Instance?.Transfer(host, port);
McClient.Instance?.Transfer(host, port);
break;
default:
@ -3393,14 +3402,14 @@ namespace MinecraftClient.Protocol.Handlers
SendMessageAcknowledgment(ConsumeAcknowledgment());
}
}
/// <summary>
/// Send a chat command to the server - 1.19 and above
/// Send a chat command to the server, with or without signing based on the online mode and version.
/// </summary>
/// <param name="command">Command</param>
/// <param name="playerKeyPair">PlayerKeyPair</param>
/// <param name="playerKeyPair">PlayerKeyPair (optional)</param>
/// <returns>True if properly sent</returns>
public bool SendChatCommand(string command, PlayerKeyPair? playerKeyPair)
public bool SendChatCommand(string command, PlayerKeyPair? playerKeyPair = null)
{
if (string.IsNullOrEmpty(command))
return true;
@ -3410,85 +3419,78 @@ namespace MinecraftClient.Protocol.Handlers
log.Debug($"chat command = {command}");
if (protocolVersion >= MC_1_20_6_Version && !isOnlineMode)
{
List<byte> fields = new();
fields.AddRange(dataTypes.GetString(command));
SendPacket(PacketTypesOut.ChatCommand, fields);
return true;
}
try
{
List<Tuple<string, string>>? needSigned = null; // List< Argument Name, Argument Value >
if (playerKeyPair != null && isOnlineMode && protocolVersion >= MC_1_19_Version
&& Config.Signature is { LoginWithSecureProfile: true, SignMessageInCommand: true })
List<Tuple<string, string>>? needSigned = null;
if (protocolVersion >= MC_1_19_Version && Config.Signature is { LoginWithSecureProfile: true, SignMessageInCommand: true })
needSigned = DeclareCommands.CollectSignArguments(command);
lock (MessageSigningLock)
{
var acknowledgment1192 =
protocolVersion == MC_1_19_2_Version ? ConsumeAcknowledgment() : null;
var acknowledgment1192 = protocolVersion == MC_1_19_2_Version ? ConsumeAcknowledgment() : null;
var (acknowledgment1193, bitset1193, messageCount1193) =
protocolVersion >= MC_1_19_3_Version
? lastSeenMessagesCollector.Collect_1_19_3()
: new(Array.Empty<LastSeenMessageList.AcknowledgedMessage>(), Array.Empty<byte>(), 0);
var (acknowledgment1193, bitset1193, messageCount1193) = protocolVersion >= MC_1_19_3_Version
? lastSeenMessagesCollector.Collect_1_19_3()
: new(Array.Empty<LastSeenMessageList.AcknowledgedMessage>(), Array.Empty<byte>(), 0);
List<byte> fields = new();
// Command: String
fields.AddRange(dataTypes.GetString(command));
// Timestamp: Instant(Long)
var timeNow = DateTimeOffset.UtcNow;
fields.AddRange(DataTypes.GetLong(timeNow.ToUnixTimeMilliseconds()));
if (needSigned == null || needSigned!.Count == 0)
if (needSigned == null || needSigned.Count == 0)
{
fields.AddRange(DataTypes.GetLong(0)); // Salt: Long
fields.AddRange(DataTypes.GetVarInt(0)); // Signature Length: VarInt
fields.AddRange(DataTypes.GetLong(0));
fields.AddRange(DataTypes.GetVarInt(0));
}
else
{
var uuid = handler.GetUserUuid();
var salt = GenerateSalt();
fields.AddRange(salt); // Salt: Long
fields.AddRange(DataTypes.GetVarInt(needSigned.Count)); // Signature Length: VarInt
fields.AddRange(salt);
fields.AddRange(DataTypes.GetVarInt(needSigned.Count));
foreach (var (argName, message) in needSigned)
{
fields.AddRange(dataTypes.GetString(argName)); // Argument name: String
fields.AddRange(dataTypes.GetString(argName));
var sign = protocolVersion switch
{
MC_1_19_Version => playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow,
ref salt),
MC_1_19_2_Version => playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow,
ref salt, acknowledgment1192!.lastSeen),
_ => playerKeyPair!.PrivateKey.SignMessage(message, uuid, chatUuid, messageIndex++,
timeNow, ref salt, acknowledgment1193)
MC_1_19_Version => playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow, ref salt),
MC_1_19_2_Version => playerKeyPair!.PrivateKey.SignMessage(message, uuid, timeNow, ref salt, acknowledgment1192!.lastSeen),
_ => playerKeyPair!.PrivateKey.SignMessage(message, uuid, chatUuid, messageIndex++, timeNow, ref salt, acknowledgment1193)
};
if (protocolVersion <= MC_1_19_2_Version)
fields.AddRange(DataTypes.GetVarInt(sign.Length)); // Signature length: VarInt
fields.AddRange(DataTypes.GetVarInt(sign.Length));
fields.AddRange(sign); // Signature: Byte Array
fields.AddRange(sign);
}
}
if (protocolVersion <= MC_1_19_2_Version)
fields.AddRange(dataTypes.GetBool(false)); // Signed Preview: Boolean
fields.AddRange(dataTypes.GetBool(false));
switch (protocolVersion)
{
case MC_1_19_2_Version:
// Message Acknowledgment (1.19.2)
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment1192!,
isOnlineMode && Config.Signature.LoginWithSecureProfile));
fields.AddRange(dataTypes.GetAcknowledgment(acknowledgment1192!, isOnlineMode && Config.Signature.LoginWithSecureProfile));
break;
case >= MC_1_19_3_Version:
// message count
fields.AddRange(DataTypes.GetVarInt(messageCount1193));
// Acknowledged: BitSet
fields.AddRange(bitset1193);
break;
}
SendPacket(PacketTypesOut.ChatCommand, fields);
SendPacket(protocolVersion < MC_1_20_6_Version ? PacketTypesOut.ChatCommand : PacketTypesOut.SignedChatCommand, fields);
}
return true;
@ -3506,7 +3508,7 @@ namespace MinecraftClient.Protocol.Handlers
return false;
}
}
/// <summary>
/// Send a chat message to the server
/// </summary>
@ -4687,21 +4689,22 @@ namespace MinecraftClient.Protocol.Handlers
if (hasPayload)
packet.AddRange(dataTypes.GetArray(data!)); // Payload Data Array Size + Data Array
switch(currentState)
switch (currentState)
{
case CurrentState.Login:
case CurrentState.Login:
SendPacket(0x04, packet);
break;
case CurrentState.Configuration:
case CurrentState.Configuration:
SendPacket(ConfigurationPacketTypesOut.CookieResponse, packet);
break;
case CurrentState.Play:
SendPacket(PacketTypesOut.CookieResponse, packet);
break;
}
McClient.Instance?.DeleteCookie(name);
return true;
}
catch (SocketException)

View file

@ -46,6 +46,9 @@ namespace MinecraftClient.Protocol
ILogger GetLogger();
void GetCookie(string key, out byte[]? data);
void SetCookie(string key, byte[] data);
void DeleteCookie(string key);
void Transfer(string newHost, int newPort);
/// <summary>
/// Invoke a task on the main thread, wait for completion and retrieve return value.