From 35366ddf5f15ef23b6a0f4f5332afd675190c853 Mon Sep 17 00:00:00 2001 From: blacktop Date: Sun, 4 Aug 2024 14:43:43 -0600 Subject: [PATCH] feat: add new 'hidden' `ipsw dinfo` cmd --- cmd/ipsw/cmd/deviceInfo.go | 108 ++++++++++++++++++++++++++++++ pkg/info/db.go | 132 +++++++++++++++++++++++++++++++++++++ pkg/info/info.go | 27 ++++++++ 3 files changed, 267 insertions(+) create mode 100644 cmd/ipsw/cmd/deviceInfo.go diff --git a/cmd/ipsw/cmd/deviceInfo.go b/cmd/ipsw/cmd/deviceInfo.go new file mode 100644 index 000000000..458e53fde --- /dev/null +++ b/cmd/ipsw/cmd/deviceInfo.go @@ -0,0 +1,108 @@ +/* +Copyright © 2024 blacktop + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "encoding/json" + "fmt" + + "github.com/apex/log" + "github.com/blacktop/ipsw/pkg/info" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +func init() { + rootCmd.AddCommand(deviceInfoCmd) + + deviceInfoCmd.Flags().StringP("name", "n", "", "Name of device to lookup info for (e.g., 'iPhone 14 Pro')") + deviceInfoCmd.Flags().StringP("prod", "d", "", "Device to lookup info for (e.g., 'iPhone15,2')") + deviceInfoCmd.Flags().StringP("model", "m", "", "Model to lookup info for (e.g., 'M1,1')") + deviceInfoCmd.Flags().StringP("board", "b", "", "Board to lookup info for") + deviceInfoCmd.Flags().StringP("cpu", "c", "", "CPID to lookup info for") + deviceInfoCmd.Flags().StringP("platform", "p", "", "Platform to lookup info for") + deviceInfoCmd.Flags().String("cpid", "", "CPID to lookup info for") + deviceInfoCmd.Flags().StringP("bdid", "i", "", "BDID to lookup info for") + deviceInfoCmd.Flags().BoolP("json", "j", false, "Output as JSON") + viper.BindPFlag("device-info.name", deviceInfoCmd.Flags().Lookup("name")) + viper.BindPFlag("device-info.prod", deviceInfoCmd.Flags().Lookup("prod")) + viper.BindPFlag("device-info.model", deviceInfoCmd.Flags().Lookup("model")) + viper.BindPFlag("device-info.board", deviceInfoCmd.Flags().Lookup("board")) + viper.BindPFlag("device-info.cpu", deviceInfoCmd.Flags().Lookup("cpu")) + viper.BindPFlag("device-info.platform", deviceInfoCmd.Flags().Lookup("platform")) + viper.BindPFlag("device-info.cpid", deviceInfoCmd.Flags().Lookup("cpid")) + viper.BindPFlag("device-info.bdid", deviceInfoCmd.Flags().Lookup("bdid")) + viper.BindPFlag("device-info.json", deviceInfoCmd.Flags().Lookup("json")) +} + +// deviceInfoCmd represents the deviceInfo command +var deviceInfoCmd = &cobra.Command{ + Use: "device-info", + Aliases: []string{"di", "dinfo", "dev-inf"}, + Short: "Lookup device info", + Args: cobra.MaximumNArgs(1), + SilenceUsage: true, + SilenceErrors: true, + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) (err error) { + + if Verbose { + log.SetLevel(log.DebugLevel) + } + + // procs, err := info.GetProcessorDB() + // if err != nil { + // return fmt.Errorf("failed to get processor DB: %v", err) + // } + // _ = procs + + db, err := info.GetIpswDB() + if err != nil { + return err + } + + devs := db.Query(&info.DeviceQuery{ + Name: viper.GetString("device-info.name"), + Prod: viper.GetString("device-info.prod"), + Model: viper.GetString("device-info.model"), + Board: viper.GetString("device-info.board"), + CPU: viper.GetString("device-info.cpu"), + Platform: viper.GetString("device-info.platform"), + CPID: viper.GetString("device-info.cpid"), + BDID: viper.GetString("device-info.bdid"), + }) + + if viper.GetBool("device-info.json") { + dat, err := json.Marshal(devs) + if err != nil { + return err + } + fmt.Println(string(dat)) + } else { + for _, dev := range *devs { + fmt.Println(dev) + } + } + + return nil + }, +} diff --git a/pkg/info/db.go b/pkg/info/db.go index 609d7e092..8370d9dae 100644 --- a/pkg/info/db.go +++ b/pkg/info/db.go @@ -12,6 +12,7 @@ import ( "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 @@ -32,6 +33,7 @@ type Board struct { 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"` @@ -41,6 +43,52 @@ type Device struct { 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 @@ -57,6 +105,90 @@ func GetIpswDB() (*Devices, error) { 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 { + if strings.EqualFold(dev.Name, q.Name) || strings.EqualFold(dev.Description, q.Name) { + db[prod] = dev + goto next + } + } + // Prod + if len(q.Prod) > 0 && strings.EqualFold(prod, q.Prod) { + db[prod] = dev + goto next + } + // Model + if len(q.Model) > 0 { + for m := range dev.Boards { + if strings.EqualFold(m, q.Model) { + db[prod] = dev + goto next + } + } + } + // Board + if len(q.Board) > 0 { + for id := range dev.Boards { + if strings.EqualFold(id, q.Board) { + db[prod] = dev + goto next + } + } + } + // CPU + if len(q.CPU) > 0 { + for _, b := range dev.Boards { + if strings.EqualFold(b.CPU, q.CPU) { + db[prod] = dev + goto next + } + } + } + // Platform + if len(q.Platform) > 0 { + for _, b := range dev.Boards { + if strings.EqualFold(b.Platform, q.Platform) { + db[prod] = dev + goto next + } + } + } + // CPID + if len(q.CPID) > 0 { + for _, b := range dev.Boards { + if strings.EqualFold(b.ChipID, q.CPID) { + db[prod] = dev + goto next + } + } + } + // BDID + if len(q.BDID) > 0 { + 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 diff --git a/pkg/info/info.go b/pkg/info/info.go index b384367bc..b8037e849 100644 --- a/pkg/info/info.go +++ b/pkg/info/info.go @@ -78,6 +78,33 @@ type processors struct { Devices []string } +type ProcessorDB []processors + +func GetProcessorDB() (*ProcessorDB, error) { + var ps ProcessorDB + + zr, err := gzip.NewReader(bytes.NewReader(procsData)) + if err != nil { + return nil, err + } + defer zr.Close() + + if err := json.NewDecoder(zr).Decode(&ps); err != nil { + return nil, fmt.Errorf("failed unmarshaling procs.gz data: %w", err) + } + + return &ps, nil +} + +func (p *ProcessorDB) GetProcessor(cpuid string) (*processors, error) { + for _, proc := range *p { + if strings.EqualFold(proc.CPUID, cpuid) { + return &proc, nil + } + } + return nil, fmt.Errorf("failed to find processor for '%s'", cpuid) +} + // getProcessors reads the processors from embedded JSON func getProcessor(cpuid string) (processors, error) { var ps []processors