use std::io::{Read, Seek, SeekFrom, Error, ErrorKind};
use std::slice;
use std::str;
use byteorder::{ReadBytesExt, LE};
type DRSVersion = [u8; 4];
pub type ResourceType = [u8; 4];
pub struct DRSHeader {
banner_msg: [u8; 40],
version: DRSVersion,
password: [u8; 12],
num_resource_types: u32,
directory_size: u32,
}
impl DRSHeader {
fn from<R: Read>(source: &mut R) -> Result<DRSHeader, Error> {
let mut banner_msg = [0 as u8; 40];
let mut version = [0 as u8; 4];
let mut password = [0 as u8; 12];
source.read_exact(&mut banner_msg)?;
source.read_exact(&mut version)?;
source.read_exact(&mut password)?;
let num_resource_types = source.read_u32::<LE>()?;
let directory_size = source.read_u32::<LE>()?;
Ok(DRSHeader {
banner_msg,
version,
password,
num_resource_types,
directory_size,
})
}
}
impl std::fmt::Debug for DRSHeader {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f,
"DRSHeader {{ banner_msg: '{}', version: '{}', password: '{}', num_resource_types: {}, directory_size: {} }}",
str::from_utf8(&self.banner_msg).unwrap(),
str::from_utf8(&self.version).unwrap(),
str::from_utf8(&self.password).unwrap(),
self.num_resource_types,
self.directory_size
)
}
}
pub struct DRSTable {
pub resource_type: ResourceType,
offset: u32,
num_resources: u32,
resources: Vec<DRSResource>,
}
impl DRSTable {
fn from<R: Read>(source: &mut R) -> Result<DRSTable, Error> {
let mut resource_type = [0 as u8; 4];
source.read_exact(&mut resource_type)?;
let offset = source.read_u32::<LE>()?;
let num_resources = source.read_u32::<LE>()?;
Ok(DRSTable {
resource_type,
offset,
num_resources,
resources: vec![],
})
}
fn read_resources<R: Read>(&mut self, source: &mut R) -> Result<(), Error> {
for _ in 0..self.num_resources {
self.resources.push(DRSResource::from(source)?);
}
Ok(())
}
pub fn len(&self) -> usize {
self.num_resources as usize
}
pub fn resources(&self) -> DRSResourceIterator {
self.resources.iter()
}
pub fn get_resource(&self, id: u32) -> Option<&DRSResource> {
self.resources().find(|resource| { resource.id == id })
}
pub fn resource_ext(&self) -> String {
let mut resource_type = [0 as u8; 4];
resource_type.clone_from_slice(&self.resource_type);
resource_type.reverse();
str::from_utf8(&resource_type).unwrap().trim().to_string()
}
}
impl std::fmt::Debug for DRSTable {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let mut resource_type = [0 as u8; 4];
resource_type.clone_from_slice(&self.resource_type);
resource_type.reverse();
write!(f,
"DRSTable {{ resource_type: '{}', offset: {}, num_resources: {} }}",
str::from_utf8(&resource_type).unwrap(),
self.offset,
self.num_resources
)
}
}
#[derive(Debug)]
pub struct DRSResource {
pub id: u32,
offset: u32,
pub size: u32,
}
impl DRSResource {
fn from<R: Read>(source: &mut R) -> Result<DRSResource, Error> {
let id = source.read_u32::<LE>()?;
let offset = source.read_u32::<LE>()?;
let size = source.read_u32::<LE>()?;
Ok(DRSResource {
id,
offset,
size,
})
}
}
pub type DRSTableIterator<'a> = slice::Iter<'a, DRSTable>;
pub type DRSResourceIterator<'a> = slice::Iter<'a, DRSResource>;
#[derive(Debug)]
pub struct DRSReader {
header: Option<DRSHeader>,
tables: Vec<DRSTable>,
}
impl DRSReader {
pub fn new<R>(handle: &mut R) -> Result<DRSReader, Error>
where R: Read + Seek
{
let mut drs = DRSReader {
header: None,
tables: vec![],
};
drs.read_header(handle)?;
drs.read_tables(handle)?;
drs.read_dictionary(handle)?;
Ok(drs)
}
fn read_header<R: Read + Seek>(&mut self, handle: &mut R) -> Result<(), Error> {
self.header = Some(DRSHeader::from(handle)?);
Ok(())
}
fn read_tables<R: Read + Seek>(&mut self, handle: &mut R) -> Result<(), Error> {
match self.header {
Some(ref header) => {
for _ in 0..header.num_resource_types {
let table = DRSTable::from(handle)?;
self.tables.push(table);
}
},
None => panic!("must read header first"),
};
Ok(())
}
fn read_dictionary<R: Read + Seek>(&mut self, handle: &mut R) -> Result<(), Error> {
for table in &mut self.tables {
table.read_resources(handle)?;
}
Ok(())
}
pub fn get_table(&self, resource_type: ResourceType) -> Option<&DRSTable> {
self.tables.iter().find(|table| { table.resource_type == resource_type })
}
pub fn get_resource(&self, resource_type: ResourceType, id: u32) -> Option<&DRSResource> {
self.get_table(resource_type).and_then(|table| table.get_resource(id))
}
pub fn get_resource_type(&self, id: u32) -> Option<ResourceType> {
self.tables.iter().find(|table| table.get_resource(id).is_some())
.map(|table| table.resource_type)
}
pub fn read_resource<R: Read + Seek>(&self, handle: &mut R, resource_type: ResourceType, id: u32) -> Result<Box<[u8]>, Error> {
let &DRSResource { size, offset, .. } = self.get_resource(resource_type, id)
.ok_or_else(|| Error::new(ErrorKind::NotFound, "Resource not found in this archive"))
?;
handle.seek(SeekFrom::Start(u64::from(offset)))?;
let mut buf = vec![0 as u8; size as usize];
handle.read_exact(&mut buf)?;
Ok(buf.into_boxed_slice())
}
pub fn tables(&self) -> DRSTableIterator {
self.tables.iter()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
#[test]
fn it_works() {
let mut file = File::open("test.drs").unwrap();
let drs = DRSReader::new(&mut file).unwrap();
let mut expected = vec![
(b" sj", 1, 632),
(b" sj", 2, 452),
(b" sj", 3, 38),
(b"nosj", 4, 710),
];
for table in drs.tables() {
for resource in table.resources() {
let content = drs.read_resource(&mut file, table.resource_type, resource.id).unwrap();
assert_eq!(expected.remove(0), (
&table.resource_type,
resource.id,
content.len()
));
}
}
}
}