"""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,就是那个世代 __all__ = ["Gen_1_20", "Gen_1_21", "Gen", "GENERATIONS", "detect_generation", "detect_from_store", "backend_class", "backend_for", "list_generations"] GEN_MARKERS = { "gen_1_21": ["net/minecraft/client/DeltaTracker.class"], "gen_1_20": [], # 兜底(无判据类 ⇒ 最老的世代) } from .gen_1_20 import Gen_1_20 # noqa: E402 from .gen_1_21 import Gen_1_21 # noqa: E402 @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": return Gen_1_21 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"), ]