Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Comment thread
planeklm marked this conversation as resolved.
Resolved
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ A four-step interactive wizard:
ipadecrypt decrypt <bundle-id|app-store-id|app-store-url|path-to-local-ipa>
```

By default the decrypted IPA is written to the current directory. Use `-o`/`--output` to change the output file path:

```sh
ipadecrypt decrypt com.example.app -o ~/Desktop/decryptedapp.ipa
ipadecrypt decrypt com.example.app -o ~/Desktop/
```

## License

MIT.
Expand Down
23 changes: 19 additions & 4 deletions cmd/ipadecrypt/decrypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ func decryptHandler(cmd *cobra.Command, args []string) {

live.OK("%s", progress.Summary())

outLocal, err := localOutputPath(appBundleID, appVersion)
outLocal, err := localOutputPath(decryptOutput, appBundleID, appVersion)
if err != nil {
tui.Err("output path: %v", err)
return
Expand Down Expand Up @@ -703,13 +703,28 @@ func remoteOutputPath(bundleID, version string) string {
)
}

func localOutputPath(bundleID, version string) (string, error) {
cwd, err := os.Getwd()
func localOutputPath(override, bundleID, version string) (string, error) {
defaultName := fmt.Sprintf("%s_%s.decrypted.ipa", bundleID, version)

if override == "" {
cwd, err := os.Getwd()
if err != nil {
return "", err
}
return filepath.Join(cwd, defaultName), nil
}

abs, err := filepath.Abs(override)
if err != nil {
return "", err
}

return filepath.Join(cwd, fmt.Sprintf("%s_%s.decrypted.ipa", bundleID, version)), nil
// if override is just a directory (not a full file path), place the default filename inside it
if info, serr := os.Stat(abs); serr == nil && info.IsDir() {
return filepath.Join(abs, defaultName), nil
}

return abs, nil
}

func cleanupDecrypt(dev *device.Client, noCleanup bool, stagingRemote, outRemote string) {
Expand Down
2 changes: 2 additions & 0 deletions cmd/ipadecrypt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var (
bootstrapReset bool

decryptExtVerID string
decryptOutput string
decryptNoCleanup bool
decryptKeepMetadata bool
decryptNoVerify bool
Expand Down Expand Up @@ -47,6 +48,7 @@ func main() {
Run: decryptHandler,
}
decrypt.Flags().StringVar(&decryptExtVerID, "external-version-id", "", "pin to a specific historical App Store version")
decrypt.Flags().StringVarP(&decryptOutput, "output", "o", "", "output path for the decrypted IPA (default: ./<bundleID>_<version>.decrypted.ipa)")
decrypt.Flags().BoolVar(&decryptNoCleanup, "no-cleanup", false, "leave remote staging files in place")
decrypt.Flags().BoolVar(&decryptKeepMetadata, "keep-metadata", false, "keep iTunesMetadata.plist (Apple ID + purchase info) in the output IPA")
decrypt.Flags().BoolVar(&decryptNoVerify, "no-verify", false, "skip the post-decrypt cryptid==0 check on every Mach-O")
Expand Down