diff --git a/src/aimedb/decoder.test.ts b/src/aimedb/decoder.test.ts index 29edc7a..fb061cd 100644 --- a/src/aimedb/decoder.test.ts +++ b/src/aimedb/decoder.test.ts @@ -1,7 +1,7 @@ import { Decoder } from "./decoder"; function decode(req) { - const decoder = new Decoder({}); + const decoder = new Decoder(); decoder.write(req); diff --git a/src/aimedb/decoder.ts b/src/aimedb/decoder.ts index a8d236c..cf80dc8 100644 --- a/src/aimedb/decoder.ts +++ b/src/aimedb/decoder.ts @@ -1,5 +1,82 @@ import { Transform } from "stream"; +import * as Request from "./request"; + +function begin(msg: Buffer): Request.AimeRequestBase { + const gameId = msg.toString("ascii", 0x000a, 0x000e); + const keychipId = msg.toString("ascii", 0x0014, 0x001f); + + return { gameId, keychipId }; +} + +function readerRegisterRequest(msg: Buffer): Request.RegisterRequest { + const luid = msg.slice(0x0020, 0x002a).toString("hex"); + + return { + ...begin(msg), + type: "register", + luid, + }; +} + +function readLogRequest(msg: Buffer): Request.LogRequest { + // idk what any of this stuff means yet + // field20 and field28 appear to be an aime id but that is all. + + return { + ...begin(msg), + type: "log", + field20: msg.readUInt32LE(0x20), + field24: msg.readUInt32LE(0x24), + field28: msg.readUInt32LE(0x28), + field2C: msg.readUInt32LE(0x2c), + field30: msg.readUInt32LE(0x30), + field34: msg.readUInt32LE(0x34), + field38: msg.readUInt32LE(0x38), + field3C: msg.readUInt32LE(0x3c), + }; +} + +function readCampaignRequest(msg: Buffer): Request.CampaignRequest { + return { + ...begin(msg), + type: "campaign", + }; +} + +function readLookupRequest(msg: Buffer): Request.LookupRequest { + const luid = msg.slice(0x0020, 0x002a).toString("hex"); + + return { + ...begin(msg), + type: "lookup", + luid, + }; +} + +function readHelloRequest(msg: Buffer): Request.HelloRequest { + return { + ...begin(msg), + type: "hello", + }; +} + +function readGoodbyeRequest(msg: Buffer): Request.GoodbyeRequest { + return { + ...begin(msg), + type: "goodbye", + }; +} + +const readers = new Map Request.AimeRequest>(); + +readers.set(0x0005, readerRegisterRequest); +readers.set(0x0009, readLogRequest); +readers.set(0x000b, readCampaignRequest); +readers.set(0x000f, readLookupRequest); +readers.set(0x0064, readHelloRequest); +readers.set(0x0066, readGoodbyeRequest); + export class Decoder extends Transform { constructor() { super({ @@ -8,88 +85,16 @@ export class Decoder extends Transform { }); } - static ident(chunk) { - const gameId = chunk.toString("ascii", 0x000a, 0x000e); - const keychipId = chunk.toString("ascii", 0x0014, 0x001f); + _transform(msg: Buffer, encoding, callback) { + const code = msg.readUInt16LE(0x04); + const reader = readers.get(code); - return { gameId, keychipId }; - } - - static cardId(chunk) { - const luid = chunk.slice(0x0020, 0x002a); - - return { luid, ...Decoder.ident(chunk) }; - } - - static log(chunk) { - // idk what any of this stuff means yet - // field20 and field28 appear to be an aime id but that is all. - - const field20 = chunk.readUInt32LE(0x20); - const field24 = chunk.readUInt32LE(0x24); - const field28 = chunk.readUInt32LE(0x28); - const field2C = chunk.readUInt32LE(0x2c); - const field30 = chunk.readUInt32LE(0x30); - const field34 = chunk.readUInt32LE(0x34); - const field38 = chunk.readUInt32LE(0x38); - const field3C = chunk.readUInt32LE(0x3c); - - return { - field20, - field24, - field28, - field2C, - field30, - field34, - field38, - field3C, - ...Decoder.ident(chunk), - }; - } - - _transform(chunk, encoding, callback) { - const cmd = chunk.readUInt16LE(0x04); - - switch (cmd) { - case 0x0005: - return callback(null, { - cmd: "register", - ...Decoder.cardId(chunk), - }); - - case 0x0009: - return callback(null, { - cmd: "log", - ...Decoder.log(chunk), - }); - - case 0x000b: - return callback(null, { - cmd: "campaign", - ...Decoder.ident(chunk), - }); - - case 0x000f: - return callback(null, { - cmd: "lookup", - ...Decoder.cardId(chunk), - }); - - case 0x0064: - return callback(null, { - cmd: "hello", - ...Decoder.ident(chunk), - }); - - case 0x0066: - return callback(null, { - cmd: "goodbye", - }); - - default: - return callback(null, { - cmd: `unknown_${cmd}`, - }); + if (reader === undefined) { + return callback( + new Error(`Unknown command code 0x${code.toString(16)}`) + ); } + + return callback(null, reader(msg)); } } diff --git a/src/aimedb/encoder.ts b/src/aimedb/encoder.ts index 25bb32a..8631855 100644 --- a/src/aimedb/encoder.ts +++ b/src/aimedb/encoder.ts @@ -1,10 +1,22 @@ import { Transform } from "stream"; -const registerLevels = { - none: 0, - portal: 1, - segaid: 2, -}; +import { AimeResponse, RegisterLevel } from "./response"; + +const registerLevels = new Map(); + +registerLevels.set("none", 0); +registerLevels.set("portal", 1); +registerLevels.set("segaid", 2); + +function begin(length: number) { + const buf = Buffer.alloc(length); + + buf.writeUInt16LE(0xa13e, 0x0000); // Magic? + buf.writeUInt16LE(0x3087, 0x0002); // ??? + buf.writeUInt16LE(length, 0x0006); + + return buf; +} export class Encoder extends Transform { constructor() { @@ -14,35 +26,25 @@ export class Encoder extends Transform { }); } - static init(length) { - const buf = Buffer.alloc(length); + _transform(msg: AimeResponse, encoding, callback) { + console.log("Aimedb: Encode", msg); - buf.writeUInt16LE(0xa13e, 0x0000); // Magic? - buf.writeUInt16LE(0x3087, 0x0002); // ??? - buf.writeUInt16LE(length, 0x0006); + let buf: Buffer; - return buf; - } - - _transform(chunk, encoding, callback) { - console.log("Aimedb: Encode", chunk); - - let buf; - - switch (chunk.cmd) { + switch (msg.type) { case "hello": - buf = Encoder.init(0x0020); + buf = begin(0x0020); buf.writeUInt16LE(0x0065, 0x0004); // cmd code - buf.writeUInt16LE(chunk.status, 0x0008); + buf.writeUInt16LE(msg.status, 0x0008); break; case "campaign": // Still figuring this out... - buf = Encoder.init(0x0200); + buf = begin(0x0200); buf.writeUInt16LE(0x000c, 0x0004); // cmd code - buf.writeUInt16LE(chunk.status, 0x0008); + buf.writeUInt16LE(msg.status, 0x0008); // Campaign array starts at 0x20 // Element size is 0xA0 @@ -53,31 +55,31 @@ export class Encoder extends Transform { // -1 aime id means card is not registered // register level does not seem to matter - buf = Encoder.init(0x0130); + buf = begin(0x0130); buf.writeUInt16LE(0x0010, 0x0004); // cmd code - buf.writeUInt16LE(chunk.status, 0x0008); - buf.writeInt32LE(chunk.aimeId || -1, 0x0020); - buf.writeUInt8(registerLevels[chunk.registerLevel], 0x0024); + buf.writeUInt16LE(msg.status, 0x0008); + buf.writeInt32LE(msg.aimeId || -1, 0x0020); + buf.writeUInt8(registerLevels[msg.registerLevel], 0x0024); break; case "register": - buf = Encoder.init(0x0030); + buf = begin(0x0030); buf.writeUInt16LE(0x0006, 0x0004); // cmd code - buf.writeUInt16LE(chunk.status, 0x0008); - buf.writeInt32LE(chunk.aimeId, 0x0020); + buf.writeUInt16LE(msg.status, 0x0008); + buf.writeInt32LE(msg.aimeId, 0x0020); break; case "log": - buf = Encoder.init(0x0020); + buf = begin(0x0020); buf.writeUInt16LE(0x000a, 0x0004); // cmd code - buf.writeUInt16LE(chunk.status, 0x0008); + buf.writeUInt16LE(msg.status, 0x0008); break; default: - return callback(new Error(`Unimplemented response: ${chunk.cmd}`)); + return callback(new Error("Unimplemented response type")); } console.log("Aimedb: Send", buf); diff --git a/src/aimedb/index.ts b/src/aimedb/index.ts index e07193d..531079f 100644 --- a/src/aimedb/index.ts +++ b/src/aimedb/index.ts @@ -1,3 +1,4 @@ +import { AimeRequest } from "./request"; import { setup } from "./pipeline"; export default async function aimedb(socket) { @@ -6,27 +7,27 @@ export default async function aimedb(socket) { const { input, output } = setup(socket); try { - for await (const req of input) { + for await (const obj of input) { + const req = obj as AimeRequest; + console.log("Aimedb: Decode", req); - const { cmd } = req; - - switch (cmd) { + switch (req.type) { case "hello": console.log("Aimedb: Hello"); - output.write({ cmd, status: 1 }); + output.write({ type: req.type, status: 1 }); break; case "campaign": console.log("Aimedb: Campaign stuff"); - output.write({ cmd, status: 1 }); + output.write({ type: req.type, status: 1 }); break; case "lookup": console.log("Aimedb: Mifare lookup", req.luid); - output.write({ cmd, status: 1 }); // Add aimeId if desired + output.write({ type: req.type, status: 1 }); // Add aimeId if desired break; @@ -34,13 +35,13 @@ export default async function aimedb(socket) { // We get sent here if lookup does not return an aimeId console.log("Aimedb: Mifare register", req.luid); - output.write({ cmd, status: 1, aimeId: 12345678 }); + output.write({ type: req.type, status: 1, aimeId: 12345678 }); break; case "log": console.log("Aimedb: Log message"); - output.write({ cmd, status: 1 }); + output.write({ type: req.type, status: 1 }); break; diff --git a/src/aimedb/request.ts b/src/aimedb/request.ts new file mode 100644 index 0000000..ebba100 --- /dev/null +++ b/src/aimedb/request.ts @@ -0,0 +1,46 @@ +export interface AimeRequestBase { + gameId: string; + keychipId: string; +} + +export interface RegisterRequest extends AimeRequestBase { + type: "register"; + luid: string; +} + +export interface LogRequest extends AimeRequestBase { + type: "log"; + field20: number; + field24: number; + field28: number; + field2C: number; + field30: number; + field34: number; + field38: number; + field3C: number; +} + +export interface CampaignRequest extends AimeRequestBase { + type: "campaign"; +} + +export interface LookupRequest extends AimeRequestBase { + type: "lookup"; + luid: string; +} + +export interface HelloRequest extends AimeRequestBase { + type: "hello"; +} + +export interface GoodbyeRequest extends AimeRequestBase { + type: "goodbye"; +} + +export type AimeRequest = + | CampaignRequest + | GoodbyeRequest + | HelloRequest + | LogRequest + | LookupRequest + | RegisterRequest; diff --git a/src/aimedb/response.ts b/src/aimedb/response.ts new file mode 100644 index 0000000..5e3ec87 --- /dev/null +++ b/src/aimedb/response.ts @@ -0,0 +1,35 @@ +export type RegisterLevel = "none" | "portal" | "segaid"; + +export interface AimeResponseBase { + status: number; +} + +export interface CampaignResponse extends AimeResponseBase { + type: "campaign"; +} + +export interface HelloResponse extends AimeResponseBase { + type: "hello"; +} + +export interface LogResponse extends AimeResponseBase { + type: "log"; +} + +export interface LookupResponse extends AimeResponseBase { + type: "lookup"; + aimeId?: number; + registerLevel: RegisterLevel; +} + +export interface RegisterResponse extends AimeResponseBase { + type: "register"; + aimeId: number; +} + +export type AimeResponse = + | CampaignResponse + | HelloResponse + | LogResponse + | LookupResponse + | RegisterResponse;