diff --git a/README.md b/README.md index b0a66d1..ffd003d 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Support of games primarily depends on who developed them - Games with structural * [x] Moorhuhn Schatzjäger 2 * [x] Moorhuhn Schatzjäger 3 * [ ] Moorhuhn Invasion -* `.sar` Starforce 3D Engine (Visual Imagination Software "VIS" GbR) +* `.sar` Starforce 3D Engine (Visual Imagination Software "VIS" GbR): LightWave 3D + Open Dynamics Engine + CEGUI * [ ] Moorhuhn Kart 3 * [ ] Moorhuhn Director's Cut * [ ] Moorhuhn Kart Thunder diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9d12b25..e939b73 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -346,6 +346,13 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mhex" +version = "0.1.0" +dependencies = [ + "starforcelib", +] + [[package]] name = "miniz_oxide" version = "0.6.2" @@ -583,6 +590,13 @@ dependencies = [ "serde-xml-rs", ] +[[package]] +name = "starforcelib" +version = "0.1.0" +dependencies = [ + "binrw", +] + [[package]] name = "syn" version = "1.0.109" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c129f4a..3a39371 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["renderwarelib", "springylib"] +members = ["renderwarelib", "springylib", "starforcelib", "mhex"] diff --git a/rust/lightwave/Cargo.toml b/rust/lightwave/Cargo.toml new file mode 100644 index 0000000..773dcbf --- /dev/null +++ b/rust/lightwave/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "lightwave" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +binrw = "0.11.1" diff --git a/rust/lightwave/src/lib.rs b/rust/lightwave/src/lib.rs new file mode 100644 index 0000000..7d12d9a --- /dev/null +++ b/rust/lightwave/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: usize, right: usize) -> usize { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} diff --git a/rust/mhex/Cargo.toml b/rust/mhex/Cargo.toml new file mode 100644 index 0000000..22a0f38 --- /dev/null +++ b/rust/mhex/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "mhex" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +starforcelib = {path = "../starforcelib"} diff --git a/rust/mhex/src/main.rs b/rust/mhex/src/main.rs new file mode 100644 index 0000000..a607994 --- /dev/null +++ b/rust/mhex/src/main.rs @@ -0,0 +1,6 @@ +use starforcelib::sarc::SarcArchive; + +fn main() { + let path = "E:\\Games\\Moorhuhn Kart 3\\data.sar"; + SarcArchive::extract_all(path).unwrap(); +} diff --git a/rust/starforcelib/.gitignore b/rust/starforcelib/.gitignore new file mode 100644 index 0000000..9f97022 --- /dev/null +++ b/rust/starforcelib/.gitignore @@ -0,0 +1 @@ +target/ \ No newline at end of file diff --git a/rust/starforcelib/Cargo.toml b/rust/starforcelib/Cargo.toml new file mode 100644 index 0000000..b075499 --- /dev/null +++ b/rust/starforcelib/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "starforcelib" +version = "0.1.0" +edition = "2021" + +[dependencies] +binrw = "0.11.1" diff --git a/rust/starforcelib/src/lib.rs b/rust/starforcelib/src/lib.rs new file mode 100644 index 0000000..1d44af5 --- /dev/null +++ b/rust/starforcelib/src/lib.rs @@ -0,0 +1 @@ +pub mod sarc; diff --git a/rust/starforcelib/src/sarc.rs b/rust/starforcelib/src/sarc.rs new file mode 100644 index 0000000..4ef9338 --- /dev/null +++ b/rust/starforcelib/src/sarc.rs @@ -0,0 +1,121 @@ +use binrw::prelude::*; +use binrw::{BinRead, PosValue}; +use std::fmt::{Debug, Formatter}; +use std::fs; +use std::fs::{File, OpenOptions}; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::path::Path; + +#[binrw] +#[brw(little, magic = b"SARC")] +#[derive(Debug)] +pub struct SarcArchive { + pub version: u32, + #[br(temp)] + #[bw(calc = files.len() as u32)] + pub count: u32, + #[br(count = count)] + pub files: Vec, + #[bw(ignore)] + pub position: PosValue<()>, +} + +#[binrw] +#[derive(Debug)] +pub struct FilePointer { + #[br(temp)] + #[bw(calc = path.len() as u8)] + pub path_len: u8, + #[br(count = path_len, map = |v| String::from_utf8(v).unwrap(), pad_after = 1)] + #[bw(map = |s| s.as_bytes())] + pub path: String, + pub position: u32, + pub size: u32, + #[br(assert(size == size_2), temp)] + #[bw(calc = *size)] + pub size_2: u32, +} + +impl std::fmt::Display for SarcArchive { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "v{}", self.version)?; + for file in self.files.iter() { + writeln!(f, "{}", file)?; + } + writeln!(f, "=> {:#x}", self.position.pos) + } +} + +impl std::fmt::Display for FilePointer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} => {:#x}..+{:#x}", + self.path, self.position, self.size + ) + } +} + +impl SarcArchive { + pub fn read_file(path: &str) -> std::io::Result { + let mut file = File::open(path)?; + SarcArchive::read(&mut file) + .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err)) + } + + pub fn read(file: &mut R) -> BinResult + where + R: Read + Seek, + { + BinRead::read(file) + } + + pub fn extract(&self, file: &mut R, path: &str) -> std::io::Result> + where + R: Read + Seek, + { + self.files + .iter() + .find(|it| it.path.as_str() == path) + .ok_or(std::io::Error::new(std::io::ErrorKind::NotFound, path)) + .and_then(|ptr| ptr.extract(file, self.position.pos)) + } + + pub fn extract_all(path: &str) -> std::io::Result<()> { + let info = SarcArchive::read_file(path)?; + let mut file = File::open(path)?; + let dir = Path::new(path); + + for ptr in info.files { + let out_path = dir.with_file_name(format!("extract\\{}", ptr.path.replace(':', ""))); + + println!("Extracting: {}", ptr.path); + println!(" ↳ {}", out_path.to_str().unwrap()); + + fs::create_dir_all(out_path.with_file_name(""))?; + + let mut data = ptr + .extract(&mut file, info.position.pos) + .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?; + let mut output = OpenOptions::new() + .create_new(true) + .write(true) + .open(out_path)?; + output.write_all(data.as_mut_slice())?; + } + + Ok(()) + } +} + +impl FilePointer { + fn extract(&self, file: &mut R, offset: u64) -> std::io::Result> + where + R: Read + Seek, + { + file.seek(SeekFrom::Start(self.position as u64 + offset))?; + let mut data = vec![0u8; self.size as usize]; + file.read_exact(data.as_mut_slice())?; + Ok(data) + } +}