1use crate::bytes_utils::read_integer;
2use crate::error::{DecompressionSnafu, IoSnafu, JImageError, Result, Utf8Snafu};
3use crate::header::Header;
4use crate::resource_header::ResourceHeader;
5use crate::resource_name::{ResourceName, ResourceNamesIter};
6use memchr::memchr;
7use memmap2::Mmap;
8use snafu::ResultExt;
9use std::borrow::Cow;
10use std::fs::File;
11use std::io::Read;
12use std::path::Path;
13#[derive(Debug)]
34pub struct JImage {
35 mmap: Mmap,
36 header: Header,
37}
38
39#[repr(u8)]
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42enum AttributeKind {
43 END,
44 MODULE,
45 PARENT,
46 BASE,
47 EXTENSION,
48 OFFSET,
49 COMPRESSED,
50 UNCOMPRESSED,
51 COUNT,
52}
53
54impl TryFrom<u8> for AttributeKind {
55 type Error = JImageError;
56
57 fn try_from(value: u8) -> Result<Self> {
58 if value >= AttributeKind::COUNT as u8 {
59 Err(JImageError::Internal {
60 value: format!("Invalid attribute kind: {}", value),
61 })
62 } else {
63 unsafe { Ok(std::mem::transmute(value)) }
64 }
65 }
66}
67
68#[derive(Debug, Clone, PartialEq)]
69pub(crate) enum Endianness {
70 Big,
71 Little,
72}
73
74const HASH_MULTIPLIER: u32 = 0x01000193;
75const SUPPORTED_DECOMPRESSOR: &str = "zip";
76
77impl JImage {
78 pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
80 let file = File::open(path.as_ref()).context(IoSnafu {
81 path: path.as_ref().to_path_buf(),
82 })?;
83 let mmap = unsafe {
84 Mmap::map(&file).context(IoSnafu {
85 path: path.as_ref().to_path_buf(),
86 })?
87 };
88 let header = Header::from_bytes(&mmap)?;
89
90 Ok(Self { mmap, header })
91 }
92
93 pub fn find_resource(&self, name: &str) -> Result<Option<Cow<[u8]>>> {
95 let Some(offset_index) = self.find_offset_index(name)? else {
97 return Ok(None);
98 };
99
100 let attribute_index = self.offset_value(offset_index)?;
102 let attribute = self.attributes(attribute_index)?;
103
104 if !self.verify(&attribute, name)? {
106 return Ok(None); }
108
109 self.get_resource(&attribute)
110 }
111
112 pub fn resource_names_iter(&self) -> ResourceNamesIter<'_> {
114 ResourceNamesIter::new(self)
115 }
116
117 pub fn resource_names(&self) -> Result<Vec<ResourceName<'_>>> {
119 self.resource_names_iter().collect()
120 }
121
122 pub(crate) fn resource_at_index(&self, idx: usize) -> Result<Option<ResourceName<'_>>> {
124 let offset_index = self.offset_value(idx as i32)?;
125 let attribute = self.attributes(offset_index)?;
126 let module = self.get_str_for_attribute(attribute, AttributeKind::MODULE)?;
127 if matches!(module.as_ref(), "" | "modules" | "packages") {
128 return Ok(None);
129 }
130 let parent = self.get_str_for_attribute(attribute, AttributeKind::PARENT)?;
131 let base = self.get_str_for_attribute(attribute, AttributeKind::BASE)?;
132 let extension = self.get_str_for_attribute(attribute, AttributeKind::EXTENSION)?;
133 Ok(Some(ResourceName {
134 module,
135 parent,
136 base,
137 extension,
138 }))
139 }
140
141 pub(crate) fn items_count(&self) -> usize {
143 self.header.items_count() as usize
144 }
145
146 fn get_str_for_attribute(&self, attribute: [u64; 8], kind: AttributeKind) -> Result<Cow<str>> {
147 let offset = attribute[kind as usize] as usize;
148 let value = self.get_string(offset)?;
149 Ok(Cow::Borrowed(value))
150 }
151
152 fn find_offset_index(&self, name: &str) -> Result<Option<i32>> {
154 let items_count = self.header.items_count() as i32;
155 let hash = Self::hash_code(name, HASH_MULTIPLIER as i32)?;
156 let redirect_index = hash % items_count;
157 let redirected_val = self.redirect_value(redirect_index)?;
158
159 match redirected_val {
160 val if val < 0 => Ok(Some(-1 - val)),
161 val if val > 0 => Ok(Some(Self::hash_code(name, val)? % items_count)),
162 _ => Ok(None),
163 }
164 }
165
166 fn hash_code(string: &str, seed: i32) -> Result<i32> {
168 let mut current_hash = seed as u32;
169 for &byte in string.as_bytes() {
170 current_hash = current_hash.overflowing_mul(HASH_MULTIPLIER).0 ^ byte as u32;
171 }
172 Ok((current_hash & 0x7FFFFFFF) as i32)
173 }
174
175 fn redirect_value(&self, index: i32) -> Result<i32> {
176 let offset = self.header.redirect(index as usize);
177 read_integer(&self.mmap, offset, self.header.endianness())
178 }
179
180 fn offset_value(&self, index: i32) -> Result<i32> {
181 let offset = self.header.offset(index as usize);
182 read_integer(&self.mmap, offset, self.header.endianness())
183 }
184
185 fn get_string(&self, index: usize) -> Result<&str> {
186 let offset = self.header.strings(index);
187 let string_slice = &self.mmap[offset..];
188 let len = memchr(0, string_slice).ok_or(JImageError::Internal {
189 value: format!("Failed to find null-terminator in string starting from {offset}"),
190 })?;
191 let slice = &self.mmap[offset..offset + len];
192 let value = std::str::from_utf8(slice).context(Utf8Snafu {
193 invalid_data: slice.to_vec(),
194 })?;
195
196 Ok(value)
197 }
198
199 fn attributes(&self, index: i32) -> Result<[u64; 8]> {
200 let offset = self.header.attributes(index as usize);
201
202 let mut attributes = [0u64; 8];
203 let mut pos = offset;
204 loop {
205 let value = &self.mmap[pos];
206
207 let kind = value >> 3;
208 let kind = AttributeKind::try_from(kind)?;
209 if kind == AttributeKind::END {
210 break;
211 }
212
213 let len = (value & 0b0000_0111) + 1;
214 let value = self.get_attribute_value(pos + 1, len)?;
215 pos += 1 + len as usize;
216
217 attributes[kind as usize] = value;
218 }
219
220 Ok(attributes)
221 }
222
223 fn get_resource(&self, attributes: &[u64; 8]) -> Result<Option<Cow<[u8]>>> {
224 let offset = attributes[AttributeKind::OFFSET as usize] as usize;
225 let compressed_size = attributes[AttributeKind::COMPRESSED as usize] as usize;
226 let uncompressed_size = attributes[AttributeKind::UNCOMPRESSED as usize] as usize;
227
228 let start = self.header.data(offset);
229 if compressed_size == 0 {
230 Ok(Some(Cow::Borrowed(
231 &self.mmap[start..start + uncompressed_size],
232 )))
233 } else {
234 let compressed_data = &self.mmap[start..start + compressed_size];
235 let resource_header = ResourceHeader::from_bytes(compressed_data)?;
236
237 let decompressor_name_offset = resource_header.decompressor_name_offset();
238 let decompressor_name = self.get_string(decompressor_name_offset as usize)?;
239 if decompressor_name != SUPPORTED_DECOMPRESSOR {
240 return Err(JImageError::UnsupportedDecompressor {
241 decompressor_name: decompressor_name.to_string(),
242 });
243 }
244
245 let from = start + ResourceHeader::SIZE;
246 let to = from + resource_header.compressed_size() as usize;
247 let compressed_payload = &self.mmap[from..to];
248 let mut zlib_decoder = flate2::read::ZlibDecoder::new(compressed_payload);
249 let mut uncompressed_payload = vec![0u8; resource_header.uncompressed_size() as usize];
250 zlib_decoder
251 .read_exact(&mut uncompressed_payload)
252 .context(DecompressionSnafu)?;
253
254 Ok(Some(Cow::Owned(uncompressed_payload)))
255 }
256 }
257
258 fn verify(&self, attributes: &[u64; 8], full_name: &str) -> Result<bool> {
261 let parts_to_check = [
262 (AttributeKind::MODULE, "/"),
263 (AttributeKind::PARENT, "/"),
264 (AttributeKind::BASE, "/"),
265 (AttributeKind::EXTENSION, "."),
266 ];
267
268 let mut remaining_name = full_name;
269 for (kind, prefix) in &parts_to_check {
270 let offset = attributes[*kind as usize] as usize;
271 let part = self.get_string(offset)?;
272
273 if !part.is_empty() {
274 remaining_name = if let Some(stripped) = remaining_name.strip_prefix(prefix) {
275 stripped
276 } else {
277 return Ok(false);
278 };
279
280 remaining_name = if let Some(stripped) = remaining_name.strip_prefix(part) {
281 stripped
282 } else {
283 return Ok(false);
284 };
285 }
286 }
287
288 Ok(remaining_name.is_empty())
289 }
290
291 fn get_attribute_value(&self, pos: usize, len: u8) -> Result<u64> {
292 if !(1..=8).contains(&len) {
293 return Err(JImageError::Internal {
294 value: format!("Invalid attribute length: {len}"),
295 });
296 }
297
298 let mut value = 0u64;
299 for i in 0..len as usize {
300 value <<= 8;
301 value |= self.mmap[i + pos] as u64;
302 }
303
304 Ok(value)
305 }
306}