diff --git a/.gitignore b/.gitignore index 92bc0c2..45b3993 100644 --- a/.gitignore +++ b/.gitignore @@ -105,4 +105,5 @@ dist .tern-port # CrepeSR -config.json \ No newline at end of file +config.json +src/data/proto \ No newline at end of file diff --git a/src/commands/account.ts b/src/commands/account.ts index a12c752..7cf3123 100644 --- a/src/commands/account.ts +++ b/src/commands/account.ts @@ -11,7 +11,7 @@ export default async function handle(command: Command) { return; } try { - const acc = await Account.createAccount(command.args[1], command.args[2]); + const acc = await Account.create(command.args[1], command.args[2]); c.log(`Account ${acc.name} with UID ${acc.uid} created.`); } catch (e) { c.error(e as Error); @@ -22,12 +22,12 @@ export default async function handle(command: Command) { c.log(`Usage: account delete `); return; } - const acc = await Account.getAccountByUID(command.args[1]); + const acc = await Account.fromUID(command.args[1]); if (!acc) { c.error(`Account with UID ${command.args[1]} does not exist.`); return; } - Account.deleteAccount(command.args[1]); + Account.delete(command.args[1]); c.log(`Account ${acc.name} with UID ${acc.uid} deleted successfully.`); break; default: diff --git a/src/data/packetIds.json b/src/data/packetIds.json deleted file mode 100644 index 4ae5e47..0000000 --- a/src/data/packetIds.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "101": "DebugNotify", - "5": "PlayerGetTokenCsReq", - "48": "PlayerGetTokenScRsp", - "22": "PlayerKeepAliveNotify" -} \ No newline at end of file diff --git a/src/data/proto/PlayerGetTokenCsReq.proto b/src/data/proto/PlayerGetTokenCsReq.proto deleted file mode 100644 index a839b61..0000000 --- a/src/data/proto/PlayerGetTokenCsReq.proto +++ /dev/null @@ -1,7 +0,0 @@ -syntax = "proto3"; - -message PlayerGetTokenCsReq { - uint32 account_type = 1; - string account_uid = 2; // Related to v2/login HTTP endpoint - string account_token = 3; -} \ No newline at end of file diff --git a/src/data/proto/PlayerGetTokenScRsp.proto b/src/data/proto/PlayerGetTokenScRsp.proto deleted file mode 100644 index 202cdd0..0000000 --- a/src/data/proto/PlayerGetTokenScRsp.proto +++ /dev/null @@ -1,28 +0,0 @@ -syntax = "proto3"; - -message PlayerGetTokenScRsp { - uint32 retcode = 1; - string msg = 2; - uint32 uid = 3; - string token = 4; - uint32 black_uid_end_time = 5; - uint32 account_type = 6; - string account_uid = 7; - bool is_proficient_player = 8; - string secret_key = 9; - uint32 gm_uid = 10; - uint64 secret_key_seed = 11; - bytes security_cmd_buffer = 12; - uint32 platform_type = 13; - bytes extra_bin_data = 14; - bool is_guest = 15; - uint32 channel_id = 16; - uint32 sub_channel_id = 17; - uint32 tag = 18; - string country_code = 19; - bool is_login_white_list = 20; - string psn_id = 21; - string client_version_random_key = 22; - uint32 reg_platform = 23; - string client_ip_str = 24; -} \ No newline at end of file diff --git a/src/data/proto/QueryCurrRegionHttpRsp.proto b/src/data/proto/QueryCurrRegionHttpRsp.proto deleted file mode 100644 index a62513c..0000000 --- a/src/data/proto/QueryCurrRegionHttpRsp.proto +++ /dev/null @@ -1,38 +0,0 @@ -// 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 . - -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; -} \ No newline at end of file diff --git a/src/db/Account.ts b/src/db/Account.ts index 192858e..41451da 100644 --- a/src/db/Account.ts +++ b/src/db/Account.ts @@ -13,21 +13,28 @@ export default class Account { } - public static async getAccountByUID(uid: string | number): Promise { + public static async fromUID(uid: string | number): Promise { 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 { + public static async fromToken(token: string): Promise { + const db = Database.getInstance(); + const account = await db.get("accounts", { token }); + if (!account) return; + return new Account(Number(account._id.toString()), account.name, account.token); + } + + public static async fromUsername(name: string): Promise { 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 { + public static async create(name: string, uid?: string | number): Promise { const db = Database.getInstance(); let selfAssignedUID = true; if (!uid) { @@ -38,7 +45,7 @@ export default class Account { const account = await db.get("accounts", { uid }); if (account) { if (!selfAssignedUID) { - return await Account.createAccount(name, uid); + return await Account.create(name, uid); } else { throw new Error(`Account with uid ${uid} already exists.`); } @@ -49,9 +56,9 @@ export default class Account { return new Account(Number(uid), name, token); } - public static async deleteAccount(uid: string | number): Promise { + public static async delete(uid: string | number): Promise { const db = Database.getInstance(); - const account = await Account.getAccountByUID(uid); + const account = await Account.fromUID(uid); if (!account) { throw new Error(`Account with uid ${uid} does not exist.`); } diff --git a/src/db/Player.ts b/src/db/Player.ts new file mode 100644 index 0000000..b9b6871 --- /dev/null +++ b/src/db/Player.ts @@ -0,0 +1,68 @@ +import Logger from "../util/Logger"; +import Account from "./Account"; +import Database from "./Database"; +const c = new Logger("Player"); + +interface PlayerI { + _id: number; + name: string; + token: string; + banned: boolean; + basicInfo: { + nickname: string; + level: number; + exp: number; + stamina: number; + mcoin: number; + hcoin: number; + scoin: number; + worldLevel: number; + } +} + +export default class Player { + private constructor(public db: PlayerI) { + + } + + public static async fromUID(uid: number | string): Promise { + if (typeof uid == "string") uid = Number(uid); + const db = Database.getInstance(); + const player = await db.get("players", { _id: uid }) as unknown as PlayerI; + if (!player) return Player.create(uid); + return new Player(player); + } + + public static async fromToken(token: string): Promise { + const db = Database.getInstance(); + const plr = await db.get("players", { token }) as unknown as PlayerI; + if (!plr) return Player.fromUID((await Account.fromToken(token))?.uid || Math.round(Math.random() * 50000)); + + return new Player(plr); + } + + public static async create(uid: number | string): Promise { + if (typeof uid == "string") uid = Number(uid); + const acc = await Account.fromUID(uid); + if (!acc) { + c.warn(`Account ${uid} not found`); + return; + } + const db = Database.getInstance(); + + const dataObj = { + _id: acc.uid, + name: acc.name, + token: acc.token, + banned: false + } as PlayerI + + await db.set("players", dataObj); + return new Player(dataObj); + } + + public async save() { + const db = Database.getInstance(); + await db.set("players", this.db); + } +} \ No newline at end of file diff --git a/src/http/routes/hkrpg_global/mdk/shield/api/login.ts b/src/http/routes/hkrpg_global/mdk/shield/api/login.ts index 574cb25..4a944ea 100644 --- a/src/http/routes/hkrpg_global/mdk/shield/api/login.ts +++ b/src/http/routes/hkrpg_global/mdk/shield/api/login.ts @@ -11,7 +11,7 @@ const c = new Logger("Dispatch"); // } export default async function handle(req: Request, res: Response) { - const acc = await Account.getAccountByUsername(req.body.account); + const acc = await Account.fromUsername(req.body.account); const dataObj: any = { retcode: 0, message: "OK", diff --git a/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts b/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts index cb37761..b5f4b34 100644 --- a/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts +++ b/src/http/routes/hkrpg_global/mdk/shield/api/verify.ts @@ -6,7 +6,7 @@ const c = new Logger("Dispatch"); // {"uid":"63884253","token":"ZQmgMdXA1StL9A3aPBUedr8yoiuoLrmV"} export default async function handle(req: Request, res: Response) { - const acc = await Account.getAccountByUID(req.body.uid); + const acc = await Account.fromUID(req.body.uid); const dataObj: any = { retcode: 0, message: "OK", diff --git a/src/http/routes/query_gateway.ts b/src/http/routes/query_gateway.ts index 9c5d8a3..2879d9e 100644 --- a/src/http/routes/query_gateway.ts +++ b/src/http/routes/query_gateway.ts @@ -1,18 +1,18 @@ import { Request, Response } from "express"; -import protobuf from 'protobufjs'; +import protobuf, { Type } from 'protobufjs'; import { resolve } from 'path'; import Config from "../../util/Config"; - -const proto = protobuf.loadSync(resolve(__dirname, '../../data/proto/QueryCurrRegionHttpRsp.proto')).lookup('QueryCurrRegionHttpRsp') as any; +import { Gateserver } from "../../data/proto/StarRail"; export default function handle(req: Request, res: Response) { - const dataObj = { + const dataObj = Gateserver.fromJSON({ retcode: 0, msg: "OK", regionName: "CrepeSR", - gateserverIp: Config.GAMESERVER.SERVER_IP, - gateserverPort: Config.GAMESERVER.SERVER_PORT, - } as any; + ip: Config.GAMESERVER.SERVER_IP, + port: Config.GAMESERVER.SERVER_PORT, + serverDescription: "This is not BingusRail" + }); if (Config.GAMESERVER.MAINTENANCE) { dataObj.retcode = 2; @@ -23,14 +23,14 @@ export default function handle(req: Request, res: Response) { let rsp; try { - rsp = proto!.encode(dataObj).finish(); + rsp = Gateserver.encode(dataObj).finish(); } catch { - rsp = proto!.encode({ + rsp = Gateserver.encode(Gateserver.fromJSON({ retcode: 2, msg: "Internal server error", stopBeginTime: Date.now(), stopEndTime: Date.now() * 2, - }).finish(); + })).finish(); } res.send(Buffer.from(rsp).toString('base64')); } \ No newline at end of file diff --git a/src/server/kcp/Packet.ts b/src/server/kcp/Packet.ts index 503fe37..07dfca5 100644 --- a/src/server/kcp/Packet.ts +++ b/src/server/kcp/Packet.ts @@ -1,22 +1,11 @@ import Logger, { VerboseLevel } from "../../util/Logger"; -import protobuf from 'protobufjs'; +import protobuf, { Root } 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; + private static root: Root = Packet.getRoot(); public body: {} = {}; public constructor(public readonly rawData: Buffer, public readonly protoName: string = "") { @@ -25,11 +14,10 @@ export default class Packet { this.data = rawData.subarray(12 + metadataLength, 12 + metadataLength + rawData.readUInt32BE(8)); this.cmdid = this.rawData.readUInt16BE(4); - this.protoName = this.protoName || packetIds[this.cmdid.toString()]; + this.protoName = this.protoName || CmdID[this.cmdid]; if (this.protoName) { try { - const root = protobuf.loadSync(resolve(__dirname, `../../data/proto/${this.protoName}.proto`)); - const Message = root.lookupTypeOrEnum(this.protoName); + const Message = Packet.root.lookupTypeOrEnum(this.protoName); this.body = Message.decode(this.data); } catch (e) { c.warn(`Failed to decode ${this.protoName}`); @@ -51,9 +39,9 @@ export default class Packet { public static encode(name: string, body: {}, customCmdId?: number): Packet | null { try { - const cmdid = switchedPacketIds[name]; - const root = protobuf.loadSync(resolve(__dirname, `../../data/proto/${name}.proto`)); - const Message = root.lookupTypeOrEnum(name); + // @ts-ignore - Bullshit. You can just get enums by name + const cmdid = CmdID[name]; + const Message = Packet.root.lookupTypeOrEnum(name); const data = Buffer.from(Message.encode(body).finish()); const packet = Buffer.allocUnsafe(16 + data.length); @@ -70,4 +58,423 @@ export default class Packet { return null; } } + + private static getRoot(): Root { + try { + // Combined proto file with all definitions + return protobuf.loadSync(resolve(__dirname, `../../data/proto/StarRail.proto`)); + } catch (e) { + c.error("Failed to load proto root! Server will not be able to function properly. Please check your data/ folder."); + c.error(e as Error, false); + process.exit(1); + } + } +} + +export type PacketName = keyof typeof CmdID; + +enum CmdID { + None = 0, + PlayerLoginCsReq = 1, + PlayerLoginScRsp = 2, + PlayerLogoutCsReq = 3, + PlayerLogoutScRsp = 4, + PlayerGetTokenCsReq = 5, + PlayerGetTokenScRsp = 6, + PlayerKeepAliveNotify = 7, + GmTalkScNotify = 8, + PlayerKickOutScNotify = 9, + GmTalkCsReq = 10, + GmTalkScRsp = 11, + ExchangeStaminaCsReq = 14, + ExchangeStaminaScRsp = 15, + GetAuthkeyCsReq = 16, + GetAuthkeyScRsp = 17, + RegionStopScNotify = 18, + AntiAddictScNotify = 19, + SetNicknameCsReq = 20, + SetNicknameScRsp = 21, + GetLevelRewardTakenListCsReq = 22, + GetLevelRewardTakenListScRsp = 23, + GetLevelRewardCsReq = 24, + GetLevelRewardScRsp = 25, + SyncTimeCsReq = 26, + SyncTimeScRsp = 27, + SetLanguageCsReq = 28, + SetLanguageScRsp = 29, + ServerAnnounceNotify = 30, + SetHeroBasicTypeCsReq = 31, + SetHeroBasicTypeScRsp = 32, + GetHeroBasicTypeInfoCsReq = 33, + GetHeroBasicTypeInfoScRsp = 34, + GetHeroPathCsReq = 35, + GetHeroPathScRsp = 36, + HeroPathChangedNotify = 37, + SetGenderCsReq = 38, + SetGenderScRsp = 39, + SetPlayerInfoCsReq = 40, + SetPlayerInfoScRsp = 41, + HeroBasicTypeChangedNotify = 42, + QueryProductInfoCsReq = 43, + QueryProductInfoScRsp = 44, + ClientDownloadDataScNotify = 45, + UpdateFeatureSwitchScNotify = 46, + GetBasicInfoCsReq = 47, + GetBasicInfoScRsp = 48, + DailyRefreshNotify = 49, + PVEBattleResultCsReq = 101, + PVEBattleResultScRsp = 102, + QuitBattleCsReq = 103, + QuitBattleScRsp = 104, + GetCurBattleInfoCsReq = 105, + GetCurBattleInfoScRsp = 106, + SyncClientResVersionCsReq = 107, + SyncClientResVersionScRsp = 108, + QuitBattleScNotify = 109, + GetStageDataCsReq = 201, + GetStageDataScRsp = 202, + StageBeginCsReq = 203, + StageBeginScRsp = 204, + GetAvatarDataCsReq = 301, + GetAvatarDataScRsp = 302, + AvatarExpUpCsReq = 303, + AvatarExpUpScRsp = 304, + UnlockSkilltreeCsReq = 305, + UnlockSkilltreeScRsp = 306, + PromoteAvatarCsReq = 307, + PromoteAvatarScRsp = 308, + DressAvatarCsReq = 309, + DressAvatarScRsp = 310, + TakeOffEquipmentCsReq = 311, + TakeOffEquipmentScRsp = 312, + AddAvatarScNotify = 313, + RankUpAvatarCsReq = 314, + RankUpAvatarScRsp = 315, + DressRelicAvatarCsReq = 316, + DressRelicAvatarScRsp = 317, + TakeOffRelicCsReq = 318, + TakeOffRelicScRsp = 319, + GetWaypointCsReq = 401, + GetWaypointScRsp = 402, + SetCurWaypointCsReq = 403, + SetCurWaypointScRsp = 404, + GetChapterCsReq = 405, + GetChapterScRsp = 406, + WaypointShowNewCsNotify = 407, + TakeChapterRewardCsReq = 408, + TakeChapterRewardScRsp = 409, + GetBagCsReq = 501, + GetBagScRsp = 502, + PromoteEquipmentCsReq = 503, + PromoteEquipmentScRsp = 504, + LockEquipmentCsReq = 505, + LockEquipmentScRsp = 506, + UseItemCsReq = 507, + UseItemScRsp = 508, + RankUpEquipmentCsReq = 509, + RankUpEquipmentScRsp = 510, + ExpUpEquipmentCsReq = 511, + ExpUpEquipmentScRsp = 512, + ComposeItemCsReq = 513, + ComposeItemScRsp = 514, + ExpUpRelicCsReq = 515, + ExpUpRelicScRsp = 516, + LockRelicCsReq = 517, + LockRelicScRsp = 518, + SellItemCsReq = 519, + SellItemScRsp = 520, + RechargeSuccNotify = 521, + PlayerSyncScNotify = 601, + GetStageLineupCsReq = 701, + GetStageLineupScRsp = 702, + GetCurLineupDataCsReq = 703, + GetCurLineupDataScRsp = 704, + JoinLineupCsReq = 705, + JoinLineupScRsp = 706, + QuitLineupCsReq = 707, + QuitLineupScRsp = 708, + SwapLineupCsReq = 709, + SwapLineupScRsp = 710, + SyncLineupNotify = 711, + GetLineupAvatarDataCsReq = 712, + GetLineupAvatarDataScRsp = 713, + ChangeLineupLeaderCsReq = 714, + ChangeLineupLeaderScRsp = 715, + SwitchLineupIndexCsReq = 716, + SwitchLineupIndexScRsp = 717, + SetLineupNameCsReq = 718, + SetLineupNameScRsp = 719, + GetAllLineupDataCsReq = 720, + GetAllLineupDataScRsp = 721, + VirtualLineupDestroyNotify = 722, + GetMailCsReq = 801, + GetMailScRsp = 802, + MarkReadMailCsReq = 803, + MarkReadMailScRsp = 804, + DelMailCsReq = 805, + DelMailScRsp = 806, + TakeMailAttachmentCsReq = 807, + TakeMailAttachmentScRsp = 808, + NewMailScNotify = 809, + GetQuestDataCsReq = 901, + GetQuestDataScRsp = 902, + TakeQuestRewardCsReq = 903, + TakeQuestRewardScRsp = 904, + TakeAchievementLevelRewardCsReq = 905, + TakeAchievementLevelRewardScRsp = 906, + GetMazeCsReq = 1001, + GetMazeScRsp = 1002, + ChooseMazeSeriesCsReq = 1003, + ChooseMazeSeriesScRsp = 1004, + ChooseMazeAbilityCsReq = 1005, + ChooseMazeAbilityScRsp = 1006, + EnterMazeCsReq = 1007, + EnterMazeScRsp = 1008, + MazeBuffScNotify = 1011, + CastMazeSkillCsReq = 1012, + CastMazeSkillScRsp = 1013, + MazePlaneEventScNotify = 1014, + EnterMazeByServerScNotify = 1015, + GetMazeMapInfoCsReq = 1016, + GetMazeMapInfoScRsp = 1017, + GetMazeTimeOfDayCsReq = 1018, + GetMazeTimeOfDayScRsp = 1019, + SetMazeTimeOfDayCsReq = 1020, + SetMazeTimeOfDayScRsp = 1021, + DelMazeTimeOfDayCsReq = 1022, + DelMazeTimeOfDayScRsp = 1023, + ReturnStartAnchorCsReq = 1024, + ReturnStartAnchorScRsp = 1025, + FinishPlotCsReq = 1101, + FinishPlotScRsp = 1102, + GetMissionDataCsReq = 1201, + GetMissionDataScRsp = 1202, + FinishTalkMissionCsReq = 1203, + FinishTalkMissionScRsp = 1204, + MissionRewardScNotify = 1205, + SyncTaskCsReq = 1206, + SyncTaskScRsp = 1207, + DailyTaskDataScNotify = 1208, + TakeDailyTaskExtraRewardCsReq = 1209, + TakeDailyTaskExtraRewardScRsp = 1210, + DailyTaskRewardScNotify = 1211, + MissionGroupWarnScNotify = 1212, + FinishCosumeItemMissionCsReq = 1213, + FinishCosumeItemMissionScRsp = 1214, + GetMissionEventDataCsReq = 1215, + GetMissionEventDataScRsp = 1216, + MissionEventRewardScNotify = 1217, + AcceptMissionEventCsReq = 1218, + AcceptMissionEventScRsp = 1219, + GetMissionStatusCsReq = 1220, + GetMissionStatusScRsp = 1221, + InterruptMissionEventCsReq = 1222, + InterruptMissionEventScRsp = 1223, + SetMissionEventProgressCsReq = 1224, + SetMissionEventProgressScRsp = 1225, + SubMissionRewardScNotify = 1226, + EnterAdventureCsReq = 1301, + EnterAdventureScRsp = 1302, + SceneEntityMoveCsReq = 1401, + SceneEntityMoveScRsp = 1402, + InteractPropCsReq = 1403, + InteractPropScRsp = 1404, + SceneCastSkillCsReq = 1405, + SceneCastSkillScRsp = 1406, + GetCurSceneInfoCsReq = 1407, + GetCurSceneInfoScRsp = 1408, + SceneEntityUpdateScNotify = 1409, + SceneEntityDisappearScNotify = 1410, + SceneEntityMoveScNotify = 1411, + SpringTransferCsReq = 1414, + SpringTransferScRsp = 1415, + UpdateBuffScNotify = 1416, + DelBuffScNotify = 1417, + SpringRefreshCsReq = 1418, + SpringRefreshScRsp = 1419, + LastSpringRefreshTimeNotify = 1420, + ReturnLastTownCsReq = 1421, + ReturnLastTownScRsp = 1422, + SceneEnterStageCsReq = 1423, + SceneEnterStageScRsp = 1424, + EnterSectionCsReq = 1427, + EnterSectionScRsp = 1428, + SetCurInteractEntityCsReq = 1431, + SetCurInteractEntityScRsp = 1432, + RecoverAllLineupCsReq = 1433, + RecoverAllLineupScRsp = 1434, + SavePointsInfoNotify = 1435, + StartCocoonStageCsReq = 1436, + StartCocoonStageScRsp = 1437, + EntityBindPropCsReq = 1438, + EntityBindPropScRsp = 1439, + SetClientPausedCsReq = 1440, + SetClientPausedScRsp = 1441, + UpdateBuffGroupStartScNotify = 1442, + UpdateBuffGroupEndScNotify = 1443, + ActivateFarmElementCsReq = 1445, + ActivateFarmElementScRsp = 1446, + GetSpringRecoverDataCsReq = 1447, + GetSpringRecoverDataScRsp = 1448, + SetSpringRecoverConfigCsReq = 1449, + SetSpringRecoverConfigScRsp = 1450, + SpringRecoverCsReq = 1451, + SpringRecoverScRsp = 1452, + HealPoolInfoNotify = 1453, + SpringRecoverSingleAvatarCsReq = 1454, + SpringRecoverSingleAvatarScRsp = 1455, + GetShopListCsReq = 1501, + GetShopListScRsp = 1502, + BuyGoodsCsReq = 1503, + BuyGoodsScRsp = 1504, + GetTutorialCsReq = 1601, + GetTutorialScRsp = 1602, + GetTutorialGuideCsReq = 1603, + GetTutorialGuideScRsp = 1604, + UnlockTutorialCsReq = 1605, + UnlockTutorialScRsp = 1606, + UnlockTutorialGuideCsReq = 1607, + UnlockTutorialGuideScRsp = 1608, + FinishTutorialCsReq = 1609, + FinishTutorialScRsp = 1610, + FinishTutorialGuideCsReq = 1611, + FinishTutorialGuideScRsp = 1612, + GetChallengeCsReq = 1701, + GetChallengeScRsp = 1702, + StartChallengeCsReq = 1703, + StartChallengeScRsp = 1704, + LeaveChallengeCsReq = 1705, + LeaveChallengeScRsp = 1706, + ChallengeSettleNotify = 1707, + FinishChallengeCsReq = 1708, + FinishChallengeScRsp = 1709, + GetCurChallengeCsReq = 1710, + GetCurChallengeScRsp = 1711, + ChallengeLineupNotify = 1712, + TakeChallengeTargetRewardCsReq = 1713, + TakeChallengeTargetRewardScRsp = 1714, + GetRogueInfoCsReq = 1801, + GetRogueInfoScRsp = 1802, + StartRogueCsReq = 1803, + StartRogueScRsp = 1804, + EnterRogueCsReq = 1805, + EnterRogueScRsp = 1806, + LeaveRogueCsReq = 1807, + LeaveRogueScRsp = 1808, + SyncRogueBuffSelectInfoScNotify = 1809, + SelectRogueBuffCsReq = 1810, + SelectRogueBuffScRsp = 1811, + RollRogueBuffCsReq = 1812, + RollRogueBuffScRsp = 1813, + EnterNextRogueRoomScNotify = 1814, + SyncRogueFinishScNotify = 1815, + PickRogueAvatarCsReq = 1816, + PickRogueAvatarScRsp = 1817, + AddRogueBuffScNotify = 1818, + ReviveRogueAvatarCsReq = 1819, + ReviveRogueAvatarScRsp = 1820, + SaveRogueRecordCsReq = 1821, + SaveRogueRecordScRsp = 1822, + RecoverRogueStaminaCsReq = 1823, + RecoverRogueStaminaScRsp = 1824, + StartRogueChallengeCsReq = 1827, + StartRogueChallengeScRsp = 1828, + LeaveRogueChallengeCsReq = 1829, + LeaveRogueChallengeScRsp = 1830, + SyncRogueChallengeFinishScNotify = 1831, + QuitRogueCsReq = 1832, + QuitRogueScRsp = 1833, + AppraisalRogueStoneCsReq = 1834, + AppraisalRogueStoneScRsp = 1835, + SyncRogueSeasonFinishScNotify = 1836, + SyncRogueInfoChangeScNotify = 1837, + AddRogueExtraBuffScNotify = 1838, + EnterRogueMapRoomCsReq = 1839, + EnterRogueMapRoomScRsp = 1840, + EnterRogueNextLevelCsReq = 1841, + EnterRogueNextLevelScRsp = 1842, + SyncRogueMapRoomScNotify = 1843, + SyncRoguePickAvatarScNotify = 1844, + SetRogueBlessCsReq = 1845, + SetRogueBlessScRsp = 1846, + SyncRogueBlessScNotify = 1847, + GetRogueShopInfoCsReq = 1848, + GetRogueShopInfoScRsp = 1849, + BuyRogueShopBuffCsReq = 1850, + BuyRogueShopBuffScRsp = 1851, + FinishRogueDialogueGroupCsReq = 1852, + FinishRogueDialogueGroupScRsp = 1853, + UnlockRogueRoomCsReq = 1856, + UnlockRogueRoomScRsp = 1857, + GetRogueGachaInfoCsReq = 1858, + GetRogueGachaInfoScRsp = 1859, + SetRogueGachaWishListCsReq = 1860, + SetRogueGachaWishListScRsp = 1861, + DoRogueGachaCsReq = 1862, + DoRogueGachaScRsp = 1863, + SyncRogueGachaRefreshScNotify = 1864, + BuyRogueShopItemCsReq = 1865, + BuyRogueShopItemScRsp = 1866, + GetRogueAppraisalItemInfoCsReq = 1867, + GetRogueAppraisalItemInfoScRsp = 1868, + SyncRogueMiracleGetItemScNotify = 1869, + SyncRogueQuestScNotify = 1870, + GetRogueQuestRewardCsReq = 1871, + GetRogueQuestRewardScRsp = 1872, + GetGachaInfoCsReq = 1901, + GetGachaInfoScRsp = 1902, + DoGachaCsReq = 1903, + DoGachaScRsp = 1904, + GetPrestigeInfoCsReq = 2001, + GetPrestigeInfoScRsp = 2002, + PrestigeInfoChangeNotify = 2003, + TakePrestigeLevelRewardCsReq = 2004, + TakePrestigeLevelRewardScRsp = 2005, + GetNpcTakenRewardCsReq = 2101, + GetNpcTakenRewardScRsp = 2102, + TakeTalkRewardCsReq = 2103, + TakeTalkRewardScRsp = 2104, + GetFirstTalkNpcCsReq = 2105, + GetFirstTalkNpcScRsp = 2106, + FinishFirstTalkNpcCsReq = 2107, + FinishFirstTalkNpcScRsp = 2108, + StartRaidCsReq = 2201, + StartRaidScRsp = 2202, + LeaveRaidCsReq = 2203, + LeaveRaidScRsp = 2204, + RaidInfoNotify = 2205, + GetChallengeRaidInfoCsReq = 2206, + GetChallengeRaidInfoScRsp = 2207, + TakeChallengeRaidRewardCsReq = 2208, + TakeChallengeRaidRewardScRsp = 2209, + ChallengeRaidNotify = 2210, + GetArchiveDataCsReq = 2301, + GetArchiveDataScRsp = 2302, + GetUpdatedArchiveDataCsReq = 2303, + GetUpdatedArchiveDataScRsp = 2304, + GetDialogueEventDataCsReq = 2401, + GetDialogueEventDataScRsp = 2402, + SelectDialogueEventCsReq = 2403, + SelectDialogueEventScRsp = 2404, + SyncDialogueEventDataScNotify = 2405, + GetExpeditionDataCsReq = 2501, + GetExpeditionDataScRsp = 2502, + AcceptExpeditionCsReq = 2503, + AcceptExpeditionScRsp = 2504, + CancelExpeditionCsReq = 2505, + CancelExpeditionScRsp = 2506, + TakeExpeditionRewardCsReq = 2507, + TakeExpeditionRewardScRsp = 2508, + GetLoginActivityCsReq = 2601, + GetLoginActivityScRsp = 2602, + TakeLoginActivityRewardCsReq = 2603, + TakeLoginActivityRewardScRsp = 2604, + GetNpcMessageGroupCsReq = 2701, + GetNpcMessageGroupScRsp = 2702, + GetNpcStatusCsReq = 2703, + GetNpcStatusScRsp = 2704, + FinishItemIdCsReq = 2705, + FinishItemIdScRsp = 2706, + FinishSectionIdCsReq = 2707, + FinishSectionIdScRsp = 2708, } \ No newline at end of file diff --git a/src/server/kcp/SRServer.ts b/src/server/kcp/SRServer.ts index f461b31..3ce7e9e 100644 --- a/src/server/kcp/SRServer.ts +++ b/src/server/kcp/SRServer.ts @@ -33,7 +33,6 @@ export default class SRServer { private async onMessage(data: Buffer, rinfo: RemoteInfo) { const client = `${rinfo.address}:${rinfo.port}`; - c.debug(data.toString("hex")); if (data.byteLength == 20) { // Hamdshanke const handshake = new Handshake(data); diff --git a/src/server/kcp/Session.ts b/src/server/kcp/Session.ts index da90c1a..94bf708 100644 --- a/src/server/kcp/Session.ts +++ b/src/server/kcp/Session.ts @@ -3,23 +3,21 @@ import { RemoteInfo } from 'dgram'; import { resolve } from 'path'; import fs from 'fs'; import KCP from 'node-kcp-token'; -import Packet from './Packet'; +import Packet, { PacketName } from './Packet'; import Logger, { VerboseLevel } from '../../util/Logger'; import defaultHandler from '../packets/PacketHandler'; +import Account from '../../db/Account'; +import Player from '../../db/Player'; 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 account!: Account; + public player!: Player; public constructor(private readonly kcpobj: KCP.KCP, public readonly ctx: RemoteInfo) { this.kcpobj = kcpobj; this.ctx = ctx; @@ -71,7 +69,7 @@ export default class Session { }); } - public send(name: string, body: {}) { + public send(name: PacketName, body: {}) { const packet = Packet.encode(name, body); if (!packet) return; if (Logger.VERBOSE_LEVEL >= VerboseLevel.WARNS) this.c.log(packet.protoName); diff --git a/src/server/packets/PlayerGetTokenCsReq.ts b/src/server/packets/PlayerGetTokenCsReq.ts index 0d40983..ff409f8 100644 --- a/src/server/packets/PlayerGetTokenCsReq.ts +++ b/src/server/packets/PlayerGetTokenCsReq.ts @@ -2,33 +2,49 @@ import Logger from "../../util/Logger"; import Account from "../../db/Account"; import Packet from "../kcp/Packet"; import Session from "../kcp/Session"; +import Player from "../../db/Player"; +import { PlayerGetTokenScRsp } from "../../data/proto/StarRail"; const c = new Logger("Dispatch"); interface PlayerGetTokenCsReq { - accountToken?: string; - accountUid?: string; - accountType?: number; + channel_id?: number; + account_uid?: string; + token?: string; + uid?: number; + device?: string; } +const retWarn = (msg: string) => c.warn(msg); + 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}`); + + let dataObj = { + retcode: 0, + secretKeySeed: 0 + } as PlayerGetTokenScRsp; + + const account = await Account.fromToken(body.token || ""); + if (!account) retWarn(`Account not found with token ${body.token}`); + + const player = await Player.fromToken(account?.token || ""); + if (!player) retWarn(`Player not found with accountToken ${account?.token}`); + if (!player || !account) { + dataObj.retcode = 6; return; } + session.account = account; + session.player = player; - const isTokenValid = account.token === body.accountToken; + const isTokenValid = player.db.token === body.token; + const isBanned = player.db.banned; + if (isBanned) dataObj.retcode = 1013; if (!isTokenValid) { - c.error(`Token invalid (${session.ctx.address}:${session.ctx.port})`); + retWarn(`Token invalid (${session.ctx.address}:${session.ctx.port})`); + dataObj.retcode = 1005; return; } - session.send("PlayerGetTokenScRsp", { - uid: account.uid, - token: body.accountToken, - secretKey: BigInt(0).toString(), - accountUid: account.uid.toString(), - accountType: body.accountType, - }); + dataObj.uid = player.db._id; + session.send("PlayerGetTokenScRsp", dataObj); } \ No newline at end of file diff --git a/src/server/packets/PlayerLoginCsReq.ts b/src/server/packets/PlayerLoginCsReq.ts new file mode 100644 index 0000000..121035d --- /dev/null +++ b/src/server/packets/PlayerLoginCsReq.ts @@ -0,0 +1,46 @@ +import { PlayerLoginCsReq, PlayerLoginScRsp } from "../../data/proto/StarRail"; +import Player from "../../db/Player"; +import Packet from "../kcp/Packet"; +import Session from "../kcp/Session"; + +// { Example body: +// platform: 3, +// deviceUuid: '406d064a8fa3bcb32f1d88df28e600e5a86bbf751658757874371', +// deviceInfo: '{"operatingSystem":"Windows 10 (10.0.19043) 64bit","deviceModel":"B450M DS3H V2 (Gigabyte Technology Co., Ltd.)","graphicsDeviceName":"NVIDIA GeForce GTX 1650","graphicsDeviceType":"Direct3D11","graphicsDeviceVendor":"NVIDIA","graphicsDeviceVersion":"Direct3D 11.0 [level 11.1]","graphicsMemorySize":3962,"processorCount":12,"processorFrequency":3394,"processorType":"AMD Ryzen 5 2600 Six-Core Processor ","systemMemorySize":16335,"DeviceSoC":""}', +// systemInfo: 'Windows 10 (10.0.19043) 64bit', +// clientVersion: 'OSCBWin0.70.0', +// language: 3, +// checkSum_1: 'ff07bc743a394e0ff1c163edc663137d', +// checkSum_2: 'ca590da88620492b921c9b3b4977f1be10', +// resolution: '1920*1080', +// systemLanguage: 'Dutch', +// resVersion: 611127, +// clientTimeZone: '01:00:00' +// } + +export default async function handle(session: Session, packet: Packet) { + const body = packet.body as PlayerLoginCsReq; + + const plr = await Player.fromUID(session.player.db._id)!; + if (!plr!.db.basicInfo) { + plr!.db.basicInfo = { + exp: 0, + level: 1, + hcoin: 0, + mcoin: 0, + nickname: plr!.db.name, + scoin: 0, + stamina: 100, + worldLevel: 1, + } + plr!.save(); + } + + session.send("PlayerLoginScRsp", { + basicInfo: plr!.db.basicInfo, + isNewPlayer: true, + stamina: 100, + curTimezone: body.clientTimeZone, + serverTimestampMs: Math.round(new Date().getTime() / 1000).toString(), + } as unknown as PlayerLoginScRsp); +} \ No newline at end of file