Releases: doesthings/FreeFCC
Release list
v1.5.3
v1.5.2 — Install permission check before download
v1.5.2 — Install permission check before download
When you tap Download in the Update section, the app now first checks if it has the "Install unknown apps" permission. If not, it opens the system Settings page so you can grant it before downloading the APK. Once granted, tap Download again and it proceeds normally.
This prevents the frustrating situation where the download completes successfully but the install silently fails because the permission was never granted.
Full changelog
v1.5.1 — 5x faster FCC apply (tested on real RC2)
v1.5.1 — 5x faster FCC apply (tested on real RC2)
FCC unlock now completes in ~1.4 seconds instead of ~6.7 seconds — a 5x speedup. Tested and confirmed working on real RC2 hardware.
What changed
- Inter-frame delay: 150ms → 30ms
- Inter-round delay: 400ms → 100ms
- Read window: 80ms → 50ms
- Keepalive inter-frame: 100ms → 30ms
- Keepalive read window: 80ms → 50ms
What did NOT change
- All 21 frames identical (same cmd sets, cmd IDs, destinations, payloads)
- 2 rounds (same as before)
- Byte order (same as v1.5.0 — the working layout)
- CRC-8 / CRC-16 algorithms (unchanged)
- Everything else from v1.5.0 (LED fix, 4G hardening, auto-update fix, update banner)
Full changelog
v1.5.0 — Critical fix: FCC unlock broken since v1.4.8 (byte order revert)
v1.5.0 — Critical fix: FCC unlock broken since v1.4.8
The problem
v1.4.8 introduced a DUML frame byte order change that broke FCC unlock on real RC2 hardware. v1.4.7 worked perfectly.
Root cause
The DUML frame builder was modified based on an incorrect analysis of the wire format. The correct byte layout is defined by the public dji-firmware-tools project (comm_mkdupc.py, GPL-3.0), which is the authoritative reference for the DUML protocol:
[4] = sender [5] = receiver/dst [6-7] = sequence [8] = cmdType [9] = cmdSet [10] = cmdId
Verification
Confirmed against three independent sources:
- dji-firmware-tools (public GPL-3.0 reference) — the DJICmdV1Header struct defines [5]=receiver_info, [8]=cmd_type_data
- Real RC2 hardware — v1.4.7 layout works, v1.4.8+ layout breaks
- Empirical testing — the reverted layout restores FCC unlock
What was fixed
- Reverted frame builder to the correct byte layout ([5]=dst, [8]=cmdType)
- Reverted response validation to match
- Reverted FCC profile timing to the known-working values (150ms inter-frame, 400ms inter-round, 80ms read window, 2 rounds)
- Reverted keepalive timing (100ms inter-frame, 80ms read window)
What was NOT reverted (still improved from v1.4.7)
- LED fix (no longer blocks FCC keepalive)
- 4G hardening (model whitelist, dongle pre-check, serial caching, single-socket reuse)
- Auto-update fix (rate-limit retry, install permission check, error surfacing, Check Again button)
- Update-available banner on FCC page
Full changelog
v1.4.9 — Auto-update fixed: rate-limit retry, install permission, error surfacing
v1.4.9 — Auto-update fixed
The auto-update was completely broken. Here's what was wrong and how it's fixed:
Rate-limit retry bug (critical)
After a failed update check (no Wi-Fi, timeout, etc.), the Retry button was a silent no-op for 1 hour. The timestamp was saved before the fetch, so any failure consumed the rate-limit window. Now the timestamp is saved only on success — failed checks can be retried immediately. The Retry button also bypasses the rate-limit entirely.
Install permission (critical for RC2)
The RC2 controller blocks in-app APK installation unless "Install unknown apps" is granted per-app. FreeFCC was firing the installer intent without checking this permission, so the install silently failed. Now it:
- Checks canRequestPackageInstalls() before installing
- Opens the Settings page to grant the permission if missing
- Falls back to telling the user to install via SD card + FileManager if Settings is unavailable (DJI hides it on some firmware)
Install reliability
- Copies the APK to external storage before installing (the RC2's installer may not handle content:// URIs from app-private cache)
- Checks that a package installer exists before launching (tells user to sideload �1_PackageInstaller if missing)
- Grants URI permission to all resolved installer activities
Error surfacing
All errors were silently swallowed (catch (_: Exception) { null }). Now:
- UpdateChecker logs the actual exception class + message
- Install failures show on the Update page, not just in the log
- Download failure message mentions Wi-Fi (the RC2 has no SIM)
Update-available banner
Added a banner on the main FCC page showing "Update available — vX.Y" so users don't have to manually check the Update tab.
Full changelog
v1.4.8 — Reliability fixes: LED no longer breaks FCC, 7x faster apply, 4G hardened
v1.4.8 — Reliability Fixes
LED toggle no longer breaks FCC mode (critical fix)
Toggling LEDs after activating FCC no longer causes FCC to stop working. The LED command was holding a hardware lock that blocked the FCC keepalive for ~1.5 seconds, creating a gap where DJI Fly could reset the radio to CE. Fixed by:
- LED command no longer holds the hardware lock (uses port 40007, keepalive uses 40009 — different subsystems, no conflict)
- LED now uses a separate transport instance
- LED send pattern updated to 2 bursts × 5 writes × 100ms (10 total, matching the reference app)
- Keepalive now retries with 200ms backoff on lock-failure instead of silently skipping ticks
7x faster FCC apply
FCC unlock now completes in ~880ms instead of ~6.7s:
- Inter-frame delay: 150ms → 10ms
- Inter-round delay: 400ms → 100ms
- Read window: 80ms → 40ms
The 10ms inter-frame delay is proven safe — it's the same timing the 4G activation path has always used successfully.
4G activation hardened
- Reuses one Unix socket for all 128 frames (was opening 128 separate sockets)
- Added aircraft model whitelist — refuses 4G on Mini series (no cellular module)
- Added fast 4G dongle pre-check before sending any frames
- Serial number validated (≥6 chars) and cached across sessions
DUML frame builder corrected
- Fixed byte order to match the DJI proxy spec: byte 5 = cmdType, byte 8 = dst
- Thread-safe sequence counter with random initialization
Full changelog
v1.4.7
FreeFCC v1.4.7 — Critical Fixes + Crash Protection
Critical Fixes
- Fixed update installation broken by scoped storage: the previous version removed
WRITE_EXTERNAL_STORAGEbutinstallUpdatestill tried to copy the APK toEnvironment.getExternalStorageDirectory()/FreeFCC/— which is unwritable on Android 10+ scoped storage. Now uses the cached APK directly viaFileProvider(thefile_paths.xmlcache-path already coverscacheDir/updates/). - Fixed app crash on malformed profile:
autoConnectAndApplyandsend4gActivationFrameshad nocatchblock — a corrupt or missing profile asset would crash the app. Both now catch and report errors gracefully. - Fixed FCC being re-applied after CE restore:
disableFccnow stops the keepalive service first. Previously, keepalive would re-apply FCC ~2 seconds after the user tapped "Stop FCC Mode", undoing the restore. - Fixed crash on Support page links: the Ko-fi and GitHub buttons called
startActivitywith anACTION_VIEWintent, which throwsActivityNotFoundExceptionon the RC2 (no browser installed). Now wrapped in try-catch.
Correctness & Safety
- Keepalive stops if profile fails to load: if
fcc_keepalive.jsonis missing or corrupt, the service nowstopSelf()instead of becoming a silent foreground no-op with a notification but no frame sends. - Fixed keepalive interval drift:
delay(INTERVAL_MS)moved to the start of the loop so the 2s interval is consistent regardless of send duration. Previously the actual interval was send_time + 2s. - Fixed serial probe binary corruption:
listenForSerialnow usesISO-8859-1instead of UTF-8 for decoding binary telemetry. UTF-8 multi-byte sequences could corrupt ASCII serial strings. - Rate-limited update checks: GitHub API is checked at most once per hour (unauthenticated limit is 60/hour). Prevents rate-limiting during development or repeated app restarts.
- Keepalive notification now opens the app when tapped (added
PendingIntent). - Guarded download against double-tap:
downloadUpdatechecksisDownloadingUpdatebefore launching. - Validated hex string parsing:
hexToBytesnow requires even-length input (throws instead of silently truncating).
About the FCC apply mechanism
A user raised concerns about the FCC apply mechanism being "brittle and unsafe" because it replays captured frames without verifying the ACK. This is a valid observation. However, there is no publicly documented DUML command to query the current radio region (FCC vs CE) — the dji-firmware-tools project confirms this. All known FCC unlock tools work the same way: write frames, discard the ACK, and report success based on TCP write completion. Verification is only possible by observing the DJI Fly transmission graph (the app already documents this in "How Do I Know If It Worked?").
The key improvements made in this version that address the safety concerns:
- The profile is now the minimal 21-frame universal set (restored in v1.4.5), not the 31-frame set that included unrelated altitude/distance/NFZ writes
disableFccnow stops keepalive before restoring CE, preventing the "persistent contention" the user described- The keepalive service now respects the user's stop intent (persistent flag + sticky-restart guard)
Compatibility
DJI RC2, RC Pro 2, RC Plus (smart controllers with a screen). 21-frame universal FCC profile. No Google Play Services required.
v1.4.6
FreeFCC v1.4.6 — Hardening + Bug Fixes
New Features
- BootReceiver: if auto-FCC is enabled, the keepalive service restarts automatically after a controller reboot — no need to reopen FreeFCC manually
- Sticky restart respects user intent: if the system kills and restarts the keepalive service, it checks the persistent flag so it won't silently re-enable after you stopped it
- UI keepalive toggle synced: the toggle reflects the actual service state after a process restart
Security
- APK SHA-256 verification: downloaded updates are verified against the GitHub asset digest before offering install — a tampered or corrupted download is refused
- FileProvider scope narrowed: the cache path is now restricted to
updates/instead of the entire cache directory - Removed deprecated storage permissions:
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGEare no-ops on API 30+ and have been removed allowBackup=false: the auto-FCC flag is device-local and no longer backed up / restored across devices- Android 14 foreground service fix: added
CHANGE_WIFI_STATEso theconnectedDeviceforeground service type has the required underlying permission on API 34+
Bug Fixes
- Keepalive no longer swallows CancellationException — coroutine cancellation now takes effect promptly instead of waiting for the next blocking call
installUpdatenow runs on a background thread — the APK copy was blocking the UI thread (ANR risk)- FileOutputStream leak fixed in the update downloader (now uses
.use {}) - Crash protection:
enableFcc,disableFcc,setLed, andqueryDeviceInfonow catch parse/load errors instead of crashing the app on a malformed profile asset queryDeviceInfoempty-frames guard: no moreNoSuchElementExceptionifdevice_info.jsonis emptysendAndReceiveshort-read guard: a truncated response no longer throwsArrayIndexOutOfBoundsException- Keepalive interval drift fixed: the 2s interval now starts at the beginning of each loop, so the actual interval is consistent regardless of send duration
- Keepalive state desync fixed: the persistent flag is checked on process restart so the UI toggle matches reality
- Duplicate install intents removed: the file manager intent that competed with the system installer has been removed
- HardwareLock.end() is now safe to call without ownership — no more
IllegalMonitorStateExceptionfrom a stray release
Performance
- Keepalive profile cached at service creation instead of re-parsing JSON and rebuilding frames with CRC every 2 seconds
- ACK read buffer reused instead of allocating a new 2KB buffer per frame
- Named constants for connect timeout, settle delay, and alternate ports (8901–8904)
Code Quality
- Hardcoded version strings in the UI now use
APP_VERSION(no more stale "1.4.4" after a bump) - Build version bumped to 1.4.6 (versionCode 14)
Compatibility
Same as before: DJI RC2, RC Pro 2, RC Plus (smart controllers with a screen). 21-frame universal FCC profile, 2 rounds, 150ms between frames.
v1.4.5
FreeFCC v1.4.5
Changes
- Restored 21-frame FCC profile — matches the verified universal frame set. Removed unverified altitude/distance/NFZ parameter writes that the DJI Fly app overrides at runtime anyway (C0 class flag). Only the FCC radio region switch is a verified effect.
- Protocol rename: DUMPL → DUML — corrected to the official name (DJI Universal Markup Language) per the dji-firmware-tools wiki. All code, comments, docs, class names, and filenames updated. Zero
dumplmentions in source or APK. - README accuracy — dropped unverified altitude/distance/NFZ claims from the tagline and features. Clarified the app targets DJI smart controllers with a screen (RC2, RC Pro 2, RC Plus).
Merged PRs
- #11 (ther3ptil31987-prog): Fix keepalive service bypassing the hardware write lock — extracted the lock into a process-wide
HardwareLocksingleton so keepalive frames can't overlap manual operations on the TCP port. Includes a concurrency test. - #12 (ther3ptil31987-prog): Fix signed-release CI job to check the keystore secret — the guard was missing
SIGNING_KEYSTORE_B64, causing an opaque build failure when that one secret was absent. Applied directly (gh token lacks workflow scope).
FCC Mode
- Switches radio from CE to FCC mode (higher power, more channels)
- 21-frame profile, 2 rounds, 150ms between frames
- Confirmed working on RC2 + RC Pro 2
Compatibility
- DJI RC2: Mini 5 Pro, Mini 4 Pro, Air 3S, Neo 1, Neo 2, Avata 360
- DJI RC Pro 2: Mavic 4 Pro (direct install, no launcher needed for FCC)
- DJI RC Plus: Use freefcc-launcher for 4G
Keepalive + Auto-FCC + 4G + LED + Device Info
- Foreground service re-applies FCC every 2s to prevent CE reset
- Auto-FCC toggle: auto-connect, apply, start keepalive, launch DJI Fly
- 4G activation frames (Unix domain socket, experimental)
- LED control (port 40007)
- Device info query
Note on Altitude
The 120m CE altitude limit is enforced by the DJI Fly app via a C0 class runtime flag. DUML parameter writes set the flight controller values, but the DJI Fly app overrides them. There is no known way to bypass this without modifying the DJI Fly app or flashing patched firmware.
v1.4.33 — Restored working FCC profile + install fix
Critical Fix
FCC Profile Restored
The trigger SET commands from the other tool only work over USB/RCLink direct transport — they get filtered by the RC2's DUMPL proxy at TCP 40009. Restored the original 21-frame profile confirmed working over TCP:
- sender=130, cmd_type=32 (standard DUMPL proxy compatible)
- No trigger SETs, no NLD keepalives
- FCC radio + WIFI + OFDM + FC params — the exact frames that work on RC2
Keepalive
Simplified to re-apply FCC radio mode every 2s (service mode → set FCC → commit → exit)
Install Fix
Copies APK to SD card and opens file manager. Added storage permissions.
Note on 120m Altitude
The 120m CE altitude limit is enforced by the DJI Fly app, not the flight controller. The other tool bypasses it via USB/RCLink direct transport (which we can't use via TCP). Altitude unlock via DUMPL TCP is not possible without modifying the DJI Fly app.