#![deny(future_incompatible)]
#![deny(nonstandard_style)]
#![deny(rust_2018_idioms)]
#![deny(unsafe_code)]
#![warn(missing_docs)]
#![warn(unused)]
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
use sorted_vec::SortedVec;
use std::io::{Error, Read, Write};
use std::slice;
use std::str;
mod read;
mod write;
pub use read::DRSReader;
pub use write::{DRSWriter, InMemoryStrategy, ReserveDirectoryStrategy, Strategy as WriteStrategy};
type DRSVersion = [u8; 4];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ResourceType([u8; 4]);
impl ResourceType {
#[inline]
fn write_to<W: Write>(self, output: &mut W) -> Result<(), Error> {
let mut bytes = [0; 4];
bytes.copy_from_slice(&self.0);
bytes.reverse();
output.write_all(&bytes)?;
Ok(())
}
}
impl PartialEq<str> for ResourceType {
fn eq(&self, ext: &str) -> bool {
let me: &str = self.as_ref();
me == ext
}
}
impl AsRef<str> for ResourceType {
fn as_ref(&self) -> &str {
str::from_utf8(&self.0[..])
.expect("resource type must be utf-8")
.trim()
}
}
impl ToString for ResourceType {
fn to_string(&self) -> String {
let s: &str = self.as_ref();
s.to_string()
}
}
#[derive(Debug, thiserror::Error)]
#[error("invalid resource type, must be 4 characters")]
pub struct ParseResourceTypeError;
impl core::str::FromStr for ResourceType {
type Err = ParseResourceTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = s.as_bytes();
if bytes.len() > 4 {
Err(ParseResourceTypeError)
} else {
Ok(bytes.into())
}
}
}
impl From<[u8; 4]> for ResourceType {
fn from(mut u: [u8; 4]) -> Self {
u.reverse();
Self(u)
}
}
impl From<&[u8]> for ResourceType {
fn from(u: &[u8]) -> Self {
assert!(u.len() <= 4);
let mut bytes = [b' '; 4];
(&mut bytes[0..u.len()]).copy_from_slice(u);
Self(bytes)
}
}
impl From<&str> for ResourceType {
fn from(s: &str) -> Self {
s.as_bytes().into()
}
}
pub struct DRSHeader {
banner_msg: [u8; 40],
version: DRSVersion,
password: [u8; 12],
num_resource_types: u32,
directory_size: u32,
}
impl Default for DRSHeader {
fn default() -> Self {
Self {
banner_msg: *b"Copyright (c) 1997 Ensemble Studios.\x1a\x00\x00\x00",
version: *b"1.00",
password: *b"tribe\x00\x00\x00\x00\x00\x00\x00",
num_resource_types: 0,
directory_size: 0,
}
}
}
impl DRSHeader {
#[inline]
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,
})
}
#[inline]
fn write_to<W: Write>(&self, output: &mut W) -> Result<(), Error> {
output.write_all(&self.banner_msg)?;
output.write_all(&self.version)?;
output.write_all(&self.password)?;
output.write_u32::<LE>(self.num_resource_types)?;
output.write_u32::<LE>(self.directory_size)?;
Ok(())
}
}
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).expect("banner must be utf8"),
str::from_utf8(&self.version).expect("version must be utf8"),
str::from_utf8(&self.password).expect("password must be utf8"),
self.num_resource_types,
self.directory_size
)
}
}
pub struct DRSTable {
pub resource_type: ResourceType,
offset: u32,
num_resources: u32,
resources: Vec<DRSResource>,
resource_ids: SortedVec<u32>,
}
impl DRSTable {
fn new(resource_type: ResourceType, offset: u32, num_resources: u32) -> Self {
Self {
resource_type,
offset,
num_resources,
resources: Default::default(),
resource_ids: Default::default(),
}
}
#[inline]
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::new(resource_type.into(), offset, num_resources))
}
#[inline]
fn write_to<W: Write>(&self, output: &mut W) -> Result<(), Error> {
self.resource_type.write_to(output)?;
output.write_u32::<LE>(self.offset)?;
output.write_u32::<LE>(self.num_resources)?;
Ok(())
}
#[inline]
fn read_resources<R: Read>(&mut self, source: &mut R) -> Result<(), Error> {
for _ in 0..self.num_resources {
let resource = DRSResource::from(source)?;
let _discard = self.resource_ids.insert(resource.id);
self.resources.push(resource);
}
Ok(())
}
#[inline]
pub fn len(&self) -> usize {
self.num_resources as usize
}
#[inline]
pub fn is_empty(&self) -> bool {
self.num_resources == 0
}
#[inline]
pub fn resources(&self) -> DRSResourceIterator<'_> {
self.resources.iter()
}
#[inline]
pub fn get_resource(&self, id: u32) -> Option<&DRSResource> {
self.resource_ids
.binary_search(&id)
.ok()
.map(|index| &self.resources[index])
}
#[inline]
pub fn resource_type(&self) -> ResourceType {
self.resource_type
}
#[inline]
pub fn resource_ext(&self) -> String {
self.resource_type.to_string()
}
#[inline]
pub(crate) fn add(&mut self, res: DRSResource) -> &mut DRSResource {
self.resources.push(res);
self.num_resources += 1;
self.resources.last_mut().expect("last_mut returned None?")
}
}
impl std::fmt::Debug for DRSTable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"DRSTable {{ resource_type: '{}', offset: {}, num_resources: {} }}",
self.resource_type.to_string(),
self.offset,
self.num_resources
)
}
}
#[derive(Debug)]
pub struct DRSResource {
pub id: u32,
offset: u32,
pub size: u32,
}
impl DRSResource {
#[inline]
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 })
}
#[inline]
fn write_to<W: Write>(&self, output: &mut W) -> Result<(), Error> {
output.write_u32::<LE>(self.id)?;
output.write_u32::<LE>(self.offset)?;
output.write_u32::<LE>(self.size)?;
Ok(())
}
}
pub type DRSTableIterator<'a> = slice::Iter<'a, DRSTable>;
pub type DRSResourceIterator<'a> = slice::Iter<'a, DRSResource>;
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
#[test]
fn it_works() -> anyhow::Result<()> {
let mut file = File::open("test.drs")?;
let drs = DRSReader::new(&mut file)?;
let mut expected = vec![
("js".parse()?, 1, 632),
("js".parse()?, 2, 452),
("js".parse()?, 3, 38),
("json".parse()?, 4, 710),
];
for table in drs.tables() {
for resource in table.resources() {
let content = drs.read_resource(&mut file, table.resource_type, resource.id)?;
assert_eq!(
expected.remove(0),
(table.resource_type, resource.id, content.len())
);
}
}
Ok(())
}
}