refactor: Refactor json loading mechanism & more

- Move json loading into separate crate `common`
- Add new http route for handling SRTools API
- Listen to `freesr-data.json` file change, and sync with client immediately
- Move json loading into `PlayerSession`, instead of load it everytime
- Implement global buff for Castorice
- Implement `GetBigDataAllRecommendCsReq`
This commit is contained in:
amizing25 2025-03-03 08:02:51 +07:00
parent 50a05a5cc2
commit de22105514
28 changed files with 7113 additions and 12024 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
target/ target/
Cargo.lock Cargo.lock
proto/StarRail.proto proto/StarRail.proto
/prebuild
/assets
.vscode

View File

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["gameserver", "proto", "sdkserver"] members = ["gameserver", "proto", "sdkserver", "common"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
@ -13,6 +13,7 @@ lazy_static = "1.4.0"
axum = "0.8.1" axum = "0.8.1"
axum-server = "0.7.1" axum-server = "0.7.1"
tower-http = "0.6.2"
env_logger = "0.11.3" env_logger = "0.11.3"
@ -32,6 +33,8 @@ prost-build = "0.13.5"
paste = "1.0.14" paste = "1.0.14"
sysinfo = "0.33.1" sysinfo = "0.33.1"
notify = "8.0.0"
notify-debouncer-mini = "0.6.0"
hex = "0.4.3" hex = "0.4.3"
@ -56,6 +59,7 @@ tracing-bunyan-formatter = "0.3.9"
proto = { path = "proto/" } proto = { path = "proto/" }
proto-derive = { path = "proto/proto-derive" } proto-derive = { path = "proto/proto-derive" }
mhy-kcp = { path = "kcp/", features = ["tokio"] } mhy-kcp = { path = "kcp/", features = ["tokio"] }
common = { path = "common/" }
[profile.release] [profile.release]

View File

@ -1,4 +1,4 @@
# Supported Version: 2.6.5x # Supported Version: 3.1.5x
Run the game by clicking run.bat file. Run the game by clicking run.bat file.
Tool website: [https://srtools.pages.dev](https://srtools.pages.dev) Tool website: [https://srtools.pages.dev](https://srtools.pages.dev)

11
common/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "common"
edition = "2024"
version.workspace = true
[dependencies]
proto.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tracing.workspace = true

View File

@ -1 +1,2 @@
pub mod resources; pub mod resources;
pub mod sr_tools;

View File

@ -696,6 +696,10 @@ impl FreesrData {
json json
} }
pub async fn update(&mut self) {
*self = Self::load().await
}
async fn verify_lineup(&mut self) { async fn verify_lineup(&mut self) {
if self.lineups.is_empty() { if self.lineups.is_empty() {
self.lineups = BTreeMap::<u32, u32>::from([(0, 8001), (1, 0), (2, 0), (3, 0)]) self.lineups = BTreeMap::<u32, u32>::from([(0, 8001), (1, 0), (2, 0), (3, 0)])
@ -704,16 +708,10 @@ impl FreesrData {
self.lineups.insert(i as u32, 0); self.lineups.insert(i as u32, 0);
} }
} }
self.save().await; self.save_persistent().await;
} }
pub async fn save_lineup(&self) { pub async fn save_persistent(&self) {
self.save().await;
}
pub async fn save(&self) {
let json = serde_json::to_string_pretty(&self).unwrap();
let _ = tokio::fs::write("freesr-data.json", json.as_bytes()).await;
let _ = tokio::fs::write( let _ = tokio::fs::write(
"persistent", "persistent",
serde_json::to_string_pretty(&Persistent { serde_json::to_string_pretty(&Persistent {
@ -723,10 +721,10 @@ impl FreesrData {
scene: self.scene.clone(), scene: self.scene.clone(),
march_type: self.march_type, march_type: self.march_type,
}) })
.unwrap() .unwrap(),
.as_bytes(),
) )
.await; .await;
tracing::info!("persistent saved");
} }
pub fn get_multi_path_info(&self) -> Vec<MultiPathAvatarTypeInfo> { pub fn get_multi_path_info(&self) -> Vec<MultiPathAvatarTypeInfo> {

View File

@ -12,7 +12,8 @@ hex.workspace = true
lazy_static.workspace = true lazy_static.workspace = true
paste.workspace = true paste.workspace = true
rbase64.workspace = true rbase64.workspace = true
sysinfo.workspace = true notify.workspace = true
notify-debouncer-mini.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
@ -32,3 +33,4 @@ proto-derive.workspace = true
rand.workspace = true rand.workspace = true
mhy-kcp.workspace = true mhy-kcp.workspace = true
common.workspace = true

View File

@ -3,7 +3,6 @@ use anyhow::Result;
mod logging; mod logging;
mod net; mod net;
mod tools;
mod util; mod util;
use logging::init_tracing; use logging::init_tracing;

View File

@ -1,20 +1,24 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
net::SocketAddr, net::SocketAddr,
path::Path,
sync::{ sync::{
atomic::{AtomicU32, Ordering},
Arc, Arc,
atomic::{AtomicU32, Ordering},
}, },
time::Duration,
}; };
use anyhow::Result; use anyhow::Result;
use common::sr_tools::FreesrData;
use rand::RngCore; use rand::RngCore;
use crate::net::PlayerSession; use crate::net::PlayerSession;
use tokio::{ use tokio::{
net::UdpSocket, net::UdpSocket,
sync::{Mutex, RwLock}, sync::{Mutex, RwLock, mpsc},
}; };
use crate::net::packet::NetOperation; use crate::net::packet::NetOperation;
@ -74,15 +78,79 @@ impl Gateway {
let (conv_id, session_token) = self.next_conv_pair(); let (conv_id, session_token) = self.next_conv_pair();
tracing::info!("New connection from addr: {addr} with conv_id: {conv_id}"); tracing::info!("New connection from addr: {addr} with conv_id: {conv_id}");
self.sessions.lock().await.insert( let session = Arc::new(RwLock::new(PlayerSession::new(
self.socket.clone(),
addr,
conv_id, conv_id,
Arc::new(RwLock::new(PlayerSession::new( session_token,
self.socket.clone(), )));
addr,
conv_id, // Init the json to session
session_token, let _ = session
))), .write()
); .await
.json_data
.set(FreesrData::load().await);
let session_ref = session.clone();
// FS watcher
tokio::spawn(async move {
let (tx, mut rx) = mpsc::channel(100);
let mut debouncer =
notify_debouncer_mini::new_debouncer(Duration::from_millis(1000), move |ev| {
let _ = tx.blocking_send(ev);
})
.unwrap();
let path = Path::new("freesr-data.json");
debouncer
.watcher()
.watch(path, notify::RecursiveMode::NonRecursive)
.unwrap();
tracing::info!("watching freesr-data.json changes");
let mut shutdown_rx = session.read().await.shutdown_rx.clone();
loop {
tokio::select! {
res = rx.recv() => {
let Some(res) = res else {
break;
};
match res {
Ok(events) => {
if events
.iter()
.any(|p| p.path.file_name() == path.file_name())
{
let mut session = session.write().await;
if let Some(json) = session.json_data.get_mut() {
let _ = json.update().await;
session.sync_player().await;
tracing::info!("json updated")
}
}
}
Err(e) => eprintln!("json watcher error: {:?}", e),
}
}
_ = shutdown_rx.changed() => {
break;
}
}
}
tracing::info!("unwatch freesr-data.json");
});
let mut sessions = self.sessions.lock().await;
for session in sessions.values_mut() {
let _ = session.write().await.shutdown_tx.send(());
}
sessions.clear();
sessions.insert(conv_id, session_ref);
self.socket self.socket
.send_to( .send_to(
@ -102,38 +170,29 @@ impl Gateway {
async fn drop_kcp_session(&mut self, conv_id: u32, token: u32, addr: SocketAddr) { async fn drop_kcp_session(&mut self, conv_id: u32, token: u32, addr: SocketAddr) {
tracing::info!("drop_kcp_session {conv_id} {token}"); tracing::info!("drop_kcp_session {conv_id} {token}");
let Some(session) = self.sessions.lock().await.get(&conv_id).cloned() else { let mut sessions = self.sessions.lock().await;
let Some(session) = sessions.get(&conv_id) else {
tracing::warn!("drop_kcp_session failed, no session with conv_id {conv_id} was found"); tracing::warn!("drop_kcp_session failed, no session with conv_id {conv_id} was found");
return; return;
}; };
let session = session.write().await;
if session.read().await.token == token { if session.token == token {
self.sessions.lock().await.remove(&conv_id); let _ = session.shutdown_tx.send(());
drop(session);
sessions.remove(&conv_id);
tracing::info!("Client from {addr} disconnected"); tracing::info!("Client from {addr} disconnected");
} }
} }
async fn process_kcp_payload(&mut self, data: Box<[u8]>, addr: SocketAddr) { async fn process_kcp_payload(&mut self, data: Box<[u8]>, addr: SocketAddr) {
let conv_id = mhy_kcp::get_conv(&data); let conv_id = mhy_kcp::get_conv(&data);
let mut sessions = self.sessions.lock().await;
let Some(session) = self let Some(session) = sessions.get_mut(&conv_id).map(|s| s.clone()) else {
.sessions
.lock()
.await
.get_mut(&conv_id)
.map(|s| s.clone())
else {
tracing::warn!("Session with conv_id {conv_id} not found!"); tracing::warn!("Session with conv_id {conv_id} not found!");
return; return;
}; };
// TODO: Temporary fix
if session.read().await.is_destroyed {
drop(session);
self.sessions.lock().await.remove(&conv_id);
return;
}
tokio::spawn(async move { tokio::spawn(async move {
if let Err(err) = Box::pin(session.write().await.consume(&data)).await { if let Err(err) = Box::pin(session.write().await.consume(&data)).await {
tracing::error!("An error occurred while processing session ({addr}): {err}"); tracing::error!("An error occurred while processing session ({addr}): {err}");

View File

@ -1,20 +1,21 @@
use crate::net::tools::FreesrData;
use super::*; use super::*;
static UNLOCKED_AVATARS: [u32; 63] = [ pub static UNLOCKED_AVATARS: [u32; 63] = [
1002, 1003, 1004, 1005, 1006, 1008, 1009, 1013, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1002, 1003, 1004, 1005, 1006, 1008, 1009, 1013, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108,
1109, 1110, 1111, 1112, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1109, 1110, 1111, 1112, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212,
1213, 1214, 1215, 1217, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1312, 1315, 1310, 1213, 1214, 1215, 1217, 1301, 1302, 1303, 1304, 1305, 1306, 1307, 1308, 1309, 1312, 1315, 1310,
1314, 1218, 1221, 1220, 1222, 1223, 1317, 1313, 1225, 1402, 1401, 1404, 1403, 1405, 1407 1314, 1218, 1221, 1220, 1222, 1223, 1317, 1313, 1225, 1402, 1401, 1404, 1403, 1405, 1407,
]; ];
pub async fn on_get_avatar_data_cs_req( pub async fn on_get_avatar_data_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
body: &GetAvatarDataCsReq, body: &GetAvatarDataCsReq,
res: &mut GetAvatarDataScRsp, res: &mut GetAvatarDataScRsp,
) { ) {
let json = FreesrData::load().await; let Some(json) = session.json_data.get() else {
tracing::error!("data is not set!");
return;
};
// TODO: HARDCODED // TODO: HARDCODED
let mc_ids = if json.main_character.get_gender() == Gender::Man { let mc_ids = if json.main_character.get_gender() == Gender::Man {

View File

@ -3,19 +3,19 @@ use std::collections::HashMap;
use rand::Rng; use rand::Rng;
use rogue_magic_battle_unit_info::Item; use rogue_magic_battle_unit_info::Item;
use crate::{ use common::{
net::tools::{self, BattleType, Monster}, resources::GAME_RES,
tools::resources::GAME_RES, sr_tools::{BattleType, Monster, RogueMagicComponentType},
}; };
use super::*; use super::*;
pub async fn on_start_cocoon_stage_cs_req( pub async fn on_start_cocoon_stage_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
req: &StartCocoonStageCsReq, req: &StartCocoonStageCsReq,
res: &mut StartCocoonStageScRsp, res: &mut StartCocoonStageScRsp,
) { ) {
let battle_info = create_battle_info(0, 0).await; let battle_info = create_battle_info(session, 0, 0).await;
res.prop_entity_id = req.prop_entity_id; res.prop_entity_id = req.prop_entity_id;
res.cocoon_id = req.cocoon_id; res.cocoon_id = req.cocoon_id;
@ -24,11 +24,11 @@ pub async fn on_start_cocoon_stage_cs_req(
} }
pub async fn on_quick_start_cocoon_stage_cs_req( pub async fn on_quick_start_cocoon_stage_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
req: &QuickStartCocoonStageCsReq, req: &QuickStartCocoonStageCsReq,
res: &mut QuickStartCocoonStageScRsp, res: &mut QuickStartCocoonStageScRsp,
) { ) {
let mut battle_info = create_battle_info(0, 0).await; let mut battle_info = create_battle_info(session, 0, 0).await;
battle_info.world_level = req.world_level; battle_info.world_level = req.world_level;
res.cocoon_id = req.cocoon_id; res.cocoon_id = req.cocoon_id;
@ -46,7 +46,7 @@ pub async fn on_pve_battle_result_cs_req(
} }
pub async fn on_scene_cast_skill_cs_req( pub async fn on_scene_cast_skill_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
req: &SceneCastSkillCsReq, req: &SceneCastSkillCsReq,
res: &mut SceneCastSkillScRsp, res: &mut SceneCastSkillScRsp,
) { ) {
@ -55,20 +55,30 @@ pub async fn on_scene_cast_skill_cs_req(
let targets = req let targets = req
.hit_target_entity_id_list .hit_target_entity_id_list
.iter() .iter()
.chain(&req.assist_monster_entity_id_list)
.filter(|id| **id > 30_000 || **id < 1_000) .filter(|id| **id > 30_000 || **id < 1_000)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if targets.is_empty() { if targets.is_empty() {
tracing::warn!("scene cast skill target is empty!");
return; return;
} }
let battle_info = create_battle_info(req.caster_id, req.skill_index).await; let battle_info = create_battle_info(session, req.caster_id, req.skill_index).await;
res.attacked_group_id = req.attacked_group_id;
res.battle_info = Some(battle_info); res.battle_info = Some(battle_info);
} }
async fn create_battle_info(caster_id: u32, skill_index: u32) -> SceneBattleInfo { async fn create_battle_info(
let player = tools::FreesrData::load().await; session: &mut PlayerSession,
caster_id: u32,
skill_index: u32,
) -> SceneBattleInfo {
let Some(player) = session.json_data.get() else {
tracing::error!("data is not set!");
return SceneBattleInfo::default();
};
let mut battle_info = SceneBattleInfo { let mut battle_info = SceneBattleInfo {
stage_id: player.battle_config.stage_id, stage_id: player.battle_config.stage_id,
@ -207,27 +217,21 @@ async fn create_battle_info(caster_id: u32, skill_index: u32) -> SceneBattleInfo
// pf score object // pf score object
if player.battle_config.battle_type == BattleType::PF { if player.battle_config.battle_type == BattleType::PF {
if battle_info.stage_id >= 30309011 { if battle_info.stage_id >= 30309011 {
battle_info.battle_target_info.insert( battle_info.battle_target_info.insert(1, BattleTargetList {
1, battle_target_list: vec![BattleTarget {
BattleTargetList { id: 10003,
battle_target_list: vec![BattleTarget { progress: 0,
id: 10003, ..Default::default()
progress: 0, }],
..Default::default() });
}],
},
);
} else { } else {
battle_info.battle_target_info.insert( battle_info.battle_target_info.insert(1, BattleTargetList {
1, battle_target_list: vec![BattleTarget {
BattleTargetList { id: 10002,
battle_target_list: vec![BattleTarget { progress: 0,
id: 10002, ..Default::default()
progress: 0, }],
..Default::default() });
}],
},
);
} }
for i in 2..=4 { for i in 2..=4 {
@ -236,37 +240,31 @@ async fn create_battle_info(caster_id: u32, skill_index: u32) -> SceneBattleInfo
.insert(i, BattleTargetList::default()); .insert(i, BattleTargetList::default());
} }
battle_info.battle_target_info.insert( battle_info.battle_target_info.insert(5, BattleTargetList {
5, battle_target_list: vec![
BattleTargetList { BattleTarget {
battle_target_list: vec![ id: 2001,
BattleTarget { progress: 0,
id: 2001, ..Default::default()
progress: 0, },
..Default::default() BattleTarget {
}, id: 2002,
BattleTarget { progress: 0,
id: 2002, ..Default::default()
progress: 0, },
..Default::default() ],
}, });
],
},
);
} }
// Apocalyptic Shadow // Apocalyptic Shadow
if player.battle_config.battle_type == BattleType::AS { if player.battle_config.battle_type == BattleType::AS {
battle_info.battle_target_info.insert( battle_info.battle_target_info.insert(1, BattleTargetList {
1, battle_target_list: vec![BattleTarget {
BattleTargetList { id: 90005,
battle_target_list: vec![BattleTarget { progress: 0,
id: 90005, ..Default::default()
progress: 0, }],
..Default::default() });
}],
},
);
} }
// SU // SU
@ -313,9 +311,9 @@ async fn create_battle_info(caster_id: u32, skill_index: u32) -> SceneBattleInfo
for component in &scepter.components { for component in &scepter.components {
let (slot_type, locked) = match component.component_type { let (slot_type, locked) = match component.component_type {
tools::RogueMagicComponentType::Passive => (3u32, false), RogueMagicComponentType::Passive => (3u32, false),
tools::RogueMagicComponentType::Active => (4, true), RogueMagicComponentType::Active => (4, true),
tools::RogueMagicComponentType::Attach => (5, false), RogueMagicComponentType::Attach => (5, false),
}; };
let slot_index = &mut index[slot_type as usize - 3]; let slot_index = &mut index[slot_type as usize - 3];
@ -339,5 +337,17 @@ async fn create_battle_info(caster_id: u32, skill_index: u32) -> SceneBattleInfo
}); });
} }
// Global Buff
if !battle_info.buff_list.iter().any(|b| b.id == 140703) {
battle_info.buff_list.push(BattleBuff {
id: 140703,
level: 1,
owner_id: u32::MAX,
wave_flag: u32::MAX,
target_index_list: Vec::with_capacity(0),
dynamic_values: HashMap::with_capacity(0),
});
}
battle_info battle_info
} }

View File

@ -1,10 +1,6 @@
use crate::{ use common::sr_tools::MultiPathAvatar;
net::{
tools::{FreesrData, MultiPathAvatar}, use crate::{net::PlayerSession, util::cur_timestamp_ms};
PlayerSession,
},
util::cur_timestamp_ms,
};
use super::*; use super::*;
@ -16,7 +12,7 @@ const SERVER_CHAT_HISTORY: [&str; 5] = [
"'mc {mc_id}' mc_id can be set from 8001 to 8008", "'mc {mc_id}' mc_id can be set from 8001 to 8008",
"'march {march_id}' march_id can be set 1001 or 1224", "'march {march_id}' march_id can be set 1001 or 1224",
"available commands:", "available commands:",
"visit srtools.pages.dev to configure the PS! (you configure relics, equipment, monsters from there)" "visit srtools.pages.dev to configure the PS! (you configure relics, equipment, monsters from there)",
]; ];
pub async fn on_get_friend_login_info_cs_req( pub async fn on_get_friend_login_info_cs_req(
@ -76,12 +72,15 @@ pub async fn on_send_msg_cs_req(
body: &SendMsgCsReq, body: &SendMsgCsReq,
_res: &mut SendMsgScRsp, _res: &mut SendMsgScRsp,
) { ) {
let mut json = FreesrData::load().await; let Some(json) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
if let Some((cmd, args)) = parse_command(&body.text) { if let Some((cmd, args)) = parse_command(&body.text) {
match cmd { match cmd {
"sync" => { "sync" => {
sync_player(session, json).await; session.sync_player().await;
session session
.send(RevcMsgScNotify { .send(RevcMsgScNotify {
msg_type: body.msg_type, msg_type: body.msg_type,
@ -90,7 +89,7 @@ pub async fn on_send_msg_cs_req(
from_uid: SERVER_UID, from_uid: SERVER_UID,
to_uid: 25, to_uid: 25,
chat_type: body.chat_type, chat_type: body.chat_type,
hnbepabnbng: body.hnbepabnbng.clone(), hnbepabnbng: body.hnbepabnbng,
}) })
.await .await
.unwrap(); .unwrap();
@ -104,7 +103,7 @@ pub async fn on_send_msg_cs_req(
); );
json.main_character = mc; json.main_character = mc;
json.save().await; json.save_persistent().await;
session session
.send(AvatarPathChangedNotify { .send(AvatarPathChangedNotify {
@ -114,7 +113,7 @@ pub async fn on_send_msg_cs_req(
.await .await
.unwrap(); .unwrap();
sync_player(session, json).await; session.sync_player().await;
session session
.send(RevcMsgScNotify { .send(RevcMsgScNotify {
@ -124,7 +123,7 @@ pub async fn on_send_msg_cs_req(
from_uid: SERVER_UID, from_uid: SERVER_UID,
to_uid: 25, to_uid: 25,
chat_type: body.chat_type, chat_type: body.chat_type,
hnbepabnbng: body.hnbepabnbng.clone(), hnbepabnbng: body.hnbepabnbng,
}) })
.await .await
.unwrap(); .unwrap();
@ -144,7 +143,7 @@ pub async fn on_send_msg_cs_req(
} }
json.march_type = march_type; json.march_type = march_type;
json.save().await; json.save_persistent().await;
session session
.send(AvatarPathChangedNotify { .send(AvatarPathChangedNotify {
@ -162,7 +161,7 @@ pub async fn on_send_msg_cs_req(
from_uid: SERVER_UID, from_uid: SERVER_UID,
to_uid: 25, to_uid: 25,
chat_type: body.chat_type, chat_type: body.chat_type,
hnbepabnbng: body.hnbepabnbng.clone(), hnbepabnbng: body.hnbepabnbng,
}) })
.await .await
.unwrap(); .unwrap();
@ -181,70 +180,3 @@ fn parse_command(command: &str) -> Option<(&str, Vec<&str>)> {
Some((parts[0], parts[1..].to_vec())) Some((parts[0], parts[1..].to_vec()))
} }
async fn sync_player(session: &mut PlayerSession, json: FreesrData) {
// clear relics & lightcones
session
.send(PlayerSyncScNotify {
del_equipment_list: (2000..3500).collect(),
del_relic_list: (1..2000).collect(),
..Default::default()
})
.await
.unwrap();
// Sync avatars
session
.send(PlayerSyncScNotify {
avatar_sync: Some(AvatarSync {
avatar_list: json
.avatars
.values()
.map(|avatar| avatar.to_avatar_proto(Option::None, vec![]))
.collect::<Vec<_>>(),
}),
multi_path_avatar_type_info_list: json.get_multi_path_info(),
..Default::default()
})
.await
.unwrap();
// Sync new relics
session
.send(PlayerSyncScNotify {
relic_list: json.relics.iter().map(|v| v.to_relic_proto()).collect(),
equipment_list: json
.lightcones
.iter()
.map(|v| v.to_equipment_proto())
.collect(),
..Default::default()
})
.await
.unwrap();
// Sync new lightcones
session
.send(PlayerSyncScNotify {
avatar_sync: Some(AvatarSync {
avatar_list: json
.avatars
.values()
.map(|avatar| {
avatar.to_avatar_proto(
json.lightcones
.iter()
.find(|v| v.equip_avatar == avatar.avatar_id),
json.relics
.iter()
.filter(|v| v.equip_avatar == avatar.avatar_id)
.collect(),
)
})
.collect(),
}),
..Default::default()
})
.await
.unwrap()
}

View File

@ -1,13 +1,18 @@
use proto::*; use proto::{get_big_data_all_recommend_sc_rsp::RecommendType, *};
use crate::net::{tools::FreesrData, PlayerSession}; use crate::net::PlayerSession;
use super::UNLOCKED_AVATARS;
pub async fn on_get_bag_cs_req( pub async fn on_get_bag_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_req: &GetBagCsReq, _req: &GetBagCsReq,
res: &mut GetBagScRsp, res: &mut GetBagScRsp,
) { ) {
let player = FreesrData::load().await; let Some(player) = session.json_data.get() else {
tracing::error!("data is not set!");
return;
};
res.equipment_list = player res.equipment_list = player
.lightcones .lightcones
@ -65,10 +70,37 @@ pub async fn on_take_off_equipment_cs_req(
) { ) {
} }
// pub async fn on_relic_recommend_cs_req( pub async fn on_get_big_data_all_recommend_cs_req(
// _: &mut PlayerSession, _: &mut PlayerSession,
// req: &RelicRecommendCsReq, req: &GetBigDataAllRecommendCsReq,
// res: &mut RelicRecommendScRsp, res: &mut GetBigDataAllRecommendScRsp,
// ) { ) {
// res.avatar_id = req.avatar_id res.big_data_recommend_type = req.big_data_recommend_type;
// }
match req.big_data_recommend_type() {
BigDataRecommendType::RelicAvatar => {
res.recommend_type = Some(RecommendType::RelicAvatar(BigDataRecommendRelicAvatar {
recommended_avatar_info_list: UNLOCKED_AVATARS
.into_iter()
.map(|recommend_avatar_id| RecomendedAvatarInfo {
avatar_id_list: vec![],
recommend_avatar_id,
relic_set_id: 0,
})
.collect(),
}))
}
BigDataRecommendType::AvatarRelic => {
res.recommend_type = Some(RecommendType::AvatarRelic(BigDataRecommendAvatarRelic {
recomended_relic_info_list: UNLOCKED_AVATARS
.into_iter()
.map(|avatar_id| RecommendedRelicInfo {
avatar_id,
..Default::default()
})
.collect(),
}))
}
_ => {}
}
}

View File

@ -1,38 +1,45 @@
use common::sr_tools::AvatarJson;
use scene_entity_info::Entity; use scene_entity_info::Entity;
use scene_entity_refresh_info::RefreshType; use scene_entity_refresh_info::RefreshType;
use crate::net::tools::{self, AvatarJson, FreesrData};
use super::*; use super::*;
pub async fn on_get_all_lineup_data_cs_req( pub async fn on_get_all_lineup_data_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_body: &GetAllLineupDataCsReq, _body: &GetAllLineupDataCsReq,
res: &mut GetAllLineupDataScRsp, res: &mut GetAllLineupDataScRsp,
) { ) {
let player = tools::FreesrData::load().await; let Some(player) = session.json_data.get() else {
tracing::error!("data is not set!");
return;
};
res.lineup_list = vec![LineupInfo { res.lineup_list = vec![LineupInfo {
extra_lineup_type: ExtraLineupType::LineupNone.into(), extra_lineup_type: ExtraLineupType::LineupNone.into(),
name: "Squad 1".to_string(), name: "Squad 1".to_string(),
mp: 5, mp: 5,
max_mp: 5, max_mp: 5,
avatar_list: AvatarJson::to_lineup_avatars(&player), avatar_list: AvatarJson::to_lineup_avatars(player),
..Default::default() ..Default::default()
}]; }];
} }
pub async fn on_get_cur_lineup_data_cs_req( pub async fn on_get_cur_lineup_data_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_body: &GetCurLineupDataCsReq, _body: &GetCurLineupDataCsReq,
res: &mut GetCurLineupDataScRsp, res: &mut GetCurLineupDataScRsp,
) { ) {
let player = tools::FreesrData::load().await; let Some(player) = session.json_data.get() else {
tracing::error!("data is not set!");
return;
};
let lineup = LineupInfo { let lineup = LineupInfo {
extra_lineup_type: ExtraLineupType::LineupNone.into(), extra_lineup_type: ExtraLineupType::LineupNone.into(),
name: "Squad 1".to_string(), name: "Squad 1".to_string(),
mp: 5, mp: 5,
max_mp: 5, max_mp: 5,
avatar_list: AvatarJson::to_lineup_avatars(&player), avatar_list: AvatarJson::to_lineup_avatars(player),
is_virtual: false, is_virtual: false,
plane_id: 0, plane_id: 0,
..Default::default() ..Default::default()
@ -46,19 +53,26 @@ pub async fn on_join_lineup_cs_req(
body: &JoinLineupCsReq, body: &JoinLineupCsReq,
_res: &mut JoinLineupScRsp, _res: &mut JoinLineupScRsp,
) { ) {
let mut player = tools::FreesrData::load().await; let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
let lineups = &mut player.lineups; let lineups = &mut player.lineups;
lineups.insert(body.slot, body.base_avatar_id); lineups.insert(body.slot, body.base_avatar_id);
player.save_lineup().await; player.save_persistent().await;
refresh_lineup(session, &player).await; refresh_lineup(session).await;
} }
pub async fn on_replace_lineup_cs_req( pub async fn on_replace_lineup_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
req: &ReplaceLineupCsReq, req: &ReplaceLineupCsReq,
_res: &mut ReplaceLineupScRsp, _res: &mut ReplaceLineupScRsp,
) { ) {
let mut player = tools::FreesrData::load().await; let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
let lineups = &mut player.lineups; let lineups = &mut player.lineups;
for (slot, avatar_id) in &mut *lineups { for (slot, avatar_id) in &mut *lineups {
@ -68,8 +82,8 @@ pub async fn on_replace_lineup_cs_req(
*avatar_id = 0; *avatar_id = 0;
} }
} }
player.save_lineup().await; player.save_persistent().await;
refresh_lineup(_session, &player).await; refresh_lineup(session).await;
} }
pub async fn on_quit_lineup_cs_req( pub async fn on_quit_lineup_cs_req(
@ -79,7 +93,12 @@ pub async fn on_quit_lineup_cs_req(
) { ) {
} }
async fn refresh_lineup(session: &mut PlayerSession, player: &FreesrData) { async fn refresh_lineup(session: &mut PlayerSession) {
let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
let lineup = LineupInfo { let lineup = LineupInfo {
extra_lineup_type: ExtraLineupType::LineupNone.into(), extra_lineup_type: ExtraLineupType::LineupNone.into(),
name: "Squad 1".to_string(), name: "Squad 1".to_string(),
@ -89,33 +108,37 @@ async fn refresh_lineup(session: &mut PlayerSession, player: &FreesrData) {
..Default::default() ..Default::default()
}; };
let new_entities = player
.lineups
.iter()
.map(|(idx, v)| SceneEntityRefreshInfo {
refresh_type: Some(RefreshType::AddEntity(SceneEntityInfo {
entity: Some(Entity::Actor(SceneActorInfo {
avatar_type: AvatarType::AvatarFormalType.into(),
base_avatar_id: *v,
map_layer: 0,
uid: 25,
})),
entity_id: idx + 1,
group_id: 0,
inst_id: 0,
..Default::default()
})),
})
.collect();
let floor_id = player.scene.floor_id;
session session
.send(SceneGroupRefreshScNotify { .send(SceneGroupRefreshScNotify {
group_refresh_info: vec![SceneGroupRefreshInfo { group_refresh_info: vec![SceneGroupRefreshInfo {
group_id: 0, group_id: 0,
state: 0, state: 0,
group_refresh_type: 0, group_refresh_type: SceneGroupRefreshType::Loaded.into(),
refresh_entity: player refresh_entity: new_entities,
.lineups
.iter()
.map(|(idx, v)| SceneEntityRefreshInfo {
refresh_type: Some(RefreshType::AddEntity(SceneEntityInfo {
entity: Some(Entity::Actor(SceneActorInfo {
avatar_type: AvatarType::AvatarFormalType.into(),
base_avatar_id: *v,
map_layer: 0,
uid: 25,
})),
entity_id: idx + 1,
group_id: 0,
inst_id: 0,
..Default::default()
})),
})
.collect(),
bccgjihncdn: Vec::with_capacity(0), bccgjihncdn: Vec::with_capacity(0),
}], }],
floor_id: 0, // TODO! floor_id,
gfhglffhfbd: 0, gfhglffhfbd: 0,
}) })
.await .await

View File

@ -1,7 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::net::tools::FreesrData;
use super::*; use super::*;
pub async fn on_get_basic_info_cs_req( pub async fn on_get_basic_info_cs_req(
@ -56,11 +54,14 @@ pub async fn on_player_login_finish_cs_req(
} }
pub async fn on_get_multi_path_avatar_info_cs_req( pub async fn on_get_multi_path_avatar_info_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_req: &GetMultiPathAvatarInfoCsReq, _req: &GetMultiPathAvatarInfoCsReq,
res: &mut GetMultiPathAvatarInfoScRsp, res: &mut GetMultiPathAvatarInfoScRsp,
) { ) {
let json = FreesrData::load().await; let Some(json) = session.json_data.get() else {
tracing::error!("data is not set!");
return;
};
res.current_multi_path_avatar_id = HashMap::from([ res.current_multi_path_avatar_id = HashMap::from([
(8001, json.main_character.get_type().into()), (8001, json.main_character.get_type().into()),

View File

@ -1,14 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use common::{
resources::GAME_RES,
sr_tools::{AvatarJson, Position},
};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use scene_entity_info::Entity; use scene_entity_info::Entity;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::{ use crate::util::{self};
net::tools::{AvatarJson, FreesrData, Position},
tools::resources::GAME_RES,
util::{self},
};
use super::*; use super::*;
@ -17,21 +17,25 @@ pub async fn on_get_cur_scene_info_cs_req(
_body: &GetCurSceneInfoCsReq, _body: &GetCurSceneInfoCsReq,
res: &mut GetCurSceneInfoScRsp, res: &mut GetCurSceneInfoScRsp,
) { ) {
let mut player = FreesrData::load().await; let Some(player) = session.json_data.get() else {
let entry = player.scene.entry_id; tracing::error!("data is not set!");
return;
};
let scene = load_scene(session, &mut player, entry, false, Option::<u32>::None).await; let default_scene = SceneInfo {
game_mode_type: 3,
entry_id: player.scene.entry_id,
plane_id: player.scene.plane_id,
floor_id: player.scene.floor_id,
..Default::default()
};
let scene = load_scene(session, default_scene.entry_id, false, Option::<u32>::None).await;
res.scene = if let Ok(scene) = scene { res.scene = if let Ok(scene) = scene {
Some(scene) Some(scene)
} else { } else {
Some(SceneInfo { Some(default_scene)
game_mode_type: 3,
entry_id: player.scene.entry_id,
plane_id: player.scene.plane_id,
floor_id: player.scene.floor_id,
..Default::default()
})
}; };
} }
@ -40,17 +44,9 @@ pub async fn on_enter_scene_cs_req(
req: &EnterSceneCsReq, req: &EnterSceneCsReq,
res: &mut EnterSceneScRsp, res: &mut EnterSceneScRsp,
) { ) {
let mut player = FreesrData::load().await; if load_scene(session, req.entry_id, true, Some(req.teleport_id))
.await
if load_scene( .is_err()
session,
&mut player,
req.entry_id,
true,
Some(req.teleport_id),
)
.await
.is_err()
{ {
res.retcode = Retcode::RetSceneEntryIdNotMatch as u32; res.retcode = Retcode::RetSceneEntryIdNotMatch as u32;
}; };
@ -126,11 +122,15 @@ lazy_static! {
} }
pub async fn on_scene_entity_move_cs_req( pub async fn on_scene_entity_move_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
req: &SceneEntityMoveCsReq, req: &SceneEntityMoveCsReq,
_res: &mut SceneEntityMoveScRsp, _res: &mut SceneEntityMoveScRsp,
) { ) {
let mut player = FreesrData::load().await; let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
let mut timestamp = NEXT_SCENE_SAVE.lock().await; let mut timestamp = NEXT_SCENE_SAVE.lock().await;
if util::cur_timestamp_ms() <= *timestamp { if util::cur_timestamp_ms() <= *timestamp {
@ -157,7 +157,7 @@ pub async fn on_scene_entity_move_cs_req(
} }
} }
player.save().await; player.save_persistent().await;
} }
pub async fn on_get_entered_scene_cs_req( pub async fn on_get_entered_scene_cs_req(
@ -187,11 +187,15 @@ pub async fn on_get_entered_scene_cs_req(
async fn load_scene( async fn load_scene(
session: &mut PlayerSession, session: &mut PlayerSession,
json: &mut FreesrData,
entry_id: u32, entry_id: u32,
is_enter_scene: bool, is_enter_scene: bool,
teleport_id: Option<u32>, teleport_id: Option<u32>,
) -> Result<SceneInfo> { ) -> Result<SceneInfo> {
let Some(json) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return Err(anyhow::format_err!("data is not set!"));
};
let (name, scene) = GAME_RES let (name, scene) = GAME_RES
.level_output_configs .level_output_configs
.get(&entry_id) .get(&entry_id)
@ -375,7 +379,7 @@ async fn load_scene(
.map(|(slot, avatar_id)| SceneEntityInfo { .map(|(slot, avatar_id)| SceneEntityInfo {
inst_id: 0, inst_id: 0,
entity_id: (*slot) + 1, entity_id: (*slot) + 1,
motion: Some(player_pos.clone()), motion: Some(player_pos),
entity: Some(Entity::Actor(SceneActorInfo { entity: Some(Entity::Actor(SceneActorInfo {
avatar_type: AvatarType::AvatarFormalType.into(), avatar_type: AvatarType::AvatarFormalType.into(),
base_avatar_id: *avatar_id, base_avatar_id: *avatar_id,
@ -388,14 +392,6 @@ async fn load_scene(
}); });
if is_enter_scene { if is_enter_scene {
session
.send(EnterSceneByServerScNotify {
scene: Some(scene_info.clone()),
lineup: Some(lineup_info),
..Default::default()
})
.await?;
json.scene.entry_id = entry_id; json.scene.entry_id = entry_id;
json.scene.floor_id = floor_id; json.scene.floor_id = floor_id;
json.scene.plane_id = plane_id; json.scene.plane_id = plane_id;
@ -403,7 +399,16 @@ async fn load_scene(
json.position.y = json_pos.y; json.position.y = json_pos.y;
json.position.z = json_pos.z; json.position.z = json_pos.z;
json.position.rot_y = json_pos.rot_y; json.position.rot_y = json_pos.rot_y;
json.save().await;
json.save_persistent().await;
session
.send(EnterSceneByServerScNotify {
scene: Some(scene_info.clone()),
lineup: Some(lineup_info),
..Default::default()
})
.await?;
} }
Ok(scene_info) Ok(scene_info)

View File

@ -3,7 +3,6 @@ pub mod gateway;
mod handlers; mod handlers;
mod packet; mod packet;
mod session; mod session;
mod tools;
pub use packet::NetPacket; pub use packet::NetPacket;
pub use session::PlayerSession; pub use session::PlayerSession;

View File

@ -27,8 +27,8 @@ use proto::{
CmdWaypointType::*, CmdWolfBroType::*, CmdWaypointType::*, CmdWolfBroType::*,
}; };
use super::handlers::*;
use super::PlayerSession; use super::PlayerSession;
use super::handlers::*;
const HEAD_MAGIC: u32 = 0x9D74C714; const HEAD_MAGIC: u32 = 0x9D74C714;
const TAIL_MAGIC: u32 = 0xD7A152C8; const TAIL_MAGIC: u32 = 0xD7A152C8;
@ -219,6 +219,6 @@ trait_handler! {
GetGachaInfo; GetGachaInfo;
DoGacha; DoGacha;
PlayerLoginFinish; PlayerLoginFinish;
// RelicRecommend; GetBigDataAllRecommend;
// SetClientPaused; // SetClientPaused;
} }

View File

@ -2,66 +2,73 @@ use std::{
io::Error, io::Error,
net::SocketAddr, net::SocketAddr,
pin::Pin, pin::Pin,
sync::Arc, sync::{Arc, OnceLock},
task::{Context, Poll}, task::{Context, Poll},
}; };
use anyhow::Result; use anyhow::Result;
use common::sr_tools::FreesrData;
use mhy_kcp::Kcp; use mhy_kcp::Kcp;
use prost::Message; use prost::Message;
use proto::{CmdID, CmdPlayerType}; use proto::{AvatarSync, CmdID, CmdPlayerType, PlayerSyncScNotify};
use tokio::{io::AsyncWrite, net::UdpSocket, sync::Mutex}; use tokio::{
io::AsyncWrite,
net::UdpSocket,
sync::{Mutex, watch},
};
use crate::util; use crate::util;
use super::{packet::CommandHandler, NetPacket}; use super::{NetPacket, packet::CommandHandler};
struct RemoteEndPoint { struct RemoteEndPoint {
socket: Arc<UdpSocket>, socket: Arc<UdpSocket>,
addr: SocketAddr, addr: SocketAddr,
} }
#[derive(Clone)]
pub struct PlayerSession { pub struct PlayerSession {
pub token: u32, pub token: u32,
kcp: Arc<Mutex<Kcp<RemoteEndPoint>>>, kcp: Arc<Mutex<Kcp<RemoteEndPoint>>>,
start_time: u64, start_time: u64,
pub is_destroyed: bool, pub shutdown_tx: watch::Sender<()>,
pub shutdown_rx: watch::Receiver<()>,
pub json_data: OnceLock<FreesrData>,
} }
impl PlayerSession { impl PlayerSession {
pub fn new(socket: Arc<UdpSocket>, addr: SocketAddr, conv: u32, token: u32) -> Self { pub fn new(socket: Arc<UdpSocket>, addr: SocketAddr, conv: u32, token: u32) -> Self {
let (shutdown_tx, shutdown_rx) = watch::channel(());
Self { Self {
token, token,
kcp: Arc::new(Mutex::new(Kcp::new( kcp: Arc::new(Mutex::new(Kcp::new(conv, token, false, RemoteEndPoint {
conv, socket,
token, addr,
false, }))),
RemoteEndPoint { socket, addr },
))),
start_time: util::cur_timestamp_secs(), start_time: util::cur_timestamp_secs(),
is_destroyed: false, json_data: OnceLock::new(),
shutdown_rx,
shutdown_tx,
} }
} }
pub async fn consume(&mut self, buffer: &[u8]) -> Result<()> { pub async fn consume(&mut self, buffer: &[u8]) -> Result<()> {
{ let mut kcp = self.kcp.lock().await;
let mut kcp = self.kcp.lock().await; kcp.input(buffer)?;
kcp.input(buffer)?; kcp.async_update(self.session_time() as u32).await?;
kcp.async_update(self.session_time() as u32).await?; kcp.async_flush().await?;
kcp.async_flush().await?;
}
let mut packets = Vec::new(); let mut packets = Vec::new();
let mut buf = [0; 24756]; let mut buf = [0; 24756];
while let Ok(length) = self.kcp.lock().await.recv(&mut buf) { while let Ok(length) = kcp.recv(&mut buf) {
packets.push(NetPacket::from(&buf[..length])); packets.push(NetPacket::from(&buf[..length]));
} }
drop(kcp);
for packet in packets { for packet in packets {
// TODO: Temporary fix
if packet.cmd_type == CmdPlayerType::CmdPlayerLogoutCsReq as u16 { if packet.cmd_type == CmdPlayerType::CmdPlayerLogoutCsReq as u16 {
self.is_destroyed = true; tracing::info!("Player logged out");
let _ = self.shutdown_tx.send(());
return Ok(()); return Ok(());
}; };
Self::on_message(self, packet.cmd_type, packet.body).await?; Self::on_message(self, packet.cmd_type, packet.body).await?;
@ -78,6 +85,7 @@ impl PlayerSession {
pub async fn send(&self, body: impl Message + CmdID) -> Result<()> { pub async fn send(&self, body: impl Message + CmdID) -> Result<()> {
let mut buf = Vec::new(); let mut buf = Vec::new();
body.encode(&mut buf)?; body.encode(&mut buf)?;
tracing::info!("sent packet with CmdID: {}", body.get_cmd_id());
let payload: Vec<u8> = NetPacket { let payload: Vec<u8> = NetPacket {
cmd_type: body.get_cmd_id(), cmd_type: body.get_cmd_id(),
@ -104,6 +112,74 @@ impl PlayerSession {
Ok(()) Ok(())
} }
pub async fn sync_player(&self) {
let Some(json) = self.json_data.get() else {
tracing::error!("data is not init!");
return;
};
// clear relics & lightcones
self.send(PlayerSyncScNotify {
del_equipment_list: (2000..3500).collect(),
del_relic_list: (1..2000).collect(),
..Default::default()
})
.await
.unwrap();
// Sync avatars
self.send(PlayerSyncScNotify {
avatar_sync: Some(AvatarSync {
avatar_list: json
.avatars
.values()
.map(|avatar| avatar.to_avatar_proto(Option::None, vec![]))
.collect::<Vec<_>>(),
}),
multi_path_avatar_type_info_list: json.get_multi_path_info(),
..Default::default()
})
.await
.unwrap();
// Sync new relics
self.send(PlayerSyncScNotify {
relic_list: json.relics.iter().map(|v| v.to_relic_proto()).collect(),
equipment_list: json
.lightcones
.iter()
.map(|v| v.to_equipment_proto())
.collect(),
..Default::default()
})
.await
.unwrap();
// Sync new lightcones
self.send(PlayerSyncScNotify {
avatar_sync: Some(AvatarSync {
avatar_list: json
.avatars
.values()
.map(|avatar| {
avatar.to_avatar_proto(
json.lightcones
.iter()
.find(|v| v.equip_avatar == avatar.avatar_id),
json.relics
.iter()
.filter(|v| v.equip_avatar == avatar.avatar_id)
.collect(),
)
})
.collect(),
}),
..Default::default()
})
.await
.unwrap()
}
fn session_time(&self) -> u64 { fn session_time(&self) -> u64 {
util::cur_timestamp_secs() - self.start_time util::cur_timestamp_secs() - self.start_time
} }

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ edition = "2024"
anyhow.workspace = true anyhow.workspace = true
env_logger.workspace = true env_logger.workspace = true
tower-http = { workspace = true, features = ["cors"]}
axum.workspace = true axum.workspace = true
axum-server.workspace = true axum-server.workspace = true
@ -28,3 +29,4 @@ ansi_term.workspace = true
prost.workspace = true prost.workspace = true
rbase64.workspace = true rbase64.workspace = true
proto.workspace = true proto.workspace = true
common.workspace = true

View File

@ -1,8 +1,11 @@
use anyhow::Result; use anyhow::Result;
use axum::routing::{get, post};
use axum::Router; use axum::Router;
use axum::http::Method;
use axum::http::header::CONTENT_TYPE;
use axum::routing::{get, post};
use logging::init_tracing; use logging::init_tracing;
use services::{auth, dispatch, errors}; use services::{auth, dispatch, errors, sr_tools};
use tower_http::cors::{Any, CorsLayer};
use tracing::Level; use tracing::Level;
mod config; mod config;
@ -40,6 +43,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),
) )
.route(
sr_tools::SRTOOLS_UPLOAD_ENDPOINT,
post(sr_tools::sr_tool_save),
)
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
.allow_headers([CONTENT_TYPE]),
)
.fallback(errors::not_found); .fallback(errors::not_found);
let addr = format!("0.0.0.0:{PORT}"); let addr = format!("0.0.0.0:{PORT}");

View File

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

View File

@ -0,0 +1,50 @@
use axum::Json;
use common::sr_tools::FreesrData;
use serde::{Deserialize, Serialize};
use tokio::fs;
pub const SRTOOLS_UPLOAD_ENDPOINT: &str = "/srtools";
#[derive(Debug, Deserialize)]
pub struct SrToolDataReq {
#[allow(dead_code)]
pub data: Option<FreesrData>,
}
#[derive(Debug, Serialize)]
pub struct SrToolDataRsp {
pub message: String,
pub status: u32,
}
#[tracing::instrument]
pub async fn sr_tool_save(Json(json): Json<SrToolDataReq>) -> Json<SrToolDataRsp> {
let Some(json) = json.data else {
return Json(SrToolDataRsp {
message: String::from("OK"),
status: 200,
});
};
let json = match serde_json::to_string_pretty(&json) {
Ok(json) => json,
Err(err) => {
return Json(SrToolDataRsp {
message: format!("malformed json: {}", err),
status: 200,
});
}
};
if let Err(err) = fs::write("freesr-data.json", json).await {
return Json(SrToolDataRsp {
message: format!("failed to write freesr-data.json: {}", err),
status: 200,
});
};
Json(SrToolDataRsp {
message: String::from("OK"),
status: 200,
})
}

View File

@ -1,20 +1,8 @@
{ {
"CNBETAWin3.0.51": { "OSBETAWin3.1.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9191572_33717c67eee7", "asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9201681_3b7fa40d696e", "ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9188077_6eddb96c0602", "lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253" "ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
},
"OSBETAWin3.0.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9191572_33717c67eee7",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9194543_a2c963cc027a",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9188077_6eddb96c0602",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
},
"CNBETAWin3.0.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_9327839_3a7f8c61dd4e",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_9330527_430d02ffd64d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_9327980_89d683a0d346",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253"
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"OSBETAWin3.1.51": { "OSBETAWin3.1.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966", "asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9574749_cf833d944ab2", "ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f", "lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253" "ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
} }