mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2026-10-15 06:09:52 +00:00
* 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>
410 lines
12 KiB
C
410 lines
12 KiB
C
#include "ibutton_protocols.h"
|
|
|
|
#include <storage/storage.h>
|
|
|
|
#include "ibutton_key_i.h"
|
|
|
|
#include "protocols/protocol_group_defs.h"
|
|
|
|
#define IBUTTON_FILE_TYPE "Flipper iButton key"
|
|
|
|
#define IBUTTON_PROTOCOL_KEY_V1 "Key type"
|
|
#define IBUTTON_PROTOCOL_KEY_V2 "Protocol"
|
|
|
|
#define IBUTTON_CURRENT_FORMAT_VERSION 2U
|
|
|
|
#define GET_PROTOCOL_GROUP(id) \
|
|
iButtonProtocolGroupInfo info; \
|
|
ibutton_protocols_get_group_by_id(protocols, (id), &info);
|
|
|
|
#define GROUP_BASE (info.base)
|
|
#define GROUP_DATA (info.group)
|
|
#define PROTOCOL_ID (info.id)
|
|
|
|
struct iButtonProtocols {
|
|
iButtonProtocolGroupData** group_datas;
|
|
};
|
|
|
|
typedef struct {
|
|
const iButtonProtocolGroupBase* base;
|
|
iButtonProtocolGroupData* group;
|
|
iButtonProtocolLocalId id;
|
|
} iButtonProtocolGroupInfo;
|
|
|
|
static void ibutton_protocols_get_group_by_id(
|
|
iButtonProtocols* protocols,
|
|
iButtonProtocolId id,
|
|
iButtonProtocolGroupInfo* info) {
|
|
iButtonProtocolLocalId local_id = id;
|
|
|
|
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
|
if(local_id < (signed)ibutton_protocol_groups[i]->protocol_count) {
|
|
info->base = ibutton_protocol_groups[i];
|
|
info->group = protocols->group_datas[i];
|
|
info->id = local_id;
|
|
return;
|
|
|
|
} else {
|
|
local_id -= ibutton_protocol_groups[i]->protocol_count;
|
|
}
|
|
}
|
|
furi_crash();
|
|
}
|
|
|
|
iButtonProtocols* ibutton_protocols_alloc(void) {
|
|
iButtonProtocols* protocols = malloc(sizeof(iButtonProtocols*));
|
|
|
|
protocols->group_datas = malloc(sizeof(iButtonProtocolGroupData*) * iButtonProtocolGroupMax);
|
|
|
|
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
|
protocols->group_datas[i] = ibutton_protocol_groups[i]->alloc();
|
|
}
|
|
|
|
return protocols;
|
|
}
|
|
|
|
void ibutton_protocols_free(iButtonProtocols* protocols) {
|
|
furi_check(protocols);
|
|
|
|
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
|
ibutton_protocol_groups[i]->free(protocols->group_datas[i]);
|
|
}
|
|
|
|
free(protocols->group_datas);
|
|
free(protocols);
|
|
}
|
|
|
|
uint32_t ibutton_protocols_get_protocol_count(void) {
|
|
uint32_t count = 0;
|
|
|
|
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
|
count += ibutton_protocol_groups[i]->protocol_count;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
iButtonProtocolId ibutton_protocols_get_id_by_name(iButtonProtocols* protocols, const char* name) {
|
|
furi_check(protocols);
|
|
furi_check(name);
|
|
|
|
iButtonProtocolLocalId offset = 0;
|
|
|
|
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
|
iButtonProtocolLocalId local_id;
|
|
if(ibutton_protocol_groups[i]->get_id_by_name(protocols->group_datas[i], &local_id, name)) {
|
|
return local_id + offset;
|
|
}
|
|
offset += ibutton_protocol_groups[i]->protocol_count;
|
|
}
|
|
return iButtonProtocolIdInvalid;
|
|
}
|
|
|
|
uint32_t ibutton_protocols_get_features(iButtonProtocols* protocols, iButtonProtocolId id) {
|
|
furi_check(protocols);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
|
|
return GROUP_BASE->get_features(GROUP_DATA, PROTOCOL_ID);
|
|
}
|
|
|
|
size_t ibutton_protocols_get_max_data_size(iButtonProtocols* protocols) {
|
|
furi_check(protocols);
|
|
|
|
size_t max_size = 0;
|
|
|
|
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
|
const size_t current_max_size =
|
|
ibutton_protocol_groups[i]->get_max_data_size(protocols->group_datas[i]);
|
|
if(current_max_size > max_size) {
|
|
max_size = current_max_size;
|
|
}
|
|
}
|
|
|
|
return max_size;
|
|
}
|
|
|
|
const char* ibutton_protocols_get_manufacturer(iButtonProtocols* protocols, iButtonProtocolId id) {
|
|
furi_check(protocols);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
|
|
return GROUP_BASE->get_manufacturer(GROUP_DATA, PROTOCOL_ID);
|
|
}
|
|
|
|
const char* ibutton_protocols_get_name(iButtonProtocols* protocols, iButtonProtocolId id) {
|
|
furi_check(protocols);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
|
|
return GROUP_BASE->get_name(GROUP_DATA, PROTOCOL_ID);
|
|
}
|
|
|
|
bool ibutton_protocols_read(iButtonProtocols* protocols, iButtonKey* key) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
|
|
iButtonProtocolLocalId id = iButtonProtocolIdInvalid;
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
iButtonProtocolLocalId offset = 0;
|
|
for(iButtonProtocolGroupId i = 0; i < iButtonProtocolGroupMax; ++i) {
|
|
if(ibutton_protocol_groups[i]->read(protocols->group_datas[i], data, &id)) {
|
|
id += offset;
|
|
break;
|
|
}
|
|
offset += ibutton_protocol_groups[i]->protocol_count;
|
|
}
|
|
|
|
ibutton_key_set_protocol_id(key, id);
|
|
return id != iButtonProtocolIdInvalid;
|
|
}
|
|
|
|
iButtonWriteTargetMask
|
|
ibutton_protocols_get_write_targets(iButtonProtocols* protocols, iButtonKey* key) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
|
|
if(!GROUP_BASE->get_write_targets) return 0;
|
|
|
|
return GROUP_BASE->get_write_targets(GROUP_DATA, PROTOCOL_ID);
|
|
}
|
|
|
|
// Every blank type the protocol supports, which is what a write did before write targets
|
|
// existed, and the documented meaning of a NULL context.
|
|
static const iButtonWriteTargetContext ibutton_protocols_every_target = {
|
|
.mask = IBUTTON_WRITE_TARGET_MASK_ALL,
|
|
};
|
|
|
|
bool ibutton_protocols_write_id(iButtonProtocols* protocols, iButtonKey* key) {
|
|
return ibutton_protocols_write_id_targets(protocols, key, NULL);
|
|
}
|
|
|
|
bool ibutton_protocols_write_id_targets(
|
|
iButtonProtocols* protocols,
|
|
iButtonKey* key,
|
|
const iButtonWriteTargetContext* write_ctx) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
if(!write_ctx) write_ctx = &ibutton_protocols_every_target;
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
return GROUP_BASE->write_id(GROUP_DATA, data, PROTOCOL_ID, write_ctx);
|
|
}
|
|
|
|
bool ibutton_protocols_write_copy(iButtonProtocols* protocols, iButtonKey* key) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
return GROUP_BASE->write_copy(GROUP_DATA, data, PROTOCOL_ID);
|
|
}
|
|
|
|
void ibutton_protocols_emulate_start(iButtonProtocols* protocols, iButtonKey* key) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->emulate_start(GROUP_DATA, data, PROTOCOL_ID);
|
|
}
|
|
|
|
void ibutton_protocols_emulate_stop(iButtonProtocols* protocols, iButtonKey* key) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->emulate_stop(GROUP_DATA, data, PROTOCOL_ID);
|
|
}
|
|
|
|
bool ibutton_protocols_save(
|
|
iButtonProtocols* protocols,
|
|
const iButtonKey* key,
|
|
const char* file_name) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
furi_check(file_name);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
bool success = false;
|
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
|
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
|
|
|
do {
|
|
const char* protocol_name = ibutton_protocols_get_name(protocols, id);
|
|
|
|
if(!flipper_format_buffered_file_open_always(ff, file_name)) break;
|
|
|
|
if(!flipper_format_write_header_cstr(ff, IBUTTON_FILE_TYPE, IBUTTON_CURRENT_FORMAT_VERSION))
|
|
break;
|
|
if(!flipper_format_write_string_cstr(ff, IBUTTON_PROTOCOL_KEY_V2, protocol_name)) break;
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
if(!GROUP_BASE->save(GROUP_DATA, data, PROTOCOL_ID, ff)) break;
|
|
|
|
success = true;
|
|
} while(false);
|
|
|
|
flipper_format_free(ff);
|
|
furi_record_close(RECORD_STORAGE);
|
|
|
|
return success;
|
|
}
|
|
|
|
bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
furi_check(file_name);
|
|
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
bool success = false;
|
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
|
|
|
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
|
|
FuriString* tmp = furi_string_alloc();
|
|
|
|
do {
|
|
if(!flipper_format_buffered_file_open_existing(ff, file_name)) break;
|
|
|
|
uint32_t version;
|
|
|
|
if(!flipper_format_read_header(ff, tmp, &version)) break;
|
|
if(!furi_string_equal(tmp, IBUTTON_FILE_TYPE)) break;
|
|
|
|
if(version == 1) {
|
|
if(!flipper_format_read_string(ff, IBUTTON_PROTOCOL_KEY_V1, tmp)) break;
|
|
} else if(version == 2) {
|
|
if(!flipper_format_read_string(ff, IBUTTON_PROTOCOL_KEY_V2, tmp)) break;
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
const iButtonProtocolId id =
|
|
ibutton_protocols_get_id_by_name(protocols, furi_string_get_cstr(tmp));
|
|
ibutton_key_set_protocol_id(key, id);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
if(!GROUP_BASE->load(GROUP_DATA, data, PROTOCOL_ID, version, ff)) break;
|
|
|
|
success = true;
|
|
} while(false);
|
|
|
|
flipper_format_free(ff);
|
|
furi_string_free(tmp);
|
|
furi_record_close(RECORD_STORAGE);
|
|
|
|
return success;
|
|
}
|
|
|
|
void ibutton_protocols_render_uid(
|
|
iButtonProtocols* protocols,
|
|
const iButtonKey* key,
|
|
FuriString* result) {
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->render_uid(GROUP_DATA, data, PROTOCOL_ID, result);
|
|
}
|
|
|
|
void ibutton_protocols_render_data(
|
|
iButtonProtocols* protocols,
|
|
const iButtonKey* key,
|
|
FuriString* result) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
furi_check(result);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->render_data(GROUP_DATA, data, PROTOCOL_ID, result);
|
|
}
|
|
|
|
void ibutton_protocols_render_brief_data(
|
|
iButtonProtocols* protocols,
|
|
const iButtonKey* key,
|
|
FuriString* result) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
furi_check(result);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->render_brief_data(GROUP_DATA, data, PROTOCOL_ID, result);
|
|
}
|
|
|
|
void ibutton_protocols_render_error(
|
|
iButtonProtocols* protocols,
|
|
const iButtonKey* key,
|
|
FuriString* result) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
furi_check(result);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->render_error(GROUP_DATA, data, PROTOCOL_ID, result);
|
|
}
|
|
|
|
bool ibutton_protocols_is_valid(iButtonProtocols* protocols, const iButtonKey* key) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
const iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
return GROUP_BASE->is_valid(GROUP_DATA, data, PROTOCOL_ID);
|
|
}
|
|
|
|
void ibutton_protocols_get_editable_data(
|
|
iButtonProtocols* protocols,
|
|
const iButtonKey* key,
|
|
iButtonEditableData* editable) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
furi_check(editable);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->get_editable_data(GROUP_DATA, data, PROTOCOL_ID, editable);
|
|
}
|
|
|
|
void ibutton_protocols_apply_edits(iButtonProtocols* protocols, const iButtonKey* key) {
|
|
furi_check(protocols);
|
|
furi_check(key);
|
|
|
|
const iButtonProtocolId id = ibutton_key_get_protocol_id(key);
|
|
iButtonProtocolData* data = ibutton_key_get_protocol_data(key);
|
|
|
|
GET_PROTOCOL_GROUP(id);
|
|
GROUP_BASE->apply_edits(GROUP_DATA, data, PROTOCOL_ID);
|
|
}
|