* RFID: review follow-ups to the T5577 dumps in the Saved list
What #1188 left behind, found by reviewing it again after it was open.
The missing-app dialog told the user to install the T5577 app from the
apps catalogue. It is not there: the .fap ships in the firmware's own SD
resources, which is what the message says now, and the comment on
T5577_APP_PATH said the same wrong thing.
A dump that will not open now says whether it is no longer on the card or
is not a dump this version reads - one message covered four causes, and
the likeliest one on a handoff, the file being gone, was not even logged.
Long names are cut to fit, as the file menu's header already did; that
clipping is one helper now rather than two copies.
The T5577 app's Saved no longer counts the dumps before opening the
browser. The browser draws "<Empty>" for an empty folder by itself, so
the scan only chose the cursor row - and since dumps moved in beside the
keys it read past every key in the folder first, then the browser read
the same folder twice more. The legacy folder is still listed, being a
subfolder, and is now made only when a block test writes its backup
rather than at every launch.
In the lfrfid app, a launch argument naming a dump no longer needs a verb
of its own: the extension already says what it is, which removes the
browse branch and its custom event, and the start scene goes back to
plain menu dispatch. A dump renamed in the T5577 app reopens the list in
the folder it was in rather than at the root. The path check asks the
storage service whether the file is under the app folder instead of
comparing bytes, since /ext is case-insensitive.
Archive: "Show" asks a predicate rather than comparing a file type
against ArchiveFileTypeBadUsb in three places, so where a type sits in
the enum stops deciding whether it can be shown as text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* T5577: keep the test backup out of the open-dump path
A block test borrowed app->path to build its backup's filename. That
string is the dump the user has open, and the saved browser opens where
it points - so after a test, Saved went to the backup's folder instead
of the dumps folder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* RFID: show T5577 dumps in the Saved list and open them in their app
The T5577 app saved into a subfolder of the lfrfid one, and the lfrfid
saved list filters on .rfid alone - so the folder appeared, and entering
it showed nothing.
Dumps now save to /ext/lfrfid beside the keys, and the saved list and the
archive's 125 kHz tab list both kinds. Picking a dump hands it to the
T5577 app, which opens it on its own file menu, and queues this app back
behind it on the same file, the way nfc_app_run_external chains mfkey32.
A path ending in .t5577 passed to the lfrfid app now means that list,
where before the app started and immediately exited.
In the T5577 app the browser became a scene of its own, so leaving a dump
returns to the list it was picked from instead of the start menu, and a
rename now writes to the folder the dump is in rather than the app folder
- opened from a subfolder or the archive, it used to move the file and
report success. Its own folder keeps the test backup and the dumps older
versions saved there, so neither shows up among the user's keys.
The archive's tab filters moved into a table of their own, which drops
two pseudo file types that existed only to carry a filter string.
Closes#1187
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chlog: T5577 dumps in the Saved list
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Adds the T5577 block read to lib/lfrfid, the .t5577 dump format, and a bundled
T5577 app: read a tag's blocks and save them, look them over, edit any block or
compose block 0 by name, write a dump back with every block read back again,
test which blocks a chip really takes, wipe a tag, and clear its password.
Wipe and Clear Password move here from RFID -> Extra Actions, since both
address blocks and are judged by reading one back. Their password list moves
out of internal flash to /ext/lfrfid/assets/t5577_dict.lfrfid, read with
keys_dict the same way the NFC key dictionaries are.
Also rebuilds the lfrfid "Write and set password" flow on verified blocks, and
bounds a verify read that could wait forever - the two together left a tag
protected with the screen still on "Writing with password".
Fixes#1180.
API 88.22.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MaBUeP1J6NwqCJjhbnTUND
* RFID: share the duplicated write_data bodies (-992 B of flash)
Nearly every LF RFID protocol filled a write request the same way and differed
only in its config word: protocol_viking_write_data() and
protocol_jablotron_write_data() were byte-identical except for four constants,
and six protocols carried a verbatim copy of the same 64-bit reversal loop.
27 call sites across 20 protocols now share two bodies in
protocols/lfrfid_write_fill.c. The block or word count is read out of the
config word's maxblock field rather than passed alongside it - it is the same
number in both places at all 27 sites, and the chip streams what that field
says, so the two could never legitimately disagree.
Behaviour is unchanged. Each original inline loop was transcribed and compared
against the shared body over random vectors for every bit width in use, and the
derived counts were checked against the ones the call sites used to pass.
No API change: the per-protocol callbacks were never exported, and
protocol_dict_get_write_data() and the lfrfid_protocols vtable are untouched.
Left alone on purpose: em4100, electra and h10301 keep their encoded data as a
uint64_t or uint32_t[] rather than an MSB-first byte buffer, so a bit_lib
helper would byte-swap them; and gallagher's EM4305 arm reads its payload one
bit higher than every sibling (#1181), which folding would silently normalise.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JoYukcdV7vyusuaXtYcEdu
* upd changelog
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JoYukcdV7vyusuaXtYcEdu
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat: add Indala 224-bit (long format) RFID protocol support
Add complete Indala 224-bit protocol: decode, encode, emulate, and
write/clone via T5577. Uses PSK2 modulation (RF/32, bitrate 2) with
a 30-bit preamble (1 + 29 zeros) and 224-bit data payload.
Decoder performs PSK2 differential decoding (data = phase XOR next_phase)
from the shift register, handles both normal and inverted preambles for
phase-alternating cards, and validates across 6 consecutive frames.
Writer configures T5577 with block 0 = 0x000820E0 matching Proxmark3
reference implementation. Encoder uses PSK2 carrier-flip modulation
where carrier phase inverts on data bit = 1.
Verified against Proxmark3 lf indala clone output:
80000001B23523A6C2E31EBA3CBEE4AFB3C6AD1FCF649393928C14E5
Includes unit tests for roundtrip encode/decode and PSK2 phase
alternation edge case.
* Append Indala224 to protocol enum, move its test registration
* fbt format
* Indala224: don't drop a full-frame phase run
---------
Co-authored-by: Štefan Croitoru <66339601+turbospok@users.noreply.github.com>
`rfid read` cannot read a Keri tag. It will not even read back a Keri
card the Flipper itself just wrote. Every decode it produces from a
genuine T5577 Keri card is wrong; validate_count usually stops those
reaching the UI, but `rfid raw_analyze` has no such gate and prints the
wrong value as a credential, with a plausible facility code and card
number.
A Keri frame is never DC balanced -- its preamble alone carries a run of
29 zeros, and on the tag measured here only 10 of 64 bits are set. On a
frame that unbalanced the PSK slicer reports every high run ~135 us long
and every low run ~135 us short, past the +/-127 us that the decoder's
bit-period rounding tolerates, so every run of ones decodes one bit too
long. Long runs of zeros survive, because the true bit period is nearer
256 us than KERI_US_PER_BIT's 255 and over a long enough run that
difference offsets the skew. The same T5577 rewritten as Idteck, whose
frame is near half ones, shows a skew of only +40/-42 us, which is why
the other PSK protocols are unaffected.
The "wrong phase synced data" correction was meant for exactly this, but
it shifts the boundary the wrong way: it lengthens the high level, which
here is already too long, by a further 120 us. That adds a bit to every
run of ones and takes one off every short run of zeros, which keeps the
frame 64 bits long -- close enough that jitter on a marginal capture
lets both preamble checks pass over a mangled payload, turning a failed
read into a printed credential whose value varies from read to read.
Correct the skew in both directions instead.
Keri has no parity and no checksum, so also require the two frames in
the shift register to carry the same ID before accepting a decode.
Because the mis-slicing is not identical from frame to frame -- which is
why the wrong values varied between reads of one card -- frame-to-frame
agreement is what turns a marginal read into a failed read rather than a
credential. That needs the register to hold two whole frames, 16 bytes
rather than 13; at 13 the second frame's ID would have been a three byte
read past the end of the buffer. The wider buffers also align every
preamble load, which at the old 13 byte stride were misaligned 32-bit
reads on three buffers in four.
Two unit tests cover the two changes. The first replays one frame of a
captured tag and asserts 80 00 30 39; uncorrected those runs total 66
bits, the preamble never aligns, and nothing decodes. The second
alternates that frame with one whose 2H/6L run pair reads as 3H/5L,
carrying 0x80003839 -- each frame decodes on its own, so only frame to
frame disagreement can reject the alternation, and without the ID check
it reads out as 80 00 30 39.
Costs 44 bytes of RAM. On a signal both versions can read, time to a
validated read does not regress: first sync needs 128 bit periods rather
than 104, but the decoder then settles into a clean 64 period cadence
instead of alternating 120 and 8, so validate_count = 6 is satisfied
after 448 bit periods rather than 506.
Also drops KERI_PREAMBLE_DATA_SIZE, whose only use was the old buffer
sizing, and the two commented out bit_lib_get_bit lines copied from
protocol_indala26.c. The latter were not merely dead: the bit 60 test
would have rejected this very tag.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* 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>
* 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>
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>
* 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>
* 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>
protocol_pyramid_add_wiegand_parity() computes the trailing odd-parity
bit with protocol_pyramid_get_parity(source + length / 2, ...). length is
a bit count (24), so source + length / 2 moves the pointer 12 bytes,
but wiegand[] in protocol_pyramid_encode() is 3 bytes. It should read
bits 12..23 of the same buffer.
So the parity bit we transmit comes from stack memory past wiegand[]
instead of the card number. Checked every 8-bit FC against every 16-bit
card number: the bit is wrong for essentially half of them, which a
reader that validates 26-bit Wiegand parity rejects. Our own decoder only
checks the CRC-8 and the format length, so reading a written card back
with a Flipper doesn't show it.
Give get_parity an explicit start position instead of doing pointer
arithmetic with a bit count. The even-parity call keeps reading bits
0..11 and is unchanged.
Signed-off-by: Cole Munz <colemunz@gmail.com>
protocol_pac_stanley_decode() fills an 8-byte asciiCardId[] and hands it
to hex_chars_to_uint8(), which takes no length and loops
while(*value_str && value_str[1]) - it stops at a terminator the buffer
doesn't have. So every successful decode reads asciiCardId[8] and [9] off
the end of the array, and if those two stack bytes happen to be hex
digits it also writes past protocol->data, which is 4 bytes.
Made the buffer 9 bytes and zeroed it. The loop still fills [0..7] and
the parse now stops after exactly 4 bytes, which is what
PAC_STANLEY_DECODED_DATA_SIZE expects.
Signed-off-by: Cole Munz <colemunz@gmail.com>
* [LFRFID]: Fix reading T5577 tags that hold multiple EM4100 IDs
A T5577 configured to emit more than one EM4100 frame per cycle (e.g. two IDs
written by the T5577 Multi Writer app) hung on Read.
The Electra protocol (2024) gave the EM4100 decoder a 64-bit "the frame must be
followed by a header" lookahead to tell EM4100 and Electra apart. That pushed
each decode ~128 bits past the read worker's per-decode reset, so a two-frame
tag locked into rigid A/B alternation and never produced the 3 identical decodes
in a row that Read requires.
Only a 9-bit lookahead is actually needed (Electra's epilogue never begins with
9 ones), so shrink the epilogue register to 9 bits. This restores the
pre-Electra decode timing while keeping EM4100/Electra disambiguation. Also reset
encoded_epilogue in decoder_start, which the original change omitted.
Fixes#1024
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [LFRFID]: Reset encoded_data in PAC/Stanley decoder_start
protocol_pac_stanley_decoder_start reset data/inverted/got_preamble but never
the encoded_data shift buffer - missing since the protocol was added, and unlike
every sibling decoder. Latent in practice (the buffer self-refreshes and
decoding is preamble/parity/checksum-gated), fixed for consistency.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [LFRFID]: Changelog for multi-EM4100 T5577 read fix (#1025)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It comes with the price, but we wanna some new features so...
TODO: Remake this to not use un-defines
Make it use "log level" when making defines instead
- Write-and-set-password screen now shows the target chip "(T5577)" under
the protocol, matching the Write screen for consistency (static literal -
that path always writes a T5577).
- Collapse the per-block pages/blocks/labels parallel arrays into one
steps[] struct-of-rows so page+data+label cannot drift apart; wire output
unchanged.
- Merge lfrfid_worker_write_verify_attempt + _finish into one
verify_and_finish() that returns the loop-stop bool, removing the nested
call duplicated across the three write branches. Behavior is identical
(abort -> stop, match -> fire OK + stop, no-match -> count + keep trying).
- Refresh stale "8265/H5" doc comments left by the rename to "8265".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use the short "8265" name on both the write-progress and success screens
instead of "8265/H5", so the on-screen label is consistent and the success
popup needs no special-case shortening to fit beside the dolphin icon. The
H5 (RU) alias stays documented in the variant enum comment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The cold-reset added at the start of hitagmicro_write called
furi_hal_rfid_tim_read_stop() while the read timer was already stopped (the
worker's verify read and any prior session leave the field off). On TIM2
(APB1) furi_hal_bus_disable() furi_check-asserts the bus is running, so this
crashed on every Hitag write attempt.
The field is already off on entry, so just hold it off with the delay; drop
the unbalanced tim_read_stop()/pins_reset() (those belong only after a
matching tim_read_start()).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix the 2nd-consecutive-write failure (same tag, not moved): a successful
write leaves the chip warm and emitting (its read-back verify holds the
field on, and the post-write latch cycles drive TTF emission), but the next
write's open-loop select chain assumed a cold power-on reset. Add a leading
cold field-off (HITAGMICRO_COLD_RESET_US) at the start of hitagmicro_write
so every session starts from a known cold state.
Review + simplify cleanup:
- pass the LOGIN password as an explicit hitagmicro_write(data, password)
argument instead of a struct field the worker patched in; normalize the
API version to 87.9 (a single bump over dev)
- collapse the variant password/name parallel arrays into one table
- use bit_lib_set_bit/get_bit instead of hand-rolled bit twiddling
- factor the field power-cycle into hitagmicro_field_on/off helpers
- guard the debug frame dump behind the log level
- name the write_chip_name buffer size; one scene status-text helper
- revert the global MAX_UNSUCCESSFUL_READS 10->5 (the too-long timer already
shows the same "Still Trying" popup); fix a stale docstring + comments
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardware logs showed a tag go from emitting its old ID to emitting nothing
and never recover - a partial write corrupting it. Root cause (vs Proxmark):
PM3 validates the select+login response before every write and aborts if it
fails; our open-loop writer can't, so it fires WRITE blindly even when the
tag wasn't actually selected, and a config write that lands while the data
writes don't leaves the tag reconfigured-but-dataless = a brick.
These magic chips are normally already EM4100/TTF-configured, so the data
blocks alone change the emitted ID. Write config LAST instead of first: a
failed/partial run then keeps the previous working config (no brick), and a
fresh tag still gets configured once its data is in place.
Note: this only mitigates bricking; the intermittent commit (chip flakiness)
remains. PM3-level reliability needs closed-loop select validation (RX),
which the Flipper HAL supports but this writer does not yet implement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardware logs showed the data we write is correct (it reads back exactly
when it lands), but the 8265 commits a WRITE only intermittently - matching
Proxmark's note that it "often fails during continuous command execution,
need to retry". The worker already retries whole passes; make each attempt
more likely to land so it succeeds in fewer passes:
- Hold the field on 30ms (was 9ms) after each WRITE so the EEPROM finishes
programming before the field drops.
- Re-send each WRITE twice per session (HITAGMICRO_WRITE_REPEATS),
idempotent, to catch a flaky commit.
Note: closed-loop RX would not help here - the 8265 returns no WRITE ack
(see PM3 hitagu.c), so only the read-back can confirm a commit, which we
already do. The intermittency is the chip; retrying is the mitigation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two issues from hardware testing:
- The "Still Trying to Write..." popup showed the write-progress text
pasted over its dolphin instead of its own message: progress events kept
firing after the warning and overwrote the popup. Guard the write scene
with a flag so progress no longer touches the popup once a warning is up.
- The 8265's first write often failed because TTF emission starts only
after real post-write power cycles; the latch pulses were ~3ms on, too
brief to count. Hold the field 50ms per latch cycle and do 6 of them so
emission starts before the read-back verify (correct variant + no stall).
Also raise LFRFID_WORKER_WRITE_MAX_UNSUCCESSFUL_READS 5 -> 10: it is counted
per verify attempt and one Hitag sweep makes up to 5, so 5 fired the
warning after a single pass.
Note: the 8265 is intermittently write-flaky (Proxmark notes the same and
retries); the worker's retry loop covers occasional first-pass misses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardware test (per-block select) cloned the tag, but the 8265 only began
TTF emission a power cycle later, so its own read-back verify missed it and
the match landed on the NEXT variant tried (wrote 8265 -> showed 8210; wrote
8210 -> showed H5.5), and it wasted a ~2.5s verify timeout each time.
- Latch: after writing all blocks, cycle the field a few times
(HITAGMICRO_LATCH_CYCLES) so the chip starts emitting before the verify.
This makes the correct variant's own verify match -> right label + much
faster for the 8265 family (no more wasted timeout + extra variant pass).
- Labels: success screen shows the model code only - 8265/H5, 8210, H5.5.
- Write progress: the worker now reports each target as it is attempted
(LFRFIDWorkerWriteStartTarget); the write screen shows it under the source
protocol, e.g. [EM4100] / (T5577) / (EM4305) / (8265/H5), so the multi-
attempt sweep is visible instead of looking frozen.
Confirmed earlier (not bugs): block1 last byte 00 is correct for an all-low
id like DEADBEEF00 (column parity is 0); config 05 00 00 00 matches
`lf em 410x clone --htu`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hardware tracing showed the open-loop select chain IS accepted by the
8265 (the tag changed state after our write), but a single select
followed by three writes left the tag emulating nothing - the chip drops
the session after a write, so only the first lands.
Mirror Proxmark3's `lf em 410x clone --htu` exactly: select the tag fresh
with a power cycle before EVERY block, writing config, block0, block1 in
that order. Each iteration powers up, runs the full open-loop select +
login, writes one block, then powers down (HITAGMICRO_POWERDOWN_US) so the
next block re-selects cleanly.
Also confirmed (not a bug): for an all-low id like DEADBEEF00 the EM4100
column parity is genuinely 0, so block1 = FB DF 00 00 is correct; and the
config we send (05 00 00 00) matches `lf em 410x clone --htu`, not the
doc's manual `lf cmdread` recipe (05 80 00 00).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A Proxmark3 `lf hitag htu` clone trace shows the 8265 only honors a WRITE
after a closed-loop select: READ UID -> GET SYSTEM INFO -> READ config ->
LOGIN, with the tag responding to each. Our previous blind LOGIN+WRITE
skipped all of that, so the chip was never selected and ignored us
(writes did nothing on 8265/8210/H5.5).
Send the whole chain open-loop: transmit READ UID, SYSINFO, READ config,
LOGIN, then the three WRITEs, waiting out each (unread) tag response
window between commands (waits derived from the PM3 trace). The tag still
selects/authenticates on receipt; we just can't observe it (no RX).
Frame builders for READ UID / SYSINFO / READ MULTIPLE BLOCK were added and
verified byte-identical to the PM3 trace (e.g. READ UID 22 04 ..,
READ 22 5F ..). Restructured so each frame's bit-banging is its own short
FURI_CRITICAL section and the long inter-frame waits run with interrupts
enabled.
Still open-loop, so unconfirmed on hardware; if this does not take, the
fallbacks are a per-write select (mirroring PM3 exactly) or a true
closed-loop implementation with RX.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Tracing (debug log level) to diagnose Hitag micro writes, which are
open-loop and have no RX to confirm the tag accepted anything:
- hitagmicro_write logs each built frame (LOGIN / WRITE x3) as hex, so the
transmitted bytes can be compared against a Proxmark3 lf hitag htu trace.
- the verify helper logs whether the read-back saw nothing, saw the wrong
data, or matched - distinguishing a write that did not take from a wrong
block/config mapping.
- the worker logs which Hitag variant it is currently writing.
UI: the detected-chip label overlapped the dolphin artwork. Move it into
the icon-free strip to the right of the 91px icon (right-aligned at the
screen edge). Note: popup text uses the thin secondary font; a Widget
would be needed for a bold label.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review fixes:
- Count WriteFobCannotBeWritten per failed verify (new verify_attempt
helper) instead of per pass, so the cannot-write hint stays timely now
that one write pass issues several writes + verifies.
- furi_check the variant password before memcpy (NULL contract).
- Generalize hitagmicro_crc16 offset for byte-aligned input (latent UB).
- Comments: T0-unit clarity, drop non-standard CCITT name, fix 8265/H5
example, inline the perf trade-off note.
Simplify:
- Build all frames before FURI_CRITICAL; only timed modulation stays in
the critical section.
- send_sof reuses hitagmicro_gap(); CRC ref-out reuses
bit_lib_reverse_16_fast (drops the now-unused string.h include).
- write_finish helper dedups the identical T5577/EM4305 verify branches.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add an open-loop writer (lib/lfrfid/tools/hitagmicro.c) that clones an
EM4100 ID onto ID82xx / Hitag micro 8265 magic chips, mirroring
Proxmark3 `lf em 410x clone --htu`. Folded into the existing sequential
Write flow (no UI picker): each target (T5577, EM4305, Hitag micro x3
passwords) is written then verified by read-back, and the chip/variant
that actually took is reported on the success screen.
- New firmware API 87.10: hitagmicro_write, hitagmicro_variant_password,
hitagmicro_variant_name, lfrfid_worker_get_write_chip_name.
- Detection verifies after each write (perf trade-off, see PR notes).
- Open-loop port of a closed-loop PM3 path; a few frame constants
(flags 0x04/0x05, config byte1) still need on-tag validation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Country list header and printf
* off load country list to asset
* lint
* Field specific getters
* check string length before cutting
* lfrfid: minor iso3166 cleanup
---------
Co-authored-by: hedger <hedger@users.noreply.github.com>
Co-authored-by: hedger <hedger@nanode.su>
* libs: stricter constness for saving RAM with .rodata section; fbt: sdk: fixed signature generation for nested const params
* hal: additional fixes for constness in USB subsystem
* debug apps: additional usb-related fixes
* mjs: more consts for token parser
* fatfs: const driver struct
* hal: more consts for ble & nfc vars
* hal: made FuriHalSpiBusHandle static
* hal: made FuriHalI2cBusHandle static
* usb: restored previous api
* linter fixes
* API fixes