From 54f63e06ec72ec3cd99fd29b7122e3a5030ecb90 Mon Sep 17 00:00:00 2001 From: Astra Date: Mon, 20 May 2024 19:08:54 +0900 Subject: [PATCH 01/28] Fixes from code review --- lib/nfc/protocols/mf_plus/mf_plus.c | 1 + lib/nfc/protocols/mf_plus/mf_plus_i.c | 196 +++++++++++++++ lib/nfc/protocols/mf_plus/mf_plus_i.h | 6 + lib/nfc/protocols/mf_plus/mf_plus_poller.c | 249 ++++--------------- lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 11 +- lib/nfc/protocols/mf_plus/mf_plus_poller_i.h | 2 + 6 files changed, 258 insertions(+), 207 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus.c b/lib/nfc/protocols/mf_plus/mf_plus.c index 98b7cd82a..5e3fe1b34 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus.c +++ b/lib/nfc/protocols/mf_plus/mf_plus.c @@ -70,6 +70,7 @@ void mf_plus_reset(MfPlusData* data) { iso14443_4a_reset(data->iso14443_4a_data); memset(&data->version, 0, sizeof(data->version)); + furi_string_reset(data->device_name); data->type = MfPlusTypeUnknown; data->security_level = MfPlusSecurityLevelUnknown; data->size = MfPlusSizeUnknown; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index c248d2568..fa7292abd 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -8,6 +8,202 @@ #define MF_PLUS_FFF_CARD_TYPE_KEY "Card Type" #define MF_PLUS_FFF_MEMORY_SIZE_KEY "Memory Size" +#define TAG "MfPlus" + +const uint8_t mf_plus_ats_t1_tk_values[][7] = { + {0xC1, 0x05, 0x2F, 0x2F, 0x00, 0x35, 0xC7}, // Mifare Plus S + {0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xBC, 0xD6}, // Mifare Plus X + {0xC1, 0x05, 0x2F, 0x2F, 0x00, 0xF6, 0xD1}, // Mifare Plus SE + {0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xF6, 0xD1}, // Mifare Plus SE +}; + +bool mf_plus_get_type_from_version( + const Iso14443_4aData* iso14443_4a_data, + MfPlusData* mf_plus_data) { + furi_assert(iso14443_4a_data); + furi_assert(mf_plus_data); + + bool detected = false; + + if(mf_plus_data->version.hw_major == 0x02 || mf_plus_data->version.hw_major == 0x82) { + detected = true; + if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) { + // Mifare Plus 2K SL2 + mf_plus_data->type = MfPlusTypePlus; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel2; + } else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) { + // Mifare Plus 4K SL3 + mf_plus_data->type = MfPlusTypePlus; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel3; + } else { + // Mifare Plus EV1/EV2 + + // Revision + switch(mf_plus_data->version.hw_major) { + case 0x11: + mf_plus_data->type = MfPlusTypeEV1; + break; + case 0x22: + mf_plus_data->type = MfPlusTypeEV2; + break; + default: + mf_plus_data->type = MfPlusTypeUnknown; + break; + } + + // Storage size + switch(mf_plus_data->version.hw_storage) { + case 0x16: + mf_plus_data->size = MfPlusSize2K; + break; + case 0x18: + mf_plus_data->size = MfPlusSize4K; + break; + default: + mf_plus_data->size = MfPlusSizeUnknown; + break; + } + + // Security level + if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) { + // Mifare Plus EV1/2 SL3 + mf_plus_data->security_level = MfPlusSecurityLevel3; + } else { + // Mifare Plus EV1/2 SL1 + mf_plus_data->security_level = MfPlusSecurityLevel1; + } + } + } + + return detected; +} + +bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) { + furi_assert(iso4_data); + furi_assert(mf_plus_data); + + switch(iso4_data->iso14443_3a_data->sak) { + case 0x08: + if(memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 2K SL1 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus X 2K SL1 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[2], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 || + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[3], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus SE 1K SL1 + mf_plus_data->type = MfPlusTypeSE; + mf_plus_data->size = MfPlusSize1K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus SE 1K SL1"); + return true; + } else { + FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type"); + return false; + } + case 0x18: + if(memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 4K SL1 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus X 4K SL1 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL1"); + return true; + } else { + FURI_LOG_D(TAG, "Sak 18 but no known Mifare Plus type"); + return false; + } + case 0x20: + if(memcmp( + iso4_data->ats_data.t1_tk, + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 2/4K SL3 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + if(iso4_data->iso14443_3a_data->atqa[1] & 0x04) { + // Mifare Plus S 2K SL3 + mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL3"); + } else if(iso4_data->iso14443_3a_data->atqa[1] & 0x02) { + // Mifare Plus S 4K SL3 + mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL3"); + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)"); + return false; + } + return true; + } else if( + memcmp( + iso4_data->ats_data.t1_tk, + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + // if atqa is 0xXX 0xX4 (other digits are not important) + if(iso4_data->iso14443_3a_data->atqa[1] & 0x04) { + mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); + } else if(iso4_data->iso14443_3a_data->atqa[1] & 0x02) { + mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); + return false; + } + return true; + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); + return false; + } + } + + FURI_LOG_D(TAG, "No known Mifare Plus type"); + return false; +} + bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h index 8ced4bdd0..bf36b9060 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -4,6 +4,12 @@ #define MF_PLUS_FFF_PICC_PREFIX "PICC" +bool mf_plus_get_type_from_version( + const Iso14443_4aData* iso14443_4a_data, + MfPlusData* mf_plus_data); + +bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data); + bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf); bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf); diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index a218229c5..37a4f723f 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -1,4 +1,5 @@ #include "mf_plus_poller_i.h" +#include "mf_plus_i.h" #include @@ -9,13 +10,6 @@ #define MF_PLUS_BUF_SIZE (64U) #define MF_PLUS_RESULT_BUF_SIZE (512U) -const char* mf_plus_ats_t1_tk_values[] = { - "\xC1\x05\x2F\x2F\x00\x35\xC7", // Mifare Plus S - "\xC1\x05\x2F\x2F\x01\xBC\xD6", // Mifare Plus X - "\xC1\x05\x2F\x2F\x00\xF6\xD1", // Mifare Plus SE - "\xC1\x05\x2F\x2F\x01\xF6\xD1", // Mifare Plus SE -}; - typedef NfcCommand (*MfPlusPollerReadHandler)(MfPlusPoller* instance); const MfPlusData* mf_plus_poller_get_data(MfPlusPoller* instance) { @@ -24,200 +18,6 @@ const MfPlusData* mf_plus_poller_get_data(MfPlusPoller* instance) { return instance->data; } -bool mf_plus_poller_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) { - furi_assert(iso4_data); - furi_assert(mf_plus_data); - - switch(iso4_data->iso14443_3a_data->sak) { - case 0x08: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - // Mifare Plus S 2K SL1 - mf_plus_data->type = MfPlusTypeS; - mf_plus_data->size = MfPlusSize2K; - mf_plus_data->security_level = MfPlusSecurityLevel1; - FURI_LOG_D(TAG, "Mifare Plus S 2K SL1"); - return true; - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - // Mifare Plus X 2K SL1 - mf_plus_data->type = MfPlusTypeX; - mf_plus_data->size = MfPlusSize2K; - mf_plus_data->security_level = MfPlusSecurityLevel1; - FURI_LOG_D(TAG, "Mifare Plus X 2K SL1"); - return true; - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[2], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 || - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[3], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - // Mifare Plus SE 1K SL1 - mf_plus_data->type = MfPlusTypeSE; - mf_plus_data->size = MfPlusSize1K; - mf_plus_data->security_level = MfPlusSecurityLevel1; - FURI_LOG_D(TAG, "Mifare Plus SE 1K SL1"); - return true; - } else { - FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type"); - return false; - } - case 0x18: - if(memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - // Mifare Plus S 4K SL1 - mf_plus_data->type = MfPlusTypeS; - mf_plus_data->size = MfPlusSize4K; - mf_plus_data->security_level = MfPlusSecurityLevel1; - FURI_LOG_D(TAG, "Mifare Plus S 4K SL1"); - return true; - } else if( - memcmp( - simple_array_get_data(iso4_data->ats_data.t1_tk), - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - // Mifare Plus X 4K SL1 - mf_plus_data->type = MfPlusTypeX; - mf_plus_data->size = MfPlusSize4K; - mf_plus_data->security_level = MfPlusSecurityLevel1; - FURI_LOG_D(TAG, "Mifare Plus X 4K SL1"); - return true; - } else { - FURI_LOG_D(TAG, "Sak 18 but no known Mifare Plus type"); - return false; - } - case 0x20: - if(memcmp( - iso4_data->ats_data.t1_tk, - mf_plus_ats_t1_tk_values[0], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - // Mifare Plus S 2/4K SL3 - mf_plus_data->type = MfPlusTypeS; - mf_plus_data->security_level = MfPlusSecurityLevel3; - - if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) { - // Mifare Plus S 2K SL3 - mf_plus_data->size = MfPlusSize2K; - FURI_LOG_D(TAG, "Mifare Plus S 2K SL3"); - } else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) { - // Mifare Plus S 4K SL3 - mf_plus_data->size = MfPlusSize4K; - FURI_LOG_D(TAG, "Mifare Plus S 4K SL3"); - } else { - FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)"); - return false; - } - return true; - - } else if( - memcmp( - iso4_data->ats_data.t1_tk, - mf_plus_ats_t1_tk_values[1], - simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - mf_plus_data->type = MfPlusTypeX; - mf_plus_data->security_level = MfPlusSecurityLevel3; - - if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) { - mf_plus_data->size = MfPlusSize2K; - FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); - } else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) { - mf_plus_data->size = MfPlusSize4K; - FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); - } else { - FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); - return false; - } - return true; - } else { - FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); - return false; - } - } - - FURI_LOG_D(TAG, "No known Mifare Plus type"); - return false; -} - -static bool mf_plus_poller_detect_type(MfPlusPoller* instance) { - furi_assert(instance); - - bool detected = false; - - const Iso14443_4aData* iso14443_4a_data = - iso14443_4a_poller_get_data(instance->iso14443_4a_poller); - const MfPlusError error = mf_plus_poller_read_version(instance, &instance->data->version); - if(error == MfPlusErrorNone) { - FURI_LOG_D(TAG, "Read version success: %d", error); - if(instance->data->version.hw_major == 0x02 || instance->data->version.hw_major == 0x82) { - detected = true; - if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) { - // Mifare Plus 2K SL2 - instance->data->type = MfPlusTypePlus; - instance->data->size = MfPlusSize2K; - instance->data->security_level = MfPlusSecurityLevel2; - } else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) { - // Mifare Plus 4K SL3 - instance->data->type = MfPlusTypePlus; - instance->data->size = MfPlusSize4K; - instance->data->security_level = MfPlusSecurityLevel3; - } else { - // Mifare Plus EV1/EV2 - - // Revision - switch(instance->data->version.hw_major) { - case 0x11: - instance->data->type = MfPlusTypeEV1; - break; - case 0x22: - instance->data->type = MfPlusTypeEV2; - break; - default: - instance->data->type = MfPlusTypeUnknown; - break; - } - - // Storage size - switch(instance->data->version.hw_storage) { - case 0x16: - instance->data->size = MfPlusSize2K; - break; - case 0x18: - instance->data->size = MfPlusSize4K; - break; - default: - instance->data->size = MfPlusSizeUnknown; - break; - } - - // Security level - if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) { - // Mifare Plus EV1/2 SL3 - instance->data->security_level = MfPlusSecurityLevel3; - } else { - // Mifare Plus EV1/2 SL1 - instance->data->security_level = MfPlusSecurityLevel1; - } - } - } - - } else { - FURI_LOG_D(TAG, "Read version error: %d", error); - detected = mf_plus_poller_get_type_from_iso4(iso14443_4a_data, instance->data); - } - - return detected; -} - MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { furi_assert(iso14443_4a_poller); @@ -259,10 +59,40 @@ static NfcCommand mf_plus_poller_handler_idle(MfPlusPoller* instance) { } static NfcCommand mf_plus_poller_handler_read_version(MfPlusPoller* instance) { - bool success = mf_plus_poller_detect_type(instance); - if(success) { + MfPlusError error = mf_plus_poller_read_version(instance, &instance->data->version); + if(error == MfPlusErrorNone) { + instance->state = MfPlusPollerStateParseVersion; + } else { + instance->state = MfPlusPollerStateParseIso4; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_plus_poller_handler_parse_version(MfPlusPoller* instance) { + furi_assert(instance); + + MfPlusError error = mf_plus_get_type_from_version( + iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data); + if(error == MfPlusErrorNone) { instance->state = MfPlusPollerStateReadSuccess; } else { + instance->error = error; + instance->state = MfPlusPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_plus_poller_handler_parse_iso4(MfPlusPoller* instance) { + furi_assert(instance); + + MfPlusError error = mf_plus_get_type_from_iso4( + iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data); + if(error == MfPlusErrorNone) { + instance->state = MfPlusPollerStateReadSuccess; + } else { + instance->error = error; instance->state = MfPlusPollerStateReadFailed; } @@ -291,6 +121,8 @@ static NfcCommand mf_plus_poller_handler_read_success(MfPlusPoller* instance) { static const MfPlusPollerReadHandler mf_plus_poller_read_handler[MfPlusPollerStateNum] = { [MfPlusPollerStateIdle] = mf_plus_poller_handler_idle, [MfPlusPollerStateReadVersion] = mf_plus_poller_handler_read_version, + [MfPlusPollerStateParseVersion] = mf_plus_poller_handler_parse_version, + [MfPlusPollerStateParseIso4] = mf_plus_poller_handler_parse_iso4, [MfPlusPollerStateReadFailed] = mf_plus_poller_handler_read_failed, [MfPlusPollerStateReadSuccess] = mf_plus_poller_handler_read_success, }; @@ -351,7 +183,16 @@ static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) { bool detected = false; if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { - detected = mf_plus_poller_detect_type(instance); + detected = mf_plus_poller_read_version(instance, &instance->data->version); + if(detected) { + detected = mf_plus_get_type_from_version( + iso14443_4a_poller_get_data(instance->iso14443_4a_poller), + instance->data) == MfPlusErrorNone; + } else { + detected = mf_plus_get_type_from_iso4( + iso14443_4a_poller_get_data(instance->iso14443_4a_poller), + instance->data) == MfPlusErrorNone; + } } return detected; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index 1a4298b67..2a615460e 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -19,8 +19,10 @@ MfPlusError mf_plus_process_error(Iso14443_4aError error) { } } -MfPlusError - mf_plus_send_chunk(MfPlusPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { +MfPlusError mf_plus_poller_send_chunk( + MfPlusPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer) { furi_assert(instance); furi_assert(instance->iso14443_4a_poller); furi_assert(instance->tx_buffer); @@ -43,8 +45,10 @@ MfPlusError if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + FURI_LOG_D(TAG, "Received %d bytes", bit_buffer_get_size_bytes(rx_buffer)); } else { bit_buffer_reset(rx_buffer); + FURI_LOG_D(TAG, "Received 0 bytes"); } } while(false); @@ -60,7 +64,8 @@ MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* d MfPlusError error; do { - error = mf_plus_send_chunk(instance, instance->input_buffer, instance->result_buffer); + error = + mf_plus_poller_send_chunk(instance, instance->input_buffer, instance->result_buffer); if(error != MfPlusErrorNone) break; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h index 79f46b8d8..c7b547a84 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h @@ -18,6 +18,8 @@ typedef enum { typedef enum { MfPlusPollerStateIdle, MfPlusPollerStateReadVersion, + MfPlusPollerStateParseVersion, + MfPlusPollerStateParseIso4, MfPlusPollerStateReadFailed, MfPlusPollerStateReadSuccess, From cf9b09042ef4e1482bf28cbbc21a0c89a10c4113 Mon Sep 17 00:00:00 2001 From: Astra Date: Mon, 20 May 2024 19:12:18 +0900 Subject: [PATCH 02/28] Remove leftover logging --- lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index 2a615460e..b7196ab2c 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -45,10 +45,8 @@ MfPlusError mf_plus_poller_send_chunk( if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); - FURI_LOG_D(TAG, "Received %d bytes", bit_buffer_get_size_bytes(rx_buffer)); } else { bit_buffer_reset(rx_buffer); - FURI_LOG_D(TAG, "Received 0 bytes"); } } while(false); From 2929955a3cd8b1f6ff4ec41357ce9c2259417917 Mon Sep 17 00:00:00 2001 From: Astra Date: Mon, 20 May 2024 19:19:11 +0900 Subject: [PATCH 03/28] Remove stray reminder comment --- lib/nfc/protocols/mf_plus/mf_plus_i.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index fa7292abd..ada4dd952 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -182,7 +182,6 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeX; mf_plus_data->security_level = MfPlusSecurityLevel3; - // if atqa is 0xXX 0xX4 (other digits are not important) if(iso4_data->iso14443_3a_data->atqa[1] & 0x04) { mf_plus_data->size = MfPlusSize2K; FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); From c8a779da3f8d7684b456c8aec5d206a360dc2211 Mon Sep 17 00:00:00 2001 From: Astra Date: Tue, 21 May 2024 19:30:05 +0900 Subject: [PATCH 04/28] Review changes and extra logging --- lib/nfc/protocols/mf_plus/mf_plus_i.c | 12 ++++++++++++ lib/nfc/protocols/mf_plus/mf_plus_poller.c | 9 ++++----- lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 6 +----- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index ada4dd952..a481f94c4 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -32,11 +32,13 @@ bool mf_plus_get_type_from_version( mf_plus_data->type = MfPlusTypePlus; mf_plus_data->size = MfPlusSize2K; mf_plus_data->security_level = MfPlusSecurityLevel2; + FURI_LOG_D(TAG, "Mifare Plus 2K SL2"); } else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) { // Mifare Plus 4K SL3 mf_plus_data->type = MfPlusTypePlus; mf_plus_data->size = MfPlusSize4K; mf_plus_data->security_level = MfPlusSecurityLevel3; + FURI_LOG_D(TAG, "Mifare Plus 4K SL3"); } else { // Mifare Plus EV1/EV2 @@ -44,12 +46,15 @@ bool mf_plus_get_type_from_version( switch(mf_plus_data->version.hw_major) { case 0x11: mf_plus_data->type = MfPlusTypeEV1; + FURI_LOG_D(TAG, "Mifare Plus EV1"); break; case 0x22: mf_plus_data->type = MfPlusTypeEV2; + FURI_LOG_D(TAG, "Mifare Plus EV2"); break; default: mf_plus_data->type = MfPlusTypeUnknown; + FURI_LOG_D(TAG, "Unknown Mifare Plus EV type"); break; } @@ -57,12 +62,15 @@ bool mf_plus_get_type_from_version( switch(mf_plus_data->version.hw_storage) { case 0x16: mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "2K"); break; case 0x18: mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "4K"); break; default: mf_plus_data->size = MfPlusSizeUnknown; + FURI_LOG_D(TAG, "Unknown storage size"); break; } @@ -70,9 +78,11 @@ bool mf_plus_get_type_from_version( if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) { // Mifare Plus EV1/2 SL3 mf_plus_data->security_level = MfPlusSecurityLevel3; + FURI_LOG_D(TAG, "Miare Plus EV1/2 SL3"); } else { // Mifare Plus EV1/2 SL1 mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Miare Plus EV1/2 SL1"); } } } @@ -158,6 +168,7 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_ats_t1_tk_values[0], simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { // Mifare Plus S 2/4K SL3 + FURI_LOG_D(TAG, "Mifare Plus S SL3"); mf_plus_data->type = MfPlusTypeS; mf_plus_data->security_level = MfPlusSecurityLevel3; @@ -179,6 +190,7 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf iso4_data->ats_data.t1_tk, mf_plus_ats_t1_tk_values[1], simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + FURI_LOG_D(TAG, "Mifare Plus X SL3"); mf_plus_data->type = MfPlusTypeX; mf_plus_data->security_level = MfPlusSecurityLevel3; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index 37a4f723f..a5ba70817 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -183,15 +183,14 @@ static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) { bool detected = false; if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { - detected = mf_plus_poller_read_version(instance, &instance->data->version); + detected = + (mf_plus_poller_read_version(instance, &instance->data->version) == MfPlusErrorNone); if(detected) { detected = mf_plus_get_type_from_version( - iso14443_4a_poller_get_data(instance->iso14443_4a_poller), - instance->data) == MfPlusErrorNone; + iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data); } else { detected = mf_plus_get_type_from_iso4( - iso14443_4a_poller_get_data(instance->iso14443_4a_poller), - instance->data) == MfPlusErrorNone; + iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data); } } diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index b7196ab2c..abc97b82e 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -42,12 +42,8 @@ MfPlusError mf_plus_poller_send_chunk( } bit_buffer_reset(instance->tx_buffer); + bit_buffer_copy(rx_buffer, instance->rx_buffer); - if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { - bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); - } else { - bit_buffer_reset(rx_buffer); - } } while(false); return error; From 514fd244faf0194d8f39942b8272a04c448e85be Mon Sep 17 00:00:00 2001 From: Astra Date: Wed, 22 May 2024 18:29:57 +0900 Subject: [PATCH 05/28] Fix atqa detection --- lib/nfc/protocols/mf_plus/mf_plus_i.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index a481f94c4..7627ce016 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -172,11 +172,11 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeS; mf_plus_data->security_level = MfPlusSecurityLevel3; - if(iso4_data->iso14443_3a_data->atqa[1] & 0x04) { + if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x04) { // Mifare Plus S 2K SL3 mf_plus_data->size = MfPlusSize2K; FURI_LOG_D(TAG, "Mifare Plus S 2K SL3"); - } else if(iso4_data->iso14443_3a_data->atqa[1] & 0x02) { + } else if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x02) { // Mifare Plus S 4K SL3 mf_plus_data->size = MfPlusSize4K; FURI_LOG_D(TAG, "Mifare Plus S 4K SL3"); @@ -194,10 +194,10 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeX; mf_plus_data->security_level = MfPlusSecurityLevel3; - if(iso4_data->iso14443_3a_data->atqa[1] & 0x04) { + if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x04) { mf_plus_data->size = MfPlusSize2K; FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); - } else if(iso4_data->iso14443_3a_data->atqa[1] & 0x02) { + } else if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x02) { mf_plus_data->size = MfPlusSize4K; FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); } else { From d3d483e60f2d76c573fde7ead07c11c20289fb53 Mon Sep 17 00:00:00 2001 From: Astra Date: Wed, 22 May 2024 19:10:26 +0900 Subject: [PATCH 06/28] Fix incorrect comparison --- lib/nfc/protocols/mf_plus/mf_plus_i.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index 7627ce016..32a28bc9d 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -164,7 +164,7 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf } case 0x20: if(memcmp( - iso4_data->ats_data.t1_tk, + simple_array_get_data(iso4_data->ats_data.t1_tk), mf_plus_ats_t1_tk_values[0], simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { // Mifare Plus S 2/4K SL3 @@ -187,7 +187,7 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf return true; } else if( memcmp( - iso4_data->ats_data.t1_tk, + simple_array_get_data(iso4_data->ats_data.t1_tk), mf_plus_ats_t1_tk_values[1], simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { FURI_LOG_D(TAG, "Mifare Plus X SL3"); From 060a488ae9cf82664e7ce669093e0f198e4c8d96 Mon Sep 17 00:00:00 2001 From: Astra Date: Wed, 22 May 2024 19:55:12 +0900 Subject: [PATCH 07/28] ATQA byte swap fix --- lib/nfc/protocols/mf_plus/mf_plus_i.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index 32a28bc9d..89bd9dcf7 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -172,11 +172,11 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeS; mf_plus_data->security_level = MfPlusSecurityLevel3; - if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x04) { + if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) { // Mifare Plus S 2K SL3 mf_plus_data->size = MfPlusSize2K; FURI_LOG_D(TAG, "Mifare Plus S 2K SL3"); - } else if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x02) { + } else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) { // Mifare Plus S 4K SL3 mf_plus_data->size = MfPlusSize4K; FURI_LOG_D(TAG, "Mifare Plus S 4K SL3"); @@ -194,10 +194,10 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeX; mf_plus_data->security_level = MfPlusSecurityLevel3; - if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x04) { + if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) { mf_plus_data->size = MfPlusSize2K; FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); - } else if((iso4_data->iso14443_3a_data->atqa[1] & 0x0F) == 0x02) { + } else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) { mf_plus_data->size = MfPlusSize4K; FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); } else { From 6ccbc16e74b132ee7fb770637be680bb4dac55a3 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 29 May 2024 16:53:12 +0100 Subject: [PATCH 08/28] mf plus: code clean up --- lib/nfc/protocols/mf_plus/mf_plus.h | 6 ++---- lib/nfc/protocols/mf_plus/mf_plus_i.c | 2 +- lib/nfc/protocols/mf_plus/mf_plus_poller.c | 5 ++--- lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus.h b/lib/nfc/protocols/mf_plus/mf_plus.h index 828d1c070..31559ffdc 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus.h +++ b/lib/nfc/protocols/mf_plus/mf_plus.h @@ -2,13 +2,11 @@ #include -#include - #ifdef __cplusplus extern "C" { #endif -#define MF_PLUS_UID_SIZE (7) +#define MF_PLUS_UID_SIZE_MAX (7) #define MF_PLUS_BATCH_SIZE (5) #define MF_PLUS_CMD_GET_VERSION (0x60) @@ -71,7 +69,7 @@ typedef struct { uint8_t sw_storage; uint8_t sw_proto; - uint8_t uid[MF_PLUS_UID_SIZE]; + uint8_t uid[MF_PLUS_UID_SIZE_MAX]; uint8_t batch[MF_PLUS_BATCH_SIZE]; uint8_t prod_week; uint8_t prod_year; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index 89bd9dcf7..ca19b458b 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -420,4 +420,4 @@ bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff) { furi_string_free(size_string); return true; -} \ No newline at end of file +} diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index a5ba70817..2cd3a57ca 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -139,13 +139,12 @@ static void mf_plus_poller_set_callback( } static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) { + furi_assert(context); furi_assert(event.protocol = NfcProtocolIso14443_4a); + furi_assert(event.event_data); MfPlusPoller* instance = context; - furi_assert(instance); - const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; - furi_assert(iso14443_4a_event); NfcCommand command = NfcCommandContinue; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index abc97b82e..3750abf42 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -55,7 +55,7 @@ MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* d bit_buffer_reset(instance->input_buffer); bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION); - MfPlusError error; + MfPlusError error = MfPlusErrorNone; do { error = From ba0051967cf2b54647887de43214883785ad2fba Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 29 May 2024 16:54:18 +0100 Subject: [PATCH 09/28] mf plus: remove unused code --- lib/nfc/protocols/mf_plus/mf_plus_i.c | 30 --------------------------- lib/nfc/protocols/mf_plus/mf_plus_i.h | 6 ------ 2 files changed, 36 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index ca19b458b..5eb38e0d6 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -225,36 +225,6 @@ bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { return can_parse; } -bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf) { - const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSecurityLevel); - - if(can_parse) { - bit_buffer_write_bytes(buf, data, sizeof(MfPlusSecurityLevel)); - } - - return can_parse; -} - -bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf) { - const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusType); - - if(can_parse) { - bit_buffer_write_bytes(buf, data, sizeof(MfPlusType)); - } - - return can_parse; -} - -bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf) { - const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSize); - - if(can_parse) { - bit_buffer_write_bytes(buf, data, sizeof(MfPlusSize)); - } - - return can_parse; -} - bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff) { return flipper_format_read_hex( ff, MF_PLUS_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfPlusVersion)); diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h index bf36b9060..313ccce55 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -12,12 +12,6 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf); -bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf); - -bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf); - -bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf); - bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff); bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff); From bf41cc43a1b17ebda9ffdc601c5cd41906f676ac Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 29 May 2024 18:23:34 +0100 Subject: [PATCH 10/28] mf plus: fix read fail event handling --- .../helpers/protocol_support/mf_plus/mf_plus.c | 15 +++++++++------ lib/nfc/protocols/mf_plus/mf_plus_poller.c | 7 +++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c index a020c9bbd..894f13652 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c @@ -26,23 +26,26 @@ static void nfc_scene_info_on_enter_mf_plus(NfcApp* instance) { furi_string_free(temp_str); } static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, void* context) { + furi_assert(context); furi_assert(event.protocol == NfcProtocolMfPlus); + furi_assert(event.event_data); NfcApp* instance = context; const MfPlusPollerEvent* mf_plus_event = event.event_data; + NfcCommand command = NfcCommandContinue; + if(mf_plus_event->type == MfPlusPollerEventTypeReadSuccess) { nfc_device_set_data( instance->nfc_device, NfcProtocolMfPlus, nfc_poller_get_data(instance->poller)); - FURI_LOG_D( - "MFP", - "Read success: %s", - nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if(mf_plus_event->type == MfPlusPollerEventTypeReadFailed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + command = NfcCommandStop; } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_mf_plus(NfcApp* instance) { diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index 2cd3a57ca..dac95b912 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -101,20 +101,27 @@ static NfcCommand mf_plus_poller_handler_parse_iso4(MfPlusPoller* instance) { static NfcCommand mf_plus_poller_handler_read_failed(MfPlusPoller* instance) { furi_assert(instance); + FURI_LOG_D(TAG, "Read failed"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); + + instance->mfp_event.type = MfPlusPollerEventTypeReadFailed; instance->mfp_event.data->error = instance->error; NfcCommand command = instance->callback(instance->general_event, instance->context); instance->state = MfPlusPollerStateIdle; + return command; } static NfcCommand mf_plus_poller_handler_read_success(MfPlusPoller* instance) { furi_assert(instance); + FURI_LOG_D(TAG, "Read success"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mfp_event.type = MfPlusPollerEventTypeReadSuccess; NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; } From 5f7a139cffb1cfc88f395d9adf3caa581f610618 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 30 May 2024 12:54:05 +0100 Subject: [PATCH 11/28] mf plus: fix return error codes --- lib/nfc/protocols/mf_plus/mf_plus_i.c | 56 ++++++++++++-------- lib/nfc/protocols/mf_plus/mf_plus_i.h | 6 +-- lib/nfc/protocols/mf_plus/mf_plus_poller.c | 19 +++---- lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 35 ++++-------- 4 files changed, 56 insertions(+), 60 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index 5eb38e0d6..2a291b609 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -17,16 +17,16 @@ const uint8_t mf_plus_ats_t1_tk_values[][7] = { {0xC1, 0x05, 0x2F, 0x2F, 0x01, 0xF6, 0xD1}, // Mifare Plus SE }; -bool mf_plus_get_type_from_version( +MfPlusError mf_plus_get_type_from_version( const Iso14443_4aData* iso14443_4a_data, MfPlusData* mf_plus_data) { furi_assert(iso14443_4a_data); furi_assert(mf_plus_data); - bool detected = false; + MfPlusError error = MfPlusErrorProtocol; if(mf_plus_data->version.hw_major == 0x02 || mf_plus_data->version.hw_major == 0x82) { - detected = true; + error = MfPlusErrorNone; if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) { // Mifare Plus 2K SL2 mf_plus_data->type = MfPlusTypePlus; @@ -87,13 +87,16 @@ bool mf_plus_get_type_from_version( } } - return detected; + return error; } -bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) { +MfPlusError + mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) { furi_assert(iso4_data); furi_assert(mf_plus_data); + MfPlusError error = MfPlusErrorProtocol; + switch(iso4_data->iso14443_3a_data->sak) { case 0x08: if(memcmp( @@ -104,8 +107,9 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize2K; mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL1"); - return true; + error = MfPlusErrorNone; } else if( memcmp( simple_array_get_data(iso4_data->ats_data.t1_tk), @@ -115,8 +119,9 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize2K; mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL1"); - return true; + error = MfPlusErrorNone; } else if( memcmp( simple_array_get_data(iso4_data->ats_data.t1_tk), @@ -130,12 +135,14 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeSE; mf_plus_data->size = MfPlusSize1K; mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus SE 1K SL1"); - return true; + error = MfPlusErrorNone; } else { FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type"); - return false; } + + break; case 0x18: if(memcmp( simple_array_get_data(iso4_data->ats_data.t1_tk), @@ -145,8 +152,9 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeS; mf_plus_data->size = MfPlusSize4K; mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL1"); - return true; + error = MfPlusErrorNone; } else if( memcmp( simple_array_get_data(iso4_data->ats_data.t1_tk), @@ -156,12 +164,14 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf mf_plus_data->type = MfPlusTypeX; mf_plus_data->size = MfPlusSize4K; mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL1"); - return true; + error = MfPlusErrorNone; } else { FURI_LOG_D(TAG, "Sak 18 but no known Mifare Plus type"); - return false; } + + break; case 0x20: if(memcmp( simple_array_get_data(iso4_data->ats_data.t1_tk), @@ -175,54 +185,56 @@ bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) { // Mifare Plus S 2K SL3 mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL3"); + error = MfPlusErrorNone; } else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) { // Mifare Plus S 4K SL3 mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL3"); + error = MfPlusErrorNone; } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)"); - return false; } - return true; } else if( memcmp( simple_array_get_data(iso4_data->ats_data.t1_tk), mf_plus_ats_t1_tk_values[1], simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { - FURI_LOG_D(TAG, "Mifare Plus X SL3"); mf_plus_data->type = MfPlusTypeX; mf_plus_data->security_level = MfPlusSecurityLevel3; + FURI_LOG_D(TAG, "Mifare Plus X SL3"); if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x04) { mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); + error = MfPlusErrorNone; } else if((iso4_data->iso14443_3a_data->atqa[0] & 0x0F) == 0x02) { mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); + error = MfPlusErrorNone; } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); - return false; } - return true; } else { FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); - return false; } } - FURI_LOG_D(TAG, "No known Mifare Plus type"); - return false; + return error; } -bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { +MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); if(can_parse) { bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion)); } - return can_parse; + return can_parse ? MfPlusErrorNone : MfPlusErrorProtocol; } bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff) { diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h index 313ccce55..1b80030f9 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.h +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -4,13 +4,13 @@ #define MF_PLUS_FFF_PICC_PREFIX "PICC" -bool mf_plus_get_type_from_version( +MfPlusError mf_plus_get_type_from_version( const Iso14443_4aData* iso14443_4a_data, MfPlusData* mf_plus_data); -bool mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data); +MfPlusError mf_plus_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data); -bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf); +MfPlusError mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf); bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c index dac95b912..c93ec9e67 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -22,7 +22,6 @@ MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { furi_assert(iso14443_4a_poller); MfPlusPoller* instance = malloc(sizeof(MfPlusPoller)); - furi_assert(instance); instance->iso14443_4a_poller = iso14443_4a_poller; @@ -178,29 +177,27 @@ void mf_plus_poller_free(MfPlusPoller* instance) { } static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(context); furi_assert(event.protocol = NfcProtocolIso14443_4a); + furi_assert(event.event_data); MfPlusPoller* instance = context; - furi_assert(instance); - Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; - furi_assert(iso14443_4a_event); - bool detected = false; + MfPlusError error = MfPlusErrorUnknown; if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { - detected = - (mf_plus_poller_read_version(instance, &instance->data->version) == MfPlusErrorNone); - if(detected) { - detected = mf_plus_get_type_from_version( + error = mf_plus_poller_read_version(instance, &instance->data->version); + if(error == MfPlusErrorNone) { + error = mf_plus_get_type_from_version( iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data); } else { - detected = mf_plus_get_type_from_iso4( + error = mf_plus_get_type_from_iso4( iso14443_4a_poller_get_data(instance->iso14443_4a_poller), instance->data); } } - return detected; + return (error == MfPlusErrorNone); } const NfcPollerBase mf_plus_poller = { diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index 3750abf42..9ed3a1d5f 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -30,21 +30,15 @@ MfPlusError mf_plus_poller_send_chunk( furi_assert(tx_buffer); furi_assert(rx_buffer); - MfPlusError error = MfPlusErrorNone; + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); + MfPlusError error = mf_plus_process_error(iso14443_4a_error); - do { - Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( - instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); - - if(iso14443_4a_error != Iso14443_4aErrorNone) { - error = mf_plus_process_error(iso14443_4a_error); - break; - } - - bit_buffer_reset(instance->tx_buffer); + if(error == MfPlusErrorNone) { bit_buffer_copy(rx_buffer, instance->rx_buffer); + } - } while(false); + bit_buffer_reset(instance->tx_buffer); return error; } @@ -55,18 +49,11 @@ MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* d bit_buffer_reset(instance->input_buffer); bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION); - MfPlusError error = MfPlusErrorNone; - - do { - error = - mf_plus_poller_send_chunk(instance, instance->input_buffer, instance->result_buffer); - - if(error != MfPlusErrorNone) break; - - if(!mf_plus_version_parse(data, instance->result_buffer)) { - error = MfPlusErrorProtocol; - } - } while(false); + MfPlusError error = + mf_plus_poller_send_chunk(instance, instance->input_buffer, instance->result_buffer); + if(error == MfPlusErrorNone) { + error = mf_plus_version_parse(data, instance->result_buffer); + } return error; } From fd90567f0e4e84005be4bb362cea4042c1f0f404 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 30 May 2024 13:49:27 +0100 Subject: [PATCH 12/28] mf plus: handle load and save errors --- lib/nfc/protocols/mf_plus/mf_plus.c | 58 +++++++++++---------------- lib/nfc/protocols/mf_plus/mf_plus_i.c | 13 +++--- 2 files changed, 31 insertions(+), 40 deletions(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus.c b/lib/nfc/protocols/mf_plus/mf_plus.c index 5e3fe1b34..32d8d6c59 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus.c +++ b/lib/nfc/protocols/mf_plus/mf_plus.c @@ -60,6 +60,7 @@ MfPlusData* mf_plus_alloc(void) { void mf_plus_free(MfPlusData* data) { furi_check(data); + furi_string_free(data->device_name); iso14443_4a_free(data->iso14443_4a_data); free(data); @@ -67,8 +68,8 @@ void mf_plus_free(MfPlusData* data) { void mf_plus_reset(MfPlusData* data) { furi_check(data); - iso14443_4a_reset(data->iso14443_4a_data); + iso14443_4a_reset(data->iso14443_4a_data); memset(&data->version, 0, sizeof(data->version)); furi_string_reset(data->device_name); data->type = MfPlusTypeUnknown; @@ -79,8 +80,8 @@ void mf_plus_reset(MfPlusData* data) { void mf_plus_copy(MfPlusData* data, const MfPlusData* other) { furi_check(data); furi_check(other); - iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); data->version = other->version; data->type = other->type; data->security_level = other->security_level; @@ -93,10 +94,9 @@ bool mf_plus_verify(MfPlusData* data, const FuriString* device_type) { } bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version) { - furi_assert(data); + furi_check(data); bool success = false; - do { if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; if(!mf_plus_version_load(&data->version, ff)) break; @@ -110,10 +110,9 @@ bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version) { } bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff) { - furi_assert(data); + furi_check(data); bool success = false; - do { if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; if(!flipper_format_write_comment_cstr(ff, MF_PLUS_PROTOCOL_NAME " specific data")) break; @@ -128,10 +127,10 @@ bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff) { } bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other) { - furi_assert(data); - furi_assert(other); - bool equal = false; + furi_check(data); + furi_check(other); + bool equal = false; do { if(!iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data)) break; if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break; @@ -147,44 +146,35 @@ bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other) { const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type) { furi_check(data); - FuriString* full_name = furi_string_alloc(); - const char* name = NULL; + if(name_type == NfcDeviceNameTypeFull) { + furi_string_printf( + data->device_name, + "Mifare %s %s %s", + mf_plus_type_strings[data->type], // Includes "Plus" for regular Mifare Plus cards + mf_plus_size_strings[data->size], + mf_plus_security_level_strings[data->security_level]); + } else if(name_type == NfcDeviceNameTypeShort) { + furi_string_set_str(data->device_name, MF_PLUS_PROTOCOL_NAME); + } else { + furi_crash("Unexpected name type"); + } - do { - if(name_type == NfcDeviceNameTypeFull) { - furi_string_reset(data->device_name); - furi_string_cat_printf( - data->device_name, - "Mifare %s %s %s", - mf_plus_type_strings[data->type], // Includes "Plus" for regular Mifare Plus cards - mf_plus_size_strings[data->size], - mf_plus_security_level_strings[data->security_level]); - name = furi_string_get_cstr(data->device_name); - FURI_LOG_D("Mifare Plus", "Full name: %s", name); - } else if(name_type == NfcDeviceNameTypeShort) { - name = "Mifare Plus"; - } else { - break; - } - } while(false); - - furi_string_free(full_name); - FURI_LOG_D("Mifare Plus", "Name: %s", name); - return name; + return furi_string_get_cstr(data->device_name); } const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len) { - furi_assert(data); + furi_check(data); return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); } bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len) { - furi_assert(data); + furi_check(data); return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); } Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data) { furi_check(data); + return data->iso14443_4a_data; } \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c index 2a291b609..d5fe5be82 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -341,10 +341,11 @@ bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* break; } - flipper_format_write_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string); + bool success = + flipper_format_write_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string); furi_string_free(security_level_string); - return true; + return success; } bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff) { @@ -374,10 +375,10 @@ bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff) { break; } - flipper_format_write_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string); + bool success = flipper_format_write_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string); furi_string_free(type_string); - return true; + return success; } bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff) { @@ -398,8 +399,8 @@ bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff) { break; } - flipper_format_write_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string); + bool success = flipper_format_write_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string); furi_string_free(size_string); - return true; + return success; } From 03f242618cbd847773e81fb12684b3937836035a Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 30 May 2024 13:55:59 +0100 Subject: [PATCH 13/28] mf plus: assert -> check in public API funxtion --- lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c index 9ed3a1d5f..cab906f1d 100644 --- a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -44,7 +44,7 @@ MfPlusError mf_plus_poller_send_chunk( } MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) { - furi_assert(instance); + furi_check(instance); bit_buffer_reset(instance->input_buffer); bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION); From ed057594f52e3956d12331234fe8ff8bfafb2bfb Mon Sep 17 00:00:00 2001 From: Aleksandr Kutuzov Date: Sat, 1 Jun 2024 17:55:56 +0100 Subject: [PATCH 14/28] Bump API Symbols version --- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7eda31675..46d111d9d 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,63.0,, +Version,+,63.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index ba05bc550..9c3860a82 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,63.0,, +Version,+,63.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, From 8decc8052e8b34c133d6c7fc460bec7418ac5ccc Mon Sep 17 00:00:00 2001 From: Astra Date: Mon, 3 Jun 2024 19:28:25 +0900 Subject: [PATCH 15/28] Fix wrong feature mask --- .../main/nfc/helpers/protocol_support/mf_plus/mf_plus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c index 894f13652..d6abba3b3 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c @@ -79,7 +79,7 @@ static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) { } const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { - .features = NfcProtocolFeatureMoreInfo, + .features = NfcProtocolFeatureEmulateUid, .scene_info = { From 3d4159009066e58eb8bcea7e5542e21c9afb78c2 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Mon, 3 Jun 2024 16:25:58 +0300 Subject: [PATCH 16/28] Added new function which returns end block for write operation --- lib/nfc/protocols/mf_ultralight/mf_ultralight.c | 16 ++++++++++++++++ lib/nfc/protocols/mf_ultralight/mf_ultralight.h | 2 ++ .../mf_ultralight/mf_ultralight_poller.c | 2 +- targets/f7/api_symbols.csv | 3 ++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index 74436dc08..6300801ab 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -570,6 +570,22 @@ uint16_t mf_ultralight_get_config_page_num(MfUltralightType type) { return mf_ultralight_features[type].config_page; } +uint8_t mf_ultralight_get_write_end_page(MfUltralightType type) { + furi_check(type < MfUltralightTypeNum); + furi_assert( + type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21 || + type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 || + type == MfUltralightTypeNTAG216); + + uint8_t end_page = mf_ultralight_get_config_page_num(type); + if(type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 || + type == MfUltralightTypeNTAG216) { + end_page -= 1; + } + + return end_page; +} + uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type) { furi_check(type < MfUltralightTypeNum); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h index 5e348a0c9..6c6a83a17 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -210,6 +210,8 @@ uint32_t mf_ultralight_get_feature_support_set(MfUltralightType type); uint16_t mf_ultralight_get_config_page_num(MfUltralightType type); +uint8_t mf_ultralight_get_write_end_page(MfUltralightType type); + uint8_t mf_ultralight_get_pwd_page_num(MfUltralightType type); bool mf_ultralight_is_page_pwd_or_pack(MfUltralightType type, uint16_t page_num); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 0c7f9f803..bc4ebd515 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -616,7 +616,7 @@ static NfcCommand mf_ultralight_poller_handler_write_pages(MfUltralightPoller* i do { const MfUltralightData* write_data = instance->mfu_event.data->write_data; - uint8_t end_page = mf_ultralight_get_config_page_num(write_data->type) - 1; + uint8_t end_page = mf_ultralight_get_write_end_page(write_data->type); if(instance->current_page == end_page) { instance->state = MfUltralightPollerStateWriteSuccess; break; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index e2e94d063..e6334c1e6 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,63.0,, +Version,+,64.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -2533,6 +2533,7 @@ Function,+,mf_ultralight_get_pages_total,uint16_t,MfUltralightType Function,+,mf_ultralight_get_pwd_page_num,uint8_t,MfUltralightType Function,+,mf_ultralight_get_type_by_version,MfUltralightType,MfUltralightVersion* Function,+,mf_ultralight_get_uid,const uint8_t*,"const MfUltralightData*, size_t*" +Function,+,mf_ultralight_get_write_end_page,uint8_t,MfUltralightType Function,+,mf_ultralight_is_all_data_read,_Bool,const MfUltralightData* Function,+,mf_ultralight_is_counter_configured,_Bool,const MfUltralightData* Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltralightData*" From 06d44550d94c73a546daf31e6cebe4df8fa6b6e7 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Mon, 3 Jun 2024 16:26:39 +0300 Subject: [PATCH 17/28] Enabled write menu item also for Ultralight 11 and 21 --- .../nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index cd16374bc..a4e0eade6 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -191,7 +191,8 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc instance); } else if( data->type == MfUltralightTypeNTAG213 || data->type == MfUltralightTypeNTAG215 || - data->type == MfUltralightTypeNTAG216) { + data->type == MfUltralightTypeNTAG216 || data->type == MfUltralightTypeUL11 || + data->type == MfUltralightTypeUL21) { submenu_add_item( submenu, "Write", From bbece015e0fa595abdd7b14547d8515585989862 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Tue, 4 Jun 2024 15:37:31 +0300 Subject: [PATCH 18/28] Fixed infinite loop in dictionary attack scene --- .../helpers/protocol_support/mf_classic/mf_classic.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 6abaa48cd..7a51e3d86 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -197,7 +197,10 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag dolphin_deed(DolphinDeedNfcDetectReader); consumed = true; } else if(event.event == SubmenuIndexDictAttack) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + if(!scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfClassicDictAttack)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + } consumed = true; } else if(event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); @@ -222,7 +225,10 @@ static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneMana scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); consumed = true; } else if(event.event == SubmenuIndexDictAttack) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + if(!scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfClassicDictAttack)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + } consumed = true; } } From 4357a1e9d2a82f533a81c400cb9d9720b418f017 Mon Sep 17 00:00:00 2001 From: Astra Date: Wed, 5 Jun 2024 18:11:57 +0900 Subject: [PATCH 19/28] Skylanders plugin separation --- .../nfc/plugins/supported_cards/skylanders.c | 785 ++---------------- .../nfc/resources/nfc/assets/skylanders.nfc | 247 ++++++ 2 files changed, 295 insertions(+), 737 deletions(-) create mode 100644 applications/main/nfc/resources/nfc/assets/skylanders.nfc diff --git a/applications/main/nfc/plugins/supported_cards/skylanders.c b/applications/main/nfc/plugins/supported_cards/skylanders.c index fca1c3185..6c199f114 100644 --- a/applications/main/nfc/plugins/supported_cards/skylanders.c +++ b/applications/main/nfc/plugins/supported_cards/skylanders.c @@ -5,11 +5,54 @@ #include #include #include +#include #define TAG "Skylanders" static const uint64_t skylanders_key = 0x4b0b20107ccb; +static const char* nfc_resources_header = "Flipper NFC resources"; +static const uint32_t nfc_resources_file_version = 1; + +static bool skylanders_search_data( + Storage* storage, + const char* file_name, + FuriString* key, + FuriString* data) { + bool parsed = false; + FlipperFormat* file = flipper_format_file_alloc(storage); + FuriString* temp_str; + temp_str = furi_string_alloc(); + + do { + // Open file + if(!flipper_format_file_open_existing(file, file_name)) break; + // Read file header and version + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, nfc_resources_header) || + (version != nfc_resources_file_version)) + break; + if(!flipper_format_read_string(file, furi_string_get_cstr(key), data)) break; + parsed = true; + } while(false); + + furi_string_free(temp_str); + flipper_format_free(file); + return parsed; +} + +bool skylanders_get_name(Storage* storage, uint16_t id, FuriString* name) { + bool parsed = false; + FuriString* key; + key = furi_string_alloc_printf("%04X", id); + if(skylanders_search_data(storage, EXT_PATH("nfc/assets/skylanders.nfc"), key, name)) { + parsed = true; + } + furi_string_free(key); + return parsed; +} + bool skylanders_verify(Nfc* nfc) { bool verified = false; @@ -75,742 +118,6 @@ static bool skylanders_read(Nfc* nfc, NfcDevice* device) { return is_read; } -static uint8_t fill_name(const uint16_t id, FuriString* name) { - // USED RESEARCH FROM https://github.com/silicontrip/SkyReader/blob/master/toynames.cpp#L15C1-L163C1 - // AND https://github.com/bettse/Solarbreeze/blob/master/Solarbreeze/ThePoster.swift#L438C1-L681C1 - switch(id) { - case 0x0000: - furi_string_cat_printf(name, "Whirlwind"); - break; - case 0x0001: - furi_string_cat_printf(name, "Sonic Boom"); - break; - case 0x0002: - furi_string_cat_printf(name, "Warnado"); - break; - case 0x0003: - furi_string_cat_printf(name, "Lightning Rod"); - break; - case 0x0004: - case 0x0194: - furi_string_cat_printf(name, "Bash"); - break; - case 0x0005: - furi_string_cat_printf(name, "Terrafin"); - break; - case 0x0006: - furi_string_cat_printf(name, "Dino-Rang"); - break; - case 0x0007: - furi_string_cat_printf(name, "Prism Break"); - break; - case 0x0008: - furi_string_cat_printf(name, "Sunburn"); - break; - case 0x0009: - furi_string_cat_printf(name, "Eruptor"); - break; - case 0x000A: - furi_string_cat_printf(name, "Ignitor"); - break; - case 0x000B: - furi_string_cat_printf(name, "Flameslinger"); - break; - case 0x000C: - furi_string_cat_printf(name, "Zap"); - break; - case 0x000D: - furi_string_cat_printf(name, "Wham-Shell"); - break; - case 0x000E: - furi_string_cat_printf(name, "Gill Grunt"); - break; - case 0x000F: - furi_string_cat_printf(name, "Slam Bam"); - break; - case 0x0010: - case 0x01A0: - furi_string_cat_printf(name, "Spyro"); - break; - case 0x0011: - furi_string_cat_printf(name, "Voodood"); - break; - case 0x0012: - furi_string_cat_printf(name, "Double Trouble"); - break; - case 0x0013: - case 0x01A3: - furi_string_cat_printf(name, "Trigger Happy"); - break; - case 0x0014: - furi_string_cat_printf(name, "Drobot"); - break; - case 0x0015: - furi_string_cat_printf(name, "Drill Sergeant"); - break; - case 0x0016: - furi_string_cat_printf(name, "Boomer"); - break; - case 0x0017: - furi_string_cat_printf(name, "Wrecking Ball"); - break; - case 0x0018: - furi_string_cat_printf(name, "Camo"); - break; - case 0x0019: - furi_string_cat_printf(name, "Zook"); - break; - case 0x001A: - furi_string_cat_printf(name, "Stealth Elf"); - break; - case 0x001B: - furi_string_cat_printf(name, "Stump Smash"); - break; - case 0x001C: - furi_string_cat_printf(name, "Dark Spyro"); - break; - case 0x001D: - furi_string_cat_printf(name, "Hex"); - break; - case 0x001E: - case 0x01AE: - furi_string_cat_printf(name, "Chop Chop"); - break; - case 0x001F: - furi_string_cat_printf(name, "Ghost Roaster"); - break; - case 0x0020: - furi_string_cat_printf(name, "Cynder"); - break; - case 0x0064: - furi_string_cat_printf(name, "Jet Vac"); - break; - case 0x0065: - furi_string_cat_printf(name, "Swarm"); - break; - case 0x0066: - furi_string_cat_printf(name, "Crusher"); - break; - case 0x0067: - furi_string_cat_printf(name, "Flashwing"); - break; - case 0x0068: - furi_string_cat_printf(name, "Hot Head"); - break; - case 0x0069: - furi_string_cat_printf(name, "Hot Dog"); - break; - case 0x006A: - furi_string_cat_printf(name, "Chill"); - break; - case 0x006B: - furi_string_cat_printf(name, "Thumpback"); - break; - case 0x006C: - furi_string_cat_printf(name, "Pop Fizz"); - break; - case 0x006D: - furi_string_cat_printf(name, "Ninjini"); - break; - case 0x006E: - furi_string_cat_printf(name, "Bouncer"); - break; - case 0x006F: - furi_string_cat_printf(name, "Sprocket"); - break; - case 0x0070: - furi_string_cat_printf(name, "Tree Rex"); - break; - case 0x0071: - furi_string_cat_printf(name, "Shroomboom"); - break; - case 0x0072: - furi_string_cat_printf(name, "Eye-Brawl"); - break; - case 0x0073: - furi_string_cat_printf(name, "Fright Rider"); - break; - case 0x00C8: - furi_string_cat_printf(name, "Anvil Rain"); - break; - case 0x00C9: - furi_string_cat_printf(name, "Treasure Chest"); - break; - case 0x00CA: - furi_string_cat_printf(name, "Healing Elixer"); - break; - case 0x00CB: - furi_string_cat_printf(name, "Ghost Swords"); - break; - case 0x00CC: - furi_string_cat_printf(name, "Time Twister"); - break; - case 0x00CD: - furi_string_cat_printf(name, "Sky-Iron Shield"); - break; - case 0x00CE: - furi_string_cat_printf(name, "Winged Boots"); - break; - case 0x00CF: - furi_string_cat_printf(name, "Sparx Dragonfly"); - break; - case 0x00D0: - furi_string_cat_printf(name, "Dragonfire Cannon"); - break; - case 0x00D1: - furi_string_cat_printf(name, "Scorpion Striker Catapult"); - break; - case 0x00D2: - furi_string_cat_printf(name, "Trap - Magic"); - break; - case 0x00D3: - furi_string_cat_printf(name, "Trap - Water"); - break; - case 0x00D4: - furi_string_cat_printf(name, "Trap - Air"); - break; - case 0x00D5: - furi_string_cat_printf(name, "Trap - Undead"); - break; - case 0x00D6: - furi_string_cat_printf(name, "Trap - Tech"); - break; - case 0x00D7: - furi_string_cat_printf(name, "Trap - Fire"); - break; - case 0x00D8: - furi_string_cat_printf(name, "Trap - Earth"); - break; - case 0x00D9: - furi_string_cat_printf(name, "Trap - Life"); - break; - case 0x00DA: - furi_string_cat_printf(name, "Trap - Light"); - break; - case 0x00DB: - furi_string_cat_printf(name, "Trap - Dark"); - break; - case 0x00DC: - furi_string_cat_printf(name, "Trap - Kaos"); - break; - case 0x00E6: - furi_string_cat_printf(name, "Hand Of Fate"); - break; - case 0x00E7: - furi_string_cat_printf(name, "Piggy Bank"); - break; - case 0x00E8: - furi_string_cat_printf(name, "Rocket Ram"); - break; - case 0x00E9: - furi_string_cat_printf(name, "Tiki Speaky"); - break; - case 0x00EB: - furi_string_cat_printf(name, "Imaginite Mystery Chest"); - break; - case 0x012C: - furi_string_cat_printf(name, "Dragons Peak"); - break; - case 0x012D: - furi_string_cat_printf(name, "Empire of Ice"); - break; - case 0x012E: - furi_string_cat_printf(name, "Pirate Seas"); - break; - case 0x012F: - furi_string_cat_printf(name, "Darklight Crypt"); - break; - case 0x0130: - furi_string_cat_printf(name, "Volcanic Vault"); - break; - case 0x0131: - furi_string_cat_printf(name, "Mirror Of Mystery"); - break; - case 0x0132: - furi_string_cat_printf(name, "Nightmare Express"); - break; - case 0x0133: - furi_string_cat_printf(name, "Sunscraper Spire"); - break; - case 0x0134: - furi_string_cat_printf(name, "Midnight Museum"); - break; - case 0x01C2: - furi_string_cat_printf(name, "Gusto"); - break; - case 0x01C3: - furi_string_cat_printf(name, "Thunderbolt"); - break; - case 0x01C4: - furi_string_cat_printf(name, "Fling Kong"); - break; - case 0x01C5: - furi_string_cat_printf(name, "Blades"); - break; - case 0x01C6: - furi_string_cat_printf(name, "Wallop"); - break; - case 0x01C7: - furi_string_cat_printf(name, "Head Rush"); - break; - case 0x01C8: - furi_string_cat_printf(name, "Fist Bump"); - break; - case 0x01C9: - furi_string_cat_printf(name, "Rocky Roll"); - break; - case 0x01CA: - furi_string_cat_printf(name, "Wildfire"); - break; - case 0x01CB: - furi_string_cat_printf(name, "Ka Boom"); - break; - case 0x01CC: - furi_string_cat_printf(name, "Trail Blazer"); - break; - case 0x01CD: - furi_string_cat_printf(name, "Torch"); - break; - case 0x01CE: - furi_string_cat_printf(name, "Snap Shot"); - break; - case 0x01CF: - furi_string_cat_printf(name, "Lob Star"); - break; - case 0x01D0: - furi_string_cat_printf(name, "Flip Wreck"); - break; - case 0x01D1: - furi_string_cat_printf(name, "Echo"); - break; - case 0x01D2: - furi_string_cat_printf(name, "Blastermind"); - break; - case 0x01D3: - furi_string_cat_printf(name, "Enigma"); - break; - case 0x01D4: - furi_string_cat_printf(name, "Deja Vu"); - break; - case 0x01D5: - furi_string_cat_printf(name, "Cobra Cadabra"); - break; - case 0x01D6: - furi_string_cat_printf(name, "Jawbreaker"); - break; - case 0x01D7: - furi_string_cat_printf(name, "Gearshift"); - break; - case 0x01D8: - furi_string_cat_printf(name, "Chopper"); - break; - case 0x01D9: - furi_string_cat_printf(name, "Tread Head"); - break; - case 0x01DA: - furi_string_cat_printf(name, "Bushwhack"); - break; - case 0x01DB: - furi_string_cat_printf(name, "Tuff Luck"); - break; - case 0x01DC: - furi_string_cat_printf(name, "Food Fight"); - break; - case 0x01DD: - furi_string_cat_printf(name, "High Five"); - break; - case 0x01DE: - furi_string_cat_printf(name, "Krypt King"); - break; - case 0x01DF: - furi_string_cat_printf(name, "Short Cut"); - break; - case 0x01E0: - furi_string_cat_printf(name, "Bat Spin"); - break; - case 0x01E1: - furi_string_cat_printf(name, "Funny Bone"); - break; - case 0x01E2: - furi_string_cat_printf(name, "Knight light"); - break; - case 0x01E3: - furi_string_cat_printf(name, "Spotlight"); - break; - case 0x01E4: - furi_string_cat_printf(name, "Knight Mare"); - break; - case 0x01E5: - furi_string_cat_printf(name, "Blackout"); - break; - case 0x01F6: - furi_string_cat_printf(name, "Bop"); - break; - case 0x01F7: - furi_string_cat_printf(name, "Spry"); - break; - case 0x01F8: - furi_string_cat_printf(name, "Hijinx"); - break; - case 0x01F9: - furi_string_cat_printf(name, "Terrabite"); - break; - case 0x01FA: - furi_string_cat_printf(name, "Breeze"); - break; - case 0x01FB: - furi_string_cat_printf(name, "Weeruptor"); - break; - case 0x01FC: - furi_string_cat_printf(name, "Pet Vac"); - break; - case 0x01FD: - furi_string_cat_printf(name, "Small Fry"); - break; - case 0x01FE: - furi_string_cat_printf(name, "Drobit"); - break; - case 0x0202: - furi_string_cat_printf(name, "Gill Runt"); - break; - case 0x0207: - furi_string_cat_printf(name, "Trigger Snappy"); - break; - case 0x020E: - furi_string_cat_printf(name, "Whisper Elf"); - break; - case 0x021C: - furi_string_cat_printf(name, "Barkley"); - break; - case 0x021D: - furi_string_cat_printf(name, "Thumpling"); - break; - case 0x021E: - furi_string_cat_printf(name, "Mini Jini"); - break; - case 0x021F: - furi_string_cat_printf(name, "Eye Small"); - break; - case 0x0259: - furi_string_cat_printf(name, "King Pen"); - break; - case 0x0265: - furi_string_cat_printf(name, "Golden Queen"); - break; - case 0x02AD: - furi_string_cat_printf(name, "Fire Acorn"); - break; - case 0x03E8: - furi_string_cat_printf(name, "(Boom) Jet"); - break; - case 0x03E9: - furi_string_cat_printf(name, "(Free) Ranger"); - break; - case 0x03EA: - furi_string_cat_printf(name, "(Rubble) Rouser"); - break; - case 0x03EB: - furi_string_cat_printf(name, "(Doom) Stone"); - break; - case 0x03EC: - furi_string_cat_printf(name, "Blast Zone"); - break; - case 0x03ED: - furi_string_cat_printf(name, "(Fire) Kraken"); - break; - case 0x03EE: - furi_string_cat_printf(name, "(Stink) Bomb"); - break; - case 0x03EF: - furi_string_cat_printf(name, "(Grilla) Drilla"); - break; - case 0x03F0: - furi_string_cat_printf(name, "(Hoot) Loop"); - break; - case 0x03F1: - furi_string_cat_printf(name, "(Trap) Shadow"); - break; - case 0x03F2: - furi_string_cat_printf(name, "(Magna) Charge"); - break; - case 0x03F3: - furi_string_cat_printf(name, "(Spy) Rise"); - break; - case 0x03F4: - furi_string_cat_printf(name, "(Night) Shift"); - break; - case 0x03F5: - furi_string_cat_printf(name, "(Rattle) Shake"); - break; - case 0x03F6: - furi_string_cat_printf(name, "(Freeze) Blade"); - break; - case 0x03F7: - furi_string_cat_printf(name, "Wash Buckler"); - break; - case 0x07D0: - furi_string_cat_printf(name, "Boom (Jet)"); - break; - case 0x07D1: - furi_string_cat_printf(name, "Free (Ranger)"); - break; - case 0x07D2: - furi_string_cat_printf(name, "Rubble (Rouser)"); - break; - case 0x07D3: - furi_string_cat_printf(name, "Doom (Stone)"); - break; - case 0x07D4: - furi_string_cat_printf(name, "Blast Zone (Head)"); - break; - case 0x07D5: - furi_string_cat_printf(name, "Fire (Kraken)"); - break; - case 0x07D6: - furi_string_cat_printf(name, "Stink (Bomb)"); - break; - case 0x07D7: - furi_string_cat_printf(name, "Grilla (Drilla)"); - break; - case 0x07D8: - furi_string_cat_printf(name, "Hoot (Loop)"); - break; - case 0x07D9: - furi_string_cat_printf(name, "Trap (Shadow)"); - break; - case 0x07DA: - furi_string_cat_printf(name, "Magna (Charge)"); - break; - case 0x07DB: - furi_string_cat_printf(name, "Spy (Rise)"); - break; - case 0x07DC: - furi_string_cat_printf(name, "Night (Shift)"); - break; - case 0x07DD: - furi_string_cat_printf(name, "Rattle (Shake)"); - break; - case 0x07DE: - furi_string_cat_printf(name, "Freeze (Blade)"); - break; - case 0x07DF: - furi_string_cat_printf(name, "Wash Buckler (Head)"); - break; - case 0x0BB8: - furi_string_cat_printf(name, "Scratch"); - break; - case 0x0BB9: - furi_string_cat_printf(name, "Pop Thorn"); - break; - case 0x0BBA: - furi_string_cat_printf(name, "Slobber Tooth"); - break; - case 0x0BBB: - furi_string_cat_printf(name, "Scorp"); - break; - case 0x0BBC: - furi_string_cat_printf(name, "Fryno"); - break; - case 0x0BBD: - furi_string_cat_printf(name, "Smolderdash"); - break; - case 0x0BBE: - furi_string_cat_printf(name, "Bumble Blast"); - break; - case 0x0BBF: - furi_string_cat_printf(name, "Zoo Lou"); - break; - case 0x0BC0: - furi_string_cat_printf(name, "Dune Bug"); - break; - case 0x0BC1: - furi_string_cat_printf(name, "Star Strike"); - break; - case 0x0BC2: - furi_string_cat_printf(name, "Countdown"); - break; - case 0x0BC3: - furi_string_cat_printf(name, "Wind Up"); - break; - case 0x0BC4: - furi_string_cat_printf(name, "Roller Brawl"); - break; - case 0x0BC5: - furi_string_cat_printf(name, "Grim Creeper"); - break; - case 0x0BC6: - furi_string_cat_printf(name, "Rip Tide"); - break; - case 0x0BC7: - furi_string_cat_printf(name, "Punk Shock"); - break; - case 0x0C80: - furi_string_cat_printf(name, "Battle Hammer"); - break; - case 0x0C81: - furi_string_cat_printf(name, "Sky Diamond"); - break; - case 0x0C82: - furi_string_cat_printf(name, "Platinum Sheep"); - break; - case 0x0C83: - furi_string_cat_printf(name, "Groove Machine"); - break; - case 0x0C84: - furi_string_cat_printf(name, "UFO Hat"); - break; - case 0x0C94: - furi_string_cat_printf(name, "Jet Stream"); - break; - case 0x0C95: - furi_string_cat_printf(name, "Tomb Buggy"); - break; - case 0x0C96: - furi_string_cat_printf(name, "Reef Ripper"); - break; - case 0x0C97: - furi_string_cat_printf(name, "Burn Cycle"); - break; - case 0x0C98: - furi_string_cat_printf(name, "Hot Streak"); - break; - case 0x0C99: - furi_string_cat_printf(name, "Shark Tank"); - break; - case 0x0C9A: - furi_string_cat_printf(name, "Thump Truck"); - break; - case 0x0C9B: - furi_string_cat_printf(name, "Crypt Crusher"); - break; - case 0x0C9C: - furi_string_cat_printf(name, "Stealth Stinger"); - break; - case 0x0C9F: - furi_string_cat_printf(name, "Dive Bomber"); - break; - case 0x0CA0: - furi_string_cat_printf(name, "Sky Slicer"); - break; - case 0x0CA1: - furi_string_cat_printf(name, "Clown Cruiser"); - break; - case 0x0CA2: - furi_string_cat_printf(name, "Gold Rusher"); - break; - case 0x0CA3: - furi_string_cat_printf(name, "Shield Striker"); - break; - case 0x0CA4: - furi_string_cat_printf(name, "Sun Runner"); - break; - case 0x0CA5: - furi_string_cat_printf(name, "Sea Shadow"); - break; - case 0x0CA6: - furi_string_cat_printf(name, "Splatter Splasher"); - break; - case 0x0CA7: - furi_string_cat_printf(name, "Soda Skimmer"); - break; - case 0x0CA8: - furi_string_cat_printf(name, "Barrel Blaster"); - break; - case 0x0CA9: - furi_string_cat_printf(name, "Buzz Wing"); - break; - case 0x0CE4: - furi_string_cat_printf(name, "Sheep Wreck Island"); - break; - case 0x0CE5: - furi_string_cat_printf(name, "Tower of Time"); - break; - case 0x0CE6: - furi_string_cat_printf(name, "Fiery Forge"); - break; - case 0x0CE7: - furi_string_cat_printf(name, "Arkeyan Crossbow"); - break; - case 0x0D48: - furi_string_cat_printf(name, "Fiesta"); - break; - case 0x0D49: - furi_string_cat_printf(name, "High Volt"); - break; - case 0x0D4A: - furi_string_cat_printf(name, "Splat"); - break; - case 0x0D4E: - furi_string_cat_printf(name, "Stormblade"); - break; - case 0x0D53: - furi_string_cat_printf(name, "Smash It"); - break; - case 0x0D54: - furi_string_cat_printf(name, "Spitfire"); - break; - case 0x0D55: - furi_string_cat_printf(name, "Hurricane Jet-Vac"); - break; - case 0x0D56: - furi_string_cat_printf(name, "Double Dare Trigger Happy"); - break; - case 0x0D57: - furi_string_cat_printf(name, "Super Shot Stealth Elf"); - break; - case 0x0D58: - furi_string_cat_printf(name, "Shark Shooter Terrafin"); - break; - case 0x0D59: - furi_string_cat_printf(name, "Bone Bash Roller Brawl"); - break; - case 0x0D5C: - furi_string_cat_printf(name, "Big Bubble Pop Fizz"); - break; - case 0x0D5D: - furi_string_cat_printf(name, "Lava Lance Eruptor"); - break; - case 0x0D5E: - furi_string_cat_printf(name, "Deep Dive Gill Grunt"); - break; - case 0x0D5F: - furi_string_cat_printf(name, "Turbo Charge Donkey Kong"); - break; - case 0x0D60: - furi_string_cat_printf(name, "Hammer Slam Bowser"); - break; - case 0x0D61: - furi_string_cat_printf(name, "Dive-Clops"); - break; - case 0x0D62: - furi_string_cat_printf(name, "Astroblast"); - break; - case 0x0D63: - furi_string_cat_printf(name, "Nightfall"); - break; - case 0x0D64: - furi_string_cat_printf(name, "Thrillipede"); - break; - case 0x0DAC: - furi_string_cat_printf(name, "Sky Trophy"); - break; - case 0x0DAD: - furi_string_cat_printf(name, "Land Trophy"); - break; - case 0x0DAE: - furi_string_cat_printf(name, "Sea Trophy"); - break; - case 0x0DAF: - furi_string_cat_printf(name, "Kaos Trophy"); - break; - default: - furi_string_cat_printf(name, "Unknown"); - break; - } - - return true; -} - static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); @@ -830,7 +137,11 @@ static bool skylanders_parse(const NfcDevice* device, FuriString* parsed_data) { const uint16_t id = (uint16_t)*data->block[1].data; if(id == 0) break; - bool success = fill_name(id, name); + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool success = skylanders_get_name(storage, id, name); + + furi_record_close(RECORD_STORAGE); if(!success) break; furi_string_printf(parsed_data, "\e#Skylanders\n%s", furi_string_get_cstr(name)); diff --git a/applications/main/nfc/resources/nfc/assets/skylanders.nfc b/applications/main/nfc/resources/nfc/assets/skylanders.nfc new file mode 100644 index 000000000..58e6d5276 --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/skylanders.nfc @@ -0,0 +1,247 @@ +Filetype: Flipper NFC resources +Version: 1 +# ID: Name +0000: Whirlwind +0001: Sonic Boom +0002: Warnado +0003: Lightning Rod +0004: Bash +0194: Bash +0005: Terrafin +0006: Dino-Rang +0007: Prism Break +0008: Sunburn +0009: Eruptor +000A: Ignitor +000B: Flameslinger +000C: Zap +000D: Wham-Shell +000E: Gill Grunt +000F: Slam Bam +0010: Spyro +01A0: Spyro +0011: Voodood +0012: Double Trouble +0013: Trigger Happy +01A3: Trigger Happy +0014: Drobot +0015: Drill Sergeant +0016: Boomer +0017: Wrecking Ball +0018: Camo +0019: Zook +001A: Stealth Elf +001B: Stump Smash +001C: Dark Spyro +001D: Hex +001E: Chop Chop +01AE: Chop Chop +001F: Ghost Roaster +0020: Cynder +0064: Jet Vac +0065: Swarm +0066: Crusher +0067: Flashwing +0068: Hot Head +0069: Hot Dog +006A: Chill +006B: Thumpback +006C: Pop Fizz +006D: Ninjini +006E: Bouncer +006F: Sprocket +0070: Tree Rex +0071: Shroomboom +0072: Eye-Brawl +0073: Fright Rider +00C8: Anvil Rain +00C9: Treasure Chest +00CA: Healing Elixer +00CB: Ghost Swords +00CC: Time Twister +00CD: Sky-Iron Shield +00CE: Winged Boots +00CF: Sparx Dragonfly +00D0: Dragonfire Cannon +00D1: Scorpion Striker Catapult +00D2: Trap - Magic +00D3: Trap - Water +00D4: Trap - Air +00D5: Trap - Undead +00D6: Trap - Tech +00D7: Trap - Fire +00D8: Trap - Earth +00D9: Trap - Life +00DA: Trap - Light +00DB: Trap - Dark +00DC: Trap - Kaos +00E6: Hand Of Fate +00E7: Piggy Bank +00E8: Rocket Ram +00E9: Tiki Speaky +00EB: Imaginite Mystery Chest +012C: Dragons Peak +012D: Empire of Ice +012E: Pirate Seas +012F: Darklight Crypt +0130: Volcanic Vault +0131: Mirror Of Mystery +0132: Nightmare Express +0133: Sunscraper Spire +0134: Midnight Museum +01C2: Gusto +01C3: Thunderbolt +01C4: Fling Kong +01C5: Blades +01C6: Wallop +01C7: Head Rush +01C8: Fist Bump +01C9: Rocky Roll +01CA: Wildfire +01CB: Ka Boom +01CC: Trail Blazer +01CD: Torch +01CE: Snap Shot +01CF: Lob Star +01D0: Flip Wreck +01D1: Echo +01D2: Blastermind +01D3: Enigma +01D4: Deja Vu +01D5: Cobra Cadabra +01D6: Jawbreaker +01D7: Gearshift +01D8: Chopper +01D9: Tread Head +01DA: Bushwhack +01DB: Tuff Luck +01DC: Food Fight +01DD: High Five +01DE: Krypt King +01DF: Short Cut +01E0: Bat Spin +01E1: Funny Bone +01E2: Knight light +01E3: Spotlight +01E4: Knight Mare +01E5: Blackout +01F6: Bop +01F7: Spry +01F8: Hijinx +01F9: Terrabite +01FA: Breeze +01FB: Weeruptor +01FC: Pet Vac +01FD: Small Fry +01FE: Drobit +0202: Gill Runt +0207: Trigger Snappy +020E: Whisper Elf +021C: Barkley +021D: Thumpling +021E: Mini Jini +021F: Eye Small +0259: King Pen +0265: Golden Queen +02AD: Fire Acorn +03E8: (Boom) Jet +03E9: (Free) Ranger +03EA: (Rubble) Rouser +03EB: (Doom) Stone +03EC: Blast Zone +03ED: (Fire) Kraken +03EE: (Stink) Bomb +03EF: (Grilla) Drilla +03F0: (Hoot) Loop +03F1: (Trap) Shadow +03F2: (Magna) Charge +03F3: (Spy) Rise +03F4: (Night) Shift +03F5: (Rattle) Shake +03F6: (Freeze) Blade +03F7: Wash Buckler +07D0: Boom (Jet) +07D1: Free (Ranger) +07D2: Rubble (Rouser) +07D3: Doom (Stone) +07D4: Blast Zone (Head) +07D5: Fire (Kraken) +07D6: Stink (Bomb) +07D7: Grilla (Drilla) +07D8: Hoot (Loop) +07D9: Trap (Shadow) +07DA: Magna (Charge) +07DB: Spy (Rise) +07DC: Night (Shift) +07DD: Rattle (Shake) +07DE: Freeze (Blade) +07DF: Wash Buckler (Head) +0BB8: Scratch +0BB9: Pop Thorn +0BBA: Slobber Tooth +0BBB: Scorp +0BBC: Fryno +0BBD: Smolderdash +0BBE: Bumble Blast +0BBF: Zoo Lou +0BC0: Dune Bug +0BC1: Star Strike +0BC2: Countdown +0BC3: Wind Up +0BC4: Roller Brawl +0BC5: Grim Creeper +0BC6: Rip Tide +0BC7: Punk Shock +0C80: Battle Hammer +0C81: Sky Diamond +0C82: Platinum Sheep +0C83: Groove Machine +0C84: UFO Hat +0C94: Jet Stream +0C95: Tomb Buggy +0C96: Reef Ripper +0C97: Burn Cycle +0C98: Hot Streak +0C99: Shark Tank +0C9A: Thump Truck +0C9B: Crypt Crusher +0C9C: Stealth Stinger +0C9F: Dive Bomber +0CA0: Sky Slicer +0CA1: Clown Cruiser +0CA2: Gold Rusher +0CA3: Shield Striker +0CA4: Sun Runner +0CA5: Sea Shadow +0CA6: Splatter Splasher +0CA7: Soda Skimmer +0CA8: Barrel Blaster +0CA9: Buzz Wing +0CE4: Sheep Wreck Island +0CE5: Tower of Time +0CE6: Fiery Forge +0CE7: Arkeyan Crossbow +0D48: Fiesta +0D49: High Volt +0D4A: Splat +0D4E: Stormblade +0D53: Smash It +0D54: Spitfire +0D55: Hurricane Jet-Vac +0D56: Double Dare Trigger Happy +0D57: Super Shot Stealth Elf +0D58: Shark Shooter Terrafin +0D59: Bone Bash Roller Brawl +0D5C: Big Bubble Pop Fizz +0D5D: Lava Lance Eruptor +0D5E: Deep Dive Gill Grunt +0D5F: Turbo Charge Donkey Kong +0D60: Hammer Slam Bowser +0D61: Dive-Clops +0D62: Astroblast +0D63: Nightfall +0D64: Thrillipede +0DAC: Sky Trophy +0DAD: Land Trophy +0DAE: Sea Trophy +0DAF: Kaos Trophy From 9bcd2ecd9b6e85b6a04562f940e4d50873a3512b Mon Sep 17 00:00:00 2001 From: Astra Date: Wed, 5 Jun 2024 18:51:15 +0900 Subject: [PATCH 20/28] Fix navigation --- applications/main/nfc/scenes/nfc_scene_exit_confirm.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index 7c4a3d19d..d62ee2d8e 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -28,13 +28,9 @@ bool nfc_scene_exit_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, NfcSceneSelectProtocol)) { - consumed = scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneSelectProtocol); - } else if( - scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack) && - (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || - scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack) && + (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { const uint32_t possible_scenes[] = {NfcSceneReadMenu, NfcSceneSavedMenu}; consumed = scene_manager_search_and_switch_to_previous_scene_one_of( nfc->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); From e817f1c38150f02f9f973227e5df4875e2083303 Mon Sep 17 00:00:00 2001 From: RebornedBrain Date: Wed, 5 Jun 2024 15:14:45 +0300 Subject: [PATCH 21/28] Fix api_symbols.csv for f18 --- targets/f18/api_symbols.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7eda31675..57a34b2c1 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,63.0,, +Version,+,64.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, From 1db05ed2c6a8393bef9df1a4b59f3635b76d8d6d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 5 Jun 2024 22:18:09 +0300 Subject: [PATCH 22/28] Revert "add temp fix since no proper fix exist at the moment" This reverts commit 75ece9b697d1e3b66a75f2d423e51f369bc387f1. --- lib/nfc/helpers/iso14443_4_layer.c | 6 +----- lib/toolbox/bit_buffer.c | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 0e742ce95..d53aef64f 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -66,13 +66,9 @@ bool iso14443_4_layer_decode_block( bool ret = false; - // TODO: Fix properly! this is a very big kostyl na velosipede - // (bit_buffer_copy_right are called to copy bigger buffer into smaller buffer causing crash on furi check) issue comes iso14443_4a_poller_send_block at line 109 - if(bit_buffer_get_size_bytes(output_data) < bit_buffer_get_size_bytes(output_data) - 1) - return ret; - do { if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; + // TODO: Fix crash bit_buffer_copy_right(output_data, block_data, 1); ret = true; } while(false); diff --git a/lib/toolbox/bit_buffer.c b/lib/toolbox/bit_buffer.c index 85a52e79d..d8f62ef4c 100644 --- a/lib/toolbox/bit_buffer.c +++ b/lib/toolbox/bit_buffer.c @@ -58,6 +58,7 @@ void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_ furi_check(buf); furi_check(other); furi_check(bit_buffer_get_size_bytes(other) > start_index); + // TODO: Fix crash furi_check(buf->capacity_bytes >= bit_buffer_get_size_bytes(other) - start_index); memcpy(buf->data, other->data + start_index, bit_buffer_get_size_bytes(other) - start_index); From 71b072b50c903e8541f35fc16750529fdb075362 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jun 2024 00:24:55 +0300 Subject: [PATCH 23/28] no server :( --- .drone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 6a25fb5ca..021e5348b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -375,7 +375,7 @@ trigger: - tag node: - typ: dev1 + typ: dev2 --- kind: pipeline @@ -678,4 +678,4 @@ trigger: - push node: - typ: dev1 + typ: dev2 From 305bf7273c81f5cb3e3e34ccf3deb2d84c7fabda Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jun 2024 00:41:57 +0300 Subject: [PATCH 24/28] fix clock --- applications/main/clock_app/clock_app.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/main/clock_app/clock_app.c b/applications/main/clock_app/clock_app.c index 8f4bbd8df..903861de4 100644 --- a/applications/main/clock_app/clock_app.c +++ b/applications/main/clock_app/clock_app.c @@ -6,8 +6,10 @@ #include "clock_app.h" -static void clock_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { - furi_assert(event_queue); +static void clock_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; furi_message_queue_put(event_queue, &event, FuriWaitForever); } From bde889c338f2cbbc0aa79d63a571c5f853bfc761 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jun 2024 00:55:29 +0300 Subject: [PATCH 25/28] Reapply "Merge remote-tracking branch 'OFW/astra/3746-mfp-detect' into dev" This reverts commit 6bfa383cea9aa029b902d5c473743a171629c1d7. --- .../protocol_support/mf_plus/mf_plus.c | 121 ++++++ .../protocol_support/mf_plus/mf_plus.h | 5 + .../protocol_support/mf_plus/mf_plus_render.c | 67 ++++ .../protocol_support/mf_plus/mf_plus_render.h | 14 + .../nfc_protocol_support_defs.c | 2 + lib/nfc/SConscript | 2 + lib/nfc/protocols/mf_plus/mf_plus.c | 189 +++++++++ lib/nfc/protocols/mf_plus/mf_plus.h | 117 ++++++ lib/nfc/protocols/mf_plus/mf_plus_i.c | 216 +++++++++++ lib/nfc/protocols/mf_plus/mf_plus_i.h | 29 ++ lib/nfc/protocols/mf_plus/mf_plus_poller.c | 367 ++++++++++++++++++ lib/nfc/protocols/mf_plus/mf_plus_poller.h | 55 +++ .../protocols/mf_plus/mf_plus_poller_defs.h | 5 + lib/nfc/protocols/mf_plus/mf_plus_poller_i.c | 73 ++++ lib/nfc/protocols/mf_plus/mf_plus_poller_i.h | 54 +++ lib/nfc/protocols/nfc_device_defs.c | 2 + lib/nfc/protocols/nfc_poller_defs.c | 2 + lib/nfc/protocols/nfc_protocol.c | 7 + lib/nfc/protocols/nfc_protocol.h | 1 + targets/f7/api_symbols.csv | 16 + 20 files changed, 1344 insertions(+) create mode 100644 applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.h create mode 100644 applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c create mode 100644 applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.h create mode 100644 lib/nfc/protocols/mf_plus/mf_plus.c create mode 100644 lib/nfc/protocols/mf_plus/mf_plus.h create mode 100644 lib/nfc/protocols/mf_plus/mf_plus_i.c create mode 100644 lib/nfc/protocols/mf_plus/mf_plus_i.h create mode 100644 lib/nfc/protocols/mf_plus/mf_plus_poller.c create mode 100644 lib/nfc/protocols/mf_plus/mf_plus_poller.h create mode 100644 lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h create mode 100644 lib/nfc/protocols/mf_plus/mf_plus_poller_i.c create mode 100644 lib/nfc/protocols/mf_plus/mf_plus_poller_i.h diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c new file mode 100644 index 000000000..a020c9bbd --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c @@ -0,0 +1,121 @@ +#include "mf_plus.h" +#include "mf_plus_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +static void nfc_scene_info_on_enter_mf_plus(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus); + + 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_plus_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} +static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfPlus); + + NfcApp* instance = context; + const MfPlusPollerEvent* mf_plus_event = event.event_data; + + if(mf_plus_event->type == MfPlusPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfPlus, nfc_poller_get_data(instance->poller)); + FURI_LOG_D( + "MFP", + "Read success: %s", + nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_mf_plus(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_plus, instance); +} + +static void nfc_scene_read_success_on_enter_mf_plus(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus); + + 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_plus_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) { + const Iso14443_4aData* iso14443_4a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = + nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { + .features = NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.h b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.h new file mode 100644 index 000000000..7f2e63dd1 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_plus; \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c new file mode 100644 index 000000000..8640fa16d --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c @@ -0,0 +1,67 @@ +#include "mf_plus_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_mf_plus_info( + const MfPlusData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(mf_plus_get_base_data(data), str); + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(mf_plus_get_base_data(data), str); +} + +void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str) { + nfc_render_mf_plus_version(&data->version, str); +} + +void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str) { + furi_string_cat_printf( + str, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + data->uid[0], + data->uid[1], + data->uid[2], + data->uid[3], + data->uid[4], + data->uid[5], + data->uid[6]); + furi_string_cat_printf( + str, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->hw_vendor, + data->hw_type, + data->hw_subtype, + data->hw_major, + data->hw_minor, + data->hw_storage, + data->hw_proto); + furi_string_cat_printf( + str, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->sw_vendor, + data->sw_type, + data->sw_subtype, + data->sw_major, + data->sw_minor, + data->sw_storage, + data->sw_proto); + furi_string_cat_printf( + str, + "batch %02x:%02x:%02x:%02x:%02x\n" + "week %d year %d\n", + data->batch[0], + data->batch[1], + data->batch[2], + data->batch[3], + data->batch[4], + data->prod_week, + data->prod_year); +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.h b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.h new file mode 100644 index 000000000..5aa8436a9 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_plus_info( + const MfPlusData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str); + +void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c index 6b42a1660..a80cd6cc0 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -17,6 +17,7 @@ #include "felica/felica.h" #include "mf_ultralight/mf_ultralight.h" #include "mf_classic/mf_classic.h" +#include "mf_plus/mf_plus.h" #include "mf_desfire/mf_desfire.h" #include "emv/emv.h" #include "slix/slix.h" @@ -39,6 +40,7 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { [NfcProtocolFelica] = &nfc_protocol_support_felica, [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, + [NfcProtocolMfPlus] = &nfc_protocol_support_mf_plus, [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, [NfcProtocolSlix] = &nfc_protocol_support_slix, [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 4fb49f792..d30467740 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -21,6 +21,7 @@ env.Append( File("protocols/iso14443_4b/iso14443_4b.h"), File("protocols/mf_ultralight/mf_ultralight.h"), File("protocols/mf_classic/mf_classic.h"), + File("protocols/mf_plus/mf_plus.h"), File("protocols/mf_desfire/mf_desfire.h"), File("protocols/emv/emv.h"), File("protocols/slix/slix.h"), @@ -33,6 +34,7 @@ env.Append( File("protocols/iso14443_4b/iso14443_4b_poller.h"), File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), + File("protocols/mf_plus/mf_plus_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/slix/slix_poller.h"), File("protocols/emv/emv_poller.h"), diff --git a/lib/nfc/protocols/mf_plus/mf_plus.c b/lib/nfc/protocols/mf_plus/mf_plus.c new file mode 100644 index 000000000..98b7cd82a --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus.c @@ -0,0 +1,189 @@ +#include "mf_plus_i.h" + +#include +#include + +#define MF_PLUS_PROTOCOL_NAME "Mifare Plus" + +static const char* mf_plus_type_strings[] = { + [MfPlusTypeS] = "Plus S", + [MfPlusTypeX] = "Plus X", + [MfPlusTypeSE] = "Plus SE", + [MfPlusTypeEV1] = "Plus EV1", + [MfPlusTypeEV2] = "Plus EV2", + [MfPlusTypePlus] = "Plus", + [MfPlusTypeUnknown] = "Unknown", +}; + +static const char* mf_plus_size_strings[] = { + [MfPlusSize1K] = "1K", + [MfPlusSize2K] = "2K", + [MfPlusSize4K] = "4K", + [MfPlusSizeUnknown] = "Unknown", +}; + +static const char* mf_plus_security_level_strings[] = { + [MfPlusSecurityLevel0] = "SL0", + [MfPlusSecurityLevel1] = "SL1", + [MfPlusSecurityLevel2] = "SL2", + [MfPlusSecurityLevel3] = "SL3", + [MfPlusSecurityLevelUnknown] = "Unknown", +}; + +const NfcDeviceBase nfc_device_mf_plus = { + .protocol_name = MF_PLUS_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_plus_alloc, + .free = (NfcDeviceFree)mf_plus_free, + .reset = (NfcDeviceReset)mf_plus_reset, + .copy = (NfcDeviceCopy)mf_plus_copy, + .verify = (NfcDeviceVerify)mf_plus_verify, + .load = (NfcDeviceLoad)mf_plus_load, + .save = (NfcDeviceSave)mf_plus_save, + .is_equal = (NfcDeviceEqual)mf_plus_is_equal, + .get_name = (NfcDeviceGetName)mf_plus_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_plus_get_uid, + .set_uid = (NfcDeviceSetUid)mf_plus_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_plus_get_base_data, +}; + +MfPlusData* mf_plus_alloc(void) { + MfPlusData* data = malloc(sizeof(MfPlusData)); + data->device_name = furi_string_alloc(); + data->iso14443_4a_data = iso14443_4a_alloc(); + + data->type = MfPlusTypeUnknown; + data->security_level = MfPlusSecurityLevelUnknown; + data->size = MfPlusSizeUnknown; + + return data; +} + +void mf_plus_free(MfPlusData* data) { + furi_check(data); + furi_string_free(data->device_name); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void mf_plus_reset(MfPlusData* data) { + furi_check(data); + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->version, 0, sizeof(data->version)); + data->type = MfPlusTypeUnknown; + data->security_level = MfPlusSecurityLevelUnknown; + data->size = MfPlusSizeUnknown; +} + +void mf_plus_copy(MfPlusData* data, const MfPlusData* other) { + furi_check(data); + furi_check(other); + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->version = other->version; + data->type = other->type; + data->security_level = other->security_level; + data->size = other->size; +} + +bool mf_plus_verify(MfPlusData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, MF_PLUS_PROTOCOL_NAME); +} + +bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + if(!mf_plus_version_load(&data->version, ff)) break; + if(!mf_plus_type_load(&data->type, ff)) break; + if(!mf_plus_security_level_load(&data->security_level, ff)) break; + if(!mf_plus_size_load(&data->size, ff)) break; + success = true; + } while(false); + + return success; +} + +bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff) { + furi_assert(data); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + if(!flipper_format_write_comment_cstr(ff, MF_PLUS_PROTOCOL_NAME " specific data")) break; + if(!mf_plus_version_save(&data->version, ff)) break; + if(!mf_plus_type_save(&data->type, ff)) break; + if(!mf_plus_security_level_save(&data->security_level, ff)) break; + if(!mf_plus_size_save(&data->size, ff)) break; + success = true; + } while(false); + + return success; +} + +bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other) { + furi_assert(data); + furi_assert(other); + bool equal = false; + + do { + if(!iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data)) break; + if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break; + if(data->security_level != other->security_level) break; + if(data->type != other->type) break; + if(data->size != other->size) break; + equal = true; + } while(false); + + return equal; +} + +const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type) { + furi_check(data); + + FuriString* full_name = furi_string_alloc(); + const char* name = NULL; + + do { + if(name_type == NfcDeviceNameTypeFull) { + furi_string_reset(data->device_name); + furi_string_cat_printf( + data->device_name, + "Mifare %s %s %s", + mf_plus_type_strings[data->type], // Includes "Plus" for regular Mifare Plus cards + mf_plus_size_strings[data->size], + mf_plus_security_level_strings[data->security_level]); + name = furi_string_get_cstr(data->device_name); + FURI_LOG_D("Mifare Plus", "Full name: %s", name); + } else if(name_type == NfcDeviceNameTypeShort) { + name = "Mifare Plus"; + } else { + break; + } + } while(false); + + furi_string_free(full_name); + FURI_LOG_D("Mifare Plus", "Name: %s", name); + return name; +} + +const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} +Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data) { + furi_check(data); + return data->iso14443_4a_data; +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus.h b/lib/nfc/protocols/mf_plus/mf_plus.h new file mode 100644 index 000000000..828d1c070 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus.h @@ -0,0 +1,117 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_PLUS_UID_SIZE (7) +#define MF_PLUS_BATCH_SIZE (5) + +#define MF_PLUS_CMD_GET_VERSION (0x60) + +typedef enum { + MfPlusErrorNone, + MfPlusErrorUnknown, + MfPlusErrorNotPresent, + MfPlusErrorProtocol, + MfPlusErrorAuth, + MfPlusErrorPartialRead, + MfPlusErrorTimeout, +} MfPlusError; + +typedef enum { + MfPlusTypePlus, + MfPlusTypeEV1, + MfPlusTypeEV2, + MfPlusTypeS, + MfPlusTypeSE, + MfPlusTypeX, + + MfPlusTypeUnknown, + MfPlusTypeNum, +} MfPlusType; + +typedef enum { + MfPlusSize1K, + MfPlusSize2K, + MfPlusSize4K, + + MfPlusSizeUnknown, + MfPlusSizeNum, +} MfPlusSize; + +typedef enum { + MfPlusSecurityLevel0, + MfPlusSecurityLevel1, + MfPlusSecurityLevel2, + MfPlusSecurityLevel3, + + MfPlusSecurityLevelUnknown, + MfPlusSecurityLevelNum, +} MfPlusSecurityLevel; + +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[MF_PLUS_UID_SIZE]; + uint8_t batch[MF_PLUS_BATCH_SIZE]; + uint8_t prod_week; + uint8_t prod_year; +} MfPlusVersion; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + MfPlusVersion version; + MfPlusType type; + MfPlusSize size; + MfPlusSecurityLevel security_level; + FuriString* device_name; +} MfPlusData; + +extern const NfcDeviceBase nfc_device_mf_plus; + +MfPlusData* mf_plus_alloc(void); + +void mf_plus_free(MfPlusData* data); + +void mf_plus_reset(MfPlusData* data); + +void mf_plus_copy(MfPlusData* data, const MfPlusData* other); + +bool mf_plus_verify(MfPlusData* data, const FuriString* device_type); + +bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version); + +bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff); + +bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other); + +const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len); + +bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c new file mode 100644 index 000000000..c248d2568 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -0,0 +1,216 @@ +#include "mf_plus_i.h" + +#define MF_PLUS_FFF_VERSION_KEY \ + MF_PLUS_FFF_PICC_PREFIX " " \ + "Version" + +#define MF_PLUS_FFF_SECURITY_LEVEL_KEY "Security Level" +#define MF_PLUS_FFF_CARD_TYPE_KEY "Card Type" +#define MF_PLUS_FFF_MEMORY_SIZE_KEY "Memory Size" + +bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion)); + } + + return can_parse; +} + +bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSecurityLevel); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusSecurityLevel)); + } + + return can_parse; +} + +bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusType); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusType)); + } + + return can_parse; +} + +bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSize); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusSize)); + } + + return can_parse; +} + +bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff) { + return flipper_format_read_hex( + ff, MF_PLUS_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfPlusVersion)); +} + +bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff) { + FuriString* security_level_string = furi_string_alloc(); + flipper_format_read_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string); + + // Take the last character of the string + char security_level_char = furi_string_get_char( + security_level_string, furi_string_utf8_length(security_level_string) - 1); + + switch(security_level_char) { + case '0': + *data = MfPlusSecurityLevel0; + break; + case '1': + *data = MfPlusSecurityLevel1; + break; + case '2': + *data = MfPlusSecurityLevel2; + break; + case '3': + *data = MfPlusSecurityLevel3; + break; + default: + *data = MfPlusSecurityLevelUnknown; + break; + } + + furi_string_free(security_level_string); + + return true; +} + +bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff) { + FuriString* type_string = furi_string_alloc(); + flipper_format_read_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string); + + if(furi_string_equal_str(type_string, "Mifare Plus")) { + *data = MfPlusTypePlus; + } else if(furi_string_equal_str(type_string, "Mifare Plus X")) { + *data = MfPlusTypeX; + } else if(furi_string_equal_str(type_string, "Mifare Plus S")) { + *data = MfPlusTypeS; + } else if(furi_string_equal_str(type_string, "Mifare Plus SE")) { + *data = MfPlusTypeSE; + } else if(furi_string_equal_str(type_string, "Mifare Plus EV1")) { + *data = MfPlusTypeEV1; + } else if(furi_string_equal_str(type_string, "Mifare Plus EV2")) { + *data = MfPlusTypeEV2; + } else { + *data = MfPlusTypeUnknown; + } + + furi_string_free(type_string); + return true; +} + +bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff) { + FuriString* size_string = furi_string_alloc(); + flipper_format_read_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string); + + if(furi_string_equal_str(size_string, "1K")) { + *data = MfPlusSize1K; + } else if(furi_string_equal_str(size_string, "2K")) { + *data = MfPlusSize2K; + } else if(furi_string_equal_str(size_string, "4K")) { + *data = MfPlusSize4K; + } else { + *data = MfPlusSizeUnknown; + } + + furi_string_free(size_string); + return true; +} + +bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff) { + return flipper_format_write_hex( + ff, MF_PLUS_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfPlusVersion)); +} + +bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff) { + FuriString* security_level_string = furi_string_alloc(); + + switch(*data) { + case MfPlusSecurityLevel0: + furi_string_cat(security_level_string, "SL0"); + break; + case MfPlusSecurityLevel1: + furi_string_cat(security_level_string, "SL1"); + break; + case MfPlusSecurityLevel2: + furi_string_cat(security_level_string, "SL2"); + break; + case MfPlusSecurityLevel3: + furi_string_cat(security_level_string, "SL3"); + break; + default: + furi_string_cat(security_level_string, "Unknown"); + break; + } + + flipper_format_write_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string); + furi_string_free(security_level_string); + + return true; +} + +bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff) { + FuriString* type_string = furi_string_alloc(); + + switch(*data) { + case MfPlusTypePlus: + furi_string_cat(type_string, "Mifare Plus"); + break; + case MfPlusTypeX: + furi_string_cat(type_string, "Mifare Plus X"); + break; + case MfPlusTypeS: + furi_string_cat(type_string, "Mifare Plus S"); + break; + case MfPlusTypeSE: + furi_string_cat(type_string, "Mifare Plus SE"); + break; + case MfPlusTypeEV1: + furi_string_cat(type_string, "Mifare Plus EV1"); + break; + case MfPlusTypeEV2: + furi_string_cat(type_string, "Mifare Plus EV2"); + break; + default: + furi_string_cat(type_string, "Unknown"); + break; + } + + flipper_format_write_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string); + furi_string_free(type_string); + + return true; +} + +bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff) { + FuriString* size_string = furi_string_alloc(); + + switch(*data) { + case MfPlusSize1K: + furi_string_cat(size_string, "1K"); + break; + case MfPlusSize2K: + furi_string_cat(size_string, "2K"); + break; + case MfPlusSize4K: + furi_string_cat(size_string, "4K"); + break; + default: + furi_string_cat(size_string, "Unknown"); + break; + } + + flipper_format_write_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string); + furi_string_free(size_string); + + return true; +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h new file mode 100644 index 000000000..8ced4bdd0 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -0,0 +1,29 @@ +#pragma once + +#include "mf_plus.h" + +#define MF_PLUS_FFF_PICC_PREFIX "PICC" + +bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf); + +bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf); + +bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf); + +bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf); + +bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff); + +bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff); + +bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff); + +bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff); + +bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff); + +bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff); + +bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff); + +bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c new file mode 100644 index 000000000..a218229c5 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -0,0 +1,367 @@ +#include "mf_plus_poller_i.h" + +#include + +#include + +#define TAG "MfPlusPoller" + +#define MF_PLUS_BUF_SIZE (64U) +#define MF_PLUS_RESULT_BUF_SIZE (512U) + +const char* mf_plus_ats_t1_tk_values[] = { + "\xC1\x05\x2F\x2F\x00\x35\xC7", // Mifare Plus S + "\xC1\x05\x2F\x2F\x01\xBC\xD6", // Mifare Plus X + "\xC1\x05\x2F\x2F\x00\xF6\xD1", // Mifare Plus SE + "\xC1\x05\x2F\x2F\x01\xF6\xD1", // Mifare Plus SE +}; + +typedef NfcCommand (*MfPlusPollerReadHandler)(MfPlusPoller* instance); + +const MfPlusData* mf_plus_poller_get_data(MfPlusPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +bool mf_plus_poller_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) { + furi_assert(iso4_data); + furi_assert(mf_plus_data); + + switch(iso4_data->iso14443_3a_data->sak) { + case 0x08: + if(memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 2K SL1 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus X 2K SL1 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[2], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 || + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[3], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus SE 1K SL1 + mf_plus_data->type = MfPlusTypeSE; + mf_plus_data->size = MfPlusSize1K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus SE 1K SL1"); + return true; + } else { + FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type"); + return false; + } + case 0x18: + if(memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 4K SL1 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus X 4K SL1 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL1"); + return true; + } else { + FURI_LOG_D(TAG, "Sak 18 but no known Mifare Plus type"); + return false; + } + case 0x20: + if(memcmp( + iso4_data->ats_data.t1_tk, + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 2/4K SL3 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) { + // Mifare Plus S 2K SL3 + mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL3"); + } else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) { + // Mifare Plus S 4K SL3 + mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL3"); + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)"); + return false; + } + return true; + + } else if( + memcmp( + iso4_data->ats_data.t1_tk, + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) { + mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); + } else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) { + mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); + return false; + } + return true; + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); + return false; + } + } + + FURI_LOG_D(TAG, "No known Mifare Plus type"); + return false; +} + +static bool mf_plus_poller_detect_type(MfPlusPoller* instance) { + furi_assert(instance); + + bool detected = false; + + const Iso14443_4aData* iso14443_4a_data = + iso14443_4a_poller_get_data(instance->iso14443_4a_poller); + const MfPlusError error = mf_plus_poller_read_version(instance, &instance->data->version); + if(error == MfPlusErrorNone) { + FURI_LOG_D(TAG, "Read version success: %d", error); + if(instance->data->version.hw_major == 0x02 || instance->data->version.hw_major == 0x82) { + detected = true; + if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) { + // Mifare Plus 2K SL2 + instance->data->type = MfPlusTypePlus; + instance->data->size = MfPlusSize2K; + instance->data->security_level = MfPlusSecurityLevel2; + } else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) { + // Mifare Plus 4K SL3 + instance->data->type = MfPlusTypePlus; + instance->data->size = MfPlusSize4K; + instance->data->security_level = MfPlusSecurityLevel3; + } else { + // Mifare Plus EV1/EV2 + + // Revision + switch(instance->data->version.hw_major) { + case 0x11: + instance->data->type = MfPlusTypeEV1; + break; + case 0x22: + instance->data->type = MfPlusTypeEV2; + break; + default: + instance->data->type = MfPlusTypeUnknown; + break; + } + + // Storage size + switch(instance->data->version.hw_storage) { + case 0x16: + instance->data->size = MfPlusSize2K; + break; + case 0x18: + instance->data->size = MfPlusSize4K; + break; + default: + instance->data->size = MfPlusSizeUnknown; + break; + } + + // Security level + if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) { + // Mifare Plus EV1/2 SL3 + instance->data->security_level = MfPlusSecurityLevel3; + } else { + // Mifare Plus EV1/2 SL1 + instance->data->security_level = MfPlusSecurityLevel1; + } + } + } + + } else { + FURI_LOG_D(TAG, "Read version error: %d", error); + detected = mf_plus_poller_get_type_from_iso4(iso14443_4a_data, instance->data); + } + + return detected; +} + +MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + furi_assert(iso14443_4a_poller); + + MfPlusPoller* instance = malloc(sizeof(MfPlusPoller)); + furi_assert(instance); + + instance->iso14443_4a_poller = iso14443_4a_poller; + + instance->data = mf_plus_alloc(); + + instance->tx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE); + instance->input_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(MF_PLUS_RESULT_BUF_SIZE); + + instance->general_event.protocol = NfcProtocolMfPlus; + instance->general_event.event_data = &instance->mfp_event; + instance->general_event.instance = instance; + + instance->mfp_event.data = &instance->mfp_event_data; + + return instance; +} + +static NfcCommand mf_plus_poller_handler_idle(MfPlusPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_reset(instance->result_buffer); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = MfPlusPollerStateReadVersion; + return NfcCommandContinue; +} + +static NfcCommand mf_plus_poller_handler_read_version(MfPlusPoller* instance) { + bool success = mf_plus_poller_detect_type(instance); + if(success) { + instance->state = MfPlusPollerStateReadSuccess; + } else { + instance->state = MfPlusPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_plus_poller_handler_read_failed(MfPlusPoller* instance) { + furi_assert(instance); + FURI_LOG_D(TAG, "Read failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mfp_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = MfPlusPollerStateIdle; + return command; +} + +static NfcCommand mf_plus_poller_handler_read_success(MfPlusPoller* instance) { + furi_assert(instance); + FURI_LOG_D(TAG, "Read success"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mfp_event.type = MfPlusPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const MfPlusPollerReadHandler mf_plus_poller_read_handler[MfPlusPollerStateNum] = { + [MfPlusPollerStateIdle] = mf_plus_poller_handler_idle, + [MfPlusPollerStateReadVersion] = mf_plus_poller_handler_read_version, + [MfPlusPollerStateReadFailed] = mf_plus_poller_handler_read_failed, + [MfPlusPollerStateReadSuccess] = mf_plus_poller_handler_read_success, +}; + +static void mf_plus_poller_set_callback( + MfPlusPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol = NfcProtocolIso14443_4a); + + MfPlusPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = mf_plus_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->mfp_event.type = MfPlusPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +void mf_plus_poller_free(MfPlusPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + bit_buffer_free(instance->input_buffer); + bit_buffer_free(instance->result_buffer); + mf_plus_free(instance->data); + free(instance); +} + +static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol = NfcProtocolIso14443_4a); + + MfPlusPoller* instance = context; + furi_assert(instance); + + Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + detected = mf_plus_poller_detect_type(instance); + } + + return detected; +} + +const NfcPollerBase mf_plus_poller = { + .alloc = (NfcPollerAlloc)mf_plus_poller_alloc, + .free = (NfcPollerFree)mf_plus_poller_free, + .set_callback = (NfcPollerSetCallback)mf_plus_poller_set_callback, + .run = (NfcPollerRun)mf_plus_poller_run, + .detect = (NfcPollerDetect)mf_plus_poller_detect, + .get_data = (NfcPollerGetData)mf_plus_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.h b/lib/nfc/protocols/mf_plus/mf_plus_poller.h new file mode 100644 index 000000000..7e892366f --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.h @@ -0,0 +1,55 @@ +#pragma once + +#include "mf_plus.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief MIFARE Plus poller opaque type definition. + */ +typedef struct MfPlusPoller MfPlusPoller; + +/** + * @brief Enumeration of possible MfPlus poller event types. + */ + +typedef enum { + MfPlusPollerEventTypeReadSuccess, /**< Card was read successfully. */ + MfPlusPollerEventTypeReadFailed, /**< Poller failed to read the card. */ +} MfPlusPollerEventType; + +/** + * @brief MIFARE Plus poller event data. + */ +typedef union { + MfPlusError error; /**< Error code indicating card reading fail reason. */ +} MfPlusPollerEventData; + +/** + * @brief MIFARE Plus poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + MfPlusPollerEventType type; /**< Type of emitted event. */ + MfPlusPollerEventData* data; /**< Pointer to event specific data. */ +} MfPlusPollerEvent; + +/** + * @brief Read MfPlus card version. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfPlusVersion structure to be filled with version data. + * @return MfPlusErrorNone on success, an error code on failure. + */ +MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h b/lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h new file mode 100644 index 000000000..366eb5116 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase mf_plus_poller; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c new file mode 100644 index 000000000..1a4298b67 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -0,0 +1,73 @@ +#include "mf_plus_poller_i.h" + +#include + +#include "mf_plus_i.h" + +#define TAG "MfPlusPoller" + +MfPlusError mf_plus_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return MfPlusErrorNone; + case Iso14443_4aErrorNotPresent: + return MfPlusErrorNotPresent; + case Iso14443_4aErrorTimeout: + return MfPlusErrorTimeout; + default: + return MfPlusErrorProtocol; + } +} + +MfPlusError + mf_plus_send_chunk(MfPlusPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { + furi_assert(instance); + furi_assert(instance->iso14443_4a_poller); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + MfPlusError error = MfPlusErrorNone; + + do { + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + error = mf_plus_process_error(iso14443_4a_error); + break; + } + + bit_buffer_reset(instance->tx_buffer); + + if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { + bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } else { + bit_buffer_reset(rx_buffer); + } + } while(false); + + return error; +} + +MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION); + + MfPlusError error; + + do { + error = mf_plus_send_chunk(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfPlusErrorNone) break; + + if(!mf_plus_version_parse(data, instance->result_buffer)) { + error = MfPlusErrorProtocol; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h new file mode 100644 index 000000000..79f46b8d8 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h @@ -0,0 +1,54 @@ +#pragma once + +#include "mf_plus_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_PLUS_FWT_FC (60000) + +typedef enum { + MfPlusCardStateDetected, + MfPlusCardStateLost, +} MfPlusCardState; + +typedef enum { + MfPlusPollerStateIdle, + MfPlusPollerStateReadVersion, + MfPlusPollerStateReadFailed, + MfPlusPollerStateReadSuccess, + + MfPlusPollerStateNum, +} MfPlusPollerState; + +struct MfPlusPoller { + Iso14443_4aPoller* iso14443_4a_poller; + + MfPlusData* data; + MfPlusPollerState state; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* input_buffer; + BitBuffer* result_buffer; + + MfPlusError error; + NfcGenericEvent general_event; + MfPlusPollerEvent mfp_event; + MfPlusPollerEventData mfp_event_data; + NfcGenericCallback callback; + void* context; +}; + +MfPlusError mf_plus_process_error(Iso14443_4aError error); + +MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller); + +void mf_plus_poller_free(MfPlusPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index e09523f23..36a58a243 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { [NfcProtocolFelica] = &nfc_device_felica, [NfcProtocolMfUltralight] = &nfc_device_mf_ultralight, [NfcProtocolMfClassic] = &nfc_device_mf_classic, + [NfcProtocolMfPlus] = &nfc_device_mf_plus, [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index e79c96d98..a4717f1b1 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolFelica] = &nfc_poller_felica, [NfcProtocolMfUltralight] = &mf_ultralight_poller, [NfcProtocolMfClassic] = &mf_classic_poller, + [NfcProtocolMfPlus] = &mf_plus_poller, [NfcProtocolMfDesfire] = &mf_desfire_poller, [NfcProtocolSlix] = &nfc_poller_slix, [NfcProtocolSt25tb] = &nfc_poller_st25tb, diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 17c39fc9f..4106e689a 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -63,6 +63,7 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { /** List of ISO14443-4A child protocols. */ static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { NfcProtocolMfDesfire, + NfcProtocolMfPlus, NfcProtocolEmv, }; @@ -131,6 +132,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, + [NfcProtocolMfPlus] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, [NfcProtocolMfDesfire] = { .parent_protocol = NfcProtocolIso14443_4a, diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index 39e8045fe..12866528e 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -184,6 +184,7 @@ typedef enum { NfcProtocolFelica, NfcProtocolMfUltralight, NfcProtocolMfClassic, + NfcProtocolMfPlus, NfcProtocolMfDesfire, NfcProtocolSlix, NfcProtocolSt25tb, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7a790b0e1..e0d592d9c 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -156,6 +156,8 @@ Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire_poller.h,, +Header,+,lib/nfc/protocols/mf_plus/mf_plus.h,, +Header,+,lib/nfc/protocols/mf_plus/mf_plus_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, @@ -2563,6 +2565,19 @@ Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" +Function,+,mf_plus_alloc,MfPlusData*, +Function,+,mf_plus_copy,void,"MfPlusData*, const MfPlusData*" +Function,+,mf_plus_free,void,MfPlusData* +Function,+,mf_plus_get_base_data,Iso14443_4aData*,const MfPlusData* +Function,+,mf_plus_get_device_name,const char*,"const MfPlusData*, NfcDeviceNameType" +Function,+,mf_plus_get_uid,const uint8_t*,"const MfPlusData*, size_t*" +Function,+,mf_plus_is_equal,_Bool,"const MfPlusData*, const MfPlusData*" +Function,+,mf_plus_load,_Bool,"MfPlusData*, FlipperFormat*, uint32_t" +Function,+,mf_plus_poller_read_version,MfPlusError,"MfPlusPoller*, MfPlusVersion*" +Function,+,mf_plus_reset,void,MfPlusData* +Function,+,mf_plus_save,_Bool,"const MfPlusData*, FlipperFormat*" +Function,+,mf_plus_set_uid,_Bool,"MfPlusData*, const uint8_t*, size_t" +Function,+,mf_plus_verify,_Bool,"MfPlusData*, const FuriString*" Function,+,mf_ultralight_alloc,MfUltralightData*, Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*" Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData* @@ -3905,6 +3920,7 @@ Variable,-,nfc_device_emv,const NfcDeviceBase, Variable,-,nfc_device_felica,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, +Variable,-,nfc_device_mf_plus,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, Variable,-,nfc_device_st25tb,const NfcDeviceBase, Variable,+,sequence_audiovisual_alert,const NotificationSequence, From 8938cbfc6e3228400f0da34ede77297c96b92e53 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jun 2024 01:33:47 +0300 Subject: [PATCH 26/28] add subghz protocol - legrand by user890104 https://github.com/Next-Flip/Momentum-Firmware/pull/120/files --- .../unit_tests/subghz/legrand_2E37F.sub | 10 + .../unit_tests/subghz/legrand_2E37F_raw.sub | 22 + lib/subghz/protocols/legrand.c | 378 ++++++++++++++++++ lib/subghz/protocols/legrand.h | 117 ++++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 6 files changed, 529 insertions(+) create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub create mode 100644 applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F_raw.sub create mode 100644 lib/subghz/protocols/legrand.c create mode 100644 lib/subghz/protocols/legrand.h diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub new file mode 100644 index 000000000..87ac584f9 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub @@ -0,0 +1,10 @@ +Filetype: Flipper SubGhz Key File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Latitute: 0.000000 +Longitude: 0.000000 +Protocol: Legrand +Bit: 18 +Key: 00 00 00 00 00 02 E3 7F +TE: 358 diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F_raw.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F_raw.sub new file mode 100644 index 000000000..ba703925c --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F_raw.sub @@ -0,0 +1,22 @@ +Filetype: Flipper SubGhz RAW File +Version: 1 +Frequency: 433920000 +Preset: FuriHalSubGhzPresetOok650Async +Protocol: RAW +RAW_Data: 10133 -4946 65 -5114 65 -1750 67 -232 165 -536 165 -166 533 -694 395 -198 263 -66 395 -12664 297 -662 133 -264 99 -102 131 -200 131 -234 263 -66 265 -100 661 -168 197 -134 297 -66 431 -132 45005 -12358 131 -100 129 -66 65 -198 65 -66 197 -1018 131 -266 97 -1052 97 -228 459 -100 1517 -100 4361 -10350 165 -1264 65 -232 131 -200 65 -166 133 -232 299 -132 65 -166 299 -134 231 -198 99 -68 429 -12270 231 -628 163 -594 97 -496 227 -1220 163 -166 129 -66 655 -98 163 -66 393 -66 195 -198 293 -12130 67 -68 331 -134 99 -98 99 -332 99 -562 163 -164 65 -130 131 -920 131 -98 65 -198 133 -98 427 -130 359 -132 627 -66 2207 -16066 163 -1316 331 -832 131 -370 131 -132 661 -166 265 -134 833 -168 34477 -10228 65 -2266 261 -230 131 -198 129 -1282 65 -66 463 -100 365 -232 233 -66 297 -166 2151 -12330 99 -400 97 -436 131 -896 165 -400 363 -298 199 -166 97 -430 165 -168 365 -132 725 -130 3253 -12440 131 -796 297 -332 65 -830 65 -828 429 -198 335 -66 665 -266 29175 -12140 163 -230 131 -992 165 -1230 397 -132 199 -132 299 -66 659 -100 9675 -11036 99 -570 99 -830 165 -662 331 -266 429 -132 391 -396 431 -132 233 -166 231 -100 3435 -11728 99 -332 165 -166 63 -128 161 -326 129 -690 231 -130 265 -98 167 -232 199 -66 201 -264 165 -166 365 -66 727 -100 363 -12688 229 -228 95 -98 391 -198 129 -196 97 -364 97 -100 65 -266 99 -64 293 -98 327 -262 761 -66 7681 -5384 99 -762 195 -134 525 -992 99 -166 67 -98 329 -100 659 -100 293 -100 97 -98 163 -264 1741 -100 57331 -9834 67 -664 67 -2158 99 -862 133 -368 331 -864 65 -330 65 -794 99 -66 431 -100 3017 -66 7061 -6558 99 -6316 99 -266 131 -166 97 -198 65 -294 99 -66 661 -132 2661 -130 19413 -12380 65 -262 133 -66 67 -232 133 -798 197 -264 165 -498 97 -166 199 -134 559 -98 165 -13118 99 -562 65 -1256 231 -232 99 -132 131 -66 199 -66 429 -296 131 -200 201 -266 65 -66 895 -132 829 -66 12455 -11974 199 -334 99 -2160 165 -1232 929 -100 327 -166 295 -134 2191 -100 20415 -11032 65 -1170 131 -2326 163 -398 263 -596 99 -400 165 -68 499 -66 661 -11724 97 -1226 99 -166 233 -400 131 -662 165 -400 165 -166 299 -296 1159 -100 131 -98 427 -68 1643 -13934 101 -1294 65 -394 63 -1458 265 -166 265 -364 133 -434 101 -398 195 -264 1097 -166 265 -166 58013 -11978 131 -100 165 -666 199 -66 163 -166 65 -198 195 -592 1295 -134 597 -136 567 -100 1615 -100 17759 -12402 261 -692 +RAW_Data: 265 -730 327 -398 131 -300 199 -432 7947 -4772 233 -366 365 -166 133 -522 99 -164 65 -366 99 -100 395 -66 4423 -15068 197 -430 163 -398 65 -230 165 -228 97 -494 165 -134 197 -168 131 -168 65 -298 267 -132 1657 -98 99 -66 165 -100 7687 -12010 197 -132 163 -198 527 -130 293 -396 297 -360 163 -264 95 -164 161 -694 427 -262 129 -100 885 -100 131 -66 461 -9862 97 -5002 497 -594 197 -66 229 -596 65 -132 195 -262 99 -132 63 -166 65 -230 431 -100 165 -168 461 -164 13159 -12060 133 -64 129 -1316 131 -758 165 -430 165 -270 133 -564 995 -7536 99 -4746 165 -358 457 -132 165 -166 99 -100 397 -132 299 -66 197 -66 131 -392 129 -196 131 -328 459 -100 565 -66 165 -66 265 -100 1131 -100 9533 -12070 65 -400 163 -366 165 -530 197 -196 133 -68 233 -398 165 -66 99 -100 163 -132 791 -66 17359 -12434 131 -328 229 -558 195 -564 167 -132 133 -234 131 -100 65 -364 65 -496 65 -200 297 -66 1725 -12958 63 -68 65 -164 67 -132 63 -200 131 -166 165 -696 131 -166 67 -200 65 -168 1061 -12848 199 -166 2039 -1660 201 -5822 1017 -1126 299 -414 1015 -388 1061 -380 1055 -1086 327 -1118 339 -1084 351 -360 1051 -376 1083 -1070 343 -346 1105 -350 1069 -354 1087 -346 1063 -362 1075 -372 1067 -334 1097 -5694 1083 -1074 351 -378 1057 -332 1101 -370 1065 -1050 379 -1050 379 -1066 363 -348 1073 -388 1047 -1076 351 -380 1053 -362 1071 -372 1067 -352 1081 -352 1069 -350 1091 -346 1087 -5682 1095 -1044 381 -342 1097 -326 1111 -346 1061 -1064 379 -1066 381 -1032 379 -360 1065 -364 1067 -1066 375 -352 1081 -350 1069 -350 1089 -352 1081 -350 1069 -350 1097 -326 1081 -5698 1097 -1074 353 -354 1083 -326 1111 -344 1083 -1054 387 -1046 349 -1094 345 -350 1081 -354 1069 -1076 385 -350 1051 -354 1077 -386 1057 -334 1099 -370 1051 -354 1083 -356 1087 -5670 1107 -1072 353 -354 1081 -326 1111 -346 1085 -1054 389 -1050 353 -1056 377 -352 1083 -350 1071 -1080 381 -312 1099 -334 1107 -358 1079 -320 1091 -350 1095 -348 1081 -318 1113 -5688 1075 -1076 353 -354 1085 -324 1111 -346 1067 -1056 387 -1048 381 -1070 383 -314 1085 -352 1073 -1078 385 -348 1049 -354 1103 -356 1055 -334 1097 -372 1049 -354 1081 -354 1087 -5680 1109 -1038 383 -356 1085 -326 1107 -344 1065 -1066 361 -1076 371 -1054 389 -330 1069 -376 1069 -1054 389 -332 1073 -374 1069 -354 1079 -320 1097 -350 1093 -348 1083 -322 1111 -5680 1105 -1044 359 -356 1083 -354 1085 -348 1097 -1050 387 -1042 349 -1074 385 -350 1053 -352 1077 -1082 379 -314 1099 -336 1095 -346 1075 -374 1071 -354 1081 -320 1095 -348 1097 -5686 +RAW_Data: 1085 -1062 369 -346 1093 -322 1105 -350 1063 -1076 351 -1070 381 -1068 355 -358 1087 -354 1049 -1070 351 -384 1081 -318 1113 -344 1065 -348 1083 -352 1071 -388 1051 -352 1081 -5706 1065 -1064 361 -348 1087 -356 1079 -348 1091 -1072 381 -1020 375 -1068 361 -356 1081 -354 1085 -1042 381 -352 1079 -352 1101 -320 1093 -346 1087 -322 1117 -354 1057 -334 1099 -5702 1085 -1044 381 -340 1087 -352 1081 -352 1069 -1080 379 -1032 379 -1050 377 -346 1099 -332 1077 -1084 355 -334 1101 -334 1089 -350 1097 -320 1095 -348 1097 -326 1077 -376 1053 -5706 1073 -1088 367 -346 1063 -354 1071 -382 1067 -1076 347 -1068 381 -1064 347 -360 1085 -354 1047 -1072 385 -352 1087 -328 1073 -374 1051 -388 1045 -354 1093 -348 1085 -354 1079 -5668 1085 -1082 349 -348 1079 -374 1051 -382 1063 -1076 355 -1082 349 -1068 353 -364 1065 -362 1075 -1064 373 -346 1067 -362 1073 -382 1063 -352 1067 -380 1065 -326 1081 -376 1065 -5714 1075 -1072 345 -346 1099 -332 1097 -346 1075 -1062 373 -1066 347 -1076 373 -344 1069 -362 1073 -1072 383 -314 1113 -344 1063 -350 1079 -374 1065 -332 1099 -370 1065 -336 1099 -5694 1087 -1046 351 -368 1097 -348 1051 -354 1109 -1046 383 -1060 365 -1072 365 -352 1049 -352 1091 -1074 347 -384 1051 -352 1073 -384 1051 -350 1079 -354 1073 -380 1067 -326 1081 -5722 1065 -1078 355 -352 1081 -356 1085 -344 1083 -1050 385 -1050 353 -1092 345 -384 1051 -352 1069 -1078 381 -350 1051 -354 1069 -386 1057 -364 1073 -368 1071 -352 1049 -354 1093 -5710 1077 -1072 353 -354 1085 -326 1079 -376 1069 -1052 381 -1064 349 -1074 381 -346 1073 -368 1067 -1056 359 -350 1107 -348 1063 -352 1069 -382 1065 -326 1111 -346 1063 -364 1071 -5686 1099 -1072 355 -352 1083 -326 1111 -346 1065 -1086 353 -1050 383 -1068 383 -350 1047 -354 1071 -1076 381 -346 1069 -370 1065 -336 1095 -334 1099 -336 1099 -330 1073 -354 1081 -5710 1077 -1050 385 -348 1085 -322 1109 -356 1055 -1084 355 -1054 377 -1052 375 -346 1103 -350 1065 -1074 351 -342 1093 -346 1091 -322 1107 -356 1055 -364 1071 -372 1053 -354 1085 -5704 1077 -1050 383 -346 1073 -370 1065 -334 1097 -1060 371 -1062 347 -1080 373 -344 1069 -360 1075 -1076 351 -346 1109 -344 1063 -364 1069 -370 1065 -334 1099 -332 1097 -336 1097 -5690 1087 -1044 383 -338 1095 -348 1083 -320 1109 -1048 385 -1056 365 -1072 365 -356 1081 -320 1095 -1078 343 -346 1103 -330 1075 -352 1095 -350 1069 -350 1085 -352 1081 -350 1071 -5712 1075 -1042 385 -354 1085 -326 1079 -374 1051 -1084 351 -1064 383 -1068 347 -346 1099 -334 1103 -1054 347 -354 1099 -346 1083 -354 1081 -354 1053 -362 1071 -374 1065 -346 1067 -5726 1053 -1082 351 -360 1091 -344 1083 -350 +RAW_Data: 1081 -1074 353 -1074 351 -1088 325 -386 1051 -386 1047 -1074 353 -378 1051 -384 1047 -354 1073 -384 1049 -376 1065 -356 1073 -352 1087 -5680 1079 -1074 381 -350 1051 -354 1071 -386 1051 -1086 351 -1080 351 -1070 381 -350 1051 -352 1071 -1080 383 -348 1049 -354 1075 -384 1065 -324 1113 -346 1061 -350 1079 -374 1051 -5706 1077 -1086 333 -384 1047 -354 1093 -346 1065 -1078 353 -1094 345 -1082 351 -360 1089 -346 1063 -1074 353 -366 1065 -360 1075 -372 1067 -334 1101 -368 1051 -352 1085 -356 1051 -5734 1073 -1072 353 -354 1083 -328 1073 -376 1083 -1040 375 -1066 359 -1082 353 -334 1099 -368 1069 -1070 343 -346 1105 -354 1055 -362 1071 -370 1069 -352 1077 -352 1069 -350 1087 -5680 1079 -1086 367 -352 1049 -354 1097 -346 1063 -1078 349 -1092 345 -1098 347 -358 1065 -350 1079 -1064 373 -346 1067 -362 1081 -350 1065 -350 1095 -346 1067 -362 1075 -372 1049 -5706 1071 -1086 367 -352 1083 -320 1095 -348 1087 -1050 387 -1050 353 -1094 345 -346 1101 -332 1073 -1072 383 -346 1083 -346 1087 -352 1051 -354 1093 -346 1085 -346 1061 -354 1103 -5700 1069 -1050 393 -352 1063 -352 1069 -350 1097 -1044 383 -1068 379 -1030 379 -358 1065 -348 1073 -1068 377 -346 1099 -330 1073 -352 1093 -350 1073 -348 1087 -352 1081 -350 1069 -5710 1073 -1076 353 -354 1085 -324 1111 -344 1083 -1040 389 -1044 381 -1070 347 -350 1085 -352 1071 -1078 383 -352 1047 -352 1075 -352 1089 -350 1083 -352 1069 -350 1097 -326 1109 -5664 1113 -1040 383 -354 1085 -328 1075 -374 1051 -1084 351 -1066 351 -1096 379 -314 1099 -334 1097 -1052 379 -356 1069 -354 1079 -320 1097 -348 1093 -346 1083 -354 1077 -354 1055 -5714 1073 -1076 345 -350 1081 -354 1069 -382 1065 -1074 349 -1068 379 -1064 347 -360 1065 -364 1071 -1066 371 -344 1067 -360 1077 -354 1083 -354 1087 -346 1065 -364 1071 -368 1071 -5682 1075 -1084 367 -342 1067 -360 1075 -352 1097 -1040 353 -1074 385 -1058 363 -346 1075 -354 1081 -1076 351 -378 1057 -334 1101 -334 1103 -356 1079 -320 1091 -348 1093 -348 1085 -5678 1095 -1050 379 -356 1065 -334 1097 -370 1049 -1086 353 -1086 325 -1080 375 -352 1081 -350 1067 -1078 383 -350 1051 -352 1071 -386 1053 -334 1103 -336 1085 -354 1087 -324 1111 -5676 1079 -1078 351 -380 1055 -364 1073 -368 1051 -1082 349 -1064 351 -1092 347 -378 1073 -334 1099 -1050 381 -328 1095 -346 1083 -350 1081 -352 1069 -382 1049 -344 1095 -354 1077 -5682 1071 -1078 359 -352 1081 -356 1085 -346 1065 -1082 353 -1080 351 -1070 385 -348 1051 -354 1077 -1076 381 -314 1103 -334 1105 -354 1081 -318 1095 -348 1087 -354 1081 -320 1097 -5712 1077 -1074 319 -380 1067 -356 1087 -346 1065 -1080 349 -1092 345 -1082 351 -360 +RAW_Data: 1053 -378 1083 -1050 381 -346 1053 -376 1067 -364 1069 -370 1047 -386 1047 -356 1093 -344 1083 -5690 1079 -1060 371 -344 1067 -362 1073 -348 1095 -1076 353 -1074 353 -1054 359 -352 1101 -348 1085 -1046 361 -388 1043 -350 1093 -346 1081 -352 1081 -352 1067 -384 1055 -346 1085 -5678 1083 -1084 347 -344 1093 -348 1097 -326 1075 -1070 375 -1062 347 -1080 373 -344 1067 -362 1073 -1074 381 -314 1115 -344 1085 -356 1047 -354 1091 -348 1085 -354 1081 -320 1095 -5708 1075 -1074 353 -354 1083 -326 1109 -346 1065 -1054 387 -1050 351 -1096 381 -316 1083 -352 1073 -1080 381 -312 1101 -336 1105 -350 1063 -352 1067 -384 1065 -324 1111 -346 1065 -5704 1073 -1080 347 -350 1083 -352 1073 -350 1085 -1064 375 -1064 357 -1072 383 -314 1111 -344 1087 -1042 387 -330 1071 -374 1069 -352 1081 -350 1067 -350 1097 -348 1083 -322 1113 -5682 1075 -1076 353 -354 1083 -326 1117 -342 1085 -1054 351 -1062 383 -1070 377 -314 1103 -332 1105 -1042 381 -346 1083 -346 1065 -360 1071 -374 1069 -352 1079 -352 1067 -348 1089 -5682 1077 -1088 367 -354 1047 -354 1095 -346 1083 -1068 345 -1096 351 -1074 351 -346 1077 -372 1065 -1066 349 -386 1069 -346 1085 -356 1077 -354 1057 -364 1071 -370 1071 -352 1079 -5676 1073 -1074 381 -314 1117 -344 1063 -352 1081 -1076 357 -1080 355 -1052 359 -352 1095 -334 1097 -1064 371 -352 1083 -320 1095 -348 1085 -352 1081 -350 1069 -350 1093 -348 1085 -5676 1095 -1052 379 -356 1065 -346 1087 -356 1075 -1048 387 -1058 363 -1074 367 -356 1049 -354 1091 -1072 347 -346 1101 -332 1073 -384 1063 -350 1067 -350 1101 -326 1079 -376 1065 -5680 1107 -1074 343 -346 1067 -362 1083 -352 1085 -1078 349 -1070 345 -1066 379 -360 1065 -348 1079 -1062 375 -344 1069 -362 1075 -352 1081 -356 1085 -344 1063 -384 1047 -354 1073 -5708 1073 -1074 357 -354 1083 -356 1087 -344 1087 -1052 351 -1064 379 -1068 379 -314 1103 -334 1099 -1052 347 -386 1061 -348 1073 -374 1067 -352 1083 -350 1067 -350 1095 -348 1081 -5682 1085 -1054 383 -344 1087 -346 1067 -362 1069 -1064 373 -1062 347 -1078 373 -352 1081 -350 1065 -1078 383 -350 1051 -352 1071 -386 1051 -352 1081 -352 1069 -350 1095 -346 1087 -5678 1089 -1052 387 -330 1073 -374 1083 -354 1045 -1072 353 -1074 385 -1056 361 -350 1081 -352 1083 -1080 349 -376 1059 -346 1087 -356 1075 -354 1055 -362 1075 -370 1067 -352 1081 -5676 1075 -1076 387 -330 1071 -374 1065 -352 1079 -1072 355 -1082 353 -1054 361 -350 1095 -348 1073 -1068 373 -352 1081 -322 1097 -348 1085 -354 1079 -352 1069 -348 1093 -348 1083 -5676 1097 -1056 361 -348 1071 -354 1081 -354 1089 -1074 345 -1062 383 -1050 353 -380 1057 -364 1071 -1066 371 -354 1081 -318 +RAW_Data: 1099 -348 1085 -352 1079 -352 1071 -348 1087 -346 1095 -5678 1095 -1050 383 -328 1091 -348 1061 -386 1047 -1078 357 -1084 351 -1066 351 -366 1087 -348 1061 -1076 357 -354 1081 -354 1089 -346 1065 -352 1079 -354 1069 -386 1055 -334 1099 -5694 1085 -1044 383 -340 1087 -352 1081 -350 1065 -1080 383 -1052 351 -1082 349 -376 1059 -348 1083 -1046 395 -356 1049 -352 1091 -348 1085 -354 1083 -320 1093 -348 1087 -354 1081 -5670 1107 -1070 343 -348 1067 -384 1067 -354 1083 -1068 345 -1064 349 -1080 373 -350 1081 -352 1067 -1076 383 -348 1049 -354 1071 -386 1053 -364 1071 -368 1069 -352 1079 -350 1069 -5708 1075 -1074 351 -354 1083 -326 1111 -344 1067 -1052 385 -1050 381 -1070 347 -346 1101 -332 1073 -1072 383 -344 1085 -346 1067 -362 1069 -370 1053 -352 1085 -356 1085 -346 1067 -5682 1111 -1060 369 -344 1071 -360 1081 -354 1053 -1080 353 -1092 345 -1084 351 -360 1053 -376 1067 -1086 351 -330 1081 -376 1051 -382 1067 -352 1067 -380 1067 -326 1081 -374 1051 -5696 1109 -1058 369 -354 1079 -320 1097 -350 1085 -1054 387 -1048 353 -1060 377 -352 1085 -350 1069 -1078 383 -350 1049 -352 1071 -384 1065 -326 1081 -376 1067 -334 1101 -334 1101 -5692 1063 -1064 375 -346 1067 -362 1073 -380 1065 -1074 355 -1072 349 -1066 349 -366 1085 -350 1081 -1076 355 -350 1085 -326 1077 -376 1083 -352 1049 -354 1095 -346 1065 -380 1051 -5712 1083 -1052 389 -330 1073 -374 1053 -354 1083 -1074 351 -1074 385 -1052 343 -346 1107 -352 1065 -1072 351 -376 1065 -356 1081 -346 1065 -362 1069 -372 1067 -336 1097 -332 1099 -5678 1101 -1068 343 -350 1081 -352 1069 -384 1055 -1082 349 -1056 377 -1082 353 -330 1081 -374 1067 -1082 351 -330 1081 -376 1081 -352 1047 -354 1097 -348 1081 -352 1081 -352 1069 -5712 1077 -1038 383 -354 1083 -326 1079 -374 1055 -1074 383 -1064 347 -1066 381 -316 1099 -370 1051 -1082 353 -336 1097 -368 1053 -352 1081 -354 1089 -346 1063 -350 1081 -352 1071 -5714 1075 -1074 355 -350 1083 -358 1053 -376 1065 -1084 347 -1064 353 -1070 385 -346 1085 -352 1077 -1042 387 -346 1053 -354 1077 -386 1055 -364 1069 -370 1051 -388 1051 -354 1087 -5672 1081 -1074 351 -378 1065 -356 1083 -346 1065 -1078 347 -1092 345 -1066 359 -350 1107 -356 1045 -1068 383 -354 1083 -328 1075 -372 1053 -388 1051 -356 1087 -346 1065 -362 1071 -5702 1083 -1076 349 -376 1055 -346 1083 -356 1073 -1078 353 -1054 361 -1078 369 -352 1083 -352 1065 -1072 347 -346 1103 -332 1101 -346 1081 -354 1045 -386 1051 -362 1073 -370 1049 -5718 1075 -1048 365 -388 1047 -354 1091 -346 1085 -1040 387 -1050 351 -1096 349 -348 1083 -352 1073 -1076 379 -316 1101 -334 1097 -336 1099 -330 1071 -388 1047 -354 +RAW_Data: 1087 -348 1085 -5684 1075 -1084 367 -344 1067 -358 1073 -350 1063 -1074 353 -1108 349 -1068 353 -362 1067 -344 1097 -1050 363 -386 1047 -356 1087 -346 1085 -352 1079 -320 1097 -346 1083 -352 1081 -5676 1103 -1054 387 -330 1071 -372 1053 -354 1085 -1074 351 -1070 385 -1058 347 -362 1069 -350 1087 -1054 381 -346 1085 -346 1065 -364 1069 -370 1071 -354 1081 -320 1097 -348 1083 -5676 1079 -1090 367 -344 1067 -360 1073 -350 1095 -1074 351 -1076 349 -1068 353 -364 1081 -352 1081 -1074 321 -382 1051 -360 1075 -374 1051 -386 1049 -354 1091 -346 1085 -346 1067 -5706 1065 -1084 351 -360 1087 -346 1085 -356 1047 -1074 353 -1072 383 -1064 347 -362 1067 -364 1069 -1068 371 -352 1083 -320 1095 -350 1097 -346 1083 -322 1077 -388 1051 -350 1081 -5706 1051 -1084 387 -328 1073 -372 1049 -386 1049 -1076 353 -1072 383 -1056 363 -348 1071 -354 1085 -1076 351 -376 1057 -344 1087 -356 1073 -352 1081 -350 1053 -376 1051 -384 1049 -5710 1083 -1076 351 -348 1073 -372 1063 -334 1099 -1062 371 -1052 389 -1050 353 -364 1087 -354 1049 -1072 353 -382 1055 -362 1075 -370 1051 -352 1095 -350 1067 -348 1099 -326 1077 -5702 1077 -1076 353 -378 1053 -362 1071 -370 1051 -1084 349 -1066 353 -1096 345 -382 1049 -354 1069 -1080 379 -346 1069 -368 1041 -386 1047 -354 1093 -346 1085 -350 1083 -350 1067 -5708 1075 -1074 353 -352 1083 -326 1109 -344 1083 -1050 387 -1052 355 -1054 377 -352 1085 -350 1069 -1076 377 -314 1103 -332 1101 -336 1097 -330 1073 -354 1085 -354 1089 -346 1083 -5688 1073 -1084 331 -386 1047 -356 1087 -346 1061 -1082 383 -1050 353 -1070 385 -346 1051 -354 1105 -1046 385 -346 1085 -322 1073 -386 1049 -382 1047 -354 1069 -386 1053 -362 1071 -5692 1079 -1076 351 -378 1055 -364 1071 -370 1073 -1040 373 -1070 357 -1084 353 -334 1097 -334 1105 -1056 387 -330 1071 -372 1069 -354 1081 -318 1095 -348 1085 -352 1077 -352 1069 -5708 1075 -1076 353 -352 1087 -326 1081 -372 1067 -1084 351 -1050 353 -1098 345 -344 1101 -332 1101 -1054 365 -348 1079 -354 1087 -326 1081 -374 1049 -382 1063 -352 1067 -384 1055 -5708 1071 -1076 345 -350 1081 -352 1069 -386 1059 -1054 361 -1076 371 -1054 387 -330 1073 -372 1069 -1054 383 -344 1085 -346 1065 -364 1073 -368 1065 -334 1099 -334 1073 -382 1061 -5700 1067 -1064 383 -328 1093 -348 1083 -354 1083 -1040 383 -1046 387 -1056 361 -348 1073 -354 1085 -1074 351 -378 1055 -332 1099 -338 1089 -354 1085 -324 1115 -344 1085 -350 1063 -5704 1069 -1068 349 -360 1091 -346 1067 -362 1075 -1062 373 -1068 333 -1102 365 -354 1049 -356 1087 -1074 345 -384 1047 -352 1069 -386 1053 -364 1071 -370 1051 -352 1085 -354 1085 -5678 1081 -1072 351 -378 +RAW_Data: 1055 -362 1069 -372 1069 -1054 387 -1048 353 -1092 345 -346 1101 -332 1073 -1074 383 -312 1113 -346 1063 -364 1071 -370 1073 -354 1081 -320 1091 -348 1085 -5680 1075 -1088 367 -344 1069 -360 1075 -350 1063 -1074 353 -1076 385 -1054 365 -348 1071 -354 1083 -1074 351 -374 1067 -326 1077 -376 1085 -352 1049 -354 1091 -346 1063 -380 1051 -5714 1085 -1038 387 -328 1081 -376 1053 -382 1063 -1072 355 -1076 349 -1064 349 -368 1097 -326 1073 -1070 377 -344 1099 -330 1085 -354 1087 -326 1079 -374 1069 -350 1081 -352 1071 -5700 1067 -1076 357 -350 1095 -320 1095 -348 1065 -1078 349 -1092 347 -1066 379 -358 1069 -332 1101 -1062 371 -350 1085 -320 1095 -348 1097 -346 1049 -356 1075 -386 1057 -364 1069 -5692 1077 -1070 351 -380 1051 -350 1075 -376 1065 -1052 385 -1048 383 -1068 381 -316 1083 -352 1073 -1080 377 -314 1103 -334 1099 -336 1099 -328 1075 -350 1095 -352 1065 -350 1095 -5674 1079 -1076 379 -346 1071 -368 1067 -336 1065 -1088 369 -1050 387 -1048 353 -366 1063 -382 1045 -1078 361 -386 1047 -354 1089 -346 1063 -384 1045 -354 1071 -386 1057 -332 1101 -5698 1081 -1044 383 -340 1085 -352 1079 -352 1067 -1080 379 -1030 379 -1050 377 -344 1099 -330 1073 -1074 381 -346 1085 -346 1085 -356 1047 -354 1091 -348 1063 -362 1075 -374 1063 -5678 1103 -1042 377 -352 1081 -352 1073 -348 1095 -1056 347 -1080 373 -1054 383 -314 1113 -346 1065 -1082 355 -358 1067 -352 1079 -352 1067 -350 1093 -346 1083 -354 1075 -354 1087 -5678 1079 -1074 345 -348 1099 -332 1101 -336 1067 -1090 365 -1052 387 -1048 355 -362 1067 -364 1073 -1066 371 -350 1081 -352 1065 -348 1069 -362 1075 -372 1067 -334 1099 -332 1099 -848 153 -153886 131 -896 231 -464 231 -428 97 -166 99 -966 233 -200 101 -66 199 -102 265 -266 299 -98 131 -100 65 -232 131 -100 44699 -12320 65 -132 131 -300 231 -100 167 -266 197 -68 297 -330 131 -496 131 -400 131 -166 633 -66 431 -230 589 -166 10885 -11354 329 -792 99 -296 129 -166 97 -98 65 -132 229 -294 297 -98 561 -166 231 -98 825 -166 57693 -12116 131 -98 295 -424 163 -398 131 -232 97 -130 65 -66 97 -66 165 -460 529 -66 197 -132 393 -198 267 -168 43035 -10678 99 -1716 97 -498 65 -298 67 -2020 331 -166 957 -66 199 -100 529 -12206 199 -796 67 -66 131 -132 129 -398 197 -230 131 -66 227 -196 65 -526 197 -132 589 -132 597 -100 327 -66 8489 -11212 133 -630 131 -100 133 -166 99 -994 99 -198 333 -1626 329 -130 6045 -13890 65 -434 463 -264 295 -266 1385 -132 461 -134 163951 -12284 131 -858 233 -532 199 -402 97 -266 133 -496 431 -66 1161 -66 493 -68 21495 -5856 +RAW_Data: 95 -560 65 -2896 199 -762 97 -100 65 -430 131 -896 199 -234 101 -800 231 -332 99 -334 133 -132 265 -268 1425 -66 33043 -12400 99 -666 131 -234 67 -232 65 -498 65 -266 65 -498 165 -102 165 -200 2189 -11534 133 -266 197 -232 97 -166 165 -100 99 -98 99 -500 97 -332 99 -200 12547 -12014 65 -232 99 -600 131 -330 299 -332 625 -66 1243 -134 7929 -12344 97 -762 131 -860 165 -298 295 -298 231 -266 97 -134 397 -132 2891 -68 299 -16210 67 -266 133 -234 101 -268 163 -466 97 -232 65 -198 67 -132 67 -100 263 -98 437 -302 133 -66 165 -168 24439 -10722 131 -1534 229 -1318 99 -98 195 -2100 597 -100 733 -134 28455 -10484 231 -2030 67 -100 101 -132 163 -166 265 -854 361 -166 531 -134 233 -100 495 -164 25733 -12966 65 -1880 65 -132 163 -1086 799 -68 16865 -12956 295 -330 163 -232 233 -166 97 -1860 99 -68 333 -66 1613 -98 55627 -12998 97 -266 263 -1594 131 -894 167 -134 231 -332 131 -66 399 -98 99 -66 697 -4972 65 -3944 65 -132 459 -328 197 -100 1557 -98 23673 -12982 333 -928 133 -232 433 -198 131 -360 99 -64 497 -100 463 -98 397 -132 197 -68 765 -13710 65 -166 99 -1684 131 -632 691 -230 263 -66 359 -132 2073 -64 6059 -12430 97 -462 65 -328 65 -792 229 -824 97 -892 529 -166 163 -168 165 -698 165 -166 431 -130 20385 -12774 97 -98 65 -1384 131 -198 131 -622 131 -598 99 -164 129 -98 193 -98 195 -98 263 -98 459 -132 131 -66 73855 -13048 65 -2554 97 -166 761 -68 761 -366 15305 -12094 165 -764 963 -68 65 -66 163 -66 233 -102 333 -100 965 -66 40087 -10292 165 -1724 65 -1754 165 -266 463 -396 267 -432 795 -132 297 -66 267 -68 261 -134 36583 -10588 65 -2738 97 -1184 259 -298 131 -294 395 -98 819 -132 689 -166 7999 -11970 65 -468 63 -1892 429 -262 263 -200 199 -632 265 -66 229 -66 1149 -68 2355 -15782 233 -198 67 -166 295 -818 229 -164 1059 -68 265 -68 1789 -10374 131 -1904 65 -196 229 -230 197 -360 229 -164 129 -1120 963 -166 231 -200 199 -230 333 -10180 67 -966 133 -1264 65 -798 99 -432 65 -898 65 -134 7681 -12052 131 -928 129 -624 99 -526 65 -432 65 -532 65 -596 199 -100 265 -296 895 -100 229 -98 589 -15506 97 -266 65 -434 65 -1520 199 -98 265 -894 293 -198 427 -198 97 -132 459 -64 2145 -66 9075 -7224 99 -66 65 -1060 65 -200 99 -932 167 -894 65 -528 165 -102 927 -100 597 -232 99 -66 97 -98 461 -196 261 -98 197 -264 2909 -134 11635 -804 99 -240 143 -244 369 -5700 1077 -1074 353 -354 1083 -324 +RAW_Data: 1117 -346 1063 -1086 353 -1050 381 -1070 383 -348 1049 -354 1073 -1082 383 -350 1047 -354 1077 -386 1057 -334 1101 -336 1105 -354 1081 -320 1093 -5710 1073 -1076 351 -354 1085 -324 1111 -344 1065 -1084 351 -1082 349 -1068 385 -348 1053 -354 1075 -1078 385 -348 1051 -354 1075 -382 1065 -354 1085 -346 1065 -364 1069 -370 1069 -5686 1077 -1086 367 -354 1047 -354 1095 -346 1095 -1054 363 -1072 369 -1050 387 -328 1077 -374 1051 -1084 349 -348 1081 -376 1081 -348 1063 -352 1073 -380 1067 -356 1053 -376 1083 -5684 1073 -1086 365 -356 1049 -354 1089 -348 1085 -1066 385 -1046 351 -1074 385 -348 1083 -320 1109 -1048 381 -346 1071 -336 1097 -336 1097 -334 1085 -352 1093 -320 1097 -348 1095 -5680 1089 -1064 371 -352 1081 -352 1067 -348 1067 -1078 349 -1094 347 -1096 349 -358 1067 -350 1081 -1076 355 -352 1081 -356 1055 -378 1065 -350 1079 -352 1069 -384 1053 -348 1079 -5702 1099 -1072 353 -350 1065 -354 1087 -348 1097 -1046 347 -1096 377 -1034 377 -360 1085 -356 1049 -1074 351 -382 1051 -362 1071 -374 1051 -388 1049 -352 1093 -346 1061 -384 1049 -5708 1087 -1068 349 -362 1089 -344 1061 -382 1051 -1076 359 -1080 351 -1088 355 -358 1069 -352 1081 -1078 353 -350 1087 -326 1079 -376 1065 -350 1079 -354 1069 -384 1059 -332 1101 -5700 1087 -1044 353 -368 1095 -348 1085 -320 1111 -1044 381 -1064 347 -1090 345 -344 1101 -332 1073 -1074 383 -344 1087 -344 1067 -352 1083 -352 1071 -384 1051 -352 1083 -352 1071 -5712 1077 -1076 355 -350 1087 -326 1079 -374 1053 -1070 375 -1072 357 -1080 353 -334 1103 -334 1073 -1084 349 -378 1051 -376 1067 -364 1073 -368 1065 -336 1101 -332 1083 -352 1083 -5712 1075 -1044 383 -348 1069 -372 1051 -354 1083 -1076 351 -1074 383 -1052 385 -326 1089 -346 1065 -1066 381 -340 1099 -326 1077 -374 1067 -352 1083 -350 1069 -350 1087 -352 1081 -5676 1085 -1074 387 -330 1071 -372 1069 -352 1079 -1076 355 -1078 351 -1084 347 -360 1067 -348 1081 -1074 357 -352 1081 -356 1085 -344 1065 -384 1049 -354 1071 -384 1059 -346 1085 -5680 1087 -1084 343 -348 1103 -352 1065 -354 1087 -1070 345 -1086 351 -1082 351 -340 1097 -346 1087 -1046 361 -352 1083 -354 1089 -346 1067 -350 1081 -354 1071 -386 1053 -346 1097 -5676 1085 -1084 349 -360 1085 -344 1081 -348 1065 -1076 357 -1080 351 -1086 325 -388 1065 -366 1067 -1064 371 -352 1081 -352 1065 -382 1049 -348 1083 -352 1071 -382 1049 -384 1047 -5708 1065 -1062 377 -350 1083 -354 1085 -326 1077 -1070 377 -1052 381 -1066 351 -378 1057 -332 1103 -1060 373 -344 1069 -362 1073 -350 1095 -350 1065 -348 1095 -350 1085 -344 1065 -5704 1079 -1072 347 -382 1049 -354 1075 -382 1051 -1082 351 -1082 351 -1072 +RAW_Data: 381 -348 1053 -354 1071 -1074 381 -348 1071 -370 1049 -386 1047 -356 1091 -346 1083 -346 1067 -354 1071 -5700 1103 -1046 361 -386 1047 -354 1087 -346 1085 -1050 387 -1050 351 -1098 345 -350 1083 -352 1073 -1078 379 -316 1103 -332 1101 -336 1097 -330 1073 -380 1065 -352 1069 -384 1061 -5684 1089 -1064 369 -346 1093 -320 1109 -352 1067 -1076 349 -1070 381 -1032 379 -360 1065 -362 1071 -1064 373 -342 1099 -330 1073 -350 1093 -352 1067 -350 1087 -350 1081 -352 1071 -5710 1077 -1074 355 -354 1081 -326 1113 -346 1083 -1054 381 -1034 383 -1066 379 -316 1099 -334 1097 -1052 379 -358 1067 -350 1081 -352 1067 -386 1055 -334 1101 -334 1107 -322 1085 -5708 1077 -1050 387 -348 1087 -320 1111 -356 1055 -1086 355 -1050 375 -1054 387 -330 1075 -376 1067 -1068 385 -326 1087 -346 1063 -382 1047 -354 1105 -354 1057 -332 1097 -372 1049 -5716 1071 -1080 365 -356 1047 -354 1095 -348 1085 -1070 353 -1050 383 -1070 379 -314 1101 -334 1099 -1052 377 -356 1071 -354 1081 -320 1097 -348 1087 -346 1095 -322 1105 -352 1063 -5702 1075 -1076 351 -346 1109 -342 1087 -322 1081 -1074 351 -1076 383 -1062 347 -362 1065 -364 1071 -1064 373 -344 1099 -330 1077 -350 1097 -320 1097 -348 1095 -326 1079 -376 1063 -5712 1071 -1074 347 -346 1101 -332 1083 -350 1097 -1040 385 -1044 387 -1054 363 -348 1085 -354 1087 -1046 351 -370 1085 -346 1097 -320 1107 -350 1063 -350 1087 -346 1065 -360 1075 -5704 1077 -1076 351 -378 1055 -362 1071 -370 1049 -1084 351 -1084 355 -1054 375 -350 1083 -352 1067 -1076 379 -346 1075 -332 1101 -336 1065 -362 1073 -382 1065 -352 1069 -384 1049 -5682 1077 -1090 369 -354 1049 -354 1093 -348 1087 -1070 347 -1062 383 -1042 385 -346 1073 -372 1051 -1084 351 -346 1079 -374 1083 -354 1051 -354 1091 -348 1089 -354 1047 -354 1097 -5702 1075 -1076 353 -352 1085 -326 1077 -374 1053 -1082 385 -1050 353 -1092 345 -348 1083 -352 1069 -1076 383 -346 1051 -354 1073 -384 1051 -374 1067 -358 1073 -350 1069 -354 1087 -5706 1075 -1042 385 -352 1085 -326 1077 -378 1067 -1050 387 -1048 383 -1064 381 -350 1049 -354 1069 -1080 383 -350 1049 -354 1073 -388 1055 -334 1101 -336 1107 -354 1047 -354 1093 -5674 1109 -1072 353 -350 1083 -348 1051 -376 1065 -1082 351 -1082 351 -1072 379 -348 1073 -332 1073 -1072 381 -346 1085 -344 1065 -362 1073 -372 1047 -386 1047 -354 1089 -346 1083 -5686 1075 -1092 367 -354 1049 -354 1093 -348 1065 -1066 383 -1070 347 -1062 379 -358 1063 -348 1079 -1066 375 -344 1067 -362 1073 -350 1097 -350 1071 -348 1095 -346 1083 -320 1107 -5668 1107 -1078 329 -388 1047 -354 1093 -344 1065 -1082 343 -1098 353 -1080 349 -346 1077 -370 1067 -1052 347 -390 +RAW_Data: 1067 -364 1069 -366 1051 -386 1051 -356 1053 -378 1085 -346 1067 -5712 1053 -1074 381 -346 1083 -344 1063 -350 1077 -1068 377 -1066 383 -1050 351 -378 1057 -364 1073 -1066 371 -346 1063 -354 1071 -386 1053 -346 1103 -332 1071 -382 1063 -352 1065 -5716 1077 -1074 353 -352 1083 -326 1077 -376 1051 -1084 381 -1066 351 -1066 379 -348 1069 -368 1067 -1066 349 -362 1067 -362 1071 -370 1067 -334 1099 -366 1065 -336 1067 -364 1073 -5714 1075 -1050 361 -388 1045 -350 1091 -348 1085 -1054 381 -1066 349 -1070 381 -346 1071 -368 1065 -1052 379 -358 1067 -334 1097 -334 1085 -352 1085 -354 1087 -346 1083 -354 1051 -5706 1087 -1078 349 -348 1075 -372 1051 -382 1067 -1072 353 -1076 353 -1056 361 -350 1083 -352 1083 -1076 351 -376 1055 -364 1071 -370 1049 -352 1083 -356 1087 -346 1083 -352 1049 -5702 1087 -1052 389 -346 1087 -322 1111 -354 1057 -1082 355 -1054 375 -1064 347 -354 1105 -354 1081 -1040 383 -352 1085 -326 1075 -374 1051 -354 1083 -354 1089 -346 1085 -348 1065 -5710 1087 -1062 345 -352 1071 -382 1061 -352 1069 -1078 383 -1052 351 -1082 349 -380 1055 -334 1099 -1062 371 -354 1083 -318 1097 -348 1083 -352 1081 -352 1065 -350 1087 -352 1077 -5710 1069 -1054 389 -328 1075 -376 1049 -356 1079 -1076 351 -1074 385 -1058 365 -344 1073 -352 1097 -1036 383 -350 1063 -354 1087 -348 1097 -328 1071 -376 1063 -332 1101 -336 1101 -5676 1103 -1038 379 -344 1099 -332 1077 -354 1085 -1076 349 -1070 385 -1052 353 -360 1087 -344 1085 -1040 387 -328 1081 -374 1051 -386 1047 -354 1089 -346 1065 -362 1077 -372 1065 -5710 1075 -1042 377 -350 1083 -352 1069 -350 1095 -1050 373 -1064 357 -1074 353 -364 1073 -372 1053 -1074 351 -346 1081 -376 1069 -332 1099 -334 1087 -352 1085 -354 1085 -346 1085 -5682 1075 -1088 329 -388 1049 -354 1089 -346 1065 -1070 353 -1096 345 -1084 351 -362 1085 -344 1067 -1082 355 -358 1083 -354 1047 -354 1093 -346 1065 -360 1073 -372 1081 -354 1045 -5708 1073 -1086 353 -346 1075 -374 1055 -352 1095 -1074 353 -1074 351 -1062 379 -340 1095 -326 1079 -1070 373 -344 1099 -330 1083 -356 1083 -324 1113 -344 1067 -352 1081 -352 1073 -5706 1073 -1076 355 -354 1085 -326 1081 -374 1067 -1054 389 -1048 353 -1096 345 -350 1081 -352 1071 -1080 383 -348 1047 -354 1073 -386 1057 -334 1099 -334 1077 -384 1063 -350 1067 -5704 1071 -1076 357 -354 1081 -356 1083 -346 1067 -1086 353 -1048 383 -1070 383 -314 1081 -354 1071 -1078 385 -348 1047 -354 1107 -354 1057 -334 1099 -334 1085 -354 1083 -354 1085 -5670 1111 -1038 383 -354 1085 -328 1071 -376 1053 -1088 353 -1086 355 -1048 375 -352 1081 -350 1069 -1078 381 -350 1049 -352 1071 -386 1055 -334 1097 -336 +RAW_Data: 1087 -356 1081 -356 1085 -5672 1077 -1074 353 -382 1053 -364 1073 -372 1073 -1054 389 -1050 355 -1054 377 -344 1099 -330 1073 -1076 387 -330 1069 -372 1067 -344 1099 -330 1073 -354 1081 -354 1087 -348 1063 -5718 1081 -1060 369 -342 1067 -360 1073 -354 1083 -1076 349 -1068 385 -1052 353 -360 1085 -346 1087 -1052 389 -330 1069 -372 1051 -356 1083 -354 1087 -346 1089 -348 1065 -352 1069 -5700 1101 -1046 359 -354 1083 -354 1087 -348 1063 -1082 353 -1054 377 -1062 379 -356 1067 -334 1101 -1060 369 -344 1067 -360 1083 -356 1087 -328 1075 -374 1067 -352 1081 -350 1071 -5706 1075 -1076 351 -354 1085 -324 1111 -344 1085 -1040 389 -1048 353 -1094 345 -350 1081 -352 1071 -1076 379 -316 1097 -336 1097 -336 1097 -334 1095 -346 1073 -372 1051 -354 1079 -5692 1085 -1084 351 -346 1075 -374 1051 -354 1083 -1074 351 -1072 385 -1058 381 -328 1073 -348 1085 -1054 389 -328 1075 -376 1067 -350 1081 -352 1069 -350 1085 -352 1083 -350 1071 -5710 1077 -1042 383 -352 1089 -326 1073 -376 1065 -1064 375 -1072 365 -1038 373 -348 1071 -386 1059 -1050 379 -358 1087 -354 1047 -354 1089 -348 1085 -354 1081 -320 1095 -348 1087 -5686 1079 -1084 365 -356 1049 -354 1093 -346 1085 -1052 387 -1048 355 -1090 347 -350 1083 -352 1071 -1076 379 -314 1099 -334 1097 -336 1097 -332 1099 -336 1095 -332 1075 -354 1083 -5708 1051 -1080 383 -348 1049 -354 1105 -354 1055 -1084 355 -1052 375 -1084 351 -330 1079 -376 1051 -1074 387 -328 1075 -376 1067 -350 1079 -352 1069 -380 1067 -326 1081 -374 1051 -5714 1075 -1050 395 -356 1047 -354 1091 -348 1095 -1052 375 -1064 353 -1076 351 -346 1077 -372 1065 -1050 379 -360 1065 -362 1069 -370 1065 -334 1099 -332 1097 -336 1097 -332 1083 -5682 1101 -1046 395 -356 1047 -354 1089 -346 1097 -1050 385 -1046 351 -1074 385 -348 1051 -354 1105 -1042 385 -350 1051 -354 1073 -384 1067 -326 1113 -344 1063 -364 1069 -370 1065 -5690 1077 -1074 383 -348 1047 -354 1075 -386 1057 -1054 361 -1076 373 -1052 387 -330 1073 -374 1069 -1054 387 -330 1075 -374 1067 -352 1081 -352 1067 -350 1085 -352 1079 -352 1069 -5712 1075 -1076 351 -354 1085 -326 1109 -344 1083 -1054 387 -1052 355 -1054 375 -352 1085 -318 1097 -1076 379 -314 1099 -334 1103 -356 1079 -320 1091 -348 1085 -352 1081 -350 1067 -5708 1075 -1076 355 -350 1085 -326 1079 -374 1053 -1084 351 -1084 353 -1090 343 -350 1079 -354 1069 -1080 379 -346 1073 -332 1073 -382 1065 -352 1067 -380 1067 -326 1081 -374 1069 -5680 1103 -1070 345 -346 1099 -330 1073 -388 1047 -1070 351 -1078 385 -1058 363 -346 1081 -354 1087 -1046 351 -368 1095 -348 1051 -354 1077 -384 1065 -326 1113 -344 1085 -350 1061 -5708 1053 -1084 +RAW_Data: 351 -364 1073 -372 1069 -352 1083 -1040 353 -1110 353 -1056 361 -348 1081 -354 1083 -1078 351 -376 1059 -346 1055 -354 1079 -386 1057 -364 1071 -368 1037 -386 1047 -5706 1073 -1084 355 -334 1101 -336 1083 -354 1083 -1074 351 -1072 383 -1050 351 -360 1087 -346 1087 -1038 383 -344 1087 -346 1063 -362 1077 -370 1051 -354 1085 -356 1087 -344 1065 -5704 1081 -1074 345 -384 1047 -354 1069 -384 1057 -1086 325 -1078 373 -1052 385 -330 1079 -374 1049 -1072 381 -346 1085 -344 1067 -362 1073 -370 1067 -334 1101 -332 1071 -386 1049 -5712 1077 -1044 383 -346 1075 -370 1065 -348 1085 -1048 361 -1072 389 -1048 353 -364 1085 -354 1079 -1042 385 -354 1087 -328 1073 -374 1071 -352 1081 -320 1099 -348 1085 -352 1083 -5668 1087 -1084 353 -364 1069 -368 1053 -380 1067 -1072 353 -1078 349 -1068 355 -362 1067 -348 1083 -1074 357 -350 1085 -356 1081 -344 1083 -354 1047 -354 1093 -348 1083 -352 1079 -5704 1073 -1052 387 -330 1077 -372 1051 -352 1085 -1076 349 -1072 379 -1070 323 -388 1063 -348 1077 -1064 375 -352 1079 -352 1065 -384 1049 -352 1083 -352 1069 -350 1085 -352 1083 -5676 1085 -1080 351 -366 1071 -366 1053 -352 1085 -1076 351 -1070 379 -1064 349 -360 1065 -364 1073 -1064 369 -346 1071 -358 1073 -350 1065 -354 1091 -380 1051 -348 1083 -352 1069 -5712 1077 -1074 353 -352 1085 -326 1075 -374 1065 -1066 361 -1074 371 -1056 387 -330 1071 -372 1051 -1086 353 -336 1095 -370 1069 -354 1079 -320 1095 -348 1097 -350 1051 -354 1077 -5708 1075 -1078 355 -352 1085 -326 1081 -374 1051 -1082 349 -1100 321 -1098 345 -348 1101 -332 1099 -1072 325 -388 1065 -334 1099 -368 1051 -352 1083 -356 1053 -378 1083 -352 1047 -5710 1073 -1072 383 -314 1113 -344 1067 -364 1067 -1068 371 -1054 389 -1050 355 -362 1087 -354 1049 -1074 351 -382 1053 -362 1077 -370 1051 -352 1095 -352 1065 -348 1097 -324 1079 -5690 1101 -1074 355 -350 1081 -350 1085 -346 1081 -1050 387 -1048 355 -1094 345 -350 1083 -352 1069 -1074 379 -346 1073 -366 1067 -346 1085 -354 1073 -352 1055 -360 1075 -374 1051 -5718 1071 -1048 395 -356 1047 -354 1091 -346 1085 -1052 383 -1066 349 -1070 381 -314 1101 -334 1085 -1074 351 -346 1081 -376 1067 -332 1099 -334 1099 -336 1101 -330 1085 -350 1065 -5702 1075 -1072 381 -348 1051 -374 1069 -366 1067 -1064 371 -1070 351 -1080 351 -376 1047 -382 1047 -1078 357 -352 1083 -358 1053 -374 1051 -386 1049 -354 1093 -344 1065 -350 1079 -5720 1067 -1076 355 -352 1085 -326 1077 -376 1053 -1086 353 -1084 355 -1056 375 -344 1093 -322 1105 -1074 351 -348 1073 -372 1065 -334 1099 -332 1101 -346 1083 -352 1079 -354 1057 -5706 1077 -1074 347 -348 1081 -354 1071 -384 1057 -1052 +RAW_Data: 361 -1076 373 -1050 387 -330 1075 -374 1049 -1086 387 -330 1073 -370 1053 -352 1083 -354 1085 -346 1065 -362 1071 -370 1065 -5682 1105 -1036 377 -346 1099 -330 1085 -354 1085 -1046 353 -1096 347 -1086 351 -360 1085 -344 1085 -1052 381 -314 1113 -344 1083 -354 1047 -354 1093 -348 1067 -362 1073 -374 1049 -5716 1071 -1052 363 -388 1047 -354 1091 -346 1065 -1080 347 -1090 345 -1064 379 -358 1069 -334 1097 -1062 369 -352 1083 -320 1099 -348 1065 -362 1075 -374 1067 -334 1099 -332 1073 -5720 1073 -1048 395 -356 1049 -356 1089 -346 1065 -1084 343 -1072 361 -1084 355 -334 1101 -336 1099 -1058 363 -344 1085 -354 1089 -326 1075 -376 1051 -356 1081 -356 1085 -346 1065 -5688 1111 -1058 367 -348 1065 -352 1069 -384 1067 -1042 379 -1070 383 -1052 351 -330 1115 -346 1087 -1056 351 -346 1077 -376 1083 -352 1061 -352 1067 -350 1097 -326 1109 -346 1083 -5686 1075 -1048 397 -356 1047 -354 1091 -346 1085 -1054 389 -1048 353 -1058 377 -352 1085 -352 1069 -1074 381 -350 1051 -352 1069 -386 1055 -366 1069 -368 1051 -354 1083 -356 1083 -5672 1081 -1074 351 -382 1055 -364 1069 -372 1053 -1086 355 -1054 359 -1076 373 -352 1079 -350 1065 -1078 383 -316 1083 -354 1073 -352 1085 -352 1083 -350 1071 -348 1099 -328 1075 -5694 1099 -1072 351 -354 1083 -326 1111 -342 1085 -1054 353 -1062 381 -1064 379 -314 1101 -334 1099 -1058 361 -348 1081 -354 1081 -348 1087 -346 1085 -356 1049 -354 1091 -348 1065 -5710 1073 -1078 347 -346 1101 -332 1073 -350 1093 -1074 355 -1050 385 -1062 347 -366 1099 -326 1077 -1066 373 -352 1083 -320 1097 -350 1097 -326 1075 -376 1065 -332 1097 -370 1065 -5684 1101 -1036 377 -344 1101 -330 1073 -356 1079 -1074 349 -1074 385 -1056 365 -344 1081 -354 1085 -1042 379 -340 1095 -348 1085 -320 1109 -356 1055 -332 1101 -334 1107 -354 1049 -5704 1087 -1048 389 -350 1081 -320 1111 -356 1055 -1052 389 -1048 377 -1056 391 -328 1073 -374 1057 -1084 353 -344 1077 -372 1053 -356 1083 -356 1085 -346 1063 -352 1081 -352 1073 -5712 1073 -1074 353 -356 1079 -318 1117 -346 1065 -1056 389 -1048 381 -1066 383 -314 1081 -352 1073 -1080 385 -316 1085 -352 1071 -352 1097 -326 1109 -344 1071 -354 1081 -320 1099 -5696 1069 -1076 355 -356 1083 -324 1113 -346 1063 -1078 347 -1090 379 -1032 377 -356 1069 -354 1081 -1040 387 -354 1087 -326 1075 -376 1069 -342 1097 -330 1077 -356 1083 -324 1115 -5678 1081 -1076 349 -346 1089 -334 1095 -336 1107 -1058 355 -1054 389 -1046 377 -354 1081 -320 1097 -1078 379 -314 1097 -332 1099 -338 1093 -332 1073 -354 1091 -350 1069 -350 1095 -5674 1111 -1044 381 -314 1099 -336 1097 -336 1095 -1064 369 -1038 389 -1050 381 -338 1093 -350 +RAW_Data: 1081 -1044 395 -322 1081 -354 1087 -348 1085 -356 1083 -320 1093 -348 1089 -356 1049 -5706 1077 -1052 387 -350 1087 -320 1113 -318 1097 -1042 379 -1066 381 -1030 379 -362 1063 -364 1069 -1068 373 -356 1081 -320 1095 -350 1087 -352 1063 -352 1071 -350 1099 -326 1111 -5670 1081 -1076 349 -380 1057 -334 1097 -334 1089 -1054 389 -1056 361 -1072 373 -354 1083 -320 1091 -1074 381 -318 1079 -352 1071 -352 1091 -348 1083 -322 1113 -358 1053 -332 1133 -810 65 -262 181 -142572 65 -98 199 -100 133 -300 99 -166 99 -232 99 -134 333 -166 231 -330 99 -230 493 -100 361 -66 525 -12248 131 -268 133 -268 131 -400 229 -726 97 -494 265 -402 65 -664 363 -134 365 -166 131 -262 395 -98 795 -98 68827 -6766 99 -1626 99 -66 65 -700 97 -864 565 -464 99 -98 133 -230 197 -166 591 -98 5135 -12244 97 -2452 65 -734 165 -198 233 -266 631 -198 12423 -12322 165 -796 67 -100 99 -98 67 -862 133 -728 263 -66 361 -166 129 -100 395 -166 625 -232 131 -66 8113 -12068 229 -132 65 -1394 99 -134 65 -300 165 -134 65 -102 99 -166 133 -766 297 -98 199 -102 1067 -166 859 -98 25307 -13210 65 -1764 97 -264 163 -496 133 -132 65 -66 427 -98 721 -68 531 -134 1591 -100 54955 -11122 65 -726 65 -368 65 -492 99 -428 233 -164 99 -266 131 -498 197 -430 295 -66 2793 -130 11307 -12324 97 -132 193 -1216 65 -1286 131 -594 399 -100 431 -266 495 -100 165 -132 63531 -12252 99 -166 165 -596 99 -496 99 -298 97 -368 65 -134 131 -198 165 -434 99 -266 301 -364 37153 -11928 65 -332 195 -528 131 -230 97 -1126 131 -530 99 -790 361 -100 895 -98 597 -66 699 -100 15803 -10136 99 -2044 65 -400 165 -534 165 -134 99 -434 65 -966 97 -100 133 -134 1125 -66 589 -100 26555 -12218 165 -334 65 -100 99 -2586 65 -732 531 -100 461 -132 3347 -10206 97 -2084 63 -296 99 -164 65 -1052 97 -66 231 -330 65 -232 331 -596 1119 -424 361 -66 28413 -12166 67 -264 161 -228 97 -390 65 -856 131 -66 763 -132 201 -132 9717 -13508 129 -990 65 -100 97 -296 165 -724 65 -368 231 -98 401 -66 527 -66 767 -100 637 -66 109351 -13036 165 -796 65 -200 133 -100 199 -600 133 -134 99 -932 461 -132 399 -264 47323 -12184 97 -566 231 -400 67 -1456 229 -132 97 -394 163 -230 1579 -66 231 -164 10685 -12812 99 -132 67 -464 133 -1162 331 -132 99 -266 167 -200 99 -268 791 -164 823 -132 329 -134 63603 -10646 65 -662 165 -234 163 -630 463 -98 233 -100 195 -262 131 -132 491 -12204 65 -1156 131 -954 295 -132 99 -332 65 -664 789 -328 +RAW_Data: 1351 -8346 67 -4708 133 -400 99 -298 65 -556 65 -396 295 -66 263 -64 163 -100 131 -100 97 -68 131 -166 65 -166 497 -132 999 -132 5609 -4806 131 -4842 131 -298 133 -332 67 -596 131 -1186 263 -268 495 -100 461 -462 165 -66 525 -100 1579 -100 4557 -10356 165 -1614 165 -98 97 -630 65 -164 461 -164 65 -558 231 -130 131 -164 591 -66 557 -9122 65 -166 101 -6060 295 -68 65 -594 163 -368 263 -98 65 -98 99 -294 99 -66 363 -432 329 -66 1987 -100 25465 -9810 197 -100 131 -262 97 -530 99 -168 65 -166 431 -530 99 -232 167 -266 97 -234 1065 -66 131 -198 17549 -9930 899 -100 367 -994 67 -132 167 -794 231 -98 99 -464 299 -134 131 -100 199 -732 97 -296 131 -66 1157 -132 359 -130 47799 -12132 99 -630 197 -132 165 -502 195 -164 261 -98 261 -98 263 -66 195 -296 195 -198 65 -66 393 -66 955 -100 12501 -12452 97 -2942 165 -262 97 -228 65 -98 359 -130 625 -100 723 -66 365 -13612 99 -832 233 -66 99 -298 65 -464 233 -164 65 -164 1053 -362 10409 -11212 197 -794 131 -970 67 -132 99 -366 167 -366 165 -134 167 -664 99 -302 1253 -200 131 -15330 97 -132 259 -100 63 -558 165 -398 65 -860 231 -66 163 -98 263 -364 559 -100 1313 -66 89369 -10410 65 -1788 233 -132 133 -1062 165 -532 65 -632 63 -794 65 -166 97 -132 133 -66 431 -100 467 -132 199 -364 15615 -12276 99 -662 165 -66 133 -564 197 -1460 65 -66 65 -364 231 -396 165 -134 5835 -12662 165 -436 163 -468 65 -100 299 -728 99 -534 265 -166 263 -100 627 -100 197 -66 43301 -12090 163 -96 197 -692 97 -862 129 -368 165 -232 65 -998 197 -66 393 -196 683 -98 6471 -12036 165 -662 133 -302 99 -566 727 -66 2289 -66 7119 -12372 99 -662 97 -300 99 -66 131 -962 165 -362 131 -168 65 -428 133 -134 1797 -66 63965 -10524 263 -1458 265 -298 99 -166 199 -130 789 -68 23871 -12668 65 -990 99 -196 331 -990 755 -100 233 -132 267 -13192 65 -932 233 -134 199 -164 133 -568 99 -898 331 -134 99 -66 961 -66 7013 -10302 65 -664 131 -432 99 -532 199 -466 329 -364 65 -134 429 -198 759 -132 617 -198 38455 -12426 199 -134 99 -1252 99 -396 229 -660 99 -362 197 -164 197 -132 689 -66 161 -98 357 -330 265 -100 15001 -11544 133 -636 131 -132 99 -232 65 -1488 165 -636 131 -266 133 -134 367 -66 197 -100 267 -6574 131 -260 165 -166 65 -132 65 -828 295 -66 589 -100 diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c new file mode 100644 index 000000000..9459fa2e7 --- /dev/null +++ b/lib/subghz/protocols/legrand.c @@ -0,0 +1,378 @@ +#include "legrand.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolLegrand" + +static const SubGhzBlockConst subghz_protocol_legrand_const = { + .te_short = 375, + .te_long = 1125, + .te_delta = 150, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderLegrand { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint32_t te; + uint32_t last_data; +}; + +struct SubGhzProtocolEncoderLegrand { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t te; +}; + +typedef enum { + LegrandDecoderStepReset = 0, + LegrandDecoderStepFirstBit, + LegrandDecoderStepSaveDuration, + LegrandDecoderStepCheckDuration, +} LegrandDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_legrand_decoder = { + .alloc = subghz_protocol_decoder_legrand_alloc, + .free = subghz_protocol_decoder_legrand_free, + + .feed = subghz_protocol_decoder_legrand_feed, + .reset = subghz_protocol_decoder_legrand_reset, + + .get_hash_data = subghz_protocol_decoder_legrand_get_hash_data, + .serialize = subghz_protocol_decoder_legrand_serialize, + .deserialize = subghz_protocol_decoder_legrand_deserialize, + .get_string = subghz_protocol_decoder_legrand_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_legrand_encoder = { + .alloc = subghz_protocol_encoder_legrand_alloc, + .free = subghz_protocol_encoder_legrand_free, + + .deserialize = subghz_protocol_encoder_legrand_deserialize, + .stop = subghz_protocol_encoder_legrand_stop, + .yield = subghz_protocol_encoder_legrand_yield, +}; + +const SubGhzProtocol subghz_protocol_legrand = { + .name = SUBGHZ_PROTOCOL_LEGRAND_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_legrand_decoder, + .encoder = &subghz_protocol_legrand_encoder, +}; + +void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderLegrand* instance = malloc(sizeof(SubGhzProtocolEncoderLegrand)); + + instance->base.protocol = &subghz_protocol_legrand; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = subghz_protocol_legrand_const.min_count_bit_for_found * 2 + 1; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_legrand_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderLegrand* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderLegrand instance + * @return true On success + */ +static bool subghz_protocol_encoder_legrand_get_upload(SubGhzProtocolEncoderLegrand* instance) { + furi_assert(instance); + + size_t size_upload = (instance->generic.data_count_bit * 2) + 1; + if(size_upload != instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Invalid data bit count"); + return false; + } + + size_t index = 0; + + // Send sync + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 16); + + // Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // send bit 1 + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + } else { + // send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); + } + } + + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderLegrand* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_legrand_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + ret = SubGhzProtocolStatusErrorParserTe; + break; + } + // optional parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!subghz_protocol_encoder_legrand_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_legrand_stop(void* context) { + SubGhzProtocolEncoderLegrand* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_legrand_yield(void* context) { + SubGhzProtocolEncoderLegrand* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderLegrand* instance = malloc(sizeof(SubGhzProtocolDecoderLegrand)); + instance->base.protocol = &subghz_protocol_legrand; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_legrand_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + free(instance); +} + +void subghz_protocol_decoder_legrand_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + instance->decoder.parser_step = LegrandDecoderStepReset; + instance->last_data = 0; +} + +void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + + switch(instance->decoder.parser_step) { + case LegrandDecoderStepReset: + if(!level && DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short * 16) < + subghz_protocol_legrand_const.te_delta * 8) { + instance->decoder.parser_step = LegrandDecoderStepFirstBit; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->te = 0; + } + break; + case LegrandDecoderStepFirstBit: + if(level) { + if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) < + subghz_protocol_legrand_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->te += duration * 4; // long low that is part of sync, then short high + } + + if(DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) < + subghz_protocol_legrand_const.te_delta * 3) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->te += duration / 3 * 4; // short low that is part of sync, then long high + } + + if(instance->decoder.decode_count_bit > 0) { + // advance to the next step if either short or long is found + instance->decoder.parser_step = LegrandDecoderStepSaveDuration; + break; + } + } + + instance->decoder.parser_step = LegrandDecoderStepReset; + break; + case LegrandDecoderStepSaveDuration: + if(!level) { + instance->decoder.te_last = duration; + instance->te += duration; + instance->decoder.parser_step = LegrandDecoderStepCheckDuration; + break; + } + + instance->decoder.parser_step = LegrandDecoderStepReset; + break; + case LegrandDecoderStepCheckDuration: + if(level) { + uint8_t found = 0; + + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_long) < + subghz_protocol_legrand_const.te_delta * 3 && + DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short) < + subghz_protocol_legrand_const.te_delta) { + found = 1; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + + if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_legrand_const.te_short) < + subghz_protocol_legrand_const.te_delta && + DURATION_DIFF(duration, subghz_protocol_legrand_const.te_long) < + subghz_protocol_legrand_const.te_delta * 3) { + found = 1; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + + if(found) { + instance->te += duration; + + if(instance->decoder.decode_count_bit < + subghz_protocol_legrand_const.min_count_bit_for_found) { + instance->decoder.parser_step = LegrandDecoderStepSaveDuration; + break; + } + + // enough bits for a packet found, save it only if there was a previous packet + // with the same data + if(instance->last_data && (instance->last_data == instance->decoder.decode_data)) { + instance->te /= instance->decoder.decode_count_bit * 4; + + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->last_data = instance->decoder.decode_data; + // fallthrough to reset, the next bit is expected to be a sync + // it also takes care of resetting the decoder state + } + } + + instance->decoder.parser_step = LegrandDecoderStepReset; + break; + } +} + +uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_write_uint32(flipper_format, "TE", &instance->te, 1)) { + FURI_LOG_E(TAG, "Unable to add TE"); + ret = SubGhzProtocolStatusErrorParserTe; + } + return ret; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_legrand_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(!flipper_format_read_uint32(flipper_format, "TE", (uint32_t*)&instance->te, 1)) { + FURI_LOG_E(TAG, "Missing TE"); + ret = SubGhzProtocolStatusErrorParserTe; + break; + } + } while(false); + + return ret; +} + +void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderLegrand* instance = context; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:0x%05lX\r\n" + "Te:%luus\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + instance->te); +} diff --git a/lib/subghz/protocols/legrand.h b/lib/subghz/protocols/legrand.h new file mode 100644 index 000000000..0948ddb8b --- /dev/null +++ b/lib/subghz/protocols/legrand.h @@ -0,0 +1,117 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_LEGRAND_NAME "Legrand" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct SubGhzProtocolDecoderLegrand SubGhzProtocolDecoderLegrand; +typedef struct SubGhzProtocolEncoderLegrand SubGhzProtocolEncoderLegrand; + +extern const SubGhzProtocolDecoder subghz_protocol_legrand_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_legrand_encoder; +extern const SubGhzProtocol subghz_protocol_legrand; + +/** + * Allocate SubGhzProtocolEncoderLegrand. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderLegrand* pointer to a SubGhzProtocolEncoderLegrand instance + */ +void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderLegrand. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + */ +void subghz_protocol_encoder_legrand_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_legrand_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + */ +void subghz_protocol_encoder_legrand_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderLegrand instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_legrand_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderLegrand. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderLegrand* pointer to a SubGhzProtocolDecoderLegrand instance + */ +void* subghz_protocol_decoder_legrand_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + */ +void subghz_protocol_decoder_legrand_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + */ +void subghz_protocol_decoder_legrand_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_legrand_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_legrand_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderLegrand. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_legrand_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderLegrand instance + * @param output Resulting text + */ +void subghz_protocol_decoder_legrand_get_string(void* context, FuriString* output); + +#ifdef __cplusplus +} +#endif diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 026f3f54a..da1a1e87a 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -45,6 +45,7 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = { &subghz_protocol_bin_raw, &subghz_protocol_mastercode, &subghz_protocol_honeywell, + &subghz_protocol_legrand, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 53c479637..c0f3be0d6 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -46,3 +46,4 @@ #include "bin_raw.h" #include "mastercode.h" #include "honeywell.h" +#include "legrand.h" From 8dad9468d5018c81d72ea61bf31e2a2433936a08 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jun 2024 01:34:35 +0300 Subject: [PATCH 27/28] fix deprecated --- applications/services/gui/canvas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 45a52d18b..b67b2faba 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -545,7 +545,7 @@ void canvas_draw_icon_bitmap( x += canvas->offset_x; y += canvas->offset_y; uint8_t* icon_data = NULL; - compress_icon_decode(canvas->compress_icon, icon_get_data(icon), &icon_data); + compress_icon_decode(canvas->compress_icon, icon_get_frame_data(icon, 0), &icon_data); u8g2_DrawXBM(&canvas->fb, x, y, w, h, icon_data); } From 62ab6b70262c741bbe7cbe6cf6dc077897a6455f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 6 Jun 2024 02:10:23 +0300 Subject: [PATCH 28/28] cleanup --- .../unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub | 2 -- 1 file changed, 2 deletions(-) diff --git a/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub index 87ac584f9..7041324cb 100644 --- a/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub +++ b/applications/debug/unit_tests/resources/unit_tests/subghz/legrand_2E37F.sub @@ -2,8 +2,6 @@ Filetype: Flipper SubGhz Key File Version: 1 Frequency: 433920000 Preset: FuriHalSubGhzPresetOok650Async -Latitute: 0.000000 -Longitude: 0.000000 Protocol: Legrand Bit: 18 Key: 00 00 00 00 00 02 E3 7F