master #102

Closed
scu11 wants to merge 3 commits from <deleted>:master into master
  1. 144
      asm/src/main/java/dev/openrs2/asm/classpath/Library.kt
  2. 35
      asm/src/main/java/dev/openrs2/asm/io/JarLibraryReader.kt
  3. 45
      asm/src/main/java/dev/openrs2/asm/io/JarLibraryWriter.kt
  4. 12
      asm/src/main/java/dev/openrs2/asm/io/Js5LibraryWriter.kt
  5. 7
      asm/src/main/java/dev/openrs2/asm/io/LibraryReader.kt
  6. 8
      asm/src/main/java/dev/openrs2/asm/io/LibraryWriter.kt
  7. 35
      asm/src/main/java/dev/openrs2/asm/io/Pack200LibraryReader.kt
  8. 36
      asm/src/main/java/dev/openrs2/asm/io/Pack200LibraryWriter.kt
  9. 54
      asm/src/main/java/dev/openrs2/asm/io/SignedJarLibraryWriter.kt
  10. 57
      bundler/src/main/java/dev/openrs2/bundler/Bundler.kt
  11. 25
      bundler/src/main/java/dev/openrs2/bundler/Resource.kt
  12. 55
      deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt
  13. 28
      util/src/main/java/dev/openrs2/util/io/DeterministicJarOutputStream.kt

@ -1,30 +1,13 @@
package dev.openrs2.asm.classpath
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.ClassVersionUtils
import dev.openrs2.asm.NopClassVisitor
import dev.openrs2.asm.remap
import dev.openrs2.compress.gzip.Gzip
import dev.openrs2.crypto.Pkcs12KeyStore
import dev.openrs2.util.io.DeterministicJarOutputStream
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.commons.Remapper
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.util.CheckClassAdapter
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.util.TreeMap
import java.util.jar.JarEntry
import java.util.jar.JarInputStream
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.jar.Pack200
class Library() : Iterable<ClassNode> {
private var classes = TreeMap<String, ClassNode>()
class Library(classes: Map<String, ClassNode> = emptyMap()) : Iterable<ClassNode> {
private var classes = TreeMap(classes)
constructor(library: Library) : this() {
for (clazz in library.classes.values) {
@ -61,125 +44,4 @@ class Library() : Iterable<ClassNode> {
classes = classes.mapKeysTo(TreeMap()) { (_, clazz) -> clazz.name }
}
fun writeJar(classPath: ClassPath, path: Path, manifest: Manifest? = null) {
logger.info { "Writing jar $path" }
Files.newOutputStream(path).use {
writeJar(classPath, it, manifest)
}
}
fun writeJar(classPath: ClassPath, out: OutputStream, manifest: Manifest? = null) {
DeterministicJarOutputStream.create(out, manifest).use { jar ->
for (clazz in classes.values) {
val writer = if (ClassVersionUtils.gte(clazz.version, Opcodes.V1_7)) {
StackFrameClassWriter(classPath)
} else {
ClassWriter(ClassWriter.COMPUTE_MAXS)
}
clazz.accept(writer)
jar.putNextEntry(JarEntry(clazz.name + CLASS_SUFFIX))
jar.write(writer.toByteArray())
/*
* XXX(gpe): CheckClassAdapter breaks the Label offset
* calculation in the OriginalPcTable's write method, so we do
* a second pass without any attributes to check the class,
* feeding the callbacks into a no-op visitor.
*/
for (method in clazz.methods) {
method.attrs?.clear()
}
clazz.accept(CheckClassAdapter(NopClassVisitor, true))
}
}
}
fun writeSignedJar(classPath: ClassPath, path: Path, keyStore: Pkcs12KeyStore, manifest: Manifest? = null) {
logger.info { "Writing signed jar $path" }
val unsignedPath = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
writeJar(classPath, unsignedPath, manifest)
val signedPath = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
keyStore.signJar(unsignedPath, signedPath)
DeterministicJarOutputStream.repack(signedPath, path)
} finally {
Files.deleteIfExists(signedPath)
}
} finally {
Files.deleteIfExists(unsignedPath)
}
}
fun writePack(classPath: ClassPath, out: OutputStream) {
val temp = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
writeJar(classPath, temp)
JarInputStream(Files.newInputStream(temp)).use { `in` ->
Gzip.createHeaderlessOutputStream(out).use { gzip ->
Pack200.newPacker().pack(`in`, gzip)
}
}
} finally {
Files.deleteIfExists(temp)
}
}
fun writeJs5(classPath: ClassPath, out: OutputStream) {
// TODO(gpe): implement
}
companion object {
private val logger = InlineLogger()
private const val CLASS_SUFFIX = ".class"
private const val TEMP_PREFIX = "tmp"
private const val JAR_SUFFIX = ".jar"
fun readJar(path: Path): Library {
logger.info { "Reading jar $path" }
val library = Library()
JarInputStream(Files.newInputStream(path)).use { `in` ->
while (true) {
val entry = `in`.nextJarEntry ?: break
if (!entry.name.endsWith(CLASS_SUFFIX)) {
continue
}
val clazz = ClassNode()
val reader = ClassReader(`in`)
reader.accept(JsrInliner(clazz), ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
library.add(clazz)
}
}
return library
}
fun readPack(path: Path): Library {
logger.info { "Reading pack $path" }
val temp = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
Gzip.createHeaderlessInputStream(Files.newInputStream(path)).use { `in` ->
JarOutputStream(Files.newOutputStream(temp)).use { out ->
Pack200.newUnpacker().unpack(`in`, out)
}
}
return readJar(temp)
} finally {
Files.deleteIfExists(temp)
}
}
}
}

@ -0,0 +1,35 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.classpath.JsrInliner
import dev.openrs2.asm.classpath.Library
import org.objectweb.asm.ClassReader
import org.objectweb.asm.tree.ClassNode
import java.util.jar.JarEntry
import java.util.jar.JarInputStream
class JarLibraryReader(private val input: JarInputStream) : LibraryReader {
override fun read(): Library {
val classes = generateSequence { input.nextJarEntry }
.filter(::isClass)
.map { input.readClass() }
.associateBy(ClassNode::name)
return Library(classes)
}
private fun isClass(entry: JarEntry): Boolean {
return entry.name.endsWith(CLASS_SUFFIX)
}
private fun JarInputStream.readClass(): ClassNode {
val clazz = ClassNode()
val reader = ClassReader(this)
reader.accept(JsrInliner(clazz), ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return clazz
}
private companion object {
private const val CLASS_SUFFIX = ".class"
}
}

@ -0,0 +1,45 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.ClassVersionUtils
import dev.openrs2.asm.NopClassVisitor
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.classpath.StackFrameClassWriter
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.util.CheckClassAdapter
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
class JarLibraryWriter(private val output: JarOutputStream) : LibraryWriter {
override fun write(classPath: ClassPath, library: Library) {
for (clazz in library) {
val writer = if (ClassVersionUtils.gte(clazz.version, Opcodes.V1_7)) {
StackFrameClassWriter(classPath)
} else {
ClassWriter(ClassWriter.COMPUTE_MAXS)
}
clazz.accept(writer)
output.putNextEntry(JarEntry(clazz.name + CLASS_SUFFIX))
output.write(writer.toByteArray())
/*
* XXX(gpe): CheckClassAdapter breaks the Label offset
* calculation in the OriginalPcTable's write method, so we do
* a second pass without any attributes to check the class,
* feeding the callbacks into a no-op visitor.
*/
for (method in clazz.methods) {
method.attrs?.clear()
}
clazz.accept(CheckClassAdapter(NopClassVisitor, true))
}
}
private companion object {
private const val CLASS_SUFFIX = ".class"
}
}

@ -0,0 +1,12 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import java.io.OutputStream
class Js5LibraryWriter(private val output: OutputStream) : LibraryWriter {
override fun write(classPath: ClassPath, library: Library) {
// TODO(gpe): implement
}
}

@ -0,0 +1,7 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.classpath.Library
interface LibraryReader {
fun read(): Library
}

@ -0,0 +1,8 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
interface LibraryWriter {
fun write(classPath: ClassPath, library: Library)
}

@ -0,0 +1,35 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.classpath.Library
import dev.openrs2.compress.gzip.Gzip
import java.io.InputStream
import java.nio.file.Files
import java.util.jar.JarInputStream
import java.util.jar.JarOutputStream
import java.util.jar.Pack200
class Pack200LibraryReader(private val input: InputStream) : LibraryReader {
override fun read(): Library {
val temp = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
Gzip.createHeaderlessInputStream(input).use { gzipInput ->
JarOutputStream(Files.newOutputStream(temp)).use { output ->
Pack200.newUnpacker().unpack(gzipInput, output)
}
}
return JarInputStream(Files.newInputStream(temp)).use { tempInput ->
JarLibraryReader(tempInput).read()
}
} finally {
Files.deleteIfExists(temp)
}
}
private companion object {
private const val TEMP_PREFIX = "tmp"
private const val JAR_SUFFIX = ".jar"
}
}

@ -0,0 +1,36 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.compress.gzip.Gzip
import dev.openrs2.util.io.DeterministicJarOutputStream
import java.io.OutputStream
import java.nio.file.Files
import java.util.jar.JarInputStream
import java.util.jar.Pack200
class Pack200LibraryWriter(private val output: OutputStream) : LibraryWriter {
override fun write(classPath: ClassPath, library: Library) {
val tempJar = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
DeterministicJarOutputStream(Files.newOutputStream(tempJar)).use { tempOutput ->
JarLibraryWriter(tempOutput).write(classPath, library)
}
JarInputStream(Files.newInputStream(tempJar)).use { input ->
Gzip.createHeaderlessOutputStream(output).use { gzip ->
Pack200.newPacker().pack(input, gzip)
}
}
} finally {
Files.deleteIfExists(tempJar)
}
}
private companion object {
private const val TEMP_PREFIX = "tmp"
private const val JAR_SUFFIX = ".jar"
}
}

@ -0,0 +1,54 @@
package dev.openrs2.asm.io
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.crypto.Pkcs12KeyStore
import dev.openrs2.util.io.DeterministicJarOutputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.JarInputStream
import java.util.jar.Manifest
class SignedJarLibraryWriter(
private val output: OutputStream,
private val manifest: Manifest,
private val keyStore: Pkcs12KeyStore
) : LibraryWriter {
override fun write(classPath: ClassPath, library: Library) {
val unsignedJar = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
DeterministicJarOutputStream(Files.newOutputStream(unsignedJar), manifest).use { unsignedOutput ->
JarLibraryWriter(unsignedOutput).write(classPath, library)
}
val signedJar = Files.createTempFile(TEMP_PREFIX, JAR_SUFFIX)
try {
keyStore.signJar(unsignedJar, signedJar)
repack(signedJar)
} finally {
Files.deleteIfExists(signedJar)
}
} finally {
Files.deleteIfExists(unsignedJar)
}
}
private fun repack(signedJar: Path) {
JarInputStream(Files.newInputStream(signedJar)).use { input ->
DeterministicJarOutputStream(output, input.manifest).use { output ->
generateSequence { input.nextJarEntry }.forEach {
output.putNextEntry(it)
input.copyTo(output)
}
}
}
}
private companion object {
private const val TEMP_PREFIX = "tmp"
private const val JAR_SUFFIX = ".jar"
}
}

@ -3,6 +3,10 @@ package dev.openrs2.bundler
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.io.JarLibraryReader
import dev.openrs2.asm.io.JarLibraryWriter
import dev.openrs2.asm.io.Pack200LibraryReader
import dev.openrs2.asm.io.SignedJarLibraryWriter
import dev.openrs2.bundler.transform.BufferSizeTransformer
import dev.openrs2.bundler.transform.CachePathTransformer
import dev.openrs2.bundler.transform.HostCheckTransformer
@ -14,9 +18,12 @@ import dev.openrs2.bundler.transform.ResourceTransformer
import dev.openrs2.bundler.transform.RightClickTransformer
import dev.openrs2.bundler.transform.TypoTransformer
import dev.openrs2.crypto.Pkcs12KeyStore
import dev.openrs2.util.io.DeterministicJarOutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.Attributes
import java.util.jar.Attributes.Name.MANIFEST_VERSION
import java.util.jar.JarInputStream
import java.util.jar.Manifest
import javax.inject.Inject
import javax.inject.Singleton
@ -28,12 +35,12 @@ class Bundler @Inject constructor(publicKeyTransformer: PublicKeyTransformer) {
fun run(input: Path, output: Path, keyStorePath: Path) {
// read input jars/packs
logger.info { "Reading input jars" }
val unpacker = Library.readJar(input.resolve("unpackclass.pack"))
val loader = Library.readJar(input.resolve("loader.jar"))
val glLoader = Library.readJar(input.resolve("loader_gl.jar"))
val gl = Library.readPack(input.resolve("jaggl.pack200"))
val client = Library.readJar(input.resolve("runescape.jar"))
val glClient = Library.readPack(input.resolve("runescape_gl.pack200"))
val unpacker = readJar(input.resolve("unpackclass.pack"))
val loader = readJar(input.resolve("loader.jar"))
val glLoader = readJar(input.resolve("loader_gl.jar"))
val gl = readPack(input.resolve("jaggl.pack200"))
val client = readJar(input.resolve("runescape.jar"))
val glClient = readPack(input.resolve("runescape_gl.pack200"))
// bundle libraries together into a common classpath
val runtime = ClassLoader.getPlatformClassLoader()
@ -109,11 +116,43 @@ class Bundler @Inject constructor(publicKeyTransformer: PublicKeyTransformer) {
}
// write unsigned client and loaders
client.writeJar(classPath, output.resolve("runescape.jar"), unsignedManifest)
writeJar(classPath, client, output.resolve("runescape.jar"))
val keyStore = Pkcs12KeyStore.open(keyStorePath)
loader.writeSignedJar(classPath, output.resolve("loader.jar"), keyStore, signedManifest)
glLoader.writeSignedJar(glClassPath, output.resolve("loader_gl.jar"), keyStore, signedManifest)
writeSignedJar(classPath, loader, output.resolve("loader.jar"), keyStore)
writeSignedJar(glClassPath, glLoader, output.resolve("loader_gl.jar"), keyStore)
}
private fun readJar(path: Path): Library {
logger.info { "Reading jar $path" }
return JarInputStream(Files.newInputStream(path)).use { input ->
JarLibraryReader(input).read()
}
}
private fun readPack(path: Path): Library {
logger.info { "Reading pack $path" }
return Files.newInputStream(path).use { input ->
Pack200LibraryReader(input).read()
}
}
private fun writeJar(classPath: ClassPath, library: Library, path: Path) {
logger.info { "Writing jar $path" }
DeterministicJarOutputStream(Files.newOutputStream(path), unsignedManifest).use { output ->
JarLibraryWriter(output).write(classPath, library)
}
}
private fun writeSignedJar(classPath: ClassPath, library: Library, path: Path, keyStore: Pkcs12KeyStore) {
logger.info { "Writing signed jar $path" }
Files.newOutputStream(path).use {
SignedJarLibraryWriter(it, signedManifest, keyStore).write(classPath, library)
}
}
companion object {

@ -3,6 +3,10 @@ package dev.openrs2.bundler
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.io.JarLibraryWriter
import dev.openrs2.asm.io.Js5LibraryWriter
import dev.openrs2.asm.io.Pack200LibraryWriter
import dev.openrs2.util.io.DeterministicJarOutputStream
import java.io.ByteArrayOutputStream
import java.nio.file.Files
import java.nio.file.Path
@ -66,23 +70,26 @@ class Resource(
}
fun compressJar(source: String, destination: String, classPath: ClassPath, library: Library): Resource {
ByteArrayOutputStream().use { out ->
library.writeJar(classPath, out)
return compress(source, destination, out.toByteArray())
ByteArrayOutputStream().use { output ->
DeterministicJarOutputStream(output).use { jarOutput ->
JarLibraryWriter(jarOutput).write(classPath, library)
}
return compress(source, destination, output.toByteArray())
}
}
fun compressPack(source: String, destination: String, classPath: ClassPath, library: Library): Resource {
ByteArrayOutputStream().use { out ->
library.writePack(classPath, out)
return compress(source, destination, out.toByteArray())
ByteArrayOutputStream().use { output ->
Pack200LibraryWriter(output).write(classPath, library)
return compress(source, destination, output.toByteArray())
}
}
fun compressJs5(source: String, destination: String, classPath: ClassPath, library: Library): Resource {
ByteArrayOutputStream().use { out ->
library.writeJs5(classPath, out)
return compress(source, destination, out.toByteArray())
ByteArrayOutputStream().use { output ->
Js5LibraryWriter(output).write(classPath, library)
return compress(source, destination, output.toByteArray())
}
}

@ -3,8 +3,9 @@ package dev.openrs2.deob
import com.github.michaelbull.logging.InlineLogger
import dev.openrs2.asm.classpath.ClassPath
import dev.openrs2.asm.classpath.Library
import dev.openrs2.asm.classpath.Library.Companion.readJar
import dev.openrs2.asm.classpath.Library.Companion.readPack
import dev.openrs2.asm.io.JarLibraryReader
import dev.openrs2.asm.io.JarLibraryWriter
import dev.openrs2.asm.io.Pack200LibraryReader
import dev.openrs2.bundler.Bundler
import dev.openrs2.bundler.transform.ResourceTransformer
import dev.openrs2.deob.SignedClassUtils.move
@ -36,8 +37,10 @@ import dev.openrs2.deob.transform.UnusedArgTransformer
import dev.openrs2.deob.transform.UnusedLocalTransformer
import dev.openrs2.deob.transform.UnusedMethodTransformer
import dev.openrs2.deob.transform.VisibilityTransformer
import dev.openrs2.util.io.DeterministicJarOutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.util.jar.JarInputStream
class Deobfuscator(private val input: Path, private val output: Path) {
fun run() {
@ -124,20 +127,44 @@ class Deobfuscator(private val input: Path, private val output: Path) {
Files.createDirectories(output)
client.writeJar(classPath, output.resolve("runescape.jar"))
loader.writeJar(classPath, output.resolve("loader.jar"))
signLink.writeJar(classPath, output.resolve("signlink.jar"))
unpack.writeJar(classPath, output.resolve("unpack.jar"))
unpackClass.writeJar(classPath, output.resolve("unpackclass.jar"))
writeJar(classPath, client, output.resolve("runescape.jar"))
writeJar(classPath, loader, output.resolve("loader.jar"))
writeJar(classPath, signLink, output.resolve("signlink.jar"))
writeJar(classPath, unpack, output.resolve("unpack.jar"))
writeJar(classPath, unpackClass, output.resolve("unpackclass.jar"))
gl.writeJar(glClassPath, output.resolve("jaggl.jar"))
glClient.writeJar(glClassPath, output.resolve("runescape_gl.jar"))
glLoader.writeJar(glClassPath, output.resolve("loader_gl.jar"))
glSignLink.writeJar(glClassPath, output.resolve("signlink_gl.jar"))
glUnpack.writeJar(glClassPath, output.resolve("unpack_gl.jar"))
glUnpackClass.writeJar(glClassPath, output.resolve("unpackclass_gl.jar"))
writeJar(glClassPath, gl, output.resolve("jaggl.jar"))
writeJar(glClassPath, glClient, output.resolve("runescape_gl.jar"))
writeJar(glClassPath, glLoader, output.resolve("loader_gl.jar"))
writeJar(glClassPath, glSignLink, output.resolve("signlink_gl.jar"))
writeJar(glClassPath, glUnpack, output.resolve("unpack_gl.jar"))
writeJar(glClassPath, glUnpackClass, output.resolve("unpackclass_gl.jar"))
unsignedClient.writeJar(unsignedClassPath, output.resolve("runescape_unsigned.jar"))
writeJar(unsignedClassPath, unsignedClient, output.resolve("runescape_unsigned.jar"))
}
private fun readJar(path: Path): Library {
logger.info { "Reading jar $path" }
return JarInputStream(Files.newInputStream(path)).use { input ->
JarLibraryReader(input).read()
}
}
private fun readPack(path: Path): Library {
logger.info { "Reading pack $path" }
return Files.newInputStream(path).use { input ->
Pack200LibraryReader(input).read()
}
}
private fun writeJar(classPath: ClassPath, library: Library, path: Path) {
logger.info { "Writing jar $path" }
DeterministicJarOutputStream(Files.newOutputStream(path)).use { output ->
JarLibraryWriter(output).write(classPath, library)
}
}
companion object {

@ -1,17 +1,15 @@
package dev.openrs2.util.io
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.FileTime
import java.util.jar.JarInputStream
import java.util.jar.JarOutputStream
import java.util.jar.Manifest
import java.util.zip.ZipEntry
class DeterministicJarOutputStream : JarOutputStream {
private constructor(out: OutputStream) : super(out)
private constructor(out: OutputStream, man: Manifest) : super(out, man)
constructor(out: OutputStream) : super(out)
constructor(out: OutputStream, man: Manifest) : super(out, man)
override fun putNextEntry(ze: ZipEntry) {
ze.creationTime = UNIX_EPOCH
@ -22,25 +20,5 @@ class DeterministicJarOutputStream : JarOutputStream {
companion object {
private val UNIX_EPOCH = FileTime.fromMillis(0)
fun create(out: OutputStream, manifest: Manifest? = null): JarOutputStream {
return if (manifest != null) {
DeterministicJarOutputStream(out, manifest)
} else {
DeterministicJarOutputStream(out)
}
}
fun repack(src: Path, dest: Path) {
JarInputStream(Files.newInputStream(src)).use { input ->
create(Files.newOutputStream(dest), input.manifest).use { output ->
while (true) {
val entry = input.nextJarEntry ?: break
output.putNextEntry(entry)
input.copyTo(output)
}
}
}
}
}
}

Loading…
Cancel
Save