Compare commits

..

No commits in common. "master" and "0.1.1" have entirely different histories.

54 changed files with 406 additions and 7381 deletions

View file

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

1
.gitignore vendored
View file

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

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["common", "dispatch", "gameserver", "proto", "sdkserver", "xtask"] members = [ "gameserver", "proto", "sdkserver"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
@ -13,10 +13,6 @@ 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" dirs = "5.0.1"
dotenv = "0.15.0" dotenv = "0.15.0"
@ -53,7 +49,6 @@ 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.
AcheronSR RobinSR
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:
AcheronSR Copyright (C) 2024 reversedrooms RobinSR 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,7 +1,7 @@
# AcheronSR # RobinSR
A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/en-us/) 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) ![screenshot](https://git.xeondev.com/reversedrooms/RobinSR/raw/branch/master/screenshot.png)
## Installation ## Installation
@ -11,56 +11,52 @@ A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/e
- [Rust](https://www.rust-lang.org/tools/install) - [Rust](https://www.rust-lang.org/tools/install)
**NOTE**: Nightly Rust is required to build the project. To install it, first install
Rust itself, then run the following command:
```sh
rustup toolchain install nightly
rustup default nightly
```
#### Building #### Building
##### Manually
```sh ```sh
git clone https://git.xeondev.com/reversedrooms/AcheronSR.git git clone https://git.xeondev.com/reversedrooms/RobinSR.git
cd AcheronSR cd RobinSR
cargo build --bin gameserver cargo install --path gameserver
cargo build --bin dispatch cargo install --path sdkserver
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 ### From Pre-built Binaries
Navigate to the [Releases](https://git.xeondev.com/reversedrooms/AcheronSR/releases) Navigate to the [Releases](https://git.xeondev.com/reversedrooms/RobinSR/releases)
page and download the latest release for your platform. page and download the latest release for your platform.
## Usage ## Usage
To begin using the server, you need to run both the SDK server and the game server. To begin using the server, you need to run both the SDK server and the game server.
If you installed from source, Rust's installer should have added .cargo/bin to your
path, so simply run the following:
```sh
gameserver
sdkserver
```
If you installed from pre-built binaries, navigate to the directory where you downloaded 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) the binaries and either a) double-click on the following executable names or b)
run the following in a terminal: run the following in a terminal:
```sh ```sh
./gameserver ./gameserver
./dispatch
./sdkserver ./sdkserver
``` ```
##### Note: the `assets` folder should be in the same directory with the `gameserver`, otherwise it will panic.
## Connecting ## Connecting
[Get 2.2 beta client](https://bhrpg-prod.oss-accelerate.aliyuncs.com/client/beta/20240322124944_scfGE0xJXlWtoJ1r/StarRail_2.1.51.zip), [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) replace [mhypbase.dll](https://git.xeondev.com/reversedrooms/RobinSR/raw/branch/master/mhypbase.dll) file in your game folder, it will redirect game traffic (and also disable in-game censorship)
file in your game folder, it will redirect game traffic (and disable in-game censorship)
## Contributing ## Contributing

File diff suppressed because it is too large Load diff

View file

@ -1,13 +0,0 @@
[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

View file

@ -1,20 +0,0 @@
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,
}

View file

@ -1,37 +0,0 @@
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()
}

View file

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

View file

@ -1,7 +0,0 @@
#[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()
})
}

View file

@ -1,34 +0,0 @@
[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

View file

@ -1,34 +0,0 @@
{
"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"
}
}
}

View file

@ -1,51 +0,0 @@
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()
};
}

View file

@ -1,85 +0,0 @@
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)
}

View file

@ -1,35 +0,0 @@
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(())
}

1
gameserver/.env Normal file
View file

@ -0,0 +1 @@
LINEUP=1309,1308,1307,1315

View file

@ -4,8 +4,6 @@ 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

View file

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

View file

@ -1,19 +0,0 @@
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,16 @@
use std::env;
use lazy_static::lazy_static;
lazy_static! {
pub static ref LINEUP: Vec<u32> = {
let lineup_str = env::var("LINEUP").expect("Missing .env parameter: LINEUP");
let mut lineup = Vec::new();
for s in lineup_str.trim().replace(" ", "").split(",") {
lineup.push(s.parse().unwrap());
}
lineup
};
}

View file

@ -1,9 +1 @@
mod global_config; pub mod globals;
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

@ -1,59 +0,0 @@
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,20 +1,42 @@
use anyhow::Result; use anyhow::Result;
mod game; mod game;
mod logging;
mod net; mod net;
mod util; mod util;
use common::data::init_assets; use logging::init_tracing;
use common::logging::init_tracing;
use game::init_config; const DEFAULT_DOTENV: &str = include_str!("../.env");
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
init_tracing(); init_tracing();
init_config(); init_config()?;
init_assets();
net::gateway::listen("0.0.0.0", 23301).await?; net::gateway::listen("0.0.0.0", 23301).await?;
Ok(())
}
fn init_config() -> Result<()> {
let local_dotenv = std::path::Path::new(".env");
if local_dotenv.exists() {
dotenv::dotenv()?;
} else {
let config = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("No config directory found"))?
.join("hkrpg-gameserver");
std::fs::create_dir_all(&config)?;
let env = config.join(".env");
if !env.exists() {
std::fs::write(&env, DEFAULT_DOTENV)?;
}
dotenv::from_path(&env)?;
}
Ok(()) Ok(())
} }

View file

@ -1,9 +1,8 @@
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::net::PlayerSession; use crate::{log_error, 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: &PlayerSession, session: &mut 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: session.player_uid(), uid: 1337,
..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: &PlayerSession, session: &mut 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("AcheronSR"), nickname: String::from("xeondev"),
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: &PlayerSession, session: &mut PlayerSession,
body: &GetAvatarDataCsReq, body: &GetAvatarDataCsReq,
) -> Result<()> { ) -> Result<()> {
session session

View file

@ -2,11 +2,9 @@ use super::*;
use crate::game::globals; use crate::game::globals;
pub async fn on_start_cocoon_stage_cs_req( pub async fn on_start_cocoon_stage_cs_req(
session: &PlayerSession, session: &mut 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,
@ -16,13 +14,12 @@ 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: player_info battle_avatar_list: globals::LINEUP
.lineup
.avatar_list
.iter() .iter()
.map(|avatar| BattleAvatar { .enumerate()
index: avatar.slot, .map(|(idx, id)| BattleAvatar {
id: avatar.id, index: idx as u32,
id: *id,
level: 80, level: 80,
promotion: 6, promotion: 6,
rank: 6, rank: 6,
@ -35,20 +32,13 @@ pub async fn on_start_cocoon_stage_cs_req(
..Default::default() ..Default::default()
}) })
.collect(), .collect(),
monster_wave_list: globals monster_wave_list: vec![SceneMonsterWave {
.monster_wave_list monster_list: vec![SceneMonsterParam {
.iter() monster_id: 3013010,
.map(|monster_list| SceneMonsterWave {
monster_list: monster_list
.iter()
.map(|id| SceneMonsterParam {
monster_id: *id,
..Default::default()
})
.collect(),
..Default::default() ..Default::default()
}) }],
.collect(), ..Default::default()
}],
..Default::default() ..Default::default()
}), }),
}; };
@ -57,7 +47,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: &PlayerSession, session: &mut PlayerSession,
body: &PveBattleResultCsReq, body: &PveBattleResultCsReq,
) -> Result<()> { ) -> Result<()> {
session session

View file

@ -1,47 +1,81 @@
use super::*; use super::*;
use crate::game::globals;
pub async fn on_get_all_lineup_data_cs_req( pub async fn on_get_all_lineup_data_cs_req(
session: &PlayerSession, session: &mut 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![player_info.lineup.clone()], lineup_list: vec![LineupInfo {
plane_id: 10001,
name: String::from("Lineup 1"),
index: 0,
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()
}],
}, },
) )
.await .await
} }
pub async fn on_get_cur_lineup_data_cs_req( pub async fn on_get_cur_lineup_data_cs_req(
session: &PlayerSession, session: &mut 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(player_info.lineup.clone()), lineup: Some(LineupInfo {
plane_id: 10001,
name: String::from("Lineup 1"),
index: 0,
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()
}),
}, },
) )
.await .await
} }
pub async fn on_change_lineup_leader_cs_req( pub async fn on_change_lineup_leader_cs_req(
session: &PlayerSession, session: &mut 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,
@ -52,102 +86,3 @@ 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

@ -1,27 +0,0 @@
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: &PlayerSession, session: &mut PlayerSession,
body: &GetMissionStatusCsReq, body: &GetMissionStatusCsReq,
) -> Result<()> { ) -> Result<()> {
let rsp = GetMissionStatusScRsp { let rsp = GetMissionStatusScRsp {

View file

@ -2,7 +2,6 @@ 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;
@ -20,7 +19,6 @@ 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::*;
@ -48,7 +46,7 @@ macro_rules! dummy {
} }
} }
pub async fn send_dummy_response(&self, req_id: u16) -> Result<()> { pub async fn send_dummy_response(&mut 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,
@ -63,7 +61,7 @@ macro_rules! dummy {
} }
.into(); .into();
self.client_socket().await.write_all(&payload).await?; self.client_socket.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: &PlayerSession, session: &mut 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: &PlayerSession, session: &mut 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: &PlayerSession, session: &mut 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("G0x1YVMBGZMNChoKBAQICHhWAAAAAAAAAAAAAAAod0ABKEBDOlxVc2Vyc1x4ZW9uZGV2XERvd25sb2Fkc1xhY2hlcm9uLmx1YQAAAAAAAAAAAAEEHwAAACQAQAApQEAAKYBAACnAQABWAAEALIAAAR1AQQCkgEEA5ABAAOnAwQHpAMIB6UDCAawAAAEsgAAAH8BChSQAQAApQEAAKYBAACnAQABWAAMALIAAAR1AQQCkgEEA5ABAAOnAwQHpAMIB6UDCAawAAAEsgAAAH0BDhRkAgAAOAAAABANDUwQMVW5pdHlFbmdpbmUEC0dhbWVPYmplY3QEBUZpbmQEKVVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkEF0dldENvbXBvbmVudEluQ2hpbGRyZW4EB3R5cGVvZgQEUlBHBAdDbGllbnQEDkxvY2FsaXplZFRleHQEBXRleHQULUFjaGVyb25TUiBpcyBhIGZyZWUgYW5kIG9wZW4gc291cmNlIHNvZnR3YXJlBAxWZXJzaW9uVGV4dBQuVmlzaXQgZGlzY29yZC5nZy9yZXZlcnNlZHJvb21zIGZvciBtb3JlIGluZm8hAQAAAAEAAAAAAB8AAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAgAAAAIAAAACAAAAAAAAAAEAAAAFX0VOVg==").unwrap() data: rbase64::decode("G0x1YVMBGZMNChoKBAQICHhWAAAAAAAAAAAAAAAod0ABKEBDOlxVc2Vyc1x4ZW9uZGV2XERvd25sb2Fkc1xyYWJzdHZvLmx1YQAAAAAAAAAAAAEEEAAAACQAQAApQEAAKYBAACnAQABWAAEALIAAAR1AQQCkgEEA5ABAAOnAwQHpAMIB6UDCAawAAAEsgAAAH8BChRkAgAAMAAAABANDUwQMVW5pdHlFbmdpbmUEC0dhbWVPYmplY3QEBUZpbmQEKVVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkEF0dldENvbXBvbmVudEluQ2hpbGRyZW4EB3R5cGVvZgQEUlBHBAdDbGllbnQEDkxvY2FsaXplZFRleHQEBXRleHQURVJvYmluU1IgaXMgYSBmcmVlIGFuZCBvcGVuIHNvdXJjZSBzb2Z0d2FyZS4gZGlzY29yZC5nZy9yZXZlcnNlZHJvb21zAQAAAAEAAAAAABAAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAAAAAAEAAAAFX0VOVg==").unwrap()
}), }),
}, },
) )

View file

@ -1,38 +1,7 @@
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: &PlayerSession, session: &mut PlayerSession,
_body: &GetCurSceneInfoCsReq, _body: &GetCurSceneInfoCsReq,
) -> Result<()> { ) -> Result<()> {
session session
@ -57,7 +26,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: session.player_uid(), uid: 1337,
}), }),
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: &PlayerSession, session: &mut 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: &PlayerSession, session: &mut 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: &PlayerSession, session: &mut 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: &PlayerSession, body: &$name) -> Result<()> { async fn [<on_$name:snake>](session: &mut PlayerSession, body: &$name) -> Result<()> {
[<on_$name:snake>](session, body).await [<on_$name:snake>](session, body).await
} }
} }
)* )*
async fn on_message(session: &PlayerSession, cmd_type: u16, payload: Vec<u8>) -> Result<()> { async fn on_message(session: &mut 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,38 +1,26 @@
use anyhow::Result; use anyhow::Result;
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use prost::Message; use prost::Message;
use std::sync::Arc; use tokio::{io::AsyncWriteExt, net::TcpStream};
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 {
client_socket: Arc<Mutex<TcpStream>>, pub(crate) client_socket: TcpStream,
player_info: Arc<AtomicRefCell<PlayerInfo>>,
} }
impl PlayerSession { impl PlayerSession {
pub fn new(client_socket: TcpStream) -> Self { pub const fn new(client_socket: TcpStream) -> Self {
Self { Self { client_socket }
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).await?; let net_packet = NetPacket::read(&mut self.client_socket).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(&self, cmd_type: u16, body: impl Message) -> Result<()> { pub async fn send(&mut 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)?;
@ -43,25 +31,9 @@ impl PlayerSession {
} }
.into(); .into();
self.client_socket().await.write_all(&payload).await?; self.client_socket.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,7 +6,6 @@ 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,13 +1,10 @@
use std::path::Path;
pub fn main() { pub fn main() {
let proto_file = "StarRail.proto"; let proto_file = "StarRail.proto";
if Path::new(proto_file).exists() { if std::path::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: 2.7 MiB

After

Width:  |  Height:  |  Size: 4.7 MiB

4
sdkserver/.env Normal file
View file

@ -0,0 +1,4 @@
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

View file

@ -4,21 +4,15 @@ 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 dirs.workspace = true
dotenv.workspace = true dotenv.workspace = true
lazy_static.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
@ -32,6 +26,6 @@ tracing-subscriber.workspace = true
tracing-bunyan-formatter.workspace = true tracing-bunyan-formatter.workspace = true
ansi_term.workspace = true ansi_term.workspace = true
tower.workspace = true prost.workspace = true
tower-http.workspace = true rbase64.workspace = true
proto.workspace = true

View file

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

View file

@ -1,23 +0,0 @@
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()
};
}

29
sdkserver/src/logging.rs Normal file
View file

@ -0,0 +1,29 @@
#[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,43 +1,33 @@
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, ServiceExt}; use axum::Router;
use hyper_util::{client::legacy::connect::HttpConnector, rt::TokioExecutor}; use logging::init_tracing;
use services::{auth, errors}; use services::{auth, dispatch, errors};
use tokio::net::TcpListener; use std::path::Path;
use tower::Layer;
use tower_http::normalize_path::NormalizePathLayer;
use tracing::Level; use tracing::Level;
type Client = hyper_util::client::legacy::Client<HttpConnector, Body>; mod logging;
mod config;
mod services; mod services;
use common::logging::init_tracing; const PORT: u16 = 21000;
const DEFAULT_DOTENV: &str = include_str!("../.env");
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(); init_config()?;
let span = tracing::span!(Level::DEBUG, "main"); let span = tracing::span!(Level::DEBUG, "main");
let _ = span.enter(); let _ = span.enter();
// For dispatch reverse proxy let router = Router::new()
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(
"/query_gateway/:region_name", dispatch::QUERY_DISPATCH_ENDPOINT,
get(reverse_proxy::forward_to_dispatch), get(dispatch::query_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(
@ -52,16 +42,36 @@ 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 app = NormalizePathLayer::trim_trailing_slash().layer(app); let addr = format!("0.0.0.0:{PORT}");
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}");
axum::serve(server, ServiceExt::<Request>::into_make_service(app)).await?; server.serve(router.into_make_service()).await?;
Ok(())
}
fn init_config() -> Result<()> {
let local_dotenv = Path::new(".env");
if local_dotenv.exists() {
dotenv::dotenv()?;
} else {
let config = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("No config directory found"))?
.join("hkrpg-sdkserver");
std::fs::create_dir_all(&config)?;
let env = config.join(".env");
if !env.exists() {
std::fs::write(&env, DEFAULT_DOTENV)?;
}
dotenv::from_path(&env)?;
}
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": "AcheronSR", "email": "ReversedRooms",
"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": "AcheronSR", "email": "ReversedRooms",
"country": "RU", "country": "RU",
"is_email_verify": "1", "is_email_verify": "1",
"token": "mostsecuretokenever", "token": "mostsecuretokenever",

View file

@ -0,0 +1,61 @@
use std::env;
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: env::var("ASSET_BUNDLE_URL").unwrap(),
ex_resource_url: env::var("EX_RESOURCE_URL").unwrap(),
lua_url: env::var("LUA_URL").unwrap(),
lua_version: env::var("LUA_VERSION").unwrap(),
ifix_version: String::from("0"),
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

@ -1,33 +0,0 @@
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())
}

View file

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

View file

@ -1,112 +0,0 @@
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(())
}