mirror of
https://github.com/ReVanced/revanced-cli.git
synced 2026-01-18 00:43:58 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d954424b30 | ||
|
|
168c003a31 | ||
|
|
96ec6a0384 | ||
|
|
1cd0976415 | ||
|
|
d8dbffd7a7 | ||
|
|
5f2bccc4e5 | ||
|
|
5af2f10e14 | ||
|
|
40c8500e38 | ||
|
|
9add2f9e82 | ||
|
|
a942a57364 | ||
|
|
718df54bdc | ||
|
|
507b6f3409 | ||
|
|
c183c38f2a | ||
|
|
d9c5a179c5 |
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
9
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -3,21 +3,22 @@ name: Bug report
|
|||||||
about: Create a bug report on the CLI. Do not submit suggestions for patches here.
|
about: Create a bug report on the CLI. Do not submit suggestions for patches here.
|
||||||
title: 'problem: some problem'
|
title: 'problem: some problem'
|
||||||
labels: bug
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 🐞 Issue
|
## 🐞 Issue
|
||||||
|
|
||||||
<!-- Describe your issue in detail here -->
|
<!-- Describe your issue in detail here -->
|
||||||
|
|
||||||
# ⚙ Reproduce
|
## ⚙ Reproduce
|
||||||
|
|
||||||
<!-- Include your environment and steps to reproduce the issue as detailed as possible -->
|
<!-- Include your environment and steps to reproduce the issue as detailed as possible -->
|
||||||
|
|
||||||
# 🛠 Solution
|
## 🛠 Solution
|
||||||
|
|
||||||
<!-- If applicable, add a possible solution -->
|
<!-- If applicable, add a possible solution -->
|
||||||
|
|
||||||
# ⚠ Additional context
|
## ⚠ Additional context
|
||||||
|
|
||||||
<!-- Add any other context about the problem here -->
|
<!-- Add any other context about the problem here -->
|
||||||
|
|||||||
21
.github/ISSUE_TEMPLATE/feature_request.md
vendored
21
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -3,17 +3,22 @@ name: Feature request
|
|||||||
about: Suggest a change to the CLI. Do not submit suggestions for patches here.
|
about: Suggest a change to the CLI. Do not submit suggestions for patches here.
|
||||||
title: 'feat: some feature'
|
title: 'feat: some feature'
|
||||||
labels: feature-request
|
labels: feature-request
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
## 🐞 Issue
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
<!-- Describe your issue in detail here -->
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
## ⚙ Reproduce
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
<!-- Include your environment and steps to reproduce the issue as detailed as possible -->
|
||||||
Add any other context or screenshots about the feature request here.
|
|
||||||
|
## 🛠 Solution
|
||||||
|
|
||||||
|
<!-- If applicable, add a possible solution -->
|
||||||
|
|
||||||
|
## ⚠ Additional context
|
||||||
|
|
||||||
|
<!-- Add any other context about the problem here -->
|
||||||
|
|||||||
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,3 +1,31 @@
|
|||||||
|
## [2.7.1](https://github.com/revanced/revanced-cli/compare/v2.7.0...v2.7.1) (2022-07-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* align every file ([96ec6a0](https://github.com/revanced/revanced-cli/commit/96ec6a0384e4c710c36b99ccf88bb14c5addc7af))
|
||||||
|
|
||||||
|
# [2.7.0](https://github.com/revanced/revanced-cli/compare/v2.6.0...v2.7.0) (2022-07-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* `--custom-aapt2-binary` option ([#104](https://github.com/revanced/revanced-cli/issues/104)) ([d8dbffd](https://github.com/revanced/revanced-cli/commit/d8dbffd7a7ebc583476d368d2ae78853cbb40382))
|
||||||
|
|
||||||
|
# [2.6.0](https://github.com/revanced/revanced-cli/compare/v2.5.3...v2.6.0) (2022-07-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* more efficient zipalign ([a942a57](https://github.com/revanced/revanced-cli/commit/a942a57364777d6ab6fcb11e6171cc4b496c8540))
|
||||||
|
|
||||||
|
## [2.5.3](https://github.com/revanced/revanced-cli/compare/v2.5.2...v2.5.3) (2022-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Log not showing in CLI ([#80](https://github.com/revanced/revanced-cli/issues/80)) ([d9c5a17](https://github.com/revanced/revanced-cli/commit/d9c5a179c529c386f66df4fa63cd321d396836b5)), closes [#79](https://github.com/revanced/revanced-cli/issues/79)
|
||||||
|
|
||||||
## [2.5.2](https://github.com/revanced/revanced-cli/compare/v2.5.1...v2.5.2) (2022-07-10)
|
## [2.5.2](https://github.com/revanced/revanced-cli/compare/v2.5.1...v2.5.2) (2022-07-10)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 2.5.2
|
version = 2.7.1
|
||||||
|
|||||||
12
src/main/kotlin/app/revanced/cli/aligning/Aligning.kt
Normal file
12
src/main/kotlin/app/revanced/cli/aligning/Aligning.kt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.cli.aligning
|
||||||
|
|
||||||
|
import app.revanced.cli.command.MainCommand.logger
|
||||||
|
import app.revanced.utils.signing.align.ZipAligner
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object Aligning {
|
||||||
|
fun align(inputFile: File, outputFile: File) {
|
||||||
|
logger.info("Aligning ${inputFile.name} to ${outputFile.name}")
|
||||||
|
ZipAligner.align(inputFile, outputFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.revanced.cli.command
|
package app.revanced.cli.command
|
||||||
|
|
||||||
|
import app.revanced.cli.aligning.Aligning
|
||||||
import app.revanced.cli.logging.impl.DefaultCliLogger
|
import app.revanced.cli.logging.impl.DefaultCliLogger
|
||||||
import app.revanced.cli.patcher.Patcher
|
import app.revanced.cli.patcher.Patcher
|
||||||
import app.revanced.cli.patcher.logging.impl.PatcherLogger
|
import app.revanced.cli.patcher.logging.impl.PatcherLogger
|
||||||
@@ -43,18 +44,18 @@ internal object MainCommand : Runnable {
|
|||||||
var deploy: String? = null
|
var deploy: String? = null
|
||||||
|
|
||||||
@ArgGroup(exclusive = false)
|
@ArgGroup(exclusive = false)
|
||||||
var sArgs: StartPatcherArgs? = null
|
var patchArgs: PatchArgs? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
class StartPatcherArgs {
|
class PatchArgs {
|
||||||
@Option(names = ["-b", "--bundles"], description = ["One or more bundles of patches"], required = true)
|
@Option(names = ["-b", "--bundles"], description = ["One or more bundles of patches"], required = true)
|
||||||
var patchBundles = arrayOf<String>()
|
var patchBundles = arrayOf<String>()
|
||||||
|
|
||||||
@ArgGroup(exclusive = false)
|
@ArgGroup(exclusive = false)
|
||||||
var lArgs: ListingArgs? = null
|
var listingArgs: ListingArgs? = null
|
||||||
|
|
||||||
@ArgGroup(exclusive = false)
|
@ArgGroup(exclusive = false)
|
||||||
var pArgs: PatchingArgs? = null
|
var patchingArgs: PatchingArgs? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
class ListingArgs {
|
class ListingArgs {
|
||||||
@@ -78,7 +79,10 @@ internal object MainCommand : Runnable {
|
|||||||
@Option(names = ["-e", "--exclude"], description = ["Explicitly exclude patches"])
|
@Option(names = ["-e", "--exclude"], description = ["Explicitly exclude patches"])
|
||||||
var excludedPatches = arrayOf<String>()
|
var excludedPatches = arrayOf<String>()
|
||||||
|
|
||||||
@Option(names = ["--exclusive"], description = ["Only installs the patches you include, not including any patch by default"])
|
@Option(
|
||||||
|
names = ["--exclusive"],
|
||||||
|
description = ["Only installs the patches you include, not including any patch by default"]
|
||||||
|
)
|
||||||
var defaultExclude = false
|
var defaultExclude = false
|
||||||
|
|
||||||
@Option(names = ["-i", "--include"], description = ["Include patches"])
|
@Option(names = ["-i", "--include"], description = ["Include patches"])
|
||||||
@@ -113,85 +117,120 @@ internal object MainCommand : Runnable {
|
|||||||
description = ["Clean the temporal resource cache directory. This will be done anyways when running the patcher"]
|
description = ["Clean the temporal resource cache directory. This will be done anyways when running the patcher"]
|
||||||
)
|
)
|
||||||
var clean: Boolean = false
|
var clean: Boolean = false
|
||||||
|
|
||||||
|
@Option(names = ["--custom-aapt2-binary"], description = ["Path to custom aapt2 binary"])
|
||||||
|
var aaptPath: String = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (args.sArgs?.lArgs?.listOnly == true) {
|
if (args.patchArgs?.listingArgs?.listOnly == true) {
|
||||||
printListOfPatches()
|
printListOfPatches()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.uninstall) {
|
if (args.uninstall) {
|
||||||
// temporarily get package name using Patcher method
|
uninstall()
|
||||||
// fix: abstract options in patcher
|
|
||||||
val patcher = app.revanced.patcher.Patcher(
|
|
||||||
PatcherOptions(
|
|
||||||
args.inputFile,
|
|
||||||
"uninstaller-cache",
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
File("uninstaller-cache").deleteRecursively()
|
|
||||||
|
|
||||||
val adb: Adb? = args.deploy?.let {
|
|
||||||
Adb(File("placeholder_file"), patcher.data.packageMetadata.packageName, args.deploy!!, false)
|
|
||||||
}
|
|
||||||
adb?.uninstall()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val _args = args
|
val pArgs = this.args.patchArgs?.patchingArgs ?: return
|
||||||
val args = args.sArgs?.pArgs ?: return
|
|
||||||
|
// the file to write to
|
||||||
|
val outputFile = File(pArgs.outputPath)
|
||||||
|
|
||||||
val patcher = app.revanced.patcher.Patcher(
|
val patcher = app.revanced.patcher.Patcher(
|
||||||
PatcherOptions(
|
PatcherOptions(
|
||||||
_args.inputFile,
|
args.inputFile,
|
||||||
args.cacheDirectory,
|
pArgs.cacheDirectory,
|
||||||
!args.disableResourcePatching,
|
!pArgs.disableResourcePatching,
|
||||||
|
pArgs.aaptPath,
|
||||||
logger = PatcherLogger
|
logger = PatcherLogger
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val outputFile = File(args.outputPath)
|
// prepare adb
|
||||||
|
val adb: Adb? = args.deploy?.let {
|
||||||
val adb: Adb? = _args.deploy?.let {
|
Adb(outputFile, patcher.data.packageMetadata.packageName, args.deploy!!, !pArgs.mount)
|
||||||
Adb(outputFile, patcher.data.packageMetadata.packageName, _args.deploy!!, !args.mount)
|
|
||||||
}
|
}
|
||||||
val patchedFile = if (args.mount) outputFile
|
|
||||||
else File(args.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk")
|
|
||||||
|
|
||||||
|
val patchedFile = File(pArgs.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk")
|
||||||
|
|
||||||
|
// start the patcher
|
||||||
Patcher.start(patcher, patchedFile)
|
Patcher.start(patcher, patchedFile)
|
||||||
|
|
||||||
if (!args.mount) {
|
val cacheDirectory = File(pArgs.cacheDirectory)
|
||||||
Signing.start(
|
|
||||||
patchedFile,
|
// align the file
|
||||||
outputFile,
|
val alignedFile = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
|
||||||
|
Aligning.align(patchedFile, alignedFile)
|
||||||
|
|
||||||
|
// sign the file
|
||||||
|
val finalFile = if (!pArgs.mount) {
|
||||||
|
val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk")
|
||||||
|
Signing.sign(
|
||||||
|
alignedFile,
|
||||||
|
signedOutput,
|
||||||
SigningOptions(
|
SigningOptions(
|
||||||
args.cn,
|
pArgs.cn,
|
||||||
args.password,
|
pArgs.password,
|
||||||
args.keystorePath ?: outputFile.absoluteFile.parentFile
|
pArgs.keystorePath ?: outputFile.absoluteFile.parentFile
|
||||||
.resolve("${outputFile.nameWithoutExtension}.keystore")
|
.resolve("${outputFile.nameWithoutExtension}.keystore")
|
||||||
.canonicalPath
|
.canonicalPath
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if (args.clean) File(args.cacheDirectory).deleteRecursively()
|
signedOutput
|
||||||
|
} else
|
||||||
|
alignedFile
|
||||||
|
|
||||||
|
// finally copy to the specified output file
|
||||||
|
logger.info("Copying ${finalFile.name} to ${outputFile.name}")
|
||||||
|
finalFile.copyTo(outputFile, overwrite = true)
|
||||||
|
|
||||||
|
// clean up the cache directory if needed
|
||||||
|
if (pArgs.clean)
|
||||||
|
cleanUp(pArgs.cacheDirectory)
|
||||||
|
|
||||||
|
// deploy if specified
|
||||||
adb?.deploy()
|
adb?.deploy()
|
||||||
|
|
||||||
if (args.clean && _args.deploy != null) Files.delete(outputFile.toPath())
|
if (pArgs.clean && args.deploy != null) Files.delete(outputFile.toPath())
|
||||||
|
|
||||||
logger.info("Finished")
|
logger.info("Finished")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cleanUp(cacheDirectory: String) {
|
||||||
|
val result = if (File(cacheDirectory).deleteRecursively())
|
||||||
|
"Cleaned up cache directory"
|
||||||
|
else
|
||||||
|
"Failed to clean up cache directory"
|
||||||
|
logger.info(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uninstall() {
|
||||||
|
// temporarily get package name using Patcher method
|
||||||
|
// fix: abstract options in patcher
|
||||||
|
val patcher = app.revanced.patcher.Patcher(
|
||||||
|
PatcherOptions(
|
||||||
|
args.inputFile,
|
||||||
|
"uninstaller-cache",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
File("uninstaller-cache").deleteRecursively()
|
||||||
|
|
||||||
|
val adb: Adb? = args.deploy?.let {
|
||||||
|
Adb(File("placeholder_file"), patcher.data.packageMetadata.packageName, args.deploy!!, false)
|
||||||
|
}
|
||||||
|
adb?.uninstall()
|
||||||
|
}
|
||||||
|
|
||||||
private fun printListOfPatches() {
|
private fun printListOfPatches() {
|
||||||
for (patchBundlePath in args.sArgs?.patchBundles!!) for (patch in JarPatchBundle(patchBundlePath).loadPatches()) {
|
for (patchBundlePath in args.patchArgs?.patchBundles!!) for (patch in JarPatchBundle(patchBundlePath).loadPatches()) {
|
||||||
for (compatiblePackage in patch.compatiblePackages!!) {
|
for (compatiblePackage in patch.compatiblePackages!!) {
|
||||||
val packageEntryStr = buildString {
|
val packageEntryStr = buildString {
|
||||||
// Add package if flag is set
|
// Add package if flag is set
|
||||||
if (args.sArgs?.lArgs?.withPackages == true) {
|
if (args.patchArgs?.listingArgs?.withPackages == true) {
|
||||||
val packageName = compatiblePackage.name.substringAfterLast(".").padStart(10)
|
val packageName = compatiblePackage.name.substringAfterLast(".").padStart(10)
|
||||||
append(packageName)
|
append(packageName)
|
||||||
append("\t")
|
append("\t")
|
||||||
@@ -200,12 +239,12 @@ internal object MainCommand : Runnable {
|
|||||||
val patchName = patch.patchName.padStart(25)
|
val patchName = patch.patchName.padStart(25)
|
||||||
append(patchName)
|
append(patchName)
|
||||||
// Add description if flag is set.
|
// Add description if flag is set.
|
||||||
if (args.sArgs?.lArgs?.withDescriptions == true) {
|
if (args.patchArgs?.listingArgs?.withDescriptions == true) {
|
||||||
append("\t")
|
append("\t")
|
||||||
append(patch.description)
|
append(patch.description)
|
||||||
}
|
}
|
||||||
// Add compatible versions, if flag is set
|
// Add compatible versions, if flag is set
|
||||||
if (args.sArgs?.lArgs?.withVersions == true) {
|
if (args.patchArgs?.listingArgs?.withVersions == true) {
|
||||||
val compatibleVersions = compatiblePackage.versions.joinToString(separator = ", ")
|
val compatibleVersions = compatiblePackage.versions.joinToString(separator = ", ")
|
||||||
append("\t")
|
append("\t")
|
||||||
append(compatibleVersions)
|
append(compatibleVersions)
|
||||||
|
|||||||
@@ -4,17 +4,19 @@ import app.revanced.cli.command.MainCommand
|
|||||||
import app.revanced.cli.logging.CliLogger
|
import app.revanced.cli.logging.CliLogger
|
||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
import java.util.logging.SimpleFormatter
|
import java.util.logging.SimpleFormatter
|
||||||
import java.util.logging.StreamHandler
|
|
||||||
|
|
||||||
internal class DefaultCliLogger(
|
internal class DefaultCliLogger(
|
||||||
private val logger: Logger = Logger.getLogger(MainCommand::javaClass.name),
|
private val logger: Logger = Logger.getLogger(MainCommand::class.java.name),
|
||||||
private val errorLogger: Logger = Logger.getLogger(MainCommand::javaClass.name + "Err")
|
private val errorLogger: Logger = Logger.getLogger(logger.name + "Err")
|
||||||
) : CliLogger {
|
) : CliLogger {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
logger.useParentHandlers = false
|
logger.useParentHandlers = false
|
||||||
logger.addHandler(StreamHandler(System.out, SimpleFormatter()))
|
if (logger.handlers.isEmpty()) {
|
||||||
|
logger.addHandler(FlushingStreamHandler(System.out, SimpleFormatter()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
init {
|
init {
|
||||||
System.setProperty("java.util.logging.SimpleFormatter.format", "%4\$s: %5\$s %n")
|
System.setProperty("java.util.logging.SimpleFormatter.format", "%4\$s: %5\$s %n")
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.cli.logging.impl
|
||||||
|
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.util.logging.Formatter
|
||||||
|
import java.util.logging.LogRecord
|
||||||
|
import java.util.logging.StreamHandler
|
||||||
|
|
||||||
|
internal class FlushingStreamHandler(out: OutputStream, format: Formatter) : StreamHandler(out, format) {
|
||||||
|
override fun publish(record: LogRecord) {
|
||||||
|
super.publish(record)
|
||||||
|
flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ import java.nio.file.Files
|
|||||||
internal object Patcher {
|
internal object Patcher {
|
||||||
internal fun start(patcher: app.revanced.patcher.Patcher, output: File) {
|
internal fun start(patcher: app.revanced.patcher.Patcher, output: File) {
|
||||||
val inputFile = args.inputFile
|
val inputFile = args.inputFile
|
||||||
val args = args.sArgs?.pArgs!!
|
val args = args.patchArgs?.patchingArgs!!
|
||||||
|
|
||||||
// merge files like necessary integrations
|
// merge files like necessary integrations
|
||||||
patcher.mergeFiles()
|
patcher.mergeFiles()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import app.revanced.cli.logging.impl.DefaultCliLogger
|
|||||||
import java.util.logging.Logger
|
import java.util.logging.Logger
|
||||||
|
|
||||||
internal object PatcherLogger : app.revanced.patcher.logging.Logger{
|
internal object PatcherLogger : app.revanced.patcher.logging.Logger{
|
||||||
private val logger = DefaultCliLogger(Logger.getLogger(app.revanced.patcher.Patcher::javaClass.name))
|
private val logger = DefaultCliLogger(Logger.getLogger(app.revanced.patcher.Patcher::class.java.name))
|
||||||
|
|
||||||
override fun error(msg: String) = logger.error(msg)
|
override fun error(msg: String) = logger.error(msg)
|
||||||
override fun info(msg: String) = logger.info(msg)
|
override fun info(msg: String) = logger.info(msg)
|
||||||
|
|||||||
@@ -1,28 +1,12 @@
|
|||||||
package app.revanced.cli.signing
|
package app.revanced.cli.signing
|
||||||
|
|
||||||
import app.revanced.cli.command.MainCommand.args
|
|
||||||
import app.revanced.cli.command.MainCommand.logger
|
import app.revanced.cli.command.MainCommand.logger
|
||||||
import app.revanced.utils.signing.Signer
|
import app.revanced.utils.signing.Signer
|
||||||
import app.revanced.utils.signing.align.ZipAligner
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object Signing {
|
object Signing {
|
||||||
fun start(inputFile: File, outputFile: File, signingOptions: SigningOptions) {
|
fun sign(alignedFile: File, signedOutput: File, signingOptions: SigningOptions) {
|
||||||
val cacheDirectory = File(args.sArgs?.pArgs?.cacheDirectory)
|
logger.info("Signing ${alignedFile.name} to ${signedOutput.name}")
|
||||||
val alignedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_aligned.apk")
|
Signer(signingOptions).signApk(alignedFile, signedOutput)
|
||||||
val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk")
|
|
||||||
|
|
||||||
// align the inputFile and write to alignedOutput
|
|
||||||
logger.info("Aligning ${inputFile.name}")
|
|
||||||
ZipAligner.align(inputFile, alignedOutput)
|
|
||||||
// sign the alignedOutput and write to signedOutput
|
|
||||||
// the reason is, in case the signer fails
|
|
||||||
// it does not damage the output file
|
|
||||||
logger.info("Signing ${alignedOutput.name}")
|
|
||||||
Signer(signingOptions).signApk(alignedOutput, signedOutput)
|
|
||||||
|
|
||||||
// afterwards copy over the file to the output
|
|
||||||
logger.info("Copying ${signedOutput.name} to ${outputFile.name}")
|
|
||||||
signedOutput.copyTo(outputFile, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ fun Patcher.addPatchesFiltered() {
|
|||||||
val packageName = this.data.packageMetadata.packageName
|
val packageName = this.data.packageMetadata.packageName
|
||||||
val packageVersion = this.data.packageMetadata.packageVersion
|
val packageVersion = this.data.packageMetadata.packageVersion
|
||||||
|
|
||||||
args.sArgs?.patchBundles!!.forEach { bundle ->
|
args.patchArgs?.patchBundles!!.forEach { bundle ->
|
||||||
val includedPatches = mutableListOf<Class<out Patch<Data>>>()
|
val includedPatches = mutableListOf<Class<out Patch<Data>>>()
|
||||||
JarPatchBundle(bundle).loadPatches().forEach patch@{ patch ->
|
JarPatchBundle(bundle).loadPatches().forEach patch@{ patch ->
|
||||||
val compatiblePackages = patch.compatiblePackages
|
val compatiblePackages = patch.compatiblePackages
|
||||||
@@ -23,10 +23,10 @@ fun Patcher.addPatchesFiltered() {
|
|||||||
|
|
||||||
val prefix = "Skipping $patchName"
|
val prefix = "Skipping $patchName"
|
||||||
|
|
||||||
val args = MainCommand.args.sArgs?.pArgs!!
|
val args = MainCommand.args.patchArgs?.patchingArgs!!
|
||||||
|
|
||||||
if (args.excludedPatches.contains(patchName)) {
|
if (args.excludedPatches.contains(patchName)) {
|
||||||
logger.info("$prefix: Explicitely excluded")
|
logger.info("$prefix: Explicitly excluded")
|
||||||
return@patch
|
return@patch
|
||||||
} else if ((!patch.include || args.defaultExclude) && !args.includedPatches.contains(patchName)) {
|
} else if ((!patch.include || args.defaultExclude) && !args.includedPatches.contains(patchName)) {
|
||||||
logger.info("$prefix: Not explicitly included")
|
logger.info("$prefix: Not explicitly included")
|
||||||
@@ -72,7 +72,7 @@ fun Patcher.applyPatchesVerbose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun Patcher.mergeFiles() {
|
fun Patcher.mergeFiles() {
|
||||||
this.addFiles(args.sArgs?.pArgs!!.mergeFiles) { file ->
|
this.addFiles(args.patchArgs?.patchingArgs!!.mergeFiles) { file ->
|
||||||
logger.info("Merging $file")
|
logger.info("Merging $file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ internal class Signer(
|
|||||||
// TODO: keystore should be saved securely
|
// TODO: keystore should be saved securely
|
||||||
val ks = File(signingOptions.keyStoreFilePath)
|
val ks = File(signingOptions.keyStoreFilePath)
|
||||||
if (!ks.exists()) newKeystore(ks) else {
|
if (!ks.exists()) newKeystore(ks) else {
|
||||||
logger.info("Found existing keystore: ${ks.nameWithoutExtension}")
|
logger.info("Found existing keystore: ${ks.name}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val keyStore = KeyStore.getInstance("BKS", "BC")
|
val keyStore = KeyStore.getInstance("BKS", "BC")
|
||||||
|
|||||||
@@ -1,63 +1,28 @@
|
|||||||
package app.revanced.utils.signing.align
|
package app.revanced.utils.signing.align
|
||||||
|
|
||||||
import app.revanced.utils.signing.align.stream.MultiOutputStream
|
import app.revanced.utils.signing.align.zip.ZipFile
|
||||||
import app.revanced.utils.signing.align.stream.PeekingFakeStream
|
|
||||||
import java.io.BufferedOutputStream
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
|
||||||
import java.util.zip.ZipEntry
|
|
||||||
import java.util.zip.ZipFile
|
|
||||||
import java.util.zip.ZipOutputStream
|
|
||||||
|
|
||||||
internal object ZipAligner {
|
internal object ZipAligner {
|
||||||
fun align(input: File, output: File, alignment: Int = 4) {
|
private const val DEFAULT_ALIGNMENT = 4
|
||||||
val zipFile = ZipFile(input)
|
private const val LIBRARY_ALIGNMENT = 4096
|
||||||
|
|
||||||
val entries: Enumeration<out ZipEntry?> = zipFile.entries()
|
fun align(input: File, output: File) {
|
||||||
|
val inputZip = ZipFile(input)
|
||||||
|
val outputZip = ZipFile(output)
|
||||||
|
|
||||||
// fake
|
for (entry in inputZip.entries) {
|
||||||
val peekingFakeStream = PeekingFakeStream()
|
val data = inputZip.getDataForEntry(entry)
|
||||||
val fakeOutputStream = ZipOutputStream(peekingFakeStream)
|
|
||||||
// real
|
|
||||||
val zipOutputStream = ZipOutputStream(BufferedOutputStream(output.outputStream()))
|
|
||||||
|
|
||||||
val multiOutputStream = MultiOutputStream(
|
if (entry.compression == 0.toUShort()) {
|
||||||
listOf(
|
val alignment = if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
||||||
fakeOutputStream, // fake, used to add the data to the fake stream
|
|
||||||
zipOutputStream // real
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
var bias = 0
|
outputZip.addEntryAligned(entry, data, alignment)
|
||||||
while (entries.hasMoreElements()) {
|
} else {
|
||||||
var padding = 0
|
outputZip.addEntry(entry, data)
|
||||||
|
|
||||||
val entry: ZipEntry = entries.nextElement()!!
|
|
||||||
// fake, used to calculate the file offset of the entry
|
|
||||||
fakeOutputStream.putNextEntry(entry)
|
|
||||||
|
|
||||||
if (entry.size == entry.compressedSize) {
|
|
||||||
val fileOffset = peekingFakeStream.peek()
|
|
||||||
val newOffset = fileOffset + bias
|
|
||||||
padding = ((alignment - (newOffset % alignment)) % alignment).toInt()
|
|
||||||
|
|
||||||
// real
|
|
||||||
entry.extra = if (entry.extra == null) ByteArray(padding)
|
|
||||||
else Arrays.copyOf(entry.extra, entry.extra.size + padding)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
zipOutputStream.putNextEntry(entry)
|
|
||||||
zipFile.getInputStream(entry).copyTo(multiOutputStream)
|
|
||||||
|
|
||||||
// fake, used to add remaining bytes
|
|
||||||
fakeOutputStream.closeEntry()
|
|
||||||
// real
|
|
||||||
zipOutputStream.closeEntry()
|
|
||||||
|
|
||||||
bias += padding
|
|
||||||
}
|
}
|
||||||
|
|
||||||
zipFile.close()
|
outputZip.finish()
|
||||||
zipOutputStream.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
package app.revanced.utils.signing.align.stream
|
|
||||||
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
internal class MultiOutputStream(
|
|
||||||
private val streams: Iterable<OutputStream>,
|
|
||||||
) : OutputStream() {
|
|
||||||
override fun write(b: ByteArray, off: Int, len: Int) = streams.forEach {
|
|
||||||
it.write(b, off, len)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(b: ByteArray) = streams.forEach {
|
|
||||||
it.write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(b: Int) = streams.forEach {
|
|
||||||
it.write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package app.revanced.utils.signing.align.stream
|
|
||||||
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
internal class PeekingFakeStream : OutputStream() {
|
|
||||||
private var numberOfBytes: Long = 0
|
|
||||||
|
|
||||||
fun peek() = numberOfBytes
|
|
||||||
|
|
||||||
override fun write(b: Int) {
|
|
||||||
numberOfBytes++
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(b: ByteArray) {
|
|
||||||
numberOfBytes += b.size
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(b: ByteArray, offset: Int, len: Int) {
|
|
||||||
numberOfBytes += len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package app.revanced.utils.signing.align.zip
|
||||||
|
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.io.DataOutput
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
fun UInt.toLittleEndian() =
|
||||||
|
(((this.toInt() and 0xff000000.toInt()) shr 24) or ((this.toInt() and 0x00ff0000) shr 8) or ((this.toInt() and 0x0000ff00) shl 8) or (this.toInt() shl 24)).toUInt()
|
||||||
|
|
||||||
|
fun UShort.toLittleEndian() = (this.toUInt() shl 16).toLittleEndian().toUShort()
|
||||||
|
|
||||||
|
fun UInt.toBigEndian() = (((this.toInt() and 0xff) shl 24) or ((this.toInt() and 0xff00) shl 8)
|
||||||
|
or ((this.toInt() and 0x00ff0000) ushr 8) or (this.toInt() ushr 24)).toUInt()
|
||||||
|
|
||||||
|
fun UShort.toBigEndian() = (this.toUInt() shl 16).toBigEndian().toUShort()
|
||||||
|
|
||||||
|
fun ByteBuffer.getUShort() = this.getShort().toUShort()
|
||||||
|
fun ByteBuffer.getUInt() = this.getInt().toUInt()
|
||||||
|
|
||||||
|
fun ByteBuffer.putUShort(ushort: UShort) = this.putShort(ushort.toShort())
|
||||||
|
fun ByteBuffer.putUInt(uint: UInt) = this.putInt(uint.toInt())
|
||||||
|
|
||||||
|
fun DataInput.readUShort() = this.readShort().toUShort()
|
||||||
|
fun DataInput.readUInt() = this.readInt().toUInt()
|
||||||
|
|
||||||
|
fun DataOutput.writeUShort(ushort: UShort) = this.writeShort(ushort.toInt())
|
||||||
|
fun DataOutput.writeUInt(uint: UInt) = this.writeInt(uint.toInt())
|
||||||
|
|
||||||
|
fun DataInput.readUShortLE() = this.readUShort().toBigEndian()
|
||||||
|
fun DataInput.readUIntLE() = this.readUInt().toBigEndian()
|
||||||
|
|
||||||
|
fun DataOutput.writeUShortLE(ushort: UShort) = this.writeUShort(ushort.toLittleEndian())
|
||||||
|
fun DataOutput.writeUIntLE(uint: UInt) = this.writeUInt(uint.toLittleEndian())
|
||||||
130
src/main/kotlin/app/revanced/utils/signing/align/zip/ZipFile.kt
Normal file
130
src/main/kotlin/app/revanced/utils/signing/align/zip/ZipFile.kt
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package app.revanced.utils.signing.align.zip
|
||||||
|
|
||||||
|
import app.revanced.utils.signing.align.zip.structures.ZipEndRecord
|
||||||
|
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
||||||
|
import java.io.File
|
||||||
|
import java.io.RandomAccessFile
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.channels.FileChannel
|
||||||
|
|
||||||
|
class ZipFile(val file: File) {
|
||||||
|
var entries: MutableList<ZipEntry> = mutableListOf()
|
||||||
|
|
||||||
|
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
|
||||||
|
|
||||||
|
init {
|
||||||
|
//if file isn't empty try to load entries
|
||||||
|
if (file.length() > 0) {
|
||||||
|
val endRecord = findEndRecord()
|
||||||
|
|
||||||
|
if (endRecord.diskNumber > 0u || endRecord.totalEntries != endRecord.diskEntries)
|
||||||
|
throw IllegalArgumentException("Multi-file archives are not supported")
|
||||||
|
|
||||||
|
entries = readEntries(endRecord).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
|
//seek back to start for writing
|
||||||
|
filePointer.seek(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findEndRecord(): ZipEndRecord {
|
||||||
|
//look from end to start since end record is at the end
|
||||||
|
for (i in filePointer.length() - 1 downTo 0) {
|
||||||
|
filePointer.seek(i)
|
||||||
|
//possible beginning of signature
|
||||||
|
if (filePointer.readByte() == 0x50.toByte()) {
|
||||||
|
//seek back to get the full int
|
||||||
|
filePointer.seek(i)
|
||||||
|
val possibleSignature = filePointer.readUIntLE()
|
||||||
|
if (possibleSignature == ZipEndRecord.ECD_SIGNATURE) {
|
||||||
|
filePointer.seek(i)
|
||||||
|
return ZipEndRecord.fromECD(filePointer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("Couldn't find end record")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readEntries(endRecord: ZipEndRecord): List<ZipEntry> {
|
||||||
|
filePointer.seek(endRecord.centralDirectoryStartOffset.toLong())
|
||||||
|
|
||||||
|
val numberOfEntries = endRecord.diskEntries.toInt()
|
||||||
|
|
||||||
|
return buildList(numberOfEntries) {
|
||||||
|
for (i in 1..numberOfEntries) {
|
||||||
|
add(ZipEntry.fromCDE(filePointer).also
|
||||||
|
{
|
||||||
|
//for some reason the local extra field can be different from the central one
|
||||||
|
it.readLocalExtra(
|
||||||
|
filePointer.channel.map(
|
||||||
|
FileChannel.MapMode.READ_ONLY,
|
||||||
|
it.localHeaderOffset.toLong() + 28,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeCDE() {
|
||||||
|
val CDEStart = filePointer.channel.position().toUInt()
|
||||||
|
|
||||||
|
entries.forEach {
|
||||||
|
filePointer.channel.write(it.toCDE())
|
||||||
|
}
|
||||||
|
|
||||||
|
val entriesCount = entries.size.toUShort()
|
||||||
|
|
||||||
|
val endRecord = ZipEndRecord(
|
||||||
|
0u,
|
||||||
|
0u,
|
||||||
|
entriesCount,
|
||||||
|
entriesCount,
|
||||||
|
filePointer.channel.position().toUInt() - CDEStart,
|
||||||
|
CDEStart,
|
||||||
|
""
|
||||||
|
)
|
||||||
|
|
||||||
|
filePointer.channel.write(endRecord.toECD())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
||||||
|
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
||||||
|
|
||||||
|
filePointer.channel.write(entry.toLFH())
|
||||||
|
filePointer.channel.write(data)
|
||||||
|
|
||||||
|
entries.add(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) {
|
||||||
|
//calculate where data would end up
|
||||||
|
val dataOffset = filePointer.filePointer + entry.LFHSize
|
||||||
|
|
||||||
|
val mod = dataOffset % alignment
|
||||||
|
|
||||||
|
//wrong alignment
|
||||||
|
if (mod != 0L) {
|
||||||
|
//add padding at end of extra field
|
||||||
|
entry.localExtraField =
|
||||||
|
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
addEntry(entry, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDataForEntry(entry: ZipEntry): ByteBuffer {
|
||||||
|
return filePointer.channel.map(
|
||||||
|
FileChannel.MapMode.READ_ONLY,
|
||||||
|
entry.dataOffset.toLong(),
|
||||||
|
entry.compressedSize.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun finish() {
|
||||||
|
writeCDE()
|
||||||
|
filePointer.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package app.revanced.utils.signing.align.zip.structures
|
||||||
|
|
||||||
|
import app.revanced.utils.signing.align.zip.putUInt
|
||||||
|
import app.revanced.utils.signing.align.zip.putUShort
|
||||||
|
import app.revanced.utils.signing.align.zip.readUIntLE
|
||||||
|
import app.revanced.utils.signing.align.zip.readUShortLE
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
data class ZipEndRecord(
|
||||||
|
val diskNumber: UShort,
|
||||||
|
val startingDiskNumber: UShort,
|
||||||
|
val diskEntries: UShort,
|
||||||
|
val totalEntries: UShort,
|
||||||
|
val centralDirectorySize: UInt,
|
||||||
|
val centralDirectoryStartOffset: UInt,
|
||||||
|
val fileComment: String,
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ECD_HEADER_SIZE = 22
|
||||||
|
const val ECD_SIGNATURE = 0x06054b50u
|
||||||
|
|
||||||
|
fun fromECD(input: DataInput): ZipEndRecord {
|
||||||
|
val signature = input.readUIntLE()
|
||||||
|
|
||||||
|
if (signature != ECD_SIGNATURE)
|
||||||
|
throw IllegalArgumentException("Input doesn't start with end record signature")
|
||||||
|
|
||||||
|
val diskNumber = input.readUShortLE()
|
||||||
|
val startingDiskNumber = input.readUShortLE()
|
||||||
|
val diskEntries = input.readUShortLE()
|
||||||
|
val totalEntries = input.readUShortLE()
|
||||||
|
val centralDirectorySize = input.readUIntLE()
|
||||||
|
val centralDirectoryStartOffset = input.readUIntLE()
|
||||||
|
val fileCommentLength = input.readUShortLE()
|
||||||
|
var fileComment = ""
|
||||||
|
|
||||||
|
if (fileCommentLength > 0u) {
|
||||||
|
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
||||||
|
input.readFully(fileCommentBytes)
|
||||||
|
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZipEndRecord(
|
||||||
|
diskNumber,
|
||||||
|
startingDiskNumber,
|
||||||
|
diskEntries,
|
||||||
|
totalEntries,
|
||||||
|
centralDirectorySize,
|
||||||
|
centralDirectoryStartOffset,
|
||||||
|
fileComment
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toECD(): ByteBuffer {
|
||||||
|
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||||
|
|
||||||
|
buffer.putUInt(ECD_SIGNATURE)
|
||||||
|
buffer.putUShort(diskNumber)
|
||||||
|
buffer.putUShort(startingDiskNumber)
|
||||||
|
buffer.putUShort(diskEntries)
|
||||||
|
buffer.putUShort(totalEntries)
|
||||||
|
buffer.putUInt(centralDirectorySize)
|
||||||
|
buffer.putUInt(centralDirectoryStartOffset)
|
||||||
|
buffer.putUShort(commentBytes.size.toUShort())
|
||||||
|
|
||||||
|
buffer.put(commentBytes)
|
||||||
|
|
||||||
|
buffer.flip()
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package app.revanced.utils.signing.align.zip.structures
|
||||||
|
|
||||||
|
import app.revanced.utils.signing.align.zip.getUShort
|
||||||
|
import app.revanced.utils.signing.align.zip.putUInt
|
||||||
|
import app.revanced.utils.signing.align.zip.putUShort
|
||||||
|
import app.revanced.utils.signing.align.zip.readUIntLE
|
||||||
|
import app.revanced.utils.signing.align.zip.readUShortLE
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
|
||||||
|
data class ZipEntry(
|
||||||
|
val version: UShort,
|
||||||
|
val versionNeeded: UShort,
|
||||||
|
val flags: UShort,
|
||||||
|
val compression: UShort,
|
||||||
|
val modificationTime: UShort,
|
||||||
|
val modificationDate: UShort,
|
||||||
|
val crc32: UInt,
|
||||||
|
val compressedSize: UInt,
|
||||||
|
val uncompressedSize: UInt,
|
||||||
|
val diskNumber: UShort,
|
||||||
|
val internalAttributes: UShort,
|
||||||
|
val externalAttributes: UInt,
|
||||||
|
var localHeaderOffset: UInt,
|
||||||
|
val fileName: String,
|
||||||
|
val extraField: ByteArray,
|
||||||
|
val fileComment: String,
|
||||||
|
var localExtraField: ByteArray = ByteArray(0), //seperate for alignment
|
||||||
|
) {
|
||||||
|
val LFHSize: Int
|
||||||
|
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
||||||
|
|
||||||
|
val dataOffset: UInt
|
||||||
|
get() = localHeaderOffset + LFHSize.toUInt()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CDE_HEADER_SIZE = 46
|
||||||
|
const val CDE_SIGNATURE = 0x02014b50u
|
||||||
|
|
||||||
|
const val LFH_HEADER_SIZE = 30
|
||||||
|
const val LFH_SIGNATURE = 0x04034b50u
|
||||||
|
|
||||||
|
fun fromCDE(input: DataInput): ZipEntry {
|
||||||
|
val signature = input.readUIntLE()
|
||||||
|
|
||||||
|
if (signature != CDE_SIGNATURE)
|
||||||
|
throw IllegalArgumentException("Input doesn't start with central directory entry signature")
|
||||||
|
|
||||||
|
val version = input.readUShortLE()
|
||||||
|
val versionNeeded = input.readUShortLE()
|
||||||
|
var flags = input.readUShortLE()
|
||||||
|
val compression = input.readUShortLE()
|
||||||
|
val modificationTime = input.readUShortLE()
|
||||||
|
val modificationDate = input.readUShortLE()
|
||||||
|
val crc32 = input.readUIntLE()
|
||||||
|
val compressedSize = input.readUIntLE()
|
||||||
|
val uncompressedSize = input.readUIntLE()
|
||||||
|
val fileNameLength = input.readUShortLE()
|
||||||
|
var fileName = ""
|
||||||
|
val extraFieldLength = input.readUShortLE()
|
||||||
|
var extraField = ByteArray(extraFieldLength.toInt())
|
||||||
|
val fileCommentLength = input.readUShortLE()
|
||||||
|
var fileComment = ""
|
||||||
|
val diskNumber = input.readUShortLE()
|
||||||
|
val internalAttributes = input.readUShortLE()
|
||||||
|
val externalAttributes = input.readUIntLE()
|
||||||
|
val localHeaderOffset = input.readUIntLE()
|
||||||
|
|
||||||
|
val variableFieldsLength = fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
||||||
|
|
||||||
|
if (variableFieldsLength > 0) {
|
||||||
|
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
||||||
|
input.readFully(fileNameBytes)
|
||||||
|
fileName = fileNameBytes.toString(Charsets.UTF_8)
|
||||||
|
|
||||||
|
input.readFully(extraField)
|
||||||
|
|
||||||
|
val fileCommentBytes = ByteArray(fileCommentLength.toInt())
|
||||||
|
input.readFully(fileCommentBytes)
|
||||||
|
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
flags = (flags and 0b1000u.inv().toUShort()) //disable data descriptor flag as they are not used
|
||||||
|
|
||||||
|
return ZipEntry(
|
||||||
|
version,
|
||||||
|
versionNeeded,
|
||||||
|
flags,
|
||||||
|
compression,
|
||||||
|
modificationTime,
|
||||||
|
modificationDate,
|
||||||
|
crc32,
|
||||||
|
compressedSize,
|
||||||
|
uncompressedSize,
|
||||||
|
diskNumber,
|
||||||
|
internalAttributes,
|
||||||
|
externalAttributes,
|
||||||
|
localHeaderOffset,
|
||||||
|
fileName,
|
||||||
|
extraField,
|
||||||
|
fileComment,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readLocalExtra(buffer: ByteBuffer) {
|
||||||
|
buffer.order(ByteOrder.LITTLE_ENDIAN)
|
||||||
|
localExtraField = ByteArray(buffer.getUShort().toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toLFH(): ByteBuffer {
|
||||||
|
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.allocate(LFH_HEADER_SIZE + nameBytes.size + localExtraField.size)
|
||||||
|
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||||
|
|
||||||
|
buffer.putUInt(LFH_SIGNATURE)
|
||||||
|
buffer.putUShort(versionNeeded)
|
||||||
|
buffer.putUShort(flags)
|
||||||
|
buffer.putUShort(compression)
|
||||||
|
buffer.putUShort(modificationTime)
|
||||||
|
buffer.putUShort(modificationDate)
|
||||||
|
buffer.putUInt(crc32)
|
||||||
|
buffer.putUInt(compressedSize)
|
||||||
|
buffer.putUInt(uncompressedSize)
|
||||||
|
buffer.putUShort(nameBytes.size.toUShort())
|
||||||
|
buffer.putUShort(localExtraField.size.toUShort())
|
||||||
|
|
||||||
|
buffer.put(nameBytes)
|
||||||
|
buffer.put(localExtraField)
|
||||||
|
|
||||||
|
buffer.flip()
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toCDE(): ByteBuffer {
|
||||||
|
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
||||||
|
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
|
val buffer = ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
|
||||||
|
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||||
|
|
||||||
|
buffer.putUInt(CDE_SIGNATURE)
|
||||||
|
buffer.putUShort(version)
|
||||||
|
buffer.putUShort(versionNeeded)
|
||||||
|
buffer.putUShort(flags)
|
||||||
|
buffer.putUShort(compression)
|
||||||
|
buffer.putUShort(modificationTime)
|
||||||
|
buffer.putUShort(modificationDate)
|
||||||
|
buffer.putUInt(crc32)
|
||||||
|
buffer.putUInt(compressedSize)
|
||||||
|
buffer.putUInt(uncompressedSize)
|
||||||
|
buffer.putUShort(nameBytes.size.toUShort())
|
||||||
|
buffer.putUShort(extraField.size.toUShort())
|
||||||
|
buffer.putUShort(commentBytes.size.toUShort())
|
||||||
|
buffer.putUShort(diskNumber)
|
||||||
|
buffer.putUShort(internalAttributes)
|
||||||
|
buffer.putUInt(externalAttributes)
|
||||||
|
buffer.putUInt(localHeaderOffset)
|
||||||
|
|
||||||
|
buffer.put(nameBytes)
|
||||||
|
buffer.put(extraField)
|
||||||
|
buffer.put(commentBytes)
|
||||||
|
|
||||||
|
buffer.flip()
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user