use std::io::Cursor;
use byteorder::{LittleEndian, ReadBytesExt};
use failure::{bail, Error, ResultExt};
use log::warn;
use crate::chunks::{
Chunk, ChunkLoaderStream, PackageWrapper, ResourceWrapper, StringTableWrapper,
TableTypeWrapper, TypeSpecWrapper, XmlNamespaceEndWrapper, XmlNamespaceStartWrapper,
XmlTagEndWrapper, XmlTagStartWrapper, XmlTextWrapper,
};
pub mod model;
mod print;
mod xml;
pub use self::{
model::{ModelVisitor, RefPackage, Resources},
xml::XmlVisitor,
};
pub trait ChunkVisitor<'a> {
fn visit_string_table(&mut self, _string_table: StringTableWrapper<'a>, _origin: Origin) {}
fn visit_package(&mut self, _package: PackageWrapper<'a>) {}
fn visit_table_type(&mut self, _table_type: TableTypeWrapper<'a>) {}
fn visit_type_spec(&mut self, _type_spec: TypeSpecWrapper<'a>) {}
fn visit_xml_namespace_start(&mut self, _namespace_start: XmlNamespaceStartWrapper<'a>) {}
fn visit_xml_namespace_end(&mut self, _namespace_end: XmlNamespaceEndWrapper<'a>) {}
fn visit_xml_tag_start(&mut self, _tag_start: XmlTagStartWrapper<'a>) {}
fn visit_xml_tag_end(&mut self, _tag_end: XmlTagEndWrapper<'a>) {}
fn visit_xml_text(&mut self, _text: XmlTextWrapper<'a>) {}
fn visit_resource(&mut self, _resource: ResourceWrapper<'a>) {}
}
#[derive(Debug, Copy, Clone)]
pub struct Executor;
impl Executor {
pub fn arsc<'a, V: ChunkVisitor<'a>>(buffer: &'a [u8], visitor: &mut V) -> Result<(), Error> {
let mut cursor = Cursor::new(buffer);
let token = cursor
.read_u16::<LittleEndian>()
.context("error reading first token")?;
if token != 0x2 {
bail!("file does not start with ARSC token: {:X}", token);
}
let header_size = cursor
.read_u16::<LittleEndian>()
.context("error reading header size")?;
let _chunk_size = cursor
.read_u32::<LittleEndian>()
.context("error reading chunk size")?;
let _package_amount = cursor
.read_u32::<LittleEndian>()
.context("error reading package amount")?;
cursor.set_position(u64::from(header_size));
let stream = ChunkLoaderStream::new(cursor);
let mut origin = Origin::Global;
for c in stream {
match c.context("error reading next chunk")? {
Chunk::StringTable(stw) => {
visitor.visit_string_table(stw, origin);
origin = Origin::next(origin);
}
Chunk::Package(pw) => {
visitor.visit_package(pw);
}
Chunk::TableType(ttw) => {
visitor.visit_table_type(ttw);
}
Chunk::TableTypeSpec(tsw) => {
visitor.visit_type_spec(tsw);
}
_ => {
warn!("Not expected chunk on ARSC");
}
}
}
Ok(())
}
pub fn xml<'a, V: ChunkVisitor<'a>>(
mut cursor: Cursor<&'a [u8]>,
visitor: &mut V,
) -> Result<(), Error> {
let token = cursor
.read_u16::<LittleEndian>()
.context("error reading first token")?;
if token != 0x3 {
bail!("document does not start with XML token: {:X}", token);
}
let header_size = cursor
.read_u16::<LittleEndian>()
.context("error reading header size")?;
let _chunk_size = cursor
.read_u32::<LittleEndian>()
.context("error reading chunk size")?;
cursor.set_position(u64::from(header_size));
let stream = ChunkLoaderStream::new(cursor);
for c in stream {
match c.context("error reading next chunk")? {
Chunk::StringTable(stw) => {
visitor.visit_string_table(stw, Origin::Global);
}
Chunk::XmlNamespaceStart(xnsw) => {
visitor.visit_xml_namespace_start(xnsw);
}
Chunk::XmlNamespaceEnd(xnsw) => {
visitor.visit_xml_namespace_end(xnsw);
}
Chunk::XmlTagStart(xnsw) => {
visitor.visit_xml_tag_start(xnsw);
}
Chunk::XmlTagEnd(xnsw) => {
visitor.visit_xml_tag_end(xnsw);
}
Chunk::XmlText(xsnw) => {
visitor.visit_xml_text(xsnw);
}
Chunk::Resource(rw) => {
visitor.visit_resource(rw);
}
_ => (),
}
}
Ok(())
}
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub enum Origin {
Global,
Spec,
Entries,
}
impl Origin {
pub fn next(origin: Self) -> Self {
match origin {
Self::Global => Self::Spec,
Self::Spec => Self::Entries,
Self::Entries => Self::Global,
}
}
}