1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
use std::io::Read;
use std::fs::File;
use std::fs;

use byteorder::LittleEndian;
use byteorder::WriteBytesExt;
use std::io;
use std::io::Write;
use std::path::Path;

pub struct PackedFile {
    relative_path: String,
    data: Vec<u8>
}

impl From<io::Error> for ::BuildPackError {
    fn from(_: io::Error) -> Self {
        ::BuildPackError::IOError
    }
}

pub fn build_pack(input_directory: &Path, output_file: &mut File, version: u32, bitmask: u32) -> Result<(), ::BuildPackError> {
    if version != 4 && version != 5 {
        return Err(::BuildPackError::UnsupportedPFHVersionError)
    }
    let (_content_size, index_size, files) = traverse_directory(input_directory, "".to_string())?;
    if files.len() < 1 {
        return Err(::BuildPackError::EmptyInputError)
    }
    write_header(output_file, version, bitmask, index_size+4, &files)?;
    write_index(output_file, &files)?;
    write_content(output_file, &files)?;
    Ok(())
}

fn traverse_directory(directory: &Path, prefix: String) -> Result<(u32, u32, Vec<PackedFile>), ::BuildPackError> {
    let mut files = vec!();
    let mut content_size = 0;
    let mut index_size = 0;
    for entry in fs::read_dir(directory)? {
        let entry = entry?;
        let path = entry.path();
        let metadata = fs::metadata(&path)?;
        let relative_path = if prefix.len() > 0 {
            prefix.clone() + &"\\".to_string() + &entry.file_name().into_string().unwrap()
        } else {
            entry.file_name().into_string().unwrap()
        };
        if metadata.is_dir() {
            let (child_content_size, child_index_size, child_files) = traverse_directory(&path, relative_path)?;
            content_size += child_content_size;
            index_size += child_index_size;
            files.extend(child_files)
        } else if metadata.is_file() {
            let mut file = File::open(path)?;
            let mut buf = vec!();
            file.read_to_end(&mut buf)?;
            content_size += buf.len() as u32;
            index_size += relative_path.len() as u32 + 1;
            files.push(PackedFile {
                relative_path: relative_path,
                data: buf
            })
        }
    }
    Ok((content_size, index_size, files))
}

fn write_header(output_file: &mut File, version: u32, bitmask: u32, index_size: u32, files: &Vec<PackedFile>) -> Result<(), ::BuildPackError> {
    if version == 4 {
        output_file.write_u32::<LittleEndian>(::PFH4_PREAMBLE)?;
        output_file.write_u32::<LittleEndian>(bitmask)?;
        output_file.write_u32::<LittleEndian>(0)?; // PF Index Count
        output_file.write_u32::<LittleEndian>(0)?; // PF Index Size
        output_file.write_u32::<LittleEndian>(files.len() as u32)?;
        output_file.write_u32::<LittleEndian>(index_size as u32)?;
        output_file.write_u32::<LittleEndian>(0)?; // timestamp
    } else if version == 5 {
        output_file.write_u32::<LittleEndian>(::PFH5_PREAMBLE)?;
        output_file.write_u32::<LittleEndian>(bitmask)?;
        output_file.write_u32::<LittleEndian>(0)?; // PF Index Count
        output_file.write_u32::<LittleEndian>(0)?; // PF Index Size
        output_file.write_u32::<LittleEndian>(files.len() as u32)?;
        output_file.write_u32::<LittleEndian>(index_size as u32)?;
        output_file.write_u32::<LittleEndian>(0)?;
        output_file.write_u32::<LittleEndian>(0)?;
        output_file.write_u32::<LittleEndian>(0)?;
        output_file.write_u32::<LittleEndian>(0)?;
        output_file.write_u32::<LittleEndian>(0)?;
        output_file.write_u32::<LittleEndian>(0)?;
    } else {
        panic!();
    }
    Ok(())
}

fn write_index(output_file: &mut File, files: &Vec<PackedFile>) -> Result<(), ::BuildPackError> {
    for file in files {
        output_file.write_u32::<LittleEndian>(file.data.len() as u32)?;
        output_file.write_all(file.relative_path.as_ref())?;
        output_file.write_u8(0)?;
    }
    Ok(())
}

fn write_content(output_file: &mut File, files: &Vec<PackedFile>) -> Result<(), ::BuildPackError> {
    for file in files {
        output_file.write_all(&file.data)?;
    }
    Ok(())
}