MacOS save file AES256 decryption fallback, otherwise error
This commit is contained in:
51
Cargo.lock
generated
51
Cargo.lock
generated
@@ -8,6 +8,17 @@ version = "2.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ashpd"
|
name = "ashpd"
|
||||||
version = "0.9.2"
|
version = "0.9.2"
|
||||||
@@ -217,6 +228,15 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block2"
|
name = "block2"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@@ -272,6 +292,16 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cipher"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||||
|
dependencies = [
|
||||||
|
"crypto-common",
|
||||||
|
"inout",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -378,6 +408,15 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ecb"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7"
|
||||||
|
dependencies = [
|
||||||
|
"cipher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "endi"
|
name = "endi"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -545,8 +584,10 @@ dependencies = [
|
|||||||
name = "gd_save_browser"
|
name = "gd_save_browser"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes",
|
||||||
"base64",
|
"base64",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"ecb",
|
||||||
"flate2",
|
"flate2",
|
||||||
"phf",
|
"phf",
|
||||||
"quick-xml 0.37.0",
|
"quick-xml 0.37.0",
|
||||||
@@ -743,6 +784,16 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inout"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||||
|
dependencies = [
|
||||||
|
"block-padding",
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
|||||||
@@ -12,3 +12,5 @@ quick-xml = "0.37.0"
|
|||||||
serde = { version = "1.0.214", features = ["derive"] }
|
serde = { version = "1.0.214", features = ["derive"] }
|
||||||
serde_json = "1.0.132"
|
serde_json = "1.0.132"
|
||||||
phf = { version = "0.11.2", features = ["macros"] }
|
phf = { version = "0.11.2", features = ["macros"] }
|
||||||
|
aes = "0.8.4"
|
||||||
|
ecb = "0.1.2"
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ pub const TRUNCATE_LEVEL_DATA: bool = true;
|
|||||||
pub const YOUTUBE_CHANNEL_BASE_URL: &str = "https://www.youtube.com/channel/";
|
pub const YOUTUBE_CHANNEL_BASE_URL: &str = "https://www.youtube.com/channel/";
|
||||||
|
|
||||||
pub const XOR_KEY: u8 = 0xB;
|
pub const XOR_KEY: u8 = 0xB;
|
||||||
|
pub const AES_KEY: [u8; 32] = [
|
||||||
|
0x69, 0x70, 0x75, 0x39, 0x54, 0x55, 0x76, 0x35, 0x34, 0x79, 0x76, 0x5d, 0x69, 0x73, 0x46, 0x4d,
|
||||||
|
0x68, 0x35, 0x40, 0x3b, 0x74, 0x2e, 0x35, 0x77, 0x33, 0x34, 0x45, 0x32, 0x52, 0x79, 0x40, 0x7b,
|
||||||
|
];
|
||||||
// pub const PASSWORD_SALT: &str = "mI29fmAnxgTs";
|
// pub const PASSWORD_SALT: &str = "mI29fmAnxgTs";
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
@@ -105,10 +109,12 @@ pub static GAMESAVE_KEYS: Map<&'static str, &'static str> = phf_map! {
|
|||||||
"GJA_005" => "hashed_password",
|
"GJA_005" => "hashed_password",
|
||||||
"LLM_01" => "local_levels",
|
"LLM_01" => "local_levels",
|
||||||
"LLM_02" => "binary_version",
|
"LLM_02" => "binary_version",
|
||||||
|
"LLM_03" => "local_lists",
|
||||||
"MDLM_001" => "song_info",
|
"MDLM_001" => "song_info",
|
||||||
"MDLM_002" => "song_priority",
|
"MDLM_002" => "song_priority",
|
||||||
|
"MDLM_003" => "unknown_4",
|
||||||
"KBM_001" => "keybinds",
|
"KBM_001" => "keybinds",
|
||||||
"KBM_002" => "keybinds2",
|
"KBM_002" => "keybinds_2",
|
||||||
"texQuality" => "texture_quality",
|
"texQuality" => "texture_quality",
|
||||||
"customObjectDict" => "custom_objects",
|
"customObjectDict" => "custom_objects",
|
||||||
"playerUserID" => "player_id",
|
"playerUserID" => "player_id",
|
||||||
@@ -267,7 +273,7 @@ pub static LEVEL_KEYS: Map<&'static str, &'static str> = phf_map! {
|
|||||||
"k43" => "two_player",
|
"k43" => "two_player",
|
||||||
"k45" => "custom_song_id",
|
"k45" => "custom_song_id",
|
||||||
"k46" => "revision",
|
"k46" => "revision",
|
||||||
"k47" => "edited",
|
"k47" => "edited_outside_editor",
|
||||||
"k48" => "objects",
|
"k48" => "objects",
|
||||||
"k50" => "binary_version",
|
"k50" => "binary_version",
|
||||||
"k60" => "account_id",
|
"k60" => "account_id",
|
||||||
|
|||||||
21
src/main.rs
21
src/main.rs
@@ -3,6 +3,7 @@ mod util;
|
|||||||
|
|
||||||
use constants::XOR_KEY;
|
use constants::XOR_KEY;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use std::error::Error;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use util::decrypt::*;
|
use util::decrypt::*;
|
||||||
use util::file::*;
|
use util::file::*;
|
||||||
@@ -10,8 +11,24 @@ use util::xml::*;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Some(data_encrypted) = get_dat_data() {
|
if let Some(data_encrypted) = get_dat_data() {
|
||||||
let data: Vec<u8> =
|
let decrypt_data_windows = || -> Result<Vec<u8>, Box<dyn Error>> {
|
||||||
gz_decompress(&decode_urlsafe_base64(&xor_decrypt(&data_encrypted, XOR_KEY)).unwrap());
|
Ok(gz_decompress(&decode_urlsafe_base64(&xor_decrypt(
|
||||||
|
&data_encrypted,
|
||||||
|
XOR_KEY,
|
||||||
|
))?))
|
||||||
|
};
|
||||||
|
|
||||||
|
let decrypt_data_mac =
|
||||||
|
|| -> Result<Vec<u8>, Box<dyn Error>> { Ok(decode_aes(&data_encrypted)) };
|
||||||
|
|
||||||
|
let data: Vec<u8> = if let Ok(d) = decrypt_data_windows() {
|
||||||
|
d
|
||||||
|
} else if let Ok(d) = decrypt_data_mac() {
|
||||||
|
d
|
||||||
|
} else {
|
||||||
|
println!("Decryption failed, corrupted save file?");
|
||||||
|
exit(1);
|
||||||
|
};
|
||||||
|
|
||||||
match validate_xml(&data) {
|
match validate_xml(&data) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
|
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyInit};
|
||||||
|
use aes::Aes256;
|
||||||
use base64::{
|
use base64::{
|
||||||
engine::general_purpose::{STANDARD, URL_SAFE},
|
engine::general_purpose::{STANDARD, URL_SAFE},
|
||||||
DecodeError, Engine,
|
DecodeError, Engine,
|
||||||
};
|
};
|
||||||
|
use ecb::Decryptor;
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
|
use crate::constants::AES_KEY;
|
||||||
|
|
||||||
|
type Aes256Decryptor = Decryptor<Aes256>;
|
||||||
|
|
||||||
// Step 1
|
// Step 1
|
||||||
pub fn xor_decrypt(data: &[u8], key: u8) -> Vec<u8> {
|
pub fn xor_decrypt(data: &[u8], key: u8) -> Vec<u8> {
|
||||||
data.iter().map(|byte| byte ^ key).collect()
|
data.iter().map(|byte| byte ^ key).collect()
|
||||||
@@ -41,3 +48,11 @@ pub fn decode_base64(data: &str) -> String {
|
|||||||
.map(|&byte| byte as char)
|
.map(|&byte| byte as char)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_aes(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut buf: Vec<u8> = data.to_vec();
|
||||||
|
Aes256Decryptor::new(&AES_KEY.into())
|
||||||
|
.decrypt_padded_mut::<Pkcs7>(&mut buf)
|
||||||
|
.unwrap();
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ fn parse_dict(
|
|||||||
_ => Some(key.to_string()),
|
_ => Some(key.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key if parent_key.unwrap_or_default() == "amount"
|
key if ["amount", "local_levels", "local_lists"]
|
||||||
|
.contains(&parent_key.unwrap_or_default())
|
||||||
&& key.starts_with("k_") =>
|
&& key.starts_with("k_") =>
|
||||||
{
|
{
|
||||||
let replaced_key = key.replace("k_", "");
|
let replaced_key = key.replace("k_", "");
|
||||||
|
|||||||
Reference in New Issue
Block a user