From 2ac41a3d90c55f382a2a1564e2ebdabf339df0b6 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:13:01 +0300 Subject: [PATCH 1/4] Add system node discovery to 'hf felica dump' --- client/src/cmdhffelica.c | 159 +++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 39 deletions(-) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index a57bfde76..39f254183 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -67,6 +67,7 @@ #define FELICA_SYSTEM_CODE_MAX_COUNT 16U #define FELICA_DISCOVERED_SYSTEM_MAX_COUNT FELICA_SYSTEM_CODE_MAX_COUNT #define FELICA_SYSTEM_CODE_WILDCARD 0xFFFFU +#define FELICA_SYSTEM_NODE 0xFFFFU #define FELICA_SYSTEM_CODE_NFC_TYPE3 0x12FCU #define FELICA_SYSTEM_CODE_FELICA_LITE 0x88B4U #define FELICA_SYSTEM_CODE_FELICA_SECURE_ID 0x957AU @@ -85,6 +86,7 @@ #define FELICA_SERVICE_ATTRIBUTE_PURSE (0b010000) #define FELICA_SERVICE_ATTRIBUTE_PIN_REQUIRED (0b100000) #define FELICA_SERVICE_ATTRIBUTE_PURSE_SUBFIELD (0b000110) +#define FELICA_NODE_ATTRIBUTE_MASK 0x3FU #define FELICA_AREA_ATTRIBUTE_CAN_CREATE_SUBAREA 0x00U #define FELICA_AREA_ATTRIBUTE_CANNOT_CREATE_SUBAREA 0x01U @@ -255,8 +257,14 @@ typedef enum { FELICA_NODE_DISCOVERY_READ_WITHOUT_ENCRYPTION, } felica_node_discovery_method_t; +typedef enum { + FELICA_NODE_TYPE_AREA = 0, + FELICA_NODE_TYPE_SERVICE, + FELICA_NODE_TYPE_SYSTEM, +} felica_node_type_t; + typedef struct { - bool is_area; + felica_node_type_t type; uint16_t node_code_le; bool has_end_code; uint16_t end_code_le; @@ -278,7 +286,7 @@ typedef struct { } felica_dump_encryption_identifier_t; typedef struct { - bool is_area; + felica_node_type_t type; uint16_t node_code_le; bool has_end_code; uint16_t end_code_le; @@ -299,6 +307,32 @@ typedef struct { size_t node_capacity; } felica_dump_system_t; +static uint8_t felica_node_attribute(uint16_t node_code_le) { + return node_code_le & FELICA_NODE_ATTRIBUTE_MASK; +} + +static bool felica_is_area_start_attribute(uint8_t attribute) { + switch (attribute) { + case FELICA_AREA_ATTRIBUTE_CAN_CREATE_SUBAREA: + case FELICA_AREA_ATTRIBUTE_CANNOT_CREATE_SUBAREA: + case FELICA_AREA_ATTRIBUTE_CAN_CREATE_SUBAREA_WITH_PIN: + case FELICA_AREA_ATTRIBUTE_CANNOT_CREATE_SUBAREA_WITH_PIN: + return true; + default: + return false; + } +} + +static felica_node_type_t felica_node_type_from_code(uint16_t node_code_le) { + if (node_code_le == FELICA_SYSTEM_NODE) { + return FELICA_NODE_TYPE_SYSTEM; + } + if (felica_is_area_start_attribute(felica_node_attribute(node_code_le))) { + return FELICA_NODE_TYPE_AREA; + } + return FELICA_NODE_TYPE_SERVICE; +} + typedef struct { uint8_t encryption_identifier; bool has_aes_key_versions; @@ -3182,12 +3216,14 @@ static bool felica_discover_nodes_with_request_code_list(uint8_t *flags, for (uint8_t i = 0; i < area_count; i++) { felica_discovered_node_t node = {0}; - node.is_area = true; node.node_code_le = (uint16_t)resp.data.asBytes[offset] | ((uint16_t)resp.data.asBytes[offset + 1] << 8); - node.has_end_code = true; - node.end_code_le = (uint16_t)resp.data.asBytes[offset + 2] | - ((uint16_t)resp.data.asBytes[offset + 3] << 8); + node.type = felica_node_type_from_code(node.node_code_le); + node.has_end_code = (node.type == FELICA_NODE_TYPE_AREA); + node.end_code_le = node.has_end_code + ? ((uint16_t)resp.data.asBytes[offset + 2] | + ((uint16_t)resp.data.asBytes[offset + 3] << 8)) + : 0; offset += 4; if (batch_count < ARRAYLEN(batch_nodes)) { batch_nodes[batch_count++] = node; @@ -3203,9 +3239,9 @@ static bool felica_discover_nodes_with_request_code_list(uint8_t *flags, for (uint8_t i = 0; i < service_count; i++) { felica_discovered_node_t node = {0}; - node.is_area = false; node.node_code_le = (uint16_t)resp.data.asBytes[offset] | ((uint16_t)resp.data.asBytes[offset + 1] << 8); + node.type = felica_node_type_from_code(node.node_code_le); node.has_end_code = false; node.end_code_le = 0; offset += 2; @@ -3227,6 +3263,23 @@ static bool felica_discover_nodes_with_request_code_list(uint8_t *flags, } } + if (supported) { + felica_discovered_node_t node = {0}; + node.type = FELICA_NODE_TYPE_SYSTEM; + node.node_code_le = FELICA_SYSTEM_NODE; + node.has_end_code = false; + node.end_code_le = 0; + /* + * RequestCodeList does not report the system node explicitly, but any + * target supporting node-list discovery has a system node. Treat it as + * implicit and emit it last, matching SearchServiceCode ordering. + */ + if (felica_emit_discovered_node(&node, visitor, ctx, &local_count, stop_status) != PM3_SUCCESS) { + felica_set_discovered_count(discovered_count, local_count); + return false; + } + } + felica_set_discovered_count(discovered_count, local_count); return supported; @@ -3280,20 +3333,20 @@ static bool felica_discover_nodes_with_search_service_code(uint8_t *flags, } const uint16_t node_code_le = (uint16_t)resp.payload[0] | ((uint16_t)resp.payload[1] << 8); - if (node_code_le == 0xFFFF) { - break; - } - felica_discovered_node_t node = {0}; - node.is_area = (frame_len == 0x0E); node.node_code_le = node_code_le; - node.has_end_code = (frame_len == 0x0E); + node.type = felica_node_type_from_code(node_code_le); + node.has_end_code = (node.type == FELICA_NODE_TYPE_AREA && frame_len == 0x0E); node.end_code_le = node.has_end_code ? ((uint16_t)resp.payload[2] | ((uint16_t)resp.payload[3] << 8)) : 0; if (felica_emit_discovered_node(&node, visitor, ctx, &local_count, stop_status) != PM3_SUCCESS) { felica_set_discovered_count(discovered_count, local_count); return false; } + + if (node.type == FELICA_NODE_TYPE_SYSTEM) { + break; + } } felica_set_discovered_count(discovered_count, local_count); @@ -3305,7 +3358,7 @@ static bool felica_request_service_send_probe_batch(uint8_t *flags, const uint8_t *idm, uint32_t retry_count, const uint16_t *node_codes_le, - const bool *is_area_nodes, + const felica_node_type_t *node_types, size_t node_count, felica_node_discovery_visitor_t visitor, void *ctx, @@ -3358,12 +3411,12 @@ static bool felica_request_service_send_probe_batch(uint8_t *flags, for (size_t i = 0; i < returned_nodes; i++) { const uint16_t key_version = (uint16_t)resp.data.asBytes[offset + i * 2] | ((uint16_t)resp.data.asBytes[offset + i * 2 + 1] << 8); - if (key_version == 0xFFFF) { + if (key_version == 0xFFFF && node_types[i] != FELICA_NODE_TYPE_SYSTEM) { continue; } felica_discovered_node_t node = {0}; - node.is_area = is_area_nodes[i]; + node.type = node_types[i]; node.node_code_le = node_codes_le[i]; node.has_end_code = false; node.end_code_le = 0; @@ -3387,7 +3440,7 @@ static bool felica_discover_nodes_with_request_service(uint8_t *flags, uint32_t local_count = 0; uint16_t batch_codes[FELICA_REQUEST_SERVICE_DISCOVERY_BATCH_SIZE] = {0}; - bool batch_is_area[FELICA_REQUEST_SERVICE_DISCOVERY_BATCH_SIZE] = {0}; + felica_node_type_t batch_types[FELICA_REQUEST_SERVICE_DISCOVERY_BATCH_SIZE] = {0}; size_t batch_count = 0; for (uint16_t number = 0; number <= FELICA_MAX_NODE_NUMBER; number++) { @@ -3405,14 +3458,17 @@ static bool felica_discover_nodes_with_request_service(uint8_t *flags, } const uint16_t node_code_le = (uint16_t)((number << 6) | probe_attr.attribute); + if (node_code_le == FELICA_SYSTEM_NODE) { + continue; + } batch_codes[batch_count] = node_code_le; - batch_is_area[batch_count] = probe_attr.is_area; + batch_types[batch_count] = felica_node_type_from_code(node_code_le); batch_count++; if (batch_count == FELICA_REQUEST_SERVICE_DISCOVERY_BATCH_SIZE) { if (felica_request_service_send_probe_batch(flags, idm, retry_count, - batch_codes, batch_is_area, batch_count, + batch_codes, batch_types, batch_count, visitor, ctx, &supported, &local_count, stop_status) == false) { felica_set_discovered_count(discovered_count, local_count); if (stop_status && *stop_status == PM3_EOPABORTED) { @@ -3428,9 +3484,13 @@ static bool felica_discover_nodes_with_request_service(uint8_t *flags, } } + batch_codes[batch_count] = FELICA_SYSTEM_NODE; + batch_types[batch_count] = FELICA_NODE_TYPE_SYSTEM; + batch_count++; + if (batch_count > 0) { if (felica_request_service_send_probe_batch(flags, idm, retry_count, - batch_codes, batch_is_area, batch_count, + batch_codes, batch_types, batch_count, visitor, ctx, &supported, &local_count, stop_status) == false) { felica_set_discovered_count(discovered_count, local_count); if (stop_status && *stop_status == PM3_EOPABORTED) { @@ -3517,7 +3577,7 @@ static bool felica_discover_nodes_with_read_without_encryption_ex(uint8_t *flags } felica_discovered_node_t node = {0}; - node.is_area = false; + node.type = FELICA_NODE_TYPE_SERVICE; node.node_code_le = service_code_le; node.has_end_code = false; node.end_code_le = 0; @@ -3699,8 +3759,8 @@ static int felica_compare_discovered_nodes(const void *lhs, const void *rhs) { return 1; } - if (a->is_area != b->is_area) { - return a->is_area ? -1 : 1; + if (a->type != b->type) { + return a->type < b->type ? -1 : 1; } if (a->has_end_code != b->has_end_code) { @@ -3742,7 +3802,9 @@ static int felica_scsvcode_discovery_visitor(const felica_discovered_node_t *nod const uint16_t node_code_be = felica_to_network_order(node->node_code_le); const uint16_t node_number = node->node_code_le >> 6; - if (node->is_area) { + if (node->type == FELICA_NODE_TYPE_SYSTEM) { + PrintAndLogEx(INFO, "%sSYSTEM_%04X", prefix, node_code_be); + } else if (node->type == FELICA_NODE_TYPE_AREA) { scsv_ctx->area_count++; if (node->has_end_code) { const uint16_t end_code_be = felica_to_network_order(node->end_code_le); @@ -3816,7 +3878,7 @@ static felica_dump_node_t *felica_dump_system_add_node(felica_dump_system_t *sys for (size_t i = 0; i < system->node_count; i++) { felica_dump_node_t *dump_node = &system->nodes[i]; - if (dump_node->is_area == node->is_area && + if (dump_node->type == node->type && dump_node->node_code_le == node->node_code_le && dump_node->has_end_code == node->has_end_code && dump_node->end_code_le == node->end_code_le) { @@ -3842,11 +3904,10 @@ static felica_dump_node_t *felica_dump_system_add_node(felica_dump_system_t *sys felica_dump_node_t *dump_node = &system->nodes[system->node_count++]; memset(dump_node, 0, sizeof(*dump_node)); - dump_node->is_area = node->is_area; + dump_node->type = node->type; dump_node->node_code_le = node->node_code_le; dump_node->has_end_code = node->has_end_code; dump_node->end_code_le = node->end_code_le; - return dump_node; } @@ -3987,14 +4048,21 @@ static json_t *felica_dump_node_json(const felica_dump_node_t *node) { char node_code_hex[9] = {0}; const uint16_t node_code_be = felica_to_network_order(node->node_code_le); - if (node->is_area && node->has_end_code) { + if (node->type == FELICA_NODE_TYPE_AREA && node->has_end_code) { const uint16_t end_code_be = felica_to_network_order(node->end_code_le); snprintf(node_code_hex, sizeof(node_code_hex), "%04X%04X", node_code_be, end_code_be); } else { snprintf(node_code_hex, sizeof(node_code_hex), "%04X", node_code_be); } - if (felica_dump_json_set_new(node_json, "type", json_string(node->is_area ? "area" : "service")) != PM3_SUCCESS || + const char *node_type = "service"; + if (node->type == FELICA_NODE_TYPE_SYSTEM) { + node_type = "system"; + } else if (node->type == FELICA_NODE_TYPE_AREA) { + node_type = "area"; + } + + if (felica_dump_json_set_new(node_json, "type", json_string(node_type)) != PM3_SUCCESS || felica_dump_json_set_new(node_json, "code", json_string(node_code_hex)) != PM3_SUCCESS || felica_dump_json_set_new(node_json, "des_key_version", node->des_key_version.has_value ? felica_dump_key_version_json(node->des_key_version.value_le) : json_null()) != PM3_SUCCESS || felica_dump_json_set_new(node_json, "aes_key_version", node->aes_key_version.has_value ? felica_dump_key_version_json(node->aes_key_version.value_le) : json_null()) != PM3_SUCCESS) { @@ -4062,7 +4130,14 @@ static json_t *felica_dump_system_json(const felica_dump_system_t *system) { } for (size_t i = 0; i < system->node_count; i++) { - json_t *node_json = felica_dump_node_json(&system->nodes[i]); + const felica_dump_node_t *node = &system->nodes[i]; + if (node->type == FELICA_NODE_TYPE_SYSTEM && + node->des_key_version.has_value == false && + node->aes_key_version.has_value == false) { + continue; + } + + json_t *node_json = felica_dump_node_json(node); if (node_json == NULL) { json_decref(nodes_json); json_decref(system_json); @@ -4262,7 +4337,7 @@ static int felica_dump_discovery_visitor(const felica_discovered_node_t *node, v } } - if (node->is_area) { + if (node->type != FELICA_NODE_TYPE_SERVICE) { return PM3_SUCCESS; } @@ -4378,26 +4453,32 @@ static int felica_dump_single_system(const felica_discovered_system_t *system, dump_ctx.block_frame[14] = 0x80; uint32_t discovered_nodes = 0; - const int ret = felica_discover_nodes(idm, pmm, &flags, retry_count, - FELICA_NODE_DISCOVERY_NONE, - felica_dump_discovery_visitor, &dump_ctx, - NULL, &discovered_nodes); - - *discovered_nodes_out = discovered_nodes; - *service_count_out = dump_ctx.service_count; - *public_service_count_out = dump_ctx.public_service_count; + int ret = felica_discover_nodes(idm, pmm, &flags, retry_count, + FELICA_NODE_DISCOVERY_NONE, + felica_dump_discovery_visitor, &dump_ctx, + NULL, &discovered_nodes); if (ret == PM3_EOPABORTED) { + *discovered_nodes_out = discovered_nodes; + *service_count_out = dump_ctx.service_count; + *public_service_count_out = dump_ctx.public_service_count; PrintAndLogEx(WARNING, "Unauth service dump aborted by user. Discovered %" PRIu32 " node(s), visited %" PRIu32 " service(s), dumped %" PRIu32 " public service(s).", discovered_nodes, dump_ctx.service_count, dump_ctx.public_service_count); return ret; } if (ret != PM3_SUCCESS) { + *discovered_nodes_out = discovered_nodes; + *service_count_out = dump_ctx.service_count; + *public_service_count_out = dump_ctx.public_service_count; PrintAndLogEx(FAILED, "Unable to discover nodes using RequestCodeList/SearchServiceCode/RequestService/ReadWithoutEncryption."); return ret; } + *discovered_nodes_out = discovered_nodes; + *service_count_out = dump_ctx.service_count; + *public_service_count_out = dump_ctx.public_service_count; + PrintAndLogEx(SUCCESS, "System dump complete. Discovered %" PRIu32 " node(s), visited %" PRIu32 " service(s), dumped %" PRIu32 " public service(s).", discovered_nodes, dump_ctx.service_count, dump_ctx.public_service_count); From 35869128e5965ee6d741676eebe26a4fb93504f6 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:28:01 +0300 Subject: [PATCH 2/4] Add specification version to 'hf felica dump' --- client/src/cmdhffelica.c | 88 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 39f254183..32f1d7712 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -56,6 +56,7 @@ #define FELICA_PLATFORM_INFO_WITH_MAC_LEN 20U #define FELICA_PLATFORM_INFO_WITH_MAC_TOTAL_LEN (FELICA_PLATFORM_INFO_WITH_MAC_INFO_LEN + FELICA_PLATFORM_INFO_WITH_MAC_LEN) #define FELICA_CONTAINER_PROPERTY_MAX_LEN 64U +#define FELICA_SPECIFICATION_VERSION_MAX_LEN (4U + (FELICA_SPECIFICATION_VERSION_MAX_OPTIONS * 2U)) #define FELICA_OPTIONAL_CMD_TIMEOUT_MS 250U #define FELICA_OPTIONAL_CMD_RETRIES 3U // Per FeliCa spec, Polling max response at 16 timeslots is ~25ms; keep extra margin. @@ -307,6 +308,12 @@ typedef struct { size_t node_capacity; } felica_dump_system_t; +typedef struct { + bool has_specification_version; + uint8_t specification_version[FELICA_SPECIFICATION_VERSION_MAX_LEN]; + size_t specification_version_len; +} felica_dump_metadata_t; + static uint8_t felica_node_attribute(uint16_t node_code_le) { return node_code_le & FELICA_NODE_ATTRIBUTE_MASK; } @@ -4033,6 +4040,18 @@ static int felica_dump_json_set_new(json_t *object, const char *key, json_t *val return (ret == 0) ? PM3_SUCCESS : PM3_EMALLOC; } +static int felica_dump_json_set_hex_or_null(json_t *object, const char *key, + const uint8_t *data, size_t data_len, bool has_value) { + if (has_value == false) { + return felica_dump_json_set_new(object, key, json_null()); + } + if (data == NULL) { + return PM3_EINVARG; + } + + return felica_dump_json_set_new(object, key, json_string(sprint_hex_inrow(data, data_len))); +} + static json_t *felica_dump_node_json(const felica_dump_node_t *node) { if (node == NULL) { return NULL; @@ -4162,7 +4181,8 @@ static json_t *felica_dump_system_json(const felica_dump_system_t *system) { return system_json; } -static json_t *felica_dump_systems_json(const felica_dump_system_t *systems, size_t system_count) { +static json_t *felica_dump_systems_json(const felica_dump_system_t *systems, size_t system_count, + const felica_dump_metadata_t *metadata) { if (systems == NULL) { return NULL; } @@ -4175,6 +4195,15 @@ static json_t *felica_dump_systems_json(const felica_dump_system_t *systems, siz return NULL; } + if (felica_dump_json_set_hex_or_null(root, "specification_version", + metadata ? metadata->specification_version : NULL, + metadata ? metadata->specification_version_len : 0, + metadata && metadata->has_specification_version) != PM3_SUCCESS) { + json_decref(systems_json); + json_decref(root); + return NULL; + } + for (size_t i = 0; i < system_count; i++) { json_t *system_json = felica_dump_system_json(&systems[i]); if (system_json == NULL) { @@ -4201,6 +4230,58 @@ static json_t *felica_dump_systems_json(const felica_dump_system_t *systems, siz return root; } +static bool felica_dump_specification_version_to_bytes(const felica_request_specification_version_info_t *info, + uint8_t *data, size_t data_capacity, size_t *data_len) { + if (info == NULL || data == NULL || data_len == NULL || info->has_specification_version == false) { + return false; + } + + *data_len = 0; + if (info->option_version_count < info->number_of_option) { + return false; + } + + const size_t option_bytes = (size_t)info->number_of_option * 2U; + const size_t payload_len = 4U + option_bytes; + if (payload_len > data_capacity || option_bytes > sizeof(info->option_version_list)) { + return false; + } + + data[0] = info->format_version; + memcpy(data + 1, info->basic_version, sizeof(info->basic_version)); + data[3] = info->number_of_option; + memcpy(data + 4, info->option_version_list, option_bytes); + *data_len = payload_len; + return true; +} + +static void felica_dump_collect_metadata(const uint8_t *idm, felica_dump_metadata_t *metadata) { + if (idm == NULL || metadata == NULL) { + return; + } + + memset(metadata, 0, sizeof(*metadata)); + const uint8_t optional_flags = FELICA_NO_DISCONNECT | FELICA_APPEND_CRC | FELICA_RAW; + + felica_request_specification_version_request_t spec_request; + memset(&spec_request, 0, sizeof(spec_request)); + spec_request.length[0] = sizeof(spec_request); + spec_request.command_code[0] = FELICA_REQUEST_SPEC_VERSION_REQ; + memcpy(spec_request.IDm, idm, sizeof(spec_request.IDm)); + + felica_request_specification_version_info_t spec_info; + if (send_request_specification_version(optional_flags, sizeof(spec_request), + (uint8_t *)&spec_request, false, false, + FELICA_OPTIONAL_CMD_TIMEOUT_MS, FELICA_OPTIONAL_CMD_RETRIES, + &spec_info) == PM3_SUCCESS && + felica_dump_specification_version_to_bytes(&spec_info, + metadata->specification_version, + sizeof(metadata->specification_version), + &metadata->specification_version_len)) { + metadata->has_specification_version = true; + } +} + static int felica_dump_collect_key_versions_v2(felica_dump_system_t *system, uint8_t flags, uint32_t retry_count) { if (system == NULL) { return PM3_EINVARG; @@ -5541,6 +5622,9 @@ static int CmdHFFelicaDump(const char *Cmd) { PrintAndLogEx(INFO, "Press " _GREEN_("") " to abort discovery or dumping"); + felica_dump_metadata_t dump_metadata; + felica_dump_collect_metadata(idm, &dump_metadata); + uint8_t system_flags = FELICA_NO_DISCONNECT | FELICA_APPEND_CRC | FELICA_RAW; felica_discovered_system_list_t discovered_systems; res = discover_systems(system_flags, idm, true, retry_count, &discovered_systems); @@ -5601,7 +5685,7 @@ static int CmdHFFelicaDump(const char *Cmd) { snprintf(filename, sizeof(filename), "hf-felica-%s-dump", sprint_hex_inrow(idm, sizeof(idm))); } - json_t *root = felica_dump_systems_json(dump_systems, dump_system_count); + json_t *root = felica_dump_systems_json(dump_systems, dump_system_count, &dump_metadata); if (root == NULL) { felica_dump_systems_free(dump_systems, dump_system_count); return PM3_EMALLOC; From 3e86f399782058a4388fa778c91b8614fe42e3e0 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:35:01 +0300 Subject: [PATCH 3/4] Add product information to 'hf felica dump' --- client/src/cmdhffelica.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 32f1d7712..46a9af790 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -312,6 +312,9 @@ typedef struct { bool has_specification_version; uint8_t specification_version[FELICA_SPECIFICATION_VERSION_MAX_LEN]; size_t specification_version_len; + bool has_product_information; + uint8_t product_information[FELICA_PLATFORM_INFO_MAX_LEN]; + size_t product_information_len; } felica_dump_metadata_t; static uint8_t felica_node_attribute(uint16_t node_code_le) { @@ -4198,7 +4201,11 @@ static json_t *felica_dump_systems_json(const felica_dump_system_t *systems, siz if (felica_dump_json_set_hex_or_null(root, "specification_version", metadata ? metadata->specification_version : NULL, metadata ? metadata->specification_version_len : 0, - metadata && metadata->has_specification_version) != PM3_SUCCESS) { + metadata && metadata->has_specification_version) != PM3_SUCCESS || + felica_dump_json_set_hex_or_null(root, "product_information", + metadata ? metadata->product_information : NULL, + metadata ? metadata->product_information_len : 0, + metadata && metadata->has_product_information) != PM3_SUCCESS) { json_decref(systems_json); json_decref(root); return NULL; @@ -4280,6 +4287,23 @@ static void felica_dump_collect_metadata(const uint8_t *idm, felica_dump_metadat &metadata->specification_version_len)) { metadata->has_specification_version = true; } + + felica_get_platform_info_request_t product_request; + memset(&product_request, 0, sizeof(product_request)); + product_request.length[0] = sizeof(product_request); + product_request.command_code[0] = FELICA_GETPLATFORMINFO_REQ; + memcpy(product_request.IDm, idm, sizeof(product_request.IDm)); + + felica_status_flags_t product_status_flags; + if (send_get_platform_information(optional_flags, + sizeof(product_request), (uint8_t *)&product_request, + false, &product_status_flags, + metadata->product_information, + sizeof(metadata->product_information), + &metadata->product_information_len) == PM3_SUCCESS && + metadata->product_information_len > 0) { + metadata->has_product_information = true; + } } static int felica_dump_collect_key_versions_v2(felica_dump_system_t *system, uint8_t flags, uint32_t retry_count) { From 0f2a56cdec1151989852e800d9929097defd4484 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Fri, 12 Jun 2026 05:45:01 +0300 Subject: [PATCH 4/4] Add container issue information to 'hf felica dump' --- client/src/cmdhffelica.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhffelica.c b/client/src/cmdhffelica.c index 46a9af790..880dea6b4 100644 --- a/client/src/cmdhffelica.c +++ b/client/src/cmdhffelica.c @@ -55,6 +55,7 @@ #define FELICA_PLATFORM_INFO_WITH_MAC_INFO_LEN 25U #define FELICA_PLATFORM_INFO_WITH_MAC_LEN 20U #define FELICA_PLATFORM_INFO_WITH_MAC_TOTAL_LEN (FELICA_PLATFORM_INFO_WITH_MAC_INFO_LEN + FELICA_PLATFORM_INFO_WITH_MAC_LEN) +#define FELICA_CONTAINER_ISSUE_INFO_LEN 16U #define FELICA_CONTAINER_PROPERTY_MAX_LEN 64U #define FELICA_SPECIFICATION_VERSION_MAX_LEN (4U + (FELICA_SPECIFICATION_VERSION_MAX_OPTIONS * 2U)) #define FELICA_OPTIONAL_CMD_TIMEOUT_MS 250U @@ -315,6 +316,8 @@ typedef struct { bool has_product_information; uint8_t product_information[FELICA_PLATFORM_INFO_MAX_LEN]; size_t product_information_len; + bool has_container_issue_information; + uint8_t container_issue_information[FELICA_CONTAINER_ISSUE_INFO_LEN]; } felica_dump_metadata_t; static uint8_t felica_node_attribute(uint16_t node_code_le) { @@ -4205,7 +4208,11 @@ static json_t *felica_dump_systems_json(const felica_dump_system_t *systems, siz felica_dump_json_set_hex_or_null(root, "product_information", metadata ? metadata->product_information : NULL, metadata ? metadata->product_information_len : 0, - metadata && metadata->has_product_information) != PM3_SUCCESS) { + metadata && metadata->has_product_information) != PM3_SUCCESS || + felica_dump_json_set_hex_or_null(root, "container_issue_information", + metadata ? metadata->container_issue_information : NULL, + metadata ? sizeof(metadata->container_issue_information) : 0, + metadata && metadata->has_container_issue_information) != PM3_SUCCESS) { json_decref(systems_json); json_decref(root); return NULL; @@ -4304,6 +4311,25 @@ static void felica_dump_collect_metadata(const uint8_t *idm, felica_dump_metadat metadata->product_information_len > 0) { metadata->has_product_information = true; } + + felica_get_container_issue_info_request_t container_issue_request; + memset(&container_issue_request, 0, sizeof(container_issue_request)); + container_issue_request.length[0] = sizeof(container_issue_request); + container_issue_request.command_code[0] = FELICA_GET_CONTAINER_ISSUE_INFO_REQ; + memcpy(container_issue_request.IDm, idm, sizeof(container_issue_request.IDm)); + + felica_get_container_issue_info_response_t container_issue_response; + if (send_get_container_issue_information(optional_flags, + sizeof(container_issue_request), (uint8_t *)&container_issue_request, + false, &container_issue_response) == PM3_SUCCESS) { + memcpy(metadata->container_issue_information, + container_issue_response.format_version_carrier_information, + sizeof(container_issue_response.format_version_carrier_information)); + memcpy(metadata->container_issue_information + sizeof(container_issue_response.format_version_carrier_information), + container_issue_response.mobile_phone_model_information, + sizeof(container_issue_response.mobile_phone_model_information)); + metadata->has_container_issue_information = true; + } } static int felica_dump_collect_key_versions_v2(felica_dump_system_t *system, uint8_t flags, uint32_t retry_count) {