package main import ( "archive/zip" "bytes" "encoding/json" "fmt" "io" "net/http" "os" "regexp" "strings" "time" ) // Magisk-assisted boot patching (gated behind a Settings toggle in the UI). // We use the robust, version-agnostic flow: extract boot/init_boot from the // factory image, push it to the phone, let the installed Magisk app patch it // (one tap), then pull the patched image back to live-boot or flash. This works // without pre-existing root and survives Magisk version changes. var magiskPackages = []string{ "com.topjohnwu.magisk", // official "io.github.huskydg.magisk", // delta "io.github.vvb2060.magisk", // alpha } // BootImages holds local temp paths of the boot images pulled out of a factory // zip ("" when absent — modern Pixels patch init_boot, older ones boot). type BootImages struct { Boot string `json:"boot"` InitBoot string `json:"initBoot"` Source string `json:"source"` } // MagiskInstalled returns the Magisk package name on the device, or an error. func (a *App) MagiskInstalled() (string, error) { for _, pkg := range magiskPackages { out, err := a.runAdbShell("pm", "path", pkg) if err == nil && strings.Contains(out, "package:") { return pkg, nil } } return "", fmt.Errorf("Magisk app not found on device — install Magisk first") } // InstallMagisk downloads the latest official Magisk APK from GitHub and // installs it on the device — so the root flow is self-contained (ATK does not // bundle Magisk; it fetches it on demand). func (a *App) InstallMagisk() (string, error) { if err := a.requireDangerUnlocked(); err != nil { return "", err } client := &http.Client{Timeout: 5 * time.Minute} req, _ := http.NewRequest("GET", "https://api.github.com/repos/topjohnwu/Magisk/releases/latest", nil) req.Header.Set("User-Agent", "ATK") req.Header.Set("Accept", "application/vnd.github+json") resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("could not reach GitHub: %w", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return "", fmt.Errorf("GitHub API returned %d (rate limited? try again later)", resp.StatusCode) } var rel struct { TagName string `json:"tag_name"` Assets []struct { Name string `json:"name"` URL string `json:"browser_download_url"` } `json:"assets"` } if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil { return "", fmt.Errorf("could not parse release info: %w", err) } var apkURL string for _, as := range rel.Assets { if strings.HasSuffix(strings.ToLower(as.Name), ".apk") { apkURL = as.URL break } } if apkURL == "" { return "", fmt.Errorf("no APK in latest Magisk release") } dreq, _ := http.NewRequest("GET", apkURL, nil) dreq.Header.Set("User-Agent", "ATK") dresp, err := client.Do(dreq) if err != nil { return "", fmt.Errorf("download failed: %w", err) } defer dresp.Body.Close() tmp, err := os.CreateTemp("", "magisk-*.apk") if err != nil { return "", err } defer os.Remove(tmp.Name()) if _, err := io.Copy(tmp, dresp.Body); err != nil { tmp.Close() return "", fmt.Errorf("download write failed: %w", err) } tmp.Close() if _, err := a.runCommandTimeout(3*time.Minute, "adb", "install", "-r", tmp.Name()); err != nil { return "", fmt.Errorf("adb install failed: %w", err) } return fmt.Sprintf("Installed Magisk %s — open it once on the phone to finish setup.", rel.TagName), nil } // ExtractBootImages pulls boot.img / init_boot.img out of a Pixel factory zip // (the nested image-*.zip) into local temp files. func (a *App) ExtractBootImages(zipPath string) (BootImages, error) { var res BootImages r, err := zip.OpenReader(zipPath) if err != nil { return res, fmt.Errorf("cannot open zip: %w", err) } defer r.Close() var imgZip *zip.File for _, f := range r.File { base := f.Name if i := strings.LastIndex(base, "/"); i >= 0 { base = base[i+1:] } if strings.HasPrefix(base, "image-") && strings.HasSuffix(base, ".zip") { imgZip = f res.Source = base break } } if imgZip == nil { return res, fmt.Errorf("no image-*.zip inside — is this a Pixel factory image?") } rc, err := imgZip.Open() if err != nil { return res, err } data, err := io.ReadAll(rc) rc.Close() if err != nil { return res, err } zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data))) if err != nil { return res, fmt.Errorf("cannot read inner image zip: %w", err) } for _, f := range zr.File { switch f.Name { case "boot.img": if p, e := extractZipEntryToTemp(f, "atk-boot-*.img"); e == nil { res.Boot = p } case "init_boot.img": if p, e := extractZipEntryToTemp(f, "atk-initboot-*.img"); e == nil { res.InitBoot = p } } } if res.Boot == "" && res.InitBoot == "" { return res, fmt.Errorf("no boot/init_boot image found in factory image") } return res, nil } func extractZipEntryToTemp(f *zip.File, pattern string) (string, error) { rc, err := f.Open() if err != nil { return "", err } defer rc.Close() tmp, err := os.CreateTemp("", pattern) if err != nil { return "", err } defer tmp.Close() if _, err := io.Copy(tmp, rc); err != nil { return "", err } return tmp.Name(), nil } // PushImageToDevice copies a local image into /sdcard/Download for Magisk to // patch, returning the remote path. func (a *App) PushImageToDevice(localPath string) (string, error) { if strings.TrimSpace(localPath) == "" { return "", fmt.Errorf("no image to push") } remote := "/sdcard/Download/" + baseName(localPath) if _, err := a.runCommandTimeout(5*time.Minute, "adb", "push", localPath, remote); err != nil { return "", fmt.Errorf("push failed: %w", err) } return remote, nil } // OpenMagisk launches the Magisk app on the device. func (a *App) OpenMagisk() error { pkg, err := a.MagiskInstalled() if err != nil { return err } if _, err := a.runAdbShell("monkey", "-p", pkg, "-c", "android.intent.category.LAUNCHER", "1"); err != nil { return fmt.Errorf("could not open Magisk: %w", err) } return nil } // ── Magisk module management (requires root / su) ────────────────────────── type MagiskModule struct { Id string `json:"id"` Name string `json:"name"` Version string `json:"version"` Author string `json:"author"` Description string `json:"description"` Enabled bool `json:"enabled"` } var moduleIdRe = regexp.MustCompile(`^[A-Za-z0-9._-]+$`) func validModuleId(id string) error { if !moduleIdRe.MatchString(id) { return fmt.Errorf("invalid module id") } return nil } // ListMagiskModules reads /data/adb/modules via su. Returns an error if the // device isn't rooted (su unavailable or not granted to shell). func (a *App) ListMagiskModules() ([]MagiskModule, error) { script := `for d in /data/adb/modules/*/; do [ -d "$d" ] || continue; echo "===MODULE==="; echo "dir=$(basename "$d")"; if [ -f "$d/disable" ]; then echo "disabled=1"; else echo "disabled=0"; fi; cat "$d/module.prop" 2>/dev/null; done` out, err := a.runAdbShell("su", "-c", shellQuote(script)) if err != nil { return nil, fmt.Errorf("could not read modules — device must be rooted, and shell granted root in Magisk") } var mods []MagiskModule for _, b := range strings.Split(out, "===MODULE===") { b = strings.TrimSpace(b) if b == "" { continue } m := MagiskModule{Enabled: true} for _, line := range strings.Split(b, "\n") { k, v, ok := strings.Cut(strings.TrimSpace(line), "=") if !ok { continue } switch k { case "dir": m.Id = v case "disabled": if v == "1" { m.Enabled = false } case "id": if v != "" { m.Id = v } case "name": m.Name = v case "version": m.Version = v case "author": m.Author = v case "description": m.Description = v } } if m.Id != "" { if m.Name == "" { m.Name = m.Id } mods = append(mods, m) } } return mods, nil } // ToggleMagiskModule enables/disables a module (Magisk applies on next reboot). func (a *App) ToggleMagiskModule(id string, enable bool) (string, error) { if err := validModuleId(id); err != nil { return "", err } cmd := "touch /data/adb/modules/" + id + "/disable" if enable { cmd = "rm -f /data/adb/modules/" + id + "/disable" } if _, err := a.runAdbShell("su", "-c", shellQuote(cmd)); err != nil { return "", fmt.Errorf("failed: %w", err) } state := "disabled" if enable { state = "enabled" } return fmt.Sprintf("%s %s — reboot to apply", id, state), nil } // RemoveMagiskModule flags a module for removal on next reboot. func (a *App) RemoveMagiskModule(id string) (string, error) { if err := validModuleId(id); err != nil { return "", err } if _, err := a.runAdbShell("su", "-c", shellQuote("touch /data/adb/modules/"+id+"/remove")); err != nil { return "", fmt.Errorf("failed: %w", err) } return fmt.Sprintf("%s flagged for removal — reboot to apply", id), nil } // PullPatchedBoot finds the newest magisk_patched-*.img in /sdcard/Download and // pulls it to a local temp file (ready to live-boot or flash). func (a *App) PullPatchedBoot() (string, error) { out, err := a.runAdbShell("ls", "-t", "/sdcard/Download/") if err != nil { return "", fmt.Errorf("cannot list Download: %w", err) } var name string for _, line := range strings.Split(out, "\n") { line = strings.TrimSpace(line) if strings.HasPrefix(line, "magisk_patched") && strings.HasSuffix(line, ".img") { name = line break } } if name == "" { return "", fmt.Errorf("no magisk_patched-*.img in Download — patch the image in Magisk first") } tmp, err := os.CreateTemp("", "atk-patched-*.img") if err != nil { return "", err } tmp.Close() if _, err := a.runCommandTimeout(5*time.Minute, "adb", "pull", "/sdcard/Download/"+name, tmp.Name()); err != nil { return "", fmt.Errorf("pull failed: %w", err) } return tmp.Name(), nil }