Structure change.

cmdids now an enum, protos gitignored, compiled protos for type safety
This commit is contained in:
memetrollsXD 2022-07-31 11:39:00 +02:00
parent babc829c44
commit 20d916e30b
No known key found for this signature in database
GPG Key ID: 105C2F3417AC32CD
16 changed files with 607 additions and 144 deletions

3
.gitignore vendored
View File

@ -105,4 +105,5 @@ dist
.tern-port
# CrepeSR
config.json
config.json
src/data/proto

View File

@ -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 <uid>`);
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:

View File

@ -1,6 +0,0 @@
{
"101": "DebugNotify",
"5": "PlayerGetTokenCsReq",
"48": "PlayerGetTokenScRsp",
"22": "PlayerKeepAliveNotify"
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 <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;
}

View File

@ -13,21 +13,28 @@ export default class Account {
}
public static async getAccountByUID(uid: string | number): Promise<Account | undefined> {
public static async fromUID(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> {
public static async fromToken(token: string): Promise<Account | undefined> {
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<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> {
public static async create(name: string, uid?: string | number): Promise<Account> {
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<void> {
public static async delete(uid: string | number): Promise<void> {
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.`);
}

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

@ -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<Player | undefined> {
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<Player | undefined> {
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<Player | undefined> {
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);
}
}

View File

@ -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",

View File

@ -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",

View File

@ -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'));
}

View File

@ -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,
}

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}