mirror of
https://gitea.tendokyu.moe/ppc/amnet.git
synced 2026-09-27 17:27:55 +03:00
133 lines
3.6 KiB
TypeScript
133 lines
3.6 KiB
TypeScript
import {Buffer} from "buffer";
|
|
import NfcManager, {NfcTech} from "react-native-nfc-manager";
|
|
|
|
import {performDecrypt} from "./iccard-crypto";
|
|
|
|
export enum ICType {
|
|
Amusement = 1,
|
|
Transport = 2
|
|
}
|
|
|
|
export enum AmusementICVendor {
|
|
Sega = 0x0078,
|
|
Konami = 0x0068,
|
|
BandaiNamcoEntertainment = 0x002A,
|
|
BandaiNamcoGames = 0x003A,
|
|
Nesica = 0x0079,
|
|
Unknown = 0x0000
|
|
}
|
|
|
|
type AmusementICCard = {
|
|
type: ICType.Amusement,
|
|
|
|
idm: string,
|
|
accessCode: string,
|
|
vendor: AmusementICVendor
|
|
}
|
|
|
|
type TransportICCard = {
|
|
type: ICType.Transport,
|
|
|
|
idm: string
|
|
}
|
|
|
|
export type ICCard = AmusementICCard | TransportICCard;
|
|
|
|
/**
|
|
* Converts an IC card IDm to the 20-digit "0008" number used by some networks
|
|
* @param idm The IDm to convert
|
|
*/
|
|
export function icCard0008Number(idm: string): string {
|
|
// 0008 is the numerical representation of the idm padded with zeros until it is 20 digits
|
|
return BigInt(`0x${idm}`).toString().padStart(20, '0');
|
|
}
|
|
|
|
/**
|
|
* Reads a FeliCa card, returning access code information.
|
|
*/
|
|
export async function felicaCardReadAsync(): Promise<ICCard | null> {
|
|
try {
|
|
await NfcManager.requestTechnology(NfcTech.FelicaIOS, {
|
|
isReaderModeEnabled: true,
|
|
invalidateAfterFirstRead: false,
|
|
alertMessage: "Place an Amusement IC or Transport card on your device."
|
|
});
|
|
|
|
const tag = await NfcManager.getTag();
|
|
const idm = tag['idm'] as string;
|
|
|
|
switch (tag['systemCode'] as string) {
|
|
case "0003":
|
|
return {
|
|
type: ICType.Transport,
|
|
idm
|
|
};
|
|
|
|
case "88B4":
|
|
return await readAmusementICCardContents(idm);
|
|
|
|
default:
|
|
return null;
|
|
}
|
|
} catch (ex) {
|
|
return null;
|
|
} finally {
|
|
await NfcManager.cancelTechnologyRequest();
|
|
}
|
|
}
|
|
|
|
async function readAmusementICCardContents(idm: string): Promise<AmusementICCard | null> {
|
|
const idmBytes = Uint8Array.from(Buffer.from(idm, 'hex'));
|
|
const readCommand = [
|
|
0x06, // read
|
|
...idmBytes,
|
|
0x01, // service count (fixed)
|
|
0x0B, 0x00, // service code (000b, le)
|
|
0x02, // block count
|
|
0x80, 0x00, // S_PAD0 block
|
|
0x80, 0x82 // ID block
|
|
];
|
|
|
|
let response = await NfcManager.sendFelicaCommandIOS(readCommand);
|
|
const offset = response.indexOf(0x07); // look for 0x07 as for some reason the response may not start at response[0]
|
|
|
|
// response must have 44 bytes
|
|
if (offset === -1 || response.length - offset !== 44) {
|
|
return null;
|
|
} else if (offset !== 0) {
|
|
response = response.slice(offset);
|
|
}
|
|
|
|
// response format:
|
|
// 0 - read response (0x07)
|
|
// 1-8 - IDm
|
|
// 9 - status flag 1
|
|
// 10 - status flag 2
|
|
// 11 - number of blocks (0x02)
|
|
// 12-27 - S_PAD0 block
|
|
// 28-43 - ID block (DFC is 35-36)
|
|
|
|
if (response[9] !== 0x00 || response[11] !== 0x02) {
|
|
return null;
|
|
}
|
|
|
|
const dfcValue = response[37] | (response[36] << 8);
|
|
|
|
if (dfcValue in AmusementICVendor && dfcValue !== AmusementICVendor.Unknown) {
|
|
const accessCode = performDecrypt(new Uint8Array(response.slice(12, 28)));
|
|
return {
|
|
type: ICType.Amusement,
|
|
accessCode: [...accessCode.accessCodeBytes].map(x => x.toString(16).padStart(2, '0')).join(''),
|
|
vendor: dfcValue,
|
|
idm
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: ICType.Amusement,
|
|
accessCode: icCard0008Number(idm),
|
|
vendor: AmusementICVendor.Unknown,
|
|
idm
|
|
};
|
|
}
|