Parse XML to json
This commit is contained in:
38
Cargo.lock
generated
38
Cargo.lock
generated
@@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
pub const XOR_KEY: u8 = 0xB;
|
pub const XOR_KEY: u8 = 0xB;
|
||||||
|
pub const PASSWORD_SALT: &str = "mI29fmAnxgTs";
|
||||||
|
|||||||
26
src/main.rs
26
src/main.rs
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user