Mixin 与底层补丁
基本信息
| 属性 | 值 |
|---|---|
| Mixin 枚举 | core/mixinplugin/Mixins.java:7(implements IMixins) |
| coremod 入口 | core/ModularUICore.java:18(implements IFMLLoadingPlugin, IEarlyMixinLoader) |
| MC 版本声明 | @IFMLLoadingPlugin.MCVersion("1.7.10")(ModularUICore.java:17) |
| 实际注册的 Mixin | 17 |
| ASM 变换器 | 1(ModularUITransformer) |
| Mixin 配置文件 | 3 个(early / 默认 / late) |
| 加载机制 | GTNHLib Mixins 框架(com.gtnewhorizon.gtnhmixins),非 FML 原生 |
功能
加载机制(重要)
src/main/resources/ 下 3 个 mixins.modularui2*.json 的
mixins / client / server 数组全是空的:
// mixins.modularui2.json
{ "required": true, "minVersion": "0.8.5-GTNH",
"package": "com.cleanroommc.modularui.core.mixins",
"refmap": "mixins.modularui2.refmap.json", …,
"mixins": [], "client": [], "server": [] }
真正的 Mixin 列表来自 GTNHLib 的 IMixins 枚举:
| 阶段 | 入口 | 文件 |
|---|---|---|
| EARLY | coremod IFMLLoadingPlugin.getMixins() → IMixins.getEarlyMixins(Mixins.class, loadedCoreMods) |
core/ModularUICore.java:33-35 |
| LATE | ModularUILateMixinLoader(@LateMixin)→ IMixins.getLateMixins(Mixins.class, loadedMods) |
core/mixinplugin/ModularUILateMixinLoader.java:20-22 |
ModularUICore.getMixinConfig()(:28-30)返回 mixins.modularui2.early.json;
ModularUILateMixinLoader.getMixinConfig()(:15-17)返回 mixins.modularui2.late.json。
17 个实际注册的 Mixin
来自 core/mixinplugin/Mixins.java(grep -c 'Mixin"' 计 17 项):
| 阶段 | 组 | Mixin | 行 |
|---|---|---|---|
| EARLY | MINECRAFT(客户端,10 个) | forge.ForgeHooksClientMixin |
:11 |
minecraft.FontRendererAccessor |
:12 |
||
minecraft.GuiAccessor |
:13 |
||
minecraft.GuiButtonMixin |
:14 |
||
minecraft.GuiContainerAccessor |
:15 |
||
minecraft.GuiContainerMixin |
:16 |
||
minecraft.GuiScreenAccessor |
:17 |
||
minecraft.GuiScreenMixin |
:18 |
||
minecraft.MinecraftMixin |
:19 |
||
minecraft.SimpleResourceAccessor |
:20 |
||
| EARLY | MINECRAFT(通用,5 个) | minecraft.ContainerAccessor |
:22 |
minecraft.EntityAccessor |
:23 |
||
minecraft.EntityPlayerMPMixin |
:24 |
||
minecraft.InventoryCraftingAccessor |
:25 |
||
forge.SimpleNetworkWrapperMixin |
:26 |
||
| LATE | THAUMCRAFT(1 个) | thaumcraft.ClientTickEventsFMLMixin |
:29 |
| LATE | NEI(1 个) | nei.RecipeInfoMixin |
:35 |
补丁目标可归为 5 类:
| 类别 | 目标 | 用途 |
|---|---|---|
访问器(*Accessor) |
GuiAccessor、GuiScreenAccessor、GuiContainerAccessor、ContainerAccessor、EntityAccessor、InventoryCraftingAccessor、SimpleResourceAccessor、FontRendererAccessor |
让 MUI2 读取原版私有字段(如 guiLeft/guiTop/xSize) |
| GUI 行为注入 | GuiScreenMixin、GuiContainerMixin、GuiButtonMixin |
拦截输入、派发 KeyboardInputEvent / MouseInputEvent |
| 客户端核心 | MinecraftMixin、ForgeHooksClientMixin |
模板缓冲、键盘重复等 |
| 服务端实体 | EntityPlayerMPMixin |
玩家同步 |
| 网络 | SimpleNetworkWrapperMixin |
放宽封包处理 |
三个 Mixin 组的目标 mod 与冲突回避
Mixins 枚举用 TargetedMod(core/mixinplugin/TargetedMod.java:7)声明依赖:
| 组 | 需要的 mod | 声明行 | 排除的 mod | 排除行 |
|---|---|---|---|---|
THAUMCRAFT |
Thaumcraft |
:33 |
salisarcana |
:32 |
NEI |
NotEnoughItems |
:37 |
—— | —— |
TargetedMod 只有 3 个常量:SALISARCANA("salisarcana")(:9)、
THAUMCRAFT("Thaumcraft")(:10)、NEI("NotEnoughItems")(:11)。
Salis Arcana 的排除有源码注释说明(
Mixins.java:31): 「salis arcana implements the same mixin so our fails」——两者实现同一 Mixin, 同时加载会冲突。
ASM 变换器
core/ModularUITransformer.java:11 实现 IClassTransformer,
在 core/ModularUICore.java:37-40 以
new String[]{"com.cleanroommc.modularui.core.ModularUITransformer"} 注册。
只改写一个类:net.minecraft.network.PacketBuffer
(core/visitor/PacketByteBufferVisitor.java:14),
把 ItemStack 的堆叠数字段从 byte 改成 varint。详见 网络封包。
开发环境检测
ModularUICore.injectData(:52-55)读取 runtimeDeobfuscationEnvironment,
存入静态字段 isDevEnv(:21),供 ASM 变换器选择 MCP 名还是混淆名
(PacketByteBufferVisitor.java:17-22)。
已知缺陷
❌ 缩放光标纹理路径写错,缩放功能静默失效
ClientProxy.java:99 与 :104 从旧资源域读取缩放光标纹理:
// ClientProxy.java:99
BufferedImage img = ImageIO.read(getClass().getClassLoader()
.getResourceAsStream("assets/modularui/textures/gui/icons/cursor_resize_diag.png"));
// ClientProxy.java:104
img = ImageIO.read(getClass().getClassLoader()
.getResourceAsStream("assets/modularui/textures/gui/icons/cursor_resize.png"));
但这两个文件实际位于 modularui2 域:
src/main/resources/assets/modularui2/textures/gui/icons/cursor_resize.png
src/main/resources/assets/modularui2/textures/gui/icons/cursor_resize_diag.png
而 assets/modularui/ 下只有 lang/en_us.lang 和 themes.json,
没有 textures/ 目录。三种验证:
# A. 字面量定位
grep -n 'assets/modularui' src/main/java/com/cleanroommc/modularui/ClientProxy.java
→ 99, 104(且全仓仅此两处引用旧域)
# B. 目录是否存在
ls src/main/resources/assets/modularui/
→ lang, themes.json (无 textures)
# C. 实际文件在哪
find src/main/resources -name 'cursor_resize*'
→ assets/modularui2/textures/gui/icons/cursor_resize.png
→ assets/modularui2/textures/gui/icons/cursor_resize_diag.png
后果:getResourceAsStream 返回 null,ImageIO.read(null) 抛异常,
被 ClientProxy.java:110-113 的 catch (Throwable e) 吞掉,
仅打一行 ModularUI.LOGGER.info("Custom Cursors failed to load.")。
窗口缩放光标在运行时不会生效(setCursorResizeIcon 因此永远拿不到光标对象)。
同时 resizeCursorH/V/Diag/DiagInverse 四个静态字段保持 null(:56-59)。