Skip to content

Instantly share code, notes, and snippets.

@jandk
Created June 5, 2024 12:47
Show Gist options
  • Save jandk/cf27207633193d7b24b0590908cf17d0 to your computer and use it in GitHub Desktop.
Save jandk/cf27207633193d7b24b0590908cf17d0 to your computer and use it in GitHub Desktop.
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