Created
May 9, 2018 18:42
-
-
Save 0xa/0b1bac9f2aaad6c9ac3d545111fba131 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Ok( | |
JsavFile { | |
version: 115, | |
tokens: [ | |
"uv", | |
"landmark", | |
"state", | |
"globalentity_t", | |
"mapName", | |
"levelName", | |
"originMapName", | |
"m_list", | |
"GameHeader", | |
"elems", | |
"GLOBAL", | |
"counter", | |
"name", | |
"comment" | |
], | |
map_name: "d1_town_04", | |
save_name: "#HL2_Chapter6_Title 052:37", | |
content: [ | |
JsavValvEntry { name: "d1_town_02a.hl1", data: [429164 bytes] }, | |
JsavValvEntry { name: "d1_town_02a.hl2", data: [122170 bytes] }, | |
JsavValvEntry { name: "d1_town_02a.hl3", data: [56 bytes] }, | |
JsavValvEntry { name: "d1_town_04.hl1", data: [347028 bytes] }, | |
JsavValvEntry { name: "d1_town_04.hl2", data: [83718 bytes] }, | |
JsavValvEntry { name: "d1_town_04.hl3", data: [4 bytes] } | |
] | |
} | |
) | |
*/ | |
#[macro_use] extern crate structopt; | |
#[macro_use] extern crate quick_error; | |
extern crate byteorder; | |
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; | |
use std::path::PathBuf; | |
use structopt::StructOpt; | |
extern crate pretty_env_logger; | |
#[macro_use] extern crate log; | |
extern crate itertools; | |
use itertools::Itertools; | |
use std::io::Cursor; | |
use std::fs::File; | |
use std::io::prelude::*; | |
use std::fmt; | |
quick_error! { | |
#[derive(Debug)] | |
pub enum DecodeError { | |
Io(err: std::io::Error) { | |
cause(err) | |
description(err.description()) | |
from() | |
} | |
Incomplete(len: usize, minimum: usize) { | |
display("Incomplete data: {} bytes, expected at least {}", len, minimum) | |
} | |
InvalidFileType(found_header: Vec<u8>, expected: Vec<u8>) { | |
display("Found header {:?}, expected {:?}", found_header, expected) | |
} | |
Other(err: Box<std::error::Error>) { | |
cause(&**err) | |
description(err.description()) | |
} | |
} | |
} | |
fn check_input_size(input: &[u8], required: usize) -> Result<(), DecodeError> { | |
if input.len() < required { | |
return Err(DecodeError::Incomplete(input.len(), required)); | |
} | |
Ok(()) | |
} | |
fn get_bytes(cursor: &mut Cursor<&[u8]>, l: usize) -> Result<Vec<u8>, DecodeError> { | |
let mut r = vec![0; l]; | |
cursor.read_exact(&mut r)?; | |
Ok(r) | |
} | |
fn get_string(cursor: &mut Cursor<&[u8]>, l: usize) -> Result<String, DecodeError> { | |
let bytes = get_bytes(cursor, l)?; | |
let end = bytes.iter().take_while(|&&x| x != 0).count(); | |
let s = String::from_utf8_lossy(&bytes[0..end]).to_string(); | |
Ok(s) | |
} | |
fn get_u32(cursor: &mut Cursor<&[u8]>) -> Result<u32, DecodeError> { | |
let bytes_vec = get_bytes(cursor, 4)?; | |
let mut bytes: &[u8] = bytes_vec.as_ref(); | |
Ok(bytes.read_u32::<LittleEndian>()?) | |
} | |
/// a VALV file entry, in a JSAV file. | |
#[derive(Clone)] | |
struct JsavValvEntry { | |
name: String, | |
data: Vec<u8>, | |
} | |
impl fmt::Debug for JsavValvEntry { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
write!(f, "JsavValvEntry {{ name: {:?}, data: [{} bytes] }}", self.name, self.data.len()) | |
} | |
} | |
#[derive(Debug, Clone)] | |
struct JsavFile { | |
version: u32, | |
tokens: Vec<String>, | |
map_name: String, | |
save_name: String, | |
content: Vec<JsavValvEntry>, | |
} | |
impl JsavFile { | |
pub fn load(data: &[u8]) -> Result<Self, DecodeError> { | |
check_input_size(data, 20)?; | |
let header = &data[0..4]; | |
if header != b"JSAV" { | |
return Err(DecodeError::InvalidFileType(header.to_vec(), b"JSAV".to_vec())); | |
} | |
let mut cursor = Cursor::new(&data[4 .. ]); | |
let version = get_u32(&mut cursor)?; | |
trace!("JSAV: Version 0x{:04x} (known: 0x0073)", version); | |
let size = get_u32(&mut cursor)? as usize; | |
let token_count = get_u32(&mut cursor)? as usize; | |
let token_size = get_u32(&mut cursor)? as usize; | |
trace!("JSAV: size={}, token_count={}, token_size={}", size, 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()); | |
//let _unused = data[token_size.. token_size + 20]; | |
let unk1 = get_bytes(&mut cursor, 12)?; | |
trace!("read unk1: {:?}", unk1); | |
let mapname = get_string(&mut cursor, 32)?; | |
trace!("read mapname: {:?}", mapname); | |
let unk2 = get_bytes(&mut cursor, 4)?; | |
trace!("read unk2: {:?} (50 00 AE 0F ?)", unk2); | |
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 mut entries = Vec::new(); | |
loop { | |
let offset = cursor.position() as usize; | |
if offset >= cursor.get_ref().len() { | |
break; | |
} | |
let valv_pre = get_string(&mut cursor, 260)?; | |
let valv_size = get_u32(&mut cursor)? as usize; | |
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, | |
}) | |
} | |
Ok(JsavFile { | |
version: version, | |
tokens: tokens, | |
map_name: mapname, | |
save_name: savename, | |
content: entries, | |
}) | |
} | |
} | |
#[derive(StructOpt, Debug)] | |
struct Opt { | |
#[structopt(short = "d", long = "debug")] | |
debug: bool, | |
#[structopt(short = "v", long = "verbose", parse(from_occurrences))] | |
verbose: u8, | |
/// Save file (.sav) | |
#[structopt(name = "SAVFILE", parse(from_os_str))] | |
file: PathBuf, | |
} | |
fn main() { | |
pretty_env_logger::init(); | |
let opt = Opt::from_args(); | |
let mut f = File::open(opt.file).expect("file not found"); | |
let mut data: Vec<u8> = Vec::new(); | |
f.read_to_end(&mut data) | |
.expect("something went wrong reading the file"); | |
println!("{:#?}", JsavFile::load(&data)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment