用户定向:在 python 层写 switch,调用不同 java 后端(而非一个文件里 switch)。
新增:
mcfold/backends/{__init__,base,gen_1_20,gen_1_21}.py 后端体系与分派
scaffold/java/gen_1_20/ 对 1.20.x client jar 编译
scaffold/java/gen_1_21/ 对 1.21.1 client jar 编译(✅ 实测编译通过)
scaffold/java/VERSION-DIFFS.md 实测差异全表
判据:DeltaTracker 类是否存在(1.21 引入,正是 render 签名变化的根源)
⇒ 比硬编码版本号稳
实测差异共 11 处(21 个检查点里 13 个跨世代完全一致):
setLevel / GameRenderer.render / countRendered* / createPackRepository /
ResourceLocation 构造 / enableDataFixerOptimizations(移除) / User 构造 /
ChunkStatus 包路径 / ClientPacketListener 构造(参数个数语义都变) /
loadEffect(变 private)
为什么必须分后端:DeltaTracker 在 1.20.x jar 中不存在,
两代调用无法共存于一份源码(实测编译报 cannot find symbol)。
1.21.1 反混淆已验证(29MB named jar,DeltaTracker 3 处)
110 lines
3.9 KiB
Python
110 lines
3.9 KiB
Python
"""Java 后端分派 —— 按 MC 版本的 API 世代选择对应的渲染服务实现。
|
||
|
||
为什么用「世代」而不是「版本号」
|
||
--------------------------------
|
||
Java 是**编译期绑定**的:1.21 的 `render(DeltaTracker, boolean)` 里
|
||
DeltaTracker 在 1.20.1 的 jar 中不存在,所以两代的调用**无法写在同一份源码里**
|
||
(实测:世代 B 的调用对 1.20.1 jar 编译直接报 "cannot find symbol")。
|
||
|
||
⇒ 每个世代必须有**独立编译**的 Java 后端,各自对着自己版本的 client jar 编译。
|
||
Python 这层负责选后端 —— 这是「加 case 支持新版本」的落点。
|
||
|
||
实测世代边界(来自官方 client_mappings 逐版本对比)
|
||
--------------------------------------------------
|
||
| 方法 | 1.20.x | 1.21+ |
|
||
|---------------------|-------------------|--------------------------|
|
||
| setLevel | (ClientLevel) | (ClientLevel, Reason) |
|
||
| GameRenderer.render | (float,long,bool) | (DeltaTracker, bool) |
|
||
| 渲染计数 | countRendered-* | 同(1.20.2 起改名) |
|
||
|
||
判据:**DeltaTracker 类是否存在**(1.21 引入的时间抽象,正是签名变化的根源)。
|
||
这比硬编码版本号稳 —— 官方改版本号不影响判据。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import zipfile
|
||
from dataclasses import dataclass
|
||
from pathlib import Path
|
||
|
||
# 世代的权威判据:这个类出现在哪个 version,就是那个世代
|
||
GEN_MARKERS = {
|
||
"gen_1_21": ["net/minecraft/client/DeltaTracker.class"],
|
||
"gen_1_20": [], # 兜底(无判据类 ⇒ 最老的世代)
|
||
}
|
||
|
||
|
||
@dataclass(frozen=True)
|
||
class Gen:
|
||
"""一个 API 世代。"""
|
||
|
||
name: str
|
||
min_version: str # 该世代起始版本(文档用)
|
||
markers: tuple[str, ...] # 判据类(任一存在即属于本世代)
|
||
backend: str # Python 后端模块名
|
||
java_dir: str # Java 源码目录名
|
||
|
||
|
||
GENERATIONS: list[Gen] = [
|
||
Gen(
|
||
name="gen_1_21",
|
||
min_version="1.21",
|
||
markers=tuple(GEN_MARKERS["gen_1_21"]),
|
||
backend="gen_1_21",
|
||
java_dir="gen_1_21",
|
||
),
|
||
]
|
||
|
||
|
||
def detect_generation(client_jar: str | Path) -> Gen | None:
|
||
"""从 client jar 判世代(看判据类是否存在)。找不到则返回 None(属最老世代)。"""
|
||
try:
|
||
with zipfile.ZipFile(client_jar) as z:
|
||
names = set(z.namelist())
|
||
except Exception:
|
||
return None
|
||
for g in GENERATIONS:
|
||
if any(m in names for m in g.markers):
|
||
return g
|
||
return None
|
||
|
||
|
||
def detect_from_store(store: str | Path) -> Gen | None:
|
||
"""从 store 目录找 client jar 并判世代。"""
|
||
store = Path(store)
|
||
for jar in sorted(store.glob("versions/*/*.jar")):
|
||
g = detect_generation(jar)
|
||
if g:
|
||
return g
|
||
return None
|
||
|
||
|
||
def backend_class(client_jar: str | Path | None = None,
|
||
generation: str | None = None):
|
||
"""选中后端类。
|
||
|
||
★ 这是「Python 层 switch」的落点:加新世代只需在下面加一条 elif。
|
||
"""
|
||
if generation is None:
|
||
generation = backend_for(client_jar) if client_jar else "gen_1_20"
|
||
if generation == "gen_1_21":
|
||
from .gen_1_21 import Gen_1_21
|
||
return Gen_1_21
|
||
from .gen_1_20 import Gen_1_20
|
||
return Gen_1_20
|
||
|
||
|
||
def backend_for(client_jar: str | Path) -> str:
|
||
"""判世代并返回后端模块名。"""
|
||
g = detect_generation(client_jar)
|
||
if g is None:
|
||
return "gen_1_20" # 无判据类 ⇒ 最老世代
|
||
return g.backend
|
||
|
||
|
||
def list_generations() -> list[tuple[str, str, str]]:
|
||
"""(世代名, 版本区间, Java 目录) —— 供诊断/文档用。"""
|
||
return [
|
||
("gen_1_20", "1.20.x(实测 1.20.1/1.20.2)", "java/gen_1_20"),
|
||
("gen_1_21", "1.21+", "java/gen_1_21"),
|
||
]
|