Suica （含むPASMO等）をMacで読みたい

## はじめに

2007年にMacにPaSoRiを接続してSuicaやPASMOの履歴情報を読み出すプログラムを書いて公開しましたが、その後ほったらかしていました。
Sony さんが非接触ICカードリーダー／ライター　PaSoRi®(パソリ) 「RC-S390」を発売し、iPhone/iPad 等で利用ができるようになっていいのですが、折角のWi-Fi 対応の PaSoRi なので、Macでも利用できればと思って心待ちにしているものの、未だにSonyさんからアプリの提供はありません。
古い情報ですが、Suica（含PASMO等）のデータ構造等を開発者の皆さんに公開して、Mac版を作ってもらいたいという他力本願の投稿です。

## Suicaのデータ構造
この情報は、自分の Suica のデータをダンプしてリバースした結果ですので、**私の推測であって、正しいという保証はありません。**たまたまうまく行っているのかもしれないということで責任は負えません。

Suicaのデータは残金や通番など一部のバイトの並びが逆（リトルエンディアン）です。

1. 最初の1バイト: データ種別
> * 0x1B だとクレジット入金
> * 0x07 or 0x08だと入金
> * 0x46だとサンクスチャージの入金
> * 0x16が自動改札乗降
> * 0xC7が購買のようです

1. 5バイト目から２バイト:日付
> * 先頭から7ビットが年
> * ４ビットが月
> * 残り５ビットが日のようです。
　（ここが面倒でした。パズルを解くみたいで楽しかったけど。）

1. 7バイト目からの２バイト：入った駅（コード）
1. 続く２バイト：出た駅（コード）
> 駅のコードは、路線コード／駅コードの組合わせで、有志によるデータベースが公開されています。[IC SFCard Fan DB Srevice](http://www.denno.net/SFCardFan/webservice.html)　このソフトではコードからの変換はしていません。

1. 1２バイト目から２バイト：残金（リトルエンディアン）

## MacでSuicaを読むプログラム
2007年に公開したプログラムです。
殆どの機能はネット上に公開されているlibpasoril]に依存していますが現時点では公開されていません。（残念です）
更にlibpasoriは[libusb](http://libusb.info/)を利用しています。（Wi-Fi利用なら別のライブラリーが必要でしょう）

ソースはここに再掲しますが、詳細は [Macで Suica や PASMO の履歴を読む](http://www.imfj.net/people/nomura/soft/PaSoRi/SuicaValue.html)
を御覧ください。

```c:SuicaValue.c
#include <stdio.h>
#include <stdlib.h>
#include "libpasori.h"

int
main(void){
	pasori* p;
	felica* f;
	int i;
	int m;
	int wk, wk2;
	uint8 b[16];
	
	p = pasori_open(NULL);
	if(!p){
		printf("error\n");
		exit(-1);
	}
	pasori_init(p);
	f = felica_polling(p,0x0003,0,0);
	printf("*** Suica Value ***\n");
	i=0;
		while(!felica_read_without_encryption02(f,0x090f,0,i,b)){
			printf("[%02d] : ",i);
			printf("%02X ",b[0]);
			printf("%02X ",b[1]);
			printf("%02X",b[2]);
			printf("%02X ",b[3]);
			wk=b[4]>>1;
			wk=wk&0x7F;
			printf("%02d/",wk);
			wk=b[4]&0x01;
			wk=wk<<3;
			wk2=b[5]>>5;
			wk2=wk2&0x07;
			wk=wk+wk2;
			printf("%02d/",wk);
			wk=b[5]&0x1F;
			printf("%02d ",wk);
			printf("%02X-%02X -> ",b[6],b[7]);
			printf("%02X-%02X      ",b[8],b[9]);
			printf("  % 6d Yen        ",b[11]*256+b[10]);
			printf("%02X%02X%02X%02X\n",b[12],b[13],b[14],b[15]);
		i++;
	}
	pasori_close(p);
	return 0;
}
```

更に、これを利用して見やすくフォーマットするAppleScriptのソースも再掲しておきます。

``` AppleScript:SuicaValue.scpt
-- 「SuicaValue.scpt」
-- PaSoRiでSuicaの履歴を抽出して表示するスクリプトです。
-- Suicaを読むアプリケーション「SuicaValue」はホームディレクトリ直下に置いて実行権を与えておいて下さい。
-- 最初に駅名の検索／表示をするかを問い合わせます。駅名の検索／表示ではインターネットに接続して検索しますので、
-- ネットに接続されていることが必要です。（商品の購入では表示されません）
-- 結果を表示後にＯＫボタンを押すと、表示結果のテキストをクリップボードにコピーして終了します。
-- このスクリプトでは、IC SFCardFanのページ（http://www.denno.net/SFCardFan/）にあるSOAPのサービスを
-- 利用して駅名と店名を検索しています。（サービスの提供に感謝します）

property SOAP_Endpoint_URL : "http://www.denno.net/SFCardFan/soapserver.php"
property SOAP_app : "soap"
property method_name_ST : "getStationName"
property method_name_SH : "getShopName"
property method_namespace_URI : ""
property SOAP_action : ""

display dialog "　駅名／店名検索をしますか？" & return & "（インターネットに接続します）" buttons {"Yes", "No"} default button 2
set SerchOn to button returned of result

-- SuicaValue のある場所を指定
set cmdPath to "$HOME/SuicaValue"
do shell script cmdPath
set SuicaValue to result as text

set dataCount to the number of paragraphs of SuicaValue
set sortedData to ""
repeat with i from 0 to dataCount - 2
	set eachData to paragraph (dataCount - i) of SuicaValue
	set Proc to word 2 of eachData
	if Proc = "00" then
	else
		set lineData to characters 19 thru 26 of eachData & " "
		set zankin to (characters 43 thru 55 of eachData) as text
		if Proc = "1B" or Proc = "07" or Proc = "08" or Proc = "46" then
			set lineData to (lineData & " 入金後 " & word 1 of zankin as string) & "円"
			set lineData to lineData & " 場所：" & (characters 28 thru 32 of eachData)
		else if Proc = "C7" or Proc = "C8" then
			set lineData to (lineData & " 購入後 " & word 1 of zankin as string) & "円"
			set lineData to lineData & " 店舗：" & (characters 28 thru 32 of eachData)
			set lineData to lineData & "ー" & (characters 37 thru 41 of eachData)
			if SerchOn = "Yes" then
				set shop to my shopname(Proc as string, (characters 37 thru 41 of eachData) as string)
				set lineData to lineData & "　" & shop
			end if
		else
			set lineData to lineData & " 乗車後 " & word 1 of zankin & "円"
			set lineData to lineData & " 区間：" & (characters 28 thru 32 of eachData)
			set lineData to lineData & "～" & (characters 37 thru 41 of eachData)
			-- 駅名の検索
			if SerchOn = "Yes" then
				set fromStation to my stationname((characters 28 thru 32 of eachData) as string)
				set toStation to my stationname((characters 37 thru 41 of eachData) as string)
				set lineData to lineData & "　" & fromStation & " --> " & toStation
			end if
		end if
		set sortedData to sortedData & lineData & (ASCII character 13)
	end if
end repeat
display dialog sortedData
set the clipboard to sortedData as string

-- 処理終了（結果をクリップボードにコピー）

-- 駅名の検索
on stationname(LSCode)
	set LC to Hex2Dec((characters 1 thru 2 of LSCode) as string)
	set SC to Hex2Dec((characters 4 thru 5 of LSCode) as string)
	if LC < 128 then
		set AC to "0"
	else
		set AC to "1"
	end if
	set the method_parameters to {AreaCode:AC, LineCode:LC, StationCode:SC}
	copy my SOAP_call_ST(SOAP_Endpoint_URL, method_name_ST, method_namespace_URI, method_parameters, SOAP_action) to {call_indicator, call_result}
	if the call_indicator is false then
		beep
		display dialog "An error occurred." & return & return & call_result buttons {"Cancel"} default button 1
	else
		try
			set findName to value of item 6 of item 1 of every item of call_result
		on error
			set findName to "駅名不明"
		end try
		return findName
	end if
end stationname

-- 店舗名の検索
on shopname(TermCode, LSCode)
	set TC to Hex2Dec(TermCode)
	set LC to Hex2Dec((characters 1 thru 2 of LSCode) as string)
	set SC to Hex2Dec((characters 4 thru 5 of LSCode) as string)
	set AC to "1"
	set the method_parameters to {AreaCode:AC, TerminalCode:TC, LineCode:LC, StationCode:SC}
	copy my SOAP_call_SH(SOAP_Endpoint_URL, method_name_SH, method_namespace_URI, method_parameters, SOAP_action) to {call_indicator, call_result}
	if the call_indicator is false then
		beep
		display dialog "An error occurred." & return & return & call_result buttons {"Cancel"} default button 1
	else
		try
			set findName to value of item 5 of item 1 of every item of call_result
			set findName to findName & "、" & value of item 6 of item 1 of every item of call_result
		on error
			set findName to "店名不明"
		end try
		return findName
	end if
end shopname

-- SOAP 検索（駅名）
on SOAP_call_ST(SOAP_Endpoint_URL, method_name_ST, method_namespace_URI, method_parameters, SOAP_action)
	try
		using terms from application "http://www.apple.com/"
			tell application SOAP_Endpoint_URL
				set this_result to call soap {method name:method_name_ST, method namespace uri:method_namespace_URI, parameters:method_parameters, SOAPAction:SOAP_action}
			end tell
		end using terms from
		return {true, this_result}
	on error error_message number error_number
		if the error_number is -916 then set the error_message to "インターネットに接続できませんでした。"
		return {false, error_message}
	end try
end SOAP_call_ST

-- SOAP 検索（店舗）
on SOAP_call_SH(SOAP_Endpoint_URL, method_name_SH, method_namespace_URI, method_parameters, SOAP_action)
	try
		using terms from application "http://www.apple.com/"
			tell application SOAP_Endpoint_URL
				set this_result to call soap {method name:method_name_SH, method namespace uri:method_namespace_URI, parameters:method_parameters, SOAPAction:SOAP_action}
			end tell
		end using terms from
		return {true, this_result}
	on error error_message number error_number
		if the error_number is -916 then set the error_message to "インターネットに接続できませんでした。"
		return {false, error_message}
	end try
end SOAP_call_SH

-- 16進を10進に変換（２桁のみ）
on Hex2Dec(HEX)
	set ans to ((offset of (character 1 of HEX) in "0123456789ABCDEF") - 1) * 16
	set ans to ans + (offset of (character 2 of HEX) in "0123456789ABCDEF") - 1
	return ans
end Hex2Dec
```