pelite\resources/
find.rs

1/*!
2Resources Find API.
3*/
4
5use std::{fmt, str};
6
7#[cfg(feature = "std")]
8use std::{error, path::Path};
9
10use super::{Resources, Directory, Entry, Name, DataEntry};
11
12//------------------------------------------------
13
14/// Find error.
15#[derive(Copy, Clone, Debug, Eq, PartialEq)]
16pub enum FindError {
17	/// An error happened when reading the underlying resources.
18	///
19	/// This error indicates the resources are corrupt.
20	Pe(crate::Error),
21	/// The resources work with UTF-16 path names.
22	///
23	/// For this to work the given path must be valid unicode for the path comparison to make sense.
24	///
25	/// This error means the given path contained non-unicode parts.
26	Bad8Path,
27	/// The requested data entry or directory doesn't exist.
28	NotFound,
29	/// Paths from the resources root must start with a `/` or `\`.
30	NoRootPath,
31	/// Encountered a data entry when expecting a directory.
32	///
33	/// This error means the given path contained a directory name which is actually a data entry.
34	UnDataEntry,
35	/// Encountered a directory when expecting a data entry.
36	UnDirectory,
37}
38impl FindError {
39	/// Returns a simple string representation of the error.
40	pub fn to_str(self) -> &'static str {
41		match self {
42			FindError::Pe(err) => err.to_str(),
43			FindError::Bad8Path => "invalid utf8 path",
44			FindError::NotFound => "entry not found",
45			FindError::NoRootPath => "missing '/' root",
46			FindError::UnDataEntry => "unexpected data entry",
47			FindError::UnDirectory => "unexpected directory",
48		}
49	}
50}
51impl From<crate::Error> for FindError {
52	fn from(err: crate::Error) -> FindError {
53		FindError::Pe(err)
54	}
55}
56impl From<str::Utf8Error> for FindError {
57	fn from(_err: str::Utf8Error) -> FindError {
58		FindError::Pe(crate::Error::Encoding)
59	}
60}
61impl fmt::Display for FindError {
62	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
63		self.to_str().fmt(f)
64	}
65}
66#[cfg(feature = "std")]
67impl error::Error for FindError {
68	fn description(&self) -> &str {
69		self.to_str()
70	}
71	fn cause(&self) -> Option<&dyn error::Error> {
72		self.source()
73	}
74	fn source(&self) -> Option<&(dyn error::Error + 'static)> {
75		match self {
76			FindError::Pe(err) => Some(err),
77			_ => None,
78		}
79	}
80}
81
82//------------------------------------------------
83
84impl<'a> Resources<'a> {
85	/// Finds a resource by its type and name.
86	pub fn find_resource(&self, path: &[Name<'_>; 2]) -> Result<&'a [u8], FindError> {
87		Ok(self.root()?.get_dir(path[0])?.get_dir(path[1])?.first_data()?.bytes()?)
88	}
89	/// Finds the language directory for a resource with given type and name.
90	pub fn find_resources(&self, path: &[Name<'_>; 2]) -> Result<Directory<'a>, FindError> {
91		self.root()?.get_dir(path[0])?.get_dir(path[1])
92	}
93	/// Finds the resource with specified type, name and language.
94	pub fn find_resource_ex(&self, path: &[Name<'_>; 3]) -> Result<&'a [u8], FindError> {
95		Ok(self.root()?.get_dir(path[0])?.get_dir(path[1])?.get_data(path[2])?.bytes()?)
96	}
97	/// Gets the Version Information.
98	pub fn version_info(&self) -> Result<super::version_info::VersionInfo<'a>, FindError> {
99		let bytes = self.find_resource(&[Name::VERSION, Name::Id(1)])?;
100		let version_info = super::version_info::VersionInfo::try_from(bytes)?;
101		Ok(version_info)
102	}
103	/// Gets the Application Manifest.
104	pub fn manifest(&self) -> Result<&'a str, FindError> {
105		// Ok, new assumption: just take whatever we can find in the Manifest directory
106		let bytes = self.root()?.get_dir(Name::MANIFEST)?.first_dir()?.first_data()?.bytes()?;
107		let manifest = str::from_utf8(bytes)?;
108		Ok(manifest)
109	}
110	/// Gets the icons.
111	pub fn icons(&self) -> impl 'a + Iterator<Item = Result<(Name<'a>, super::group::GroupIcon<'a>), FindError>> + Clone {
112		let resources = *self;
113		let icons = self.root().map_err(FindError::Pe)
114			.and_then(|root| root.get_dir(Name::GROUP_ICON));
115
116		icons.into_iter().flat_map(move |icons| icons.entries().map(move |de| {
117			let name = de.name()?;
118			// A lot of assumptions being made here...
119			let bytes = de.entry()?.dir().ok_or(FindError::UnDataEntry)?.first_data()?.bytes()?;
120			let group_icon = super::group::GroupIcon::new(resources, bytes)?;
121			Ok((name, group_icon))
122		}))
123	}
124	/// Gets the cursors.
125	pub fn cursors(&self) -> impl 'a + Iterator<Item = Result<(Name<'a>, super::group::GroupCursor<'a>), FindError>> + Clone {
126		let resources = *self;
127		let cursors = self.root().map_err(FindError::Pe)
128			.and_then(|root| root.get_dir(Name::GROUP_CURSOR));
129
130		cursors.into_iter().flat_map(move |cursors| cursors.entries().map(move |de| {
131			let name = de.name()?;
132			// A lot of assumptions being made here...
133			let bytes = de.entry()?.dir().ok_or(FindError::UnDataEntry)?.first_data()?.bytes()?;
134			let group_cursor = super::group::GroupCursor::new(resources, bytes)?;
135			Ok((name, group_cursor))
136		}))
137	}
138}
139impl<'a> Directory<'a> {
140	/// Looks up the entry by name.
141	pub fn get(&self, name: Name<'_>) -> Result<Entry<'a>, FindError> {
142		self.entries().find(|de| de.name() == Ok(name)).ok_or(FindError::NotFound)?.entry().map_err(FindError::Pe)
143	}
144	/// Looks up the data entry by name.
145	pub fn get_data(&self, name: Name<'_>) -> Result<DataEntry<'a>, FindError> {
146		self.entries().find(|de| de.name() == Ok(name)).ok_or(FindError::NotFound)?.entry()?.data().ok_or(FindError::UnDirectory)
147	}
148	/// Looks up the directory by name.
149	pub fn get_dir(&self, name: Name<'_>) -> Result<Directory<'a>, FindError> {
150		self.entries().find(|de| de.name() == Ok(name)).ok_or(FindError::NotFound)?.entry()?.dir().ok_or(FindError::UnDataEntry)
151	}
152	/// Gets the first entry.
153	pub fn first(&self) -> Result<Entry<'a>, FindError> {
154		self.entries().next().ok_or(FindError::NotFound)?.entry().map_err(FindError::Pe)
155	}
156	/// Gets the first data entry.
157	pub fn first_data(&self) -> Result<DataEntry<'a>, FindError> {
158		self.entries().next().ok_or(FindError::NotFound)?.entry()?.data().ok_or(FindError::UnDirectory)
159	}
160	/// Gets the first directory.
161	pub fn first_dir(&self) -> Result<Directory<'a>, FindError> {
162		self.entries().next().ok_or(FindError::NotFound)?.entry()?.dir().ok_or(FindError::UnDataEntry)
163	}
164}
165
166//------------------------------------------------
167
168#[cfg(feature = "std")]
169impl<'a> Resources<'a> {
170	/// Finds a file or directory by its path.
171	pub fn find<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<Entry<'a>, FindError> {
172		self.find_internal(path.as_ref())
173	}
174	/// Finds a file by its path.
175	pub fn find_data<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<DataEntry<'a>, FindError> {
176		self.find(path).and_then(|e| e.data().ok_or(FindError::UnDirectory))
177	}
178	/// Finds a directory by its path.
179	pub fn find_dir<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<Directory<'a>, FindError> {
180		self.find(path).and_then(|e| e.dir().ok_or(FindError::UnDataEntry))
181	}
182	fn find_internal(&self, path: &Path) -> Result<Entry<'a>, FindError> {
183		let mut iter = path.iter();
184		if let Some(slash) = iter.next() {
185			// Not an absolute path
186			if slash != "/" && slash != "\\" {
187				Err(FindError::NoRootPath)
188			}
189			// Find the path in the root
190			else {
191				(*self).root()?.find_internal(iter.as_path())
192			}
193		}
194		else {
195			// The path is empty
196			Err(FindError::NotFound)
197		}
198	}
199}
200#[cfg(feature = "std")]
201impl<'a> Directory<'a> {
202	/// Finds a file or directory by its path.
203	pub fn find<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<Entry<'a>, FindError> {
204		self.find_internal(path.as_ref())
205	}
206	/// Finds a file by its path.
207	pub fn find_data<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<DataEntry<'a>, FindError> {
208		self.find(path).and_then(|e| e.data().ok_or(FindError::UnDirectory))
209	}
210	/// Finds a directory by its path.
211	pub fn find_dir<P: AsRef<Path> + ?Sized>(&self, path: &P) -> Result<Directory<'a>, FindError> {
212		self.find(path).and_then(|e| e.dir().ok_or(FindError::UnDataEntry))
213	}
214	fn find_internal(&self, path: &Path) -> Result<Entry<'a>, FindError> {
215		let mut entry = Entry::Directory(*self);
216		'parts: for part in path {
217			// The names of resources are UTF16
218			let name = Name::Str(part.to_str().ok_or(FindError::Bad8Path)?);
219			match entry {
220				Entry::Directory(dir) => {
221					// Find a child with matching name for this part of the path
222					for child in dir.entries() {
223						if child.name() == Ok(name) {
224							entry = child.entry()?;
225							continue 'parts;
226						}
227					}
228					return Err(FindError::NotFound);
229				},
230				Entry::DataEntry(_) => {
231					return Err(FindError::UnDataEntry);
232				},
233			};
234		}
235		Ok(entry)
236	}
237}