feat: add --sim flag to download XCode simulators via ipsw dl ota cmd #700

This commit is contained in:
blacktop
2025-05-13 20:52:23 -06:00
parent bf44c3d928
commit 6a044aafcb
4 changed files with 99 additions and 5 deletions
+24 -5
View File
@@ -65,6 +65,7 @@ func init() {
otaDLCmd.Flags().Bool("latest", false, "Download latest OTAs")
otaDLCmd.Flags().Bool("delta", false, "Download Delta OTAs")
otaDLCmd.Flags().Bool("rsr", false, "Download Rapid Security Response OTAs")
otaDLCmd.Flags().Bool("sim", false, "Download Simulator OTAs")
otaDLCmd.Flags().BoolP("kernel", "k", false, "Extract kernelcache from remote OTA zip")
otaDLCmd.Flags().Bool("dyld", false, "Extract dyld_shared_cache(s) from remote OTA zip")
otaDLCmd.Flags().BoolP("urls", "u", false, "Dump URLs only")
@@ -87,6 +88,7 @@ func init() {
viper.BindPFlag("download.ota.latest", otaDLCmd.Flags().Lookup("latest"))
viper.BindPFlag("download.ota.delta", otaDLCmd.Flags().Lookup("delta"))
viper.BindPFlag("download.ota.rsr", otaDLCmd.Flags().Lookup("rsr"))
viper.BindPFlag("download.ota.sim", otaDLCmd.Flags().Lookup("sim"))
viper.BindPFlag("download.ota.dyld", otaDLCmd.Flags().Lookup("dyld"))
viper.BindPFlag("download.ota.urls", otaDLCmd.Flags().Lookup("urls"))
viper.BindPFlag("download.ota.json", otaDLCmd.Flags().Lookup("json"))
@@ -159,6 +161,7 @@ var otaDLCmd = &cobra.Command{
getBeta := viper.GetBool("download.ota.beta")
getLatest := viper.GetBool("download.ota.latest")
getRSR := viper.GetBool("download.ota.rsr")
getSim := viper.GetBool("download.ota.sim")
remoteDyld := viper.GetBool("download.ota.dyld")
dyldArches := viper.GetStringSlice("download.ota.dyld-arch")
dyldDriverKit := viper.GetBool("download.ota.driver-kit")
@@ -269,6 +272,7 @@ var otaDLCmd = &cobra.Command{
Latest: getLatest,
Delta: viper.GetBool("download.ota.delta"),
RSR: getRSR,
Simulator: getSim,
Device: device,
Model: model,
Version: ver,
@@ -322,7 +326,7 @@ var otaDLCmd = &cobra.Command{
for _, o := range otas {
utils.Indent(log.WithFields(log.Fields{
"name": o.DocumentationID,
"version": o.OSVersion,
"version": or([]string{o.OSVersion, o.SimulatorVersion}),
"build": o.Build,
"device_count": len(o.SupportedDevices),
"model_count": len(o.SupportedDeviceModels),
@@ -357,7 +361,7 @@ var otaDLCmd = &cobra.Command{
"devices": fmt.Sprintf("%s... (count=%d)", strings.Join(o.SupportedDevices, " "), len(o.SupportedDevices)),
"model": strings.Join(o.SupportedDeviceModels, " "),
}
if o.IsEncrypted {
if o.IsEncrypted || len(o.ArchiveDecryptionKey) > 0 {
fields["encrypted"] = true
fields["key"] = o.ArchiveDecryptionKey
}
@@ -422,6 +426,9 @@ var otaDLCmd = &cobra.Command{
downloader := download.NewDownload(proxy, insecure, skipAll, resumeAll, restartAll, false, viper.GetBool("verbose"))
for _, o := range otas {
folder := filepath.Join(destPath, fmt.Sprintf("%s%s_OTAs", o.ProductSystemName, strings.TrimPrefix(o.OSVersion, "9.9.")))
if getSim {
folder = filepath.Join(destPath, fmt.Sprintf("%s_%s_Simulator_OTAs", strings.ToUpper(platform), o.SimulatorVersion))
}
os.MkdirAll(folder, 0750)
var devices string
if len(o.SupportedDevices) > 0 {
@@ -445,21 +452,24 @@ var otaDLCmd = &cobra.Command{
isRSR = fmt.Sprintf("%s_%s_%s_RSR_", o.OSVersion, o.ProductVersionExtra, o.Build)
}
var isAEA string
if o.IsEncrypted {
if o.IsEncrypted || len(o.ArchiveDecryptionKey) > 0 {
filesafe := o.ArchiveDecryptionKey
filesafe = strings.ReplaceAll(filesafe, "/", "_")
filesafe = strings.ReplaceAll(filesafe, "+", "-")
isAEA = "KEY_[" + filesafe + "]_"
}
destName := filepath.Join(folder, fmt.Sprintf("%s_%s%s%s", devices, isRSR, isAEA, getDestName(url, removeCommas)))
if getSim {
destName = filepath.Join(folder, fmt.Sprintf("simulator_%s%s", isAEA, getDestName(url, removeCommas)))
}
if _, err := os.Stat(destName); os.IsNotExist(err) {
fields := log.Fields{
"device": strings.Join(o.SupportedDevices, " "),
"model": strings.Join(o.SupportedDeviceModels, " "),
"build": o.Build,
"type": o.DocumentationID,
"type": or([]string{o.DocumentationID, "simulator"}),
}
if o.IsEncrypted {
if o.IsEncrypted || len(o.ArchiveDecryptionKey) > 0 {
fields["encrypted"] = true
fields["key"] = o.ArchiveDecryptionKey
}
@@ -482,3 +492,12 @@ var otaDLCmd = &cobra.Command{
return nil
},
}
func or(values []string) string {
for _, v := range values {
if len(v) > 0 {
return v
}
}
return ""
}
+50
View File
@@ -88,6 +88,7 @@ type OtaConf struct {
Latest bool
Delta bool
RSR bool
Simulator bool
Device string
Model string
Version *version.Version
@@ -110,6 +111,7 @@ type pallasRequest struct {
BuildVersion string `json:"BuildVersion"`
Build string `json:"Build,omitempty"`
RequestedProductVersion string `json:"RequestedProductVersion,omitempty"`
RequestedBuild string `json:"RequestedBuild,omitempty"`
Supervised bool `json:"Supervised,omitempty"`
DelayRequested bool `json:"DelayRequested,omitempty"`
CompatibilityVersion int `json:"CompatibilityVersion,omitempty"`
@@ -305,6 +307,20 @@ func (o *Ota) getRequestAssetTypes() ([]assetType, error) {
if o.Config.RSR {
return []assetType{rsrUpdate}, nil
}
if o.Config.Simulator {
switch o.Config.Platform {
case "ios":
return []assetType{iOsSimulatorUpdate}, nil
case "watchos":
return []assetType{watchOsSimulatorUpdate}, nil
case "tvos":
return []assetType{tvOsSimulatorUpdate}, nil
case "visionos":
return []assetType{visionOaSimulatorUpdate}, nil
default:
return nil, fmt.Errorf("unsupported simulator platform %s", o.Config.Platform)
}
}
if o.Config.Platform == "ios" {
return []assetType{softwareUpdate}, nil
}
@@ -381,6 +397,9 @@ func (o *Ota) getRequestAudienceIDs() ([]string, error) {
}, nil
}
default:
if o.Config.Simulator {
return []string{assetAudienceDB["macos"].Generic}, nil
}
if o.Config.Version != nil {
segs := o.Config.Version.Segments()
if len(segs) == 0 {
@@ -464,6 +483,10 @@ func (o *Ota) getRequests(atype assetType, audienceID string) (reqs []pallasRequ
req.Build = o.Config.Build
}
if o.Config.Simulator {
req.RequestedBuild = o.Config.Build
}
if len(o.Config.Device) > 0 && len(o.Config.Model) == 0 {
dev, err := o.db.LookupDevice(o.Config.Device)
if err != nil {
@@ -561,6 +584,21 @@ func (o *Ota) GetPallasOTAs() ([]types.Asset, error) {
oassets := o.QueryPublicXML()
if o.Config.Simulator {
if o.Config.Version.Original() == "0" && o.Config.Build == "0" {
return nil, fmt.Errorf("you must supply: --build, --version or --latest WITH --sim")
} else if o.Config.Version.Original() != "0" && o.Config.Build == "0" {
dvt, err := GetDVTDownloadableIndex()
if err != nil {
return nil, fmt.Errorf("failed to get simulators index: %v", err)
}
o.Config.Build, err = dvt.LookupBuild(o.Config.Version.Original(), o.Config.Platform)
if err != nil {
return nil, fmt.Errorf("failed to lookup simulator build: %v", err)
}
}
}
pallasReqs, err := o.buildPallasRequests()
if err != nil {
return nil, fmt.Errorf("failed to build the pallas requests: %v", err)
@@ -618,6 +656,8 @@ func (o *Ota) GetPallasOTAs() ([]types.Asset, error) {
continue
}
// os.WriteFile("pallas.json", b64data, 0644)
res := ota{}
if err := json.Unmarshal(b64data, &res); err != nil {
log.Errorf("failed to unmarshall JSON: %v", err)
@@ -699,6 +739,16 @@ func (o *Ota) filterOTADevices(otas []types.Asset) []types.Asset { // FIXME: thi
var filteredDevices []string
var filteredOtas []types.Asset
if o.Config.Simulator {
for _, ota := range otas {
switch assetType(ota.AssetType) {
case iOsSimulatorUpdate, watchOsSimulatorUpdate, tvOsSimulatorUpdate, visionOaSimulatorUpdate:
filteredOtas = append(filteredOtas, ota)
}
}
return filteredOtas
}
if o.Config.Platform == "macos" {
if o.Config.Build != "0" && !o.Config.RSR {
for _, ota := range otas {
+23
View File
@@ -14,6 +14,7 @@ import (
"strings"
"time"
"github.com/apex/log"
"github.com/blacktop/go-plist"
"github.com/blacktop/ipsw/internal/utils"
)
@@ -24,6 +25,14 @@ const (
xcodeReleasesAPI = "https://xcodereleases.com/data.json"
)
var platforms = map[string]string{
"macos": "com.apple.platform.macosx",
"ios": "com.apple.platform.iphoneos",
"tvos": "com.apple.platform.appletvos",
"watchos": "com.apple.platform.watchos",
"visionos": "com.apple.platform.xros",
}
type Downloadable struct {
Authentication string `plist:"authentication,omitempty"`
Category string `plist:"category,omitempty"`
@@ -88,6 +97,20 @@ func GetDVTDownloadableIndex() (*DVTDownloadable, error) {
return &dvt, nil
}
func (d *DVTDownloadable) LookupBuild(version, platform string) (string, error) {
platform, ok := platforms[platform]
if !ok {
return "", fmt.Errorf("platform not supported: %s", platform)
}
for _, dl := range d.Downloadables {
if dl.SimulatorVersion.Version == version && dl.Platform == platform {
log.WithField("name", dl.Name).Debug("Simulator")
return dl.SimulatorVersion.BuildUpdate, nil
}
}
return "", fmt.Errorf("build not found for: %s", version)
}
type Contents struct {
Key string
Generation int64
+2
View File
@@ -46,6 +46,7 @@ type Asset struct {
AssetType string `json:"AssetType" plist:"AssetType,omitempty"`
BridgeVersionInfo bridgeVersionInfo `json:"BridgeVersionInfo" plist:"BridgeVersionInfo,omitempty"`
Build string `json:"Build" plist:"Build,omitempty"`
SimulatorVersion string `json:"SimulatorVersion" plist:"SimulatorVersion,omitempty"`
DataTemplateSize int `json:"DataTemplateSize" plist:"DataTemplateSize,omitempty"`
EAPFSEnabled bool `json:"EAPFSEnabled,omitempty" plist:"EAPFSEnabled,omitempty"`
InstallationSize string `json:"InstallationSize" plist:"InstallationSize,omitempty"`
@@ -83,6 +84,7 @@ type Asset struct {
IsZipStreamable bool `json:"_IsZipStreamable" plist:"_IsZipStreamable,omitempty"`
MasteredVersion string `json:"_MasteredVersion" plist:"_MasteredVersion,omitempty"`
Hash []byte `json:"_Measurement" plist:"_Measurement,omitempty"`
Sha256Hash []byte `json:"_Measurement-SHA256" plist:"_Measurement-SHA256,omitempty"`
HashAlgorithm string `json:"_MeasurementAlgorithm" plist:"_MeasurementAlgorithm,omitempty"`
UnarchivedSize int `json:"_UnarchivedSize" plist:"_UnarchivedSize,omitempty"`
AssetDefaultGarbageCollectionBehavior string `json:"__AssetDefaultGarbageCollectionBehavior" plist:"__AssetDefaultGarbageCollectionBehavior,omitempty"`