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