Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/java/low/citory/ExampleUsage.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static void main(String[] args) {
// Colors work only with "System.out.println()"
// Idk how to fix colors with log4j
LOGGER.info("Server MOTD: {}", server.getAnsiMotd());
LOGGER.info("Server Raw MOTD: {}", server.getRawMotd());
LOGGER.info("Players online: {}/{}", server.getPlayersOnline(), server.getMaxPlayers());
LOGGER.info("Server ping: {}ms", server.getServerPing());
}
Expand Down
21 changes: 4 additions & 17 deletions src/main/java/low/citory/MinecraftPinger.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ public final class MinecraftPinger {
private boolean serverStatus;
private String serverVersion;
private int protocolVersion;
private String serverMOTD;
private String strippedMOTD;
private String rawMOTD;
private String ansiMOTD;
private int playersOnline;
private int maxPlayers;
Expand Down Expand Up @@ -44,11 +43,7 @@ public void setProtocol(int protocol) {
}

public void setRawMotd(String rawMotd) {
this.serverMOTD = rawMotd;
}

public void setStrippedMotd(String strippedMotd) {
this.strippedMOTD = strippedMotd;
this.rawMOTD = rawMotd;
}

public void setAnsiMotd(String ansiMotd) {
Expand Down Expand Up @@ -97,16 +92,8 @@ public int getProtocolVersion() {
* Get server motd
* @return string
*/
public String getMotd() {
return this.serverMOTD;
}

/**
* Get stripped motd
* @return string
*/
public String getStrippedMotd() {
return this.strippedMOTD;
public String getRawMotd() {
return this.rawMOTD;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/low/citory/util/AbstractPinger.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ protected static void setServerData(
pinger.setVersion(version);
pinger.setProtocol(protocol);
pinger.setRawMotd(rawMotd);
pinger.setStrippedMotd(TextColorizer.stripFormatting(rawMotd));
pinger.setAnsiMotd(TextColorizer.convertToAnsi(rawMotd));
pinger.setAnsiMotd(MinecraftANSI.toAnsi(JsonToANSI.convert(rawMotd)));
//Don't know how to convert hex color into ANSI plz finish this.
pinger.setPlayersOnline(playersOnline);
pinger.setMaxPlayers(maxPlayers);
pinger.setServerPing(ping);
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/low/citory/util/JsonToANSI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package low.citory.util;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

public class JsonToANSI {

public static String convert(String rawMotd) {

try {
JsonElement root = JsonParser.parseString(rawMotd);
StringBuilder result = new StringBuilder();
parseElement(root, result);
return result.toString();
} catch (Exception e) {
e.printStackTrace();
return "A Minecraft Server";
}
}

private static void parseElement(JsonElement element, StringBuilder sb) {
if (element.isJsonObject()) {
JsonObject obj = element.getAsJsonObject();

// 添加 text 字段
if (obj.has("text")) {
String text = obj.get("text").getAsString();
sb.append(text);
}

// 递归处理 extra 数组
if (obj.has("extra")) {
JsonArray extra = obj.getAsJsonArray("extra");
for (JsonElement extraElement : extra) {
parseElement(extraElement, sb); // 递归处理每个 extra 元素
}
}

} else if (element.isJsonArray()) {
// 处理数组(如顶层是数组)
for (JsonElement item : element.getAsJsonArray()) {
parseElement(item, sb);
}
} else if (element.isJsonPrimitive()) {
// 处理原始类型:字符串
String value = element.getAsString();
if ("\n".equals(value)) {
sb.append("\n");
} else {
sb.append(value);
}
}
// 忽略 null 或其他类型
}
}
46 changes: 46 additions & 0 deletions src/main/java/low/citory/util/MinecraftANSI.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package low.citory.util;

public class MinecraftANSI {

/**
* 将 Minecraft 的 § 颜色代码转换为 ANSI 转义序列
* 用于在支持 ANSI 的终端中显示颜色
*/
public static String toAnsi(String text) {
if (text == null) return null;

StringBuilder result = new StringBuilder();
boolean inColor = false;
boolean inFormat = false;

for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == '§' && i + 1 < text.length()) {
char code = text.charAt(i + 1);
MinecraftColors color = MinecraftColors.fromChar(code);
if (color != null) {
result.append(color.getAnsiCode());
inColor = true;
i++; // 跳过 color code
continue;
}

MinecraftFormat format = MinecraftFormat.fromChar(code);
if (format != null) {
result.append(format.getAnsiCode());
inFormat = true;
i++; // 跳过 format code
continue;
}
}
result.append(c);
}

// 最后重置所有样式
if (inColor || inFormat) {
result.append(MinecraftColors.RESET.getAnsiCode());
}

return result.toString();
}
}
59 changes: 41 additions & 18 deletions src/main/java/low/citory/util/MinecraftColors.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package low.citory.util;

enum MinecraftColors {
V_0("\u001B[30m"), // BLACK
V_1("\u001B[34m"), // DARK BLUE
V_2("\u001B[32m"), // DARK GREEN
V_3("\u001B[36m"), // DARK AQUA
V_4("\u001B[31m"), // DARK RED
V_5("\u001B[35m"), // DARK PURPLE
V_6("\u001B[33m"), // GOLD
V_7("\u001B[37m"), // GRAY
V_8("\u001B[90m"), // DARK GRAY
V_9("\u001B[94m"), // BLUE
V_A("\u001B[92m"), // GREEN
V_B("\u001B[96m"), // AQUA
V_C("\u001B[91m"), // RED
V_D("\u001B[95m"), // PURPLE
V_E("\u001B[93m"), // YELLOW
V_F("\u001B[97m"), // WHITE
V_R("\u001B[0m"); // RESET
public enum MinecraftColors {
BLACK ("\u001B[30m"), // §0
DARK_BLUE ("\u001B[34m"), // §1
DARK_GREEN ("\u001B[32m"), // §2
DARK_AQUA ("\u001B[36m"), // §3
DARK_RED ("\u001B[31m"), // §4
DARK_PURPLE("\u001B[35m"), // §5
GOLD ("\u001B[33m"), // §6
GRAY ("\u001B[37m"), // §7
DARK_GRAY ("\u001B[90m"), // §8
BLUE ("\u001B[94m"), // §9
GREEN ("\u001B[92m"), // §a
AQUA ("\u001B[96m"), // §b
RED ("\u001B[91m"), // §c
LIGHT_PURPLE("\u001B[95m"), // §d
YELLOW ("\u001B[93m"), // §e
WHITE ("\u001B[97m"), // §f
RESET ("\u001B[0m"); // §r

private final String ansiCode;

Expand All @@ -33,4 +33,27 @@ public String getAnsiCode() {
public String toString() {
return ansiCode;
}

public static MinecraftColors fromChar(char code) {
return switch (Character.toLowerCase(code)) {
case '0' -> BLACK;
case '1' -> DARK_BLUE;
case '2' -> DARK_GREEN;
case '3' -> DARK_AQUA;
case '4' -> DARK_RED;
case '5' -> DARK_PURPLE;
case '6' -> GOLD;
case '7' -> GRAY;
case '8' -> DARK_GRAY;
case '9' -> BLUE;
case 'a' -> GREEN;
case 'b' -> AQUA;
case 'c' -> RED;
case 'd' -> LIGHT_PURPLE;
case 'e' -> YELLOW;
case 'f' -> WHITE;
case 'r' -> RESET;
default -> null;
};
}
}
30 changes: 30 additions & 0 deletions src/main/java/low/citory/util/MinecraftFormat.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package low.citory.util;

public enum MinecraftFormat {
BOLD ("\u001B[1m"), // §l
ITALIC ("\u001B[3m"), // §o
UNDERLINE ("\u001B[4m"), // §n
STRIKETHROUGH ("\u001B[9m"), // §m
RESET ("\u001B[0m"); // §r

private final String ansiCode;

MinecraftFormat(String ansiCode) {
this.ansiCode = ansiCode;
}

public String getAnsiCode() {
return ansiCode;
}

public static MinecraftFormat fromChar(char code) {
return switch (Character.toLowerCase(code)) {
case 'l' -> BOLD;
case 'o' -> ITALIC;
case 'n' -> UNDERLINE;
case 'm' -> STRIKETHROUGH;
case 'r' -> RESET;
default -> null;
};
}
}
10 changes: 6 additions & 4 deletions src/main/java/low/citory/util/PacketUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
public final class PacketUtil {

// Writers
public static void writeVarInt(DataOutputStream outputStream, int value) throws IOException {
public static void writeVarInt(DataOutputStream out, int value) throws IOException {
while (true) {
if ((value & 0xFFFFFF80) == 0) outputStream.writeByte(value);
if ((value & 0xFFFFFF80) == 0) return;
if ((value & 0xFFFFFF80) == 0) {
out.writeByte(value);
return; // 👈 必须 return!
}

outputStream.writeByte(value & 0x7F | 0x80);
out.writeByte((value & 0x7F) | 0x80);
value >>>= 7;
}
}
Expand Down
41 changes: 21 additions & 20 deletions src/main/java/low/citory/util/TextColorizer.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
package low.citory.util;

import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public final class TextColorizer {

private static final Pattern PATTERN = Pattern.compile("§([0-9a-fk-or])", Pattern.CASE_INSENSITIVE);

public static String convertToAnsi(String string) {
Matcher matcher = PATTERN.matcher(string+"§r");
StringBuilder finallyString = new StringBuilder();
private static final String COLOR_CODE_PATTERN = "§[0-9a-fk-or]";

while (matcher.find()) {
char color = matcher.group(1).charAt(0);
String colorCode = "V_"+Character.toUpperCase(color);
String code = Optional.of(MinecraftColors.valueOf(colorCode)).
map(MinecraftColors::getAnsiCode).orElse("");
/**
* 移除所有 Minecraft 颜色和格式代码 (§r, §a, §l 等)
*/
public static String stripFormatting(String text) {
return stripColorCodes(text);
}

matcher.appendReplacement(finallyString, Matcher.quoteReplacement(code));
}
matcher.appendTail(finallyString);
return finallyString.toString();
/**
* 移除所有 Minecraft 颜色和格式代码
* 此方法可在类内部或外部调用
*/
public static String stripColorCodes(String text) {
if (text == null) return null;
return text.replaceAll(COLOR_CODE_PATTERN, "");
}

public static String stripFormatting(String text) {
return text.replaceAll("§[0-9a-fk-or]", "");
/**
* 将 Minecraft 的 § 颜色代码转换为 ANSI 转义序列,用于终端着色
* 使用 MinecraftANSI 工具类
*/
public static String convertToAnsi(String string) {
if (string == null) return null;
return MinecraftANSI.toAnsi(string);
}
}
15 changes: 12 additions & 3 deletions src/main/java/low/citory/versions/ModernPing.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,26 @@ public static boolean pingServer(MinecraftPinger pinger, String serverIP, int se
DataOutputStream handshake = new DataOutputStream(rawHandshake);

handshake.writeByte(0x00);
PacketUtil.writeVarInt(handshake, -1);
PacketUtil.writeVarInt(handshake, 765);
PacketUtil.writeString(handshake, serverIP);
handshake.writeShort(serverPORT);
PacketUtil.writeVarInt(handshake, 1);

PacketUtil.writeVarInt(outputStream, rawHandshake.size());
outputStream.write(rawHandshake.toByteArray());

PacketUtil.writeVarInt(outputStream, 1);
PacketUtil.writeVarInt(outputStream, 0);
ByteArrayOutputStream statusReq = new ByteArrayOutputStream();
DataOutputStream tmp = new DataOutputStream(statusReq);
PacketUtil.writeVarInt(tmp, 0x00); // Status Request packet ID
byte[] reqBytes = statusReq.toByteArray();

PacketUtil.writeVarInt(outputStream, reqBytes.length);
outputStream.write(reqBytes);
outputStream.flush();

// Step 1: 读取整个包的长度(VarInt)
int packetLength = PacketUtil.readVarInt(inputStream);
// Step 2: 读取 Packet ID
int packetId = PacketUtil.readVarInt(inputStream);
if (packetId != 0x00) return false;

Expand All @@ -61,6 +69,7 @@ private static void parseJsonResponse(MinecraftPinger pinger, String data, long
try {
Gson gson = new Gson();
JsonObject json = gson.fromJson(data, JsonObject.class);
//System.out.println(json.toString());

JsonObject versionData = json.getAsJsonObject("version");
String version = versionData.get("name").getAsString();
Expand Down