This reference describes the CLI at the current commit. flutterdec --help is
the quickest overview, flutterdec <COMMAND> --help covers a single command,
and flutterdec --version reports the build you are running.
Usage:
flutterdec info <INPUT> [--json] [--adapter-backend <BACKEND>] [--adapter-timeout <SECONDS>]Arguments:
<INPUT>: APK orlibapp.so--json: print JSON output--adapter-backend <auto|internal|blutter|r2-flutter>(defaultauto;autotries r2flutter, then blutter, then internal)--adapter-timeout <SECONDS>: wall-clock deadline for one adapter invocation
Exit status is nonzero when an adapter was authorized, ran, and failed. The
report is still printed, and adapter_error_category names what went wrong.
Resolved only after a FullAOT header identity exactly matches a host registry record (hash, target, and canonical layout-feature fingerprint). The runtime profile is loaded and SHA-256 verified from that record:
dart_aliases(SDK labels with provenance; never selectors)dart_tag_style(CID_INT32,CID_SHIFT1, orOBJECT_HEADER)registry_record_present
If adapter metadata is available, JSON output also includes app-package hints:
app_package_count_totalapp_package_counts_toprequested_backend,resolved_backend,backend_fallback_reasonproducer_id,producer_trust,compatibility_record_sha256snapshot_identity_is_exact,identity_rejection,model_capabilitiescompatibility_warningsadapter_containment(per control:appliedwith its bound, orunavailablewith the reason)adapter_error,adapter_error_categorywhen an authorized adapter failedprovider: the block described under Provider reporting
Usage:
flutterdec decompile <INPUT> -o <OUT_DIR> [OPTIONS]Required:
<INPUT>: APK orlibapp.so-o, --out <OUT_DIR>
General options:
-
--emit-asm -
--emit-asm-opcodes(requires--emit-asm; prepends raw 32-bit opcode words inasm/*.s) -
--emit-ghidra-script(writesghidra_apply_symbols.pywith function/label symbol application helpers) -
--emit-ida-script(writesida_apply_symbols.pywith function/label symbol application helpers) -
--emit-ir -
--focus <FOCUS> -
--target <TARGET>(decompile/disassemble a specific function by selector:id:<N>,va:0x<ADDR>,0x<ADDR>, or<N>; ambiguous<N>matches fail and require explicit prefix) -
--max-functions <N> -
--function-scope <app-unknown|app|all>(defaultapp-unknown) -
--app-package <NAME>(repeatable; restricts to selectedpackage:<NAME>/...libraries) -
--split-records(split a function record that spans more than one real function). The adapter sizes a record as the gap to the next start it recovered, so a function it missed is swallowed by its predecessor and never emitted at all. On the two research samples that hides roughly three quarters of the decoded blocks, which is why every figure indocs/research-pseudocode-quality.mdis measured with this on.Off by default, and the reasons matter: it multiplies the emitted function count (5,800 and 8,329 declared records yield 22,102 and 28,753 emitted functions), which moves every absolute quality counter, and it makes
--max-functionsand--function-scopeapply to records rather than to what is emitted.disassembly_ratiodeliberately keeps the model's pre-split function list as its denominator, so the ratio is not inflated by split pieces. Comparing a split run against an unsplit one compares unlike populations. -
--adapter-backend <auto|internal|blutter|r2-flutter>(defaultauto;autotries r2flutter, then blutter, then internal) -
--adapter-timeout <SECONDS>: wall-clock deadline for one adapter invocation -
--require-snapshot-hash-match(fail if adapter-reported snapshot hash differs from loader hash)
Symbol ingestion:
--extra-symbol-map-target <PATH>(repeatable;--extra-symbol-map-targetsremains accepted as an alias)--extra-symbol-elf <PATH>(repeatable)--include-nearest-symbol-map
Quality-gate options:
--max-placeholder-ifs <N>(default0)--max-unresolved-cf <N>(default0)--max-indirect-call-ratio <R>(default0.30)--min-disassembly-ratio <R>(default0.80)
Analysis-engine profile:
--analysis-profile <light|balanced>(defaultbalanced)
Analysis-engine feature toggles:
--with-canonical-model-symbols--no-canonical-model-symbols--with-pool-value-hints--no-pool-value-hints--with-pool-semantic-hints--no-pool-semantic-hints--with-semantic-reporting--no-semantic-reporting--with-bootflow-category-seeds--no-bootflow-category-seeds--with-apk-startup-analysis--no-apk-startup-analysis
Conflict rule:
- each
--with-*conflicts with its matching--no-*
Target selection behavior:
- when
--targetis set, output is narrowed to the matched function - if scope filters exclude that function, target mode may override scope to keep the explicit match
- selection diagnostics are written to
report.json.target_selection
Adapter backend environment:
FLUTTERDEC_R2FLUTTER_BIN: path to ther2flutterbinaryFLUTTERDEC_R2FLUTTER_CMD: full command to execute the r2flutter backendFLUTTERDEC_R2FLUTTER_TIMEOUT: per-invocation timeout in seconds (default 900)FLUTTERDEC_BLUTTER_CMD: full command to execute Blutter bridge backendFLUTTERDEC_BLUTTER_PY: path toblutter.py(uses current Python interpreter)
Usage:
flutterdec diff --old <OLD_INPUT> --new <NEW_INPUT> -o <OUT_DIR> [OPTIONS]Required:
--old <OLD_INPUT>: APK orlibapp.sobaseline--new <NEW_INPUT>: APK orlibapp.socandidate-o, --out <OUT_DIR>
Options:
--function-scope <app-unknown|app|all>(defaultapp-unknown)--app-package <NAME>(repeatable; limit compare set to selected app packages)--adapter-backend <auto|internal|blutter|r2-flutter>(defaultauto)--adapter-timeout <SECONDS>: wall-clock deadline for one adapter invocation, applied to each side--require-snapshot-hash-match(fail if either side has adapter/loader snapshot hash mismatch)--json
Output:
- writes
diff_report.jsonwith function-level deltas and package-level summaries (added_packages_top,removed_packages_top) old_providerandnew_provider, each the block under Provider reporting. The two sides are selected independently, so a run whose sides were not produced the same way setsprovider_mismatch: a name-bearing model and a core-recovered one differ in every descriptor whether or not the code changed.old_uncomparable_function_countandnew_uncomparable_function_count: functions with no name, owner or library. An address alone does not survive a rebuild, so these are counted and excluded from the compared sets rather than collapsed into one descriptor that would read as "unchanged".- a failure on either side names which side it was, and carries that side's error category
Usage:
flutterdec engine-fingerprint <INPUT> [--json] [-o <OUT_DIR>] [--max-markers <N>]Arguments:
<INPUT>: ELF file (usuallylibflutter.so)-o, --out <OUT_DIR>--max-markers <N>(default24)--json
Usage:
flutterdec map-symbols --stripped <PATH> --unstripped <PATH> -o <OUT_DIR> [OPTIONS]Arguments:
--stripped <PATH>--unstripped <PATH>-o, --out <OUT_DIR>--include-branches--nearest-max-distance <N>(default8192)--require-exec-match--register-local-cache(copy the generated target summary into the resolved symbol cache and register it in itsmanifest.jsonfor later auto-ingestion; see Resolved locations)--json
Install:
flutterdec adapter install --dart-hash <HASH> [--target-arch <ARCH>] [--from <PATH>] [--json]--dart-hash <HASH>: 32 lowercase hexadecimal characters, asinforeports it--target-arch <ARCH>: required only when one hash has records for more than one target--from <PATH>: publish this artifact instead of the packaged producer. It must still match the digest and size the compatibility record declares--json: print the installation record as JSON
The compatibility registry is the only install authority: a hash with no record, a record that serves
no artifact variant for this host, a requested target the record does not serve, a profile whose digest
no longer matches, a source that is not a regular file, and any bytes that do not match the record's
declared digest and size are all refused with a nonzero exit and no store write. The published path is
store-relative and contained, so an absolute path, .., or a symlinked directory in the chain is
refused rather than followed.
Output names the store path, the artifact and profile digests, the host variant, the target, the
compatibility record digest, the protocol/model majors, and whether the result was idempotent
(installed or already-installed). Installing the same content twice writes nothing.
List:
flutterdec adapter list [--json]Reports one row per compatibility record, with a state that is verified rather than inferred from file
existence: verified, missing, corrupt, incompatible, or unavailable. Exit status is 2 when any
entry is missing or corrupt, 0 otherwise, and nonzero with a message when the store's own state file
cannot be read.
info, decompile (report.json, under adapter_selection.provider) and each
side of diff (old_provider, new_provider) carry the same block. It is
built once from host facts and the protocol result, so the three surfaces cannot
describe one run differently.
requested_backend,resolved_backend,backend_mismatch,backend_fallback_reasonadapter_executed,adapter_exec_path,containmentcore_fallback_reason,core_fallback_detail,core_fallback_effectproducer_id,producer_version,producer_artifact_sha256,producer_trustregistry_record_present,compatibility_record_sha256,parser_family_id,profile_id,profile_sha256,artifact_id,artifact_sha256host_os,host_arch(the machine that ran) andtarget_arch(the machine the snapshot targets), which are separate factssnapshot_identity_is_exact,identity_rejection,capabilities,warnings
When nothing is authorized to parse a snapshot, no adapter is executed and core
recovers what it can from the instruction bytes. core_fallback_reason says
which of these it was:
| reason | meaning |
|---|---|
internal_requested |
--adapter-backend internal; no registry read, no execution |
identity_rejected |
not a FullAOT snapshot, or the hash did not come from a header |
no_compatibility_record |
the identity is exact and no record covers it |
compatibility_unsupported |
a record exists for the hash but not for this target or feature tuple |
adapter_not_installed |
a record authorizes an artifact and none is installed |
What core recovery produces: ARM64 code candidates from frame prologues and
repeatedly-called targets, every one of them heuristic and unnamed. What it
does not: libraries, classes, class relationships, function names, the original
entry function, and any ObjectPool index space, all of which stay unavailable
with a diagnostic saying why.
A malformed registry, two records claiming one snapshot (registry_ambiguous),
a record that fails its own invariants, a profile that does not verify, and an
artifact whose bytes are not the ones the registry authorized are not fallback
conditions. They are integrity failures of the
installation and they stop the command. So does an adapter that was authorized,
spawned, and then failed.
--adapter-backend blutter and --adapter-backend r2-flutter are refused
rather than answered by core recovery: those flags mean "exact names or
nothing", and substituting prologue scanning is what the protocol already
forbids inside a run.
Every failure prints error category: <token> on stderr alongside its message.
The message is for a human and may be reworded; the token is stable.
- identity and registry:
identity_rejected,registry_no_record,registry_target_mismatch,registry_feature_mismatch,registry_ambiguous,registry_malformed,registry_unsupported_version,registry_invalid_record,registry_profile_rejected,registry_artifact_absent,registry_artifact_rejected - refused before any child existed:
record_invalid,record_digest_mismatch,unsupported_majors,identity_record_mismatch,target_mismatch,feature_mismatch,host_variant_mismatch,variant_not_in_record,artifact_path_rejected,artifact_not_executable,artifact_digest_mismatch,profile_rejected,producer_mismatch,binding_mismatch,input_rejected,request_rejected,output_handle_rejected,image_not_sealed - a child ran:
spawn_failed,workspace_failed,adapter_timeout,adapter_output_limit_exceeded,adapter_crashed,adapter_no_result,adapter_document_too_large,adapter_malformed_document,adapter_result_mismatch,adapter_model_path_mismatch,adapter_reported_failure,adapter_model_rejected,containment_unreported,adapter_io - installing into or reading the adapter store:
store_invalid_input,store_no_record,store_ambiguous,store_incompatible,store_artifact_source_rejected,store_artifact_digest_mismatch,store_path_rejected,store_profile_rejected,store_state_malformed,store_io,store_install_interrupted - resolving where the packaged data and the writable store are, before any
command runs:
layout_executable_unknown,layout_data_dir_override_invalid,layout_no_data_directory,layout_no_data_home unclassifiedfor anything else
The category does not depend on which command hit the condition. A registry
record that fails validation reports registry_invalid_record through both
adapter list and info, because the typed error is carried up rather than
flattened into a message.
Neither directory depends on the current working directory.
- Read-only package data (
adapters/registry.json,data/*.json, the packaged producer):FLUTTERDEC_DATA_DIR, else<binary>/../share/flutterdec, else<binary>, else<binary>/../... The first candidate that actually holdsadapters/registry.jsonwins, and an override that holds none is an error rather than a fallback. - Writable adapter store:
FLUTTERDEC_ADAPTER_STORE, else$XDG_DATA_HOME/flutterdec/adapters, else$HOME/.local/share/flutterdec/adapters. - Local symbol cache:
FLUTTERDEC_SYMBOL_CACHE, else<data home>/flutterdec/symbols.
FLUTTERDEC_INSTALL_FAIL_BEFORE=<lock|stage|publish_artifact|publish_state> fails an install on purpose
before a named publish step. It exists so the "no partial state" guarantee can be tested.