mirror of
https://github.com/ReVanced/revanced-cli.git
synced 2026-01-18 00:43:58 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d96eb847d7 | ||
|
|
c8e793efab | ||
|
|
63a8aa315d | ||
|
|
491874bb51 | ||
|
|
cb887ead2c | ||
|
|
cd36e710d6 | ||
|
|
1d096c3e92 | ||
|
|
8d29b8cd47 | ||
|
|
0ce680a6f1 | ||
|
|
ddebe4d43c | ||
|
|
bc17298a80 | ||
|
|
536f6f90b1 | ||
|
|
ccf5206da8 | ||
|
|
07714abe49 | ||
|
|
d954424b30 | ||
|
|
168c003a31 | ||
|
|
96ec6a0384 |
61
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: 🐞 Bug report
|
||||||
|
description: Report a very clearly broken issue.
|
||||||
|
title: 'bug: <title>'
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# ReVanced bug report
|
||||||
|
|
||||||
|
Important to note that your issue may have already been reported before. Please check for existing issues [here](https://github.com/revanced/revanced-cli/labels/bug).
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Type
|
||||||
|
options:
|
||||||
|
- Error while running the CLI
|
||||||
|
- Error at runtime
|
||||||
|
- Cosmetic
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Bug description
|
||||||
|
description: How did you find the bug? Any additional details that might help?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Steps to reproduce
|
||||||
|
description: Add the steps to reproduce this bug including your environment.
|
||||||
|
placeholder: Step 1. Download some files. Step 2. ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Screenshots or videos
|
||||||
|
description: Add screenshots or videos that show the bug here.
|
||||||
|
placeholder: Drag and drop the screenshots/videos into this box.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Solution
|
||||||
|
description: If applicable, add a possible solution.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add additional context here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
24
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a bug report on the CLI. Do not submit suggestions for patches here.
|
|
||||||
title: 'problem: some problem'
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐞 Issue
|
|
||||||
|
|
||||||
<!-- Describe your issue in detail here -->
|
|
||||||
|
|
||||||
## ⚙ Reproduce
|
|
||||||
|
|
||||||
<!-- Include your environment and steps to reproduce the issue as detailed as possible -->
|
|
||||||
|
|
||||||
## 🛠 Solution
|
|
||||||
|
|
||||||
<!-- If applicable, add a possible solution -->
|
|
||||||
|
|
||||||
## ⚠ Additional context
|
|
||||||
|
|
||||||
<!-- Add any other context about the problem here -->
|
|
||||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: 📃 Documentation
|
||||||
|
url: https://github.com/revanced/revanced-documentation/
|
||||||
|
about: Don't know how or where to start? Check out our documentation!
|
||||||
|
- name: 🗨 Discussions
|
||||||
|
url: https://github.com/revanced/revanced-suggestions/discussions
|
||||||
|
about: Got something you think should change or be added? Search for or start a new discussion!
|
||||||
46
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
Normal file
46
.github/ISSUE_TEMPLATE/feature-issue.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: ⭐ Feature request
|
||||||
|
description: Create a detailed feature request.
|
||||||
|
title: 'feat: <title>'
|
||||||
|
labels: [feature-request]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# ReVanced feature request
|
||||||
|
|
||||||
|
Do not submit requests for patches here. Please submit them [here](https://github.com/orgs/revanced/discussions/categories/patches) instead.
|
||||||
|
Important to note that your feature request may have already been made before. Please check for existing feature requests [here](https://github.com/revanced/revanced-cli/labels/feature-request).
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
attributes:
|
||||||
|
label: Type
|
||||||
|
options:
|
||||||
|
- Functionality
|
||||||
|
- Cosmetic
|
||||||
|
- Other
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Issue
|
||||||
|
description: What is the current problem. Why does it require a feature request?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Feature
|
||||||
|
description: Describe your feature in detail. How does it solve the issue?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Motivation
|
||||||
|
description: Why should your feature should be considered?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Add additional context here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
24
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest a change to the CLI. Do not submit suggestions for patches here.
|
|
||||||
title: 'feat: some feature'
|
|
||||||
labels: feature-request
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐞 Issue
|
|
||||||
|
|
||||||
<!-- Describe your issue in detail here -->
|
|
||||||
|
|
||||||
## ⚙ Reproduce
|
|
||||||
|
|
||||||
<!-- Include your environment and steps to reproduce the issue as detailed as possible -->
|
|
||||||
|
|
||||||
## 🛠 Solution
|
|
||||||
|
|
||||||
<!-- If applicable, add a possible solution -->
|
|
||||||
|
|
||||||
## ⚠ Additional context
|
|
||||||
|
|
||||||
<!-- Add any other context about the problem here -->
|
|
||||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,3 +1,35 @@
|
|||||||
|
# [2.9.0](https://github.com/revanced/revanced-cli/compare/v2.8.3...v2.9.0) (2022-08-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* remove extra zipalign step ([#106](https://github.com/revanced/revanced-cli/issues/106)) ([c8e793e](https://github.com/revanced/revanced-cli/commit/c8e793efab8eed39b2cb564bee80ef6e0b2a7d03))
|
||||||
|
|
||||||
|
## [2.8.3](https://github.com/revanced/revanced-cli/compare/v2.8.2...v2.8.3) (2022-08-03)
|
||||||
|
|
||||||
|
## [2.8.2](https://github.com/revanced/revanced-cli/compare/v2.8.1...v2.8.2) (2022-08-02)
|
||||||
|
|
||||||
|
## [2.8.1](https://github.com/revanced/revanced-cli/compare/v2.8.0...v2.8.1) (2022-08-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* remove requirement for solution [skip ci] ([#108](https://github.com/revanced/revanced-cli/issues/108)) ([0ce680a](https://github.com/revanced/revanced-cli/commit/0ce680a6f1de139434edd80876a168ff695c2d79))
|
||||||
|
|
||||||
|
# [2.8.0](https://github.com/revanced/revanced-cli/compare/v2.7.1...v2.8.0) (2022-07-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* `frameworkFolderLocation` patcher option ([bc17298](https://github.com/revanced/revanced-cli/commit/bc17298a807ce035b8baa1f7c30e1392ca4ee43b))
|
||||||
|
|
||||||
|
## [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)
|
# [2.7.0](https://github.com/revanced/revanced-cli/compare/v2.6.0...v2.7.0) (2022-07-21)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ dependencies {
|
|||||||
implementation(kotlin("stdlib"))
|
implementation(kotlin("stdlib"))
|
||||||
implementation(kotlin("reflect"))
|
implementation(kotlin("reflect"))
|
||||||
|
|
||||||
implementation("app.revanced:revanced-patcher:2.4.0")
|
implementation("app.revanced:revanced-patcher:3.3.1")
|
||||||
implementation("info.picocli:picocli:4.6.3")
|
implementation("info.picocli:picocli:4.6.3")
|
||||||
implementation("com.android.tools.build:apksig:7.2.1")
|
implementation("com.android.tools.build:apksig:7.2.1")
|
||||||
implementation("com.github.revanced:jadb:master-SNAPSHOT") // updated fork
|
implementation("com.github.revanced:jadb:master-SNAPSHOT") // updated fork
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 2.7.0
|
version = 2.9.0
|
||||||
|
|||||||
@@ -43,18 +43,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 +78,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"])
|
||||||
@@ -119,83 +122,111 @@ internal object MainCommand : Runnable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
args.aaptPath,
|
pArgs.aaptPath,
|
||||||
logger = PatcherLogger
|
pArgs.cacheDirectory,
|
||||||
|
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(
|
|
||||||
|
// sign the file
|
||||||
|
val finalFile = if (!pArgs.mount) {
|
||||||
|
val signedOutput = cacheDirectory.resolve("${outputFile.nameWithoutExtension}_signed.apk")
|
||||||
|
Signing.sign(
|
||||||
patchedFile,
|
patchedFile,
|
||||||
outputFile,
|
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
|
||||||
|
patchedFile
|
||||||
|
|
||||||
|
// 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")
|
||||||
@@ -204,12 +235,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)
|
||||||
|
|||||||
@@ -2,17 +2,19 @@ package app.revanced.cli.patcher
|
|||||||
|
|
||||||
import app.revanced.cli.command.MainCommand.args
|
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.filesystem.ZipFileSystemUtils
|
|
||||||
import app.revanced.utils.patcher.addPatchesFiltered
|
import app.revanced.utils.patcher.addPatchesFiltered
|
||||||
import app.revanced.utils.patcher.applyPatchesVerbose
|
import app.revanced.utils.patcher.applyPatchesVerbose
|
||||||
import app.revanced.utils.patcher.mergeFiles
|
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.io.File
|
||||||
import java.nio.file.Files
|
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()
|
||||||
@@ -23,26 +25,22 @@ internal object Patcher {
|
|||||||
|
|
||||||
// write output file
|
// write output file
|
||||||
if (output.exists()) Files.delete(output.toPath())
|
if (output.exists()) Files.delete(output.toPath())
|
||||||
inputFile.copyTo(output)
|
|
||||||
|
|
||||||
val result = patcher.save()
|
val result = patcher.save()
|
||||||
ZipFileSystemUtils(output).use { outputFileSystem ->
|
ZipFile(output).use { outputFile ->
|
||||||
// replace all dex files
|
// replace all dex files
|
||||||
result.dexFiles.forEach {
|
result.dexFiles.forEach {
|
||||||
logger.info("Writing dex file ${it.name}")
|
logger.info("Writing dex file ${it.name}")
|
||||||
outputFileSystem.write(it.name, it.dexFileInputStream.readAllBytes())
|
outputFile.addEntryCompressData(ZipEntry.createWithName(it.name), it.dexFileInputStream.readAllBytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!args.disableResourcePatching) {
|
if (!args.disableResourcePatching) {
|
||||||
logger.info("Writing resources...")
|
logger.info("Writing resources...")
|
||||||
|
|
||||||
ZipFileSystemUtils(result.resourceFile!!).use { resourceFileSystem ->
|
outputFile.copyEntriesFromFileAligned(ZipFile(result.resourceFile!!), ZipAligner::getEntryAlignment)
|
||||||
val resourceFiles = resourceFileSystem.getFile(File.separator)
|
|
||||||
outputFileSystem.writePathRecursively(resourceFiles)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.doNotCompress?.let { outputFileSystem.uncompress(*it.toTypedArray()) }
|
outputFile.copyEntriesFromFileAligned(ZipFile(inputFile), ZipAligner::getEntryAlignment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
@@ -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,7 +23,7 @@ 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: Explicitly excluded")
|
logger.info("$prefix: Explicitly excluded")
|
||||||
@@ -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,28 +1,11 @@
|
|||||||
package app.revanced.utils.signing.align
|
package app.revanced.utils.signing.align
|
||||||
|
|
||||||
import app.revanced.utils.signing.align.zip.ZipFile
|
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
internal object ZipAligner {
|
internal object ZipAligner {
|
||||||
private const val DEFAULT_ALIGNMENT = 4
|
private const val DEFAULT_ALIGNMENT = 4
|
||||||
private const val LIBRARY_ALIGNMENT = 4096
|
private const val LIBRARY_ALIGNMENT = 4096
|
||||||
|
|
||||||
fun align(input: File, output: File) {
|
fun getEntryAlignment(entry: ZipEntry): Int? =
|
||||||
val inputZip = ZipFile(input)
|
if (entry.compression.toUInt() != 0u) null else if (entry.fileName.endsWith(".so")) LIBRARY_ALIGNMENT else DEFAULT_ALIGNMENT
|
||||||
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,15 +2,21 @@ package app.revanced.utils.signing.align.zip
|
|||||||
|
|
||||||
import app.revanced.utils.signing.align.zip.structures.ZipEndRecord
|
import app.revanced.utils.signing.align.zip.structures.ZipEndRecord
|
||||||
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
import app.revanced.utils.signing.align.zip.structures.ZipEntry
|
||||||
|
import java.io.Closeable
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.RandomAccessFile
|
import java.io.RandomAccessFile
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.channels.FileChannel
|
import java.nio.channels.FileChannel
|
||||||
|
import java.util.zip.CRC32
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
|
||||||
class ZipFile(val file: File) {
|
class ZipFile(val file: File) : Closeable {
|
||||||
var entries: MutableList<ZipEntry> = mutableListOf()
|
var entries: MutableList<ZipEntry> = mutableListOf()
|
||||||
|
|
||||||
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
|
private val filePointer: RandomAccessFile = RandomAccessFile(file, "rw")
|
||||||
|
private var CDNeedsRewrite = false
|
||||||
|
|
||||||
|
private val compressionLevel = 5
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//if file isn't empty try to load entries
|
//if file isn't empty try to load entries
|
||||||
@@ -53,23 +59,24 @@ class ZipFile(val file: File) {
|
|||||||
|
|
||||||
return buildList(numberOfEntries) {
|
return buildList(numberOfEntries) {
|
||||||
for (i in 1..numberOfEntries) {
|
for (i in 1..numberOfEntries) {
|
||||||
add(ZipEntry.fromCDE(filePointer).also
|
add(
|
||||||
{
|
ZipEntry.fromCDE(filePointer).also
|
||||||
//for some reason the local extra field can be different from the central one
|
{
|
||||||
it.readLocalExtra(
|
//for some reason the local extra field can be different from the central one
|
||||||
filePointer.channel.map(
|
it.readLocalExtra(
|
||||||
FileChannel.MapMode.READ_ONLY,
|
filePointer.channel.map(
|
||||||
it.localHeaderOffset.toLong() + 28,
|
FileChannel.MapMode.READ_ONLY,
|
||||||
2
|
it.localHeaderOffset.toLong() + 28,
|
||||||
|
2
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeCDE() {
|
private fun writeCD() {
|
||||||
val CDEStart = filePointer.channel.position().toUInt()
|
val CDStart = filePointer.channel.position().toUInt()
|
||||||
|
|
||||||
entries.forEach {
|
entries.forEach {
|
||||||
filePointer.channel.write(it.toCDE())
|
filePointer.channel.write(it.toCDE())
|
||||||
@@ -82,15 +89,17 @@ class ZipFile(val file: File) {
|
|||||||
0u,
|
0u,
|
||||||
entriesCount,
|
entriesCount,
|
||||||
entriesCount,
|
entriesCount,
|
||||||
filePointer.channel.position().toUInt() - CDEStart,
|
filePointer.channel.position().toUInt() - CDStart,
|
||||||
CDEStart,
|
CDStart,
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
|
|
||||||
filePointer.channel.write(endRecord.toECD())
|
filePointer.channel.write(endRecord.toECD())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
private fun addEntry(entry: ZipEntry, data: ByteBuffer) {
|
||||||
|
CDNeedsRewrite = true
|
||||||
|
|
||||||
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
entry.localHeaderOffset = filePointer.channel.position().toUInt()
|
||||||
|
|
||||||
filePointer.channel.write(entry.toLFH())
|
filePointer.channel.write(entry.toLFH())
|
||||||
@@ -99,17 +108,45 @@ class ZipFile(val file: File) {
|
|||||||
entries.add(entry)
|
entries.add(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addEntryAligned(entry: ZipEntry, data: ByteBuffer, alignment: Int) {
|
fun addEntryCompressData(entry: ZipEntry, data: ByteArray) {
|
||||||
//calculate where data would end up
|
val compressor = Deflater(compressionLevel, true)
|
||||||
val dataOffset = filePointer.filePointer + entry.LFHSize
|
compressor.setInput(data)
|
||||||
|
compressor.finish()
|
||||||
|
|
||||||
val mod = dataOffset % alignment
|
val uncompressedSize = data.size
|
||||||
|
val compressedData =
|
||||||
|
ByteArray(uncompressedSize) //i'm guessing compression won't make the data bigger
|
||||||
|
|
||||||
//wrong alignment
|
val compressedDataLength = compressor.deflate(compressedData)
|
||||||
if (mod != 0L) {
|
val compressedBuffer =
|
||||||
//add padding at end of extra field
|
ByteBuffer.wrap(compressedData.take(compressedDataLength).toByteArray())
|
||||||
entry.localExtraField =
|
|
||||||
entry.localExtraField.copyOf((entry.localExtraField.size + (alignment - mod)).toInt())
|
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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addEntry(entry, data)
|
addEntry(entry, data)
|
||||||
@@ -123,8 +160,17 @@ class ZipFile(val file: File) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finish() {
|
fun copyEntriesFromFileAligned(file: ZipFile, entryAlignment: (entry: ZipEntry) -> Int?) {
|
||||||
writeCDE()
|
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()
|
||||||
filePointer.close()
|
filePointer.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ data class ZipEndRecord(
|
|||||||
fun toECD(): ByteBuffer {
|
fun toECD(): ByteBuffer {
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
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.putUInt(ECD_SIGNATURE)
|
||||||
buffer.putUShort(diskNumber)
|
buffer.putUShort(diskNumber)
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
package app.revanced.utils.signing.align.zip.structures
|
package app.revanced.utils.signing.align.zip.structures
|
||||||
|
|
||||||
import app.revanced.utils.signing.align.zip.getUShort
|
import app.revanced.utils.signing.align.zip.*
|
||||||
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.io.DataInput
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.nio.ByteOrder
|
import java.nio.ByteOrder
|
||||||
@@ -13,12 +9,12 @@ data class ZipEntry(
|
|||||||
val version: UShort,
|
val version: UShort,
|
||||||
val versionNeeded: UShort,
|
val versionNeeded: UShort,
|
||||||
val flags: UShort,
|
val flags: UShort,
|
||||||
val compression: UShort,
|
var compression: UShort,
|
||||||
val modificationTime: UShort,
|
val modificationTime: UShort,
|
||||||
val modificationDate: UShort,
|
val modificationDate: UShort,
|
||||||
val crc32: UInt,
|
var crc32: UInt,
|
||||||
val compressedSize: UInt,
|
var compressedSize: UInt,
|
||||||
val uncompressedSize: UInt,
|
var uncompressedSize: UInt,
|
||||||
val diskNumber: UShort,
|
val diskNumber: UShort,
|
||||||
val internalAttributes: UShort,
|
val internalAttributes: UShort,
|
||||||
val externalAttributes: UInt,
|
val externalAttributes: UInt,
|
||||||
@@ -26,7 +22,7 @@ data class ZipEntry(
|
|||||||
val fileName: String,
|
val fileName: String,
|
||||||
val extraField: ByteArray,
|
val extraField: ByteArray,
|
||||||
val fileComment: String,
|
val fileComment: String,
|
||||||
var localExtraField: ByteArray = ByteArray(0), //seperate for alignment
|
var localExtraField: ByteArray = ByteArray(0), //separate for alignment
|
||||||
) {
|
) {
|
||||||
val LFHSize: Int
|
val LFHSize: Int
|
||||||
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
get() = LFH_HEADER_SIZE + fileName.toByteArray(Charsets.UTF_8).size + localExtraField.size
|
||||||
@@ -41,6 +37,27 @@ data class ZipEntry(
|
|||||||
const val LFH_HEADER_SIZE = 30
|
const val LFH_HEADER_SIZE = 30
|
||||||
const val LFH_SIGNATURE = 0x04034b50u
|
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 {
|
fun fromCDE(input: DataInput): ZipEntry {
|
||||||
val signature = input.readUIntLE()
|
val signature = input.readUIntLE()
|
||||||
|
|
||||||
@@ -67,7 +84,8 @@ data class ZipEntry(
|
|||||||
val externalAttributes = input.readUIntLE()
|
val externalAttributes = input.readUIntLE()
|
||||||
val localHeaderOffset = 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) {
|
if (variableFieldsLength > 0) {
|
||||||
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
val fileNameBytes = ByteArray(fileNameLength.toInt())
|
||||||
@@ -81,7 +99,8 @@ data class ZipEntry(
|
|||||||
fileComment = fileCommentBytes.toString(Charsets.UTF_8)
|
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(
|
return ZipEntry(
|
||||||
version,
|
version,
|
||||||
@@ -138,8 +157,9 @@ data class ZipEntry(
|
|||||||
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
val nameBytes = fileName.toByteArray(Charsets.UTF_8)
|
||||||
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
val commentBytes = fileComment.toByteArray(Charsets.UTF_8)
|
||||||
|
|
||||||
val buffer = ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
|
val buffer =
|
||||||
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
ByteBuffer.allocate(CDE_HEADER_SIZE + nameBytes.size + extraField.size + commentBytes.size)
|
||||||
|
.also { it.order(ByteOrder.LITTLE_ENDIAN) }
|
||||||
|
|
||||||
buffer.putUInt(CDE_SIGNATURE)
|
buffer.putUInt(CDE_SIGNATURE)
|
||||||
buffer.putUShort(version)
|
buffer.putUShort(version)
|
||||||
|
|||||||
Reference in New Issue
Block a user