トップへ(mam-mam.net/)

Reading and Writing NFC Cards (NXP NTAG215) with PaSoRi and Delphi – Windows Sample Code Collection

Japanese

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.

PaSoRi RC-S380 Contactless IC Card Reader/Writer

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.

IC Tag NXP NTAG215 NFC Card
IC Tag NXP NTAG215 NFC Tag

NXP NTAG215 provides a writable area of 504 bytes (126 pages × 4).
Page size: 4 bytes
Memory structure

PageByte 0Byte 1Byte 2Byte 3
0UIDChecksum
1UID
2ChecksumInternalLock bytesLock bytes
3Capability Container (CC)
E1 (magic number indicating NDEF presence), version, NDEF message size (×8), 00
4User Memory
...
129
130Dynamic lock byteRFUI
131CFG0
132CFG1
133PWD
134PACKRFUI

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.

Retrieve UID
Byte 0CLA$FF
Byte 1INS$CA
Byte 2P1$00
Byte 3P2$00
Byte 4Le$00
Read Data
Byte 0CLA$FF
Byte 1INS$B0
Byte 2P1$00
Byte 3P2Page (Block) Number
Byte 4LeNumber of Bytes to Read
Update (Write) Data
Byte 0CLA$FF
Byte 1INS$D6
Byte 2P1$00
Byte 3P2Page (Block) Number
Byte 4LcNumber of Bytes to Update
(For NTAG215, $04)
Byte 5DATA0Update Data
Byte 6DATA1Update Data
Byte 7DATA2Update Data
Byte 8DATA3Update 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 0CLA$FF
Byte 1INS$82
Byte 2P1$00
Byte 3P2Key Number (e.g., $00)
Byte 4LcLength of Data (Key) ($06)
Byte 5DATA0(Example) $FF
Byte 6DATA1(Example) $FF
Byte 7DATA2(Example) $FF
Byte 8DATA3(Example) $FF
Byte 9DATA4(Example) $FF
Byte 10DATA5(Example) $FF
Authenticate
Byte 0CLA$FF
Byte 1INS$86
Byte 2P1$00
Byte 3P2Key Number (e.g., $00)
Byte 4LcLength of Data ($05)
Byte 5DATA0$01
Byte 6DATA1$00
Byte 7DATA2Block Number
Byte 8DATA3$60 (KeyA) or $61 (KeyB)
Byte 9DATA4$00

Format

When writing data to an RFID card, it must be written in the NDEF (NFC Data Exchange Format).

$03TLV start marker
$xxNumber of bytes in NDEF
(Example) $D1
Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
MB
(Message Begin)
ME
(Message End)
CF
(Chunk Flag)
SR
(Short Record, ≤255 bytes)
IL
(ID Length present)
TNF
(Type Name Format $00:Empty $01:Well-Known ...)
(Example) $01Type Length (length of the Type field)
(Example) $04Payload Length
ID Length (omitted if IL=0)
(Example) $54Type (according to Type Length). If IL=0: $54 = Text, $55 = URI, ...
ID (omitted if IL=0)
Payload
(Example) If Type = $54 (Text):
First, the length of the language code ($02)
followed by the two-character language code ('ja' = $6A $61)
followed by the UTF-8 string byte array.
$FETLV 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].

Delphi VCL Project Setup

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.

Read the ATR of the IC card

Clicking Button2 will display the card’s UID.

Read the UID of the IC card

Clicking Button3 will read blocks 0 through 134 of the IC card (all 135 blocks, 520 bytes) and display them in 4-byte units.

Read all block data from the IC card

Clicking Button4 will clear the data from blocks 4 through 129 (this takes a few seconds).

Clear the memory area of the IC card

Enter a string such as “Hello” in Edit1, and clicking Button5 will write the string to the IC card.

Write a string to the IC card

Hold the card with “Hello” written on it against the back of a smartphone.

Hold the card with 'Hello' written on it against the back of a smartphone