8477 Commits
Author SHA1 Message Date
MX d7ba9ea12e upd changelog 2026-09-30 23:53:32 +03:00
MX 64d285c8e2 faac/genius/erreka hop capturer app
to get seed later via qunleashed app
2026-09-30 19:11:52 +03:00
MX e4eda9efca OwO What's this? 2026-09-30 06:25:10 +03:00
MX 15bca58e64 upd changelog 2026-09-26 21:39:57 +03:00
MX 8ed3560517 upd changelog 2026-09-26 21:39:02 +03:00
MX ed50682677 subghz cli better checks on module init 2026-09-26 21:38:27 +03:00
MX af71ba434e fbt format [ci skip] 2026-09-25 02:14:16 +03:00
MX b497bcfbab bump apps tag 2026-09-24 21:06:09 +03:00
MX 079d84374c lets call it this way 2026-09-23 19:07:53 +03:00
MX 867b133dff bump apps tag 2026-09-23 18:37:03 +03:00
MX 178a99e5d6 subghz: make genius remotes work 2026-09-23 17:43:56 +03:00
Mykhailo ShevchukandClaude Opus 5.5 9e4951074b [GUI]: Let the Loading view show a label next to the spinner (#1169)
* GUI: let the Loading view show a label next to the spinner

The NFC app drew its "spinner + label + progress bar" screen with a private
view that duplicated most of gui Loading, so no other app could use it.
Loading gains loading_set_text(): with a label the spinner moves left, the
label sits to its right and the progress bar goes under the label; with no
label the centred layout is unchanged. The text is copied, since FAPs will
call it. API 88.13 (additive).

NFC drops views/loading_label and uses a second Loading instance for the
labelled screen, so its label and bar can never show on the plain spinner.

Closes #1168

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* CHANGELOG: Loading view label

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 09:41:52 +03:00
MX 439204454e bump apps tag 2026-09-22 23:04:55 +03:00
Mykhailo ShevchukandClaude Opus 5.5 418d802b41 [NFC]: Show progress while a large CUID dictionary loads on Read (#1167)
* NFC: show progress while a per-UID (CUID) MFC dictionary loads

The CUID dictionary scan on Read showed only a spinner, so a large dictionary
gave no sense of how much was left. The NFC loading label view gains a progress
bar under the label; the scan reports keys x line size / file size, since CUID
lines are fixed-size and stream_tell is a storage round trip per key. The bar
redraws only when the whole percent changes, and setting new label text hides
it, so other users of the popup are unaffected.

Closes #1166

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* CHANGELOG: NFC CUID dictionary loading progress

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-22 22:13:21 +03:00
Mykhailo ShevchukandClaude Opus 5 65d271d751 LF RFID: correct the write-targets masking rationale and finish the iButton alignment (#1165)
* LF RFID: say what the write-target masking actually fixes

The comment landed in #1164 credited the change with stopping the
settings page writing unknown bits back into the file. It cannot:
lfrfid_settings_set_write_targets() has masked its input since the
feature landed in #1146 and is the only writer of that file, so those
bits were always dropped on save. The claim came from the issue and I
carried it through review without checking it.

What the mask on read is really for: in tree, lfrfid_scene_write.c
stashes the raw value as scene state and tests it against 0, so a
newer-only bit made "everything switched off" report "No enabled chip
can write this protocol" instead of pointing at Settings. Out of tree,
the getter is exported, and an app cannot re-mask against an enum it
does not have.

Also moves the obligation next to the rule that creates it, at the top
of the file where the next target gets appended, and drops a clause that
restated the log line below it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LF RFID: port the last two iButton settings guards

#1164 aligned the masking but left the rest of the divergence in place.

_Static_assert: lfrfid_settings_set_write_targets() builds the whole
LFRFIDSettings from a designated initialiser, so a second setting added
later would be zeroed on every save with no error and no symptom until a
user noticed a choice that would not stick. ibutton_settings.c has
guarded this since #1153; this is the same assert with the same message.

The unchecked storage_simply_mkdir() return gets the twin's second
sentence, so the next reader does not have to re-derive that the save
below reports the failure.

LFRFID_WRITE_TARGET_MASK_ALL's doc pointed at MASK_DEFAULT, which has
never existed anywhere in the tree; ibutton_write_targets.h already
names the function correctly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: correct the LF RFID write-targets entry

The entry described a settings-corruption round trip that the code could
not produce - the setter has always masked. Replaced with the symptom
that was real.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LF RFID: bound the getter's promise to this firmware, not the app's SDK

Review of the previous commit caught it making the same shape of
over-claim it was written to delete. "every bit it is handed is
nameable" is not true for an app: the mask applied is this firmware's
LFRFID_WRITE_TARGET_MASK_ALL, and an app built against an older SDK has
a narrower LFRFIDWriteTargetMax, so its own loop never reaches the bit.
The firmware can name that target; the app has no way to learn it
exists. What actually holds is the bound - what comes back is this
firmware's targets, never whatever the file happened to contain.

The CHANGELOG quoted one of the two write-screen messages and
paraphrased the other, so half of it could not be grepped; both are
quoted now. The file-top clause becomes its own sentence rather than a
fourth one hanging off an already long one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 22:06:11 +03:00
Mykhailo ShevchukandClaude Opus 5 4eaa7aaea9 LF RFID: mask the stored write targets on read as well as on write (#1164)
* LF RFID: mask the stored write targets on read as well as on write

Appending a write target is deliberately not a layout change, so the
saved_struct version does not move and a settings file written by a
firmware that knows more targets is still accepted by an older build -
carrying bits that build has no meaning for. Nothing iterates past
LFRFIDWriteTargetMax today, but the settings page seeds itself from
this getter and writes the result back, so a downgrade plus one
unrelated toggle round-trips those unknown bits.

Same one-line guard the iButton twin already applies in both
directions. The load-failure log moves with it: it used to print the
stat description for a file that had just stat'ed FSE_OK, so a corrupt
or wrong-version file was reported as "unreadable (OK)".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: LF RFID write-target settings compatibility fix

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 20:00:21 +03:00
MX c85b4cf84b bump apps tag 2026-09-22 18:37:57 +03:00
MX 5643ae0c42 remake nord ice protocol
to replicate original remote properly and to fix button support and detection + add manually support
2026-09-22 18:26:07 +03:00
MX 22ec403ed5 bump apps tag 2026-09-21 14:11:17 +03:00
Mykhailo ShevchukandClaude Opus 5 c21c79bfba CI: guard PRs and their linked issues against the Backlog milestone (#1162)
* CI: guard PRs and their linked issues against the Backlog milestone

Anything with an implementation belongs in the release it ships in. A PR
parked on Backlog - or on no milestone at all - drops out of the release
notes and out of the milestone-per-release tracking, and so does an issue
that is actively being implemented.

The check fails when the PR has no milestone or sits on Backlog, and when
any issue the PR closes does the same. Linked issues come from GitHub's own
closingIssuesReferences rather than from grepping the description, so it
sees exactly what the merge will close; a PR that closes nothing is fine.
Issues nobody is implementing are never looked at - they may stay in
Backlog or be closed with a reason.

Nothing is checked out and every permission is read-only, so plain
pull_request stays safe for fork PRs. milestoned/demilestoned are in the
trigger list so setting the milestone re-runs the check by itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CI: close the fail-open paths in the milestone guard

Review of #1162 found three ways the guard could pass a PR it had not
actually checked.

grep read the milestone title as its own options when the title started
with a dash, exited 2, and is_blocked reported "not blocked" - a guard
failing open. The blocklist is now normalised once, with blank lines
stripped (an empty line made is_blocked "" return true, harmless only
because both callers tested for empty first) and matched with `--`. An
empty list now refuses to run instead of silently passing everything.

closingIssuesReferences was capped at 50 with nothing reading totalCount,
so references past the cap vanished. The count is now fetched and
exceeding it fails. A row the loop cannot parse used to be skipped
silently; it now reports. Merging stderr into the captured data meant a
stray gh warning became a data row and suppressed the "closes nothing"
line, so stderr goes to its own file - which also lets the annotation lead
with gh's one-line reason instead of 400 characters of raw JSON body.

Cross-repo issues warn rather than notice: skipping one is a real pass for
an unchecked issue. The PR and issue branches were identical bar their
wording and are now one helper.

Comments: the security note credited the permissions block for fork-PR
safety. A fork ships its own copy of this file - what protects us is
GitHub capping the token read-only, as pr-build.yml already says. Also
corrected the claim that only Closes/Fixes are seen (every closing keyword
and sidebar links are) and documented the cross-repo carve-out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CI: run the milestone guard through github-script

Most of the step was transport, not logic: build a query string, shell out
to gh, flatten objects to TSV, smuggle totalCount in as the first output
line, capture stderr to a temp file, reverse-engineer gh's error layout for
a readable reason, check that line one is a number, re-split TSV in a read
loop. actions/github-script is already a dependency here (pr-comment.yml)
and hands back an object, so all of that goes.

It also deletes the row contract that lived in three places at once - the
jq projection, the head/tail split and the field unpacking. Adding a field
to the query and forgetting the unpacking would have shifted a column into
the milestone variable, where an unrecognised title reads as "not blocked"
and the check passes a PR it should fail, green and silent.

Three things that were awkward in bash and are now cheap:

The PR's own milestone comes from the API rather than the event payload, so
both halves of the check are equally fresh. A re-run from the Checks tab
replays the original payload, which is the one workflow the file tells
people to use for a changed issue milestone.

The call is retried twice on a 5xx or a dropped connection. It runs on
every push, so at roughly a thousand calls per release cycle even a rare
transient becomes a red X no contributor can clear. A GraphQL error is the
server answering rather than failing, so those are not retried.

A milestone that is neither blocked nor unlshd-NNN now warns. A deny-list
cannot tell a new release from a new parking lot, and a typo'd unlshd-94
passed silently before. Warning rather than failing keeps the policy where
it was: contributors cannot set milestones, so a red X they are not allowed
to clear is worse than a leaked unusual milestone.

Error text now addresses the maintainer, since triage permission is what
setting a milestone needs. Documented that none of this gates anything
until "Release milestone" is a required status check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 13:05:57 +03:00
7f372c908a SubGHz: guard transmitter cleanup when unavailable (#1104)
A protocol that the firmware can decode but not encode makes
subghz_transmitter_alloc_init() return NULL before a transmitter is allocated. The
error path then handed that NULL to subghz_transmitter_free(), which opens with
furi_check(instance) - a hard abort, release builds included. The helper also left
the transmitter pointer stale after a normal TX teardown.

iDo 117/111 is the reachable case: it has a complete decoder but .alloc = NULL on its
encoder, and it is registered with SubGhzProtocolFlag_Save, so iDo .sub files exist
and load fine - subghz_key_load() only validates the decoder. On the device the Send
button is gated by subghz_txrx_protocol_is_transmittable() and iDo carries no
SubGhzProtocolFlag_Send, but the RPC path has no such gate: subghz_scene_rpc.c goes
straight from SubGhzRpcStateLoaded to subghz_txrx_tx_start(). Sending a saved iDo
signal from the mobile app or qFlipper crashed the Flipper.

The error cleanup is guarded now, and the pointer is cleared after every teardown so
NULL reliably means "no live transmission" rather than "freed a moment ago".

The no-encoder branch also logs which protocol it was. It was the only failure exit
in subghz_txrx_tx_start() without a log line, and the dialog it produces - "Error in
protocol parameters description" - blames a file that parsed perfectly.

Co-authored-by: Mykhailo Shevchuk <mishamyte@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 12:16:36 +03:00
matthewb-wortheandClaude Opus 5 9effa4b44f RFID: enter cards by facility code and card number, read HID formats and Casi-Rusco badges (#1149)
Add Manually now asks whether to enter a card as FC/ID or as hex. FC/ID takes the
numbers printed on the card, one number input per field, and lays them out in the
protocol data itself: EM4100, HID H10301, ioProx XSF, AWID and Pyramid 26-bit, and
Gallagher. Parity and checksums are added by the firmware encoders as for hex-entered
data. Formats whose scrambling and checksums are not exported by the protocol library
go straight to hex as before.

HID H10302, H10304, H10306, AMAG S10401 and Corporate 1000 35-bit are read on top of
the firmware's Generic HIDProx protocol, whose data is the raw 44-bit field. The read
and saved-card screens add a line for every format the frame fits by length and parity;
a 37-bit frame fits more than one, so the second and later readings are prefixed "or".
Layouts follow the Proxmark3 wiegand_formats.c reference except S10401, whose even
parity covers bits 1-17 rather than 18: of 42 badges exported from a live access
system, all 42 pass that and only the 13 with bit 18 clear pass Proxmark3's.

Casi-Rusco C10106 badges transmit a plain EM4100 frame at RF/32, so the app reads the
badge id out of those 40 bits - a 19-bit credential beginning with 15, then a 19-bit
card field, less 66606 when its top bit is set. The frame carries no parity, so the
line is shown as the alternative reading and the credential range keeps it off almost
all other EM4100 cards.

The Generic HIDProx render now gives the frame length alone. Its "Data:" line repeated
the hex every caller already prints, and read the frame one nibble at a time from
offsets that assumed a full first nibble, so a frame whose length is not a multiple of
four lost bits after its first digit. A unit test covers every size header.

A number input whose range excludes 0 opened on an empty field that could not be
confirmed, because 0 is drawn as empty and the starting value was only clamped when it
was not 0. It is now clamped unconditionally, so the field is only ever empty when 0 is
in range, and an empty field reads back as 0 and is range-checked like any other value.

Co-authored-by: matthewb-worthe <135917560+matthewb-worthe@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 09:13:42 +03:00
Maksims NerobaandClaude Opus 5 8fdb70dd32 HID: improve Mouse Jiggler timers and Bluetooth controls (#1112)
Pressing Stop now stops the timer instead of leaving it firing into an empty
callback, and leaving the screen clears the running state, so re-opening it no
longer shows a highlighted "Stop" for a jiggler that is not running.

Stealth re-arms its one-shot from the timer callback while holding the model
lock, so the running flag is committed under that lock before any timer command
is issued - otherwise a stop could be queued ahead of a re-arm that had already
read the old flag. Stealth movement is never zero on an axis, and the HID write
and the RNG both run outside the model lock.

Over Bluetooth the jiggler waits for the link instead of sending into nothing,
and the status callback is registered before advertising starts so a connection
completing during startup is no longer missed.

Co-authored-by: Maksims Neroba <68654993+MNeroba@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 08:55:12 +03:00
Mykhailo ShevchukandClaude Opus 5 6b15b891c9 Revert the loader relaunch-after-reboot series
This reverts commits 279d2cc13, 0f0c75252, 9b2edccc2 and a0005f00f, which
went to dev directly. The tree is back to c74f6b1d5; reverting this commit
restores the whole series unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 22:52:16 +03:00
apfxtech a0005f00fb Loader: add oom fap 2026-09-20 21:28:59 +03:00
apfxtech 9b2edccc25 Loader: simplify pending launch 2026-09-20 21:28:59 +03:00
apfxtech 0f0c75252e Loader: shrink reboot relaunch code 2026-09-20 21:28:59 +03:00
apfxtech 279d2cc13a Loader: relaunch app after reboot 2026-09-20 21:28:58 +03:00
Mykhailo ShevchukandClaude Opus 5 c74f6b1d57 iButton: choose which blank types a write may try, and show the current one (#1153)
* iButton: choose which blank types a write may try, and show the current one

A Dallas key carries no hint of which blank is in front of the reader, so a
write attempt walks the blank types in turn and keeps the one whose read-back
matches. Until now that chain was hidden inside dallas_ds1990_write_id(): the
app could not say which type it was attempting, and the user could not stop it
attempting one. Mirrors the LFRFID write-target design.

iButtonWriteTarget names the four writers (RW1990.1, RW1990.2, TM2004, TM01x)
and a protocol declares which of them apply to it, so DS1420 no longer has to
repeat DS1990's chain by hand. The group walks that mask, announcing each
target before it is tried - from outside the critical section, because the
announcement reaches the UI thread.

Settings > Write Blanks toggles them, stored via saved_struct and honoured by
the app and the CLI. Honouring it is opt-in: a worker starts at
ibutton_write_targets_default(), which is all four, matching the behaviour
before this existed.

ibutton_protocols_write_id() keeps its signature and delegates with no context,
so the API change is additive (88.12) and existing FAPs still link.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* iButton: address review of the write-target work

Every Dallas protocol now goes through write_targets. ds_generic, ds1971,
ds1992 and ds1996 each had a write_id that called tm2004_write() directly, so
they took a fallback branch that consulted neither mask and wrote a TM2004
with TM2004 switched off - the setting covered two protocols out of six. With
all six converted the fallback branch, the write_id slot in the Dallas
descriptor and the "zero means it writes itself" sentinel all go away, and the
ROM comes from get_editable_data() rather than a cast onto the front of the
protocol's data.

A mask that resolves to nothing no longer looks like an empty reader: the
worker reports iButtonWorkerWriteNoEnabledTarget, and the write screen
distinguishes "no blanks enabled" from "no enabled blank can write this key" -
reachable without disabling everything, since DS1420 cannot use TM01x. The CLI
says so too instead of spinning.

Settings are saved on Back while the plugin is still mapped, and a failure
raises a storage error dialog instead of only a log line, which is what the
plugin ABI comment always claimed happened.

Also: iButtonWriteTargetContext moves beside the types it is built from, so
ibutton_protocols.h stops pulling protocol_group_base.h and protocol_common_i.h
into the SDK tree; write_chip_name is cleared per write session; the settings
loader masks on read and no longer logs "unreadable (OK)" for a corrupt file;
static asserts pin the enum bit positions the saved mask depends on; and the
success screen names the blank that accepted the key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* iButton: simplify the write-target code

The blank name on the write screen was always one target behind. Worker, app
and GUI threads all run at Normal priority, so posting the progress event does
not yield, and the next line masks the scheduler for most of a second - the
label queued before a target was painted only once that target had finished.
A 1 ms delay after the announcement is what actually lets the screen repaint.

Nothing-enabled is terminal, not something a retry can fix while the screen is
up, so it now reports once and drops the worker to idle instead of rebuilding
the screen and re-blinking every second forever.

The write context is normalised once at the API boundary, so the group stops
testing it for NULL twice; write_chip_name's 16-byte buffer and its snprintf
become the target itself, with the name derived on read; and the four places a
write reports collapse into one helper.

ibutton_protocols_get_write_targets, _write_id_targets, ibutton_write_target_write
and ibutton_write_targets_default have no caller outside lib/ibutton, so they
drop to '-' and stop costing FAP export thunks - the same treatment
lfrfid_write_target_type already gets.

Also: write_id's doc block had been orphaned by an insertion above it; the four
bit-position asserts collapse into one; .write_targets sits with the data fields
in all six descriptors rather than where write_id used to be; read() and
write_copy() stop the bus outside their critical sections, matching write_id;
and a dead scene-state enum, an overstated settings comment and three verbose
blocks go.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* iButton: let the write screen keep up with the blank being tried

The label showed only TM01x - the last blank of the pass. Announcing a target
and then masking the scheduler microseconds later just queues the events up, so
the app got to paint once, at the end, with the final name. 1 ms was nowhere
near a widget rebuild plus a GUI frame; 50 ms lets each one land, costing
200 ms on a pass where all four are tried and fail.

With nothing enabled the scene also flashed up a writing screen that the first
tick immediately replaced with an error. It already knows the mask before the
worker starts, so it now says so directly. The narrower case - something
enabled, but nothing that fits this key - still needs the worker to answer,
since the app cannot ask which blanks a protocol supports.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* iButton: changelog for the write-target selection

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* iButton: address review of the write-target selection

Honour the documented NULL write_ctx instead of dereferencing it, and let
ibutton_protocols_write_id() be the NULL case it describes.

The blank-type setting governs Write ID only, so an empty list no longer
blocks Full Writing, which rewrites the same chip and never consults it.

Do not crash on a write result this build has not heard of: the app is a
separate .fap, so a newer firmware must not take an older one down with it.
The switch keeps no default, so -Wswitch still catches in-tree additions.

Drop an unreadable settings file rather than fail every later read, which
left the settings page presenting defaults as a saved choice and skipping
the save that would have replaced it. Make the setter read-modify-write so
a setting added beside this one is not zeroed.

Clear the blank-type label when a pass gives up, reset the app-wide Popup
before the plugin error uses it, name the remedy instead of printing a
PluginManagerError ordinal, return before touching the bus when no target
is enabled, and check the ROM size the blank writers assume.

Export ibutton_write_targets_default(), which an exported header already
told apps a worker starts from.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* iButton: simplify the write-target selection

Derive the WriteId feature from write_targets instead of storing the same
fact twice in all six Dallas tables, which also deletes the runtime checks
that existed to catch the two disagreeing.

Charge the announce delay to the callback that repaints, not to the 1-Wire
path: the CLI registers a write callback but drops the progress result, so
it paid 50 ms per target for a name it never prints.

Drop the read-modify-write in the settings setter - it read a value it
overwrote on the next line, costing a stat and a full load on every save.
A static assert on the struct size forces the question when a second
setting is actually added.

Stop deleting an unreadable settings file from inside the getter: it is a
filesystem write on a read path, and "unreadable" includes a version a
newer firmware wrote. The page now saves on the way out even when nothing
changed, which replaces a bad file without destroying a good one.

Remove the "none enabled at all" branch and the scene state behind it. The
early return in on_enter means that event can only arrive with targets
enabled, so the distinction was unreachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-20 20:08:28 +03:00
MX 2c4fdf9ffb add authors and upd changelog 2026-09-20 00:05:14 +03:00
ApertureFox Technology 16def217c5 Menu styles 3d macintosh (#1155)
* Menu styles: add Macintosh style

* Menu styles: add 3D style
2026-09-20 00:00:00 +03:00
370ca57bc3 Toolbox: fix SimpleArray equality length (#1107)
* Toolbox: compare complete simple array elements

* NFC: compare FeliCa systems and DESFire applications by content

Both element types hold nothing but scalars and pointers to nested arrays
of their own, so comparing the elements bytewise compares those pointers:
once the comparison covers the whole element, two copies of one card can
never be equal, and every exit from FeliCa emulation would write a shadow
file. Compare the entries themselves instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Changelog entry for the SimpleArray equality fix

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Maksim Neroba <mneroba@MB14.local>
Co-authored-by: Mykhailo Shevchuk <myte@ukr.net>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 11:47:22 +03:00
Mykhailo ShevchukandClaude Opus 5 7db0c26d05 NFC: stop the MIFARE Classic dictionary attack once the card is fully read (#1151)
Key reuse fills sectors ahead of the sector cursor, so a card that shares keys across its sectors can be solved while the pass is still on sector 0. The pass kept asking for keys to sectors already read, and the scene then started the next dictionary phase on the same solved card.

Checked in three places, one per state machine that could keep working: the key request handler (the state every continuing transition comes through), the nested controller entry (covering all three callers that route into it), and the CUID -> user -> system phase chain in the dict attack scene.

Closes #1150

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 11:25:22 +03:00
MX 30a1bead81 fbt format and apps tag bump 2026-09-19 03:53:17 +03:00
MX 37e7db36f6 sub hertz
make some stuff work
2026-09-18 23:42:34 +03:00
MX 3aa19d1b77 bump apps tag 2026-09-18 20:19:06 +03:00
MX 0f14692f88 bump apps tag 2026-09-17 01:46:00 +03:00
MX e3d25b4341 bump apps tag 2026-09-15 02:57:06 +03:00
Mykhailo ShevchukandClaude Opus 5 49238e1bd3 RFID: write an EM4100 id onto ID8268 / Hitag S clone chips (#1148)
* RFID: write an EM4100 id onto ID8268 / Hitag S clone chips

Adds the ID82xx Hitag S "magic" clones (F8268, 8211 and relatives) as a
write target beside T5577, EM4305 and the Hitag micro variants, so a saved
EM4100 key can be cloned onto one from the app and the rfid CLI. The chips
ship streaming pages 4 and 5 as an EM4100 frame, so writing the 64-bit frame
into those two pages changes the id they emit.

Unlike every other write target this one cannot be blind: SELECT needs the
tag's own UID. The Flipper's LF envelope detector cannot resolve an
anticollision '1', so the reply is decoded from its '0' cells and the two
remaining unknowns - the leading bits and the frame alignment - are put back
to the tag as AC SEQUENCE prefixes, which only the tag whose UID starts that
way answers. A UID is used only once the tag itself has confirmed it, and a
negative control plus a re-verify guard against a chattering field.

That read doubles as a presence check no blind writer can make, so when no
such chip answers the write and its verify read are both skipped rather than
costing a 2 s verify per pass.

The target is opt-in: lfrfid_write_target_is_default() classifies every
target through a -Wswitch'd switch, so appending one fails the build until
someone decides whether it is safe to run unasked. Pages 4 and 5 hold
application data on a genuine Hitag S and nothing readable here tells a
genuine tag from a clone, so this must not fire while cloning a key with a
real Hitag S in range.

EM4100 at RF/64 only - the rate the factory TTF config emits; the faster
variants would need the config page rewritten too, so they are refused.

Ported from a standalone reader and writer validated over 575+ reads on
three sample tags; written to two tags on hardware.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: ID8268 / Hitag S EM4100 write target

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 17:49:36 +03:00
22ce1d9ae5 RFID: choose which chip types a write may try (#1146)
* LFRFID: let a caller restrict which chips a write may target

The write loop tried every writable chip in turn - T5577, EM4305, then each
Hitag micro password variant - and verified after each one. A verify read
costs up to 2 s, so a pass that does not find the chip in front of you can
take 10 s before it starts over, most of it spent on chips the user knows are
not there.

Adds LFRFIDWriteTarget, which names a chip rather than an encoding: the Hitag
micro variants share one write type but differ by password, so each is its own
target. lfrfid_worker_set_write_targets() takes a mask of them, defaulting to
all, so a caller that never sets one behaves exactly as before.

lfrfid_settings holds that mask on the SD card next to the saved keys, so the
app, the CLI and any app writing through LFRFIDWorker agree on it without one
having to hand it to another.

The loop now intersects the mask with what the protocol can actually encode
for, once, before writing anything. That replaces the per-pass skip counter
(which re-fired "cannot be written" on every pass) and flattens the nested
variant loop into the single pass over targets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: add a Settings menu with a Write Chips page

Settings sits at the bottom of the app menu and holds one page for now, where
each writable chip - T5577, EM4305 and the three Hitag micro / ID82xx variants
- can be switched on or off. Everything starts enabled, so a user who never
opens it sees no change.

The page itself lives in a plugin, loaded when the scene opens and unloaded
when it closes, since it is only reachable from one menu entry. The plugin
resolves against the firmware API alone - it needs no app-private symbols, so
there is no composite resolver or app symbol table here - and it is handed a
three-field context rather than LfRfid, which keeps the app's own layout out
of the plugin ABI.

The app keeps the VariableItemList: removing a view while it is the one on
screen latches the event loop stop, and the scene manager runs on_exit before
the previous scene's on_enter, so a plugin-owned view would be removed at
exactly the wrong moment. The list is reset before the plugin is freed, since
its change callbacks point into plugin code.

A missing or stale .fal costs only the ability to edit the setting: writes
read the mask from the settings file, not from the plugin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: say why a write cannot start instead of spinning

With every chip enabled the write screen always had something to try, so the
only failure it reported was the protocol being unwritable, and only after a
full pass. Now that chips can be switched off, two more ways to have nothing
to do exist, and they are worth telling apart: nothing enabled at all, and
nothing enabled that this protocol can be written to (HID, for instance, only
encodes for a T5577).

The check runs before the worker starts, so the error is immediate rather than
10 s of "Writing" first. Probing re-encodes the key, hence the snapshot before
it and the restore after.

The worker is only stopped on exit if it was actually started - stopping a
thread that never ran is not allowed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: honour the write chip setting in the CLI

rfid write drives the same worker as the app, so leaving it on the default
would have it quietly write to a chip the user switched off on the device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: RFID write chip settings

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: rework the write chip settings after review

A 5-agent review and a 4-agent cleanup pass over the previous commits. The
feature is unchanged; what it costs and how it fails are not.

Settings file layout is no longer SDK ABI. saved_struct_load() copies as many
bytes as the *firmware* thinks the struct is, so exporting LFRFIDSettings meant
adding a field later would overflow the stack object of any .fap built against
the older header - and only the major API version gates loading, which this
would not have bumped. The struct, path, magic and version are now private to
lfrfid_settings.c and the SDK gets two scalar accessors.

A chip the user switched off is now its own outcome. The worker reported
LFRFIDWorkerWriteProtocolCannotBeWritten for both "this protocol has no
writable chip" and "every chip that could write it is disabled", so the CLI
told users their protocol was unwritable when the real fix was a setting. It
now reports LFRFIDWorkerWriteNoEnabledTarget for the second, which also lets
the write screen drop its own pre-flight probe: the scene ran the same
lfrfid_write_targets_supported() sweep the worker was about to run, purely to
pick the message, and paid a data snapshot/restore and a started-the-worker
flag for the privilege.

The settings page no longer leaves anything behind. variable_item_list_reset()
does not clear the enter callback - it lives outside the model and
set_enter_callback() rejects NULL - so a pointer into the unloaded plugin stayed
parked in app-owned state; the app now installs its own. The cached VariableItem
pointers are gone: they were justified by a deadlock that cannot happen (the
view model mutex is recursive, and subghz already calls
variable_item_list_get() from a change callback) and would have dangled once
the list outgrew M*LIB's initial capacity of 16.

A failed save is no longer silent. It reported nothing, and re-entering the page
showed the old values as though the user had chosen them. Saving moved to the
Back event, where the screen still belongs to this scene, and a failure goes
through dialog_message_show_storage_error() like the app's other storage errors.

The VariableItemList is allocated on first use. variable_item_list_alloc()
starts a periodic 333 ms timer it never stops, so allocating it at startup
capped tickless idle for the whole RFID session over a screen most users never
open.

Also: the static assert forbade the append the header told you to make, so
appending a target broke the build with a message about Hitag; the two ordering
asserts and the variant subtraction are replaced by a table whose rows state
their own variant. lfrfid_write_target_type() lost its default label, so
-Wswitch now forces a new target to be classified at compile time. The plugin
ABI lost its context struct - a page gets the list and the scene owns the view
switch, matching subghz. Four helpers with no caller outside the firmware are
marked "-" rather than exported for good. The plugin load shows the loading
animation and names the loader error, which separates a missing .fal from a
stale one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: second review pass over the write chip settings

Nine more agents over the reworked code. Four real defects, the rest comment
and shape corrections.

The lazily allocated VariableItemList was allocated before the plugin load, so
a failed load still started the periodic 333 ms timer the laziness exists to
avoid - for the rest of the session, on a screen the user could not use. It now
happens only once the page is known to be there.

lfrfid_settings_get_write_targets() stat'ed the file after trying to load it,
purely to pick a log level, so the missing-file case still went through
saved_struct_load()'s E log. It checks first and skips the load, which makes
the default path - every device whose owner never opened the screen - silent
again on every write.

The plugin-error popup was one character too long for the buffer and rendered
as "(error 1", because lfrfid_text_store_set() passed LFRFID_TEXT_STORE_SIZE
against a buffer that carries the extra byte for the terminator. Fixed at the
helper, so every caller gets its last character back.

The CLI never handled LFRFIDWorkerWriteTooLongToWrite, so a non-writable card
left it printing nothing while the app said "Still Trying to Write" on screen.
Both write callbacks now log an unhandled result instead of sending custom
event 0, which is not a LfRfidCustomEvent and would be dropped in silence.

Shape: the write scene's file static moved into the scene manager's own
per-scene state, lfrfid_settings_load() folded into its only caller, the write
loop's "done" flag stopped standing in for "never started", and the write type
switch lost its default so -Wswitch covers it like the target switch (a probe
build confirms an unclassified enumerator is now a build error). The <= 32
assert became < 32: at exactly 32 targets LFRFID_WRITE_TARGET_MASK_ALL shifts
by the full width of its type.

Comments: five said things that were not true - that the error code tells a
missing .fal from a stale one (plugin_manager collapses both into one value),
that a failed settings read had no other trace (saved_struct logs all four
causes), that the loading view swallows input (leaving it resets the input
queue), that opening the settings page writes a file, and that a scene's screen
belongs to the next scene by the time on_exit runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: annotate the RFID write chip entry with its issue and PR

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: MX <10697207+xMasterX@users.noreply.github.com>
2026-09-14 08:40:22 +03:00
MX 946a52a640 merge some rfid related PRs 2026-09-13 23:59:18 +03:00
MX 0f09492cdd cleanup chlog [ci skip] 2026-09-13 00:11:39 +03:00
github-actions[bot] 9819755ac2 issue templates: set Found-in-version stable option to unlshd-093 [ci skip] 2026-09-12 20:53:11 +00:00
Mykhailo ShevchukandClaude Opus 5 945234a767 NFC: reset the parse popup so its hourglass stops drawing under later screens (#1142)
* NFC: reset the parse popup when leaving the read-success screen

The scene borrows the shared popup for the "Parsing" progress screen and
then switches to the widget, which stops it being drawn but leaves it
configured. Its A_Loading_24 stayed parked at (12, 23) for the rest of the
session, so the next popup that set no icon of its own drew the hourglass
under its own text - "Keys Saved" in Save Keys to Dictionary, and "Field
is on" under Debug.

Every other popup-configuring scene already resets on exit or at the head
of its setup function; this was the only one that did neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: NFC parse popup icon leak

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:49:29 +03:00
Mykhailo ShevchukandClaude Opus 5 0a3e5acfaf SubGHz: correct the radio poll comments after the split (#1140)
Follow-up to #1139. Splitting the poll into a cheap check and a rate-limited
search left three comments describing the shape it had before.

subghz_txrx_radio_device_probe() still claimed the probe tick was stamped
early so "the re-entry from rx_start() below cannot immediately pay for a
second one". There is no rx_start() below it any more, and that re-entry
now returns at the top of poll(), because by then the device type is
Internal. What the stamp actually buys is that a module which just dropped
out is not hunted for again on the next trip through the menu.

radio_device_probe_tick said it recorded the last search for a missing
module. probe() stamps it on the cheap path too, so it records the last
probe of any kind.

subghz_txrx_hopper_update() is public and now depends on its callers having
run subghz_txrx_radio_device_poll_active() earlier in the same tick - true
of both of them today, and the reason a hop pays nothing for the check.
That was only written down at the call sites, which is not where someone
adding a third caller would look.

No functional change.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 10:02:41 +03:00
MX 69c036819e show loading progress when unpacking assets from apps 2026-09-10 03:17:34 +03:00
8009e81350 SubGHz: survive an external CC1101 module unplugged mid-session (#1139)
* SubGHz: survive an external module unplugged mid-session

The external CC1101 was probed once, in subghz_txrx_alloc(), and the
result latched for the life of the app. Unplug it and enter Read, and the
app drove a chip that was not there: subghz_txrx_rx_start() ->
subghz_txrx_begin() -> ... -> subghz_device_cc1101_ext_rx() waiting for
CC1101StateRX, which never arrives, and the furi_check around it took the
firmware down.

App side, the probe now happens where the radio is about to be used
instead of once at startup. subghz_txrx_radio_device_set() tears the
module down first and honours subghz_devices_begin()'s return value - the
self-test that actually distinguishes a present module from a floating
header, and previously discarded - so it returns what came up rather than
what was asked for. subghz_txrx_radio_device_poll() re-checks before every
RX and TX start, on every hop (hopping skips begin()) and on the Sub-GHz
menu, falling back to the internal radio and picking the external one back
up once it is plugged in again. A missing module reads back as a steady
-74 dBm rather than as nothing, so Read would otherwise show a receiver
that looks healthier than an idle one; poll_active() catches that from the
receiver tick and restarts RX on whatever answered.

Driver side, the three furi_check(cc1101_wait_status_state(...)) calls now
log and carry on. A hot-pluggable peripheral being absent is an I/O
condition, not an invariant violation - the internal radio keeps its
furi_check, since that chip is soldered on and there is nothing to fall
back to. This is also the only part of the fix that reaches the other
consumers of the driver (CLI, JS, subghz_remote, subghz_tx_rx_worker),
none of which re-probe. Alongside it:

- set_frequency() waited for CC1101StateIDLE in an unbounded while(true)
  while holding the SPI bus; the hopper reaches it every hop, so a module
  that stopped answering was a watchdog reset waiting to happen
- is_connect() tested PARTNUM, which a genuine CC1101 reports as 0x00 -
  the same as an empty bus. It reads VERSION now, which is what makes the
  cheap liveness check on the receiver tick possible
- the E07 amp is no longer keyed without confirmation that the chip
  reached TX, and is still dropped on the IDLE path when the transition
  times out

Known gaps, deliberately left: SubGhz Remote keeps its own copy of the
txrx helper and gets only the driver half of this; TX failing because the
module is gone still reports "Transmission is blocked / Frequency is
outside of default range", which needs its own error state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: external SubGHz module hot-unplug

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* apply review fixes

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: MX <10697207+xMasterX@users.noreply.github.com>
2026-09-10 01:59:20 +03:00
MX d12a695470 bump apps tag 2026-09-10 00:55:14 +03:00
MX ab9bdf375b move file resources out of the clock app 2026-09-09 05:50:46 +03:00
MX 872b420f31 just in case 2026-09-09 05:38:28 +03:00