mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Merge remote-tracking branch 'origin/fabianthdev/feature/SwiftUI' into naturecodevoid/swiftui-improvements
This commit is contained in:
27
.github/workflows/beta.yml
vendored
27
.github/workflows/beta.yml
vendored
@@ -51,18 +51,6 @@ jobs:
|
||||
- name: Convert to IPA
|
||||
run: make ipa
|
||||
|
||||
- name: Upload SideStore.ipa Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- name: Upload *.dSYM Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-dSYM
|
||||
path: ./*.dSYM/
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||
@@ -94,3 +82,18 @@ jobs:
|
||||
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: 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 *.dSYM Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||
path: ./*.dSYM/
|
||||
|
||||
27
.github/workflows/nightly.yml
vendored
27
.github/workflows/nightly.yml
vendored
@@ -60,18 +60,6 @@ jobs:
|
||||
- name: Convert to IPA
|
||||
run: make ipa
|
||||
|
||||
- name: Upload SideStore.ipa Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- name: Upload *.dSYM Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-dSYM
|
||||
path: ./*.dSYM/
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||
@@ -102,5 +90,20 @@ jobs:
|
||||
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: 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 *.dSYM Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||
path: ./*.dSYM/
|
||||
|
||||
- name: Reset cache for apps.sidestore.io/nightly
|
||||
run: sleep 10 && curl https://apps.sidestore.io/reset-cache/nightly/${{ secrets.SIDESOURCE_KEY }}
|
||||
|
||||
7
.github/workflows/pr.yml
vendored
7
.github/workflows/pr.yml
vendored
@@ -51,14 +51,17 @@ jobs:
|
||||
- name: Convert to IPA
|
||||
run: make ipa
|
||||
|
||||
- name: Add version to IPA file name
|
||||
run: mv SideStore.ipa SideStore-${{ 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.ipa
|
||||
path: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||
|
||||
- name: Upload *.dSYM Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-dSYM
|
||||
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||
path: ./*.dSYM/
|
||||
|
||||
27
.github/workflows/stable.yml
vendored
27
.github/workflows/stable.yml
vendored
@@ -48,18 +48,6 @@ jobs:
|
||||
- name: Convert to IPA
|
||||
run: make ipa
|
||||
|
||||
- name: Upload SideStore.ipa Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- name: Upload *.dSYM Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-dSYM
|
||||
path: ./*.dSYM/
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
|
||||
@@ -88,3 +76,18 @@ jobs:
|
||||
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: 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 *.dSYM Artifact
|
||||
uses: actions/upload-artifact@v3.1.0
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}-dSYM
|
||||
path: ./*.dSYM/
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
1F943C6F2927F90400ABE095 /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5B82927E0EE00B8D837 /* NewsView.swift */; };
|
||||
1F943C702927F90400ABE095 /* BrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BA2927E0F800B8D837 /* BrowseView.swift */; };
|
||||
1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FAFC5BD2927E10D00B8D837 /* MyAppsView.swift */; };
|
||||
1F981B1129AA0FAE0014950E /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1029AA0FAE0014950E /* OnboardingView.swift */; };
|
||||
1F981B1329AA101F0014950E /* OnboardingStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1229AA101F0014950E /* OnboardingStepView.swift */; };
|
||||
1F981B1529AA1E070014950E /* AppIconsShowcase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1429AA1E070014950E /* AppIconsShowcase.swift */; };
|
||||
1F981B1729AA34A70014950E /* AppStoreProductView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F981B1629AA34A70014950E /* AppStoreProductView.swift */; };
|
||||
1FA1C8CA294906890083119D /* MyAppsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA1C8C9294906890083119D /* MyAppsViewModel.swift */; };
|
||||
1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */; };
|
||||
1FA5A6CC298E8FE4007BA946 /* MailComposeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */; };
|
||||
@@ -76,14 +80,17 @@
|
||||
1FB96FCF292BBBCA007E68D1 /* SiriShortcutSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */; };
|
||||
1FB96FEC292C171D007E68D1 /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FEB292C171D007E68D1 /* NotificationManager.swift */; };
|
||||
1FB96FF3292D0539007E68D1 /* PillButtonProgressViewStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */; };
|
||||
1FFA56C2299994390011B6F5 /* OutputCapturer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFA56C1299994390011B6F5 /* OutputCapturer.swift */; };
|
||||
1FFEF104298552DB0098374C /* AppVersionHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */; };
|
||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
||||
992C896029A6A56500FB3501 /* LocalConsole in Frameworks */ = {isa = PBXBuildFile; productRef = 992C895F29A6A56500FB3501 /* LocalConsole */; };
|
||||
994D6E9B29E326080045B3F7 /* minimuxer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F87D1729D8E4C900B40039 /* minimuxer.swift */; };
|
||||
994D6EB529E35C130045B3F7 /* StoreApp+SideStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */; };
|
||||
99BCB7DF29A2AC050041D1A7 /* AdvancedSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */; };
|
||||
1FF8C6182A1780C60041352C /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8C6172A1780C60041352C /* ActivityView.swift */; };
|
||||
1FF8C61B2A1782F10041352C /* Reachability in Frameworks */ = {isa = PBXBuildFile; productRef = 1FF8C61A2A1782F10041352C /* Reachability */; };
|
||||
1FFA56C2299994390011B6F5 /* OutputCapturer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFA56C1299994390011B6F5 /* OutputCapturer.swift */; };
|
||||
1FFEF104298552DB0098374C /* AppVersionHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */; };
|
||||
4879A95F2861046500FC1BBD /* AltSign in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A95E2861046500FC1BBD /* AltSign */; };
|
||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 4879A9612861049C00FC1BBD /* OpenSSL */; };
|
||||
9922FFEC29B501C50020F868 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = 9922FFEB29B501C50020F868 /* Starscream */; };
|
||||
99C4EF4D2979132100CB538D /* SemanticVersion in Frameworks */ = {isa = PBXBuildFile; productRef = 99C4EF4C2979132100CB538D /* SemanticVersion */; };
|
||||
99D87A60299F1B1100ED09A9 /* DevModeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A5F299F1B1100ED09A9 /* DevModeView.swift */; };
|
||||
99D87A62299F3EC300ED09A9 /* FileExplorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D87A61299F3EC300ED09A9 /* FileExplorer.swift */; };
|
||||
@@ -617,6 +624,10 @@
|
||||
1F943C632927EF4200ABE095 /* NewsItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsItemView.swift; sourceTree = "<group>"; };
|
||||
1F943C652927F36600ABE095 /* NewsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsViewModel.swift; sourceTree = "<group>"; };
|
||||
1F943C672927F39400ABE095 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = "<group>"; };
|
||||
1F981B1029AA0FAE0014950E /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = "<group>"; };
|
||||
1F981B1229AA101F0014950E /* OnboardingStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingStepView.swift; sourceTree = "<group>"; };
|
||||
1F981B1429AA1E070014950E /* AppIconsShowcase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconsShowcase.swift; sourceTree = "<group>"; };
|
||||
1F981B1629AA34A70014950E /* AppStoreProductView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreProductView.swift; sourceTree = "<group>"; };
|
||||
1FA1C8C9294906890083119D /* MyAppsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyAppsViewModel.swift; sourceTree = "<group>"; };
|
||||
1FA5A6C9298E8B2F007BA946 /* RefreshAttemptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshAttemptsView.swift; sourceTree = "<group>"; };
|
||||
1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposeView.swift; sourceTree = "<group>"; };
|
||||
@@ -637,9 +648,10 @@
|
||||
1FB96FCE292BBBC9007E68D1 /* SiriShortcutSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiriShortcutSetupView.swift; sourceTree = "<group>"; };
|
||||
1FB96FEB292C171D007E68D1 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
|
||||
1FB96FF2292D0539007E68D1 /* PillButtonProgressViewStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButtonProgressViewStyle.swift; sourceTree = "<group>"; };
|
||||
994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+SideStore.swift"; sourceTree = "<group>"; };
|
||||
1FF8C6172A1780C60041352C /* ActivityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = "<group>"; };
|
||||
1FFA56C1299994390011B6F5 /* OutputCapturer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutputCapturer.swift; sourceTree = "<group>"; };
|
||||
1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppVersionHistoryView.swift; sourceTree = "<group>"; };
|
||||
994D6EB429E35C130045B3F7 /* StoreApp+SideStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+SideStore.swift"; sourceTree = "<group>"; };
|
||||
9961EC2D29BE9F2E00AF2C6F /* minimuxer-helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "minimuxer-helpers.swift"; path = "Dependencies/minimuxer/minimuxer-helpers.swift"; sourceTree = SOURCE_ROOT; };
|
||||
99BCB7DE29A2AC050041D1A7 /* AdvancedSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedSettingsView.swift; sourceTree = "<group>"; };
|
||||
99D87A5F299F1B1100ED09A9 /* DevModeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevModeView.swift; sourceTree = "<group>"; };
|
||||
@@ -1061,6 +1073,8 @@
|
||||
B3146ED2284F581E00BBC3FD /* Roxas.framework in Frameworks */,
|
||||
D533E8B72727841800A9B5DD /* libAppleArchive.tbd in Frameworks */,
|
||||
B3C395F9284F362400DA9E2F /* AppCenterCrashes in Frameworks */,
|
||||
9922FFEC29B501C50020F868 /* Starscream in Frameworks */,
|
||||
1FF8C61B2A1782F10041352C /* Reachability in Frameworks */,
|
||||
D533E8BE2727BBF800A9B5DD /* libcurl.a in Frameworks */,
|
||||
4879A9622861049C00FC1BBD /* OpenSSL in Frameworks */,
|
||||
B3C395F4284F35DD00DA9E2F /* Nuke in Frameworks */,
|
||||
@@ -1142,15 +1156,28 @@
|
||||
1F6284D6295218980060AAD8 /* DocumentPicker.swift */,
|
||||
1F545E84298D84CF00589F68 /* FilePreviewView.swift */,
|
||||
1FA5A6CB298E8FE4007BA946 /* MailComposeView.swift */,
|
||||
1F981B1629AA34A70014950E /* AppStoreProductView.swift */,
|
||||
1FF8C6172A1780C60041352C /* ActivityView.swift */,
|
||||
);
|
||||
path = "UIView Representables";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1F981B0F29AA0F9B0014950E /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1F981B1029AA0FAE0014950E /* OnboardingView.swift */,
|
||||
1F981B1229AA101F0014950E /* OnboardingStepView.swift */,
|
||||
1F981B1429AA1E070014950E /* AppIconsShowcase.swift */,
|
||||
);
|
||||
path = Onboarding;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
1FAFC5B02927E01400B8D837 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1FAFC5B52927E06300B8D837 /* RootView.swift */,
|
||||
1FAFC5B12927E02E00B8D837 /* Authentication */,
|
||||
1F981B0F29AA0F9B0014950E /* Onboarding */,
|
||||
1FAFC5B22927E03300B8D837 /* News */,
|
||||
1FAFC5B32927E03D00B8D837 /* Browse */,
|
||||
1FAFC5BC2927E0FD00B8D837 /* My Apps */,
|
||||
@@ -1299,6 +1326,7 @@
|
||||
99F87D1729D8E4C900B40039 /* minimuxer.swift */,
|
||||
);
|
||||
name = Generated;
|
||||
path = minimuxer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B3146EC7284F580500BBC3FD /* Products */ = {
|
||||
@@ -2329,12 +2357,14 @@
|
||||
B3C395F6284F362400DA9E2F /* AppCenterAnalytics */,
|
||||
B3C395F8284F362400DA9E2F /* AppCenterCrashes */,
|
||||
4879A9612861049C00FC1BBD /* OpenSSL */,
|
||||
1F74FF1D295263510047C051 /* AsyncImage */,
|
||||
1F07F5662955D16A00F7BE95 /* SFSafeSymbols */,
|
||||
1F1295802989B51F0048FCB9 /* ExpandableText */,
|
||||
99D87A6429A04D5E00ED09A9 /* Inject */,
|
||||
99DE640529A1753800B920BF /* ZIPFoundation */,
|
||||
992C895F29A6A56500FB3501 /* LocalConsole */,
|
||||
9922FFEB29B501C50020F868 /* Starscream */,
|
||||
1F74FF1D295263510047C051 /* AsyncImage */,
|
||||
1F07F5662955D16A00F7BE95 /* SFSafeSymbols */,
|
||||
1F1295802989B51F0048FCB9 /* ExpandableText */,
|
||||
1FF8C61A2A1782F10041352C /* Reachability */,
|
||||
);
|
||||
productName = AltStore;
|
||||
productReference = BFD2476A2284B9A500981D42 /* SideStore.app */;
|
||||
@@ -2407,12 +2437,14 @@
|
||||
4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */,
|
||||
4879A9602861049C00FC1BBD /* XCRemoteSwiftPackageReference "OpenSSL" */,
|
||||
99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */,
|
||||
1F74FF1C295263510047C051 /* XCRemoteSwiftPackageReference "AsyncImage" */,
|
||||
1F07F5652955D16A00F7BE95 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
|
||||
1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */,
|
||||
99D87A6329A04D5E00ED09A9 /* XCRemoteSwiftPackageReference "Inject" */,
|
||||
99DE640429A1753800B920BF /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
|
||||
992C895E29A6A56500FB3501 /* XCRemoteSwiftPackageReference "LocalConsole" */,
|
||||
9922FFEA29B501C50020F868 /* XCRemoteSwiftPackageReference "Starscream" */,
|
||||
1F74FF1C295263510047C051 /* XCRemoteSwiftPackageReference "AsyncImage" */,
|
||||
1F07F5652955D16A00F7BE95 /* XCRemoteSwiftPackageReference "SFSafeSymbols" */,
|
||||
1F12957F2989B51F0048FCB9 /* XCRemoteSwiftPackageReference "ExpandableText" */,
|
||||
1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */,
|
||||
);
|
||||
productRefGroup = BFD2476B2284B9A500981D42 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -2840,6 +2872,7 @@
|
||||
BFC57A652416C72400EB891E /* DeactivateAppOperation.swift in Sources */,
|
||||
1F943C712927F90400ABE095 /* MyAppsView.swift in Sources */,
|
||||
1FA1C8CA294906890083119D /* MyAppsViewModel.swift in Sources */,
|
||||
1F981B1529AA1E070014950E /* AppIconsShowcase.swift in Sources */,
|
||||
BF3BEFC124086A1E00DE7D55 /* RefreshAppOperation.swift in Sources */,
|
||||
1FFEF104298552DB0098374C /* AppVersionHistoryView.swift in Sources */,
|
||||
BFE60740231AFD2A002B0E8E /* InsetGroupTableViewCell.swift in Sources */,
|
||||
@@ -2867,10 +2900,13 @@
|
||||
1FB96FEC292C171D007E68D1 /* NotificationManager.swift in Sources */,
|
||||
BF770E6722BD57C4002A40FE /* BackgroundTaskManager.swift in Sources */,
|
||||
1F07F56B2955F11500F7BE95 /* AppScreenshotsPreview.swift in Sources */,
|
||||
1F981B1329AA101F0014950E /* OnboardingStepView.swift in Sources */,
|
||||
BF44EEFC246B4550002A52F2 /* RemoveAppOperation.swift in Sources */,
|
||||
BF3D64B022E8D4B800E9056B /* AppContentViewControllerCells.swift in Sources */,
|
||||
1F981B1729AA34A70014950E /* AppStoreProductView.swift in Sources */,
|
||||
BFC57A6E2416FC5D00EB891E /* InstalledAppsCollectionHeaderView.swift in Sources */,
|
||||
B3EE16B62925E27D00B3B1F5 /* AnisetteManager.swift in Sources */,
|
||||
1FF8C6182A1780C60041352C /* ActivityView.swift in Sources */,
|
||||
1F943C6E2927F90400ABE095 /* NewsItemView.swift in Sources */,
|
||||
BF88F97224F8727D00BB75DF /* AppManagerErrors.swift in Sources */,
|
||||
B39F16152918D7DA002E9404 /* Consts+Proxy.swift in Sources */,
|
||||
@@ -2912,9 +2948,9 @@
|
||||
1F6284D7295218980060AAD8 /* DocumentPicker.swift in Sources */,
|
||||
BFBE0007250AD0E70080826E /* ViewAppIntentHandler.swift in Sources */,
|
||||
99E59E1D299BFE5D00FAF33D /* AppIconsView.swift in Sources */,
|
||||
99BCB7DF29A2AC050041D1A7 /* AdvancedSettingsView.swift in Sources */,
|
||||
1F0DD83F29367F6C007608A4 /* ConnectAppleIDView.swift in Sources */,
|
||||
1F07F56F2955FB2000F7BE95 /* AppIDsView.swift in Sources */,
|
||||
99BCB7DF29A2AC050041D1A7 /* AdvancedSettingsView.swift in Sources */,
|
||||
BFDB6A0822AAED73007EA6D6 /* ResignAppOperation.swift in Sources */,
|
||||
D593F1942717749A006E82DE /* PatchAppOperation.swift in Sources */,
|
||||
BF770E5122BB1CF6002A40FE /* InstallAppOperation.swift in Sources */,
|
||||
@@ -2948,6 +2984,7 @@
|
||||
BF6C8FB02429599900125131 /* TextCollectionReusableView.swift in Sources */,
|
||||
BF663C4F2433ED8200DAA738 /* FileManager+DirectorySize.swift in Sources */,
|
||||
1F943C702927F90400ABE095 /* BrowseView.swift in Sources */,
|
||||
1F981B1129AA0FAE0014950E /* OnboardingView.swift in Sources */,
|
||||
1F943C692927F8F200ABE095 /* RootView.swift in Sources */,
|
||||
1FB84BA62928DE08006A5CF4 /* AppDetailView.swift in Sources */,
|
||||
D57DF63F271E51E400677701 /* ALTAppPatcher.m in Sources */,
|
||||
@@ -3814,6 +3851,14 @@
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ashleymills/Reachability.swift";
|
||||
requirement = {
|
||||
branch = master;
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SideStore/AltSign";
|
||||
@@ -3838,6 +3883,14 @@
|
||||
kind = branch;
|
||||
};
|
||||
};
|
||||
9922FFEA29B501C50020F868 /* XCRemoteSwiftPackageReference "Starscream" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/daltoniam/Starscream.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 4.0.0;
|
||||
};
|
||||
};
|
||||
99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/SwiftPackageIndex/SemanticVersion.git";
|
||||
@@ -3943,6 +3996,11 @@
|
||||
package = 1F74FF1C295263510047C051 /* XCRemoteSwiftPackageReference "AsyncImage" */;
|
||||
productName = AsyncImage;
|
||||
};
|
||||
1FF8C61A2A1782F10041352C /* Reachability */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 1FF8C6192A1782F10041352C /* XCRemoteSwiftPackageReference "Reachability" */;
|
||||
productName = Reachability;
|
||||
};
|
||||
4879A95E2861046500FC1BBD /* AltSign */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 4879A95D2861046500FC1BBD /* XCRemoteSwiftPackageReference "AltSign" */;
|
||||
@@ -3958,6 +4016,11 @@
|
||||
package = 992C895E29A6A56500FB3501 /* XCRemoteSwiftPackageReference "LocalConsole" */;
|
||||
productName = LocalConsole;
|
||||
};
|
||||
9922FFEB29B501C50020F868 /* Starscream */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 9922FFEA29B501C50020F868 /* XCRemoteSwiftPackageReference "Starscream" */;
|
||||
productName = Starscream;
|
||||
};
|
||||
99C4EF4C2979132100CB538D /* SemanticVersion */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 99C4EF472978D52400CB538D /* XCRemoteSwiftPackageReference "SemanticVersion" */;
|
||||
|
||||
@@ -99,6 +99,15 @@
|
||||
"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",
|
||||
@@ -126,6 +135,15 @@
|
||||
"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",
|
||||
|
||||
@@ -54,15 +54,29 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(true)
|
||||
#if !targetEnvironment(simulator)
|
||||
if !UserDefaults.standard.onboardingComplete {
|
||||
self.showOnboarding()
|
||||
return
|
||||
}
|
||||
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
|
||||
guard let pf = fetchPairingFile() else {
|
||||
displayError("Device pairing file not found.")
|
||||
self.showOnboarding(step: .pairing)
|
||||
return
|
||||
}
|
||||
start_minimuxer_threads(pf)
|
||||
#endif
|
||||
}
|
||||
|
||||
func showOnboarding(step: OnboardingView.OnboardingStep = .welcome) {
|
||||
let onboardingView = OnboardingView(onDismiss: { self.dismiss(animated: true) }, currentStep: step)
|
||||
.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"
|
||||
@@ -157,7 +171,7 @@ final class LaunchViewController: RSTLaunchViewController, UIDocumentPickerDeleg
|
||||
try start(pairing_file, documentsDirectory)
|
||||
} catch {
|
||||
try! FileManager.default.removeItem(at: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)"))
|
||||
displayError("minimuxer failed to start, please restart SideStore. \(minimuxerToOperationError(error).failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||
displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||
}
|
||||
set_debug(UserDefaults.shared.isDebugLoggingEnabled)
|
||||
start_auto_mounter(documentsDirectory)
|
||||
|
||||
@@ -874,13 +874,11 @@ private extension AppManager
|
||||
// Check if backup app is installed in place of real app.
|
||||
let uti = UTTypeCopyDeclaration(app.installedBackupAppUTI as CFString)?.takeRetainedValue() as NSDictionary?
|
||||
|
||||
// for some reason, `app.certificateSerialNumber != group.context.certificate?.serialNumber` is true on first SideStore refresh
|
||||
// in most cases, the first refresh gets stuck since it is a full reinstall, and to fix it you must exit to home screen
|
||||
// which finishes it but removes all app data
|
||||
// so we want to ensure we don't reinstall for SideStore if it's true (it will still reinstall if needsResign is true)
|
||||
if (app.certificateSerialNumber != group.context.certificate?.serialNumber && app.bundleIdentifier != StoreApp.altstoreAppID) ||
|
||||
if app.certificateSerialNumber != group.context.certificate?.serialNumber ||
|
||||
uti != nil ||
|
||||
app.needsResign
|
||||
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
|
||||
|
||||
@@ -11,6 +11,7 @@ import CoreData
|
||||
|
||||
import AltStoreCore
|
||||
import EmotionalDamage
|
||||
import minimuxer
|
||||
|
||||
enum RefreshError: LocalizedError
|
||||
{
|
||||
@@ -97,6 +98,14 @@ final class BackgroundRefreshAppsOperation: ResultOperation<[String: Result<Inst
|
||||
return
|
||||
}
|
||||
start_em_proxy(bind_addr: Consts.Proxy.serverURL)
|
||||
target_minimuxer_address()
|
||||
let documentsDirectory = FileManager.default.documentsDirectory.absoluteString
|
||||
do {
|
||||
try minimuxer.start(try String(contentsOf: FileManager.default.documentsDirectory.appendingPathComponent("\(pairingFileName)")), documentsDirectory)
|
||||
} catch {
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
start_auto_mounter(documentsDirectory)
|
||||
|
||||
self.managedObjectContext.perform {
|
||||
print("Apps to refresh:", self.installedApps.map(\.bundleIdentifier))
|
||||
|
||||
@@ -46,7 +46,7 @@ final class DeactivateAppOperation: ResultOperation<InstalledApp>
|
||||
do {
|
||||
try remove_provisioning_profile(profile)
|
||||
} catch {
|
||||
return self.finish(.failure(minimuxerToOperationError(error)))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ final class EnableJITOperation<Context: EnableJITContext>: ResultOperation<Void>
|
||||
do {
|
||||
try debug_app(installedApp.resignedBundleIdentifier)
|
||||
} catch {
|
||||
return self.finish(.failure(minimuxerToOperationError(error)))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
self.finish(.success(()))
|
||||
|
||||
@@ -7,15 +7,28 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CommonCrypto
|
||||
import Starscream
|
||||
|
||||
import AltStoreCore
|
||||
import AltSign
|
||||
import Roxas
|
||||
|
||||
@objc(FetchAnisetteDataOperation)
|
||||
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>, WebSocketDelegate
|
||||
{
|
||||
let context: OperationContext
|
||||
var socket: WebSocket!
|
||||
|
||||
var url: URL?
|
||||
var startProvisioningURL: URL?
|
||||
var endProvisioningURL: URL?
|
||||
|
||||
var clientInfo: String?
|
||||
var userAgent: String?
|
||||
|
||||
var mdLu: String?
|
||||
var deviceId: String?
|
||||
|
||||
init(context: OperationContext)
|
||||
{
|
||||
@@ -32,32 +45,412 @@ final class FetchAnisetteDataOperation: ResultOperation<ALTAnisetteData>
|
||||
return
|
||||
}
|
||||
|
||||
let url = AnisetteManager.currentURL
|
||||
DLOG("Anisette URL: %@", url.absoluteString)
|
||||
self.url = AnisetteManager.currentURL
|
||||
print("Anisette URL: \(self.url!.absoluteString)")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard let data = data, error == nil else { return }
|
||||
|
||||
do {
|
||||
// make sure this JSON is in the format we expect
|
||||
// convert data to json
|
||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
||||
// try to read out a dictionary
|
||||
//for some reason serial number isn't needed but it doesn't work unless it has a value
|
||||
let formattedJSON: [String: String] = ["machineID": json["X-Apple-I-MD-M"]!, "oneTimePassword": json["X-Apple-I-MD"]!, "localUserID": json["X-Apple-I-MD-LU"]!, "routingInfo": json["X-Apple-I-MD-RINFO"]!, "deviceUniqueIdentifier": json["X-Mme-Device-Id"]!, "deviceDescription": json["X-MMe-Client-Info"]!, "date": json["X-Apple-I-Client-Time"]!, "locale": json["X-Apple-Locale"]!, "timeZone": json["X-Apple-I-TimeZone"]!, "deviceSerialNumber": "1"]
|
||||
|
||||
if let anisette = ALTAnisetteData(json: formattedJSON) {
|
||||
DLOG("Anisette used: %@", formattedJSON)
|
||||
self.finish(.success(anisette))
|
||||
}
|
||||
if let identifier = Keychain.shared.identifier,
|
||||
let adiPb = Keychain.shared.adiPb {
|
||||
fetchAnisetteV3(identifier, adiPb)
|
||||
} else {
|
||||
provision()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - COMMON
|
||||
|
||||
func extractAnisetteData(_ data: Data, _ response: HTTPURLResponse?, v3: Bool) throws {
|
||||
// make sure this JSON is in the format we expect
|
||||
// convert data to json
|
||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
||||
if v3 {
|
||||
if json["result"] == "GetHeadersError" {
|
||||
let message = json["message"]
|
||||
print("Error getting V3 headers: \(message ?? "no message")")
|
||||
if let message = message,
|
||||
message.contains("-45061") {
|
||||
print("Error message contains -45061 (not provisioned), resetting adi.pb and retrying")
|
||||
Keychain.shared.adiPb = nil
|
||||
return provision()
|
||||
} else { throw OperationError.anisetteV3Error(message: message ?? "Unknown error") }
|
||||
}
|
||||
}
|
||||
|
||||
// try to read out a dictionary
|
||||
// for some reason serial number isn't needed but it doesn't work unless it has a value
|
||||
var formattedJSON: [String: String] = ["deviceSerialNumber": "0"]
|
||||
if let machineID = json["X-Apple-I-MD-M"] { formattedJSON["machineID"] = machineID }
|
||||
if let oneTimePassword = json["X-Apple-I-MD"] { formattedJSON["oneTimePassword"] = oneTimePassword }
|
||||
if let routingInfo = json["X-Apple-I-MD-RINFO"] { formattedJSON["routingInfo"] = routingInfo }
|
||||
|
||||
if v3 {
|
||||
formattedJSON["deviceDescription"] = self.clientInfo!
|
||||
formattedJSON["localUserID"] = self.mdLu!
|
||||
formattedJSON["deviceUniqueIdentifier"] = self.deviceId!
|
||||
|
||||
// Generate date stuff on client
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.calendar = Calendar(identifier: .gregorian)
|
||||
formatter.timeZone = TimeZone.current
|
||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||
let dateString = formatter.string(from: Date())
|
||||
formattedJSON["date"] = dateString
|
||||
formattedJSON["locale"] = Locale.current.identifier
|
||||
formattedJSON["timeZone"] = TimeZone.current.abbreviation()
|
||||
} else {
|
||||
if let deviceDescription = json["X-MMe-Client-Info"] { formattedJSON["deviceDescription"] = deviceDescription }
|
||||
if let localUserID = json["X-Apple-I-MD-LU"] { formattedJSON["localUserID"] = localUserID }
|
||||
if let deviceUniqueIdentifier = json["X-Mme-Device-Id"] { formattedJSON["deviceUniqueIdentifier"] = deviceUniqueIdentifier }
|
||||
|
||||
if let date = json["X-Apple-I-Client-Time"] { formattedJSON["date"] = date }
|
||||
if let locale = json["X-Apple-Locale"] { formattedJSON["locale"] = locale }
|
||||
if let timeZone = json["X-Apple-I-TimeZone"] { formattedJSON["timeZone"] = timeZone }
|
||||
}
|
||||
|
||||
if let response = response,
|
||||
let version = response.value(forHTTPHeaderField: "Implementation-Version") {
|
||||
print("Implementation-Version: \(version)")
|
||||
} else { print("No Implementation-Version header") }
|
||||
|
||||
print("Anisette used: \(formattedJSON)")
|
||||
print("Original JSON: \(json)")
|
||||
if let anisette = ALTAnisetteData(json: formattedJSON) {
|
||||
print("Anisette is valid!")
|
||||
self.finish(.success(anisette))
|
||||
} else {
|
||||
print("Anisette is invalid!!!!")
|
||||
if v3 {
|
||||
throw OperationError.anisetteV3Error(message: "Invalid anisette (the returned data may not have all the required fields)")
|
||||
} else {
|
||||
throw OperationError.anisetteV1Error(message: "Invalid anisette (the returned data may not have all the required fields)")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if v3 {
|
||||
throw OperationError.anisetteV3Error(message: "Invalid anisette (the returned data may not be in JSON)")
|
||||
} else {
|
||||
throw OperationError.anisetteV1Error(message: "Invalid anisette (the returned data may not be in JSON)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - V1
|
||||
|
||||
func handleV1() {
|
||||
print("Server is V1")
|
||||
|
||||
if UserDefaults.shared.trustedServerURL == AnisetteManager.currentURLString {
|
||||
print("Server has already been trusted, fetching anisette")
|
||||
return self.fetchAnisetteV1()
|
||||
}
|
||||
|
||||
print("Alerting user about outdated server")
|
||||
let alert = UIAlertController(title: "WARNING: Outdated anisette server", message: "We've detected you are using an older anisette server. Using this server has a higher likelihood of locking your account and causing other issues. Are you sure you want to continue?", preferredStyle: UIAlertController.Style.alert)
|
||||
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertAction.Style.destructive, handler: { action in
|
||||
print("Fetching anisette via V1")
|
||||
UserDefaults.shared.trustedServerURL = AnisetteManager.currentURLString
|
||||
self.fetchAnisetteV1()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { action in
|
||||
print("Cancelled anisette operation")
|
||||
self.finish(.failure(OperationError.cancelled))
|
||||
}))
|
||||
|
||||
let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
|
||||
|
||||
DispatchQueue.main.async {
|
||||
if let presentingController = keyWindow?.rootViewController?.presentedViewController {
|
||||
presentingController.present(alert, animated: true)
|
||||
} else {
|
||||
keyWindow?.rootViewController?.present(alert, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAnisetteV1() {
|
||||
print("Fetching anisette V1")
|
||||
URLSession.shared.dataTask(with: self.url!) { data, response, error in
|
||||
do {
|
||||
guard let data = data, error == nil else { throw OperationError.anisetteV1Error(message: "Unable to fetch data\(error != nil ? " (\(error!.localizedDescription))" : "")") }
|
||||
|
||||
try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: false)
|
||||
} catch let error as NSError {
|
||||
print("Failed to load: \(error.localizedDescription)")
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
|
||||
}.resume()
|
||||
}
|
||||
|
||||
// MARK: - V3: PROVISIONING
|
||||
|
||||
func provision() {
|
||||
fetchClientInfo {
|
||||
print("Getting provisioning URLs")
|
||||
var request = self.buildAppleRequest(url: URL(string: "https://gsa.apple.com/grandslam/GsService2/lookup")!)
|
||||
request.httpMethod = "GET"
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let data = data,
|
||||
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
|
||||
let startProvisioningString = plist["urls"]?["midStartProvisioning"] as? String,
|
||||
let startProvisioningURL = URL(string: startProvisioningString),
|
||||
let endProvisioningString = plist["urls"]?["midFinishProvisioning"] as? String,
|
||||
let endProvisioningURL = URL(string: endProvisioningString) {
|
||||
self.startProvisioningURL = startProvisioningURL
|
||||
self.endProvisioningURL = endProvisioningURL
|
||||
print("startProvisioningURL: \(self.startProvisioningURL!.absoluteString)")
|
||||
print("endProvisioningURL: \(self.endProvisioningURL!.absoluteString)")
|
||||
print("Starting a provisioning session")
|
||||
self.startProvisioningSession()
|
||||
} else {
|
||||
print("Apple didn't give valid URLs! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
|
||||
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid URLs. Please try again later", message: nil)))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
}
|
||||
|
||||
func startProvisioningSession() {
|
||||
let provisioningSessionURL = self.url!.appendingPathComponent("v3").appendingPathComponent("provisioning_session")
|
||||
var wsRequest = URLRequest(url: provisioningSessionURL)
|
||||
wsRequest.timeoutInterval = 5
|
||||
self.socket = WebSocket(request: wsRequest)
|
||||
self.socket.delegate = self
|
||||
self.socket.connect()
|
||||
}
|
||||
|
||||
func didReceive(event: WebSocketEvent, client: WebSocket) {
|
||||
switch event {
|
||||
case .text(let string):
|
||||
do {
|
||||
if let json = try JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: []) as? [String: Any] {
|
||||
guard let result = json["result"] as? String else {
|
||||
print("The server didn't give us a result")
|
||||
client.disconnect(closeCode: 0)
|
||||
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a result", message: nil)))
|
||||
return
|
||||
}
|
||||
print("Received result: \(result)")
|
||||
switch result {
|
||||
case "GiveIdentifier":
|
||||
print("Giving identifier")
|
||||
client.json(["identifier": Keychain.shared.identifier!])
|
||||
|
||||
case "GiveStartProvisioningData":
|
||||
print("Getting start provisioning data")
|
||||
let body = [
|
||||
"Header": [String: Any](),
|
||||
"Request": [String: Any](),
|
||||
]
|
||||
var request = self.buildAppleRequest(url: self.startProvisioningURL!)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try! PropertyListSerialization.data(fromPropertyList: body, format: .xml, options: 0)
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let data = data,
|
||||
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
|
||||
let spim = plist["Response"]?["spim"] as? String {
|
||||
print("Giving start provisioning data")
|
||||
client.json(["spim": spim])
|
||||
} else {
|
||||
print("Apple didn't give valid start provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
|
||||
client.disconnect(closeCode: 0)
|
||||
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid start provisioning data. Please try again later", message: nil)))
|
||||
}
|
||||
}.resume()
|
||||
|
||||
case "GiveEndProvisioningData":
|
||||
print("Getting end provisioning data")
|
||||
guard let cpim = json["cpim"] as? String else {
|
||||
print("The server didn't give us a cpim")
|
||||
client.disconnect(closeCode: 0)
|
||||
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us a cpim", message: nil)))
|
||||
return
|
||||
}
|
||||
let body = [
|
||||
"Header": [String: Any](),
|
||||
"Request": [
|
||||
"cpim": cpim,
|
||||
],
|
||||
]
|
||||
var request = self.buildAppleRequest(url: self.endProvisioningURL!)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try! PropertyListSerialization.data(fromPropertyList: body, format: .xml, options: 0)
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
if let data = data,
|
||||
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? Dictionary<String, Dictionary<String, Any>>,
|
||||
let ptm = plist["Response"]?["ptm"] as? String,
|
||||
let tk = plist["Response"]?["tk"] as? String {
|
||||
print("Giving end provisioning data")
|
||||
client.json(["ptm": ptm, "tk": tk])
|
||||
} else {
|
||||
print("Apple didn't give valid end provisioning data! Got response: \(String(data: data ?? Data("nothing".utf8), encoding: .utf8) ?? "not utf8")")
|
||||
client.disconnect(closeCode: 0)
|
||||
self.finish(.failure(OperationError.provisioningError(result: "Apple didn't give valid end provisioning data. Please try again later", message: nil)))
|
||||
}
|
||||
}.resume()
|
||||
|
||||
case "ProvisioningSuccess":
|
||||
print("Provisioning succeeded!")
|
||||
client.disconnect(closeCode: 0)
|
||||
guard let adiPb = json["adi_pb"] as? String else {
|
||||
print("The server didn't give us an adi.pb file")
|
||||
self.finish(.failure(OperationError.provisioningError(result: "The server didn't give us an adi.pb file", message: nil)))
|
||||
return
|
||||
}
|
||||
Keychain.shared.adiPb = adiPb
|
||||
self.fetchAnisetteV3(Keychain.shared.identifier!, Keychain.shared.adiPb!)
|
||||
|
||||
default:
|
||||
if result.contains("Error") || result.contains("Invalid") || result == "ClosingPerRequest" || result == "Timeout" || result == "TextOnly" {
|
||||
print("Failing because of \(result)")
|
||||
self.finish(.failure(OperationError.provisioningError(result: result, message: json["message"] as? String)))
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch let error as NSError {
|
||||
print("Failed to handle text: \(error.localizedDescription)")
|
||||
self.finish(.failure(OperationError.provisioningError(result: error.localizedDescription, message: nil)))
|
||||
}
|
||||
|
||||
case .connected:
|
||||
print("Connected")
|
||||
|
||||
case .disconnected(let string, let code):
|
||||
print("Disconnected: \(code); \(string)")
|
||||
|
||||
case .error(let error):
|
||||
print("Got error: \(String(describing: error))")
|
||||
|
||||
default:
|
||||
print("Unknown event: \(event)")
|
||||
}
|
||||
}
|
||||
|
||||
func buildAppleRequest(url: URL) -> URLRequest {
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue(self.clientInfo!, forHTTPHeaderField: "X-Mme-Client-Info")
|
||||
request.setValue(self.userAgent!, forHTTPHeaderField: "User-Agent")
|
||||
request.setValue("text/x-xml-plist", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue("*/*", forHTTPHeaderField: "Accept")
|
||||
|
||||
request.setValue(self.mdLu!, forHTTPHeaderField: "X-Apple-I-MD-LU")
|
||||
request.setValue(self.deviceId!, forHTTPHeaderField: "X-Mme-Device-Id")
|
||||
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.calendar = Calendar(identifier: .gregorian)
|
||||
formatter.timeZone = TimeZone(identifier: "UTC")
|
||||
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||
let dateString = formatter.string(from: Date())
|
||||
request.setValue(dateString, forHTTPHeaderField: "X-Apple-I-Client-Time")
|
||||
request.setValue(Locale.current.identifier, forHTTPHeaderField: "X-Apple-Locale")
|
||||
request.setValue(TimeZone.current.abbreviation(), forHTTPHeaderField: "X-Apple-I-TimeZone")
|
||||
return request
|
||||
}
|
||||
|
||||
// MARK: - V3: FETCHING
|
||||
|
||||
func fetchClientInfo(_ callback: @escaping () -> Void) {
|
||||
if self.clientInfo != nil &&
|
||||
self.userAgent != nil &&
|
||||
self.mdLu != nil &&
|
||||
self.deviceId != nil &&
|
||||
Keychain.shared.identifier != nil {
|
||||
print("Skipping client_info fetch since all the properties we need aren't nil")
|
||||
return callback()
|
||||
}
|
||||
print("Trying to get client_info")
|
||||
let clientInfoURL = self.url!.appendingPathComponent("v3").appendingPathComponent("client_info")
|
||||
URLSession.shared.dataTask(with: clientInfoURL) { data, response, error in
|
||||
do {
|
||||
guard let data = data, error == nil else {
|
||||
return self.finish(.failure(OperationError.anisetteV3Error(message: "Couldn't fetch client info. The server may be down\(error != nil ? " (\(error!.localizedDescription))" : "")")))
|
||||
}
|
||||
|
||||
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String] {
|
||||
if let clientInfo = json["client_info"] {
|
||||
print("Server is V3")
|
||||
|
||||
self.clientInfo = clientInfo
|
||||
self.userAgent = json["user_agent"]!
|
||||
print("Client-Info: \(self.clientInfo!)")
|
||||
print("User-Agent: \(self.userAgent!)")
|
||||
|
||||
if Keychain.shared.identifier == nil {
|
||||
print("Generating identifier")
|
||||
var bytes = [Int8](repeating: 0, count: 16)
|
||||
let status = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
|
||||
|
||||
if status != errSecSuccess {
|
||||
print("ERROR GENERATING IDENTIFIER!!! \(status)")
|
||||
return self.finish(.failure(OperationError.provisioningError(result: "Couldn't generate identifier", message: nil)))
|
||||
}
|
||||
|
||||
Keychain.shared.identifier = Data(bytes: &bytes, count: bytes.count).base64EncodedString()
|
||||
}
|
||||
|
||||
let decoded = Data(base64Encoded: Keychain.shared.identifier!)!
|
||||
self.mdLu = decoded.sha256().hexEncodedString()
|
||||
print("X-Apple-I-MD-LU: \(self.mdLu!)")
|
||||
let uuid: UUID = decoded.object()
|
||||
self.deviceId = uuid.uuidString.uppercased()
|
||||
print("X-Mme-Device-Id: \(self.deviceId!)")
|
||||
|
||||
callback()
|
||||
} else { self.handleV1() }
|
||||
} else { self.finish(.failure(OperationError.anisetteV3Error(message: "Couldn't fetch client info. The returned data may not be in JSON"))) }
|
||||
} catch let error as NSError {
|
||||
print("Failed to load: \(error.localizedDescription)")
|
||||
self.handleV1()
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func fetchAnisetteV3(_ identifier: String, _ adiPb: String) {
|
||||
fetchClientInfo {
|
||||
print("Fetching anisette V3")
|
||||
var request = URLRequest(url: self.url!.appendingPathComponent("v3").appendingPathComponent("get_headers"))
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try! JSONSerialization.data(withJSONObject: [
|
||||
"identifier": identifier,
|
||||
"adi_pb": adiPb
|
||||
], options: [])
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
||||
do {
|
||||
guard let data = data, error == nil else { throw OperationError.anisetteV3Error(message: "Couldn't fetch anisette") }
|
||||
|
||||
try self.extractAnisetteData(data, response as? HTTPURLResponse, v3: true)
|
||||
} catch let error as NSError {
|
||||
print("Failed to load: \(error.localizedDescription)")
|
||||
self.finish(.failure(error))
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
extension WebSocket {
|
||||
func json(_ dictionary: [String: String]) {
|
||||
let data = try! JSONSerialization.data(withJSONObject: dictionary, options: [])
|
||||
self.write(string: String(data: data, encoding: .utf8)!)
|
||||
}
|
||||
}
|
||||
|
||||
extension Data {
|
||||
// https://stackoverflow.com/a/25391020
|
||||
func sha256() -> Data {
|
||||
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
||||
self.withUnsafeBytes {
|
||||
_ = CC_SHA256($0.baseAddress, CC_LONG(self.count), &hash)
|
||||
}
|
||||
return Data(hash)
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/40089462
|
||||
func hexEncodedString() -> String {
|
||||
return self.map { String(format: "%02hhX", $0) }.joined()
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/59127761
|
||||
func object<T>() -> T { self.withUnsafeBytes { $0.load(as: T.self) } }
|
||||
}
|
||||
|
||||
@@ -268,8 +268,17 @@ extension FetchProvisioningProfilesOperation
|
||||
}
|
||||
}
|
||||
}
|
||||
//App ID name must be ascii. If the name is not ascii, using bundleID instead
|
||||
let appIDName: String
|
||||
if !name.allSatisfy({ $0.isASCII }) {
|
||||
//Contains non ASCII (Such as Chinese/Japanese...), using bundleID
|
||||
appIDName = bundleIdentifier
|
||||
}else {
|
||||
//ASCII text, keep going as usual
|
||||
appIDName = name
|
||||
}
|
||||
|
||||
ALTAppleAPI.shared.addAppID(withName: name, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
||||
ALTAppleAPI.shared.addAppID(withName: appIDName, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
||||
do
|
||||
{
|
||||
do
|
||||
@@ -394,17 +403,26 @@ extension FetchProvisioningProfilesOperation
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we add .AltWidget for the widget
|
||||
var altStoreAppGroupID = Bundle.baseAltStoreAppGroupID
|
||||
for (_, group) in applicationGroups.enumerated() {
|
||||
if group.contains("AltWidget") {
|
||||
altStoreAppGroupID += ".AltWidget"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Potentially updating app groups for this specific AltStore.
|
||||
// Find the (unique) AltStore app group, then replace it
|
||||
// with the correct "base" app group ID.
|
||||
// Otherwise, we may append a duplicate team identifier to the end.
|
||||
if let index = applicationGroups.firstIndex(where: { $0.contains(Bundle.baseAltStoreAppGroupID) })
|
||||
{
|
||||
applicationGroups[index] = Bundle.baseAltStoreAppGroupID
|
||||
applicationGroups[index] = altStoreAppGroupID
|
||||
}
|
||||
else
|
||||
{
|
||||
applicationGroups.append(Bundle.baseAltStoreAppGroupID)
|
||||
applicationGroups.append(altStoreAppGroupID)
|
||||
}
|
||||
}
|
||||
print("Application groups: \(applicationGroups)")
|
||||
|
||||
@@ -149,10 +149,68 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
})
|
||||
}
|
||||
|
||||
var installing = true
|
||||
if installedApp.storeApp?.bundleIdentifier == Bundle.Info.appbundleIdentifier {
|
||||
// Reinstalling ourself will hang until we leave the app, so we need to exit it without force closing
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
if UIApplication.shared.applicationState != .active {
|
||||
print("We are not in the foreground, let's not do anything")
|
||||
return
|
||||
}
|
||||
if !installing {
|
||||
print("Installing finished")
|
||||
return
|
||||
}
|
||||
print("We are still installing after 3 seconds")
|
||||
|
||||
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
||||
switch (settings.authorizationStatus) {
|
||||
case .authorized, .ephemeral, .provisional:
|
||||
print("Notifications are enabled")
|
||||
|
||||
let content = UNMutableNotificationContent()
|
||||
content.title = "Refreshing..."
|
||||
content.body = "To finish refreshing, SideStore must be moved to the background, which it does by opening Safari. Please reopen SideStore after it is done refreshing!"
|
||||
let notification = UNNotificationRequest(identifier: Bundle.Info.appbundleIdentifier + ".FinishRefreshNotification", content: content, trigger: UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false))
|
||||
UNUserNotificationCenter.current().add(notification)
|
||||
|
||||
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
|
||||
|
||||
break
|
||||
default:
|
||||
print("Notifications are not enabled")
|
||||
|
||||
let alert = UIAlertController(title: "Finish Refresh", message: "To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen or open Safari by pressing Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
|
||||
print("Opening Safari")
|
||||
DispatchQueue.main.async { UIApplication.shared.open(URL(string: "x-web-search://")!) }
|
||||
}))
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let keyWindow = UIApplication.shared.windows.filter { $0.isKeyWindow }.first
|
||||
if var topController = keyWindow?.rootViewController {
|
||||
while let presentedViewController = topController.presentedViewController {
|
||||
topController = presentedViewController
|
||||
}
|
||||
topController.present(alert, animated: true)
|
||||
} else {
|
||||
print("No key window? Let's just open Safari")
|
||||
UIApplication.shared.open(URL(string: "x-web-search://")!)
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
} catch {
|
||||
return self.finish(.failure(minimuxerToOperationError(error)))
|
||||
installing = false
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
installedApp.refreshedDate = Date()
|
||||
|
||||
@@ -34,20 +34,9 @@ enum OperationError: LocalizedError
|
||||
case openAppFailed(name: String)
|
||||
case missingAppGroup
|
||||
|
||||
case noDevice
|
||||
case createService(name: String)
|
||||
case getFromDevice(name: String)
|
||||
case setArgument(name: String)
|
||||
case afc
|
||||
case install
|
||||
case uninstall
|
||||
case lookupApps
|
||||
case detach
|
||||
case attach
|
||||
case functionArguments
|
||||
case profileManage
|
||||
case noConnection
|
||||
case invalidPairingFile
|
||||
case anisetteV1Error(message: String)
|
||||
case provisioningError(result: String, message: String?)
|
||||
case anisetteV3Error(message: String)
|
||||
|
||||
var failureReason: String? {
|
||||
switch self {
|
||||
@@ -64,20 +53,9 @@ enum OperationError: LocalizedError
|
||||
case .openAppFailed(let name): return String(format: NSLocalizedString("SideStore was denied permission to launch %@.", comment: ""), name)
|
||||
case .missingAppGroup: return NSLocalizedString("SideStore's shared app group could not be found.", comment: "")
|
||||
case .maximumAppIDLimitReached: return NSLocalizedString("Cannot register more than 10 App IDs.", comment: "")
|
||||
case .noDevice: return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .createService(let name): return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||
case .getFromDevice(let name): return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
||||
case .setArgument(let name): return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||
case .afc: return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||
case .install: return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
|
||||
case .uninstall: return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
case .lookupApps: return NSLocalizedString("Unable to fetch apps from the device", comment: "")
|
||||
case .detach: return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
||||
case .attach: return NSLocalizedString("Unable to attach to the app's process", comment: "")
|
||||
case .functionArguments: return NSLocalizedString("A function was passed invalid arguments", comment: "")
|
||||
case .profileManage: return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .noConnection: return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
||||
case .invalidPairingFile: return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
|
||||
case .anisetteV1Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V1 server: %@. Try using another anisette server.", comment: ""), message)
|
||||
case .provisioningError(let result, let message): return String(format: NSLocalizedString("An error occurred when provisioning: %@%@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), result, message != nil ? (" (" + message! + ")") : "")
|
||||
case .anisetteV3Error(let message): return String(format: NSLocalizedString("An error occurred when getting anisette data from a V3 server: %@. Please try again. If the issue persists, report it on GitHub Issues!", comment: ""), message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,53 +101,67 @@ enum OperationError: LocalizedError
|
||||
}
|
||||
}
|
||||
|
||||
/// crashes if error is not a MinimuxerError or OperationError
|
||||
func minimuxerToOperationError(_ error: Error) -> OperationError {
|
||||
if let error = error as? MinimuxerError {
|
||||
switch error {
|
||||
extension MinimuxerError: LocalizedError {
|
||||
public var failureReason: String? {
|
||||
switch self {
|
||||
case .NoDevice:
|
||||
return OperationError.noDevice
|
||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .NoConnection:
|
||||
return OperationError.noConnection
|
||||
return NSLocalizedString("Unable to connect to the device, make sure Wireguard is enabled and you're connected to WiFi", comment: "")
|
||||
case .PairingFile:
|
||||
return OperationError.invalidPairingFile
|
||||
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use jitterbugpair to generate it", comment: "")
|
||||
|
||||
case .CreateDebug:
|
||||
return OperationError.createService(name: "debug")
|
||||
case .CreateInstproxy:
|
||||
return OperationError.createService(name: "instproxy")
|
||||
return self.createService(name: "debug")
|
||||
case .LookupApps:
|
||||
return OperationError.getFromDevice(name: "installed apps")
|
||||
return self.getFromDevice(name: "installed apps")
|
||||
case .FindApp:
|
||||
return OperationError.getFromDevice(name: "path to the app")
|
||||
return self.getFromDevice(name: "path to the app")
|
||||
case .BundlePath:
|
||||
return OperationError.getFromDevice(name: "bundle path")
|
||||
return self.getFromDevice(name: "bundle path")
|
||||
case .MaxPacket:
|
||||
return OperationError.setArgument(name: "max packet")
|
||||
return self.setArgument(name: "max packet")
|
||||
case .WorkingDirectory:
|
||||
return OperationError.setArgument(name: "working directory")
|
||||
return self.setArgument(name: "working directory")
|
||||
case .Argv:
|
||||
return OperationError.setArgument(name: "argv")
|
||||
return self.setArgument(name: "argv")
|
||||
case .LaunchSuccess:
|
||||
return OperationError.getFromDevice(name: "launch success")
|
||||
return self.getFromDevice(name: "launch success")
|
||||
case .Detach:
|
||||
return OperationError.detach
|
||||
return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
||||
case .Attach:
|
||||
return OperationError.attach
|
||||
return NSLocalizedString("Unable to attach to the app's process", comment: "")
|
||||
|
||||
case .CreateInstproxy:
|
||||
return self.createService(name: "instproxy")
|
||||
case .CreateAfc:
|
||||
return OperationError.createService(name: "AFC")
|
||||
return self.createService(name: "AFC")
|
||||
case .RwAfc:
|
||||
return OperationError.afc
|
||||
return NSLocalizedString("AFC was unable to manage files on the device", comment: "")
|
||||
case .InstallApp:
|
||||
return OperationError.install
|
||||
return NSLocalizedString("Unable to install the app from the staging directory", comment: "")
|
||||
case .UninstallApp:
|
||||
return OperationError.uninstall
|
||||
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
|
||||
case .CreateMisagent:
|
||||
return OperationError.createService(name: "misagent")
|
||||
return self.createService(name: "misagent")
|
||||
case .ProfileInstall:
|
||||
return OperationError.profileManage
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .ProfileRemove:
|
||||
return OperationError.profileManage
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func createService(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||
}
|
||||
|
||||
fileprivate func getFromDevice(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
||||
}
|
||||
|
||||
fileprivate func setArgument(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||
}
|
||||
return error as! OperationError
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
} catch {
|
||||
return self.finish(.failure(minimuxerToOperationError(error)))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
@@ -41,7 +41,7 @@ final class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
do {
|
||||
try remove_app(resignedBundleIdentifier)
|
||||
} catch {
|
||||
return self.finish(.failure(minimuxerToOperationError(error)))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
DatabaseManager.shared.persistentContainer.performBackgroundTask { (context) in
|
||||
|
||||
@@ -170,6 +170,14 @@ private extension ResignAppOperation
|
||||
infoDictionary[Bundle.Info.exportedUTIs] = exportedUTIs
|
||||
|
||||
try (infoDictionary as NSDictionary).write(to: bundle.infoPlistURL)
|
||||
|
||||
// Remove _CodeSignature folder (if it exists) because it will be added when resigning and it may have files that aren't overwritten when resigning
|
||||
// These files might be the cause of some ApplicationVerificationFailed errors
|
||||
let codeSignaturePath = bundle.bundleURL.appendingPathComponent("_CodeSignature").absoluteString.replacingOccurrences(of: "file://", with: "")
|
||||
if FileManager.default.fileExists(atPath: codeSignaturePath) {
|
||||
try FileManager.default.removeItem(atPath: codeSignaturePath)
|
||||
print("Removed _CodeSignature folder at \(codeSignaturePath)")
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.global().async {
|
||||
|
||||
@@ -50,7 +50,7 @@ final class SendAppOperation: ResultOperation<()>
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
} catch {
|
||||
return self.finish(.failure(minimuxerToOperationError(error)))
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
self.progress.completedUnitCount += 1
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>application-identifier</key>
|
||||
<string>A72ZC8AJ5X.com.SideStore.SideStore</string>
|
||||
<string>XYZ0123456.com.SideStore.SideStore</string>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.siri</key>
|
||||
<true/>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>A72ZC8AJ5X</string>
|
||||
<string>XYZ0123456</string>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.com.SideStore.SideStore</string>
|
||||
@@ -21,7 +21,7 @@
|
||||
<color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="separatorColor" white="1" alpha="0.25" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<label key="tableFooterView" opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SideStore 1.0" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bUR-rp-Nw2">
|
||||
<rect key="frame" x="0.0" y="1082" width="375" height="25"/>
|
||||
<rect key="frame" x="0.0" y="1194" width="375" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -592,7 +592,7 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset Pairing File" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ysS-9s-dXm">
|
||||
<rect key="frame" x="30" y="15.5" width="140" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
@@ -618,9 +618,42 @@
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="e7s-hL-kv9" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1074" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="e7s-hL-kv9" id="yjL-Mu-HTk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Reset adi.pb" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="eds-Dj-36y">
|
||||
<rect key="frame" x="30" y="15.5" width="102" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Next" translatesAutoresizingMaskIntoConstraints="NO" id="0dh-yd-7i9">
|
||||
<rect key="frame" x="327" y="16.5" width="18" height="18"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="0dh-yd-7i9" firstAttribute="centerY" secondItem="yjL-Mu-HTk" secondAttribute="centerY" id="8OI-PI-weT"/>
|
||||
<constraint firstItem="eds-Dj-36y" firstAttribute="leading" secondItem="yjL-Mu-HTk" secondAttribute="leadingMargin" id="BqG-Ef-xQo"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="0dh-yd-7i9" secondAttribute="trailing" id="TFW-nu-jo4"/>
|
||||
<constraint firstItem="eds-Dj-36y" firstAttribute="centerY" secondItem="yjL-Mu-HTk" secondAttribute="centerY" id="YiJ-OF-FXE"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<color key="backgroundColor" white="1" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<edgeInsets key="layoutMargins" top="8" left="30" bottom="8" right="30"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="number" keyPath="style">
|
||||
<integer key="value" value="2"/>
|
||||
</userDefinedRuntimeAttribute>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="isSelectable" value="YES"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="51" id="fj2-EJ-Z98" customClass="InsetGroupTableViewCell" customModule="SideStore" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="1125" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="fj2-EJ-Z98" id="BcT-Fs-KNg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="51"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
||||
@@ -54,6 +54,7 @@ extension SettingsViewController
|
||||
case refreshAttempts
|
||||
case errorLog
|
||||
case resetPairingFile
|
||||
case resetAdiPb
|
||||
case advancedSettings
|
||||
}
|
||||
}
|
||||
@@ -530,6 +531,25 @@ extension SettingsViewController
|
||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||
self.present(alertController, animated: true)
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
case .resetAdiPb:
|
||||
let alertController = UIAlertController(
|
||||
title: NSLocalizedString("Are you sure you want to reset the adi.pb file?", comment: ""),
|
||||
message: NSLocalizedString("The adi.pb file is used to generate anisette data, which is required to log into an Apple ID. If you are having issues with account related things, you can try this. However, you will be required to do 2FA again. This will do nothing if you are using an older anisette server.", comment: ""),
|
||||
preferredStyle: UIAlertController.Style.actionSheet)
|
||||
|
||||
alertController.addAction(UIAlertAction(title: NSLocalizedString("Reset adi.pb", comment: ""), style: .destructive){ _ in
|
||||
if Keychain.shared.adiPb != nil {
|
||||
Keychain.shared.adiPb = nil
|
||||
print("Cleared adi.pb from keychain")
|
||||
}
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
})
|
||||
alertController.addAction(.cancel)
|
||||
//Fix crash on iPad
|
||||
alertController.popoverPresentationController?.sourceView = self.tableView
|
||||
alertController.popoverPresentationController?.sourceRect = self.tableView.rectForRow(at: indexPath)
|
||||
self.present(alertController, animated: true)
|
||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||
case .advancedSettings:
|
||||
// Create the URL that deep links to your app's custom settings.
|
||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// ActivityView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 19.05.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
|
||||
struct ActivityView: UIViewControllerRepresentable {
|
||||
let items: [Any]
|
||||
|
||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
|
||||
return UIActivityViewController(activityItems: items, applicationActivities: nil)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//
|
||||
// AppStoreProductView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import StoreKit
|
||||
|
||||
|
||||
struct AppStoreView: UIViewControllerRepresentable {
|
||||
typealias UIViewControllerType = AppStoreProductViewController
|
||||
|
||||
var isVisible: Binding<Bool>
|
||||
let itunesItemId: Int
|
||||
|
||||
func makeUIViewController(context: Context) -> AppStoreProductViewController {
|
||||
AppStoreProductViewController(isVisible: self.isVisible, itunesId: self.itunesItemId)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
|
||||
if self.isVisible.wrappedValue {
|
||||
uiViewController.presentStoreProduct()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AppStoreProductViewController: UIViewController {
|
||||
|
||||
private var isVisible: Binding<Bool>
|
||||
private let itunesId: Int
|
||||
|
||||
init(isVisible: Binding<Bool>, itunesId: Int) {
|
||||
self.isVisible = isVisible
|
||||
self.itunesId = itunesId
|
||||
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
}
|
||||
|
||||
func presentStoreProduct() {
|
||||
let storeProductViewController = SKStoreProductViewController()
|
||||
storeProductViewController.delegate = self
|
||||
|
||||
let parameters = [SKStoreProductParameterITunesItemIdentifier: self.itunesId]
|
||||
storeProductViewController.loadProduct(withParameters: parameters) { (success, error) -> Void in
|
||||
if let error = error {
|
||||
print("Failed to load App Store product: \(error.localizedDescription)")
|
||||
}
|
||||
guard success else {
|
||||
return
|
||||
}
|
||||
|
||||
self.present(storeProductViewController, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SKStoreProductViewControllerDelegate
|
||||
|
||||
extension AppStoreProductViewController: SKStoreProductViewControllerDelegate {
|
||||
|
||||
func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) {
|
||||
DispatchQueue.main.async {
|
||||
self.isVisible.wrappedValue = false
|
||||
}
|
||||
// viewController.presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
89
AltStore/Views/Onboarding/AppIconsShowcase.swift
Normal file
89
AltStore/Views/Onboarding/AppIconsShowcase.swift
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// AppIconsShowcase.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct AppIconsShowcase: View {
|
||||
|
||||
@State var animationProgress = 0.0
|
||||
@State var animation2Progress = 0.0
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
GeometryReader { proxy in
|
||||
ZStack(alignment: .bottom) {
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.2 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: -0.3*proxy.size.width * self.animationProgress, y: -30)
|
||||
.rotationEffect(.degrees(-20 * self.animationProgress))
|
||||
.shadow(radius: 8 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.25 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: -0.15*proxy.size.width * self.animationProgress, y: -10)
|
||||
.rotationEffect(.degrees(-10 * self.animationProgress))
|
||||
.shadow(radius: 12 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.2 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: self.animationProgress*0.3*proxy.size.width, y: -30)
|
||||
.rotationEffect(.degrees(self.animationProgress*20))
|
||||
.shadow(radius: 8 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.25 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.offset(x: self.animationProgress * 0.15*proxy.size.width, y: -10)
|
||||
.rotationEffect(.degrees(self.animationProgress * 10))
|
||||
.shadow(radius: 12 * self.animationProgress)
|
||||
|
||||
Image(uiImage: UIImage(named: "AppIcon")!)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 0.3 * proxy.size.width)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .circular))
|
||||
.shadow(radius: 16 * self.animationProgress + 8 * self.animation2Progress)
|
||||
.scaleEffect(1.0 + 0.05 * self.animation2Progress)
|
||||
}
|
||||
.frame(maxWidth: proxy.size.width)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
withAnimation(.spring()) {
|
||||
self.animationProgress = 1.0
|
||||
self.animation2Progress = 1.0
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
|
||||
withAnimation(.spring()) {
|
||||
self.animation2Progress = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AppIconsShowcase_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
AppIconsShowcase()
|
||||
.frame(height: 150)
|
||||
}
|
||||
}
|
||||
86
AltStore/Views/Onboarding/OnboardingStepView.swift
Normal file
86
AltStore/Views/Onboarding/OnboardingStepView.swift
Normal file
@@ -0,0 +1,86 @@
|
||||
//
|
||||
// OnboardingStepView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
struct OnboardingStep<Title: View, Hero: View, Content: View, Action: View> {
|
||||
|
||||
@ViewBuilder
|
||||
var title: Title
|
||||
|
||||
@ViewBuilder
|
||||
var hero: Hero
|
||||
|
||||
@ViewBuilder
|
||||
var content: Content
|
||||
|
||||
@ViewBuilder
|
||||
var action: Action
|
||||
}
|
||||
|
||||
|
||||
struct OnboardingStepView<Title: View, Hero: View, Content: View, Action: View>: View {
|
||||
|
||||
@ViewBuilder
|
||||
var title: Title
|
||||
|
||||
@ViewBuilder
|
||||
var hero: Hero
|
||||
|
||||
@ViewBuilder
|
||||
var content: Content
|
||||
|
||||
@ViewBuilder
|
||||
var action: Action
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 64) {
|
||||
self.title
|
||||
.font(.largeTitle.weight(.bold))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
self.hero
|
||||
.frame(height: 150)
|
||||
|
||||
self.content
|
||||
|
||||
Spacer()
|
||||
|
||||
self.action
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct OnboardingStepView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Welcome to")
|
||||
Text("SideStore")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}, hero: {
|
||||
AppIconsShowcase()
|
||||
}, content: {
|
||||
VStack(spacing: 16) {
|
||||
Text("Before you can start sideloading apps, there is some setup to do.")
|
||||
Text("The following setup will guide you through the steps one by one.")
|
||||
Text("You will need a computer (Windows, macOS, Linux) and your Apple ID.")
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Continue") {
|
||||
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
443
AltStore/Views/Onboarding/OnboardingView.swift
Normal file
443
AltStore/Views/Onboarding/OnboardingView.swift
Normal file
@@ -0,0 +1,443 @@
|
||||
//
|
||||
// OnboardingView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 25.02.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CoreData
|
||||
import AltStoreCore
|
||||
import minimuxer
|
||||
import Reachability
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
|
||||
struct OnboardingView: View {
|
||||
enum OnboardingStep: Int, CaseIterable {
|
||||
case welcome, pairing, wireguard, wireguardConfig, addSources, finish
|
||||
}
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
// Temporary workaround for UIKit compatibility
|
||||
var onDismiss: (() -> Void)? = nil
|
||||
|
||||
@State var currentStep: OnboardingStep = .wireguard //.welcome
|
||||
@State private var pairingFileURL: URL? = nil
|
||||
@State private var isWireGuardAppStorePageVisible: Bool = false
|
||||
@State private var isDownloadingWireGuardProfile: Bool = false
|
||||
@State private var wireGuardProfileFileURL: URL? = nil
|
||||
@State private var reachabilityNotifier: Reachability? = nil
|
||||
@State private var isWireGuardTunnelReachable: Bool = false
|
||||
@State private var areTrustedSourcesEnabled: Bool = false
|
||||
@State private var isLoadingTrustedSources: Bool = false
|
||||
|
||||
let pairingFileTypes = UTType.types(tag: "plist", tagClass: UTTagClass.filenameExtension, conformingTo: nil) + UTType.types(tag: "mobiledevicepairing", tagClass: UTTagClass.filenameExtension, conformingTo: UTType.data) + [.xml]
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: self.$currentStep) {
|
||||
welcomeStep
|
||||
.tag(OnboardingStep.welcome)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
pairingView
|
||||
.tag(OnboardingStep.pairing)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
wireguardView
|
||||
.tag(OnboardingStep.wireguard)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
wireguardConfigView
|
||||
.tag(OnboardingStep.wireguardConfig)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
addSourcesView
|
||||
.tag(OnboardingStep.addSources)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
finishView
|
||||
.tag(OnboardingStep.finish)
|
||||
.highPriorityGesture(DragGesture())
|
||||
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
.edgesIgnoringSafeArea(.bottom)
|
||||
.background(Color.accentColor.opacity(0.1).edgesIgnoringSafeArea(.all))
|
||||
.onChange(of: self.currentStep) { step in
|
||||
switch step {
|
||||
case .wireguardConfig:
|
||||
self.startPingingWireGuardTunnel()
|
||||
default:
|
||||
self.stopPingingWireGuardTunnel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func showNextStep() {
|
||||
withAnimation {
|
||||
self.currentStep = OnboardingStep(rawValue: self.currentStep.rawValue + 1) ?? self.currentStep
|
||||
}
|
||||
}
|
||||
|
||||
var welcomeStep: some View {
|
||||
OnboardingStepView {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Welcome to")
|
||||
Text("SideStore")
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
} hero: {
|
||||
AppIconsShowcase()
|
||||
} content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Before you can start sideloading apps, there is some setup to do.")
|
||||
Text("The following setup will guide you through the steps one by one.")
|
||||
Text("You will need a computer (Windows, macOS, Linux) and your Apple ID.")
|
||||
}
|
||||
} action: {
|
||||
SwiftUI.Button("Continue") {
|
||||
self.showNextStep()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
}
|
||||
}
|
||||
|
||||
var pairingView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Pair your Device")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .link)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("SideStore supports sideloading even on non-jailbroken devices.")
|
||||
Text("For it to work, you have to generate a pairing file as described [here in our documentation](https://wiki.sidestore.io/guides/install#pairing-process).")
|
||||
Text("Once you have the `<UUID>.mobiledevicepairing`, import it using the button below.")
|
||||
}
|
||||
}, action: {
|
||||
ModalNavigationLink("Select Pairing File") {
|
||||
DocumentPicker(selectedUrl: self.$pairingFileURL,
|
||||
supportedTypes: self.pairingFileTypes.map { $0.identifier })
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
.onChange(of: self.pairingFileURL) { newValue in
|
||||
guard let url = newValue else {
|
||||
return
|
||||
}
|
||||
|
||||
self.importPairingFile(url: url)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var wireguardView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Download WireGuard")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .icloudAndArrowDown)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("To sideload and sign app on-device without the need of a computer program like SideServer, a local WireGuard connection is required.")
|
||||
Text("This connection is strictly local-only and does not connect to a server on the internet.")
|
||||
Text("First, download WireGuard from the App Store (free).")
|
||||
}
|
||||
}, action: {
|
||||
AppStoreView(isVisible: self.$isWireGuardAppStorePageVisible, itunesItemId: 1441195209)
|
||||
.frame(width: .zero, height: .zero)
|
||||
|
||||
VStack {
|
||||
SwiftUI.Button("Show in App Store") {
|
||||
self.isWireGuardAppStorePageVisible = true
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
|
||||
SwiftUI.Button("Continue") {
|
||||
self.showNextStep()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
var wireguardConfigView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Enable the WireGuard Tunnel")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .network)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Once WireGuard is installed, a configuration file has to be installed in the WireGuard app.")
|
||||
Text("Tap the button below and open the downloaded file in the WireGuard app.")
|
||||
Text("Then, activate the VPN tunnel to continue.")
|
||||
}
|
||||
}, action: {
|
||||
VStack {
|
||||
SwiftUI.Button("Download and Install Configuration File") {
|
||||
self.downloadWireGuardProfile()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle(isLoading: self.isDownloadingWireGuardProfile))
|
||||
.sheet(item: self.$wireGuardProfileFileURL) { fileURL in
|
||||
ActivityView(items: [fileURL])
|
||||
}
|
||||
|
||||
SwiftUI.Button(self.isWireGuardTunnelReachable ? "Continue" : "Waiting for connection...",
|
||||
action: self.showNextStep)
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
.disabled(!self.isWireGuardTunnelReachable)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var addSourcesView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Add Sources")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .booksVertical)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("All apps are provided through sources, which anyone can create and share with the world.")
|
||||
Text("We have compiled a list of trusted sources for SideStore which you can enable to start sideloading your favorite apps.")
|
||||
Text("By default, only the source containing SideStore itself is enabled.")
|
||||
|
||||
Toggle("Enable Trusted Sources", isOn: $areTrustedSourcesEnabled)
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Continue") {
|
||||
self.setupTrustedSources()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle(isLoading: self.isLoadingTrustedSources))
|
||||
.disabled(self.isLoadingTrustedSources)
|
||||
})
|
||||
}
|
||||
|
||||
var finishView: some View {
|
||||
OnboardingStepView(title: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Setup Completed")
|
||||
}
|
||||
}, hero: {
|
||||
Image(systemSymbol: .checkmark)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.accentColor)
|
||||
.shadow(color: .accentColor.opacity(0.8), radius: 12)
|
||||
}, content: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Congratulations, you did it! 🎉")
|
||||
Text("You can start your sideloading journey.")
|
||||
}
|
||||
}, action: {
|
||||
SwiftUI.Button("Let's Go") {
|
||||
self.finishOnboarding()
|
||||
}
|
||||
.buttonStyle(FilledButtonStyle())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func importPairingFile(url: URL) {
|
||||
let isSecuredURL = url.startAccessingSecurityScopedResource() == true
|
||||
|
||||
do {
|
||||
// Read to a string
|
||||
let data = try Data(contentsOf: url)
|
||||
let pairing_string = String(bytes: data, encoding: .utf8)
|
||||
if pairing_string == nil {
|
||||
// TODO: Show error message
|
||||
debugPrint("Unable to read pairing file")
|
||||
// displayError("Unable to read pairing file")
|
||||
}
|
||||
|
||||
// Save to a file for next launch
|
||||
let filename = "ALTPairingFile.mobiledevicepairing"
|
||||
let fm = FileManager.default
|
||||
let documentsPath = fm.documentsDirectory.appendingPathComponent("/\(filename)")
|
||||
try pairing_string?.write(to: documentsPath, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
// Start minimuxer now that we have a file
|
||||
start_minimuxer_threads(pairing_string!)
|
||||
|
||||
// Show the next onboarding step
|
||||
self.showNextStep()
|
||||
|
||||
} catch {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
}
|
||||
|
||||
if (isSecuredURL) {
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
|
||||
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)"))
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
debugPrint("minimuxer failed to start, please restart SideStore.", error)
|
||||
// displayError("minimuxer failed to start, please restart SideStore. \((error as? LocalizedError)?.failureReason ?? "UNKNOWN ERROR!!!!!! REPORT TO GITHUB ISSUES!")")
|
||||
}
|
||||
start_auto_mounter(documentsDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func downloadWireGuardProfile() {
|
||||
let profileDownloadUrl = "https://github.com/SideStore/SideStore/releases/download/0.3.1/SideStore.conf"
|
||||
let destinationUrl = FileManager.default.temporaryDirectory.appendingPathComponent("SideStore.conf")
|
||||
|
||||
self.isDownloadingWireGuardProfile = true
|
||||
URLSession.shared.dataTask(with: URLRequest(url: URL(string: profileDownloadUrl)!)) { data, response, error in
|
||||
|
||||
defer { self.isDownloadingWireGuardProfile = false }
|
||||
|
||||
if let error {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
return
|
||||
}
|
||||
|
||||
guard let response = response as? HTTPURLResponse, 200..<300 ~= response.statusCode, let data else {
|
||||
// TODO: Show error message
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
try data.write(to: destinationUrl)
|
||||
self.wireGuardProfileFileURL = destinationUrl
|
||||
} catch {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
return
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
func startPingingWireGuardTunnel() {
|
||||
do {
|
||||
self.reachabilityNotifier = try Reachability(hostname: "10.7.0.1")
|
||||
self.reachabilityNotifier?.whenReachable = { _ in
|
||||
self.isWireGuardTunnelReachable = true
|
||||
}
|
||||
self.reachabilityNotifier?.whenUnreachable = { _ in
|
||||
self.isWireGuardTunnelReachable = false
|
||||
}
|
||||
|
||||
try self.reachabilityNotifier?.startNotifier()
|
||||
} catch {
|
||||
// TODO: Show error message
|
||||
debugPrint(error)
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
}
|
||||
}
|
||||
|
||||
func stopPingingWireGuardTunnel() {
|
||||
self.reachabilityNotifier?.stopNotifier()
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func setupTrustedSources() {
|
||||
guard self.areTrustedSourcesEnabled else {
|
||||
return self.showNextStep()
|
||||
}
|
||||
|
||||
self.isLoadingTrustedSources = true
|
||||
|
||||
AppManager.shared.fetchTrustedSources { result in
|
||||
|
||||
switch result {
|
||||
case .success(let trustedSources):
|
||||
// Cache trusted source IDs.
|
||||
UserDefaults.shared.trustedSourceIDs = trustedSources.map { $0.identifier }
|
||||
|
||||
// Don't show sources without a sourceURL.
|
||||
let featuredSourceURLs = trustedSources.compactMap { $0.sourceURL }
|
||||
|
||||
// This context is never saved, but keeps the managed sources alive.
|
||||
let context = DatabaseManager.shared.persistentContainer.newBackgroundSavingViewContext()
|
||||
|
||||
let dispatchGroup = DispatchGroup()
|
||||
for sourceURL in featuredSourceURLs {
|
||||
dispatchGroup.enter()
|
||||
|
||||
AppManager.shared.fetchSource(sourceURL: sourceURL, managedObjectContext: context) { result in
|
||||
dispatchGroup.leave()
|
||||
}
|
||||
}
|
||||
|
||||
dispatchGroup.notify(queue: .main) {
|
||||
self.isLoadingTrustedSources = false
|
||||
|
||||
// Save the fetched trusted sources
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
}
|
||||
|
||||
self.showNextStep()
|
||||
}
|
||||
case .failure(let error):
|
||||
NotificationManager.shared.reportError(error: error)
|
||||
self.isLoadingTrustedSources = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingView {
|
||||
func finishOnboarding() {
|
||||
// Set the onboarding complete flag
|
||||
UserDefaults.standard.onboardingComplete = true
|
||||
|
||||
if let onDismiss {
|
||||
onDismiss()
|
||||
} else {
|
||||
self.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct OnboardingView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ForEach(OnboardingView.OnboardingStep.allCases, id: \.self) { step in
|
||||
Color.red
|
||||
.ignoresSafeArea()
|
||||
.sheet(isPresented: .constant(true)) {
|
||||
OnboardingView(currentStep: step)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,12 @@ public class Keychain
|
||||
@KeychainItem(key: "patreonAccountID")
|
||||
public var patreonAccountID: String?
|
||||
|
||||
@KeychainItem(key: "identifier")
|
||||
public var identifier: String?
|
||||
|
||||
@KeychainItem(key: "adiPb")
|
||||
public var adiPb: String?
|
||||
|
||||
private init()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public extension UserDefaults
|
||||
}()
|
||||
|
||||
@NSManaged var firstLaunch: Date?
|
||||
@NSManaged var onboardingComplete: Bool
|
||||
@NSManaged var requiresAppGroupMigration: Bool
|
||||
@NSManaged var textServer: Bool
|
||||
@NSManaged var textInputAnisetteURL: String?
|
||||
@@ -46,6 +47,7 @@ public extension UserDefaults
|
||||
@NSManaged var patronsRefreshID: String?
|
||||
|
||||
@NSManaged var trustedSourceIDs: [String]?
|
||||
@NSManaged var trustedServerURL: String?
|
||||
|
||||
var activeAppsLimit: Int? {
|
||||
get {
|
||||
@@ -77,6 +79,7 @@ public extension UserDefaults
|
||||
#keyPath(UserDefaults.isDevModeEnabled): false,
|
||||
#keyPath(UserDefaults.isConsoleEnabled): false,
|
||||
#keyPath(UserDefaults.isDebugLoggingEnabled): false,
|
||||
#keyPath(UserDefaults.onboardingComplete): false,
|
||||
#keyPath(UserDefaults.isBackgroundRefreshEnabled): true,
|
||||
#keyPath(UserDefaults.isLegacyDeactivationSupported): isLegacyDeactivationSupported,
|
||||
#keyPath(UserDefaults.activeAppLimitIncludesExtensions): activeAppLimitIncludesExtensions,
|
||||
|
||||
2
Makefile
2
Makefile
@@ -170,7 +170,7 @@ build:
|
||||
|
||||
fakesign:
|
||||
rm -rf archive.xcarchive/Products/Applications/SideStore.app/Frameworks/AltStoreCore.framework/Frameworks/
|
||||
ldid -SAltStore/Resources/tempEnt.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
ldid -SAltStore/Resources/ReleaseEntitlements.plist archive.xcarchive/Products/Applications/SideStore.app/SideStore
|
||||
|
||||
ipa:
|
||||
mkdir Payload
|
||||
|
||||
Reference in New Issue
Block a user