A static decompiler for Flutter Android apps. Point it at an APK or a libapp.so and it
recovers readable Dart-like pseudocode, ARM64 disassembly, an intermediate representation,
build-to-build diffs, and structured reports. Everything runs offline on your machine: the target
is never executed.
- Input: a Flutter app for Android built for ARM64, as an APK or a bare
libapp.so - Output:
pseudocode/*.dartpseudoplusreport.jsonandquality.json, with optional asm, IR, and symbol scripts - Use it for: reversing an app, reviewing it for hardcoded secrets or logic flaws, or diffing two releases
- Status: alpha research tool; see Project status
If you remember only three commands:
flutterdec infotells you what the target is.flutterdec adapter installgives the decompiler real Dart names for that target.flutterdec decompileproduces the pseudocode and reports.
- What you get
- What it is not
- Requirements
- Installation
- Quick start: your first decompile
- How it works
- Common tasks
- Command reference
- Output files
- Troubleshooting
- See it in action
- Project status
- Documentation
- Contributing
- Credits and license
| Artifact | What it is |
|---|---|
pseudocode/*.dartpseudo |
The recovered logic: branches, loops, returns, and named callsites. Start here. |
report.json and quality.json |
What was analyzed, what was recovered, and how complete it was. |
asm/*.s and ir/*.json |
Lower-level views to verify the pseudocode (--emit-asm --emit-ir). |
ghidra_apply_symbols.py / ida_apply_symbols.py |
Recovered names for Ghidra or IDA (--emit-ghidra-script, --emit-ida-script). |
diff_report.json |
Function and package churn between two builds (flutterdec diff). |
- Not the original source. It reconstructs readable approximations. Names, types, and comments are best effort, so verify anything important against the asm and IR.
- Not dynamic analysis. The app is never executed. No emulator, device, or instrumentation is involved.
- Not multi-platform. Supported targets are Android Flutter AOT builds for ARM64. iOS, x86/x86_64, and JIT/debug builds are not supported at this maturity.
- Not finished. This is alpha software. Output quality varies by app and Dart/Flutter version, and the command set can change between prereleases.
| Target | A Flutter release build for Android ARM64 (arm64-v8a), as an APK or an extracted libapp.so. Debug/JIT builds cannot be decompiled. |
| Input | Any file on disk. A split APK works as long as it includes the arm64-v8a split, or you can use a universal APK. |
| Host | Linux x86_64 or macOS Apple Silicon for the prebuilt binaries. Other systems can build from source with Nix. |
| Runtime | Nothing beyond the CLI binary. Analysis is fully static and offline. |
| Optional | Nix for the zero-install path; a Rust toolchain (provided by nix develop) to build from source. |
Flutter release apps ship their compiled Dart code as lib/arm64-v8a/libapp.so inside the APK.
flutterdec accepts either the APK or the extracted libapp.so.
If you have Nix installed, one command runs the latest main without touching your system:
nix run github:caverav/flutterdec -- --helpEvery example in this README works through that entry point, for example:
nix run github:caverav/flutterdec -- info ./app.apk --json
nix run github:caverav/flutterdec -- decompile ./app.apk -o ./outTo install it permanently instead:
nix profile install github:caverav/flutterdec
flutterdec --helpUpdate later with nix profile upgrade flutterdec.
Download the archive for your platform from the
Releases page: Linux x86_64 or macOS Apple
Silicon. The current prerelease is
v0.1.0-alpha.4.
Important
The standalone v0.1.0-alpha.4 binary can inspect a target (flutterdec info), but it cannot
install adapters or decompile without the packaged producer and registry from a source checkout.
For decompilation today, use Nix (Option 1) or a source checkout (Option 3).
The archive contains a single flutterdec binary:
# Linux x86_64
curl -fLO https://github.com/caverav/flutterdec/releases/download/v0.1.0-alpha.4/flutterdec-v0.1.0-alpha.4-Linux-X64.tar.gz
tar -xzf flutterdec-v0.1.0-alpha.4-Linux-X64.tar.gz
# macOS Apple Silicon
curl -fLO https://github.com/caverav/flutterdec/releases/download/v0.1.0-alpha.4/flutterdec-v0.1.0-alpha.4-macOS-ARM64.tar.gz
tar -xzf flutterdec-v0.1.0-alpha.4-macOS-ARM64.tar.gz
# install the binary, then verify
sudo mkdir -p /usr/local/bin
sudo install -m 0755 flutterdec /usr/local/bin/flutterdec
flutterdec --versionReleases built from current main will ship a different layout: a prefix with bin/flutterdec plus
the compatibility registry, the runtime profiles, and the packaged producer under
share/flutterdec. On those releases the CLI resolves its data relative to its own executable, so
keep bin and share together and copy both (sudo cp -R bin share /usr/local/).
This is the route for contributors, or for running unreleased code:
git clone https://github.com/caverav/flutterdec.git
cd flutterdec
nix develop -c cargo build -p flutterdec-cli --release
./target/release/flutterdec --helpYou can also run directly from the checkout without building, with nix run . -- --help. The
resulting CLI finds the adapters/ registry and data/ profiles in the repository, so adapters
work out of the box.
This walkthrough takes a few minutes on a normal machine. It uses app.apk and ./out as example
paths; replace them with your own.
flutterdec info ./app.apk --jsoninfo reads the snapshot identity out of the header, with no adapter installed and no
disassembly. It tells you what you need next:
snapshot_hash: the identity of the compiled Dart snapshot, and the valueadapter installtakesarchandsnapshot_features: the target architecture and the engine build characteristicsregistry_record_presentand thecompatibilityblock: whether a known adapter covers this app, and if not, why (identity_rejection)
For APK inputs, info also reports manifest-derived startup signals (android_startup_present,
android_startup_confidence, android_startup_entrypoint_count,
android_startup_flutter_activity_count). When adapter metadata is available it reports app
package counts and compatibility warnings too.
The dart_aliases, dart_version, and dart_tag_style fields describe the matched registry
record. Aliases are provenance labels and never select a parser or profile; dart_version is a
display value (unverified when the record carries aliases, unavailable when it carries none);
all three are null when no record matches or its adapter is not installed.
No adapter is needed for this step. --json prints the full report; omit it for a plain-text
summary.
flutterdec adapter install --dart-hash <snapshot_hash from step 1>
flutterdec adapter listAn adapter is a small, hash-specific parser that knows how one Dart snapshot version lays out libraries, classes, function names, and the object pool. Installing the adapter for your target is what turns anonymous ARM64 into named, readable code.
adapter installrefuses a hash the compatibility registry has no record for, a record that does not serve this host, and any artifact whose bytes do not match the digest and size the record declares. Installs are atomic, safe to run concurrently, and reportalready-installedwhen the store is already correct.adapter listreports a verified state per record:verified,missing,corrupt,incompatible, orunavailable. It exits2when the store holds an install it cannot back.
If no record covers your hash, skip this step. decompile still runs using core recovery and says
so in report.json; see Fallbacks and core recovery.
flutterdec decompile ./app.apk -o ./outBy default this focuses on app-owned code (--function-scope app-unknown) and excludes Flutter and
Dart framework internals.
Start with these three files, in this order:
out/pseudocode/*.dartpseudo- the recovered logicout/report.json- what was analyzed and what was recoveredout/quality.json- how complete the recovery was
Important
A non-zero exit is expected on a real app, and your artifacts are still written. The strict
quality gate is on by default (--max-placeholder-ifs 0), and every real Flutter app contains
placeholder if statements the decompiler could not fully resolve. The command prints
reasons: placeholder if-count exceeded threshold, exits 1, and still writes every artifact
listed above. Nothing is missing; the gate is reporting a quality measurement, not a failure to
decompile.
Measured on LocalSend 1.17 at the default scope: 501 placeholder ifs across 5,800 pseudocode files, exit 1.
Read your own number rather than guessing one. It is placeholder_ifs in out/quality.json, and
it grows with scope. Setting the threshold from that measurement is the point: a huge round number
silences the gate permanently, whereas a real one still fails when the count rises, which is
the only thing the gate is useful for. Keep the strict default in CI.
flutterdec decompile ./app.apk -o ./out # exits 1, writes everything
python3 -c "import json;print(json.load(open('out/quality.json'))['placeholder_ifs'])"
flutterdec decompile ./app.apk -o ./out --max-placeholder-ifs <that number>The other gates are --max-unresolved-cf, --max-indirect-call-ratio, and
--min-disassembly-ratio. See the CLI reference for their defaults and for
--split-records.
A Flutter release compiles Dart ahead-of-time (AOT) to ARM64 machine code and packs it, with a
snapshot header and an object pool, into libapp.so. Symbols and source are gone; names survive
only as metadata. flutterdec walks the pipeline below and records what it recovered at each
stage.
APK / libapp.so
|
v
[1] Loader ------> [2] Adapter ------> [3] ARM64 disassembly
|
v
[4] IR + control-flow graph
|
v
[5] Pseudo-Dart decompiler
|
v
[6] quality.json + report.json
- Loader - opens the APK or ELF and locates the snapshot blob.
- Adapter - parses the version-specific snapshot format to recover libraries, classes,
function names, and object-pool layout. Adapters are hash-verified and run as a bounded,
one-shot job;
report.jsonrecords which execution controls were actually established. - Disassembly - decodes ARM64 and annotates Dart ABI details such as register and pool loads.
- IR / CFG - lifts instructions into an intermediate representation and rebuilds basic blocks.
- Decompiler - emits structured pseudo-Dart: branches, loops, returns, and named callsites.
- Quality and reporting - counts unresolved constructs and writes the reports and artifacts.
Auxiliary commands extend this core: map-symbols derives engine symbol names from a
stripped/unstripped pair, engine-fingerprint identifies an engine build from an ELF, symbol
ingestion feeds those names into decompile, and diff compares two builds at recovered-function
level.
The backend that parses the snapshot decides how much is actually recovered:
| Backend | Function names | Classes | ObjectPool |
|---|---|---|---|
| core recovery | none at all; code ranges are unnamed | none | unavailable |
internal |
none at all; code ranges are unnamed | none | carved strings, ordinal index space |
blutter |
scraped from Blutter's rendered source, heuristic | yes | Blutter pp.txt entries, ordinal index space |
r2flutter |
exact, from the AOT instruction table | yes, library attribution unavailable | real slots, resolvable from x27 displacements |
--adapter-backend auto (the default) tries r2flutter, then Blutter, then the internal path.
--adapter-backend internal, blutter, and r2-flutter pin the choice; a named backend either
runs or fails, and is never silently substituted.
Every recovered domain carries a capability level (complete / partial / unavailable) and
every fact a provenance (exact / derived / heuristic), so report.json distinguishes "not
recovered" from "recovered" rather than inventing names. A function whose name was not recovered
has no name instead of a sub_<addr> placeholder, and is labeled from its entry address at emit
time.
Only a backend that recovers the real ObjectPool layout claims a hardware index space. Without
one, pool references are left unresolved on purpose rather than filled from an unrelated index
space, and report.json.pool_metadata.hints_suppressed_reason says why. If pseudocode has fewer
string literals than you expected, check pool_metadata.index_space_authoritative.
Backend environment variables
r2flutter
FLUTTERDEC_R2FLUTTER_BIN: path to ther2flutterbinaryFLUTTERDEC_R2FLUTTER_CMD: full command to launch it, when a wrapper is neededFLUTTERDEC_R2FLUTTER_TIMEOUT: per-invocation timeout in seconds (default 900)- otherwise
r2flutteris resolved fromPATH
r2flutter is an external MIT tool (radareorg/r2flutter)
that parses Dart AOT snapshots directly. It needs radare2 available at build time.
Blutter
FLUTTERDEC_BLUTTER_CMD: full command used to launch Blutter, for examplepython3 /opt/blutter/blutter.pyFLUTTERDEC_BLUTTER_PY: direct path toblutter.py- in
nix develop,FLUTTERDEC_BLUTTER_CMDis exported automatically to a Nix-managed wrapper; run it directly withnix run .#blutter-bridge -- --help
When nothing is authorized to parse a snapshot, the run does not end. Core recovers ARM64 code
candidates from the instruction bytes, marks every one of them heuristic and unnamed, and leaves
libraries, classes, function names, the original entry function, and the ObjectPool unavailable
with a diagnostic for each. core_fallback_reason says which condition caused it
(internal_requested, identity_rejected, no_compatibility_record,
compatibility_unsupported, or adapter_not_installed). See
Core recovery for the full table.
Two things are not fallback conditions and stop the command instead: integrity failures of the
installation (a malformed registry, an ambiguous record, an artifact that fails its digest or
profile check), and an adapter that was authorized, ran, and then failed. A pinned external
backend is refused by name rather than answered by core recovery, because --adapter-backend blutter and --adapter-backend r2-flutter mean "exact names or nothing".
| Goal | Command |
|---|---|
| Inspect a target | flutterdec info ./app.apk --json |
| Decompile app code (default scope) | flutterdec decompile ./app.apk -o ./out |
| Include framework internals | flutterdec decompile ./app.apk -o ./out --function-scope all |
| Only specific packages | flutterdec decompile ./app.apk -o ./out --function-scope app --app-package my_app |
| One function, plus asm | flutterdec decompile ./app.apk -o ./out --target id:42 --emit-asm |
| Also write asm and IR | flutterdec decompile ./app.apk -o ./out --emit-asm --emit-ir |
| Add raw opcode words | flutterdec decompile ./app.apk -o ./out --emit-asm --emit-asm-opcodes |
| Ghidra import script | flutterdec decompile ./app.apk -o ./out --emit-ghidra-script |
| IDA import script | flutterdec decompile ./app.apk -o ./out --emit-ida-script |
| Compare two builds | flutterdec diff --old ./old.apk --new ./new.apk -o ./out-diff --json |
| Faster large-scale runs | flutterdec decompile ./app.apk -o ./out --analysis-profile light |
| Engine symbol names | see Improve naming with engine symbols |
--function-scope decides which functions reach every emitted artifact:
app-unknown(default): app (package:*) plus functions of unknown ownershipapp: only app (package:*) functionsall: also Flutter, Dart runtime, and framework internals
If package names are unknown, inspect report.json under
function_scope.app_package_counts_top. When --app-package is not provided, capped
prioritization also uses manifest-derived package hints under
function_scope.priority_package_hints to favor app-owned code.
flutterdec decompile ./app.apk -o ./out --target id:42 --emit-asm
flutterdec decompile ./app.apk -o ./out --target va:0x613468 --emit-asm--target accepts id:<N>, va:0x<ADDR>, 0x<ADDR>, or <N>. A bare number fails if it matches
both an id and an address, so use an explicit id: or va: prefix when in doubt. Target mode emits
only the matched function and reports selection details in report.json.target_selection; it can
override the scope filter to keep an explicit match.
If you have a stripped/unstripped libflutter.so pair, map direct-call targets to engine symbol
names and register the result in the local symbol cache:
flutterdec map-symbols \
--stripped ./libflutter.stripped.so \
--unstripped ./libflutter.unstripped.so \
-o ./out/symbol-map \
--register-local-cacheThen use the mapping in later decompile runs:
flutterdec decompile ./app.apk -o ./out \
--extra-symbol-elf ./libflutter.unstripped.soWhen the cached engine build id matches the APK's embedded libflutter.so, decompile auto-loads
the registered target summary and reports the match under report.json.engine_symbol_ingestion.
flutterdec diff --old ./old.apk --new ./new.apk -o ./out-diff --jsondiff_report.json includes added, removed, and common function summaries plus
added_packages_top and removed_packages_top churn summaries. This is useful when you care more
about what changed between two versions than about reconstructing one function in isolation.
Per-feature analysis toggles
Each toggle has a --with-* and a --no-* form, and overrides whichever
--analysis-profile is selected:
--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
| Command | One-liner |
|---|---|
flutterdec info <INPUT> |
Inspect a target: snapshot identity, arch, features, adapter status |
flutterdec decompile <INPUT> -o <DIR> |
Recover pseudocode and reports |
flutterdec diff --old <IN> --new <IN> -o <DIR> |
Compare two builds at recovered-function level |
flutterdec adapter install --dart-hash <HASH> |
Install the registry-verified adapter for a snapshot hash |
flutterdec adapter list |
Show install state per known adapter record |
flutterdec map-symbols --stripped <ELF> --unstripped <ELF> -o <DIR> |
Derive engine symbol names from a strip pair |
flutterdec engine-fingerprint <ELF> |
Identify the Flutter engine build from an libflutter.so |
This table is only an overview. The CLI reference documents every flag,
artifact, error category, and location rule, and flutterdec <command> --help documents each
command.
Everything is written under the -o <DIR> you pass:
| File | Written by | Purpose |
|---|---|---|
pseudocode/*.dartpseudo |
decompile |
Recovered pseudo-Dart, one file per function |
report.json |
decompile |
Analysis report and diagnostics |
quality.json |
decompile |
Quality counters and gate results |
asm/*.s |
--emit-asm |
Per-function ARM64 disassembly |
ir/*.json |
--emit-ir |
Intermediate representation |
ghidra_apply_symbols.py |
--emit-ghidra-script |
Recovered symbols for Ghidra |
ida_apply_symbols.py |
--emit-ida-script |
Recovered symbols for IDA |
diff_report.json |
diff |
Added, removed, and common functions plus package churn |
What report.json contains
compatibility: schema, hash, and manifest alignment diagnosticsadapter_selection/provider: requested and resolved backend, whether an adapter ran at all and why not, host and target architectures, the producer and its artifact digest, the parser family, the profile and artifact the registry named, and the containment the child reportedandroid_manifest: manifest-derived launcher, deeplink, and activity signalsandroid_startup: APK bytecode startup evidence such as embedding calls, JNI bootstrap stages, and recoveredDartEntrypointcallsites when present. Entries can carryfunction_name,library_uri, andapp_bundle_pathwhen directly recoverable.bootstrap_chainsummarizes the observed Android embedder startup stages per source method, including ownership, stage ordering, completeness, and missing stepsfunction_scope: scope selection, package counts, and prioritization hintstarget_selection: how--targetresolvedpool_metadata: index-space authority and why hints were suppressedengine_symbol_ingestion: auto-loaded local engine symbol cache matches keyed bylibflutter.sobuild idbootflow_discovery: startup-flow seeds tagged bysource(android_manifest,apk_startup,model_name_pattern) and byprovenance(derivedorheuristic, never exact)
Not sure where to start? Pick a file by question:
| If you want to... | Start with... |
|---|---|
| Read recovered logic | pseudocode/*.dartpseudo |
| Validate the decompiler | asm/*.s and ir/*.json |
| Understand app startup | report.json.android_startup |
| Check analysis health | quality.json and report.json |
| Review version-to-version changes | diff_report.json |
decompile exited 1. Did it fail?
No. The quality gate is reporting a measurement, and all artifacts were written. See
About the exit code 1 you will probably see for how
to read your own placeholder_ifs number and set an honest threshold.
My pseudocode has no function names. Why?
Names come from the adapter. Check report.json.adapter_selection.resolved_backend:
r2flutter: exact names from the AOT instruction tableblutter: heuristic names scraped from Blutter's rendered sourceinternalor core recovery: names are unavailable
If the backend was internal, install the adapter for your snapshot_hash, or provide an external
backend. See Adapters and backends.
Why are string literals or pool values missing from the pseudocode?
The backend that parsed your snapshot probably did not recover the real ObjectPool layout, so
pool[N] references were deliberately left unresolved instead of being filled from an unrelated
index space. Check report.json.pool_metadata.index_space_authoritative and
hints_suppressed_reason.
adapter install refuses my hash.
The compatibility registry has no record for that snapshot identity, or the record does not serve
this host. Run decompile anyway: it falls back to core recovery. An adapter for that hash has to
be contributed to the registry before it can be installed.
adapter list exits 2.
The store holds an entry that is missing or corrupt, so the store is treated as an error rather
than silently ignored. Reinstall the affected record with
flutterdec adapter install --dart-hash <HASH>, or point FLUTTERDEC_ADAPTER_STORE at a fresh
directory.
Where does flutterdec keep its data?
Two locations, neither of which depends on your current directory:
- Read-only package data (compatibility registry, runtime profiles, packaged producer):
FLUTTERDEC_DATA_DIRwhen set, otherwise<binary>/../share/flutterdec, then<binary>, then<binary>/../... The first candidate that actually holdsadapters/registry.jsonwins; an explicit override that holds none is an error rather than a fallback. - Writable adapter store (installed adapters):
FLUTTERDEC_ADAPTER_STOREwhen set, otherwise$XDG_DATA_HOME/flutterdec/adaptersor$HOME/.local/share/flutterdec/adapters. - Local symbol cache (
map-symbols --register-local-cache):FLUTTERDEC_SYMBOL_CACHE, otherwise<data home>/flutterdec/symbols.
Point FLUTTERDEC_ADAPTER_STORE at a temporary directory for a throwaway store.
My target is not supported.
Only Flutter AOT snapshots for Android ARM64 are supported. iOS, 32-bit ARM, x86/x86_64 Android
devices, JIT/debug builds, and Flutter web/desktop are out of scope at this maturity. If info
rejects the snapshot identity, the run can still fall back to core recovery, but names and classes
will be unavailable.
These captures compare public app source with what flutterdec recovers from the shipped APK. The
goal is simple: show original source first, then the recovered artifacts.
Original. App: hiVPN v1.0.0 (released October 29, 2025). MainActivity and the manifest
launcher are public in the
source repository, with a
release APK.
The app enters Flutter from MainActivity.onCreate. The second card shows the app-side Flutter
bridge that exposes MethodChannel('com.example.vpn/VpnChannel') to Dart code.
Recovered. flutterdec parsed the APK manifest, recovered com.example.hivpn.MainActivity as
the launcher, and correlated the startup chain from MainActivity.onCreate into Flutter JNI
bootstrap calls such as attachToNative and nativeAttach.
Original. App: ZedSecure v1.2.0
(source,
release APK). This is ordinary app
UI code that builds a ping badge with BoxConstraints(minWidth: 50) and other Flutter widget APIs.
Recovered. At the machine-code layer, the APK still looks like indirect selector dispatch through pool-loaded metadata and call targets.
Function IR
The IR stage makes the selector-bearing pool values explicit before readability passes.
Pseudocode
The important part is not the anonymous function name. The important part is that flutterdec
surfaced readable Flutter selector names from the AOT payload, including
dispatch.minWidth(...), dispatch.messageMap(...), and the framework-side
flutter.foundation.invoke(...).
Selector naming is gated on adapter metadata. On a target where the adapter recovers only strings, the same run emits no selector names at all (measured zero on two release APKs). Function names, control flow, and expressions do not depend on it.
App: LocalSend (releases), comparing
v1.16.1 (November 5, 2024) with v1.17.0 (February 20, 2025). flutterdec diff compared the two
arm64 APKs directly and emitted added, removed, and common function summaries plus package-level
change counts.
What these captures show
flutterdeccan recover Android startup structure from the APK surface.flutterdeccan preserve recognizable Flutter and Dart selector names inside app-owned recovered code, when the adapter recovers that metadata.- Selector-bearing pool metadata survives from asm to IR to pseudocode.
- The pipeline is inspectable at every stage: asm, IR, and pseudocode.
flutterdec is an alpha research tool. The current prerelease is
v0.1.0-alpha.4.
North star: recover readable behavior from Flutter AOT ARM64 binaries with enough semantic structure that reverse-engineering decisions can be made from pseudocode and reports.
Primary goals
- Robust semantic extraction from snapshots and metadata: libraries, classes, functions, selectors, and pool semantics
- Stable reverse-engineering-oriented pseudocode for Android ARM64 release builds
- Version-aware adapter behavior that can be updated without rewriting the core and decompiler logic
Non-goals
- Perfect reconstruction of the original Dart source
- Broad multi-architecture support at the same maturity level (x86, iOS, JIT modes)
- Dynamic runtime emulation as the default analysis path
- User guide: install paths, adapter store rules, scopes, profiles, quality gates, and how to read the pseudocode
- CLI reference: every command, flag, error category, and location rule
- How it works: the internals walkthrough
- Architecture: pipeline and module boundaries
- Development guide: building, testing, and environment setup
- Research decisions: why the tool is built the way it is
- Project context and history: long-form background and progress log
Contributions are welcome. Start with CONTRIBUTING.md for setup, the checks to run before opening a PR, commit style, and PR expectations.
- Bug report: new bug issue
- Feature request: new feature issue
- Research finding: new research issue
- Security reports: see SECURITY.md
Third-party credits:
data/dart-profiles.json: Dart AOT snapshot layout profiles imported from radareorg/r2flutter (MIT). Rationale in docs/research-decisions.md.- The
--adapter-backend r2-flutterbackend drives the same project as an external tool; it is not bundled or linked. - The
--adapter-backend blutterbackend drives worawit/blutter as an external tool.
flutterdec is released under the MIT License.
