mirror of
https://github.com/blacktop/ipsw.git
synced 2026-06-07 12:27:36 +00:00
555 lines
16 KiB
Go
555 lines
16 KiB
Go
package info
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/blacktop/ipsw/internal/utils"
|
|
"github.com/blacktop/ipsw/pkg/ota/types"
|
|
"github.com/blacktop/ipsw/pkg/xcode"
|
|
"github.com/fatih/color"
|
|
)
|
|
|
|
//go:embed data/ipsw_db.gz
|
|
var ipswDbData []byte
|
|
|
|
type Board struct {
|
|
CPU string `json:"cpu,omitempty"`
|
|
Platform string `json:"platform,omitempty"`
|
|
PlatformName string `json:"platform_name,omitempty"`
|
|
ChipID string `json:"cpuid,omitempty"`
|
|
Arch string `json:"arch,omitempty"`
|
|
CpuISA string `json:"cpuisa,omitempty"`
|
|
BoardID string `json:"board_id,omitempty"`
|
|
BasebandChipID string `json:"bbid,omitempty"`
|
|
KernelCacheType string `json:"kc_type,omitempty"`
|
|
ResearchSupported bool `json:"research_support,omitempty"`
|
|
}
|
|
|
|
type Device struct {
|
|
Name string `json:"name,omitempty"`
|
|
Product string `json:"product,omitempty"`
|
|
Description string `json:"desc,omitempty"`
|
|
Boards map[string]Board `json:"boards,omitempty"`
|
|
MemClass uint64 `json:"mem_class,omitempty"`
|
|
SDKPlatform string `json:"sdk,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
}
|
|
|
|
type Devices map[string]Device
|
|
|
|
var colorName = color.New(color.Bold).SprintFunc()
|
|
var colorBoard = color.New(color.Bold, color.FgHiMagenta).SprintFunc()
|
|
var colorField = color.New(color.Bold, color.FgHiBlue).SprintFunc()
|
|
|
|
func (d Device) String() string {
|
|
var sb strings.Builder
|
|
sb.WriteString("\n" + colorName(d.Name) + "\n")
|
|
if len(d.Description) > 0 {
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Description"), d.Description))
|
|
}
|
|
if len(d.Product) > 0 {
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Prod"), d.Product))
|
|
}
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Type"), d.Type))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("SDK"), d.SDKPlatform))
|
|
sb.WriteString(fmt.Sprintf(" %s: %d\n", colorField("Memory Class"), d.MemClass))
|
|
sb.WriteString(fmt.Sprintf(" %s:\n", colorField("Boards")))
|
|
for board, b := range d.Boards {
|
|
sb.WriteString(fmt.Sprintf(" %s:\n", colorBoard(board)))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("CPU"), b.CPU))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("CPU ISA"), b.CpuISA))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Chip ID"), b.ChipID))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Platform"), b.Platform))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Platform Name"), b.PlatformName))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Arch"), b.Arch))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Board ID"), b.BoardID))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Baseband Chip ID"), b.BasebandChipID))
|
|
sb.WriteString(fmt.Sprintf(" %s: %s\n", colorField("Kernel Cache Type"), b.KernelCacheType))
|
|
if b.ResearchSupported {
|
|
sb.WriteString(fmt.Sprintf(" %s: %t\n", colorField("Research Supported"), b.ResearchSupported))
|
|
}
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
type DeviceQuery struct {
|
|
Name string
|
|
Prod string
|
|
Model string
|
|
Board string
|
|
CPU string
|
|
Platform string
|
|
CPID string
|
|
BDID string
|
|
}
|
|
|
|
func GetIpswDB() (*Devices, error) {
|
|
var db Devices
|
|
|
|
zr, err := gzip.NewReader(bytes.NewReader(ipswDbData))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer zr.Close()
|
|
|
|
if err := json.NewDecoder(zr).Decode(&db); err != nil {
|
|
return nil, fmt.Errorf("failed unmarshaling ipsw_db data: %w", err)
|
|
}
|
|
|
|
return &db, nil
|
|
}
|
|
|
|
func (ds Devices) Query(q *DeviceQuery) *Devices {
|
|
db := make(Devices)
|
|
for prod, dev := range ds {
|
|
dev.Product = prod
|
|
if strings.EqualFold(dev.Name, "iFPGA") ||
|
|
strings.HasSuffix(strings.ToLower(dev.Name), "sim") ||
|
|
strings.HasSuffix(strings.ToLower(dev.Name), "xxx") ||
|
|
strings.HasSuffix(strings.ToLower(dev.Name), "ref") {
|
|
continue
|
|
}
|
|
if len(dev.Type) == 0 || strings.EqualFold(dev.Type, "unknown") {
|
|
continue
|
|
}
|
|
// Name
|
|
if len(q.Name) > 0 {
|
|
// Try regex matching first
|
|
if re, err := regexp.Compile("(?i)" + q.Name); err == nil {
|
|
if re.MatchString(dev.Name) || re.MatchString(dev.Description) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
} else {
|
|
// Fallback to exact matching if regex is invalid
|
|
if strings.EqualFold(dev.Name, q.Name) || strings.EqualFold(dev.Description, q.Name) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
// Prod
|
|
if len(q.Prod) > 0 {
|
|
if re, err := regexp.Compile("(?i)" + q.Prod); err == nil {
|
|
if re.MatchString(prod) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
} else {
|
|
if strings.EqualFold(prod, q.Prod) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
// Model
|
|
if len(q.Model) > 0 {
|
|
if re, err := regexp.Compile("(?i)" + q.Model); err == nil {
|
|
for m := range dev.Boards {
|
|
if re.MatchString(m) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
} else {
|
|
for m := range dev.Boards {
|
|
if strings.EqualFold(m, q.Model) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Board
|
|
if len(q.Board) > 0 {
|
|
if re, err := regexp.Compile("(?i)" + q.Board); err == nil {
|
|
for id := range dev.Boards {
|
|
if re.MatchString(id) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
} else {
|
|
for id := range dev.Boards {
|
|
if strings.EqualFold(id, q.Board) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// CPU
|
|
if len(q.CPU) > 0 {
|
|
if re, err := regexp.Compile("(?i)" + q.CPU); err == nil {
|
|
for _, b := range dev.Boards {
|
|
if re.MatchString(b.CPU) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
} else {
|
|
for _, b := range dev.Boards {
|
|
if strings.EqualFold(b.CPU, q.CPU) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Platform
|
|
if len(q.Platform) > 0 {
|
|
if re, err := regexp.Compile("(?i)" + q.Platform); err == nil {
|
|
for _, b := range dev.Boards {
|
|
if re.MatchString(b.Platform) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
} else {
|
|
for _, b := range dev.Boards {
|
|
if strings.EqualFold(b.Platform, q.Platform) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// CPID
|
|
if len(q.CPID) > 0 {
|
|
if re, err := regexp.Compile("(?i)" + q.CPID); err == nil {
|
|
for _, b := range dev.Boards {
|
|
if re.MatchString(b.ChipID) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
} else {
|
|
for _, b := range dev.Boards {
|
|
if strings.EqualFold(b.ChipID, q.CPID) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// BDID
|
|
if len(q.BDID) > 0 {
|
|
if re, err := regexp.Compile("(?i)" + q.BDID); err == nil {
|
|
for _, b := range dev.Boards {
|
|
if re.MatchString(b.BoardID) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
} else {
|
|
for _, b := range dev.Boards {
|
|
if strings.EqualFold(b.BoardID, q.BDID) {
|
|
db[prod] = dev
|
|
goto next
|
|
}
|
|
}
|
|
}
|
|
}
|
|
next:
|
|
}
|
|
return &db
|
|
}
|
|
|
|
func (ds Devices) LookupDevice(prod string) (Device, error) {
|
|
if d, ok := ds[prod]; ok {
|
|
return d, nil
|
|
}
|
|
return Device{}, fmt.Errorf("device %s not found", prod)
|
|
}
|
|
|
|
func (ds Devices) GetProductForModel(model string) (string, error) {
|
|
for prod, dev := range ds {
|
|
for m := range dev.Boards {
|
|
if strings.EqualFold(m, model) {
|
|
return prod, nil
|
|
}
|
|
}
|
|
}
|
|
return "", fmt.Errorf("model %s not found", model)
|
|
}
|
|
|
|
func (ds Devices) GetDevicesForType(typ string) (*Devices, error) {
|
|
devs := make(Devices)
|
|
for prod, dev := range ds {
|
|
if dev.Type == typ {
|
|
devs[prod] = dev
|
|
}
|
|
}
|
|
return &devs, nil
|
|
}
|
|
|
|
func (ds Devices) GetDeviceForName(name string) (string, Device, error) {
|
|
for prod, dev := range ds {
|
|
if strings.EqualFold(dev.Name, name) || strings.EqualFold(dev.Description, name) {
|
|
return prod, dev, nil
|
|
}
|
|
}
|
|
return "", Device{}, fmt.Errorf("device not found with name %s", name)
|
|
}
|
|
|
|
func (ds Devices) GetDevicesForSDK(sdk string) (*Devices, error) {
|
|
devs := make(Devices)
|
|
for prod, dev := range ds {
|
|
if dev.SDKPlatform == sdk {
|
|
devs[prod] = dev
|
|
}
|
|
}
|
|
return &devs, nil
|
|
}
|
|
|
|
func (i *Info) GetDevices(devs *Devices) error {
|
|
if len(i.DeviceTrees) > 0 {
|
|
for _, dtree := range i.DeviceTrees {
|
|
dt, err := dtree.Summary()
|
|
if err != nil {
|
|
return fmt.Errorf("error getting device tree summary: %v", err)
|
|
}
|
|
|
|
var kctype string
|
|
if kcs := i.Plists.BuildManifest.GetKernelForModel(strings.ToLower(dt.BoardConfig)); kcs != nil {
|
|
if len(kcs) == 1 {
|
|
kctype = kcs[0][strings.LastIndex(kcs[0], ".")+1:]
|
|
} else {
|
|
kctype = kcs[0][strings.LastIndex(kcs[0], ".")+1:] // FIXME: what?
|
|
}
|
|
}
|
|
|
|
xdev, err := xcode.GetDeviceForProd(dt.ProductType)
|
|
if err != nil {
|
|
xdev = &xcode.Device{}
|
|
log.Errorf("error getting device %s in xcode device list: %v", dt.ProductType, err)
|
|
// return fmt.Errorf("error getting device %s in xcode device list: %v", dt.ProductType, err)
|
|
}
|
|
|
|
devType := "unknown"
|
|
devSDK := "unknown"
|
|
switch {
|
|
case strings.HasPrefix(dt.ProductType, "iPod"):
|
|
fallthrough
|
|
case strings.HasPrefix(dt.ProductType, "iPad"):
|
|
fallthrough
|
|
case strings.HasPrefix(dt.ProductType, "iPhone"):
|
|
devType = "ios"
|
|
devSDK = "iphoneos"
|
|
case strings.HasPrefix(dt.ProductType, "Watch"):
|
|
devType = "watchos"
|
|
devSDK = "watchos"
|
|
case strings.HasPrefix(dt.ProductType, "AudioAccessory"):
|
|
devType = "audioos"
|
|
devSDK = "appletvos"
|
|
case strings.HasPrefix(dt.ProductType, "AppleTV"):
|
|
devType = "tvos"
|
|
devSDK = "appletvos"
|
|
case strings.HasPrefix(dt.ProductType, "Mac"):
|
|
devType = "macos"
|
|
devSDK = "macosx"
|
|
case strings.HasPrefix(dt.ProductType, "AppleDisplay"):
|
|
devType = "accessory"
|
|
devSDK = "iphoneos"
|
|
}
|
|
|
|
if len(dt.ProductType) > 0 {
|
|
if _, ok := (*devs)[dt.ProductType]; !ok {
|
|
if dt.ProductName != dt.ProductDescription {
|
|
(*devs)[dt.ProductType] = Device{
|
|
Name: dt.ProductName,
|
|
Description: dt.ProductDescription,
|
|
Boards: make(map[string]Board),
|
|
MemClass: uint64(xdev.DeviceTrait.DevicePerformanceMemoryClass),
|
|
Type: devType,
|
|
SDKPlatform: devSDK,
|
|
}
|
|
} else {
|
|
(*devs)[dt.ProductType] = Device{
|
|
Name: dt.ProductName,
|
|
Boards: make(map[string]Board),
|
|
MemClass: uint64(xdev.DeviceTrait.DevicePerformanceMemoryClass),
|
|
Type: devType,
|
|
SDKPlatform: devSDK,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if i.Plists.Restore != nil && i.Plists.BuildManifest != nil {
|
|
if dev := i.Plists.GetDeviceForBoardConfig(dt.BoardConfig); dev != nil {
|
|
proc := resolveProcessor(dev.Platform, fmt.Sprintf("%s/%s", dt.ProductType, dt.BoardConfig))
|
|
(*devs)[dt.ProductType].Boards[dt.BoardConfig] = Board{
|
|
CPU: proc.Name,
|
|
Platform: dev.Platform,
|
|
PlatformName: dt.SocGeneration,
|
|
ChipID: fmt.Sprintf("%#x", dev.CPID),
|
|
CpuISA: proc.CPUISA,
|
|
Arch: xdev.DeviceTrait.PreferredArchitecture,
|
|
BoardID: fmt.Sprintf("%#x", dev.BDID),
|
|
BasebandChipID: i.Plists.BuildManifest.BuildIdentities[0].BbChipID,
|
|
KernelCacheType: kctype,
|
|
// ResearchSupported: d.ResearchSupported,
|
|
}
|
|
}
|
|
} else {
|
|
return fmt.Errorf("no restore/BuildManifest plist found for %s", dt.ProductType)
|
|
}
|
|
}
|
|
} else {
|
|
if i.Plists.Restore != nil {
|
|
var prodType string
|
|
var prodName string
|
|
var arch string
|
|
var memClass uint64
|
|
if len(i.Plists.Restore.SupportedProductTypes) == 1 {
|
|
prodType = i.Plists.Restore.SupportedProductTypes[0]
|
|
} else {
|
|
prodType = i.Plists.Restore.SupportedProductTypes[0]
|
|
log.Error("BAD ASSUMPTIONS: multiple product types in restore plist")
|
|
}
|
|
kcs := i.Plists.BuildManifest.GetKernelCaches()
|
|
for _, dev := range i.Plists.Restore.DeviceMap {
|
|
if xdev, err := xcode.GetDeviceForProd(prodType); err == nil {
|
|
prodName = xdev.ProductDescription
|
|
arch = xdev.DeviceTrait.PreferredArchitecture
|
|
memClass = uint64(xdev.DeviceTrait.DevicePerformanceMemoryClass)
|
|
}
|
|
if len(prodName) == 0 {
|
|
if i.Plists.OTAInfo != nil {
|
|
prodName = "FIXME - " + i.Plists.OTAInfo.MobileAssetProperties.DeviceName
|
|
} else {
|
|
prodName = "FIXME"
|
|
}
|
|
}
|
|
if _, ok := (*devs)[prodType]; !ok {
|
|
proc := resolveProcessor(dev.Platform, fmt.Sprintf("%s/%s", prodType, strings.ToUpper(dev.BoardConfig)))
|
|
(*devs)[prodType] = Device{
|
|
Name: prodName,
|
|
Boards: map[string]Board{
|
|
strings.ToUpper(dev.BoardConfig): {
|
|
CPU: proc.Name,
|
|
Platform: dev.Platform,
|
|
// PlatformName: d.PlatformName,
|
|
ChipID: i.Plists.BuildManifest.BuildIdentities[0].ApChipID,
|
|
CpuISA: proc.CPUISA,
|
|
Arch: arch,
|
|
BoardID: i.Plists.BuildManifest.BuildIdentities[0].ApBoardID,
|
|
BasebandChipID: i.Plists.BuildManifest.BuildIdentities[0].BbChipID,
|
|
KernelCacheType: kcs[dev.BoardConfig][0][strings.LastIndex(kcs[dev.BoardConfig][0], ".")+1:],
|
|
// ResearchSupported: d.ResearchSupported,
|
|
},
|
|
},
|
|
MemClass: memClass,
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
panic("unsupported IPSW/OTA type")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func mLBTypeToBoardConfig(bc string, mlbType string) string {
|
|
return mlbType + strings.ToUpper(strings.TrimPrefix(bc, strings.ToLower(mlbType)))
|
|
}
|
|
|
|
func resolveProcessor(cpuid, context string) processors {
|
|
proc, err := getProcessor(cpuid)
|
|
if err != nil {
|
|
log.WithError(err).WithFields(log.Fields{
|
|
"cpuid": cpuid,
|
|
"context": context,
|
|
}).Warn("missing processor metadata; using CPU ID fallback")
|
|
return processors{Name: cpuid}
|
|
}
|
|
if len(proc.Name) == 0 {
|
|
log.WithFields(log.Fields{
|
|
"cpuid": cpuid,
|
|
"context": context,
|
|
}).Warn("processor metadata missing name; using CPU ID fallback")
|
|
proc.Name = cpuid
|
|
}
|
|
return proc
|
|
}
|
|
|
|
func (i *Info) GetDevicesFromMap(dmap *types.DeviceMap, devs *Devices) error {
|
|
for bc, d := range *dmap {
|
|
if len(d.ProductType) > 0 {
|
|
if _, ok := (*devs)[d.ProductType]; !ok {
|
|
var err error
|
|
var memClass uint64
|
|
if len(d.DevicePerformanceMemoryClass) > 0 {
|
|
memClass, err = utils.ConvertStrToInt(d.DevicePerformanceMemoryClass)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse int: %v", err)
|
|
}
|
|
}
|
|
devType := "unknown"
|
|
switch {
|
|
case strings.HasPrefix(d.ProductType, "iPod"):
|
|
fallthrough
|
|
case strings.HasPrefix(d.ProductType, "iPad"):
|
|
fallthrough
|
|
case strings.HasPrefix(d.ProductType, "iPhone"):
|
|
devType = "ios"
|
|
case strings.HasPrefix(d.ProductType, "Watch"):
|
|
devType = "watchos"
|
|
case strings.HasPrefix(d.ProductType, "AudioAccessory"):
|
|
devType = "audioos"
|
|
case strings.HasPrefix(d.ProductType, "AppleTV") || d.SDKPlatform == "appletvos":
|
|
devType = "tvos"
|
|
case strings.HasPrefix(d.ProductType, "Mac") || d.SDKPlatform == "macosx":
|
|
devType = "macos"
|
|
case strings.HasPrefix(d.ProductType, "AppleDisplay"):
|
|
devType = "accessory"
|
|
}
|
|
if d.ProductName != d.ProductDescription {
|
|
(*devs)[d.ProductType] = Device{
|
|
Name: d.ProductName,
|
|
Description: d.ProductDescription,
|
|
Boards: make(map[string]Board),
|
|
MemClass: memClass,
|
|
SDKPlatform: d.SDKPlatform,
|
|
Type: devType,
|
|
}
|
|
} else {
|
|
(*devs)[d.ProductType] = Device{
|
|
Name: d.ProductName,
|
|
Boards: make(map[string]Board),
|
|
MemClass: memClass,
|
|
SDKPlatform: d.SDKPlatform,
|
|
Type: devType,
|
|
}
|
|
}
|
|
}
|
|
proc := resolveProcessor(d.Platform, fmt.Sprintf("%s/%s", d.ProductType, bc))
|
|
(*devs)[d.ProductType].Boards[mLBTypeToBoardConfig(bc, d.MLBType)] = Board{
|
|
CPU: proc.Name,
|
|
Platform: d.Platform,
|
|
PlatformName: d.PlatformName,
|
|
ChipID: d.ChipID,
|
|
CpuISA: proc.CPUISA,
|
|
Arch: d.KernelMachOArchitecture,
|
|
BoardID: d.BoardID,
|
|
BasebandChipID: d.BasebandChipID,
|
|
KernelCacheType: d.KernelCacheType,
|
|
ResearchSupported: d.ResearchSupported,
|
|
}
|
|
} else {
|
|
log.Debugf("Board %s has no product type", bc)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|