ddc
English | 简体中文
ddc decompiles Android DEX bytecode back into readable Java — at
real-world app scale, queryable like a database, and javac-verified:
every one of the 909,689 files it emits across seven real-world APKs
parses cleanly.
Highlights
- Fast — a 226 MB / 20-dex APK (weibo, 98k classes) fully
decompiles in ~21s at high quality (~11s with
--fast), a 62 MB app (Telegram) in ~6.9s (~4.1s with--fast); pathological classes run on deadline-bounded monitored threads instead of hanging the run. - Compiles — the full output of all seven benchmark APKs
(reqable, Telegram, WhatsApp, weibo, weixin, qq, lark — 0.91M
files) passes
javacwith zero syntax errors; every class of a case-variant name pair (X/CuavsX/cua) is preserved as its own file instead of the last one silently overwriting. - Progressive decompilation — 15 query subcommands (strings, cross-references, hierarchies, manifest, resources, per-method decompiles) answer in milliseconds: query metadata first, decompile on demand, skip the full run entirely.
- DEX 035–041, complete — multi-dex APKs, XAPK/APKS/APKM
containers, invoke-custom; lambdas and string concats fold back into
real Java (
(a) -> …,Cls::name,a + b). - Readable output — jadx-style local names (
str,getName() → name, KotlinIntrinsicsparameter strings) instead ofv12; IntDef literals render as their constants out of the box (setVisibility(8)→View.GONE; a built-in android-37 domain table,--symbolsoverrides); Kotlin null-check noise is elided and syntheticaccess$NNNbridges inline at their call sites. - Reproducible output — no timestamps; two runs diff cleanly.
- Human-shaped output, by default (cure):
every file goes through a semantic-preserving source-level pass that
inlines register-copy chains, folds if/else assignments into
ternaries, recovers for-each, drops dead stores and compiler
artifacts — −15% output lines on real corpora with identical
javac-gate results. The pass is exact, so it costs time: weibo
(110k files) takes 21 s optimized vs 10 s with
--fast(skip the simplification, keep formatting). - HTTP REST + MCP, built in (xyz-rust):
ddc serveexposes every query as a REST route (GET with query params or POST with a JSON body) plusGET /openapi.jsonand a streamable MCP endpoint at/mcp— point an MCP client athttp://addr/mcp;ddc httpis REST-only (no/mcp);ddc mcp stdio|httpruns the same commands as MCP tools with input and output schemas. Query commands take--format auto|text|table|markdown|md|json|jsonl(auto: table on a terminal, the stable text form when piped). - Bilingual CLI — messages localize automatically
(
DDC_LANG=zh|enforces; English fallback). - Built on
jdc-core, the machine-neutral core shared with jcdc.
Install
macOS (Homebrew):
brew install ejfkdev/tap/ddc
Windows (Scoop) — add the bucket once, then install by name (scoop
does not resolve a bare user/repo/app form — issue #3):
scoop bucket add ejfkdev https://github.com/ejfkdev/scoop-bucket
scoop install ddc
# or install straight from the manifest URL, no bucket needed:
scoop install https://raw.githubusercontent.com/ejfkdev/scoop-bucket/main/bucket/ddc.json
cargo-binstall (any platform — fetches the prebuilt release binary instead of compiling):
cargo binstall ddc-cli
Binaries: every release
ships raw binaries (no archives) for linux-amd64, linux-arm64,
windows-amd64, windows-arm64, macos-amd64 and macos-arm64 —
non-macOS builds UPX-compressed.
From source (Rust stable):
git clone https://github.com/ejfkdev/ddc && cd ddc
cargo build --release
Usage
ddc app.apk # full decompile → app-out/
ddc app.apk -c com.example.Foo # one class to stdout
ddc app.apk -o - | less # everything to stdout
ddc info app.apk # app context (label via arsc, package,
# version, md5) + per-dex counts
ddc listclasses app.apk # package tree with class counts
# (--full: raw names)
ddc mainactivity app.apk # entry point: package + launcher
ddc findrefs app.apk string token # every const-string "token" site
ddc getmethod app.apk Foo.toString # one method, all overloads
ddc pkg app.apk --app -o own/ # the app's own code only
Full options and examples: ddc --help (subcommand menu grouped by
workflow). The complete reference — every option's semantics, every
subcommand's output format and behavior — lives in
docs/cli.md (中文).
Performance
Seven real-world APKs, release build, measured after a cooldown (Apple Silicon, 6P+12E). The 39-APK validation corpus (4.08M decompiled files, per-APK wall time / peak RSS / javac parse gate) is in docs/validation.md (中文).
Full decompile — 7 real-world APKs
| APK | Size | Full decompile | --fast |
Peak RSS (full) |
|---|---|---|---|---|
| reqable | 34 MB | 0.83s | 0.50s | 280 MB |
| Telegram | 62 MB | 6.91s | 4.08s | 924 MB |
| 139 MB | 24.98s | 11.41s | 1067 MB | |
| 226 MB | 21.03s | 10.87s | 1380 MB | |
| weixin | 268 MB | 40.21s | 25.29s | 1497 MB |
| lark | 398 MB | 26.29s | 14.61s | 2406 MB |
| 374 MB | 57.00s | 31.96s | 2511 MB |
Recorded 2026-10-22 with v0.1.33: one run per cell after a short settle; the previous table was recorded on an earlier (pre-cure) version; full and --fast are now separate columns. Peak RSS is /usr/bin/time -l.
Query subcommands on the same APKs (cells: time / peak RSS;
strings -f <package> --with-locations, findrefs on the package
string and method onCreate, hierarchy/disasm/getclass on each
app's launcher class):
Query subcommands — 13 commands × 7 APKs
| Command | reqable | Telegram | weixin | lark | |||
|---|---|---|---|---|---|---|---|
info |
0.24s / 55MB | 0.41s / 117MB | 0.85s / 316MB | 1.32s / 569MB | 1.61s / 602MB | 2.46s / 969MB | 2.30s / 1145MB |
listclasses |
0.02s / 25MB | 0.03s / 89MB | 0.04s / 96MB | 0.14s / 337MB | 0.16s / 371MB | 0.18s / 251MB | 0.24s / 311MB |
manifest |
0.00s / 12MB | 0.00s / 19MB | 0.00s / 26MB | 0.01s / 48MB | 0.01s / 37MB | 0.01s / 34MB | 0.01s / 54MB |
mainactivity |
0.02s / 20MB | 0.06s / 72MB | 0.08s / 136MB | 0.17s / 199MB | 0.18s / 199MB | 0.22s / 226MB | 0.31s / 300MB |
res |
0.00s / 12MB | 0.00s / 20MB | 0.00s / 27MB | 0.02s / 56MB | 0.01s / 37MB | 0.01s / 36MB | 0.01s / 57MB |
largest |
0.02s / 28MB | 0.05s / 71MB | 0.13s / 195MB | 0.32s / 363MB | 0.30s / 346MB | 0.46s / 519MB | 0.66s / 631MB |
strings |
0.02s / 20MB | 0.04s / 55MB | 0.12s / 117MB | 0.23s / 177MB | 0.28s / 198MB | 0.18s / 192MB | 0.49s / 295MB |
findrefs-string |
0.02s / 31MB | 0.03s / 74MB | 0.04s / 203MB | 0.06s / 397MB | 0.08s / 425MB | 0.04s / 322MB | 0.10s / 849MB |
findrefs-method |
0.02s / 31MB | 0.05s / 81MB | 0.04s / 212MB | 0.06s / 374MB | 0.07s / 448MB | 0.07s / 581MB | 0.11s / 917MB |
members |
0.01s / 19MB | 0.03s / 55MB | 0.05s / 118MB | 0.11s / 174MB | 0.11s / 180MB | 0.14s / 218MB | 0.21s / 285MB |
hierarchy |
0.01s / 20MB | 0.03s / 55MB | 0.05s / 127MB | 0.10s / 174MB | 0.11s / 177MB | 0.15s / 194MB | 0.21s / 294MB |
disasm |
0.01s / 19MB | 0.03s / 58MB | 0.05s / 124MB | 0.10s / 175MB | 0.11s / 182MB | 0.14s / 193MB | 0.20s / 281MB |
getclass |
0.02s / 33MB | 0.21s / 131MB | 0.18s / 256MB | 0.41s / 515MB | 0.50s / 527MB | 0.70s / 830MB | 1.30s / 1200MB |
Documentation
| Architecture | crates, the lift/structure/emit pipeline, DEX versions, invoke-custom (中文) |
| CLI reference | every option and subcommand, in detail (中文) |
| Subcommands | the progressive-analysis reference (中文) |
| Benchmarks | timings, memory, methodology, ASC comparison (中文) |
| Performance engineering | 5min → 6s: six rounds, measured dead ends (中文) |
| Corpus validation | 39 real APKs, 4.08M files, javac parse-clean, bugs found & fixed (中文) |
Known limitations
Erased types (DEX has no Signature); d8-desugared -$$Lambda$ classes
stay separate files; a few R8 monster methods degrade via timeout (and
a handful of deadline-truncated ones can vary between runs);
pattern-switch renders as desugared dispatch chains. The javac gate
above is a SYNTAX gate — semantic diagnostics (missing Android
classpath, and in rare register-heavy monster methods a type-confused
local) remain in the output. For scale: a full-classpath javac battery
over four corpora currently sits at ~7,500 ddc-attributable errors
across 0.91M files (v0.1.19 measured 11,345 — see
benchmarks); jadx on the same battery lands at
roughly half that, the gap concentrated in type recovery. Details:
architecture.
Testing
cargo test # 74 tests
License
MIT © ejfkdev
