
OptiFabric 重铸版
OptiFine在Fabric上,同时加载了Fabric API。
OptiFabric —— 在 Fabric 上运行 OptiFine
一个把 OptiFine 带到 Fabric 上的 Fabric 模组。把 OptiFabric 和 你自己的 OptiFine jar 放进 mods/ 即可,其余的交由它处理——无需手动修改 jar,无需运行 OptiFine 安装器,无需 Forge。
ℹ️ OptiFine 不随本模组捆绑或再分发。请从官方网站获取与你的 Minecraft 版本匹配的构建——1.20.6 和 26.1.2 目前仅有预览构建。
OptiFabric 以每条发布线一个项目的方式开发;各 jar 不可互换使用,每个文件名中都带有对应的 Minecraft 版本:
| 发布线 | Minecraft | Jar | 加载器 | Java |
|---|---|---|---|---|
| 1.20.6 | 1.20.6 | OptiFabric-1.0.0+mc1.20.6.jar |
≥ 0.19.3 | 21+ |
| 1.21.x | 1.21 – 1.21.11(十个发行版) | OptiFabric-1.1.2+mc<version>.jar(1.21 / 1.21.1 为 1.1.0) |
≥ 0.19.5 | 21+ |
| 26.x | 26.1.2 | OptiFabric-Reforged-2.0.0+mc26.1.2.jar |
≥ 0.19.5 | 25 |
为什么需要它
OptiFine 是为原版(以及 Forge)构建的:它的补丁是针对官方混淆名称编译的,而 Fabric 运行在 intermediary 命名空间中。Fabric API 还会注入到许多相同的类中。两者结合在一起时,会以难以诊断的方式产生分歧——不同的构造器形态、不同的合成字段名、被内联掉的辅助方法、对象创建被替换为 OptiFine 自己的子类,等等。
这些分歧并非无关紧要。在重映射器的类路径上没有游戏的情况下进行重映射,会静默地在一个类中留下 35 个未映射的方法,表现为 281 个损坏的抽象契约和 254 个丢失的虚方法重写。OptiFine 的重新编译器会把原版方法变成转发到其自身重载的薄壳——这会把 Fabric API 的注入点从其下方移走,于是 Mixin 在加载时整个类都会失败。OptiFine 还会整体替换类(它的 ChunkOF 区块、它的视频设置界面包括其超类),并重写 Fabric 用于匹配注入点的 lambda 体。
在 26.x 线上,问题的形态变了:Minecraft 26.1 及更新版本是未混淆的——官方名称就是运行时名称,没有 yarn,也没有真正可用于重映射的 intermediary(26.1.2 只发布了 0.0.0 占位符)。因此该线完全不使用映射,运行在 official 命名空间中,并带有自己的模组 id(optifabric_reforged),因为一些模组声明 "breaks": {"optifabric": "*"},而 Fabric Loader 是按 id 进行匹配的——仅更改显示名称是不够的。
它的功能
在启动的最早阶段(加载器的 preLaunch),OptiFabric 会:
- 运行 OptiFine 自己的安装器以提取其针对游戏类的补丁(自 1.21.6 起补丁以 xdelta 差分形式传输;
optifine.Patcher的用法相同); - 去除 volde 化处理,并重建目标被 OptiFine 移动的 lambda(
LambdaRebuilder); - 将补丁从 official 重映射到 intermediary——重映射器的类路径和输入中都包含游戏 jar,因此从超类型继承的成员会保留其映射后的名称;
- 修复 OptiFine 与 Fabric API 之间已知的结构性冲突,每一处都追溯到字节码层面;
- 将结果一分为二:OptiFine 自身的类和资源放在游戏类路径上,被修补的
net/minecraft/**类放入ClassCache; - 将修复后的类交给 Fabric Loader 自己的
GameTransformer,并将所有内容缓存到<game dir>/.optifine/<OptiFine version>/下,以便后续启动复用而不是重新处理(1–2 秒而非 5–7 秒)。
第 2 步和第 3 步在 26.x 线上被跳过,这正是它作为独立项目存在的原因。
替换发生在 Mixin 之前:当某个 Minecraft 类即将被加载时,Loader 会向游戏提供者的 GameTransformer.transform(...) 请求现成的字节码。因此 OptiFabric 交出的是 Mixin 的输入,而非其输出——这就是其他模组针对相同类的 mixin 仍能正常工作的原因。由此产生一个硬性约束:在类被交出之前,任何代码都不得对游戏类进行反射——一次 Class.getMethods() 就会加载那些方法签名中的所有类型,并在本次会话剩余时间内将类固定为原版。
缓存目录包含 cache-format.txt(当前为 26;不匹配则重建全部)、Optifine-mapped.jar(不含 MC 类的 OptiFine,即放到类路径上的内容)以及 Optifine.classes.gz(被修补的 MC 类)。
安装
- 为你的 Minecraft 版本安装一个带有 Fabric Loader 的客户端(1.20.6 需 ≥ 0.19.3,1.21.x 和 26.x 需 ≥ 0.19.5),运行在 Java 21+ 上——或者对于 26.1.2 使用 Java 25,这是游戏本身的要求。
- 将 OptiFabric 和你自有的、对应确切版本的 OptiFine jar 放入该实例的
mods/文件夹。直接放入 OptiFine 安装器 jar 即可——你不需要先运行它的安装器(已提取、带有notch/类的 OptiFine 同样可用)。不要安装两个 OptiFine jar(游戏会报告DUPLICATED),也不要混用来自两条线的 jar。 - 使用 Fabric 配置文件启动游戏——而不是启动器生成的
1.21.x-OptiFine_xxx配置文件,后者会自行注入 OptiFine。成功时 OptiFine 版本会显示在标题界面左上角,其选项会出现在视频设置中。
Fabric API 可以同时加载(本移植版专门为其做了适配)。启用版本隔离时(PCL2 / HMCL),游戏目录和 mods/ 位于 versions/<name>/ 下,.optifine/ 缓存也会创建在那里。
要求
| 1.20.6 | 1.21.x | 26.x | |
|---|---|---|---|
| Minecraft | 1.20.6 | 1.21 – 1.21.11(每个版本一个 jar) | 26.1.2 |
| Fabric Loader | 0.19.3 或更新 | 0.19.5 或更新 | 0.19.5 或更新 |
| Java | 21+(在 25 上测试) | 21+(在 25 上测试) | 25 |
| 端 | 客户端 | 客户端 | 客户端 |
| OptiFine | preview_OptiFine_1.20.6_HD_U_I9_pre1.jar(仅预览) |
你自有的构建,需版本完全匹配 | preview_OptiFine_26.1.2_HD_U_K1_pre2(仅预览) |
| 可选 | Fabric API(支持) | Fabric API(支持) | Fabric API(已在 0.155.3+26.1.2 上测试) |
已验证的内容
每个发行版都通过一条命令、在同一个加载器中进行检查,方式与游戏加载这些类相同:JVM 验证器加上一个 ASM 数据流验证器,覆盖每个被修补的类和每个 OptiFine 类,随后是五个扫描器(mixin 成员引用、@At 注入点、抽象契约 / 丢失的虚方法重写 / 无法解析的引用、invokedynamic 句柄、局部变量捕获)。
| Minecraft | 被修补的游戏类(JVM) | OptiFine 类(JVM) | ASM 验证器 | 扫描器 |
|---|---|---|---|---|
| 1.20.6 | 425 / 425 | — | 0 问题 | 干净 |
| 1.21.3 / 1.21.4 | 440 / 440 · 474 / 474 | 816 / 816 · 812 / 812 | 0 | 仅有被禁用的 Indigo @At 未命中 |
| 1.21.6 / 1.21.7 / 1.21.8 | 487 / 487 · 500 / 500 · 516 / 516 | 820 / 820 · 823 / 823 · 831 / 831 | 0 | 同上 |
| 1.21.9 / 1.21.10 / 1.21.11 | 519 / 519 · 553 / 553 · 570 / 570 | 832 / 832 · 836 / 836 · 874 / 874 | 0 | 同上 |
| 26.1.2 | 567 / 567 | 879 / 879 | 0 | 所有列均为 0(该线未声明 contains_renderer,因此 Indigo 是活跃的) |
游戏内验证:启动、标题界面、单人游戏、多人服务器、方块/区块/物品和实体渲染、着色器(已用 ComplementaryReimagined 验证)、在 1.21.3 – 1.21.11 上的抗锯齿、F3 调试界面,均为无 [ERROR] 会话且无崩溃报告。在 26.1.2 上,Indigo 注册自己的渲染器,而按方块位置生成几何体的模组(LambdaBetterGrass 的优化草、连接纹理)在开着色器的情况下也能正确渲染。
已修复的兼容性问题
以下问题均通过真实崩溃发现,并追溯到字节码层面:
- Fabric API 的
ShaderProgramMixin在super()之前注入 → OptiFine 的委托构造器被重写,内联的标识符创建现在会复制游戏自身的Identifier.ofVanilla(...)调用(包装器正是针对该调用,而new Identifier(...)会绕过它); - OptiFine 留下混淆、描述符不匹配的字段(例如粒子工厂表)→ 按名称、类型和存储值重新对齐,并沿继承层次查找重写;
- OptiFine 在重新编译时内联掉的私有辅助方法 → 恢复原版方法体,使注入重新有目标;
- OptiFine 转为其自身重载转发器的方法(区块构建、模型烘焙、方块轮廓、物品模型)→ 恢复原版方法体——这正是“所有模型烘焙失败”和方块隐形的背后原因;
- OptiFine 重定向到其自身子类的对象创建(
ChunkOF区块对象)→ 一个惰性标记把 Fabric 的NEW注入点放回去; - 一个移植的修复器连同它替换的构造器一起丢弃了字段初始化 → 该构造器不再被替换(此问题曾在两秒后断开多人会话);
- 在不同名称和签名下重新编译的 lambda 体(
lambda$addMainPass$1多了一个参数)→ 恢复原版方法体;当 OptiFine 用自己的 lambda 替换方法引用时,LambdaMethodRefFix将它们重命名回方法名,因为该发行版的 Fabric API 是按 bootstrap 句柄匹配自定义注入点的; - 一个从不存储区段位置的区域构造器(
ChunkCacheOF.renderStart()随后在空区域内 NPE)→ 修复器调用六参数构造器,并从该方法已有的打包 long 推导出参数——1.21 – 1.21.4 的形态则接收ChunkSectionPos对象,两种形态都已处理; - 一个 Fabric 钩子,其上下文从未被 OptiFine 的渲染通道结构填充(
BEFORE_BLOCK_OUTLINE)→StubInjectionTargetFix重命名目标并保留一个同名副本,使该钩子注入到无人调用的代码中:崩溃消失,方块轮廓仍由 OptiFine 绘制; - 同样的技巧在移动方块渲染器钩子上失败,其调用者位于另一个类中并触及了被注入的副本 →
CallSiteRedirectFix也重定向该调用点(若无此项,多人客户端会在约 30 秒后崩溃); - 在没有注册任何渲染器的情况下 F3 崩溃——声明
contains_renderer只会让 Indigo 让路;Fabric 自己的调试条目仍会调用Renderer.get()→RendererApiFallback注册一个惰性占位渲染器; - 占位渲染器的第一个版本破坏了整个会话——它用
Class.getMethods()读取 Fabric API 接口,这会在被修补的类交出之前加载游戏类,将它们永远固定为原版 → 现在该占位符仅根据接口的类文件用 ASM 生成(不做类型解析),通过MethodHandles.findStatic注册,且仅在类就位之后;在 26.1.2 上,渲染器 API 也已迁移到api.client.renderer.v1,旧查找在那里会静默失败; - 没有修复器修改的类不再重新计算其栈帧——全局重写修复器始终报告有变更,因此对每个被修补的类重新计算会把合并的局部类型退化为
java.lang.Object,游戏以VerifyError: Bad type on operand stack拒绝该类; - 26.x:OptiFine 的薄壳留下两个同名方法,而 Fabric API 命名其目标时不带描述符 → 每个位置都被消歧(删除无人调用的重载;用
CallSiteRedirectFix重命名仍被调用的那个并跟踪其调用者)。搞错这一点会表现为LVTGeneratorError: Could not locate method metadata …或Scanned 0 target(s); - 26.x:自行发出四边形的模型静默消失——Fabric 的地形钩子注入到原版区块构建的
BlockPos.betweenClosed循环中,而 OptiFine 自己的编译重载没有这样的循环,因此钩子无处运行。FrapiTesselateBridgeFix+OptifineFrapiBridge将该次曲面细分调用路由通过 Fabric 的渲染器,并把四边形交给 OptiFine 自己的BlockQuadOutput,因此顶点格式、层、光照和着色器属性都保持为 OptiFine 的;只有emitQuads声明在net.minecraft.之外的模型才会被路由(原版模型留在 OptiFine 的路径上,否则着色器会丢失 OptiFine 的额外顶点属性); - 抗锯齿:FXAA 链由游戏的后处理链加载器从
post_effect/解析,因此本模组早期一个“好心”删除该文件的构建会让每次资源重载都记录Resource not found: minecraft:post_effect/fxaa_of_2x.json,且每次抗锯齿切换都以Failed to load post chain失败。现在 OptiFine 自己的链文件按原样保留发布状态,并且在 1.21.9 / 1.21.10 上——其后处理管线从gl_VertexID绘制无属性的全屏三角形,而 OptiFine 的fxaa_of_*.vsh仍读取Position属性(这就是“抗锯齿把屏幕变黑”的 bug)——两个顶点着色器已相应地重写。
完整列表(症状 / 原因 / 修复)见变更日志,每轮的证据见 docs/DEVELOPMENT.md(未混淆线见 docs/PORT_26.x.md)。
已知问题
- 与 Sodium 冲突——两者都是渲染器;不要同时安装。
- 与 RyoamicLights 不兼容——OptiFine 会替换整个视频设置界面(包括其超类),这会使该模组的注入失败,并在界面一打开时崩溃。OptiFine 有内置动态光源(视频设置 → 品质 → 动态光源),因此并不需要它。
- 依赖 FRAPI/indigo 的模组在 1.20.6 和 1.21.x 上不再获得 indigo 的自定义渲染;地形由 OptiFine 渲染,
Renderer.get()返回一个惰性占位符(F3 显示OptifineRendererPlaceholder)。在 26.1.2 上情况相反——Indigo 注册自己的渲染器,这些几何体会被渲染。 - 两个 Fabric API 钩子被有意设为惰性(
BEFORE_BLOCK_OUTLINE事件不会触发;移动方块 FRAPI 路径被绕过)。这两条路径仍能通过原版/OptiFine 正确渲染。 - 1.21.6 / 1.21.7 一启用着色器就崩溃(
ShadersTex.initDynamicTextureNS中的NullPointerException … "multiTex" is null)。这不是着色器包的问题(三个不相关的包崩溃表现一致,且剥离包的自定义纹理也无济于事),也不是本模组的问题:这两个发行版的 OptiFine 预览构建在第一次纹理创建时注入了一个调用,却没有后续构建所执行的setParentTexture关联。为它们提供的全部七个 OptiFine 构建都以相同方式崩溃。不开着色器时,两个发行版均可启动和游玩。 - 26.1.2 需要 Java 25——在 Java 21 上启动会在窗口出现前失败。这是游戏本身的要求,不是本模组的。
- OptiFine 无法看到 Fabric 模组内部的资源——你会在日志中看到
Unknown resource pack type: ...ModNioResourcePack。这是 OptiFine 侧的限制。 - 与你的 OptiFine 版本不匹配的着色器包会记录
[Shaders] Invalid program name: ...、Unknown macro value: IRIS_VERSION或ParseException: Model variable not found: …;这些来自着色器包。 - 仅覆盖上述发行版——其他 Minecraft 版本需要各自单独适配,而 OptiFine 尚未为 1.21.2 / 1.21.5 或 26.1.2 之后的任何版本发布构建。
故障排除
缓存在哪里 / 如何强制重建? <game dir>/.optifine/<OptiFine version>/。删除 .optifine/ 文件夹即可强制重建——缓存格式(26)通常会在升级后自行重建。
为什么日志里找不到 [OptiFabric]? 它的输出发往启动器控制台,而非 logs/latest.log;加载器只把 log4j 输出写到那里。过滤 [OptiFabric] 可以看到准备了多少个类、Loader 接管了多少个。
找不到游戏 jar / 想手动指定路径——添加 -Doptifabric.mc-jar=<path to the vanilla client jar>。
想检查被修补的类——添加 -Doptifabric.extract=true;重映射后的 OptiFine 类会被解包到 .optifine/<version>/optifine-classes/。
卡在加载界面——采集两份线程转储(jstack <pid>,间隔约 15 秒)并对比。堆栈相同且 CPU 平坦意味着真正的停滞;堆栈停留在原生调用(glfwSwapBuffers)中是呈现问题,并且加载时不要最小化窗口(开启垂直同步时渲染线程会在那里阻塞)。
模型/物品/纹理大批消失(日志中满是 Unable to bake … model)——某个 Fabric mixin 未能转换该类;最外层的消息通常会掩盖真正的原因。OptiFine 把原版方法变成转发器是注入点移动的常见原因。
正常且可忽略的警告——[OptiFine] (Reflector) Class not present: net.minecraftforge.* / sun.misc.SharedSecrets(OptiFine 探测 Forge 和旧 JDK)、Failed to locate initialiser injection point in <init>(class_2591,…)(应用 OptiFine 的 BlockEntity 补丁的代价)、[OptiFabric] Resource not found: minecraft:shaders/post/fxaa_of_{2,4}x.json(OptiFine 探测其 1.21.6 之前的链位置;实际使用的链位于 post_effect/),以及 Skipping bad option: lastServer。
报告问题
请附上:
logs/latest.log(如果崩溃还请附上crash-reports/中对应的文件——它以-- OptiFabric --段结尾,列出 OptiFine 版本、jar 状态和重映射后的 jar 路径);- 你的
mods/文件夹列表; - 你的 OptiFine 版本(例如
OptiFine_1.21.11_HD_U_J9)以及你运行的 Minecraft 版本。
许可与致谢
由 Modmuss50 和 Chocohead 移植的 Chocohead/OptiFabric,采用 MPL-2.0 许可;移植文件保留其原始头信息。OptiFine 本身既未包含也未再分发——它是 sp614x 的作品,请从官方网站获取。这是一个社区移植版,与 OptiFine、Fabric 或 Mojang 团队无关联、未获其背书或支持。
正在加载版本记录…
正在加载评论…
评论在新手盒子客户端中发表,这里同步展示。