UnityでFeliCaからIDmを取得する
UnityでPaSoRiから取得したIDmをもとにログインなどの処理を行う。
winscard.dll(PC/SC)を用いるため、Windowsのみ対応。
環境
- Windows 10 Pro
- Unity 2019.2.0f1
- Sony PaSoRi RC-S380
ソースコード
ソースコードは 「WindowsでNFCタグを読み取る」を参考にした(API部分はほとんどそのまま)。
NfcApi.cs
using System;
using System.Runtime.InteropServices;
namespace SmardCard
{
class NfcApi
{
[DllImport("winscard.dll")]
public static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);
[DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
public static extern uint SCardListReaders(
IntPtr hContext, byte[] mszGroups, byte[] mszReaders, ref UInt32 pcchReaders);
[DllImport("winscard.dll")]
public static extern uint SCardReleaseContext(IntPtr phContext);
[DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
public static extern uint SCardConnect(IntPtr hContext, string szReader,
uint dwShareMode, uint dwPreferredProtocols, ref IntPtr phCard,
ref IntPtr pdwActiveProtocol);
[DllImport("winscard.dll")]
public static extern uint SCardDisconnect(IntPtr hCard, int Disposition);
[StructLayout(LayoutKind.Sequential)]
internal class SCARD_IO_REQUEST
{
internal uint dwProtocol;
internal int cbPciLength;
public SCARD_IO_REQUEST()
{
dwProtocol = 0;
}
}
[DllImport("winscard.dll")]
public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
byte[] RecvBuff, ref int RecvBuffLen);
[DllImport("winscard.dll")]
public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SCARD_READERSTATE
{
internal string szReader;
internal IntPtr pvUserData;
internal UInt32 dwCurrentState;
internal UInt32 dwEventState;
internal UInt32 cbAtr;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
internal byte[] rgbAtr;
}
[DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);
[DllImport("winscard.dll")]
public static extern int SCardStatus(IntPtr hCard, string szReader, ref int cch, ref int state, ref IntPtr protocol, byte[] bAttr, ref int cByte);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll")]
public static extern void FreeLibrary(IntPtr handle);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr handle, string procName);
}
}
NfcConstant.cs
using System;
namespace SmardCard
{
class NfcConstant
{
public const uint SCARD_S_SUCCESS = 0;
public const uint SCARD_E_NO_SERVICE = 0x8010001D;
public const uint SCARD_E_TIMEOUT = 0x8010000A;
public const uint SCARD_SCOPE_USER = 0;
public const uint SCARD_SCOPE_TERMINAL = 1;
public const uint SCARD_SCOPE_SYSTEM = 2;
public const int SCARD_STATE_UNAWARE = 0x0000;
public const int SCARD_STATE_CHANGED = 0x00000002;
public const int SCARD_STATE_PRESENT = 0x00000020;
public const UInt32 SCARD_STATE_EMPTY = 0x00000010;
public const int SCARD_SHARE_SHARED = 0x00000002;
public const int SCARD_SHARE_EXCLUSIVE = 0x00000001;
public const int SCARD_SHARE_DIRECT = 0x00000003;
public const int SCARD_PROTOCOL_T0 = 1;
public const int SCARD_PROTOCOL_T1 = 2;
public const int SCARD_PROTOCOL_RAW = 4;
public const int SCARD_LEAVE_CARD = 0;
public const int SCARD_RESET_CARD = 1;
public const int SCARD_UNPOWER_CARD = 2;
public const int SCARD_EJECT_CARD = 3;
// SCardStatus status values
public const int SCARD_UNKNOWN = 0x00000000;
public const int SCARD_ABSENT = 0x00000001;
public const int SCARD_PRESENT = 0x00000002;
public const int SCARD_SWALLOWED = 0x00000003;
public const int SCARD_POWERED = 0x00000004;
public const int SCARD_NEGOTIABLE = 0x00000005;
public const int SCARD_SPECIFICMODE = 0x00000006;
}
}
NfcReader.cs
using System;
using UnityEngine;
using SmardCard;
using System.Text;
public class NfcReader : MonoBehaviour
{
private IntPtr hContext = IntPtr.Zero;
private IntPtr hCard;
private IntPtr activeProtocol;
public string readerName;
public string cardId;
public bool DetectOnlyFeliCa;
public bool ThrowExceptionLog;
public class CardData
{
public string ReaderName = "";
public string CardID = "";
}
public CardData ReadCardData()
{
try
{
SCardEstablishContext();
SCardListReaders();
SCardConnect();
SCardStatus();
SCardTransmit();
SCardDisconnect();
}
catch(Exception e)
{
if (ThrowExceptionLog)
{
Debug.LogWarning(e);
}
return new CardData();
}
return new CardData { ReaderName = readerName, CardID = cardId };
}
public string GetCardReaderName()
{
try
{
SCardEstablishContext();
SCardListReaders();
}
catch (Exception e)
{
if (ThrowExceptionLog)
{
Debug.LogWarning(e);
}
return "";
}
return readerName;
}
private uint SCardEstablishContext()
{
uint ret = NfcApi.SCardEstablishContext(NfcConstant.SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, out hContext);
if (ret != NfcConstant.SCARD_S_SUCCESS)
{
string message;
switch (ret)
{
case NfcConstant.SCARD_E_NO_SERVICE:
message = "サービスが起動されていません。";
break;
default:
message = "サービスに接続できません。code = " + ret;
break;
}
throw new ApplicationException(message);
}
if (hContext == IntPtr.Zero)
{
throw new ApplicationException("コンテキストの取得に失敗しました。");
}
return ret;
}
private void SCardListReaders()
{
uint pcchReaders = 0;
// NFCリーダの文字列バッファのサイズを取得
uint ret = NfcApi.SCardListReaders(hContext, null, null, ref pcchReaders);
if (ret != NfcConstant.SCARD_S_SUCCESS)
{
// 検出失敗
throw new ApplicationException("NFCリーダを確認できません。");
}
// NFCリーダの文字列を取得
byte[] mszReaders = new byte[pcchReaders * 2]; // 1文字2byte
ret = NfcApi.SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
if (ret != NfcConstant.SCARD_S_SUCCESS)
{
// 検出失敗
throw new ApplicationException("NFCリーダの取得に失敗しました。");
}
UnicodeEncoding unicodeEncoding = new UnicodeEncoding();
string readerNameMultiString = unicodeEncoding.GetString(mszReaders);
// 認識したNDCリーダの最初の1台を使用
int nullindex = readerNameMultiString.IndexOf((char)0);
readerName = readerNameMultiString.Substring(0, nullindex);
}
private void SCardConnect()
{
activeProtocol = IntPtr.Zero;
hCard = IntPtr.Zero;
uint ret = NfcApi.SCardConnect(hContext, readerName, NfcConstant.SCARD_SHARE_SHARED, NfcConstant.SCARD_PROTOCOL_T1, ref hCard, ref activeProtocol);
if (ret != NfcConstant.SCARD_S_SUCCESS)
{
throw new ApplicationException("カードに接続できません。code = " + ret);
}
}
private void SCardStatus()
{
int dwReaderLen = readerName.Length;
int dwState = 0;
byte[] atr = new byte[64]; //ATR
int dwAtrLen = atr.Length;
long lResult = NfcApi.SCardStatus(hCard, null, ref dwReaderLen, ref dwState, ref activeProtocol, atr, ref dwAtrLen);
if (lResult != NfcConstant.SCARD_S_SUCCESS)
{
throw new ApplicationException("ATR取得に失敗しました。");
}
lResult = NfcApi.SCardStatus(hCard, readerName, ref dwReaderLen, ref dwState, ref activeProtocol, atr, ref dwAtrLen);
if (lResult != NfcConstant.SCARD_S_SUCCESS)
{
throw new ApplicationException("ATR取得に失敗しました。");
}
// FeliCaかどうか判別
if ((atr[13] != 0x00 || atr[14] != 0x3b) && DetectOnlyFeliCa)
{
throw new ApplicationException("FeliCaではありません。");
}
}
private void SCardTransmit()
{
uint maxRecvDataLen = 256;
var recvBuffer = new byte[maxRecvDataLen + 2];
var sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, 0x00 }; // IDmを取得するコマンド
NfcApi.SCARD_IO_REQUEST ioRecv = new NfcApi.SCARD_IO_REQUEST();
ioRecv.cbPciLength = 255;
int pcbRecvLength = recvBuffer.Length;
int cbSendLength = sendBuffer.Length;
IntPtr handle = NfcApi.LoadLibrary("Winscard.dll");
IntPtr pci = NfcApi.GetProcAddress(handle, "g_rgSCardT1Pci");
NfcApi.FreeLibrary(handle);
uint ret = NfcApi.SCardTransmit(hCard, pci, sendBuffer, cbSendLength, ioRecv, recvBuffer, ref pcbRecvLength);
if (ret != NfcConstant.SCARD_S_SUCCESS)
{
throw new ApplicationException("NFCカードへの送信に失敗しました。code = " + ret);
}
// 受信データからIDmを抽出
cardId = BitConverter.ToString(recvBuffer, 0, pcbRecvLength - 2);
}
private void SCardDisconnect()
{
uint ret = NfcApi.SCardDisconnect(hCard, NfcConstant.SCARD_LEAVE_CARD);
if (ret != NfcConstant.SCARD_S_SUCCESS)
{
throw new ApplicationException("NFCカードとの切断に失敗しました。code = " + ret);
}
}
}
ゲーム上での使用
任意のスクリプト(ここではLoginController
とする)からNfcReader
を用いて、IDmを一定間隔で常時取得する例を示す。
インスペクターからNfcReader
を含むゲームオブジェクトを、LoginController
のNfcReader
にドラッグアンドドロップしておく。
LoginController.cs
using UnityEngine;
public class LoginController : MonoBehaviour
{
NFCReader R;
public GameObject NfcReader;
public float TimeOutLength = 1f;
private float LastCardReadTime;
public string IDm;
void Awake()
{
R = NfcReader.GetComponent<NfcReader>();
DontDestroyOnLoad(this.gameObject);
}
void Update()
{
if(Time.time - LastCardReadTime >= TimeOutLength && CardReadingEnabled)
{
LastCardReadTime = Time.time;
IDm = R.ReadCardData().CardID;
if(IDm != "")
{
//ここで処理を行う
}
}
}
}