mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-13 13:09:49 +04:00
Merge branch 'dev' into gsurkov/3764_expansion_control
This commit is contained in:
@@ -110,6 +110,15 @@ App(
|
||||
sources=["plugins/supported_cards/umarsh.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="clipper_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="clipper_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/clipper.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="hid_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
@@ -119,6 +128,15 @@ App(
|
||||
sources=["plugins/supported_cards/hid.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="itso_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="itso_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/itso.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="nfc_start",
|
||||
targets=["f7"],
|
||||
|
||||
@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) {
|
||||
const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
@@ -58,8 +59,8 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) {
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEdit) {
|
||||
static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) {
|
||||
const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
@@ -95,8 +97,8 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) {
|
||||
instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance);
|
||||
}
|
||||
|
||||
static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEmulate) {
|
||||
static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,17 @@ void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const d
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str) {
|
||||
const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3';
|
||||
furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-A)\n", iso_type);
|
||||
}
|
||||
|
||||
void nfc_render_iso14443_3a_info(
|
||||
const Iso14443_3aData* data,
|
||||
NfcProtocolFormatType format_type,
|
||||
FuriString* str) {
|
||||
if(format_type == NfcProtocolFormatTypeFull) {
|
||||
const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3';
|
||||
furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type);
|
||||
nfc_render_iso14443_tech_type(data, str);
|
||||
}
|
||||
|
||||
nfc_render_iso14443_3a_brief(data, str);
|
||||
@@ -30,5 +34,5 @@ void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str)
|
||||
|
||||
void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) {
|
||||
furi_string_cat_printf(str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]);
|
||||
furi_string_cat_printf(str, "SAK: %02X", data->sak);
|
||||
furi_string_cat_printf(str, "\nSAK: %02X", data->sak);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ void nfc_render_iso14443_3a_info(
|
||||
NfcProtocolFormatType format_type,
|
||||
FuriString* str);
|
||||
|
||||
void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str);
|
||||
|
||||
void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size);
|
||||
|
||||
void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str);
|
||||
|
||||
@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) {
|
||||
const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
@@ -59,8 +60,8 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) {
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEdit) {
|
||||
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
||||
return true;
|
||||
}
|
||||
@@ -68,7 +69,7 @@ bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) {
|
||||
static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) {
|
||||
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
|
||||
#include "iso14443_3b.h"
|
||||
|
||||
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event);
|
||||
bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event);
|
||||
|
||||
@@ -6,7 +6,7 @@ void nfc_render_iso14443_3b_info(
|
||||
FuriString* str) {
|
||||
if(format_type == NfcProtocolFormatTypeFull) {
|
||||
const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3';
|
||||
furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type);
|
||||
furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-B)\n", iso_type);
|
||||
}
|
||||
|
||||
furi_string_cat_printf(str, "UID:");
|
||||
@@ -20,7 +20,7 @@ void nfc_render_iso14443_3b_info(
|
||||
|
||||
if(format_type != NfcProtocolFormatTypeFull) return;
|
||||
|
||||
furi_string_cat_printf(str, "\n\e#Protocol info\n");
|
||||
furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n");
|
||||
|
||||
if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) {
|
||||
furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n");
|
||||
@@ -68,7 +68,7 @@ void nfc_render_iso14443_3b_info(
|
||||
iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not ";
|
||||
furi_string_cat_printf(str, "CID: %ssupported", cid_support_str);
|
||||
|
||||
furi_string_cat_printf(str, "\n\e#Application data\nRaw:");
|
||||
furi_string_cat_printf(str, "\n::::::::::::[Application data]::::::::::::\nRaw:");
|
||||
|
||||
size_t app_data_size;
|
||||
const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size);
|
||||
|
||||
@@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) {
|
||||
const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
@@ -99,8 +100,8 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) {
|
||||
instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
|
||||
}
|
||||
|
||||
static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEmulate) {
|
||||
static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ void nfc_render_iso14443_4a_info(
|
||||
}
|
||||
|
||||
void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) {
|
||||
nfc_render_iso14443_tech_type(iso14443_4a_get_base_data(data), str);
|
||||
nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str);
|
||||
}
|
||||
|
||||
void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) {
|
||||
furi_string_cat_printf(str, "\n\e#Protocol info\n");
|
||||
furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n");
|
||||
|
||||
if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) {
|
||||
furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n");
|
||||
@@ -72,7 +73,7 @@ void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str)
|
||||
const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count);
|
||||
|
||||
if(hist_bytes_count > 0) {
|
||||
furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:");
|
||||
furi_string_cat_printf(str, "\n:::::::::::::[Historical bytes]:::::::::::::\nRaw:");
|
||||
|
||||
for(size_t i = 0; i < hist_bytes_count; ++i) {
|
||||
furi_string_cat_printf(str, " %02X", hist_bytes[i]);
|
||||
|
||||
@@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) {
|
||||
const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
@@ -64,8 +65,8 @@ static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) {
|
||||
UNUSED(instance);
|
||||
}
|
||||
|
||||
static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEmulate) {
|
||||
static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
|
||||
return true;
|
||||
}
|
||||
@@ -73,7 +74,7 @@ static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
|
||||
static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) {
|
||||
return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) {
|
||||
const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
|
||||
widget_reset(instance->widget);
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
|
||||
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static void nfc_scene_more_info_on_enter_iso15693_3(NfcApp* instance) {
|
||||
const NfcDevice* device = instance->nfc_device;
|
||||
const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_render_iso15693_3_system_info(data, temp_str);
|
||||
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso15693_3);
|
||||
|
||||
@@ -94,8 +108,8 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) {
|
||||
instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance);
|
||||
}
|
||||
|
||||
static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEdit) {
|
||||
static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
||||
return true;
|
||||
}
|
||||
@@ -104,13 +118,19 @@ static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid,
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid |
|
||||
NfcProtocolFeatureMoreInfo,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
.on_enter = nfc_scene_info_on_enter_iso15693_3,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_more_info =
|
||||
{
|
||||
.on_enter = nfc_scene_more_info_on_enter_iso15693_3,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_read =
|
||||
{
|
||||
.on_enter = nfc_scene_read_on_enter_iso15693_3,
|
||||
|
||||
@@ -36,32 +36,7 @@ void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) {
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
|
||||
furi_string_cat(str, "\n\e#General info\n");
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
|
||||
furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref);
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
|
||||
furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi);
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
|
||||
furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref);
|
||||
}
|
||||
|
||||
furi_string_cat(str, "\e#Lock bits\n");
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
|
||||
furi_string_cat_printf(
|
||||
str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
|
||||
furi_string_cat_printf(
|
||||
str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
|
||||
}
|
||||
|
||||
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) {
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
|
||||
furi_string_cat(str, "\e#Memory data\n\e*--------------------\n");
|
||||
|
||||
@@ -88,5 +63,34 @@ void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
|
||||
"(Data is too big. Showing only the first %u bytes.)",
|
||||
display_block_count * block_size);
|
||||
}
|
||||
} else {
|
||||
furi_string_cat(str, "\e#No available data\n");
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
|
||||
furi_string_cat(str, "\n::::::::::::::::[General info]:::::::::::::::::\n");
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
|
||||
furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref);
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
|
||||
furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi);
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
|
||||
furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref);
|
||||
}
|
||||
|
||||
furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n");
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
|
||||
furi_string_cat_printf(
|
||||
str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
|
||||
}
|
||||
|
||||
if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
|
||||
furi_string_cat_printf(
|
||||
str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,3 +12,5 @@ void nfc_render_iso15693_3_info(
|
||||
void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str);
|
||||
|
||||
void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str);
|
||||
|
||||
void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str);
|
||||
|
||||
@@ -14,6 +14,7 @@ enum {
|
||||
SubmenuIndexDetectReader = SubmenuIndexCommonMax,
|
||||
SubmenuIndexWrite,
|
||||
SubmenuIndexUpdate,
|
||||
SubmenuIndexDictAttack
|
||||
};
|
||||
|
||||
static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
|
||||
@@ -21,6 +22,7 @@ static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
furi_string_replace(temp_str, "Mifare", "MIFARE");
|
||||
@@ -99,8 +101,9 @@ static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) {
|
||||
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance);
|
||||
}
|
||||
|
||||
static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) {
|
||||
if(event == NfcCustomEventPollerIncomplete) {
|
||||
static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom &&
|
||||
event.event == NfcCustomEventPollerIncomplete) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
}
|
||||
|
||||
@@ -118,6 +121,13 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) {
|
||||
SubmenuIndexDetectReader,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Unlock with Dictionary",
|
||||
SubmenuIndexDictAttack,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +159,13 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) {
|
||||
SubmenuIndexDetectReader,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Unlock with Dictionary",
|
||||
SubmenuIndexDictAttack,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
}
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
@@ -156,6 +173,7 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) {
|
||||
SubmenuIndexWrite,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Update from Initial Card",
|
||||
@@ -170,37 +188,49 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) {
|
||||
nfc_listener_start(instance->listener, NULL, NULL);
|
||||
}
|
||||
|
||||
static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexDetectReader) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
|
||||
dolphin_deed(DolphinDeedNfcDetectReader);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
|
||||
static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event == SubmenuIndexDetectReader) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
|
||||
consumed = true;
|
||||
} else if(event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
|
||||
consumed = true;
|
||||
} else if(event == SubmenuIndexUpdate) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
|
||||
consumed = true;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexDetectReader) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm);
|
||||
dolphin_deed(DolphinDeedNfcDetectReader);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexDictAttack) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) {
|
||||
static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event == NfcCustomEventTextInputDone) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexDetectReader) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexUpdate) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexDictAttack) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == NfcCustomEventTextInputDone) {
|
||||
mf_classic_key_cache_save(
|
||||
instance->mfc_key_cache,
|
||||
nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic));
|
||||
|
||||
@@ -18,13 +18,48 @@ void nfc_render_mf_classic_info(
|
||||
furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total);
|
||||
}
|
||||
|
||||
static void
|
||||
mf_classic_render_raw_data(const uint8_t* data, size_t size, bool data_read, FuriString* str) {
|
||||
furi_assert((size % 2) == 0);
|
||||
|
||||
for(size_t i = 0; i < size; i += 2) {
|
||||
if(data_read) {
|
||||
furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]);
|
||||
} else {
|
||||
furi_string_cat_printf(str, "???? ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
mf_classic_render_block(const MfClassicData* data, uint8_t block_num, FuriString* str) {
|
||||
if(mf_classic_is_sector_trailer(block_num)) {
|
||||
uint8_t sec_num = mf_classic_get_sector_by_block(block_num);
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num);
|
||||
|
||||
// Render key A
|
||||
bool key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeA);
|
||||
mf_classic_render_raw_data(sec_tr->key_a.data, sizeof(MfClassicKey), key_read, str);
|
||||
|
||||
// Render access bits
|
||||
bool access_bits_read = mf_classic_is_block_read(data, block_num);
|
||||
mf_classic_render_raw_data(
|
||||
sec_tr->access_bits.data, sizeof(MfClassicAccessBits), access_bits_read, str);
|
||||
|
||||
// Render key B
|
||||
key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeB);
|
||||
mf_classic_render_raw_data(sec_tr->key_b.data, sizeof(MfClassicKey), key_read, str);
|
||||
} else {
|
||||
const uint8_t* block_data = data->block[block_num].data;
|
||||
bool block_read = mf_classic_is_block_read(data, block_num);
|
||||
mf_classic_render_raw_data(block_data, sizeof(MfClassicBlock), block_read, str);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) {
|
||||
uint16_t total_blocks = mf_classic_get_total_block_num(data->type);
|
||||
|
||||
for(size_t i = 0; i < total_blocks; i++) {
|
||||
for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) {
|
||||
furi_string_cat_printf(
|
||||
str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]);
|
||||
}
|
||||
mf_classic_render_block(data, i, str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ static void nfc_scene_info_on_enter_mf_desfire(NfcApp* instance) {
|
||||
const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
furi_string_replace(temp_str, "Mifare", "MIFARE");
|
||||
nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
|
||||
widget_add_text_scroll_element(
|
||||
@@ -56,6 +58,7 @@ static void nfc_scene_read_success_on_enter_mf_desfire(NfcApp* instance) {
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
furi_string_replace(temp_str, "Mifare", "MIFARE");
|
||||
nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeShort, temp_str);
|
||||
|
||||
widget_add_text_scroll_element(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "mf_ultralight_render.h"
|
||||
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
|
||||
#include <toolbox/pretty_format.h>
|
||||
|
||||
#include "nfc/nfc_app_i.h"
|
||||
|
||||
@@ -15,11 +16,18 @@ enum {
|
||||
SubmenuIndexWrite,
|
||||
};
|
||||
|
||||
enum {
|
||||
NfcSceneMoreInfoStateASCII,
|
||||
NfcSceneMoreInfoStateRawData,
|
||||
};
|
||||
|
||||
static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) {
|
||||
const NfcDevice* device = instance->nfc_device;
|
||||
const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
@@ -35,11 +43,62 @@ static void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) {
|
||||
const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight);
|
||||
|
||||
furi_string_reset(instance->text_box_store);
|
||||
nfc_render_mf_ultralight_dump(mfu, instance->text_box_store);
|
||||
uint32_t scene_state =
|
||||
scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo);
|
||||
|
||||
text_box_set_font(instance->text_box, TextBoxFontHex);
|
||||
text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
|
||||
view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
|
||||
if(scene_state == NfcSceneMoreInfoStateASCII) {
|
||||
pretty_format_bytes_hex_canonical(
|
||||
instance->text_box_store,
|
||||
MF_ULTRALIGHT_PAGE_SIZE,
|
||||
PRETTY_FORMAT_FONT_MONOSPACE,
|
||||
(uint8_t*)mfu->page,
|
||||
mfu->pages_read * MF_ULTRALIGHT_PAGE_SIZE);
|
||||
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store));
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Raw Data",
|
||||
nfc_protocol_support_common_widget_callback,
|
||||
instance);
|
||||
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Info",
|
||||
nfc_protocol_support_common_widget_callback,
|
||||
instance);
|
||||
} else if(scene_state == NfcSceneMoreInfoStateRawData) {
|
||||
nfc_render_mf_ultralight_dump(mfu, instance->text_box_store);
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store));
|
||||
|
||||
widget_add_button_element(
|
||||
instance->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"ASCII",
|
||||
nfc_protocol_support_common_widget_callback,
|
||||
instance);
|
||||
}
|
||||
}
|
||||
|
||||
static bool nfc_scene_more_info_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) ||
|
||||
(event.type == SceneManagerEventTypeBack)) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII);
|
||||
scene_manager_previous_scene(instance->scene_manager);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData);
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static NfcCommand
|
||||
@@ -132,15 +191,17 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) {
|
||||
nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance);
|
||||
}
|
||||
|
||||
bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, uint32_t event) {
|
||||
if(event == NfcCustomEventCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound);
|
||||
nfc_scene_read_setup_view(instance);
|
||||
} else if((event == NfcCustomEventPollerIncomplete)) {
|
||||
notification_message(instance->notifications, &sequence_semi_success);
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound);
|
||||
nfc_scene_read_setup_view(instance);
|
||||
} else if((event.event == NfcCustomEventPollerIncomplete)) {
|
||||
notification_message(instance->notifications, &sequence_semi_success);
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
|
||||
dolphin_deed(DolphinDeedNfcReadSuccess);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -202,14 +263,17 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) {
|
||||
nfc_listener_start(instance->listener, NULL, NULL);
|
||||
}
|
||||
|
||||
static bool
|
||||
nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexUnlock) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
|
||||
return true;
|
||||
} else if(event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
|
||||
return true;
|
||||
static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
|
||||
NfcApp* instance,
|
||||
SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexUnlock) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexWrite) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -225,7 +289,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {
|
||||
.scene_more_info =
|
||||
{
|
||||
.on_enter = nfc_scene_more_info_on_enter_mf_ultralight,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
.on_event = nfc_scene_more_info_on_event_mf_ultralight,
|
||||
},
|
||||
.scene_read =
|
||||
{
|
||||
|
||||
@@ -36,10 +36,11 @@ void nfc_render_mf_ultralight_info(
|
||||
}
|
||||
|
||||
void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) {
|
||||
furi_string_cat_printf(str, "\e*");
|
||||
for(size_t i = 0; i < data->pages_read; i++) {
|
||||
const uint8_t* page_data = data->page[i].data;
|
||||
for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) {
|
||||
furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]);
|
||||
furi_string_cat_printf(str, " %02X%02X", page_data[j], page_data[j + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context)
|
||||
nfc_protocol_support_scenes[scene].on_exit(instance);
|
||||
}
|
||||
|
||||
static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
|
||||
bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
|
||||
return nfc_protocol_support[protocol]->features & feature;
|
||||
}
|
||||
|
||||
@@ -131,16 +131,15 @@ static bool
|
||||
nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) {
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
|
||||
consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event);
|
||||
}
|
||||
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
|
||||
consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) {
|
||||
text_box_reset(instance->text_box);
|
||||
widget_reset(instance->widget);
|
||||
furi_string_reset(instance->text_box_store);
|
||||
}
|
||||
|
||||
@@ -188,8 +187,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
|
||||
} else {
|
||||
const NfcProtocol protocol =
|
||||
instance->protocols_detected[instance->protocols_detected_selected_idx];
|
||||
consumed =
|
||||
nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event);
|
||||
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
|
||||
}
|
||||
} else if(event.event == NfcCustomEventPollerFailure) {
|
||||
nfc_poller_stop(instance->poller);
|
||||
@@ -202,7 +200,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana
|
||||
} else if(event.event == NfcCustomEventCardDetected) {
|
||||
const NfcProtocol protocol =
|
||||
instance->protocols_detected[instance->protocols_detected_selected_idx];
|
||||
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event);
|
||||
consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
nfc_poller_stop(instance->poller);
|
||||
@@ -287,8 +285,7 @@ static bool
|
||||
consumed = true;
|
||||
} else {
|
||||
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
|
||||
consumed =
|
||||
nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event);
|
||||
consumed = nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event);
|
||||
}
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
@@ -456,8 +453,7 @@ static bool
|
||||
consumed = true;
|
||||
} else {
|
||||
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
|
||||
consumed =
|
||||
nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event);
|
||||
consumed = nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event);
|
||||
}
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
@@ -521,10 +517,10 @@ static bool
|
||||
scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType) ?
|
||||
DolphinDeedNfcAddSave :
|
||||
DolphinDeedNfcSave);
|
||||
const NfcProtocol protocol =
|
||||
instance->protocols_detected[instance->protocols_detected_selected_idx];
|
||||
consumed = nfc_protocol_support[protocol]->scene_save_name.on_event(
|
||||
instance, event.event);
|
||||
|
||||
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
|
||||
consumed =
|
||||
nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event);
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
instance->scene_manager, NfcSceneStart);
|
||||
@@ -565,11 +561,11 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
|
||||
|
||||
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
|
||||
widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64);
|
||||
|
||||
if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
|
||||
widget_add_string_element(
|
||||
widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID");
|
||||
widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating UID");
|
||||
|
||||
size_t uid_len;
|
||||
const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
|
||||
@@ -581,7 +577,8 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
|
||||
furi_string_trim(temp_str);
|
||||
|
||||
} else {
|
||||
widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
|
||||
widget_add_string_element(
|
||||
widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating");
|
||||
if(!furi_string_empty(instance->file_name)) {
|
||||
furi_string_set(temp_str, instance->file_name);
|
||||
} else {
|
||||
@@ -589,11 +586,12 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
|
||||
temp_str,
|
||||
"Unsaved\n%s",
|
||||
nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
|
||||
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
|
||||
}
|
||||
}
|
||||
|
||||
widget_add_text_box_element(
|
||||
widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
|
||||
widget, 56, 33, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
#include <lib/nfc/protocols/nfc_protocol.h>
|
||||
|
||||
#include "nfc_protocol_support_common.h"
|
||||
|
||||
@@ -111,3 +112,5 @@ bool nfc_protocol_support_on_event(
|
||||
* @param[in,out] context pointer to a user-specified context (will be passed to concrete handler).
|
||||
*/
|
||||
void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context);
|
||||
|
||||
bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature);
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <core/string.h>
|
||||
|
||||
#include "../../nfc_app.h"
|
||||
#include "../../nfc_app_i.h"
|
||||
|
||||
/**
|
||||
* @brief Scene entry handler.
|
||||
@@ -19,10 +20,10 @@ typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance);
|
||||
* @brief Scene event handler.
|
||||
*
|
||||
* @param[in,out] instance pointer to the NFC application instance.
|
||||
* @param[in] event custom event that has occurred.
|
||||
* @param[in] event scene manager event that has occurred.
|
||||
* @returns true if the event was handled, false otherwise.
|
||||
*/
|
||||
typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event);
|
||||
typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, SceneManagerEvent event);
|
||||
|
||||
/**
|
||||
* @brief Abstract scene interface.
|
||||
|
||||
@@ -35,8 +35,8 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) {
|
||||
UNUSED(instance);
|
||||
}
|
||||
|
||||
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) {
|
||||
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event) {
|
||||
UNUSED(instance);
|
||||
UNUSED(event);
|
||||
return true;
|
||||
return event.type != SceneManagerEventTypeBack;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <gui/modules/widget.h>
|
||||
|
||||
#include "nfc/nfc_app.h"
|
||||
#include "nfc/nfc_app_i.h"
|
||||
|
||||
/**
|
||||
* @brief Common submenu indices.
|
||||
@@ -82,4 +83,4 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance);
|
||||
* @param[in] event custom event type that has occurred.
|
||||
* @returns always true.
|
||||
*/
|
||||
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event);
|
||||
bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event);
|
||||
|
||||
@@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_slix(NfcApp* instance) {
|
||||
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
|
||||
widget_reset(instance->widget);
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
|
||||
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static void nfc_scene_more_info_on_enter_slix(NfcApp* instance) {
|
||||
const NfcDevice* device = instance->nfc_device;
|
||||
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_render_iso15693_3_system_info(slix_get_base_data(data), temp_str);
|
||||
|
||||
widget_add_text_scroll_element(
|
||||
instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolSlix);
|
||||
|
||||
@@ -91,8 +105,8 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) {
|
||||
nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance);
|
||||
}
|
||||
|
||||
static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEdit) {
|
||||
static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
||||
return true;
|
||||
}
|
||||
@@ -101,13 +115,18 @@ static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event)
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_slix = {
|
||||
.features = NfcProtocolFeatureEmulateFull,
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
.on_enter = nfc_scene_info_on_enter_slix,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_more_info =
|
||||
{
|
||||
.on_enter = nfc_scene_more_info_on_enter_slix,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
.scene_read =
|
||||
{
|
||||
.on_enter = nfc_scene_read_on_enter_slix,
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
#include "slix_render.h"
|
||||
|
||||
#include "../iso15693_3/iso15693_3_render.h"
|
||||
|
||||
void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) {
|
||||
nfc_render_iso15693_3_brief(slix_get_base_data(data), str);
|
||||
|
||||
if(format_type != NfcProtocolFormatTypeFull) return;
|
||||
const SlixType slix_type = slix_get_type(data);
|
||||
|
||||
furi_string_cat(str, "\n\e#Passwords\n");
|
||||
furi_string_cat(str, "\n::::::::::::::::::[Passwords]:::::::::::::::::\n");
|
||||
|
||||
static const char* slix_password_names[] = {
|
||||
"Read",
|
||||
@@ -25,7 +23,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_cat(str, "\e#Lock bits\n");
|
||||
furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n");
|
||||
|
||||
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) {
|
||||
furi_string_cat_printf(
|
||||
@@ -38,7 +36,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
|
||||
|
||||
const SlixProtection protection = data->system_info.protection;
|
||||
|
||||
furi_string_cat(str, "\e#Page protection\n");
|
||||
furi_string_cat(str, "::::::::::::[Page protection]::::::::::::\n");
|
||||
furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer);
|
||||
|
||||
const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un";
|
||||
@@ -52,12 +50,12 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ
|
||||
}
|
||||
|
||||
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) {
|
||||
furi_string_cat(str, "\e#Privacy\n");
|
||||
furi_string_cat(str, "::::::::::::::::::::[Privacy]::::::::::::::::::::::\n");
|
||||
furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis");
|
||||
}
|
||||
|
||||
if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) {
|
||||
furi_string_cat(str, "\e#Signature\n");
|
||||
furi_string_cat(str, ":::::::::::::::::::[Signature]::::::::::::::::::\n");
|
||||
for(uint32_t i = 0; i < 4; ++i) {
|
||||
furi_string_cat_printf(str, "%02X ", data->signature[i]);
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
#include <nfc/protocols/slix/slix.h>
|
||||
|
||||
#include "../nfc_protocol_support_render_common.h"
|
||||
#include "../iso15693_3/iso15693_3_render.h"
|
||||
|
||||
void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str);
|
||||
|
||||
@@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) {
|
||||
const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
nfc_append_filename_string_when_present(instance, temp_str);
|
||||
furi_string_cat_printf(
|
||||
temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
|
||||
nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str);
|
||||
@@ -60,8 +61,8 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) {
|
||||
furi_string_free(temp_str);
|
||||
}
|
||||
|
||||
static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) {
|
||||
if(event == SubmenuIndexCommonEdit) {
|
||||
static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) {
|
||||
if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) {
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "nfc_app_i.h"
|
||||
#include "helpers/protocol_support/nfc_protocol_support.h"
|
||||
|
||||
#include <dolphin/dolphin.h>
|
||||
|
||||
@@ -445,6 +446,15 @@ void nfc_app_reset_detected_protocols(NfcApp* instance) {
|
||||
instance->protocols_detected_num = 0;
|
||||
}
|
||||
|
||||
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string) {
|
||||
furi_assert(instance);
|
||||
furi_assert(string);
|
||||
|
||||
if(!furi_string_empty(instance->file_name)) {
|
||||
furi_string_cat_printf(string, "Name:%s\n", furi_string_get_cstr(instance->file_name));
|
||||
}
|
||||
}
|
||||
|
||||
static bool nfc_is_hal_ready() {
|
||||
if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
|
||||
// No connection to the chip, show an error screen
|
||||
@@ -466,6 +476,15 @@ static bool nfc_is_hal_ready() {
|
||||
}
|
||||
}
|
||||
|
||||
static void nfc_show_initial_scene_for_device(NfcApp* nfc) {
|
||||
NfcProtocol prot = nfc_device_get_protocol(nfc->nfc_device);
|
||||
uint32_t scene = nfc_protocol_support_has_feature(
|
||||
prot, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ?
|
||||
NfcSceneEmulate :
|
||||
NfcSceneSavedMenu;
|
||||
scene_manager_next_scene(nfc->scene_manager, scene);
|
||||
}
|
||||
|
||||
int32_t nfc_app(void* p) {
|
||||
if(!nfc_is_hal_ready()) return 0;
|
||||
|
||||
@@ -485,7 +504,7 @@ int32_t nfc_app(void* p) {
|
||||
|
||||
furi_string_set(nfc->file_path, args);
|
||||
if(nfc_load_file(nfc, nfc->file_path, false)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate);
|
||||
nfc_show_initial_scene_for_device(nfc);
|
||||
} else {
|
||||
view_dispatcher_stop(nfc->view_dispatcher);
|
||||
}
|
||||
|
||||
@@ -192,3 +192,5 @@ void nfc_make_app_folder(NfcApp* instance);
|
||||
void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count);
|
||||
|
||||
void nfc_app_reset_detected_protocols(NfcApp* instance);
|
||||
|
||||
void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string);
|
||||
|
||||
@@ -60,7 +60,7 @@ static bool aime_read(Nfc* nfc, NfcDevice* device) {
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
621
applications/main/nfc/plugins/supported_cards/clipper.c
Normal file
621
applications/main/nfc/plugins/supported_cards/clipper.c
Normal file
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
* clipper.c - Parser for Clipper cards (San Francisco, California).
|
||||
*
|
||||
* Based on research, some of which dates to 2007!
|
||||
*
|
||||
* Copyright 2024 Jeremy Cooper <jeremy.gthb@baymoo.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
|
||||
#include <lib/nfc/helpers/nfc_util.h>
|
||||
#include <applications/services/locale/locale.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
//
|
||||
// Table of application ids observed in the wild, and their sources.
|
||||
//
|
||||
static const struct {
|
||||
const MfDesfireApplicationId app;
|
||||
const char* type;
|
||||
} clipper_types[] = {
|
||||
// Application advertised on classic, plastic cards.
|
||||
{.app = {.data = {0x90, 0x11, 0xf2}}, .type = "Card"},
|
||||
// Application advertised on a mobile device.
|
||||
{.app = {.data = {0x91, 0x11, 0xf2}}, .type = "Mobile Device"},
|
||||
};
|
||||
static const size_t kNumCardTypes = sizeof(clipper_types) / sizeof(clipper_types[0]);
|
||||
|
||||
struct IdMapping_struct {
|
||||
uint16_t id;
|
||||
const char* name;
|
||||
};
|
||||
typedef struct IdMapping_struct IdMapping;
|
||||
|
||||
#define COUNT(_array) sizeof(_array) / sizeof(_array[0])
|
||||
|
||||
//
|
||||
// Known transportation agencies and their identifiers.
|
||||
//
|
||||
static const IdMapping agency_names[] = {
|
||||
{.id = 0x0001, .name = "AC Transit"},
|
||||
{.id = 0x0004, .name = "BART"},
|
||||
{.id = 0x0006, .name = "Caltrain"},
|
||||
{.id = 0x0008, .name = "CCTA"},
|
||||
{.id = 0x000b, .name = "GGT"},
|
||||
{.id = 0x000f, .name = "SamTrans"},
|
||||
{.id = 0x0011, .name = "VTA"},
|
||||
{.id = 0x0012, .name = "Muni"},
|
||||
{.id = 0x0019, .name = "GG Ferry"},
|
||||
{.id = 0x001b, .name = "SF Bay Ferry"},
|
||||
};
|
||||
static const size_t kNumAgencies = COUNT(agency_names);
|
||||
|
||||
//
|
||||
// Known station names for various agencies.
|
||||
//
|
||||
static const IdMapping bart_zones[] = {
|
||||
{.id = 0x0001, .name = "Colma"},
|
||||
{.id = 0x0002, .name = "Daly City"},
|
||||
{.id = 0x0003, .name = "Balboa Park"},
|
||||
{.id = 0x0004, .name = "Glen Park"},
|
||||
{.id = 0x0005, .name = "24th St Mission"},
|
||||
{.id = 0x0006, .name = "16th St Mission"},
|
||||
{.id = 0x0007, .name = "Civic Center/UN Plaza"},
|
||||
{.id = 0x0008, .name = "Powell St"},
|
||||
{.id = 0x0009, .name = "Montgomery St"},
|
||||
{.id = 0x000a, .name = "Embarcadero"},
|
||||
{.id = 0x000b, .name = "West Oakland"},
|
||||
{.id = 0x000c, .name = "12th St/Oakland City Center"},
|
||||
{.id = 0x000d, .name = "19th St/Oakland"},
|
||||
{.id = 0x000e, .name = "MacArthur"},
|
||||
{.id = 0x000f, .name = "Rockridge"},
|
||||
{.id = 0x0010, .name = "Orinda"},
|
||||
{.id = 0x0011, .name = "Lafayette"},
|
||||
{.id = 0x0012, .name = "Walnut Creek"},
|
||||
{.id = 0x0013, .name = "Pleasant Hill/Contra Costa Centre"},
|
||||
{.id = 0x0014, .name = "Concord"},
|
||||
{.id = 0x0015, .name = "North Concord/Martinez"},
|
||||
{.id = 0x0016, .name = "Pittsburg/Bay Point"},
|
||||
{.id = 0x0017, .name = "Ashby"},
|
||||
{.id = 0x0018, .name = "Downtown Berkeley"},
|
||||
{.id = 0x0019, .name = "North Berkeley"},
|
||||
{.id = 0x001a, .name = "El Cerrito Plaza"},
|
||||
{.id = 0x001b, .name = "El Cerrito Del Norte"},
|
||||
{.id = 0x001c, .name = "Richmond"},
|
||||
{.id = 0x001d, .name = "Lake Merrit"},
|
||||
{.id = 0x001e, .name = "Fruitvale"},
|
||||
{.id = 0x001f, .name = "Coliseum"},
|
||||
{.id = 0x0021, .name = "San Leandro"},
|
||||
{.id = 0x0022, .name = "Hayward"},
|
||||
{.id = 0x0023, .name = "South Hayward"},
|
||||
{.id = 0x0024, .name = "Union City"},
|
||||
{.id = 0x0025, .name = "Fremont"},
|
||||
{.id = 0x0026, .name = "Daly City(2)?"},
|
||||
{.id = 0x0027, .name = "Dublin/Pleasanton"},
|
||||
{.id = 0x0028, .name = "South San Francisco"},
|
||||
{.id = 0x0029, .name = "San Bruno"},
|
||||
{.id = 0x002a, .name = "SFO Airport"},
|
||||
{.id = 0x002b, .name = "Millbrae"},
|
||||
{.id = 0x002c, .name = "West Dublin/Pleasanton"},
|
||||
{.id = 0x002d, .name = "OAK Airport"},
|
||||
{.id = 0x002e, .name = "Warm Springs/South Fremont"},
|
||||
};
|
||||
static const size_t kNumBARTZones = COUNT(bart_zones);
|
||||
|
||||
static const IdMapping muni_zones[] = {
|
||||
{.id = 0x0000, .name = "City Street"},
|
||||
{.id = 0x0005, .name = "Embarcadero"},
|
||||
{.id = 0x0006, .name = "Montgomery"},
|
||||
{.id = 0x0007, .name = "Powell"},
|
||||
{.id = 0x0008, .name = "Civic Center"},
|
||||
{.id = 0x0009, .name = "Van Ness"}, // Guessed
|
||||
{.id = 0x000a, .name = "Church"},
|
||||
{.id = 0x000b, .name = "Castro"},
|
||||
{.id = 0x000c, .name = "Forest Hill"}, // Guessed
|
||||
{.id = 0x000d, .name = "West Portal"},
|
||||
};
|
||||
static const size_t kNumMUNIZones = COUNT(muni_zones);
|
||||
|
||||
static const IdMapping actransit_zones[] = {
|
||||
{.id = 0x0000, .name = "City Street"},
|
||||
};
|
||||
static const size_t kNumACTransitZones = COUNT(actransit_zones);
|
||||
|
||||
//
|
||||
// Full agency+zone mapping.
|
||||
//
|
||||
static const struct {
|
||||
uint16_t agency_id;
|
||||
const IdMapping* zone_map;
|
||||
size_t zone_count;
|
||||
} agency_zone_map[] = {
|
||||
{.agency_id = 0x0001, .zone_map = actransit_zones, .zone_count = kNumACTransitZones},
|
||||
{.agency_id = 0x0004, .zone_map = bart_zones, .zone_count = kNumBARTZones},
|
||||
{.agency_id = 0x0012, .zone_map = muni_zones, .zone_count = kNumMUNIZones}};
|
||||
static const size_t kNumAgencyZoneMaps = COUNT(agency_zone_map);
|
||||
|
||||
// File ids of important files on the card.
|
||||
static const MfDesfireFileId clipper_ecash_file_id = 2;
|
||||
static const MfDesfireFileId clipper_histidx_file_id = 6;
|
||||
static const MfDesfireFileId clipper_identity_file_id = 8;
|
||||
static const MfDesfireFileId clipper_history_file_id = 14;
|
||||
|
||||
struct ClipperCardInfo_struct {
|
||||
uint32_t serial_number;
|
||||
uint16_t counter;
|
||||
uint16_t last_txn_id;
|
||||
uint32_t last_updated_tm_1900;
|
||||
uint16_t last_terminal_id;
|
||||
int16_t balance_cents;
|
||||
};
|
||||
typedef struct ClipperCardInfo_struct ClipperCardInfo;
|
||||
|
||||
// Forward declarations for helper functions.
|
||||
static void furi_string_cat_timestamp(
|
||||
FuriString* str,
|
||||
const char* date_hdr,
|
||||
const char* time_hdr,
|
||||
uint32_t tmst_1900);
|
||||
static void epoch_1900_datetime_to_furi(uint32_t seconds, FuriHalRtcDateTime* out);
|
||||
static bool get_file_contents(
|
||||
const MfDesfireApplication* app,
|
||||
const MfDesfireFileId* id,
|
||||
MfDesfireFileType type,
|
||||
size_t min_size,
|
||||
const uint8_t** out);
|
||||
static bool decode_id_file(const uint8_t* ef8_data, ClipperCardInfo* info);
|
||||
static bool decode_cash_file(const uint8_t* ef2_data, ClipperCardInfo* info);
|
||||
static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out);
|
||||
static bool get_agency_zone_name(uint16_t agency_id, uint16_t zone_id, const char** out);
|
||||
static void
|
||||
decode_usd(int16_t amount_cents, bool* out_is_negative, int16_t* out_usd, uint16_t* out_cents);
|
||||
static bool dump_ride_history(
|
||||
const uint8_t* index_file,
|
||||
const uint8_t* history_file,
|
||||
size_t len,
|
||||
FuriString* parsed_data);
|
||||
static bool dump_ride_event(const uint8_t* record, FuriString* parsed_data);
|
||||
|
||||
// Unmarshal a 32-bit integer, big endian, unsigned
|
||||
static inline uint32_t get_u32be(const uint8_t* field) {
|
||||
return nfc_util_bytes2num(field, 4);
|
||||
}
|
||||
|
||||
// Unmarshal a 16-bit integer, big endian, unsigned
|
||||
static uint16_t get_u16be(const uint8_t* field) {
|
||||
return nfc_util_bytes2num(field, 2);
|
||||
}
|
||||
|
||||
// Unmarshal a 16-bit integer, big endian, signed, two's-complement
|
||||
static int16_t get_i16be(const uint8_t* field) {
|
||||
uint16_t raw = get_u16be(field);
|
||||
if(raw > 0x7fff)
|
||||
return -((uint32_t)0x10000 - raw);
|
||||
else
|
||||
return raw;
|
||||
}
|
||||
|
||||
static bool clipper_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
furi_assert(parsed_data);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
|
||||
|
||||
const MfDesfireApplication* app = NULL;
|
||||
const char* device_description = NULL;
|
||||
|
||||
for(size_t i = 0; i < kNumCardTypes; i++) {
|
||||
app = mf_desfire_get_application(data, &clipper_types[i].app);
|
||||
device_description = clipper_types[i].type;
|
||||
if(app != NULL) break;
|
||||
}
|
||||
|
||||
// If no matching application was found, abort this parser.
|
||||
if(app == NULL) break;
|
||||
|
||||
ClipperCardInfo info;
|
||||
const uint8_t* id_data;
|
||||
if(!get_file_contents(
|
||||
app, &clipper_identity_file_id, MfDesfireFileTypeStandard, 5, &id_data))
|
||||
break;
|
||||
if(!decode_id_file(id_data, &info)) break;
|
||||
|
||||
const uint8_t* cash_data;
|
||||
if(!get_file_contents(app, &clipper_ecash_file_id, MfDesfireFileTypeBackup, 32, &cash_data))
|
||||
break;
|
||||
if(!decode_cash_file(cash_data, &info)) break;
|
||||
|
||||
int16_t balance_usd;
|
||||
uint16_t balance_cents;
|
||||
bool _balance_is_negative;
|
||||
decode_usd(info.balance_cents, &_balance_is_negative, &balance_usd, &balance_cents);
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Clipper\n"
|
||||
"Serial: %" PRIu32 "\n"
|
||||
"Balance: $%d.%02u\n"
|
||||
"Type: %s\n"
|
||||
"\e#Last Update\n",
|
||||
info.serial_number,
|
||||
balance_usd,
|
||||
balance_cents,
|
||||
device_description);
|
||||
if(info.last_updated_tm_1900 != 0)
|
||||
furi_string_cat_timestamp(
|
||||
parsed_data, "Date: ", "\nTime: ", info.last_updated_tm_1900);
|
||||
else
|
||||
furi_string_cat_str(parsed_data, "Never");
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nTerminal: 0x%04x\n"
|
||||
"Transaction Id: %u\n"
|
||||
"Counter: %u\n",
|
||||
info.last_terminal_id,
|
||||
info.last_txn_id,
|
||||
info.counter);
|
||||
|
||||
const uint8_t *history_index, *history;
|
||||
|
||||
if(!get_file_contents(
|
||||
app, &clipper_histidx_file_id, MfDesfireFileTypeBackup, 16, &history_index))
|
||||
break;
|
||||
if(!get_file_contents(
|
||||
app, &clipper_history_file_id, MfDesfireFileTypeStandard, 512, &history))
|
||||
break;
|
||||
|
||||
if(!dump_ride_history(history_index, history, 512, parsed_data)) break;
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static bool get_file_contents(
|
||||
const MfDesfireApplication* app,
|
||||
const MfDesfireFileId* id,
|
||||
MfDesfireFileType type,
|
||||
size_t min_size,
|
||||
const uint8_t** out) {
|
||||
const MfDesfireFileSettings* settings = mf_desfire_get_file_settings(app, id);
|
||||
if(settings == NULL) return false;
|
||||
if(settings->type != type) return false;
|
||||
|
||||
const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, id);
|
||||
if(file_data == NULL) return false;
|
||||
|
||||
if(simple_array_get_count(file_data->data) < min_size) return false;
|
||||
|
||||
*out = simple_array_cget_data(file_data->data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_id_file(const uint8_t* ef8_data, ClipperCardInfo* info) {
|
||||
// Identity file (8)
|
||||
//
|
||||
// Byte view
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// +----+----.----.----.----+----.----.----+
|
||||
// 0x00 | uk | card_id | unknown |
|
||||
// +----+----.----.----.----+----.----.----+
|
||||
// 0x08 | unknown |
|
||||
// +----.----.----.----.----.----.----.----+
|
||||
// 0x10 ...
|
||||
//
|
||||
//
|
||||
// Field Datatype Description
|
||||
// ----- -------- -----------
|
||||
// uk ?8?? Unknown, 8-bit byte
|
||||
// card_id U32BE Card identifier
|
||||
//
|
||||
info->serial_number = nfc_util_bytes2num(&ef8_data[1], 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool decode_cash_file(const uint8_t* ef2_data, ClipperCardInfo* info) {
|
||||
// ECash file (2)
|
||||
//
|
||||
// Byte view
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// +----.----+----.----+----.----.----.----+
|
||||
// 0x00 | unk00 | counter | timestamp_1900 |
|
||||
// +----.----+----.----+----.----.----.----+
|
||||
// 0x08 | term_id | unk01 |
|
||||
// +----.----+----.----+----.----.----.----+
|
||||
// 0x10 | txn_id | balance | unknown |
|
||||
// +----.----+----.----+----.----.----.----+
|
||||
// 0x18 | unknown |
|
||||
// +---------------------------------------+
|
||||
//
|
||||
// Field Datatype Description
|
||||
// ----- -------- -----------
|
||||
// unk00 U8[2] Unknown bytes
|
||||
// counter U16BE Unknown, appears to be a counter
|
||||
// timestamp_1900 U32BE Timestamp of last transaction, in seconds
|
||||
// since 1900-01-01 GMT.
|
||||
// unk01 U8[6] Unknown bytes
|
||||
// txn_id U16BE Id of last transaction.
|
||||
// balance S16BE Card cash balance, in cents.
|
||||
// Cards can obtain negative balances in this
|
||||
// system, so balances are signed integers.
|
||||
// Maximum card balance is therefore
|
||||
// $327.67.
|
||||
// unk02 U8[12] Unknown bytes.
|
||||
//
|
||||
info->counter = get_u16be(&ef2_data[2]);
|
||||
info->last_updated_tm_1900 = get_u32be(&ef2_data[4]);
|
||||
info->last_terminal_id = get_u16be(&ef2_data[8]);
|
||||
info->last_txn_id = get_u16be(&ef2_data[0x10]);
|
||||
info->balance_cents = get_i16be(&ef2_data[0x12]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dump_ride_history(
|
||||
const uint8_t* index_file,
|
||||
const uint8_t* history_file,
|
||||
size_t len,
|
||||
FuriString* parsed_data) {
|
||||
static const size_t kRideRecordSize = 0x20;
|
||||
|
||||
for(size_t i = 0; i < 16; i++) {
|
||||
uint8_t record_num = index_file[i];
|
||||
if(record_num == 0xff) break;
|
||||
|
||||
size_t record_offset = record_num * kRideRecordSize;
|
||||
|
||||
if(record_offset + kRideRecordSize > len) break;
|
||||
|
||||
const uint8_t* record = &history_file[record_offset];
|
||||
if(!dump_ride_event(record, parsed_data)) break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool dump_ride_event(const uint8_t* record, FuriString* parsed_data) {
|
||||
// Ride record
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
// +----+----+----.----+----.----+----.----+
|
||||
// 0x00 |0x10| ? | agency | ? | fare |
|
||||
// +----.----+----.----+----.----.----.----+
|
||||
// 0x08 | ? | vehicle | time_on |
|
||||
// +----.----.----.----+----.----+----.----+
|
||||
// 0x10 | time_off | zone_on | zone_off|
|
||||
// +----+----.----.----.----+----+----+----+
|
||||
// 0x18 | ? | ? | ? | ? | ? |
|
||||
// +----+----.----.----.----+----+----+----+
|
||||
//
|
||||
// Field Datatype Description
|
||||
// ----- -------- -----------
|
||||
// agency U16BE Transportation agency identifier.
|
||||
// Known ids:
|
||||
// 1 == AC Transit
|
||||
// 4 == BART
|
||||
// 18 == SF MUNI
|
||||
// fare I16BE Fare deducted, in cents.
|
||||
// vehicle U16BE Vehicle id (0 == not provided)
|
||||
// time_on U32BE Boarding time, in seconds since 1900-01-01 GMT.
|
||||
// time_off U32BE Off-boarding time, if present, in seconds
|
||||
// since 1900-01-01 GMT. Set to zero if no offboard
|
||||
// has been recorded.
|
||||
// zone_on U16BE Id of boarding zone or station. Agency-specific.
|
||||
// zone_off U16BE Id of offboarding zone or station. Agency-
|
||||
// specific.
|
||||
if(record[0] != 0x10) return false;
|
||||
|
||||
uint16_t agency_id = get_u16be(&record[2]);
|
||||
if(agency_id == 0)
|
||||
// Likely empty record. Skip.
|
||||
return false;
|
||||
const char* agency_name;
|
||||
bool ok = get_map_item(agency_id, agency_names, kNumAgencies, &agency_name);
|
||||
if(!ok) agency_name = "Unknown";
|
||||
|
||||
uint16_t vehicle_id = get_u16be(&record[0x0a]);
|
||||
|
||||
int16_t fare_raw_cents = get_i16be(&record[6]);
|
||||
bool _fare_is_negative;
|
||||
int16_t fare_usd;
|
||||
uint16_t fare_cents;
|
||||
decode_usd(fare_raw_cents, &_fare_is_negative, &fare_usd, &fare_cents);
|
||||
|
||||
uint32_t time_on_raw = get_u32be(&record[0x0c]);
|
||||
uint32_t time_off_raw = get_u32be(&record[0x10]);
|
||||
uint16_t zone_id_on = get_u16be(&record[0x14]);
|
||||
uint16_t zone_id_off = get_u16be(&record[0x16]);
|
||||
|
||||
const char *zone_on, *zone_off;
|
||||
if(!get_agency_zone_name(agency_id, zone_id_on, &zone_on)) {
|
||||
zone_on = "Unknown";
|
||||
}
|
||||
if(!get_agency_zone_name(agency_id, zone_id_off, &zone_off)) {
|
||||
zone_off = "Unknown";
|
||||
}
|
||||
|
||||
furi_string_cat_str(parsed_data, "\e#Ride Record\n");
|
||||
furi_string_cat_timestamp(parsed_data, "Date: ", "\nTime: ", time_on_raw);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\n"
|
||||
"Fare: $%d.%02u\n"
|
||||
"Agency: %s (%04x)\n"
|
||||
"On: %s (%04x)\n",
|
||||
fare_usd,
|
||||
fare_cents,
|
||||
agency_name,
|
||||
agency_id,
|
||||
zone_on,
|
||||
zone_id_on);
|
||||
if(vehicle_id != 0) {
|
||||
furi_string_cat_printf(parsed_data, "Vehicle id: %d\n", vehicle_id);
|
||||
}
|
||||
if(time_off_raw != 0) {
|
||||
furi_string_cat_printf(parsed_data, "Off: %s (%04x)\n", zone_off, zone_id_off);
|
||||
furi_string_cat_timestamp(parsed_data, "Date Off: ", "\nTime Off: ", time_off_raw);
|
||||
furi_string_cat_str(parsed_data, "\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out) {
|
||||
for(size_t i = 0; i < sz; i++) {
|
||||
if(map[i].id == id) {
|
||||
*out = map[i].name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool get_agency_zone_name(uint16_t agency_id, uint16_t zone_id, const char** out) {
|
||||
for(size_t i = 0; i < kNumAgencyZoneMaps; i++) {
|
||||
if(agency_zone_map[i].agency_id == agency_id) {
|
||||
return get_map_item(
|
||||
zone_id, agency_zone_map[i].zone_map, agency_zone_map[i].zone_count, out);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split a balance/fare amount from raw cents to dollars and cents portion,
|
||||
// automatically adjusting the cents portion so that it is always positive,
|
||||
// for easier display.
|
||||
static void
|
||||
decode_usd(int16_t amount_cents, bool* out_is_negative, int16_t* out_usd, uint16_t* out_cents) {
|
||||
*out_usd = amount_cents / 100;
|
||||
|
||||
if(amount_cents >= 0) {
|
||||
*out_is_negative = false;
|
||||
*out_cents = amount_cents % 100;
|
||||
} else {
|
||||
*out_is_negative = true;
|
||||
*out_cents = (amount_cents * -1) % 100;
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a raw 1900-based timestamp and append a human-readable form to a
|
||||
// FuriString.
|
||||
static void furi_string_cat_timestamp(
|
||||
FuriString* str,
|
||||
const char* date_hdr,
|
||||
const char* time_hdr,
|
||||
uint32_t tmst_1900) {
|
||||
FuriHalRtcDateTime tm;
|
||||
|
||||
epoch_1900_datetime_to_furi(tmst_1900, &tm);
|
||||
|
||||
FuriString* date_str = furi_string_alloc();
|
||||
locale_format_date(date_str, &tm, locale_get_date_format(), "-");
|
||||
|
||||
FuriString* time_str = furi_string_alloc();
|
||||
locale_format_time(time_str, &tm, locale_get_time_format(), true);
|
||||
|
||||
furi_string_cat_printf(
|
||||
str,
|
||||
"%s%s%s%s (UTC)",
|
||||
date_hdr,
|
||||
furi_string_get_cstr(date_str),
|
||||
time_hdr,
|
||||
furi_string_get_cstr(time_str));
|
||||
|
||||
furi_string_free(date_str);
|
||||
furi_string_free(time_str);
|
||||
}
|
||||
|
||||
// Convert a "1900"-based timestamp to Furi time, assuming a UTC/GMT timezone.
|
||||
static void epoch_1900_datetime_to_furi(uint32_t seconds, FuriHalRtcDateTime* out) {
|
||||
uint16_t year, month, day, hour, minute, second;
|
||||
|
||||
// Calculate absolute number of days elapsed since the 1900 epoch
|
||||
// and save the residual for the time within the day.
|
||||
uint32_t absolute_days = seconds / 86400;
|
||||
uint32_t seconds_within_day = seconds % 86400;
|
||||
|
||||
// Calculate day of the week.
|
||||
// January 1, 1900 was a Monday ("day of week" = 1)
|
||||
uint8_t dow = (absolute_days + 1) % 7;
|
||||
|
||||
//
|
||||
// Compute the date by simply marching through time in as large chunks
|
||||
// as possible.
|
||||
//
|
||||
|
||||
for(year = 1900;; year++) {
|
||||
uint16_t year_days = furi_hal_rtc_get_days_per_year(year);
|
||||
if(absolute_days >= year_days)
|
||||
absolute_days -= year_days;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
bool is_leap = furi_hal_rtc_is_leap_year(year);
|
||||
|
||||
for(month = 1;; month++) {
|
||||
uint8_t days_in_month = furi_hal_rtc_get_days_per_month(is_leap, month);
|
||||
if(absolute_days >= days_in_month)
|
||||
absolute_days -= days_in_month;
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
day = absolute_days + 1;
|
||||
hour = seconds_within_day / 3600;
|
||||
uint16_t sub_hour = seconds_within_day % 3600;
|
||||
minute = sub_hour / 60;
|
||||
second = sub_hour % 60;
|
||||
|
||||
out->year = year;
|
||||
out->month = month;
|
||||
out->day = day;
|
||||
out->hour = hour;
|
||||
out->minute = minute;
|
||||
out->second = second;
|
||||
out->weekday = dow;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin clipper_plugin = {
|
||||
.protocol = NfcProtocolMfDesfire,
|
||||
.verify = NULL,
|
||||
.read = NULL,
|
||||
.parse = clipper_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor clipper_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &clipper_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* clipper_plugin_ep() {
|
||||
return &clipper_plugin_descriptor;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ static bool hid_read(Nfc* nfc, NfcDevice* device) {
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
124
applications/main/nfc/plugins/supported_cards/itso.c
Normal file
124
applications/main/nfc/plugins/supported_cards/itso.c
Normal file
@@ -0,0 +1,124 @@
|
||||
/* itso.c - Parser for ITSO cards (United Kingdom). */
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
|
||||
|
||||
#include <applications/services/locale/locale.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
|
||||
static const MfDesfireApplicationId itso_app_id = {.data = {0x16, 0x02, 0xa0}};
|
||||
static const MfDesfireFileId itso_file_id = 0x0f;
|
||||
|
||||
int64_t swap_int64(int64_t val) {
|
||||
val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
|
||||
val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
|
||||
return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
|
||||
}
|
||||
|
||||
uint64_t swap_uint64(uint64_t val) {
|
||||
val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
|
||||
val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
|
||||
return (val << 32) | (val >> 32);
|
||||
}
|
||||
|
||||
static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
furi_assert(parsed_data);
|
||||
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
|
||||
|
||||
const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_app_id);
|
||||
if(app == NULL) break;
|
||||
|
||||
typedef struct {
|
||||
uint64_t part1;
|
||||
uint64_t part2;
|
||||
uint64_t part3;
|
||||
uint64_t part4;
|
||||
} ItsoFile;
|
||||
|
||||
const MfDesfireFileSettings* file_settings =
|
||||
mf_desfire_get_file_settings(app, &itso_file_id);
|
||||
|
||||
if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
|
||||
file_settings->data.size < sizeof(ItsoFile))
|
||||
break;
|
||||
|
||||
const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &itso_file_id);
|
||||
if(file_data == NULL) break;
|
||||
|
||||
const ItsoFile* itso_file = simple_array_cget_data(file_data->data);
|
||||
|
||||
uint64_t x1 = swap_uint64(itso_file->part1);
|
||||
uint64_t x2 = swap_uint64(itso_file->part2);
|
||||
|
||||
char cardBuff[32];
|
||||
char dateBuff[18];
|
||||
|
||||
snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2);
|
||||
snprintf(dateBuff, sizeof(dateBuff), "%llx", x2);
|
||||
|
||||
char* cardp = cardBuff + 4;
|
||||
cardp[18] = '\0';
|
||||
|
||||
// All itso card numbers are prefixed with "633597"
|
||||
if(strncmp(cardp, "633597", 6) != 0) break;
|
||||
|
||||
char* datep = dateBuff + 12;
|
||||
dateBuff[17] = '\0';
|
||||
|
||||
// DateStamp is defined in BS EN 1545 - Days passed since 01/01/1997
|
||||
uint32_t dateStamp = (int)strtol(datep, NULL, 16);
|
||||
uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U;
|
||||
|
||||
furi_string_set(parsed_data, "\e#ITSO Card\n");
|
||||
|
||||
// Digit count in each space-separated group
|
||||
static const uint8_t digit_count[] = {6, 4, 4, 4};
|
||||
|
||||
for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) {
|
||||
for(uint32_t j = 0; j < digit_count[i]; ++j) {
|
||||
furi_string_push_back(parsed_data, cardp[j + k]);
|
||||
}
|
||||
furi_string_push_back(parsed_data, ' ');
|
||||
}
|
||||
|
||||
FuriHalRtcDateTime timestamp = {0};
|
||||
furi_hal_rtc_timestamp_to_datetime(unixTimestamp, ×tamp);
|
||||
FuriString* timestamp_str = furi_string_alloc();
|
||||
locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-");
|
||||
|
||||
furi_string_cat(parsed_data, "\nExpiry: ");
|
||||
furi_string_cat(parsed_data, timestamp_str);
|
||||
|
||||
furi_string_free(timestamp_str);
|
||||
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
/* Actual implementation of app<>plugin interface */
|
||||
static const NfcSupportedCardsPlugin itso_plugin = {
|
||||
.protocol = NfcProtocolMfDesfire,
|
||||
.verify = NULL,
|
||||
.read = NULL,
|
||||
.parse = itso_parse,
|
||||
};
|
||||
|
||||
/* Plugin descriptor to comply with basic plugin specification */
|
||||
static const FlipperAppPluginDescriptor itso_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &itso_plugin,
|
||||
};
|
||||
|
||||
/* Plugin entry point - must return a pointer to const descriptor */
|
||||
const FlipperAppPluginDescriptor* itso_plugin_ep() {
|
||||
return &itso_plugin_descriptor;
|
||||
}
|
||||
@@ -135,14 +135,14 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = true;
|
||||
is_read = (error == MfClassicErrorNone);
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <nfc/helpers/nfc_util.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include "furi_hal_rtc.h"
|
||||
|
||||
#define TAG "Troika"
|
||||
|
||||
@@ -18,6 +19,19 @@ typedef struct {
|
||||
uint32_t data_sector;
|
||||
} TroikaCardConfig;
|
||||
|
||||
typedef enum {
|
||||
TroikaLayoutUnknown = 0x0,
|
||||
TroikaLayout2 = 0x2,
|
||||
TroikaLayoutE = 0xE,
|
||||
} TroikaLayout;
|
||||
|
||||
typedef enum {
|
||||
TroikaSublayoutUnknown = 0x0,
|
||||
TroikaSublayout3 = 0x3,
|
||||
TroikaSublayout5 = 0x5,
|
||||
TroikaSublayout6 = 0x6,
|
||||
} TroikaSubLayout;
|
||||
|
||||
static const MfClassicKeyPair troika_1k_keys[] = {
|
||||
{.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58},
|
||||
{.a = 0xa82607b01c0d, .b = 0x2910989b6880},
|
||||
@@ -67,7 +81,7 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type)
|
||||
config->data_sector = 8;
|
||||
config->keys = troika_1k_keys;
|
||||
} else if(type == MfClassicType4k) {
|
||||
config->data_sector = 4;
|
||||
config->data_sector = 8; // Further testing needed
|
||||
config->keys = troika_4k_keys;
|
||||
} else {
|
||||
success = false;
|
||||
@@ -76,6 +90,126 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type)
|
||||
return success;
|
||||
}
|
||||
|
||||
static TroikaLayout troika_get_layout(const MfClassicData* data, uint8_t start_block_num) {
|
||||
furi_assert(data);
|
||||
|
||||
// Layout is stored in byte 6 of block, length 4 bits (bits 52 - 55), second nibble.
|
||||
const uint8_t* layout_ptr = &data->block[start_block_num].data[6];
|
||||
const uint8_t layout = (*layout_ptr & 0x0F);
|
||||
|
||||
TroikaLayout result = TroikaLayoutUnknown;
|
||||
switch(layout) {
|
||||
case TroikaLayout2:
|
||||
case TroikaLayoutE:
|
||||
result = layout;
|
||||
break;
|
||||
default:
|
||||
// If debug is enabled - pass the actual layout value for the debug text
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
return layout;
|
||||
} else {
|
||||
return TroikaLayoutUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static TroikaSubLayout troika_get_sub_layout(const MfClassicData* data, uint8_t start_block_num) {
|
||||
furi_assert(data);
|
||||
|
||||
// Sublayout is stored in byte 7 (bits 56 - 60) of block, length 5 bits (first nibble and one bit from second nibble)
|
||||
const uint8_t* sub_layout_ptr = &data->block[start_block_num].data[7];
|
||||
const uint8_t sub_layout = (*sub_layout_ptr & 0x3F) >> 3;
|
||||
|
||||
TroikaSubLayout result = TroikaSublayoutUnknown;
|
||||
switch(sub_layout) {
|
||||
case TroikaSublayout3:
|
||||
case TroikaSublayout5:
|
||||
case TroikaSublayout6:
|
||||
result = sub_layout;
|
||||
break;
|
||||
default:
|
||||
// If debug is enabled - pass the actual sublayout value for the debug text
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
return sub_layout;
|
||||
} else {
|
||||
return TroikaSublayoutUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool troika_has_balance(TroikaLayout layout, TroikaSubLayout sub_layout) {
|
||||
UNUSED(sub_layout);
|
||||
// Layout 0x2 has no balance
|
||||
|
||||
if(layout == TroikaLayout2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint16_t troika_get_balance(
|
||||
const MfClassicData* data,
|
||||
uint8_t start_block_num,
|
||||
TroikaLayout layout,
|
||||
TroikaSubLayout sub_layout) {
|
||||
furi_assert(data);
|
||||
|
||||
// In layout 0x3 balance in bits 188:209 ( from sector start, length 22).
|
||||
// In layout 0x5 balance in bits 165:185 ( from sector start, length 20).
|
||||
|
||||
uint32_t balance = 0;
|
||||
uint8_t balance_data_offset = 0;
|
||||
bool supported_layout = false;
|
||||
|
||||
if(layout == TroikaLayoutE && sub_layout == TroikaSublayout3) {
|
||||
balance_data_offset = 7;
|
||||
supported_layout = true;
|
||||
} else if(layout == TroikaLayoutE && sub_layout == TroikaSublayout5) {
|
||||
balance_data_offset = 4;
|
||||
supported_layout = true;
|
||||
}
|
||||
|
||||
if(supported_layout) {
|
||||
const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[balance_data_offset];
|
||||
balance |= (temp_ptr[0] & 0x3) << 18;
|
||||
balance |= temp_ptr[1] << 10;
|
||||
balance |= temp_ptr[2] << 2;
|
||||
balance |= (temp_ptr[3] & 0xC0) >> 6;
|
||||
}
|
||||
|
||||
return balance / 100;
|
||||
}
|
||||
|
||||
static uint32_t troika_get_number(
|
||||
const MfClassicData* data,
|
||||
uint8_t start_block_num,
|
||||
TroikaLayout layout,
|
||||
TroikaSubLayout sub_layout) {
|
||||
furi_assert(data);
|
||||
UNUSED(sub_layout);
|
||||
|
||||
if(layout == TroikaLayoutE || layout == TroikaLayout2) {
|
||||
const uint8_t* temp_ptr = &data->block[start_block_num].data[2];
|
||||
|
||||
uint32_t number = 0;
|
||||
for(size_t i = 1; i < 5; i++) {
|
||||
number <<= 8;
|
||||
number |= temp_ptr[i];
|
||||
}
|
||||
number >>= 4;
|
||||
number |= (temp_ptr[0] & 0xf) << 28;
|
||||
|
||||
return number;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static bool troika_verify_type(Nfc* nfc, MfClassicType type) {
|
||||
bool verified = false;
|
||||
|
||||
@@ -137,14 +271,14 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) {
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = true;
|
||||
is_read = (error == MfClassicErrorNone);
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
@@ -171,22 +305,39 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||
if(key != cfg.keys[cfg.data_sector].a) break;
|
||||
|
||||
// Parse data
|
||||
// Get the block number of the block that contains the data
|
||||
const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
|
||||
|
||||
const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5];
|
||||
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
|
||||
temp_ptr = &data->block[start_block_num].data[2];
|
||||
// Get layout, sublayout, balance and number
|
||||
TroikaLayout layout = troika_get_layout(data, start_block_num);
|
||||
TroikaSubLayout sub_layout = troika_get_sub_layout(data, start_block_num);
|
||||
|
||||
uint32_t number = 0;
|
||||
for(size_t i = 1; i < 5; i++) {
|
||||
number <<= 8;
|
||||
number |= temp_ptr[i];
|
||||
if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
// If debug is enabled - proceed even if layout or sublayout is unknown, that will make collecting data easier
|
||||
if(layout == TroikaLayoutUnknown || sub_layout == TroikaSublayoutUnknown) break;
|
||||
}
|
||||
|
||||
uint32_t number = troika_get_number(data, start_block_num, layout, sub_layout);
|
||||
|
||||
furi_string_printf(parsed_data, "\e#Troika\nNum: %lu", number);
|
||||
|
||||
if(troika_has_balance(layout, sub_layout) ||
|
||||
furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
uint16_t balance = troika_get_balance(data, start_block_num, layout, sub_layout);
|
||||
furi_string_cat_printf(parsed_data, "\nBalance: %u RUR", balance);
|
||||
} else {
|
||||
furi_string_cat_printf(parsed_data, "\nBalance: Not available");
|
||||
}
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nLayout: %02x\nSublayout: %02x\nData Block: %u",
|
||||
layout,
|
||||
sub_layout,
|
||||
start_block_num);
|
||||
}
|
||||
number >>= 4;
|
||||
number |= (temp_ptr[0] & 0xf) << 28;
|
||||
|
||||
furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance);
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
|
||||
@@ -85,14 +85,14 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNone) {
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
is_read = true;
|
||||
is_read = (error == MfClassicErrorNone);
|
||||
} while(false);
|
||||
|
||||
mf_classic_free(data);
|
||||
|
||||
@@ -51,8 +51,11 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) {
|
||||
if(nfc_delete(nfc)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess);
|
||||
} else {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneStart);
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneStart)) {
|
||||
scene_manager_stop(nfc->scene_manager);
|
||||
view_dispatcher_stop(nfc->view_dispatcher);
|
||||
}
|
||||
}
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,11 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
|
||||
if(!consumed) {
|
||||
scene_manager_stop(nfc->scene_manager);
|
||||
view_dispatcher_stop(nfc->view_dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == DialogExResultRight) {
|
||||
consumed = scene_manager_previous_scene(nfc->scene_manager);
|
||||
} else if(event.event == DialogExResultLeft) {
|
||||
if(scene_manager_has_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfUltralightUnlockWarn)) {
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicDictAttack);
|
||||
} else if(scene_manager_has_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfUltralightUnlockWarn)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfUltralightUnlockMenu);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) {
|
||||
|
||||
@@ -31,12 +31,25 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader);
|
||||
consumed = true;
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) {
|
||||
consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadSuccess)) {
|
||||
consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneFileSelect)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneSavedMenu);
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_another_scene(
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ void nfc_scene_select_protocol_on_enter(void* context) {
|
||||
"%s %s",
|
||||
prefix,
|
||||
nfc_device_get_protocol_name(instance->protocols_detected[i]));
|
||||
|
||||
furi_string_replace_str(temp_str, "Mifare", "MIFARE");
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
furi_string_get_cstr(temp_str),
|
||||
|
||||
@@ -24,6 +24,8 @@ void nfc_scene_start_on_enter(void* context) {
|
||||
furi_string_reset(nfc->file_name);
|
||||
nfc_device_clear(nfc->nfc_device);
|
||||
iso14443_3a_reset(nfc->iso14443_3a_edit_data);
|
||||
// Reset detected protocols list
|
||||
nfc_app_reset_detected_protocols(nfc);
|
||||
|
||||
submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc);
|
||||
submenu_add_item(
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
BIN
assets/icons/NFC/NFC_dolphin_emulation_51x64.png
Normal file
BIN
assets/icons/NFC/NFC_dolphin_emulation_51x64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -39,6 +39,7 @@ typedef enum {
|
||||
MfClassicErrorNotPresent,
|
||||
MfClassicErrorProtocol,
|
||||
MfClassicErrorAuth,
|
||||
MfClassicErrorPartialRead,
|
||||
MfClassicErrorTimeout,
|
||||
} MfClassicError;
|
||||
|
||||
|
||||
@@ -475,19 +475,16 @@ MfClassicError
|
||||
|
||||
nfc_poller_stop(poller);
|
||||
|
||||
if(poller_context.error != MfClassicErrorNone) {
|
||||
error = poller_context.error;
|
||||
} else {
|
||||
const MfClassicData* mfc_data = nfc_poller_get_data(poller);
|
||||
uint8_t sectors_read = 0;
|
||||
uint8_t keys_found = 0;
|
||||
const MfClassicData* mfc_data = nfc_poller_get_data(poller);
|
||||
uint8_t sectors_read = 0;
|
||||
uint8_t keys_found = 0;
|
||||
|
||||
mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found);
|
||||
if((sectors_read > 0) || (keys_found > 0)) {
|
||||
mf_classic_copy(data, mfc_data);
|
||||
} else {
|
||||
error = MfClassicErrorNotPresent;
|
||||
}
|
||||
mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found);
|
||||
if((sectors_read == 0) && (keys_found == 0)) {
|
||||
error = MfClassicErrorNotPresent;
|
||||
} else {
|
||||
mf_classic_copy(data, mfc_data);
|
||||
error = mf_classic_is_card_read(mfc_data) ? MfClassicErrorNone : MfClassicErrorPartialRead;
|
||||
}
|
||||
|
||||
nfc_poller_free(poller);
|
||||
|
||||
Reference in New Issue
Block a user