From bcbcb14f89eecd90ed9322b8c7b62773e04b2cbb Mon Sep 17 00:00:00 2001 From: blacktop Date: Thu, 12 Sep 2024 13:08:45 -0600 Subject: [PATCH] =?UTF-8?q?fix:=20add=20better=20error=20handling=20to=20X?= =?UTF-8?q?Code=2016=20`ipsw=20idev=20img`=20cmds=20=F0=9F=8F=8E=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/ipsw/cmd/idev/idev_img_mount.go | 20 +++++++++++++ cmd/ipsw/cmd/idev/idev_img_sign.go | 32 ++++++++++++++------ internal/utils/macos.go | 46 ++++++++++++++++++++++++++--- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/cmd/ipsw/cmd/idev/idev_img_mount.go b/cmd/ipsw/cmd/idev/idev_img_mount.go index 77527d586..927b76e87 100644 --- a/cmd/ipsw/cmd/idev/idev_img_mount.go +++ b/cmd/ipsw/cmd/idev/idev_img_mount.go @@ -185,6 +185,26 @@ var idevImgMountCmd = &cobra.Command{ imageType = "Personalized" if len(dmgPath) == 0 { + xcodeVersion, err := utils.GetXCodeVersion(xcode) + if err != nil { + return fmt.Errorf("failed to get Xcode version: %w", err) + } + xcver, err := semver.NewVersion(xcodeVersion) // check + if err != nil { + return fmt.Errorf("failed to convert version into semver object") + } + var ddiPath string + if xcver.LessThan(semver.Must(semver.NewVersion("16.0"))) { + ddiPath = filepath.Join(xcode, "/Contents/Resources/CoreDeviceDDIs/iOS_DDI.dmg") + if _, err := os.Stat(ddiPath); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to find iOS_DDI.dmg in '%s' (install NEW XCode.app or Xcode-beta.app)", ddiPath) + } + } else { + ddiPath = "/Library/Developer/DeveloperDiskImages/iOS_DDI.dmg" + if _, err := os.Stat(ddiPath); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to find iOS_DDI.dmg in '%s' (run `%s -runFirstLaunch` and try again)", ddiPath, filepath.Join(xcode, "Contents/Developer/usr/bin/xcodebuild")) + } + } ddiDMG := filepath.Join(xcode, "/Contents/Resources/CoreDeviceDDIs/iOS_DDI.dmg") if _, err := os.Stat(ddiDMG); errors.Is(err, os.ErrNotExist) { ddiDMG = "/Library/Developer/DeveloperDiskImages/iOS_DDI.dmg" diff --git a/cmd/ipsw/cmd/idev/idev_img_sign.go b/cmd/ipsw/cmd/idev/idev_img_sign.go index 5e69f704f..ecc98248c 100644 --- a/cmd/ipsw/cmd/idev/idev_img_sign.go +++ b/cmd/ipsw/cmd/idev/idev_img_sign.go @@ -34,6 +34,7 @@ import ( "github.com/blacktop/ipsw/pkg/plist" "github.com/blacktop/ipsw/pkg/tss" "github.com/fatih/color" + semver "github.com/hashicorp/go-version" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -134,27 +135,40 @@ var idevImgSignCmd = &cobra.Command{ } if xcode != "" { - dmgPath := filepath.Join(xcode, "/Contents/Resources/CoreDeviceDDIs/iOS_DDI.dmg") - if _, err := os.Stat(dmgPath); errors.Is(err, os.ErrNotExist) { - dmgPath = "/Library/Developer/DeveloperDiskImages/iOS_DDI.dmg" - if _, err := os.Stat(dmgPath); errors.Is(err, os.ErrNotExist) { + xcodeVersion, err := utils.GetXCodeVersion(xcode) + if err != nil { + return fmt.Errorf("failed to get Xcode version: %w", err) + } + xcver, err := semver.NewVersion(xcodeVersion) // check + if err != nil { + return fmt.Errorf("failed to convert version into semver object") + } + var ddiPath string + if xcver.LessThan(semver.Must(semver.NewVersion("16.0"))) { + ddiPath = filepath.Join(xcode, "/Contents/Resources/CoreDeviceDDIs/iOS_DDI.dmg") + if _, err := os.Stat(ddiPath); errors.Is(err, os.ErrNotExist) { return fmt.Errorf("failed to find iOS_DDI.dmg in '%s' (install NEW XCode.app or Xcode-beta.app)", xcode) } + } else { + ddiPath = "/Library/Developer/DeveloperDiskImages/iOS_DDI.dmg" + if _, err := os.Stat(ddiPath); errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("failed to find iOS_DDI.dmg in '%s' (run `%s -runFirstLaunch` and try again)", ddiPath, filepath.Join(xcode, "Contents/Developer/usr/bin/xcodebuild")) + } } - utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", dmgPath)) - mountPoint, alreadyMounted, err := utils.MountDMG(dmgPath) + utils.Indent(log.Info, 2)(fmt.Sprintf("Mounting %s", ddiPath)) + mountPoint, alreadyMounted, err := utils.MountDMG(ddiPath) if err != nil { return fmt.Errorf("failed to mount iOS_DDI.dmg: %w", err) } if alreadyMounted { - utils.Indent(log.Info, 3)(fmt.Sprintf("%s already mounted", dmgPath)) + utils.Indent(log.Info, 3)(fmt.Sprintf("%s already mounted", ddiPath)) } else { defer func() { - utils.Indent(log.Debug, 2)(fmt.Sprintf("Unmounting %s", dmgPath)) + utils.Indent(log.Debug, 2)(fmt.Sprintf("Unmounting %s", ddiPath)) if err := utils.Retry(3, 2*time.Second, func() error { return utils.Unmount(mountPoint, false) }); err != nil { - log.Errorf("failed to unmount %s at %s: %v", dmgPath, mountPoint, err) + log.Errorf("failed to unmount %s at %s: %v", ddiPath, mountPoint, err) } }() } diff --git a/internal/utils/macos.go b/internal/utils/macos.go index b844c84af..8adeceb1e 100644 --- a/internal/utils/macos.go +++ b/internal/utils/macos.go @@ -147,7 +147,7 @@ func CodeSignAdHoc(filePath string) error { func CodesignVerify(path string) (string, error) { if runtime.GOOS == "darwin" { - cmd := exec.Command("codesign", "--verify", "--deep", "--strict", "--verbose=4", path) + cmd := exec.Command("/usr/bin/codesign", "--verify", "--deep", "--strict", "--verbose=4", path) out, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("%v: %s", err, out) @@ -159,7 +159,7 @@ func CodesignVerify(path string) (string, error) { func CodesignShow(path string) (string, error) { if runtime.GOOS == "darwin" { - cmd := exec.Command("codesign", "-d", "--verbose=4", path) + cmd := exec.Command("/usr/bin/codesign", "-d", "--verbose=4", path) out, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("%v: %s", err, out) @@ -173,7 +173,7 @@ func CodesignShow(path string) (string, error) { func CreateSparseDiskImage(volumeName, diskPath string) (string, error) { if runtime.GOOS == "darwin" { - cmd := exec.Command("hdiutil", "create", "-size", "16g", "-fs", "HFS+", "-volname", volumeName, "-type", "SPARSE", "-plist", diskPath) + cmd := exec.Command("usr/bin/hdiutil", "create", "-size", "16g", "-fs", "HFS+", "-volname", volumeName, "-type", "SPARSE", "-plist", diskPath) out, err := cmd.CombinedOutput() if err != nil { @@ -372,7 +372,7 @@ func GetBuildInfo() (*BuildInfo, error) { func GetXCodePath() (string, error) { if runtime.GOOS == "darwin" { - cmd := exec.Command("xcode-select", "-p") + cmd := exec.Command("/usr/bin/xcode-select", "-p") out, err := cmd.CombinedOutput() if err != nil { return "", fmt.Errorf("%v: %s", err, out) @@ -382,6 +382,44 @@ func GetXCodePath() (string, error) { return "", fmt.Errorf("only supported on macOS") } +type xcodeVersionPlist struct { + BuildVersion string `json:"BuildVersion"` + CFBundleShortVersionString string `json:"CFBundleShortVersionString"` + CFBundleVersion string `json:"CFBundleVersion"` + ProductBuildVersion string `json:"ProductBuildVersion"` + ProjectName string `json:"ProjectName"` + SourceVersion string `json:"SourceVersion"` +} + +func GetXCodeVersion(path ...string) (string, error) { + if runtime.GOOS == "darwin" { + if len(path) == 0 { + cmd := exec.Command("/usr/bin/xcodebuild", "-version") + out, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("%v: %s", err, out) + } + version, _, ok := strings.Cut(strings.TrimSpace(string(out)), "\n") + if !ok { + return "", fmt.Errorf("failed to parse xcodebuild version: %s", out) + } + return strings.TrimPrefix(version, "Xcode "), nil + } else { + f, err := os.Open(filepath.Join(path[0], "Contents", "version.plist")) + if err != nil { + return "", fmt.Errorf("failed to open Xcode version.plist: %v", err) + } + defer f.Close() + var version xcodeVersionPlist + if err := plist.NewDecoder(f).Decode(&version); err != nil { + return "", fmt.Errorf("failed to decode Xcode version.plist: %v", err) + } + return version.CFBundleShortVersionString, nil + } + } + return "", fmt.Errorf("only supported on macOS") +} + func GetKernelPath() (string, error) { if runtime.GOOS == "darwin" { cmd := exec.Command("uname", "-a")