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