PaSoRiとDelphiでNFCカード(MIFARE Ultralight)を読み書き ~Delphiソースコード集
PaSoRi(RC-S3800)をパソコンに接続する前に、
https://www.sony.co.jp/Products/felica/consumer/support/download/nfcportsoftware.html
から「NFCポートソフトウェア」をダウンロードしてダブルクリックしてインストールし、Windowsを再起動します。
その後、PaSoRi(RC-S3800)をパソコンに接続しましょう。
ICタグ(NFCカード)は、Amazonで購入しました。
「NFCタグ」などで検索して2種類購入しました(タグの種類の説明はないので買わないとわからなかったです)が、2種類とも「MIFARE Ultralight C」のNFCタグのICカードでした。
NFCは13.56MHzで動作するRFIDだそうです。
プロジェクトを作成する
Delphiを起動し、メニューから「ファイル」⇒「新規作成」⇒「Windows VCLアプリケーション -Delphi(W)」 をクリックする。
TEdit×1個、TButton×5個、TMemo1×1個を配置します。
[ファイル]⇒[すべて保存]からプロジェクトとユニットの保存をします。
ユニットを追加する
[ファイル]⇒[新規作成]⇒[ユニット]をクリックして新規ユニットを追加します。
以下のソースコードをコピペして、「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 C」は1ブロック(4バイト)ずつしか更新できない
function UpdateBuffer4Byte( BlockNum:Byte;Data:TBytes ):Boolean;
//指定したDataを指定したBlockNumから更新する
function UpdateBuffer(Data:TBytes;BlockNum:Byte=4):Boolean;
public
constructor Create;
destructor Destroy();override;
//リーダーに接続
function ConnectReader():Boolean;
//ConnectReaderを呼んだ後、カードに接続
function ConnectCard():Boolean;
//ATRの取得
function GetATR():String;
//UIDの取得
function GetUID():String;
//指定したブロックの出データを読む
function ReadBlock(BlockNum:Byte):String;
//データを初期化する
function ClearAll():Boolean;
//文字を書き込む(更新する)
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;
//スマートカードリソースマネージャへの接続
Function SCardEstablishContext(
dwScope:DWORD;
pvReserved1:Pointer;
pvReserved2: Pointer;
phContext :PSCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
//コンテキストの解放
Function SCardReleaseContext(
hContext:SCARDCONTEXT
):LONG; stdcall; external 'Winscard.dll';
////カードリーダーのリスト取得
Function SCardListReadersA(
hContext : SCARDCONTEXT;
mszGroups:PAnsiChar;
szReaders:PAnsiChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
////カードリーダーのリスト取得
Function SCardListReadersW(
hContext : SCARDCONTEXT;
mszGroups:PWideChar;
szReaders:PWideChar;
pcchReaders:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの接続
Function SCardConnectA(
hContext : SCARDCONTEXT;
szReaders:PAnsiChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの接続
Function SCardConnectW(
hContext : SCARDCONTEXT;
szReaders:PWideChar;
dwShareMode : DWORD;
dwPreferredProtocols : DWORD;
phCard : PSCARDHANDLE;
pdwActiveProtocols:PDWORD
):LONG; stdcall; external 'Winscard.dll';
//カードの切断
Function SCardDisconnect(
hCard : SCARDHANDLE;
dwDisposition :DWORD
):LONG; stdcall; external 'Winscard.dll';
//カードのステータス取得
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';
//サービス要求を送信し、カードからデータを受信する
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';
//指定したブロック(4Byte)のデータを読む
function ReadBlock(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;
BlockNum:Byte
):TBytes;
//指定したブロック(Blocknum=0が1ブロック目)にdataの先頭4バイトのみ書き込む
//1~4ブロック目は書き込めないようです
function UpdateBuffer4Byte(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; BlockNum:Byte;Data:TBytes
):Boolean;
//指定したブロックにdata(4の倍数バイト分)を書き込む
function UpdateBuffer(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; Data:TBytes; BlockNum:Byte=4
):Boolean;
//4ブロック目から文字を書く
function WreiteString(
hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;st:String
):Boolean;
const
//スマートカードの戻り値
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;
//SCardEstablishContext の dwScope
//データベース操作は、ユーザーのドメイン内で実行されます
SCARD_SCOPE_USER=0;
//コンテキストは現在の端末のコンテキストであり、
//データベース操作はすべてその端末のドメイン内で実行されます
//(呼び出し側アプリケーションには、データベース アクションに対する
// 適切なアクセス許可が必要です。)
SCARD_SCOPE_TERMINAL=1;
//コンテキストはシステム コンテキストであり、
//データベース操作はすべてシステムのドメイン内で実行されます
//(呼び出し側アプリケーションには、データベース アクションに対する
//適切なアクセス許可が必要です。)
SCARD_SCOPE_SYSTEM=2;
//SCardConnectA,SCardConnectW の dwShareMode
SCARD_SHARE_EXCLUSIVE =1;
SCARD_SHARE_SHARED =2;
SCARD_SHARE_DIRECT =3;
//SCardConnectA,SCardConnectW の dwPreferredProtocols
SCARD_PROTOCOL_UNDEFINED =$00000000;
SCARD_PROTOCOL_T0 =$00000001;
SCARD_PROTOCOL_T1 =$00000002;
SCARD_PROTOCOL_RAW =$00010000;
SCARD_PROTOCOL_DEFAULT =$80000000;
//SCardDisconnect の dwDisposition
SCARD_LEAVE_CARD =$00000000;
SCARD_RESET_CARD =$00000001;
SCARD_UNPOWER_CARD =$00000002;
SCARD_EJECT_CARD =$00000003;
//SCardTransmit の pioSendPci
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 読み取るバイト数
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
//16バイト(4Block)読み取ってるけど1ブロックのみ返す(4Byte)
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 ブロック番号4以上
SendBuf[4]:=$04;//Lc 書き込みバイト数4固定
SendBuf[5]:=d[0];//書き込みデータ
SendBuf[6]:=d[1];//書き込みデータ
SendBuf[7]:=d[2];//書き込みデータ
SendBuf[8]:=d[3];//書き込みデータ
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;
//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識別子 TVL開始
data[1]:=paylen+4;
data[2]:=$D1;//MB ME SR TNF
data[3]:=$01;//レコードタイプ $00:Empty $01:Well-Known・・・
data[4]:=paylen;//ペイロードの長さ
data[5]:=Ord('T');//Text:$54
data[6]:=$02; //言語コードの長さ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;//TVLの終わり
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:='カードが見つかりません';
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end
else
begin
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
function TSCard.ConnectReader: Boolean;
var Readers:String;
ReadersLen:DWORD;
begin
result:=False;
fReader:='';//リーダーの名前
fErr:=$00;
fErrStr:='';
//スマートカードリソースマネージャへの接続
fErr:=SCardEstablishContext(SCARD_SCOPE_USER,nil,nil,@fContext);
if fErr=SCARD_S_SUCCESS then
begin
ReadersLen:=255;
SetLength(Readers,ReadersLen);
//リーダーの一覧を取得する
fErr:=SCardListReadersW(fContext,nil,PChar(Readers),@ReadersLen);
if fErr=SCARD_S_SUCCESS then
begin
SetLength(Readers,ReadersLen);
//一覧の最初の1つのリーダーの名前を取得する
fReader:=Readers;
result:=True;
end;
end
else
begin
fErrStr:='カードリーダーが見つかりません';
//コンテキストの解放
SCardReleaseContext(fContext);
end;
end;
constructor TSCard.Create;
begin
fErr:=0;
fErrStr:='';
fReader:='';//リーダーの名前
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:='ATR取得失敗';
end;
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
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:UIDを取得 $01: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:='UID取得失敗';
end;
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
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 読み取るバイト数
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 C」は必ず16Byte(4ブロック)読める
//4Byte(1ブロック分)のみ返す
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:='読み込み失敗';
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
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 ブロック番号4以上
SendBuf[4]:=$04;//Lc 書き込みバイト数4固定
SendBuf[5]:=d[0];//書き込みデータ
SendBuf[6]:=d[1];//書き込みデータ
SendBuf[7]:=d[2];//書き込みデータ
SendBuf[8]:=d[3];//書き込みデータ
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;
//カードの切断
SCardDisconnect( fHCard, SCARD_LEAVE_CARD );
//コンテキストの解放
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識別子 TVL開始
data[1]:=paylen+4;
data[2]:=$D1;//MB ME SR TNF
data[3]:=$01;//レコードタイプ $00:Empty $01:Well-Known・・・
data[4]:=paylen;//ペイロードの長さ
data[5]:=Ord('T');//Text:$54 Uri:$55
data[6]:=$02; //言語コードの長さ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;//TVLの終わり
result:=UpdateBuffer(data);
end;
function TSCard.ClearAll: Boolean;
var i:Integer;
Data:TBytes;
begin
//0~3ブロック ロック
//4~129ブロック 利用可能
//130~134ブロック ロック
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.
フォームのソースコードの入力
Form1のOnCreate、OnDestroy、Button1のOnClickなどに以下のソースコードを記述します。
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 宣言 }
SCard:TSCard;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
//ATRを読む
Memo1.Lines.Add('ATR:'+SCard.GetATR);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
//UIDを読む
Memo1.Lines.Add( 'UID:'+SCard.GetUID() );
end;
procedure TForm1.Button3Click(Sender: TObject);
var i:Integer;
begin
//「MIFARE Ultralight C」の容量は540バイト(4Byte × 135ブロック)
//全ブロックのメモリを読む
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('データのクリア成功')
else
Memo1.Lines.Add('データのクリア失敗');
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
if SCard.UpdateString(Edit1.Text) then
Memo1.Lines.Add('文字列の書き込み成功')
else
Memo1.Lines.Add('文字列の書き込み失敗')
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SCard:=TSCard.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SCard.Free;
end;
end.
実行
PaSoRi(RC-S3800)をパソコンに接続し、ICタグを載せます。
Delphi IDEの「実行」ボタンを押して実行します。
Button1をクリックすると、カードのATRとカードの種類が表示されます。
Button2をクリックすると、カードのUIDが表示されます。
Button3をクリックすると、ICカードの0ブロック目から134ブロック目(全135ブロック、520Byte)を読み込んで4Byteずつ表示します。
Button4をクリックすると4ブロック目~129ブロック目のデータをクリアします。(数秒時間がかかります)
Edit1に「こんにちは」などICカードに入れたい文字列を設定し、
Button5をクリックするとICカードに文字列を書き込みます。
「こんにちは」と書き込んだカードをスマホの背面にかざします。
PaSoRi(RC-S3800)をパソコンに接続する前に、
https://www.sony.co.jp/Products/felica/consumer/support/download/nfcportsoftware.html
から「NFCポートソフトウェア」をダウンロードしてダブルクリックしてインストールし、Windowsを再起動します。
その後、PaSoRi(RC-S3800)をパソコンに接続しましょう。
ICタグ(NFCカード)は、Amazonで購入しました。
「NFCタグ」などで検索して2種類購入しました(タグの種類の説明はないので買わないとわからなかったです)が、2種類とも「MIFARE Ultralight C」のNFCタグのICカードでした。
NFCは13.56MHzで動作するRFIDだそうです。
プロジェクトを作成する
Delphiを起動し、メニューから「ファイル」⇒「新規作成」⇒「Windows VCLアプリケーション -Delphi(W)」 をクリックする。
TEdit×1個、TButton×5個、TMemo1×1個を配置します。
[ファイル]⇒[すべて保存]からプロジェクトとユニットの保存をします。
ユニットを追加する
[ファイル]⇒[新規作成]⇒[ユニット]をクリックして新規ユニットを追加します。
以下のソースコードをコピペして、「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 C」は1ブロック(4バイト)ずつしか更新できない function UpdateBuffer4Byte( BlockNum:Byte;Data:TBytes ):Boolean; //指定したDataを指定したBlockNumから更新する function UpdateBuffer(Data:TBytes;BlockNum:Byte=4):Boolean; public constructor Create; destructor Destroy();override; //リーダーに接続 function ConnectReader():Boolean; //ConnectReaderを呼んだ後、カードに接続 function ConnectCard():Boolean; //ATRの取得 function GetATR():String; //UIDの取得 function GetUID():String; //指定したブロックの出データを読む function ReadBlock(BlockNum:Byte):String; //データを初期化する function ClearAll():Boolean; //文字を書き込む(更新する) 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; //スマートカードリソースマネージャへの接続 Function SCardEstablishContext( dwScope:DWORD; pvReserved1:Pointer; pvReserved2: Pointer; phContext :PSCARDCONTEXT ):LONG; stdcall; external 'Winscard.dll'; //コンテキストの解放 Function SCardReleaseContext( hContext:SCARDCONTEXT ):LONG; stdcall; external 'Winscard.dll'; ////カードリーダーのリスト取得 Function SCardListReadersA( hContext : SCARDCONTEXT; mszGroups:PAnsiChar; szReaders:PAnsiChar; pcchReaders:PDWORD ):LONG; stdcall; external 'Winscard.dll'; ////カードリーダーのリスト取得 Function SCardListReadersW( hContext : SCARDCONTEXT; mszGroups:PWideChar; szReaders:PWideChar; pcchReaders:PDWORD ):LONG; stdcall; external 'Winscard.dll'; //カードの接続 Function SCardConnectA( hContext : SCARDCONTEXT; szReaders:PAnsiChar; dwShareMode : DWORD; dwPreferredProtocols : DWORD; phCard : PSCARDHANDLE; pdwActiveProtocols:PDWORD ):LONG; stdcall; external 'Winscard.dll'; //カードの接続 Function SCardConnectW( hContext : SCARDCONTEXT; szReaders:PWideChar; dwShareMode : DWORD; dwPreferredProtocols : DWORD; phCard : PSCARDHANDLE; pdwActiveProtocols:PDWORD ):LONG; stdcall; external 'Winscard.dll'; //カードの切断 Function SCardDisconnect( hCard : SCARDHANDLE; dwDisposition :DWORD ):LONG; stdcall; external 'Winscard.dll'; //カードのステータス取得 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'; //サービス要求を送信し、カードからデータを受信する 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'; //指定したブロック(4Byte)のデータを読む function ReadBlock( hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; BlockNum:Byte ):TBytes; //指定したブロック(Blocknum=0が1ブロック目)にdataの先頭4バイトのみ書き込む //1~4ブロック目は書き込めないようです function UpdateBuffer4Byte( hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; BlockNum:Byte;Data:TBytes ):Boolean; //指定したブロックにdata(4の倍数バイト分)を書き込む function UpdateBuffer( hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST; Data:TBytes; BlockNum:Byte=4 ):Boolean; //4ブロック目から文字を書く function WreiteString( hCard:SCARDHANDLE; SendPci:SCARD_IO_REQUEST;st:String ):Boolean; const //スマートカードの戻り値 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; //SCardEstablishContext の dwScope //データベース操作は、ユーザーのドメイン内で実行されます SCARD_SCOPE_USER=0; //コンテキストは現在の端末のコンテキストであり、 //データベース操作はすべてその端末のドメイン内で実行されます //(呼び出し側アプリケーションには、データベース アクションに対する // 適切なアクセス許可が必要です。) SCARD_SCOPE_TERMINAL=1; //コンテキストはシステム コンテキストであり、 //データベース操作はすべてシステムのドメイン内で実行されます //(呼び出し側アプリケーションには、データベース アクションに対する //適切なアクセス許可が必要です。) SCARD_SCOPE_SYSTEM=2; //SCardConnectA,SCardConnectW の dwShareMode SCARD_SHARE_EXCLUSIVE =1; SCARD_SHARE_SHARED =2; SCARD_SHARE_DIRECT =3; //SCardConnectA,SCardConnectW の dwPreferredProtocols SCARD_PROTOCOL_UNDEFINED =$00000000; SCARD_PROTOCOL_T0 =$00000001; SCARD_PROTOCOL_T1 =$00000002; SCARD_PROTOCOL_RAW =$00010000; SCARD_PROTOCOL_DEFAULT =$80000000; //SCardDisconnect の dwDisposition SCARD_LEAVE_CARD =$00000000; SCARD_RESET_CARD =$00000001; SCARD_UNPOWER_CARD =$00000002; SCARD_EJECT_CARD =$00000003; //SCardTransmit の pioSendPci 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 読み取るバイト数 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 //16バイト(4Block)読み取ってるけど1ブロックのみ返す(4Byte) 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 ブロック番号4以上 SendBuf[4]:=$04;//Lc 書き込みバイト数4固定 SendBuf[5]:=d[0];//書き込みデータ SendBuf[6]:=d[1];//書き込みデータ SendBuf[7]:=d[2];//書き込みデータ SendBuf[8]:=d[3];//書き込みデータ 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; //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識別子 TVL開始 data[1]:=paylen+4; data[2]:=$D1;//MB ME SR TNF data[3]:=$01;//レコードタイプ $00:Empty $01:Well-Known・・・ data[4]:=paylen;//ペイロードの長さ data[5]:=Ord('T');//Text:$54 data[6]:=$02; //言語コードの長さ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;//TVLの終わり 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:='カードが見つかりません'; //コンテキストの解放 SCardReleaseContext(fContext); end; end else begin //コンテキストの解放 SCardReleaseContext(fContext); end; end; function TSCard.ConnectReader: Boolean; var Readers:String; ReadersLen:DWORD; begin result:=False; fReader:='';//リーダーの名前 fErr:=$00; fErrStr:=''; //スマートカードリソースマネージャへの接続 fErr:=SCardEstablishContext(SCARD_SCOPE_USER,nil,nil,@fContext); if fErr=SCARD_S_SUCCESS then begin ReadersLen:=255; SetLength(Readers,ReadersLen); //リーダーの一覧を取得する fErr:=SCardListReadersW(fContext,nil,PChar(Readers),@ReadersLen); if fErr=SCARD_S_SUCCESS then begin SetLength(Readers,ReadersLen); //一覧の最初の1つのリーダーの名前を取得する fReader:=Readers; result:=True; end; end else begin fErrStr:='カードリーダーが見つかりません'; //コンテキストの解放 SCardReleaseContext(fContext); end; end; constructor TSCard.Create; begin fErr:=0; fErrStr:=''; fReader:='';//リーダーの名前 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:='ATR取得失敗'; end; //カードの切断 SCardDisconnect( fHCard, SCARD_LEAVE_CARD ); //コンテキストの解放 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:UIDを取得 $01: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:='UID取得失敗'; end; //カードの切断 SCardDisconnect( fHCard, SCARD_LEAVE_CARD ); //コンテキストの解放 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 読み取るバイト数 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 C」は必ず16Byte(4ブロック)読める //4Byte(1ブロック分)のみ返す 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:='読み込み失敗'; //カードの切断 SCardDisconnect( fHCard, SCARD_LEAVE_CARD ); //コンテキストの解放 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 ブロック番号4以上 SendBuf[4]:=$04;//Lc 書き込みバイト数4固定 SendBuf[5]:=d[0];//書き込みデータ SendBuf[6]:=d[1];//書き込みデータ SendBuf[7]:=d[2];//書き込みデータ SendBuf[8]:=d[3];//書き込みデータ 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; //カードの切断 SCardDisconnect( fHCard, SCARD_LEAVE_CARD ); //コンテキストの解放 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識別子 TVL開始 data[1]:=paylen+4; data[2]:=$D1;//MB ME SR TNF data[3]:=$01;//レコードタイプ $00:Empty $01:Well-Known・・・ data[4]:=paylen;//ペイロードの長さ data[5]:=Ord('T');//Text:$54 Uri:$55 data[6]:=$02; //言語コードの長さ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;//TVLの終わり result:=UpdateBuffer(data); end; function TSCard.ClearAll: Boolean; var i:Integer; Data:TBytes; begin //0~3ブロック ロック //4~129ブロック 利用可能 //130~134ブロック ロック 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.
フォームのソースコードの入力
Form1のOnCreate、OnDestroy、Button1のOnClickなどに以下のソースコードを記述します。
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 宣言 }
SCard:TSCard;
public
{ Public 宣言 }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
//ATRを読む
Memo1.Lines.Add('ATR:'+SCard.GetATR);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
//UIDを読む
Memo1.Lines.Add( 'UID:'+SCard.GetUID() );
end;
procedure TForm1.Button3Click(Sender: TObject);
var i:Integer;
begin
//「MIFARE Ultralight C」の容量は540バイト(4Byte × 135ブロック)
//全ブロックのメモリを読む
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('データのクリア成功')
else
Memo1.Lines.Add('データのクリア失敗');
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
if SCard.UpdateString(Edit1.Text) then
Memo1.Lines.Add('文字列の書き込み成功')
else
Memo1.Lines.Add('文字列の書き込み失敗')
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
SCard:=TSCard.Create;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SCard.Free;
end;
end.
実行
PaSoRi(RC-S3800)をパソコンに接続し、ICタグを載せます。
Delphi IDEの「実行」ボタンを押して実行します。
Button1をクリックすると、カードのATRとカードの種類が表示されます。
Button2をクリックすると、カードのUIDが表示されます。
Button3をクリックすると、ICカードの0ブロック目から134ブロック目(全135ブロック、520Byte)を読み込んで4Byteずつ表示します。
Button4をクリックすると4ブロック目~129ブロック目のデータをクリアします。(数秒時間がかかります)
Edit1に「こんにちは」などICカードに入れたい文字列を設定し、 Button5をクリックするとICカードに文字列を書き込みます。
「こんにちは」と書き込んだカードをスマホの背面にかざします。