Files
ipsw/pkg/usb/mcinstall/mcinstall.go
blacktop 8e6f2c554b chore: modernize to 1.26
Refactor string concatenation to use strings.Builder for improved performance in multiple files
2026-02-12 10:02:01 -07:00

256 lines
8.4 KiB
Go

package mcinstall
import (
"fmt"
"os"
"strings"
"github.com/blacktop/go-plist"
"github.com/blacktop/ipsw/pkg/usb"
"github.com/blacktop/ipsw/pkg/usb/lockdownd"
"github.com/fatih/color"
)
const (
serviceName = "com.apple.mobile.MCInstall"
)
var colorHeader = color.New(color.FgHiBlue).SprintFunc()
var colorFaint = color.New(color.Faint, color.FgHiBlue).SprintFunc()
var colorBold = color.New(color.Bold).SprintFunc()
type Client struct {
c *usb.Client
}
func NewClient(udid string) (*Client, error) {
c, err := lockdownd.NewClientForService(serviceName, udid, false)
if err != nil {
return nil, err
}
return &Client{
c: c,
}, nil
}
func (c *Client) Close() error {
return c.c.Close()
}
type ProfileInfo struct {
profileSendResponse
IDs []string `plist:"OrderedIdentifiers,omitempty" json:"ids,omitempty"`
Manifests map[string]ProfileManifest `plist:"ProfileManifest,omitempty" json:"manifests,omitempty"`
Metadatas map[string]ProfileMetadata `plist:"ProfileMetadata,omitempty" json:"metadatas,omitempty"`
Status string `plist:"Status,omitempty" json:"status,omitempty"`
}
func (p ProfileInfo) String() string {
var out strings.Builder
for _, id := range p.IDs {
out.WriteString(fmt.Sprintf(colorHeader("[ %s ]\n"), id))
out.WriteString(colorHeader(" Manifest:\n"))
out.WriteString(fmt.Sprintf(colorFaint(" Description: ")+colorBold("%s\n"), p.Manifests[id].Description))
out.WriteString(fmt.Sprintf(colorFaint(" Active: ")+colorBold("%t\n"), p.Manifests[id].IsActive))
out.WriteString(colorHeader(" Metadata:\n"))
out.WriteString(fmt.Sprintf(colorFaint(" UUID: ")+colorBold("%s\n"), p.Metadatas[id].UUID))
out.WriteString(fmt.Sprintf(colorFaint(" Version: ")+colorBold("%d\n"), p.Metadatas[id].Version))
out.WriteString(fmt.Sprintf(colorFaint(" Name: ")+colorBold("%s\n"), p.Metadatas[id].Name))
out.WriteString(fmt.Sprintf(colorFaint(" Description: ")+colorBold("%s\n"), p.Metadatas[id].Description))
out.WriteString(fmt.Sprintf(colorFaint(" Organization: ")+colorBold("%s\n"), p.Metadatas[id].Organization))
out.WriteString(fmt.Sprintf(colorFaint(" RemovalDisallowed: ")+colorBold("%t\n"), p.Metadatas[id].RemovalDisallowed))
}
return out.String()
}
type ProfileMetadata struct {
Description string `plist:"PayloadDescription,omitempty" json:"desc,omitempty"`
Name string `plist:"PayloadDisplayName,omitempty" json:"name,omitempty"`
Organization string `plist:"PayloadOrganization,omitempty" json:"org,omitempty"`
RemovalDisallowed bool `plist:"PayloadRemovalDisallowed,omitempty" json:"removal_disallowed,omitempty"`
UUID string `plist:"PayloadUUID,omitempty" json:"uuid,omitempty"`
Version int `plist:"PayloadVersion,omitempty" json:"version,omitempty"`
}
type ProfileManifest struct {
Description string `plist:"Description,omitempty" json:"desc,omitempty"`
IsActive bool `plist:"IsActive,omitempty" json:"active,omitempty"`
}
type profileSendResponse struct {
CommandErrorArchive []byte `plist:"CommandErrorArchive,omitempty" json:"command_error_archive,omitempty"`
ErrorChain []struct {
ErrorCode int `plist:"ErrorCode,omitempty" json:"error_code,omitempty"`
ErrorDomain string `plist:"ErrorDomain,omitempty" json:"error_domain,omitempty"`
LocalizedDescription string `plist:"LocalizedDescription,omitempty" json:"localized_description,omitempty"`
USEnglishDescription string `plist:"USEnglishDescription,omitempty" json:"us_english_description,omitempty"`
} `plist:"ErrorChain,omitempty" json:"error_chain,omitempty"`
Status string `plist:"Status,omitempty" json:"status,omitempty"`
}
type profileRemoveRequest struct {
PayloadType string `plist:"PayloadType,omitempty"`
PayloadIdentifier string `plist:"PayloadIdentifier,omitempty"`
PayloadUUID string `plist:"PayloadUUID,omitempty"`
PayloadVersion int `plist:"PayloadVersion,omitempty"`
}
func (c *Client) Install(profilePath string) error {
dat, err := os.ReadFile(profilePath)
if err != nil {
return fmt.Errorf("failed to read profile: %s", err)
}
var resp profileSendResponse
if err := c.c.Request(&map[string]any{
"RequestType": "InstallProfile",
"Payload": dat,
}, &resp); err != nil {
return err
}
if resp.Status != "Acknowledged" {
return fmt.Errorf("failed to get cloud config: %#v", resp.ErrorChain)
}
return nil
}
func (c *Client) Upload(profilePath string) error { // FIXME: I'm not sure what this is for, but fails if I use it like Install
dat, err := os.ReadFile(profilePath)
if err != nil {
return fmt.Errorf("failed to read profile: %s", err)
}
var resp map[string]any
if err := c.c.Request(&map[string]any{
"RequestType": "StoreProfile",
"ProfileData": dat,
"Purpose": "PostSetupInstallation",
}, &resp); err != nil {
return err
}
if status, ok := resp["Status"]; !ok || status != "Acknowledged" {
return fmt.Errorf("failed to get cloud config: resp = %#v", resp)
}
return nil
}
func (c *Client) Remove(identifier string) error {
profiles, err := c.List()
if err != nil {
return err
}
for id, meta := range profiles.Metadatas {
if identifier == id {
dat, err := plist.Marshal(profileRemoveRequest{
PayloadType: "Configuration",
PayloadIdentifier: id,
PayloadUUID: meta.UUID,
PayloadVersion: meta.Version,
}, plist.XMLFormat)
if err != nil {
return err
}
var resp map[string]any
if err := c.c.Request(&map[string]any{
"RequestType": "RemoveProfile",
"ProfileIdentifier": dat,
}, &resp); err != nil {
return err
}
if status, ok := resp["Status"]; !ok || status != "Acknowledged" {
return fmt.Errorf("failed to get cloud config: resp=%#v", resp)
}
return nil
}
}
return fmt.Errorf("profile %s not found", identifier)
}
func (c *Client) List() (*ProfileInfo, error) {
var resp ProfileInfo
if err := c.c.Request(&map[string]any{
"RequestType": "GetProfileList",
}, &resp); err != nil {
return nil, err
}
if resp.Status != "Acknowledged" {
return nil, fmt.Errorf("failed to list profiles: resp=%#v", resp)
}
return &resp, nil
}
type CloudConfiguration struct {
AllowPairing bool `plist:"AllowPairing,omitempty" json:"allow_pairing,omitempty"`
CloudConfigurationUIComplete bool `plist:"CloudConfigurationUIComplete,omitempty" json:"cloud_conf_ui_complete,omitempty"`
ConfigurationSource int `plist:"ConfigurationSource,omitempty" json:"conf_source"`
ConfigurationWasApplied bool `plist:"ConfigurationWasApplied,omitempty" json:"conf_was_applied,omitempty"`
IsSupervised bool `plist:"IsSupervised,omitempty" json:"is_supervised,omitempty"`
PostSetupProfileWasInstalled bool `plist:"PostSetupProfileWasInstalled,omitempty" json:"post_setup_profile_was_installed,omitempty"`
}
func (cc *CloudConfiguration) String() string {
return fmt.Sprintf(
colorFaint("CloudConfiguration: ")+colorBold("%t\n")+
colorFaint("CloudConfigurationUIComplete: ")+colorBold("%t\n")+
colorFaint("ConfigurationSource: ")+colorBold("%d\n")+
colorFaint("ConfigurationWasApplied: ")+colorBold("%t\n")+
colorFaint("IsSupervised: ")+colorBold("%t\n")+
colorFaint("PostSetupProfileWasInstalled: ")+colorBold("%t\n"),
cc.AllowPairing,
cc.CloudConfigurationUIComplete,
cc.ConfigurationSource,
cc.ConfigurationWasApplied,
cc.IsSupervised,
cc.PostSetupProfileWasInstalled,
)
}
type cloudConfigurationResponse struct {
CloudConfiguration CloudConfiguration `plist:"CloudConfiguration,omitempty" json:"cloud_conf"`
Status string `plist:"Status,omitempty" json:"status,omitempty"`
}
func (c *Client) GetCloudConfig() (*CloudConfiguration, error) {
var resp cloudConfigurationResponse
if err := c.c.Request(&map[string]any{
"RequestType": "GetCloudConfiguration",
}, &resp); err != nil {
return nil, err
}
if resp.Status != "Acknowledged" {
return nil, fmt.Errorf("failed to get cloud config: resp=%#v", resp)
}
return &resp.CloudConfiguration, nil
}
func (c *Client) SetWifiPowerState(turnON bool) error {
var resp map[string]any
if err := c.c.Request(&map[string]any{
"RequestType": "SetWiFiPowerState",
"PowerState": turnON,
}, &resp); err != nil {
return err
}
if status, ok := resp["Status"]; !ok || status != "Acknowledged" {
return fmt.Errorf("failed to get cloud config: resp=%#v", resp)
}
return nil
}