Compare commits

..

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

19 changed files with 124 additions and 293 deletions

View file

@ -3,10 +3,6 @@
A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/en-us/) A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/en-us/)
![screenshot](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/screenshot.png) ![screenshot](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/screenshot.png)
## Prerequisites
- [MongoDB](https://www.mongodb.com/try/download/community)
## Installation ## Installation
### From Source ### From Source
@ -14,8 +10,7 @@ A Server emulator for the game [`Honkai: Star Rail`](https://hsr.hoyoverse.com/e
#### Requirements #### Requirements
- [Rust](https://www.rust-lang.org/tools/install) - [Rust](https://www.rust-lang.org/tools/install)
- [MongoDB](https://www.mongodb.com/try/download/community-edition) - [MongoDB](https://www.mongodb.com/try/download/community)
- [Protobuf Compiler (protoc)](https://github.com/protocolbuffers/protobuf/releases/download/v26.1/protoc-26.1-win64.zip). You should manually extract it to somewhere and add `bin` subfolder to the system environment variable `PATH`.
#### Building #### Building
@ -59,29 +54,12 @@ By default, servers will try to use local mongodb (at 127.0.0.1:27017),
this should work out of box if you installed MongoDB on your machine. this should work out of box if you installed MongoDB on your machine.
<br> <br>
You can change this in configuration file of specific server. You can change this in configuration file of specific server.
Currently only sdkserver and gameserver communicate with database, so if you **need** to configure it, Currently only 2 servers communicate with database, so if you **need** to configure it,
edit sdkserver.json and gameserver.json files. edit sdkserver.json and gameserver.json files.
## Connecting ## Connecting
### For the latest 2.3 Beta patch [Get 2.3 beta client](https://autopatchos.starrails.com/client/Beta/20240501125700_dUBAjS7YiX9nF7mJ/StarRail_2.2.51.zip),
If you want to play the latest Beta patch, the `mhypbase.dll` patch for 2.2.51 OS is not usable. You should use a separate proxy like [FireflySR.Tool.Proxy](https://git.xeondev.com/YYHEggEgg/FireflySR.Tool.Proxy) (Prebuilt binary can be downloaded [here](https://git.xeondev.com/YYHEggEgg/FireflySR.Tool.Proxy/releases)).
Also, you need to configure `disable_password_check` in `sdkserver.json`. That is to say, if you have used a older version, you should go to the configuration file **in the root directory** and check if it's `true`.
Clients (2.3 Beta v4):
- [OS - 2.2.54](https://autopatchos.starrails.com/client/Beta/20240524111944_IhuuEu6NfLJtObzr/StarRail_2.2.54.zip)
- [CN - 2.2.54](https://autopatchcn.bhsr.com/client/beta/20240524111603_TZHPxvCZGt1eRV0w/StarRail_2.2.54.zip)
- [OS hdiff - 2.2.53-2.2.54](https://autopatchos.starrails.com/client/hkrpg_global/game_2.2.53_2.2.54_hdiff_vTcPduNiNYcyOqnd.zip)
- [CN hdiff - 2.2.53-2.2.54](https://autopatchcn.bhsr.com/client/hkrpg_cn/game_2.2.53_2.2.54_hdiff_GUdpMZLRReCpnJdX.zip)
Notice: **CN Package may not have languages other than Chinese, and vice versa**. Be careful when choosing which to download; luckily they are all compatiable with this server.
### For v1 Players
[Get 2.3 beta client v1(.51)](https://autopatchos.starrails.com/client/Beta/20240501125700_dUBAjS7YiX9nF7mJ/StarRail_2.2.51.zip),
replace [mhypbase.dll](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/mhypbase.dll) replace [mhypbase.dll](https://git.xeondev.com/reversedrooms/FireflySR/raw/branch/master/mhypbase.dll)
file in your game folder, it will redirect game traffic (and disable in-game censorship) file in your game folder, it will redirect game traffic (and disable in-game censorship)

View file

@ -27,20 +27,11 @@ pub struct RuntimeGroupInstanceInfo {
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
#[serde(default)] #[serde(default)]
pub struct RuntimeGroupInfo { pub struct RuntimeGroupInfo {
pub load_on_initial: bool,
pub load_side: GroupLoadSide,
pub group_name: Option<String>, pub group_name: Option<String>,
pub anchor_list: Option<Vec<LevelAnchorInfo>>, pub anchor_list: Option<Vec<LevelAnchorInfo>>,
pub prop_list: Option<Vec<LevelPropInfo>>, pub prop_list: Option<Vec<LevelPropInfo>>,
} }
#[derive(Default, Deserialize, PartialEq, Eq)]
pub enum GroupLoadSide {
#[default]
Client,
Server,
}
#[derive(Default, Deserialize)] #[derive(Default, Deserialize)]
#[serde(rename_all = "PascalCase")] #[serde(rename_all = "PascalCase")]
#[serde(default)] #[serde(default)]

View file

@ -17,30 +17,6 @@
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7033392_aaca9c1b456b", "ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7033392_aaca9c1b456b",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7050564_f05a0f949b10", "lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7050564_f05a0f949b10",
"lua_version": "7050564" "lua_version": "7050564"
},
"CNBETAWin2.2.53": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_7128256_5f77b249238a",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_7134377_b1f36fb2d9b8",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_7120090_469169697c23",
"lua_version": "7120090"
},
"OSBETAWin2.2.53": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_7128256_5f77b249238a",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7134377_b1f36fb2d9b8",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7120090_469169697c23",
"lua_version": "7120090"
},
"CNBETAWin2.2.54": {
"asset_bundle_url": "https://autopatchcn.bhsr.com/asb/BetaLive/output_7165870_7fb7dbd8a7a4",
"ex_resource_url": "https://autopatchcn.bhsr.com/design_data/BetaLive/output_7171381_bb763a07e196",
"lua_url": "https://autopatchcn.bhsr.com/lua/BetaLive/output_7165994_1569306165f0",
"lua_version": "7165994"
},
"OSBETAWin2.2.54": {
"asset_bundle_url": "https://autopatchos.starrails.com/asb/BetaLive/output_7165870_7fb7dbd8a7a4",
"ex_resource_url": "https://autopatchos.starrails.com/design_data/BetaLive/output_7171381_bb763a07e196",
"lua_url": "https://autopatchos.starrails.com/lua/BetaLive/output_7165994_1569306165f0",
"lua_version": "7165994"
} }
} }
} }

View file

@ -1,61 +0,0 @@
use common::data::EXCEL_COLLECTION;
use super::*;
pub async fn max_traces(args: &[&str], session: &PlayerSession) -> Result<()> {
let Some(Ok(avatar_id)) = args.get(0).map(|s| s.parse::<u32>()) else {
return send_text(session, "Usage: /avatar max_traces [avatar_id]").await;
};
{
let mut player_info = session.context.player.borrow_mut();
let avatar_comp = player_info.data.avatar_bin.as_mut().unwrap();
let Some(avatar) = avatar_comp
.avatar_list
.iter_mut()
.find(|a| a.avatar_id == avatar_id)
else {
return send_text(session, &format!("Avatar {avatar_id} doesn't exist")).await;
};
EXCEL_COLLECTION
.avatar_skill_tree_configs
.iter()
.filter(|c| c.avatar_id == avatar_id)
.map(|c| (c.point_id, c.max_level))
.for_each(|(pt, lv)| {
if let Some(skill_tree) = avatar
.skill_tree_list
.iter_mut()
.find(|st| st.point_id == pt)
{
skill_tree.level = lv
} else {
avatar.skill_tree_list.push(AvatarSkillTreeBin {
point_id: pt,
level: lv,
})
}
});
}
let avatar_mgr = session.context.avatar_mgr.borrow();
session
.send(
CMD_PLAYER_SYNC_SC_NOTIFY,
PlayerSyncScNotify {
avatar_sync: Some(AvatarSync {
avatar_list: avatar_mgr.avatar_list_proto(),
}),
..Default::default()
},
)
.await?;
send_text(
session,
&format!("Successfully maxed out traces of avatar {avatar_id}"),
)
.await
}

View file

@ -1,72 +0,0 @@
use anyhow::Result;
use proto::*;
use crate::{net::PlayerSession, util};
mod avatar;
mod relic;
mod scene;
macro_rules! commands {
($($category:ident $action:ident;)*) => {
pub async fn execute_command(command: &str, session: &PlayerSession) -> Result<()> {
let input = command[1..].split(" ").collect::<Vec<&str>>();
let (Some(category), Some(action)) = (input.get(0), input.get(1)) else {
let mut help_text = "Available Commands: ".to_string();
$(
help_text.push_str(stringify!($category));
help_text.push_str(" ");
help_text.push_str(stringify!($action));
help_text.push_str("; ");
)*
let _ = send_text(session, &help_text).await;
let _ = send_text(session, "Usage: /[category] [action] [arg1] [arg2] ...").await;
return send_text(session, "Type /[category] [action] to get more detailed help.").await;
};
let args = &input[2..];
if let Err(_) = match (*category, *action) {
$(
(stringify!($category), stringify!($action)) => {
$category::$action(args, session).await
}
)*,
_ => send_text(session, "Unknown command").await,
} {
return send_text(
session,
"Command execution failed. Re-check your input and try again.",
)
.await;
}
Ok(())
}
};
}
commands! {
avatar max_traces;
relic give;
scene enter;
}
async fn send_text(session: &PlayerSession, content: &str) -> Result<()> {
session
.send(
CMD_GET_PRIVATE_CHAT_HISTORY_SC_RSP,
GetPrivateChatHistoryScRsp {
contact_id: 13371337,
chat_message_list: vec![ChatMessageData {
sender_id: 13371337,
message_type: MsgType::CustomText.into(),
timestamp: util::cur_timestamp_seconds(),
content: content.to_string(),
..Default::default()
}],
..Default::default()
},
)
.await
}

View file

@ -1,42 +0,0 @@
use super::*;
const GIVE_RELIC_USAGE: &'static str = "Usage: /relic give [id] [level] [main_affix] [sub_affix_count] [sub_affix1_id] [sub_affix1_cnt] ...";
pub async fn give(args: &[&str], session: &PlayerSession) -> Result<()> {
if args.len() < 4 {
return send_text(session, GIVE_RELIC_USAGE).await;
}
let id = args[0].parse::<u32>()?;
let level = args[1].parse::<u32>()?;
let main_affix = args[2].parse::<u32>()?;
let sub_affix_count = args[3].parse::<usize>()?;
if args.len() - 4 < (sub_affix_count * 2) as usize {
return send_text(session, GIVE_RELIC_USAGE).await;
}
let mut sub_affix_params = Vec::with_capacity(sub_affix_count);
let args = &args[4..];
for i in 0..sub_affix_count {
let sub_affix_id = args[i * 2].parse::<u32>()?;
let sub_affix_cnt = args[i * 2 + 1].parse::<u32>()?;
sub_affix_params.push((sub_affix_id, sub_affix_cnt));
}
let item_mgr = session.context.item_mgr.borrow();
item_mgr.give_relic(id, level, main_affix, sub_affix_params)?;
session
.send(
CMD_PLAYER_SYNC_SC_NOTIFY,
PlayerSyncScNotify {
relic_list: item_mgr.relic_list_proto(),
..Default::default()
},
)
.await?;
send_text(session, "Relic added successfully").await
}

View file

@ -1,25 +0,0 @@
use super::*;
pub async fn enter(args: &[&str], session: &PlayerSession) -> Result<()> {
let Some(Ok(entry_id)) = args.get(0).map(|s| s.parse::<u32>()) else {
return send_text(session, "Usage: /scene enter [entry_id]").await;
};
let mut scene_mgr = session.context.scene_mgr.borrow_mut();
let scene = match scene_mgr.enter_scene(entry_id) {
Ok(scene_info) => Some(scene_info),
Err(_) => return send_text(session, &format!("Failed to enter scene {entry_id}.")).await,
};
let lineup_mgr = session.context.lineup_mgr.borrow();
session
.send(
CMD_ENTER_SCENE_BY_SERVER_SC_NOTIFY,
EnterSceneByServerScNotify {
scene,
lineup: Some(lineup_mgr.cur_lineup_proto()),
reason: EnterSceneReason::None.into(),
},
)
.await
}

View file

@ -16,6 +16,7 @@ pub struct GameContext {
logged_in: OnceCell<()>, logged_in: OnceCell<()>,
pub player: Arc<AtomicRefCell<PlayerInfo>>, pub player: Arc<AtomicRefCell<PlayerInfo>>,
pub avatar_mgr: Arc<AtomicRefCell<AvatarManager>>, pub avatar_mgr: Arc<AtomicRefCell<AvatarManager>>,
pub command_mgr: Arc<AtomicRefCell<CommandManager>>,
pub hero_basic_type_mgr: Arc<AtomicRefCell<HeroBasicTypeManager>>, pub hero_basic_type_mgr: Arc<AtomicRefCell<HeroBasicTypeManager>>,
pub item_mgr: Arc<AtomicRefCell<ItemManager>>, pub item_mgr: Arc<AtomicRefCell<ItemManager>>,
pub lineup_mgr: Arc<AtomicRefCell<LineupManager>>, pub lineup_mgr: Arc<AtomicRefCell<LineupManager>>,
@ -37,7 +38,8 @@ impl GameContext {
Self { Self {
logged_in: OnceCell::new(), logged_in: OnceCell::new(),
player: player.clone(), player: player.clone(),
avatar_mgr, avatar_mgr: avatar_mgr.clone(),
command_mgr: Arc::new(AtomicRefCell::new(CommandManager::new(item_mgr.clone()))),
hero_basic_type_mgr: Arc::new(AtomicRefCell::new(HeroBasicTypeManager::new( hero_basic_type_mgr: Arc::new(AtomicRefCell::new(HeroBasicTypeManager::new(
player.clone(), player.clone(),
))), ))),

View file

@ -0,0 +1,100 @@
use crate::{net::PlayerSession, util};
use anyhow::Result;
use proto::*;
use super::*;
pub struct CommandManager {
item_mgr: Arc<AtomicRefCell<ItemManager>>,
}
impl CommandManager {
pub fn new(item_mgr: Arc<AtomicRefCell<ItemManager>>) -> Self {
Self { item_mgr }
}
pub async fn execute_command(&self, command: &str, session: &PlayerSession) -> Result<()> {
let input = command.split(" ").collect::<Vec<&str>>();
let (Some(category), Some(action)) = (input.get(0), input.get(1)) else {
return self
.send_text(session, "Usage: /[category] [action] [arg1] [arg2] ...")
.await;
};
let args = &input[2..];
if let Err(_) = match (*category, *action) {
("/relic", "give") => self.give_relic(args, session).await,
_ => self.send_text(session, "Unknown command").await,
} {
return self
.send_text(
session,
"Command execution failed. Re-check your input and try again.",
)
.await;
}
Ok(())
}
const GIVE_RELIC_USAGE: &'static str = "Usage: /relic give [id] [level] [main_affix] [sub_affix_count] [sub_affix1_id] [sub_affix1_cnt] ...";
async fn give_relic(&self, args: &[&str], session: &PlayerSession) -> Result<()> {
if args.len() < 4 {
return self.send_text(session, Self::GIVE_RELIC_USAGE).await;
}
let id = args[0].parse::<u32>()?;
let level = args[1].parse::<u32>()?;
let main_affix = args[2].parse::<u32>()?;
let sub_affix_count = args[3].parse::<usize>()?;
if args.len() - 4 < (sub_affix_count * 2) as usize {
return self.send_text(session, Self::GIVE_RELIC_USAGE).await;
}
let mut sub_affix_params = Vec::with_capacity(sub_affix_count);
let args = &args[4..];
for i in 0..sub_affix_count {
let sub_affix_id = args[i * 2].parse::<u32>()?;
let sub_affix_cnt = args[i * 2 + 1].parse::<u32>()?;
sub_affix_params.push((sub_affix_id, sub_affix_cnt));
}
let item_mgr = self.item_mgr.borrow();
item_mgr.give_relic(id, level, main_affix, sub_affix_params)?;
session
.send(
CMD_PLAYER_SYNC_SC_NOTIFY,
PlayerSyncScNotify {
relic_list: item_mgr.relic_list_proto(),
..Default::default()
},
)
.await?;
self.send_text(session, "Relic added successfully").await
}
async fn send_text(&self, session: &PlayerSession, content: &str) -> Result<()> {
session
.send(
CMD_GET_PRIVATE_CHAT_HISTORY_SC_RSP,
GetPrivateChatHistoryScRsp {
contact_id: 13371337,
chat_message_list: vec![ChatMessageData {
sender_id: 13371337,
message_type: MsgType::CustomText.into(),
timestamp: util::cur_timestamp_seconds(),
content: content.to_string(),
..Default::default()
}],
..Default::default()
},
)
.await
}
}

View file

@ -3,6 +3,7 @@ use atomic_refcell::AtomicRefCell;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
mod avatar; mod avatar;
mod command;
mod hero_basic_type; mod hero_basic_type;
mod item; mod item;
mod lineup; mod lineup;
@ -11,6 +12,7 @@ mod time;
mod tutorial; mod tutorial;
pub use avatar::AvatarManager; pub use avatar::AvatarManager;
pub use command::CommandManager;
pub use hero_basic_type::HeroBasicTypeManager; pub use hero_basic_type::HeroBasicTypeManager;
pub use item::ItemManager; pub use item::ItemManager;
pub use lineup::LineupManager; pub use lineup::LineupManager;

View file

@ -1,4 +1,3 @@
pub mod commands;
mod context; mod context;
mod gameplay_config; mod gameplay_config;
pub mod managers; pub mod managers;

View file

@ -5,10 +5,7 @@ pub use group::SceneGroup;
use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::atomic::{AtomicU32, Ordering};
use common::data::{ use common::data::{level::LevelAnchorInfo, LEVEL_TABLE};
level::{GroupLoadSide, LevelAnchorInfo},
LEVEL_TABLE,
};
use proto::{AvatarType, VectorBin}; use proto::{AvatarType, VectorBin};
use self::entity::{EntityType, SceneActor, SceneEntity, SceneProp}; use self::entity::{EntityType, SceneActor, SceneEntity, SceneProp};
@ -90,19 +87,11 @@ impl GameWorld {
pub fn init_groups(&mut self) { pub fn init_groups(&mut self) {
let floor = LEVEL_TABLE.level_floors.get(&self.floor_id).unwrap(); let floor = LEVEL_TABLE.level_floors.get(&self.floor_id).unwrap();
for group_instance in floor for group_instance in &floor.group_instance_list {
.group_instance_list
.iter()
.filter(|inst| !inst.is_delete)
{
let level_group = LEVEL_TABLE let level_group = LEVEL_TABLE
.get_level_group(self.floor_id, group_instance.id) .get_level_group(self.floor_id, group_instance.id)
.unwrap(); .unwrap();
if level_group.load_side == GroupLoadSide::Client {
continue;
}
let mut prop_entity_list = Vec::new(); let mut prop_entity_list = Vec::new();
if let Some(prop_list) = level_group.prop_list.as_ref() { if let Some(prop_list) = level_group.prop_list.as_ref() {
for level_prop in prop_list { for level_prop in prop_list {

View file

@ -1,10 +1,13 @@
use crate::{game::commands, util}; use crate::util;
use super::*; use super::*;
pub async fn on_send_msg_cs_req(session: &PlayerSession, body: &SendMsgCsReq) -> Result<()> { pub async fn on_send_msg_cs_req(session: &PlayerSession, body: &SendMsgCsReq) -> Result<()> {
let command_mgr = session.context.command_mgr.borrow();
if body.message_text.starts_with("/") { if body.message_text.starts_with("/") {
commands::execute_command(&body.message_text, session).await?; command_mgr
.execute_command(&body.message_text, session)
.await?;
} }
session session

View file

@ -59,7 +59,7 @@ impl NetPacket {
macro_rules! trait_handler { macro_rules! trait_handler {
($($name:ident $cmd_type:expr;)*) => { ($($name:ident $cmd_type:expr;)*) => {
pub trait NetCommandHandler { pub trait CommandHandler {
$( $(
paste! { paste! {
async fn [<on_$name:snake>](session: &PlayerSession, body: &$name) -> Result<()> { async fn [<on_$name:snake>](session: &PlayerSession, body: &$name) -> Result<()> {

View file

@ -10,7 +10,7 @@ use tokio::{
use crate::game::{GameContext, PlayerInfo}; use crate::game::{GameContext, PlayerInfo};
use super::{packet::NetCommandHandler, NetPacket}; use super::{packet::CommandHandler, NetPacket};
pub struct PlayerSession { pub struct PlayerSession {
client_socket: Arc<Mutex<TcpStream>>, client_socket: Arc<Mutex<TcpStream>>,
@ -82,4 +82,4 @@ impl PlayerSession {
} }
// Auto implemented // Auto implemented
impl NetCommandHandler for PlayerSession {} impl CommandHandler for PlayerSession {}

View file

@ -1,4 +1,3 @@
// This file is @generated by prost-build.
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct PlayerBasicCompBin { pub struct PlayerBasicCompBin {

View file

@ -1,7 +1,6 @@
{ {
"http_port": 21000, "http_port": 21000,
"dispatch_endpoint": "http://127.0.0.1:21041", "dispatch_endpoint": "http://127.0.0.1:21041",
"disable_password_check": true,
"database": { "database": {
"connection_string": "mongodb://127.0.0.1:27017", "connection_string": "mongodb://127.0.0.1:27017",
"name": "FireflySR", "name": "FireflySR",

View file

@ -14,7 +14,6 @@ pub struct SDKServerConfiguration {
pub http_port: u16, pub http_port: u16,
pub dispatch_endpoint: String, pub dispatch_endpoint: String,
pub database: DatabaseConfig, pub database: DatabaseConfig,
pub disable_password_check: bool,
} }
lazy_static! { lazy_static! {

View file

@ -3,7 +3,7 @@ use common::document::AccountDocument;
use serde::Deserialize; use serde::Deserialize;
use serde_json::json; use serde_json::json;
use crate::{config::CONFIGURATION, database, util, SdkContext}; use crate::{database, util, SdkContext};
const LOGIN: &str = "/:product_name/mdk/shield/api/login"; const LOGIN: &str = "/:product_name/mdk/shield/api/login";
const VERIFY: &str = "/:product_name/mdk/shield/api/verify"; const VERIFY: &str = "/:product_name/mdk/shield/api/verify";
@ -38,15 +38,9 @@ async fn login(
); );
} }
let mut password_opt: Option<String> = None; let Ok(password) = util::decrypt_string(&request.password) else {
if !CONFIGURATION.disable_password_check return fail_json(-10, "Your patch is outdated.\r\nGet new one at https://discord.gg/reversedrooms\r\n(Password decryption failed)");
{ };
if let Ok(password) = util::decrypt_string(&request.password) {
password_opt = Some(password);
} else {
return fail_json(-10, "Your patch is outdated.\r\nGet new one at https://discord.gg/reversedrooms\r\n(Password decryption failed)");
};
}
let account = match database::get_account_by_name(&context.db_client, &request.account).await { let account = match database::get_account_by_name(&context.db_client, &request.account).await {
Ok(Some(account)) => account, Ok(Some(account)) => account,
@ -54,7 +48,7 @@ async fn login(
Err(_) => return fail_json(-1, "Internal server error"), Err(_) => return fail_json(-1, "Internal server error"),
}; };
if !CONFIGURATION.disable_password_check && util::verify_password(&password_opt.unwrap(), &account.account_password).is_err() { if util::verify_password(&password, &account.account_password).is_err() {
return fail_json(-101, "Account or password error"); return fail_json(-101, "Account or password error");
} }