Merge branch 'fz-dev' into dev
|
@ -16,7 +16,7 @@ static void lfrfid_view_read_draw_callback(Canvas* canvas, void* _model) {
|
|||
LfRfidReadViewModel* model = _model;
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
canvas_draw_icon(canvas, 0, 8, &I_NFC_manual);
|
||||
canvas_draw_icon(canvas, 0, 8, &I_NFC_manual_60x50);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
|
|
|
@ -37,6 +37,10 @@
|
|||
|
||||
#include "rpc/rpc_app.h"
|
||||
|
||||
#include <m-array.h>
|
||||
|
||||
ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST);
|
||||
|
||||
#define NFC_TEXT_STORE_SIZE 128
|
||||
|
||||
typedef enum {
|
||||
|
@ -60,6 +64,7 @@ struct Nfc {
|
|||
char text_store[NFC_TEXT_STORE_SIZE + 1];
|
||||
string_t text_box_store;
|
||||
uint8_t byte_input_store[6];
|
||||
MfClassicUserKeys_t mfc_key_strs; // Used in MFC key listing
|
||||
|
||||
void* rpc_ctx;
|
||||
NfcRpcState rpc_state;
|
||||
|
|
|
@ -32,6 +32,9 @@ ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu)
|
|||
ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate)
|
||||
ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)
|
||||
ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd)
|
||||
ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList)
|
||||
ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete)
|
||||
ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate)
|
||||
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
|
||||
ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
|
||||
ADD_SCENE(nfc, emv_menu, EmvMenu)
|
||||
|
|
|
@ -25,8 +25,13 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
|
|||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneFileSelect);
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
|
|
|
@ -47,7 +47,9 @@ void nfc_scene_device_info_on_enter(void* context) {
|
|||
}
|
||||
string_clear(country_name);
|
||||
}
|
||||
} else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
|
||||
} else if(
|
||||
dev_data->protocol == NfcDeviceProtocolMifareClassic ||
|
||||
dev_data->protocol == NfcDeviceProtocolMifareUl) {
|
||||
string_set(temp_str, nfc->dev->dev_data.parsed_data);
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,10 @@ bool nfc_scene_dict_not_found_on_event(void* context, SceneManagerEvent event) {
|
|||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) {
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
} else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneExtraActions);
|
||||
} else {
|
||||
|
|
|
@ -17,7 +17,7 @@ void nfc_scene_extra_actions_on_enter(void* context) {
|
|||
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Mf Classic Keys",
|
||||
"Mifare Classic Keys",
|
||||
SubmenuIndexMfClassicKeys,
|
||||
nfc_scene_extra_actions_submenu_callback,
|
||||
nfc);
|
||||
|
|
|
@ -26,15 +26,25 @@ void nfc_scene_mf_classic_keys_on_enter(void* context) {
|
|||
}
|
||||
|
||||
widget_add_string_element(
|
||||
nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MF Classic Keys");
|
||||
nfc->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Mifare Classic Keys");
|
||||
char temp_str[32];
|
||||
snprintf(temp_str, sizeof(temp_str), "Flipper dict: %ld", flipper_dict_keys_total);
|
||||
snprintf(temp_str, sizeof(temp_str), "Flipper list: %ld", flipper_dict_keys_total);
|
||||
widget_add_string_element(nfc->widget, 0, 20, AlignLeft, AlignTop, FontSecondary, temp_str);
|
||||
snprintf(temp_str, sizeof(temp_str), "User dict: %ld", user_dict_keys_total);
|
||||
snprintf(temp_str, sizeof(temp_str), "User list: %ld", user_dict_keys_total);
|
||||
widget_add_string_element(nfc->widget, 0, 32, AlignLeft, AlignTop, FontSecondary, temp_str);
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeCenter, "Add", nfc_scene_mf_classic_keys_widget_callback, nfc);
|
||||
widget_add_icon_element(nfc->widget, 90, 12, &I_Keychain);
|
||||
widget_add_button_element(
|
||||
nfc->widget, GuiButtonTypeLeft, "Back", nfc_scene_mf_classic_keys_widget_callback, nfc);
|
||||
widget_add_icon_element(nfc->widget, 87, 13, &I_Keychain_39x36);
|
||||
if(user_dict_keys_total > 0) {
|
||||
widget_add_button_element(
|
||||
nfc->widget,
|
||||
GuiButtonTypeRight,
|
||||
"List",
|
||||
nfc_scene_mf_classic_keys_widget_callback,
|
||||
nfc);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
@ -47,6 +57,12 @@ bool nfc_scene_mf_classic_keys_on_event(void* context, SceneManagerEvent event)
|
|||
if(event.event == GuiButtonTypeCenter) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysAdd);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_previous_scene(nfc->scene_manager);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysList);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,18 +29,19 @@ bool nfc_scene_mf_classic_keys_add_on_event(void* context, SceneManagerEvent eve
|
|||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventByteInputDone) {
|
||||
// Add key to dict
|
||||
bool key_added = false;
|
||||
MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser);
|
||||
if(dict) {
|
||||
if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) {
|
||||
key_added = true;
|
||||
}
|
||||
}
|
||||
if(key_added) {
|
||||
if(mf_classic_dict_is_key_present(dict, nfc->byte_input_store)) {
|
||||
scene_manager_next_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeysWarnDuplicate);
|
||||
} else if(mf_classic_dict_add_key(dict, nfc->byte_input_store)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveSuccess);
|
||||
} else {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
|
||||
}
|
||||
} else {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneDictNotFound);
|
||||
}
|
||||
mf_classic_dict_free(dict);
|
||||
consumed = true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
#include "../nfc_i.h"
|
||||
|
||||
void nfc_scene_mf_classic_keys_delete_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
Nfc* nfc = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_delete_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser);
|
||||
uint32_t key_index =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete);
|
||||
// Setup Custom Widget view
|
||||
string_t key_str;
|
||||
string_init(key_str);
|
||||
|
||||
widget_add_string_element(
|
||||
nfc->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?");
|
||||
widget_add_button_element(
|
||||
nfc->widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Cancel",
|
||||
nfc_scene_mf_classic_keys_delete_widget_callback,
|
||||
nfc);
|
||||
widget_add_button_element(
|
||||
nfc->widget,
|
||||
GuiButtonTypeRight,
|
||||
"Delete",
|
||||
nfc_scene_mf_classic_keys_delete_widget_callback,
|
||||
nfc);
|
||||
|
||||
mf_classic_dict_get_key_at_index_str(dict, key_str, key_index);
|
||||
widget_add_string_element(
|
||||
nfc->widget, 64, 32, AlignCenter, AlignCenter, FontSecondary, string_get_cstr(key_str));
|
||||
|
||||
string_clear(key_str);
|
||||
mf_classic_dict_free(dict);
|
||||
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_classic_keys_delete_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
uint32_t key_index =
|
||||
scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicKeysDelete);
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
} else if(event.event == GuiButtonTypeRight) {
|
||||
MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser);
|
||||
if(mf_classic_dict_delete_index(dict, key_index)) {
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess);
|
||||
} else {
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
}
|
||||
mf_classic_dict_free(dict);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_delete_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
widget_reset(nfc->widget);
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
#include "../nfc_i.h"
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_submenu_callback(void* context, uint32_t index) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
Submenu* submenu = nfc->submenu;
|
||||
MfClassicDict* dict = mf_classic_dict_alloc(MfClassicDictTypeUser);
|
||||
uint32_t index = 0;
|
||||
string_t temp_key;
|
||||
MfClassicUserKeys_init(nfc->mfc_key_strs);
|
||||
string_init(temp_key);
|
||||
if(dict) {
|
||||
mf_classic_dict_rewind(dict);
|
||||
while(mf_classic_dict_get_next_key_str(dict, temp_key)) {
|
||||
char* current_key = (char*)malloc(sizeof(char) * 13);
|
||||
strncpy(current_key, string_get_cstr(temp_key), 12);
|
||||
MfClassicUserKeys_push_back(nfc->mfc_key_strs, current_key);
|
||||
FURI_LOG_D("ListKeys", "Key %d: %s", index, current_key);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
current_key,
|
||||
index++,
|
||||
nfc_scene_mf_classic_keys_list_submenu_callback,
|
||||
nfc);
|
||||
}
|
||||
}
|
||||
submenu_set_header(submenu, "Select key to delete:");
|
||||
mf_classic_dict_free(dict);
|
||||
string_clear(temp_key);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_classic_keys_list_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeysDelete, event.event);
|
||||
scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicKeysDelete);
|
||||
consumed = true;
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_list_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
MfClassicUserKeys_it_t it;
|
||||
for(MfClassicUserKeys_it(it, nfc->mfc_key_strs); !MfClassicUserKeys_end_p(it);
|
||||
MfClassicUserKeys_next(it)) {
|
||||
free(*MfClassicUserKeys_ref(it));
|
||||
}
|
||||
MfClassicUserKeys_clear(nfc->mfc_key_strs);
|
||||
submenu_reset(nfc->submenu);
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
#include "../nfc_i.h"
|
||||
|
||||
void nfc_scene_mf_classic_keys_warn_duplicate_popup_callback(void* context) {
|
||||
Nfc* nfc = context;
|
||||
view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_warn_duplicate_on_enter(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
// Setup view
|
||||
Popup* popup = nfc->popup;
|
||||
popup_set_icon(popup, 72, 16, &I_DolphinCommon_56x48);
|
||||
popup_set_header(popup, "Key already exists!", 64, 3, AlignCenter, AlignTop);
|
||||
popup_set_text(
|
||||
popup,
|
||||
"Please enter a\n"
|
||||
"different key.",
|
||||
4,
|
||||
24,
|
||||
AlignLeft,
|
||||
AlignTop);
|
||||
popup_set_timeout(popup, 5000);
|
||||
popup_set_context(popup, nfc);
|
||||
popup_set_callback(popup, nfc_scene_mf_classic_keys_warn_duplicate_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
|
||||
}
|
||||
|
||||
bool nfc_scene_mf_classic_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) {
|
||||
Nfc* nfc = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeysAdd);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_scene_mf_classic_keys_warn_duplicate_on_exit(void* context) {
|
||||
Nfc* nfc = context;
|
||||
|
||||
popup_reset(nfc->popup);
|
||||
}
|
|
@ -27,7 +27,7 @@ void nfc_scene_mf_ultralight_read_auth_set_state(Nfc* nfc, NfcSceneMfUlReadState
|
|||
popup_reset(nfc->popup);
|
||||
popup_set_text(
|
||||
nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop);
|
||||
popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual);
|
||||
popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50);
|
||||
} else if(state == NfcSceneMfUlReadStateReading) {
|
||||
popup_reset(nfc->popup);
|
||||
popup_set_header(
|
||||
|
|
|
@ -34,6 +34,9 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) {
|
|||
nfc);
|
||||
|
||||
string_t temp_str;
|
||||
if(string_size(nfc->dev->dev_data.parsed_data)) {
|
||||
string_init_set(temp_str, nfc->dev->dev_data.parsed_data);
|
||||
} else {
|
||||
string_init_printf(temp_str, "\e#%s\n", nfc_mf_ul_type(mf_ul_data->type, true));
|
||||
string_cat_printf(temp_str, "UID:");
|
||||
for(size_t i = 0; i < data->uid_len; i++) {
|
||||
|
@ -44,6 +47,7 @@ void nfc_scene_mf_ultralight_read_success_on_enter(void* context) {
|
|||
if(mf_ul_data->data_read != mf_ul_data->data_size) {
|
||||
string_cat_printf(temp_str, "\nPassword-protected pages!");
|
||||
}
|
||||
}
|
||||
widget_add_text_scroll_element(widget, 0, 0, 128, 52, string_get_cstr(temp_str));
|
||||
string_clear(temp_str);
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ void nfc_scene_read_set_state(Nfc* nfc, NfcSceneReadState state) {
|
|||
popup_reset(nfc->popup);
|
||||
popup_set_text(
|
||||
nfc->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop);
|
||||
popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual);
|
||||
popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50);
|
||||
} else if(state == NfcSceneReadStateReading) {
|
||||
popup_reset(nfc->popup);
|
||||
popup_set_header(
|
||||
|
|
|
@ -11,7 +11,7 @@ void nfc_scene_restore_original_confirm_on_enter(void* context) {
|
|||
DialogEx* dialog_ex = nfc->dialog_ex;
|
||||
|
||||
dialog_ex_set_header(dialog_ex, "Restore Card Data?", 64, 0, AlignCenter, AlignTop);
|
||||
dialog_ex_set_icon(dialog_ex, 5, 15, &I_Restoring);
|
||||
dialog_ex_set_icon(dialog_ex, 5, 15, &I_Restoring_38x32);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, "It will be returned\nto its original state.", 47, 21, AlignLeft, AlignTop);
|
||||
dialog_ex_set_left_button_text(dialog_ex, "Cancel");
|
||||
|
|
|
@ -27,7 +27,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) {
|
|||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcCustomEventViewExit) {
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) {
|
||||
if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc->scene_manager, NfcSceneMfClassicKeys);
|
||||
} 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 {
|
||||
|
|
|
@ -91,7 +91,9 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
|||
bool application_info_present = false;
|
||||
if(dev_data->protocol == NfcDeviceProtocolEMV) {
|
||||
application_info_present = true;
|
||||
} else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) {
|
||||
} else if(
|
||||
dev_data->protocol == NfcDeviceProtocolMifareClassic ||
|
||||
dev_data->protocol == NfcDeviceProtocolMifareUl) {
|
||||
application_info_present = nfc_supported_card_verify_and_parse(dev_data);
|
||||
}
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ const BtHidKeyboardKey bt_hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = {
|
|||
{.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS},
|
||||
},
|
||||
{
|
||||
{.width = 1, .icon = &I_Pin_arrow_up7x9, .value = HID_KEYBOARD_L_SHIFT},
|
||||
{.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT},
|
||||
{.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYPAD_COMMA},
|
||||
{.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT},
|
||||
{.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR},
|
||||
|
|
|
@ -53,7 +53,7 @@ static void bt_hid_mouse_draw_callback(Canvas* canvas, void* context) {
|
|||
canvas_set_bitmap_mode(canvas, 0);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up7x9);
|
||||
canvas_draw_icon(canvas, 84, 10, &I_Pin_arrow_up_7x9);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
// Down
|
||||
|
|
|
@ -117,7 +117,7 @@ static void desktop_view_pin_input_draw_cells(Canvas* canvas, DesktopViewPinInpu
|
|||
canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_down_7x9);
|
||||
break;
|
||||
case InputKeyUp:
|
||||
canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up7x9);
|
||||
canvas_draw_icon(canvas, x + 3, y + 2, &I_Pin_arrow_up_7x9);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
canvas_draw_icon(canvas, x + 2, y + 3, &I_Pin_arrow_left_9x7);
|
||||
|
|
Before Width: | Height: | Size: 3.7 KiB |
BIN
assets/icons/NFC/Keychain_39x36.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
assets/icons/NFC/Reader_detect_43x40.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
@ -4162,7 +4162,7 @@ Variable,+,I_KeyBackspaceSelected_16x9,const Icon,
|
|||
Variable,+,I_KeyBackspace_16x9,const Icon,
|
||||
Variable,+,I_KeySaveSelected_24x11,const Icon,
|
||||
Variable,+,I_KeySave_24x11,const Icon,
|
||||
Variable,+,I_Keychain,const Icon,
|
||||
Variable,+,I_Keychain_39x36,const Icon,
|
||||
Variable,+,I_Left_mouse_icon_9x9,const Icon,
|
||||
Variable,+,I_Lock_7x8,const Icon,
|
||||
Variable,+,I_Lock_8x8,const Icon,
|
||||
|
@ -4172,7 +4172,7 @@ Variable,+,I_Mode_25x27,const Icon,
|
|||
Variable,+,I_Mode_hvr_25x27,const Icon,
|
||||
Variable,+,I_Mute_25x27,const Icon,
|
||||
Variable,+,I_Mute_hvr_25x27,const Icon,
|
||||
Variable,+,I_NFC_manual,const Icon,
|
||||
Variable,+,I_NFC_manual_60x50,const Icon,
|
||||
Variable,+,I_Nfc_10px,const Icon,
|
||||
Variable,+,I_Ok_btn_9x9,const Icon,
|
||||
Variable,+,I_Ok_btn_pressed_13x13,const Icon,
|
||||
|
@ -4180,7 +4180,7 @@ Variable,+,I_Percent_10x14,const Icon,
|
|||
Variable,+,I_Pin_arrow_down_7x9,const Icon,
|
||||
Variable,+,I_Pin_arrow_left_9x7,const Icon,
|
||||
Variable,+,I_Pin_arrow_right_9x7,const Icon,
|
||||
Variable,+,I_Pin_arrow_up7x9,const Icon,
|
||||
Variable,+,I_Pin_arrow_up_7x9,const Icon,
|
||||
Variable,+,I_Pin_attention_dpad_29x29,const Icon,
|
||||
Variable,+,I_Pin_back_arrow_10x8,const Icon,
|
||||
Variable,+,I_Pin_back_full_40x8,const Icon,
|
||||
|
|
|
|
@ -86,38 +86,30 @@ void mf_classic_dict_free(MfClassicDict* dict) {
|
|||
free(dict);
|
||||
}
|
||||
|
||||
static void mf_classic_dict_int_to_str(uint8_t* key_int, string_t key_str) {
|
||||
string_reset(key_str);
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
string_cat_printf(key_str, "%02X", key_int[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void mf_classic_dict_str_to_int(string_t key_str, uint64_t* key_int) {
|
||||
uint8_t key_byte_tmp;
|
||||
|
||||
*key_int = 0ULL;
|
||||
for(uint8_t i = 0; i < 12; i += 2) {
|
||||
args_char_to_hex(
|
||||
string_get_char(key_str, i), string_get_char(key_str, i + 1), &key_byte_tmp);
|
||||
*key_int |= (uint8_t)key_byte_tmp << 8 * (5 - i / 2);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) {
|
||||
furi_assert(dict);
|
||||
|
||||
return dict->total_keys;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
uint8_t key_byte_tmp = 0;
|
||||
string_t next_line;
|
||||
string_init(next_line);
|
||||
|
||||
bool key_read = false;
|
||||
*key = 0ULL;
|
||||
while(!key_read) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(string_get_char(next_line, 0) == '#') continue;
|
||||
if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
for(uint8_t i = 0; i < 12; i += 2) {
|
||||
args_char_to_hex(
|
||||
string_get_char(next_line, i), string_get_char(next_line, i + 1), &key_byte_tmp);
|
||||
*key |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2);
|
||||
}
|
||||
key_read = true;
|
||||
}
|
||||
|
||||
string_clear(next_line);
|
||||
return key_read;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_rewind(MfClassicDict* dict) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
@ -125,24 +117,194 @@ bool mf_classic_dict_rewind(MfClassicDict* dict) {
|
|||
return stream_rewind(dict->stream);
|
||||
}
|
||||
|
||||
bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) {
|
||||
bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t key_str;
|
||||
string_init(key_str);
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
string_cat_printf(key_str, "%02X", key[i]);
|
||||
bool key_read = false;
|
||||
string_reset(key);
|
||||
while(!key_read) {
|
||||
if(!stream_read_line(dict->stream, key)) break;
|
||||
if(string_get_char(key, 0) == '#') continue;
|
||||
if(string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
string_left(key, 12);
|
||||
key_read = true;
|
||||
}
|
||||
string_cat_printf(key_str, "\n");
|
||||
|
||||
return key_read;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t temp_key;
|
||||
string_init(temp_key);
|
||||
bool key_read = mf_classic_dict_get_next_key_str(dict, temp_key);
|
||||
if(key_read) {
|
||||
mf_classic_dict_str_to_int(temp_key, key);
|
||||
}
|
||||
string_clear(temp_key);
|
||||
return key_read;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, string_t key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t next_line;
|
||||
string_init(next_line);
|
||||
|
||||
bool key_found = false;
|
||||
stream_rewind(dict->stream);
|
||||
while(!key_found) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(string_get_char(next_line, 0) == '#') continue;
|
||||
if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
string_left(next_line, 12);
|
||||
if(!string_equal_p(key, next_line)) continue;
|
||||
key_found = true;
|
||||
}
|
||||
|
||||
string_clear(next_line);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) {
|
||||
string_t temp_key;
|
||||
|
||||
string_init(temp_key);
|
||||
mf_classic_dict_int_to_str(key, temp_key);
|
||||
bool key_found = mf_classic_dict_is_key_present_str(dict, temp_key);
|
||||
string_clear(temp_key);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_cat_printf(key, "\n");
|
||||
|
||||
bool key_added = false;
|
||||
do {
|
||||
if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break;
|
||||
if(!stream_insert_string(dict->stream, key_str)) break;
|
||||
if(!stream_insert_string(dict->stream, key)) break;
|
||||
dict->total_keys++;
|
||||
key_added = true;
|
||||
} while(false);
|
||||
|
||||
string_clear(key_str);
|
||||
string_left(key, 12);
|
||||
return key_added;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t temp_key;
|
||||
string_init(temp_key);
|
||||
mf_classic_dict_int_to_str(key, temp_key);
|
||||
bool key_added = mf_classic_dict_add_key_str(dict, temp_key);
|
||||
|
||||
string_clear(temp_key);
|
||||
return key_added;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t next_line;
|
||||
uint32_t index = 0;
|
||||
string_init(next_line);
|
||||
string_reset(key);
|
||||
|
||||
bool key_found = false;
|
||||
while(!key_found) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(string_get_char(next_line, 0) == '#') continue;
|
||||
if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
if(index++ != target) continue;
|
||||
string_set_n(key, next_line, 0, 12);
|
||||
key_found = true;
|
||||
}
|
||||
|
||||
string_clear(next_line);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t temp_key;
|
||||
string_init(temp_key);
|
||||
bool key_found = mf_classic_dict_get_key_at_index_str(dict, temp_key, target);
|
||||
if(key_found) {
|
||||
mf_classic_dict_str_to_int(temp_key, key);
|
||||
}
|
||||
string_clear(temp_key);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t next_line;
|
||||
string_init(next_line);
|
||||
|
||||
bool key_found = false;
|
||||
uint32_t index = 0;
|
||||
stream_rewind(dict->stream);
|
||||
while(!key_found) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(string_get_char(next_line, 0) == '#') continue;
|
||||
if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
string_left(next_line, 12);
|
||||
if(!string_equal_p(key, next_line)) continue;
|
||||
key_found = true;
|
||||
*target = index;
|
||||
}
|
||||
|
||||
string_clear(next_line);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t temp_key;
|
||||
string_init(temp_key);
|
||||
mf_classic_dict_int_to_str(key, temp_key);
|
||||
bool key_found = mf_classic_dict_find_index_str(dict, temp_key, target);
|
||||
|
||||
string_clear(temp_key);
|
||||
return key_found;
|
||||
}
|
||||
|
||||
bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) {
|
||||
furi_assert(dict);
|
||||
furi_assert(dict->stream);
|
||||
|
||||
string_t next_line;
|
||||
string_init(next_line);
|
||||
uint32_t index = 0;
|
||||
|
||||
bool key_removed = false;
|
||||
while(!key_removed) {
|
||||
if(!stream_read_line(dict->stream, next_line)) break;
|
||||
if(string_get_char(next_line, 0) == '#') continue;
|
||||
if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
|
||||
if(index++ != target) continue;
|
||||
stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent);
|
||||
if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break;
|
||||
dict->total_keys--;
|
||||
key_removed = true;
|
||||
}
|
||||
|
||||
string_clear(next_line);
|
||||
return key_removed;
|
||||
}
|
||||
|
|
|
@ -21,8 +21,26 @@ void mf_classic_dict_free(MfClassicDict* dict);
|
|||
|
||||
uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict);
|
||||
|
||||
bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key);
|
||||
|
||||
bool mf_classic_dict_rewind(MfClassicDict* dict);
|
||||
|
||||
bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key);
|
||||
|
||||
bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, string_t key);
|
||||
|
||||
bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key);
|
||||
|
||||
bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, string_t key);
|
||||
|
||||
bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target);
|
||||
|
||||
bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, string_t key, uint32_t target);
|
||||
|
||||
bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key);
|
||||
|
||||
bool mf_classic_dict_add_key_str(MfClassicDict* dict, string_t key);
|
||||
|
||||
bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target);
|
||||
|
||||
bool mf_classic_dict_find_index_str(MfClassicDict* dict, string_t key, uint32_t* target);
|
||||
|
||||
bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target);
|
||||
|
|
|
@ -123,7 +123,25 @@ static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxC
|
|||
}
|
||||
|
||||
do {
|
||||
// Read card
|
||||
// Try to read supported card
|
||||
FURI_LOG_I(TAG, "Trying to read a supported card ...");
|
||||
for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) {
|
||||
if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareUl) {
|
||||
if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) {
|
||||
if(nfc_supported_card[i].read(nfc_worker, tx_rx)) {
|
||||
read_success = true;
|
||||
nfc_supported_card[i].parse(nfc_worker->dev_data);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
furi_hal_nfc_sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(read_success) break;
|
||||
furi_hal_nfc_sleep();
|
||||
|
||||
// Otherwise, try to read as usual
|
||||
if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break;
|
||||
if(!mf_ul_read_card(tx_rx, &reader, &data)) break;
|
||||
// Copy data
|
||||
|
@ -149,14 +167,17 @@ static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxCont
|
|||
|
||||
do {
|
||||
// Try to read supported card
|
||||
FURI_LOG_I(TAG, "Try read supported card ...");
|
||||
FURI_LOG_I(TAG, "Trying to read a supported card ...");
|
||||
for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) {
|
||||
if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareClassic) {
|
||||
if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) {
|
||||
if(nfc_supported_card[i].read(nfc_worker, tx_rx)) {
|
||||
read_success = true;
|
||||
nfc_supported_card[i].parse(nfc_worker->dev_data);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
furi_hal_nfc_sleep();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
113
lib/nfc/parsers/all_in_one.c
Normal file
|
@ -0,0 +1,113 @@
|
|||
#include "nfc_supported_card.h"
|
||||
#include "all_in_one.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
|
||||
#define ALL_IN_ONE_LAYOUT_UNKNOWN 0
|
||||
#define ALL_IN_ONE_LAYOUT_A 1
|
||||
#define ALL_IN_ONE_LAYOUT_D 2
|
||||
#define ALL_IN_ONE_LAYOUT_E2 3
|
||||
#define ALL_IN_ONE_LAYOUT_E3 4
|
||||
#define ALL_IN_ONE_LAYOUT_E5 5
|
||||
#define ALL_IN_ONE_LAYOUT_2 6
|
||||
|
||||
uint8_t all_in_one_get_layout(NfcDeviceData* dev_data) {
|
||||
// I absolutely hate what's about to happen here.
|
||||
|
||||
// Switch on the second half of the third byte of page 5
|
||||
FURI_LOG_I("all_in_one", "Layout byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 2]);
|
||||
FURI_LOG_I(
|
||||
"all_in_one", "Layout half-byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 3] & 0x0F);
|
||||
switch(dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F) {
|
||||
// If it is A, the layout type is a type A layout
|
||||
case 0x0A:
|
||||
return ALL_IN_ONE_LAYOUT_A;
|
||||
case 0x0D:
|
||||
return ALL_IN_ONE_LAYOUT_D;
|
||||
case 0x02:
|
||||
return ALL_IN_ONE_LAYOUT_2;
|
||||
default:
|
||||
FURI_LOG_I(
|
||||
"all_in_one",
|
||||
"Unknown layout type: %d",
|
||||
dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F);
|
||||
return ALL_IN_ONE_LAYOUT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
UNUSED(nfc_worker);
|
||||
// If this is a all_in_one pass, first 2 bytes of page 4 are 0x45 0xD9
|
||||
MfUltralightReader reader = {};
|
||||
MfUltralightData data = {};
|
||||
|
||||
if(!mf_ul_read_card(tx_rx, &reader, &data)) {
|
||||
return false;
|
||||
} else {
|
||||
if(data.data[4 * 4] == 0x45 && data.data[4 * 4 + 1] == 0xD9) {
|
||||
FURI_LOG_I("all_in_one", "Pass verified");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
MfUltralightReader reader = {};
|
||||
MfUltralightData data = {};
|
||||
if(!mf_ul_read_card(tx_rx, &reader, &data)) {
|
||||
return false;
|
||||
} else {
|
||||
memcpy(&nfc_worker->dev_data->mf_ul_data, &data, sizeof(data));
|
||||
FURI_LOG_I("all_in_one", "Card read");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool all_in_one_parser_parse(NfcDeviceData* dev_data) {
|
||||
if(dev_data->mf_ul_data.data[4 * 4] != 0x45 || dev_data->mf_ul_data.data[4 * 4 + 1] != 0xD9) {
|
||||
FURI_LOG_I("all_in_one", "Pass not verified");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the layout is a then the ride count is stored in the first byte of page 8
|
||||
uint8_t ride_count = 0;
|
||||
uint32_t serial = 0;
|
||||
if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_A) {
|
||||
ride_count = dev_data->mf_ul_data.data[4 * 8];
|
||||
} else if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_D) {
|
||||
// If the layout is D, the ride count is stored in the second byte of page 9
|
||||
ride_count = dev_data->mf_ul_data.data[4 * 9 + 1];
|
||||
// I hate this with a burning passion.
|
||||
|
||||
// The number starts at the second half of the third byte on page 4, and is 32 bits long
|
||||
// So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte
|
||||
// B8 17 A2 A4 BD becomes 81 7A 2A 4B
|
||||
serial = (dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 |
|
||||
dev_data->mf_ul_data.data[4 * 4 + 3] << 20 |
|
||||
dev_data->mf_ul_data.data[4 * 4 + 4] << 12 |
|
||||
dev_data->mf_ul_data.data[4 * 4 + 5] << 4 |
|
||||
(dev_data->mf_ul_data.data[4 * 4 + 6] >> 4);
|
||||
} else {
|
||||
FURI_LOG_I("all_in_one", "Unknown layout: %d", all_in_one_get_layout(dev_data));
|
||||
ride_count = 137;
|
||||
}
|
||||
|
||||
// I hate this with a burning passion.
|
||||
|
||||
// The number starts at the second half of the third byte on page 4, and is 32 bits long
|
||||
// So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte
|
||||
// B8 17 A2 A4 BD becomes 81 7A 2A 4B
|
||||
serial =
|
||||
(dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 |
|
||||
dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | dev_data->mf_ul_data.data[4 * 4 + 4] << 12 |
|
||||
dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4);
|
||||
|
||||
// Format string for rides count
|
||||
string_printf(
|
||||
dev_data->parsed_data, "\e#All-In-One\nNumber: %u\nRides left: %u", serial, ride_count);
|
||||
return true;
|
||||
}
|
9
lib/nfc/parsers/all_in_one.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool all_in_one_parser_parse(NfcDeviceData* dev_data);
|
|
@ -1,14 +1,54 @@
|
|||
#include "nfc_supported_card.h"
|
||||
|
||||
#include "troyka_parser.h"
|
||||
#include "plantain_parser.h"
|
||||
#include "troika_parser.h"
|
||||
#include "plantain_4k_parser.h"
|
||||
#include "troika_4k_parser.h"
|
||||
#include "two_cities.h"
|
||||
#include "all_in_one.h"
|
||||
|
||||
NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = {
|
||||
[NfcSupportedCardTypeTroyka] =
|
||||
[NfcSupportedCardTypePlantain] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = troyka_parser_verify,
|
||||
.read = troyka_parser_read,
|
||||
.parse = troyka_parser_parse,
|
||||
.verify = plantain_parser_verify,
|
||||
.read = plantain_parser_read,
|
||||
.parse = plantain_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeTroika] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = troika_parser_verify,
|
||||
.read = troika_parser_read,
|
||||
.parse = troika_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypePlantain4K] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = plantain_4k_parser_verify,
|
||||
.read = plantain_4k_parser_read,
|
||||
.parse = plantain_4k_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeTroika4K] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = troika_4k_parser_verify,
|
||||
.read = troika_4k_parser_read,
|
||||
.parse = troika_4k_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeTwoCities] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareClassic,
|
||||
.verify = two_cities_parser_verify,
|
||||
.read = two_cities_parser_read,
|
||||
.parse = two_cities_parser_parse,
|
||||
},
|
||||
[NfcSupportedCardTypeAllInOne] =
|
||||
{
|
||||
.protocol = NfcDeviceProtocolMifareUl,
|
||||
.verify = all_in_one_parser_verify,
|
||||
.read = all_in_one_parser_read,
|
||||
.parse = all_in_one_parser_parse,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
#include <m-string.h>
|
||||
|
||||
typedef enum {
|
||||
NfcSupportedCardTypeTroyka,
|
||||
NfcSupportedCardTypePlantain,
|
||||
NfcSupportedCardTypeTroika,
|
||||
NfcSupportedCardTypePlantain4K,
|
||||
NfcSupportedCardTypeTroika4K,
|
||||
NfcSupportedCardTypeTwoCities,
|
||||
NfcSupportedCardTypeAllInOne,
|
||||
|
||||
NfcSupportedCardTypeEnd,
|
||||
} NfcSupportedCardType;
|
||||
|
|
153
lib/nfc/parsers/plantain_4k_parser.c
Normal file
|
@ -0,0 +1,153 @@
|
|||
#include "nfc_supported_card.h"
|
||||
#include "plantain_parser.h" // For luhn and string_push_uint64
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
|
||||
static const MfClassicAuthContext plantain_keys_4k[] = {
|
||||
{.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF},
|
||||
{.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b},
|
||||
{.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a},
|
||||
{.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7},
|
||||
{.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3},
|
||||
{.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb},
|
||||
{.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56},
|
||||
{.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26},
|
||||
{.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf},
|
||||
{.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a},
|
||||
{.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b},
|
||||
{.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3},
|
||||
{.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22},
|
||||
{.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056},
|
||||
{.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510},
|
||||
{.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf},
|
||||
{.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439},
|
||||
{.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406},
|
||||
{.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3},
|
||||
{.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA},
|
||||
{.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f},
|
||||
{.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3},
|
||||
{.sector = 30, .key_a = 0x0eb23cc8110b, .key_b = 0x04dc35277635},
|
||||
{.sector = 31, .key_a = 0xbc4580b7f20b, .key_b = 0xd0a4131fb290},
|
||||
{.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023},
|
||||
{.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9},
|
||||
{.sector = 34, .key_a = 0xfd8705e721b0, .key_b = 0x296fc317a513},
|
||||
{.sector = 35, .key_a = 0x22052b480d11, .key_b = 0xe19504c39461},
|
||||
{.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7},
|
||||
{.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a},
|
||||
{.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7},
|
||||
{.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085},
|
||||
};
|
||||
|
||||
bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 8;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Plant4K", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Plant4K", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(plantain_keys_4k); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader,
|
||||
plantain_keys_4k[i].sector,
|
||||
plantain_keys_4k[i].key_a,
|
||||
plantain_keys_4k[i].key_b);
|
||||
FURI_LOG_T("plant4k", "Added sector %d", plantain_keys_4k[i].sector);
|
||||
}
|
||||
for(int i = 0; i < 5; i++) {
|
||||
if(mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool plantain_4k_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != plantain_keys_4k[8].key_a) return false;
|
||||
|
||||
// Point to block 0 of sector 4, value 0
|
||||
uint8_t* temp_ptr = &data->block[4 * 4].value[0];
|
||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
||||
uint32_t balance =
|
||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
||||
// Read card number
|
||||
// Point to block 0 of sector 0, value 0
|
||||
temp_ptr = &data->block[0 * 4].value[0];
|
||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
||||
// 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal
|
||||
uint8_t card_number_arr[7];
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_arr[i] = temp_ptr[6 - i];
|
||||
}
|
||||
// Copy card number to uint64_t
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number = (card_number << 8) | card_number_arr[i];
|
||||
}
|
||||
// Convert card number to string
|
||||
string_t card_number_str;
|
||||
string_init(card_number_str);
|
||||
// Should look like "361301047292848684"
|
||||
// %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead
|
||||
string_push_uint64(card_number, card_number_str);
|
||||
// Add suffix with luhn checksum (1 digit) to the card number string
|
||||
string_t card_number_suffix;
|
||||
string_init(card_number_suffix);
|
||||
|
||||
// The number to calculate the checksum on doesn't fit into uint64_t, idk
|
||||
//uint8_t luhn_checksum = plantain_calculate_luhn(card_number);
|
||||
|
||||
// // Convert luhn checksum to string
|
||||
// string_t luhn_checksum_str;
|
||||
// string_init(luhn_checksum_str);
|
||||
// string_push_uint64(luhn_checksum, luhn_checksum_str);
|
||||
|
||||
string_cat_printf(card_number_suffix, "-");
|
||||
// FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum);
|
||||
string_cat_printf(card_number_str, string_get_cstr(card_number_suffix));
|
||||
// Free all not needed strings
|
||||
string_clear(card_number_suffix);
|
||||
// string_clear(luhn_checksum_str);
|
||||
|
||||
string_printf(
|
||||
dev_data->parsed_data,
|
||||
"\e#Plantain\nN:%s\nBalance:%d\n",
|
||||
string_get_cstr(card_number_str),
|
||||
balance);
|
||||
string_clear(card_number_str);
|
||||
|
||||
return true;
|
||||
}
|
9
lib/nfc/parsers/plantain_4k_parser.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_4k_parser_parse(NfcDeviceData* dev_data);
|
147
lib/nfc/parsers/plantain_parser.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
#include "nfc_supported_card.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
|
||||
static const MfClassicAuthContext plantain_keys[] = {
|
||||
{.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b},
|
||||
{.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a},
|
||||
{.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7},
|
||||
{.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3},
|
||||
{.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb},
|
||||
{.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56},
|
||||
{.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26},
|
||||
{.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
};
|
||||
|
||||
bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 8;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Plant", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Plant", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(plantain_keys); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader, plantain_keys[i].sector, plantain_keys[i].key_a, plantain_keys[i].key_b);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16;
|
||||
}
|
||||
|
||||
void string_push_uint64(uint64_t input, string_t output) {
|
||||
const uint8_t base = 10;
|
||||
|
||||
do {
|
||||
char c = input % base;
|
||||
input /= base;
|
||||
|
||||
if(c < 10)
|
||||
c += '0';
|
||||
else
|
||||
c += 'A' - 10;
|
||||
string_push_back(output, c);
|
||||
} while(input);
|
||||
|
||||
// reverse string
|
||||
for(uint8_t i = 0; i < string_size(output) / 2; i++) {
|
||||
char c = string_get_char(output, i);
|
||||
string_set_char(output, i, string_get_char(output, string_size(output) - i - 1));
|
||||
string_set_char(output, string_size(output) - i - 1, c);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t plantain_calculate_luhn(uint64_t number) {
|
||||
// No.
|
||||
UNUSED(number);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool plantain_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != plantain_keys[8].key_a) return false;
|
||||
|
||||
// Point to block 0 of sector 4, value 0
|
||||
uint8_t* temp_ptr = &data->block[4 * 4].value[0];
|
||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
||||
uint32_t balance =
|
||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
||||
// Read card number
|
||||
// Point to block 0 of sector 0, value 0
|
||||
temp_ptr = &data->block[0 * 4].value[0];
|
||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
||||
// 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal
|
||||
uint8_t card_number_arr[7];
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_arr[i] = temp_ptr[6 - i];
|
||||
}
|
||||
// Copy card number to uint64_t
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number = (card_number << 8) | card_number_arr[i];
|
||||
}
|
||||
// Convert card number to string
|
||||
string_t card_number_str;
|
||||
string_init(card_number_str);
|
||||
// Should look like "361301047292848684"
|
||||
// %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead
|
||||
string_push_uint64(card_number, card_number_str);
|
||||
// Add suffix with luhn checksum (1 digit) to the card number string
|
||||
string_t card_number_suffix;
|
||||
string_init(card_number_suffix);
|
||||
|
||||
// The number to calculate the checksum on doesn't fit into uint64_t, idk
|
||||
//uint8_t luhn_checksum = plantain_calculate_luhn(card_number);
|
||||
|
||||
// // Convert luhn checksum to string
|
||||
// string_t luhn_checksum_str;
|
||||
// string_init(luhn_checksum_str);
|
||||
// string_push_uint64(luhn_checksum, luhn_checksum_str);
|
||||
|
||||
string_cat_printf(card_number_suffix, "-");
|
||||
// FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum);
|
||||
string_cat_printf(card_number_str, string_get_cstr(card_number_suffix));
|
||||
// Free all not needed strings
|
||||
string_clear(card_number_suffix);
|
||||
// string_clear(luhn_checksum_str);
|
||||
|
||||
string_printf(
|
||||
dev_data->parsed_data,
|
||||
"\e#Plantain\nN:%s\nBalance:%d\n",
|
||||
string_get_cstr(card_number_str),
|
||||
balance);
|
||||
string_clear(card_number_str);
|
||||
|
||||
return true;
|
||||
}
|
13
lib/nfc/parsers/plantain_parser.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool plantain_parser_parse(NfcDeviceData* dev_data);
|
||||
|
||||
void string_push_uint64(uint64_t input, string_t output);
|
||||
|
||||
uint8_t plantain_calculate_luhn(uint64_t number);
|
104
lib/nfc/parsers/troika_4k_parser.c
Normal file
|
@ -0,0 +1,104 @@
|
|||
#include "nfc_supported_card.h"
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
static const MfClassicAuthContext troika_4k_keys[] = {
|
||||
{.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
|
||||
{.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
|
||||
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5},
|
||||
{.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698},
|
||||
{.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb},
|
||||
{.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
|
||||
{.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
|
||||
{.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d},
|
||||
{.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b},
|
||||
{.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0},
|
||||
{.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
|
||||
{.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75},
|
||||
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408},
|
||||
{.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df},
|
||||
{.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9},
|
||||
{.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4},
|
||||
{.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216},
|
||||
{.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba},
|
||||
{.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252},
|
||||
{.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01},
|
||||
{.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994},
|
||||
{.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6},
|
||||
{.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32},
|
||||
{.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3},
|
||||
{.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5},
|
||||
{.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a},
|
||||
{.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80},
|
||||
{.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d},
|
||||
{.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2},
|
||||
{.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324},
|
||||
{.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae},
|
||||
{.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda},
|
||||
{.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d},
|
||||
{.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465},
|
||||
{.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4},
|
||||
{.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70},
|
||||
};
|
||||
|
||||
bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 11;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Troika", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Troika", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(troika_4k_keys); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader, troika_4k_keys[i].sector, troika_4k_keys[i].key_a, troika_4k_keys[i].key_b);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40;
|
||||
}
|
||||
|
||||
bool troika_4k_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != troika_4k_keys[4].key_a) return false;
|
||||
|
||||
// Verify card type
|
||||
if(data->type != MfClassicType4k) return false;
|
||||
|
||||
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
|
||||
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
|
||||
temp_ptr = &data->block[8 * 4].value[3];
|
||||
uint32_t number = 0;
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
number <<= 8;
|
||||
number |= temp_ptr[i];
|
||||
}
|
||||
number >>= 4;
|
||||
|
||||
string_printf(dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance);
|
||||
|
||||
return true;
|
||||
}
|
9
lib/nfc/parsers/troika_4k_parser.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_4k_parser_parse(NfcDeviceData* dev_data);
|
|
@ -3,7 +3,7 @@
|
|||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
static const MfClassicAuthContext troyka_keys[] = {
|
||||
static const MfClassicAuthContext troika_keys[] = {
|
||||
{.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58},
|
||||
{.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880},
|
||||
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
|
@ -22,42 +22,50 @@ static const MfClassicAuthContext troyka_keys[] = {
|
|||
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
};
|
||||
|
||||
bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
|
||||
MfClassicAuthContext auth_ctx = {
|
||||
.key_a = MF_CLASSIC_NO_KEY,
|
||||
.key_b = MF_CLASSIC_NO_KEY,
|
||||
.sector = 8,
|
||||
};
|
||||
return mf_classic_auth_attempt(tx_rx, &auth_ctx, 0xa73f5dc1d333);
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
uint8_t sector = 11;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("Troika", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) {
|
||||
FURI_LOG_D("Troika", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
|
||||
for(size_t i = 0; i < COUNT_OF(troyka_keys); i++) {
|
||||
for(size_t i = 0; i < COUNT_OF(troika_keys); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader, troyka_keys[i].sector, troyka_keys[i].key_a, troyka_keys[i].key_b);
|
||||
&reader, troika_keys[i].sector, troika_keys[i].key_a, troika_keys[i].key_b);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16;
|
||||
}
|
||||
|
||||
bool troyka_parser_parse(NfcDeviceData* dev_data) {
|
||||
bool troika_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
bool troyka_parsed = false;
|
||||
bool troika_parsed = false;
|
||||
|
||||
do {
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != troyka_keys[8].key_a) break;
|
||||
if(key != troika_keys[8].key_a) break;
|
||||
|
||||
// Verify card type
|
||||
if(data->type != MfClassicType1k) break;
|
||||
|
||||
// Parse data
|
||||
uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5];
|
||||
|
@ -71,9 +79,9 @@ bool troyka_parser_parse(NfcDeviceData* dev_data) {
|
|||
number >>= 4;
|
||||
|
||||
string_printf(
|
||||
dev_data->parsed_data, "\e#Troyka\nNum: %ld\nBalance: %d rur.", number, balance);
|
||||
troyka_parsed = true;
|
||||
dev_data->parsed_data, "\e#Troika\nNum: %ld\nBalance: %d rur.", number, balance);
|
||||
troika_parsed = true;
|
||||
} while(false);
|
||||
|
||||
return troyka_parsed;
|
||||
return troika_parsed;
|
||||
}
|
9
lib/nfc/parsers/troika_parser.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troika_parser_parse(NfcDeviceData* dev_data);
|
|
@ -1,9 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool troyka_parser_parse(NfcDeviceData* dev_data);
|
171
lib/nfc/parsers/two_cities.c
Normal file
|
@ -0,0 +1,171 @@
|
|||
#include "nfc_supported_card.h"
|
||||
#include "plantain_parser.h" // For plantain-specific stuff
|
||||
|
||||
#include <gui/modules/widget.h>
|
||||
#include <nfc_worker_i.h>
|
||||
|
||||
#include "furi_hal.h"
|
||||
|
||||
static const MfClassicAuthContext two_cities_keys_4k[] = {
|
||||
{.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b},
|
||||
{.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a},
|
||||
{.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81},
|
||||
{.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763},
|
||||
{.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb},
|
||||
{.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56},
|
||||
{.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26},
|
||||
{.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff},
|
||||
{.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf},
|
||||
{.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a},
|
||||
{.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b},
|
||||
{.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3},
|
||||
{.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22},
|
||||
{.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056},
|
||||
{.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510},
|
||||
{.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf},
|
||||
{.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439},
|
||||
{.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406},
|
||||
{.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3},
|
||||
{.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA},
|
||||
{.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f},
|
||||
{.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3},
|
||||
{.sector = 30, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7},
|
||||
{.sector = 31, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3},
|
||||
{.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023},
|
||||
{.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9},
|
||||
{.sector = 34, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 35, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99},
|
||||
{.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7},
|
||||
{.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a},
|
||||
{.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7},
|
||||
{.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085},
|
||||
};
|
||||
|
||||
bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
UNUSED(nfc_worker);
|
||||
|
||||
if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t sector = 4;
|
||||
uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector);
|
||||
FURI_LOG_D("2cities", "Verifying sector %d", sector);
|
||||
if(mf_classic_authenticate(tx_rx, block, 0xe56ac127dd45, MfClassicKeyA)) {
|
||||
FURI_LOG_D("2cities", "Sector %d verified", sector);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) {
|
||||
furi_assert(nfc_worker);
|
||||
|
||||
MfClassicReader reader = {};
|
||||
FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data;
|
||||
reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak);
|
||||
for(size_t i = 0; i < COUNT_OF(two_cities_keys_4k); i++) {
|
||||
mf_classic_reader_add_sector(
|
||||
&reader,
|
||||
two_cities_keys_4k[i].sector,
|
||||
two_cities_keys_4k[i].key_a,
|
||||
two_cities_keys_4k[i].key_b);
|
||||
FURI_LOG_T("2cities", "Added sector %d", two_cities_keys_4k[i].sector);
|
||||
}
|
||||
|
||||
return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40;
|
||||
}
|
||||
|
||||
bool two_cities_parser_parse(NfcDeviceData* dev_data) {
|
||||
MfClassicData* data = &dev_data->mf_classic_data;
|
||||
|
||||
// Verify key
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4);
|
||||
uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6);
|
||||
if(key != two_cities_keys_4k[4].key_a) return false;
|
||||
|
||||
// =====
|
||||
// PLANTAIN
|
||||
// =====
|
||||
|
||||
// Point to block 0 of sector 4, value 0
|
||||
uint8_t* temp_ptr = &data->block[4 * 4].value[0];
|
||||
// Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
|
||||
// 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
|
||||
uint32_t balance =
|
||||
((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
|
||||
// Read card number
|
||||
// Point to block 0 of sector 0, value 0
|
||||
temp_ptr = &data->block[0 * 4].value[0];
|
||||
// Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
|
||||
// 80 5C 23 8A 16 31 04 becomes 04 31 16 8A 23 5C 80, and equals to 36130104729284868 decimal
|
||||
uint8_t card_number_arr[7];
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_arr[i] = temp_ptr[6 - i];
|
||||
}
|
||||
// Copy card number to uint64_t
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number = (card_number << 8) | card_number_arr[i];
|
||||
}
|
||||
// Convert card number to string
|
||||
string_t card_number_str;
|
||||
string_init(card_number_str);
|
||||
// Should look like "361301047292848684"
|
||||
// %llu doesn't work for some reason in sprintf, so we use string_push_uint64 instead
|
||||
string_push_uint64(card_number, card_number_str);
|
||||
// Add suffix with luhn checksum (1 digit) to the card number string
|
||||
string_t card_number_suffix;
|
||||
string_init(card_number_suffix);
|
||||
|
||||
// The number to calculate the checksum on doesn't fit into uint64_t, idk
|
||||
//uint8_t luhn_checksum = two_cities_calculate_luhn(card_number);
|
||||
|
||||
// // Convert luhn checksum to string
|
||||
// string_t luhn_checksum_str;
|
||||
// string_init(luhn_checksum_str);
|
||||
// string_push_uint64(luhn_checksum, luhn_checksum_str);
|
||||
|
||||
string_cat_printf(card_number_suffix, "-");
|
||||
// FURI_LOG_D("plant4k", "Card checksum: %d", luhn_checksum);
|
||||
string_cat_printf(card_number_str, string_get_cstr(card_number_suffix));
|
||||
// Free all not needed strings
|
||||
string_clear(card_number_suffix);
|
||||
// string_clear(luhn_checksum_str);
|
||||
|
||||
// =====
|
||||
// --PLANTAIN--
|
||||
// =====
|
||||
// TROIKA
|
||||
// =====
|
||||
|
||||
uint8_t* troika_temp_ptr = &data->block[8 * 4 + 1].value[5];
|
||||
uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25;
|
||||
troika_temp_ptr = &data->block[8 * 4].value[3];
|
||||
uint32_t troika_number = 0;
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
troika_number <<= 8;
|
||||
troika_number |= troika_temp_ptr[i];
|
||||
}
|
||||
troika_number >>= 4;
|
||||
|
||||
string_printf(
|
||||
dev_data->parsed_data,
|
||||
"\e#Troika+Plantain\nPN: %s\nPB: %d rur.\nTN: %d\nTB: %d rur.\n",
|
||||
string_get_cstr(card_number_str),
|
||||
balance,
|
||||
troika_number,
|
||||
troika_balance);
|
||||
string_clear(card_number_str);
|
||||
|
||||
return true;
|
||||
}
|
9
lib/nfc/parsers/two_cities.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "nfc_supported_card.h"
|
||||
|
||||
bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx);
|
||||
|
||||
bool two_cities_parser_parse(NfcDeviceData* dev_data);
|