Skip to content

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

PathDescription
AndroidManifest.xmlApp manifest (binary format)
resources.arscResource table
classes.dexDalvik bytecode
assets/Asset files
res/Compiled resources
lib/<arch>/Native libraries
META-INF/CERT.RSASigning certificate
META-INF/CERT.SFSignature file
META-INF/MANIFEST.MFManifest 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);
}

Released under the MIT License.