mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Add server status display and protocol version upgrade handling
- Introduced a new `ServerStatusInfo` class to encapsulate server status data including MOTD, player counts, and version information. - Implemented `ServerStatusDisplay` to format and display server status information in both classic and TUI modes. - Added protocol version upgrade logic in `ProtocolHandler` to determine the highest supported protocol version for multi-version servers. - Updated translations to support new server status labels and messages. - Created `ServerStatusPanelBuilder` for TUI to visually represent server status with player information and connection details.
This commit is contained in:
parent
2f03f66f5c
commit
871305bd72
10 changed files with 954 additions and 269 deletions
|
|
@ -449,7 +449,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
McClient.Instance?.GetCookie(cookieName, out cookieData);
|
||||
SendCookieResponse(cookieName, cookieData);
|
||||
break;
|
||||
|
||||
|
||||
// Ignore other packets at this stage
|
||||
default:
|
||||
return true;
|
||||
|
|
@ -467,7 +467,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
McClient.Instance?.GetCookie(cookieName, out cookieData);
|
||||
SendCookieResponse(cookieName, cookieData);
|
||||
break;
|
||||
|
||||
|
||||
case ConfigurationPacketTypesIn.Disconnect:
|
||||
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick,
|
||||
dataTypes.ReadNextChat(packetData));
|
||||
|
|
@ -509,7 +509,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
var dimensionIdMap = isDimension ? new Dictionary<int, string>() : null;
|
||||
var attributeIdMap = isAttribute ? new Dictionary<int, string>() : null;
|
||||
var enchantmentIdMap = isEnchantment ? new Dictionary<int, string>() : null;
|
||||
|
||||
|
||||
for (var i = 0; i < entryCount; i++)
|
||||
{
|
||||
var entryId = dataTypes.ReadNextString(packetData);
|
||||
|
|
@ -537,7 +537,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
else if (isEnchantment)
|
||||
enchantmentIdMap!.Add(i, entryId);
|
||||
}
|
||||
|
||||
|
||||
if (isChat)
|
||||
ChatParser.ReadChatType(availableChats!);
|
||||
else if (isDimension)
|
||||
|
|
@ -553,7 +553,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
}
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case ConfigurationPacketTypesIn.RemoveResourcePack:
|
||||
if (dataTypes.ReadNextBool(packetData)) // Has UUID
|
||||
dataTypes.ReadNextUUID(packetData); // UUID
|
||||
|
|
@ -562,24 +562,24 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
case ConfigurationPacketTypesIn.ResourcePack:
|
||||
HandleResourcePackPacket(packetData);
|
||||
break;
|
||||
|
||||
|
||||
case ConfigurationPacketTypesIn.StoreCookie:
|
||||
var name = dataTypes.ReadNextString(packetData);
|
||||
var data = dataTypes.ReadNextByteArray(packetData);
|
||||
McClient.Instance?.SetCookie(name, data);
|
||||
break;
|
||||
|
||||
|
||||
case ConfigurationPacketTypesIn.Transfer:
|
||||
var host = dataTypes.ReadNextString(packetData);
|
||||
var port = dataTypes.ReadNextVarInt(packetData);
|
||||
|
||||
|
||||
McClient.Instance?.Transfer(host, port);
|
||||
break;
|
||||
|
||||
|
||||
case ConfigurationPacketTypesIn.KnownDataPacks:
|
||||
var knownPacksCount = dataTypes.ReadNextVarInt(packetData);
|
||||
List<(string, string, string)> knownDataPacks = new();
|
||||
|
||||
|
||||
for (var i = 0; i < knownPacksCount; i++)
|
||||
{
|
||||
var nameSpace = dataTypes.ReadNextString(packetData);
|
||||
|
|
@ -645,7 +645,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
currentState == CurrentState.Login,
|
||||
innerException.GetType()),
|
||||
innerException);
|
||||
|
||||
|
||||
SentrySdk.AddBreadcrumb(new Breadcrumb("S -> C Packet", "network", new Dictionary<string, string>()
|
||||
{
|
||||
{ "Packet ID", packetId.ToString() },
|
||||
|
|
@ -786,26 +786,26 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_16_Version:
|
||||
{
|
||||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_19_Version:
|
||||
dimensionTypeName =
|
||||
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
|
||||
break;
|
||||
case >= MC_1_16_2_Version:
|
||||
dimensionType =
|
||||
dataTypes.ReadNextNbt(
|
||||
packetData); // Dimension Type: NBT Tag Compound
|
||||
break;
|
||||
default:
|
||||
dataTypes.ReadNextString(packetData);
|
||||
break;
|
||||
}
|
||||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_19_Version:
|
||||
dimensionTypeName =
|
||||
dataTypes.ReadNextString(packetData); // Dimension Type: Identifier
|
||||
break;
|
||||
case >= MC_1_16_2_Version:
|
||||
dimensionType =
|
||||
dataTypes.ReadNextNbt(
|
||||
packetData); // Dimension Type: NBT Tag Compound
|
||||
break;
|
||||
default:
|
||||
dataTypes.ReadNextString(packetData);
|
||||
break;
|
||||
}
|
||||
|
||||
currentDimension = 0;
|
||||
break;
|
||||
}
|
||||
currentDimension = 0;
|
||||
break;
|
||||
}
|
||||
case >= MC_1_9_1_Version:
|
||||
currentDimension = dataTypes.ReadNextInt(packetData);
|
||||
break;
|
||||
|
|
@ -820,27 +820,27 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
|
||||
break;
|
||||
case >= MC_1_16_Version:
|
||||
{
|
||||
var dimensionName =
|
||||
dataTypes.ReadNextString(
|
||||
packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
|
||||
if (handler.GetTerrainEnabled())
|
||||
{
|
||||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_16_2_Version and <= MC_1_18_2_Version:
|
||||
World.StoreOneDimension(dimensionName, dimensionType!);
|
||||
World.SetDimension(dimensionName);
|
||||
break;
|
||||
default:
|
||||
World.SetDimension(dimensionTypeName!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var dimensionName =
|
||||
dataTypes.ReadNextString(
|
||||
packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
|
||||
break;
|
||||
}
|
||||
if (handler.GetTerrainEnabled())
|
||||
{
|
||||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_16_2_Version and <= MC_1_18_2_Version:
|
||||
World.StoreOneDimension(dimensionName, dimensionType!);
|
||||
World.SetDimension(dimensionName);
|
||||
break;
|
||||
default:
|
||||
World.SetDimension(dimensionTypeName!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1354,7 +1354,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
case PacketTypesIn.Respawn:
|
||||
string? dimensionTypeNameRespawn = null;
|
||||
Dictionary<string, object>? dimensionTypeRespawn = null;
|
||||
|
||||
|
||||
if (protocolVersion >= MC_1_16_Version)
|
||||
{
|
||||
switch (protocolVersion)
|
||||
|
|
@ -1386,27 +1386,27 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_16_Version:
|
||||
{
|
||||
var dimensionName =
|
||||
dataTypes.ReadNextString(
|
||||
packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
|
||||
if (handler.GetTerrainEnabled())
|
||||
{
|
||||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_16_2_Version and <= MC_1_18_2_Version:
|
||||
World.StoreOneDimension(dimensionName, dimensionTypeRespawn!);
|
||||
World.SetDimension(dimensionName);
|
||||
break;
|
||||
default:
|
||||
World.SetDimension(dimensionTypeNameRespawn!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var dimensionName =
|
||||
dataTypes.ReadNextString(
|
||||
packetData); // Dimension Name (World Name) - 1.16 and above
|
||||
|
||||
break;
|
||||
}
|
||||
if (handler.GetTerrainEnabled())
|
||||
{
|
||||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_16_2_Version and <= MC_1_18_2_Version:
|
||||
World.StoreOneDimension(dimensionName, dimensionTypeRespawn!);
|
||||
World.SetDimension(dimensionName);
|
||||
break;
|
||||
default:
|
||||
World.SetDimension(dimensionTypeNameRespawn!);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case < MC_1_14_Version:
|
||||
dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below
|
||||
break;
|
||||
|
|
@ -1453,77 +1453,77 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
handler.OnRespawn();
|
||||
break;
|
||||
case PacketTypesIn.PlayerPositionAndLook:
|
||||
{
|
||||
int teleportId;
|
||||
Location location;
|
||||
float yaw, pitch;
|
||||
int locMask;
|
||||
{
|
||||
int teleportId;
|
||||
Location location;
|
||||
float yaw, pitch;
|
||||
int locMask;
|
||||
|
||||
if (protocolVersion >= MC_1_21_2_Version)
|
||||
{
|
||||
teleportId = dataTypes.ReadNextVarInt(packetData);
|
||||
location = new Location(
|
||||
dataTypes.ReadNextDouble(packetData), // X
|
||||
dataTypes.ReadNextDouble(packetData), // Y
|
||||
dataTypes.ReadNextDouble(packetData) // Z
|
||||
);
|
||||
dataTypes.ReadNextDouble(packetData); // Delta X
|
||||
dataTypes.ReadNextDouble(packetData); // Delta Y
|
||||
dataTypes.ReadNextDouble(packetData); // Delta Z
|
||||
yaw = dataTypes.ReadNextFloat(packetData);
|
||||
pitch = dataTypes.ReadNextFloat(packetData);
|
||||
locMask = dataTypes.ReadNextInt(packetData); // Int flags (was Byte before 1.21.2)
|
||||
}
|
||||
else
|
||||
{
|
||||
location = new Location(
|
||||
dataTypes.ReadNextDouble(packetData), // X
|
||||
dataTypes.ReadNextDouble(packetData), // Y
|
||||
dataTypes.ReadNextDouble(packetData) // Z
|
||||
);
|
||||
yaw = dataTypes.ReadNextFloat(packetData);
|
||||
pitch = dataTypes.ReadNextFloat(packetData);
|
||||
locMask = dataTypes.ReadNextByte(packetData);
|
||||
teleportId = protocolVersion >= MC_1_9_Version
|
||||
? dataTypes.ReadNextVarInt(packetData) : -1;
|
||||
}
|
||||
|
||||
if (handler.GetTerrainEnabled() || handler.GetEntityHandlingEnabled())
|
||||
{
|
||||
if (protocolVersion >= MC_1_8_Version)
|
||||
if (protocolVersion >= MC_1_21_2_Version)
|
||||
{
|
||||
var currentLocation = handler.GetCurrentLocation();
|
||||
location.X = (locMask & 1 << 0) != 0 ? currentLocation.X + location.X : location.X;
|
||||
location.Y = (locMask & 1 << 1) != 0 ? currentLocation.Y + location.Y : location.Y;
|
||||
location.Z = (locMask & 1 << 2) != 0 ? currentLocation.Z + location.Z : location.Z;
|
||||
teleportId = dataTypes.ReadNextVarInt(packetData);
|
||||
location = new Location(
|
||||
dataTypes.ReadNextDouble(packetData), // X
|
||||
dataTypes.ReadNextDouble(packetData), // Y
|
||||
dataTypes.ReadNextDouble(packetData) // Z
|
||||
);
|
||||
dataTypes.ReadNextDouble(packetData); // Delta X
|
||||
dataTypes.ReadNextDouble(packetData); // Delta Y
|
||||
dataTypes.ReadNextDouble(packetData); // Delta Z
|
||||
yaw = dataTypes.ReadNextFloat(packetData);
|
||||
pitch = dataTypes.ReadNextFloat(packetData);
|
||||
locMask = dataTypes.ReadNextInt(packetData); // Int flags (was Byte before 1.21.2)
|
||||
}
|
||||
}
|
||||
|
||||
if (teleportId >= 0)
|
||||
{
|
||||
LastYaw = yaw;
|
||||
LastPitch = pitch;
|
||||
handler.UpdateLocation(location, yaw, pitch);
|
||||
SendPacket(PacketTypesOut.TeleportConfirm, DataTypes.GetVarInt(teleportId));
|
||||
|
||||
if (Config.Main.Advanced.TemporaryFixBadpacket)
|
||||
else
|
||||
{
|
||||
SendLocationUpdate(location, true, false, yaw, pitch, true);
|
||||
location = new Location(
|
||||
dataTypes.ReadNextDouble(packetData), // X
|
||||
dataTypes.ReadNextDouble(packetData), // Y
|
||||
dataTypes.ReadNextDouble(packetData) // Z
|
||||
);
|
||||
yaw = dataTypes.ReadNextFloat(packetData);
|
||||
pitch = dataTypes.ReadNextFloat(packetData);
|
||||
locMask = dataTypes.ReadNextByte(packetData);
|
||||
teleportId = protocolVersion >= MC_1_9_Version
|
||||
? dataTypes.ReadNextVarInt(packetData) : -1;
|
||||
}
|
||||
|
||||
if (teleportId == 1)
|
||||
if (handler.GetTerrainEnabled() || handler.GetEntityHandlingEnabled())
|
||||
{
|
||||
if (protocolVersion >= MC_1_8_Version)
|
||||
{
|
||||
var currentLocation = handler.GetCurrentLocation();
|
||||
location.X = (locMask & 1 << 0) != 0 ? currentLocation.X + location.X : location.X;
|
||||
location.Y = (locMask & 1 << 1) != 0 ? currentLocation.Y + location.Y : location.Y;
|
||||
location.Z = (locMask & 1 << 2) != 0 ? currentLocation.Z + location.Z : location.Z;
|
||||
}
|
||||
}
|
||||
|
||||
if (teleportId >= 0)
|
||||
{
|
||||
LastYaw = yaw;
|
||||
LastPitch = pitch;
|
||||
handler.UpdateLocation(location, yaw, pitch);
|
||||
SendPacket(PacketTypesOut.TeleportConfirm, DataTypes.GetVarInt(teleportId));
|
||||
|
||||
if (Config.Main.Advanced.TemporaryFixBadpacket)
|
||||
{
|
||||
SendLocationUpdate(location, true, false, yaw, pitch, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handler.UpdateLocation(location, yaw, pitch);
|
||||
LastYaw = yaw;
|
||||
LastPitch = pitch;
|
||||
}
|
||||
|
||||
if (protocolVersion is >= MC_1_17_Version and < MC_1_19_4_Version)
|
||||
dataTypes.ReadNextBool(packetData); // Dismount Vehicle - 1.17 to 1.19.3
|
||||
}
|
||||
if (teleportId == 1)
|
||||
SendLocationUpdate(location, true, false, yaw, pitch, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handler.UpdateLocation(location, yaw, pitch);
|
||||
LastYaw = yaw;
|
||||
LastPitch = pitch;
|
||||
}
|
||||
|
||||
if (protocolVersion is >= MC_1_17_Version and < MC_1_19_4_Version)
|
||||
dataTypes.ReadNextBool(packetData); // Dismount Vehicle - 1.17 to 1.19.3
|
||||
}
|
||||
break;
|
||||
case PacketTypesIn.ChunkData:
|
||||
if (handler.GetTerrainEnabled())
|
||||
|
|
@ -1679,26 +1679,26 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
// 1.8 - 1.13
|
||||
case < MC_1_13_2_Version:
|
||||
{
|
||||
var directionAndType = dataTypes.ReadNextByte(packetData);
|
||||
byte direction, type;
|
||||
|
||||
// 1.12.2+
|
||||
if (protocolVersion >= MC_1_12_2_Version)
|
||||
{
|
||||
direction = (byte)(directionAndType & 0xF);
|
||||
type = (byte)(directionAndType >> 4 & 0xF);
|
||||
}
|
||||
else // 1.8 - 1.12
|
||||
{
|
||||
direction = (byte)(directionAndType >> 4 & 0xF);
|
||||
type = (byte)(directionAndType & 0xF);
|
||||
}
|
||||
var directionAndType = dataTypes.ReadNextByte(packetData);
|
||||
byte direction, type;
|
||||
|
||||
mapIcon.Type = (MapIconType)type;
|
||||
mapIcon.Direction = direction;
|
||||
break;
|
||||
}
|
||||
// 1.12.2+
|
||||
if (protocolVersion >= MC_1_12_2_Version)
|
||||
{
|
||||
direction = (byte)(directionAndType & 0xF);
|
||||
type = (byte)(directionAndType >> 4 & 0xF);
|
||||
}
|
||||
else // 1.8 - 1.12
|
||||
{
|
||||
direction = (byte)(directionAndType >> 4 & 0xF);
|
||||
type = (byte)(directionAndType & 0xF);
|
||||
}
|
||||
|
||||
mapIcon.Type = (MapIconType)type;
|
||||
mapIcon.Direction = direction;
|
||||
break;
|
||||
}
|
||||
// 1.13.2+
|
||||
case >= MC_1_13_2_Version:
|
||||
mapIcon.Type = (MapIconType)dataTypes.ReadNextVarInt(packetData);
|
||||
|
|
@ -2290,7 +2290,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
handler.OnPluginChannelMessage(channel, packetData.ToArray());
|
||||
return pForge.HandlePluginMessage(channel, packetData, ref currentDimension);
|
||||
case PacketTypesIn.Disconnect:
|
||||
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick,
|
||||
handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick,
|
||||
dataTypes.ReadNextChat(packetData));
|
||||
return false;
|
||||
case PacketTypesIn.SetCompression:
|
||||
|
|
@ -2427,17 +2427,17 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (handler.GetEntityHandlingEnabled())
|
||||
{
|
||||
var entity = dataTypes.ReadNextEntity(packetData, entityPalette, false);
|
||||
|
||||
|
||||
if (protocolVersion >= MC_1_20_2_Version)
|
||||
{
|
||||
if (entity.Type == EntityType.Player)
|
||||
handler.OnSpawnPlayer(entity.ID, entity.UUID, entity.Location, (byte)entity.Yaw, (byte)entity.Pitch);
|
||||
else
|
||||
handler.OnSpawnEntity(entity);
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
handler.OnSpawnEntity(entity);
|
||||
}
|
||||
|
||||
|
|
@ -2648,7 +2648,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
var numberOfProperties = protocolVersion >= MC_1_17_Version
|
||||
? dataTypes.ReadNextVarInt(packetData)
|
||||
: dataTypes.ReadNextInt(packetData);
|
||||
|
||||
|
||||
Dictionary<string, double> keys = new();
|
||||
for (var i = 0; i < numberOfProperties; i++)
|
||||
{
|
||||
|
|
@ -3008,17 +3008,17 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
McClient.Instance?.GetCookie(cookieName, out cookieData);
|
||||
SendCookieResponse(cookieName, cookieData);
|
||||
break;
|
||||
|
||||
|
||||
case PacketTypesIn.StoreCookie:
|
||||
var cookieName2 = dataTypes.ReadNextString(packetData);
|
||||
var cookieData2 = dataTypes.ReadNextByteArray(packetData);
|
||||
McClient.Instance?.SetCookie(cookieName2, cookieData2);
|
||||
break;
|
||||
|
||||
|
||||
case PacketTypesIn.Transfer:
|
||||
var host = dataTypes.ReadNextString(packetData);
|
||||
var port = dataTypes.ReadNextVarInt(packetData);
|
||||
|
||||
|
||||
McClient.Instance?.Transfer(host, port);
|
||||
break;
|
||||
|
||||
|
|
@ -3286,17 +3286,17 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
switch (protocolVersion)
|
||||
{
|
||||
case >= MC_1_19_2_Version and < MC_1_20_2_Version:
|
||||
{
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case >= MC_1_20_2_Version:
|
||||
uuid = handler.GetUserUuid();
|
||||
|
||||
|
|
@ -3324,42 +3324,42 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
// Encryption request
|
||||
case 0x01:
|
||||
{
|
||||
isOnlineMode = true;
|
||||
var serverId = dataTypes.ReadNextString(packetData);
|
||||
var serverPublicKey = dataTypes.ReadNextByteArray(packetData);
|
||||
var token = dataTypes.ReadNextByteArray(packetData);
|
||||
{
|
||||
isOnlineMode = true;
|
||||
var serverId = dataTypes.ReadNextString(packetData);
|
||||
var serverPublicKey = dataTypes.ReadNextByteArray(packetData);
|
||||
var token = dataTypes.ReadNextByteArray(packetData);
|
||||
|
||||
var shouldAuthetnicate = false;
|
||||
var shouldAuthetnicate = false;
|
||||
|
||||
if (protocolVersion >= MC_1_20_6_Version)
|
||||
shouldAuthetnicate = dataTypes.ReadNextBool(packetData);
|
||||
|
||||
return StartEncryption(handler.GetUserUuidStr(), handler.GetSessionID(),
|
||||
Config.Main.General.AccountType, token, serverId,
|
||||
serverPublicKey, playerKeyPair, session, shouldAuthetnicate);
|
||||
}
|
||||
if (protocolVersion >= MC_1_20_6_Version)
|
||||
shouldAuthetnicate = dataTypes.ReadNextBool(packetData);
|
||||
|
||||
return StartEncryption(handler.GetUserUuidStr(), handler.GetSessionID(),
|
||||
Config.Main.General.AccountType, token, serverId,
|
||||
serverPublicKey, playerKeyPair, session, shouldAuthetnicate);
|
||||
}
|
||||
|
||||
// Login successful
|
||||
case 0x02:
|
||||
{
|
||||
log.Info($"§8{Translations.mcc_server_offline}");
|
||||
currentState = protocolVersion < MC_1_20_2_Version
|
||||
? CurrentState.Play
|
||||
: CurrentState.Configuration;
|
||||
|
||||
if (protocolVersion >= MC_1_20_2_Version)
|
||||
SendPacket(0x03, new List<byte>());
|
||||
|
||||
if (!pForge.CompleteForgeHandshake())
|
||||
{
|
||||
log.Error($"§8{Translations.error_forge}");
|
||||
return false;
|
||||
}
|
||||
log.Info($"§8{Translations.mcc_server_offline}");
|
||||
currentState = protocolVersion < MC_1_20_2_Version
|
||||
? CurrentState.Play
|
||||
: CurrentState.Configuration;
|
||||
|
||||
StartUpdating();
|
||||
return true; //No need to check session or start encryption
|
||||
}
|
||||
if (protocolVersion >= MC_1_20_2_Version)
|
||||
SendPacket(0x03, new List<byte>());
|
||||
|
||||
if (!pForge.CompleteForgeHandshake())
|
||||
{
|
||||
log.Error($"§8{Translations.error_forge}");
|
||||
return false;
|
||||
}
|
||||
|
||||
StartUpdating();
|
||||
return true; //No need to check session or start encryption
|
||||
}
|
||||
default:
|
||||
HandlePacket(packetId, packetData);
|
||||
break;
|
||||
|
|
@ -3392,7 +3392,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (session.SessionPreCheckTask.Result) // PreCheck Success
|
||||
needCheckSession = false;
|
||||
}
|
||||
|
||||
|
||||
// 1.20.6++
|
||||
if (shouldAuthetnicate)
|
||||
needCheckSession = true;
|
||||
|
|
@ -3465,51 +3465,51 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected,
|
||||
ChatParser.ParseText(dataTypes.ReadNextString(packetData)));
|
||||
return false;
|
||||
|
||||
|
||||
//Login successful
|
||||
case 0x02:
|
||||
{
|
||||
var uuidReceived = protocolVersion >= MC_1_16_Version
|
||||
? dataTypes.ReadNextUUID(packetData)
|
||||
: Guid.Parse(dataTypes.ReadNextString(packetData));
|
||||
var userName = dataTypes.ReadNextString(packetData);
|
||||
Tuple<string, string, string>[]? playerProperty = null;
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
{
|
||||
var count = dataTypes.ReadNextVarInt(packetData); // Number Of Properties
|
||||
playerProperty = new Tuple<string, string, string>[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
var uuidReceived = protocolVersion >= MC_1_16_Version
|
||||
? dataTypes.ReadNextUUID(packetData)
|
||||
: Guid.Parse(dataTypes.ReadNextString(packetData));
|
||||
var userName = dataTypes.ReadNextString(packetData);
|
||||
Tuple<string, string, string>[]? playerProperty = null;
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
{
|
||||
var name = dataTypes.ReadNextString(packetData);
|
||||
var value = dataTypes.ReadNextString(packetData);
|
||||
var isSigned = dataTypes.ReadNextBool(packetData);
|
||||
var signature = isSigned ? dataTypes.ReadNextString(packetData) : string.Empty;
|
||||
playerProperty[i] = new Tuple<string, string, string>(name, value, signature);
|
||||
var count = dataTypes.ReadNextVarInt(packetData); // Number Of Properties
|
||||
playerProperty = new Tuple<string, string, string>[count];
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var name = dataTypes.ReadNextString(packetData);
|
||||
var value = dataTypes.ReadNextString(packetData);
|
||||
var isSigned = dataTypes.ReadNextBool(packetData);
|
||||
var signature = isSigned ? dataTypes.ReadNextString(packetData) : string.Empty;
|
||||
playerProperty[i] = new Tuple<string, string, string>(name, value, signature);
|
||||
}
|
||||
}
|
||||
|
||||
// Strict Error Handling (removed in 1.21.2)
|
||||
if (protocolVersion >= MC_1_20_6_Version && protocolVersion < MC_1_21_2_Version)
|
||||
dataTypes.ReadNextBool(packetData);
|
||||
|
||||
currentState = protocolVersion < MC_1_20_2_Version
|
||||
? CurrentState.Play
|
||||
: CurrentState.Configuration;
|
||||
|
||||
if (protocolVersion >= MC_1_20_2_Version)
|
||||
SendPacket(0x03, new List<byte>());
|
||||
|
||||
handler.OnLoginSuccess(uuidReceived, userName, playerProperty);
|
||||
|
||||
if (!pForge.CompleteForgeHandshake())
|
||||
{
|
||||
log.Error($"§8{Translations.error_forge_encrypt}");
|
||||
return false;
|
||||
}
|
||||
|
||||
StartUpdating();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Strict Error Handling (removed in 1.21.2)
|
||||
if (protocolVersion >= MC_1_20_6_Version && protocolVersion < MC_1_21_2_Version)
|
||||
dataTypes.ReadNextBool(packetData);
|
||||
|
||||
currentState = protocolVersion < MC_1_20_2_Version
|
||||
? CurrentState.Play
|
||||
: CurrentState.Configuration;
|
||||
|
||||
if (protocolVersion >= MC_1_20_2_Version)
|
||||
SendPacket(0x03, new List<byte>());
|
||||
|
||||
handler.OnLoginSuccess(uuidReceived, userName, playerProperty);
|
||||
|
||||
if (!pForge.CompleteForgeHandshake())
|
||||
{
|
||||
log.Error($"§8{Translations.error_forge_encrypt}");
|
||||
return false;
|
||||
}
|
||||
|
||||
StartUpdating();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
HandlePacket(packetId, packetData);
|
||||
break;
|
||||
|
|
@ -3548,15 +3548,15 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
dataTypes.GetString(BehindCursor.Replace(' ', (char)0x00)));
|
||||
break;
|
||||
case >= MC_1_8_Version:
|
||||
{
|
||||
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, dataTypes.GetString(BehindCursor));
|
||||
{
|
||||
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, dataTypes.GetString(BehindCursor));
|
||||
|
||||
if (protocolVersion >= MC_1_9_Version)
|
||||
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, assumeCommand);
|
||||
if (protocolVersion >= MC_1_9_Version)
|
||||
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, assumeCommand);
|
||||
|
||||
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, hasPosition);
|
||||
break;
|
||||
}
|
||||
tabCompletePacket = dataTypes.ConcatBytes(tabCompletePacket, hasPosition);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
tabCompletePacket = dataTypes.ConcatBytes(dataTypes.GetString(BehindCursor));
|
||||
break;
|
||||
|
|
@ -3620,7 +3620,8 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (dataTypes.ReadNextVarInt(packetData) != 0x00)
|
||||
return false;
|
||||
|
||||
var result = dataTypes.ReadNextString(packetData); // Get the Json data
|
||||
// Get the Json data
|
||||
var result = dataTypes.ReadNextString(packetData);
|
||||
|
||||
if (Config.Logging.DebugMessages)
|
||||
{
|
||||
|
|
@ -3651,7 +3652,44 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
// Check for forge on the server.
|
||||
Protocol18Forge.ServerInfoCheckForge(jsonObj, ref forgeInfo);
|
||||
|
||||
// Complete the normal status exchange so the probe connection closes cleanly server-side.
|
||||
int onlinePlayers = 0, maxPlayers = 0;
|
||||
List<ServerStatusInfo.SamplePlayer> samplePlayers = [];
|
||||
|
||||
if (jsonObj["players"] is System.Text.Json.Nodes.JsonObject playersObj)
|
||||
{
|
||||
if (playersObj["online"] is { } onlineNode)
|
||||
onlinePlayers = int.Parse(onlineNode.GetStringValue(), NumberStyles.Any, CultureInfo.CurrentCulture);
|
||||
if (playersObj["max"] is { } maxNode)
|
||||
maxPlayers = int.Parse(maxNode.GetStringValue(), NumberStyles.Any, CultureInfo.CurrentCulture);
|
||||
if (playersObj["sample"] is System.Text.Json.Nodes.JsonArray sampleArray)
|
||||
{
|
||||
foreach (var entry in sampleArray)
|
||||
{
|
||||
if (entry is not System.Text.Json.Nodes.JsonObject playerObj) continue;
|
||||
samplePlayers.Add(new ServerStatusInfo.SamplePlayer
|
||||
{
|
||||
Name = playerObj["name"]?.GetStringValue() ?? "",
|
||||
Id = playerObj["id"]?.GetStringValue() ?? ""
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string motdRaw = "";
|
||||
if (jsonObj["description"] is { } descNode)
|
||||
motdRaw = descNode.ToJsonString();
|
||||
|
||||
string? faviconBase64 = null;
|
||||
if (jsonObj["favicon"] is { } faviconNode)
|
||||
{
|
||||
var faviconStr = faviconNode.GetStringValue();
|
||||
const string prefix = "data:image/png;base64,";
|
||||
faviconBase64 = faviconStr.StartsWith(prefix, StringComparison.Ordinal)
|
||||
? faviconStr[prefix.Length..]
|
||||
: faviconStr;
|
||||
}
|
||||
|
||||
long pingMs = -1;
|
||||
try
|
||||
{
|
||||
long pingPayload = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
|
|
@ -3663,7 +3701,10 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
{
|
||||
packetData = new Queue<byte>(socketWrapper.ReadDataRAW(packetLength));
|
||||
if (dataTypes.ReadNextVarInt(packetData) == 0x01)
|
||||
dataTypes.ReadNextLong(packetData);
|
||||
{
|
||||
long pongPayload = dataTypes.ReadNextLong(packetData);
|
||||
pingMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - pingPayload;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
|
@ -3671,9 +3712,28 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
// Some servers may close the probe connection immediately after the status response.
|
||||
}
|
||||
|
||||
var statusInfo = new ServerStatusInfo
|
||||
{
|
||||
Host = host,
|
||||
Port = port,
|
||||
VersionName = version,
|
||||
ProtocolVersion = protocolVersion,
|
||||
OnlinePlayers = onlinePlayers,
|
||||
MaxPlayers = maxPlayers,
|
||||
SamplePlayers = samplePlayers,
|
||||
MotdRaw = motdRaw,
|
||||
FaviconBase64 = faviconBase64,
|
||||
PingMs = pingMs
|
||||
};
|
||||
|
||||
ProtocolHandler.TryUpgradeProtocolVersion(version, ref protocolVersion);
|
||||
statusInfo.ResolvedProtocol = protocolVersion;
|
||||
|
||||
ConsoleIO.WriteLineFormatted("§8" + string.Format(Translations.mcc_server_protocol, version,
|
||||
protocolVersion + (forgeInfo is not null ? Translations.mcc_with_forge : "")));
|
||||
|
||||
ServerStatusDisplay.Show(statusInfo);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
|
|
@ -3792,7 +3852,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
SendMessageAcknowledgment(ConsumeAcknowledgment());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat command to the server, with or without signing based on the online mode and version.
|
||||
/// </summary>
|
||||
|
|
@ -3917,7 +3977,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat message to the server
|
||||
/// </summary>
|
||||
|
|
@ -4541,7 +4601,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
packet.AddRange(dataTypes.GetFloat(LastYaw));
|
||||
packet.AddRange(dataTypes.GetFloat(LastPitch));
|
||||
}
|
||||
|
||||
|
||||
SendPacket(PacketTypesOut.UseItem, packet);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -4604,12 +4664,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
if (playerInventory?.Items is null)
|
||||
return false;
|
||||
|
||||
int[] slotWindowIds = [36, 37, 38, 39, 40, 41, 42, 43, 44];
|
||||
int[] slotWindowIds = [36, 37, 38, 39, 40, 41, 42, 43, 44];
|
||||
var currentSlot = ((McClient)handler).GetCurrentSlot();
|
||||
|
||||
|
||||
playerInventory.Items.TryGetValue(slotWindowIds[currentSlot], out var item);
|
||||
packet.AddRange(dataTypes.GetItemSlot(item, itemPalette));
|
||||
|
||||
|
||||
packet.Add(0); // cursorX
|
||||
packet.Add(0); // cursorY
|
||||
packet.Add(0); // cursorZ
|
||||
|
|
@ -4626,12 +4686,12 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
packet.AddRange(DataTypes.GetVarInt(dataTypes.GetBlockFace(face)));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
packet.AddRange(dataTypes.GetFloat(cursorX)); // cursorX
|
||||
packet.AddRange(dataTypes.GetFloat(cursorY)); // cursorY
|
||||
packet.AddRange(dataTypes.GetFloat(cursorZ)); // cursorZ
|
||||
|
||||
if(protocolVersion >= MC_1_14_Version)
|
||||
|
||||
if (protocolVersion >= MC_1_14_Version)
|
||||
packet.Add(0); // insideBlock = false
|
||||
|
||||
if (protocolVersion >= MC_1_21_2_Version)
|
||||
|
|
@ -4639,7 +4699,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
|
||||
if (protocolVersion >= MC_1_19_Version)
|
||||
packet.AddRange(DataTypes.GetVarInt(sequenceId));
|
||||
|
||||
|
||||
SendPacket(PacketTypesOut.PlayerBlockPlacement, packet);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -5235,7 +5295,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool SendCookieResponse(string name, byte[]? data)
|
||||
{
|
||||
try
|
||||
|
|
@ -5244,7 +5304,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
var hasPayload = data is not null;
|
||||
packet.AddRange(dataTypes.GetString(name)); // Identifier
|
||||
packet.AddRange(dataTypes.GetBool(hasPayload)); // Has payload
|
||||
|
||||
|
||||
if (hasPayload)
|
||||
packet.AddRange(dataTypes.GetArray(data!)); // Payload Data Array Size + Data Array
|
||||
|
||||
|
|
@ -5262,7 +5322,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
SendPacket(PacketTypesOut.CookieResponse, packet);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
McClient.Instance?.DeleteCookie(name);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -5293,17 +5353,17 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
packet.AddRange(dataTypes.GetString(dataPack.Item3));
|
||||
}
|
||||
|
||||
switch(currentState)
|
||||
switch (currentState)
|
||||
{
|
||||
case CurrentState.Configuration:
|
||||
case CurrentState.Configuration:
|
||||
SendPacket(ConfigurationPacketTypesOut.KnownDataPacks, packet);
|
||||
break;
|
||||
|
||||
|
||||
case CurrentState.Play:
|
||||
SendPacket(PacketTypesOut.KnownDataPacks, packet);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (SocketException)
|
||||
|
|
@ -5319,7 +5379,7 @@ namespace MinecraftClient.Protocol.Handlers
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private byte[] GenerateSalt()
|
||||
{
|
||||
var salt = new byte[8];
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using DnsClient;
|
||||
using MinecraftClient.Protocol.Handlers;
|
||||
using MinecraftClient.Protocol.Handlers.Forge;
|
||||
|
|
@ -388,6 +389,61 @@ namespace MinecraftClient.Protocol
|
|||
}
|
||||
}
|
||||
|
||||
private static readonly Regex VersionTokenRegex = new(@"\d+\.\d+(?:\.\d+)?", RegexOptions.Compiled);
|
||||
|
||||
private static readonly int[] SupportedProtocols18 =
|
||||
[
|
||||
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, 761, 762, 763, 764, 765, 766, 767, 768, 769, 770, 771,
|
||||
772, 773, 774, 775
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// For multi-version servers (e.g. "Requires MC 1.8 / 1.21"), try to find the
|
||||
/// highest protocol version that both the server and MCC support.
|
||||
/// Returns true if the protocol was upgraded, with the new value in
|
||||
/// <paramref name="protocolVersion"/>.
|
||||
/// </summary>
|
||||
public static bool TryUpgradeProtocolVersion(string versionName, ref int protocolVersion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(versionName))
|
||||
return false;
|
||||
|
||||
var matches = VersionTokenRegex.Matches(versionName);
|
||||
if (matches.Count < 2)
|
||||
return false;
|
||||
|
||||
int bestProtocol = protocolVersion;
|
||||
string bestVersion = "";
|
||||
|
||||
foreach (Match m in matches)
|
||||
{
|
||||
int proto = MCVer2ProtocolVersion(m.Value);
|
||||
if (proto <= 0)
|
||||
continue;
|
||||
if (Array.IndexOf(SupportedProtocols18, proto) < 0)
|
||||
continue;
|
||||
if (proto > bestProtocol)
|
||||
{
|
||||
bestProtocol = proto;
|
||||
bestVersion = m.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestProtocol > protocolVersion && bestVersion.Length > 0)
|
||||
{
|
||||
ConsoleIO.WriteLineFormatted("§8" + string.Format(
|
||||
Translations.mcc_server_info_version_upgrade,
|
||||
ProtocolVersion2MCVer(protocolVersion), protocolVersion,
|
||||
"§a" + bestVersion + "§8", bestProtocol));
|
||||
protocolVersion = bestProtocol;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a network protocol version number to human-readable Minecraft version number
|
||||
/// </summary>
|
||||
|
|
|
|||
118
MinecraftClient/Protocol/ServerStatusDisplay.cs
Normal file
118
MinecraftClient/Protocol/ServerStatusDisplay.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using MinecraftClient.Protocol.Message;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
{
|
||||
internal static class ServerStatusDisplay
|
||||
{
|
||||
private const int MaxSamplePlayers = 10;
|
||||
|
||||
internal static void Show(ServerStatusInfo info)
|
||||
{
|
||||
if (ConsoleIO.Backend is Tui.TuiConsoleBackend tuiBackend)
|
||||
ShowTui(info, tuiBackend);
|
||||
else
|
||||
ShowClassic(info);
|
||||
}
|
||||
|
||||
private static void ShowClassic(ServerStatusInfo info)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine();
|
||||
sb.Append("§8§m");
|
||||
sb.Append(new string('-', 50));
|
||||
sb.AppendLine("§r");
|
||||
|
||||
if (!string.IsNullOrEmpty(info.MotdRaw))
|
||||
{
|
||||
try
|
||||
{
|
||||
sb.AppendLine(ChatParser.ParseText(info.MotdRaw));
|
||||
}
|
||||
catch
|
||||
{
|
||||
sb.AppendLine(info.MotdRaw);
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append("§f");
|
||||
sb.Append(Translations.mcc_server_info_label_server);
|
||||
sb.Append(" §b");
|
||||
sb.Append(info.Host);
|
||||
sb.Append("§7:§b");
|
||||
sb.AppendLine(info.Port.ToString());
|
||||
|
||||
sb.Append("§f");
|
||||
sb.Append(Translations.mcc_server_info_label_version);
|
||||
sb.Append(" §b");
|
||||
sb.Append(info.VersionName);
|
||||
sb.Append(" §7(");
|
||||
sb.Append(string.Format(Translations.mcc_server_info_label_protocol, "§e" + info.ProtocolVersion + "§7"));
|
||||
sb.AppendLine(")");
|
||||
|
||||
if (info.ResolvedProtocol != 0 && info.ResolvedProtocol != info.ProtocolVersion)
|
||||
{
|
||||
string resolvedMcVer = ProtocolHandler.ProtocolVersion2MCVer(info.ResolvedProtocol);
|
||||
sb.Append("§f");
|
||||
sb.Append(Translations.mcc_server_info_label_connecting_as);
|
||||
sb.Append(" §a");
|
||||
sb.Append(resolvedMcVer);
|
||||
sb.Append(" §7(");
|
||||
sb.Append(string.Format(Translations.mcc_server_info_label_protocol, "§a" + info.ResolvedProtocol + "§7"));
|
||||
sb.AppendLine(")");
|
||||
}
|
||||
|
||||
if (info.PingMs >= 0)
|
||||
{
|
||||
sb.Append("§f");
|
||||
sb.Append(Translations.mcc_server_info_label_ping);
|
||||
sb.Append(" §a");
|
||||
sb.AppendLine(string.Format(Translations.mcc_server_info_label_ping_ms, info.PingMs));
|
||||
}
|
||||
|
||||
sb.Append("§f");
|
||||
sb.Append(Translations.mcc_server_info_label_players);
|
||||
sb.Append(" §a");
|
||||
sb.Append(info.OnlinePlayers);
|
||||
sb.Append("§7/§c");
|
||||
sb.AppendLine(info.MaxPlayers.ToString());
|
||||
|
||||
if (info.SamplePlayers.Count > 0)
|
||||
{
|
||||
sb.Append("§f");
|
||||
sb.AppendLine(Translations.mcc_server_info_label_online);
|
||||
|
||||
int shown = Math.Min(info.SamplePlayers.Count, MaxSamplePlayers);
|
||||
for (int i = 0; i < shown; i++)
|
||||
sb.AppendLine($" §a{info.SamplePlayers[i].Name}");
|
||||
|
||||
if (info.SamplePlayers.Count > shown)
|
||||
sb.AppendLine($" §7{string.Format(Translations.mcc_server_info_sample_more, info.SamplePlayers.Count - shown)}");
|
||||
}
|
||||
|
||||
sb.Append("§8§m");
|
||||
sb.Append(new string('-', 50));
|
||||
sb.Append("§r");
|
||||
|
||||
ConsoleIO.WriteLineFormatted(sb.ToString(), acceptnewlines: true);
|
||||
}
|
||||
|
||||
private static void ShowTui(ServerStatusInfo info, Tui.TuiConsoleBackend backend)
|
||||
{
|
||||
var view = backend.GetView();
|
||||
if (view is null)
|
||||
{
|
||||
ShowClassic(info);
|
||||
return;
|
||||
}
|
||||
|
||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
var panel = Tui.ServerStatusPanelBuilder.Build(info);
|
||||
view.AppendControlToLog(panel);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
30
MinecraftClient/Protocol/ServerStatusInfo.cs
Normal file
30
MinecraftClient/Protocol/ServerStatusInfo.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MinecraftClient.Protocol
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the structured result of a Minecraft server status (SLP) ping,
|
||||
/// including MOTD, player counts, sample player list, version, and favicon.
|
||||
/// </summary>
|
||||
public sealed class ServerStatusInfo
|
||||
{
|
||||
public string Host { get; init; } = string.Empty;
|
||||
public int Port { get; init; }
|
||||
public string VersionName { get; init; } = string.Empty;
|
||||
public int ProtocolVersion { get; init; }
|
||||
public int ResolvedProtocol { get; set; }
|
||||
public int OnlinePlayers { get; init; }
|
||||
public int MaxPlayers { get; init; }
|
||||
public List<SamplePlayer> SamplePlayers { get; init; } = [];
|
||||
public string MotdRaw { get; init; } = string.Empty;
|
||||
public string? FaviconBase64 { get; init; }
|
||||
public long PingMs { get; init; }
|
||||
|
||||
public sealed class SamplePlayer
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Id { get; init; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2269,6 +2269,66 @@ namespace MinecraftClient {
|
|||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_server {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_server", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_version {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_version", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_protocol {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_protocol", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_players {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_players", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_ping {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_ping", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_ping_ms {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_ping_ms", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_connecting_as {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_connecting_as", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_label_online {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.label_online", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_sample_more {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.sample_more", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
internal static string mcc_server_info_version_upgrade {
|
||||
get {
|
||||
return ResourceManager.GetString("mcc.server_info.version_upgrade", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Converting session cache from disk: {0}.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -830,6 +830,36 @@ Add the ID of this chat to "Authorized_Chat_Ids" field in the configuration file
|
|||
<data name="botname.TestBot" xml:space="preserve">
|
||||
<value>TestBot</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_server" xml:space="preserve">
|
||||
<value>Server:</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_version" xml:space="preserve">
|
||||
<value>Version:</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_protocol" xml:space="preserve">
|
||||
<value>Protocol: {0}</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_players" xml:space="preserve">
|
||||
<value>Players:</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_ping" xml:space="preserve">
|
||||
<value>Ping:</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_ping_ms" xml:space="preserve">
|
||||
<value>{0} ms</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_connecting_as" xml:space="preserve">
|
||||
<value>Connecting as:</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.label_online" xml:space="preserve">
|
||||
<value>Online:</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.sample_more" xml:space="preserve">
|
||||
<value>... +{0}</value>
|
||||
</data>
|
||||
<data name="mcc.server_info.version_upgrade" xml:space="preserve">
|
||||
<value>Server reported protocol {0} ({1}), upgraded to {2} ({3}) for best compatibility</value>
|
||||
</data>
|
||||
<data name="cache.converting" xml:space="preserve">
|
||||
<value>Converting session cache from disk: {0}</value>
|
||||
</data>
|
||||
|
|
@ -2015,10 +2045,10 @@ MCC is running with default settings.</value>
|
|||
<value>Server is in offline mode.</value>
|
||||
</data>
|
||||
<data name="mcc.server_protocol" xml:space="preserve">
|
||||
<value>Server version : {0} (protocol v{1})</value>
|
||||
<value>Server version: {0} (protocol v{1})</value>
|
||||
</data>
|
||||
<data name="mcc.server_version" xml:space="preserve">
|
||||
<value>Server version : </value>
|
||||
<value>Server version: </value>
|
||||
</data>
|
||||
<data name="mcc.session" xml:space="preserve">
|
||||
<value>Checking Session...</value>
|
||||
|
|
|
|||
|
|
@ -1171,5 +1171,18 @@ namespace MinecraftClient.Tui
|
|||
_commandInput.Focus();
|
||||
}, DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
#region Custom Control Append
|
||||
|
||||
public void AppendControlToLog(Control control)
|
||||
{
|
||||
_logLines.Add(string.Empty);
|
||||
_logControls.Add(control);
|
||||
TrimLog();
|
||||
if (_autoScroll)
|
||||
ScheduleScrollToEnd();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ namespace MinecraftClient.Tui
|
|||
IBrush currentColor = Brushes.White;
|
||||
bool bold = false;
|
||||
bool italic = false;
|
||||
bool underline = false;
|
||||
bool strikethrough = false;
|
||||
int start = 0;
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
|
|
@ -59,7 +61,7 @@ namespace MinecraftClient.Tui
|
|||
if (text[i] == '§' && i + 1 < text.Length)
|
||||
{
|
||||
if (i > start)
|
||||
AddRun(tb, text[start..i], currentColor, bold, italic);
|
||||
AddRun(tb, text[start..i], currentColor, bold, italic, underline, strikethrough);
|
||||
|
||||
char code = char.ToLower(text[i + 1]);
|
||||
|
||||
|
|
@ -68,6 +70,8 @@ namespace MinecraftClient.Tui
|
|||
currentColor = brush;
|
||||
bold = false;
|
||||
italic = false;
|
||||
underline = false;
|
||||
strikethrough = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -75,10 +79,14 @@ namespace MinecraftClient.Tui
|
|||
{
|
||||
case 'l': bold = true; break;
|
||||
case 'o': italic = true; break;
|
||||
case 'n': underline = true; break;
|
||||
case 'm': strikethrough = true; break;
|
||||
case 'r':
|
||||
currentColor = Brushes.White;
|
||||
bold = false;
|
||||
italic = false;
|
||||
underline = false;
|
||||
strikethrough = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +97,7 @@ namespace MinecraftClient.Tui
|
|||
}
|
||||
|
||||
if (start < text.Length)
|
||||
AddRun(tb, text[start..], currentColor, bold, italic);
|
||||
AddRun(tb, text[start..], currentColor, bold, italic, underline, strikethrough);
|
||||
|
||||
if (tb.Inlines?.Count == 0)
|
||||
{
|
||||
|
|
@ -100,16 +108,29 @@ namespace MinecraftClient.Tui
|
|||
return tb;
|
||||
}
|
||||
|
||||
private static void AddRun(TextBlock tb, string text, IBrush color, bool bold, bool italic)
|
||||
private static void AddRun(TextBlock tb, string text, IBrush color,
|
||||
bool bold, bool italic, bool underline, bool strikethrough)
|
||||
{
|
||||
if (text.Length == 0) return;
|
||||
|
||||
tb.Inlines ??= new InlineCollection();
|
||||
|
||||
TextDecorationCollection? decorations = null;
|
||||
if (underline || strikethrough)
|
||||
{
|
||||
decorations = [];
|
||||
if (underline)
|
||||
decorations.Add(new TextDecoration { Location = TextDecorationLocation.Underline });
|
||||
if (strikethrough)
|
||||
decorations.Add(new TextDecoration { Location = TextDecorationLocation.Strikethrough });
|
||||
}
|
||||
|
||||
tb.Inlines.Add(new Run(text)
|
||||
{
|
||||
Foreground = color,
|
||||
FontWeight = bold ? FontWeight.Bold : FontWeight.Normal,
|
||||
FontStyle = italic ? FontStyle.Italic : FontStyle.Normal,
|
||||
TextDecorations = decorations,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
296
MinecraftClient/Tui/ServerStatusPanelBuilder.cs
Normal file
296
MinecraftClient/Tui/ServerStatusPanelBuilder.cs
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Documents;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace MinecraftClient.Tui
|
||||
{
|
||||
internal static class ServerStatusPanelBuilder
|
||||
{
|
||||
private const int MaxSamplePlayers = 10;
|
||||
private const int FaviconDisplaySize = 16;
|
||||
|
||||
internal static Border Build(Protocol.ServerStatusInfo info)
|
||||
{
|
||||
var contentPanel = new DockPanel { Background = Brushes.Black };
|
||||
|
||||
if (info.FaviconBase64 is not null)
|
||||
{
|
||||
var iconGrid = BuildFaviconGrid(info.FaviconBase64, FaviconDisplaySize);
|
||||
DockPanel.SetDock(iconGrid, Dock.Left);
|
||||
contentPanel.Children.Add(iconGrid);
|
||||
}
|
||||
|
||||
var infoPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
Margin = new Thickness(1, 0, 0, 0),
|
||||
};
|
||||
|
||||
AddMotd(infoPanel, info);
|
||||
AddAddress(infoPanel, info);
|
||||
AddVersion(infoPanel, info);
|
||||
AddConnectingAs(infoPanel, info);
|
||||
AddPing(infoPanel, info);
|
||||
AddPlayers(infoPanel, info);
|
||||
AddSamplePlayers(infoPanel, info);
|
||||
|
||||
contentPanel.Children.Add(infoPanel);
|
||||
|
||||
return new Border
|
||||
{
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(80, 80, 80)),
|
||||
BorderThickness = new Thickness(1),
|
||||
Background = new SolidColorBrush(Color.FromArgb(240, 20, 20, 20)),
|
||||
Padding = new Thickness(1, 0),
|
||||
Child = contentPanel,
|
||||
Margin = new Thickness(0, 1),
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddMotd(StackPanel panel, Protocol.ServerStatusInfo info)
|
||||
{
|
||||
if (string.IsNullOrEmpty(info.MotdRaw))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
string motdFormatted = Protocol.Message.ChatParser.ParseText(info.MotdRaw);
|
||||
foreach (string line in motdFormatted.Split('\n'))
|
||||
panel.Children.Add(McColorParser.CreateColoredTextBlock(line, TextWrapping.NoWrap));
|
||||
}
|
||||
catch
|
||||
{
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = info.MotdRaw,
|
||||
Foreground = Brushes.White,
|
||||
TextWrapping = TextWrapping.NoWrap,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddAddress(StackPanel panel, Protocol.ServerStatusInfo info)
|
||||
{
|
||||
var row = new TextBlock();
|
||||
row.Inlines!.Add(Label(Translations.mcc_server_info_label_server));
|
||||
row.Inlines.Add(Value(info.Host, McColors.Aqua));
|
||||
row.Inlines.Add(new Run($":{info.Port}") { Foreground = McColors.Gray });
|
||||
panel.Children.Add(row);
|
||||
}
|
||||
|
||||
private static void AddVersion(StackPanel panel, Protocol.ServerStatusInfo info)
|
||||
{
|
||||
var row = new TextBlock();
|
||||
row.Inlines!.Add(Label(Translations.mcc_server_info_label_version));
|
||||
row.Inlines.Add(Value(info.VersionName, McColors.Aqua));
|
||||
row.Inlines.Add(new Run(" (") { Foreground = McColors.Gray });
|
||||
row.Inlines.Add(new Run(string.Format(Translations.mcc_server_info_label_protocol, info.ProtocolVersion))
|
||||
{ Foreground = McColors.Gray });
|
||||
row.Inlines.Add(new Run(")") { Foreground = McColors.Gray });
|
||||
panel.Children.Add(row);
|
||||
}
|
||||
|
||||
private static void AddConnectingAs(StackPanel panel, Protocol.ServerStatusInfo info)
|
||||
{
|
||||
if (info.ResolvedProtocol == 0 || info.ResolvedProtocol == info.ProtocolVersion)
|
||||
return;
|
||||
|
||||
string resolvedMcVer = Protocol.ProtocolHandler.ProtocolVersion2MCVer(info.ResolvedProtocol);
|
||||
var row = new TextBlock();
|
||||
row.Inlines!.Add(Label(Translations.mcc_server_info_label_connecting_as));
|
||||
row.Inlines.Add(Value(resolvedMcVer, McColors.Green));
|
||||
row.Inlines.Add(new Run(" (") { Foreground = McColors.Gray });
|
||||
row.Inlines.Add(new Run(string.Format(Translations.mcc_server_info_label_protocol, info.ResolvedProtocol))
|
||||
{ Foreground = McColors.Gray });
|
||||
row.Inlines.Add(new Run(")") { Foreground = McColors.Gray });
|
||||
panel.Children.Add(row);
|
||||
}
|
||||
|
||||
private static void AddPing(StackPanel panel, Protocol.ServerStatusInfo info)
|
||||
{
|
||||
if (info.PingMs < 0)
|
||||
return;
|
||||
|
||||
var pingColor = info.PingMs < 100
|
||||
? McColors.Green
|
||||
: info.PingMs < 300
|
||||
? McColors.Yellow
|
||||
: McColors.Red;
|
||||
|
||||
var row = new TextBlock();
|
||||
row.Inlines!.Add(Label(Translations.mcc_server_info_label_ping));
|
||||
row.Inlines.Add(new Run(string.Format(Translations.mcc_server_info_label_ping_ms, info.PingMs))
|
||||
{ Foreground = pingColor });
|
||||
panel.Children.Add(row);
|
||||
}
|
||||
|
||||
private static void AddPlayers(StackPanel panel, Protocol.ServerStatusInfo info)
|
||||
{
|
||||
var row = new TextBlock();
|
||||
row.Inlines!.Add(Label(Translations.mcc_server_info_label_players));
|
||||
row.Inlines.Add(Value($"{info.OnlinePlayers}", McColors.Green));
|
||||
row.Inlines.Add(new Run("/") { Foreground = McColors.Gray });
|
||||
row.Inlines.Add(Value($"{info.MaxPlayers}", McColors.Red));
|
||||
panel.Children.Add(row);
|
||||
}
|
||||
|
||||
private static void AddSamplePlayers(StackPanel panel, Protocol.ServerStatusInfo info)
|
||||
{
|
||||
if (info.SamplePlayers.Count == 0)
|
||||
return;
|
||||
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = Translations.mcc_server_info_label_online,
|
||||
Foreground = McColors.Gray,
|
||||
Margin = new Thickness(0, 1, 0, 0),
|
||||
});
|
||||
|
||||
int shown = Math.Min(info.SamplePlayers.Count, MaxSamplePlayers);
|
||||
for (int i = 0; i < shown; i++)
|
||||
{
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $" {info.SamplePlayers[i].Name}",
|
||||
Foreground = McColors.Green,
|
||||
});
|
||||
}
|
||||
|
||||
if (info.SamplePlayers.Count > shown)
|
||||
{
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $" {string.Format(Translations.mcc_server_info_sample_more, info.SamplePlayers.Count - shown)}",
|
||||
Foreground = McColors.Gray,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static Run Label(string text) =>
|
||||
new(text + " ") { Foreground = McColors.Gray };
|
||||
|
||||
private static Run Value(string text, IBrush color) =>
|
||||
new(text) { Foreground = color };
|
||||
|
||||
#region Favicon Rendering
|
||||
|
||||
private static Grid BuildFaviconGrid(string base64Png, int displaySize)
|
||||
{
|
||||
byte[] pngBytes;
|
||||
try
|
||||
{
|
||||
pngBytes = Convert.FromBase64String(base64Png);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Grid();
|
||||
}
|
||||
|
||||
int srcWidth, srcHeight;
|
||||
byte[] rgba;
|
||||
try
|
||||
{
|
||||
(srcWidth, srcHeight, rgba) = DecodePngToRgba(pngBytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Grid();
|
||||
}
|
||||
|
||||
int cellCols = displaySize;
|
||||
int cellRows = displaySize / 2;
|
||||
|
||||
var grid = new Grid();
|
||||
for (int c = 0; c < cellCols; c++)
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition(1, GridUnitType.Auto));
|
||||
for (int r = 0; r < cellRows; r++)
|
||||
grid.RowDefinitions.Add(new RowDefinition(1, GridUnitType.Auto));
|
||||
|
||||
for (int row = 0; row < cellRows; row++)
|
||||
{
|
||||
for (int col = 0; col < cellCols; col++)
|
||||
{
|
||||
int topPixelY = row * 2;
|
||||
int bottomPixelY = row * 2 + 1;
|
||||
|
||||
var topColor = SamplePixel(rgba, srcWidth, srcHeight, col, topPixelY, cellCols, displaySize);
|
||||
var bottomColor = SamplePixel(rgba, srcWidth, srcHeight, col, bottomPixelY, cellCols, displaySize);
|
||||
|
||||
var cell = new TextBlock
|
||||
{
|
||||
Text = "\u2580",
|
||||
Foreground = new SolidColorBrush(topColor),
|
||||
Background = new SolidColorBrush(bottomColor),
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0),
|
||||
};
|
||||
|
||||
Grid.SetRow(cell, row);
|
||||
Grid.SetColumn(cell, col);
|
||||
grid.Children.Add(cell);
|
||||
}
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private static Color SamplePixel(byte[] rgba, int srcW, int srcH, int dstX, int dstY, int dstW, int dstH)
|
||||
{
|
||||
int srcX = dstX * srcW / dstW;
|
||||
int srcY = dstY * srcH / dstH;
|
||||
srcX = Math.Clamp(srcX, 0, srcW - 1);
|
||||
srcY = Math.Clamp(srcY, 0, srcH - 1);
|
||||
|
||||
int idx = (srcY * srcW + srcX) * 4;
|
||||
if (idx + 3 >= rgba.Length)
|
||||
return Color.FromRgb(0, 0, 0);
|
||||
|
||||
byte r = rgba[idx];
|
||||
byte g = rgba[idx + 1];
|
||||
byte b = rgba[idx + 2];
|
||||
byte a = rgba[idx + 3];
|
||||
|
||||
return a < 128 ? Color.FromRgb(0, 0, 0) : Color.FromRgb(r, g, b);
|
||||
}
|
||||
|
||||
private static (int Width, int Height, byte[] Rgba) DecodePngToRgba(byte[] png)
|
||||
{
|
||||
using var image = new ImageMagick.MagickImage(png);
|
||||
int w = (int)image.Width;
|
||||
int h = (int)image.Height;
|
||||
|
||||
using var pixels = image.GetPixelsUnsafe();
|
||||
var rgba = new byte[w * h * 4];
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
var pixel = pixels.GetPixel(x, y)!;
|
||||
int idx = (y * w + x) * 4;
|
||||
var color = pixel.ToColor()!;
|
||||
rgba[idx] = (byte)(color.R >> 8);
|
||||
rgba[idx + 1] = (byte)(color.G >> 8);
|
||||
rgba[idx + 2] = (byte)(color.B >> 8);
|
||||
rgba[idx + 3] = (byte)(color.A >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
return (w, h, rgba);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static class McColors
|
||||
{
|
||||
public static readonly IBrush Gray = new SolidColorBrush(Color.FromRgb(170, 170, 170));
|
||||
public static readonly IBrush Aqua = new SolidColorBrush(Color.FromRgb(85, 255, 255));
|
||||
public static readonly IBrush Green = new SolidColorBrush(Color.FromRgb(85, 255, 85));
|
||||
public static readonly IBrush Red = new SolidColorBrush(Color.FromRgb(255, 85, 85));
|
||||
public static readonly IBrush Yellow = new SolidColorBrush(Color.FromRgb(255, 255, 85));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue