Compare commits

..

4 commits

Author SHA1 Message Date
be729aa533
object blacklist, storing second length 2023-09-05 22:34:00 -07:00
86adf64575
oops forgot to do this 2023-09-05 21:48:30 -07:00
1e913b9ec8
level parsing i think 2023-09-05 21:43:36 -07:00
22a87d6a38
update readme, drop unused import 2023-09-04 22:23:47 -07:00
10 changed files with 275 additions and 24 deletions

35
Cargo.lock generated
View file

@ -2,6 +2,12 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.0.4" version = "1.0.4"
@ -175,6 +181,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -337,6 +352,16 @@ dependencies = [
"version_check", "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]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -415,6 +440,7 @@ dependencies = [
"base64", "base64",
"diesel", "diesel",
"dotenvy", "dotenvy",
"flate2",
"maplit", "maplit",
"password-auth", "password-auth",
"regex", "regex",
@ -690,6 +716,15 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 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]] [[package]]
name = "mio" name = "mio"
version = "0.8.6" version = "0.8.6"

View file

@ -5,8 +5,9 @@ edition = "2021"
[dependencies] [dependencies]
base64 = "0.21.3" 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" dotenvy = "0.15.7"
flate2 = "1.0.27"
maplit = "1.0.2" maplit = "1.0.2"
password-auth = "0.3.0" password-auth = "0.3.0"
regex = "1.9.4" regex = "1.9.4"

View file

@ -20,4 +20,8 @@ allow_registration = true
[db] [db]
# path for your data to be stored # path for your data to be stored
data_folder = "data" data_folder = "data"
[levels]
# object ids to block
blocklist = [ 31 ] # start position

View file

@ -24,11 +24,12 @@ CREATE TABLE levels (
song_id INTEGER NOT NULL, song_id INTEGER NOT NULL,
length INTEGER NOT NULL, length INTEGER NOT NULL,
objects INTEGER NOT NULL, length_real DOUBLE PRECISION NOT NULL,
coins INTEGER NOT NULL DEFAULT 0, objects INTEGER NOT NULL,
has_ldm INTEGER NOT NULL DEFAULT 0, coins INTEGER NOT NULL DEFAULT 0,
two_player 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, downloads INTEGER NOT NULL DEFAULT 0,
likes INTEGER NOT NULL DEFAULT 0, likes INTEGER NOT NULL DEFAULT 0,

View file

@ -34,8 +34,8 @@ _these features are implemented_
## todo ## 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 (friends only stuff)
- in our level parsing, check for dual portals rather than just starting dual
- <small>green name users...</small> - <small>green name users...</small>
- patch uploadlevel to use 2.2 unlisted(honsetly idk what this is) - maybe tone down the clone usage (this is for sure causing memory problems 😉)
- 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!!)

View file

@ -6,7 +6,8 @@ use std::sync::LazyLock;
pub struct Config { pub struct Config {
pub general: ConfigGeneral, pub general: ConfigGeneral,
pub accounts: ConfigAccounts, pub accounts: ConfigAccounts,
pub db: ConfigDB pub db: ConfigDB,
pub levels: ConfigLevels
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -25,6 +26,11 @@ pub struct ConfigDB {
pub data_folder: String pub data_folder: String
} }
#[derive(Deserialize)]
pub struct ConfigLevels {
pub blocklist: Vec<i32>
}
impl Config { impl Config {
pub fn load_from_file(file_path: &str) -> Self { pub fn load_from_file(file_path: &str) -> Self {
let toml_str = fs::read_to_string(file_path).expect("Error finding toml config:"); let toml_str = fs::read_to_string(file_path).expect("Error finding toml config:");

View file

@ -108,6 +108,7 @@ pub struct Level {
pub editor_time_copies: i32, pub editor_time_copies: i32,
pub song_id: i32, pub song_id: i32,
pub length: i32, pub length: i32,
pub length_real: f64,
pub objects: i32, pub objects: i32,
pub coins: i32, pub coins: i32,
pub has_ldm: i32, pub has_ldm: i32,
@ -142,6 +143,7 @@ pub struct NewLevel {
pub editor_time_copies: i32, pub editor_time_copies: i32,
pub song_id: i32, pub song_id: i32,
pub length: i32, pub length: i32,
pub length_real: f64,
pub objects: i32, pub objects: i32,
pub coins: i32, pub coins: i32,
pub has_ldm: i32, pub has_ldm: i32,

View file

@ -46,6 +46,7 @@ diesel::table! {
editor_time_copies -> Int4, editor_time_copies -> Int4,
song_id -> Int4, song_id -> Int4,
length -> Int4, length -> Int4,
length_real -> Float8,
objects -> Int4, objects -> Int4,
coins -> Int4, coins -> Int4,
has_ldm -> Int4, has_ldm -> Int4,

View file

@ -1,4 +1,3 @@
use password_auth::verify_password;
use rocket::form::Form; use rocket::form::Form;
use rocket::http::Status; use rocket::http::Status;
use rocket::response::status; use rocket::response::status;
@ -74,7 +73,35 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
None => { extra_string = helpers::levels::DEFAULT_EXTRA_STRING.to_owned() } 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::<i32>().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::<i32>().expect("kA10 not int") == 1 { 1 } else { 0 };
let level_length_val = helpers::levels::secs_to_time(level_length_secs);
// blocking coins
if coins_val > 3 {
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 🤣😁
use crate::models::{Level, NewLevel}; use crate::models::{Level, NewLevel};
{ {
@ -111,11 +138,11 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
editor_time.eq(input.wt.unwrap_or(0)), editor_time.eq(input.wt.unwrap_or(0)),
editor_time_copies.eq(input.wt2.unwrap_or(0)), editor_time_copies.eq(input.wt2.unwrap_or(0)),
song_id.eq(song_id_val), song_id.eq(song_id_val),
length.eq(0), // unimplemeneted length.eq(level_length_val),
objects.eq(0), // unimplemented objects.eq(objects_val as i32),
coins.eq(0), // unimplemented coins.eq(coins_val as i32),
has_ldm.eq(input.ldm.unwrap_or(0)), has_ldm.eq(input.ldm.unwrap_or(0)),
two_player.eq(0) // unimplemented two_player.eq(two_player_val)
)) ))
.get_result::<Level, >(connection) .get_result::<Level, >(connection)
.expect("failed to update level"); .expect("failed to update level");
@ -144,11 +171,12 @@ pub fn upload_level(input: Form<FormUploadLevel>) -> status::Custom<&'static str
editor_time: input.wt.unwrap_or(0), editor_time: input.wt.unwrap_or(0),
editor_time_copies: input.wt2.unwrap_or(0), editor_time_copies: input.wt2.unwrap_or(0),
song_id: song_id_val, song_id: song_id_val,
length: 0, // not implemeneted length: level_length_val,
objects: 0, // not implemeneted length_real: level_length_secs,
coins: 0, // not implemeneted objects: objects_val as i32,
coins: coins_val as i32,
has_ldm: input.ldm.unwrap_or(0), has_ldm: input.ldm.unwrap_or(0),
two_player: 0 // not implemented two_player: two_player_val
}; };
let inserted_level = diesel::insert_into(levels) let inserted_level = diesel::insert_into(levels)

View file

@ -1,7 +1,180 @@
use std::sync::LazyLock; 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<String> = LazyLock::new(|| { pub static DEFAULT_EXTRA_STRING: LazyLock<String> = LazyLock::new(|| {
let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29"); let string = String::from("29_29_29_40_29_29_29_29_29_29_29_29_29_29_29_29");
return string; return string;
}); });
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<String, String>
}
impl ObjectData {
pub fn new(raw: HashMap<String, String>) -> 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<portal_speed::PortalSpeed> {
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<ObjectData>) -> 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<ObjectData>, 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<ObjectData> = 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<String>) -> HashMap<String, String> {
return arr.chunks(2)
.map(|chunk| (chunk[0].clone(), chunk[1].clone()))
.collect()
}
pub fn parse(raw_level_data: &str) -> Vec<HashMap<String, String>> {
raw_level_data
.trim_end_matches(';')
.split(';')
.map(|v| {
let values: Vec<String> = v.split(',').map(|s| s.to_string()).collect();
array_to_hash(values)
})
.collect()
}
pub fn decode(level_data: String) -> Vec<HashMap<String, String>> {
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<HashMap<String, String>>) -> Vec<ObjectData> {
return objects
.into_iter()
.filter(|v| v.contains_key("1"))
.map(|v| ObjectData::new(v))
.collect()
}