Created
June 5, 2024 12:47
-
-
Save jandk/cf27207633193d7b24b0590908cf17d0 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
use std::fs::File; | |
use std::io; | |
use std::io::BufReader; | |
use std::io::Read; | |
use std::rc::Rc; | |
use byteorder::{LE, ReadBytesExt}; | |
#[derive(Debug)] | |
struct VpkHeader { | |
signature: u32, | |
version: u32, | |
tree_size: u32, | |
file_data_section_size: u32, | |
archive_md5section_size: u32, | |
other_md5section_size: u32, | |
signature_section_size: u32, | |
} | |
#[derive(Debug)] | |
struct VpkEntry { | |
extension: Rc<str>, | |
path: Rc<str>, | |
file: String, | |
crc: u32, | |
preload_bytes: u16, | |
archive_index: u16, | |
entry_offset: u32, | |
entry_length: u32, | |
terminator: u16, | |
} | |
impl VpkHeader { | |
fn read(reader: &mut impl io::Read) -> io::Result<VpkHeader> { | |
let signature = reader.read_u32::<LE>()?; | |
let version = reader.read_u32::<LE>()?; | |
let tree_size = reader.read_u32::<LE>()?; | |
let file_data_section_size = reader.read_u32::<LE>()?; | |
let archive_md5section_size = reader.read_u32::<LE>()?; | |
let other_md5section_size = reader.read_u32::<LE>()?; | |
let signature_section_size = reader.read_u32::<LE>()?; | |
Ok(VpkHeader { | |
signature, | |
version, | |
tree_size, | |
file_data_section_size, | |
archive_md5section_size, | |
other_md5section_size, | |
signature_section_size, | |
}) | |
} | |
} | |
impl VpkEntry { | |
fn read( | |
reader: &mut impl io::Read, | |
extension: Rc<str>, | |
path: Rc<str>, | |
file: String, | |
) -> io::Result<VpkEntry> { | |
let crc = reader.read_u32::<LE>()?; | |
let preload_bytes = reader.read_u16::<LE>()?; | |
let archive_index = reader.read_u16::<LE>()?; | |
let entry_offset = reader.read_u32::<LE>()?; | |
let entry_length = reader.read_u32::<LE>()?; | |
let terminator = reader.read_u16::<LE>()?; | |
Ok(VpkEntry { | |
extension, | |
path, | |
file, | |
crc, | |
preload_bytes, | |
archive_index, | |
entry_offset, | |
entry_length, | |
terminator, | |
}) | |
} | |
} | |
fn read_cstring(reader: &mut impl io::BufRead) -> io::Result<String> { | |
let mut buffer = Vec::new(); | |
loop { | |
let byte = reader.read_u8()?; | |
if byte == 0 { | |
break; | |
} | |
buffer.push(byte); | |
} | |
String::from_utf8(buffer).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) | |
} | |
fn main() -> io::Result<()> { | |
let path = r#"C:\Program Files (x86)\Steam\steamapps\common\Half-Life 2\hl2\hl2_misc_dir.vpk"#; | |
let mut reader = File::open(path) | |
.map(BufReader::new) | |
.expect("Could not open file"); | |
let header = VpkHeader::read(&mut reader)?; | |
if header.signature != 0x55aa1234 || header.version != 2 { | |
eprintln!("Invalid signature or version"); | |
std::process::exit(1); | |
} | |
let mut entries = Vec::new(); | |
loop { | |
let extension: Rc<str> = read_cstring(&mut reader)?.into(); | |
if extension.is_empty() { | |
break; | |
} | |
loop { | |
let path: Rc<str> = read_cstring(&mut reader)?.into(); | |
if path.is_empty() { | |
break; | |
} | |
loop { | |
let file = read_cstring(&mut reader)?; | |
if file.is_empty() { | |
break; | |
} | |
let entry = | |
VpkEntry::read(&mut reader, Rc::clone(&extension), Rc::clone(&path), file)?; | |
entries.push(entry); | |
} | |
} | |
} | |
println!("{header:?}"); | |
println!("Parsed {} entries", entries.len()); | |
let max_index = entries.iter().map(|e| e.archive_index).max().unwrap(); | |
println!("Max file index: {}", max_index); | |
Ok(()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment