See commit description

* Basic command implementation:
- /debug: Sets VerboseLevel
- /account: Creates or deletes an account
- /exit: Exits the process
- /maintenance: Toggle maintenance

* Database System
* Basic HTTP handling
This commit is contained in:
memetrollsXD 2022-07-26 15:53:24 +02:00
parent 2317e5cbb5
commit 09b49c52bf
No known key found for this signature in database
GPG Key ID: 105C2F3417AC32CD
33 changed files with 2511 additions and 16 deletions

20
config.json Normal file
View File

@ -0,0 +1,20 @@
{
"VERBOSE_LEVEL": 2,
"MONGO_URI": "mongodb://localhost:27017/crepesr",
"HTTP": {
"HTTP_HOST": "0.0.0.0",
"HTTP_PORT": 443
},
"DISPATCH": [
{
"DISPATCH_NAME": "CrepeSR",
"DISPATCH_URL": "http://localhost/query_gateway"
}
],
"GAMESERVER": {
"SERVER_IP": "127.0.0.1",
"SERVER_PORT": 22102,
"MAINTENANCE": true,
"MAINTENANCE_MSG": "Server is in maintenance mode."
}
}

1649
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,5 +4,12 @@
},
"scripts": {
"start": "ts-node-dev src/index.ts --respawn --transpile-only"
},
"dependencies": {
"@types/express": "^4.17.13",
"colorts": "^0.1.63",
"express": "^4.18.1",
"mongodb": "^4.8.0",
"protobufjs": "^7.0.0"
}
}

51
src/commands/Interface.ts Normal file
View File

@ -0,0 +1,51 @@
import { createInterface } from 'readline';
import _alias from './alias.json';
import Logger from '../util/Logger';
const c = new Logger("Command", "blue");
const alias: { [key: string]: string } = _alias;
export class Command {
public readonly name: string;
public readonly args: string[];
public constructor(public readonly full: string) {
const split = full.split(" ");
this.name = split[0];
this.args = split.slice(1);
}
}
export default class Interface {
public static readonly rl = createInterface({
input: process.stdin,
output: process.stdout
});
private constructor() { }
public static readonly start = () => {
Interface.rl.question("", (_command) => {
if (!_command) {
Interface.start();
return;
}
const cmd = new Command(_command);
import(`./${alias[cmd.name] || cmd.name}`).then(async module => {
await module.default(cmd);
}).catch(err => {
if (err.code == "MODULE_NOT_FOUND") {
c.log(`Command ${cmd.name} not found.`);
return;
}
c.error(err);
});
Interface.start();
});
Interface.rl.on('close', () => {
console.log('Have a great day!');
process.exit(0);
});
}
}

37
src/commands/account.ts Normal file
View File

@ -0,0 +1,37 @@
import Account from "../db/Account";
import Logger from "../util/Logger";
import { Command } from "./Interface";
const c = new Logger("/account", "blue");
export default async function handle(command: Command) {
switch (command.args[0]) {
case "create":
if (!command.args[1]) {
c.log(`Usage: account create <name> [uid]`);
return;
}
try {
const acc = await Account.createAccount(command.args[1], command.args[2]);
c.log(`Account ${acc.name} with UID ${acc.uid} created.`);
} catch (e) {
c.error(e as Error);
}
break;
case "delete":
if (!command.args[1]) {
c.log(`Usage: account delete <uid>`);
return;
}
const acc = await Account.getAccountByUID(command.args[1]);
if (!acc) {
c.error(`Account with UID ${command.args[1]} does not exist.`);
return;
}
Account.deleteAccount(command.args[1]);
c.log(`Account ${acc.name} with UID ${acc.uid} deleted successfully.`);
break;
default:
c.log(`Usage: account create <name> [uid]`);
c.log(`Usage: account delete <uid>`);
}
}

3
src/commands/alias.json Normal file
View File

@ -0,0 +1,3 @@
{
"close": "exit"
}

19
src/commands/debug.ts Normal file
View File

@ -0,0 +1,19 @@
import Config from "../util/Config";
import Logger, { VerboseLevel } from "../util/Logger";
import { Command } from "./Interface";
const c = new Logger("/debug", "blue");
export default async function handle(command: Command) {
if (!command.args[0]) c.log(`VerboseLevel: ${Config.VERBOSE_LEVEL}`);
else {
let level = parseInt(command.args[0]);
if (!level) level = 0;
if (level > 2 || level < 0) {
c.log("Invalid verbose level. Must be between 0 and 2.");
return;
}
Config.VERBOSE_LEVEL = level as unknown as VerboseLevel;
c.log(`VerboseLevel set to ${Config.VERBOSE_LEVEL} (${VerboseLevel[level]})`);
}
}

8
src/commands/exit.ts Normal file
View File

@ -0,0 +1,8 @@
import Logger from "../util/Logger";
import { Command } from "./Interface";
const c = new Logger("/exit", "blue");
export default async function handle(command: Command) {
c.log("Good riddance!");
process.exit(1);
}

View File

@ -0,0 +1,23 @@
import Config from "../util/Config";
import Logger, { VerboseLevel } from "../util/Logger";
import { Command } from "./Interface";
const c = new Logger("/maintenance", "blue");
export default async function handle(command: Command) {
switch (command.args[0]) {
case "on":
Config.GAMESERVER.MAINTENANCE = true;
if (command.args[1]) Config.GAMESERVER.MAINTENANCE_MSG = command.args.slice(1).join(" ");
c.log("Maintenance mode enabled.");
break;
case "off":
Config.GAMESERVER.MAINTENANCE = false;
c.log("Maintenance mode disabled.");
break;
default:
c.log(`Maintenance mode is ${Config.GAMESERVER.MAINTENANCE ? "enabled" : "disabled"}`);
c.log(`Maintenance message: ${Config.GAMESERVER.MAINTENANCE_MSG}`);
c.log("Usage: /maintenance [on|off] [message]");
break;
}
}

68
src/db/Account.ts Normal file
View File

@ -0,0 +1,68 @@
import Logger from "../util/Logger";
import Database from "./Database";
const c = new Logger("Account");
interface AccountI {
uid: string | number;
name: string;
token: string;
}
export default class Account {
private constructor(public readonly uid: string | number, public readonly name: string, public readonly token: string) {
}
public static async getAccountByUID(uid: string | number): Promise<Account | undefined> {
const db = Database.getInstance();
const account = await db.get("accounts", { _id: Number(uid) });
if (!account) return;
return new Account(Number(account._id.toString()), account.name, account.token);
}
public static async getAccountByUsername(name: string): Promise<Account | undefined> {
const db = Database.getInstance();
const account = await db.get("accounts", { name });
if (!account) return;
return new Account(Number(account._id.toString()), account.name, account.token);
}
public static async createAccount(name: string, uid?: string | number): Promise<Account> {
const db = Database.getInstance();
let selfAssignedUID = true;
if (!uid) {
uid = Math.round(Math.random() * 50000);
selfAssignedUID = false;
}
const account = await db.get("accounts", { uid });
if (account) {
if (!selfAssignedUID) {
return await Account.createAccount(name, uid);
} else {
throw new Error(`Account with uid ${uid} already exists.`);
}
}
const token = generateToken();
await db.set("accounts", { _id: Number(uid), name, token });
return new Account(Number(uid), name, token);
}
public static async deleteAccount(uid: string | number): Promise<void> {
const db = Database.getInstance();
const account = await Account.getAccountByUID(uid);
if (!account) {
throw new Error(`Account with uid ${uid} does not exist.`);
}
await db.delete("accounts", { _id: Number(uid) });
}
}
function generateToken(): string {
let token = "";
for (let i = 0; i < 16; i++) {
token += Math.random().toString(36).substring(2, 15)
}
return token;
}

68
src/db/Database.ts Normal file
View File

@ -0,0 +1,68 @@
import { MongoClient } from "mongodb";
import Config from "../util/Config";
import Logger from "../util/Logger";
const c = new Logger("Database");
export default class Database {
public static instance: Database;
public static client: MongoClient;
private constructor() {
Database.client = new MongoClient(Config.MONGO_URI);
try {
Database.client.connect();
} catch (e) {
c.error(e as Error);
}
}
public static getInstance(): Database {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
public async get(collection: string, query?: {}) {
try {
const db = await Database.client.db();
const _collection = db.collection(collection);
if (!(await db.listCollections({ name: collection }).toArray()).length) {
c.warn(`Collection ${collection} does not exist. Creating...`);
await _collection.createIndexes([{ key: { id: 1 }, unique: true }]);
}
const result = query ? await _collection.findOne(query) : await _collection.findOne();
return result;
} catch (e) {
c.error(e as Error);
return null;
}
}
public async set(collection: string, payload: any) {
try {
const db = await Database.client.db();
const _collection = db.collection(collection);
if (!(await db.listCollections({ name: collection }).toArray()).length) {
c.warn(`Collection ${collection} does not exist. Creating...`);
await _collection.createIndexes([{ key: { id: 1 }, unique: true }]);
}
return await _collection.insertOne(payload);
} catch (e) {
c.error(e as Error);
}
}
public async delete(collection: string, query: {}) {
try {
const db = await Database.client.db();
const _collection = db.collection(collection);
if (!(await db.listCollections({ name: collection }).toArray()).length) {
c.warn(`Collection ${collection} does not exist. Creating...`);
await _collection.createIndexes([{ key: { id: 1 }, unique: true }]);
}
return await _collection.deleteOne(query);
} catch (e) {
c.error(e as Error);
}
}
}

42
src/http/HttpServer.ts Normal file
View File

@ -0,0 +1,42 @@
import express from 'express';
import https from 'https';
import fs from 'fs';
import { resolve } from 'path';
import Config from '../util/Config';
import Logger, { VerboseLevel } from '../util/Logger';
const c = new Logger("HTTP");
function r(...args: string[]) {
return fs.readFileSync(resolve(__dirname, ...args)).toString();
}
const HTTPS_CONFIG = {
key: r('./cert/cert.key'),
cert: r('./cert/cert.crt'),
}
export default class HttpServer {
private readonly server;
public constructor() {
this.server = express();
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 => {
await r.default(req, res);
}).catch(err => {
res.send({
code: 0
});
if (err.code === 'MODULE_NOT_FOUND') return;
c.error(err);
});
});
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}`);
});
}
}

23
src/http/cert/cert.crt Normal file
View File

@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIID2TCCAsGgAwIBAgIUO4lg/Dj+kZ9fv+AFxQBrMNUJc9cwDQYJKoZIhvcNAQEL
BQAwVjESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJDWTEOMAwGA1UECAwF
Q3JlcGUxDjAMBgNVBAcMBUNyZXBlMRMwEQYDVQQKDApDcmVwZSBJbmMuMB4XDTIx
MTIxMjE2NDY0NloXDTMxMTIxMDE2NDY0NlowVjESMBAGA1UEAwwJbG9jYWxob3N0
MQswCQYDVQQGEwJDWTEOMAwGA1UECAwFQ3JlcGUxDjAMBgNVBAcMBUNyZXBlMRMw
EQYDVQQKDApDcmVwZSBJbmMuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
AQEAnPFIkkIHKJdmWdUdmwja9qENmnmVL7iSFrgMhBRuZadsD1gYb78liWHH/CT4
5zmVJ44LiON3DjmCP/gAbFe3epPifB5i6CIh+tolqG8fg9WyyWrP71Z+raaGtlV4
NIyybRJI/9Gjysf4aehpCtEKJf4BAy82lrfBhNhmHf16W65c0NCGMJoB9Wr+wZCd
R6PzcKgWNa33YVfXTD8PiTOU9cLRvRFgwO870f/8jekxVdHggfTdQmVj9rcNet6X
MrWvzUI4LnI2JPyyEpMtlAQnDQ2+aGG5A3GdPPkWeaST+vF6CDCTFkg8Dxw0jQ30
jc0uQv/zz0mFuqvvivgpGSXeuwIDAQABo4GeMIGbMB0GA1UdDgQWBBS8E5THWThf
TVAwTnGez4druLTacDAfBgNVHSMEGDAWgBS8E5THWThfTVAwTnGez4druLTacDAO
BgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMC
MCcGA1UdEQQgMB6CDCoubWlob3lvLmNvbYIOKi55dWFuc2hlbi5jb20wDQYJKoZI
hvcNAQELBQADggEBAIVqx99S1DYtyLqsi15KwTUawShW9iJVsvpk59vK6qbbzuGZ
L72DrRJVTMxodv4WLaiM+te1TsW0WmdrhUYUBfIi+JpB67QB6aWKkOCP8fYq+mWn
Q3vuAEC6KpWH30j+0S58LVV+2iaGVetXYmYDXKoNslyVuJAM4iZSutTZhctO2Fxm
Vicp0fiPq/HJzxsmKHxyFJsgsdV0Dl9ElnlhpH77qxi/nXuUz9YlWZwwQI8KSsKm
sOzTUpSHHHpDocT24Yx73bR3Hd8CJam2bCEOOIIJG7sPx2lhTEJ+sKHDh4jHmRUI
3rVk5R+x1CcIrAgin+8nH28PhZFdOKs+CYMYGk0=
-----END CERTIFICATE-----

28
src/http/cert/cert.key Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCc8UiSQgcol2ZZ
1R2bCNr2oQ2aeZUvuJIWuAyEFG5lp2wPWBhvvyWJYcf8JPjnOZUnjguI43cOOYI/
+ABsV7d6k+J8HmLoIiH62iWobx+D1bLJas/vVn6tpoa2VXg0jLJtEkj/0aPKx/hp
6GkK0Qol/gEDLzaWt8GE2GYd/XpbrlzQ0IYwmgH1av7BkJ1Ho/NwqBY1rfdhV9dM
Pw+JM5T1wtG9EWDA7zvR//yN6TFV0eCB9N1CZWP2tw163pcyta/NQjgucjYk/LIS
ky2UBCcNDb5oYbkDcZ08+RZ5pJP68XoIMJMWSDwPHDSNDfSNzS5C//PPSYW6q++K
+CkZJd67AgMBAAECggEBAJv3KQC4f3a2Zu+1XBObTEc2rFcsprbi/MN5Km8EAuYg
6MGi8b3zvrD1rJGGiJj5X6IMhqgGLWXEfw1lP75ruZomZzij1fUNHqm1qyDlNfOF
JoUGEhiu43tc95kx/SB0Bklgl40rYFQAQH23itRGA4jYEVeBzwUfHkEP8QOyyKtc
YKJHa1jCEvE4XHxLqxFC+z7aNmpFonrW8OerVwrDPIkFbXYrC1alzxqMcNBJVeKE
LFFc6EK9ppoPSsm600yac5gOX2ima9JkaYkGKhK25D4NXCd511Ea/TJNctA8IkYA
QB7mCzLHdapRlhSplDwwwgNRzlrkImbqyS2+768ejYkCgYEAzyatfH62cDSDP5e1
oukWI46AqybfIhd9kHzIbIyPjuSb3RvvuxIfNBFjUxRyh+ggCjnRA14HcZzDcGRd
6VpApq80LQk6mhklKicJNRrcuEvdmj+kRJ+XtSbNZhJVUkNf7nmHCOoJkEwRsblL
kIZCVmfkWAKFjHnCEqH5RHvTbacCgYEAwfObyw0eMD+X23IpyEsCsTKIwIk4Zl8J
iTNnR9yp6B9JdwLSdEWQjhXSltkfmNZ3WXYoyh6kr3jp8d5EqWX1P8JrZBaZikTD
y526kKIvs/I/iW3obOoE6CX6LJEGob8bAPkvu2u37Rghign57W02fYtzJUuqPAoK
b5mtrQ/ycM0CgYBsR8pthgq1Mi3dAt82DeK9qVKGpGYEewTujttxKjQsPEFg3aZ9
Qaa/38rsdYa8ldCRp9EikncPoyLh0ATq4nti5bg/RlC0lipAE3GTqbvwNe/bHiMu
n8F8NpEtJq4ktwUhMbMtLLDdFXY2USY3oIZyhhHtEzxdxpN0i+gxLQzChwKBgQCG
FFztYGIwRKY8hI2x83km+qJjR/l/e8/h03Fg0oF7ALYO2hqXWsf2EcwFkJAxXoIf
jHniUJDU5agFFv0shlmm/Ea1aJI4bhVVG/MvrY+AvMWDwkFdmeJOgoKScKe/BZgr
chi3Xl5GP9pfzUnEAy4aWF7/t3E2FFLml7zi2RVnOQKBgQCYmzwikvHpR+HRiZL9
m0TB6mayGbwQO3xU/naZ1UyCydsRORQnbKaTWSMZgrr7nTAqDhegfUyJXX+uxhGQ
cGu9uLENZWrysXROpMZBhgyNUbDPdO0okIMoJ3kUSocC7L7lpWyZGOxgMwi0a4qK
nV1QDKXsA3oBZn9MJpkuQ81jCw==
-----END PRIVATE KEY-----

View File

@ -0,0 +1,9 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
// Test handler
res.send({
retcode: -1,
message: "Invalid endpoint"
});
}

View File

@ -0,0 +1,21 @@
import { Request, Response } from "express";
// Example request:
// {
// "action_type": "login",
// "api_name": "/shield/api/login",
// "username": "test"
// }
export default function handle(req: Request, res: Response) {
// Test handler
res.send({
retcode: 0,
message: "OK",
data: {
id: "",
action: "ACTION_NONE",
geetest: null
}
});
}

View File

@ -0,0 +1,9 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
res.send({
data: null,
message: "RetCode_NoConfig",
retcode: 7
});
}

View File

@ -0,0 +1,10 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
res.send({
retcode: 0,
success: true,
message: "",
data: []
});
}

View File

@ -0,0 +1,14 @@
import { Request, Response } from "express";
import Logger, { VerboseLevel } from "../../../util/Logger";
const c = new Logger("dataUpload", "green");
export default function handle(req: Request, res: Response) {
try {
const content = req.body[0].uploadContent;
if (content.LogStr) {
c.warn(content.LogStr);
if (Logger.VERBOSE_LEVEL == VerboseLevel.ALL) c.trail(content.StackTrace);
}
} catch { }
res.send({ code: 0 });
}

View File

@ -0,0 +1,24 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
// Test handler
res.send({
retcode: 0,
message: "OK",
data: {
modified: true,
protocol: {
id: 0,
app_id: 11,
language: "en",
user_proto: "",
priv_proto: "",
major: 1,
minimum: 2,
create_time: "0",
teenager_proto: "",
third_proto: ""
}
}
});
}

View File

@ -0,0 +1,17 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
res.send({
retcode: 0,
message: "OK",
data: {
protocol: true,
qr_enabled: true,
log_level: "INFO",
announce_url: "https://sdk.hoyoverse.com/hkrpg/announcement/index.html?sdk_presentation_style=fullscreen\u0026sdk_screen_transparent=true\u0026auth_appid=announcement\u0026authkey_ver=1\u0026sign_type=2#/",
push_alias_type: 0,
disable_ysdk_guard: true,
enable_announce_pic_popup: true
}
});
}

View File

@ -0,0 +1,17 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
res.send({
retcode: 0,
message: "OK",
data: {
combo_id: "0",
open_id: "",
combo_token: "",
data: '{"guest":false}',
heartbeat: false,
account_type: 1,
fatigue_remind: null
}
});
}

View File

@ -0,0 +1,11 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
res.send({
retcode: 0,
message: "OK",
data: {
marketing_agreements: []
}
});
}

View File

@ -0,0 +1,29 @@
import { Request, Response } from "express";
export default function handle(req: Request, res: Response) {
res.send({
retcode: 0,
message: "OK",
data: {
id: 24,
game_key: "hkrpg_global",
client: "PC",
identity: "I_IDENTITY",
guest: false,
ignore_versions: "",
scene: "S_NORMAL",
name: "崩坏RPG",
disable_regist: true,
enable_email_captcha: false,
thirdparty: [],
disable_mmt: false,
server_guest: true,
thirdparty_ignore: {
fb: "",
tw: ""
},
enable_ps_bind_account: false,
thirdparty_login_configs: {}
}
});
}

View File

@ -0,0 +1,32 @@
import { Request, Response } from "express";
import Account from "../../../../../../db/Account";
import Logger from "../../../../../../util/Logger";
// Example request:
// {
// account: "test",
// (RSA)password: "BKWPZjqKfKr6ZKuO40ONwV5JxOi4dg71aeBcxPVK/U+8FM8d5kc5EjLdEXyn6McBvUOL67CmT89eo9jrdwp9xpFexA/C1d9BCxen0NQ+zCrQUkSc6AFD9PYkAmdTNnila5L15SrveQQRtbsDwZeZ9owVH7kyoXuDGUOOA6dc4qE=",
// is_crypto: true
// }
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,
message: "OK",
data: {
account: {}
}
}
if (!acc) {
dataObj.retcode = -202;
dataObj.message = "Account not found";
c.warn(`[DISPATCH] Player ${req.body.account} not found`);
res.send(dataObj);
} else {
dataObj.data.account = acc;
c.log(`[DISPATCH] Player ${req.body.account} logged in`);
res.send(dataObj);
}
}

View File

@ -0,0 +1,35 @@
import { Request, Response } from "express";
import Account from "../../../../../../db/Account";
import Logger from "../../../../../../util/Logger";
// 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,
message: "OK",
data: {
account: {}
}
}
if (!acc) {
dataObj.retcode = -202;
dataObj.message = "Account not found";
res.send(dataObj);
c.warn(`[DISPATCH] Player ${req.body.uid} not found`);
} else {
if (acc.token === req.body.token) {
dataObj.data.account = acc;
c.log(`[DISPATCH] Player ${req.body.uid} logged in`);
res.send(dataObj);
} else {
dataObj.retcode = -202;
dataObj.message = "Invalid token";
res.send(dataObj);
}
}
}

View File

@ -0,0 +1,27 @@
import { Request, Response } from "express";
import Config from "../../util/Config";
interface Region {
dispatch_url: string;
env_type: string;
name: string;
title: string;
}
export default function handle(req: Request, res: Response) {
const dataObj = {
region_list: [] as Region[],
retcode: 0
}
Config.DISPATCH.forEach(item => {
dataObj.region_list.push({
dispatch_url: item.DISPATCH_URL,
env_type: "2",
name: item.DISPATCH_NAME,
title: "CrepeSR"
});
});
res.send(dataObj);
}

View File

@ -0,0 +1,36 @@
import { Request, Response } from "express";
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;
export default function handle(req: Request, res: Response) {
const dataObj = {
retcode: 0,
msg: "OK",
regionName: "CrepeSR",
gateserverIp: Config.GAMESERVER.SERVER_IP,
gateserverPort: Config.GAMESERVER.SERVER_PORT,
} as any;
if (Config.GAMESERVER.MAINTENANCE) {
dataObj.retcode = 2;
dataObj.msg = Config.GAMESERVER.MAINTENANCE_MSG;
dataObj.stopBeginTime = Date.now();
dataObj.stopEndTime = Date.now() * 2;
}
let rsp;
try {
rsp = proto!.encode(dataObj).finish();
} catch {
rsp = proto!.encode({
retcode: 2,
msg: "Internal server error",
stopBeginTime: Date.now(),
stopEndTime: Date.now() * 2,
}).finish();
}
res.send(Buffer.from(rsp).toString('base64'));
}

View File

@ -0,0 +1,7 @@
import { Request, Response } from "express";
import Logger from "../../../util/Logger";
const c = new Logger("dataUpload", "green");
export default function handle(req: Request, res: Response) {
res.send({ code: 0 });
}

View File

@ -3,4 +3,11 @@
* @author Crepe-Inc
* @license AGPL-3.0
*/
import Interface from "./commands/Interface";
import HttpServer from "./http/HttpServer";
import Logger from "./util/Logger";
const c = new Logger("CrepeSR");
c.log(`Starting CrepeSR...`);
Interface.start();
const http = new HttpServer();

View File

@ -0,0 +1,38 @@
// Copyright (C) 2022 CrepeSR
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
syntax = "proto3";
message QueryCurrRegionHttpRsp {
uint32 retcode = 1; // Always present
string msg = 2; // Always present
string region_name = 3; // Always present
string gateserver_ip = 4;
uint32 gateserver_port = 5;
int64 stop_begin_time = 6;
int64 stop_end_time = 7;
bool unknown_boolean_one = 8;
bool unknown_boolean_two = 9;
string resource_url = 10;
string data_url = 11;
string lua_url = 12;
uint32 unknown_int_one = 15;
string client_secret_key = 17;
string region_config = 18;
string feedback_url = 20;
string beta_text = 24; // "当前版本为测试版本,不代表游戏最终品质"
bool unknown_boolean_three = 25;
bool unknown_boolean_four = 26;
}

85
src/util/Config.ts Normal file
View File

@ -0,0 +1,85 @@
import fs from 'fs';
import { resolve } from 'path';
import { VerboseLevel } from './Logger';
const DEFAULT_CONFIG = {
// General
VERBOSE_LEVEL: 1,
// MongoDB
MONGO_URI: "mongodb://localhost:27017/crepesr",
// HTTP
HTTP: {
HTTP_HOST: "0.0.0.0",
HTTP_PORT: 443
},
// Dispatch
DISPATCH: [{
DISPATCH_NAME: "CrepeSR",
DISPATCH_URL: "http://localhost/query_gateway"
}],
// GameServer
GAMESERVER: {
SERVER_IP: "0.0.0.0",
SERVER_PORT: 22102,
MAINTENANCE: false,
MAINTENANCE_MSG: "Server is in maintenance mode."
}
}
type DefaultConfig = typeof DEFAULT_CONFIG;
function r(...args: string[]) {
return fs.readFileSync(resolve(__dirname, ...args)).toString();
}
function readConfig(): any {
let config: DefaultConfig;
try {
config = JSON.parse(r('../../config.json'));
// Check if config object.keys is the same as DEFAULT_CONFIG.keys
const missing = Object.keys(DEFAULT_CONFIG).filter(key => !config.hasOwnProperty(key));
if (missing.length > 0) {
missing.forEach(key => {
// @ts-ignore
config[key] = DEFAULT_CONFIG[key];
});
updateConfig(config);
console.log(`Added missing config keys: ${missing.join(', ')}`);
}
} catch {
console.error("Could not read config file. Creating one for you...");
config = DEFAULT_CONFIG;
updateConfig(config);
}
return config;
}
function updateConfig(config: any) {
fs.writeFileSync('./config.json', JSON.stringify(config, null, 2));
}
export default class Config {
public static config = readConfig();
public static VERBOSE_LEVEL: VerboseLevel = Config.config.VERBOSE_LEVEL;
public static MONGO_URI: string = Config.config.MONGO_URI;
public static HTTP: {
HTTP_HOST: string,
HTTP_PORT: number
} = Config.config.HTTP;
public static DISPATCH: {
DISPATCH_NAME: string;
DISPATCH_URL: string;
}[] = Config.config.DISPATCH;
public static GAMESERVER: {
SERVER_IP: string;
SERVER_PORT: number;
MAINTENANCE: boolean;
MAINTENANCE_MSG: string;
} = Config.config.GAMESERVER;
private constructor() { }
}

53
src/util/Logger.ts Normal file
View File

@ -0,0 +1,53 @@
import 'colorts/lib/string';
import Config from './Config';
export enum VerboseLevel {
NONE = 0, // No logging except for errors
WARNS = 1, // Log warns
ALL = 2, // Warns and (useless) debug
}
type Color = 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white' | 'gray' | 'black' | 'italic' | 'bold' | 'underline' | 'strikethrough' | 'inverse' | 'bgRed' | 'bgGreen' | 'bgYellow' | 'bgBlue' | 'bgMagenta' | 'bgCyan' | 'bgWhite' | 'bgBlack' | 'bgGray' | 'bgItalic';
export default class Logger {
public static VERBOSE_LEVEL: VerboseLevel = Config.VERBOSE_LEVEL || 1;
constructor(public name: string, public color: Color = 'cyan') {
this.name = name;
this.color = color;
}
private getDate(): string {
return new Date().toLocaleTimeString();
}
private raw(...args: string[]) {
// @ts-ignore - Element implicitly has an 'any' type because index expression is not of type 'number'
console.log(`[${this.getDate().white.bold}] <${this.name[this.color].bold}>`, ...args);
}
public log(...args: string[]) {
this.raw(...args);
}
public trail(...args: any[]) {
console.log(`\t↳ ${args.join(' ').gray}`);
}
public error(e: Error | string) {
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);
}
public warn(...args: string[]) {
if (Logger.VERBOSE_LEVEL < VerboseLevel.WARNS) return;
console.log(`[${this.getDate().white.bold}] ${`WARN<${this.name}>`.bgYellow.bold}`, ...args);
}
public debug(...args: any) {
if (Logger.VERBOSE_LEVEL < VerboseLevel.ALL) return;
console.log(`[${this.getDate().white.bold}] ${`DEBUG<${this.name}>`.bgBlue.bold}`, ...args);
this.trail(new Error().stack!.split('\n').slice(2).join('\n'));
}
}