use error::*;
use chariot_io_tools::ReadExt;
use either::Either;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::io::Seek;
use std::io::SeekFrom;
const EXPECTED_AOE_COPYRIGHT: &'static str = "Copyright (c) 1997 Ensemble Studios.\u{1A}";
const EXPECTED_AOE_VERSION: &'static str = "1.00";
const EXPECTED_AOE_TYPE: &'static str = "tribe";
const AOE_COPYRIGHT_LEN: usize = 40;
type AoeCopyright = [u8; AOE_COPYRIGHT_LEN];
const AOE_COPYRIGHT_EMPTY: AoeCopyright = [0u8; AOE_COPYRIGHT_LEN];
const EXPECTED_SWBG_COPYRIGHT: &'static str = "Copyright (c) 2001 LucasArts Entertainment Company LLC\u{1A}";
const EXPECTED_SWBG_VERSION: &'static str = "1.00";
const EXPECTED_SWBG_TYPE: &'static str = "swbg";
const SWBG_COPYRIGHT_LEN: usize = 60;
type SwbgCopyright = [u8; SWBG_COPYRIGHT_LEN];
const SWBG_COPYRIGHT_EMPTY: SwbgCopyright = [0u8; SWBG_COPYRIGHT_LEN];
type DrsCopyrightType = Either<AoeCopyright, SwbgCopyright>;
pub enum DrsGameType {
AOE,
SWBG,
}
pub struct DrsHeader {
pub copyright_info: DrsCopyrightType,
pub file_version: [u8; 4],
pub file_type: [u8; 12],
pub table_count: u32,
pub file_offset: u32,
}
impl DrsHeader {
pub fn empty() -> DrsHeader {
DrsHeader {
copyright_info: Either::Left(AOE_COPYRIGHT_EMPTY),
file_version: [0u8; 4],
file_type: [0u8; 12],
table_count: 0,
file_offset: 0,
}
}
pub fn game_type(&self) -> DrsGameType {
match self.copyright_info {
Either::Left(_) => DrsGameType::AOE,
Either::Right(_) => DrsGameType::SWBG,
}
}
pub fn read_from_file(file: &mut File, file_name: &Path) -> Result<DrsHeader> {
file.seek(SeekFrom::Start(64))?;
let mut type_str_buf = [0u8; 4];
try!(file.read_exact(&mut type_str_buf));
file.seek(SeekFrom::Start(0))?;
let type_str = ::std::str::from_utf8(&type_str_buf[..]).expect(&format!("Non-UTF8 file type: {:?}", type_str_buf));
let game_type = if type_str.trim() == "swbg" {
DrsGameType::SWBG
} else {
DrsGameType::AOE
};
let copyright_info = match game_type {
DrsGameType::AOE => {
let mut buf = AOE_COPYRIGHT_EMPTY;
try!(file.read_exact(&mut buf));
Either::Left(buf)
},
DrsGameType::SWBG => {
let mut buf = SWBG_COPYRIGHT_EMPTY;
try!(file.read_exact(&mut buf));
Either::Right(buf)
}
};
let mut file_version = [0u8; 4];
try!(file.read_exact(&mut file_version));
let mut file_type = [0u8; 12];
try!(file.read_exact(&mut file_type));
let table_count = try!(file.read_u32());
let file_offset = try!(file.read_u32());
match game_type {
DrsGameType::AOE => {
try!(validate_str(file_name, ©right_info.left().unwrap()[..], EXPECTED_AOE_COPYRIGHT));
try!(validate_str(file_name, &file_version[..], EXPECTED_AOE_VERSION));
try!(validate_str(file_name, &file_type[..], EXPECTED_AOE_TYPE));
},
DrsGameType::SWBG => {
try!(validate_str(file_name, ©right_info.right().unwrap()[..], EXPECTED_SWBG_COPYRIGHT));
try!(validate_str(file_name, &file_version[..], EXPECTED_SWBG_VERSION));
try!(validate_str(file_name, &file_type[..], EXPECTED_SWBG_TYPE));
}
}
let header = DrsHeader {
copyright_info: copyright_info,
file_version: file_version,
file_type: file_type,
table_count: table_count,
file_offset: file_offset,
};
Ok(header)
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum DrsFileType {
Binary,
Slp,
Shp,
Wav,
}
impl From<u32> for DrsFileType {
fn from(binary_val: u32) -> Self {
match binary_val {
0x62696E61 => DrsFileType::Binary,
0x736C7020 => DrsFileType::Slp,
0x73687020 => DrsFileType::Shp,
0x77617620 => DrsFileType::Wav,
_ => {
panic!("unknown file type encountered in DRS archive: 0x{:X}",
binary_val)
}
}
}
}
pub struct DrsTableHeader {
pub file_type: DrsFileType,
pub table_offset: u32,
pub file_count: u32,
}
impl DrsTableHeader {
pub fn new() -> DrsTableHeader {
DrsTableHeader {
file_type: DrsFileType::Binary,
table_offset: 0u32,
file_count: 0u32,
}
}
fn read_from_file<R: Read>(file: &mut R) -> Result<DrsTableHeader> {
let mut header = DrsTableHeader::new();
header.file_type = DrsFileType::from(try!(file.read_u32()));
header.table_offset = try!(file.read_u32());
header.file_count = try!(file.read_u32());
Ok(header)
}
pub fn file_extension(&self) -> &'static str {
match self.file_type {
DrsFileType::Binary => "bin",
DrsFileType::Slp => "slp",
DrsFileType::Shp => "shp",
DrsFileType::Wav => "wav",
}
}
}
pub struct DrsTableEntry {
pub file_id: u32,
pub file_offset: u32,
pub file_size: u32,
}
impl DrsTableEntry {
pub fn new() -> DrsTableEntry {
DrsTableEntry {
file_id: 0u32,
file_offset: 0u32,
file_size: 0u32,
}
}
fn read_from_file<R: Read>(file: &mut R) -> Result<DrsTableEntry> {
let mut entry = DrsTableEntry::new();
entry.file_id = try!(file.read_u32());
entry.file_offset = try!(file.read_u32());
entry.file_size = try!(file.read_u32());
Ok(entry)
}
}
pub type DrsFileContents = Vec<u8>;
pub struct DrsLogicalTable {
pub header: DrsTableHeader,
pub entries: Vec<DrsTableEntry>,
pub contents: Vec<DrsFileContents>,
index_map: HashMap<u32, usize>,
}
impl DrsLogicalTable {
pub fn new() -> DrsLogicalTable {
DrsLogicalTable {
header: DrsTableHeader::new(),
entries: Vec::new(),
contents: Vec::new(),
index_map: HashMap::new(),
}
}
pub fn find_file_contents(&self, file_id: u32) -> Option<&DrsFileContents> {
match self.index_map.get(&file_id) {
Some(index) => Some(&self.contents[*index]),
None => None,
}
}
fn populate_index_map(&mut self) {
for i in 0..self.entries.len() {
self.index_map.insert(self.entries[i].file_id, i);
}
}
}
pub struct DrsFile {
pub header: DrsHeader,
pub tables: Vec<DrsLogicalTable>,
}
impl DrsFile {
pub fn empty() -> DrsFile {
DrsFile {
header: DrsHeader::empty(),
tables: Vec::new(),
}
}
pub fn find_table(&self, file_type: DrsFileType) -> Option<&DrsLogicalTable> {
for table in &self.tables {
if table.header.file_type == file_type {
return Some(table);
}
}
return None;
}
pub fn read_from_file<P: AsRef<Path>>(file_name: P) -> Result<DrsFile> {
let file_name = file_name.as_ref();
let mut file = try!(File::open(file_name));
let mut drs_file = DrsFile::empty();
drs_file.header = try!(DrsHeader::read_from_file(&mut file, file_name));
try!(DrsFile::read_table_headers(&mut file, &mut drs_file));
try!(DrsFile::read_file_entry_headers(&mut file, &mut drs_file));
try!(DrsFile::read_file_contents(&mut file, &mut drs_file));
for table in &mut drs_file.tables {
table.populate_index_map();
}
Ok(drs_file)
}
fn read_table_headers<R: Read>(file: &mut R, drs_file: &mut DrsFile) -> Result<()> {
for table_index in 0..drs_file.header.table_count {
drs_file.tables.push(DrsLogicalTable::new());
drs_file.tables[table_index as usize].header = try!(DrsTableHeader::read_from_file(file));
}
Ok(())
}
fn read_file_entry_headers<R: Read>(file: &mut R, drs_file: &mut DrsFile) -> Result<()> {
for table_index in 0..drs_file.header.table_count {
for _file_index in 0..drs_file.tables[table_index as usize].header.file_count {
let table_entry = try!(DrsTableEntry::read_from_file(file));
drs_file.tables[table_index as usize].entries.push(table_entry);
}
}
Ok(())
}
fn read_file_contents<R: Read>(file: &mut R, drs_file: &mut DrsFile) -> Result<()> {
for table_index in 0..drs_file.header.table_count {
let file_sizes: Vec<u32> = drs_file.tables[table_index as usize]
.entries
.iter()
.map(|e| e.file_size)
.collect();
for file_size in file_sizes {
let mut buffer = vec![0u8; file_size as usize];
try!(file.read_exact(&mut buffer[..]));
drs_file.tables[table_index as usize].contents.push(buffer);
}
}
Ok(())
}
}
fn validate_str(file_name: &Path, bytes: &[u8], expected: &'static str) -> Result<()> {
if bytes.len() < expected.len() || &bytes[0..expected.len()] != expected.as_bytes() {
return Err(ErrorKind::InvalidDrs(file_name.into()).into());
}
Ok(())
}