mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
235 lines
6.9 KiB
Go
235 lines
6.9 KiB
Go
package device
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/ipsw/internal/utils"
|
|
"github.com/blacktop/ipsw/pkg/plist"
|
|
"github.com/blacktop/ipsw/pkg/usb/lockdownd"
|
|
semver "github.com/hashicorp/go-version"
|
|
)
|
|
|
|
type DDIConfig struct {
|
|
Dev *lockdownd.DeviceValues
|
|
|
|
ImageType string
|
|
|
|
XCodePath string
|
|
DDIDmgPath string
|
|
DDIFolder string
|
|
SignaturePath string
|
|
TrustcachePath string
|
|
ManifestPath string
|
|
}
|
|
|
|
// DDIInfo holds all configuration for Developer Disk Images
|
|
type DDIInfo struct {
|
|
// Used for image selection from manifest
|
|
ImageType string
|
|
|
|
// Image data and signature
|
|
ImageData []byte
|
|
SignatureData []byte
|
|
|
|
// Paths to required files
|
|
TrustcachePath string
|
|
ManifestPath string
|
|
|
|
// DMG mounting info
|
|
MountPoint string
|
|
NeedsUnmount bool
|
|
|
|
// For personalization
|
|
BuildManifest *plist.BuildManifest
|
|
}
|
|
|
|
func (d *DDIInfo) Clean() error {
|
|
if d.NeedsUnmount {
|
|
utils.Indent(log.Debug, 2)(fmt.Sprintf("Unmounting from %s", d.MountPoint))
|
|
if err := utils.Retry(3, 2*time.Second, func() error {
|
|
return utils.Unmount(d.MountPoint, false)
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to unmount from %s: %w", d.MountPoint, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d *DDIInfo) Verify() error {
|
|
if d.ImageData == nil {
|
|
return fmt.Errorf("image data is nil")
|
|
}
|
|
if d.SignatureData == nil {
|
|
return fmt.Errorf("signature data is nil")
|
|
}
|
|
|
|
switch d.ImageType {
|
|
case "Developer":
|
|
case "Personalized":
|
|
if d.TrustcachePath == "" {
|
|
return fmt.Errorf("trustcache path is nil")
|
|
}
|
|
if d.ManifestPath == "" {
|
|
return fmt.Errorf("manifest path is nil")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetDDIInfo(c *DDIConfig) (info *DDIInfo, err error) {
|
|
info = &DDIInfo{
|
|
ImageType: c.ImageType,
|
|
}
|
|
|
|
if c.Dev != nil {
|
|
ver, err := semver.NewVersion(c.Dev.ProductVersion)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse device version: %w", err)
|
|
}
|
|
|
|
/* Old device (pre-iOS 17) */
|
|
if ver.LessThan(semver.Must(semver.NewVersion("17.0"))) {
|
|
info.ImageType = "Developer" // Developer was the only option for pre-iOS 17
|
|
log.Warn("iOS < 17.0 detected, using DeveloperDiskImage.dmg (image type 'Developer')")
|
|
|
|
if c.XCodePath != "" {
|
|
version := ver.Segments()
|
|
imgPath := filepath.Join(c.XCodePath,
|
|
fmt.Sprintf("/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/%d.%d/DeveloperDiskImage.dmg",
|
|
version[0], version[1]))
|
|
sigPath := imgPath + ".signature"
|
|
|
|
info.ImageData, err = os.ReadFile(imgPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read DeveloperDiskImage.dmg: %w", err)
|
|
}
|
|
|
|
info.SignatureData, err = os.ReadFile(sigPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read DeveloperDiskImage.dmg.signature: %w", err)
|
|
}
|
|
|
|
return info, nil
|
|
}
|
|
if c.DDIDmgPath == "" || c.SignaturePath == "" {
|
|
return nil, fmt.Errorf("for iOS < 17.0, either --xcode or both --ddi-img and --signature must be specified")
|
|
}
|
|
}
|
|
|
|
/* iOS 17+ device */
|
|
}
|
|
|
|
// If manifest path is already provided, use it
|
|
if c.ManifestPath != "" {
|
|
info.ManifestPath = c.ManifestPath
|
|
c.DDIFolder = filepath.Dir(c.ManifestPath)
|
|
}
|
|
if c.TrustcachePath != "" {
|
|
info.TrustcachePath = c.TrustcachePath
|
|
}
|
|
|
|
// Need to find files based on Xcode or DMG path
|
|
if c.XCodePath != "" {
|
|
xcodeVer, err := utils.GetXCodeVersion(c.XCodePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get Xcode version: %w", err)
|
|
}
|
|
|
|
xcver, err := semver.NewVersion(xcodeVer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse Xcode version: %w", err)
|
|
}
|
|
|
|
// Xcode Older than 16.0
|
|
if xcver.LessThan(semver.Must(semver.NewVersion("16.0"))) {
|
|
// DDI is in Xcode.app
|
|
dmgPath := filepath.Join(c.XCodePath, "/Contents/Resources/CoreDeviceDDIs/iOS_DDI.dmg")
|
|
if _, err := os.Stat(dmgPath); errors.Is(err, os.ErrNotExist) {
|
|
return nil, fmt.Errorf("failed to find iOS_DDI.dmg in '%s'", dmgPath)
|
|
}
|
|
} else { // Newer Xcode (16.0+)
|
|
dmgPath := "/Library/Developer/DeveloperDiskImages/iOS_DDI.dmg"
|
|
if _, err := os.Stat(dmgPath); errors.Is(err, os.ErrNotExist) {
|
|
// Try NEW (as of Xcode 16.2 maybe?) DDI expanded folder structure
|
|
c.DDIFolder = "/Library/Developer/DeveloperDiskImages/iOS_DDI"
|
|
info.ManifestPath = filepath.Join(c.DDIFolder, "Restore/BuildManifest.plist")
|
|
if _, err := os.Stat(info.ManifestPath); errors.Is(err, os.ErrNotExist) {
|
|
return nil, fmt.Errorf("failed to find BuildManifest.plist at '%s' (run `%s -runFirstLaunch`)",
|
|
info.ManifestPath,
|
|
filepath.Join(c.XCodePath, "Contents/Developer/usr/bin/xcodebuild"),
|
|
)
|
|
}
|
|
info.ManifestPath = filepath.Join(c.DDIFolder, "Restore/BuildManifest.plist")
|
|
}
|
|
}
|
|
} else if c.DDIFolder != "" {
|
|
info.ManifestPath = filepath.Join(c.DDIFolder, "Restore/BuildManifest.plist")
|
|
if _, err := os.Stat(info.ManifestPath); errors.Is(err, os.ErrNotExist) {
|
|
return nil, fmt.Errorf("failed to find BuildManifest.plist at '%s' (run `%s -runFirstLaunch`)",
|
|
info.ManifestPath,
|
|
filepath.Join(c.XCodePath, "Contents/Developer/usr/bin/xcodebuild"),
|
|
)
|
|
}
|
|
info.ManifestPath = filepath.Join(c.DDIFolder, "Restore/BuildManifest.plist")
|
|
}
|
|
|
|
if info.ManifestPath == "" {
|
|
// At this point we have a DMG path that needs to be mounted to find the BuildManifest.plist
|
|
utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", c.DDIDmgPath))
|
|
mountPoint, alreadyMounted, err := utils.MountDMG(c.DDIDmgPath, "")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to mount %s: %w", c.DDIDmgPath, err)
|
|
}
|
|
|
|
info.MountPoint = mountPoint
|
|
c.DDIFolder = info.MountPoint
|
|
|
|
if alreadyMounted {
|
|
utils.Indent(log.Info, 3)(fmt.Sprintf("%s already mounted", c.DDIDmgPath))
|
|
info.NeedsUnmount = false
|
|
} else {
|
|
info.NeedsUnmount = true
|
|
}
|
|
|
|
info.ManifestPath = filepath.Join(c.DDIFolder, "Restore/BuildManifest.plist")
|
|
}
|
|
|
|
if c.DDIDmgPath == "" {
|
|
// Read the BuildManifest.plist to find the DDI file
|
|
manifestData, err := os.ReadFile(info.ManifestPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read BuildManifest.plist: %w", err)
|
|
}
|
|
info.BuildManifest, err = plist.ParseBuildManifest(manifestData)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse BuildManifest.plist: %w", err)
|
|
}
|
|
info.TrustcachePath = filepath.Join(c.DDIFolder, "Restore", info.BuildManifest.BuildIdentities[0].Manifest["LoadableTrustCache"].Info["Path"].(string))
|
|
c.DDIDmgPath = filepath.Join(c.DDIFolder, "Restore", info.BuildManifest.BuildIdentities[0].Manifest["PersonalizedDMG"].Info["Path"].(string))
|
|
}
|
|
|
|
if c.DDIDmgPath != "" {
|
|
// Read the DDI file
|
|
info.ImageData, err = os.ReadFile(c.DDIDmgPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read image '%s': %w", c.DDIDmgPath, err)
|
|
}
|
|
}
|
|
|
|
if c.SignaturePath != "" {
|
|
// Read the signature file
|
|
info.SignatureData, err = os.ReadFile(c.SignaturePath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read signature '%s': %w", c.SignaturePath, err)
|
|
}
|
|
}
|
|
|
|
return info, nil
|
|
}
|