Commit cad6ff5d by Alice

the most beautiful thing in this is making a wild guess and having to commit it quickly (unk3)

parent bd7bc8ae
/target/
**/*.rs.bk
test/
.vscode/
/*
~~~ first of all i love half-life. thank u valv
Saves are .sav containers, with a "JSAV" magic header. (JsavFile)
They contain some metadata about the save, and multiple named files.
Those files are named <mapname>.hl1/hl2/hl3, look very different,
but .hl1/hl2 start with the same VALV magic header. The hl3 one is very small.
I'm guessing on load they are extracted in the same folder, and on save they
are written as files and then added to the main .sav.
It looks like there is a set of those for the current and previous map,
probably because more would make the save file too heavy and the game's
linearity doesn't require to be able to get far back. (so enemies should
respawn in the map before the last, i'm pretty sure that's right)
References:
- Valve wiki on save files. not much actual format info, but interesting anyway
https://developer.valvesoftware.com/wiki/Save_Game_Files
https://developer.valvesoftware.com/wiki/Data_Descriptions
- a fork of Xash3D, which is compatible with GoldSrc saves.
modern Source saves have a very similar structure.
https://github.com/FWGS/xash3d-fwgs/blob/master/engine/server/sv_save.c
*/
#[macro_use] extern crate quick_error;
#[macro_use] extern crate log;
extern crate clap;
......@@ -125,8 +152,8 @@ impl JsavFile {
let savename = get_string(&mut cursor, 80)?;
trace!("read savename: {:?}", savename);
let unk3 = get_bytes(&mut cursor, 2168)?;
trace!("read unk3 ({} bytes)", unk3.len());
let unk3 = get_bytes(&mut cursor, size - 124)?;
trace!("read unk3 (offset=0x{:06x} size={} bytes)", cursor.position() as usize - unk3.len(), unk3.len());
let mut entries = Vec::new();
loop {
......@@ -136,8 +163,8 @@ impl JsavFile {
}
let valv_pre = get_string(&mut cursor, 260)?;
let valv_size = get_u32(&mut cursor)? as usize;
trace!("read block (offset=0x{:06x} size={} bytes): {}", offset, valv_size, valv_pre);
let valv_content = get_bytes(&mut cursor, valv_size)?;
trace!("read VALV file (offset=0x{:06x} size={} bytes): {}", offset, valv_size, valv_pre);
entries.push(JsavValvEntry {
name: valv_pre,
data: valv_content,
......@@ -154,6 +181,63 @@ impl JsavFile {
}
}
#[derive(Debug, Clone)]
struct ValvFile {
version: u32,
}
impl ValvFile {
pub fn load(data: &[u8]) -> Result<Self, DecodeError> {
let header = &data[0..4];
if header != b"VALV" {
return Err(DecodeError::InvalidFileType(header.to_vec(), b"VALV".to_vec()));
}
let mut cursor = Cursor::new(&data[4 .. ]);
let version = get_u32(&mut cursor)?;
trace!("VALV: Version 0x{:04x} (known: 0x0073)", version);
let token_size = get_u32(&mut cursor)? as usize;
let table_count = get_u32(&mut cursor)?;
let token_count = get_u32(&mut cursor)?;
let size = get_u32(&mut cursor)?;
trace!("VALV: size={}, table_count={}, token_count={}, token_size={}", size, table_count, token_count, token_size);
let token_buffer = get_bytes(&mut cursor, token_size)?;
let tokens: Vec<String> = token_buffer.iter()
.group_by(|&&c| c == 0)
.into_iter()
.filter(|(key, _)| *key == false)
.map(|(_, group)| {
let bytes: Vec<u8> = group.cloned().collect();
String::from_utf8_lossy(&bytes).into_owned()
})
.collect();
trace!("found {} tokens", tokens.len());
for t in tokens.iter() { trace!("- {}", t); }
trace!("position: {}", cursor.position());
loop {
let offset = cursor.position() as usize;
if offset >= cursor.get_ref().len() {
break;
}
let block_size = get_u32(&mut cursor)? as usize;
trace!("read block (offset=0x{:06x} size={} bytes)", offset, block_size);
let _block_content = get_bytes(&mut cursor, block_size)?;
/*entries.push(JsavValvEntry {
name: valv_pre,
data: valv_content,
})*/
}
Ok(ValvFile {
version: version,
})
}
}
fn main() {
pretty_env_logger::init();
......@@ -167,6 +251,11 @@ fn main() {
.help("Dump .valv files to this directory")
)
)
.subcommand(
SubCommand::with_name("valv")
.about("Reads VALV (extracted .hl1/.hl2) files")
.arg(Arg::with_name("path").value_name("FILE").required(true))
)
.get_matches();
if let Some(matches) = matches.subcommand_matches("jsav") {
......@@ -190,6 +279,17 @@ fn main() {
}
}
}
else if let Some(matches) = matches.subcommand_matches("valv") {
let path = matches.value_of("path").expect("require path");
let mut f = File::open(path).expect("file not found");
let mut data: Vec<u8> = Vec::new();
f.read_to_end(&mut data)
.expect("something went wrong reading the file");
let save = ValvFile::load(&data).expect("failed to read file");
println!("{:#?}", save);
}
else {
println!("Unknown or missing subcommand. Try with 'help' or -h.");
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment