From c849c5ad4651a3b2404bd65a195821472a2d83bb Mon Sep 17 00:00:00 2001 From: Graham Date: Thu, 2 Apr 2020 19:07:06 +0100 Subject: [PATCH] Add command-line tools for headerless compression These are useful for manipulating the files used by the loader, some of which are compressed with DEFLATE or headerless gzip. Signed-off-by: Graham --- all/build.gradle.kts | 1 + all/src/main/java/dev/openrs2/Command.kt | 2 + cli/build.gradle.kts | 24 +++++++++++ .../java/dev/openrs2/cli/OptionExtensions.kt | 40 +++++++++++++++++++ compress-cli/build.gradle.kts | 32 +++++++++++++++ .../openrs2/compress/cli/CompressCommand.kt | 29 ++++++++++++++ .../compress/cli/bzip2/Bunzip2Command.kt | 22 ++++++++++ .../compress/cli/bzip2/Bzip2Command.kt | 22 ++++++++++ .../compress/cli/deflate/DeflateCommand.kt | 31 ++++++++++++++ .../compress/cli/deflate/InflateCommand.kt | 23 +++++++++++ .../compress/cli/gzip/GunzipCommand.kt | 22 ++++++++++ .../openrs2/compress/cli/gzip/GzipCommand.kt | 31 ++++++++++++++ settings.gradle.kts | 2 + 13 files changed, 281 insertions(+) create mode 100644 cli/build.gradle.kts create mode 100644 cli/src/main/java/dev/openrs2/cli/OptionExtensions.kt create mode 100644 compress-cli/build.gradle.kts create mode 100644 compress-cli/src/main/java/dev/openrs2/compress/cli/CompressCommand.kt create mode 100644 compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bunzip2Command.kt create mode 100644 compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bzip2Command.kt create mode 100644 compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/DeflateCommand.kt create mode 100644 compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/InflateCommand.kt create mode 100644 compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GunzipCommand.kt create mode 100644 compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GzipCommand.kt diff --git a/all/build.gradle.kts b/all/build.gradle.kts index d5d1d66b..a14ef778 100644 --- a/all/build.gradle.kts +++ b/all/build.gradle.kts @@ -16,6 +16,7 @@ application { dependencies { implementation(project(":bundler")) + implementation(project(":compress-cli")) implementation(project(":decompiler")) implementation(project(":deob")) implementation(project(":deob-ast")) diff --git a/all/src/main/java/dev/openrs2/Command.kt b/all/src/main/java/dev/openrs2/Command.kt index ee68cb20..657105cf 100644 --- a/all/src/main/java/dev/openrs2/Command.kt +++ b/all/src/main/java/dev/openrs2/Command.kt @@ -3,6 +3,7 @@ package dev.openrs2 import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.subcommands import dev.openrs2.bundler.BundleCommand +import dev.openrs2.compress.cli.CompressCommand import dev.openrs2.decompiler.DecompileCommand import dev.openrs2.deob.DeobfuscateCommand import dev.openrs2.deob.ast.AstDeobfuscateCommand @@ -10,6 +11,7 @@ import dev.openrs2.game.GameCommand fun main(args: Array) = Command().subcommands( BundleCommand(), + CompressCommand(), DecompileCommand(), DeobfuscateCommand(), AstDeobfuscateCommand(), diff --git a/cli/build.gradle.kts b/cli/build.gradle.kts new file mode 100644 index 00000000..2d576f42 --- /dev/null +++ b/cli/build.gradle.kts @@ -0,0 +1,24 @@ +plugins { + `maven-publish` + kotlin("jvm") +} + +dependencies { + api("com.github.ajalt:clikt:${Versions.clikt}") +} + +publishing { + publications.create("maven") { + from(components["java"]) + + pom { + packaging = "jar" + name.set("OpenRS2 CLI") + description.set( + """ + Clikt extensions. + """.trimIndent() + ) + } + } +} diff --git a/cli/src/main/java/dev/openrs2/cli/OptionExtensions.kt b/cli/src/main/java/dev/openrs2/cli/OptionExtensions.kt new file mode 100644 index 00000000..0f56e45f --- /dev/null +++ b/cli/src/main/java/dev/openrs2/cli/OptionExtensions.kt @@ -0,0 +1,40 @@ +package dev.openrs2.cli + +import com.github.ajalt.clikt.parameters.options.NullableOption +import com.github.ajalt.clikt.parameters.options.OptionWithValues +import com.github.ajalt.clikt.parameters.options.RawOption +import com.github.ajalt.clikt.parameters.options.convert +import com.github.ajalt.clikt.parameters.options.default +import java.io.InputStream +import java.io.OutputStream +import java.nio.file.Files + +fun RawOption.inputStream(): NullableOption { + return convert("FILE") { + return@convert if (it == "-") { + System.`in` + } else { + Files.newInputStream(java.nio.file.Paths.get(it)) + } + } +} + +fun NullableOption.defaultStdin(): OptionWithValues { + return default(System.`in`, "-") +} + +fun RawOption.outputStream(): NullableOption { + return convert("FILE") { + return@convert if (it == "-") { + System.out + } else { + Files.newOutputStream(java.nio.file.Paths.get(it)) + } + } +} + +fun NullableOption.defaultStdout(): OptionWithValues { + + return default(System.out, "-") +} diff --git a/compress-cli/build.gradle.kts b/compress-cli/build.gradle.kts new file mode 100644 index 00000000..4da0b9ce --- /dev/null +++ b/compress-cli/build.gradle.kts @@ -0,0 +1,32 @@ +plugins { + `maven-publish` + application + kotlin("jvm") +} + +application { + mainClassName = "dev.openrs2.compress.cli.CompressCommandKt" +} + +dependencies { + api(project(":cli")) + + implementation(project(":compress")) +} + +publishing { + publications.create("maven") { + from(components["java"]) + + pom { + packaging = "jar" + name.set("OpenRS2 Compression CLI") + description.set( + """ + Tools for compressing and uncompressing headerless bzip2, + DEFLATE and gzip files. + """.trimIndent() + ) + } + } +} diff --git a/compress-cli/src/main/java/dev/openrs2/compress/cli/CompressCommand.kt b/compress-cli/src/main/java/dev/openrs2/compress/cli/CompressCommand.kt new file mode 100644 index 00000000..83154c6b --- /dev/null +++ b/compress-cli/src/main/java/dev/openrs2/compress/cli/CompressCommand.kt @@ -0,0 +1,29 @@ +package dev.openrs2.compress.cli + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands +import dev.openrs2.compress.cli.bzip2.Bunzip2Command +import dev.openrs2.compress.cli.bzip2.Bzip2Command +import dev.openrs2.compress.cli.deflate.DeflateCommand +import dev.openrs2.compress.cli.deflate.InflateCommand +import dev.openrs2.compress.cli.gzip.GunzipCommand +import dev.openrs2.compress.cli.gzip.GzipCommand + +fun main(args: Array) = CompressCommand().main(args) + +class CompressCommand : CliktCommand(name = "compress") { + init { + subcommands( + Bzip2Command(), + Bunzip2Command(), + DeflateCommand(), + InflateCommand(), + GzipCommand(), + GunzipCommand() + ) + } + + override fun run() { + // empty + } +} diff --git a/compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bunzip2Command.kt b/compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bunzip2Command.kt new file mode 100644 index 00000000..fba13f08 --- /dev/null +++ b/compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bunzip2Command.kt @@ -0,0 +1,22 @@ +package dev.openrs2.compress.cli.bzip2 + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import dev.openrs2.cli.defaultStdin +import dev.openrs2.cli.defaultStdout +import dev.openrs2.cli.inputStream +import dev.openrs2.cli.outputStream +import dev.openrs2.compress.bzip2.Bzip2 + +class Bunzip2Command : CliktCommand(name = "bunzip2") { + private val input by option().inputStream().defaultStdin() + private val output by option().outputStream().defaultStdout() + + override fun run() { + Bzip2.createHeaderlessInputStream(input).use { input -> + output.use { output -> + input.copyTo(output) + } + } + } +} diff --git a/compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bzip2Command.kt b/compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bzip2Command.kt new file mode 100644 index 00000000..f129a624 --- /dev/null +++ b/compress-cli/src/main/java/dev/openrs2/compress/cli/bzip2/Bzip2Command.kt @@ -0,0 +1,22 @@ +package dev.openrs2.compress.cli.bzip2 + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import dev.openrs2.cli.defaultStdin +import dev.openrs2.cli.defaultStdout +import dev.openrs2.cli.inputStream +import dev.openrs2.cli.outputStream +import dev.openrs2.compress.bzip2.Bzip2 + +class Bzip2Command : CliktCommand(name = "bzip2") { + private val input by option().inputStream().defaultStdin() + private val output by option().outputStream().defaultStdout() + + override fun run() { + input.use { input -> + Bzip2.createHeaderlessOutputStream(output).use { output -> + input.copyTo(output) + } + } + } +} diff --git a/compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/DeflateCommand.kt b/compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/DeflateCommand.kt new file mode 100644 index 00000000..42c70f7f --- /dev/null +++ b/compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/DeflateCommand.kt @@ -0,0 +1,31 @@ +package dev.openrs2.compress.cli.deflate + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.validate +import com.github.ajalt.clikt.parameters.types.int +import dev.openrs2.cli.defaultStdin +import dev.openrs2.cli.defaultStdout +import dev.openrs2.cli.inputStream +import dev.openrs2.cli.outputStream +import java.util.zip.Deflater +import java.util.zip.DeflaterOutputStream + +class DeflateCommand : CliktCommand(name = "deflate") { + private val input by option().inputStream().defaultStdin() + private val output by option().outputStream().defaultStdout() + private val level by option().int().default(Deflater.BEST_COMPRESSION).validate { + require(it >= Deflater.NO_COMPRESSION && it <= Deflater.BEST_COMPRESSION) { + "--level must be between ${Deflater.NO_COMPRESSION} and ${Deflater.BEST_COMPRESSION} inclusive" + } + } + + override fun run() { + input.use { input -> + DeflaterOutputStream(output, Deflater(level, true)).use { output -> + input.copyTo(output) + } + } + } +} diff --git a/compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/InflateCommand.kt b/compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/InflateCommand.kt new file mode 100644 index 00000000..b5aa25b3 --- /dev/null +++ b/compress-cli/src/main/java/dev/openrs2/compress/cli/deflate/InflateCommand.kt @@ -0,0 +1,23 @@ +package dev.openrs2.compress.cli.deflate + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import dev.openrs2.cli.defaultStdin +import dev.openrs2.cli.defaultStdout +import dev.openrs2.cli.inputStream +import dev.openrs2.cli.outputStream +import java.util.zip.Inflater +import java.util.zip.InflaterInputStream + +class InflateCommand : CliktCommand(name = "inflate") { + private val input by option().inputStream().defaultStdin() + private val output by option().outputStream().defaultStdout() + + override fun run() { + InflaterInputStream(input, Inflater(true)).use { input -> + output.use { output -> + input.copyTo(output) + } + } + } +} diff --git a/compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GunzipCommand.kt b/compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GunzipCommand.kt new file mode 100644 index 00000000..26970c9a --- /dev/null +++ b/compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GunzipCommand.kt @@ -0,0 +1,22 @@ +package dev.openrs2.compress.cli.gzip + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import dev.openrs2.cli.defaultStdin +import dev.openrs2.cli.defaultStdout +import dev.openrs2.cli.inputStream +import dev.openrs2.cli.outputStream +import dev.openrs2.compress.gzip.Gzip + +class GunzipCommand : CliktCommand(name = "gunzip") { + private val input by option().inputStream().defaultStdin() + private val output by option().outputStream().defaultStdout() + + override fun run() { + Gzip.createHeaderlessInputStream(input).use { input -> + output.use { output -> + input.copyTo(output) + } + } + } +} diff --git a/compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GzipCommand.kt b/compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GzipCommand.kt new file mode 100644 index 00000000..80bc35f9 --- /dev/null +++ b/compress-cli/src/main/java/dev/openrs2/compress/cli/gzip/GzipCommand.kt @@ -0,0 +1,31 @@ +package dev.openrs2.compress.cli.gzip + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.default +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.validate +import com.github.ajalt.clikt.parameters.types.int +import dev.openrs2.cli.defaultStdin +import dev.openrs2.cli.defaultStdout +import dev.openrs2.cli.inputStream +import dev.openrs2.cli.outputStream +import dev.openrs2.compress.gzip.Gzip +import java.util.zip.Deflater + +class GzipCommand : CliktCommand(name = "gzip") { + private val input by option().inputStream().defaultStdin() + private val output by option().outputStream().defaultStdout() + private val level by option().int().default(Deflater.BEST_COMPRESSION).validate { + require(it >= Deflater.NO_COMPRESSION && it <= Deflater.BEST_COMPRESSION) { + "--level must be between ${Deflater.NO_COMPRESSION} and ${Deflater.BEST_COMPRESSION} inclusive" + } + } + + override fun run() { + input.use { input -> + Gzip.createHeaderlessOutputStream(output, level).use { output -> + input.copyTo(output) + } + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 5225c18d..55a4b761 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,8 +6,10 @@ include( "all", "asm", "bundler", + "cli", "common", "compress", + "compress-cli", "decompiler", "deob", "deob-annotations",