downloading levels... IS REAL!
This commit is contained in:
parent
91fc1433b3
commit
cc1c365e5f
6 changed files with 244 additions and 4 deletions
|
@ -34,7 +34,7 @@ _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!!)
|
- swap to chrono instead of `(TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS'))` (thats REALLY ugly!!) this would also make the `28` and `29` parameters work on downloadlevel
|
||||||
- 2.2 friends only unlisted
|
- 2.2 friends only unlisted
|
||||||
- add more old endpoints + better support for older versions
|
- add more old endpoints + better support for older versions
|
||||||
- add dailies, events, weekly
|
- add dailies, events, weekly
|
||||||
|
@ -43,4 +43,7 @@ _these features are implemented_
|
||||||
- sqlite would make sense for this
|
- sqlite would make sense for this
|
||||||
- unscuff difficulties
|
- unscuff difficulties
|
||||||
- moderation utilities
|
- moderation utilities
|
||||||
- probably make more things bools in the database
|
- probably make more things bools in the database
|
||||||
|
- ip actions
|
||||||
|
- fix downlopading levels
|
||||||
|
- better song support
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub mod download_level;
|
||||||
pub mod get_levels;
|
pub mod get_levels;
|
||||||
pub mod upload_level;
|
pub mod upload_level;
|
206
src/endpoints/levels/download_level.rs
Normal file
206
src/endpoints/levels/download_level.rs
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
use rocket::form::Form;
|
||||||
|
use rocket::http::Status;
|
||||||
|
use rocket::response::status;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
|
||||||
|
use flate2::read::GzDecoder;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
|
use crate::helpers;
|
||||||
|
use crate::db;
|
||||||
|
|
||||||
|
#[derive(FromForm)]
|
||||||
|
pub struct FormDownloadLevel {
|
||||||
|
levelID: i32,
|
||||||
|
gameVersion: Option<i32>,
|
||||||
|
extras: Option<i32>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/downloadGJLevel22.php", data = "<input>")]
|
||||||
|
pub fn download_level(input: Form<FormDownloadLevel>) -> status::Custom<&'static str> {
|
||||||
|
let connection = &mut db::establish_connection_pg();
|
||||||
|
|
||||||
|
use crate::schema::{levels, users};
|
||||||
|
|
||||||
|
use crate::models::{Level, User};
|
||||||
|
|
||||||
|
let mut response: Vec<String> = Vec::new();
|
||||||
|
|
||||||
|
let query = levels::table.into_boxed();
|
||||||
|
|
||||||
|
match input.levelID {
|
||||||
|
-1 => {
|
||||||
|
unimplemented!("no daily support")
|
||||||
|
},
|
||||||
|
-2 => {
|
||||||
|
unimplemented!("no weeky support")
|
||||||
|
},
|
||||||
|
-3 => {
|
||||||
|
unimplemented!("what is an event level.")
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// do nothing special
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// database query
|
||||||
|
{
|
||||||
|
let result = query
|
||||||
|
.filter(levels::id.eq(input.levelID))
|
||||||
|
.get_result::<Level, >(connection)
|
||||||
|
.expect("fatal error loading levels");
|
||||||
|
|
||||||
|
let user: User = users::table.find(result.user_id).get_result::<User, >(connection).expect("couldnt get user from lvl");
|
||||||
|
let level: Level = result;
|
||||||
|
|
||||||
|
let set_difficulty = match level.difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
let community_difficulty = match level.community_difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
Some(helpers::difficulty::LevelDifficulty::new(diff))
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
let difficulty = match set_difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
Some(diff)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
match community_difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
Some(diff)
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let demon_difficulty = match level.demon_difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
Some(helpers::difficulty::DemonDifficulty::new(diff))
|
||||||
|
},
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let xor_pass: String;
|
||||||
|
if input.gameVersion.unwrap_or(19) >= 20 {
|
||||||
|
xor_pass = helpers::encryption::cyclic_xor_string(&level.password.clone().unwrap_or(String::from("0")), "26364")
|
||||||
|
} else {
|
||||||
|
xor_pass = level.password.clone().unwrap_or(String::from("0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let compressed_level_data = fs::read(format!("{}/{}/{}.lvl", crate::CONFIG.db.data_folder, "levels", level.id)).expect("couldnt read level file");
|
||||||
|
|
||||||
|
let mut level_data_decoder = GzDecoder::new(compressed_level_data.as_slice());
|
||||||
|
|
||||||
|
let mut uncompressed_level_data = String::new();
|
||||||
|
level_data_decoder.read_to_string(&mut uncompressed_level_data).expect("err unzipping level");
|
||||||
|
|
||||||
|
let level_data = uncompressed_level_data.as_bytes();
|
||||||
|
|
||||||
|
response.push(helpers::format::format(hashmap! {
|
||||||
|
1 => level.id.to_string(),
|
||||||
|
2 => level.name,
|
||||||
|
3 => if input.gameVersion.unwrap_or(19) >= 20 {
|
||||||
|
general_purpose::URL_SAFE.encode(level.description)
|
||||||
|
} else {
|
||||||
|
level.description
|
||||||
|
},
|
||||||
|
4 => String::from_utf8(level_data.to_vec()).expect("invalid utf-8 sequence"),
|
||||||
|
5 => level.version.to_string(),
|
||||||
|
6 => user.id.to_string(),
|
||||||
|
// this argument is weird. its the "difficulty divisor"
|
||||||
|
// used to be vote count but yeah
|
||||||
|
8 => 10.to_string(),
|
||||||
|
9 => (match difficulty {
|
||||||
|
Some(diff) => diff.to_star_difficulty(),
|
||||||
|
None => 0
|
||||||
|
} * 10).to_string(),
|
||||||
|
10 => level.downloads.to_string(),
|
||||||
|
12 => (if level.song_id < 50 { level.song_id } else { 0 }).to_string(),
|
||||||
|
13 => level.game_version.to_string(),
|
||||||
|
14 => level.likes.to_string(),
|
||||||
|
16 => (-level.likes).to_string(),
|
||||||
|
15 => level.length.to_string(),
|
||||||
|
17 => match difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => 0
|
||||||
|
}.to_string(),
|
||||||
|
18 => (if let Some(stars) = level.stars { stars } else { 0 }).to_string(),
|
||||||
|
19 => level.featured.to_string(),
|
||||||
|
25 => match difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
if diff == helpers::difficulty::LevelDifficulty::Auto {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => 0
|
||||||
|
}.to_string(),
|
||||||
|
27 => xor_pass,
|
||||||
|
28 => "1".to_string(), // unimplemented
|
||||||
|
29 => "1".to_string(), // unimplemented
|
||||||
|
30 => (if let Some(original) = level.original { original } else { 0 }).to_string(),
|
||||||
|
31 => level.two_player.to_string(),
|
||||||
|
35 => (if level.song_id >= 50 { level.song_id } else { 0 }).to_string(),
|
||||||
|
36 => String::from_utf8(if input.extras.is_some() { level.extra_data } else { Vec::new() }).expect("invalid utf-8 sequence"),
|
||||||
|
37 => level.coins.to_string(),
|
||||||
|
38 => level.rated_coins.to_string(),
|
||||||
|
39 => (if let Some(requested_stars) = level.requested_stars { requested_stars } else { 0 }).to_string(),
|
||||||
|
40 => level.has_ldm.to_string(),
|
||||||
|
41 => "".to_string(), // unimplemented
|
||||||
|
42 => level.epic.to_string(),
|
||||||
|
43 => match demon_difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
diff
|
||||||
|
},
|
||||||
|
None => helpers::difficulty::DemonDifficulty::Hard
|
||||||
|
}.to_demon_difficulty().to_string(),
|
||||||
|
44 => "0".to_string(), // unimplemented
|
||||||
|
45 => level.objects.to_string(),
|
||||||
|
46 => level.editor_time.to_string(),
|
||||||
|
47 => level.editor_time_copies.to_string()
|
||||||
|
}));
|
||||||
|
response.push(helpers::encryption::gen_solo(String::from_utf8(level_data.to_vec()).expect("invalid utf-8 sequence")));
|
||||||
|
|
||||||
|
let thing = [
|
||||||
|
user.id.to_string(),
|
||||||
|
level.stars.unwrap_or(0).to_string(),
|
||||||
|
match difficulty {
|
||||||
|
Some(diff) => {
|
||||||
|
if diff == helpers::difficulty::LevelDifficulty::Demon {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => 0
|
||||||
|
}.to_string(),
|
||||||
|
level.id.to_string(),
|
||||||
|
level.rated_coins.to_string(),
|
||||||
|
level.featured.to_string(),
|
||||||
|
level.password.unwrap_or(String::new()).to_string(),
|
||||||
|
0.to_string()
|
||||||
|
];
|
||||||
|
response.push(helpers::encryption::gen_solo_2(thing.join(",")));
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{:?}", response);
|
||||||
|
return status::Custom(Status::Ok, Box::leak(response.join("#").into_boxed_str()))
|
||||||
|
}
|
|
@ -46,5 +46,35 @@ pub fn gen_multi(level_hash_data: Vec<(i32, i32, bool)>) -> String {
|
||||||
bytes.extend(input_str.as_bytes().to_vec());
|
bytes.extend(input_str.as_bytes().to_vec());
|
||||||
bytes.extend("xI25fpAapCQg".as_bytes().to_vec());
|
bytes.extend("xI25fpAapCQg".as_bytes().to_vec());
|
||||||
|
|
||||||
return Sha1::default().digest(bytes.as_mut_slice()).to_hex();
|
return Sha1::default().digest(bytes.as_slice()).to_hex();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_solo(level_string: String) -> String {
|
||||||
|
let mut hash = String::new();
|
||||||
|
let divided = level_string.len() as i32 / 40;
|
||||||
|
let mut i = 0;
|
||||||
|
let mut k = 0;
|
||||||
|
while k < level_string.len() {
|
||||||
|
if i > 39 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
hash += level_string.chars().nth(k).expect("invalid char access").to_string().as_str();
|
||||||
|
i += 1;
|
||||||
|
k += divided as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut bytes: Vec<u8> = Vec::new();
|
||||||
|
bytes.extend(format!("{:a<5}", hash).as_bytes().to_vec());
|
||||||
|
bytes.extend("xI25fpAapCQg".as_bytes().to_vec());
|
||||||
|
|
||||||
|
return Sha1::default().digest(bytes.as_slice()).to_hex();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_solo_2(level_mutli_string: String) -> String {
|
||||||
|
let mut bytes: Vec<u8> = Vec::new();
|
||||||
|
bytes.extend(level_mutli_string.as_bytes().to_vec());
|
||||||
|
bytes.extend("xI25fpAapCQg".as_bytes().to_vec());
|
||||||
|
|
||||||
|
return Sha1::default().digest(bytes.as_slice()).to_hex();
|
||||||
}
|
}
|
|
@ -21,7 +21,6 @@ macro_rules! object_prop_bool {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ObjectData {
|
pub struct ObjectData {
|
||||||
raw: HashMap<String, String>
|
raw: HashMap<String, String>
|
||||||
|
|
|
@ -58,6 +58,7 @@ fn rocket() -> _ {
|
||||||
|
|
||||||
endpoints::users::get_users::get_users,
|
endpoints::users::get_users::get_users,
|
||||||
|
|
||||||
|
endpoints::levels::download_level::download_level,
|
||||||
endpoints::levels::get_levels::get_levels,
|
endpoints::levels::get_levels::get_levels,
|
||||||
endpoints::levels::upload_level::upload_level
|
endpoints::levels::upload_level::upload_level
|
||||||
])
|
])
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue