Skip to content

Commit

Permalink
feat: Added basic HSSP 1-3 functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
Le0X8 committed Aug 9, 2024
1 parent c4f9d4c commit 1481a30
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ name = 'corelib'
[dependencies]
chrono = "0.4.38"
crc32fast = "1.4.2"
murmur3 = "0.5.2"
5 changes: 5 additions & 0 deletions src/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ pub fn metadata<'a>(
};
OriginalArchiveMetadata::Rar(metadata)
}
Formats::Hssp => todo!(),
};

Ok(Box::new(metadata))
Expand Down Expand Up @@ -110,6 +111,7 @@ pub fn extract(
};
&metadata.clone() as &dyn ArchiveMetadata
}
Formats::Hssp => todo!(),
};

let files = metadata.get_files();
Expand All @@ -128,6 +130,7 @@ pub fn extract(
format!("{}/{}", &output, &path)
});
}
Formats::Hssp => todo!(),
}
} else if index.is_some() {
let index = index.unwrap();
Expand All @@ -147,6 +150,7 @@ pub fn extract(
&buffer_size,
&|path| format!("{}/{}", &output, &path),
),
Formats::Hssp => todo!(),
};
} else {
let path = path.unwrap();
Expand Down Expand Up @@ -183,6 +187,7 @@ pub fn extract(
format!("{}/{}", &output, &path)
});
}
Formats::Hssp => todo!(),
}
};

Expand Down
24 changes: 24 additions & 0 deletions src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ impl<'a> FileReader {
}
}

pub fn set_end(&mut self, end: &u64) -> LimitedFileReader {
LimitedFileReader {
file: self,
end: *end,
}
}

pub fn close(self) {
self.file.sync_all().unwrap();
drop(self);
Expand Down Expand Up @@ -264,6 +271,23 @@ impl Clone for FileReader {
}
}

#[derive(Debug)]
pub struct LimitedFileReader<'a> {
file: &'a mut FileReader,
end: u64,
}

impl Read for LimitedFileReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.file.get_position() >= self.end {
return Ok(0);
}
let to_read = min(buf.len(), (self.end - self.file.get_position()) as usize);
let read = self.file.read(&mut buf[..to_read]);
Ok(read.len())
}
}

#[derive(Debug)]
pub struct FileWriter {
path: String,
Expand Down
6 changes: 6 additions & 0 deletions src/formats.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use crate::archive::ArchiveMetadata;

pub mod hssp;
pub mod rar;
pub mod zip;

pub enum Formats {
Zip,
Rar,
Hssp,
}

pub fn from_string(format: &str) -> Formats {
match format {
"zip" => Formats::Zip,
"rar" => Formats::Rar,
"hssp" => Formats::Hssp,
_ => panic!("Unsupported format"),
}
}
Expand All @@ -19,6 +23,7 @@ pub fn to_string(format: &Formats) -> String {
match format {
Formats::Zip => "zip".to_string(),
Formats::Rar => "rar".to_string(),
Formats::Hssp => "hssp".to_string(),
}
}

Expand All @@ -33,5 +38,6 @@ pub fn to_format_metadata<'a>(
match format {
Formats::Zip => FormatMetadata::Zip(zip::to_zip_archive_metadata(metadata)),
Formats::Rar => todo!(),
Formats::Hssp => todo!(),
}
}
25 changes: 25 additions & 0 deletions src/formats/hssp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
pub mod parser;

#[derive(Debug)]
pub struct HsspMetadata {
pub version: u8,
pub checksum: u32,
pub encryption: Option<HsspEncryption>,
pub files: Vec<HsspFileEntry>,
pub has_main: bool,
}

#[derive(Debug)]
pub struct HsspEncryption {
pub hash: [u8; 32],
pub iv: [u8; 16],
}

#[derive(Debug)]
pub struct HsspFileEntry {
pub name: String,
pub offset: u64,
pub size: u64,
pub is_main: bool,
pub is_directory: bool,
}
69 changes: 69 additions & 0 deletions src/formats/hssp/parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::{file::FileReader, helpers::hash::murmur3};

use super::{HsspEncryption, HsspFileEntry, HsspMetadata};

pub fn metadata(file: &mut FileReader) -> HsspMetadata {
let mut version = 2;
let magic = file.read_utf8(&4);
if magic == "SFA\0" {
version = 1;
}
let checksum = file.read_u32le();
let file_count = file.read_u32le();
let pwd_hash: [u8; 32] = file.read_u8array(&32).try_into().unwrap();
let iv: [u8; 16] = file.read_u8array(&16).try_into().unwrap();
let main = file.read_u32le();
if version == 2 {
let bytes = file.read_u128le();
if bytes == 0 {
version = 3;
} else {
file.jump(&-16);
}
}

let encrypted = !(pwd_hash == [0; 32] && iv == [0; 16]); // TODO: handle encryption

let mut files = Vec::new();

for idx in 0..file_count {
let size = file.read_u64le();
println!("size: {}", size);
let name_len = file.read_u16le();
println!("name_len: {}", name_len);
let mut name = file.read_utf8(&(name_len as u64));
let is_directory = name.starts_with("//");
if is_directory {
name = name[2..].to_string();
}
let offset = file.get_position();
files.push(HsspFileEntry {
name,
offset,
size,
is_main: idx + 1 == main,
is_directory,
});
file.jump(&(size as i128 + name_len as i128)); // that actually was a bug
}

HsspMetadata {
version,
checksum,
encryption: if encrypted {
Some(HsspEncryption { hash: pwd_hash, iv })
} else {
None
},
files,
has_main: main != 0,
}
}

pub fn check_integrity_all(file: &mut FileReader, metadata: &HsspMetadata) -> bool {
let offset = if metadata.version > 2 { 128 } else { 64 };
if murmur3::hash(file, &offset, &(file.get_size() - offset), &822616071) == metadata.checksum {
return true;
}
false
}
1 change: 1 addition & 0 deletions src/helpers/hash.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod crc32;
pub mod murmur3;
12 changes: 12 additions & 0 deletions src/helpers/hash/murmur3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::file::FileReader;
use murmur3::murmur3_32;

// Buffer size is fixed to 4 Bytes
pub fn hash(file: &mut FileReader, offset: &u64, size: &u64, seed: &u32) -> u32 {
let pos_before = file.get_position();
file.seek(offset);
let mut reader = file.set_end(&(offset + size));
let result = murmur3_32(&mut reader, *seed).unwrap();
file.seek(&pos_before);
result
}
20 changes: 20 additions & 0 deletions tests/hssp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use corelib::file::FileReader;

#[test]
fn sample_000() {
let mut file = FileReader::new(&"tests/samples/hssp/000.hssp".to_string());

let metadata = corelib::formats::hssp::parser::metadata(&mut file);

assert_eq!(metadata.version, 2);
assert!(metadata.encryption.is_none());
assert_eq!(metadata.files.len(), 1);
assert_eq!(metadata.files[0].name, "test.txt");
assert!(!metadata.files[0].is_main);
assert!(!metadata.files[0].is_directory);
assert!(!metadata.has_main);

assert!(corelib::formats::hssp::parser::check_integrity_all(
&mut file, &metadata
));
}
8 changes: 8 additions & 0 deletions tests/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,11 @@ Thanks to [@ssokolow](https://github.com/ssokolow) for providing samples 000-002
| [000](rar/000.rar) | <https://github.com/ssokolow/rar-test-files/raw/master/build/testfile.rar5.rar> |
| [001](rar/001.rar) | <https://github.com/ssokolow/rar-test-files/raw/master/build/testfile.rar5.locked.rar> |
| [002](rar/002.rar) | <https://github.com/ssokolow/rar-test-files/raw/master/build/testfile.rar5.rr.rar> |

## HSSP

Most of the samples are taken from the official [HSSP JavaScript reference implementation](https://github.com/HSSPfile/js/blob/main/test/samples).

| ID | Source/Credits |
| -------------------- | ----------------------------------------------------------------------- |
| [000](hssp/000.hssp) | <https://github.com/HSSPfile/js/raw/main/test/samples/rfld-normal.hssp> |
Binary file added tests/samples/hssp/000.hssp
Binary file not shown.

0 comments on commit 1481a30

Please sign in to comment.