56 Commits

Author SHA1 Message Date
amizing25 243df2b03e feat: add gitea prebuilt actions
Prebuilt release / prebuilt (push) Successful in 4m27s
2026-07-17 06:03:33 +07:00
amizing25 592ad690d9 feat: Add support for 4.4.5x 2026-07-17 06:03:18 +07:00
amizing25 99e1e09707 feat: Add support for 4.3.5x 2026-07-16 19:54:13 +07:00
yuvlian a2e81c40fc update res 2026-07-16 19:54:13 +07:00
yuvlian 701a78e9dc move global buff toggle logic in commands, add compile flags for playerheartbeat data 2026-07-16 19:53:57 +07:00
yuvlian 6aa7670ca8 add toggle for global buffs in persistent 2026-05-01 14:07:18 +07:00
yuvlian 80754bbfea FINAL FIX FOR HARDCODED BUFFS ON GOD 2026-05-01 13:54:15 +07:00
yuvlian 137db60000 remove debugging & add note for dahlia's hardcode 2026-05-01 13:08:49 +07:00
yuvlian 676bd63dac bump deps ver, use result instead of panic for fs watcher, hardcode dahlia tech to 1st unit 2026-04-30 20:31:00 +07:00
amizing25 5e0f2a9673 feat: Add support for 4.2.5x 2026-04-30 14:15:28 +07:00
yuvlian 26956e3193 custom lineup support 2026-03-18 07:37:21 +07:00
amizing25 cc389bd368 feat: Add support for 4.1.5x 2026-03-17 21:24:16 +07:00
amizing25 c697c799d6 feat: Support SceneEnterStageC and remove avatar red DOM 2026-03-01 01:14:45 +08:00
amizing25 b34e7af824 feat: Add support for 4.0.5x 2026-02-25 13:25:25 +07:00
amizing25 9d856e76c8 feat: lua executor via chat 2026-02-09 14:19:32 +07:00
amizing25 5e5a90cb50 fix: swapped del_relic_list & del_equipment_list and increase inv limit 2026-01-08 06:57:27 +07:00
amizing25 0208899832 feat: account plat native auth handling 2026-01-07 17:22:02 +07:00
amizing25 5ad0ac3556 fix: loading bar when clicking quick lineup 2026-01-07 17:20:41 +07:00
amizing25 7807973f93 fix: chat history & chat send 2026-01-07 17:20:21 +07:00
amizing25 c2b3bb4dd9 fix: main character switching 2026-01-07 17:19:54 +07:00
amizing25 779efd2771 feat: update res to 3.8.51 2026-01-07 05:29:50 +07:00
amizing25 14692dc78e feat: Add support for 3.8.5x 2026-01-06 23:57:37 +07:00
amizing25 56bdb1abc1 feat: Add support for 3.7.5x 2025-11-04 23:16:14 +07:00
amizing25 f1ef8d8da2 feat: Add support for 3.6.5x 2025-09-25 20:02:12 +07:00
amizing25 77f4bdf686 feat: hardcode cerydra & dh-pt technique 2025-08-15 17:01:11 +07:00
amizing25 aed52a523e feat: Add support for 3.5.5x 2025-08-14 11:52:14 +07:00
Keirakoro 14dd31c11f add avatars 2025-07-01 10:53:23 +07:00
Keirakoro 8c0d4fdc34 env type change
22 fuck up
2025-07-01 10:40:58 +07:00
amizing25 f6717b3157 feat: Add support for 3.4.5x 2025-07-01 09:53:08 +07:00
amizing25 bd40dc058f chore: cargo clippy 2025-06-30 15:10:50 +07:00
amizing25 8d8686692e feat: deobf field 2025-05-24 11:14:43 +07:00
amizing25 04f517daed fix: handle unhandled packet for handbook teleport 2025-05-24 11:04:49 +07:00
amizing25 6e72b9b509 feat: some changes 2025-05-24 06:03:24 +07:00
amizing25 2d995ef7d0 feat: update hotfix 2025-05-24 05:52:03 +07:00
amizing25 42545bbc53 fix: fix relic recommend & some scene not appearing 2025-05-24 05:50:58 +07:00
amizing25 81a783a078 feat: handle avatar enhanced state 2025-05-24 05:50:15 +07:00
amizing25 0cb4138562 fix: vector proto 2025-05-22 20:24:25 +07:00
amizing25 d1c8eeaa52 fix: multi path 2025-05-22 13:47:28 +07:00
amizing25 ac5ef9706f feat: Initial support for 3.3.5x 2025-05-22 13:47:15 +07:00
amizing25 284e5cd48e fix(deps): use rustls-tls for reqwest to fix openssl error on android target 2025-04-15 18:48:31 +07:00
amizing25 d14bc79fcc feat: lighten map sections 2025-04-15 18:46:35 +07:00
aorus22 90c2f332a5 fix: fix vector proto 2025-04-08 15:46:09 +07:00
supriyadimarseto e69e3c4e61 feat: new res 3.2.51 2025-04-08 14:05:16 +07:00
supriyadimarseto 38ee98cea2 feat: 3.2.5x proto 2025-04-08 11:29:20 +07:00
supriyadimarseto 74455e23f8 fix(sdk): fix hotfix retcode false-positive 2025-04-08 11:27:33 +07:00
amizing25 6c5ba8b6e4 feat(sdk): add automatic hotfix downloader 2025-03-12 13:32:32 +07:00
amizing25 608775b428 feat: 3.1.53 res 2025-03-11 17:46:32 +07:00
amizing25 d3af43fb06 refactor: some restructuring
- move structs inside `sr_tools.rs` into individual file
- implement in-game equip handler (relic & lightcone)
2025-03-11 17:37:35 +07:00
amizing25 fff8fc803e chore: expose sdkserver & gameserver as lib 2025-03-09 05:20:26 +07:00
amizing25 ea640e7992 feat: update res & version 3.1.52 2025-03-09 05:16:08 +07:00
amizing25 505c446a53 chore: split executable into lib and main 2025-03-09 05:11:16 +07:00
amizing25 c034f8a2f9 fix: modify file listener to ensure the file is modified 2025-03-09 05:10:33 +07:00
amizing25 6d9bf58a85 perf: remove lazy_static 2025-03-09 05:09:09 +07:00
amizing25 7d233dc614 chore: remove unused fields 2025-03-09 05:07:06 +07:00
amizing25 6d2489de47 chore: better response message & status 2025-03-08 22:06:16 +07:00
Keirakoro 6435d4e30b support IOS ( ig?) 2025-03-03 12:00:36 +07:00
55 changed files with 47355 additions and 104793 deletions
+109
View File
@@ -0,0 +1,109 @@
name: Prebuilt release
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: read
releases: write
jobs:
prebuilt:
runs-on: ubuntu-latest
env:
CARGO_TERM_COLOR: always
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Install nightly Rust
uses: dtolnay/rust-toolchain@nightly
with:
targets: x86_64-pc-windows-gnu
- name: Install native build tools
run: |
sudo apt-get update
sudo apt-get install -y mingw-w64 protobuf-compiler zip unzip jq
- name: Build release binaries
env:
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: x86_64-w64-mingw32-gcc
run: cargo build --release --target x86_64-pc-windows-gnu --bin gameserver --bin sdkserver
- name: Package prebuilt release
run: |
set -euo pipefail
rm -rf dist
mkdir -p dist
cp target/x86_64-pc-windows-gnu/release/gameserver.exe dist/gameserver.exe
cp target/x86_64-pc-windows-gnu/release/sdkserver.exe dist/sdkserver.exe
cp persistent res.json freesr-data.json versions.json dist/
for file in gameserver.exe sdkserver.exe persistent res.json freesr-data.json versions.json; do
test -f "dist/${file}"
done
(
cd dist
zip -9 -q ../prebuilt-win64.zip \
gameserver.exe \
sdkserver.exe \
persistent \
res.json \
freesr-data.json \
versions.json
)
unzip -l prebuilt-win64.zip
- name: Publish prebuilt release
env:
GITEA_API_URL: ${{ gitea.server_url }}/api/v1
GITEA_REPOSITORY: ${{ gitea.repository }}
GITEA_SHA: ${{ gitea.sha }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
api="${GITEA_API_URL}"
repo="${GITEA_REPOSITORY}"
tag="prebuilt"
auth=(-H "Authorization: token ${GITEA_TOKEN}" -H "Accept: application/json")
release_status="$(curl -sS "${auth[@]}" -o release.json -w '%{http_code}' \
"${api}/repos/${repo}/releases/tags/${tag}" || true)"
case "${release_status}" in
200)
;;
404)
curl -fsS -X POST "${auth[@]}" \
-H "Content-Type: application/json" \
-d "{\"tag_name\":\"${tag}\",\"target_commitish\":\"${GITEA_SHA}\",\"name\":\"${tag}\",\"body\":\"Automated prebuilt binaries for ${GITEA_SHA}\",\"draft\":false,\"prerelease\":false}" \
"${api}/repos/${repo}/releases" > release.json
;;
*)
cat release.json
exit 1
;;
esac
release_id="$(jq -er '.id' release.json)"
assets="$(curl -fsS "${auth[@]}" \
"${api}/repos/${repo}/releases/${release_id}/assets")"
while read -r asset_id; do
test -z "${asset_id}" || curl -fsS -X DELETE "${auth[@]}" \
"${api}/repos/${repo}/releases/${release_id}/assets/${asset_id}"
done < <(printf '%s' "${assets}" | jq -r '.[] | select(.name == "prebuilt-win64.zip") | .id')
curl -fsS -X POST "${auth[@]}" \
-F "attachment=@prebuilt-win64.zip;type=application/zip" \
"${api}/repos/${repo}/releases/${release_id}/assets?name=prebuilt-win64.zip"
+1 -1
View File
@@ -1,6 +1,6 @@
target/ target/
Cargo.lock Cargo.lock
proto/StarRail.proto
/prebuild /prebuild
/assets /assets
.vscode .vscode
.prebuilt
+42 -41
View File
@@ -6,64 +6,65 @@ resolver = "3"
version = "0.1.0" version = "0.1.0"
[workspace.dependencies] [workspace.dependencies]
anyhow = "1.0.81" # Framework
tokio = { version = "1.52.1", features = ["full"] }
axum = "0.8.9"
axum-server = "0.8.0"
tower-http = "0.6.8"
reqwest = { version = "0.13.3", default-features = false, features = [
"json",
"multipart",
"cookies",
"gzip",
"brotli",
"deflate",
"rustls",
"charset",
"http2",
"system-proxy",
] }
# JSON
serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149"
# Logging
tracing = "0.1.44"
ansi_term = "0.12.1" ansi_term = "0.12.1"
atomic_refcell = "0.1.13" env_logger = "0.11.10"
lazy_static = "1.4.0"
axum = "0.8.1" # Cryptography
axum-server = "0.7.1" rsa = { version = "0.9.10", features = [
tower-http = "0.6.2"
env_logger = "0.11.3"
rbase64 = "2.0.3"
rand = "0.9.0"
rsa = { version = "0.9.6", features = [
"sha1", "sha1",
"nightly", "nightly",
"pkcs5", "pkcs5",
"serde", "serde",
"sha2", "sha2",
] } ] }
rand = "0.10.1"
prost = "0.13.5" # Serialization
prost-types = "0.13.5" prost = "0.14.3"
prost-build = "0.13.5" prost-types = "0.14.3"
prost-build = "0.14.3"
rbase64 = "2.0.3"
paste = "1.0.14" # Utilities
sysinfo = "0.33.1" paste = "1.0.15"
notify = "8.0.0" notify = "8.2.0"
notify-debouncer-mini = "0.6.0" notify-debouncer-mini = "0.7.0"
anyhow = "1.0.102"
hex = "0.4.3"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.114"
tokio = { version = "1.36.0", features = ["full"] }
tokio-util = { version = "0.7.10", features = ["io"] }
tracing = "0.1.40"
tracing-futures = "0.2.5"
tracing-log = { version = "0.2.0", features = ["std", "log-tracer"] }
tracing-subscriber = { version = "0.3.18", features = [
"env-filter",
"registry",
"std",
"tracing",
"tracing-log",
] }
tracing-bunyan-formatter = "0.3.9"
# Local
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/" } common = { path = "common/" }
sdkserver = { path = "sdkserver/" }
gameserver = { path = "gameserver/" }
[profile.release] [profile.release]
strip = true # Automatically strip symbols from the binary.
opt-level = "z" # Optimize for size. opt-level = "z" # Optimize for size.
strip = true # Automatically strip symbols from the binary.
lto = true lto = true
codegen-units = 1 codegen-units = 1
+5 -3
View File
@@ -1,13 +1,15 @@
# Supported Version: 3.1.5x # Supported Version: 4.4.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.neonteam.dev](https://srtools.neonteam.dev)
Start battle by entering any calyx in the map, DON'T ATTACK THE ENEMIES, IT WON'T WORK (maybe) Start battle by entering any calyx in the map, DON'T ATTACK THE ENEMIES, IT WON'T WORK (maybe)
Some scenes might not loaded properly. If you stuck at loading screen, remove `persistent` file. Some scenes might not loaded properly. If you stuck at loading screen, remove `persistent` file.
# RobinSR # RobinSR
Original: Original:
[https://git.xeondev.com/reversedrooms/RobinSR](https://git.xeondev.com/reversedrooms/RobinSR) [https://git.xeondev.com/reversedrooms/RobinSR](https://git.xeondev.com/reversedrooms/RobinSR)
@@ -32,4 +34,4 @@ rustup default nightly
To begin using the server, you need to run both the SDK server and the game server. Just run the `run.bat` file. To begin using the server, you need to run both the SDK server and the game server. Just run the `run.bat` file.
# Special thanks for keiracoder for updating the repo 🔥 # Special thanks to keiracoder for updating the repo! 🔥
+1
View File
@@ -9,3 +9,4 @@ serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
tokio.workspace = true tokio.workspace = true
tracing.workspace = true tracing.workspace = true
anyhow.workspace = true
+1
View File
@@ -1,2 +1,3 @@
pub mod resources; pub mod resources;
pub mod sr_tools; pub mod sr_tools;
pub mod structs;
+7 -1
View File
@@ -114,6 +114,9 @@ pub struct SceneData {
pub props: Vec<ScenePropInfo>, pub props: Vec<ScenePropInfo>,
pub monsters: Vec<SceneMonsterInfo>, pub monsters: Vec<SceneMonsterInfo>,
pub teleports: HashMap<u32, TeleportInfo>, pub teleports: HashMap<u32, TeleportInfo>,
pub finished_sub_missions: Vec<u32>,
pub finished_main_missions: Vec<u32>,
pub chests: Vec<u32>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -123,6 +126,8 @@ pub struct LevelOutputConfig {
pub scenes: HashMap<u32, SceneData>, pub scenes: HashMap<u32, SceneData>,
pub plane_type: u32, pub plane_type: u32,
pub world_id: u32, pub world_id: u32,
pub sections: Vec<u32>,
pub saved_values: HashMap<String, i32>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -138,7 +143,8 @@ pub struct JsonConfig {
/// `entryid` -> `P[planeId]_F[floorId]` -> `groupId` /// `entryid` -> `P[planeId]_F[floorId]` -> `groupId`
pub level_output_configs: HashMap<u32, HashMap<String, LevelOutputConfig>>, pub level_output_configs: HashMap<u32, HashMap<String, LevelOutputConfig>>,
pub avatar_configs: HashMap<u32, AvatarConfig>, pub avatar_configs: HashMap<u32, AvatarConfig>,
pub map_default_entrance_map: HashMap<u32, u32> pub map_default_entrance_map: HashMap<u32, u32>,
pub relic_avatar_recommend: HashMap<u32, Vec<u32>>,
} }
pub static GAME_RES: LazyLock<JsonConfig> = LazyLock::new(|| { pub static GAME_RES: LazyLock<JsonConfig> = LazyLock::new(|| {
+93 -705
View File
@@ -1,517 +1,17 @@
use proto::*; use proto::{Avatar, AvatarPathData};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
// AVATAR use crate::structs::{
#[derive(Serialize, Deserialize, Debug, Clone)] // AllowedLanguages,
pub struct AvatarJson { avatar::{AvatarJson, MultiPathAvatar},
#[serde(alias = "ownerUid")] battle::BattleConfig,
pub owner_uid: u32, lightcone::Lightcone,
#[serde(alias = "avatarId")] persistent::Persistent,
pub avatar_id: u32, relic::Relic,
pub data: AvatarData, scene::{Position, Scene},
pub level: u32,
pub promotion: u32,
#[serde(alias = "use_technique")]
#[serde(alias = "useTechnique")]
pub techniques: Vec<u32>,
#[serde(alias = "spValue")]
pub sp_value: Option<u32>,
#[serde(alias = "spMax")]
pub sp_max: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AvatarData {
pub rank: u32,
pub skills: HashMap<u32, u32>,
}
impl AvatarJson {
pub fn to_avatar_proto(&self, lightcone: Option<&Lightcone>, relics: Vec<&Relic>) -> Avatar {
// TODO: HARDCODED
let base_avatar_id = if self.avatar_id > 8000 {
8001
} else if self.avatar_id == 1001 || self.avatar_id == 1224 {
1001
} else {
self.avatar_id
}; };
Avatar {
base_avatar_id,
level: self.level,
promotion: self.promotion,
rank: self.data.rank,
skilltree_list: self
.data
.skills
.iter()
.map(|v| AvatarSkillTree {
point_id: *v.0,
level: *v.1,
})
.collect::<Vec<_>>(),
equipment_unique_id: if let Some(lc) = lightcone {
// TODO: HARDCODED LIGHTCONE ID
2000 + lc.internal_uid
} else {
0
},
first_met_timestamp: 1712924677,
equip_relic_list: relics
.iter()
.map(|v| v.to_equipment_relic_proto())
.collect::<Vec<_>>(),
..Default::default()
}
}
pub fn to_battle_avatar_proto(
&self,
index: u32,
lightcone: Option<&Lightcone>,
relics: Vec<&Relic>,
) -> (BattleAvatar, Vec<BattleBuff>) {
let battle_avatar = BattleAvatar {
index,
avatar_type: AvatarType::AvatarUpgradeAvailableType.into(),
id: self.avatar_id,
level: self.level,
rank: self.data.rank,
skilltree_list: self
.data
.skills
.iter()
.map(|v| AvatarSkillTree {
point_id: *v.0,
level: *v.1,
})
.collect::<Vec<_>>(),
equipment_list: if let Some(lc) = lightcone {
vec![lc.to_battle_equipment_proto()]
} else {
vec![]
},
hp: 10_000,
promotion: self.promotion,
relic_list: relics
.iter()
.map(|v| v.to_battle_relic_proto())
.collect::<Vec<_>>(),
world_level: 6,
sp_bar: Some(SpBarInfo {
cur_sp: self.sp_value.unwrap_or(10_000),
max_sp: self.sp_max.unwrap_or(10_000),
}),
..Default::default()
};
let mut battle_buff = Vec::<BattleBuff>::new();
for buff_id in &self.techniques {
battle_buff.push(BattleBuff {
wave_flag: 0xffffffff,
owner_id: index,
level: 1,
id: *buff_id,
dynamic_values: HashMap::from([(String::from("SkillIndex"), 2.0)]),
..Default::default()
});
}
(battle_avatar, battle_buff)
}
pub fn to_lineup_avatar_proto(&self, slot: u32) -> LineupAvatar {
LineupAvatar {
id: self.avatar_id,
hp: 10_000,
satiety: 100,
avatar_type: AvatarType::AvatarFormalType.into(),
sp_bar: Some(SpBarInfo {
cur_sp: self.sp_value.unwrap_or(10_000),
max_sp: self.sp_max.unwrap_or(10_000),
}),
slot,
}
}
pub fn to_lineup_avatars(player: &FreesrData) -> Vec<LineupAvatar> {
let avatar_ids = player
.avatars
.values()
.map(|v| &v.avatar_id)
.collect::<Vec<_>>();
player
.lineups
.iter()
.filter(|(slot, v)| **slot < 4 && v > &&0 && avatar_ids.contains(v))
.map(|(slot, avatar_id)| {
player
.avatars
.get(avatar_id)
.unwrap()
.to_lineup_avatar_proto(*slot)
})
.collect::<Vec<LineupAvatar>>()
}
pub fn to_lineup_info(lineups: &BTreeMap<u32, u32>) -> LineupInfo {
let mut lineup_info = LineupInfo {
extra_lineup_type: ExtraLineupType::LineupNone.into(),
name: "Squad 1".to_string(),
mp: 5,
max_mp: 5,
..Default::default()
};
for id in lineups.values() {
if *id == 0 {
continue;
}
lineup_info.avatar_list.push(LineupAvatar {
id: *id,
hp: 10_000,
satiety: 100,
avatar_type: AvatarType::AvatarFormalType.into(),
sp_bar: Some(SpBarInfo {
cur_sp: 10_000,
max_sp: 10_000,
}),
slot: lineup_info.avatar_list.len() as u32,
});
}
lineup_info
}
}
// LIGHTCONE
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Lightcone {
pub level: u32,
#[serde(alias = "itemId")]
pub item_id: u32,
#[serde(alias = "equipAvatar")]
pub equip_avatar: u32,
pub rank: u32,
pub promotion: u32,
#[serde(alias = "internalUid")]
pub internal_uid: u32,
}
impl Lightcone {
pub fn to_equipment_proto(&self) -> Equipment {
Equipment {
equip_avatar_id: self.equip_avatar,
exp: 0,
is_protected: false,
level: self.level,
promotion: self.promotion,
rank: self.rank,
tid: self.item_id,
// ?
unique_id: 2000 + self.internal_uid,
}
}
pub fn to_battle_equipment_proto(&self) -> BattleEquipment {
BattleEquipment {
id: self.item_id,
level: self.level,
promotion: self.promotion,
rank: self.rank,
}
}
}
// RELIC
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Relic {
pub level: u32,
#[serde(alias = "relicId")]
pub relic_id: u32,
#[serde(alias = "relicSetId")]
pub relic_set_id: u32,
#[serde(alias = "mainAffixId")]
pub main_affix_id: u32,
#[serde(alias = "subAffixes")]
pub sub_affixes: Vec<SubAffix>,
#[serde(alias = "internalUid")]
pub internal_uid: u32,
#[serde(alias = "equipAvatar")]
pub equip_avatar: u32,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct SubAffix {
#[serde(alias = "subAffixId")]
pub sub_affix_id: u32,
pub count: u32,
pub step: u32,
}
impl Relic {
pub fn to_relic_proto(&self) -> proto::Relic {
proto::Relic {
equip_avatar_id: self.equip_avatar,
exp: 0,
is_protected: false,
level: self.level,
main_affix_id: self.main_affix_id,
tid: self.relic_id,
// ?
unique_id: 1 + self.internal_uid,
sub_affix_list: self
.sub_affixes
.iter()
.map(|v| RelicAffix {
affix_id: v.sub_affix_id,
cnt: v.count,
step: v.step,
})
.collect::<Vec<_>>(),
..Default::default()
}
}
pub fn to_battle_relic_proto(&self) -> BattleRelic {
BattleRelic {
id: self.relic_id,
level: self.level,
main_affix_id: self.main_affix_id,
unique_id: self.internal_uid,
sub_affix_list: self
.sub_affixes
.iter()
.map(|v| RelicAffix {
affix_id: v.sub_affix_id,
cnt: v.count,
step: v.step,
})
.collect::<Vec<_>>(),
..Default::default()
}
}
pub fn to_equipment_relic_proto(&self) -> EquipRelic {
EquipRelic {
slot: self.relic_id % 10,
// ?
relic_unique_id: 1 + self.internal_uid,
}
}
}
// MONSTER
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Monster {
pub level: u32,
#[serde(alias = "monsterId")]
pub monster_id: u32,
#[serde(default)]
pub max_hp: u32,
}
impl Monster {
fn to_scene_monster_info(&self) -> SceneMonster {
SceneMonster {
monster_id: self.monster_id,
max_hp: self.max_hp,
cur_hp: self.max_hp,
}
}
pub fn to_scene_monster_wave(wave_index: u32, monsters: &[Self]) -> SceneMonsterWave {
let mut wave_index = wave_index;
if wave_index < 1 {
wave_index += 1;
}
SceneMonsterWave {
wave_id: wave_index, // wave indexx??
wave_param: Some(SceneMonsterWaveParam {
// monster param
level: monsters.iter().map(|v| v.level).max().unwrap_or(95),
..Default::default()
}),
monster_list: monsters
.iter()
.map(|v| v.to_scene_monster_info())
.collect::<Vec<_>>(),
..Default::default()
}
}
pub fn to_scene_monster_waves(monsters: &[Vec<Self>]) -> Vec<SceneMonsterWave> {
monsters
.iter()
.enumerate()
.map(|(i, v)| Self::to_scene_monster_wave(i as u32, v))
.collect::<_>()
}
}
// BATTLE CONFIG
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BattleConfig {
pub battle_type: BattleType,
pub monsters: Vec<Vec<Monster>>,
pub blessings: Vec<BattleBuffJson>,
pub stage_id: u32,
pub cycle_count: u32,
pub path_resonance_id: u32,
pub custom_stats: Vec<SubAffix>,
#[serde(default)]
pub scepters: Vec<RogueMagicScepter>,
}
impl Default for BattleConfig {
fn default() -> Self {
Self {
battle_type: Default::default(),
monsters: vec![vec![Monster {
level: 60,
monster_id: 3014022,
max_hp: 0,
}]],
stage_id: 201012311,
blessings: Default::default(),
cycle_count: Default::default(),
path_resonance_id: Default::default(),
custom_stats: Default::default(),
scepters: Default::default(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum BattleType {
#[serde(alias = "DEFAULT")]
Default = 0,
#[serde(alias = "MOC")]
Moc = 1,
PF = 2,
SU = 3,
AS = 4,
}
impl Default for BattleType {
fn default() -> Self {
Self::Default
}
}
// BATTLE BUFFS
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct BattleBuffJson {
pub level: u32,
pub id: u32,
pub dynamic_key: Option<DynamicKey>,
#[serde(default)]
pub dynamic_values: Vec<DynamicKey>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct DynamicKey {
pub key: String,
pub value: u32,
}
#[allow(dead_code)]
impl BattleBuffJson {
pub fn to_battle_buff_proto(&self) -> proto::BattleBuff {
proto::BattleBuff {
id: self.id,
level: self.level,
wave_flag: 0xffffffff,
owner_id: 0xffffffff,
dynamic_values: if let Some(dyn_key) = &self.dynamic_key {
HashMap::from([(dyn_key.key.clone(), dyn_key.value as f32)])
} else {
Default::default()
},
..Default::default()
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)]
#[repr(u32)]
pub enum RogueMagicComponentType {
Passive = 3,
#[default]
Active = 4,
Attach = 5,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RogueMagicScepter {
pub level: u32,
pub id: u32,
pub components: Vec<RogueMagicComponent>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RogueMagicComponent {
pub id: u32,
pub level: u32,
pub component_type: RogueMagicComponentType,
}
// SCENE
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Scene {
pub plane_id: u32,
pub floor_id: u32,
pub entry_id: u32,
}
impl Default for Scene {
fn default() -> Self {
Self {
entry_id: 2032101,
plane_id: 20321,
floor_id: 20321001,
}
}
}
// Position
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Position {
pub x: i32,
pub y: i32,
pub z: i32,
pub rot_y: i32,
}
impl Position {
#[allow(unused)]
pub fn is_empty(&self) -> bool {
self.x == 0 && self.y == 0 && self.z == 0
}
pub fn to_motion(&self) -> MotionInfo {
MotionInfo {
rot: Some(Vector {
x: 0,
y: self.rot_y,
z: 0,
}),
pos: Some(Vector {
x: self.x,
y: self.y,
z: self.z,
}),
}
}
}
// FREESR-DATA.json
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct FreesrData { pub struct FreesrData {
pub lightcones: Vec<Lightcone>, pub lightcones: Vec<Lightcone>,
@@ -520,186 +20,115 @@ pub struct FreesrData {
#[serde(default)] #[serde(default)]
pub battle_config: BattleConfig, pub battle_config: BattleConfig,
#[serde(default, skip_serializing)] // Non freesr-data.json fields
#[serde(skip_serializing, skip_deserializing)]
pub lineups: BTreeMap<u32, u32>, pub lineups: BTreeMap<u32, u32>,
#[serde(default, skip_serializing)] #[serde(skip_serializing, skip_deserializing)]
pub position: Position, pub position: Position,
#[serde(default, skip_serializing)] #[serde(skip_serializing, skip_deserializing)]
pub scene: Scene, pub scene: Scene,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub main_character: MultiPathAvatar, pub main_character: MultiPathAvatar,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub march_type: MultiPathAvatar, pub march_type: MultiPathAvatar,
#[serde(skip_serializing, skip_deserializing)]
pub enable_sw_global: Option<bool>,
#[serde(skip_serializing, skip_deserializing)]
pub enable_castorice_global: Option<bool>,
} }
#[derive(Debug, Serialize, Deserialize)] impl FreesrData {
pub struct Persistent { pub fn get_avatar_proto(&self, avatar_id: u32, mc_id: u32, march_id: u32) -> Option<Avatar> {
#[serde(default)] let avatar = self.avatars.get(&avatar_id)?;
pub lineups: BTreeMap<u32, u32>, let lightcone = self.lightcones.iter().find(|l| l.equip_avatar == avatar_id);
#[serde(default)] Some(avatar.to_avatar_proto(lightcone, mc_id, march_id))
pub position: Position,
#[serde(default)]
pub scene: Scene,
pub main_character: MultiPathAvatar,
pub march_type: MultiPathAvatar,
} }
impl Default for Persistent { pub fn get_avatar_path_data_proto(&self, avatar_id: u32) -> Option<AvatarPathData> {
fn default() -> Self { let avatar = self.avatars.get(&avatar_id)?;
let mut lineups = BTreeMap::<u32, u32>::new();
lineups.insert(0, 8001);
lineups.insert(1, 0);
lineups.insert(2, 0);
lineups.insert(3, 0);
Self { Some(
lineups, avatar.to_avatar_path_data_proto(
position: Default::default(), self.lightcones.iter().find(|v| v.equip_avatar == avatar_id),
main_character: MultiPathAvatar::FemaleRememberance, self.relics
scene: Default::default(), .iter()
march_type: MultiPathAvatar::MarchHunt, .filter(|relic| relic.equip_avatar == avatar_id)
} .collect(),
} ),
} )
#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Default)]
#[repr(u32)]
pub enum MultiPathAvatar {
MalePyhsical = 8001,
FemalePhysical = 8002,
MalePreservation = 8003,
FemalePreservation = 8004,
MaleHarmony = 8005,
FemaleHarmony = 8006,
MaleRememberance = 8007,
FemaleRememberance = 8008,
MarchHunt = 1224,
MarchPreservation = 1001,
#[default]
Unk = 0,
}
impl From<u32> for MultiPathAvatar {
fn from(value: u32) -> Self {
match value {
8001 => Self::MalePyhsical,
8002 => Self::FemalePhysical,
8003 => Self::MalePreservation,
8004 => Self::FemalePreservation,
8005 => Self::MaleHarmony,
8006 => Self::FemaleHarmony,
8007 => Self::MaleRememberance,
8008 => Self::FemaleRememberance,
1224 => Self::MarchHunt,
1001 => Self::MarchPreservation,
_ => Self::Unk,
}
}
}
impl From<MultiPathAvatar> for u32 {
fn from(value: MultiPathAvatar) -> Self {
match value {
MultiPathAvatar::MalePyhsical => 8001,
MultiPathAvatar::FemalePhysical => 8002,
MultiPathAvatar::MalePreservation => 8003,
MultiPathAvatar::FemalePreservation => 8004,
MultiPathAvatar::MaleHarmony => 8005,
MultiPathAvatar::FemaleHarmony => 8006,
MultiPathAvatar::MaleRememberance => 8007,
MultiPathAvatar::FemaleRememberance => 8008,
MultiPathAvatar::MarchHunt => 1224,
MultiPathAvatar::MarchPreservation => 1001,
_ => 8006,
}
}
}
impl MultiPathAvatar {
#[allow(unused)]
pub fn get_gender(&self) -> Gender {
if (*self as u32) < 8000 {
Gender::None
} else if *self as u32 % 2 == 1 {
Gender::Man
} else {
Gender::Woman
}
}
pub fn get_type(&self) -> MultiPathAvatarType {
match *self {
MultiPathAvatar::MalePyhsical => MultiPathAvatarType::BoyWarriorType,
MultiPathAvatar::FemalePhysical => MultiPathAvatarType::GirlWarriorType,
MultiPathAvatar::MalePreservation => MultiPathAvatarType::BoyKnightType,
MultiPathAvatar::FemalePreservation => MultiPathAvatarType::GirlKnightType,
MultiPathAvatar::MaleHarmony => MultiPathAvatarType::BoyShamanType,
MultiPathAvatar::FemaleHarmony => MultiPathAvatarType::GirlShamanType,
MultiPathAvatar::MarchHunt => MultiPathAvatarType::Mar7thRogueType,
MultiPathAvatar::MarchPreservation => MultiPathAvatarType::Mar7thKnightType,
MultiPathAvatar::Unk => MultiPathAvatarType::None,
MultiPathAvatar::MaleRememberance => MultiPathAvatarType::BoyMemoryType,
MultiPathAvatar::FemaleRememberance => MultiPathAvatarType::GirlMemoryType,
}
}
pub fn is_mc(&self) -> bool {
(*self as u32) > 8000
}
pub fn to_vec() -> Vec<MultiPathAvatar> {
vec![
Self::MalePyhsical,
Self::FemalePhysical,
Self::MalePreservation,
Self::FemalePreservation,
Self::MaleHarmony,
Self::FemaleHarmony,
Self::MaleRememberance,
Self::FemaleRememberance,
Self::MarchHunt,
Self::MarchPreservation,
]
} }
} }
impl FreesrData { impl FreesrData {
pub async fn load() -> Self { pub async fn load() -> anyhow::Result<Self> {
let mut json: FreesrData = tokio::fs::read_to_string("freesr-data.json") let mut freesr_data: FreesrData = tokio::fs::read_to_string("freesr-data.json")
.await .await
.map(|v| { .map_err(|e| anyhow::anyhow!("failed to read freesr-data.json: {e}"))
.and_then(|v| {
serde_json::from_str::<FreesrData>(&v) serde_json::from_str::<FreesrData>(&v)
.expect("freesr-data.json is broken, pls redownload") .map_err(|e| anyhow::anyhow!("freesr-data.json is broken: {e}, pls redownload"))
}) })?;
.expect("failed to read freesr-data.json");
let json2: Persistent = serde_json::from_str( let persistent: Persistent = serde_json::from_str(
&tokio::fs::read_to_string("persistent") &tokio::fs::read_to_string("persistent")
.await .await
.unwrap_or_default(), .unwrap_or_default(),
) )
.unwrap_or_default(); .unwrap_or_default();
json.lineups = json2.lineups; freesr_data.lineups = persistent.lineups;
json.position = json2.position; freesr_data.position = persistent.position;
json.scene = json2.scene; freesr_data.scene = persistent.scene;
json.main_character = json2.main_character; freesr_data.main_character = persistent.main_character;
json.march_type = json2.march_type; freesr_data.march_type = persistent.march_type;
freesr_data.enable_sw_global = persistent.enable_sw_global;
freesr_data.enable_castorice_global = persistent.enable_castorice_global;
// freesr_data.game_language = persistent.game_language;
// freesr_data.voice_langauge = persistent.voice_language;
json.verify_lineup().await; // remove unequipped lightcones
if freesr_data.lightcones.len() > 1500 {
if json.march_type as u32 > 8000 { freesr_data.lightcones.retain(|v| v.equip_avatar != 0);
json.march_type = MultiPathAvatar::MarchHunt;
} }
json // remove unequipped relics
if freesr_data.relics.len() > 3000 {
freesr_data.relics.retain(|v| v.equip_avatar != 0);
} }
pub async fn update(&mut self) { freesr_data.verify_lineup().await;
*self = Self::load().await
if freesr_data.march_type as u32 > 8000 {
freesr_data.march_type = MultiPathAvatar::MarchHunt;
} }
Ok(freesr_data)
}
pub async fn update(&mut self) -> anyhow::Result<()> {
*self = Self::load().await?;
Ok(())
}
// pub async fn refresh_persistent(&mut self) -> anyhow::Result<()> {
// let persistent: Persistent = serde_json::from_str(
// &tokio::fs::read_to_string("persistent")
// .await
// .unwrap_or_default(),
// )
// .unwrap_or_default();
// self.lineups = persistent.lineups;
// self.position = persistent.position;
// self.scene = persistent.scene;
// self.main_character = persistent.main_character;
// self.march_type = persistent.march_type;
// self.enable_sw_global = persistent.enable_sw_global;
// self.enable_castorice_global = persistent.enable_castorice_global;
// Ok(())
// }
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)])
@@ -712,60 +141,19 @@ impl FreesrData {
} }
pub async fn save_persistent(&self) { pub async fn save_persistent(&self) {
let _ = tokio::fs::write( if let Ok(data) = serde_json::to_string_pretty(&Persistent {
"persistent",
serde_json::to_string_pretty(&Persistent {
lineups: self.lineups.clone(), lineups: self.lineups.clone(),
main_character: self.main_character, main_character: self.main_character,
position: self.position.clone(), position: self.position.clone(),
scene: self.scene.clone(), scene: self.scene.clone(),
march_type: self.march_type, march_type: self.march_type,
}) enable_sw_global: self.enable_sw_global,
.unwrap(), enable_castorice_global: self.enable_castorice_global,
) // game_language: self.game_language,
.await; // voice_language: self.voice_langauge,
}) {
let _ = tokio::fs::write("persistent", data).await;
tracing::info!("persistent saved"); tracing::info!("persistent saved");
} }
pub fn get_multi_path_info(&self) -> Vec<MultiPathAvatarTypeInfo> {
MultiPathAvatar::to_vec()
.iter()
.filter_map(|mp_type| {
if mp_type.is_mc() && mp_type.get_gender() != self.main_character.get_gender() {
return Option::None;
}
let avatar_info = self.avatars.get(&((*mp_type) as u32))?;
Some(MultiPathAvatarTypeInfo {
avatar_id: *mp_type as i32,
rank: avatar_info.data.rank,
equip_relic_list: self
.relics
.iter()
.filter(|relic| relic.equip_avatar == *mp_type as u32)
.map(|relic| relic.to_equipment_relic_proto())
.collect(),
skilltree_list: avatar_info
.data
.skills
.iter()
.map(|(point_id, level)| AvatarSkillTree {
point_id: *point_id,
level: *level,
})
.collect(),
path_equipment_id: self
.lightcones
.iter()
.find(|v| v.equip_avatar == *mp_type as u32)
.map(|v| {
// TODO: HARDCODED LIGHTCONE ID
2000 + v.internal_uid
})
.unwrap_or_default(),
dressed_skin_id: 0,
})
})
.collect()
} }
} }
+334
View File
@@ -0,0 +1,334 @@
use std::collections::{BTreeMap, HashMap};
use proto::{
Avatar, AvatarPathData, AvatarPathSkillTree, AvatarSkillTree, AvatarType, BattleAvatar,
BattleBuff, ExtraLineupType, Gender, LineupAvatar, LineupInfo, MultiPathAvatarType, SpBarInfo,
};
use serde::{Deserialize, Serialize};
use crate::sr_tools::FreesrData;
use super::{lightcone::Lightcone, relic::Relic};
#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Default)]
#[repr(u32)]
pub enum MultiPathAvatar {
MalePyhsical = 8001,
FemalePhysical = 8002,
MalePreservation = 8003,
FemalePreservation = 8004,
MaleHarmony = 8005,
FemaleHarmony = 8006,
MaleRemembrance = 8007,
FemaleRemembrance = 8008,
MaleElation = 8009,
FemaleElation = 8010,
MarchHunt = 1224,
MarchPreservation = 1001,
#[default]
Unk = 0,
}
impl From<u32> for MultiPathAvatar {
fn from(value: u32) -> Self {
match value {
8001 => Self::MalePyhsical,
8002 => Self::FemalePhysical,
8003 => Self::MalePreservation,
8004 => Self::FemalePreservation,
8005 => Self::MaleHarmony,
8006 => Self::FemaleHarmony,
8007 => Self::MaleRemembrance,
8008 => Self::FemaleRemembrance,
8009 => Self::MaleElation,
8010 => Self::FemaleElation,
1224 => Self::MarchHunt,
1001 => Self::MarchPreservation,
_ => Self::Unk,
}
}
}
impl From<MultiPathAvatar> for u32 {
fn from(value: MultiPathAvatar) -> Self {
match value {
MultiPathAvatar::MalePyhsical => 8001,
MultiPathAvatar::FemalePhysical => 8002,
MultiPathAvatar::MalePreservation => 8003,
MultiPathAvatar::FemalePreservation => 8004,
MultiPathAvatar::MaleHarmony => 8005,
MultiPathAvatar::FemaleHarmony => 8006,
MultiPathAvatar::MaleRemembrance => 8007,
MultiPathAvatar::FemaleRemembrance => 8008,
MultiPathAvatar::MaleElation => 8009,
MultiPathAvatar::FemaleElation => 8010,
MultiPathAvatar::MarchHunt => 1224,
MultiPathAvatar::MarchPreservation => 1001,
MultiPathAvatar::Unk => 8006,
}
}
}
impl MultiPathAvatar {
#[allow(unused)]
pub fn get_gender(&self) -> Gender {
if (*self as u32) < 8000 {
Gender::None
} else if *self as u32 % 2 == 1 {
Gender::Man
} else {
Gender::Woman
}
}
pub fn get_type(&self) -> MultiPathAvatarType {
match *self {
MultiPathAvatar::MalePyhsical => MultiPathAvatarType::BoyWarriorType,
MultiPathAvatar::FemalePhysical => MultiPathAvatarType::GirlWarriorType,
MultiPathAvatar::MalePreservation => MultiPathAvatarType::BoyKnightType,
MultiPathAvatar::FemalePreservation => MultiPathAvatarType::GirlKnightType,
MultiPathAvatar::MaleHarmony => MultiPathAvatarType::BoyShamanType,
MultiPathAvatar::FemaleHarmony => MultiPathAvatarType::GirlShamanType,
MultiPathAvatar::MarchHunt => MultiPathAvatarType::Mar7thRogueType,
MultiPathAvatar::MarchPreservation => MultiPathAvatarType::Mar7thKnightType,
MultiPathAvatar::Unk => MultiPathAvatarType::None,
MultiPathAvatar::MaleRemembrance => MultiPathAvatarType::BoyMemoryType,
MultiPathAvatar::FemaleRemembrance => MultiPathAvatarType::GirlMemoryType,
MultiPathAvatar::MaleElation => MultiPathAvatarType::BoyElationType,
MultiPathAvatar::FemaleElation => MultiPathAvatarType::GirlElationType,
}
}
pub fn is_mc(&self) -> bool {
(*self as u32) > 8000
}
pub fn to_vec() -> Vec<MultiPathAvatar> {
vec![
Self::MalePyhsical,
Self::FemalePhysical,
Self::MalePreservation,
Self::FemalePreservation,
Self::MaleHarmony,
Self::FemaleHarmony,
Self::MaleRemembrance,
Self::FemaleRemembrance,
Self::MaleElation,
Self::FemaleElation,
Self::MarchHunt,
Self::MarchPreservation,
]
}
}
// AVATAR
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AvatarJson {
// #[serde(alias = "ownerUid")]
// pub owner_uid: u32,
#[serde(alias = "avatarId")]
pub avatar_id: u32,
pub data: AvatarData,
pub level: u32,
pub promotion: u32,
#[serde(alias = "use_technique")]
#[serde(alias = "useTechnique")]
pub techniques: Vec<u32>,
#[serde(alias = "spValue")]
pub sp_value: Option<u32>,
#[serde(alias = "spMax")]
pub sp_max: Option<u32>,
pub enhanced_id: Option<u32>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AvatarData {
pub rank: u32,
pub skills: HashMap<u32, u32>,
#[serde(default)]
pub skills_by_anchor_type: HashMap<u32, u32>,
}
impl AvatarJson {
/// This should only be used to serialize "BaseAvatar". For example, only 8001 for MC and 1001 for March
pub fn to_avatar_proto(
&self,
lightcone: Option<&Lightcone>,
mc_id: u32,
march_id: u32,
) -> Avatar {
// TODO: HARDCODED
let base_avatar_id = if self.avatar_id > 8000 {
8001
} else if self.avatar_id == 1001 || self.avatar_id == 1224 {
1001
} else {
self.avatar_id
};
// TODO: HARDCODED
let cur_multi_path_avatar_type = if base_avatar_id == 8001 {
mc_id
} else if base_avatar_id == 1001 {
march_id
} else {
base_avatar_id
};
Avatar {
base_avatar_id,
level: self.level,
promotion: self.promotion,
equipment_unique_id: lightcone.map(|v| v.get_unique_id()).unwrap_or_default(),
first_met_time_stamp: 1712924677,
cur_multi_path_avatar_type,
has_taken_promotion_reward_list: vec![1, 3, 5],
is_marked: false,
exp: 0,
}
}
pub fn to_avatar_path_data_proto(
&self,
lightcone: Option<&Lightcone>,
relics: Vec<&Relic>,
) -> AvatarPathData {
AvatarPathData {
avatar_id: self.avatar_id,
rank: self.data.rank,
equip_relic_list: relics
.iter()
.filter(|relic| relic.equip_avatar == self.avatar_id)
.map(|&relic| relic.into())
.collect(),
avatar_path_skill_tree: self
.data
.skills_by_anchor_type
.iter()
.map(|(&anchor_type, &level)| AvatarPathSkillTree { anchor_type, level })
.collect(),
path_equipment_id: lightcone.map(|v| v.get_unique_id()).unwrap_or_default(),
unk_enhanced_id: self.enhanced_id.unwrap_or_default(),
unlock_timestamp: 0,
dressed_skin_id: 0,
}
}
pub fn to_battle_avatar_proto(
&self,
index: u32,
lightcone: Option<&Lightcone>,
relics: Vec<&Relic>,
) -> (BattleAvatar, Vec<BattleBuff>) {
let battle_avatar = BattleAvatar {
index,
avatar_type: AvatarType::AvatarUpgradeAvailableType.into(),
id: self.avatar_id,
level: self.level,
rank: self.data.rank,
skilltree_list: self
.data
.skills
.iter()
.map(|v| AvatarSkillTree {
point_id: *v.0,
level: *v.1,
})
.collect::<Vec<_>>(),
equipment_list: if let Some(lc) = lightcone {
vec![lc.into()]
} else {
vec![]
},
hp: 10_000,
promotion: self.promotion,
relic_list: relics.iter().map(|v| (*v).into()).collect::<Vec<_>>(),
world_level: 6,
sp_bar: Some(SpBarInfo {
cur_sp: self.sp_value.unwrap_or(10_000),
max_sp: self.sp_max.unwrap_or(10_000),
}),
enhanced_id: self.enhanced_id.unwrap_or_default(),
..Default::default()
};
let mut battle_buff = Vec::<BattleBuff>::new();
for buff_id in &self.techniques {
battle_buff.push(BattleBuff {
wave_flag: 0xffffffff,
owner_index: index,
level: 1,
id: *buff_id,
dynamic_values: HashMap::from([(String::from("SkillIndex"), 2.0)]),
..Default::default()
});
}
(battle_avatar, battle_buff)
}
pub fn to_lineup_avatar_proto(&self, slot: u32) -> LineupAvatar {
LineupAvatar {
id: self.avatar_id,
hp: 10_000,
satiety: 100,
avatar_type: AvatarType::AvatarFormalType.into(),
sp_bar: Some(SpBarInfo {
cur_sp: self.sp_value.unwrap_or(10_000),
max_sp: self.sp_max.unwrap_or(10_000),
}),
slot,
}
}
pub fn to_lineup_avatars(player: &FreesrData) -> Vec<LineupAvatar> {
let avatar_ids = player
.avatars
.values()
.map(|v| &v.avatar_id)
.collect::<Vec<_>>();
player
.lineups
.iter()
.filter(|(slot, v)| **slot < 4 && v > &&0 && avatar_ids.contains(v))
.map(|(slot, avatar_id)| {
player
.avatars
.get(avatar_id)
.unwrap()
.to_lineup_avatar_proto(*slot)
})
.collect::<Vec<LineupAvatar>>()
}
pub fn to_lineup_info(lineups: &BTreeMap<u32, u32>) -> LineupInfo {
let max_mp = if lineups.contains_key(&1408) { 8 } else { 5 };
let mut lineup_info = LineupInfo {
extra_lineup_type: ExtraLineupType::LineupNone.into(),
name: "Squad 1".to_string(),
mp: max_mp,
max_mp,
..Default::default()
};
for id in lineups.values() {
if *id == 0 {
continue;
}
lineup_info.avatar_list.push(LineupAvatar {
id: *id,
hp: 10_000,
satiety: 100,
avatar_type: AvatarType::AvatarFormalType.into(),
sp_bar: Some(SpBarInfo {
cur_sp: 10_000,
max_sp: 10_000,
}),
slot: lineup_info.avatar_list.len() as u32,
});
}
lineup_info
}
}
+111
View File
@@ -0,0 +1,111 @@
use std::collections::{BTreeMap, HashMap};
use serde::{Deserialize, Serialize};
use super::{monster::Monster, relic::SubAffix};
// BATTLE CONFIG
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BattleConfig {
pub battle_type: BattleType,
pub monsters: Vec<Vec<Monster>>,
pub blessings: Vec<BattleBuffJson>,
pub stage_id: u32,
pub cycle_count: u32,
pub path_resonance_id: u32,
pub custom_stats: Vec<SubAffix>,
#[serde(default)]
pub scepters: Vec<RogueMagicScepter>,
#[serde(default)]
pub custom_battle_lineup: Option<BTreeMap<u32, u32>>,
}
impl Default for BattleConfig {
fn default() -> Self {
Self {
battle_type: Default::default(),
monsters: vec![vec![Monster {
level: 60,
monster_id: 3014022,
max_hp: 0,
}]],
stage_id: 201012311,
blessings: Default::default(),
cycle_count: Default::default(),
path_resonance_id: Default::default(),
custom_stats: Default::default(),
scepters: Default::default(),
custom_battle_lineup: Default::default(),
}
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum BattleType {
#[serde(alias = "DEFAULT")]
#[default]
Default = 0,
#[serde(alias = "MOC")]
Moc = 1,
PF = 2,
SU = 3,
AS = 4,
AA = 5,
}
// BATTLE BUFFS
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct BattleBuffJson {
pub level: u32,
pub id: u32,
pub dynamic_key: Option<DynamicKey>,
#[serde(default)]
pub dynamic_values: Vec<DynamicKey>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct DynamicKey {
pub key: String,
pub value: u32,
}
#[allow(dead_code)]
impl BattleBuffJson {
pub fn to_battle_buff_proto(&self) -> proto::BattleBuff {
proto::BattleBuff {
id: self.id,
level: self.level,
wave_flag: 0xffffffff,
owner_index: 0xffffffff,
dynamic_values: if let Some(dyn_key) = &self.dynamic_key {
HashMap::from([(dyn_key.key.clone(), dyn_key.value as f32)])
} else {
Default::default()
},
..Default::default()
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)]
#[repr(u32)]
pub enum RogueMagicComponentType {
Passive = 3,
#[default]
Active = 4,
Attach = 5,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RogueMagicScepter {
pub level: u32,
pub id: u32,
pub components: Vec<RogueMagicComponent>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct RogueMagicComponent {
pub id: u32,
pub level: u32,
pub component_type: RogueMagicComponentType,
}
+45
View File
@@ -0,0 +1,45 @@
use proto::{BattleEquipment, Equipment, ItemType};
use serde::{Deserialize, Serialize};
use crate::impl_from;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Lightcone {
pub level: u32,
#[serde(alias = "itemId")]
pub item_id: u32,
#[serde(alias = "equipAvatar")]
pub equip_avatar: u32,
pub rank: u32,
pub promotion: u32,
#[serde(alias = "internalUid")]
pub internal_uid: u32,
}
impl_from!(Lightcone, Equipment, |value| {
Equipment {
dress_avatar_id: value.equip_avatar,
exp: 0,
is_protected: false,
level: value.level,
promotion: value.promotion,
rank: value.rank,
tid: value.item_id,
unique_id: value.get_unique_id(),
}
});
impl_from!(Lightcone, BattleEquipment, |value| {
BattleEquipment {
id: value.item_id,
level: value.level,
promotion: value.promotion,
rank: value.rank,
}
});
impl Lightcone {
pub fn get_unique_id(&self) -> u32 {
super::get_item_unique_id(self.internal_uid, ItemType::ItemEquipment)
}
}
+48
View File
@@ -0,0 +1,48 @@
use proto::ItemType;
pub mod avatar;
pub mod battle;
pub mod lightcone;
pub mod monster;
pub mod persistent;
pub mod relic;
pub mod scene;
pub use avatar::*;
pub use battle::*;
pub use lightcone::*;
pub use monster::*;
pub use persistent::*;
pub use scene::*;
pub fn get_item_unique_id(internal_uid: u32, item_type: ItemType) -> u32 {
// srtools start internal_uid from 1
if item_type == ItemType::ItemEquipment {
3000 + internal_uid // 3001..3500
} else {
internal_uid // 1..2000 ->
}
}
#[macro_export]
macro_rules! impl_from {
($from:ty, $to:ty, |$param:ident| $body:block) => {
impl From<$from> for $to {
fn from($param: $from) -> Self {
$body
}
}
impl From<&$from> for $to {
fn from($param: &$from) -> Self {
$body
}
}
impl From<&mut $from> for $to {
fn from($param: &mut $from) -> Self {
$body
}
}
};
}
+49
View File
@@ -0,0 +1,49 @@
use proto::{SceneMonster, SceneMonsterWave, SceneMonsterWaveParam};
use serde::{Deserialize, Serialize};
use crate::impl_from;
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct Monster {
pub level: u32,
#[serde(alias = "monsterId")]
pub monster_id: u32,
#[serde(default)]
pub max_hp: u32,
}
impl_from!(Monster, SceneMonster, |value| {
SceneMonster {
monster_id: value.monster_id,
max_hp: value.max_hp,
cur_hp: value.max_hp,
extra_info: None,
}
});
impl Monster {
pub fn to_scene_monster_wave(mut wave_id: u32, monsters: &[Self]) -> SceneMonsterWave {
if wave_id < 1 {
wave_id += 1;
}
SceneMonsterWave {
battle_wave_id: wave_id,
monster_param: Some(SceneMonsterWaveParam {
level: monsters.iter().map(|v| v.level).max().unwrap_or(95),
..Default::default()
}),
monster_list: monsters.iter().map(|v| v.into()).collect(),
..Default::default()
}
}
pub fn to_scene_monster_waves(monsters: &[Vec<Self>]) -> Vec<SceneMonsterWave> {
monsters
.iter()
.enumerate()
.map(|(i, v)| Self::to_scene_monster_wave(i as u32, v))
.collect::<_>()
}
}
+70
View File
@@ -0,0 +1,70 @@
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use super::{
avatar::MultiPathAvatar,
scene::{Position, Scene},
};
// #[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Default)]
// #[repr(u32)]
// pub enum AllowedLanguages {
// #[default]
// En,
// Jp,
// Kr,
// Cn,
// }
// impl FromStr for AllowedLanguages {
// type Err = ();
// fn from_str(input: &str) -> Result<AllowedLanguages, Self::Err> {
// match input {
// "en" => Ok(AllowedLanguages::En),
// "kr" => Ok(AllowedLanguages::Kr),
// "jp" => Ok(AllowedLanguages::Jp),
// "cn" => Ok(AllowedLanguages::Cn),
// _ => Ok(AllowedLanguages::En),
// }
// }
// }
#[derive(Debug, Serialize, Deserialize)]
pub struct Persistent {
#[serde(default)]
pub lineups: BTreeMap<u32, u32>,
#[serde(default)]
pub position: Position,
#[serde(default)]
pub scene: Scene,
pub main_character: MultiPathAvatar,
pub march_type: MultiPathAvatar,
// pub game_language: AllowedLanguages,
// pub voice_language: AllowedLanguages,
#[serde(default = "default_true")]
pub enable_sw_global: Option<bool>,
#[serde(default = "default_true")]
pub enable_castorice_global: Option<bool>,
}
fn default_true() -> Option<bool> {
Some(true)
}
impl Default for Persistent {
fn default() -> Self {
Self {
lineups: BTreeMap::from([(0, 1313), (1, 1006), (2, 8001), (3, 1405)]),
position: Default::default(),
main_character: MultiPathAvatar::FemaleRemembrance,
scene: Default::default(),
march_type: MultiPathAvatar::MarchHunt,
// game_language: AllowedLanguages::En,
// voice_language: AllowedLanguages::Jp,
enable_sw_global: Some(true),
enable_castorice_global: Some(true),
}
}
}
+95
View File
@@ -0,0 +1,95 @@
use proto::{BattleRelic, EquipRelic, ItemType, RelicAffix};
use serde::{Deserialize, Serialize};
use crate::impl_from;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Relic {
pub level: u32,
#[serde(alias = "relicId")]
pub relic_id: u32,
// #[serde(alias = "relicSetId")]
// pub relic_set_id: u32,
#[serde(alias = "mainAffixId")]
pub main_affix_id: u32,
#[serde(alias = "subAffixes")]
pub sub_affixes: Vec<SubAffix>,
#[serde(alias = "internalUid")]
pub internal_uid: u32,
#[serde(alias = "equipAvatar")]
pub equip_avatar: u32,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
pub struct SubAffix {
#[serde(alias = "subAffixId")]
pub sub_affix_id: u32,
pub count: u32,
pub step: u32,
}
impl Relic {
pub fn is_matching_slot(&self, slot: u32) -> bool {
self.get_slot() == slot
}
pub fn get_slot(&self) -> u32 {
self.relic_id % 10
}
pub fn get_unique_id(&self) -> u32 {
super::get_item_unique_id(self.internal_uid, ItemType::ItemRelic)
}
}
impl_from!(SubAffix, RelicAffix, |v| {
RelicAffix {
affix_id: v.sub_affix_id,
cnt: v.count,
step: v.step,
}
});
impl_from!(Relic, BattleRelic, |value| {
BattleRelic {
id: value.relic_id,
level: value.level,
main_affix_id: value.main_affix_id,
unique_id: value.get_unique_id(),
sub_affix_list: value
.sub_affixes
.iter()
.map(|v| v.into())
.collect::<Vec<_>>(),
..Default::default()
}
});
impl_from!(Relic, EquipRelic, |value| {
EquipRelic {
r#type: value.get_slot(),
relic_unique_id: value.get_unique_id(),
}
});
impl_from!(Relic, proto::Relic, |value| {
proto::Relic {
dress_avatar_id: value.equip_avatar,
exp: 0,
is_protected: false,
level: value.level,
main_affix_id: value.main_affix_id,
tid: value.relic_id,
unique_id: value.get_unique_id(),
sub_affix_list: value
.sub_affixes
.iter()
.map(|v| RelicAffix {
affix_id: v.sub_affix_id,
cnt: v.count,
step: v.step,
})
.collect::<Vec<_>>(),
..Default::default()
}
});
+56
View File
@@ -0,0 +1,56 @@
use proto::{MotionInfo, Vector};
use serde::{Deserialize, Serialize};
use crate::impl_from;
// SCENE
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Scene {
pub plane_id: u32,
pub floor_id: u32,
pub entry_id: u32,
}
impl Default for Scene {
fn default() -> Self {
Self {
plane_id: 20411,
floor_id: 20411001,
entry_id: 2041101,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Position {
pub x: i32,
pub y: i32,
pub z: i32,
pub rot_y: i32,
}
impl Default for Position {
fn default() -> Self {
Position {
x: -26968,
y: 78953,
z: 14457,
rot_y: 11858,
}
}
}
impl_from!(Position, MotionInfo, |value| {
MotionInfo {
rot: Some(Vector {
x: 0,
y: value.rot_y,
z: 0,
}),
pos: Some(Vector {
x: value.x,
y: value.y,
z: value.z,
}),
}
});
+20730 -38657
View File
File diff suppressed because it is too large Load Diff
+21 -19
View File
@@ -4,33 +4,35 @@ edition = "2024"
version.workspace = true version.workspace = true
[dependencies] [dependencies]
ansi_term.workspace = true # Framework
anyhow.workspace = true tokio.workspace = true
atomic_refcell.workspace = true
env_logger.workspace = true
hex.workspace = true
lazy_static.workspace = true
paste.workspace = true
rbase64.workspace = true
notify.workspace = true
notify-debouncer-mini.workspace = true
# JSON
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
tokio.workspace = true # Logging
tokio-util.workspace = true
tracing.workspace = true tracing.workspace = true
tracing-futures.workspace = true ansi_term.workspace = true
tracing-log.workspace = true env_logger.workspace = true
tracing-subscriber.workspace = true
tracing-bunyan-formatter.workspace = true
# Encoding / Serialization
prost.workspace = true prost.workspace = true
proto.workspace = true rbase64.workspace = true
proto-derive.workspace = true
# Cryptography
rand.workspace = true rand.workspace = true
# Utilities
paste.workspace = true
notify.workspace = true
notify-debouncer-mini.workspace = true
# Error handling
anyhow.workspace = true
# Local
mhy-kcp.workspace = true mhy-kcp.workspace = true
common.workspace = true common.workspace = true
proto.workspace = true
proto-derive.workspace = true
+11
View File
@@ -0,0 +1,11 @@
use anyhow::Result;
mod net;
mod util;
use net::gateway::Gateway;
pub async fn start_gameserver() -> Result<()> {
let mut gateway = Gateway::new("0.0.0.0", 23301).await?;
Box::pin(gateway.listen()).await
}
-29
View File
@@ -1,29 +0,0 @@
#[macro_export]
macro_rules! log_error {
($e:expr) => {
if let Err(e) = $e {
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
}
};
($context:expr, $e:expr $(,)?) => {
if let Err(e) = $e {
let e = format!("{:?}", ::anyhow::anyhow!(e).context($context));
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
}
};
($ok_context:expr, $err_context:expr, $e:expr $(,)?) => {
if let Err(e) = $e {
let e = format!("{:?}", ::anyhow::anyhow!(e).context($err_context));
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
} else {
tracing::info!($ok_context);
}
};
}
pub fn init_tracing() {
#[cfg(target_os = "windows")]
ansi_term::enable_ansi_support().unwrap();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
}
+6 -8
View File
@@ -1,16 +1,14 @@
#![feature(let_chains)]
use anyhow::Result; use anyhow::Result;
mod logging; pub fn init_tracing() {
mod net; #[cfg(target_os = "windows")]
mod util; ansi_term::enable_ansi_support().unwrap();
use logging::init_tracing; env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
use net::gateway::Gateway; }
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
init_tracing(); init_tracing();
let mut gateway = Gateway::new("0.0.0.0", 23301).await?; gameserver::start_gameserver().await
Box::pin(gateway.listen()).await
} }
+37 -17
View File
@@ -6,13 +6,13 @@ use std::{
Arc, Arc,
atomic::{AtomicU32, Ordering}, atomic::{AtomicU32, Ordering},
}, },
time::Duration, time::{Duration, SystemTime},
}; };
use anyhow::Result; use anyhow::Result;
use common::sr_tools::FreesrData; use common::sr_tools::FreesrData;
use rand::RngCore; use rand::Rng;
use crate::net::PlayerSession; use crate::net::PlayerSession;
@@ -86,11 +86,11 @@ impl Gateway {
))); )));
// Init the json to session // Init the json to session
let _ = session if let Ok(data) = FreesrData::load().await {
.write() let _ = session.write().await.json_data.set(data);
.await } else {
.json_data tracing::error!("Failed to load initial freesr-data.json");
.set(FreesrData::load().await); }
let session_ref = session.clone(); let session_ref = session.clone();
@@ -98,21 +98,34 @@ impl Gateway {
tokio::spawn(async move { tokio::spawn(async move {
let (tx, mut rx) = mpsc::channel(100); let (tx, mut rx) = mpsc::channel(100);
let mut debouncer = let mut debouncer =
notify_debouncer_mini::new_debouncer(Duration::from_millis(1000), move |ev| { match notify_debouncer_mini::new_debouncer(Duration::from_millis(1000), move |ev| {
let _ = tx.blocking_send(ev); let _ = tx.blocking_send(ev);
}) }) {
.unwrap(); Ok(d) => d,
Err(e) => {
tracing::error!("debouncer init err: {e}");
return;
}
};
let path = Path::new("freesr-data.json"); let path = Path::new("freesr-data.json");
debouncer if let Err(e) = debouncer
.watcher() .watcher()
.watch(path, notify::RecursiveMode::NonRecursive) .watch(path, notify::RecursiveMode::NonRecursive)
.unwrap(); {
tracing::error!("failed to watch {path:?}: {e}");
return;
}
tracing::info!("watching freesr-data.json changes"); tracing::info!("watching freesr-data.json changes");
let mut shutdown_rx = session.read().await.shutdown_rx.clone(); let mut shutdown_rx = session.read().await.shutdown_rx.clone();
let mut last_modified = std::fs::metadata(path)
.and_then(|meta| meta.modified())
.unwrap_or(SystemTime::UNIX_EPOCH);
loop { loop {
tokio::select! { tokio::select! {
res = rx.recv() => { res = rx.recv() => {
@@ -124,16 +137,23 @@ impl Gateway {
if events if events
.iter() .iter()
.any(|p| p.path.file_name() == path.file_name()) .any(|p| p.path.file_name() == path.file_name())
{ && let Ok(metadata) = std::fs::metadata(path)
&& let Ok(modified) = metadata.modified()
&& modified > last_modified {
last_modified = modified;
let mut session = session.write().await; let mut session = session.write().await;
if let Some(json) = session.json_data.get_mut() { if let Some(json) = session.json_data.get_mut() {
let _ = json.update().await; if let Err(e) = json.update().await {
session.sync_player().await; tracing::error!("Failed to update json: {e}");
tracing::info!("json updated") } else {
let _ = session.sync_player().await;
tracing::info!("json updated");
} }
} }
} }
Err(e) => eprintln!("json watcher error: {:?}", e), }
Err(e) => tracing::error!("json watcher error: {e:?}"),
} }
} }
_ = shutdown_rx.changed() => { _ = shutdown_rx.changed() => {
@@ -7,7 +7,6 @@ pub async fn on_player_get_token_cs_req(
_body: &PlayerGetTokenCsReq, _body: &PlayerGetTokenCsReq,
res: &mut PlayerGetTokenScRsp, res: &mut PlayerGetTokenScRsp,
) { ) {
res.msg = String::from("OK");
res.uid = 25; res.uid = 25;
} }
+60 -27
View File
@@ -1,10 +1,14 @@
use super::*; use super::*;
pub static UNLOCKED_AVATARS: [u32; 63] = [ pub static BASE_AVATAR_IDS: [u32; 87] = [
8001, 1001, //
//
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, 1406,
1409, 1014, 1015, 1408, 1410, 1412, 1413, 1414, 1415, 1321, 1501, 1502, 1504, 1505, 1506, 1507,
1508, 1509, 1510, 1512, 1513,
]; ];
pub async fn on_get_avatar_data_cs_req( pub async fn on_get_avatar_data_cs_req(
@@ -17,46 +21,75 @@ pub async fn on_get_avatar_data_cs_req(
return; return;
}; };
// TODO: HARDCODED
let mc_ids = if json.main_character.get_gender() == Gender::Man {
[8001, 8003, 8005, 8007]
} else {
[8002, 8004, 8006, 8008]
};
let march_ids = [1001, 1224];
res.is_get_all = body.is_get_all; res.is_get_all = body.is_get_all;
res.avatar_list = UNLOCKED_AVATARS res.avatar_list = BASE_AVATAR_IDS
.into_iter() .into_iter()
.chain(mc_ids.iter().copied())
.chain(march_ids.iter().copied())
.map(|id| { .map(|id| {
json.avatars json.avatars
.get(&id) .get(&id)
.map(|v| { .map(|v| {
v.to_avatar_proto( v.to_avatar_proto(
json.lightcones.iter().find(|v| v.equip_avatar == id), json.lightcones.iter().find(|v| v.equip_avatar == id),
json.relics json.main_character as u32,
.iter() json.march_type as u32,
.filter(|v| v.equip_avatar == id)
.collect(),
) )
}) })
.unwrap_or(Avatar { .unwrap_or(Avatar {
base_avatar_id: id, base_avatar_id: id,
level: 80, level: 80,
promotion: 6, promotion: 6,
rank: 6, first_met_time_stamp: 1712924677,
skilltree_list: (1..=4) cur_multi_path_avatar_type: 0,
.map(|m| AvatarSkillTree { equipment_unique_id: 0,
point_id: id * 1000 + m, has_taken_promotion_reward_list: vec![1, 3, 5],
level: 1, is_marked: false,
}) exp: 0,
.collect(),
first_met_timestamp: 1712924677,
..Default::default()
}) })
}) })
.collect(); .collect();
res.avatar_path_data_info_list = json
.avatars
.values()
.map(|avatar| {
avatar.to_avatar_path_data_proto(
json.lightcones
.iter()
.find(|l| l.equip_avatar == avatar.avatar_id),
json.relics
.iter()
.filter(|r| r.equip_avatar == avatar.avatar_id)
.collect(),
)
})
.collect();
}
pub async fn on_take_promotion_reward_cs_req(
_session: &mut PlayerSession,
_req: &TakePromotionRewardCsReq,
_res: &mut TakePromotionRewardScRsp,
) {
// retcode defaults to 0, reward_list defaults to empty
// All avatars already report has_taken_promotion_reward_list = [0..=6] in GetAvatarData
}
pub async fn on_set_avatar_enhanced_id_cs_req(
_session: &mut PlayerSession,
_req: &SetAvatarEnhancedIdCsReq,
_res: &mut SetAvatarEnhancedIdScRsp,
) {
// let Some(json) = session.json_data.get_mut() else {
// return;
// };
// let Some(avatar) = json.avatars.get_mut(&req.avatar_id) else {
// return;
// };
// avatar.enhanced_id = if req.enhanced_id == 0 {
// Option::<u32>::None
// } else {
// Some(req.enhanced_id)
// };
// res.growth_avatar_id = avatar.avatar_id;
// res.unk_enhanced_id = req.enhanced_id;
} }
+198 -77
View File
@@ -1,11 +1,10 @@
use std::collections::HashMap; use std::collections::HashMap;
use rand::Rng; use rand::RngExt;
use rogue_magic_battle_unit_info::Item;
use common::{ use common::{
resources::GAME_RES, resources::GAME_RES,
sr_tools::{BattleType, Monster, RogueMagicComponentType}, structs::{BattleType, Monster},
}; };
use super::*; use super::*;
@@ -36,6 +35,16 @@ pub async fn on_quick_start_cocoon_stage_cs_req(
res.battle_info = Some(battle_info); res.battle_info = Some(battle_info);
} }
pub async fn on_scene_enter_stage_cs_req(
session: &mut PlayerSession,
_req: &SceneEnterStageCsReq,
res: &mut SceneEnterStageScRsp,
) {
let battle_info = create_battle_info(session, 0, 0).await;
res.battle_info = Some(battle_info);
}
pub async fn on_pve_battle_result_cs_req( pub async fn on_pve_battle_result_cs_req(
_session: &mut PlayerSession, _session: &mut PlayerSession,
req: &PveBattleResultCsReq, req: &PveBattleResultCsReq,
@@ -50,7 +59,7 @@ pub async fn on_scene_cast_skill_cs_req(
req: &SceneCastSkillCsReq, req: &SceneCastSkillCsReq,
res: &mut SceneCastSkillScRsp, res: &mut SceneCastSkillScRsp,
) { ) {
res.attacked_group_id = req.attacked_group_id; res.cast_entity_id = req.cast_entity_id;
let targets = req let targets = req
.hit_target_entity_id_list .hit_target_entity_id_list
@@ -64,9 +73,9 @@ pub async fn on_scene_cast_skill_cs_req(
return; return;
} }
let battle_info = create_battle_info(session, req.caster_id, req.skill_index).await; let battle_info = create_battle_info(session, req.attacked_by_entity_id, req.skill_index).await;
res.attacked_group_id = req.attacked_group_id; res.cast_entity_id = req.cast_entity_id;
res.battle_info = Some(battle_info); res.battle_info = Some(battle_info);
} }
@@ -75,6 +84,10 @@ async fn create_battle_info(
caster_id: u32, caster_id: u32,
skill_index: u32, skill_index: u32,
) -> SceneBattleInfo { ) -> SceneBattleInfo {
// if let Some(player) = session.json_data.get_mut() {
// let _ = player.refresh_persistent().await;
// }
let Some(player) = session.json_data.get() else { let Some(player) = session.json_data.get() else {
tracing::error!("data is not set!"); tracing::error!("data is not set!");
return SceneBattleInfo::default(); return SceneBattleInfo::default();
@@ -89,8 +102,27 @@ async fn create_battle_info(
..Default::default() ..Default::default()
}; };
let is_calyx = caster_id == 0 && skill_index == 0;
let mut has_dahlia = false;
let mut first_avatar_id = 0;
let mut first_avatar_idx = 9999;
let lineup_uwu = if let Some(custom) = &player.battle_config.custom_battle_lineup {
custom.iter()
} else {
player.lineups.iter()
};
// avatars // avatars
for (avatar_index, avatar_id) in player.lineups.iter() { for (avatar_index, avatar_id) in lineup_uwu {
if first_avatar_id == 0 {
first_avatar_id = *avatar_id;
first_avatar_idx = *avatar_index;
}
if !has_dahlia {
has_dahlia = *avatar_id == 1321;
}
let is_trailblazer = *avatar_id == 8001; let is_trailblazer = *avatar_id == 8001;
let is_march = *avatar_id == 1001; let is_march = *avatar_id == 1001;
@@ -115,9 +147,8 @@ async fn create_battle_info(
.filter(|v| v.equip_avatar == avatar.avatar_id) .filter(|v| v.equip_avatar == avatar.avatar_id)
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
); );
for tech in techs {
battle_info.buff_list.push(tech); battle_info.buff_list.extend(techs);
}
if caster_id > 0 if caster_id > 0
&& *avatar_index == (caster_id - 1) && *avatar_index == (caster_id - 1)
@@ -127,7 +158,7 @@ async fn create_battle_info(
battle_info.buff_list.push(BattleBuff { battle_info.buff_list.push(BattleBuff {
id: avatar_config.weakness_buff_id, id: avatar_config.weakness_buff_id,
level: 1, level: 1,
owner_id: *avatar_index, owner_index: *avatar_index,
wave_flag: 0xffffffff, wave_flag: 0xffffffff,
dynamic_values: HashMap::from([( dynamic_values: HashMap::from([(
String::from("SkillIndex"), String::from("SkillIndex"),
@@ -141,23 +172,79 @@ async fn create_battle_info(
// hardcoded march // hardcoded march
if avatar.avatar_id == 1224 { if avatar.avatar_id == 1224 {
let buffs = BattleBuff { battle_info.buff_list.push(BattleBuff {
id: 122401, id: 122401,
level: 3, level: 3,
wave_flag: 0xffffffff, wave_flag: 0xffffffff,
owner_id: *avatar_index, owner_index: *avatar_index,
dynamic_values: HashMap::from([ dynamic_values: HashMap::from([
(String::from("#ADF_1"), 3f32), (String::from("#ADF_1"), 3f32),
(String::from("#ADF_2"), 3f32), (String::from("#ADF_2"), 3f32),
]), ]),
target_index_list: vec![0], target_index_list: vec![0],
}; });
battle_info.buff_list.push(buffs);
} }
}; };
} }
// Hardcoded Cerydra & Danheng PT technique
// and hardcode dahlia dance partner to 1st in lineup
let first_avatar_attack_id = if let Some(c) = GAME_RES.avatar_configs.get(&first_avatar_id) {
c.weakness_buff_id
} else {
0
};
let len = battle_info.buff_list.len();
let mut has_replaced = false;
let mut dahlia_buffs = Vec::new();
for (i, buff) in battle_info.buff_list.iter_mut().enumerate() {
let is_last = i == len - 1;
if buff.id == 141202 || buff.id == 141403 {
buff.owner_index = first_avatar_idx;
continue;
}
// this is actually useless because there is 0 attack buff id in calyx but idc
if has_dahlia
&& is_calyx
&& let 1000111..=1000117 = buff.id
&& !has_replaced
&& first_avatar_attack_id != 0
{
buff.id = first_avatar_attack_id;
buff.owner_index = first_avatar_idx;
has_replaced = true;
}
if has_dahlia && is_calyx && !has_replaced && first_avatar_attack_id != 0 && is_last {
dahlia_buffs.push(BattleBuff {
id: 1000121,
level: 1,
wave_flag: u32::MAX,
target_index_list: vec![first_avatar_idx],
..Default::default()
});
dahlia_buffs.push(BattleBuff {
id: first_avatar_attack_id,
level: 1,
wave_flag: u32::MAX,
target_index_list: vec![first_avatar_idx],
dynamic_values: HashMap::from([(
String::from("SkillIndex"),
first_avatar_idx as f32,
)]),
..Default::default()
});
}
}
if !dahlia_buffs.is_empty() {
battle_info.buff_list.extend(dahlia_buffs);
}
// custom stats for avatars // custom stats for avatars
for stat in &player.battle_config.custom_stats { for stat in &player.battle_config.custom_stats {
for avatar in &mut battle_info.battle_avatar_list { for avatar in &mut battle_info.battle_avatar_list {
@@ -192,7 +279,7 @@ async fn create_battle_info(
id: blessing.id, id: blessing.id,
level: blessing.level, level: blessing.level,
wave_flag: 0xffffffff, wave_flag: 0xffffffff,
owner_id: 0xffffffff, owner_index: 0xffffffff,
..Default::default() ..Default::default()
}; };
@@ -217,21 +304,27 @@ async fn create_battle_info(
// 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(1, BattleTargetList { battle_info.battle_target_info.insert(
1,
BattleTargetList {
battle_target_list: vec![BattleTarget { battle_target_list: vec![BattleTarget {
id: 10003, id: 10003,
progress: 0, progress: 0,
..Default::default() ..Default::default()
}], }],
}); },
);
} else { } else {
battle_info.battle_target_info.insert(1, BattleTargetList { battle_info.battle_target_info.insert(
1,
BattleTargetList {
battle_target_list: vec![BattleTarget { battle_target_list: vec![BattleTarget {
id: 10002, id: 10002,
progress: 0, progress: 0,
..Default::default() ..Default::default()
}], }],
}); },
);
} }
for i in 2..=4 { for i in 2..=4 {
@@ -240,7 +333,9 @@ async fn create_battle_info(
.insert(i, BattleTargetList::default()); .insert(i, BattleTargetList::default());
} }
battle_info.battle_target_info.insert(5, BattleTargetList { battle_info.battle_target_info.insert(
5,
BattleTargetList {
battle_target_list: vec![ battle_target_list: vec![
BattleTarget { BattleTarget {
id: 2001, id: 2001,
@@ -253,27 +348,29 @@ async fn create_battle_info(
..Default::default() ..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(1, BattleTargetList { battle_info.battle_target_info.insert(
1,
BattleTargetList {
battle_target_list: vec![BattleTarget { battle_target_list: vec![BattleTarget {
id: 90005, id: 90005,
progress: 0, progress: 0,
..Default::default() ..Default::default()
}], }],
}); },
);
} }
// SU // SU
if player.battle_config.battle_type == BattleType::SU { if player.battle_config.battle_type == BattleType::SU {
battle_info battle_info.battle_event.push(BattleEventBattleInfo {
.event_battle_info_list
.push(BattleEventBattleInfo {
battle_event_id: player.battle_config.path_resonance_id, battle_event_id: player.battle_config.path_resonance_id,
status: Some(BattleEventInitedData { status: Some(BattleEventProperty {
sp_bar: Some(SpBarInfo { sp_bar: Some(SpBarInfo {
cur_sp: 10_000, cur_sp: 10_000,
max_sp: 10_000, max_sp: 10_000,
@@ -287,62 +384,86 @@ async fn create_battle_info(
battle_info.monster_wave_list = Monster::to_scene_monster_waves(&player.battle_config.monsters); battle_info.monster_wave_list = Monster::to_scene_monster_waves(&player.battle_config.monsters);
// Rogue Magic // Rogue Magic
// TODO: i dont need these shit
if !player.battle_config.scepters.is_empty() { if !player.battle_config.scepters.is_empty() {
battle_info.rogue_magic_battle_info = Some(RogueMagicBattleInfo { // battle_info.battle_rogue_magic_info = Some(BattleRogueMagicInfo {
player_detail_info: Some(RogueMagicBattleUnitInfo { // detail_info: Some(BattleRogueMagicDetailInfo {
item: Some(Item::BattleRogueMagicData(BattleRogueMagicData { // paiigoggofj: Some(Item::BattleRogueMagicData(BattleRogueMagicData {
round_cnt: Some(BattleRogueMagicRoundCount { // round_cnt: Some(BattleRogueMagicRoundCount {
gpojenhaiba: 3, // gpojenhaiba: 3,
kljklbmlefo: 0, // kljklbmlefo: 0,
}), // }),
battle_scepter_list: player // battle_scepter_list: player
.battle_config // .battle_config
.scepters // .scepters
.iter() // .iter()
.map(|scepter| { // .map(|scepter| {
let mut battle_scepter = BattleRogueMagicScepter { // let mut battle_scepter = BattleRogueMagicScepter {
level: scepter.level, // level: scepter.level,
scepter_id: scepter.id, // scepter_id: scepter.id,
magic_list: Vec::new(), // magic_list: Vec::new(),
trench_count: HashMap::from([(3, 0), (4, 0), (5, 0)]), // trench_count: HashMap::from([(3, 0), (4, 0), (5, 0)]),
}; // };
//
let mut index = [0u32; 3]; // let mut index = [0u32; 3];
//
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 {
RogueMagicComponentType::Passive => (3u32, false), // RogueMagicComponentType::Passive => (3u32, false),
RogueMagicComponentType::Active => (4, true), // RogueMagicComponentType::Active => (4, true),
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];
battle_scepter.magic_list.push(BattleRogueMagicUnit { // battle_scepter.magic_list.push(BattleRogueMagicUnit {
level: component.level, // level: component.level,
unit_id: component.id, // unit_id: component.id,
slot_id: *slot_index, // slot_id: *slot_index,
locked, // locked,
counter_map: Default::default(), // counter_map: Default::default(),
}); // });
*slot_index += 1; // *slot_index += 1;
*battle_scepter.trench_count.get_mut(&slot_type).unwrap() += 1; // *battle_scepter.trench_count.get_mut(&slot_type).unwrap() += 1;
// }
//
// battle_scepter
// })
// .collect(),
// })),
// }),
// modifier_content: Some(BattleRogueMagicModifierInfo {
// rogue_magic_battle_const: 5,
// }),
// });
} }
battle_scepter // Global buffs
}) let (mut has_castorice_global_buff, mut has_silver_wolf_global_buff) = (false, false);
.collect(), for buff in &battle_info.buff_list {
})), if buff.id == 140703 {
}), has_castorice_global_buff = true;
scepter: Some(Plgjihifpag { egmebanhhnf: 5 }), }
}); if buff.id == 150602 {
has_silver_wolf_global_buff = true;
}
} }
// Global Buff if !has_castorice_global_buff && player.enable_castorice_global.unwrap_or(true) {
if !battle_info.buff_list.iter().any(|b| b.id == 140703) {
battle_info.buff_list.push(BattleBuff { battle_info.buff_list.push(BattleBuff {
id: 140703, id: 140703,
level: 1, level: 1,
owner_id: u32::MAX, owner_index: u32::MAX,
wave_flag: u32::MAX,
target_index_list: Vec::with_capacity(0),
dynamic_values: HashMap::with_capacity(0),
});
}
if !has_silver_wolf_global_buff && player.enable_sw_global.unwrap_or(true) {
battle_info.buff_list.push(BattleBuff {
id: 150602,
level: 1,
owner_index: u32::MAX,
wave_flag: u32::MAX, wave_flag: u32::MAX,
target_index_list: Vec::with_capacity(0), target_index_list: Vec::with_capacity(0),
dynamic_values: HashMap::with_capacity(0), dynamic_values: HashMap::with_capacity(0),
+212 -46
View File
@@ -1,18 +1,27 @@
use common::sr_tools::MultiPathAvatar; use common::structs::MultiPathAvatar;
use proto::chat_data::ExtendType;
use std::path::Path;
use tokio::fs;
use crate::{net::PlayerSession, util::cur_timestamp_ms}; use crate::{
net::PlayerSession,
util::{self, cur_timestamp_ms},
};
use super::*; use super::*;
const SERVER_UID: u32 = 727; const SERVER_UID: u32 = 727;
const SERVER_HEAD_ICON: u32 = 201402; const SERVER_HEAD_ICON: u32 = 201402;
const SERVER_CHAT_BUBBLE_ID: u32 = 220005; const SERVER_CHAT_BUBBLE_ID: u32 = 220005;
const SERVER_CHAT_HISTORY: [&str; 5] = [ const SERVER_CHAT_HISTORY: &[&str] = &[
"'lua {path_to_lua_script}' execute lua script",
"'sw {on/off}' enable/disable silver wolf global buff",
"'castorice {on/off}' enable/disable castorice global buff",
"'sync' to synchronize stats between json and in-game view", "'sync' to synchronize stats between json and in-game view",
"'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.neonteam.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(
@@ -20,6 +29,7 @@ pub async fn on_get_friend_login_info_cs_req(
_req: &GetFriendLoginInfoCsReq, _req: &GetFriendLoginInfoCsReq,
res: &mut GetFriendLoginInfoScRsp, res: &mut GetFriendLoginInfoScRsp,
) { ) {
res.black_uid_list = vec![SERVER_UID];
res.friend_uid_list = vec![SERVER_UID]; res.friend_uid_list = vec![SERVER_UID];
} }
@@ -28,11 +38,11 @@ pub async fn on_get_friend_list_info_cs_req(
_req: &GetFriendListInfoCsReq, _req: &GetFriendListInfoCsReq,
res: &mut GetFriendListInfoScRsp, res: &mut GetFriendListInfoScRsp,
) { ) {
res.friend_list = vec![FriendListInfo { res.friend_list = vec![FriendSimpleInfo {
friend_name: String::from("RobinSR"), remark_name: String::from("RobinSR"),
simple_info: Some(SimpleInfo { player_info: Some(PlayerSimpleInfo {
uid: SERVER_UID, uid: SERVER_UID,
platform_type: PlatformType::Pc.into(), platform: PlatformType::Pc.into(),
online_status: FriendOnlineStatus::Online.into(), online_status: FriendOnlineStatus::Online.into(),
head_icon: SERVER_HEAD_ICON, head_icon: SERVER_HEAD_ICON,
chat_bubble_id: SERVER_CHAT_BUBBLE_ID, chat_bubble_id: SERVER_CHAT_BUBBLE_ID,
@@ -42,7 +52,7 @@ pub async fn on_get_friend_list_info_cs_req(
..Default::default() ..Default::default()
}), }),
is_marked: true, is_marked: true,
sent_time: 0, create_time: 0,
..Default::default() ..Default::default()
}]; }];
} }
@@ -53,18 +63,29 @@ pub async fn on_get_private_chat_history_cs_req(
res: &mut GetPrivateChatHistoryScRsp, res: &mut GetPrivateChatHistoryScRsp,
) { ) {
let cur_time = cur_timestamp_ms(); let cur_time = cur_timestamp_ms();
res.chat_list = SERVER_CHAT_HISTORY res.chat_message_list = SERVER_CHAT_HISTORY
.iter() .iter()
.map(|text| Chat { .map(|text| ChatMessageData {
msg_type: MsgType::CustomText.into(), create_time: cur_time,
sent_time: cur_time, ckhpffenobe: Some(Eknabklpeel {
text: String::from(*text), kpobmnlklok: 1,
sender_uid: SERVER_UID, role_id: SERVER_UID,
}),
bkoalkhdlob: Some(Eknabklpeel {
kpobmnlklok: 1,
role_id: SERVER_UID,
}),
message_datas: vec![MessageChatData {
message_type: 1,
chat_data: Some(ChatData {
extend_type: Some(ExtendType::MessageText(text.to_string())),
}),
}],
..Default::default() ..Default::default()
}) })
.collect(); .collect();
res.to_uid = req.to_uid; res.target_side = req.target_side;
res.from_uid = SERVER_UID; res.contact_side = SERVER_UID;
} }
pub async fn on_send_msg_cs_req( pub async fn on_send_msg_cs_req(
@@ -77,20 +98,66 @@ pub async fn on_send_msg_cs_req(
return; return;
}; };
if let Some((cmd, args)) = parse_command(&body.text) { let msg = body
.message_datas
.as_ref()
.and_then(|x| x.chat_data.as_ref())
.and_then(|x| x.extend_type.as_ref())
.and_then(|x| match x {
ExtendType::MessageText(s) => Some(s.as_str()),
_ => Option::<&str>::None,
})
.unwrap_or("");
if let Some((cmd, args)) = parse_command(msg) {
match cmd { match cmd {
"sync" => { "sync" => {
session.sync_player().await; let _ = session.sync_player().await;
session session
.send(RevcMsgScNotify { .send(create_send_message(
msg_type: body.msg_type, 25,
text: String::from("Inventory Synced"), SERVER_UID,
emote: body.emote, body.message_datas
from_uid: SERVER_UID, .as_ref()
to_uid: 25, .map(|v| v.message_type)
chat_type: body.chat_type, .unwrap_or_default(),
hnbepabnbng: body.hnbepabnbng, body.chat_type,
}) String::from("Inventory Synced"),
))
.await
.unwrap();
}
"sw" | "castorice" => {
let status = args.first().unwrap_or(&"on").to_lowercase();
let enabled = match status.as_str() {
"on" | "1" | "true" => true,
"off" | "0" | "false" => false,
_ => true,
};
if cmd == "sw" {
json.enable_sw_global = Some(enabled);
} else {
json.enable_castorice_global = Some(enabled);
}
json.save_persistent().await;
session
.send(create_send_message(
25,
SERVER_UID,
body.message_datas
.as_ref()
.map(|v| v.message_type)
.unwrap_or_default(),
body.chat_type,
format!(
"{} Global Buff: {}",
if cmd == "sw" { "SW" } else { "Castorice" },
if enabled { "Enabled" } else { "Disabled" }
),
))
.await .await
.unwrap(); .unwrap();
} }
@@ -113,18 +180,19 @@ pub async fn on_send_msg_cs_req(
.await .await
.unwrap(); .unwrap();
session.sync_player().await; let _ = session.sync_player().await;
session session
.send(RevcMsgScNotify { .send(create_send_message(
msg_type: body.msg_type, 25,
text: format!("Success change mc to {:#?}", mc), SERVER_UID,
emote: body.emote, body.message_datas
from_uid: SERVER_UID, .as_ref()
to_uid: 25, .map(|v| v.message_type)
chat_type: body.chat_type, .unwrap_or_default(),
hnbepabnbng: body.hnbepabnbng, body.chat_type,
}) format!("Success change mc to {mc:#?}"),
))
.await .await
.unwrap(); .unwrap();
} }
@@ -154,17 +222,84 @@ pub async fn on_send_msg_cs_req(
.unwrap(); .unwrap();
session session
.send(RevcMsgScNotify { .send(create_send_message(
msg_type: body.msg_type, 25,
text: format!("Success change march to {:#?}", march_type), SERVER_UID,
emote: body.emote, body.message_datas
from_uid: SERVER_UID, .as_ref()
to_uid: 25, .map(|v| v.message_type)
chat_type: body.chat_type, .unwrap_or_default(),
hnbepabnbng: body.hnbepabnbng, body.chat_type,
format!("Success change march to {march_type:#?}"),
))
.await
.unwrap();
}
"lua" => {
let path = Path::new(args.first().unwrap_or(&""));
if !path.is_file() {
session
.send(create_send_message(
25,
SERVER_UID,
body.message_datas
.as_ref()
.map(|v| v.message_type)
.unwrap_or_default(),
body.chat_type,
format!("File {path:?} does not exist!"),
))
.await
.unwrap();
}
let data = match fs::read(&path).await {
Ok(file) => file,
Err(err) => {
session
.send(create_send_message(
25,
SERVER_UID,
body.message_datas
.as_ref()
.map(|v| v.message_type)
.unwrap_or_default(),
body.chat_type,
format!("Failed to read file: {err:?}"),
))
.await
.unwrap();
return;
}
};
session
.send(ClientDownloadDataScNotify {
download_data: Some(ClientDownloadData {
version: 51,
time: util::cur_timestamp_ms() as i64,
data,
..Default::default()
}),
}) })
.await .await
.unwrap(); .unwrap();
session
.send(create_send_message(
25,
SERVER_UID,
body.message_datas
.as_ref()
.map(|v| v.message_type)
.unwrap_or_default(),
body.chat_type,
format!("Executed {path:?}"),
))
.await
.unwrap();
} }
_ => {} _ => {}
} }
@@ -180,3 +315,34 @@ fn parse_command(command: &str) -> Option<(&str, Vec<&str>)> {
Some((parts[0], parts[1..].to_vec())) Some((parts[0], parts[1..].to_vec()))
} }
fn create_send_message(
to: u32,
from: u32,
message_type: i32,
chat_type: i32,
msg: String,
) -> RevcMsgScNotify {
RevcMsgScNotify {
chat_type,
pffpfkoglpo: to,
recv_message_data: Some(ChatMessageData {
create_time: cur_timestamp_ms(),
ckhpffenobe: Some(Eknabklpeel {
kpobmnlklok: 1,
role_id: from,
}),
bkoalkhdlob: Some(Eknabklpeel {
kpobmnlklok: 1,
role_id: from,
}),
message_datas: vec![MessageChatData {
message_type,
chat_data: Some(ChatData {
extend_type: Some(chat_data::ExtendType::MessageText(msg)),
}),
}],
..Default::default()
}),
}
}
+2 -2
View File
@@ -9,8 +9,8 @@ pub async fn on_get_gacha_info_cs_req(
end_time: 1924992000, end_time: 1924992000,
begin_time: 0, begin_time: 0,
gacha_ceiling: Some(GachaCeiling::default()), gacha_ceiling: Some(GachaCeiling::default()),
up_info: vec![23002, 1003, 1101, 1104, 23000, 23003], prize_item_list: vec![23002, 1003, 1101, 1104, 23000, 23003],
featured: vec![ item_detail_list: vec![
1001, 1002, 1008, 1009, 1013, 1103, 1105, 1106, 1108, 1109, 1110, 1111, 1201, 1202, 1001, 1002, 1008, 1009, 1013, 1103, 1105, 1106, 1108, 1109, 1110, 1111, 1201, 1202,
1206, 1207, 1210, 1003, 1004, 1101, 1107, 1104, 1209, 1211, 21000, 21001, 21002, 21003, 1206, 1207, 1210, 1003, 1004, 1101, 1107, 1104, 1209, 1211, 21000, 21001, 21002, 21003,
21004, 21005, 21006, 21007, 21008, 21009, 21010, 21011, 21012, 21013, 21014, 21015, 21004, 21005, 21006, 21007, 21008, 21009, 21010, 21011, 21012, 21013, 21014, 21015,
+270 -27
View File
@@ -1,8 +1,9 @@
use common::{resources::GAME_RES, sr_tools::FreesrData};
use proto::{get_big_data_all_recommend_sc_rsp::RecommendType, *}; use proto::{get_big_data_all_recommend_sc_rsp::RecommendType, *};
use crate::net::PlayerSession; use crate::net::PlayerSession;
use super::UNLOCKED_AVATARS; use super::BASE_AVATAR_IDS;
pub async fn on_get_bag_cs_req( pub async fn on_get_bag_cs_req(
session: &mut PlayerSession, session: &mut PlayerSession,
@@ -14,12 +15,8 @@ pub async fn on_get_bag_cs_req(
return; return;
}; };
res.equipment_list = player res.equipment_list = player.lightcones.iter().map(|v| v.into()).collect();
.lightcones res.relic_list = player.relics.iter().map(|v| v.into()).collect();
.iter()
.map(|v| v.to_equipment_proto())
.collect();
res.relic_list = player.relics.iter().map(|v| v.to_relic_proto()).collect();
res.material_list = vec![ res.material_list = vec![
Material { Material {
tid: 101, // Normal Pass tid: 101, // Normal Pass
@@ -43,31 +40,63 @@ pub async fn on_get_archive_data_cs_req(
} }
pub async fn on_dress_relic_avatar_cs_req( pub async fn on_dress_relic_avatar_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_req: &DressRelicAvatarCsReq, req: &DressRelicAvatarCsReq,
_res: &mut DressRelicAvatarScRsp, _: &mut DressRelicAvatarScRsp,
) { ) {
let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
if let Some(pkt) = equip_relic(player, req) {
let _ = session.send(pkt).await;
};
} }
pub async fn on_take_off_relic_cs_req( pub async fn on_take_off_relic_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_req: &TakeOffRelicCsReq, req: &TakeOffRelicCsReq,
_res: &mut TakeOffRelicScRsp, _: &mut TakeOffRelicScRsp,
) { ) {
let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
if let Some(pkt) = unequip_relic(player, req) {
let _ = session.send(pkt).await;
};
} }
pub async fn on_dress_avatar_cs_req( pub async fn on_dress_avatar_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_req: &DressAvatarCsReq, req: &DressAvatarCsReq,
_res: &mut DressAvatarScRsp, _: &mut DressAvatarScRsp,
) { ) {
let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
if let Some(pkt) = set_lightcone_equipper(player, req.avatar_id, req.equipment_unique_id) {
let _ = session.send(pkt).await;
};
} }
pub async fn on_take_off_equipment_cs_req( pub async fn on_take_off_equipment_cs_req(
_session: &mut PlayerSession, session: &mut PlayerSession,
_req: &TakeOffEquipmentCsReq, req: &TakeOffEquipmentCsReq,
_res: &mut TakeOffEquipmentScRsp, _: &mut TakeOffEquipmentScRsp,
) { ) {
let Some(player) = session.json_data.get_mut() else {
tracing::error!("data is not set!");
return;
};
if let Some(pkt) = set_lightcone_equipper(player, req.avatar_id, 0) {
let _ = session.send(pkt).await;
};
} }
pub async fn on_get_big_data_all_recommend_cs_req( pub async fn on_get_big_data_all_recommend_cs_req(
@@ -80,21 +109,22 @@ pub async fn on_get_big_data_all_recommend_cs_req(
match req.big_data_recommend_type() { match req.big_data_recommend_type() {
BigDataRecommendType::RelicAvatar => { BigDataRecommendType::RelicAvatar => {
res.recommend_type = Some(RecommendType::RelicAvatar(BigDataRecommendRelicAvatar { res.recommend_type = Some(RecommendType::RelicAvatar(BigDataRecommendRelicAvatar {
recommended_avatar_info_list: UNLOCKED_AVATARS recommended_avatar_info_list: GAME_RES
.into_iter() .relic_avatar_recommend
.map(|recommend_avatar_id| RecomendedAvatarInfo { .iter()
avatar_id_list: vec![], .map(|(set_id, avatar_list)| RecomendedAvatarInfo {
recommend_avatar_id, avatar_id_list: avatar_list.clone(),
relic_set_id: 0, recommend_avatar_id: avatar_list.first().copied().unwrap_or_default(),
relic_set_id: *set_id,
}) })
.collect(), .collect(),
})) }))
} }
BigDataRecommendType::AvatarRelic => { BigDataRecommendType::AvatarRelic => {
res.recommend_type = Some(RecommendType::AvatarRelic(BigDataRecommendAvatarRelic { res.recommend_type = Some(RecommendType::AvatarRelic(BigDataRecommendAvatarRelic {
recomended_relic_info_list: UNLOCKED_AVATARS recomended_relic_info_list: BASE_AVATAR_IDS
.into_iter() .into_iter()
.map(|avatar_id| RecommendedRelicInfo { .map(|avatar_id| BigDataAvatarRelicRecommend {
avatar_id, avatar_id,
..Default::default() ..Default::default()
}) })
@@ -104,3 +134,216 @@ pub async fn on_get_big_data_all_recommend_cs_req(
_ => {} _ => {}
} }
} }
pub async fn on_rank_up_avatar_cs_req(
session: &mut PlayerSession,
req: &RankUpAvatarCsReq,
_: &mut RankUpAvatarScRsp,
) -> Option<()> {
let player = session.json_data.get_mut()?;
let avatar = player.avatars.get_mut(&req.avatar_id)?;
avatar.data.rank = req.rank;
let avatar_id = avatar.avatar_id;
let mut ret = PlayerSyncScNotify::default();
build_sync(
player,
&mut ret,
vec![avatar_id],
Vec::with_capacity(0),
Vec::with_capacity(0),
);
let _ = session.send(ret).await;
Some(())
}
// TODO: move these somewhere else?
fn set_lightcone_equipper(
player: &mut FreesrData,
target_avatar: u32,
target_lightcone_uid: u32,
) -> Option<PlayerSyncScNotify> {
let mut ret = PlayerSyncScNotify::default();
let target_avatar = player.avatars.get(&target_avatar)?;
let cur_avatar_lc_idx = player
.lightcones
.iter()
.position(|l| l.equip_avatar == target_avatar.avatar_id);
// undress
if target_lightcone_uid == 0
&& let Some(cur_avatar_lc_idx) = cur_avatar_lc_idx
{
player.lightcones[cur_avatar_lc_idx].equip_avatar = 0;
build_sync(
player,
&mut ret,
vec![target_avatar.avatar_id],
vec![cur_avatar_lc_idx],
Vec::with_capacity(0),
);
return Some(ret);
}
let target_lightcone_idx = player
.lightcones
.iter()
.position(|l| l.get_unique_id() == target_lightcone_uid)?;
// jika avatar sekarang sedang pakai LC, kita tukar pemiliknya dengan pemilik dari LC target
if let Some(cur_avatar_lc_idx) = cur_avatar_lc_idx {
player.lightcones[cur_avatar_lc_idx].equip_avatar =
player.lightcones[target_lightcone_idx].equip_avatar;
}
let avatars_sync = vec![
player.lightcones[target_lightcone_idx].equip_avatar, // old
target_avatar.avatar_id, // cur
];
// set kepemilikan lightcone barunya ke avatar sekarang
player.lightcones[target_lightcone_idx].equip_avatar = target_avatar.avatar_id;
build_sync(
player,
&mut ret,
avatars_sync,
[Some(target_lightcone_idx), cur_avatar_lc_idx]
.into_iter()
.flatten()
.collect(),
Vec::with_capacity(0),
);
Some(ret)
}
fn equip_relic(player: &mut FreesrData, req: &DressRelicAvatarCsReq) -> Option<PlayerSyncScNotify> {
let mut ret = PlayerSyncScNotify::default();
let target_avatar = player.avatars.get(&req.avatar_id)?;
let mut avatar_ids_to_sy = vec![];
let mut relic_index_to_sync = vec![];
for param in &req.switch_list {
let Some(target_relic_idx) = player
.relics
.iter()
.position(|v| v.get_unique_id() == param.relic_unique_id)
else {
continue;
};
let cur_avatar_relic_idx = player.relics.iter().position(|r| {
r.equip_avatar == target_avatar.avatar_id && r.get_slot() == param.relic_type
});
// jika avatar sekarang sedang pakai LC, kita tukar pemiliknya dengan pemilik dari LC target
if let Some(cur_avatar_relic_idx) = cur_avatar_relic_idx {
avatar_ids_to_sy.push(player.relics[cur_avatar_relic_idx].equip_avatar);
player.relics[cur_avatar_relic_idx].equip_avatar =
player.relics[target_relic_idx].equip_avatar;
relic_index_to_sync.push(cur_avatar_relic_idx);
}
// old owner
avatar_ids_to_sy.push(player.relics[target_relic_idx].equip_avatar);
// set kepemilikan relic barunya ke avatar sekarang
player.relics[target_relic_idx].equip_avatar = target_avatar.avatar_id;
// new owner
avatar_ids_to_sy.push(player.relics[target_relic_idx].equip_avatar);
relic_index_to_sync.push(target_relic_idx);
}
build_sync(
player,
&mut ret,
avatar_ids_to_sy,
Vec::with_capacity(0),
relic_index_to_sync,
);
Some(ret)
}
fn unequip_relic(player: &mut FreesrData, req: &TakeOffRelicCsReq) -> Option<PlayerSyncScNotify> {
let mut ret = PlayerSyncScNotify::default();
let target_avatar = player.avatars.get(&req.avatar_id)?;
let mut relic_index_to_sync = vec![];
for slot in &req.relic_type_list {
let relics = player
.relics
.iter()
.enumerate()
.filter(|(_, r)| r.equip_avatar == target_avatar.avatar_id && r.get_slot() == *slot)
.map(|(i, _)| i)
.collect::<Vec<_>>();
for relic_idx in &relics {
player.relics[*relic_idx].equip_avatar = 0;
}
relic_index_to_sync.extend(relics);
}
build_sync(
player,
&mut ret,
vec![req.avatar_id],
Vec::with_capacity(0),
relic_index_to_sync,
);
Some(ret)
}
fn build_sync(
player: &mut FreesrData,
ret: &mut PlayerSyncScNotify,
avatar_ids: Vec<u32>,
lightcone_indexes: Vec<usize>,
relic_indexes: Vec<usize>,
) {
let avatar_list = avatar_ids
.iter()
.filter_map(|id| {
player.get_avatar_proto(*id, player.main_character as u32, player.march_type as u32)
})
.collect::<Vec<_>>();
let avatar_path_data_info_list = avatar_ids
.into_iter()
.filter_map(|id| player.get_avatar_path_data_proto(id))
.collect();
ret.avatar_sync = Some(AvatarSync {
avatar_list,
avatar_path_data_info_list,
});
ret.relic_list = relic_indexes
.into_iter()
.map(|id| (&player.relics[id]).into())
.collect();
ret.equipment_list = lightcone_indexes
.into_iter()
.map(|id| (&player.lightcones[id]).into())
.collect();
}
+6 -6
View File
@@ -1,4 +1,4 @@
use common::sr_tools::AvatarJson; use common::structs::AvatarJson;
use scene_entity_info::Entity; use scene_entity_info::Entity;
use scene_entity_refresh_info::RefreshType; use scene_entity_refresh_info::RefreshType;
@@ -76,7 +76,7 @@ pub async fn on_replace_lineup_cs_req(
let lineups = &mut player.lineups; let lineups = &mut player.lineups;
for (slot, avatar_id) in &mut *lineups { for (slot, avatar_id) in &mut *lineups {
if let Some(lineup) = req.slots.get(*slot as usize) { if let Some(lineup) = req.lineup_slot_list.get(*slot as usize) {
*avatar_id = lineup.id; *avatar_id = lineup.id;
} else { } else {
*avatar_id = 0; *avatar_id = 0;
@@ -131,15 +131,15 @@ async fn refresh_lineup(session: &mut PlayerSession) {
session session
.send(SceneGroupRefreshScNotify { .send(SceneGroupRefreshScNotify {
group_refresh_info: vec![SceneGroupRefreshInfo { group_refresh_list: vec![GroupRefreshInfo {
group_id: 0, group_id: 0,
state: 0, state: 0,
group_refresh_type: SceneGroupRefreshType::Loaded.into(), refresh_type: SceneGroupRefreshType::Loaded.into(),
refresh_entity: new_entities, refresh_entity: new_entities,
bccgjihncdn: Vec::with_capacity(0), ..Default::default()
}], }],
floor_id, floor_id,
gfhglffhfbd: 0, dimension_id: 0,
}) })
.await .await
.unwrap(); .unwrap();
+1 -1
View File
@@ -23,7 +23,7 @@ pub async fn on_get_mail_cs_req(
..Default::default() ..Default::default()
}], }],
}), }),
mail_type: MailType::Normal.into(), mail_type: 0,
template_id: 0, template_id: 0,
}]; }];
} }
+2 -2
View File
@@ -5,9 +5,9 @@ pub async fn on_get_mission_status_cs_req(
body: &GetMissionStatusCsReq, body: &GetMissionStatusCsReq,
res: &mut GetMissionStatusScRsp, res: &mut GetMissionStatusScRsp,
) { ) {
res.finished_main_mission_id_list = body.main_mission_id_list.clone(); res.finished_main_mission_id_list = body.sub_mission_id_list.clone();
res.sub_mission_status_list = body res.sub_mission_status_list = body
.sub_mission_id_list .main_mission_id_list
.iter() .iter()
.map(|id| Mission { .map(|id| Mission {
id: *id, id: *id,
+23 -39
View File
@@ -29,30 +29,6 @@ pub use mission::*;
pub use player::*; pub use player::*;
pub use scene::*; pub use scene::*;
#[allow(unused_imports)]
use proto::{
CmdActivityType::*, CmdAdventureType::*, CmdAetherDivideType::*, CmdAlleyType::*,
CmdArchiveType::*, CmdAvatarType::*, CmdBattleCollegeType::*, CmdBattlePassType::*,
CmdBattleType::*, CmdBoxingClubType::*, CmdChallengeType::*, CmdChatType::*,
CmdChessRogueType::*, CmdClockParkType::*, CmdContentPackageType::*, CmdDailyActiveType::*,
CmdDrinkMakerType::*, CmdExpeditionType::*, CmdFantasticStoryActivityType::*,
CmdFeverTimeActivityType::*, CmdFightActivityType::*, CmdFightMathc3Type::*, CmdFightType::*,
CmdFriendType::*, CmdGachaType::*, CmdHeartdialType::*, CmdHeliobusType::*, CmdItemType::*,
CmdJukeboxType::*, CmdLineupType::*, CmdLobbyType::*, CmdMailType::*, CmdMapRotationType::*,
CmdMatchThreeModuleType::*, CmdMatchType::*, CmdMessageType::*, CmdMiscModuleType::*,
CmdMissionType::*, CmdMonopolyType::*, CmdMultiplayerType::*, CmdMultipleDropType::*,
CmdMuseumType::*, CmdOfferingType::*, CmdPamMissionType::*, CmdPhoneType::*,
CmdPlayerBoardType::*, CmdPlayerReturnType::*, CmdPlayerSync::*, CmdPlayerType::*,
CmdPlotType::*, CmdPunkLordType::*, CmdQuestType::*, CmdRaidCollectionType::*, CmdRaidType::*,
CmdRechargeGiftType::*, CmdRecommendType::*, CmdRedDotType::*, CmdReplayType::*,
CmdRndOptionType::*, CmdRogueCommonType::*, CmdRogueEndless::*, CmdRogueModifierType::*,
CmdRogueTournType::*, CmdRogueType::*, CmdRollShopType::*, CmdSceneType::*,
CmdServerPrefsType::*, CmdShopType::*, CmdSpaceZooType::*, CmdStarFightType::*,
CmdStoryLineType::*, CmdStrongChallengeActivityType::*, CmdTalkRewardType::*,
CmdTelevisionActivityType::*, CmdTextJoinType::*, CmdTrainVisitorType::*,
CmdTreasureDungeonType::*, CmdTutorialType::*, CmdWaypointType::*, CmdWolfBroType::*,
};
macro_rules! dummy { macro_rules! dummy {
($($cmd:ident),* $(,)*) => { ($($cmd:ident),* $(,)*) => {
paste! { paste! {
@@ -60,7 +36,7 @@ macro_rules! dummy {
pub const fn should_send_dummy_rsp(cmd_id: u16) -> bool { pub const fn should_send_dummy_rsp(cmd_id: u16) -> bool {
match cmd_id { match cmd_id {
$( $(
x if x == [<Cmd $cmd CsReq>] as u16 => true, x if x == <proto::[<$cmd CsReq>] as proto::CmdID>::CMD_ID => true,
)* )*
_ => false, _ => false,
} }
@@ -69,7 +45,9 @@ macro_rules! dummy {
pub async fn send_dummy_response(&mut 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 == <proto::[<$cmd CsReq>] as proto::CmdID>::CMD_ID => {
<proto::[<$cmd ScRsp>] as proto::CmdID>::CMD_ID
},
)* )*
_ => return Err(anyhow::anyhow!("Invalid request id {req_id:?}")), _ => return Err(anyhow::anyhow!("Invalid request id {req_id:?}")),
}; };
@@ -90,7 +68,7 @@ macro_rules! dummy {
dummy! { dummy! {
// SceneEntityMove, // SceneEntityMove,
GetLevelRewardTakenList, GetLevelRewardTakenList,
GetRogueScoreRewardInfo, // GetRogueScoreRewardInfo, // ?3.7.51
// GetGachaInfo, // GetGachaInfo,
QueryProductInfo, QueryProductInfo,
GetQuestData, GetQuestData,
@@ -98,33 +76,33 @@ dummy! {
// GetFriendListInfo, // GetFriendListInfo,
// GetFriendApplyListInfo, // GetFriendApplyListInfo,
GetCurAssist, GetCurAssist,
GetRogueHandbookData, // GetRogueHandbookData, // ?3.7.51
GetDailyActiveInfo, GetDailyActiveInfo,
GetFightActivityData, GetFightActivityData,
GetMultipleDropInfo, // GetMultipleDropInfo, // ?3.7.51
GetPlayerReturnMultiDropInfo, // GetPlayerReturnMultiDropInfo, // ?3.7.51
GetShareData, // GetShareData, // ?3.7.51
GetTreasureDungeonActivityData, // GetTreasureDungeonActivityData, // ?3.7.51
PlayerReturnInfoQuery, // PlayerReturnInfoQuery, // ?3.7.51
// GetBag, // GetBag,
GetPlayerBoardData, GetPlayerBoardData,
GetActivityScheduleConfig, GetActivityScheduleConfig,
GetMissionData, GetMissionData,
GetChallenge, GetChallenge,
GetCurChallenge, GetCurChallenge,
GetRogueInfo, // GetRogueInfo, // ?3.7.51
GetExpeditionData, GetExpeditionData,
// GetRogueDialogueEventData, // GetRogueDialogueEventData,
GetJukeboxData, GetJukeboxData,
SyncClientResVersion, SyncClientResVersion,
DailyFirstMeetPam, // DailyFirstMeetPam, // ?3.7.51
GetMuseumInfo, // GetMuseumInfo, // ?3.7.51
GetLoginActivity, GetLoginActivity,
GetRaidInfo, GetRaidInfo,
GetTrialActivityData, GetTrialActivityData,
GetBoxingClubInfo, // GetBoxingClubInfo, // ?3.7.51
GetNpcStatus, GetNpcStatus,
TextJoinQuery, // TextJoinQuery, // ?3.7.51
// GetSpringRecoverData, // Removed 2.7.51 // GetSpringRecoverData, // Removed 2.7.51
// GetChatFriendHistory, // GetChatFriendHistory,
GetSecretKeyInfo, GetSecretKeyInfo,
@@ -134,6 +112,12 @@ dummy! {
// PlayerLoginFinish, // PlayerLoginFinish,
InteractProp, InteractProp,
FinishTalkMission, FinishTalkMission,
GetRechargeGiftInfo GetRechargeGiftInfo,
// RelicRecommend // RelicRecommend
GetPreAvatarGrowthInfo,
GetPreAvatarActivityList,
// GetUnreleasedBlockInfo,
GetFriendAssistList,
GetAssistList,
B51RacingGetData
} }
+12 -29
View File
@@ -1,5 +1,3 @@
use std::collections::HashMap;
use super::*; use super::*;
pub async fn on_get_basic_info_cs_req( pub async fn on_get_basic_info_cs_req(
@@ -22,8 +20,8 @@ pub async fn on_player_heart_beat_cs_req(
res.download_data = Some(ClientDownloadData { res.download_data = Some(ClientDownloadData {
version: 51, version: 51,
time: res.server_time_ms as i64, time: res.server_time_ms as i64,
data: rbase64::decode("bG9jYWwgZnVuY3Rpb24gYmV0YV90ZXh0KG9iaikKICAgIGxvY2FsIGdhbWVPYmplY3QgPSBDUy5Vbml0eUVuZ2luZS5HYW1lT2JqZWN0LkZpbmQoIlVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkiKQogICAgaWYgZ2FtZU9iamVjdCB0aGVuCiAgICAgICAgbG9jYWwgdGV4dENvbXBvbmVudCA9IGdhbWVPYmplY3Q6R2V0Q29tcG9uZW50SW5DaGlsZHJlbih0eXBlb2YoQ1MuUlBHLkNsaWVudC5Mb2NhbGl6ZWRUZXh0KSkKICAgICAgICBpZiB0ZXh0Q29tcG9uZW50IHRoZW4KICAgICAgICAgICAgdGV4dENvbXBvbmVudC50ZXh0ID0gIiIKICAgICAgICBlbmQKICAgIGVuZAplbmQKCmxvY2FsIGZ1bmN0aW9uIHZlcnNpb25fdGV4dChvYmopCiAgICBsb2NhbCBnYW1lT2JqZWN0ID0gQ1MuVW5pdHlFbmdpbmUuR2FtZU9iamVjdC5GaW5kKCJWZXJzaW9uVGV4dCIpCiAgICBpZiBnYW1lT2JqZWN0IHRoZW4KICAgICAgICBsb2NhbCB0ZXh0Q29tcG9uZW50ID0gZ2FtZU9iamVjdDpHZXRDb21wb25lbnRJbkNoaWxkcmVuKHR5cGVvZihDUy5SUEcuQ2xpZW50LkxvY2FsaXplZFRleHQpKQogICAgICAgIGlmIHRleHRDb21wb25lbnQgdGhlbgogICAgICAgICAgICB0ZXh0Q29tcG9uZW50LnRleHQgPSAiPGNvbG9yPSMwMGUxZmY+Um9iaW5TUiE8L2NvbG9yPiIKICAgICAgICBlbmQKICAgIGVuZAplbmQKCnZlcnNpb25fdGV4dCgpCmJldGFfdGV4dCgpCg==").unwrap(), data: rbase64::decode("BAEcA2xvZwJDUwtVbml0eUVuZ2luZQtBcHBsaWNhdGlvbg90YXJnZXRGcmFtZVJhdGUPUXVhbGl0eVNldHRpbmdzCnZTeW5jQ291bnQIcGF0Y2hGcHMKR2FtZU9iamVjdARGaW5kKFVJUm9vdC9BYm92ZURpYWxvZy9CZXRhSGludERpYWxvZyhDbG9uZSkDUlBHBkNsaWVudA1Mb2NhbGl6ZWRUZXh0BnR5cGVvZhZHZXRDb21wb25lbnRJbkNoaWxkcmVuJjxjb2xvcj0jMDBlMWZmPnQubWUvbmVvbnRlYW0yNTwvY29sb3I+BHRleHQLVmVyc2lvblRleHQiPGNvbG9yPSMwMGUxZmY+Um9iaW5TUiEgfCA8L2NvbG9yPg88Y29sb3I9IzAwZTFmZj4KR2xvYmFsVmFycw1zX1ZlcnNpb25EYXRhF0dldFNlcnZlclBha1R5cGVWZXJzaW9uCDwvY29sb3I+C3BhdGNoQmV0YVdtBG1haW4GeHBjYWxsBQEBAAAAAAEWAAEAAAAEAQEYAAUAAAAAAgAAAAAACwwAAwACBADABAFoARABAAMEAAAADAAGAAUEAMAEAQAAEAEA0AcAAAAWAAEACAMCAwMDBAQCBADAAwUDBgQFBADAAwcABwgBGAAAAAAAAQAAAAABCAAAAAAJAAAAAAA2DAEDAAIEAMAPAAF2BAAAAAUBBQAVAAICDAQIAAcYAMAPAwQUCQAAAE0sAwIMAgsAAACgQBUCAgAUAACxDAAAABUAAAIFAQ0AEAEA1Q4AAAAMAQMAAgQAwA8AAXYEAAAABQEPABUAAgIMBAgABxgAwA8DBBQJAAAATSwDAgwCCwAAAKBAFQICABQAALEMAAAAFQAAAgUCEAAFAxEADAgIAAcYAMAPBwisEgAAAA8GByMTAAAAFAYGkRQAAAAVBgICBgQGAAUFFQA1AQIFEAEA1Q4AAAAWAAEAFgMCAwMDCQQCBADAAwoDCwMMAw0EBxgAwAMOAw8EAACgQAMQAxEDEgMTAxQDFQMWAxcDGAMZAAwaARgAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAQEBAAAAAAAAAAAAAQD8AAUOAAAAAAEAAQAAAAMJAAAAFQABARYAAQAAABkbARgAAAEaAAAAAAcAAAEAAAxFAAAARAAAAEQBAQBEAgIARAMDAEoAAgAMBAUAAABAQAYFAwAGBgAAFQQDARYAAQAGBgAGAQYCBgMDHAQAAEBABAABAgMBAAEYAAMDBQ0ABAAAAAAAAQAAAAAE").unwrap(),
haehhcpoapp: 0 ..Default::default()
}); });
} }
@@ -34,15 +32,18 @@ pub async fn on_player_login_finish_cs_req(
) -> Result<()> { ) -> Result<()> {
session session
.send(ContentPackageSyncDataScNotify { .send(ContentPackageSyncDataScNotify {
data: Some(PackageData { data: Some(ContentPackageData {
info_list: [ content_package_list: [
200001, 200002, 200003, 200004, 150017, 150015, 150021, 150018, 130011, 130012, 200001, 200002, 200003, 200004, 200005, 200006, 200007, 200008, 200009, 200010,
130013, 200011, 200012, 150017, 150015, 150021, 150018, 130011, 130012, 130013, 150025,
140006, 150026, 130014, 150034, 150029, 150035, 150041, 150039, 150045, 150057,
150042, 150067, 150064, 150063, 150024, 171002, 150068, 150070, 150071, 150073,
150074, 150075, 150076, 150077, 150078, 150079,
] ]
.iter() .into_iter()
.map(|v| ContentInfo { .map(|v| ContentPackageInfo {
status: ContentPackageStatus::Finished.into(), status: ContentPackageStatus::Finished.into(),
content_id: *v, content_id: v,
}) })
.collect(), .collect(),
..Default::default() ..Default::default()
@@ -52,21 +53,3 @@ pub async fn on_player_login_finish_cs_req(
Ok(()) Ok(())
} }
pub async fn on_get_multi_path_avatar_info_cs_req(
session: &mut PlayerSession,
_req: &GetMultiPathAvatarInfoCsReq,
res: &mut GetMultiPathAvatarInfoScRsp,
) {
let Some(json) = session.json_data.get() else {
tracing::error!("data is not set!");
return;
};
res.current_multi_path_avatar_id = HashMap::from([
(8001, json.main_character.get_type().into()),
(1001, json.march_type.get_type().into()),
]);
res.multi_path_avatar_type_info_list = json.get_multi_path_info();
}
+88 -48
View File
@@ -2,11 +2,9 @@ use std::collections::HashMap;
use common::{ use common::{
resources::GAME_RES, resources::GAME_RES,
sr_tools::{AvatarJson, Position}, structs::{AvatarJson, Position},
}; };
use lazy_static::lazy_static;
use scene_entity_info::Entity; use scene_entity_info::Entity;
use tokio::sync::Mutex;
use crate::util::{self}; use crate::util::{self};
@@ -44,11 +42,11 @@ pub async fn on_enter_scene_cs_req(
req: &EnterSceneCsReq, req: &EnterSceneCsReq,
res: &mut EnterSceneScRsp, res: &mut EnterSceneScRsp,
) { ) {
if load_scene(session, req.entry_id, true, Some(req.teleport_id)) if load_scene(session, req.entry_id, true, Some(req.entry_id2))
.await .await
.is_err() .is_err()
{ {
res.retcode = Retcode::RetSceneEntryIdNotMatch as u32; res.retcode = 2605;
}; };
} }
@@ -57,34 +55,33 @@ pub async fn on_get_scene_map_info_cs_req(
req: &GetSceneMapInfoCsReq, req: &GetSceneMapInfoCsReq,
res: &mut GetSceneMapInfoScRsp, res: &mut GetSceneMapInfoScRsp,
) { ) {
for floor_id in &req.floor_id_list { for floor_id in req.scene_identifiers.iter().map(|v| v.floor_id) {
let mut map_info = MazeMapData { let mut map_info = SceneMapInfo {
retcode: 0, chest_list: vec![
unlocked_chest_list: vec![ ChestInfo {
MazeChest { chest_type: 101,
map_info_chest_type: MapInfoChestType::Normal.into(),
..Default::default() ..Default::default()
}, },
MazeChest { ChestInfo {
map_info_chest_type: MapInfoChestType::Puzzle.into(), chest_type: 102,
..Default::default() ..Default::default()
}, },
MazeChest { ChestInfo {
map_info_chest_type: MapInfoChestType::Challenge.into(), chest_type: 104,
..Default::default() ..Default::default()
}, },
], ],
floor_id: *floor_id, floor_id: floor_id,
scene_identifier: Some(SceneIdentifier {
floor_id,
..Default::default()
}),
..Default::default() ..Default::default()
}; };
for i in 0..100 { let floor_configs = GAME_RES
map_info.lighten_section_list.push(i)
}
let group_config = GAME_RES
.map_default_entrance_map .map_default_entrance_map
.get(floor_id) .get(&floor_id)
.and_then(|v| { .and_then(|v| {
GAME_RES GAME_RES
.level_output_configs .level_output_configs
@@ -92,33 +89,47 @@ pub async fn on_get_scene_map_info_cs_req(
.and_then(|v| v.iter().next()) .and_then(|v| v.iter().next())
}); });
if let Some((_, group_config)) = group_config { if let Some((_, floor_config)) = floor_configs {
for (group_id, group) in group_config.scenes.iter() { for (group_id, group) in floor_config.scenes.iter() {
map_info.maze_group_list.push(MazeGroup { map_info.group_list.push(MapInfoGroup {
group_id: *group_id, group_id: *group_id,
..Default::default() ..Default::default()
}); });
for teleport in group.teleports.keys() { for teleport in group.teleports.keys() {
map_info.unlocked_teleport_list.push(*teleport) map_info.unlock_teleport_list.push(*teleport)
} }
for prop in &group.props { for prop in &group.props {
map_info.maze_prop_list.push(MazeProp { map_info.map_info_prop_list.push(MazePropState {
group_id: prop.group_id, group_id: prop.group_id,
state: prop.prop_state, state: prop.prop_state,
config_id: prop.inst_id, config_id: prop.inst_id,
extra_info: Option::<PropExtraInfo>::None,
}); });
} // map_info.maze_group_list.push(MazeGroup {
} // group_id: prop.group_id,
} // state: prop.prop_state,
// config_id: prop.inst_id,
res.map_list.push(map_info) // extra_info: Option::None,
// });
} }
} }
lazy_static! { map_info.lighten_section_list = floor_config.sections.clone();
static ref NEXT_SCENE_SAVE: Mutex<u64> = Mutex::new(0); map_info.floor_saved_value_map = floor_config.saved_values.clone();
// #TODO!
// map_info
// .chest_unlock_progress_list
// .push(ChestUnlockProgress {
// r#type: 0,
// total_chest_count: 25,
// unlocked_chest_count: 25,
// });
}
res.scene_map_info_list.push(map_info)
}
} }
pub async fn on_scene_entity_move_cs_req( pub async fn on_scene_entity_move_cs_req(
@@ -131,14 +142,12 @@ pub async fn on_scene_entity_move_cs_req(
return; return;
}; };
let mut timestamp = NEXT_SCENE_SAVE.lock().await; if util::cur_timestamp_ms() <= session.next_scene_save {
if util::cur_timestamp_ms() <= *timestamp {
return; return;
} }
// save every 5 sec // save every 5 sec
*timestamp = util::cur_timestamp_ms() + (5 * 1000); session.next_scene_save = util::cur_timestamp_ms() + (5 * 1000);
for entity in &req.entity_motion_list { for entity in &req.entity_motion_list {
if entity.entity_id != 0 { if entity.entity_id != 0 {
@@ -165,7 +174,7 @@ pub async fn on_get_entered_scene_cs_req(
_req: &GetEnteredSceneCsReq, _req: &GetEnteredSceneCsReq,
res: &mut GetEnteredSceneScRsp, res: &mut GetEnteredSceneScRsp,
) { ) {
res.entered_scene_info = GAME_RES res.entered_scene_info_list = GAME_RES
.level_output_configs .level_output_configs
.iter() .iter()
.flat_map(|(_, v)| { .flat_map(|(_, v)| {
@@ -243,6 +252,36 @@ async fn load_scene(
} else { } else {
scene.world_id scene.world_id
}, },
lighten_section_list: scene.sections.clone(),
opened_chests_list: scene
.scenes
.values()
.flat_map(|v| v.chests.clone())
.collect::<Vec<_>>(),
scene_mission_info: Some(MissionStatusBySceneInfo {
finished_main_mission_id_list: scene
.scenes
.values()
.flat_map(|s| s.finished_main_missions.clone())
.collect::<Vec<_>>(),
sub_mission_status_list: scene
.scenes
.values()
.flat_map(|s| {
s.finished_sub_missions.iter().map(|sm| Mission {
id: *sm,
status: MissionStatus::MissionFinish.into(),
progress: 0,
})
})
.collect::<Vec<_>>(),
..Default::default()
}),
floor_saved_data: scene.saved_values.clone(),
scene_identifier: Some(SceneIdentifier {
floor_id,
..Default::default()
}),
..Default::default() ..Default::default()
}; };
@@ -285,7 +324,7 @@ async fn load_scene(
let entity_info = SceneEntityInfo { let entity_info = SceneEntityInfo {
inst_id: prop.inst_id, inst_id: prop.inst_id,
group_id: prop.group_id, group_id: prop.group_id,
motion: Some(prop_position.to_motion()), motion: Some(prop_position.into()),
entity: Some(Entity::Prop(ScenePropInfo { entity: Some(Entity::Prop(ScenePropInfo {
prop_state: prop.prop_state, prop_state: prop.prop_state,
prop_id: prop.prop_id, prop_id: prop.prop_id,
@@ -316,7 +355,7 @@ async fn load_scene(
inst_id: npc.inst_id, inst_id: npc.inst_id,
group_id: npc.group_id, group_id: npc.group_id,
entity_id: npc_entity_id, entity_id: npc_entity_id,
motion: Some(npc_position.to_motion()), motion: Some(npc_position.into()),
entity: Some(Entity::Npc(SceneNpcInfo { entity: Some(Entity::Npc(SceneNpcInfo {
npc_id: npc.npc_id, npc_id: npc.npc_id,
..Default::default() ..Default::default()
@@ -347,7 +386,7 @@ async fn load_scene(
inst_id: monster.inst_id, inst_id: monster.inst_id,
group_id: monster.group_id, group_id: monster.group_id,
entity_id: monster_entity_id, entity_id: monster_entity_id,
motion: Some(monster_position.to_motion()), motion: Some(monster_position.into()),
entity: Some(Entity::NpcMonster(npc_monster)), entity: Some(Entity::NpcMonster(npc_monster)),
}; };
@@ -361,18 +400,19 @@ async fn load_scene(
scene_info.entity_group_list.push(group_info); scene_info.entity_group_list.push(group_info);
scene_info.group_state_list.push(SceneGroupState { // TODO: ?
group_id: *group_id, // scene_info.group_state_list.push(SceneGroupState {
is_default: true, // group_id: *group_id,
state: 0, // is_default: true,
}); // state: 0,
// });
} }
// load player entity // load player entity
scene_info.entity_group_list.push(SceneEntityGroupInfo { scene_info.entity_group_list.push(SceneEntityGroupInfo {
state: 0, state: 0,
group_id: 0, group_id: 0,
hejamoojbcj: HashMap::with_capacity(0), banbecdcdhg: HashMap::with_capacity(0),
entity_list: json entity_list: json
.lineups .lineups
.iter() .iter()
+11 -32
View File
@@ -3,29 +3,6 @@ use paste::paste;
use tracing::Instrument; use tracing::Instrument;
use proto::*; use proto::*;
#[allow(unused_imports)]
use proto::{
CmdActivityType::*, CmdAdventureType::*, CmdAetherDivideType::*, CmdAlleyType::*,
CmdArchiveType::*, CmdAvatarType::*, CmdBattleCollegeType::*, CmdBattlePassType::*,
CmdBattleType::*, CmdBoxingClubType::*, CmdChallengeType::*, CmdChatType::*,
CmdChessRogueType::*, CmdClockParkType::*, CmdContentPackageType::*, CmdDailyActiveType::*,
CmdDrinkMakerType::*, CmdExpeditionType::*, CmdFantasticStoryActivityType::*,
CmdFeverTimeActivityType::*, CmdFightActivityType::*, CmdFightMathc3Type::*, CmdFightType::*,
CmdFriendType::*, CmdGachaType::*, CmdHeartdialType::*, CmdHeliobusType::*, CmdItemType::*,
CmdJukeboxType::*, CmdLineupType::*, CmdLobbyType::*, CmdMailType::*, CmdMapRotationType::*,
CmdMatchThreeModuleType::*, CmdMatchType::*, CmdMessageType::*, CmdMiscModuleType::*,
CmdMissionType::*, CmdMonopolyType::*, CmdMultiplayerType::*, CmdMultipleDropType::*,
CmdMuseumType::*, CmdOfferingType::*, CmdPamMissionType::*, CmdPhoneType::*,
CmdPlayerBoardType::*, CmdPlayerReturnType::*, CmdPlayerSync::*, CmdPlayerType::*,
CmdPlotType::*, CmdPunkLordType::*, CmdQuestType::*, CmdRaidCollectionType::*, CmdRaidType::*,
CmdRecommendType::*, CmdRedDotType::*, CmdReplayType::*, CmdRndOptionType::*,
CmdRogueCommonType::*, CmdRogueEndless::*, CmdRogueModifierType::*, CmdRogueTournType::*,
CmdRogueType::*, CmdRollShopType::*, CmdSceneType::*, CmdServerPrefsType::*, CmdShopType::*,
CmdSpaceZooType::*, CmdStarFightType::*, CmdStoryLineType::*,
CmdStrongChallengeActivityType::*, CmdTalkRewardType::*, CmdTelevisionActivityType::*,
CmdTextJoinType::*, CmdTrainVisitorType::*, CmdTreasureDungeonType::*, CmdTutorialType::*,
CmdWaypointType::*, CmdWolfBroType::*,
};
use super::PlayerSession; use super::PlayerSession;
use super::handlers::*; use super::handlers::*;
@@ -138,27 +115,27 @@ macro_rules! trait_handler {
} }
)* )*
async fn on_message(session: &mut PlayerSession, cmd_type: u16, payload: Vec<u8>) -> Result<()> { async fn on_message(session: &mut PlayerSession, cmd_id: 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_id) {
session.send_dummy_response(cmd_type).await?; session.send_dummy_response(cmd_id).await?;
return Ok(()); return Ok(());
} }
match cmd_type { match cmd_id {
$( $(
cmd_type if cmd_type == paste! { [<Cmd$name CsReq>] as u16 } => { cmd_id if cmd_id == paste! { <proto::[<$name CsReq>] as proto::CmdID>::CMD_ID } => {
let body = paste! { proto::[<$name CsReq>]::decode(&mut &payload[..])? }; let body = paste! { proto::[<$name CsReq>]::decode(&mut &payload[..])? };
paste! { paste! {
Self::[<on_$name:snake _cs_req>](session, &body) Self::[<on_$name:snake _cs_req>](session, &body)
.instrument(tracing::info_span!(stringify!([<on_$name:snake>]), cmd_type = cmd_type)) .instrument(tracing::info_span!(stringify!([<on_$name:snake>]), cmd_id = cmd_id))
.await .await
} }
} }
)* )*
_ => { _ => {
tracing::warn!("Unknown command type: {cmd_type}"); tracing::warn!("Unknown command ID: {cmd_id}");
Ok(()) Ok(())
}, },
} }
@@ -172,13 +149,13 @@ trait_handler! {
PlayerLogin; PlayerLogin;
GetMissionStatus; GetMissionStatus;
GetBasicInfo; GetBasicInfo;
GetMultiPathAvatarInfo;
GetAvatarData; GetAvatarData;
GetAllLineupData; GetAllLineupData;
GetCurLineupData; GetCurLineupData;
GetCurSceneInfo; GetCurSceneInfo;
PlayerHeartBeat; PlayerHeartBeat;
SetAvatarEnhancedId;
TakePromotionReward;
// Entity move (dummy!) // Entity move (dummy!)
SceneEntityMove; SceneEntityMove;
@@ -190,6 +167,7 @@ trait_handler! {
TakeOffEquipment; TakeOffEquipment;
DressRelicAvatar; DressRelicAvatar;
TakeOffRelic; TakeOffRelic;
RankUpAvatar;
// Chat (dummy!) // Chat (dummy!)
SendMsg; SendMsg;
@@ -208,6 +186,7 @@ trait_handler! {
PveBattleResult; PveBattleResult;
SceneCastSkill; SceneCastSkill;
QuickStartCocoonStage; QuickStartCocoonStage;
SceneEnterStage;
// Teleport // Teleport
GetEnteredScene; GetEnteredScene;
+65 -43
View File
@@ -17,7 +17,7 @@ use tokio::{
sync::{Mutex, watch}, sync::{Mutex, watch},
}; };
use crate::util; use crate::{net::handlers::BASE_AVATAR_IDS, util};
use super::{NetPacket, packet::CommandHandler}; use super::{NetPacket, packet::CommandHandler};
@@ -33,6 +33,7 @@ pub struct PlayerSession {
pub shutdown_tx: watch::Sender<()>, pub shutdown_tx: watch::Sender<()>,
pub shutdown_rx: watch::Receiver<()>, pub shutdown_rx: watch::Receiver<()>,
pub json_data: OnceLock<FreesrData>, pub json_data: OnceLock<FreesrData>,
pub next_scene_save: u64,
} }
impl PlayerSession { impl PlayerSession {
@@ -40,14 +41,17 @@ impl PlayerSession {
let (shutdown_tx, shutdown_rx) = watch::channel(()); let (shutdown_tx, shutdown_rx) = watch::channel(());
Self { Self {
token, token,
kcp: Arc::new(Mutex::new(Kcp::new(conv, token, false, RemoteEndPoint { kcp: Arc::new(Mutex::new(Kcp::new(
socket, conv,
addr, token,
}))), false,
RemoteEndPoint { socket, addr },
))),
start_time: util::cur_timestamp_secs(), start_time: util::cur_timestamp_secs(),
json_data: OnceLock::new(), json_data: OnceLock::new(),
shutdown_rx, shutdown_rx,
shutdown_tx, shutdown_tx,
next_scene_save: 0,
} }
} }
@@ -112,72 +116,90 @@ impl PlayerSession {
Ok(()) Ok(())
} }
pub async fn sync_player(&self) { pub async fn sync_player(&self) -> Result<()> {
let Some(json) = self.json_data.get() else { let Some(json) = self.json_data.get() else {
tracing::error!("data is not init!"); tracing::error!("data is not init!");
return; return Ok(());
}; };
// clear relics & lightcones // clear relics & lightcones
self.send(PlayerSyncScNotify { self.send(PlayerSyncScNotify {
del_equipment_list: (2000..3500).collect(), del_relic_list: (1..=3000).collect(),
del_relic_list: (1..2000).collect(), del_equipment_list: (3001..=3500).collect(),
..Default::default() ..Default::default()
}) })
.await .await?;
.unwrap();
// Sync avatars // clear all avatars equip item
self.send(PlayerSyncScNotify { self.send(PlayerSyncScNotify {
avatar_sync: Some(AvatarSync { avatar_sync: Some(AvatarSync {
avatar_list: json avatar_list: BASE_AVATAR_IDS
.avatars .into_iter()
.values() .filter_map(|id| {
.map(|avatar| avatar.to_avatar_proto(Option::None, vec![])) json.avatars.get(&id).map(|v| {
.collect::<Vec<_>>(), v.to_avatar_proto(
}), Option::None,
multi_path_avatar_type_info_list: json.get_multi_path_info(), json.main_character as u32,
..Default::default() json.march_type as u32,
)
})
}) })
.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(), .collect(),
..Default::default() avatar_path_data_info_list: json
})
.await
.unwrap();
// Sync new lightcones
self.send(PlayerSyncScNotify {
avatar_sync: Some(AvatarSync {
avatar_list: json
.avatars .avatars
.values() .values()
.map(|avatar| { .map(|avatar| {
avatar.to_avatar_proto( avatar.to_avatar_path_data_proto(Option::None, Vec::with_capacity(0))
})
.collect::<Vec<_>>(),
}),
..Default::default()
})
.await?;
// Sync new relics & lightcones
self.send(PlayerSyncScNotify {
relic_list: json.relics.iter().map(|v| v.into()).collect(),
equipment_list: json.lightcones.iter().map(|v| v.into()).collect(),
..Default::default()
})
.await?;
// Sync new avatars equip item
self.send(PlayerSyncScNotify {
avatar_sync: Some(AvatarSync {
avatar_list: BASE_AVATAR_IDS
.into_iter()
.filter_map(|avatar_id| {
json.avatars.get(&avatar_id).map(|v| {
v.to_avatar_proto(
json.lightcones.iter().find(|v| v.equip_avatar == avatar_id),
json.main_character as u32,
json.march_type as u32,
)
})
})
.collect(),
avatar_path_data_info_list: json
.avatars
.values()
.map(|avatar| {
avatar.to_avatar_path_data_proto(
json.lightcones json.lightcones
.iter() .iter()
.find(|v| v.equip_avatar == avatar.avatar_id), .find(|v| v.equip_avatar == avatar.avatar_id),
json.relics json.relics
.iter() .iter()
.filter(|v| v.equip_avatar == avatar.avatar_id) .filter(|r| r.equip_avatar == avatar.avatar_id)
.collect(), .collect(),
) )
}) })
.collect(), .collect::<Vec<_>>(),
}), }),
..Default::default() ..Default::default()
}) })
.await .await
.unwrap()
} }
fn session_time(&self) -> u64 { fn session_time(&self) -> u64 {
+2 -6
View File
@@ -16,7 +16,7 @@ use bytes::{Buf, BufMut, BytesMut};
#[cfg(feature = "tokio")] #[cfg(feature = "tokio")]
use tokio::io::{AsyncWrite, AsyncWriteExt}; use tokio::io::{AsyncWrite, AsyncWriteExt};
use crate::{error::Error, KcpResult}; use crate::{KcpResult, error::Error};
const KCP_RTO_NDL: u32 = 20; // no delay min rto const KCP_RTO_NDL: u32 = 20; // no delay min rto
const KCP_RTO_MIN: u32 = 100; // normal min rto const KCP_RTO_MIN: u32 = 100; // normal min rto
@@ -528,11 +528,7 @@ impl<Output> Kcp<Output> {
self.rx_srtt = rtt; self.rx_srtt = rtt;
self.rx_rttval = rtt / 2; self.rx_rttval = rtt / 2;
} else { } else {
let delta = if rtt > self.rx_srtt { let delta = rtt.abs_diff(self.rx_srtt);
rtt - self.rx_srtt
} else {
self.rx_srtt - rtt
};
self.rx_rttval = (3 * self.rx_rttval + delta) / 4; self.rx_rttval = (3 * self.rx_rttval + delta) / 4;
self.rx_srtt = ((7 * u64::from(self.rx_srtt) + u64::from(rtt)) / 8) as u32; self.rx_srtt = ((7 * u64::from(self.rx_srtt) + u64::from(rtt)) / 8) as u32;
if self.rx_srtt < 1 { if self.rx_srtt < 1 {
+2 -2
View File
@@ -7,11 +7,11 @@ mod kcp;
/// The `KCP` prelude /// The `KCP` prelude
pub mod prelude { pub mod prelude {
pub use super::{get_conv, Kcp, KCP_OVERHEAD}; pub use super::{KCP_OVERHEAD, Kcp, get_conv};
} }
pub use error::Error; pub use error::Error;
pub use kcp::{get_conv, get_sn, set_conv, Kcp, KCP_OVERHEAD}; pub use kcp::{KCP_OVERHEAD, Kcp, get_conv, get_sn, set_conv};
/// KCP result /// KCP result
pub type KcpResult<T> = Result<T, Error>; pub type KcpResult<T> = Result<T, Error>;
+15 -13
View File
@@ -1,21 +1,23 @@
{ {
"lineups": { "lineups": {
"0": 1403, "0": 1513,
"1": 1407, "1": 1308,
"2": 8001, "2": 1403,
"3": 1405 "3": 1409
}, },
"position": { "position": {
"x": -26968, "x": 674182,
"y": 78953, "y": 75562,
"z": 14457, "z": -16482,
"rot_y": 11858 "rot_y": 204639
}, },
"scene": { "scene": {
"plane_id": 20411, "plane_id": 20511,
"floor_id": 20411001, "floor_id": 20511001,
"entry_id": 2041101 "entry_id": 2051101
}, },
"main_character": "FemaleRememberance", "main_character": "FemaleElation",
"march_type": "MarchHunt" "march_type": "MarchHunt",
"enable_sw_global": true,
"enable_castorice_global": true
} }
+5841
View File
File diff suppressed because it is too large Load Diff
+12327 -58798
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -1,6 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::{quote, ToTokens}; use quote::{ToTokens, quote};
use syn::{parse_macro_input, DeriveInput, Meta, MetaList}; use syn::{DeriveInput, Meta, MetaList, parse_macro_input};
#[proc_macro_derive(CmdID, attributes(cmdid))] #[proc_macro_derive(CmdID, attributes(cmdid))]
pub fn message_id_derive(input: TokenStream) -> TokenStream { pub fn message_id_derive(input: TokenStream) -> TokenStream {
+1 -1
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
ignore = [
"proto/"
]
+13 -13
View File
@@ -4,29 +4,29 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
anyhow.workspace = true # Framework
env_logger.workspace = true tokio.workspace = true
tower-http = { workspace = true, features = ["cors"] } tower-http = { workspace = true, features = ["cors"] }
axum.workspace = true axum.workspace = true
axum-server.workspace = true axum-server.workspace = true
reqwest.workspace = true
lazy_static.workspace = true # JSON
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
tokio.workspace = true # Logging
tokio-util.workspace = true
tracing.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 ansi_term.workspace = true
env_logger.workspace = true
# Encoding / Serialization
prost.workspace = true prost.workspace = true
rbase64.workspace = true rbase64.workspace = true
proto.workspace = true
# Error handling
anyhow.workspace = true
# Local
common.workspace = true common.workspace = true
proto.workspace = true
+1 -3
View File
@@ -1,3 +1 @@
mod version_config; pub mod version_config;
pub use version_config::INSTANCE as versions;
+112 -14
View File
@@ -1,29 +1,127 @@
use std::collections::HashMap; use std::collections::HashMap;
use lazy_static::lazy_static; use anyhow::{Result, anyhow};
use serde::Deserialize; use prost::Message as _;
use serde_json::from_str; use proto::GateServer;
use serde::{Deserialize, Serialize};
use tokio::fs;
const DEFAULT_VERSIONS: &str = include_str!("../../versions.json"); const DEFAULT_VERSIONS: &str = include_str!("../../../versions.json");
#[derive(Deserialize)] #[derive(Serialize, Deserialize, Clone, Default)]
pub struct VersionConfig { pub struct VersionConfig {
pub asset_bundle_url: String, pub asset_bundle_url: String,
pub ex_resource_url: String, pub ex_resource_url: String,
pub lua_url: String, pub lua_url: String,
// pub lua_version: String, pub ifix_url: String,
} }
lazy_static! { impl VersionConfig {
pub static ref INSTANCE: HashMap<String, VersionConfig> = { const CNPROD_HOST: &str = "prod-gf-cn-dp01.bhsr.com";
let local_config = std::path::Path::new("versions.json"); const CNBETA_HOST: &str = "beta-release01-cn.bhsr.com";
let data = if local_config.exists() { const OSPROD_HOST: &str = "prod-official-asia-dp01.starrails.com";
std::fs::read_to_string("versions.json").unwrap() const OSBETA_HOST: &str = "beta-release01-asia.starrails.com";
} else {
std::fs::write("versions.json", DEFAULT_VERSIONS).unwrap(); const PROXY_HOST: &str = "proxy1.neonteam.dev"; // we used this because PS users usually have a proxy enabled
pub async fn load_hotfix() -> HashMap<String, VersionConfig> {
const CONFIG_PATH: &str = "versions.json";
let data = match fs::read_to_string(CONFIG_PATH).await {
Ok(data) => data,
Err(_) => {
fs::write(CONFIG_PATH, DEFAULT_VERSIONS)
.await
.expect("Failed to create versions file");
DEFAULT_VERSIONS.to_string() DEFAULT_VERSIONS.to_string()
}
}; };
from_str(&data).unwrap() match serde_json::from_str(&data) {
Ok(data) => data,
Err(_) => {
tracing::error!("malformed versions.json. replacing it with default one");
let _ = fs::write(CONFIG_PATH, DEFAULT_VERSIONS).await;
serde_json::from_str(DEFAULT_VERSIONS).unwrap()
}
}
}
pub async fn fetch_hotfix(version: &str, dispatch_seed: &str) -> Result<VersionConfig> {
let (region, branch, _, _) = Self::parse_version_string(version)?;
let host = match (region.as_str(), branch.as_str()) {
("OS", "BETA") => Self::OSBETA_HOST,
("OS", "PROD") => Self::OSPROD_HOST,
("CN", "BETA") => Self::CNBETA_HOST,
("CN", "PROD") => Self::CNPROD_HOST,
// TODO: Support more host, or use query_dispatch result to determine the urls
_ => Self::OSBETA_HOST,
}; };
let url = format!(
"https://{}/{host}/query_gateway?version={}&platform_type=1&language_type=3&dispatch_seed={}&channel_id=1&sub_channel_id=1&is_need_url=1",
Self::PROXY_HOST,
version,
dispatch_seed
);
tracing::info!("fetching hotfix: {url}");
let res = reqwest::get(url).await?.text().await?;
tracing::info!("raw gateway response: {}", res);
let bytes = rbase64::decode(&res)?;
let decoded = GateServer::decode(bytes.as_slice())?;
if decoded.asset_bundle_url.is_empty() && decoded.ex_resource_url.is_empty() {
return Err(anyhow::format_err!(
"asset_bundle_url and ex_resource_url are empty! gateway result code: {} message: {}",
decoded.retcode,
decoded.msg
));
}
tracing::info!("{:#?}", decoded);
Ok(VersionConfig {
asset_bundle_url: decoded.asset_bundle_url,
ex_resource_url: decoded.ex_resource_url,
lua_url: decoded.lua_url,
ifix_url: decoded.ifix_url,
})
}
fn parse_version_string(s: &str) -> Result<(String, String, String, String)> {
const BRANCHES: [&str; 7] = ["PREbeta", "BETA", "PROD", "DEV", "PRE", "GM", "CECREATION"];
const OS: [&str; 3] = ["Android", "Win", "iOS"];
let region = s
.get(0..2)
.ok_or_else(|| anyhow!("version parse failed. reason: invalid region url: {s}"))?;
let after_region = s.get(2..).unwrap_or(s);
let branch = BRANCHES
.iter()
.find(|&&b| after_region.starts_with(b))
.map(|&b| b.to_string())
.ok_or_else(|| anyhow!("version parse failed. reason: invalid branch url: {s}"))?;
let branch_len = branch.len();
let after_branch = after_region.get(branch_len..).unwrap_or(after_region);
let os = OS
.iter()
.find(|&&o| after_branch.starts_with(o))
.map(|&o| o.to_string())
.ok_or_else(|| anyhow!("version parse failed. reason: invalid os url: {s}"))?;
let os_len = os.len();
let version = after_branch
.get(os_len..)
.ok_or_else(|| anyhow!("version parse failed. reason: invalid version url: {s}"))?;
Ok((region.to_string(), branch, os, version.to_string()))
}
} }
+114
View File
@@ -0,0 +1,114 @@
use std::collections::HashMap;
use std::sync::Arc;
use anyhow::Result;
use axum::Router;
use axum::http::Method;
use axum::http::header::CONTENT_TYPE;
use axum::routing::{get, post};
use config::version_config::VersionConfig;
use services::{auth, dispatch, errors, sr_tools};
use tokio::fs;
use tokio::sync::RwLock;
use tower_http::cors::{Any, CorsLayer};
use tracing::Level;
mod config;
mod services;
const PORT: u16 = 21000;
#[derive(Clone)]
struct AppState {
hotfix_map: HashMap<String, VersionConfig>,
}
impl AppState {
async fn get_or_insert_hotfix(&mut self, version: &str, dispatch_seed: &str) -> &VersionConfig {
if self.hotfix_map.contains_key(version) {
return &self.hotfix_map[version];
}
tracing::info!(
"trying to fetch hotfix for version {version} with dispatch seed {dispatch_seed}"
);
let hotfix = match VersionConfig::fetch_hotfix(version, dispatch_seed).await {
Ok(hotfix) => hotfix,
Err(err) => {
tracing::error!("failed to fetch hotfix. reason: {err}");
VersionConfig::default()
}
};
self.hotfix_map.insert(version.to_string(), hotfix);
if let Ok(serialized) = serde_json::to_string_pretty(&self.hotfix_map) {
let _ = fs::write("versions.json", serialized).await;
}
&self.hotfix_map[version]
}
}
pub async fn start_sdkserver() -> Result<()> {
let span = tracing::span!(Level::DEBUG, "main");
let _ = span.enter();
let hotfix_map = VersionConfig::load_hotfix().await;
tracing::info!(
"loaded {} hotfix versions. supported versions: {:?}",
hotfix_map.len(),
hotfix_map.keys()
);
let state = Arc::new(RwLock::new(AppState { hotfix_map }));
let router = Router::new()
.route(
dispatch::QUERY_DISPATCH_ENDPOINT,
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::LOGIN_WITH_PASSWORD_ENDPOINT,
post(auth::login_with_password),
)
.route(
auth::LOGIN_WITH_SESSION_TOKEN_ENDPOINT,
post(auth::login_with_session_token),
)
.route(
auth::GRANTER_LOGIN_VERIFICATION_ENDPOINT,
post(auth::granter_login_verification),
)
.route(
sr_tools::SRTOOLS_UPLOAD_ENDPOINT,
post(sr_tools::sr_tool_save),
)
.route(
auth::APN_LOGIN_BY_PASSWORD_ENDPOINT,
post(auth::apn_login_with_password),
)
.route(auth::APN_VERIFY_TOKEN_ENDPOINT, post(auth::apn_veriy_token))
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST, Method::PATCH, Method::DELETE])
.allow_headers([CONTENT_TYPE]),
)
.with_state(state)
.fallback(errors::not_found);
let addr = format!("0.0.0.0:{PORT}");
let server = axum_server::bind(addr.parse::<std::net::SocketAddr>()?);
tracing::info!("sdkserver is listening at {addr}");
server.serve(router.into_make_service()).await?;
Ok(())
}
-29
View File
@@ -1,29 +0,0 @@
#[macro_export]
macro_rules! log_error {
($e:expr) => {
if let Err(e) = $e {
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
}
};
($context:expr, $e:expr $(,)?) => {
if let Err(e) = $e {
let e = format!("{:?}", ::anyhow::anyhow!(e).context($context));
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
}
};
($ok_context:expr, $err_context:expr, $e:expr $(,)?) => {
if let Err(e) = $e {
let e = format!("{:?}", ::anyhow::anyhow!(e).context($err_context));
tracing::error!(error.message = %format!("{}", &e), "{:?}", e);
} else {
tracing::info!($ok_context);
}
};
}
pub fn init_tracing() {
#[cfg(target_os = "windows")]
ansi_term::enable_ansi_support().unwrap();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
}
+6 -57
View File
@@ -1,65 +1,14 @@
use anyhow::Result; use anyhow::Result;
use axum::Router;
use axum::http::Method;
use axum::http::header::CONTENT_TYPE;
use axum::routing::{get, post};
use logging::init_tracing;
use services::{auth, dispatch, errors, sr_tools};
use tower_http::cors::{Any, CorsLayer};
use tracing::Level;
mod config; pub fn init_tracing() {
mod logging; #[cfg(target_os = "windows")]
mod services; ansi_term::enable_ansi_support().unwrap();
const PORT: u16 = 21000; env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
init_tracing(); init_tracing();
sdkserver::start_sdkserver().await
let span = tracing::span!(Level::DEBUG, "main");
let _ = span.enter();
let router = Router::new()
.route(
dispatch::QUERY_DISPATCH_ENDPOINT,
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::LOGIN_WITH_PASSWORD_ENDPOINT,
post(auth::login_with_password),
)
.route(
auth::LOGIN_WITH_SESSION_TOKEN_ENDPOINT,
post(auth::login_with_session_token),
)
.route(
auth::GRANTER_LOGIN_VERIFICATION_ENDPOINT,
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);
let addr = format!("0.0.0.0:{PORT}");
let server = axum_server::bind(addr.parse()?);
tracing::info!("sdkserver is listening at {addr}");
server.serve(router.into_make_service()).await?;
Ok(())
} }
+52 -1
View File
@@ -3,8 +3,11 @@ 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";
pub const APN_LOGIN_BY_PASSWORD_ENDPOINT: &str = "/account/ma-cn-passport/app/loginByPassword";
pub const APN_VERIFY_TOKEN_ENDPOINT: &str = "/account/ma-cn-session/app/verify";
#[tracing::instrument] #[tracing::instrument]
pub async fn login_with_password() -> Json<serde_json::Value> { pub async fn login_with_password() -> Json<serde_json::Value> {
@@ -78,3 +81,51 @@ pub async fn risky_api_check() -> Json<serde_json::Value> {
"retcode": 0 "retcode": 0
})) }))
} }
#[tracing::instrument]
pub async fn apn_login_with_password() -> Json<serde_json::Value> {
Json(json!({
"data": {
"token": {
"token": "mostsecuretokenever",
"token_type": 1
},
"user_info": {
"aid": "1337",
"mid": "1337",
"is_email_verify": 1,
"area_code": "**",
"country": "US",
"is_adult": 1,
"email": "motorized@wheel.chair"
}
},
"message": "OK",
"retcode": 0
}))
}
#[tracing::instrument]
pub async fn apn_veriy_token() -> Json<serde_json::Value> {
Json(json!({
"data": {
"tokens": [
{
"token": "mostsecuretokenever",
"token_type": 1
}
],
"user_info": {
"aid": "1337",
"mid": "1337",
"is_email_verify": 1,
"area_code": "**",
"country": "US",
"is_adult": 1,
"email": "motorized@wheel.chair"
}
},
"message": "OK",
"retcode": 0
}))
}
+30 -28
View File
@@ -1,20 +1,24 @@
use crate::config::versions; use std::sync::Arc;
use axum::extract::Query;
use crate::AppState;
use axum::extract::{Query, State};
use prost::Message; use prost::Message;
use proto::{DispatchRegionData, Gateserver, RegionEntry}; use proto::{Dispatch, GateServer, RegionInfo};
use serde::Deserialize; use serde::Deserialize;
use tokio::sync::RwLock;
use tracing::instrument;
pub const QUERY_DISPATCH_ENDPOINT: &str = "/query_dispatch"; pub const QUERY_DISPATCH_ENDPOINT: &str = "/query_dispatch";
pub const QUERY_GATEWAY_ENDPOINT: &str = "/query_gateway"; pub const QUERY_GATEWAY_ENDPOINT: &str = "/query_gateway";
#[tracing::instrument] #[tracing::instrument]
pub async fn query_dispatch() -> String { pub async fn query_dispatch() -> String {
let rsp = DispatchRegionData { let rsp = Dispatch {
retcode: 0, retcode: 0,
region_list: vec![RegionEntry { region_list: vec![RegionInfo {
name: String::from("RobinSR"), name: String::from("RobinSR"),
title: String::from("RobinSR"), title: String::from("RobinSR"),
env_type: String::from("21"), env_type: String::from("9"),
dispatch_url: String::from("http://127.0.0.1:21000/query_gateway"), dispatch_url: String::from("http://127.0.0.1:21000/query_gateway"),
..Default::default() ..Default::default()
}], }],
@@ -30,37 +34,35 @@ pub async fn query_dispatch() -> String {
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct QueryGatewayParameters { pub struct QueryGatewayParameters {
pub version: String, pub version: String,
pub dispatch_seed: String,
} }
#[tracing::instrument] #[instrument(skip(state))]
pub async fn query_gateway(parameters: Query<QueryGatewayParameters>) -> String { pub async fn query_gateway(
let rsp = if let Some(config) = versions.get(&parameters.version) { State(state): State<Arc<RwLock<AppState>>>,
Gateserver { parameters: Query<QueryGatewayParameters>,
retcode: 0, ) -> String {
let mut lock = state.write().await;
let config = lock
.get_or_insert_hotfix(&parameters.version, &parameters.dispatch_seed)
.await;
let rsp = GateServer {
ip: String::from("127.0.0.1"), ip: String::from("127.0.0.1"),
port: 23301, port: 23301,
asset_bundle_url: config.asset_bundle_url.clone(), asset_bundle_url: config.asset_bundle_url.clone(),
asset_bundle_url_android: config.asset_bundle_url.clone(),
ex_resource_url: config.ex_resource_url.clone(), ex_resource_url: config.ex_resource_url.clone(),
lua_url: config.lua_url.clone(), lua_url: config.lua_url.clone(),
ifix_version: String::from("0"), ifix_version: String::from("0"),
enable_design_data_version_update: true, unk1: true,
enable_version_update: true, unk2: true,
enable_upload_battle_log: true, unk3: true,
network_diagnostic: true, unk4: true,
close_redeem_code: true, unk5: true,
enable_android_middle_package: true, unk6: true,
enable_watermark: true, unk7: true,
event_tracking_open: true,
enable_cdn_ipv6: 1,
enable_save_replay_file: true,
..Default::default() ..Default::default()
}
} else {
Gateserver {
retcode: 9,
login_white_msg: format!("forbidden version: {} or invalid bind", parameters.version),
..Default::default()
}
}; };
let mut buff = Vec::new(); let mut buff = Vec::new();
+12 -5
View File
@@ -1,3 +1,5 @@
use std::path::Path;
use axum::Json; use axum::Json;
use common::sr_tools::FreesrData; use common::sr_tools::FreesrData;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -29,16 +31,21 @@ pub async fn sr_tool_save(Json(json): Json<SrToolDataReq>) -> Json<SrToolDataRsp
Ok(json) => json, Ok(json) => json,
Err(err) => { Err(err) => {
return Json(SrToolDataRsp { return Json(SrToolDataRsp {
message: format!("malformed json: {}", err), message: format!("malformed json: {err}"),
status: 200, status: 500,
}); });
} }
}; };
if let Err(err) = fs::write("freesr-data.json", json).await { let path = Path::new("freesr-data.json");
let env = std::env::current_dir();
if let Err(err) = fs::write(&path, json).await {
return Json(SrToolDataRsp { return Json(SrToolDataRsp {
message: format!("failed to write freesr-data.json: {}", err), message: format!(
status: 200, "failed to write freesr-data.json: {err} at path: {path:#?} env: {env:#?}"
),
status: 500,
}); });
}; };
-8
View File
@@ -1,8 +0,0 @@
{
"OSBETAWin3.1.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253"
}
}
+53 -5
View File
@@ -1,8 +1,56 @@
{ {
"OSBETAWin3.1.51": { "CNBETAWin3.5.55": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_9573347_b03981f01966", "asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_11797411_74704086c081_d40a71b41ba492",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_9589567_9c50629b0369", "ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_11823033_c9e8c870bcc7_b78f37c36487d0",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_9567078_0e2b6acf6a2f", "lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_11798340_8c6543a1d6c5_2d60cd9b1584b8",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253" "ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"CNBETAWin3.6.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12066992_f083970b907e_999074cab6dce6",
"ex_resource_url": "https://autopatchcn-bhsr.neonteam.dev/design_data/BetaLive/output_12114942_e99cbde25134_e63a6b835f17f9",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12103115_ee78155e9867_3626f0948d93e2",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12118783_55113408814f_c874267d04c04a"
},
"CNBETAWin3.6.52": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12150127_00d6d096d968_cd76a04beb7ba6",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12178965_e246796e0bb6_05bcce36cd648b",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12150614_5279f6d8029a_bdecff99d2d817",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_12164593_8e3fba5163df_b2b6fc46de4c06"
},
"CNBETAWin3.7.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_12579793_48327ff319b5_1b794dd1071e3a",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_12611332_5f583f2f54ae_c04979f13c950d",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_12579929_9566349ee5fb_c6341faaf9b027",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_0_40d2ce0253_c61ba99f70b885"
},
"OSBETAWin3.8.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_13490885_bf25e1553b38_e9adc23a6b6710",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_13532881_eb1de9bedabd_c74ab2513059ad",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_13493894_ec7d6d879305_8b2cfd39d6e545",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_13491130_ecfa51828143_4bd221b76c0d9a"
},
"OSBETAWin4.0.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_14026982_55a517a26691_89300298bf2b2e",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_14036546_28a134460a9a_5805431f75f0ff",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_14028456_7088b08fd431_59194837d90977",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
},
"CNBETAWin4.1.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_14372365_fa78e03ad599_36972b4e3bb553",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_14372319_01febed213ae_b1f343e62353c2",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_14358961_e055a63b3c34_b199a0234548b5",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_14374543_04d971175704_ab00cb3f54bb00"
},
"CNBETAWin4.2.51": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_14851990_9aa8ee8de0fa_ec7d1e42f851f1",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_14890490_fcc969a5461f_1f9258ee9068a5",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_14852119_8789f55c1c30_0c0cf5d3c9089b",
"ifix_url": "https://autopatchcn.bhsr.com/ifix/BetaLive/output_14887825_65010135064b_71536d98b8abbf"
},
"OSBETAWin4.4.51": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_15711835_1986f76c287e_9c3d058458edf0",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_15753142_3bb8238470fc_a7cec92c089da7",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_15712109_65eeab393c89_0c116c2699518d",
"ifix_url": "https://autopatchos.starrails.com/ifix/BetaLive/output_0_40d2ce0253_6d871f8bca6eb4"
} }
} }