Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf28710000 | ||
|
|
8c61bda865 | ||
|
|
1fa268e6ba | ||
|
|
4fe69077fc | ||
|
|
4f5eb97309 | ||
|
|
122c397b59 | ||
|
|
d3984dcd5d | ||
|
|
4abc04e9f6 | ||
|
|
c01dbb50f7 | ||
|
|
fa5ed284e1 | ||
|
|
76583f17ce | ||
|
|
f959993197 | ||
|
|
6dceb7c48a |
+33
-28
@@ -1,53 +1,58 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v013
|
||||||
|
|
||||||
|
- IDZ: Miscellaneous refactoring (Tau)
|
||||||
|
- Chunithm: Add stubs to support Amazon Plus (Felix)
|
||||||
|
|
||||||
## v012
|
## v012
|
||||||
|
|
||||||
* Chunithm: Fix large data loads (NeumPhis)
|
- Chunithm: Fix large data loads (NeumPhis)
|
||||||
* Chunithm: Add some support for older versions (NeumPhis)
|
- Chunithm: Add some support for older versions (NeumPhis)
|
||||||
* Chunithm: Fix large data loads (Matt Bilker)
|
- Chunithm: Fix large data loads (Felix)
|
||||||
* Chunithm: Enable all events (Matt Bilker)
|
- Chunithm: Enable all events (Felix)
|
||||||
* Chunithm: Remove server maintenance period (Matt Bilker)
|
- Chunithm: Remove server maintenance period (Felix)
|
||||||
* Chunithm: Fix data loss (esterTion)
|
- Chunithm: Fix data loss (esterTion)
|
||||||
* Chunithm: Add some support for older versions (esterTion)
|
- Chunithm: Add some support for older versions (esterTion)
|
||||||
* IDZ: Team-related fixes (Emi Midnight)
|
- IDZ: Team-related fixes (Emi Midnight)
|
||||||
* Add support for newer AIME card services (cxm)
|
- Add support for newer AIME card services (cxm)
|
||||||
* Chunithm: Enable ticket system (Rob)
|
- Chunithm: Enable ticket system (Rob)
|
||||||
* Chunithm: Implement Recent Rating feature (seika1)
|
- Chunithm: Implement Recent Rating feature (seika1)
|
||||||
* Chunithm: Fix User Activity feature (seika1)
|
- Chunithm: Fix User Activity feature (seika1)
|
||||||
|
|
||||||
## v011
|
## v011
|
||||||
|
|
||||||
* IDZ: Fix loading of >10 cars in garage (Tau)
|
- IDZ: Fix loading of >10 cars in garage (Tau)
|
||||||
|
|
||||||
## v010
|
## v010
|
||||||
|
|
||||||
* Chunithm: Bug fix: Fix new database creation and repair affected databases. Again. (Tau)
|
- Chunithm: Bug fix: Fix new database creation and repair affected databases. Again. (Tau)
|
||||||
Switch to a different SQLite native code extension. C compiler is no longer required. (Tau)
|
Switch to a different SQLite native code extension. C compiler is no longer required. (Tau)
|
||||||
|
|
||||||
## v009
|
## v009
|
||||||
|
|
||||||
* IDZ: Display team banners in attract loop rankings (Tau)
|
- IDZ: Display team banners in attract loop rankings (Tau)
|
||||||
* Chunithm: Bug fix: Fix new database creation and repair affected databases (Tau)
|
- Chunithm: Bug fix: Fix new database creation and repair affected databases (Tau)
|
||||||
|
|
||||||
## v008
|
## v008
|
||||||
|
|
||||||
* Implement auras in IDZ (BemaniWitch)
|
- Implement auras in IDZ (BemaniWitch)
|
||||||
* Fix user ratings (NeumPhis)
|
- Fix user ratings (NeumPhis)
|
||||||
* Load and save course results (NeumPhis)
|
- Load and save course results (NeumPhis)
|
||||||
* Load and save user duel list (Felix)
|
- Load and save user duel list (Felix)
|
||||||
|
|
||||||
## v007
|
## v007
|
||||||
|
|
||||||
* Fix IDZ team creation (Tau)
|
- Fix IDZ team creation (Tau)
|
||||||
* Fix startup script working directory (Tau)
|
- Fix startup script working directory (Tau)
|
||||||
|
|
||||||
## v006
|
## v006
|
||||||
|
|
||||||
* Fix Chunithm HTTP POST size limit (Tau)
|
- Fix Chunithm HTTP POST size limit (Tau)
|
||||||
|
|
||||||
## v005
|
## v005
|
||||||
|
|
||||||
* Initial public release (Tau)
|
- Initial public release (Tau)
|
||||||
* Chunithm v1.30.00 Amazon
|
- Chunithm v1.30.00 Amazon
|
||||||
* Initial D Zero v1.21.00
|
- Initial D Zero v1.21.00
|
||||||
* Initial D Zero v1.31.00
|
- Initial D Zero v1.31.00
|
||||||
|
|||||||
Generated
+4007
-1836
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -27,12 +27,12 @@
|
|||||||
"@types/jest": "^24.0.11",
|
"@types/jest": "^24.0.11",
|
||||||
"@types/multiparty": "^0.0.32",
|
"@types/multiparty": "^0.0.32",
|
||||||
"@types/node": "^12.11.7",
|
"@types/node": "^12.11.7",
|
||||||
"jest": "^24.5.0",
|
"jest": "^26.4.2",
|
||||||
"jest-haste-map": "^24.5.0",
|
"jest-haste-map": "^26.3.0",
|
||||||
"jest-resolve": "^24.5.0",
|
"jest-resolve": "^24.5.0",
|
||||||
"prettier": "^1.16.4",
|
"prettier": "^1.16.4",
|
||||||
"ts-jest": "^24.0.0",
|
"ts-jest": "^26.3.0",
|
||||||
"typescript": "^3.6.4",
|
"typescript": "^3.8.3",
|
||||||
"utility-types": "^3.6.1"
|
"utility-types": "^3.6.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Repositories } from "../repo";
|
||||||
|
import { GetUserFavoriteMusicRequest } from "../request/getUserFavoriteMusic";
|
||||||
|
import { GetUserFavoriteMusicResponse } from "../response/getUserFavoriteMusic";
|
||||||
|
import { readAimeId } from "../proto/base";
|
||||||
|
|
||||||
|
export default async function getUserFavoriteMusic(
|
||||||
|
rep: Repositories,
|
||||||
|
req: GetUserFavoriteMusicRequest
|
||||||
|
): Promise<GetUserFavoriteMusicResponse> {
|
||||||
|
const aimeId = readAimeId(req.userId);
|
||||||
|
|
||||||
|
const profileId = await rep.userData().lookup(aimeId);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* `Chunithm Amazon Plus` does not appear to save a favorites list and there
|
||||||
|
* is no user-accessible favorites function from what I can tell.
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
userId: req.userId,
|
||||||
|
length: "0",
|
||||||
|
userFavoriteMusicList: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import getUserCourse from "./getUserCourse";
|
|||||||
import getUserData from "./getUserData";
|
import getUserData from "./getUserData";
|
||||||
import getUserDataEx from "./getUserDataEx";
|
import getUserDataEx from "./getUserDataEx";
|
||||||
import getUserDuel from "./getUserDuel";
|
import getUserDuel from "./getUserDuel";
|
||||||
|
import getUserFavoriteMusic from "./getUserFavoriteMusic";
|
||||||
import getUserItem from "./getUserItem";
|
import getUserItem from "./getUserItem";
|
||||||
import getUserMap from "./getUserMap";
|
import getUserMap from "./getUserMap";
|
||||||
import getUserMusic from "./getUserMusic";
|
import getUserMusic from "./getUserMusic";
|
||||||
@@ -99,6 +100,7 @@ export default function chunithm(db: DataSource) {
|
|||||||
wrapper.rpc("/GetUserDataApi", getUserData);
|
wrapper.rpc("/GetUserDataApi", getUserData);
|
||||||
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
|
wrapper.rpc("/GetUserDataExApi", getUserDataEx);
|
||||||
wrapper.rpc("/GetUserDuelApi", getUserDuel);
|
wrapper.rpc("/GetUserDuelApi", getUserDuel);
|
||||||
|
wrapper.rpc("/GetUserFavoriteMusicApi", getUserFavoriteMusic);
|
||||||
wrapper.rpc("/GetUserItemApi", getUserItem);
|
wrapper.rpc("/GetUserItemApi", getUserItem);
|
||||||
wrapper.rpc("/GetUserMapApi", getUserMap);
|
wrapper.rpc("/GetUserMapApi", getUserMap);
|
||||||
wrapper.rpc("/GetUserMusicApi", getUserMusic);
|
wrapper.rpc("/GetUserMusicApi", getUserMusic);
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface GetUserFavoriteMusicRequest {
|
||||||
|
/** Integer, AiMe ID */
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface GetUserFavoriteMusicResponse {
|
||||||
|
/** Integer, AiMe ID */
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
/** Integer, number of results returned */
|
||||||
|
length: string;
|
||||||
|
|
||||||
|
/** TBD */
|
||||||
|
userFavoriteMusicList: [];
|
||||||
|
}
|
||||||
@@ -29,7 +29,8 @@ export class SqlUserActivityRepository implements UserActivityRepository {
|
|||||||
.from("cm_user_activity")
|
.from("cm_user_activity")
|
||||||
.where("profile_id", profileId)
|
.where("profile_id", profileId)
|
||||||
.where("kind", kind)
|
.where("kind", kind)
|
||||||
.orderBy("sort_number DESC");
|
.orderBy("sort_number DESC")
|
||||||
|
.limit(100);
|
||||||
|
|
||||||
const rows = await this._txn.fetchRows(stmt);
|
const rows = await this._txn.fetchRows(stmt);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Decipher, Cipher, createCipheriv, createDecipheriv } from "crypto";
|
||||||
|
|
||||||
|
import { ByteStream } from "../../util/stream";
|
||||||
|
|
||||||
|
export const BLOCK_SIZE = 16;
|
||||||
|
|
||||||
|
export default class AesEcbStream implements ByteStream {
|
||||||
|
private readonly _dec: Decipher;
|
||||||
|
private readonly _enc: Cipher;
|
||||||
|
|
||||||
|
constructor(private _stm: ByteStream, keybuf: Buffer) {
|
||||||
|
this._dec = createDecipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
this._enc = createCipheriv("aes-128-ecb", keybuf, null).setAutoPadding(
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this._stm.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
async read(nbytes: number): Promise<Buffer> {
|
||||||
|
if (nbytes % BLOCK_SIZE !== 0) {
|
||||||
|
throw new Error("Attempted to read partial cipher block");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = await this._stm.read(nbytes);
|
||||||
|
|
||||||
|
return this._dec.update(ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
async write(buf: Buffer): Promise<void> {
|
||||||
|
if (buf.length % BLOCK_SIZE !== 0) {
|
||||||
|
throw new Error("Attempted to write partial cipher block");
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphertext = this._enc.update(buf);
|
||||||
|
|
||||||
|
await this._stm.write(ciphertext);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,17 @@
|
|||||||
export function byteString(n: bigint, length: number) {
|
export function readBigInt(buf: Buffer) {
|
||||||
|
let result = 0n;
|
||||||
|
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
|
const shift = 8n * BigInt(i);
|
||||||
|
const byte = buf.readUInt8(i);
|
||||||
|
|
||||||
|
result |= BigInt(byte) << shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function writeBigInt(n: bigint, length: number) {
|
||||||
const result = Buffer.alloc(length);
|
const result = Buffer.alloc(length);
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import setup from "./setup";
|
||||||
|
|
||||||
|
export { BLOCK_SIZE } from "./aes";
|
||||||
|
export default setup;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { Socket } from "net";
|
||||||
|
|
||||||
|
import AesEcbStream from "./aes";
|
||||||
|
import { readBigInt, modPow, writeBigInt } from "./bigint";
|
||||||
|
import IoByteStream from "../../util/stream";
|
||||||
|
|
||||||
|
interface RsaKey {
|
||||||
|
N: bigint;
|
||||||
|
d: bigint;
|
||||||
|
e: bigint;
|
||||||
|
hashN: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proof-of-concept, so we only ever use one of the ten RSA key pairs
|
||||||
|
const rsaKey = {
|
||||||
|
N: 4922323266120814292574970172377860734034664704992758249880018618131907367614177800329506877981986877921220485681998287752778495334541127048495486311792061n,
|
||||||
|
d: 1163847742215766215216916151663017691387519688859977157498780867776436010396072628219119707788340687440419444081289736279466637153082223960965411473296473n,
|
||||||
|
e: 3961365081960959178294197133768419551060435043430437330799371731939550352626564261219865471710058480523874787120718634318364066605378505537556570049131337n,
|
||||||
|
hashN: 2662304617,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Proof-of-concept, so we only use one fixed session key
|
||||||
|
const aesKey = Buffer.from("ffddeeccbbaa99887766554433221100", "hex");
|
||||||
|
|
||||||
|
function writeServerHello(aesKey: Buffer, rsaKey: RsaKey) {
|
||||||
|
const M = readBigInt(aesKey);
|
||||||
|
const keyEnc = modPow(M, rsaKey.e, rsaKey.N);
|
||||||
|
const result = Buffer.alloc(0x48);
|
||||||
|
|
||||||
|
result.set(writeBigInt(keyEnc, 0x40), 0x00);
|
||||||
|
result.writeUInt32LE(0x01020304, 0x40); // Meaning is unknown
|
||||||
|
result.writeUInt32LE(rsaKey.hashN, 0x44);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readClientHello(buf: Buffer) {
|
||||||
|
const magic = buf.readUInt32LE(0x00);
|
||||||
|
|
||||||
|
if (magic !== 0x01020304) {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid magic number, cryptographic processing probably incorrect."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pcbId: buf.slice(0x04, 0x0f).toString("ascii"),
|
||||||
|
protocol: buf.slice(0x10, 0x13).toString("ascii"),
|
||||||
|
model: buf.slice(0x18, 0x0c).toString("ascii"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function setup(socket: Socket) {
|
||||||
|
const tcpStream = new IoByteStream(socket);
|
||||||
|
const serverHello = writeServerHello(aesKey, rsaKey);
|
||||||
|
|
||||||
|
await tcpStream.write(serverHello);
|
||||||
|
|
||||||
|
const aesStream = new AesEcbStream(tcpStream, aesKey);
|
||||||
|
|
||||||
|
const response = await aesStream.read(0x30);
|
||||||
|
|
||||||
|
if (response.length !== 0x30) {
|
||||||
|
throw new Error("Truncated client hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientHello = readClientHello(response);
|
||||||
|
|
||||||
|
return { aesStream, clientHello };
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import logger from "debug";
|
|
||||||
import { Socket } from "net";
|
|
||||||
|
|
||||||
import { dispatch } from "./handler";
|
|
||||||
import { setup } from "./setup";
|
|
||||||
import { DataSource } from "../sql";
|
|
||||||
import { SqlRepositories } from "./sql";
|
|
||||||
|
|
||||||
const debug = logger("app:idz:session");
|
|
||||||
|
|
||||||
export default function idz(db: DataSource) {
|
|
||||||
return async function(socket: Socket) {
|
|
||||||
const { input, output } = setup(socket);
|
|
||||||
|
|
||||||
debug("Connection opened");
|
|
||||||
|
|
||||||
try {
|
|
||||||
for await (const req of input) {
|
|
||||||
const res = await db.transaction(txn =>
|
|
||||||
dispatch(new SqlRepositories(txn), req)
|
|
||||||
);
|
|
||||||
|
|
||||||
output.write(res);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (debug.enabled) {
|
|
||||||
debug("Error: %s", e.stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug("Connection closed");
|
|
||||||
|
|
||||||
input.end();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import { createCipheriv, createDecipheriv } from "crypto";
|
|
||||||
import { Socket } from "net";
|
|
||||||
import { pipeline as pipelineWithCallback } from "stream";
|
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
import { byteString, modPow } from "./bigint";
|
|
||||||
import { Decoder } from "./decoder";
|
|
||||||
import { Encoder } from "./encoder";
|
|
||||||
import { Request } from "./request";
|
|
||||||
import { Response } from "./response";
|
|
||||||
|
|
||||||
// Drops the stupid mandatory callback parameter crap
|
|
||||||
|
|
||||||
const pipeline = promisify(pipelineWithCallback);
|
|
||||||
|
|
||||||
// Proof-of-concept, so we only ever use one of the ten RSA key pairs
|
|
||||||
// (SEGA shipped their central server private keys for god knows what reason)
|
|
||||||
|
|
||||||
const key = {
|
|
||||||
N: 4922323266120814292574970172377860734034664704992758249880018618131907367614177800329506877981986877921220485681998287752778495334541127048495486311792061n,
|
|
||||||
d: 1163847742215766215216916151663017691387519688859977157498780867776436010396072628219119707788340687440419444081289736279466637153082223960965411473296473n,
|
|
||||||
e: 3961365081960959178294197133768419551060435043430437330799371731939550352626564261219865471710058480523874787120718634318364066605378505537556570049131337n,
|
|
||||||
hashN: 2662304617,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Proof-of-concept, so we only use one fixed session key
|
|
||||||
const sessionKey = 0xffddeeccbbaa99887766554433221100n;
|
|
||||||
|
|
||||||
interface Session {
|
|
||||||
input: AsyncIterable<Request> & {
|
|
||||||
end: () => void;
|
|
||||||
};
|
|
||||||
output: {
|
|
||||||
write: (res: Response) => void;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function doNothing() {}
|
|
||||||
|
|
||||||
export function setup(socket: Socket): Session {
|
|
||||||
//
|
|
||||||
// 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 input = new Decoder();
|
|
||||||
const output = new Encoder();
|
|
||||||
const keybuf = byteString(sessionKey, 0x10);
|
|
||||||
|
|
||||||
pipeline(
|
|
||||||
socket,
|
|
||||||
createDecipheriv("aes-128-ecb", keybuf, null).setAutoPadding(false),
|
|
||||||
input
|
|
||||||
).catch(doNothing);
|
|
||||||
|
|
||||||
pipeline(
|
|
||||||
output,
|
|
||||||
createCipheriv("aes-128-ecb", keybuf, null).setAutoPadding(false),
|
|
||||||
socket
|
|
||||||
).catch(doNothing);
|
|
||||||
|
|
||||||
return { input, output };
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CreateAutoTeamRequest } from "../request/createAutoTeam";
|
import { CreateAutoTeamRequest } from "../request/createAutoTeam";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
createAutoTeam.msgCode = 0x007b;
|
createAutoTeam.msgCode = 0x007b;
|
||||||
createAutoTeam.msgLen = 0x0010;
|
createAutoTeam.msgLen = 0x0010;
|
||||||
@@ -3,7 +3,7 @@ import iconv from "iconv-lite";
|
|||||||
import { car } from "./_car";
|
import { car } from "./_car";
|
||||||
import { chara } from "./_chara";
|
import { chara } from "./_chara";
|
||||||
import { CreateProfileRequest } from "../request/createProfile";
|
import { CreateProfileRequest } from "../request/createProfile";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
createProfile.msgCode = 0x0066;
|
createProfile.msgCode = 0x0066;
|
||||||
createProfile.msgLen = 0x00c0;
|
createProfile.msgLen = 0x00c0;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import iconv from "iconv-lite";
|
import iconv from "iconv-lite";
|
||||||
|
|
||||||
import { CreateTeamRequest } from "../request/createTeam";
|
import { CreateTeamRequest } from "../request/createTeam";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
createTeam.msgCode = 0x0071;
|
createTeam.msgCode = 0x0071;
|
||||||
createTeam.msgLen = 0x0050;
|
createTeam.msgLen = 0x0050;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { DiscoverProfileRequest } from "../request/discoverProfile";
|
import { DiscoverProfileRequest } from "../request/discoverProfile";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
discoverProfile.msgCode = 0x006b;
|
discoverProfile.msgCode = 0x006b;
|
||||||
discoverProfile.msgLen = 0x0010;
|
discoverProfile.msgLen = 0x0010;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import logger from "debug";
|
import logger from "debug";
|
||||||
import { Transform } from "stream";
|
|
||||||
|
|
||||||
import { checkTeamName } from "./checkTeamName";
|
import { checkTeamName } from "./checkTeamName";
|
||||||
import { createProfile } from "./createProfile";
|
import { createProfile } from "./createProfile";
|
||||||
@@ -49,8 +48,10 @@ import { updateTeamPoints } from "./updateTeamPoints";
|
|||||||
import { updateUiReport } from "./updateUiReport";
|
import { updateUiReport } from "./updateUiReport";
|
||||||
import { updateUserLog } from "./updateUserLog";
|
import { updateUserLog } from "./updateUserLog";
|
||||||
import { lockProfileExtend } from "./lockProfileExtend";
|
import { lockProfileExtend } from "./lockProfileExtend";
|
||||||
|
import { BLOCK_SIZE } from "../../common";
|
||||||
|
import { ByteStream } from "../../../util/stream";
|
||||||
|
|
||||||
const debug = logger("app:idz:decoder");
|
const debug = logger("app:idz:userdb:decoder");
|
||||||
|
|
||||||
export type ReaderFn = ((buf: Buffer) => Request) & {
|
export type ReaderFn = ((buf: Buffer) => Request) & {
|
||||||
msgCode: number;
|
msgCode: number;
|
||||||
@@ -119,88 +120,53 @@ for (const fn of funcList) {
|
|||||||
msgLengths.set(fn.msgCode, fn.msgLen);
|
msgLengths.set(fn.msgCode, fn.msgLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
function readHeader(buf: Buffer) {
|
async function readRequest(stm: ByteStream): Promise<Request | undefined> {
|
||||||
return {
|
const head = await stm.read(BLOCK_SIZE);
|
||||||
blah: "blah",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Decoder extends Transform {
|
if (head.length === 0) {
|
||||||
state: Buffer;
|
// Connection closed
|
||||||
|
return undefined;
|
||||||
constructor() {
|
|
||||||
super({
|
|
||||||
readableObjectMode: true,
|
|
||||||
writableObjectMode: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.state = Buffer.alloc(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_transform(chunk: Buffer, encoding, callback) {
|
const msgCode = head.readUInt16LE(0x0000);
|
||||||
this.state = Buffer.concat([this.state, chunk]);
|
const msgLen = msgLengths.get(msgCode);
|
||||||
|
|
||||||
// Read header
|
if (msgLen === undefined) {
|
||||||
|
throw new Error(`Message ${msgCode.toString(16)}: Unknown command code`);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.state.length < 0x04) {
|
const tail = await stm.read(msgLen - BLOCK_SIZE);
|
||||||
return callback(null);
|
const msg = Buffer.concat([head, tail]);
|
||||||
|
|
||||||
|
if (msg.length < msgLen) {
|
||||||
|
throw new Error(`Message ${msgCode.toString(16)}: Truncated read`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug.enabled) {
|
||||||
|
debug("Raw: %s", msg.toString("hex"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = readerFns.get(msgCode);
|
||||||
|
|
||||||
|
if (reader === undefined) {
|
||||||
|
throw new Error(`Message ${msgCode.toString(16)}: No read handler`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = reader(msg);
|
||||||
|
|
||||||
|
debug("Payload: %j", payload);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function* readRequestStream(stm: ByteStream) {
|
||||||
|
while (true) {
|
||||||
|
const req = await readRequest(stm);
|
||||||
|
|
||||||
|
if (req === undefined) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const magic = this.state.readUInt32LE(0);
|
yield req;
|
||||||
|
|
||||||
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 = msgLengths.get(msgCode);
|
|
||||||
|
|
||||||
if (msgLen === 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(0, 0x30 + msgLen);
|
|
||||||
const payloadBuf = reqBuf.slice(0x30);
|
|
||||||
|
|
||||||
if (debug.enabled) {
|
|
||||||
debug("Raw: %s", reqBuf.toString("hex"));
|
|
||||||
debug("Header: %j", header);
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = readerFns.get(msgCode);
|
|
||||||
|
|
||||||
if (reader === undefined) {
|
|
||||||
return callback(
|
|
||||||
new Error(`No reader for command code ${msgCode.toString(16)}`)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = reader(payloadBuf);
|
|
||||||
|
|
||||||
debug("Payload: %j", payload);
|
|
||||||
|
|
||||||
return callback(null, payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ExtId } from "../model/base";
|
import { ExtId } from "../model/base";
|
||||||
import { Team } from "../model/team";
|
import { Team } from "../model/team";
|
||||||
import { Load2on2Request1, Load2on2Request2 } from "../request/load2on2";
|
import { Load2on2Request1, Load2on2Request2 } from "../request/load2on2";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
load2on2_v1.msgCode = 0x00b0;
|
load2on2_v1.msgCode = 0x00b0;
|
||||||
load2on2_v1.msgLen = 0x0010;
|
load2on2_v1.msgLen = 0x0010;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LoadEventInfoRequest } from "../request/loadEventInfo";
|
import { LoadEventInfoRequest } from "../request/loadEventInfo";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadEventInfo.msgCode = 0x00be;
|
loadEventInfo.msgCode = 0x00be;
|
||||||
loadEventInfo.msgLen = 0x0010;
|
loadEventInfo.msgLen = 0x0010;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LoadGachaRequest } from "../request/loadGacha";
|
import { LoadGachaRequest } from "../request/loadGacha";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadGacha.msgCode = 0x00c1;
|
loadGacha.msgCode = 0x00c1;
|
||||||
loadGacha.msgLen = 0x0010;
|
loadGacha.msgLen = 0x0010;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LoadGarageRequest } from "../request/loadGarage";
|
import { LoadGarageRequest } from "../request/loadGarage";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadGarage.msgCode = 0x0090;
|
loadGarage.msgCode = 0x0090;
|
||||||
loadGarage.msgLen = 0x0010;
|
loadGarage.msgLen = 0x0010;
|
||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
LoadGeneralRewardRequest1,
|
LoadGeneralRewardRequest1,
|
||||||
LoadGeneralRewardRequest2,
|
LoadGeneralRewardRequest2,
|
||||||
} from "../request/loadGeneralReward";
|
} from "../request/loadGeneralReward";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadGeneralReward1.msgCode = 0x009c;
|
loadGeneralReward1.msgCode = 0x009c;
|
||||||
loadGeneralReward1.msgLen = 0x0010;
|
loadGeneralReward1.msgLen = 0x0010;
|
||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
LoadProfileRequest2,
|
LoadProfileRequest2,
|
||||||
LoadProfileRequest3,
|
LoadProfileRequest3,
|
||||||
} from "../request/loadProfile";
|
} from "../request/loadProfile";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadProfile2.msgCode = 0x0067;
|
loadProfile2.msgCode = 0x0067;
|
||||||
loadProfile2.msgLen = 0x0020;
|
loadProfile2.msgLen = 0x0020;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LoadStockerRequest } from "../request/loadStocker";
|
import { LoadStockerRequest } from "../request/loadStocker";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadStocker.msgCode = 0x00a7;
|
loadStocker.msgCode = 0x00a7;
|
||||||
loadStocker.msgLen = 0x0010;
|
loadStocker.msgLen = 0x0010;
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
LoadTopTenRequest,
|
LoadTopTenRequest,
|
||||||
LoadTopTenRequestSelector,
|
LoadTopTenRequestSelector,
|
||||||
} from "../request/loadTopTen";
|
} from "../request/loadTopTen";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadTopTen1.msgCode = 0x00b5;
|
loadTopTen1.msgCode = 0x00b5;
|
||||||
loadTopTen1.msgLen = 0x00e0;
|
loadTopTen1.msgLen = 0x00e0;
|
||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
LoadTopTenRequest,
|
LoadTopTenRequest,
|
||||||
LoadTopTenRequestSelector,
|
LoadTopTenRequestSelector,
|
||||||
} from "../request/loadTopTen";
|
} from "../request/loadTopTen";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
loadTopTen2.msgCode = 0x012c;
|
loadTopTen2.msgCode = 0x012c;
|
||||||
loadTopTen2.msgLen = 0x0110;
|
loadTopTen2.msgLen = 0x0110;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { LockProfileExtendRequest } from "../request/lockProfileExtend";
|
import { LockProfileExtendRequest } from "../request/lockProfileExtend";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
lockProfileExtend.msgCode = 0x006d;
|
lockProfileExtend.msgCode = 0x006d;
|
||||||
lockProfileExtend.msgLen = 0x0020;
|
lockProfileExtend.msgLen = 0x0020;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Message00AD } from "../request/msg00AD";
|
import { Message00AD } from "../request/msg00AD";
|
||||||
|
|
||||||
msg00AD.msgCode = 0x00ad;
|
msg00AD.msgCode = 0x00ad;
|
||||||
msg00AD.msgLen = 0x061c;
|
msg00AD.msgLen = 0x0620;
|
||||||
|
|
||||||
export function msg00AD(buf: Buffer): Message00AD {
|
export function msg00AD(buf: Buffer): Message00AD {
|
||||||
return { type: "msg_00AD_req" };
|
return { type: "msg_00AD_req" };
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { car } from "./_car";
|
import { car } from "./_car";
|
||||||
import { SaveNewCarRequest } from "../request/saveNewCar";
|
import { SaveNewCarRequest } from "../request/saveNewCar";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
saveNewCar.msgCode = 0x0079;
|
saveNewCar.msgCode = 0x0079;
|
||||||
saveNewCar.msgLen = 0x0090;
|
saveNewCar.msgLen = 0x0090;
|
||||||
@@ -3,7 +3,7 @@ import { mission } from "./_mission";
|
|||||||
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
||||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||||
import { bitmap } from "./_bitmap";
|
import { bitmap } from "./_bitmap";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
saveProfile2.msgCode = 0x0068;
|
saveProfile2.msgCode = 0x0068;
|
||||||
saveProfile2.msgLen = 0x0940;
|
saveProfile2.msgLen = 0x0940;
|
||||||
@@ -3,7 +3,7 @@ import { mission } from "./_mission";
|
|||||||
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
import { BackgroundCode, CourseNo, TitleCode } from "../model/base";
|
||||||
import { SaveProfileRequest2 } from "../request/saveProfile";
|
import { SaveProfileRequest2 } from "../request/saveProfile";
|
||||||
import { bitmap } from "./_bitmap";
|
import { bitmap } from "./_bitmap";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
saveProfile3.msgCode = 0x0138;
|
saveProfile3.msgCode = 0x0138;
|
||||||
saveProfile3.msgLen = 0x0a70;
|
saveProfile3.msgLen = 0x0a70;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { SaveSettingsRequest } from "../request/saveSettings";
|
import { SaveSettingsRequest } from "../request/saveSettings";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
saveSettings.msgCode = 0x00a5;
|
saveSettings.msgCode = 0x00a5;
|
||||||
saveSettings.msgLen = 0x0020;
|
saveSettings.msgLen = 0x0020;
|
||||||
@@ -2,7 +2,7 @@ import { bitmap } from "./_bitmap";
|
|||||||
import { chara } from "./_chara";
|
import { chara } from "./_chara";
|
||||||
import { CarSelector } from "../model/car";
|
import { CarSelector } from "../model/car";
|
||||||
import { SaveStockerRequest } from "../request/saveStocker";
|
import { SaveStockerRequest } from "../request/saveStocker";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
saveStocker.msgCode = 0x00a6;
|
saveStocker.msgCode = 0x00a6;
|
||||||
saveStocker.msgLen = 0x00c0;
|
saveStocker.msgLen = 0x00c0;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { RouteNo } from "../model/base";
|
import { RouteNo } from "../model/base";
|
||||||
import { CarSelector } from "../model/car";
|
import { CarSelector } from "../model/car";
|
||||||
import { SaveTimeAttackRequest } from "../request/saveTimeAttack";
|
import { SaveTimeAttackRequest } from "../request/saveTimeAttack";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
|
function saveTimeAttack(buf: Buffer): SaveTimeAttackRequest {
|
||||||
return {
|
return {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ExtId } from "../model/base";
|
import { ExtId } from "../model/base";
|
||||||
import { Team } from "../model/team";
|
import { Team } from "../model/team";
|
||||||
import { UpdateTeamLeaderRequest } from "../request/updateTeamLeader";
|
import { UpdateTeamLeaderRequest } from "../request/updateTeamLeader";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
updateTeamLeader.msgCode = 0x008a;
|
updateTeamLeader.msgCode = 0x008a;
|
||||||
updateTeamLeader.msgLen = 0x0020;
|
updateTeamLeader.msgLen = 0x0020;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ExtId } from "../model/base";
|
import { ExtId } from "../model/base";
|
||||||
import { Team } from "../model/team";
|
import { Team } from "../model/team";
|
||||||
import { UpdateTeamMemberRequest } from "../request/updateTeamMember";
|
import { UpdateTeamMemberRequest } from "../request/updateTeamMember";
|
||||||
import { AimeId } from "../../model";
|
import { AimeId } from "../../../model";
|
||||||
|
|
||||||
updateTeamMember.msgCode = 0x0073;
|
updateTeamMember.msgCode = 0x0073;
|
||||||
updateTeamMember.msgLen = 0x0010;
|
updateTeamMember.msgLen = 0x0010;
|
||||||
@@ -34,7 +34,7 @@ import { updateTeamLeader } from "./updateTeamLeader";
|
|||||||
import { updateTeamMember } from "./updateTeamMember";
|
import { updateTeamMember } from "./updateTeamMember";
|
||||||
import { Response } from "../response";
|
import { Response } from "../response";
|
||||||
|
|
||||||
const debug = logger("app:idz:encoder");
|
const debug = logger("app:idz:userdb:encoder");
|
||||||
|
|
||||||
function encode(res: Response): Buffer {
|
function encode(res: Response): Buffer {
|
||||||
switch (res.type) {
|
switch (res.type) {
|
||||||
@@ -141,27 +141,18 @@ function encode(res: Response): Buffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Encoder extends Transform {
|
export default function writeResponse(res: Response) {
|
||||||
constructor() {
|
debug("Object: %j", res);
|
||||||
super({
|
|
||||||
readableObjectMode: true,
|
const buf = encode(res);
|
||||||
writableObjectMode: true,
|
|
||||||
});
|
if (debug.enabled) {
|
||||||
|
debug("Encoded: %s", buf.toString("hex"));
|
||||||
}
|
}
|
||||||
|
|
||||||
_transform(res: Response, encoding, callback) {
|
if (buf.readInt16LE(0) === 0) {
|
||||||
debug("Object: %j", res);
|
throw new Error("Programming error: missing message type code");
|
||||||
|
|
||||||
const buf = encode(res);
|
|
||||||
|
|
||||||
if (debug.enabled) {
|
|
||||||
debug("Encoded: %s", buf.toString("hex"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf.readInt16LE(0) === 0) {
|
|
||||||
throw new Error("Missing message type code");
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user