Skip to content

Fabric 1.21.1 移植(对齐 1.10.1-beta.1) - #2

Open
E33EPUS wants to merge 11 commits into
november521:mainfrom
E33EPUS:1.21.1-Fabric
Open

Fabric 1.21.1 移植(对齐 1.10.1-beta.1)#2
E33EPUS wants to merge 11 commits into
november521:mainfrom
E33EPUS:1.21.1-Fabric

Conversation

@E33EPUS

@E33EPUS E33EPUS commented Sep 8, 2026

Copy link
Copy Markdown

Fabric 1.21.1 移植(1.10.1-beta.1 同步版)

main 这一支(NeoForge 1.21.1)移植到 Fabric 1.21.1,功能对齐到上游
v1.10.1-beta.10f9e0a8)。

已验证:./gradlew build 全绿(compileJava、dist 隔离校验、12 份断言测试、remapJar),
专用服务器 runServer 冒烟加载正常,实机跑过完整流程(见下)

为什么是独立 fork 而不是 Architectury

上游是单加载器工程,拆 common/neoforge/fabric 的工程量是数倍于移植本身。独立分支能最快
出可跑版本,也可以直接当作平行分支参考——每个 MC 版本/加载器各占一个分支,和 1.20.1-forge
的现状一致

映射用 officialMojangMappings(Loom 在 remapJar 时自动映射到 intermediary)。这是刻意的:
上游源码是 Mojang 映射写的,换 Yarn 等于整棵树重写类名与方法名,后续每次同步上游都会在
每个文件上冲突

修掉的上游 bug(两个都是 Windows 上的静默失败)

1. 「打开文件夹」在 Windows 上点了没反应

Util.getPlatform().openPath() 在 Windows 上执行
rundll32 url.dll,FileProtocolHandler <uri>,这条命令是给文件与 URL 设计的,对目录
静默失败
:进程正常退出、返回码 0、什么都不弹。实测(数资源管理器窗口):

命令 新窗口
rundll32 url.dll,FileProtocolHandler … 0
explorer.exe … 1

受影响的不止壁纸页——相册与表情页的「打开文件夹」是同一行代码,同样失效

新增 core/client/FolderOpener:Windows 走 explorer.exe,其他平台保留原版 openPath
兜底 AWT Desktop.open,并把相对路径转成绝对路径。三处调用点统一走它

2. 壁纸目录为空时,「打开文件夹」与按钮全都点不动

WallpaperPicker.render() 先算好标题行与按钮的命中(局部 hovered),随后"目录为空"
分支在 return 前写了 this.hoveredIdx = -1把本帧刚算出的命中结果覆盖掉了
壁纸目录为空时(玩家还没放图),那个键的点击判定永远不成立——不弹窗、不打日志。

而"目录是空的"恰恰是玩家最想点它的时刻:他就是来放第一张图的。已改为
this.hoveredIdx = hovered

(同类排查:ChatMediaPicker 的空分支只清 hoveredIdx、不动 openFolderHovered
Gallery 在空分支之前就完成了 hover 判定——两者都没有这个问题。)

3. NetMusic 1.2.x 上网络 CD 放不出声

NetMusic 改过包名:1.2.x 的客户端播放类在 netmusic.audio,1.5.x 挪到了
netmusic.client.audio。上游按 1.5.x 编译写死了新包名,于是 1.2.x 上
MusicPlayManagerNoClassDefFoundError,只在日志里留一行,玩家看到的是
"唱片放进槽位、点了播放没反应"。

NetMusicPlayback 改为反射依次探测两代包名(两代的方法签名与构造器一致,一次查找两边通用)
服务端的 ItemMusicCD 两代同路径——这正是"CD 认得出、只是不响"的原因

新增功能:图片选择器

上游让玩家自己把图放进 config/mcphone/wallpapers/;这里补一个游戏内入口
移植自我们自己的另一个项目(AtomChat,MIT),在壁纸页加了「选择图片」按钮

为什么不用系统原生选择器:Windows 的 GetOpenFileName 既不继承 WS_EX_TOPMOST
也不理会我们设的任何提示,永远待在 z 序最底下——玩家点"选一张图"什么都看不见
所以窗口必须是我们自己的 JFrame(always-on-top)+ JFileChooser + FlatLaf 换皮肤,
带行内缩略图与右侧实时预览。选完复制进壁纸目录,重名挂序号不覆盖

与上游联动 mod 的差异

上游的联动里有一部分在 Fabric 1.21.1 上没有构建,所以这边只能这样:

联动 上游 Fabric 侧
Waystones / Balm ✅(Fabric 版)
MCEF(浏览器) ✅(Fabric 版)
NetMusic ✅(Fabric 版,兼容 1.2.x 与 1.5.x)
Patchouli ✅(Fabric 版)
FTB Quests ✅(走反射,零编译依赖)
Refined Storage(终端 App) ✅(Fabric 版,真实后端
Tom's Simple Storage(终端 App) ✅(Fabric 版,真实后端
Curios 1.21.1 Fabric 无任何构建,"手机挂腰"功能暂缺
AE2 / AE2WTLib(终端 App) 1.21.1 只有 NeoForge 构建(AE2 官方 Fabric 移植到 19.x 未出)。取 NeoForge jar 仅作编译期,运行期 Fabric 上不存在,这两个集成是死代码但无害
Integrated Dynamics ❌ 仅 Forge/NeoForge

需要作者注意的三件事

1. 第三方许可证:FlatLaf(Apache-2.0)

图片选择器的界面外观用了 FlatLafcom.formdev:flatlaf:3.7.2,Apache-2.0),
以未修改的二进制形式 jar-in-jar 打包。Apache-2.0 要求许可证声明随二进制一起分发
所以我把它写进了 src/main/resources/THIRD-PARTY.txt(该文件上游已有,原本只列 JavaMP3)

如果这个 PR 里只合并移植部分、不合并选择器,那这一条不适用——FlatLaf 与选择器可以整体摘掉,
移植其余部分不依赖它

(说明:选择器代码本身移植自 AtomChat,那是 MIT,版权人就是我们;FlatLaf 是它依赖的
第三方库,是 Apache-2.0

2. 版本号与 CI 的 guard-version

build.yml 里那道闸会拦下任何非 bot 手改 mod_version 的提交。移植分支需要自己的版本号
1.10.1-beta.1-fabric.N),和 1.20.1-forge 分支自带 0.12.0-beta.1 是同一个道理,
所以这个 PR 必然触发它。

如果作者决定合并,建议像 1.20.1-forge 那样先建 1.21.1-Fabric 分支,再让 CI 接管
该分支的版本号;PR 里的版本号改动不必保留。

3. 分支命名

合并后建新分支 1.21.1-Fabric(我这边已经用这个名字推了)

工程上的几处 Fabric 特有处理(供评审参考)

  • 网络层:新增 MCphoneNetwork 门面在 Fabric 侧用"候车室"实现——共享阶段只登记编解码并
    挂起 S2C 接收器,core/client/ClientNetworking 在客户端启动时领走注册。这样上游
    1.10.1 那套 registerToServer/registerToClient 的调用面可以逐字保留,S2C 处理函数
    继续住在共享的 *Networking 类里
  • 附件同步:Fabric 1.21.1 的附件 API 没有 sync()(1.4.0+/MC1.21.5 才有),
    PHONE_TERMINAL 的客户端同步用 SyncPhoneTerminalPacket 手工补(写入时发 + JOIN 推初值)。
  • 按键KeyboardHandlerMixin 转发 GLFW 键盘事件;新增可取消的 MouseHandlerMixin
    补上 NeoForge InputEvent.MouseButton.Pre 的取消语义(绑在侧键上的 App 打开时不会顺带
    挥手/放方块)
  • 配置:NeoForge 的 TOML → 自管 JSON(config/mcphone-client.json
    <世界>/serverconfig/mcphone-server.json),键名与语义不变,服务端配置靠
    SyncServerConfigPacket 在 JOIN 时推送
  • dist 隔离:沿用上游 verifyDistIsolation 思路并接进 check,全部客户端类型关在
    client/ 包内
  • 断言测试docs/ 下 12 份带 main() 的测试已挂进 check(含上游 1.10.1 新加的两份
    编解码测试与移植带来的动画测试)

测试情况

  • ./gradlew build:compileJava 0 错误、dist 隔离校验过、12 份断言测试全过、remapJar 出包
  • 专用服务器冒烟:加载正常,无 NoClassDefFoundError
  • 实机(Fabric 1.21.1 + Fabric API 0.116.17):手机开关、设置、壁纸(含新选择器)、
    商店购买、聊天收发图、音乐本地/网络播放、终端 App(RS/TOMS)、副手 HUD、快捷键
    (键盘 + 鼠标侧键 + 组合键)、界面缩放 —— 均通过

后续

如果这个方向有价值,我可以按同样的方式继续跟上游的每次发版(这次同步 1.9.3→1.10.1-beta.1
解了 58 个冲突)。也欢迎指出哪里不符合仓库惯例

Convert the NeoForge mod to a Fabric 1.21.1 Loom project using official
Mojang mappings. Port registry, networking, config, attachments, key
bindings, custom audio stream, and optional deps (Waystones/Balm/MCEF/
NetMusic/Patchouli). Omit Curios and Integrated Dynamics (no Fabric 1.21.1
build). Carry the pure animation classes and SettingsList hover fade.
Keep server/client dist isolation by moving all client networking into
/client packages.
Vanilla @reDIrect on SoundEngine crashed game init with MixinExtras
0.5.0 (bundled in Fabric Loader 0.17.x): FactoryRedirectWrapperMixinTransformer
casts the @reDIrect at value to a single AnnotationNode while javac stores
it as a single-element array, causing ClassCastException. Switch to
@WrapOperation which avoids that transformer and is conflict-friendly.
GuideME and Immersive Engineering have no Fabric 1.21.1 build, so listing
them as missing integrations in the About page is misleading. Remove them
from ReaderApp companion mods, BookSources, and the external book whitelist;
delete the two reflection adapters that can never activate on Fabric.
…cker

1. NetMusic CD playback did nothing on 1.2.x: that release keeps its
   client playback classes in netmusic.audio, while 1.5.x moved them to
   netmusic.client.audio. Compiling against 1.5.2 hardcoded the new
   package, so on 1.2.x MusicPlayManager threw NoClassDefFoundError and
   the only trace was a log line. NetMusicPlayback now resolves both
   package layouts by reflection (both generations share the same
   signatures) and logs which one it found; failure still degrades to
   "this CD will not play" instead of breaking the packet handler.
   ItemMusicCD (the server-side read) never moved, which is why the CD
   was recognised but silent.

2. "Open folder" did nothing on Windows. Util.getPlatform().openPath
   runs rundll32 url.dll,FileProtocolHandler, which is built for files
   and URLs and fails silently on directories (measured: 0 Explorer
   windows opened, while explorer.exe opens 1). Upstream has the same
   code, so this was not introduced by the port. New FolderOpener uses
   explorer.exe on Windows, keeps openPath elsewhere, and falls back to
   AWT Desktop; all three call sites (wallpapers, gallery, stickers) go
   through it.

3. New feature: image picker ported from AtomChat (JFileChooser in our
   own always-on-top JFrame with FlatLaf, inline thumbnails, live
   preview). System native dialogs cannot be raised above Minecraft's
   fullscreen window. Wired into the wallpaper page as "Pick image";
   WallpaperStore.importFile copies the choice into the wallpaper folder
   without overwriting same-named files. FlatLaf is jar-in-jar.

docs/ImagePickerTest.java (19 checks) pins the accepted extensions and
the no-upscaling rule, both of which fail silently otherwise.

Build green: 12 assertion tests, dist isolation, remapJar.
render() computes the header and button hit-tests, then the empty-folder
branch overwrote hoveredIdx with -1 before returning. With no wallpapers
on disk that wiped the hit result, so "Open folder" and the new "Pick
image" button did nothing at all - no window, no log line. An empty
folder is exactly when a player wants those buttons: they are there to
put the first image in.

Only the wallpaper page had this shape. ChatMediaPicker clears
hoveredIdx but leaves openFolderHovered alone, and Gallery computes the
hit before its empty branch, so both were already fine.

Verified explorer.exe really opens a window from a Java process (Explorer
window count 2 -> 3), so FolderOpener itself was sound; the click just
never reached it.
The image picker bundles FlatLaf jar-in-jar; Apache-2.0 requires the
notice to travel with the binary. Also fixes the JavaMP3 path in the
same file (Fabric puts jar-in-jar under META-INF/jars/, not jarjar).
@november521

Copy link
Copy Markdown
Owner

LGTM

@chiba233

chiba233 commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

不大行,暂时不大能合
你等等

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants