Compare commits

..

20 commits

Author SHA1 Message Date
f2aec40756
tidying 2024-04-15 19:28:11 -04:00
da466f961b Split SDK and Dispatch into different servers 2024-04-16 00:00:57 +03:00
9624a07ad2 Move data structs to 'common' crate and assets folder to the root 2024-04-15 21:16:37 +03:00
1f17230374 rename project 2024-04-15 21:01:18 +03:00
d5f2e42ac9 Create configs in server's folder, fix xtask for asset loading 2024-04-15 18:10:02 +03:00
133ac7bbc8 map view and teleporting 2024-04-15 17:46:52 +03:00
d14681417d load/create config at startup, add new betas to versions.json 2024-04-15 16:10:33 +03:00
2fe3285ce7 Fix for trailing slash in SDK requests 2024-04-15 15:52:20 +03:00
7a2e88bfaa
feat: xtasks 2024-04-04 14:21:14 -04:00
b2427a333b SDKServer: versions.json configuration for hotfix links 2024-03-31 11:27:45 +03:00
adbdaa38ab Add uid field in PlayerInfo 2024-03-30 00:16:06 +03:00
a856f719f0 Refactor PlayerSession, in-game lineup editing 2024-03-29 23:39:42 +03:00
dfffe1612e Fix globals.json loading when using AppData directory 2024-03-29 21:17:17 +03:00
84984f2da6 Battle monster waves configuration with globals.json 2024-03-29 15:16:29 +03:00
11dad98c7b Gameserver: use globals.json instead of .env 2024-03-29 14:50:06 +03:00
be3ce8369a MP points set to 5 (technique) 2024-03-29 14:28:44 +03:00
daf4507b34 Gameserver: lineup configuration with .env 2024-03-29 13:55:08 +03:00
c132f0d2ee Merge branch 'master' of https://git.xeondev.com/reversedrooms/RobinSR 2024-03-29 13:31:28 +03:00
bccf928bbe SDKServer: query_gateway cdn links configuration 2024-03-29 13:31:19 +03:00
7c1d485ad5 Update README.md 2024-03-29 07:42:00 +00:00
51 changed files with 7440 additions and 325 deletions

2
.cargo/config.toml Normal file
View file

@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
target/ target/
Cargo.lock Cargo.lock
proto/StarRail.proto proto/StarRail.proto
/*.json

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = [ "gameserver", "proto", "sdkserver"] members = ["common", "dispatch", "gameserver", "proto", "sdkserver", "xtask"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
@ -13,18 +13,18 @@ lazy_static = "1.4.0"
axum = "0.7.4" axum = "0.7.4"
axum-server = "0.6.0" axum-server = "0.6.0"
tower = "0.4.13"
tower-http = { version = "0.5.2", features = ["normalize-path"] }
hyper = { version = "1.3.0", features = [ "client" ] }
hyper-util = { version = "0.1.3", features = [ "client-legacy" ] }
dirs = "5.0.1"
dotenv = "0.15.0"
env_logger = "0.11.3" env_logger = "0.11.3"
rbase64 = "2.0.3" rbase64 = "2.0.3"
rand = "0.8.5" rand = "0.8.5"
rsa = { version = "0.9.6", features = [
"sha1",
"nightly",
"pkcs5",
"serde",
"sha2",
] }
prost = "0.12.3" prost = "0.12.3"
prost-types = "0.12.3" prost-types = "0.12.3"
@ -53,6 +53,7 @@ tracing-subscriber = { version = "0.3.18", features = [
] } ] }
tracing-bunyan-formatter = "0.3.9" tracing-bunyan-formatter = "0.3.9"
common = { path = "common/" }
proto = { path = "proto/" } proto = { path = "proto/" }
[profile.release] [profile.release]

View file

@ -208,7 +208,7 @@ If you develop a new program, and you want it to be of the greatest possible use
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
RobinSR AcheronSR
Copyright (C) 2024 reversedrooms Copyright (C) 2024 reversedrooms
This program is free software: you can redistribute it and/or modify it under the terms of the GNU 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 free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
RobinSR Copyright (C) 2024 reversedrooms AcheronSR Copyright (C) 2024 reversedrooms
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.

View file

@ -1,3 +1,77 @@
# RobinSR # AcheronSR
Honkai: Star Rail Server Emulator (2.1.5) A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/en-us/)
![screenshot](https://git.xeondev.com/reversedrooms/AcheronSR/raw/branch/master/screenshot.png)
## Installation
### From Source
#### Requirements
- [Rust](https://www.rust-lang.org/tools/install)
#### Building
##### Manually
```sh
git clone https://git.xeondev.com/reversedrooms/AcheronSR.git
cd AcheronSR
cargo build --bin gameserver
cargo build --bin dispatch
cargo build --bin sdkserver
```
##### Using xtasks (use this if stupid)
```sh
cargo xtask run
```
##### To run it with automatic recompilation when any Rust files are changed
```sh
cargo xtask watch
```
### From Pre-built Binaries
Navigate to the [Releases](https://git.xeondev.com/reversedrooms/AcheronSR/releases)
page and download the latest release for your platform.
## Usage
To begin using the server, you need to run both the SDK server and the game server.
If you installed from pre-built binaries, navigate to the directory where you downloaded
the binaries and either a) double-click on the following executable names or b)
run the following in a terminal:
```sh
./gameserver
./dispatch
./sdkserver
```
##### Note: the `assets` folder should be in the same directory with the `gameserver`, otherwise it will panic.
## Connecting
[Get 2.2 beta client](https://bhrpg-prod.oss-accelerate.aliyuncs.com/client/beta/20240322124944_scfGE0xJXlWtoJ1r/StarRail_2.1.51.zip),
replace [mhypbase.dll](https://git.xeondev.com/reversedrooms/AcheronSR/raw/branch/master/mhypbase.dll)
file in your game folder, it will redirect game traffic (and disable in-game censorship)
## Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss
what you would like to change, and why.
## Bug Reports
If you find a bug, please open an issue with as much detail as possible. If you
can, please include steps to reproduce the bug.
Bad issues such as "This doesn't work" will be closed immediately, be _sure_ to
provide exact detailed steps to reproduce your bug. If it's hard to reproduce, try
to explain it and write a reproducer as best as you can.

File diff suppressed because it is too large Load diff

13
common/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "common"
edition = "2021"
version.workspace = true
[dependencies]
anyhow.workspace = true
ansi_term.workspace = true
env_logger.workspace = true
tracing.workspace = true
lazy_static.workspace = true
serde.workspace = true
serde_json.workspace = true

20
common/src/data/excels.rs Normal file
View file

@ -0,0 +1,20 @@
use serde::Deserialize;
#[derive(Default, Deserialize)]
#[serde(rename_all = "PascalCase")]
#[serde(default)]
pub struct MapEntranceConfig {
#[serde(rename = "BeginMainMissionIDList")]
pub begin_main_mission_idlist: Vec<u32>,
pub entrance_type: String,
#[serde(rename = "FinishMainMissionIDList")]
pub finish_main_mission_idlist: Vec<u32>,
#[serde(rename = "FinishSubMissionIDList")]
pub finish_sub_mission_idlist: Vec<u32>,
#[serde(rename = "FloorID")]
pub floor_id: u32,
#[serde(rename = "ID")]
pub id: u32,
#[serde(rename = "PlaneID")]
pub plane_id: u32,
}

37
common/src/data/mod.rs Normal file
View file

@ -0,0 +1,37 @@
mod excels;
pub use excels::*;
use lazy_static::lazy_static;
use serde_json::from_str;
pub fn init_assets() {
tracing::info!("Loaded {} excel tables", EXCEL_COLLECTION.table_count());
}
lazy_static! {
pub static ref EXCEL_COLLECTION: ExcelCollection = ExcelCollection::new();
}
pub struct ExcelCollection {
pub map_entrance_configs: Vec<MapEntranceConfig>,
}
impl ExcelCollection {
fn new() -> Self {
Self {
map_entrance_configs: from_str(&load_asset(
"assets/ExcelOutput/MapEntranceConfig.json",
))
.unwrap(),
}
}
#[must_use]
pub const fn table_count(&self) -> usize {
1
}
}
fn load_asset(path: &str) -> String {
std::fs::read_to_string(path).unwrap()
}

3
common/src/lib.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod data;
pub mod logging;
pub mod util;

7
common/src/util.rs Normal file
View file

@ -0,0 +1,7 @@
#[must_use]
pub fn load_or_create_config(path: &str, defaults: &str) -> String {
std::fs::read_to_string(path).unwrap_or_else(|_| {
std::fs::write(path, defaults).unwrap();
defaults.to_string()
})
}

34
dispatch/Cargo.toml Normal file
View file

@ -0,0 +1,34 @@
[package]
name = "dispatch"
edition = "2021"
version.workspace = true
[dependencies]
common.workspace = true
anyhow.workspace = true
env_logger.workspace = true
axum.workspace = true
axum-server.workspace = true
lazy_static.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tokio-util.workspace = true
tracing.workspace = true
tracing-futures.workspace = true
tracing-log.workspace = true
tracing-subscriber.workspace = true
tracing-bunyan-formatter.workspace = true
ansi_term.workspace = true
prost.workspace = true
rbase64.workspace = true
proto.workspace = true
tower.workspace = true
tower-http.workspace = true

34
dispatch/dispatch.json Normal file
View file

@ -0,0 +1,34 @@
{
"http_port": 21041,
"game_servers": {
"acheron_sr": {
"name": "AcheronSR",
"title": "AcheronSR",
"dispatch_url": "http://127.0.0.1:21041/query_gateway/acheron_sr",
"env_type": "2",
"gateserver_ip": "127.0.0.1",
"gateserver_port": 23301,
"gateserver_protocol": "Tcp"
}
},
"versions": {
"CNBETAWin2.1.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_6744505_89b2f5dc973e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_6759713_b4e0e740f0da",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_6755976_3c46d7c46e2c",
"lua_version": "6755976"
},
"CNBETAWin2.1.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_6785106_15237df2ef89",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_6787319_5f3f1dae4769",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_6785460_26c4b6c61a8b",
"lua_version": "6785460"
},
"CNBETAWin2.1.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_6828321_72f2df86102b",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_6834225_44836493b261",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_6828764_f749b48347fd",
"lua_version": "6828764"
}
}
}

51
dispatch/src/config.rs Normal file
View file

@ -0,0 +1,51 @@
use std::collections::HashMap;
use common::util::load_or_create_config;
use lazy_static::lazy_static;
use serde::Deserialize;
use serde_json::from_str;
const DEFAULT_CONFIG: &str = include_str!("../dispatch.json");
pub fn init_config() {
let _configuration = &*CONFIGURATION;
}
#[derive(Deserialize)]
pub struct DispatchServerConfiguration {
pub http_port: u16,
pub game_servers: HashMap<String, GameServerConfig>,
pub versions: HashMap<String, VersionConfig>,
}
#[derive(Deserialize)]
pub struct VersionConfig {
pub asset_bundle_url: String,
pub ex_resource_url: String,
pub lua_url: String,
pub lua_version: String,
}
#[derive(Deserialize)]
pub struct GameServerConfig {
pub name: String,
pub title: String,
pub dispatch_url: String,
pub env_type: String,
pub gateserver_ip: String,
pub gateserver_port: u16,
pub gateserver_protocol: GatewayProtocolType,
}
#[derive(Deserialize, Eq, PartialEq)]
pub enum GatewayProtocolType {
Tcp,
Kcp,
}
lazy_static! {
pub static ref CONFIGURATION: DispatchServerConfiguration = {
let data = load_or_create_config("dispatch.json", DEFAULT_CONFIG);
from_str(&data).unwrap()
};
}

85
dispatch/src/handlers.rs Normal file
View file

@ -0,0 +1,85 @@
use crate::config::*;
use axum::extract::{Path, Query};
use prost::Message;
use proto::{Dispatch, Gateserver, RegionInfo};
use serde::Deserialize;
pub const QUERY_DISPATCH_PATH: &str = "/query_dispatch";
pub const QUERY_GATEWAY_PATH: &str = "/query_gateway/:region_name";
#[tracing::instrument]
pub async fn query_dispatch() -> String {
let rsp = Dispatch {
retcode: 0,
region_list: CONFIGURATION
.game_servers
.values()
.map(|c| RegionInfo {
name: c.name.clone(),
title: c.title.clone(),
env_type: c.env_type.clone(),
dispatch_url: c.dispatch_url.clone(),
..Default::default()
})
.collect(),
..Default::default()
};
let mut buff = Vec::new();
rsp.encode(&mut buff).unwrap();
rbase64::encode(&buff)
}
#[derive(Deserialize, Debug)]
pub struct QueryGatewayParameters {
pub version: String,
}
#[tracing::instrument]
pub async fn query_gateway(
Path(region_name): Path<String>,
parameters: Query<QueryGatewayParameters>,
) -> String {
let rsp = if let Some(server_config) = CONFIGURATION.game_servers.get(&region_name) {
if let Some(version_config) = CONFIGURATION.versions.get(&parameters.version) {
Gateserver {
retcode: 0,
ip: server_config.gateserver_ip.clone(),
port: u32::from(server_config.gateserver_port),
asset_bundle_url: version_config.asset_bundle_url.clone(),
ex_resource_url: version_config.ex_resource_url.clone(),
lua_url: version_config.lua_url.clone(),
lua_version: version_config.lua_version.clone(),
ifix_version: String::from("0"),
jblkncaoiao: true,
hjdjakjkdbi: true,
ldknmcpffim: true,
feehapamfci: true,
eebfeohfpph: true,
dfmjjcfhfea: true,
najikcgjgan: true,
giddjofkndm: true,
use_tcp: server_config.gateserver_protocol == GatewayProtocolType::Tcp,
..Default::default()
}
} else {
Gateserver {
retcode: 9,
msg: format!("forbidden version: {} or invalid bind", parameters.version),
..Default::default()
}
}
} else {
Gateserver {
retcode: 9,
msg: format!("server config for {region_name} not found"),
..Default::default()
}
};
let mut buff = Vec::new();
rsp.encode(&mut buff).unwrap();
rbase64::encode(&buff)
}

35
dispatch/src/main.rs Normal file
View file

@ -0,0 +1,35 @@
use anyhow::Result;
use axum::{extract::Request, routing::get, Router, ServiceExt};
use common::logging::init_tracing;
use tokio::net::TcpListener;
use tower::Layer;
use tower_http::normalize_path::NormalizePathLayer;
use tracing::Level;
mod config;
mod handlers;
use config::{init_config, CONFIGURATION};
#[tokio::main]
async fn main() -> Result<()> {
init_tracing();
init_config();
let span = tracing::span!(Level::DEBUG, "main");
let _ = span.enter();
let app = Router::new()
.route(handlers::QUERY_DISPATCH_PATH, get(handlers::query_dispatch))
.route(handlers::QUERY_GATEWAY_PATH, get(handlers::query_gateway));
let app = NormalizePathLayer::trim_trailing_slash().layer(app);
let addr = format!("0.0.0.0:{}", CONFIGURATION.http_port);
let server = TcpListener::bind(&addr).await?;
tracing::info!("dispatch is listening at {addr}");
axum::serve(server, ServiceExt::<Request>::into_make_service(app)).await?;
Ok(())
}

View file

@ -4,9 +4,13 @@ edition = "2021"
version.workspace = true version.workspace = true
[dependencies] [dependencies]
common.workspace = true
ansi_term.workspace = true ansi_term.workspace = true
anyhow.workspace = true anyhow.workspace = true
atomic_refcell.workspace = true atomic_refcell.workspace = true
dirs.workspace = true
dotenv.workspace = true
env_logger.workspace = true env_logger.workspace = true
hex.workspace = true hex.workspace = true
lazy_static.workspace = true lazy_static.workspace = true

8
gameserver/globals.json Normal file
View file

@ -0,0 +1,8 @@
{
"lineup": [1308, 1309, 1307, 1315],
"monster_wave_list":
[
[3013010, 3013010],
[3024020]
]
}

View file

@ -0,0 +1,19 @@
use common::util::load_or_create_config;
use lazy_static::lazy_static;
use serde::Deserialize;
use serde_json::from_str;
const DEFAULT_GLOBALS: &str = include_str!("../../globals.json");
lazy_static! {
pub static ref INSTANCE: Globals = {
let data = load_or_create_config("globals.json", DEFAULT_GLOBALS);
from_str(&data).unwrap()
};
}
#[derive(Deserialize)]
pub struct Globals {
pub lineup: Vec<u32>,
pub monster_wave_list: Vec<Vec<u32>>,
}

View file

@ -0,0 +1,9 @@
mod global_config;
mod player_info;
pub use global_config::INSTANCE as globals;
pub use player_info::PlayerInfo;
pub fn init_config() {
let _globals = &*globals; // this will initialize the static (and create default config)
}

View file

@ -0,0 +1,59 @@
use crate::net::PlayerSession;
use super::globals;
use anyhow::Result;
use proto::*;
pub struct PlayerInfo {
pub uid: u32,
pub lineup: LineupInfo,
}
impl PlayerInfo {
pub fn new() -> Self {
Self {
uid: 1337,
lineup: default_lineup(),
}
}
pub async fn sync_lineup(&self, session: &PlayerSession) -> Result<()> {
session
.send(
CMD_SYNC_LINEUP_NOTIFY,
SyncLineupNotify {
lineup: Some(self.lineup.clone()),
..Default::default()
},
)
.await
}
}
fn default_lineup() -> LineupInfo {
LineupInfo {
plane_id: 10001,
name: String::from("Lineup 1"),
index: 0,
leader_slot: 0,
mp: 5,
mp_max: 5,
avatar_list: globals
.lineup
.iter()
.enumerate()
.map(|(idx, id)| LineupAvatar {
id: *id,
slot: idx as u32,
hp: 10000,
sp: Some(AmountInfo {
cur_amount: 10000,
max_amount: 10000,
}),
satiety: 100,
avatar_type: 3,
})
.collect(),
..Default::default()
}
}

View file

@ -1,14 +1,19 @@
use anyhow::Result; use anyhow::Result;
mod logging; mod game;
mod net; mod net;
mod util; mod util;
use logging::init_tracing; use common::data::init_assets;
use common::logging::init_tracing;
use game::init_config;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
init_tracing(); init_tracing();
init_config();
init_assets();
net::gateway::listen("0.0.0.0", 23301).await?; net::gateway::listen("0.0.0.0", 23301).await?;
Ok(()) Ok(())

View file

@ -1,8 +1,9 @@
use anyhow::Result; use anyhow::Result;
use common::log_error;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tracing::{info_span, Instrument}; use tracing::{info_span, Instrument};
use crate::{log_error, net::PlayerSession}; use crate::net::PlayerSession;
pub async fn listen(host: &str, port: u16) -> Result<()> { pub async fn listen(host: &str, port: u16) -> Result<()> {
let listener = TcpListener::bind(format!("{host}:{port}")).await?; let listener = TcpListener::bind(format!("{host}:{port}")).await?;

View file

@ -4,7 +4,7 @@ use proto::*;
use crate::{net::PlayerSession, util}; use crate::{net::PlayerSession, util};
pub async fn on_player_get_token_cs_req( pub async fn on_player_get_token_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &PlayerGetTokenCsReq, _body: &PlayerGetTokenCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -13,7 +13,7 @@ pub async fn on_player_get_token_cs_req(
PlayerGetTokenScRsp { PlayerGetTokenScRsp {
retcode: 0, retcode: 0,
msg: String::from("OK"), msg: String::from("OK"),
uid: 1337, uid: session.player_uid(),
..Default::default() ..Default::default()
}, },
) )
@ -21,7 +21,7 @@ pub async fn on_player_get_token_cs_req(
} }
pub async fn on_player_login_cs_req( pub async fn on_player_login_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &PlayerLoginCsReq, body: &PlayerLoginCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -32,7 +32,7 @@ pub async fn on_player_login_cs_req(
server_timestamp_ms: util::cur_timestamp_ms(), server_timestamp_ms: util::cur_timestamp_ms(),
stamina: 240, stamina: 240,
basic_info: Some(PlayerBasicInfo { basic_info: Some(PlayerBasicInfo {
nickname: String::from("xeondev"), nickname: String::from("AcheronSR"),
level: 5, level: 5,
stamina: 240, stamina: 240,
..Default::default() ..Default::default()

View file

@ -8,7 +8,7 @@ static UNLOCKED_AVATARS: [u32; 49] = [
]; ];
pub async fn on_get_avatar_data_cs_req( pub async fn on_get_avatar_data_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &GetAvatarDataCsReq, body: &GetAvatarDataCsReq,
) -> Result<()> { ) -> Result<()> {
session session

View file

@ -1,11 +1,12 @@
use super::*; use super::*;
use crate::game::globals;
static BATTLE_LINEUP: [u32; 4] = [1309, 1308, 1307, 1315];
pub async fn on_start_cocoon_stage_cs_req( pub async fn on_start_cocoon_stage_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &StartCocoonStageCsReq, body: &StartCocoonStageCsReq,
) -> Result<()> { ) -> Result<()> {
let player_info = session.player_info();
let rsp = StartCocoonStageScRsp { let rsp = StartCocoonStageScRsp {
retcode: 0, retcode: 0,
prop_entity_id: body.prop_entity_id, prop_entity_id: body.prop_entity_id,
@ -15,12 +16,13 @@ pub async fn on_start_cocoon_stage_cs_req(
stage_id: 201012311, stage_id: 201012311,
logic_random_seed: 4444, logic_random_seed: 4444,
battle_id: 1, battle_id: 1,
battle_avatar_list: BATTLE_LINEUP battle_avatar_list: player_info
.lineup
.avatar_list
.iter() .iter()
.enumerate() .map(|avatar| BattleAvatar {
.map(|(idx, id)| BattleAvatar { index: avatar.slot,
index: idx as u32, id: avatar.id,
id: *id,
level: 80, level: 80,
promotion: 6, promotion: 6,
rank: 6, rank: 6,
@ -33,13 +35,20 @@ pub async fn on_start_cocoon_stage_cs_req(
..Default::default() ..Default::default()
}) })
.collect(), .collect(),
monster_wave_list: vec![SceneMonsterWave { monster_wave_list: globals
monster_list: vec![SceneMonsterParam { .monster_wave_list
monster_id: 3013010, .iter()
.map(|monster_list| SceneMonsterWave {
monster_list: monster_list
.iter()
.map(|id| SceneMonsterParam {
monster_id: *id,
..Default::default() ..Default::default()
}], })
.collect(),
..Default::default() ..Default::default()
}], })
.collect(),
..Default::default() ..Default::default()
}), }),
}; };
@ -48,7 +57,7 @@ pub async fn on_start_cocoon_stage_cs_req(
} }
pub async fn on_pve_battle_result_cs_req( pub async fn on_pve_battle_result_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &PveBattleResultCsReq, body: &PveBattleResultCsReq,
) -> Result<()> { ) -> Result<()> {
session session

View file

@ -1,82 +1,47 @@
use super::*; use super::*;
static STARTING_LINEUP: [u32; 4] = [1309, 1308, 1307, 1315];
pub async fn on_get_all_lineup_data_cs_req( pub async fn on_get_all_lineup_data_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &GetAllLineupDataCsReq, _body: &GetAllLineupDataCsReq,
) -> Result<()> { ) -> Result<()> {
let player_info = session.player_info();
session session
.send( .send(
CMD_GET_ALL_LINEUP_DATA_SC_RSP, CMD_GET_ALL_LINEUP_DATA_SC_RSP,
GetAllLineupDataScRsp { GetAllLineupDataScRsp {
retcode: 0, retcode: 0,
cur_index: 0, cur_index: 0,
lineup_list: vec![LineupInfo { lineup_list: vec![player_info.lineup.clone()],
plane_id: 10001,
name: String::from("Lineup 1"),
index: 0,
avatar_list: STARTING_LINEUP
.iter()
.enumerate()
.map(|(idx, id)| LineupAvatar {
id: *id,
slot: idx as u32,
hp: 10000,
sp: Some(AmountInfo {
cur_amount: 10000,
max_amount: 10000,
}),
satiety: 100,
avatar_type: 3,
})
.collect(),
..Default::default()
}],
}, },
) )
.await .await
} }
pub async fn on_get_cur_lineup_data_cs_req( pub async fn on_get_cur_lineup_data_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &GetCurLineupDataCsReq, _body: &GetCurLineupDataCsReq,
) -> Result<()> { ) -> Result<()> {
let player_info = session.player_info();
session session
.send( .send(
CMD_GET_CUR_LINEUP_DATA_SC_RSP, CMD_GET_CUR_LINEUP_DATA_SC_RSP,
GetCurLineupDataScRsp { GetCurLineupDataScRsp {
retcode: 0, retcode: 0,
lineup: Some(LineupInfo { lineup: Some(player_info.lineup.clone()),
plane_id: 10001,
name: String::from("Lineup 1"),
index: 0,
avatar_list: STARTING_LINEUP
.iter()
.enumerate()
.map(|(idx, id)| LineupAvatar {
id: *id,
slot: idx as u32,
hp: 10000,
sp: Some(AmountInfo {
cur_amount: 10000,
max_amount: 10000,
}),
satiety: 100,
avatar_type: 3,
})
.collect(),
..Default::default()
}),
}, },
) )
.await .await
} }
pub async fn on_change_lineup_leader_cs_req( pub async fn on_change_lineup_leader_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &ChangeLineupLeaderCsReq, body: &ChangeLineupLeaderCsReq,
) -> Result<()> { ) -> Result<()> {
let mut player_info = session.player_info_mut();
player_info.lineup.leader_slot = body.slot;
session session
.send( .send(
CMD_CHANGE_LINEUP_LEADER_SC_RSP, CMD_CHANGE_LINEUP_LEADER_SC_RSP,
@ -87,3 +52,102 @@ pub async fn on_change_lineup_leader_cs_req(
) )
.await .await
} }
pub async fn on_join_lineup_cs_req(session: &PlayerSession, body: &JoinLineupCsReq) -> Result<()> {
let mut player_info = session.player_info_mut();
if !(0..4).contains(&body.slot) {
return session
.send(
CMD_JOIN_LINEUP_CS_REQ,
JoinLineupScRsp {
retcode: Retcode::RetLineupInvalidMemberPos as u32,
},
)
.await;
}
if player_info
.lineup
.avatar_list
.iter()
.any(|avatar| avatar.slot == body.slot)
{
return session
.send(
CMD_JOIN_LINEUP_CS_REQ,
JoinLineupScRsp {
retcode: Retcode::RetLineupAvatarAlreadyInit as u32,
},
)
.await;
}
player_info
.lineup
.avatar_list
.push(lineup_avatar(body.base_avatar_id, body.slot));
player_info.sync_lineup(session).await?;
session
.send(CMD_JOIN_LINEUP_SC_RSP, JoinLineupScRsp::default())
.await
}
pub async fn on_replace_lineup_cs_req(
session: &PlayerSession,
body: &ReplaceLineupCsReq,
) -> Result<()> {
let mut player_info = session.player_info_mut();
player_info.lineup.avatar_list.clear();
for slot_info in &body.lineup_slots {
player_info
.lineup
.avatar_list
.push(lineup_avatar(slot_info.id, slot_info.slot));
}
player_info.lineup.leader_slot = body.leader_slot;
player_info.sync_lineup(session).await?;
session
.send(CMD_REPLACE_LINEUP_SC_RSP, ReplaceLineupScRsp::default())
.await
}
pub async fn on_quit_lineup_cs_req(session: &PlayerSession, body: &QuitLineupCsReq) -> Result<()> {
let mut player_info = session.player_info_mut();
player_info
.lineup
.avatar_list
.retain(|avatar| avatar.id != body.base_avatar_id);
player_info.sync_lineup(session).await?;
session
.send(
CMD_QUIT_LINEUP_SC_RSP,
QuitLineupScRsp {
plane_id: body.plane_id,
is_mainline: !body.is_virtual,
is_virtual: body.is_virtual,
base_avatar_id: body.base_avatar_id,
retcode: 0,
},
)
.await
}
#[must_use]
const fn lineup_avatar(id: u32, slot: u32) -> LineupAvatar {
LineupAvatar {
id,
slot,
hp: 10000,
sp: Some(AmountInfo {
cur_amount: 10000,
max_amount: 10000,
}),
satiety: 100,
avatar_type: 3,
}
}

View file

@ -0,0 +1,27 @@
use super::*;
pub async fn on_get_scene_map_info_cs_req(
session: &PlayerSession,
body: &GetSceneMapInfoCsReq,
) -> Result<()> {
session
.send(
CMD_GET_SCENE_MAP_INFO_SC_RSP,
GetSceneMapInfoScRsp {
entry_id: body.entry_id,
cur_map_entry_id: body.entry_id,
scene_map_info: body
.entry_id_list
.iter()
.map(|id| SceneMapInfo {
cur_map_entry_id: body.entry_id,
entry_id: *id,
..Default::default()
})
.collect(),
retcode: 0,
..Default::default()
},
)
.await
}

View file

@ -41,7 +41,7 @@ static FINISHED_MAIN_MISSIONS: [u32; 365] = [
]; ];
pub async fn on_get_mission_status_cs_req( pub async fn on_get_mission_status_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &GetMissionStatusCsReq, body: &GetMissionStatusCsReq,
) -> Result<()> { ) -> Result<()> {
let rsp = GetMissionStatusScRsp { let rsp = GetMissionStatusScRsp {

View file

@ -2,6 +2,7 @@ mod authentication;
mod avatar; mod avatar;
mod battle; mod battle;
mod lineup; mod lineup;
mod map;
mod mission; mod mission;
mod player; mod player;
mod scene; mod scene;
@ -19,6 +20,7 @@ pub use authentication::*;
pub use avatar::*; pub use avatar::*;
pub use battle::*; pub use battle::*;
pub use lineup::*; pub use lineup::*;
pub use map::*;
pub use mission::*; pub use mission::*;
pub use player::*; pub use player::*;
pub use scene::*; pub use scene::*;
@ -46,7 +48,7 @@ macro_rules! dummy {
} }
} }
pub async fn send_dummy_response(&mut self, req_id: u16) -> Result<()> { pub async fn send_dummy_response(&self, req_id: u16) -> Result<()> {
let cmd_type = match req_id { let cmd_type = match req_id {
$( $(
x if x == [<Cmd $cmd CsReq>] as u16 => [<Cmd $cmd ScRsp>] as u16, x if x == [<Cmd $cmd CsReq>] as u16 => [<Cmd $cmd ScRsp>] as u16,
@ -61,7 +63,7 @@ macro_rules! dummy {
} }
.into(); .into();
self.client_socket.write_all(&payload).await?; self.client_socket().await.write_all(&payload).await?;
Ok(()) Ok(())
} }

View file

@ -3,7 +3,7 @@ use crate::util;
use super::*; use super::*;
pub async fn on_get_basic_info_cs_req( pub async fn on_get_basic_info_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &GetBasicInfoCsReq, _body: &GetBasicInfoCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -19,7 +19,7 @@ pub async fn on_get_basic_info_cs_req(
} }
pub async fn on_get_hero_basic_type_info_cs_req( pub async fn on_get_hero_basic_type_info_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &GetHeroBasicTypeInfoCsReq, _body: &GetHeroBasicTypeInfoCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -40,7 +40,7 @@ pub async fn on_get_hero_basic_type_info_cs_req(
} }
pub async fn on_player_heart_beat_cs_req( pub async fn on_player_heart_beat_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &PlayerHeartBeatCsReq, body: &PlayerHeartBeatCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -53,7 +53,7 @@ pub async fn on_player_heart_beat_cs_req(
download_data: Some(ClientDownloadData { download_data: Some(ClientDownloadData {
version: 51, version: 51,
time: util::cur_timestamp_ms() as i64, time: util::cur_timestamp_ms() as i64,
data: rbase64::decode("G0x1YVMBGZMNChoKBAQICHhWAAAAAAAAAAAAAAAod0ABKEBDOlxVc2Vyc1x4ZW9uZGV2XERvd25sb2Fkc1xyYWJzdHZvLmx1YQAAAAAAAAAAAAEEEAAAACQAQAApQEAAKYBAACnAQABWAAEALIAAAR1AQQCkgEEA5ABAAOnAwQHpAMIB6UDCAawAAAEsgAAAH8BChRkAgAAMAAAABANDUwQMVW5pdHlFbmdpbmUEC0dhbWVPYmplY3QEBUZpbmQEKVVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkEF0dldENvbXBvbmVudEluQ2hpbGRyZW4EB3R5cGVvZgQEUlBHBAdDbGllbnQEDkxvY2FsaXplZFRleHQEBXRleHQURVJvYmluU1IgaXMgYSBmcmVlIGFuZCBvcGVuIHNvdXJjZSBzb2Z0d2FyZS4gZGlzY29yZC5nZy9yZXZlcnNlZHJvb21zAQAAAAEAAAAAABAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAAAAAAEAAAAFX0VOVg==").unwrap() data: rbase64::decode("G0x1YVMBGZMNChoKBAQICHhWAAAAAAAAAAAAAAAod0ABKEBDOlxVc2Vyc1x4ZW9uZGV2XERvd25sb2Fkc1xhY2hlcm9uLmx1YQAAAAAAAAAAAAEEHwAAACQAQAApQEAAKYBAACnAQABWAAEALIAAAR1AQQCkgEEA5ABAAOnAwQHpAMIB6UDCAawAAAEsgAAAH8BChSQAQAApQEAAKYBAACnAQABWAAMALIAAAR1AQQCkgEEA5ABAAOnAwQHpAMIB6UDCAawAAAEsgAAAH0BDhRkAgAAOAAAABANDUwQMVW5pdHlFbmdpbmUEC0dhbWVPYmplY3QEBUZpbmQEKVVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkEF0dldENvbXBvbmVudEluQ2hpbGRyZW4EB3R5cGVvZgQEUlBHBAdDbGllbnQEDkxvY2FsaXplZFRleHQEBXRleHQULUFjaGVyb25TUiBpcyBhIGZyZWUgYW5kIG9wZW4gc291cmNlIHNvZnR3YXJlBAxWZXJzaW9uVGV4dBQuVmlzaXQgZGlzY29yZC5nZy9yZXZlcnNlZHJvb21zIGZvciBtb3JlIGluZm8hAQAAAAEAAAAAAB8AAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAAAAAAEAAAAFX0VOVg==").unwrap()
}), }),
}, },
) )

View file

@ -1,7 +1,38 @@
use common::data::EXCEL_COLLECTION;
use super::*; use super::*;
pub async fn on_enter_scene_cs_req(session: &PlayerSession, body: &EnterSceneCsReq) -> Result<()> {
session
.send(CMD_ENTER_SCENE_SC_RSP, EnterSceneScRsp::default())
.await?;
let entrance_config = EXCEL_COLLECTION
.map_entrance_configs
.iter()
.find(|c| c.id == body.entry_id)
.unwrap();
let player = session.player_info();
let enter_scene_by_server = EnterSceneByServerScNotify {
reason: EnterSceneReason::None.into(),
lineup: Some(player.lineup.clone()),
scene: Some(SceneInfo {
plane_id: entrance_config.plane_id,
floor_id: entrance_config.floor_id,
entry_id: entrance_config.id,
game_mode_type: 2, // TODO: EntranceType -> enum repr(u32)
..Default::default()
}),
};
session
.send(CMD_ENTER_SCENE_BY_SERVER_SC_NOTIFY, enter_scene_by_server)
.await
}
pub async fn on_get_cur_scene_info_cs_req( pub async fn on_get_cur_scene_info_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &GetCurSceneInfoCsReq, _body: &GetCurSceneInfoCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -26,7 +57,7 @@ pub async fn on_get_cur_scene_info_cs_req(
avatar_type: 3, avatar_type: 3,
base_avatar_id: 1309, base_avatar_id: 1309,
map_layer: 2, map_layer: 2,
uid: 1337, uid: session.player_uid(),
}), }),
motion: Some(MotionInfo { motion: Some(MotionInfo {
aomilajjmii: Some(Vector { aomilajjmii: Some(Vector {

View file

@ -8,7 +8,7 @@ static TUTORIAL_IDS: [u32; 55] = [
]; ];
pub async fn on_get_tutorial_cs_req( pub async fn on_get_tutorial_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &GetTutorialCsReq, _body: &GetTutorialCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -29,7 +29,7 @@ pub async fn on_get_tutorial_cs_req(
} }
pub async fn on_get_tutorial_guide_cs_req( pub async fn on_get_tutorial_guide_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
_body: &GetTutorialGuideCsReq, _body: &GetTutorialGuideCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -44,7 +44,7 @@ pub async fn on_get_tutorial_guide_cs_req(
} }
pub async fn on_unlock_tutorial_guide_cs_req( pub async fn on_unlock_tutorial_guide_cs_req(
session: &mut PlayerSession, session: &PlayerSession,
body: &UnlockTutorialGuideCsReq, body: &UnlockTutorialGuideCsReq,
) -> Result<()> { ) -> Result<()> {
session session

View file

@ -62,13 +62,13 @@ macro_rules! trait_handler {
pub trait CommandHandler { pub trait CommandHandler {
$( $(
paste! { paste! {
async fn [<on_$name:snake>](session: &mut PlayerSession, body: &$name) -> Result<()> { async fn [<on_$name:snake>](session: &PlayerSession, body: &$name) -> Result<()> {
[<on_$name:snake>](session, body).await [<on_$name:snake>](session, body).await
} }
} }
)* )*
async fn on_message(session: &mut PlayerSession, cmd_type: u16, payload: Vec<u8>) -> Result<()> { async fn on_message(session: &PlayerSession, cmd_type: u16, payload: Vec<u8>) -> Result<()> {
use ::prost::Message; use ::prost::Message;
if PlayerSession::should_send_dummy_rsp(cmd_type) { if PlayerSession::should_send_dummy_rsp(cmd_type) {
session.send_dummy_response(cmd_type).await?; session.send_dummy_response(cmd_type).await?;
@ -641,18 +641,18 @@ trait_handler! {
// ExtraLineupDestroyNotify 763; // ExtraLineupDestroyNotify 763;
// GetLineupAvatarDataCsReq 768; // GetLineupAvatarDataCsReq 768;
// SwitchLineupIndexScRsp 795; // SwitchLineupIndexScRsp 795;
// JoinLineupCsReq 702; JoinLineupCsReq 702;
// GetAllLineupDataScRsp 716; // GetAllLineupDataScRsp 716;
// SetLineupNameCsReq 742; // SetLineupNameCsReq 742;
// ChangeLineupLeaderScRsp 733; // ChangeLineupLeaderScRsp 733;
ChangeLineupLeaderCsReq 706; ChangeLineupLeaderCsReq 706;
// ReplaceLineupCsReq 785; ReplaceLineupCsReq 785;
// SwapLineupCsReq 786; // SwapLineupCsReq 786;
// QuitLineupScRsp 743; // QuitLineupScRsp 743;
// GetLineupAvatarDataScRsp 796; // GetLineupAvatarDataScRsp 796;
// ReplaceLineupScRsp 756; // ReplaceLineupScRsp 756;
// GetStageLineupCsReq 734; // GetStageLineupCsReq 734;
// QuitLineupCsReq 719; QuitLineupCsReq 719;
// SetLineupNameScRsp 737; // SetLineupNameScRsp 737;
// SwitchLineupIndexCsReq 759; // SwitchLineupIndexCsReq 759;
GetCurLineupDataCsReq 762; GetCurLineupDataCsReq 762;
@ -1241,7 +1241,7 @@ trait_handler! {
// SetCurInteractEntityScRsp 1497; // SetCurInteractEntityScRsp 1497;
// SceneCastSkillCsReq 1402; // SceneCastSkillCsReq 1402;
StartCocoonStageCsReq 1408; StartCocoonStageCsReq 1408;
// GetSceneMapInfoCsReq 1470; GetSceneMapInfoCsReq 1470;
// SceneEntityMoveScRsp 1448; // SceneEntityMoveScRsp 1448;
// DeactivateFarmElementScRsp 1467; // DeactivateFarmElementScRsp 1467;
// SetCurInteractEntityCsReq 1491; // SetCurInteractEntityCsReq 1491;
@ -1263,7 +1263,7 @@ trait_handler! {
// GetSpringRecoverDataCsReq 1466; // GetSpringRecoverDataCsReq 1466;
// SceneEntityTeleportScRsp 1415; // SceneEntityTeleportScRsp 1415;
// SetClientPausedScRsp 1465; // SetClientPausedScRsp 1465;
// EnterSceneCsReq 1472; EnterSceneCsReq 1472;
// GetAllServerPrefsDataScRsp 6148; // GetAllServerPrefsDataScRsp 6148;
// GetServerPrefsDataCsReq 6162; // GetServerPrefsDataCsReq 6162;
// UpdateServerPrefsDataScRsp 6109; // UpdateServerPrefsDataScRsp 6109;

View file

@ -1,26 +1,38 @@
use anyhow::Result; use anyhow::Result;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use prost::Message; use prost::Message;
use tokio::{io::AsyncWriteExt, net::TcpStream}; use std::sync::Arc;
use tokio::{
io::AsyncWriteExt,
net::TcpStream,
sync::{Mutex, MutexGuard},
};
use crate::game::PlayerInfo;
use super::{packet::CommandHandler, NetPacket}; use super::{packet::CommandHandler, NetPacket};
pub struct PlayerSession { pub struct PlayerSession {
pub(crate) client_socket: TcpStream, client_socket: Arc<Mutex<TcpStream>>,
player_info: Arc<AtomicRefCell<PlayerInfo>>,
} }
impl PlayerSession { impl PlayerSession {
pub const fn new(client_socket: TcpStream) -> Self { pub fn new(client_socket: TcpStream) -> Self {
Self { client_socket } Self {
client_socket: Arc::new(Mutex::new(client_socket)),
player_info: Arc::new(AtomicRefCell::new(PlayerInfo::new())),
}
} }
pub async fn run(&mut self) -> Result<()> { pub async fn run(&mut self) -> Result<()> {
loop { loop {
let net_packet = NetPacket::read(&mut self.client_socket).await?; let net_packet = NetPacket::read(&mut *self.client_socket().await).await?;
Self::on_message(self, net_packet.cmd_type, net_packet.body).await?; Self::on_message(self, net_packet.cmd_type, net_packet.body).await?;
} }
} }
pub async fn send(&mut self, cmd_type: u16, body: impl Message) -> Result<()> { pub async fn send(&self, cmd_type: u16, body: impl Message) -> Result<()> {
let mut buf = Vec::new(); let mut buf = Vec::new();
body.encode(&mut buf)?; body.encode(&mut buf)?;
@ -31,9 +43,25 @@ impl PlayerSession {
} }
.into(); .into();
self.client_socket.write_all(&payload).await?; self.client_socket().await.write_all(&payload).await?;
Ok(()) Ok(())
} }
pub async fn client_socket(&self) -> MutexGuard<'_, TcpStream> {
self.client_socket.lock().await
}
pub fn player_uid(&self) -> u32 {
self.player_info().uid
}
pub fn player_info(&self) -> AtomicRef<PlayerInfo> {
self.player_info.borrow()
}
pub fn player_info_mut(&self) -> AtomicRefMut<PlayerInfo> {
self.player_info.borrow_mut()
}
} }
// Auto implemented // Auto implemented

View file

@ -6,6 +6,7 @@ version.workspace = true
[dependencies] [dependencies]
prost.workspace = true prost.workspace = true
prost-types.workspace = true prost-types.workspace = true
serde.workspace = true
[build-dependencies] [build-dependencies]
prost-build.workspace = true prost-build.workspace = true

View file

@ -1,10 +1,13 @@
use std::path::Path;
pub fn main() { pub fn main() {
let proto_file = "StarRail.proto"; let proto_file = "StarRail.proto";
if std::path::Path::new(proto_file).exists() { if Path::new(proto_file).exists() {
println!("cargo:rerun-if-changed={proto_file}"); println!("cargo:rerun-if-changed={proto_file}");
prost_build::Config::new() prost_build::Config::new()
.out_dir("out/") .out_dir("out/")
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]")
.compile_protos(&[proto_file], &["."]) .compile_protos(&[proto_file], &["."])
.unwrap(); .unwrap();
} }

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

View file

@ -4,11 +4,20 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
common.workspace = true
anyhow.workspace = true anyhow.workspace = true
env_logger.workspace = true env_logger.workspace = true
axum.workspace = true axum.workspace = true
axum-server.workspace = true axum-server.workspace = true
hyper.workspace = true
hyper-util.workspace = true
dirs.workspace = true
dotenv.workspace = true
lazy_static.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
@ -23,6 +32,6 @@ tracing-subscriber.workspace = true
tracing-bunyan-formatter.workspace = true tracing-bunyan-formatter.workspace = true
ansi_term.workspace = true ansi_term.workspace = true
prost.workspace = true tower.workspace = true
rbase64.workspace = true tower-http.workspace = true
proto.workspace = true

4
sdkserver/sdkserver.json Normal file
View file

@ -0,0 +1,4 @@
{
"http_port": 21000,
"dispatch_endpoint": "http://127.0.0.1:21041"
}

23
sdkserver/src/config.rs Normal file
View file

@ -0,0 +1,23 @@
use common::util::load_or_create_config;
use lazy_static::lazy_static;
use serde::Deserialize;
use serde_json::from_str;
const DEFAULT_CONFIG: &str = include_str!("../sdkserver.json");
pub fn init_config() {
let _configuration = &*CONFIGURATION;
}
#[derive(Deserialize)]
pub struct SDKServerConfiguration {
pub http_port: u16,
pub dispatch_endpoint: String,
}
lazy_static! {
pub static ref CONFIGURATION: SDKServerConfiguration = {
let data = load_or_create_config("sdkserver.json", DEFAULT_CONFIG);
from_str(&data).unwrap()
};
}

View file

@ -1,29 +0,0 @@
#[macro_export]
macro_rules! log_error {
($e:expr) => {
if let Err(e) = $e {
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
}
};
($context:expr, $e:expr $(,)?) => {
if let Err(e) = $e {
let e = format!("{:?}", ::anyhow::anyhow!(e).context($context));
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
}
};
($ok_context:expr, $err_context:expr, $e:expr $(,)?) => {
if let Err(e) = $e {
let e = format!("{:?}", ::anyhow::anyhow!(e).context($err_context));
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
} else {
tracing::info!($ok_context);
}
};
}
pub fn init_tracing() {
#[cfg(target_os = "windows")]
ansi_term::enable_ansi_support().unwrap();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
}

View file

@ -1,30 +1,43 @@
use anyhow::Result; use anyhow::Result;
use axum::body::Body;
use axum::extract::Request;
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::Router; use axum::{Router, ServiceExt};
use logging::init_tracing; use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor};
use services::{auth, dispatch, errors}; use services::{auth, errors};
use tokio::net::TcpListener;
use tower::Layer;
use tower_http::normalize_path::NormalizePathLayer;
use tracing::Level; use tracing::Level;
mod logging; type Client = hyper_util::client::legacy::Client<HttpConnector, Body>;
mod config;
mod services; mod services;
const PORT: u16 = 21000; use common::logging::init_tracing;
use config::{init_config, CONFIGURATION};
use services::reverse_proxy;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
init_tracing(); init_tracing();
init_config();
let span = tracing::span!(Level::DEBUG, "main"); let span = tracing::span!(Level::DEBUG, "main");
let _ = span.enter(); let _ = span.enter();
let router = Router::new() // For dispatch reverse proxy
let client: Client =
hyper_util::client::legacy::Client::<(), ()>::builder(TokioExecutor::new())
.build(HttpConnector::new());
let app = Router::new()
.route("/query_dispatch", get(reverse_proxy::forward_to_dispatch))
.route( .route(
dispatch::QUERY_DISPATCH_ENDPOINT, "/query_gateway/:region_name",
get(dispatch::query_dispatch), get(reverse_proxy::forward_to_dispatch),
)
.route(
dispatch::QUERY_GATEWAY_ENDPOINT,
get(dispatch::query_gateway),
) )
.route(auth::RISKY_API_CHECK_ENDPOINT, post(auth::risky_api_check)) .route(auth::RISKY_API_CHECK_ENDPOINT, post(auth::risky_api_check))
.route( .route(
@ -39,13 +52,16 @@ async fn main() -> Result<()> {
auth::GRANTER_LOGIN_VERIFICATION_ENDPOINT, auth::GRANTER_LOGIN_VERIFICATION_ENDPOINT,
post(auth::granter_login_verification), post(auth::granter_login_verification),
) )
.fallback(errors::not_found); .fallback(errors::not_found)
.with_state(client);
let addr = format!("0.0.0.0:{PORT}"); let app = NormalizePathLayer::trim_trailing_slash().layer(app);
let server = axum_server::bind(addr.parse()?);
let addr = format!("0.0.0.0:{}", CONFIGURATION.http_port);
let server = TcpListener::bind(&addr).await?;
tracing::info!("sdkserver is listening at {addr}"); tracing::info!("sdkserver is listening at {addr}");
server.serve(router.into_make_service()).await?; axum::serve(server, ServiceExt::<Request>::into_make_service(app)).await?;
Ok(()) Ok(())
} }

View file

@ -1,7 +1,7 @@
use axum::Json; use axum::Json;
use serde_json::json; use serde_json::json;
pub const LOGIN_WITH_PASSWORD_ENDPOINT: &str = "/:product_name/mdk/shield/api/login/"; pub const LOGIN_WITH_PASSWORD_ENDPOINT: &str = "/:product_name/mdk/shield/api/login";
pub const LOGIN_WITH_SESSION_TOKEN_ENDPOINT: &str = "/:product_name/mdk/shield/api/verify"; pub const LOGIN_WITH_SESSION_TOKEN_ENDPOINT: &str = "/:product_name/mdk/shield/api/verify";
pub const GRANTER_LOGIN_VERIFICATION_ENDPOINT: &str = "/:product_name/combo/granter/login/v2/login"; pub const GRANTER_LOGIN_VERIFICATION_ENDPOINT: &str = "/:product_name/combo/granter/login/v2/login";
pub const RISKY_API_CHECK_ENDPOINT: &str = "/account/risky/api/check"; pub const RISKY_API_CHECK_ENDPOINT: &str = "/account/risky/api/check";
@ -12,7 +12,7 @@ pub async fn login_with_password() -> Json<serde_json::Value> {
"data": { "data": {
"account": { "account": {
"area_code": "**", "area_code": "**",
"email": "ReversedRooms", "email": "AcheronSR",
"country": "RU", "country": "RU",
"is_email_verify": "1", "is_email_verify": "1",
"token": "mostsecuretokenever", "token": "mostsecuretokenever",
@ -34,7 +34,7 @@ pub async fn login_with_session_token() -> Json<serde_json::Value> {
"data": { "data": {
"account": { "account": {
"area_code": "**", "area_code": "**",
"email": "ReversedRooms", "email": "AcheronSR",
"country": "RU", "country": "RU",
"is_email_verify": "1", "is_email_verify": "1",
"token": "mostsecuretokenever", "token": "mostsecuretokenever",

View file

@ -1,65 +0,0 @@
use prost::Message;
use proto::{Dispatch, Gateserver, RegionInfo};
pub const QUERY_DISPATCH_ENDPOINT: &str = "/query_dispatch";
pub const QUERY_GATEWAY_ENDPOINT: &str = "/query_gateway";
#[tracing::instrument]
pub async fn query_dispatch() -> String {
let rsp = Dispatch {
retcode: 0,
region_list: vec![RegionInfo {
name: String::from("RobinSR"),
title: String::from("RobinSR"),
env_type: String::from("9"),
dispatch_url: String::from("http://127.0.0.1:21000/query_gateway"),
..Default::default()
}],
..Default::default()
};
let mut buff = Vec::new();
rsp.encode(&mut buff).unwrap();
rbase64::encode(&buff)
}
#[tracing::instrument]
pub async fn query_gateway() -> String {
let rsp = Gateserver {
retcode: 0,
ip: String::from("127.0.0.1"),
port: 23301,
asset_bundle_url: String::from(
"https://autopatchcn.bhsr.com/asb/BetaLive/output_6744505_89b2f5dc973e",
),
lua_url: String::from(
"https://autopatchcn.bhsr.com/lua/BetaLive/output_6755976_3c46d7c46e2c",
),
ex_resource_url: String::from(
"https://autopatchcn.bhsr.com/design_data/BetaLive/output_6759713_b4e0e740f0da",
),
ifix_version: String::from("0"),
lua_version: String::from("6755976"),
jblkncaoiao: true,
hjdjakjkdbi: true,
ldknmcpffim: true,
feehapamfci: true,
eebfeohfpph: true,
dfmjjcfhfea: true,
najikcgjgan: true,
giddjofkndm: true,
fbnbbembcgn: false,
dedgfjhbnok: false,
use_tcp: true,
linlaijbboh: false,
ahmbfbkhmgh: false,
nmdccehcdcc: false,
..Default::default()
};
let mut buff = Vec::new();
rsp.encode(&mut buff).unwrap();
rbase64::encode(&buff)
}

View file

@ -1,3 +1,3 @@
pub mod auth; pub mod auth;
pub mod dispatch;
pub mod errors; pub mod errors;
pub mod reverse_proxy;

View file

@ -0,0 +1,33 @@
use axum::{
body::Body,
extract::{Request, State},
http::uri::{PathAndQuery, Uri},
response::{IntoResponse, Response},
};
use hyper::StatusCode;
use hyper_util::client::legacy::connect::HttpConnector;
use crate::config::CONFIGURATION;
type Client = hyper_util::client::legacy::Client<HttpConnector, Body>;
pub async fn forward_to_dispatch(
State(client): State<Client>,
mut req: Request,
) -> Result<Response, StatusCode> {
let path = req.uri().path();
let path_query = req
.uri()
.path_and_query()
.map_or(path, PathAndQuery::as_str);
let uri = format!("{}{}", CONFIGURATION.dispatch_endpoint, path_query);
*req.uri_mut() = Uri::try_from(uri).unwrap();
Ok(client
.request(req)
.await
.map_err(|_| StatusCode::BAD_REQUEST)?
.into_response())
}

7
xtask/Cargo.toml Normal file
View file

@ -0,0 +1,7 @@
[package]
name = "xtask"
edition = "2021"
version.workspace = true
[dependencies]
notify = "6.1.1"

112
xtask/src/main.rs Normal file
View file

@ -0,0 +1,112 @@
use std::{io, process::Command, sync::mpsc, thread};
fn print_help() {
println!(
"
xtask must specify a task to run.
Usage: `cargo xtask <task>`
Tasks:
run
Run the gameserver and sdkserver.
watch
Watch for changes in the project and restart the servers if any file changes.
"
);
}
// run gameserver and sdkserver, wait till any of them exit
fn spawn_servers(release: bool) -> Result<(), Box<dyn std::error::Error>> {
let (tx, rx) = mpsc::channel();
let tx1 = tx.clone();
let handle1 = thread::spawn(move || {
let mut gameserver = Command::new("cargo")
.arg("run")
.arg("--bin")
.arg("gameserver")
.args(if release { vec!["--release"] } else { vec![] })
.spawn()
.expect("failed to start gameserver");
gameserver.wait()?;
tx1.send(()).expect("failed to send completion signal");
Ok::<(), io::Error>(())
});
let tx2 = tx.clone();
let handle2 = thread::spawn(move || {
let mut sdkserver = Command::new("cargo")
.arg("run")
.arg("--bin")
.arg("sdkserver")
.args(if release { vec!["--release"] } else { vec![] })
.spawn()
.expect("failed to start sdkserver");
let _ = sdkserver.wait()?;
tx2.send(()).expect("failed to send completion signal");
Ok::<(), io::Error>(())
});
let handle3 = thread::spawn(move || {
let mut dispatch = Command::new("cargo")
.arg("run")
.arg("--bin")
.arg("dispatch")
.args(if release { vec!["--release"] } else { vec![] })
.spawn()
.expect("failed to start dispatch");
let _ = dispatch.wait()?;
tx.send(()).expect("failed to send completion signal");
Ok::<(), io::Error>(())
});
rx.recv().expect("failed to receive from channel");
handle1.join().expect("failed to join gameserver thread")?;
handle2.join().expect("failed to join sdkserver thread")?;
handle3.join().expect("failed to join dispatch thread")?;
Ok(())
}
// watch for changes in the project and restart the servers if any file changes
fn watch(release: bool) -> Result<(), Box<dyn std::error::Error>> {
let mut cmd = std::process::Command::new("cargo");
cmd.arg("watch").arg("-x").arg(format!(
"xtask run {}",
if release { "--release" } else { "" }
));
let mut child = cmd.spawn()?;
child.wait()?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let Some(task) = std::env::args().nth(1) else {
print_help();
std::process::exit(0);
};
let release = std::env::args().any(|arg| arg == "--release");
match task.as_str() {
"run" => spawn_servers(release)?,
"watch" => watch(release)?,
_ => {
println!("invalid task: `{task}`, run `cargo xtask` for a list of tasks");
std::process::exit(1);
}
}
Ok(())
}