## 新增两步(原五步 → 七步)
**[6/7] 反射名次表** —— 调 gen_names.py 生成
names.json(named 形态)与 names-intermediary.json(intermediary 形态)
**[7/7] remap named → intermediary** —— 三步:
7a. 复合出 named→intermediary 的 SRG(gen_intermediary_srg.py)
7b. 造一个 **intermediary 形态的 client jar** ← 继承查找的输入,缺它不行
7c. 从它生成继承映射(-H),再用 -h 把我们的 classes remap 过去
## 为什么必须 remap
实 mod(Iris/Sodium 等)的 mixin 注解写的是 **intermediary** 名且不带 refmap。
跑 named 时 42 个 mod 的 464 处 mixin 全部找不到目标(实测)。
⇒「mod 要 intermediary」与「我们的产物是 named」在同一 JVM 互斥,
解法是把**我们的产物** remap 过去(非通用数据由 extern 名次表解决)。
## gen_intermediary_srg.py 的两个实测坑(不处理会出错且症状误导)
① **描述符必须转**。mappings.srg 的 MD 行两侧 desc **都是 official 形式**:
MD: enn/Z ()Laki; net/.../Minecraft/getResourcePackRepository ()Laki;
左 desc 必须转成 named、右转成 intermediary。
只改类名不改 desc 的症状:**类改名成功了、方法却没改** → NoSuchMethodError。
② **必须生成继承映射**。SpecialSource 按 owner+name+desc 精确匹配、
**不查继承链**;而 MC 里大量方法继承自父类/接口
(如 ClientPacketCache.registryAccess 实际声明在 RegistryAccess)。
不带它漏 ~60 处(实测:60 → 12,且剩余 12 全是 JDK 继承方法、
record component、和我们自己那个不该改名的桥类)。
## 另修三处
- **SS_CP 提到全局**:原来在第 3 步的 if 里定义,而第 7 步也要用它 ——
若 client-named.jar 已是最新,第 3 步不进入 if ⇒ 变量为空。
- **LoaderBoot 纳入编译**:launcher_fabric 从没被 build.sh 编译过,
运行期报 ClassNotFoundException: LoaderBoot。
- **common/ 纳入编译**:mcfold/McfoldNames.java 在 common 下,
漏了它 ⇒ remap 后产物里没有这个类 ⇒ NoSuchFieldException(实测踩过)。
- **natives 平铺**(见上一批提交):jar 内是 linux/x64/org/lwjgl/<mod>/lib*.so,
而 LWJGL 按**文件名**找库 ⇒ 保留包路径会 UnsatisfiedLinkError。
## 产物清单(落在内容根 .mcfold/)
classes/ 裸启动 / named
classes-intermediary.jar 加载器 / intermediary
client-named.jar · client-intermediary.jar
names.json · names-intermediary.json
named2intermediary.srg · inheritance.map
classpath.txt · natives/(平铺)· fabric/(8 库)
131 lines
5.0 KiB
Python
131 lines
5.0 KiB
Python
#!/usr/bin/env python3
|
||
"""复合两份映射 → named→intermediary 的 SRG(供 SpecialSource 把我们的产物 remap 过去)。
|
||
|
||
## 为什么需要
|
||
|
||
实 mod(Iris/Sodium 等)的 mixin 注解写的是 **intermediary** 名,且不带 refmap。
|
||
我们跑 named 时,42 个 mod 的 464 处 mixin 全部找不到目标。
|
||
⇒ 「mod 要 intermediary」与「我们的产物是 named」在同一 JVM 互斥,
|
||
解法:把**我们的产物** remap 到 intermediary。
|
||
|
||
## 输入与复合
|
||
|
||
mappings.srg official → named (ProGuard;我们做 client-named.jar 用的)
|
||
intermediary mappings.tiny official → intermediary (Fabric)
|
||
|
||
两者键在**同一套 official 名**上(实测交集 7388/7413),所以:
|
||
named ──(srg 反查)──> official ──(tiny)──> intermediary
|
||
|
||
## ★ 两个实测坑(不处理就会出错,且症状具有误导性)
|
||
|
||
① **描述符必须转**。mappings.srg 的 MD 行两侧 desc **都是 official 形式**:
|
||
MD: enn/Z ()Laki; net/minecraft/client/Minecraft/getResourcePackRepository ()Laki;
|
||
⇒ 左(from)desc 必须转成 named,右(to)desc 转成 intermediary。
|
||
只改类名不改 desc 的症状:**类改名成功了,方法却没改** → 运行期 NoSuchMethodError。
|
||
|
||
② **必须另生成继承映射**(build.sh 里用 -H/-h)。SpecialSource 按 owner+name+desc
|
||
精确匹配,**不查继承链**;而 MC 里大量方法继承自父类/接口
|
||
(如 ClientPacketConnection.registryAccess 实际声明在 RegistryAccess)。
|
||
不带继承映射会漏 ~60 处(实测)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import re
|
||
import sys
|
||
import zipfile
|
||
from pathlib import Path
|
||
|
||
|
||
def load_srg(path: Path):
|
||
"""official → named。返回 (类表, 方法行, 字段行)。"""
|
||
off2named, mds, fds = {}, [], []
|
||
for line in path.read_text(errors="replace").splitlines():
|
||
p = line.split()
|
||
if not p:
|
||
continue
|
||
if p[0] == "CL:" and len(p) == 3:
|
||
off2named[p[1]] = p[2]
|
||
elif p[0] == "MD:" and len(p) == 5:
|
||
mds.append(p)
|
||
elif p[0] == "FD:" and len(p) == 3:
|
||
fds.append(p)
|
||
return off2named, mds, fds
|
||
|
||
|
||
def load_tiny(jar: Path):
|
||
"""official → intermediary。返回 (类表, (kind,owner,desc,name)→intName, (owner,name)→intName)。"""
|
||
off2int_c, mem, fld_by_name = {}, {}, {}
|
||
with zipfile.ZipFile(jar) as z:
|
||
text = z.read("mappings/mappings.tiny").decode("utf-8", "replace")
|
||
for line in text.splitlines():
|
||
p = line.split("\t")
|
||
if not p:
|
||
continue
|
||
if p[0] == "CLASS" and len(p) >= 3:
|
||
off2int_c[p[1]] = p[2]
|
||
elif p[0] in ("METHOD", "FIELD") and len(p) >= 5:
|
||
# METHOD/FIELD <offOwner> <desc> <name> <intName>
|
||
mem[(p[0], p[1], p[2], p[3])] = p[4]
|
||
if p[0] == "FIELD":
|
||
fld_by_name[(p[1], p[3])] = p[4]
|
||
return off2int_c, mem, fld_by_name
|
||
|
||
|
||
def main() -> int:
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--srg", required=True)
|
||
ap.add_argument("--intermediary", required=True,
|
||
help="含 mappings/mappings.tiny 的 jar")
|
||
ap.add_argument("--out", required=True)
|
||
args = ap.parse_args()
|
||
|
||
off2named, mds, fds = load_srg(Path(args.srg))
|
||
off2int_c, tiny_mem, tiny_fld = load_tiny(Path(args.intermediary))
|
||
print(f" official→named cls={len(off2named)} md={len(mds)} fd={len(fds)}")
|
||
print(f" official→int cls={len(off2int_c)} mem={len(tiny_mem)}")
|
||
|
||
def to_named(desc: str) -> str:
|
||
return re.sub(r"L([^;]+);",
|
||
lambda m: "L" + off2named.get(m.group(1), m.group(1)) + ";", desc)
|
||
|
||
def to_inter(desc: str) -> str:
|
||
return re.sub(r"L([^;]+);",
|
||
lambda m: "L" + off2int_c.get(m.group(1), m.group(1)) + ";", desc)
|
||
|
||
out, n_cls, n_mth, n_fld = [], 0, 0, 0
|
||
for off, named in off2named.items():
|
||
if off in off2int_c:
|
||
out.append(f"CL: {named} {off2int_c[off]}")
|
||
n_cls += 1
|
||
|
||
for p in mds:
|
||
o = p[1].rsplit("/", 1)
|
||
n = p[3].rsplit("/", 1)
|
||
if len(o) != 2 or len(n) != 2:
|
||
continue
|
||
k = ("METHOD", o[0], p[2], o[1])
|
||
if k in tiny_mem and o[0] in off2int_c:
|
||
out.append("MD: %s/%s %s %s/%s %s" % (
|
||
n[0], n[1], to_named(p[4]), # ← 左 desc 转 named(坑 ①)
|
||
off2int_c[o[0]], tiny_mem[k], to_inter(p[2])))
|
||
n_mth += 1
|
||
|
||
for p in fds:
|
||
o = p[1].rsplit("/", 1)
|
||
n = p[2].rsplit("/", 1)
|
||
if len(o) != 2 or len(n) != 2 or o[0] not in off2int_c:
|
||
continue
|
||
hit = tiny_fld.get((o[0], o[1]))
|
||
if hit:
|
||
out.append(f"FD: {n[0]}/{n[1]} {off2int_c[o[0]]}/{hit}")
|
||
n_fld += 1
|
||
|
||
Path(args.out).write_text("\n".join(out) + "\n")
|
||
print(f" ✅ named→intermediary: cls={n_cls} mth={n_mth} fld={n_fld} → {args.out}")
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|