Files
minime/src/billing.ts
2019-11-06 17:29:07 -05:00

145 lines
3.3 KiB
TypeScript

import express from "express";
import logger from "debug";
import read from "raw-body";
import { createSign } from "crypto";
import { inflateRawSync } from "zlib";
import { readFileSync } from "fs";
interface Kvps {
[key: string]: string;
}
const debug = logger("app:billing");
const billingKeyPair = readFileSync("pki/billing.key");
// nearfull: high 16 bits is billing mode, low 16 bits is actual nearfull val.
const playlimit = 1024;
const nearfull = 0x00010200;
const app = express();
app.use(async function(req, res, next) {
const reqBytesZ = await read(req);
const reqBytes = inflateRawSync(reqBytesZ);
const reqStr = reqBytes.toString("ascii");
req.body = reqStr
.trim()
.split("\r\n")
.map(line => {
// Crack key-value pairs. There is no URI encoding here.
const params = {};
for (const kvp of line.split("&")) {
const [key, val] = kvp.split("=");
params[key] = val;
}
return params;
});
const send_ = res.send;
res.send = (lines: Kvps[]) => {
const str =
lines
.map(line =>
Object.entries(line)
.map(([key, val]) => key + "=" + val)
.join("&")
)
.join("\r\n") + "\r\n";
res.set("content-type", "text/plain");
return send_.apply(res, [str]);
};
return next();
});
app.post("/request/", function(req, res) {
debug("Billing request: %j", req.body);
const first = req.body[0];
// Do cryptographic signatures
const [playlimitsig, nearfullsig] = [playlimit, nearfull].map(value => {
const buf = Buffer.alloc(15);
buf.writeInt32LE(value, 0);
buf.write(first.keychipid, 4);
return createSign("RSA-SHA1")
.update(buf)
.sign(billingKeyPair, "hex");
});
// Assemble other params
const resItems: Kvps[] = [];
resItems.push({
// 0 or 6 is success, anything else is an error
result: "0",
// ???
waittime: "100",
// Some sort of bandwidth-limiting thing..?
// "line" refers to the shop's internet connection
linelimit: "1",
// Server error message, copied to ALLNet debug log
message: "",
// Keychip odometer limit. Game will lock out if the odometer reaches
// this value
playlimit: playlimit.toString(),
// RSA-SHA1 signature for new odometer limit.
playlimitsig,
// idk
protocolver: "1.000",
// A compound of two values. High 16 bits is the billing mode, low 16 bits
// is the actual "nearfull" value. Not sure exactly what nearfull is, at a
// guess it causes the client to check-in with the billing server when the
// odometer is fewer than this many ticks away from its current limit.
nearfull: nearfull.toString(),
// RSA-SHA1 signature for the 32-bit nearfull value.
nearfullsig,
// ???
fixlogcnt: "0",
// ???
fixinterval: "5",
// Monthly play count summary, visible in operator menu. The value below
// is for a virgin machine fresh from the factory. Another example might be
//
// 201809/123:201810/456:201811/789
//
// which means
//
// Month 2018/09: 123 total plays
// Month 2018/10: 456 total plays
// Month 2018/11: 789 total plays
playhistory: "000000/0:000000/0:000000/0",
});
debug("Billing response: %j", resItems);
res.set("content-type", "text/plain");
res.send(resItems);
});
export default app;