[C/C++]WinSCard.dllを使ってPCSCでカードIDを取得する

# この記事は

FeliCaカードから製造番号（IDm）を読み取るためのC++,PowerShellのコード

- SONY RC-S300
- Windows 11 Home

# Visual Studio CodeでC/C++開発する環境をつくる

1. VSCodeはインストールして必要な拡張機能を入れるだけ。
2. [MSYS](https://www.msys2.org/)も入れて、コンソールから`pacman -S --needed base-devel mingw-w64-x86_64-toolchain`。必要な機能をインストールする。
3. 1)～11)の番号で指定する。**gcc, gdb, g++** の3つが必要。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/66fd6a4c-d1bf-5769-adfe-9a5a254ad75b.png)

https://code.visualstudio.com/docs/languages/cpp

# コマンドプロンプトから実行

テストのために下記記事のソースで試した（[zlibライセンス](https://ja.wikipedia.org/wiki/Zlib_License)）

https://qiita.com/gpsnmeajp/items/d4810b175189609494ac

"C:\Windows\System32\WinSCard.dll"を使うために`-l`オプションで指定する

```bash:コマンドプロンプト
gcc qiita.cpp -lwinscard
```

## VS Codeのtasks.jsonを変更する

VSCodeの`F5`で実行したときのタスクにも`-lwinscard`を付与するために、`tasks.json`に一行追加する

```diff_json:.vscode/tasks.json
{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++.exe アクティブなファイルのビルド",
            "command": "C:\\msys64\\mingw64\\bin\\g++.exe",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe",
+               "-lwinscard"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "デバッガーによって生成されたタスク。"
        }
    ],
    "version": "2.0.0"
}
```

# ソースコード

## C++

出展：https://qiita.com/gpsnmeajp/items/d4810b175189609494ac

一部コードを改変しています

```cpp:main.cpp
/*
zlib License
Copyright (c) 2018 GPS_NMEA_JP

This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include <stdio.h>
#include <windows.h>
#include <winscard.h>
#include <stdint.h>

/*
SW1,SW2のエラーに関しては以下を参照
http://eternalwindows.jp/security/scard/scard07.html

掲載されていないエラーの場合は，Felica SDKに記載されていることがある．
Felicaエラーに関しては別
*/

//--------------Felica PaSoRi------------------------

// IOCTL_PCSC_CCID_ESCAPE : SCARD_CTL_CODE(3500) //汎用制御処理
#define ESC_CMD_GET_INFO 0xC0 //バージョンなど各種情報の取得

// ESC_CMD_GET_INFO
#define PRODUCT_SERIAL_NUMBER 0x08

//----------APDU------------
#define APDU_CLA_GENERIC 0xFF

#define APDU_INS_GET_DATA 0xCA

#define APDU_P2_NONE 0x00

//----APDU_INS_GET_DATA----
#define APDU_P1_GET_UID 0x00
#define APDU_P1_GET_CARD_TYPE 0xF3

#define APDU_LE_MAX_LENGTH 0x00

#define CARD_TYPE_FELICA 0x04

// スマートカードリソースマネージャへ接続
int openService(LPSCARDCONTEXT hContextPtr)
{
    LONG res = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, hContextPtr);
    if (res != SCARD_S_SUCCESS)
    {
        printf("[openService][FAILURE]%d\n", res);
        return -1;
    }

    printf("[openService][SUCCESS]Connect to Smart Card Resource Manager\n");
    return 0;
}

// スマートカードリソースマネージャと切断・リーダー名領域開放
int closeService(SCARDCONTEXT hContext, LPTSTR lpszReaderName)
{
    if (lpszReaderName != NULL)
    {
        SCardFreeMemory(hContext, lpszReaderName);
    }
    SCardReleaseContext(hContext);

    printf("[closeService]Disconnect from Smart Card Resource Manager\n");
    return 0;
}

//カードリーダーのリストを取得．文字列ポインタのポインタ
int getCardReaderNameList(SCARDCONTEXT hContext, LPTSTR *lpszReaderName, LPDWORD readerNameListSize)
{
    DWORD dwAutoAllocate = SCARD_AUTOALLOCATE;

    LONG res = SCardListReaders(hContext, NULL, (LPTSTR)lpszReaderName, &dwAutoAllocate);
    if (res != SCARD_S_SUCCESS)
    {
        if (res == SCARD_E_NO_READERS_AVAILABLE)
        {
            printf("[getCardReaderNameList]Card reader is not connected\n");
        }
        else
        {
            printf("[getCardReaderNameList]Card reader detection fails:%X\n", res);
        }

        return -1;
    }

    *readerNameListSize = dwAutoAllocate;

    printf("[getCardReaderNameList]CardReaderNameList:");
    for (unsigned int i = 0; i < (*readerNameListSize); i++)
    {
        putchar((*lpszReaderName)[i]);
    }
    printf("\n");

    return 0;
}

// 現在の状態を取得する(SCARD_STATE_UNAWARE)
int checkCardStatus(SCARDCONTEXT hContext, LPTSTR lpszReaderName, LPDWORD state, int timeout)
{
    SCARD_READERSTATE readerState;

    readerState.szReader = lpszReaderName;
    readerState.dwCurrentState = *state; // 現在の状態は不明

    if (timeout == INFINITE)
    {
        printf("[checkCardStatus]Started to scan the status(timeout=infinite)\n");
    }
    else if (timeout == 0)
    {
        printf("[checkCardStatus]Started to scan the status(timeout=zero)\n");
    }
    else
    {
        printf("[checkCardStatus]Started to scan the status(timeout=%dms)\n", timeout);
    }

    LONG res = SCardGetStatusChange(hContext, timeout, &readerState, 1);
    if (res == SCARD_E_TIMEOUT)
    {
        printf("[checkCardStatus]Read timeout!!\n");
        return -1;
    }

    if (res != SCARD_S_SUCCESS)
    {
        printf("[checkCardStatus]Fail to get status:%X\n", res);
        SCardFreeMemory(hContext, lpszReaderName);
        SCardReleaseContext(hContext);

        return -2;
    }

    // 状態を更新
    *state = readerState.dwEventState;

    if (readerState.dwEventState & SCARD_STATE_EMPTY)
    {
        printf("[checkCardStatus]No card is set\n");
        return -3;
    }

    if (readerState.dwEventState & SCARD_STATE_UNAVAILABLE)
    {
        printf("[checkCardStatus]Not connect to card reader\n");
        return -4;
    }

    if (!(readerState.dwEventState & SCARD_STATE_PRESENT))
    {
        printf("[checkCardStatus]Unknown status\n");
        return -5;
    }

    printf("[checkCardStatus]Card is set\n");
    return 0;
}

int connectCard(SCARDCONTEXT hContext, LPTSTR lpszReaderName, LPSCARDHANDLE hCardPtr)
{
    DWORD dwActiveProtocol;
    LONG res = SCardConnect(hContext, lpszReaderName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, hCardPtr, &dwActiveProtocol);
    if (res != SCARD_S_SUCCESS)
    {
        if (res == SCARD_W_REMOVED_CARD)
        {
            printf("[connectCard]No card is set\n");
            return -1;
        }
        else
        {
            printf("[connectCard]Unknown error\n");
            return -2;
        }
    }
    printf("[connectCard]Connect to card\n");

    return 0;
}

// 直接接続(カードリーダーへダイレクトに指示可能)
int connectDirect(SCARDCONTEXT hContext, LPTSTR lpszReaderName, LPSCARDHANDLE hCardPtr)
{
    DWORD dwActiveProtocol;
    LONG res = SCardConnect(hContext, lpszReaderName, SCARD_SHARE_DIRECT, 0, hCardPtr, &dwActiveProtocol);
    if (res != SCARD_S_SUCCESS)
    {
        if (res == SCARD_W_REMOVED_CARD)
        {
            printf("[connectDirect]Fail to connect directly\n");
            return -1;
        }
        else
        {
            printf("[connectDirect]Unknown error\n");
            return -2;
        }
    }
    printf("[connectDirect][SUCCESS]Connect direct\n");

    return 0;
}

int disconnectCard(SCARDHANDLE hCard)
{
    printf("[disconnectCard][SUCCESS]Disconnect card\n");

    SCardDisconnect(hCard, SCARD_LEAVE_CARD);
    return 0;
}

int readIDm(SCARDHANDLE hCard)
{
    // CLA,INS,P1,P2,Le
    BYTE pbSendBuffer[5] = {APDU_CLA_GENERIC, APDU_INS_GET_DATA, APDU_P1_GET_UID, APDU_P2_NONE, APDU_LE_MAX_LENGTH};

    BYTE pbRecvBuffer[256];
    DWORD pcbRecvLength = 256;

    //コマンド送信
    LONG res = SCardTransmit(hCard, SCARD_PCI_T1, pbSendBuffer, sizeof(pbSendBuffer), NULL, pbRecvBuffer, &pcbRecvLength);
    if (res != SCARD_S_SUCCESS)
    {
        printf("[readIDm]Incorrect command or incorrect status%X\n", res);
        return -1;
    }

    printf("[readIDm]");
    for (unsigned int i = 0; i < pcbRecvLength - 2; i++) // 最後の2バイト(0x90,0x00)はレスポンスなので省略
    {
        printf("%02X,", pbRecvBuffer[i]);
    }
    printf("\n");

    // レスポンス解析
    BYTE SW1 = pbRecvBuffer[pcbRecvLength - 2];
    BYTE SW2 = pbRecvBuffer[pcbRecvLength - 1];
    if (SW1 != 0x90 || SW2 != 0x00)
    {
        printf("[readIDm]Card response ERROR SW1=%02X,SW2=%02X\n", SW1, SW2);
        return -2;
    }

    return 0;
}

int main(void)
{
    // スマートカードリソースマネージャへ接続
    SCARDCONTEXT hContext;
    if (openService(&hContext) != 0)
    {
        return -1;
    }

    // カードリーダー名リストを取得
    LPTSTR lpszReaderName;
    DWORD readerNameListSize;
    if (getCardReaderNameList(hContext, &lpszReaderName, &readerNameListSize))
    {
        closeService(hContext, NULL);
        return -1;
    }

    // カードリーダーに接続
    SCARDHANDLE hCardreader;
    if (connectDirect(hContext, lpszReaderName, &hCardreader) != 0)
    {
        closeService(hContext, lpszReaderName);
        return -1;
    }

    // カード切断
    disconnectCard(hCardreader);

    // 現在のカードの状態を取得する
    DWORD state = SCARD_STATE_UNAWARE;
    if (checkCardStatus(hContext, lpszReaderName, &state, 0) == -3)
    {
        // カードがセットされるまで待つ
        if (checkCardStatus(hContext, lpszReaderName, &state, INFINITE) != 0)
        {
            closeService(hContext, lpszReaderName);
            return -1;
        }
    }

    // カードに接続
    SCARDHANDLE hCard;
    if (connectCard(hContext, lpszReaderName, &hCard) != 0)
    {
        closeService(hContext, lpszReaderName);
        return -1;
    }

    // IDM読み取り
    readIDm(hCard);

    // カード切断
    disconnectCard(hCard);

    // スマートカードリソースマネージャ切断
    closeService(hContext, lpszReaderName);

    return 0;
}
```

## バッチ(.bat）

```bat:p.ps1を呼び出すバッチ
powershell -NoProfile -ExecutionPolicy Unrestricted .\p.ps1
```
 
## PowerShell

```powershell:main.exeを呼び出すパワーシェル(p.ps1)
Write-Host "--- start PowerShell"
$filename = Get-Date -Format "yyyy-MMdd-HHmmss"

# main.exeを起動するための前準備
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "main.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo

# data.csvを連想配列 $hash に格納する
$data = Import-Csv -Path .\data.csv -Delimiter ","  -Header "key", "value"
$hash = $data | Group-Object -AsHashTable -AsString -Property key

$ans = "y"
while ($ans -eq "y") {

    $p.Start() | Out-Null
    $p.WaitForExit()
    $stdout = $p.StandardOutput.ReadToEnd()
    $stderr = $p.StandardError.ReadToEnd()

    Write-Host "stdout: $stdout"
    Write-Host "stderr: $stderr"
    Write-Host "exit code: " + $p.ExitCode

    # [readIDm]の行からIDmだけを取り出す
    $arr = $stdout.split("`n")
    $a = $arr | Select-String idm
    $b = $a -replace "\[readIDm\]", ""
    $c = $b -replace ",", ""

    Write-Host $c -ForegroundColor Green
    $v = $hash[$c.Trim()].value

    if ($null -eq $v) {
        Write-Host "not found key"
        Write-Output $c | Out-File ".\out\notfound$filename.txt" -Append
    }
    else {
        Write-Host $v -ForegroundColor Green
        Write-Output $v | Out-File ".\out\log_$filename.txt" -Append
    }

    $ans = Read-Host "continue?(y/n)"
}

Write-Host "--- end PowerShell"
```

### data.csv

IDmとそれに紐づく何らかの情報を`data.csv`としてもつ
```csv:data.csv
0123456789012345,vvv111
0147395329527923,vvv222
```


# 参考

https://qiita.com/seriru13/items/c2f5192615162c4c3f47

https://stackoverflow.com/questions/8761888/capturing-standard-out-and-error-with-start-process

# おまけ

Felicaカードのダンプがとれる。実行ファイル（.exe）あり。

http://oasis.halfmoon.jp/mw/index.php?title=Soft-FelicaRawViewer

サービスコードが特定できたら、**リトルエンディアン**でSelect File, Read Binary

https://tomosoft.jp/design/?p=5543