From 8f45d173195957405142be91b588e70232c41152 Mon Sep 17 00:00:00 2001 From: memetrollsXD Date: Thu, 28 Jul 2022 22:02:12 +0200 Subject: [PATCH] Basic KCP --- .gitignore | 1 + package-lock.json | 39 ++++++++ package.json | 2 + src/data/packetIds.json | 6 ++ src/data/proto/PlayerGetTokenCsReq.proto | 7 ++ src/data/proto/PlayerGetTokenScRsp.proto | 28 ++++++ .../proto/QueryCurrRegionHttpRsp.proto | 0 src/db/Database.ts | 2 +- src/http/HttpServer.ts | 10 ++- .../combo/granter/login/v2/login.ts | 12 ++- .../hkrpg_global/mdk/shield/api/login.ts | 6 +- .../hkrpg_global/mdk/shield/api/verify.ts | 7 +- src/http/routes/query_gateway.ts | 2 +- src/index.ts | 4 +- src/server/kcp/Handshake.ts | 57 ++++++++++++ src/server/kcp/Packet.ts | 73 +++++++++++++++ src/server/kcp/SRServer.ts | 83 ++++++++++++++++++ src/server/kcp/Session.ts | 81 +++++++++++++++++ src/server/kcp/initial.key | Bin 0 -> 4096 bytes src/server/packets/PacketHandler.ts | 6 ++ src/server/packets/PlayerGetTokenCsReq.ts | 34 +++++++ src/util/Config.ts | 2 +- src/util/Logger.ts | 6 +- 23 files changed, 446 insertions(+), 22 deletions(-) create mode 100644 src/data/packetIds.json create mode 100644 src/data/proto/PlayerGetTokenCsReq.proto create mode 100644 src/data/proto/PlayerGetTokenScRsp.proto rename src/{ => data}/proto/QueryCurrRegionHttpRsp.proto (100%) create mode 100644 src/server/kcp/Handshake.ts create mode 100644 src/server/kcp/Packet.ts create mode 100644 src/server/kcp/SRServer.ts create mode 100644 src/server/kcp/Session.ts create mode 100644 src/server/kcp/initial.key create mode 100644 src/server/packets/PacketHandler.ts create mode 100644 src/server/packets/PlayerGetTokenCsReq.ts diff --git a/.gitignore b/.gitignore index 29ce859..92bc0c2 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ build/Release # Dependency directories node_modules/ jspm_packages/ +package-lock.json # TypeScript v1 declaration files typings/ diff --git a/package-lock.json b/package-lock.json index 598b5c6..1a5fe2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,8 +7,10 @@ "dependencies": { "@types/express": "^4.17.13", "colorts": "^0.1.63", + "dgram": "^1.0.1", "express": "^4.18.1", "mongodb": "^4.8.0", + "node-kcp-token": "github:memetrollsxd/node-kcp", "protobufjs": "^7.0.0" }, "devDependencies": { @@ -536,6 +538,12 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dgram": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dgram/-/dgram-1.0.1.tgz", + "integrity": "sha512-zJVFL1EWfKtE0z2VN6qfpn/a+qG1viEzcwJA0EjtzS76ONSE3sEyWBwEbo32hS4IFw/EWVuWN+8b89aPW6It2A==", + "deprecated": "npm is holding this package for security reasons. As it's a core Node module, we will not transfer it over to other users. You may safely remove the package from your dependencies." + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1014,6 +1022,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1022,6 +1035,15 @@ "node": ">= 0.6" } }, + "node_modules/node-kcp-token": { + "version": "1.0.12", + "resolved": "git+ssh://git@github.com/memetrollsxd/node-kcp.git#c92fdc77cfa3b62aed09976ac774102107b7452f", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "nan": "^2.15.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -2055,6 +2077,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dgram": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dgram/-/dgram-1.0.1.tgz", + "integrity": "sha512-zJVFL1EWfKtE0z2VN6qfpn/a+qG1viEzcwJA0EjtzS76ONSE3sEyWBwEbo32hS4IFw/EWVuWN+8b89aPW6It2A==" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -2411,11 +2438,23 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==" + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-kcp-token": { + "version": "git+ssh://git@github.com/memetrollsxd/node-kcp.git#c92fdc77cfa3b62aed09976ac774102107b7452f", + "from": "node-kcp-token@github:memetrollsxd/node-kcp", + "requires": { + "nan": "^2.15.0" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index 4bda276..a90f736 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,10 @@ "dependencies": { "@types/express": "^4.17.13", "colorts": "^0.1.63", + "dgram": "^1.0.1", "express": "^4.18.1", "mongodb": "^4.8.0", + "node-kcp-token": "github:memetrollsxd/node-kcp", "protobufjs": "^7.0.0" } } diff --git a/src/data/packetIds.json b/src/data/packetIds.json new file mode 100644 index 0000000..5529bed --- /dev/null +++ b/src/data/packetIds.json @@ -0,0 +1,6 @@ +{ + "101": "DebugNotify", + "5": "PlayerGetTokenCsReq", + "49": "PlayerGetTokenScRsp", + "22": "PlayerKeepAliveNotify" +} \ No newline at end of file diff --git a/src/data/proto/PlayerGetTokenCsReq.proto b/src/data/proto/PlayerGetTokenCsReq.proto new file mode 100644 index 0000000..a839b61 --- /dev/null +++ b/src/data/proto/PlayerGetTokenCsReq.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +message PlayerGetTokenCsReq { + uint32 account_type = 1; + string account_uid = 2; // Related to v2/login HTTP endpoint + string account_token = 3; +} \ No newline at end of file diff --git a/src/data/proto/PlayerGetTokenScRsp.proto b/src/data/proto/PlayerGetTokenScRsp.proto new file mode 100644 index 0000000..202cdd0 --- /dev/null +++ b/src/data/proto/PlayerGetTokenScRsp.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +message PlayerGetTokenScRsp { + uint32 retcode = 1; + string msg = 2; + uint32 uid = 3; + string token = 4; + uint32 black_uid_end_time = 5; + uint32 account_type = 6; + string account_uid = 7; + bool is_proficient_player = 8; + string secret_key = 9; + uint32 gm_uid = 10; + uint64 secret_key_seed = 11; + bytes security_cmd_buffer = 12; + uint32 platform_type = 13; + bytes extra_bin_data = 14; + bool is_guest = 15; + uint32 channel_id = 16; + uint32 sub_channel_id = 17; + uint32 tag = 18; + string country_code = 19; + bool is_login_white_list = 20; + string psn_id = 21; + string client_version_random_key = 22; + uint32 reg_platform = 23; + string client_ip_str = 24; +} \ No newline at end of file diff --git a/src/proto/QueryCurrRegionHttpRsp.proto b/src/data/proto/QueryCurrRegionHttpRsp.proto similarity index 100% rename from src/proto/QueryCurrRegionHttpRsp.proto rename to src/data/proto/QueryCurrRegionHttpRsp.proto diff --git a/src/db/Database.ts b/src/db/Database.ts index 73819df..09e388b 100644 --- a/src/db/Database.ts +++ b/src/db/Database.ts @@ -4,7 +4,7 @@ import Logger from "../util/Logger"; const c = new Logger("Database"); export default class Database { - public static instance: Database; + private static instance: Database; public static client: MongoClient; private constructor() { Database.client = new MongoClient(Config.MONGO_URI); diff --git a/src/http/HttpServer.ts b/src/http/HttpServer.ts index ed21933..3026858 100644 --- a/src/http/HttpServer.ts +++ b/src/http/HttpServer.ts @@ -4,7 +4,7 @@ import fs from 'fs'; import { resolve } from 'path'; import Config from '../util/Config'; import Logger, { VerboseLevel } from '../util/Logger'; -const c = new Logger("HTTP"); +const c = new Logger("HTTP", "cyan"); function r(...args: string[]) { return fs.readFileSync(resolve(__dirname, ...args)).toString(); @@ -17,11 +17,11 @@ const HTTPS_CONFIG = { export default class HttpServer { private readonly server; - public static instance: HttpServer; + private static instance: HttpServer; private constructor() { this.server = express(); - this.server.use(express.json()); + this.server.use(express.json()); this.server.route('/*').all((req, res) => { if (Logger.VERBOSE_LEVEL > VerboseLevel.WARNS) c.log(`${req.method} ${req.url}`); import(`./routes${req.url.split('?')[0]}`).then(async r => { @@ -34,7 +34,9 @@ export default class HttpServer { c.error(err); }); }); - + } + + public start(): void { https.createServer(HTTPS_CONFIG, this.server).listen(Config.HTTP.HTTP_PORT, Config.HTTP.HTTP_PORT); this.server.listen(80, Config.HTTP.HTTP_HOST, () => { c.log(`Listening on ${Config.HTTP.HTTP_HOST}:${Config.HTTP.HTTP_PORT}`); diff --git a/src/http/routes/hkrpg_global/combo/granter/login/v2/login.ts b/src/http/routes/hkrpg_global/combo/granter/login/v2/login.ts index 189bf36..dd8456c 100644 --- a/src/http/routes/hkrpg_global/combo/granter/login/v2/login.ts +++ b/src/http/routes/hkrpg_global/combo/granter/login/v2/login.ts @@ -1,14 +1,18 @@ import { Request, Response } from "express"; export default function handle(req: Request, res: Response) { + const data = JSON.parse(req.body.data) + res.send({ retcode: 0, message: "OK", data: { - combo_id: "0", - open_id: "", - combo_token: "", - data: '{"guest":false}', + combo_id: 1, + open_id: data.uid, + combo_token: data.token, + data: { + guest: data.guest + }, heartbeat: false, account_type: 1, fatigue_remind: null diff --git a/src/http/routes/hkrpg_global/mdk/shield/api/login.ts b/src/http/routes/hkrpg_global/mdk/shield/api/login.ts index 30bb2fe..574cb25 100644 --- a/src/http/routes/hkrpg_global/mdk/shield/api/login.ts +++ b/src/http/routes/hkrpg_global/mdk/shield/api/login.ts @@ -1,6 +1,7 @@ import { Request, Response } from "express"; import Account from "../../../../../../db/Account"; import Logger from "../../../../../../util/Logger"; +const c = new Logger("Dispatch"); // Example request: // { @@ -10,7 +11,6 @@ import Logger from "../../../../../../util/Logger"; // } export default async function handle(req: Request, res: Response) { - const c = new Logger(req.ip); const acc = await Account.getAccountByUsername(req.body.account); const dataObj: any = { retcode: 0, @@ -22,11 +22,11 @@ export default async function handle(req: Request, res: Response) { if (!acc) { dataObj.retcode = -202; dataObj.message = "Account not found"; - c.warn(`[DISPATCH] Player ${req.body.account} not found`); + c.warn(`Player ${req.body.account} not found (${req.ip})`); res.send(dataObj); } else { dataObj.data.account = acc; - c.log(`[DISPATCH] Player ${req.body.account} logged in`); + c.log(`Player ${req.body.account} logged in (${req.ip})`); res.send(dataObj); } } \ No newline at end of file diff --git a/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts b/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts index efd2b54..cb37761 100644 --- a/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts +++ b/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts @@ -1,12 +1,11 @@ import { Request, Response } from "express"; import Account from "../../../../../../db/Account"; import Logger from "../../../../../../util/Logger"; - +const c = new Logger("Dispatch"); // Example request: // {"uid":"63884253","token":"ZQmgMdXA1StL9A3aPBUedr8yoiuoLrmV"} export default async function handle(req: Request, res: Response) { - const c = new Logger(req.ip); const acc = await Account.getAccountByUID(req.body.uid); const dataObj: any = { retcode: 0, @@ -19,11 +18,11 @@ export default async function handle(req: Request, res: Response) { dataObj.retcode = -202; dataObj.message = "Account not found"; res.send(dataObj); - c.warn(`[DISPATCH] Player ${req.body.uid} not found`); + c.warn(`Player ${req.body.uid} not found (${req.ip})`); } else { if (acc.token === req.body.token) { dataObj.data.account = acc; - c.log(`[DISPATCH] Player ${req.body.uid} logged in`); + c.log(`Player ${req.body.uid} logged in (${req.ip})`); res.send(dataObj); } else { dataObj.retcode = -202; diff --git a/src/http/routes/query_gateway.ts b/src/http/routes/query_gateway.ts index 4028fd0..9c5d8a3 100644 --- a/src/http/routes/query_gateway.ts +++ b/src/http/routes/query_gateway.ts @@ -3,7 +3,7 @@ import protobuf from 'protobufjs'; import { resolve } from 'path'; import Config from "../../util/Config"; -const proto = protobuf.loadSync(resolve(__dirname, '../../proto/QueryCurrRegionHttpRsp.proto')).lookup('QueryCurrRegionHttpRsp') as any; +const proto = protobuf.loadSync(resolve(__dirname, '../../data/proto/QueryCurrRegionHttpRsp.proto')).lookup('QueryCurrRegionHttpRsp') as any; export default function handle(req: Request, res: Response) { const dataObj = { diff --git a/src/index.ts b/src/index.ts index a6c8973..73d69fc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,9 +5,11 @@ */ import Interface from "./commands/Interface"; import HttpServer from "./http/HttpServer"; +import SRServer from "./server/kcp/SRServer"; import Logger from "./util/Logger"; const c = new Logger("CrepeSR"); c.log(`Starting CrepeSR...`); Interface.start(); -HttpServer.getInstance(); \ No newline at end of file +HttpServer.getInstance().start(); +SRServer.getInstance().start(); \ No newline at end of file diff --git a/src/server/kcp/Handshake.ts b/src/server/kcp/Handshake.ts new file mode 100644 index 0000000..92c191f --- /dev/null +++ b/src/server/kcp/Handshake.ts @@ -0,0 +1,57 @@ +export enum HandshakeType { + CONNECT = 1, + DISCONNECT = 2, + SEND_BACK_CONV = 3, + UNKNOWN = 4 +} + +export default class Handshake { + private static readonly CONNECT: number[] = [0xff, 0xFFFFFFFF] + private static readonly SEND_BACK_CONV: number[] = [0x145, 0x14514545] + private static readonly DISCONNECT: number[] = [0x194, 0x19419494] + + public readonly conv: number; + public readonly type: number[]; + public readonly handshakeType!: HandshakeType; + public readonly token: number; + public readonly data: number; + + public constructor(public readonly bytes: Buffer | HandshakeType) { + if (Buffer.isBuffer(bytes)) { + this.conv = bytes.readUInt32BE(4); + this.token = bytes.readUInt32BE(8); + this.data = bytes.readUInt32BE(12); + this.type = [bytes.readUInt32BE(0), bytes.readUInt32BE(16)]; + this.handshakeType = this.decodeType(); + } else { + this.conv = 0x69; + this.token = 0x420; + this.data = 0; + this.type = Handshake.SEND_BACK_CONV; + this.handshakeType = HandshakeType.SEND_BACK_CONV; + } + } + + public encode(): Buffer { + const buf = Buffer.alloc(20); + buf.writeUInt32BE(this.type[0]); + buf.writeUInt32BE(this.conv, 4); + buf.writeUInt32BE(this.token, 8); + buf.writeUInt32BE(this.data, 12); + buf.writeUInt32BE(this.type[1], 16); + return buf; + } + + public decodeType(): HandshakeType { + if (this.type[0] == Handshake.CONNECT[0] && this.type[1] == Handshake.CONNECT[1]) { + return HandshakeType.CONNECT; + } + if (this.type[0] == Handshake.SEND_BACK_CONV[0] && this.type[1] == Handshake.SEND_BACK_CONV[1]) { + return HandshakeType.SEND_BACK_CONV + } + if (this.type[0] == Handshake.DISCONNECT[0] && this.type[1] == Handshake.DISCONNECT[1]) { + return HandshakeType.DISCONNECT; + } + return HandshakeType.UNKNOWN; + } +} \ No newline at end of file diff --git a/src/server/kcp/Packet.ts b/src/server/kcp/Packet.ts new file mode 100644 index 0000000..05075ec --- /dev/null +++ b/src/server/kcp/Packet.ts @@ -0,0 +1,73 @@ +import Logger, { VerboseLevel } from "../../util/Logger"; +import protobuf from 'protobufjs'; +import { resolve } from 'path'; +import _packetIds from '../../data/packetIds.json'; +const packetIds = _packetIds as { [key: string]: string }; +const switchedPacketIds: { [key: string]: number } = (function () { + const obj: { [key: string]: number } = {}; + + Object.keys(packetIds).forEach((key) => { + obj[packetIds[key]] = Number(key); + }); + + return obj; +})(); +const c = new Logger("Packet") + +export default class Packet { + public readonly cmdid: number; + public readonly data: Buffer; + public body: {} = {}; + + public constructor(public readonly rawData: Buffer, public readonly protoName = "") { + // Remove the header and metadata + const metadataLength = rawData.readUInt16BE(6); + this.data = rawData.subarray(12 + metadataLength, 12 + metadataLength + rawData.readUInt32BE(8)); + this.cmdid = this.rawData.readUInt16BE(4); + + this.protoName = packetIds[this.cmdid.toString()]; + if (this.protoName) { + try { + const root = protobuf.loadSync(resolve(__dirname, `../../data/proto/${this.protoName}.proto`)); + const Message = root.lookupTypeOrEnum(this.protoName); + this.body = Message.decode(this.data); + } catch (e) { + c.warn(`Failed to decode ${this.protoName}`); + if (Logger.VERBOSE_LEVEL >= VerboseLevel.ALL) { + c.error(e as Error, false); + } + c.debug(`Data: ${this.data.toString("hex")}`); + } + } else { + c.error(`Unknown packet id ${this.cmdid}`); + } + } + + public static isValid(data: Buffer): boolean { + // Buffer acting fucky so i'll just use good ol' string manipulation + const str = data.toString('hex'); + return str.startsWith("01234567") && str.endsWith("89abcdef"); + } + + public static encode(name: string, body: {}): Packet | null { + try { + const cmdid = switchedPacketIds[name]; + const root = protobuf.loadSync(resolve(__dirname, `../../data/proto/${name}.proto`)); + const Message = root.lookupTypeOrEnum(name); + + const data = Buffer.from(Message.encode(body).finish()); + const packet = Buffer.allocUnsafe(16 + data.length); + packet.writeUInt32BE(0x1234567); + packet.writeUint16BE(cmdid, 4); + packet.writeUint16BE(0, 6); + packet.writeUint32BE(data.length, 8); + data.copy(packet, 12); + packet.writeUint32BE(0x89abcdef, 12 + data.length); + + return new Packet(packet); + } catch (e) { + c.error(e as Error); + return null; + } + } +} \ No newline at end of file diff --git a/src/server/kcp/SRServer.ts b/src/server/kcp/SRServer.ts new file mode 100644 index 0000000..4e22590 --- /dev/null +++ b/src/server/kcp/SRServer.ts @@ -0,0 +1,83 @@ +import _KCP from 'node-kcp-token'; +import Logger from "../../util/Logger"; +import { Socket, createSocket, RemoteInfo } from "dgram"; +import Session from "./Session"; +import Config from "../../util/Config"; +import Handshake, { HandshakeType } from "./Handshake"; +const KCP = _KCP.KCP; +const c = new Logger("KCP", "yellow"); + +export default class SRServer { + private static instance: SRServer; + public readonly udpSocket: Socket; + public readonly sessions: Map = new Map(); + + private constructor() { + this.udpSocket = createSocket("udp4"); + } + + public static getInstance(): SRServer { + if (!SRServer.instance) { + SRServer.instance = new SRServer(); + } + return SRServer.instance; + } + + public start() { + this.udpSocket.bind(Config.GAMESERVER.SERVER_PORT, "0.0.0.0"); + + this.udpSocket.on('listening', () => this.onListening()); + this.udpSocket.on('message', (d, i) => this.onMessage(d, i)); + this.udpSocket.on('error', (e) => this.onError(e)); + } + + private async onMessage(data: Buffer, rinfo: RemoteInfo) { + const client = `${rinfo.address}:${rinfo.port}`; + if (data.byteLength == 20) { + // Hamdshanke + const handshake = new Handshake(data); + c.debug(data.toString("hex")); + + switch (handshake.handshakeType) { + case HandshakeType.CONNECT: + c.log(`${client} connected`); + const rsp = new Handshake(HandshakeType.SEND_BACK_CONV).encode(); + this.udpSocket.send(rsp, 0, rsp.byteLength, rinfo.port, rinfo.address); + const kcpobj = new KCP(0x69, 0x420, { + address: rinfo.address, + port: rinfo.port, + family: rinfo.family + }); + kcpobj.nodelay(1, 5, 2, 0); + kcpobj.output((d, s, u) => this.output(d, s, u)); + kcpobj.wndsize(256, 256); + this.sessions.set(client, new Session(kcpobj, rinfo)); + break; + case HandshakeType.DISCONNECT: + c.log(`${client} disconnected`); + this.sessions.delete(client); + break; + default: + c.error(`${client} unknown Handshake: ${data.readUint32BE(0)}`); + } + return; + } + + const session = this.sessions.get(client); + if (!session) return; + session.inputRaw(data); + } + + private output(buf: Buffer, size: number, ctx: { address: string, port: number, family: string }) { + if (!buf) return; + this.udpSocket.send(buf, 0, size, ctx.port, ctx.address); + } + + private async onError(err: Error) { + c.error(err); + } + + private async onListening() { + c.log(`Listening on 0.0.0.0:${Config.GAMESERVER.SERVER_PORT}`); + } +} \ No newline at end of file diff --git a/src/server/kcp/Session.ts b/src/server/kcp/Session.ts new file mode 100644 index 0000000..3069a53 --- /dev/null +++ b/src/server/kcp/Session.ts @@ -0,0 +1,81 @@ +import _KCP from 'node-kcp-token'; +import { RemoteInfo } from 'dgram'; +import { resolve } from 'path'; +import fs from 'fs'; +import KCP from 'node-kcp-token'; +import Packet from './Packet'; +import Logger, { VerboseLevel } from '../../util/Logger'; +import defaultHandler from '../packets/PacketHandler'; + +function r(...args: string[]) { + return fs.readFileSync(resolve(__dirname, ...args)); +} + +function xor(data: Buffer, key: Buffer) { + const ret: Buffer = Buffer.from(data); + for (let i = 0; i < data.length; i++) ret.writeUInt8(data.readUInt8(i) ^ key.readUInt8(i % key.length), i); + return ret; +} + +export default class Session { + public key: Buffer = r('./initial.key'); + public c: Logger; + public constructor(private readonly kcpobj: KCP.KCP, public readonly ctx: RemoteInfo) { + this.kcpobj = kcpobj; + this.ctx = ctx; + this.c = new Logger(`${this.ctx.address}:${this.ctx.port}`, 'yellow'); + this.update(); + } + + public inputRaw(data: Buffer) { + this.kcpobj.input(data); + } + + public async update() { + if (!this.kcpobj) { + console.error("wtf kcpobj is undefined"); + console.debug(this) + return; + } + const hr = process.hrtime(); + + const timestamp = hr[0] * 1000000 + hr[1] / 1000; + this.kcpobj.update(timestamp); + + let recv; + do { + recv = this.kcpobj.recv(); + if (!recv) break; + + this.c.debug(`recv ${recv.toString("hex")}`); + + if (Packet.isValid(recv)) { + this.handlePacket(new Packet(recv)); + } + + } while (recv) + + setTimeout(() => this.update(), 1); + } + + public async handlePacket(packet: Packet) { + if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName) + + import(`../packets/${packet.protoName}`).then(mod => { + mod.default(this, packet); + }).catch(e => { + if (e.code === 'MODULE_NOT_FOUND') this.c.warn(`Unhandled packet: ${packet.protoName}`); + else this.c.error(e); + + defaultHandler(this, packet); + }); + } + + public send(name: string, body: {}) { + const packet = Packet.encode(name, body); + if (!packet) return; + if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName); + this.c.debug(`send ${packet.rawData.toString('hex')}`); + this.kcpobj.send(packet.rawData); + } +} \ No newline at end of file diff --git a/src/server/kcp/initial.key b/src/server/kcp/initial.key new file mode 100644 index 0000000000000000000000000000000000000000..29b396f149b177e92f9075ee299b93655d3057de GIT binary patch literal 4096 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.bgRed.bold}`, e.message); - if (e.stack) this.trail(e.stack); + if (e.stack && stack) this.trail(e.stack); } public warn(...args: string[]) {