FEAT! Add T55 write commands

This commit is contained in:
Niel Nielsen
2026-04-07 10:36:06 +02:00
committed by Niel Nielsen
parent a3d3c1fc34
commit e16505e6a7
8 changed files with 234 additions and 8 deletions
+24 -7
View File
@@ -833,17 +833,33 @@ static data_frame_tx_t *cmd_processor_viking_write_to_t55xx(uint16_t cmd, uint16
return data_frame_make(cmd, status, 0, NULL);
}
static data_frame_tx_t *cmd_processor_pac_write_to_t55xx(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
static data_frame_tx_t *cmd_processor_lf_t55xx_write(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) {
typedef struct {
uint8_t id[LF_PAC_TAG_ID_SIZE];
uint8_t new_key[4];
uint8_t old_keys[4];
uint8_t block; /* block number */
uint8_t word[4]; /* 32-bit data word, big-endian */
uint8_t use_pwd; /* 1 = password write, 0 = open write */
uint8_t pwd[4]; /* 32-bit password, big-endian (ignored when use_pwd == 0) */
uint8_t page1; /* 1 = target page 1, 0 = page 0 */
} PACKED payload_t;
payload_t *payload = (payload_t *)data;
if (length < sizeof(payload_t) || (length - offsetof(payload_t, old_keys)) % sizeof(payload->old_keys) != 0) {
if (length < sizeof(payload_t)) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
status = write_pac_to_t55xx(payload->id, payload->new_key, payload->old_keys, (length - offsetof(payload_t, old_keys)) / sizeof(payload->old_keys));
payload_t *p = (payload_t *)data;
bool page1 = (bool)p->page1;
uint8_t max_block = page1 ? 3u : 7u;
if (p->block > max_block) {
return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL);
}
uint32_t word = bytes_to_num(p->word, 4);
uint32_t passwd = bytes_to_num(p->pwd, 4);
bool use_pwd = (bool)p->use_pwd;
status = lf_t55xx_write_block(p->block, word, passwd, use_pwd, page1);
return data_frame_make(cmd, status, 0, NULL);
}
@@ -2361,6 +2377,7 @@ static cmd_data_map_t m_data_cmd_map[] = {
{ DATA_CMD_IOPROX_WRITE_TO_T55XX, before_reader_run, cmd_processor_ioprox_write_to_t55xx, NULL },
{ DATA_CMD_PAC_SCAN, before_reader_run, cmd_processor_pac_scan, NULL },
{ DATA_CMD_PAC_WRITE_TO_T55XX, before_reader_run, cmd_processor_pac_write_to_t55xx, NULL },
{ DATA_CMD_LF_T55XX_WRITE, before_reader_run, cmd_processor_lf_t55xx_write, NULL },
{ DATA_CMD_ADC_GENERIC_READ, before_reader_run, cmd_processor_generic_read, NULL },
{ DATA_CMD_HF14A_SET_FIELD_ON, before_reader_run, cmd_processor_hf14a_set_field_on, NULL },
+1
View File
@@ -106,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 (3014)
//
// ******************************************************************
@@ -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
@@ -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
+168
View File
@@ -764,6 +764,7 @@ lf_hid_prox = lf_hid.subgroup("prox", "HID Prox commands")
lf_ioprox = lf.subgroup("ioprox", "ioProx commands")
lf_pac = lf.subgroup("pac", "PAC/Stanley commands")
lf_viking = lf.subgroup("viking", "Viking commands")
lf_t55xx = lf.subgroup("t55xx", "T55xx raw commands")
lf_generic = lf.subgroup("generic", "Generic commands")
@@ -6008,6 +6009,173 @@ class LFVikingWriteT55xx(LFVikingIdArgsUnit, ReaderRequiredUnit):
print(f" - Viking ID(8H): {id_hex} write done.")
@lf_t55xx.command("clone")
class LFT55xxClone(ReaderRequiredUnit):
"""
Clone a scanned or manually-specified LF card ID onto a blank T55xx tag.
Supported types and their required arguments:
em410x --id <10 hex> e.g. --id DEADBEEF88
electra --id <26 hex> e.g. --id DEADBEEF880102030405060708
hid -f <format> --cn <n> e.g. -f H10301 --fc 10 --cn 1234
ioprox --ver <n> --fc <n> --cn <n> OR --raw8 <16 hex>
viking --id <8 hex> e.g. --id DEADBEEF
Only supported on Chameleon Ultra (Lite has no LF writer).
"""
TYPES = ["em410x", "electra", "hid", "ioprox", "viking"]
def args_parser(self) -> ArgumentParserNoExit:
parser = ArgumentParserNoExit()
parser.description = (
"Clone a LF card ID onto a blank T55xx tag.\n"
"Supported types: em410x, electra, hid, ioprox, viking.\n"
"Only supported on Chameleon Ultra (Lite has no LF writer)."
)
parser.add_argument(
"-t", "--type",
type=str,
required=True,
choices=self.TYPES,
metavar="TYPE",
help="Card type: " + ", ".join(self.TYPES),
)
# EM410x / Electra / Viking
parser.add_argument(
"--id",
type=str,
required=False,
metavar="HEX",
help="Card ID in hex: 10 hex for em410x, 26 for electra, 8 for viking",
)
# HID Prox
parser.add_argument(
"-f", "--format",
type=str,
required=False,
choices=[x.name for x in HIDFormat],
metavar="FORMAT",
help="HID Prox format, e.g. H10301 (required for hid type)",
)
parser.add_argument(
"--fc",
type=int,
required=False,
metavar="INT",
help="Facility code (HID / ioProx)",
)
parser.add_argument(
"--cn",
type=int,
required=False,
metavar="INT",
help="Card number (HID / ioProx)",
)
parser.add_argument(
"--il",
type=int,
required=False,
metavar="INT",
help="Issue level (HID, optional)",
)
parser.add_argument(
"--oem",
type=int,
required=False,
metavar="INT",
help="OEM code (HID, optional)",
)
# ioProx
parser.add_argument(
"--ver",
type=int,
required=False,
metavar="INT",
help="Version byte (ioProx)",
)
parser.add_argument(
"--raw8",
type=str,
required=False,
metavar="HEX",
help="ioProx raw 8 bytes in hex, e.g. 007854E03A5D65AB",
)
return parser
def on_exec(self, args: argparse.Namespace):
t = args.type
if t in ("em410x", "electra"):
if args.id is None:
raise ArgsParserError("--id is required for em410x / electra")
expected = 10 if t == "em410x" else 26
if not re.match(r"^[a-fA-F0-9]{" + str(expected) + r"}$", args.id):
raise ArgsParserError(
f"--id must be exactly {expected} hex characters for {t}"
)
id_bytes = bytes.fromhex(args.id)
self.cmd.em410x_write_to_t55xx(id_bytes)
label = "EM410x Electra" if t == "electra" else "EM410x"
print(f" - {label} ID cloned to T55xx: {args.id.upper()}")
elif t == "hid":
if args.format is None:
raise ArgsParserError("-f/--format is required for hid")
if args.cn is None:
raise ArgsParserError("--cn is required for hid")
fmt = HIDFormat[args.format]
fc = args.fc if args.fc is not None else 0
il = args.il if args.il is not None else 0
oem = args.oem if args.oem is not None else 0
LFHIDIdArgsUnit.check_limits(fmt.value, fc, args.cn, il, oem)
cn = args.cn
id_bytes = struct.pack(
">BIBIBH",
fmt.value,
fc,
(cn >> 32),
cn & 0xFFFFFFFF,
il,
oem,
)
self.cmd.hidprox_write_to_t55xx(id_bytes)
print(f" - HID Prox cloned to T55xx")
print(f" Format : {fmt.name}")
if fc: print(f" FC : {fc}")
if il: print(f" IL : {il}")
if oem: print(f" OEM : {oem}")
print(f" CN : {cn}")
elif t == "ioprox":
ver = args.ver if args.ver is not None else 1
fc = int(args.fc, 0) if args.fc is not None else 0
cn = args.cn if args.cn is not None else 0
if args.raw8 is not None:
raw8 = LFIOProxIdArgsUnit.parse_raw8(args.raw8)
ver, fc, cn, raw8, *_ = self.cmd.ioprox_decode_raw(raw8)
else:
res = self.cmd.ioprox_compose_id(ver, fc, cn)
raw8 = res[3]
payload16 = struct.pack(">BBH8s4x", ver & 0xFF, fc & 0xFF, cn & 0xFFFF, raw8)
self.cmd.ioprox_write_to_t55xx(payload16)
print(f" - ioProx cloned to T55xx")
print(f" Ver : {ver}")
print(f" FC : {fc} [0x{fc:02X}]")
print(f" CN : {cn}")
print(f" Raw8 : {raw8.hex().upper()}")
elif t == "viking":
if args.id is None:
raise ArgsParserError("--id is required for viking")
if not re.match(r"^[a-fA-F0-9]{8}$", args.id):
raise ArgsParserError("--id must be exactly 8 hex characters for viking")
id_bytes = bytes.fromhex(args.id)
self.cmd.viking_write_to_t55xx(id_bytes)
print(f" - Viking ID cloned to T55xx: {args.id.upper()}")
@lf_generic.command("adcread")
class LFADCGenericRead(ReaderRequiredUnit):
def args_parser(self) -> ArgumentParserNoExit:
+1
View File
@@ -668,6 +668,7 @@ class ChameleonCMD:
def lf_sniff(self, timeout_ms: int = 2000):
"""
Capture raw LF field ADC samples.
+1
View File
@@ -92,6 +92,7 @@ class Command(enum.IntEnum):
IOPROX_WRITE_TO_T55XX = 3011
IOPROX_DECODE_RAW = 3012
IOPROX_COMPOSE_ID = 3013
LF_T55XX_WRITE = 3014
MF1_WRITE_EMU_BLOCK_DATA = 4000
HF14A_SET_ANTI_COLL_DATA = 4001