import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.launch.knot.Knot; /** * 【加载器后端】用 Fabric 加载器,但不写 mod。 * * 用户定向(2026-09-25): * 「不写 mod」+「用 mod 加载器」+「功能在现有后端里」。 * * 实现关键(全部实测): * 1. Knot.init() 是 **public**,它做完 * 「发现 mod → 建 KnotClassLoader → 应用 mixin → invokeEntrypoints(preLaunch)」 * 后返回,**不**启动官方主循环(那在 GameProvider.launch() 里 —— 我们不调)。 * 2. Knot.loadIntoTarget(name) 把我们的启动类加载进同一个 KnotClassLoader * ⇒ 我们的代码与 mod 共享 classloader 与 mixin 变换,但控制权仍在我们手里。 * * 实测输出: * [loaderboot] ✅ init 返回: net.fabricmc.loader.impl.launch.knot.KnotClassLoader * [loaderboot] - mcfold_loadprobe 1.0 ← mod 被识别 * [loaderboot] - minecraft 1.20.1 * [loaderboot] ✅ 加载到: McfoldRenderServer / CL=KnotClassLoader * {"event":"ready"} ← 我们的渲染服务同时就绪 * * ★ 两个必需条件(缺一则我们的 named 代码无法在加载器下运行): * a) classpath 里放 **反混淆后的** client jar(client-named.jar),不是官方 obf jar * b) -Dfabric.gameMappingNamespace=named * 否则游戏被 remap 成 intermediary,堆栈里是 `enn.`, * 我们的代码会抛 NoClassDefFoundError: net/minecraft/client/Minecraft(实测)。 * * 启动示例: * java -cp "::client-named.jar:<我们的classes>" \ * -Dfabric.gameMappingNamespace=named \ * -Dfabric.modsFolder=/path/to/mods \ * LoaderBoot --gameDir ... --assets ... --assetIndex 1.20.1 * * ★ asm 必须只留一份(用 Fabric 的 9.10.1):1.20.1 自带 9.2 与 9.6 两版, * 与 Fabric 的并存会让 Knot. 抛 "duplicate ASM classes found on classpath"。 */ public class LoaderBoot { /** 我们自己的服务端主类(默认包,与 McfoldRenderServer 同名)。 */ private static final String TARGET = "McfoldRenderServer"; public static void main(String[] args) throws Exception { Knot knot = new Knot(EnvType.CLIENT); // ① 加载 mod + 应用 mixin;不起官方主循环 ClassLoader gameCl = knot.init(args); System.err.println("[loaderboot] KnotClassLoader = " + gameCl.getClass().getName()); // 报告已加载的 mod(诊断用;也是「加载器真的在工作」的证据) try { for (var m : net.fabricmc.loader.api.FabricLoader.getInstance().getAllMods()) { System.err.println("[loaderboot] mod " + m.getMetadata().getId() + " " + m.getMetadata().getVersion().getFriendlyString()); } } catch (Throwable t) { System.err.println("[loaderboot] 读 mod 列表失败: " + t); } // ② 把我们的服务端注入同一个 classloader,然后调用它的 main Class boot = knot.loadIntoTarget(TARGET); System.err.println("[loaderboot] 注入 " + boot.getName() + " 到 " + boot.getClassLoader().getClass().getSimpleName()); boot.getMethod("main", String[].class).invoke(null, (Object) args); } }