some logging bullshit here, some refactoring there

This commit is contained in:
beerpsi 2023-11-22 13:13:14 +07:00
parent ad0e31bf55
commit c22cc400d5
2 changed files with 149 additions and 79 deletions

View File

@ -27,6 +27,50 @@ import type { AimeDBHandlerFn } from "../types/handlers";
const logger = CreateLogCtx(__filename);
async function createAndInsertNewAccessCode(
table: typeof felicaCardLookup | typeof felicaMobileLookup,
cardType: "FeliCa Card" | "FeliCa Mobile",
idm: bigint,
prefix: string,
key: string
) {
const mostRecentRow = await db
.select()
.from(table)
.orderBy(desc(table.id))
.limit(1)
.then((r) => r[0]);
const nextId = mostRecentRow ? mostRecentRow.id + 1 : 1;
const accessCode = CalculateAccessCode(nextId, prefix, key);
logger.debug(`Created ${cardType} access code for serial ${nextId}.`, {
nextId,
accessCode,
});
const value = { idm: idm.toString(16), accessCode };
const result = await db
.insert(felicaCardLookup)
.values(value)
.returning()
.then((r) => r[0]);
if (!result) {
logger.crit("Failed to insert new lookup entry into the database.", value);
return null;
}
return result;
}
// This is supposed to be just a lookup handler, but my guess is that most games assume that there
// is already a valid card in the database if the response is successful and don't bother to try
// registering a new card, if the card is not FeliCa Mobile, since the game will happily display
// access code 00000000000000000000.
export const FelicaLookupHandler: AimeDBHandlerFn<"FelicaLookupResponse"> = async (
header,
data
@ -46,22 +90,56 @@ export const FelicaLookupHandler: AimeDBHandlerFn<"FelicaLookupResponse"> = asyn
const table = IsSupportedFelicaMobile(req.osVer) ? felicaMobileLookup : felicaCardLookup;
const result = await db
let 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"));
// Exit early if card is FeliCa mobile, so the game can use the register endpoint
if (IsSupportedFelicaMobile(req.osVer)) {
resp.felicaIndex = -1;
return resp;
}
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 tmp = await createAndInsertNewAccessCode(
felicaCardLookup,
"FeliCa Card",
req.idm,
Config.AIMEDB_CONFIG.RESERVED_CARD_PREFIX,
Config.AIMEDB_CONFIG.RESERVED_CARD_KEY
);
if (!tmp) {
header.result = ResultCodes.UNKNOWN_ERROR;
return resp;
}
result = tmp;
}
header.result = ResultCodes.SUCCESS;
resp.felicaIndex = result.id;
resp.accessCode.set(Buffer.from(result.accessCode, "hex"));
return resp;
};
// This is supposed to be just a lookup handler, but my guess is that most games assume that there
// is already a valid card in the database if the response is successful and don't bother to try
// registering a new card, if the card is not FeliCa Mobile, since the game will happily display
// access code 00000000000000000000.
export const FelicaExtendedLookupHandler: AimeDBHandlerFn<"FelicaExtendedLookupResponse"> = async (
header,
data
@ -112,16 +190,15 @@ export const FelicaExtendedLookupHandler: AimeDBHandlerFn<"FelicaExtendedLookupR
: result.aimedb_felica_card_lookup;
const cardResult = result.aimedb_card;
// 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;
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;
@ -143,43 +220,36 @@ export const FelicaExtendedLookupHandler: AimeDBHandlerFn<"FelicaExtendedLookupR
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,
const row = await createAndInsertNewAccessCode(
felicaCardLookup,
"FeliCa Card",
req.idm,
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;
// 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;
resp.accessCode.set(Buffer.from(row.accessCode, "hex"));
const cardResult = await db
.select()
.from(card)
.where(eq(card.accessCode, row.accessCode))
.then((r) => r[0]);
if (cardResult) {
resp.accountId = cardResult.id;
resp.portalRegistered = PortalRegistration.UNREGISTERED;
}
return resp;
};
@ -193,9 +263,12 @@ export const FelicaRegisterHandler: AimeDBHandlerFn<"FelicaLookupResponse"> = as
const req = new FelicaLookupRequestStruct(data);
const resp = new FelicaLookupResponseStruct();
if (!IsSupportedFelica(req.osVer)) {
logger.verbose("Rejecting card of unknown OS version.", {
req,
if (!IsSupportedFelicaMobile(req.osVer)) {
logger.error("Rejecting card because it is not FeliCa Mobile.", {
idm: req.idm,
chipCode: req.chipCode,
osVer: req.osVer,
timing: req.timing,
});
header.result = ResultCodes.INVALID_AIME_ID;
@ -229,35 +302,15 @@ export const FelicaRegisterHandler: AimeDBHandlerFn<"FelicaLookupResponse"> = as
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,
const row = await createAndInsertNewAccessCode(
felicaMobileLookup,
"FeliCa Mobile",
req.idm,
"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;

View File

@ -35,14 +35,20 @@ const AimeDBServerFactory = () => {
const AIMEDB_KEY = Buffer.from(Config.AIMEDB_CONFIG.KEY, "utf-8");
const logResponse = (header: InstanceType<typeof PacketHeaderStruct>, body: unknown) => {
const logResponse = (
socket: Socket,
header: InstanceType<typeof PacketHeaderStruct>,
body: unknown
) => {
if (header.result !== 1) {
logger.info(`Returned result code ${header.result}.`, {
fromIp: socket.remoteAddress,
header,
body,
});
} else {
logger.verbose(`Returned result code ${header.result}.`, {
fromIp: socket.remoteAddress,
header,
body,
});
@ -84,16 +90,16 @@ const AimeDBServerFactory = () => {
});
socket.on("data", async (data) => {
logger.verbose(`Received packet: ${data.toString("hex")}.`);
logger.debug(`Received packet: ${data.toString("hex")}.`, {
ip: socket.remoteAddress,
});
let packet: Buffer;
try {
packet = decryptPacket(data, AIMEDB_KEY);
} catch (err) {
logger.error("Could not decrypt AimeDB packet.", {
err,
});
logger.error("Could not decrypt AimeDB packet.", { err });
return;
}
@ -104,29 +110,35 @@ const AimeDBServerFactory = () => {
} catch (err) {
logger.error("Decoding AimeDB header failed.", {
err,
data: packet.toString("hex"),
header: packet.slice(0, 32).toString("hex"),
});
return;
}
logger.debug("Received AimeDB request.", {
header,
data: packet.slice(32).toString("hex"),
});
if (header.magic !== 0xa13e) {
logger.error("Request's magic bytes did not match expected value 0xA13E.", {
ip: socket.remoteAddress,
header,
});
return;
}
if (header.keychipId === "ABCD1234567" || header.storeId === 0xfff0) {
logger.warning("Received request from uninitialized AMLib.", { header });
logger.warning("Received request from uninitialized AMLib.", {
ip: socket.remoteAddress,
header,
body: packet.slice(0, 32).toString("hex"),
});
} else {
logger.debug("Received AimeDB request.", {
ip: socket.remoteAddress,
header,
body: packet.slice(0, 32).toString("hex"),
});
}
if (header.commandId === CommandId.CLIENT_END_REQUEST) {
logger.debug("Client ended the session.", { header });
logger.debug("Client ended the session.", { ip: socket.remoteAddress });
socket.destroy();
return;
}
@ -142,6 +154,8 @@ const AimeDBServerFactory = () => {
);
} else if (expectedLength !== header.length || packet.length !== header.length) {
logger.error("Packet does not have expected size.", {
ip: socket.remoteAddress,
header,
expectedLength,
actualLength: packet.length,
declaredLength: header.length,
@ -150,7 +164,7 @@ const AimeDBServerFactory = () => {
header.result = ResultCodes.UNKNOWN_ERROR;
header.length = 32;
logResponse(header, null);
logResponse(socket, header, null);
writeResponse(socket, header, null);
return;
@ -159,13 +173,16 @@ const AimeDBServerFactory = () => {
const handler = AIMEDB_HANDLERS[header.commandId];
if (!handler) {
logger.error("No handlers available for the requested command ID.", { header });
logger.error("No handlers available for the requested command ID.", {
ip: socket.remoteAddress,
header,
});
return;
}
const body = await handler(header, packet.slice(32));
logResponse(header, body);
logResponse(socket, header, body);
writeResponse(socket, header, body);
});