From 1070064f8954b17047ff64c5f892f787d5b2e731 Mon Sep 17 00:00:00 2001 From: Rodrigo Basoalto Date: Mon, 4 Mar 2024 06:50:28 -0300 Subject: [PATCH 1/4] Parser for Santiago, Chile BIP transit card (#3456) * Parser for Santiago, Chile BIP transit card It's used for all public transportation in the city of Santiago, Chile (bus, subway). We've reverse-engineered most of the non-zero bits of data found, and checked them across dozens of cards. * Update to use bit_lib instead of nfc_util * PR feedback Co-authored-by: Rodrigo Basoalto Co-authored-by: gornekich --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/bip.c | 368 ++++++++++++++++++ 2 files changed, 377 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/bip.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 4bcc798237..b6a0f09d37 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -128,6 +128,15 @@ App( sources=["plugins/supported_cards/aime.c"], ) +App( + appid="bip_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="bip_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/bip.c"], +) + App( appid="umarsh_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/bip.c b/applications/main/nfc/plugins/supported_cards/bip.c new file mode 100644 index 0000000000..211c27cb56 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/bip.c @@ -0,0 +1,368 @@ +#include "nfc_supported_card_plugin.h" + +#include +#include +#include +#include + +#define TAG "Bip" + +#define SECTOR_BLOCK_OFFSET(sector, block) (((sector) * 4) + (block)) + +static const uint64_t bip_keys_a[] = { + 0x3a42f33af429, + 0x6338a371c0ed, + 0xf124c2578ad0, + 0x32ac3b90ac13, + 0x4ad1e273eaf1, + 0xe2c42591368a, + 0x2a3c347a1200, + 0x16f3d5ab1139, + 0x937a4fff3011, + 0x35c3d2caee88, + 0x693143f10368, + 0xa3f97428dd01, + 0x63f17a449af0, + 0xc4652c54261c, + 0xd49e2826664f, + 0x3df14c8000a1, +}; + +static const uint64_t bip_keys_b[] = { + 0x1fc235ac1309, + 0x243f160918d1, + 0x9afc42372af1, + 0x682d401abb09, + 0x067db45454a9, + 0x15fc4c7613fe, + 0x68d30288910a, + 0xf59a36a2546d, + 0x64e3c10394c2, + 0xb736412614af, + 0x324f5df65310, + 0x643fb6de2217, + 0x82f435dedf01, + 0x0263de1278f3, + 0x51284c3686a6, + 0x6a470d54127c, +}; + +bool bip_verify(Nfc* nfc) { + bool verified = true; + + const uint8_t verify_sector = 0; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key_a_0 = {}; + bit_lib_num_to_bytes_be(bip_keys_a[0], COUNT_OF(key_a_0.data), key_a_0.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx); + + if(error == MfClassicErrorNotPresent) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + verified = false; + } + + return verified; +} + +static bool bip_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Card not MIFARE Classic 1k"); + break; + } + + data->type = type; + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be(bip_keys_a[i], sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be(bip_keys_b[i], sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data. Bad keys?"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +typedef struct { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +} BipTimestamp; + +static void parse_bip_timestamp(const MfClassicBlock* block, BipTimestamp* timestamp) { + furi_assert(block); + furi_assert(timestamp); + + timestamp->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f; + timestamp->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf; + timestamp->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f); + timestamp->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f; + timestamp->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f; + timestamp->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f; +} + +static int compare_bip_timestamp(const BipTimestamp* t1, const BipTimestamp* t2) { + furi_assert(t1); + furi_assert(t2); + if(t1->year != t2->year) { + return t1->year - t2->year; + } + if(t1->month != t2->month) { + return t1->month - t2->month; + } + if(t1->day != t2->day) { + return t1->day - t2->day; + } + if(t1->hour != t2->hour) { + return t1->hour - t2->hour; + } + if(t1->minute != t2->minute) { + return t1->minute - t2->minute; + } + if(t1->second != t2->second) { + return t1->second - t2->second; + } + return 0; +} + +static void print_bip_timestamp(const BipTimestamp* timestamp, FuriString* str) { + furi_assert(timestamp); + furi_assert(str); + furi_string_cat_printf( + str, + "%04u-%02u-%02u %02u:%02u:%02u", + timestamp->year, + timestamp->month, + timestamp->day, + timestamp->hour, + timestamp->minute, + timestamp->second); +} + +static bool is_bip_block_empty(const MfClassicBlock* block) { + furi_assert(block); + // check if all but last byte are zero (last is checksum) + for(size_t i = 0; i < sizeof(block->data) - 1; i++) { + if(block->data[i] != 0) { + return false; + } + } + return true; +} + +static void parse_uint16_le(const uint8_t* data, uint16_t* value) { + furi_assert(data); + furi_assert(value); + + *value = (data[0]) | (data[1] << 8); +} + +static void parse_uint32_le(const uint8_t* data, uint32_t* value) { + furi_assert(data); + furi_assert(value); + + *value = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +static void parse_uint16_txn_amount(const uint8_t* data, uint16_t* value) { + furi_assert(data); + furi_assert(value); + + parse_uint16_le(data, value); + *value = *value >> 2; +} + +typedef struct { + BipTimestamp timestamp; + uint16_t amount; +} BipTransaction; + +static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = true; + + struct { + uint32_t card_id; + uint16_t balance; + uint16_t flags; + BipTimestamp trip_time_window; + BipTransaction top_ups[3]; + BipTransaction charges[3]; + } bip_data = { + .card_id = 0, + .balance = 0, + .flags = 0, + .trip_time_window = {0, 0, 0, 0, 0, 0}, + .top_ups = + { + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + }, + .charges = + { + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + }, + }; + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + do { + // verify first sector keys + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); + if(key != bip_keys_a[0]) { + parsed = false; + break; + } + key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); + if(key != bip_keys_b[0]) { + parsed = false; + break; + } + + // Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7 + parse_uint32_le(&data->block[SECTOR_BLOCK_OFFSET(0, 1)].data[4], &bip_data.card_id); + + // Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1 + parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[0], &bip_data.balance); + + // Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3 + parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[2], &bip_data.flags); + + // Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7 + parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(5, 1)], &bip_data.trip_time_window); + + // Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10 + for(size_t i = 0; i < 3; i++) { + if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(10, i)])) { + continue; + } + BipTransaction* top_up = &bip_data.top_ups[i]; + parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(10, i)], &top_up->timestamp); + parse_uint16_txn_amount( + &data->block[SECTOR_BLOCK_OFFSET(10, i)].data[9], &top_up->amount); + } + + // Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11 + for(size_t i = 0; i < 3; i++) { + if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(11, i)])) { + continue; + } + BipTransaction* charge = &bip_data.charges[i]; + parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(11, i)], &charge->timestamp); + parse_uint16_txn_amount( + &data->block[SECTOR_BLOCK_OFFSET(11, i)].data[10], &charge->amount); + } + + // All data is now parsed and stored in bip_data, now print it + + // Print basic info + furi_string_printf( + parsed_data, + "\e#Tarjeta Bip!\n" + "Card Number: %ld\n" + "Balance: $%d (flags %x)\n" + "Current Trip Window Ends:\n @", + bip_data.card_id, + bip_data.balance, + bip_data.flags); + + print_bip_timestamp(&bip_data.trip_time_window, parsed_data); + + // Find newest top-up + size_t newest_top_up = 0; + for(size_t i = 1; i < 3; i++) { + const BipTimestamp* newest = &bip_data.top_ups[newest_top_up].timestamp; + const BipTimestamp* current = &bip_data.top_ups[i].timestamp; + if(compare_bip_timestamp(current, newest) > 0) { + newest_top_up = i; + } + } + + // Print top-ups, newest first + furi_string_cat_printf(parsed_data, "\n\e#Last Top-ups"); + for(size_t i = 0; i < 3; i++) { + const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3]; + furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount); + print_bip_timestamp(&top_up->timestamp, parsed_data); + } + + // Find newest charge + size_t newest_charge = 0; + for(size_t i = 1; i < 3; i++) { + const BipTimestamp* newest = &bip_data.charges[newest_charge].timestamp; + const BipTimestamp* current = &bip_data.charges[i].timestamp; + if(compare_bip_timestamp(current, newest) > 0) { + newest_charge = i; + } + } + + // Print charges + furi_string_cat_printf(parsed_data, "\n\e#Last Charges (Trips)"); + for(size_t i = 0; i < 3; i++) { + const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3]; + furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount); + print_bip_timestamp(&charge->timestamp, parsed_data); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin bip_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = bip_verify, + .read = bip_read, + .parse = bip_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor bip_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &bip_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* bip_plugin_ep() { + return &bip_plugin_descriptor; +} From 7b26dc5ebfaa9d137e9d1764f95ee3738ee75a01 Mon Sep 17 00:00:00 2001 From: philicious Date: Tue, 5 Mar 2024 03:42:39 +0100 Subject: [PATCH 2/4] NFC: Fix washcity plugin verify function being to greedy (#3467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Fix washcity plugin verify function being to greedy It verifies only a single sector and that one using one of the commonly used key 0xA0A1A2A3A4A5 This leads to false-positives and the dicts not being used at all * nfc app: washcity plugin: fix verify function Co-authored-by: gornekich Co-authored-by: あく --- .../main/nfc/plugins/supported_cards/washcity.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index 2c0ec094c1..179b569d7e 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -57,20 +57,20 @@ static bool washcity_verify(Nfc* nfc) { bool verified = false; do { - const uint8_t ticket_sector_number = 0; - const uint8_t ticket_block_number = - mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; - FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); + const uint8_t verify_sector_number = 1; + const uint8_t verify_block_number = + mf_classic_get_first_block_num_of_sector(verify_sector_number); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector_number); MfClassicKey key = {0}; bit_lib_num_to_bytes_be( - washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + washcity_1k_keys[verify_sector_number].a, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_context; MfClassicError error = mf_classic_poller_sync_auth( - nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + nfc, verify_block_number, &key, MfClassicKeyTypeA, &auth_context); if(error != MfClassicErrorNone) { - FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + FURI_LOG_D(TAG, "Failed to read block %u: %d", verify_block_number, error); break; } From a6c5a1ba6972131d08adef3a3d81a5d3eb776610 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Tue, 5 Mar 2024 05:56:35 +0300 Subject: [PATCH 3/4] Troyka parser improvements (#3390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated troyka layout (full version) * Changed to furi func * Small refactor * Bitlib refactor * Moved to API * Rollback troyka parser * Fix functions * Parser func refactor start * Layout E3 refactored * Layout E4 refactored * Layout 6 refactored * Layout E5 refactored * Layout 2 refactored * Layout E5 fix * Layout E6 refactored, valid_date need fix * Layout E6 fix * Layout FCB refactored * Layout F0B refactored * Layout 8 refactored * Layout A refactored * Layout C refactored * Layout D refactored * Layout E1 refactored * Layout E2 refactored * Fixes * Old code cleanup * Memory cleanup * Unused imports cleanup * Keys struct refactor * Layout E1 fix * Added debug info for layout and department * Fix PVS warnings * Fix more PVS warnings Co-authored-by: gornekich Co-authored-by: あく --- .../nfc/api/mosgortrans/mosgortrans_util.c | 1308 +++++++++++++++++ .../nfc/api/mosgortrans/mosgortrans_util.h | 17 + .../main/nfc/api/nfc_app_api_table_i.h | 7 +- .../main/nfc/plugins/supported_cards/bip.c | 4 +- .../main/nfc/plugins/supported_cards/troika.c | 247 +--- 5 files changed, 1398 insertions(+), 185 deletions(-) create mode 100644 applications/main/nfc/api/mosgortrans/mosgortrans_util.c create mode 100644 applications/main/nfc/api/mosgortrans/mosgortrans_util.h diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c new file mode 100644 index 0000000000..f2484a2af5 --- /dev/null +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -0,0 +1,1308 @@ +#include "mosgortrans_util.h" + +#define TAG "Mosgortrans" + +void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = days * 24 * 60 * 60; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + +void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + +void from_seconds_to_datetime(uint32_t seconds, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = seconds; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + +typedef struct { + uint16_t view; //101 + uint16_t type; //102 + uint8_t layout; //111 + uint8_t layout2; //112 + uint16_t blank_type; //121 + uint16_t type_of_extended; //122 + uint8_t extended; //123 + uint8_t benefit_code; //124 + uint32_t number; //201 + uint16_t use_before_date; //202 + uint16_t use_before_date2; //202.2 + uint16_t use_with_date; //205 + uint8_t requires_activation; //301 + uint16_t activate_during; //302 + uint16_t extension_counter; //304 + uint8_t blocked; //303 + uint32_t valid_from_date; //311 + uint16_t valid_to_date; //312 + uint8_t valid_for_days; //313 + uint32_t valid_for_minutes; //314 + uint16_t valid_for_time; //316 + uint16_t valid_for_time2; //316.2 + uint32_t valid_to_time; //317 + uint16_t remaining_trips; //321 + uint8_t remaining_trips1; //321.1 + uint32_t remaining_funds; //322 + uint16_t total_trips; //331 + uint16_t start_trip_date; //402 + uint16_t start_trip_time; //403 + uint32_t start_trip_neg_minutes; //404 + uint32_t start_trip_minutes; //405 + uint8_t start_trip_seconds; //406 + uint8_t minutes_pass; //412 + uint8_t passage_5_minutes; //413 + uint8_t metro_ride_with; //414 + uint8_t transport_type; //421 + uint8_t transport_type_flag; //421.0 + uint8_t transport_type1; //421.1 + uint8_t transport_type2; //421.2 + uint8_t transport_type3; //421.3 + uint8_t transport_type4; //421.4 + uint16_t validator; //422 + uint8_t validator1; //422.1 + uint16_t validator2; //422.2 + uint16_t route; //424 + uint8_t passage_in_metro; //431 + uint8_t transfer_in_metro; //432 + uint8_t passages_ground_transport; //433 + uint8_t fare_trip; //441 + uint16_t crc16; //501.1 + uint16_t crc16_2; //501.2 + uint32_t hash; //502 + uint16_t hash1; //502.1 + uint32_t hash2; //502.2 + uint8_t geozone_a; //GeoZoneA + uint8_t geozone_b; //GeoZoneB + uint8_t company; //Company + uint8_t units; //Units + uint64_t rfu1; //rfu1 + uint16_t rfu2; //rfu2 + uint32_t rfu3; //rfu3 + uint8_t rfu4; //rfu4 + uint8_t rfu5; //rfu5 + uint8_t write_enabled; //write_enabled + uint32_t tech_code; //TechCode + uint8_t interval; //Interval + uint16_t app_code1; //AppCode1 + uint16_t app_code2; //AppCode2 + uint16_t app_code3; //AppCode3 + uint16_t app_code4; //AppCode4 + uint16_t type1; //Type1 + uint16_t type2; //Type2 + uint16_t type3; //Type3 + uint16_t type4; //Type4 + uint8_t zoo; //zoo +} BlockData; + +void parse_layout_2(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->benefit_code = bit_lib_get_bits(block->data, 0x48, 8); //124 + data_block->rfu1 = bit_lib_get_bits_32(block->data, 0x50, 32); //rfu1 + data_block->crc16 = bit_lib_get_bits_16(block->data, 0x70, 16); //501.1 + data_block->blocked = bit_lib_get_bits(block->data, 0x80, 1); //303 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0x81, 12); //403 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0x8D, 16); //402 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x9D, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0xAD, 16); //312 + data_block->start_trip_seconds = bit_lib_get_bits(block->data, 0xDB, 6); //406 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xC3, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xC5, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0xC7, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0xC9, 2); //421.4 + data_block->use_with_date = bit_lib_get_bits_16(block->data, 0xBD, 16); //205 + data_block->route = bit_lib_get_bits(block->data, 0xCD, 1); //424 + data_block->validator1 = bit_lib_get_bits_16(block->data, 0xCE, 15); //422.1 + data_block->validator = bit_lib_get_bits_16(block->data, 0xCD, 16); + data_block->total_trips = bit_lib_get_bits_16(block->data, 0xDD, 16); //331 + data_block->write_enabled = bit_lib_get_bits(block->data, 0xED, 1); //write_enabled + data_block->rfu2 = bit_lib_get_bits(block->data, 0xEE, 2); //rfu2 + data_block->crc16_2 = bit_lib_get_bits_16(block->data, 0xF0, 16); //501.2 +} + +void parse_layout_6(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->geozone_a = bit_lib_get_bits(block->data, 0x48, 4); //GeoZoneA + data_block->geozone_b = bit_lib_get_bits(block->data, 0x4C, 4); //GeoZoneB + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x50, 10); //121 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x5A, 10); //122 + data_block->rfu1 = bit_lib_get_bits_16(block->data, 0x64, 12); //rfu1 + data_block->crc16 = bit_lib_get_bits_16(block->data, 0x70, 16); //501.1 + data_block->blocked = bit_lib_get_bits(block->data, 0x80, 1); //303 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0x81, 12); //403 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0x8D, 16); //402 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x9D, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0xAD, 16); //312 + data_block->company = bit_lib_get_bits(block->data, 0xBD, 4); //Company + data_block->validator1 = bit_lib_get_bits(block->data, 0xC1, 4); //422.1 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xC5, 10); //321 + data_block->units = bit_lib_get_bits(block->data, 0xCF, 6); //Units + data_block->validator2 = bit_lib_get_bits_16(block->data, 0xD5, 10); //422.2 + data_block->total_trips = bit_lib_get_bits_16(block->data, 0xDF, 16); //331 + data_block->extended = bit_lib_get_bits(block->data, 0xEF, 1); //123 + data_block->crc16_2 = bit_lib_get_bits_16(block->data, 0xF0, 16); //501.2 +} + +void parse_layout_8(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->rfu1 = bit_lib_get_bits_64(block->data, 0x48, 56); //rfu1 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x80, 16); //311 + data_block->valid_for_days = bit_lib_get_bits(block->data, 0x90, 8); //313 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x98, 1); //301 + data_block->rfu2 = bit_lib_get_bits(block->data, 0x99, 7); //rfu2 + data_block->remaining_trips1 = bit_lib_get_bits(block->data, 0xA0, 8); //321.1 + data_block->remaining_trips = bit_lib_get_bits(block->data, 0xA8, 8); //321 + data_block->validator1 = bit_lib_get_bits(block->data, 0xB0, 2); //422.1 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB1, 15); //422 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 + data_block->rfu3 = bit_lib_get_bits_32(block->data, 0xE0, 32); //rfu3 +} + +void parse_layout_A(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x40, 12); //311 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x4C, 19); //314 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x5F, 1); //301 + data_block->start_trip_minutes = bit_lib_get_bits_32(block->data, 0x60, 19); //405 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x77, 7); //412 + data_block->transport_type_flag = bit_lib_get_bits(block->data, 0x7E, 2); //421.0 + data_block->remaining_trips = bit_lib_get_bits(block->data, 0x80, 8); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0x88, 16); //422 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0x98, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0x9A, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0x9C, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0x9E, 2); //421.4 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 +} + +void parse_layout_C(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->rfu1 = bit_lib_get_bits_64(block->data, 0x48, 56); //rfu1 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x80, 16); //311 + data_block->valid_for_days = bit_lib_get_bits(block->data, 0x90, 8); //313 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x98, 1); //301 + data_block->rfu2 = bit_lib_get_bits_16(block->data, 0x99, 13); //rfu2 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA6, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB0, 16); //422 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0xE0, 16); //402 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0xF0, 11); //403 + data_block->transport_type = bit_lib_get_bits(block->data, 0xFB, 2); //421 + data_block->rfu3 = bit_lib_get_bits(block->data, 0xFD, 2); //rfu3 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xFF, 1); //432 +} + +void parse_layout_D(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->rfu1 = bit_lib_get_bits(block->data, 0x38, 8); //rfu1 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x40, 16); //202 + data_block->valid_for_time = bit_lib_get_bits_16(block->data, 0x50, 11); //316 + data_block->rfu2 = bit_lib_get_bits(block->data, 0x5B, 5); //rfu2 + data_block->use_before_date2 = bit_lib_get_bits_16(block->data, 0x60, 16); //202.2 + data_block->valid_for_time2 = bit_lib_get_bits_16(block->data, 0x70, 11); //316.2 + data_block->rfu3 = bit_lib_get_bits(block->data, 0x7B, 5); //rfu3 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x80, 16); //311 + data_block->valid_for_days = bit_lib_get_bits(block->data, 0x90, 8); //313 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x98, 1); //301 + data_block->rfu4 = bit_lib_get_bits(block->data, 0x99, 2); //rfu4 + data_block->passage_5_minutes = bit_lib_get_bits(block->data, 0x9B, 5); //413 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xA0, 2); //421.1 + data_block->passage_in_metro = bit_lib_get_bits(block->data, 0xA2, 1); //431 + data_block->passages_ground_transport = bit_lib_get_bits(block->data, 0xA3, 3); //433 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA6, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB0, 16); //422 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0xE0, 16); //402 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0xF0, 11); //403 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xFB, 2); //421.2 + data_block->rfu5 = bit_lib_get_bits(block->data, 0xFD, 2); //rfu5 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xFF, 1); //432 +} + +void parse_layout_E1(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x3D, 16); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x4D, 10); //121 + data_block->validator = bit_lib_get_bits_16(block->data, 0x80, 16); //422 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0x90, 16); //402 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0xA0, 11); //403 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xAB, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xAD, 2); //421.2 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xB1, 1); //432 + data_block->passage_in_metro = bit_lib_get_bits(block->data, 0xB2, 1); //431 + data_block->passages_ground_transport = bit_lib_get_bits(block->data, 0xB3, 3); //433 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0xB9, 8); //412 + data_block->remaining_funds = bit_lib_get_bits_32(block->data, 0xC4, 19); //322 + data_block->fare_trip = bit_lib_get_bits(block->data, 0xD7, 2); //441 + data_block->blocked = bit_lib_get_bits(block->data, 0x9D, 1); //303 + data_block->zoo = bit_lib_get_bits(block->data, 0xDA, 1); //zoo + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E2(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x3D, 10); //122 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x47, 16); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x57, 10); //121 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x61, 16); //311 + data_block->activate_during = bit_lib_get_bits_16(block->data, 0x71, 9); //302 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x83, 20); //314 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x9A, 8); //412 + data_block->transport_type = bit_lib_get_bits(block->data, 0xA3, 2); //421 + data_block->passage_in_metro = bit_lib_get_bits(block->data, 0xA5, 1); //431 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xA6, 1); //432 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA7, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB1, 16); //422 + data_block->start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 0xC4, 20); //404 + data_block->requires_activation = bit_lib_get_bits(block->data, 0xD8, 1); //301 + data_block->blocked = bit_lib_get_bits(block->data, 0xD9, 1); //303 + data_block->extended = bit_lib_get_bits(block->data, 0xDA, 1); //123 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E3(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x4D, 10); //121 + data_block->remaining_funds = bit_lib_get_bits_32(block->data, 0xBC, 22); //322 + data_block->hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + data_block->validator = bit_lib_get_bits_16(block->data, 0x80, 16); //422 + data_block->start_trip_minutes = bit_lib_get_bits_32(block->data, 0x90, 23); //405 + data_block->fare_trip = bit_lib_get_bits(block->data, 0xD2, 2); //441 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0xAB, 7); //412 + data_block->transport_type_flag = bit_lib_get_bits(block->data, 0xB2, 2); //421.0 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xB4, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xB6, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0xB8, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0xBA, 2); //421.4 + data_block->blocked = bit_lib_get_bits(block->data, 0xD4, 1); //303 +} + +void parse_layout_E4(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x3D, 10); //122 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x47, 13); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x54, 10); //121 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x5E, 13); //311 + data_block->activate_during = bit_lib_get_bits_16(block->data, 0x6B, 9); //302 + data_block->extension_counter = bit_lib_get_bits_16(block->data, 0x74, 10); //304 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x80, 20); //314 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x98, 7); //412 + data_block->transport_type_flag = bit_lib_get_bits(block->data, 0x9F, 2); //421.0 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xA1, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xA3, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0xA5, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0xA7, 2); //421.4 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA9, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB3, 16); //422 + data_block->start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 0xC3, 20); //404 + data_block->requires_activation = bit_lib_get_bits(block->data, 0xD7, 1); //301 + data_block->blocked = bit_lib_get_bits(block->data, 0xD8, 1); //303 + data_block->extended = bit_lib_get_bits(block->data, 0xD9, 1); //123 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E5(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x3D, 13); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x4A, 10); //121 + data_block->valid_to_time = bit_lib_get_bits_32(block->data, 0x54, 23); //317 + data_block->extension_counter = bit_lib_get_bits_16(block->data, 0x6B, 10); //304 + data_block->start_trip_minutes = bit_lib_get_bits_32(block->data, 0x80, 23); //405 + data_block->metro_ride_with = bit_lib_get_bits(block->data, 0x97, 7); //414 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x9E, 7); //412 + data_block->remaining_funds = bit_lib_get_bits_32(block->data, 0xA7, 19); //322 + data_block->validator = bit_lib_get_bits_16(block->data, 0xBA, 16); //422 + data_block->blocked = bit_lib_get_bits(block->data, 0xCA, 1); //303 + data_block->route = bit_lib_get_bits_16(block->data, 0xCC, 12); //424 + data_block->passages_ground_transport = bit_lib_get_bits(block->data, 0xD8, 7); //433 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E6(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x3D, 10); //122 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x47, 13); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x54, 10); //121 + data_block->valid_from_date = bit_lib_get_bits_32(block->data, 0x5E, 23); //311 + data_block->extension_counter = bit_lib_get_bits_16(block->data, 0x75, 10); //304 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x80, 20); //314 + data_block->start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 0x94, 20); //404 + data_block->metro_ride_with = bit_lib_get_bits(block->data, 0xA8, 7); //414 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0xAF, 7); //412 + data_block->remaining_trips = bit_lib_get_bits(block->data, 0xB6, 7); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xBD, 16); //422 + data_block->blocked = bit_lib_get_bits(block->data, 0xCD, 1); //303 + data_block->extended = bit_lib_get_bits(block->data, 0xCE, 1); //123 + data_block->route = bit_lib_get_bits_16(block->data, 0xD4, 12); //424 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_FCB(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->tech_code = bit_lib_get_bits_32(block->data, 0x38, 10); //tech_code + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x42, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0x52, 16); //312 + data_block->interval = bit_lib_get_bits(block->data, 0x62, 4); //interval + data_block->app_code1 = bit_lib_get_bits_16(block->data, 0x66, 10); //app_code1 + data_block->hash1 = bit_lib_get_bits_16(block->data, 0x70, 16); //502.1 + data_block->type1 = bit_lib_get_bits_16(block->data, 0x80, 10); //type1 + data_block->app_code2 = bit_lib_get_bits_16(block->data, 0x8A, 10); //app_code2 + data_block->type2 = bit_lib_get_bits_16(block->data, 0x94, 10); //type2 + data_block->app_code3 = bit_lib_get_bits_16(block->data, 0x9E, 10); //app_code3 + data_block->type3 = bit_lib_get_bits_16(block->data, 0xA8, 10); //type3 + data_block->app_code4 = bit_lib_get_bits_16(block->data, 0xB2, 10); //app_code4 + data_block->type4 = bit_lib_get_bits_16(block->data, 0xBC, 10); //type4 + data_block->hash2 = bit_lib_get_bits_32(block->data, 0xE0, 32); //502.2 +} + +void parse_layout_F0B(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->tech_code = bit_lib_get_bits_32(block->data, 0x38, 10); //tech_code + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x42, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0x52, 16); //312 + data_block->hash1 = bit_lib_get_bits_32(block->data, 0x70, 16); //502.1 +} + +void parse_transport_type(BlockData* data_block, FuriString* transport) { + switch(data_block->transport_type_flag) { + case 1: + uint8_t transport_type = + (data_block->transport_type1 || data_block->transport_type2 || + data_block->transport_type3 || data_block->transport_type4); + switch(transport_type) { + case 1: + furi_string_cat(transport, "Metro"); + break; + case 2: + furi_string_cat(transport, "Monorail"); + break; + case 3: + furi_string_cat(transport, "MCC"); + break; + default: + furi_string_cat(transport, "Unknown"); + break; + } + break; + case 2: + furi_string_cat(transport, "Ground"); + break; + default: + furi_string_cat(transport, ""); + break; + } +} + +bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result) { + BlockData data_block = {}; + const uint16_t valid_departments[] = {0x106, 0x108, 0x10A, 0x10E, 0x110, 0x117}; + uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + furi_string_cat_printf(result, "Transport departament: %x\n", transport_departament); + } + bool departament_valid = false; + for(uint8_t i = 0; i < 6; i++) { + if(transport_departament == valid_departments[i]) { + departament_valid = true; + break; + } + } + if(!departament_valid) { + return false; + } + FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); + uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); + if(layout_type == 0xE) { + layout_type = bit_lib_get_bits_16(block->data, 52, 9); + } else if(layout_type == 0xF) { + layout_type = bit_lib_get_bits_16(block->data, 52, 14); + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + furi_string_cat_printf(result, "Layout: %x\n", layout_type); + } + FURI_LOG_D(TAG, "Layout type %x", layout_type); + switch(layout_type) { + case 0x02: { + parse_layout_2(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + + if(data_block.valid_from_date == 0 || data_block.valid_to_date == 0) { + furi_string_cat(result, "\e#No ticket\n"); + return true; + } + //remaining_trips + furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_number + furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); + //trip_from + DateTime card_start_trip_minutes_s = {0}; + from_seconds_to_datetime( + data_block.start_trip_date * 24 * 60 * 60 + data_block.start_trip_time * 60 + + data_block.start_trip_seconds, + &card_start_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + break; + } + case 0x06: { + parse_layout_6(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_number + furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); + //trip_from + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (data_block.start_trip_date) * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + //validator + furi_string_cat_printf( + result, "Validator: %05d", data_block.validator1 * 1024 + data_block.validator2); + break; + } + case 0x08: { + parse_layout_8(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + break; + } + case 0x0A: { + parse_layout_A(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2016); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 2016); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - 1, + &card_valid_to_date_s, + 2016); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_from + if(data_block.start_trip_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.start_trip_minutes, + &card_start_trip_minutes_s, + 2016); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //trip_switch + if(data_block.minutes_pass) { + DateTime card_start_switch_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.start_trip_minutes + + data_block.minutes_pass, + &card_start_switch_trip_minutes_s, + 2016); + furi_string_cat_printf( + result, + "Trip switch: %02d.%02d.%04d %02d:%02d\n", + card_start_switch_trip_minutes_s.day, + card_start_switch_trip_minutes_s.month, + card_start_switch_trip_minutes_s.year, + card_start_switch_trip_minutes_s.hour, + card_start_switch_trip_minutes_s.minute); + } + //transport + FuriString* transport = furi_string_alloc(); + parse_transport_type(&data_block, transport); + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d\n", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0x0C: { + parse_layout_C(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //trip_from + if(data_block.start_trip_date) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + } + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0x0D: { + parse_layout_D(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_from + if(data_block.start_trip_date) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + } + //trip_switch + if(data_block.passage_5_minutes) { + DateTime card_start_switch_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time + + data_block.passage_5_minutes, + &card_start_switch_trip_minutes_s, + 1992); + } + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0xE1: + case 0x1C1: { + parse_layout_E1(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_funds + furi_string_cat_printf(result, "Balance: %ld rub\n", data_block.remaining_funds / 100); + //trip_from + if(data_block.start_trip_date) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //transport + FuriString* transport = furi_string_alloc(); + switch(data_block.transport_type1) { + case 1: + switch(data_block.transport_type2) { + case 1: + furi_string_cat(transport, "Metro"); + break; + case 2: + furi_string_cat(transport, "Monorail"); + break; + default: + furi_string_cat(transport, "Unknown"); + break; + } + break; + case 2: + furi_string_cat(transport, "Ground"); + break; + case 3: + furi_string_cat(transport, "MCC"); + break; + default: + furi_string_cat(transport, ""); + break; + } + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0xE2: + case 0x1C2: { + parse_layout_E2(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + if(data_block.activate_during) { + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.activate_during, + &card_valid_to_date_s, + 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + } else { + DateTime card_valid_to_date_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - 1, + &card_valid_to_date_s, + 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + } + //trip_from + if(data_block.start_trip_neg_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_to_date * 24 * 60 + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes, + &card_start_trip_minutes_s, + 1992); //-time + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //trip_switch + if(data_block.minutes_pass) { + DateTime card_start_switch_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes + data_block.minutes_pass, + &card_start_switch_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip switch: %02d.%02d.%04d %02d:%02d\n", + card_start_switch_trip_minutes_s.day, + card_start_switch_trip_minutes_s.month, + card_start_switch_trip_minutes_s.year, + card_start_switch_trip_minutes_s.hour, + card_start_switch_trip_minutes_s.minute); + } + //transport + FuriString* transport = furi_string_alloc(); + switch(data_block.transport_type) { + case 1: + furi_string_cat(transport, "Metro"); + break; + case 2: + furi_string_cat(transport, "Monorail"); + break; + case 3: + furi_string_cat(transport, "Ground"); + break; + default: + furi_string_cat(transport, "Unknown"); + break; + } + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0xE3: + case 0x1C3: { + parse_layout_E3(&data_block, block); + // number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + // use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + // remaining_funds + furi_string_cat_printf(result, "Balance: %lu rub\n", data_block.remaining_funds); + // start_trip_minutes + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(data_block.start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + // transport + FuriString* transport = furi_string_alloc(); + parse_transport_type(&data_block, transport); + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + // validator + furi_string_cat_printf(result, "Validator: %05d\n", data_block.validator); + // fare + FuriString* fare = furi_string_alloc(); + switch(data_block.fare_trip) { + case 0: + furi_string_cat(fare, ""); + break; + case 1: + furi_string_cat(fare, "Single"); + break; + case 2: + furi_string_cat(fare, "90 minutes"); + break; + default: + furi_string_cat(fare, "Unknown"); + break; + } + furi_string_cat_printf(result, "Fare: %s", furi_string_get_cstr(fare)); + furi_string_free(fare); + furi_string_free(transport); + break; + } + case 0xE4: + case 0x1C4: { + parse_layout_E4(&data_block, block); + + // number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + // use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2016); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + // remaining_funds + furi_string_cat_printf(result, "Balance: %lu rub\n", data_block.remaining_funds); + // valid_from_date + DateTime card_use_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 2016); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + // valid_to_date + DateTime card_use_to_date_s = {0}; + if(data_block.requires_activation) { + from_days_to_datetime( + data_block.valid_from_date + data_block.activate_during, + &card_use_to_date_s, + 2016); + } else { + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - 1, + &card_use_to_date_s, + 2016); + } + + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + // trip_number + // furi_string_cat_printf(result, "Trips left: %d", data_block.remaining_trips); + // trip_from + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); + //transport + FuriString* transport = furi_string_alloc(); + parse_transport_type(&data_block, transport); + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + // validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0xE5: + case 0x1C5: { + parse_layout_E5(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2019); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_funds + furi_string_cat_printf(result, "Balance: %ld rub\n", data_block.remaining_funds / 100); + //start_trip_minutes + if(data_block.start_trip_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_minutes, &card_start_trip_minutes_s, 2019); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //start_m_trip_minutes + if(data_block.metro_ride_with) { + DateTime card_start_m_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_minutes + data_block.metro_ride_with, + &card_start_m_trip_minutes_s, + 2019); + furi_string_cat_printf( + result, + "(M) from: %02d.%02d.%04d %02d:%02d\n", + card_start_m_trip_minutes_s.day, + card_start_m_trip_minutes_s.month, + card_start_m_trip_minutes_s.year, + card_start_m_trip_minutes_s.hour, + card_start_m_trip_minutes_s.minute); + } + if(data_block.minutes_pass) { + DateTime card_start_change_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_minutes + data_block.minutes_pass, + &card_start_change_trip_minutes_s, + 2019); + furi_string_cat_printf( + result, + "Trip edit: %02d.%02d.%04d %02d:%02d\n", + card_start_change_trip_minutes_s.day, + card_start_change_trip_minutes_s.month, + card_start_change_trip_minutes_s.year, + card_start_change_trip_minutes_s.hour, + card_start_change_trip_minutes_s.minute); + } + //transport + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0xE6: + case 0x1C6: { + parse_layout_E6(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2019); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_use_from_date_s = {0}; + from_minutes_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 2019); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + //valid_to_date + DateTime card_use_to_date_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date + data_block.valid_for_minutes - 1, + &card_use_to_date_s, + 2019); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + //start_trip_minutes + if(data_block.start_trip_neg_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2019); //-time + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //start_trip_m_minutes + if(data_block.metro_ride_with) { + DateTime card_start_trip_m_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes + data_block.metro_ride_with, + &card_start_trip_m_minutes_s, + 2019); + furi_string_cat_printf( + result, + "(M) from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_m_minutes_s.day, + card_start_trip_m_minutes_s.month, + card_start_trip_m_minutes_s.year, + card_start_trip_m_minutes_s.hour, + card_start_trip_m_minutes_s.minute); + } + //transport + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0x3CCB: { + parse_layout_FCB(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //valid_from_date + DateTime card_use_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + //valid_to_date + DateTime card_use_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_use_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + break; + } + case 0x3C0B: { + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //valid_from_date + DateTime card_use_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + //valid_to_date + DateTime card_use_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_use_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + break; + } + default: + result = NULL; + return false; + } + + return true; +} diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h new file mode 100644 index 0000000000..e5da8ddeb4 --- /dev/null +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index 08bff072ea..bf0e926ee6 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -1,4 +1,5 @@ #include "gallagher/gallagher_util.h" +#include "mosgortrans/mosgortrans_util.h" /* * A list of app's private functions and objects to expose for plugins. @@ -10,4 +11,8 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( gallagher_deobfuscate_and_parse_credential, void, (GallagherCredential * credential, const uint8_t* cardholder_data_obfuscated)), - API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*))); + API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*), + API_METHOD( + mosgortrans_parse_transport_block, + bool, + (const MfClassicBlock* block, FuriString* result)))); diff --git a/applications/main/nfc/plugins/supported_cards/bip.c b/applications/main/nfc/plugins/supported_cards/bip.c index 211c27cb56..f6fed6774b 100644 --- a/applications/main/nfc/plugins/supported_cards/bip.c +++ b/applications/main/nfc/plugins/supported_cards/bip.c @@ -296,8 +296,8 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, "\e#Tarjeta Bip!\n" - "Card Number: %ld\n" - "Balance: $%d (flags %x)\n" + "Card Number: %lu\n" + "Balance: $%hu (flags %hu)\n" "Current Trip Window Ends:\n @", bip_data.card_id, bip_data.balance, diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index ba7c3bf5e3..50806e137d 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1,10 +1,12 @@ #include "nfc_supported_card_plugin.h" +#include #include #include #include #include +#include "../../api/mosgortrans/mosgortrans_util.h" #include "furi_hal_rtc.h" #define TAG "Troika" @@ -19,19 +21,6 @@ typedef struct { uint32_t data_sector; } TroikaCardConfig; -typedef enum { - TroikaLayoutUnknown = 0x0, - TroikaLayout2 = 0x2, - TroikaLayoutE = 0xE, -} TroikaLayout; - -typedef enum { - TroikaSublayoutUnknown = 0x0, - TroikaSublayout3 = 0x3, - TroikaSublayout5 = 0x5, - TroikaSublayout6 = 0x6, -} TroikaSubLayout; - static const MfClassicKeyPair troika_1k_keys[] = { {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, @@ -52,33 +41,53 @@ static const MfClassicKeyPair troika_1k_keys[] = { }; static const MfClassicKeyPair troika_4k_keys[] = { - {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, - {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, - {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698}, - {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dbb}, - {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, - {.a = 0x9becdf3d9273, .b = 0xf8493407799d}, {.a = 0x08b386463229, .b = 0x5efbaecef46b}, - {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, - {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, - {.a = 0x6b02733bb6ec, .b = 0x7038cd25c408}, {.a = 0x403d706ba880, .b = 0xb39d19a280df}, - {.a = 0xc11f4597efb5, .b = 0x70d901648cb9}, {.a = 0x0db520c78c1c, .b = 0x73e5b9d9d3a4}, - {.a = 0x3ebce0925b2f, .b = 0x372cc880f216}, {.a = 0x16a27af45407, .b = 0x9868925175ba}, - {.a = 0xaba208516740, .b = 0xce26ecb95252}, {.a = 0xcd64e567abcd, .b = 0x8f79c4fd8a01}, - {.a = 0x764cd061f1e6, .b = 0xa74332f74994}, {.a = 0x1cc219e9fec1, .b = 0xb90de525ceb6}, - {.a = 0x2fe3cb83ea43, .b = 0xfba88f109b32}, {.a = 0x07894ffec1d6, .b = 0xefcb0e689db3}, - {.a = 0x04c297b91308, .b = 0xc8454c154cb5}, {.a = 0x7a38e3511a38, .b = 0xab16584c972a}, - {.a = 0x7545df809202, .b = 0xecf751084a80}, {.a = 0x5125974cd391, .b = 0xd3eafb5df46d}, - {.a = 0x7a86aa203788, .b = 0xe41242278ca2}, {.a = 0xafcef64c9913, .b = 0x9db96dca4324}, - {.a = 0x04eaa462f70b, .b = 0xac17b93e2fae}, {.a = 0xe734c210f27e, .b = 0x29ba8c3e9fda}, - {.a = 0xd5524f591eed, .b = 0x5daf42861b4d}, {.a = 0xe4821a377b75, .b = 0xe8709e486465}, - {.a = 0x518dc6eea089, .b = 0x97c64ac98ca4}, {.a = 0xbb52f8cce07f, .b = 0x6b6119752c70}, + {.a = 0xEC29806D9738, .b = 0xFBF225DC5D58}, //1 + {.a = 0xA0A1A2A3A4A5, .b = 0x7DE02A7F6025}, //2 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //3 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //4 + {.a = 0x73068F118C13, .b = 0x2B7F3253FAC5}, //5 + {.a = 0xFBC2793D540B, .b = 0xD3A297DC2698}, //6 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //7 + {.a = 0xAE3D65A3DAD4, .b = 0x0F1C63013DBA}, //8 + {.a = 0xA73F5DC1D333, .b = 0xE35173494A81}, //9 + {.a = 0x69A32F1C2F19, .b = 0x6B8BD9860763}, //10 + {.a = 0x9BECDF3D9273, .b = 0xF8493407799D}, //11 + {.a = 0x08B386463229, .b = 0x5EFBAECEF46B}, //12 + {.a = 0xCD4C61C26E3D, .b = 0x31C7610DE3B0}, //13 + {.a = 0xA82607B01C0D, .b = 0x2910989B6880}, //14 + {.a = 0x0E8F64340BA4, .b = 0x4ACEC1205D75}, //15 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //16 + {.a = 0x6B02733BB6EC, .b = 0x7038CD25C408}, //17 + {.a = 0x403D706BA880, .b = 0xB39D19A280DF}, //18 + {.a = 0xC11F4597EFB5, .b = 0x70D901648CB9}, //19 + {.a = 0x0DB520C78C1C, .b = 0x73E5B9D9D3A4}, //20 + {.a = 0x3EBCE0925B2F, .b = 0x372CC880F216}, //21 + {.a = 0x16A27AF45407, .b = 0x9868925175BA}, //22 + {.a = 0xABA208516740, .b = 0xCE26ECB95252}, //23 + {.a = 0xCD64E567ABCD, .b = 0x8F79C4FD8A01}, //24 + {.a = 0x764CD061F1E6, .b = 0xA74332F74994}, //25 + {.a = 0x1CC219E9FEC1, .b = 0xB90DE525CEB6}, //26 + {.a = 0x2FE3CB83EA43, .b = 0xFBA88F109B32}, //27 + {.a = 0x07894FFEC1D6, .b = 0xEFCB0E689DB3}, //28 + {.a = 0x04C297B91308, .b = 0xC8454C154CB5}, //29 + {.a = 0x7A38E3511A38, .b = 0xAB16584C972A}, //30 + {.a = 0x7545DF809202, .b = 0xECF751084A80}, //31 + {.a = 0x5125974CD391, .b = 0xD3EAFB5DF46D}, //32 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //33 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //34 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //35 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //36 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //37 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //38 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //39 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //40 }; static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; if(type == MfClassicType1k) { - config->data_sector = 8; + config->data_sector = 11; config->keys = troika_1k_keys; } else if(type == MfClassicType4k) { config->data_sector = 8; // Further testing needed @@ -90,126 +99,6 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) return success; } -static TroikaLayout troika_get_layout(const MfClassicData* data, uint8_t start_block_num) { - furi_assert(data); - - // Layout is stored in byte 6 of block, length 4 bits (bits 52 - 55), second nibble. - const uint8_t* layout_ptr = &data->block[start_block_num].data[6]; - const uint8_t layout = (*layout_ptr & 0x0F); - - TroikaLayout result = TroikaLayoutUnknown; - switch(layout) { - case TroikaLayout2: - case TroikaLayoutE: - result = layout; - break; - default: - // If debug is enabled - pass the actual layout value for the debug text - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - return layout; - } else { - return TroikaLayoutUnknown; - } - } - - return result; -} - -static TroikaSubLayout troika_get_sub_layout(const MfClassicData* data, uint8_t start_block_num) { - furi_assert(data); - - // Sublayout is stored in byte 7 (bits 56 - 60) of block, length 5 bits (first nibble and one bit from second nibble) - const uint8_t* sub_layout_ptr = &data->block[start_block_num].data[7]; - const uint8_t sub_layout = (*sub_layout_ptr & 0x3F) >> 3; - - TroikaSubLayout result = TroikaSublayoutUnknown; - switch(sub_layout) { - case TroikaSublayout3: - case TroikaSublayout5: - case TroikaSublayout6: - result = sub_layout; - break; - default: - // If debug is enabled - pass the actual sublayout value for the debug text - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - return sub_layout; - } else { - return TroikaSublayoutUnknown; - } - } - - return result; -} - -static bool troika_has_balance(TroikaLayout layout, TroikaSubLayout sub_layout) { - UNUSED(sub_layout); - // Layout 0x2 has no balance - - if(layout == TroikaLayout2) { - return false; - } - - return true; -} - -static uint16_t troika_get_balance( - const MfClassicData* data, - uint8_t start_block_num, - TroikaLayout layout, - TroikaSubLayout sub_layout) { - furi_assert(data); - - // In layout 0x3 balance in bits 188:209 ( from sector start, length 22). - // In layout 0x5 balance in bits 165:185 ( from sector start, length 20). - - uint32_t balance = 0; - uint8_t balance_data_offset = 0; - bool supported_layout = false; - - if(layout == TroikaLayoutE && sub_layout == TroikaSublayout3) { - balance_data_offset = 7; - supported_layout = true; - } else if(layout == TroikaLayoutE && sub_layout == TroikaSublayout5) { - balance_data_offset = 4; - supported_layout = true; - } - - if(supported_layout) { - const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[balance_data_offset]; - balance |= (temp_ptr[0] & 0x3) << 18; - balance |= temp_ptr[1] << 10; - balance |= temp_ptr[2] << 2; - balance |= (temp_ptr[3] & 0xC0) >> 6; - } - - return balance / 100; -} - -static uint32_t troika_get_number( - const MfClassicData* data, - uint8_t start_block_num, - TroikaLayout layout, - TroikaSubLayout sub_layout) { - furi_assert(data); - UNUSED(sub_layout); - - if(layout == TroikaLayoutE || layout == TroikaLayout2) { - const uint8_t* temp_ptr = &data->block[start_block_num].data[2]; - - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; - } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - return number; - } else { - return 0; - } -} - static bool troika_verify_type(Nfc* nfc, MfClassicType type) { bool verified = false; @@ -230,7 +119,7 @@ static bool troika_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; } - + FURI_LOG_D(TAG, "Verify success!"); verified = true; } while(false); @@ -306,40 +195,34 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Get the block number of the block that contains the data - const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + FuriString* tat_result = furi_string_alloc(); - // Get layout, sublayout, balance and number - TroikaLayout layout = troika_get_layout(data, start_block_num); - TroikaSubLayout sub_layout = troika_get_sub_layout(data, start_block_num); + bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result); + bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result); + bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); - if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - // If debug is enabled - proceed even if layout or sublayout is unknown, that will make collecting data easier - if(layout == TroikaLayoutUnknown || sub_layout == TroikaSublayoutUnknown) break; + furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); + if(result1) { + furi_string_cat_printf( + parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); } - uint32_t number = troika_get_number(data, start_block_num, layout, sub_layout); - - furi_string_printf(parsed_data, "\e#Troika\nNum: %lu", number); - - if(troika_has_balance(layout, sub_layout) || - furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - uint16_t balance = troika_get_balance(data, start_block_num, layout, sub_layout); - furi_string_cat_printf(parsed_data, "\nBalance: %u RUR", balance); - } else { - furi_string_cat_printf(parsed_data, "\nBalance: Not available"); + if(result2) { + furi_string_cat_printf( + parsed_data, "\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - furi_string_cat_printf( - parsed_data, - "\nLayout: %02x\nSublayout: %02x\nData Block: %u", - layout, - sub_layout, - start_block_num); + if(result3) { + furi_string_cat_printf(parsed_data, "\e#TAT\n%s\n", furi_string_get_cstr(tat_result)); } - parsed = true; + furi_string_free(tat_result); + furi_string_free(ground_result); + furi_string_free(metro_result); + + parsed = result1 || result2 || result3; } while(false); return parsed; @@ -363,4 +246,4 @@ static const FlipperAppPluginDescriptor troika_plugin_descriptor = { /* Plugin entry point - must return a pointer to const descriptor */ const FlipperAppPluginDescriptor* troika_plugin_ep() { return &troika_plugin_descriptor; -} +} \ No newline at end of file From 5e47048369477e6569f62a05d196eca520492402 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 5 Mar 2024 04:03:46 +0100 Subject: [PATCH 4/4] Archive: Fix item focus after aborting the Delete operation (#3475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- applications/main/archive/scenes/archive_scene_browser.c | 1 + applications/main/archive/scenes/archive_scene_delete.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index d156d171d5..284e534aba 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -157,6 +157,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { break; case ArchiveBrowserEventFileMenuDelete: if(archive_get_tab(browser) != ArchiveTabFavorites) { + archive_show_file_menu(browser, false); scene_manager_set_scene_state( archive->scene_manager, ArchiveAppSceneBrowser, SCENE_STATE_NEED_REFRESH); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneDelete); diff --git a/applications/main/archive/scenes/archive_scene_delete.c b/applications/main/archive/scenes/archive_scene_delete.c index d3964d0feb..45d5b47cc9 100644 --- a/applications/main/archive/scenes/archive_scene_delete.c +++ b/applications/main/archive/scenes/archive_scene_delete.c @@ -29,6 +29,12 @@ void archive_scene_delete_on_enter(void* context) { filename = furi_string_alloc(); ArchiveFile_t* current = archive_get_current_file(app->browser); + + FuriString* filename_no_ext = furi_string_alloc(); + path_extract_filename(current->path, filename_no_ext, true); + strlcpy(app->text_store, furi_string_get_cstr(filename_no_ext), MAX_NAME_LEN); + furi_string_free(filename_no_ext); + path_extract_filename(current->path, filename, false); char delete_str[64];