diff --git a/.github/workflows/build-loader.yml b/.github/workflows/build-loader.yml index fbfd2b2..067c142 100644 --- a/.github/workflows/build-loader.yml +++ b/.github/workflows/build-loader.yml @@ -92,10 +92,13 @@ jobs: # "builtin-baseline". A floating HEAD clone made the qtbase port # (and thus its package ABI) drift every time upstream vcpkg moved, # so the restored Qt cache never matched and qtbase was rebuilt - # from source (~31 min) on every run. Tag 2026.05.25 == - # d015e31e90838a4c9dfa3eed45979bc70d9357fc and ships qtbase 6.11.0. + # from source (~31 min) on every run. Tag 2026.04.27 == + # 56bb2411609227288b70117ead2c47585ba07713 and ships qtbase 6.10.2. + # We pin 6.10.2 (not the newer 6.11.0 from 2026.05.25) because MSVC + # 14.44.x crashes with an internal compiler error (C1001) building + # Qt 6.11.0 from source; 6.10.2 builds cleanly on every toolset we use. # Keep this tag and the builtin-baseline in vcpkg.json in lockstep. - $vcpkgTag = '2026.05.25' + $vcpkgTag = '2026.04.27' $vcpkg = Join-Path $env:GITHUB_WORKSPACE 'vcpkg' if (Test-Path $vcpkg) { Write-Host "Reusing existing $vcpkg" @@ -156,20 +159,28 @@ jobs: $sha = "${{ steps.rev.outputs.sha }}" $exeSrc = "build\dist\OpenZenLoader.exe" $jarSrc = "build\libs\hey-1.0.jar" + # The class-name obfuscator emits a fresh, random old->new mapping on every + # build; rename-mapping.txt is the ONLY way to de-obfuscate a stack trace, so + # ship it with the artifacts/release. + $mapSrc = "build\rename-mapping.txt" if (-not (Test-Path $exeSrc)) { throw "missing $exeSrc" } if (-not (Test-Path $jarSrc)) { throw "missing $jarSrc" } + if (-not (Test-Path $mapSrc)) { throw "missing $mapSrc" } $release = "build\release" New-Item -ItemType Directory -Force -Path $release | Out-Null $exeDst = Join-Path $release "OpenZenLoader-$sha.exe" $jarDst = Join-Path $release "OpenZen-$sha.jar" + $mapDst = Join-Path $release "OpenZen-$sha-mapping.txt" Copy-Item -Force $exeSrc $exeDst Copy-Item -Force $jarSrc $jarDst + Copy-Item -Force $mapSrc $mapDst $exeSz = (Get-Item $exeDst).Length $jarSz = (Get-Item $jarDst).Length Write-Host ("OpenZenLoader-{0}.exe : {1:N0} bytes ({2:N2} MB)" -f $sha, $exeSz, ($exeSz/1MB)) Write-Host ("OpenZen-{0}.jar : {1:N0} bytes ({2:N2} MB)" -f $sha, $jarSz, ($jarSz/1MB)) + Write-Host ("OpenZen-{0}-mapping.txt : {1:N0} bytes" -f $sha, (Get-Item $mapDst).Length) # NOTE: actions/upload-artifact always wraps its content in a zip; that # is a platform limitation we cannot disable. By giving each artifact a @@ -193,6 +204,14 @@ jobs: if-no-files-found: error retention-days: 30 + - name: Upload de-obfuscation mapping + uses: actions/upload-artifact@v4 + with: + name: OpenZen-${{ steps.rev.outputs.sha }}-mapping.txt + path: build/release/OpenZen-${{ steps.rev.outputs.sha }}-mapping.txt + if-no-files-found: error + retention-days: 30 + # ===== Optional GitHub Release publish ===== # If the HEAD commit message contains the literal marker "[Release]", # cut a GitHub Release tagged build- and attach the exe + jar. @@ -224,9 +243,30 @@ jobs: # Write notes via a file so quoting / [brackets] / newlines in the # commit message can't corrupt the gh command line. $notes = "release-notes.md" - git log -1 --pretty=%B HEAD | Out-File -FilePath $notes -Encoding utf8 + # Prepend a PRE-BUILT warning to the release body: these artifacts all share + # one fixed obfuscation mapping, so an anti-cheat class-name blacklist can + # target them. Tell users to self-compile for unique, per-build random names. + # Build the banner as a string array (one line each) to avoid PowerShell + # here-string column-0 terminator issues inside a YAML block scalar. + $warn = @( + '> ⚠️ **这是预构建版本(PRE-BUILT)**' + '>' + '> 本 Release 里的 `OpenZenLoader.exe` / `OpenZen-*.jar` 是 GitHub Actions 编译的成品,**所有人下载到的是同一套混淆类名**。这套固定的名字随时可能被反作弊(如布吉岛)收录进**类名黑名单**而失效。' + '>' + '> 想要一套**独一无二、别人都不知道**的类名,请**自己编译**(每次构建都会生成全新随机类名):' + '> - **Fork 本仓库**,在你自己的 GitHub Actions 里跑 `Build Loader` 工作流,下载你自己的 artifact;**或**' + '> - **clone 到本地**自己 `gradlew jar` / `gradlew dll`。' + '>' + '> 详见仓库 README 的「编译时类名混淆」。`OpenZen-*-mapping.txt` 是本次构建的反混淆映射(每次构建都不同)。' + '' + '---' + '' + ) + $warn | Out-File -FilePath $notes -Encoding utf8 + git log -1 --pretty=%B HEAD | Out-File -FilePath $notes -Encoding utf8 -Append gh release create $tag ` --title $title ` --notes-file $notes ` "build/release/OpenZenLoader-$env:SHA.exe" ` - "build/release/OpenZen-$env:SHA.jar" + "build/release/OpenZen-$env:SHA.jar" ` + "build/release/OpenZen-$env:SHA-mapping.txt" diff --git a/.gitignore b/.gitignore index 705cdad..8ae0baf 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ forge*changelog.txt # Native DLL build artifacts (CMake out-of-source build + staged jar) native/build/ native/zen.jar + +# Generated at build time by ext.obfuscateJar (holds the obfuscated bridge FQCN) +native/dll/src/generated_names.h diff --git a/README.md b/README.md index fb182ab..b6dd90e 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,20 @@ OpenZen 支持两种交付形式:**Java Agent jar**(挂到 Minecraft JVM 启 > **本项目不能作为 Forge mod 启动。** `mods/` 加载路径不被支持,不要把 jar 丢进 `.minecraft/mods/`。 +### 编译时类名混淆(重要) + +每次构建,OpenZen 会**自动把所有自有类(`shit.zen.*` / `asm.patchify.*`)重命名为随机的 16 位名字**——包名和类名都随机,**每次构建都不一样**、互不重复,原始类名/包名一律不保留(连日志里残留的类名字符串也清理掉了)。引导链(Agent 入口、DLL 加载、`Class.forName`)会在构建时自动联动到新名字,无需手工处理。两种交付形式(jar / 注入器)都已混淆。 + +这是为了对抗按**类名黑名单**工作的反作弊(见下方[常见问题](#布吉岛反作弊绕过))。正因为名字每次构建随机: + +> ⚠️ **从 GitHub Actions / Release 下载到的是预编译版本,所有人拿到的是同一套混淆名**——这套固定的名字随时可能被反作弊收录进黑名单。想要一套**别人都不知道、独一无二**的类名,请**自己编译**: +> - **Fork 本仓库**,在你自己的 GitHub Actions 里跑一次构建(每次运行都生成全新随机名),下载你自己的 artifact;**或** +> - **clone 到本地**自己 `gradlew jar` / `gradlew dll`(每次本地构建同样是全新随机名)。 + +每次构建的"旧名 → 新名"映射写在 `build/rename-mapping.txt`(CI 也会把它作为 artifact 上传、并附到 Release),这是反混淆崩溃日志的**唯一**依据。**注意它每次构建都不同,务必和对应产物一起保存。** + +实现细节见 `build.gradle` 的 `ext.obfuscateJar`:用项目自带的 ASM 在 ForgeGradle `reobf` 之后对产出 jar 做 `ClassRemapper` 重命名,**只改类名、不动方法/字段名**(避免破坏反射、JNI、`@SerializedName` 等)。 + ### 共同前置 - **JDK 17**(推荐 Microsoft Build of OpenJDK / Temurin / Azul Zulu 任一)。 @@ -172,14 +186,15 @@ OpenZen 支持两种交付形式:**Java Agent jar**(挂到 Minecraft JVM 启 诊断日志: - Native 端:`%TEMP%\openzen.log` -- Java 端:Minecraft 自己的 `logs/latest.log`(搜索 `OpenZen-Bootstrap`、`GameLoaderBridge`、`PatchAgent`、`ZenBootstrap`) +- Java 端:Minecraft 自己的 `logs/latest.log`(类名已被构建时混淆、logger 名是随机的,改用固定日志文案定位,如 `bootstrap.start`、`bridge.load`、`agent attached`、`Runtime mapping`) ## 常见问题 ### 布吉岛反作弊绕过 ~~截止至目前(2026/05/23),布吉岛并未检测本项目,考虑其反作弊为黑名单类名机制。~~ ~~建议构建时修改类名。~~ -类名已黑名单,请在构建时修改类名。 +~~类名已黑名单,请在构建时修改类名。~~ +类名已黑名单。现在**每次构建都会自动随机化全部类名**(见上方[编译时类名混淆](#编译时类名混淆重要))——但**务必自己 Fork/clone 编译**,别直接用 GitHub Actions / Release 上的预编译版:那是固定的一套名字,会被拉黑。 ## 致谢 diff --git a/build.gradle b/build.gradle index eada613..486dbb6 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,27 @@ +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassWriter +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.commons.ClassRemapper +import org.objectweb.asm.commons.Remapper + +buildscript { + repositories { + mavenCentral() + } + dependencies { + // ASM is used by the build-time class-name obfuscation step (ext.obfuscateJar). + // It runs on the already-built jar, so it lives on the buildscript classpath + // rather than the project compile/runtime classpath. + // + // NOTE: the buildscript-classpath ASM also backs Groovy's build-script type + // resolution, so it must be able to read the class files of the JDK that runs + // Gradle. ASM 9.6 supports up to Java 21 (major version 65) — run Gradle on the + // project's JDK 17 (not a newer JDK like 25/major 69, which 9.6 cannot read). + classpath 'org.ow2.asm:asm-commons:9.6' + } +} + plugins { id 'eclipse' id 'idea' @@ -209,6 +233,182 @@ tasks.named('jar', Jar).configure { finalizedBy 'reobfJar' } +// ===== Build-time class-name obfuscation ===== +// +// Renames EVERY OpenZen class (shit.zen.** and asm.patchify.**) to an opaque +// generated name, so no original class name survives in the distributed jar. +// It runs on the reobf'd jar and only touches our two package trees — +// net.minecraft.** / net.minecraftforge.** references are left exactly as +// ForgeGradle's mojmap->SRG reobf produced them, so reobf and runtime +// remapping are unaffected. +// +// CLASS NAMES ONLY: method/field names are preserved, so the JNI method lookups +// (GameLoaderBridge.load, DllBootstrap.start), reflection, GSON @SerializedName +// wire keys and the manifest member contracts all keep working. +// +// NOTHING IS HARD-CODED. The three bootstrap classes referenced by string +// OUTSIDE the bytecode the remapper rewrites are kept in lockstep by +// propagating their freshly generated names: +// asm.patchify.loader.PatchAgent -> jar manifest Premain/Agent-Class (rewritten below) +// shit.zen.dll.DllBootstrap -> Class.forName(...) string literal (rewritten via Remapper.mapValue) +// shit.zen.dll.GameLoaderBridge -> native DLL loader, via a generated C++ header +// (native/dll/src/generated_names.h) emitted below. +ext.obfuscateJar = { File jarFile, File mappingOut -> + def owned = { String internal -> internal.startsWith('shit/zen/') || internal.startsWith('asm/patchify/') } + + // Pass 1: enumerate the classes we own (every shit.zen.* / asm.patchify.* class). + def ownedNames = [] + new java.util.zip.ZipFile(jarFile).withCloseable { zf -> + for (entry in Collections.list(zf.entries())) { + if (!entry.directory && entry.name.endsWith('.class')) { + def internal = entry.name.substring(0, entry.name.length() - 6) + if (owned(internal)) ownedNames << internal + } + } + } + if (ownedNames.isEmpty()) { + logger.lifecycle("obfuscateJar: no original class names in ${jarFile.name} (already obfuscated) — skipping") + return + } + // Pass 2: assign each owned class a FRESH RANDOM 16-char name, so the obfuscated + // names differ on every build and encode nothing. Uniqueness is enforced. The + // names are unpredictable, so build/rename-mapping.txt (written below) is the only + // way to de-obfuscate a stack trace — each build's mapping is different; keep it. + // + // ALL classes go into ONE shared (also random, 16-char) package. They must share a + // single package so that package-private (default-access) members originally used + // between same-package classes still resolve — flattening into one package only + // widens access, never breaks it. Per-class packages would turn those into illegal + // cross-package accesses (IllegalAccessError) unless every member were made public. + def typeMap = [:] + def usedNames = new HashSet() + def secureRandom = new java.security.SecureRandom() + def leadAlphabet = (('a'..'z') + ('A'..'Z')).join('') // first char: a letter + def nameAlphabet = (('a'..'z') + ('A'..'Z') + ('0'..'9')).join('') // rest: alphanumeric + def randomName = { + def sb = new StringBuilder(16) + sb.append(leadAlphabet.charAt(secureRandom.nextInt(leadAlphabet.length()))) + 15.times { sb.append(nameAlphabet.charAt(secureRandom.nextInt(nameAlphabet.length()))) } + sb.toString() + } + def obfPackage = randomName() // one random package for every class (see note above) + ownedNames.each { internal -> + def newName + while (true) { newName = randomName(); if (usedNames.add(newName)) break } + typeMap[internal] = obfPackage + '/' + newName + } + // String-constant remap table — catches class names embedded as String + // literals (e.g. Class.forName("shit.zen.dll.DllBootstrap")). Both the + // dotted (Class.forName) and slash (internal) spellings are covered. + def stringMap = [:] + typeMap.each { o, n -> + stringMap[o.replace('/', '.')] = n.replace('/', '.') + stringMap[o] = n + } + + def remapper = new Remapper() { + String map(String internalName) { + def n = typeMap[internalName] + return n != null ? n : internalName + } + + Object mapValue(Object value) { + if (value instanceof String) { + def repl = stringMap[value] + if (repl != null) return repl + } + return super.mapValue(value) + } + } + + // Pass 2: rewrite into a temp jar, then atomically swap it in. + def tmp = new File(jarFile.parentFile, jarFile.name + '.obf') + tmp.delete() + new java.util.zip.ZipFile(jarFile).withCloseable { zf -> + new java.util.zip.ZipOutputStream(new FileOutputStream(tmp)).withCloseable { zos -> + for (entry in Collections.list(zf.entries())) { + if (entry.directory) continue + def name = entry.name + byte[] bytes = zf.getInputStream(entry).bytes + if (name.endsWith('.class')) { + def internal = name.substring(0, name.length() - 6) + if (owned(internal)) { + def cr = new ClassReader(bytes) + def cw = new ClassWriter(0) + // Drop the original SourceFile name ("ZenClient.java" etc.) but keep + // line numbers so stack traces still carry positions. + def stripSource = new ClassVisitor(Opcodes.ASM9, cw) { + void visitSource(String source, String debug) { super.visitSource(null, null) } + } + cr.accept(new ClassRemapper(stripSource, remapper), 0) + bytes = cw.toByteArray() + name = typeMap[internal] + '.class' + } + zos.putNextEntry(new java.util.zip.ZipEntry(name)); zos.write(bytes); zos.closeEntry() + } else if (name == 'META-INF/MANIFEST.MF') { + def mf = new java.util.jar.Manifest(new ByteArrayInputStream(bytes)) + def attrs = mf.getMainAttributes() + ['Premain-Class', 'Agent-Class'].each { key -> + def v = attrs.getValue(key) + if (v != null) { + def internal = v.replace('.', '/') + if (typeMap.containsKey(internal)) attrs.putValue(key, typeMap[internal].replace('/', '.')) + } + } + def bos = new ByteArrayOutputStream(); mf.write(bos) + zos.putNextEntry(new java.util.zip.ZipEntry(name)); zos.write(bos.toByteArray()); zos.closeEntry() + } else if (name.startsWith('META-INF/') && + (name.endsWith('.SF') || name.endsWith('.RSA') || name.endsWith('.DSA') || name.endsWith('.EC'))) { + // Drop signature files — renaming class entries invalidates their digests. + } else { + // Resources verbatim: mapping.srg, webui/**, assets/**, fonts, mods.toml, pack.mcmeta. + zos.putNextEntry(new java.util.zip.ZipEntry(name)); zos.write(bytes); zos.closeEntry() + } + } + } + } + if (!jarFile.delete()) throw new GradleException("obfuscateJar: could not delete ${jarFile}") + if (!tmp.renameTo(jarFile)) throw new GradleException("obfuscateJar: could not move ${tmp} -> ${jarFile}") + + mappingOut.parentFile.mkdirs() + mappingOut.withWriter('UTF-8') { w -> + typeMap.sort { it.key }.each { o, n -> w.writeLine("${o.replace('/', '.')} -> ${n.replace('/', '.')}") } + } + + // Propagate the GameLoaderBridge name to the native DLL loader. class_loader.cpp + // #includes this generated header and loads the class by OZ_BRIDGE_FQCN, so the + // native side always matches whatever opaque name the bridge received — no + // hard-coded class name. Generated, never committed (see .gitignore). + def bridgeFqcn = typeMap['shit/zen/dll/GameLoaderBridge'].replace('/', '.') + def header = file('native/dll/src/generated_names.h') + header.parentFile.mkdirs() + header.text = """\ +// AUTO-GENERATED by build.gradle (ext.obfuscateJar). DO NOT EDIT, DO NOT COMMIT. +// The build renames every OpenZen class to an opaque name; this captures the +// generated FQCN of the DLL bootstrap bridge (originally shit.zen.dll.GameLoaderBridge) +// so the native loader can request it by name. The bridge's load(String, ClassLoader) +// method name is preserved by the rename, so main.cpp's GetStaticMethodID still works. +#pragma once +#define OZ_BRIDGE_FQCN "${bridgeFqcn}" +""" + + logger.lifecycle("obfuscateJar: renamed ${typeMap.size()} classes in ${jarFile.name}; " + + "bridge=${bridgeFqcn}; mapping -> ${mappingOut}") +} + +tasks.register('obfuscateClasses') { + group = 'openzen' + description = 'Rename every OpenZen class to an opaque name in the built jar (class names only).' + dependsOn 'reobfJar' + doLast { + obfuscateJar(tasks.jar.archiveFile.get().asFile, file("$buildDir/rename-mapping.txt")) + } +} + +// Auto-run after every reobf so `gradlew jar` / `build` / `dll` all emit obfuscated +// names. reobfJar is created lazily by ForgeGradle, so wire via matching/configureEach. +tasks.matching { it.name == 'reobfJar' }.configureEach { finalizedBy 'obfuscateClasses' } + // However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing: // tasks.named('publish').configure { // dependsOn 'reobfJar' @@ -366,7 +566,10 @@ tasks.register('stageNativeJar', Copy) { group = 'openzen' description = 'Stage the freshly built mod jar at native/zen.jar so the DLL ' + 'resource compiler can embed it.' - dependsOn 'jar' + // obfuscateClasses (which dependsOn reobfJar, which dependsOn jar) rewrites + // the jar in place, so depend on it to guarantee the DLL embeds the + // obfuscated jar rather than racing the rename. + dependsOn 'obfuscateClasses' from { tasks.jar.archiveFile } into nativeDir rename { 'zen.jar' } @@ -378,6 +581,13 @@ tasks.register('configureNative', Exec) { dependsOn 'stageNativeJar' workingDir nativeDir commandLine 'cmake', '-S', '.', '-B', 'build', '-A', 'x64' + // Local builds compile Qt single-threaded (vcpkg honours VCPKG_MAX_CONCURRENCY, + // which sets ninja -j for the qtbase port). This rules out parallel-compilation + // instability on the dev toolchain. CI is left parallel (fast) — it builds Qt + // fine, so we only serialise locally (off GitHub Actions). + if (System.getenv('GITHUB_ACTIONS') == null) { + environment 'VCPKG_MAX_CONCURRENCY', '1' + } doFirst { def cmake = findCmake() if (!cmake) throw new GradleException(cmakeMissingMessage) @@ -405,10 +615,12 @@ tasks.register('buildNative', Exec) { doFirst { def cmake = findCmake() if (!cmake) throw new GradleException(cmakeMissingMessage) - // --parallel without a number lets CMake pick the host's logical CPU - // count and forwards it to the underlying generator (MSBuild gets /m, + // --parallel forwards a job count to the generator (MSBuild gets /m, // Ninja gets -j). MSBuild defaults to a single process otherwise. - def jobs = Runtime.runtime.availableProcessors() + // Local builds use 1 job (single-threaded) to match the serialised Qt + // build above and rule out parallel-compilation instability; CI keeps + // full parallelism for speed. + def jobs = (System.getenv('GITHUB_ACTIONS') == null) ? 1 : Runtime.runtime.availableProcessors() logger.lifecycle("Parallel jobs: ${jobs}") commandLine cmake, '--build', 'build', '--config', 'Release', '--parallel', jobs.toString() diff --git a/native/dll/src/class_loader.cpp b/native/dll/src/class_loader.cpp index 91e4a3a..450077a 100644 --- a/native/dll/src/class_loader.cpp +++ b/native/dll/src/class_loader.cpp @@ -1,4 +1,5 @@ #include "openzen.h" +#include "generated_names.h" // OZ_BRIDGE_FQCN — generated by build.gradle ext.obfuscateJar #include @@ -114,17 +115,24 @@ jclass load_dll_bootstrap(JNIEnv* env, jobject game_loader, if (check_and_clear(env, "new URLClassLoader")) return nullptr; log::info("URLClassLoader constructed with parent=gameLoader"); - // urlcl.loadClass("shit.zen.dll.GameLoaderBridge") + // urlcl.loadClass(OZ_BRIDGE_FQCN) (the build-time-obfuscated GameLoaderBridge) // // We deliberately load GameLoaderBridge - not DllBootstrap - because the // bridge's job is to re-define every class in zen.jar onto the game // class loader so retransformed Minecraft classes can resolve our patch // handlers (defining-loader equality). DllBootstrap is then loaded by // the game loader from the bridge. + // + // The build renames every OpenZen class to an opaque generated name (see + // build.gradle ext.obfuscateJar). It also emits generated_names.h with the + // bridge's new FQCN as OZ_BRIDGE_FQCN, so this stays in lockstep with the + // embedded jar without any hard-coded class name. The bridge's + // load(String, ClassLoader) method name is preserved by the rename, so the + // GetStaticMethodID(bridge_cls, "load", ...) lookup in main.cpp still works. jclass classLoaderCls = env->FindClass("java/lang/ClassLoader"); jmethodID loadClass = env->GetMethodID(classLoaderCls, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - jstring name = env->NewStringUTF("shit.zen.dll.GameLoaderBridge"); + jstring name = env->NewStringUTF(OZ_BRIDGE_FQCN); jobject loaded = env->CallObjectMethod(urlcl, loadClass, name); if (check_and_clear(env, "URLClassLoader.loadClass GameLoaderBridge")) return nullptr; if (!loaded) { diff --git a/native/vcpkg.json b/native/vcpkg.json index 7a422ea..5fc74bb 100644 --- a/native/vcpkg.json +++ b/native/vcpkg.json @@ -10,5 +10,4 @@ "features": [ "widgets" ] } ], - "builtin-baseline": "d015e31e90838a4c9dfa3eed45979bc70d9357fc" -} + "builtin-baseline": "56bb2411609227288b70117ead2c47585ba07713混淆器 \ No newline at end of file diff --git a/src/main/java/asm/patchify/loader/PatchAgent.java b/src/main/java/asm/patchify/loader/PatchAgent.java index 0a78389..c075a8c 100644 --- a/src/main/java/asm/patchify/loader/PatchAgent.java +++ b/src/main/java/asm/patchify/loader/PatchAgent.java @@ -18,8 +18,8 @@ import org.apache.logging.log4j.Logger; * regardless of which class loader it lives in.

*/ public final class PatchAgent { - public static final String INSTRUMENTATION_KEY = "asm.patchify.instrumentation"; - private static final Logger LOGGER = LogManager.getLogger("PatchAgent"); + public static final String INSTRUMENTATION_KEY = "oz.instrumentation"; + private static final Logger LOGGER = LogManager.getLogger(PatchAgent.class); private static volatile boolean transformerInstalled = false; @@ -40,7 +40,7 @@ public final class PatchAgent { return; } System.getProperties().put(INSTRUMENTATION_KEY, inst); - LOGGER.info("PatchAgent attached, retransform supported = {}", inst.isRetransformClassesSupported()); + LOGGER.info("agent attached, retransform supported = {}", inst.isRetransformClassesSupported()); } /** @@ -62,7 +62,7 @@ public final class PatchAgent { } Instrumentation inst = getInstrumentation(); if (inst == null) { - LOGGER.warn("PatchAgent not attached; cannot install patches"); + LOGGER.warn("agent not attached; cannot install patches"); return; } PatchClassFileTransformer transformer = new PatchClassFileTransformer(); diff --git a/src/main/java/asm/patchify/loader/PatchClassFileTransformer.java b/src/main/java/asm/patchify/loader/PatchClassFileTransformer.java index 30c4f0f..62f5f78 100644 --- a/src/main/java/asm/patchify/loader/PatchClassFileTransformer.java +++ b/src/main/java/asm/patchify/loader/PatchClassFileTransformer.java @@ -23,13 +23,13 @@ import org.objectweb.asm.tree.ClassNode; *

Indexes the patches by the JVM-internal name of their target class and rewrites class * bytes via ASM. Classes that have no patch are returned untouched (null).

* - *

If the system property {@code asm.patchify.dumpDir} is set, every successfully + *

If the system property {@code oz.dumpDir} is set, every successfully * transformed class is written under that directory as {@code .class} for * inspection (use {@code javap -v} or open in Recaf).

*/ public final class PatchClassFileTransformer implements ClassFileTransformer { - private static final Logger LOGGER = LogManager.getLogger("PatchTransformer"); - private static final String DUMP_DIR_PROPERTY = "asm.patchify.dumpDir"; + private static final Logger LOGGER = LogManager.getLogger(PatchClassFileTransformer.class); + private static final String DUMP_DIR_PROPERTY = "oz.dumpDir"; private final Map>> patchesByTarget = new HashMap<>(); diff --git a/src/main/java/asm/patchify/loader/PatchRegistry.java b/src/main/java/asm/patchify/loader/PatchRegistry.java index a282b2f..23d1bb5 100644 --- a/src/main/java/asm/patchify/loader/PatchRegistry.java +++ b/src/main/java/asm/patchify/loader/PatchRegistry.java @@ -17,7 +17,7 @@ import org.apache.logging.log4j.Logger; * lightweight no-op fallback when no transformer is installed.

*/ public final class PatchRegistry { - private static final Logger LOGGER = LogManager.getLogger("PatchRegistry"); + private static final Logger LOGGER = LogManager.getLogger(PatchRegistry.class); private static final List> PATCHES = new ArrayList<>(); private PatchRegistry() { diff --git a/src/main/java/shit/zen/ZenClient.java b/src/main/java/shit/zen/ZenClient.java index 8b21eb6..3418d20 100644 --- a/src/main/java/shit/zen/ZenClient.java +++ b/src/main/java/shit/zen/ZenClient.java @@ -109,7 +109,7 @@ public class ZenClient extends ClientBase { if (PatchAgent.getInstrumentation() != null) { PatchAgent.installPatchesAndRetransform(); } else { - logger.warn("PatchAgent not attached. Launch with `./gradlew runClient0` so the agent jvmArg is set."); + logger.warn("agent not attached. Launch with `./gradlew runClient0` so the agent jvmArg is set."); } isReady = true; logger.info("{} v{} initialized.", CLIENT_NAME, VERSION); diff --git a/src/main/java/shit/zen/asm/Bootstrap.java b/src/main/java/shit/zen/asm/Bootstrap.java index ea6e316..71e2211 100644 Binary files a/src/main/java/shit/zen/asm/Bootstrap.java and b/src/main/java/shit/zen/asm/Bootstrap.java differ diff --git a/src/main/java/shit/zen/dll/DllBootstrap.java b/src/main/java/shit/zen/dll/DllBootstrap.java index f29ba52..58f0e0f 100644 --- a/src/main/java/shit/zen/dll/DllBootstrap.java +++ b/src/main/java/shit/zen/dll/DllBootstrap.java @@ -22,7 +22,7 @@ import shit.zen.asm.Bootstrap; * onwards.

*/ public final class DllBootstrap { - private static final Logger LOGGER = LogManager.getLogger("DllBootstrap"); + private static final Logger LOGGER = LogManager.getLogger(DllBootstrap.class); private static volatile boolean started = false; private DllBootstrap() { @@ -30,16 +30,16 @@ public final class DllBootstrap { public static synchronized void start(String extractedJarPath) { if (started) { - LOGGER.info("DllBootstrap.start ignored (already started)"); + LOGGER.info("bootstrap.start ignored (already started)"); return; } started = true; try { - LOGGER.info("DllBootstrap.start jar={}", extractedJarPath); - LOGGER.info("DllBootstrap loader = {}", DllBootstrap.class.getClassLoader()); - LOGGER.info("ZenClient loader = {}", ZenClient.class.getClassLoader()); + LOGGER.info("bootstrap.start jar={}", extractedJarPath); + LOGGER.info("bootstrap loader = {}", DllBootstrap.class.getClassLoader()); + LOGGER.info("client loader = {}", ZenClient.class.getClassLoader()); LOGGER.info("Minecraft loader = {}", Minecraft.class.getClassLoader()); - LOGGER.info("PatchAgent inst = {}", PatchAgent.getInstrumentation()); + LOGGER.info("agent inst = {}", PatchAgent.getInstrumentation()); // Load mojmap → SRG mappings before we attempt any retransform. Bootstrap.init(); @@ -53,9 +53,9 @@ public final class DllBootstrap { ZenClient.registerPatches(); PatchAgent.installPatchesAndRetransform(); - LOGGER.info("DllBootstrap done. ZenClient will be constructed on the next Minecraft.tick."); + LOGGER.info("bootstrap done. client will be constructed on the next tick."); } catch (Throwable t) { - LOGGER.error("DllBootstrap.start failed", t); + LOGGER.error("bootstrap.start failed", t); } } } diff --git a/src/main/java/shit/zen/dll/GameLoaderBridge.java b/src/main/java/shit/zen/dll/GameLoaderBridge.java index 47ce89e..d77cceb 100644 --- a/src/main/java/shit/zen/dll/GameLoaderBridge.java +++ b/src/main/java/shit/zen/dll/GameLoaderBridge.java @@ -46,19 +46,19 @@ import org.apache.logging.log4j.Logger; * */ public final class GameLoaderBridge { - private static final Logger LOGGER = LogManager.getLogger("GameLoaderBridge"); + private static final Logger LOGGER = LogManager.getLogger(GameLoaderBridge.class); public static final String RESOURCES_PROP = "openzen.resources"; private GameLoaderBridge() { } public static void load(String jarPath, ClassLoader gameLoader) throws Throwable { - LOGGER.info("GameLoaderBridge.load jar={} gameLoader={}", jarPath, gameLoader); + LOGGER.info("bridge.load jar={} gameLoader={}", jarPath, gameLoader); long t0 = System.nanoTime(); Instrumentation inst = PatchAgent.getInstrumentation(); if (inst == null) { - throw new IllegalStateException("PatchAgent.getInstrumentation() returned null - " + throw new IllegalStateException("agent instrumentation returned null - " + "did instrument.dll Agent_OnAttach run?"); } @@ -128,7 +128,7 @@ public final class GameLoaderBridge { classCount, resourceCount, resourceDir, ms); Class bootstrapCls = Class.forName("shit.zen.dll.DllBootstrap", true, gameLoader); - LOGGER.info("DllBootstrap loader (should be gameLoader): {}", bootstrapCls.getClassLoader()); + LOGGER.info("bootstrap loader (should be gameLoader): {}", bootstrapCls.getClassLoader()); Method start = bootstrapCls.getMethod("start", String.class); start.invoke(null, jarPath); }