sourcepak 0.3.0

A library for reading and writing Source engine package files.
Documentation
use crate::{
    common::{
        file::VPKFileReader,
        format::{PakReader, PakWriter, VPKDirectoryEntry},
    },
    pak::v1::format::VPKVersion1,
};
use std::{
    fs::{remove_dir, remove_file, File},
    io::Seek,
    path::Path,
};

#[cfg(feature = "mem-map")]
use filebuffer::FileBuffer;
#[cfg(feature = "mem-map")]
use std::collections::HashMap;

#[test]
fn read_empty_vpk_v1() {
    let path = Path::new("./test_files/empty_v1_dir.vpk");
    let mut file = File::open(path).expect("Failed to open file");
    let vpk = VPKVersion1::try_from(&mut file).expect("Failed to read VPK file");
    assert_eq!(vpk.tree.files.len(), 0, "VPK tree should have 0 entries");
    assert_eq!(
        vpk.tree.files.get("test/file.txt"),
        None,
        "File \"test/file.txt\" shouldn't exist"
    );
    assert!(
        file.stream_position().unwrap() >= file.seek(std::io::SeekFrom::End(0)).unwrap() - 1,
        "Should be at end of file"
    );
}

#[test]
fn read_invalid_vpk_v1() {
    let path = Path::new("./test_files/single_file_v1_000.vpk");
    let mut file = File::open(path).expect("Failed to open file");
    let vpk = VPKVersion1::try_from(&mut file);
    assert!(
        vpk.is_err_and(|x| x.contains("VPK header signature should be 0x55aa1234")),
        "VPK file should be invalid",
    );
}

#[test]
fn read_single_file_vpk_v1() {
    let path = Path::new("./test_files/single_file_v1_dir.vpk");
    let mut file = File::open(path).expect("Failed to open file");
    let vpk = VPKVersion1::try_from(&mut file).expect("Failed to read VPK file");
    assert_eq!(vpk.tree.files.len(), 1, "VPK tree should have 1 entry");
    assert_eq!(
        vpk.tree.files.get("test/file.txt"),
        Some(&VPKDirectoryEntry {
            crc: 0x4570FA16,
            preload_length: 0,
            archive_index: 0,
            entry_length: 9,
            entry_offset: 0,
            terminator: 0xFFFF,
        }),
        "File \"test/file.txt\" should exist"
    );
    assert!(
        file.stream_position().unwrap() >= file.seek(std::io::SeekFrom::End(0)).unwrap() - 1,
        "Should be at end of file"
    );

    let test_file = vpk.read_file(
        &String::from("./test_files"),
        &String::from("single_file_v1"),
        &String::from("test/file.txt"),
    );

    assert_eq!(
        test_file,
        Some(Vec::from("test text")),
        "File contents should be \"test text\""
    );

    let out_path = String::from("./test_files/out/file_v1.txt");

    let _ = vpk.extract_file(
        &String::from("./test_files"),
        &String::from("single_file_v1"),
        &String::from("test/file.txt"),
        &out_path,
    );

    assert_eq!(
        test_file,
        Some(File::open(&out_path).unwrap().read_string().unwrap().into()),
        "File contents should match {}",
        &out_path
    );

    let _ = remove_file(out_path);
    let _ = remove_dir("./test_files/out");
}

#[test]
fn read_single_file_eof_data_vpk_v1() {
    let path = Path::new("./test_files/single_file_eof_data_v1_dir.vpk");
    let mut file = File::open(path).expect("Failed to open file");
    let vpk = VPKVersion1::try_from(&mut file).expect("Failed to read VPK file");
    assert_eq!(vpk.tree.files.len(), 1, "VPK tree should have 1 entry");
    assert_eq!(
        vpk.tree.files.get("test/file.txt"),
        Some(&VPKDirectoryEntry {
            crc: 0x4570FA16,
            preload_length: 0,
            archive_index: 0xFF7F,
            entry_length: 9,
            entry_offset: 0,
            terminator: 0xFFFF,
        }),
        "File \"test/file.txt\" should exist"
    );

    let test_file = vpk.read_file(
        &String::from("./test_files"),
        &String::from("single_file_eof_data_v1"),
        &String::from("test/file.txt"),
    );

    assert_eq!(
        test_file,
        Some(Vec::from("test text")),
        "File contents should be \"test text\""
    );

    let out_path = String::from("./test_files/out/file_v1.txt");

    let _ = vpk.extract_file(
        &String::from("./test_files"),
        &String::from("single_file_eof_data_v1"),
        &String::from("test/file.txt"),
        &out_path,
    );

    assert_eq!(
        test_file,
        Some(File::open(&out_path).unwrap().read_string().unwrap().into()),
        "File contents should match {}",
        &out_path
    );

    let _ = remove_file(out_path);
    let _ = remove_dir("./test_files/out");
}

#[cfg(feature = "mem-map")]
#[test]
fn extract_mem_map_single_file_vpk_v1() {
    let path = Path::new("./test_files/single_file_v1_dir.vpk");
    let mut file = File::open(path).expect("Failed to open file");
    let vpk = VPKVersion1::try_from(&mut file).expect("Failed to read VPK file");

    let mut archive_mmaps = HashMap::new();
    archive_mmaps.insert(
        0,
        FileBuffer::open("./test_files/single_file_v1_000.vpk").unwrap(),
    );

    let out_path = String::from("./test_files/out/file_mem_map_v1.txt");

    let _ = vpk.extract_file_mem_map(
        &String::from("./test_files"),
        &archive_mmaps,
        &String::from("single_file_v1"),
        &String::from("test/file.txt"),
        &out_path,
    );

    assert_eq!(
        Some(Vec::from("test text")),
        Some(File::open(&out_path).unwrap().read_string().unwrap().into()),
        "File contents should match {}",
        &out_path
    );

    let _ = remove_file(out_path);
    let _ = remove_dir("./test_files/out");
}

#[test]
fn read_big_vpk_v1() {
    let path = Path::new("./test_files/portal/pak01_dir.vpk");
    let mut file: File = File::open(path).expect("Failed to open file");
    let vpk = VPKVersion1::try_from(&mut file).expect("Failed to read VPK file");
    assert_eq!(
        vpk.tree.files.len(),
        449,
        "VPK tree should have 449 entries"
    );
    assert!(
        file.stream_position().unwrap() >= file.seek(std::io::SeekFrom::End(0)).unwrap() - 1,
        "Should be at end of file"
    );
}

#[test]
fn write_parity_vpk_v1() {
    let path = Path::new("./test_files/portal/pak01_dir.vpk");
    let mut file: File = File::open(path).expect("Failed to open file");
    let vpk = VPKVersion1::try_from(&mut file).expect("Failed to read VPK file");
    assert_eq!(
        vpk.tree.files.len(),
        449,
        "VPK tree should have 449 entries"
    );
    assert!(
        file.stream_position().unwrap() >= file.seek(std::io::SeekFrom::End(0)).unwrap() - 1,
        "Should be at end of file"
    );

    let out_path = String::from("./test_files/out/pak01_dir.vpk");

    vpk.write_dir(&out_path).unwrap();

    let mut file: File = File::open(&out_path).expect("Failed to open file");
    let new_vpk = VPKVersion1::try_from(&mut file).expect("Failed to read VPK file");
    assert_eq!(
        new_vpk.tree.files.len(),
        449,
        "VPK tree should have 449 entries"
    );
    assert!(
        file.stream_position().unwrap() >= file.seek(std::io::SeekFrom::End(0)).unwrap() - 1,
        "Should be at end of file"
    );

    let _ = remove_file(out_path);
    let _ = remove_dir("./test_files/out");

    assert!(
        new_vpk == vpk,
        "Written VPK did not contain the same data as the original"
    );
}