From f427252a5e286581bcf3dd27644ff33484c81dc4 Mon Sep 17 00:00:00 2001 From: GEEKiDoS Date: Thu, 11 Apr 2024 15:20:36 +0800 Subject: [PATCH] add api stuff --- package-lock.json | 84 ++ package.json | 4 + src/apis/index.ts | 13 + src/apis/p2d/index.ts | 68 ++ src/{services => controllers}/default.ts | 0 src/{services => controllers}/p2d/data.json | 861 +++++++------------ src/{services => controllers}/p2d/game.ts | 0 src/{services => controllers}/p2d/index.ts | 6 +- src/{services => controllers}/p2d/service.ts | 0 src/controllers/p2d/user.ts | 315 +++++++ src/database/index.ts | 12 + src/decorators/eacnet.ts | 151 +--- src/main.ts | 41 +- src/middlewares/eacnet.ts | 118 +-- src/services/p2d/user.ts | 448 +++------- src/types.ts | 2 + src/utils/kbinxml.ts | 223 +++++ src/utils/sha256.ts | 7 + 18 files changed, 1194 insertions(+), 1159 deletions(-) create mode 100644 src/apis/index.ts create mode 100644 src/apis/p2d/index.ts rename src/{services => controllers}/default.ts (100%) rename src/{services => controllers}/p2d/data.json (89%) rename src/{services => controllers}/p2d/game.ts (100%) rename src/{services => controllers}/p2d/index.ts (87%) rename src/{services => controllers}/p2d/service.ts (100%) create mode 100644 src/controllers/p2d/user.ts create mode 100644 src/utils/kbinxml.ts create mode 100644 src/utils/sha256.ts diff --git a/package-lock.json b/package-lock.json index 230fc45..071a612 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@cordisjs/logger": "^0.1.3", "@kamyu/kbinxml": "^2.0.2", + "@koa/cors": "^5.0.0", + "@koa/router": "^12.0.1", "arc4": "^2.1.4", "dedent-js": "^1.0.1", "dotenv": "^16.4.5", @@ -27,6 +29,8 @@ "devDependencies": { "@types/jest": "~29.5", "@types/koa": "^2.13.12", + "@types/koa__cors": "^5.0.0", + "@types/koa__router": "^12.0.4", "@types/koa-bodyparser": "^4.3.12", "@types/lodash": "^4.14.202", "@types/node": "~20", @@ -1744,6 +1748,55 @@ "resolved": "https://registry.npmmirror.com/@kamyu/kbinxml/-/kbinxml-2.0.2.tgz", "integrity": "sha512-41ZiqAunpiwF0yu4LtWcYyF4BIhRGrOPMRVe4eAXWQ1KRL1cZI0+XGqnQIIxlhTcfBMDMLUnrmqpXRAh1sxhPQ==" }, + "node_modules/@koa/cors": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@koa/cors/-/cors-5.0.0.tgz", + "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==", + "dependencies": { + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@koa/router": { + "version": "12.0.1", + "resolved": "https://registry.npmmirror.com/@koa/router/-/router-12.0.1.tgz", + "integrity": "sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==", + "dependencies": { + "debug": "^4.3.4", + "http-errors": "^2.0.0", + "koa-compose": "^4.1.0", + "methods": "^1.1.2", + "path-to-regexp": "^6.2.1" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@koa/router/node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@koa/router/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/@mongodb-js/saslprep": { "version": "1.1.5", "resolved": "https://registry.npmmirror.com/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", @@ -2047,6 +2100,24 @@ "@types/node": "*" } }, + "node_modules/@types/koa__cors": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/@types/koa__cors/-/koa__cors-5.0.0.tgz", + "integrity": "sha512-LCk/n25Obq5qlernGOK/2LUwa/2YJb2lxHUkkvYFDOpLXlVI6tKcdfCHRBQnOY4LwH6el5WOLs6PD/a8Uzau6g==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, + "node_modules/@types/koa__router": { + "version": "12.0.4", + "resolved": "https://registry.npmmirror.com/@types/koa__router/-/koa__router-12.0.4.tgz", + "integrity": "sha512-Y7YBbSmfXZpa/m5UGGzb7XadJIRBRnwNY9cdAojZGp65Cpe5MAP3mOZE7e3bImt8dfKS4UFcR16SLH8L/z7PBw==", + "dev": true, + "dependencies": { + "@types/koa": "*" + } + }, "node_modules/@types/koa-bodyparser": { "version": "4.3.12", "resolved": "https://registry.npmmirror.com/@types/koa-bodyparser/-/koa-bodyparser-4.3.12.tgz", @@ -5332,6 +5403,14 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -5704,6 +5783,11 @@ "node": "14 || >=16.14" } }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", diff --git a/package.json b/package.json index 4d567f2..f230b2e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "devDependencies": { "@types/jest": "~29.5", "@types/koa": "^2.13.12", + "@types/koa__cors": "^5.0.0", + "@types/koa__router": "^12.0.4", "@types/koa-bodyparser": "^4.3.12", "@types/lodash": "^4.14.202", "@types/node": "~20", @@ -45,6 +47,8 @@ "dependencies": { "@cordisjs/logger": "^0.1.3", "@kamyu/kbinxml": "^2.0.2", + "@koa/cors": "^5.0.0", + "@koa/router": "^12.0.1", "arc4": "^2.1.4", "dedent-js": "^1.0.1", "dotenv": "^16.4.5", diff --git a/src/apis/index.ts b/src/apis/index.ts new file mode 100644 index 0000000..78d2f5d --- /dev/null +++ b/src/apis/index.ts @@ -0,0 +1,13 @@ +import Router from "@koa/router"; +import { p2dRoutes } from "./p2d/index.js"; +import { DefaultContext, DefaultState, ParameterizedContext } from "koa"; +import { ILaochanContext } from "../types.js"; + +const router = new Router({ + prefix: '/api', +}) + .use(p2dRoutes); + +export const apiRoutes = router.routes(); +export type Context = ParameterizedContext, any>; + diff --git a/src/apis/p2d/index.ts b/src/apis/p2d/index.ts new file mode 100644 index 0000000..11ef9da --- /dev/null +++ b/src/apis/p2d/index.ts @@ -0,0 +1,68 @@ +import { Logger } from "@cordisjs/logger"; +import Router from "@koa/router"; +import { Context } from "../index.js"; +import { UserService } from "../../services/p2d/user.js"; +import { PlayerCustomizeSetting } from "../../database/index.js"; + +const logger = new Logger('p2d-api'); +const router = new Router({ + prefix: '/p2d', +}) + + .get('/pdata/:token', async (ctx: Context) => { + const { token } = ctx.params; + + if (!token) { + ctx.status = 400; + return; + } + + logger.info('request pdata %s', token); + const pdata = await ctx.resolve(UserService).getPDataDecoded(token); + + ctx.status = pdata ? 200 : 404; + ctx.body = pdata ? pdata : {}; + }) + + .get('/customize/:token', async (ctx: Context) => { + const { token } = ctx.params; + + if (!token) { + ctx.status = 400; + return; + } + + logger.info('request customize %s', token); + const customize = await ctx.resolve(UserService).getCustomizeSetting(token); + + ctx.status = 200; + ctx.body = { + customize, + }; + }) + + .put('/customize/:token', async (ctx: Context) => { + const { token } = ctx.params; + + if (!token) { + ctx.status = 400; + return; + } + + logger.info('set customize %s', token); + const customize = ctx.request.body as PlayerCustomizeSetting; + + if (!customize) { + ctx.status = 400; + return; + } + + customize._id = token; + await ctx.resolve(UserService).upsertCustomizeSetting(customize); + + ctx.status = 200; + ctx.body = { success: true }; + }); + + +export const p2dRoutes = router.routes(); diff --git a/src/services/default.ts b/src/controllers/default.ts similarity index 100% rename from src/services/default.ts rename to src/controllers/default.ts diff --git a/src/services/p2d/data.json b/src/controllers/p2d/data.json similarity index 89% rename from src/services/p2d/data.json rename to src/controllers/p2d/data.json index bccf76a..595be90 100644 --- a/src/services/p2d/data.json +++ b/src/controllers/p2d/data.json @@ -1,555 +1,316 @@ { - "customize": { - "2": [ - "I1100000", - "I1100001", - "I1100002", - "I1100003", - "I1100004", - "I1100005", - "I1100006", - "I1100007", - "I1100008", - "I1100009", - "I1100010", - "I1100011", - "I1100012", - "I1100013", - "I1100014", - "I1100015", - "I1100016", - "I1100017", - "I1100018", - "I1100019", - "I1100020" - ], - "3": [ - "I1200000", - "I1200001", - "I1200002", - "I1200003", - "I1200004", - "I1200005", - "I1200006", - "I1200007", - "I1200008", - "I1200009", - "I1200010", - "I1200011", - "I1200012", - "I1200013", - "I1200014", - "I1200015", - "I1200016", - "I1200017", - "I1200018", - "I1200019", - "I1200020", - "I1200021" - ], - "4": [ - "I1300000", - "I1300001", - "I1300002", - "I1300003", - "I1300004", - "I1300005", - "I1300006", - "I1300007", - "I1300008", - "I1300009", - "I1300010", - "I1300011", - "I1300012", - "I1300013", - "I1300014", - "I1300015", - "I1300016", - "I1300017", - "I1300018", - "I1300019", - "I1300020", - "I1300021", - "I1300022", - "I1300023", - "I1300024", - "I1300025", - "I1300026", - "I1300027", - "I1300028", - "I1300029", - "I1300030", - "I1300031", - "I1300032", - "I1300033", - "I1300034", - "I1300035", - "I1300036", - "I1300037", - "I1300038", - "I1300039", - "I1300040", - "I1300041", - "I1300042", - "I1300043", - "I1300044", - "I1300045", - "I1300046", - "I1300047", - "I1300048", - "I1300049", - "I1300050", - "I1300051", - "I1300052", - "I1300053", - "I1300054", - "I1300055", - "I1300056", - "I1300057", - "I1300058", - "I1300059", - "I1300060", - "I1300061", - "I1300062", - "I1300063", - "I1300064", - "I1300065", - "I1300066", - "I1300067", - "I1300068", - "I1300069", - "I1300070", - "I1300071", - "I1300072", - "I1300073", - "I1300074", - "I1300075", - "I1300076", - "I1300077", - "I1300078", - "I1300079", - "I1300080", - "I1300081", - "I1300082", - "I1300083", - "I1300084", - "I1300085", - "I1300086", - "I1300087", - "I1300088", - "I1300089", - "I1300090", - "I1300091", - "I1300092", - "I1300093", - "I1300094", - "I1300095", - "I1300096", - "I1300097", - "I1300098", - "I1300099", - "I1300100", - "I1300101", - "I1300102", - "I1300103", - "I1300104", - "I1300105", - "I1300106", - "I1300107", - "I1300108", - "I1300109", - "I1300110", - "I1300111", - "I1300112", - "I1300113", - "I1300114", - "I1300115", - "I1300116", - "I1300117", - "I1300118", - "I1300119", - "I1300120", - "I1300121", - "I1300122", - "I1300123", - "I1300124", - "I1300125", - "I1300126", - "I1300127", - "I1300128", - "I1300129", - "I1300130", - "I1300131", - "I1300132", - "I1300133", - "I1300134", - "I1300135", - "I1300136", - "I1300137", - "I1300138", - "I1300139", - "I1300140", - "I1300141", - "I1300142", - "I1300143", - "I1300144", - "I1300145", - "I1300146", - "I1300147", - "I1300148", - "I1300149", - "I1300150", - "I1300151", - "I1300152", - "I1300153", - "I1300154", - "I1300155", - "I1300156", - "I1300157", - "I1300158", - "I1300159", - "I1300160", - "I1300161", - "I1300162", - "I1300163", - "I1300164", - "I1300165", - "I1300166", - "I1300167", - "I1300168", - "I1300169", - "I1300170", - "I1300171", - "I1300172", - "I1300173", - "I1300174", - "I1300175", - "I1300176", - "I1300177", - "I1300178", - "I1300179", - "I1300180", - "I1300181", - "I1300182", - "I1300183", - "I1300184", - "I1300185", - "I1300186" - ], - "5": [ - "I1400000", - "I1400001", - "I1400002", - "I1400003", - "I1400004", - "I1400005", - "I1400006", - "I1400007", - "I1400008", - "I1400009", - "I1400010", - "I1400011", - "I1400012", - "I1400013", - "I1400014" - ], - "6": [ - "I1500000", - "I1500001", - "I1500002", - "I1500003", - "I1500004", - "I1500005", - "I1500006", - "I1500007", - "I1500008", - "I1500009", - "I1500010", - "I1500011" - ], - "7": [ - "I1600000", - "I1600001", - "I1600002", - "I1600003", - "I1600004", - "I1600005", - "I1600006", - "I1600007", - "I1600008", - "I1600009", - "I1600010", - "I1600011", - "I1600012", - "I1600013", - "I1600014", - "I1600015", - "I1600016", - "I1600017", - "I1600018" - ], - "8": [ - "I1700000", - "I1700001", - "I1700002", - "I1700003", - "I1700004", - "I1700005", - "I1700006", - "I1700007", - "I1700008", - "I1700009", - "I1700010", - "I1700011" - ], - "11": [ - "I1300000", - "I1300001", - "I1300002", - "I1300003", - "I1300004", - "I1300005", - "I1300006", - "I1300007", - "I1300008", - "I1300009", - "I1300010", - "I1300011", - "I1300012", - "I1300013", - "I1300014", - "I1300015", - "I1300016", - "I1300017", - "I1300018", - "I1300019", - "I1300020", - "I1300021", - "I1300022", - "I1300023", - "I1300024", - "I1300025", - "I1300026", - "I1300027", - "I1300028", - "I1300029", - "I1300030", - "I1300031", - "I1300032", - "I1300033", - "I1300034", - "I1300035", - "I1300036", - "I1300037", - "I1300038", - "I1300039", - "I1300040", - "I1300041", - "I1300042", - "I1300043", - "I1300044", - "I1300045", - "I1300046", - "I1300047", - "I1300048", - "I1300049", - "I1300050", - "I1300051", - "I1300052", - "I1300053", - "I1300054", - "I1300055", - "I1300056", - "I1300057", - "I1300058", - "I1300059", - "I1300060", - "I1300061", - "I1300062", - "I1300063", - "I1300064", - "I1300065", - "I1300066", - "I1300067", - "I1300068", - "I1300069", - "I1300070", - "I1300071", - "I1300072", - "I1300073", - "I1300074", - "I1300075", - "I1300076", - "I1300077", - "I1300078", - "I1300079", - "I1300080", - "I1300081", - "I1300082", - "I1300083", - "I1300084", - "I1300085", - "I1300086", - "I1300087", - "I1300088", - "I1300089", - "I1300090", - "I1300091", - "I1300092", - "I1300093", - "I1300094", - "I1300095", - "I1300096", - "I1300097", - "I1300098", - "I1300099", - "I1300100", - "I1300101", - "I1300102", - "I1300103", - "I1300104", - "I1300105", - "I1300106", - "I1300107", - "I1300108", - "I1300109", - "I1300110", - "I1300111", - "I1300112", - "I1300113", - "I1300114", - "I1300115", - "I1300116", - "I1300117", - "I1300118", - "I1300119", - "I1300120", - "I1300121", - "I1300122", - "I1300123", - "I1300124", - "I1300125", - "I1300126", - "I1300127", - "I1300128", - "I1300129", - "I1300130", - "I1300131", - "I1300132", - "I1300133", - "I1300134", - "I1300135", - "I1300136", - "I1300137", - "I1300138", - "I1300139", - "I1300140", - "I1300141", - "I1300142", - "I1300143", - "I1300144", - "I1300145", - "I1300146", - "I1300147", - "I1300148", - "I1300149", - "I1300150", - "I1300151", - "I1300152", - "I1300153", - "I1300154", - "I1300155", - "I1300156", - "I1300157", - "I1300158", - "I1300159", - "I1300160", - "I1300161", - "I1300162", - "I1300163", - "I1300164", - "I1300165", - "I1300166", - "I1300167", - "I1300168", - "I1300169", - "I1300170", - "I1300171", - "I1300172", - "I1300173", - "I1300174", - "I1300175", - "I1300176", - "I1300177", - "I1300178", - "I1300179", - "I1300180", - "I1300181", - "I1300182", - "I1300183", - "I1300184", - "I1300185", - "I1300186" - ], - "10": [ - "I1900000", - "I1900001", - "I1900002", - "I1900003", - "I1900004", - "I1900005", - "I1900006", - "I1900007", - "I1900008", - "I1900009", - "I1900010", - "I1900011", - "I1900012", - "I1900013", - "I1900014", - "I1900015", - "I1900016", - "I1900017", - "I1900018", - "I1900019", - "I1900020", - "I1900021", - "I1900022" - ] - }, - "other_customize": { - "1": [ - "C1000000", - "C1000001", - "C1000002" - ], - "2": [ - "C1100000", - "C1100001", - "C1100002", - "C1100003", - "C1100004", - "C1100005", - "C1100006" - ], - "3": [ - "C1200000", - "C1200001" - ], - "4": [ - "C1300000", - "C1300001", - "C1300002", - "C1300003" - ], - "5": [ - "C1400000", - "C1400001", - "C1400002", - "C1400003" - ] - }, "items": [ + "I1700000", + "I1700001", + "I1700002", + "I1700003", + "I1700004", + "I1700005", + "I1700006", + "I1700007", + "I1700008", + "I1700009", + "I1700010", + "I1700011", + "I1500000", + "I1500001", + "I1500002", + "I1500003", + "I1500004", + "I1500005", + "I1500006", + "I1500007", + "I1500008", + "I1500009", + "I1500010", + "I1500011", + "I1400000", + "I1400001", + "I1400002", + "I1400003", + "I1400004", + "I1400005", + "I1400006", + "I1400007", + "I1400008", + "I1400009", + "I1400010", + "I1400011", + "I1400012", + "I1400013", + "I1400014", + "I1300000", + "I1300001", + "I1300002", + "I1300003", + "I1300004", + "I1300005", + "I1300006", + "I1300007", + "I1300008", + "I1300009", + "I1300010", + "I1300011", + "I1300012", + "I1300013", + "I1300014", + "I1300015", + "I1300016", + "I1300017", + "I1300018", + "I1300019", + "I1300020", + "I1300021", + "I1300022", + "I1300023", + "I1300024", + "I1300025", + "I1300026", + "I1300027", + "I1300028", + "I1300029", + "I1300030", + "I1300031", + "I1300032", + "I1300033", + "I1300034", + "I1300035", + "I1300036", + "I1300037", + "I1300038", + "I1300039", + "I1300040", + "I1300041", + "I1300042", + "I1300043", + "I1300044", + "I1300045", + "I1300046", + "I1300047", + "I1300048", + "I1300049", + "I1300050", + "I1300051", + "I1300052", + "I1300053", + "I1300054", + "I1300055", + "I1300056", + "I1300057", + "I1300058", + "I1300059", + "I1300060", + "I1300061", + "I1300062", + "I1300063", + "I1300064", + "I1300065", + "I1300066", + "I1300067", + "I1300068", + "I1300069", + "I1300070", + "I1300071", + "I1300072", + "I1300073", + "I1300074", + "I1300075", + "I1300076", + "I1300077", + "I1300078", + "I1300079", + "I1300080", + "I1300081", + "I1300082", + "I1300083", + "I1300084", + "I1300085", + "I1300086", + "I1300087", + "I1300088", + "I1300089", + "I1300090", + "I1300091", + "I1300092", + "I1300093", + "I1300094", + "I1300095", + "I1300096", + "I1300097", + "I1300098", + "I1300099", + "I1300100", + "I1300101", + "I1300102", + "I1300103", + "I1300104", + "I1300105", + "I1300106", + "I1300107", + "I1300108", + "I1300109", + "I1300110", + "I1300111", + "I1300112", + "I1300113", + "I1300114", + "I1300115", + "I1300116", + "I1300117", + "I1300118", + "I1300119", + "I1300120", + "I1300121", + "I1300122", + "I1300123", + "I1300124", + "I1300125", + "I1300126", + "I1300127", + "I1300128", + "I1300129", + "I1300130", + "I1300131", + "I1300132", + "I1300133", + "I1300134", + "I1300135", + "I1300136", + "I1300137", + "I1300138", + "I1300139", + "I1300140", + "I1300141", + "I1300142", + "I1300143", + "I1300144", + "I1300145", + "I1300146", + "I1300147", + "I1300148", + "I1300149", + "I1300150", + "I1300151", + "I1300152", + "I1300153", + "I1300154", + "I1300155", + "I1300156", + "I1300157", + "I1300158", + "I1300159", + "I1300160", + "I1300161", + "I1300162", + "I1300163", + "I1300164", + "I1300165", + "I1300166", + "I1300167", + "I1300168", + "I1300169", + "I1300170", + "I1300171", + "I1300172", + "I1300173", + "I1300174", + "I1300175", + "I1300176", + "I1300177", + "I1300178", + "I1300179", + "I1300180", + "I1300181", + "I1300182", + "I1300183", + "I1300184", + "I1300185", + "I1300186", + "I1200000", + "I1200001", + "I1200002", + "I1200003", + "I1200004", + "I1200005", + "I1200006", + "I1200007", + "I1200008", + "I1200009", + "I1200010", + "I1200011", + "I1200012", + "I1200013", + "I1200014", + "I1200015", + "I1200016", + "I1200017", + "I1200018", + "I1200019", + "I1200020", + "I1200021", + "I1100000", + "I1100001", + "I1100002", + "I1100003", + "I1100004", + "I1100005", + "I1100006", + "I1100007", + "I1100008", + "I1100009", + "I1100010", + "I1100011", + "I1100012", + "I1100013", + "I1100014", + "I1100015", + "I1100016", + "I1100017", + "I1100018", + "I1100019", + "I1100020", + "I1900000", + "I1900001", + "I1900002", + "I1900003", + "I1900004", + "I1900005", + "I1900006", + "I1900007", + "I1900008", + "I1900009", + "I1900010", + "I1900011", + "I1900012", + "I1900013", + "I1900014", + "I1900015", + "I1900016", + "I1900017", + "I1900018", + "I1900019", + "I1900020", + "I1900021", + "I1900022", + "I1600000", + "I1600001", + "I1600002", + "I1600003", + "I1600004", + "I1600005", + "I1600006", + "I1600007", + "I1600008", + "I1600009", + "I1600010", + "I1600011", + "I1600012", + "I1600013", + "I1600014", + "I1600015", + "I1600016", + "I1600017", + "I1600018", "I1000000", "I1000001", "I1800001", diff --git a/src/services/p2d/game.ts b/src/controllers/p2d/game.ts similarity index 100% rename from src/services/p2d/game.ts rename to src/controllers/p2d/game.ts diff --git a/src/services/p2d/index.ts b/src/controllers/p2d/index.ts similarity index 87% rename from src/services/p2d/index.ts rename to src/controllers/p2d/index.ts index 948e5ea..3904a35 100644 --- a/src/services/p2d/index.ts +++ b/src/controllers/p2d/index.ts @@ -4,7 +4,7 @@ import { Service } from "./service.js"; import { User } from "./user.js"; import data from './data.json' with { type: "json" }; import { inject, singleton } from "tsyringe"; -import { Db } from "mongodb"; +import { UserService } from "../../services/p2d/user.js"; function Combine(...modules: ({ new(): any })[]) { class Modules { @@ -31,11 +31,11 @@ function Combine(...modules: ({ new(): any })[]) { @singleton() export default class extends Combine(Service, User, Game) { constructor( - @inject(Db) private readonly db: Db, + @inject(UserService) private readonly userService: UserService, ) { super(); - this.db; + this.userService; } } diff --git a/src/services/p2d/service.ts b/src/controllers/p2d/service.ts similarity index 100% rename from src/services/p2d/service.ts rename to src/controllers/p2d/service.ts diff --git a/src/controllers/p2d/user.ts b/src/controllers/p2d/user.ts new file mode 100644 index 0000000..dfc9c93 --- /dev/null +++ b/src/controllers/p2d/user.ts @@ -0,0 +1,315 @@ +import { eacnet } from "../../decorators/eacnet.js"; +import { Context } from "../../types.js"; +import { v } from "../../utils/kxml-value.js"; +import { ITEM_LIST, MUSIC_LIST } from "./index.js"; +import { PlayerPlayLog } from "../../database/index.js"; +import { fromToken } from "../../utils/laochan-id.js"; +import { UserService } from "../../services/p2d/user.js"; + +export class User { + userService: UserService; + + + @eacnet('p2d') + async sendGradeCertificationLog(ctx: Context) { + await this.userService.addCourseLog({ + player: ctx.token, + ...ctx.body, + }); + + return { + status: v.s32(0), + error: v.s32(0), + } + } + + @eacnet('p2d') + async consumeItem() { + return { + status: v.s32(0), + error: v.s32(0), + result: { + free_count: v.s32(1000), + not_free_count: v.s32(1000), + } + }; + } + + @eacnet('p2d') + async reserveConsumeItem() { + return { + status: v.s32(0), + error: v.s32(0), + }; + } + + @eacnet('p2d') + async getCustomizeSetting(ctx: Context) { + const { customize, other_customize } = await this.userService.getCustomizeSetting(ctx.token); + + return { + status: v.s32(0), + error: v.s32(0), + result: { + customize: customize.map(c => ({ + item_category: v.s32(c.item_category), + item_id: v.str(c.item_id), + })), + other_customize: other_customize.map(c => ({ + item_category: v.s32(c.item_category), + item_id: v.str(c.item_id), + })), + } + } + } + + @eacnet('p2d') + async gameEnd() { + return { + status: v.s32(0), + error: v.s32(0), + } + } + + @eacnet('p2d') + async getPointList() { + return { + status: v.s32(0), + error: v.s32(0), + result: { + point_count: v.s32(1), + point: [{ + point_id: v.str('P0100000'), + point_num: v.u32(114514), + }] + } + } + } + + @eacnet('p2d') + async getPrivilegeClient() { + return { + status: v.s32(0), + error: v.s32(0), + result: { + list_num: v.s32(0), + } + } + } + + @eacnet('p2d') + async getPrivilegeServer() { + return { + status: v.s32(0), + error: v.s32(0), + result: { + list_num: v.s32(0), + } + } + } + + @eacnet('p2d') + async reportMusicResult(ctx: Context) { + const result = ctx.body as PlayerPlayLog; + result.player = ctx.token; + + let updateTask = Promise.resolve(); + + const play_style = Math.floor(result.note_id / 5); + const diff = result.note_id % 5; + + updateTask = (async () => { + const musicData = await this.userService.getMusicData(result.player, result.music_id, play_style); + + if (result.clear_flag > musicData.clear_flag[diff]) + musicData.clear_flag[diff] = result.clear_flag; + + if (result.miss_count < musicData.miss_count[diff]) + musicData.miss_count[diff] = result.miss_count; + + if (result.score > musicData.score[diff]) + musicData.score[diff] = result.score; + + if (result.clear_flag >= 2) + musicData.clear_num[diff]++; + + musicData.play_num[diff]++; + + this.userService.upsertMusicData(musicData); + })(); + + await Promise.all([ + this.userService.addPlayLog(result), + updateTask, + ]) + + return { + status: v.s32(0), + error: v.s32(0), + }; + } + + @eacnet('p2d') + async savePlayData(ctx: Context) { + const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string }; + + await this.userService.upsertPDataBinary(ctx.token, pdata, check_sum); + + return { + status: v.s32(0), + error: v.s32(0), + } + } + + @eacnet('p2d') + async getPlayData(ctx: Context) { + const result = await this.userService.getPDataBinary(ctx.token); + + if (!result) { + return { + status: v.s32(1), + error: v.s32(404), + } + } + + return { + status: v.s32(0), + error: v.s32(0), + result: { + size: v.s32(result.pdata.length), + rest_size: v.s32(0), + check_sum: v.str(result.check_sum), + pdata: v.bin(result.pdata), + } + } + } + + @eacnet('p2d') + async registPlayer(ctx: Context) { + const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string }; + + await this.userService.upsertPDataBinary(ctx.token, pdata, check_sum); + + return { + status: v.s32(0), + error: v.s32(0), + } + } + + @eacnet('p2d') + async checkPlayData(ctx: Context) { + const { check_sum } = ctx.body as { check_sum: string }; + + const checksum = await this.userService.getPDataChecksum(ctx.token); + const id = fromToken(ctx.token); + + if (!checksum) { + return { + status: v.s32(0), + error: v.s32(0), + result: { + infinitas_id: v.str(id), + valid: v.bool(false), + is_exist: v.bool(false), + } + }; + } + + if (checksum !== check_sum) { + return { + status: v.s32(0), + error: v.s32(0), + result: { + infinitas_id: v.str(id), + valid: v.bool(false), + is_exist: v.bool(true), + } + }; + } + + return { + status: v.s32(0), + error: v.s32(0), + result: { + infinitas_id: v.str(id), + valid: v.bool(true), + is_exist: v.bool(true), + } + }; + } + + @eacnet('p2d') + async getRivalInfo() { + return { + status: v.s32(0), + error: v.s32(0), + result: {}, + } + } + + @eacnet('p2d') + async getMusicList() { + return { + status: v.s32(0), + error: v.s32(0), + result: { + status: v.s32(0), + check_sum: v.str('2e49eba0b6ca8b6898f5d684ea0c3bc1ed6f1d572a4d308cf32727d64069a588'), + music_list: { + music_num: v.s32(MUSIC_LIST.length), + music: MUSIC_LIST, + } + }, + } + } + + @eacnet('p2d') + async getCompeScoreData() { + return { + status: v.s32(0), + error: v.s32(0), + result: {}, + } + } + + @eacnet('p2d') + async getMusicData(ctx: Context) { + const { play_style } = ctx.body as { play_style: number }; + + const musicDatas = await this.userService.getMusicDatas(ctx.token, play_style); + + return { + status: v.s32(0), + error: v.s32(0), + play_style: v.s32(play_style), + result: { + music: musicDatas.map(data => ({ + music_id: v.s32(data.music_id), + score: v.s32(data.score), + clear_flag: v.s32(data.clear_flag), + miss_count: v.s32(data.miss_count), + play_num: v.s32(data.play_num), + clear_num: v.s32(data.clear_num), + })) + }, + } + } + + @eacnet('p2d') + async getItemList() { + return { + status: v.s32(0), + error: v.s32(0), + result: { + item_list: { + item_num: v.s32(ITEM_LIST.length), + item: ITEM_LIST.map(id => ({ + item_id: v.str(id), + not_free_count: v.s32(1000), + free_count: v.s32(1000), + })) + } + } + }; + } +} diff --git a/src/database/index.ts b/src/database/index.ts index 0e0ede1..df047f1 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -76,3 +76,15 @@ export interface CourseResult { clear_rate: number; groove_gage: number; } + +export interface PlayerCustomizeSetting { + _id: string; + customize: { + item_id: string, + item_category: number, + }[]; + other_customize: { + item_id: string, + item_category: number, + }[]; +} diff --git a/src/decorators/eacnet.ts b/src/decorators/eacnet.ts index 89fe5a2..e16c0f2 100644 --- a/src/decorators/eacnet.ts +++ b/src/decorators/eacnet.ts @@ -1,130 +1,12 @@ -import _ from 'lodash'; -import { to_bin } from '@kamyu/kbinxml'; import { LZ77 } from '../utils/lz77.js'; - -function serializeValue(value: any, type: string): string { - if ( - [ - 's8', - 'u8', - 's16', - 'u16', - 's32', - 'u32', - 's64', - 'u64', - 'float', - 'double', - ].includes(type) - ) { - return value.toString(); - } - - if (['b', 'bool'].includes(type)) { - return value ? '1' : '0'; - } - - if (['bin', 'binary'].includes(type)) { - if (!(value instanceof Buffer)) { - throw new Error('binary with non-buffer value'); - } - - return value.toString('hex'); - } - - if (['ip4', 'str', 'string'].includes(type)) { - return value; - } - - if (type == 'time') { - if (value instanceof Date) { - return Math.floor(value.valueOf() / 1000).toString(); - } - - if (typeof value === 'number') { - return value.toString(); - } - - throw new Error('time with not date or number'); - } - - throw new Error(`unsupported type ${type}`); -} - -function serializeObject(obj: Object, name: string, linePrefix: string = '') { - let output = `${linePrefix}<${name}`; - - const entries = Object.entries(obj); - const attrs = entries - .filter(v => v[0].startsWith('$')) - .map(v => [v[0].substring(1), v[1]]); - - // handle kxml value shit - const value = entries.find(v => v[0] == '__value'); - - if (value && value[1] instanceof Array) { - attrs.push(['__count', value[1].length]); - } - - if (value && value[1] instanceof Buffer) { - attrs.push(['__size', value[1].length]); - } - - // the kbinxml library had issue when serializing non-ordered stuff - attrs.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); - - for (const attr of attrs) { - output += ` ${attr[0]}="${_.escape(attr[1])}"`; - } - - output += '>'; - - if (value) { - const type = attrs.find(v => v[0] == '__type'); - - if (!type) { - // or normal xml innerXml? - throw new Error(`value with no type in ${name}`); - } - - output += (value[1] instanceof Array ? value[1] : [value[1]]) - .map(v => serializeValue(v, type[1])) - .join(' '); - } else { - const elements = entries - .filter(v => !v[0].startsWith('$')) - .sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); - - if (elements.length) { - output += '\n'; - - for (const element of elements) { - if (element[1] instanceof Array) { - for (const item of element[1]) - output += serializeObject(item, element[0], linePrefix + ' '); - - continue; - } - - output += serializeObject(element[1], element[0], linePrefix + ' '); - } - - output += linePrefix; - } - } - - return output + `\n`; -} +import { toKBinXml } from '../utils/kbinxml.js'; export function eacnet(topName: string, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8'): MethodDecorator { return function (_target, _propertyKey, descriptor) { const orig = descriptor.value as Function; descriptor.value = async function (...args: any[]) { const obj = await orig.apply(this, args); - const xml = - `` + serializeObject(obj, topName); - - const kxml = to_bin(xml); + const kxml = toKBinXml(topName, obj, encoding); const compressed = LZ77.compress(kxml.data); const result = Buffer.allocUnsafe(compressed.length + 0x2E); @@ -135,32 +17,3 @@ export function eacnet(topName: string, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8 } as unknown as any; } } - -// console.log(serializeObject({ -// $attr: 'attr1', -// sb: { -// $attr: 'attr2', -// $__type: 'str', -// __value: 'sba', -// }, -// sb1: { -// $attr: 'attr3', -// nested: { -// $attr: 'attr4', -// $__type: 's32', -// __value: [1, 1, 4, 5, 1, 4], -// } -// }, -// array: [{ -// $attr: 'test' -// }, { -// $attr: 'test2' -// }, { -// $attr: 'test3' -// }, { -// $attr: 'test4' -// }, { -// $attr: 'test5' -// }] -// }, 'test')) - diff --git a/src/main.ts b/src/main.ts index f95cee0..84c1862 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,15 +1,17 @@ import 'reflect-metadata'; +import cors from '@koa/cors'; import Koa, { DefaultState } from 'koa'; import bodyParser from 'koa-bodyparser'; import { InjectionToken, Provider, container } from 'tsyringe'; import { ILaochanContext } from './types.js'; -import { DefaultService } from './services/default.js'; +import { DefaultService } from './controllers/default.js'; import { Logger } from '@cordisjs/logger'; import { eacnet } from './middlewares/eacnet.js'; import config from './utils/config.js'; import { MongoClient, Db } from 'mongodb'; +import { apiRoutes } from './apis/index.js'; function tryResolve(token: InjectionToken): T | undefined { if (!container.isRegistered(token)) return undefined; @@ -57,21 +59,22 @@ async function main(): Promise { ctx.body = `${e.stack ?? 'Internal Error'}`; } }) + .use(cors()) .use(bodyParser({ formLimit: '1mb', })) .use(eacnet) .use(async (ctx, next) => { - if (!ctx.service) { - return await next(); - } - ctx.logger = ctx.service ? register(`logger:${ctx.service.name}:${ctx.service.method}`, { useValue: new Logger(`${ctx.service.name}:${ctx.service.method}`), }) : logger; + if (!ctx.service) { + return await next(); + } + let service: object | undefined = tryResolve(ctx.service.name); if (service === undefined) { @@ -80,17 +83,17 @@ async function main(): Promise { try { module = await import( - `./services/${ctx.service.name}/index.js` + `./controllers/${ctx.service.name}/index.js` ); } catch (e) { if (e.code != 'ERR_MODULE_NOT_FOUND') { - logger.error( + ctx.logger.error( `load service error %s`, JSON.stringify(e, Object.getOwnPropertyNames(e), 2), ); } else { module = await import( - `./services/${ctx.service.name}.js` + `./controllers/${ctx.service.name}.js` ); } } @@ -100,19 +103,17 @@ async function main(): Promise { }); } catch (e) { if (e.code != 'ERR_MODULE_NOT_FOUND') { - logger.error( - `load service error %s`, + ctx.logger.error( + `load controller error %s`, JSON.stringify(e, Object.getOwnPropertyNames(e), 2), ); } } } - logger.info( - '%s %s.%s [%s]: request = %s', + ctx.logger.info( + '%s [%s]: request = %s', ctx.url, - ctx.service.name, - ctx.service.method, ctx.token, JSON.stringify(ctx.body, (key, value) => { if (key === 'pdata') { @@ -126,7 +127,7 @@ async function main(): Promise { let method = ctx.service.method; if (!service || !(service[method])?.bind) { - logger.warn(`unimplemented method ${method} in ${ctx.service.name}`); + ctx.logger.warn(`unimplemented method.`); service = container.resolve(DefaultService); method = 'default'; @@ -135,13 +136,19 @@ async function main(): Promise { ctx.body = await (service[method] as Function).bind(service)(ctx); ctx.status = 200; - logger.info('%s.%s [server]: status = %d', ctx.service.name, ctx.service.method, ctx.status); + ctx.logger.info('[server]: status = %d', ctx.status); }) + .use(apiRoutes) .use(async (ctx, next) => { - ctx.body = 'Laochan-Eacnet is running.'; + if (!ctx.body) { + ctx.body = 'Laochan-Eacnet is running.'; + } + return next(); }); + app.context.resolve = container.resolve.bind(container); + logger.info('listening on %d', config.port); app.listen(config.port); } diff --git a/src/middlewares/eacnet.ts b/src/middlewares/eacnet.ts index 1912a03..c0a7001 100644 --- a/src/middlewares/eacnet.ts +++ b/src/middlewares/eacnet.ts @@ -1,103 +1,7 @@ import { Next } from 'koa'; import { Context } from '../types.js'; import { LZ77 } from '../utils/lz77.js'; -import { to_xml } from '@kamyu/kbinxml'; -import { XMLParser } from 'fast-xml-parser'; - -const parser = new XMLParser({ - ignoreAttributes: false, - parseAttributeValue: false, - attributeNamePrefix: '$', - removeNSPrefix: true, - numberParseOptions: { - hex: false, - leadingZeros: false, - eNotation: false, - skipLike: /.*/, - }, -}); - -function parseValue(node: { $__type: string; $__count?: unknown }): any { - const isArray = '$__count' in node; - - if ( - [ - 's8', - 'u8', - 's16', - 'u16', - 's32', - 'u32', - 's64', - 'u64', - ].includes(node.$__type) - ) { - if (isArray) { - return node['#text'].split(' ').map((v) => parseInt(v)); - } - - return parseInt(node['#text']); - } - - if (['float', 'double'].includes(node.$__type)) { - if (isArray) { - return node['#text'].split(' ').map((v) => parseFloat(v)); - } - - return parseFloat(node['#text']); - } - - if (['b', 'bool'].includes(node.$__type)) { - return !!parseInt(node['#text']); - } - - if (['bin', 'binary'].includes(node.$__type)) { - return Buffer.from(node['#text'], 'hex'); - } - - if (['ip4', 'str', 'string'].includes(node.$__type)) { - return node['#text']; - } - - if (node.$__type == 'time') { - return new Date(parseInt(node['#text']) * 1000); - } - - return { - type: node.$__type, - value: node['#text'], - }; -} - -function kxmlToObject(node: Record | unknown[] | string): any { - if (node instanceof Array) { - return node.map((v) => kxmlToObject(v as Record)); - } - - if (typeof node === 'string') { - return {}; - } - - if ('$__type' in node) { - return parseValue(node as { $__type: string; $__count?: unknown }); - } - - const obj: object = {}; - - for (const key in node) { - if (key == '?xml') continue; - const value = node[key]; - - if (key.startsWith('$')) { - obj[key.substring(1)] = value; - continue; - } - - obj[key] = kxmlToObject(node[key] as Record); - } - - return obj; -} +import { fromKBinXml } from '../utils/kbinxml.js'; export async function eacnet(ctx: Context, next: Next): Promise { const body = ctx.request.body as { @@ -110,32 +14,36 @@ export async function eacnet(ctx: Context, next: Next): Promise { } const { request, p2d_token: token } = body; + + if (!request || !token) { + return next(); + } + const buffer = Buffer.from(request .replaceAll(' ', '+') .replaceAll('-', '+') .replaceAll('_', '/') + '===', 'base64' ); + const decoded = LZ77.decompress(buffer); - const xml = to_xml(decoded).data; - const parsedXml = parser.parse(xml); - const xmlResult = kxmlToObject(parsedXml); + const result = fromKBinXml(decoded); - const game = Object.keys(xmlResult)[0]; + const game = Object.keys(result)[0]; - if (!(game in xmlResult)) { + if (!(game in result)) { return next(); } - if (xmlResult[game].params) { - ctx.body = xmlResult.p2d.params; + if (result[game].params) { + ctx.body = result.p2d.params; } else { ctx.body = {}; } ctx.service = { name: game, - method: xmlResult[game].method, + method: result[game].method, } ctx.token = token; diff --git a/src/services/p2d/user.ts b/src/services/p2d/user.ts index 3572887..6dc1bd2 100644 --- a/src/services/p2d/user.ts +++ b/src/services/p2d/user.ts @@ -1,13 +1,18 @@ import { Binary, Db } from "mongodb"; -import { eacnet } from "../../decorators/eacnet.js"; -import { Context } from "../../types.js"; -import { v } from "../../utils/kxml-value.js"; -import { ITEM_LIST, MUSIC_LIST } from "./index.js"; -import { PlayerCourseLog, PlayerMusicData, PlayerPlayData, PlayerPlayLog } from "../../database/index.js"; -import { fromToken } from "../../utils/laochan-id.js"; +import { inject, injectable } from "tsyringe"; +import { PlayerPlayData, PlayerMusicData, PlayerPlayLog, PlayerCourseLog, PlayerCustomizeSetting } from "../../database/index.js"; +import { Pdata } from "../../database/pdata.js"; +import { fromKBinXml } from "../../utils/kbinxml.js"; -export class User { - db: Db; +@injectable() +export class UserService { + /** + * + */ + constructor( + @inject(Db) + private readonly db: Db, + ) { } get playDataCol() { return this.db.collection('player_play_data'); @@ -25,360 +30,133 @@ export class User { return this.db.collection('player_course_log'); } - @eacnet('p2d') - async sendGradeCertificationLog(ctx: Context) { - await this.courseLogCol.insertOne({ - player: ctx.token, - ...ctx.body, - }); + get customizeSettingCol() { + return this.db.collection('player_customize_setting'); + } + + addCourseLog(coruseLog: PlayerCourseLog) { + return this.courseLogCol.insertOne(coruseLog); + } + + addPlayLog(playLog: PlayerPlayLog) { + return this.playLogCol.insertOne(playLog); + } + + upsertCustomizeSetting(customizeSetting: PlayerCustomizeSetting) { + return this.customizeSettingCol.updateOne({ _id: customizeSetting._id }, { + $set: customizeSetting + }, { upsert: true }); + } + + async getCustomizeSetting(player: string): Promise { + const result = await this.customizeSettingCol.findOne({ _id: player }); + if (result) + return result; return { - status: v.s32(0), - error: v.s32(0), + _id: player, + customize: [ + { item_category: 2, item_id: 'I1100000' }, + { item_category: 3, item_id: 'I1200000' }, + { item_category: 4, item_id: 'I1300000' }, + { item_category: 5, item_id: 'I1400000' }, + { item_category: 6, item_id: 'I1500000' }, + { item_category: 7, item_id: 'I1600000' }, + { item_category: 8, item_id: 'I1700000' }, + { item_category: 11, item_id: 'I1300000' }, + { item_category: 10, item_id: 'I1900000' }, + ], + other_customize: [ + { item_category: 1, item_id: 'C1000000' }, + { item_category: 2, item_id: 'C1100000' }, + { item_category: 3, item_id: 'C1200000' }, + { item_category: 4, item_id: 'C1300000' }, + // change default to very short + { item_category: 5, item_id: 'C1400002' }, + ] } } - @eacnet('p2d') - async consumeItem() { - return { - status: v.s32(0), - error: v.s32(0), - result: { - free_count: v.s32(1000), - not_free_count: v.s32(1000), - } - }; - } - - @eacnet('p2d') - async reserveConsumeItem() { - return { - status: v.s32(0), - error: v.s32(0), - }; - } - - @eacnet('p2d') - async getCustomizeSetting() { - return { - status: v.s32(0), - error: v.s32(0), - result: { - customize: [ - { item_category: v.s32(2), item_id: v.str('I1100000') }, - { item_category: v.s32(3), item_id: v.str('I1200000') }, - { item_category: v.s32(4), item_id: v.str('I1300000') }, - { item_category: v.s32(5), item_id: v.str('I1400000') }, - { item_category: v.s32(6), item_id: v.str('I1500000') }, - { item_category: v.s32(7), item_id: v.str('I1600000') }, - { item_category: v.s32(8), item_id: v.str('I1700000') }, - { item_category: v.s32(11), item_id: v.str('I1300000') }, - { item_category: v.s32(10), item_id: v.str('I1900000') } - ], - other_customize: [ - { item_category: v.s32(1), item_id: v.str('C1000000') }, - { item_category: v.s32(2), item_id: v.str('C1100000') }, - { item_category: v.s32(3), item_id: v.str('C1200000') }, - { item_category: v.s32(4), item_id: v.str('C1300000') }, - { item_category: v.s32(5), item_id: v.str('C1400000') } - ], - } - } - } - - @eacnet('p2d') - async gameEnd() { - return { - status: v.s32(0), - error: v.s32(0), - } - } - - @eacnet('p2d') - async getPointList() { - return { - status: v.s32(0), - error: v.s32(0), - result: { - point_count: v.s32(1), - point: [{ - point_id: v.str('P0100000'), - point_num: v.u32(114514), - }] - } - } - } - - @eacnet('p2d') - async getPrivilegeClient() { - return { - status: v.s32(0), - error: v.s32(0), - result: { - list_num: v.s32(0), - } - } - } - - @eacnet('p2d') - async getPrivilegeServer() { - return { - status: v.s32(0), - error: v.s32(0), - result: { - list_num: v.s32(0), - } - } - } - - @eacnet('p2d') - async reportMusicResult(ctx: Context) { - const result = ctx.body as PlayerPlayLog; - result.player = ctx.token; - - let updateTask = Promise.resolve(); - - const play_style = Math.floor(result.note_id / 5); - const diff = result.note_id % 5; - - updateTask = (async () => { - const musicData = (await this.musicDataCol.findOne({ - player: result.player, - music_id: result.music_id, - play_style: play_style - })) ?? { - player: result.player, - music_id: result.music_id, - play_style: play_style, - - score: [0, 0, 0, 0, 0], - clear_flag: [0, 0, 0, 0, 0], - miss_count: [0, 0, 0, 0, 0], - play_num: [0, 0, 0, 0, 0], - clear_num: [0, 0, 0, 0, 0], - }; - - if (result.clear_flag > musicData.clear_flag[diff]) - musicData.clear_flag[diff] = result.clear_flag; - - if (result.miss_count < musicData.miss_count[diff]) - musicData.miss_count[diff] = result.miss_count; - - if (result.score > musicData.score[diff]) - musicData.score[diff] = result.score; - - if (result.clear_flag >= 2) - musicData.clear_num[diff]++; - - musicData.play_num[diff]++; - - await this.musicDataCol.updateOne({ - player: result.player, - music_id: result.music_id, - play_style: play_style - }, { - $set: musicData - }, { - upsert: true - }); - })(); - - await Promise.all([ - this.playLogCol.insertOne(result), - updateTask, - ]) - - return { - status: v.s32(0), - error: v.s32(0), - }; - } - - @eacnet('p2d') - async savePlayData(ctx: Context) { - const uid = ctx.token; - const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string }; - - await this.playDataCol - .updateOne({ _id: uid }, { - $set: { - pdata: new Binary(pdata), - check_sum, - } - }); - - return { - status: v.s32(0), - error: v.s32(0), - } - } - - @eacnet('p2d') - async getPlayData(ctx: Context) { - const uid = ctx.token; - - const result = await this.playDataCol - .findOne({ _id: uid }); - - if (!result) { - return { - status: v.s32(1), - error: v.s32(404), - } - } - - return { - status: v.s32(0), - error: v.s32(0), - result: { - size: v.s32(result.pdata.length()), - rest_size: v.s32(0), - check_sum: v.str(result.check_sum), - pdata: v.bin(result.pdata.buffer), - } - } - } - - @eacnet('p2d') - async registPlayer(ctx: Context) { - const uid = ctx.token; - const { pdata, check_sum } = ctx.body as { pdata: Buffer, check_sum: string }; - - await this.playDataCol - .insertOne({ _id: uid, pdata: new Binary(pdata), check_sum }); - - return { - status: v.s32(0), - error: v.s32(0), - } - } - - @eacnet('p2d') - async checkPlayData(ctx: Context) { - const { check_sum } = ctx.body as { check_sum: string }; - - const data = await this.playDataCol.findOne({ _id: ctx.token }, { + async getPDataChecksum(player: string): Promise { + const result = await this.playDataCol.findOne({ _id: player }, { projection: { check_sum: 1, } }); - const id = fromToken(ctx.token); + if (result) + return result.check_sum; - if (!data) { - return { - status: v.s32(0), - error: v.s32(0), - result: { - infinitas_id: v.str(id), - valid: v.bool(false), - is_exist: v.bool(false), - } - }; - } + return undefined; + } - if (data.check_sum !== check_sum) { - return { - status: v.s32(0), - error: v.s32(0), - result: { - infinitas_id: v.str(id), - valid: v.bool(false), - is_exist: v.bool(true), - } - }; - } + async getPDataBinary(player: string): Promise<{ pdata: Buffer, check_sum: string } | undefined> { + const result = await this.playDataCol.findOne({ _id: player }); + + if (!result) + return undefined; return { - status: v.s32(0), - error: v.s32(0), - result: { - infinitas_id: v.str(id), - valid: v.bool(true), - is_exist: v.bool(true), - } + pdata: Buffer.from(result.pdata.buffer), + check_sum: result.check_sum, }; } - @eacnet('p2d') - async getRivalInfo() { - return { - status: v.s32(0), - error: v.s32(0), - result: { - rival_data: [{ + async getPDataDecoded(player: string): Promise { + const binary = await this.getPDataBinary(player); + if (!binary) return undefined; - }] - }, - } + return fromKBinXml(binary.pdata); } - @eacnet('p2d') - async getMusicList() { - return { - status: v.s32(0), - error: v.s32(0), - result: { - status: v.s32(0), - check_sum: v.str('2e49eba0b6ca8b6898f5d684ea0c3bc1ed6f1d572a4d308cf32727d64069a588'), - music_list: { - music_num: v.s32(MUSIC_LIST.length), - music: MUSIC_LIST, + upsertPDataBinary(player: string, pdata: Buffer, check_sum: string) { + return this.playDataCol + .updateOne({ _id: player }, { + $set: { + pdata: new Binary(pdata), + check_sum, } - }, - } + }, { upsert: true }); } - @eacnet('p2d') - async getCompeScoreData() { - return { - status: v.s32(0), - error: v.s32(0), - result: {}, - } - } - - @eacnet('p2d') - async getMusicData(ctx: Context) { - const { play_style } = ctx.body as { play_style: number }; - - const musicDatas = await this.musicDataCol.find({ - player: ctx.token, - play_style, + getMusicDatas(player: string, play_style: number): Promise { + return this.musicDataCol.find({ + player, play_style, }).toArray(); - - return { - status: v.s32(0), - error: v.s32(0), - play_style: v.s32(play_style), - result: { - music: musicDatas.map(data => ({ - music_id: v.s32(data.music_id), - score: v.s32(data.score), - clear_flag: v.s32(data.clear_flag), - miss_count: v.s32(data.miss_count), - play_num: v.s32(data.play_num), - clear_num: v.s32(data.clear_num), - })) - }, - } } - @eacnet('p2d') - async getItemList() { + async getMusicData(player: string, music_id: number, play_style: number): Promise { + const result = await this.musicDataCol.findOne({ + player: player, + music_id: music_id, + play_style: play_style + }); + + if (result) + return result; + return { - status: v.s32(0), - error: v.s32(0), - result: { - item_list: { - item_num: v.s32(ITEM_LIST.length), - item: ITEM_LIST.map(id => ({ - item_id: v.str(id), - not_free_count: v.s32(1000), - free_count: v.s32(1000), - })) - } - } + player: result.player, + music_id: result.music_id, + play_style: play_style, + + score: [0, 0, 0, 0, 0], + clear_flag: [0, 0, 0, 0, 0], + miss_count: [-1, -1, -1, -1, -1], + play_num: [0, 0, 0, 0, 0], + clear_num: [0, 0, 0, 0, 0], }; } + + upsertMusicData(musicData: PlayerMusicData) { + return this.musicDataCol.updateOne({ + player: musicData.player, + music_id: musicData.music_id, + play_style: musicData.play_style + }, { + $set: musicData + }, { + upsert: true + }); + } } diff --git a/src/types.ts b/src/types.ts index 5438bef..012409f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ import { Logger } from '@cordisjs/logger'; import { DefaultState, ParameterizedContext } from 'koa'; +import { InjectionToken } from 'tsyringe'; export interface ILaochanContext { token?: string; @@ -11,6 +12,7 @@ export interface ILaochanContext { logger: Logger; body?: unknown; + resolve: (resolveToken: InjectionToken) => T; } export type Context = ParameterizedContext; diff --git a/src/utils/kbinxml.ts b/src/utils/kbinxml.ts new file mode 100644 index 0000000..bd9f48b --- /dev/null +++ b/src/utils/kbinxml.ts @@ -0,0 +1,223 @@ +import { to_bin, to_xml } from "@kamyu/kbinxml"; +import { XMLParser } from "fast-xml-parser"; +import _ from "lodash"; + +const parser = new XMLParser({ + ignoreAttributes: false, + parseAttributeValue: false, + attributeNamePrefix: '$', + removeNSPrefix: true, + numberParseOptions: { + hex: false, + leadingZeros: false, + eNotation: false, + skipLike: /.*/, + }, +}); + +function parseValue(node: { $__type: string; $__count?: unknown }): any { + const isArray = '$__count' in node; + + if ( + [ + 's8', + 'u8', + 's16', + 'u16', + 's32', + 'u32', + 's64', + 'u64', + ].includes(node.$__type) + ) { + if (isArray) { + return node['#text'].split(' ').map((v) => parseInt(v)); + } + + return parseInt(node['#text']); + } + + if (['float', 'double'].includes(node.$__type)) { + if (isArray) { + return node['#text'].split(' ').map((v) => parseFloat(v)); + } + + return parseFloat(node['#text']); + } + + if (['b', 'bool'].includes(node.$__type)) { + return !!parseInt(node['#text']); + } + + if (['bin', 'binary'].includes(node.$__type)) { + return Buffer.from(node['#text'], 'hex'); + } + + if (['ip4', 'str', 'string'].includes(node.$__type)) { + return node['#text']; + } + + if (node.$__type == 'time') { + return new Date(parseInt(node['#text']) * 1000); + } + + return { + type: node.$__type, + value: node['#text'], + }; +} + +function toObject(node: Record | unknown[] | string): any { + if (node instanceof Array) { + return node.map((v) => toObject(v as Record)); + } + + if (typeof node === 'string') { + return {}; + } + + if ('$__type' in node) { + return parseValue(node as { $__type: string; $__count?: unknown }); + } + + const obj: object = {}; + + for (const key in node) { + if (key == '?xml') continue; + const value = node[key]; + + if (key.startsWith('$')) { + obj[key.substring(1)] = value; + continue; + } + + obj[key] = toObject(node[key] as Record); + } + + return obj; +} + +export function fromKBinXml(kbinxml: Buffer) { + const xml = to_xml(kbinxml).data; + const parsedXml = parser.parse(xml); + return toObject(parsedXml); +} + +function serializeValue(value: any, type: string): string { + if ( + [ + 's8', + 'u8', + 's16', + 'u16', + 's32', + 'u32', + 's64', + 'u64', + 'float', + 'double', + ].includes(type) + ) { + return value.toString(); + } + + if (['b', 'bool'].includes(type)) { + return value ? '1' : '0'; + } + + if (['bin', 'binary'].includes(type)) { + if (!(value instanceof Buffer)) { + throw new Error('binary with non-buffer value'); + } + + return value.toString('hex'); + } + + if (['ip4', 'str', 'string'].includes(type)) { + return value; + } + + if (type == 'time') { + if (value instanceof Date) { + return Math.floor(value.valueOf() / 1000).toString(); + } + + if (typeof value === 'number') { + return value.toString(); + } + + throw new Error('time with not date or number'); + } + + throw new Error(`unsupported type ${type}`); +} + +function serializeObject(obj: Object, name: string, linePrefix: string = '') { + let output = `${linePrefix}<${name}`; + + const entries = Object.entries(obj); + const attrs = entries + .filter(v => v[0].startsWith('$')) + .map(v => [v[0].substring(1), v[1]]); + + // handle kxml value shit + const value = entries.find(v => v[0] == '__value'); + + if (value && value[1] instanceof Array) { + attrs.push(['__count', value[1].length]); + } + + if (value && value[1] instanceof Buffer) { + attrs.push(['__size', value[1].length]); + } + + // the kbinxml library had issue when serializing non-ordered stuff + attrs.sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); + + for (const attr of attrs) { + output += ` ${attr[0]}="${_.escape(attr[1])}"`; + } + + output += '>'; + + if (value) { + const type = attrs.find(v => v[0] == '__type'); + + if (!type) { + // or normal xml innerXml? + throw new Error(`value with no type in ${name}`); + } + + output += (value[1] instanceof Array ? value[1] : [value[1]]) + .map(v => serializeValue(v, type[1])) + .join(' '); + } else { + const elements = entries + .filter(v => !v[0].startsWith('$')) + .sort((a, b) => a[0] < b[0] ? 1 : (a[0] > b[0] ? -1 : 0)); + + if (elements.length) { + output += '\n'; + + for (const element of elements) { + if (element[1] instanceof Array) { + for (const item of element[1]) + output += serializeObject(item, element[0], linePrefix + ' '); + + continue; + } + + output += serializeObject(element[1], element[0], linePrefix + ' '); + } + + output += linePrefix; + } + } + + return output + `\n`; +} + +export function toKBinXml(topName: string, obj: unknown, encoding: 'UTF-8' | 'SHIFT_JIS' = 'UTF-8') { + const xml = `` + serializeObject(obj, topName); + return to_bin(xml); +} diff --git a/src/utils/sha256.ts b/src/utils/sha256.ts new file mode 100644 index 0000000..e345897 --- /dev/null +++ b/src/utils/sha256.ts @@ -0,0 +1,7 @@ +import { createHash } from "crypto"; + +export function sha256(data: Buffer) { + return createHash('sha256') + .update(data) + .digest('hex'); +}