From 22a87d6a383f83f998b1b43e4397f62ded0bac57 Mon Sep 17 00:00:00 2001 From: reidlab Date: Mon, 4 Sep 2023 22:23:47 -0700 Subject: [PATCH 1/4] update readme, drop unused import --- readme.md | 9 ++++----- src/endpoints/levels/upload_level.rs | 1 - 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index 6f91c6f..8acd30c 100644 --- a/readme.md +++ b/readme.md @@ -34,8 +34,7 @@ _these features are implemented_ ## todo -- green name users... -- patch uploadlevel to use 2.2 unlisted(honsetly idk what this is) -- clean up uploadlevel -- add level parsing -- maybe swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!) \ No newline at end of file +- swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!) +- patch `upload_level` to use 2.2 unlisted(honsetly idk what this is but it exists?) +- add level parsing (`upload_level` can use the correct `length`, `objects`, `coins`, and `two_player`) +- green name users... \ No newline at end of file diff --git a/src/endpoints/levels/upload_level.rs b/src/endpoints/levels/upload_level.rs index bae6579..6a1e2d0 100644 --- a/src/endpoints/levels/upload_level.rs +++ b/src/endpoints/levels/upload_level.rs @@ -1,4 +1,3 @@ -use password_auth::verify_password; use rocket::form::Form; use rocket::http::Status; use rocket::response::status; From 1e913b9ec86a85ce7fd360565aeaa462d4e98b69 Mon Sep 17 00:00:00 2001 From: reidlab Date: Tue, 5 Sep 2023 21:43:36 -0700 Subject: [PATCH 2/4] level parsing i think --- Cargo.lock | 35 ++++ Cargo.toml | 3 +- migrations/2023-09-03-032651_levels/up.sql | 1 + readme.md | 7 +- src/db/models.rs | 2 + src/db/schema.rs | 1 + src/endpoints/levels/upload_level.rs | 42 +++-- src/helpers/levels.rs | 177 ++++++++++++++++++++- 8 files changed, 253 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0a93b9a..b10230d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aho-corasick" version = "1.0.4" @@ -175,6 +181,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -337,6 +352,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -415,6 +440,7 @@ dependencies = [ "base64", "diesel", "dotenvy", + "flate2", "maplit", "password-auth", "regex", @@ -690,6 +716,15 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.6" diff --git a/Cargo.toml b/Cargo.toml index acf5aa9..03b9da2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,9 @@ edition = "2021" [dependencies] base64 = "0.21.3" -diesel = { version = "=2.1.0", features = ["postgres"] } +diesel = { version = "=2.1.0", features = ["postgres", "64-column-tables"] } dotenvy = "0.15.7" +flate2 = "1.0.27" maplit = "1.0.2" password-auth = "0.3.0" regex = "1.9.4" diff --git a/migrations/2023-09-03-032651_levels/up.sql b/migrations/2023-09-03-032651_levels/up.sql index 531b2b3..7f2cc72 100644 --- a/migrations/2023-09-03-032651_levels/up.sql +++ b/migrations/2023-09-03-032651_levels/up.sql @@ -25,6 +25,7 @@ CREATE TABLE levels ( song_id INTEGER NOT NULL, length INTEGER NOT NULL, + length_real INTEGER NOT NULL, objects INTEGER NOT NULL, coins INTEGER NOT NULL DEFAULT 0, has_ldm INTEGER NOT NULL DEFAULT 0, diff --git a/readme.md b/readme.md index 8acd30c..10a587e 100644 --- a/readme.md +++ b/readme.md @@ -35,6 +35,7 @@ _these features are implemented_ ## todo - swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!) -- patch `upload_level` to use 2.2 unlisted(honsetly idk what this is but it exists?) -- add level parsing (`upload_level` can use the correct `length`, `objects`, `coins`, and `two_player`) -- green name users... \ No newline at end of file +- patch `upload_level` to use 2.2 unlisted (friends only stuff) +- in our level parsing, check for dual portals rather than just starting dual +- green name users... +- maybe tone down the clone usage (this is for sure causing memory problems 😉) \ No newline at end of file diff --git a/src/db/models.rs b/src/db/models.rs index b78af61..464f162 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -108,6 +108,7 @@ pub struct Level { pub editor_time_copies: i32, pub song_id: i32, pub length: i32, + pub length_real: f64, pub objects: i32, pub coins: i32, pub has_ldm: i32, @@ -142,6 +143,7 @@ pub struct NewLevel { pub editor_time_copies: i32, pub song_id: i32, pub length: i32, + pub length_real: f64, pub objects: i32, pub coins: i32, pub has_ldm: i32, diff --git a/src/db/schema.rs b/src/db/schema.rs index 1a25502..04d3cac 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -46,6 +46,7 @@ diesel::table! { editor_time_copies -> Int4, song_id -> Int4, length -> Int4, + length_real -> Int4, objects -> Int4, coins -> Int4, has_ldm -> Int4, diff --git a/src/endpoints/levels/upload_level.rs b/src/endpoints/levels/upload_level.rs index 6a1e2d0..2b03004 100644 --- a/src/endpoints/levels/upload_level.rs +++ b/src/endpoints/levels/upload_level.rs @@ -73,7 +73,30 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str None => { extra_string = helpers::levels::DEFAULT_EXTRA_STRING.to_owned() } } - // db shit + // level parsing + let level_raw_objects = helpers::levels::decode(input.levelString.clone()); + let level_objects = helpers::levels::to_objectdata(level_raw_objects.clone()); + let inner_level_string = level_raw_objects + .iter() + .find(|obj| !obj.contains_key("1") && obj.get("kA9") == Some(&"0".to_string())) + .expect("couldnt decode inner level string"); + + let level_length_secs = helpers::levels::measure_length( + level_objects.clone(), + inner_level_string.get("kA4").unwrap_or(&String::from("0")).parse::().expect("kA4 not int") + ); + + let coins_val = level_objects.iter().filter(|obj| obj.id() == 1329).count(); // 1329 is coin id + let objects_val = level_objects.len(); + let two_player_val = if inner_level_string.get("kA10").unwrap_or(&String::from("0")).parse::().expect("kA10 not int") == 1 { 1 } else { 0 }; + let level_length_val = helpers::levels::secs_to_time(level_length_secs); + + // blocking + if coins_val > 3 { + return status::Custom(Status::Ok, "1") + } + + // data base 🤣😁 use crate::models::{Level, NewLevel}; { @@ -110,11 +133,11 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str editor_time.eq(input.wt.unwrap_or(0)), editor_time_copies.eq(input.wt2.unwrap_or(0)), song_id.eq(song_id_val), - length.eq(0), // unimplemeneted - objects.eq(0), // unimplemented - coins.eq(0), // unimplemented + length.eq(level_length_val), + objects.eq(objects_val as i32), + coins.eq(coins_val as i32), has_ldm.eq(input.ldm.unwrap_or(0)), - two_player.eq(0) // unimplemented + two_player.eq(two_player_val) )) .get_result::(connection) .expect("failed to update level"); @@ -143,11 +166,12 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str editor_time: input.wt.unwrap_or(0), editor_time_copies: input.wt2.unwrap_or(0), song_id: song_id_val, - length: 0, // not implemeneted - objects: 0, // not implemeneted - coins: 0, // not implemeneted + length: level_length_val, + length_real: level_length_secs, + objects: objects_val as i32, + coins: coins_val as i32, has_ldm: input.ldm.unwrap_or(0), - two_player: 0 // not implemented + two_player: two_player_val }; let inserted_level = diesel::insert_into(levels) diff --git a/src/helpers/levels.rs b/src/helpers/levels.rs index d85a395..c5d1e50 100644 --- a/src/helpers/levels.rs +++ b/src/helpers/levels.rs @@ -1,7 +1,180 @@ use std::sync::LazyLock; +use std::io::prelude::*; + +use base64::{Engine as _, engine::general_purpose}; + +use flate2::read::GzDecoder; pub static DEFAULT_EXTRA_STRING: LazyLock = LazyLock::new(|| { let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29"); - + return string; -}); \ No newline at end of file +}); + +macro_rules! object_prop_bool { + ($key:expr, $name:ident) => { + pub fn $name(&self) -> bool { + self.raw.get($key).map_or(false, |value| value == "1") + } + }; +} + +use std::collections::HashMap; + +#[derive(Clone)] +pub struct ObjectData { + raw: HashMap +} + +impl ObjectData { + pub fn new(raw: HashMap) -> Self { + ObjectData { raw } + } + + pub fn id(&self) -> i32 { + self.raw.get("1").unwrap_or(&String::new()).parse().unwrap_or(0) + } + + pub fn x(&self) -> f64 { + self.raw.get("2").unwrap_or(&String::new()).parse().unwrap_or(0.0) + } + + pub fn y(&self) -> f64 { + self.raw.get("3").unwrap_or(&String::new()).parse().unwrap_or(0.0) + } + + object_prop_bool!("13", checked); +} + +pub mod portal_speed { + pub enum PortalSpeed { + Slow, + Normal, + Medium, + Fast, + VeryFast + } + + impl PortalSpeed { + pub fn portal_speed(&self) -> f64 { + match self { + PortalSpeed::Slow => 251.16, + PortalSpeed::Normal => 311.58, + PortalSpeed::Medium => 387.42, + PortalSpeed::Fast => 478.0, + PortalSpeed::VeryFast => 576.0 + } + } + } +} + +pub fn id_to_portal_speed(id: i32) -> Option { + match id { + 200 => Some(portal_speed::PortalSpeed::Slow), + 201 => Some(portal_speed::PortalSpeed::Normal), + 202 => Some(portal_speed::PortalSpeed::Medium), + 203 => Some(portal_speed::PortalSpeed::Fast), + 1334 => Some(portal_speed::PortalSpeed::VeryFast), + _ => None, + } +} + +pub fn get_seconds_from_xpos(pos: f64, start_speed: portal_speed::PortalSpeed, portals: Vec) -> f64 { + let mut speed; + let mut last_obj_pos = 0.0; + let mut last_segment = 0.0; + let mut segments = 0.0; + + speed = start_speed.portal_speed(); + + if portals.is_empty() { + return pos / speed + } + + for portal in portals { + let mut s = portal.x() - last_obj_pos; + + if pos < s { + s = s / speed; + last_segment = s; + segments += s; + + speed = id_to_portal_speed(portal.id()).expect("not a portal").portal_speed(); + + last_obj_pos = portal.x() + } + } + + return ((pos - last_segment) / speed) + segments; +} + +pub fn measure_length(objects: Vec, ka4: i32) -> f64 { + let start_speed = match ka4 { + 0 => portal_speed::PortalSpeed::Normal, + 1 => portal_speed::PortalSpeed::Slow, + 2 => portal_speed::PortalSpeed::Medium, + 3 => portal_speed::PortalSpeed::Fast, + 4 => portal_speed::PortalSpeed::VeryFast, + _ => portal_speed::PortalSpeed::Normal + }; + + let max_x_pos = objects + .iter() + .fold(0.0, |max_x, obj| f64::max(max_x, obj.x())); + + let mut portals: Vec = objects + .into_iter() + .filter(|obj| id_to_portal_speed(obj.id()).is_some() && obj.checked()) + .collect(); + + portals.sort_by(|a, b| a.x().partial_cmp(&b.x()).unwrap()); + + return get_seconds_from_xpos(max_x_pos, start_speed, portals) +} + +pub fn secs_to_time(time: f64) -> i32 { + match time { + time if time < 10.0 => return 0, + time if time < 30.0 => return 1, + time if time < 60.0 => return 2, + time if time < 120.0 => return 3, + time if time >= 120.0 => return 4, + _ => 0 + } +} + +pub fn array_to_hash(arr: Vec) -> HashMap { + return arr.chunks(2) + .map(|chunk| (chunk[0].clone(), chunk[1].clone())) + .collect() +} + +pub fn parse(raw_level_data: &str) -> Vec> { + raw_level_data + .trim_end_matches(';') + .split(';') + .map(|v| { + let values: Vec = v.split(',').map(|s| s.to_string()).collect(); + array_to_hash(values) + }) + .collect() +} + +pub fn decode(level_data: String) -> Vec> { + let decoded_bytes = general_purpose::URL_SAFE.decode(level_data).expect("couldnt decode b64"); + + let mut decoder = GzDecoder::new(&decoded_bytes[..]); + + let mut uncompressed_data = String::new(); + decoder.read_to_string(&mut uncompressed_data).expect("err unzipping level"); + + return parse(uncompressed_data.as_str()) +} + +pub fn to_objectdata(objects: Vec>) -> Vec { + return objects + .into_iter() + .filter(|v| v.contains_key("1")) + .map(|v| ObjectData::new(v)) + .collect() +} \ No newline at end of file From 86adf64575196fded71ec425c7da1f8ec67d9be9 Mon Sep 17 00:00:00 2001 From: reidlab Date: Tue, 5 Sep 2023 21:48:30 -0700 Subject: [PATCH 3/4] oops forgot to do this --- migrations/2023-09-03-032651_levels/up.sql | 2 +- src/db/schema.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/2023-09-03-032651_levels/up.sql b/migrations/2023-09-03-032651_levels/up.sql index 7f2cc72..601ac01 100644 --- a/migrations/2023-09-03-032651_levels/up.sql +++ b/migrations/2023-09-03-032651_levels/up.sql @@ -25,7 +25,7 @@ CREATE TABLE levels ( song_id INTEGER NOT NULL, length INTEGER NOT NULL, - length_real INTEGER NOT NULL, + length_real DOUBLE PRECISION NOT NULL, objects INTEGER NOT NULL, coins INTEGER NOT NULL DEFAULT 0, has_ldm INTEGER NOT NULL DEFAULT 0, diff --git a/src/db/schema.rs b/src/db/schema.rs index 04d3cac..f116654 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -46,7 +46,7 @@ diesel::table! { editor_time_copies -> Int4, song_id -> Int4, length -> Int4, - length_real -> Int4, + length_real -> Float8, objects -> Int4, coins -> Int4, has_ldm -> Int4, From be729aa533bb0e702e3cc5eab7e9287307de927b Mon Sep 17 00:00:00 2001 From: reidlab Date: Tue, 5 Sep 2023 22:34:00 -0700 Subject: [PATCH 4/4] object blacklist, storing second length --- config.example.toml | 6 +++++- migrations/2023-09-03-032651_levels/up.sql | 12 ++++++------ src/config.rs | 8 +++++++- src/endpoints/levels/upload_level.rs | 9 +++++++-- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/config.example.toml b/config.example.toml index 76fb6e2..7e20b53 100644 --- a/config.example.toml +++ b/config.example.toml @@ -20,4 +20,8 @@ allow_registration = true [db] # path for your data to be stored -data_folder = "data" \ No newline at end of file +data_folder = "data" + +[levels] +# object ids to block +blocklist = [ 31 ] # start position \ No newline at end of file diff --git a/migrations/2023-09-03-032651_levels/up.sql b/migrations/2023-09-03-032651_levels/up.sql index 601ac01..3b53884 100644 --- a/migrations/2023-09-03-032651_levels/up.sql +++ b/migrations/2023-09-03-032651_levels/up.sql @@ -24,12 +24,12 @@ CREATE TABLE levels ( song_id INTEGER NOT NULL, - length INTEGER NOT NULL, - length_real DOUBLE PRECISION NOT NULL, - objects INTEGER NOT NULL, - coins INTEGER NOT NULL DEFAULT 0, - has_ldm INTEGER NOT NULL DEFAULT 0, - two_player INTEGER NOT NULL DEFAULT 0, + length INTEGER NOT NULL, + length_real DOUBLE PRECISION NOT NULL, + objects INTEGER NOT NULL, + coins INTEGER NOT NULL DEFAULT 0, + has_ldm INTEGER NOT NULL DEFAULT 0, + two_player INTEGER NOT NULL DEFAULT 0, downloads INTEGER NOT NULL DEFAULT 0, likes INTEGER NOT NULL DEFAULT 0, diff --git a/src/config.rs b/src/config.rs index c6b37ae..6333b20 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,7 +6,8 @@ use std::sync::LazyLock; pub struct Config { pub general: ConfigGeneral, pub accounts: ConfigAccounts, - pub db: ConfigDB + pub db: ConfigDB, + pub levels: ConfigLevels } #[derive(Deserialize)] @@ -25,6 +26,11 @@ pub struct ConfigDB { pub data_folder: String } +#[derive(Deserialize)] +pub struct ConfigLevels { + pub blocklist: Vec +} + impl Config { pub fn load_from_file(file_path: &str) -> Self { let toml_str = fs::read_to_string(file_path).expect("Error finding toml config:"); diff --git a/src/endpoints/levels/upload_level.rs b/src/endpoints/levels/upload_level.rs index 2b03004..347d6c7 100644 --- a/src/endpoints/levels/upload_level.rs +++ b/src/endpoints/levels/upload_level.rs @@ -91,9 +91,14 @@ pub fn upload_level(input: Form) -> status::Custom<&'static str let two_player_val = if inner_level_string.get("kA10").unwrap_or(&String::from("0")).parse::().expect("kA10 not int") == 1 { 1 } else { 0 }; let level_length_val = helpers::levels::secs_to_time(level_length_secs); - // blocking + // blocking coins if coins_val > 3 { - return status::Custom(Status::Ok, "1") + return status::Custom(Status::Ok, "-1") + } + + // forbidden object checking + if let Some(_forbidden_object) = level_objects.iter().find(|obj| crate::CONFIG.levels.blocklist.contains(&obj.id())) { + return status::Custom(Status::Ok, "-1") } // data base 🤣😁