mirror of
https://github.com/djhackersdev/minime.git
synced 2026-04-26 10:00:00 -05:00
wip
This commit is contained in:
parent
acc1590aa2
commit
558c05095a
33
src/idz/bigint.ts
Normal file
33
src/idz/bigint.ts
Normal 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
91
src/idz/decoder.ts
Normal 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
14
src/idz/defs.ts
Normal 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
76
src/idz/encoder.ts
Normal 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
80
src/idz/index.ts
Normal 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
11
src/idz/ping.ts
Normal 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
62
src/idz/setup.ts
Normal 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 };
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,8 @@ import aimedb from "./aimedb";
|
||||||
import billing from "./billing";
|
import billing from "./billing";
|
||||||
import chunithm from "./chunithm";
|
import chunithm from "./chunithm";
|
||||||
import diva from "./diva";
|
import diva from "./diva";
|
||||||
|
import idz from "./idz";
|
||||||
|
import idzPing from "./idz/ping";
|
||||||
import startup from "./startup";
|
import startup from "./startup";
|
||||||
|
|
||||||
const tls = {
|
const tls = {
|
||||||
|
|
@ -21,4 +23,10 @@ https.createServer(tls, billing).listen(8443);
|
||||||
http.createServer(chunithm).listen(9000);
|
http.createServer(chunithm).listen(9000);
|
||||||
http.createServer(diva).listen(9001);
|
http.createServer(diva).listen(9001);
|
||||||
|
|
||||||
|
net.createServer(idz).listen(10000);
|
||||||
|
idzPing(10001);
|
||||||
|
idzPing(10003);
|
||||||
|
idzPing(10010);
|
||||||
|
idzPing(10011);
|
||||||
|
|
||||||
console.log("Startup OK");
|
console.log("Startup OK");
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,15 @@ import read = require("raw-body");
|
||||||
import { unzipSync } from "zlib";
|
import { unzipSync } from "zlib";
|
||||||
import { hostname } from "os";
|
import { hostname } from "os";
|
||||||
|
|
||||||
const services = new Map();
|
const myHost = hostname();
|
||||||
|
const uris = new Map<string, string>();
|
||||||
|
|
||||||
services.set("SDBT", 9000); // Chunithm
|
uris.set("SDBT", `http://${myHost}:9000/`); // Chunithm
|
||||||
services.set("SBZV", 9001); // Project Diva Future Tone
|
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();
|
const app = express();
|
||||||
|
|
||||||
|
|
@ -60,9 +65,6 @@ app.use(async function(req, res, next) {
|
||||||
app.post("/sys/servlet/PowerOn", function(req, resp) {
|
app.post("/sys/servlet/PowerOn", function(req, resp) {
|
||||||
console.log("\n--- Startup Request ---\n\n", req.body);
|
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
|
// Cut milliseconds out of ISO timestamp
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
@ -71,8 +73,8 @@ app.post("/sys/servlet/PowerOn", function(req, resp) {
|
||||||
|
|
||||||
const resParams = {
|
const resParams = {
|
||||||
stat: 1,
|
stat: 1,
|
||||||
uri,
|
uri: uris.get(req.body.game_id) || "",
|
||||||
host: "",
|
host: hosts.get(req.body.game_id) || "",
|
||||||
place_id: "123",
|
place_id: "123",
|
||||||
name: "Name",
|
name: "Name",
|
||||||
nickname: "Nick",
|
nickname: "Nick",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./bin/",
|
"outDir": "./bin/",
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"target": "es2017"
|
"target": "esnext"
|
||||||
},
|
},
|
||||||
"include": ["./src/"],
|
"include": ["./src/"],
|
||||||
"exclude": ["./src/**/*.test.ts"]
|
"exclude": ["./src/**/*.test.ts"]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user