mirror of
https://github.com/djhackersdev/minime.git
synced 2026-08-27 21:14:00 -05:00
Apply Prettier
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const { Transform } = require('stream')
|
||||
const { Transform } = require("stream");
|
||||
|
||||
class Decoder extends Transform {
|
||||
constructor(options) {
|
||||
@@ -6,84 +6,91 @@ class Decoder extends Transform {
|
||||
readableObjectMode: true,
|
||||
writableObjectMode: true,
|
||||
...options,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
static ident(chunk) {
|
||||
const gameId = chunk.toString('ascii', 0x000A, 0x000E)
|
||||
const keychipId = chunk.toString('ascii', 0x0014, 0x001F)
|
||||
const gameId = chunk.toString("ascii", 0x000a, 0x000e);
|
||||
const keychipId = chunk.toString("ascii", 0x0014, 0x001f);
|
||||
|
||||
return { gameId, keychipId }
|
||||
return { gameId, keychipId };
|
||||
}
|
||||
|
||||
static cardId(chunk) {
|
||||
const luid = chunk.slice(0x0020, 0x002A)
|
||||
const luid = chunk.slice(0x0020, 0x002a);
|
||||
|
||||
return { luid, ...Decoder.ident(chunk) }
|
||||
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)
|
||||
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)
|
||||
}
|
||||
field20,
|
||||
field24,
|
||||
field28,
|
||||
field2C,
|
||||
field30,
|
||||
field34,
|
||||
field38,
|
||||
field3C,
|
||||
...Decoder.ident(chunk),
|
||||
};
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, callback) {
|
||||
const cmd = chunk.readUInt16LE(0x04)
|
||||
const cmd = chunk.readUInt16LE(0x04);
|
||||
|
||||
switch (cmd) {
|
||||
case 0x0005:
|
||||
return callback(null, {
|
||||
cmd: 'register',
|
||||
cmd: "register",
|
||||
...Decoder.cardId(chunk),
|
||||
})
|
||||
});
|
||||
|
||||
case 0x0009:
|
||||
return callback(null, {
|
||||
cmd: 'log',
|
||||
cmd: "log",
|
||||
...Decoder.log(chunk),
|
||||
})
|
||||
});
|
||||
|
||||
case 0x000B:
|
||||
case 0x000b:
|
||||
return callback(null, {
|
||||
cmd: 'campaign',
|
||||
cmd: "campaign",
|
||||
...Decoder.ident(chunk),
|
||||
})
|
||||
});
|
||||
|
||||
case 0x000F:
|
||||
case 0x000f:
|
||||
return callback(null, {
|
||||
cmd: 'lookup',
|
||||
cmd: "lookup",
|
||||
...Decoder.cardId(chunk),
|
||||
})
|
||||
});
|
||||
|
||||
case 0x0064:
|
||||
return callback(null, {
|
||||
cmd: 'hello',
|
||||
...Decoder.ident(chunk)
|
||||
})
|
||||
cmd: "hello",
|
||||
...Decoder.ident(chunk),
|
||||
});
|
||||
|
||||
case 0x0066:
|
||||
return callback(null, {
|
||||
cmd: 'goodbye',
|
||||
})
|
||||
cmd: "goodbye",
|
||||
});
|
||||
|
||||
default:
|
||||
return callback(null, {
|
||||
cmd: `unknown_${cmd}`,
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,78 +101,78 @@ class Encoder extends Transform {
|
||||
readableObjectMode: true,
|
||||
writableObjectMode: true,
|
||||
...options,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
static init(length) {
|
||||
const buf = Buffer.alloc(length)
|
||||
const buf = Buffer.alloc(length);
|
||||
|
||||
buf.writeUInt16LE(0xA13E, 0x0000) // Magic?
|
||||
buf.writeUInt16LE(0x3087, 0x0002) // ???
|
||||
buf.writeUInt16LE(length, 0x0006)
|
||||
buf.writeUInt16LE(0xa13e, 0x0000); // Magic?
|
||||
buf.writeUInt16LE(0x3087, 0x0002); // ???
|
||||
buf.writeUInt16LE(length, 0x0006);
|
||||
|
||||
return buf
|
||||
return buf;
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, callback) {
|
||||
console.log('Aimedb: Encode', chunk)
|
||||
console.log("Aimedb: Encode", chunk);
|
||||
|
||||
let buf
|
||||
let buf;
|
||||
|
||||
switch (chunk.cmd) {
|
||||
case 'hello':
|
||||
buf = Encoder.init(0x0020)
|
||||
buf.writeUInt16LE(0x0065, 0x0004) // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008)
|
||||
case "hello":
|
||||
buf = Encoder.init(0x0020);
|
||||
buf.writeUInt16LE(0x0065, 0x0004); // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008);
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'campaign':
|
||||
case "campaign":
|
||||
// Still figuring this out...
|
||||
|
||||
buf = Encoder.init(0x0200)
|
||||
buf.writeUInt16LE(0x000C, 0x0004) // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008)
|
||||
buf = Encoder.init(0x0200);
|
||||
buf.writeUInt16LE(0x000c, 0x0004); // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008);
|
||||
|
||||
// Campaign array starts at 0x20
|
||||
// Element size is 0xA0
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'lookup':
|
||||
case "lookup":
|
||||
// -1 aime id means card is not registered
|
||||
// register level does not seem to matter
|
||||
|
||||
buf = Encoder.init(0x0130)
|
||||
buf.writeUInt16LE(0x0010, 0x0004) // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008)
|
||||
buf.writeInt32LE(chunk.aimeId || -1, 0x0020)
|
||||
buf.writeUInt8(Encoder.registerLevels[chunk.registerLevel], 0x0024)
|
||||
buf = Encoder.init(0x0130);
|
||||
buf.writeUInt16LE(0x0010, 0x0004); // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008);
|
||||
buf.writeInt32LE(chunk.aimeId || -1, 0x0020);
|
||||
buf.writeUInt8(Encoder.registerLevels[chunk.registerLevel], 0x0024);
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'register':
|
||||
buf = Encoder.init(0x0030)
|
||||
buf.writeUInt16LE(0x0006, 0x0004) // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008)
|
||||
buf.writeInt32LE(chunk.aimeId, 0x0020)
|
||||
case "register":
|
||||
buf = Encoder.init(0x0030);
|
||||
buf.writeUInt16LE(0x0006, 0x0004); // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008);
|
||||
buf.writeInt32LE(chunk.aimeId, 0x0020);
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
buf = Encoder.init(0x0020)
|
||||
buf.writeUInt16LE(0x000A, 0x0004) // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008)
|
||||
case "log":
|
||||
buf = Encoder.init(0x0020);
|
||||
buf.writeUInt16LE(0x000a, 0x0004); // cmd code
|
||||
buf.writeUInt16LE(chunk.status, 0x0008);
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
default:
|
||||
return callback(new Error(`Unimplemented response: ${cmd}`))
|
||||
return callback(new Error(`Unimplemented response: ${cmd}`));
|
||||
}
|
||||
|
||||
console.log('Aimedb: Send', buf)
|
||||
console.log("Aimedb: Send", buf);
|
||||
|
||||
return callback(null, buf)
|
||||
return callback(null, buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,9 +180,9 @@ Encoder.registerLevels = {
|
||||
none: 0,
|
||||
portal: 1,
|
||||
segaid: 2,
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Decoder,
|
||||
Encoder,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,47 +1,114 @@
|
||||
const { Decoder } = require('./cmd')
|
||||
const { Decoder } = require("./cmd");
|
||||
|
||||
function decode(req) {
|
||||
const decoder = new Decoder()
|
||||
const decoder = new Decoder();
|
||||
|
||||
decoder.write(req)
|
||||
decoder.write(req);
|
||||
|
||||
return decoder.read()
|
||||
return decoder.read();
|
||||
}
|
||||
|
||||
test('decode hello', () => {
|
||||
test("decode hello", () => {
|
||||
const req = Buffer.from([
|
||||
0x3e, 0xa1, 0x87, 0x30, 0x64, 0x00, 0x20, 0x00,
|
||||
0x00, 0x00, 0x53, 0x44, 0x42, 0x54, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x41, 0x36, 0x39, 0x45,
|
||||
0x30, 0x31, 0x41, 0x39, 0x39, 0x39, 0x39, 0x00,
|
||||
])
|
||||
0x3e,
|
||||
0xa1,
|
||||
0x87,
|
||||
0x30,
|
||||
0x64,
|
||||
0x00,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x53,
|
||||
0x44,
|
||||
0x42,
|
||||
0x54,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x41,
|
||||
0x36,
|
||||
0x39,
|
||||
0x45,
|
||||
0x30,
|
||||
0x31,
|
||||
0x41,
|
||||
0x39,
|
||||
0x39,
|
||||
0x39,
|
||||
0x39,
|
||||
0x00,
|
||||
]);
|
||||
|
||||
const obj = decode(req)
|
||||
const obj = decode(req);
|
||||
|
||||
expect(obj.cmd).toBe('hello')
|
||||
expect(obj.gameId).toBe('SDBT')
|
||||
expect(obj.keychipId).toBe('A69E01A9999')
|
||||
})
|
||||
expect(obj.cmd).toBe("hello");
|
||||
expect(obj.gameId).toBe("SDBT");
|
||||
expect(obj.keychipId).toBe("A69E01A9999");
|
||||
});
|
||||
|
||||
test('decode lookup', () => {
|
||||
test("decode lookup", () => {
|
||||
const req = Buffer.from([
|
||||
0x3e, 0xa1, 0x87, 0x30, 0x0f, 0x00, 0x30, 0x00,
|
||||
0x00, 0x00, 0x53, 0x44, 0x42, 0x54, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x41, 0x36, 0x39, 0x45,
|
||||
0x30, 0x31, 0x41, 0x39, 0x39, 0x39, 0x39, 0x00,
|
||||
0x01, 0x03, 0x64, 0x95, 0x85, 0x05, 0x23, 0x03,
|
||||
0x06, 0x76, 0x01, 0x02, 0x22, 0xc6, 0x4e, 0x00,
|
||||
])
|
||||
0x3e,
|
||||
0xa1,
|
||||
0x87,
|
||||
0x30,
|
||||
0x0f,
|
||||
0x00,
|
||||
0x30,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x53,
|
||||
0x44,
|
||||
0x42,
|
||||
0x54,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x41,
|
||||
0x36,
|
||||
0x39,
|
||||
0x45,
|
||||
0x30,
|
||||
0x31,
|
||||
0x41,
|
||||
0x39,
|
||||
0x39,
|
||||
0x39,
|
||||
0x39,
|
||||
0x00,
|
||||
0x01,
|
||||
0x03,
|
||||
0x64,
|
||||
0x95,
|
||||
0x85,
|
||||
0x05,
|
||||
0x23,
|
||||
0x03,
|
||||
0x06,
|
||||
0x76,
|
||||
0x01,
|
||||
0x02,
|
||||
0x22,
|
||||
0xc6,
|
||||
0x4e,
|
||||
0x00,
|
||||
]);
|
||||
|
||||
const obj = decode(req)
|
||||
const obj = decode(req);
|
||||
|
||||
expect(obj.cmd).toBe('lookup')
|
||||
expect(obj.gameId).toBe('SDBT')
|
||||
expect(obj.keychipId).toBe('A69E01A9999')
|
||||
expect(obj.cmd).toBe("lookup");
|
||||
expect(obj.gameId).toBe("SDBT");
|
||||
expect(obj.keychipId).toBe("A69E01A9999");
|
||||
expect(obj.luid).toEqual(
|
||||
Buffer.from([
|
||||
0x01, 0x03, 0x64, 0x95, 0x85, 0x05, 0x23, 0x03,
|
||||
0x06, 0x76,
|
||||
])
|
||||
)
|
||||
})
|
||||
Buffer.from([0x01, 0x03, 0x64, 0x95, 0x85, 0x05, 0x23, 0x03, 0x06, 0x76])
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,44 +1,44 @@
|
||||
const { Transform } = require('stream')
|
||||
const { Transform } = require("stream");
|
||||
|
||||
class Deframer extends Transform {
|
||||
constructor(options) {
|
||||
super({
|
||||
readableObjectMode: true,
|
||||
...options,
|
||||
})
|
||||
});
|
||||
|
||||
this.state = Buffer.alloc(0)
|
||||
this.state = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
_transform(chunk, encoding, callback) {
|
||||
this.state = Buffer.concat([ this.state, chunk ])
|
||||
this.state = Buffer.concat([this.state, chunk]);
|
||||
|
||||
if (this.state.length < 8) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const magic = this.state.readUInt16LE(0x0000)
|
||||
const magic = this.state.readUInt16LE(0x0000);
|
||||
|
||||
if (magic != 0xA13E) {
|
||||
return callback(new Error(`Invalid magic (decimal ${magic})`))
|
||||
if (magic != 0xa13e) {
|
||||
return callback(new Error(`Invalid magic (decimal ${magic})`));
|
||||
}
|
||||
|
||||
const len = this.state.readUInt16LE(0x0006)
|
||||
const len = this.state.readUInt16LE(0x0006);
|
||||
|
||||
if (this.state.length < len) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const frame = this.state.slice(0, len)
|
||||
const frame = this.state.slice(0, len);
|
||||
|
||||
console.log('Aimedb: Recv', frame)
|
||||
console.log("Aimedb: Recv", frame);
|
||||
|
||||
this.state = this.state.slice(len)
|
||||
this.state = this.state.slice(len);
|
||||
|
||||
return callback(null, frame)
|
||||
return callback(null, frame);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Deframer,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
const setup = require('./pipeline')
|
||||
const setup = require("./pipeline");
|
||||
|
||||
async function aimedb(socket) {
|
||||
console.log('Aimedb: Connection opened')
|
||||
console.log("Aimedb: Connection opened");
|
||||
|
||||
const { input, output } = setup(socket)
|
||||
const { input, output } = setup(socket);
|
||||
|
||||
try {
|
||||
for await (const req of input) {
|
||||
console.log('Aimedb: Decode', req)
|
||||
console.log("Aimedb: Decode", req);
|
||||
|
||||
const { cmd } = req
|
||||
const { cmd } = req;
|
||||
|
||||
switch (cmd) {
|
||||
case 'hello':
|
||||
console.log('Aimedb: Hello')
|
||||
output.write({ cmd, status: 1 })
|
||||
case "hello":
|
||||
console.log("Aimedb: Hello");
|
||||
output.write({ cmd, status: 1 });
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'campaign':
|
||||
console.log('Aimedb: Campaign stuff')
|
||||
output.write({ cmd, status: 1 })
|
||||
case "campaign":
|
||||
console.log("Aimedb: Campaign stuff");
|
||||
output.write({ cmd, status: 1 });
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'lookup':
|
||||
console.log('Aimedb: Mifare lookup', req.luid)
|
||||
output.write({ cmd, status: 1 }) // Add aimeId if desired
|
||||
case "lookup":
|
||||
console.log("Aimedb: Mifare lookup", req.luid);
|
||||
output.write({ cmd, status: 1 }); // Add aimeId if desired
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'register':
|
||||
case "register":
|
||||
// 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 })
|
||||
console.log("Aimedb: Mifare register", req.luid);
|
||||
output.write({ cmd, status: 1, aimeId: 12345678 });
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
console.log('Aimedb: Log message')
|
||||
output.write({ cmd, status: 1 })
|
||||
case "log":
|
||||
console.log("Aimedb: Log message");
|
||||
output.write({ cmd, status: 1 });
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
case 'goodbye':
|
||||
console.log('Aimedb: Goodbye')
|
||||
case "goodbye":
|
||||
console.log("Aimedb: Goodbye");
|
||||
|
||||
break
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('Aimedb: Handler not implemented!')
|
||||
console.log("Aimedb: Handler not implemented!");
|
||||
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Aimedb: Connection error:\n', e)
|
||||
console.log("Aimedb: Connection error:\n", e);
|
||||
}
|
||||
|
||||
console.log('Aimedb: Connection closed\n')
|
||||
socket.end()
|
||||
console.log("Aimedb: Connection closed\n");
|
||||
socket.end();
|
||||
}
|
||||
|
||||
module.exports = aimedb
|
||||
module.exports = aimedb;
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
const crypto = require('crypto')
|
||||
const { pipeline } = require('stream')
|
||||
const crypto = require("crypto");
|
||||
const { pipeline } = require("stream");
|
||||
|
||||
const { Deframer } = require('./frame')
|
||||
const { Decoder, Encoder } = require('./cmd')
|
||||
const { Deframer } = require("./frame");
|
||||
const { Decoder, Encoder } = require("./cmd");
|
||||
|
||||
const K = Buffer.from('Copyright(C)SEGA', 'utf8')
|
||||
const K = Buffer.from("Copyright(C)SEGA", "utf8");
|
||||
|
||||
function setup(socket) {
|
||||
const input = pipeline(
|
||||
socket,
|
||||
crypto
|
||||
.createDecipheriv('aes-128-ecb', K, null)
|
||||
.setAutoPadding(false),
|
||||
crypto.createDecipheriv("aes-128-ecb", K, null).setAutoPadding(false),
|
||||
new Deframer(),
|
||||
new Decoder(),
|
||||
)
|
||||
new Decoder()
|
||||
);
|
||||
|
||||
const output = new Encoder()
|
||||
const output = new Encoder();
|
||||
|
||||
pipeline(
|
||||
output,
|
||||
crypto
|
||||
.createCipheriv('aes-128-ecb', K, null)
|
||||
.setAutoPadding(false),
|
||||
socket,
|
||||
)
|
||||
crypto.createCipheriv("aes-128-ecb", K, null).setAutoPadding(false),
|
||||
socket
|
||||
);
|
||||
|
||||
return { input, output }
|
||||
return { input, output };
|
||||
}
|
||||
|
||||
module.exports = setup
|
||||
module.exports = setup;
|
||||
|
||||
116
src/billing.js
116
src/billing.js
@@ -1,77 +1,79 @@
|
||||
const bodyParser = require('body-parser')
|
||||
const express = require('express')
|
||||
const crypto = require('crypto')
|
||||
const zlib = require('zlib')
|
||||
const fs = require('fs')
|
||||
const bodyParser = require("body-parser");
|
||||
const express = require("express");
|
||||
const crypto = require("crypto");
|
||||
const zlib = require("zlib");
|
||||
const fs = require("fs");
|
||||
|
||||
const billingKeyPair = fs.readFileSync('pki/billing.key')
|
||||
const billingKeyPair = fs.readFileSync("pki/billing.key");
|
||||
|
||||
const playlimit = 1024
|
||||
const nearfull = 0x00010200
|
||||
const playlimit = 1024;
|
||||
const nearfull = 0x00010200;
|
||||
|
||||
// nearfull: high 16 bits is billing mode, low 16 bits is actual nearfull val.
|
||||
|
||||
const app = express()
|
||||
const app = express();
|
||||
|
||||
function decodeRequest(buf) {
|
||||
const reqBytes = zlib.inflateRawSync(buf)
|
||||
const reqStr = reqBytes.toString()
|
||||
const reqBytes = zlib.inflateRawSync(buf);
|
||||
const reqStr = reqBytes.toString();
|
||||
|
||||
return reqStr.trim().split('\r\n').map(line => {
|
||||
// Crack key-value pairs
|
||||
return reqStr
|
||||
.trim()
|
||||
.split("\r\n")
|
||||
.map(line => {
|
||||
// Crack key-value pairs
|
||||
|
||||
const params = {}
|
||||
const params = {};
|
||||
|
||||
for (const kvp of line.split('&')) {
|
||||
const [ key, val ] = kvp.split('=')
|
||||
params[key] = val
|
||||
}
|
||||
for (const kvp of line.split("&")) {
|
||||
const [key, val] = kvp.split("=");
|
||||
params[key] = val;
|
||||
}
|
||||
|
||||
return params
|
||||
})
|
||||
return params;
|
||||
});
|
||||
}
|
||||
|
||||
function encodeResponse(items) {
|
||||
return items.map(
|
||||
item => Object.entries(
|
||||
item
|
||||
).map( ([ key, val ]) =>
|
||||
key + '=' + val // Keys,vals are not url escaped
|
||||
).join('&')
|
||||
).join(
|
||||
'\r\n'
|
||||
) + '\r\n'
|
||||
return (
|
||||
items
|
||||
.map(item =>
|
||||
Object.entries(item)
|
||||
.map(
|
||||
([key, val]) => key + "=" + val // Keys,vals are not url escaped
|
||||
)
|
||||
.join("&")
|
||||
)
|
||||
.join("\r\n") + "\r\n"
|
||||
);
|
||||
}
|
||||
|
||||
app.use(bodyParser.raw())
|
||||
app.use(bodyParser.raw());
|
||||
|
||||
app.post('/request/', function (req, resp) {
|
||||
const reqItems = decodeRequest(req.body)
|
||||
app.post("/request/", function(req, resp) {
|
||||
const reqItems = decodeRequest(req.body);
|
||||
|
||||
console.log('\n--- Billing Request ---\n\n', reqItems)
|
||||
console.log("\n--- Billing Request ---\n\n", reqItems);
|
||||
|
||||
const first = reqItems[0]
|
||||
const first = reqItems[0];
|
||||
|
||||
// Do cryptographic signatures
|
||||
|
||||
const [
|
||||
playlimitsig, nearfullsig
|
||||
] = [
|
||||
playlimit, nearfull
|
||||
].map(value => {
|
||||
const buf = Buffer.alloc(15)
|
||||
const [playlimitsig, nearfullsig] = [playlimit, nearfull].map(value => {
|
||||
const buf = Buffer.alloc(15);
|
||||
|
||||
buf.writeInt32LE(value, 0)
|
||||
buf.write(first.keychipid, 4)
|
||||
buf.writeInt32LE(value, 0);
|
||||
buf.write(first.keychipid, 4);
|
||||
|
||||
return crypto.createSign('RSA-SHA1')
|
||||
.update(buf)
|
||||
.sign(billingKeyPair, 'hex')
|
||||
})
|
||||
return crypto
|
||||
.createSign("RSA-SHA1")
|
||||
.update(buf)
|
||||
.sign(billingKeyPair, "hex");
|
||||
});
|
||||
|
||||
// Assemble other params
|
||||
|
||||
const respItems = []
|
||||
const respItems = [];
|
||||
|
||||
respItems.push({
|
||||
// 0 or 6 is success, anything else is an error
|
||||
@@ -85,7 +87,7 @@ app.post('/request/', function (req, resp) {
|
||||
linelimit: 1,
|
||||
|
||||
// Server error message, copied to ALLNet debug log
|
||||
message: '',
|
||||
message: "",
|
||||
|
||||
// Keychip odometer limit. Game will lock out if the odometer reaches
|
||||
// this value
|
||||
@@ -95,7 +97,7 @@ app.post('/request/', function (req, resp) {
|
||||
playlimitsig,
|
||||
|
||||
// idk
|
||||
protocolver: '1.000',
|
||||
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
|
||||
@@ -122,15 +124,15 @@ app.post('/request/', function (req, resp) {
|
||||
// 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',
|
||||
})
|
||||
playhistory: "000000/0:000000/0:000000/0",
|
||||
});
|
||||
|
||||
console.log('\n--- Billing Response ---\n\n', respItems)
|
||||
console.log("\n--- Billing Response ---\n\n", respItems);
|
||||
|
||||
const respStr = encodeResponse(respItems)
|
||||
const respStr = encodeResponse(respItems);
|
||||
|
||||
resp.set('content-type', 'text/plain')
|
||||
resp.send(respStr)
|
||||
})
|
||||
resp.set("content-type", "text/plain");
|
||||
resp.send(respStr);
|
||||
});
|
||||
|
||||
module.exports = app
|
||||
module.exports = app;
|
||||
|
||||
212
src/chunithm.js
212
src/chunithm.js
@@ -1,107 +1,107 @@
|
||||
const express = require('express')
|
||||
const zlib = require('zlib')
|
||||
const express = require("express");
|
||||
const zlib = require("zlib");
|
||||
|
||||
const app = express()
|
||||
const app = express();
|
||||
|
||||
// Standard inbound JSON deserialization
|
||||
|
||||
app.use(express.json())
|
||||
app.use(express.json());
|
||||
|
||||
// Custom outbound JSON serialization that forces compression. Client tries to
|
||||
// inflate the response whether you have a Transfer-Encoding header or not -.-
|
||||
|
||||
app.use(function (req, resp, next) {
|
||||
resp.json = function (obj) {
|
||||
const str = JSON.stringify(obj)
|
||||
const buf = Buffer.from(str)
|
||||
const comp = zlib.deflateSync(buf)
|
||||
app.use(function(req, resp, next) {
|
||||
resp.json = function(obj) {
|
||||
const str = JSON.stringify(obj);
|
||||
const buf = Buffer.from(str);
|
||||
const comp = zlib.deflateSync(buf);
|
||||
|
||||
resp.set('Content-Type', 'application/json')
|
||||
resp.set('Content-Length', comp.length)
|
||||
resp.set('Transfer-Encoding', 'deflate')
|
||||
resp.send(comp)
|
||||
}
|
||||
resp.set("Content-Type", "application/json");
|
||||
resp.set("Content-Length", comp.length);
|
||||
resp.set("Transfer-Encoding", "deflate");
|
||||
resp.send(comp);
|
||||
};
|
||||
|
||||
next()
|
||||
})
|
||||
next();
|
||||
});
|
||||
|
||||
// Trace requests and responses
|
||||
|
||||
app.use(function (req, resp, next) {
|
||||
console.log('\n--- Chunithm %s ---\n', req.url)
|
||||
console.log('Request:', req.body)
|
||||
app.use(function(req, resp, next) {
|
||||
console.log("\n--- Chunithm %s ---\n", req.url);
|
||||
console.log("Request:", req.body);
|
||||
|
||||
const prevJson = resp.json
|
||||
const prevJson = resp.json;
|
||||
|
||||
resp.json = function (obj) {
|
||||
console.log('Response:', obj)
|
||||
resp.json = function(obj) {
|
||||
console.log("Response:", obj);
|
||||
|
||||
resp.json = prevJson
|
||||
resp.json.apply(this, arguments)
|
||||
}
|
||||
resp.json = prevJson;
|
||||
resp.json.apply(this, arguments);
|
||||
};
|
||||
|
||||
next()
|
||||
})
|
||||
next();
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/GetGameSettingApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/GetGameSettingApi", function(req, resp) {
|
||||
resp.json({
|
||||
gameSetting: {
|
||||
dataVersion: 1,
|
||||
isMaintenance: false,
|
||||
requestInterval: 10,
|
||||
rebootStartTime: 0,
|
||||
rebootEndTime: 0,
|
||||
isBackgroundDistribute: false,
|
||||
maxCountCharacter: 999,
|
||||
maxCountItem: 999,
|
||||
maxCountMusic: 999,
|
||||
},
|
||||
isDumpUpload: false,
|
||||
isAou: false
|
||||
})
|
||||
})
|
||||
gameSetting: {
|
||||
dataVersion: 1,
|
||||
isMaintenance: false,
|
||||
requestInterval: 10,
|
||||
rebootStartTime: 0,
|
||||
rebootEndTime: 0,
|
||||
isBackgroundDistribute: false,
|
||||
maxCountCharacter: 999,
|
||||
maxCountItem: 999,
|
||||
maxCountMusic: 999,
|
||||
},
|
||||
isDumpUpload: false,
|
||||
isAou: false,
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/UpsertClientSettingApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/UpsertClientSettingApi", function(req, resp) {
|
||||
resp.json({
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientSettingApi"
|
||||
})
|
||||
})
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientSettingApi",
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/UpsertClientBookkeepingApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/UpsertClientBookkeepingApi", function(req, resp) {
|
||||
resp.json({
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientBookkeepingApi"
|
||||
})
|
||||
})
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientBookkeepingApi",
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/UpsertClientTestmodeApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/UpsertClientTestmodeApi", function(req, resp) {
|
||||
resp.json({
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientTestmodeApi"
|
||||
})
|
||||
})
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientTestmodeApi",
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/UpsertClientErrorApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/UpsertClientErrorApi", function(req, resp) {
|
||||
resp.json({
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientErrorApi"
|
||||
})
|
||||
})
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientErrorApi",
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/UpsertClientDevelopApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/UpsertClientDevelopApi", function(req, resp) {
|
||||
resp.json({
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientDevelopApi"
|
||||
})
|
||||
})
|
||||
returnCode: 1,
|
||||
apiName: "UpsertClientDevelopApi",
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/GetGameMessageApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/GetGameMessageApi", function(req, resp) {
|
||||
resp.json({
|
||||
type: 1,
|
||||
length: 0,
|
||||
gameMessageList: [
|
||||
/* {
|
||||
type: 1,
|
||||
length: 0,
|
||||
gameMessageList: [
|
||||
/* {
|
||||
type: 2,
|
||||
id: 1,
|
||||
message: "true",
|
||||
@@ -109,56 +109,61 @@ app.post('/ChuniServlet/GetGameMessageApi', function (req, resp) {
|
||||
endDate: "0"
|
||||
},*/
|
||||
],
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/GetGameIdlistApi', function (req, resp) {
|
||||
const { type } = req.body
|
||||
app.post("/ChuniServlet/GetGameIdlistApi", function(req, resp) {
|
||||
const { type } = req.body;
|
||||
|
||||
resp.json({
|
||||
type,
|
||||
length: 0,
|
||||
gameIdlistList: [],
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/GetGameEventApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/GetGameEventApi", function(req, resp) {
|
||||
resp.json({
|
||||
type: 1,
|
||||
length: 0,
|
||||
gameEventList: [/*
|
||||
gameEventList: [
|
||||
/*
|
||||
{
|
||||
type: 1,
|
||||
id: 1102, // data/A000/event/event00001102
|
||||
startDate: 'STRINGIDK',
|
||||
endDate: 'STRINGIDK',
|
||||
},
|
||||
*/],
|
||||
})
|
||||
})
|
||||
*/
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/GetGameRankingApi', function (req, resp) {
|
||||
const { type } = req.body
|
||||
app.post("/ChuniServlet/GetGameRankingApi", function(req, resp) {
|
||||
const { type } = req.body;
|
||||
|
||||
resp.json({
|
||||
type,
|
||||
gameRankingList: [/*
|
||||
gameRankingList: [
|
||||
/*
|
||||
// QWORD fields maybe?
|
||||
{
|
||||
id: 1,
|
||||
point: 1,
|
||||
}
|
||||
*/],
|
||||
})
|
||||
})
|
||||
*/
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/GetGameSaleApi', function (req, resp) {
|
||||
const { type } = req.body
|
||||
app.post("/ChuniServlet/GetGameSaleApi", function(req, resp) {
|
||||
const { type } = req.body;
|
||||
|
||||
resp.json({
|
||||
type,
|
||||
length: 0,
|
||||
gameSaleList: [/*
|
||||
gameSaleList: [
|
||||
/*
|
||||
{
|
||||
orderId: 1234,
|
||||
type,
|
||||
@@ -167,14 +172,16 @@ app.post('/ChuniServlet/GetGameSaleApi', function (req, resp) {
|
||||
startDate: 'STRINGIDK',
|
||||
endDate: 'STRINGIDK',
|
||||
},
|
||||
*/],
|
||||
})
|
||||
})
|
||||
*/
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/ChuniServlet/GetGameChargeApi', function (req, resp) {
|
||||
app.post("/ChuniServlet/GetGameChargeApi", function(req, resp) {
|
||||
resp.json({
|
||||
length: 0,
|
||||
gameChargeList: [/*
|
||||
gameChargeList: [
|
||||
/*
|
||||
{
|
||||
orderId: 1,
|
||||
chargeId: 1,
|
||||
@@ -185,8 +192,9 @@ app.post('/ChuniServlet/GetGameChargeApi', function (req, resp) {
|
||||
saleStartDate: 'STRINGIDK',
|
||||
saleEndDate: 'STRINGIDK',
|
||||
},
|
||||
*/],
|
||||
})
|
||||
})
|
||||
*/
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = app
|
||||
module.exports = app;
|
||||
|
||||
30
src/index.js
30
src/index.js
@@ -1,19 +1,19 @@
|
||||
const fs = require('fs')
|
||||
const https = require('https')
|
||||
const http = require('http')
|
||||
const net = require('net')
|
||||
const fs = require("fs");
|
||||
const https = require("https");
|
||||
const http = require("http");
|
||||
const net = require("net");
|
||||
|
||||
const aimedb = require('./aimedb')
|
||||
const billing = require('./billing')
|
||||
const startup = require('./startup')
|
||||
const chunithm = require('./chunithm')
|
||||
const aimedb = require("./aimedb");
|
||||
const billing = require("./billing");
|
||||
const startup = require("./startup");
|
||||
const chunithm = require("./chunithm");
|
||||
|
||||
const tls = {
|
||||
cert: fs.readFileSync('pki/server.pem'),
|
||||
key: fs.readFileSync('pki/server.key'),
|
||||
}
|
||||
cert: fs.readFileSync("pki/server.pem"),
|
||||
key: fs.readFileSync("pki/server.key"),
|
||||
};
|
||||
|
||||
net.createServer(aimedb).listen(22345)
|
||||
http.createServer(startup).listen(80)
|
||||
https.createServer(tls, billing).listen(8443)
|
||||
http.createServer(chunithm).listen(9000)
|
||||
net.createServer(aimedb).listen(22345);
|
||||
http.createServer(startup).listen(80);
|
||||
https.createServer(tls, billing).listen(8443);
|
||||
http.createServer(chunithm).listen(9000);
|
||||
|
||||
100
src/startup.js
100
src/startup.js
@@ -1,92 +1,92 @@
|
||||
const bodyParser = require('body-parser')
|
||||
const leftPad = require('left-pad') // the infamous...
|
||||
const express = require('express')
|
||||
const zlib = require('zlib')
|
||||
const os = require('os')
|
||||
const bodyParser = require("body-parser");
|
||||
const leftPad = require("left-pad"); // the infamous...
|
||||
const express = require("express");
|
||||
const zlib = require("zlib");
|
||||
const os = require("os");
|
||||
|
||||
const services = new Map();
|
||||
|
||||
services.set("SDBT", 9000); // Chunithm
|
||||
|
||||
function decodeRequest(req) {
|
||||
const buf = Buffer.from(req, 'base64')
|
||||
const bytes = zlib.unzipSync(buf)
|
||||
const str = bytes.toString().trim()
|
||||
const buf = Buffer.from(req, "base64");
|
||||
const bytes = zlib.unzipSync(buf);
|
||||
const str = bytes.toString().trim();
|
||||
|
||||
const kvps = str.split('&')
|
||||
const params = {}
|
||||
const kvps = str.split("&");
|
||||
const params = {};
|
||||
|
||||
kvps.forEach(kvp => {
|
||||
const [ key, val ] = kvp.split('=')
|
||||
const [key, val] = kvp.split("=");
|
||||
|
||||
params[key] = val
|
||||
})
|
||||
params[key] = val;
|
||||
});
|
||||
|
||||
return params
|
||||
return params;
|
||||
}
|
||||
|
||||
function encodeResponse(params) {
|
||||
const str = Object.entries(
|
||||
params
|
||||
).map( ([ key, val ]) =>
|
||||
key + '=' + val
|
||||
).join('&') // Keys and values are not URL-escaped
|
||||
const str = Object.entries(params)
|
||||
.map(([key, val]) => key + "=" + val)
|
||||
.join("&"); // Keys and values are not URL-escaped
|
||||
|
||||
return str + '\r\n'
|
||||
return str + "\r\n";
|
||||
}
|
||||
|
||||
const app = express()
|
||||
const app = express();
|
||||
|
||||
// Startup request is url-encoded-ish... except it's also zlibed and base64ed.
|
||||
// So in the absence of any exotic Transfer-Encoding headers this Content-Type
|
||||
// is incorrect and we have to override Express' built-in handling.
|
||||
|
||||
app.use(bodyParser.raw({
|
||||
type: 'application/x-www-form-urlencoded',
|
||||
}))
|
||||
app.use(
|
||||
bodyParser.raw({
|
||||
type: "application/x-www-form-urlencoded",
|
||||
})
|
||||
);
|
||||
|
||||
app.post('/sys/servlet/PowerOn', function (req, resp) {
|
||||
const reqParams = decodeRequest(req.body.toString())
|
||||
app.post("/sys/servlet/PowerOn", function(req, resp) {
|
||||
const reqParams = decodeRequest(req.body.toString());
|
||||
|
||||
console.log('\n--- Startup Request ---\n\n', reqParams)
|
||||
console.log("\n--- Startup Request ---\n\n", reqParams);
|
||||
|
||||
const now = new Date()
|
||||
const now = new Date();
|
||||
|
||||
// Cut milliseconds out of ISO timestamp
|
||||
|
||||
const isoStrWithMs = now.toISOString()
|
||||
const isoStr = isoStrWithMs.substr(0, 19) + 'Z'
|
||||
const isoStrWithMs = now.toISOString();
|
||||
const isoStr = isoStrWithMs.substr(0, 19) + "Z";
|
||||
|
||||
const respParams = {
|
||||
stat: 1,
|
||||
uri: `http://${os.hostname()}:${services.get(reqParams.game_id)}/`,
|
||||
host: '',
|
||||
place_id: '123',
|
||||
name: 'Name',
|
||||
nickname: 'Nick',
|
||||
region0: '1',
|
||||
region_name0: 'W',
|
||||
region_name1: 'X',
|
||||
region_name2: 'Y',
|
||||
region_name3: 'Z',
|
||||
country: 'JPN',
|
||||
allnet_id: '456',
|
||||
host: "",
|
||||
place_id: "123",
|
||||
name: "Name",
|
||||
nickname: "Nick",
|
||||
region0: "1",
|
||||
region_name0: "W",
|
||||
region_name1: "X",
|
||||
region_name2: "Y",
|
||||
region_name3: "Z",
|
||||
country: "JPN",
|
||||
allnet_id: "456",
|
||||
|
||||
// Always UTC to simplify the segatools fake time system
|
||||
// (which is used to skip the hardcoded 2AM - 7AM warn/unplayable period)
|
||||
client_timezone: '+0000',
|
||||
client_timezone: "+0000",
|
||||
|
||||
utc_time: isoStr,
|
||||
setting: '',
|
||||
res_ver: '3',
|
||||
setting: "",
|
||||
res_ver: "3",
|
||||
token: reqParams.token,
|
||||
}
|
||||
};
|
||||
|
||||
console.log('\n--- Startup Response ---\n\n', respParams)
|
||||
console.log("\n--- Startup Response ---\n\n", respParams);
|
||||
|
||||
const respStrZ = encodeResponse(respParams)
|
||||
const respStrZ = encodeResponse(respParams);
|
||||
|
||||
resp.send(respStrZ)
|
||||
})
|
||||
resp.send(respStrZ);
|
||||
});
|
||||
|
||||
module.exports = app
|
||||
module.exports = app;
|
||||
|
||||
Reference in New Issue
Block a user