use crate::crypto::{MapleCipher, MapleVersion};
use crate::extra::bundle::Bundle;
use crate::extra::cache::EntryCache;
use crate::extra::iter::ComponentIter;
use crate::{
AccessorBuilder, AccessorOpt, Directories, Directory, EntryKind, Error, ImageKind,
PlainPrimitive, PlainProperties, PlainProperty, Primitive, Properties, Property, PropertyKind,
TryFromBuilder, error,
};
use std::io::SeekFrom;
use std::path::Path;
enum EntryBuilder {
Value,
File(Box<dyn AccessorBuilder>),
Bundle(Bundle),
PlainProperties(PlainProperties),
Complex(Box<dyn AccessorBuilder>, Bundle),
}
#[derive(Debug, Clone)]
pub enum EntryPrimitive {
Nil,
Int16(i16),
Int32(i32),
Int64(i64),
Float32(f32),
Float64(f64),
String(String),
}
#[derive(Debug, Clone)]
pub enum EntryValue {
Kind(EntryKind),
Primitive(EntryPrimitive),
}
pub struct Entry {
name: String,
opt: AccessorOpt,
value: EntryValue,
builder: EntryBuilder,
cipher: Box<dyn MapleCipher>,
}
impl Entry {
pub fn from_path<P: AsRef<Path>>(
path: P,
cipher: Box<dyn MapleCipher>,
version: MapleVersion,
no_version: bool,
) -> Result<Self, Error> {
let path = path.as_ref();
let file_name = path
.file_name()
.and_then(|f| f.to_str())
.ok_or(error::io_err_invalid_input())?;
let bundle = Bundle::from_path(path, version, no_version)?.ok_or(Error::InvalidArgument)?;
Ok(Entry {
name: file_name.to_owned(),
cipher,
opt: AccessorOpt {
offset: 0,
ver_hash: version.hash(),
parent_offset: 0,
},
value: EntryValue::Kind(EntryKind::Folder),
builder: EntryBuilder::Bundle(bundle),
})
}
#[inline]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[inline]
pub fn value(&self) -> &EntryValue {
&self.value
}
#[inline]
pub fn has_children(&self) -> bool {
match &self.value {
EntryValue::Kind(k) => match k {
EntryKind::Image(img) => matches!(
img,
ImageKind::Canvas | ImageKind::Video | ImageKind::Sound | ImageKind::RawData
),
EntryKind::Folder | EntryKind::Property(_) => true,
},
EntryValue::Primitive(_) => false,
}
}
#[inline]
pub fn builder(&self) -> Option<&dyn AccessorBuilder> {
match &self.builder {
EntryBuilder::File(f) => Some(f.as_ref()),
_ => None,
}
}
#[inline]
pub fn accessor_opt(&self) -> AccessorOpt {
self.opt
}
#[inline]
pub fn try_get(&self, name: &str) -> Result<Option<Entry>, Error> {
Ok(self.try_iter()?.find(|e| e.name == name))
}
#[inline]
pub fn get_exact(&self, name: &str) -> Entry {
self.get(name).unwrap_or_else(|| panic!("entry not exists"))
}
#[inline]
pub fn get(&self, name: &str) -> Option<Entry> {
self.try_get(name)
.unwrap_or_else(|e| panic!("get entry failed: {e}"))
}
pub fn try_get_by_path<P: AsRef<Path>>(&self, path: P) -> Result<Option<Entry>, Error> {
let path = path.as_ref();
let mut components = ComponentIter::from(path.components());
let Some(first) = components.next() else {
return Ok(None);
};
let mut cursor = self.try_get(first)?;
for name in components {
if let Some(entry) = cursor {
cursor = entry.try_get(name)?;
} else {
return Ok(None);
}
}
Ok(cursor)
}
#[inline]
pub fn get_by_path<P: AsRef<Path>>(&self, path: P) -> Option<Entry> {
self.try_get_by_path(path)
.unwrap_or_else(|e| panic!("get_by_path: {e}"))
}
#[inline]
pub fn get_by_path_exact<P: AsRef<Path>>(&self, path: P) -> Entry {
self.get_by_path(path)
.unwrap_or_else(|| panic!("path not exists"))
}
#[inline]
pub fn try_to<'a, T: TryFrom<&'a Entry, Error = impl Into<Error>>>(
&'a self,
) -> Result<T, Error> {
T::try_from(self).map_err(|e| e.into())
}
#[inline]
pub fn to<'a, T: TryFrom<&'a Entry, Error = impl Into<Error>>>(&'a self) -> T {
self.try_to().unwrap_or_else(|e| panic!("to: {e}"))
}
fn iter_image<'a>(
&'a self,
img: ImageKind,
builder: &'a dyn AccessorBuilder,
) -> Result<Box<dyn Iterator<Item = Entry> + 'a>, Error> {
match img {
ImageKind::Canvas | ImageKind::Video => {
let mut accessor = builder.accessor();
accessor.seek(SeekFrom::Start(self.opt.offset as u64));
let Some(props) = Properties::builtin(self.opt, accessor.as_mut())? else {
return Ok(Box::new(std::iter::empty()));
};
Ok(Box::new(
props
.into_inner()
.into_iter()
.map(|p| property_to_entry(p, self, builder)),
))
}
ImageKind::Sound | ImageKind::RawData => {
let mut accessor = builder.accessor();
accessor.seek(SeekFrom::Start(self.opt.offset as u64));
let Some(props) = Properties::optional(self.opt, accessor.as_mut())? else {
return Ok(Box::new(std::iter::empty()));
};
Ok(Box::new(
props
.into_inner()
.into_iter()
.map(|p| property_to_entry(p, self, builder)),
))
}
_ => Ok(Box::new(std::iter::empty())),
}
}
fn iter_property<'a>(
&'a self,
props: PropertyKind,
builder: &'a dyn AccessorBuilder,
) -> Result<Box<dyn Iterator<Item = Entry> + 'a>, Error> {
match props {
PropertyKind::Plain => Ok(Box::new(
PlainProperties::try_from_builder(self.opt, builder)?
.into_inner()
.into_iter()
.map(|p| plain_property_to_entry(p, self)),
)),
PropertyKind::Encode => Ok(Box::new(
Properties::try_from_builder(self.opt, builder)?
.into_inner()
.into_iter()
.map(|p| property_to_entry(p, self, builder)),
)),
}
}
pub fn try_iter<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Entry> + 'a>, Error> {
let (builder, bundle) = match &self.builder {
EntryBuilder::File(builder) => match self.value {
EntryValue::Kind(kind) => match kind {
EntryKind::Folder => (Some(builder), None),
EntryKind::Image(img) => {
return self.iter_image(img, builder.as_ref());
}
EntryKind::Property(props) => {
return self.iter_property(props, builder.as_ref());
}
},
_ => {
return Ok(Box::new(std::iter::empty()));
}
},
EntryBuilder::Bundle(bundle) => match self.value {
EntryValue::Kind(EntryKind::Folder) => (None, Some(bundle)),
_ => {
return Ok(Box::new(std::iter::empty()));
}
},
EntryBuilder::Complex(builder, bundle) => match self.value {
EntryValue::Kind(kind) => match kind {
EntryKind::Folder => (Some(builder), Some(bundle)),
EntryKind::Image(img) => {
return self.iter_image(img, builder.as_ref());
}
EntryKind::Property(props) => {
return self.iter_property(props, builder.as_ref());
}
},
_ => {
return Ok(Box::new(std::iter::empty()));
}
},
EntryBuilder::PlainProperties(plain_props) => {
return Ok(Box::new(
plain_props
.iter()
.cloned()
.map(|p| plain_property_to_entry(p, self)),
));
}
EntryBuilder::Value => return Ok(Box::new(std::iter::empty())),
};
let mut entries = if let Some(builder) = builder {
directories_to_entries(self, self.opt, builder.as_ref(), bundle)?
} else {
vec![]
};
if let Some(bundle) = bundle {
for (opt, builder) in bundle.builders(self.cipher.as_ref())? {
entries.extend(directories_to_entries(
self,
opt,
builder.as_ref(),
Some(bundle),
)?);
}
}
Ok(Box::new(entries.into_iter()))
}
#[inline]
pub fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = Entry> + 'a> {
self.try_iter().unwrap_or_else(|e| panic!("iter: {e}"))
}
#[inline]
pub fn try_into_cache(self) -> Result<EntryCache, Error> {
EntryCache::try_from(self)
}
#[inline]
pub fn into_cache(self) -> EntryCache {
EntryCache::try_from(self).unwrap_or_else(|e| panic!("into_cache error: {e}"))
}
}
#[inline]
fn directories_to_entries(
entry: &Entry,
opt: AccessorOpt,
builder: &dyn AccessorBuilder,
bundle: Option<&Bundle>,
) -> Result<Vec<Entry>, Error> {
Ok(Directories::try_from_builder(opt, builder)?
.into_inner()
.into_iter()
.map(|x| {
let sub = bundle.and_then(|bundle| bundle.load_by_name(x.name.as_str()));
directory_to_entry(x, entry, builder, sub)
})
.collect::<Vec<_>>())
}
#[inline]
fn directory_to_entry(
dir: Directory,
entry: &Entry,
builder: &dyn AccessorBuilder,
bundle: Option<Bundle>,
) -> Entry {
Entry {
name: dir.name,
opt: entry.opt.clone_with_parent(dir.offset, dir.parent_offset),
value: EntryValue::Kind(dir.kind),
builder: match bundle {
Some(b) => EntryBuilder::Complex(builder.clone_boxed(), b),
None => EntryBuilder::File(builder.clone_boxed()),
},
cipher: entry.cipher.clone_boxed(),
}
}
#[inline]
fn property_to_entry(prop: Property, entry: &Entry, builder: &dyn AccessorBuilder) -> Entry {
let mut offset = prop.offset;
let value = match prop.value {
Primitive::Nil => EntryValue::Primitive(EntryPrimitive::Nil),
Primitive::Int16(i) => EntryValue::Primitive(EntryPrimitive::Int16(i)),
Primitive::Int32(i) => EntryValue::Primitive(EntryPrimitive::Int32(i)),
Primitive::Int64(i) => EntryValue::Primitive(EntryPrimitive::Int64(i)),
Primitive::Float32(f) => EntryValue::Primitive(EntryPrimitive::Float32(f)),
Primitive::Float64(f) => EntryValue::Primitive(EntryPrimitive::Float64(f)),
Primitive::String(s) => EntryValue::Primitive(EntryPrimitive::String(s)),
Primitive::Image(img) => {
offset = img.offset;
EntryValue::Kind(img.kind)
}
};
Entry {
name: prop.name.to_owned(),
opt: entry.opt.clone_with(offset),
value,
builder: EntryBuilder::File(builder.clone_boxed()),
cipher: entry.cipher.clone_boxed(),
}
}
#[inline]
fn plain_property_to_entry(prop: PlainProperty, entry: &Entry) -> Entry {
match prop.value {
PlainPrimitive::Value(val) => Entry {
name: prop.name,
opt: entry.opt,
value: EntryValue::Primitive(EntryPrimitive::String(val)),
builder: EntryBuilder::Value,
cipher: entry.cipher.clone_boxed(),
},
PlainPrimitive::Nested(nested) => Entry {
name: prop.name,
opt: entry.opt,
value: EntryValue::Kind(EntryKind::Property(PropertyKind::Plain)),
builder: EntryBuilder::PlainProperties(nested),
cipher: entry.cipher.clone_boxed(),
},
}
}