From 3d2b7078e4d295e9e3d5923a8f5db3620ee3d305 Mon Sep 17 00:00:00 2001 From: "NG (Graham)" Date: Sat, 28 Feb 2026 10:51:12 -0500 Subject: [PATCH] Implement vehicle validation before match --- Cargo.lock | 1 + rc_core/src/data/player_data.rs | 2 +- rc_core/src/persist/combat.rs | 3 + rc_core/src/persist/config/cubes_json.rs | 11 ++ rc_core/src/persist/config/mod.rs | 2 +- rc_core/src/persist/config/traits.rs | 9 ++ rc_core/src/persist/mod.rs | 3 + rc_core/src/persist/multiplayer.rs | 9 ++ rc_core/src/persist/singleplayer.rs | 12 ++ rc_core/src/persist/user/account_json.rs | 30 ++++ rc_core/src/persist/user/traits.rs | 1 + rc_core/src/persist/vehicle_validator.rs | 22 +++ rc_plugins/src/chat/c_binding.rs | 2 +- rc_plugins/src/lib.rs | 7 +- .../src/vehicle_validation/c_binding.rs | 48 +++++++ rc_plugins/src/vehicle_validation/mod.rs | 5 + rc_plugins/src/vehicle_validation/plugin.rs | 25 ++++ rc_services_room/Cargo.toml | 1 + rc_services_room/src/data/mod.rs | 1 + .../src/data/vehicle_validation.rs | 22 +++ rc_services_room/src/main.rs | 9 ++ .../src/operations/battle_arena_config.rs | 2 +- rc_services_room/src/operations/mod.rs | 7 +- .../src/operations/validate_machine.rs | 46 ------ .../validate_vehicle_by_campaign.rs | 48 +++++++ .../operations/validate_vehicle_by_lobby.rs | 58 ++++++++ rc_services_room/src/vehicle_validators.rs | 136 ++++++++++++++++++ 27 files changed, 468 insertions(+), 54 deletions(-) create mode 100644 rc_core/src/persist/vehicle_validator.rs create mode 100644 rc_plugins/src/vehicle_validation/c_binding.rs create mode 100644 rc_plugins/src/vehicle_validation/mod.rs create mode 100644 rc_plugins/src/vehicle_validation/plugin.rs create mode 100644 rc_services_room/src/data/vehicle_validation.rs delete mode 100644 rc_services_room/src/operations/validate_machine.rs create mode 100644 rc_services_room/src/operations/validate_vehicle_by_campaign.rs create mode 100644 rc_services_room/src/operations/validate_vehicle_by_lobby.rs create mode 100644 rc_services_room/src/vehicle_validators.rs diff --git a/Cargo.lock b/Cargo.lock index 1977d3c..3cfe48c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2661,6 +2661,7 @@ dependencies = [ "oj_polariton_auth", "oj_rc_core", "oj_rc_factory", + "oj_rc_plugins", "oj_serdes", "polariton", "polariton_server", diff --git a/rc_core/src/data/player_data.rs b/rc_core/src/data/player_data.rs index 21cc050..badc67f 100644 --- a/rc_core/src/data/player_data.rs +++ b/rc_core/src/data/player_data.rs @@ -108,7 +108,7 @@ impl PlayerDatas { pub fn as_transmissible(&self) -> Typed { let mut buf = Vec::new(); let write_size = self.dump(&mut std::io::Cursor::new(&mut buf)).unwrap(); - log::debug!("PlayerDatas serialized to {} bytes: {:?}", write_size, buf); + log::debug!("PlayerDatas serialized to {} bytes", write_size); Typed::Bytes(buf.into()) } } diff --git a/rc_core/src/persist/combat.rs b/rc_core/src/persist/combat.rs index a9c95c4..aedbdf5 100644 --- a/rc_core/src/persist/combat.rs +++ b/rc_core/src/persist/combat.rs @@ -428,6 +428,7 @@ pub(super) fn default_campaigns() -> super::SingleplayerConfig { time_max: 60, } ], + vehicle_validator: super::VehicleValidator::None, } ], vehicles: vec![ @@ -454,6 +455,7 @@ pub(super) fn default_campaigns() -> super::SingleplayerConfig { ], max_teammates: 0, max_enemies: 5, + vehicle_validator: super::VehicleValidator::None, } } @@ -629,6 +631,7 @@ fn default_multiplayer() -> super::MultiplayerConfig { battle_arena: super::multiplayer::default_ba_conf(), pit_config: super::multiplayer::default_pit_conf(), team_death_match: super::multiplayer::default_tdm_conf(), + vehicle_validator: super::multiplayer::default_validator(), } } diff --git a/rc_core/src/persist/config/cubes_json.rs b/rc_core/src/persist/config/cubes_json.rs index 5bbe960..d78db9f 100644 --- a/rc_core/src/persist/config/cubes_json.rs +++ b/rc_core/src/persist/config/cubes_json.rs @@ -553,4 +553,15 @@ impl super::ConfigProvider for CubeConfig { } map } + + fn vehicle_validation(&self) -> super::VehicleValidators { + super::VehicleValidators { + multiplayer: self.battle.multiplayer.vehicle_validator.clone(), + custom_game: crate::persist::VehicleValidator::None, // TODO + singleplayer: self.battle.singleplayer.vehicle_validator.clone(), + campaigns: self.battle.singleplayer.campaigns.iter() + .map(|campaign| (campaign.id.clone(), campaign.vehicle_validator.clone())) + .collect(), + } + } } diff --git a/rc_core/src/persist/config/mod.rs b/rc_core/src/persist/config/mod.rs index 3dfe18e..5b7b645 100644 --- a/rc_core/src/persist/config/mod.rs +++ b/rc_core/src/persist/config/mod.rs @@ -2,7 +2,7 @@ mod cubes_json; pub use cubes_json::CubeConfig; mod traits; -pub use traits::{ConfigProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig, BattleArenaResolver, PitSettings, PitWinCondition, TeamDeathMatchSettings, ShopEntriesResolver, ShopAction, ShopGain, PromoCode, MultiplayerSettings, BattleArenaCrystalParams}; +pub use traits::{ConfigProvider, DevMessageProvider, ServerConfig, GarageUpgrades, GarageUpgradeIncrement, ChatSystemConfig, GameEventSequence, GameEvents, GameRotationStrategy, GameEvent, GameMap, GameVisibility, GameType, SingleplayerConfig, VehicleInfo, VehicleDescriptor, QueueChangeMode, Point, Sphere, MapConfig, LinksConfig, FakePlayer, ClientEmulator, EnergyConfig, BattleArenaResolver, PitSettings, PitWinCondition, TeamDeathMatchSettings, ShopEntriesResolver, ShopAction, ShopGain, PromoCode, MultiplayerSettings, BattleArenaCrystalParams, VehicleValidators}; mod validation; pub use validation::{SelfValidator, ValidationInfo, ValidationMessage}; diff --git a/rc_core/src/persist/config/traits.rs b/rc_core/src/persist/config/traits.rs index f89710f..569cb84 100644 --- a/rc_core/src/persist/config/traits.rs +++ b/rc_core/src/persist/config/traits.rs @@ -40,6 +40,7 @@ pub trait ConfigProvider { fn tdm_settings(&self) -> TeamDeathMatchSettings; fn shop_entries(&self) -> ShopEntriesResolver; fn promo_codes(&self) -> std::collections::HashMap; + fn vehicle_validation(&self) -> VehicleValidators; } pub struct DevMessageProvider { @@ -536,3 +537,11 @@ pub struct MultiplayerSettings { pub lobby_autostart_after: Option, pub loading_autostart_after: Option, } + +pub struct VehicleValidators { + // FIXME don't use serializable types in traits + pub multiplayer: crate::persist::VehicleValidator, + pub custom_game: crate::persist::VehicleValidator, + pub singleplayer: crate::persist::VehicleValidator, + pub campaigns: std::collections::HashMap, +} diff --git a/rc_core/src/persist/mod.rs b/rc_core/src/persist/mod.rs index a7e9b0c..2e87a1f 100644 --- a/rc_core/src/persist/mod.rs +++ b/rc_core/src/persist/mod.rs @@ -44,6 +44,9 @@ pub use maps::{MapsConfig, MapConfig}; mod item_shop; pub use item_shop::{ItemShopConfig, ItemBundle}; +mod vehicle_validator; +pub use vehicle_validator::VehicleValidator; + const VALID_ROBOT: &[u8] = &[64, 0, 0, diff --git a/rc_core/src/persist/multiplayer.rs b/rc_core/src/persist/multiplayer.rs index 258c2f1..b9694c7 100644 --- a/rc_core/src/persist/multiplayer.rs +++ b/rc_core/src/persist/multiplayer.rs @@ -20,6 +20,8 @@ pub struct MultiplayerConfig { pub pit_config: PitConfig, #[serde(default = "default_tdm_conf")] pub team_death_match: TeamDeathMatchConfig, + #[serde(default = "default_validator")] + pub vehicle_validator: super::VehicleValidator, } impl super::config::SelfValidator for MultiplayerConfig { @@ -525,3 +527,10 @@ pub(super) fn default_tdm_conf() -> TeamDeathMatchConfig { self_destruct_is_kill: true, } } + +pub(super) fn default_validator() -> super::VehicleValidator { + super::VehicleValidator::Cpu { + min: 100, + max: 2_000, + } +} diff --git a/rc_core/src/persist/singleplayer.rs b/rc_core/src/persist/singleplayer.rs index e0beffa..8a0e7a8 100644 --- a/rc_core/src/persist/singleplayer.rs +++ b/rc_core/src/persist/singleplayer.rs @@ -7,6 +7,8 @@ pub struct SingleplayerConfig { pub vehicles: Vec, pub max_teammates: u32, pub max_enemies: u32, + #[serde(default = "default_singleplayer_vehicle_validator")] + pub vehicle_validator: super::VehicleValidator, } impl SingleplayerConfig { @@ -62,6 +64,8 @@ pub struct Campaign { pub map: String, pub campaign_type: CampaignType, pub waves: Vec, + #[serde(default = "default_campaign_vehicle_validator")] + pub vehicle_validator: super::VehicleValidator, } impl Campaign { @@ -193,3 +197,11 @@ impl std::convert::From for crate::data::campaign::CampaignType { fn default_campaigns() -> Vec { super::combat::default_campaigns().campaigns } + +fn default_campaign_vehicle_validator() -> super::VehicleValidator { + super::VehicleValidator::None +} + +fn default_singleplayer_vehicle_validator() -> super::VehicleValidator { + super::VehicleValidator::None +} diff --git a/rc_core/src/persist/user/account_json.rs b/rc_core/src/persist/user/account_json.rs index 5f138b3..bc8d8ad 100644 --- a/rc_core/src/persist/user/account_json.rs +++ b/rc_core/src/persist/user/account_json.rs @@ -859,6 +859,36 @@ impl super::User for UserData { }) } + async fn selected_vehicle_data(&self) -> Result { + let garage = self.db.garage_selected(self.account.id).await + .map_err(|e| { + log::error!("Failed to retrieve selected garage data for user_id {}: {}", self.account.id, e); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::DatabaseError as i16, + "Failed to retrieve selected garage data for user".to_owned(), + ) + })? + .ok_or_else(|| { + log::error!("No selected garage data for user_id {}", self.account.id); + polariton_server::operations::SimpleOpError::with_message( + crate::data::error_codes::WebServicesError::UnexpectedError as i16, + "No selected garage data for user".to_owned(), + ) + })?; + Ok(super::VehicleData { + name: Some(garage.name), + slot: garage.slot, + robot_data: garage.robot_data, + colour_data: garage.colour_data, + weapon_order: oj_rc_database::schema::parse_int_csv(&garage.weapon_order) + .into_iter() + .map(|x| x as i32) + .collect(), + crf_id: garage.crf_id, + was_rated: Some(garage.was_rated), + }) + } + async fn all_slots(&self) -> super::UserSlots { let slots = match self.all_vehicles().await { Ok(slots) => slots, diff --git a/rc_core/src/persist/user/traits.rs b/rc_core/src/persist/user/traits.rs index 9da58fa..434062e 100644 --- a/rc_core/src/persist/user/traits.rs +++ b/rc_core/src/persist/user/traits.rs @@ -78,6 +78,7 @@ pub trait User: ChatUser + SocialUser + SocialUserC + LobbyUser + Multipla async fn unlock_parts(&self, parts: &[u32]) -> Result<(), polariton_server::operations::SimpleOpError>; async fn selected_garage(&self) -> (String, u32); async fn select_garage(&self, slot: i32) -> Result<(), i16>; + async fn selected_vehicle_data(&self) -> Result; async fn all_slots(&self) -> UserSlots; async fn slot_by_id(&self, id: i32) -> Result, i16>; async fn save_slot(&self, vehicle: VehicleData, cpu_counter: &crate::cubes::CpuListParser) -> Result<(), i16>; diff --git a/rc_core/src/persist/vehicle_validator.rs b/rc_core/src/persist/vehicle_validator.rs new file mode 100644 index 0000000..57754a3 --- /dev/null +++ b/rc_core/src/persist/vehicle_validator.rs @@ -0,0 +1,22 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(tag = "validator")] +pub enum VehicleValidator { + None, + Cpu { + min: u32, + max: u32, + }, + // TODO other validators + All { + all: Vec, + }, + Any { + any: Vec, + }, + Custom { + #[serde(alias = "library")] + path: String, + }, +} diff --git a/rc_plugins/src/chat/c_binding.rs b/rc_plugins/src/chat/c_binding.rs index 3dd8f92..887764b 100644 --- a/rc_plugins/src/chat/c_binding.rs +++ b/rc_plugins/src/chat/c_binding.rs @@ -19,7 +19,7 @@ impl ChatCPlugin { let dll = unsafe { libloading::Library::new(file.as_ref()) }?; Ok(Self { dll, - pretty_name: file.as_ref().to_string_lossy().to_string(), + pretty_name: file.as_ref().display().to_string(), provider: std::sync::Mutex::new(None), }) } diff --git a/rc_plugins/src/lib.rs b/rc_plugins/src/lib.rs index 949c695..4b0b175 100644 --- a/rc_plugins/src/lib.rs +++ b/rc_plugins/src/lib.rs @@ -1,3 +1,8 @@ pub mod chat; +pub mod vehicle_validation; -pub trait Plugin: Send + Sync {} +pub trait Plugin: Send + Sync { + fn self_check(&self) -> bool { + true + } +} diff --git a/rc_plugins/src/vehicle_validation/c_binding.rs b/rc_plugins/src/vehicle_validation/c_binding.rs new file mode 100644 index 0000000..7bb9184 --- /dev/null +++ b/rc_plugins/src/vehicle_validation/c_binding.rs @@ -0,0 +1,48 @@ +//! The foreign function interface implementation for validation vehicles in different shared objects/libraries. +//use std::ffi::{CString, c_char, CStr}; + +const VALIDATE_VEHICLE_SYMBOL_NAME: &[u8] = b"oj_rc_validate_vehicle"; +const VALIDATE_VEHICLE_SYMBOL_NAME_STR: &str = "oj_rc_validate_vehicle"; + +pub struct VehicleValidatorCPlugin { + dll: libloading::Library, + pretty_name: String, +} + +impl VehicleValidatorCPlugin { + pub fn new(file: impl AsRef) -> Result { + let dll = unsafe { libloading::Library::new(file.as_ref()) }?; + Ok(Self { + dll, + pretty_name: file.as_ref().display().to_string(), + }) + } +} + +impl super::VehicleValidatorPlugin for VehicleValidatorCPlugin { + fn validate(&self, cube_data: &[u8], colour_data: &[u8]) -> super::ValidationResultCode { + let func: libloading::Symbol u8> = match unsafe { self.dll.get(VALIDATE_VEHICLE_SYMBOL_NAME) } { + Ok(x) => x, + Err(e) => { + log::error!("Failed to find symbol {} in library {}: {}", VALIDATE_VEHICLE_SYMBOL_NAME_STR, self.pretty_name, e); + return super::ValidationResultCode::Invalid; + } + }; + let cubes_len = cube_data.len() as u32; + let cubes = cube_data.as_ptr(); + let colour_len = colour_data.len() as u32; + let colours = colour_data.as_ptr(); + let code = unsafe { + func(cubes_len, cubes, colour_len, colours) + }; + super::ValidationResultCode::from_u8(code) + } +} + +impl crate::Plugin for VehicleValidatorCPlugin { + fn self_check(&self) -> bool { + unsafe { + self.dll.get:: u8>(VALIDATE_VEHICLE_SYMBOL_NAME) + }.is_ok() + } +} diff --git a/rc_plugins/src/vehicle_validation/mod.rs b/rc_plugins/src/vehicle_validation/mod.rs new file mode 100644 index 0000000..35de136 --- /dev/null +++ b/rc_plugins/src/vehicle_validation/mod.rs @@ -0,0 +1,5 @@ +mod plugin; +pub use plugin::{ValidationResultCode, VehicleValidatorPlugin}; + +mod c_binding; +pub use c_binding::VehicleValidatorCPlugin; diff --git a/rc_plugins/src/vehicle_validation/plugin.rs b/rc_plugins/src/vehicle_validation/plugin.rs new file mode 100644 index 0000000..2fe3387 --- /dev/null +++ b/rc_plugins/src/vehicle_validation/plugin.rs @@ -0,0 +1,25 @@ +#[repr(u8)] +pub enum ValidationResultCode { + Invalid = 0, + Ok = 1, + NoWeapon = 2, + NoMovement = 3, + Sanctioned = 4, +} + +impl ValidationResultCode { + pub(super) fn from_u8(num: u8) -> Self { + match num { + 0 => Self::Invalid, + 1 => Self::Ok, + 2 => Self::NoWeapon, + 3 => Self::NoMovement, + 4 => Self::Sanctioned, + _ => Self::Invalid, + } + } +} + +pub trait VehicleValidatorPlugin: crate::Plugin { + fn validate(&self, cube_data: &[u8], colour_data: &[u8]) -> ValidationResultCode; +} diff --git a/rc_services_room/Cargo.toml b/rc_services_room/Cargo.toml index 4ae8612..8a87ca0 100644 --- a/rc_services_room/Cargo.toml +++ b/rc_services_room/Cargo.toml @@ -27,3 +27,4 @@ async-trait.workspace = true git-version.workspace = true libfj.workspace = true oj_serdes.workspace = true +oj_rc_plugins = { version = "*", path = "../rc_plugins" } diff --git a/rc_services_room/src/data/mod.rs b/rc_services_room/src/data/mod.rs index b8dc1ad..d096a5a 100644 --- a/rc_services_room/src/data/mod.rs +++ b/rc_services_room/src/data/mod.rs @@ -29,3 +29,4 @@ pub mod quest; pub mod score_multipliers; //pub use oj_rc_core::data::campaign; pub use oj_rc_core::data::crf; +pub mod vehicle_validation; diff --git a/rc_services_room/src/data/vehicle_validation.rs b/rc_services_room/src/data/vehicle_validation.rs new file mode 100644 index 0000000..f960700 --- /dev/null +++ b/rc_services_room/src/data/vehicle_validation.rs @@ -0,0 +1,22 @@ +// possible codes +#[allow(dead_code)] +#[repr(u8)] +pub enum ValidateMachineResult { + Invalid = 0, + Ok = 1, + NoWeapon = 2, + NoMovement = 3, + Sanctioned = 4, +} + +impl ValidateMachineResult { + pub fn from_plugin(code: oj_rc_plugins::vehicle_validation::ValidationResultCode) -> Self { + match code { + oj_rc_plugins::vehicle_validation::ValidationResultCode::Invalid => Self::Invalid, + oj_rc_plugins::vehicle_validation::ValidationResultCode::Ok => Self::Ok, + oj_rc_plugins::vehicle_validation::ValidationResultCode::NoWeapon => Self::NoWeapon, + oj_rc_plugins::vehicle_validation::ValidationResultCode::NoMovement => Self::NoMovement, + oj_rc_plugins::vehicle_validation::ValidationResultCode::Sanctioned => Self::Sanctioned, + } + } +} diff --git a/rc_services_room/src/main.rs b/rc_services_room/src/main.rs index 505f627..1112531 100644 --- a/rc_services_room/src/main.rs +++ b/rc_services_room/src/main.rs @@ -4,6 +4,7 @@ mod cli; mod data; mod events; mod operations; +mod vehicle_validators; use oj_polariton_auth::Handshake; use tokio::net; @@ -23,6 +24,7 @@ pub struct InitConfig { pub users: std::sync::Arc, pub factory: std::sync::Arc, pub parsers: oj_rc_core::cubes::CubeParsers, + pub vehicle_validators: vehicle_validators::InitedVehicleValidators, } #[tokio::main] @@ -35,11 +37,18 @@ async fn main() -> std::io::Result<()> { let users = std::sync::Arc::new(oj_rc_core::persist::user::UserImpl::load(&args.data, &cubes).await.expect("Bad user data")); let factory = std::sync::Arc::new(>::factory(&cubes, &|| users.factory_impl()).await.expect("Bad vehicle factory (CRF) config")); let parsers = oj_rc_core::cubes::CubeParsers::new(&cubes); + let vehicle_validator_plugins_path = std::path::PathBuf::from(&args.data).join("plugins/vehicle_validation"); + let vehicle_validators = vehicle_validators::validators_from_conf( + &>::vehicle_validation(&cubes), + &parsers, + vehicle_validator_plugins_path, + ); let init_ctx = std::sync::Arc::new(InitConfig { cubes, users, factory, parsers, + vehicle_validators }); let server = std::sync::Arc::new(polariton_server::Server::new(operations::handler(&init_ctx), polariton_server::events::EventsHandler::new())); diff --git a/rc_services_room/src/operations/battle_arena_config.rs b/rc_services_room/src/operations/battle_arena_config.rs index a29df26..6e43bf2 100644 --- a/rc_services_room/src/operations/battle_arena_config.rs +++ b/rc_services_room/src/operations/battle_arena_config.rs @@ -19,7 +19,7 @@ impl SimpleOperation for BattleArenaConfigurer { async fn handle(&self, params: ParameterTable, user: &Self::User) -> Result, SimpleOpError> { let user_info = user.user()?; - log::warn!("Retrieved ba config"); + //log::warn!("Retrieved ba config"); let data = self.ba_conf.resolve_typed(user_info.as_ref().as_ref(), self.factory.as_ref(), &self.weapon_list, &self.cpu_counter).await?; let mut params = params.to_dict(); params.insert(PARAM_KEY, data); diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs index e0c4343..4e6c458 100644 --- a/rc_services_room/src/operations/mod.rs +++ b/rc_services_room/src/operations/mod.rs @@ -75,7 +75,8 @@ mod reconnect_game; mod regen_config; mod pageantry; mod signup_time; -mod validate_machine; +mod validate_vehicle_by_lobby; +mod validate_vehicle_by_campaign; mod game_mode_config; mod score_multipliers_config; mod player_robot_rank; @@ -202,11 +203,11 @@ pub fn handler(init_ctx: &crate::InitConfig) -> OperationsHandler .add(regen_config::auto_regen_config_provider(&init_ctx.cubes)) .add(pageantry::after_battle_vote_thresholds_provider(&init_ctx.cubes)) .add(signup_time::user_signup_date_provider()) - .add(validate_machine::validate_robot_provider()) + .add(validate_vehicle_by_lobby::validate_robot_provider(&init_ctx.vehicle_validators)) .add(game_mode_config::game_mode_config_provider(&init_ctx.cubes)) .add(score_multipliers_config::tdm_ai_score_config_provider()) .add(player_robot_rank::player_robot_rank_provider()) - .add(validate_machine::validate_campaign_robot_provider()) + .add(validate_vehicle_by_campaign::validate_campaign_robot_provider(&init_ctx.vehicle_validators)) .add(singleplayer_campaigns::singleplayer_complete_campaign_provider(init_ctx)) .add(campaign_save_result::campaign_save_awards_provider()) .add(singleplayer_campaigns::singleplayer_save_complete_campaign_provider()) // TODO handle UpdatePlayerCompletedCampaignWaveRequest saving diff --git a/rc_services_room/src/operations/validate_machine.rs b/rc_services_room/src/operations/validate_machine.rs deleted file mode 100644 index 48319d3..0000000 --- a/rc_services_room/src/operations/validate_machine.rs +++ /dev/null @@ -1,46 +0,0 @@ -use polariton_server::operations::SimpleFunc; -use polariton::operation::{ParameterTable, Typed}; - -const LOBBY_PARAM_KEY: u8 = 134; -const VALIDATE_ROBOT_RESULT_PARAM_KEY: u8 = 111; - -// possible codes -#[allow(dead_code)] -#[repr(u8)] -enum ValidateMachineResult { - Invalid = 0, - Ok = 1, - NoWeapon = 2, - NoMovement = 3, - Sanctioned = 4, -} - -pub(super) fn validate_robot_provider() -> SimpleFunc<102, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { - SimpleFunc::new(|params, _user: &crate::UserTy| { - let mut params = params.to_dict(); - if let Some(Typed::Int(lobby_ty)) = params.get(&LOBBY_PARAM_KEY) { - log::info!("Got lobby type {} ({:?})", lobby_ty, oj_rc_core::data::lobby::LobbyType::from_int(*lobby_ty)); - } - // let lock = user.read().unwrap(); - // let user_info = lock.user()?; - // TODO actually validate the vehicle - params.insert(VALIDATE_ROBOT_RESULT_PARAM_KEY, Typed::Int(ValidateMachineResult::Ok as _)); - Ok(params.into()) - }) -} - -const CAMPAIGN_ID_PARAM_KEY: u8 = 22; - -pub(super) fn validate_campaign_robot_provider() -> SimpleFunc<59, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { - SimpleFunc::new(|params, _user: &crate::UserTy| { - let mut params = params.to_dict(); - if let Some(Typed::Str(campaign_id)) = params.get(&CAMPAIGN_ID_PARAM_KEY) { - log::info!("Got campaign id {}", campaign_id.string); - } - // let lock = user.read().unwrap(); - // let user_info = lock.user()?; - // TODO actually validate the vehicle - params.insert(VALIDATE_ROBOT_RESULT_PARAM_KEY, Typed::Int(ValidateMachineResult::Ok as _)); // this is ignored - Ok(params.into()) - }) -} diff --git a/rc_services_room/src/operations/validate_vehicle_by_campaign.rs b/rc_services_room/src/operations/validate_vehicle_by_campaign.rs new file mode 100644 index 0000000..7284b99 --- /dev/null +++ b/rc_services_room/src/operations/validate_vehicle_by_campaign.rs @@ -0,0 +1,48 @@ +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; +use polariton::operation::{ParameterTable, Typed}; + +use crate::data::vehicle_validation::*; + +const VALIDATE_CAMPAIGN_ROBOT_CODE: u8 = 59; + +const CAMPAIGN_ID_PARAM_KEY: u8 = 22; +const VALIDATE_ROBOT_RESULT_PARAM_KEY: u8 = 111; + +pub(super) struct CampaignVehicleValidator { + campaign_map: std::sync::Arc>, +} + +#[async_trait::async_trait] +impl SimpleOperation for CampaignVehicleValidator { + type User = crate::UserTy; + const CODE: u8 = VALIDATE_CAMPAIGN_ROBOT_CODE; + + async fn handle(&self, params: ParameterTable, user: &Self::User) -> Result, SimpleOpError> { + let mut params = params.to_dict(); + if let Some(Typed::Str(campaign_id)) = params.get(&CAMPAIGN_ID_PARAM_KEY) { + log::debug!("Got campaign id {}", campaign_id.string); + if let Some(campaign_validator) = self.campaign_map.get(&campaign_id.string) { + let user_info = user.user()?; + let vehicle_data = user_info.selected_vehicle_data().await?; + let result_code = ValidateMachineResult::from_plugin(campaign_validator.validate( + &vehicle_data.robot_data, + &vehicle_data.colour_data, + )); + params.insert(VALIDATE_ROBOT_RESULT_PARAM_KEY, Typed::Int(result_code as _)); + } else { + // bad state, assume user is doing something sketchy + log::warn!("Failed to find vehicle validator for campaign {}", campaign_id.string); + params.insert(VALIDATE_ROBOT_RESULT_PARAM_KEY, Typed::Int(ValidateMachineResult::Sanctioned as _)); + } + } else { + log::warn!("No campaign ID provided for campaign vehicle validator"); + } + Ok(params.into()) + } +} + +pub(super) fn validate_campaign_robot_provider(validators: &crate::vehicle_validators::InitedVehicleValidators) -> SimpleOpImpl { + SimpleOpImpl::new(CampaignVehicleValidator { + campaign_map: validators.campaigns.clone(), + }) +} diff --git a/rc_services_room/src/operations/validate_vehicle_by_lobby.rs b/rc_services_room/src/operations/validate_vehicle_by_lobby.rs new file mode 100644 index 0000000..9748d0d --- /dev/null +++ b/rc_services_room/src/operations/validate_vehicle_by_lobby.rs @@ -0,0 +1,58 @@ +use polariton_server::operations::{SimpleOpError, SimpleOperation, SimpleOpImpl}; +use polariton::operation::{ParameterTable, Typed}; + +use crate::data::vehicle_validation::*; + +const VALIDATE_LOBBY_ROBOT_CODE: u8 = 102; + +const LOBBY_PARAM_KEY: u8 = 134; +const VALIDATE_ROBOT_RESULT_PARAM_KEY: u8 = 111; + +pub(super) struct MultiplayerVehicleValidator { + multiplayer: std::sync::Arc, + custom_game: std::sync::Arc, + singleplayer: std::sync::Arc, +} + +#[async_trait::async_trait] +impl SimpleOperation for MultiplayerVehicleValidator { + type User = crate::UserTy; + const CODE: u8 = VALIDATE_LOBBY_ROBOT_CODE; + + async fn handle(&self, params: ParameterTable, user: &Self::User) -> Result, SimpleOpError> { + let mut params = params.to_dict(); + if let Some(Typed::Int(lobby_ty)) = params.get(&LOBBY_PARAM_KEY) { + let lobby_ty = oj_rc_core::data::lobby::LobbyType::from_int(*lobby_ty)?; + log::debug!("Got lobby type {:?}", lobby_ty); + let user_info = user.user()?; + let vehicle_data = user_info.selected_vehicle_data().await?; + let result_code = match lobby_ty { + oj_rc_core::data::lobby::LobbyType::None => ValidateMachineResult::Ok, + oj_rc_core::data::lobby::LobbyType::CustomGame => ValidateMachineResult::from_plugin(self.custom_game.validate( + &vehicle_data.robot_data, + &vehicle_data.colour_data, + )), + oj_rc_core::data::lobby::LobbyType::QuickPlay => ValidateMachineResult::from_plugin(self.multiplayer.validate( + &vehicle_data.robot_data, + &vehicle_data.colour_data, + )), + oj_rc_core::data::lobby::LobbyType::Solo => ValidateMachineResult::from_plugin(self.singleplayer.validate( + &vehicle_data.robot_data, + &vehicle_data.colour_data, + )), + }; + params.insert(VALIDATE_ROBOT_RESULT_PARAM_KEY, Typed::Int(result_code as _)); + } else { + log::warn!("No lobby type provided for vehicle validator"); + } + Ok(params.into()) + } +} + +pub(super) fn validate_robot_provider(validators: &crate::vehicle_validators::InitedVehicleValidators) -> SimpleOpImpl { + SimpleOpImpl::new(MultiplayerVehicleValidator { + multiplayer: validators.multiplayer.clone(), + custom_game: validators.custom_game.clone(), + singleplayer: validators.singleplayer.clone(), + }) +} diff --git a/rc_services_room/src/vehicle_validators.rs b/rc_services_room/src/vehicle_validators.rs new file mode 100644 index 0000000..06b16be --- /dev/null +++ b/rc_services_room/src/vehicle_validators.rs @@ -0,0 +1,136 @@ +pub type InitedVehicleValidator = Box; + +pub struct InitedVehicleValidators { + pub multiplayer: std::sync::Arc, + pub custom_game: std::sync::Arc, + pub singleplayer: std::sync::Arc, + pub campaigns: std::sync::Arc>, +} + +pub fn validators_from_conf(conf: &oj_rc_core::persist::config::VehicleValidators, parsers: &oj_rc_core::cubes::CubeParsers, plugins_path: impl AsRef) -> InitedVehicleValidators { + let plugins_path = plugins_path.as_ref(); + InitedVehicleValidators { + multiplayer: std::sync::Arc::new(validator_from_conf(&conf.multiplayer, parsers, plugins_path)), + custom_game: std::sync::Arc::new(validator_from_conf(&conf.multiplayer, parsers, plugins_path)), + singleplayer: std::sync::Arc::new(validator_from_conf(&conf.singleplayer, parsers, plugins_path)), + campaigns: std::sync::Arc::new( + conf.campaigns.iter() + .map(|(campaign_id, validator)| (campaign_id.to_owned(), validator_from_conf(validator, parsers, plugins_path))) + .collect() + ), + } +} + +fn validator_from_conf(conf: &oj_rc_core::persist::VehicleValidator, parsers: &oj_rc_core::cubes::CubeParsers, plugins_path: impl AsRef) -> InitedVehicleValidator { + match conf { + oj_rc_core::persist::VehicleValidator::None => Box::new(AlwaysValid) as _, + oj_rc_core::persist::VehicleValidator::Cpu { min, max } => Box::new(CpuRange { + range: *min..=*max, + parser: parsers.cpu_counter(), + }) as _, + oj_rc_core::persist::VehicleValidator::All { all } => Box::new(All::init(all, parsers, plugins_path)) as _, + oj_rc_core::persist::VehicleValidator::Any { any } => Box::new(Any::init(any, parsers, plugins_path)) as _, + oj_rc_core::persist::VehicleValidator::Custom { path } => { + let full_path = plugins_path.as_ref().join(path); + log::warn!("Custom vehicle validator plugin {} is experimental and insecure", full_path.display()); + let result = oj_rc_plugins::vehicle_validation::VehicleValidatorCPlugin::new(&full_path); + match result { + Ok(c_plugin) => Box::new(c_plugin) as _, + Err(e) => { + log::error!("Failed to load custom vehicle validator plugin {}: {} (crashing!)", full_path.display(), e); + panic!("Failed to load custom vehicle validator plugin {}: {}", full_path.display(), e) + } + } + }, + } +} + +struct AlwaysValid; + +impl oj_rc_plugins::vehicle_validation::VehicleValidatorPlugin for AlwaysValid { + fn validate(&self, _cube_data: &[u8], _colour_data: &[u8]) -> oj_rc_plugins::vehicle_validation::ValidationResultCode { + oj_rc_plugins::vehicle_validation::ValidationResultCode::Ok + } +} + +impl oj_rc_plugins::Plugin for AlwaysValid {} + +struct CpuRange { + range: std::ops::RangeInclusive, + parser: std::sync::Arc, +} + +impl oj_rc_plugins::vehicle_validation::VehicleValidatorPlugin for CpuRange { + fn validate(&self, cube_data: &[u8], _colour_data: &[u8]) -> oj_rc_plugins::vehicle_validation::ValidationResultCode { + let cpu_info = self.parser.calculate_cpu(&mut std::io::Cursor::new(cube_data)); + if self.range.contains(&(cpu_info.total - cpu_info.cosmetic)) { + oj_rc_plugins::vehicle_validation::ValidationResultCode::Ok + } else { + oj_rc_plugins::vehicle_validation::ValidationResultCode::Invalid + } + } +} + +impl oj_rc_plugins::Plugin for CpuRange {} + +struct All(Vec); + +impl All { + fn init(items: &[oj_rc_core::persist::VehicleValidator], parsers: &oj_rc_core::cubes::CubeParsers, plugins_path: impl AsRef) -> Self { + let plugins_path = plugins_path.as_ref(); + All( + items.iter() + .map(|item| validator_from_conf(item, parsers, plugins_path)) + .collect() + ) + } +} + +impl oj_rc_plugins::vehicle_validation::VehicleValidatorPlugin for All { + fn validate(&self, cube_data: &[u8], colour_data: &[u8]) -> oj_rc_plugins::vehicle_validation::ValidationResultCode { + self.0.iter() + .filter_map(|item| { + let code = item.validate(cube_data, colour_data); + if matches!(code, oj_rc_plugins::vehicle_validation::ValidationResultCode::Ok) { + None + } else { + Some(code) + } + }) + .next() + .unwrap_or(oj_rc_plugins::vehicle_validation::ValidationResultCode::Ok) + } +} + +impl oj_rc_plugins::Plugin for All {} + +struct Any(Vec); + +impl Any { + fn init(items: &[oj_rc_core::persist::VehicleValidator], parsers: &oj_rc_core::cubes::CubeParsers, plugins_path: impl AsRef) -> Self { + let plugins_path = plugins_path.as_ref(); + Any( + items.iter() + .map(|item| validator_from_conf(item, parsers, plugins_path)) + .collect() + ) + } +} + +impl oj_rc_plugins::vehicle_validation::VehicleValidatorPlugin for Any { + fn validate(&self, cube_data: &[u8], colour_data: &[u8]) -> oj_rc_plugins::vehicle_validation::ValidationResultCode { + self.0.iter() + .map_while(|item| { + let code = item.validate(cube_data, colour_data); + if matches!(code, oj_rc_plugins::vehicle_validation::ValidationResultCode::Ok) { + None + } else { + Some(code) + } + }) + .next() + .unwrap_or(oj_rc_plugins::vehicle_validation::ValidationResultCode::Ok) + } +} + +impl oj_rc_plugins::Plugin for Any {}