// mklipo merges two Mach-O binaries into one macOS universal (fat) binary. package main import ( "encoding/binary" "fmt" "os" ) const ( fatMagic = 0xCAFEBABE cpuTypeX86_64 = 0x01000007 cpuTypeARM64 = 0x0100000c fatArchSize = 20 ) type slice struct { cpu, sub uint32 data []byte } func main() { if len(os.Args) != 4 { fmt.Fprintln(os.Stderr, "usage: mklipo ") os.Exit(2) } amd, err := os.ReadFile(os.Args[1]) if err != nil { fatal(err) } arm, err := os.ReadFile(os.Args[2]) if err != nil { fatal(err) } if err := writeFat(os.Args[3], []slice{ {cpuTypeX86_64, 3, amd}, {cpuTypeARM64, 0, arm}, }); err != nil { fatal(err) } } func writeFat(path string, parts []slice) error { const align uint32 = 0x4000 hdr := 8 + fatArchSize*len(parts) offsets := make([]uint32, len(parts)) cur := uint32(hdr) for i, p := range parts { pad := (align - (cur % align)) % align cur += pad offsets[i] = cur cur += uint32(len(p.data)) } out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) if err != nil { return err } defer out.Close() if err := binary.Write(out, binary.BigEndian, uint32(fatMagic)); err != nil { return err } if err := binary.Write(out, binary.BigEndian, uint32(len(parts))); err != nil { return err } for i, p := range parts { if err := binary.Write(out, binary.BigEndian, p.cpu); err != nil { return err } if err := binary.Write(out, binary.BigEndian, p.sub); err != nil { return err } if err := binary.Write(out, binary.BigEndian, offsets[i]); err != nil { return err } if err := binary.Write(out, binary.BigEndian, uint32(len(p.data))); err != nil { return err } if err := binary.Write(out, binary.BigEndian, align); err != nil { return err } } cur = uint32(hdr) for _, p := range parts { pad := (align - (cur % align)) % align if pad > 0 { if _, err := out.Write(make([]byte, pad)); err != nil { return err } cur += pad } if _, err := out.Write(p.data); err != nil { return err } cur += uint32(len(p.data)) } return nil } func fatal(err error) { fmt.Fprintln(os.Stderr, "mklipo:", err) os.Exit(1) }