using System; using System.IO; using System.Linq; using System.Threading; using NfcAime.Dll.PN532; using static NfcAime.Dll.MiFareHandle; namespace NfcAime.Dll; public class AimeReader { private string Port = "COM15"; private int Baud = 115200; private Pn532Session session = null; public enum CardKind { Felica, MifareClassic, Null} private sealed record FlowResult(CardKind CardKind, byte[] CardId, string AccessCode); private sealed record CardTarget(CardKind Kind, byte Tg, byte[] CardId); // 兼容低版本 .NET:实现 ToHexString 和 FromHexString public static string ToHexString(byte[] bytes) { return BitConverter.ToString(bytes).Replace("-", ""); } public static byte[] FromHexString(string hex) { if (hex.Length % 2 != 0) throw new ArgumentException("Invalid hex string length."); var bytes = new byte[hex.Length / 2]; for (int i = 0; i < bytes.Length; i++) bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16); return bytes; } public AimeReader(string port, int baud) { Port = port; Baud = baud; TimeSpan timeout = TimeSpan.FromMilliseconds(100); using var transport = new SerialFrameTransport(Port, Baud, timeout); session = new Pn532Session(transport, timeout, 0); } public (CardKind CardKind, byte[] IDm, string AccessCode) ReadCard() { session.Open(); try { var result = RunPn532Flow(session); return (result.CardKind , result.CardId, result.AccessCode); } finally { session.Close(); } } public void CloseReader() { session.Close(); } private FlowResult RunPn532Flow(Pn532Session session) { ExpectPn532ResponseCode(session.SendCommand(new byte[] { 0x02 }), expectedResponseCode: 0x03); ExpectPn532StatusOk(session.SendCommand(new byte[] { 0x14, 0x01, 0x14, 0x01 }), expectedResponseCode: 0x15); ExpectPn532StatusOk(session.SendCommand(new byte[] { 0x32, 0x01, 0x03 }), expectedResponseCode: 0x33); var target = WaitForCard(session); Thread.Sleep(100); if (target.Kind == CardKind.Null) { return new FlowResult(target.Kind, target.CardId, ""); } if (target.Kind == CardKind.Felica) { Console.WriteLine($"Card detected! IDm: {ToHexString(target.CardId)}"); var readCmd = FelicaCommandBuilder.BuildReadWithoutEncryptionCommand(target.CardId); var readResponse = SendInDataExchange(session, target.Tg, readCmd, TimeSpan.FromSeconds(5)); var spad0 = FelicaResponseParser.ParseSpad0(readResponse); var decryptor = new FeliCaDecryptor(); var decrypted = decryptor.Decrypt(spad0); var accessCode = AccessCodeFormatter.ToAccessCodeString(decrypted); return new FlowResult(target.Kind, target.CardId, accessCode); } Console.WriteLine($"Card detected! TypeA UID: {ToHexString(target.CardId)}"); var m1AccessCode = TryReadMifareClassicAccessCode(session, target.Tg, target.CardId); if (m1AccessCode is null) { throw new InvalidOperationException("Failed to read Mifare Classic AccessCode: no key matched sector 0."); } return new FlowResult(target.Kind, target.CardId, m1AccessCode); } private byte[] SendInDataExchange(Pn532Session session, byte tg, ReadOnlySpan payloadToTarget, TimeSpan? timeout = null) { var pn532Payload = new byte[2 + payloadToTarget.Length]; pn532Payload[0] = 0x40; pn532Payload[1] = tg; payloadToTarget.CopyTo(pn532Payload.AsSpan(2)); var response = ExpectPn532ResponseCode(session.SendCommand(pn532Payload, responseTimeout: timeout ?? TimeSpan.FromSeconds(4)), expectedResponseCode: 0x41); if (response.Payload.Length < 2 || response.Payload[1] != 0x00) throw new InvalidOperationException($"InDataExchange failed: 0x{(response.Payload.Length > 1 ? response.Payload[1] : 0):X2}"); // 替换 System.Range 语法 return response.Payload.Length == 2 ? Array.Empty() : response.Payload.Skip(2).ToArray(); } private string? TryReadMifareClassicAccessCode(Pn532Session session, byte tg, ReadOnlySpan uid) { const byte blockNumber = 2; // sector 0 block 2 var uid4 = new byte[4]; Array.Copy(uid.ToArray(), 0, uid4, 0, 4); foreach (var keyHex in MifareClassicKeys) { var key = FromHexString(keyHex); if (TryMifareAuthenticate(session, tg, blockNumber, keyTypeA: true, key, uid4) || TryMifareAuthenticate(session, tg, blockNumber, keyTypeA: false, key, uid4)) { var block = ReadMifareBlock(session, tg, blockNumber); var hex = ToHexString(block); return hex.Length <= 20 ? hex : hex.Substring(hex.Length - 20, 20); } } return null; } public static Pn532FrameParseResult ExpectPn532ResponseCode(Pn532FrameParseResult response, byte expectedResponseCode) { if (response.Kind != Pn532FrameKind.Data) throw new InvalidOperationException($"PN532 error: {response.Kind} ({response.Error})."); if (response.Payload.Length < 1 || response.Payload[0] != expectedResponseCode) throw new InvalidOperationException($"Expected 0x{expectedResponseCode:X2}, got 0x{(response.Payload.Length > 0 ? response.Payload[0] : 0):X2}"); return response; } private void ExpectPn532StatusOk(Pn532FrameParseResult response, byte expectedResponseCode) { response = ExpectPn532ResponseCode(response, expectedResponseCode); if (response.Payload.Length >= 2 && response.Payload[1] != 0x00) throw new InvalidOperationException($"Status fail: 0x{response.Payload[1]:X2}"); } private CardTarget WaitForCard(Pn532Session session) { var f212 = session.SendCommand(new byte[] { 0x4A, 0x01, 0x01, 0x00, 0xFF, 0xFF, 0x01, 0x00 }, TimeSpan.FromMilliseconds(500)); if (ExtractFeliCaTarget(f212, out var target)) return target; var a106 = session.SendCommand(new byte[] { 0x4A, 0x01, 0x00 }, TimeSpan.FromMilliseconds(600)); if (ExtractTypeATarget(a106, out target)) return target; return new CardTarget(CardKind.Null, 0, [0]); } private bool ExtractFeliCaTarget(Pn532FrameParseResult result, out CardTarget target) { target = null; if (result.Kind == Pn532FrameKind.Data && result.Payload.Length >= 15 && result.Payload[0] == 0x4B && result.Payload[1] > 0) { var tg = result.Payload[2]; var idm = result.Payload.AsSpan(5, 8).ToArray(); target = new CardTarget(CardKind.Felica, tg, idm); return true; } return false; } private bool ExtractTypeATarget(Pn532FrameParseResult result, out CardTarget target) { target = null; if (result.Kind == Pn532FrameKind.Data && result.Payload.Length >= 8 && result.Payload[0] == 0x4B && result.Payload[1] > 0) { var tg = result.Payload[2]; var uidLen = result.Payload[6]; var uidOffset = 7; if (uidLen > 0 && result.Payload.Length >= uidOffset + uidLen) { var uid = result.Payload.AsSpan(uidOffset, uidLen).ToArray(); target = new CardTarget(CardKind.MifareClassic, tg, uid); return true; } } return false; } }