LoginSignup
3
4

More than 3 years have passed since last update.

UnityでPaSoRiからFeliCaのデータを取得する(winscard.dll使用)

Last updated at Posted at 2020-04-13

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を含むゲームオブジェクトを、LoginControllerNfcReaderにドラッグアンドドロップしておく。

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 != "")
            {
                //ここで処理を行う
            }
        }
    }
}

参考

3
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4