Parse XML to json

This commit is contained in:
Ahmed Al-Taiar
2024-11-07 20:57:26 -05:00
parent fae2ad25e5
commit 6270ae6d2b
6 changed files with 127 additions and 20 deletions

38
Cargo.lock generated
View File

@@ -548,7 +548,10 @@ dependencies = [
"base64", "base64",
"dirs", "dirs",
"flate2", "flate2",
"quick-xml 0.37.0",
"rfd", "rfd",
"serde",
"serde_json",
] ]
[[package]] [[package]]
@@ -739,6 +742,12 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.72" version = "0.3.72"
@@ -1049,6 +1058,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "quick-xml"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.37"
@@ -1140,6 +1158,12 @@ dependencies = [
"windows-sys 0.52.0", "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]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@@ -1166,6 +1190,18 @@ dependencies = [
"syn", "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]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.19" version = "0.1.19"
@@ -1520,7 +1556,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3" checksum = "597f2001b2e5fc1121e3d5b9791d3e78f05ba6bfa4641053846248e3a13661c3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quick-xml", "quick-xml 0.36.2",
"quote", "quote",
] ]

View File

@@ -8,3 +8,6 @@ rfd = "0.15.0"
base64 = "0.22.1" base64 = "0.22.1"
flate2 = "1.0.34" flate2 = "1.0.34"
dirs = "5.0.1" dirs = "5.0.1"
quick-xml = "0.37.0"
serde = { version = "1.0.214", features = ["derive"] }
serde_json = "1.0.132"

View File

@@ -1 +1,2 @@
pub const XOR_KEY: u8 = 0xB; pub const XOR_KEY: u8 = 0xB;
pub const PASSWORD_SALT: &str = "mI29fmAnxgTs";

View File

@@ -2,21 +2,29 @@ mod constants;
mod util; mod util;
use constants::XOR_KEY; use constants::XOR_KEY;
use std::process::exit;
use util::decrypt::*; use util::decrypt::*;
use util::file::*; use util::file::*;
use util::xml::*; use util::xml::*;
fn main() { fn main() {
let data: Vec<u8> = if let Some(data_encrypted) = get_dat_data() {
gz_decompress(&base64_decode(&xor_decrypt(&get_dat_data(), XOR_KEY)).unwrap()); let data: Vec<u8> =
gz_decompress(&base64_decode(&xor_decrypt(&data_encrypted, XOR_KEY)).unwrap());
match validate_xml(&data) { match validate_xml(&data) {
Ok(_) => { Ok(_) => {
println!("Decryption successful"); println!("{:#?}", parse_save(&data));
write_decrypted_data(&data, prompt_output_path()); println!("Decryption successful");
}
Err(e) => { if let Some(output_path) = prompt_output_path() {
println!("Error: {e}"); write_decrypted_data(&data, output_path);
}
}
Err(e) => {
println!("Decryption failed: {e}");
exit(1);
}
} }
} }
} }

View File

@@ -2,29 +2,32 @@ use dirs::{data_local_dir, desktop_dir};
use rfd::FileDialog; use rfd::FileDialog;
use std::{fs::File, io::Read, io::Write, path::PathBuf}; use std::{fs::File, io::Read, io::Write, path::PathBuf};
pub fn get_dat_data() -> Vec<u8> { pub fn get_dat_data() -> Option<Vec<u8>> {
let path: PathBuf = FileDialog::new() if let Some(path) = prompt_input_path() {
let mut file: File = File::open(path).unwrap();
let mut data: Vec<u8> = Vec::new();
file.read_to_end(&mut data).unwrap();
return Some(data);
}
None
}
pub fn prompt_input_path() -> Option<PathBuf> {
FileDialog::new()
.set_title("Select Geometry Dash Save") .set_title("Select Geometry Dash Save")
.set_file_name("CCGameManager.dat") .set_file_name("CCGameManager.dat")
.add_filter("Geometry Dash Save", &["dat"]) .add_filter("Geometry Dash Save", &["dat"])
.set_directory(data_local_dir().unwrap()) .set_directory(data_local_dir().unwrap())
.pick_file() .pick_file()
.unwrap();
let mut file: File = File::open(path).unwrap();
let mut data: Vec<u8> = Vec::new();
file.read_to_end(&mut data).unwrap();
data
} }
pub fn prompt_output_path() -> PathBuf { pub fn prompt_output_path() -> Option<PathBuf> {
FileDialog::new() FileDialog::new()
.set_title("Select Output Location") .set_title("Select Output Location")
.set_file_name("CCGameManager.xml") .set_file_name("CCGameManager.xml")
.add_filter("Geometry Dash Decrypted Save", &["xml"]) .add_filter("Geometry Dash Decrypted Save", &["xml"])
.set_directory(desktop_dir().unwrap()) .set_directory(desktop_dir().unwrap())
.save_file() .save_file()
.unwrap()
} }
pub fn write_decrypted_data(data: &[u8], output_path: PathBuf) { pub fn write_decrypted_data(data: &[u8], output_path: PathBuf) {

View File

@@ -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> { pub fn validate_xml(xml: &[u8]) -> Result<(), String> {
let xml_str: String = String::from_utf8_lossy(xml).to_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)] #[allow(unreachable_code)]
Ok(()) 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<u8> = Vec::new();
parse_dict(&mut reader, &mut buf)
}
fn parse_dict(reader: &mut Reader<&[u8]>, buf: &mut Vec<u8>) -> Value {
let mut map: Map<String, Value> = Map::new();
let mut current_key: Option<String> = 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<u8>) -> 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"),
}
}