Custom Extraction
Extracting arbitrary files from APK packages.
Basic Extraction
typescript
import { Apk } from "node-apk";
import { promises as fs } from "node:fs";
const apk = new Apk("app.apk");
// Extract any file by path
const manifest = await apk.extract("AndroidManifest.xml");
console.log(`Manifest: ${manifest.length} bytes`);
// Extract assets
const config = await apk.extract("assets/config.json");
console.log(JSON.parse(config.toString("utf8")));
// Extract native libraries
const lib = await apk.extract("lib/arm64-v8a/libnative.so");
await fs.writeFile("libnative.so", lib);Listing Available Files
The APK is a ZIP file, so you can use the ZipEntry class:
typescript
import { ZipEntry } from "node-apk";
const index = await ZipEntry.index(() => fs.readFile("app.apk"));
console.log("Files in APK:");
for (const [name, entry] of index) {
console.log(` ${name} (${entry.unCompressedSize} bytes)`);
}Common APK Paths
| Path | Description |
|---|---|
AndroidManifest.xml | App manifest (binary format) |
resources.arsc | Resource table |
classes.dex | Dalvik bytecode |
assets/ | Asset files |
res/ | Compiled resources |
lib/<arch>/ | Native libraries |
META-INF/CERT.RSA | Signing certificate |
META-INF/CERT.SF | Signature file |
META-INF/MANIFEST.MF | Manifest file |
Extracting Assets
typescript
import { Apk } from "node-apk";
import { promises as fs } from "node:fs";
import * as path from "node:path";
async function extractAssets(apkPath: string, outputDir: string) {
const apk = new Apk(apkPath);
// Common asset paths
const assetPaths = [
"assets/config.json",
"assets/data.db",
"assets/fonts/",
"assets/images/",
];
for (const assetPath of assetPaths) {
try {
const content = await apk.extract(assetPath);
const outputPath = path.join(outputDir, assetPath);
// Ensure directory exists
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, content);
console.log(`Extracted: ${assetPath}`);
} catch (error) {
console.log(`Not found: ${assetPath}`);
}
}
}Extracting Native Libraries
typescript
async function extractNativeLibs(apkPath: string, outputDir: string) {
const apk = new Apk(apkPath);
const architectures = [
"arm64-v8a",
"armeabi-v7a",
"x86",
"x86_64",
];
for (const arch of architectures) {
try {
const libPath = `lib/${arch}/libnative.so`;
const lib = await apk.extract(libPath);
await fs.mkdir(`${outputDir}/${arch}`, { recursive: true });
await fs.writeFile(`${outputDir}/${arch}/libnative.so`, lib);
console.log(`Extracted: ${arch}`);
} catch (error) {
console.log(`No libs for: ${arch}`);
}
}
}Extracting DEX Files
typescript
async function extractDexFiles(apkPath: string, outputDir: string) {
const apk = new Apk(apkPath);
// Main DEX file
const classes = await apk.extract("classes.dex");
await fs.writeFile(`${outputDir}/classes.dex`, classes);
// Secondary DEX files (if any)
for (let i = 2; i <= 100; i++) {
try {
const dex = await apk.extract(`classes${i}.dex`);
await fs.writeFile(`${outputDir}/classes${i}.dex`, dex);
console.log(`Extracted: classes${i}.dex`);
} catch {
break; // No more DEX files
}
}
}Error Handling
typescript
async function safeExtract(apk: Apk, path: string): Promise<Buffer | null> {
try {
return await apk.extract(path);
} catch (error) {
if (error instanceof Error && error.message.includes("Entry not found")) {
return null;
}
throw error;
}
}
// Usage
const config = await safeExtract(apk, "assets/config.json");
if (config) {
console.log("Config found:", JSON.parse(config.toString()));
} else {
console.log("No config file in APK");
}Batch Extraction
typescript
async function extractMultiple(
apkPath: string,
files: string[],
outputDir: string
): Promise<Map<string, boolean>> {
const apk = new Apk(apkPath);
const results = new Map<string, boolean>();
for (const file of files) {
try {
const content = await apk.extract(file);
const outputPath = path.join(outputDir, file);
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, content);
results.set(file, true);
} catch {
results.set(file, false);
}
}
return results;
}
// Usage
const results = await extractMultiple("app.apk", [
"AndroidManifest.xml",
"assets/config.json",
"lib/arm64-v8a/libnative.so",
], "./extracted");
for (const [file, success] of results) {
console.log(`${success ? "✓" : "✗"} ${file}`);
}Extracting to Memory
Sometimes you don't want to write files to disk:
typescript
async function extractToMemory(apkPath: string): Promise<Map<string, Buffer>> {
const apk = new Apk(apkPath);
const files = new Map<string, Buffer>();
const paths = [
"AndroidManifest.xml",
"assets/config.json",
"assets/version.txt",
];
for (const p of paths) {
try {
files.set(p, await apk.extract(p));
} catch {
// File not found, skip
}
}
return files;
}
// Usage
const files = await extractToMemory("app.apk");
const config = files.get("assets/config.json");
if (config) {
const data = JSON.parse(config.toString("utf8"));
console.log("Config version:", data.version);
}Related
- Apk API - Extract method reference
- ZipEntry API - ZIP handling