# CLI reference
[English] | [简体中文](zh-CN/cli.md)
Everything `ddc --help` tells you, in more detail: invocation modes,
every option's semantics, every subcommand's output format and behavior.
The binary's help is the quick card; this is the manual.
Four invocation shapes:
```
ddc [OPTIONS] ... [OUTPUT] # full decompile
ddc [ARGS...] # progressive analysis (query)
ddc serve|http [--addr HOST:PORT] # REST + OpenAPI (+ /mcp on serve)
ddc mcp stdio|http # MCP tool server
```
## Language
Messages (help, errors, summaries, table headers) localize automatically.
Resolution: `DDC_LANG` (explicit `zh`/`en`) overrides `LC_ALL` >
`LC_MESSAGES` > `LANG` > `LANGUAGE`; the first variable stating a
language decides — any `zh*` value selects Chinese (`zh`, `zh_CN`,
`zh-Hans`, `zh_TW.UTF-8`, …), any other language English. `LANGUAGE`
is a colon-separated priority list (`zh:en`) — the first entry counts.
`C`, `POSIX` and empty values state no language: the chain keeps
walking. With no locale variable set at all — plain cmd.exe and
PowerShell export none — Windows falls back to the user's UI language
(the one Windows itself displays); set `DDC_LANG` to pin a language.
Git Bash, Cygwin and WSL export `LANG` and are covered by the chain.
```bash
DDC_LANG=zh ddc --help # 中文帮助
LANG=zh_CN.UTF-8 ddc -V # also Chinese
LC_ALL=C LANG=zh_CN ddc -V # C states nothing → LANG decides → Chinese
DDC_LANG=en ddc -V # forced English even under a zh locale
```
## Full decompile
`ddc [OPTIONS] ... [OUTPUT]`
**Inputs** (multiple merge into one class pool; duplicate classes are
deduplicated, first definition wins):
- `.dex` — versions 035–041
- `.apk` / `.jar` / `.zip` — every `classes.dex`, `classes2.dex`, … entry,
merged in numeric order
- `.xapk` / `.apks` / `.apkm` — a zip of APKs: every inner APK's dexes
merge, base first; labels are three-level (`container!base.apk!
classes.dex`)
- a directory — scanned recursively for all of the above
**Output** — the last positional argument or `-o`:
- `` — output root, package structure preserved; default
`-out/` next to the input
- `` — a single class only (single-class input or `-c`)
- `-` — stdout, classes separated by `// ===== class =====` lines, in pool
order (forces one thread)
Positional-argument disambiguation: with no `-o` and two or more
positionals, a **last** argument that isn't input-shaped (no dex-bearing
extension, not an existing file, not a directory containing dex files) is
the output — so `ddc app.apk out/` writes into the pre-created `out/`,
while `ddc a.dex dump/` treats a real dex directory as an input.
| Option | Semantics |
|---|---|
| `-o, --output ` | output location: dir / `file.java` / `-` |
| `-c, --class FQCN` | decompile only this class (dotted or slashed; nested/anonymous/local classes of it come along) |
| `-l, --list` | list class names and exit |
| `-t, --threads ` | worker count (default: CPU count; stdout forces one for pool order) |
| `--no-comments` | omit the provenance header |
| `-v, --verbose` | per-dex stats and slow classes on stderr |
| `-h, --help` / `-V, --version` | help / name+version+homepage |
`--opt=value` and `-o=path` forms both work. Bare `ddc help` / `ddc
version` do the same as `-h` / `-V`; `ddc` with no arguments at all
prints the help (exit 0). `ddc help ` and
`ddc --help` print that command's own arguments and
options instead of the main help; `ddc help serve|http|mcp` (or
`ddc serve -h`, …) document each server mode's flags, endpoints and
worked connection examples.
### Output formats (`--format` on query commands with a typed report; `res` and `largest` print text only)
`auto` (the default) picks by stdout: a terminal gets the human
rendering, a pipe gets the stable text form scripts have always
parsed.
| Format | Renders |
|---|---|
| `auto` | table on a terminal, text when piped |
| `text` | the fixed-width column format (legacy, stable) |
| `table` | aligned tables with header rules |
| `markdown` / `md` | pipe tables; source-shaped reports (`getclass`, `disasm`, `manifest`) wrap in fenced code blocks |
| `json` | the typed reports the HTTP/MCP frontends serve |
| `jsonl` | the same, one compact line per element |
### Server modes (HTTP REST, OpenAPI, MCP)
One process, three fronts — every query command defined once via
[xyz-rust](https://github.com/ejfkdev/xyz-rust):
| Command | Serves | `/mcp` |
|---|---|---|
| `ddc serve [--addr :8080]` | 10 REST routes + `GET /openapi.json` + `GET /healthz` | streamable MCP endpoint |
| `ddc http [--addr :8080]` | REST + OpenAPI only | not mounted (404) |
| `ddc mcp stdio` | MCP over stdin/stdout (agent subprocess) | — |
| `ddc mcp http` | MCP-only streamable server | — |
Shared flags: `--addr`, `--bearer tok1,tok2` (Authorization: Bearer
on REST and `/mcp`), `--timeout`, `--cors`, `--tls-cert` +
`--tls-key` (HTTPS), `--default k=v` (channel defaults). MCP modes
add `--versions` (spec revisions), `--stateless`, `--session-timeout`,
`--name`, `--server-version`.
An MCP client connects to `http:///mcp` when serve is running
(the endpoint answers plain curl GETs with 406 — it speaks the MCP
streamable protocol). Each REST route accepts both GET (query
params) and POST (JSON body); REST examples:
```bash
ddc serve --addr 127.0.0.1:8080
curl '127.0.0.1:8080/class?input=app.apk&class=com.example.Foo'
curl -X POST '127.0.0.1:8080/class' -H 'Content-Type: application/json' \
-d '{"input":"app.apk","class":"com.example.Foo"}'
curl '127.0.0.1:8080/findrefs?input=app.apk&kind=string&query=token'
# Claude Desktop: mcpServers → { ddc: { command: ddc, args: [mcp, stdio] } }
```
**Provenance header** (unless `--no-comments`):
```java
// Decompiled by https://github.com/ejfkdev/ddc 1.2.3
// From: app!classes3.dex (DEX 038)
// Source file: Foo.java
```
`From:` names the input file, the dex image, and the DEX version — the
fastest clue for which image a class lives in (jadx's `loaded from:`
pattern). Headers carry no timestamps, so two runs diff cleanly; a few
variable IDs may still differ between runs (std HashMap seed), with
identical semantics.
**Exit codes**: `0` success; `1` some classes failed (e.g. a
pathological-CFG timeout); `2` usage error (the error message only —
run `ddc --help` for the reference). A one-line summary goes to stderr when a run finishes: `ddc:
wrote 98348 file(s) to out/, 1 failed in 6.13s`.
## Progressive-analysis subcommands
Every dex-reading subcommand accepts `-d/--dex NAME` (repeatable;
entry-name substring — the filter runs before parsing, so
`getclass --dex classes20` parses exactly one image); several accept
`-o FILE` to write the result. `ddc help ` (or
`ddc --help`) documents each command's own signature.
Queries never enter the lift/structure/render pipeline; stdout stays
clean (timing prints only with `-o`).
### Get oriented
- **`ddc info `** — one command, the whole picture. A context
header first (when a manifest exists): app label — an `@0x…` ref is
resolved through a minimal resources.arsc walk, literals print
as-is — package, `versionName (versionCode)`, custom Application
class, launcher activity, `uses-sdk` bounds, file size and MD5 (the
label lookup mirrors the manifest's base-first container rule). Then
the per-dex table: one row per image with version, class, method,
field, string counts, plus a total row. Bare `.dex` inputs skip the
header (no manifest) and print the table only.
- **`ddc listclasses [pattern]`** — class structure as a
compressed prefix frontier (the default): one row per branching package
(`pkg/ (classes)`) plus class leaves, so a 200k-class app fits on one
screen. Pattern is a case-insensitive substring filter applied first.
Drill with `--prefix P` (segment-exact: `a.b` matches `a.b` and
`a.b.*`) and `--depth N` (aggregate exactly N segments below the
scope); `--sort count` puts the biggest groups first. `--limit` /
`--offset` page rows (classes in the flat view): `--format json`
carries `total`/`offset`/`returned`/`next_offset`, and the summary view
is always a JSON object with `rows[].path/kind/classes`. Paging is
deterministic: images are visited in input order and rows within an
image in table order, so `--offset` resumes without skipping or
duplicating rows. `--full` restores the pre-summary behavior: flat,
unwindowed class names.
- **`ddc manifest [--component C] [-o FILE]`** — decodes the
binary AndroidManifest.xml to text XML. `--component` filters to one
element kind: `launcher` (the MAIN/LAUNCHER activity), `activity`,
`service`, `receiver`, `provider`, `permission`, `activity-alias`,
`application`. Raw `.axml` files are accepted directly. In XAPK/APKS
containers the base APK's manifest is used.
- **`ddc mainactivity `** — package, custom Application class (if
any), and the launcher activity, resolved through relative-name rules
(`.MainActivity` → package-prefixed; bare word → package + word) and
activity-alias `targetActivity`; then verified against the dex images
(the defining image is reported).
- **`ddc res [entry] [-o FILE]`** — without an entry: a directory
summary of the archive with per-prefix entry counts (`res/drawable/
(1234)`), `--full` for the flat table (method, compressed size; XAPK
inner APKs flattened into `apk!name` labels), `--prefix` to expand a
directory, plus `--depth/--sort/--limit/--offset`. With an entry: dumps
it — binary XML (first chunk `0x0003`) decodes through the AXML
decoder, text prints as-is, binary content is saved via `-o` (or the
error tells you to). Entry matching: exact name first, then a unique
substring.
### Find things
- **`ddc strings [-f TEXT] [--with-locations]`** — the string
table (one row per string). `-f` filters by substring;
`--with-locations` walks every method's const-string sites and adds a
`used-by` column mapping each hit to its owner methods. `--summary`
groups matching **reference sites** by owning class/package (which
packages use this URL/key?), drilled with `--prefix`/`--depth` and
optionally annotated with `--samples N` example classes; `--full`
prints the raw string table (the default view). `--limit` /
`--offset` page rows (JSON gains `total`/`next_offset`); the MCP
tool caps the default at 500 rows and appends a truncation hint.
- **`ddc findrefs [--class
FQCN] [--fuzzy-class] [-o FILE]`** — every reference to a string
literal / type / method call site / field access (`class` is a plain-
word alias for `type`; definition-level relations such as
extends/implements belong to `ddc hierarchy`). The default view is a
prefix frontier of the **owning classes** with per-package hit counts
(which packages use this string?), drilling with `--prefix`/`--depth`;
`--full` prints every hit row: columnar with a header (`dex kind class
method refs`), **one row per method**, multiple hits aggregate into
`refs` (`; `-separated, deduped). `--sort`, `--limit` and `--offset`
apply to either view. Match semantics: queries are case-insensitive
substrings; `--class` defaults to exact (dots, slashes and `L…;`
descriptor forms all normalize) and `--fuzzy-class` widens it to
substring.
- **`ddc callers NAME [FQCN]`** — who invokes method NAME (the
findrefs method machinery, scoped to one class optionally; accepts the
same `--full/--prefix/--depth/--sort/--limit/--offset` view flags).
- **`ddc members [NAME] [--class FQCN] [--fuzzy-class]
[--method|--field]`** — method/field name search over the method and
field id tables; `--method` / `--field` restrict the kind. The default
view groups matches by owning class/package with counts; `--full`
prints one row per member; `--format json` is available; the summary
accepts `--prefix/--depth/--sort/--limit/--offset/--samples N`.
### Understand structure
- **`ddc hierarchy FQCN`** — the class's lineage: `class` /
`extends` / `implements` forward, `sub` / `impl` for every class that
extends or implements it. Works across images (name-matched when the
parent lives in another dex). The default view is a per-relation
prefix frontier (`sub com.example/ (123)`), so a class with 150k
subtypes fits on one screen; `--full` prints every relation row,
`--prefix/--depth/--sort/--limit/--offset` shape the view.
- **`ddc largest [-n N]`** — top-N methods by instruction count
(find the monsters; default 20).
- **`ddc disasm FQCN[.method]`** — raw bytecode of a class or
one method: one line per instruction (`pc opcode mnemonic`). The
target resolves as a whole-string class first, then splits at the last
dot — so both `org.foo.Cells.t1` (a class) and `Greeter.greet` (a
method) work. A plain method name lists every overload; append the
full descriptor (`'Greeter.greet()Ljava/lang/String;'`) to select one
exactly.
### Decompile surgically
- **`ddc getclass FQCN [-o FILE] [--dex NAME] [--low-memory]`** —
one class with its nested/anonymous/local classes, through the full
pipeline. Ambiguous names (defined in several images) get a warning
listing the images; the defining image is registered first so the pool
resolves from it. `--low-memory` starts from the defining image and
loads further images only when the renderer actually asks for a class
they define (re-rendering until stable). On multi-dex APKs this cuts
peak memory roughly in half for self-contained classes; highly
connected classes can be slower. Text output only; the default path is
unchanged. Archives with four or fewer images use the full path
automatically.
- **`ddc getmethod FQCN[.method] [-o FILE]`** — one method,
sliced out of the decompiled class: provenance header + package line +
every matching overload, dedented. A miss lists the class's method
names. A bare class name falls back to the whole class.
- **`ddc pkg PACKAGE [-o DIR] [-t N] [--app]`** — decompile a
whole package subtree through the full pipeline: the package name is
a segment-boundary prefix, so `com.example.app` matches every class
under it, subpackages like `com.example.app.ui` included (default output:
`-pkg/` next to the input). `""` or `.`
means the root (default package included). `--app` takes the package
from the manifest — and when that package has no classes (Telegram:
manifest says `org.telegram.messenger.web`, code lives in
`org.telegram.messenger`) it retries with the launcher class's
package, which is where an app's own code clusters.
## Output quality passes
Four passes clean the decompiled body, all on by default:
- **jadx-style local names** — a synthetic `v12`/`p3` never survives
when a better name exists: a Kotlin
`Intrinsics.checkNotNullParameter(x, "name")` names `x` from the
message string (the compiler wrote the real parameter name into the
check); the single consistent defining call (`getFoo() → foo`,
`new File(…) → file`); jadx's type-alias table (`str/cls/it/…`)
with the lowercased class simple name as fallback. Collisions take
`2, 3, …`; debug-info names are never touched.
- **Kotlin null-check elision** — statement-position
`Intrinsics.checkNotNull…` calls are runtime assertions; they are
dropped after the naming pass harvested their strings (lark:
59,106 → 9).
- **Synthetic-accessor inlining** — `access$NNN` static bridges inline
at call sites when the body is an identity, a field getter, or a
method forwarder (the d8 APM trace wrappers around the core are
tolerated). Only STATIC+SYNTHETIC callees qualify. lark: 43% of
9,214 call sites.
- **IntDef constant rendering** — see below.
## Platform symbols
ddc renders IntDef/LongDef literal arguments as their constant names
out of the box (`setVisibility(8)` → `android.view.View.GONE`). The
domain table (android-37) lives in the repo as a READABLE,
diffable text file — `crates/ddc-cli/src/platform_symbols.txt`, one
domain per line — which build.rs raw-DEFLATEs into the binary (72KB).
Regenerate with `scripts/gen-platform-symbols.sh [platform-dir]` and
commit the .txt. The table is exact-match — combined flag values stay
numeric — and version-independent in effect: methods missing from the
baked API level simply stay numeric. Startup cost is under 5ms.
`--symbols ` (e.g.
`~/Library/Android/sdk/platforms/android-37.0`, needs `android.jar`
and `data/annotations.zip`) rebuilds the table from that platform for
this invocation, overriding the built-in — full decompile and
subcommands alike.
## Exit codes for subcommands
Usage errors (missing arguments, unknown options, bad `--dex`) print the
error message only and exit `2`. Query misses (class/method/entry
not found) are also `2` but carry the specific miss in the error —
`getmethod` lists the available methods, `--dex` the available images.