Reading and Writing NFC Cards (NXP NTAG215) with PaSoRi and Delphi – Windows Sample Code Collection
This article is dedicated to NXP NTAG215 cards.
It introduces how to read and write NFC cards (NTAG215) in a Windows environment using PaSoRi (RC-S380) together with Delphi sample source code.
Practical examples using winscard.dll and ScardTransmit are included. While UID can be retrieved from other IC cards as well, the read/write operations described here are specifically tailored for NTAG215.
To read and write IC cards on a PC, a contactless IC card reader/writer is required.
I purchased a used PaSoRi (RC-S380) from Amazon and used it.
Before connecting PaSoRi (RC-S380) to the PC via USB,
please download the “NFC Port Software” from:
https://www.sony.co.jp/Products/felica/consumer/support/download/nfcportsoftware.html
Double-click to install it, then restart Windows.
After that, connect PaSoRi (RC-S380) to your PC.
I purchased the IC tags (NFC cards) from Amazon.
Searching for “NFC tag” gave me two different types (there was no clear description, so I had to buy them to find out), but both turned out to be NXP NTAG215 NFC card tags.
NFC operates as RFID at 13.56 MHz.
NXP NTAG215 provides a writable area of 504 bytes (126 pages × 4).
Page size: 4 bytes
Memory structure
| Page | Byte 0 | Byte 1 | Byte 2 | Byte 3 |
|---|---|---|---|---|
| 0 | UID | Checksum | ||
| 1 | UID | |||
| 2 | Checksum | Internal | Lock bytes | Lock bytes |
| 3 | Capability Container (CC) E1 (magic number indicating NDEF presence), version, NDEF message size (×8), 00 |
|||
| 4 | User Memory | |||
| ... | ||||
| 129 | ||||
| 130 | Dynamic lock byte | RFUI | ||
| 131 | CFG0 | |||
| 132 | CFG1 | |||
| 133 | PWD | |||
| 134 | PACK | RFUI | ||
Windows API Functions Related to Card Readers
- Connect to the Smart Card Resource Manager
-
Function SCardEstablishContext( dwScope : DWORD; pvReserved1 : Pointer; pvReserved2 : Pointer; phContext : PSCARDCONTEXT ):LONG; stdcall; external 'Winscard.dll';
- Release the Context
-
Function SCardReleaseContext( hContext:SCARDCONTEXT ):LONG; stdcall; external 'Winscard.dll';
- Retrieve the List of Card Readers
-
Function SCardListReadersW( hContext : SCARDCONTEXT; mszGroups:PWideChar; szReaders:PWideChar; pcchReaders:PDWORD ):LONG; stdcall; external 'Winscard.dll';
- Connect to a Card
-
Function SCardConnectW( hContext : SCARDCONTEXT; szReaders:PWideChar; dwShareMode : DWORD; dwPreferredProtocols : DWORD; phCard : PSCARDHANDLE; pdwActiveProtocols:PDWORD ):LONG; stdcall; external 'Winscard.dll';
- Disconnect from a Card
-
Function SCardDisconnect( hCard : SCARDHANDLE; dwDisposition :DWORD ):LONG; stdcall; external 'Winscard.dll';
- Retrieve Card Status
-
Function SCardStatusW( hCard : SCARDHANDLE; szReaderNames :PWideChar; pcchReaderLen : PDWORD; pdwState :PDWORD; pdwProtocol :PDWORD; pbATR :PBYTE; pcbAtrLen :PDWORD ):LONG; stdcall; external 'Winscard.dll';
- Send a Service Request and Receive Data from the Card
-
Function SCardTransmit( hCard : SCARDHANDLE; pioSendPci :PSCARD_IO_REQUEST; pbSendBuffer :PByte; cbSendLength :DWORD; pioRecvPci :PSCARD_IO_REQUEST; pbRecvBuffer :PByte; pcbRecvLength:PDWORD ):LONG; stdcall; external 'Winscard.dll';
SCardTransmit() Commands
To read or write UID and data from an RFID card, use the “SCardTransmit” API function.
The command is sent via the pointer to the byte array passed as the third argument of the “SCardTransmit” API function.
-
Byte 0 CLA $FF Byte 1 INS $CA Byte 2 P1 $00 Byte 3 P2 $00 Byte 4 Le $00
- Retrieve UID
- Read Data
| Byte 0 | CLA | $FF |
| Byte 1 | INS | $B0 |
| Byte 2 | P1 | $00 |
| Byte 3 | P2 | Page (Block) Number |
| Byte 4 | Le | Number of Bytes to Read |
- Update (Write) Data
| Byte 0 | CLA | $FF |
| Byte 1 | INS | $D6 |
| Byte 2 | P1 | $00 |
| Byte 3 | P2 | Page (Block) Number |
| Byte 4 | Lc | Number of Bytes to Update (For NTAG215, $04) |
| Byte 5 | DATA0 | Update Data |
| Byte 6 | DATA1 | Update Data |
| Byte 7 | DATA2 | Update Data |
| Byte 8 | DATA3 | Update Data |
For cards such as Mifare Classic 1K, authentication is required every four sectors, and the following commands must be used:
- Load Authentication Key
| Byte 0 | CLA | $FF |
| Byte 1 | INS | $82 |
| Byte 2 | P1 | $00 |
| Byte 3 | P2 | Key Number (e.g., $00) |
| Byte 4 | Lc | Length of Data (Key) ($06) |
| Byte 5 | DATA0 | (Example) $FF |
| Byte 6 | DATA1 | (Example) $FF |
| Byte 7 | DATA2 | (Example) $FF |
| Byte 8 | DATA3 | (Example) $FF |
| Byte 9 | DATA4 | (Example) $FF |
| Byte 10 | DATA5 | (Example) $FF |
- Authenticate
| Byte 0 | CLA | $FF |
| Byte 1 | INS | $86 |
| Byte 2 | P1 | $00 |
| Byte 3 | P2 | Key Number (e.g., $00) |
| Byte 4 | Lc | Length of Data ($05) |
| Byte 5 | DATA0 | $01 |
| Byte 6 | DATA1 | $00 |
| Byte 7 | DATA2 | Block Number |
| Byte 8 | DATA3 | $60 (KeyA) or $61 (KeyB) |
| Byte 9 | DATA4 | $00 |
Format
When writing data to an RFID card, it must be written in the NDEF (NFC Data Exchange Format).
| $03 | TLV start marker | ||||||||||||||||
| $xx | Number of bytes in NDEF | ||||||||||||||||
| (Example) $D1 |
|
||||||||||||||||
| (Example) $01 | Type Length (length of the Type field) | ||||||||||||||||
| (Example) $04 | Payload Length | ||||||||||||||||
| ID Length (omitted if IL=0) | |||||||||||||||||
| (Example) $54 | Type (according to Type Length). If IL=0: $54 = Text, $55 = URI, ... | ||||||||||||||||
| ID (omitted if IL=0) | |||||||||||||||||
|
|||||||||||||||||
| $FE | TLV terminator | ||||||||||||||||
Create the Project
Launch Delphi, then from the menu select “File” ⇒ “New” ⇒ “Windows VCL Application - Delphi (W)”.
Place one TEdit, five TButton components, and one TMemo component on the form.
Save the project and unit files via [File] ⇒ [Save All].
Add a Unit
From the menu, select [File] ⇒ [New] ⇒ [Unit] to add a new unit.
Copy and paste the following source code, then save it as “winscard.pas”.
unit winscard;
interface
uses Winapi.Windows,System.SysUtils;
type
SCARDCONTEXT=ULONG;
PSCARDCONTEXT=^SCARDCONTEXT;
SCARDHANDLE = ULONG;
PSCARDHANDLE = ^SCARDHANDLE;
SCARD_IO_REQUEST = Record
dwProtocol :DWORD;
cbPciLength :DWORD;
end;
PSCARD_IO_REQUEST=^SCARD_IO_REQUEST;
TSCard=class(TObject)
private
fReader:String;
fContext:SCARDCONTEXT;
fErr:LONG;
fErrStr:String;
fHCard:SCARDHandle;
fProtocol:DWORD;
fSendPci:SCARD_IO_REQUEST;
fATR:TBytes;
fCardName:String;
fReadBlockData:TBytes;
procedure GetCardName();
// “MIFARE Ultralight” and “NXP NTAG215” can only be updated one block (4 bytes) at a time
function UpdateBuffer4Byte( BlockNum:Byte;Data:TBytes ):Boolean;
// Update the specified Data starting from the specified BlockNum
function UpdateBuffer(Data:TBytes;BlockNum:Byte=4):Boolean;
public
constructor Create;
destructor Destroy();override;
// Connect to the reader
function ConnectReader():Boolean;
// After calling ConnectReader, connect to the card
function ConnectCard():Boolean;
// Retrieve ATR
function GetATR():String;
// Retrieve UID
function GetUID():String;
// Read data from the specified block
function ReadBlock(BlockNum:Byte):String;
// Initialize data
function ClearAll():Boolean;
// Write (update) a string
function UpdateString(WriteStr:String):Boolean;
property Err:LONG read fErr;
property ErrStr:String read fErrStr;
property Reader:String read fReader;
property CardName:String read fCardName;
property ATR:TBytes read fATR;
end;
// Connect to the Smart Card Resource Manager
Function SCardEstablishContext(
dwScope:DWORD;
pvReserved1:Pointer;
pvReserved2: Pointer;
phContext :PSCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
// Release the context
Function SCardReleaseContext(
hContext:SCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
//// Retrieve the list of card readers
Function SCardListReadersA(
hContext : SCARDCONTEXT;
mszGroups:PAnsiChar;
szReaders:PAnsiChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//// Retrieve the list of card readers
Function SCardListReadersW(
hContext : SCARDCONTEXT;
mszGroups:PWideChar;
szReaders:PWideChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
// Connect to the card
Function SCardConnectA(
hContext : SCARDCONTEXT;
szReaders:PAnsiChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
// Connect to the card
Function SCardConnectW(
hContext : SCARDCONTEXT;
szReaders:PWideChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
// Disconnect from the card
Function SCardDisconnect(
hCard : SCARDHANDLE;
dwDisposition :DWORD
):LONG; stdcall; external 'Winscard.dll';
// Retrieve card status
Function SCardStatusA(
hCard : SCARDHANDLE;
szReaderNames :PAnsiChar;
pcchReaderLen : PDWORD;
pdwState :PDWORD;
pdwProtocol :PDWORD;
pbATR :PBYTE;
pcbAtrLen :PDWORD
):LONG; stdcall; external 'Winscard.dll';
Function SCardStatusW(
hCard : SCARDHANDLE;
szReaderNames :PWideChar;
pcchReaderLen : PDWORD;
pdwState :PDWORD;
pdwProtocol :PDWORD;
pbATR :PBYTE;
pcbAtrLen :PDWORD
):LONG; stdcall; external 'Winscard.dll';
// Send a service request and receive data from the card
Function SCardTransmit(
hCard : SCARDHANDLE;
pioSendPci :PSCARD_IO_REQUEST;
pbSendBuffer :PByte;
cbSendLength :DWORD;
pioRecvPci :PSCARD_IO_REQUEST;
pbRecvBuffer :PByte;
pcbRecvLength:PDWORD
):LONG; stdcall; external 'Winscard.dll';
// Read data from the specified block (4 bytes)
function ReadBlock(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;
BlockNum:Byte
):TBytes;
// Write only the first 4 bytes of data to the specified block (BlockNum=0 is the first block)
// Blocks 1–4 cannot be written
function UpdateBuffer4Byte(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; BlockNum:Byte;Data:TBytes
):Boolean;
// Write data (multiples of 4 bytes) to the specified block
function UpdateBuffer(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; Data:TBytes; BlockNum:Byte=4
):Boolean;
// Write a string starting from block 4
function WreiteString(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;st:String
):Boolean;
const
// Smart card return values
ERROR_BROKEN_PIPE =$00000109;
SCARD_E_BAD_SEEK =$80100029;
SCARD_E_CANCELLED =$80100002;
SCARD_E_CANT_DISPOSE =$8010000E;
SCARD_E_CARD_UNSUPPORTED=$8010001C;
SCARD_E_CERTIFICATE_UNAVAILABLE=$8010002D;
SCARD_E_COMM_DATA_LOST =$8010002;
SCARD_E_DIR_NOT_FOUND =$80100023;
SCARD_E_DUPLICATE_READER=$8010001B;
SCARD_E_FILE_NOT_FOUND =$80100024;
SCARD_E_ICC_CREATEORDER =$80100021;
SCARD_E_ICC_INSTALLATION=$80100020;
SCARD_E_INSUFFICIENT_BUFFER=$80100008;
SCARD_E_INVALID_ATR =$80100015;
SCARD_E_INVALID_CHV =$8010002A;
SCARD_E_INVALID_HANDLE =$80100003;
SCARD_E_INVALID_PARAMETER=$80100004;
SCARD_E_INVALID_TARGET =$80100005;
SCARD_E_INVALID_VALUE =$80100011;
SCARD_E_NO_ACCESS =$80100027;
SCARD_E_NO_DIR =$80100025;
SCARD_E_NO_FILE =$80100026;
SCARD_E_NO_KEY_CONTAINER=$80100030;
SCARD_E_NO_MEMORY =$80100006;
SCARD_E_NO_PIN_CACHE =$80100033;
SCARD_E_NO_READERS_AVAILABLE=$8010002E;
SCARD_E_NO_SERVICE =$8010001D;
SCARD_E_NO_SMARTCARD =$8010000C;
SCARD_E_NO_SUCH_CERTIFICATE=$8010002C;
SCARD_E_NOT_READY =$80100010;
SCARD_E_NOT_TRANSACTED =$80100016;
SCARD_E_PCI_TOO_SMALL =$80100019;
SCARD_E_PIN_CACHE_EXPIRED=$80100032;
SCARD_E_PROTO_MISMATCH =$8010000F;
SCARD_E_READ_ONLY_CARD =$80100034;
SCARD_E_READER_UNAVAILABLE=$80100017;
SCARD_E_READER_UNSUPPORTED=$8010001A;
SCARD_E_SERVER_TOO_BUSY =$80100031;
SCARD_E_SERVICE_STOPPED =$8010001E;
SCARD_E_SHARING_VIOLATION=$8010000B;
SCARD_E_SYSTEM_CANCELLED=$80100012;
SCARD_E_TIMEOUT =$8010000A;
SCARD_E_UNEXPECTED =$8010001F;
SCARD_E_UNKNOWN_CARD =$8010000D;
SCARD_E_UNKNOWN_READER =$80100009;
SCARD_E_UNKNOWN_RES_MNG =$8010002B;
SCARD_E_UNSUPPORTED_FEATURE=$80100022;
SCARD_E_WRITE_TOO_MANY =$80100028;
SCARD_F_COMM_ERROR =$80100013;
SCARD_F_INTERNAL_ERROR =$80100001;
SCARD_F_UNKNOWN_ERROR =$80100014;
SCARD_F_WAITED_TOO_LONG =$80100007;
SCARD_P_SHUTDOWN =$80100018;
SCARD_S_SUCCESS =$00000000;
SCARD_W_CANCELLED_BY_USER=$8010006E;
SCARD_W_CACHE_ITEM_NOT_FOUND=$80100070;
SCARD_W_CACHE_ITEM_STALE=$80100071;
SCARD_W_CACHE_ITEM_TOO_BIG=$80100072;
SCARD_W_CARD_NOT_AUTHENTICATED=$8010006F;
SCARD_W_CHV_BLOCKED =$8010006C;
SCARD_W_EOF =$8010006D;
SCARD_W_REMOVED_CARD =$80100069;
SCARD_W_RESET_CARD =$80100068;
SCARD_W_SECURITY_VIOLATION=$8010006A;
SCARD_W_UNPOWERED_CARD =$80100067;
SCARD_W_UNRESPONSIVE_CARD=$80100066;
SCARD_W_UNSUPPORTED_CARD=$80100065;
SCARD_W_WRONG_CHV =$8010006B;
// dwScope for SCardEstablishContext
// Database operations are executed within the user’s domain
SCARD_SCOPE_USER=0;
// The context is the current terminal context,
// and all database operations are executed within that terminal’s domain
// (The calling application must have appropriate access rights for database actions.)
SCARD_SCOPE_TERMINAL=1;
// The context is the system context,
// and all database operations are executed within the system’s domain
// (The calling application must have appropriate access rights for database actions.)
SCARD_SCOPE_SYSTEM=2;
// dwShareMode for SCardConnectA, SCardConnectW
SCARD_SHARE_EXCLUSIVE =1;
SCARD_SHARE_SHARED =2;
SCARD_SHARE_DIRECT =3;
// dwPreferredProtocols for SCardConnectA, SCardConnectW
SCARD_PROTOCOL_UNDEFINED =$00000000;
SCARD_PROTOCOL_T0 =$00000001;
SCARD_PROTOCOL_T1 =$00000002;
SCARD_PROTOCOL_RAW =$00010000;
SCARD_PROTOCOL_DEFAULT =$80000000;
// dwDisposition for SCardDisconnect
SCARD_LEAVE_CARD =$00000000;
SCARD_RESET_CARD =$00000001;
SCARD_UNPOWER_CARD =$00000002;
SCARD_EJECT_CARD =$00000003;
// pioSendPci for SCardTransmit
SCARD_PCI_T0 : SCARD_IO_REQUEST = (dwProtocol:1; cbPciLength:8);
SCARD_PCI_T1 : SCARD_IO_REQUEST = (dwProtocol:2; cbPciLength:8);
implementation
function ReadBlock(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;
BlockNum:Byte
):TBytes;
var res:DWORD;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
begin
SetLength(Result,4);
ZeroMemory(Result,4);
SendLen:=255;
SetLength(SendBuf,SendLen);
ZeroMemory(@SendBuf[0],SendLen);
SendBuf[0]:=$FF; // CLA
SendBuf[1]:=$B0; // INS FFB0: ReadBinary
SendBuf[2]:=$00; // P1
SendBuf[3]:=BlockNum;// P2
SendBuf[4]:=$04; // Le: number of bytes to read
RecvLen:=255;
SetLength(RecvBuf,RecvLen);
ZeroMemory(@RecvBuf[0],RecvLen);
Res:=SCardTransmit(
hCard,@SendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if Res=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
// Reads 16 bytes (4 blocks), but only returns one block (4 bytes)
move(Recvbuf[0],Result[0],4);
end;
end;
end;
function UpdateBuffer4Byte(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; BlockNum:Byte;Data:TBytes
):Boolean;
var len:Integer;
res:DWORD;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
d:TBytes;
begin
result:=False;
len:=Length(Data);
if len=0 then exit;
if len>4 then len:=4;
SetLength(d,4);
ZeroMemory(d,4);
// CopyMemory(@d[0],@Data[0],len);
Move(Data[0],d[0],len);
SendLen:=9;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF; // CLA
SendBuf[1]:=$D6; // INS FFD6: Update Binary
SendBuf[2]:=$00; // P1
SendBuf[3]:=BlockNum; // P2: block number 4 or higher
SendBuf[4]:=$04; // Lc: fixed write length of 4 bytes
SendBuf[5]:=d[0]; // write data
SendBuf[6]:=d[1]; // write data
SendBuf[7]:=d[2]; // write data
SendBuf[8]:=d[3]; // write data
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
res:=SCardTransmit(
hCard,@SendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if res=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
result:=True;
end;
end;
function UpdateBuffer(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; Data:TBytes; BlockNum:Byte=4
):Boolean;
var i:Integer;
d:TBytes;
bn:Byte;
len:Integer;
begin
result:=False;
if Length(Data)=0 then exit;
SetLength(d,4);
for i := 0 to (Length(Data) div 4) do
begin
ZeroMemory(@d[0],4);
len:=Length(Data)-i*4;
if len>4 then len:=4;
Move(Data[i*4],d[0],len);
bn:=BlockNum+i;
result:=UpdateBuffer4Byte(hCard,SendPci,bn,d);
if result=false then break;
end;
end;
// Write text starting from block 4
function WreiteString(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;st:String
):Boolean;
var arr,data:TBytes;
paylen:Byte;
i:Integer;
BlockNum:Byte;
begin
BlockNum:=4;
arr:=TEncoding.UTF8.GetBytes(st);
paylen:=Length(arr)+3;
SetLength(data,paylen+7);
data[0]:=$03; // NDEF identifier TLV start
data[1]:=paylen+4;
data[2]:=$D1; // MB ME SR TNF
data[3]:=$01; // Record type $00: Empty, $01: Well-Known, ...
data[4]:=paylen; // Payload length
data[5]:=Ord('T'); // Text: $54
data[6]:=$02; // Language code length = 2
data[7]:=Ord('j'); // j
data[8]:=Ord('a'); // a
for i := Low(arr) to High(arr) do
data[9+i]:=arr[i];
data[9+Length(arr)]:=$FE; // TLV terminator
result:=UpdateBuffer(
hCard,SendPci,
data,BlockNum
);
end;
{ TSCard }
function TSCard.ConnectCard: Boolean;
var ActiveProtocols:DWORD;
begin
result:=False;
if ConnectReader() then
begin
fErr:=SCardConnectW(
fContext,PChar(fReader),SCARD_SHARE_SHARED,
SCARD_PROTOCOL_T0 or SCARD_PROTOCOL_T1,@fHCard,@ActiveProtocols
);
if fErr=SCARD_S_SUCCESS then
begin
if (ActiveProtocols and SCARD_PROTOCOL_T1)>0 then
begin
fProtocol:=SCARD_PROTOCOL_T1;
fSendPci:=SCARD_PCI_T1;
end
else
begin
fProtocol:=SCARD_PROTOCOL_T0;
fSendPci:=SCARD_PCI_T0;
end;
result:=True;
end
else
begin
fErrStr:='Card not found';
// Release the context
SCardReleaseContext(fContext);
end;
end
else
begin
// Release the context
SCardReleaseContext(fContext);
end;
end;
function TSCard.ConnectReader: Boolean;
var Readers:String;
ReadersLen:DWORD;
begin
result:=False;
fReader:=''; // Reader name
fErr:=$00;
fErrStr:='';
// Connect to the Smart Card Resource Manager
fErr:=SCardEstablishContext(SCARD_SCOPE_USER,nil,nil,@fContext);
if fErr=SCARD_S_SUCCESS then
begin
ReadersLen:=255;
SetLength(Readers,ReadersLen);
// Retrieve the list of readers
fErr:=SCardListReadersW(fContext,nil,PChar(Readers),@ReadersLen);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(Readers,ReadersLen);
// Get the name of the first reader in the list
fReader:=Readers;
result:=True;
end;
end
else
begin
fErrStr:='Card reader not found';
// Release the context
SCardReleaseContext(fContext);
end;
end;
constructor TSCard.Create;
begin
fErr:=0;
fErrStr:='';
fReader:=''; // Reader name
end;
destructor TSCard.Destroy;
begin
inherited;
end;
function TSCard.GetATR: String;
var i:Integer;
AtrLen:DWORD;
ReadersLen,State:DWORD;
begin
Result:='';
if ConnectCard() then
begin
AtrLen:=32;
SetLength(fATR,AtrLen);
fErr:=SCardStatusW(
fHCard,PChar(fReader),@ReadersLen,@State,
@fProtocol,
@fATR[0],@AtrLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(fATR,AtrLen);
self.GetCardName();
for i := 0 to AtrLen-1 do
Result:=Result+IntToHex(fAtr[i],2);
Result:=Result+'('+fCardName+')';
end
else
begin
SetLength(fATR,0);
fErrStr:='Failed to retrieve ATR';
end;
// Disconnect the card
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
// Release the context
SCardReleaseContext(fContext);
end;
end;
procedure TSCard.GetCardName();
begin
if Length(fATR)<15 then
fCardName:='UnKnown'
else if (fATR[13]=$00) and (fATR[14]=$01) then
fCardName:='MIFARE Classic 1K'
else if (fATR[13]=$00) and (fATR[14]=$02) then
fCardName:='MIFARE Classic 4K'
else if (fATR[13]=$00) and (fATR[14]=$03) then
fCardName:='MIFARE Ultralight'
else if (fATR[13]=$00) and (fATR[14]=$26) then
fCardName:='MIFARE Mini'
else if (fATR[13]=$00) and (fATR[14]=$3A) then
fCardName:='MIFARE Ultralight C'
else if (fATR[13]=$00) and (fATR[14]=$36) then
fCardName:='MIFARE Plus SL1 2k'
else if (fATR[13]=$00) and (fATR[14]=$37) then
fCardName:='MIFARE Plus SL1 4k'
else if (fATR[13]=$00) and (fATR[14]=$38) then
fCardName:='MIFARE Plus SL2 2k'
else if (fATR[13]=$00) and (fATR[14]=$39) then
fCardName:='MIFARE Plus SL2 4k'
else if (fATR[13]=$00) and (fATR[14]=$30) then
fCardName:='Topaz and Jewel'
else if (fATR[13]=$00) and (fATR[14]=$3B) then
fCardName:='FeliCa'
else if (fATR[13]=$FF) and (fATR[14]=$28) then
fCardName:='JCOP 30'
else if (fATR[13]=$00) and (fATR[14]=$07) then
fCardName:='SRIX'
else
fCardName:='UnKnown';
end;
function TSCard.GetUID: String;
var SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
i:Integer;
begin
Result:='';
if ConnectCard() then
begin
SendLen:=5;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF; // CLA
SendBuf[1]:=$CA; // INS
SendBuf[2]:=$00; // P1 $00: get UID, $01: get ATS
SendBuf[3]:=$00; // P2
SendBuf[4]:=$00; // Le
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
ZeroMemory(RecvBuf,RecvLen);
fErr:=SCardTransmit(
fHCard,@fSendPci,@SendBuf[0],SendLen,nil,@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
for i := Low(RecvBuf) to High(RecvBuf)-2 do
begin
Result:=Result+IntToHex(RecvBuf[i],2);
end;
end
else
begin
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end;
end
else
begin
fErrStr:='Failed to retrieve UID';
end;
// Disconnect the card
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
// Release the context
SCardReleaseContext(fContext);
end;
end;
function TSCard.ReadBlock(BlockNum: Byte): String;
var SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
i:Integer;
begin
Result:='';
if ConnectCard() then
begin
SendLen:=5;
SetLength(SendBuf,SendLen);
ZeroMemory(SendBuf,SendLen);
SendBuf[0]:=$FF; // CLA
SendBuf[1]:=$B0; // INS FFB0: ReadBinary
SendBuf[2]:=$00; // P1
SendBuf[3]:=BlockNum;// P2
SendBuf[4]:=$10; // Le: number of bytes to read
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
ZeroMemory(RecvBuf,RecvLen);
fErr:=SCardTransmit(
fHCard,@fSendPci,@SendBuf[0],SendLen,nil,@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
begin
// “MIFARE Ultralight” and “NXP NTAG215” always return 16 bytes (4 blocks)
// Only return 4 bytes (1 block)
SetLength(fReadBlockData,4);
for i := 0 to 3 do
begin
fReadBlockData[i]:=RecvBuf[i];
Result:=Result+IntToHex(RecvBuf[i],2);
end;
end
else
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end
else
fErrStr:='Read failed';
// Disconnect the card
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
// Release the context
SCardReleaseContext(fContext);
end;
end;
function TSCard.UpdateBuffer(Data: TBytes; BlockNum: Byte): Boolean;
var i:Integer;
d:TBytes;
WriteBlockNum:Byte;
len:Integer;
begin
result:=False;
if Length(Data)=0 then exit;
SetLength(d,4);
for i := 0 to (Length(Data) div 4) do
begin
ZeroMemory(@d[0],4);
len:=Length(Data)-i*4;
if len>4 then len:=4;
Move(Data[i*4],d[0],len);
WriteBlockNum:=BlockNum+i;
result:=UpdateBuffer4Byte(WriteBlockNum,d);
if result=false then break;
end;
end;
function TSCard.UpdateBuffer4Byte(BlockNum: Byte; Data: TBytes): Boolean;
var len:Integer;
d:TBytes;
SendLen,RecvLen:DWORD;
SendBuf,RecvBuf:TBytes;
begin
Result:=false;
if ConnectCard() then
begin
len:=Length(Data);
if len=0 then exit;
if len>4 then len:=4;
SetLength(d,4);
ZeroMemory(d,4);
Move(Data[0],d[0],len);
SendLen:=9;
SetLength(SendBuf,SendLen);
SendBuf[0]:=$FF; // CLA
SendBuf[1]:=$D6; // INS FFD6: Update Binary
SendBuf[2]:=$00; // P1
SendBuf[3]:=BlockNum; // P2: block number 4 or higher
SendBuf[4]:=$04; // Lc: fixed write length of 4 bytes
SendBuf[5]:=d[0]; // write data
SendBuf[6]:=d[1]; // write data
SendBuf[7]:=d[2]; // write data
SendBuf[8]:=d[3]; // write data
RecvLen:=256;
SetLength(RecvBuf,RecvLen);
fErr:=SCardTransmit(
fHCard,@fSendPci,
@SendBuf[0],SendLen,
nil,
@RecvBuf[0],@RecvLen
);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(RecvBuf,RecvLen);
if (RecvBuf[RecvLen-2]=$90) and (RecvBuf[RecvLen-1]=$00) then
result:=True;
end
else
begin
fErrStr:=Format(
'SW1:%2.2x SW2:%2.2x',
[RecvBuf[RecvLen-2],RecvBuf[RecvLen-1]]
);
end;
// Disconnect the card
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
// Release the context
SCardReleaseContext(fContext);
end;
end;
function TSCard.UpdateString(WriteStr: String): Boolean;
var arr,data:TBytes;
paylen:Byte;
i:Integer;
begin
arr:=TEncoding.UTF8.GetBytes(WriteStr);
paylen:=Length(arr)+3;
SetLength(data,paylen+7);
data[0]:=$03; // NDEF identifier TLV start
data[1]:=paylen+4;
data[2]:=$D1; // MB ME SR TNF
data[3]:=$01; // Record type $00: Empty, $01: Well-Known, ...
data[4]:=paylen; // Payload length
data[5]:=Ord('T'); // Text: $54, URI: $55
data[6]:=$02; // Language code length = 2
data[7]:=Ord('j'); // j
data[8]:=Ord('a'); // a
for i := Low(arr) to High(arr) do
data[9+i]:=arr[i];
data[9+Length(arr)]:=$FE; // TLV terminator
result:=UpdateBuffer(data);
end;
function TSCard.ClearAll: Boolean;
var i:Integer;
Data:TBytes;
begin
// Blocks 0–3: locked
// Blocks 4–129: available
// Blocks 130–134: locked
SetLength(Data,4);
Data[0]:=$03;
Data[1]:=$00;
Data[2]:=$FE;
Data[3]:=$00;
Result:=Self.UpdateBuffer4Byte(4,Data);
if Result then
begin
Data[0]:=$00;
Data[1]:=$00;
Data[2]:=$00;
Data[3]:=$00;
for i := 5 to 129 do
begin
Result:=Self.UpdateBuffer4Byte(i,Data);
if Result=False then Break;
end;
end;
end;
end.
Enter the Form Source Code
Write the following source code in Form1’s OnCreate, OnDestroy, and Button1’s OnClick, etc.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls ,winscard;
type
TForm1 = class(TForm)
Button1: TButton;
Memo1: TMemo;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Edit1: TEdit;
Button5: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
{ Private declarations }
SCard:TSCard;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
// Read ATR
Memo1.Lines.Add('ATR:'+SCard.GetATR);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
// Read UID
Memo1.Lines.Add('UID:'+SCard.GetUID() );
end;
procedure TForm1.Button3Click(Sender: TObject);
var i:Integer;
begin
// Capacity of “NXP NTAG215” is 540 bytes (4 bytes × 135 blocks)
// Read memory of all blocks
for i := 0 to 134 do
begin
Memo1.Lines.Add(
Format('%2.2x:',[i])+SCard.ReadBlock(i)
);
end;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
if SCard.ClearAll() then
Memo1.Lines.Add('Data clear succeeded')
else
Memo1.Lines.Add('Data clear failed');
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
if SCard.UpdateString(Edit1.Text) then
Memo1.Lines.Add('String write succeeded')
else
Memo1.Lines.Add('String write failed');
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SCard:=TSCard.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SCard.Free;
end;
end.
Execution
Connect the PaSoRi (RC-S3800) to your PC and place the IC tag on it.
Press the “Run” button in the Delphi IDE to execute.
Clicking Button1 will display the card’s ATR and card type.
Clicking Button2 will display the card’s UID.
Clicking Button3 will read blocks 0 through 134 of the IC card (all 135 blocks, 520 bytes) and display them in 4-byte units.
Clicking Button4 will clear the data from blocks 4 through 129 (this takes a few seconds).
Enter a string such as “Hello” in Edit1, and clicking Button5 will write the string to the IC card.
Hold the card with “Hello” written on it against the back of a smartphone.
