1use {
6 anyhow::Result,
7 goblin::mach::{
8 fat::{FatArch, FAT_MAGIC, SIZEOF_FAT_ARCH, SIZEOF_FAT_HEADER},
9 Mach,
10 },
11 scroll::{IOwrite, Pwrite},
12 std::io::Write,
13 thiserror::Error,
14};
15
16#[derive(Debug, Error)]
17pub enum UniversalMachOError {
18 #[error("I/O error: {0}")]
19 Io(#[from] std::io::Error),
20
21 #[error("mach-o parse error: {0}")]
22 Goblin(#[from] goblin::error::Error),
23
24 #[error("scroll error: {0}")]
25 Scroll(#[from] scroll::Error),
26}
27
28#[derive(Clone, Default)]
30pub struct UniversalBinaryBuilder {
31 binaries: Vec<Vec<u8>>,
32}
33
34impl UniversalBinaryBuilder {
35 pub fn add_binary(&mut self, data: impl AsRef<[u8]>) -> Result<usize, UniversalMachOError> {
36 let data = data.as_ref();
37
38 match Mach::parse(data)? {
39 Mach::Binary(_) => {
40 self.binaries.push(data.to_vec());
41 Ok(1)
42 }
43 Mach::Fat(multiarch) => {
44 for arch in multiarch.iter_arches() {
45 let arch = arch?;
46
47 let data =
48 &data[arch.offset as usize..arch.offset as usize + arch.size as usize];
49 self.binaries.push(data.to_vec());
50 }
51
52 Ok(multiarch.narches)
53 }
54 }
55 }
56
57 pub fn write(&self, writer: &mut impl Write) -> Result<(), UniversalMachOError> {
59 create_universal_macho(writer, self.binaries.iter().map(|x| x.as_slice()))
60 }
61}
62
63pub fn create_universal_macho<'a>(
70 writer: &mut impl Write,
71 binaries: impl Iterator<Item = &'a [u8]>,
72) -> Result<(), UniversalMachOError> {
73 const ALIGN_VALUE: u32 = 14;
77 let align: u32 = 2u32.pow(ALIGN_VALUE);
78
79 let mut records = vec![];
80
81 let mut offset: u32 = align;
82
83 for binary in binaries {
84 let macho = goblin::mach::MachO::parse(binary, 0)?;
85
86 let pad_bytes = match offset % align {
88 0 => 0,
89 x => align - x,
90 };
91
92 offset += pad_bytes;
93
94 let arch = FatArch {
95 cputype: macho.header.cputype,
96 cpusubtype: macho.header.cpusubtype,
97 offset,
98 size: binary.len() as u32,
99 align: ALIGN_VALUE,
100 };
101
102 offset += arch.size;
103
104 records.push((arch, pad_bytes as usize, binary));
105 }
106
107 writer.iowrite_with(FAT_MAGIC, scroll::BE)?;
109 writer.iowrite_with(records.len() as u32, scroll::BE)?;
110
111 for (fat_arch, _, _) in &records {
112 let mut buffer = [0u8; SIZEOF_FAT_ARCH];
113 buffer.pwrite_with(fat_arch, 0, scroll::BE)?;
114 writer.write_all(&buffer)?;
115 }
116
117 let current_offset = SIZEOF_FAT_HEADER + records.len() * SIZEOF_FAT_ARCH;
119 writer.write_all(&b"\0".repeat(align as usize - current_offset % align as usize))?;
120
121 assert!(current_offset <= align as usize, "too many mach-o entries");
123
124 for (_, pad_bytes, macho_data) in records {
125 writer.write_all(&b"\0".repeat(pad_bytes))?;
126 writer.write_all(macho_data)?;
127 }
128
129 Ok(())
130}