titanium-apk-recover 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
- ![ti_recover](https://user-images.githubusercontent.com/57605485/133170750-20244127-1ea0-4cd0-9c67-ac5ca44f17bc.png)
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/puntorigen/ti_recover/master/assets/logo.png" alt="titanium-apk-recover" width="200" />
3
+ </p>
2
4
 
3
5
  ![npm version](https://img.shields.io/npm/v/titanium-apk-recover) ![npm downloads](https://img.shields.io/npm/dm/titanium-apk-recover) ![license](https://img.shields.io/npm/l/titanium-apk-recover) ![lines](https://img.shields.io/tokei/lines/github/puntorigen/ti_recover)
4
6
 
@@ -183,6 +185,13 @@ npm run typecheck # tsc --noEmit
183
185
 
184
186
  ## Updates
185
187
 
188
+ ### version 2.2.1
189
+
190
+ - **Renamed the npm package to `titanium-apk-recover`** (previously `ti_recover`)
191
+ for better discoverability. The published package now lives under
192
+ [`titanium-apk-recover`](https://www.npmjs.com/package/titanium-apk-recover);
193
+ the CLI keeps a `ti_recover` command alias for continuity. No API changes.
194
+
186
195
  ### version 2.2.0
187
196
 
188
197
  - **Recovers the newer `ti.cloak` (`.bin`) encryption scheme** in pure JS
package/dist/cli.cjs CHANGED
@@ -1006,7 +1006,7 @@ async function readSourceFiles(base) {
1006
1006
  var EXIT_ERROR = 1;
1007
1007
  var EXIT_NOT_TITANIUM = 2;
1008
1008
  var program = new import_commander.Command();
1009
- program.name("titanium-apk-recover").description("Recover the source code from an Appcelerator Titanium APK.").version("2.2.0");
1009
+ program.name("titanium-apk-recover").description("Recover the source code from an Appcelerator Titanium APK.").version("2.2.1");
1010
1010
  program.command("recover", { isDefault: true }).description("Recover source code and assets from an APK into a directory.").argument("<apk>", "path to the .apk file").argument("<outdir>", "directory to write the recovered project into").option("--no-reconstruct", "keep the flat recovered layout instead of a Titanium project").option("--keep-tmp", "keep the temporary working directory").option("--tmp-dir <dir>", "temporary working directory", "_tmp").option("--json", "print recovery info as JSON").option("-q, --quiet", "suppress progress output").option("-d, --debug", "verbose logging").action(async (apk, outdir, opts) => {
1011
1011
  const quiet = Boolean(opts.quiet);
1012
1012
  if (!quiet) {
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../node_modules/tsup/assets/cjs_shims.js","../src/cli.ts","../src/index.ts","../src/apk.ts","../src/zip.ts","../src/manifest.ts","../src/fs-utils.ts","../src/decrypt.ts","../src/dex.ts","../src/cloak.ts","../src/info.ts","../src/reconstruct.ts","../src/write.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","#!/usr/bin/env node\n/**\n * ti_recover command-line interface.\n */\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { TiRecover, recover, type TitaniumInfo } from \"./index.js\";\n\nconst EXIT_ERROR = 1;\nconst EXIT_NOT_TITANIUM = 2;\n\nconst program = new Command();\n\nprogram\n .name(\"titanium-apk-recover\")\n .description(\"Recover the source code from an Appcelerator Titanium APK.\")\n .version(\"2.2.0\");\n\nprogram\n .command(\"recover\", { isDefault: true })\n .description(\"Recover source code and assets from an APK into a directory.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .argument(\"<outdir>\", \"directory to write the recovered project into\")\n .option(\"--no-reconstruct\", \"keep the flat recovered layout instead of a Titanium project\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print recovery info as JSON\")\n .option(\"-q, --quiet\", \"suppress progress output\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, outdir: string, opts) => {\n const quiet = Boolean(opts.quiet);\n if (!quiet) {\n console.log(\n `${pc.yellow(\"Appcelerator Titanium\")} - ${pc.green(pc.underline(\"APK Source Code Recovery Tool\"))}`,\n );\n }\n try {\n const result = await recover({\n apk,\n outDir: outdir,\n reconstruct: opts.reconstruct,\n clean: !opts.keepTmp,\n tmpDir: opts.tmpDir,\n debug: Boolean(opts.debug),\n });\n\n if (!result.recovered) {\n console.error(\n pc.red(\"The given APK was not created using Appcelerator Titanium.\"),\n );\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n\n if (opts.json) {\n console.log(JSON.stringify(result.info, null, 2));\n } else if (!quiet) {\n printInfo(result.info);\n console.log(pc.green(`\\nRecovered ${result.files?.length ?? 0} file(s) to ${outdir}`));\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n }\n });\n\nprogram\n .command(\"info\")\n .description(\"Print Titanium metadata about an APK without writing sources.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print info as JSON (default is a formatted summary)\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, opts) => {\n const ti = new TiRecover({ apk, tmpDir: opts.tmpDir, debug: Boolean(opts.debug) });\n try {\n await ti.init();\n if (!(await ti.test())) {\n console.error(pc.red(\"The given APK was not created using Appcelerator Titanium.\"));\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n await ti.extract();\n const info = await ti.info();\n if (opts.json) {\n console.log(JSON.stringify(info, null, 2));\n } else {\n printInfo(info);\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n } finally {\n if (!opts.keepTmp) await ti.clean();\n }\n });\n\nfunction printInfo(info?: TitaniumInfo): void {\n if (!info) return;\n const row = (label: string, value: unknown) =>\n console.log(` ${pc.dim(label.padEnd(16))} ${value ?? pc.dim(\"(unknown)\")}`);\n console.log(pc.bold(\"\\nTitanium APK info:\"));\n row(\"App name\", info.appName);\n row(\"Package\", info.package);\n row(\"Version\", info.versionName);\n row(\"Version code\", info.versionCode);\n row(\"Mode\", info.developmentMode ? \"development\" : \"distribution\");\n row(\"Titanium\", info.titaniumVersion);\n row(\"Alloy\", info.alloy ? \"yes\" : \"no\");\n row(\"Files\", info.files.length);\n row(\"Total bytes\", info.totalBytes);\n}\n\n// Exit explicitly once the command finishes so any lingering worker handles\n// (e.g. fflate's async unzip pool) don't hold the process open.\nprogram\n .parseAsync()\n .then(() => process.exit(process.exitCode ?? 0))\n .catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(EXIT_ERROR);\n });\n","/**\n * ti_recover - recover the source code from Appcelerator Titanium APKs.\n *\n * Public entry point exposing the {@link TiRecover} class and the one-shot\n * {@link recover} helper.\n */\nimport path from \"node:path\";\nimport { readdir, readFile, rm } from \"node:fs/promises\";\nimport { unpackApk } from \"./apk.js\";\nimport { readManifest } from \"./manifest.js\";\nimport { decryptRanges, detectAlloy } from \"./decrypt.js\";\nimport { readAssetCrypt, type AssetCryptResult } from \"./dex.js\";\nimport { pickCloakKey, decryptCloakAsset } from \"./cloak.js\";\nimport { buildInfo } from \"./info.js\";\nimport { reconstruct as reconstructProject } from \"./reconstruct.js\";\nimport { writeToDisk, copyAssets, type WrittenFile } from \"./write.js\";\nimport { fileExists, dirExists } from \"./fs-utils.js\";\nimport { isDexEntry } from \"./zip.js\";\nimport type {\n DecryptMeta,\n ManifestInfo,\n MemorySource,\n RecoverConfig,\n TitaniumInfo,\n} from \"./types.js\";\n\nexport type {\n RecoverConfig,\n RecoveredFile,\n MemorySource,\n ManifestInfo,\n TitaniumInfo,\n TitaniumVersion,\n DecryptMeta,\n DecryptResult,\n} from \"./types.js\";\nexport { parseManifest, parseBinaryManifest } from \"./manifest.js\";\nexport {\n parseRanges,\n parseAssetBuffer,\n decodeJavaInt,\n detectAlloy,\n decryptRange,\n decryptRanges,\n type Range,\n} from \"./decrypt.js\";\nexport {\n readAssetCrypt,\n extractRanges,\n extractStringChunks,\n extractByteArrayFields,\n extractCloakSalt,\n instructionWidth,\n type DexRange,\n type AssetCrypt,\n type AssetCryptResult,\n} from \"./dex.js\";\nexport { deriveCloakKey, decryptCloakAsset, pickCloakKey, isProbablyText } from \"./cloak.js\";\nexport { buildInfo } from \"./info.js\";\nexport { reconstruct as buildReconstruct, buildTiappXml } from \"./reconstruct.js\";\nexport type { WrittenFile } from \"./write.js\";\n\n/**\n * Thrown when an APK uses Titanium's ti.cloak (.bin) encryption and recovery\n * could not complete — typically because the hardcoded `salt` or the bundled\n * `libti.cloak.so` (needed to derive the AES key) is missing.\n */\nexport class UnsupportedEncryptionError extends Error {\n constructor(reason?: string) {\n super(\n \"This APK uses Titanium's newer ti.cloak asset encryption (.bin assets). \" +\n (reason ??\n \"Its AES key could not be derived from the bundled native library, so \" +\n \"the sources could not be recovered.\"),\n );\n this.name = \"UnsupportedEncryptionError\";\n }\n}\n\n/** Source file extensions recovered into memory in development mode. */\nconst DEV_SOURCE_EXTENSIONS = [\".js\", \".json\", \".xml\", \".tss\", \".rjss\", \".jss\", \".css\"];\n\nconst DEFAULT_CONFIG: Required<Pick<RecoverConfig, \"tmpDir\" | \"debug\">> = {\n tmpDir: \"_tmp\",\n debug: false,\n};\n\n/**\n * Orchestrates recovery of a Titanium APK: unpack, detect, decrypt/extract,\n * optionally reconstruct a project, and write everything to disk.\n */\nexport class TiRecover {\n private readonly cwd = process.cwd();\n private config: RecoverConfig & typeof DEFAULT_CONFIG;\n\n private apkDir = \"\";\n private tmpUsed = false;\n private manifest: ManifestInfo | null = null;\n private dexBuffers: Uint8Array[] = [];\n private cloakLibs: Buffer[] = [];\n private assetCrypt: AssetCryptResult = { kind: \"none\" };\n private developmentMode = false;\n private tested = false;\n private isTitanium = false;\n\n private memorySource: MemorySource = {};\n private meta?: DecryptMeta;\n private restructured = false;\n\n constructor(config: RecoverConfig = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /** Absolute output directory (resolved against cwd for relative paths). */\n private get outDir(): string {\n const out = this.config.outDir ?? \"\";\n return path.isAbsolute(out) ? out : path.resolve(this.cwd, out);\n }\n\n private log(message: string): void {\n if (this.config.debug) console.log(message);\n }\n\n /**\n * Prepares the working directory. If `apkDir` is supplied it is used directly\n * (reading any `classes*.dex` and manifest found there); otherwise the\n * configured `apk` is unzipped into `tmpDir`.\n */\n async init(): Promise<void> {\n if (this.config.apkDir) {\n this.apkDir = this.config.apkDir.endsWith(path.sep)\n ? this.config.apkDir\n : this.config.apkDir + path.sep;\n this.dexBuffers = await readDexBuffersFromDir(this.apkDir);\n this.cloakLibs = await readCloakLibsFromDir(this.apkDir);\n this.manifest = await readManifest(this.apkDir);\n return;\n }\n if (!this.config.apk) {\n throw new Error(\"init() requires either 'apk' or 'apkDir' in the config.\");\n }\n const apkResolved = path.isAbsolute(this.config.apk)\n ? this.config.apk\n : path.resolve(this.cwd, this.config.apk);\n const result = await unpackApk(apkResolved, this.config.tmpDir, this.config.debug);\n this.apkDir = result.apkDir;\n this.manifest = result.manifest;\n this.dexBuffers = result.dexBuffers;\n this.cloakLibs = result.cloakLibs;\n this.tmpUsed = true;\n }\n\n /**\n * Returns `true` if the prepared APK was built with Titanium (in either\n * distribution or development mode), populating internal detection state.\n */\n async test(): Promise<boolean> {\n if (!this.apkDir) {\n throw new Error(\"Call init() before test().\");\n }\n if (!this.manifest) {\n this.manifest = await readManifest(this.apkDir);\n }\n this.tested = true;\n\n // Distribution mode: look for the generated AssetCryptImpl class in the DEX.\n this.assetCrypt = readAssetCrypt(this.dexBuffers, this.manifest?.package);\n if (this.assetCrypt.kind !== \"none\") {\n // `classic` (recoverable) and `newscheme` (unsupported) are both Titanium.\n this.developmentMode = false;\n this.isTitanium = true;\n return true;\n }\n\n // Development-mode APKs ship plain sources under assets/Resources.\n const appDev = path.join(this.apkDir, \"assets\", \"Resources\", \"app.js\");\n if (await fileExists(appDev)) {\n this.developmentMode = true;\n this.isTitanium = true;\n return true;\n }\n\n this.isTitanium = false;\n return false;\n }\n\n /**\n * Extracts recovered sources into memory. For distribution builds this\n * decrypts the AES asset blob (from data lifted out of the DEX); for\n * development builds it reads the plain `assets/Resources` tree.\n */\n async extract(): Promise<MemorySource> {\n if (!this.tested) await this.test();\n if (!this.isTitanium) {\n throw new Error(\"The given APK was not created using Appcelerator Titanium.\");\n }\n\n if (!this.developmentMode) {\n if (this.assetCrypt.kind === \"newscheme\") {\n const files = await this.recoverCloak(this.assetCrypt.salt);\n if (!files) throw new UnsupportedEncryptionError();\n this.memorySource = files;\n return files;\n }\n if (this.assetCrypt.kind !== \"classic\") {\n throw new Error(\"No recoverable Titanium asset data was found in the APK.\");\n }\n const { blob, ranges, titaniumVersion } = this.assetCrypt.data;\n const { files, totalBytes } = decryptRanges(blob, ranges, this.config.debug);\n this.memorySource = files;\n this.meta = { totalBytes, titaniumVersion, alloy: detectAlloy(files) };\n return files;\n }\n\n // Development mode: read plain source files from assets/Resources.\n const baseAssets = path.join(this.apkDir, \"assets\", \"Resources\");\n const relFiles = await readSourceFiles(baseAssets);\n const source: MemorySource = {};\n for (const rel of relFiles) {\n const content = await readFile(path.join(baseAssets, rel));\n source[rel.split(path.sep).join(\"/\")] = {\n offset: 0,\n bytes: content.length,\n content,\n };\n }\n this.memorySource = source;\n return source;\n }\n\n /**\n * Attempts ti.cloak (.bin) recovery: derive the AES key from a bundled\n * `libti.cloak.so` + the `salt` lifted from `AssetCryptImpl`, then AES-128-CBC\n * decrypt every `Resources/*.bin` asset. Returns the recovered sources, or\n * `null` when the salt/native lib is missing or no key validates.\n */\n private async recoverCloak(salt: Buffer | null): Promise<MemorySource | null> {\n if (!salt) {\n this.log(\"ti.cloak-> no salt found in AssetCryptImpl; cannot derive key\");\n return null;\n }\n if (this.cloakLibs.length === 0) {\n this.log(\"ti.cloak-> no libti.cloak.so bundled in the APK; cannot derive key\");\n return null;\n }\n\n const baseAssets = path.join(this.apkDir, \"assets\", \"Resources\");\n const bins = await readCloakBinFiles(baseAssets);\n if (bins.length === 0) {\n this.log(\"ti.cloak-> no .bin assets found under assets/Resources\");\n return null;\n }\n\n // Confirm a working key by trial-decrypting a sample asset (prefer a .js).\n const sample = bins.find((b) => b.rel.endsWith(\".js\")) ?? bins[0]!;\n const key = pickCloakKey(this.cloakLibs, salt, await readFile(sample.abs));\n if (!key) {\n this.log(\"ti.cloak-> could not derive a valid AES key from libti.cloak.so\");\n return null;\n }\n\n const source: MemorySource = {};\n let totalBytes = 0;\n for (const bin of bins) {\n const decrypted = decryptCloakAsset(await readFile(bin.abs), key, salt);\n if (!decrypted) continue;\n const isText = DEV_SOURCE_EXTENSIONS.includes(path.extname(bin.rel).toLowerCase());\n source[bin.rel] = {\n offset: 0,\n bytes: decrypted.length,\n content: isText ? decrypted.toString(\"utf8\") : decrypted,\n };\n totalBytes += decrypted.length;\n this.log(`ti.cloak-> decrypted ${bin.rel}`);\n }\n if (Object.keys(source).length === 0) return null;\n\n this.meta = { totalBytes, titaniumVersion: \"unknown\", alloy: detectAlloy(source) };\n return source;\n }\n\n /**\n * Returns Titanium metadata for the current APK. Must be called after\n * {@link extract} to include the recovered file list.\n */\n async info(): Promise<TitaniumInfo> {\n if (!this.manifest && this.apkDir) {\n this.manifest = await readManifest(this.apkDir);\n }\n return buildInfo({\n manifest: this.manifest,\n memorySource: this.memorySource,\n meta: this.meta,\n developmentMode: this.developmentMode,\n });\n }\n\n /**\n * Rebuilds the in-memory sources into a Titanium-openable project structure\n * (everything under `Resources/`, plus a synthesised `tiapp.xml`).\n */\n async reconstruct(): Promise<MemorySource> {\n if (Object.keys(this.memorySource).length === 0) {\n throw new Error(\"Call extract() before reconstruct().\");\n }\n const info = await this.info();\n const result = reconstructProject(this.memorySource, info);\n this.memorySource = result.memorySource;\n this.restructured = result.restructured;\n return this.memorySource;\n }\n\n /** Writes the in-memory sources to the configured `outDir`. */\n async writeToDisk(): Promise<WrittenFile[]> {\n if (!this.config.outDir) {\n throw new Error(\"writeToDisk() requires 'outDir' in the config.\");\n }\n if (Object.keys(this.memorySource).length === 0) {\n throw new Error(\"Call extract() before writeToDisk().\");\n }\n const written = await writeToDisk(this.memorySource, this.outDir);\n for (const file of written) {\n const size = file.bytes > 1000 ? `${Math.round(file.bytes / 1024)} KB` : `${file.bytes} Bytes`;\n this.log(`writeToDisk-> file ${file.name} written (${size}).`);\n }\n return written;\n }\n\n /** Copies the APK's static assets and manifest into `outDir`. */\n async copyAssets(): Promise<void> {\n if (!this.config.outDir) {\n throw new Error(\"copyAssets() requires 'outDir' in the config.\");\n }\n await copyAssets(this.apkDir, this.outDir, this.restructured);\n }\n\n /** Removes the temporary directory created during {@link init}. */\n async clean(): Promise<void> {\n if (!this.tmpUsed || !this.apkDir) return;\n try {\n await rm(this.apkDir, { recursive: true, force: true });\n this.log(\"clean->ok\");\n } catch {\n // best-effort cleanup\n }\n }\n}\n\nexport interface RecoverOptions extends RecoverConfig {\n /** Reconstruct an openable Titanium project layout. Defaults to `false`. */\n reconstruct?: boolean;\n /** Remove the temporary working directory when finished. Defaults to `true`. */\n clean?: boolean;\n}\n\nexport interface RecoverResult {\n /** Whether the APK was recognised as a Titanium app and recovered. */\n recovered: boolean;\n /** Titanium metadata, present when `recovered` is true. */\n info?: TitaniumInfo;\n /** Files written to disk, present when `recovered` is true. */\n files?: WrittenFile[];\n}\n\n/**\n * One-shot recovery: unpack, detect, extract, optionally reconstruct, write to\n * disk, copy assets and clean up. Returns whether recovery succeeded plus the\n * gathered info.\n */\nexport async function recover(options: RecoverOptions): Promise<RecoverResult> {\n const { reconstruct = false, clean = true, ...config } = options;\n const ti = new TiRecover(config);\n try {\n await ti.init();\n if (!(await ti.test())) {\n return { recovered: false };\n }\n await ti.extract();\n const info = await ti.info();\n if (reconstruct) {\n await ti.reconstruct();\n }\n const files = await ti.writeToDisk();\n await ti.copyAssets();\n return { recovered: true, info, files };\n } finally {\n if (clean) await ti.clean();\n }\n}\n\n/** Reads `classes*.dex` buffers from a pre-extracted directory (if any). */\nasync function readDexBuffersFromDir(dir: string): Promise<Uint8Array[]> {\n if (!(await dirExists(dir))) return [];\n const names = (await readdir(dir)).filter(isDexEntry).sort();\n const buffers: Uint8Array[] = [];\n for (const name of names) {\n buffers.push(await readFile(path.join(dir, name)));\n }\n return buffers;\n}\n\n/** Reads any `lib/**\\/libti.cloak.so` buffers from a pre-extracted directory. */\nasync function readCloakLibsFromDir(dir: string): Promise<Buffer[]> {\n const libDir = path.join(dir, \"lib\");\n if (!(await dirExists(libDir))) return [];\n const entries = await readdir(libDir, { recursive: true, withFileTypes: true });\n const libs: Buffer[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || entry.name !== \"libti.cloak.so\") continue;\n const parent = (entry as unknown as { parentPath?: string; path?: string }).parentPath ??\n (entry as unknown as { path?: string }).path ??\n libDir;\n libs.push(await readFile(path.join(parent, entry.name)));\n }\n return libs;\n}\n\n/** Lists ti.cloak `.bin` assets under `base`, mapping each to its logical name. */\nasync function readCloakBinFiles(base: string): Promise<{ rel: string; abs: string }[]> {\n if (!(await dirExists(base))) return [];\n const entries = await readdir(base, { recursive: true, withFileTypes: true });\n const bins: { rel: string; abs: string }[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".bin\")) continue;\n const parent = (entry as unknown as { parentPath?: string; path?: string }).parentPath ??\n (entry as unknown as { path?: string }).path ??\n base;\n const abs = path.join(parent, entry.name);\n // Logical name = path under Resources with the `.bin` suffix removed.\n const rel = path.relative(base, abs).split(path.sep).join(\"/\").replace(/\\.bin$/, \"\");\n bins.push({ rel, abs });\n }\n return bins;\n}\n\n/** Recursively lists source files (relative paths) under `base`. */\nasync function readSourceFiles(base: string): Promise<string[]> {\n if (!(await dirExists(base))) return [];\n const entries = await readdir(base, { recursive: true, withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const ext = path.extname(entry.name).toLowerCase();\n if (!DEV_SOURCE_EXTENSIONS.includes(ext)) continue;\n const parentPath = (entry as unknown as { parentPath?: string; path?: string }).parentPath ??\n (entry as unknown as { path?: string }).path ??\n base;\n const abs = path.join(parentPath, entry.name);\n files.push(path.relative(base, abs));\n }\n return files;\n}\n","/**\n * Pure-JS APK unpack (no JVM). Reads the APK zip with `fflate`, parses the\n * binary manifest, extracts `assets/Resources/**` and a readable manifest into\n * a working directory, and returns the raw `classes*.dex` buffers for DEX-based\n * asset recovery.\n */\nimport path from \"node:path\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { readApkEntries, isDexEntry } from \"./zip.js\";\nimport { parseBinaryManifest } from \"./manifest.js\";\nimport { fileExists } from \"./fs-utils.js\";\nimport type { ManifestInfo } from \"./types.js\";\n\nexport interface UnpackResult {\n /** Working directory (with trailing separator) holding manifest + assets. */\n apkDir: string;\n /** Parsed manifest info, or null if the APK had no manifest. */\n manifest: ManifestInfo | null;\n /** Raw `classes*.dex` buffers, ordered `classes.dex`, `classes2.dex`, ... */\n dexBuffers: Uint8Array[];\n /** `lib/<abi>/libti.cloak.so` buffers (for ti.cloak key derivation). */\n cloakLibs: Buffer[];\n}\n\nconst ASSETS_PREFIX = \"assets/Resources/\";\nconst CLOAK_LIB_SUFFIX = \"/libti.cloak.so\";\n\nfunction isCloakLib(name: string): boolean {\n return name.startsWith(\"lib/\") && name.endsWith(CLOAK_LIB_SUFFIX);\n}\n\n/**\n * Unpacks an APK into `tmpDir` (relative to `cwd`): writes a readable\n * `AndroidManifest.xml` and `assets/Resources/**`, and returns the manifest and\n * DEX buffers for downstream recovery.\n */\nexport async function unpackApk(\n apkPath: string,\n tmpDir: string,\n debug = false,\n): Promise<UnpackResult> {\n if (!(await fileExists(apkPath))) {\n throw new Error(`The given APK file doesn't exist: ${apkPath}`);\n }\n\n if (debug) console.log(\"preparing -> reading APK entries\");\n const entries = await readApkEntries(\n apkPath,\n (f) =>\n f.name === \"AndroidManifest.xml\" ||\n isDexEntry(f.name) ||\n f.name.startsWith(ASSETS_PREFIX) ||\n isCloakLib(f.name),\n );\n\n const apkDir = path.join(process.cwd(), tmpDir) + path.sep;\n await mkdir(apkDir, { recursive: true });\n\n const dexBuffers = Object.keys(entries)\n .filter(isDexEntry)\n .sort()\n .map((name) => entries[name]!);\n\n const cloakLibs = Object.keys(entries)\n .filter(isCloakLib)\n .sort()\n .map((name) => Buffer.from(entries[name]!));\n\n let manifest: ManifestInfo | null = null;\n const manifestBytes = entries[\"AndroidManifest.xml\"];\n if (manifestBytes) {\n const { info, xml } = parseBinaryManifest(Buffer.from(manifestBytes), apkDir);\n manifest = info;\n await writeFile(path.join(apkDir, \"AndroidManifest.xml\"), xml);\n }\n\n for (const [name, data] of Object.entries(entries)) {\n if (!name.startsWith(ASSETS_PREFIX) || name.endsWith(\"/\")) continue;\n const dest = path.join(apkDir, name);\n await mkdir(path.dirname(dest), { recursive: true });\n await writeFile(dest, Buffer.from(data));\n }\n\n if (debug) console.log(\"preparing -> ready\");\n return { apkDir, manifest, dexBuffers, cloakLibs };\n}\n","/**\n * Minimal pure-JS APK (zip) reader built on `fflate`. Used to pull the entries\n * we care about (`AndroidManifest.xml`, `classes*.dex`, `assets/Resources/**`)\n * without shelling out to apktool or a JVM.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { unzip, type Unzipped, type UnzipFileInfo } from \"fflate\";\n\nexport type ApkEntries = Unzipped;\n\n/**\n * Reads an APK from disk and returns the decompressed entries matching\n * `filter` (or all entries when no filter is given).\n */\nexport async function readApkEntries(\n apkPath: string,\n filter?: (info: UnzipFileInfo) => boolean,\n): Promise<ApkEntries> {\n const buf = await readFile(apkPath);\n const u8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n return await new Promise<ApkEntries>((resolve, reject) => {\n unzip(u8, { filter: filter ?? (() => true) }, (err, data) => {\n if (err) reject(err);\n else resolve(data);\n });\n });\n}\n\n/** True for `classes.dex`, `classes2.dex`, ... entries. */\nexport function isDexEntry(name: string): boolean {\n return /^classes\\d*\\.dex$/.test(name);\n}\n","/**\n * Parse Android manifests.\n *\n * The main recovery path reads the APK's *binary* `AndroidManifest.xml` (AXML)\n * via adbkit-apkreader's binary XML parser and also serialises a readable XML\n * copy for the recovered output. A text parser (`parseManifest`) is kept for\n * the case where a caller points at a pre-decoded, textual manifest.\n */\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport { load } from \"cheerio\";\nimport type { ManifestInfo } from \"./types.js\";\nimport { fileExists } from \"./fs-utils.js\";\n\nconst require = createRequire(import.meta.url);\n// adbkit-apkreader ships no types; load its binary XML parser as a CJS module.\nconst BinaryXmlParser = require(\n \"adbkit-apkreader/lib/apkreader/parser/binaryxml\",\n) as new (buffer: Buffer, options?: { debug?: boolean }) => { parse(): XmlNode };\n\ninterface XmlAttribute {\n namespaceURI: string | null;\n name: string;\n value: string | null;\n typedValue: { value: unknown; type: string } | null;\n}\n\ninterface XmlNode {\n namespaceURI: string | null;\n nodeType: number;\n nodeName: string;\n attributes?: XmlAttribute[];\n childNodes?: XmlNode[];\n}\n\nconst ANDROID_NS = \"http://schemas.android.com/apk/res/android\";\n\n/** Binary AXML magic (`RES_XML_TYPE` = 0x0003 with header size 0x0008). */\nexport function isBinaryManifest(bytes: Buffer): boolean {\n return bytes.length >= 4 && bytes[0] === 0x03 && bytes[1] === 0x00;\n}\n\nfunction attr(node: XmlNode, name: string): XmlAttribute | undefined {\n return node.attributes?.find((a) => a.name === name);\n}\n\nfunction attrString(node: XmlNode, name: string): string | undefined {\n const a = attr(node, name);\n if (!a) return undefined;\n if (a.value != null) return a.value;\n const tv = a.typedValue?.value;\n return tv == null ? undefined : String(tv);\n}\n\n/**\n * Parses a binary `AndroidManifest.xml` buffer, returning both structured info\n * and a readable XML serialisation.\n */\nexport function parseBinaryManifest(bytes: Buffer, dir?: string): { info: ManifestInfo; xml: string } {\n const doc = new BinaryXmlParser(bytes).parse();\n const application = doc.childNodes?.find((n) => n.nodeName === \"application\");\n const info: ManifestInfo = {\n package: attrString(doc, \"package\"),\n versionCode: attrString(doc, \"versionCode\"),\n versionName: attrString(doc, \"versionName\"),\n appName: application ? attrString(application, \"label\") : undefined,\n dir,\n };\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n${serialize(doc)}\\n`;\n return { info, xml };\n}\n\n/**\n * Text manifest parser (cheerio) for pre-decoded, human-readable manifests.\n */\nexport function parseManifest(xml: string, dir?: string): ManifestInfo {\n const $ = load(xml, { xml: true });\n return {\n package: $(\"manifest[package]\").attr(\"package\"),\n versionCode: $(\"manifest\").attr(\"android:versionCode\"),\n versionName: $(\"manifest\").attr(\"android:versionName\"),\n appName: $(\"manifest application\").attr(\"android:label\"),\n dir,\n };\n}\n\n/**\n * Reads `AndroidManifest.xml` from a directory, handling both binary (AXML) and\n * already-decoded text manifests. Returns `null` if the manifest is missing.\n */\nexport async function readManifest(apkDir: string): Promise<ManifestInfo | null> {\n const manifestPath = path.join(apkDir, \"AndroidManifest.xml\");\n if (!(await fileExists(manifestPath))) return null;\n const bytes = await readFile(manifestPath);\n if (isBinaryManifest(bytes)) {\n return parseBinaryManifest(bytes, apkDir).info;\n }\n return parseManifest(bytes.toString(\"utf8\"), apkDir);\n}\n\n// ---------------------------------------------------------------------------\n// Readable XML serialisation of the parsed manifest DOM.\n// ---------------------------------------------------------------------------\n\nfunction prefixFor(namespaceURI: string | null): string {\n if (!namespaceURI) return \"\";\n return namespaceURI === ANDROID_NS ? \"android:\" : \"\";\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction serialize(node: XmlNode, depth = 0): string {\n const indent = \" \".repeat(depth);\n const attrs: string[] = [];\n if (depth === 0) attrs.push(`xmlns:android=\"${ANDROID_NS}\"`);\n for (const a of node.attributes ?? []) {\n const raw = a.value ?? (a.typedValue?.value == null ? \"\" : String(a.typedValue.value));\n attrs.push(`${prefixFor(a.namespaceURI)}${a.name}=\"${escapeXml(raw)}\"`);\n }\n const attrText = attrs.length ? \" \" + attrs.join(\" \") : \"\";\n const children = (node.childNodes ?? []).filter((c) => c.nodeType === 1);\n if (children.length === 0) {\n return `${indent}<${node.nodeName}${attrText} />`;\n }\n const inner = children.map((c) => serialize(c, depth + 1)).join(\"\\n\");\n return `${indent}<${node.nodeName}${attrText}>\\n${inner}\\n${indent}</${node.nodeName}>`;\n}\n","/**\n * Small async filesystem helpers used across the library.\n */\nimport { stat } from \"node:fs/promises\";\n\n/** Resolves to `true` if `filePath` exists and is a regular file. */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n return (await stat(filePath)).isFile();\n } catch {\n return false;\n }\n}\n\n/** Resolves to `true` if `dirPath` exists and is a directory. */\nexport async function dirExists(dirPath: string): Promise<boolean> {\n try {\n return (await stat(dirPath)).isDirectory();\n } catch {\n return false;\n }\n}\n","/**\n * Decrypt Titanium's asset blob with pure Node crypto (no JVM).\n *\n * Distribution-mode Titanium stores every source file as one AES-encrypted\n * blob; the AES key is the last 16 bytes of that blob and each file occupies an\n * `[offset, length]` range within it. Java's default `Cipher.getInstance(\"AES\")`\n * is `AES/ECB/PKCS5Padding`, which maps directly to Node's `aes-128-ecb` with\n * automatic PKCS padding.\n *\n * The blob and ranges now come from {@link readAssetCrypt} (DEX parsing). The\n * smali/java text parsers below (`parseAssetBuffer`, `parseRanges`,\n * `decodeJavaInt`) are retained as standalone helpers for callers working from\n * decompiled sources, but are no longer on the main recovery path.\n */\nimport { createDecipheriv } from \"node:crypto\";\nimport type { DexRange } from \"./dex.js\";\nimport type { MemorySource, TitaniumVersion } from \"./types.js\";\n\n/** A single file's location inside the decrypted asset blob. */\nexport interface Range {\n offset: number;\n bytes: number;\n}\n\n/** Result of parsing `AssetCryptImpl.smali`'s `initAssetsBytes()`. */\nexport interface AssetBufferParse {\n titaniumVersion: TitaniumVersion;\n bufferLen: number;\n escaped: string;\n}\n\n/**\n * Decodes an integer literal the way `java.lang.Integer.decode` does:\n * supports optional sign, `0x`/`0X`/`#` hex, leading-zero octal, and decimal.\n */\nexport function decodeJavaInt(literal: string): number {\n let s = literal.trim();\n let sign = 1;\n if (s.startsWith(\"+\")) {\n s = s.slice(1);\n } else if (s.startsWith(\"-\")) {\n sign = -1;\n s = s.slice(1);\n }\n let value: number;\n if (s.startsWith(\"0x\") || s.startsWith(\"0X\")) {\n value = parseInt(s.slice(2), 16);\n } else if (s.startsWith(\"#\")) {\n value = parseInt(s.slice(1), 16);\n } else if (s.length > 1 && s.startsWith(\"0\")) {\n value = parseInt(s.slice(1), 8);\n } else {\n value = parseInt(s, 10);\n }\n return sign * value;\n}\n\n/**\n * Parses the `hashMap.put(\"file\", new Range(offset, length))` entries from an\n * `AssetCryptImpl.java` source into an ordered map of file -> range.\n */\nexport function parseRanges(javaContent: string): Record<string, Range> {\n const ranges: Record<string, Range> = {};\n const re =\n /hashMap\\.put\\(\\s*\"((?:[^\"\\\\]|\\\\.)*)\"\\s*,\\s*new Range\\(\\s*([^,]+?)\\s*,\\s*([^)]+?)\\s*\\)\\s*\\)/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(javaContent)) !== null) {\n const file = match[1];\n const offset = match[2];\n const length = match[3];\n if (file === undefined || offset === undefined || length === undefined) continue;\n ranges[file] = {\n offset: decodeJavaInt(offset),\n bytes: decodeJavaInt(length),\n };\n }\n return ranges;\n}\n\n/**\n * Parses `AssetCryptImpl.smali`, extracting the declared buffer length, the\n * concatenated escaped string literal and a coarse Titanium version.\n */\nexport function parseAssetBuffer(smaliContent: string): AssetBufferParse {\n const lines = smaliContent.split(/\\r?\\n/);\n let started = false;\n let titaniumVersion: TitaniumVersion = \"unknown\";\n let bufferLen = -1;\n const chunks: string[] = [];\n\n for (const line of lines) {\n if (line.indexOf(\"private static initAssetsBytes()Ljava/nio/CharBuffer\") !== -1) {\n started = true;\n continue;\n }\n if (!started) continue;\n\n if (line.indexOf(\"const v0, \") !== -1) {\n titaniumVersion = \"<5\";\n bufferLen = decodeJavaInt(line.split(\"const v0, \").join(\"\").trim());\n } else if (line.indexOf(\"const/16 v0, \") !== -1) {\n titaniumVersion = \"5.x\";\n bufferLen = decodeJavaInt(line.split(\"const/16 v0, \").join(\"\").trim());\n } else if (line.indexOf(\"const-string v1\") !== -1) {\n let content = line.split('const-string v1, \"').join(\"\").trim();\n content = content.slice(0, -1);\n chunks.push(content);\n } else if (line.indexOf(\"rewind()Ljava/nio/Buffer;\") !== -1) {\n break;\n }\n }\n\n return { titaniumVersion, bufferLen, escaped: chunks.join(\"\") };\n}\n\n/** Heuristic: does the recovered file set look like an Alloy project? */\nexport function detectAlloy(files: MemorySource): boolean {\n return Object.keys(files).some(\n (name) =>\n name === \"alloy.js\" ||\n name.startsWith(\"alloy/\") ||\n name.includes(\"/alloy/\") ||\n name === \"_app_props_.json\",\n );\n}\n\nconst KEY_LEN = 16;\n\n/**\n * Decrypts one `[offset, length]` slice of the blob. The key is the last 16\n * bytes of the blob. Titanium varied two conventions across versions, so we try\n * the key derived from both `length` and `length - 1`, and the slice from both\n * `offset` and `offset - 1` (some files have a padded offset).\n */\nexport function decryptRange(blob: Buffer, offset: number, length: number): string | null {\n for (const total of [blob.length - 1, blob.length]) {\n if (total - KEY_LEN < 0) continue;\n const key = blob.subarray(total - KEY_LEN, total);\n for (const start of [offset, offset - 1]) {\n if (start < 0 || start + length > blob.length) continue;\n try {\n const decipher = createDecipheriv(\"aes-128-ecb\", key, null);\n decipher.setAutoPadding(true);\n const out = Buffer.concat([\n decipher.update(blob.subarray(start, start + length)),\n decipher.final(),\n ]);\n const text = out.toString(\"utf8\");\n if (text !== \"\") return text;\n } catch {\n // try the next key/offset combination\n }\n }\n }\n return null;\n}\n\nexport interface DecryptRangesResult {\n files: MemorySource;\n totalBytes: number;\n}\n\n/**\n * Decrypts every range out of the blob into an in-memory source map. Files that\n * fail to decrypt are skipped rather than aborting the whole run.\n */\nexport function decryptRanges(\n blob: Buffer,\n ranges: DexRange[],\n debug = false,\n): DecryptRangesResult {\n const files: MemorySource = {};\n let totalBytes = 0;\n for (const range of ranges) {\n const content = decryptRange(blob, range.offset, range.bytes);\n if (content === null) continue;\n files[range.file] = { offset: range.offset, bytes: range.bytes, content };\n totalBytes += range.bytes;\n if (debug) console.log(`file:${range.file}, decrypted !`);\n }\n return { files, totalBytes };\n}\n","/**\n * Extract Titanium's encrypted asset data directly from `classes*.dex`,\n * replacing the apktool (smali) + jadx (java) decompile step.\n *\n * The generated `AssetCryptImpl` class has two private static methods:\n * - `initAssetsBytes()` builds the encrypted blob by appending a sequence of\n * `const-string` literals into a CharBuffer.\n * - `initAssets()` fills a HashMap with `put(\"file\", new Range(offset, length))`\n * entries.\n *\n * Both are ordinary DEX bytecode. We locate the class, then walk each method's\n * instruction stream: for the blob we collect `const-string`/`const-string/jumbo`\n * operands in order; for the ranges we do a tiny register trace to recover the\n * (file, offset, length) triples. A DEX instruction width table lets us step\n * over every opcode correctly. Since DEX stores real (already-unescaped)\n * strings, the old Java string-unescape step is gone entirely.\n */\nimport { DexFile, type DexClassDef, type DexCode } from \"libdex-ts\";\nimport type { TitaniumVersion } from \"./types.js\";\n\n/** A single file's location inside the decrypted blob, from `initAssets`. */\nexport interface DexRange {\n file: string;\n offset: number;\n bytes: number;\n}\n\nexport interface AssetCrypt {\n /** The raw encrypted asset blob (last 16 bytes are the AES key). */\n blob: Buffer;\n ranges: DexRange[];\n titaniumVersion: TitaniumVersion;\n}\n\nexport type AssetCryptResult =\n | { kind: \"classic\"; data: AssetCrypt }\n // AssetCryptImpl present but the ti.cloak/.bin variant; `salt` is the AES-CBC\n // IV lifted from the class's <clinit> (null if it couldn't be located).\n | { kind: \"newscheme\"; salt: Buffer | null }\n | { kind: \"none\" };\n\n// ---------------------------------------------------------------------------\n// DEX instruction widths (in 16-bit code units), indexed by base opcode.\n// Derived from the Dalvik bytecode instruction formats.\n// ---------------------------------------------------------------------------\nconst WIDTHS = buildWidthTable();\n\nfunction buildWidthTable(): Uint8Array {\n const w = new Uint8Array(256).fill(1);\n const set = (from: number, to: number, units: number) => {\n for (let op = from; op <= to; op++) w[op] = units;\n };\n // 2-code-unit instructions\n set(0x02, 0x03, 2); // move/from16, move/16 (0x03 is 3 -> fixed below)\n w[0x03] = 3;\n set(0x05, 0x06, 2);\n w[0x06] = 3;\n set(0x08, 0x09, 2);\n w[0x09] = 3;\n w[0x13] = 2; // const/16\n w[0x14] = 3; // const\n w[0x15] = 2; // const/high16\n w[0x16] = 2; // const-wide/16\n w[0x17] = 3; // const-wide/32\n w[0x18] = 5; // const-wide\n w[0x19] = 2; // const-wide/high16\n w[0x1a] = 2; // const-string\n w[0x1b] = 3; // const-string/jumbo\n w[0x1c] = 2; // const-class\n w[0x1f] = 2; // check-cast\n w[0x20] = 2; // instance-of\n w[0x22] = 2; // new-instance\n w[0x23] = 2; // new-array\n w[0x24] = 3; // filled-new-array (35c)\n w[0x25] = 3; // filled-new-array/range (3rc)\n w[0x26] = 3; // fill-array-data (31t)\n w[0x29] = 2; // goto/16\n w[0x2a] = 3; // goto/32\n w[0x2b] = 3; // packed-switch\n w[0x2c] = 3; // sparse-switch\n set(0x2d, 0x31, 2); // cmpkind (23x)\n set(0x32, 0x37, 2); // if-test (22t)\n set(0x38, 0x3d, 2); // if-testz (21t)\n set(0x44, 0x51, 2); // aget/aput (23x)\n set(0x52, 0x5f, 2); // iget/iput (22c)\n set(0x60, 0x6d, 2); // sget/sput (21c)\n set(0x6e, 0x72, 3); // invoke-kind (35c)\n set(0x74, 0x78, 3); // invoke-kind/range (3rc)\n set(0x90, 0xaf, 2); // binop (23x)\n set(0xd0, 0xd7, 2); // binop/lit16 (22s)\n set(0xd8, 0xe2, 2); // binop/lit8 (22b)\n w[0xfa] = 4; // invoke-polymorphic (45cc)\n w[0xfb] = 4; // invoke-polymorphic/range (4rcc)\n w[0xfc] = 3; // invoke-custom (35c)\n w[0xfd] = 3; // invoke-custom/range (3rc)\n w[0xfe] = 2; // const-method-handle\n w[0xff] = 2; // const-method-type\n return w;\n}\n\n/** Width, in code units, of the instruction at `idx` (handles payloads). */\nexport function instructionWidth(insns: Uint16Array, idx: number): number {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n if (op === 0x00 && unit !== 0x0000) {\n // Pseudo-op payload tables.\n switch (unit) {\n case 0x0100: {\n // packed-switch-payload: ident + size + first_key(2) + targets(size*2)\n const size = insns[idx + 1] ?? 0;\n return size * 2 + 4;\n }\n case 0x0200: {\n // sparse-switch-payload: ident + size + keys(size*2) + targets(size*2)\n const size = insns[idx + 1] ?? 0;\n return size * 4 + 2;\n }\n case 0x0300: {\n // fill-array-data-payload\n const elementWidth = insns[idx + 1] ?? 0;\n const size = (insns[idx + 2] ?? 0) + (insns[idx + 3] ?? 0) * 0x10000;\n return 4 + Math.ceil((size * elementWidth) / 2);\n }\n default:\n return 1;\n }\n }\n return WIDTHS[op] ?? 1;\n}\n\nconst s16 = (v: number): number => (v > 0x7fff ? v - 0x10000 : v);\n\n/** Collects `const-string` operands from a method in execution order. */\nexport function extractStringChunks(dex: DexFile, insns: Uint16Array): string[] {\n const chunks: string[] = [];\n let idx = 0;\n while (idx < insns.length) {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n if (op === 0x1a) {\n chunks.push(dex.getStringById(insns[idx + 1] ?? 0));\n } else if (op === 0x1b) {\n chunks.push(dex.getStringById((insns[idx + 1] ?? 0) + (insns[idx + 2] ?? 0) * 0x10000));\n }\n idx += instructionWidth(insns, idx);\n }\n return chunks;\n}\n\n/**\n * Walks `initAssets` with a small register trace to recover the file ranges.\n * Tracks integer/string constants per register, associates `new Range(off,len)`\n * with the object register at its `<init>`, and emits an entry when that object\n * is stored into the map via `put(key, rangeObj)`.\n */\nexport function extractRanges(dex: DexFile, insns: Uint16Array): DexRange[] {\n const regInt = new Map<number, number>();\n const regStr = new Map<number, string>();\n const pending = new Map<number, { offset: number; length: number }>();\n const ranges: DexRange[] = [];\n\n const resolveInvoke = (methodIdx: number, args: number[]): void => {\n const mid = dex.getMethodId(methodIdx);\n const cls = dex.getTypeDescriptorByIdx(mid.classIdx);\n const name = dex.getStringById(mid.nameIdx);\n if (name === \"<init>\" && /[/$]Range;$/.test(cls) && args.length >= 3) {\n const offset = regInt.get(args[1]!);\n const length = regInt.get(args[2]!);\n if (offset !== undefined && length !== undefined) {\n pending.set(args[0]!, { offset, length });\n }\n } else if (name === \"put\" && args.length >= 3) {\n const key = regStr.get(args[1]!);\n const range = pending.get(args[2]!);\n if (key !== undefined && range) {\n ranges.push({ file: key, offset: range.offset, bytes: range.length });\n }\n }\n };\n\n let idx = 0;\n while (idx < insns.length) {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n switch (op) {\n case 0x12: {\n // const/4 (11n)\n const a = (unit >> 8) & 0xf;\n let v = (unit >> 12) & 0xf;\n if (v > 7) v -= 16;\n regInt.set(a, v);\n break;\n }\n case 0x13: {\n // const/16 (21s)\n regInt.set((unit >> 8) & 0xff, s16(insns[idx + 1] ?? 0));\n break;\n }\n case 0x14: {\n // const (31i)\n const v = ((insns[idx + 1] ?? 0) | ((insns[idx + 2] ?? 0) << 16)) | 0;\n regInt.set((unit >> 8) & 0xff, v);\n break;\n }\n case 0x15: {\n // const/high16 (21h)\n regInt.set((unit >> 8) & 0xff, ((insns[idx + 1] ?? 0) << 16) | 0);\n break;\n }\n case 0x1a: {\n // const-string (21c)\n regStr.set((unit >> 8) & 0xff, dex.getStringById(insns[idx + 1] ?? 0));\n break;\n }\n case 0x1b: {\n // const-string/jumbo (31c)\n regStr.set(\n (unit >> 8) & 0xff,\n dex.getStringById((insns[idx + 1] ?? 0) + (insns[idx + 2] ?? 0) * 0x10000),\n );\n break;\n }\n case 0x6e:\n case 0x6f:\n case 0x70:\n case 0x71:\n case 0x72: {\n // invoke-kind (35c)\n const a = (unit >> 12) & 0xf;\n const g = (unit >> 8) & 0xf;\n const methodIdx = insns[idx + 1] ?? 0;\n const regsWord = insns[idx + 2] ?? 0;\n const args = [\n regsWord & 0xf,\n (regsWord >> 4) & 0xf,\n (regsWord >> 8) & 0xf,\n (regsWord >> 12) & 0xf,\n g,\n ].slice(0, a);\n resolveInvoke(methodIdx, args);\n break;\n }\n case 0x74:\n case 0x75:\n case 0x76:\n case 0x77:\n case 0x78: {\n // invoke-kind/range (3rc)\n const count = (unit >> 8) & 0xff;\n const methodIdx = insns[idx + 1] ?? 0;\n const first = insns[idx + 2] ?? 0;\n const args: number[] = [];\n for (let i = 0; i < count; i++) args.push(first + i);\n resolveInvoke(methodIdx, args);\n break;\n }\n }\n idx += instructionWidth(insns, idx);\n }\n\n return ranges;\n}\n\n/** Heuristic Titanium version from the CharBuffer.allocate size opcode. */\nfunction detectVersion(insns: Uint16Array): TitaniumVersion {\n let idx = 0;\n while (idx < insns.length) {\n const op = (insns[idx] ?? 0) & 0xff;\n if (op === 0x14) return \"<5\"; // const (large size)\n if (op === 0x13) return \"5.x\"; // const/16 (fits 16 bits)\n if (op === 0x1a || op === 0x1b) break; // reached the string appends\n idx += instructionWidth(insns, idx);\n }\n return \"unknown\";\n}\n\nconst s32 = (lo: number, hi: number): number => (lo | (hi << 16)) | 0;\n\n/** Reads a `fill-array-data-payload` of byte elements at `payloadIdx`. */\nfunction readFillArrayBytes(insns: Uint16Array, payloadIdx: number): Buffer | null {\n if (payloadIdx < 0 || (insns[payloadIdx] ?? 0) !== 0x0300) return null;\n const elementWidth = insns[payloadIdx + 1] ?? 0;\n if (elementWidth !== 1) return null; // only byte[] arrays (the salt)\n const size = (insns[payloadIdx + 2] ?? 0) + (insns[payloadIdx + 3] ?? 0) * 0x10000;\n const out = Buffer.alloc(size);\n const dataStart = payloadIdx + 4;\n for (let j = 0; j < size; j++) {\n const unit = insns[dataStart + (j >> 1)] ?? 0;\n out[j] = (j & 1) === 1 ? (unit >> 8) & 0xff : unit & 0xff;\n }\n return out;\n}\n\n/**\n * Extracts static `byte[]` fields (assigned via `fill-array-data` +\n * `sput-object`) from a method's instruction stream, keyed by field name.\n */\nexport function extractByteArrayFields(dex: DexFile, insns: Uint16Array): Record<string, Buffer> {\n const pending = new Map<number, Buffer>();\n const fields: Record<string, Buffer> = {};\n let idx = 0;\n while (idx < insns.length) {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n if (op === 0x26) {\n // fill-array-data (31t): vAA, +BBBBBBBB (signed, code units, relative)\n const reg = (unit >> 8) & 0xff;\n const off = s32(insns[idx + 1] ?? 0, insns[idx + 2] ?? 0);\n const bytes = readFillArrayBytes(insns, idx + off);\n if (bytes) pending.set(reg, bytes);\n } else if (op === 0x69) {\n // sput-object (21c): vAA, field@BBBB\n const reg = (unit >> 8) & 0xff;\n const bytes = pending.get(reg);\n if (bytes) {\n const fieldId = dex.getFieldId(insns[idx + 1] ?? 0);\n fields[dex.getStringById(fieldId.nameIdx)] = bytes;\n }\n }\n idx += instructionWidth(insns, idx);\n }\n return fields;\n}\n\n/** Lifts the ti.cloak `salt` (AES-CBC IV) out of `AssetCryptImpl.<clinit>`. */\nexport function extractCloakSalt(dex: DexFile, classDef: DexClassDef): Buffer | null {\n const clinit = methodCode(dex, classDef, \"<clinit>\");\n if (!clinit) return null;\n const fields = extractByteArrayFields(dex, clinit.insns);\n if (fields.salt) return fields.salt;\n // Fallback: a lone 16-byte byte[] literal is almost certainly the IV/salt.\n const sixteen = Object.values(fields).filter((b) => b.length === 16);\n return sixteen.length === 1 ? sixteen[0]! : null;\n}\n\nfunction findAssetCryptClassDef(dex: DexFile, packageHint?: string): DexClassDef | null {\n if (packageHint) {\n const descriptor = `L${packageHint.split(\".\").join(\"/\")}/AssetCryptImpl;`;\n const byHint = dex.getClassDefByDescriptor(descriptor);\n if (byHint) return byHint;\n }\n for (let i = 0; i < dex.header.classDefsSize; i++) {\n const classDef = dex.getClassDef(i);\n const descriptor = dex.getTypeDescriptorByIdx(classDef.classIdx);\n if (descriptor.endsWith(\"/AssetCryptImpl;\")) return classDef;\n }\n return null;\n}\n\nfunction methodCode(dex: DexFile, classDef: DexClassDef, methodName: string): DexCode | null {\n const classData = dex.getClassData(classDef);\n // In a class_data_item, method indices are ULEB128 deltas that must be\n // accumulated (separately for direct and virtual methods). libdex-ts returns\n // the raw delta in `methodIdx`, so we do the running sum here.\n for (const list of [classData.directMethods, classData.virtualMethods]) {\n let methodIdx = 0;\n for (const method of list) {\n methodIdx += method.methodIdx;\n const name = dex.getStringById(dex.getMethodId(methodIdx).nameIdx);\n if (name === methodName) return dex.getDexCode(method);\n }\n }\n return null;\n}\n\n/**\n * Locates `AssetCryptImpl` across the given DEX buffers and extracts the\n * encrypted blob plus file ranges. Returns `newscheme` when the class exists\n * but lacks `initAssetsBytes` (the ti.cloak/.bin variant), or `none` when the\n * class is absent entirely.\n */\nexport function readAssetCrypt(\n dexBuffers: Uint8Array[],\n packageHint?: string,\n): AssetCryptResult {\n for (const bytes of dexBuffers) {\n let dex: DexFile;\n try {\n dex = new DexFile(bytes);\n } catch {\n continue;\n }\n const classDef = findAssetCryptClassDef(dex, packageHint);\n if (!classDef) continue;\n\n const bytesCode = methodCode(dex, classDef, \"initAssetsBytes\");\n if (!bytesCode) return { kind: \"newscheme\", salt: extractCloakSalt(dex, classDef) };\n\n const assetsCode = methodCode(dex, classDef, \"initAssets\");\n const chunks = extractStringChunks(dex, bytesCode.insns);\n const blob = Buffer.from(chunks.join(\"\"), \"latin1\");\n const ranges = assetsCode ? extractRanges(dex, assetsCode.insns) : [];\n const titaniumVersion = detectVersion(bytesCode.insns);\n\n return { kind: \"classic\", data: { blob, ranges, titaniumVersion } };\n }\n return { kind: \"none\" };\n}\n","/**\n * Recover Titanium's newer \"ti.cloak\" asset encryption in pure JS.\n *\n * Since ~Titanium SDK 8, distribution builds no longer embed the sources as one\n * blob in `AssetCryptImpl`. Instead each asset ships as an encrypted\n * `Resources/<name>.bin` file and is decrypted at runtime with\n * `AES/CBC/PKCS5Padding`, IV = a hardcoded `salt` (in `AssetCryptImpl`), and a\n * key produced by the native `libti.cloak.so` via `ti.cloak.Binding.getKey(salt)`.\n *\n * The native key is not truly dynamic: the build writes a fixed 64-byte block\n * into every `libti.cloak.so`, from which the AES key is `salt XOR xor`, where\n * `xor` is assembled from four 4-byte slices of that block. This mirrors the\n * technique documented by @hacker1024 and @j4k0xb in ti_recover issue #6.\n *\n * Because the exact block offset can vary by arch/SDK, callers should derive a\n * candidate key from each bundled `libti.cloak.so` and confirm it by trial\n * decryption (see {@link pickCloakKey}).\n */\nimport { createDecipheriv } from \"node:crypto\";\nimport { gunzipSync } from \"node:zlib\";\n\n/** IV/salt length and AES-128 key length. */\nconst KEY_LEN = 16;\n\n/** Base offset of the embedded key block inside `libti.cloak.so`. */\nconst BLOCK_BASE = 0x2008;\n\n/**\n * Derives the AES key from a `libti.cloak.so` buffer and the `salt`.\n * Returns `null` if the buffer is too small or the salt is not 16 bytes.\n */\nexport function deriveCloakKey(so: Buffer, salt: Buffer): Buffer | null {\n if (salt.length !== KEY_LEN) return null;\n try {\n if (so.length < BLOCK_BASE + 0x40) return null;\n const randomOffset = so.readUInt8(BLOCK_BASE + 0x3e);\n const xor = Buffer.concat([\n so.subarray(BLOCK_BASE + 1, BLOCK_BASE + 5),\n so.subarray(BLOCK_BASE + randomOffset, BLOCK_BASE + randomOffset + 4),\n so.subarray(BLOCK_BASE + 0xf, BLOCK_BASE + 0xf + 4),\n so.subarray(BLOCK_BASE + 0x1e, BLOCK_BASE + 0x1e + 4),\n ]);\n if (xor.length < KEY_LEN) return null;\n const key = Buffer.alloc(KEY_LEN);\n for (let i = 0; i < KEY_LEN; i++) key[i] = (salt[i] ?? 0) ^ (xor[i] ?? 0);\n return key;\n } catch {\n return null;\n }\n}\n\n/**\n * Decrypts one `.bin` asset with AES-128-CBC (IV = salt), transparently\n * gunzipping the result when it is gzip-compressed. Returns `null` on failure.\n */\nexport function decryptCloakAsset(bin: Buffer, key: Buffer, salt: Buffer): Buffer | null {\n if (key.length !== KEY_LEN || salt.length !== KEY_LEN) return null;\n try {\n const decipher = createDecipheriv(\"aes-128-cbc\", key, salt);\n decipher.setAutoPadding(true);\n let out = Buffer.concat([decipher.update(bin), decipher.final()]);\n if (out.length >= 2 && out[0] === 0x1f && out[1] === 0x8b) {\n try {\n out = gunzipSync(out);\n } catch {\n // not actually gzip; keep the decrypted bytes\n }\n }\n return out;\n } catch {\n return null;\n }\n}\n\n/** Heuristic: does this buffer look like decoded UTF-8 text (e.g. JS source)? */\nexport function isProbablyText(buf: Buffer): boolean {\n if (buf.length === 0) return false;\n const sample = buf.subarray(0, Math.min(buf.length, 512));\n let control = 0;\n for (const b of sample) {\n // Allow tab/newline/carriage-return; count other C0 control bytes.\n if (b === 9 || b === 10 || b === 13) continue;\n if (b < 32 || b === 127) control++;\n }\n return control / sample.length < 0.1;\n}\n\n/**\n * Tries each `libti.cloak.so` buffer, derives a candidate key and confirms it\n * by decrypting `sampleBin` into readable text. Returns the first working key,\n * or `null` if none produce a valid decryption.\n */\nexport function pickCloakKey(\n cloakLibs: Buffer[],\n salt: Buffer,\n sampleBin: Buffer,\n): Buffer | null {\n for (const so of cloakLibs) {\n const key = deriveCloakKey(so, salt);\n if (!key) continue;\n const decrypted = decryptCloakAsset(sampleBin, key, salt);\n if (decrypted && isProbablyText(decrypted)) return key;\n }\n return null;\n}\n","/**\n * Assemble Titanium metadata from a parsed manifest plus decryption results.\n */\nimport { detectAlloy } from \"./decrypt.js\";\nimport type { DecryptMeta, ManifestInfo, MemorySource, TitaniumInfo } from \"./types.js\";\n\nexport interface BuildInfoInput {\n manifest: ManifestInfo | null;\n memorySource: MemorySource;\n meta?: DecryptMeta;\n developmentMode: boolean;\n}\n\n/**\n * Combines manifest fields, decryption metadata and the recovered file list\n * into the public {@link TitaniumInfo} shape returned by `TiRecover.info()`.\n */\nexport function buildInfo(input: BuildInfoInput): TitaniumInfo {\n const { manifest, memorySource, meta, developmentMode } = input;\n\n const files = Object.entries(memorySource).map(([name, file]) => ({\n name,\n bytes: file.bytes,\n }));\n\n const totalBytes = meta\n ? meta.totalBytes\n : files.reduce((sum, f) => sum + (f.bytes || 0), 0);\n\n return {\n package: manifest?.package,\n versionCode: manifest?.versionCode,\n versionName: manifest?.versionName,\n appName: manifest?.appName,\n dir: manifest?.dir,\n developmentMode,\n titaniumVersion: meta?.titaniumVersion ?? \"unknown\",\n alloy: meta?.alloy ?? detectAlloy(memorySource),\n files,\n totalBytes,\n };\n}\n","/**\n * Rebuild recovered sources into a structure that opens as a Titanium project.\n *\n * Distribution-mode recovery yields the *compiled* `Resources/`-relative files\n * (for both classic and Alloy apps). A classic `Resources/` project is runnable\n * and openable, so reconstruct normalises everything under `Resources/` and\n * synthesises a minimal `tiapp.xml` from the manifest info. For Alloy apps the\n * original `app/` sources (notably `.xml` views) cannot be derived from the\n * compiled output, so the compiled `Resources/` project is emitted as-is and\n * flagged via {@link ReconstructResult.alloy}.\n */\nimport { randomUUID } from \"node:crypto\";\nimport type { MemorySource, RecoveredFile, TitaniumInfo } from \"./types.js\";\n\nexport interface ReconstructResult {\n /** Remapped sources, everything nested under `Resources/`, plus `tiapp.xml`. */\n memorySource: MemorySource;\n /** The generated tiapp.xml content. */\n tiappXml: string;\n /** Whether the source appears to be an Alloy project. */\n alloy: boolean;\n /** Always true; signals that assets should be nested under `Resources/`. */\n restructured: true;\n}\n\nconst RESOURCES_PREFIX = \"Resources/\";\n\n/**\n * Produces a reconstructed, Titanium-openable project from recovered sources.\n */\nexport function reconstruct(memorySource: MemorySource, info: TitaniumInfo): ReconstructResult {\n const remapped: MemorySource = {};\n\n for (const [relPath, file] of Object.entries(memorySource)) {\n const normalized = relPath.replace(/^[/\\\\]+/, \"\").split(\"\\\\\").join(\"/\");\n const key = normalized.startsWith(RESOURCES_PREFIX)\n ? normalized\n : RESOURCES_PREFIX + normalized;\n remapped[key] = file;\n }\n\n const tiappXml = buildTiappXml(info);\n const tiappFile: RecoveredFile = {\n offset: 0,\n bytes: Buffer.byteLength(tiappXml, \"utf8\"),\n content: tiappXml,\n };\n remapped[\"tiapp.xml\"] = tiappFile;\n\n return { memorySource: remapped, tiappXml, alloy: info.alloy, restructured: true };\n}\n\n/** Builds a minimal but valid classic `tiapp.xml` from recovered metadata. */\nexport function buildTiappXml(info: TitaniumInfo): string {\n const id = escapeXml(info.package || \"com.recovered.app\");\n const name = escapeXml(info.appName || info.package || \"RecoveredApp\");\n const version = escapeXml(info.versionName || \"1.0.0\");\n const guid = randomUUID();\n const alloyNote = info.alloy\n ? \"\\n <!-- Reconstructed from an Alloy build: files under Resources/ are the\\n compiled output. Original app/ sources (views/styles) are not\\n recoverable from a distribution APK. -->\"\n : \"\";\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Reconstructed by ti_recover. Review before building. -->\n<ti:app xmlns:ti=\"http://ti.appcelerator.org\">${alloyNote}\n <id>${id}</id>\n <name>${name}</name>\n <version>${version}</version>\n <guid>${guid}</guid>\n <deployment-targets>\n <target device=\"android\">true</target>\n </deployment-targets>\n <android xmlns:android=\"http://schemas.android.com/apk/res/android\"/>\n</ti:app>\n`;\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\");\n}\n","/**\n * Write recovered in-memory sources to disk and copy over static assets.\n */\nimport path from \"node:path\";\nimport { mkdir, writeFile, cp, copyFile } from \"node:fs/promises\";\nimport jsBeautify from \"js-beautify\";\nimport type { MemorySource } from \"./types.js\";\nimport { fileExists, dirExists } from \"./fs-utils.js\";\n\n/** Prettifies JavaScript source with tab indentation. */\nexport function prettyCode(code: string): string {\n return jsBeautify.js(code, { indent_with_tabs: true });\n}\n\nexport interface WrittenFile {\n name: string;\n bytes: number;\n}\n\n/**\n * Writes every file in `memorySource` under `outDir`, creating directories as\n * needed. `.js` files are beautified; everything else is written verbatim.\n */\nexport async function writeToDisk(\n memorySource: MemorySource,\n outDir: string,\n): Promise<WrittenFile[]> {\n const written: WrittenFile[] = [];\n await mkdir(outDir, { recursive: true });\n\n for (const [relPath, file] of Object.entries(memorySource)) {\n const target = path.join(outDir, relPath);\n await mkdir(path.dirname(target), { recursive: true });\n\n let data: string | Buffer = file.content;\n if (typeof data === \"string\" && relPath.endsWith(\".js\")) {\n data = prettyCode(data);\n }\n await writeFile(target, data);\n const bytes = typeof data === \"string\" ? Buffer.byteLength(data, \"utf8\") : data.length;\n written.push({ name: relPath, bytes });\n }\n\n return written;\n}\n\n/**\n * Copies the APK's static resources (and its `AndroidManifest.xml`) into the\n * output directory. When `restructured` is set the resources are nested under\n * `Resources/` to match a reconstructed Titanium project layout.\n */\nexport async function copyAssets(\n apkDir: string,\n outDir: string,\n restructured = false,\n): Promise<void> {\n const manifestSrc = path.join(apkDir, \"AndroidManifest.xml\");\n if (await fileExists(manifestSrc)) {\n await mkdir(outDir, { recursive: true });\n await copyFile(manifestSrc, path.join(outDir, \"AndroidManifest.xml\"));\n }\n\n const resourcesSrc = path.join(apkDir, \"assets\", \"Resources\");\n if (await dirExists(resourcesSrc)) {\n const dest = restructured ? path.join(outDir, \"Resources\") : outDir;\n await mkdir(dest, { recursive: true });\n // Skip ti.cloak `.bin` files: those are encrypted sources recovered into\n // memory and written by writeToDisk(), not copied verbatim.\n await cp(resourcesSrc, dest, {\n recursive: true,\n filter: (src) => !src.endsWith(\".bin\"),\n });\n }\n}\n"],"mappings":"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","names":["import_node_path","import_promises","import_node_path","import_promises","import_promises","import_promises","require","path","path","import_node_crypto","KEY_LEN","import_node_crypto","escapeXml","import_node_path","import_promises","jsBeautify","path","path","files","reconstruct","pc"]}
1
+ {"version":3,"sources":["../node_modules/tsup/assets/cjs_shims.js","../src/cli.ts","../src/index.ts","../src/apk.ts","../src/zip.ts","../src/manifest.ts","../src/fs-utils.ts","../src/decrypt.ts","../src/dex.ts","../src/cloak.ts","../src/info.ts","../src/reconstruct.ts","../src/write.ts"],"sourcesContent":["// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","#!/usr/bin/env node\n/**\n * ti_recover command-line interface.\n */\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { TiRecover, recover, type TitaniumInfo } from \"./index.js\";\n\nconst EXIT_ERROR = 1;\nconst EXIT_NOT_TITANIUM = 2;\n\nconst program = new Command();\n\nprogram\n .name(\"titanium-apk-recover\")\n .description(\"Recover the source code from an Appcelerator Titanium APK.\")\n .version(\"2.2.1\");\n\nprogram\n .command(\"recover\", { isDefault: true })\n .description(\"Recover source code and assets from an APK into a directory.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .argument(\"<outdir>\", \"directory to write the recovered project into\")\n .option(\"--no-reconstruct\", \"keep the flat recovered layout instead of a Titanium project\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print recovery info as JSON\")\n .option(\"-q, --quiet\", \"suppress progress output\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, outdir: string, opts) => {\n const quiet = Boolean(opts.quiet);\n if (!quiet) {\n console.log(\n `${pc.yellow(\"Appcelerator Titanium\")} - ${pc.green(pc.underline(\"APK Source Code Recovery Tool\"))}`,\n );\n }\n try {\n const result = await recover({\n apk,\n outDir: outdir,\n reconstruct: opts.reconstruct,\n clean: !opts.keepTmp,\n tmpDir: opts.tmpDir,\n debug: Boolean(opts.debug),\n });\n\n if (!result.recovered) {\n console.error(\n pc.red(\"The given APK was not created using Appcelerator Titanium.\"),\n );\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n\n if (opts.json) {\n console.log(JSON.stringify(result.info, null, 2));\n } else if (!quiet) {\n printInfo(result.info);\n console.log(pc.green(`\\nRecovered ${result.files?.length ?? 0} file(s) to ${outdir}`));\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n }\n });\n\nprogram\n .command(\"info\")\n .description(\"Print Titanium metadata about an APK without writing sources.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print info as JSON (default is a formatted summary)\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, opts) => {\n const ti = new TiRecover({ apk, tmpDir: opts.tmpDir, debug: Boolean(opts.debug) });\n try {\n await ti.init();\n if (!(await ti.test())) {\n console.error(pc.red(\"The given APK was not created using Appcelerator Titanium.\"));\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n await ti.extract();\n const info = await ti.info();\n if (opts.json) {\n console.log(JSON.stringify(info, null, 2));\n } else {\n printInfo(info);\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n } finally {\n if (!opts.keepTmp) await ti.clean();\n }\n });\n\nfunction printInfo(info?: TitaniumInfo): void {\n if (!info) return;\n const row = (label: string, value: unknown) =>\n console.log(` ${pc.dim(label.padEnd(16))} ${value ?? pc.dim(\"(unknown)\")}`);\n console.log(pc.bold(\"\\nTitanium APK info:\"));\n row(\"App name\", info.appName);\n row(\"Package\", info.package);\n row(\"Version\", info.versionName);\n row(\"Version code\", info.versionCode);\n row(\"Mode\", info.developmentMode ? \"development\" : \"distribution\");\n row(\"Titanium\", info.titaniumVersion);\n row(\"Alloy\", info.alloy ? \"yes\" : \"no\");\n row(\"Files\", info.files.length);\n row(\"Total bytes\", info.totalBytes);\n}\n\n// Exit explicitly once the command finishes so any lingering worker handles\n// (e.g. fflate's async unzip pool) don't hold the process open.\nprogram\n .parseAsync()\n .then(() => process.exit(process.exitCode ?? 0))\n .catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(EXIT_ERROR);\n });\n","/**\n * ti_recover - recover the source code from Appcelerator Titanium APKs.\n *\n * Public entry point exposing the {@link TiRecover} class and the one-shot\n * {@link recover} helper.\n */\nimport path from \"node:path\";\nimport { readdir, readFile, rm } from \"node:fs/promises\";\nimport { unpackApk } from \"./apk.js\";\nimport { readManifest } from \"./manifest.js\";\nimport { decryptRanges, detectAlloy } from \"./decrypt.js\";\nimport { readAssetCrypt, type AssetCryptResult } from \"./dex.js\";\nimport { pickCloakKey, decryptCloakAsset } from \"./cloak.js\";\nimport { buildInfo } from \"./info.js\";\nimport { reconstruct as reconstructProject } from \"./reconstruct.js\";\nimport { writeToDisk, copyAssets, type WrittenFile } from \"./write.js\";\nimport { fileExists, dirExists } from \"./fs-utils.js\";\nimport { isDexEntry } from \"./zip.js\";\nimport type {\n DecryptMeta,\n ManifestInfo,\n MemorySource,\n RecoverConfig,\n TitaniumInfo,\n} from \"./types.js\";\n\nexport type {\n RecoverConfig,\n RecoveredFile,\n MemorySource,\n ManifestInfo,\n TitaniumInfo,\n TitaniumVersion,\n DecryptMeta,\n DecryptResult,\n} from \"./types.js\";\nexport { parseManifest, parseBinaryManifest } from \"./manifest.js\";\nexport {\n parseRanges,\n parseAssetBuffer,\n decodeJavaInt,\n detectAlloy,\n decryptRange,\n decryptRanges,\n type Range,\n} from \"./decrypt.js\";\nexport {\n readAssetCrypt,\n extractRanges,\n extractStringChunks,\n extractByteArrayFields,\n extractCloakSalt,\n instructionWidth,\n type DexRange,\n type AssetCrypt,\n type AssetCryptResult,\n} from \"./dex.js\";\nexport { deriveCloakKey, decryptCloakAsset, pickCloakKey, isProbablyText } from \"./cloak.js\";\nexport { buildInfo } from \"./info.js\";\nexport { reconstruct as buildReconstruct, buildTiappXml } from \"./reconstruct.js\";\nexport type { WrittenFile } from \"./write.js\";\n\n/**\n * Thrown when an APK uses Titanium's ti.cloak (.bin) encryption and recovery\n * could not complete — typically because the hardcoded `salt` or the bundled\n * `libti.cloak.so` (needed to derive the AES key) is missing.\n */\nexport class UnsupportedEncryptionError extends Error {\n constructor(reason?: string) {\n super(\n \"This APK uses Titanium's newer ti.cloak asset encryption (.bin assets). \" +\n (reason ??\n \"Its AES key could not be derived from the bundled native library, so \" +\n \"the sources could not be recovered.\"),\n );\n this.name = \"UnsupportedEncryptionError\";\n }\n}\n\n/** Source file extensions recovered into memory in development mode. */\nconst DEV_SOURCE_EXTENSIONS = [\".js\", \".json\", \".xml\", \".tss\", \".rjss\", \".jss\", \".css\"];\n\nconst DEFAULT_CONFIG: Required<Pick<RecoverConfig, \"tmpDir\" | \"debug\">> = {\n tmpDir: \"_tmp\",\n debug: false,\n};\n\n/**\n * Orchestrates recovery of a Titanium APK: unpack, detect, decrypt/extract,\n * optionally reconstruct a project, and write everything to disk.\n */\nexport class TiRecover {\n private readonly cwd = process.cwd();\n private config: RecoverConfig & typeof DEFAULT_CONFIG;\n\n private apkDir = \"\";\n private tmpUsed = false;\n private manifest: ManifestInfo | null = null;\n private dexBuffers: Uint8Array[] = [];\n private cloakLibs: Buffer[] = [];\n private assetCrypt: AssetCryptResult = { kind: \"none\" };\n private developmentMode = false;\n private tested = false;\n private isTitanium = false;\n\n private memorySource: MemorySource = {};\n private meta?: DecryptMeta;\n private restructured = false;\n\n constructor(config: RecoverConfig = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n /** Absolute output directory (resolved against cwd for relative paths). */\n private get outDir(): string {\n const out = this.config.outDir ?? \"\";\n return path.isAbsolute(out) ? out : path.resolve(this.cwd, out);\n }\n\n private log(message: string): void {\n if (this.config.debug) console.log(message);\n }\n\n /**\n * Prepares the working directory. If `apkDir` is supplied it is used directly\n * (reading any `classes*.dex` and manifest found there); otherwise the\n * configured `apk` is unzipped into `tmpDir`.\n */\n async init(): Promise<void> {\n if (this.config.apkDir) {\n this.apkDir = this.config.apkDir.endsWith(path.sep)\n ? this.config.apkDir\n : this.config.apkDir + path.sep;\n this.dexBuffers = await readDexBuffersFromDir(this.apkDir);\n this.cloakLibs = await readCloakLibsFromDir(this.apkDir);\n this.manifest = await readManifest(this.apkDir);\n return;\n }\n if (!this.config.apk) {\n throw new Error(\"init() requires either 'apk' or 'apkDir' in the config.\");\n }\n const apkResolved = path.isAbsolute(this.config.apk)\n ? this.config.apk\n : path.resolve(this.cwd, this.config.apk);\n const result = await unpackApk(apkResolved, this.config.tmpDir, this.config.debug);\n this.apkDir = result.apkDir;\n this.manifest = result.manifest;\n this.dexBuffers = result.dexBuffers;\n this.cloakLibs = result.cloakLibs;\n this.tmpUsed = true;\n }\n\n /**\n * Returns `true` if the prepared APK was built with Titanium (in either\n * distribution or development mode), populating internal detection state.\n */\n async test(): Promise<boolean> {\n if (!this.apkDir) {\n throw new Error(\"Call init() before test().\");\n }\n if (!this.manifest) {\n this.manifest = await readManifest(this.apkDir);\n }\n this.tested = true;\n\n // Distribution mode: look for the generated AssetCryptImpl class in the DEX.\n this.assetCrypt = readAssetCrypt(this.dexBuffers, this.manifest?.package);\n if (this.assetCrypt.kind !== \"none\") {\n // `classic` (recoverable) and `newscheme` (unsupported) are both Titanium.\n this.developmentMode = false;\n this.isTitanium = true;\n return true;\n }\n\n // Development-mode APKs ship plain sources under assets/Resources.\n const appDev = path.join(this.apkDir, \"assets\", \"Resources\", \"app.js\");\n if (await fileExists(appDev)) {\n this.developmentMode = true;\n this.isTitanium = true;\n return true;\n }\n\n this.isTitanium = false;\n return false;\n }\n\n /**\n * Extracts recovered sources into memory. For distribution builds this\n * decrypts the AES asset blob (from data lifted out of the DEX); for\n * development builds it reads the plain `assets/Resources` tree.\n */\n async extract(): Promise<MemorySource> {\n if (!this.tested) await this.test();\n if (!this.isTitanium) {\n throw new Error(\"The given APK was not created using Appcelerator Titanium.\");\n }\n\n if (!this.developmentMode) {\n if (this.assetCrypt.kind === \"newscheme\") {\n const files = await this.recoverCloak(this.assetCrypt.salt);\n if (!files) throw new UnsupportedEncryptionError();\n this.memorySource = files;\n return files;\n }\n if (this.assetCrypt.kind !== \"classic\") {\n throw new Error(\"No recoverable Titanium asset data was found in the APK.\");\n }\n const { blob, ranges, titaniumVersion } = this.assetCrypt.data;\n const { files, totalBytes } = decryptRanges(blob, ranges, this.config.debug);\n this.memorySource = files;\n this.meta = { totalBytes, titaniumVersion, alloy: detectAlloy(files) };\n return files;\n }\n\n // Development mode: read plain source files from assets/Resources.\n const baseAssets = path.join(this.apkDir, \"assets\", \"Resources\");\n const relFiles = await readSourceFiles(baseAssets);\n const source: MemorySource = {};\n for (const rel of relFiles) {\n const content = await readFile(path.join(baseAssets, rel));\n source[rel.split(path.sep).join(\"/\")] = {\n offset: 0,\n bytes: content.length,\n content,\n };\n }\n this.memorySource = source;\n return source;\n }\n\n /**\n * Attempts ti.cloak (.bin) recovery: derive the AES key from a bundled\n * `libti.cloak.so` + the `salt` lifted from `AssetCryptImpl`, then AES-128-CBC\n * decrypt every `Resources/*.bin` asset. Returns the recovered sources, or\n * `null` when the salt/native lib is missing or no key validates.\n */\n private async recoverCloak(salt: Buffer | null): Promise<MemorySource | null> {\n if (!salt) {\n this.log(\"ti.cloak-> no salt found in AssetCryptImpl; cannot derive key\");\n return null;\n }\n if (this.cloakLibs.length === 0) {\n this.log(\"ti.cloak-> no libti.cloak.so bundled in the APK; cannot derive key\");\n return null;\n }\n\n const baseAssets = path.join(this.apkDir, \"assets\", \"Resources\");\n const bins = await readCloakBinFiles(baseAssets);\n if (bins.length === 0) {\n this.log(\"ti.cloak-> no .bin assets found under assets/Resources\");\n return null;\n }\n\n // Confirm a working key by trial-decrypting a sample asset (prefer a .js).\n const sample = bins.find((b) => b.rel.endsWith(\".js\")) ?? bins[0]!;\n const key = pickCloakKey(this.cloakLibs, salt, await readFile(sample.abs));\n if (!key) {\n this.log(\"ti.cloak-> could not derive a valid AES key from libti.cloak.so\");\n return null;\n }\n\n const source: MemorySource = {};\n let totalBytes = 0;\n for (const bin of bins) {\n const decrypted = decryptCloakAsset(await readFile(bin.abs), key, salt);\n if (!decrypted) continue;\n const isText = DEV_SOURCE_EXTENSIONS.includes(path.extname(bin.rel).toLowerCase());\n source[bin.rel] = {\n offset: 0,\n bytes: decrypted.length,\n content: isText ? decrypted.toString(\"utf8\") : decrypted,\n };\n totalBytes += decrypted.length;\n this.log(`ti.cloak-> decrypted ${bin.rel}`);\n }\n if (Object.keys(source).length === 0) return null;\n\n this.meta = { totalBytes, titaniumVersion: \"unknown\", alloy: detectAlloy(source) };\n return source;\n }\n\n /**\n * Returns Titanium metadata for the current APK. Must be called after\n * {@link extract} to include the recovered file list.\n */\n async info(): Promise<TitaniumInfo> {\n if (!this.manifest && this.apkDir) {\n this.manifest = await readManifest(this.apkDir);\n }\n return buildInfo({\n manifest: this.manifest,\n memorySource: this.memorySource,\n meta: this.meta,\n developmentMode: this.developmentMode,\n });\n }\n\n /**\n * Rebuilds the in-memory sources into a Titanium-openable project structure\n * (everything under `Resources/`, plus a synthesised `tiapp.xml`).\n */\n async reconstruct(): Promise<MemorySource> {\n if (Object.keys(this.memorySource).length === 0) {\n throw new Error(\"Call extract() before reconstruct().\");\n }\n const info = await this.info();\n const result = reconstructProject(this.memorySource, info);\n this.memorySource = result.memorySource;\n this.restructured = result.restructured;\n return this.memorySource;\n }\n\n /** Writes the in-memory sources to the configured `outDir`. */\n async writeToDisk(): Promise<WrittenFile[]> {\n if (!this.config.outDir) {\n throw new Error(\"writeToDisk() requires 'outDir' in the config.\");\n }\n if (Object.keys(this.memorySource).length === 0) {\n throw new Error(\"Call extract() before writeToDisk().\");\n }\n const written = await writeToDisk(this.memorySource, this.outDir);\n for (const file of written) {\n const size = file.bytes > 1000 ? `${Math.round(file.bytes / 1024)} KB` : `${file.bytes} Bytes`;\n this.log(`writeToDisk-> file ${file.name} written (${size}).`);\n }\n return written;\n }\n\n /** Copies the APK's static assets and manifest into `outDir`. */\n async copyAssets(): Promise<void> {\n if (!this.config.outDir) {\n throw new Error(\"copyAssets() requires 'outDir' in the config.\");\n }\n await copyAssets(this.apkDir, this.outDir, this.restructured);\n }\n\n /** Removes the temporary directory created during {@link init}. */\n async clean(): Promise<void> {\n if (!this.tmpUsed || !this.apkDir) return;\n try {\n await rm(this.apkDir, { recursive: true, force: true });\n this.log(\"clean->ok\");\n } catch {\n // best-effort cleanup\n }\n }\n}\n\nexport interface RecoverOptions extends RecoverConfig {\n /** Reconstruct an openable Titanium project layout. Defaults to `false`. */\n reconstruct?: boolean;\n /** Remove the temporary working directory when finished. Defaults to `true`. */\n clean?: boolean;\n}\n\nexport interface RecoverResult {\n /** Whether the APK was recognised as a Titanium app and recovered. */\n recovered: boolean;\n /** Titanium metadata, present when `recovered` is true. */\n info?: TitaniumInfo;\n /** Files written to disk, present when `recovered` is true. */\n files?: WrittenFile[];\n}\n\n/**\n * One-shot recovery: unpack, detect, extract, optionally reconstruct, write to\n * disk, copy assets and clean up. Returns whether recovery succeeded plus the\n * gathered info.\n */\nexport async function recover(options: RecoverOptions): Promise<RecoverResult> {\n const { reconstruct = false, clean = true, ...config } = options;\n const ti = new TiRecover(config);\n try {\n await ti.init();\n if (!(await ti.test())) {\n return { recovered: false };\n }\n await ti.extract();\n const info = await ti.info();\n if (reconstruct) {\n await ti.reconstruct();\n }\n const files = await ti.writeToDisk();\n await ti.copyAssets();\n return { recovered: true, info, files };\n } finally {\n if (clean) await ti.clean();\n }\n}\n\n/** Reads `classes*.dex` buffers from a pre-extracted directory (if any). */\nasync function readDexBuffersFromDir(dir: string): Promise<Uint8Array[]> {\n if (!(await dirExists(dir))) return [];\n const names = (await readdir(dir)).filter(isDexEntry).sort();\n const buffers: Uint8Array[] = [];\n for (const name of names) {\n buffers.push(await readFile(path.join(dir, name)));\n }\n return buffers;\n}\n\n/** Reads any `lib/**\\/libti.cloak.so` buffers from a pre-extracted directory. */\nasync function readCloakLibsFromDir(dir: string): Promise<Buffer[]> {\n const libDir = path.join(dir, \"lib\");\n if (!(await dirExists(libDir))) return [];\n const entries = await readdir(libDir, { recursive: true, withFileTypes: true });\n const libs: Buffer[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || entry.name !== \"libti.cloak.so\") continue;\n const parent = (entry as unknown as { parentPath?: string; path?: string }).parentPath ??\n (entry as unknown as { path?: string }).path ??\n libDir;\n libs.push(await readFile(path.join(parent, entry.name)));\n }\n return libs;\n}\n\n/** Lists ti.cloak `.bin` assets under `base`, mapping each to its logical name. */\nasync function readCloakBinFiles(base: string): Promise<{ rel: string; abs: string }[]> {\n if (!(await dirExists(base))) return [];\n const entries = await readdir(base, { recursive: true, withFileTypes: true });\n const bins: { rel: string; abs: string }[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(\".bin\")) continue;\n const parent = (entry as unknown as { parentPath?: string; path?: string }).parentPath ??\n (entry as unknown as { path?: string }).path ??\n base;\n const abs = path.join(parent, entry.name);\n // Logical name = path under Resources with the `.bin` suffix removed.\n const rel = path.relative(base, abs).split(path.sep).join(\"/\").replace(/\\.bin$/, \"\");\n bins.push({ rel, abs });\n }\n return bins;\n}\n\n/** Recursively lists source files (relative paths) under `base`. */\nasync function readSourceFiles(base: string): Promise<string[]> {\n if (!(await dirExists(base))) return [];\n const entries = await readdir(base, { recursive: true, withFileTypes: true });\n const files: string[] = [];\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n const ext = path.extname(entry.name).toLowerCase();\n if (!DEV_SOURCE_EXTENSIONS.includes(ext)) continue;\n const parentPath = (entry as unknown as { parentPath?: string; path?: string }).parentPath ??\n (entry as unknown as { path?: string }).path ??\n base;\n const abs = path.join(parentPath, entry.name);\n files.push(path.relative(base, abs));\n }\n return files;\n}\n","/**\n * Pure-JS APK unpack (no JVM). Reads the APK zip with `fflate`, parses the\n * binary manifest, extracts `assets/Resources/**` and a readable manifest into\n * a working directory, and returns the raw `classes*.dex` buffers for DEX-based\n * asset recovery.\n */\nimport path from \"node:path\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { readApkEntries, isDexEntry } from \"./zip.js\";\nimport { parseBinaryManifest } from \"./manifest.js\";\nimport { fileExists } from \"./fs-utils.js\";\nimport type { ManifestInfo } from \"./types.js\";\n\nexport interface UnpackResult {\n /** Working directory (with trailing separator) holding manifest + assets. */\n apkDir: string;\n /** Parsed manifest info, or null if the APK had no manifest. */\n manifest: ManifestInfo | null;\n /** Raw `classes*.dex` buffers, ordered `classes.dex`, `classes2.dex`, ... */\n dexBuffers: Uint8Array[];\n /** `lib/<abi>/libti.cloak.so` buffers (for ti.cloak key derivation). */\n cloakLibs: Buffer[];\n}\n\nconst ASSETS_PREFIX = \"assets/Resources/\";\nconst CLOAK_LIB_SUFFIX = \"/libti.cloak.so\";\n\nfunction isCloakLib(name: string): boolean {\n return name.startsWith(\"lib/\") && name.endsWith(CLOAK_LIB_SUFFIX);\n}\n\n/**\n * Unpacks an APK into `tmpDir` (relative to `cwd`): writes a readable\n * `AndroidManifest.xml` and `assets/Resources/**`, and returns the manifest and\n * DEX buffers for downstream recovery.\n */\nexport async function unpackApk(\n apkPath: string,\n tmpDir: string,\n debug = false,\n): Promise<UnpackResult> {\n if (!(await fileExists(apkPath))) {\n throw new Error(`The given APK file doesn't exist: ${apkPath}`);\n }\n\n if (debug) console.log(\"preparing -> reading APK entries\");\n const entries = await readApkEntries(\n apkPath,\n (f) =>\n f.name === \"AndroidManifest.xml\" ||\n isDexEntry(f.name) ||\n f.name.startsWith(ASSETS_PREFIX) ||\n isCloakLib(f.name),\n );\n\n const apkDir = path.join(process.cwd(), tmpDir) + path.sep;\n await mkdir(apkDir, { recursive: true });\n\n const dexBuffers = Object.keys(entries)\n .filter(isDexEntry)\n .sort()\n .map((name) => entries[name]!);\n\n const cloakLibs = Object.keys(entries)\n .filter(isCloakLib)\n .sort()\n .map((name) => Buffer.from(entries[name]!));\n\n let manifest: ManifestInfo | null = null;\n const manifestBytes = entries[\"AndroidManifest.xml\"];\n if (manifestBytes) {\n const { info, xml } = parseBinaryManifest(Buffer.from(manifestBytes), apkDir);\n manifest = info;\n await writeFile(path.join(apkDir, \"AndroidManifest.xml\"), xml);\n }\n\n for (const [name, data] of Object.entries(entries)) {\n if (!name.startsWith(ASSETS_PREFIX) || name.endsWith(\"/\")) continue;\n const dest = path.join(apkDir, name);\n await mkdir(path.dirname(dest), { recursive: true });\n await writeFile(dest, Buffer.from(data));\n }\n\n if (debug) console.log(\"preparing -> ready\");\n return { apkDir, manifest, dexBuffers, cloakLibs };\n}\n","/**\n * Minimal pure-JS APK (zip) reader built on `fflate`. Used to pull the entries\n * we care about (`AndroidManifest.xml`, `classes*.dex`, `assets/Resources/**`)\n * without shelling out to apktool or a JVM.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { unzip, type Unzipped, type UnzipFileInfo } from \"fflate\";\n\nexport type ApkEntries = Unzipped;\n\n/**\n * Reads an APK from disk and returns the decompressed entries matching\n * `filter` (or all entries when no filter is given).\n */\nexport async function readApkEntries(\n apkPath: string,\n filter?: (info: UnzipFileInfo) => boolean,\n): Promise<ApkEntries> {\n const buf = await readFile(apkPath);\n const u8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n return await new Promise<ApkEntries>((resolve, reject) => {\n unzip(u8, { filter: filter ?? (() => true) }, (err, data) => {\n if (err) reject(err);\n else resolve(data);\n });\n });\n}\n\n/** True for `classes.dex`, `classes2.dex`, ... entries. */\nexport function isDexEntry(name: string): boolean {\n return /^classes\\d*\\.dex$/.test(name);\n}\n","/**\n * Parse Android manifests.\n *\n * The main recovery path reads the APK's *binary* `AndroidManifest.xml` (AXML)\n * via adbkit-apkreader's binary XML parser and also serialises a readable XML\n * copy for the recovered output. A text parser (`parseManifest`) is kept for\n * the case where a caller points at a pre-decoded, textual manifest.\n */\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport { load } from \"cheerio\";\nimport type { ManifestInfo } from \"./types.js\";\nimport { fileExists } from \"./fs-utils.js\";\n\nconst require = createRequire(import.meta.url);\n// adbkit-apkreader ships no types; load its binary XML parser as a CJS module.\nconst BinaryXmlParser = require(\n \"adbkit-apkreader/lib/apkreader/parser/binaryxml\",\n) as new (buffer: Buffer, options?: { debug?: boolean }) => { parse(): XmlNode };\n\ninterface XmlAttribute {\n namespaceURI: string | null;\n name: string;\n value: string | null;\n typedValue: { value: unknown; type: string } | null;\n}\n\ninterface XmlNode {\n namespaceURI: string | null;\n nodeType: number;\n nodeName: string;\n attributes?: XmlAttribute[];\n childNodes?: XmlNode[];\n}\n\nconst ANDROID_NS = \"http://schemas.android.com/apk/res/android\";\n\n/** Binary AXML magic (`RES_XML_TYPE` = 0x0003 with header size 0x0008). */\nexport function isBinaryManifest(bytes: Buffer): boolean {\n return bytes.length >= 4 && bytes[0] === 0x03 && bytes[1] === 0x00;\n}\n\nfunction attr(node: XmlNode, name: string): XmlAttribute | undefined {\n return node.attributes?.find((a) => a.name === name);\n}\n\nfunction attrString(node: XmlNode, name: string): string | undefined {\n const a = attr(node, name);\n if (!a) return undefined;\n if (a.value != null) return a.value;\n const tv = a.typedValue?.value;\n return tv == null ? undefined : String(tv);\n}\n\n/**\n * Parses a binary `AndroidManifest.xml` buffer, returning both structured info\n * and a readable XML serialisation.\n */\nexport function parseBinaryManifest(bytes: Buffer, dir?: string): { info: ManifestInfo; xml: string } {\n const doc = new BinaryXmlParser(bytes).parse();\n const application = doc.childNodes?.find((n) => n.nodeName === \"application\");\n const info: ManifestInfo = {\n package: attrString(doc, \"package\"),\n versionCode: attrString(doc, \"versionCode\"),\n versionName: attrString(doc, \"versionName\"),\n appName: application ? attrString(application, \"label\") : undefined,\n dir,\n };\n const xml = `<?xml version=\"1.0\" encoding=\"utf-8\"?>\\n${serialize(doc)}\\n`;\n return { info, xml };\n}\n\n/**\n * Text manifest parser (cheerio) for pre-decoded, human-readable manifests.\n */\nexport function parseManifest(xml: string, dir?: string): ManifestInfo {\n const $ = load(xml, { xml: true });\n return {\n package: $(\"manifest[package]\").attr(\"package\"),\n versionCode: $(\"manifest\").attr(\"android:versionCode\"),\n versionName: $(\"manifest\").attr(\"android:versionName\"),\n appName: $(\"manifest application\").attr(\"android:label\"),\n dir,\n };\n}\n\n/**\n * Reads `AndroidManifest.xml` from a directory, handling both binary (AXML) and\n * already-decoded text manifests. Returns `null` if the manifest is missing.\n */\nexport async function readManifest(apkDir: string): Promise<ManifestInfo | null> {\n const manifestPath = path.join(apkDir, \"AndroidManifest.xml\");\n if (!(await fileExists(manifestPath))) return null;\n const bytes = await readFile(manifestPath);\n if (isBinaryManifest(bytes)) {\n return parseBinaryManifest(bytes, apkDir).info;\n }\n return parseManifest(bytes.toString(\"utf8\"), apkDir);\n}\n\n// ---------------------------------------------------------------------------\n// Readable XML serialisation of the parsed manifest DOM.\n// ---------------------------------------------------------------------------\n\nfunction prefixFor(namespaceURI: string | null): string {\n if (!namespaceURI) return \"\";\n return namespaceURI === ANDROID_NS ? \"android:\" : \"\";\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction serialize(node: XmlNode, depth = 0): string {\n const indent = \" \".repeat(depth);\n const attrs: string[] = [];\n if (depth === 0) attrs.push(`xmlns:android=\"${ANDROID_NS}\"`);\n for (const a of node.attributes ?? []) {\n const raw = a.value ?? (a.typedValue?.value == null ? \"\" : String(a.typedValue.value));\n attrs.push(`${prefixFor(a.namespaceURI)}${a.name}=\"${escapeXml(raw)}\"`);\n }\n const attrText = attrs.length ? \" \" + attrs.join(\" \") : \"\";\n const children = (node.childNodes ?? []).filter((c) => c.nodeType === 1);\n if (children.length === 0) {\n return `${indent}<${node.nodeName}${attrText} />`;\n }\n const inner = children.map((c) => serialize(c, depth + 1)).join(\"\\n\");\n return `${indent}<${node.nodeName}${attrText}>\\n${inner}\\n${indent}</${node.nodeName}>`;\n}\n","/**\n * Small async filesystem helpers used across the library.\n */\nimport { stat } from \"node:fs/promises\";\n\n/** Resolves to `true` if `filePath` exists and is a regular file. */\nexport async function fileExists(filePath: string): Promise<boolean> {\n try {\n return (await stat(filePath)).isFile();\n } catch {\n return false;\n }\n}\n\n/** Resolves to `true` if `dirPath` exists and is a directory. */\nexport async function dirExists(dirPath: string): Promise<boolean> {\n try {\n return (await stat(dirPath)).isDirectory();\n } catch {\n return false;\n }\n}\n","/**\n * Decrypt Titanium's asset blob with pure Node crypto (no JVM).\n *\n * Distribution-mode Titanium stores every source file as one AES-encrypted\n * blob; the AES key is the last 16 bytes of that blob and each file occupies an\n * `[offset, length]` range within it. Java's default `Cipher.getInstance(\"AES\")`\n * is `AES/ECB/PKCS5Padding`, which maps directly to Node's `aes-128-ecb` with\n * automatic PKCS padding.\n *\n * The blob and ranges now come from {@link readAssetCrypt} (DEX parsing). The\n * smali/java text parsers below (`parseAssetBuffer`, `parseRanges`,\n * `decodeJavaInt`) are retained as standalone helpers for callers working from\n * decompiled sources, but are no longer on the main recovery path.\n */\nimport { createDecipheriv } from \"node:crypto\";\nimport type { DexRange } from \"./dex.js\";\nimport type { MemorySource, TitaniumVersion } from \"./types.js\";\n\n/** A single file's location inside the decrypted asset blob. */\nexport interface Range {\n offset: number;\n bytes: number;\n}\n\n/** Result of parsing `AssetCryptImpl.smali`'s `initAssetsBytes()`. */\nexport interface AssetBufferParse {\n titaniumVersion: TitaniumVersion;\n bufferLen: number;\n escaped: string;\n}\n\n/**\n * Decodes an integer literal the way `java.lang.Integer.decode` does:\n * supports optional sign, `0x`/`0X`/`#` hex, leading-zero octal, and decimal.\n */\nexport function decodeJavaInt(literal: string): number {\n let s = literal.trim();\n let sign = 1;\n if (s.startsWith(\"+\")) {\n s = s.slice(1);\n } else if (s.startsWith(\"-\")) {\n sign = -1;\n s = s.slice(1);\n }\n let value: number;\n if (s.startsWith(\"0x\") || s.startsWith(\"0X\")) {\n value = parseInt(s.slice(2), 16);\n } else if (s.startsWith(\"#\")) {\n value = parseInt(s.slice(1), 16);\n } else if (s.length > 1 && s.startsWith(\"0\")) {\n value = parseInt(s.slice(1), 8);\n } else {\n value = parseInt(s, 10);\n }\n return sign * value;\n}\n\n/**\n * Parses the `hashMap.put(\"file\", new Range(offset, length))` entries from an\n * `AssetCryptImpl.java` source into an ordered map of file -> range.\n */\nexport function parseRanges(javaContent: string): Record<string, Range> {\n const ranges: Record<string, Range> = {};\n const re =\n /hashMap\\.put\\(\\s*\"((?:[^\"\\\\]|\\\\.)*)\"\\s*,\\s*new Range\\(\\s*([^,]+?)\\s*,\\s*([^)]+?)\\s*\\)\\s*\\)/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(javaContent)) !== null) {\n const file = match[1];\n const offset = match[2];\n const length = match[3];\n if (file === undefined || offset === undefined || length === undefined) continue;\n ranges[file] = {\n offset: decodeJavaInt(offset),\n bytes: decodeJavaInt(length),\n };\n }\n return ranges;\n}\n\n/**\n * Parses `AssetCryptImpl.smali`, extracting the declared buffer length, the\n * concatenated escaped string literal and a coarse Titanium version.\n */\nexport function parseAssetBuffer(smaliContent: string): AssetBufferParse {\n const lines = smaliContent.split(/\\r?\\n/);\n let started = false;\n let titaniumVersion: TitaniumVersion = \"unknown\";\n let bufferLen = -1;\n const chunks: string[] = [];\n\n for (const line of lines) {\n if (line.indexOf(\"private static initAssetsBytes()Ljava/nio/CharBuffer\") !== -1) {\n started = true;\n continue;\n }\n if (!started) continue;\n\n if (line.indexOf(\"const v0, \") !== -1) {\n titaniumVersion = \"<5\";\n bufferLen = decodeJavaInt(line.split(\"const v0, \").join(\"\").trim());\n } else if (line.indexOf(\"const/16 v0, \") !== -1) {\n titaniumVersion = \"5.x\";\n bufferLen = decodeJavaInt(line.split(\"const/16 v0, \").join(\"\").trim());\n } else if (line.indexOf(\"const-string v1\") !== -1) {\n let content = line.split('const-string v1, \"').join(\"\").trim();\n content = content.slice(0, -1);\n chunks.push(content);\n } else if (line.indexOf(\"rewind()Ljava/nio/Buffer;\") !== -1) {\n break;\n }\n }\n\n return { titaniumVersion, bufferLen, escaped: chunks.join(\"\") };\n}\n\n/** Heuristic: does the recovered file set look like an Alloy project? */\nexport function detectAlloy(files: MemorySource): boolean {\n return Object.keys(files).some(\n (name) =>\n name === \"alloy.js\" ||\n name.startsWith(\"alloy/\") ||\n name.includes(\"/alloy/\") ||\n name === \"_app_props_.json\",\n );\n}\n\nconst KEY_LEN = 16;\n\n/**\n * Decrypts one `[offset, length]` slice of the blob. The key is the last 16\n * bytes of the blob. Titanium varied two conventions across versions, so we try\n * the key derived from both `length` and `length - 1`, and the slice from both\n * `offset` and `offset - 1` (some files have a padded offset).\n */\nexport function decryptRange(blob: Buffer, offset: number, length: number): string | null {\n for (const total of [blob.length - 1, blob.length]) {\n if (total - KEY_LEN < 0) continue;\n const key = blob.subarray(total - KEY_LEN, total);\n for (const start of [offset, offset - 1]) {\n if (start < 0 || start + length > blob.length) continue;\n try {\n const decipher = createDecipheriv(\"aes-128-ecb\", key, null);\n decipher.setAutoPadding(true);\n const out = Buffer.concat([\n decipher.update(blob.subarray(start, start + length)),\n decipher.final(),\n ]);\n const text = out.toString(\"utf8\");\n if (text !== \"\") return text;\n } catch {\n // try the next key/offset combination\n }\n }\n }\n return null;\n}\n\nexport interface DecryptRangesResult {\n files: MemorySource;\n totalBytes: number;\n}\n\n/**\n * Decrypts every range out of the blob into an in-memory source map. Files that\n * fail to decrypt are skipped rather than aborting the whole run.\n */\nexport function decryptRanges(\n blob: Buffer,\n ranges: DexRange[],\n debug = false,\n): DecryptRangesResult {\n const files: MemorySource = {};\n let totalBytes = 0;\n for (const range of ranges) {\n const content = decryptRange(blob, range.offset, range.bytes);\n if (content === null) continue;\n files[range.file] = { offset: range.offset, bytes: range.bytes, content };\n totalBytes += range.bytes;\n if (debug) console.log(`file:${range.file}, decrypted !`);\n }\n return { files, totalBytes };\n}\n","/**\n * Extract Titanium's encrypted asset data directly from `classes*.dex`,\n * replacing the apktool (smali) + jadx (java) decompile step.\n *\n * The generated `AssetCryptImpl` class has two private static methods:\n * - `initAssetsBytes()` builds the encrypted blob by appending a sequence of\n * `const-string` literals into a CharBuffer.\n * - `initAssets()` fills a HashMap with `put(\"file\", new Range(offset, length))`\n * entries.\n *\n * Both are ordinary DEX bytecode. We locate the class, then walk each method's\n * instruction stream: for the blob we collect `const-string`/`const-string/jumbo`\n * operands in order; for the ranges we do a tiny register trace to recover the\n * (file, offset, length) triples. A DEX instruction width table lets us step\n * over every opcode correctly. Since DEX stores real (already-unescaped)\n * strings, the old Java string-unescape step is gone entirely.\n */\nimport { DexFile, type DexClassDef, type DexCode } from \"libdex-ts\";\nimport type { TitaniumVersion } from \"./types.js\";\n\n/** A single file's location inside the decrypted blob, from `initAssets`. */\nexport interface DexRange {\n file: string;\n offset: number;\n bytes: number;\n}\n\nexport interface AssetCrypt {\n /** The raw encrypted asset blob (last 16 bytes are the AES key). */\n blob: Buffer;\n ranges: DexRange[];\n titaniumVersion: TitaniumVersion;\n}\n\nexport type AssetCryptResult =\n | { kind: \"classic\"; data: AssetCrypt }\n // AssetCryptImpl present but the ti.cloak/.bin variant; `salt` is the AES-CBC\n // IV lifted from the class's <clinit> (null if it couldn't be located).\n | { kind: \"newscheme\"; salt: Buffer | null }\n | { kind: \"none\" };\n\n// ---------------------------------------------------------------------------\n// DEX instruction widths (in 16-bit code units), indexed by base opcode.\n// Derived from the Dalvik bytecode instruction formats.\n// ---------------------------------------------------------------------------\nconst WIDTHS = buildWidthTable();\n\nfunction buildWidthTable(): Uint8Array {\n const w = new Uint8Array(256).fill(1);\n const set = (from: number, to: number, units: number) => {\n for (let op = from; op <= to; op++) w[op] = units;\n };\n // 2-code-unit instructions\n set(0x02, 0x03, 2); // move/from16, move/16 (0x03 is 3 -> fixed below)\n w[0x03] = 3;\n set(0x05, 0x06, 2);\n w[0x06] = 3;\n set(0x08, 0x09, 2);\n w[0x09] = 3;\n w[0x13] = 2; // const/16\n w[0x14] = 3; // const\n w[0x15] = 2; // const/high16\n w[0x16] = 2; // const-wide/16\n w[0x17] = 3; // const-wide/32\n w[0x18] = 5; // const-wide\n w[0x19] = 2; // const-wide/high16\n w[0x1a] = 2; // const-string\n w[0x1b] = 3; // const-string/jumbo\n w[0x1c] = 2; // const-class\n w[0x1f] = 2; // check-cast\n w[0x20] = 2; // instance-of\n w[0x22] = 2; // new-instance\n w[0x23] = 2; // new-array\n w[0x24] = 3; // filled-new-array (35c)\n w[0x25] = 3; // filled-new-array/range (3rc)\n w[0x26] = 3; // fill-array-data (31t)\n w[0x29] = 2; // goto/16\n w[0x2a] = 3; // goto/32\n w[0x2b] = 3; // packed-switch\n w[0x2c] = 3; // sparse-switch\n set(0x2d, 0x31, 2); // cmpkind (23x)\n set(0x32, 0x37, 2); // if-test (22t)\n set(0x38, 0x3d, 2); // if-testz (21t)\n set(0x44, 0x51, 2); // aget/aput (23x)\n set(0x52, 0x5f, 2); // iget/iput (22c)\n set(0x60, 0x6d, 2); // sget/sput (21c)\n set(0x6e, 0x72, 3); // invoke-kind (35c)\n set(0x74, 0x78, 3); // invoke-kind/range (3rc)\n set(0x90, 0xaf, 2); // binop (23x)\n set(0xd0, 0xd7, 2); // binop/lit16 (22s)\n set(0xd8, 0xe2, 2); // binop/lit8 (22b)\n w[0xfa] = 4; // invoke-polymorphic (45cc)\n w[0xfb] = 4; // invoke-polymorphic/range (4rcc)\n w[0xfc] = 3; // invoke-custom (35c)\n w[0xfd] = 3; // invoke-custom/range (3rc)\n w[0xfe] = 2; // const-method-handle\n w[0xff] = 2; // const-method-type\n return w;\n}\n\n/** Width, in code units, of the instruction at `idx` (handles payloads). */\nexport function instructionWidth(insns: Uint16Array, idx: number): number {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n if (op === 0x00 && unit !== 0x0000) {\n // Pseudo-op payload tables.\n switch (unit) {\n case 0x0100: {\n // packed-switch-payload: ident + size + first_key(2) + targets(size*2)\n const size = insns[idx + 1] ?? 0;\n return size * 2 + 4;\n }\n case 0x0200: {\n // sparse-switch-payload: ident + size + keys(size*2) + targets(size*2)\n const size = insns[idx + 1] ?? 0;\n return size * 4 + 2;\n }\n case 0x0300: {\n // fill-array-data-payload\n const elementWidth = insns[idx + 1] ?? 0;\n const size = (insns[idx + 2] ?? 0) + (insns[idx + 3] ?? 0) * 0x10000;\n return 4 + Math.ceil((size * elementWidth) / 2);\n }\n default:\n return 1;\n }\n }\n return WIDTHS[op] ?? 1;\n}\n\nconst s16 = (v: number): number => (v > 0x7fff ? v - 0x10000 : v);\n\n/** Collects `const-string` operands from a method in execution order. */\nexport function extractStringChunks(dex: DexFile, insns: Uint16Array): string[] {\n const chunks: string[] = [];\n let idx = 0;\n while (idx < insns.length) {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n if (op === 0x1a) {\n chunks.push(dex.getStringById(insns[idx + 1] ?? 0));\n } else if (op === 0x1b) {\n chunks.push(dex.getStringById((insns[idx + 1] ?? 0) + (insns[idx + 2] ?? 0) * 0x10000));\n }\n idx += instructionWidth(insns, idx);\n }\n return chunks;\n}\n\n/**\n * Walks `initAssets` with a small register trace to recover the file ranges.\n * Tracks integer/string constants per register, associates `new Range(off,len)`\n * with the object register at its `<init>`, and emits an entry when that object\n * is stored into the map via `put(key, rangeObj)`.\n */\nexport function extractRanges(dex: DexFile, insns: Uint16Array): DexRange[] {\n const regInt = new Map<number, number>();\n const regStr = new Map<number, string>();\n const pending = new Map<number, { offset: number; length: number }>();\n const ranges: DexRange[] = [];\n\n const resolveInvoke = (methodIdx: number, args: number[]): void => {\n const mid = dex.getMethodId(methodIdx);\n const cls = dex.getTypeDescriptorByIdx(mid.classIdx);\n const name = dex.getStringById(mid.nameIdx);\n if (name === \"<init>\" && /[/$]Range;$/.test(cls) && args.length >= 3) {\n const offset = regInt.get(args[1]!);\n const length = regInt.get(args[2]!);\n if (offset !== undefined && length !== undefined) {\n pending.set(args[0]!, { offset, length });\n }\n } else if (name === \"put\" && args.length >= 3) {\n const key = regStr.get(args[1]!);\n const range = pending.get(args[2]!);\n if (key !== undefined && range) {\n ranges.push({ file: key, offset: range.offset, bytes: range.length });\n }\n }\n };\n\n let idx = 0;\n while (idx < insns.length) {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n switch (op) {\n case 0x12: {\n // const/4 (11n)\n const a = (unit >> 8) & 0xf;\n let v = (unit >> 12) & 0xf;\n if (v > 7) v -= 16;\n regInt.set(a, v);\n break;\n }\n case 0x13: {\n // const/16 (21s)\n regInt.set((unit >> 8) & 0xff, s16(insns[idx + 1] ?? 0));\n break;\n }\n case 0x14: {\n // const (31i)\n const v = ((insns[idx + 1] ?? 0) | ((insns[idx + 2] ?? 0) << 16)) | 0;\n regInt.set((unit >> 8) & 0xff, v);\n break;\n }\n case 0x15: {\n // const/high16 (21h)\n regInt.set((unit >> 8) & 0xff, ((insns[idx + 1] ?? 0) << 16) | 0);\n break;\n }\n case 0x1a: {\n // const-string (21c)\n regStr.set((unit >> 8) & 0xff, dex.getStringById(insns[idx + 1] ?? 0));\n break;\n }\n case 0x1b: {\n // const-string/jumbo (31c)\n regStr.set(\n (unit >> 8) & 0xff,\n dex.getStringById((insns[idx + 1] ?? 0) + (insns[idx + 2] ?? 0) * 0x10000),\n );\n break;\n }\n case 0x6e:\n case 0x6f:\n case 0x70:\n case 0x71:\n case 0x72: {\n // invoke-kind (35c)\n const a = (unit >> 12) & 0xf;\n const g = (unit >> 8) & 0xf;\n const methodIdx = insns[idx + 1] ?? 0;\n const regsWord = insns[idx + 2] ?? 0;\n const args = [\n regsWord & 0xf,\n (regsWord >> 4) & 0xf,\n (regsWord >> 8) & 0xf,\n (regsWord >> 12) & 0xf,\n g,\n ].slice(0, a);\n resolveInvoke(methodIdx, args);\n break;\n }\n case 0x74:\n case 0x75:\n case 0x76:\n case 0x77:\n case 0x78: {\n // invoke-kind/range (3rc)\n const count = (unit >> 8) & 0xff;\n const methodIdx = insns[idx + 1] ?? 0;\n const first = insns[idx + 2] ?? 0;\n const args: number[] = [];\n for (let i = 0; i < count; i++) args.push(first + i);\n resolveInvoke(methodIdx, args);\n break;\n }\n }\n idx += instructionWidth(insns, idx);\n }\n\n return ranges;\n}\n\n/** Heuristic Titanium version from the CharBuffer.allocate size opcode. */\nfunction detectVersion(insns: Uint16Array): TitaniumVersion {\n let idx = 0;\n while (idx < insns.length) {\n const op = (insns[idx] ?? 0) & 0xff;\n if (op === 0x14) return \"<5\"; // const (large size)\n if (op === 0x13) return \"5.x\"; // const/16 (fits 16 bits)\n if (op === 0x1a || op === 0x1b) break; // reached the string appends\n idx += instructionWidth(insns, idx);\n }\n return \"unknown\";\n}\n\nconst s32 = (lo: number, hi: number): number => (lo | (hi << 16)) | 0;\n\n/** Reads a `fill-array-data-payload` of byte elements at `payloadIdx`. */\nfunction readFillArrayBytes(insns: Uint16Array, payloadIdx: number): Buffer | null {\n if (payloadIdx < 0 || (insns[payloadIdx] ?? 0) !== 0x0300) return null;\n const elementWidth = insns[payloadIdx + 1] ?? 0;\n if (elementWidth !== 1) return null; // only byte[] arrays (the salt)\n const size = (insns[payloadIdx + 2] ?? 0) + (insns[payloadIdx + 3] ?? 0) * 0x10000;\n const out = Buffer.alloc(size);\n const dataStart = payloadIdx + 4;\n for (let j = 0; j < size; j++) {\n const unit = insns[dataStart + (j >> 1)] ?? 0;\n out[j] = (j & 1) === 1 ? (unit >> 8) & 0xff : unit & 0xff;\n }\n return out;\n}\n\n/**\n * Extracts static `byte[]` fields (assigned via `fill-array-data` +\n * `sput-object`) from a method's instruction stream, keyed by field name.\n */\nexport function extractByteArrayFields(dex: DexFile, insns: Uint16Array): Record<string, Buffer> {\n const pending = new Map<number, Buffer>();\n const fields: Record<string, Buffer> = {};\n let idx = 0;\n while (idx < insns.length) {\n const unit = insns[idx] ?? 0;\n const op = unit & 0xff;\n if (op === 0x26) {\n // fill-array-data (31t): vAA, +BBBBBBBB (signed, code units, relative)\n const reg = (unit >> 8) & 0xff;\n const off = s32(insns[idx + 1] ?? 0, insns[idx + 2] ?? 0);\n const bytes = readFillArrayBytes(insns, idx + off);\n if (bytes) pending.set(reg, bytes);\n } else if (op === 0x69) {\n // sput-object (21c): vAA, field@BBBB\n const reg = (unit >> 8) & 0xff;\n const bytes = pending.get(reg);\n if (bytes) {\n const fieldId = dex.getFieldId(insns[idx + 1] ?? 0);\n fields[dex.getStringById(fieldId.nameIdx)] = bytes;\n }\n }\n idx += instructionWidth(insns, idx);\n }\n return fields;\n}\n\n/** Lifts the ti.cloak `salt` (AES-CBC IV) out of `AssetCryptImpl.<clinit>`. */\nexport function extractCloakSalt(dex: DexFile, classDef: DexClassDef): Buffer | null {\n const clinit = methodCode(dex, classDef, \"<clinit>\");\n if (!clinit) return null;\n const fields = extractByteArrayFields(dex, clinit.insns);\n if (fields.salt) return fields.salt;\n // Fallback: a lone 16-byte byte[] literal is almost certainly the IV/salt.\n const sixteen = Object.values(fields).filter((b) => b.length === 16);\n return sixteen.length === 1 ? sixteen[0]! : null;\n}\n\nfunction findAssetCryptClassDef(dex: DexFile, packageHint?: string): DexClassDef | null {\n if (packageHint) {\n const descriptor = `L${packageHint.split(\".\").join(\"/\")}/AssetCryptImpl;`;\n const byHint = dex.getClassDefByDescriptor(descriptor);\n if (byHint) return byHint;\n }\n for (let i = 0; i < dex.header.classDefsSize; i++) {\n const classDef = dex.getClassDef(i);\n const descriptor = dex.getTypeDescriptorByIdx(classDef.classIdx);\n if (descriptor.endsWith(\"/AssetCryptImpl;\")) return classDef;\n }\n return null;\n}\n\nfunction methodCode(dex: DexFile, classDef: DexClassDef, methodName: string): DexCode | null {\n const classData = dex.getClassData(classDef);\n // In a class_data_item, method indices are ULEB128 deltas that must be\n // accumulated (separately for direct and virtual methods). libdex-ts returns\n // the raw delta in `methodIdx`, so we do the running sum here.\n for (const list of [classData.directMethods, classData.virtualMethods]) {\n let methodIdx = 0;\n for (const method of list) {\n methodIdx += method.methodIdx;\n const name = dex.getStringById(dex.getMethodId(methodIdx).nameIdx);\n if (name === methodName) return dex.getDexCode(method);\n }\n }\n return null;\n}\n\n/**\n * Locates `AssetCryptImpl` across the given DEX buffers and extracts the\n * encrypted blob plus file ranges. Returns `newscheme` when the class exists\n * but lacks `initAssetsBytes` (the ti.cloak/.bin variant), or `none` when the\n * class is absent entirely.\n */\nexport function readAssetCrypt(\n dexBuffers: Uint8Array[],\n packageHint?: string,\n): AssetCryptResult {\n for (const bytes of dexBuffers) {\n let dex: DexFile;\n try {\n dex = new DexFile(bytes);\n } catch {\n continue;\n }\n const classDef = findAssetCryptClassDef(dex, packageHint);\n if (!classDef) continue;\n\n const bytesCode = methodCode(dex, classDef, \"initAssetsBytes\");\n if (!bytesCode) return { kind: \"newscheme\", salt: extractCloakSalt(dex, classDef) };\n\n const assetsCode = methodCode(dex, classDef, \"initAssets\");\n const chunks = extractStringChunks(dex, bytesCode.insns);\n const blob = Buffer.from(chunks.join(\"\"), \"latin1\");\n const ranges = assetsCode ? extractRanges(dex, assetsCode.insns) : [];\n const titaniumVersion = detectVersion(bytesCode.insns);\n\n return { kind: \"classic\", data: { blob, ranges, titaniumVersion } };\n }\n return { kind: \"none\" };\n}\n","/**\n * Recover Titanium's newer \"ti.cloak\" asset encryption in pure JS.\n *\n * Since ~Titanium SDK 8, distribution builds no longer embed the sources as one\n * blob in `AssetCryptImpl`. Instead each asset ships as an encrypted\n * `Resources/<name>.bin` file and is decrypted at runtime with\n * `AES/CBC/PKCS5Padding`, IV = a hardcoded `salt` (in `AssetCryptImpl`), and a\n * key produced by the native `libti.cloak.so` via `ti.cloak.Binding.getKey(salt)`.\n *\n * The native key is not truly dynamic: the build writes a fixed 64-byte block\n * into every `libti.cloak.so`, from which the AES key is `salt XOR xor`, where\n * `xor` is assembled from four 4-byte slices of that block. This mirrors the\n * technique documented by @hacker1024 and @j4k0xb in ti_recover issue #6.\n *\n * Because the exact block offset can vary by arch/SDK, callers should derive a\n * candidate key from each bundled `libti.cloak.so` and confirm it by trial\n * decryption (see {@link pickCloakKey}).\n */\nimport { createDecipheriv } from \"node:crypto\";\nimport { gunzipSync } from \"node:zlib\";\n\n/** IV/salt length and AES-128 key length. */\nconst KEY_LEN = 16;\n\n/** Base offset of the embedded key block inside `libti.cloak.so`. */\nconst BLOCK_BASE = 0x2008;\n\n/**\n * Derives the AES key from a `libti.cloak.so` buffer and the `salt`.\n * Returns `null` if the buffer is too small or the salt is not 16 bytes.\n */\nexport function deriveCloakKey(so: Buffer, salt: Buffer): Buffer | null {\n if (salt.length !== KEY_LEN) return null;\n try {\n if (so.length < BLOCK_BASE + 0x40) return null;\n const randomOffset = so.readUInt8(BLOCK_BASE + 0x3e);\n const xor = Buffer.concat([\n so.subarray(BLOCK_BASE + 1, BLOCK_BASE + 5),\n so.subarray(BLOCK_BASE + randomOffset, BLOCK_BASE + randomOffset + 4),\n so.subarray(BLOCK_BASE + 0xf, BLOCK_BASE + 0xf + 4),\n so.subarray(BLOCK_BASE + 0x1e, BLOCK_BASE + 0x1e + 4),\n ]);\n if (xor.length < KEY_LEN) return null;\n const key = Buffer.alloc(KEY_LEN);\n for (let i = 0; i < KEY_LEN; i++) key[i] = (salt[i] ?? 0) ^ (xor[i] ?? 0);\n return key;\n } catch {\n return null;\n }\n}\n\n/**\n * Decrypts one `.bin` asset with AES-128-CBC (IV = salt), transparently\n * gunzipping the result when it is gzip-compressed. Returns `null` on failure.\n */\nexport function decryptCloakAsset(bin: Buffer, key: Buffer, salt: Buffer): Buffer | null {\n if (key.length !== KEY_LEN || salt.length !== KEY_LEN) return null;\n try {\n const decipher = createDecipheriv(\"aes-128-cbc\", key, salt);\n decipher.setAutoPadding(true);\n let out = Buffer.concat([decipher.update(bin), decipher.final()]);\n if (out.length >= 2 && out[0] === 0x1f && out[1] === 0x8b) {\n try {\n out = gunzipSync(out);\n } catch {\n // not actually gzip; keep the decrypted bytes\n }\n }\n return out;\n } catch {\n return null;\n }\n}\n\n/** Heuristic: does this buffer look like decoded UTF-8 text (e.g. JS source)? */\nexport function isProbablyText(buf: Buffer): boolean {\n if (buf.length === 0) return false;\n const sample = buf.subarray(0, Math.min(buf.length, 512));\n let control = 0;\n for (const b of sample) {\n // Allow tab/newline/carriage-return; count other C0 control bytes.\n if (b === 9 || b === 10 || b === 13) continue;\n if (b < 32 || b === 127) control++;\n }\n return control / sample.length < 0.1;\n}\n\n/**\n * Tries each `libti.cloak.so` buffer, derives a candidate key and confirms it\n * by decrypting `sampleBin` into readable text. Returns the first working key,\n * or `null` if none produce a valid decryption.\n */\nexport function pickCloakKey(\n cloakLibs: Buffer[],\n salt: Buffer,\n sampleBin: Buffer,\n): Buffer | null {\n for (const so of cloakLibs) {\n const key = deriveCloakKey(so, salt);\n if (!key) continue;\n const decrypted = decryptCloakAsset(sampleBin, key, salt);\n if (decrypted && isProbablyText(decrypted)) return key;\n }\n return null;\n}\n","/**\n * Assemble Titanium metadata from a parsed manifest plus decryption results.\n */\nimport { detectAlloy } from \"./decrypt.js\";\nimport type { DecryptMeta, ManifestInfo, MemorySource, TitaniumInfo } from \"./types.js\";\n\nexport interface BuildInfoInput {\n manifest: ManifestInfo | null;\n memorySource: MemorySource;\n meta?: DecryptMeta;\n developmentMode: boolean;\n}\n\n/**\n * Combines manifest fields, decryption metadata and the recovered file list\n * into the public {@link TitaniumInfo} shape returned by `TiRecover.info()`.\n */\nexport function buildInfo(input: BuildInfoInput): TitaniumInfo {\n const { manifest, memorySource, meta, developmentMode } = input;\n\n const files = Object.entries(memorySource).map(([name, file]) => ({\n name,\n bytes: file.bytes,\n }));\n\n const totalBytes = meta\n ? meta.totalBytes\n : files.reduce((sum, f) => sum + (f.bytes || 0), 0);\n\n return {\n package: manifest?.package,\n versionCode: manifest?.versionCode,\n versionName: manifest?.versionName,\n appName: manifest?.appName,\n dir: manifest?.dir,\n developmentMode,\n titaniumVersion: meta?.titaniumVersion ?? \"unknown\",\n alloy: meta?.alloy ?? detectAlloy(memorySource),\n files,\n totalBytes,\n };\n}\n","/**\n * Rebuild recovered sources into a structure that opens as a Titanium project.\n *\n * Distribution-mode recovery yields the *compiled* `Resources/`-relative files\n * (for both classic and Alloy apps). A classic `Resources/` project is runnable\n * and openable, so reconstruct normalises everything under `Resources/` and\n * synthesises a minimal `tiapp.xml` from the manifest info. For Alloy apps the\n * original `app/` sources (notably `.xml` views) cannot be derived from the\n * compiled output, so the compiled `Resources/` project is emitted as-is and\n * flagged via {@link ReconstructResult.alloy}.\n */\nimport { randomUUID } from \"node:crypto\";\nimport type { MemorySource, RecoveredFile, TitaniumInfo } from \"./types.js\";\n\nexport interface ReconstructResult {\n /** Remapped sources, everything nested under `Resources/`, plus `tiapp.xml`. */\n memorySource: MemorySource;\n /** The generated tiapp.xml content. */\n tiappXml: string;\n /** Whether the source appears to be an Alloy project. */\n alloy: boolean;\n /** Always true; signals that assets should be nested under `Resources/`. */\n restructured: true;\n}\n\nconst RESOURCES_PREFIX = \"Resources/\";\n\n/**\n * Produces a reconstructed, Titanium-openable project from recovered sources.\n */\nexport function reconstruct(memorySource: MemorySource, info: TitaniumInfo): ReconstructResult {\n const remapped: MemorySource = {};\n\n for (const [relPath, file] of Object.entries(memorySource)) {\n const normalized = relPath.replace(/^[/\\\\]+/, \"\").split(\"\\\\\").join(\"/\");\n const key = normalized.startsWith(RESOURCES_PREFIX)\n ? normalized\n : RESOURCES_PREFIX + normalized;\n remapped[key] = file;\n }\n\n const tiappXml = buildTiappXml(info);\n const tiappFile: RecoveredFile = {\n offset: 0,\n bytes: Buffer.byteLength(tiappXml, \"utf8\"),\n content: tiappXml,\n };\n remapped[\"tiapp.xml\"] = tiappFile;\n\n return { memorySource: remapped, tiappXml, alloy: info.alloy, restructured: true };\n}\n\n/** Builds a minimal but valid classic `tiapp.xml` from recovered metadata. */\nexport function buildTiappXml(info: TitaniumInfo): string {\n const id = escapeXml(info.package || \"com.recovered.app\");\n const name = escapeXml(info.appName || info.package || \"RecoveredApp\");\n const version = escapeXml(info.versionName || \"1.0.0\");\n const guid = randomUUID();\n const alloyNote = info.alloy\n ? \"\\n <!-- Reconstructed from an Alloy build: files under Resources/ are the\\n compiled output. Original app/ sources (views/styles) are not\\n recoverable from a distribution APK. -->\"\n : \"\";\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Reconstructed by ti_recover. Review before building. -->\n<ti:app xmlns:ti=\"http://ti.appcelerator.org\">${alloyNote}\n <id>${id}</id>\n <name>${name}</name>\n <version>${version}</version>\n <guid>${guid}</guid>\n <deployment-targets>\n <target device=\"android\">true</target>\n </deployment-targets>\n <android xmlns:android=\"http://schemas.android.com/apk/res/android\"/>\n</ti:app>\n`;\n}\n\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\");\n}\n","/**\n * Write recovered in-memory sources to disk and copy over static assets.\n */\nimport path from \"node:path\";\nimport { mkdir, writeFile, cp, copyFile } from \"node:fs/promises\";\nimport jsBeautify from \"js-beautify\";\nimport type { MemorySource } from \"./types.js\";\nimport { fileExists, dirExists } from \"./fs-utils.js\";\n\n/** Prettifies JavaScript source with tab indentation. */\nexport function prettyCode(code: string): string {\n return jsBeautify.js(code, { indent_with_tabs: true });\n}\n\nexport interface WrittenFile {\n name: string;\n bytes: number;\n}\n\n/**\n * Writes every file in `memorySource` under `outDir`, creating directories as\n * needed. `.js` files are beautified; everything else is written verbatim.\n */\nexport async function writeToDisk(\n memorySource: MemorySource,\n outDir: string,\n): Promise<WrittenFile[]> {\n const written: WrittenFile[] = [];\n await mkdir(outDir, { recursive: true });\n\n for (const [relPath, file] of Object.entries(memorySource)) {\n const target = path.join(outDir, relPath);\n await mkdir(path.dirname(target), { recursive: true });\n\n let data: string | Buffer = file.content;\n if (typeof data === \"string\" && relPath.endsWith(\".js\")) {\n data = prettyCode(data);\n }\n await writeFile(target, data);\n const bytes = typeof data === \"string\" ? Buffer.byteLength(data, \"utf8\") : data.length;\n written.push({ name: relPath, bytes });\n }\n\n return written;\n}\n\n/**\n * Copies the APK's static resources (and its `AndroidManifest.xml`) into the\n * output directory. When `restructured` is set the resources are nested under\n * `Resources/` to match a reconstructed Titanium project layout.\n */\nexport async function copyAssets(\n apkDir: string,\n outDir: string,\n restructured = false,\n): Promise<void> {\n const manifestSrc = path.join(apkDir, \"AndroidManifest.xml\");\n if (await fileExists(manifestSrc)) {\n await mkdir(outDir, { recursive: true });\n await copyFile(manifestSrc, path.join(outDir, \"AndroidManifest.xml\"));\n }\n\n const resourcesSrc = path.join(apkDir, \"assets\", \"Resources\");\n if (await dirExists(resourcesSrc)) {\n const dest = restructured ? path.join(outDir, \"Resources\") : outDir;\n await mkdir(dest, { recursive: true });\n // Skip ti.cloak `.bin` files: those are encrypted sources recovered into\n // memory and written by writeToDisk(), not copied verbatim.\n await cp(resourcesSrc, dest, {\n recursive: true,\n filter: (src) => !src.endsWith(\".bin\"),\n });\n }\n}\n"],"mappings":"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","names":["import_node_path","import_promises","import_node_path","import_promises","import_promises","import_promises","require","path","path","import_node_crypto","KEY_LEN","import_node_crypto","escapeXml","import_node_path","import_promises","jsBeautify","path","path","files","reconstruct","pc"]}
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import pc from "picocolors";
10
10
  var EXIT_ERROR = 1;
11
11
  var EXIT_NOT_TITANIUM = 2;
12
12
  var program = new Command();
13
- program.name("titanium-apk-recover").description("Recover the source code from an Appcelerator Titanium APK.").version("2.2.0");
13
+ program.name("titanium-apk-recover").description("Recover the source code from an Appcelerator Titanium APK.").version("2.2.1");
14
14
  program.command("recover", { isDefault: true }).description("Recover source code and assets from an APK into a directory.").argument("<apk>", "path to the .apk file").argument("<outdir>", "directory to write the recovered project into").option("--no-reconstruct", "keep the flat recovered layout instead of a Titanium project").option("--keep-tmp", "keep the temporary working directory").option("--tmp-dir <dir>", "temporary working directory", "_tmp").option("--json", "print recovery info as JSON").option("-q, --quiet", "suppress progress output").option("-d, --debug", "verbose logging").action(async (apk, outdir, opts) => {
15
15
  const quiet = Boolean(opts.quiet);
16
16
  if (!quiet) {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * ti_recover command-line interface.\n */\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { TiRecover, recover, type TitaniumInfo } from \"./index.js\";\n\nconst EXIT_ERROR = 1;\nconst EXIT_NOT_TITANIUM = 2;\n\nconst program = new Command();\n\nprogram\n .name(\"titanium-apk-recover\")\n .description(\"Recover the source code from an Appcelerator Titanium APK.\")\n .version(\"2.2.0\");\n\nprogram\n .command(\"recover\", { isDefault: true })\n .description(\"Recover source code and assets from an APK into a directory.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .argument(\"<outdir>\", \"directory to write the recovered project into\")\n .option(\"--no-reconstruct\", \"keep the flat recovered layout instead of a Titanium project\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print recovery info as JSON\")\n .option(\"-q, --quiet\", \"suppress progress output\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, outdir: string, opts) => {\n const quiet = Boolean(opts.quiet);\n if (!quiet) {\n console.log(\n `${pc.yellow(\"Appcelerator Titanium\")} - ${pc.green(pc.underline(\"APK Source Code Recovery Tool\"))}`,\n );\n }\n try {\n const result = await recover({\n apk,\n outDir: outdir,\n reconstruct: opts.reconstruct,\n clean: !opts.keepTmp,\n tmpDir: opts.tmpDir,\n debug: Boolean(opts.debug),\n });\n\n if (!result.recovered) {\n console.error(\n pc.red(\"The given APK was not created using Appcelerator Titanium.\"),\n );\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n\n if (opts.json) {\n console.log(JSON.stringify(result.info, null, 2));\n } else if (!quiet) {\n printInfo(result.info);\n console.log(pc.green(`\\nRecovered ${result.files?.length ?? 0} file(s) to ${outdir}`));\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n }\n });\n\nprogram\n .command(\"info\")\n .description(\"Print Titanium metadata about an APK without writing sources.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print info as JSON (default is a formatted summary)\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, opts) => {\n const ti = new TiRecover({ apk, tmpDir: opts.tmpDir, debug: Boolean(opts.debug) });\n try {\n await ti.init();\n if (!(await ti.test())) {\n console.error(pc.red(\"The given APK was not created using Appcelerator Titanium.\"));\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n await ti.extract();\n const info = await ti.info();\n if (opts.json) {\n console.log(JSON.stringify(info, null, 2));\n } else {\n printInfo(info);\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n } finally {\n if (!opts.keepTmp) await ti.clean();\n }\n });\n\nfunction printInfo(info?: TitaniumInfo): void {\n if (!info) return;\n const row = (label: string, value: unknown) =>\n console.log(` ${pc.dim(label.padEnd(16))} ${value ?? pc.dim(\"(unknown)\")}`);\n console.log(pc.bold(\"\\nTitanium APK info:\"));\n row(\"App name\", info.appName);\n row(\"Package\", info.package);\n row(\"Version\", info.versionName);\n row(\"Version code\", info.versionCode);\n row(\"Mode\", info.developmentMode ? \"development\" : \"distribution\");\n row(\"Titanium\", info.titaniumVersion);\n row(\"Alloy\", info.alloy ? \"yes\" : \"no\");\n row(\"Files\", info.files.length);\n row(\"Total bytes\", info.totalBytes);\n}\n\n// Exit explicitly once the command finishes so any lingering worker handles\n// (e.g. fflate's async unzip pool) don't hold the process open.\nprogram\n .parseAsync()\n .then(() => process.exit(process.exitCode ?? 0))\n .catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(EXIT_ERROR);\n });\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * ti_recover command-line interface.\n */\nimport { Command } from \"commander\";\nimport pc from \"picocolors\";\nimport { TiRecover, recover, type TitaniumInfo } from \"./index.js\";\n\nconst EXIT_ERROR = 1;\nconst EXIT_NOT_TITANIUM = 2;\n\nconst program = new Command();\n\nprogram\n .name(\"titanium-apk-recover\")\n .description(\"Recover the source code from an Appcelerator Titanium APK.\")\n .version(\"2.2.1\");\n\nprogram\n .command(\"recover\", { isDefault: true })\n .description(\"Recover source code and assets from an APK into a directory.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .argument(\"<outdir>\", \"directory to write the recovered project into\")\n .option(\"--no-reconstruct\", \"keep the flat recovered layout instead of a Titanium project\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print recovery info as JSON\")\n .option(\"-q, --quiet\", \"suppress progress output\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, outdir: string, opts) => {\n const quiet = Boolean(opts.quiet);\n if (!quiet) {\n console.log(\n `${pc.yellow(\"Appcelerator Titanium\")} - ${pc.green(pc.underline(\"APK Source Code Recovery Tool\"))}`,\n );\n }\n try {\n const result = await recover({\n apk,\n outDir: outdir,\n reconstruct: opts.reconstruct,\n clean: !opts.keepTmp,\n tmpDir: opts.tmpDir,\n debug: Boolean(opts.debug),\n });\n\n if (!result.recovered) {\n console.error(\n pc.red(\"The given APK was not created using Appcelerator Titanium.\"),\n );\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n\n if (opts.json) {\n console.log(JSON.stringify(result.info, null, 2));\n } else if (!quiet) {\n printInfo(result.info);\n console.log(pc.green(`\\nRecovered ${result.files?.length ?? 0} file(s) to ${outdir}`));\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n }\n });\n\nprogram\n .command(\"info\")\n .description(\"Print Titanium metadata about an APK without writing sources.\")\n .argument(\"<apk>\", \"path to the .apk file\")\n .option(\"--keep-tmp\", \"keep the temporary working directory\")\n .option(\"--tmp-dir <dir>\", \"temporary working directory\", \"_tmp\")\n .option(\"--json\", \"print info as JSON (default is a formatted summary)\")\n .option(\"-d, --debug\", \"verbose logging\")\n .action(async (apk: string, opts) => {\n const ti = new TiRecover({ apk, tmpDir: opts.tmpDir, debug: Boolean(opts.debug) });\n try {\n await ti.init();\n if (!(await ti.test())) {\n console.error(pc.red(\"The given APK was not created using Appcelerator Titanium.\"));\n process.exitCode = EXIT_NOT_TITANIUM;\n return;\n }\n await ti.extract();\n const info = await ti.info();\n if (opts.json) {\n console.log(JSON.stringify(info, null, 2));\n } else {\n printInfo(info);\n }\n } catch (err) {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exitCode = EXIT_ERROR;\n } finally {\n if (!opts.keepTmp) await ti.clean();\n }\n });\n\nfunction printInfo(info?: TitaniumInfo): void {\n if (!info) return;\n const row = (label: string, value: unknown) =>\n console.log(` ${pc.dim(label.padEnd(16))} ${value ?? pc.dim(\"(unknown)\")}`);\n console.log(pc.bold(\"\\nTitanium APK info:\"));\n row(\"App name\", info.appName);\n row(\"Package\", info.package);\n row(\"Version\", info.versionName);\n row(\"Version code\", info.versionCode);\n row(\"Mode\", info.developmentMode ? \"development\" : \"distribution\");\n row(\"Titanium\", info.titaniumVersion);\n row(\"Alloy\", info.alloy ? \"yes\" : \"no\");\n row(\"Files\", info.files.length);\n row(\"Total bytes\", info.totalBytes);\n}\n\n// Exit explicitly once the command finishes so any lingering worker handles\n// (e.g. fflate's async unzip pool) don't hold the process open.\nprogram\n .parseAsync()\n .then(() => process.exit(process.exitCode ?? 0))\n .catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : String(err)}`));\n process.exit(EXIT_ERROR);\n });\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "titanium-apk-recover",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Recover the source code from almost any APK made with Appcelerator Titanium (development, distribution or ti.cloak mode).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",