mirror of
https://github.com/ReVanced/revanced-cli.git
synced 2026-01-11 13:56:18 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a530624fb2 | ||
|
|
0e3ecc3a51 | ||
|
|
7fac75c6b6 | ||
|
|
7ca0b35e45 | ||
|
|
cb0fde4908 | ||
|
|
75e810e42e | ||
|
|
37cc77dfc5 | ||
|
|
075bf406fd | ||
|
|
71c81510f7 | ||
|
|
51fd16409f | ||
|
|
3f5345af6e | ||
|
|
649d9bdb2a | ||
|
|
f4b04698d8 | ||
|
|
240ab18eaf | ||
|
|
096dba2337 | ||
|
|
26780f94e5 | ||
|
|
faa52e2c68 | ||
|
|
e3e74ac0e9 | ||
|
|
de1bdb708c | ||
|
|
9b2b933998 | ||
|
|
fb5a72fdc0 | ||
|
|
86c5992630 | ||
|
|
06d2139ebf | ||
|
|
15ba4f40cd | ||
|
|
2d70037913 | ||
|
|
34108b9229 | ||
|
|
64a323e7e7 | ||
|
|
372470c77b | ||
|
|
3cef245728 | ||
|
|
aa54a2b1a9 | ||
|
|
f8c430be33 | ||
|
|
7692c18282 | ||
|
|
469d0861af | ||
|
|
208c5bc5b0 | ||
|
|
82efd9941f | ||
|
|
48e9043517 | ||
|
|
c3d8fecad0 |
12
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
12
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
@@ -59,3 +59,15 @@ body:
|
||||
description: Add additional context here.
|
||||
validations:
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I filled out all of the requested information in this issue properly.
|
||||
required: true
|
||||
14
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
14
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
@@ -43,4 +43,16 @@ body:
|
||||
label: Additional context
|
||||
description: Add additional context here.
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
- type: checkboxes
|
||||
id: acknowledgements
|
||||
attributes:
|
||||
label: Acknowledgements
|
||||
description: Your issue will be closed if you haven't done these steps.
|
||||
options:
|
||||
- label: I have searched the existing issues and this is a new and no duplicate or related to another open issue.
|
||||
required: true
|
||||
- label: I have written a short but informative title.
|
||||
required: true
|
||||
- label: I filled out all of the requested information in this issue properly.
|
||||
required: true
|
||||
1
.idea/.gitignore
generated
vendored
1
.idea/.gitignore
generated
vendored
@@ -6,3 +6,4 @@
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
/kotlinc.xml
|
||||
|
||||
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@@ -8,7 +8,5 @@
|
||||
</component>
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../revanced-patcher" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../revanced-patches" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -1,3 +1,61 @@
|
||||
# [2.11.0](https://github.com/revanced/revanced-cli/compare/v2.10.2...v2.11.0) (2022-09-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* section `acknowledgements` for issue templates ([0e3ecc3](https://github.com/revanced/revanced-cli/commit/0e3ecc3a51540b71072ae0be0eb94d115a5b1f92))
|
||||
|
||||
## [2.10.2](https://github.com/revanced/revanced-cli/compare/v2.10.1...v2.10.2) (2022-09-18)
|
||||
|
||||
## [2.10.1](https://github.com/revanced/revanced-cli/compare/v2.10.0...v2.10.1) (2022-09-09)
|
||||
|
||||
# [2.10.0](https://github.com/revanced/revanced-cli/compare/v2.9.10...v2.10.0) (2022-09-08)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Patch Options CLI implementation ([#132](https://github.com/revanced/revanced-cli/issues/132)) ([3f5345a](https://github.com/revanced/revanced-cli/commit/3f5345af6e45bfb6c91d52fc089ab18d81fdc998))
|
||||
|
||||
## [2.9.10](https://github.com/revanced/revanced-cli/compare/v2.9.9...v2.9.10) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't print same patch multiple times ([f4b0469](https://github.com/revanced/revanced-cli/commit/f4b04698d8c1717824e86f91da5e01c5021612da))
|
||||
|
||||
## [2.9.9](https://github.com/revanced/revanced-cli/compare/v2.9.8...v2.9.9) (2022-09-08)
|
||||
|
||||
## [2.9.8](https://github.com/revanced/revanced-cli/compare/v2.9.7...v2.9.8) (2022-09-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* broken deprecation message ([e3e74ac](https://github.com/revanced/revanced-cli/commit/e3e74ac0e9a844f9d717a499bca09e575dd90435))
|
||||
|
||||
## [2.9.7](https://github.com/revanced/revanced-cli/compare/v2.9.6...v2.9.7) (2022-09-08)
|
||||
|
||||
## [2.9.6](https://github.com/revanced/revanced-cli/compare/v2.9.5...v2.9.6) (2022-09-07)
|
||||
|
||||
## [2.9.5](https://github.com/revanced/revanced-cli/compare/v2.9.4...v2.9.5) (2022-09-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* mount bind revanced.apk from magisk's mirror ([372470c](https://github.com/revanced/revanced-cli/commit/372470c77b82e8601ca523e87a2cfd44f79d0e31))
|
||||
|
||||
## [2.9.4](https://github.com/revanced/revanced-cli/compare/v2.9.3...v2.9.4) (2022-08-31)
|
||||
|
||||
## [2.9.3](https://github.com/revanced/revanced-cli/compare/v2.9.2...v2.9.3) (2022-08-14)
|
||||
|
||||
## [2.9.2](https://github.com/revanced/revanced-cli/compare/v2.9.1...v2.9.2) (2022-08-07)
|
||||
|
||||
## [2.9.1](https://github.com/revanced/revanced-cli/compare/v2.9.0...v2.9.1) (2022-08-04)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* feat: remove extra zipalign step ([c3d8fec](https://github.com/revanced/revanced-cli/commit/c3d8fecad0ed9d583b9f1f79bc271e0535d87be2))
|
||||
|
||||
# [2.9.0](https://github.com/revanced/revanced-cli/compare/v2.8.3...v2.9.0) (2022-08-03)
|
||||
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("stdlib"))
|
||||
implementation(kotlin("reflect"))
|
||||
|
||||
implementation("app.revanced:revanced-patcher:3.3.1")
|
||||
implementation("app.revanced:revanced-patcher:4.4.2")
|
||||
implementation("info.picocli:picocli:4.6.3")
|
||||
implementation("com.android.tools.build:apksig:7.2.1")
|
||||
implementation("com.github.revanced:jadb:master-SNAPSHOT") // updated fork
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
implementation("cc.ekblad:4koma:1.1.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
kotlin.code.style = official
|
||||
version = 2.9.0
|
||||
version = 2.11.0
|
||||
|
||||
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
|
||||
|
||||
import app.revanced.cli.aligning.Aligning
|
||||
import app.revanced.cli.logging.impl.DefaultCliLogger
|
||||
import app.revanced.cli.patcher.Patcher
|
||||
import app.revanced.cli.patcher.logging.impl.PatcherLogger
|
||||
@@ -9,7 +10,8 @@ import app.revanced.patcher.PatcherOptions
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import app.revanced.patcher.extensions.PatchExtensions.description
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.util.patch.implementation.JarPatchBundle
|
||||
import app.revanced.patcher.util.patch.impl.JarPatchBundle
|
||||
import app.revanced.utils.OptionsLoader
|
||||
import app.revanced.utils.adb.Adb
|
||||
import picocli.CommandLine.*
|
||||
import java.io.File
|
||||
@@ -50,6 +52,9 @@ internal object MainCommand : Runnable {
|
||||
@Option(names = ["-b", "--bundles"], description = ["One or more bundles of patches"], required = true)
|
||||
var patchBundles = arrayOf<String>()
|
||||
|
||||
@Option(names = ["--options"], description = ["Configuration file for all patch options"])
|
||||
var options: File = File("options.toml")
|
||||
|
||||
@ArgGroup(exclusive = false)
|
||||
var listingArgs: ListingArgs? = null
|
||||
|
||||
@@ -108,12 +113,12 @@ internal object MainCommand : Runnable {
|
||||
@Option(names = ["-p", "--password"], description = ["Overwrite the default password for the signed file"])
|
||||
var password = "ReVanced"
|
||||
|
||||
@Option(names = ["-t", "--temp-dir"], description = ["Temporal resource cache directory"])
|
||||
@Option(names = ["-t", "--temp-dir"], description = ["Temporary resource cache directory"])
|
||||
var cacheDirectory = "revanced-cache"
|
||||
|
||||
@Option(
|
||||
names = ["-c", "--clean"],
|
||||
description = ["Clean the temporal resource cache directory. This will be done anyways when running the patcher"]
|
||||
description = ["Clean the temporary resource cache directory. This will be done anyways when running the patcher"]
|
||||
)
|
||||
var clean: Boolean = false
|
||||
|
||||
@@ -122,20 +127,17 @@ internal object MainCommand : Runnable {
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
if (args.patchArgs?.listingArgs?.listOnly == true) {
|
||||
printListOfPatches()
|
||||
return
|
||||
}
|
||||
|
||||
if (args.uninstall) {
|
||||
uninstall()
|
||||
return
|
||||
}
|
||||
if (args.patchArgs?.listingArgs?.listOnly == true) return printListOfPatches()
|
||||
if (args.uninstall) return uninstall()
|
||||
|
||||
val pArgs = this.args.patchArgs?.patchingArgs ?: return
|
||||
val outputFile = File(pArgs.outputPath) // the file to write to
|
||||
|
||||
// the file to write to
|
||||
val outputFile = File(pArgs.outputPath)
|
||||
val allPatches = args.patchArgs!!.patchBundles.flatMap { bundle ->
|
||||
JarPatchBundle(bundle).loadPatches()
|
||||
}
|
||||
|
||||
OptionsLoader.init(args.patchArgs!!.options, allPatches)
|
||||
|
||||
val patcher = app.revanced.patcher.Patcher(
|
||||
PatcherOptions(
|
||||
@@ -156,15 +158,19 @@ internal object MainCommand : Runnable {
|
||||
val patchedFile = File(pArgs.cacheDirectory).resolve("${outputFile.nameWithoutExtension}_raw.apk")
|
||||
|
||||
// start the patcher
|
||||
Patcher.start(patcher, patchedFile)
|
||||
Patcher.start(patcher, patchedFile, allPatches)
|
||||
|
||||
val cacheDirectory = File(pArgs.cacheDirectory)
|
||||
|
||||
// align the file
|
||||
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(
|
||||
patchedFile,
|
||||
alignedFile,
|
||||
signedOutput,
|
||||
SigningOptions(
|
||||
pArgs.cn,
|
||||
@@ -177,7 +183,7 @@ internal object MainCommand : Runnable {
|
||||
|
||||
signedOutput
|
||||
} else
|
||||
patchedFile
|
||||
alignedFile
|
||||
|
||||
// finally copy to the specified output file
|
||||
logger.info("Copying ${finalFile.name} to ${outputFile.name}")
|
||||
@@ -222,7 +228,9 @@ internal object MainCommand : Runnable {
|
||||
}
|
||||
|
||||
private fun printListOfPatches() {
|
||||
val logged = mutableListOf<String>()
|
||||
for (patchBundlePath in args.patchArgs?.patchBundles!!) for (patch in JarPatchBundle(patchBundlePath).loadPatches()) {
|
||||
if (patch.patchName in logged) continue
|
||||
for (compatiblePackage in patch.compatiblePackages!!) {
|
||||
val packageEntryStr = buildString {
|
||||
// Add package if flag is set
|
||||
@@ -245,8 +253,9 @@ internal object MainCommand : Runnable {
|
||||
append("\t")
|
||||
append(compatibleVersions)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
logged.add(patch.patchName)
|
||||
logger.info(packageEntryStr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,45 +2,49 @@ package app.revanced.cli.patcher
|
||||
|
||||
import app.revanced.cli.command.MainCommand.args
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.utils.filesystem.ZipFileSystemUtils
|
||||
import app.revanced.utils.patcher.addPatchesFiltered
|
||||
import app.revanced.utils.patcher.applyPatchesVerbose
|
||||
import app.revanced.utils.patcher.mergeFiles
|
||||
import app.revanced.utils.signing.align.ZipAligner
|
||||
import app.revanced.utils.signing.align.zip.ZipFile
|
||||
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
|
||||
internal object Patcher {
|
||||
internal fun start(patcher: app.revanced.patcher.Patcher, output: File) {
|
||||
internal fun start(patcher: app.revanced.patcher.Patcher, output: File, allPatches: List<Class<out Patch<Data>>>) {
|
||||
val inputFile = args.inputFile
|
||||
val args = args.patchArgs?.patchingArgs!!
|
||||
|
||||
// merge files like necessary integrations
|
||||
patcher.mergeFiles()
|
||||
// add patches, but filter incompatible or excluded patches
|
||||
patcher.addPatchesFiltered()
|
||||
patcher.addPatchesFiltered(allPatches)
|
||||
// apply patches
|
||||
patcher.applyPatchesVerbose()
|
||||
|
||||
// write output file
|
||||
if (output.exists()) Files.delete(output.toPath())
|
||||
inputFile.copyTo(output)
|
||||
|
||||
val result = patcher.save()
|
||||
ZipFile(output).use { outputFile ->
|
||||
ZipFileSystemUtils(output).use { outputFileSystem ->
|
||||
// replace all dex files
|
||||
result.dexFiles.forEach {
|
||||
logger.info("Writing dex file ${it.name}")
|
||||
outputFile.addEntryCompressData(ZipEntry.createWithName(it.name), it.dexFileInputStream.readAllBytes())
|
||||
outputFileSystem.write(it.name, it.stream.readAllBytes())
|
||||
}
|
||||
|
||||
if (!args.disableResourcePatching) {
|
||||
logger.info("Writing resources...")
|
||||
|
||||
outputFile.copyEntriesFromFileAligned(ZipFile(result.resourceFile!!), ZipAligner::getEntryAlignment)
|
||||
ZipFileSystemUtils(result.resourceFile!!).use { resourceFileSystem ->
|
||||
val resourceFiles = resourceFileSystem.getFile(File.separator)
|
||||
outputFileSystem.writePathRecursively(resourceFiles)
|
||||
}
|
||||
}
|
||||
|
||||
outputFile.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment)
|
||||
result.doNotCompress?.let { outputFileSystem.uncompress(*it.toTypedArray()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
src/main/kotlin/app/revanced/utils/OptionsLoader.kt
Normal file
77
src/main/kotlin/app/revanced/utils/OptionsLoader.kt
Normal file
@@ -0,0 +1,77 @@
|
||||
package app.revanced.utils
|
||||
|
||||
import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.extensions.PatchExtensions.options
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import cc.ekblad.toml.encodeToString
|
||||
import cc.ekblad.toml.model.TomlValue
|
||||
import cc.ekblad.toml.serialization.from
|
||||
import cc.ekblad.toml.tomlMapper
|
||||
import java.io.File
|
||||
|
||||
private typealias PatchList = List<Class<out Patch<Data>>>
|
||||
private typealias OptionsMap = MutableMap<String, MutableMap<String, Any>>
|
||||
|
||||
object OptionsLoader {
|
||||
@JvmStatic
|
||||
private val mapper = tomlMapper {}
|
||||
|
||||
@JvmStatic
|
||||
fun init(file: File, patches: PatchList) {
|
||||
if (!file.exists()) file.createNewFile()
|
||||
val map = mapper.decodeWithDefaults(generateDefaults(patches), TomlValue.from(file.toPath()))
|
||||
readAndSet(map, patches)
|
||||
save(map, file)
|
||||
}
|
||||
|
||||
private fun readAndSet(map: OptionsMap, patches: PatchList) {
|
||||
for ((patchName, options) in map) {
|
||||
val patch = patches.find { it.patchName == patchName } ?: continue
|
||||
val patchOptions = patch.options ?: continue
|
||||
for ((key, value) in options) {
|
||||
if (value == "null") { // backwards compatibility, subject to removal
|
||||
options.remove(key)
|
||||
continue
|
||||
}
|
||||
try {
|
||||
patchOptions[key] = value
|
||||
} catch (e: Exception) {
|
||||
logger.error("Error while setting option $key for patch $patchName: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun save(map: OptionsMap, file: File) {
|
||||
val toml = mapper.encodeToString(map)
|
||||
file.writeText(
|
||||
"""
|
||||
# A list of options for each patch.
|
||||
# This file does not contain all options by default.
|
||||
# Run the CLI with the "--list --with-options" flags to see all available options.
|
||||
# You can also run the CLI with the aforementioned flags and a patch name to see all available options for that patch.
|
||||
# To set an option, add a line with the format "option = value" or set the value if the option already exists.
|
||||
# To reset an option to its default value, delete the line.
|
||||
# To reset all options to their default values, delete this file.
|
||||
#
|
||||
# This file was generated by ReVanced Patcher version ${Patcher.version}.
|
||||
""".trimIndent() + "\n\n$toml"
|
||||
)
|
||||
}
|
||||
|
||||
private fun generateDefaults(patches: PatchList) = buildMap {
|
||||
for (patch in patches) {
|
||||
val options = patch.options ?: continue
|
||||
if (!options.iterator().hasNext()) continue
|
||||
put(patch.patchName, buildMap {
|
||||
for (option in options) {
|
||||
put(option.key, option.value ?: continue)
|
||||
}
|
||||
} as MutableMap)
|
||||
}
|
||||
} as MutableMap
|
||||
}
|
||||
@@ -44,12 +44,14 @@ internal object Constants {
|
||||
internal val CONTENT_MOUNT_SCRIPT =
|
||||
"""
|
||||
#!/system/bin/sh
|
||||
MAGISKTMP="${'$'}(magisk --path)" || MAGISKTMP=/sbin
|
||||
MIRROR="${'$'}MAGISKTMP/.magisk/mirror"
|
||||
while [ "${'$'}(getprop sys.boot_completed | tr -d '\r')" != "1" ]; do sleep 1; done
|
||||
|
||||
base_path="$PATH_REVANCED_APP"
|
||||
stock_path=${'$'}( pm path $PLACEHOLDER | grep base | sed 's/package://g' )
|
||||
|
||||
chcon u:object_r:apk_data_file:s0 ${'$'}base_path
|
||||
mount -o bind ${'$'}base_path ${'$'}stock_path
|
||||
mount -o bind ${'$'}MIRROR${'$'}base_path ${'$'}stock_path
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package app.revanced.utils.filesystem
|
||||
|
||||
import java.io.Closeable
|
||||
import java.io.File
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.zip.ZipEntry
|
||||
|
||||
internal class ZipFileSystemUtils(
|
||||
file: File
|
||||
) : Closeable {
|
||||
private var zipFileSystem = FileSystems.newFileSystem(file.toPath(), mapOf("noCompression" to true))
|
||||
|
||||
private fun Path.deleteRecursively() {
|
||||
if (!Files.exists(this)) {
|
||||
throw IllegalStateException("File exists in real folder but not in zip file system")
|
||||
}
|
||||
|
||||
if (Files.isDirectory(this)) {
|
||||
Files.list(this).forEach { path ->
|
||||
path.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
Files.delete(this)
|
||||
}
|
||||
|
||||
internal fun getFile(path: String) = zipFileSystem.getPath(path)
|
||||
|
||||
internal fun writePathRecursively(path: Path) {
|
||||
Files.list(path).use { fileStream ->
|
||||
fileStream.forEach { filePath ->
|
||||
val fileSystemPath = filePath.getRelativePath(path)
|
||||
fileSystemPath.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
Files.walk(path).use { fileStream ->
|
||||
// don't include build directory
|
||||
// by skipping the root node.
|
||||
fileStream.skip(1).forEach { filePath ->
|
||||
val relativePath = filePath.getRelativePath(path)
|
||||
|
||||
if (Files.isDirectory(filePath)) {
|
||||
Files.createDirectory(relativePath)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
Files.copy(filePath, relativePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun write(path: String, content: ByteArray) = Files.write(zipFileSystem.getPath(path), content)
|
||||
|
||||
private fun Path.getRelativePath(path: Path): Path = zipFileSystem.getPath(path.relativize(this).toString())
|
||||
|
||||
// TODO: figure out why the file system is uncompressed by default and how to fix it
|
||||
internal fun uncompress(vararg paths: String) =
|
||||
paths.forEach { Files.setAttribute(zipFileSystem.getPath(it), "zip:method", ZipEntry.STORED) }
|
||||
|
||||
override fun close() = zipFileSystem.close()
|
||||
}
|
||||
@@ -6,58 +6,63 @@ import app.revanced.cli.command.MainCommand.logger
|
||||
import app.revanced.patcher.Patcher
|
||||
import app.revanced.patcher.data.Data
|
||||
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
|
||||
import app.revanced.patcher.extensions.PatchExtensions.deprecated
|
||||
import app.revanced.patcher.extensions.PatchExtensions.include
|
||||
import app.revanced.patcher.extensions.PatchExtensions.patchName
|
||||
import app.revanced.patcher.patch.Patch
|
||||
import app.revanced.patcher.util.patch.implementation.JarPatchBundle
|
||||
|
||||
fun Patcher.addPatchesFiltered() {
|
||||
fun Patcher.addPatchesFiltered(allPatches: List<Class<out Patch<Data>>>) {
|
||||
val packageName = this.data.packageMetadata.packageName
|
||||
val packageVersion = this.data.packageMetadata.packageVersion
|
||||
|
||||
args.patchArgs?.patchBundles!!.forEach { bundle ->
|
||||
val includedPatches = mutableListOf<Class<out Patch<Data>>>()
|
||||
JarPatchBundle(bundle).loadPatches().forEach patch@{ patch ->
|
||||
val compatiblePackages = patch.compatiblePackages
|
||||
val patchName = patch.patchName
|
||||
val includedPatches = mutableListOf<Class<out Patch<Data>>>()
|
||||
allPatches.forEach patchLoop@{ patch ->
|
||||
val compatiblePackages = patch.compatiblePackages
|
||||
val patchName = patch.patchName
|
||||
|
||||
val prefix = "Skipping $patchName"
|
||||
val prefix = "Skipping $patchName, reason"
|
||||
|
||||
val args = MainCommand.args.patchArgs?.patchingArgs!!
|
||||
val args = MainCommand.args.patchArgs?.patchingArgs!!
|
||||
|
||||
if (args.excludedPatches.contains(patchName)) {
|
||||
logger.info("$prefix: Explicitly excluded")
|
||||
return@patch
|
||||
} else if ((!patch.include || args.defaultExclude) && !args.includedPatches.contains(patchName)) {
|
||||
logger.info("$prefix: Not explicitly included")
|
||||
return@patch
|
||||
}
|
||||
|
||||
if (compatiblePackages == null) logger.warn("$prefix: Missing compatibility annotation. Continuing.")
|
||||
else {
|
||||
if (!compatiblePackages.any { it.name == packageName }) {
|
||||
logger.warn("$prefix: Incompatible with $packageName. This patch is only compatible with ${
|
||||
compatiblePackages.joinToString(
|
||||
", "
|
||||
) { it.name }
|
||||
}")
|
||||
return@patch
|
||||
}
|
||||
|
||||
if (!(args.experimental || compatiblePackages.any { it.versions.isEmpty() || it.versions.any { version -> version == packageVersion } })) {
|
||||
val compatibleWith = compatiblePackages.map { _package ->
|
||||
"${_package.name}: ${_package.versions.joinToString(", ")}"
|
||||
}.joinToString(";")
|
||||
logger.warn("$prefix: Incompatible with version $packageVersion. This patch is only compatible with version $compatibleWith")
|
||||
return@patch
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("Adding $patchName")
|
||||
includedPatches.add(patch)
|
||||
if (args.excludedPatches.contains(patchName)) {
|
||||
logger.info("$prefix: manually excluded")
|
||||
return@patchLoop
|
||||
} else if ((!patch.include || args.defaultExclude) && !args.includedPatches.contains(patchName)) {
|
||||
logger.info("$prefix: excluded by default")
|
||||
return@patchLoop
|
||||
}
|
||||
this.addPatches(includedPatches)
|
||||
|
||||
patch.deprecated?.let { (reason, replacement) ->
|
||||
logger.warn("$prefix: deprecated: $reason")
|
||||
if (replacement != null) logger.warn("Either use ${replacement.java.patchName} instead or include it manually")
|
||||
return@patchLoop
|
||||
}
|
||||
|
||||
if (compatiblePackages == null) logger.warn("$prefix: Missing compatibility annotation. Continuing.")
|
||||
else {
|
||||
if (!compatiblePackages.any { it.name == packageName }) {
|
||||
logger.warn("$prefix: incompatible with $packageName. This patch is only compatible with ${
|
||||
compatiblePackages.joinToString(
|
||||
", "
|
||||
) { it.name }
|
||||
}")
|
||||
return@patchLoop
|
||||
}
|
||||
|
||||
if (!(args.experimental || compatiblePackages.any { it.versions.isEmpty() || it.versions.any { version -> version == packageVersion } })) {
|
||||
val compatibleWith = compatiblePackages.joinToString(";") { _package ->
|
||||
"${_package.name}: ${_package.versions.joinToString(", ")}"
|
||||
}
|
||||
logger.warn("$prefix: incompatible with version $packageVersion. This patch is only compatible with version $compatibleWith")
|
||||
return@patchLoop
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("Adding $patchName")
|
||||
includedPatches.add(patch)
|
||||
}
|
||||
|
||||
this.addPatches(includedPatches)
|
||||
}
|
||||
|
||||
fun Patcher.applyPatchesVerbose() {
|
||||
|
||||
@@ -1,11 +1,28 @@
|
||||
package app.revanced.utils.signing.align
|
||||
|
||||
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
||||
import app.revanced.utils.signing.align.zip.ZipFile
|
||||
import java.io.File
|
||||
|
||||
internal object ZipAligner {
|
||||
private const val DEFAULT_ALIGNMENT = 4
|
||||
private const val LIBRARY_ALIGNMENT = 4096
|
||||
|
||||
fun getEntryAlignment(entry: ZipEntry): Int? =
|
||||
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
||||
fun align(input: File, output: File) {
|
||||
val inputZip = ZipFile(input)
|
||||
val outputZip = ZipFile(output)
|
||||
|
||||
for (entry in inputZip.entries) {
|
||||
val data = inputZip.getDataForEntry(entry)
|
||||
|
||||
if (entry.compression == 0.toUShort()) {
|
||||
val alignment = if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
||||
|
||||
outputZip.addEntryAligned(entry, data, alignment)
|
||||
} else {
|
||||
outputZip.addEntry(entry, data)
|
||||
}
|
||||
}
|
||||
|
||||
outputZip.finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,15 @@ 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.Closeable
|
||||
import java.io.File
|
||||
import java.io.RandomAccessFile
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.channels.FileChannel
|
||||
import java.util.zip.CRC32
|
||||
import java.util.zip.Deflater
|
||||
|
||||
class ZipFile(val file: File) : Closeable {
|
||||
class ZipFile(val file: File) {
|
||||
var entries: MutableList<ZipEntry> = mutableListOf()
|
||||
|
||||
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
|
||||
private var CDNeedsRewrite = false
|
||||
|
||||
private val compressionLevel = 5
|
||||
|
||||
init {
|
||||
//if file isn't empty try to load entries
|
||||
@@ -59,24 +53,23 @@ class ZipFile(val file: File) : Closeable {
|
||||
|
||||
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
|
||||
)
|
||||
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 writeCD() {
|
||||
val CDStart = filePointer.channel.position().toUInt()
|
||||
private fun writeCDE() {
|
||||
val CDEStart = filePointer.channel.position().toUInt()
|
||||
|
||||
entries.forEach {
|
||||
filePointer.channel.write(it.toCDE())
|
||||
@@ -89,17 +82,15 @@ class ZipFile(val file: File) : Closeable {
|
||||
0u,
|
||||
entriesCount,
|
||||
entriesCount,
|
||||
filePointer.channel.position().toUInt() - CDStart,
|
||||
CDStart,
|
||||
filePointer.channel.position().toUInt() - CDEStart,
|
||||
CDEStart,
|
||||
""
|
||||
)
|
||||
|
||||
filePointer.channel.write(endRecord.toECD())
|
||||
}
|
||||
|
||||
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
||||
CDNeedsRewrite = true
|
||||
|
||||
fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
||||
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
||||
|
||||
filePointer.channel.write(entry.toLFH())
|
||||
@@ -108,45 +99,17 @@ class ZipFile(val file: File) : Closeable {
|
||||
entries.add(entry)
|
||||
}
|
||||
|
||||
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
|
||||
val compressor = Deflater(compressionLevel, true)
|
||||
compressor.setInput(data)
|
||||
compressor.finish()
|
||||
fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) {
|
||||
//calculate where data would end up
|
||||
val dataOffset = filePointer.filePointer + entry.LFHSize
|
||||
|
||||
val uncompressedSize = data.size
|
||||
val compressedData =
|
||||
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger
|
||||
val mod = dataOffset % alignment
|
||||
|
||||
val compressedDataLength = compressor.deflate(compressedData)
|
||||
val compressedBuffer =
|
||||
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
|
||||
|
||||
compressor.end()
|
||||
|
||||
val crc = CRC32()
|
||||
crc.update(data)
|
||||
|
||||
entry.compression = 8u //deflate compression
|
||||
entry.uncompressedSize = uncompressedSize.toUInt()
|
||||
entry.compressedSize = compressedDataLength.toUInt()
|
||||
entry.crc32 = crc.value.toUInt()
|
||||
|
||||
addEntry(entry, compressedBuffer)
|
||||
}
|
||||
|
||||
fun addEntryCopyData(entry: ZipEntry, data: ByteBuffer, alignment: Int? = null) {
|
||||
alignment?.let { alignment ->
|
||||
//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())
|
||||
}
|
||||
//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)
|
||||
@@ -160,17 +123,8 @@ class ZipFile(val file: File) : Closeable {
|
||||
)
|
||||
}
|
||||
|
||||
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
|
||||
for (entry in file.entries) {
|
||||
if (entries.any { it.fileName == entry.fileName }) continue //don't add duplicates
|
||||
|
||||
val data = file.getDataForEntry(entry)
|
||||
addEntryCopyData(entry, data, entryAlignment(entry))
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (CDNeedsRewrite) writeCD()
|
||||
fun finish() {
|
||||
writeCDE()
|
||||
filePointer.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,7 @@ data class ZipEndRecord(
|
||||
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) }
|
||||
val buffer = ByteBuffer.allocate(ECD_HEADER_SIZE + commentBytes.size).also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||
|
||||
buffer.putUInt(ECD_SIGNATURE)
|
||||
buffer.putUShort(diskNumber)
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package app.revanced.utils.signing.align.zip.structures
|
||||
|
||||
import app.revanced.utils.signing.align.zip.*
|
||||
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
|
||||
@@ -9,12 +13,12 @@ data class ZipEntry(
|
||||
val version: UShort,
|
||||
val versionNeeded: UShort,
|
||||
val flags: UShort,
|
||||
var compression: UShort,
|
||||
val compression: UShort,
|
||||
val modificationTime: UShort,
|
||||
val modificationDate: UShort,
|
||||
var crc32: UInt,
|
||||
var compressedSize: UInt,
|
||||
var uncompressedSize: UInt,
|
||||
val crc32: UInt,
|
||||
val compressedSize: UInt,
|
||||
val uncompressedSize: UInt,
|
||||
val diskNumber: UShort,
|
||||
val internalAttributes: UShort,
|
||||
val externalAttributes: UInt,
|
||||
@@ -22,7 +26,7 @@ data class ZipEntry(
|
||||
val fileName: String,
|
||||
val extraField: ByteArray,
|
||||
val fileComment: String,
|
||||
var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
||||
var localExtraField: ByteArray = ByteArray(0), //seperate for alignment
|
||||
) {
|
||||
val LFHSize: Int
|
||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
||||
@@ -37,27 +41,6 @@ data class ZipEntry(
|
||||
const val LFH_HEADER_SIZE = 30
|
||||
const val LFH_SIGNATURE = 0x04034b50u
|
||||
|
||||
fun createWithName(fileName: String): ZipEntry {
|
||||
return ZipEntry(
|
||||
0x1403u, //made by unix, version 20
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0x0821u, //seems to be static time google uses, no idea
|
||||
0x0221u, //same as above
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
fileName,
|
||||
ByteArray(0),
|
||||
""
|
||||
)
|
||||
}
|
||||
|
||||
fun fromCDE(input: DataInput): ZipEntry {
|
||||
val signature = input.readUIntLE()
|
||||
|
||||
@@ -84,8 +67,7 @@ data class ZipEntry(
|
||||
val externalAttributes = input.readUIntLE()
|
||||
val localHeaderOffset = input.readUIntLE()
|
||||
|
||||
val variableFieldsLength =
|
||||
fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
||||
val variableFieldsLength = fileNameLength.toInt() + extraFieldLength.toInt() + fileCommentLength.toInt()
|
||||
|
||||
if (variableFieldsLength > 0) {
|
||||
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
||||
@@ -99,8 +81,7 @@ data class ZipEntry(
|
||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
||||
}
|
||||
|
||||
flags = (flags and 0b1000u.inv()
|
||||
.toUShort()) //disable data descriptor flag as they are not used
|
||||
flags = (flags and 0b1000u.inv().toUShort()) //disable data descriptor flag as they are not used
|
||||
|
||||
return ZipEntry(
|
||||
version,
|
||||
@@ -157,9 +138,8 @@ data class ZipEntry(
|
||||
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) }
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user