272 lines
7.4 KiB
TypeScript
272 lines
7.4 KiB
TypeScript
// On real ALL.Net, there's just a massive database of every supported FeliCa's
|
|
// IDm -> access code mappings. Since we don't have access to such information,
|
|
// every FeliCa card will be treated like FeliCa mobile: an access code is
|
|
// generated, and then stored with its IDm for future lookups.
|
|
//
|
|
// This also means you'll need to get some more keys, but if you're lazy
|
|
// you can probably just set
|
|
// - RESERVED_CARD_PREFIX to something not starting with 0 or 3
|
|
// - RESERVED_CARD_KEY to a random 16-digit hex string, where each digit displays
|
|
// exactly once.
|
|
import {
|
|
FelicaExtendedLookupRequestStruct,
|
|
FelicaExtendedLookupResponseStruct,
|
|
FelicaLookupRequestStruct,
|
|
FelicaLookupResponseStruct,
|
|
} from "../types/felica-conversion";
|
|
import { PacketHeaderStruct } from "../types/header";
|
|
import { CalculateAccessCode } from "../utils/access-code";
|
|
import { IsSupportedFelicaMobile, IsSupportedFelica } from "../utils/felica";
|
|
import { CommandId, CompanyCodes, PortalRegistration, ResultCodes } from "../utils/misc";
|
|
import { desc, eq } from "drizzle-orm";
|
|
import { db } from "external/db/db";
|
|
import { card, felicaCardLookup, felicaMobileLookup } from "external/db/schemas/index";
|
|
import CreateLogCtx from "lib/logger/logger";
|
|
import { Config } from "lib/setup/config";
|
|
import type { AimeDBHandlerFn } from "../types/handlers";
|
|
|
|
const logger = CreateLogCtx(__filename);
|
|
|
|
export const FelicaLookupHandler: AimeDBHandlerFn<"FelicaLookupResponse"> = async (
|
|
header,
|
|
data
|
|
) => {
|
|
header.commandId = CommandId.FELICA_LOOKUP_RESPONSE;
|
|
header.length = PacketHeaderStruct.baseSize + FelicaLookupResponseStruct.baseSize;
|
|
header.result = ResultCodes.SUCCESS;
|
|
|
|
const req = new FelicaLookupRequestStruct(data);
|
|
const resp = new FelicaLookupResponseStruct();
|
|
|
|
if (!IsSupportedFelica(req.osVer)) {
|
|
header.result = ResultCodes.INVALID_AIME_ID;
|
|
resp.felicaIndex = -1;
|
|
return resp;
|
|
}
|
|
|
|
const table = IsSupportedFelicaMobile(req.osVer) ? felicaMobileLookup : felicaCardLookup;
|
|
|
|
const result = await db
|
|
.select()
|
|
.from(table)
|
|
.where(eq(table.idm, req.idm.toString(16)))
|
|
.then((r) => r[0]);
|
|
|
|
if (!result) {
|
|
resp.felicaIndex = -1;
|
|
} else {
|
|
resp.felicaIndex = result.id;
|
|
resp.accessCode.set(Buffer.from(result.accessCode, "hex"));
|
|
}
|
|
|
|
return resp;
|
|
};
|
|
|
|
export const FelicaExtendedLookupHandler: AimeDBHandlerFn<"FelicaExtendedLookupResponse"> = async (
|
|
header,
|
|
data
|
|
) => {
|
|
header.commandId = CommandId.EXTENDED_FELICA_ACCOUNT_RESPONSE;
|
|
header.length = PacketHeaderStruct.baseSize + FelicaExtendedLookupResponseStruct.baseSize;
|
|
header.result = ResultCodes.SUCCESS;
|
|
|
|
const req = new FelicaExtendedLookupRequestStruct(data);
|
|
const resp = new FelicaExtendedLookupResponseStruct();
|
|
|
|
resp.accountId = -1;
|
|
|
|
logger.debug("Parsed response body.", { req });
|
|
|
|
if (req.companyCode < 0 || req.companyCode > 4) {
|
|
header.result = ResultCodes.INVALID_AIME_ID;
|
|
return resp;
|
|
}
|
|
|
|
if (!IsSupportedFelica(req.osVer)) {
|
|
header.result = ResultCodes.INVALID_AIME_ID;
|
|
return resp;
|
|
}
|
|
|
|
let result;
|
|
|
|
if (IsSupportedFelicaMobile(req.osVer)) {
|
|
result = await db
|
|
.select()
|
|
.from(felicaMobileLookup)
|
|
.where(eq(felicaMobileLookup.idm, req.idm.toString(16)))
|
|
.leftJoin(card, eq(card.accessCode, felicaMobileLookup.accessCode))
|
|
.then((r) => r[0]);
|
|
} else {
|
|
result = await db
|
|
.select()
|
|
.from(felicaCardLookup)
|
|
.where(eq(felicaCardLookup.idm, req.idm.toString(16)))
|
|
.leftJoin(card, eq(card.accessCode, felicaCardLookup.accessCode))
|
|
.then((r) => r[0]);
|
|
}
|
|
|
|
if (result) {
|
|
const lookupResult =
|
|
"aimedb_felica_mobile_lookup" in result
|
|
? result.aimedb_felica_mobile_lookup
|
|
: result.aimedb_felica_card_lookup;
|
|
const cardResult = result.aimedb_card;
|
|
|
|
resp.accessCode.set(Buffer.from(lookupResult.accessCode, "hex"));
|
|
|
|
if (cardResult) {
|
|
resp.accountId = cardResult.id;
|
|
resp.portalRegistered = PortalRegistration.UNREGISTERED;
|
|
|
|
// HACK: Since we cannot possibly know who made it (even AICC cards have
|
|
// the same manufacturer code `01:2e`!), we're just going to treat everything
|
|
// as a SEGA card.
|
|
resp.companyCode = CompanyCodes.SEGA;
|
|
}
|
|
|
|
return resp;
|
|
}
|
|
|
|
// Assuming that FeliCa Mobile is handled by their own registration endpoint...
|
|
if (IsSupportedFelicaMobile(req.osVer)) {
|
|
return resp;
|
|
}
|
|
|
|
// Card is not in the lookup tables, register a new card...
|
|
if (!Config.AIMEDB_CONFIG.RESERVED_CARD_KEY || !Config.AIMEDB_CONFIG.RESERVED_CARD_PREFIX) {
|
|
logger.error(
|
|
"AIMEDB_CONFIG.RESERVED_CARD_KEY or AIMEDB_CONFIG.RESERVED_CARD_PREFIX is not set in config file. Cannot generate a new access code.",
|
|
{ req }
|
|
);
|
|
|
|
header.result = ResultCodes.INVALID_AIME_ID;
|
|
return resp;
|
|
}
|
|
|
|
const mostRecentRow = await db
|
|
.select()
|
|
.from(felicaCardLookup)
|
|
.orderBy(desc(felicaCardLookup.id))
|
|
.limit(1)
|
|
.then((r) => r[0]);
|
|
|
|
const nextId = mostRecentRow ? mostRecentRow.id + 1 : 1;
|
|
|
|
const accessCode = CalculateAccessCode(
|
|
nextId,
|
|
Config.AIMEDB_CONFIG.RESERVED_CARD_PREFIX,
|
|
Config.AIMEDB_CONFIG.RESERVED_CARD_KEY
|
|
);
|
|
|
|
logger.verbose(`Created FeliCa Card access code for serial ${nextId}.`, {
|
|
nextId,
|
|
accessCode,
|
|
});
|
|
|
|
const value = { idm: req.idm.toString(16), accessCode };
|
|
const row = await db
|
|
.insert(felicaCardLookup)
|
|
.values(value)
|
|
.returning()
|
|
.then((r) => r[0]);
|
|
|
|
if (!row) {
|
|
logger.crit("Failed to insert new lookup entry into the database.", value);
|
|
|
|
header.result = ResultCodes.UNKNOWN_ERROR;
|
|
return resp;
|
|
}
|
|
|
|
header.result = ResultCodes.SUCCESS;
|
|
resp.accessCode.set(Buffer.from(row.accessCode, "hex"));
|
|
|
|
return resp;
|
|
};
|
|
|
|
export const FelicaRegisterHandler: AimeDBHandlerFn<"FelicaLookupResponse"> = async (
|
|
header,
|
|
data
|
|
) => {
|
|
header.commandId = CommandId.FELICA_LOOKUP_RESPONSE;
|
|
header.length = PacketHeaderStruct.baseSize + FelicaLookupResponseStruct.baseSize;
|
|
|
|
const req = new FelicaLookupRequestStruct(data);
|
|
const resp = new FelicaLookupResponseStruct();
|
|
|
|
if (!IsSupportedFelica(req.osVer)) {
|
|
logger.verbose("Rejecting card of unknown OS version.", {
|
|
req,
|
|
});
|
|
|
|
header.result = ResultCodes.INVALID_AIME_ID;
|
|
resp.felicaIndex = -1;
|
|
return resp;
|
|
}
|
|
|
|
const result = await db
|
|
.select()
|
|
.from(felicaMobileLookup)
|
|
.where(eq(felicaMobileLookup.idm, req.idm.toString(16)))
|
|
.limit(1)
|
|
.then((r) => r[0]);
|
|
|
|
if (result) {
|
|
header.result = ResultCodes.ID_ALREADY_REGISTERED;
|
|
resp.felicaIndex = result.id;
|
|
resp.accessCode.set(Buffer.from(result.accessCode, "hex"));
|
|
|
|
return resp;
|
|
}
|
|
|
|
if (!Config.AIMEDB_CONFIG.AIME_MOBILE_CARD_KEY) {
|
|
logger.error(
|
|
"AIMEDB_CONFIG.AIME_MOBILE_CARD_KEY is not set in config file. Cannot register FeliCa Mobile ID.",
|
|
{ req }
|
|
);
|
|
|
|
header.result = ResultCodes.INVALID_AIME_ID;
|
|
resp.felicaIndex = -1;
|
|
return resp;
|
|
}
|
|
|
|
const mostRecentRow = await db
|
|
.select()
|
|
.from(felicaMobileLookup)
|
|
.orderBy(desc(felicaMobileLookup.id))
|
|
.limit(1)
|
|
.then((r) => r[0]);
|
|
|
|
const nextId = mostRecentRow ? mostRecentRow.id + 1 : 1;
|
|
|
|
const accessCode = CalculateAccessCode(
|
|
nextId,
|
|
"01035",
|
|
Config.AIMEDB_CONFIG.AIME_MOBILE_CARD_KEY
|
|
);
|
|
|
|
logger.verbose(`Created FeliCa Mobile access code for serial ${nextId}.`, {
|
|
nextId,
|
|
accessCode,
|
|
});
|
|
|
|
const value = { idm: req.idm.toString(16), accessCode };
|
|
const row = await db
|
|
.insert(felicaMobileLookup)
|
|
.values(value)
|
|
.returning()
|
|
.then((r) => r[0]);
|
|
|
|
if (!row) {
|
|
logger.crit("Failed to insert new lookup entry into the database.", value);
|
|
header.result = ResultCodes.UNKNOWN_ERROR;
|
|
resp.felicaIndex = -1;
|
|
return resp;
|
|
}
|
|
|
|
header.result = ResultCodes.SUCCESS;
|
|
resp.felicaIndex = row.id;
|
|
resp.accessCode.set(Buffer.from(row.accessCode, "hex"));
|
|
|
|
return resp;
|
|
};
|