This commit is contained in:
Tau 2019-03-07 23:50:51 -05:00
parent acc1590aa2
commit 558c05095a
10 changed files with 386 additions and 9 deletions

33
src/idz/bigint.ts Normal file
View File

@ -0,0 +1,33 @@
export function byteString(n: bigint, length: number) {
const result = Buffer.alloc(length);
for (let i = 0; i < length; i++) {
const shift = 8n * BigInt(i);
const byte = (n >> shift) & 0xffn;
result[i] = Number(byte);
}
return result;
}
// i pick the one implementation language that doesn't have this built in
export function modPow(b: bigint, e: bigint, m: bigint) {
// https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method
let result = 1n;
b = b % m;
while (e > 0n) {
if ((e & 1n) === 1n) {
result = (result * b) % m;
}
e = e >> 1n;
b = (b * b) % m;
}
return result;
}

91
src/idz/decoder.ts Normal file
View File

@ -0,0 +1,91 @@
import { Transform } from "stream";
import { MSG, MSG_LEN } from "./defs";
const readers = new Map();
function readHeader(buf) {
return {
blah: "blah",
};
}
readers.set(MSG.GET_CONFIG_DATA_REQ, buf => {
return { type: "get_config_data_req" };
});
readers.set(MSG.GET_CONFIG_DATA_2_REQ, buf => {
return { type: "get_config_data_2_req" };
});
readers.set(MSG.GET_SERVER_LIST_REQ, buf => {
return { type: "get_server_list_req" };
});
export class Decoder extends Transform {
state: Buffer;
constructor() {
super({
readableObjectMode: true,
writableObjectMode: true,
});
this.state = Buffer.alloc(0);
}
_transform(chunk, encoding, callback) {
this.state = Buffer.concat([this.state, chunk]);
// Read header
if (this.state.length < 0x04) {
return callback(null);
}
const magic = this.state.readUInt32LE(0);
if (magic !== 0x01020304) {
return callback(
new Error(
"Invalid magic number, cryptographic processing probably incorrect."
)
);
}
if (this.state.length < 0x30) {
return callback(null);
}
const header = readHeader(this.state);
if (this.state.length < 0x32) {
return callback(null);
}
const msgCode = this.state.readUInt16LE(0x30);
const msgLen = MSG_LEN.get(msgCode);
const reader = readers.get(msgCode);
if (msgLen === undefined || reader === undefined) {
return callback(
new Error(
`Unknown command code ${msgCode.toString(16)}, cannot continue`
)
);
}
if (this.state.length < 0x30 + msgLen) {
return callback(null);
}
const reqBuf = this.state.slice(0x30, 0x30 + msgLen);
const payload = reader(reqBuf);
console.log("Idz: RAW:", reqBuf.toString("hex"));
console.log("Idz: Header:", header);
console.log("Idz: Payload:", payload);
return callback(null, { header, payload });
}
}

14
src/idz/defs.ts Normal file
View File

@ -0,0 +1,14 @@
export const MSG = {
GET_CONFIG_DATA_REQ: 0x0004,
GET_CONFIG_DATA_RES: 0x0005,
GET_SERVER_LIST_REQ: 0x0006,
GET_SERVER_LIST_RES: 0x0007,
GET_CONFIG_DATA_2_REQ: 0x00ab,
GET_CONFIG_DATA_2_RES: 0x00ac,
};
export const MSG_LEN = new Map();
MSG_LEN.set(MSG.GET_CONFIG_DATA_REQ, 0x0050);
MSG_LEN.set(MSG.GET_CONFIG_DATA_2_REQ, 0x0010);
MSG_LEN.set(MSG.GET_SERVER_LIST_REQ, 0x0020);

76
src/idz/encoder.ts Normal file
View File

@ -0,0 +1,76 @@
import { Transform } from "stream";
import { MSG } from "./defs";
const writers = new Map<string, (foo: any) => Buffer>();
writers.set("get_config_data_res", obj => {
const buf = Buffer.alloc(0x01a0);
buf.writeUInt16LE(MSG.GET_CONFIG_DATA_RES, 0x0000);
buf.writeUInt16LE(obj.status, 0x0002);
return buf;
});
writers.set("get_config_data_2_res", obj => {
const buf = Buffer.alloc(0x230);
buf.writeUInt16LE(MSG.GET_CONFIG_DATA_2_RES, 0x0000);
buf.writeUInt16LE(obj.status, 0x0002);
return buf;
});
writers.set("get_server_list_res", obj => {
const buf = Buffer.alloc(0x04b0);
buf.writeUInt16LE(MSG.GET_SERVER_LIST_RES, 0x0000);
buf.writeUInt16LE(obj.status, 0x0002);
buf.write(obj.userDb.addr, 0x0004);
buf.writeUInt16LE(obj.userDb.tcp, 0x0084);
buf.writeUInt16LE(obj.userDb.http, 0x0086);
buf.write(obj.matchAddr, 0x0088);
buf.writeUInt16LE(obj.matchPort.tcp, 0x0108);
buf.writeUInt16LE(obj.matchPort.udpSend, 0x010a);
buf.writeUInt16LE(obj.matchPort.udpRecv, 0x010c);
buf.writeUInt16LE(obj.tagMatchPort.tcp, 0x010e);
buf.writeUInt16LE(obj.tagMatchPort.udpSend, 0x0110);
buf.writeUInt16LE(obj.tagMatchPort.udpRecv, 0x0112);
buf.write(obj.event.addr, 0x0114);
buf.writeUInt16LE(obj.event.tcp, 0x0194);
buf.write(obj.screenshot.addr, 0x0198);
buf.writeUInt16LE(obj.screenshot.tcp, 0x0218);
buf.write(obj.pingReturn, 0x021c);
buf.write(obj.echo1.addr, 0x029c);
buf.write(obj.echo2.addr, 0x031c);
buf.writeUInt16LE(obj.echo1.udp, 0x39c);
buf.writeUInt16LE(obj.echo2.udp, 0x39e);
buf.write(obj.newsUrl, 0x03a0);
buf.write(obj.reportErrorUrl, 0x0424);
return buf;
});
export class Encoder extends Transform {
constructor() {
super({
readableObjectMode: true,
writableObjectMode: true,
});
}
_transform(obj, encoding, callback) {
console.log("Idz: Response:", obj);
const writer = writers.get(obj.type);
if (writer === undefined) {
return callback(new Error(`No writer for type ${obj.type}`));
}
const buf = writer(obj);
return callback(null, buf);
}
}

80
src/idz/index.ts Normal file
View File

@ -0,0 +1,80 @@
import { hostname } from "os";
import setup from "./setup";
export default async function idz(socket) {
const { input, output } = setup(socket);
console.log("Idz: Connection opened");
try {
for await (const msg of input) {
switch (msg.payload.type) {
case "get_server_list_req":
const myHost = hostname();
output.write({
type: "get_server_list_res",
status: 1,
userDb: {
addr: myHost,
tcp: 10000,
http: 10001,
},
matchAddr: myHost,
matchPort: {
tcp: 10002,
udpSend: 10003,
udpRecv: 10004,
},
tagMatchPort: {
tcp: 10005,
udpSend: 10006,
udpRecv: 10007,
},
event: {
addr: myHost,
tcp: 10008,
},
screenshot: {
addr: myHost,
tcp: 10009,
},
pingReturn: myHost,
echo1: {
addr: myHost,
udp: 10010,
},
echo2: {
addr: myHost,
udp: 10011,
},
newsUrl: `http://${myHost}:10012/news`,
reportErrorUrl: `http://${myHost}:10013/error`,
});
break;
case "get_config_data_req":
output.write({
type: "get_config_data_res",
status: 1,
});
break;
case "get_config_data_2_req":
output.write({
type: "get_config_data_2_res",
status: 1,
});
break;
}
}
} catch (e) {
console.log("Idz: Error", e);
}
console.log("Idz: Connection closed");
}

11
src/idz/ping.ts Normal file
View File

@ -0,0 +1,11 @@
import { createSocket } from "dgram";
export default function createPing(port: number) {
const socket = createSocket("udp4");
socket.bind(port);
socket.on("message", (msg, rinfo) => {
console.log(`Idz Ping: Ping from ${rinfo.address}:${rinfo.port}`);
socket.send(msg, rinfo.port, rinfo.address);
});
}

62
src/idz/setup.ts Normal file
View File

@ -0,0 +1,62 @@
import { createCipheriv, createDecipheriv } from "crypto";
import { Socket } from "net";
import { pipeline } from "stream";
import { byteString, modPow } from "./bigint";
import { Decoder } from "./decoder";
import { Encoder } from "./encoder";
// Proof-of-concept, so we only ever use one of the ten RSA keys
const key = {
N: 4922323266120814292574970172377860734034664704992758249880018618131907367614177800329506877981986877921220485681998287752778495334541127048495486311792061n,
d: 1163847742215766215216916151663017691387519688859977157498780867776436010396072628219119707788340687440419444081289736279466637153082223960965411473296473n,
e: 3961365081960959178294197133768419551060435043430437330799371731939550352626564261219865471710058480523874787120718634318364066605378505537556570049131337n,
hashN: 2662304617,
};
// Proof-of-concept, so we only use one fixed session key
const sessionKey = 0xffddeeccbbaa99887766554433221100n;
// -- TEST --
const test1 = modPow(sessionKey, key.e, key.N);
const test2 = modPow(test1, key.d, key.N);
console.log("RSA ENC :", byteString(test1, 0x40).toString("hex"));
console.log("RSA ENCDEC :", byteString(test2, 0x40).toString("hex"));
// -- TEST --
export default function setup(socket: Socket) {
//
// Construct and transmit setup message
//
const keyEnc = modPow(sessionKey, key.e, key.N);
const msg = Buffer.alloc(0x48);
msg.set(byteString(keyEnc, 0x40), 0x00);
msg.writeUInt32LE(0x01020304, 0x40); // Meaning of this field is unknown
msg.writeUInt32LE(key.hashN, 0x44);
socket.write(msg);
//
// Set up pipeline
//
const keybuf = byteString(sessionKey, 0x10);
const input = pipeline(
socket,
createDecipheriv("aes-128-ecb", keybuf, null).setAutoPadding(false),
new Decoder()
);
const output = new Encoder();
pipeline(
output,
createCipheriv("aes-128-ecb", keybuf, null).setAutoPadding(false),
socket
);
return { input, output };
}

View File

@ -7,6 +7,8 @@ import aimedb from "./aimedb";
import billing from "./billing";
import chunithm from "./chunithm";
import diva from "./diva";
import idz from "./idz";
import idzPing from "./idz/ping";
import startup from "./startup";
const tls = {
@ -21,4 +23,10 @@ https.createServer(tls, billing).listen(8443);
http.createServer(chunithm).listen(9000);
http.createServer(diva).listen(9001);
net.createServer(idz).listen(10000);
idzPing(10001);
idzPing(10003);
idzPing(10010);
idzPing(10011);
console.log("Startup OK");

View File

@ -4,10 +4,15 @@ import read = require("raw-body");
import { unzipSync } from "zlib";
import { hostname } from "os";
const services = new Map();
const myHost = hostname();
const uris = new Map<string, string>();
services.set("SDBT", 9000); // Chunithm
services.set("SBZV", 9001); // Project Diva Future Tone
uris.set("SDBT", `http://${myHost}:9000/`); // Chunithm
uris.set("SBZV", `http://${myHost}:9001/`); // Project Diva Future Tone
const hosts = new Map<string, string>();
hosts.set("SDDF", `${myHost}:10000`); // Initial D Zero
const app = express();
@ -60,9 +65,6 @@ app.use(async function(req, res, next) {
app.post("/sys/servlet/PowerOn", function(req, resp) {
console.log("\n--- Startup Request ---\n\n", req.body);
const portNo = services.get(req.body.game_id);
const uri = portNo !== undefined ? `http://${hostname()}:${portNo}/` : "";
// Cut milliseconds out of ISO timestamp
const now = new Date();
@ -71,8 +73,8 @@ app.post("/sys/servlet/PowerOn", function(req, resp) {
const resParams = {
stat: 1,
uri,
host: "",
uri: uris.get(req.body.game_id) || "",
host: hosts.get(req.body.game_id) || "",
place_id: "123",
name: "Name",
nickname: "Nick",

View File

@ -5,7 +5,7 @@
"moduleResolution": "node",
"outDir": "./bin/",
"strictNullChecks": true,
"target": "es2017"
"target": "esnext"
},
"include": ["./src/"],
"exclude": ["./src/**/*.test.ts"]