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