Fix PN532 transport and NFC tag handling.

Tighten PN532 framing and NFC access locking so scan, read, write, and raw operations behave reliably on hardware, and correct Classic versus Type 2 tag classification so common cards hit the right code paths. Refresh the embedded web assets and launcher files to match the corrected firmware behavior.

Made-with: Cursor
This commit is contained in:
drjones
2026-04-02 18:32:06 -07:00
parent 8968560565
commit 3be2d3f936
19 changed files with 1162 additions and 287 deletions

View File

@@ -26,14 +26,14 @@ export function parseTrailerAccess(hex16: string): AccessBits | null {
export function sakLabel(sak: number): string {
switch (sak) {
case 0x00:
return "Ultralight / NTAG / Type 2";
case 0x08:
return "Ultralight family";
return "MIFARE Classic 1K";
case 0x09:
return "Mini / Classic";
return "MIFARE Mini";
case 0x18:
return "Classic 1K";
case 0x19:
return "Classic 4K";
return "MIFARE Classic 4K";
default:
return `SAK 0x${sak.toString(16).toUpperCase()}`;
}
@@ -68,14 +68,15 @@ export function scanCheatLines(tag: { uidLen: number; atqa: number; sak: number
} else if (tag.uidLen === 10) {
lines.push("10-byte UID — triple cascade path.");
}
if (tag.sak === 0x08) {
lines.push("SAK 0x08often Ultralight / Type 2; use page read/write for user memory.");
if (tag.sak === 0x00) {
lines.push("SAK 0x00typical Type 2 / Ultralight-style path; use page read/write.");
}
if (tag.sak === 0x18 || tag.sak === 0x19) {
lines.push("MIFARE Classic — authenticate per sector trailer, then block read/write.");
if (tag.sak === 0x08 || tag.sak === 0x09 || tag.sak === 0x18) {
lines.push("MIFARE Classic family — authenticate per sector trailer, then block read/write.");
}
if (tag.atqa === 0x0044 || tag.atqa === 0x4400) {
lines.push("ATQA 0x4400 pattern — very common Type A inventory response.");
/* Firmware stores ATQA as (resp[3]<<8)|resp[4]; e.g. 04 00 → 0x0400. */
if (tag.atqa === 0x0400 || tag.atqa === 0x4400) {
lines.push("ATQA 0x0400/0x4400 class — common Type A / MIFARE inventory shapes.");
}
return lines;
}

View File

@@ -19,11 +19,25 @@ export default function WriteClone() {
const [ulData, setUlData] = useState("00000000");
const write = async () => {
const cleanKey = key.replace(/\s/g, "");
const cleanData = data.replace(/\s/g, "");
if (cleanKey.length !== 12 || !/^[0-9A-Fa-f]+$/.test(cleanKey)) {
toast("Key must be exactly 12 hex digits", "err");
return;
}
if (cleanData.length !== 32 || !/^[0-9A-Fa-f]+$/.test(cleanData)) {
toast("Block data must be exactly 32 hex digits (16 bytes)", "err");
return;
}
if (!Number.isInteger(block) || block < 0 || block > 255) {
toast("Block must be an integer 0255", "err");
return;
}
if (!confirm("Write will modify tag memory. Continue?")) {
return;
}
try {
await apiPost("/api/mifare/write-block", { block, key, keyB, data });
await apiPost("/api/mifare/write-block", { block, key: cleanKey, keyB, data: cleanData });
toast("Write OK");
} catch (e) {
toast(String(e), "err");
@@ -31,11 +45,16 @@ export default function WriteClone() {
};
const writeUl = async () => {
const h = ulData.replace(/\s/g, "");
if (h.length !== 8 || !/^[0-9A-Fa-f]+$/.test(h)) {
toast("UL data must be exactly 8 hex digits (4 bytes)", "err");
return;
}
if (!confirm("Ultralight page write — can brick OTP/lock bytes if misused. Continue?")) {
return;
}
try {
await apiPost("/api/ul/write-page", { page: ulPage, data: ulData.replace(/\s/g, "") });
await apiPost("/api/ul/write-page", { page: ulPage, data: h });
toast("UL page write OK");
} catch (e) {
toast(String(e), "err");