diff --git a/Cargo.lock b/Cargo.lock index de74a00..b0dbc96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1550,6 +1550,14 @@ dependencies = [ "simple-rijndael", ] +[[package]] +name = "polariton_server" +version = "0.1.0" +dependencies = [ + "log", + "polariton", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1759,6 +1767,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "rc_services_room" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "clap", + "env_logger", + "hex", + "log", + "polariton", + "polariton_auth", + "polariton_server", + "tokio", +] + [[package]] name = "rc_static_data" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index cf90889..a3644dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [workspace] members = [ - "auth", "polariton_auth", "rc_services", "rc_static_data", + "auth", "polariton_auth", "rc_services", "rc_static_data", "rc_services_room" ] [workspace.dependencies] @@ -14,4 +14,5 @@ libfj = { version = "0.7.5", path = "../libfj" } log = "0.4" env_logger = "0.11" clap = { version = "4.5", features = [ "derive" ] } -polariton = { version = "*", path = "../polariton" } +polariton = { version = "0.1", path = "../polariton" } +polariton_server = { version = "0.1", path = "../polariton/server" } diff --git a/README.md b/README.md index 2829c86..f01909c 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,17 @@ A collection of open source servers for FreeJam games ### CardLife -To get CardLife to use these servers, replace the ServerConfig.json file in the game files with [this ServerConfig.json](assets/cardlife/ServerConfig.json). +To get CardLife to use these servers, replace the `ServerConfig.json` file in the game files with [this ServerConfig.json](assets/cardlife/ServerConfig.json). ### Robocraft -To get Robocraft to use these servers, please add the following to your OS's `hosts` file: +To get Robocraft to use these servers, place [this servenvmulti.config](assets/robocraft/serenvmulti.config) file in the game files. + + +You may also need to add the following to your OS's `hosts` file: ``` 127.0.0.1 robocraftstaticdata.s3.amazonaws.com -127.0.0.1 services-1.servers.robocraftgame.com ``` The `hosts` file can be found at `/etc/hosts` on Linux and `C:\Windows\system32\drivers\etc\hosts` on Windows. Usually this requires elevated permissions (root/admin) to edit. diff --git a/assets/robocraft/servenvmulti.config b/assets/robocraft/servenvmulti.config new file mode 100644 index 0000000..47eb7c7 --- /dev/null +++ b/assets/robocraft/servenvmulti.config @@ -0,0 +1,12 @@ + + dev + robocraft + + + 127.0.0.1:4532 + not.used.hopefully:4534 + chat.server.not.a.valid.tld:4534 + http://127.0.0.1:8001/ + + + diff --git a/rc_services/src/main.rs b/rc_services/src/main.rs index 26c515b..f879cd3 100644 --- a/rc_services/src/main.rs +++ b/rc_services/src/main.rs @@ -24,10 +24,17 @@ async fn main() -> std::io::Result<()> { let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?; + #[cfg(not(debug_assertions))] loop { let (socket, address) = listener.accept().await?; tokio::spawn(process_socket(socket, address, NonZero::new(args.retries), redirect_static, room_name_static)); } + #[cfg(debug_assertions)] + { + let (socket, address) = listener.accept().await?; + process_socket(socket, address, NonZero::new(args.retries), redirect_static, room_name_static).await; + Ok(()) + } } async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, retries: Option>, game_server_url: &str, game_server_name: &str) { @@ -50,6 +57,7 @@ async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAdd Packet::Packet(packet) => log::warn!("Not handling packet {:?}", packet), } } + log::debug!("Goodbye connection from address {}", address); } async fn handle_ping(ping: Ping, buf: &mut Vec, socket: &mut net::TcpStream) { diff --git a/rc_services_room/Cargo.toml b/rc_services_room/Cargo.toml new file mode 100644 index 0000000..635ae2d --- /dev/null +++ b/rc_services_room/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rc_services_room" +version = "0.1.0" +edition = "2021" + +[dependencies] +log.workspace = true +env_logger.workspace = true +tokio = { version = "1.43", features = [ "net", "macros", "rt-multi-thread", "io-util" ] } +clap.workspace = true +polariton.workspace = true +polariton_auth = { version = "*", path = "../polariton_auth" } +polariton_server.workspace = true +base64 = "0.22" +hex = "0.4" diff --git a/rc_services_room/build_arm64.sh b/rc_services_room/build_arm64.sh new file mode 100755 index 0000000..7010ff6 --- /dev/null +++ b/rc_services_room/build_arm64.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +cargo build --release --target aarch64-unknown-linux-musl diff --git a/rc_services_room/run_debug.sh b/rc_services_room/run_debug.sh new file mode 100755 index 0000000..eedcd2c --- /dev/null +++ b/rc_services_room/run_debug.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +RUST_BACKTRACE=1 RUST_LOG=debug cargo run diff --git a/rc_services_room/src/cli.rs b/rc_services_room/src/cli.rs new file mode 100644 index 0000000..018412d --- /dev/null +++ b/rc_services_room/src/cli.rs @@ -0,0 +1,23 @@ +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct CliArgs { + /// TCP port on which to accept connections + #[arg(short, long, default_value_t = 4533)] + pub port: u16, + + /// IP Address on which to accept connections + #[arg(long, default_value_t = {"127.0.0.1".to_string()})] + pub ip: String, + + /// Socket read tries before giving up (0 to never give up) + #[arg(long, default_value_t = 5)] + pub retries: usize, +} + +impl CliArgs { + pub fn get() -> Self { + Self::parse() + } +} diff --git a/rc_services_room/src/data/battle_arena_config.rs b/rc_services_room/src/data/battle_arena_config.rs new file mode 100644 index 0000000..67d9247 --- /dev/null +++ b/rc_services_room/src/data/battle_arena_config.rs @@ -0,0 +1,44 @@ +use polariton::operation::Typed; +use base64::{Engine, engine::general_purpose::STANDARD}; + +pub struct BattleArenaData { + pub protonium_health: i64, + pub respawn_time_seconds: i64, + pub heal_over_time_per_tower: Vec, + pub base_machine_map: Vec, // aka team base model, converted into base64 + pub equalizer_model: Vec, // converted into base64 + pub equalizer_health: i64, + pub equalizer_trigger_time_seconds: Vec, + pub equalizer_warning_seconds: i64, + pub equalizer_duration_seconds: Vec, + pub capture_time_seconds_per_player: Vec, + pub num_segments: i32, + pub heal_escalation_time_seconds: i64, +} + +fn to_obj_arr_u(slice: &[u64]) -> Typed { + Typed::ObjArr(slice.iter().map(|x| Typed::Long(*x as i64)).collect::>().into()) +} + +fn to_obj_arr_i(slice: &[i64]) -> Typed { + Typed::ObjArr(slice.iter().map(|x| Typed::Long(*x)).collect::>().into()) +} + +impl BattleArenaData { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("protoniumHealth".into()), Typed::Long(self.protonium_health)), + (Typed::Str("respawnTimeSeconds".into()), Typed::Long(self.respawn_time_seconds)), + (Typed::Str("healOverTimePerTower".into()), to_obj_arr_u(&self.heal_over_time_per_tower)), + (Typed::Str("baseMachineMap".into()), Typed::Str(STANDARD.encode(&self.base_machine_map).into())), + (Typed::Str("equalizerModel".into()), Typed::Str(STANDARD.encode(&self.equalizer_model).into())), + (Typed::Str("equalizerHealth".into()), Typed::Long(self.equalizer_health)), + (Typed::Str("equalizerTriggerTimeSeconds".into()), to_obj_arr_u(&self.equalizer_trigger_time_seconds)), + (Typed::Str("equalizerWarningSeconds".into()), Typed::Long(self.equalizer_warning_seconds)), + (Typed::Str("equalizerDurationSeconds".into()), to_obj_arr_u(&self.equalizer_duration_seconds)), + (Typed::Str("captureTimeSecondsPerPlayer".into()), to_obj_arr_i(&self.capture_time_seconds_per_player)), + (Typed::Str("numSegments".into()), Typed::Int(self.num_segments)), + (Typed::Str("healEscalationTimeSeconds".into()), Typed::Long(self.heal_escalation_time_seconds)), + ].into()) + } +} diff --git a/rc_services_room/src/data/client_config.rs b/rc_services_room/src/data/client_config.rs new file mode 100644 index 0000000..3ba7f20 --- /dev/null +++ b/rc_services_room/src/data/client_config.rs @@ -0,0 +1,32 @@ +use polariton::operation::Typed; + +pub struct GameplaySettings { + pub show_tutorial_after_date: String, + pub health_threshold: f32, // percent + pub microbot_sphere: f32, // radius + pub misfire_angle: f32, // degrees? + pub shield_dps: i32, + pub shield_hps: u32, + pub request_review_level: u32, + pub critical_ratio: f32, + pub cross_promo_image: String, // url + pub cross_promo_link: String, // url +} + +impl GameplaySettings { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + // TODO + (Typed::Str("showTutorialAfterDate".into()), Typed::Str(self.show_tutorial_after_date.clone().into())), + (Typed::Str("healthTresholdPercent".into()), Typed::Float(self.health_threshold)), + (Typed::Str("microbotSphereRadius".into()), Typed::Float(self.microbot_sphere)), + (Typed::Str("smartRotationMisfireAngle".into()), Typed::Float(self.misfire_angle)), + (Typed::Str("fusionShieldDPS".into()), Typed::Int(self.shield_dps)), + (Typed::Str("fusionShieldHPS".into()), Typed::Int(self.shield_hps as i32)), + (Typed::Str("requestReviewAtLevel".into()), Typed::Int(self.request_review_level as i32)), + (Typed::Str("criticalRatio".into()), Typed::Float(self.critical_ratio)), + (Typed::Str("crossPromotionAdImageUrl".into()), Typed::Str(self.cross_promo_image.clone().into())), + (Typed::Str("crossPromotionAdLinkUrl".into()), Typed::Str(self.cross_promo_link.clone().into())), + ].into()) + } +} diff --git a/rc_services_room/src/data/cosmetic_limits.rs b/rc_services_room/src/data/cosmetic_limits.rs new file mode 100644 index 0000000..3af0ab2 --- /dev/null +++ b/rc_services_room/src/data/cosmetic_limits.rs @@ -0,0 +1,18 @@ +use polariton::operation::Typed; + +pub struct CosmeticLimitsData { + pub others_max_holo_and_trails: u32, + pub others_max_headlamps: u32, + pub others_max_cosmetic_items_with_particles: u32, +} + +impl CosmeticLimitsData { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("OthersMaxNumberHoloAndTrails".into()), Typed::Int(self.others_max_holo_and_trails as i32)), + (Typed::Str("OthersMaxNumberHeadlamps".into()), Typed::Int(self.others_max_headlamps as i32)), + (Typed::Str("OthersMaxCosmeticItemsWithParticleSystem".into()), Typed::Int(self.others_max_cosmetic_items_with_particles as i32)), + ].into() + ) + } +} diff --git a/rc_services_room/src/data/cpu_limits.rs b/rc_services_room/src/data/cpu_limits.rs new file mode 100644 index 0000000..4963369 --- /dev/null +++ b/rc_services_room/src/data/cpu_limits.rs @@ -0,0 +1,22 @@ +use polariton::operation::Typed; + +pub struct CpuLimitsData { + pub premium_for_life_cosmetic_gpu: i32, + pub premium_cosmetic_cpu: i32, + pub no_premium_cosmetic_cpu: i32, + pub max_regular_health: i32, + pub max_megabot_health: i32, +} + +impl CpuLimitsData { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("PremiumForLifeCosmeticCPU".into()), Typed::Int(self.premium_for_life_cosmetic_gpu)), + (Typed::Str("PremiumCosmeticCPU".into()), Typed::Int(self.premium_cosmetic_cpu)), + (Typed::Str("NoPremiumCosmeticCPU".into()), Typed::Int(self.no_premium_cosmetic_cpu)), + (Typed::Str("MaxRegularHealth".into()), Typed::Int(self.max_regular_health)), + (Typed::Str("MaxMegabotHealth".into()), Typed::Int(self.max_megabot_health)), + ].into() + ) + } +} diff --git a/rc_services_room/src/data/crf_config.rs b/rc_services_room/src/data/crf_config.rs new file mode 100644 index 0000000..458cd98 --- /dev/null +++ b/rc_services_room/src/data/crf_config.rs @@ -0,0 +1,17 @@ +use polariton::operation::Typed; + +pub struct RobotShopConfig { + pub cpu_ranges: Vec, + pub submission_mult: f32, + pub earnings_mult: f32, +} + +impl RobotShopConfig { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("robotShopPriceRanges".into()), Typed::IntArr(self.cpu_ranges.clone().into())), + (Typed::Str("submissionMultiplier".into()), Typed::Float(self.submission_mult)), + (Typed::Str("earningsMultiplier".into()), Typed::Float(self.earnings_mult)), + ].into()) + } +} diff --git a/rc_services_room/src/data/cube_list.rs b/rc_services_room/src/data/cube_list.rs new file mode 100644 index 0000000..2c12e17 --- /dev/null +++ b/rc_services_room/src/data/cube_list.rs @@ -0,0 +1,120 @@ +#![allow(dead_code)] +use std::collections::HashMap; +use polariton::operation::Typed; + +pub struct CubeInfo { + pub cpu: u32, + pub health: u32, + pub health_boost: f32, + pub grey_out_in_tutorial: bool, + pub visibility: VisibilityMode, + pub indestructible: bool, + pub category: u32, + pub placements: u32, // default 63 + pub protonium: bool, + pub unlocked_by_league: bool, + pub league_unlock_index: i32, + pub stats: HashMap, + pub description: String, + pub size: ItemTier, + pub type_: ItemType, + pub ranking: i32, + pub cosmetic: bool, + pub variant_of: String, // cube id (in hex) + pub ignore_in_weapon_list: bool, +} + +impl CubeInfo { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("cpuRating".into()), Typed::Int(self.cpu as i32)), + (Typed::Str("health".into()), Typed::Int(self.health as i32)), + (Typed::Str("healthBoost".into()), Typed::Float(self.health_boost)), + (Typed::Str("GreyOutInTutorial".into()), Typed::Bool(self.grey_out_in_tutorial.into())), + (Typed::Str("buildVisibility".into()), Typed::Str(self.visibility.as_str().into())), + (Typed::Str("isIndestructible".into()), Typed::Bool(self.indestructible.into())), + (Typed::Str("ItemCategory".into()), Typed::Int(self.category as i32)), + (Typed::Str("PlacementFaces".into()), Typed::Int(self.placements as i32)), + (Typed::Str("protoniumCrystal".into()), Typed::Bool(self.protonium.into())), + (Typed::Str("UnlockedByLeague".into()), Typed::Bool(self.unlocked_by_league.into())), + (Typed::Str("LeagueUnlockIndex".into()), Typed::Int(self.league_unlock_index)), + (Typed::Str("DisplayStats".into()), { + let items: Vec<(Typed, Typed)> = self.stats.iter().map(|(key, val)| (Typed::Str(key.into()), val.to_owned())).collect(); + Typed::HashMap(items.into()) + }), + (Typed::Str("Description".into()), Typed::Str(self.description.clone().into())), + (Typed::Str("ItemSize".into()), Typed::Int(self.size as i32)), + (Typed::Str("ItemType".into()), Typed::Str(self.type_.as_str().into())), + (Typed::Str("robotRanking".into()), Typed::Int(self.ranking)), + (Typed::Str("isCosmetic".into()), Typed::Bool(self.cosmetic.into())), + (Typed::Str("variantOf".into()), Typed::Str(self.variant_of.clone().into())), + (Typed::Str("ignoreInWeaponsList".into()), Typed::Bool(self.ignore_in_weapon_list.into())), // optional + ].into()) + } +} + +#[derive(Clone, Copy)] +pub enum VisibilityMode { + Mothership, + All, + Tutorial, + None, +} + +impl VisibilityMode { + fn as_str(&self) -> &'static str { + match self { + Self::Mothership => "Mothership", + Self::All => "All", + Self::Tutorial => "Tutorial", + Self::None => "None", + } + } +} + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum ItemTier { + NoTier = 0, + T0 = 100, + T1 = 200, + T2 = 300, + T3 = 400, + T4 = 500, + T5 = 600, +} + +impl ItemTier { + pub fn as_str(&self) -> &'static str { + match self { + ItemTier::NoTier => "NotAWeapon", + ItemTier::T0 => "T0", + ItemTier::T1 => "T1", + ItemTier::T2 => "T2", + ItemTier::T3 => "T3", + ItemTier::T4 => "T4", + ItemTier::T5 => "T5", + } + } +} + +#[derive(Clone, Copy)] +pub enum ItemType { + NoFunction, + Weapon, + Module, + Movement, + Cosmetic, +} + +impl ItemType { + fn as_str(&self) -> &'static str { + match self { + Self::NoFunction => "NotAFunctionalItem", + Self::Weapon => "Weapon", + Self::Module => "Module", + Self::Movement => "Movement", + Self::Cosmetic => "Cosmetic", + } + } +} diff --git a/rc_services_room/src/data/customisation_info.rs b/rc_services_room/src/data/customisation_info.rs new file mode 100644 index 0000000..ebfdc58 --- /dev/null +++ b/rc_services_room/src/data/customisation_info.rs @@ -0,0 +1,23 @@ +use polariton::operation::Typed; + +pub struct CustomisationData { + pub id: String, + pub localised_name: String, + pub skin_scene_name: String, + pub simulation_prefab: String, + pub preview_image_name: String, + pub is_default: bool, +} + +impl CustomisationData { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("id".into()), Typed::Str(self.id.clone().into())), + (Typed::Str("localisedName".into()), Typed::Str(self.localised_name.clone().into())), + (Typed::Str("skinsceneName".into()), Typed::Str(self.skin_scene_name.clone().into())), + (Typed::Str("simulationPrefab".into()), Typed::Str(self.simulation_prefab.clone().into())), + (Typed::Str("previewImageName".into()), Typed::Str(self.preview_image_name.clone().into())), + (Typed::Str("isDefault".into()), Typed::Bool(self.is_default.into())), + ].into()) + } +} diff --git a/rc_services_room/src/data/damage_boost.rs b/rc_services_room/src/data/damage_boost.rs new file mode 100644 index 0000000..a43123d --- /dev/null +++ b/rc_services_room/src/data/damage_boost.rs @@ -0,0 +1,17 @@ +use polariton::operation::{Typed, Dict}; + +pub struct DamageBoostData { + pub damage_map: Vec<(u32, f32)>, // (cpu, boost) +} + +impl DamageBoostData { + pub fn as_transmissible(&self) -> Typed { + Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 42, // obj + items: self.damage_map.iter() + .map(|(cpu, boost)| (Typed::Str(cpu.to_string().into()), Typed::Float(*boost))) + .collect(), + }) + } +} diff --git a/rc_services_room/src/data/mod.rs b/rc_services_room/src/data/mod.rs new file mode 100644 index 0000000..5d19c43 --- /dev/null +++ b/rc_services_room/src/data/mod.rs @@ -0,0 +1,14 @@ +pub mod cube_list; +pub mod special_item; +pub mod premium_config; +pub mod palette; +pub mod client_config; +pub mod crf_config; +pub mod weapon_list; +pub mod movement_list; +pub mod damage_boost; +pub mod battle_arena_config; +pub mod cpu_limits; +pub mod cosmetic_limits; +pub mod taunts_config; +pub mod customisation_info; diff --git a/rc_services_room/src/data/movement_list.rs b/rc_services_room/src/data/movement_list.rs new file mode 100644 index 0000000..76d2324 --- /dev/null +++ b/rc_services_room/src/data/movement_list.rs @@ -0,0 +1,450 @@ +#![allow(dead_code)] + +use polariton::operation::{Typed, Dict}; + +use super::cube_list::ItemTier; + +#[derive(Default)] +pub struct MovementCategoryData { + pub horizontal_top_speed: Option, + pub vertical_top_speed: Option, + pub min_required_items: Option, + pub min_item_modifier: Option, + pub max_hover_height: Option, + pub light_machine_mass: Option, + pub heavy_machine_mass: Option, + pub specifics: MovementCategorySpecificData, + pub stats: Vec<(ItemTier, MovementData)>, +} + +impl MovementCategoryData { + pub fn as_transmissible(&self) -> Typed { + let mut out = Vec::new(); + self.horizontal_top_speed.map(|x| out.push((Typed::Str("horizontalTopSpeed".into()), Typed::Float(x)))); + self.vertical_top_speed.map(|x| out.push((Typed::Str("verticalTopSpeed".into()), Typed::Float(x)))); + self.min_required_items.map(|x| out.push((Typed::Str("minRequiredItems".into()), Typed::Int(x)))); + self.min_item_modifier.map(|x| out.push((Typed::Str("minItemsModifier".into()), Typed::Float(x)))); + self.max_hover_height.map(|x| out.push((Typed::Str("maxHoverHeight".into()), Typed::Float(x)))); + self.light_machine_mass.map(|x| out.push((Typed::Str("lightMachineMass".into()), Typed::Float(x)))); + self.heavy_machine_mass.map(|x| out.push((Typed::Str("heavyMachineMass".into()), Typed::Float(x)))); + out.append(&mut self.specifics.as_transmissible()); + for (tier, mov_data) in self.stats.iter() { + out.push((Typed::Str(tier.as_str().into()), mov_data.as_transmissible())); + } + Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 42, // any + items: out.into(), + }) + } +} + +#[derive(Default)] +pub enum MovementCategorySpecificData { + #[default] + Wheel, + Hover(HoverCategoryData), + Wing, + Rudder, // same as wing + Thruster, + Propeller, // same as thruster + InsectLeg, + MechLeg(MechLegCategoryData), + SprinterLeg(MechLegCategoryData), // same as mech leg + TankTrack, + Rotor(RotorCategoryData), +} + +impl MovementCategorySpecificData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + match self { + Self::Wheel => Vec::default(), + Self::Hover(x) => x.as_transmissible(), + Self::Wing => Vec::default(), + Self::Rudder => Vec::default(), + Self::Thruster => Vec::default(), + Self::Propeller => Vec::default(), + Self::InsectLeg => Vec::default(), + Self::MechLeg(x) => x.as_transmissible(), + Self::SprinterLeg(x) => x.as_transmissible(), + Self::TankTrack => Vec::default(), + Self::Rotor(x) => x.as_transmissible(), + } + } +} + +pub struct HoverCategoryData { + pub height_tolerance: f32, + pub force_y_offset: f32, + pub turning_scale: f32, + pub small_angle_turning_scale: f32, + pub max_vertical_velocity: f32, + pub hover_damping: f32, + pub angular_damping: f32, + pub deceleration_multiplier: f32, +} + +impl HoverCategoryData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("heightTolerance".into()), Typed::Float(self.height_tolerance)), + (Typed::Str("forceYOffset".into()), Typed::Float(self.force_y_offset)), + (Typed::Str("turningScale".into()), Typed::Float(self.turning_scale)), + (Typed::Str("smallAngleTurningScale".into()), Typed::Float(self.small_angle_turning_scale)), + (Typed::Str("verticalTopSpeed".into()), Typed::Float(self.max_vertical_velocity)), + (Typed::Str("hoverDamping".into()), Typed::Float(self.hover_damping)), + (Typed::Str("angularDamping".into()), Typed::Float(self.angular_damping)), + (Typed::Str("decelerationMultiplier".into()), Typed::Float(self.deceleration_multiplier)), + ] + } +} + +pub struct MechLegCategoryData { + pub deceleration_multiplier: f32, +} + +impl MechLegCategoryData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("decelerationMultiplier".into()), Typed::Float(self.deceleration_multiplier)), + ] + } +} + +pub struct RotorCategoryData { + pub max_turn_rate: f32, +} + + +impl RotorCategoryData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("maxTurnRate".into()), Typed::Float(self.max_turn_rate)), + ] + } +} + +pub struct MovementData { + pub speed_boost: Option, + pub max_carry_mass: Option, + pub horizontal_top_speed: Option, + pub vertical_top_speed: Option, + pub specifics: MovementSpecificData, +} + +impl MovementData { + pub fn as_transmissible(&self) -> Typed { + let mut out = Vec::new(); + self.speed_boost.map(|x| out.push((Typed::Str("speedBoost".into()), Typed::Float(x)))); + self.max_carry_mass.map(|x| out.push((Typed::Str("maxCarryMass".into()), Typed::Float(x)))); + self.horizontal_top_speed.map(|x| out.push((Typed::Str("horizontalTopSpeed".into()), Typed::Float(x)))); + self.vertical_top_speed.map(|x| out.push((Typed::Str("verticalTopSpeed".into()), Typed::Float(x)))); + out.append(&mut self.specifics.as_transmissible()); + Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 42, // any + items: out.into(), + }) + } +} + +pub enum MovementSpecificData { + Wheel(WheelData), + Hover(HoverData), + Wing(AerofoilData), + Rudder(AerofoilData), // same as wing + Thruster(ThrusterData), + Propeller(ThrusterData), // same as thruster + InsectLeg(InsectLegData), + MechLeg(MechLegData), + SprinterLeg(MechLegData), // same as mech leg + TankTrack(TankTrackData), + Rotor(RotorData), +} + +impl MovementSpecificData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + match self { + Self::Wheel(x) => x.as_transmissible(), + Self::Hover(x) => x.as_transmissible(), + Self::Wing(x) => x.as_transmissible(), + Self::Rudder(x) => x.as_transmissible(), + Self::Thruster(x) => x.as_transmissible(), + Self::Propeller(x) => x.as_transmissible(), + Self::InsectLeg(x) => x.as_transmissible(), + Self::MechLeg(x) => x.as_transmissible(), + Self::SprinterLeg(x) => x.as_transmissible(), + Self::TankTrack(x) => x.as_transmissible(), + Self::Rotor(x) => x.as_transmissible(), + } + } +} + +pub struct WheelData { + pub steering_speed_light: f32, + pub steering_speed_heavy: f32, + pub steering_force_multiplier_light: f32, + pub steering_force_multiplier_heavy: f32, + pub lateral_acceleration_light: f32, + pub lateral_acceleration_heavy: f32, + pub time_to_max_acceleration_light: f32, + pub time_to_max_acceleration_heavy: f32, + pub brake_force_light: f32, + pub brake_force_heavy: f32, +} + +impl WheelData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("steeringSpeedLight".into()), Typed::Float(self.steering_speed_light)), + (Typed::Str("steeringSpeedHeavy".into()), Typed::Float(self.steering_speed_heavy)), + (Typed::Str("steeringForceMultiplierLight".into()), Typed::Float(self.steering_force_multiplier_light)), + (Typed::Str("steeringForceMultiplierHeavy".into()), Typed::Float(self.steering_force_multiplier_heavy)), + (Typed::Str("lateralAccelerationLight".into()), Typed::Float(self.lateral_acceleration_light)), + (Typed::Str("lateralAccelerationHeavy".into()), Typed::Float(self.lateral_acceleration_heavy)), + (Typed::Str("timeToMaxAccelerationLight".into()), Typed::Float(self.time_to_max_acceleration_light)), + (Typed::Str("timeToMaxAccelerationHeavy".into()), Typed::Float(self.time_to_max_acceleration_heavy)), + (Typed::Str("brakeForceLight".into()), Typed::Float(self.brake_force_light)), + (Typed::Str("brakeForceHeavy".into()), Typed::Float(self.brake_force_heavy)), + ] + } +} + +pub struct HoverData { + pub max_hover_height_light: f32, + pub max_hover_height_heaver: f32, + pub height_change_speed_light: f32, + pub height_change_speed_heavy: f32, + pub turn_torque_light: f32, + pub turn_torque_heavy: f32, + pub acceleration_light: f32, + pub acceleration_heavy: f32, + pub max_angular_velocity_light: f32, + pub max_angular_velocity_heavy: f32, + pub lateral_damping_light: f32, + pub lateral_damping_heavy: f32, +} + +impl HoverData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("maxHoverHeightLight".into()), Typed::Float(self.max_hover_height_light)), + (Typed::Str("maxHoverHeightHeavy".into()), Typed::Float(self.max_hover_height_heaver)), + (Typed::Str("heightChangeSpeedLight".into()), Typed::Float(self.height_change_speed_light)), + (Typed::Str("heightChangeSpeedHeavy".into()), Typed::Float(self.height_change_speed_heavy)), + (Typed::Str("turnTorqueLight".into()), Typed::Float(self.turn_torque_light)), + (Typed::Str("turnTorqueHeavy".into()), Typed::Float(self.turn_torque_heavy)), + (Typed::Str("accelerationLight".into()), Typed::Float(self.acceleration_light)), + (Typed::Str("accelerationHeavy".into()), Typed::Float(self.acceleration_heavy)), + (Typed::Str("maxAngularVelocityLight".into()), Typed::Float(self.max_angular_velocity_light)), + (Typed::Str("maxAngularVelocityHeavy".into()), Typed::Float(self.max_angular_velocity_heavy)), + (Typed::Str("lateralDampingLight".into()), Typed::Float(self.lateral_damping_light)), + (Typed::Str("lateralDampingHeavy".into()), Typed::Float(self.lateral_damping_heavy)), + ] + } +} + +pub struct AerofoilData { + pub barrel_speed_light: f32, + pub barrel_speed_heavy: f32, + pub bank_speed_light: f32, + pub bank_speed_heavy: f32, + pub elevation_speed_light: f32, + pub elevation_speed_heavy: f32, + pub rudder_speed_light: f32, + pub rudder_speed_heavy: f32, + pub thrust_light: f32, + pub thrust_heavy: f32, + pub vtol_velocity_light: f32, + pub vtol_velocity_heavy: f32, +} + +impl AerofoilData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("barrelSpeedLight".into()), Typed::Float(self.barrel_speed_light)), + (Typed::Str("barrelSpeedHeavy".into()), Typed::Float(self.barrel_speed_heavy)), + (Typed::Str("bankSpeedLight".into()), Typed::Float(self.bank_speed_light)), + (Typed::Str("bankSpeedHeavy".into()), Typed::Float(self.bank_speed_heavy)), + (Typed::Str("elevationSpeedLight".into()), Typed::Float(self.elevation_speed_light)), + (Typed::Str("elevationSpeedHeavy".into()), Typed::Float(self.elevation_speed_heavy)), + (Typed::Str("rudderSpeedLight".into()), Typed::Float(self.rudder_speed_light)), + (Typed::Str("rudderSpeedHeavy".into()), Typed::Float(self.rudder_speed_heavy)), + (Typed::Str("thrustLight".into()), Typed::Float(self.thrust_light)), + (Typed::Str("thrustHeavy".into()), Typed::Float(self.thrust_heavy)), + (Typed::Str("vtolVelocityLight".into()), Typed::Float(self.vtol_velocity_light)), + (Typed::Str("vtolVelocityHeavy".into()), Typed::Float(self.vtol_velocity_heavy)), + ] + } +} + +pub struct ThrusterData { + pub acceleration_delay_light: f32, + pub acceleration_delay_heavy: f32, +} + +impl ThrusterData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("accelerationDelayLight".into()), Typed::Float(self.acceleration_delay_light)), + (Typed::Str("accelerationDelayHeavy".into()), Typed::Float(self.acceleration_delay_heavy)), + ] + } +} + +pub struct InsectLegData { + pub ideal_height_light: f32, + pub ideal_height_heavy: f32, + pub ideal_crouching_height_light: f32, + pub ideal_crouching_height_heavy: f32, + pub ideal_height_range_light: f32, + pub ideal_height_range_heavy: f32, + pub jump_height_light: f32, + pub jump_height_heavy: f32, + pub max_upwards_force_light: f32, + pub max_upwards_force_heavy: f32, + pub max_lateral_force_light: f32, + pub max_lateral_force_heavy: f32, + pub max_turning_force_light: f32, + pub max_turning_force_heavy: f32, + pub max_damping_force_light: f32, + pub max_damping_force_heavy: f32, + pub max_stopped_force_light: f32, + pub max_stopped_force_heavy: f32, + pub max_new_stopped_force_light: f32, + pub max_new_stopped_force_heavy: f32, + pub upwards_damping_force_light: f32, + pub upwards_damping_force_heavy: f32, + pub lateral_damp_force_light: f32, + pub lateral_damp_force_heavy: f32, + pub swagger_force_light: f32, + pub swagger_force_heavy: f32, +} + +impl InsectLegData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("idealHeightLight".into()), Typed::Float(self.ideal_height_light)), + (Typed::Str("idealHeightHeavy".into()), Typed::Float(self.ideal_height_heavy)), + (Typed::Str("idealCrouchingHeightLight".into()), Typed::Float(self.ideal_crouching_height_light)), + (Typed::Str("idealCrouchingHeightHeavy".into()), Typed::Float(self.ideal_crouching_height_heavy)), + (Typed::Str("idealHeightRangeLight".into()), Typed::Float(self.ideal_height_range_light)), + (Typed::Str("idealHeightRangeHeavy".into()), Typed::Float(self.ideal_height_range_heavy)), + (Typed::Str("jumpHeightLight".into()), Typed::Float(self.jump_height_light)), + (Typed::Str("jumpHeightHeavy".into()), Typed::Float(self.jump_height_heavy)), + (Typed::Str("maxUpwardsForceLight".into()), Typed::Float(self.max_upwards_force_light)), + (Typed::Str("maxUpwardsForceHeavy".into()), Typed::Float(self.max_upwards_force_heavy)), + (Typed::Str("maxLateralForceLight".into()), Typed::Float(self.max_lateral_force_light)), + (Typed::Str("maxLateralForceHeavy".into()), Typed::Float(self.max_lateral_force_heavy)), + (Typed::Str("maxTurningForceLight".into()), Typed::Float(self.max_turning_force_light)), + (Typed::Str("maxTurningForceHeavy".into()), Typed::Float(self.max_turning_force_heavy)), + (Typed::Str("maxDampingForceLight".into()), Typed::Float(self.max_damping_force_light)), + (Typed::Str("maxDampingForceHeavy".into()), Typed::Float(self.max_damping_force_heavy)), + (Typed::Str("maxStoppedForceLight".into()), Typed::Float(self.max_stopped_force_light)), + (Typed::Str("maxStoppedForceHeavy".into()), Typed::Float(self.max_stopped_force_heavy)), + (Typed::Str("maxNewStoppedForceLight".into()), Typed::Float(self.max_new_stopped_force_light)), + (Typed::Str("maxNewStoppedForceHeavy".into()), Typed::Float(self.max_new_stopped_force_heavy)), + (Typed::Str("upwardsDampingForceLight".into()), Typed::Float(self.upwards_damping_force_light)), + (Typed::Str("upwardsDampingForceHeavy".into()), Typed::Float(self.upwards_damping_force_heavy)), + (Typed::Str("lateralDampForceLight".into()), Typed::Float(self.lateral_damp_force_light)), + (Typed::Str("lateralDampForceHeavy".into()), Typed::Float(self.lateral_damp_force_heavy)), + (Typed::Str("swaggerForceLight".into()), Typed::Float(self.swagger_force_light)), + (Typed::Str("swaggerForceHeavy".into()), Typed::Float(self.swagger_force_heavy)), + ] + } +} + +pub struct MechLegData { + pub time_grounded_after_jump_light: f32, + pub time_grounded_after_jump_heavy: f32, + pub jump_height_light: f32, + pub jump_height_heavy: f32, + pub turn_acceleration_light: f32, + pub turn_acceleration_heavy: f32, + pub legacy_turn_acceleration_light: f32, + pub legacy_turn_acceleration_heavy: f32, + pub long_jump_speec_scale_light: f32, + pub long_jump_speec_scale_heavy: f32, + pub max_lateral_force_light: f32, + pub max_lateral_force_heavy: f32, + pub max_damping_force_light: f32, + pub max_damping_force_heavy: f32, +} + +impl MechLegData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("timeGroundedAfterJumpLight".into()), Typed::Float(self.time_grounded_after_jump_light)), + (Typed::Str("timeGroundedAfterJumpHeavy".into()), Typed::Float(self.time_grounded_after_jump_heavy)), + (Typed::Str("jumpHeightLight".into()), Typed::Float(self.jump_height_light)), + (Typed::Str("jumpHeightHeavy".into()), Typed::Float(self.jump_height_heavy)), + (Typed::Str("turnAccelerationLight".into()), Typed::Float(self.turn_acceleration_light)), + (Typed::Str("turnAccelerationHeavy".into()), Typed::Float(self.turn_acceleration_heavy)), + (Typed::Str("legacyTurnAccelerationLight".into()), Typed::Float(self.legacy_turn_acceleration_light)), + (Typed::Str("legacyTurnAccelerationHeavy".into()), Typed::Float(self.legacy_turn_acceleration_heavy)), + (Typed::Str("longJumpSpeedScaleLight".into()), Typed::Float(self.long_jump_speec_scale_light)), + (Typed::Str("longJumpSpeedScaleHeavy".into()), Typed::Float(self.long_jump_speec_scale_heavy)), + (Typed::Str("maxLateralForceLight".into()), Typed::Float(self.max_lateral_force_light)), + (Typed::Str("maxLateralForceHeavy".into()), Typed::Float(self.max_lateral_force_heavy)), + (Typed::Str("maxDampingForceLight".into()), Typed::Float(self.max_damping_force_light)), + (Typed::Str("maxDampingForceHeavy".into()), Typed::Float(self.max_damping_force_heavy)), + ] + } +} + +pub struct TankTrackData { + pub max_turn_rate_moving_light: f32, + pub max_turn_rate_moving_heavy: f32, + pub max_turn_rate_stopped_light: f32, + pub max_turn_rate_stopped_heavy: f32, + pub turn_acceleration_light: f32, + pub turn_acceleration_heavy: f32, + pub lateral_acceleration_light: f32, + pub lateral_acceleration_heavy: f32, +} + +impl TankTrackData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("maxTurnRateMovingLight".into()), Typed::Float(self.max_turn_rate_moving_light)), + (Typed::Str("maxTurnRateMovingHeavy".into()), Typed::Float(self.max_turn_rate_moving_heavy)), + (Typed::Str("maxTurnRateStoppedLight".into()), Typed::Float(self.max_turn_rate_stopped_light)), + (Typed::Str("maxTurnRateStoppedHeavy".into()), Typed::Float(self.max_turn_rate_stopped_heavy)), + (Typed::Str("turnAccelerationLight".into()), Typed::Float(self.turn_acceleration_light)), + (Typed::Str("turnAccelerationHeavy".into()), Typed::Float(self.turn_acceleration_heavy)), + (Typed::Str("lateralAccelerationLight".into()), Typed::Float(self.lateral_acceleration_light)), + (Typed::Str("lateralAccelerationHeavy".into()), Typed::Float(self.lateral_acceleration_heavy)), + ] + } +} + +pub struct RotorData { + pub height_acceleration_light: f32, + pub height_acceleration_heavy: f32, + pub strafe_acceleration_light: f32, + pub strafe_acceleration_heavy: f32, + pub turn_acceleration_light: f32, + pub turn_acceleration_heavy: f32, + pub height_max_change_speed_light: f32, + pub height_max_change_speed_heavy: f32, + pub level_acceleration_light: f32, + pub level_acceleration_heavy: f32, +} + + +impl RotorData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("heightAccelerationLight".into()), Typed::Float(self.height_acceleration_light)), + (Typed::Str("heightAccelerationHeavy".into()), Typed::Float(self.height_acceleration_heavy)), + (Typed::Str("strafeAccelerationLight".into()), Typed::Float(self.strafe_acceleration_light)), + (Typed::Str("strafeAccelerationHeavy".into()), Typed::Float(self.strafe_acceleration_heavy)), + (Typed::Str("turnAccelerationLight".into()), Typed::Float(self.turn_acceleration_light)), + (Typed::Str("turnAccelerationHeavy".into()), Typed::Float(self.turn_acceleration_heavy)), + (Typed::Str("heightMaxChangeSpeedLight".into()), Typed::Float(self.height_max_change_speed_light)), + (Typed::Str("heightMaxChangeSpeedHeavy".into()), Typed::Float(self.height_max_change_speed_heavy)), + (Typed::Str("levelAccelerationLight".into()), Typed::Float(self.level_acceleration_light)), + (Typed::Str("levelAccelerationHeavy".into()), Typed::Float(self.level_acceleration_heavy)), + ] + } +} diff --git a/rc_services_room/src/data/palette.rs b/rc_services_room/src/data/palette.rs new file mode 100644 index 0000000..37a9eb8 --- /dev/null +++ b/rc_services_room/src/data/palette.rs @@ -0,0 +1,76 @@ +#![allow(dead_code)] +pub struct ColourValue { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} + +impl ColourValue { + fn read_no_alpha(reader: &mut R) -> std::io::Result { + let mut buf = [0u8; 1]; + reader.read(&mut buf)?; + let r = buf[0]; + reader.read(&mut buf)?; + let g = buf[0]; + reader.read(&mut buf)?; + let b = buf[0]; + Ok(Self { + r, g, b, a: u8::MAX, + }) + } + + fn write_no_alpha(&self, writer: &mut W) -> std::io::Result { + writer.write(&[self.r, self.g, self.b]) + } +} + +pub struct Colour { + pub index: u8, + pub diffuse: ColourValue, + pub specular: ColourValue, + pub overlay: ColourValue, + pub premium: bool, +} + +impl Colour { + fn read_with_index(index: u8, reader: &mut R) -> std::io::Result { + let diffuse = ColourValue::read_no_alpha(reader)?; + let specular = ColourValue::read_no_alpha(reader)?; + let overlay = ColourValue::read_no_alpha(reader)?; + let mut buf = [0u8; 1]; + reader.read(&mut buf)?; + let premium = buf[0] != 0; + Ok(Self { + index, diffuse, specular, overlay, premium, + }) + } + + pub fn read_many(reader: &mut R) -> std::io::Result> { + let mut buf = [0u8; 4]; + reader.read(&mut buf)?; + let count = i32::from_le_bytes(buf); + let mut results = Vec::with_capacity(count as _); + for i in 0..count { + results.push(Self::read_with_index(i as u8, reader)?); + } + Ok(results) + } + + pub fn write(&self, writer: &mut W) -> std::io::Result { + let mut total = 0; + total += self.diffuse.write_no_alpha(writer)?; + total += self.specular.write_no_alpha(writer)?; + total += self.overlay.write_no_alpha(writer)?; + total += writer.write(&[self.premium as u8])?; + Ok(total) + } + + pub fn write_many(items: &[Self], writer: &mut W) -> std::io::Result { + let mut total = writer.write(&(items.len() as i32).to_le_bytes())?; + for item in items.iter() { + total += item.write(writer)?; + } + Ok(total) + } +} diff --git a/rc_services_room/src/data/premium_config.rs b/rc_services_room/src/data/premium_config.rs new file mode 100644 index 0000000..19a8230 --- /dev/null +++ b/rc_services_room/src/data/premium_config.rs @@ -0,0 +1,45 @@ +use polariton::operation::{Typed, Dict}; + +pub struct PremiumEffects { + pub factor: PremiumFactor, + pub multiplayer: PremiumMultiplayer, +} + +impl PremiumEffects { + pub fn as_transmissible(&self) -> Typed { + Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 104, // hashtable + items: vec![ + (Typed::Str("PremiumFactor".into()), self.factor.as_transmissible()), + (Typed::Str("TieredMultiplayer".into()), self.multiplayer.as_transmissible()), + ] + }) + } +} + +pub struct PremiumFactor { + pub factor: i32, // percent + pub party_bonus: i32, // percent +} + +impl PremiumFactor { + fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("Factor".into()), Typed::Int(self.factor)), + (Typed::Str("PartyBonusPercentagePerPlayer".into()), Typed::Int(self.party_bonus)), + ].into()) + } +} + +pub struct PremiumMultiplayer { + pub tier_multiplier: f64, +} + +impl PremiumMultiplayer { + fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("BonusPerTierMultiplier".into()), Typed::Double(self.tier_multiplier)), + ].into()) + } +} diff --git a/rc_services_room/src/data/special_item.rs b/rc_services_room/src/data/special_item.rs new file mode 100644 index 0000000..a149bfb --- /dev/null +++ b/rc_services_room/src/data/special_item.rs @@ -0,0 +1,17 @@ +use polariton::operation::Typed; + +pub struct SpecialItem { + pub name: String, + pub sprite: String, + pub size: u32, +} + +impl SpecialItem { + pub fn as_transmissible(&self) -> Typed { + Typed::HashMap(vec![ + (Typed::Str("name".into()), Typed::Str(self.name.clone().into())), + (Typed::Str("spriteName".into()), Typed::Str(self.sprite.clone().into())), + (Typed::Str("mothershipSize".into()), Typed::Int(self.size as i32)), + ].into()) + } +} diff --git a/rc_services_room/src/data/taunts_config.rs b/rc_services_room/src/data/taunts_config.rs new file mode 100644 index 0000000..ce38214 --- /dev/null +++ b/rc_services_room/src/data/taunts_config.rs @@ -0,0 +1,86 @@ +use polariton::operation::{Typed, Dict}; + +pub struct TauntsData { + pub taunts: Vec, +} + +impl TauntsData { + pub fn as_transmissible(&self) -> Typed { + Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: self.taunts.iter().map(|t| (Typed::Str(t.group_name.clone().into()), t.as_transmissible())).collect(), + }) + } +} + +pub struct TauntData { + pub group_name: String, // parent key + // Dict + pub assets: AssetData, + pub animation_offset_x: f32, + pub animation_offset_y: f32, + pub animation_offset_z: f32, + pub cubes: Vec, +} + +impl TauntData { + pub fn as_transmissible(&self) -> Typed { + let mut items = vec![ + (Typed::Str("defaultAnimOffsetx".into()), Typed::Float(self.animation_offset_x)), + (Typed::Str("defaultAnimOffsety".into()), Typed::Float(self.animation_offset_y)), + (Typed::Str("defaultAnimOffsetz".into()), Typed::Float(self.animation_offset_z)), + (Typed::Str("cubes".into()), Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: self.cubes.iter().enumerate().map(|(i, cube)| (Typed::Str(i.to_string().into()), cube.as_transmissible())).collect(), + })), + ]; + items.append(&mut self.assets.as_transmissible()); + Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items, + }) + } +} + +pub struct AssetData { + pub idle_effect: String, + pub active_effect: String, + pub sound_effect: String, +} + +impl AssetData { + pub fn as_transmissible(&self) -> Vec<(Typed, Typed)> { + vec![ + (Typed::Str("idleEffect".into()), Typed::Str(self.idle_effect.clone().into())), + (Typed::Str("tauntEffect".into()), Typed::Str(self.active_effect.clone().into())), + (Typed::Str("tauntSoundEffect".into()), Typed::Str(self.sound_effect.clone().into())), + ] + } +} + +pub struct CubeData { + pub cube_id: u32, // hex + pub position_x: i32, + pub position_y: i32, + pub position_z: i32, + pub rotation: u8, +} + +impl CubeData { + pub fn as_transmissible(&self) -> Typed { + Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: vec![ + (Typed::Str("cubeid".into()), Typed::Str(hex::encode((self.cube_id as i32).to_le_bytes()).into())), + (Typed::Str("positionx".into()), Typed::Int(self.position_x)), + (Typed::Str("positiony".into()), Typed::Int(self.position_y)), + (Typed::Str("positionz".into()), Typed::Int(self.position_z)), + (Typed::Str("rotation".into()), Typed::Byte(self.rotation)), + ] + }) + } +} diff --git a/rc_services_room/src/data/weapon_list.rs b/rc_services_room/src/data/weapon_list.rs new file mode 100644 index 0000000..cbe4a90 --- /dev/null +++ b/rc_services_room/src/data/weapon_list.rs @@ -0,0 +1,213 @@ +#![allow(dead_code)] +use polariton::operation::Typed; + +#[derive(Default)] +pub struct WeaponData { + pub damage_inflicted: Option, + pub protonium_damage_scale: Option, + pub projectile_speed: Option, + pub projectile_range: Option, + pub base_inaccuracy: Option, + pub base_air_inaccuracy: Option, + pub movement_inaccuracy: Option, + pub movement_max_speed: Option, + pub movement_min_speed: Option, + pub gun_rotation_slow: Option, + pub movement_inaccuracy_decay: Option, + pub slow_rotation_decay: Option, + pub quick_rotation_decay: Option, + pub movement_inaccuracy_recovery: Option, + pub repeat_fire_inaccuracy_total_degrees: Option, + pub repeat_fire_inaccuracy_decay: Option, + pub repeat_fire_innaccuracy_recovery: Option, + pub fire_instant_accuracy_decay: Option, // degrees + pub accuracy_non_recover_time: Option, + pub accuracy_decay: Option, + pub damage_radius: Option, + pub plasma_time_to_full_damage: Option, + pub plasma_starting_radius_scale: Option, + pub nano_dps: Option, + pub nano_hps: Option, + pub tesla_damage: Option, + pub tesla_charges: Option, + pub aeroflak_proximity_damage: Option, + pub aeroflak_damage_radius: Option, + pub aeroflak_explosion_radius: Option, + pub aeroflak_ground_clearance: Option, + pub aeroflak_max_stacks: Option, + pub aeroflak_damage_per_stack: Option, + pub aeroflak_stack_expire: Option, + pub shot_cooldown: Option, + pub smart_rotation_cooldown: Option, + pub smart_rotation_cooldown_extra: Option, + pub smart_rotation_max_stacks: Option, + pub spin_up_time: Option, + pub spin_down_time: Option, + pub spin_initial_cooldown: Option, + pub group_fire_scales: Vec, + pub mana_cost: Option, + pub lock_time: Option, + pub full_lock_release: Option, + pub change_lock_time: Option, + pub max_rotation_speed: Option, + pub initial_rotation_speed: Option, + pub rotation_acceleration: Option, + pub nano_healing_priority_time: Option, + pub module_range: Option, + pub shield_lifetime: Option, + pub teleport_time: Option, + pub camera_time: Option, + pub camera_delay: Option, + pub to_invisible_speed: Option, + pub to_invisible_duration: Option, + pub to_visible_duration: Option, + pub countdown_time: Option, + pub stun_time: Option, + pub stun_radius: Option, + pub effect_duration: Option, +} + +impl WeaponData { + pub fn as_transmissible(&self) -> Typed { + let mut out = Vec::new(); + + self.damage_inflicted.map(|x| out.push((Typed::Str("damageInflicted".into()), Typed::Int(x)))); + self.protonium_damage_scale.map(|x| out.push((Typed::Str("protoniumDamageScale".into()), Typed::Float(x)))); + self.projectile_speed.map(|x| out.push((Typed::Str("projectileSpeed".into()), Typed::Float(x)))); + self.projectile_range.map(|x| out.push((Typed::Str("projectileRange".into()), Typed::Float(x)))); + self.base_inaccuracy.map(|x| out.push((Typed::Str("baseInaccuracy".into()), Typed::Float(x)))); + self.base_air_inaccuracy.map(|x| out.push((Typed::Str("baseAirInaccuracy".into()), Typed::Float(x)))); + self.movement_inaccuracy.map(|x| out.push((Typed::Str("movementInaccuracy".into()), Typed::Float(x)))); + self.movement_max_speed.map(|x| out.push((Typed::Str("movementMaxThresholdSpeed".into()), Typed::Float(x)))); + self.movement_min_speed.map(|x| out.push((Typed::Str("movementMinThresholdSpeed".into()), Typed::Float(x)))); + self.gun_rotation_slow.map(|x| out.push((Typed::Str("gunRotationThresholdSlow".into()), Typed::Float(x)))); + self.movement_inaccuracy_decay.map(|x| out.push((Typed::Str("movementInaccuracyDecayTime".into()), Typed::Float(x)))); + self.slow_rotation_decay.map(|x| out.push((Typed::Str("slowRotationInaccuracyDecayTime".into()), Typed::Float(x)))); + self.quick_rotation_decay.map(|x| out.push((Typed::Str("quickRotationInaccuracyDecayTime".into()), Typed::Float(x)))); + self.movement_inaccuracy_recovery.map(|x| out.push((Typed::Str("movementInaccuracyRecoveryTime".into()), Typed::Float(x)))); + self.repeat_fire_inaccuracy_total_degrees.map(|x| out.push((Typed::Str("repeatFireInaccuracyTotalDegrees".into()), Typed::Float(x)))); + self.repeat_fire_inaccuracy_decay.map(|x| out.push((Typed::Str("repeatFireInaccuracyDecayTime".into()), Typed::Float(x)))); + self.repeat_fire_innaccuracy_recovery.map(|x| out.push((Typed::Str("repeatFireInaccuracyRecoveryTime".into()), Typed::Float(x)))); + self.fire_instant_accuracy_decay.map(|x| out.push((Typed::Str("fireInstantAccuracyDecayDegrees".into()), Typed::Float(x)))); // degrees + self.accuracy_non_recover_time.map(|x| out.push((Typed::Str("accuracyNonRecoverTime".into()), Typed::Float(x)))); + self.accuracy_decay.map(|x| out.push((Typed::Str("accuracyDecayTime".into()), Typed::Float(x)))); + self.damage_radius.map(|x| out.push((Typed::Str("damageRadius".into()), Typed::Float(x)))); + self.plasma_time_to_full_damage.map(|x| out.push((Typed::Str("plasmaTimeToFullDamage".into()), Typed::Float(x)))); + self.plasma_starting_radius_scale.map(|x| out.push((Typed::Str("plasmaStartingRadiusScale".into()), Typed::Float(x)))); + self.nano_dps.map(|x| out.push((Typed::Str("nanoDPS".into()), Typed::Float(x)))); + self.nano_hps.map(|x| out.push((Typed::Str("nanoHPS".into()), Typed::Float(x)))); + self.tesla_damage.map(|x| out.push((Typed::Str("teslaDamage".into()), Typed::Float(x)))); + self.tesla_charges.map(|x| out.push((Typed::Str("teslaCharges".into()), Typed::Float(x)))); + self.aeroflak_proximity_damage.map(|x| out.push((Typed::Str("aeroflakProximityDamage".into()), Typed::Float(x)))); + self.aeroflak_damage_radius.map(|x| out.push((Typed::Str("aeroflakDamageRadius".into()), Typed::Float(x)))); + self.aeroflak_explosion_radius.map(|x| out.push((Typed::Str("aeroflakExplosionRadius".into()), Typed::Float(x)))); + self.aeroflak_ground_clearance.map(|x| out.push((Typed::Str("aeroflakGroundClearance".into()), Typed::Float(x)))); + self.aeroflak_max_stacks.map(|x| out.push((Typed::Str("aeroflakBuffMaxStacks".into()), Typed::Int(x)))); + self.aeroflak_damage_per_stack.map(|x| out.push((Typed::Str("aeroflakBuffDamagePerStack".into()), Typed::Int(x)))); + self.aeroflak_stack_expire.map(|x| out.push((Typed::Str("aeroflakBuffTimeToExpire".into()), Typed::Float(x)))); + self.shot_cooldown.map(|x| out.push((Typed::Str("cooldownBetweenShots".into()), Typed::Float(x)))); + self.smart_rotation_cooldown.map(|x| out.push((Typed::Str("smartRotationCooldown".into()), Typed::Float(x)))); + self.smart_rotation_cooldown_extra.map(|x| out.push((Typed::Str("smartRotationExtraCooldownTime".into()), Typed::Float(x)))); + self.smart_rotation_max_stacks.map(|x| out.push((Typed::Str("smartRotationMaxStacks".into()), Typed::Float(x)))); + self.spin_up_time.map(|x| out.push((Typed::Str("spinUpTime".into()), Typed::Float(x)))); + self.spin_down_time.map(|x| out.push((Typed::Str("spinDownTime".into()), Typed::Float(x)))); + self.spin_initial_cooldown.map(|x| out.push((Typed::Str("spinInitialCooldown".into()), Typed::Float(x)))); + + if !self.group_fire_scales.is_empty() { + let typed_arr: Vec = self.group_fire_scales.iter().map(|x| Typed::Float(*x)).collect(); + out.push((Typed::Str("groupFireScales".into()), Typed::ObjArr(typed_arr.into()))); + } + + self.mana_cost.map(|x| out.push((Typed::Str("manaCost".into()), Typed::Float(x)))); + self.lock_time.map(|x| out.push((Typed::Str("lockTime".into()), Typed::Float(x)))); + self.full_lock_release.map(|x| out.push((Typed::Str("fullLockRelease".into()), Typed::Float(x)))); + self.change_lock_time.map(|x| out.push((Typed::Str("changeLockTime".into()), Typed::Float(x)))); + self.max_rotation_speed.map(|x| out.push((Typed::Str("maxRotationSpeed".into()), Typed::Float(x)))); + self.initial_rotation_speed.map(|x| out.push((Typed::Str("initialRotationSpeed".into()), Typed::Float(x)))); + self.rotation_acceleration.map(|x| out.push((Typed::Str("rotationAcceleration".into()), Typed::Float(x)))); + self.nano_healing_priority_time.map(|x| out.push((Typed::Str("nanoHealingPriorityTime".into()), Typed::Float(x)))); + self.module_range.map(|x| out.push((Typed::Str("moduleRange".into()), Typed::Float(x)))); + self.shield_lifetime.map(|x| out.push((Typed::Str("shieldLifetime".into()), Typed::Float(x)))); + self.teleport_time.map(|x| out.push((Typed::Str("teleportTime".into()), Typed::Float(x)))); + self.camera_time.map(|x| out.push((Typed::Str("cameraTime".into()), Typed::Float(x)))); + self.camera_delay.map(|x| out.push((Typed::Str("cameraDelay".into()), Typed::Float(x)))); + self.to_invisible_speed.map(|x| out.push((Typed::Str("toInvisibleSpeed".into()), Typed::Float(x)))); + self.to_invisible_duration.map(|x| out.push((Typed::Str("toInvisibleDuration".into()), Typed::Float(x)))); + self.to_visible_duration.map(|x| out.push((Typed::Str("toVisibleDuration".into()), Typed::Float(x)))); + self.countdown_time.map(|x| out.push((Typed::Str("countdownTime".into()), Typed::Float(x)))); + self.stun_time.map(|x| out.push((Typed::Str("stunTime".into()), Typed::Float(x)))); + self.stun_radius.map(|x| out.push((Typed::Str("stunRadius".into()), Typed::Float(x)))); + self.effect_duration.map(|x| out.push((Typed::Str("effectDuration".into()), Typed::Float(x)))); + Typed::HashMap(out.into()) + } +} + +#[repr(u32)] +#[derive(Clone, Copy)] +pub enum ItemCategory { + NoFunction, + Wheel, + Hover, + Wing, + Rudder, + Thruster, + InsectLeg, + MechLeg, + Ski, + TankTrack, + Rotor, + SrpinterLeg, + Propeller, + Laser = 100, + Plasma = 200, + Mortar = 250, + Rail = 300, + Nano = 400, + Tesla = 500, + Aeroflak = 600, + Ion = 650, + Seeker = 701, + Chaingun = 750, + ShieldModule = 800, + GhostModule, + BlinkModule, + EmpModule, + WindowmakerModule, + EnergyModule = 900, +} + +impl ItemCategory { + pub fn as_str(&self) -> &'static str { + match self { + ItemCategory::NoFunction => "NotAFunctionalItem", + ItemCategory::Wheel => "Wheel", + ItemCategory::Hover => "Hover", + ItemCategory::Wing => "Wing", + ItemCategory::Rudder => "Rudder", + ItemCategory::Thruster => "Thruster", + ItemCategory::InsectLeg => "InsectLeg", + ItemCategory::MechLeg => "MechLeg", + ItemCategory::Ski => "Ski", + ItemCategory::TankTrack => "TankTrack", + ItemCategory::Rotor => "Rotor", + ItemCategory::SrpinterLeg => "SrpinterLeg", + ItemCategory::Propeller => "Propeller", + ItemCategory::Laser => "Laser", + ItemCategory::Plasma => "Plasma", + ItemCategory::Mortar => "Mortar", + ItemCategory::Rail => "Rail", + ItemCategory::Nano => "Nano", + ItemCategory::Tesla => "Tesla", + ItemCategory::Aeroflak => "Aeroflak", + ItemCategory::Ion => "Ion", + ItemCategory::Seeker => "Seeker", + ItemCategory::Chaingun => "Chaingun", + ItemCategory::ShieldModule => "ShieldModule", + ItemCategory::GhostModule => "GhostModule", + ItemCategory::BlinkModule => "BlinkModule", + ItemCategory::EmpModule => "EmpModule", + ItemCategory::WindowmakerModule => "WindowmakerModule", + ItemCategory::EnergyModule => "EnergyModule", + } + } +} diff --git a/rc_services_room/src/events/mod.rs b/rc_services_room/src/events/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/rc_services_room/src/main.rs b/rc_services_room/src/main.rs new file mode 100644 index 0000000..52b2619 --- /dev/null +++ b/rc_services_room/src/main.rs @@ -0,0 +1,402 @@ +mod cli; +mod state; + +mod data; +mod events; +mod operations; + +use std::num::NonZero; +use std::sync::Arc; + +use polariton_auth::Handshake; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net; + +use polariton::packet::{Cryptographer, Data, Message, Packet, Ping, StandardMessage, StandardPacket}; +use polariton::operation::{OperationResponse, Typed}; + +pub type UserTy = std::sync::RwLock; + +#[tokio::main] +async fn main() -> std::io::Result<()> { + env_logger::init(); + let args = cli::CliArgs::get(); + log::debug!("Got cli args {:?}", args); + + let op_handler = Arc::new(operations::handler()); + + let ip_addr: std::net::IpAddr = args.ip.parse().expect("Invalid IP address"); + + let listener = net::TcpListener::bind(std::net::SocketAddr::new(ip_addr, args.port)).await?; + + #[cfg(not(debug_assertions))] + loop { + let (socket, address) = listener.accept().await?; + tokio::spawn(process_socket(socket, address, NonZero::new(args.retries), op_handler.clone())); + } + #[cfg(debug_assertions)] + { + let (socket, address) = listener.accept().await?; + process_socket(socket, address, NonZero::new(args.retries), op_handler.clone()).await; + Ok(()) + } +} + +async fn process_socket(mut socket: net::TcpStream, address: std::net::SocketAddr, retries: Option>, op_handler: Arc>) { + log::debug!("Accepting connection from address {}", address); + + let mut read_buf = Vec::new(); + let mut write_buf = Vec::new(); + let enc = match do_connect_handshake(&mut read_buf, &mut socket, retries).await { + Some(x) => x, + None => { + log::error!("Failed to do connect handshake with {}", address); + return; + } + }; + let sock_state = state::State::new(enc); + let user_state = sock_state.user(); + while let Ok(packet) = receive_packet(&mut read_buf, &mut socket, retries, sock_state.binrw_args()).await { + match packet { + Packet::Ping(ping) => { + handle_ping(ping, &mut write_buf, &mut socket).await; + }, + Packet::Packet(packet) => { + // remove packet's advertised size from the buffer + for _ in 0..packet.header.len { + read_buf.remove(0); + } + match packet.message { + Message::Ping(ping) => { + handle_ping(ping, &mut write_buf, &mut socket).await; + }, + Message::Standard(msg) => { + + let is_encrypted = msg.is_encrypted(); + match msg.data { + Data::OpReq(req) => { + let resp = op_handler.handle_op(&user_state, req); + let result = send_packet( + Packet::from_message( + Message::Standard(StandardMessage { + flags: 0, + data: Data::OpResp(resp), + }.encrypt(is_encrypted)), + packet.header.channel, + packet.header.is_reliable(), + sock_state.binrw_args()).unwrap(), + &mut write_buf, &mut socket, sock_state.binrw_args()).await; + match result { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send operation response packet: {}", e); + } + } + }, + data => log::warn!("Failed to handle packet with message data {:?}", data), + } + } + } + } + //log::warn!("Not handling packet {:?}", packet), + } + } + log::debug!("Goodbye connection from address {}", address); +} + +async fn handle_ping(ping: Ping, buf: &mut Vec, socket: &mut net::TcpStream) { + buf.clear(); + let resp = Packet::Ping(polariton_auth::ping_pong(ping)); + resp.to_buf(buf, None).unwrap(); + let write_count = socket.write(buf).await.unwrap(); + log::debug!("(ping) Write {} bytes to socket: {:?}", write_count, buf); + buf.clear(); +} + +fn buf_likely_valid(buf: &[u8]) -> bool { + buf.is_empty() || buf[0] == Packet::PING_MAGIC || buf[0] == Packet::FRAMED_MAGIC +} + +async fn read_more(buf: &mut Vec, socket: &mut net::TcpStream) -> Result { + let read_count = socket.read_buf(buf).await?; + log::debug!("Read {} bytes from socket: {:?}", read_count, buf); + Ok(read_count) +} + +async fn receive_packet(buf: &mut Vec, socket: &mut net::TcpStream, max_retries: Option>, args: Option>>) -> Result { + if buf.is_empty() { + let read_count = read_more(buf, socket).await?; + if read_count == 0 { return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "socket did not read any bytes")); } // bad packet + } + + let mut last_err = None; + let mut must_succeed_next = false; + if let Some(max_retries) = max_retries { + for _ in 0..max_retries.get() { + match Packet::from_buf(&buf, args.clone()) { + Ok(packet) => { + log::debug!("Received packet {:?}", packet); + return Ok(packet); + }, + Err(e) => last_err = Some(e), + } + if must_succeed_next { + break; + } + must_succeed_next = read_more(buf, socket).await? == 0; + } + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, last_err.unwrap())); + } else { + while buf_likely_valid(buf.as_slice()) { + match Packet::from_buf(&buf, args.clone()) { + Ok(packet) => { + log::debug!("Received packet {:?}", packet); + return Ok(packet); + }, + Err(e) => last_err = Some(e), + } + if must_succeed_next { + break; + } + must_succeed_next = read_more(buf, socket).await? == 0; + } + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, last_err.unwrap())); + } +} + +async fn send_packet(packet: Packet, buf: &mut Vec, socket: &mut net::TcpStream, args: Option>>) -> Result<(), std::io::Error> { + log::debug!("Sending packet {:?}", packet); + buf.clear(); + packet.to_buf(buf, args).map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?; + let write_count = socket.write(buf).await?; + log::debug!("Write {} bytes to socket: {:?}", write_count, buf); + #[cfg(debug_assertions)] + { + // print out unencrypted packet too + if let Packet::Packet(standard_p) = packet { + if let Message::Standard(standard_m) = standard_p.message { + if standard_m.is_encrypted() { + let standard_m = standard_m.encrypt(false); + let packet = Packet::Packet(StandardPacket { header: standard_p.header, message: Message::Standard(standard_m) }); + packet.to_buf(buf, None).map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?; + log::debug!("Unencrypted bytes of packet: {:?} (len: {})", buf, buf.len()); + } + + } + } + } + buf.clear(); + Ok(()) +} + +const APP_ID: &str = "WebServicesServer"; + +struct AuthImpl; + +const TOKEN_KEY: u8 = 216; // token;refresh_token +//const UNKNOWN_BYTE_KEY: u8 = 217; +const SERVICE_KEY: u8 = 224; +const USERNAME_KEY: u8 = 225; + +//const CCU_KEY: u8 = 245; + +#[derive(Debug)] +enum AuthError { + WrongService { expected: String, actual: String }, + MissingService, + MissingToken, + MissingUsername, +} + +impl AuthError { + fn log_err(&self) { + match self { + Self::WrongService { expected, actual } => log::error!("(auth fail) Got unexpected service {}, expected {}", actual, expected), + Self::MissingService => log::error!("(auth fail) No service name param ({}) received", SERVICE_KEY), + Self::MissingToken => log::error!("(auth fail) No token param ({}) received", TOKEN_KEY), + Self::MissingUsername => log::error!("(auth fail) No username param ({}) received", USERNAME_KEY), + } + } +} + +impl polariton_auth::AuthProvider for AuthImpl { + fn validate(&mut self, params: &std::collections::HashMap) -> Result, AuthError> { + if let Some(Typed::Str(token)) = params.get(&TOKEN_KEY) { + if let Some(Typed::Str(service)) = params.get(&SERVICE_KEY) { + if let Some(Typed::Str(user)) = params.get(&USERNAME_KEY) { + if service.string == APP_ID { + let params_resp = std::collections::HashMap::::new(); + //params_resp.insert(CCU_KEY, Typed::Byte(0)); + log::debug!("Auth success for {} (token: {})", user.string, token.string); + Ok(params_resp) + } else { Err(AuthError::WrongService { expected: APP_ID.to_owned(), actual: service.string.to_owned() }) } + } else { Err(AuthError::MissingUsername) } + } else { Err(AuthError::MissingService) } + } else { Err(AuthError::MissingToken) } + } +} + +async fn do_connect_handshake( + buf: &mut Vec, + socket: &mut net::TcpStream, + max_retries: Option>, +) -> Option>> { + let handshake = Handshake::new(APP_ID); + // connect + log::debug!("(connect) Handling first packet"); + let packet1 = match receive_packet(buf, socket, max_retries, None).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read connect packet: {}", e); + return None; + } + }; + buf.clear(); + let (handshake, to_send) = match handshake.connect(&packet1) { + Ok(x) => (x.handshake, x.extra), + Err(e) => { + log::error!("Failed to handle connect handshake: {:?}", e.extra); + return None; + } + }; + match send_packet(to_send, buf, socket, None).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send connect ack packet: {}", e); + return None; + } + } + // encrypt + log::debug!("(connect) Handling second packet"); + let mut packet2 = match receive_packet(buf, socket, max_retries, None).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) public key packet: {}", e); + return None; + } + }; + buf.clear(); + while let Packet::Ping(ping) = packet2 { + handle_ping(ping, buf, socket).await; + packet2 = match receive_packet(buf, socket, max_retries, None).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) public key packet: {}", e); + return None; + } + }; + buf.clear(); + } + let (handshake, to_send, crypto) = match handshake.encrypt(&packet2) { + Ok(x) => (x.handshake, x.extra.0, x.extra.1), + Err(e) => { + log::error!("Failed to handle encryption handshake: {:?}", e.extra); + return None; + } + }; + match send_packet(to_send, buf, socket, None).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send encryption ack packet: {}", e); + return None; + } + } + // pre-auth + let handshake = handshake.with_auth(AuthImpl); + // authenticate + log::debug!("(connect) Handling third packet"); + let mut packet3 = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) auth packet: {}", e); + return None; + } + }; + buf.clear(); + while let Packet::Ping(ping) = packet3 { + handle_ping(ping, buf, socket).await; + packet3 = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) auth packet: {}", e); + return None; + } + }; + buf.clear(); + } + let to_send = match handshake.authenticate(&packet3, crypto.clone()) { + Ok(x) => x, + Err(h) => match h.extra { + polariton_auth::AuthError::Validation(e) => { + e.log_err(); + return None; + }, + e => { + log::error!("Failed to handle auth handshake: {:?}", e); + return None; + }, + }, + }; + match send_packet(to_send, buf, socket, Some(crypto.clone())).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send auth ack packet: {}", e); + return None; + } + } + + // join lobby + log::debug!("(join lobby) Handling fourth packet"); + let mut packet_j = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) join packet: {}", e); + return None; + } + }; + buf.clear(); + while let Packet::Ping(ping) = packet_j { + handle_ping(ping, buf, socket).await; + packet_j = match receive_packet(buf, socket, max_retries, Some(crypto.clone())).await { + Ok(x) => x, + Err(e) => { + log::error!("Failed to read (maybe) join packet: {}", e); + return None; + } + }; + buf.clear(); + } + if let Packet::Packet(msg) = &packet_j { + if let Message::Standard(st) = &msg.message { + if let Data::OpReq(req) = &st.data { + if req.code == 226 { // join lobby (but for real this time) + let mut params = std::collections::HashMap::::new(); + //params.insert(252 /* actors in game */, Typed::Str(game_server_url.into())); + params.insert(254 /* game server address */, Typed::Int(42)); + params.insert(249 /* actor properties */, Typed::HashMap(Vec::new().into())); + params.insert(248 /* game properties */, Typed::HashMap(Vec::new().into())); + let resp = Packet::from_message( + Message::Standard( + StandardMessage { flags: 0, + data: Data::OpResp(OperationResponse { + code: req.code, + return_code: 0, + message: Typed::Null, + params: params.into(), + }), + }.encrypt(true)), 0, true, Some(crypto.clone())).unwrap(); + match send_packet(resp, buf, socket, Some(crypto.clone())).await { + Ok(_) => {}, + Err(e) => { + log::error!("Failed to send lobby ack packet: {}", e); + return None; + } + } + } + } + } + } + buf.clear(); + + Some(crypto) +} diff --git a/rc_services_room/src/operations/all_customisations_info.rs b/rc_services_room/src/operations/all_customisations_info.rs new file mode 100644 index 0000000..1cbabcc --- /dev/null +++ b/rc_services_room/src/operations/all_customisations_info.rs @@ -0,0 +1,64 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Arr}; + +use crate::data::customisation_info::CustomisationData; + +const SKINS_KEY: u8 = 228; +const SPAWNS_KEY: u8 = 229; +const DEATHS_KEY: u8 = 230; + +const OWNED_SKINS_KEY: u8 = 231; +const OWNED_SPAWNS_KEY: u8 = 232; +const OWNED_DEATHS_KEY: u8 = 233; +const OWNED_EMOTES_KEY: u8 = 76; + +pub(super) fn all_customisations_provider() -> SimpleFunc<216, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(SKINS_KEY, Typed::Arr(Arr { + ty: 104, // hashtable + items: vec![ + CustomisationData { + id: "skin0".to_string(), + localised_name: "Default".to_string(), + skin_scene_name: "TODO_skin".to_string(), + simulation_prefab: "TODO_sim_prefab".to_string(), + preview_image_name: "TODO_preview_img".to_string(), + is_default: true, + }.as_transmissible(), + ], + })); + params.insert(SPAWNS_KEY, Typed::Arr(Arr { + ty: 104, // hashtable + items: vec![ + CustomisationData { + id: "spawn0".to_string(), + localised_name: "Default".to_string(), + skin_scene_name: "TODO_skin".to_string(), + simulation_prefab: "TODO_sim_prefab".to_string(), + preview_image_name: "TODO_preview_img".to_string(), + is_default: true, + }.as_transmissible(), + ], + })); + params.insert(DEATHS_KEY, Typed::Arr(Arr { + ty: 104, // hashtable + items: vec![ + CustomisationData { + id: "death0".to_string(), + localised_name: "Default".to_string(), + skin_scene_name: "TODO_skin".to_string(), + simulation_prefab: "TODO_sim_prefab".to_string(), + preview_image_name: "TODO_preview_img".to_string(), + is_default: true, + }.as_transmissible(), + ], + })); + + params.insert(OWNED_SKINS_KEY, Typed::StrArr(vec![].into())); + params.insert(OWNED_SPAWNS_KEY, Typed::StrArr(vec![].into())); + params.insert(OWNED_DEATHS_KEY, Typed::StrArr(vec![].into())); + params.insert(OWNED_EMOTES_KEY, Typed::StrArr(vec![].into())); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/battle_arena_config.rs b/rc_services_room/src/operations/battle_arena_config.rs new file mode 100644 index 0000000..e32a8d6 --- /dev/null +++ b/rc_services_room/src/operations/battle_arena_config.rs @@ -0,0 +1,33 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +use crate::data::battle_arena_config::*; + +const PARAM_KEY: u8 = 1; + +pub(super) fn battle_arena_config_provider() -> SimpleFunc<53, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 104, // obj + items: vec![ + (Typed::Str("BattleArenaSettings".into()), BattleArenaData { + protonium_health: 1_000, + respawn_time_seconds: 10, + heal_over_time_per_tower: vec![10, 10, 10, 10], + base_machine_map: Vec::default(), + equalizer_model: Vec::default(), + equalizer_health: 1_000_000, + equalizer_trigger_time_seconds: vec![10, 10, 10, 10, 10], + equalizer_warning_seconds: 10, + equalizer_duration_seconds: vec![20, 20, 20, 20, 20], + capture_time_seconds_per_player: vec![30, 20, 10, 5, 1], + num_segments: 4, + heal_escalation_time_seconds: 5, + }.as_transmissible()) + ], + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/client_config.rs b/rc_services_room/src/operations/client_config.rs new file mode 100644 index 0000000..88186ef --- /dev/null +++ b/rc_services_room/src/operations/client_config.rs @@ -0,0 +1,31 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +use crate::data::client_config::*; + +const PARAM_KEY: u8 = 36; + +pub(super) fn client_config_provider() -> SimpleFunc<34, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 104, // hashtable + items: vec![ + (Typed::Str("GameplaySettings".into()), GameplaySettings { + show_tutorial_after_date: "2025-01-01".to_owned(), + health_threshold: 10.0, + microbot_sphere: 10.0, + misfire_angle: 20.0, + shield_dps: 100, + shield_hps: 2_000, + request_review_level: 10_000, + critical_ratio: 10.0, + cross_promo_image: "https://git.ngni.us/TODO".to_owned(), // TODO + cross_promo_link: "https://git.ngni.us/OpenJam/servers".to_owned(), + }.as_transmissible()) + ].into(), + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/cosmetic_config.rs b/rc_services_room/src/operations/cosmetic_config.rs new file mode 100644 index 0000000..b0e3576 --- /dev/null +++ b/rc_services_room/src/operations/cosmetic_config.rs @@ -0,0 +1,18 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::ParameterTable; + +use crate::data::cosmetic_limits::CosmeticLimitsData; + +const PARAM_KEY: u8 = 196; + +pub(super) fn cosmetic_limits_config_provider() -> SimpleFunc<72, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, CosmeticLimitsData { + others_max_holo_and_trails: 16, + others_max_headlamps: 8, + others_max_cosmetic_items_with_particles: 12, + }.as_transmissible()); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/cpu_limits_config.rs b/rc_services_room/src/operations/cpu_limits_config.rs new file mode 100644 index 0000000..252ee7e --- /dev/null +++ b/rc_services_room/src/operations/cpu_limits_config.rs @@ -0,0 +1,20 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::ParameterTable; + +use crate::data::cpu_limits::CpuLimitsData; + +const PARAM_KEY: u8 = 194; + +pub(super) fn cpu_config_provider() -> SimpleFunc<75, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, CpuLimitsData { + premium_for_life_cosmetic_gpu: 12, + premium_cosmetic_cpu: 6, + no_premium_cosmetic_cpu: 3, + max_regular_health: 2_000_000, + max_megabot_health: 200_000_000, + }.as_transmissible()); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/crf_config.rs b/rc_services_room/src/operations/crf_config.rs new file mode 100644 index 0000000..59ed562 --- /dev/null +++ b/rc_services_room/src/operations/crf_config.rs @@ -0,0 +1,18 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::ParameterTable; + +use crate::data::crf_config::*; + +const PARAM_KEY: u8 = 110; + +pub(super) fn crf_config_provider() -> SimpleFunc<92, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, RobotShopConfig { + cpu_ranges: vec![100, 500, 1_000, 2_000], + submission_mult: 1.0, + earnings_mult: 1.0, + }.as_transmissible()); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/cube_list.rs b/rc_services_room/src/operations/cube_list.rs new file mode 100644 index 0000000..8ac4a24 --- /dev/null +++ b/rc_services_room/src/operations/cube_list.rs @@ -0,0 +1,43 @@ +use std::collections::HashMap; + +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +use crate::data::cube_list::*; + +const PARAM_KEY: u8 = 1; + +pub(super) fn cube_list_provider() -> SimpleFunc<2, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 104, // hashtable + items: vec![ + //(u32 in base16 aka hex, hashtable) + (Typed::Str("DEADBEEF".into()), CubeInfo { + cpu: 1, + health: 1, + health_boost: 1.0, + grey_out_in_tutorial: false, + visibility: VisibilityMode::All, + indestructible: true, + category: 1, + placements: 63, + protonium: false, + unlocked_by_league: false, + league_unlock_index: 1, + stats: HashMap::default(), + description: "This is a very descriptive description".to_string(), + size: ItemTier::NoTier, + type_: ItemType::NoFunction, + ranking: 1, + cosmetic: false, + variant_of: "0".to_string(), + ignore_in_weapon_list: true, + }.as_transmissible()) + ].into(), + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/damage_boost_stats.rs b/rc_services_room/src/operations/damage_boost_stats.rs new file mode 100644 index 0000000..1398c29 --- /dev/null +++ b/rc_services_room/src/operations/damage_boost_stats.rs @@ -0,0 +1,26 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +use crate::data::damage_boost::*; + +const PARAM_KEY: u8 = 192; + +pub(super) fn damage_boost_provider() -> SimpleFunc<163, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 42, // obj + items: vec![ + (Typed::Str("damageBoost".into()), DamageBoostData { + damage_map: vec![ + (100, 1000.0), + (1000, 100.0), + (2000, 1.0), + ], + }.as_transmissible()) + ], + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/eac.rs b/rc_services_room/src/operations/eac.rs new file mode 100644 index 0000000..fc2bcd4 --- /dev/null +++ b/rc_services_room/src/operations/eac.rs @@ -0,0 +1,23 @@ +use polariton_server::operations::{Operation, OperationCode}; + +pub struct EacChallengeIgnorer; + +impl Operation for EacChallengeIgnorer { + type State = (); + type User = crate::UserTy; + + fn handle(&self, params: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse { + polariton::operation::OperationResponse { + code: 161, // skip the challenge (hopefully) + return_code: 0, + message: polariton::operation::Typed::Null, + params, + } + } +} + +impl OperationCode for EacChallengeIgnorer { + fn op_code() -> u8 { + 160 + } +} diff --git a/rc_services_room/src/operations/game_quality.rs b/rc_services_room/src/operations/game_quality.rs new file mode 100644 index 0000000..6187d36 --- /dev/null +++ b/rc_services_room/src/operations/game_quality.rs @@ -0,0 +1,82 @@ +use std::collections::HashMap; + +use polariton::operation::{Typed, ParameterTable, OperationResponse, Dict}; +use polariton_server::operations::{Operation, OperationCode}; + +pub struct QualityConfigTeller; + +impl Operation for QualityConfigTeller { + type State = (); + type User = crate::UserTy; + + fn handle(&self, _: ParameterTable, _: &mut Self::State, _: &Self::User) -> OperationResponse { + let quality_levels = Typed::HashMap(vec![ + (Typed::Str("extremLow".into()), Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: vec![ + (Typed::Str("Level".into()), Typed::Long(0)), + (Typed::Str("default".into()), Typed::Float(0.0)), + ], + })), + (Typed::Str("low".into()), Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: vec![ + (Typed::Str("Level".into()), Typed::Long(1)), + (Typed::Str("default".into()), Typed::Float(0.0)), + ], + })), + (Typed::Str("normal".into()), Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: vec![ + (Typed::Str("Level".into()), Typed::Long(2)), + (Typed::Str("default".into()), Typed::Float(0.0)), + ], + })), + (Typed::Str("beautiful".into()), Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: vec![ + (Typed::Str("Level".into()), Typed::Long(3)), + (Typed::Str("default".into()), Typed::Float(0.0)), + ], + })), + (Typed::Str("fantastic".into()), Typed::Dict(Dict { + key_ty: 115, + val_ty: 42, + items: vec![ + (Typed::Str("Level".into()), Typed::Long(4)), + (Typed::Str("default".into()), Typed::Float(f32::MAX)), + ], + })), + ].into()); + let mem_thresholds = Typed::HashMap(vec![ + (Typed::Str("low".into()), Typed::Int(69)), + (Typed::Str("extremeLow".into()), Typed::Int(42)), + ].into()); + let mut resp_params = HashMap::new(); + resp_params.insert(1 /* dict */, Typed::Dict( + Dict { + key_ty: 115, // str + val_ty: 104, // hash table + items: vec![ + (Typed::Str("qualityLevels".into()), quality_levels), + (Typed::Str("systemMemoryThresholds".into()), mem_thresholds), + ], + })); + OperationResponse { + code: 104, + return_code: 0, + message: Typed::Null, + params: resp_params.into(), + } + } +} + +impl OperationCode for QualityConfigTeller { + fn op_code() -> u8 { + 104 + } +} diff --git a/rc_services_room/src/operations/load_analytics.rs b/rc_services_room/src/operations/load_analytics.rs new file mode 100644 index 0000000..d2232fc --- /dev/null +++ b/rc_services_room/src/operations/load_analytics.rs @@ -0,0 +1,34 @@ +use polariton::operation::Dict; +use polariton_server::operations::{Operation, OperationCode}; + +pub struct NoAnalytics; + +impl NoAnalytics { + const ANALYTICS_DICT_KEY: u8 = 83; +} + +impl Operation for NoAnalytics { + type State = (); + type User = crate::UserTy; + + fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse { + let mut resp_params = std::collections::HashMap::new(); + resp_params.insert(Self::ANALYTICS_DICT_KEY, polariton::operation::Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 115, // str + items: Vec::new(), + })); + polariton::operation::OperationResponse { + code: 70, + return_code: 0, + message: polariton::operation::Typed::Null, + params: resp_params.into(), + } + } +} + +impl OperationCode for NoAnalytics { + fn op_code() -> u8 { + 70 + } +} diff --git a/rc_services_room/src/operations/login_flags.rs b/rc_services_room/src/operations/login_flags.rs new file mode 100644 index 0000000..a2f1ceb --- /dev/null +++ b/rc_services_room/src/operations/login_flags.rs @@ -0,0 +1,45 @@ +use polariton_server::operations::{Operation, OperationCode}; + +pub struct UserFlagsTeller; + +impl UserFlagsTeller { + const REMOVE_OBSOLETE_CUBES_KEY: u8 = 113; + const REMOVE_UNOWNED_CUBES_KEY: u8 = 114; + const REWARD_TITLE_KEY: u8 = 115; + const REWARD_BODY_KEY: u8 = 116; + const REFUND_OBSOLETE_CUBES_KEY: u8 = 117; + const CUBES_ARE_REPLACED_KEY: u8 = 118; + const NEW_USER_KEY: u8 = 119; + const AB_TEST_KEY: u8 = 166; + const AB_GROUP_KEY: u8 = 167; +} + +impl Operation for UserFlagsTeller { + type State = (); + type User = crate::UserTy; + + fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse { + let mut resp_params = std::collections::HashMap::new(); + resp_params.insert(Self::REMOVE_OBSOLETE_CUBES_KEY, polariton::operation::Typed::Bool(false.into())); + resp_params.insert(Self::REMOVE_UNOWNED_CUBES_KEY, polariton::operation::Typed::Bool(false.into())); + resp_params.insert(Self::REWARD_TITLE_KEY, polariton::operation::Typed::Str("Yay a reward!".into())); + resp_params.insert(Self::REWARD_BODY_KEY, polariton::operation::Typed::Str("I love you very much so here's nothing as a reward.".into())); + resp_params.insert(Self::REFUND_OBSOLETE_CUBES_KEY, polariton::operation::Typed::Bool(false.into())); + resp_params.insert(Self::CUBES_ARE_REPLACED_KEY, polariton::operation::Typed::Bool(false.into())); + resp_params.insert(Self::NEW_USER_KEY, polariton::operation::Typed::Bool(false.into())); + resp_params.insert(Self::AB_TEST_KEY, polariton::operation::Typed::Str("".into())); + resp_params.insert(Self::AB_GROUP_KEY, polariton::operation::Typed::Str("".into())); + polariton::operation::OperationResponse { + code: 105, + return_code: 0, + message: polariton::operation::Typed::Null, + params: resp_params.into(), + } + } +} + +impl OperationCode for UserFlagsTeller { + fn op_code() -> u8 { + 105 + } +} diff --git a/rc_services_room/src/operations/maintenancer.rs b/rc_services_room/src/operations/maintenancer.rs new file mode 100644 index 0000000..c31c711 --- /dev/null +++ b/rc_services_room/src/operations/maintenancer.rs @@ -0,0 +1,28 @@ +use std::collections::HashMap; + +use polariton_server::operations::{Operation, OperationCode}; + +pub struct MaintenanceModeTeller; + +impl Operation for MaintenanceModeTeller { + type State = (); + type User = crate::UserTy; + + fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse { + let mut resp_params = HashMap::new(); + resp_params.insert(20 /* is in maintenance mode? */, polariton::operation::Typed::Bool(false.into())); + resp_params.insert(19 /* maintenace mode message */, polariton::operation::Typed::Str("OpenJam's servers are currently undergoing maintenance".into())); + polariton::operation::OperationResponse { + code: 20, + return_code: 0, + message: polariton::operation::Typed::Null, + params: resp_params.into(), + } + } +} + +impl OperationCode for MaintenanceModeTeller { + fn op_code() -> u8 { + 20 + } +} diff --git a/rc_services_room/src/operations/mod.rs b/rc_services_room/src/operations/mod.rs new file mode 100644 index 0000000..9ac2fc3 --- /dev/null +++ b/rc_services_room/src/operations/mod.rs @@ -0,0 +1,57 @@ +mod eac; +mod more_auth; +mod versioner; +mod maintenancer; +mod game_quality; +mod login_flags; +mod load_analytics; +mod platform_config; +mod tier_banding; +mod cube_list; +mod special_items; +mod premium_config; +mod palette_town; +mod client_config; +mod crf_config; +mod weapon_stats; +mod movement_stats; +mod power_bar_stats; +mod damage_boost_stats; +mod battle_arena_config; +mod cpu_limits_config; +mod cosmetic_config; +mod taunts_config; +mod all_customisations_info; + +use polariton_server::operations::OperationsHandler; + +pub fn handler() -> OperationsHandler { + OperationsHandler::new() + .without_state(eac::EacChallengeIgnorer) + .without_state(more_auth::MoreLobbyAuth) + .without_state(versioner::VersionTeller) + .without_state(maintenancer::MaintenanceModeTeller) + .without_state(game_quality::QualityConfigTeller) + .without_state(login_flags::UserFlagsTeller) + .without_state(polariton_server::operations::Ack::<132, _>::default()) // verify user level + .without_state(load_analytics::NoAnalytics) + .without_state(polariton_server::operations::Ack::<131, _>::default()) // analytics updated notification + .without_state(platform_config::platform_config_provider()) + .without_state(tier_banding::tiers_banding_provider()) + .without_state(cube_list::cube_list_provider()) + .without_state(special_items::special_item_list_provider()) + .without_state(premium_config::premium_config_provider()) + .without_state(palette_town::kanto()) + .without_state(client_config::client_config_provider()) + .without_state(crf_config::crf_config_provider()) + .without_state(weapon_stats::weapon_config_provider()) + .without_state(movement_stats::movement_config_provider()) + .without_state(power_bar_stats::power_bar_provider()) + .without_state(damage_boost_stats::damage_boost_provider()) + .without_state(battle_arena_config::battle_arena_config_provider()) + .without_state(cpu_limits_config::cpu_config_provider()) + .without_state(cosmetic_config::cosmetic_limits_config_provider()) + .without_state(taunts_config::taunts_config_provider()) + .without_state(all_customisations_info::all_customisations_provider()) + //.without_state(polariton_server::operations::Ack::<70, _>::default()) +} diff --git a/rc_services_room/src/operations/more_auth.rs b/rc_services_room/src/operations/more_auth.rs new file mode 100644 index 0000000..b154590 --- /dev/null +++ b/rc_services_room/src/operations/more_auth.rs @@ -0,0 +1,42 @@ +use polariton::operation::Typed; +use polariton_server::operations::{Operation, OperationCode}; + +pub struct MoreLobbyAuth; + +impl MoreLobbyAuth { + const AUTH_PAYLOAD_KEY: u8 = 245; +} + +impl Operation for MoreLobbyAuth { + type State = (); + type User = crate::UserTy; + + fn handle(&self, params: polariton::operation::ParameterTable, _: &mut Self::State, user: &Self::User) -> polariton::operation::OperationResponse { + let params_dict = params.to_dict(); + if let Some(Typed::Str(auth_payload)) = params_dict.get(&Self::AUTH_PAYLOAD_KEY) { + let mut write_lock = user.write().unwrap(); + if write_lock.update_with_auth(&auth_payload.string) { + let mut resp_params = std::collections::HashMap::new(); + resp_params.insert(Self::AUTH_PAYLOAD_KEY, polariton::operation::Typed::Byte(0)); + return polariton::operation::OperationResponse { + code: 230, + return_code: 0, + message: polariton::operation::Typed::Null, + params: resp_params.into(), + } + } + } + polariton::operation::OperationResponse { + code: 230, + return_code: 120, + message: polariton::operation::Typed::Null, + params: std::collections::HashMap::new().into(), + } + } +} + +impl OperationCode for MoreLobbyAuth { + fn op_code() -> u8 { + 230 + } +} diff --git a/rc_services_room/src/operations/movement_stats.rs b/rc_services_room/src/operations/movement_stats.rs new file mode 100644 index 0000000..8e2fdd3 --- /dev/null +++ b/rc_services_room/src/operations/movement_stats.rs @@ -0,0 +1,142 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +use crate::data::movement_list::*; +use crate::data::cube_list::ItemTier; +use crate::data::weapon_list::ItemCategory; + +const PARAM_KEY: u8 = 1; + +pub(super) fn movement_config_provider() -> SimpleFunc<62, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 104, // hashtable + items: vec![ + (Typed::Str("Global".into()), Typed::HashMap(vec![ + (Typed::Str("lerpValue".into()), Typed::Float(10.0)), + ].into())), + (Typed::Str("Movements".into()), Typed::HashMap(vec![ + (Typed::Str(ItemCategory::Wheel.as_str().into()), MovementCategoryData { + horizontal_top_speed: Some(1.0), + vertical_top_speed: Some(1.0), + specifics: MovementCategorySpecificData::Wheel, + stats: vec![ + (ItemTier::T0, MovementData { + speed_boost: Some(1.0), + max_carry_mass: Some(1.0), + horizontal_top_speed: Some(1.0), + vertical_top_speed: Some(1.0), + specifics: MovementSpecificData::Wheel(WheelData { + steering_speed_light: 1.0, + steering_speed_heavy: 1.0, + steering_force_multiplier_light: 1.0, + steering_force_multiplier_heavy: 1.0, + lateral_acceleration_light: 1.0, + lateral_acceleration_heavy: 1.0, + time_to_max_acceleration_light: 1.0, + time_to_max_acceleration_heavy: 1.0, + brake_force_light: 1.0, + brake_force_heavy: 1.0, + }), + }), + (ItemTier::T1, MovementData { + speed_boost: Some(1.0), + max_carry_mass: Some(1.0), + horizontal_top_speed: Some(1.0), + vertical_top_speed: Some(1.0), + specifics: MovementSpecificData::Wheel(WheelData { + steering_speed_light: 1.0, + steering_speed_heavy: 1.0, + steering_force_multiplier_light: 1.0, + steering_force_multiplier_heavy: 1.0, + lateral_acceleration_light: 1.0, + lateral_acceleration_heavy: 1.0, + time_to_max_acceleration_light: 1.0, + time_to_max_acceleration_heavy: 1.0, + brake_force_light: 1.0, + brake_force_heavy: 1.0, + }), + }), + (ItemTier::T2, MovementData { + speed_boost: Some(1.0), + max_carry_mass: Some(1.0), + horizontal_top_speed: Some(1.0), + vertical_top_speed: Some(1.0), + specifics: MovementSpecificData::Wheel(WheelData { + steering_speed_light: 1.0, + steering_speed_heavy: 1.0, + steering_force_multiplier_light: 1.0, + steering_force_multiplier_heavy: 1.0, + lateral_acceleration_light: 1.0, + lateral_acceleration_heavy: 1.0, + time_to_max_acceleration_light: 1.0, + time_to_max_acceleration_heavy: 1.0, + brake_force_light: 1.0, + brake_force_heavy: 1.0, + }), + }), + (ItemTier::T3, MovementData { + speed_boost: Some(1.0), + max_carry_mass: Some(1.0), + horizontal_top_speed: Some(1.0), + vertical_top_speed: Some(1.0), + specifics: MovementSpecificData::Wheel(WheelData { + steering_speed_light: 1.0, + steering_speed_heavy: 1.0, + steering_force_multiplier_light: 1.0, + steering_force_multiplier_heavy: 1.0, + lateral_acceleration_light: 1.0, + lateral_acceleration_heavy: 1.0, + time_to_max_acceleration_light: 1.0, + time_to_max_acceleration_heavy: 1.0, + brake_force_light: 1.0, + brake_force_heavy: 1.0, + }), + }), + (ItemTier::T4, MovementData { + speed_boost: Some(1.0), + max_carry_mass: Some(1.0), + horizontal_top_speed: Some(1.0), + vertical_top_speed: Some(1.0), + specifics: MovementSpecificData::Wheel(WheelData { + steering_speed_light: 1.0, + steering_speed_heavy: 1.0, + steering_force_multiplier_light: 1.0, + steering_force_multiplier_heavy: 1.0, + lateral_acceleration_light: 1.0, + lateral_acceleration_heavy: 1.0, + time_to_max_acceleration_light: 1.0, + time_to_max_acceleration_heavy: 1.0, + brake_force_light: 1.0, + brake_force_heavy: 1.0, + }), + }), + (ItemTier::T5, MovementData { + speed_boost: Some(1.0), + max_carry_mass: Some(1.0), + horizontal_top_speed: Some(1.0), + vertical_top_speed: Some(1.0), + specifics: MovementSpecificData::Wheel(WheelData { + steering_speed_light: 1.0, + steering_speed_heavy: 1.0, + steering_force_multiplier_light: 1.0, + steering_force_multiplier_heavy: 1.0, + lateral_acceleration_light: 1.0, + lateral_acceleration_heavy: 1.0, + time_to_max_acceleration_light: 1.0, + time_to_max_acceleration_heavy: 1.0, + brake_force_light: 1.0, + brake_force_heavy: 1.0, + }), + }), + ], + ..Default::default() + }.as_transmissible()), + ].into())), + ].into(), + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/palette_town.rs b/rc_services_room/src/operations/palette_town.rs new file mode 100644 index 0000000..776fb92 --- /dev/null +++ b/rc_services_room/src/operations/palette_town.rs @@ -0,0 +1,61 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed}; + +use crate::data::palette::*; + +const PALETTE_KEY: u8 = 34; +const ORDER_KEY: u8 = 149; + +pub(super) fn kanto() -> SimpleFunc<31, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PALETTE_KEY, Typed::Bytes({ + let mut buf = Vec::new(); + Colour::write_many(vec![ + // Red + Colour { + index: 0, + diffuse: ColourValue { r: 255, g: 0, b: 0, a: u8::MAX }, + specular: ColourValue { r: 255, g: 0, b: 0, a: u8::MAX }, + overlay: ColourValue { r: 255, g: 0, b: 0, a: u8::MAX }, + premium: false, + }, + // Blue + Colour { + index: 1, + diffuse: ColourValue { r: 0, g: 0, b: 255, a: u8::MAX }, + specular: ColourValue { r: 0, g: 0, b: 255, a: u8::MAX }, + overlay: ColourValue { r: 0, g: 0, b: 255, a: u8::MAX }, + premium: false, + }, + // Green + Colour { + index: 2, + diffuse: ColourValue { r: 0, g: 255, b: 0, a: u8::MAX }, + specular: ColourValue { r: 0, g: 255, b: 0, a: u8::MAX }, + overlay: ColourValue { r: 0, g: 255, b: 0, a: u8::MAX }, + premium: false, + }, + // Black + Colour { + index: 3, + diffuse: ColourValue { r: 0, g: 0, b: 0, a: u8::MAX }, + specular: ColourValue { r: 0, g: 0, b: 0, a: u8::MAX }, + overlay: ColourValue { r: 0, g: 0, b: 0, a: u8::MAX }, + premium: false, + }, + // White + Colour { + index: 4, + diffuse: ColourValue { r: 255, g: 255, b: 255, a: u8::MAX }, + specular: ColourValue { r: 255, g: 255, b: 255, a: u8::MAX }, + overlay: ColourValue { r: 255, g: 255, b: 255, a: u8::MAX }, + premium: false, + }, + ].as_slice(), &mut buf).unwrap_or_default(); + buf.into() + })); + params.insert(ORDER_KEY, Typed::Bytes(vec![0u8, 1, 2, 3, 4].into())); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/platform_config.rs b/rc_services_room/src/operations/platform_config.rs new file mode 100644 index 0000000..94b1314 --- /dev/null +++ b/rc_services_room/src/operations/platform_config.rs @@ -0,0 +1,29 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +const PLATFORM_CONFIG_KEY: u8 = 197; + +pub(super) fn platform_config_provider() -> SimpleFunc<165, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PLATFORM_CONFIG_KEY, Typed::Dict(Dict { + key_ty: 42, // obj + val_ty: 42, // obj + items: vec![ + (Typed::Str("BuyPremiumAvailable".into()), Typed::Bool(false.into())), + (Typed::Str("MainShopButtonAvailable".into()), Typed::Bool(false.into())), + (Typed::Str("RoboPassButtonAvailable".into()), Typed::Bool(false.into())), + (Typed::Str("LanguageSelectionAvailable".into()), Typed::Bool(false.into())), + (Typed::Str("AutoJoinPublicChatRoom".into()), Typed::Bool(false.into())), // TODO maybe? + (Typed::Str("CanCreateChatRooms".into()), Typed::Bool(false.into())), // TODO + (Typed::Str("CurseVoiceEnabled".into()), Typed::Bool(false.into())), + (Typed::Str("DeltaDNAEnabled".into()), Typed::Bool(false.into())), + (Typed::Str("UseDecimalSystem".into()), Typed::Bool(false.into())), + (Typed::Str("FeedbackURL".into()), Typed::Str("https://mstdn.ca/@ngram".into())), + (Typed::Str("SupportURL".into()), Typed::Str("https://git.ngni.us/OpenJam/servers".into())), + (Typed::Str("WikiURL".into()), Typed::Str("https://docs.rs/libfj/latest/libfj/".into())), + ].into(), + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/power_bar_stats.rs b/rc_services_room/src/operations/power_bar_stats.rs new file mode 100644 index 0000000..e65306c --- /dev/null +++ b/rc_services_room/src/operations/power_bar_stats.rs @@ -0,0 +1,16 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed}; + +const PARAM_KEY: u8 = 61; + +pub(super) fn power_bar_provider() -> SimpleFunc<51, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::HashMap(vec![ + (Typed::Str("refillRatePerSecond".into()), Typed::Float(1.0)), + (Typed::Str("powerForAllRobots".into()), Typed::Int(1_000 /* converted to u32 */)), + ].into() + )); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/premium_config.rs b/rc_services_room/src/operations/premium_config.rs new file mode 100644 index 0000000..cdf1d32 --- /dev/null +++ b/rc_services_room/src/operations/premium_config.rs @@ -0,0 +1,22 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::ParameterTable; + +use crate::data::premium_config::*; + +const PARAM_KEY: u8 = 1; + +pub(super) fn premium_config_provider() -> SimpleFunc<5, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, PremiumEffects { + factor: PremiumFactor { + factor: 100, + party_bonus: 100, + }, + multiplayer: PremiumMultiplayer { + tier_multiplier: 2.0, + } + }.as_transmissible()); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/special_items.rs b/rc_services_room/src/operations/special_items.rs new file mode 100644 index 0000000..5bd0114 --- /dev/null +++ b/rc_services_room/src/operations/special_items.rs @@ -0,0 +1,25 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +use crate::data::special_item::*; + +const PARAM_KEY: u8 = 1; + +pub(super) fn special_item_list_provider() -> SimpleFunc<6, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 104, // hashtable + items: vec![ + //(u32 in base16 aka hex, hashtable) + (Typed::Str("DEADBEEF".into()), SpecialItem { + name: "cool".to_string(), + sprite: "chair".to_string(), + size: 1, + }.as_transmissible()) + ].into(), + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/taunts_config.rs b/rc_services_room/src/operations/taunts_config.rs new file mode 100644 index 0000000..a9b55ed --- /dev/null +++ b/rc_services_room/src/operations/taunts_config.rs @@ -0,0 +1,43 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed}; + +use crate::data::taunts_config::*; + +const PARAM_KEY: u8 = 195; + +pub(super) fn taunts_config_provider() -> SimpleFunc<164, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(polariton::operation::Dict { + key_ty: 115, + val_ty: 42, + items: vec![ + (Typed::Str("taunts".into()), TauntsData { + taunts: vec![ + TauntData { + group_name: "totally_real_group_name".to_string(), + assets: AssetData { + idle_effect: "tbd".to_string(), + active_effect: "something".to_string(), + sound_effect: "3rd thing here".to_string(), + }, + animation_offset_x: 0.0, + animation_offset_y: 0.0, + animation_offset_z: 0.0, + cubes: vec![ + CubeData { + cube_id: 1, + position_x: 0, + position_y: 0, + position_z: 0, + rotation: 0, + } + ] + } + ] + }.as_transmissible()) + ] + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/tier_banding.rs b/rc_services_room/src/operations/tier_banding.rs new file mode 100644 index 0000000..35d4cd5 --- /dev/null +++ b/rc_services_room/src/operations/tier_banding.rs @@ -0,0 +1,21 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +const PARAM_KEY: u8 = 1; + +pub(super) fn tiers_banding_provider() -> SimpleFunc<7, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 42, // obj + items: vec![ + (Typed::Str("tiersbands".into()), Typed::IntArr(vec![ + 1 + ].into())), + (Typed::Str("maximumRobotRankingARobotCanObtain".into()), Typed::Int(1)), + ].into(), + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/operations/versioner.rs b/rc_services_room/src/operations/versioner.rs new file mode 100644 index 0000000..a7b2730 --- /dev/null +++ b/rc_services_room/src/operations/versioner.rs @@ -0,0 +1,30 @@ +use polariton_server::operations::{Operation, OperationCode}; + +pub struct VersionTeller; + +impl VersionTeller { + const VERSION_NUMBER_KEY: u8 = 112; + const LATEST_VERSION: i32 = 2855; +} + +impl Operation for VersionTeller { + type State = (); + type User = crate::UserTy; + + fn handle(&self, _: polariton::operation::ParameterTable, _: &mut Self::State, _: &Self::User) -> polariton::operation::OperationResponse { + let mut resp_params = std::collections::HashMap::new(); + resp_params.insert(Self::VERSION_NUMBER_KEY, polariton::operation::Typed::Int(Self::LATEST_VERSION)); + polariton::operation::OperationResponse { + code: 103, + return_code: 0, + message: polariton::operation::Typed::Null, + params: resp_params.into(), + } + } +} + +impl OperationCode for VersionTeller { + fn op_code() -> u8 { + 103 + } +} diff --git a/rc_services_room/src/operations/weapon_stats.rs b/rc_services_room/src/operations/weapon_stats.rs new file mode 100644 index 0000000..d0b24d7 --- /dev/null +++ b/rc_services_room/src/operations/weapon_stats.rs @@ -0,0 +1,47 @@ +use polariton_server::operations::SimpleFunc; +use polariton::operation::{ParameterTable, Typed, Dict}; + +use crate::data::weapon_list::*; +use crate::data::cube_list::ItemTier; + +const PARAM_KEY: u8 = 57; + +pub(super) fn weapon_config_provider() -> SimpleFunc<47, crate::UserTy, impl (Fn(ParameterTable, &crate::UserTy) -> Result) + Sync + Sync> { + SimpleFunc::new(|params, _| { + let mut params = params.to_dict(); + params.insert(PARAM_KEY, Typed::Dict(Dict { + key_ty: 115, // str + val_ty: 104, // hashtable + items: vec![ + // (Item category, map) + (Typed::Str(ItemCategory::Laser.as_str().into()), Typed::HashMap(vec![ + (Typed::Str(ItemTier::T0.as_str().into()), WeaponData { + damage_inflicted: Some(42), + ..Default::default() + }.as_transmissible()), + (Typed::Str(ItemTier::T1.as_str().into()), WeaponData { + damage_inflicted: Some(420), + ..Default::default() + }.as_transmissible()), + (Typed::Str(ItemTier::T2.as_str().into()), WeaponData { + damage_inflicted: Some(4200), + ..Default::default() + }.as_transmissible()), + (Typed::Str(ItemTier::T3.as_str().into()), WeaponData { + damage_inflicted: Some(42000), + ..Default::default() + }.as_transmissible()), + (Typed::Str(ItemTier::T4.as_str().into()), WeaponData { + damage_inflicted: Some(420000), + ..Default::default() + }.as_transmissible()), + (Typed::Str(ItemTier::T5.as_str().into()), WeaponData { + damage_inflicted: Some(4200000), + ..Default::default() + }.as_transmissible()), + ].into())) + ].into(), + })); + Ok(params.into()) + }) +} diff --git a/rc_services_room/src/state.rs b/rc_services_room/src/state.rs new file mode 100644 index 0000000..7adeeb0 --- /dev/null +++ b/rc_services_room/src/state.rs @@ -0,0 +1,43 @@ +use std::sync::{Arc, RwLock}; + +pub struct State { + pub crypto: Box>, +} + +impl State { + pub fn new(c: Box>) -> Self { + Self { + crypto: c, + } + } + + pub fn binrw_args(&self) -> polariton::packet::WriteArgs { + Some(self.crypto.clone()) + } + + pub fn user(&self) -> crate::UserTy { + RwLock::new(UserState::default()) + } +} + +#[derive(Default, Debug)] +pub struct UserState { + pub uuid: String, + pub token: String, + pub refresh_token: String, +} + +impl UserState { + pub fn update_with_auth(&mut self, auth_str: &str) -> bool { + let splits: Vec<&str> = auth_str.split(';').collect(); + if splits.len() != 3 { + log::warn!("Invalid auth payload: {}", auth_str); + false + } else { + self.uuid = splits[0].to_owned(); + self.token = splits[1].to_owned(); + self.refresh_token = splits[2].to_owned(); + true + } + } +}