1use crate::format::{PackFooter, FOOTER_SIZE};
12use crate::packer::{read_footer_from_sidecar, sidecar_path_for};
13use std::path::{Path, PathBuf};
14
15pub enum PackedMode {
17 #[cfg(target_os = "macos")]
19 Section {
20 manifest: Box<crate::format::PackManifest>,
22 checksum: u32,
24 assets_ptr: *const u8,
26 assets_size: usize,
28 },
29 Embedded {
31 exe_path: PathBuf,
33 footer: PackFooter,
35 },
36 Sidecar {
38 sidecar_path: PathBuf,
40 footer: PackFooter,
42 },
43}
44
45pub fn detect_packed_mode() -> Option<PackedMode> {
54 let exe_path = std::env::current_exe().ok()?;
55
56 #[cfg(target_os = "macos")]
58 {
59 if let Some(mode) = try_section_mode() {
60 return Some(mode);
61 }
62 }
63
64 if let Some(mode) = try_sidecar_mode(&exe_path) {
66 return Some(mode);
67 }
68
69 if let Some(mode) = try_embedded_mode(&exe_path) {
71 return Some(mode);
72 }
73
74 None
75}
76
77fn try_sidecar_mode(exe_path: &Path) -> Option<PackedMode> {
78 let sidecar = sidecar_path_for(exe_path);
79 if !sidecar.exists() {
80 return None;
81 }
82 let footer = read_footer_from_sidecar(&sidecar).ok()?;
83 Some(PackedMode::Sidecar {
84 sidecar_path: sidecar,
85 footer,
86 })
87}
88
89fn try_embedded_mode(exe_path: &Path) -> Option<PackedMode> {
90 let footer = read_footer_direct(exe_path).ok()?;
91 if footer.assets_offset > 0 {
93 Some(PackedMode::Embedded {
94 exe_path: exe_path.to_path_buf(),
95 footer,
96 })
97 } else {
98 None
99 }
100}
101
102fn read_footer_direct(path: &Path) -> crate::Result<PackFooter> {
107 use std::fs::File;
108 use std::io::{Read, Seek, SeekFrom};
109
110 let mut file = File::open(path)?;
111 let file_size = file.metadata()?.len();
112 if file_size < FOOTER_SIZE as u64 {
113 return Err(crate::PackError::InvalidMagic);
114 }
115 file.seek(SeekFrom::End(-(FOOTER_SIZE as i64)))?;
116 let mut buf = [0u8; FOOTER_SIZE];
117 file.read_exact(&mut buf)?;
118 PackFooter::from_bytes(&buf)
119}
120
121#[cfg(target_os = "macos")]
126fn try_section_mode() -> Option<PackedMode> {
127 let embedded = read_embedded_section()?;
128 Some(PackedMode::Section {
129 manifest: Box::new(embedded.manifest),
130 checksum: embedded.header.checksum,
131 assets_ptr: embedded.assets_ptr,
132 assets_size: embedded.assets_size,
133 })
134}
135
136#[cfg(target_os = "macos")]
138struct EmbeddedData {
139 header: crate::format::SectionHeader,
140 manifest: crate::format::PackManifest,
141 assets_ptr: *const u8,
142 assets_size: usize,
143}
144
145#[cfg(target_os = "macos")]
152fn read_embedded_section() -> Option<EmbeddedData> {
153 use crate::format::{PackManifest, SectionHeader, SECTION_HEADER_SIZE, SECTION_MAGIC};
154
155 extern "C" {
156 fn getsectiondata(
157 mhp: *const MachHeader64,
158 segname: *const i8,
159 sectname: *const i8,
160 size: *mut usize,
161 ) -> *const u8;
162 }
163
164 #[repr(C)]
165 struct MachHeader64 {
166 magic: u32,
167 cputype: i32,
168 cpusubtype: i32,
169 filetype: u32,
170 ncmds: u32,
171 sizeofcmds: u32,
172 flags: u32,
173 reserved: u32,
174 }
175
176 extern "C" {
177 fn _dyld_get_image_header(image_index: u32) -> *const MachHeader64;
178 }
179
180 unsafe {
181 let header = _dyld_get_image_header(0);
182 if header.is_null() {
183 return None;
184 }
185
186 let segname = c"__SMOLVM";
187 let sectname = c"__smolvm";
188 let mut size: usize = 0;
189
190 let data_ptr = getsectiondata(header, segname.as_ptr(), sectname.as_ptr(), &mut size);
191
192 if data_ptr.is_null() || size < SECTION_HEADER_SIZE {
193 return None;
194 }
195
196 let magic_bytes = std::slice::from_raw_parts(data_ptr, 8);
198 if magic_bytes != SECTION_MAGIC {
199 return None;
200 }
201
202 let header_bytes = std::slice::from_raw_parts(data_ptr, SECTION_HEADER_SIZE);
204 let section_header = SectionHeader::from_bytes(header_bytes).ok()?;
205
206 let expected_size = SECTION_HEADER_SIZE
208 + section_header.manifest_size as usize
209 + section_header.assets_size as usize;
210 if size < expected_size {
211 return None;
212 }
213
214 let manifest_start = data_ptr.add(SECTION_HEADER_SIZE);
216 let manifest_bytes =
217 std::slice::from_raw_parts(manifest_start, section_header.manifest_size as usize);
218 let manifest = PackManifest::from_json(manifest_bytes).ok()?;
219
220 let assets_ptr = manifest_start.add(section_header.manifest_size as usize);
222
223 Some(EmbeddedData {
224 header: section_header,
225 manifest,
226 assets_ptr,
227 assets_size: section_header.assets_size as usize,
228 })
229 }
230}
231
232#[cfg(test)]
233mod tests {
234 use super::*;
235
236 #[test]
237 fn test_detect_returns_none_for_normal_binary() {
238 assert!(detect_packed_mode().is_none());
240 }
241
242 #[test]
243 fn test_read_footer_direct_rejects_short_file() {
244 let dir = tempfile::tempdir().unwrap();
245 let path = dir.path().join("short.bin");
246 std::fs::write(&path, b"too short").unwrap();
247 assert!(read_footer_direct(&path).is_err());
248 }
249
250 #[test]
251 fn test_read_footer_direct_rejects_invalid_magic() {
252 let dir = tempfile::tempdir().unwrap();
253 let path = dir.path().join("no_magic.bin");
254 std::fs::write(&path, &[0u8; 128]).unwrap();
255 assert!(read_footer_direct(&path).is_err());
256 }
257}