diff --git a/src/main/java/cn/alini/trueuuid/Trueuuid.java b/src/main/java/cn/alini/trueuuid/Trueuuid.java index 5db090f..0fb615a 100644 --- a/src/main/java/cn/alini/trueuuid/Trueuuid.java +++ b/src/main/java/cn/alini/trueuuid/Trueuuid.java @@ -12,24 +12,24 @@ public class Trueuuid { private static final Logger LOGGER = LogUtils.getLogger(); public Trueuuid() { - // 注册并生成 config/trueuuid-common.toml + // 注册并生成 config/trueuuid-common.toml (Register and generate config/trueuuid-common.toml) TrueuuidConfig.register(); - // 初始化运行时单例(注册表、最近 IP 容错缓存等) + // 初始化运行时单例(注册表、最近 IP 容错缓存等) (Initialize runtime singleton (registry, recent IP grace cache, etc.)) TrueuuidRuntime.init(); - // =====MoJang网络连通性测试===== - // 若开启 nomojang,则跳过启动时的 Mojang 网络连通性检测 + // ===== MoJang网络连通性测试 (Mojang Network Connectivity Test)===== + // 若开启 nomojang,则跳过启动时的 Mojang 网络连通性检测 (If nomojang is enabled, skip Mojang network connectivity check at startup) if (TrueuuidConfig.nomojangEnabled()) { LOGGER.info("nomojang 已启用,跳过 Mojang 会话服务器连通性检测"); } else { - // =====MoJang网络连通性测试===== + // ===== MoJang网络连通性测试 (Mojang Network Connectivity Test )===== try { String testUrl = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=Mojang&serverId=test"; java.net.URL url = new java.net.URL(testUrl); java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); - conn.setConnectTimeout(3000); // 3秒超时 + conn.setConnectTimeout(3000); // 3秒超时 (3 seconds timeout) conn.setReadTimeout(3000); conn.connect(); diff --git a/src/main/java/cn/alini/trueuuid/api/TrueuuidApi.java b/src/main/java/cn/alini/trueuuid/api/TrueuuidApi.java index 78ee11f..f0612a8 100644 --- a/src/main/java/cn/alini/trueuuid/api/TrueuuidApi.java +++ b/src/main/java/cn/alini/trueuuid/api/TrueuuidApi.java @@ -5,12 +5,14 @@ /** * TrueUUID API: 提供正版状态查询接口,供附属mod调用。 + * (TrueUUID API: Provides premium status query interface for addon mods to call.) */ public class TrueuuidApi { /** * 判断指定玩家名称是否已被TrueUUID判定为正版。 - * @param name 玩家名称(不区分大小写) - * @return 如果已被判定为正版,返回true,否则返回false。 + * (Determines whether the specified player name has been determined as premium by TrueUUID.) + * @param name 玩家名称(不区分大小写) (Player name (case-insensitive)) + * @return 如果已被判定为正版,返回true,否则返回false。 (Returns true if determined as premium, otherwise false.) */ public static boolean isPremium(String name) { return TrueuuidRuntime.NAME_REGISTRY.isKnownPremiumName(name); @@ -18,8 +20,9 @@ public static boolean isPremium(String name) { /** * 获取指定玩家名称对应的正版UUID(如有)。 - * @param name 玩家名称(不区分大小写) - * @return 如果有正版UUID,返回UUID,否则返回null。 + * (Gets the premium UUID corresponding to the specified player name (if any).) + * @param name 玩家名称(不区分大小写) (Player name (case-insensitive)) + * @return 如果有正版UUID,返回UUID,否则返回null。 (Returns UUID if there is a premium UUID, otherwise null.) */ public static UUID getPremiumUuid(String name) { return TrueuuidRuntime.NAME_REGISTRY.getPremiumUuid(name).orElse(null); diff --git a/src/main/java/cn/alini/trueuuid/command/TrueuuidCommands.java b/src/main/java/cn/alini/trueuuid/command/TrueuuidCommands.java index add6576..88d680d 100644 --- a/src/main/java/cn/alini/trueuuid/command/TrueuuidCommands.java +++ b/src/main/java/cn/alini/trueuuid/command/TrueuuidCommands.java @@ -43,7 +43,7 @@ public static void onRegister(RegisterCommandsEvent e) { CommandDispatcher d = e.getDispatcher(); d.register(Commands.literal("trueuuid") .requires(src -> src.hasPermission(3)) - // 新增:/trueuuid mojang status + // 新增(Added):/trueuuid mojang status .then(Commands.literal("config") .requires(src -> src.hasPermission(3)) .then(Commands.literal("nomojang") @@ -91,24 +91,24 @@ public static void onRegister(RegisterCommandsEvent e) { } - // 新增方法:runtime 从磁盘重载配置并将值写入 TrueuuidConfig.COMMON + // 新增方法:runtime 从磁盘重载配置并将值写入 TrueuuidConfig.COMMON (Added method: runtime reload config from disk and write values to TrueuuidConfig.COMMON) private static int cmdConfigReload(CommandSourceStack src) { try { Path cfgPath = FMLPaths.CONFIGDIR.get().resolve("trueuuid-common.toml"); CommentedFileConfig cfg = CommentedFileConfig.builder(cfgPath) - .sync() // 与磁盘保持同步 + .sync() // 与磁盘保持同步 (Sync with disk) .autosave() .build(); cfg.load(); - // 辅助读取函数:优先读取 auth.xxx,其次尝试不带 auth 前缀的样式(兼容不同定义位置) + // 辅助读取函数:优先读取 auth.xxx,其次尝试不带 auth 前缀的样式(兼容不同定义位置) (Helper read function: Prioritize reading auth.xxx, then try style without auth prefix (compatible with different definition locations)) java.util.function.BiFunction getVal = (authKey, altKey) -> { if (cfg.contains(authKey)) return cfg.get(authKey); if (altKey != null && cfg.contains(altKey)) return cfg.get(altKey); return null; }; - // 布尔项 + // 布尔项 (Boolean items) Object v; v = getVal.apply("auth.nomojang.enabled", "nomojang.enabled"); if (v instanceof Boolean) TrueuuidConfig.COMMON.nomojangEnabled.set((Boolean) v); @@ -131,14 +131,14 @@ private static int cmdConfigReload(CommandSourceStack src) { v = getVal.apply("auth.allowOfflineOnFailure", "allowOfflineOnFailure"); if (v instanceof Boolean) TrueuuidConfig.COMMON.allowOfflineOnFailure.set((Boolean) v); - // 数值项 + // 数值项 (Numeric items) v = getVal.apply("auth.timeoutMs", "timeoutMs"); if (v instanceof Number) TrueuuidConfig.COMMON.timeoutMs.set(((Number) v).longValue()); v = getVal.apply("auth.recentIpGrace.ttlSeconds", "recentIpGrace.ttlSeconds"); if (v instanceof Number) TrueuuidConfig.COMMON.recentIpGraceTtlSeconds.set(((Number) v).intValue()); - // 字符串项 + // 字符串项 (String items) v = getVal.apply("auth.timeoutKickMessage", "timeoutKickMessage"); if (v != null) TrueuuidConfig.COMMON.timeoutKickMessage.set(String.valueOf(v)); @@ -151,7 +151,7 @@ private static int cmdConfigReload(CommandSourceStack src) { v = getVal.apply("auth.onlineShortSubtitle", "onlineShortSubtitle"); if (v != null) TrueuuidConfig.COMMON.onlineShortSubtitle.set(String.valueOf(v)); - // 完成反馈 + // 完成反馈 (Completion feedback) src.sendSuccess(() -> Component.literal("[TrueUUID] 配置已从磁盘重载").withStyle(net.minecraft.ChatFormatting.GREEN), false); return 1; } catch (Exception ex) { @@ -160,7 +160,7 @@ private static int cmdConfigReload(CommandSourceStack src) { } } - // 以下方法加入到 `TrueuuidCommands` 类中(同一文件) + // 以下方法加入到 `TrueuuidCommands` 类中(同一文件) (The following methods are added to `TrueuuidCommands` class (same file)) private static int cmdNomojangStatus(CommandSourceStack src) { boolean enabled = TrueuuidConfig.nomojangEnabled(); if (enabled) { @@ -174,7 +174,7 @@ private static int cmdNomojangStatus(CommandSourceStack src) { private static int cmdNomojangSet(CommandSourceStack src, boolean value) { try { TrueuuidConfig.COMMON.nomojangEnabled.set(value); - // 运行时也可记录日志 + // 运行时也可记录日志 (Can also log at runtime) src.sendSuccess(() -> Component.literal("[TrueUUID] NoMojang 已" + (value ? "启用" : "禁用")) .withStyle(value ? net.minecraft.ChatFormatting.GREEN : net.minecraft.ChatFormatting.RED), false); return 1; @@ -263,16 +263,16 @@ private static int run(CommandSourceStack src, String name, copyIfExists(offStats, backupDir.resolve("offline.stats.json")); } - // ==== NBT 合并实现 ==== + // ==== NBT 合并实现 (Merge Implementation) ==== if (Files.exists(offDat)) { if (!Files.exists(premDat)) { Files.move(offDat, premDat, StandardCopyOption.REPLACE_EXISTING); } else { - // 合并 NBT(背包/末影箱) + // 合并 NBT(背包/末影箱) (Merge NBT (Inventory/EnderChest)) mergePlayerDatNBT(premDat, offDat, mergeInv, mergeEnder); } } - // ==== advancements 合并实现 ==== + // ==== advancements 合并实现 (Merge Implementation) ==== if (Files.exists(offAdv)) { if (!Files.exists(premAdv)) { Files.move(offAdv, premAdv, StandardCopyOption.REPLACE_EXISTING); @@ -280,7 +280,7 @@ private static int run(CommandSourceStack src, String name, mergeAdvancementsJson(premAdv, offAdv); } } - // ==== stats 合并实现 ==== + // ==== stats 合并实现 (Merge Implementation) ==== if (Files.exists(offStats)) { if (!Files.exists(premStats)) { Files.move(offStats, premStats, StandardCopyOption.REPLACE_EXISTING); @@ -299,13 +299,13 @@ private static int run(CommandSourceStack src, String name, } private static Optional getEntry(String name) { - // 仅供命令打印 premium,用不到其它字段时可改为直接 getPremiumUuid + // 仅供命令打印 premium,用不到其它字段时可改为直接 getPremiumUuid (Only for command printing premium, can be changed to direct getPremiumUuid when other fields are not used) try { var f = NameRegistry.class.getDeclaredField("map"); f.setAccessible(true); } catch (Throwable ignored) { } - // 简化:复用 getPremiumUuid,并构造一个 Entry + // 简化:复用 getPremiumUuid,并构造一个 Entry (Simplify: Reuse getPremiumUuid and construct an Entry) return TrueuuidRuntime.NAME_REGISTRY.getPremiumUuid(name).map(u -> { NameRegistry.Entry e = new NameRegistry.Entry(); e.premiumUuid = u; @@ -320,7 +320,7 @@ private static void copyIfExists(Path from, Path to) throws Exception { } } - // --- NBT合并:背包/末影箱 --- + // --- NBT合并:背包/末影箱 (NBT Merge: Inventory/EnderChest) --- private static void mergePlayerDatNBT(Path premDat, Path offDat, boolean mergeInv, boolean mergeEnder) throws Exception { CompoundTag prem, off; try (InputStream is = Files.newInputStream(premDat)) { @@ -345,12 +345,12 @@ private static void mergePlayerDatNBT(Path premDat, Path offDat, boolean mergeIn } } - // 合并规则:以 premium 为主,offline中未出现的item补到后面(不会覆盖原物品槽号) + // 合并规则:以 premium 为主,offline中未出现的item补到后面(不会覆盖原物品槽号) (Merge rule: Premium is primary, items not appearing in offline are appended (will not overwrite original item slot numbers)) private static boolean mergeItemListTag(CompoundTag prem, CompoundTag off, String key) { if (!prem.contains(key) || !off.contains(key)) return false; ListTag premList = prem.getList(key, 10); // 10: CompoundTag ListTag offList = off.getList(key, 10); - // 以槽号为主键 + // 以槽号为主键 (Slot number as primary key) Set premSlots = new HashSet<>(); for (int i = 0; i < premList.size(); ++i) { CompoundTag tag = premList.getCompound(i); @@ -373,7 +373,7 @@ private static boolean mergeItemListTag(CompoundTag prem, CompoundTag off, Strin return changed; } - // --- advancements 合并:并集 --- + // --- advancements 合并:并集 (Advancements Merge: Union) --- private static void mergeAdvancementsJson(Path premAdv, Path offAdv) throws Exception { Gson gson = new GsonBuilder().setPrettyPrinting().create(); JsonObject prem, off; @@ -397,7 +397,7 @@ private static void mergeAdvancementsJson(Path premAdv, Path offAdv) throws Exce } } - // 更健壮的 mergeStatsJson 实现,处理 JsonElement 类型差异,避免 ClassCastException + // 更健壮的 mergeStatsJson 实现,处理 JsonElement 类型差异,避免 ClassCastException (More robust mergeStatsJson implementation, handling JsonElement type differences to avoid ClassCastException) private static void mergeStatsJson(Path premStats, Path offStats) throws Exception { Gson gson = new GsonBuilder().setPrettyPrinting().create(); JsonObject prem, off; @@ -411,7 +411,7 @@ private static void mergeStatsJson(Path premStats, Path offStats) throws Excepti for (String cat : off.keySet()) { JsonElement offElem = off.get(cat); - // 如果 premium 中没有该分类,直接拷贝整个元素(无论类型) + // 如果 premium 中没有该分类,直接拷贝整个元素(无论类型) (If premium does not have this category, copy the entire element directly (regardless of type)) if (!prem.has(cat)) { prem.add(cat, offElem); changed = true; @@ -420,7 +420,7 @@ private static void mergeStatsJson(Path premStats, Path offStats) throws Excepti JsonElement premElem = prem.get(cat); - // 两边都是对象 -> 逐条合并(数值累加,非数值保留 premium) + // 两边都是对象 -> 逐条合并(数值累加,非数值保留 premium) (Both are objects -> Merge item by item (accumulate numbers, keep premium for non-numbers)) if (offElem.isJsonObject() && premElem.isJsonObject()) { JsonObject offCat = offElem.getAsJsonObject(); JsonObject premCat = premElem.getAsJsonObject(); @@ -431,7 +431,7 @@ private static void mergeStatsJson(Path premStats, Path offStats) throws Excepti changed = true; } else { JsonElement premVal = premCat.get(key); - // 尝试对原语数值做累加 + // 尝试对原语数值做累加 (Try to accumulate primitive numbers) if (premVal.isJsonPrimitive() && offVal.isJsonPrimitive()) { JsonPrimitive pPri = premVal.getAsJsonPrimitive(); JsonPrimitive oPri = offVal.getAsJsonPrimitive(); @@ -442,17 +442,17 @@ private static void mergeStatsJson(Path premStats, Path offStats) throws Excepti premCat.addProperty(key, a + b); changed = true; } catch (Exception ignored) { - // 若不能以 long 累加则保持 premium 原值 + // 若不能以 long 累加则保持 premium 原值 (If cannot accumulate as long, keep premium original value) } } } - // 其他类型(数组/对象/非数值原语)优先保留 prem,不覆盖 + // 其他类型(数组/对象/非数值原语)优先保留 prem,不覆盖 (Other types (array/object/non-numeric primitive) prioritize keeping prem, do not overwrite) } } prem.add(cat, premCat); } else { - // 类型不一致或都不是对象: - // 若两边都是原语且为数字,则尝试累加(例如少见的数值统计) + // 类型不一致或都不是对象: (Types inconsistent or neither are objects:) + // 若两边都是原语且为数字,则尝试累加(例如少见的数值统计) (If both are primitives and numbers, try to accumulate (e.g. rare numeric stats)) if (premElem.isJsonPrimitive() && offElem.isJsonPrimitive()) { JsonPrimitive pPri = premElem.getAsJsonPrimitive(); JsonPrimitive oPri = offElem.getAsJsonPrimitive(); @@ -463,11 +463,11 @@ private static void mergeStatsJson(Path premStats, Path offStats) throws Excepti prem.addProperty(cat, a + b); changed = true; } catch (Exception ignored) { - // 不可累加则保留 prem + // 不可累加则保留 prem (Cannot accumulate, keep prem) } } } - // 其余情况(类型不一致且 prem 已存在)保持 prem,不覆盖 + // 其余情况(类型不一致且 prem 已存在)保持 prem,不覆盖 (Other cases (types inconsistent and prem exists) keep prem, do not overwrite) } } diff --git a/src/main/java/cn/alini/trueuuid/config/TrueuuidConfig.java b/src/main/java/cn/alini/trueuuid/config/TrueuuidConfig.java index 8f0c311..ccab569 100644 --- a/src/main/java/cn/alini/trueuuid/config/TrueuuidConfig.java +++ b/src/main/java/cn/alini/trueuuid/config/TrueuuidConfig.java @@ -21,23 +21,23 @@ public static void register() { public static long timeoutMs() { return COMMON.timeoutMs.get(); } public static boolean allowOfflineOnTimeout() { return COMMON.allowOfflineOnTimeout.get(); } - // 旧开关:保留兼容,但新策略将更细化 + // 旧开关:保留兼容,但新策略将更细化 (Old switch: Keep for compatibility, but new strategy will be more granular) public static boolean allowOfflineOnFailure() { return COMMON.allowOfflineOnFailure.get(); } public static String timeoutKickMessage() { return COMMON.timeoutKickMessage.get(); } public static String offlineFallbackMessage() { return COMMON.offlineFallbackMessage.get(); } - // 新增:短副标题(用于屏幕 Title 区域) + // 新增:短副标题(用于屏幕 Title 区域) (Added: Short subtitle (for screen Title area)) public static String offlineShortSubtitle() { return COMMON.offlineShortSubtitle.get(); } public static String onlineShortSubtitle() { return COMMON.onlineShortSubtitle.get(); } - // 新增:策略相关 + // 新增:策略相关 (Added: Strategy related) public static boolean knownPremiumDenyOffline() { return COMMON.knownPremiumDenyOffline.get(); } public static boolean allowOfflineForUnknownOnly() { return COMMON.allowOfflineForUnknownOnly.get(); } public static boolean recentIpGraceEnabled() { return COMMON.recentIpGraceEnabled.get(); } public static int recentIpGraceTtlSeconds() { return COMMON.recentIpGraceTtlSeconds.get(); } public static boolean debug() { return COMMON.debug.get(); } - // 新增 nomojang 开关访问器 + // 新增 nomojang 开关访问器 (Added nomojang switch accessor) public static boolean nomojangEnabled() { return COMMON.nomojangEnabled.get(); } public static final class Common { @@ -47,14 +47,14 @@ public static final class Common { public final ForgeConfigSpec.ConfigValue timeoutKickMessage; public final ForgeConfigSpec.ConfigValue offlineFallbackMessage; - // 新增 + // 新增 (Added) public final ForgeConfigSpec.ConfigValue offlineShortSubtitle; public final ForgeConfigSpec.ConfigValue onlineShortSubtitle; - // 新增 nomojang 配置 + // 新增 nomojang 配置 (Added nomojang config) public final ForgeConfigSpec.BooleanValue nomojangEnabled; - // 新增:策略相关 + // 新增:策略相关 (Added: Strategy related) public final ForgeConfigSpec.BooleanValue knownPremiumDenyOffline; public final ForgeConfigSpec.BooleanValue allowOfflineForUnknownOnly; public final ForgeConfigSpec.BooleanValue recentIpGraceEnabled; @@ -74,11 +74,11 @@ public static final class Common { "注意:你当前以离线模式进入服务器;如果你是正版账号,可能是网络原因导致无法成功鉴权,请重新登陆重试。继续游玩,若后续鉴权成功可能会丢失玩家数据。" ); - // 默认短、不占屏 + // 默认短、不占屏 (Default short, does not occupy screen) offlineShortSubtitle = b.define("offlineShortSubtitle", "鉴权失败:离线模式"); onlineShortSubtitle = b.define("onlineShortSubtitle", "已通过正版校验"); - // 策略项 + // 策略项 (Strategy items) knownPremiumDenyOffline = b.comment("一旦该名字成功验证过正版,后续鉴权失败时禁止以离线身份进入。") .define("knownPremiumDenyOffline", true); allowOfflineForUnknownOnly = b.comment("仅对从未验证为正版的新名字允许离线兜底。") @@ -88,7 +88,7 @@ public static final class Common { recentIpGraceTtlSeconds = b.comment("“近期同 IP 成功”容错的 TTL 秒数。建议 60~600。") .defineInRange("recentIpGrace.ttlSeconds", 300, 30, 3600); debug = b.comment("启用调试日志输出").define("debug", false); - // 新增:跳过 Mojang 会话认证(开启后不再通过 sessionserver 验证) + // 新增:跳过 Mojang 会话认证(开启后不再通过 sessionserver 验证) (Added: Skip Mojang session auth (no longer verify via sessionserver when enabled)) nomojangEnabled = b.comment("开启后关闭对 Mojang 会话服务的在线校验逻辑;同 IP 且近期有正版成功的名称按正版 UUID 处理,其余直接按离线进入处理。") .define("nomojang.enabled", false); b.pop(); diff --git a/src/main/java/cn/alini/trueuuid/mixin/client/ClientHandshakeMixin.java b/src/main/java/cn/alini/trueuuid/mixin/client/ClientHandshakeMixin.java index a8b7219..9604a2c 100644 --- a/src/main/java/cn/alini/trueuuid/mixin/client/ClientHandshakeMixin.java +++ b/src/main/java/cn/alini/trueuuid/mixin/client/ClientHandshakeMixin.java @@ -3,7 +3,7 @@ import cn.alini.trueuuid.net.NetIds; import io.netty.buffer.Unpooled; import net.minecraft.client.Minecraft; -import net.minecraft.client.User; // official 映射 +import net.minecraft.client.User; // official 映射 (mapping) import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; import net.minecraft.network.Connection; import net.minecraft.network.FriendlyByteBuf; @@ -33,7 +33,7 @@ public abstract class ClientHandshakeMixin { var profile = user.getGameProfile(); String token = user.getAccessToken(); - // 令牌只在本地使用 + // 令牌只在本地使用 (Token is only used locally) mc.getMinecraftSessionService().joinServer(profile, token, serverId); ok = true; } catch (Throwable t) { diff --git a/src/main/java/cn/alini/trueuuid/mixin/server/ServerLoginMixin.java b/src/main/java/cn/alini/trueuuid/mixin/server/ServerLoginMixin.java index ed4e186..df66eca 100644 --- a/src/main/java/cn/alini/trueuuid/mixin/server/ServerLoginMixin.java +++ b/src/main/java/cn/alini/trueuuid/mixin/server/ServerLoginMixin.java @@ -32,25 +32,24 @@ public abstract class ServerLoginMixin { @Shadow private GameProfile gameProfile; @Shadow private MinecraftServer server; - @Shadow private Connection connection; // 1.20.1 是字段 + @Shadow private Connection connection; // 1.20.1 是字段 (is a field) @Shadow public abstract void disconnect(Component reason); - - // 握手状态 + // 握手状态 (handshake state) @Unique private static final AtomicInteger TRUEUUID$NEXT_TX_ID = new AtomicInteger(1); @Unique private int trueuuid$txId = 0; @Unique private String trueuuid$nonce = null; @Unique private long trueuuid$sentAt = 0L; - // 新增:防止重复处理客户端认证包(同次握手只处理一次) + // 新增:防止重复处理客户端认证包(同次握手只处理一次)(Added: Prevent duplicate processing of client auth packets (only process once per handshake)) @Unique private volatile boolean trueuuid$ackHandled = false; @Inject(method = "handleHello", at = @At("TAIL")) private void trueuuid$afterHello(ServerboundHelloPacket pkt, CallbackInfo ci) { if (this.server.usesAuthentication() || this.gameProfile == null) return; - // 若开启 nomojang,则直接使用本地策略,不向客户端发送会话认证包 + // 若开启 nomojang,则直接使用本地策略,不向客户端发送会话认证包 (If nomojang is enabled, use local policy directly, do not send session auth packet to client) if (TrueuuidConfig.nomojangEnabled()) { String name = this.gameProfile.getName(); String ip; @@ -63,7 +62,7 @@ public abstract class ServerLoginMixin { System.out.println("[TrueUUID] nomojang 模式:跳过 Mojang 会话认证, 玩家: " + (name != null ? name : "") + ", ip: " + ip); } - // 尝试同 IP 的近期容错命中 -> 视为正版 + // 尝试同 IP 的近期容错命中 -> 视为正版 (Try recent same IP grace hit -> Treat as premium) if (TrueuuidConfig.recentIpGraceEnabled() && ip != null) { var pOpt = TrueuuidRuntime.IP_GRACE.tryGrace(name, ip, TrueuuidConfig.recentIpGraceTtlSeconds()); if (pOpt.isPresent()) { @@ -74,24 +73,24 @@ public abstract class ServerLoginMixin { } GameProfile newProfile = new GameProfile(premium, name); this.gameProfile = newProfile; - // 记录成功(保持注册表/缓存一致) + // 记录成功(保持注册表/缓存一致) (Record success (keep registry/cache consistent)) TrueuuidRuntime.NAME_REGISTRY.recordSuccess(name, premium, ip); TrueuuidRuntime.IP_GRACE.record(name, ip, premium); - return; // 直接返回,按正版处理完毕 + return; // 直接返回,按正版处理完毕 (Return directly, premium processing complete) } } } - // 其余情况:直接按离线处理(不阻止进入) + // 其余情况:直接按离线处理(不阻止进入) (Other cases: Treat as offline directly (do not block entry)) if (TrueuuidConfig.debug()) { System.out.println("[TrueUUID] nomojang: 未命中同IP正版记录,按离线方式放行"); } - // 不发送自定义认证包,保持默认的离线行为 + // 不发送自定义认证包,保持默认的离线行为 (Do not send custom auth packet, keep default offline behavior) return; } - // 清理 ack 处理标志(新握手重新可处理) + // 清理 ack 处理标志(新握手重新可处理) (Clear ack handled flag (new handshake can be processed again)) this.trueuuid$ackHandled = false; this.trueuuid$nonce = UUID.randomUUID().toString().replace("-", ""); @@ -173,7 +172,7 @@ public abstract class ServerLoginMixin { reset(); ci.cancel(); return; } - // 幂等保护:如果已经处理过本次握手的 ack,则忽略重复包 + // 幂等保护:如果已经处理过本次握手的 ack,则忽略重复包 (Idempotency protection: If ack for this handshake has been processed, ignore duplicate packets) if (this.trueuuid$ackHandled) { if (TrueuuidConfig.debug()) { System.out.println("[TrueUUID] 重复认证包忽略, txId: " + this.trueuuid$txId); @@ -183,14 +182,15 @@ public abstract class ServerLoginMixin { } this.trueuuid$ackHandled = true; - // 关键:使用异步 API,不在主线程阻塞 + // 关键:使用异步 API,不在主线程阻塞 (Key: Use async API, do not block main thread) try { // 立即取消原始调用(以免继续执行原有逻辑),但不要 reset(),保留状态直到回调完成 + // (Immediately cancel the original call (to avoid executing original logic), but do not reset(); keep state until callback completes) ci.cancel(); SessionCheck.hasJoinedAsync(this.gameProfile.getName(), this.trueuuid$nonce, ip) .whenComplete((resOpt, throwable) -> { - // 始终在主线程处理后续逻辑 + // 始终在主线程处理后续逻辑 (Always process subsequent logic on main thread) server.execute(() -> { try { if (throwable != null) { @@ -211,7 +211,7 @@ public abstract class ServerLoginMixin { var res = resOpt.get(); - // 成功:记录注册表/近期 IP;替换为正版 UUID + 名称大小写矫正 + 注入皮肤 + // 成功:记录注册表/近期 IP;替换为正版 UUID + 名称大小写矫正 + 注入皮肤 (Success: Record registry/recent IP; replace with premium UUID + name case correction + inject skin) TrueuuidRuntime.NAME_REGISTRY.recordSuccess(res.name(), res.uuid(), ip); TrueuuidRuntime.IP_GRACE.record(res.name(), ip, res.uuid()); @@ -249,6 +249,7 @@ public abstract class ServerLoginMixin { } catch (Throwable t) { // 若构造异步调用时报错(极少见),则回退为失败处理并重置 + // (If an error occurs when constructing the async call (very rare), fall back to failure handling and reset) if (TrueuuidConfig.debug()) { System.out.println("[TrueUUID] 启动异步认证时出错: " + t); } @@ -295,7 +296,7 @@ private void handleAuthFailure(String ip, String why) { @Unique private void sendDisconnectWithReason(Component reason) { - // 异步断开,避免主线程卡死 + // 异步断开,避免主线程卡死 (Async disconnect, avoid main thread freeze) new Thread(() -> { try { this.connection.send(new ClientboundLoginDisconnectPacket(reason)); diff --git a/src/main/java/cn/alini/trueuuid/server/AuthDecider.java b/src/main/java/cn/alini/trueuuid/server/AuthDecider.java index 49b6d70..fb9d049 100644 --- a/src/main/java/cn/alini/trueuuid/server/AuthDecider.java +++ b/src/main/java/cn/alini/trueuuid/server/AuthDecider.java @@ -10,7 +10,7 @@ public final class AuthDecider { public static class Decision { public enum Kind { PREMIUM_GRACE, OFFLINE, DENY } public Kind kind; - public UUID premiumUuid; // PREMIUM_GRACE 时填 + public UUID premiumUuid; // PREMIUM_GRACE 时填 (Fill when PREMIUM_GRACE) public String denyMessage; } @@ -19,14 +19,14 @@ public static Decision onFailure(String name, String ip) { boolean known = TrueuuidRuntime.NAME_REGISTRY.isKnownPremiumName(name); - // 1) 已验证过正版的名字:禁止离线回落 + // 1) 已验证过正版的名字:禁止离线回落 (Names already verified as premium: Deny offline fallback) if (known && TrueuuidConfig.knownPremiumDenyOffline()) { d.kind = Decision.Kind.DENY; d.denyMessage = "该名称已绑定正版 UUID,鉴权失败时不允许以离线模式进入。请检查网络后重试。"; return d; } - // 2) 近期同 IP 成功容错:临时按正版处理 + // 2) 近期同 IP 成功容错:临时按正版处理 (Recent same IP success grace: Temporarily treat as premium) if (TrueuuidConfig.recentIpGraceEnabled()) { Optional p = TrueuuidRuntime.IP_GRACE.tryGrace(name, ip, TrueuuidConfig.recentIpGraceTtlSeconds()); if (p.isPresent()) { @@ -36,13 +36,13 @@ public static Decision onFailure(String name, String ip) { } } - // 3) 未知名字:可允许离线兜底 + // 3) 未知名字:可允许离线兜底 (Unknown names: Allow offline fallback) if (TrueuuidConfig.allowOfflineForUnknownOnly() && !known) { d.kind = Decision.Kind.OFFLINE; return d; } - // 4) 否则拒绝 + // 4) 否则拒绝 (Otherwise deny) d.kind = Decision.Kind.DENY; d.denyMessage = "鉴权失败,已禁止离线进入以保护你的正版存档。请稍后重试。"; return d; diff --git a/src/main/java/cn/alini/trueuuid/server/AuthState.java b/src/main/java/cn/alini/trueuuid/server/AuthState.java index a699a28..d98e200 100644 --- a/src/main/java/cn/alini/trueuuid/server/AuthState.java +++ b/src/main/java/cn/alini/trueuuid/server/AuthState.java @@ -7,6 +7,7 @@ /** * 在登录阶段记录“放行离线”的连接;玩家完全进服后消费并提示。 + * (Record "offline fallback" connections during login phase; consume and notify after player fully joins server.) */ public final class AuthState { public enum FallbackReason { TIMEOUT, FAILURE } diff --git a/src/main/java/cn/alini/trueuuid/server/SessionCheck.java b/src/main/java/cn/alini/trueuuid/server/SessionCheck.java index 83a1d57..44b651e 100644 --- a/src/main/java/cn/alini/trueuuid/server/SessionCheck.java +++ b/src/main/java/cn/alini/trueuuid/server/SessionCheck.java @@ -17,6 +17,7 @@ /** * 服务端调用 hasJoined 校验正版并获取最终 UUID 与皮肤属性 + * (Server calls hasJoined to verify premium status and get final UUID and skin properties) */ public final class SessionCheck { private static final HttpClient HTTP = HttpClient.newHttpClient(); @@ -27,7 +28,7 @@ public record Property(String name, String value, String signature) {} public record HasJoinedResult(UUID uuid, String name, List properties) {} private static class HasJoinedJson { - String id; // 无连字符的 UUID + String id; // 无连字符的 UUID (UUID without hyphens) String name; List properties; } @@ -40,6 +41,7 @@ private static class Prop { /** * 异步版本:不阻塞调用线程,返回 CompletableFuture\\> + * (Async version: Does not block calling thread, returns CompletableFuture>) */ public static CompletableFuture> hasJoinedAsync(String username, String serverId, String ip) { String url = "https://sessionserver.mojang.com/session/minecraft/hasJoined" @@ -97,9 +99,9 @@ public static CompletableFuture> hasJoinedAsync(String }); } - // 保留同步方法(若需要)或移除 + // 保留同步方法(若需要)或移除 (Keep synchronous method (if needed) or remove) public static Optional hasJoined(String username, String serverId, String ip) throws Exception { - // 保留原同步实现(或内部调用 hasJoinedAsync().get(),视需要) + // 保留原同步实现(或内部调用 hasJoinedAsync().get(),视需要) (Keep original synchronous implementation (or call hasJoinedAsync().get() internally, as needed)) throw new UnsupportedOperationException("同步 hasJoined 已不推荐使用,请使用 hasJoinedAsync"); } diff --git a/src/main/java/cn/alini/trueuuid/server/SkinRefreshHandler.java b/src/main/java/cn/alini/trueuuid/server/SkinRefreshHandler.java index b09f518..3970d2f 100644 --- a/src/main/java/cn/alini/trueuuid/server/SkinRefreshHandler.java +++ b/src/main/java/cn/alini/trueuuid/server/SkinRefreshHandler.java @@ -19,10 +19,11 @@ /** * 登录后刷新外观,并在“离线放行”时提示玩家;同时显示屏幕标题提示当前模式。 + * (Refresh skin after login, and notify player when "offline fallback" occurs; also display screen title to indicate current mode.) */ @Mod.EventBusSubscriber(modid = Trueuuid.MODID) public class SkinRefreshHandler { - private static final int SUBTITLE_MAX_CHARS = 64; // 保护:过长截断,避免越界 + private static final int SUBTITLE_MAX_CHARS = 64; // 保护:过长截断,避免越界 (Protection: Truncate if too long to avoid out of bounds) @SubscribeEvent(priority = EventPriority.HIGHEST) public static void onLogin(PlayerEvent.PlayerLoggedInEvent event) { @@ -30,26 +31,25 @@ public static void onLogin(PlayerEvent.PlayerLoggedInEvent event) { var server = sp.getServer(); if (server == null) return; - // 1) 登录后一帧刷新外观(强制客户端重拉皮肤) + // 1) 登录后一帧刷新外观(强制客户端重拉皮肤) (Refresh skin one frame after login (force client to re-fetch skin)) server.execute(() -> { var list = server.getPlayerList(); list.broadcastAll(new ClientboundPlayerInfoRemovePacket(List.of(sp.getUUID()))); list.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(sp))); }); - // 2) 判断是否离线放行,并发送聊天提示 + 屏幕标题(副标题使用短文案) + // 2) 判断是否离线放行,并发送聊天提示 + 屏幕标题(副标题使用短文案) (Determine if offline fallback is active, and send chat notification + screen title (subtitle uses short text)) var netConn = sp.connection.connection; // ServerGamePacketListenerImpl.connection var fallbackOpt = AuthState.consume(netConn); if (fallbackOpt.isPresent()) { - // 聊天提示:长文案 + // 聊天提示:长文案 (Chat notification: Long text) String longMsg = TrueuuidConfig.offlineFallbackMessage(); if (longMsg == null || longMsg.isEmpty()) { longMsg = "注意:你当前以离线模式进入服务器;如果你是正版账号,可能是网络原因导致无法成功鉴权,请重新登陆重试。"; } - sp.sendSystemMessage(Component.literal(longMsg).withStyle(ChatFormatting.RED)); - // Title:红色“离线模式”,副标题短文案(黄色) + // Title:红色“离线模式”,副标题短文案(黄色) (Title: Red "Offline Mode", subtitle short text (Yellow)) var title = Component.literal("离线模式").withStyle(ChatFormatting.RED); String shortSubtitle = TrueuuidConfig.offlineShortSubtitle(); var subtitle = Component.literal(clamp(shortSubtitle, SUBTITLE_MAX_CHARS)).withStyle(ChatFormatting.YELLOW); @@ -58,7 +58,7 @@ public static void onLogin(PlayerEvent.PlayerLoggedInEvent event) { sp.connection.send(new ClientboundSetTitleTextPacket(title)); sp.connection.send(new ClientboundSetSubtitleTextPacket(subtitle)); } else { - // 正版模式:绿色标题,副标题短文案(灰色) + // 正版模式:绿色标题,副标题短文案(灰色) (Premium Mode: Green title, subtitle short text (Gray)) var title = Component.literal("正版模式").withStyle(ChatFormatting.GREEN); String shortSubtitle = TrueuuidConfig.onlineShortSubtitle(); var subtitle = Component.literal(clamp(shortSubtitle, SUBTITLE_MAX_CHARS)).withStyle(ChatFormatting.GRAY);