From 6270ae6d2b96759026f3e4524a1607de4ce56886 Mon Sep 17 00:00:00 2001 From: Ahmed Al-Taiar Date: Thu, 7 Nov 2024 20:57:26 -0500 Subject: [PATCH] Parse XML to json --- Cargo.lock | 38 +++++++++++++++++++++++++++++++- Cargo.toml | 3 +++ src/constants.rs | 1 + src/main.rs | 26 ++++++++++++++-------- src/util/file.rs | 23 +++++++++++--------- src/util/xml.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 127 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe9b66c..86a031c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -548,7 +548,10 @@ dependencies = [ "base64", "dirs", "flate2", + "quick-xml 0.37.0", "rfd", + "serde", + "serde_json", ] [[package]] @@ -739,6 +742,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "js-sys" version = "0.3.72" @@ -1049,6 +1058,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.37" @@ -1140,6 +1158,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1166,6 +1190,18 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -1520,7 +1556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" dependencies = [ "proc-macro2", - "quick-xml", + "quick-xml 0.36.2", "quote", ] diff --git a/Cargo.toml b/Cargo.toml index 7c15fe0..8c22747 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,6 @@ rfd = "0.15.0" base64 = "0.22.1" flate2 = "1.0.34" dirs = "5.0.1" +quick-xml = "0.37.0" +serde = { version = "1.0.214", features = ["derive"] } +serde_json = "1.0.132" diff --git a/src/constants.rs b/src/constants.rs index bd2d6ee..3c4df6a 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1 +1,2 @@ pub const XOR_KEY: u8 = 0xB; +pub const PASSWORD_SALT: &str = "mI29fmAnxgTs"; diff --git a/src/main.rs b/src/main.rs index b50f384..472547d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,21 +2,29 @@ mod constants; mod util; use constants::XOR_KEY; +use std::process::exit; use util::decrypt::*; use util::file::*; use util::xml::*; fn main() { - let data: Vec = - gz_decompress(&base64_decode(&xor_decrypt(&get_dat_data(), XOR_KEY)).unwrap()); + if let Some(data_encrypted) = get_dat_data() { + let data: Vec = + gz_decompress(&base64_decode(&xor_decrypt(&data_encrypted, XOR_KEY)).unwrap()); - match validate_xml(&data) { - Ok(_) => { - println!("Decryption successful"); - write_decrypted_data(&data, prompt_output_path()); - } - Err(e) => { - println!("Error: {e}"); + match validate_xml(&data) { + Ok(_) => { + println!("{:#?}", parse_save(&data)); + println!("Decryption successful"); + + if let Some(output_path) = prompt_output_path() { + write_decrypted_data(&data, output_path); + } + } + Err(e) => { + println!("Decryption failed: {e}"); + exit(1); + } } } } diff --git a/src/util/file.rs b/src/util/file.rs index 89c67de..e190340 100644 --- a/src/util/file.rs +++ b/src/util/file.rs @@ -2,29 +2,32 @@ use dirs::{data_local_dir, desktop_dir}; use rfd::FileDialog; use std::{fs::File, io::Read, io::Write, path::PathBuf}; -pub fn get_dat_data() -> Vec { - let path: PathBuf = FileDialog::new() +pub fn get_dat_data() -> Option> { + if let Some(path) = prompt_input_path() { + let mut file: File = File::open(path).unwrap(); + let mut data: Vec = Vec::new(); + file.read_to_end(&mut data).unwrap(); + return Some(data); + } + None +} + +pub fn prompt_input_path() -> Option { + FileDialog::new() .set_title("Select Geometry Dash Save") .set_file_name("CCGameManager.dat") .add_filter("Geometry Dash Save", &["dat"]) .set_directory(data_local_dir().unwrap()) .pick_file() - .unwrap(); - - let mut file: File = File::open(path).unwrap(); - let mut data: Vec = Vec::new(); - file.read_to_end(&mut data).unwrap(); - data } -pub fn prompt_output_path() -> PathBuf { +pub fn prompt_output_path() -> Option { FileDialog::new() .set_title("Select Output Location") .set_file_name("CCGameManager.xml") .add_filter("Geometry Dash Decrypted Save", &["xml"]) .set_directory(desktop_dir().unwrap()) .save_file() - .unwrap() } pub fn write_decrypted_data(data: &[u8], output_path: PathBuf) { diff --git a/src/util/xml.rs b/src/util/xml.rs index c0810cd..3fb76a2 100644 --- a/src/util/xml.rs +++ b/src/util/xml.rs @@ -1,3 +1,6 @@ +use quick_xml::{escape::unescape, events::Event, reader::Reader}; +use serde_json::{Map, Value}; + pub fn validate_xml(xml: &[u8]) -> Result<(), String> { let xml_str: String = String::from_utf8_lossy(xml).to_string(); @@ -10,3 +13,56 @@ pub fn validate_xml(xml: &[u8]) -> Result<(), String> { #[allow(unreachable_code)] Ok(()) } + +pub fn parse_save(data: &[u8]) -> Value { + let mut reader: Reader<&[u8]> = Reader::from_reader(data); + reader.config_mut().trim_text(true); + let mut buf: Vec = Vec::new(); + + parse_dict(&mut reader, &mut buf) +} + +fn parse_dict(reader: &mut Reader<&[u8]>, buf: &mut Vec) -> Value { + let mut map: Map = Map::new(); + let mut current_key: Option = None; + + loop { + match reader.read_event_into(buf) { + Err(e) => panic!("Error at position {}: {:?}", reader.error_position(), e), + Ok(Event::Eof) => break, + + Ok(Event::Start(ref e)) => match e.name().as_ref() { + b"k" => current_key = Some(read_text(reader, buf).to_string()), + b"s" | b"r" | b"i" => { + if let Some(key) = current_key.take() { + let value = read_text(reader, buf); + map.insert(key, Value::String(value)); + } + } + b"d" => { + if let Some(key) = current_key.take() { + let nested_dict = parse_dict(reader, buf); + map.insert(key, nested_dict); + } + } + _ => (), + }, + Ok(Event::End(ref e)) if e.name().as_ref() == b"d" => break, + _ => (), + } + + buf.clear(); + } + + Value::Object(map) +} + +fn read_text(reader: &mut Reader<&[u8]>, buf: &mut Vec) -> String { + match reader.read_event_into(buf) { + Ok(Event::Text(e)) => unescape(String::from_utf8_lossy(&e).to_string().as_str()) + .expect("Failed to unescape text") + .into_owned(), + Err(e) => panic!("Error at position {}: {:?}", reader.error_position(), e), + _ => panic!("Failed to read text"), + } +}