2023-05-31 16:31:52 +07:00
|
|
|
// / Contains the declaration of FatHeader and FatArch
|
|
|
|
// / These structs are always written using Big-Endian,
|
|
|
|
// / as documented in the mach-o/fat.h
|
|
|
|
// / This file also has a CreateFat function to generate
|
|
|
|
// / Fat file from a list of MachoContext
|
2023-05-31 16:17:03 +07:00
|
|
|
package fat
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"sort"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
|
|
|
|
. "ios-wrapper/pkg/ios"
|
|
|
|
macho "ios-wrapper/pkg/ios/macho"
|
|
|
|
)
|
|
|
|
|
2023-05-31 16:31:52 +07:00
|
|
|
// / Get the alignment for the Mach-O in Fat binary
|
|
|
|
// / The returned value is the multiplier of 2
|
2023-05-31 16:17:03 +07:00
|
|
|
func GetAlignment(h *macho.Header) uint32 {
|
|
|
|
switch h.Cputype() {
|
|
|
|
case CPU_TYPE_ARM, CPU_TYPE_ARM64:
|
|
|
|
return 0xe // log2(0x4000)
|
|
|
|
case CPU_TYPE_POWERPC,
|
|
|
|
CPU_TYPE_POWERPC64,
|
|
|
|
CPU_TYPE_I386,
|
|
|
|
CPU_TYPE_X86_64:
|
|
|
|
return 0xc // log2(0x1000)
|
|
|
|
default:
|
|
|
|
return 0xd // log2(0x2000)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func MachosToFatArchs(machos []*macho.MachoContext) []*FatArch {
|
|
|
|
var fa []*FatArch
|
|
|
|
for _, m := range machos {
|
|
|
|
fa = append(fa, &FatArch{
|
|
|
|
Cputype: m.Header().Cputype(),
|
|
|
|
Cpusubtype: m.Header().Cpusubtype(),
|
|
|
|
Size: m.FileSize(),
|
|
|
|
Offset: 0,
|
|
|
|
Align: GetAlignment(m.Header()),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return fa
|
|
|
|
}
|
|
|
|
|
2023-05-31 16:31:52 +07:00
|
|
|
// / Create a Fat binary from MachoContext
|
|
|
|
// / Convert MachoContext to FatArch
|
|
|
|
// / Calculate the alignment, now using basic calculation
|
|
|
|
// / because iOS always has valid archs ARM
|
|
|
|
// /
|
|
|
|
// / Sort the Fat arch as per the cctools/lipo
|
|
|
|
// / Calculate the offset with each Mach-O
|
|
|
|
// /
|
|
|
|
// / Write the FatHeader
|
|
|
|
// / Write the FatArchs
|
|
|
|
// / Write the Mach-Os
|
2023-05-31 16:17:03 +07:00
|
|
|
func CreateFat(machos []*macho.MachoContext, outfilename string) error {
|
|
|
|
archs := MachosToFatArchs(machos)
|
|
|
|
sort.SliceStable(archs, func(i, j int) bool {
|
|
|
|
if archs[i].Cputype == archs[j].Cputype {
|
|
|
|
return archs[i].Cpusubtype < archs[i].Cpusubtype
|
|
|
|
}
|
|
|
|
if archs[i].Cputype == CPU_TYPE_ARM64 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if archs[j].Cputype == CPU_TYPE_ARM64 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return archs[i].Cpusubtype < archs[j].Cpusubtype
|
|
|
|
})
|
|
|
|
|
|
|
|
// calculate the offset for each FatArch
|
|
|
|
offset := uint32(8) // size of fat header, bytes
|
|
|
|
// offset to the first macho
|
|
|
|
offset += uint32(len(archs)) * 5 // size of fat_arch, bytes
|
|
|
|
|
|
|
|
for _, arch := range archs {
|
|
|
|
offset = uint32(OffsetRounder(uint64(offset), 1<<arch.Align))
|
|
|
|
arch.Offset = offset
|
|
|
|
offset += arch.Size
|
|
|
|
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"cputype": arch.Cputype,
|
|
|
|
"cpusubtype": arch.Cpusubtype,
|
|
|
|
"size": arch.Size,
|
|
|
|
"offset": arch.Offset,
|
|
|
|
"algin": arch.Align,
|
|
|
|
}).Debug("Arch to add")
|
|
|
|
}
|
|
|
|
|
|
|
|
file, err := os.OpenFile(outfilename, os.O_CREATE|os.O_WRONLY, 0777)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
// log.Fatal("Cannot open output file")
|
|
|
|
}
|
|
|
|
|
|
|
|
var w fatWriter
|
|
|
|
w.WriteFatHeader(file, &FatHeader{
|
|
|
|
Magic: MagicFat,
|
|
|
|
Nfat_arch: uint32(len(archs)),
|
|
|
|
})
|
|
|
|
w.WriteFatArchs(file, archs)
|
|
|
|
w.WriteMachos(file, archs, machos)
|
|
|
|
return w.err
|
|
|
|
}
|
|
|
|
|
|
|
|
type fatWriter struct {
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fw *fatWriter) WriteFatHeader(w io.Writer, h *FatHeader) {
|
|
|
|
if fw.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b := h.Serialize()
|
|
|
|
_, err := w.Write(b)
|
|
|
|
fw.err = err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fw *fatWriter) WriteFatArchs(w io.Writer, archs []*FatArch) {
|
|
|
|
for _, arch := range archs {
|
|
|
|
if fw.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
b := arch.Serialize()
|
|
|
|
_, err := w.Write(b)
|
|
|
|
fw.err = err
|
|
|
|
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"cputype": arch.Cputype,
|
|
|
|
"cpusubtype": arch.Cpusubtype,
|
|
|
|
"size": arch.Size,
|
|
|
|
"offset": arch.Offset,
|
|
|
|
"algin": arch.Align,
|
|
|
|
}).Info("Attempt to write Fat Arch")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-31 16:31:52 +07:00
|
|
|
// / for each fat arch sorted, we locate the MachoContext and
|
|
|
|
// / use it to Write to the buffer
|
|
|
|
// / locating the macho by its cputype and cpusubtype
|
2023-05-31 16:17:03 +07:00
|
|
|
func (fw *fatWriter) WriteMachos(
|
|
|
|
w io.WriteSeeker,
|
|
|
|
archs []*FatArch,
|
|
|
|
machos []*macho.MachoContext,
|
|
|
|
) {
|
|
|
|
for _, arch := range archs {
|
|
|
|
for _, macho := range machos {
|
|
|
|
if fw.err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cputype := macho.Header().Cputype()
|
|
|
|
cpusubtype := macho.Header().Cpusubtype()
|
|
|
|
if cputype == arch.Cputype &&
|
|
|
|
cpusubtype == arch.Cpusubtype {
|
|
|
|
|
|
|
|
log.WithFields(log.Fields{
|
|
|
|
"cputype": cputype,
|
|
|
|
"cpusubtype": cpusubtype,
|
|
|
|
"offset": arch.Offset,
|
|
|
|
"size": arch.Size,
|
|
|
|
}).Info("Attempt to write Mach-O to file")
|
|
|
|
|
|
|
|
_, err1 := w.Seek(int64(arch.Offset), io.SeekStart)
|
|
|
|
_, err2 := macho.WriteBufferTo(w)
|
|
|
|
if err1 != nil {
|
|
|
|
fw.err = err1
|
|
|
|
} else if err2 != nil {
|
|
|
|
fw.err = err2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func OffsetRounder(v, r uint64) uint64 {
|
|
|
|
r--
|
|
|
|
v += r
|
|
|
|
v &= uint64(^int64(r))
|
|
|
|
return v
|
|
|
|
}
|