Merge pull request #401 from nieldk/t55write

T55xx clone
This commit is contained in:
GameTec-live
2026-04-19 15:18:46 +02:00
committed by GitHub
17 changed files with 2497 additions and 320 deletions
+1
View File
@@ -28,6 +28,7 @@ SRC_FILES += \
$(PROJ_DIR)/rfid/nfctag/tag_persistence.c \
$(PROJ_DIR)/rfid/nfctag/hf/crypto1_helper.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_14a_4.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf1.c \
$(PROJ_DIR)/rfid/nfctag/hf/nfc_mf0_ntag.c \
$(PROJ_DIR)/rfid/nfctag/lf/lf_tag_em.c \
File diff suppressed because it is too large Load Diff
+10
View File
@@ -67,6 +67,7 @@
#define DATA_CMD_MF1_READ_ONE_BLOCK (2008)
#define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009)
#define DATA_CMD_HF14A_RAW (2010)
#define DATA_CMD_HF14A_SCAN_KEEP (2016) /* scan+RATS, keep field alive for APDU exchange */
#define DATA_CMD_MF1_MANIPULATE_VALUE_BLOCK (2011)
#define DATA_CMD_MF1_CHECK_KEYS_OF_SECTORS (2012)
#define DATA_CMD_MF1_HARDNESTED_ACQUIRE (2013)
@@ -105,6 +106,7 @@
#define DATA_CMD_IOPROX_WRITE_TO_T55XX (3011)
#define DATA_CMD_IOPROX_DECODE_RAW (3012)
#define DATA_CMD_IOPROX_COMPOSE_ID (3013)
#define DATA_CMD_LF_T55XX_WRITE (3016)
//
// ******************************************************************
@@ -167,6 +169,14 @@
//
// ******************************************************************
/* ISO14443-4 T=CL emulation commands */
#define DATA_CMD_HF14A_4_APDU_RECV (6000) /* non-blocking poll: firmware->host APDU */
#define DATA_CMD_HF14A_4_APDU_SEND (6001) /* host->firmware APDU response */
#define DATA_CMD_HF14A_4_SET_ANTI_COLL (6002) /* set UID/ATQA/SAK/ATS */
#define DATA_CMD_HF14A_4_STATIC_RESP (6003) /* add/clear static APDU response pair */
#define DATA_CMD_HF14A_4_READER_APDU (6004) /* select+RATS+send APDU, keep field */
#define DATA_CMD_HF14A_4_EMV_SCAN (6005) /* full EMV scan in one call */
#define DATA_CMD_EM410X_SET_EMU_ID (5000)
#define DATA_CMD_EM410X_GET_EMU_ID (5001)
#define DATA_CMD_HIDPROX_SET_EMU_ID (5002)
@@ -70,6 +70,25 @@ void nfc_tag_14a_set_sniff_cb(nfc_tag_14a_sniff_cb_t cb) {
void nfc_tag_14a_clear_sniff_cb(void) {
m_sniff_cb = NULL;
}
/* TX sniff: captures card→reader frames at TX_FRAMESTART */
static nfc_tag_14a_tx_sniff_cb_t m_tx_sniff_cb = NULL;
void nfc_tag_14a_set_tx_sniff_cb(nfc_tag_14a_tx_sniff_cb_t cb) {
m_tx_sniff_cb = cb;
}
void nfc_tag_14a_clear_tx_sniff_cb(void) {
m_tx_sniff_cb = NULL;
}
/* Passive sniff mode: suppress all tag TX responses so the CU does not
* participate in anticollision and avoids colliding with the real card. */
static bool m_sniff_passive = false;
void nfc_tag_14a_set_sniff_passive(bool passive) {
m_sniff_passive = passive;
}
static uint8_t m_nfc_tx_buffer[MAX_NFC_TX_BUFFER_SIZE] = { 0x00 };
// The N -secondary connection needs to use SAK, when the "third 'bit' in SAK is 1 is 1, the logo UID is incomplete
static uint8_t m_uid_incomplete_sak[] = { 0x04, 0xda, 0x17 };
@@ -366,9 +385,11 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
if (auto_coll_res != NULL) {
// The status machine is set to the preparation state, and the next operation is to enter the card selection link
m_tag_state_14a = NFC_TAG_STATE_14A_READY;
// After receiving the WUPA or REQA instruction, we need to reply to ATQA
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
// NRF_LOG_INFO("ATQA reply: %02x%02x", auto_coll_res->atqa[0], auto_coll_res->atqa[1]);
if (!m_sniff_passive) {
// After receiving the WUPA or REQA instruction, we need to reply to ATQA
nfc_tag_14a_tx_bytes(auto_coll_res->atqa, 2, false);
// NRF_LOG_INFO("ATQA reply: %02x%02x", auto_coll_res->atqa[0], auto_coll_res->atqa[1]);
}
} else {
m_tag_state_14a = NFC_TAG_STATE_14A_IDLE;
NRF_LOG_INFO("Auto anti-collision resource no exists.");
@@ -484,7 +505,9 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
}
// Incoming SELECT ALL for any cascade level
if (szDataBits == 16 && p_data[1] == 0x20) {
nfc_tag_14a_tx_bytes(uid, 5, false);
if (!m_sniff_passive) {
nfc_tag_14a_tx_bytes(uid, 5, false);
}
// NRF_LOG_INFO("[MFEMUL_SELECT] SEL Reply.");
break;
}
@@ -498,10 +521,14 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
if (cl_finished) {
// NRF_LOG_INFO("[MFEMUL_SELECT] m_tag_state_14a = MFEMUL_WORK");
m_tag_state_14a = NFC_TAG_STATE_14A_ACTIVE;
nfc_tag_14a_tx_bytes(auto_coll_res->sak, 1, true);
if (!m_sniff_passive) {
nfc_tag_14a_tx_bytes(auto_coll_res->sak, 1, true);
}
} else {
// It is necessary to continue the level, so we need to respond to a data that marks the incomplete UID in SAK
nfc_tag_14a_tx_bytes(m_uid_incomplete_sak, 3, false);
if (!m_sniff_passive) {
nfc_tag_14a_tx_bytes(m_uid_incomplete_sak, 3, false);
}
}
} else {
// IDLE, not our UID
@@ -527,6 +554,10 @@ void nfc_tag_14a_data_process(uint8_t *p_data) {
}
// RATS instruction
if (p_data[0] == NFC_TAG_14A_CMD_RATS && nfc_tag_14a_checks_crc(p_data, 4)) {
// Reset T=CL layer state for the new session
if (m_tag_handler.cb_reset != NULL) {
m_tag_handler.cb_reset();
}
// Make sure the sub -packaging opens the support of ATS
if (auto_coll_res->ats->length > 0) {
// Take out FSD and return according to the maximum FSD
@@ -571,11 +602,10 @@ static inline void nrf_nfct_reset(void) {
// Use Window Grid frame delay mode.
nrf_nfct_frame_delay_mode_set(NRF_NFCT_FRAME_DELAY_MODE_WINDOWGRID);
/* Begin: Workaround for anomaly 25 */
/* Workaround for wrong SENSRES values require using SDD00001, but here SDD00100 is used
because it is required to operate with Windows Phone */
nrf_nfct_sensres_bit_frame_sdd_set(NRF_NFCT_SENSRES_BIT_FRAME_SDD_00100);
/* End: Workaround for anomaly 25 */
/* Use SDD00001 per ISO14443-3 standard.
* Note: SDD00100 was previously used for Windows Phone compatibility
* but breaks standard readers (including Proxmark3). SDD00001 is correct. */
nrf_nfct_sensres_bit_frame_sdd_set(NRF_NFCT_SENSRES_BIT_FRAME_SDD_00001);
// Restore interrupts.
nrf_nfct_int_enable(int_enabled);
@@ -648,7 +678,19 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) {
}
case NRFX_NFCT_EVT_TX_FRAMESTART: {
// NRF_LOG_INFO("TX start.\n");
// NRF_LOG_INFO("TX config is %d.\n", nrf_nfct_tx_frame_config_get(NRF_NFCT));
if (m_tx_sniff_cb != NULL) {
uint32_t amt = NRF_NFCT->TXD.AMOUNT;
uint16_t tx_bytes = (amt >> NFCT_TXD_AMOUNT_TXDATABYTES_Pos)
& (NFCT_TXD_AMOUNT_TXDATABYTES_Msk >> NFCT_TXD_AMOUNT_TXDATABYTES_Pos);
uint16_t tx_bits_rem = (amt >> NFCT_TXD_AMOUNT_TXDATABITS_Pos)
& (NFCT_TXD_AMOUNT_TXDATABITS_Msk >> NFCT_TXD_AMOUNT_TXDATABITS_Pos);
uint16_t tx_bits = (tx_bits_rem > 0)
? ((tx_bytes - 1) * 8 + tx_bits_rem)
: (tx_bytes * 8);
if (tx_bits > 0 && tx_bytes <= MAX_NFC_TX_BUFFER_SIZE) {
m_tx_sniff_cb(m_nfc_tx_buffer, tx_bits);
}
}
break;
}
case NRFX_NFCT_EVT_TX_FRAMEEND: {
@@ -4,7 +4,7 @@
#include "tag_emulation.h"
#define MAX_NFC_RX_BUFFER_SIZE 257
#define MAX_NFC_TX_BUFFER_SIZE 64
#define MAX_NFC_TX_BUFFER_SIZE 512 /* must hold PCB + max APDU response */
#define NFC_TAG_14A_CRC_LENGTH 2
@@ -90,6 +90,19 @@ typedef void (*nfc_tag_14a_sniff_cb_t)(const uint8_t *data, uint16_t szBits);
void nfc_tag_14a_set_sniff_cb(nfc_tag_14a_sniff_cb_t cb);
void nfc_tag_14a_clear_sniff_cb(void);
/* TX sniff callback — fires at TX_FRAMESTART with the frame the tag is about
* to send (cardreader direction). Same signature as the RX sniff callback.
* Install alongside nfc_tag_14a_set_sniff_cb() to capture both directions. */
typedef void (*nfc_tag_14a_tx_sniff_cb_t)(const uint8_t *data, uint16_t szBits);
void nfc_tag_14a_set_tx_sniff_cb(nfc_tag_14a_tx_sniff_cb_t cb);
void nfc_tag_14a_clear_tx_sniff_cb(void);
/* Passive sniff mode: when true, suppresses all CU anticollision responses
* (ATQA, UID, SAK) so the CU does not collide with real cards in the field.
* Enable before starting a sniff session, disable on completion. */
void nfc_tag_14a_set_sniff_passive(bool passive);
typedef void (*nfc_tag_14a_state_handler_t)(uint8_t *data, uint16_t szBits);
typedef nfc_tag_14a_coll_res_reference_t *(*nfc_tag_14a_coll_handler_t)(void);
@@ -0,0 +1,446 @@
/**
* @file nfc_14a_4.c
* @brief ISO14443-4 T=CL emulation for ChameleonUltra
*
* Implements a full ISO14443-4 tag emulator with a static APDU response
* table. The table is populated by the host before field activation, so
* the firmware can respond to an EMV reader autonomously without any USB
* communication while the RF field is active.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <string.h>
#include "nfc_14a_4.h"
#include "nfc_14a.h"
#include "tag_emulation.h"
#include "tag_persistence.h"
#include "fds_util.h"
#include "nrf_log.h"
/* ------------------------------------------------------------------ */
/* PCB byte constants (ISO14443-4 §7) */
/* ------------------------------------------------------------------ */
#define PCB_IBLOCK_MASK 0xC0
#define PCB_IBLOCK_VAL 0x00
#define PCB_RBLOCK_MASK 0xE0
#define PCB_RBLOCK_VAL 0x80 /* R(ACK) = 0xA2/0xA3, R(NAK) = 0xB2/0xB3 */
#define PCB_SBLOCK_MASK 0xC0
#define PCB_SBLOCK_VAL 0xC0
#define PCB_BLOCK_NUM 0x01
#define PCB_CID_FOLLOWING 0x10 /* bit4: CID follows */
#define PCB_NAD_FOLLOWING 0x08 /* bit3: NAD follows */
#define PCB_CHAIN 0x20 /* bit5: chaining flag per ISO14443-4 Table 3 */
#define PCB_SBLOCK_WTX 0x30
#define PCB_SBLOCK_DESELECT 0xC2
#define WTX_VALUE 0x3B /* WTXM=59 (~3s extra wait) */
static inline bool is_iblock(uint8_t pcb) {
return (pcb & PCB_IBLOCK_MASK) == PCB_IBLOCK_VAL;
}
static inline bool is_rblock(uint8_t pcb) {
/* R-block: bit7=1, bit6=0, bit2=1, bit1=0 (mask 0xC6, value 0x82) */
return (pcb & 0xC6) == 0x82;
}
static inline bool is_sblock(uint8_t pcb) {
return (pcb & PCB_SBLOCK_MASK) == PCB_SBLOCK_VAL;
}
/* ------------------------------------------------------------------ */
/* Module state */
/* ------------------------------------------------------------------ */
static nfc_tag_14a_4_information_t *m_tag_information = NULL;
/* Shadow coll-res references into m_tag_information */
static nfc_tag_14a_coll_res_reference_t m_shadow_coll_res;
/* T=CL session state */
static uint8_t m_block_num = 0;
static bool m_cid_supported = false;
static uint8_t m_cid = 0;
static uint8_t m_apdu_buf[NFC_14A_4_MAX_APDU];
static uint16_t m_apdu_len = 0;
static bool m_apdu_pending = false;
static uint8_t m_resp_buf[NFC_14A_4_MAX_APDU];
static uint16_t m_resp_len = 0;
static bool m_response_ready = false;
/* TX scratch buffer */
static uint8_t m_tx_buf[NFC_14A_4_MAX_APDU + 4];
/* Debug counters — readable via hf 14a debug */
static uint8_t m_dbg_iblocks_rx = 0; /* I-blocks received */
static uint8_t m_dbg_iblocks_tx = 0; /* I-blocks sent */
static uint8_t m_dbg_last_rx_pcb = 0; /* PCB of last received I-block */
static uint8_t m_dbg_last_match = 0; /* last find_static_response result */
/* Static APDU response table (RAM copy, populated from m_tag_information) */
static nfc_tag_14a_4_static_response_t m_static_resp[NFC_14A_4_MAX_STATIC_RESPONSES];
static uint8_t m_static_resp_count = 0;
/* Large response overflow (RAM only, > NFC_14A_4_MAX_STATIC_RESP_LEN bytes).
* NOT persisted to flash. Must reload via emv load after power cycle. */
typedef struct {
uint8_t cmd[NFC_14A_4_MAX_STATIC_CMD_LEN];
uint8_t cmd_len;
uint8_t resp[NFC_14A_4_MAX_LARGE_RESP_LEN];
uint16_t resp_len;
} nfc_tag_14a_4_large_response_t;
static nfc_tag_14a_4_large_response_t m_large_resp[NFC_14A_4_MAX_LARGE_RESPONSES];
static uint8_t m_large_resp_count = 0;
/* ------------------------------------------------------------------ */
/* Static response table */
/* ------------------------------------------------------------------ */
void nfc_tag_14a_4_add_static_response(const uint8_t *cmd, uint8_t cmd_len,
const uint8_t *resp, uint16_t resp_len) {
if (cmd_len > NFC_14A_4_MAX_STATIC_CMD_LEN) cmd_len = NFC_14A_4_MAX_STATIC_CMD_LEN;
if (resp_len > NFC_14A_4_MAX_STATIC_RESP_LEN) {
/* Large response: RAM-only overflow table */
if (m_large_resp_count >= NFC_14A_4_MAX_LARGE_RESPONSES) return;
if (resp_len > NFC_14A_4_MAX_LARGE_RESP_LEN) resp_len = NFC_14A_4_MAX_LARGE_RESP_LEN;
nfc_tag_14a_4_large_response_t *le = &m_large_resp[m_large_resp_count++];
le->cmd_len = cmd_len;
le->resp_len = resp_len;
memcpy(le->cmd, cmd, cmd_len);
memcpy(le->resp, resp, resp_len);
return;
}
/* Normal response: flash-backed table */
if (m_static_resp_count >= NFC_14A_4_MAX_STATIC_RESPONSES) return;
nfc_tag_14a_4_static_response_t *e = &m_static_resp[m_static_resp_count++];
e->cmd_len = cmd_len;
e->resp_len = (uint8_t)resp_len;
memcpy(e->cmd, cmd, cmd_len);
memcpy(e->resp, resp, resp_len);
if (m_tag_information &&
m_tag_information->static_resp_count < NFC_14A_4_MAX_STATIC_RESPONSES) {
memcpy(&m_tag_information->static_resp[m_tag_information->static_resp_count++],
e, sizeof(*e));
}
}
void nfc_tag_14a_4_clear_static_responses(void) {
m_static_resp_count = 0;
m_large_resp_count = 0;
if (m_tag_information) {
m_tag_information->static_resp_count = 0;
}
}
static bool find_static_response(const uint8_t *apdu, uint16_t apdu_len,
uint8_t **resp_out, uint16_t *resp_len_out) {
/* Flash-backed table */
for (uint8_t i = 0; i < m_static_resp_count; i++) {
nfc_tag_14a_4_static_response_t *e = &m_static_resp[i];
if (apdu_len >= e->cmd_len &&
memcmp(apdu, e->cmd, e->cmd_len) == 0) {
*resp_out = e->resp;
*resp_len_out = e->resp_len;
return true;
}
}
/* RAM-only large response table */
for (uint8_t i = 0; i < m_large_resp_count; i++) {
nfc_tag_14a_4_large_response_t *e = &m_large_resp[i];
if (apdu_len >= e->cmd_len &&
memcmp(apdu, e->cmd, e->cmd_len) == 0) {
*resp_out = e->resp;
*resp_len_out = e->resp_len;
return true;
}
}
return false;
}
/* ------------------------------------------------------------------ */
/* TX helpers */
/* ------------------------------------------------------------------ */
static void send_iblock(const uint8_t *data, uint16_t len) {
uint8_t pcb = 0x02 | (m_block_num & 0x01);
if (m_cid_supported) pcb |= PCB_CID_FOLLOWING;
uint8_t off = 0;
m_tx_buf[off++] = pcb;
if (m_cid_supported) m_tx_buf[off++] = m_cid & 0x0F;
if (len > NFC_14A_4_MAX_APDU) len = NFC_14A_4_MAX_APDU;
memcpy(&m_tx_buf[off], data, len);
nfc_tag_14a_tx_bytes(m_tx_buf, off + len, true);
m_block_num ^= 1;
}
static void send_rack(void) {
uint8_t pcb = 0xA2 | (m_block_num & 0x01);
if (m_cid_supported) {
pcb |= PCB_CID_FOLLOWING;
uint8_t buf[2] = { pcb, m_cid & 0x0F };
nfc_tag_14a_tx_bytes(buf, 2, true);
} else {
nfc_tag_14a_tx_bytes(&pcb, 1, true);
}
}
static void send_wtx(void) {
uint8_t buf[3];
uint8_t off = 0;
buf[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) buf[off++] = m_cid & 0x0F;
buf[off++] = WTX_VALUE;
nfc_tag_14a_tx_bytes(buf, off, true);
}
/* ------------------------------------------------------------------ */
/* State handler (called from NFCT ISR on each received frame) */
/* ------------------------------------------------------------------ */
static void nfc_tag_14a_4_state_handler(uint8_t *data, uint16_t szBytes) {
if (szBytes == 0) return;
uint8_t pcb = data[0];
/* ---- S-block ---- */
if (is_sblock(pcb)) {
if ((pcb & 0xF7) == PCB_SBLOCK_DESELECT) {
/* Echo DESELECT */
nfc_tag_14a_tx_bytes(data, szBytes, true);
nfc_tag_14a_4_reset_handler();
return;
}
if ((pcb & 0x3F) == (PCB_SBLOCK_WTX & 0x3F)) {
/* Reader sending WTX — echo back with our WTXM */
uint8_t wtxm = (szBytes > 1) ? data[szBytes - 1] & 0x3F : WTX_VALUE;
uint8_t resp[3];
uint8_t off = 0;
resp[off++] = PCB_SBLOCK_WTX | (m_cid_supported ? PCB_CID_FOLLOWING : 0);
if (m_cid_supported) resp[off++] = m_cid & 0x0F;
resp[off++] = wtxm;
nfc_tag_14a_tx_bytes(resp, off, true);
/* If we now have a response ready, send it next I-block */
if (m_response_ready) {
m_response_ready = false;
send_iblock(m_resp_buf, m_resp_len);
}
return;
}
return;
}
/* ---- R-block ---- */
if (is_rblock(pcb)) {
send_rack();
return;
}
/* ---- I-block ---- */
if (is_iblock(pcb)) {
uint8_t reader_blknum = pcb & PCB_BLOCK_NUM;
bool has_cid = (pcb & PCB_CID_FOLLOWING) != 0;
bool has_nad = (pcb & PCB_NAD_FOLLOWING) != 0;
bool more_chain = (pcb & PCB_CHAIN) != 0;
uint8_t offset = 1;
if (has_cid) {
/* CID acknowledged but not used in responses (keeps protocol simpler) */
m_cid_supported = false;
offset++; /* skip CID byte */
}
if (has_nad) offset++;
if (offset >= szBytes) {
send_rack();
return;
}
uint16_t apdu_len = szBytes - offset;
if (apdu_len > NFC_14A_4_MAX_APDU) apdu_len = NFC_14A_4_MAX_APDU;
m_dbg_iblocks_rx++;
m_dbg_last_rx_pcb = pcb;
NRF_LOG_INFO("14A4 I-block #%d: reader_blk=%d m_block_num=%d apdu_len=%d",
m_dbg_iblocks_rx, reader_blknum, m_block_num, apdu_len);
/* Block number check per ISO14443-4 §7.5.3.3:
* If block number matches expected, process new APDU.
* If block number does NOT match, it is a retransmit
* resend the last response without re-processing. */
if (reader_blknum != (m_block_num & 0x01)) {
/* Retransmit: resend last response */
if (m_resp_len > 0) {
/* Restore block num to what we sent last time and resend */
m_block_num ^= 1; /* undo the increment from last send */
send_iblock(m_resp_buf, m_resp_len);
} else {
send_rack();
}
return;
}
memcpy(m_apdu_buf, &data[offset], apdu_len);
m_apdu_len = apdu_len;
m_apdu_pending = true;
m_response_ready = false;
if (more_chain) {
send_rack();
return;
}
/* APDU complete — check static table first, then WTX */
{
uint8_t *static_resp = NULL;
uint16_t static_len = 0;
bool _found = find_static_response(m_apdu_buf, apdu_len,
&static_resp, &static_len);
m_dbg_last_match = _found ? 1 : 0;
NRF_LOG_INFO("14A4 find_static: found=%d static_len=%d resp_count=%d",
_found, static_len, m_static_resp_count);
if (_found) {
m_dbg_iblocks_tx++;
memcpy(m_resp_buf, static_resp, static_len);
m_resp_len = static_len;
send_iblock(m_resp_buf, m_resp_len);
} else if (m_response_ready) {
m_response_ready = false;
send_iblock(m_resp_buf, m_resp_len);
} else {
/* No response ready — keep reader alive with WTX */
send_wtx();
}
}
return;
}
NRF_LOG_INFO("14A-4: unknown PCB 0x%02x", pcb);
}
/* ------------------------------------------------------------------ */
/* APDU relay API (for host-driven responses) */
/* ------------------------------------------------------------------ */
bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length) {
if (!m_apdu_pending) return false;
m_apdu_pending = false;
*length = m_apdu_len;
memcpy(buf, m_apdu_buf, m_apdu_len);
return true;
}
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length) {
if (length > NFC_14A_4_MAX_APDU) length = NFC_14A_4_MAX_APDU;
memcpy(m_resp_buf, data, length);
m_resp_len = length;
m_response_ready = true;
}
/* ------------------------------------------------------------------ */
/* Reset handler */
/* ------------------------------------------------------------------ */
void nfc_tag_14a_4_reset_handler(void) {
m_block_num = 0;
m_cid_supported = false;
m_cid = 0;
m_apdu_pending = false;
m_response_ready = false;
m_apdu_len = 0;
m_resp_len = 0;
}
void nfc_tag_14a_4_get_debug_counters(uint8_t *rx, uint8_t *tx,
uint8_t *last_pcb, uint8_t *last_match) {
*rx = m_dbg_iblocks_rx;
*tx = m_dbg_iblocks_tx;
*last_pcb = m_dbg_last_rx_pcb;
*last_match = m_dbg_last_match;
}
/* ------------------------------------------------------------------ */
/* Anti-collision resource */
/* ------------------------------------------------------------------ */
nfc_tag_14a_coll_res_reference_t *nfc_tag_14a_4_get_coll_res(void) {
if (m_tag_information == NULL) return NULL;
m_shadow_coll_res.sak = m_tag_information->res_coll.sak;
m_shadow_coll_res.atqa = m_tag_information->res_coll.atqa;
m_shadow_coll_res.uid = m_tag_information->res_coll.uid;
m_shadow_coll_res.size = &m_tag_information->res_coll.size;
m_shadow_coll_res.ats = &m_tag_information->res_coll.ats;
return &m_shadow_coll_res;
}
/* ------------------------------------------------------------------ */
/* Data load / save / factory callbacks */
/* ------------------------------------------------------------------ */
int nfc_tag_14a_4_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
int info_size = sizeof(nfc_tag_14a_4_information_t);
if (buffer->length < info_size) {
NRF_LOG_ERROR("14A-4 loadcb: buffer too small (%d < %d)",
buffer->length, info_size);
return info_size;
}
m_tag_information = (nfc_tag_14a_4_information_t *)buffer->buffer;
/* Populate RAM static table from persisted slot data */
m_static_resp_count = m_tag_information->static_resp_count;
if (m_static_resp_count > NFC_14A_4_MAX_STATIC_RESPONSES)
m_static_resp_count = NFC_14A_4_MAX_STATIC_RESPONSES;
memcpy(m_static_resp, m_tag_information->static_resp,
m_static_resp_count * sizeof(nfc_tag_14a_4_static_response_t));
nfc_tag_14a_handler_t handler = {
.get_coll_res = nfc_tag_14a_4_get_coll_res,
.cb_state = nfc_tag_14a_4_state_handler,
.cb_reset = nfc_tag_14a_4_reset_handler,
};
nfc_tag_14a_set_handler(&handler);
NRF_LOG_INFO("14A-4 loadcb OK: SAK=%02x uid_sz=%d static_resp=%d",
m_tag_information->res_coll.sak[0],
m_tag_information->res_coll.size,
m_static_resp_count);
return info_size;
}
int nfc_tag_14a_4_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) {
return sizeof(nfc_tag_14a_4_information_t);
}
bool nfc_tag_14a_4_data_factory(uint8_t slot, tag_specific_type_t tag_type) {
if (tag_type != TAG_TYPE_HF14A_4) return false;
/* Build factory defaults on stack and write directly to FDS
* (same pattern as nfc_tag_mf1_data_factory). */
nfc_tag_14a_4_information_t info;
memset(&info, 0, sizeof(info));
/* Placeholder 7-byte NXP-style UID */
info.res_coll.size = NFC_TAG_14A_UID_DOUBLE_SIZE;
info.res_coll.atqa[0] = 0x04;
info.res_coll.atqa[1] = 0x00;
info.res_coll.sak[0] = 0x20; /* ISO14443-4 */
info.res_coll.uid[0] = 0x04;
info.res_coll.uid[1] = 0x01;
info.res_coll.uid[2] = 0x02;
info.res_coll.uid[3] = 0x03;
info.res_coll.uid[4] = 0x04;
info.res_coll.uid[5] = 0x05;
info.res_coll.uid[6] = 0x06;
static const uint8_t default_ats[] = {
0x10, 0x78, 0x80, 0x70, 0x02, 0x00,
0x31, 0xC1, 0x64, 0x09, 0x97, 0x61,
0x26, 0x00, 0x90, 0x00
};
info.res_coll.ats.length = sizeof(default_ats);
memcpy(info.res_coll.ats.data, default_ats, sizeof(default_ats));
info.static_resp_count = 0;
fds_slot_record_map_t map_info;
get_fds_map_by_slot_sense_type_for_dump(slot, TAG_SENSE_HF, &map_info);
bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(info), &info);
NRF_LOG_INFO("14A-4 factory slot %d: %s", slot, ret ? "OK" : "FAIL");
return ret;
}
@@ -0,0 +1,71 @@
/**
* @file nfc_14a_4.h
* @brief ISO14443-4 T=CL emulation for ChameleonUltra
*
* Implements a full ISO14443-4 tag emulator:
* - I-blocks (information, chaining, CID)
* - R-blocks (ACK/NAK retransmit)
* - S-blocks (WTX to keep reader alive, DESELECT)
* - Static APDU response table (pre-loaded before field, no USB needed
* during field exchange)
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef NFC_14A_4_H
#define NFC_14A_4_H
#include "nfc_14a.h"
#include "tag_emulation.h"
/* Maximum APDU size (FSCI=8 → FSC=256, minus PCB+CRC = 253) */
#define NFC_14A_4_MAX_APDU 260 /* max APDU in RAM; flash entries capped at 253 */
/* Static APDU response table — up to 12 pre-configured command/response pairs.
* Loaded before field activation; firmware responds autonomously without USB. */
#define NFC_14A_4_MAX_STATIC_RESPONSES 12
#define NFC_14A_4_MAX_LARGE_RESPONSES 4 /* RAM-only, for resp > 253 bytes */
#define NFC_14A_4_MAX_LARGE_RESP_LEN 260 /* max large response size */
#define NFC_14A_4_MAX_STATIC_CMD_LEN 16
#define NFC_14A_4_MAX_STATIC_RESP_LEN 253 /* max bytes in flash-backed slot */
typedef struct __attribute__((packed)) {
uint8_t cmd_len;
uint8_t cmd[NFC_14A_4_MAX_STATIC_CMD_LEN];
uint8_t resp_len;
uint8_t resp[NFC_14A_4_MAX_STATIC_RESP_LEN];
} nfc_tag_14a_4_static_response_t;
/**
* Per-slot persistent data layout stored in FDS flash.
* Anti-collision response (UID/ATQA/SAK/ATS) plus the static response table.
*/
typedef struct __attribute__((packed)) {
nfc_tag_14a_coll_res_entity_t res_coll;
uint8_t static_resp_count;
nfc_tag_14a_4_static_response_t static_resp[NFC_14A_4_MAX_STATIC_RESPONSES];
} nfc_tag_14a_4_information_t;
/* Anti-collision resource — used by get_coll_res_data in app_cmd.c */
nfc_tag_14a_coll_res_reference_t *nfc_tag_14a_4_get_coll_res(void);
/* tag_base_map callbacks */
int nfc_tag_14a_4_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer);
int nfc_tag_14a_4_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer);
bool nfc_tag_14a_4_data_factory(uint8_t slot, tag_specific_type_t tag_type);
/* Static response table management (called before hw mode -e) */
void nfc_tag_14a_4_add_static_response(const uint8_t *cmd, uint8_t cmd_len,
const uint8_t *resp, uint16_t resp_len);
void nfc_tag_14a_4_clear_static_responses(void);
/* APDU relay — host-driven responses */
bool nfc_tag_14a_4_get_pending_apdu(uint8_t *buf, uint16_t *length);
void nfc_tag_14a_4_set_response(const uint8_t *data, uint16_t length);
/* Reset handler */
void nfc_tag_14a_4_reset_handler(void);
#endif /* NFC_14A_4_H */
void nfc_tag_14a_4_get_debug_counters(uint8_t *rx, uint8_t *tx, uint8_t *last_pcb, uint8_t *last_match);
@@ -81,9 +81,11 @@ extern "C" {
T5577_PWD | \
(4 << T5577_MAXBLOCK_SHIFT))
#if defined(PROJECT_CHAMELEON_ULTRA)
void t55xx_write_data(uint32_t passwd, uint32_t *blks, uint8_t blk_count);
void t55xx_reset_passwd(uint32_t old_passwd, uint32_t new_passwd);
void t55xx_send_cmd(uint8_t opcode, uint32_t *passwd, uint8_t data_len, uint32_t *data, uint8_t block);
#endif
#ifdef __cplusplus
}
#endif
@@ -91,6 +91,7 @@ typedef enum {
// ST25TA series 2000
// HF14A-4 series 3000
TAG_TYPE_HF14A_4 = 3000,
} tag_specific_type_t;
@@ -115,7 +116,7 @@ typedef enum {
TAG_TYPE_MIFARE_4096, TAG_TYPE_NTAG_213, TAG_TYPE_NTAG_215, \
TAG_TYPE_NTAG_216, TAG_TYPE_MF0ICU1, TAG_TYPE_MF0ICU2, \
TAG_TYPE_MF0UL11, TAG_TYPE_MF0UL21, TAG_TYPE_NTAG_210, \
TAG_TYPE_NTAG_212
TAG_TYPE_NTAG_212, TAG_TYPE_HF14A_4
typedef struct {
tag_specific_type_t tag_hf;
@@ -7,6 +7,7 @@
#include "nfc_14a.h"
#include "nfc_mf0_ntag.h"
#include "nfc_mf1.h"
#include "nfc_14a_4.h"
#include "rgb_marquee.h"
#include "tag_persistence.h"
@@ -111,6 +112,8 @@ static tag_base_handler_map_t tag_base_map[] = {
{TAG_SENSE_HF, TAG_TYPE_MF0ICU2, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf},
{TAG_SENSE_HF, TAG_TYPE_MF0UL11, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf},
{TAG_SENSE_HF, TAG_TYPE_MF0UL21, nfc_tag_mf0_ntag_data_loadcb, nfc_tag_mf0_ntag_data_savecb, nfc_tag_mf0_ntag_data_factory, &m_tag_data_hf},
// ISO14443-4 T=CL emulation
{TAG_SENSE_HF, TAG_TYPE_HF14A_4, nfc_tag_14a_4_data_loadcb, nfc_tag_14a_4_data_savecb, nfc_tag_14a_4_data_factory, &m_tag_data_hf},
};
static void tag_emulation_load_config(void);
@@ -861,7 +861,7 @@ uint8_t pcd_14a_reader_scan_auto(picc_14a_tag_t *tag) {
* @retval : Status value hf_tag_ok, success
*/
uint8_t pcd_14a_reader_ats_request(uint8_t *pAts, uint16_t *szAts, uint16_t szAtsBitMax) {
uint8_t rats[] = { PICC_RATS, 0x80, 0x31, 0x73 }; // FSD=256, FSDI=8, CID=0
uint8_t rats[] = { PICC_RATS, 0x40, 0x3D, 0xB5 }; // FSD=48, FSDI=4, CID=0 (fits RC522 64-byte FIFO)
uint8_t status;
status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, rats, sizeof(rats), pAts, szAts, szAtsBitMax);
@@ -1,5 +1,6 @@
#include "lf_reader_main.h"
#include <stdbool.h>
#include "bsp_delay.h"
#include "bsp_time.h"
#include "hex_utils.h"
@@ -209,3 +210,35 @@ uint8_t write_pac_to_t55xx(uint8_t *data, uint8_t *new_passwd, uint8_t *old_pass
* Set the LF card scanning timeout value (in milliseconds).
*/
void set_scan_tag_timeout(uint32_t ms) { g_timeout_readem_ms = ms; }
#if defined(PROJECT_CHAMELEON_ULTRA)
/**
* Write a single raw 32-bit word to a T55xx block.
*
* Unlike write_em410x_to_t55xx() and friends, this writes the exact word
* supplied with no protocol encoding useful for custom configuration
* words, recovery of locked tags, or scripted programming.
*
* Only available on Chameleon Ultra (Lite has no LF writer hardware).
*
* @param block Block number (0-7 for page 0, 0-3 for page 1)
* @param word 32-bit data word to write
* @param passwd Password for password-protected write (ignored when use_passwd is false)
* @param use_passwd true = password-protected write, false = open write
* @param page1 true = target page 1, false = page 0
* @return STATUS_LF_TAG_OK always (T55xx gives no ACK; verify by reading back)
*/
uint8_t lf_t55xx_write_block(uint8_t block, uint32_t word, uint32_t passwd, bool use_passwd, bool page1) {
uint8_t opcode = page1 ? T5577_OPCODE_PAGE1 : T5577_OPCODE_PAGE0;
uint32_t *pwd_ptr = use_passwd ? &passwd : NULL;
start_lf_125khz_radio();
bsp_delay_ms(1); // Delay for a while after starting the field
t55xx_send_cmd(opcode, pwd_ptr, 0, &word, block);
t55xx_send_cmd(T5577_OPCODE_RESET, NULL, 0, NULL, 0);
stop_lf_125khz_radio();
return STATUS_LF_TAG_OK;
}
#endif
@@ -24,3 +24,6 @@ uint8_t write_hidprox_to_t55xx(uint8_t format, uint32_t fc, uint64_t cn, uint32_
uint8_t write_ioprox_to_t55xx(uint8_t *raw_data, uint8_t *new_passwd, uint8_t *old_passwds, uint8_t old_passwd_count);
uint8_t write_viking_to_t55xx(uint8_t *uid, uint8_t *newkey, uint8_t *old_keys, uint8_t old_key_count);
uint8_t write_pac_to_t55xx(uint8_t *data, uint8_t *new_passwd, uint8_t *old_passwds, uint8_t old_passwd_count);
#if defined(PROJECT_CHAMELEON_ULTRA)
uint8_t lf_t55xx_write_block(uint8_t block, uint32_t word, uint32_t passwd, bool use_passwd, bool page1);
#endif
File diff suppressed because it is too large Load Diff
+96
View File
@@ -235,6 +235,101 @@ class ChameleonCMD:
return resp
@expect_response(Status.HF_TAG_OK)
def hf14a_scan_keep(self):
"""
Scan ISO14443-A tag with full select + RATS, keeping field alive.
Identical to hf14a_scan but does NOT tear down the RF field afterward.
The card remains powered and in ISO14443-4 T=CL state so subsequent
hf14a_raw calls can exchange APDUs without re-selecting.
"""
resp = self.device.send_cmd_sync(Command.HF14A_SCAN_KEEP)
if resp.status == Status.HF_TAG_OK:
offset = 0
data = []
while offset < len(resp.data):
uidlen, = struct.unpack_from('!B', resp.data, offset); offset += 1
uid, atqa, sak, atslen = struct.unpack_from(
f'!{uidlen}s2s1sB', resp.data, offset)
offset += struct.calcsize(f'!{uidlen}s2s1sB')
ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset)
offset += atslen
data.append({'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats})
resp.parsed = data
return resp
def hf14a_4_set_anti_coll(self, uid: bytes, atqa: bytes, sak: int, ats: bytes):
"""
Set UID / ATQA / SAK / ATS for the active HF14A_4 slot.
:param uid: UID bytes (4 or 7 bytes)
:param atqa: ATQA 2 bytes (wire order, e.g. b'\x04\x00' for ATQA 00 04)
:param sak: SAK byte value (int), use 0x20 for ISO14443-4
:param ats: ATS bytes (without CRC)
"""
uid_size = len(uid)
payload = (bytes([uid_size]) + bytes(uid) + bytes(atqa) +
bytes([sak]) + bytes([len(ats)]) + bytes(ats))
return self.device.send_cmd_sync(Command.HF14A_4_SET_ANTI_COLL, payload)
def hf14a_4_apdu_recv(self):
"""
Non-blocking poll for a pending APDU from the ISO14443-4 T=CL stack.
Returns immediately: STATUS_SUCCESS + APDU bytes if one is pending,
STATUS_HF_TAG_NO if no APDU is waiting. Call in a tight loop from
the host side for relay/capture use cases.
"""
return self.device.send_cmd_sync(Command.HF14A_4_APDU_RECV, b'', timeout=2)
def hf14a_4_apdu_send(self, resp: bytes):
"""Send an APDU response to the ISO14443-4 T=CL stack."""
payload = bytes([(len(resp) >> 8) & 0xFF, len(resp) & 0xFF]) + bytes(resp)
return self.device.send_cmd_sync(Command.HF14A_4_APDU_SEND, payload)
def hf14a_4_add_static_response(self, cmd: bytes, resp: bytes):
"""
Add a static APDU commandresponse pair to the HF14A_4 slot.
The firmware will automatically reply with resp whenever it receives
an APDU whose first len(cmd) bytes match cmd, without USB involvement.
Must be called before hw mode -e.
"""
rlen = len(resp); payload = bytes([len(cmd)]) + bytes(cmd) + bytes([(rlen >> 8) & 0xFF, rlen & 0xFF]) + bytes(resp)
return self.device.send_cmd_sync(Command.HF14A_4_STATIC_RESP, payload)
def hf14a_4_reader_apdu(self, apdu: bytes):
"""
Select card (with RATS) and send one ISO14443-4 T=CL APDU in a single
firmware call avoiding the USB round-trip gap that would depower the card.
:param apdu: raw APDU bytes (no PCB wrapping needed)
:return: response object with resp.data = APDU response bytes (no PCB/CRC)
"""
return self.device.send_cmd_sync(
Command.HF14A_4_READER_APDU, bytes(apdu), timeout=3)
def hf14a_4_emv_scan(self):
"""
Full EMV card scan in a single firmware call.
The firmware performs the complete sequence (field cycle, select, RATS,
PPSE, SELECT AID, GPO, READ RECORDs) without returning to the host
between APDUs, avoiding the field-drop issue with separate calls.
Response format:
uid_len(1) uid(n) atqa(2) sak(1) ats_len(1) ats(m)
num_apdus(1)
for each APDU pair:
cmd_len(1) cmd(n) resp_len_le(2) resp(m)
"""
resp = self.device.send_cmd_sync(Command.HF14A_4_EMV_SCAN, b'', timeout=10)
return resp
def hf14a_4_clear_static_responses(self):
"""Clear all static APDU responses from the active HF14A_4 slot."""
return self.device.send_cmd_sync(Command.HF14A_4_STATIC_RESP, b'\x00')
def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bitlen=None):
"""
Send raw cmd to 14a tag.
@@ -573,6 +668,7 @@ class ChameleonCMD:
def lf_sniff(self, timeout_ms: int = 2000):
"""
Capture raw LF field ADC samples.
+12 -1
View File
@@ -68,6 +68,7 @@ class Command(enum.IntEnum):
MF1_READ_ONE_BLOCK = 2008
MF1_WRITE_ONE_BLOCK = 2009
HF14A_RAW = 2010
HF14A_SCAN_KEEP = 2016
MF1_MANIPULATE_VALUE_BLOCK = 2011
MF1_CHECK_KEYS_OF_SECTORS = 2012
MF1_HARDNESTED_ACQUIRE = 2013
@@ -91,6 +92,7 @@ class Command(enum.IntEnum):
IOPROX_WRITE_TO_T55XX = 3011
IOPROX_DECODE_RAW = 3012
IOPROX_COMPOSE_ID = 3013
LF_T55XX_WRITE = 3016
MF1_WRITE_EMU_BLOCK_DATA = 4000
HF14A_SET_ANTI_COLL_DATA = 4001
@@ -138,6 +140,14 @@ class Command(enum.IntEnum):
MF1_SET_FIELD_OFF_DO_RESET = 4038
MF1_GET_FIELD_OFF_DO_RESET = 4039
# ISO14443-4 T=CL emulation
HF14A_4_APDU_RECV = 6000
HF14A_4_APDU_SEND = 6001
HF14A_4_SET_ANTI_COLL = 6002
HF14A_4_STATIC_RESP = 6003
HF14A_4_READER_APDU = 6004
HF14A_4_EMV_SCAN = 6005
EM410X_SET_EMU_ID = 5000
EM410X_GET_EMU_ID = 5001
HIDPROX_SET_EMU_ID = 5002
@@ -333,7 +343,8 @@ class TagSpecificType(enum.IntEnum):
# ST25TA series 2000
# HF14A-4 series 3000
# ISO14443-4 T=CL emulation
HF14A_4 = 3000
@staticmethod
def list(exclude_meta=True):
+56 -41
View File
@@ -56,61 +56,76 @@ class ArgumentParserNoExit(argparse.ArgumentParser):
def exit(self, status: int = 0, message: Union[str, None] = None):
if message:
raise ParserExitIntercept(message)
# status=0 means help was printed; raise to stop argparse continuing
# to validate required args (which would cause a second print_help call)
raise ParserExitIntercept('')
def error(self, message: str):
args = {'prog': self.prog, 'message': message}
raise ArgsParserError('%(prog)s: error: %(message)s\n' % args)
def print_help(self):
def print_help(self):
"""
Colorize argparse help
"""
print("-" * 80)
print(color_string((CR, self.prog)))
lines = self.format_help().splitlines()
usage = lines[:lines.index('')]
assert usage[0].startswith('usage:')
usage[0] = usage[0].replace('usage:', f'{color_string((CG, "usage:"))}\n ')
usage[0] = usage[0].replace(self.prog, color_string((CR, self.prog)))
usage = [usage[0]] + [x[4:] for x in usage[1:]] + ['']
lines = lines[lines.index('')+1:]
desc = lines[:lines.index('')]
print(color_string((CC, "\n".join(desc))))
print('\n'.join(usage))
lines = lines[lines.index('')+1:]
if '' in lines:
options = lines[:lines.index('')]
lines = lines[lines.index('')+1:]
else:
options = lines
lines = []
if len(options) > 0 and options[0].strip() == 'positional arguments:':
positional_args = options
positional_args[0] = positional_args[0].replace('positional arguments:', color_string((CG, "positional arguments:")))
if len(positional_args) > 1:
positional_args.append('')
print('\n'.join(positional_args))
if '' in lines:
options = lines[:lines.index('')]
lines = lines[lines.index('')+1:]
else:
options = lines
# Get the help text and split it, filtering out leading empty lines
raw_lines = self.format_help().splitlines()
lines = [line for line in raw_lines if line.strip() or line == '']
# Find the usage block safely
usage_start = -1
for i, line in enumerate(lines):
if line.strip().startswith('usage:'):
usage_start = i
break
if usage_start != -1:
# We found a usage line, extract the block until the first empty line
try:
empty_after_usage = lines.index('', usage_start)
usage = lines[usage_start:empty_after_usage]
# Apply coloring to the usage string
usage[0] = usage[0].replace('usage:', f'{color_string((CG, "usage:"))}\n ')
usage[0] = usage[0].replace(self.prog, color_string((CR, self.prog)))
# Reformat indentation and print
usage_to_print = [usage[0]] + [x[4:] for x in usage[1:]] + ['']
print('\n'.join(usage_to_print))
# Advance lines pointer to after the usage block
lines = lines[empty_after_usage + 1:]
except ValueError:
# If no empty line found, just print what we have
print('\n'.join(lines[usage_start:]))
lines = []
if len(options) > 0:
# 2 variants depending on Python version(?)
assert options[0].strip() in ['options:', 'optional arguments:']
options[0] = options[0].replace('options:', color_string((CG, "options:")))
options[0] = options[0].replace('optional arguments:', color_string((CG, "optional arguments:")))
if len(options) > 1:
options.append('')
print('\n'.join(options))
if len(lines) > 0:
lines[0] = color_string((CG, lines[0]))
print('\n'.join(lines))
# Print description if available
if lines and lines[0].strip() != '':
try:
desc_end = lines.index('')
desc = lines[:desc_end]
print(color_string((CC, "\n".join(desc))))
lines = lines[desc_end + 1:]
except ValueError:
pass
# Handle options and positional arguments without crashing on strict matches
for line in lines:
clean_line = line.strip().lower()
if clean_line == 'positional arguments:':
print(color_string((CG, line)))
elif clean_line in ['options:', 'optional arguments:']:
print(color_string((CG, line)))
else:
print(line)
print('')
self.help_requested = True
def print_mem_dump(bindata, blocksize):
hexadecimal_len = blocksize*3+1