mod qualifier;
mod reader;
mod v1;
mod v2;
mod value;
use std::borrow::Cow;
use std::fmt;
use std::ops::Range;
pub use qualifier::{Qualifier, QualifierKey, Qualifiers};
use reader::Reader;
pub use value::{Attr, ResourceRef, ResourceType, ResourceValue, UnknownResourceType};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum ResourceIndexError {
#[error("unknown resource index version {version:?} (known: \"Restool <version>\", \"RestoolV2 <version>\")")]
UnknownVersion {
version: String,
},
#[error("{what} at {offset:#x} needs {needed} bytes, {available} are left")]
Truncated {
what: &'static str,
offset: usize,
needed: usize,
available: usize,
},
#[error("{what} offset {offset:#x} is outside the index of {len:#x} bytes")]
OutOfBounds {
what: &'static str,
offset: u32,
len: usize,
},
#[error("expected the tag {expected:?} at {offset:#x}, found {found:?}")]
BadTag {
expected: &'static str,
offset: usize,
found: [u8; 4],
},
#[error("the table lists resource {listed:#010x} at {offset:#x}, where {found:#010x} is stored")]
IdMismatch {
listed: u32,
found: u32,
offset: usize,
},
#[error("resource {id:#010x} has a value for qualifier set {config}, which the index does not define")]
UnknownQualifiers {
id: u32,
config: u32,
},
#[error("the tables describe more than {limit} values, more than an index of this size holds")]
TooManyValues {
limit: usize,
},
}
type Result<T> = std::result::Result<T, ResourceIndexError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum IndexFormat {
V1,
V2,
}
impl IndexFormat {
fn terminates_values(self) -> bool {
match self {
IndexFormat::V1 => true,
IndexFormat::V2 => false,
}
}
}
#[derive(Debug, Clone, Copy)]
struct Entry<'a> {
id: u32,
kind: ResourceType,
qualifiers: usize,
name: &'a [u8],
value: &'a [u8],
}
struct Entries<'a> {
entries: Vec<Entry<'a>>,
limit: usize,
}
impl<'a> Entries<'a> {
const MIN_BYTES_PER_VALUE: usize = 8;
fn for_index(bytes: &[u8]) -> Entries<'a> {
Entries { entries: Vec::new(), limit: bytes.len() / Self::MIN_BYTES_PER_VALUE }
}
fn push(&mut self, entry: Entry<'a>) -> Result<()> {
if self.entries.len() >= self.limit {
return Err(ResourceIndexError::TooManyValues { limit: self.limit });
}
self.entries.push(entry);
Ok(())
}
}
struct Tables<'a> {
qualifiers: Vec<Qualifiers>,
entries: Vec<Entry<'a>>,
}
#[derive(Debug, Clone)]
pub struct ResourceIndex<'a> {
version: Cow<'a, str>,
format: IndexFormat,
qualifiers: Vec<Qualifiers>,
entries: Vec<Entry<'a>>,
by_name: Vec<usize>,
}
impl<'a> ResourceIndex<'a> {
const VERSION_LEN: usize = 128;
pub fn parse(bytes: &'a [u8]) -> Result<ResourceIndex<'a>> {
let mut reader = Reader::new(bytes);
let version = reader.take(Self::VERSION_LEN, "version")?;
let version = String::from_utf8_lossy(version.split(|&b| b == 0).next().unwrap_or_default());
let format = match version.split(' ').next() {
Some("Restool") => IndexFormat::V1,
Some("RestoolV2") => IndexFormat::V2,
_ => return Err(ResourceIndexError::UnknownVersion { version: version.into_owned() }),
};
let Tables { qualifiers, mut entries } = match format {
IndexFormat::V1 => v1::read(bytes, reader)?,
IndexFormat::V2 => v2::read(bytes, reader)?,
};
entries.sort_by_key(|e| (e.id, e.qualifiers));
let mut by_name: Vec<usize> = (0..entries.len()).collect();
by_name.sort_by_key(|&i| (entries[i].kind.code(), entries[i].name, i));
Ok(ResourceIndex { version, format, qualifiers, entries, by_name })
}
pub fn version(&self) -> &str {
&self.version
}
pub fn format(&self) -> IndexFormat {
self.format
}
pub fn qualifiers(&self) -> &[Qualifiers] {
&self.qualifiers
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn iter(&self) -> Resources<'_> {
Resources { index: self, order: Order::ById(0..self.entries.len()) }
}
pub fn by_id(&self, id: u32) -> Resources<'_> {
let start = self.entries.partition_point(|e| e.id < id);
let end = start + self.entries[start..].partition_point(|e| e.id == id);
Resources { index: self, order: Order::ById(start..end) }
}
pub fn by_name(&self, kind: ResourceType, name: &str) -> Resources<'_> {
let key = (kind.code(), name.as_bytes());
let of = |&i: &usize| (self.entries[i].kind.code(), self.entries[i].name);
let start = self.by_name.partition_point(|i| of(i) < key);
let end = start + self.by_name[start..].partition_point(|i| of(i) == key);
Resources { index: self, order: Order::ByName(start..end) }
}
pub fn resolve(&self, reference: ResourceRef<'_>) -> Resources<'_> {
let named = self.by_name(reference.kind, reference.name);
if named.len() > 0 {
return named;
}
match reference.name.parse() {
Ok(id) if self.by_id(id).all(|r| r.kind() == reference.kind) => self.by_id(id),
_ => named,
}
}
}
impl<'r> IntoIterator for &'r ResourceIndex<'_> {
type Item = Resource<'r>;
type IntoIter = Resources<'r>;
fn into_iter(self) -> Resources<'r> {
self.iter()
}
}
#[derive(Debug, Clone)]
enum Order {
ById(Range<usize>),
ByName(Range<usize>),
}
#[derive(Debug, Clone)]
pub struct Resources<'r> {
index: &'r ResourceIndex<'r>,
order: Order,
}
impl<'r> Resources<'r> {
fn resource(&self, position: usize) -> Resource<'r> {
let position = match self.order {
Order::ById(_) => position,
Order::ByName(_) => self.index.by_name[position],
};
Resource { index: self.index, entry: &self.index.entries[position] }
}
fn range(&mut self) -> &mut Range<usize> {
match &mut self.order {
Order::ById(range) | Order::ByName(range) => range,
}
}
}
impl<'r> Iterator for Resources<'r> {
type Item = Resource<'r>;
fn next(&mut self) -> Option<Resource<'r>> {
let position = self.range().next()?;
Some(self.resource(position))
}
fn size_hint(&self) -> (usize, Option<usize>) {
match &self.order {
Order::ById(range) | Order::ByName(range) => range.size_hint(),
}
}
}
impl DoubleEndedIterator for Resources<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
let position = self.range().next_back()?;
Some(self.resource(position))
}
}
impl ExactSizeIterator for Resources<'_> {}
#[derive(Debug, Clone, Copy)]
pub struct Resource<'r> {
index: &'r ResourceIndex<'r>,
entry: &'r Entry<'r>,
}
impl<'r> Resource<'r> {
pub fn id(&self) -> u32 {
self.entry.id
}
pub fn kind(&self) -> ResourceType {
self.entry.kind
}
pub fn name(&self) -> Cow<'r, str> {
String::from_utf8_lossy(self.entry.name)
}
pub fn qualifiers(&self) -> &'r Qualifiers {
&self.index.qualifiers[self.entry.qualifiers]
}
pub fn raw_value(&self) -> &'r [u8] {
self.entry.value
}
pub fn value(&self) -> ResourceValue<'r> {
ResourceValue::decode(self.entry.kind, self.entry.value, self.index.format.terminates_values())
}
}
impl fmt::Display for Resource<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:#010x} {}/{} [{}] {}", self.id(), self.kind(), self.name(), self.qualifiers(), self.value())
}
}
#[cfg(test)]
mod tests;