7.1 KiB
7.1 KiB
BOOT_NOTES (agent "boot", 2026-10-19)
Goal: the deobfuscated Prestige client boots under Fabric (dev client, ./gradlew runClient), Prestige.init() completes
and the UI bridge binds to the live modules.
Findings (root causes, in order of discovery)
- Two copies of the client were loaded.
build.gradlehasimplementation files("client/work/client-runtime.jar")(a Yarn-named jar on the dev classpath). A jar inrun/modsis a second copy (loaded as a mod and remapped intermediary->named by Fabric Loader at runtime). Whichever won the race was used, so every earlier "fix" that only went intorun/modsseemed flaky (the log showed classes loaded fromclient/work/client-runtime.jar, static keys = 0). Decision: the classpath jarclient/work/client-runtime.jaris the one used by the dev client;run/modsstays empty. (If you want to userun/modsinstead, the jar must be INTERMEDIARY-named incl. intermediary AW:work/client-boot.jar, andbuild.gradle'simplementation files(...)line must be removed. Loom's loader remaps mods-folder jars itself.) - ZKM-like key generator is stateful. Every class (358 of them) computes its decryption key in
<clinit>throughhc.a(seed1, seed2, lookupClass).a(const)(hb/hc= an interpreter with shared, mutable cached objects:this.a = a ^ arg ^ d, instance cache sorted byidentityHashCode...). The result depends on how often / in which order the generator was called before, so the values obtained under Fabric (different class-init order) differ from the values the isolated child JVMs ofIndyResolvesee (e.g.cJ.g= 0 / 182876734801923 instead of 22310984220293). Wrong key -> wrong table index ->Index 184919 out of bounds for length 81inbO.a. - The old
IndyResolve"clinit cut" (agotoover the table initialisation) was not the real problem for these classes (only for the tables' decoded content); but it also removed the table init, so it is now disabled for the runtime jar. FixFlowrecomputed frames withgetCommonSuperClass -> Objectfallback =>VerifyError(Objectnot assignable toPlayerEntity). Now it resolves common super classes through a URLClassLoader over MC + libs + the jar.
Fix (pipeline client/tools/build_runtime.sh, replaces the previous ad-hoc jars)
IndyResolve -Dno.clinitcut=true: no clinit cut. InsteadkeyFix()replaces, in every<clinit>,ldc s1; ldc s2; MethodHandles.lookup().lookupClass(); hc.a(JJObject); ldc c; ha.a(J)Jbyldc <value recorded by the isolated child run>(IndyRec.recH->info.rec["H"+ordinal]). Result: 357 of 358 classes get the constant; the whole table init (h/iarrays,a()V, DES decrypt,ldctables) stays intact and runs at run time. The 1 remaining class isgf(no child value; see below).- Module constructors are hooked:
PatchBootstrapinsertsdev.prestige.ui.bridge.Bootstrap.pre(this)aftersuper()in all 122 subclasses ofdV(the native module normally injects the settings before the ctor body runs). FixFlow(frames,getCommonSuperClassvia real classes) ->work/client-boot.jar(intermediary-named, usable in a mods folder for Prism, with the Bootstrap hook; the hook class lives inprestige-ui).- tiny-remapper intermediary->named +
fix_aw.py->work/client-runtime.jar(Yarn-named; this is what Gradle uses). -Xverify:allcheck of all 708 classes standalone:client/work/t2/V.java(ok=708 bad=0).
Bridge
Bridge.bind()is called from the first client tick (Entry). The manager (Prestige.d, classcJ) list is empty afterPrestige.init()(modules are registered by the native module), soBridge.buildModulesconstructs every class ofsrc/main/resources/prestige_ui/manifest_live.json(copied fromliberate/work/deobf/manifest_live.json), callsBootstrap.post(m)and registers the list with the manager'sa(List)setter. Bootstrap = port ofwork/deobf/gui/zkminit/Bootstrap.java(packagedev.prestige.ui.bridge).
Run
bash client/tools/build_runtime.sh # ~2 min; writes work/client-boot.jar (intermediary) + work/client-runtime.jar (yarn)
bash client/tools/boot_test.sh 200 # kills stale dev clients, runs ./gradlew runClient (timeout), copies the log to run/logs/boot_*.log
Log lines to look for: [prestige-ui] bridge bound: 121 modules.
Result (verified 15:20)
Prestige.init()completes (no STDERR in the log),Prestige.dmanager exists; its module list is empty (native registers it).[prestige-ui] bridge: built 121 modules from manifest, failed 0/[prestige-ui] bridge bound: 121 modules(log:client/work/boot_success_latest.log).- UI click test (
run/cmd/ui.txtcommand file,-PuiCmd=; new dev commandspointmod <module>/state <module>,ui/DevCmd.java):Aim Assist ui=false live=false-> click on its toggle ->ui=true live.a()=true enabledSetting.value=true-> click -> false -> click -> true. Screenshotclient/work/boot_toggle_on.png(header "1 enabled", Aim Assist switch on). Note: the very first click after opening is sometimes ignored (earlier run); a 40-frame wait afteropenmakes it reliable.
Not done / caveats
- Class
gfhas no recorded key value (child run produced no value) -> its<clinit>still calls the statefulhcgenerator (KEYFIX-NOVALUE dev/zprestige/prestige/gf 0inwork/boot_indy.report.txt). Not hit during boot; check when it is used. - Module behaviour (onEnable hooks, event bus dispatch, rendering) is not verified; only construction + enable flag round trip.
- The enable flag round trip goes through the module's
dMsetting (Bridge.LiveModule.setEnabled);onEnable/onDisableside effects depend on how the original dM listener fires (not checked). run/modsis empty on purpose (see Finding 1).work/client-runtime.pre-boot.jaris the previous classpath jar (backup).- Source tree compile (client/src) was not touched.
Update (agent "rename", 2026-10-19 16:45)
- API surface renamed by bytecode:
docs/mapping.tsv(applied bytools/rename.sh= tiny-remapper +Widen, called frombuild_runtime.shandbuild_source.sh). dV->Module, i->Category, dK->Setting, dU->SettingHolder, dM/dN/dO/dP/dQ/dR/dS/dL/dT -> Bool/Color/Float/Int/Range/Enum/Multi/ Keybind/TextSetting, bO->EventBus, cJ->ModuleManager, e_->MenuModule, q->Gamemode, bP->Subscribe, Prestige.b/d -> eventBus/moduleManager, plus the fields/methods the UI uses (name/description/value/min/max/low/high/options/selected/toggleMode, getModules/setModules, isEnabled ...). The 121 module classes are mapped todev.zprestige.prestige.module.<category>.<PascalCaseName>(section generated bytools/gen_module_mapping.py). - Bridge/Bootstrap use the new names; manifest class/prop names are rewritten from
docs/manifest_live.obf.jsonbytools/rename_manifest.py. - Evidence:
work/boot_rename_success.log(API rename, jar route),work/boot_jar_modules_renamed.log(+ module classes renamed),work/boot_source_modules_renamed.log(source route,-PwithClient=true); all showbridge bound: 121 modulesand the Aim Assist toggle round trip. - Source route: see
TODO_COMPILE.md.