Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add APKM support #2379

Merged
merged 2 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ and also packed to `build/jadx-<version>.zip`

### Usage
```
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)
jadx[-gui] [command] [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)
commands (use '<command> --help' for command options):
plugins - manage jadx plugins

Expand Down
1 change: 1 addition & 0 deletions jadx-cli/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies {
runtimeOnly(project(":jadx-plugins:jadx-script:jadx-script-plugin"))
runtimeOnly(project(":jadx-plugins:jadx-xapk-input"))
runtimeOnly(project(":jadx-plugins:jadx-aab-input"))
runtimeOnly(project(":jadx-plugins:jadx-apkm-input"))

implementation("org.jcommander:jcommander:2.0")
implementation("ch.qos.logback:logback-classic:1.5.13")
Expand Down
2 changes: 1 addition & 1 deletion jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

public class JadxCLIArgs {

@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .jadx.kts)")
@Parameter(description = "<input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab, .xapk, .apkm, .jadx.kts)")
protected List<String> files = new ArrayList<>(1);

@Parameter(names = { "-d", "--output-dir" }, description = "output directory")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
public class FileDialogWrapper {

private static final List<String> OPEN_FILES_EXTS = Arrays.asList(
"apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc", "jadx.kts", "xapk");
"apk", "dex", "jar", "class", "smali", "zip", "aar", "arsc", "jadx.kts", "xapk", "apkm");

private final MainWindow mainWindow;

Expand Down
11 changes: 11 additions & 0 deletions jadx-plugins/jadx-apkm-input/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id("jadx-library")
id("jadx-kotlin")
}

dependencies {
api(project(":jadx-core"))

implementation(project(":jadx-plugins:jadx-dex-input"))
implementation("com.google.code.gson:gson:2.11.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package jadx.plugins.input.apkm

import jadx.api.plugins.input.ICodeLoader
import jadx.api.plugins.input.JadxCodeInput
import jadx.api.plugins.utils.CommonFileUtils
import jadx.api.plugins.utils.ZipSecurity
import java.io.File
import java.nio.file.Path

class ApkmCustomCodeInput(
private val plugin: ApkmInputPlugin,
) : JadxCodeInput {
override fun loadFiles(input: List<Path>): ICodeLoader {
val apkFiles = mutableListOf<File>()
for (file in input.map { it.toFile() }) {
// Check if this is a valid APKM file
val manifest = ApkmUtils.getManifest(file) ?: continue
if (!ApkmUtils.isSupported(manifest)) continue

// Load all files ending with .apk
ZipSecurity.visitZipEntries<Any>(file) { zip, entry ->
if (entry.name.endsWith(".apk")) {
val tmpFile = ZipSecurity.getInputStreamForEntry(zip, entry).use {
CommonFileUtils.saveToTempFile(it, ".apk").toFile()
}
apkFiles.add(tmpFile)
}
null
}
}

val codeLoader = plugin.dexInputPlugin.loadFiles(apkFiles.map { it.toPath() })

apkFiles.forEach { CommonFileUtils.safeDeleteFile(it) }

return codeLoader
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package jadx.plugins.input.apkm

import jadx.api.ResourceFile
import jadx.api.ResourcesLoader
import jadx.api.plugins.CustomResourcesLoader
import jadx.api.plugins.utils.CommonFileUtils
import jadx.api.plugins.utils.ZipSecurity
import java.io.File

class ApkmCustomResourcesLoader : CustomResourcesLoader {
private val tmpFiles = mutableListOf<File>()

override fun load(loader: ResourcesLoader, list: MutableList<ResourceFile>, file: File): Boolean {
// Check if this is a valid APKM file
val manifest = ApkmUtils.getManifest(file) ?: return false
if (!ApkmUtils.isSupported(manifest)) return false

// Load all files ending with .apk
ZipSecurity.visitZipEntries<Any>(file) { zip, entry ->
if (entry.name.endsWith(".apk")) {
val tmpFile = ZipSecurity.getInputStreamForEntry(zip, entry).use {
CommonFileUtils.saveToTempFile(it, ".apk").toFile()
}
loader.defaultLoadFile(list, tmpFile, entry.name + "/")
tmpFiles += tmpFile
}
null
}
return true
}

override fun close() {
tmpFiles.forEach(CommonFileUtils::safeDeleteFile)
tmpFiles.clear()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package jadx.plugins.input.apkm

import jadx.api.plugins.JadxPlugin
import jadx.api.plugins.JadxPluginContext
import jadx.api.plugins.JadxPluginInfo
import jadx.plugins.input.dex.DexInputPlugin

class ApkmInputPlugin : JadxPlugin {
private val codeInput = ApkmCustomCodeInput(this)
private val resourcesLoader = ApkmCustomResourcesLoader()
internal lateinit var dexInputPlugin: DexInputPlugin

override fun getPluginInfo() = JadxPluginInfo(
"apkm-input",
"APKM Input",
"Load .apkm files",
)

override fun init(context: JadxPluginContext) {
dexInputPlugin = context.plugins().getInstance(DexInputPlugin::class.java)
context.addCodeInput(codeInput)
context.decompiler.addCustomResourcesLoader(resourcesLoader)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package jadx.plugins.input.apkm

import com.google.gson.annotations.SerializedName

data class ApkmManifest(
@SerializedName("apkm_version")
var apkmVersion: Int = -1,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package jadx.plugins.input.apkm

import jadx.api.plugins.utils.ZipSecurity
import jadx.core.utils.GsonUtils.buildGson
import jadx.core.utils.files.FileUtils
import jadx.core.utils.files.ZipFile
import java.io.File
import java.io.InputStreamReader

object ApkmUtils {
fun getManifest(file: File): ApkmManifest? {
if (!FileUtils.isZipFile(file)) return null
try {
ZipFile(file).use { zip ->
val manifestEntry = zip.getEntry("info.json") ?: return null
return InputStreamReader(ZipSecurity.getInputStreamForEntry(zip, manifestEntry)).use {
buildGson().fromJson(it, ApkmManifest::class.java)
}
}
} catch (e: Exception) {
return null
}
}

fun isSupported(manifest: ApkmManifest): Boolean {
return manifest.apkmVersion != -1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
jadx.plugins.input.apkm.ApkmInputPlugin
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ include("jadx-plugins:jadx-rename-mappings")
include("jadx-plugins:jadx-kotlin-metadata")
include("jadx-plugins:jadx-xapk-input")
include("jadx-plugins:jadx-aab-input")
include("jadx-plugins:jadx-apkm-input")

include("jadx-plugins:jadx-script:jadx-script-plugin")
include("jadx-plugins:jadx-script:jadx-script-runtime")
Expand Down
Loading