mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
253 lines
7.4 KiB
Go
253 lines
7.4 KiB
Go
//go:build darwin && cgo
|
|
|
|
package dyld
|
|
|
|
/*
|
|
#cgo CFLAGS: -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include
|
|
#cgo CFLAGS: -Wno-nullability-completeness
|
|
#cgo LDFLAGS: -ldl
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <dlfcn.h>
|
|
int
|
|
dsc_extract(void *f, const char* shared_cache_file_path, const char* extraction_root_path){
|
|
int (*extractor_proc)(const char* shared_cache_file_path, const char* extraction_root_path,
|
|
void (^progress)(unsigned current, unsigned total));
|
|
extractor_proc = f;
|
|
int result = (*extractor_proc)(shared_cache_file_path, extraction_root_path,
|
|
^(unsigned c, unsigned total) { printf("%d/%d\n", c, total); });
|
|
return result;
|
|
}
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/go-macho"
|
|
"github.com/blacktop/go-plist"
|
|
"github.com/blacktop/ipsw/internal/utils"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var xcodePaths = []string{
|
|
"/Applications/Xcode-beta.app",
|
|
"/Applications/Xcode.app",
|
|
}
|
|
|
|
// LibHandle represents an open handle to a library
|
|
type LibHandle struct {
|
|
Handle unsafe.Pointer
|
|
Libname string
|
|
}
|
|
|
|
type XCodeInfoPlist struct {
|
|
ExtractorVersion string `plist:"DSC Extractor Version,omitempty"`
|
|
DateCollected time.Time `plist:"DateCollected,omitempty"`
|
|
XCodeVersion string `plist:"Version,omitempty"`
|
|
}
|
|
type XCodeAppInfoPlist struct {
|
|
CFBundleShortVersionString string `plist:"CFBundleShortVersionString,omitempty"`
|
|
}
|
|
|
|
// GetHandle returns a handle to a library
|
|
func GetHandle(libs []string) *LibHandle {
|
|
for _, name := range libs {
|
|
libname := C.CString(name)
|
|
defer C.free(unsafe.Pointer(libname))
|
|
handle := C.dlopen(libname, C.RTLD_LAZY)
|
|
if handle != nil {
|
|
utils.Indent(log.Debug, 2)(fmt.Sprintf("Using bundle: %s", name))
|
|
h := &LibHandle{
|
|
Handle: handle,
|
|
Libname: name,
|
|
}
|
|
return h
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetSymbolPointer takes a symbol name and returns a pointer to the symbol.
|
|
func (l *LibHandle) GetSymbolPointer(symbol string) (unsafe.Pointer, error) {
|
|
sym := C.CString(symbol)
|
|
defer C.free(unsafe.Pointer(sym))
|
|
|
|
C.dlerror()
|
|
p := C.dlsym(l.Handle, sym)
|
|
e := C.dlerror()
|
|
if e != nil {
|
|
return nil, fmt.Errorf("error resolving symbol %q: %v", symbol, errors.New(C.GoString(e)))
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// Close closes a LibHandle.
|
|
func (l *LibHandle) Close() error {
|
|
C.dlerror()
|
|
C.dlclose(l.Handle)
|
|
e := C.dlerror()
|
|
if e != nil {
|
|
return fmt.Errorf("error closing %v: %v", l.Libname, errors.New(C.GoString(e)))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Split extracts all the dyld_shared_cache libraries
|
|
func Split(dyldSharedCachePath, destinationPath, xcodePath string, xcodeCache bool) error {
|
|
var xcodeVersion string
|
|
var xcodePathProvided bool
|
|
|
|
if len(xcodePath) == 0 {
|
|
var err error
|
|
xcodePath, err = utils.GetXCodePath()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get Xcode path: %v", err)
|
|
}
|
|
xcodePathProvided = false
|
|
} else {
|
|
if !strings.HasSuffix(xcodePath, "Contents/Developer") {
|
|
xcodePath = filepath.Join(xcodePath, "Contents/Developer")
|
|
}
|
|
xcodePathProvided = true
|
|
}
|
|
|
|
dscExtractorPath := filepath.Join(xcodePath, "Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle")
|
|
_, err := os.Stat(dscExtractorPath)
|
|
if err != nil {
|
|
err_msg := "failed to find DSC extractor library in %s"
|
|
if !xcodePathProvided {
|
|
err_msg += " (use --xcode to specify the path to Xcode)"
|
|
}
|
|
return fmt.Errorf(err_msg, dscExtractorPath)
|
|
}
|
|
|
|
dscExtractor := GetHandle([]string{dscExtractorPath})
|
|
if dscExtractor == nil {
|
|
err_msg := "unable to open a handle to the DSC extractor library in %s"
|
|
if !xcodePathProvided {
|
|
err_msg += " (use --xcode to specify the path to Xcode)"
|
|
}
|
|
|
|
return fmt.Errorf(err_msg, dscExtractorPath)
|
|
}
|
|
|
|
if xcodeCache {
|
|
// get ExtractorVersion
|
|
extVer := "1040.2.2.0.0"
|
|
|
|
fat, err := macho.OpenFat(dscExtractor.Libname)
|
|
switch err {
|
|
case nil:
|
|
// successfully opened fat mach-o
|
|
if len(fat.Arches) > 0 {
|
|
if sv := fat.Arches[0].SourceVersion(); sv != nil {
|
|
extVer = sv.Version.String()
|
|
}
|
|
}
|
|
fat.Close()
|
|
|
|
case macho.ErrNotFat:
|
|
// fall back on thin mach-o loading
|
|
m, err := macho.Open(dscExtractor.Libname)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open mach-o %s, %v", dscExtractor.Libname, err)
|
|
}
|
|
if sv := m.SourceVersion(); sv != nil {
|
|
extVer = sv.Version.String()
|
|
}
|
|
m.Close()
|
|
|
|
default:
|
|
// failed to load fat mach-o
|
|
return fmt.Errorf("failed to open fat mach-o %s: %v", dscExtractor.Libname, err)
|
|
}
|
|
|
|
// get XCodeVersion
|
|
xcodeContentPath := strings.TrimSuffix(dscExtractor.Libname, "/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle")
|
|
xcodeContentPath = filepath.Join(xcodeContentPath, "Info.plist")
|
|
data, err := os.ReadFile(xcodeContentPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read %s: %v", xcodeContentPath, err)
|
|
}
|
|
appInfo := XCodeAppInfoPlist{}
|
|
if err := plist.NewDecoder(bytes.NewReader(data)).Decode(&appInfo); err != nil {
|
|
return fmt.Errorf("failed to decode %s: %v", xcodeContentPath, err)
|
|
}
|
|
xcodeVersion = "14.0"
|
|
if len(appInfo.CFBundleShortVersionString) > 0 {
|
|
xcodeVersion = appInfo.CFBundleShortVersionString
|
|
}
|
|
// write Info.plist
|
|
infoPlistPath := filepath.Join(destinationPath, "Info.plist")
|
|
data, err = plist.MarshalIndent(XCodeInfoPlist{
|
|
ExtractorVersion: extVer,
|
|
DateCollected: time.Now(),
|
|
XCodeVersion: xcodeVersion,
|
|
}, plist.XMLFormat, "\t")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal Xcode cache Info.plist: %v", err)
|
|
}
|
|
if err := os.WriteFile(infoPlistPath, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write %s: %v", infoPlistPath, err)
|
|
}
|
|
|
|
destinationPath = filepath.Join(destinationPath, "Symbols")
|
|
|
|
dscCopyPath := filepath.Join(destinationPath, "private/preboot/Cryptexes/OS/System/Library/Caches/com.apple.dyld/")
|
|
|
|
matches, err := filepath.Glob(filepath.Join(filepath.Dir(dyldSharedCachePath), "dyld_shared_cache_*"))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to glob dyld_shared_cache_*: %v", err)
|
|
}
|
|
if err := os.MkdirAll(dscCopyPath, 0750); err != nil {
|
|
return fmt.Errorf("failed to create output directory %s: %v", dscCopyPath, err)
|
|
}
|
|
for _, match := range matches {
|
|
f, err := os.Create(filepath.Join(dscCopyPath, ".copied_"+filepath.Base(match)))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create .copied_%s: %v", match, err)
|
|
}
|
|
if err := f.Close(); err != nil {
|
|
return fmt.Errorf("failed to close %s: %v", f.Name(), err)
|
|
}
|
|
if err := utils.Copy(match, filepath.Join(dscCopyPath, filepath.Base(match))); err != nil {
|
|
return fmt.Errorf("failed to copy %s to %s: %v", match, dscCopyPath, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
extractorProc, err := dscExtractor.GetSymbolPointer("dyld_shared_cache_extract_dylibs_progress")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get symbol 'dyld_shared_cache_extract_dylibs_progress' pointer: %v", err)
|
|
}
|
|
|
|
dscPath := C.CString(dyldSharedCachePath)
|
|
defer C.free(unsafe.Pointer(dscPath))
|
|
|
|
destPath := C.CString(destinationPath)
|
|
defer C.free(unsafe.Pointer(destPath))
|
|
|
|
result := C.dsc_extract(extractorProc, dscPath, destPath)
|
|
if result != 0 {
|
|
return fmt.Errorf("failed to run dsc_extract: returned %d", result)
|
|
}
|
|
|
|
if err := dscExtractor.Close(); err != nil {
|
|
return fmt.Errorf("failed to close dylib %s: %v", dscExtractor.Libname, err)
|
|
}
|
|
|
|
return nil
|
|
}
|