新增 cmd=blockinfo(两代后端编译通过、实测):
返回 block 名(含 mod 方块)、stateId、属性、isAir/isSolid、
光照/硬度、贴图缺失的程序化判据、是否来自 mod。
★ 修掉一个真实缺陷(附录 D 的语义区分落到了实处):
未加载区块原先返回 void_air(known=true),实测发现两个 API 都不可用:
- ClientLevel.hasChunk() 反编译实测**恒返回 true**
- ChunkSource.hasChunk() 走 getChunk(...,false),而 ClientChunkCache
覆写后在该参数下返回 emptyChunk(占位,非 null)⇒ 也恒 true
正解:getChunk(..., requireChunk=**false**) 判 null
(反编译:return requireChunk ? emptyChunk : null;)
⇒ 现在未加载明确回 known=false + reason=chunk_not_loaded
实测(1.20.1):
草地 → minecraft:grass_block props={'snowy':'false'} missing=False
原木 → minecraft:oak_log props={'axis':'y'}
空气 → minecraft:air
远处 → known=false reason=chunk_not_loaded ← 与空气区分
实测(1.21.1 + mod):
mts:mtsofficialpack.fancydirt_block mod=True tex=mtsofficialpack:block/fancydirt
createbigcannons:steel_block mod=True tex=createbigcannons:block/steel_block
⇒ mod 方块同样能查到正确贴图名与 fromMod 标记
顺带修 gen_1_21 两处世代差异(ChunkStatus 换包、bridge 编译顺序)。
测试:69 项全绿(新增 test_get_block_info 6 条判据)
68 lines
3.3 KiB
Java
68 lines
3.3 KiB
Java
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.<init>`,
|
||
* 我们的代码会抛 NoClassDefFoundError: net/minecraft/client/Minecraft(实测)。
|
||
*
|
||
* 启动示例:
|
||
* java -cp "<vanilla基础库(去asm)>:<fabric的8库>: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.<clinit> 抛 "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);
|
||
}
|
||
}
|