Files
agentmc/scaffold/build.sh
JianFeeeee e6f8c0e596 build.sh 七步:加 remap 与名字表(加载器路线所需)
## 新增两步(原五步 → 七步)

**[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 库)
2026-09-26 10:18:08 +08:00

192 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# mcfold 构建脚本 —— 把官方客户端 jar 变成「可直接调用的渲染库」。
#
# ★ 版本隔离策略(用户 2026-09-25 强调)
# 产物写入**内容根** store/<loader>-<version>/.mcfold/,不写 /tmp:
# 内容根/.mcfold/client-named.jar 反混淆后的客户端(可只读共享)
# 内容根/.mcfold/classes/ 我们的渲染服务
# 内容根/.mcfold/natives/ LWJGL .so
# 理由:这些都由该版本的 client jar 派生,属「可共享内容」;
# /tmp 是 tmpfs,重启即丢(实测踩过)。实例独占的东西(mods/config/options)
# 一律不进这里。
#
# 用法: ./build.sh <内容根|实例名> <MC版本> [java模式目录]
# ./build.sh /home/newqqagent/mcview/store/vanilla-1.20.1 1.20.1
# ./build.sh /home/newqqagent/mcview/store/vanilla-1.21.1 1.21.1 java/gen_1_21
set -euo pipefail
STORE="${1:?用法: build.sh <内容根> <MC版本> [java目录]}"
MCVER="${2:?需要 MC 版本}"
JAVA_SUB="${3:-}"
HERE="$(cd "$(dirname "$0")" && pwd)"
# 按版本挑世代目录(除非显式指定)
if [ -z "$JAVA_SUB" ]; then
case "$MCVER" in
1.21*) JAVA_SUB="java/gen_1_21" ;;
*) JAVA_SUB="java/gen_1_20" ;;
esac
fi
OUT="$STORE/.mcfold"
WORK="${MCFOLD_BUILD_WORK:-$(mktemp -d /tmp/mcfold-build-XXXXXX)}"
CLIENT_JAR="$STORE/versions/$MCVER/$MCVER.jar"
[ -f "$CLIENT_JAR" ] || { echo "找不到客户端 jar: $CLIENT_JAR"; exit 1; }
mkdir -p "$OUT/classes" "$OUT/natives" "$WORK"
echo "[build] 版本 $MCVER 世代 $JAVA_SUB"
echo "[build] 输出 → $OUT"
# ★ SpecialSource 的 classpath 在**这里**提前建好(不在第 3 步的 if 里):
# 第 7 步「remap 我们的产物到 intermediary」也要用它,
# 而那时若 client-named.jar 已是最新,第 3 步不会进入 if ⇒ 变量会空。
SS=$(find /home/minecraft/libraries -name "SpecialSource-*.jar" 2>/dev/null | head -1)
GUAVA=$(find /home/minecraft/libraries -name "guava-*.jar" ! -name "*sources*" 2>/dev/null | head -1)
for a in asm asm-commons asm-tree; do
[ -f "$WORK/$a.jar" ] || curl -sL -o "$WORK/$a.jar" \
"https://repo1.maven.org/maven2/org/ow2/asm/$a/9.7/$a-9.7.jar"
done
[ -f "$WORK/joptsimple.jar" ] || curl -sL -o "$WORK/joptsimple.jar" \
"https://repo1.maven.org/maven2/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar"
SS_CP="$SS:$WORK/joptsimple.jar:$WORK/asm.jar:$WORK/asm-commons.jar:$WORK/asm-tree.jar:$GUAVA"
# ── 1. 官方映射 ────────────────────────────────────────────────
if [ ! -s "$WORK/client_mappings.txt" ]; then
URL=$(python3 - "$STORE/versions/$MCVER/$MCVER.json" <<'PY'
import json,sys
print(json.load(open(sys.argv[1]))['downloads']['client_mappings']['url'])
PY
)
echo "[1/7] 下载官方映射..."
curl -sL -o "$WORK/client_mappings.txt" "$URL"
fi
echo "[1/7] 映射 $(wc -l < "$WORK/client_mappings.txt") 行"
# ── 2. ProGuard → SRG ─────────────────────────────────────────
echo "[2/7] 转换映射格式..."
python3 "$HERE/pg2srg.py" "$WORK/client_mappings.txt" "$WORK/mappings.srg"
# ── 3. 重映射客户端 jar ───────────────────────────────────────
NAMED="$OUT/client-named.jar"
if [ ! -f "$NAMED" ] || [ "$CLIENT_JAR" -nt "$NAMED" ]; then
echo "[3/7] 重映射(1-3 分钟)..."
[ -n "$SS" ] || { echo "找不到 SpecialSource"; exit 1; }
java -Xmx3G -cp "$SS_CP" net.md_5.specialsource.SpecialSource \
-i "$CLIENT_JAR" -o "$NAMED" -m "$WORK/mappings.srg"
else
echo "[3/7] client-named.jar 已是最新"
fi
# ── 4. classpath(去 asm 重复,Fabric 需要) + natives ────────
# classpath.txt **只含第三方 libraries** —— client-named.jar 与 classes/ 由使用方拼接
# (build.sh 自己在下面拼,测试/后端则从各自路径取)。
# ★ 别在这里把 named/classes 也写进去:那会让「libraries 清单」与「运行 classpath」
# 两个概念混成一列,而 Fabric 路线需要把 asm 从 libraries 里剔掉后**另行**拼 8 个库。
find "$STORE/libraries" -name "*.jar" ! -name "*-sources.jar" ! -name "*natives*" 2>/dev/null \
| grep -vE "minecraftforge|neoforge" > "$OUT/classpath.txt"
NATLIST=$(find "$STORE/libraries" -name "*natives-linux*.jar" 2>/dev/null)
if [ -n "$NATLIST" ]; then
rm -rf "$OUT/natives"; mkdir -p "$OUT/natives"
# ★ 必须**平铺**。natives jar 里的路径是 linux/x64/org/lwjgl/<module>/lib*.so,
# 而 LWJGL 是经 java.library.path/LD_LIBRARY_PATH 按**文件名**找库的 ——
# 嵌套一层的目录它**找不到**(实测 UnsatisfiedLinkError: Failed to locate library: liblwjgl.so)。
while read -r j; do
[ -f "$j" ] || continue
unzip -o -q "$j" -d "$OUT/natives.tmp" 2>/dev/null || true
done <<< "$NATLIST"
find "$OUT/natives.tmp" -name '*.so' -exec cp {} "$OUT/natives/" \; 2>/dev/null
rm -rf "$OUT/natives.tmp"
fi
echo "[4/7] 库 $(wc -l < "$OUT/classpath.txt") 个, natives $(find "$OUT/natives" -name '*.so' | wc -l) 个"
# ── 5. 编译渲染服务 ───────────────────────────────────────────
echo "[5/7] 编译 $JAVA_SUB ..."
CP="$(paste -sd: "$OUT/classpath.txt"):$NAMED:$OUT/classes"
find "$HERE/$JAVA_SUB" "$HERE/common" -name "*.java" > "$WORK/srcs.txt"
# ★ LoaderBoot(加载器启动器)也要编进来:它是 launcher="fabric" 的主类,
# 少了它运行期报 ClassNotFoundException: LoaderBoot。
find "$HERE/launcher_fabric" -name "*.java" >> "$WORK/srcs.txt"
javac -nowarn -proc:none -cp "$CP" -d "$OUT/classes" @"$WORK/srcs.txt"
# ── 6. 反射名次表(extern 参数;B 方案)──────────────────────
#
# 为什么需要:我们的代码要对 MC 的**私有字段/方法**走反射,而反射参数是**字符串**:
# Minecraft.class.getDeclaredField("resourceManager")
# remap 只改常量池里的符号引用,**改不了字符串** ⇒ 切到 intermediary 后
# `NoSuchFieldException`(实测撞到过)。
#
# 做法:源码写**语义路径**,运行时查 `McfoldNames` 读的次表。
# 表分两份:named(自查;物理名=末段)与 intermediary(需映射)。
if [ -f "$HERE/gen_names.py" ]; then
INTERMEDIARY_JAR=$(find "$OUT/fabric" -name 'intermediary-*.jar' 2>/dev/null | head -1)
echo "[6/7] 生成反射名次表 ..."
if [ -n "$INTERMEDIARY_JAR" ]; then
python3 "$HERE/gen_names.py" --srg "$WORK/mappings.srg" \
--intermediary "$INTERMEDIARY_JAR" \
--java "$HERE/$JAVA_SUB" "$HERE/common" \
--out "$OUT/names-intermediary.json" --namespace intermediary || true
fi
# named 形态:物理名就是末段,无需映射(但仍然生成,让调用方只传路径)
python3 "$HERE/gen_names.py" --srg "$WORK/mappings.srg" \
--intermediary "${INTERMEDIARY_JAR:-$NAMED}" \
--java "$HERE/$JAVA_SUB" "$HERE/common" \
--out "$OUT/names.json" --namespace named || true
fi
# ── 7. remap:named → intermediary(加载器路线需要)──────────
#
# ★ 为什么必须 remap:实 mod(Iris/Sodium 等)的 mixin 注解里写的是
# **intermediary** 名(@Mixin(net.minecraft.class_310)),且不带 refmap。
# 我们跑 named 时那 464 处 mixin 全部找不到目标(42 个 mod)。
# ⇒ 「mod 要 intermediary」与「我们的产物要 named」在同一 JVM 互斥,
# 解法是把**我们的产物** remap 过去(非通用数据走 extern → 见第 6 步)。
#
# 两个细节都是实测必需:
# ① srg 的 desc 两侧都是 official 形式 ⇒ 左 desc 必须自己转成 named,
# 否则 SpecialSource 匹配不上(症状:类改名了、方法没改 → NoSuchMethodError)
# ② 必须带 -h 继承映射:SpecialSource 按 owner 精确匹配、**不查继承链**,
# 而 MC 里大量方法继承自父类/接口(如 ClientPacketHandler.registryAccess
# 实际声明在 RegistryAccess) ⇒ 不带就把 ~60 处漏掉
if [ -f "$HERE/gen_intermediary_srg.py" ]; then
echo "[7/7] remap named → intermediary ..."
python3 "$HERE/gen_intermediary_srg.py" --srg "$WORK/mappings.srg" \
--intermediary "${INTERMEDIARY_JAR:-}" --out "$OUT/named2intermediary.srg" || true
if [ -s "$OUT/named2intermediary.srg" ]; then
# 7a. 造一个 intermediary 形态的 client jar —— 它是继承查找的输入
java -Xmx3G -cp "$SS_CP" net.md_5.specialsource.SpecialSource \
-i "$NAMED" -o "$OUT/client-intermediary.jar" \
-m "$OUT/named2intermediary.srg" > /dev/null 2>&1 || true
# 7b. 从它生成继承映射(-H)
[ -f "$OUT/client-intermediary.jar" ] && \
java -Xmx3G -cp "$SS_CP" net.md_5.specialsource.SpecialSource \
-i "$OUT/client-intermediary.jar" -o "$WORK/throwaway.jar" \
-m "$OUT/named2intermediary.srg" -H "$OUT/inheritance.map" > /dev/null 2>&1 || true
# 7c. remap 我们的 classes(带继承映射)
if [ -f "$OUT/inheritance.map" ]; then
( cd "$OUT/classes" && jar cf "$WORK/classes-named.jar" . )
java -Xmx3G -cp "$SS_CP" net.md_5.specialsource.SpecialSource \
-i "$WORK/classes-named.jar" -o "$OUT/classes-intermediary.jar" \
-m "$OUT/named2intermediary.srg" -h "$OUT/inheritance.map" > /dev/null 2>&1 || true
echo " 产物: $OUT/classes-intermediary.jar"
fi
fi
fi
echo
echo "✅ 构建完成(产物在内容根,不是 /tmp)"
echo " 内容根 : $STORE"
echo " 产物 : $OUT"
echo " named : $OUT/client-named.jar"
echo " classes: $OUT/classes (裸启动 / named)"
echo " int : $OUT/classes-intermediary.jar(加载器 / intermediary)"
echo " 次表 : $OUT/names.json · $OUT/names-intermediary.json"
echo
echo "运行(渲染服务从 stdin 读 NDJSON):"
echo " 裸启动: java -cp \"\$(paste -sd: $OUT/classpath.txt):$NAMED:$OUT/classes\" -Djava.library.path=$OUT/natives McfoldRenderServer ..."
echo " 加载器: java -cp \"<基础库去asm>:<fabric 8库>:$STORE/versions/$MCVER/$MCVER.jar:$OUT/classes-intermediary.jar\" \\"
echo " -Dmcfold.names=$OUT/names-intermediary.json -Dfabric.modsFolder=<实例>/mods LoaderBoot ..."
echo
echo "★ 实例目录必须是**实例独占**的那个(mods/config/options 的边界),"
echo " 不要指向别的版本或别的加载器的实例 —— 那正是「多实例共用 mods」的崩因。"
echo " 同一个版本可以有多个实例(装不同 mod)⇒ 实例目录与内容根**同级**。"