First push

This commit is contained in:
xeon 2024-03-19 08:30:08 +03:00
parent 92a206ba33
commit 6c89b18e7a
60 changed files with 9843 additions and 0 deletions

18
.gitignore vendored Normal file
View file

@ -0,0 +1,18 @@
# ---> Rust
# Generated by Cargo
# will have compiled files and executables
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# Visual Studio crap
.vs/

52
Cargo.toml Normal file
View file

@ -0,0 +1,52 @@
[workspace]
members = ["gameserver", "protocol", "qwer", "qwer/qwer-derive", "sdkserver"]
resolver = "2"
[workspace.package]
version = "0.1.0"
[workspace.dependencies]
ansi_term = "0.12.1"
anyhow = "1.0.81"
atomic_refcell = "0.1.13"
axum = "0.7.4"
axum-server = "0.6.0"
byteorder = "1.5.0"
dirs = "5.0.1"
dotenv = "0.15.0"
encoding = "0.2.33"
env_logger = "0.11.3"
heck = "0.5.0"
hex = "0.4.3"
lazy_static = "1.4.0"
leb128 = "0.2.5"
paste = "1.0.14"
sysinfo = "0.30.7"
serde = "1.0.197"
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"
protocol = { version = "0.1.0", path = "protocol" }
qwer = { version = "0.1.0", path = "qwer", features = ["full"] }
qwer-derive = { version = "0.1.0", path = "qwer/qwer-derive" }
[profile.release]
strip = true # Automatically strip symbols from the binary.
lto = true # Link-time optimization.
opt-level = 3 # Optimize for speed.
codegen-units = 1 # Maximum size reduction optimizations.

1
gameserver/.env Normal file
View file

@ -0,0 +1 @@
SKIP_TUTORIAL=0

35
gameserver/Cargo.toml Normal file
View file

@ -0,0 +1,35 @@
[package]
name = "gameserver"
edition = "2021"
version.workspace = true
[dependencies]
ansi_term.workspace = true
anyhow.workspace = true
atomic_refcell.workspace = true
dirs.workspace = true
dotenv.workspace = true
env_logger.workspace = true
hex.workspace = true
lazy_static.workspace = true
paste.workspace = true
sysinfo.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
tokio-util.workspace = true
tracing.workspace = true
tracing-futures.workspace = true
tracing-log.workspace = true
tracing-subscriber.workspace = true
tracing-bunyan-formatter.workspace = true
protocol.workspace = true
qwer.workspace = true
[[bin]]
name = "nap-gameserver"
path = "src/main.rs"

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,97 @@
use std::sync::Arc;
use anyhow::Result;
use atomic_refcell::AtomicRefCell;
use protocol::{PlayerInfo, PtcPlayerInfoChangedArg};
use crate::net::NetworkSession;
use super::manager::{
DungeonManager, HollowGridManager, ItemManager, QuestManager, SceneUnitManager,
UniqueIDManager, UnlockManager, YorozuyaQuestManager,
};
pub struct GameContext {
pub player: Arc<AtomicRefCell<PlayerInfo>>,
pub uid_manager: Arc<AtomicRefCell<UniqueIDManager>>,
pub item_manager: Arc<AtomicRefCell<ItemManager>>,
pub dungeon_manager: Arc<AtomicRefCell<DungeonManager>>,
pub quest_manager: Arc<AtomicRefCell<QuestManager>>,
pub scene_unit_manager: Arc<AtomicRefCell<SceneUnitManager>>,
pub hollow_grid_manager: Arc<AtomicRefCell<HollowGridManager>>,
pub unlock_manager: Arc<AtomicRefCell<UnlockManager>>,
pub yorozuya_quest_manager: Arc<AtomicRefCell<YorozuyaQuestManager>>,
}
impl GameContext {
pub fn new(player: Arc<AtomicRefCell<PlayerInfo>>) -> Self {
let uid_manager = Arc::new(AtomicRefCell::new(UniqueIDManager::new()));
Self {
player: player.clone(),
uid_manager: uid_manager.clone(),
item_manager: Arc::new(AtomicRefCell::new(ItemManager::new(
uid_manager.clone(),
player.clone(),
))),
dungeon_manager: Arc::new(AtomicRefCell::new(DungeonManager::new(
uid_manager.clone(),
player.clone(),
))),
quest_manager: Arc::new(AtomicRefCell::new(QuestManager::new(
uid_manager.clone(),
player.clone(),
))),
scene_unit_manager: Arc::new(AtomicRefCell::new(SceneUnitManager::new(uid_manager))),
hollow_grid_manager: Arc::new(AtomicRefCell::new(HollowGridManager::new(
player.clone(),
))),
unlock_manager: Arc::new(AtomicRefCell::new(UnlockManager::new(player.clone()))),
yorozuya_quest_manager: Arc::new(AtomicRefCell::new(YorozuyaQuestManager::new(player))),
}
}
}
pub struct PlayerOperationResult<T>
where
T: Send + Sync,
{
result: T,
player_info_changes: Option<PlayerInfo>,
}
impl<T> PlayerOperationResult<T>
where
T: Send + Sync,
{
pub const fn unwrap(&self) -> &T {
&self.result
}
pub async fn send_changes(&mut self, session: &NetworkSession) -> Result<&T> {
if self.player_info_changes.is_some() {
let ptc_player_info_changed = PtcPlayerInfoChangedArg {
player_uid: session.get_player_uid(),
player_info: self.player_info_changes.take().unwrap(),
};
session.send_rpc_arg(101, &ptc_player_info_changed).await?;
}
Ok(self.unwrap())
}
pub const fn ret(result: T) -> Self {
Self {
result,
player_info_changes: None,
}
}
pub const fn with_changes(result: T, player_info_changes: PlayerInfo) -> Self {
Self {
result,
player_info_changes: Some(player_info_changes),
}
}
}

View file

@ -0,0 +1,18 @@
use lazy_static::lazy_static;
use serde_json::{Map, Value};
pub const EVENT_GRAPH_COLLECTION: &str = include_str!("../../EventGraphCollection.json");
lazy_static! {
static ref EVENT_MAP: Map<String, Value> = {
serde_json::from_str::<Value>(EVENT_GRAPH_COLLECTION)
.unwrap()
.as_object()
.unwrap()
.clone()
};
}
pub fn get_event_config_json(id: i32) -> &'static Value {
EVENT_MAP.get(&id.to_string()).unwrap()
}

View file

@ -0,0 +1,11 @@
use std::env;
use lazy_static::lazy_static;
lazy_static! {
static ref SKIP_TUTORIAL: i32 = env::var("SKIP_TUTORIAL").map_or(0, |v| v.parse().unwrap());
}
pub fn should_skip_tutorial() -> bool {
*SKIP_TUTORIAL != 0
}

View file

@ -0,0 +1,780 @@
use anyhow::{anyhow, bail, Result};
use atomic_refcell::AtomicRefCell;
use protocol::*;
use std::sync::Arc;
use crate::game::{manager::UniqueIDManager, util, PlayerOperationResult};
use qwer::{
pdkhashmap, phashmap, phashset, PropertyDoubleKeyHashMap, PropertyHashMap, PropertyHashSet,
};
pub struct DungeonManager {
uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>,
player: Arc<AtomicRefCell<PlayerInfo>>,
scene_properties: AtomicRefCell<PropertyDoubleKeyHashMap<u64, u16, i32>>,
}
impl DungeonManager {
pub fn new(
uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>,
player: Arc<AtomicRefCell<PlayerInfo>>,
) -> Self {
Self {
uid_mgr,
player,
scene_properties: AtomicRefCell::new(pdkhashmap![]),
}
}
pub fn enter_main_city(&self) -> Result<PlayerOperationResult<PtcEnterSceneArg>> {
let (player_uid, scene_position, scene_rotation) = {
let player = self.player.borrow();
let pos_in_main_city = player.pos_in_main_city.as_ref().unwrap();
(
player.uid.unwrap(),
*pos_in_main_city.position.as_ref().unwrap(),
*pos_in_main_city.rotation.as_ref().unwrap(),
)
};
let mut player = self.player.borrow_mut();
let default_scene_uid = player
.dungeon_collection
.as_ref()
.unwrap()
.default_scene_uid
.unwrap();
player.scene_uid.replace(default_scene_uid);
let dungeon_collection = player.dungeon_collection.as_mut().unwrap();
let scene_info = dungeon_collection
.scenes
.as_mut()
.unwrap()
.get_mut(&default_scene_uid)
.ok_or_else(|| anyhow!("Scene with uid {default_scene_uid} doesn't exist"))?;
let dungeon_uid = scene_info.get_dungeon_uid();
let dungeon_info = dungeon_collection
.dungeons
.as_mut()
.unwrap()
.get_mut(&scene_info.get_dungeon_uid())
.ok_or_else(|| anyhow!("Dungeon with uid {dungeon_uid} doesn't exist"))?;
scene_info.set_entered_times(scene_info.get_entered_times() + 1);
dungeon_info.entered_times += 1;
let ptc_enter_scene = PtcEnterSceneArg {
player_uid,
scene_uid: default_scene_uid,
section_id: scene_info.get_section_id(),
open_ui: UIType::Default,
condition_config_ids: Vec::new(),
transform: Transform {
position: scene_position,
rotation: scene_rotation,
},
timestamp: util::cur_timestamp_ms(),
camera_x: 0,
camera_y: 6000,
entered_times: scene_info.get_entered_times(),
ext: match scene_info {
SceneInfo::Hall { .. } => SceneTableExt::Hall {
event_graphs_info: EventGraphsInfo {
default_event_graph_id: 0,
event_graphs_info: phashmap![],
},
},
_ => bail!("Unexpected main city scene type"),
},
};
Ok(PlayerOperationResult::with_changes(
ptc_enter_scene,
PlayerInfo {
dungeon_collection: Some(DungeonCollection {
dungeons: Some(PropertyHashMap::Modify {
to_add: vec![(dungeon_info.uid, dungeon_info.clone())],
to_remove: Vec::new(),
}),
scenes: Some(PropertyHashMap::Modify {
to_add: vec![(scene_info.get_uid(), scene_info.clone())],
to_remove: Vec::new(),
}),
..Default::default()
}),
scene_uid: Some(scene_info.get_uid()),
..Default::default()
},
))
}
pub fn enter_scene_section(
&self,
scene_uid: u64,
section_id: i32,
) -> PlayerOperationResult<PtcEnterSectionArg> {
let mut player = self.player.borrow_mut();
let scene_info = player
.dungeon_collection
.as_mut()
.unwrap()
.scenes
.as_mut()
.unwrap()
.get_mut(&scene_uid)
.unwrap();
scene_info.set_section_id(section_id);
PlayerOperationResult::with_changes(
PtcEnterSectionArg { section_id },
PlayerInfo {
dungeon_collection: Some(DungeonCollection {
scenes: Some(PropertyHashMap::Modify {
to_add: vec![(scene_uid, scene_info.clone())],
to_remove: Vec::new(),
}),
..Default::default()
}),
..Default::default()
},
)
}
pub fn enter_scene(&self, scene_uid: u64) -> Result<PlayerOperationResult<PtcEnterSceneArg>> {
let (player_uid, prev_scene_uid) = {
let player = self.player.borrow();
(
*player.uid.as_ref().unwrap(),
*player.scene_uid.as_ref().unwrap(),
)
};
let mut player = self.player.borrow_mut();
player.scene_uid.replace(scene_uid);
player.prev_scene_uid.replace(prev_scene_uid);
let dungeon_collection = player.dungeon_collection.as_mut().unwrap();
let scene_info = dungeon_collection
.scenes
.as_mut()
.unwrap()
.get_mut(&scene_uid)
.ok_or_else(|| anyhow!("Scene with uid {scene_uid} doesn't exist"))?;
let dungeon_uid = scene_info.get_dungeon_uid();
let dungeon_info = dungeon_collection
.dungeons
.as_mut()
.unwrap()
.get_mut(&scene_info.get_dungeon_uid())
.ok_or_else(|| anyhow!("Dungeon with uid {dungeon_uid} doesn't exist"))?;
scene_info.set_entered_times(scene_info.get_entered_times() + 1);
dungeon_info.entered_times += 1;
if let SceneInfo::Hollow { sections_info, .. } = scene_info {
let section = sections_info.get_mut(&1).unwrap();
section.entered_times += 1;
}
let ptc_enter_scene = PtcEnterSceneArg {
player_uid,
scene_uid,
section_id: scene_info.get_section_id(),
open_ui: UIType::Default,
condition_config_ids: Vec::new(),
transform: Transform::default(),
timestamp: util::cur_timestamp_ms(),
camera_x: 0,
camera_y: 6000,
entered_times: scene_info.get_entered_times(),
ext: match scene_info {
SceneInfo::Hall { .. } => SceneTableExt::Hall {
event_graphs_info: EventGraphsInfo {
default_event_graph_id: 0,
event_graphs_info: phashmap![],
},
},
SceneInfo::Fresh { .. } => SceneTableExt::Fresh {
event_graphs_info: EventGraphsInfo {
default_event_graph_id: 0,
event_graphs_info: phashmap![],
},
},
SceneInfo::Hollow { .. } => SceneTableExt::Hollow {
event_graphs_info: EventGraphsInfo {
default_event_graph_id: 0,
event_graphs_info: phashmap![],
},
grid_random_seed: 0,
alter_section_id: 0,
},
SceneInfo::Fight { .. } => SceneTableExt::Fight {
event_graphs_info: EventGraphsInfo {
default_event_graph_id: 0,
event_graphs_info: phashmap![],
},
},
},
};
Ok(PlayerOperationResult::with_changes(
ptc_enter_scene,
PlayerInfo {
dungeon_collection: Some(DungeonCollection {
dungeons: Some(PropertyHashMap::Modify {
to_add: vec![(dungeon_info.uid, dungeon_info.clone())],
to_remove: Vec::new(),
}),
scenes: Some(PropertyHashMap::Modify {
to_add: vec![(scene_info.get_uid(), scene_info.clone())],
to_remove: Vec::new(),
}),
..Default::default()
}),
scene_uid: Some(scene_uid),
prev_scene_uid: Some(prev_scene_uid),
..Default::default()
},
))
}
pub fn get_default_scene_uid(&self) -> u64 {
self.player
.borrow()
.dungeon_collection
.as_ref()
.unwrap()
.default_scene_uid
.unwrap()
}
#[allow(dead_code)]
pub fn get_default_scene_uid_for_dungeon(&self, dungeon_uid: u64) -> u64 {
self.player
.borrow()
.dungeon_collection
.as_ref()
.unwrap()
.dungeons
.as_ref()
.unwrap()
.get(&dungeon_uid)
.unwrap()
.default_scene_uid
}
pub fn get_cur_scene_uid(&self) -> u64 {
self.player.borrow().scene_uid.unwrap()
}
fn add_default_hollow_properties(&self, scene_uid: u64) {
let mut props = self.scene_properties.borrow_mut();
for (sub_key, value) in &[
(1001, 0),
(1002, 100),
(1003, 0),
(1004, 0),
(1019, 10),
(1020, 1),
(1005, 0),
(1006, 0),
(1007, 0),
(1008, 0),
(1009, 0),
(1010, 0),
(1011, 0),
(1012, 0),
(1013, 1),
(1014, 1),
(1015, 0),
(1016, 0),
(1017, 4),
(1018, 10000),
(1021, 1),
(1025, 1),
(1035, 10000),
(1041, 10000),
(1042, 10000),
(1043, 1),
(1044, 1),
] {
props.insert(scene_uid, *sub_key, *value);
}
}
pub fn leave_battle(&self) -> Result<PlayerOperationResult<PtcEnterSceneArg>> {
let back_scene_uid = self.get_back_scene_uid();
{
let mut player = self.player.borrow_mut();
let hollow_scene = player
.dungeon_collection
.as_mut()
.unwrap()
.scenes
.as_mut()
.unwrap()
.get_mut(&back_scene_uid)
.unwrap();
if let SceneInfo::Hollow {
battle_scene_uid, ..
} = hollow_scene
{
*battle_scene_uid = 0;
}
}
self.enter_scene(back_scene_uid)
}
fn get_back_scene_uid(&self) -> u64 {
let player = self.player.borrow();
let fight_scene_uid = player.scene_uid.as_ref().unwrap();
let fight_scene = player
.dungeon_collection
.as_ref()
.unwrap()
.scenes
.as_ref()
.unwrap()
.get(fight_scene_uid)
.unwrap();
fight_scene.get_back_scene_uid()
}
pub fn enter_battle(&self, scene_uid: u64) -> PlayerOperationResult<PtcEnterSceneArg> {
let hollow_scene_uid = *self.player.borrow().scene_uid.as_ref().unwrap();
let hollow_scene = self.set_cur_hollow_battle(scene_uid, hollow_scene_uid);
let ptc_enter_scene = self.enter_scene(scene_uid).unwrap().unwrap().clone();
let player = self.player.borrow();
let dungeon_collection = player.dungeon_collection.as_ref().unwrap();
let fight_scene = dungeon_collection
.scenes
.as_ref()
.unwrap()
.get(&scene_uid)
.unwrap();
PlayerOperationResult::with_changes(
ptc_enter_scene,
PlayerInfo {
dungeon_collection: Some(DungeonCollection {
scenes: Some(PropertyHashMap::Modify {
to_add: vec![
(hollow_scene_uid, hollow_scene),
(scene_uid, fight_scene.clone()),
],
to_remove: Vec::new(),
}),
..Default::default()
}),
scene_uid: Some(scene_uid),
prev_scene_uid: Some(hollow_scene_uid),
..Default::default()
},
)
}
fn set_cur_hollow_battle(&self, scene_uid: u64, hollow_scene_uid: u64) -> SceneInfo {
let mut player = self.player.borrow_mut();
let hollow_scene = player
.dungeon_collection
.as_mut()
.unwrap()
.scenes
.as_mut()
.unwrap()
.get_mut(&hollow_scene_uid)
.unwrap();
let SceneInfo::Hollow {
on_battle_success,
battle_scene_uid,
..
} = hollow_scene
else {
panic!("Unexpected scene type")
};
*battle_scene_uid = scene_uid;
*on_battle_success = String::from("OnEnd");
hollow_scene.clone()
}
pub fn create_fight(&self, id: i32, hollow_scene_uid: u64) -> PlayerOperationResult<u64> {
let mut player = self.player.borrow_mut();
let dungeon_collection = player.dungeon_collection.as_mut().unwrap();
let scenes = dungeon_collection.scenes.as_mut().unwrap();
let hollow_scene = scenes.get_mut(&hollow_scene_uid).unwrap();
let fight_scene_uid = self.uid_mgr.borrow().next();
let fight_scene = SceneInfo::Fight {
uid: fight_scene_uid,
id,
dungeon_uid: hollow_scene.get_dungeon_uid(),
end_timestamp: 0,
back_scene_uid: hollow_scene_uid,
entered_times: 1,
section_id: 1,
open_ui: UIType::Default,
to_be_destroyed: false,
camera_x: 0xFFFFFFFF,
camera_y: 0xFFFFFFFF,
perform_show_progress: phashmap![],
end_hollow: false,
random_seed: 2281337,
};
scenes.insert(fight_scene_uid, fight_scene.clone());
PlayerOperationResult::with_changes(
fight_scene_uid,
PlayerInfo {
dungeon_collection: Some(DungeonCollection {
scenes: Some(PropertyHashMap::Modify {
to_add: vec![(fight_scene_uid, fight_scene)],
to_remove: Vec::new(),
}),
..Default::default()
}),
..Default::default()
},
)
}
pub fn is_in_tutorial(&self) -> bool {
let cur_scene_uid = self.get_cur_scene_uid();
let player = self.player.borrow();
let cur_scene = player
.dungeon_collection
.as_ref()
.unwrap()
.scenes
.as_ref()
.unwrap()
.get(&cur_scene_uid)
.unwrap();
matches!(cur_scene, SceneInfo::Fresh { .. })
}
pub fn create_hollow(
&self,
id: i32,
world_quest_id: i32,
avatar_uids: &[u64],
) -> PlayerOperationResult<(u64, u64)> {
let back_scene_uid = self.get_default_scene_uid();
let mut dungeon = self.create_base_dungeon(id, back_scene_uid, world_quest_id);
dungeon.hollow_event_version = 526;
let scene_uid = self.uid_mgr.borrow().next();
dungeon.default_scene_uid = scene_uid;
dungeon.scene_properties_uid = scene_uid;
self.add_default_hollow_properties(scene_uid);
for (index, avatar_uid) in avatar_uids.iter().enumerate() {
dungeon.avatar_map.insert(
index.try_into().unwrap(),
AvatarUnitInfo {
uid: *avatar_uid,
properties_uid: self.uid_mgr.borrow().next(),
is_banned: false,
modified_property: pdkhashmap![],
hp_add_hollow: 0,
hp_lost_hollow: 0,
layer_property_change: phashmap![],
},
);
}
let scene = SceneInfo::Hollow {
uid: scene_uid,
id,
dungeon_uid: dungeon.uid,
end_timestamp: 0,
back_scene_uid,
entered_times: 1,
section_id: 1,
open_ui: UIType::Default,
to_be_destroyed: false,
camera_x: 0xFFFFFFFF,
camera_y: 0xFFFFFFFF,
event_variables: phashmap![],
buddy: BuddyUnitInfo {
uid: 0,
properties: 0,
},
stress_punish_ability_random_pool: vec![String::from(
"Stress_Punish_RandomDebuff_Normal",
)],
finished: false,
event_weight_factor: phashmap![],
shop_modification: HollowShopModification {
ability_modified_num: pdkhashmap![],
action_modified_num: phashmap![],
overwrite_price: phashmap![],
},
last_challenge_stat: phashmap![],
cur_challenge: phashset![],
hollow_system_switch: phashmap![],
sections_info: phashmap![(
1,
PlayerHollowSectionInfo {
prev_grid_index: 0,
cur_grid_index: 22,
entered_times: 0,
global_event: 0,
perform_event_graph: 3405096459205834,
pos_before_move: 0,
}
)],
executing_event: true,
event_id: 1000,
hollow_event_graph_uid: 22,
on_battle_success: String::new(),
on_battle_failure: String::new(),
battle_finished: false,
battle_success: false,
battle_scene_uid: 0,
scene_global_events: phashmap![],
prepare_section: PrepareSection {
section_id: 0,
initial_pos: 0,
show_other: false,
battle_end_goto_next_hollow: false,
},
abilities_info: AbilitiesInfo {
abilities: phashmap![],
sequence_no: 0,
},
blackout: false,
hollow_system_ui_state: phashmap![],
};
{
let mut player = self.player.borrow_mut();
player
.scene_properties
.replace(self.scene_properties.borrow().clone());
let dungeon_collection = player.dungeon_collection.as_mut().unwrap();
dungeon_collection
.dungeons
.as_mut()
.unwrap()
.insert(dungeon.uid, dungeon.clone());
dungeon_collection
.scenes
.as_mut()
.unwrap()
.insert(scene_uid, scene.clone());
}
let mut player = self.player.borrow_mut();
let items = player.items.as_mut().unwrap();
let mut updated_items = Vec::new();
for avatar_uid in avatar_uids {
let item = items.get_mut(avatar_uid).unwrap();
let ItemInfo::Avatar { robot_id, .. } = item else {
continue;
};
*robot_id = 101000101;
updated_items.push((*avatar_uid, item.clone()));
}
let mut prop_changes = Vec::new();
for (key, sub_key, value) in &*self.scene_properties.borrow_mut() {
prop_changes.push((*key, *sub_key, *value));
}
PlayerOperationResult::with_changes(
(dungeon.uid, scene_uid),
PlayerInfo {
items: Some(PropertyHashMap::Modify {
to_add: updated_items,
to_remove: vec![],
}),
dungeon_collection: Some(DungeonCollection {
dungeons: Some(PropertyHashMap::Modify {
to_add: vec![(dungeon.uid, dungeon)],
to_remove: Vec::new(),
}),
scenes: Some(PropertyHashMap::Modify {
to_add: vec![(scene_uid, scene)],
to_remove: Vec::new(),
}),
..Default::default()
}),
scene_properties: Some(PropertyDoubleKeyHashMap::Modify {
to_add: prop_changes,
to_remove: Vec::new(),
}),
..Default::default()
},
)
}
pub fn create_hall(&self, id: i32) -> PlayerOperationResult<u64> {
let mut dungeon = self.create_base_dungeon(id, 0, 0);
let dungeon_uid = dungeon.uid;
let scene_uid = self.uid_mgr.borrow().next();
let hall_scene_info = SceneInfo::Hall {
uid: scene_uid,
id,
dungeon_uid,
end_timestamp: 0,
back_scene_uid: 0,
entered_times: 1,
section_id: 1,
open_ui: UIType::Default,
to_be_destroyed: true,
camera_x: 0xFFFFFFFF,
camera_y: 0xFFFFFFFF,
};
dungeon.default_scene_uid = scene_uid;
let mut player = self.player.borrow_mut();
let dungeon_collection = player.dungeon_collection.as_mut().unwrap();
dungeon_collection
.dungeons
.as_mut()
.unwrap()
.insert(dungeon_uid, dungeon.clone());
dungeon_collection
.scenes
.as_mut()
.unwrap()
.insert(scene_uid, hall_scene_info.clone());
dungeon_collection.default_scene_uid.replace(scene_uid);
PlayerOperationResult::with_changes(
scene_uid,
PlayerInfo {
dungeon_collection: Some(DungeonCollection {
dungeons: Some(PropertyHashMap::Modify {
to_add: vec![(dungeon_uid, dungeon)],
to_remove: vec![],
}),
scenes: Some(PropertyHashMap::Modify {
to_add: vec![(scene_uid, hall_scene_info)],
to_remove: vec![],
}),
default_scene_uid: Some(scene_uid),
..Default::default()
}),
..Default::default()
},
)
}
pub fn create_fresh(&self) -> PlayerOperationResult<u64> {
let mut dungeon = self.create_base_dungeon(2, 0, 0);
let dungeon_uid = dungeon.uid;
let scene_uid = self.uid_mgr.borrow().next();
let fresh_scene_info = SceneInfo::Fresh {
uid: scene_uid,
id: 2,
dungeon_uid,
end_timestamp: 0,
back_scene_uid: 0,
entered_times: 1,
section_id: 1,
open_ui: UIType::Default,
to_be_destroyed: true,
camera_x: 0xFFFFFFFF,
camera_y: 0xFFFFFFFF,
};
dungeon.default_scene_uid = scene_uid;
let mut player = self.player.borrow_mut();
let dungeon_collection = player.dungeon_collection.as_mut().unwrap();
dungeon_collection
.dungeons
.as_mut()
.unwrap()
.insert(dungeon_uid, dungeon.clone());
dungeon_collection
.scenes
.as_mut()
.unwrap()
.insert(scene_uid, fresh_scene_info.clone());
PlayerOperationResult::with_changes(
scene_uid,
PlayerInfo {
dungeon_collection: Some(DungeonCollection {
dungeons: Some(PropertyHashMap::Modify {
to_add: vec![(dungeon_uid, dungeon)],
to_remove: vec![],
}),
scenes: Some(PropertyHashMap::Modify {
to_add: vec![(scene_uid, fresh_scene_info)],
to_remove: vec![],
}),
..Default::default()
}),
..Default::default()
},
)
}
fn create_base_dungeon(
&self,
id: i32,
back_scene_uid: u64,
world_quest_id: i32,
) -> DungeonInfo {
let player = self.player.borrow();
let uid = self.uid_mgr.borrow().next();
DungeonInfo {
uid,
id,
default_scene_uid: 0,
start_timestamp: util::cur_timestamp_ms(),
to_be_destroyed: false,
back_scene_uid,
quest_collection_uid: uid,
avatars: phashmap![],
buddy: BuddyUnitInfo {
uid: 0,
properties: 0,
},
world_quest_id,
scene_properties_uid: 0,
drop_poll_chg_infos: phashmap![],
is_in_dungeon: false,
initiative_item: 0,
initiative_item_used_times: 0,
avatar_map: phashmap![],
battle_report: Vec::new(),
dungeon_group_uid: player.uid.unwrap(),
entered_times: 0,
is_preset_avatar: false,
hollow_event_version: 0,
}
}
}

View file

@ -0,0 +1,809 @@
use std::{
collections::{hash_map::Entry, HashMap},
sync::Arc,
};
use crate::game::data;
use atomic_refcell::AtomicRefCell;
use protocol::*;
use qwer::{phashmap, phashset, PropertyHashMap, PropertyHashSet};
pub struct HollowGridManager {
player: Arc<AtomicRefCell<PlayerInfo>>,
map: AtomicRefCell<Option<HollowGridMapProtocolInfo>>,
events: AtomicRefCell<HashMap<u64, EventInfo>>,
}
impl HollowGridManager {
pub fn new(player: Arc<AtomicRefCell<PlayerInfo>>) -> Self {
Self {
player,
map: AtomicRefCell::new(None),
events: AtomicRefCell::new(HashMap::new()),
}
}
pub fn get_cur_position_in_hollow(&self) -> u16 {
self.map.borrow().as_ref().unwrap().start_grid
}
pub fn move_to(
&self,
destination_grid: u16,
scene_uid: u64,
) -> (PtcHollowGridArg, Option<PtcSyncHollowEventInfoArg>) {
let mut map = self.map.borrow_mut();
let map = map.as_mut().unwrap();
map.start_grid = destination_grid;
let grid = map.grids.get_mut(&destination_grid).unwrap();
self.update_position_to_scene(scene_uid, destination_grid);
let mut events = self.events.borrow_mut();
let sync_event_info =
if let Entry::Vacant(entry) = events.entry(u64::from(destination_grid)) {
let event_info = EventInfo {
id: 1000,
cur_action_id: 1001,
action_move_path: vec![1001],
state: EventState::WaitingClient,
prev_state: EventState::Running,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
};
entry.insert(event_info.clone());
Some(PtcSyncHollowEventInfoArg {
event_graph_uid: u64::from(destination_grid),
hollow_event_template_id: grid.grid.event_graph_info.hollow_event_template_id,
event_graph_id: grid.grid.event_graph_info.hollow_event_template_id,
updated_event: event_info,
specials: phashmap![],
})
} else {
None
};
if !grid.grid.event_graph_info.finished {
grid.grid.flag |= HollowGridFlag::Travelled as i32;
grid.grid.flag |= HollowGridFlag::ShowEventID as i32;
grid.grid.flag &= !(HollowGridFlag::Guide as i32);
grid.grid.flag &= !(HollowGridFlag::CanTriggerEvent as i32);
grid.grid.flag &= !(HollowGridFlag::ShowEventType as i32);
grid.grid.event_graph_info.finished = true;
grid.grid.event_graph_info.fired_count = 2;
}
(
PtcHollowGridArg {
player_uid: self.player.borrow().uid.unwrap(),
is_partial: true,
scene_uid,
hollow_level: 1,
grids: HashMap::from([(destination_grid, grid.clone())]),
},
sync_event_info,
)
}
pub fn battle_finished(&self) -> PtcSyncHollowEventInfoArg {
let map = self.map.borrow();
let map = map.as_ref().unwrap();
let cur_grid = map.grids.get(&map.start_grid).unwrap();
PtcSyncHollowEventInfoArg {
event_graph_uid: u64::from(map.start_grid),
hollow_event_template_id: cur_grid.grid.event_graph_info.hollow_event_template_id,
event_graph_id: cur_grid.grid.event_graph_info.hollow_event_template_id,
updated_event: EventInfo {
id: 1000,
cur_action_id: 2001,
action_move_path: vec![1001, 1002, 2001],
state: EventState::WaitingClient,
prev_state: EventState::Running,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
},
specials: phashmap![],
}
}
pub fn get_cur_event_template_id(&self) -> i32 {
let map = self.map.borrow();
let map = map.as_ref().unwrap();
let cur_grid = map.grids.get(&map.start_grid).unwrap();
cur_grid.grid.event_graph_info.hollow_event_template_id
}
fn update_position_to_scene(&self, scene_uid: u64, pos: u16) {
let mut player = self.player.borrow_mut();
let scene = player
.dungeon_collection
.as_mut()
.unwrap()
.scenes
.as_mut()
.unwrap()
.get_mut(&scene_uid)
.unwrap();
if let SceneInfo::Hollow {
sections_info,
hollow_event_graph_uid,
..
} = scene
{
let section = sections_info.get_mut(&1).unwrap();
section.prev_grid_index = section.cur_grid_index;
section.pos_before_move = section.cur_grid_index;
section.cur_grid_index = pos;
*hollow_event_graph_uid = u64::from(pos);
} else {
panic!("Unexpected scene type")
}
}
pub fn run_event_graph(
&self,
event_graph_uid: u64,
_event_id: i32,
move_path: Vec<i32>,
) -> (PtcSyncHollowEventInfoArg, PtcHollowGridArg, Option<i32>) {
let (player_uid, scene_uid) = {
let player = self.player.borrow();
(player.uid.unwrap(), player.scene_uid.unwrap())
};
let mut map = self.map.borrow_mut();
let map = map.as_mut().unwrap();
let mut trigger_battle_id = None;
let mut grid_update = PtcHollowGridArg {
player_uid,
is_partial: true,
scene_uid,
hollow_level: 1,
grids: HashMap::new(),
};
let sync_hollow_event = {
let info = map.grids.get(&(event_graph_uid as u16)).unwrap().clone();
let event_config =
data::get_event_config_json(info.grid.event_graph_info.hollow_event_template_id);
let mut last_exec_type = "";
for id in &move_path {
let index = (id % 1000) - 1;
let actions = if id / 1000 == 1 {
event_config["Events"]["OnStart"]["Actions"]
.as_array()
.unwrap()
} else {
event_config["Events"]["OnEnd"]["Actions"]
.as_array()
.unwrap()
};
if let Some(action) = actions.get(index as usize) {
last_exec_type = action["$type"].as_str().unwrap();
match action["$type"].as_str().unwrap() {
"Share.CConfigSetMapState" => {
let x = action["X"].as_i64().unwrap() as u16;
let y = action["Y"].as_i64().unwrap() as u16;
let uid = (y * 11) + x;
if let Some(info) = map.grids.get_mut(&uid) {
info.grid.flag |= HollowGridFlag::Visible as i32
| HollowGridFlag::CanMove as i32
| HollowGridFlag::ShowEventType as i32;
grid_update.grids.insert(uid, info.clone());
}
}
"Share.CConfigTriggerBattle" => {
trigger_battle_id =
Some(match info.grid.event_graph_info.hollow_event_template_id {
1000107 => 10101002,
_ => 10101001,
});
}
_ => {}
};
}
}
let mut action_move_path = move_path;
let last_client_action = *action_move_path.iter().last().unwrap();
let actions = if last_client_action / 1000 == 1 {
event_config["Events"]["OnStart"]["Actions"]
.as_array()
.unwrap()
} else {
event_config["Events"]["OnEnd"]["Actions"]
.as_array()
.unwrap()
};
let state = if last_client_action == -1 {
EventState::Finished
} else if last_client_action % 1000 >= actions.len() as i32 {
action_move_path.push(-1);
EventState::Finished
} else {
if last_exec_type != "Share.CConfigEmpty" {
action_move_path.push(last_client_action + 1);
}
EventState::WaitingClient
};
let finish_event = if last_exec_type != "Share.CConfigTriggerBattle" {
PtcSyncHollowEventInfoArg {
event_graph_uid,
hollow_event_template_id: info.grid.event_graph_info.hollow_event_template_id,
event_graph_id: info.grid.event_graph_info.hollow_event_template_id,
updated_event: EventInfo {
id: 1000,
cur_action_id: *action_move_path.iter().last().unwrap(),
action_move_path,
state,
prev_state: EventState::Running,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
},
specials: phashmap![],
}
} else {
PtcSyncHollowEventInfoArg {
event_graph_uid,
hollow_event_template_id: info.grid.event_graph_info.hollow_event_template_id,
event_graph_id: info.grid.event_graph_info.hollow_event_template_id,
updated_event: EventInfo {
id: 0,
cur_action_id: 0,
action_move_path: vec![],
state: EventState::Initing,
prev_state: EventState::Initing,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
},
specials: phashmap![],
}
};
//tracing::info!("sending evt info: {:#?}", &finish_event);
finish_event
};
(sync_hollow_event, grid_update, trigger_battle_id)
}
pub fn sync_hollow_maps(&self, player_uid: u64, scene_uid: u64) -> PtcSyncHollowGridMapsArg {
PtcSyncHollowGridMapsArg {
player_uid,
scene_uid,
hollow_level: 1,
main_map: self.map.borrow().clone().unwrap(),
time_period: TimePeriodType::Random,
weather: WeatherType::Random,
}
}
pub fn init_default_map(&self) {
*self.map.borrow_mut() = Some(HollowGridMapProtocolInfo {
row: 5,
col: 11,
start_grid: 22,
grids: phashmap![
(
48,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 12,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
7,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2872,
link_to: 10,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1017,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
24,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2658,
link_to: 12,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
36,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 3,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
29,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 5,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
49,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2872,
link_to: 9,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1018,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
27,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 3,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000104,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::Dialog,
use_perform: false,
}
),
(
6,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 12,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
16,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 3,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000105,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::Dialog,
use_perform: false,
}
),
(
22,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2686,
link_to: 4,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000101,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::Begin,
use_perform: false,
}
),
(
30,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 12,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
18,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 3,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
38,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 3,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
32,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2872,
link_to: 8,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000107,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::ChangeLevelInteract,
use_perform: false,
}
),
(
47,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2872,
link_to: 5,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000103,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::BattleNormal,
use_perform: false,
}
),
(
25,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2682,
link_to: 10,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000102,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::Dialog,
use_perform: false,
}
),
(
5,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 6,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::DialogPositive,
use_perform: false,
}
),
(
31,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 2848,
link_to: 12,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000106,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::Dialog,
use_perform: false,
}
),
(
23,
HollowGridProtocolInfo {
grid: HollowGridInfo {
flag: 35434,
link_to: 12,
event_graph_info: HollowEventGraphInfo {
config_id: 0,
events_info: phashmap![],
specials: phashmap![],
is_new: false,
finished: false,
list_specials: phashmap![],
fired_count: 0,
hollow_event_template_id: 1000109,
uid: 0,
is_create_by_gm: false,
},
travelled_count: 0,
node_state: NodeState::All,
node_visible: NodeVisible::All,
},
event_type: HollowEventType::Dialog,
use_perform: false,
}
)
],
chessboard_id: 1000101,
});
}
}

View file

@ -0,0 +1,172 @@
use atomic_refcell::AtomicRefCell;
use protocol::{ItemInfo, PlayerInfo};
use qwer::{phashmap, PropertyHashMap};
use std::sync::Arc;
use crate::game::{util, PlayerOperationResult};
use super::UniqueIDManager;
pub struct ItemManager {
uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>,
player_info: Arc<AtomicRefCell<PlayerInfo>>,
}
impl ItemManager {
pub fn new(
uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>,
player_info: Arc<AtomicRefCell<PlayerInfo>>,
) -> Self {
Self {
uid_mgr,
player_info,
}
}
pub fn add_resource(&self, currency_id: i32, amount: i32) -> PlayerOperationResult<i32> {
let mut player_info = self.player_info.borrow_mut();
for (uid, item) in player_info.items.as_mut().unwrap() {
if let ItemInfo::Resource { id, count, .. } = item {
if currency_id == *id {
*count += amount;
return PlayerOperationResult::with_changes(
*count,
PlayerInfo {
items: Some(PropertyHashMap::Modify {
to_add: vec![(*uid, item.clone())],
to_remove: Vec::new(),
}),
..Default::default()
},
);
}
}
}
let uid = self.uid_mgr.borrow().next();
let item = ItemInfo::Resource {
uid,
id: currency_id,
count: amount,
package: 3,
first_get_time: util::cur_timestamp_ms(),
};
let items = player_info.items.as_mut().unwrap();
items.insert(uid, item.clone());
PlayerOperationResult::with_changes(
amount,
PlayerInfo {
items: Some(PropertyHashMap::Modify {
to_add: vec![(uid, item)],
to_remove: Vec::new(),
}),
..Default::default()
},
)
}
pub fn unlock_avatar(&self, id: i32) -> PlayerOperationResult<u64> {
let uid = self.uid_mgr.borrow().next();
let avatar = ItemInfo::Avatar {
uid,
id,
count: 1,
package: 3,
first_get_time: util::cur_timestamp_ms(),
star: 1,
exp: 0,
level: 1,
rank: 1,
unlocked_talent_num: 0,
skills: phashmap![(2, 1), (1, 1), (0, 1), (3, 1), (4, 1)],
is_custom_by_dungeon: true,
robot_id: 0,
};
// Unlock & equip default weapon
let weapon_uid = *self.unlock_weapon(10012).unwrap();
self.equip_weapon(weapon_uid, uid);
let mut player_info = self.player_info.borrow_mut();
let items = player_info.items.as_mut().unwrap();
items.insert(uid, avatar.clone());
PlayerOperationResult::with_changes(
uid,
PlayerInfo {
items: Some(PropertyHashMap::Modify {
to_add: vec![
(uid, avatar),
(weapon_uid, items.get(&weapon_uid).unwrap().clone()),
],
to_remove: vec![],
}),
..Default::default()
},
)
}
pub fn unlock_weapon(&self, id: i32) -> PlayerOperationResult<u64> {
let mut player_info = self.player_info.borrow_mut();
let items = player_info.items.as_mut().unwrap();
let uid = self.uid_mgr.borrow().next();
let weapon = ItemInfo::Weapon {
uid,
id,
count: 1,
package: 3,
first_get_time: util::cur_timestamp_ms(),
avatar_uid: 0,
star: 0,
exp: 0,
level: 1,
lock: 0,
refine_level: 1,
};
items.insert(uid, weapon.clone());
PlayerOperationResult::with_changes(
uid,
PlayerInfo {
items: Some(PropertyHashMap::Modify {
to_add: vec![(uid, weapon)],
to_remove: Vec::new(),
}),
..Default::default()
},
)
}
pub fn equip_weapon(
&self,
weapon_uid: u64,
equip_avatar_uid: u64,
) -> PlayerOperationResult<bool> {
let mut player_info = self.player_info.borrow_mut();
let items = player_info.items.as_mut().unwrap();
let Some(ItemInfo::Weapon { avatar_uid, .. }) = items.get_mut(&weapon_uid) else {
return PlayerOperationResult::ret(false);
};
*avatar_uid = equip_avatar_uid;
PlayerOperationResult::with_changes(
true,
PlayerInfo {
items: Some(PropertyHashMap::Modify {
to_add: vec![(weapon_uid, items.get(&weapon_uid).unwrap().clone())],
to_remove: Vec::new(),
}),
..Default::default()
},
)
}
}

View file

@ -0,0 +1,18 @@
mod dungeon;
mod hollow_grid;
mod item;
pub mod net_stream;
mod quest;
mod scene_unit;
mod unique_id;
mod unlock;
mod yorozuya_quest;
pub use dungeon::DungeonManager;
pub use hollow_grid::HollowGridManager;
pub use item::ItemManager;
pub use quest::QuestManager;
pub use scene_unit::SceneUnitManager;
pub use unique_id::UniqueIDManager;
pub use unlock::UnlockManager;
pub use yorozuya_quest::YorozuyaQuestManager;

View file

@ -0,0 +1,30 @@
use atomic_refcell::AtomicRefCell;
use protocol::{AccountInfo, PlayerInfo, PropertyBlob};
use qwer::OctData;
use std::sync::Arc;
const CLIENT_PROP_FLAG: u16 = 1;
#[derive(Default)]
pub struct PropertyManager {
pub account_info: Arc<AtomicRefCell<AccountInfo>>,
pub player_info: Arc<AtomicRefCell<PlayerInfo>>,
}
impl PropertyManager {
pub fn serialize_account_info(&self) -> PropertyBlob {
Self::serialize_property(&*self.account_info.borrow()).unwrap()
}
pub fn serialize_player_info(&self) -> PropertyBlob {
Self::serialize_property(&*self.player_info.borrow()).unwrap()
}
pub fn serialize_property(prop: &impl OctData) -> Result<PropertyBlob, std::io::Error> {
let mut stream = Vec::new();
let mut cursor = std::io::Cursor::new(&mut stream);
prop.marshal_to(&mut cursor, CLIENT_PROP_FLAG)?;
Ok(PropertyBlob { stream })
}
}

View file

@ -0,0 +1,77 @@
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use qwer::PropertyDoubleKeyHashMap;
use crate::game::PlayerOperationResult;
use super::UniqueIDManager;
use protocol::*;
pub struct QuestManager {
uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>,
player: Arc<AtomicRefCell<PlayerInfo>>,
}
impl QuestManager {
pub fn new(
uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>,
player: Arc<AtomicRefCell<PlayerInfo>>,
) -> Self {
Self { uid_mgr, player }
}
pub fn add_world_quest(&self, quest: QuestInfo) -> PlayerOperationResult<u64> {
let mut world_quest_collection_uid = self
.player
.borrow()
.quest_data
.as_ref()
.unwrap()
.world_quest_collection_uid
.unwrap();
if world_quest_collection_uid == 0 {
world_quest_collection_uid = self.uid_mgr.borrow().next();
self.player
.borrow_mut()
.quest_data
.as_mut()
.unwrap()
.world_quest_collection_uid
.replace(world_quest_collection_uid);
}
self.add_quest_to_collection(world_quest_collection_uid, quest)
}
pub fn add_quest_to_collection(
&self,
collection_uid: u64,
mut quest: QuestInfo,
) -> PlayerOperationResult<u64> {
let mut player = self.player.borrow_mut();
let quest_data = player.quest_data.as_mut().unwrap();
quest.set_collection_uid(collection_uid);
quest_data
.quests
.as_mut()
.unwrap()
.insert(collection_uid, quest.get_id(), quest.clone());
PlayerOperationResult::with_changes(
collection_uid,
PlayerInfo {
quest_data: Some(QuestData {
quests: Some(PropertyDoubleKeyHashMap::Modify {
to_add: vec![(collection_uid, quest.get_id(), quest)],
to_remove: Vec::new(),
}),
..Default::default()
}),
..Default::default()
},
)
}
}

View file

@ -0,0 +1,231 @@
use std::{collections::HashMap, sync::Arc};
use atomic_refcell::AtomicRefCell;
use protocol::*;
use qwer::{phashmap, PropertyHashMap};
use super::UniqueIDManager;
pub struct SceneUnitManager {
uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>,
units: AtomicRefCell<HashMap<u64, SceneUnitProtocolInfo>>,
}
impl SceneUnitManager {
pub fn new(uid_mgr: Arc<AtomicRefCell<UniqueIDManager>>) -> Self {
Self {
uid_mgr,
units: AtomicRefCell::new(HashMap::new()),
}
}
pub fn create_npc(
&self,
id: i32,
tag: i32,
quest_id: i32,
interacts_info: PropertyHashMap<i32, InteractInfo>,
) -> u64 {
let uid = self.uid_mgr.borrow().next();
self.units.borrow_mut().insert(
uid,
SceneUnitProtocolInfo::NpcProtocolInfo {
uid,
tag,
id,
quest_id,
interacts_info,
},
);
uid
}
pub fn sync(&self, scene_uid: u64, section_id: i32) -> PtcSyncSceneUnitArg {
PtcSyncSceneUnitArg {
scene_uid,
section_id,
is_partial: false,
removed_scene_units: Vec::new(),
scene_units: self
.units
.borrow()
.iter()
.map(|(_uid, unit)| unit.clone())
.collect(),
}
}
// TODO: partial_sync for newly added/removed units
// currently hardcoded for Main City section 2
pub fn add_default_units(&self) {
self.create_npc(
100171011,
3,
0,
phashmap![(
19900006,
create_interact(
0,
1,
2.0,
0.0,
0.0,
0.0,
0.0,
"",
phashmap![(0, String::new())]
)
)],
);
self.create_npc(
100171011,
4,
0,
phashmap![(
19900006,
create_interact(
0,
1,
2.0,
0.0,
0.0,
0.0,
0.0,
"",
phashmap![(0, String::new())]
)
)],
);
self.create_npc(
100171011,
1002,
0,
phashmap![(
19900062,
create_interact(
0,
1,
2.0,
0.0,
0.0,
0.0,
0.0,
"",
phashmap![(0, String::new())]
)
)],
);
self.create_npc(
100171011,
1001,
0,
phashmap![(
10000010,
create_interact(
10000010,
1,
1.0,
0.0,
0.0,
0.0,
0.0,
"A",
phashmap![(1001, String::from("A"))]
)
)],
);
self.create_npc(
100171011,
1005,
0,
phashmap![(
10000014,
create_interact(
10000014,
1,
1.0,
0.0,
0.0,
0.0,
0.0,
"A",
phashmap![(1005, String::from("A"))]
)
)],
);
self.create_npc(
100173001,
2028,
0,
phashmap![(
19900052,
create_interact(
19900052,
2,
9.0,
2.0,
2.0,
90.0,
10.0,
"A",
phashmap![(2028, String::from("A"))]
)
)],
);
self.create_npc(
100172011,
2000,
0,
phashmap![(
19900030,
create_interact(
0,
1,
2.0,
0.0,
0.0,
0.0,
0.0,
"",
phashmap![(2000, String::from("A")), (2052, String::from("B"))]
)
)],
);
self.create_npc(100172081, 2052, 0, phashmap![]);
}
}
#[allow(clippy::too_many_arguments)]
fn create_interact(
interact_id: i32,
interact_shape: u16,
scale_x: f64,
scale_y: f64,
scale_z: f64,
scale_w: f64,
scale_r: f64,
name: &str,
participators: PropertyHashMap<i32, String>,
) -> InteractInfo {
InteractInfo {
interact_id,
interact_shape,
scale_x,
scale_y,
scale_z,
scale_w,
scale_r,
name: name.to_string(),
participators,
}
}

View file

@ -0,0 +1,22 @@
use std::sync::atomic::{AtomicU64, Ordering};
const BASE_UID: u64 = 1000000;
pub struct UniqueIDManager {
uid_counter: AtomicU64,
}
impl UniqueIDManager {
pub const fn new() -> Self {
Self {
uid_counter: AtomicU64::new(BASE_UID),
}
}
pub fn next(&self) -> u64 {
let uid = self.uid_counter.load(Ordering::SeqCst) + 1;
self.uid_counter.store(uid, Ordering::SeqCst);
uid
}
}

View file

@ -0,0 +1,42 @@
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use protocol::*;
use qwer::PropertyHashSet;
use crate::game::PlayerOperationResult;
pub struct UnlockManager {
player: Arc<AtomicRefCell<PlayerInfo>>,
}
impl UnlockManager {
pub fn new(player: Arc<AtomicRefCell<PlayerInfo>>) -> Self {
Self { player }
}
pub fn unlock(&self, unlock_id: i32) -> PlayerOperationResult<PtcUnlockArg> {
let mut player = self.player.borrow_mut();
let unlock_info = player.unlock_info.as_mut().unwrap();
unlock_info
.unlocked_list
.as_mut()
.unwrap()
.insert(unlock_id);
PlayerOperationResult::with_changes(
PtcUnlockArg { unlock_id },
PlayerInfo {
unlock_info: Some(UnlockInfo {
unlocked_list: Some(PropertyHashSet::Modify {
to_add: vec![unlock_id],
to_remove: Vec::new(),
}),
condition_progress: None,
}),
..Default::default()
},
)
}
}

View file

@ -0,0 +1,68 @@
use std::collections::HashSet;
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use qwer::{PropertyDoubleKeyHashMap, PropertyHashSet};
use crate::game::PlayerOperationResult;
use protocol::*;
pub struct YorozuyaQuestManager {
player: Arc<AtomicRefCell<PlayerInfo>>,
}
impl YorozuyaQuestManager {
pub fn new(player: Arc<AtomicRefCell<PlayerInfo>>) -> Self {
Self { player }
}
pub fn add_hollow_quest(
&self,
yorozuya_collection_id: i32,
hollow_quest_type: HollowQuestType,
id: i32,
) -> PlayerOperationResult<i32> {
let mut player = self.player.borrow_mut();
let yorozuya = player.yorozuya_info.as_mut().unwrap();
let hollow_quests = yorozuya.hollow_quests.as_mut().unwrap();
let updated_set = {
if let Some(quests) = hollow_quests.get_mut(&yorozuya_collection_id, &hollow_quest_type)
{
quests.insert(id);
let PropertyHashSet::Base(set) = quests else {
return PlayerOperationResult::ret(yorozuya_collection_id);
};
set.clone()
} else {
let set = HashSet::from([id]);
hollow_quests.insert(
yorozuya_collection_id,
hollow_quest_type,
PropertyHashSet::Base(set.clone()),
);
set
}
};
PlayerOperationResult::with_changes(
yorozuya_collection_id,
PlayerInfo {
yorozuya_info: Some(YorozuyaInfo {
hollow_quests: Some(PropertyDoubleKeyHashMap::Modify {
to_add: vec![(
yorozuya_collection_id,
hollow_quest_type,
PropertyHashSet::Base(updated_set),
)],
to_remove: Vec::new(),
}),
..Default::default()
}),
..Default::default()
},
)
}
}

View file

@ -0,0 +1,7 @@
mod context;
pub mod data;
pub mod globals;
pub mod manager;
pub mod util;
pub use context::{GameContext, PlayerOperationResult};

293
gameserver/src/game/util.rs Normal file
View file

@ -0,0 +1,293 @@
use protocol::*;
use qwer::{
pdkhashmap, phashmap, phashset, PropertyDoubleKeyHashMap, PropertyHashMap, PropertyHashSet,
};
use std::time::{SystemTime, UNIX_EPOCH};
pub fn cur_timestamp_ms() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64
}
pub fn create_default_account(id: u64) -> AccountInfo {
AccountInfo {
account_name: Some(format!("1_{id}")),
players: Some(vec![id]),
gm_level: Some(10),
account_type: Some(1),
register_cps: Some(String::new()),
}
}
pub fn create_default_player(id: u64) -> PlayerInfo {
PlayerInfo {
uid: Some(id),
account_name: Some(format!("1_{id}")),
items: Some(qwer::phashmap![(
3405096459205780,
ItemInfo::Buddy {
uid: 3405096459205780,
id: 50012,
count: 1,
package: 3,
first_get_time: 0,
}
)]),
dungeon_collection: Some(DungeonCollection {
dungeons: Some(qwer::phashmap![]),
scenes: Some(qwer::phashmap![]),
default_scene_uid: Some(0),
transform: Some(Transform::default()),
used_story_mode: Some(true),
used_manual_qte_mode: Some(true),
}),
properties: Some(pdkhashmap![]),
scene_properties: Some(pdkhashmap![]),
quest_data: Some(QuestData {
quests: Some(pdkhashmap![]),
is_afk: Some(false),
unlock_condition_progress: Some(pdkhashmap![]),
world_quest_collection_uid: Some(0),
world_quest_for_cur_dungeon: Some(0),
world_quest_for_cur_dungeon_afk: Some(0),
}),
joined_chat_rooms: Some(Vec::new()),
last_enter_world_timestamp: Some(0),
scene_uid: Some(0),
archive_info: Some(ArchiveInfo {
videotapes_info: Some(phashmap![]),
}),
auto_recovery_info: Some(phashmap![(
501,
AutoRecoveryInfo {
buy_times: 0,
last_recovery_timestamp: 0,
}
)]),
unlock_info: Some(UnlockInfo {
condition_progress: Some(pdkhashmap![]),
unlocked_list: Some(phashset![]),
}),
yorozuya_info: Some(YorozuyaInfo {
yorozuya_level: Some(1),
yorozuya_rank: Some(1),
gm_enabled: Some(true),
gm_quests: Some(phashmap![]),
finished_hollow_quest_count: Some(0),
finished_hollow_quest_count_of_type: Some(phashmap![]),
hollow_quests: Some(pdkhashmap![]),
urgent_quests_queue: Some(phashmap![]),
unlock_hollow_id: Some(vec![102]),
unlock_hollow_id_progress: Some(pdkhashmap![]),
last_refresh_timestamp_common: Some(0),
last_refresh_timestamp_urgent: Some(0),
next_refresh_timestamp_urgent: Some(0),
}),
equip_gacha_info: Some(EquipGachaInfo {
avatar_level_advance_times: Some(0),
equip_star_up_times: Some(0),
security_num_by_lv: Some(phashmap![]),
smithy_level: Some(0),
total_gacha_times: Some(0),
}),
beginner_procedure_info: Some(BeginnerProcedureInfo {
procedure_info: Some(0),
}),
pos_in_main_city: Some(PlayerPosInMainCity {
position: Some(Vector3f {
x: 0.0,
y: 0.0,
z: 0.0,
}),
rotation: Some(Vector3f {
x: 0.0,
y: 0.0,
z: 0.0,
}),
initial_pos_id: Some(0),
}),
fairy_info: Some(FairyInfo {
condition_progress: Some(pdkhashmap![]),
fairy_groups: Some(phashmap![]),
}),
popup_window_info: Some(PopupWindowInfo {
condition_progress: Some(pdkhashmap![]),
popup_window_list: Some(Vec::new()),
}),
tips_info: Some(TipsInfo {
tips_list: Some(Vec::new()),
tips_group: Some(Vec::new()),
tips_condition_progress: Some(pdkhashmap![]),
tips_group_condition_progress: Some(pdkhashmap![]),
}),
main_city_quest_data: Some(MainCityQuestData {
in_progress_quests: Some(Vec::new()),
exicing_finish_script_group: Some(vec![10020001]),
}),
embattles: Some(Embattles {
last_embattles: Some(phashmap![]),
}),
day_change_info: Some(DayChangeInfo {
last_daily_refresh_timing: Some(0),
}),
npcs_info: Some(PlayerNPCsInfo {
npcs_info: Some(phashmap![]),
destroy_npc_when_leave_section: Some(phashset![]),
}),
scripts_to_execute: Some(pdkhashmap![]),
scripts_to_remove: Some(phashmap![]),
last_leave_world_timestamp: Some(0),
muip_data: Some(MUIPData {
alread_cmd_uids: Some(phashset![]),
ban_end_time: Some(String::new()),
tag_value: Some(0),
scene_pass_times: Some(phashmap![]),
scene_enter_times: Some(phashmap![]),
dungeon_pass_times: Some(phashmap![]),
dungeon_enter_times: Some(phashmap![]),
ban_begin_time: Some(String::new()),
game_total_time: Some(0),
language_type: Some(0),
}),
nick_name: Some(String::new()),
ramen_data: Some(RamenData {
unlock_ramen: Some(phashset![20301, 20401, 20501, 20601, 20201]),
cur_ramen: Some(0),
used_times: Some(0),
unlock_initiative_item: Some(phashset![]),
unlock_ramen_condition_progress: Some(pdkhashmap![]),
unlock_item_condition_progress: Some(pdkhashmap![]),
has_mystical_spice: Some(true),
unlock_has_mystical_spice_condition_progress: Some(phashmap![]),
cur_mystical_spice: Some(0),
unlock_mystical_spice: Some(phashset![
30101, 30601, 30201, 30501, 30301, 30801, 31201, 30401, 31401, 31001
]),
unlock_mystical_spice_condition_progress: Some(pdkhashmap![]),
unlock_initiative_item_group: Some(phashset![]),
hollow_item_history: Some(phashmap![]),
initial_item_ability: Some(0),
new_unlock_ramen: Some(Vec::new()),
eat_ramen_times: Some(0),
make_hollow_item_times: Some(0),
new_unlock_initiative_item: Some(phashset![]),
}),
shop: Some(ShopsInfo {
shops: Some(phashmap![]),
shop_buy_times: Some(0),
vip_level: Some(0),
}),
vhs_store_data: Some(VHSStoreData {
store_level: Some(0),
unreceived_reward: Some(0),
hollow_enter_times: Some(0),
last_receive_time: Some(0),
vhs_collection_slot: Some(Vec::new()),
unlock_vhs_collection: Some(phashset![]),
already_trending: Some(phashset![]),
unlock_trending_condition_progress: Some(pdkhashmap![]),
is_need_refresh: Some(true),
scripts_id: Some(phashset![]),
store_exp: Some(0),
is_level_chg_tips: Some(true),
vhs_hollow: Some(Vec::new()),
is_receive_trending_reward: Some(false),
is_need_first_trending: Some(false),
last_basic_script: Some(0),
is_complete_first_trending: Some(false),
last_basic_npc: Some(0),
can_random_trending: Some(phashset![]),
vhs_trending_info: Some(Vec::new()),
unlock_vhs_trending_info: Some(phashmap![]),
vhs_flow: Some(0),
received_reward: Some(0),
last_reward: Some(0),
last_exp: Some(0),
last_flow: Some(0),
last_vhs_trending_info: Some(Vec::new()),
new_know_trend: Some(Vec::new()),
quest_finish_script: Some(pdkhashmap![]),
quest_finish_scripts_id: Some(phashset![]),
total_received_reward: Some(phashmap![]),
last_vhs_npc_info: Some(Vec::new()),
vhs_npc_info: Some(Vec::new()),
npc_info: Some(phashset![]),
total_received_reward_times: Some(0),
}),
operation_mail_receive_info: Some(OperationMailReceiveInfo {
receive_list: Some(phashset![]),
condition_progress: Some(pdkhashmap![]),
}),
second_last_enter_world_timestamp: Some(0),
login_times: Some(1),
create_timestamp: Some(cur_timestamp_ms()),
gender: Some(0),
avatar_id: Some(0),
prev_scene_uid: Some(2),
register_cps: Some(String::new()),
register_platform: Some(3),
pay_info: Some(PayInfo {
month_total_pay: Some(0),
}),
private_npcs: Some(phashmap![]),
battle_event_info: Some(BattleEventInfo {
unlock_battle: Some(phashset![]),
unlock_battle_condition_progress: Some(pdkhashmap![]),
alread_rand_battle: Some(pdkhashmap![]),
alread_battle_stage: Some(Vec::new()),
rand_battle_type: Some(phashmap![]),
}),
gm_data: Some(GMData {
register_conditions: Some(phashset![]),
condition_proress: Some(pdkhashmap![]),
completed_conditions: Some(phashset![]),
}),
player_mail_ext_infos: Some(PlayerMailExtInfos {
player_mail_ext_info: Some(phashmap![]),
}),
single_dungeon_group: Some(SingleDungeonGroup {
dungeons: Some(phashmap![]),
scenes: Some(pdkhashmap![]),
npcs: Some(pdkhashmap![]),
section: Some(pdkhashmap![]),
}),
newbie_info: Some(NewbieInfo {
unlocked_id: Some(phashset![3]),
condition_progress: Some(pdkhashmap![]),
}),
loading_page_tips_info: Some(LoadingPageTipsInfo {
unlocked_id: Some(phashset![1, 2, 3]),
condition_progress: Some(pdkhashmap![]),
}),
switch_of_story_mode: Some(true),
switch_of_qte: Some(true),
collect_map: Some(CollectMap {
card_map: Some(phashset![]),
curse_map: Some(phashset![]),
unlock_cards: Some(phashset![]),
unlock_curses: Some(phashset![]),
event_icon_map: Some(phashset![]),
unlock_events: Some(phashset![]),
new_card_map: Some(phashset![]),
new_curse_map: Some(phashset![]),
new_event_icon_map: Some(phashset![]),
unlock_event_icon_condition_progress: Some(pdkhashmap![]),
unlock_card_condition_progress: Some(pdkhashmap![]),
unlock_curse_condition_progress: Some(pdkhashmap![]),
unlock_event_condition_progress: Some(pdkhashmap![]),
unlock_event_icons: Some(phashset![]),
}),
areas_info: Some(AreasInfo {
area_owners_info: Some(pdkhashmap![]),
sequence: Some(0),
}),
bgm_info: Some(BGMInfo { bgm_id: Some(1) }),
main_city_objects_state: Some(phashmap![]),
hollow_info: Some(HollowInfo {
banned_hollow_event: Some(phashset![]),
}),
}
}

58
gameserver/src/logging.rs Normal file
View file

@ -0,0 +1,58 @@
use tracing::Instrument;
#[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() {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
}
pub async fn init_system_logging() {
use std::time::Duration;
use sysinfo::System;
tokio::spawn(
async {
let mut s = System::new_all();
s.refresh_all();
let num_cpus = s.cpus().len();
loop {
tokio::time::sleep(Duration::from_millis(20000)).await;
s.refresh_all();
let process = s.process(sysinfo::get_current_pid().unwrap()).unwrap();
tracing::info!(
cpu_usage = %format!("{:.2}%", process.cpu_usage() / num_cpus as f32)
);
let memory = process.memory();
let formatted = match memory {
m if m < 1024 => format!("{m} B"),
m if m < 1024 * 1024 => format!("{:.2} KB", m as f32 / 1024.0),
m if m < 1024 * 1024 * 1024 => format!("{:.2} MB", m as f32 / 1024.0 / 1024.0),
m => format!("{:.2} GB", m as f32 / 1024.0 / 1024.0 / 1024.0),
};
tracing::info!(total_memory = %format!("{formatted}"));
}
}
.instrument(tracing::info_span!("system-usage")),
);
}

53
gameserver/src/main.rs Normal file
View file

@ -0,0 +1,53 @@
use std::path::Path;
use anyhow::Result;
use tracing::Level;
mod game;
mod logging;
mod net;
use logging::{init_system_logging, init_tracing};
const GATE_HOST: &str = "0.0.0.0";
const GATE_PORT: u16 = 10301;
#[tokio::main]
async fn main() -> Result<()> {
#[cfg(target_os = "windows")]
ansi_term::enable_ansi_support().unwrap();
init_config()?;
init_tracing();
let span = tracing::span!(Level::DEBUG, "main");
let _enter = span.enter();
init_system_logging().await;
net::gateway::listen(GATE_HOST, GATE_PORT).await?;
Ok(())
}
fn init_config() -> Result<()> {
let local_dotenv = Path::new(".env");
if local_dotenv.exists() {
dotenv::dotenv()?;
} else {
let config = dirs::config_dir()
.ok_or_else(|| anyhow::anyhow!("No config directory found"))?
.join("nap-gameserver");
std::fs::create_dir_all(&config)?;
let env = config.join(".env");
if !env.exists() {
std::fs::write(&env, "SKIP_TUTORIAL=0")?;
}
dotenv::from_path(&env)?;
}
Ok(())
}

View file

@ -0,0 +1,32 @@
use anyhow::Result;
use tokio::net::TcpListener;
use tracing::Instrument;
use crate::log_error;
use super::NetworkSession;
pub async fn listen(host: &str, port: u16) -> Result<()> {
let listener = TcpListener::bind(format!("{host}:{port}")).await?;
tracing::info!("Listening at {host}:{port}");
loop {
let Ok((client_socket, client_addr)) = listener.accept().await else {
continue;
};
tracing::info!("New session from {client_addr}");
let mut session = NetworkSession::new(client_socket, client_addr);
tokio::spawn(
async move {
log_error!(
"Session from {client_addr} disconnected",
format!("An error occurred while processing session ({client_addr})"),
Box::pin(session.run()).await
);
}
.instrument(tracing::info_span!("session", addr = %client_addr)),
);
}
}

View file

@ -0,0 +1,16 @@
use super::*;
pub async fn on_rpc_battle_report_arg(
session: &NetworkSession,
arg: &RpcBattleReportArg,
) -> Result<()> {
let need_index = arg
.battle_reports
.iter()
.last()
.map_or(0, |report| report.index + 1);
session
.send_rpc_ret(RpcBattleReportRet::new(need_index))
.await
}

View file

@ -0,0 +1,51 @@
use std::time::{SystemTime, UNIX_EPOCH};
use super::*;
const START_PROC_ID: i32 = 1;
pub async fn on_rpc_advance_beginner_procedure_arg(
session: &NetworkSession,
arg: &RpcAdvanceBeginnerProcedureArg,
) -> Result<()> {
let next_procedure_id = if arg.procedure_id == 0 {
START_PROC_ID
} else {
arg.procedure_id + 1
};
tracing::info!("{arg:?}");
if arg.procedure_id == 6 {
Box::pin(world::enter_main_city(session)).await?;
}
session
.send_rpc_ret(RpcAdvanceBeginnerProcedureRet::new(next_procedure_id))
.await
}
pub async fn on_rpc_beginnerbattle_begin_arg(
session: &NetworkSession,
arg: &RpcBeginnerbattleBeginArg,
) -> Result<()> {
let cur_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
session
.send_rpc_ret(RpcBeginnerbattleBeginRet::new(format!(
"{cur_timestamp}-{}",
arg.battle_id
)))
.await
}
pub async fn on_rpc_beginnerbattle_end_arg(
session: &NetworkSession,
arg: &RpcBeginnerbattleEndArg,
) -> Result<()> {
tracing::info!("Battle statistics: {:?}", arg.battle_statistics);
session.send_rpc_ret(RpcBeginnerbattleEndRet::new()).await
}

View file

@ -0,0 +1,256 @@
use qwer::{phashmap, phashset, PropertyHashMap, PropertyHashSet};
use std::collections::HashMap;
use super::*;
pub async fn on_rpc_hollow_move_arg(
session: &mut NetworkSession,
arg: &RpcHollowMoveArg,
) -> Result<()> {
tracing::info!("Hollow movement {:?}", &arg);
let destination_pos = *arg.positions.iter().last().unwrap();
let scene_uid = session.get_player().scene_uid.unwrap();
let hollow_grid_manager = session.context.hollow_grid_manager.borrow();
let (ptc_hollow_grid, ptc_sync_hollow_event) =
hollow_grid_manager.move_to(destination_pos, scene_uid);
session.send_rpc_arg(114, &ptc_hollow_grid).await?;
if let Some(ptc_sync_hollow_event) = ptc_sync_hollow_event {
session.send_rpc_arg(210, &ptc_sync_hollow_event).await?;
}
let pos = PtcPositionInHollowChangedArg {
player_uid: 1337,
hollow_level: arg.hollow_level,
position: destination_pos,
};
session.send_rpc_arg(141, &pos).await?;
session
.send_rpc_ret(RpcHollowMoveRet::new(
arg.hollow_level,
*arg.positions.iter().last().unwrap(),
))
.await
}
pub async fn on_rpc_end_battle_arg(session: &NetworkSession, arg: &RpcEndBattleArg) -> Result<()> {
tracing::info!("RpcEndBattle: {:?}", &arg);
let player_uid = session.get_player_uid();
let hollow_grid_manager = session.context.hollow_grid_manager.borrow();
session
.send_rpc_arg(210, &hollow_grid_manager.battle_finished())
.await?;
let dungeon_manager = session.context.dungeon_manager.borrow();
let ptc_enter_scene = dungeon_manager
.leave_battle()
.unwrap()
.send_changes(session)
.await?
.clone();
session
.send_rpc_arg(
124,
&hollow_grid_manager.sync_hollow_maps(player_uid, dungeon_manager.get_cur_scene_uid()),
)
.await?;
let ptc_position_in_hollow_changed = PtcPositionInHollowChangedArg {
player_uid,
hollow_level: 1,
position: hollow_grid_manager.get_cur_position_in_hollow(),
};
session
.send_rpc_arg(141, &ptc_position_in_hollow_changed)
.await?;
session.send_rpc_arg(118, &ptc_enter_scene).await?;
session
.send_rpc_ret(RpcEndBattleRet::new(
hollow_grid_manager.get_cur_event_template_id(),
HashMap::new(),
))
.await
}
pub async fn on_rpc_run_hollow_event_graph_arg(
session: &mut NetworkSession,
arg: &RpcRunHollowEventGraphArg,
) -> Result<()> {
tracing::info!("Run hollow event graph {:?}", arg);
let scene_uid = session.get_player().scene_uid.unwrap();
if arg.event_graph_uid == 3405096459205834 {
// Perform (cutscene)
let finish_perform = PtcSyncHollowEventInfoArg {
event_graph_uid: 3405096459205834,
hollow_event_template_id: 1000108,
event_graph_id: 1000108,
updated_event: EventInfo {
id: 1000,
cur_action_id: -1,
action_move_path: vec![1001, 1002, -1],
state: EventState::Finished,
prev_state: EventState::Running,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
},
specials: phashmap![],
};
session.send_rpc_arg(210, &finish_perform).await?;
let hollow_grid_manager = session.context.hollow_grid_manager.borrow();
let (ptc_hollow_grid, ptc_sync_hollow_event) = hollow_grid_manager.move_to(22, scene_uid);
session.send_rpc_arg(114, &ptc_hollow_grid).await?;
if let Some(ptc_sync_hollow_event) = ptc_sync_hollow_event {
session.send_rpc_arg(210, &ptc_sync_hollow_event).await?;
}
} else {
let hollow_grid_manager = session.context.hollow_grid_manager.borrow();
let (sync_hollow_event, hollow_grid, trigger_battle_id) = hollow_grid_manager
.run_event_graph(arg.event_graph_uid, arg.event_id, arg.move_path.clone());
session.send_rpc_arg(210, &sync_hollow_event).await?;
session.send_rpc_arg(114, &hollow_grid).await?;
if let Some(trigger_battle_id) = trigger_battle_id {
let dungeon_manager = session.context.dungeon_manager.borrow();
let hollow_uid = *session.get_player().scene_uid.as_ref().unwrap();
let battle_scene_uid = *dungeon_manager
.create_fight(trigger_battle_id, hollow_uid)
.send_changes(session)
.await?;
let ptc_position_in_hollow_changed = PtcPositionInHollowChangedArg {
player_uid: 1337,
hollow_level: 1,
position: hollow_grid_manager.get_cur_position_in_hollow(),
};
session
.send_rpc_arg(141, &ptc_position_in_hollow_changed)
.await?;
session
.send_rpc_arg(
118,
dungeon_manager
.enter_battle(battle_scene_uid)
.send_changes(session)
.await?,
)
.await?;
}
}
session.send_rpc_ret(RpcRunHollowEventGraphRet::new()).await
}
pub async fn on_rpc_start_hollow_quest_arg(
session: &NetworkSession,
arg: &RpcStartHollowQuestArg,
) -> Result<()> {
tracing::info!("start hollow quest: {arg:?}");
for (_idx, avatar_uid) in &arg.avatar_map {
// Set character HP
let update_properties = PtcPropertyChangedArg {
scene_unit_uid: *avatar_uid,
is_partial: true,
changed_properties: phashmap![(1, 500), (111, 500)],
};
session.send_rpc_arg(129, &update_properties).await?;
}
let dungeon_manager = session.context.dungeon_manager.borrow();
let avatars: Vec<u64> = arg.avatar_map.iter().map(|(_idx, uid)| *uid).collect();
let (dungeon_uid, scene_uid) = *dungeon_manager
.create_hollow(10001, 10010001, &avatars)
.send_changes(session)
.await?;
let quest_manager = session.context.quest_manager.borrow();
quest_manager
.add_quest_to_collection(
dungeon_uid,
QuestInfo::DungeonInner {
id: 1001000101,
finished_count: 0,
collection_uid: 0,
progress: 0,
parent_quest_id: 10010001,
state: QuestState::InProgress,
finish_condition_progress: phashmap![],
progress_time: 2111605,
sort_id: 2000,
},
)
.send_changes(session)
.await?;
let ptc_enter_scene = dungeon_manager
.enter_scene(scene_uid)?
.send_changes(session)
.await?
.clone();
let hollow_grid_manager = session.context.hollow_grid_manager.borrow();
hollow_grid_manager.init_default_map();
session
.send_rpc_arg(
124,
&hollow_grid_manager.sync_hollow_maps(session.get_player_uid(), scene_uid),
)
.await?;
let ptc_position_in_hollow_changed = PtcPositionInHollowChangedArg {
player_uid: 1337,
hollow_level: 1,
position: hollow_grid_manager.get_cur_position_in_hollow(),
};
session
.send_rpc_arg(141, &ptc_position_in_hollow_changed)
.await?;
let ptc_sync_hollow_event_info = PtcSyncHollowEventInfoArg {
event_graph_uid: 3405096459205834,
hollow_event_template_id: 1000108,
event_graph_id: 1000108,
updated_event: EventInfo {
id: 1000,
cur_action_id: 1001,
action_move_path: vec![1001],
state: EventState::WaitingClient,
prev_state: EventState::Running,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
},
specials: phashmap![],
};
session
.send_rpc_arg(210, &ptc_sync_hollow_event_info)
.await?;
session.send_rpc_arg(118, &ptc_enter_scene).await?;
session.send_rpc_ret(RpcStartHollowQuestRet::new()).await
}

View file

@ -0,0 +1,36 @@
use std::time::{SystemTime, UNIX_EPOCH};
use super::*;
use crate::game::util;
const DEFAULT_ACCOUNT_ID: u64 = 1337;
pub async fn on_rpc_login_arg(session: &NetworkSession, arg: &RpcLoginArg) -> Result<()> {
tracing::info!("Received rpc login arg: {}", arg.account_name);
*session.get_account_mut() = util::create_default_account(DEFAULT_ACCOUNT_ID);
session
.send_rpc_ret(RpcLoginRet::new(
session.ns_prop_mgr.serialize_account_info(),
))
.await
}
pub async fn on_ptc_get_server_timestamp_arg(
session: &NetworkSession,
_arg: &PtcGetServerTimestampArg,
) -> Result<()> {
session
.send_rpc_ret(PtcGetServerTimestampRet::new(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
0,
))
.await
}
pub async fn on_rpc_keep_alive_arg(session: &NetworkSession, _arg: &RpcKeepAliveArg) -> Result<()> {
session.send_rpc_ret(RpcKeepAliveRet::new()).await
}

View file

@ -0,0 +1,8 @@
use super::*;
pub async fn on_rpc_get_player_mails_arg(
session: &NetworkSession,
_arg: &RpcGetPlayerMailsArg,
) -> Result<()> {
session.send_rpc_ret(RpcGetPlayerMailsRet::new(0)).await
}

View file

@ -0,0 +1,25 @@
mod battle;
mod beginner_procedure;
mod hollow;
mod login;
mod mail;
mod plot_play;
mod progression;
mod role;
mod world;
mod yorozuya;
use super::NetworkSession;
use anyhow::Result;
use protocol::*;
pub use battle::*;
pub use beginner_procedure::*;
pub use hollow::*;
pub use login::*;
pub use mail::*;
pub use plot_play::*;
pub use progression::*;
pub use role::*;
pub use world::*;
pub use yorozuya::*;

View file

@ -0,0 +1,36 @@
use super::*;
pub async fn on_rpc_perform_trigger_arg(
session: &NetworkSession,
arg: &RpcPerformTriggerArg,
) -> Result<()> {
session
.send_rpc_ret(RpcPerformTriggerRet::new(format!(
"{}-{}",
arg.perform_id, arg.perform_type
)))
.await
}
pub async fn on_rpc_perform_end_arg(
session: &NetworkSession,
_arg: &RpcPerformEndArg,
) -> Result<()> {
session.send_rpc_ret(RpcPerformEndRet::new()).await
}
pub async fn on_rpc_finish_a_c_t_perform_show_arg(
session: &NetworkSession,
_arg: &RpcFinishACTPerformShowArg,
) -> Result<()> {
session
.send_rpc_ret(RpcFinishACTPerformShowRet::new())
.await
}
pub async fn on_rpc_perform_jump_arg(
session: &NetworkSession,
_arg: &RpcPerformJumpArg,
) -> Result<()> {
session.send_rpc_ret(RpcPerformJumpRet::new()).await
}

View file

@ -0,0 +1,8 @@
use super::*;
pub async fn on_rpc_close_level_chg_tips_arg(
session: &NetworkSession,
_arg: &RpcCloseLevelChgTipsArg,
) -> Result<()> {
session.send_rpc_ret(RpcCloseLevelChgTipsRet::new()).await
}

View file

@ -0,0 +1,25 @@
use super::*;
#[tracing::instrument(skip(session))]
pub async fn on_rpc_mod_nick_name_arg(
session: &NetworkSession,
arg: &RpcModNickNameArg,
) -> Result<()> {
tracing::info!("creating character");
let mut player = session.get_player_mut();
player.nick_name.replace(arg.nick_name.clone());
player.avatar_id.replace(arg.avatar_id);
let player_info_changed = PtcPlayerInfoChangedArg {
player_uid: player.uid.unwrap(),
player_info: PlayerInfo {
nick_name: Some(arg.nick_name.clone()),
avatar_id: Some(arg.avatar_id),
..Default::default()
},
};
session.send_rpc_arg(101, &player_info_changed).await?;
session.send_rpc_ret(RpcModNickNameRet::new()).await
}

View file

@ -0,0 +1,252 @@
use qwer::{
pdkhashmap, phashmap, phashset, PropertyDoubleKeyHashMap, PropertyHashMap, PropertyHashSet,
};
use crate::game::{globals, util};
use super::*;
static UNLOCK_AVATARS: [i32; 12] = [
1011, 1021, 1031, 1041, 1061, 1081, 1091, 1101, 1111, 1121, 1131, 1141,
];
static UNLOCK_FEATURES: [i32; 35] = [
1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1013, 1014, 1015, 1016, 1017,
1018, 1019, 10001, 10002, 10003, 10004, 10005, 10006, 10007, 10008, 10009, 10010, 10012, 10013,
10014, 10015, 10017, 10018, 10019,
];
#[tracing::instrument(skip(session))]
pub async fn on_rpc_run_event_graph_arg(
session: &NetworkSession,
arg: &RpcRunEventGraphArg,
) -> Result<()> {
tracing::info!("RunEventGraph requested");
let mut ptc_sync_event_info = PtcSyncEventInfoArg {
owner_type: EventGraphOwnerType::SceneUnit,
owner_uid: arg.owner_uid,
updated_events: pdkhashmap![],
};
ptc_sync_event_info.updated_events.insert(
10000009,
100,
EventInfo {
id: 100,
cur_action_id: 101,
action_move_path: Vec::from([101, 102, 101]),
state: EventState::Initing,
prev_state: EventState::WaitingClient,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
},
);
session.send_rpc_arg(177, &ptc_sync_event_info).await?;
session.send_rpc_ret(RpcRunEventGraphRet::new()).await
}
#[tracing::instrument(skip(session))]
pub async fn on_rpc_interact_with_unit_arg(
session: &NetworkSession,
arg: &RpcInteractWithUnitArg,
) -> Result<()> {
tracing::info!("InteractWithUnit");
if arg.event_graph_id == 19900062 {
let mut ptc_sync_event_info = PtcSyncEventInfoArg {
owner_type: EventGraphOwnerType::SceneUnit,
owner_uid: arg.unit_uid,
updated_events: pdkhashmap![],
};
ptc_sync_event_info.updated_events.insert(
10000009,
100,
EventInfo {
id: 100,
cur_action_id: 101,
action_move_path: Vec::from([101]),
state: EventState::WaitingClient,
prev_state: EventState::Running,
cur_action_info: ActionInfo::None {},
cur_action_state: ActionState::Init,
predicated_failed_actions: phashset![],
stack_frames: Vec::new(),
},
);
session.send_rpc_arg(177, &ptc_sync_event_info).await?;
}
session.send_rpc_ret(RpcInteractWithUnitRet::new()).await
}
pub async fn on_rpc_leave_cur_dungeon_arg(
session: &NetworkSession,
_arg: &RpcLeaveCurDungeonArg,
) -> Result<()> {
let dungeon_manager = session.context.dungeon_manager.borrow();
if dungeon_manager.is_in_tutorial() {
Box::pin(enter_main_city(session)).await?;
}
// TODO: enter scene by back_scene_uid from cur DungeonInfo.
session.send_rpc_ret(RpcLeaveCurDungeonRet::new()).await
}
pub async fn on_ptc_player_operation_arg(
session: &NetworkSession,
_arg: &PtcPlayerOperationArg,
) -> Result<()> {
session.send_rpc_ret(PtcPlayerOperationRet::new()).await
}
#[tracing::instrument(skip(session))]
pub async fn on_rpc_save_pos_in_main_city_arg(
session: &NetworkSession,
arg: &RpcSavePosInMainCityArg,
) -> Result<()> {
tracing::info!("MainCity pos updated");
session.send_rpc_ret(RpcSavePosInMainCityRet::new()).await
}
fn create_player(id: u64) -> PlayerInfo {
let mut player = util::create_default_player(id);
let pos_in_main_city = player.pos_in_main_city.as_mut().unwrap();
pos_in_main_city.initial_pos_id.replace(2);
pos_in_main_city.position.replace(Vector3f {
x: 30.31,
y: 0.58002,
z: 11.18,
});
if globals::should_skip_tutorial() {
let beginner_procedure = player.beginner_procedure_info.as_mut().unwrap();
beginner_procedure.procedure_info.replace(6);
player.nick_name.replace(String::from("xeondev"));
player.avatar_id.replace(2021);
}
player
}
pub async fn enter_main_city(session: &NetworkSession) -> Result<()> {
let dungeon_manager = session.context.dungeon_manager.borrow();
let scene_unit_mgr = session.context.scene_unit_manager.borrow();
let hall_scene_uid = dungeon_manager.get_default_scene_uid();
session
.send_rpc_arg(
243,
dungeon_manager
.enter_scene_section(hall_scene_uid, 2)
.unwrap(),
)
.await?;
session
.send_rpc_arg(180, &scene_unit_mgr.sync(hall_scene_uid, 2))
.await?;
session
.send_rpc_arg(
118,
dungeon_manager
.enter_main_city()?
.send_changes(session)
.await?,
)
.await
}
pub async fn on_rpc_enter_world_arg(
session: &NetworkSession,
_arg: &RpcEnterWorldArg,
) -> Result<()> {
let account = session.get_account();
let id = *account.players.as_ref().unwrap().iter().next().unwrap(); // get first id from list
*session.get_player_mut() = create_player(id);
let item_manager = session.context.item_manager.borrow();
item_manager.add_resource(501, 120);
item_manager.add_resource(10, 228);
item_manager.add_resource(100, 1337);
for avatar_id in UNLOCK_AVATARS {
item_manager.unlock_avatar(avatar_id);
}
let unlock_manager = session.context.unlock_manager.borrow();
for unlock_id in UNLOCK_FEATURES {
unlock_manager.unlock(unlock_id);
}
let dungeon_manager = session.context.dungeon_manager.borrow();
dungeon_manager.create_hall(1);
let scene_unit_mgr = session.context.scene_unit_manager.borrow();
scene_unit_mgr.add_default_units();
let quest_manager = session.context.quest_manager.borrow();
quest_manager.add_world_quest(QuestInfo::MainCity {
id: 10020001,
finished_count: 0,
collection_uid: 0,
progress: 0,
parent_quest_id: 0,
state: QuestState::InProgress,
finish_condition_progress: phashmap![],
progress_time: 2111012,
sort_id: 1000,
bound_npc_and_interact: phashmap![],
});
quest_manager.add_world_quest(QuestInfo::Hollow {
id: 10010002,
finished_count: 0,
collection_uid: 3405096459205774,
progress: 0,
parent_quest_id: 0,
state: QuestState::Ready,
sort_id: 1001,
statistics: phashmap![],
statistics_ext: pdkhashmap![],
acquired_hollow_challenge_reward: 0,
progress_time: 0,
finish_condition_progress: phashmap![],
dungeon_uid: 0,
});
let yorozuya_quest_manager = session.context.yorozuya_quest_manager.borrow();
yorozuya_quest_manager.add_hollow_quest(102, HollowQuestType::SideQuest, 10010002);
if globals::should_skip_tutorial() {
Box::pin(enter_main_city(session)).await?;
} else {
let fresh_scene_uid = *dungeon_manager.create_fresh().unwrap();
session
.send_rpc_arg(
118,
dungeon_manager
.enter_scene(fresh_scene_uid)
.unwrap()
.unwrap(),
)
.await?;
}
session
.send_rpc_ret(RpcEnterWorldRet::new(
session.ns_prop_mgr.serialize_player_info(),
))
.await
}

View file

@ -0,0 +1,10 @@
use super::*;
pub async fn on_rpc_check_yorozuya_info_refresh_arg(
session: &NetworkSession,
_arg: &RpcCheckYorozuyaInfoRefreshArg,
) -> Result<()> {
session
.send_rpc_ret(RpcCheckYorozuyaInfoRefreshRet::new())
.await
}

View file

@ -0,0 +1,9 @@
pub mod gateway;
mod handlers;
mod packet;
mod session;
pub use packet::Packet;
pub use packet::RequestBody;
pub use packet::ResponseBody;
pub use session::NetworkSession;

View file

@ -0,0 +1,270 @@
use anyhow::Result;
use paste::paste;
use tokio::io::AsyncReadExt;
use tokio::net::TcpStream;
use tracing::Instrument;
use protocol::*;
use qwer::ProtocolHeader;
use super::handlers::*;
use super::NetworkSession;
pub struct Packet {
pub to_channel: u16,
pub header: ProtocolHeader,
pub body: Vec<u8>,
}
pub struct RequestBody {
pub protocol_id: u16,
pub payload: Vec<u8>,
}
pub struct ResponseBody {
pub middleware_id: u16,
pub middleware_error_code: u16,
pub payload: Vec<u8>,
}
impl Packet {
pub async fn read(stream: &mut TcpStream) -> std::io::Result<Self> {
let to_channel = stream.read_u16_le().await?;
let body_size = stream.read_u32_le().await? as usize;
let header_size = stream.read_u16_le().await? as usize;
let mut header = vec![0; header_size];
stream.read_exact(&mut header).await?;
let mut body = vec![0; body_size];
stream.read_exact(&mut body).await?;
Ok(Self {
to_channel,
header: header.into(),
body,
})
}
}
impl From<Vec<u8>> for RequestBody {
fn from(value: Vec<u8>) -> Self {
let protocol_id = u16::from_le_bytes(value[0..2].try_into().unwrap());
let payload_length = u32::from_be_bytes(value[2..6].try_into().unwrap()) as usize;
let payload = value[6..payload_length + 6].to_vec();
Self {
protocol_id,
payload,
}
}
}
impl From<RequestBody> for Vec<u8> {
fn from(value: RequestBody) -> Self {
let mut out = Self::new();
out.extend(value.protocol_id.to_le_bytes());
out.extend((value.payload.len() as u32).to_be_bytes());
out.extend(value.payload);
out
}
}
impl From<ResponseBody> for Vec<u8> {
fn from(value: ResponseBody) -> Self {
let mut out = Self::with_capacity(4 + value.payload.len());
out.extend(value.middleware_id.to_le_bytes());
out.extend(value.middleware_error_code.to_le_bytes());
out.extend(value.payload);
out
}
}
macro_rules! trait_handler {
($($name:ident $protocol_id:expr;)*) => {
#[allow(dead_code)]
#[allow(unused_variables)]
pub trait PacketHandler {
$(
paste! {
async fn [<on_$name:snake>](session: &mut NetworkSession, arg: &$name) -> Result<()> {
[<on_$name:snake>](session, arg).await
}
}
)*
async fn on_message(session: &mut NetworkSession, protocol_id: u16, payload: Vec<u8>) -> Result<()> {
use ::qwer::OctData;
match protocol_id {
$(
$protocol_id => {
let arg = $name::unmarshal_from(&mut &payload[..], 0)?;
paste! {
Self::[<on_$name:snake>](session, &arg)
.instrument(tracing::info_span!(stringify!([<on_$name:snake>]), protocol_id = protocol_id))
.await
}
}
)*
_ => {
tracing::warn!("Unknown protocol id: {protocol_id}");
Ok(())
},
}
}
}
};
}
trait_handler! {
RpcLoginArg 100;
// PtcAbilityPopText 239;
// PtcAccountInfoChanged 102;
// PtcAvatarMapChanged 246;
// PtcBeforeGoToHollowLevel 145;
// PtcCardDisable 217;
// PtcChallengeQuestFinished 193;
// PtcClientCommon 110;
// PtcConfigUpdated 277;
// PtcDungeonQuestFinished 148;
// PtcDungeonQuestPrepareToFinish 136;
// PtcEnterScene 118;
// PtcEnterSceneBegin 200;
// PtcEnterSceneEnd 224;
// PtcEnterSection 243;
// PtcFairyInfoChanged 134;
// PtcFunctionSwitchMask 279;
PtcGetServerTimestampArg 204;
// PtcGoToHollowLevel 154;
// PtcHollowBlackout 268;
// PtcHollowGlobalEvent 138;
// PtcHollowGrid 114;
// PtcHollowPushBack 284;
// PtcHollowQuestUnlockedByMainCityQuest 201;
// PtcHpOrStressChanged 226;
// PtcItemChanged 117;
// PtcKickPlayer 184;
// PtcPauseMainCityTime 116;
// PtcPlayerInfoChangedArg 101;
// PtcPlayerMailsReceived 222;
// PtcPlayerMailsRemoved 225;
PtcPlayerOperationArg 203;
// PtcPopupWindow 206;
// PtcPosition 176;
// PtcPositionInHollowChanged 141;
// PtcPrepareSection 115;
// PtcPreventAddiction 270;
// PtcPropertyChanged 129;
// PtcQuestUnlocked 158;
// PtcReceivedChatMessage_Player2Client 165;
// PtcScenePropertyChanged 128;
// PtcShowCardGenreTips 276;
// PtcShowTips 207;
// PtcShowUnlockIDTips 278;
// PtcStaminaOverLevelPunish 147;
// PtcSyncEventInfo 177;
// PtcSyncHollowEventInfo 210;
// PtcSyncHollowGridMaps 124;
// PtcSyncSceneTime 249;
// PtcSyncSceneUnit 180;
// PtcTransformToHollowGrid 144;
// PtcUnlock 196;
// RpcAFKHollowQuest 241;
RpcAdvanceBeginnerProcedureArg 171;
// RpcAvatarAdvance 111;
// RpcAvatarLevelUp 107;
// RpcAvatarSkillLevelUp 197;
// RpcAvatarStarUp 108;
// RpcAvatarUnlockTalent 199;
// RpcAwardAllPlayerMail 257;
// RpcAwardPlayerMail 256;
// RpcBattleRebegin 286;
RpcBattleReportArg 125;
// RpcBeginArchiveBattleQuest 137;
RpcBeginnerbattleBeginArg 258;
RpcBeginnerbattleEndArg 285;
// RpcBeginnerbattleRebegin 250;
// RpcBuyAutoRecoveryItem 167;
// RpcBuyVHSCollection 269;
RpcCheckYorozuyaInfoRefreshArg 245;
// RpcClickHollowSystem 282;
RpcCloseLevelChgTipsArg 244;
// RpcCreatePlayer 104;
// RpcDebugPay 216;
// RpcDelNewMap 287;
// RpcDelNewRamen 228;
// RpcDressEquipment 112;
// RpcEatRamen 283;
RpcEndBattleArg 251;
// RpcEndSlotMachine 186;
// RpcEnterSection 175;
RpcEnterWorldArg 105;
// RpcEquipDecompose 170;
// RpcEquipGacha 169;
// RpcEquipLock 172;
// RpcEquipmentLevelUp 130;
// RpcEquipmentStarUp 131;
RpcFinishACTPerformShowArg 185;
// RpcFinishBlackout 267;
// RpcFinishEventGraphPerformShow 187;
// RpcFinishGraphInClient 146;
// RpcGMCommand 113;
// RpcGacha 173;
// RpcGetArchiveReward 166;
// RpcGetAuthKey 280;
// RpcGetChatHistory_Client2Player 159;
RpcGetPlayerMailsArg 221;
// RpcGetShopInfo 122;
// RpcGetYorozuyaInfo 182;
// RpcGiveUpDungeonQuest 142;
// RpcHollowChangeAffix 143;
RpcHollowMoveArg 248;
// RpcHollowShopping 213;
RpcInteractWithUnitArg 181;
// RpcItemConvert 281;
RpcKeepAliveArg 149;
RpcLeaveCurDungeonArg 140;
// RpcLeaveWorld 190;
// RpcLogin 100;
// RpcLogout 103;
// RpcMakeChoiceOfEvent 214;
// RpcMakeInitiativeItem 234;
RpcModNickNameArg 215;
// RpcOpenVHSStore 135;
RpcPerformEndArg 255;
RpcPerformJumpArg 254;
RpcPerformTriggerArg 253;
// RpcPrepareNextHollowEnd 252;
// RpcReadPlayerMail 263;
// RpcReceiveVHSStoreReward 227;
// RpcReenterWorld 150;
// RpcRefreshShop 237;
// RpcRefreshVHSTrending 235;
// RpcRemoveHollowCurse 229;
// RpcRemovePlayerMailsFromClient 264;
RpcRunEventGraphArg 179;
RpcRunHollowEventGraphArg 211;
RpcSavePosInMainCityArg 202;
// RpcSelectChallenge 236;
// RpcSelectVHSCollection 219;
// RpcSendChatMessage_Client2Player 163;
// RpcSetBGM 273;
// RpcSetMainCityObjectState 274;
// RpcSetPlayerMailOld 265;
// RpcShopping 230;
RpcStartHollowQuestArg 183;
// RpcSwitchHollowRank 198;
// RpcUndressEquipment 109;
// RpcUseInitiativeItem 220;
// RpcWeaponDecompose 120;
// RpcWeaponDress 132;
// RpcWeaponLevelUp 126;
// RpcWeaponLock 119;
// RpcWeaponRefine 232;
// RpcWeaponStarUp 127;
// RpcWeaponUnDress 139;
// RpcYorozuyaManualReceiveReward 168;
}

View file

@ -0,0 +1,148 @@
use anyhow::Result;
use atomic_refcell::{AtomicRef, AtomicRefMut};
use protocol::*;
use qwer::{OctData, ProtocolHeader};
use std::io::Cursor;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::{Mutex, MutexGuard};
use crate::game::manager::net_stream;
use crate::game::GameContext;
use super::{packet::PacketHandler, Packet, RequestBody, ResponseBody};
pub struct NetworkSession {
client_socket: Arc<Mutex<TcpStream>>,
client_addr: SocketAddr,
cur_rpc_uid: u64,
pub ns_prop_mgr: net_stream::PropertyManager,
pub context: GameContext,
}
impl NetworkSession {
pub fn new(client_socket: TcpStream, client_addr: SocketAddr) -> Self {
let ns_prop_mgr = net_stream::PropertyManager::default();
Self {
client_socket: Arc::new(Mutex::new(client_socket)),
client_addr,
cur_rpc_uid: 0,
context: GameContext::new(ns_prop_mgr.player_info.clone()),
ns_prop_mgr,
}
}
pub fn get_player_uid(&self) -> u64 {
self.get_player().uid.unwrap()
}
pub fn get_account(&self) -> AtomicRef<AccountInfo> {
self.ns_prop_mgr.account_info.borrow()
}
pub fn get_player(&self) -> AtomicRef<PlayerInfo> {
self.ns_prop_mgr.player_info.borrow()
}
pub fn get_account_mut(&self) -> AtomicRefMut<'_, AccountInfo> {
self.ns_prop_mgr.account_info.try_borrow_mut().unwrap()
}
pub fn get_player_mut(&self) -> AtomicRefMut<'_, PlayerInfo> {
self.ns_prop_mgr.player_info.try_borrow_mut().unwrap()
}
pub async fn client_socket(&self) -> MutexGuard<'_, TcpStream> {
self.client_socket.lock().await
}
pub async fn run(&mut self) -> Result<()> {
let channel_id = match self.read_handshake().await {
Ok(channel_id) => channel_id,
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(()),
Err(e) => return Err(e.into()),
};
tracing::info!(
"Session ({}) bound to channel {channel_id}",
self.client_addr
);
loop {
let packet = match Packet::read(&mut *self.client_socket().await).await {
Ok(packet) => packet,
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => return Ok(()),
Err(e) => return Err(e.into()),
};
let request: RequestBody = packet.body.into();
self.cur_rpc_uid = packet.header.rpc_arg_uid;
Box::pin(Self::on_message(self, request.protocol_id, request.payload)).await?;
}
}
async fn read_handshake(&mut self) -> Result<u16, std::io::Error> {
self.client_socket().await.read_u16_le().await
}
pub async fn send_rpc_ret(&self, data: impl OctData) -> Result<()> {
let header = ProtocolHeader {
is_rpc_ret: true,
rpc_arg_uid: self.cur_rpc_uid,
..Default::default()
};
let mut payload = Vec::new();
let mut cursor = Cursor::new(&mut payload);
data.marshal_to(&mut cursor, 0)?;
let body: Vec<u8> = ResponseBody {
middleware_id: 0,
middleware_error_code: 0,
payload,
}
.into();
let header_buf: Vec<u8> = header.into();
let mut packet = Vec::new();
packet.extend(0_u16.to_le_bytes());
packet.extend((body.len() as u32).to_le_bytes());
packet.extend((header_buf.len() as u16).to_le_bytes());
packet.extend(header_buf);
packet.extend(body);
self.client_socket().await.write_all(&packet).await?;
Ok(())
}
pub async fn send_rpc_arg(&self, protocol_id: u16, data: &impl OctData) -> Result<()> {
let header: Vec<u8> = ProtocolHeader::default().into();
let mut payload = Vec::new();
let mut cursor = Cursor::new(&mut payload);
data.marshal_to(&mut cursor, 0)?;
let body: Vec<u8> = RequestBody {
protocol_id,
payload,
}
.into();
let mut packet = Vec::new();
packet.extend(0_u16.to_le_bytes());
packet.extend(((body.len() + 2) as u32).to_le_bytes());
packet.extend((header.len() as u16).to_le_bytes());
packet.extend(header);
packet.extend(body);
packet.extend(0_u16.to_le_bytes()); // middleware count
self.client_socket().await.write_all(&packet).await?;
tracing::info!("Ptc with protocol id {protocol_id} sent");
Ok(())
}
}
// Auto implemented
impl PacketHandler for NetworkSession {}

9
protocol/Cargo.toml Normal file
View file

@ -0,0 +1,9 @@
[package]
name = "protocol"
edition = "2021"
version.workspace = true
[dependencies]
byteorder.workspace = true
hex.workspace = true
qwer.workspace = true

585
protocol/src/enums.rs Normal file
View file

@ -0,0 +1,585 @@
use super::*;
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum ErrorCode {
Fail = -1,
Success = 0,
}
#[derive(OctData, Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum HollowQuestType {
Common = 0,
MainQuest = 1,
SideQuest = 2,
Urgent = 3,
UrgentSupplement = 4,
Challenge = 5,
ChallengeChaos = 6,
AvatarSide = 7,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum FairyState {
Unlock = 0,
Close = 1,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum FightRanking {
None = 0,
D = 1,
C = 2,
B = 3,
A = 4,
S = 5,
}
#[derive(OctData, Hash, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum BattleRewardType {
Client = 1,
BattleEvt = 2,
Ext = 3,
Fight = 4,
Challenge = 5,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum MailState {
New = 0,
Old = 1,
Read = 2,
Awarded = 3,
Removed = 4,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum HollowBattleEventType {
Default = 0,
Normal = 1,
Elite = 2,
Boss = 3,
LevelEnd = 4,
LevelFin = 5,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum QuestType {
ArchiveFile = 1,
DungeonInner = 2,
Hollow = 3,
Manual = 4,
MainCity = 5,
HollowChallenge = 6,
ArchiveBattle = 7,
Knowledge = 8,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum EventState {
Initing = 0,
Running = 1,
Pause = 2,
WaitingMsg = 3,
WaitingClient = 4,
Finished = 5,
Error = 6,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum ActionState {
Init = 0,
Running = 1,
Finished = 2,
Error = 3,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum DungeonContentDropPoolType {
Card = 0,
BaneCard = 1,
Arcana = 2,
Blessing = 3,
Curse = 4,
Reward = 5,
HollowItem = 6,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum ReportType {
Fairy = 0,
Dialog = 1,
Task = 2,
DialogInFairy = 3,
}
#[derive(OctData, Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u16)]
pub enum UIType {
Default = 0,
None = 1,
HollowQuest = 2,
Archive = 3,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum ACTPerformShowMoment {
Begin = 0,
End = 1,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum HollowSystemType {
Card = 1,
Menu = 2,
Curse = 3,
Bag = 4,
HollowItem = 5,
HollowResultPage = 6,
CurseInfo = 7,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq)]
#[repr(i16)]
pub enum HollowSystemUIState {
Normal = 0,
Close = 1,
Brighten = 2,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum HollowShopType {
All = 0,
Item = 1,
Card = 2,
Curse = 3,
HollowItem = 4,
Discount = 5,
Gachashop = 6,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum TimePeriodType {
Random = 0,
Morning = 1,
Evening = 2,
Night = 3,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum WeatherType {
None = -1,
Random = 0,
SunShine = 1,
Fog = 2,
Cloudy = 3,
Rain = 4,
Thunder = 5,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum PropertyType {
Hp = 1,
Armor = 2,
Shield = 3,
Stun = 4,
Sp = 5,
Usp = 6,
Dead = 99,
HpMax = 111,
ArmorMax = 112,
ShieldMax = 113,
StunMax = 114,
SpMax = 115,
UspMax = 116,
Atk = 121,
BreakStun = 122,
Def = 131,
Crit = 201,
CritRes = 202,
CritDmg = 211,
CritDmgRes = 212,
Pen = 231,
PenValue = 232,
Endurance = 301,
SpRecover = 305,
HpHealRatio = 306,
AddedDamageRatio = 307,
HpMaxBattle = 1111,
ArmorMaxBattle = 1112,
ShieldMaxBattle = 1113,
StunMaxBattle = 1114,
SpBattle = 1115,
// UspBattle = 1115,
AtkBattle = 1121,
BreakStunBattle = 1122,
DefBattle = 1131,
CritBattle = 1201,
CritResBattle = 1202,
CritDmgBattle = 1211,
CritDmgResBattle = 1212,
PenRatioBattle = 1231,
PenDeltaBattle = 1232,
EnduranceBattle = 1301,
SpRecoverBattle = 1305,
HpHealRatioBattle = 1306,
AddedDamageRatioBattle = 1307,
HpMaxBase = 11101,
ArmorMaxBase = 11201,
ShieldMaxBase = 11301,
AtkBase = 12101,
DefBase = 13101,
CritBase = 20101,
CritResBase = 20201,
CritDmgBase = 21101,
CritDmgResBase = 21201,
PenBase = 23101,
PenValueBase = 23201,
BreakStunBase = 12201,
StunMaxBase = 11401,
SpMaxBase = 11501,
EnduranceBase = 30101,
UspMaxBase = 11601,
SpRecoverBase = 30501,
HpHealRatio1 = 30601,
AddedDamageRatio1 = 30701,
HpMaxRatio = 11102,
ArmorMaxRatio = 11202,
ShieldMaxRatio = 11302,
AtkRatio = 12102,
DefRatio = 13102,
BreakStunRatio = 12202,
StunMaxRatio = 11402,
EnduranceRatio = 30102,
SpRecoverRatio = 30502,
HpMaxDelta = 11103,
ArmorMaxDelta = 11203,
ShieldMaxDelta = 11303,
AtkDelta = 12103,
DefDelta = 13103,
BreakStunDelta = 12203,
StunMaxDelta = 11403,
SpMaxDelta = 11503,
CritDelta = 20103,
CritResDelta = 20203,
CritDmgDelta = 21103,
CritDmgResDelta = 21203,
UspMaxDelta = 11603,
PenDelta = 23103,
PenValueDelta = 23203,
EnduranceDelta = 30103,
SpRecoverDelta = 30503,
HpHealRatio3 = 30603,
AddedDamageRatio3 = 30703,
HpMaxRatioRL = 11104,
ArmorMaxRatioRL = 11204,
ShieldMaxRatioRL = 11304,
AtkRatioRL = 12104,
DefRatioRL = 13104,
HpMaxDeltaRL = 11105,
ArmorMaxDeltaRL = 11205,
ShieldMaxDeltaRL = 11305,
AtkDeltaRL = 12105,
DefDeltaRL = 13105,
CritRL = 20105,
CritResRL = 20205,
CritDmgRL = 21105,
CritDmgResRL = 21205,
PenRatioRL = 23105,
PenDeltaRL = 23205,
BreakStunRatioRL = 12204,
BreakStunDeltaRL = 12205,
StunMaxRatioRL = 11404,
// StunMaxDeltaRL = 11404,
SpMaxDeltaRL = 11505,
UspMaxDeltaRL = 11605,
EnduranceRatioRL = 30104,
EnduranceDeltaRL = 30105,
SpRecoverRatioRL = 30504,
SpRecoverDeltaRL = 30505,
HpHealRatioRL = 30605,
AddedDamageRatioRL = 30705,
MapHpreserveMaxhp = 10320,
MapHpreserveCurhp = 10330,
MapHpreserveAbsolute = 10340,
ActorMaxCurHP = 10350,
EnumCount = 10351,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(i16)]
pub enum ScenePropertyType {
Stamina = 1001,
StaminaMax = 1002,
StaminaRatio = 1003,
StaminaDelta = 1004,
GoldRatio = 1005,
GoldDelta = 1006,
CardRWeight = 1007,
CardRWeightRatio = 1008,
CardSRWeight = 1009,
CardSRWeightRatio = 1010,
CardSSRWeight = 1011,
CardSSRWeightRatio = 1012,
Mobility = 1013,
BuffTurn = 1014,
ForbiddenStamina = 1015,
ForbiddenGold = 1016,
OptionNum = 1017,
ShopPrice = 1018,
StaminaIncrease = 1019,
StaminaOverLevel = 1020,
DropRate = 1021,
BanCharacter1 = 1022,
BanCharacter2 = 1023,
BanCharacter3 = 1024,
PlayerView = 1025,
ActorAddedDamageRatio = 1030,
ActorDamageTakeRatio = 1031,
MapHpreserveMaxhp = 1032,
MapHpreserveCurhp = 1033,
MapHpreserveAbsolute = 1034,
ActorMaxCurHP = 1035,
ShopPriceDelta = 1036,
ShopPriceOverwriteCard = 1037,
ShopPriceOverwriteItem = 1038,
CardOptionHideNum = 1039,
CardOptionForbidNum = 1040,
HealingRatio = 1041,
DinyRatio = 1042,
Weather = 1043,
TimePeriod = 1044,
ShopPriceOverwriteCurse = 1045,
ShopPriceOverwriteHollowItem = 1046,
ShopPriceOverwriteDiscount = 1047,
ShopPriceOverwriteGachashop = 1048,
}
macro_rules! flag {
($repr:ty, $(#[$attr:meta])* $name:ident { $($flag:ident = $value:expr,)* }) => {
$(#[$attr])*
#[repr($repr)]
pub enum $name {
$($flag = $value,)*
}
impl From<$name> for $repr {
fn from(flag: $name) -> $repr {
flag as $repr
}
}
impl From<$repr> for $name {
fn from(flag: $repr) -> $name {
match flag {
$($value => $name::$flag,)*
_ => panic!("invalid flag value: {}", flag),
}
}
}
impl $name {
#[must_use]
pub fn unpack_flags(val: $repr) -> Vec<$name> {
let mut flags = Vec::new();
let mut val = val;
let mut i = 0 as $repr;
while val > 0 {
if val & 1 == 1 {
flags.push(unsafe { std::mem::transmute(i) });
}
val >>= 1;
i += 1;
}
flags
}
#[must_use]
pub fn pack_flags(flags: &[Self]) -> $repr {
flags.iter().fold(0, |acc, &flag| acc | (flag as $repr))
}
}
};
}
flag! {
u32,
#[derive(OctData, Clone, Debug, Copy)]
HollowGridFlag {
Core = 1,
CanMove = 2,
Travelled = 4,
ShowEventType = 8,
ShowEventID = 16,
CanTriggerEvent = 32,
Visible = 64,
VisibleAtGridAround = 128,
VisibleByTriggerEvent = 256,
SyncToClient = 512,
Door = 1024,
CanTriggerMultiTimes = 2048,
TemporaryVisibleAtAround = 4096,
Unlocked = 8192,
Brighten = 16384,
Guide = 32768,
Target = 65536,
BrightenOnlyVisible = 131072,
Unstable = 262144,
}
}
flag! {
u8,
#[derive(OctData, Clone, Debug, Copy)]
HollowGridLink {
None = 0,
Up = 1,
Down = 2,
Right = 4,
Left = 8,
All = 15,
}
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum NodeState {
All = 0,
Locked = 1,
Unlocked = 2,
Finished = 3,
ShowEvent = 4,
Door = 5,
Brighten = 6,
Guide = 7,
Target = 8,
BrightenOnlyVisible = 9,
Unstable = 10,
EnumCount = 11,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum NodeVisible {
All = 0,
Visible = 1,
VisibleAtGridAround = 2,
VisibleByTriggerEvent = 3,
TemporaryVisibleAtAround = 4,
EnumCount = 5,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum HollowEventType {
None = 0,
All = 1,
Begin = 10,
End = 20,
InteractEnd = 21,
BattleEnd = 22,
ChangeLevelInteract = 23,
ChangeLevelFight = 24,
Battle = 30,
BattleNormal = 31,
BattleElite = 32,
BattleBoss = 33,
Dialog = 40,
DialogPositive = 41,
DialogNegative = 42,
DialogSpecial = 43,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum HollowShopCurrency {
Coin = 1,
Curse = 2,
Random = 3,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum QuestState {
Unlocked = 0,
Ready = 10,
InProgress = 1,
ToFinish = 2,
Finished = 3,
}
#[derive(OctData, Clone, Debug, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum QuestStatisticsType {
ArrivedLevel = 1,
EventCount = 2,
CostTime = 3,
KilledEnemyCount = 4,
ArcanaCount = 5,
TarotCardCount = 6,
StaminaOverLevelTimes = 7,
RebornTimes = 8,
FinishedEventTypeCount = 9,
FinishedEventIDCount = 10,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum System {
HollowQuestUI = 0,
VHSUI = 1,
RoleUI = 2,
SmithyUI = 3,
PackageUI = 4,
TeleportUI = 5,
YorozuyaManualUI = 6,
VHSStoreUI = 7,
RamenUI = 8,
WorkbenchUI = 9,
GroceryUI = 10,
VideoshopUI = 11,
SwitchOfStoryMode = 12,
SwitchOfQTE = 13,
LineupSelect = 14,
UseStoryMode = 15,
UseManualQTEMode = 16,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum InteractTarget {
NPC = 0,
TriggerBox = 1,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum EventGraphOwnerType {
Scene = 0,
Section = 1,
SceneUnit = 2,
Hollow = 3,
}
#[derive(OctData, Clone, Debug)]
#[repr(i16)]
pub enum Operator {
Enter = 0,
}

13
protocol/src/lib.rs Normal file
View file

@ -0,0 +1,13 @@
use std::collections::{HashMap, HashSet};
use qwer::{OctData, PropertyDoubleKeyHashMap, PropertyHashMap, PropertyHashSet};
mod enums;
mod polymorphic;
mod rpc_ptc;
mod structs;
pub use enums::*;
pub use polymorphic::*;
pub use rpc_ptc::*;
pub use structs::*;

905
protocol/src/polymorphic.rs Normal file
View file

@ -0,0 +1,905 @@
use super::*;
macro_rules! polymorphic_scene_unit_protocol_info {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 2]
pub enum $name {
$(
$variant {
uid: u64,
tag: i32,
$($field: $ty),*
} = $tag,
)*
}
impl $name {
#[must_use]
pub const fn get_uid(&self) -> u64 {
match self {
$(
$name::$variant { uid, .. } => *uid,
)*
}
}
pub fn set_uid(&mut self, uid: u64) {
match self {
$(
$name::$variant { uid: ref mut u, .. } => *u = uid,
)*
}
}
#[must_use]
pub const fn get_tag(&self) -> i32 {
match self {
$(
$name::$variant { tag, .. } => *tag,
)*
}
}
pub fn set_tag(&mut self, tag: i32) {
match self {
$(
$name::$variant { tag: ref mut t, .. } => *t = tag,
)*
}
}
}
};
}
macro_rules! polymorphic_scene_info {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 11]
pub enum $name {
$(
$variant {
uid: u64,
id: i32,
dungeon_uid: u64,
end_timestamp: u64,
back_scene_uid: u64,
entered_times: u16,
section_id: i32,
open_ui: UIType,
to_be_destroyed: bool,
camera_x: u32,
camera_y: u32,
$($field: $ty),*
} = $tag,
)*
}
impl $name {
#[must_use]
pub const fn get_uid(&self) -> u64 {
match self {
$(
$name::$variant { uid, .. } => *uid,
)*
}
}
pub fn set_uid(&mut self, uid: u64) {
match self {
$(
$name::$variant { uid: ref mut u, .. } => *u = uid,
)*
}
}
#[must_use]
pub const fn get_id(&self) -> i32 {
match self {
$(
$name::$variant { id, .. } => *id,
)*
}
}
pub fn set_id(&mut self, id: i32) {
match self {
$(
$name::$variant { id: ref mut i, .. } => *i = id,
)*
}
}
#[must_use]
pub const fn get_dungeon_uid(&self) -> u64 {
match self {
$(
$name::$variant { dungeon_uid, .. } => *dungeon_uid,
)*
}
}
pub fn set_dungeon_uid(&mut self, dungeon_uid: u64) {
match self {
$(
$name::$variant { dungeon_uid: ref mut d, .. } => *d = dungeon_uid,
)*
}
}
#[must_use]
pub const fn get_end_timestamp(&self) -> u64 {
match self {
$(
$name::$variant { end_timestamp, .. } => *end_timestamp,
)*
}
}
pub fn set_end_timestamp(&mut self, end_timestamp: u64) {
match self {
$(
$name::$variant { end_timestamp: ref mut e, .. } => *e = end_timestamp,
)*
}
}
#[must_use]
pub const fn get_back_scene_uid(&self) -> u64 {
match self {
$(
$name::$variant { back_scene_uid, .. } => *back_scene_uid,
)*
}
}
pub fn set_back_scene_uid(&mut self, back_scene_uid: u64) {
match self {
$(
$name::$variant { back_scene_uid: ref mut b, .. } => *b = back_scene_uid,
)*
}
}
#[must_use]
pub const fn get_entered_times(&self) -> u16 {
match self {
$(
$name::$variant { entered_times, .. } => *entered_times,
)*
}
}
pub fn set_entered_times(&mut self, entered_times: u16) {
match self {
$(
$name::$variant { entered_times: ref mut e, .. } => *e = entered_times,
)*
}
}
#[must_use]
pub const fn get_section_id(&self) -> i32 {
match self {
$(
$name::$variant { section_id, .. } => *section_id,
)*
}
}
pub fn set_section_id(&mut self, section_id: i32) {
match self {
$(
$name::$variant { section_id: ref mut s, .. } => *s = section_id,
)*
}
}
#[must_use]
pub const fn get_open_ui(&self) -> UIType {
match self {
$(
$name::$variant { open_ui, .. } => *open_ui,
)*
}
}
pub fn set_open_ui(&mut self, open_ui: UIType) {
match self {
$(
$name::$variant { open_ui: ref mut o, .. } => *o = open_ui,
)*
}
}
#[must_use]
pub const fn get_to_be_destroyed(&self) -> bool {
match self {
$(
$name::$variant { to_be_destroyed, .. } => *to_be_destroyed,
)*
}
}
pub fn set_to_be_destroyed(&mut self, to_be_destroyed: bool) {
match self {
$(
$name::$variant { to_be_destroyed: ref mut t, .. } => *t = to_be_destroyed,
)*
}
}
#[must_use]
pub const fn get_camera_x(&self) -> u32 {
match self {
$(
$name::$variant { camera_x, .. } => *camera_x,
)*
}
}
pub fn set_camera_x(&mut self, camera_x: u32) {
match self {
$(
$name::$variant { camera_x: ref mut c, .. } => *c = camera_x,
)*
}
}
#[must_use]
pub const fn get_camera_y(&self) -> u32 {
match self {
$(
$name::$variant { camera_y, .. } => *camera_y,
)*
}
}
pub fn set_camera_y(&mut self, camera_y: u32) {
match self {
$(
$name::$variant { camera_y: ref mut c, .. } => *c = camera_y,
)*
}
}
}
};
}
macro_rules! polymorphic_item_info {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 5]
pub enum $name {
$(
$variant {
uid: u64,
id: i32,
count: i32,
package: u16,
first_get_time: u64,
$($field: $ty),*
} = $tag,
)*
}
impl $name {
#[must_use]
pub const fn get_uid(&self) -> u64 {
match self {
$(
$name::$variant { uid, .. } => *uid,
)*
}
}
pub fn set_uid(&mut self, uid: u64) {
match self {
$(
$name::$variant { uid: ref mut u, .. } => *u = uid,
)*
}
}
#[must_use]
pub const fn get_id(&self) -> i32 {
match self {
$(
$name::$variant { id, .. } => *id,
)*
}
}
pub fn set_id(&mut self, id: i32) {
match self {
$(
$name::$variant { id: ref mut i, .. } => *i = id,
)*
}
}
#[must_use]
pub const fn get_count(&self) -> i32 {
match self {
$(
$name::$variant { count, .. } => *count,
)*
}
}
pub fn set_count(&mut self, count: i32) {
match self {
$(
$name::$variant { count: ref mut c, .. } => *c = count,
)*
}
}
#[must_use]
pub const fn get_package(&self) -> u16 {
match self {
$(
$name::$variant { package, .. } => *package,
)*
}
}
pub fn set_package(&mut self, package: u16) {
match self {
$(
$name::$variant { package: ref mut p, .. } => *p = package,
)*
}
}
#[must_use]
pub const fn get_first_get_time(&self) -> u64 {
match self {
$(
$name::$variant { first_get_time, .. } => *first_get_time,
)*
}
}
pub fn set_first_get_time(&mut self, first_get_time: u64) {
match self {
$(
$name::$variant { first_get_time: ref mut f, .. } => *f = first_get_time,
)*
}
}
}
};
}
macro_rules! polymorphic_dungeon_table_ext {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 0]
pub enum $name {
$(
$variant {
$($field: $ty),*
} = $tag,
)*
}
};
}
macro_rules! polymorphic_scene_table_ext {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 1]
pub enum $name {
$(
$variant {
event_graphs_info: EventGraphsInfo,
$($field: $ty),*
} = $tag,
)*
}
impl $name {
#[must_use]
pub const fn get_event_graphs_info(&self) -> &EventGraphsInfo {
match self {
$(
$name::$variant { event_graphs_info, .. } => event_graphs_info,
)*
}
}
pub fn set_event_graphs_info(&mut self, event_graphs_info: EventGraphsInfo) {
match self {
$(
$name::$variant { event_graphs_info: ref mut e, .. } => *e = event_graphs_info,
)*
}
}
}
};
}
macro_rules! polymorphic_section_info_ext {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 1]
pub enum $name {
$(
$variant {
destroy_npc_when_no_player: PropertyHashSet<u64>,
$($field: $ty),*
} = $tag,
)*
}
impl $name {
#[must_use]
pub const fn get_destroy_npc_when_no_player(&self) -> &PropertyHashSet<u64> {
match self {
$(
$name::$variant { destroy_npc_when_no_player, .. } => destroy_npc_when_no_player,
)*
}
}
pub fn set_destroy_npc_when_no_player(&mut self, destroy_npc_when_no_player: PropertyHashSet<u64>) {
match self {
$(
$name::$variant { destroy_npc_when_no_player: ref mut d, .. } => *d = destroy_npc_when_no_player,
)*
}
}
}
};
}
macro_rules! polymorphic_action_info {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 0]
pub enum $name {
$(
$variant {
$($field: $ty),*
} = $tag,
)*
#[polymorphic_none]
None {} = 0xFFFF, // weird detail, polymorphism can be empty.
}
};
}
macro_rules! polymorphic_event_graph_info {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 6]
pub enum $name {
$(
$variant {
config_id: i32,
events_info: PropertyHashMap<i32, EventInfo>,
specials: PropertyHashMap<String, u64>,
is_new: bool,
finished: bool,
list_specials: PropertyHashMap<String, Vec<u64>>,
$($field: $ty),*
} = $tag,
)*
}
impl $name {
#[must_use]
pub const fn get_config_id(&self) -> i32 {
match self {
$(
$name::$variant { config_id, .. } => *config_id,
)*
}
}
pub fn set_config_id(&mut self, config_id: i32) {
match self {
$(
$name::$variant { config_id: ref mut c, .. } => *c = config_id,
)*
}
}
#[must_use]
pub const fn get_events_info(&self) -> &PropertyHashMap<i32, EventInfo> {
match self {
$(
$name::$variant { events_info, .. } => events_info,
)*
}
}
pub fn set_events_info(&mut self, events_info: PropertyHashMap<i32, EventInfo>) {
match self {
$(
$name::$variant { events_info: ref mut e, .. } => *e = events_info,
)*
}
}
#[must_use]
pub const fn get_specials(&self) -> &PropertyHashMap<String, u64> {
match self {
$(
$name::$variant { specials, .. } => specials,
)*
}
}
pub fn set_specials(&mut self, specials: PropertyHashMap<String, u64>) {
match self {
$(
$name::$variant { specials: ref mut s, .. } => *s = specials,
)*
}
}
#[must_use]
pub const fn get_is_new(&self) -> bool {
match self {
$(
$name::$variant { is_new, .. } => *is_new,
)*
}
}
pub fn set_is_new(&mut self, is_new: bool) {
match self {
$(
$name::$variant { is_new: ref mut i, .. } => *i = is_new,
)*
}
}
#[must_use]
pub const fn get_finished(&self) -> bool {
match self {
$(
$name::$variant { finished, .. } => *finished,
)*
}
}
pub fn set_finished(&mut self, finished: bool) {
match self {
$(
$name::$variant { finished: ref mut f, .. } => *f = finished,
)*
}
}
#[must_use]
pub const fn get_list_specials(&self) -> &PropertyHashMap<String, Vec<u64>> {
match self {
$(
$name::$variant { list_specials, .. } => list_specials,
)*
}
}
pub fn set_list_specials(&mut self, list_specials: PropertyHashMap<String, Vec<u64>>) {
match self {
$(
$name::$variant { list_specials: ref mut l, .. } => *l = list_specials,
)*
}
}
}
};
}
macro_rules! polymorphic_quest_info {
(enum $name:ident { $($variant:ident { $($field:ident: $ty:ty),* $(,)? } = $tag:expr,)* }) => {
#[derive(OctData, Clone, Debug)]
#[repr(u16)]
#[base = 9]
pub enum $name {
$(
$variant {
id: i32,
finished_count: i32,
collection_uid: u64,
progress: u16,
parent_quest_id: i32,
state: QuestState,
finish_condition_progress: PropertyHashMap<i32, i32>,
progress_time: u32,
sort_id: u64,
$($field: $ty),*
} = $tag,
)*
}
impl $name {
#[must_use]
pub const fn get_id(&self) -> i32 {
match self {
$(
$name::$variant { id, .. } => *id,
)*
}
}
pub fn set_id(&mut self, id: i32) {
match self {
$(
$name::$variant { id: ref mut c, .. } => *c = id,
)*
}
}
#[must_use]
pub const fn get_collection_uid(&self) -> u64 {
match self {
$(
$name::$variant { collection_uid, .. } => *collection_uid,
)*
}
}
pub fn set_collection_uid(&mut self, collection_uid: u64) {
match self {
$(
$name::$variant { collection_uid: ref mut c, .. } => *c = collection_uid,
)*
}
}
}
};
}
polymorphic_scene_unit_protocol_info! {
enum SceneUnitProtocolInfo {
NpcProtocolInfo {
id: i32,
quest_id: i32,
interacts_info: PropertyHashMap<i32, InteractInfo>,
} = 0,
}
}
polymorphic_scene_info! {
enum SceneInfo {
Fight {
perform_show_progress: PropertyHashMap<ACTPerformShowMoment, u8>,
end_hollow: bool,
random_seed: i32,
} = 3,
Fresh {} = 4,
Hall {
// main_city_time_info: MainCityTimeInfo,
} = 1,
Hollow {
event_variables: PropertyHashMap<String, i32>,
buddy: BuddyUnitInfo,
stress_punish_ability_random_pool: Vec<String>,
finished: bool,
event_weight_factor: PropertyHashMap<i32, i32>,
shop_modification: HollowShopModification,
last_challenge_stat: PropertyHashMap<i32, u8>,
cur_challenge: PropertyHashSet<i32>,
hollow_system_switch: PropertyHashMap<HollowSystemType, bool>,
sections_info: PropertyHashMap<i32, PlayerHollowSectionInfo>,
executing_event: bool,
event_id: i32,
hollow_event_graph_uid: u64,
on_battle_success: String,
on_battle_failure: String,
battle_finished: bool,
battle_success: bool,
battle_scene_uid: u64,
//event_graphs_info: PropertyHashMap<u64, HollowEventGraphInfo>,
scene_global_events: PropertyHashMap<i32, u64>,
prepare_section: PrepareSection,
abilities_info: AbilitiesInfo,
blackout: bool,
hollow_system_ui_state: PropertyHashMap<HollowSystemType, HollowSystemUIState>,
} = 2,
}
}
polymorphic_item_info! {
enum ItemInfo {
Arcana {
affix_list: Vec<i32>,
dress_index: u8,
} = 33,
Avatar {
star: u8,
exp: u32,
level: u8,
rank: u8,
unlocked_talent_num: u8,
skills: PropertyHashMap<u8, u8>,
is_custom_by_dungeon: bool,
robot_id: i32,
} = 3,
AvatarLevelUpMaterial { } = 12,
AvatarPiece { } = 4,
Bless {
remain_time: i32,
get_time: u64,
ban_character: Vec<i32>,
specials: PropertyHashMap<String, i32>,
slot: u8,
is_super_curse: bool,
} = 32,
Buddy { } = 8,
Consumable { } = 10,
Currency { } = 1,
Equip {
avatar_uid: u64,
avatar_dressed_index: u8,
rand_properties: Vec<PropertyKeyValue>,
star: u8,
exp: u32,
leve: u8,
lock: u8,
base_rand_properties: Vec<PropertyKeyValue>,
rand_properties_lv: Vec<i32>,
} = 7,
EquipLevelUpMaterial { } = 14,
Gift { } = 51,
HollowItem { } = 15,
OptionalGift { } = 52,
Resource { } = 2,
TarotCard {
is_mute: bool,
specials: PropertyHashMap<String, i32>,
} = 31,
Useable { } = 11,
Weapon {
avatar_uid: u64,
star: u8,
exp: u32,
level: u8,
lock: u8,
refine_level: u8,
} = 5,
WeaponLevelUpMaterial { } = 13,
}
}
polymorphic_dungeon_table_ext! {
enum DungeonTableExt {
Hall {} = 1,
Hollow {
avatars: PropertyHashSet<HollowDungeonAvatarInfo>,
scene_properties_uid: u64,
buddy: HollowDungeonBuddyInfo,
} = 2,
}
}
polymorphic_section_info_ext! {
enum SectionInfoExt {
Hall {} = 1,
Hollow {
hollow_level_info: HollowLevelInfo,
hollow_grid_map_info: HollowGridMapInfo,
} = 0,
}
}
polymorphic_scene_table_ext! {
enum SceneTableExt {
Fight {} = 3,
Fresh {} = 4,
Hall {} = 1,
Hollow {
grid_random_seed: i32,
alter_section_id: i32,
} = 2,
}
}
polymorphic_action_info! {
enum ActionInfo {
ServerChoices {
choices: Vec<ChoiceInfo>,
finished: bool,
} = 52,
DropHollowItem {
drop_item: i32,
} = 162,
FinishBlackout {
finished: bool,
show_tips: bool,
} = 133,
Loop {
loop_times: u16,
} = 141,
Perform {
step: u8,
r#return: PropertyHashMap<String, i32>,
} = 23,
PrepareNextHollow {
section_id: i32,
finished: bool,
show_other: bool,
main_map: HollowGridMapProtocolInfo,
} = 130,
ActionRandomChallenge {
choices: Vec<i32>,
choice_result: i32,
finished: bool,
} = 109,
RemoveCurse {
curse_can_remove: Vec<u64>,
to_remove_num: u8,
choosed: bool,
} = 105,
SetHollowSystemState {
finished: bool,
} = 134,
Shop {
shop_info: PropertyHashMap<HollowShopType, ConfigShopInfo>,
finished: bool,
} = 62,
SlotMachine {
indexes: Vec<i32>,
index: i32,
finished: bool,
} = 131,
TriggerBattle {
next_action_id: i32,
finished: bool,
} = 56,
}
}
polymorphic_event_graph_info! {
enum EventGraphInfo {
Hollow {
fired_count: u8,
hollow_event_template_id: i32,
uid: u64,
is_created_by_gm: bool,
} = 3,
NPC {
sequence_of_group: u16,
section_list_events: PropertyHashMap<String, EventListenerInfo>,
interact_info: InteractInfo,
hide: bool,
} = 2,
Section { } = 1,
}
}
polymorphic_quest_info! {
enum QuestInfo {
ArchiveBattle {
statistics: PropertyHashMap<QuestStatisticsType, u64>,
dungeon_uid: u64,
star: u8,
} = 7,
ArchiveFile { } = 1,
Challenge { } = 6,
DungeonInner { } = 2,
Hollow {
statistics: PropertyHashMap<QuestStatisticsType, u64>,
dungeon_uid: u64,
statistics_ext: PropertyDoubleKeyHashMap<QuestStatisticsType, i32, i32>,
acquired_hollow_challenge_reward: i32,
} = 3,
Knowledge { } = 8,
MainCity {
bound_npc_and_interact: PropertyHashMap<u64, BoundNPCAndInteractInfo>,
} = 5,
Manual { } = 4,
}
}

403
protocol/src/rpc_ptc.rs Normal file
View file

@ -0,0 +1,403 @@
use super::*;
// :skull:
macro_rules! ret {
(struct $name:ident $(< $lt:lifetime >)? {
$(
$(#[$attr:meta])*
$field:ident: $ty:ty,
)*
}) => {
#[derive(OctData)]
pub struct $name $(< $lt >)? {
pub error_code: ErrorCode,
pub error_code_params: Vec<String>,
$(
$(#[$attr])*
pub $field: $ty,
)*
}
impl $(< $lt >)? $name $(< $lt >)? {
#[must_use]
pub const fn new($($field: $ty,)*) -> Self {
Self {
error_code: ErrorCode::Success,
error_code_params: Vec::new(),
$($field,)*
}
}
#[must_use]
pub fn error(error_code: ErrorCode, error_code_params: Vec<String>) -> Self {
Self {
error_code,
error_code_params,
$($field: Default::default(),)*
}
}
}
impl $(< $lt >)? Default for $name $(< $lt >)? {
fn default() -> Self {
Self {
error_code: ErrorCode::Success,
error_code_params: Vec::new(),
$($field: Default::default(),)*
}
}
}
};
($(struct $name:ident $(< $lt:lifetime >)? { $($field:tt)* })+) => {
$(ret!(struct $name $(< $lt >)? { $($field)* });)+
};
}
#[derive(OctData)]
pub struct RpcLoginArg {
pub account_name: String,
pub token: String,
pub client_protocol_sign: String,
pub config_sign: String,
}
#[derive(OctData, Clone, Debug)]
pub struct PtcEnterSceneArg {
pub player_uid: u64,
pub scene_uid: u64,
pub ext: SceneTableExt,
pub entered_times: u16,
pub section_id: i32,
pub transform: Transform,
pub open_ui: UIType,
pub condition_config_ids: Vec<i32>,
pub timestamp: u64,
pub camera_x: u32,
pub camera_y: u32,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcEnterWorldArg {}
#[derive(OctData, Clone, Debug)]
pub struct RpcGetPlayerMailsArg {}
#[derive(OctData, Clone, Debug)]
pub struct PtcUnlockArg {
pub unlock_id: i32,
}
#[derive(OctData, Clone, Debug)]
pub struct PtcGetServerTimestampArg {}
#[derive(OctData, Clone, Debug)]
pub struct RpcAdvanceBeginnerProcedureArg {
pub player_uid: u64,
pub procedure_id: i32,
pub params: i32,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcPerformTriggerArg {
pub perform_id: i32,
pub perform_type: i32,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcPerformEndArg {
pub perform_id: i32,
pub perform_type: i32,
pub perform_uid: String,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcModNickNameArg {
pub nick_name: String,
pub avatar_id: i32,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcFinishACTPerformShowArg {
pub moment: ACTPerformShowMoment,
pub step: u8,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcKeepAliveArg {}
#[derive(OctData, Clone, Debug)]
pub struct RpcPerformJumpArg {
pub perform_id: i32,
pub perform_type: i32,
pub perform_uid: String,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcBeginnerbattleBeginArg {
pub battle_id: i32,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcBattleReportArg {
pub battle_reports: Vec<BattleReport>,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcBeginnerbattleEndArg {
pub battle_id: i32,
pub battle_uid: String,
pub battle_statistics: LogBattleStatistics,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcLeaveCurDungeonArg {
pub player_uid: u64,
pub dungeon_uid: u64,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcSavePosInMainCityArg {
pub position: Vector3f,
pub rotation: Vector3f,
}
#[derive(OctData, Clone, Debug)]
pub struct RpcCloseLevelChgTipsArg {}
#[derive(OctData, Clone, Debug)]
pub struct PtcPlayerInfoChangedArg {
pub player_uid: u64,
#[property_blob]
pub player_info: PlayerInfo,
}
#[derive(OctData, Clone, Debug)]
pub struct PtcPlayerOperationArg {
pub system: System,
pub operator: Operator,
pub param: i32,
}
#[derive(OctData, Debug)]
pub struct PtcScenePropertyChangedArg {
pub player_uid: u64,
pub is_partial: bool,
pub changed_properties: PropertyHashMap<u16, i32>,
}
#[derive(OctData, Debug)]
pub struct PtcPropertyChangedArg {
pub scene_unit_uid: u64,
pub is_partial: bool,
pub changed_properties: PropertyHashMap<u16, i32>,
}
#[derive(OctData, Debug)]
pub struct PtcSyncSceneUnitArg {
pub scene_uid: u64,
pub section_id: i32,
pub is_partial: bool,
pub removed_scene_units: Vec<u64>,
pub scene_units: Vec<SceneUnitProtocolInfo>,
}
#[derive(OctData, Debug)]
pub struct PtcEnterSectionArg {
pub section_id: i32,
}
#[derive(OctData, Debug)]
pub struct RpcRunEventGraphArg {
pub owner_type: EventGraphOwnerType,
pub owner_uid: u64,
pub event_graph_id: i32,
pub event_id: i32,
pub move_path: Vec<i32>,
}
#[derive(OctData, Debug)]
pub struct RpcInteractWithUnitArg {
pub unit_uid: u64,
pub unit_type: InteractTarget,
pub event_graph_id: i32,
pub interaction: u16,
}
#[derive(OctData, Debug)]
pub struct PtcSyncEventInfoArg {
pub owner_type: EventGraphOwnerType,
pub owner_uid: u64,
pub updated_events: PropertyDoubleKeyHashMap<i32, i32, EventInfo>,
}
#[derive(OctData, Debug)]
pub struct RpcCheckYorozuyaInfoRefreshArg {}
#[derive(OctData, Debug)]
pub struct PtcHollowQuestUnlockedByMainCityQuest {
pub quest_id: i32,
}
#[derive(OctData, Debug)]
pub struct RpcStartHollowQuestArg {
pub hollow_quest_id: i32,
pub buddy: u64,
pub initiative_item: i32,
pub avatar_map: PropertyHashMap<i8, u64>,
pub is_story: bool,
}
#[derive(OctData, Debug)]
pub struct PtcSyncHollowGridMapsArg {
pub player_uid: u64,
pub scene_uid: u64,
pub hollow_level: i32,
pub main_map: HollowGridMapProtocolInfo,
pub time_period: TimePeriodType,
pub weather: WeatherType,
}
#[derive(OctData, Debug)]
pub struct PtcPositionInHollowChangedArg {
pub player_uid: u64,
pub hollow_level: i32,
pub position: u16,
}
#[derive(OctData, Debug)]
pub struct PtcSyncHollowEventInfoArg {
pub event_graph_uid: u64,
pub hollow_event_template_id: i32,
pub event_graph_id: i32,
pub updated_event: EventInfo,
pub specials: PropertyHashMap<String, i32>,
}
#[derive(OctData, Debug)]
pub struct RpcRunHollowEventGraphArg {
pub event_graph_uid: u64,
pub event_id: i32,
pub move_path: Vec<i32>,
}
#[derive(OctData, Debug)]
pub struct PtcHollowGridArg {
pub player_uid: u64,
pub is_partial: bool,
pub scene_uid: u64,
pub hollow_level: i32,
pub grids: HashMap<u16, HollowGridProtocolInfo>,
}
#[derive(OctData, Debug)]
pub struct RpcHollowMoveArg {
pub player_uid: u64,
pub scene_uid: u64,
pub hollow_level: i32,
pub positions: Vec<u16>,
}
#[derive(OctData, Debug)]
pub struct RpcEndBattleArg {
pub player_uid: u64,
pub fight_ranking: FightRanking,
pub success: bool,
pub avatar_properties: PropertyHashMap<u64, HashMap<u16, i32>>,
pub killed_enemy_count: u16,
pub condition_statistics: HashMap<i32, i32>,
pub star: u8,
pub challenge_stat: HashMap<i32, u8>,
pub fight_drop_infos: Vec<FightDropInfo>,
pub challenge_result_info: PropertyHashMap<i32, ChallengeResultInfo>,
pub battle_statistics: LogBattleStatistics,
}
ret! {
struct RpcLoginRet {
account_info: PropertyBlob,
}
struct RpcEnterWorldRet {
player_info: PropertyBlob,
}
struct RpcGetPlayerMailsRet {
mail_count: u32, // Actually List<CPlayerMailInfo>, TODO!
}
struct PtcGetServerTimestampRet {
timestamp: u64,
base_utc_offset_milliseconds: i64,
}
struct RpcAdvanceBeginnerProcedureRet {
next_procedure_id: i32,
}
struct RpcPerformTriggerRet {
perform_uid: String,
}
struct RpcPerformEndRet {
}
struct RpcModNickNameRet {
}
struct RpcFinishACTPerformShowRet {
}
struct RpcKeepAliveRet {
}
struct RpcPerformJumpRet {
}
struct RpcBeginnerbattleBeginRet {
battle_uid: String,
}
struct RpcBattleReportRet {
need_index: i32,
}
struct RpcBeginnerbattleEndRet {
}
struct RpcLeaveCurDungeonRet {
}
struct RpcSavePosInMainCityRet {
}
struct RpcCloseLevelChgTipsRet {
}
struct PtcPlayerOperationRet {
}
struct RpcRunEventGraphRet {
}
struct RpcInteractWithUnitRet {
}
struct RpcCheckYorozuyaInfoRefreshRet {
}
struct RpcStartHollowQuestRet {
}
struct RpcRunHollowEventGraphRet {
}
struct RpcHollowMoveRet {
hollow_level: i32,
position: u16,
}
struct RpcEndBattleRet {
hollow_event_id: i32,
reward_items_classify: HashMap<BattleRewardType, HashMap<u64, ItemIDCount>>,
}
}

1339
protocol/src/structs.rs Normal file

File diff suppressed because it is too large Load diff

15
qwer/Cargo.toml Normal file
View file

@ -0,0 +1,15 @@
[package]
name = "qwer"
edition = "2021"
version.workspace = true
[features]
default = ["collection", "fastoct"]
full = ["default", "protocol"]
collection = []
fastoct = []
protocol = []
[dependencies]
byteorder.workspace = true
qwer-derive.workspace = true

View file

@ -0,0 +1,16 @@
[package]
name = "qwer-derive"
version.workspace = true
edition = "2021"
description = "Codegen for Zenless Zone Zero's Qwer Protocol"
license = "MIT"
publish = true
[lib]
proc-macro = true
[dependencies]
heck.workspace = true
proc-macro2 = "1.0.78"
quote = "1.0.35"
syn = "2.0.52"

View file

@ -0,0 +1,39 @@
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
mod oct_data;
/// Generate `OctData` implementation for structs and enums that have all fields
/// implement `OctData`.
///
/// For structs, fields are written one by one in order.
/// If the struct must pad a byte due to it being a `CPropertyObject`, the `#[property_object]`
/// attribute must be present, and must contain the value to pad with.
///
/// e.g. `#[property_object(u16, 0x01)]` will pad with 0x01 as a u16.
///
/// In the presence of these property objects, all fields must be Optional, e.g. `Option<T>`,
/// and must also have a tag attribute attached to them for marshalling and unmarshalling, of the
/// form `#[tag = <number>]`.
///
/// For enums, the structure starts with a discriminant with the type specified in the `#[repr]` of
/// the enum, followed by the fields of the enum one by one.
#[proc_macro_derive(
OctData,
attributes(
property_object,
property_blob,
skip_property,
tag,
root,
base,
polymorphic_none
)
)]
pub fn derive_message(item: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(item as DeriveInput);
match oct_data::imp(&parsed) {
Ok(item) => item.into(),
Err(err) => err.to_compile_error().into(),
}
}

File diff suppressed because it is too large Load diff

782
qwer/src/collection.rs Normal file
View file

@ -0,0 +1,782 @@
use std::{
collections::{HashMap, HashSet},
io::Result,
};
use crate::OctData;
pub type DoubleKeyHashMap<K1, K2, V> = HashMap<K1, HashMap<K2, V>>;
#[macro_export]
macro_rules! phashmap {
($(($key:expr, $value:expr)),*) => {
PropertyHashMap::Base(
{
let mut map = std::collections::HashMap::new();
$(
map.insert($key, $value);
)*
map
}
)
};
}
#[macro_export]
macro_rules! phashset {
($($value:expr),*) => {
PropertyHashSet::Base(
{
let mut set = std::collections::HashSet::new();
$(
set.insert($value);
)*
set
}
)
};
}
#[macro_export]
macro_rules! pdkhashmap {
($(($key1:expr, $key2:expr, $value:expr)),*) => {
PropertyDoubleKeyHashMap::Base(
{
let mut map = qwer::DoubleKeyHashMap::new();
$(
map.entry($key1).or_insert(HashMap::new()).insert($key2, $value);
)*
map
}
)
};
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PropertyHashMap<K, V>
where
K: OctData + Eq + std::hash::Hash,
V: OctData,
{
Base(HashMap<K, V>),
Modify {
to_add: Vec<(K, V)>,
to_remove: Vec<K>,
},
}
impl<K, V> PropertyHashMap<K, V>
where
K: OctData + Eq + std::hash::Hash,
V: OctData,
{
pub fn insert(&mut self, key: K, value: V) {
match self {
Self::Base(base) => {
base.insert(key, value);
}
Self::Modify { to_add, .. } => {
to_add.push((key, value));
}
}
}
pub fn remove(&mut self, key: K) -> Option<V> {
match self {
Self::Base(base) => base.remove(&key),
Self::Modify { to_remove, .. } => {
to_remove.push(key);
None
}
}
}
pub fn get(&self, key: &K) -> Option<&V> {
match self {
Self::Base(base) => base.get(key),
Self::Modify { .. } => None,
}
}
pub fn get_mut(&mut self, key: &K) -> Option<&mut V> {
match self {
Self::Base(base) => base.get_mut(key),
Self::Modify { .. } => None,
}
}
#[must_use]
pub fn len(&self) -> usize {
match self {
Self::Base(base) => base.len(),
Self::Modify { to_add, to_remove } => to_add.len() + to_remove.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match self {
Self::Base(base) => base.is_empty(),
Self::Modify { to_add, to_remove } => to_add.is_empty() && to_remove.is_empty(),
}
}
#[must_use]
pub fn iter(&self) -> std::collections::hash_map::Iter<K, V> {
match self {
Self::Base(base) => base.iter(),
Self::Modify { .. } => unreachable!("PropertyHashMap::Modify::iter()"),
}
}
pub fn iter_mut(&mut self) -> std::collections::hash_map::IterMut<K, V> {
match self {
Self::Base(base) => base.iter_mut(),
Self::Modify { .. } => unreachable!("PropertyHashMap::Modify::iter_mut()"),
}
}
}
impl<K, V> IntoIterator for PropertyHashMap<K, V>
where
K: OctData + Eq + std::hash::Hash,
V: OctData,
{
type IntoIter = std::collections::hash_map::IntoIter<K, V>;
type Item = (K, V);
fn into_iter(self) -> Self::IntoIter {
match self {
Self::Base(base) => base.into_iter(),
Self::Modify { .. } => unreachable!("PropertyHashMap::Modify::into_iter()"),
}
}
}
impl<'a, K, V> IntoIterator for &'a PropertyHashMap<K, V>
where
K: OctData + Eq + std::hash::Hash,
V: OctData,
{
type IntoIter = std::collections::hash_map::Iter<'a, K, V>;
type Item = (&'a K, &'a V);
fn into_iter(self) -> Self::IntoIter {
match self {
PropertyHashMap::Base(base) => base.iter(),
PropertyHashMap::Modify { .. } => unreachable!("PropertyHashMap::Modify::into_iter()"),
}
}
}
impl<'a, K, V> IntoIterator for &'a mut PropertyHashMap<K, V>
where
K: OctData + Eq + std::hash::Hash,
V: OctData,
{
type IntoIter = std::collections::hash_map::IterMut<'a, K, V>;
type Item = (&'a K, &'a mut V);
fn into_iter(self) -> Self::IntoIter {
match self {
PropertyHashMap::Base(base) => base.iter_mut(),
PropertyHashMap::Modify { .. } => unreachable!("PropertyHashMap::Modify::into_iter()"),
}
}
}
#[test]
fn test_hashmap_iter() {
let mut map = phashmap![(1, 2), (3, 4)];
let mut expecting = vec![(1, 2), (3, 4)];
let iter = map.iter_mut();
for (key, value) in iter {
assert!(expecting.contains(&(*key, *value)));
expecting.retain(|x| x != &(*key, *value));
*value += 1;
}
}
impl<T> PropertyHashSet<T>
where
T: OctData + Eq + std::hash::Hash,
{
pub fn insert(&mut self, value: T) {
match self {
Self::Base(base) => {
base.insert(value);
}
Self::Modify { to_add, .. } => {
to_add.push(value);
}
}
}
pub fn remove(&mut self, value: T) {
match self {
Self::Base(base) => {
base.remove(&value);
}
Self::Modify { to_remove, .. } => {
to_remove.push(value);
}
}
}
#[must_use]
pub fn len(&self) -> usize {
match self {
Self::Base(base) => base.len(),
Self::Modify { to_add, to_remove } => to_add.len() + to_remove.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match self {
Self::Base(base) => base.is_empty(),
Self::Modify { to_add, to_remove } => to_add.is_empty() && to_remove.is_empty(),
}
}
#[must_use]
pub fn iter(&self) -> std::collections::hash_set::Iter<T> {
match self {
Self::Base(base) => base.iter(),
Self::Modify { .. } => unreachable!("PropertyHashSet::Modify::iter()"),
}
}
#[must_use]
pub fn iter_mut(&mut self) -> std::collections::hash_set::Iter<T> {
self.into_iter()
}
}
impl<T> IntoIterator for PropertyHashSet<T>
where
T: OctData + Eq + std::hash::Hash,
{
type IntoIter = std::collections::hash_set::IntoIter<T>;
type Item = T;
fn into_iter(self) -> Self::IntoIter {
match self {
Self::Base(base) => base.into_iter(),
Self::Modify { .. } => unreachable!("PropertyHashSet::Modify::into_iter()"),
}
}
}
impl<'a, T> IntoIterator for &'a PropertyHashSet<T>
where
T: OctData + Eq + std::hash::Hash,
{
type IntoIter = std::collections::hash_set::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
match self {
PropertyHashSet::Base(base) => base.iter(),
PropertyHashSet::Modify { .. } => unreachable!("PropertyHashSet::Modify::into_iter()"),
}
}
}
impl<'a, T> IntoIterator for &'a mut PropertyHashSet<T>
where
T: OctData + Eq + std::hash::Hash,
{
type IntoIter = std::collections::hash_set::Iter<'a, T>;
type Item = &'a T;
fn into_iter(self) -> Self::IntoIter {
match self {
PropertyHashSet::Base(base) => base.iter(),
PropertyHashSet::Modify { .. } => unreachable!("PropertyHashSet::Modify::into_iter()"),
}
}
}
#[test]
fn test_phashmap_macro() {
let map = phashmap![(1, 2), (3, 4)];
assert_eq!(
map,
PropertyHashMap::Base([(1, 2), (3, 4)].into_iter().collect())
);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PropertyHashSet<T>
where
T: OctData + Eq + std::hash::Hash,
{
Base(HashSet<T>),
Modify { to_add: Vec<T>, to_remove: Vec<T> },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PropertyDoubleKeyHashMap<K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
Base(DoubleKeyHashMap<K1, K2, V>),
Modify {
to_add: Vec<(K1, K2, V)>,
to_remove: Vec<(K1, K2)>,
},
}
pub struct PropertyDoubleKeyHashMapIterMut<'a, K1, K2, V> {
outer_iter: std::collections::hash_map::IterMut<'a, K1, HashMap<K2, V>>,
inner_iter: Option<std::collections::hash_map::IterMut<'a, K2, V>>,
current_outer_key: Option<&'a K1>,
}
pub struct PropertyDoubleKeyHashMapIter<'a, K1, K2, V> {
outer_iter: std::collections::hash_map::Iter<'a, K1, HashMap<K2, V>>,
inner_iter: Option<std::collections::hash_map::Iter<'a, K2, V>>,
current_outer_key: Option<&'a K1>,
}
pub struct PropertyDoubleKeyHashMapIntoIter<K1, K2, V> {
outer_iter: std::collections::hash_map::IntoIter<K1, HashMap<K2, V>>,
inner_iter: Option<std::collections::hash_map::IntoIter<K2, V>>,
current_outer_key: Option<K1>,
}
impl<'a, K1, K2, V> Iterator for PropertyDoubleKeyHashMapIterMut<'a, K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
type Item = (&'a K1, &'a K2, &'a mut V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.inner_iter.is_none() {
if let Some((key1, sub_map)) = self.outer_iter.next() {
self.current_outer_key = Some(key1);
self.inner_iter = Some(sub_map.iter_mut());
} else {
return None;
}
}
if let Some(inner_iter) = &mut self.inner_iter {
if let Some((key2, value)) = inner_iter.next() {
return Some((self.current_outer_key.unwrap(), key2, value));
}
self.inner_iter = None;
}
}
}
}
impl<'a, K1, K2, V> Iterator for PropertyDoubleKeyHashMapIter<'a, K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
type Item = (&'a K1, &'a K2, &'a V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.inner_iter.is_none() {
if let Some((key1, sub_map)) = self.outer_iter.next() {
self.current_outer_key = Some(key1);
self.inner_iter = Some(sub_map.iter());
} else {
return None;
}
}
if let Some(inner_iter) = &mut self.inner_iter {
if let Some((key2, value)) = inner_iter.next() {
return Some((self.current_outer_key.unwrap(), key2, value));
}
self.inner_iter = None;
}
}
}
}
impl<K1, K2, V> Iterator for PropertyDoubleKeyHashMapIntoIter<K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash + Copy,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
type Item = (K1, K2, V);
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.inner_iter.is_none() {
if let Some((key1, sub_map)) = self.outer_iter.next() {
self.current_outer_key = Some(key1);
self.inner_iter = Some(sub_map.into_iter());
} else {
return None;
}
}
if let Some(inner_iter) = &mut self.inner_iter {
if let Some((key2, value)) = inner_iter.next() {
return Some((self.current_outer_key.unwrap(), key2, value));
}
self.inner_iter = None;
}
}
}
}
impl<K1, K2, V> IntoIterator for PropertyDoubleKeyHashMap<K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash + Copy,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
type IntoIter = PropertyDoubleKeyHashMapIntoIter<K1, K2, V>;
type Item = (K1, K2, V);
fn into_iter(self) -> Self::IntoIter {
match self {
Self::Base(base) => PropertyDoubleKeyHashMapIntoIter {
outer_iter: base.into_iter(),
inner_iter: None,
current_outer_key: None,
},
Self::Modify { .. } => {
unreachable!("PropertyDoubleKeyHashMap::Modify::into_iter() is not implemented")
}
}
}
}
impl<'a, K1, K2, V> IntoIterator for &'a PropertyDoubleKeyHashMap<K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
type IntoIter = PropertyDoubleKeyHashMapIter<'a, K1, K2, V>;
type Item = (&'a K1, &'a K2, &'a V);
fn into_iter(self) -> Self::IntoIter {
match self {
PropertyDoubleKeyHashMap::Base(base) => PropertyDoubleKeyHashMapIter {
outer_iter: base.iter(),
inner_iter: None,
current_outer_key: None,
},
PropertyDoubleKeyHashMap::Modify { .. } => {
unreachable!("PropertyDoubleKeyHashMap::Modify::into_iter() is not implemented")
}
}
}
}
impl<'a, K1, K2, V> IntoIterator for &'a mut PropertyDoubleKeyHashMap<K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
type IntoIter = PropertyDoubleKeyHashMapIterMut<'a, K1, K2, V>;
type Item = (&'a K1, &'a K2, &'a mut V);
fn into_iter(self) -> Self::IntoIter {
match self {
PropertyDoubleKeyHashMap::Base(base) => PropertyDoubleKeyHashMapIterMut {
outer_iter: base.iter_mut(),
inner_iter: None,
current_outer_key: None,
},
PropertyDoubleKeyHashMap::Modify { .. } => {
unreachable!("PropertyDoubleKeyHashMap::Modify::into_iter() is not implemented")
}
}
}
}
impl<K1, K2, V> PropertyDoubleKeyHashMap<K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
pub fn insert(&mut self, key: K1, sub_key: K2, value: V) {
match self {
Self::Base(base) => {
base.entry(key)
.or_insert_with(HashMap::new)
.insert(sub_key, value);
}
Self::Modify { to_add, .. } => {
to_add.push((key, sub_key, value));
}
}
}
pub fn get(&self, key: &K1, sub_key: &K2) -> Option<&V> {
match self {
Self::Base(base) => base.get(key).and_then(|sub_map| sub_map.get(sub_key)),
Self::Modify { .. } => {
unreachable!("PropertyDoubleKeyHashMap::Modify::get() is not implemented")
}
}
}
pub fn get_mut(&mut self, key: &K1, sub_key: &K2) -> Option<&mut V> {
match self {
Self::Base(base) => base
.get_mut(key)
.and_then(|sub_map| sub_map.get_mut(sub_key)),
Self::Modify { .. } => {
unreachable!("PropertyDoubleKeyHashMap::Modify::get_mut() is not implemented")
}
}
}
pub fn remove(&mut self, key: K1, sub_key: K2) -> Option<V> {
match self {
Self::Base(base) => {
let mut removed = None;
base.entry(key)
.and_modify(|sub_map| removed = sub_map.remove(&sub_key));
removed
}
Self::Modify { to_remove, .. } => {
to_remove.push((key, sub_key));
None
}
}
}
#[must_use]
pub fn len(&self) -> usize {
match self {
Self::Base(base) => base.len(),
Self::Modify { to_add, to_remove } => to_add.len() + to_remove.len(),
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
match self {
Self::Base(base) => base.is_empty(),
Self::Modify { to_add, to_remove } => to_add.is_empty() && to_remove.is_empty(),
}
}
#[must_use]
pub fn iter(&self) -> PropertyDoubleKeyHashMapIter<K1, K2, V> {
match self {
Self::Base(base) => PropertyDoubleKeyHashMapIter {
outer_iter: base.iter(),
inner_iter: None,
current_outer_key: None,
},
Self::Modify { .. } => {
unreachable!("PropertyDoubleKeyHashMap::Modify::iter() is not implemented")
}
}
}
pub fn iter_mut(&mut self) -> PropertyDoubleKeyHashMapIterMut<K1, K2, V> {
match self {
Self::Base(base) => PropertyDoubleKeyHashMapIterMut {
outer_iter: base.iter_mut(),
inner_iter: None,
current_outer_key: None,
},
Self::Modify { .. } => {
unreachable!("PropertyDoubleKeyHashMap::Modify::iter_mut() is not implemented")
}
}
}
}
#[test]
fn test_dkhashmap_iter() {
let mut map = PropertyDoubleKeyHashMap::Base(
std::iter::once((1, [(2, 3), (4, 5)].into_iter().collect())).collect(),
);
let mut expecting = vec![(1, 2, 3), (1, 4, 5)];
let iter = map.iter_mut();
for (key1, key2, value) in iter {
assert!(expecting.contains(&(*key1, *key2, *value)));
expecting.retain(|x| x != &(*key1, *key2, *value));
*value += 1;
}
}
impl<K, V> OctData for PropertyHashMap<K, V>
where
K: OctData + Eq + std::hash::Hash,
V: OctData,
{
fn marshal_to<W: std::io::Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
match self {
Self::Base(map) => {
map.marshal_to(w, bt_property_tag)?;
}
Self::Modify { to_add, to_remove } => {
let len = -(to_add.len() as i32 + to_remove.len() as i32);
len.marshal_to(w, bt_property_tag)?;
for (key, value) in to_add {
key.marshal_to(w, bt_property_tag)?;
false.marshal_to(w, bt_property_tag)?;
value.marshal_to(w, bt_property_tag)?;
}
for key in to_remove {
key.marshal_to(w, bt_property_tag)?;
true.marshal_to(w, bt_property_tag)?;
}
}
}
Ok(())
}
fn unmarshal_from<R: std::io::Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len >= 0 {
let mut map = HashMap::with_capacity(len as usize);
for _ in 0..len {
map.insert(
K::unmarshal_from(r, bt_property_tag)?,
V::unmarshal_from(r, bt_property_tag)?,
);
}
Ok(Self::Base(map))
} else {
let mut to_add = Vec::new();
let mut to_remove = Vec::new();
for _ in 0..-len {
let key = K::unmarshal_from(r, bt_property_tag)?;
if !bool::unmarshal_from(r, bt_property_tag)? {
to_add.push((key, V::unmarshal_from(r, bt_property_tag)?));
} else {
to_remove.push(key);
}
}
Ok(Self::Modify { to_add, to_remove })
}
}
}
impl<K> OctData for PropertyHashSet<K>
where
K: OctData + Eq + std::hash::Hash,
{
fn marshal_to<W: std::io::Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
match self {
Self::Base(set) => {
set.marshal_to(w, bt_property_tag)?;
}
Self::Modify { to_add, to_remove } => {
let len = -(to_add.len() as i32 + to_remove.len() as i32);
len.marshal_to(w, bt_property_tag)?;
for value in to_add {
value.marshal_to(w, bt_property_tag)?;
false.marshal_to(w, bt_property_tag)?;
}
for value in to_remove {
value.marshal_to(w, bt_property_tag)?;
true.marshal_to(w, bt_property_tag)?;
}
}
}
Ok(())
}
fn unmarshal_from<R: std::io::Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len >= 0 {
let mut set = HashSet::with_capacity(len as usize);
for _ in 0..len {
set.insert(K::unmarshal_from(r, bt_property_tag)?);
}
Ok(Self::Base(set))
} else {
let mut to_add = Vec::new();
let mut to_remove = Vec::new();
for _ in 0..-len {
let value = K::unmarshal_from(r, bt_property_tag)?;
if !bool::unmarshal_from(r, bt_property_tag)? {
to_add.push(value);
} else {
to_remove.push(value);
}
}
Ok(Self::Modify { to_add, to_remove })
}
}
}
impl<K1, K2, V> OctData for PropertyDoubleKeyHashMap<K1, K2, V>
where
K1: OctData + Eq + std::hash::Hash,
K2: OctData + Eq + std::hash::Hash,
V: OctData,
{
fn marshal_to<W: std::io::Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
match self {
Self::Base(map) => {
map.marshal_to(w, bt_property_tag)?;
}
Self::Modify { to_add, to_remove } => {
let len = -(to_add.len() as i32 + to_remove.len() as i32);
len.marshal_to(w, bt_property_tag)?;
for (key1, key2, value) in to_add {
key1.marshal_to(w, bt_property_tag)?;
key2.marshal_to(w, bt_property_tag)?;
false.marshal_to(w, bt_property_tag)?;
value.marshal_to(w, bt_property_tag)?;
}
for (key1, key2) in to_remove {
key1.marshal_to(w, bt_property_tag)?;
key2.marshal_to(w, bt_property_tag)?;
true.marshal_to(w, bt_property_tag)?;
}
}
}
Ok(())
}
fn unmarshal_from<R: std::io::Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len >= 0 {
let mut map = HashMap::new();
for _ in 0..len {
let key1 = K1::unmarshal_from(r, bt_property_tag)?;
let key2 = K2::unmarshal_from(r, bt_property_tag)?;
let value = V::unmarshal_from(r, bt_property_tag)?;
map.entry(key1)
.or_insert_with(HashMap::new)
.insert(key2, value);
}
Ok(Self::Base(map))
} else {
let mut to_add = Vec::new();
let mut to_remove = Vec::new();
for _ in 0..-len {
let key1 = K1::unmarshal_from(r, bt_property_tag)?;
let key2 = K2::unmarshal_from(r, bt_property_tag)?;
if !bool::unmarshal_from(r, bt_property_tag)? {
to_add.push((key1, key2, V::unmarshal_from(r, bt_property_tag)?));
} else {
to_remove.push((key1, key2));
}
}
Ok(Self::Modify { to_add, to_remove })
}
}
}

278
qwer/src/fastoct.rs Normal file
View file

@ -0,0 +1,278 @@
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
hash::Hash,
io::{Read, Result, Write},
};
use byteorder::{ReadBytesExt, WriteBytesExt};
pub use qwer_derive::OctData;
use crate::DoubleKeyHashMap;
// LE encoded data
pub trait OctData: Sized + Send + Sync {
fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()>;
fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self>;
}
impl OctData for bool {
fn marshal_to<W: Write>(&self, w: &mut W, _: u16) -> Result<()> {
w.write_u8(u8::from(*self))
}
fn unmarshal_from<R: Read>(r: &mut R, _: u16) -> Result<Self> {
Ok(r.read_u8()? != 0)
}
}
impl OctData for u8 {
fn marshal_to<W: Write>(&self, w: &mut W, _: u16) -> Result<()> {
w.write_u8(*self)
}
fn unmarshal_from<R: Read>(r: &mut R, _: u16) -> Result<Self> {
r.read_u8()
}
}
impl OctData for i8 {
fn marshal_to<W: Write>(&self, w: &mut W, _: u16) -> Result<()> {
w.write_i8(*self)
}
fn unmarshal_from<R: Read>(r: &mut R, _: u16) -> Result<Self> {
r.read_i8()
}
}
macro_rules! impl_primitive {
($($t:ty, $write:ident, $read:ident,)*) => {
$(
impl OctData for $t {
fn marshal_to<W: Write>(&self, w: &mut W, _: u16) -> Result<()> {
w.$write::<byteorder::LittleEndian>(*self)
}
fn unmarshal_from<R: Read>(r: &mut R, _: u16) -> Result<Self> {
r.$read::<byteorder::LittleEndian>()
}
}
)*
};
}
impl_primitive! {
u16, write_u16, read_u16,
i16, write_i16, read_i16,
u32, write_u32, read_u32,
i32, write_i32, read_i32,
u64, write_u64, read_u64,
i64, write_i64, read_i64,
}
// floats are a bit special, the bits are casted to an integer and then treated as an integer
impl OctData for f32 {
fn marshal_to<W: Write>(&self, w: &mut W, _: u16) -> Result<()> {
w.write_u32::<byteorder::LittleEndian>(self.to_bits())
}
fn unmarshal_from<R: Read>(r: &mut R, _: u16) -> Result<Self> {
Ok(Self::from_bits(r.read_u32::<byteorder::LittleEndian>()?))
}
}
impl OctData for f64 {
fn marshal_to<W: Write>(&self, w: &mut W, _: u16) -> Result<()> {
w.write_u64::<byteorder::LittleEndian>(self.to_bits())
}
fn unmarshal_from<R: Read>(r: &mut R, _: u16) -> Result<Self> {
Ok(Self::from_bits(r.read_u64::<byteorder::LittleEndian>()?))
}
}
impl<T> OctData for Vec<T>
where
T: OctData,
{
fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
if self.is_empty() {
(0i32).marshal_to(w, bt_property_tag)?;
return Ok(());
}
(self.len() as i32).marshal_to(w, bt_property_tag)?;
for item in self {
item.marshal_to(w, bt_property_tag)?;
}
Ok(())
}
fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len < 0 {
let real_len = -len;
let mut vec = Self::with_capacity(real_len as usize);
for _ in 0..real_len {
bool::unmarshal_from(r, bt_property_tag)?;
vec.push(T::unmarshal_from(r, bt_property_tag)?);
}
Ok(vec)
} else {
let mut vec = Self::with_capacity(len as usize);
for _ in 0..len {
vec.push(T::unmarshal_from(r, bt_property_tag)?);
}
Ok(vec)
}
}
}
impl<K, V> OctData for HashMap<K, V>
where
K: OctData + Eq + Hash,
V: OctData,
{
default fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
(self.len() as i32).marshal_to(w, bt_property_tag)?;
for (key, value) in self {
key.marshal_to(w, bt_property_tag)?;
value.marshal_to(w, bt_property_tag)?;
}
Ok(())
}
default fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len == -1 {
return Ok(Self::new());
}
let mut map = Self::with_capacity(len as usize);
for _ in 0..len {
map.insert(
K::unmarshal_from(r, bt_property_tag)?,
V::unmarshal_from(r, bt_property_tag)?,
);
}
Ok(map)
}
}
#[cfg(feature = "collection")]
impl<K1, K2, V> OctData for DoubleKeyHashMap<K1, K2, V>
where
K1: OctData + Eq + Hash,
K2: OctData + Eq + Hash,
V: OctData,
{
fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
self.iter()
.map(|(_, inner_map)| inner_map.len() as i32)
.sum::<i32>()
.marshal_to(w, bt_property_tag)?;
for (key1, inner_map) in self {
for (key2, value) in inner_map {
key1.marshal_to(w, bt_property_tag)?;
key2.marshal_to(w, bt_property_tag)?;
value.marshal_to(w, bt_property_tag)?;
}
}
Ok(())
}
fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len == -1 {
return Ok(Self::new());
}
let mut map = Self::new();
for _ in 0..len {
let key1 = K1::unmarshal_from(r, bt_property_tag)?;
let key2 = K2::unmarshal_from(r, bt_property_tag)?;
let value = V::unmarshal_from(r, bt_property_tag)?;
map.entry(key1)
.or_insert_with(HashMap::new)
.insert(key2, value);
}
Ok(map)
}
}
impl<T> OctData for HashSet<T>
where
T: OctData + Eq + Hash,
{
fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
(self.len() as i32).marshal_to(w, bt_property_tag)?;
for item in self {
item.marshal_to(w, bt_property_tag)?;
}
Ok(())
}
fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len == -1 {
return Ok(Self::new());
}
let mut set = Self::with_capacity(len as usize);
for _ in 0..len {
set.insert(T::unmarshal_from(r, bt_property_tag)?);
}
Ok(set)
}
}
impl<T> OctData for Option<T>
where
T: OctData,
{
fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
if let Some(item) = self {
item.marshal_to(w, bt_property_tag)?;
}
Ok(())
}
fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
Ok(Some(T::unmarshal_from(r, bt_property_tag)?))
}
}
impl OctData for String {
fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
if self.is_empty() {
(-1i32).marshal_to(w, bt_property_tag)?;
return Ok(());
}
(self.len() as i32).marshal_to(w, bt_property_tag)?;
w.write_all(self.as_bytes())
}
fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
let len = i32::unmarshal_from(r, bt_property_tag)?;
if len == -1 {
return Ok(Self::new());
}
let mut buf = vec![0; len as usize];
r.read_exact(&mut buf)?;
Ok(Self::from_utf8(buf).unwrap())
}
}
impl<'a, T> OctData for Cow<'a, T>
where
T: OctData + Clone,
{
fn marshal_to<W: Write>(&self, w: &mut W, bt_property_tag: u16) -> Result<()> {
self.as_ref().marshal_to(w, bt_property_tag)
}
fn unmarshal_from<R: Read>(r: &mut R, bt_property_tag: u16) -> Result<Self> {
Ok(Cow::Owned(T::unmarshal_from(r, bt_property_tag)?))
}
}

16
qwer/src/lib.rs Normal file
View file

@ -0,0 +1,16 @@
#![allow(incomplete_features)]
#![feature(specialization)]
#[cfg(feature = "collection")]
mod collection;
#[cfg(feature = "fastoct")]
mod fastoct;
#[cfg(feature = "protocol")]
mod protocol;
#[cfg(feature = "collection")]
pub use collection::*;
#[cfg(feature = "fastoct")]
pub use fastoct::*;
#[cfg(feature = "protocol")]
pub use protocol::*;

36
qwer/src/protocol.rs Normal file
View file

@ -0,0 +1,36 @@
#[derive(Debug, Default)]
pub struct ProtocolHeader {
pub to_channel: u16,
pub from_channel: u16,
pub is_rpc_ret: bool,
pub rpc_arg_uid: u64,
}
impl From<Vec<u8>> for ProtocolHeader {
fn from(value: Vec<u8>) -> Self {
let to_channel = u16::from_le_bytes(value[0..2].try_into().unwrap());
let from_channel = u16::from_le_bytes(value[2..4].try_into().unwrap());
let is_rpc_ret = value[4] != 100;
let rpc_arg_uid = u64::from_le_bytes(value[5..13].try_into().unwrap());
Self {
to_channel,
from_channel,
is_rpc_ret,
rpc_arg_uid,
}
}
}
impl From<ProtocolHeader> for Vec<u8> {
fn from(value: ProtocolHeader) -> Self {
let mut out = Self::with_capacity(13);
out.extend(value.to_channel.to_le_bytes());
out.extend(value.from_channel.to_le_bytes());
out.push(if value.is_rpc_ret { 1 } else { 100 });
out.extend(value.rpc_arg_uid.to_le_bytes());
out
}
}

23
sdkserver/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "sdkserver"
edition = "2021"
version.workspace = true
[dependencies]
axum.workspace = true
axum-server.workspace = true
encoding.workspace = true
leb128.workspace = true
anyhow.workspace = true
env_logger.workspace = true
tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
serde.workspace = true
serde_json.workspace = true
tokio-util.workspace = true
ansi_term.workspace = true
[[bin]]
name = "nap-sdkserver"
path = "src/main.rs"

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

@ -0,0 +1,23 @@
use encoding::all::UTF_16LE;
use encoding::{EncoderTrap, Encoding};
pub fn encrypt_config(content: &str, key: &str) -> Vec<u8> {
let mut content_bytes = UTF_16LE.encode(content, EncoderTrap::Strict).unwrap();
let key_bytes = UTF_16LE.encode(key, EncoderTrap::Strict).unwrap();
for i in 0..content_bytes.len() {
content_bytes[i] ^= key_bytes[i % key_bytes.len()];
}
let k = key.as_bytes();
let mut out = Vec::with_capacity(4 + k.len() + 8 + content_bytes.len());
leb128::write::unsigned(&mut out, u64::try_from(k.len()).unwrap()).unwrap();
out.extend_from_slice(k);
out.extend(0_u32.to_le_bytes());
out.extend(u32::try_from(content_bytes.len()).unwrap().to_le_bytes());
out.extend(content_bytes);
out
}

70
sdkserver/src/main.rs Normal file
View file

@ -0,0 +1,70 @@
use anyhow::Result;
use tracing_subscriber::{layer::SubscriberExt, EnvFilter, Registry};
use axum::{
routing::{get, post},
Router,
};
mod crypto;
mod services;
use services::{auth, config, entry, errors};
const HOST: &str = "0.0.0.0";
const PORT: u16 = 21000;
#[tokio::main]
async fn main() -> Result<()> {
init_tracing()?;
let router = Router::new()
.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(config::APP_CONFIG_ENDPOINT, get(config::application))
.route(config::SERVER_LIST_ENDPOINT, get(config::server_list))
.route(
config::VERSIONS_BUNDLE_ENDPOINT,
get(config::versions_bundle),
)
.route(entry::ACCOUNT_TOKEN_ENDPOINT, post(entry::account_token))
.route(entry::ACCOUNT_SERVER_ENDPOINT, post(entry::account_server))
.fallback(errors::not_found);
let bind_url = format!("{HOST}:{PORT}");
let http_server = axum_server::bind(bind_url.parse()?);
tracing::info!("SDK Server is listening at {bind_url}");
http_server.serve(router.into_make_service()).await?;
Ok(())
}
fn init_tracing() -> Result<()> {
#[cfg(target_os = "windows")]
ansi_term::enable_ansi_support().unwrap();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let log_filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| EnvFilter::try_new("info").unwrap())
.add_directive("sdkserver=debug".parse().unwrap());
tracing::subscriber::set_global_default(
Registry::default()
.with(log_filter)
.with(tracing_subscriber::fmt::Layer::default()),
)?;
Ok(())
}

View file

@ -0,0 +1,84 @@
use axum::Json;
use serde_json::json;
pub const LOGIN_WITH_PASSWORD_ENDPOINT: &str = "/nap_global/mdk/shield/api/login";
pub const LOGIN_WITH_SESSION_TOKEN_ENDPOINT: &str = "/nap_global/mdk/shield/api/verify";
pub const GRANTER_LOGIN_VERIFICATION_ENDPOINT: &str = "/nap_global/combo/granter/login/v2/login";
pub const RISKY_API_CHECK_ENDPOINT: &str = "/account/risky/api/check";
#[tracing::instrument]
pub async fn login_with_password() -> Json<serde_json::Value> {
tracing::info!("login_with_password");
Json(json!({
"data": {
"account": {
"area_code": "**",
"email": "ReversedRooms",
"country": "RU",
"is_email_verify": "1",
"token": "mostsecuretokenever",
"uid": "1337"
},
"device_grant_required": false,
"reactivate_required": false,
"realperson_required": false,
"safe_mobile_required": false
},
"message": "OK",
"retcode": 0
}))
}
#[tracing::instrument]
pub async fn login_with_session_token() -> Json<serde_json::Value> {
tracing::info!("login_with_session_token");
Json(json!({
"data": {
"account": {
"area_code": "**",
"email": "ReversedRooms",
"country": "RU",
"is_email_verify": "1",
"token": "mostsecuretokenever",
"uid": "1337"
},
"device_grant_required": false,
"reactivate_required": false,
"realperson_required": false,
"safe_mobile_required": false
},
"message": "OK",
"retcode": 0
}))
}
#[tracing::instrument]
pub async fn granter_login_verification() -> Json<serde_json::Value> {
tracing::info!("granter_login_verification");
Json(json!({
"data": {
"account_type": 1,
"combo_id": "1337",
"combo_token": "9065ad8507d5a1991cb6fddacac5999b780bbd92",
"data": "{\"guest\":false}",
"heartbeat": false,
"open_id": "1337"
},
"message": "OK",
"retcode": 0
}))
}
#[tracing::instrument]
pub async fn risky_api_check() -> Json<serde_json::Value> {
tracing::info!("risky_api_check");
Json(json!({
"data": {
"id": "06611ed14c3131a676b19c0d34c0644b",
"action": "ACTION_NONE",
"geetest": null
},
"message": "OK",
"retcode": 0
}))
}

View file

@ -0,0 +1,57 @@
use axum::{body::Body, response::IntoResponse};
use serde_json::json;
use crate::crypto;
pub const APP_CONFIG_ENDPOINT: &str = "/design_data/NAP_Publish_AppStore_0.1.0/oversea/config.bin";
pub const SERVER_LIST_ENDPOINT: &str =
"/design_data/NAP_Publish_AppStore_0.1.0/oversea/serverlist.bin";
pub const VERSIONS_BUNDLE_ENDPOINT: &str = "/game_res/NAP_Publish/output_147608_1361f678bc/client/StandaloneWindows64/oversea/versions.bundle";
pub async fn application() -> Vec<u8> {
crypto::encrypt_config(
json!({
"InfoGroups": {
"StandaloneWindows64": {
"VersionInfoGroups": {
"0.1.0": {
"MinVersion": "0.1.0",
"LatestVersion": "0.1.0",
"GameResUrl": "http://127.0.0.1:21000/game_res/NAP_Publish/output_147608_1361f678bc/client/",
"DesignDataUrl": "http://127.0.0.1:21000/",
"ServerListUrl": "http://127.0.0.1:21000/design_data/NAP_Publish_AppStore_0.1.0/oversea/serverlist.bin",
"$Type": "Foundation.ConfigurationInfo"
},
},
"$Type": "Foundation.ConfigurationInfoGroup"
},
},
})
.to_string()
.as_str(),
"MostSecureKey",
)
}
pub async fn server_list() -> Vec<u8> {
crypto::encrypt_config(
json!([{
"sid": 142,
"serverName": "HollowPS",
"ip": "127.0.0.1",
"port": "21000",
"noticeRegion": "nap_glb_cbus01",
"protocol": "http",
"$Type": "MoleMole.ServerListInfo"
}])
.to_string()
.as_str(),
"MostSecureKey",
)
}
pub const VERSION_BUNDLE: &[u8] = include_bytes!("../../versions.bundle");
pub async fn versions_bundle() -> impl IntoResponse {
Body::from(VERSION_BUNDLE)
}

View file

@ -0,0 +1,31 @@
use axum::Json;
use serde_json::json;
pub const ACCOUNT_TOKEN_ENDPOINT: &str = "/account/token";
pub const ACCOUNT_SERVER_ENDPOINT: &str = "/account/account_server";
#[tracing::instrument]
pub async fn account_token() -> Json<serde_json::Value> {
tracing::info!("account_token");
Json(json!({
"ErrorCode": 0,
"ErrorMsg": null,
"Ext": {
"Birthday": "01.01",
"Country": "RU",
"Token": "MostSecureTokenEver"
}
}))
}
#[tracing::instrument]
pub async fn account_server() -> Json<serde_json::Value> {
tracing::info!("account_server");
Json(json!({
"ErrorCode": 0,
"ErrorMsg": null,
"Ext": {
"Address": "127.0.0.1:10301/0"
}
}))
}

View file

@ -0,0 +1,7 @@
use axum::http::{StatusCode, Uri};
use axum::response::IntoResponse;
pub async fn not_found(uri: Uri) -> impl IntoResponse {
tracing::warn!("unhandled http request: {uri}");
StatusCode::NOT_FOUND
}

View file

@ -0,0 +1,4 @@
pub mod auth;
pub mod config;
pub mod entry;
pub mod errors;

BIN
sdkserver/versions.bundle Normal file

Binary file not shown.