Basic KCP
This commit is contained in:
parent
8779554ed2
commit
8f45d17319
1
.gitignore
vendored
1
.gitignore
vendored
@ -40,6 +40,7 @@ build/Release
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
package-lock.json
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
39
package-lock.json
generated
39
package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
6
src/data/packetIds.json
Normal file
6
src/data/packetIds.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"101": "DebugNotify",
|
||||
"5": "PlayerGetTokenCsReq",
|
||||
"49": "PlayerGetTokenScRsp",
|
||||
"22": "PlayerKeepAliveNotify"
|
||||
}
|
7
src/data/proto/PlayerGetTokenCsReq.proto
Normal file
7
src/data/proto/PlayerGetTokenCsReq.proto
Normal file
@ -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;
|
||||
}
|
28
src/data/proto/PlayerGetTokenScRsp.proto
Normal file
28
src/data/proto/PlayerGetTokenScRsp.proto
Normal file
@ -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;
|
||||
}
|
@ -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);
|
||||
|
@ -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}`);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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 = {
|
||||
|
@ -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();
|
||||
HttpServer.getInstance().start();
|
||||
SRServer.getInstance().start();
|
57
src/server/kcp/Handshake.ts
Normal file
57
src/server/kcp/Handshake.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
73
src/server/kcp/Packet.ts
Normal file
73
src/server/kcp/Packet.ts
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
83
src/server/kcp/SRServer.ts
Normal file
83
src/server/kcp/SRServer.ts
Normal file
@ -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<string, Session> = 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}`);
|
||||
}
|
||||
}
|
81
src/server/kcp/Session.ts
Normal file
81
src/server/kcp/Session.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
BIN
src/server/kcp/initial.key
Normal file
BIN
src/server/kcp/initial.key
Normal file
Binary file not shown.
6
src/server/packets/PacketHandler.ts
Normal file
6
src/server/packets/PacketHandler.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
session.c.debug(packet.body);
|
||||
}
|
34
src/server/packets/PlayerGetTokenCsReq.ts
Normal file
34
src/server/packets/PlayerGetTokenCsReq.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import Logger from "../../util/Logger";
|
||||
import Account from "../../db/Account";
|
||||
import Packet from "../kcp/Packet";
|
||||
import Session from "../kcp/Session";
|
||||
const c = new Logger("Dispatch");
|
||||
|
||||
interface PlayerGetTokenCsReq {
|
||||
accountToken?: string;
|
||||
accountUid?: string;
|
||||
accountType?: number;
|
||||
}
|
||||
|
||||
export default async function handle(session: Session, packet: Packet) {
|
||||
const body = packet.body as PlayerGetTokenCsReq;
|
||||
const account = await Account.getAccountByUID(body.accountUid || 0);
|
||||
if (!account) {
|
||||
c.error(`Account not found: ${body.accountUid}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const isTokenValid = account.token === body.accountToken;
|
||||
if (!isTokenValid) {
|
||||
c.error(`Token invalid (${session.ctx.address}:${session.ctx.port})`);
|
||||
return;
|
||||
}
|
||||
|
||||
session.send('PlayerGetTokenScRsp', {
|
||||
uid: account.uid,
|
||||
token: body.accountToken,
|
||||
secretKey: BigInt(0).toString(),
|
||||
accountUid: account.uid.toString(),
|
||||
accountType: body.accountType,
|
||||
});
|
||||
}
|
@ -23,7 +23,7 @@ const DEFAULT_CONFIG = {
|
||||
|
||||
// GameServer
|
||||
GAMESERVER: {
|
||||
SERVER_IP: "0.0.0.0",
|
||||
SERVER_IP: "127.0.0.1",
|
||||
SERVER_PORT: 22102,
|
||||
MAINTENANCE: false,
|
||||
MAINTENANCE_MSG: "Server is in maintenance mode."
|
||||
|
@ -12,7 +12,7 @@ type Color = 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white'
|
||||
export default class Logger {
|
||||
public static VERBOSE_LEVEL: VerboseLevel = Config.VERBOSE_LEVEL || 1;
|
||||
|
||||
constructor(public name: string, public color: Color = 'cyan') {
|
||||
constructor(public name: string, public color: Color = 'blue') {
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
@ -34,10 +34,10 @@ export default class Logger {
|
||||
console.log(`\t↳ ${args.join(' ').gray}`);
|
||||
}
|
||||
|
||||
public error(e: Error | string) {
|
||||
public error(e: Error | string, stack: boolean = true) {
|
||||
if (typeof e === 'string') e = new Error(e);
|
||||
console.log(`[${this.getDate().white.bold}] ${`ERROR<${this.name}>`.bgRed.bold}`, e.message);
|
||||
if (e.stack) this.trail(e.stack);
|
||||
if (e.stack && stack) this.trail(e.stack);
|
||||
}
|
||||
|
||||
public warn(...args: string[]) {
|
||||
|
Loading…
Reference in New Issue
Block a user