Proven by paranoid runs over lorep (17 classes, fully green), LoVisual (4081 classes) and oc2r (1278 classes) with per-class java -Xverify:all, each fix first reproduced as a red test: - merge: Top absorbs instead of Top-wins (javac declares Top for slots unwritten on some inflow; precise types reject those edges). Regression: loop_head_keeps_top_for_loop_local. - emit: collapse [Long|Double, Top] pairs on operand stacks (javac writes one entry; the pair is rejected). Regression: wide_stack_join_emits_single_entries. - xfer ArrayLoad: push the wide pair, fixing lcmp underflow. Regression: array_load_wide_pushes_pair. - xfer <init>: convert only the receiver's own allocation site instead of every Uninitialized (nested construction clobbered the outer object). Regression: nested_construction_keeps_outer. - xfer ANewArray: array operands gain one dimension ([B] -> [[B) instead of an illegal L-wrap ([L[B;). Covered by the same test. - jar load: duplicate class names (multi-release module-info twins) keep the first occurrence instead of crashing save. Test: duplicate_class_names_keep_first. - strings/constants: skip methods past MAX_METHOD_INSNS (8000) so giants cannot overflow the 64KB writer ceiling. Tests: oversized_methods_are_skipped in both passes. - merge errors now name both types being merged. Also: em-dash removed repo-wide (724 spots, rule added to todo.md), allow-lints only in tests (scratch example deleted), code comments migrated to English in every file this change touches.
5.2 KiB
5.2 KiB
Payload-раннер EvoVis: как мод попадает в игру
Разбор по коду (байт-код evo-agent.jar, payload.json, runtime.json, структура
evojdk_x/). Источник: javap -p -constants -cp mod/evo-agent.jar ….
Участники
| артефакт | роль |
|---|---|
mod/payload.json |
манифест: version, file, sha256, size |
mod/evovis.payload (47.5 MB) |
зашифрованный jar мода (AES-GCM) |
mod/evo-agent.jar (60 KB) |
Java-agent: расшифровывает payload и подсовывает его Fabric |
mod/evojdk-win-x64.zip → bin/java.dll, classes/…/java/lang/EvoShare.class |
подменённый JVM: класс java.lang.EvoShare.compute(...) отдаёт вторую долю ключа |
mod/jar/META-INF/jars/*.jar (7 шт.) |
вложенные зависимости, распаковываются агентом |
mod/jar/META-INF/evo/native/{win-x64,win-arm64,macos-x64,macos-arm64}/evoguard.dll|dylib |
нативная защита мода (внутри уже загруженного мода) |
Манифест (payload.json)
{ "version": "2026.10.1.994", "file": "evovis.payload",
"sha256": "e118274a92181e2a8ef04d2f6c45c4f6d4a10b87119d9d41c6f8e39b1bd5f485",
"size": 47491101 }
Лаунчер качает payload.json → сверяет sha256 → качает evovis.payload,
evo-agent.jar, evojdk-*.zip → поднимает JVM с агентом.
Конфигурация агента (из констант EvoAgent)
| константа | значение |
|---|---|
MOUNT_NAME |
"evovis" |
| payload | свойство evo.agent.payload / env EVO_AGENT_PAYLOAD |
| ключ | свойство evo.agent.key / env EVO_AGENT_KEY (hex, 32 байта) |
| токен / hwid | env EVO_TOKEN, EVO_HWID |
| вложенные библиотеки | evo.agent.libraries / EVO_AGENT_LIBRARIES, каталог libraries |
Манифест агента: Premain-Class: evo.agent.EvoAgent,
Launcher-Agent-Class: evo.agent.EvoAgent,
Can-Redefine-Classes: true, Can-Retransform-Classes: true.
Цепочка запуска
laунчер
├─ sha256(payload.json) → качает evovis.payload / evo-agent.jar / evojdk
├─ env: EVO_AGENT_PAYLOAD, EVO_AGENT_KEY, EVO_TOKEN, EVO_HWID, EVO_AGENT_LIBRARIES
└─ java -javaagent:evo-agent.jar … (свой evojdk, в нём java.lang.EvoShare)
│
▼
EvoAgent.premain/agentmain(args, inst)
├─ keyProvider(): LocalKeyProvider(hex-ключ из env) + JvmShare.share(hwid)
│ └─ NativeJvmShare → reflect вызов java.lang.EvoShare.compute(...) → 32 байта
├─ PayloadCipher.decrypt(bytes(payload), key) // AES/GCM/NoPadding, IV 12B, tag 128b
├─ NestedLibraries.extract(payloadBytes, dir) // META-INF/jars/*.jar → <dir>/libraries
├─ PreparedMod(mount="evovis", candidate=Path, classpathUrl, libraries)
└─ bootstrap(inst)
├─ appendToSystemClassLoaderSearch(classpathUrl) → классы мода видны JVM
└─ inst.addTransformer(FabricDiscoveryInjector)
│
▼
FabricDiscoveryInjector.transform(...) // java.lang.classfile API (байткод-в-байткоде)
├─ цель: net/fabricmc/loader/impl/discovery/ArgumentModCandidateFinder.findCandidates
├─ вставляет вызов EvoAgentHook.contribute(Consumer) (слот CONSUMER_SLOT = 1)
└─ consume.accept(PreparedMod) → Fabric видит мод-кандидат, которого нет на диске
│
▼
Fabric загружает мод → мод сам распаковывает evoguard.dll из META-INF/evo/native/
→ SealedBoot / KeyAssembler / SessionHeartbeat / NativeGuard
Ключевые свойства:
- Мод не лежит на диске в открытом виде - расшифровка в памяти агента,
candidate- временный mount-файл. - Ключ собирается из двух мест: hex из env (лаунчер) +
java.lang.EvoShare.compute(hwid)в подменённом JVM - поэтому мод гоняется только на их evojdk. - Инъекция в Fabric - не мод в
mods/, а байткод-трансформация внутреннего класса Fabric (ArgumentModCandidateFinder) черезjava.lang.classfileAPI. Классы:EvoAgent,EvoAgentHook,FabricDiscoveryInjector,PreparedMod,NestedLibraries. - Далее внутри мода - свой слой:
KeyAssembler(Shamir: сервер + integrity + native + hwid),SealedBoot(sealed.bin в этом билде отсутствует → fail-open),SessionHeartbeat.
Что здесь не проверено
- Тело
java.dll/EvoShare- натив не реверсился (план вtodo.md). - Реальное поведение при запуске (нужен MC 26.2 + Fabric 0.19.5 + их evojdk + дисплей).
evoguard.dll- не реверсился.